Extended the code using Copilot with tons of cool features. Read the CHAT.md for what I asked and what it did

This commit is contained in:
2026-04-12 18:29:03 -06:00
parent b61b35dfc9
commit d098618b9a
9 changed files with 1931 additions and 301 deletions
+428 -301
View File
@@ -1,85 +1,129 @@
"""Snake: A CLI-based Snake game using curses."""
import curses
import random
import time
from typing import Any
import pygame
import multiprocessing
import signal
import argparse
import sys
from typing import Tuple, List, Optional
from multiprocessing import Process, Queue
from constants import (
DEFAULT_SNAKE_LENGTH, DEFAULT_SNAKE_SPEED, MIN_SNAKE_SPEED, MAX_SNAKE_SPEED,
HEAD_CHAR, BODY_CHAR, FOOD_CHAR, MIN_SCREEN_HEIGHT, MIN_SCREEN_WIDTH,
SPEED_TO_MS, SCREEN_PADDING, MIN_SNAKE_LENGTH
)
from theme import ThemeManager
from sound_manager import SoundManager
from high_score import HighScoreManager
from game_config import GameConfig
class Game:
"""Main game class."""
def __init__(self, stdscr, queue):
# Get curses standard screen
def __init__(self, stdscr, queue: Queue, args: argparse.Namespace):
self.stdscr = stdscr
# Multiprocessing Queue
self.queue = queue
self.args = args
# Defaults
self.snake_length = 3
self.snake_speed = 7
self.enable_effects = True
self.enable_music = False
# Load configuration
self.config = GameConfig()
self.high_score_manager = HighScoreManager()
# Theme
self.theme = ThemeManager()
self.theme.initialize(stdscr)
# Game state
self.snake_length = self.config.get('snake_length', DEFAULT_SNAKE_LENGTH)
self.snake_speed = self.config.get('snake_speed', DEFAULT_SNAKE_SPEED)
self.enable_effects = args.no_effects is False and self.config.get('enable_effects', True)
self.enable_music = args.music and self.config.get('enable_music', False)
self.music_started_once = False
# Snake and food characters
# ASCII Table: https://theasciicode.com.ar/
self.head_char = ''
#head_char = '■'
#head_char = '0'
#head_char_up = '▄'
#head_char_down = '▀'
self.body_char = ''
#body_char = 'Φ'
#body_char = '■'
#body_char = 'O'
#body_char = '░'
#body_char = '■'
#food_char = '💗'
self.food_char = ''
#food_char = '▓'
#food_char = '▒'
# Game statistics
self.game_start_time = 0.0
# Character customization
self.head_char = self.config.get('head_char', HEAD_CHAR)
self.body_char = self.config.get('body_char', BODY_CHAR)
self.food_char = self.config.get('food_char', FOOD_CHAR)
def load(self):
# Screen dimensions
self.screen_height = 0
self.screen_width = 0
# Setup colors
curses.start_color()
self.colors = {
'green': 1,
'red': 2,
'white': 3
}
curses.init_pair(self.colors['green'], curses.COLOR_GREEN, curses.COLOR_BLACK) # Snake
curses.init_pair(self.colors['red'], curses.COLOR_RED, curses.COLOR_BLACK) # Food
curses.init_pair(self.colors['white'], curses.COLOR_WHITE, curses.COLOR_BLACK) # Score/Text
self.stdscr.bkgd(' ', curses.color_pair(self.colors['white']))
# Player name
self.player_name = ""
def load(self) -> None:
"""Load game and show setup menu."""
curses.set_escdelay(1)
# Get terminal (screen) height and width
self.screen_height, self.screen_width = self.stdscr.getmaxyx()
# Show selcome page
# Show welcome page
self.effect('start')
self.load_welcome()
# Ask for player name
self.player_name = self.ask_question(
"Enter your name: ",
str,
"Player"
)
# Show high score for this player if exists
stats = self.high_score_manager.get_player_stats(self.player_name)
if stats['total_games'] > 0:
self.print_center(
f"High Score: {stats['high_score']} | Games: {stats['total_games']}",
1,
self.theme.get_color_pair('text')
)
time.sleep(1)
# Ask for snake length
self.snake_length = self.ask_question(f"Snake Length (1 to {self.screen_width - 5}, Default {self.snake_length}): ", int, 3, 3, self.screen_width - 5)
self.snake_length = self.ask_question(
f"Snake Length (1 to {self.screen_width - 2*SCREEN_PADDING}, Default {self.snake_length}): ",
int,
self.snake_length,
MIN_SNAKE_LENGTH,
self.screen_width - 2*SCREEN_PADDING
)
# Ask for snake speed
self.snake_speed = self.ask_question(f"Snake Speed (1 to 10, Default {self.snake_speed}): ", int, 7, 1, 10)
self.snake_speed = self.ask_question(
f"Snake Speed (1 to 10, Default {self.snake_speed}): ",
int,
self.snake_speed,
MIN_SNAKE_SPEED,
MAX_SNAKE_SPEED
)
# Ask for Music
self.enable_music = (self.ask_question(f"Turn on Music (y/N): ", str, 'N')).upper() == 'Y'
# Ask for Music (only if not disabled via CLI)
if not args.no_music:
enable_music = (self.ask_question(
f"Turn on Music (Y/n): ",
str,
'Y'
)).upper() == 'Y'
self.enable_music = enable_music
self.config.set('enable_music', enable_music)
# Ask for Effects
self.enable_effects = (self.ask_question(f"Turn on Sound Effects (Y/n): ", str, 'Y')).upper() == 'Y'
# Ask for Effects (only if not disabled via CLI)
if not args.no_effects:
enable_effects = (self.ask_question(
f"Turn on Sound Effects (Y/n): ",
str,
'Y'
)).upper() == 'Y'
self.enable_effects = enable_effects
self.config.set('enable_effects', enable_effects)
# 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)
# Save preferences
self.config.set('snake_length', self.snake_length)
self.config.set('snake_speed', self.snake_speed)
# Start music
if self.enable_music:
@@ -90,357 +134,440 @@ class Game:
else:
self.music_pause()
# Start game!!!
# Start game
self.start_game()
def start_game(self):
# Hide cursor
def start_game(self) -> None:
"""Main game loop."""
self.hide_cursor()
# Non-blocking getch (get character)
self.stdscr.nodelay(1)
# Calculate snake speed
wait_for_input_ms = {
1: 250, 2: 225, 3: 200,
4: 175, 5: 150, 6: 125,
7: 100, 8: 75, 9: 50, 10: 25
}
self.stdscr.timeout(wait_for_input_ms[self.snake_speed])
# Ensure window is large enough
if self.screen_height < 10 or self.screen_width < 20:
self.print_center("Terminal window is too small!")
# Validate screen size
if self.screen_height < MIN_SCREEN_HEIGHT or self.screen_width < MIN_SCREEN_WIDTH:
self.print_center("Terminal window is too small!", 0, self.theme.get_color_pair('food'))
time.sleep(2)
exit()
return
# Start the head at x=self.snake_length so the tail ends at x=1
snake = [[self.screen_height // 2, self.snake_length - i] for i in range(self.snake_length)]
# Set timeout based on snake speed
self.stdscr.timeout(SPEED_TO_MS[self.snake_speed])
# Initial food
def get_new_food():
# Initialize snake at center
snake: List[List[int]] = [
[self.screen_height // 2, self.snake_length - i]
for i in range(self.snake_length)
]
# Get food
def get_new_food() -> List[int]:
while True:
nf = [random.randint(1, self.screen_height - 2), random.randint(1, self.screen_width - 2)]
nf = [
random.randint(1, self.screen_height - 2),
random.randint(1, self.screen_width - 2)
]
if nf not in snake:
return nf
food = get_new_food()
# Initial direction
# Game state
key = curses.KEY_RIGHT
score = self.snake_length
self.game_start_time = time.time()
# Main game loop
while True:
next_key = self.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:
# DOWN
if next_key == curses.KEY_DOWN and key != curses.KEY_UP:
key = next_key
# UP
elif next_key == curses.KEY_UP and key != curses.KEY_DOWN:
key = next_key
# LEFT
elif next_key == curses.KEY_LEFT and key != curses.KEY_RIGHT:
key = next_key
# RIGHT
elif next_key == curses.KEY_RIGHT and key != curses.KEY_LEFT:
key = next_key
# ESCAPE Menu
elif next_key == 27:
previous_key = key
if self.enable_music: self.music_pause()
self.print_center(f"GAME PAUSED...", 0, self.colors['red'])
self.print_center(f"ESC Resume, (R)estart, (Q)uit, (M)enu", 2, self.colors['green'])
while True:
key = self.stdscr.getch()
if key == 27:
break
elif key == ord('r'):
if self.enable_music: self.music_unpause()
self.start_game()
return
elif key == ord('q'):
self.exit_game()
elif key == ord('m'):
self.load()
return
# Resume
key = previous_key
if self.enable_music: self.music_unpause()
continue
# (R)estart
elif next_key == ord('r'):
self.start_game()
return
# # (Q)uit
# elif next_key == ord('q'):
# break
# Calculate new head
# Handle input
if next_key != -1:
key = self._handle_input(key, next_key)
if key is None: # Quit from menu
return
# Calculate new head position
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:
new_head = [head[0], head[1]]
pass
# Should not happen
#exit('yyyyy')
#new_head = head
new_head = self._calculate_new_head(head, key)
# Check wall collisions
if (new_head[0] in [0, self.screen_height - 1] or
new_head[1] in [0, self.screen_width - 1]):
# Crashed into wall, quit
if self._check_wall_collision(new_head):
self.effect('wall')
break
# Check self collisions
if new_head in snake:
# Crashed into self, quit
self.effect('self')
break
# Add new head
snake.insert(0, new_head)
# Check if snake ate food
# Check if food eaten
if snake[0] == food:
self.effect('eat')
score += 1
food = get_new_food()
else:
snake.pop() # Remove tail
snake.pop()
# Draw everything
self.clear_screen()
self.stdscr.attron(curses.color_pair(3))
self.stdscr.border(0)
self.stdscr.attroff(curses.color_pair(3))
# Render frame
self._render_frame(snake, food, score)
# Draw snake
for i, (y, x) in enumerate(snake):
char = self.head_char if i == 0 else self.body_char
self.stdscr.addch(y, x, char, curses.color_pair(1))
# Game over
self._handle_game_over(score, self.snake_length)
# Draw food
self.stdscr.addch(food[0], food[1], self.food_char, curses.color_pair(2))
def _handle_input(self, current_key: int, next_key: int) -> Optional[int]:
"""Handle player input and return new direction, or None if quitting."""
# Arrow key handling
if next_key == curses.KEY_DOWN and current_key != curses.KEY_UP:
return next_key
elif next_key == curses.KEY_UP and current_key != curses.KEY_DOWN:
return next_key
elif next_key == curses.KEY_LEFT and current_key != curses.KEY_RIGHT:
return next_key
elif next_key == curses.KEY_RIGHT and current_key != curses.KEY_LEFT:
return next_key
elif next_key == 27: # ESC - pause menu
return self._handle_pause_menu(current_key)
elif next_key == ord('r'): # Restart
self.start_game()
return None
# Draw score
self.stdscr.addstr(0, 2, f' Length: {score} @ {self.snake_speed}MPH ({self.screen_width}x{self.screen_height}) ', curses.color_pair(3))
return current_key
self.refresh_screen()
def _handle_pause_menu(self, previous_key: int) -> Optional[int]:
"""Handle pause menu. Returns new key or None if quitting."""
self._apply_music_state(paused=True)
# Game Over screen
duration = int(time.time() - self.game_start_time)
menu_text = f"GAME PAUSED (elapsed: {duration}s)"
music_status = "ON" if self.enable_music else "OFF"
effects_status = "ON" if self.enable_effects else "OFF"
options = f"ESC Resume | R Restart | M Menu | M(U)sic {music_status} | (E)ffects {effects_status} | Q Quit"
self.print_center(menu_text, 0, self.theme.get_color_pair('food'))
self.print_center(options, 2, self.theme.get_color_pair('snake'))
while True:
key = self.stdscr.getch()
if key == 27: # ESC - resume
self._apply_music_state(paused=False)
return previous_key
elif key == ord('r'): # Restart
self._apply_music_state(paused=False)
self.start_game()
return None
elif key == ord('m'): # Menu
self.load()
return None
elif key == ord('u'): # Toggle music
self.enable_music = not self.enable_music
self.config.set('enable_music', self.enable_music)
self._apply_music_state(paused=True)
music_status = "ON" if self.enable_music else "OFF"
effects_status = "ON" if self.enable_effects else "OFF"
options = f"ESC Resume | R Restart | M Menu | M(U)sic {music_status} | (E)ffects {effects_status} | Q Quit"
self.print_center(menu_text, 0, self.theme.get_color_pair('food'))
self.print_center(options, 2, self.theme.get_color_pair('snake'))
elif key == ord('e'): # Toggle effects
self.enable_effects = not self.enable_effects
self.config.set('enable_effects', self.enable_effects)
music_status = "ON" if self.enable_music else "OFF"
effects_status = "ON" if self.enable_effects else "OFF"
options = f"ESC Resume | R Restart | M Menu | M(U)sic {music_status} | (E)ffects {effects_status} | Q Quit"
self.print_center(menu_text, 0, self.theme.get_color_pair('food'))
self.print_center(options, 2, self.theme.get_color_pair('snake'))
elif key == ord('q'): # Quit
self.exit_game()
def _calculate_new_head(self, head: List[int], key: int) -> List[int]:
"""Calculate new head position based on direction."""
y, x = head
if key == curses.KEY_DOWN:
return [y + 1, x]
elif key == curses.KEY_UP:
return [y - 1, x]
elif key == curses.KEY_LEFT:
return [y, x - 1]
elif key == curses.KEY_RIGHT:
return [y, x + 1]
return head
def _check_wall_collision(self, head: List[int]) -> bool:
"""Check if head hit a wall."""
return (head[0] in [0, self.screen_height - 1] or
head[1] in [0, self.screen_width - 1])
def _render_frame(self, snake: List[List[int]], food: List[int], score: int) -> None:
"""Render the game frame."""
self.clear_screen()
# Draw border
self.stdscr.attron(curses.color_pair(self.theme.get_color_pair('text')))
self.stdscr.border(0)
self.stdscr.attroff(curses.color_pair(self.theme.get_color_pair('text')))
# Draw snake
for i, (y, x) in enumerate(snake):
char = self.head_char if i == 0 else self.body_char
self.stdscr.addch(y, x, char, curses.color_pair(self.theme.get_color_pair('snake')))
# Draw food
self.stdscr.addch(
food[0], food[1], self.food_char,
curses.color_pair(self.theme.get_color_pair('food'))
)
# Draw score
elapsed = int(time.time() - self.game_start_time)
score_text = f' {self.player_name} | Length: {score} @ {self.snake_speed}MPH {elapsed}s '
self.stdscr.addstr(
0, 2, score_text,
curses.color_pair(self.theme.get_color_pair('text'))
)
self.refresh_screen()
def _handle_game_over(self, score: int, initial_length: int) -> None:
"""Handle game over screen."""
self.effect('over')
self.print_center(f"GAME OVER! SCORE: {score}", 0, self.colors['red'])
self.print_center(f"(R)estart, (Q)uit, (M)enu", 2, self.colors['green'])
# Save game
self.high_score_manager.save_game(self.player_name, score, initial_length, self.snake_speed)
stats = self.high_score_manager.get_player_stats(self.player_name)
elapsed = int(time.time() - self.game_start_time)
self.print_center(
f"{self.player_name} - GAME OVER! SCORE: {score} (Time: {elapsed}s)",
-1,
self.theme.get_color_pair('food')
)
self.print_center(
f"Best: {stats['high_score']} | Games: {stats['total_games']}",
0,
self.theme.get_color_pair('text')
)
self.print_center(
"(R)estart | (M)enu | (Q)uit",
2,
self.theme.get_color_pair('snake')
)
self.stdscr.nodelay(0)
#if self.enable_music: self.music_pause()
while True:
key = self.stdscr.getch()
if key == ord('r'):
#if self.enable_music: self.music_unpause()
self.start_game()
return
elif key == ord('q'):
# Game over, exit app!
self.exit_game()
elif key == ord('m'):
self.load()
return
def print_center(self, value, y_offset = 0, color = 3):
# Calculate center position
def print_center(
self,
value: str,
y_offset: int = 0,
color: int = 3
) -> None:
"""Print text centered on screen."""
y = self.screen_height // 2
x = (self.screen_width - len(value)) // 2
if x < 0: x = 1
if x < 0:
x = 1
self.stdscr.addstr(int(y + y_offset), int(x), value, curses.color_pair(color))
self.refresh_screen()
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
def ask_question(
self,
question: str,
value_type: type,
value_default,
value_min: int = 0,
value_max: int = 0
):
"""Ask user a question and validate the response."""
curses.curs_set(1)
# Calculate center position of prompt
y = self.screen_height // 2
x = (self.screen_width - len(question)) // 2
if x < 0: x = 1
if x < 0:
x = 1
# Display question and wait for input + ENTER key to finish
self.clear_screen()
self.stdscr.addstr(y, x, question)
self.refresh_screen()
curses.noecho()
curses.cbreak()
# Wait for answer with ENTER key to end input
answer = ""
while True:
key = self.stdscr.getch()
# Check if the Enter key (ASCII 10 or 13) was pressed
if key == curses.KEY_ENTER or key in [10, 13]:
break
# Handle backspace
elif key == curses.KEY_BACKSPACE or key == 127:
if len(answer) > 0:
answer = answer[:-1]
# Erase the character from the screen
self.stdscr.addch('\b')
self.stdscr.addch(' ')
self.stdscr.addch('\b')
# Handle regular characters (check for printable ASCII range if needed)
elif 32 <= key <= 126:
answer += chr(key)
self.stdscr.addch(key) # Manually echo the character
self.clear_screen()
self.stdscr.addstr(y, x, question)
self.refresh_screen()
# Validate input
if value_type == int:
try:
answer = int(answer)
except Exception as e:
answer = value_default
if answer < value_min or answer > value_max:
# Invalid integer, ask again
self.print_center(f"ERROR: Must be an integer between {value_min} and {value_max}", 2, self.colors['red'])
curses.noecho()
curses.cbreak()
answer = ""
while True:
key = self.stdscr.getch()
if key == curses.KEY_ENTER or key in [10, 13]:
break
elif key == curses.KEY_BACKSPACE or key == 127:
if len(answer) > 0:
answer = answer[:-1]
self.stdscr.addch(ord('\b'))
self.stdscr.addch(ord(' '))
self.stdscr.addch(ord('\b'))
elif 32 <= key <= 126:
answer += chr(key)
self.stdscr.addch(key)
self.refresh_screen()
# Validate input
if value_type == int:
if not answer: # Empty answer, use default
curses.curs_set(0)
return value_default
try:
answer = int(answer)
if value_min <= answer <= value_max:
curses.curs_set(0)
return answer
except ValueError:
pass
# Invalid input, show error and retry
self.print_center(
f"ERROR: Must be {value_min}-{value_max}",
1,
self.theme.get_color_pair('food')
)
time.sleep(2)
self.ask_question(question, value_type, value_min, value_max)
else:
if not answer:
answer = value_default
else:
# String input
if not answer:
answer = value_default
curses.curs_set(0)
return answer
return answer
def load_welcome(self):
def load_welcome(self) -> None:
"""Show welcome screen."""
self.hide_cursor()
self.clear_screen()
self.print_center("█▓▒░ Welcome to Curses Python!!! ░▒▓█", 0, self.colors['red'])
self.print_center("by mReschke Productions", 2, self.colors['green'])
self.print_center("█▓▒░ Welcome to Snake CLI! ░▒▓█", 0, self.theme.get_color_pair('food'))
self.print_center("by mReschke Productions", 2, self.theme.get_color_pair('snake'))
time.sleep(2)
def hide_cursor(self):
def hide_cursor(self) -> None:
"""Hide the cursor."""
curses.curs_set(0)
def show_cursor(self):
def show_cursor(self) -> None:
"""Show the cursor."""
curses.curs_set(1)
def clear_screen(self):
def clear_screen(self) -> None:
"""Clear the screen."""
self.stdscr.clear()
def refresh_screen(self):
def refresh_screen(self) -> None:
"""Refresh the screen."""
self.stdscr.refresh()
def exit_game(self):
def exit_game(self) -> None:
"""Exit the game."""
self.queue.put('quit')
exit()
exit(0)
def effect(self, effect):
def effect(self, effect: str) -> None:
"""Play a sound effect."""
if self.enable_effects:
self.queue.put(effect)
def music_start(self):
def _apply_music_state(self, paused: bool = False) -> None:
"""Apply current music setting and requested pause state."""
if not self.enable_music:
self.music_pause()
return
if not self.music_started_once:
self.music_start()
if paused:
self.music_pause()
return
if paused:
self.music_pause()
else:
self.music_unpause()
def music_start(self) -> None:
"""Start background music."""
self.music_started_once = True
self.queue.put('music.start')
def music_stop(self):
self.queue.put('music.stop')
def music_pause(self):
def music_pause(self) -> None:
"""Pause music."""
self.queue.put('music.pause')
def music_unpause(self):
def music_unpause(self) -> None:
"""Resume music."""
self.queue.put('music.unpause')
def main(stdscr, queue):
game = Game(stdscr, queue)
game.load()
def sound_process(sound_queue):
# Initialize pygame mixer in the new process
pygame.mixer.init()
effects = {
'eat': pygame.mixer.Sound('effects/bing.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)
# Use .wav or .ogg for better compatibility
#effect = pygame.mixer.Sound('effects/bing.mp3')
#music_file = 'music/QonsVivaEveryone.mp3'
#music_file = 'music/Aztec.mp3'
#music_file = 'music/Snug_Neon_Artery.mp3'
music_file = 'music/bubbles.ogg'
#music_file = 'music/s.mp3'
while True:
if not sound_queue.empty():
command = sound_queue.get()
# 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.5)
pygame.mixer.music.play(-1) # Play music indefinitely
elif command == 'music.stop':
pygame.mixer.music.stop()
elif command == 'music.pause':
pygame.mixer.music.pause();
elif command == 'music.unpause':
pygame.mixer.music.unpause();
# Quit came
elif command == 'quit':
break
# Small delay to prevent high CPU usage
time.sleep(0.1)
if __name__ == "__main__":
def main(stdscr, queue: Queue, args: argparse.Namespace) -> None:
"""Main entry point."""
try:
# Handle CTRL+C (uvicore also handles with Aborted! but this catches other odd cases)
signal.signal(signal.SIGINT, lambda sig, frame: exit())
# 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)
game = Game(stdscr, queue, args)
game.load()
except KeyboardInterrupt:
exit(0)
def parse_args() -> argparse.Namespace:
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description='Snake CLI Game')
parser.add_argument(
'--music',
action='store_true',
help='Enable background music'
)
parser.add_argument(
'--no-effects',
action='store_true',
help='Disable sound effects'
)
parser.add_argument(
'--no-music',
action='store_true',
help='Disable music option in menu'
)
return parser.parse_args()
if __name__ == "__main__":
try:
signal.signal(signal.SIGINT, lambda sig, frame: exit(0))
args = parse_args()
queue = Queue()
# Start sound process
sound_manager = SoundManager()
p = Process(target=sound_manager.process_queue, args=(queue,))
p.daemon = True
p.start()
# Start game
curses.wrapper(main, queue, args)
# Cleanup
queue.put('quit')
p.join(timeout=2)
if p.is_alive():
p.terminate()
except KeyboardInterrupt:
exit(0)