feat: add standalone Nim game page, route, and container layout; remove global header/footer from /games/nim
This commit is contained in:
parent
bfa6e66450
commit
3705152d34
5 changed files with 69 additions and 8 deletions
|
|
@ -29,6 +29,7 @@ import TernarySearchTrickPage from "./pages/TernarySearchTrickPage";
|
||||||
import TernarySearchTrickGreenScreen from "./pages/TernarySearchTrickGreenScreen";
|
import TernarySearchTrickGreenScreen from "./pages/TernarySearchTrickGreenScreen";
|
||||||
import NorthcottsGamePage from "./pages/NorthcottsGamePage";
|
import NorthcottsGamePage from "./pages/NorthcottsGamePage";
|
||||||
import KnightsPuzzlePage from "./pages/KnightsPuzzlePage";
|
import KnightsPuzzlePage from "./pages/KnightsPuzzlePage";
|
||||||
|
import NimGamePage from "./pages/NimGamePage";
|
||||||
import Foundations from "./pages/theme-pages/discrete-math/Foundations";
|
import Foundations from "./pages/theme-pages/discrete-math/Foundations";
|
||||||
import Proofs from "./pages/theme-pages/discrete-math/Proofs";
|
import Proofs from "./pages/theme-pages/discrete-math/Proofs";
|
||||||
import Counting from "./pages/theme-pages/discrete-math/Counting";
|
import Counting from "./pages/theme-pages/discrete-math/Counting";
|
||||||
|
|
@ -84,6 +85,7 @@ const App = () => (
|
||||||
<Route path="/ternary-search-trick/:mode" element={<TernarySearchTrickGreenScreen />} />
|
<Route path="/ternary-search-trick/:mode" element={<TernarySearchTrickGreenScreen />} />
|
||||||
<Route path="/northcotts-game" element={<NorthcottsGamePage />} />
|
<Route path="/northcotts-game" element={<NorthcottsGamePage />} />
|
||||||
<Route path="/knights-puzzle" element={<KnightsPuzzlePage />} />
|
<Route path="/knights-puzzle" element={<KnightsPuzzlePage />} />
|
||||||
|
<Route path="/games/nim" element={<NimGamePage />} />
|
||||||
|
|
||||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,15 @@ const allInteractives: Interactive[] = [
|
||||||
path: '/knights-puzzle',
|
path: '/knights-puzzle',
|
||||||
theme: 'Puzzles',
|
theme: 'Puzzles',
|
||||||
dateAdded: '2024-07-21'
|
dateAdded: '2024-07-21'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'nim',
|
||||||
|
title: 'Game of Nim',
|
||||||
|
description: 'A classic two-player game of strategy. Take turns removing stones from heaps. The player to take the last stone wins!',
|
||||||
|
tags: ['strategy', 'two-player', 'math', 'logic', 'game-theory'],
|
||||||
|
path: '/games/nim',
|
||||||
|
theme: 'Games',
|
||||||
|
dateAdded: '2024-07-21'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ const NimGame: React.FC = () => {
|
||||||
const initialMaxHeapSizeRef = useRef<number>(0);
|
const initialMaxHeapSizeRef = useRef<number>(0);
|
||||||
// Store the pixel width for the entire heaps group container (label + stones)
|
// Store the pixel width for the entire heaps group container (label + stones)
|
||||||
const [heapsGroupWidth, setHeapsGroupWidth] = useState<string>('0px');
|
const [heapsGroupWidth, setHeapsGroupWidth] = useState<string>('0px');
|
||||||
|
// Store the initial heaps for reset
|
||||||
|
const initialHeapsRef = useRef<number[]>([]);
|
||||||
|
|
||||||
// When a new game starts, record the initial heap sizes and max size and compute the group width
|
// When a new game starts, record the initial heap sizes and max size and compute the group width
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
@ -58,6 +60,14 @@ const NimGame: React.FC = () => {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [mode, heaps.length]);
|
}, [mode, heaps.length]);
|
||||||
|
|
||||||
|
// When a new game starts, record the initial heaps
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (mode !== null && heaps.length > 0) {
|
||||||
|
initialHeapsRef.current = [...heaps];
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [mode, heaps.length]);
|
||||||
|
|
||||||
// Start a new game
|
// Start a new game
|
||||||
const startGame = (selectedMode: Mode) => {
|
const startGame = (selectedMode: Mode) => {
|
||||||
setMode(selectedMode);
|
setMode(selectedMode);
|
||||||
|
|
@ -103,6 +113,21 @@ const NimGame: React.FC = () => {
|
||||||
setCurrentPlayer(0);
|
setCurrentPlayer(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Reset the game to the initial heaps
|
||||||
|
const handleResetGame = () => {
|
||||||
|
setHeaps([...initialHeapsRef.current]);
|
||||||
|
setWinner(null);
|
||||||
|
setCurrentPlayer(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start a new game (go back to mode selection)
|
||||||
|
const handleNewGame = () => {
|
||||||
|
setMode(null);
|
||||||
|
setHeaps([]);
|
||||||
|
setWinner(null);
|
||||||
|
setCurrentPlayer(0);
|
||||||
|
};
|
||||||
|
|
||||||
// User heap count slider change
|
// User heap count slider change
|
||||||
const handleHeapCountChange = (val: number) => {
|
const handleHeapCountChange = (val: number) => {
|
||||||
setUserHeapCount(val);
|
setUserHeapCount(val);
|
||||||
|
|
@ -166,15 +191,21 @@ const NimGame: React.FC = () => {
|
||||||
|
|
||||||
{mode !== null && (
|
{mode !== null && (
|
||||||
<>
|
<>
|
||||||
{/* Current Turn Indicator */}
|
{/* Current Turn Indicator and Reset Button */}
|
||||||
<div className="text-center">
|
<div className="text-center flex flex-col items-center gap-2">
|
||||||
{winner === null ? (
|
{winner === null ? (
|
||||||
<div className="space-y-2">
|
<>
|
||||||
<p className="text-sm text-muted-foreground">Current Turn:</p>
|
<div>
|
||||||
<span className={`inline-block text-lg px-4 py-2 rounded font-semibold ${currentPlayer === 0 ? 'bg-sky-200 text-sky-800' : 'bg-red-200 text-red-800'}`}>
|
<p className="text-sm text-muted-foreground">Current Turn:</p>
|
||||||
Player {currentPlayer + 1}
|
<span className={`inline-block text-lg px-4 py-2 rounded font-semibold ${currentPlayer === 0 ? 'bg-sky-200 text-sky-800' : 'bg-red-200 text-red-800'}`}>
|
||||||
</span>
|
Player {currentPlayer + 1}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 mt-2">
|
||||||
|
<Button variant="outline" size="sm" onClick={handleResetGame}>Reset Game</Button>
|
||||||
|
<Button variant="outline" size="sm" onClick={handleNewGame}>New Game</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<span className="inline-block text-lg px-4 py-2 rounded font-semibold bg-green-200 text-green-800">Winner: Player {winner + 1}!</span>
|
<span className="inline-block text-lg px-4 py-2 rounded font-semibold bg-green-200 text-green-800">Winner: Player {winner + 1}!</span>
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,13 @@ const allInteractives = [{
|
||||||
tags: ['chess', 'knights', 'puzzle'],
|
tags: ['chess', 'knights', 'puzzle'],
|
||||||
path: '/knights-puzzle',
|
path: '/knights-puzzle',
|
||||||
theme: 'Puzzles'
|
theme: 'Puzzles'
|
||||||
|
}, {
|
||||||
|
id: 'nim',
|
||||||
|
title: 'Game of Nim',
|
||||||
|
description: 'A classic two-player game of strategy. Take turns removing stones from heaps. The player to take the last stone wins!',
|
||||||
|
tags: ['strategy', 'two-player', 'math', 'logic'],
|
||||||
|
path: '/games/nim',
|
||||||
|
theme: 'Games'
|
||||||
}];
|
}];
|
||||||
|
|
||||||
// Featured interactives (3 fixed ones)
|
// Featured interactives (3 fixed ones)
|
||||||
|
|
|
||||||
12
src/pages/NimGamePage.tsx
Normal file
12
src/pages/NimGamePage.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import NimGame from '@/components/NimGame';
|
||||||
|
|
||||||
|
const NimGamePage = () => {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<NimGame />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NimGamePage;
|
||||||
Loading…
Add table
Add a link
Reference in a new issue