elyor-ml commited on
Commit
a432b46
·
1 Parent(s): 285d2af

memory game

Browse files
.dockerignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ npm-debug.log
3
+ Dockerfile
4
+ .dockerignore
5
+ .git
6
+ .github
7
+ .gitignore
8
+ README.md
9
+ .DS_Store
10
+ dist
.gitattributes CHANGED
@@ -1,38 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  public/media/*.png filter=lfs diff=lfs merge=lfs -text
37
  public/media/*.jpg filter=lfs diff=lfs merge=lfs -text
38
  public/media/*.mp4 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  public/media/*.png filter=lfs diff=lfs merge=lfs -text
2
  public/media/*.jpg filter=lfs diff=lfs merge=lfs -text
3
  public/media/*.mp4 filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
Dockerfile CHANGED
@@ -1,50 +1,27 @@
1
- # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
- # you will also find guides on how best to write your Dockerfile
3
 
4
- ############################
5
- # Stage 1 – Build frontend #
6
- ############################
7
-
8
- FROM node:20 AS frontend-builder
9
-
10
- WORKDIR /frontend
11
-
12
- # Copy and install deps
13
- COPY ./frontend/package.json ./frontend/tsconfig.json ./frontend/vite.config.ts ./frontend/index.html ./
14
- COPY ./frontend/src ./src
15
- RUN npm install --legacy-peer-deps && npm run build
16
 
17
- ###########################
18
- # Stage 2 – Backend image #
19
- ###########################
20
 
21
- FROM python:3.9
 
 
22
 
23
- RUN useradd -m -u 1000 user
24
- USER user
25
- ENV PATH="/home/user/.local/bin:$PATH"
26
 
27
  WORKDIR /app
28
 
29
- COPY --chown=user ./requirements.txt requirements.txt
30
- RUN pip install --no-cache-dir --upgrade -r requirements.txt
31
-
32
- COPY --chown=user . /app
33
-
34
- # Copy built static files from frontend stage
35
- COPY --from=frontend-builder /frontend/dist ./static
36
-
37
- # ------------------------------------------------------------------------------
38
- # NOTE: Hugging Face Spaces run inside a headless container. The Snake game
39
- # itself requires an X11 display and therefore cannot run on the Space. The
40
- # FastAPI app in `app.py` just serves a landing page and a health-check.
41
- # ------------------------------------------------------------------------------
42
 
43
- # Install lightweight additional libraries to satisfy pygame at import time.
44
- # These X11/SDL libs are small and safe even for headless use.
45
- RUN apt-get update && apt-get install -y --no-install-recommends \
46
- libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-ttf-2.0-0 \
47
- && rm -rf /var/lib/apt/lists/*
48
 
49
- # Default command that Hugging Face will execute
50
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
1
+ # Build stage
2
+ FROM node:20-alpine as build
3
 
4
+ WORKDIR /app
 
 
 
 
 
 
 
 
 
 
 
5
 
6
+ # Copy package files and install dependencies
7
+ COPY package.json package-lock.json ./
8
+ RUN npm ci
9
 
10
+ # Copy all files and build the project
11
+ COPY . .
12
+ RUN npm run build
13
 
14
+ # Serve stage
15
+ FROM node:20-alpine as serve
 
16
 
17
  WORKDIR /app
18
 
19
+ # Install serve globally
20
+ RUN npm install -g serve
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ # Copy built files from previous stage
23
+ COPY --from=build /app/dist ./dist
 
 
 
24
 
25
+ # Expose port and start server
26
+ EXPOSE 3000
27
+ CMD ["serve", "-s", "dist", "-l", "3000"]
README.md CHANGED
@@ -1,10 +1,70 @@
1
- ---
2
- title: Verbs Past
3
- emoji: 📊
4
- colorFrom: red
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Memory Card Game
2
+
3
+ A simple memory card game built with React and Vite.
4
+
5
+ ## Features
6
+
7
+ - Match pairs of cards with emoji symbols
8
+ - Track number of moves
9
+ - Responsive design for desktop and mobile
10
+
11
+ ## Quick Start
12
+
13
+ ### Local Development
14
+
15
+ ```bash
16
+ # Install dependencies
17
+ npm install
18
+
19
+ # Start development server
20
+ npm run dev
21
+ ```
22
+
23
+ ### Building for Production
24
+
25
+ ```bash
26
+ # Build for production
27
+ npm run build
28
+
29
+ # Preview production build
30
+ npm run preview
31
+ ```
32
+
33
+ ## Docker Deployment
34
+
35
+ Build and run the Docker container:
36
+
37
+ ```bash
38
+ # Build the Docker image
39
+ docker build -t memory-game .
40
+
41
+ # Run the container
42
+ docker run -p 3000:3000 memory-game
43
+ ```
44
+
45
+ ## Deployment to Hugging Face Spaces
46
+
47
+ 1. Create a new Space on Hugging Face: https://huggingface.co/spaces
48
+ 2. Choose Docker as the SDK
49
+ 3. Clone your Space repository
50
+ 4. Copy your project files to the repository
51
+ 5. Push to Hugging Face:
52
+
53
+ ```bash
54
+ git add .
55
+ git commit -m "Initial commit"
56
+ git push
57
+ ```
58
+
59
+ 6. Hugging Face will automatically build and deploy your Docker container
60
+
61
+ ## How to Play
62
+
63
+ 1. Click on cards to flip them over
64
+ 2. Try to find matching pairs of emojis
65
+ 3. The game is complete when all pairs have been matched
66
+ 4. Click "New Game" to reset and play again
67
+
68
+ ## License
69
+
70
+ MIT
app.py DELETED
@@ -1,15 +0,0 @@
1
- from fastapi import FastAPI
2
- from fastapi.responses import RedirectResponse
3
- from fastapi.staticfiles import StaticFiles
4
-
5
- # FastAPI application
6
- app = FastAPI(title="Snake Game React Space")
7
-
8
- # Serve compiled React app (generated by Vite) as static files
9
- app.mount("/", StaticFiles(directory="static", html=True), name="static")
10
-
11
-
12
- # Health-check endpoint for the Space
13
- @app.get("/ping")
14
- def ping():
15
- return {"status": "ok"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eslint.config.js ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+
7
+ export default tseslint.config(
8
+ { ignores: ['dist'] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ['**/*.{ts,tsx}'],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ 'react-hooks': reactHooks,
18
+ 'react-refresh': reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ 'react-refresh/only-export-components': [
23
+ 'warn',
24
+ { allowConstantExport: true },
25
+ ],
26
+ },
27
+ },
28
+ )
frontend/package.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "name": "snake-react",
3
- "version": "0.0.1",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview"
10
- },
11
- "dependencies": {
12
- "react": "^18.2.0",
13
- "react-dom": "^18.2.0"
14
- },
15
- "devDependencies": {
16
- "@types/react": "^18.2.0",
17
- "@types/react-dom": "^18.2.0",
18
- "@vitejs/plugin-react": "^4.1.0",
19
- "typescript": "^5.4.4",
20
- "vite": "^5.2.0"
21
- }
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/App.css DELETED
@@ -1,3 +0,0 @@
1
- canvas {
2
- border: 1px solid #333;
3
- }
 
 
 
 
frontend/src/App.tsx DELETED
@@ -1,100 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import './App.css';
3
-
4
- type Point = { x: number; y: number };
5
-
6
- const CELL = 20;
7
- const COLS = 30;
8
- const ROWS = 20;
9
- const WIDTH = COLS * CELL;
10
- const HEIGHT = ROWS * CELL;
11
-
12
- const DIRECTIONS: Record<string, Point> = {
13
- ArrowUp: { x: 0, y: -1 },
14
- ArrowDown: { x: 0, y: 1 },
15
- ArrowLeft: { x: -1, y: 0 },
16
- ArrowRight: { x: 1, y: 0 },
17
- };
18
-
19
- function App() {
20
- const canvasRef = useRef<HTMLCanvasElement>(null);
21
- const [snake, setSnake] = useState<Point[]>([
22
- { x: 5, y: 5 },
23
- ]);
24
- const [food, setFood] = useState<Point>({ x: 10, y: 10 });
25
- const [dir, setDir] = useState<Point>({ x: 1, y: 0 });
26
- const [gameOver, setGameOver] = useState(false);
27
-
28
- const randomFood = () => {
29
- return {
30
- x: Math.floor(Math.random() * COLS),
31
- y: Math.floor(Math.random() * ROWS),
32
- };
33
- };
34
-
35
- useEffect(() => {
36
- const handleKey = (e: KeyboardEvent) => {
37
- if (DIRECTIONS[e.key]) {
38
- setDir(DIRECTIONS[e.key]);
39
- }
40
- };
41
- window.addEventListener('keydown', handleKey);
42
- return () => window.removeEventListener('keydown', handleKey);
43
- }, []);
44
-
45
- useEffect(() => {
46
- const ctx = canvasRef.current?.getContext('2d');
47
- if (!ctx) return;
48
-
49
- const interval = setInterval(() => {
50
- setSnake((prev: Point[]) => {
51
- const head = { x: prev[0].x + dir.x, y: prev[0].y + dir.y };
52
- // check wall
53
- if (head.x < 0 || head.x >= COLS || head.y < 0 || head.y >= ROWS) {
54
- setGameOver(true);
55
- clearInterval(interval);
56
- return prev;
57
- }
58
- // check self
59
- if (prev.some((p: Point) => p.x === head.x && p.y === head.y)) {
60
- setGameOver(true);
61
- clearInterval(interval);
62
- return prev;
63
- }
64
- const newSnake = [head, ...prev];
65
- if (head.x === food.x && head.y === food.y) {
66
- setFood(randomFood());
67
- } else {
68
- newSnake.pop();
69
- }
70
- return newSnake;
71
- });
72
- }, 100);
73
- return () => clearInterval(interval);
74
- }, [dir, food]);
75
-
76
- useEffect(() => {
77
- const ctx = canvasRef.current?.getContext('2d');
78
- if (!ctx) return;
79
- ctx.clearRect(0, 0, WIDTH, HEIGHT);
80
-
81
- // draw food
82
- ctx.fillStyle = 'red';
83
- ctx.fillRect(food.x * CELL, food.y * CELL, CELL, CELL);
84
-
85
- // draw snake
86
- ctx.fillStyle = 'green';
87
- snake.forEach((s: Point) => {
88
- ctx.fillRect(s.x * CELL, s.y * CELL, CELL, CELL);
89
- });
90
- }, [snake, food]);
91
-
92
- return (
93
- <div className="wrapper">
94
- {gameOver && <h2>Game Over</h2>}
95
- <canvas ref={canvasRef} width={WIDTH} height={HEIGHT} />
96
- </div>
97
- );
98
- }
99
-
100
- export default App;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/global.d.ts DELETED
@@ -1 +0,0 @@
1
- declare module '*.css';
 
 
frontend/src/index.css DELETED
@@ -1,13 +0,0 @@
1
- body {
2
- margin: 0;
3
- font-family: system-ui, sans-serif;
4
- display: flex;
5
- justify-content: center;
6
- align-items: center;
7
- height: 100vh;
8
- background-color: #f5f5f5;
9
- }
10
-
11
- .wrapper {
12
- text-align: center;
13
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/main.tsx DELETED
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import App from './App';
4
- import './index.css';
5
-
6
- ReactDOM.createRoot(document.getElementById('root')!).render(
7
- <React.StrictMode>
8
- <App />
9
- </React.StrictMode>
10
- );
 
 
 
 
 
 
 
 
 
 
 
frontend/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "jsx": "react-jsx",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "forceConsistentCasingInFileNames": true,
9
- "skipLibCheck": true,
10
- "moduleResolution": "bundler",
11
- "allowJs": false,
12
- "resolveJsonModule": true
13
- },
14
- "types": ["vite/client"],
15
- "include": ["src", "src/**/*.css", "src/global.d.ts"]
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/vite.config.ts DELETED
@@ -1,12 +0,0 @@
1
- import { defineConfig } from 'vite';
2
- import react from '@vitejs/plugin-react';
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- server: {
7
- port: 5173,
8
- },
9
- build: {
10
- outDir: 'dist',
11
- },
12
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
huggingface-space.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Memory Card Game
3
+ emoji: 🎮
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 3000
9
+ ---
10
+
11
+ # Memory Card Game
12
+
13
+ A fun memory matching game built with React and Vite.
14
+
15
+ Play by clicking on cards to reveal them and find matching pairs. The game tracks your moves and lets you know when you've won!
16
+
17
+ ## Technology Stack
18
+ - React + TypeScript
19
+ - Vite
20
+ - Docker
frontend/index.html → index.html RENAMED
@@ -2,11 +2,12 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Snake Game</title>
7
  </head>
