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
14 changes: 4 additions & 10 deletions src/components/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,12 @@ interface BoardProps {
onSquareClick: (index: number) => void;
}

export default function Board(props: BoardProps) {
export default function Board({ squares, onSquareClick }: BoardProps) {
return (
<div className="board">
<Square value={props.squares[0]} onClick={() => props.onSquareClick(0)} />
<Square value={props.squares[1]} onClick={() => props.onSquareClick(1)} />
<Square value={props.squares[2]} onClick={() => props.onSquareClick(2)} />
<Square value={props.squares[3]} onClick={() => props.onSquareClick(3)} />
<Square value={props.squares[4]} onClick={() => props.onSquareClick(4)} />
<Square value={props.squares[5]} onClick={() => props.onSquareClick(5)} />
<Square value={props.squares[6]} onClick={() => props.onSquareClick(6)} />
<Square value={props.squares[7]} onClick={() => props.onSquareClick(7)} />
<Square value={props.squares[8]} onClick={() => props.onSquareClick(8)} />
{squares.map((square, index) => {
return <Square key={index} value={square} onClick={() => onSquareClick(index)} />;
})}
</div>
);
}
91 changes: 75 additions & 16 deletions src/components/Game.tsx

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several magic numbers in the component (board size, turn duration, timer interval).
Consider extracting them into named constants to improve readability and maintainability.

Original file line number Diff line number Diff line change
@@ -1,24 +1,83 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import Board from "./Board";
import { GAME_CONSTANTS } from "../constants/GameConstants";

export default function Game() {
const [squares, setSquares] = useState<(string | null)[]>(
Array(9).fill(null)
);
const [isXNext, setIsXNext] = useState(true);

function handleSquareClick(index: number) {
// Temporary: no gameplay logic yet
console.log("Clicked square:", index);
const [squares, setSquares] = useState<("X" | "O" | null)[]>(
Array(GAME_CONSTANTS.BOARD_SIZE).fill(null)
);
const [isXNext, setIsXNext] = useState(true);
const [timer, setTimer] = useState(GAME_CONSTANTS.TURN_DURATION_SECONDS);
const [stop, setStop] = useState(false);

const pauseButtonLabel = stop ? GAME_CONSTANTS.RESUME : GAME_CONSTANTS.STOP;

function calculateWinner(): "X" | "O" | null {
for (const [a, b, c] of GAME_CONSTANTS.WINNING_LINES) {
const value = squares[a];

if (value && value === squares[b] && value === squares[c]) {
return value;
}
}
return null;
}

const winner = calculateWinner();

function handleSquareClick(index: number) {
if (stop || winner) return;
if (squares[index]) return;
const nextSquares = [...squares];
nextSquares[index] = isXNext ? GAME_CONSTANTS.X : GAME_CONSTANTS.O;
setSquares(nextSquares);
setIsXNext((prev) => !prev);
setTimer(GAME_CONSTANTS.TURN_DURATION_SECONDS);
}

function handleReset() {
setIsXNext(true);
setSquares(Array(GAME_CONSTANTS.BOARD_SIZE).fill(null));
setTimer(GAME_CONSTANTS.TURN_DURATION_SECONDS);
setStop(false);
}

useEffect(() => {
if (stop || winner) return;

const id = setInterval(() => {
setTimer((prev) => {
if (prev === 0) {
setIsXNext((p) => !p);
return GAME_CONSTANTS.TURN_DURATION_SECONDS;
}
return prev - 1;
});
}, GAME_CONSTANTS.TIMER_INTERVAL_MS);

return (
<div className="game-container">
<h1>Tic Tac Toe</h1>
return () => clearInterval(id);
}, [stop, winner]);

<Board squares={squares} onSquareClick={handleSquareClick} />
<p>Next Player: {isXNext ? "X" : "O"}</p>
function handleStop() {
setStop((stop) => !stop);
}
return (
<div className="game-container">
<h1>Tic Tac Toe</h1>
<Board squares={squares} onSquareClick={handleSquareClick} />
{winner ? (
<h2>Winner: {winner}</h2>
) : (
<>
<h2>
time for player {isXNext ? GAME_CONSTANTS.X : GAME_CONSTANTS.O} : {timer} sec
</h2>
<p>Next Player: {isXNext ? GAME_CONSTANTS.X : GAME_CONSTANTS.O}</p>
<button onClick={handleStop}>{pauseButtonLabel}</button>
</>
)}

</div>
);
<button onClick={handleReset}>reset</button>
</div>
);
}
6 changes: 3 additions & 3 deletions src/components/Square.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ interface SquareProps {
onClick: () => void;
}

export default function Square(props: SquareProps) {
export default function Square({value,onClick}: SquareProps) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
<button className="square" onClick={onClick}>
{value}
</button>
);
}
19 changes: 19 additions & 0 deletions src/constants/GameConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const GAME_CONSTANTS = {
RESUME: "resume",
STOP: "stop",
X: "X",
O: "O",
BOARD_SIZE: 9 as number,
TURN_DURATION_SECONDS: 10 as number,
TIMER_INTERVAL_MS: 1000 as number,
WINNING_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],
],
} as const;