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 NorthcottsGamePage from "./pages/NorthcottsGamePage";
|
||||
import KnightsPuzzlePage from "./pages/KnightsPuzzlePage";
|
||||
import NimGamePage from "./pages/NimGamePage";
|
||||
import Foundations from "./pages/theme-pages/discrete-math/Foundations";
|
||||
import Proofs from "./pages/theme-pages/discrete-math/Proofs";
|
||||
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="/northcotts-game" element={<NorthcottsGamePage />} />
|
||||
<Route path="/knights-puzzle" element={<KnightsPuzzlePage />} />
|
||||
<Route path="/games/nim" element={<NimGamePage />} />
|
||||
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
|
|
|
|||
|
|
@ -90,6 +90,15 @@ const allInteractives: Interactive[] = [
|
|||
path: '/knights-puzzle',
|
||||
theme: 'Puzzles',
|
||||
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);
|
||||
// Store the pixel width for the entire heaps group container (label + stones)
|
||||
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
|
||||
React.useEffect(() => {
|
||||
|
|
@ -58,6 +60,14 @@ const NimGame: React.FC = () => {
|
|||
// eslint-disable-next-line
|
||||
}, [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
|
||||
const startGame = (selectedMode: Mode) => {
|
||||
setMode(selectedMode);
|
||||
|
|
@ -103,6 +113,21 @@ const NimGame: React.FC = () => {
|
|||
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
|
||||
const handleHeapCountChange = (val: number) => {
|
||||
setUserHeapCount(val);
|
||||
|
|
@ -166,15 +191,21 @@ const NimGame: React.FC = () => {
|
|||
|
||||
{mode !== null && (
|
||||
<>
|
||||
{/* Current Turn Indicator */}
|
||||
<div className="text-center">
|
||||
{/* Current Turn Indicator and Reset Button */}
|
||||
<div className="text-center flex flex-col items-center gap-2">
|
||||
{winner === null ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">Current Turn:</p>
|
||||
<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'}`}>
|
||||
Player {currentPlayer + 1}
|
||||
</span>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Current Turn:</p>
|
||||
<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'}`}>
|
||||
Player {currentPlayer + 1}
|
||||
</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">
|
||||
<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'],
|
||||
path: '/knights-puzzle',
|
||||
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)
|
||||
|
|
|
|||
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