Skip to content

Commit 372974b

Browse files
Merge pull request #3230 from inayushgupta/snakegame
Snakegame
2 parents 70aea8e + 8a8fd69 commit 372974b

13 files changed

Lines changed: 416 additions & 0 deletions

File tree

‎Snake/.gitignore‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Python-generated files
2+
__pycache__/
3+
*.py[oc]
4+
build/
5+
dist/
6+
wheels/
7+
*.egg-info
8+
9+
# Virtual environments
10+
.venv

‎Snake/.python-version‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

‎Snake/README.md‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# SnakePix
2+
3+
SnakePix is a terminal-based clone of the classic Snake game built with Python and `curses`.
4+
5+
## Description
6+
7+
A polished terminal game with a fixed-size board, responsive controls, score tracking, and a main menu for choosing difficulty. The game renders a bordered arena, displays score live, and shows a game over screen with restart options.
8+
9+
## Features
10+
11+
- Fixed board size: `72 x 36`
12+
- Live score display during gameplay
13+
- Difficulty menu with three speeds: `EASY`, `MEDIUM`, `HARD`
14+
- `WASD` and arrow key movement
15+
- Auto-pause when terminal window is too small
16+
- Simple snake body rendered as `@`
17+
- Food rendered as `*`
18+
- Game over screen with `MAIN MENU` and `QUIT` options
19+
- No external dependencies beyond Python and `curses`
20+
21+
## Requirements
22+
23+
- Python 3.13+
24+
- Terminal with `curses` support
25+
26+
## Installation
27+
28+
1. Linux or Mac - Curses comes pre-installed with python3
29+
30+
2. Windows - Run
31+
```bash
32+
pip3 install -r requirements.txt
33+
34+
## Running the Game
35+
36+
From the project root:
37+
38+
```bash
39+
python main.py
40+
```
41+
42+
## Controls
43+
44+
- `W` or `Up Arrow`: move up
45+
- `S` or `Down Arrow`: move down
46+
- `A` or `Left Arrow`: move left
47+
- `D` or `Right Arrow`: move right
48+
- `Q`: quit from the main menu screen
49+
- `Enter`: select menu options
50+
51+
## Project Structure
52+
53+
- `main.py` — application entry point and main loop
54+
- `menu.py` — difficulty selection menu
55+
- `game.py` — main game loop, snake movement, collision detection, and food handling
56+
- `arena.py` — arena rendering, score display, and boundary drawing
57+
- `snake.py` — snake body representation, movement, and collision logic
58+
- `utils.py` — food generation and drawing utilities
59+
- `pause.py` — resize handling when the terminal is too small
60+
- `constants.py` — board dimensions and input key constants
61+
62+
## Notes
63+
64+
- The terminal should be at least `72` columns by `36` rows for the game to display correctly.
65+
- The game will pause if the terminal is resized smaller than the required board dimensions.
66+
- The snake cannot reverse directly into itself (up/down and left/right inversions are blocked).

‎Snake/arena.py‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import curses
2+
from constants import *
3+
from pause import pause
4+
5+
def arena(screen, score = 0):
6+
height, width = screen.getmaxyx()
7+
8+
if height < HEIGHT or width < WIDTH:
9+
pause(screen, height, width)
10+
screen.refresh()
11+
else:
12+
try:
13+
for r in range(HEIGHT):
14+
if r == 0:
15+
screen.addch(r, 0, curses.ACS_ULCORNER)
16+
for c in range(1, WIDTH - 1):
17+
screen.addch(r, c, curses.ACS_HLINE)
18+
screen.addch(r, WIDTH - 1, curses.ACS_URCORNER)
19+
elif r == 1:
20+
score_string = f"SCORE : {score}"
21+
screen.addstr(r, 1, score_string)
22+
for c in (0, WIDTH-1):
23+
screen.addch(r, c, curses.ACS_VLINE)
24+
elif r == HEIGHT - 1:
25+
screen.addch(r, 0, curses.ACS_LLCORNER)
26+
for c in range(1, WIDTH - 1):
27+
screen.addch(r, c, curses.ACS_HLINE)
28+
screen.addch(r, WIDTH - 1, curses.ACS_LRCORNER)
29+
elif r == 2:
30+
screen.addch(r, 0, curses.ACS_LTEE)
31+
for c in range(1, WIDTH - 1):
32+
screen.addch(r, c, curses.ACS_HLINE)
33+
screen.addch(r, WIDTH - 1, curses.ACS_RTEE)
34+
else:
35+
screen.addch(r, 0, curses.ACS_VLINE)
36+
for c in range(1, WIDTH - 1):
37+
screen.addch(r, c, " ")
38+
screen.addch(r, WIDTH - 1, curses.ACS_VLINE)
39+
except curses.error:
40+
pass

‎Snake/constants.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#board
2+
WIDTH = 72
3+
HEIGHT = 36
4+
5+
# wasd movement
6+
7+
W = ord('w')
8+
A = ord('a')
9+
S = ord('s')
10+
D = ord('d')
11+
12+
# directions
13+
UP = (0, 1)
14+
DW = (0, -1)
15+
LT = (-1, 0)
16+
RT = (1, 0)
17+
18+

‎Snake/game.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import curses
2+
from arena import arena
3+
import constants
4+
from utils import *
5+
from collections import deque
6+
from time import sleep
7+
from pause import *
8+
from snake import Snake
9+
10+
"""
11+
order of execution
12+
1. check if the previous state produced a collision
13+
2. if it does then do not render the next state and quit to over screen
14+
3. if no collision, then render the state on the screen
15+
"""
16+
17+
18+
def game(screen, setting):
19+
if setting is None:
20+
return
21+
22+
snake = Snake([(12, 12), (12, 11), (12, 10)])
23+
score = 0
24+
25+
screen.nodelay(True)
26+
try:
27+
food = gen_food(snake)
28+
29+
while True:
30+
31+
r, c = screen.getmaxyx()
32+
if r < HEIGHT or c < WIDTH:
33+
pause(screen, r, c)
34+
screen.refresh()
35+
sleep(0.1)
36+
continue
37+
38+
key = screen.getch()
39+
snake.change_direction(key)
40+
snake.move()
41+
42+
if snake.head() != food:
43+
snake.removeTail()
44+
else:
45+
score += 1
46+
food = gen_food(snake)
47+
48+
if snake.is_collision():
49+
screen.clear()
50+
break
51+
52+
screen.clear()
53+
arena(screen, score)
54+
snake.draw(screen)
55+
draw_food(screen, food)
56+
57+
screen.refresh()
58+
59+
sleep(setting)
60+
finally:
61+
screen.nodelay(False)
62+
return score
63+
64+
65+
66+
67+
68+
69+

‎Snake/main.py‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import curses
2+
from time import sleep
3+
from menu import *
4+
from arena import arena
5+
from pause import *
6+
from game import *
7+
from over import *
8+
from sys import exit
9+
10+
def main(screen):
11+
screen.keypad(True)
12+
curses.noecho()
13+
curses.curs_set(0)
14+
try:
15+
while True:
16+
screen.clear()
17+
18+
height, width = screen.getmaxyx()
19+
20+
if height < HEIGHT or width < WIDTH:
21+
pause(screen, height, width)
22+
else:
23+
arena(screen)
24+
screen.refresh()
25+
setting = menu(screen)
26+
score = game(screen, setting)
27+
selection = over(screen, score)
28+
29+
if selection == "MAIN MENU":
30+
continue
31+
else:
32+
exit(0)
33+
34+
screen.refresh()
35+
key = screen.getch()
36+
if key == ord('q'):
37+
break
38+
finally:
39+
screen.keypad(False)
40+
41+
if __name__ == "__main__":
42+
curses.wrapper(main)

‎Snake/menu.py‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import curses
2+
from constants import *
3+
from arena import *
4+
5+
def menu(screen):
6+
menu_options = ['EASY', 'MEDIUM', 'HARD']
7+
8+
settings = {
9+
'EASY': 0.20,
10+
'MEDIUM': 0.10,
11+
'HARD': 0.05
12+
}
13+
14+
heading = "SNAKE PIX!"
15+
selection = 0
16+
17+
while True:
18+
screen.clear()
19+
height, width = screen.getmaxyx()
20+
arena(screen)
21+
try:
22+
23+
if height < HEIGHT or width < WIDTH:
24+
pause(screen, height, width)
25+
screen.refresh()
26+
continue
27+
28+
screen.addstr(HEIGHT//3 - 6, (WIDTH // 2) - len(heading)//2 , heading)
29+
screen.addstr(HEIGHT//3 + 6 + selection * 2, (WIDTH // 2) - 10, ">>")
30+
for i, option in enumerate(menu_options):
31+
screen.addstr(HEIGHT//3 + 6 + i * 2, (WIDTH // 2) - len(option)//2, option)
32+
33+
screen.refresh()
34+
except curses.error:
35+
return
36+
key = screen.getch()
37+
38+
if key in (S, curses.KEY_DOWN):
39+
selection = selection + 1 if selection < 2 else selection
40+
elif key in (W, curses.KEY_UP):
41+
selection = selection - 1 if selection > 0 else selection
42+
elif key == ord('\n'):
43+
return settings[menu_options[selection]]
44+
45+

‎Snake/over.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from constants import *
2+
from arena import *
3+
4+
def over(screen, score):
5+
6+
heading = "GAME OVER!"
7+
selection = 0
8+
menu_options = ['MAIN MENU', 'QUIT']
9+
10+
while True:
11+
screen.clear()
12+
height, width = screen.getmaxyx()
13+
arena(screen, score)
14+
try:
15+
16+
if height < HEIGHT or width < WIDTH:
17+
raise curses.error
18+
19+
score_string = f"FINAL SCORE : {score}"
20+
screen.addstr(HEIGHT//3 - 6, (WIDTH // 2) - len(heading)//2 , heading)
21+
screen.addstr(HEIGHT//3 - 3, (WIDTH // 2) - len(score_string)//2 , score_string)
22+
screen.addstr(HEIGHT//3 + 6 + selection * 2, (WIDTH // 2) - 10, ">>")
23+
for i, option in enumerate(menu_options):
24+
screen.addstr(HEIGHT//3 + 6 + i * 2, (WIDTH // 2) - len(option)//2, option)
25+
26+
screen.refresh()
27+
except curses.error:
28+
return
29+
key = screen.getch()
30+
31+
if key in (S, curses.KEY_DOWN):
32+
selection = selection + 1 if selection < 1 else selection
33+
elif key in (W, curses.KEY_UP):
34+
selection = selection - 1 if selection > 0 else selection
35+
elif key == ord('\n'):
36+
return menu_options[selection]

‎Snake/pause.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import curses
2+
from constants import *
3+
from arena import *
4+
5+
def pause(screen, r, c):
6+
7+
screen.clear()
8+
9+
message = "--FIX THE TERMINAL SIZE--"
10+
11+
if len('Pause') < c < len(message):
12+
message = "Pause"
13+
14+
if c < len(message):
15+
message = "P"
16+
17+
try:
18+
screen.addstr(r // 2, max(0, c // 2 - len(message)//2), message)
19+
except curses.error:
20+
pass

0 commit comments

Comments
 (0)