CursosEmpresasBlogLiveConfPrecios

Depurando errores del juego

Clase 20 de 24 • Taller Práctico de JavaScript: ¡Crea tu Primer Videojuego!

Clase anteriorSiguiente clase

Contenido del curso

Introducción
  • 1
    Programemos un juego con JavaScript

    Programemos un juego con JavaScript

    17:13
Canvas
  • 2
    ¿Qué es canvas en JavaScript?

    ¿Qué es canvas en JavaScript?

    18:37
  • 3
    Tamaño del canvas y sus elementos

    Tamaño del canvas y sus elementos

    17:37
  • 4
    Canvas responsive

    Canvas responsive

    12:37
Mapa del juego
  • 5
    ¿Qué es un array bidimensional?

    ¿Qué es un array bidimensional?

    05:48
  • 6
    Arreglos multidimensionales en JavaScript

    Arreglos multidimensionales en JavaScript

    23:38
  • 7
    Refactor del mapa de juego

    Refactor del mapa de juego

    20:48
Movimientos del jugador
  • 8
    Eventos y botones

    Eventos y botones

    14:14
  • 9
    Objeto playerPosition

    Objeto playerPosition

    11:34
  • 10
    Limpieza de movimientos

    Limpieza de movimientos

    09:53
  • 11
    No te salgas del mapa

    No te salgas del mapa

    11:15
Colisiones
  • 12
    Detectando colisiones fijas

    Detectando colisiones fijas

    13:29
  • 13
    Detectando colisiones con arrays

    Detectando colisiones con arrays

    14:11
  • 14
    Victoria: subiendo de nivel

    Victoria: subiendo de nivel

    07:50
  • 15
    Derrota: perdiendo vidas

    Derrota: perdiendo vidas

    11:45
Bonus: adictividad
  • 16
    Sistema de vidas y corazones

    Sistema de vidas y corazones

    13:02
  • 17
    Sistema de tiempo y puntajes

    Sistema de tiempo y puntajes

    19:30
  • 18
    ¿Qué es localStorage?

    ¿Qué es localStorage?

    07:33
  • 19
    Guardando records del jugador

    Guardando records del jugador

    18:35
Deploy
  • 20
    Depurando errores del juego

    Depurando errores del juego

    23:16
  • 21
    Desplegando el juego a GitHub Pages

    Desplegando el juego a GitHub Pages

    07:39
