¿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.