Tic Tac Toe Parte 0: Comparando Svelte e Reagir
Tic Tac Toe Parte 1: Svelte e Canvas 2D
Tic Tac Toe Parte 2: Desfazer / Refazer sem estado
Tic Tac Toe, parte 3: Desfazer / Refazer com armazenamento de comando
Tic Tac Toe Parte 4: Interagindo com o back-end do frasco usando HTTP
No artigo "Comparison: Svelte and React" , tentei repetir o desenvolvimento do jogo Tic Tac Toe. Lá, completei apenas a primeira parte do tutorial original do React sem apoiar o histórico de movimentos. Neste artigo, iniciaremos o desenvolvimento deste jogo usando a estrutura Svelte, com suporte para o histórico de jogadas. A história dos movimentos é na verdade um sistema de Desfazer / Refazer. No tutorial original sobre React, o sistema Desfazer / Refazer com armazenamento de estado com acesso aleatório a qualquer estado é implementado. Ao implementar o sistema Desfazer / Refazer, o padrão Comando geralmente é usado e os comandos Desfazer / Refazer são armazenados na lista de comandos. Tentaremos implementar essa abordagem mais tarde; agora, executaremos o sistema Desfazer / Refazer com armazenamento de estado.
O desenvolvimento usa a solução arquitetônica Flux usando armazenamento. Esta é uma seção separada no artigo.
Código inicial
Código 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(); </script> <canvas bind:this={canvas} width={boardWidth} height={boardHeight} ></canvas>
O código inicial exibe uma grade vazia. É exibido usando o elemento de tela HTML5. Para obter mais informações sobre o uso desse elemento, consulte o artigo anterior Developing Breakout on Svelte . Como desenhar uma grade espionada aqui . O componente do tabuleiro pode ser reutilizado em outros jogos. Alterando as variáveis width e height , é possível redimensionar a grade; alterando os valores das variáveis cellWidth e cellHeight, você pode redimensionar a célula.
Preencher células com zeros
Código REPL
Na função onMount (), adicionou a saída de zeros nas células após a saída da grade. E alguns números mágicos relacionados ao posicionamento de valores nas células.
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();
Adicionar armazenamento de estado
Código REPL
Nesta seção, verificamos as alterações de estado usando o repositório do usuário . Adicionado armazenamento de estado em um arquivo stores.js separado, esse armazenamento é importado para os dois componentes: App e Board. Os métodos state1 e state2 que alteram o estado do jogo são definidos nesta loja.
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();
Adicionados dois botões Estado 1 e Estado 2 ao componente App. Ao clicar nos botões, chamamos os métodos correspondentes no repositório.
<button on:click={state.state1}>State 1</button> <button on:click={state.state2}>State 2</button>
No componente Placa, mudei a linha de saída de zeros para a saída de dados de seu armazenamento de estado . Aqui usamos a assinatura automática para armazenamento .
ctx.fillText($state[k], j * cellWidth + d + 1, (i + 1) * cellHeight - d);
Nesta fase, o campo do jogo é preenchido com zeros por padrão, clique no botão Estado 1 - o campo é preenchido com unidades, clique no botão Estado 2 - o campo é preenchido com duques.
Preenchendo uma célula com um clique do mouse
Código REPL
No armazenamento de estado , adicionei o método setCell () , que preenche a célula selecionada com uma cruz.
setCell: (i) => update(a => {a[i] = 'X'; return a;}),
Um manipulador de eventos com clique do mouse foi adicionado à tela, aqui determinamos o índice da célula e chamamos o método setCell () do armazenamento de estado .
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); }
Por padrão, o campo de jogo é preenchido com zeros, clicamos em qualquer célula e o zero é substituído por uma cruz.
História dos movimentos
Código REPL
Deixe-me lembrá-lo de que atualmente estamos executando o sistema Desfazer / Refazer com armazenamento de estado com acesso aleatório.
import { writable } from 'svelte/store'; class History { constructor() { this.history = new Array; this.current = -1; } currentState() { return this.history[this.current]; } push(state) {
O armazenamento de estado foi excluído, o armazenamento de histórico foi adicionado para armazenar o histórico de movimentos. Nós o descrevemos usando a classe History . Para armazenar os estados, use a matriz do histórico . Às vezes, ao implementar o sistema Desfazer / Refazer, duas pilhas LIFO são usadas: desfazer e refazer a pilha. Usamos uma única matriz de histórico para armazenar estados no Histórico . A propriedade atual é usada para determinar o estado atual do jogo. Todos os estados do histórico, desde o início da matriz até o estado com o índice atual , por assim dizer, estão na lista Desfazer e todos os outros na lista Refazer. Diminuindo ou aumentando a propriedade atual , ou seja, executando os comandos Desfazer ou Refazer, selecionamos o estado mais próximo do início ou do final do jogo. Os métodos de desfazer e refazer ainda não foram implementados; eles serão adicionados posteriormente. Os métodos CurrentState () e push () foram adicionados à classe History . O método currentState () retorna o estado atual do jogo, usando o método push () , adicionamos um novo estado ao histórico de movimentação.
No componente App , removemos os botões Estado 1 e Estado 2 . E adicionou um botão:
<button on:click={() => history.push(Array(9).fill($history.current + 1))}>Push</button>
Ao clicar neste botão, um novo estado é adicionado ao histórico de movimentos, a matriz do histórico é simplesmente preenchida com o valor atual do índice de estado atual .
No componente Placa , exiba o estado atual do histórico de movimentos. Veja como usar a assinatura automática para armazenamento :
ctx.fillText($history.currentState()[k], j * cellWidth + d + 1, (i + 1) * cellHeight - d);
No método push do armazenamento de histórico , você pode adicionar saída ao console do navegador e observar como ela muda depois de clicar no botão Push .
h.push(state); console.log(h); return h;
Clicando em uma célula
Código REPL
No componente App , o botão Push foi removido.
O método clickCell foi definido no armazenamento de histórico . Aqui, criamos uma cópia completa do estado do jogo, alteramos o estado da célula selecionada e adicionamos um novo estado ao histórico de movimentação:
clickCell: (i) => update(h => {
No componente Placa , a chamada do método de armazenamento callClick () foi adicionada à função handleClick () :
history.clickCell(i);
No console do navegador, também podemos ver como o estado do histórico de movimentos muda após cada clique do mouse.
Nos artigos a seguir, terminaremos o jogo até o fim, com uma interface para cancelar / retornar etapas e acesso aleatório a qualquer etapa do jogo. Considere uma implementação do sistema Desfazer / Refazer usando o padrão de design do Comando. Considere a interação com o back-end, o jogador competirá com o agente intelectual no back-end.
Arquitetura de fluxo
No desenvolvimento deste jogo, é observado o uso da solução arquitetônica do Flux . As ações são implementadas como métodos na definição de armazenamento do histórico no arquivo stores.js . Existe um repositório de histórico, descrito na forma da classe Histórico . As visualizações são implementadas como componentes de App e Board . Para mim, tudo isso é igual à arquitetura MVC , vista lateral. Ações - controlador, armazenamento - modelo, visão - visão. As descrições de ambas as arquiteturas praticamente coincidem.
Repositório do GitHub
https://github.com/nomhoi/tic-tac-toe-part1
Instalando o jogo no computador local:
git clone https://github.com/nomhoi/tic-tac-toe-part1.git cd tic-tac-toe-part1 npm install npm run dev
Iniciamos o jogo em um navegador no endereço: http: // localhost: 5000 / .