diff --git a/index.html b/index.html
index 0c589ec..0068160 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Vite + React
+ Tic Tac Toe Bolt
diff --git a/src/App.css b/src/App.css
index b9d355d..b7b6a48 100644
--- a/src/App.css
+++ b/src/App.css
@@ -5,38 +5,33 @@
text-align: center;
}
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
+.board {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
+.board-row {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
}
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
+.board-cell {
+ width: 100px;
+ height: 100px;
+ border: 1px solid black;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 24px;
+ cursor: pointer;
}
-.card {
- padding: 2em;
+.board-cell:hover {
+ background-color: #f0f0f0;
}
-.read-the-docs {
- color: #888;
-}
+.board-cell.fading {
+ color: red;
+}
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index f67355a..838919a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,33 +1,159 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
+import { useEffect, useState } from 'react'
import './App.css'
+import React from 'react';
+
+type Symbol = 'X' | 'O';
+
+type CellState = {
+ symbol?: Symbol;
+ isFading?: boolean;
+};
+
+type RowState = CellState[];
+type BoardState = RowState[];
+
+type CellClickHandler = (row: number, col: number) => boolean;
+type CellProps = {
+ row: number;
+ col: number;
+ state: CellState;
+ onClick?: CellClickHandler;
+}
+
+
+function Cell({row, col, state: {symbol, isFading}, onClick}: CellProps) {
+ const onClick1 = (() => {
+ onClick && onClick(row, col)
+ });
+
+ return {symbol}
;
+}
+
+type RowProps = {
+ index: number, state: RowState
+ onCellClick: CellClickHandler;
+};
+
+function Row({state, index, onCellClick}: RowProps) {
+ return (
+
+ {state.map((cell: CellState, i) => )}
+ |
+ )
+}
+
+type BoardProps = {
+ state: BoardState;
+ onCellClick: CellClickHandler;
+}
+
+function Board({state, onCellClick}: BoardProps) {
+ return (
+
+ {state.map((row, i) => )}
+
+ )
+
+}
+
+function findWinner(state: BoardState): Symbol | undefined {
+ // check rows
+ if (state[0]?.[0]?.symbol === state[0]?.[1]?.symbol && state[0]?.[1]?.symbol === state[0]?.[2]?.symbol)
+ return state[0]?.[0]?.symbol;
+
+ if (state[1]?.[0]?.symbol === state[1]?.[1]?.symbol && state[1]?.[1]?.symbol === state[1]?.[2]?.symbol)
+ return state[1]?.[0]?.symbol;
+
+ if (state[2]?.[0]?.symbol === state[2]?.[1]?.symbol && state[2]?.[1]?.symbol === state[2]?.[2]?.symbol)
+ return state[2]?.[0]?.symbol;
+
+ // check columns
+ if (state[0]?.[0]?.symbol === state[1]?.[0]?.symbol && state[1]?.[0]?.symbol === state[2]?.[0]?.symbol)
+ return state[0]?.[0]?.symbol;
+
+ if (state[0]?.[1]?.symbol === state[1]?.[1]?.symbol && state[1]?.[1]?.symbol === state[2]?.[1]?.symbol)
+ return state[0]?.[1]?.symbol;
+
+ if (state[0]?.[2]?.symbol === state[1]?.[2]?.symbol && state[1]?.[2]?.symbol === state[2]?.[2]?.symbol)
+ return state[0]?.[2]?.symbol;
+
+ // check diagonals
+ if (state[0]?.[0]?.symbol === state[1]?.[1]?.symbol && state[1]?.[1]?.symbol === state[2]?.[2]?.symbol)
+ return state[0]?.[0]?.symbol;
+
+ if (state[0]?.[2]?.symbol === state[1]?.[1]?.symbol && state[1]?.[1]?.symbol === state[2]?.[0]?.symbol)
+ return state[0]?.[2]?.symbol;
+
+ return undefined;
+}
function App() {
- const [count, setCount] = useState(0)
+ const [nextPlayer, setNextPlayer] = useState('O')
+
+ const [state, updateState] = useState([
+ [{}, {}, {}],
+ [{}, {}, {}],
+ [{}, {}, {}],
+ ])
+
+ const [latestCells, updateLatestCells] = useState<{ row: number, col: number }[]>([]);
+ const [winner, updateWinner] = useState(undefined);
+
+ const onClick = (rowNumber: number, colNumber: number): boolean => {
+ const targetCell = state[rowNumber]?.[colNumber];
+ if (winner || !targetCell || targetCell.symbol) {
+ return false;
+ }
+
+ updateState((prevState) => prevState.map((row, r) => row.map((cell, c) => {
+ if (r === rowNumber && c === colNumber) {
+ return {...cell, symbol: nextPlayer};
+ }
+ if (latestCells.length >= 5 && latestCells[0] && latestCells[0].row === r && latestCells[0].col === c) {
+ return {...cell, isFading: true};
+ }
+ return cell.isFading ? {symbol: undefined, isFading: false} : cell;
+ })));
+
+ updateLatestCells((prevState) => {
+ console.info('updating latestCells', {rowNumber, colNumber});
+ const appended = [...prevState, {row: rowNumber, col: colNumber}];
+ if (appended.length >= 6) {
+ return appended.slice(1);
+ }
+ return appended;
+ })
+ setNextPlayer(nextPlayer === 'X' ? 'O' : 'X');
+ return false;
+ };
+
+ useEffect(() => {
+ const winner = findWinner(state);
+ if (winner) {
+ updateWinner(winner);
+ }
+ }, [state]);
return (
<>
-
-
-
-
-
-
-
+
+
+ {winner ?
Winner is {winner}
:
Next player is {nextPlayer}
}
+ {latestCells.length === 0 && (
Click any cell to start
)}
+ {winner &&
{
+
+ updateState((prevState) => prevState.map((row, r) => row.map((cell, c) => {
+ return {};
+ })));
+
+ updateLatestCells([]);
+
+ setNextPlayer('O');
+ updateWinner(undefined);
+ }}>Click to restart
}
- Vite + React
-
-
-
- Edit src/App.jsx
and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
>
)
}