Javascript:javascript
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Square(props) { return ( <button className={ props.winner ? 'square gold' : 'square' } onClick={props.onClick} > {props.value} </button> ); } class Board extends React.Component { isWinnerSquare(i) { if(this.props.winner && this.props.winner.line.findIndex(el => el === i) !== -1) { return true; } return null; } renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} winner={this.isWinnerSquare(i)} key={i} /> ); } render() { return ( <div className="board"> <div className="row-counters"> <div className="row-num">1</div> <div className="row-num">2</div> <div className="row-num">3</div> </div> { Array(3).fill(null).map((row, x) => { return ( <div className="board-row" key={x} > { Array(3).fill(null).map((square, y) => this.renderSquare(3 * x + y)) } </div> ) }) } <div className="column-counters"> <div className="column-num">a</div> <div className="column-num">b</div> <div className="column-num">c</div> </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [ { squares: Array(9).fill(null), position: null, } ], XIsNext: true, stepNumber: 0, historyReverse: false, } } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); const position = getPosition(i); // 當有玩家勝出時,或者某個 Square 已經被填充時,該函數不作任何處理直接返回。 if (caculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.XIsNext ? 'X' : 'O'; this.setState({ history: history.concat([{ squares, position, }]), XIsNext: !this.state.XIsNext, stepNumber: history.length, }); } jumpTo(step) { this.setState({ stepNumber: step, XIsNext: (step % 2) === 0, }); } historyReverse() { this.setState({ historyReverse: !this.state.historyReverse, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = caculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move + ` ( ${history[move].position.join(',')} )` : 'Go to game start'; const bold = move === this.state.stepNumber ? 'bold' : ''; return ( <li key={move} className={bold} > <button onClick={() => this.jumpTo(move)} >{desc}</button> </li> ); }) let movesView = this.state.historyReverse ? moves.reverse() : moves; let status; if (winner) { status = 'Winner: ' + winner.name; } else if(this.state.stepNumber === 9) { status = `Draw!`; } else { status = `Next player: ${ this.state.XIsNext ? 'X' : 'O'}`; } // console.log(history) return ( <div className="game"> <div className="game-board"> <Board squares = {current.squares} winner = {winner} onClick = {(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <div> <button onClick={() => this.historyReverse()}>Reverse</button> </div> <ul>{movesView}</ul> </div> </div> ); } } // ======================================== ReactDOM.render( <Game />, document.getElementById('root') ); // 傳入長度爲 9 的數組,此函數將判斷出獲勝者,並根據狀況返回 「X」,「O」 或 「null」。 function caculateWinner(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 { name: squares[a], line: lines[i], }; } } return null; } function getPosition(i) { const rowMap = [ ['a', 3], ['b', 3], ['c', 3], ['a', 2], ['b', 2], ['c', 2], ['a', 1], ['b', 1], ['c', 1], ] return rowMap[i]; }
css:css
body { font: 14px "Century Gothic", Futura, sans-serif; margin: 20px; } ol, ul { padding-left: 30px; } .board{ counter-reset: row; position: relative; padding-left: 34px; } .board-row:after { clear: both; content: ""; display: table; } .row-counters { display: flex; flex-flow: column-reverse nowrap; position: absolute; left: 0; top: 0; } .row-num { width: 34px; height: 34px; font-size: 20px; /* font-weight: bold; */ line-height: 34px; padding: 0; text-align: center; } .column-counters { display: flex; } .column-num { width: 34px; height: 34px; font-size: 20px; /* font-weight: bold; */ line-height: 34px; padding: 0; text-align: center; } .status { margin-bottom: 10px; } .square { background: #fff; border: 1px solid #999; float: left; font-size: 24px; font-weight: bold; line-height: 34px; height: 34px; margin-right: -1px; margin-top: -1px; padding: 0; text-align: center; width: 34px; } .square:focus { outline: none; } .kbd-navigation .square:focus { background: #ddd; } .game { display: flex; flex-direction: row; } .game-info { margin-left: 20px; } .bold { font-weight: bold; } .bold button { font-weight: bold; } .gold { background: gold; }