Changes
This commit is contained in:
parent
dee3482dfa
commit
55c01cea33
4 changed files with 744 additions and 0 deletions
|
|
@ -63,6 +63,7 @@ import PresentsPuzzlePage from './pages/PresentsPuzzlePage';
|
|||
import FerrersRogersRamanujanPage from './pages/FerrersRogersRamanujanPage';
|
||||
import DominoRetilingPuzzlePage from './pages/DominoRetilingPuzzlePage';
|
||||
import RentDivisionPuzzlePage from './pages/RentDivisionPuzzlePage';
|
||||
import BagchalGamePage from './pages/BagchalGamePage';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
|
@ -101,6 +102,7 @@ const App = () => (
|
|||
<Route path="/themes/games/subtraction-game" element={<SubtractionGamePage />} />
|
||||
<Route path="/themes/games/craps-game" element={<CrapsGamePage />} />
|
||||
<Route path="/themes/games/sim" element={<GameOfSimPage />} />
|
||||
<Route path="/themes/games/bagchal" element={<BagchalGamePage />} />
|
||||
<Route path="/themes/cards-math" element={<CardsMath />} />
|
||||
<Route path="/themes/puzzles" element={<Puzzles />} />
|
||||
<Route path="/themes/puzzles/stacking-blocks" element={<StackingBlocksPage />} />
|
||||
|
|
|
|||
720
src/components/BagchalGame.tsx
Normal file
720
src/components/BagchalGame.tsx
Normal file
|
|
@ -0,0 +1,720 @@
|
|||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import SocialShare from '@/components/SocialShare';
|
||||
|
||||
interface BagchalGameProps {
|
||||
showSocialShare?: boolean;
|
||||
}
|
||||
|
||||
type Phase = 'setup' | 'tiger-placement' | 'goat-placement' | 'playing';
|
||||
type GameMode = '2-player' | 'vs-computer';
|
||||
type ComputerSide = 'tiger' | 'goats';
|
||||
type Turn = 'tiger' | 'goats';
|
||||
|
||||
interface Position {
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
interface GameState {
|
||||
phase: Phase;
|
||||
tigerPosition: Position | null;
|
||||
goatPositions: Position[];
|
||||
goatsPlaced: number;
|
||||
goatsToPlace: number;
|
||||
turn: Turn;
|
||||
winner: 'tiger' | 'goats' | null;
|
||||
selectedGoat: number | null; // index of selected goat for moving
|
||||
}
|
||||
|
||||
const BagchalGame: React.FC<BagchalGameProps> = ({ showSocialShare = false }) => {
|
||||
// Grid parameters
|
||||
const [rows, setRows] = useState(2);
|
||||
const [cols, setCols] = useState(6);
|
||||
const [numGoats, setNumGoats] = useState(4);
|
||||
|
||||
// Game mode
|
||||
const [gameMode, setGameMode] = useState<GameMode>('2-player');
|
||||
const [computerSide, setComputerSide] = useState<ComputerSide>('tiger');
|
||||
|
||||
// Game state
|
||||
const [gameState, setGameState] = useState<GameState>({
|
||||
phase: 'setup',
|
||||
tigerPosition: null,
|
||||
goatPositions: [],
|
||||
goatsPlaced: 0,
|
||||
goatsToPlace: numGoats,
|
||||
turn: 'tiger',
|
||||
winner: null,
|
||||
selectedGoat: null,
|
||||
});
|
||||
|
||||
const maxGoats = rows * cols - 1;
|
||||
|
||||
// Reset game when parameters change
|
||||
const resetGame = useCallback(() => {
|
||||
setGameState({
|
||||
phase: 'setup',
|
||||
tigerPosition: null,
|
||||
goatPositions: [],
|
||||
goatsPlaced: 0,
|
||||
goatsToPlace: numGoats,
|
||||
turn: 'tiger',
|
||||
winner: null,
|
||||
selectedGoat: null,
|
||||
});
|
||||
}, [numGoats]);
|
||||
|
||||
// Start the game
|
||||
const startGame = () => {
|
||||
setGameState({
|
||||
phase: 'tiger-placement',
|
||||
tigerPosition: null,
|
||||
goatPositions: [],
|
||||
goatsPlaced: 0,
|
||||
goatsToPlace: numGoats,
|
||||
turn: 'tiger',
|
||||
winner: null,
|
||||
selectedGoat: null,
|
||||
});
|
||||
};
|
||||
|
||||
// Check if a position is occupied
|
||||
const isOccupied = (row: number, col: number): boolean => {
|
||||
if (gameState.tigerPosition?.row === row && gameState.tigerPosition?.col === col) {
|
||||
return true;
|
||||
}
|
||||
return gameState.goatPositions.some(g => g.row === row && g.col === col);
|
||||
};
|
||||
|
||||
// Get adjacent positions
|
||||
const getAdjacentPositions = (pos: Position): Position[] => {
|
||||
const adjacent: Position[] = [];
|
||||
const directions = [
|
||||
[-1, 0], [1, 0], [0, -1], [0, 1], // orthogonal
|
||||
];
|
||||
|
||||
for (const [dr, dc] of directions) {
|
||||
const newRow = pos.row + dr;
|
||||
const newCol = pos.col + dc;
|
||||
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) {
|
||||
adjacent.push({ row: newRow, col: newCol });
|
||||
}
|
||||
}
|
||||
return adjacent;
|
||||
};
|
||||
|
||||
// Get valid tiger moves (adjacent empty positions)
|
||||
const getValidTigerMoves = (tigerPos: Position): Position[] => {
|
||||
return getAdjacentPositions(tigerPos).filter(
|
||||
pos => !isOccupied(pos.row, pos.col)
|
||||
);
|
||||
};
|
||||
|
||||
// Check if tiger can capture a goat (jump over it to empty space)
|
||||
const getTigerCaptures = (tigerPos: Position): { jumpTo: Position; capturedGoat: Position }[] => {
|
||||
const captures: { jumpTo: Position; capturedGoat: Position }[] = [];
|
||||
const directions = [
|
||||
[-1, 0], [1, 0], [0, -1], [0, 1],
|
||||
];
|
||||
|
||||
for (const [dr, dc] of directions) {
|
||||
const goatRow = tigerPos.row + dr;
|
||||
const goatCol = tigerPos.col + dc;
|
||||
const landRow = tigerPos.row + 2 * dr;
|
||||
const landCol = tigerPos.col + 2 * dc;
|
||||
|
||||
// Check if there's a goat adjacent and empty space beyond
|
||||
if (goatRow >= 0 && goatRow < rows && goatCol >= 0 && goatCol < cols &&
|
||||
landRow >= 0 && landRow < rows && landCol >= 0 && landCol < cols) {
|
||||
const hasGoat = gameState.goatPositions.some(g => g.row === goatRow && g.col === goatCol);
|
||||
const landingEmpty = !isOccupied(landRow, landCol);
|
||||
|
||||
if (hasGoat && landingEmpty) {
|
||||
captures.push({
|
||||
jumpTo: { row: landRow, col: landCol },
|
||||
capturedGoat: { row: goatRow, col: goatCol }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return captures;
|
||||
};
|
||||
|
||||
// Check if tiger is trapped (no valid moves)
|
||||
const isTigerTrapped = (tigerPos: Position): boolean => {
|
||||
const moves = getValidTigerMoves(tigerPos);
|
||||
const captures = getTigerCaptures(tigerPos);
|
||||
return moves.length === 0 && captures.length === 0;
|
||||
};
|
||||
|
||||
// Computer strategy for goats on 2xn board with 4 goats
|
||||
const getGoatComputerMove = (): { type: 'place' | 'move'; position?: Position; fromIndex?: number; toPosition?: Position } | null => {
|
||||
const tiger = gameState.tigerPosition;
|
||||
if (!tiger) return null;
|
||||
|
||||
if (gameState.phase === 'goat-placement') {
|
||||
// Strategy: Place goats to surround the tiger
|
||||
// For 2xn board, place goats to block tiger's movement
|
||||
const adjacentEmpty = getAdjacentPositions(tiger).filter(pos => !isOccupied(pos.row, pos.col));
|
||||
|
||||
// Prioritize positions that help trap the tiger
|
||||
// First, block escape routes
|
||||
if (adjacentEmpty.length > 0) {
|
||||
// Pick the position that leaves tiger with fewest options
|
||||
let bestPos = adjacentEmpty[0];
|
||||
let minOptions = Infinity;
|
||||
|
||||
for (const pos of adjacentEmpty) {
|
||||
// Simulate placing goat here
|
||||
const simulatedGoats = [...gameState.goatPositions, pos];
|
||||
const tigerOptions = getAdjacentPositions(tiger).filter(
|
||||
p => !simulatedGoats.some(g => g.row === p.row && g.col === p.col) &&
|
||||
!(tiger.row === p.row && tiger.col === p.col)
|
||||
);
|
||||
|
||||
if (tigerOptions.length < minOptions) {
|
||||
minOptions = tigerOptions.length;
|
||||
bestPos = pos;
|
||||
}
|
||||
}
|
||||
return { type: 'place', position: bestPos };
|
||||
}
|
||||
|
||||
// If can't place adjacent, find any empty spot
|
||||
for (let r = 0; r < rows; r++) {
|
||||
for (let c = 0; c < cols; c++) {
|
||||
if (!isOccupied(r, c)) {
|
||||
return { type: 'place', position: { row: r, col: c } };
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (gameState.phase === 'playing') {
|
||||
// Move goats to trap tiger
|
||||
for (let i = 0; i < gameState.goatPositions.length; i++) {
|
||||
const goat = gameState.goatPositions[i];
|
||||
const goatMoves = getAdjacentPositions(goat).filter(pos => !isOccupied(pos.row, pos.col));
|
||||
|
||||
for (const move of goatMoves) {
|
||||
// Check if this move helps trap the tiger
|
||||
const simulatedGoats = gameState.goatPositions.map((g, idx) =>
|
||||
idx === i ? move : g
|
||||
);
|
||||
|
||||
// Avoid moving into capture position
|
||||
const wouldBeCaptured = getTigerCapturesWithGoats(tiger, simulatedGoats).length > 0;
|
||||
if (!wouldBeCaptured) {
|
||||
const adjacentToTiger = getAdjacentPositions(tiger);
|
||||
const isAdjacentToTiger = adjacentToTiger.some(p => p.row === move.row && p.col === move.col);
|
||||
|
||||
if (isAdjacentToTiger) {
|
||||
return { type: 'move', fromIndex: i, toPosition: move };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: move any goat that can move
|
||||
for (let i = 0; i < gameState.goatPositions.length; i++) {
|
||||
const goat = gameState.goatPositions[i];
|
||||
const goatMoves = getAdjacentPositions(goat).filter(pos => !isOccupied(pos.row, pos.col));
|
||||
if (goatMoves.length > 0) {
|
||||
return { type: 'move', fromIndex: i, toPosition: goatMoves[0] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Helper for computer strategy
|
||||
const getTigerCapturesWithGoats = (tigerPos: Position, goats: Position[]): Position[] => {
|
||||
const captures: Position[] = [];
|
||||
const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
|
||||
|
||||
for (const [dr, dc] of directions) {
|
||||
const goatRow = tigerPos.row + dr;
|
||||
const goatCol = tigerPos.col + dc;
|
||||
const landRow = tigerPos.row + 2 * dr;
|
||||
const landCol = tigerPos.col + 2 * dc;
|
||||
|
||||
if (goatRow >= 0 && goatRow < rows && goatCol >= 0 && goatCol < cols &&
|
||||
landRow >= 0 && landRow < rows && landCol >= 0 && landCol < cols) {
|
||||
const hasGoat = goats.some(g => g.row === goatRow && g.col === goatCol);
|
||||
const tigerThere = tigerPos.row === landRow && tigerPos.col === landCol;
|
||||
const goatThere = goats.some(g => g.row === landRow && g.col === landCol);
|
||||
|
||||
if (hasGoat && !tigerThere && !goatThere) {
|
||||
captures.push({ row: goatRow, col: goatCol });
|
||||
}
|
||||
}
|
||||
}
|
||||
return captures;
|
||||
};
|
||||
|
||||
// Computer move for tiger
|
||||
const getTigerComputerMove = (): Position | null => {
|
||||
const tiger = gameState.tigerPosition;
|
||||
if (!tiger) return null;
|
||||
|
||||
// Check for captures first
|
||||
const captures = getTigerCaptures(tiger);
|
||||
if (captures.length > 0) {
|
||||
return captures[0].jumpTo;
|
||||
}
|
||||
|
||||
// Otherwise move to any valid position
|
||||
const moves = getValidTigerMoves(tiger);
|
||||
if (moves.length > 0) {
|
||||
// Prefer moves that might set up captures
|
||||
return moves[Math.floor(Math.random() * moves.length)];
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Handle cell click
|
||||
const handleCellClick = (row: number, col: number) => {
|
||||
if (gameState.winner) return;
|
||||
|
||||
const clickedPos = { row, col };
|
||||
const isComputerTurn = gameMode === 'vs-computer' &&
|
||||
((gameState.turn === 'tiger' && computerSide === 'tiger') ||
|
||||
(gameState.turn === 'goats' && computerSide === 'goats'));
|
||||
|
||||
if (isComputerTurn) return;
|
||||
|
||||
if (gameState.phase === 'tiger-placement') {
|
||||
// Place tiger
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
tigerPosition: clickedPos,
|
||||
phase: 'goat-placement',
|
||||
turn: 'goats',
|
||||
}));
|
||||
} else if (gameState.phase === 'goat-placement') {
|
||||
if (gameState.turn === 'goats') {
|
||||
// Place a goat
|
||||
if (isOccupied(row, col)) return;
|
||||
|
||||
const newGoatPositions = [...gameState.goatPositions, clickedPos];
|
||||
const newGoatsPlaced = gameState.goatsPlaced + 1;
|
||||
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
goatPositions: newGoatPositions,
|
||||
goatsPlaced: newGoatsPlaced,
|
||||
turn: 'tiger',
|
||||
}));
|
||||
} else if (gameState.turn === 'tiger') {
|
||||
// Tiger moves during goat placement
|
||||
if (!gameState.tigerPosition) return;
|
||||
|
||||
const validMoves = getValidTigerMoves(gameState.tigerPosition);
|
||||
const captures = getTigerCaptures(gameState.tigerPosition);
|
||||
|
||||
// Check for capture
|
||||
const capture = captures.find(c => c.jumpTo.row === row && c.jumpTo.col === col);
|
||||
if (capture) {
|
||||
// Tiger captures a goat - tiger wins!
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
tigerPosition: capture.jumpTo,
|
||||
goatPositions: prev.goatPositions.filter(
|
||||
g => !(g.row === capture.capturedGoat.row && g.col === capture.capturedGoat.col)
|
||||
),
|
||||
winner: 'tiger',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular move
|
||||
const isValidMove = validMoves.some(m => m.row === row && m.col === col);
|
||||
if (!isValidMove) return;
|
||||
|
||||
const newPhase = gameState.goatsPlaced >= gameState.goatsToPlace - 1 ? 'playing' : 'goat-placement';
|
||||
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
tigerPosition: clickedPos,
|
||||
phase: prev.goatsPlaced >= prev.goatsToPlace ? 'playing' : 'goat-placement',
|
||||
turn: 'goats',
|
||||
}));
|
||||
}
|
||||
} else if (gameState.phase === 'playing') {
|
||||
if (gameState.turn === 'tiger') {
|
||||
if (!gameState.tigerPosition) return;
|
||||
|
||||
const validMoves = getValidTigerMoves(gameState.tigerPosition);
|
||||
const captures = getTigerCaptures(gameState.tigerPosition);
|
||||
|
||||
// Check for capture
|
||||
const capture = captures.find(c => c.jumpTo.row === row && c.jumpTo.col === col);
|
||||
if (capture) {
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
tigerPosition: capture.jumpTo,
|
||||
goatPositions: prev.goatPositions.filter(
|
||||
g => !(g.row === capture.capturedGoat.row && g.col === capture.capturedGoat.col)
|
||||
),
|
||||
winner: 'tiger',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const isValidMove = validMoves.some(m => m.row === row && m.col === col);
|
||||
if (!isValidMove) return;
|
||||
|
||||
setGameState(prev => {
|
||||
const newState = {
|
||||
...prev,
|
||||
tigerPosition: clickedPos,
|
||||
turn: 'goats' as Turn,
|
||||
};
|
||||
|
||||
// Check if goats win (tiger trapped after this move)
|
||||
if (isTigerTrapped(clickedPos)) {
|
||||
return { ...newState, winner: 'goats' };
|
||||
}
|
||||
|
||||
return newState;
|
||||
});
|
||||
} else {
|
||||
// Goat turn - select or move a goat
|
||||
const clickedGoatIndex = gameState.goatPositions.findIndex(
|
||||
g => g.row === row && g.col === col
|
||||
);
|
||||
|
||||
if (clickedGoatIndex !== -1) {
|
||||
// Select this goat
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
selectedGoat: clickedGoatIndex,
|
||||
}));
|
||||
} else if (gameState.selectedGoat !== null) {
|
||||
// Try to move selected goat
|
||||
const selectedGoatPos = gameState.goatPositions[gameState.selectedGoat];
|
||||
const validMoves = getAdjacentPositions(selectedGoatPos).filter(
|
||||
pos => !isOccupied(pos.row, pos.col)
|
||||
);
|
||||
|
||||
const isValidMove = validMoves.some(m => m.row === row && m.col === col);
|
||||
if (!isValidMove) return;
|
||||
|
||||
const newGoatPositions = gameState.goatPositions.map((g, i) =>
|
||||
i === gameState.selectedGoat ? clickedPos : g
|
||||
);
|
||||
|
||||
setGameState(prev => {
|
||||
const newState = {
|
||||
...prev,
|
||||
goatPositions: newGoatPositions,
|
||||
selectedGoat: null,
|
||||
turn: 'tiger' as Turn,
|
||||
};
|
||||
|
||||
// Check if tiger is trapped
|
||||
if (prev.tigerPosition && isTigerTrapped(prev.tigerPosition)) {
|
||||
return { ...newState, winner: 'goats' };
|
||||
}
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Computer makes a move
|
||||
useEffect(() => {
|
||||
if (gameState.winner) return;
|
||||
if (gameMode !== 'vs-computer') return;
|
||||
if (gameState.phase === 'setup') return;
|
||||
|
||||
const isComputerTurn =
|
||||
(gameState.turn === 'tiger' && computerSide === 'tiger') ||
|
||||
(gameState.turn === 'goats' && computerSide === 'goats');
|
||||
|
||||
if (!isComputerTurn) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (gameState.turn === 'tiger' && gameState.tigerPosition) {
|
||||
const move = getTigerComputerMove();
|
||||
if (move) {
|
||||
// Check if it's a capture
|
||||
const captures = getTigerCaptures(gameState.tigerPosition);
|
||||
const capture = captures.find(c => c.jumpTo.row === move.row && c.jumpTo.col === move.col);
|
||||
|
||||
if (capture) {
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
tigerPosition: capture.jumpTo,
|
||||
goatPositions: prev.goatPositions.filter(
|
||||
g => !(g.row === capture.capturedGoat.row && g.col === capture.capturedGoat.col)
|
||||
),
|
||||
winner: 'tiger',
|
||||
}));
|
||||
} else {
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
tigerPosition: move,
|
||||
phase: prev.goatsPlaced >= prev.goatsToPlace ? 'playing' : prev.phase,
|
||||
turn: 'goats',
|
||||
}));
|
||||
}
|
||||
}
|
||||
} else if (gameState.turn === 'goats') {
|
||||
const goatMove = getGoatComputerMove();
|
||||
if (goatMove) {
|
||||
if (goatMove.type === 'place' && goatMove.position) {
|
||||
const newGoatPositions = [...gameState.goatPositions, goatMove.position];
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
goatPositions: newGoatPositions,
|
||||
goatsPlaced: prev.goatsPlaced + 1,
|
||||
turn: 'tiger',
|
||||
}));
|
||||
} else if (goatMove.type === 'move' && goatMove.fromIndex !== undefined && goatMove.toPosition) {
|
||||
const newGoatPositions = gameState.goatPositions.map((g, i) =>
|
||||
i === goatMove.fromIndex ? goatMove.toPosition! : g
|
||||
);
|
||||
|
||||
setGameState(prev => {
|
||||
const newState = {
|
||||
...prev,
|
||||
goatPositions: newGoatPositions,
|
||||
turn: 'tiger' as Turn,
|
||||
};
|
||||
|
||||
if (prev.tigerPosition && isTigerTrapped(prev.tigerPosition)) {
|
||||
return { ...newState, winner: 'goats' };
|
||||
}
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 800);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [gameState, gameMode, computerSide]);
|
||||
|
||||
// Check for goat placement phase completion
|
||||
useEffect(() => {
|
||||
if (gameState.phase === 'goat-placement' &&
|
||||
gameState.goatsPlaced >= gameState.goatsToPlace &&
|
||||
gameState.turn === 'goats') {
|
||||
setGameState(prev => ({
|
||||
...prev,
|
||||
phase: 'playing',
|
||||
}));
|
||||
}
|
||||
}, [gameState.goatsPlaced, gameState.goatsToPlace, gameState.phase, gameState.turn]);
|
||||
|
||||
// Render grid cell
|
||||
const renderCell = (row: number, col: number) => {
|
||||
const isTiger = gameState.tigerPosition?.row === row && gameState.tigerPosition?.col === col;
|
||||
const goatIndex = gameState.goatPositions.findIndex(g => g.row === row && g.col === col);
|
||||
const isGoat = goatIndex !== -1;
|
||||
const isSelected = gameState.selectedGoat === goatIndex;
|
||||
|
||||
const validTigerMoves = gameState.tigerPosition ? getValidTigerMoves(gameState.tigerPosition) : [];
|
||||
const tigerCaptures = gameState.tigerPosition ? getTigerCaptures(gameState.tigerPosition) : [];
|
||||
const isValidTigerMove = gameState.turn === 'tiger' && gameState.phase !== 'setup' && gameState.phase !== 'tiger-placement' &&
|
||||
(validTigerMoves.some(m => m.row === row && m.col === col) ||
|
||||
tigerCaptures.some(c => c.jumpTo.row === row && c.jumpTo.col === col));
|
||||
|
||||
const selectedGoatPos = gameState.selectedGoat !== null ? gameState.goatPositions[gameState.selectedGoat] : null;
|
||||
const validGoatMoves = selectedGoatPos ? getAdjacentPositions(selectedGoatPos).filter(p => !isOccupied(p.row, p.col)) : [];
|
||||
const isValidGoatMove = gameState.turn === 'goats' && gameState.phase === 'playing' &&
|
||||
validGoatMoves.some(m => m.row === row && m.col === col);
|
||||
|
||||
const canPlaceGoat = gameState.phase === 'goat-placement' && gameState.turn === 'goats' && !isOccupied(row, col);
|
||||
const isCaptureTarget = tigerCaptures.some(c => c.capturedGoat.row === row && c.capturedGoat.col === col);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${row}-${col}`}
|
||||
onClick={() => handleCellClick(row, col)}
|
||||
className={`
|
||||
w-16 h-16 border-2 border-border rounded-lg flex items-center justify-center text-3xl
|
||||
cursor-pointer transition-all duration-200
|
||||
${isSelected ? 'ring-4 ring-primary bg-primary/20' : ''}
|
||||
${isValidTigerMove ? 'bg-orange-200 dark:bg-orange-900/30' : ''}
|
||||
${isValidGoatMove ? 'bg-green-200 dark:bg-green-900/30' : ''}
|
||||
${canPlaceGoat ? 'bg-blue-100 dark:bg-blue-900/20 hover:bg-blue-200 dark:hover:bg-blue-900/40' : ''}
|
||||
${isCaptureTarget && gameState.turn === 'tiger' ? 'bg-red-200 dark:bg-red-900/30' : ''}
|
||||
${!isTiger && !isGoat && !isValidTigerMove && !isValidGoatMove && !canPlaceGoat ? 'hover:bg-muted' : ''}
|
||||
`}
|
||||
>
|
||||
{isTiger && '🐯'}
|
||||
{isGoat && '🐐'}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-4xl mx-auto">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Bagchal (Tiger and Goats)</CardTitle>
|
||||
<CardDescription>
|
||||
A strategic game where the tiger tries to capture goats, while goats try to trap the tiger.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{gameState.phase === 'setup' ? (
|
||||
<div className="space-y-6">
|
||||
{/* Grid size controls */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-3">
|
||||
<Label>Rows (m): {rows}</Label>
|
||||
<Slider
|
||||
value={[rows]}
|
||||
onValueChange={(v) => { setRows(v[0]); setNumGoats(Math.min(numGoats, v[0] * cols - 1)); }}
|
||||
min={2}
|
||||
max={10}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Label>Columns (n): {cols}</Label>
|
||||
<Slider
|
||||
value={[cols]}
|
||||
onValueChange={(v) => { setCols(v[0]); setNumGoats(Math.min(numGoats, rows * v[0] - 1)); }}
|
||||
min={2}
|
||||
max={10}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Number of goats */}
|
||||
<div className="space-y-3">
|
||||
<Label>Number of Goats (k): {numGoats}</Label>
|
||||
<Slider
|
||||
value={[numGoats]}
|
||||
onValueChange={(v) => setNumGoats(v[0])}
|
||||
min={1}
|
||||
max={maxGoats}
|
||||
step={1}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">Max: {maxGoats} goats for {rows}×{cols} grid</p>
|
||||
</div>
|
||||
|
||||
{/* Game mode */}
|
||||
<div className="space-y-3">
|
||||
<Label>Game Mode</Label>
|
||||
<RadioGroup value={gameMode} onValueChange={(v) => setGameMode(v as GameMode)}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="2-player" id="2-player" />
|
||||
<Label htmlFor="2-player">2 Player</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="vs-computer" id="vs-computer" />
|
||||
<Label htmlFor="vs-computer">Play vs Computer</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
|
||||
{/* Computer side selection */}
|
||||
{gameMode === 'vs-computer' && (
|
||||
<div className="space-y-3">
|
||||
<Label>You play as:</Label>
|
||||
<RadioGroup value={computerSide === 'tiger' ? 'goats' : 'tiger'} onValueChange={(v) => setComputerSide(v === 'tiger' ? 'goats' : 'tiger')}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="tiger" id="play-tiger" />
|
||||
<Label htmlFor="play-tiger">🐯 Tiger</Label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<RadioGroupItem value="goats" id="play-goats" />
|
||||
<Label htmlFor="play-goats">🐐 Goats</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button onClick={startGame} size="lg" className="w-full">
|
||||
Start Game
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{/* Game status */}
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge variant={gameState.turn === 'tiger' ? 'default' : 'secondary'} className="text-lg px-3 py-1">
|
||||
{gameState.turn === 'tiger' ? '🐯 Tiger\'s Turn' : '🐐 Goats\' Turn'}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-lg px-3 py-1">
|
||||
Phase: {gameState.phase === 'tiger-placement' ? 'Place Tiger' :
|
||||
gameState.phase === 'goat-placement' ? `Place Goats (${gameState.goatsPlaced}/${gameState.goatsToPlace})` :
|
||||
'Playing'}
|
||||
</Badge>
|
||||
{gameState.winner && (
|
||||
<Badge variant="destructive" className="text-lg px-3 py-1">
|
||||
{gameState.winner === 'tiger' ? '🐯 Tiger Wins!' : '🐐 Goats Win!'}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{gameState.phase === 'tiger-placement' && 'Click on any cell to place the tiger.'}
|
||||
{gameState.phase === 'goat-placement' && gameState.turn === 'goats' && 'Click on an empty cell to place a goat.'}
|
||||
{gameState.phase === 'goat-placement' && gameState.turn === 'tiger' && 'Tiger: Click on an adjacent empty cell to move.'}
|
||||
{gameState.phase === 'playing' && gameState.turn === 'tiger' && 'Tiger: Click on an adjacent cell to move, or jump over a goat to capture.'}
|
||||
{gameState.phase === 'playing' && gameState.turn === 'goats' && 'Click a goat to select it, then click an adjacent empty cell to move.'}
|
||||
</div>
|
||||
|
||||
{/* Game grid */}
|
||||
<div className="flex justify-center overflow-x-auto">
|
||||
<div
|
||||
className="grid gap-1"
|
||||
style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}
|
||||
>
|
||||
{Array.from({ length: rows }, (_, row) =>
|
||||
Array.from({ length: cols }, (_, col) => renderCell(row, col))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="flex flex-wrap gap-4 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-orange-200 dark:bg-orange-900/30 rounded"></div>
|
||||
<span>Valid tiger move</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-green-200 dark:bg-green-900/30 rounded"></div>
|
||||
<span>Valid goat move</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-red-200 dark:bg-red-900/30 rounded"></div>
|
||||
<span>Capturable goat</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Controls */}
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={resetGame} variant="outline">
|
||||
Reset Game
|
||||
</Button>
|
||||
<Button onClick={() => setGameState(prev => ({ ...prev, phase: 'setup' }))} variant="secondary">
|
||||
Change Settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showSocialShare && <SocialShare title="Bagchal Game" description="A strategic game where the tiger tries to capture goats while goats try to trap the tiger." />}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default BagchalGame;
|
||||
|
|
@ -13,6 +13,7 @@ import GuessingGame from '@/components/GuessingGame';
|
|||
import SubtractionGame from '@/components/SubtractionGame';
|
||||
import ParityBitsGame from '@/components/ParityBitsGame';
|
||||
import CrapsGame from '@/components/CrapsGame';
|
||||
import BagchalGame from '@/components/BagchalGame';
|
||||
|
||||
interface Game {
|
||||
id: string;
|
||||
|
|
@ -23,6 +24,13 @@ interface Game {
|
|||
}
|
||||
|
||||
const games: Game[] = [
|
||||
{
|
||||
id: 'bagchal',
|
||||
title: 'Bagchal (Tiger and Goats)',
|
||||
description: 'A strategic board game where the tiger tries to capture goats by jumping over them, while goats try to trap the tiger so it cannot move.',
|
||||
tags: ['strategy', 'two-player', 'board-game', 'traditional', 'nepal', 'asymmetric'],
|
||||
component: BagchalGame
|
||||
},
|
||||
{
|
||||
id: 'craps-game',
|
||||
title: 'Craps: An Exploration',
|
||||
|
|
|
|||
14
src/pages/BagchalGamePage.tsx
Normal file
14
src/pages/BagchalGamePage.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Layout from "@/components/Layout";
|
||||
import BagchalGame from "@/components/BagchalGame";
|
||||
|
||||
const BagchalGamePage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<BagchalGame showSocialShare={true} />
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default BagchalGamePage;
|
||||
Loading…
Add table
Add a link
Reference in a new issue