Tic Tac Toe Partie 1: Svelte et Canvas 2D

Tic Tac Toe Partie 0: Comparaison de Svelte et React
Tic Tac Toe Partie 1: Svelte et Canvas 2D
Tic Tac Toe Partie 2: Annuler / Rétablir sans état
Tic Tac Toe, partie 3: Annuler / Rétablir avec stockage des commandes
Tic Tac Toe Partie 4: Interaction avec le backend Flask en utilisant HTTP

Dans l'article "Comparaison: Svelte et React" j'ai essayé de répéter le développement du jeu Tic Tac Toe. Là, je n'ai terminé que la première partie du didacticiel d'origine pour React sans prendre en charge l'historique des mouvements. Dans cet article, nous allons commencer le développement de ce jeu en utilisant le framework Svelte avec prise en charge de l'historique des mouvements. L'historique des mouvements est en fait un système Annuler / Rétablir. Dans le didacticiel d'origine sur React, le système Annuler / Rétablir avec stockage d'état avec accès aléatoire à n'importe quel état est implémenté. Lors de l'implémentation du système Annuler / Rétablir, le modèle de commande est généralement utilisé et les commandes Annuler / Rétablir sont stockées dans la liste des commandes. Nous allons essayer d'implémenter cette approche plus tard; nous allons maintenant exécuter le système Annuler / Rétablir avec stockage d'état.


Le développement utilise la solution architecturale Flux utilisant le stockage. Il s'agit d'une section distincte de l'article.


Code de démarrage

Code REPL


App.svelte
<script> import Board from './Board.svelte'; </script> <div class="game"> <div class="game-board"> <Board /> </div> <div class="game-info"> <div class="status">Next player: X</div> <div></div> <ol></ol> </div> </div> <style> .game { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; display: flex; flex-direction: row; } .game-info { margin-left: 20px; } .status { margin-bottom: 10px; } ol { padding-left: 30px; } </style> 

