Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions Snake/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv
1 change: 1 addition & 0 deletions Snake/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.13
66 changes: 66 additions & 0 deletions Snake/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SnakePix

SnakePix is a terminal-based clone of the classic Snake game built with Python and `curses`.

## Description

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.

## Features

- Fixed board size: `72 x 36`
- Live score display during gameplay
- Difficulty menu with three speeds: `EASY`, `MEDIUM`, `HARD`
- `WASD` and arrow key movement
- Auto-pause when terminal window is too small
- Simple snake body rendered as `@`
- Food rendered as `*`
- Game over screen with `MAIN MENU` and `QUIT` options
- No external dependencies beyond Python and `curses`

## Requirements

- Python 3.13+
- Terminal with `curses` support

## Installation

1. Linux or Mac - Curses comes pre-installed with python3

2. Windows - Run
```bash
pip3 install -r requirements.txt

## Running the Game

From the project root:

```bash
python main.py
```

## Controls

- `W` or `Up Arrow`: move up
- `S` or `Down Arrow`: move down
- `A` or `Left Arrow`: move left
- `D` or `Right Arrow`: move right
- `Q`: quit from the main menu screen
- `Enter`: select menu options

## Project Structure

- `main.py` — application entry point and main loop
- `menu.py` — difficulty selection menu
- `game.py` — main game loop, snake movement, collision detection, and food handling
- `arena.py` — arena rendering, score display, and boundary drawing
- `snake.py` — snake body representation, movement, and collision logic
- `utils.py` — food generation and drawing utilities
- `pause.py` — resize handling when the terminal is too small
- `constants.py` — board dimensions and input key constants

## Notes

- The terminal should be at least `72` columns by `36` rows for the game to display correctly.
- The game will pause if the terminal is resized smaller than the required board dimensions.
- The snake cannot reverse directly into itself (up/down and left/right inversions are blocked).
40 changes: 40 additions & 0 deletions Snake/arena.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import curses
from constants import *
from pause import pause

def arena(screen, score = 0):
height, width = screen.getmaxyx()

if height < HEIGHT or width < WIDTH:
pause(screen, height, width)
screen.refresh()
else:
try:
for r in range(HEIGHT):
if r == 0:
screen.addch(r, 0, curses.ACS_ULCORNER)
for c in range(1, WIDTH - 1):
screen.addch(r, c, curses.ACS_HLINE)
screen.addch(r, WIDTH - 1, curses.ACS_URCORNER)
elif r == 1:
score_string = f"SCORE : {score}"
screen.addstr(r, 1, score_string)
for c in (0, WIDTH-1):
screen.addch(r, c, curses.ACS_VLINE)
elif r == HEIGHT - 1:
screen.addch(r, 0, curses.ACS_LLCORNER)
for c in range(1, WIDTH - 1):
screen.addch(r, c, curses.ACS_HLINE)
screen.addch(r, WIDTH - 1, curses.ACS_LRCORNER)
elif r == 2:
screen.addch(r, 0, curses.ACS_LTEE)
for c in range(1, WIDTH - 1):
screen.addch(r, c, curses.ACS_HLINE)
screen.addch(r, WIDTH - 1, curses.ACS_RTEE)
else:
screen.addch(r, 0, curses.ACS_VLINE)
for c in range(1, WIDTH - 1):
screen.addch(r, c, " ")
screen.addch(r, WIDTH - 1, curses.ACS_VLINE)
except curses.error:
pass
18 changes: 18 additions & 0 deletions Snake/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#board
WIDTH = 72
HEIGHT = 36

# wasd movement

W = ord('w')
A = ord('a')
S = ord('s')
D = ord('d')

# directions
UP = (0, 1)
DW = (0, -1)
LT = (-1, 0)
RT = (1, 0)


69 changes: 69 additions & 0 deletions Snake/game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import curses
from arena import arena
import constants
from utils import *
from collections import deque
from time import sleep
from pause import *
from snake import Snake

"""
order of execution
1. check if the previous state produced a collision
2. if it does then do not render the next state and quit to over screen
3. if no collision, then render the state on the screen
"""


def game(screen, setting):
if setting is None:
return

snake = Snake([(12, 12), (12, 11), (12, 10)])
score = 0

screen.nodelay(True)
try:
food = gen_food(snake)

while True:

r, c = screen.getmaxyx()
if r < HEIGHT or c < WIDTH:
pause(screen, r, c)
screen.refresh()
sleep(0.1)
continue

key = screen.getch()
snake.change_direction(key)
snake.move()

if snake.head() != food:
snake.removeTail()
else:
score += 1
food = gen_food(snake)

if snake.is_collision():
screen.clear()
break

screen.clear()
arena(screen, score)
snake.draw(screen)
draw_food(screen, food)

screen.refresh()

sleep(setting)
finally:
screen.nodelay(False)
return score







42 changes: 42 additions & 0 deletions Snake/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import curses
from time import sleep
from menu import *
from arena import arena
from pause import *
from game import *
from over import *
from sys import exit

def main(screen):
screen.keypad(True)
curses.noecho()
curses.curs_set(0)
try:
while True:
screen.clear()

height, width = screen.getmaxyx()

if height < HEIGHT or width < WIDTH:
pause(screen, height, width)
else:
arena(screen)
screen.refresh()
setting = menu(screen)
score = game(screen, setting)
selection = over(screen, score)

if selection == "MAIN MENU":
continue
else:
exit(0)

screen.refresh()
key = screen.getch()
if key == ord('q'):
break
finally:
screen.keypad(False)

if __name__ == "__main__":
curses.wrapper(main)
45 changes: 45 additions & 0 deletions Snake/menu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import curses
from constants import *
from arena import *

def menu(screen):
menu_options = ['EASY', 'MEDIUM', 'HARD']

settings = {
'EASY': 0.20,
'MEDIUM': 0.10,
'HARD': 0.05
}

heading = "SNAKE PIX!"
selection = 0

while True:
screen.clear()
height, width = screen.getmaxyx()
arena(screen)
try:

if height < HEIGHT or width < WIDTH:
pause(screen, height, width)
screen.refresh()
continue

screen.addstr(HEIGHT//3 - 6, (WIDTH // 2) - len(heading)//2 , heading)
screen.addstr(HEIGHT//3 + 6 + selection * 2, (WIDTH // 2) - 10, ">>")
for i, option in enumerate(menu_options):
screen.addstr(HEIGHT//3 + 6 + i * 2, (WIDTH // 2) - len(option)//2, option)

screen.refresh()
except curses.error:
return
key = screen.getch()

if key in (S, curses.KEY_DOWN):
selection = selection + 1 if selection < 2 else selection
elif key in (W, curses.KEY_UP):
selection = selection - 1 if selection > 0 else selection
elif key == ord('\n'):
return settings[menu_options[selection]]


36 changes: 36 additions & 0 deletions Snake/over.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from constants import *
from arena import *

def over(screen, score):

heading = "GAME OVER!"
selection = 0
menu_options = ['MAIN MENU', 'QUIT']

while True:
screen.clear()
height, width = screen.getmaxyx()
arena(screen, score)
try:

if height < HEIGHT or width < WIDTH:
raise curses.error

score_string = f"FINAL SCORE : {score}"
screen.addstr(HEIGHT//3 - 6, (WIDTH // 2) - len(heading)//2 , heading)
screen.addstr(HEIGHT//3 - 3, (WIDTH // 2) - len(score_string)//2 , score_string)
screen.addstr(HEIGHT//3 + 6 + selection * 2, (WIDTH // 2) - 10, ">>")
for i, option in enumerate(menu_options):
screen.addstr(HEIGHT//3 + 6 + i * 2, (WIDTH // 2) - len(option)//2, option)

screen.refresh()
except curses.error:
return
key = screen.getch()

if key in (S, curses.KEY_DOWN):
selection = selection + 1 if selection < 1 else selection
elif key in (W, curses.KEY_UP):
selection = selection - 1 if selection > 0 else selection
elif key == ord('\n'):
return menu_options[selection]
20 changes: 20 additions & 0 deletions Snake/pause.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import curses
from constants import *
from arena import *

def pause(screen, r, c):

screen.clear()

message = "--FIX THE TERMINAL SIZE--"

if len('Pause') < c < len(message):
message = "Pause"

if c < len(message):
message = "P"

try:
screen.addstr(r // 2, max(0, c // 2 - len(message)//2), message)
except curses.error:
pass
6 changes: 6 additions & 0 deletions Snake/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Standard dependencies (if you have any)
# requests>=2.31.0

# Install curses wrapper ONLY if the target system is Windows
windows-curses; sys_platform == 'win32'

Loading