import random
import time
from collections import deque
from enum import Enum
from typing import Deque
from typing import Optional
try:
import curses
class Window:
def __init__(self) -> None:
self._window = curses.initscr()
self._window.keypad(True)
self._window.nodelay(True)
self._window.clear()
def refresh(self) -> None:
self._window.refresh()
def getch(self) -> int:
return self._window.getch()
def addstr(self, y: int, x: int, str: str, attr: int) -> None:
self._window.addstr(y, x, str, attr)
except ImportError:
try:
import unicurses as curses
class Window:
def __init__(self) -> None:
self._window = curses.initscr()
curses.keypad(self._window, True)
curses.nodelay(self._window, True)
curses.wclear(self._window)
def refresh(self) -> None:
curses.wrefresh(self._window)
def getch(self) -> int:
return curses.wgetch(self._window)
def addstr(self, y: int, x: int, str: str, attr: int) -> None:
curses.wbkgdset(self._window, attr)
curses.wmove(self._window, y, x)
curses.waddstr(self._window, str)
except ImportError:
print("\n setup unicurses : pip install Uni-Curses \n")
quit()
class Direction(Enum):
Left = 1
Up = 2
Down = 3
Right = 4
class Cell:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
class Navigator:
def get_direction(self) -> Optional[Direction]:
return None
class Console(Navigator):
def __init__(self) -> None:
self._window = Window()
curses.curs_set(0)
curses.noecho()
curses.cbreak()
curses.start_color()
def init(self, max_x: int, max_y: int, color: int) -> None:
self._max_x = max_x
self._max_y = max_y
self._color_map = [
[color for j in range(max_y + 1)] for i in range(max_x)]
for i in range(max_x):
for j in range(max_y):
self._refresh_cell(Cell(i, j))
def set_collor(self, cell: Cell, color: int) -> None:
self._color_map[cell.x][cell.y] = color
self._refresh_cell(cell)
def print_status(self, text: str) -> None:
color_pair = self._get_collor_pair(
curses.COLOR_WHITE, curses.COLOR_BLACK)
self._window.addstr(self._max_y // 2 + 1, 0, text, color_pair)
def refresh(self) -> None:
self._window.refresh()
def get_direction(self) -> Optional[Direction]:
c = self._window.getch()
if c == curses.ERR:
return None
if c == curses.KEY_UP:
return Direction.Up
if c == curses.KEY_DOWN:
return Direction.Down
if c == curses.KEY_LEFT:
return Direction.Left
if c == curses.KEY_RIGHT:
return Direction.Right
return None
def _get_collor_pair(self, foreground: int, background: int) -> int:
pair = foreground * 8 + background
curses.init_pair(pair, foreground, background)
return curses.color_pair(pair)
def _refresh_cell(self, cell: Cell) -> None:
foreground = self._color_map[cell.x][(cell.y // 2) * 2]
background = self._color_map[cell.x][(cell.y // 2) * 2 + 1]
color_pair = self._get_collor_pair(foreground, background)
self._window.addstr(cell.y // 2 + 1, cell.x, "▀", color_pair)
class CellType(Enum):
FREE = curses.COLOR_BLUE
WALL = curses.COLOR_BLACK
APPLE = curses.COLOR_RED
SNAKE = curses.COLOR_WHITE
class Field:
def __init__(self, max_x: int, max_y: int, console: Console) -> None:
self.max_x = max_x
self.max_y = max_y
self._console = console
self._console.init(max_x, max_y, CellType.FREE.value)
self._data = [[CellType.FREE for j in range(max_y)]
for i in range(max_x)]
def get_cell_type(self, cell: Cell) -> CellType:
if cell.x >= len(self._data) or cell.x < 0:
return CellType.WALL
if cell.y >= len(self._data[cell.x]) or cell.y < 0:
return CellType.WALL
return self._data[cell.x][cell.y]
def set_cell_type(self, cell: Cell, type: CellType) -> None:
self._data[cell.x][cell.y] = type
self._console.set_collor(cell, type.value)
def generate_apple(self) -> None:
x = random.randint(0, len(self._data) - 1)
y = random.randint(0, len(self._data[x]) - 1)
self.set_cell_type(Cell(x, y), CellType.APPLE)
class Snake:
def __init__(self, cell: Cell, direction: Direction, field: Field):
self._direction = direction
self._field = field
self._body: Deque[Cell] = deque()
self._occupate_cell(cell)
def change_direction(self, new_direction: Direction) -> bool:
self._direction = new_direction
return True
def move(self) -> bool:
new_head = self._get_next_cell()
cell_type = self._field.get_cell_type(new_head)
if cell_type == CellType.WALL:
return False
if cell_type == CellType.SNAKE:
return False
if cell_type == CellType.APPLE:
self._occupate_cell(new_head)
self._field.generate_apple()
return True
self._occupate_cell(new_head)
self._release_last_cell()
return True
def head(self) -> Cell:
return self._body[-1]
def len(self) -> int:
return len(self._body)
def _occupate_cell(self, cell: Cell) -> None:
self._body.append(cell)
self._field.set_cell_type(cell, CellType.SNAKE)
def _release_last_cell(self) -> None:
tail = self._body.popleft()
self._field.set_cell_type(tail, CellType.FREE)
def _get_next_cell(self) -> Cell:
head = self.head()
if self._direction == Direction.Up:
return Cell(head.x, head.y - 1)
if self._direction == Direction.Down:
return Cell(head.x, head.y + 1)
if self._direction == Direction.Left:
return Cell(head.x - 1, head.y)
if self._direction == Direction.Right:
return Cell(head.x + 1, head.y)
class Autopilot(Navigator):
def __init__(self, field: Field, snake: Snake):
self._snake = snake
self._field = field
def get_direction(self) -> Direction:
head = self._snake.head()
if head.y == 0:
if head.x % 2 == 0:
return Direction.Right
else:
return Direction.Down
elif head.y < self._field.max_y - 2:
if head.x % 2 == 0:
return Direction.Up
else:
return Direction.Down
elif head.y == self._field.max_y - 2:
if head.x == self._field.max_x - 1:
return Direction.Down
elif head.x % 2 == 0:
return Direction.Up
else:
return Direction.Right
else:
if head.x == 0:
return Direction.Up
else:
return Direction.Left
MAX_X = 10
MAX_Y = 10
AUTOPILOT = True
FRAME_DUARATION = 0.3 if not AUTOPILOT else 0.03
console = Console()
field = Field(MAX_X, MAX_Y, console)
field.generate_apple()
snake = Snake(Cell(MAX_X // 2, MAX_Y // 2), Direction.Right, field)
navigator: Navigator = Autopilot(field, snake) if AUTOPILOT else console
while snake.move():
time.sleep(FRAME_DUARATION)
direction = navigator.get_direction()
if direction:
snake.change_direction(direction)
console.print_status("len : {}".format(snake.len()))
console.refresh()
print("\n Game over... \n")