Created
February 5, 2020 09:14
-
-
Save MircoT/96998a30613aa32bd17ba857a031dd96 to your computer and use it in GitHub Desktop.
Snake game with Python and 2 different libraries (raylib and termbox)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from random import randint | |
| class Direction: | |
| """The possible directions of the snake parts.""" | |
| LEFT = 0 | |
| RIGHT = 1 | |
| UP = 2 | |
| DOWN = 3 | |
| class State: | |
| """All game states.""" | |
| ALIVE = 0 | |
| GAME_OVER = 1 | |
| class Component: | |
| """A basic game component.""" | |
| def __init__(self, x, y): | |
| self._x = x | |
| self._y = y | |
| @property | |
| def x(self): | |
| """Get x coordinate.""" | |
| return self._x | |
| @property | |
| def y(self): | |
| """Get y coordinate.""" | |
| return self._y | |
| def move(self, dx, dy): | |
| """Move the component.""" | |
| self._x += dx | |
| self._y += dy | |
| def drop(self, x, y): | |
| """Put the component in a new position.""" | |
| self._x = x | |
| self._y = y | |
| class SnakePart(Component): | |
| """A part of the snake.""" | |
| def __init__(self, x, y, direction): | |
| super().__init__(x, y) | |
| self.dir = direction | |
| class Food(Component): | |
| """Food component.""" | |
| def __init__(self, x, y): | |
| super().__init__(x, y) | |
| class SnakeGame: | |
| """The snake game.""" | |
| def __init__(self, width, height): | |
| """Initialize the game stuff.""" | |
| self._parts = [] | |
| self._width = width - 2 | |
| self._height = height - 2 | |
| self._state = State.GAME_OVER | |
| self._food = Food(randint(0, self._width - 2), | |
| randint(0, self._height - 2)) | |
| self.reset() | |
| def reset(self): | |
| """Reset the game to the initial state.""" | |
| self._parts = [ | |
| SnakePart(int(self._width / 2), | |
| int(self._height / 2), Direction.RIGHT), | |
| SnakePart(int(self._width / 2) - 1, | |
| int(self._height / 2), Direction.RIGHT), | |
| SnakePart(int(self._width / 2) - 2, | |
| int(self._height / 2), Direction.RIGHT), | |
| SnakePart(int(self._width / 2) - 3, | |
| int(self._height / 2), Direction.RIGHT), | |
| ] | |
| self._state = State.ALIVE | |
| self._food.drop( | |
| randint(0, self._width - 2), | |
| randint(0, self._height - 2) | |
| ) | |
| def _update_part_dirs(self): | |
| """Update the directions of the snake parts.""" | |
| for idx in reversed(range(len(self._parts) - 1)): | |
| self._parts[idx+1].dir = self._parts[idx].dir | |
| def _update_part_positions(self): | |
| """Update the positions of the snake parts.""" | |
| for part in self._parts: | |
| if part.dir == Direction.RIGHT: | |
| part.move(1, 0) | |
| elif part.dir == Direction.LEFT: | |
| part.move(-1, 0) | |
| elif part.dir == Direction.UP: | |
| part.move(0, -1) | |
| elif part.dir == Direction.DOWN: | |
| part.move(0, 1) | |
| def _check_eat_food(self): | |
| """Check if the snake has eaten the food. | |
| NOTE: This method reallocate the food when it's eaten | |
| """ | |
| head = self._head | |
| tail = self._tail | |
| if head.x == self._food.x and head.y == self._food.y: | |
| if tail.dir == Direction.RIGHT: | |
| self._parts.append(SnakePart(tail.x-1, tail.y, tail.dir)) | |
| elif tail.dir == Direction.LEFT: | |
| self._parts.append(SnakePart(tail.x+1, tail.y, tail.dir)) | |
| elif tail.dir == Direction.UP: | |
| self._parts.append(SnakePart(tail.x, tail.y+1, tail.dir)) | |
| elif tail.dir == Direction.DOWN: | |
| self._parts.append(SnakePart(tail.x, tail.y-1, tail.dir)) | |
| self._food.drop( | |
| randint(0, self._width - 2), | |
| randint(0, self._height - 2) | |
| ) | |
| def _check_game_over(self): | |
| """Check if the game comes to end.""" | |
| head = self._head | |
| if head.x >= self._width or head.x < 0: | |
| self._state = State.GAME_OVER | |
| if head.y >= self._height or head.y < 0: | |
| self._state = State.GAME_OVER | |
| for part in self._parts[1:]: | |
| if head.x == part.x and head.y == part.y: | |
| self._state = State.GAME_OVER | |
| def update(self): | |
| """Update the game logic.""" | |
| if self._state == State.ALIVE: | |
| self._update_part_positions() | |
| self._check_eat_food() | |
| self._check_game_over() | |
| self._update_part_dirs() | |
| def change_head_dir(self, direction): | |
| """Change the direction of the snake's head.""" | |
| head = self._head | |
| if head.dir == Direction.RIGHT and direction == Direction.LEFT: | |
| return | |
| elif head.dir == Direction.LEFT and direction == Direction.RIGHT: | |
| return | |
| elif head.dir == Direction.UP and direction == Direction.DOWN: | |
| return | |
| elif head.dir == Direction.DOWN and direction == Direction.UP: | |
| return | |
| head.dir = direction | |
| @property | |
| def state(self): | |
| """Get the game state.""" | |
| return self._state | |
| @property | |
| def _head(self) -> 'SnakePart': | |
| """Get the head of the snake.""" | |
| return self._parts[0] | |
| @property | |
| def _tail(self) -> 'SnakePart': | |
| """Get the tail of the snake.""" | |
| return self._parts[-1] | |
| @property | |
| def snake_parts(self) -> list: | |
| """Get all the snake parts.""" | |
| return self._parts | |
| @property | |
| def food(self) -> 'Food': | |
| """Get the food component.""" | |
| return self._food |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import raylibpy as ray | |
| from snake import Direction, SnakeGame, State | |
| _CELL_SIZE = 24 | |
| def draw_borders(width, height): | |
| """Render game borders.""" | |
| ray.draw_rectangle(0, 0, width, _CELL_SIZE, ray.GRAY) | |
| ray.draw_rectangle(0, height - _CELL_SIZE, width, _CELL_SIZE, ray.GRAY) | |
| ray.draw_rectangle(0, 0, _CELL_SIZE, height, ray.GRAY) | |
| ray.draw_rectangle(width - _CELL_SIZE, 0, _CELL_SIZE, height, ray.GRAY) | |
| def draw_snake(parts, state): | |
| """Render the snake.""" | |
| color = ray.YELLOW if state == State.ALIVE else ray.RED | |
| for idx, part in enumerate(parts): | |
| ray.draw_rectangle( | |
| part.x * _CELL_SIZE + _CELL_SIZE, | |
| part.y * _CELL_SIZE + _CELL_SIZE, | |
| _CELL_SIZE, _CELL_SIZE, | |
| color if idx != 0 else ray.ORANGE | |
| ) | |
| def draw_food(food): | |
| """Render the food.""" | |
| ray.draw_rectangle( | |
| food.x * _CELL_SIZE + _CELL_SIZE, | |
| food.y * _CELL_SIZE + _CELL_SIZE, | |
| _CELL_SIZE, _CELL_SIZE, | |
| ray.GREEN | |
| ) | |
| def main(): | |
| screen_width: int = 640 | |
| screen_height: int = 480 | |
| ray.init_window(screen_width, screen_height, "Snake") | |
| ray.set_target_fps(10) | |
| snake = SnakeGame( | |
| int(screen_width / _CELL_SIZE), | |
| int(screen_height / _CELL_SIZE) | |
| ) | |
| while not ray.window_should_close(): | |
| ## | |
| # Inputs | |
| if ray.is_key_down(ray.KEY_RIGHT): | |
| snake.change_head_dir(Direction.RIGHT) | |
| if ray.is_key_down(ray.KEY_LEFT): | |
| snake.change_head_dir(Direction.LEFT) | |
| if ray.is_key_down(ray.KEY_UP): | |
| snake.change_head_dir(Direction.UP) | |
| if ray.is_key_down(ray.KEY_DOWN): | |
| snake.change_head_dir(Direction.DOWN) | |
| if ray.is_key_down(ray.KEY_R): | |
| snake.reset() | |
| ## | |
| # Game logic | |
| snake.update() | |
| ## | |
| # Rendering | |
| ray.clear_background(ray.RAYWHITE) | |
| draw_borders(screen_width, screen_height) | |
| draw_snake(snake.snake_parts, snake.state) | |
| draw_food(snake.food) | |
| ray.end_drawing() | |
| ray.close_window() | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from time import sleep | |
| import termbox as tbx | |
| from snake import Direction, SnakeGame, State | |
| def draw_borders(box, width, height): | |
| """Render game borders.""" | |
| for idx in range(width): | |
| box.change_cell(idx, 0, ord(u'#'), tbx.WHITE, tbx.WHITE) | |
| box.change_cell(idx, height-1, ord(u'#'), tbx.WHITE, tbx.WHITE) | |
| for idx in range(height): | |
| box.change_cell(0, idx, ord(u'#'), tbx.WHITE, tbx.WHITE) | |
| box.change_cell(width-1, idx, ord(u'#'), tbx.WHITE, tbx.WHITE) | |
| def draw_snake(box, parts, state): | |
| """Render the snake.""" | |
| color = tbx.YELLOW if state == State.ALIVE else tbx.RED | |
| for idx, part in enumerate(parts): | |
| box.change_cell( | |
| part.x + 1, part.y + 1, | |
| ord(u'#'), | |
| color if idx != 0 else tbx.MAGENTA, | |
| color if idx != 0 else tbx.MAGENTA | |
| ) | |
| def draw_food(box, food): | |
| """Render the food.""" | |
| box.change_cell(food.x + 1, food.y + 1, ord(u'@'), tbx.GREEN, tbx.GREEN) | |
| def main(): | |
| with tbx.Termbox() as box: | |
| box.clear() | |
| box.present() | |
| width = box.width() | |
| height = box.height() | |
| run_app = True | |
| snake = SnakeGame(width, height) | |
| while run_app: | |
| ## | |
| # Inputs | |
| event_here = box.peek_event() | |
| while event_here: | |
| type_, ch, key, mod, w, h, x, y = event_here | |
| if type_ == tbx.EVENT_KEY: | |
| if key == tbx.KEY_ESC: | |
| run_app = False | |
| elif key == tbx.KEY_ARROW_UP: | |
| snake.change_head_dir(Direction.UP) | |
| elif key == tbx.KEY_ARROW_DOWN: | |
| snake.change_head_dir(Direction.DOWN) | |
| elif key == tbx.KEY_ARROW_LEFT: | |
| snake.change_head_dir(Direction.LEFT) | |
| elif key == tbx.KEY_ARROW_RIGHT: | |
| snake.change_head_dir(Direction.RIGHT) | |
| elif ch == "r": | |
| snake.reset() | |
| event_here = box.peek_event() | |
| ## | |
| # Game logic | |
| snake.update() | |
| ## | |
| # Rendering | |
| box.clear() | |
| draw_borders(box, width, height) | |
| draw_food(box, snake.food) | |
| draw_snake(box, snake.snake_parts, snake.state) | |
| box.present() | |
| sleep(0.066) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment