Juego Javascript: descubre la frase

Este juego es bastante conocido, se trata de una variante dle juego del ahorcado. El juego consiste en adivinar una frase inicialmente oculta, para ello se van eligiendo letras, si la letra existe en la frase se mostrará en la frase. Si sabes la solución antes de revelar todas las letras puedes escribirla en el recuadro que hay bajo el panel.

Solución

<body>

<div class="botonera">

<button>A</button><button>B</button><button>C</button><button>D</button>

<button>E</button><button>F</button><button>G</button><button>H</button>

<button>I</button><button>J</button><button>K</button><button>L</button>

<button>M</button><button>N</button><button>Ñ</button><button>O</button>

<button>P</button><button>Q</button><button>R</button><button>S</button>

<button>T</button><button>U</button><button>V</button><button>W</button>

<button>X</button><button>Y</button><button>Z</button></div>

<div class="panel"></div>

<div class="respuesta">

  <form action="" onsubmit="return verificar()">

    <h2>¡Ya lo se !</h2>

    <textarea id="respuesta" placeholder="Escribe tu respuesta"></textarea><br>

    <input type="submit" name="button" id="button" value=" Comprobar ">

  </form>

</div>

<div class="modal cerrada"><h1>Muy bien: Correcto</h1></div>

</body>

<style>

html{font-family: "sans-serif";}

body{ margin: 0}

.botonera{

  display: flex;

  flex-wrap: wrap;

  justify-content: center;

  font-family: sans-serif;

  padding: 80px 0;

  background: aliceblue;

}

.botonera button{

  width: 3rem;

  height: 3rem;

  border: 1px solid red;

  text-align: center;

  padding: 8px;

  margin: 0 4px;

  font-size: 2rem;

  border-radius: 50%;

  display: flex;

  justify-content: center;

  align-items: center;

}

.botonera button:hover{

  background: red;

  color: white;

  cursor: pointer

}

.botonera button.usada{

  background: white;

  color: lightgray;

  cursor: none;

  border-color: #ff000085;

}

.panel{

  display: flex;

  flex-direction: column;

  flex-wrap:wrap;

  justify-content:center;

  align-items:center;

  margin: 80px auto;

}

.panel .fila{

  display: flex;

}

.letra, .espacio{

  padding: 10px;

  margin: 8px 4px;

  font-size: 1.8rem;

  width: 2rem;

  height: 2rem;

  display: flex;

  align-items:center;

  justify-content: center;

}

.letra{

  border: 1px solid black;

}

.espacio{

  border: none;

  background: transparent;

}

@keyframes girar{

  0% {transform: rotateY(180deg);color: transparent;}

  49% {color: transparent;}

  51% {color: black;}

  100% {transform: rotateY( 0deg);}

}

.oculto{

  color: transparent;

  transform: rotateY(180deg);

}

.visible{

  animation: girar 500ms linear forwards;

}

.respuesta{

  text-align:center;

  margin: 0 auto;

  margin-top: 80px

}

.respuesta textarea{

  width: 75vw;

  height: 4rem;

}

.modal{

  position: absolute;

  top: 0;

  bottom: 0;

  left: 0;

  right: 0;

  margin: auto;

  width: fit-content;

  height: fit-content;

  background: white;

  color: red;

  padding: 40px;

  border: 1px solid red;

  border-radius: 25px;

  box-shadow: 10px 10px 15px 5px lightgray;

  transform: scale(0);

  transition: transform 500s linear;

}

.modal.abierta{

  display: block;

  transform: scale(1)

}

.modal.cerrada{

  transform: scale(0)

  display: none

}

#button{

  font-size: 1.5rem;

  background-color: red;

  color: white;

  border: none;

  border-radius: 15px

}

  #button:hover{

  background-color: #ff000091;

  color: #1f1d1d;

  box-shadow: 0 0 5px 5px #f6cfcf;

}

</style>

<script>

//Definiciones iniciales

  !function init(){

  const frases=[];

  let elegida = 0, fraseActual;

  let panel, cols, filas;

  const anchoLetra = 65;

  //anchoLetra es el ancho que tienen los paneles de las letras (CSS)

  //Frases posibles para acertar, las vocales no llevan acentos

  //No distingue entre mayúsuclas y minúsculas

  frases[0] = "Cuando el viento sopla airado, no hay paz en ningun lado";

  frases[1] = "En abril espigas mil, en mayo espigas y grano";

  frases[2] = "Cuando llueve y hace sol, baila el perro y el pastor";

  elegida = Math.floor(Math.random()*(frases.length))

  fraseActual = frases[elegida];

  //El contenedro del tablero de letras es un div con class panel

  //Guardo la frase en una propiedad añadida (frase)

  panel = document.querySelector(".panel");

  panel.frase = fraseActual;

//numero de columnas para evitar ruptura de palabras

  cols = Math.floor(panel.offsetWidth / anchoLetra);

  filas = wrap(fraseActual, cols);

//Añadir una fila por cada segmento de la frase formateada al panel

  filas.forEach(creaFila, panel)

//Asigna evento a los botones de las letras.

  let botonera = document.querySelector(".botonera");

  botonera.addEventListener('click', seleccionar)

//FUNCIONES

  //crea fila con bloques div para las letras

  //usa this para el panel que contiene todas las filas

function creaFila(cadena){

  let fila = document.createElement('div');

  let divLetra;

  fila.classList.add('fila');

  for(let i = 0; i < cadena.length; i++){

  divLetra = document.createElement('div');

  if(cadena[i] == ' '){

  divLetra.classList.add('espacio');

  divLetra.innerHTML = "&nbsp;";

  }

  else{

  divLetra.classList.add('letra');

  if( cadena[i].match(/,|\.|;|:|\?|¿|¡|\!/)){

  divLetra.innerHTML = cadena[i];

  divLetra.classList.add('espacio', 'visible');

  }

  else{

  divLetra.innerHTML = cadena[i].toUpperCase();

  divLetra.classList.add('oculto');

  }

  }

  fila.appendChild(divLetra);

  }

  this.appendChild(fila);

  }

//Parte la frase en secciones para no tener que romper palabras

  //ancho es la anchura máxima del bloque que contendrá la frase

function wrap(frase, ancho){

  let filas;

  let palabras = frase.split(' ');

  filas = palabras.reduce((pal, act)=>{

  if(pal.length==0){

    pal[0] = act

  }

  else{

    if(pal[pal.length-1].length+act.length < ancho){

      pal[pal.length-1] += ' '+act;

    }

    else{

      pal.push(act);

    }

  }

  return pal;

  }, [])

  return filas;

}

//Lógica

function seleccionar(e){

  let tecla = e.target;

  let panel = document.querySelectorAll('.panel .letra');

  if (tecla.tagName.toLowerCase()=="button" && !tecla.classList.contains('usada')){

    tecla.classList.add('usada');

    for(let i = 0; i< panel.length; i++){

      if (panel[i].innerText === tecla.innerText){

        panel[i].classList.replace('oculto', 'visible')

        }

      }

    }

  }

}();

