3

Refactorización del juego "Goku VS Vegeta" implementado inicialmente por un alumno de Platzi

Dando continuidad al ejercicio desarrollado por el compañero @cosmosoftroot, quise mostrar en forma de transiciones animadas la batalla.
Me pareció una estructura interesante en materia de buenas prácticas (uso de los arrow Functions y de más), y simpleza en el código, tanto así que todo el contenido (CSS, JS y semántica HTML5), decidí dejarlo en un único fichero HTML para mantener de momento la versión propuesta por el alumno y centrarme en modificar parte de la lógica del juego en fragmentos que comparten un mismo Scope.
La idea en general, es poder implementar el patrón de diseño “revealing module pattern” o patrón revelador en otro tutorial, para encapsular toda la lógica javascript dentro de una única función.
Para no extender un poco el tutorial decidí incluir la librería de JQuery para recorrer algunos elementos del DOM.

** Fuentes **
el repositorio donde pueden descargar el proyecto para proponer mejoras es:
Repositorio Github
la URL de github-pages para ejecutar el ejercicio:
github-pages

** Proceso **
Las etapas para refactorizar el código se listan en orden a continuación hasta el punto #8 que es donde está integrado todo el bloque de código Javascript:

(1) Definición de los miembros públicos al comienzo del bloque javascript.

  • Como parte de las definiciones, Se crea un array de imagenes para evitar tener que ir hasta la lógica del juego para acceder a una u otra.
<!-- SCRIPT  --><scripttype="text/javascript">// SCOPE  >>>>>let vidaGoku=100;
let vidaVegeta=100;

const minPower=5;
const maxPower=12;

let round =0;
let imgGoku=1;
let imgVegeta=1;
const altoLienzoActual = 500;
// Imagenes:let arrImages = {
    'goku_injured': 'images/goku_injured.jpg',
    'vegeta_injured': 'images/vegeta_injured.png'
}				

// Arrow functionsconst ambosSiguenVivos = () => vidaGoku > 0 && vidaVegeta > 0;
const calcularGolpe = () => Math.round(Math.random() * (maxPower - minPower)) + minPower;
const sigueVivoGoku =() => vidaGoku > 0;
let contadorPelea = 0// de acá para abajo son los pasos siguientes...</script>

(2) Identificar la sentencia “while” para iterar sobre la Batalla mientras ambos sigan vivos y copiar todo este bloque a una nueva función, esta función tiene el nombre de “comenzarBatalla”:

// functión que permite comenzar la batallafunctioncomenzarBatalla(){
        // estado golpeslet estados=null// inicio de la pelea				while(ambosSiguenVivos()){
            // todo el contenido de este while...
        }
    }

(3) para este paso, quise sacar las lineas que escriben elementos sobre el DOM, tipo: “document.white(’<div>elemento</div>’)”, que se implementan cuando el golpe de Goku es mayor al golpe de vegeta y viceversa.
Esto con el objetivo de refactorizar ese código en una función aparte y reciba como parametros el estado de la batalla.

  • Justo después de la linea comentada JS ‘// render’, está el llamado a la función que se detalla en el siguiente paso:
// inicio de la pelea				while(ambosSiguenVivos()){

    console.log("cuerpo INICIO");				
    contadorPelea++
    round++;						// constantes
    const  golpeGoku = calcularGolpe();
    const  golpeVegeta = calcularGolpe();// golpes (evaluacion)if (golpeGoku > golpeVegeta){
        if(imgGoku>3)
            imgGoku=1;
        vidaVegeta-=golpeGoku;// render
        estados = renderEscena(round, "goku", golpeGoku, imgGoku, vidaVegeta)
        imgGoku++;
    }else{
        if(imgVegeta>3)
            imgVegeta=1;
        vidaGoku-=golpeVegeta;// render
        estados = renderEscena(round, "vegeta", golpeVegeta, imgVegeta, vidaGoku)
        imgVegeta++;
    }
    console.log("cuerpo FINAL, imagenes de test: "+arrImages.goku_injured);
}

(4) Ahora el paso siguiente será implementar esa función nueva: “renderEscena”, la cual recibe como parámetros el round actual, quien golpea, imagen de quien golpea y la vida del oponente según sea el caso:

functionrenderEscena(round, nombreGanador, golpeGanador, imgGanador, vidaActualOponente){
    // clear HTMLdocument.write('')					
    // definicioneslet golpeGoku=falselet golpeVegeta=falsedocument.write('<table class="storyboard">');
    document.write(`<tr><th colspan='2'>Round ${round}</th></tr>`)

    // Validaciónif (nombreGanador=="goku"){
        // estado atacantedocument.write('<tr>')
        document.write(`<td>Goku ataca con un golpe de ${golpeGanador}</td>`)
        document.write(`<td><img src='images/goku_${imgGanador}.jpg'></td>`)
        document.write('</tr>')
        // estado oponentedocument.write('<tr>')
        document.write(`<td>Vegeta queda con ${vidaActualOponente} de vida</td>`)
        document.write(`<td><img src='images/vegeta_injured.png'></td>`)	
        document.write('</tr>')					
    }else{
        // estado atacantedocument.write('<tr>')
        document.write(`<td>Vegeta ataca con un golpe de ${golpeGanador}</td>`)
        document.write(`<td><img src='images/vegeta_${imgGanador}.jpg'></td>`)
        document.write('</tr>')
        vidaGoku-=golpeVegeta;
        // estado oponentedocument.write('<tr>')
        document.write(`<td>Goku queda con ${vidaActualOponente} de vida</td>`)
        document.write(`<td><img src='images/goku_injured.jpg'></td>`)
        document.write('</tr>')					
    }
    document.write(`</table>`)
    return golpeGanador
}

