diff --git a/src/components/GameOfSim.tsx b/src/components/GameOfSim.tsx new file mode 100644 index 0000000..f404cf1 --- /dev/null +++ b/src/components/GameOfSim.tsx @@ -0,0 +1,274 @@ +import React, { useState, useCallback } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { RotateCcw, Undo2 } from 'lucide-react'; + +interface Edge { + from: number; + to: number; + color: 'red' | 'blue' | null; +} + +interface GameState { + edges: Edge[]; + currentPlayer: 'red' | 'blue'; + gameOver: boolean; + winner: 'red' | 'blue' | null; + moveHistory: Edge[]; +} + +const GameOfSim = () => { + const [vertices, setVertices] = useState(6); + const [gameState, setGameState] = useState(() => initializeGame(6)); + + function initializeGame(numVertices: number): GameState { + const edges: Edge[] = []; + + // Create all possible edges between vertices + for (let i = 0; i < numVertices; i++) { + for (let j = i + 1; j < numVertices; j++) { + edges.push({ from: i, to: j, color: null }); + } + } + + return { + edges, + currentPlayer: 'red', + gameOver: false, + winner: null, + moveHistory: [] + }; + } + + const resetGame = useCallback(() => { + setGameState(initializeGame(vertices)); + }, [vertices]); + + const undoMove = useCallback(() => { + if (gameState.moveHistory.length === 0) return; + + const lastMove = gameState.moveHistory[gameState.moveHistory.length - 1]; + const newEdges = gameState.edges.map(edge => { + if (edge.from === lastMove.from && edge.to === lastMove.to) { + return { ...edge, color: null }; + } + return edge; + }); + + setGameState(prev => ({ + ...prev, + edges: newEdges, + currentPlayer: prev.currentPlayer === 'red' ? 'blue' : 'red', + moveHistory: prev.moveHistory.slice(0, -1), + gameOver: false, + winner: null + })); + }, [gameState.moveHistory, gameState.edges]); + + const checkForTriangle = useCallback((edges: Edge[], player: 'red' | 'blue'): boolean => { + const playerEdges = edges.filter(edge => edge.color === player); + + // Check all possible triangles + for (let i = 0; i < vertices; i++) { + for (let j = i + 1; j < vertices; j++) { + for (let k = j + 1; k < vertices; k++) { + // Check if all three edges of triangle exist for this player + const edge1 = playerEdges.find(e => (e.from === i && e.to === j) || (e.from === j && e.to === i)); + const edge2 = playerEdges.find(e => (e.from === i && e.to === k) || (e.from === k && e.to === i)); + const edge3 = playerEdges.find(e => (e.from === j && e.to === k) || (e.from === k && e.to === j)); + + if (edge1 && edge2 && edge3) { + return true; + } + } + } + } + return false; + }, [vertices]); + + const handleEdgeClick = useCallback((edgeIndex: number) => { + if (gameState.gameOver || gameState.edges[edgeIndex].color !== null) return; + + const newEdges = [...gameState.edges]; + const clickedEdge = { ...newEdges[edgeIndex], color: gameState.currentPlayer }; + newEdges[edgeIndex] = clickedEdge; + + // Check if this move creates a triangle + const hasTriangle = checkForTriangle(newEdges, gameState.currentPlayer); + + setGameState(prev => ({ + edges: newEdges, + currentPlayer: hasTriangle ? prev.currentPlayer : (prev.currentPlayer === 'red' ? 'blue' : 'red'), + gameOver: hasTriangle, + winner: hasTriangle ? (prev.currentPlayer === 'red' ? 'blue' : 'red') : null, + moveHistory: [...prev.moveHistory, clickedEdge] + })); + }, [gameState, checkForTriangle]); + + const getVertexPosition = (index: number, total: number) => { + const angle = (2 * Math.PI * index) / total; + const radius = 120; + const centerX = 150; + const centerY = 150; + + return { + x: centerX + radius * Math.cos(angle - Math.PI / 2), + y: centerY + radius * Math.sin(angle - Math.PI / 2) + }; + }; + + const handleVerticesChange = (value: string) => { + const newVertices = parseInt(value); + setVertices(newVertices); + setGameState(initializeGame(newVertices)); + }; + + return ( +
+ + + Game of Sim + + Take turns coloring the lines between dots. Avoid creating a triangle of your color - first player to make one loses! + + + + {/* Game Controls */} +
+
+ + +
+ +
+ + +
+
+ + {/* Current Turn Indicator */} +
+ {gameState.gameOver ? ( +
+ + Game Over! + +

+ + {gameState.winner === 'blue' ? 'Blue' : 'Red'} Player + {' '} + wins! +

+
+ ) : ( +
+

Current Turn:

+ + {gameState.currentPlayer === 'red' ? 'Red' : 'Blue'} Player + +
+ )} +
+ + {/* Game Board */} +
+
+ + {/* Draw edges */} + {gameState.edges.map((edge, index) => { + const pos1 = getVertexPosition(edge.from, vertices); + const pos2 = getVertexPosition(edge.to, vertices); + + return ( + handleEdgeClick(index)} + /> + ); + })} + + {/* Draw vertices */} + {Array.from({ length: vertices }, (_, i) => { + const pos = getVertexPosition(i, vertices); + return ( + + + + {i + 1} + + + ); + })} + +
+
+ + {/* Instructions */} +
+

+ Click on the lines between dots to color them with your color. +

+

+ Avoid creating a triangle of your own color! +

+
+
+
+
+ ); +}; + +export default GameOfSim; \ No newline at end of file diff --git a/src/components/GamesGallery.tsx b/src/components/GamesGallery.tsx new file mode 100644 index 0000000..8604242 --- /dev/null +++ b/src/components/GamesGallery.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Search } from 'lucide-react'; +import GameOfSim from '@/components/GameOfSim'; + +interface Game { + id: string; + title: string; + description: string; + tags: string[]; + component: React.ComponentType; +} + +const games: Game[] = [ + { + id: 'game-of-sim', + title: 'Game of Sim', + description: 'A strategic two-player game where you take turns coloring edges between vertices, trying to avoid creating a triangle of your own color.', + tags: ['strategy', 'graph-theory', 'two-player', 'logic', 'game-theory'], + component: GameOfSim + } +]; + +const GamesGallery = () => { + const [searchTerm, setSearchTerm] = useState(''); + const [selectedGame, setSelectedGame] = useState(null); + + const filteredGames = games.filter(game => + game.title.toLowerCase().includes(searchTerm.toLowerCase()) || + game.description.toLowerCase().includes(searchTerm.toLowerCase()) || + game.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())) + ); + + if (selectedGame) { + const GameComponent = selectedGame.component; + return ( +
+
+ +
+
+ +
+
+ ); + } + + return ( +
+
+

Educational Games

+

+ Learn through play with interactive games that reinforce computer science and mathematical concepts. +

+
+ + {/* Search bar */} +
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+ + {/* Games Gallery */} +
+ {filteredGames.map(game => ( + setSelectedGame(game)} + > + + {game.title} + + {game.description} + + + +
+ {game.tags.map(tag => ( + + {tag} + + ))} +
+
+
+ ))} +
+ + {filteredGames.length === 0 && ( +
+

No games found matching your search.

+
+ )} +
+ ); +}; + +export default GamesGallery; \ No newline at end of file diff --git a/src/pages/theme-pages/Games.tsx b/src/pages/theme-pages/Games.tsx index 8cb70fa..7e0a5a9 100644 --- a/src/pages/theme-pages/Games.tsx +++ b/src/pages/theme-pages/Games.tsx @@ -1,11 +1,13 @@ -import ComingSoon from "@/components/ComingSoon"; +import Layout from "@/components/Layout"; +import GamesGallery from "@/components/GamesGallery"; const Games = () => { return ( - + +
+ +
+
); };