From 735472644356ed0ae028b648753e14d6869419ad Mon Sep 17 00:00:00 2001 From: Matthew Reschke Date: Tue, 10 Mar 2026 18:34:52 -0600 Subject: [PATCH] Snake game --- snake.py | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 snake.py diff --git a/snake.py b/snake.py new file mode 100644 index 0000000..eedbab0 --- /dev/null +++ b/snake.py @@ -0,0 +1,152 @@ +import curses +import random +import time + +def main(stdscr, start_length): + # Setup curses + curses.curs_set(0) # Hide cursor + stdscr.nodelay(1) # Non-blocking getch + stdscr.timeout(100) # Wait 100ms for input + + # Snake and food characters + head_char = '█' + #head_char_up = '▄' + #head_char_down = '▀' + head_char_up = head_char_down = head_char + body_char = '▓' + #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): + break # Game over + + 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} ', 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 any key to exit", curses.color_pair(3)) + stdscr.nodelay(0) + stdscr.getch() + +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 + + curses.wrapper(main, start_len)