(5) Justo después de la función definida en el paso anterior y justo antes del cierre del script, se ejecutan las siguientes lineas como flujo normal de eventos:

//  salidas
    console.log("alto lienzo "+altoLienzoActual)
    if(sigueVivoGoku()){
        document.write('<table class="storyboard">')
        document.write(`<tr><thcolspan='2'>Goku gana la pelea!!!</th></tr>`)
        document.write(`<td><imgsrc='images/goku_win.jpg'></td>`)
        document.write(`</table>`)
    }else{
        document.write('<table class="storyboard">')
        document.write(`<tr><th colspan='2'>Vegeta gana la pelea!!!</th></tr>`)
        document.write(`<td><imgsrc='images/vegeta_win.jpg'></td>`)
        document.write(`</table>`)
    }
    // final SCOPE <<<<<

Teniendo estos primeros 5 pasos listos, está casi terminado, pero falta lo más importante y un poco desafiante en la refactorización, y es el poder recorrer los elementos “storyboard” que son tablas HTML, y mostrar cada una por intervalos de tiempo hasta la última tabla.
Es decir, no se tiene certeza de si se desplegarán 15 tablas en una batalla se motrará en la ultima de las secuencuas a Goku como vencedor, o una siguiente batalla donde hayan 18 escenas o tablas donde la última (secuencua), es quizás Vegeta el ganador.
Primero intenté con:

while (ambosSiguenVivos()){
        setInterval(function()//recorrer tablas y procesar ...
        }, 3000)
    }

Pero esto provoca un problema y no se despliega como debería.
Entonces el paso #6 será implementar ese intervalo de tiempo para cada recorrido del ciclo while.

(6) Se genera un retardo para cada estado de la batalla, es decir, un recorrido dentro del ciclo while para cuando golpea Goku o cuando golpea Vegeta.
La implementación esta vez no es con “setInterval”, sino con: “setTimeout…”:

while (ambosSiguenVivos()){
    // ...// definicioneslet arrTables = $(".contenedor table")
    let cont =0;
    // animación

    setTimeout(function() {
        console.log("INICIAL")
        $(arrTables[0]).fadeOut("slow")//.css({"display":"none"})                        // recorrer los elementos diferentes del primero//
        De momento no se coloca la implementación a partir de esta linea        
    }, 2000);
}

(7) Sobre el bloque de código anterior, se implementa el contenido dentro de “setTimeOut(function(){…})”, donde inicialmente se realiza un recorrido del JQuery Object definido con el nombre de “arrTables” dela siguiente manera:

arrTables.each(function(i,j){
    // el elemento i, representa el indice actual del elemento en cuestion
    // el elemento j, representa el contexto de cada elemento HTML recorrido
});
  • Una vez definida la sentencia para recorrer todas las tablas, se incrementa la variable “cont” definida previamente como “let cont =0;”:
arrTables.each(function(i,j){
    // incremento
    con++
    // el elemento i, representa el indice actual del elemento en cuestion
    // el elemento j, representa el contexto de cada elemento HTML recorrido, es decir, cada tabla
})
  • Finalmente en este paso, se define otro “setTimeout(function(){ … }, INTERVALO) })” con el objetivo de mostrar transitivamente las secuencias multiplicando 3000 milisegundos * countque es el valor que tendrá la variable cont para cada recorrido dela tabla.
  • Aprovechando una de las variables de entrada “j”, obtendremos el contenido de ese elemento para ocultar cada vez la tabla actual transcurrido cierta cantidad de tiempo.
    obteniendo este jqueryObject, podré aplicar un método que permite ocultar la tabla actual de manera animada:
      $(j).fadeOut("slow")
  • Una muestra dela implementación hasta el momento solo del recorrido de tablas sería la siguiente:
// recorrer los elementos diferentes del primero
        arrTables.each(function(i,j){
            cont++
            setTimeout(function () {
                console.log("iteracion "+ $(j).attr("class")+ " contenido de i: "+i)
                $(j).fadeOut("slow")
            }, 3000 * cont)
        })    

(8) Una muestra de como se ve el código completo refactorizado (SOLO EL SCRIPT JS) es:

<!-- SCRIPT  --><scripttype="text/javascript">// SCOPE  >>>>>let vidaGoku=100;
let vidaVegeta=100;

const minPower=5;
const maxPower=12;

