Exercice - Logique de jeu
Dans cet exercice, nous ajoutons une logique de jeu à notre application pour être sûrs d’obtenir un jeu entièrement fonctionnel.
Pour que ce didacticiel ne dévie pas du sujet, à savoir l’apprentissage de Blazor, nous fournissons une classe appelée GameState
qui contient la logique pour gérer le jeu.
Ajout de l’état du jeu
Ajoutons la classe GameState
à votre projet, puis mettons-la à la disposition des composants en tant que service singleton par le biais de l’injection de dépendances.
Copiez le fichier GameState.cs à la racine de votre projet.
Ouvrez le fichier Program.cs à la racine du projet et ajoutez cette instruction qui configure
GameState
en tant que service de base de données unique dans votre application :builder.Services.AddSingleton<GameState>();
Nous pouvons maintenant injecter une instance de la classe
GameState
dans notre composantBoard
.Ajoutez la directive
@inject
suivante en haut du fichier Board.razor. La directive injecte l’état actuel du jeu dans le composant :@inject GameState State
Nous pouvons maintenant commencer à connecter notre composant
Board
à l’état du jeu.
Réinitialiser l’état
Commençons par réinitialiser l’état du jeu lorsque le composant Board
est peint pour la première fois à l’écran. Ajoutez du code pour réinitialiser l’état du jeu à l’initialisation du composant.
Ajoutez la méthode
OnInitialized
avec un appel àResetBoard
au sein du bloc@code
, en bas du fichier Board.razor comme suit :@code { protected override void OnInitialized() { State.ResetBoard(); } }
Lorsque le plateau est présenté pour la première fois à l’utilisateur, l’état est réinitialisé au début d’une partie.
Créer des pièces de jeu
Ensuite, allouons les 42 pièces de jeu possibles qui pourraient être jouées. Nous pouvons représenter les pièces de jeu sous la forme d’un tableau référencé par 42 éléments HTML sur le plateau. Nous pouvons déplacer et placer ces pièces en affectant un ensemble de classes CSS avec des positions de colonne et de ligne.
Pour conserver nos pièces de jeu, définissez un champ du tableau de chaînes dans le bloc de code :
private string[] pieces = new string[42];
Ajoutez du code à la section HTML qui crée 42 balises
span
, une pour chaque pièce de jeu, dans le même composant :@for (var i = 0; i < 42; i++) { <span class="@pieces[i]"></span> }
L’intégralité de votre code devrait ressembler à ceci :
<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(); } }
Cela affecte une chaîne vide à la classe CSS de chaque étendue de jeu. Une chaîne vide pour une classe CSS empêche les pièces de jeu d’apparaître à l’écran, car aucun style ne leur est appliqué.
Gérer le placement des pièces de jeu
Ajoutons une méthode pour gérer le moment où un joueur place une pièce de jeu dans une colonne. La classe GameState
sait comment attribuer la bonne ligne à la pièce de jeu et signale la ligne dans laquelle elle atterrit. Nous pouvons utiliser ces informations pour affecter des classes CSS représentant la couleur du joueur, l’emplacement final de la pièce et une animation de suppression CSS.
Nous appelons cette méthode PlayPiece
, laquelle accepte un paramètre d’entrée qui spécifie la colonne choisie par le joueur.
Ajoutez ce code sous le tableau
pieces
que nous avons défini à l’étape précédente.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}"; }
Voici ce que fait le code PlayPiece
:
- Nous disons à l’état du jeu de jouer une pièce dans la colonne envoyée appelée
col
et de capturer la ligne dans laquelle la pièce a atterri. - Nous pouvons ensuite définir les trois classes CSS à affecter à la pièce de jeu pour identifier le joueur qui est en train de jouer, la colonne dans laquelle la pièce a été placée et la ligne de destination.
- La dernière ligne de la méthode affecte ces classes à cette pièce de jeu dans le tableau
pieces
.
Si vous recherchez dans le fichier board.razor.css fourni, vous allez trouver les classes CSS correspondant à la colonne, à la ligne et au tour du lecteur.
L’effet résultant est que la pièce de jeu est placée dans la colonne et animée pour tomber dans la ligne la plus basse lorsque cette méthode est appelée.
Choix d’une colonne
Nous devons ensuite placer des contrôles qui permettent aux joueurs de choisir une colonne et d’appeler notre nouvelle méthode PlayPiece
. Nous utilisons le caractère « 🔽 » pour indiquer que vous pouvez déposer une pièce de jeu dans cette colonne.
Au-dessus de la balise
<div>
de départ, ajoutez une ligne de boutons cliquables :<nav> @for (byte i = 0; i < 7; i++) { var col = i; <span title="Click to play a piece" @onclick="() => PlayPiece(col)">🔽</span> } </nav>
L’attribut
@onclick
spécifie un gestionnaire d’événements pour l’événement de clic. Toutefois, pour gérer les événements d’IU, un composant Blazor doit être rendu à l’aide d’un mode de rendu interactif. Par défaut, les composants Blazor sont rendus statiquement à partir du serveur. Nous pouvons appliquer un mode de rendu interactif à un composant à l’aide de l’attribut@rendermode
.Mettez à jour le composant
Board
sur la pageHome
afin qu’il utilise le mode de renduInteractiveServer
.<Board @rendermode="InteractiveServer" />
Le mode de rendu
InteractiveServer
gère les événements d’IU de vos composants à partir du serveur au travers d’une connexion WebSocket avec le navigateur.Exécutez l’application avec ces modifications. Ce que vous obtenez doit désormais ressembler à ceci :
Encore mieux, lorsque nous sélectionnons l’un des boutons permettant de faire tomber une pièce en haut, le comportement suivant peut être observé :
Cela représente une grande avancée ! Nous pouvons maintenant ajouter des pièces au plateau. L’objet GameState
est suffisamment intelligent pour passer d’un joueur à l’autre. Sélectionnez d’autres boutons permettant de faire tomber une pièce et regardez les résultats.
Victoire et gestion des erreurs
Si vous jouez le jeu dans sa configuration actuelle, vous constatez qu’il génère des erreurs lorsque vous essayez de placer trop de pièces dans la même colonne et quand un joueur gagne une partie.
Assurons-nous que l’état actuel du jeu soit clair en ajoutant des indicateurs et une gestion des erreurs à notre panneau. Ajoutez une zone d’état au-dessus du panneau et sous les boutons permettant de déposer.
Insérons le balisage suivant après l’élément
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>
Ce balisage nous permet d’afficher les indicateurs pour :
- Annoncer qui a gagné
- Un bouton qui nous permet de recommencer une partie
- Messages d’erreur
- Le tour du joueur actuel
Renseignons à présent une logique qui définit ces valeurs.
Ajoutez le code suivant après le tableau de pièces :
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 propriété
CurrentTurn
est automatiquement calculé en fonction de l’état dewinnerMessage
et de la propriétéPlayerTurn
duGameState
. ResetStyle
est calculé en fonction du contenu de la propriétéWinnerMessage
. S’il existe unwinnerMessage
, nous faisons apparaître le bouton de réinitialisation à l’écran.
- La propriété
Gérons le message d’erreur lorsqu’une pièce est jouée. Ajoutez une ligne pour effacer le message d’erreur, puis wrappez le code dans la méthode
PlayPiece
avec un bloctry...catch
pour définir leerrorMessage
si une exception s’est produite :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; }
Notre indicateur de gestionnaire d’erreurs est simple et utilise le framework CSS Bootstrap pour afficher une erreur en mode danger.
Ensuite, nous allons ajouter la méthode
ResetGame
que notre bouton déclenche pour recommencer une partie. Actuellement, la seule façon de redémarrer un jeu consiste à actualiser la page. Ce code nous permet de rester sur la même page.void ResetGame() { State.ResetBoard(); winnerMessage = string.Empty; errorMessage = string.Empty; pieces = new string[42]; }
À présent, notre méthode
ResetGame
a la logique suivante :- Réinitialisez l’état du plateau.
- Masquez nos indicateurs.
- Réinitialisez le tableau de pièces à un tableau vide de 42 chaînes.
Cette mise à jour devrait nous permettre de jouer une nouvelle partie, et nous voyons maintenant un indicateur juste au-dessus du plateau déclarant à qui est le tour, puis la fin de la partie.
Nous avons toujours une situation dans laquelle nous ne pouvons pas sélectionner le bouton de réinitialisation. Ajoutons une logique dans la méthode
PlayPiece
qui détecte la fin de la partie.Nous allons détecter si la partie a un gagnant en ajoutant une expression switch après notre bloc
try...catch
dansPlayPiece
.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!", _ => "" };
La méthode
CheckForWin
retourne un enum qui indique quel joueur, le cas échéant, a gagné la partie ou si la partie finit sur une égalité. Cette expression de bascule définit le champwinnerMessage
de manière appropriée si un état de fin de partie a lieu.Maintenant, lorsque nous jouons et arrivons à un scénario de fin de partie, ces indicateurs s’affichent :
Résumé
Nous en avons beaucoup appris sur Blazor et créé un petit jeu soigné. Voici quelques-unes des compétences que nous avons apprises :
- Création d’un composant
- Ajout de ce composant à notre page d’accueil
- Utilisation de l’injection de dépendances pour gérer l’état d’une partie
- Ajout de gestionnaires d’événements pour rendre le jeu interactif et placer des pièces et réinitialiser le jeu
- Écriture d’un gestionnaire d’erreurs pour signaler l’état de la partie
- Ajout de paramètres à notre composant
Le projet que nous avons créé est un jeu simple, mais vous pourriez aller beaucoup plus loin. Vous êtes en quête de défis pour l’améliorer ?
Défis
En voici quelques-uns :
- Pour alléger l’application, supprimez la disposition par défaut et les pages superflues.
- Améliorez les paramètres du composant
Board
afin de pouvoir passer n’importe quelle valeur de couleur CSS valide. - Améliorez l’apparence des indicateurs avec une disposition CSS et HTML.
- Introduisez des effets sonores.
- Ajoutez un indicateur visuel et empêchez l’utilisation d’un bouton permettant de faire tomber une pièce lorsque la colonne est pleine.
- Ajoutez des fonctionnalités de mise en réseau pour pouvoir lire un ami dans son navigateur.
- Insérer le jeu dans une application .NET MAUI avec Blazor et y jouer sur votre téléphone ou tablette.
Heureux codage et amusez-vous bien !