Tic Tac Toe Parte 2: Desfazer / Refazer sem estado

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

Continuação do artigo Tic Tac Toe, parte 1 , em que iniciamos o desenvolvimento deste jogo no Svelte . Nesta parte, terminaremos o jogo até o fim. Adicione equipes de Desfazer / Refazer , acesso aleatório a qualquer etapa do jogo, movimentos alternados com o oponente, exibindo o status do jogo, determinando o vencedor.


Comandos Desfazer / Refazer

Código REPL


Neste ponto, os comandos Desfazer / Refazer foram adicionados ao aplicativo. Adicionados métodos push e refazer ao repositório de histórico .


undo: () => update(h => { h.undo(); return h; }), redo: () => update(h => { h.redo(); return h; }), 

Os métodos push , redo , canUndo , canRedo são adicionados à classe History .


 canUndo() { return this.current > 0; } canRedo() { return this.current < this.history.length - 1; } undo() { if (this.canUndo()) this.current--; } redo() { if (this.canRedo()) this.current++; } 

O método histórico foi adicionado ao método push de excluir todos os estados do atual para o último. Se executarmos o comando Desfazer várias vezes e clicarmos no campo de jogo, todos os estados da direita até a última serão excluídos da loja e um novo estado será adicionado.


 push(state) { // remove all redo states if (this.canRedo()) this.history.splice(this.current + 1); // add a new state this.current++; this.history.push(state); } 

Os botões Desfazer e Refazer foram adicionados ao componente Aplicativo . Se a execução dos comandos não for possível, eles serão desativados.


 <div> {#if $history.canUndo()} <button on:click={history.undo}>Undo</button> {:else} <button disabled>Undo</button> {/if} {#if $history.canRedo()} <button on:click={history.redo}>Redo</button> {:else} <button disabled>Redo</button> {/if} </div> 

Mudança de rumo

Código REPL


Uma aparência alternada de uma cruz ou dedo do pé após um clique do mouse.


O método clickCell () foi removido do repositório de histórico , todo o código do método foi transferido para o manipulador handleClick () do componente Board .


 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; const state = $history.currentState(); const squares = state.squares.slice(); squares[i] = state.xIsNext ? 'X' : 'O'; let newState = { squares: squares, xIsNext: !state.xIsNext, }; history.push(newState); } 

Assim, o erro cometido anteriormente foi eliminado; o repositório dependia da lógica desse jogo em particular. Agora, esse erro foi corrigido e o repositório pode ser reutilizado em outros jogos e aplicativos sem alterações.


Anteriormente, o estado de uma etapa do jogo era descrito apenas por uma matriz de 9 valores. Agora, o estado do jogo é determinado pelo objeto que contém a matriz e a propriedade xIsNext. A inicialização desse objeto no início do jogo é assim:


 let state = { squares: Array(9).fill(''), xIsNext: true, }; 

E também pode ser observado que o armazenamento do histórico agora pode perceber os estados descritos de qualquer maneira.


Acesso aleatório para mover o histórico

Código REPL


Na loja de histórico , o método setCurrent (current) foi adicionado, com o qual definimos o estado atual selecionado do jogo.


 setCurrent(current) { if (current >= 0 && current < this.history.length) this.current = current; } 

 setCurrent: (current) => update(h => { h.setCurrent(current); return h; }), 

No componente App , adicionou a exibição do histórico de movimentos na forma de botões.


 <ol> {#each $history.history as value, i} {#if i==0} <li><button on:click={() => history.setCurrent(i)}>Go to game start</button></li> {:else} <li><button on:click={() => history.setCurrent(i)}>Go to move #{i}</button></li> {/if} {/each} </ol> 

Determinando o vencedor, exibindo o status do jogo

Código REPL


Função adicionada para determinar o vencedor calcule Winner () em um arquivo helpers.js separado:


 export function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; } 

A derivada de status foi adicionada para determinar o status do jogo, aqui o resultado do jogo é determinado: vencedor ou empate:


 export const status = derived( history, $history => { if ($history.currentState()) { if (calculateWinner($history.currentState().squares)) return 1; else if ($history.current == 9) return 2; } return 0; } ); 

A saída do status do jogo foi adicionada ao componente App :


 <div class="status"> {#if $status === 1} <b>Winner: {!$history.currentState().xIsNext ? 'X' : 'O'}</b> {:else if $status === 2} <b>Draw</b> {:else} Next player: {$history.currentState().xIsNext ? 'X' : 'O'} {/if} </div> 

No componente Tabuleiro , foram adicionadas limitações ao manipulador de identificadores do clique handleClick () : é impossível clicar na célula preenchida, mesmo após o final do jogo.


 const state = $history.currentState(); if ($status == 1 || state.squares[i]) return; 

O jogo acabou! No próximo artigo, consideraremos a implementação do mesmo jogo usando o padrão Comando, ou seja, com o armazenamento de comandos Desfazer / Refazer em vez de armazenar estados individuais.


Repositório do GitHub

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


Instalando o jogo no computador local:


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

Iniciamos o jogo em um navegador no endereço: http: // localhost: 5000 / .

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


All Articles