let round =0;
let imgGoku=1;
let imgVegeta=1;
const altoLienzoActual = 500;
// Imagenes:let arrImages = {
    'goku_injured': 'images/goku_injured.jpg',
    'vegeta_injured': 'images/vegeta_injured.png'
}				

// Arrow functionsconst ambosSiguenVivos = () => vidaGoku > 0 && vidaVegeta > 0;
const calcularGolpe = () => Math.round(Math.random() * (maxPower - minPower)) + minPower;
const sigueVivoGoku =() => vidaGoku > 0;
let contadorPelea = 0// comenzar batalla
comenzarBatalla()

//-------------------------------------------------functioncomenzarBatalla(){
    // estado golpeslet estados=null// inicio de la pelea				while(ambosSiguenVivos()){

        console.log("cuerpo INICIO");				
        contadorPelea++
        round++;						
        // constantesconst  golpeGoku = calcularGolpe();
        const  golpeVegeta = calcularGolpe();
        // golpes (evaluacion)if (golpeGoku > golpeVegeta){
            if(imgGoku>3)
                imgGoku=1;
            vidaVegeta-=golpeGoku;
            // render
            estados = renderEscena(round, "goku", golpeGoku, imgGoku, vidaVegeta)
            imgGoku++;
        }else{
            if(imgVegeta>3)
                imgVegeta=1;
            vidaGoku-=golpeVegeta;
            // render
            estados = renderEscena(round, "vegeta", golpeVegeta, imgVegeta, vidaGoku)
            imgVegeta++;
        }
        console.log("cuerpo FINAL, imagenes de test: "+arrImages.goku_injured);
    }
    // definicioneslet arrTables = $(".contenedor table")
    let cont =0;
    // animación
    setTimeout(function() {
        console.log("INICIAL")
        $(arrTables[0]).fadeOut("slow")//.css({"display":"none"})// recorrer los elementos diferentes del primero
        arrTables.each(function(i,j){
            cont++
            setTimeout(function () {
                console.log("iteracion "+ $(j).attr("class")+ " contenido de i: "+i)
                $(j).fadeOut("slow")
            }, 3000 * cont)
        })

    }, 2000);

}

functionrenderEscena(round, nombreGanador, golpeGanador, imgGanador, vidaActualOponente){
    // clear HTMLdocument.write('')					
    // definicioneslet golpeGoku=falselet golpeVegeta=falsedocument.write('<table class="storyboard">');
    document.write(`<tr><th colspan='2'>Round ${round}</th></tr>`)

    // Validaciónif (nombreGanador=="goku"){
        // estado atacantedocument.write('<tr>')
        document.write(`<td>Goku ataca con un golpe de ${golpeGanador}</td>`)
        document.write(`<td><img src='images/goku_${imgGanador}.jpg'></td>`)
        document.write('</tr>')
        // estado oponentedocument.write('<tr>')
        document.write(`<td>Vegeta queda con ${vidaActualOponente} de vida</td>`)
        document.write(`<td><img src='images/vegeta_injured.png'></td>`)	
        document.write('</tr>')					
    }else{
        // estado atacantedocument.write('<tr>')
        document.write(`<td>Vegeta ataca con un golpe de ${golpeGanador}</td>`)
        document.write(`<td><img src='images/vegeta_${imgGanador}.jpg'></td>`)
        document.write('</tr>')
        vidaGoku-=golpeVegeta;
        // estado oponentedocument.write('<tr>')
        document.write(`<td>Goku queda con ${vidaActualOponente} de vida</td>`)
        document.write(`<td><img src='images/goku_injured.jpg'></td>`)
        document.write('</tr>')					
    }
    document.write(`</table>`)
    return golpeGanador
}
//  salidasconsole.log("alto lienzo "+altoLienzoActual)
if(sigueVivoGoku()){
    document.write('<table class="storyboard">')
    document.write(`<tr><th colspan='2'>Goku gana la pelea!!!</th></tr>`)
    document.write(`<td><img src='images/goku_win.jpg'></td>`)
    document.write(`</table>`)
}else{
    document.write('<table class="storyboard">')
    document.write(`<tr><th colspan='2'>Vegeta gana la pelea!!!</th></tr>`)
    document.write(`<td><img src='images/vegeta_win.jpg'></td>`)
    document.write(`</table>`)
}
// final SCOPE <<<<<</script>

Conclusión
La invitación es a los compañeros de Platzi que quieran contribuir con las tareas propuestas en el proyecto creado dentro del repositorio desde la página de Github, para ello tendrás que estar autenticado con una cuenta en github para poder clonar este repositorio.
Una vez tengas una propuesta o idea para agregar a este proyecto estaré atento para realizar el merge a la rama principal del repositorio “master” y juntos crearemos la mejor versiónde este juego.

  • Recibiendo los pull request se podrá generar una mejor versión del juego que integre posiblemente sonidos en cada escena hasta terminar la batalla.
    Para un próximo tutorial explico algunas generalidades del lado del HTML y CSS, para finalmente aplicar el patron de diseño que les comenté al principio de este turorial “revelador”.
    Gracias y hasta pronto.
Escribe tu comentario
+ 2