Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
32 changes: 32 additions & 0 deletions tic-tac-toe-app/src/components/board/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Square, SquareValue } from "../square";

type BoardProps = {
squares: SquareValue[];
onClick: (i: number) => void;
};

export const Board = (props: BoardProps) => {
function renderSquare(i: number) {
return <Square value={props.squares[i]} onClick={() => props.onClick(i)} />;
}

return (
<div>
<div className="board-row">
{renderSquare(0)}
{renderSquare(1)}
{renderSquare(2)}
</div>
<div className="board-row">
{renderSquare(3)}
{renderSquare(4)}
{renderSquare(5)}
</div>
<div className="board-row">
{renderSquare(6)}
{renderSquare(7)}
{renderSquare(8)}
</div>
</div>
);
};
92 changes: 92 additions & 0 deletions tic-tac-toe-app/src/components/game/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useState } from "react";
import { Board } from "../board";
import { SquareValue } from "../square";

type GameHistory = {
squares: SquareValue[];
};

const initialHistory: GameHistory[] = [
{
squares: Array(9).fill(null),
},
];

export const Game = () => {
const [history, setHistory] = useState(initialHistory);
const [stepNumber, setStepNumber] = useState(0);
const [xIsNext, setXIsNext] = useState(true);

function handleClick(i: number) {
setHistory(history.slice(0, stepNumber + 1));
const current = history[stepNumber];
const squares = current.squares.slice();
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] = xIsNext ? "X" : "O";

setHistory(
Comment thread
ookura-mf marked this conversation as resolved.
Outdated
history.concat([
{
squares: squares,
},
])
);
setStepNumber(history.length);
setXIsNext(!xIsNext);
}

function jumpTo(step: number) {
setStepNumber(step);
setXIsNext(step % 2 === 0);
}

const current = history[stepNumber];
const winner = calculateWinner(current.squares);
Comment thread
ookura-mf marked this conversation as resolved.
Outdated
let statusMessage;
if (winner) {
statusMessage = "Winner: " + winner;
} else {
statusMessage = "Next player: " + (xIsNext ? "X" : "O");
}
const moves = history.map((_, move) => {
const desc = move ? "Go to move #" + move : "Go to game start";
return (
<li key={move}>
Comment thread
ookura-mf marked this conversation as resolved.
Outdated
<button onClick={() => jumpTo(move)}>{desc}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board squares={current.squares} onClick={(i) => handleClick(i)} />
</div>
<div className="game-info">
<div>{statusMessage}</div>
<ol>{moves}</ol>
</div>
</div>
);
};

function calculateWinner(squares: Array<SquareValue>) {
Comment thread
ookura-mf marked this conversation as resolved.
Outdated
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;
}
13 changes: 13 additions & 0 deletions tic-tac-toe-app/src/components/square/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type SquareValue = "X" | "O" | null;
Comment thread
ookura-mf marked this conversation as resolved.
Outdated
type SquareProps = {
value: SquareValue;
onClick: () => void;
};

export const Square = (props: SquareProps) => {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
};
152 changes: 1 addition & 151 deletions tic-tac-toe-app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,157 +2,7 @@ import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import reportWebVitals from "./reportWebVitals";

type SquareValue = "X" | "O" | null;
type SquareProps = {
value: SquareValue;
onClick: () => void;
};
function Square(props: SquareProps) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}

type BoardProps = {
squares: SquareValue[];
onClick: (i: number) => void;
};
class Board extends React.Component<BoardProps> {
renderSquare(i: number) {
return (
<Square
value={this.props.squares[i]}
onClick={() => this.props.onClick(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>
);
}
}
type GameHistory = {
squares: SquareValue[];
};
type GameProps = {};
type GameState = {
history: GameHistory[];
stepNumber: number;
xIsNext: boolean;
};
class Game extends React.Component<GameProps, GameState> {
constructor(props: GameProps) {
super(props);
this.state = {
history: [
{
squares: Array(9).fill(null),
},
],
stepNumber: 0,
xIsNext: true,
};
}
handleClick(i: number) {
const history = this.state.history.slice(0, this.state.stepNumber + 1);
const current = history[this.state.stepNumber];
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,
},
]),
stepNumber: history.length,
xIsNext: !this.state.xIsNext,
});
}
jumpTo(step: number) {
this.setState({
stepNumber: step,
xIsNext: step % 2 === 0,
});
}
render() {
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
let status;
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (this.state.xIsNext ? "X" : "O");
}
const moves = history.map((_, move) => {
const desc = move ? "Go to move #" + move : "Go to game start";
return (
<li key={move}>
<button onClick={() => this.jumpTo(move)}>{desc}</button>
</li>
);
});
return (
<div className="game">
<div className="game-board">
<Board
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}

function calculateWinner(squares: Array<SquareValue>) {
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;
}

import { Game } from "./components/game";
// ========================================

ReactDOM.render(<Game />, document.getElementById("root"));
Expand Down