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
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions src/components/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ import Square from "./Square";

interface BoardProps {
squares: (string | null)[];
isGameOver: boolean;
onSquareClick: (index: number) => void;
}

export default function Board(props: 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)} />
<Square value={props.squares[0]} onClick={() => props.onSquareClick(0)} isDisabled={props.squares[0] != null || props.isGameOver} />
<Square value={props.squares[1]} onClick={() => props.onSquareClick(1)} isDisabled={props.squares[1] != null || props.isGameOver}/>
<Square value={props.squares[2]} onClick={() => props.onSquareClick(2)} isDisabled={props.squares[2] != null || props.isGameOver}/>
<Square value={props.squares[3]} onClick={() => props.onSquareClick(3)} isDisabled={props.squares[3] != null || props.isGameOver}/>
<Square value={props.squares[4]} onClick={() => props.onSquareClick(4)} isDisabled={props.squares[4] != null || props.isGameOver}/>
<Square value={props.squares[5]} onClick={() => props.onSquareClick(5)} isDisabled={props.squares[5] != null || props.isGameOver}/>
<Square value={props.squares[6]} onClick={() => props.onSquareClick(6)} isDisabled={props.squares[6] != null || props.isGameOver}/>
<Square value={props.squares[7]} onClick={() => props.onSquareClick(7)} isDisabled={props.squares[7] != null || props.isGameOver}/>
<Square value={props.squares[8]} onClick={() => props.onSquareClick(8)} isDisabled={props.squares[8] != null || props.isGameOver}/>
</div>

Choose a reason for hiding this comment

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

Avoid repetition here, render the squares with map to keep the code DRY and easier to maintain.

Choose a reason for hiding this comment

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

(DRY - Don't Repeat Yourself)

);
}
79 changes: 73 additions & 6 deletions src/components/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,90 @@
import { useState } from "react";
import {useEffect, useState } from "react";
import Board from "./Board";

export default function Game() {
const [squares, setSquares] = useState<(string | null)[]>(
Array(9).fill(null)
);
const [isXNext, setIsXNext] = useState(true);
const [winner, setWinner] = useState<(string | null)>(null)
const turnTime = 10;
const X = "x"
const O = "O";
const currentPlayer = isXNext? X:O
const[timeLeft, setTimeLeft] = useState(turnTime)
const winningCombinations: number[][] = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[1,4,7],
[2,5,8],
[0,4,8],
[2,4,6]
];

const tide = !winner && !squares.flat().includes(null)
const winnerText = tide? "It's A tide": winner? `winner is ${winner}`: ""
const nextTurnText = `Next Player: ${isXNext ? X : O}`

useEffect(() => {
if(winner || tide) return

if(timeLeft <= 0) {
setIsXNext(prev => !prev)
setTimeLeft(turnTime)
};

const timer = setTimeout(() => {
setTimeLeft(timeLeft - 1)
}, 1000)

return () => clearTimeout(timer)

},[timeLeft])

function checkCombination(comb: number[], newSquares: (string | null)[]): Boolean {

Choose a reason for hiding this comment

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

Boolean → use lowercase boolean for TypeScript consistency

return newSquares[comb[0]] !== null &&
newSquares[comb[1]] === newSquares[comb[0]] &&
newSquares[comb[2]] === newSquares[comb[1]]
}

function calculateWinner(newSquares: (string | null)[]): (string | null) {
for(const comb of winningCombinations) {
if(checkCombination(comb, newSquares)) {
return newSquares[comb[0]]
}
}

return null;
}

function handleSquareClick(index: number) {
// Temporary: no gameplay logic yet
console.log("Clicked square:", index);
const newSquares: (string | null)[] = [...squares]
newSquares[index] = currentPlayer;
const winner = calculateWinner(newSquares)
setTimeLeft(turnTime)
if(!winner) {
setIsXNext(prev => !prev)
}

setSquares(newSquares)
setWinner(winner)
}

function reset() {
setSquares(Array(9).fill(null))
setWinner(null)
setIsXNext(true)
}

return (
<div className="game-container">
<h1>Tic Tac Toe</h1>

<Board squares={squares} onSquareClick={handleSquareClick} />
<p>Next Player: {isXNext ? "X" : "O"}</p>
<Board squares={squares} onSquareClick={handleSquareClick} isGameOver={!!winner}/>
<p>{winnerText || nextTurnText}</p>
{(winner || tide) && <button onClick={reset}>Play Again</button>}
<p>{!(winner || tide) && `time: ${timeLeft}`}</p>

</div>
);
Expand Down
3 changes: 2 additions & 1 deletion src/components/Square.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
interface SquareProps {
value: string | null;
isDisabled: boolean;
onClick: () => void;
}

export default function Square(props: SquareProps) {
return (
<button className="square" onClick={props.onClick}>
<button className="square" onClick={props.onClick} disabled={props.isDisabled}>
{props.value}
</button>
);
Expand Down