React tutorial: tic-tac-toe game (last edit 2018-01-12)
// React tutorial: tic-tac-toe game | |
// https://reactjs.org/tutorial/tutorial.html | |
// https://davidfq.me/blog/react-first-steps/ | |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import './index.css'; | |
function Square(props) { | |
return ( | |
<button className={`square ${props.className}`} onClick={props.onClick}> | |
{props.value} | |
</button> | |
); | |
} | |
class Board extends React.Component { | |
renderSquare(i) { | |
const isWinnerPlay = (this.props.winner && | |
this.props.winner.sequence && | |
this.props.winner.sequence.indexOf(i) >= 0) ? 'is-winner' : '' | |
return ( | |
<Square | |
key={`square-${i}`} | |
value={this.props.squares[i]} | |
onClick={() => this.props.onClick(i)} | |
className={isWinnerPlay} | |
/> | |
); | |
} | |
render() { | |
const rows = Array.from(Array(3).keys()) | |
const cols = Array.from(Array(3).keys()) | |
return ( | |
<div> | |
{rows.map((row) => | |
<div key={`board-row-${row}`} className="board-row"> | |
{cols.map((col) => { | |
const index = row === 0 ? col : (row * cols.length) + col; | |
return this.renderSquare(index) | |
})} | |
</div> | |
)} | |
</div> | |
); | |
} | |
} | |
class Game extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
history: [{ | |
squares: Array(9).fill(null), | |
playIndex: null, | |
}], | |
stepNumber: 0, | |
xIsNext: true, | |
direction: 1, // as in SQL: ASC -> 1, DESC -> -1 | |
}; | |
} | |
handleClick(i) { | |
const history = this.state.history.slice(0, this.state.stepNumber + 1); | |
const current = history[history.length - 1]; | |
const squares = current.squares.slice(); | |
if (calculateWinner(squares) || squares[i]) { | |
return; | |
} | |
squares[i] = this.state.xIsNext ? 'X' : 'O'; | |
this.setState({ | |
history: history.concat([{ | |
squares: squares, | |
playIndex: i, | |
}]), | |
stepNumber: history.length, | |
xIsNext: !this.state.xIsNext, | |
}); | |
} | |
jumpTo(step) { | |
this.setState({ | |
stepNumber: step, | |
xIsNext: (step % 2) === 0, | |
}); | |
} | |
flipMoves() { | |
this.setState({ direction: this.state.direction * -1 }) | |
} | |
render() { | |
const history = this.state.history.slice(); | |
const current = history[this.state.stepNumber]; | |
const winner = calculateWinner(current.squares); | |
if (this.state.direction < 0) { | |
history.reverse() | |
} | |
const moves = history.map((step, move) => { | |
move = this.state.direction < 0 ? (history.length - 1) - move : move | |
let pos = '' | |
if (step.playIndex !== null) { | |
const quotient = Math.floor(step.playIndex / 3); | |
const remainder = (step.playIndex + 1) % 3; | |
const row = quotient + 1; | |
const col = remainder === 0 ? 3 : remainder; | |
pos = `(${row}, ${col})`; | |
} | |
const desc = move ? | |
`Go to move #${move}` : | |
'Go to game start'; | |
const className = this.state.stepNumber === move ? 'is-active' : '' | |
return ( | |
<li key={desc} className={className}> | |
<button onClick={() => this.jumpTo(move)}>{desc}</button> {pos} | |
</li> | |
); | |
}); | |
let status; | |
if (winner) { | |
status = 'Winner: ' + winner.player; | |
} else { | |
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); | |
} | |
return ( | |
<div className="game"> | |
<div className="game-board"> | |
<Board | |
squares={current.squares} | |
onClick={(i) => this.handleClick(i)} | |
winner={winner} | |
/> | |
</div> | |
<div className="game-info"> | |
<div>{status}</div> | |
<ol reversed={this.state.direction < 0}>{moves}</ol> | |
<button onClick={() => this.flipMoves()}>flip moves</button> | |
</div> | |
</div> | |
); | |
} | |
} | |
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 { | |
player: squares[a], | |
sequence: lines[i] | |
} | |
} | |
} | |
return null; | |
} | |
// ======================================== | |
ReactDOM.render( | |
<Game />, | |
document.getElementById('root') | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment