Ejercicio: Lógica del juego

Completado

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.

  1. Copie el archivo GameState.cs en la raíz del proyecto.

  2. 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 componente Board.

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

  1. Agregue un método OnInitialized con una llamada a ResetBoard, 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.

  1. 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];
    
  2. 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.

  1. 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:

  1. 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.
  2. 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.
  3. 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.

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

  2. Actualice el componente Board en la página de Home para que use el modo de representación InteractiveServer.

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

  3. Ejecute la aplicación con estos cambios. Debería tener este aspecto ahora:

    Captura de pantalla del tablero de Conecta 4.

    Mejor, cuando seleccionamos uno de los botones de colocación de la parte superior, se puede observar el siguiente comportamiento:

    Captura de pantalla de la animación de Conecta 4.

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

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

  2. 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 de winnerMessage y de la propiedad PlayerTurn de GameState.
    • ResetStyle se calcula en función del contenido de WinnerMessage. Si hay winnerMessage, hacemos que el botón de restablecimiento aparezca en la pantalla.
  3. 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 bloque try...catch para establecer errorMessage 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.

    Captura de pantalla del juego hasta el momento, con un tablero y piezas.

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

    Captura de pantalla en la que se muestra 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.

  5. Vamos a detectar si hay un ganador de la partida agregando una expresión switch después de nuestro bloque try...catch en PlayPiece.

    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 campo winnerMessage adecuadamente si se produce un juego sobre el estado.

    Ahora, cuando jugamos y llegamos a un escenario de fin de partida, aparecen estos indicadores:

    Captura de pantalla en la que se muestra la opción para restablecer el juego.

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!