Tutorial: Intro To Reactをやったメモ
追加課題のメモです。初心者です。
1. Display the location for each move in the format (col, row) in the move history list.
@@ -49,6 +49,8 @@ class Game extends React.Component { this.state = { history: [{ squares: Array(9).fill(null), + col: null, + row: null, }], stepNumber: 0, xIsNext: true, @@ -66,6 +68,8 @@ class Game extends React.Component { this.setState({ history: history.concat([{ squares: squares, + col: (i % 3) + 1, + row: Math.floor(i / 3) + 1, }]), stepNumber: history.length, xIsNext: !this.state.xIsNext, @@ -86,7 +90,7 @@ class Game extends React.Component { const moves = history.map((step, move) => { const desc = move ? - 'Go to move #' + move : + 'Go to move #' + move + ' (' + step.col + ',' + step.row + ')' : 'Go to game start'; return ( <li key={move}>
状態にcol
とrow
を持つように変更。
2. Bold the currently selected item in the move list.
index.js
@@ -92,9 +92,10 @@ class Game extends React.Component { const desc = move ? 'Go to move #' + move + ' (' + step.col + ',' + step.row + ')' : 'Go to game start'; + const textStyle = (move === this.state.stepNumber) ? 'bold' : null; return ( <li key={move}> - <button onClick={() => this.jumpTo(move)}>{desc}</button> + <button className={textStyle} onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); });
style.css
@@ -48,3 +48,7 @@ ol, ul { .game-info { margin-left: 20px; } + +.bold { + font-weight: bold; +}
className
を切り替え。
3. Rewrite Board to use two loops to make the squares instead of hardcoding them.
@@ -16,30 +16,21 @@ class Board extends React.Component { <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} + key={i} /> ); } render() { - return ( - <div> - <div className="board-row"> - {this.renderSquare(0)} - {this.renderSquare(1)} - {this.renderSquare(2)} - </div> - <div className="board-row"> - {this.renderSquare(3)} - {this.renderSquare(4)} - {this.renderSquare(5)} - </div> - <div className="board-row"> - {this.renderSquare(6)} - {this.renderSquare(7)} - {this.renderSquare(8)} - </div> - </div> - ); + let squares = []; + for(let row = 0; row < 3; row++) { + let rows = []; + for(let col = 0; col < 3; col++) { + rows.push(this.renderSquare(row*3 + col)); + } + squares.push(<div className="board-row" key={row}>{rows}</div>); + } + return (<div>{squares}</div>); } }
key
を追加。
4. Add a toggle button that lets you sort the moves in either ascending or descending order.
@@ -45,6 +45,7 @@ class Game extends React.Component { }], stepNumber: 0, xIsNext: true, + isDescendingOrder: false, }; } @@ -74,12 +75,18 @@ class Game extends React.Component { }); } + toggleOrder() { + this.setState({ + isDescendingOrder: !this.state.isDescendingOrder, + }); + } + render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); - const moves = history.map((step, move) => { + let moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move + ' (' + step.col + ',' + step.row + ')' : 'Go to game start'; @@ -90,6 +97,14 @@ class Game extends React.Component { </li> ); }); + let movesList; + if(this.state.isDescendingOrder) { + moves.reverse(); + movesList = <ol reversed>{moves}</ol>; + } + else { + movesList = <ol>{moves}</ol>; + } let status; if(winner) { @@ -108,7 +123,8 @@ class Game extends React.Component { </div> <div className="game-info"> <div>{status}</div> - <ol>{moves}</ol> + <button onClick={() => this.toggleOrder()}>toggle</button> + {movesList} </div> </div> );
状態にisDescendingOrder
を追加。<ol>
まで逆順にする必要はなかったかもしれない。
5. When someone wins, highlight the three squares that caused the win.
index.js
@@ -3,8 +3,9 @@ import ReactDOM from 'react-dom'; import './style.css'; function Square(props) { + const buttonClass = props.isHighlight ? "square highlight" : "square"; return ( - <button className="square" onClick={props.onClick}> + <button className={buttonClass} onClick={props.onClick}> {props.value} </button> ); @@ -17,6 +18,7 @@ class Board extends React.Component { value={this.props.squares[i]} onClick={() => this.props.onClick(i)} key={i} + isHighlight={this.props.highlightLine.includes(i)} /> ); } @@ -84,7 +86,8 @@ class Game extends React.Component { render() { const history = this.state.history; const current = history[this.state.stepNumber]; - const winner = calculateWinner(current.squares); + const w = calculateWinner(current.squares); + const [winner, line] = (w) ? w : [null, [null]]; let moves = history.map((step, move) => { const desc = move ? @@ -119,6 +122,7 @@ class Game extends React.Component { <Board squares={current.squares} onClick={(i) => this.handleClick(i)} + highlightLine={line} /> </div> <div className="game-info"> @@ -152,7 +156,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 [squares[a], lines[i]]; } } return null;
style.css
@@ -40,6 +40,10 @@ ol, ul { background: #ddd; } +.highlight { + color: #ff0000; +} + .game { display: flex; flex-direction: row;
calculateWinner
でハイライトするラインを返すように修正。そのラインをBoard
のprops
に渡す。square
の添字がラインに含まれていたらハイライトするようにhighlight
をclassName
に追加する。
6. When no one wins, display a message about the result being a draw.
@@ -113,7 +113,11 @@ class Game extends React.Component { if(winner) { status = 'Winner: ' + winner; } else { - status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + if(this.state.stepNumber === 9) { + status = 'Draw'; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } } return (
あれ、なんかこれ簡単じゃね?と思って書いたけど、これだと全てのマスが埋まった時にDraw
と表示されるだけなので、"When no one wins,"じゃない。
@@ -55,7 +55,9 @@ class Game extends React.Component { 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]) { + if(calculateWinner(squares) || + squares[i] || + isDraw(squares, this.state.xIsNext)) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O'; @@ -113,7 +115,11 @@ class Game extends React.Component { if(winner) { status = 'Winner: ' + winner; } else { - status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + if(isDraw(current.squares, this.state.xIsNext)) { + status = 'Draw'; + } else { + status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); + } } return ( @@ -160,4 +166,19 @@ function calculateWinner(squares) { } } return null; -} +} + +function isDraw(squares, xIsNext) { + if(calculateWinner(squares)) { + return false; + } + + for(let i = 0; i < squares.length; i++) { + if(squares[i] === null) { + let sqrs = squares.slice(); + sqrs[i] = xIsNext ? 'X' : 'O'; + if(isDraw(sqrs, !xIsNext) === false) return false; + } + } + return true; +}
isDraw()
を追加。引き分けの時にtrue, それ以外(どちらかが勝てるパターンが残っている場合)はfalseを返す。
全探索でざっくり512パターン。イミュータブルが大事だよ、と書かれていたのでslice()
してるけど、計算量とかメモリってどのくらいまでなら許容されるのかなぁ。