¿Recordáis el juego que comenzamos a desarrollar en Ionic 2 hace cosa de 7 meses?… ¿no?. A continuación os dejo los 2 enlaces que hacen referencia a la parte 1 y 2 de la creación del juego, de este modo os podéis poner rápidamente al día. En este momento, tenéis 2 opciones:
- Seguir las explicaciones y picar el código (recomendado).
- Descargar directamente el código desde los enlaces, si es que tu nivel es más avanzado.
https://luisjordan.net/tutorial-de-ionic/juego-con-ionic-2-creacion-del-fantastico-ahorcado/ https://luisjordan.net/tutorial-de-ionic/juego-en-ionic-2-creacion-del-fantastico-ahorcado-parte-2/ En este artículo, vamos a perfeccionar el juego ampliando funcionalidades y mejorando aspectos visuales. Tengamos en cuenta que en estos meses el framework Ionic ha lanzado y madurado su versión 3, es más, está a punto de lanzar la versión 4 que traerá nuevas novedades. Si ya habéis retomado el tema, vamos a ver cómo quedará nuestro proyecto al finalizar el ejercicio. Vaya! … ya va tomando forma, incluso va pareciendo un juego de verdad. Quien sabe, igual cualquier día de estos podemos ofrecerlo a nuestros ‘colegas’ que lo instalen en sus dispositivos móviles. Empecemos.
Índice
Primeros pasos con Ionic 3.
- Actualizar nuestro framework ionic a la versión 3. [codesyntax lang=»bash»]
npm update -g ionic
[/codesyntax] - Comprobar las versiones de Ionic y de NPM. [codesyntax lang=»bash»]
ionic -v npm -v
[/codesyntax] - Creación del proyecto. Vamos a ser ágiles y no entraremos en estos detalles, doy por hecho que si has realizado las entradas 1 y 2, no tendrás ningún problema.
Scripts y librerías con las que vamos a trabajar.
Seguimos utilizando los scripts de los ejercicios anteriores. Home.ts para la lógica y home.html para la vista, incluyendo una nueva hoja de estilos general: app.scss. Las librerías utilizadas son: [codesyntax lang=»typoscript»]
import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; // Importación necesaria para mensajes flash. import { ToastController } from 'ionic-angular'; // Importación necesaria para alerts. import { AlertController } from 'ionic-angular';
[/codesyntax]
Vista home.html
Podéis ver tal y como acostumbro a hacer, que el código viene con un buen ‘puñado’ de comentarios, siendo así facilito su comprensión. Veamos que tenemos en la vista home.html [codesyntax lang=»xml»]
<!-- Sección superior --> <ion-header> <ion-navbar> <!-- Título de la aplicación --> <ion-title>AHORCADO</ion-title> </ion-navbar> </ion-header> <!-- Sección contenido --> <ion-content padding no-bounce> <!-- Sección header--> <ion-card class="panel-juego"> <ion-card-header text-center> PANEL DE JUEGO </ion-card-header> </ion-card> <!-- Sección imagen + vidas + puntos --> <ion-row> <ion-col col-4 ><img src="/assets/imgs/ahor-{{imagen}}.png" width='80%' /></ion-col> <ion-col col-8 > <ion-row> <ion-col col-3 text-center><H5><ion-icon name="heart" color="danger"></ion-icon></H5></ion-col> <ion-col col-6><H5>Vidas:</H5></ion-col> <ion-col col-3 text-center><H5>{{vidas}}</H5></ion-col> </ion-row> <ion-row> <ion-col col-3 text-center><H5><ion-icon name="calculator" color="primary"></ion-icon></H5></ion-col> <ion-col col-6><H5>Puntos:</H5></ion-col> <ion-col col-3 text-center><H5>{{puntos}}</H5></ion-col> </ion-row> </ion-col> </ion-row> <!-- Sección huecos de palabra --> <ion-list > <!-- Mensajes de la aplicación --> <ion-label text-center>{{mensaje}}</ion-label> <ion-grid> <ion-row *ngIf="ganador!=1"> <ion-col class="palabra" col-1 *ngFor="let item of palabra"> {{item}} </ion-col> </ion-row> <ion-row *ngIf="ganador==1"> <ion-col class="acierto"> La palabra secreta es: {{nombreSecreto}} </ion-col> </ion-row> </ion-grid> <ion-item> <!-- Selector que nos permitirá elegir una letra --> <ion-label text-center>Seleccione una letra</ion-label> <ion-select [(ngModel)]="letra"> <ion-option value="A">A</ion-option> <ion-option value="B">B</ion-option> <ion-option value="C">C</ion-option> <ion-option value="D">D</ion-option> <ion-option value="E">E</ion-option> <ion-option value="F">F</ion-option> <ion-option value="G">G</ion-option> <ion-option value="H">H</ion-option> <ion-option value="I">I</ion-option> <ion-option value="J">J</ion-option> <ion-option value="K">K</ion-option> <ion-option value="L">L</ion-option> <ion-option value="M">M</ion-option> <ion-option value="N">N</ion-option> <ion-option value="Ñ">Ñ</ion-option> <ion-option value="O">O</ion-option> <ion-option value="P">P</ion-option> <ion-option value="Q">Q</ion-option> <ion-option value="R">R</ion-option> <ion-option value="S">S</ion-option> <ion-option value="T">T</ion-option> <ion-option value="U">U</ion-option> <ion-option value="V">V</ion-option> <ion-option value="W">W</ion-option> <ion-option value="X">X</ion-option> <ion-option value="Y">Y</ion-option> <ion-option value="Z">Z</ion-option> </ion-select> </ion-item> </ion-list> <ion-row> <!-- Listado de letras utilizadas --> <ion-label text-center>Listado de letras utilizadas</ion-label> <ion-col col-12 text-center><H6>{{letras_utilizadas}}</H6></ion-col> </ion-row> <!-- En caso de no tener vidas, mostramos un botón para reiniciar el juego --> <ion-row *ngIf="vidas!=0 && ganador!=1"> <ion-col col-4> <!-- Si continuamos teniendo vidas, mostramos el botón para seleccionar letra --> <button ion-button block color="primary" (click)="confirmarResolver()">Resolver</button></ion-col> <ion-col col-8> <!-- Si continuamos teniendo vidas, mostramos el botón para seleccionar letra --> <button ion-button block color="secondary" (click)="compruebaLetra()">Seleccionar letra</button> </ion-col> </ion-row> <button *ngIf="vidas==0 || ganador==1" ion-button block (click)="reiniciaJuego()">Jugar de nuevo</button> </ion-content>
[/codesyntax] <!– Sección superior –> corresponde a la zona superior gris donde indica el título del juego. <!– Sección header–> creación de un panel para destacar: panel del juego. <!– Sección imagen + vidas + puntos –> se ha añadido esta sección para darle un poco de dinamismo a la aplicación, cada vez que pongamos una letra se realizará una serie de acciones en la lógica y…, en caso de acierto se sumarán puntos, y en caso de error se restarán vidas, puntos y la imagen del ahorcado cambiará. <!– Sección huecos de palabra –> como ya pasaba en las versiones anteriores del juego, en esta sección se reemplazarán los huecos de la palabra secreta por las letras que se hayan acertado. <!– Selector que nos permitirá elegir una letra –> selector de letras del abecedario. <!– Listado de letras utilizadas –> para evitar tener que ir memorizando cada una de las letras que se seleccionan, se añade un histórico o leyenda de letras utilizadas. Y por último, la sección de botones. Aquí se mostrarán unos botones u otros dependiendo del estado del juego.
Hoja de estilos: app.scss
[codesyntax lang=»css»]
H2{ color: #777777; } .panel-juego{ margin-bottom: 25px; } .palabra{ } .letras{ } .acierto{ color:#32db64!important; } // Mensages TOAST .toast-success{ > div{ background-color:#32db64!important; } } .toast-danger{ > div{ background-color:#f53d3d!important; } } .toast-warning{ > div{ background-color:#f2cd3c!important; } } #palabraSolucion{ text-transform: uppercase; }
[/codesyntax]
Lógica de juego ahorcado con ionic 3 en home.ts
[codesyntax lang=»typoscript»]
import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; // Importación necesaria para mensajes flash. import { ToastController } from 'ionic-angular'; // Importación necesaria para alerts. import { AlertController } from 'ionic-angular'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { // Definimos las variables letra: string = ''; nombres: any = ['COCHE']; nombreSecreto: any = this.palabraAleatoria(0, (this.nombres.length - 1)); palabra: any = ''; muestraHuecos: any = this.muestraHuecosPalabra(); mensaje: string = ''; letras_utilizadas: string = ''; nombresecretomostrar: string = ''; vidas: number = 6; puntos: number = 0; ganador: number = 0; imagen: number = 1; durationMessages: number = 3000; // Creamos un array para guardar las letras que se van seleccionando. controlLetras = new Array; constructor(public navCtrl: NavController, private toastCtrl: ToastController, public alertCtrl: AlertController) { } // Método que valida la letra seleccionada. public compruebaLetra() { // Formateamos a mayúsculas para mejorar la legibilidad. let letraMayusculas = this.letra.toUpperCase(); // Si se ha seleccionado una letra... if (letraMayusculas) { if (this.controlLetras.indexOf(letraMayusculas) == -1) { // Recorremos las letras de la palabra (array), para detectar si la letra se encuentra en ella. if (this.nombreSecreto.indexOf(letraMayusculas) != -1) { let nombreSecretoModificado = this.nombreSecreto; let posicion = new Array; let posicionTotal = 0; let contador = 1; while (nombreSecretoModificado.indexOf(letraMayusculas) != -1) { posicion[contador] = nombreSecretoModificado.indexOf(letraMayusculas); nombreSecretoModificado = nombreSecretoModificado.substring(nombreSecretoModificado.indexOf(letraMayusculas) + letraMayusculas.length, nombreSecretoModificado.length); // Calculamos la posición total. if (contador > 1) { posicionTotal = posicionTotal + posicion[contador] + 1; } else { posicionTotal = posicionTotal + posicion[contador]; } // Preparamos la palabra para que sea mostrara en modal de solución directa. this.palabra[posicionTotal] = letraMayusculas; // Sumamos puntos if (this.controlLetras.indexOf(letraMayusculas) == -1) { this.puntos = this.puntos + 10; // Hacemos uso de Toast Controller para lanzar mensajes flash. let toast = this.toastCtrl.create({ message: 'Genial, la letra ' + letraMayusculas + ' está en la palabra secreta.', duration: this.durationMessages, cssClass: 'toast-success', position: 'top' }); toast.present(); } contador++; // Si ya no quedan huecos, mostramos el mensaje para el ganador. if (this.palabra.indexOf('_') == -1) { // Sumamos puntos if (this.controlLetras.indexOf(letraMayusculas) == -1) { this.puntos = this.puntos + 50; } // Damos el juego por finalizado, el jugador gana. this.finDelJuego('gana') } } } else { // Restamos una vida. this.nuevoFallo(); // Actualizamos la imagen this.nuevaImagen(this.imagen); // Comprobamos si nos queda alguna vida. if (this.vidas > 0) { // Restamos puntos siempre y cuando no sean 0. if (this.puntos > 0) { if (this.controlLetras.indexOf(letraMayusculas) == -1) { this.puntos = this.puntos - 5; } } // Mostramos un mensaje indicando el fallo. let toast = this.toastCtrl.create({ message: 'Fallo, la letra ' + letraMayusculas + ' no está en la palabra secreta. Recuerda que te quedan ' + this.vidas + ' vidas.', duration: this.durationMessages, cssClass: 'toast-danger', position: 'top' }); toast.present(); } else { // Damos el juego por finalizado, el jugador pierde. this.finDelJuego('pierde') } } // Array de letras utilizadas para mostrar al jugador. if(this.letras_utilizadas == ''){ this.letras_utilizadas += letraMayusculas; } else{ this.letras_utilizadas += ' - '+letraMayusculas; } // Añadimos al array de letras la nueva letra seleccionada. this.controlLetras.push(letraMayusculas); } else{ // En caso de que la letra ya hubiera sido seleccionada, mostramos un mensaje. let toast = this.toastCtrl.create({ message: 'La letra ' + letraMayusculas + ' fue seleccionada anteriormente. Por favor, seleccione una letra diferente.', duration: this.durationMessages, cssClass: 'toast-warning', position: 'top' }); toast.present(); } } } public muestraHuecosPalabra() { let totalHuecos = this.nombreSecreto.length; // Declaramos la variable huecos como nuevo array. let huecos = new Array; for (let i = 0; i < totalHuecos; i++) { //Asignamos tantos huecos como letras tenga la palabra. huecos.push('_'); } // Para empezar formamos la variable palabra tan solo con los huecos, ya que en este momento aún no se ha seleccionado ninguna letra. this.palabra = huecos; return this.palabra; } // Método que genera una palabra aleatoria comprendida en el array nombres. public palabraAleatoria(primer, ultimo) { let numberOfName = Math.round(Math.random() * (ultimo - primer) + (primer)); return this.nombres[numberOfName]; } public nuevoFallo() { this.vidas = this.vidas - 1; return this.vidas; } public nuevaImagen(imagen) { this.imagen = imagen + 1; return this.imagen; } public confirmarResolver(){ this.showPrompt(); } public showPrompt() { const prompt = this.alertCtrl.create({ title: 'Solución directa', message: "¿Está seguro de resolver la palabra secreta directamente?", inputs: [ { name: 'palabraSolucion', id: 'palabraSolucion', placeholder: this.palabra }, ], buttons: [ { text: 'Cancelar', handler: data => { // Se cierra ventana. } }, { text: 'Resolver', handler: data => { // Llamamos a método que compara la palabra secreta con la insertada mediante teclado. // var solucion = this.palabra.toString(); // var solucion = solucion.replace(/,/g, ''); var solucion = ((document.getElementById("palabraSolucion") as HTMLInputElement).value); this.resolver(solucion); } }] }); prompt.present(); } public showConfirm(accion) { // Resolver if(accion == 'resolver'){ const confirm = this.alertCtrl.create({ title: 'Solución directa', message: '¿Está seguro de resolver la palabra secreta directamente?', buttons: [ { text: 'Cancelar', handler: () => { // } }, { text: 'Confirmar', handler: () => { // } }] }); confirm.present(); } } public resolver(solucion){ // Comprobamos la solución directa. if(this.nombreSecreto == solucion.toUpperCase()){ var totalOcultas = 0; // Recorremos el array para detectar huecos sin transformar a letras. for ( var i = 0; i < this.palabra.length; i++ ) { if(this.palabra[i] == '_'){ totalOcultas = totalOcultas + 1; } } // ACIERTO :: Sumamos +50 y + 20 por cada hueco sin desvelar. this.puntos = this.puntos + 50 + (20 * totalOcultas); this.finDelJuego('gana') // Colocamos la palabra secreta en el }else{ // ERROR :: RESTAMOS 50. this.puntos = this.puntos - 25; let toast = this.toastCtrl.create({ message: 'Lo sentimos!, La palabra '+solucion+' no es la palabra secreta. Su error le resta 25 puntos.', duration: this.durationMessages, cssClass: 'toast-danger', position: 'top' }); toast.present(); } } public finDelJuego(valor) { // Perdedor if (valor == 'pierde') { this.ganador = 0; // Mostramos el mensaje como que el juego ha terminado let toast = this.toastCtrl.create({ message: 'Perdiste!, Inténtalo de nuevo. Has conseguido un total de ' + this.puntos + ' puntos. La palabra secreta es ' + this.nombreSecreto, duration: this.durationMessages, cssClass: 'toast-danger', position: 'top' }); toast.present(); } // Ganador if (valor == 'gana') { this.ganador = 1; let toast = this.toastCtrl.create({ message: 'Enhorabuena!, Has acertado la palabra secreta. Has conseguido un total de ' + this.puntos + ' puntos.', duration: this.durationMessages, cssClass: 'toast-success', position: 'top' }); toast.present(); } } public reiniciaJuego() { this.letra = ''; this.palabra = ''; this.vidas = 6; this.mensaje = ''; this.ganador = 0; this.puntos = 0; this.nombreSecreto = this.palabraAleatoria(0, (this.nombres.length-1)); this.muestraHuecos = this.muestraHuecosPalabra(); this.imagen = 1; this.letras_utilizadas = ''; this.nombresecretomostrar = ''; this.controlLetras = new Array; } }
[/codesyntax]
Explicación de la lógica del juego.
Ya que la lógica del juego va quedando algo extensa, en lugar de comentar línea por línea, lo que voy a hacer es explicar por donde pasa el flujo de la aplicación en cada uno de los escenarios posibles. En el momento que seleccionamos una letra del desplegable y pulsamos sobre el botón seleccionar letra, estamos llamando al método compruebaLetra() y es aquí donde realizamos las siguientes comprobaciones:
- Seleccionar letra:
- Comprobar que se haya seleccionado una letra.
- Comprobar si la letra está en la palabra secreta:
- SI:
- Recorremos la palabra secreta y reemplazamos los huecos por la letra seleccionada tantas veces como corresponda.
- Sumamos puntos.
- Lanzamos mensaje toast de acierto.
- Comprobamos si quedan huecos en la palabra secreta:
- SI: Seguimos jugando.
- NO: @Ganador solución indirecta.
- NO:
- Restamos puntos.
- Restamos vida.
- Comprobamos si quedan vidas:
- SI: Seguimos jugando.
- NO: Fin del juego.
- Lanzamos mensaje toast de error.
- Cambiamos imagen añadiendo un palito al muñeco del ahorcado.
- SI:
- Resolver directamente:
- Solución directa:
- SI: Sumamos más puntos que en la solución indirecta.
- NO: Restamos puntos y seguimos jugando.
- Solución directa:
Conclusión final.
Sinceramente, estoy contento. Estamos aprendiendo Ionic realizando un juego que está tomando una pinta chulísima. Versiones utilizadas, tiempo y dificultad de desarrollo: Plataforma: Ionic v3 Dificultad: Avanzado. Tiempo de desarrollo: 6 horas.