diff --git a/package-lock.json b/package-lock.json index 2fec462..88e255c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1389,6 +1390,7 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1399,6 +1401,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1458,6 +1461,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -1709,6 +1713,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1814,6 +1819,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2035,6 +2041,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2721,6 +2728,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2782,6 +2790,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2984,6 +2993,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3070,6 +3080,7 @@ "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3191,6 +3202,7 @@ "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/components/Board.tsx b/src/components/Board.tsx index 3989a44..64b709b 100644 --- a/src/components/Board.tsx +++ b/src/components/Board.tsx @@ -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 (
- props.onSquareClick(0)} /> - props.onSquareClick(1)} /> - props.onSquareClick(2)} /> - props.onSquareClick(3)} /> - props.onSquareClick(4)} /> - props.onSquareClick(5)} /> - props.onSquareClick(6)} /> - props.onSquareClick(7)} /> - props.onSquareClick(8)} /> + props.onSquareClick(0)} isDisabled={props.squares[0] != null || props.isGameOver} /> + props.onSquareClick(1)} isDisabled={props.squares[1] != null || props.isGameOver}/> + props.onSquareClick(2)} isDisabled={props.squares[2] != null || props.isGameOver}/> + props.onSquareClick(3)} isDisabled={props.squares[3] != null || props.isGameOver}/> + props.onSquareClick(4)} isDisabled={props.squares[4] != null || props.isGameOver}/> + props.onSquareClick(5)} isDisabled={props.squares[5] != null || props.isGameOver}/> + props.onSquareClick(6)} isDisabled={props.squares[6] != null || props.isGameOver}/> + props.onSquareClick(7)} isDisabled={props.squares[7] != null || props.isGameOver}/> + props.onSquareClick(8)} isDisabled={props.squares[8] != null || props.isGameOver}/>
); } diff --git a/src/components/Game.tsx b/src/components/Game.tsx index 80be18b..e0663a4 100644 --- a/src/components/Game.tsx +++ b/src/components/Game.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import {useEffect, useState } from "react"; import Board from "./Board"; export default function Game() { @@ -6,18 +6,85 @@ export default function Game() { 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 { + 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 (

Tic Tac Toe

- - -

Next Player: {isXNext ? "X" : "O"}

+ +

{winnerText || nextTurnText}

+ {(winner || tide) && } +

{!(winner || tide) && `time: ${timeLeft}`}

); diff --git a/src/components/Square.tsx b/src/components/Square.tsx index 3e9f11a..9f0e609 100644 --- a/src/components/Square.tsx +++ b/src/components/Square.tsx @@ -1,11 +1,12 @@ interface SquareProps { value: string | null; + isDisabled: boolean; onClick: () => void; } export default function Square(props: SquareProps) { return ( - );