# libraries import pygame import numpy as np import json # project modules from constants import * from player import * from new_gui import * # ------------------------------------------------------------------------------- # Enum and Tables # ------------------------------------------------------------------------------- class TileTypes(Enum): FLOOR = 0 WALL = 1 RED_PLATE = 2 RED_DOOR = 3 ORANGE_PLATE = 4 ORANGE_DOOR = 5 TEAL_PLATE = 6 TEAL_DOOR = 7 BLUE_ARRIVE = 8 GREEN_ARRIVE = 9 TILE_FILES = { TileTypes.FLOOR: "assets/floor.svg", TileTypes.WALL : "assets/wall.svg", TileTypes.RED_PLATE : "assets/red_press.svg", TileTypes.RED_DOOR : "assets/red_door.svg", TileTypes.ORANGE_PLATE : "assets/orange_press.svg", TileTypes.ORANGE_DOOR : "assets/orange_door.svg", TileTypes.TEAL_PLATE : "assets/teal_press.svg", TileTypes.TEAL_DOOR : "assets/teal_door.svg", TileTypes.BLUE_ARRIVE : "assets/blue_arrive.svg", TileTypes.GREEN_ARRIVE : "assets/green_arrive.svg" } class GamePanel(Enum): BLUE_PANE = 0 BLUE_TEXT = 1 GREEN_TEXT = 2 GREEN_PANE = 3 TIMER = 4 PANEL_FILES = { GamePanel.BLUE_PANE: "assets/blue_pane.png", GamePanel.BLUE_TEXT: "assets/blue_text.png", GamePanel.GREEN_PANE: "assets/green_pane.png", GamePanel.GREEN_TEXT: "assets/green_text.png", GamePanel.TIMER: "assets/timer.png" } class Direction(Enum): HORIZ = 0 VERT = 1 # ------------------------------------------------------------------------------- # Class Level # - represents the static data of the current level being played # ------------------------------------------------------------------------------- class Level: def __init__(self, path): # Saving the order number of the level self.level_number = int(path.name.split("_")[0]) # Level data file loading with open(path, "r") as file: level_data = json.load(file) self.map = np.array(level_data["map"]) self.id = level_data["id"] # LOADING TILE IMAGES target_size = (TILE_SIZE, TILE_SIZE) self.floor_tile = self.load_tile(TileTypes.FLOOR ,target_size) self.wall_tile = self.load_tile(TileTypes.WALL ,target_size) self.red_press = self.load_tile(TileTypes.RED_PLATE ,target_size) self.red_door = self.load_tile(TileTypes.RED_DOOR ,target_size) self.orange_press = self.load_tile(TileTypes.ORANGE_PLATE ,target_size) self.orange_door = self.load_tile(TileTypes.ORANGE_DOOR ,target_size) self.teal_press = self.load_tile(TileTypes.TEAL_PLATE ,target_size) self.teal_door = self.load_tile(TileTypes.TEAL_DOOR ,target_size) self.blue_arrive = self.load_tile(TileTypes.BLUE_ARRIVE ,target_size) self.green_arrive = self.load_tile(TileTypes.GREEN_ARRIVE ,target_size) self.doors, self.plates = self.load_objs() # For now this method does this with the map, in the future they will be encoded differently in the json # maybe already in arrays to make everything easier def load_objs(self): doors = {} plates = {} for (row, col), character in np.ndenumerate(self.map): if character in ('R'): # Todo: choose type based on character doors[character] = Door(col, row, TileTypes.RED_DOOR) if character.lower() in plates: plates[character.lower()].set_door(doors[character]) if character in ('r'): # Todo: choose type based on character plates[character] = PressurePlate(col, row, TileTypes.RED_PLATE) if character.upper() in doors: plates[character].set_door(doors[character.upper()]) #for (row, col), character in np.ndenumerate(self.map): # if character == 'r': # plates.append(PressurePlate(col, row, TileTypes.RED_PLATE)) return doors, plates def load_tile(self, type, target_size): return pygame.transform.smoothscale(pygame.image.load(TILE_FILES.get(type, TILE_FILES[TileTypes.FLOOR])).convert_alpha(), target_size) def draw(self, screen, pressing): for (row, col), character in np.ndenumerate(self.map): # Convert grid coordinates to pixel coordinates x = col * TILE_SIZE + X_OFFSET y = row * TILE_SIZE + Y_OFFSET # Create the rectangle boundary for the tile rect = pygame.Rect(x, y, TILE_SIZE, TILE_SIZE) # Draw the appropriate tile color if character == '#': screen.blit(self.wall_tile, rect) # elif character == 'R' and pressing != "red": # screen.blit(self.red_door, rect) # elif character == 'r': # screen.blit(self.red_press, rect) elif character == 'B': screen.blit(self.blue_arrive, rect) elif character == 'G': screen.blit(self.green_arrive, rect) else: screen.blit(self.floor_tile, rect) for element in list(self.doors.values()) + list(self.plates.values()): element.draw(screen) # ------------------------------------------------------------------------------- # Class LevelState # - represents the state of the current level being played # - it manages all the gameplay logic and mechanics # ------------------------------------------------------------------------------- class LevelState: def __init__(self, level): self.level = level # Players setup b_rows, b_cols = np.where(level.map == 'b') g_rows, g_cols = np.where(level.map == 'g') blue_y, blue_x = b_rows[0], b_cols[0] green_y, green_x = g_rows[0], g_cols[0] self.players = [Player(blue_x, blue_y, True, PlayerTypes.BLUE), Player(green_x, green_y, False, PlayerTypes.GREEN)] self.curr_player_i = 0 self.player = self.players[self.curr_player_i] # Sprite group for ease self.all_sprites = pygame.sprite.Group() self.all_sprites.add(self.players) # state variables self.pressing = "none" self.blue_arrive = False self.green_arrive = False # Completion and final time self.completed = False self.final_time = 0 # GUI ELEMENTS self.blue_pane = self.load_gui(GamePanel.BLUE_PANE) self.green_pane = self.load_gui(GamePanel.GREEN_PANE) self.blue_text = self.load_gui(GamePanel.BLUE_TEXT) self.green_text = self.load_gui(GamePanel.GREEN_TEXT) self.timer = self.load_gui(GamePanel.TIMER) def load_gui(self, element): return pygame.image.load(PANEL_FILES.get(element, None)).convert_alpha() def change_player(self): self.player.active = False self.curr_player_i = 1 - self.curr_player_i self.player = self.players[self.curr_player_i] self.player.active = True def try_move(self, dir, x, y, speed): if dir == Direction.HORIZ: next_x = x + speed if (self.level.map[y, next_x] == '#' or (self.level.map[y, next_x] == 'R' and self.pressing != "red") or (next_x == self.players[1-self.curr_player_i].x and y == self.players[1-self.curr_player_i].y)): return x return next_x else: next_y = y + speed if (self.level.map[next_y, x] == '#' or (self.level.map[next_y, x] == 'R' and self.pressing != "red") or (next_y == self.players[1-self.curr_player_i].y and x == self.players[1-self.curr_player_i].x)): return y return next_y def update(self): # to facilitate usage p1 = self.players[0] p2 = self.players[1] # check for pressure on plates # TODO: Add the other types of plates to press if (self.level.map[p1.y, p1.x] == "r" and p1.powers[p1.current_power_i] == Powers.PRESS.value) or \ (self.level.map[p2.y, p2.x] == "r" and p2.powers[p2.current_power_i] == Powers.PRESS.value): self.pressing = "red" else: self.pressing = "none" for plate in self.level.plates.values(): if((p1.rect.x, p1.rect.y) == (plate.x, plate.y) and p1.powers[p1.current_power_i] == Powers.PRESS.value) or ((p2.rect.x, p2.rect.y) == (plate.x, plate.y) and p2.powers[p2.current_power_i] == Powers.PRESS.value): plate.set_pressed(True) else: plate.set_pressed(False) # check for arrival self.blue_arrive = self.level.map[p1.y, p1.x] == "B" self.green_arrive = self.level.map[p2.y, p2.x] == "G" # Check completed condition if(self.blue_arrive and self.green_arrive): self.completed = True def draw(self, screen): self.level.draw(screen, self.pressing) def draw_gui(self, screen, time, font): y = Y_OFFSET # Blue pane, then blue text box with the player's power y = self._blit_panel(screen, self.blue_pane, y) y = self._blit_panel(screen, self.blue_text, y, self.players[0].current_power, BLUE, font) # Green pane, then green text box with the player's power y = self._blit_panel(screen, self.green_pane, y) y = self._blit_panel(screen, self.green_text, y, self.players[1].current_power, GREEN, font) # Timer box with the elapsed seconds self._blit_panel(screen, self.timer, y, str(time).split('.')[0], RED, font) def _blit_panel(self, screen, image, y, text=None, color=None, font=None): x = (X_OFFSET - image.get_width()) / 2 screen.blit(image, (x, y)) if text is not None: label = TextArea(text, color) label.place(x + image.get_width() / 2, y + image.get_height() / 2) label.draw(screen, None, font, anchor="center") return y + image.get_height() # --------------------------------------------------------------------- # Class MapElement # --------------------------------------------------------------------- # This is more of an abstract class that grants common method signature # and behaviour. # It inherits from Sprite so that I can add it to the group and # it already has the draw method implemented # --------------------------------------------------------------------- class MapElement(pygame.sprite.Sprite): def __init__(self, col, row, type): super().__init__() self.col = col self.row = row self.x = col * TILE_SIZE + X_OFFSET self.y = row * TILE_SIZE + Y_OFFSET self.image = self.load_image(type) self.rect = self.image.get_rect() self.rect.topleft = (self.x, self.y) def load_image(self, type): return pygame.transform.smoothscale(pygame.image.load(TILE_FILES.get(type, TILE_FILES[TileTypes.FLOOR])).convert_alpha(), (TILE_SIZE, TILE_SIZE)) def draw(self, screen): screen.blit(self.image, self.rect) # ---------------------------------------------------------------------- # Class PressurePlate # ---------------------------------------------------------------------- class PressurePlate(MapElement): def __init__(self, col, row, type): super().__init__(col, row, type) self.pressed = False self.door = None print("Plate created!") # I should do the check if the value is boolean, but I do not want to # (all of my methods do not have such checks, which is a bit ugh) def set_door(self, door): self.door = door def set_pressed(self, value): self.pressed = value if self.door is None: raise ValueError("Door is None") self.door.set_open(self.pressed) def is_pressed(self): return self.pressed # ---------------------------------------------------------------------- # Class Door # ---------------------------------------------------------------------- class Door(MapElement): def __init__(self, col, row, type): super().__init__(col, row, type) self.open = False print("Door created") def set_open(self, value): self.open = value def is_open(self): return self.open # I have to ovverride the sprite method, because I have to check if it is opened def draw(self, screen): if self.open == False: super().draw(screen) else: pass # ---------------------------------------------------------------------- # Class GrabbableObject # ---------------------------------------------------------------------- # represents objects that can be grabbed and put in inventory # they will be necessary to press certain types of pressure plates # yet to be implemented # ---------------------------------------------------------------------- class GrabbableObject(MapElement): def __init__(self, col, row, type): super().__init__(col, row, type) self.grabbed = False def grab(self): self.grabbed = True def release(self): self.grabbed = False def is_grabbed(self): return self.grabbed # I have to ovverride the sprite method, because I have to check if it is opened def draw(self, screen): if self.grabbed == False: super().draw(screen) else: pass