From e7250fd7c619acfeeb14bf42508506f06cdf4d38 Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 22:08:35 -0400 Subject: [PATCH 01/14] Step 1. Passing Data Through Props. --- react-fb-tictactoe/src/TicTacToe/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 56944d57..71bee8ed 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -13,7 +13,7 @@ class Square extends Component { render() { return ( ); } @@ -21,7 +21,7 @@ class Square extends Component { class Board extends Component { renderSquare(i) { - return ; + return ; } render() { From 7fb962c380f37728d16fc74b08eb880204a6a0bd Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 22:14:05 -0400 Subject: [PATCH 02/14] Step 2. An Interactive Component. --- react-fb-tictactoe/src/TicTacToe/index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 71bee8ed..f69189ef 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -10,10 +10,17 @@ import './TicTacToe.css' * class Square extends Component */ class Square extends Component { + constructor() { + super(); + this.state = { + value: null, + }; + } + render() { return ( - ); } From 1075b176bfbfe4cf26ff0e165aff186116400cee Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 22:40:48 -0400 Subject: [PATCH 03/14] Step 3. Lifting State Up. --- react-fb-tictactoe/src/TicTacToe/index.js | 33 +++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index f69189ef..a8764d86 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -9,26 +9,37 @@ import './TicTacToe.css' * with * class Square extends Component */ -class Square extends Component { - constructor() { - super(); - this.state = { - value: null, - }; - } - +class Square extends React.Component { render() { return ( - ); } } class Board extends Component { + constructor() { + super(); + this.state = { + squares: Array(9).fill(null), + }; + } + + + handleClick(i) { + const squares = this.state.squares.slice(); + squares[i] = 'X'; + this.setState({squares: squares}); + } + + renderSquare(i) { - return ; + return this.handleClick(i)} + />; } render() { From ad0c95c1bfbff1611128b4026835fcf26173b4d2 Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 22:43:12 -0400 Subject: [PATCH 04/14] Step 4. Functional Components. --- react-fb-tictactoe/src/TicTacToe/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index a8764d86..94d50d40 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -9,16 +9,15 @@ import './TicTacToe.css' * with * class Square extends Component */ -class Square extends React.Component { - render() { - return ( - - ); - } +function Square(props) { + return ( + + ); } + class Board extends Component { constructor() { super(); @@ -33,7 +32,7 @@ class Board extends Component { squares[i] = 'X'; this.setState({squares: squares}); } - + renderSquare(i) { return Date: Mon, 11 Sep 2017 22:46:26 -0400 Subject: [PATCH 05/14] Step 5. Taking Turns. --- react-fb-tictactoe/src/TicTacToe/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 94d50d40..9734e846 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -23,14 +23,18 @@ class Board extends Component { super(); this.state = { squares: Array(9).fill(null), + xIsNext: true, }; } handleClick(i) { const squares = this.state.squares.slice(); - squares[i] = 'X'; - this.setState({squares: squares}); + squares[i] = this.state.xIsNext ? 'X' : 'O'; + this.setState({ + squares: squares, + xIsNext: !this.state.xIsNext, + }); } @@ -42,7 +46,7 @@ class Board extends Component { } render() { - const status = 'Next player: X'; + const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); return (
From f19852f290061dc504cfd8fdce3ccc9845a8d3dc Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 22:49:18 -0400 Subject: [PATCH 06/14] Step 6. Declaring a Winner. --- react-fb-tictactoe/src/TicTacToe/index.js | 33 ++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 9734e846..e55faac4 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -30,6 +30,9 @@ class Board extends Component { handleClick(i) { const squares = this.state.squares.slice(); + if (calculateWinner(squares) || squares[i]) { + return; + } squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, @@ -46,7 +49,13 @@ class Board extends Component { } render() { - const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + const winner = calculateWinner(this.state.squares); + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } return (
@@ -88,3 +97,25 @@ class Game extends Component { } export default Game; + + + +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 squares[a]; + } + } + return null; +} From 8e41ce238f6f38a58cb349576c3757681f96b3a9 Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 22:58:21 -0400 Subject: [PATCH 07/14] Step 7. Storing a History --- react-fb-tictactoe/src/TicTacToe/index.js | 89 +++++++++++++---------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index e55faac4..3d53c91e 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -18,48 +18,20 @@ function Square(props) { } -class Board extends Component { - constructor() { - super(); - this.state = { - squares: Array(9).fill(null), - xIsNext: true, - }; - } - - - handleClick(i) { - const squares = this.state.squares.slice(); - if (calculateWinner(squares) || squares[i]) { - return; - } - squares[i] = this.state.xIsNext ? 'X' : 'O'; - this.setState({ - squares: squares, - xIsNext: !this.state.xIsNext, - }); - } - +class Board extends React.Component { renderSquare(i) { - return this.handleClick(i)} - />; + return ( + this.props.onClick(i)} + /> + ); } render() { - const winner = calculateWinner(this.state.squares); - let status; - if (winner) { - status = 'Winner: ' + winner; - } else { - status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); - } - return (
-
{status}
{this.renderSquare(0)} {this.renderSquare(1)} @@ -80,16 +52,57 @@ class Board extends Component { } } + class Game extends Component { + constructor() { + super(); + this.state = { + history: [{ + squares: Array(9).fill(null), + }], + xIsNext: true, + }; + } + + handleClick(i) { + const history = this.state.history; + 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, + }]), + xIsNext: !this.state.xIsNext, + }); + } + render() { + const history = this.state.history; + const current = history[history.length - 1]; + const winner = calculateWinner(current.squares); + + let status; + if (winner) { + status = 'Winner: ' + winner; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } + return (
- + this.handleClick(i)} + />
-
{/* status */}
-
    {/* todo */}
+
{status}
+
    {/* TODO */}
); From bf22db8289dc3593dc967b0e9fd1f6fefe8cfecc Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 23:00:35 -0400 Subject: [PATCH 08/14] Step 8. Showing the Moves. --- react-fb-tictactoe/src/TicTacToe/index.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 3d53c91e..96cc7727 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -85,6 +85,18 @@ class Game extends Component { const current = history[history.length - 1]; const winner = calculateWinner(current.squares); + const moves = history.map((step, move) => { + const desc = move ? + 'Move #' + move : + 'Game start'; + return ( +
  • + this.jumpTo(move)}>{desc} +
  • + ); + }); + + let status; if (winner) { status = 'Winner: ' + winner; @@ -102,7 +114,7 @@ class Game extends Component {
    {status}
    -
      {/* TODO */}
    +
      {moves}
    ); From bd082a70355ebf24f6f2c28234c94356b7dc6dd9 Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Mon, 11 Sep 2017 23:25:15 -0400 Subject: [PATCH 09/14] Step 9. Implementing Time Travel. --- react-fb-tictactoe/src/TicTacToe/index.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 96cc7727..9e07c31f 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -60,12 +60,13 @@ class Game extends Component { history: [{ squares: Array(9).fill(null), }], + stepNumber: 0, xIsNext: true, }; } handleClick(i) { - const history = this.state.history; + 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]) { @@ -76,13 +77,22 @@ class Game extends Component { history: history.concat([{ squares: squares, }]), + stepNumber: history.length, xIsNext: !this.state.xIsNext, }); } + + jumpTo(step) { + this.setState({ + stepNumber: step, + xIsNext: (step % 2) === 0, + }); + } + render() { const history = this.state.history; - const current = history[history.length - 1]; + const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { @@ -90,7 +100,7 @@ class Game extends Component { 'Move #' + move : 'Game start'; return ( -
  • +
  • this.jumpTo(move)}>{desc}
  • ); From dda4e396de6f30fdf7d0ed59795d3c9d1c5ec05f Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Sun, 17 Sep 2017 17:59:22 -0400 Subject: [PATCH 10/14] Step 10. Displays the move locations in the format '(1, 3)' instead of '6'. --- react-fb-tictactoe/src/TicTacToe/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 9e07c31f..0033205f 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -59,6 +59,7 @@ class Game extends Component { this.state = { history: [{ squares: Array(9).fill(null), + index: null, }], stepNumber: 0, xIsNext: true, @@ -76,6 +77,7 @@ class Game extends Component { this.setState({ history: history.concat([{ squares: squares, + index: i, }]), stepNumber: history.length, xIsNext: !this.state.xIsNext, @@ -97,7 +99,7 @@ class Game extends Component { const moves = history.map((step, move) => { const desc = move ? - 'Move #' + move : + 'Move #' + getCoordinates(step.index) : 'Game start'; return (
  • @@ -134,6 +136,12 @@ class Game extends Component { export default Game; +function getCoordinates(index) { + let x = index % 3; + let y = Math.floor(index / 3); + return `(${x}, ${y})`; +} + function calculateWinner(squares) { const lines = [ From db106110de2253ae8a6a336634dee0908905527c Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Sun, 17 Sep 2017 18:06:25 -0400 Subject: [PATCH 11/14] Step 11. Bolds the currently-selected item in the move list. --- react-fb-tictactoe/src/TicTacToe/TicTacToe.css | 5 +++++ react-fb-tictactoe/src/TicTacToe/index.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/react-fb-tictactoe/src/TicTacToe/TicTacToe.css b/react-fb-tictactoe/src/TicTacToe/TicTacToe.css index 0aa20d69..9d59ecdc 100644 --- a/react-fb-tictactoe/src/TicTacToe/TicTacToe.css +++ b/react-fb-tictactoe/src/TicTacToe/TicTacToe.css @@ -48,3 +48,8 @@ .game-info { margin-left: 20px; } + + +.current-move { + font-weight: bold; +} diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 0033205f..92cd36ca 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -101,8 +101,9 @@ class Game extends Component { const desc = move ? 'Move #' + getCoordinates(step.index) : 'Game start'; + const currentMoveClass = step == current ? 'current-move' : null; return ( -
  • +
  • this.jumpTo(move)}>{desc}
  • ); From b291d79002dbac4ee16f6aa560294b2c858296f3 Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Sun, 17 Sep 2017 18:25:41 -0400 Subject: [PATCH 12/14] Step 12. Rewrite Board to use two loops to make the squares instead of hardcoding them. --- react-fb-tictactoe/src/TicTacToe/index.js | 28 ++++++++--------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 92cd36ca..123f3779 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -30,25 +30,15 @@ class Board extends React.Component { } render() { - return ( -
    -
    - {this.renderSquare(0)} - {this.renderSquare(1)} - {this.renderSquare(2)} -
    -
    - {this.renderSquare(3)} - {this.renderSquare(4)} - {this.renderSquare(5)} -
    -
    - {this.renderSquare(6)} - {this.renderSquare(7)} - {this.renderSquare(8)} -
    -
    - ); + let rows = []; + for(let i = 0; i < 3; i++) { + let columns = []; + for(let j = 0; j < 3; j++) { + columns.push(this.renderSquare(i * 3 + j)) + } + rows.push(
    {columns}
    ) + } + return (
    {rows}
    ); } } From 1634cf7639bdac528cd29e9905c8d1f6dd538357 Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Sun, 17 Sep 2017 19:31:39 -0400 Subject: [PATCH 13/14] Step 13. Add a toggle button that lets you sort the moves in either ascending or descending order. --- react-fb-tictactoe/src/TicTacToe/TicTacToe.css | 4 ++++ react-fb-tictactoe/src/TicTacToe/index.js | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/TicTacToe.css b/react-fb-tictactoe/src/TicTacToe/TicTacToe.css index 9d59ecdc..0b5e356f 100644 --- a/react-fb-tictactoe/src/TicTacToe/TicTacToe.css +++ b/react-fb-tictactoe/src/TicTacToe/TicTacToe.css @@ -53,3 +53,7 @@ .current-move { font-weight: bold; } + +ul { + list-style: none; +} diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 123f3779..86d736d8 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -53,6 +53,7 @@ class Game extends Component { }], stepNumber: 0, xIsNext: true, + reversedOrder: false, }; } @@ -74,6 +75,12 @@ class Game extends Component { }); } + handleSort() { + this.setState({ + reversedOrder: !this.state.reversedOrder, + }) + } + jumpTo(step) { this.setState({ @@ -87,9 +94,11 @@ class Game extends Component { const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); + + const moves = history.map((step, move) => { const desc = move ? - 'Move #' + getCoordinates(step.index) : + move + '. Move ' + getCoordinates(step.index) : 'Game start'; const currentMoveClass = step == current ? 'current-move' : null; return ( @@ -117,7 +126,10 @@ class Game extends Component {
    {status}
    -
      {moves}
    +
      + {this.state.reversedOrder ? moves.reverse() : moves} +
    +
    ); From fc4b78d41c2f5f961d919b649b009f10bd9463ea Mon Sep 17 00:00:00 2001 From: Maria Romanova Date: Sun, 17 Sep 2017 20:08:41 -0400 Subject: [PATCH 14/14] Step 14. When someone wins, highlight the three squares that caused the win. --- react-fb-tictactoe/src/TicTacToe/TicTacToe.css | 5 +++++ react-fb-tictactoe/src/TicTacToe/index.js | 14 ++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/react-fb-tictactoe/src/TicTacToe/TicTacToe.css b/react-fb-tictactoe/src/TicTacToe/TicTacToe.css index 0b5e356f..5198d2b7 100644 --- a/react-fb-tictactoe/src/TicTacToe/TicTacToe.css +++ b/react-fb-tictactoe/src/TicTacToe/TicTacToe.css @@ -54,6 +54,11 @@ font-weight: bold; } + ul { list-style: none; } + +.win { + background-color: PaleGreen; +} diff --git a/react-fb-tictactoe/src/TicTacToe/index.js b/react-fb-tictactoe/src/TicTacToe/index.js index 86d736d8..b420e416 100644 --- a/react-fb-tictactoe/src/TicTacToe/index.js +++ b/react-fb-tictactoe/src/TicTacToe/index.js @@ -10,8 +10,12 @@ import './TicTacToe.css' * class Square extends Component */ function Square(props) { + let btnClass = 'square'; + if(props.highlight) { + btnClass += ' win'; + } return ( - ); @@ -21,10 +25,12 @@ function Square(props) { class Board extends React.Component { renderSquare(i) { + const highlight = this.props.winner && this.props.winner.includes(i); return ( this.props.onClick(i)} + highlight={highlight} /> ); } @@ -94,7 +100,6 @@ class Game extends Component { const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); - const moves = history.map((step, move) => { const desc = move ? @@ -111,7 +116,7 @@ class Game extends Component { let status; if (winner) { - status = 'Winner: ' + winner; + status = 'Winner: ' + current.squares[winner[0]]; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } @@ -122,6 +127,7 @@ class Game extends Component { this.handleClick(i)} + winner={winner} />
    @@ -160,7 +166,7 @@ function calculateWinner(squares) { 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 squares[a]; + return lines[i]; } } return null;