井字游戏第2部分:无状态复原/重做

井字游戏第0部分:比较苗条和反应
井字游戏第1部分:Svelte and Canvas 2D
井字游戏第2部分:无状态复原/重做
井字游戏,第3部分:使用命令存储撤消/重做
井字游戏第4部分:使用HTTP与Flask后端交互

井字游戏(Tic Tac Toe)第1部分的继续,我们在Svelte上开始开发此游戏。 在这一部分中,我们将结束游戏。 添加撤消/重做团队,随机进入游戏的任何步骤,与对手交替移动,显示游戏状态,确定获胜者。


撤消/重做命令

REPL代码


此时,“撤消/重做”命令已添加到应用程序中。 向历史记录存储添加了pushredo方法。


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

将pushredocanUndocanRedo方法添加到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++; } 

历史记录方法已添加到删除当前状态到最后一个状态的push方法中。 如果我们多次执行“ 撤消”命令并单击播放字段,那么当前到最后一个右边的所有状态将从存储中删除,并添加一个新状态。


 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); } 

撤消重做按钮已添加到应用程序组件。 如果无法执行命令,则将其禁用。


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

改变路线

REPL代码


单击鼠标后,十字或脚趾的交替出现。


clickCell()方法已从其历史记录存储库中删除,所有方法代码均已转移到Board组件的handleClick()处理程序中。


 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); } 

这样就消除了以前犯的错误;存储库依赖于此特定游戏的逻辑。 现在,此错误已得到解决,该存储库无需更改即可在其他游戏和应用程序中重用。


以前,游戏步骤的状态仅由9个值的数组描述。 现在,游戏状态由包含数组和xIsNext属性的对象确定。 游戏开始时对该对象的初始化如下所示:


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

而且还应注意, 历史存储现在可以感知以任何方式描述的状态。


随机访问移动历史

REPL代码


历史记录存储区中,添加了setCurrent(当前)方法,通过该方法可以设置游戏的选定当前状态。


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

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

App组件中,以按钮的形式添加了移动的历史记录。


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

确定赢家,显示比赛状态

REPL代码


在单独的helpers.js文件中添加了确定赢家的calculateWinner()的功能:


 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; } 

添加状态 衍生工具来确定游戏的状态,此处确定游戏的结果:获胜者或平局:


 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; } ); 

游戏状态的输出已添加到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> 

棋盘组件中,已将限制添加到handleClick() click的句柄处理程序中:即使在游戏结束后也无法单击已填充的单元格。


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

游戏结束了! 在下一篇文章中,我们将考虑使用Command模式实现同一游戏的实现,即 与存储撤消/重做命令,而不是存储单个状态。


GitHub存储库

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


在本地计算机上安装游戏:


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

我们在以下地址的浏览器中启动游戏: http:// localhost:5000 /

Source: https://habr.com/ru/post/zh-CN459630/


All Articles