Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5f9599e
Adding theme switcher component/btn
BaseMax Mar 27, 2026
2e07f61
Merge branch 'main' of https://github.com/metropolis-retro/metropolis…
BaseMax Mar 27, 2026
da1c93d
Merge branch 'main' of https://github.com/metropolis-retro/metropolis…
BaseMax Apr 2, 2026
fd9eb1c
Merge branch 'main' of https://github.com/metropolis-retro/metropolis…
BaseMax Apr 2, 2026
d487e3c
Merge branch 'main' of https://github.com/metropolis-retro/metropolis…
BaseMax Apr 4, 2026
8f707c8
Merge branch 'main' of https://github.com/metropolis-retro/metropolis…
BaseMax Apr 5, 2026
2ae5dd5
Init and start putting snake game inside the project
BaseMax Apr 5, 2026
68bd352
Integrate the game into the nextjs project
BaseMax Apr 5, 2026
e0f140a
run pre-commit
BaseMax Apr 5, 2026
066860b
fix precommit
BaseMax Apr 5, 2026
174bc84
remove extra text
BaseMax Apr 5, 2026
4775df4
fix
BaseMax Apr 5, 2026
643b3d8
Fix ai feedback, update packages
BaseMax Apr 5, 2026
35ca6cf
Fix ai feedback
BaseMax Apr 5, 2026
5968435
Fix bug
BaseMax Apr 5, 2026
a16d65b
Merge branch 'main' into max-updates-games-snake
jbampton Apr 6, 2026
476d710
Merge branch 'main' into max-updates-games-snake
jbampton Apr 6, 2026
8d71a74
Merge branch 'main' into max-updates-games-snake
jbampton Apr 6, 2026
8aab712
Merge branch 'main' into max-updates-games-snake
jbampton Apr 6, 2026
1916c7d
Fix all AI feedbacks
BaseMax Apr 6, 2026
bfc82f0
Merge branch 'main' into max-updates-games-snake
jbampton Apr 6, 2026
49ecd24
Fix some AI feedbacks
BaseMax Apr 6, 2026
f162c6c
Merge branch 'max-updates-games-snake' of https://github.com/metropol…
BaseMax Apr 6, 2026
2cbb0dd
Merge branch 'main' into max-updates-games-snake
jbampton Apr 9, 2026
6fc355f
Merge branch 'main' into max-updates-games-snake
jbampton Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/games/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ const games: GameCard[] = [
{
title: "Snake",
description: "Guide the snake to eat food and grow longer without hitting the walls or yourself.",
href: "#",
href: "/games/snake",
emoji: "🐍",
available: false,
available: true,
},
{
title: "Tetris",
Expand Down
60 changes: 60 additions & 0 deletions app/games/snake/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Metadata } from "next"
import Link from "next/link"
import { Header } from "@/components/layout/header"
import { Footer } from "@/components/layout/footer"
import { SITE_URL, TITLE_BASE, pageKeywords } from "@/lib/seo"
import { ArrowLeft } from "lucide-react"
import { SnakeEmbed } from "./snake-embed"

export const metadata: Metadata = {
title: "Snake",
description:
"Play Snake in your browser. Collect food, grow longer, and avoid crashing into yourself.",
keywords: pageKeywords(["snake", "retro game", "arcade game", "browser game"]),
alternates: {
canonical: "/games/snake",
},
openGraph: {
title: `Snake | ${TITLE_BASE}`,
description: "Play Snake in your browser. Collect food, grow longer, and avoid crashing into yourself.",
url: `${SITE_URL}/games/snake`,
type: "website",
},
twitter: {
card: "summary_large_image",
title: `Snake | ${TITLE_BASE}`,
description: "Play Snake in your browser. Collect food, grow longer, and avoid crashing into yourself.",
},
}

export default function SnakePage() {
return (
<>
Comment thread
BaseMax marked this conversation as resolved.
Outdated
<Header />
<main className="pt-24 md:pt-32">
<section className="border-b border-border py-8 md:py-12">
<div className="mx-auto max-w-[1280px] px-6 md:px-12">
<Link
href="/games"
className="mb-6 inline-flex items-center gap-2 text-sm text-muted-foreground transition-colors hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
Back to games
</Link>
<h1 className="text-3xl tracking-tight md:text-5xl lg:text-6xl">Snake</h1>
<p className="mt-4 max-w-2xl leading-relaxed text-muted-foreground">
Use the arrow keys or WASD to move. Collect food to grow, avoid collisions, and tap the on-screen controls on mobile.
</p>
</div>
</section>

<section className="py-8 md:py-12">
<div className="mx-auto max-w-[1280px] px-6 md:px-12">
<SnakeEmbed />
</div>
</section>
</main>
<Footer />
</>
)
}
16 changes: 16 additions & 0 deletions app/games/snake/snake-embed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"use client"

import dynamic from "next/dynamic"

const SnakeApp = dynamic(() => import("@games/snake"), { ssr: false })

export function SnakeEmbed() {
return (
<div
className="relative w-full overflow-hidden rounded-xl border border-border bg-slate-950"
style={{ aspectRatio: "4/3", maxHeight: "80vh" }}
>
<SnakeApp />
</div>
)
}
24 changes: 24 additions & 0 deletions games/snake/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
21 changes: 21 additions & 0 deletions games/snake/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Seyyed Ali Mohammadiyeh <maxbasecode@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
89 changes: 89 additions & 0 deletions games/snake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# React Snake

A fully functional Snake game built with **React 19**, **TypeScript**, **TailwindCSS 4**, and **HTML5 Canvas**. Exportable as a `<SnakeGame />` component for use in any React/Next.js project.

## Features

- Classic Snake gameplay on a 20×20 grid
- Keyboard controls (Arrow keys / WASD) + mobile swipe & D-pad
- Three difficulty levels: Easy, Medium, Hard
- Optional obstacles that spawn on the board
- Power-ups that appear randomly after eating food:
- **Slow Motion** - reduces snake speed temporarily
- **Double Points** - 2× score multiplier
- **Phase Mode** - pass through walls, obstacles, and yourself
- Progressive leveling - speed increases every 5 food eaten
- High score persistence via `localStorage`
- 8-bit sound effects (Web Audio API) with mute toggle
- Pause/resume support (Esc / P)
- Smooth 60 FPS rendering via `requestAnimationFrame`
- Fully responsive canvas with mobile-friendly layout
- Embeddable via iframe or importable as a React component

## Run Locally

```bash
npm install
npm run dev
```

Open [http://localhost:5173](http://localhost:5173) in your browser.

## Production Build

```bash
npm run build
npm run preview
```

## Use as a Component

The game is published as a library (`ES` + `CJS`). Import it into any React project:

```tsx
import { SnakeGame } from "react-game-snake";

export default function App() {
return <SnakeGame className="w-full h-screen" />;
}
```

## Embed in an Iframe

After building, serve the `dist/` folder and embed it:

```html
<iframe
src="https://your-domain.com/snake/"
width="600"
height="680"
style="border: none;"
title="Snake Game"
></iframe>
```

## Project Structure

```
src/
components/ UI components (SnakeGame, GameBoard, GameHUD, GameOverlay, DPad, MuteButton)
config/ Game configuration & difficulty presets
game/
engine/ Pure game logic (tick, collision, food, snake, powerups, renderer)
hooks/ React hooks (useGameController, useGameLoop, useInput, etc.)
sound/ Web Audio sound effects
utils/ Shared helpers (score formatting, power-up display config)
types/ TypeScript type definitions
```

## Controls

| Action | Keyboard | Mobile |
| -------------- | ------------------ | --------------- |
| Move | Arrow keys / WASD | Swipe / D-pad |
| Pause / Resume | Esc / P | - |
| Start / Retry | Enter / Space | Tap button |

## License

MIT
23 changes: 23 additions & 0 deletions games/snake/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
Comment thread
BaseMax marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invalid import statement with non-standard syntax


The statement import js from '@eslint/js' does not conform to standard import syntax and will cause a syntax error, stopping the script from running. This syntax error must be fixed before committing to version control.

Replace the import statement with a valid syntax, such as import * as js from '@eslint/js' or import js from '@eslint/js' if the module supports default exports.

import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
13 changes: 13 additions & 0 deletions games/snake/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Snake</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading