Files
snake/snake.py
2026-03-11 10:08:37 -06:00

186 lines
5.5 KiB
Python

import curses
import random
import time
def main(stdscr, start_length, speed):
# Setup curses
curses.curs_set(0) # Hide cursor
stdscr.nodelay(1) # Non-blocking getch
w = 100
if speed == 1: w = 250
if speed == 2: w = 225
if speed == 3: w = 200
if speed == 4: w = 175
if speed == 5: w = 150
if speed == 6: w = 125
if speed == 7: w = 100
if speed == 8: w = 75
if speed == 9: w = 50
if speed == 10: w = 25
stdscr.timeout(w) # Wait 100ms for input
# if speed = 50 then timeout = 100
# if speed = 100 then timeout = 50
# if speed = 0 then timeout = 1000
# Snake and food characters
head_char = ''
#head_char = '■'
#head_char = '0'
#head_char_up = '▄'
#head_char_down = '▀'
head_char_up = head_char_down = head_char
body_char = ''
#body_char = 'Φ'
#body_char = '■'
#body_char = 'O'
#body_char = '░'
#body_char = '■'
#food_char = '💗'
food_char = ''
#food_char = '▓'
#food_char = '▒'
# Setup colors
curses.start_color()
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) # Snake
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) # Food
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLACK) # Score/Text
stdscr.bkgd(' ', curses.color_pair(3))
# Game constants
h, w = stdscr.getmaxyx()
# Ensure window is large enough
if h < 10 or w < 20:
stdscr.addstr(0, 0, "Terminal window too small!")
stdscr.refresh()
time.sleep(2)
return
# Initial snake: list of [y, x]
# Check if snake fits horizontally
if start_length > w - 2:
stdscr.addstr(h // 2, (w - len(f"Length {start_length} too long for width {w}!")) // 2, f"Length {start_length} too long for width {w}!", curses.color_pair(3))
stdscr.refresh()
time.sleep(2)
return
# Start the head at x=start_length so the tail ends at x=1
snake = [[h // 2, start_length - i] for i in range(start_length)]
# Initial food
def get_new_food():
while True:
nf = [random.randint(1, h - 2), random.randint(1, w - 2)]
if nf not in snake:
return nf
food = get_new_food()
# Initial direction
key = curses.KEY_RIGHT
score = start_len
hc = head_char
while True:
next_key = stdscr.getch()
# If no key is pressed, next_key is -1
# Update direction if it's a valid arrow key and not opposite to current
if next_key != -1:
if next_key == curses.KEY_DOWN and key != curses.KEY_UP:
key = next_key
hc = head_char_down
elif next_key == curses.KEY_UP and key != curses.KEY_DOWN:
key = next_key
hc = head_char_up
elif next_key == curses.KEY_LEFT and key != curses.KEY_RIGHT:
key = next_key
hc = head_char
elif next_key == curses.KEY_RIGHT and key != curses.KEY_LEFT:
key = next_key
hc = head_char
elif next_key == ord('q'): # Allow quit
break
# Calculate new head
head = snake[0]
if key == curses.KEY_DOWN:
new_head = [head[0] + 1, head[1]]
elif key == curses.KEY_UP:
new_head = [head[0] - 1, head[1]]
elif key == curses.KEY_LEFT:
new_head = [head[0], head[1] - 1]
elif key == curses.KEY_RIGHT:
new_head = [head[0], head[1] + 1]
else:
# Should not happen
new_head = head
# Check collisions
if (new_head[0] in [0, h - 1] or
new_head[1] in [0, w - 1] or
new_head in snake):
key = stdscr.getch()
break
snake.insert(0, new_head)
# Check if snake ate food
if snake[0] == food:
score += 1
food = get_new_food()
else:
snake.pop() # Remove tail
# Draw everything
stdscr.clear()
stdscr.attron(curses.color_pair(3))
stdscr.border(0)
stdscr.attroff(curses.color_pair(3))
# Draw snake
for i, (y, x) in enumerate(snake):
char = hc if i == 0 else body_char
stdscr.addch(y, x, char, curses.color_pair(1))
# Draw food
stdscr.addch(food[0], food[1], food_char, curses.color_pair(2))
# Draw score
stdscr.addstr(0, 2, f' Length: {score} @ {speed}MPH ({w}x{h}) ', curses.color_pair(3))
stdscr.refresh()
# Game Over screen
#stdscr.clear()
msg = f"GAME OVER! SCORE: {score}"
stdscr.addstr(h // 2, (w - len(msg)) // 2, msg, curses.color_pair(3))
stdscr.addstr(h // 2 + 1, (w - 18) // 2, "Press Q to exit", curses.color_pair(3))
stdscr.nodelay(0)
key = stdscr.getch()
while key != ord('q'):
key = stdscr.getch()
exit('bye')
if __name__ == "__main__":
try:
start_len = input("Enter starting snake length (default 3): ")
start_len = int(start_len) if start_len.strip() else 3
if start_len < 1:
start_len = 3
except ValueError:
start_len = 3
try:
speed = input("Enter snake speed (1 to 10, default 7): ")
speed = int(speed) if speed.strip() else 7
if speed > 10: speed = 10
if speed < 1: speed = 1
except ValueError:
speed = 7
curses.wrapper(main, start_len, speed)