Last active
April 7, 2020 07:00
-
-
Save davidfq/bb76a1630991fd0bfe60b3b0519add0e to your computer and use it in GitHub Desktop.
React tutorial: tic-tac-toe game (last edit 2018-01-12)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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