井字游戏第0部分:比较苗条和反应
井字游戏第1部分:Svelte and Canvas 2D
井字游戏第2部分:无状态复原/重做
井字游戏,第3部分:使用命令存储撤消/重做
井字游戏第4部分:使用HTTP与Flask后端交互
井字游戏(Tic Tac Toe)第1部分的继续,我们在Svelte上开始开发此游戏。 在这一部分中,我们将结束游戏。 添加撤消/重做团队,随机进入游戏的任何步骤,与对手交替移动,显示游戏状态,确定获胜者。
撤消/重做命令
REPL代码
此时,“撤消/重做”命令已添加到应用程序中。 向历史记录存储添加了push和redo方法。
undo: () => update(h => { h.undo(); return h; }), redo: () => update(h => { h.redo(); return h; }),
将push , redo , canUndo , canRedo方法添加到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) {
撤消和重做按钮已添加到应用程序组件。 如果无法执行命令,则将其禁用。
<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 / 。