Untitled

mail@pastecode.io avatar
unknown
python
a year ago
11 kB
2
Indexable
Never
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
    POISON = curses.COLOR_GREEN
    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)]
        self._free_cells = list()
        for x in range(max_x):
            for y in range(max_y):
                self._free_cells.append(Cell(x, y))

    def get_cell_type(self, cell: Cell) -> CellType:
        # if cell.x >= len(self._data) or cell.x < 0:
        #     cell.x = cell.x % len(self._data)
        #     return CellType.WALL
        # if cell.y >= len(self._data[cell.x]) or cell.y < 0:
        #     cell.y = cell.y % len(self._data[cell.x])
        #     return CellType.WALL
        return self._data[cell.x][cell.y]

    def set_cell_type(self, cell: Cell, type: CellType) -> None:
        if type == CellType.SNAKE:
            for i in range(len(self._free_cells)-1, -1, -1):
                if self._free_cells[i].x == cell.x and self._free_cells[i].y == cell.y:
                    self._free_cells.pop(i)
        else:
            self._free_cells.append(cell)

        self._data[cell.x][cell.y] = type
        self._console.set_collor(cell, type.value)

    def set_random_cell_type(self, cell_type) -> None:
        # cell_type is not FREE
        # x = random.randint(0, len(self._data) - 1)
        # y = random.randint(0, len(self._data[x]) - 1)
        cell = random.choice(self._free_cells)
        self.set_cell_type(cell, cell_type)

    def generate_apple(self) -> None:
        self.set_random_cell_type(CellType.APPLE)

    def generate_poison(self) -> None:
        self.set_random_cell_type(CellType.POISON)


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)
        self._apples_eaten = 0
        self._poisons_eaten = 0

    def apples_eaten(self):
        return self._apples_eaten

    def poisons_eaten(self):
        return self._poisons_eaten

    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._apples_eaten += 1
            self._occupate_cell(new_head)

        if cell_type == CellType.POISON:
            self._poisons_eaten += 1
            self._field.set_cell_type(new_head, CellType.FREE)
            self._deoccupate_tail()

        if self.len() >= self._field.max_x * self._field.max_y:
            print("Victory!")
            exit(1)

        if cell_type == CellType.APPLE or cell_type == CellType.POISON:
            if random.randint(0, 2) == 5:
                self._field.generate_poison()
            else:
                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 _deoccupate_tail(self) -> None:
        if len(self._body) <= 1:
            exit(1)
        deleted_cell = self._body.popleft()
        self._field.set_cell_type(deleted_cell, CellType.FREE)

    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()
        cell = None
        if self._direction == Direction.Up:
            cell = Cell(head.x, head.y - 1)
        if self._direction == Direction.Down:
            cell = Cell(head.x, head.y + 1)
        if self._direction == Direction.Left:
            cell = Cell(head.x - 1, head.y)
        if self._direction == Direction.Right:
            cell = Cell(head.x + 1, head.y)
        if cell.x >= self._field.max_x or cell.x < 0:
            cell.x = cell.x % self._field.max_x
            # return CellType.WALL
        if cell.y >= self._field.max_y or cell.y < 0:
            cell.y = cell.y % self._field.max_y
            # return CellType.WALL
        return cell


class Autopilot(Navigator):
    def __init__(self, field: Field, snake: Snake):
        self._snake = snake
        self._field = field
        self._direction = Direction.Right
        self._left_len = 1

    def get_direction(self) -> Direction:
        # if self._left_len > 0:
        #     self._left_len -= 1
        #     return self._direction
        # else:
        #     # last_direction = self._direction
        #     opposite_dirs = {
        #         Direction.Up: Direction.Down,
        #         Direction.Down: Direction.Up,
        #         Direction.Left: Direction.Right,
        #         Direction.Right: Direction.Left
        #     }
        #     choices = [Direction.Up, Direction.Down, Direction.Left, Direction.Right]
        #     choices.remove(opposite_dirs[self._direction])
        #     self._direction = random.choice(choices)
        #     self._left_len = random.randint(2, 5)
        #     return 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 : {}, apples : {}, poisons : {}".format(
        snake.len(), snake.apples_eaten(), snake.poisons_eaten()))
    console.refresh()

print("\n Game over... \n")