function verificar(){

  let panel = document.querySelector('.panel')

  let respta = event.target.respuesta.value;

  if (respta.toUpperCase() === panel.frase.toUpperCase()){

    document.querySelector('.modal').classList.replace('cerrada', 'abierta');

    let letras = document.querySelectorAll('.letra.oculto');

    for(let i=0; i < letras.length;i++){

      letras[i].classList.replace('oculto','visible')

    }

  }

  else {

    event.target.respuesta.value="¡Fallaste!: pide más letras";

    }

  return false

}

document.addEventListener('click', ()=>document.querySelector('.modal').classList.replace('abierta', 'cerrada'));

</script>

Explicación

El código HTML

En el apartado de HTML colocamos una botonera para las teclas con las que elegir las letras de la frase que tenemos que adivinar. Los nombres de las clases usadas en este código son las que utiliza el script, por tanto si las cambias también debes hacerlo en el script.

La parte superior son las letras, desde la A a la Z. Estas son botones que pueden clickarse.

Después verás un panel vacio con la clase panel, este es el bloque donde se colocarán la frase, letra por letra, inicialmente ocultas mediane CSS

Debajo hay un campo input de formulario, utilizado para que el jugador pueda escribir la frase si la adivina antes de revelar todas las letras

Finalmente hay un bloque o ventana modal (clase modal) que se despliega cuando el usuario adivine la frase al escribirla en el formulario

El código CSS

La clase .botonera define el aspecto de los botones de las letras seleccionables. Usa flex para situarlas horizontalmente y bien centradas

El selector .botonera .button sitúa las letras dentro del botón, para centarlas se usa flex

La clasel .panel define y sitúa el panel de letras de la frase. Dentro se colocarán los bloques div para las letras. El panel es un flex con bloques de filas

La clase .fila se usa para el estilo de las filas en que se dividirá la frase para ponerla en pantalla. Para csituarla se usa flex

Las filas contienen dos tipos de bloque:  uno para caracteres (.letra) y otros para espacios (.espacio). Son prácitcamente iguales, solo que las letras aparecen con un borde y color de fondo, mientras el espacio es transparente y sin borde

Las letras aparecen con un efecto de giro sobre el eje Y, este giro se consigue con un keyframe. Este gira el bloue y a la mitad desvela el contenido (la letra)

Las reglas de estilo para .oculto y .visible son usadas para mostrar u ocultar las letras de la frase en el panel

La regla para .modal se encarga de la ventana modal que es la que aparece tras acertar la frase. Tiene posicionamiento absoluto y se centra en pantalla mediante los margin auto usando las coordenadas extremas en 0 (left, right, top, bottom) y con dimensiones ajustadas al contenido.

El código Javascript

Tenemos una función de inicio que define un array con las frases que se van a usar en el juego. Importante no utilizar letras con acentos. Los signos de puntuación contemplados son ¿?.,:;¡!. Si necesitas más modifica la linea 43

Con la función random() se elige una de las frases como frase activa, la que se debe adivinar.

Para evitar que las plabras aparezcan partidas entre lineas se usa una función de división de la frase (wrap).

La función wrap() rompe la frase en un array de palabras y va agrupando estas en strings con un ancho máximo: el que pueda soportar la pantalla. Este ancho corresponde al ancho ocupado por el bloque usado para almacernar las letras del panel (en este caso 65pixels: ancho+pading+borde)

Una vez tenemos las palabras agrupadas, usamos estas subcadenas para crear filas de bloques div dentro del bloque .panel

Al bloque botonera le asignamos un manejador de eventos click, al pulsar lee la letra pulsada llama a la función seleccionar encargada de buscar si la letra está en la frase. Si lo está cambia los bloques con esa letra de oculto a visible y pone el botón de letra en modo usado.

La función verificar es llamada por el evento onsubmit del formulario. Se usa para comprobar si el texto introducido por el jugador conicide con el oculto. Si lo es activa la ventan modal. Devuelve false para que el formulario no refresque la página.