<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近開始接觸React
,我認為讀官方檔案是最快上手一門技術的途徑了,恰好React
的官方檔案中有這樣一個井字棋遊戲的demo
,學習完後能夠快速上手React
,這是我學習該demo
的總結
首先看看這個遊戲都有哪些需求吧
X
和O
,每次落棋後需要切換到下一個玩家首先宣告一下,我不會像官方檔案那樣一步步從底層實現,然後逐步狀態提升至父元件的方式講解,而是直接從全域性分析,分析涉及哪些狀態,應當由哪個元件管理以及這樣做的原因是什麼
先來思考一下整個遊戲會涉及什麼元件:
Board
Square
UI
以及遊戲的邏輯,所以要有一個Game
元件X
還是O
我們可以自頂向下分析,最頂層的狀態肯定是歷史記錄,因為它裡面儲存著每一步的棋盤,而棋盤本應該作為Board
元件的狀態的,但又由於有多個變動的棋盤(使用者點選歷史記錄切換棋盤時),所以不適合作為state
放到Board
元件中,而應當作為props
,由父元件Game
去控制當前展示的棋盤
而棋盤中的格子又是在棋盤中的,所以也導致本應該由棋盤格子Square
元件管理的格子內容狀態提升至Game
元件管理,存放在歷史記錄的每個棋盤物件中,所以Square
的棋盤內容也應當以props
的形式存在
下一步輪到哪個玩家是視棋盤的情況而定的,所以我認為應當放到歷史記錄的棋盤物件裡和棋盤一起進行管理,官方那種放到Game
的state
中而不是放到歷史記錄的每個棋盤中的做法我覺得不太合適
有了以上的分析,我們就可以開始寫我們的井字棋遊戲了!
首先使用vite
建立一個react
專案
pnpm create vite react-tic-tac-toe --template react-ts cd react-tic-tac-toe pnpm i code .
這裡我使用vscode
進行開發,當然,你也可以使用別的ide
(如Neovim
、WebStorm
)
由於使用的是ts
進行開發,所以我們可以在真正寫程式碼前先明確一下每個元件的props
和state
,一方面能夠讓自己理清一下各個元件的關係,另一方面也可以為之後編寫程式碼提供一個良好的型別提示
每個棋盤格中需要放棋子,這裡我使用字元X
和O
充當棋子,當然,棋盤上也可以不放棋子,所以設定一個squareContent
屬性
點選每個格子就是落棋操作,也就是要填充一個字元到格子中,根據前面的分析我們知道,填充的邏輯應當交由棋盤Board
元件處理,所以再新增一個onFillSquare
的prop
,它起到一個類似事件通知的作用,當呼叫這個函數的時候,會呼叫父元件傳入的函數,起到一個通知的作用
所以Square
元件的props
介面定義如下:
interface Props { squareContent: string | null; fillSquare: () => void; }
棋盤中要管理多個格子,所以肯定要有一個squares
狀態,用於控制各個格子
棋盤填充棋子的邏輯也應當交給Game
元件去完成,因為要維護歷史記錄,而棋盤的狀態都是儲存在歷史記錄中的,所以填充棋子也要作為Board
元件的一個prop
還要在棋盤上顯示下一個玩家以及在對局結束時顯示贏家資訊,所以要有一個statusMsg
的prop
顯示對局資訊,以及nextPlayer
記錄下一個玩家
最終Board
元件的props
介面定義如下:
interface Props { squares: Squares; statusMsg: string; nextPlayer: Player; fillSquare: (squareIdx: number) => void; }
要記錄歷史資訊,以及通過歷史記錄下標獲取到對應歷史記錄的棋盤,所以它的State
如下
interface State { history: BoardPropsNeeded[]; historyIdx: number; }
export interface Props { squareContent: string | null; fillSquare: () => void; } export type Squares = Omit<Props, "fillSquare">[]; export default function Square(props: Props) { return ( <div className="square" onClick={() => props.fillSquare()}> {props.squareContent} </div> ); }
import React from "react"; import Square from "./Square"; import type { Squares } from "./Square"; export type Player = "X" | "O"; export interface Props { squares: Squares; statusMsg: string; nextPlayer: Player; fillSquare: (squareIdx: number) => void; } export default class Board extends React.Component<Props> { renderSquare(squareIdx: number) { const { squareContent } = this.props.squares[squareIdx]; return ( <Square squareContent={squareContent} fillSquare={() => this.props.fillSquare(squareIdx)} /> ); } render(): React.ReactNode { return ( <div> <h1 className="board-status-msg">{this.props.statusMsg}</h1> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } }
import React from "react"; import Board from "./Board"; import type { Props as BoardProps, Player } from "./Board"; import type { Squares } from "./Square"; type BoardPropsNeeded = Omit<BoardProps, "fillSquare">; interface State { history: BoardPropsNeeded[]; historyIdx: number; } export default class Game extends React.Component<any, State> { constructor(props: any) { super(props); this.state = { history: [ { squares: new Array(9).fill({ squareContent: null }), nextPlayer: "X", statusMsg: "Next player: X", }, ], historyIdx: 0, }; } togglePlayer(): Player { const currentBoard = this.state.history[this.state.historyIdx]; return currentBoard.nextPlayer === "X" ? "O" : "X"; } fillSquare(squareIdx: number) { const history = this.state.history.slice(0, this.state.historyIdx + 1); const currentBoard = history[this.state.historyIdx]; // 先判斷一下對局是否結束 結束的話就不能繼續落棋 // 當前格子有棋子的話也不能落棋 if ( calcWinner(currentBoard.squares) || currentBoard.squares[squareIdx].squareContent !== null ) return; const squares = currentBoard.squares.slice(); squares[squareIdx].squareContent = currentBoard.nextPlayer; this.setState({ history: history.concat([ { squares, statusMsg: currentBoard.statusMsg, nextPlayer: this.togglePlayer(), }, ]), historyIdx: history.length, }); } jumpTo(historyIdx: number) { this.setState({ historyIdx, }); } render(): React.ReactNode { const history = this.state.history; const currentBoard = history[this.state.historyIdx]; const { nextPlayer } = currentBoard; const winner = calcWinner(currentBoard.squares); let boardStatusMsg: string; if (winner !== null) { boardStatusMsg = `Winner is ${winner}!`; } else { boardStatusMsg = `Next player: ${nextPlayer}`; } const historyItems = history.map((_, idx) => { const desc = idx ? `Go to #${idx}` : `Go to game start`; return ( <li key={idx}> <button className="history-item" onClick={() => this.jumpTo(idx)}> {desc} </button> </li> ); }); return ( <div className="game"> <div className="game-board"> <Board squares={currentBoard.squares} statusMsg={boardStatusMsg} nextPlayer={nextPlayer} fillSquare={(squareIdx: number) => this.fillSquare(squareIdx)} /> </div> <div className="divider"></div> <div className="game-info"> <h1>History</h1> <ol>{historyItems}</ol> </div> </div> ); } } const calcWinner = (squares: Squares): Player | null => { // 贏的時候的棋局情況 const winnerCase = [ [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 < winnerCase.length; i++) { const [a, b, c] = winnerCase[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a].squareContent as Player; } } return null; };
到此這篇關於React中井字棋遊戲的實現範例的文章就介紹到這了,更多相關React 井字棋遊戲內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45