Próximos pasos
  • 22
    Reto: reinicio del juego

    Reto: reinicio del juego

    03:36
  • 23
    Reto: timeouts de victoria o derrota

    Reto: timeouts de victoria o derrota

    04:14
  • 24
    ¿Quieres un simulador laboral?

    ¿Quieres un simulador laboral?

    06:53
    marco antonio

    marco antonio

    student•
    hace 3 años

    si esta complicado mover la pantalla mientras juegas XD

    no mas por que mi compu no me aguanta el obs si no grabaría como queda con las animaciones mientras juego XD en fin.
    si quieren evitar un dolor de cabeza con las posiciones y las colisiones, animaciones ...o si le puse animaciones a esto cuando explotan las bombas y cuando mueres y mas cosas locas como sea esta es la solución para hacerles la vida mas sencilla

    corror.jpg

    es tan fácil como transformar el mapa en un array lineal y solo crear las funciones de posX y poxY usando el índice y YA no hay mas es los mas fácil del mundo en fin espero le sirva a alguien

      Andriw Jose Rollo Castro

      Andriw Jose Rollo Castro

      student•
      hace 3 años

      Perfecto. Lo hice de esta forma: en cada uno de los objetos (playerPosition, giftPosition, etc) guardo únicamente el índice. De esta forma siempre tengo una matriz de índices 10x10. La variable element size me sirve únicamente cuando voy a renderizar los emojis. Así también nos ahorramos el problema de los decimales, pues las posiciones que se comparan son número enteros del 1 al 10 (en realidad del 0 al 9 por como contamos en programación).

    Antonio Mora

    Antonio Mora

    student•
    hace 3 años

    Yo lo solucioné creando otra objeto para la posición del jugador

    Captura de pantalla de 2022-08-25 21-27-34.png
    Utilizo el objeto playerPostion para guardar el índice de la 🚪 y mi funciónmoverPlayer quedo así:
    Captura de pantalla de 2022-08-25 21-29-40.png
    Por último, por cada movimiento con las teclas o los botones solo aumento en uno el valor de playerPostion

    Les comparto mi código para que lo puedan verificar.

    Screenshot 2022-08-25 at 21-32-49 YzabKMj.png

      Wilkins Bernardo Brito Serrano

      Wilkins Bernardo Brito Serrano

      student•
      hace 3 años

      Antonio quise aplicar tu solución en mi código pero te cuento que al cambiar las constantes de la forma que tu lo hiciste mi calavera se escondió no me paraece por ningun lado o bueno debe estar en otro extremo del canvas que no lo puedo ver en la pantalla

    Eliezer Hernandez

    Eliezer Hernandez

    student•
    hace 3 años

    Reposicionar al player al cambiar el tamaño de la viewport

    Solución escrita:

    • Tuve que guardar el tamaño anterior del canvas, en mi caso toda la información del canvas estaba guardada globalmente en un objeto identificado como objCanvas, por lo que solo tuve que almacenarlo al inicio de la función setCanvasSize, antes de que se le asignara un nuevo tamaño al canvas. Al nuevo atributo lo llamé “oldSize”:
      Captura de pantalla de 2022-09-14 08-51-53.png
      .
    • Para reposicionar al player desarrolle una función que calculara los porcentajes equivalentes a las posiciones X e Y del player, a partir del tamaño anterior del canvas(oldSize). Luego ambos porcentajes llevarlos a su medida equivalente con el nuevo tamaño del canvas, lo que será suficiente para reposicionar al player. Para calcular las medidas equivalentes y porcentajes equivalentes solo utilicé la regla de tres.
      carbon (2).png
      .
    • El llamado de la nueva función (repositionPlayer) se ejecutó al final de la función setCanvasSize antes de renderizar, dibujar o mostrará el juego.
      Wilkins Bernardo Brito Serrano

      Wilkins Bernardo Brito Serrano

      student•
      hace 3 años

      Excelente, ¿podrías pasarme tu repositorio?

      Eliezer Hernandez

      Eliezer Hernandez

      student•
      hace 3 años

      @wilkinsbrito Hola, como estas? Gracias por comentar aca tienes el link del repositorio: https://github.com/eliezersam93/Taller-practico-javascript-videogames No es muy detallado en los commits, ya que este taller lo realice solo por curiosidad, porque había realizado los cursos en sus versiones anteriores, pero aun así espero que te sea de ayuda. No dudes en escribir si requieres algo mas.

    Tommy Toala Cox

    Tommy Toala Cox

    student•
    hace 3 años

    La verdad fui resolviendo todos esos bugs mientras seguía el curso, y de hecho ahí faltan mencionar; Que el tiempo que agarra para ponerlo en el record, está un poco desfasado, por unos segundo, por lo que hay que hacer unos pequeños ajuste. Así quedo mi repo https://estrelladlm.github.io/game-with-js/

      Wilkins Bernardo Brito Serrano

      Wilkins Bernardo Brito Serrano

      student•
      hace 3 años

      Súper!!! ¿me podrías pasar tu repositorio para checar tu código porque a mi no me funciona ni siquiera el código que me descargué de esta clase que se supone que es la solución me funciona es como si lo hubiesen subido con el mismo error del video. Qué frustante!!

      Tommy Toala Cox

      Tommy Toala Cox

      student•
      hace 3 años

      Hola @wilkinsbrito, qué tal? Lo lograste?, coméntamelo! Aquí te dejo mi repo

      https://github.com/EstrellaDLM/game-with-js Espero te sea de ayuda, y estoy al pendiente xD.

      Cualquier cosa, ese repo lo abandoné hace un tiempo

    Matias Martel

    Matias Martel

    student•
    hace 3 años

    Bueno vamos con los errores que no he visto aun solucionados o que puedo ofrecer una solucion alternativa.

    Bug # 1 decimales incorrectos al reajustar el canvasSize:

    function resizeCanvas () { windowHeight = window.innerHeight * 0.8; windowWidth = window.innerWidth * 0.8; //Dependiendo del tamaño de la pantalla, va a colocar el tamaño cuadrado del canvas //Al dividir entre 10 y luego aproximar el valor a un entero garantiza que el canvas será un entero múltiplo de 10. Finalmente se multiplica la expresión por 10 para obtener el dato real del canvas //Con Math.ceil nos ahorramos el problema de los decimales if (window.innerHeight > window.innerWidth) { if ((windowWidth % 10) !== 0) { canvasSize = Math.ceil(windowWidth / 10) * 10; } else { canvasSize = windowWidth; }} else { if ((windowHeight % 10) !== 0) { canvasSize = Math.ceil(windowHeight / 10) * 10; } else { canvasSize = windowHeight; } } canvas.setAttribute('width', canvasSize); canvas.setAttribute('height', canvasSize); elementsSize = (canvasSize / 10); startGame(lvl); }

    En vez de ponerme a retocar el valor elementalSize recortando decimales me decidi por ir a la raiz del error, el canvasSize, aplicando lo mostrado arriba en los casos de resoluciones extrañas que probe el valor de elementsSize en todo momento es un entero por lo que no genera ningun tipo de problema en las coordenadas.

    /////////////

    Bug #2 Al perder todas las vidas y volver a comenzar sin refrescar la pagina el juego no termina el timer al llegar a la meta del ultimo mapa:

    function bombsDetection () { //Buscamos una coincidencia en el array contenido en la propiedad lvl(ej. 0) y en caso de que la posicion del jugador coincida con alguna de las bombas reseteamos su posicion al inicio del mapa bombsDetected = bombsPosition[lvl].find(bomb => bomb[0] == playerPosition.x && bomb[1] == playerPosition.y); if (bombsDetected) { console.log("collision detected") lives--; if (lives <= 0) { lvl = 0; lives = 3; timeStart = undefined; clearInterval(timeInterval) } playerPosition.x = undefined; playerPosition.y = undefined; startGame(lvl); } }

    Este fue muy sencillo de resolver pero raro de encontrar. Debemos buscar en nuestro codigo donde reseteamos el juego si perdemos todas las vidas y observar que cuando sucede eso nosotros establecemos como undefined a la variable timeStart, pero ademas debemos agregarle un clearInterval para volver a comenzar desde cero el tiempo del juego ya que sino el juego abre otra instancia de timeInterval y esa no podemos detenerla al llegar a la meta, lo cual seria el comportamiento normal de nuesto juego.

    /////////////

    Bug #3 Desfasaje entre el tiempo en que completamos el juego y el record que seteamos en localStorage.

    Si analizamos nuestro codigo, durante la clase definimos una variable llamada: timePlayer, la cual no manipulamos en todo el ciclo de ejecucion:

    let lvl = 0; let lives = 3; let timeStart; let timePlayer; let timeInterval;

    Bueno esa variable utilizaremos para alojar el contenido que insertaremos en el local storage de la siguiente manera:

    En la parte visual:

    function timeSystem() { timePlayer = formatTime(Date.now() - timeStart); spanTime.innerHTML = timePlayer; } function showRecord(){ spanRecord.innerHTML= localStorage.getItem('record_time'); }

    Y dentro de la logica:

    function gameWin() { console.log('¡Terminaste el juego!'); clearInterval(timeInterval) const recordTime = localStorage.getItem('record_time'); const playerTime = timePlayer; if (recordTime) { if (recordTime >= playerTime) { localStorage.setItem('record_time', playerTime); pResult.innerHTML = 'Superaste el record'; } else { pResult.innerHTML = 'No superaste el record'; } } else { localStorage.setItem('record_time', playerTime); pResult.innerHTML = 'Nuevo record, ahora trata de superarlo'; } showRecord() }

    Si bien de la manera que vimos en clase trabajamos con el mismo valor, asumo que lo consulta con un pequeño desfasaje al momento de setear el visual y setar el localStorage, haciendolo de esta forma nos aseguramos que tanto uno como otro consulten el mismo valor (timePlayer).

      Oscar Israel Román Quispe

      Oscar Israel Román Quispe

      student•
      hace 2 años

      ¡Muchas gracias! Sí funcionaron =D

      Julian Avila

      Julian Avila

      student•
      hace un año

      Gracias matias.

    Angel David Velasco Bonifaz

    Angel David Velasco Bonifaz

    student•
    hace 3 años

    Para solucionar un poco los errores de los decimales, se le puede poner un toFixed(0) al ancho y alto del canvas.

      Agustin Orcha Mata

      Agustin Orcha Mata

      student•
      hace 3 años

      Me salvaste la vida, porque me estaba volviendo loco :)

    Manuel Araujo

    Manuel Araujo

    student•
    hace 3 años

    Creo 2 variables:

    let ultima_x; let ultima_y;

    Dentro de la función movePlayer, le asigno a la variable ultima_x el valor de playerPosition.x, y a la variable ultima_y le asigno el valor de playerPosition.y.

    ultima_x = playerPosition.x; ultima_y = playerPosition.y;

    Entonces dentro de la funcion setCanvasSize le asigno a playerPosition.x y playerPosition.y los últimos valores que tuvieron y que capturé en movePlayer:

    playerPosition.x = ultima_x; playerPosition.y = ultima_y;

    Esto funciona si se hace resize de una sola dimensión a la vez, ya que en este caso las dimensiones internas del canvas se mantienen constantes. Pero si se hace resize en ambas dimensiones de la pantalla al mismo tiempo, la dimensión interna del canvas cambia constantemente y el player se desplaza. Para resolver esto último, creo una tercera variable

    let canvasSize1;

    A la cual le asigno un valor con el mismo condicional del canvasSize original pero dentro de la función movePlayer

    if(window.innerHeight > window.innerWidth) { canvasSize1 = window.innerWidth * 0.7; } else { canvasSize1 = window.innerHeight * 0.7; }

    Ahora dentro de la función setCanvasSize, modifico lo que había escrito, colocando la asignación de valores que había hecho dentro de la condición de que ambos valores de dimension del canvas sean iguales, pero que si la dimension interna del canvas cambió, entonces se utilce un factor de ajuste

    if(canvasSize == canvasSize1) { playerPosition.x = ultima_x; playerPosition.y = ultima_y; } else { playerPosition.x = ultima_x*(canvasSize/canvasSize1); playerPosition.y = ultima_y*(canvasSize/canvasSize1); }
    Laura Roa

    Laura Roa

    student•
    hace 3 años

    cambie unos textos y variables a ingles! y asi fue como con la funcion fixNumber() arregle el codigo que el profe menciono :)

    index.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="./styles.css"> <title>VideoGame Project</title> </head> <body> <div class="game-container"> <canvas id="game"></canvas> <div class="btns"> <button id="up">Up</button> <button id="left">Left</button> <button id="right">Right</button> <button id="down">Down</button> </div> <div class="messages"> <p>lives: <span id="lives"></span></p> <p>Time: ⏰: <span id="time"></span></p> <p>Record: 🏁<span id="record"></span></p> <p id="result"></p> </div> </div> <script src="./maps.js"></script> <script src="./game.js"></script> </body> </html>

    game.js

    const canvas = document.querySelector('#game'); const game = canvas.getContext('2d'); const upButton = document.querySelector ('#up'); const rightButton = document.querySelector ('#right'); const leftButton = document.querySelector ('#left'); const downButton = document.querySelector ('#down'); const spanLives = document.querySelector('#lives') const spanTime = document.querySelector('#time'); const spanRecord = document.querySelector('#record'); const pResult = document.querySelector('#result'); let canvasSize; let elementsSize; let level = 0; let lives = 3; let timeStart; let timePlayer; let timeInterval; const playerPosition = { x: undefined, y: undefined, }; const giftPosition = { x: undefined, y: undefined, }; let enemyPositions = []; window.addEventListener('load', setCanvasSize); window.addEventListener('resize', setCanvasSize); function fixNumber(n) { return Number(n.toFixed(0)); } function setCanvasSize() { if (window.innerHeight > window.innerWidth) { canvasSize = window.innerWidth * 0.7; } else { canvasSize = window.innerHeight * 0.7; } canvasSize = Number(canvasSize.toFixed(0)); canvas.setAttribute('width', canvasSize); canvas.setAttribute('height', canvasSize); elementsSize = fixNumber(canvasSize / 10); playerPosition.x = undefined; playerPosition.y = undefined; startGame(); } function startGame() { console.log({ canvasSize, elementsSize }); console.log(window.innerWidth, window.innerHeight); game.font = elementsSize + 'px Verdana'; game.textAlign = 'end'; const map = maps[level]; if (!map) { gameWin(); return; } if (!timeStart) { timeStart = Date.now(); timeInterval = setInterval(showTime, 100); showRecord(); } const mapRows = map.trim().split('\n'); const mapRowCols = mapRows.map(row => row.trim().split('')); console.log({map, mapRows, mapRowCols}); showLives(); enemyPositions = []; game.clearRect(0, 0, canvasSize, canvasSize); mapRowCols.forEach((row, rowI) => { row.forEach((col, colI) => { const emoji = emojis[col]; const posX = fixNumber(elementsSize * (colI + 1)); const posY = fixNumber(elementsSize * (rowI + 1)); if (col == 'O') { if (!playerPosition.x && !playerPosition.y) { playerPosition.x = fixNumber(posX); playerPosition.y = fixNumber(posY); console.log({playerPosition}); } } else if (col == 'I') { giftPosition.x = fixNumber(posX); giftPosition.y = fixNumber(posY); } else if(col == 'X') { enemyPositions.push ({ x: fixNumber(posX), y: fixNumber(posY), }); } game.fillText(emoji, posX, posY); }); }); movePlayer(); } function movePlayer() { const giftCollisionX = playerPosition.x.toFixed(3) == giftPosition.x.toFixed(3); const giftCollisionY = playerPosition.y.toFixed(3) == giftPosition.y.toFixed(3); const giftCollision = giftCollisionX && giftCollisionY; if(giftCollision) { winningLevel(); } const enemyCollision = enemyPositions.find(enemy => { const enemyCollisionX = enemy.x.toFixed(3) == playerPosition.x.toFixed(3); const enemyCollisionY = enemy.y.toFixed(3) == playerPosition.y.toFixed(3); return enemyCollisionX && enemyCollisionY; }); if(enemyCollision) { levelFail(); } game.fillText(emojis['PLAYER'], playerPosition.x, playerPosition.y); } function winningLevel() { console.log('Level up'); level ++; startGame(); } function levelFail() { console.log("ENEMY!") lives--; console.log(lives) if(lives <= 0) { level = 0; lives = 3; timeStart= undefined; } playerPosition.x = undefined; playerPosition.y = undefined; startGame(); } function gameWin() { console.log('¡Terminaste el juego!'); clearInterval(timeInterval); const recordTime = localStorage.getItem('record_time'); const playerTime = Date.now() - timeStart; if (recordTime) { if (recordTime >= playerTime) { localStorage.setItem('record_time', playerTime) pResult.innerHTML ='you made a new record! Congratulations!'; } else { pResult.innerHTML ='Sorry, this is not a record'; } } else { localStorage.setItem('record_time', playerTime) pResult.innerHTML = 'first time playing? Try to get a new record'; } console.log({recordTime, playerTime}); } function showLives() { const heartsArray = Array(lives).fill(emojis['HEART']); console.log(heartsArray); spanLives.innerHTML = ""; heartsArray.forEach(heart => spanLives.append(heart)) } function showTime() { spanTime.innerHTML = Date.now() - timeStart; } function showRecord() { spanRecord.innerHTML = localStorage.getItem('record_time'); } //rowI and colI are the index // for (let row = 1; row <= 10; row++) { // for (let col = 1; col <= 10; col++) { // game.fillText(emojis[mapRowCols[row - 1][col - 1]], elementsSize * col, elementsSize * row); // } // } window.addEventListener('keydown', moveByKeys); upButton.addEventListener('click', moveUp); rightButton.addEventListener('click', moveRight); leftButton.addEventListener('click', moveLeft); downButton.addEventListener('click', moveDown); function moveByKeys (event) { if(event.key == 'ArrowUp') moveUp(); else if (event.key == 'ArrowRight') moveRight(); else if (event.key == 'ArrowLeft') moveLeft(); else if (event.key == 'ArrowDown') moveDown(); } //console.log(event) function moveUp() { console.log('Moving up'); if ((playerPosition.y - elementsSize) < elementsSize) { console.log('OUT'); } else { playerPosition.y -= elementsSize; startGame(); } } function moveLeft() { console.log('moving left'); if ((playerPosition.x - elementsSize) < 20) { console.log('OUT'); } else { playerPosition.x -= elementsSize; startGame(); } } function moveRight() { console.log('moving right'); if ((playerPosition.x + elementsSize) > canvasSize) { console.log(elementsSize) console.log('OUT'); } else { playerPosition.x += elementsSize; startGame(); } } function moveDown() { console.log('moving down'); if ((playerPosition.y + elementsSize) > canvasSize) { console.log('OUT'); } else { playerPosition.y += elementsSize; startGame(); } }
    David Fernando Estacio Quiroz

    David Fernando Estacio Quiroz

    student•
    hace 3 años

    Yo... desde el principio me di cuenta que tendríamos problemas con los decimales y los pixeles. Mi solución fue guardar los indices de los ciclos cuando recorremos el mapa para manejar el canvas como una matriz 10*10

    rows.forEach((row, j) => { columns[j].forEach((key, i) => { emoji = emojis[key]; // Position on pixels posX = Math.floor(elementSize * (i + 1)); posY = Math.floor(elementSize * (j + 1)); game.fillText(emoji, posX, posY); // Set origin position to render the player in the very first position if (!playerPosition.X && key == 'O') { playerPosition.X = i + 1; playerPosition.Y = j + 1; } }); });

    Y ya luego es más sencillo todo la verdad, simplemente renderizar al jugador:

    function renderPlayer() { //console.log(playerPosition) game.fillText( emojis['PLAYER'], // To draw the player we need to calculate the position on pixels Math.floor(elementSize * playerPosition.X + 1), Math.floor(elementSize * playerPosition.Y + 1) ); }

    Y en la función de movimiento usamos un switch para comparar el emoji basados en las filas y columnas de nuestro array bidimendional

    switch (columns[newRow-1][newColumn-1]) { case 'O': //console.log('Returned to the start'); (level-1 < 0) ? level=level : level-=1; break; case 'X': //console.log('Collided'); lostLive(); // reduce lives return true; case 'I': levelPassed(); break; }
    Ricardo Alfonso Chavez Vilcapoma

    Ricardo Alfonso Chavez Vilcapoma

    student•
    hace 3 años

    Agregué esto 😅

    loselives.PNG
    Miguel Angel Suarez Forero

    Miguel Angel Suarez Forero

    student•
    hace 3 años

    Para actualizar la ubicación del jugador al cambiar el tamaño de la pantalla plantee la siguiente solución:

    1. En el objeto playerPosition cree dos nuevas propiedades row y col, las cuales van a almacenar la fila y columna en la que esta ubicado el jugador.
    const playerPosition = { x: undefined, y: undefined, col: undefined, row: undefined, };
    1. Cree la variable oldElementSize en la cual se almacenara el valor del tamaño anterior, se define en la función setCanvasSize, para realizar una comparación con el tamaño actual y asi poder volver a ubicar al jugador
    if (elementSize) { oldElementSize = elementSize; } else { oldElementSize = (canvasSize / 10); } elementSize = (canvasSize / 10) ;
    1. Por ultimo se hace la comparación del oldElementSize vs elementSize dentro de la función startGame, dentro se actualiza la ubicación del jugador con el nuevo elementSize y la fila y columna en la cual esta ubicado el jugador.
    if (oldElementPos !== elementPos) { playerPosition.x = (elementPos * playerPosition.col) - (elementPos/2) + fixPos; playerPosition.y = (elementPos * playerPosition.row) - fixPos; playerPosition.x = fixNumber(playerPosition.x); playerPosition.y = fixNumber(playerPosition.y); oldElementPos = elementPos; }
    Paula Inés Cudicio

    Paula Inés Cudicio

    student•
    hace 3 años

    https://paulaxam.github.io/taller-practico-js-videojuego/

    Dejo mi jueguito, le cambié un poquito la temática para mandárselo a mi sobrino que tiene 4 añitos a que lo juegue, asique cambié las calaveras y explosiones por un paseo por el bosque. Agregué algunos mapitas, y cambié la estructura del css. De a poquito le iré agregando mas mapas y mejorándolo. Espero les guste!!!

      Juan Castro

      Juan Castro

      teacher•
      hace 3 años

      Ufff. Me encantó la temática y los nuevos niveles. :D

      Paula Inés Cudicio

      Paula Inés Cudicio

      student•
      hace 3 años

      🥰🤩

    Eduard Barrios

    Eduard Barrios

    student•
    hace 3 años

    este fue mi juego al final. https://eduardbarrios.github.io/taller_practico_JS_videogame/

      Juan Castro

      Juan Castro

      teacher•
      hace 3 años

      Me encanta :clap:

    Angel David Velasco Bonifaz

    Angel David Velasco Bonifaz

    student•
    hace 3 años

    Solucione lo de la calavera de la siguiente forma. Hay que tomar la formula de la posicion en x y en y Yo la tengo en +5 y --10 para acomodar las bombas y que no se salgan.del canvas-

    const posX = elementsSize * (col + 1) + 5; const posY = elementsSize * (rowIndex + 1) - 10;

    Bueno vamos a despejar la ubicación de la columna y de la fila donde se encuentra nuestra calavera.

    despejando col y row, me quedaron estas formulas. ahora ya se en que columna y fila se encuentra mi player

    const colPlayer=((playerPosition.x-5)/elementsSize)-1; const rowPlayer=((playerPosition.y+10)/elementsSize)-1;

    y lo que hago es volver a realizar la misma formula de un principio con el nuevo elementSize y lo dibujamos.

    playerPosition.x=elementsSize*(colPlayer+1)+5; playerPosition.y=elementsSize*(rowPlayer+1)-10; game.fillText(emojis["PLAYER"], playerPosition.x, playerPosition.y);

    y ya nos quedara nuestra calavera en la posición que esta.

    César Augusto Cortés Labrada

    César Augusto Cortés Labrada

    student•
    hace 3 años

    Comparto mi solución para corregir el desfase del objeto jugador(calavera) al hacer resize.

    Tengo dos variables globales llamadas:

    let canvasSize; //Esta fue creada desde las primeras clases let reCanvas; //Se guardará el primer canvasSize al cargar la página luego de un resize // También agregué dos propiedades más a al objeto player; lastX y lastY const player = { x: null, y: null, lastX : null, lastY : null, }

    Al principio de la función resizeCanvas(){} agregué el siguiente condicional

    function resizeCanvas(){ if(canvasSize){ reCanvas = canvasSize; //Aquí se guarda el primer tamaño del canvas anterior como expliqué arriba } . . . starGame(); }

    Luego al llamarse starGame() justo dentro del ciclo forEach doble tengo el siguiente condicional:

    starGame(){ . . . map.forEach((row, rowIndex) => { row.forEach((col, colIndex) => { if(player.x || player.y){ if(player.x.toFixed(2) == (elementSize*(colIndex+1.14)).toFixed(2)){ player.lastX = colIndex; } if(player.y.toFixed(2) == (elementSize*(rowIndex+0.88)).toFixed(2)){ player.lastY = rowIndex; } } . . . } }

    Como se puede ver; este condicional entra sólo si player.x o player.y tienen algún valor. Luego va a verificar si el valor de la posición de mi jugador en cada coordenada de la matriz es igual al valor de la coordenada actual dentro del ciclo y de esta manera guardará la posición del jugador dentro de la matriz, en que fila o columna está, y las guarda en player.lastX y player.lastY, ósea, estoy guardando los índices que tenía mi jugador dentro de la matriz.

    Finalmente en la función movePlayer(){} ejecuto el siguiente condicional:

    if(canvasSize > reCanvas || canvasSize < reCanvas){ console.log('entro en 2'); player.y = elementSize*(player.lastY+0.88); player.x = elementSize*(player.lastX+1.14); game.fillText(emojis['PLAYER'], player.x+4, player.y); } else{ //Renderizado de auto game.fillText(emojis['PLAYER'], player.x+4, player.y); }

    Cuando halla un resize el va a comparar si el nuevo tamaño del canvas es mayor o menor al anterior, de ser así, va a guardar la posición en X y Y del jugador haciendo uso de los índices que tenía antes de que se haga resize, luego lo renderiza. Pero no no existe un resize pasará al else{} donde pondrá las coordenadas que normales cuando el usuario mueva al jugador.

    Se que puede no entenderse sin ver el código completo. Lo estaré compartiendo la próxima clase. De todos modos es muy parecido a todo o que se ha hecho hasta ahora.

    Federico Ivan Llano

    Federico Ivan Llano

    student•
    hace 2 años

    Antes de la clase hice algunos cambios y fui arreglando algunas partes visuales dividiendo por 1000 y con toFixed(2) para mostrar el tiempo y el record.

    let diferencia = Date.now() - timeStart; segundos = diferencia /1000; spanTime.innerHTML = segundos.toFixed(2);

    Ademas agregando que un event keydown para que en el momento en que el player presiona una tecla inicie el tiempo.

    let onKeyDown = false; document.body.onkeydown = () => onKeyDown = true; document.body.onmousedown = () => onKeyDown = true; //...... if(!timeStart){ if(onKeyDown){ timeStart = Date.now(); timeInterval = setInterval(showTime, 10) spanResult.innerHTML = ' '; } showRecord() }

    Un titulo que muestra el level en el que estas.

    function showLevel(){ if(!level >0){ spanLevel.innerHTML = level + 1; } else { spanLevel.innerHTML = ` Game Over` } }
    Abigail Nahomi Chávez Lozano

    Abigail Nahomi Chávez Lozano

    student•
    hace 2 años

    Mi solución a ese problema fue solo redondear la resta en el condicional pero solo en la función de mover hacia arriba (porque solo ahi tenia el problema la inicio). No me valia para las démas direcciones. Está mejor solucionar el problema desde su raiz..👍👍

    Andre Huaman Yovera

    Andre Huaman Yovera

    student•
    hace 3 años

    [Link a mi juego :]](https://tathatahy.github.io/taller-practico-javascript-videogames/)

    Libian María Hernández Gil

    Libian María Hernández Gil

    student•
    hace 3 años

    Hola...les comparto.. Mi repositorio: https://github.com/libian19/videogame Mi deploy: https://libian19.github.io/videogame/

    Ivan de jesus Romero leal

    Ivan de jesus Romero leal

    student•
    hace 3 años

    Ok reubicar el player me tomo dos días, tuve que pensar super profundo porque no me funciono las soluciones de los compañeros, así que no voy a pasar el código ya que es mas confuso, esto es para ayudar a los que como yo no les funciono lo que publicaron los demás.

    La manera en que lo hice fue haciendo la regla de tres antes de empezar el start game justo en el resize, sigiendo esta formula:

    (NuevoCanvasSize * playerPosition.x ) / anteriorCanvasSize

    Hacemos esto mismo con el playerPosition.y

    • Pasos
    1. Extrae y metelos en un array cada "canvasSize", porque cuando reajustes el tamaño de la pantalla, se va a borrar del original canvasSize, yo lo extraje desde el startGame() al final. ///////////////////////////////////////////////////////////////
    2. Yo separe la funcion del resize de la funcion del load window, llamando all load window al final de la funcion resize, esto es para que primero se carge la pagina, el canvas y el startGame(), asi que cuando ya con todo cargado y las posiciones establecidas (esto es instantaneo, es imposible que muevas el tamaño de la pagina antes de que esta se haya cargado) ya cuando muevas el tamaño de la pagina, ya esta guardada en un array la posicion del canvas anterior. ///////////////////////////////////////////////////////////////////
    3. En la funcion de resize del canvas, vas a añadir la regla de tres, cuando hagas un moviento de la pagina y el resize se ejecute, ya debes estar "Guardada" la anterior playerPosition.x y playerPosition.y, asi que esas te sirven para la regla de tres, y con el array que guarda los anteriores canvaSize, lo insertas de manera que lea al ultimo elemento, que es el tamaño del canvas anterior al que tienes ahora (Elijo la ultima porque el nuevo canvasSize aun no esta en el array, que esto lo extrae mas abajo)... Y LISTO :), asegurate que llame a la funcion del windows load al final porque esa funcion llama a todas las demas y escribe la nueva posicion

    Entonces que fue lo que hice, sobre escribi la playerPosition X y Y y lo mande a imprimir cada ves que se hace un resize

    Ahora que ya lo detalle como lo hice, aca esta mi codigo

    const canvas = document.querySelector("#game") const game = canvas.getContext('2d') let canvasSize //Cree una variable que un if le dara un valor, esta variable es para el tamaño del canvas let elementsSize let level = 0 let lives = 3 let timeStart let timePlayer let timeInterval const playerPosition = {x: undefined, y: undefined} const giftPosition = {x: undefined, y: undefined} let enemiesPosition = [] let resizeElementXandY let arrayUltimoCanvasSize =[] window.addEventListener('load', setCanvasSize) window.addEventListener('resize', resize) function resize(){ if(window.innerHeight > window.innerWidth){ canvasSize = window.innerWidth * 0.7 //si el alto de la pagina es mas grande que el ancho, el canvas solo va a tomar el 80% del ancho }else if (window.innerHeight < window.innerWidth){ canvasSize = window.innerHeight * 0.7 //si el ancho de la pagina es mas grande que el alto, el canvas solo va a tomar el 80% del alto } playerPosition.x = (canvasSize * playerPosition.x) / arrayUltimoCanvasSize[arrayUltimoCanvasSize.length - 1] playerPosition.y = (canvasSize * playerPosition.y) / arrayUltimoCanvasSize[arrayUltimoCanvasSize.length - 1] console.log(playerPosition.x, playerPosition.y) setCanvasSize() } function setCanvasSize(){ if(window.innerHeight > window.innerWidth){ canvasSize = window.innerWidth * 0.7 //si el alto de la pagina es mas grande que el ancho, el canvas solo va a tomar el 80% del ancho }else if (window.innerHeight < window.innerWidth){ canvasSize = window.innerHeight * 0.7 //si el ancho de la pagina es mas grande que el alto, el canvas solo va a tomar el 80% del alto } canvas.setAttribute('width', canvasSize) canvas.setAttribute('height', canvasSize) elementsSize = canvasSize / 10 // playerPosition.x = undefined // playerPosition.y = undefined arrayUltimoCanvasSize.push(canvasSize) // resizeElementPosition.push(elementsSize) // if (resizeElementPosition){ // let ultimoElementSize = resizeElementPosition[resizeElementPosition.length - 2] // console.log(ultimoElementSize) // } startGame() } function startGame(){ game.font = (elementsSize - 15) + "px Verdana" game.fillStyle ="orange" game.textAlign = "end" const map = maps[level] if(!map){ gameWinAndSetRecord() return } if(!timeStart){ timeStart = Date.now() timeInterval = setInterval(showTime, 100 ) showRecord() } // console.log(timeStart) const mapRows = map.trim().split("\n") const mapRowsSinEspacios = mapRows.map((row) => row.trim().split('')) // console.log(mapRows, mapRowsSinEspacios ) //si con un ForEach recibe un segundo parametro. este regresa la ubicacion en el index de ese elemento enemiesPosition = [] game.clearRect(0, 0, canvasSize, canvasSize) showLives() mapRowsSinEspacios.forEach((row, rowI) => { row.forEach((col, colI) => { let emoji = emojis[col] let posX = elementsSize * (colI + 1) let posY = (elementsSize * (rowI + 1)) // console.log({col, colI, row, rowI, emoji}) if(col == 'O' && playerPosition.x == undefined && playerPosition.y == undefined){ playerPosition.x = posX playerPosition.y = posY console.log({"Aqui debe estar el jugador": 0, posX, posY, playerPosition}) } else if (col === 'I'){ giftPosition.x = posX giftPosition.y = posY // console.log({giftPosition}) } else if (col === 'X'){ enemiesPosition.push({col, positionX: posX, positionY: posY}) // console.log(enemiesPosition) } game.fillText(emoji, posX, posY) game.fillText(emojis["PLAYER"], playerPosition.x, playerPosition.y) }) }); // console.log(enemiesPosition) let tempUltimaPosiciones = [playerPosition.x, playerPosition.y] resizeElementXandY = tempUltimaPosiciones movePlayer() } window.addEventListener('keydown', moveByKeys) const btnUp = document.getElementById('up') const btnRight = document.getElementById('right') const btnLeft = document.getElementById('left') const btnDown = document.getElementById('down') const spanLives = document.getElementById('lives') const spanTime = document.getElementById('tiempo') const spanRecord = document.getElementById('record') const pResult = document.getElementById('result') btnUp.addEventListener('click', moveUp) btnRight.addEventListener('click', moveRight) btnLeft.addEventListener('click', moveLeft) btnDown.addEventListener('click', moveDown) function movePlayer(){ const giftCollisionX = playerPosition.x.toFixed(1) == giftPosition.x.toFixed(1) const giftCollisionY = playerPosition.y.toFixed(1) == giftPosition.y.toFixed(1) const giftCollision = giftCollisionX && giftCollisionY; if(giftCollision){ levelWin() } // game.fillText(emojis["PLAYER"], playerPosition.x, playerPosition.y) // console.log(playerPosition.x, playerPosition.y) ///colicion bombas// const enemyCollision = enemiesPosition.find(enemy => { const enemyCollisionX = (enemy.positionX).toFixed(2) == (playerPosition.x).toFixed(2); const enemyCollisionY = (enemy.positionY).toFixed(2) == (playerPosition.y).toFixed(2); return(enemyCollisionX && enemyCollisionY) }) if(enemyCollision){ levelFail() } } function levelWin(){ console.log("subiste de nivel") level++ return startGame() } function levelFail(){ console.log('Chocaste con un enemigo :(') playerPosition.x = undefined playerPosition.y = undefined lives-- if(lives <= 0){ level = 0 lives = 3 timeStart = undefined } startGame() } function gameWinAndSetRecord(){ console.log("Superaste el juego") level = 0 playerPosition.x = undefined playerPosition.y = undefined clearInterval(timeInterval) const recordTime = localStorage.getItem('record_time') //Variable que nos trae una iformacion del local storaage const playerTime = Date.now() - timeStart //Variable que nos trae el ultimo tiempo cuando ya se acabaron los niveles del juego, osea tu marca en la partida //Revisa si ya existia un record y empieza a comparar if(recordTime){ //Si el record en el localStorage es mayor, este lo sobre escribe if(recordTime >= playerTime ){ localStorage.setItem('record_time', playerTime) pResult.innerHTML = 'Haz superado el record ✨✨' } //Y si el tiempo en esta partida no es mayor al record ya impuesto en otras partidas, te avisa que no lo has roto else{ pResult.innerHTML = 'lo siento, no has roto el record 💀' } } //Si no habia un record en el localStorage, lo va a creear else{ localStorage.setItem('record_time', playerTime) pResult.innerHTML = 'Primer ingreso de tiempo ✍' } //Y aca te enseña en la consola el record anterior (si no existe es null), y el record de esta partida console.log({recordTime, playerTime}) // startGame() } function moveUp(){ // console.log('Arriba') if((playerPosition.y - elementsSize) < elementsSize){ console.log("limite") } else{ playerPosition.y = playerPosition.y - elementsSize startGame() } } function moveRight(){ // console.log('Derecha') if((playerPosition.x + elementsSize) > canvasSize){ console.log("limite") } else{ playerPosition.x = playerPosition.x + elementsSize startGame() } } function moveLeft(){ // console.log('Izquierda') if((playerPosition.x - elementsSize) < elementsSize){ console.log("limite") } else{ playerPosition.x = playerPosition.x - elementsSize startGame() } } function moveDown(){ // console.log('Abajo') if((playerPosition.y + elementsSize) > canvasSize ){ console.log("limite") } else{ playerPosition.y = playerPosition.y + elementsSize startGame() } } function moveByKeys(event){ // console.log(event.key) let teclaPulsada = event.key switch(teclaPulsada){ case "ArrowUp": moveUp() break; case "ArrowRight": moveRight() break; case "ArrowLeft": moveLeft() break; case "ArrowDown": moveDown() break; } } /////////////Utilidades visuales (vidas, tiempo, etc ) function showLives(){ //Esto es interesante, el metodo "Array" con a mayuscula te permite crear un array que le puedes decir cuantas posiciones quieres y despues con el metodo fill, la llenas de lo que le pediste let corazonesArray = Array(lives).fill(emojis['h']) // console.log(corazones) // let corazones = "" spanLives.innerHTML = "" corazonesArray.forEach(corazon => { spanLives.append(corazon) // corazones = corazones + corazon // console.log(corazones) } ) } function showTime(){ spanTime.innerHTML = Date.now() - timeStart } function showRecord(){ spanRecord.innerHTML = localStorage.getItem('record_time') } //Esta ves usaremos una herramiendo de navegadores que se llama el localStorage, que nos sirve para guardar informacion en el navegador y lo podamos usar de nuevo con su informacion guardada asi hallamos cerrado la pagina, usa 3 metodos (que conozco) como ".getItem" es para leer la informacio que este guardada , ".setItem" para guardar la informacion, variable etc, ".removeItem" saca la variable que se guardo

Escuelas

  • Desarrollo Web
    • Fundamentos del Desarrollo Web Profesional
    • Diseño y Desarrollo Frontend
    • Desarrollo Frontend con JavaScript
    • Desarrollo Frontend con Vue.js
    • Desarrollo Frontend con Angular
    • Desarrollo Frontend con React.js
    • Desarrollo Backend con Node.js
    • Desarrollo Backend con Python
    • Desarrollo Backend con Java
    • Desarrollo Backend con PHP
    • Desarrollo Backend con Ruby
    • Bases de Datos para Web
    • Seguridad Web & API
    • Testing Automatizado y QA para Web
    • Arquitecturas Web Modernas y Escalabilidad
    • DevOps y Cloud para Desarrolladores Web
  • English Academy
    • Inglés Básico A1
    • Inglés Básico A2
    • Inglés Intermedio B1
    • Inglés Intermedio Alto B2
    • Inglés Avanzado C1
    • Inglés para Propósitos Específicos
    • Inglés de Negocios
  • Marketing Digital
    • Fundamentos de Marketing Digital
    • Marketing de Contenidos y Redacción Persuasiva
    • SEO y Posicionamiento Web
    • Social Media Marketing y Community Management
    • Publicidad Digital y Paid Media
    • Analítica Digital y Optimización (CRO)
    • Estrategia de Marketing y Growth
    • Marketing de Marca y Comunicación Estratégica
    • Marketing para E-commerce
    • Marketing B2B
    • Inteligencia Artificial Aplicada al Marketing
    • Automatización del Marketing
    • Marca Personal y Marketing Freelance
    • Ventas y Experiencia del Cliente
    • Creación de Contenido para Redes Sociales
  • Inteligencia Artificial y Data Science
    • Fundamentos de Data Science y AI
    • Análisis y Visualización de Datos
    • Machine Learning y Deep Learning
    • Data Engineer
    • Inteligencia Artificial para la Productividad
    • Desarrollo de Aplicaciones con IA
    • AI Software Engineer
  • Ciberseguridad
    • Fundamentos de Ciberseguridad
    • Hacking Ético y Pentesting (Red Team)
    • Análisis de Malware e Ingeniería Forense
    • Seguridad Defensiva y Cumplimiento (Blue Team)
    • Ciberseguridad Estratégica
  • Liderazgo y Habilidades Blandas
    • Fundamentos de Habilidades Profesionales
    • Liderazgo y Gestión de Equipos
    • Comunicación Avanzada y Oratoria
    • Negociación y Resolución de Conflictos
    • Inteligencia Emocional y Autogestión
    • Productividad y Herramientas Digitales
    • Gestión de Proyectos y Metodologías Ágiles
    • Desarrollo de Carrera y Marca Personal
    • Diversidad, Inclusión y Entorno Laboral Saludable
    • Filosofía y Estrategia para Líderes
  • Diseño de Producto y UX
    • Fundamentos de Diseño UX/UI
    • Investigación de Usuarios (UX Research)
    • Arquitectura de Información y Usabilidad
    • Diseño de Interfaces y Prototipado (UI Design)
    • Sistemas de Diseño y DesignOps
    • Redacción UX (UX Writing)
    • Creatividad e Innovación en Diseño
    • Diseño Accesible e Inclusivo
    • Diseño Asistido por Inteligencia Artificial
    • Gestión de Producto y Liderazgo en Diseño
    • Diseño de Interacciones Emergentes (VUI/VR)
    • Desarrollo Web para Diseñadores
    • Diseño y Prototipado No-Code
  • Contenido Audiovisual
    • Fundamentos de Producción Audiovisual
    • Producción de Video para Plataformas Digitales
    • Producción de Audio y Podcast
    • Fotografía y Diseño Gráfico para Contenido Digital
    • Motion Graphics y Animación
    • Contenido Interactivo y Realidad Aumentada
    • Estrategia, Marketing y Monetización de Contenidos
  • Desarrollo Móvil
    • Fundamentos de Desarrollo Móvil
    • Desarrollo Nativo Android con Kotlin
    • Desarrollo Nativo iOS con Swift
    • Desarrollo Multiplataforma con React Native
    • Desarrollo Multiplataforma con Flutter
    • Arquitectura y Patrones de Diseño Móvil
    • Integración de APIs y Persistencia Móvil
    • Testing y Despliegue en Móvil
    • Diseño UX/UI para Móviles
  • Diseño Gráfico y Arte Digital
    • Fundamentos del Diseño Gráfico y Digital
    • Diseño de Identidad Visual y Branding
    • Ilustración Digital y Arte Conceptual
    • Diseño Editorial y de Empaques
    • Motion Graphics y Animación 3D
    • Diseño Gráfico Asistido por Inteligencia Artificial
    • Creatividad e Innovación en Diseño
  • Programación
    • Fundamentos de Programación e Ingeniería de Software
    • Herramientas de IA para el trabajo
    • Matemáticas para Programación
    • Programación con Python
    • Programación con JavaScript
    • Programación con TypeScript
    • Programación Orientada a Objetos con Java
    • Desarrollo con C# y .NET
    • Programación con PHP
    • Programación con Go y Rust
    • Programación Móvil con Swift y Kotlin
    • Programación con C y C++
    • Administración Básica de Servidores Linux
  • Negocios
    • Fundamentos de Negocios y Emprendimiento
    • Estrategia y Crecimiento Empresarial
    • Finanzas Personales y Corporativas
    • Inversión en Mercados Financieros
    • Ventas, CRM y Experiencia del Cliente
    • Operaciones, Logística y E-commerce
    • Gestión de Proyectos y Metodologías Ágiles
    • Aspectos Legales y Cumplimiento
    • Habilidades Directivas y Crecimiento Profesional
    • Diversidad e Inclusión en el Entorno Laboral
    • Herramientas Digitales y Automatización para Negocios
  • Blockchain y Web3
    • Fundamentos de Blockchain y Web3
    • Desarrollo de Smart Contracts y dApps
    • Finanzas Descentralizadas (DeFi)
    • NFTs y Economía de Creadores
    • Seguridad Blockchain
    • Ecosistemas Blockchain Alternativos (No-EVM)
    • Producto, Marketing y Legal en Web3
  • Recursos Humanos
    • Fundamentos y Cultura Organizacional en RRHH
    • Atracción y Selección de Talento
    • Cultura y Employee Experience
    • Gestión y Desarrollo de Talento
    • Desarrollo y Evaluación de Liderazgo
    • Diversidad, Equidad e Inclusión
    • AI y Automatización en Recursos Humanos
    • Tecnología y Automatización en RRHH
  • Finanzas e Inversiones
    • Fundamentos de Finanzas Personales y Corporativas
    • Análisis y Valoración Financiera
    • Inversión y Mercados de Capitales
    • Finanzas Descentralizadas (DeFi) y Criptoactivos
    • Finanzas y Estrategia para Startups
    • Inteligencia Artificial Aplicada a Finanzas
    • Domina Excel
    • Financial Analyst
    • Conseguir trabajo en Finanzas e Inversiones
  • Startups
    • Fundamentos y Validación de Ideas
    • Estrategia de Negocio y Product-Market Fit
    • Desarrollo de Producto y Operaciones Lean
    • Finanzas, Legal y Fundraising
    • Marketing, Ventas y Growth para Startups
    • Cultura, Talento y Liderazgo
    • Finanzas y Operaciones en Ecommerce
    • Startups Web3 y Blockchain
    • Startups con Impacto Social
    • Expansión y Ecosistema Startup
  • Cloud Computing y DevOps
    • Fundamentos de Cloud y DevOps
    • Administración de Servidores Linux
    • Contenerización y Orquestación
    • Infraestructura como Código (IaC) y CI/CD
    • Amazon Web Services
    • Microsoft Azure
    • Serverless y Observabilidad
    • Certificaciones Cloud (Preparación)
    • Plataforma Cloud GCP

Platzi y comunidad

  • Platzi Business
  • Live Classes
  • Lanzamientos
  • Executive Program
  • Trabaja con nosotros
  • Podcast

Recursos

  • Manual de Marca

Soporte

  • Preguntas Frecuentes
  • Contáctanos

Legal

  • Términos y Condiciones
  • Privacidad
  • Tyc promociones
Reconocimientos
Reconocimientos
Logo reconocimientoTop 40 Mejores EdTech del mundo · 2024
Logo reconocimientoPrimera Startup Latina admitida en YC · 2014
Logo reconocimientoPrimera Startup EdTech · 2018
Logo reconocimientoCEO Ganador Medalla por la Educación T4 & HP · 2024
Logo reconocimientoCEO Mejor Emprendedor del año · 2024
De LATAM conpara el mundo
YoutubeInstagramLinkedInTikTokFacebookX (Twitter)Threads