diff --git a/agent.py b/agent.py index 63183e8..cba88b2 100644 --- a/agent.py +++ b/agent.py @@ -1,5 +1,6 @@ import project_constants as const import json +from pygame import transform, Surface, SRCALPHA # Class of our agent, initialization of it @@ -7,13 +8,78 @@ import json # values that are later used by another function called 'is_valid_move' (which is defined in Minefield)); class Agent: + last_action = None + new_action = None + def __init__(self, json_path): with open(json_path) as json_data: data = json.load(json_data) self.row, self.column = data["agents_initial_state"]["position"].split(",") self.position = [int(self.row), int(self.column)] + self.on_screen_coordinates = const.get_tile_coordinates(tuple(self.position)) # self.direction = const.Direction() self.direction = const.Direction(data["agents_initial_state"]["direction"]) + self.rotation_angle = -const.Direction(self.direction).value * 90 + self.going_forward = False + self.rotating_left = False + self.rotating_right = False + + def update_and_draw(self, window, delta_time): + self.update(delta_time) + self.draw(window) + + def draw(self, window): + coord_x, coord_y = self.on_screen_coordinates[0], self.on_screen_coordinates[1] + tl_dimension = const.V_TILE_SIZE / 2 + + rotating_rect = transform.rotate(const.ASSET_SAPPER, self.rotation_angle).get_rect() + rotating_rect.center = (coord_x + tl_dimension, coord_y + tl_dimension) + + window.blit(transform.rotate(const.ASSET_SAPPER, self.rotation_angle), rotating_rect) + + def update(self, delta_time): + self.new_action = self.going_forward + self.rotating_left * 2 + self.rotating_right * 4 + + if self.going_forward: + direction = const.Direction(self.direction).value + x, y = self.on_screen_coordinates + + # heading either up or down + if direction % 2 == 0: + self.on_screen_coordinates = (x, y + (direction - 1) * const.V_TILE_SIZE * delta_time) + + # heading either left or right + else: + self.on_screen_coordinates = (x - (direction - 2) * const.V_TILE_SIZE * delta_time, y) + + elif self.rotating_right: + self.rotation_angle -= delta_time * 90 % 360 + + elif self.rotating_left: + self.rotation_angle += delta_time * 90 % 360 + + if self.last_action != self.new_action or \ + not any((self.going_forward, self.rotating_right, self.rotating_left)): + self.last_action = self.new_action + self.rotation_angle = -const.Direction(self.direction).value * 90 + self.on_screen_coordinates = const.get_tile_coordinates(tuple(self.position)) + + def animate(self, action): + self.reset_actions() + if action == const.Action.ROTATE_LEFT: + self.rotating_left = True + elif action == const.Action.ROTATE_RIGHT: + self.rotating_right = True + elif action == const.Action.GO: + self.going_forward = True + + def take_action(self, action): + if action == const.Action.ROTATE_LEFT: + self.rotate_left() + elif action == const.Action.ROTATE_RIGHT: + self.rotate_right() + elif action == const.Action.GO: + self.go() def rotate_left(self): self.direction = self.direction.previous() @@ -50,3 +116,6 @@ class Agent: def go_down(self): return self.position[0] + 1, self.position[1] + + def reset_actions(self): + self.going_forward = self.rotating_right = self.rotating_left = False diff --git a/display_assets.py b/display_assets.py index 2dc302e..72060e1 100644 --- a/display_assets.py +++ b/display_assets.py @@ -69,14 +69,15 @@ def blit_graphics(minefield): elif isinstance(tile.mine, TimeMine): display_time_mine(tile.position, "0" + str(tile.mine.timer)) - # sapper - display_sapper( - minefield.agent.position, - minefield.agent.direction - ) + # changed sapper's movement from jumping to continuous movement (moved everything to Agent's class) + # # sapper + # display_sapper( + # minefield.agent.position, + # minefield.agent.direction + # ) # update graphics after blitting - pygame.display.update() + # pygame.display.update() # moved here from minefield @@ -216,12 +217,12 @@ def display_chained_mine(coords, parent_coords=None): display_mine(coords) - if parent_coords is not None: - const.SCREEN.blit( - const.ASSET_CHAINS, - calculate_screen_position(coords) - ) + const.SCREEN.blit( + const.ASSET_CHAINS, + calculate_screen_position(coords) + ) + if parent_coords is not None: display_number(const.Coords.X, parent_coords[0]) display_number(const.Coords.Y, parent_coords[1]) diff --git a/main.py b/main.py index af8420b..4f398e5 100644 --- a/main.py +++ b/main.py @@ -27,10 +27,14 @@ def main(): # create an instance of Minefield, pass necessary data minefield = mf.Minefield(const.MAP_RANDOM_10x10) + # getting agent's instance + agent = minefield.agent + # setting flags for program running = True in_menu = True + # creating list storing all ui components ui_components_list = UiComponentsList([INPUT_ROW, INPUT_COLUMN, RANDOM_BUTTON, OK_BUTTON]) # initializing goal position @@ -41,6 +45,10 @@ def main(): while running: + # ============== # + # ==== MENU ==== # + # ============== # + while running and in_menu: events = pygame.event.get() @@ -51,11 +59,10 @@ def main(): # graphics (from display_assets) blit_graphics(minefield) + agent.update_and_draw(const.SCREEN, 0) + # drawing gui - INPUT_ROW.run(const.SCREEN, pygame.mouse.get_pos(), events) - INPUT_COLUMN.run(const.SCREEN, pygame.mouse.get_pos(), events) - OK_BUTTON.draw(const.SCREEN, pygame.mouse.get_pos()) - RANDOM_BUTTON.draw(const.SCREEN, pygame.mouse.get_pos()) + ui_components_list.run_all(const.SCREEN, pygame.mouse.get_pos(), events) # highlighting chosen tile destination (if exists) if not(INPUT_ROW.empty() or INPUT_COLUMN.empty()): @@ -79,6 +86,10 @@ def main(): clock.tick(const.V_FPS) + # ========================== # + # ==== BEFORE GAME LOOP ==== # + # ========================== # + if running: for component in ui_components_list.selectable_ui_components: component.set_flags(is_active=False) @@ -90,6 +101,13 @@ def main(): direction=minefield.agent.direction), minefield=minefield, tox=row, toy=column) + action = None + in_game_timer = 0 + + # =================== # + # ==== GAME LOOP ==== # + # =================== # + while running and not in_menu: for event in pygame.event.get(): @@ -98,11 +116,15 @@ def main(): # FPS control clock.tick(const.V_FPS) + delta_time = clock.get_time() / 1000 + action_delta_time = delta_time / const.ACTION_INTERVAL # graphics (from display_assets) blit_graphics(minefield) const.SCREEN.blit(HIGHLIGHT, const.get_tile_coordinates((row, column))) + agent.update_and_draw(const.SCREEN, action_delta_time) + # drawing ui components so they don't "disappear" ui_components_list.draw_all(const.SCREEN, pygame.mouse.get_pos()) @@ -110,27 +132,31 @@ def main(): pygame.display.flip() # make the next move from sequence of actions - if any(action_sequence): + if in_game_timer >= const.ACTION_INTERVAL and any(action_sequence): + in_game_timer -= const.ACTION_INTERVAL + minefield.next_turn() + + agent.take_action(action) + action = action_sequence.pop(0) - if action == const.Action.ROTATE_LEFT: - minefield.agent.rotate_left() - elif action == const.Action.ROTATE_RIGHT: - minefield.agent.rotate_right() - elif action == const.Action.GO: - minefield.agent.go() + agent.animate(action) - time.sleep(const.ACTION_INTERVAL) + elif in_game_timer >= const.ACTION_INTERVAL: + agent.take_action(action) + + agent.update_and_draw(const.SCREEN, delta_time) + agent.reset_actions() - else: in_menu = True if not any([x.is_selected for x in ui_components_list.ui_components]): INPUT_ROW.set_is_selected(True) - time.sleep(const.ACTION_INTERVAL) for component in ui_components_list.selectable_ui_components: component.set_flags(is_active=True) + in_game_timer += delta_time + if __name__ == "__main__": main() diff --git a/mine_models/time_mine.py b/mine_models/time_mine.py index 52e6a8d..3f0a11e 100644 --- a/mine_models/time_mine.py +++ b/mine_models/time_mine.py @@ -5,6 +5,7 @@ class TimeMine(Mine): def __init__(self, position, timer, active=True): self.type = "time" self.timer = timer + self.starting_time = timer super().__init__(position, active) def disarm(self): diff --git a/minefield.py b/minefield.py index 2b1f072..f039428 100644 --- a/minefield.py +++ b/minefield.py @@ -1,9 +1,7 @@ import agent as ag import project_constants as const import tile as tl -from mine_models import standard_mine as sm -from mine_models import time_mine as tm -from mine_models import chained_mine as cm +from mine_models.time_mine import TimeMine import json_generator as jg @@ -37,6 +35,16 @@ class Minefield: predecessor = self.matrix[predecessor_row][predecessor_column] self.matrix[successor_row][successor_column].mine.predecessor = predecessor + def next_turn(self): + self.turn += 1 + + for row in range(const.V_GRID_VER_TILES): + for column in range(const.V_GRID_VER_TILES): + mine = self.matrix[row][column].mine + + if mine is not None and isinstance(mine, TimeMine): + mine.timer = max(0, mine.starting_time - self.turn) + # ================ # # === MOVEMENT === # # ================ # @@ -50,31 +58,3 @@ class Minefield: return True return False - - # distinguishes new mine's type and creates appropriate object - def _create_mine(self, mine_data, row, column): - mine_type = mine_data["mine_type"] - - # TIME MINE - if mine_type == "time": - timer = mine_data["timer"] - mine = tm.TimeMine((row, column), int(timer)) - - # CHAINED MINE - elif mine_type == "chained": - if mine_data["predecessor"] is not None: - # locate predecessor - row, column = map(int, mine_data["predecessor"].split(',')) - - # get predecessor object and assign it to the new mine - predecessor = self.matrix[row][column].mine - mine = cm.ChainedMine((row, column), predecessor) - - else: - mine = cm.ChainedMine((row, column)) - - # STANDARD MINE - else: - mine = sm.StandardMine((row, column)) - - return mine diff --git a/project_constants.py b/project_constants.py index f11c4a1..e876235 100644 --- a/project_constants.py +++ b/project_constants.py @@ -22,7 +22,7 @@ V_NAME_OF_WINDOW = "MineFusion TM" DIR_ASSETS = os.path.join("resources", "assets") V_FPS = 60 -ACTION_INTERVAL = 0.75 # interval between two actions in seconds +ACTION_INTERVAL = 0.6 # interval between two actions in seconds V_TILE_SIZE = 60 V_GRID_VER_TILES = 10 # vertical (number of rows) @@ -164,7 +164,7 @@ INPUT_ROW = InputBox( position=(_gui_x, _gui_y), dimensions=(_gui_width, _ib_height), text="row", - valid_input_characters="123456890", + valid_input_characters="1234567890", input_centered=True, clear_input_on_click=True ) diff --git a/ui/ui_components_list.py b/ui/ui_components_list.py index 433c821..2a07b51 100644 --- a/ui/ui_components_list.py +++ b/ui/ui_components_list.py @@ -1,5 +1,6 @@ import pygame from ui.button import Button +from ui.input_box import InputBox class UiComponentsList: @@ -18,6 +19,16 @@ class UiComponentsList: self.selectable_ui_components[0].set_is_selected(select_first_item) + def run_all(self, window, mouse_position, events): + for component in filter(lambda x: x not in self.selectable_ui_components, self.ui_components): + component.draw(window) + + for component in filter(lambda x: isinstance(x, Button), self.selectable_ui_components): + component.draw(window, mouse_position) + + for component in filter(lambda x: isinstance(x, InputBox), self.selectable_ui_components): + component.run(window, mouse_position, events) + def draw_all(self, window, mouse_position): for component in filter(lambda x: x not in self.selectable_ui_components, self.ui_components): component.draw(window)