Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/__tests__/04.extra-3.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import * as React from 'react'
import {alfredTip} from '@kentcdodds/react-workshop-app/test-utils'
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import App from '../final/04.extra-3'
// import App from '../exercise/04'
// import App from '../final/04.extra-3'
import App from '../exercise/04'

test('can play a game of tic tac toe', () => {
render(<App />)
Expand Down
103 changes: 62 additions & 41 deletions src/exercise/04.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,24 @@
// http://localhost:3000/isolated/exercise/04.js

import * as React from 'react'
import { useLocalStorageState } from '../utils';

function Board() {
// 🐨 squares is the state for this component. Add useState for squares
const squares = Array(9).fill(null)

// 🐨 We'll need the following bits of derived state:
// - nextValue ('X' or 'O')
// - winner ('X', 'O', or null)
// - status (`Winner: ${winner}`, `Scratch: Cat's game`, or `Next player: ${nextValue}`)
// 💰 I've written the calculations for you! So you can use my utilities
// below to create these variables

// This is the function your square click handler will call. `square` should
// be an index. So if they click the center square, this will be `4`.
function selectSquare(square) {
// 🐨 first, if there's already winner or there's already a value at the
// given square index (like someone clicked a square that's already been
// clicked), then return early so we don't make any state changes
//
// 🦉 It's typically a bad idea to mutate or directly change state in React.
// Doing so can lead to subtle bugs that can easily slip into production.
//
// 🐨 make a copy of the squares array
// 💰 `[...squares]` will do it!)
//
// 🐨 set the value of the square that was selected
// 💰 `squaresCopy[square] = nextValue`
//
// 🐨 set the squares to your copy
}

function restart() {
// 🐨 reset the squares
// 💰 `Array(9).fill(null)` will do it!
}
function Board(props) {
const {
onClick,
squares,
} = props;

function renderSquare(i) {
return (
<button className="square" onClick={() => selectSquare(i)}>
<button className="square" onClick={() => onClick(i)}>
{squares[i]}
</button>
)
}

return (
<div>
{/* 🐨 put the status in the div below */}
<div className="status">STATUS</div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
Expand All @@ -65,18 +35,69 @@ function Board() {
{renderSquare(7)}
{renderSquare(8)}
</div>
<button className="restart" onClick={restart}>
restart
</button>
</div>
)
}

function Game() {
const [history, setHistory] = useLocalStorageState(
'tic-tac-toe:history',
[Array(9).fill(null)]
);

const [currentStep, setCurrentStep] = useLocalStorageState(
'tic-tac-toe:step',
0,
);

const currentSquares = history[currentStep];
const nextValue = calculateNextValue(currentSquares);
const winner = calculateWinner(currentSquares);
const status = calculateStatus(winner, currentSquares, nextValue);

function selectSquare(square) {
if (winner || currentSquares[square]) {
return;
}

const newHistory = history.slice(0, currentStep + 1);
const squares = [...currentSquares];
squares[square] = nextValue;
setHistory([...newHistory, squares]);
setCurrentStep(currentStep + 1);
}

const moves = history.map((stepSquares, i) => {
const buttonText = (i === 0) ? 'Go to game start' : `Go to move #${i}`;
const isCurrentStep = i === currentStep;
return (
<li key={i}>
<button
onClick={() => setCurrentStep(i)}
disabled={isCurrentStep}
>
{buttonText} {isCurrentStep ? '(current)' : ''}
</button>
</li>
);
});

function restart() {
setHistory([Array(9).fill(null)]);
setCurrentStep(0);
}

return (
<div className="game">
<div className="game-board">
<Board />
<Board onClick={selectSquare} squares={currentSquares} />
<button className="restart" onClick={restart}>
restart
</button>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
)
Expand Down