Board.svelte
 <script> import { onMount } from 'svelte'; export let width = 3; export let height = 3; export let cellWidth = 34; export let cellHeight = 34; export let colorStroke = "#999"; let boardWidth = 1 + (width * cellWidth); let boardHeight = 1 + (height * cellHeight); let canvas; onMount(() => { const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, boardWidth, boardHeight); ctx.beginPath(); // vertical lines for (let x = 0; x <= boardWidth; x += cellWidth) { ctx.moveTo(0.5 + x, 0); ctx.lineTo(0.5 + x, boardHeight); } // horizontal lines for (let y = 0; y <= boardHeight; y += cellHeight) { ctx.moveTo(0, 0.5 + y); ctx.lineTo(boardWidth, 0.5 + y); } // draw the board ctx.strokeStyle = colorStroke; ctx.stroke(); ctx.closePath(); }); </script> <canvas bind:this={canvas} width={boardWidth} height={boardHeight} ></canvas> 

Le code de démarrage affiche une grille vide. Il est affiché à l'aide de l'élément de toile HTML5. Pour plus d'informations sur l'utilisation de cet élément, consultez l'article précédent Developing Breakout on Svelte . Comment dessiner une grille espionnée ici . Le composant Board peut être réutilisé dans d'autres jeux. En modifiant les variables de largeur et de hauteur , vous pouvez redimensionner la grille; en modifiant les valeurs des variables cellWidth et cellHeight, vous pouvez redimensionner la cellule.


Remplir les cellules de zéros

Code REPL
Dans la fonction onMount () a ajouté la sortie des zéros dans les cellules après la sortie de la grille. Et quelques nombres magiques liés au positionnement des valeurs dans les cellules.


 ctx.beginPath(); ctx.font = "bold 22px Century Gothic"; let d = 8; for (let i = 0; i < height; i+=1) { for (let j = 0; j < width; j+=1) { ctx.fillText("O", j * cellWidth + d + 1, (i + 1) * cellHeight - d); } } ctx.closePath(); 

Ajouter un stockage d'état

Code REPL
Dans cette section, nous vérifions les changements d'état à l'aide du référentiel d'utilisateurs . Ajout d' un stockage d' état dans un fichier stores.js distinct, ce stockage est importé dans les deux composants: App et Board. Les méthodes state1 et state2 qui modifient l'état du jeu sont définies dans ce référentiel.


 import { writable } from 'svelte/store'; function createState() { const { subscribe, set, update } = writable(Array(9).fill('O')); return { subscribe, state1: () => set(Array(9).fill('1')), state2: () => set(Array(9).fill('2')), }; } export const state = createState(); 

Ajout de deux boutons State 1 et State 2 au composant App. En cliquant sur les boutons, nous appelons les méthodes correspondantes dans le référentiel.


 <button on:click={state.state1}>State 1</button> <button on:click={state.state2}>State 2</button> 

Dans le composant Board, j'ai changé la ligne de sortie des zéros en sortie des données de leur stockage d' état . Ici, nous utilisons l'abonnement automatique au stockage .


 ctx.fillText($state[k], j * cellWidth + d + 1, (i + 1) * cellHeight - d); 

À ce stade, le champ de jeu est rempli de zéros par défaut, cliquez sur le bouton État 1 - le champ est rempli d'unités, cliquez sur le bouton État 2 - le champ est rempli de deux.


Remplir une cellule avec un clic de souris

Code REPL
Dans le magasin d' état , j'ai ajouté la méthode setCell () , qui remplit la cellule sélectionnée avec une croix.


 setCell: (i) => update(a => {a[i] = 'X'; return a;}), 

Un gestionnaire d'événements de clic de souris a été ajouté au canevas, ici nous déterminons l'index de la cellule et appelons la méthode setCell () du stockage d' état .


 function handleClick(event) { let x = Math.trunc((event.offsetX + 0.5) / cellWidth); let y = Math.trunc((event.offsetY + 0.5) / cellHeight); let i = y * width + x; state.setCell(i); } 

Par défaut, le terrain de jeu est rempli de zéros, on clique sur n'importe quelle cellule, le zéro est remplacé par une croix.


Histoire des déménagements

Code REPL
Permettez-moi de vous rappeler que nous utilisons actuellement le système Annuler / Rétablir avec stockage d'état avec accès aléatoire.


 import { writable } from 'svelte/store'; class History { constructor() { this.history = new Array; this.current = -1; } currentState() { return this.history[this.current]; } push(state) { // TODO: remove all redo states this.current++; this.history.push(state); } } function createHistory() { const { subscribe, set, update } = writable(new History); return { subscribe, push: (state) => update(h => { h.push(state); return h; }), }; } export const history = createHistory(); 

Le magasin d' état a été supprimé, le magasin d' historique a été ajouté pour stocker l'historique des déplacements. Nous le décrivons en utilisant la classe History . Pour stocker les états, utilisez le tableau d' historique . Parfois, lors de l'implémentation du système Undo / Redo, deux piles LIFO sont utilisées: undo-stack et redo-stack. Nous utilisons un seul tableau d' historique pour stocker les états dans l' historique . La propriété actuelle est utilisée pour déterminer l'état actuel du jeu. Tous les états de l' historique depuis le début du tableau jusqu'à l'état avec l'index en cours, pour ainsi dire, sont dans la liste Annuler, et tous les autres sont dans la liste Rétablir. En diminuant ou en augmentant la propriété actuelle , en d'autres termes, en exécutant les commandes Annuler ou Rétablir, nous sélectionnons l'état plus près du début ou de la fin du jeu. Les méthodes d'annulation et de rétablissement ne sont pas encore implémentées, elles seront ajoutées ultérieurement. Les méthodes CurrentState () et push () ont été ajoutées à la classe History . La méthode currentState () renvoie l'état actuel du jeu, en utilisant la méthode push () nous ajoutons un nouvel état à l'historique des déplacements.


Dans le composant App , nous avons supprimé les boutons State 1 et State 2 . Et ajouté un bouton poussoir:


 <button on:click={() => history.push(Array(9).fill($history.current + 1))}>Push</button> 

En cliquant sur ce bouton, un nouvel état est ajouté à l'historique des déplacements, le tableau d' historique est simplement rempli avec la valeur d'index de l'état actuel .


Dans le composant Board , affichez l'état actuel de l'historique des déplacements. Voici comment utiliser l'abonnement automatique au stockage :


 ctx.fillText($history.currentState()[k], j * cellWidth + d + 1, (i + 1) * cellHeight - d); 

Dans la méthode push du magasin d' historique , vous pouvez ajouter une sortie à la console du navigateur et voir comment elle change après avoir cliqué sur le bouton Push .


 h.push(state); console.log(h); return h; 

Cliquer sur une cellule

Code REPL
Dans le composant App , le bouton poussoir a été supprimé.


La méthode clickCell a été définie dans le magasin d' historique . Ici, nous créons une copie complète de l'état du jeu, changeons l'état de la cellule sélectionnée et ajoutons un nouvel état à l'historique des déplacements:


 clickCell: (i) => update(h => { // create a copy of the current state const state = h.currentState().slice(); // change the value of the selected cell to X state[i] = 'X'; // add the new state to the history h.push(state); console.log(h.history); return h; }), 

Dans le composant Board , l'appel de la méthode de stockage callClick () a été ajouté à la fonction handleClick () :


 history.clickCell(i); 

Dans la console du navigateur, nous pouvons également voir comment l'état de l'historique des mouvements change après chaque clic de souris.


Dans les articles suivants, nous terminerons le jeu jusqu'à la fin, avec une interface pour annuler / retourner les étapes et un accès aléatoire à n'importe quelle étape du jeu. Envisagez une implémentation du système Annuler / Rétablir à l'aide du modèle de conception de commande. Considérez l'interaction avec le backend, le joueur sera en concurrence avec l'agent intellectuel dans le backend.


Architecture de flux

Dans le développement de ce jeu, l'utilisation de la solution architecturale Flux est observée. Les actions sont implémentées en tant que méthodes dans la définition du magasin d' historique dans le fichier stores.js . Il existe un référentiel historique, décrit sous la forme de la classe History . Les vues sont implémentées en tant que composants App et Board . Pour moi, tout cela est le même que l' architecture MVC , vue de côté. Actions - contrôleur, stockage - modèle, vue - vue. Les descriptions des deux architectures coïncident pratiquement.


Dépôt GitHub

https://github.com/nomhoi/tic-tac-toe-part1


Installation du jeu sur l'ordinateur local:


 git clone https://github.com/nomhoi/tic-tac-toe-part1.git cd tic-tac-toe-part1 npm install npm run dev 

Nous lançons le jeu dans un navigateur à l'adresse: http: // localhost: 5000 / .

Source: https://habr.com/ru/post/fr458752/


All Articles