Snake game
This commit is contained in:
152
snake.py
Normal file
152
snake.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user