diff --git a/Interface/grid_draw.py b/Interface/grid_draw.py deleted file mode 100644 index 250ac92..0000000 --- a/Interface/grid_draw.py +++ /dev/null @@ -1,61 +0,0 @@ -import random -import sys -import pygame - - -class Colors: - BLACK = 0, 0, 0 - WHITE = 255, 255, 255 - RED = 255, 0, 0 - GREEN = 0, 255, 0 - - -DEFAULT_COLOR = Colors.WHITE - - -def default_color(func): - def wrap(*args, **kwargs): - if "color" not in kwargs: - kwargs["color"] = DEFAULT_COLOR - result = func(*args, **kwargs) - return result - - return wrap - - -class GridDraw: - def __init__(self, width=None, height=None, background=None): - self.width = width if width != None else 100 - self.height = height if height != None else 100 - self.background = background if background != None else Colors.BLACK - - pygame.init() - self.screen = pygame.display.set_mode((self.width, self.height)) - - def start_draw(self): - self.screen.fill(Colors.BLACK) - - def end_draw(self): - pygame.display.flip() - - @default_color - def line(self, x_1, y_1, x_2, y_2, color=None): - pygame.draw.line(self.screen, color, (x_1, y_1), (x_2, y_2)) - - @default_color - def board(self, tiles_x, tiles_y, color=None): - tiles_width = self.width / tiles_x - tiles_height = self.height / tiles_y - - for i in range(1, tiles_x): - self.line(tiles_width * i, 0, tiles_width * i, self.height, color=color) - - for i in range(1, tiles_y): - self.line(0, tiles_height * i, self.width, tiles_height * i, color=color) - - @default_color - def circle(self, x, y, radius, color=None): - pygame.draw.circle(self.screen, color, (x, y), radius) - - def image(self, x, y, image): - self.screen.blit(image, (x, y)) diff --git a/Interface/movement.py b/Interface/movement.py deleted file mode 100644 index 63eaed4..0000000 --- a/Interface/movement.py +++ /dev/null @@ -1,25 +0,0 @@ -import pygame -import sys - - -def movement_key_press(board_size, x, y): - for event in pygame.event.get(): - if event.type == pygame.QUIT: - sys.exit() - if event.type == pygame.KEYDOWN: - # go left - if event.key == pygame.K_LEFT and x > 0: - x -= 1 - - # go right - if event.key == pygame.K_RIGHT and x < board_size - 1: - x += 1 - - # go up - if event.key == pygame.K_UP and y > 0: - y -= 1 - - # go down - if event.key == pygame.K_DOWN and y < board_size - 1: - y += 1 - return (x, y) diff --git a/Interface/vacuum_render.py b/Interface/vacuum_render.py deleted file mode 100644 index 059adb4..0000000 --- a/Interface/vacuum_render.py +++ /dev/null @@ -1,266 +0,0 @@ -from enum import Enum -import random -from typing import List -from Interface.grid_draw import GridDraw, Colors -import sys -import pygame -from Interface.movement import movement_key_press - - -# window_dimensions says how many pixels window have -# board_size says how many lines board have in one row -def initial_draw(window_dimensions, board_size): - # window name - pygame.display.set_caption("AI Vacuum Cleaner") - - # define additional variables - tile_size = window_dimensions / board_size - - # initialize board array - newGrid = Grid(board_size, window_dimensions=window_dimensions, board_size=board_size) - newGrid.add(objectOnTile(1, 1, acceptedType.PLAYER)) - newGrid.add(objectOnTile(7, 8, acceptedType.ANIMAL)) - newGrid.add(objectOnTile(2, 8, acceptedType.PLANT1)) - newGrid.add(objectOnTile(4, 1, acceptedType.PLANT1)) - newGrid.add(objectOnTile(3, 4, acceptedType.PLANT2)) - newGrid.add(objectOnTile(8, 8, acceptedType.PLANT2)) - newGrid.add(objectOnTile(9, 3, acceptedType.PLANT3)) - - - player = newGrid.findFirst(acceptedType.PLAYER) - newGrid.move(1, 1, 1, 2) - newGrid.move(1, 2, 1, 1) - - # set window dimension - window_width = window_dimensions - window_height = window_dimensions - - # initialize drawer - drawer = GridDraw(window_width, window_height) - - # rendering loop - while True: - drawer.start_draw() - drawer.board(board_size, board_size) - - player = newGrid.findFirst(acceptedType.PLAYER) - - (x, y) = movement_key_press(board_size, player.position_x, player.position_y) - - newGrid.move(player.position_x, player.position_y, x, y) - - newGrid.render(drawer, newGrid=newGrid) - drawer.end_draw() - pygame.time.delay(30) - - -# TODO wrap it all to another file that handles array rendering -class acceptedType(Enum): - EMPTY = "empty" - PLAYER = "player" - RUBBISH = "rubbish" - PLANT1 = "plant1" - PLANT2 = "plant2" - PLANT3 = "plant3" - ANIMAL = "animal" - - -class objectOnTile: - def __init__( - self, position_x: int, position_y: int, type: acceptedType = acceptedType.EMPTY - ): - self.position_x = position_x - self.position_y = position_y - self.type = type - - -# calculate position from array position to window position eg.: array_position = 0 => window_position = 50 (px) -def _translate_array_to_window_position(array_position, tile_size_window) -> int: - return array_position * tile_size_window + tile_size_window / 2 - - -class Grid: - - def __init__(self, size_array, window_dimensions, board_size): - self.array = [ - [objectOnTile(i, j) for j in range(size_array)] for i in range(size_array) - ] - self.list: List[objectOnTile] = [] - - self.tile_size = window_dimensions / board_size - self.board_size = board_size - - self.cat_last_tick = pygame.time.get_ticks() - self.cat_cooldown = 1000 - self.cat_velocity = 1 - self.cat_busy = False - - #region images - self.cat_front_image = pygame.transform.scale(pygame.image.load("Interface/images/cat/standing_front.png"), (self.tile_size, self.tile_size)) - self.cat_back_image = pygame.transform.scale(pygame.image.load("Interface/images/cat/standing_back.png"), (self.tile_size, self.tile_size)) - self.cat_left_image = pygame.transform.scale(pygame.image.load("Interface/images/cat/standing_left.png"), (self.tile_size, self.tile_size)) - self.cat_right_image = pygame.transform.scale(pygame.image.load("Interface/images/cat/standing_right.png"), (self.tile_size, self.tile_size)) - self.cat_current_image = self.cat_front_image - self.plant1 = pygame.transform.scale(pygame.image.load("Interface/images/plants/plant1.png"), (self.tile_size + self.tile_size/4, self.tile_size + self.tile_size/4)) - self.plant2 = pygame.transform.scale(pygame.image.load("Interface/images/plants/plant2.png"), (self.tile_size + self.tile_size/4, self.tile_size + self.tile_size/4)) - self.plant3 = pygame.transform.scale(pygame.image.load("Interface/images/plants/plant3.png"), (self.tile_size + self.tile_size/4, self.tile_size + self.tile_size/4)) - #endregion - - # render the array - def render(self, drawer: GridDraw, newGrid): - #tile_size = window_dimensions / board_size - - # render object with respect to type - for item in self.list: - if item.type == acceptedType.PLAYER: - # constants for player - PLAYER_RADIUS_RATIO = 3 - PLAYER_COLOR = Colors.RED - - # position on screen - render_x = _translate_array_to_window_position( - item.position_x, self.tile_size - ) - render_y = _translate_array_to_window_position( - item.position_y, self.tile_size - ) - - # image rendering function - drawer.circle( - render_x, - render_y, - self.tile_size / PLAYER_RADIUS_RATIO, - color=PLAYER_COLOR, - ) - if item.type == acceptedType.ANIMAL: - now = pygame.time.get_ticks() - #region cat random movement - if now - self.cat_last_tick >= self.cat_cooldown: - - if self.cat_busy == False: - while True: - self.cat_direction = random.randint(0,3) - if not((self.cat_direction == 0 and item.position_y == 0) or (self.cat_direction == 1 and item.position_x == self.board_size - 1) or (self.cat_direction == 2 and item.position_y == self.board_size - 1) or (self.cat_direction == 3 and item.position_x == 0)): - break - - if self.cat_direction == 0: #up - if self.cat_current_image == self.cat_back_image: - newGrid.move(item.position_x, item.position_y, item.position_x, item.position_y - 1) - self.cat_busy = False - else: - self.cat_busy = True - self.cat_current_image = self.cat_back_image - if self.cat_direction == 1: #right - if self.cat_current_image == self.cat_right_image: - newGrid.move(item.position_x, item.position_y, item.position_x + 1, item.position_y) - self.cat_busy = False - else: - self.cat_busy = True - self.cat_current_image = self.cat_right_image - if self.cat_direction == 2: #down - if self.cat_current_image == self.cat_front_image: - newGrid.move(item.position_x, item.position_y, item.position_x, item.position_y + 1) - self.cat_busy = False - else: - self.cat_busy = True - self.cat_current_image = self.cat_front_image - if self.cat_direction == 3: #left - if self.cat_current_image == self.cat_left_image: - newGrid.move(item.position_x, item.position_y, item.position_x - 1, item.position_y) - self.cat_busy = False - else: - self.cat_busy = True - self.cat_current_image = self.cat_left_image - self.cat_last_tick = pygame.time.get_ticks() - #endregion - - render_x = item.position_x * self.tile_size - render_y = item.position_y * self.tile_size - - drawer.image(render_x, render_y, self.cat_current_image) - - if item.type == acceptedType.PLANT1: - drawer.image((item.position_x - 0.1) * self.tile_size, (item.position_y - 0.25) * self.tile_size, self.plant1) - if item.type == acceptedType.PLANT2: - drawer.image((item.position_x - 0.1) * self.tile_size, (item.position_y - 0.25) * self.tile_size, self.plant2) - if item.type == acceptedType.PLANT3: - drawer.image((item.position_x - 0.1) * self.tile_size, (item.position_y - 0.25) * self.tile_size, self.plant3) - - - - # TODO act accordingly to other options(rubbish) - - # add new object on grid - def add(self, newObject: objectOnTile): - if ( - self.array[newObject.position_x][newObject.position_y].type - != acceptedType.EMPTY - ): - print( - f"Cannot add object at ({newObject.position_x}, {newObject.position_y}): position already occupied" - ) - return - - self.array[newObject.position_x][newObject.position_y] = newObject - self.list.append(newObject) - - # deletes object from game - # untested, potentially not working - def delete(self, position_x: int, position_y: int): - # Find the object with the given position in the list - for obj in self.list: - if obj.position_x == position_x and obj.position_y == position_y: - break - - else: # No object found with the given position - print(f"No object found at ({position_x}, {position_y})") - return - - # Remove the object from both the array and the list - self.array[position_x][position_y] = objectOnTile(position_x, position_y) - self.list.remove(obj) - - # move: update position from (start_x, start_y) to (end_x, end_y) - def move(self, start_x: int, start_y: int, end_x: int, end_y: int): - # no change - if start_x == end_x and start_y == end_y: - return - - #check if object moves beyond border - if end_x > self.board_size - 1 or end_y > self.board_size - 1 or end_x < 0 or end_y < 0: - print( - f"Cannot move object beyond board" - ) - return - - # check if obj exist at starting position - if self.array[start_x][start_y].type == acceptedType.EMPTY: - print( - f"Cannot move object at ({start_x}, {start_y}): no object on position" - ) - return - - # check if destination is empty - if self.array[end_x][end_y].type != acceptedType.EMPTY: - print( - f"Cannot move object to ({end_x}, {end_y}): position already occupied" - ) - return - - - - # all OK - # change position attribute in array - self.array[start_x][start_y].position_x = end_x - self.array[start_x][start_y].position_y = end_y - - # change position in array - self.array[end_x][end_y] = self.array[start_x][start_y] - self.array[start_x][start_y] = objectOnTile(start_x, start_y) - - def findFirst(self, find_type: acceptedType) -> objectOnTile: - for item in self.list: - if item.type == find_type: - return item - else: - print(f"Cannot find object of type: ({find_type})!") diff --git a/domain/commands/command.py b/domain/commands/command.py new file mode 100644 index 0000000..377a0f3 --- /dev/null +++ b/domain/commands/command.py @@ -0,0 +1,3 @@ +class Command: + def run(self): + raise NotImplementedError() diff --git a/domain/commands/random_cat_move_command.py b/domain/commands/random_cat_move_command.py new file mode 100644 index 0000000..3dbe005 --- /dev/null +++ b/domain/commands/random_cat_move_command.py @@ -0,0 +1,70 @@ +from random import randint +from typing import Tuple + +import pygame + +from domain.commands.command import Command +from domain.entities.cat import Cat +from domain.world import World + + +class RandomCatMoveCommand(Command): + def __init__(self, world: World, cat: Cat) -> None: + super().__init__() + self.world = world + self.cat = cat + + def run(self): + move_vector = (0, 0) + now = pygame.time.get_ticks() + # region cat random movement + cat = self.world.cat + if now - cat.last_tick >= cat.cooldown: + if not cat.busy: + while True: + cat.direction = randint(0, 3) + if not ( + (cat.direction == 0 and cat.y == 0) + or (cat.direction == 1 and cat.x == self.world.width - 1) + or (cat.direction == 2 and cat.y == self.world.height - 1) + or (cat.direction == 3 and cat.x == 0) + ): + break + + if cat.direction == 0: # up + if cat.busy: + move_vector = (0, -1) + cat.busy = not cat.busy + if cat.direction == 1: # right + if cat.busy: + move_vector = (1, 0) + cat.busy = not cat.busy + if cat.direction == 2: # down + if cat.busy: + move_vector = (0, 1) + cat.busy = not cat.busy + if cat.direction == 3: # left + if cat.busy: + move_vector = (-1, 0) + cat.busy = not cat.busy + cat.last_tick = pygame.time.get_ticks() + + if move_vector == (0, 0): + return + + end_x = cat.x + move_vector[0] + end_y = cat.y + move_vector[1] + + if ( + end_x > self.world.width - 1 + or end_y > self.world.height - 1 + or end_x < 0 + or end_y < 0 + ): + return + + self.world.obstacles[cat.x][cat.y].remove(cat) + cat.x = end_x + cat.y = end_y + self.world.obstacles[end_x][end_y].append(cat) + # endregion cat random movement diff --git a/domain/commands/vacuum_move_command.py b/domain/commands/vacuum_move_command.py new file mode 100644 index 0000000..b868e33 --- /dev/null +++ b/domain/commands/vacuum_move_command.py @@ -0,0 +1,34 @@ +from typing import Tuple + +from domain.commands.command import Command +from domain.entities.vacuum import Vacuum +from domain.world import World + + +class VacuumMoveCommand(Command): + def __init__( + self, world: World, vacuum: Vacuum, move_vector: Tuple[int, int] + ) -> None: + super().__init__() + self.world = world + self.vacuum = vacuum + self.dx = move_vector[0] + self.dy = move_vector[1] + + def run(self): + end_x = self.vacuum.x + self.dx + end_y = self.vacuum.y + self.dy + + if ( + end_x > self.world.width - 1 + or end_y > self.world.height - 1 + or end_x < 0 + or end_y < 0 + ): + return + + if self.world.is_obstacle_at(end_x, end_y): + return + + self.vacuum.x = end_x + self.vacuum.y = end_y diff --git a/domain/entities/cat.py b/domain/entities/cat.py new file mode 100644 index 0000000..807e490 --- /dev/null +++ b/domain/entities/cat.py @@ -0,0 +1,14 @@ +import pygame + +from domain.entities.entity import Entity +from domain.world import World + + +class Cat(Entity): + def __init__(self, x: int, y: int): + super().__init__(x, y, "CAT") + self.last_tick = pygame.time.get_ticks() + self.cooldown = 1000 + self.velocity = 1 + self.busy = False + self.direction = 0 diff --git a/domain/entities/entity.py b/domain/entities/entity.py new file mode 100644 index 0000000..54f0223 --- /dev/null +++ b/domain/entities/entity.py @@ -0,0 +1,5 @@ +class Entity: + def __init__(self, x: int, y: int, type: str): + self.x = x + self.y = y + self.type = type diff --git a/domain/entities/vacuum.py b/domain/entities/vacuum.py new file mode 100644 index 0000000..eaa14e5 --- /dev/null +++ b/domain/entities/vacuum.py @@ -0,0 +1,9 @@ +from domain.entities.entity import Entity +from domain.world import World + + +class Vacuum(Entity): + def __init__(self, x: int, y: int): + super().__init__(x, y, "VACUUM") + self.battery = 100 + # TODO add more properties diff --git a/domain/world.py b/domain/world.py new file mode 100644 index 0000000..e6d10b5 --- /dev/null +++ b/domain/world.py @@ -0,0 +1,26 @@ +from domain.entities.entity import Entity + + +class World: + def __init__(self, width: int, height: int): + self.width = width + self.height = height + self.dust = [[[] for j in range(height)] for i in range(width)] + self.obstacles = [[[] for j in range(height)] for i in range(width)] + + self.vacuum = None + self.cat = None + + def add_entity(self, entity: Entity): + if entity.type == "PEEL": + self.dust[entity.x][entity.y].append(entity) + elif entity.type == "VACUUM": + self.vacuum = entity + elif entity.type == "CAT": + self.cat = entity + self.obstacles[entity.x][entity.y].append(entity) + else: + self.obstacles[entity.x][entity.y].append(entity) + + def is_obstacle_at(self, x: int, y: int) -> bool: + return bool(self.obstacles[x][y]) diff --git a/main.py b/main.py index a9f53f8..52690b9 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,90 @@ -from Interface.vacuum_render import initial_draw +from random import randint -initial_draw(500, 10) +import pygame + +from domain.commands.random_cat_move_command import RandomCatMoveCommand +from domain.commands.vacuum_move_command import VacuumMoveCommand +from domain.entities.cat import Cat +from domain.entities.entity import Entity +from domain.entities.vacuum import Vacuum +from domain.world import World +from view.renderer import Renderer + + +# initial_draw(500, 10) + + +class Main: + def __init__(self): + tiles_x = 10 + tiles_y = 10 + + self.renderer = Renderer(800, 800, tiles_x, tiles_y) + + self.world = generate_world(tiles_x, tiles_y) + + self.commands = [] + + self.clock = pygame.time.Clock() + self.running = True + self.fps = 60 + + def run(self): + while self.running: + self.process_input() + self.update() + self.renderer.render(self.world) + self.clock.tick(self.fps) + + pygame.quit() + + def process_input(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + self.running = False + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_LEFT: + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (-1, 0)) + ) + if event.key == pygame.K_RIGHT: + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (1, 0)) + ) + if event.key == pygame.K_UP: + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (0, -1)) + ) + if event.key == pygame.K_DOWN: + self.commands.append( + VacuumMoveCommand(self.world, self.world.vacuum, (0, 1)) + ) + + def update(self): + self.commands.append(RandomCatMoveCommand(self.world, self.world.cat)) + + for command in self.commands: + command.run() + self.commands.clear() + + +def generate_world(tiles_x: int, tiles_y: int) -> World: + world = World(tiles_x, tiles_y) + for _ in range(10): + temp_x = randint(0, tiles_x - 1) + temp_y = randint(0, tiles_y - 1) + world.add_entity(Entity(temp_x, temp_y, "PEEL")) + world.vacuum = Vacuum(1, 1) + world.cat = Cat(7, 8) + world.add_entity(world.cat) + world.add_entity(Entity(2, 8, "PLANT1")) + world.add_entity(Entity(4, 1, "PLANT1")) + world.add_entity(Entity(3, 4, "PLANT2")) + world.add_entity(Entity(8, 8, "PLANT2")) + world.add_entity(Entity(9, 3, "PLANT3")) + return world + + +if __name__ == "__main__": + app = Main() + app.run() diff --git a/Interface/images/cat/standing_back.png b/media/sprites/cat/standing_back.png similarity index 100% rename from Interface/images/cat/standing_back.png rename to media/sprites/cat/standing_back.png diff --git a/Interface/images/cat/standing_front.png b/media/sprites/cat/standing_front.png similarity index 100% rename from Interface/images/cat/standing_front.png rename to media/sprites/cat/standing_front.png diff --git a/Interface/images/cat/standing_left.png b/media/sprites/cat/standing_left.png similarity index 100% rename from Interface/images/cat/standing_left.png rename to media/sprites/cat/standing_left.png diff --git a/Interface/images/cat/standing_right.png b/media/sprites/cat/standing_right.png similarity index 100% rename from Interface/images/cat/standing_right.png rename to media/sprites/cat/standing_right.png diff --git a/media/sprites/peel.webp b/media/sprites/peel.webp new file mode 100644 index 0000000..b654972 Binary files /dev/null and b/media/sprites/peel.webp differ diff --git a/Interface/images/plants/plant1.png b/media/sprites/plants/plant1.png similarity index 100% rename from Interface/images/plants/plant1.png rename to media/sprites/plants/plant1.png diff --git a/Interface/images/plants/plant2.png b/media/sprites/plants/plant2.png similarity index 100% rename from Interface/images/plants/plant2.png rename to media/sprites/plants/plant2.png diff --git a/Interface/images/plants/plant3.png b/media/sprites/plants/plant3.png similarity index 100% rename from Interface/images/plants/plant3.png rename to media/sprites/plants/plant3.png diff --git a/media/sprites/tile.jpeg b/media/sprites/tile.jpeg new file mode 100644 index 0000000..b3243aa Binary files /dev/null and b/media/sprites/tile.jpeg differ diff --git a/media/sprites/tile_cropped.jpeg b/media/sprites/tile_cropped.jpeg new file mode 100644 index 0000000..36d67ea Binary files /dev/null and b/media/sprites/tile_cropped.jpeg differ diff --git a/media/sprites/vacuum.png b/media/sprites/vacuum.png new file mode 100644 index 0000000..977e72c Binary files /dev/null and b/media/sprites/vacuum.png differ diff --git a/media/sprites/wall.png b/media/sprites/wall.png new file mode 100644 index 0000000..f9560c6 Binary files /dev/null and b/media/sprites/wall.png differ diff --git a/view/renderer.py b/view/renderer.py new file mode 100644 index 0000000..bfe1dde --- /dev/null +++ b/view/renderer.py @@ -0,0 +1,113 @@ +import random +from random import randint + +import pygame +from pygame import Color + +from domain.entities.cat import Cat +from domain.entities.entity import Entity +from domain.world import World + + +class Renderer: + + def __init__( + self, + width=800, + height=800, + tiles_x=10, + tiles_y=10, + ): + self.width = width + self.height = height + + self.tiles_x = tiles_x + self.tiles_y = tiles_y + + self.tile_width = self.width / self.tiles_x + self.tile_height = self.height / self.tiles_y + + pygame.init() + + pygame.display.set_caption("AI Vacuum Cleaner") + self.screen = pygame.display.set_mode((self.width, self.height)) + + self.sprites = { + "VACUUM": pygame.transform.scale(pygame.image.load("media/sprites/vacuum.png"), + (self.tile_width, self.tile_height)), + "WALL": pygame.transform.scale(pygame.image.load("media/sprites/wall.png"), + (self.tile_width, self.tile_height)), + "TILE": pygame.transform.scale(pygame.image.load("media/sprites/tile_cropped.jpeg"), + (self.tile_width, self.tile_height)), + "PEEL": pygame.transform.scale(pygame.image.load("media/sprites/peel.webp"), + (self.tile_width, self.tile_height)), + "CAT_FRONT": pygame.transform.scale(pygame.image.load("media/sprites/cat/standing_front.png"), + (self.tile_width, self.tile_height)), + "CAT_BACK": pygame.transform.scale(pygame.image.load("media/sprites/cat/standing_back.png"), + (self.tile_width, self.tile_height)), + "CAT_LEFT": pygame.transform.scale(pygame.image.load("media/sprites/cat/standing_left.png"), + (self.tile_width, self.tile_height)), + "CAT_RIGHT": pygame.transform.scale(pygame.image.load("media/sprites/cat/standing_right.png"), + (self.tile_width, self.tile_height)), + "PLANT1": pygame.transform.scale(pygame.image.load("media/sprites/plants/plant1.png"), + (self.tile_width + self.tile_width / 4, self.tile_height + self.tile_height / 4)), + "PLANT2": pygame.transform.scale(pygame.image.load("media/sprites/plants/plant2.png"), + (self.tile_width + self.tile_width / 4, self.tile_height + self.tile_height / 4)), + "PLANT3": pygame.transform.scale(pygame.image.load("media/sprites/plants/plant3.png"), + (self.tile_width + self.tile_width / 4, self.tile_height + self.tile_height / 4)), + } + + self.cat_direction_sprite = { + 0: self.sprites["CAT_BACK"], + 1: self.sprites["CAT_RIGHT"], + 2: self.sprites["CAT_FRONT"], + 3: self.sprites["CAT_LEFT"], + } + + def render(self, world: World): + self.render_floor() + self.render_board() + for x in range(world.width): + for y in range(world.height): + for entity in world.dust[x][y]: + self.draw_entity(entity) + for x in range(world.width): + for y in range(world.height): + for entity in world.obstacles[x][y]: + self.draw_entity(entity) + self.draw_entity(world.vacuum) + self.draw_entity(world.cat) + pygame.display.update() + + def line(self, x_1, y_1, x_2, y_2, color=None): + pygame.draw.line(self.screen, color, (x_1, y_1), (x_2, y_2)) + + def render_board(self, color=Color("black")): + for i in range(1, self.tiles_x): + self.line(self.tile_width * i, 0, self.tile_width * i, self.height, color=color) + + for i in range(1, self.tiles_y): + self.line(0, self.tile_height * i, self.width, self.tile_height * i, color=color) + + def draw_entity(self, entity: Entity): + sprite = self.sprites.get(entity.type, None) + draw_pos = (entity.x * self.tile_width, entity.y * self.tile_height) + if "PLANT" in entity.type: + draw_pos = ((entity.x - 0.1) * self.tile_width, (entity.y - 0.25) * self.tile_height) + if "CAT" in entity.type and isinstance(entity, Cat): + sprite = self.cat_direction_sprite[entity.direction] + self.screen.blit(sprite, draw_pos) + + def draw_sprite(self, x: int, y: int, sprite_name: str): + self.screen.blit( + self.sprites[sprite_name], + (x * self.tile_width, y * self.tile_height) + ) + + def fill_grid_with_sprite(self, sprite): + for tile_x in range(self.tiles_x): + for tile_y in range(self.tiles_y): + self.draw_sprite(tile_x, tile_y, sprite) + + def render_floor(self): + self.fill_grid_with_sprite("TILE")