A Pen by Colin Tinney on CodePen.
Last active
January 31, 2017 03:38
-
-
Save cdtinney/e61dbc16b95a9ae36a29ebea2577bcc8 to your computer and use it in GitHub Desktop.
Tic Tac Toe - A ReactJS Tutorial
This file contains hidden or 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
<div id="errors" style=" | |
background: #c00; | |
color: #fff; | |
display: none; | |
margin: -20px -20px 20px; | |
padding: 20px; | |
white-space: pre-wrap; | |
"></div> | |
<div id="container"></div> | |
<script> | |
window.addEventListener('mousedown', function(e) { | |
document.body.classList.add('mouse-navigation'); | |
document.body.classList.remove('kbd-navigation'); | |
}); | |
window.addEventListener('keydown', function(e) { | |
if (e.keyCode === 9) { | |
document.body.classList.add('kbd-navigation'); | |
document.body.classList.remove('mouse-navigation'); | |
} | |
}); | |
window.addEventListener('click', function(e) { | |
if (e.target.tagName === 'A' && e.target.getAttribute('href') === '#') { | |
e.preventDefault(); | |
} | |
}); | |
window.onerror = function(message, source, line, col, error) { | |
var text = error ? error.stack || error : message + ' (at ' + source + ':' + line + ':' + col + ')'; | |
errors.textContent += text + '\n'; | |
errors.style.display = ''; | |
}; | |
console.error = (function(old) { | |
return function error() { | |
errors.textContent += Array.prototype.slice.call(arguments).join(' ') + '\n'; | |
errors.style.display = ''; | |
old.apply(this, arguments); | |
} | |
})(console.error); | |
</script> |
This file contains hidden or 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
function Square(props) { | |
return ( | |
<button className={`square ${props.winning ? 'winning-square' : ''}`} onClick={() => props.onClick()}> | |
{ props.value } | |
</button> | |
); | |
} | |
class Board extends React.Component { | |
renderSquare(i, winning) { | |
return <Square key={i} value={this.props.squares[i]} onClick={() => this.props.onClick(i)} winning={winning}/>; | |
} | |
render() { | |
var rows = []; | |
let squares = []; | |
for (var row=0; row<3; row++) { | |
for (var index=row*3; index<row*3+3; index++) { | |
let winning = false; | |
const winningLine = this.props.winningLine; | |
if (winningLine) { | |
for (var i=0; i<winningLine.length; i++) { | |
if (winningLine[i] == index) | |
winning = true; | |
} | |
} | |
squares.push(this.renderSquare(index, winning)); | |
} | |
rows.push(<div key={row} className="board-row">{squares}</div>); | |
squares = []; | |
} | |
return <div> {rows} </div>; | |
} | |
} | |
class Game extends React.Component { | |
constructor() { | |
super(); | |
this.state = { | |
history: [{ | |
squares: Array(9).fill(null) | |
}], | |
xIsNext: true, | |
stepNumber: 0 | |
}; | |
} | |
handleClick(i) { | |
const history = this.state.history; | |
const newest = history[history.length - 1]; | |
const newestSquares = newest.squares.slice(); | |
if (calculateWinner(newestSquares) || newestSquares[i]) { | |
return; | |
} | |
const current = history[this.state.stepNumber]; | |
const squares = current.squares.slice(); | |
if (calculateWinner(squares) || squares[i]) { | |
return; | |
} | |
const sign = this.state.xIsNext ? 'X' : '0'; | |
squares[i] = sign; | |
this.setState({ | |
history: history.concat([{ | |
squares: squares, | |
selected: { | |
row: i % 3 + 1, | |
col: Math.floor(i / 3) + 1, | |
sign: sign | |
}, | |
}]), | |
xIsNext: !this.state.xIsNext, | |
stepNumber: history.length, | |
}); | |
} | |
jumpTo(step) { | |
this.setState({ | |
stepNumber: step, | |
xIsNext: (step % 2) ? false : true, | |
}); | |
} | |
render() { | |
const history = this.state.history; | |
const stepNumber = this.state.stepNumber; | |
const current = history[this.state.stepNumber]; | |
const winner = calculateWinner(current.squares); | |
let status; | |
let winningLine; | |
if (winner) { | |
status = 'Winner: ' + winner.sign; | |
winningLine = winner.line; | |
} else { | |
status = 'Next player: ' + (this.state.xIsNext ? 'X' : '0'); | |
} | |
const moves = history.map((step, move) => { | |
const desc = move ? | |
'Move ' + step.selected.sign + ' (' + step.selected.row + ',' + step.selected.col + ')': | |
'Game start'; | |
const currentlySelected = (move == stepNumber); | |
return ( | |
<li key={move}> | |
<a href="#" onClick={() => this.jumpTo(move)} className={currentlySelected ? 'selectedMove' : ''}> | |
{desc} | |
</a> | |
</li> | |
); | |
}); | |
return ( | |
<div className="game"> | |
<div className="game-board"> | |
<Board squares={current.squares} winningLine={winningLine} onClick={(i) => this.handleClick(i)} /> | |
</div> | |
<div className="game-info"> | |
<div>{status}</div> | |
<ol>{/* TODO */}</ol> | |
</div> | |
<ol>{moves}</ol> | |
</div> | |
); | |
} | |
} | |
// ======================================== | |
ReactDOM.render( | |
<Game />, | |
document.getElementById('container') | |
); | |
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 { | |
sign: squares[a], | |
line: lines[i] | |
} | |
} | |
} | |
return null; | |
} |
This file contains hidden or 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
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-with-addons.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js"></script> |
This file contains hidden or 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
body { | |
font: 14px "Century Gothic", Futura, sans-serif; | |
margin: 20px; | |
} | |
ol, ul { | |
padding-left: 30px; | |
} | |
li a.selectedMove { | |
font-weight: bold; | |
} | |
.board-row:after { | |
clear: both; | |
content: ""; | |
display: table; | |
} | |
.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; | |
} | |
.winning-square { | |
background-color: green; | |
} | |
.square:focus { | |
outline: none; | |
} | |
.kbd-navigation .square:focus { | |
background: #ddd; | |
} | |
.game { | |
display: flex; | |
flex-direction: row; | |
} | |
.game-info { | |
margin-left: 20px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment