diff --git a/effects/over.mp3 b/effects/over.mp3 new file mode 100644 index 0000000..102ca69 Binary files /dev/null and b/effects/over.mp3 differ diff --git a/effects/start.mp3 b/effects/start.mp3 new file mode 100644 index 0000000..6713b67 Binary files /dev/null and b/effects/start.mp3 differ diff --git a/effects/start2.mp3 b/effects/start2.mp3 new file mode 100644 index 0000000..f3a6828 Binary files /dev/null and b/effects/start2.mp3 differ diff --git a/effects/wall.mp3 b/effects/wall.mp3 new file mode 100644 index 0000000..190908d Binary files /dev/null and b/effects/wall.mp3 differ diff --git a/snake.py b/snake.py index 976cb01..5876fd3 100644 --- a/snake.py +++ b/snake.py @@ -4,6 +4,7 @@ import time from typing import Any import pygame import multiprocessing +import signal class Game: @@ -18,6 +19,8 @@ class Game: # Defaults self.snake_length = 3 self.snake_speed = 7 + self.enable_effects = True + self.enable_music = False # Snake and food characters # ASCII Table: https://theasciicode.com.ar/ @@ -55,10 +58,8 @@ class Game: # Get terminal (screen) height and width self.screen_height, self.screen_width = self.stdscr.getmaxyx() - # Start Music - self.queue.put('music.start') - # Show selcome page + self.effect('start') self.load_welcome() # Ask for snake length @@ -67,11 +68,21 @@ class Game: # Ask for snake speed self.snake_speed = self.ask_question(f"Snake Speed (1 to 10, Default {self.snake_speed}): ", int, 7, 1, 10) + # Ask for Music + self.enable_music = (self.ask_question(f"Turn on Music (y/N): ", str, 'N')).upper() == 'Y' + + # Ask for Effects + self.enable_effects = (self.ask_question(f"Turn on Sound Effects (Y/n): ", str, 'Y')).upper() == 'Y' + # Debug Inputs #self.print_center(f"Length: {self.snake_length}, Speed: {self.snake_speed}, Width: {self.screen_width}, Height: {self.screen_height}") #time.sleep(2) - # Start Game!!! + # Start music + if self.enable_music: + self.queue.put('music.start') + + # Start game!!! self.start_game() @@ -181,19 +192,24 @@ class Game: #exit('yyyyy') #new_head = head - # Check collisions + # Check wall collisions if (new_head[0] in [0, self.screen_height - 1] or - new_head[1] in [0, self.screen_width - 1] or - new_head in snake): - # Crashed into something, show quit screen - self.queue.put('effect.wall') + new_head[1] in [0, self.screen_width - 1]): + # Crashed into wall, quit + self.effect('wall') + break + + # Check self collisions + if new_head in snake: + # Crashed into self, quit + self.effect('self') break snake.insert(0, new_head) # Check if snake ate food if snake[0] == food: - self.queue.put('effect.eat') + self.effect('eat') score += 1 food = get_new_food() else: @@ -219,6 +235,7 @@ class Game: self.refresh_screen() # Game Over screen + self.effect('over') self.print_center(f"GAME OVER! SCORE: {score}", 0, self.colors['red']) self.print_center(f"(R)estart, or (Q)uit", 2, self.colors['green']) self.stdscr.nodelay(0) @@ -241,7 +258,7 @@ class Game: self.refresh_screen() - def ask_question(self, question: str, value_type: Any, value_default: Any, value_min: int, value_max: int) -> Any: + def ask_question(self, question: str, value_type: Any, value_default: Any, value_min: int = 0, value_max: int = 0) -> Any: # Make the cursor visible curses.curs_set(1) @@ -291,6 +308,9 @@ class Game: self.print_center(f"ERROR: Must be an integer between {value_min} and {value_max}", 2, self.colors['red']) time.sleep(2) self.ask_question(question, value_type, value_min, value_max) + else: + if not answer: + answer = value_default return answer @@ -322,6 +342,9 @@ class Game: self.queue.put('quit') exit() + def effect(self, effect): + if self.enable_effects: + self.queue.put(effect) def main(stdscr, queue): @@ -335,8 +358,10 @@ def sound_process(sound_queue): effects = { 'eat': pygame.mixer.Sound('effects/bing.mp3'), - 'wall': pygame.mixer.Sound('effects/cut.mp3'), + 'wall': pygame.mixer.Sound('effects/wall.mp3'), 'self': pygame.mixer.Sound('effects/cut.mp3'), + 'over': pygame.mixer.Sound('effects/over.mp3'), + 'start': pygame.mixer.Sound('effects/start2.mp3'), } # Load sounds/music (adjust file paths as needed) @@ -348,33 +373,45 @@ def sound_process(sound_queue): while True: if not sound_queue.empty(): command = sound_queue.get() - if command == 'effect.eat': - effects['eat'].play() - if command == 'effect.wall': - effects['wall'].play() - if command == 'effect.self': - effects['self'].play() - elif command == 'music.start': + + # Play effects if requested + for effect, sound in effects.items(): + if command == effect: + sound.play() + + # Play music + if command == 'music.start': pygame.mixer.music.load(music_file) pygame.mixer.music.set_volume(0.25) pygame.mixer.music.play(-1) # Play music indefinitely elif command == 'music.stop': pygame.mixer.music.stop() + + # Quit came elif command == 'quit': break - time.sleep(0.1) # Small delay to prevent high CPU usage + + # Small delay to prevent high CPU usage + time.sleep(0.1) if __name__ == "__main__": - # Create a queue for communication - queue = multiprocessing.Queue() + try: - # Start the sound process - p = multiprocessing.Process(target=sound_process, args=(queue,)) - p.start() + # Handle CTRL+C (uvicore also handles with Aborted! but this catches other odd cases) + signal.signal(signal.SIGINT, lambda sig, frame: exit()) - # Start curses game loop - game = curses.wrapper(main, queue) + # Create a queue for communication + queue = multiprocessing.Queue() + + # Start the sound process + p = multiprocessing.Process(target=sound_process, args=(queue,)) + p.start() + + # Start curses game loop + game = curses.wrapper(main, queue) + except KeyboardInterrupt: + exit(0)