-
-
Notifications
You must be signed in to change notification settings - Fork 5
Max updates: Game series - Snake #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
5f9599e
2e07f61
da1c93d
fd9eb1c
d487e3c
8f707c8
2ae5dd5
68bd352
e0f140a
066860b
174bc84
4775df4
643b3d8
35ca6cf
5968435
a16d65b
476d710
8d71a74
8aab712
1916c7d
bfc82f0
49ecd24
f162c6c
2cbb0dd
6fc355f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 ( | ||
| <> | ||
| <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 /> | ||
| </> | ||
| ) | ||
| } | ||
| 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> | ||
| ) | ||
| } |
| 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? |
| 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. |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import js from '@eslint/js' | ||
|
BaseMax marked this conversation as resolved.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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, | ||
| }, | ||
| }, | ||
| ]) | ||
| 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> |
Uh oh!
There was an error while loading. Please reload this page.