8
  <body>
9
  <div id="root"></div>
10
  <script type="module" src="/src/main.tsx"></script>
11
  </body>
12
- </html>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Memory Card Game</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
11
  <script type="module" src="/src/main.tsx"></script>
12
  </body>
13
+ </html>
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "memory-card-game",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^19.0.0",
14
+ "react-dom": "^19.0.0"
15
+ },
16
+ "devDependencies": {
17
+ "@eslint/js": "^9.22.0",
18
+ "@types/react": "^19.0.10",
19
+ "@types/react-dom": "^19.0.4",
20
+ "@vitejs/plugin-react": "^4.3.4",
21
+ "eslint": "^9.22.0",
22
+ "eslint-plugin-react-hooks": "^5.2.0",
23
+ "eslint-plugin-react-refresh": "^0.4.19",
24
+ "globals": "^16.0.0",
25
+ "typescript": "~5.7.2",
26
+ "typescript-eslint": "^8.26.1",
27
+ "vite": "^6.3.1"
28
+ }
29
+ }
public/vite.svg ADDED
requirements.txt DELETED
@@ -1,3 +0,0 @@
1
- fastapi
2
- uvicorn[standard]
3
- pygame
 
 
 
 
snake_game.py DELETED
@@ -1,155 +0,0 @@
1
- import pygame
2
- import random
3
- import sys
4
-
5
- # Initialize pygame
6
- pygame.init()
7
-
8
- # Screen dimensions
9
- WIDTH, HEIGHT = 600, 400
10
- CELL_SIZE = 20 # Size of a grid cell
11
- GRID_WIDTH = WIDTH // CELL_SIZE
12
- GRID_HEIGHT = HEIGHT // CELL_SIZE
13
-
14
- # Colors (R, G, B)
15
- WHITE = (255, 255, 255)
16
- BLACK = (0, 0, 0)
17
- RED = (200, 0, 0)
18
- GREEN = (0, 200, 0)
19
- DARK_GREEN = (0, 155, 0)
20
-
21
- # Frames per second
22
- FPS = 10
23
-
24
- # Directions
25
- UP = (0, -1)
26
- DOWN = (0, 1)
27
- LEFT = (-1, 0)
28
- RIGHT = (1, 0)
29
-
30
- # Create the screen
31
- screen = pygame.display.set_mode((WIDTH, HEIGHT))
32
- pygame.display.set_caption("Snake Game")
33
- clock = pygame.time.Clock()
34
- font = pygame.font.SysFont("arial", 24)
35
-
36
-
37
- def draw_grid():
38
- """Draw grid lines (optional)."""
39
- for x in range(0, WIDTH, CELL_SIZE):
40
- pygame.draw.line(screen, BLACK, (x, 0), (x, HEIGHT))
41
- for y in range(0, HEIGHT, CELL_SIZE):
42
- pygame.draw.line(screen, BLACK, (0, y), (WIDTH, y))
43
-
44
-
45
- def random_food_position(snake_body):
46
- """Return a random position not occupied by the snake."""
47
- while True:
48
- pos = (random.randint(0, GRID_WIDTH - 1), random.randint(0, GRID_HEIGHT - 1))
49
- if pos not in snake_body:
50
- return pos
51
-
52
-
53
- def draw_snake(snake_body):
54
- for segment in snake_body:
55
- rect = pygame.Rect(segment[0] * CELL_SIZE, segment[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE)
56
- pygame.draw.rect(screen, DARK_GREEN, rect)
57
- pygame.draw.rect(screen, GREEN, rect.inflate(-4, -4))
58
-
59
-
60
- def draw_food(food_pos):
61
- rect = pygame.Rect(food_pos[0] * CELL_SIZE, food_pos[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE)
62
- pygame.draw.rect(screen, RED, rect)
63
-
64
-
65
- def show_text(text, center):
66
- surface = font.render(text, True, BLACK)
67
- rect = surface.get_rect(center=center)
68
- screen.blit(surface, rect)
69
-
70
-
71
- def game_over_screen(score):
72
- screen.fill(WHITE)
73
- show_text(f"Game Over! Score: {score}", (WIDTH // 2, HEIGHT // 2 - 20))
74
- show_text("Press Enter to play again or Esc to quit", (WIDTH // 2, HEIGHT // 2 + 20))
75
- pygame.display.flip()
76
-
77
- waiting = True
78
- while waiting:
79
- for event in pygame.event.get():
80
- if event.type == pygame.QUIT:
81
- pygame.quit()
82
- sys.exit()
83
- elif event.type == pygame.KEYDOWN:
84
- if event.key == pygame.K_RETURN:
85
- waiting = False
86
- elif event.key == pygame.K_ESCAPE:
87
- pygame.quit()
88
- sys.exit()
89
- clock.tick(60)
90
-
91
-
92
- def main():
93
- # Initial snake setup
94
- snake_body = [(GRID_WIDTH // 2, GRID_HEIGHT // 2)]
95
- direction = RIGHT
96
- food_pos = random_food_position(snake_body)
97
- score = 0
98
-
99
- running = True
100
- while running:
101
- clock.tick(FPS)
102
- for event in pygame.event.get():
103
- if event.type == pygame.QUIT:
104
- pygame.quit()
105
- sys.exit()
106
- elif event.type == pygame.KEYDOWN:
107
- if event.key == pygame.K_UP and direction != DOWN:
108
- direction = UP
109
- elif event.key == pygame.K_DOWN and direction != UP:
110
- direction = DOWN
111
- elif event.key == pygame.K_LEFT and direction != RIGHT:
112
- direction = LEFT
113
- elif event.key == pygame.K_RIGHT and direction != LEFT:
114
- direction = RIGHT
115
-
116
- # Calculate new head position
117
- new_head = (snake_body[0][0] + direction[0], snake_body[0][1] + direction[1])
118
-
119
- # Check collisions with walls
120
- if (
121
- new_head[0] < 0
122
- or new_head[0] >= GRID_WIDTH
123
- or new_head[1] < 0
124
- or new_head[1] >= GRID_HEIGHT
125
- ):
126
- game_over_screen(score)
127
- return # Restart whole game
128
-
129
- # Check collision with self
130
- if new_head in snake_body:
131
- game_over_screen(score)
132
- return
133
-
134
- # Move snake
135
- snake_body.insert(0, new_head)
136
-
137
- # Check if food eaten
138
- if new_head == food_pos:
139
- score += 1
140
- food_pos = random_food_position(snake_body)
141
- else:
142
- snake_body.pop() # Remove tail segment if no food eaten
143
-
144
- # Draw everything
145
- screen.fill(WHITE)
146
- draw_grid()
147
- draw_snake(snake_body)
148
- draw_food(food_pos)
149
- show_text(f"Score: {score}", (60, 20))
150
- pygame.display.flip()
151
-
152
-
153
- if __name__ == "__main__":
154
- while True:
155
- main() # Restartable game loop
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/App.css ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .app {
2
+ max-width: 800px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ h1 {
9
+ font-size: 2.5rem;
10
+ margin-bottom: 1.5rem;
11
+ color: #333;
12
+ }
13
+
14
+ .game-info {
15
+ display: flex;
16
+ justify-content: space-between;
17
+ align-items: center;
18
+ margin-bottom: 2rem;
19
+ font-size: 1.2rem;
20
+ }
21
+
22
+ button {
23
+ background-color: #646cff;
24
+ color: white;
25
+ border: none;
26
+ padding: 0.5rem 1rem;
27
+ border-radius: 4px;
28
+ cursor: pointer;
29
+ font-size: 1rem;
30
+ transition: background-color 0.3s;
31
+ }
32
+
33
+ button:hover {
34
+ background-color: #535bf2;
35
+ }
36
+
37
+ .game-board {
38
+ display: grid;
39
+ grid-template-columns: repeat(4, 1fr);
40
+ grid-gap: 1rem;
41
+ max-width: 600px;
42
+ margin: 0 auto;
43
+ }
44
+
45
+ .card {
46
+ position: relative;
47
+ height: 120px;
48
+ border-radius: 8px;
49
+ perspective: 1000px;
50
+ cursor: pointer;
51
+ transition: transform 0.15s;
52
+ }
53
+
54
+ .card:hover {
55
+ transform: scale(1.03);
56
+ }
57
+
58
+ .card-front,
59
+ .card-back {
60
+ position: absolute;
61
+ width: 100%;
62
+ height: 100%;
63
+ backface-visibility: hidden;
64
+ border-radius: 8px;
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ transition: transform 0.6s;
69
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
70
+ }
71
+
72
+ .card-front {
73
+ background-color: white;
74
+ transform: rotateY(180deg);
75
+ font-size: 3rem;
76
+ }
77
+
78
+ .card-back {
79
+ background-color: #646cff;
80
+ color: white;
81
+ font-size: 2rem;
82
+ }
83
+
84
+ .card.flipped .card-front {
85
+ transform: rotateY(0);
86
+ }
87
+
88
+ .card.flipped .card-back {
89
+ transform: rotateY(180deg);
90
+ }
91
+
92
+ .card.matched {
93
+ opacity: 0.7;
94
+ }
95
+
96
+ .game-over {
97
+ position: fixed;
98
+ top: 0;
99
+ left: 0;
100
+ right: 0;
101
+ bottom: 0;
102
+ background-color: rgba(0, 0, 0, 0.7);
103
+ display: flex;
104
+ flex-direction: column;
105
+ justify-content: center;
106
+ align-items: center;
107
+ color: white;
108
+ z-index: 10;
109
+ }
110
+
111
+ .game-over h2 {
112
+ font-size: 3rem;
113
+ margin-bottom: 1rem;
114
+ }
115
+
116
+ .game-over p {
117
+ font-size: 1.5rem;
118
+ }
119
+
120
+ @media (max-width: 600px) {
121
+ .game-board {
122
+ grid-template-columns: repeat(3, 1fr);
123
+ }
124
+
125
+ .card {
126
+ height: 100px;
127
+ }
128
+ }
src/App.tsx ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react'
2
+ import './App.css'
3
+
4
+ // Card interface
5
+ interface Card {
6
+ id: number;
7
+ content: string;
8
+ flipped: boolean;
9
+ matched: boolean;
10
+ }
11
+
12
+ function App() {
13
+ const emojis = ['🐱', '🐶', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼'];
14
+ const [cards, setCards] = useState<Card[]>([]);
15
+ const [flippedCards, setFlippedCards] = useState<number[]>([]);
16
+ const [moves, setMoves] = useState(0);
17
+ const [gameOver, setGameOver] = useState(false);
18
+
19
+ // Initialize game
20
+ useEffect(() => {
21
+ initGame();
22
+ }, []);
23
+
24
+ // Check for matches when two cards are flipped
25
+ useEffect(() => {
26
+ if (flippedCards.length === 2) {
27
+ const timer = setTimeout(() => {
28
+ checkForMatch();
29
+ }, 700);
30
+ return () => clearTimeout(timer);
31
+ }
32
+ }, [flippedCards]);
33
+
34
+ // Init game
35
+ const initGame = () => {
36
+ // Create pairs of cards with emojis
37
+ const cardPairs = [...emojis, ...emojis]
38
+ .map((emoji, index) => ({
39
+ id: index,
40
+ content: emoji,
41
+ flipped: false,
42
+ matched: false
43
+ }))
44
+ .sort(() => Math.random() - 0.5);
45
+
46
+ setCards(cardPairs);
47
+ setFlippedCards([]);
48
+ setMoves(0);
49
+ setGameOver(false);
50
+ };
51
+
52
+ // Handle card click
53
+ const handleCardClick = (id: number) => {
54
+ // Ignore if card is already flipped or if 2 cards are already flipped
55
+ if (
56
+ flippedCards.length === 2 ||
57
+ flippedCards.includes(id) ||
58
+ cards[id].matched
59
+ ) {
60
+ return;
61
+ }
62
+
63
+ // Flip card
64
+ setCards(prevCards =>
65
+ prevCards.map(card =>
66
+ card.id === id ? { ...card, flipped: true } : card
67
+ )
68
+ );
69
+
70
+ // Add card to flipped cards
71
+ setFlippedCards(prev => [...prev, id]);
72
+ };
73
+
74
+ // Check for match
75
+ const checkForMatch = () => {
76
+ const [first, second] = flippedCards;
77
+
78
+ if (cards[first].content === cards[second].content) {
79
+ // If match, keep cards flipped and mark as matched
80
+ setCards(prevCards =>
81
+ prevCards.map(card =>
82
+ card.id === first || card.id === second
83
+ ? { ...card, matched: true }
84
+ : card
85
+ )
86
+ );
87
+ } else {
88
+ // If no match, flip cards back
89
+ setCards(prevCards =>
90
+ prevCards.map(card =>
91
+ card.id === first || card.id === second
92
+ ? { ...card, flipped: false }
93
+ : card
94
+ )
95
+ );
96
+ }
97
+
98
+ // Increment moves and reset flipped cards
99
+ setMoves(prevMoves => prevMoves + 1);
100
+ setFlippedCards([]);
101
+
102
+ // Check if all cards are matched
103
+ const allMatched = cards.every(card => card.matched || flippedCards.includes(card.id));
104
+ if (allMatched) {
105
+ setGameOver(true);
106
+ }
107
+ };
108
+
109
+ return (
110
+ <div className="app">
111
+ <h1>Memory Game</h1>
112
+ <div className="game-info">
113
+ <div>Moves: {moves}</div>
114
+ <button onClick={initGame}>New Game</button>
115
+ </div>
116
+
117
+ <div className="game-board">
118
+ {cards.map(card => (
119
+ <div
120
+ key={card.id}
121
+ className={`card ${card.flipped || card.matched ? 'flipped' : ''} ${card.matched ? 'matched' : ''}`}
122
+ onClick={() => handleCardClick(card.id)}
123
+ >
124
+ <div className="card-back">?</div>
125
+ <div className="card-front">{card.content}</div>
126
+ </div>
127
+ ))}
128
+ </div>
129
+
130
+ {gameOver && (
131
+ <div className="game-over">
132
+ <h2>Game Over!</h2>
133
+ <p>You won in {moves} moves!</p>
134
+ </div>
135
+ )}
136
+ </div>
137
+ )
138
+ }
139
+
140
+ export default App
src/assets/react.svg ADDED
src/index.css ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
3
+ line-height: 1.5;
4
+ font-weight: 400;
5
+
6
+ color-scheme: light dark;
7
+ color: rgba(255, 255, 255, 0.87);
8
+ background-color: #242424;
9
+
10
+ font-synthesis: none;
11
+ text-rendering: optimizeLegibility;
12
+ -webkit-font-smoothing: antialiased;
13
+ -moz-osx-font-smoothing: grayscale;
14
+ }
15
+
16
+ a {
17
+ font-weight: 500;
18
+ color: #646cff;
19
+ text-decoration: inherit;
20
+ }
21
+ a:hover {
22
+ color: #535bf2;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ display: flex;
28
+ place-items: center;
29
+ min-width: 320px;
30
+ min-height: 100vh;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 3.2em;
35
+ line-height: 1.1;
36
+ }
37
+
38
+ button {
39
+ border-radius: 8px;
40
+ border: 1px solid transparent;
41
+ padding: 0.6em 1.2em;
42
+ font-size: 1em;
43
+ font-weight: 500;
44
+ font-family: inherit;
45
+ background-color: #1a1a1a;
46
+ cursor: pointer;
47
+ transition: border-color 0.25s;
48
+ }
49
+ button:hover {
50
+ border-color: #646cff;
51
+ }
52
+ button:focus,
53
+ button:focus-visible {
54
+ outline: 4px auto -webkit-focus-ring-color;
55
+ }
56
+
57
+ @media (prefers-color-scheme: light) {
58
+ :root {
59
+ color: #213547;
60
+ background-color: #ffffff;
61
+ }
62
+ a:hover {
63
+ color: #747bff;
64
+ }
65
+ button {
66
+ background-color: #f9f9f9;
67
+ }
68
+ }
src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />
tsconfig.app.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2020",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "strict": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["src"]
26
+ }
tsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
tsconfig.node.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "isolatedModules": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedSideEffectImports": true
22
+ },
23
+ "include": ["vite.config.ts"]
24
+ }
vite.config.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })