From b045dd8ef96f024429113adca551e93d57e8f048 Mon Sep 17 00:00:00 2001 From: ltuffery <123221865+ltuffery@users.noreply.github.com> Date: Thu, 13 Mar 2025 22:47:55 +0000 Subject: [PATCH 1/5] fix programme start and add entity parent class --- api/entity/apple.py | 24 ++++++++++-- api/entity/entity.py | 26 +++++++++++++ api/entity/snake.py | 93 +++++++++++++++++++------------------------- api/world.py | 5 ++- main.py | 11 ++++-- 5 files changed, 96 insertions(+), 63 deletions(-) create mode 100644 api/entity/entity.py diff --git a/api/entity/apple.py b/api/entity/apple.py index 30886ba..8e66d69 100644 --- a/api/entity/apple.py +++ b/api/entity/apple.py @@ -1,4 +1,5 @@ from enum import Enum +from api.entity.entity import Entity class AppleType(Enum): @@ -13,7 +14,7 @@ class AppleType(Enum): GREEN = 1 -class Apple: +class Apple(Entity): """ Represents an apple in the game, which can be either red or green. @@ -21,13 +22,16 @@ class Apple: apple_type (AppleType): The type of the apple (RED or GREEN). """ - def __init__(self, apple_type: AppleType): + def __init__(self, x: int, y: int, apple_type: AppleType): """ - Initializes an apple with a specified type. + Initializes an apple with a specified type and position. Args: + x (int): The x-coordinate of the apple's position. + y (int): The y-coordinate of the apple's position. apple_type (AppleType): The type of the apple (RED or GREEN). """ + super().__init__(x, y) self.apple_type = apple_type def is_green(self) -> bool: @@ -47,3 +51,17 @@ def is_red(self) -> bool: bool: True if the apple is red, False otherwise. """ return self.apple_type == AppleType.RED + + def get_char(self) -> str: + """ + Returns a character representation of the apple, colored based on its + type. + + Returns: + str: A string representing the apple with appropriate color + formatting for the terminal. + """ + if self.is_green(): + return "\033[32m@\033[0m" # Green apple + + return "\033[31m@\033[0m" # Red apple diff --git a/api/entity/entity.py b/api/entity/entity.py new file mode 100644 index 0000000..1f0bd7b --- /dev/null +++ b/api/entity/entity.py @@ -0,0 +1,26 @@ +from abc import ABC, abstractmethod + + +class Entity(ABC): + def __init__(self, x: int, y: int): + self.__x = x + self.__y = y + + def get_x(self) -> int: + return self.__x + + def get_y(self) -> int: + return self.__y + + def set_x(self, x: int) -> None: + self.__x = x + + def set_y(self, y: int) -> None: + self.__y = y + + @abstractmethod + def get_char(self) -> str: + pass + + def __str__(self): + return self.get_char() diff --git a/api/entity/snake.py b/api/entity/snake.py index bd52c90..be3905e 100644 --- a/api/entity/snake.py +++ b/api/entity/snake.py @@ -2,9 +2,10 @@ from api.exception.gameover import GameOver from api.entity.apple import Apple from api.world import World +from api.entity.entity import Entity -class SnakeBody: +class SnakeBody(Entity): """ Represents a segment of a snake's body in a game. @@ -18,43 +19,34 @@ def __init__(self, x: int, y: int): Initializes a body segment of the snake at a specific position. Args: - x (int): The initial X position. - y (int): The initial Y position. + x (int): The initial X position of the body segment. + y (int): The initial Y position of the body segment. """ - self.__x = x - self.__y = y + super().__init__(x, y) - def get_x(self) -> int: + def move(self, x: int, y: int): """ - Returns the X-coordinate of the body segment. + Moves the body segment to a new position. - Returns: - int: The X-coordinate. + Args: + x (int): The new X position of the body segment. + y (int): The new Y position of the body segment. """ - return self.__x + self.set_x(x) + self.set_y(y) - def get_y(self) -> int: + def get_char(self) -> str: """ - Returns the Y-coordinate of the body segment. + Returns the character representation of a body segment for rendering in + the terminal. Returns: - int: The Y-coordinate. + str: The character "#" representing the body segment. """ - return self.__y + return "#" - def move(self, x: int, y: int): - """ - Moves the body segment to a new position. - Args: - x (int): The new X position. - y (int): The new Y position. - """ - self.__x = x - self.__y = y - - -class Snake: +class Snake(Entity): """ Represents a snake in the game, which moves, grows, and interacts with the world. @@ -79,11 +71,11 @@ def __init__(self, world: World, x: int, y: int, direction: Direction): y (int): The initial Y position of the snake's head. direction (Direction): The initial movement direction of the snake. """ - self.__x = x - self.__y = y + super().__init__(x, y) + self.__body: list[SnakeBody] = [] - self.__world = world - self.__last_direction = direction + self.__world: World = world + self.__last_direction: Direction = direction dir_x, dir_y = direction.value @@ -93,7 +85,7 @@ def __init__(self, world: World, x: int, y: int, direction: Direction): y += dir_y self.__body.append(SnakeBody(x, y)) - self.__world.spaw_entity(self.__body[-1]) + self.__world.spawn_entity(self.__body[-1]) def move(self, direction: Direction): """ @@ -108,19 +100,19 @@ def move(self, direction: Direction): GameOver: If the snake collides with an obstacle or itself. """ x, y = direction.value - info = self.__world.get_location(self.__x + x, self.__y + y) + info = self.__world.get_location(self.get_x() + x, self.get_y() + y) if not info.is_passable(): raise GameOver("End game") - self.__x += x - self.__y += y + self.set_x(self.get_x() + x) + self.set_y(self.get_y() + y) self.__last_direction = direction # Move the body segments following the head for i, body in reversed(list(enumerate(self.__body))): if i == 0: - body.move(self.__x - x, self.__y - y) + body.move(self.get_x() - x, self.get_y() - y) else: last = self.__body[i - 1] body.move(last.get_x(), last.get_y()) @@ -128,8 +120,7 @@ def move(self, direction: Direction): def eat(self, apple: Apple): """ Handles the snake eating an apple. The snake grows if the apple is - green, - otherwise, it loses a segment. + green, otherwise, it loses a segment. Args: apple (Apple): The apple that the snake is eating. @@ -141,6 +132,7 @@ def eat(self, apple: Apple): x, y = self.__last_direction last_body = self.__body[-1] + # Grow the snake by adding a new body segment self.__body.append( SnakeBody( last_body.get_x() + x, @@ -151,6 +143,7 @@ def eat(self, apple: Apple): if len(self.__body) == 0: raise GameOver("End game") + # Remove the last body segment del self.__body[-1] def size(self) -> int: @@ -163,29 +156,21 @@ def size(self) -> int: """ return len(self.__body) + 1 - def get_x(self) -> int: - """ - Returns the X-coordinate of the snake's head. - - Returns: - int: The X position of the snake's head. - """ - return self.__x - - def get_y(self) -> int: + def get_body(self) -> list[SnakeBody]: """ - Returns the Y-coordinate of the snake's head. + Returns the list of body segments of the snake. Returns: - int: The Y position of the snake's head. + list[SnakeBody]: The body segments of the snake. """ - return self.__y + return self.__body - def get_body(self) -> list[SnakeBody]: + def get_char(self) -> str: """ - Returns the list of body segments of the snake. + Returns the character representation of the snake, colored for + terminal output. Returns: - list[SnakeBody]: The body segments of the snake. + str: A string representing the snake, colored in yellow. """ - return self.__body + return "\033[33m#\033[0m" diff --git a/api/world.py b/api/world.py index 3543938..03eb155 100644 --- a/api/world.py +++ b/api/world.py @@ -1,4 +1,5 @@ from api.map_location import MapLocation +from api.entity.entity import Entity import sys import copy @@ -25,7 +26,7 @@ def __init__(self, height=10, width=10): self.__world: list = [] self.__height: int = height self.__width: int = width - self.__entities: list = [] + self.__entities: list[Entity] = [] self.__make_world() @@ -104,7 +105,7 @@ def render(self): world = copy.deepcopy(self.__world) for entity in self.__entities: - world[entity.get_y()][entity.get_x()] = '#' + world[entity.get_y()][entity.get_x()] = entity.get_char() # Uncomment if you want to render the snake's body # for body in entity.get_body(): diff --git a/main.py b/main.py index 87e863a..0a08cdf 100644 --- a/main.py +++ b/main.py @@ -2,11 +2,14 @@ from api.direction import Direction from api.exception.gameover import GameOver from api.entity.snake import Snake +from api.entity.apple import Apple, AppleType world = World() -snake = Snake(world, 5, 5, Direction.SUD) +snake = Snake(world, 5, 5, Direction.SOUTH) +green_apple = Apple(2, 2, AppleType.GREEN) -world.spaw_entity(snake) +world.spawn_entity(snake) +world.spawn_entity(green_apple) while True: world.render() @@ -21,9 +24,9 @@ if key == 'w': snake.move(Direction.NORTH) elif key == 's': - snake.move(Direction.SUD) + snake.move(Direction.SOUTH) elif key == 'a': - snake.move(Direction.WEAST) + snake.move(Direction.WEST) elif key == 'd': snake.move(Direction.EAST) except GameOver: From 4da6a08ea523c00f500fdb0c77cfc701708c8eab Mon Sep 17 00:00:00 2001 From: ltuffery <123221865+ltuffery@users.noreply.github.com> Date: Fri, 14 Mar 2025 00:51:29 +0000 Subject: [PATCH 2/5] change snake body logic --- api/entity/entity.py | 70 ++++++++++++++++++++++++++++++++ api/entity/snake.py | 96 ++++++++++++++------------------------------ api/map_location.py | 33 +++++++++++---- api/world.py | 18 ++++----- 4 files changed, 133 insertions(+), 84 deletions(-) diff --git a/api/entity/entity.py b/api/entity/entity.py index 1f0bd7b..3c40042 100644 --- a/api/entity/entity.py +++ b/api/entity/entity.py @@ -2,25 +2,95 @@ class Entity(ABC): + """ + Represents a game entity that has a position in the game world. + + Attributes: + __x (int): The X-coordinate of the entity. + __y (int): The Y-coordinate of the entity. + """ + def __init__(self, x: int, y: int): + """ + Initializes an entity with specified coordinates. + + Args: + x (int): The X-coordinate of the entity. + y (int): The Y-coordinate of the entity. + """ self.__x = x self.__y = y def get_x(self) -> int: + """ + Returns the X-coordinate of the entity. + + Returns: + int: The X-coordinate of the entity. + """ return self.__x def get_y(self) -> int: + """ + Returns the Y-coordinate of the entity. + + Returns: + int: The Y-coordinate of the entity. + """ return self.__y + def get_position(self) -> tuple[int, int]: + """ + Returns the position of the entity as a tuple of (x, y). + + Returns: + tuple[int, int]: The position of the entity. + """ + return (self.get_x(), self.get_y()) + def set_x(self, x: int) -> None: + """ + Sets the X-coordinate of the entity. + + Args: + x (int): The new X-coordinate. + """ self.__x = x def set_y(self, y: int) -> None: + """ + Sets the Y-coordinate of the entity. + + Args: + y (int): The new Y-coordinate. + """ self.__y = y @abstractmethod def get_char(self) -> str: + """ + Abstract method to return the character representation of the entity. + + Returns: + str: The character representing the entity. + """ pass + def render(self) -> list[tuple[str, int, int]]: + """ + Renders the entity's character and position. + + Returns: + list[tuple[str, int, int]]: A list containing a tuple of the + entity's character and its coordinates (x, y). + """ + return [(self.get_char(), self.get_x(), self.get_y())] + def __str__(self): + """ + Returns a string representation of the entity. + + Returns: + str: The character representation of the entity. + """ return self.get_char() diff --git a/api/entity/snake.py b/api/entity/snake.py index be3905e..4e2b8b3 100644 --- a/api/entity/snake.py +++ b/api/entity/snake.py @@ -5,47 +5,6 @@ from api.entity.entity import Entity -class SnakeBody(Entity): - """ - Represents a segment of a snake's body in a game. - - Attributes: - __x (int): The X-coordinate of the body segment. - __y (int): The Y-coordinate of the body segment. - """ - - def __init__(self, x: int, y: int): - """ - Initializes a body segment of the snake at a specific position. - - Args: - x (int): The initial X position of the body segment. - y (int): The initial Y position of the body segment. - """ - super().__init__(x, y) - - def move(self, x: int, y: int): - """ - Moves the body segment to a new position. - - Args: - x (int): The new X position of the body segment. - y (int): The new Y position of the body segment. - """ - self.set_x(x) - self.set_y(y) - - def get_char(self) -> str: - """ - Returns the character representation of a body segment for rendering in - the terminal. - - Returns: - str: The character "#" representing the body segment. - """ - return "#" - - class Snake(Entity): """ Represents a snake in the game, which moves, grows, and interacts with the @@ -54,8 +13,8 @@ class Snake(Entity): Attributes: __x (int): The X-coordinate of the snake's head. __y (int): The Y-coordinate of the snake's head. - __body (list[SnakeBody]): A list representing the snake's body - segments. + __body (list[tuple[int, int]]): A list representing the snake's body + segments. __world (World): The game world where the snake exists. __last_direction (Direction): The last movement direction of the snake. """ @@ -73,19 +32,17 @@ def __init__(self, world: World, x: int, y: int, direction: Direction): """ super().__init__(x, y) - self.__body: list[SnakeBody] = [] + self.__body: list[tuple[int, int]] = [] self.__world: World = world self.__last_direction: Direction = direction dir_x, dir_y = direction.value # Create the initial body of the snake (3 segments) - for i in range(3): + for _ in range(3): x += dir_x y += dir_y - - self.__body.append(SnakeBody(x, y)) - self.__world.spawn_entity(self.__body[-1]) + self.__body.append((x, y)) def move(self, direction: Direction): """ @@ -94,7 +51,7 @@ def move(self, direction: Direction): Args: direction (Direction): The direction in which the snake should - move. + move. Raises: GameOver: If the snake collides with an obstacle or itself. @@ -102,20 +59,22 @@ def move(self, direction: Direction): x, y = direction.value info = self.__world.get_location(self.get_x() + x, self.get_y() + y) - if not info.is_passable(): + if info.is_wall(): raise GameOver("End game") + if (self.get_x() + x, self.get_y() + y) in self.__body: + raise GameOver("End game") + + if isinstance(info.get_entity(), Apple): + self.eat(info.get_entity()) + self.set_x(self.get_x() + x) self.set_y(self.get_y() + y) self.__last_direction = direction # Move the body segments following the head - for i, body in reversed(list(enumerate(self.__body))): - if i == 0: - body.move(self.get_x() - x, self.get_y() - y) - else: - last = self.__body[i - 1] - body.move(last.get_x(), last.get_y()) + self.__body.insert(0, (self.get_x() - x, self.get_y() - y)) + del self.__body[-1] def eat(self, apple: Apple): """ @@ -129,16 +88,11 @@ def eat(self, apple: Apple): GameOver: If the snake loses its last segment. """ if apple.is_green(): - x, y = self.__last_direction + x, y = self.__last_direction.value last_body = self.__body[-1] # Grow the snake by adding a new body segment - self.__body.append( - SnakeBody( - last_body.get_x() + x, - last_body.get_y() + y - ) - ) + self.__body.append((last_body[0] + x, last_body[1] + y)) else: if len(self.__body) == 0: raise GameOver("End game") @@ -156,12 +110,12 @@ def size(self) -> int: """ return len(self.__body) + 1 - def get_body(self) -> list[SnakeBody]: + def get_body(self) -> list[tuple[int, int]]: """ Returns the list of body segments of the snake. Returns: - list[SnakeBody]: The body segments of the snake. + list[tuple[int, int]]: The body segments of the snake. """ return self.__body @@ -174,3 +128,15 @@ def get_char(self) -> str: str: A string representing the snake, colored in yellow. """ return "\033[33m#\033[0m" + + def render(self): + """ + Renders the snake and its body in the world. + + Returns: + list: A list containing the snake's head and body positions. + """ + render = super().render() + [render.append(("#", body[0], body[1])) for body in self.__body] + + return render diff --git a/api/map_location.py b/api/map_location.py index 8a4b82a..0e05436 100644 --- a/api/map_location.py +++ b/api/map_location.py @@ -1,3 +1,6 @@ +from api.entity.entity import Entity + + class MapLocation: """ Represents a specific location on the game map. @@ -5,30 +8,34 @@ class MapLocation: Attributes: __x (int): The X-coordinate of the location. __y (int): The Y-coordinate of the location. - __is_passable (bool): Indicates whether the location is passable. + __is_wall (bool): Indicates whether the location is a wall. + __entity (Entity | None): The entity present at the location, if any. """ - def __init__(self, x: int, y: int, is_passable: bool): + def __init__(self, x: int, y: int, is_wall: bool, entity: Entity | None): """ Initializes a map location with coordinates and passability status. Args: x (int): The X-coordinate of the location. y (int): The Y-coordinate of the location. - is_passable (bool): Whether the location is passable. + is_wall (bool): Whether the location is a wall (not passable). + entity (Entity | None): The entity at the location, or None if + there is no entity. """ self.__x = x self.__y = y - self.__is_passable = is_passable + self.__is_wall = is_wall + self.__entity = entity - def is_passable(self) -> bool: + def is_wall(self) -> bool: """ - Checks if the location is passable. + Checks if the location is a wall (not passable). Returns: - bool: True if the location is passable, False otherwise. + bool: True if the location is a wall, False otherwise. """ - return self.__is_passable + return self.__is_wall def get_x(self) -> int: """ @@ -47,3 +54,13 @@ def get_y(self) -> int: int: The Y-coordinate. """ return self.__y + + def get_entity(self) -> Entity | None: + """ + Returns the entity at the location, if any. + + Returns: + Entity | None: The entity at the location, or None if there + is no entity. + """ + return self.__entity diff --git a/api/world.py b/api/world.py index 03eb155..4075848 100644 --- a/api/world.py +++ b/api/world.py @@ -12,7 +12,7 @@ class World: __world (list[list[str]]): The 2D grid representing the game map. __height (int): The height of the world (number of rows). __width (int): The width of the world (number of columns). - __entities (list): A list of all entities present in the world. + __entities (list[Entity]): A list of all entities present in the world. """ def __init__(self, height=10, width=10): @@ -64,12 +64,11 @@ def get_location(self, x: int, y: int) -> MapLocation: if y > len(self.__world) or x > len(self.__world[y]): raise Exception('Invalid location') - if self.get_entity_at(x, y) is None: - return MapLocation(x, y, self.__world[y][x] == ' ') + is_wall = self.__world[y][x] == '*' - return MapLocation(x, y, False) + return MapLocation(x, y, is_wall, self.get_entity_at(x, y)) - def get_entity_at(self, x: int, y: int): + def get_entity_at(self, x: int, y: int) -> Entity | None: """ Retrieves an entity at the given coordinates. @@ -79,7 +78,7 @@ def get_entity_at(self, x: int, y: int): Returns: object | None: The entity found at the coordinates, or None if - empty. + empty. """ for entity in self.__entities: if entity.get_x() == x and entity.get_y() == y: @@ -105,11 +104,8 @@ def render(self): world = copy.deepcopy(self.__world) for entity in self.__entities: - world[entity.get_y()][entity.get_x()] = entity.get_char() - - # Uncomment if you want to render the snake's body - # for body in entity.get_body(): - # world[body.get_y()][body.get_x()] = 'T' + for position in entity.render(): + world[position[2]][position[1]] = position[0] for line in world: print("".join(line)) From 703322ee2e897cec2e5b541dd20a4745fea7baf7 Mon Sep 17 00:00:00 2001 From: ltuffery <123221865+ltuffery@users.noreply.github.com> Date: Fri, 14 Mar 2025 14:54:21 +0000 Subject: [PATCH 3/5] add: random teleport entity --- api/entity/apple.py | 15 +++++++++++---- api/entity/entity.py | 4 ++++ api/entity/snake.py | 17 +++++++++++++++++ api/world.py | 27 +++++++++++++++++++++++++-- main.py | 2 +- 5 files changed, 58 insertions(+), 7 deletions(-) diff --git a/api/entity/apple.py b/api/entity/apple.py index 8e66d69..e6b2b4c 100644 --- a/api/entity/apple.py +++ b/api/entity/apple.py @@ -1,5 +1,6 @@ from enum import Enum from api.entity.entity import Entity +from api.world import World class AppleType(Enum): @@ -22,7 +23,7 @@ class Apple(Entity): apple_type (AppleType): The type of the apple (RED or GREEN). """ - def __init__(self, x: int, y: int, apple_type: AppleType): + def __init__(self, world: World, x: int, y: int, apple_type: AppleType): """ Initializes an apple with a specified type and position. @@ -32,7 +33,9 @@ def __init__(self, x: int, y: int, apple_type: AppleType): apple_type (AppleType): The type of the apple (RED or GREEN). """ super().__init__(x, y) - self.apple_type = apple_type + + self.__world = world + self.__apple_type = apple_type def is_green(self) -> bool: """ @@ -41,7 +44,7 @@ def is_green(self) -> bool: Returns: bool: True if the apple is green, False otherwise. """ - return self.apple_type == AppleType.GREEN + return self.__apple_type == AppleType.GREEN def is_red(self) -> bool: """ @@ -50,7 +53,7 @@ def is_red(self) -> bool: Returns: bool: True if the apple is red, False otherwise. """ - return self.apple_type == AppleType.RED + return self.__apple_type == AppleType.RED def get_char(self) -> str: """ @@ -65,3 +68,7 @@ def get_char(self) -> str: return "\033[32m@\033[0m" # Green apple return "\033[31m@\033[0m" # Red apple + + def consume(self): + self.__world.remove_entity(self) + self.__world.spawn_entity(self) diff --git a/api/entity/entity.py b/api/entity/entity.py index 3c40042..13e6eaf 100644 --- a/api/entity/entity.py +++ b/api/entity/entity.py @@ -65,6 +65,10 @@ def set_y(self, y: int) -> None: y (int): The new Y-coordinate. """ self.__y = y + + def teleport(self, x: int, y: int) -> None: + self.__x = x + self.__y = y @abstractmethod def get_char(self) -> str: diff --git a/api/entity/snake.py b/api/entity/snake.py index 4e2b8b3..9c45f1e 100644 --- a/api/entity/snake.py +++ b/api/entity/snake.py @@ -3,6 +3,7 @@ from api.entity.apple import Apple from api.world import World from api.entity.entity import Entity +import random class Snake(Entity): @@ -43,6 +44,20 @@ def __init__(self, world: World, x: int, y: int, direction: Direction): x += dir_x y += dir_y self.__body.append((x, y)) + + def teleport(self, x, y): + super().teleport(x, y) + + for i in range(len(self.__body)): + del self.__body[i] + + self.__last_direction = random.choice([ x for x in list(Direction) if x.name != self.__last_direction.name ]) + dir_x, dir_y = self.__last_direction.value + + x += dir_x + y += dir_y + + self.__body.insert(i, (x, y)) def move(self, direction: Direction): """ @@ -99,6 +114,8 @@ def eat(self, apple: Apple): # Remove the last body segment del self.__body[-1] + + apple.consume() def size(self) -> int: """ diff --git a/api/world.py b/api/world.py index 4075848..99eae80 100644 --- a/api/world.py +++ b/api/world.py @@ -2,6 +2,7 @@ from api.entity.entity import Entity import sys import copy +import random class World: @@ -23,7 +24,7 @@ def __init__(self, height=10, width=10): height (int, optional): The height of the world. Defaults to 10. width (int, optional): The width of the world. Defaults to 10. """ - self.__world: list = [] + self.__world: list[list[str]] = [] self.__height: int = height self.__width: int = width self.__entities: list[Entity] = [] @@ -67,6 +68,21 @@ def get_location(self, x: int, y: int) -> MapLocation: is_wall = self.__world[y][x] == '*' return MapLocation(x, y, is_wall, self.get_entity_at(x, y)) + + def get_empty_locations(self) -> list[tuple[int, int]]: + empty_list = [] + + for y in range(len(self.__world)): + for x in range(len(self.__world[y])): + ceil = self.__world[y][x] + + if ceil != ' ': + continue + + if self.get_entity_at(x, y) is None: + empty_list.append((x, y)) + + return empty_list def get_entity_at(self, x: int, y: int) -> Entity | None: """ @@ -85,15 +101,22 @@ def get_entity_at(self, x: int, y: int) -> Entity | None: return entity return None - def spawn_entity(self, entity) -> None: + def spawn_entity(self, entity: Entity) -> None: """ Adds an entity to the world. Args: entity (object): The entity to add. """ + x, y = random.choice(self.get_empty_locations()) + + entity.teleport(x, y) + self.__entities.append(entity) + def remove_entity(self, entity: Entity): + self.__entities.remove(entity) + def render(self): """ Renders the current state of the world to the terminal. diff --git a/main.py b/main.py index 0a08cdf..d4fa68e 100644 --- a/main.py +++ b/main.py @@ -6,7 +6,7 @@ world = World() snake = Snake(world, 5, 5, Direction.SOUTH) -green_apple = Apple(2, 2, AppleType.GREEN) +green_apple = Apple(world, 2, 2, AppleType.GREEN) world.spawn_entity(snake) world.spawn_entity(green_apple) From 367c62ef155332cd5ffec043e1b4d1e6e0398f17 Mon Sep 17 00:00:00 2001 From: ltuffery <123221865+ltuffery@users.noreply.github.com> Date: Fri, 14 Mar 2025 18:55:51 +0000 Subject: [PATCH 4/5] fix: norme flake8 --- api/entity/apple.py | 12 ++++-- api/entity/entity.py | 9 ++++- api/entity/snake.py | 89 +++++++++++++++++++++++++------------------- api/world.py | 17 ++++++++- 4 files changed, 82 insertions(+), 45 deletions(-) diff --git a/api/entity/apple.py b/api/entity/apple.py index e6b2b4c..1a15748 100644 --- a/api/entity/apple.py +++ b/api/entity/apple.py @@ -57,8 +57,8 @@ def is_red(self) -> bool: def get_char(self) -> str: """ - Returns a character representation of the apple, colored based on its - type. + Returns a character representation of the apple, colored based on + its type. Returns: str: A string representing the apple with appropriate color @@ -68,7 +68,13 @@ def get_char(self) -> str: return "\033[32m@\033[0m" # Green apple return "\033[31m@\033[0m" # Red apple - + def consume(self): + """ + Removes the apple from the world and respawns it. + + The apple is removed from the world and then spawned again at the + same location. + """ self.__world.remove_entity(self) self.__world.spawn_entity(self) diff --git a/api/entity/entity.py b/api/entity/entity.py index 13e6eaf..feb43c1 100644 --- a/api/entity/entity.py +++ b/api/entity/entity.py @@ -65,8 +65,15 @@ def set_y(self, y: int) -> None: y (int): The new Y-coordinate. """ self.__y = y - + def teleport(self, x: int, y: int) -> None: + """ + Teleports the entity to a new position (x, y). + + Args: + x (int): The new X-coordinate. + y (int): The new Y-coordinate. + """ self.__x = x self.__y = y diff --git a/api/entity/snake.py b/api/entity/snake.py index 9c45f1e..d9fa9b9 100644 --- a/api/entity/snake.py +++ b/api/entity/snake.py @@ -4,6 +4,7 @@ from api.world import World from api.entity.entity import Entity import random +from collections import deque class Snake(Entity): @@ -12,12 +13,12 @@ class Snake(Entity): world. Attributes: - __x (int): The X-coordinate of the snake's head. - __y (int): The Y-coordinate of the snake's head. - __body (list[tuple[int, int]]): A list representing the snake's body - segments. - __world (World): The game world where the snake exists. - __last_direction (Direction): The last movement direction of the snake. + __body (deque[tuple[int, int]]): + Deque of tuples representing the snake's body segments. + __world (World): + The game world where the snake exists. + __last_direction (Direction): + The last movement direction of the snake. """ def __init__(self, world: World, x: int, y: int, direction: Direction): @@ -33,7 +34,7 @@ def __init__(self, world: World, x: int, y: int, direction: Direction): """ super().__init__(x, y) - self.__body: list[tuple[int, int]] = [] + self.__body: deque[tuple[int, int]] = deque() self.__world: World = world self.__last_direction: Direction = direction @@ -44,57 +45,67 @@ def __init__(self, world: World, x: int, y: int, direction: Direction): x += dir_x y += dir_y self.__body.append((x, y)) - - def teleport(self, x, y): - super().teleport(x, y) - for i in range(len(self.__body)): - del self.__body[i] + def teleport(self, x: int, y: int) -> None: + """ + Teleports the snake to a new position and resets its body direction. + + Args: + x (int): The new X position. + y (int): The new Y position. + """ + super().teleport(x, y) - self.__last_direction = random.choice([ x for x in list(Direction) if x.name != self.__last_direction.name ]) + new_body = deque() + for _ in range(len(self.__body)): + dir_name = self.__last_direction.name + self.__last_direction = random.choice([ + d for d in list(Direction) if d.name != dir_name + ]) dir_x, dir_y = self.__last_direction.value x += dir_x y += dir_y - self.__body.insert(i, (x, y)) + new_body.append((x, y)) - def move(self, direction: Direction): + self.__body = new_body + + def move(self, direction: Direction) -> None: """ Moves the snake in the given direction. If the new location is not passable, the game ends. Args: direction (Direction): The direction in which the snake should - move. + move. Raises: GameOver: If the snake collides with an obstacle or itself. """ x, y = direction.value - info = self.__world.get_location(self.get_x() + x, self.get_y() + y) - - if info.is_wall(): - raise GameOver("End game") + new_x = self.get_x() + x + new_y = self.get_y() + y + info = self.__world.get_location(new_x, new_y) - if (self.get_x() + x, self.get_y() + y) in self.__body: + if info.is_wall() or (new_x, new_y) in self.__body: raise GameOver("End game") if isinstance(info.get_entity(), Apple): self.eat(info.get_entity()) - self.set_x(self.get_x() + x) - self.set_y(self.get_y() + y) + self.set_x(new_x) + self.set_y(new_y) self.__last_direction = direction # Move the body segments following the head - self.__body.insert(0, (self.get_x() - x, self.get_y() - y)) - del self.__body[-1] + self.__body.appendleft((self.get_x() - x, self.get_y() - y)) + self.__body.pop() - def eat(self, apple: Apple): + def eat(self, apple: Apple) -> None: """ - Handles the snake eating an apple. The snake grows if the apple is - green, otherwise, it loses a segment. + Handles the snake eating an apple. + The snake grows if the apple is green, otherwise, it loses a segment. Args: apple (Apple): The apple that the snake is eating. @@ -109,12 +120,12 @@ def eat(self, apple: Apple): # Grow the snake by adding a new body segment self.__body.append((last_body[0] + x, last_body[1] + y)) else: - if len(self.__body) == 0: + if not self.__body: raise GameOver("End game") # Remove the last body segment - del self.__body[-1] - + self.__body.pop() + apple.consume() def size(self) -> int: @@ -127,33 +138,33 @@ def size(self) -> int: """ return len(self.__body) + 1 - def get_body(self) -> list[tuple[int, int]]: + def get_body(self) -> deque[tuple[int, int]]: """ Returns the list of body segments of the snake. Returns: - list[tuple[int, int]]: The body segments of the snake. + deque[tuple[int, int]]: The body segments of the snake. """ return self.__body def get_char(self) -> str: """ - Returns the character representation of the snake, colored for - terminal output. + Returns the character representation of the snake, colored for terminal + output. Returns: str: A string representing the snake, colored in yellow. """ return "\033[33m#\033[0m" - def render(self): + def render(self) -> list[tuple[str, int, int]]: """ Renders the snake and its body in the world. Returns: - list: A list containing the snake's head and body positions. + list[tuple[str, int, int]]: A list containing the snake's head and + body positions. """ render = super().render() - [render.append(("#", body[0], body[1])) for body in self.__body] - + render.extend(("#", body[0], body[1]) for body in self.__body) return render diff --git a/api/world.py b/api/world.py index 99eae80..d86f9a2 100644 --- a/api/world.py +++ b/api/world.py @@ -68,8 +68,15 @@ def get_location(self, x: int, y: int) -> MapLocation: is_wall = self.__world[y][x] == '*' return MapLocation(x, y, is_wall, self.get_entity_at(x, y)) - + def get_empty_locations(self) -> list[tuple[int, int]]: + """ + Retrieves a list of all empty locations in the world. + + Returns: + list[tuple[int, int]]: A list of coordinates (x, y) where there + are no entities. + """ empty_list = [] for y in range(len(self.__world)): @@ -94,7 +101,7 @@ def get_entity_at(self, x: int, y: int) -> Entity | None: Returns: object | None: The entity found at the coordinates, or None if - empty. + empty. """ for entity in self.__entities: if entity.get_x() == x and entity.get_y() == y: @@ -115,6 +122,12 @@ def spawn_entity(self, entity: Entity) -> None: self.__entities.append(entity) def remove_entity(self, entity: Entity): + """ + Removes an entity from the world. + + Args: + entity (Entity): The entity to remove. + """ self.__entities.remove(entity) def render(self): From 31c5e4d25cfe6b238cbdf0fedfaf853a426c569d Mon Sep 17 00:00:00 2001 From: ltuffery <123221865+ltuffery@users.noreply.github.com> Date: Fri, 14 Mar 2025 18:58:36 +0000 Subject: [PATCH 5/5] add: red apple --- main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.py b/main.py index d4fa68e..8e1624d 100644 --- a/main.py +++ b/main.py @@ -7,9 +7,11 @@ world = World() snake = Snake(world, 5, 5, Direction.SOUTH) green_apple = Apple(world, 2, 2, AppleType.GREEN) +red_apple = Apple(world, 2, 2, AppleType.RED) world.spawn_entity(snake) world.spawn_entity(green_apple) +world.spawn_entity(red_apple) while True: world.render()