Ejercicio: Lógica del juego
En este ejercicio, agregamos lógica de juego a nuestra aplicación para asegurarnos de que terminamos con un juego totalmente funcional.
Para ayudar a que este tutorial se siga centrando en la formación sobre Blazor, se proporciona una clase denominada GameState
que contiene la lógica para administrar el juego.
Adición del estado del juego
Vamos a agregar la clase GameState
al proyecto y, a continuación, ponerla a disposición de los componentes como un servicio singleton a través de la inserción de dependencias.
Copie el archivo GameState.cs en la raíz del proyecto.
Abra el archivo Program.cs en la raíz del proyecto y agregue esta instrucción que configura
GameState
como un servicio singleton en la aplicación:builder.Services.AddSingleton<GameState>();
Ahora podemos insertar una instancia de la clase
GameState
en nuestro componenteBoard
.Agregue la siguiente directiva
@inject
en la parte superior del archivo Board.razor. la directiva inserta el estado actual del juego en el componente:@inject GameState State
Ahora podemos empezar a conectar nuestro componente
Board
al estado del juego.
Restablecimiento del estado
Comencemos restableciendo el estado del juego cuando el componente Board
se muestra por primera vez en pantalla. Agregue código para restablecer el estado del juego cuando se inicialice el componente.
Agregue un método
OnInitialized
con una llamada aResetBoard
, dentro del bloque@code
situado en la parte inferior del archivo Board.razor de la siguiente manera:@code { protected override void OnInitialized() { State.ResetBoard(); } }
Cuando el tablero se muestra por primera vez a un usuario, el estado se restablece al inicio de una partida.
Creación de las piezas del juego
A continuación, asignemos las 42 piezas de juego posibles que se podrían jugar. Podemos representar las piezas del juego como una matriz a la que hacen referencia 42 elementos HTML en el tablero. Podemos mover y colocar esas piezas asignando un conjunto de clases CSS con posiciones de columna y fila.
Para contener nuestras piezas de juego, definimos un campo de matriz de cadenas en el bloque de código:
private string[] pieces = new string[42];
Agregue código a la sección HTML que crea 42 etiquetas
span
, una para cada pieza del juego, en el mismo componente:@for (var i = 0; i < 42; i++) { <span class="@pieces[i]"></span> }
El código completo debe ser similar al siguiente:
<div> <div class="board"> @for (var i = 0; i < 42; i++) { <span class="container"> <span></span> </span> } </div> @for (var i = 0; i < 42; i++) { <span class="@pieces[i]"></span> } </div> @code { private string[] pieces = new string[42]; protected override void OnInitialized() { State.ResetBoard(); } }
Esto asigna una cadena vacía a la clase CSS de cada intervalo de piezas de juego. Una cadena vacía para una clase CSS impide que las piezas del juego aparezcan en la pantalla, ya que no se les aplica ningún estilo.
Control de la colocación de las piezas del juego
Vamos a agregar un método para controlar cuándo un jugador coloca una pieza en una columna. La clase GameState
sabe cómo asignar la fila correcta para la pieza del juego e informa de la fila en la que aterriza. Podemos usar esta información para asignar clases CSS que representan el color del jugador, la ubicación final de la pieza y una animación CSS de colocación.
Llamamos a este método PlayPiece
y acepta un parámetro de entrada que especifica la columna que elige el jugador.
Agregue este código debajo de la matriz
pieces
que hemos definido en el paso anterior.private void PlayPiece(byte col) { var player = State.PlayerTurn; var turn = State.CurrentTurn; var landingRow = State.PlayPiece(col); pieces[turn] = $"player{player} col{col} drop{landingRow}"; }
Esto es lo que hace el código PlayPiece
:
- Indicamos al estado del juego que coloque una pieza en la columna enviada denominada
col
y capture la fila en la que ha aterrizado la pieza. - A continuación, podemos definir las tres clases CSS que se deben asignar a la pieza de juego para identificar qué jugador está actuando actualmente, la columna en la que se ha colocado la pieza y la fila de aterrizaje.
- La última línea del método asigna estas clases a esa pieza de juego en la matriz
pieces
.
Si busca en el archivo Board.razor.css proporcionado, encontrará las clases CSS que coinciden con la columna, la fila y el turno del jugador.
El efecto resultante es que la pieza del juego se coloca en la columna y se anima para caer en la fila más baja cuando se llama a este método.
Elección de una columna
A continuación, debemos colocar algunos controles que permitan a los jugadores elegir una columna y llamar a nuestro nuevo método PlayPiece
. Usamos el carácter "🔽" para indicar que puede colocar una pieza en esta columna.
Encima de la etiqueta inicial
<div>
, agregue una fila de botones en los que se puede hacer clic:<nav> @for (byte i = 0; i < 7; i++) { var col = i; <span title="Click to play a piece" @onclick="() => PlayPiece(col)">🔽</span> } </nav>
El atributo
@onclick
especifica un controlador de eventos para el evento de clic. Pero para controlar los eventos de la interfaz de usuario, es necesario representar un componente de Blazor mediante un modo de representación interactiva. De forma predeterminada, los componentes de Blazor se representan estáticamente desde el servidor. Podemos aplicar un modo de representación interactiva a un componente mediante el atributo@rendermode
.Actualice el componente
Board
en la página deHome
para que use el modo de representaciónInteractiveServer
.<Board @rendermode="InteractiveServer" />
El modo de representación
InteractiveServer
controla los eventos de interfaz de usuario de los componentes del servidor a través de una conexión WebSocket con el explorador.Ejecute la aplicación con estos cambios. Debería tener este aspecto ahora:
Mejor, cuando seleccionamos uno de los botones de colocación de la parte superior, se puede observar el siguiente comportamiento:
¡Un gran avance! Ahora podemos agregar piezas al tablero. El objeto GameState
es lo suficientemente inteligente como para dinamizar entre los dos jugadores. Continúe y seleccione más botones de colocación y observe los resultados.
Control de errores y victoria
Si juega con el juego con su configuración actual, verá que genera errores al intentar colocar demasiadas piezas en la misma columna y cuando un jugador gana la partida.
Vamos a dejar claro el estado actual de nuestro juego agregando algunos indicadores y control de errores a nuestro tablero. Agregue un área de estado encima del tablero y debajo de los botones de colocación.
Inserte el marcado siguiente después del elemento
nav
:<article> @winnerMessage <button style="@ResetStyle" @onclick="ResetGame">Reset the game</button> <br /> <span class="alert-danger">@errorMessage</span> <span class="alert-info">@CurrentTurn</span> </article>
Este marcado nos permite mostrar indicadores para:
- Anunciar un ganador del juego
- Un botón que nos permite reiniciar el juego
- Mensajes de error
- Turno del jugador actual
Ahora vamos a rellenar algunas lógicas que establecen estos valores.
Agregue el código siguiente después de la matriz de piezas:
private string[] pieces = new string[42]; private string winnerMessage = string.Empty; private string errorMessage = string.Empty; private string CurrentTurn => (winnerMessage == string.Empty) ? $"Player {State.PlayerTurn}'s Turn" : ""; private string ResetStyle => (winnerMessage == string.Empty) ? "display: none;" : "";
- La propiedad
CurrentTurn
se calcula automáticamente en función del estado dewinnerMessage
y de la propiedadPlayerTurn
deGameState
. ResetStyle
se calcula en función del contenido deWinnerMessage
. Si haywinnerMessage
, hacemos que el botón de restablecimiento aparezca en la pantalla.
- La propiedad
Vamos a controlar el mensaje de error cuando se coloca una pieza. Agregue una línea para borrar el mensaje de error y, a continuación, encapsule el código en el método
PlayPiece
con un bloquetry...catch
para establecererrorMessage
si se ha producido una excepción:errorMessage = string.Empty; try { var player = State.PlayerTurn; var turn = State.CurrentTurn; var landingRow = State.PlayPiece(col); pieces[turn] = $"player{player} col{col} drop{landingRow}"; } catch (ArgumentException ex) { errorMessage = ex.Message; }
Nuestro indicador del controlador de errores es sencillo y usa el marco CSS de arranque para mostrar un error en modo de peligro.
A continuación, vamos a agregar el método
ResetGame
que nuestro botón desencadena para reiniciar un juego. Actualmente, la única manera de reiniciar un juego es actualizar la página. Este código nos permite permanecer en la misma página.void ResetGame() { State.ResetBoard(); winnerMessage = string.Empty; errorMessage = string.Empty; pieces = new string[42]; }
Ahora, nuestro método
ResetGame
tiene la siguiente lógica:- Restablecer el estado del tablero.
- Ocultar nuestros indicadores.
- Restablecer la matriz de piezas en una matriz vacía de 42 cadenas.
Esta actualización debería permitirnos volver a jugar al juego, y ahora vemos un indicador justo encima del tablero que declara el turno del jugador y, en última instancia, la finalización del juego.
Todavía tenemos una situación en la que no podemos seleccionar el botón de restablecimiento. Vamos a agregar cierta lógica en el método
PlayPiece
que detecta el final de la partida.Vamos a detectar si hay un ganador de la partida agregando una expresión switch después de nuestro bloque
try...catch
enPlayPiece
.winnerMessage = State.CheckForWin() switch { GameState.WinState.Player1_Wins => "Player 1 Wins!", GameState.WinState.Player2_Wins => "Player 2 Wins!", GameState.WinState.Tie => "It's a tie!", _ => "" };
El método
CheckForWin
devuelve una enumeración que informa del jugador, si alguno ha ganado el juego o si el juego termina en empate. Esta expresión de conmutador establecerá el campowinnerMessage
adecuadamente si se produce un juego sobre el estado.Ahora, cuando jugamos y llegamos a un escenario de fin de partida, aparecen estos indicadores:
Resumen
Hemos aprendido mucho sobre Blazor y hemos creado un pequeño juego pulido. Estas son algunas de las aptitudes que hemos aprendido:
- Crear un componente
- Agregar el componente a la página principal
- Usar la inserción de dependencias para administrar el estado de un juego
- Hacer que el juego sea interactivo con controladores de eventos para colocar piezas y restablecer el juego
- Escribir un controlador de errores para notificar el estado del juego
- Agregar parámetros a nuestro componente
El proyecto que hemos creado es un juego sencillo, y podría hacer mucho más con él. ¿Busca algunos desafíos para mejorarlo?
Desafíos
Tenga en cuenta los siguientes desafíos:
- Para que la aplicación sea más pequeña, quite el diseño predeterminado y páginas adicionales.
- Mejore los parámetros del componente
Board
para poder pasar cualquier valor CSS de color válido. - Mejore la apariencia de los indicadores con algunos diseños CSS y HTML.
- Introduzca efectos de sonido.
- Agregue un indicador visual y evite que se use un botón de colocación cuando la columna esté llena.
- Agregue funcionalidades de red para que pueda jugar con un amigo en su navegador.
- Inserte el juego en una aplicación .NET MAUI con Blazor y juegue en su teléfono o tableta.
¡Feliz programación y diviértase!