added agent movement animations; redefined game loop; added turn counting; adjusted classes; fixed a bug; code refactoring

This commit is contained in:
JakubR 2021-05-12 14:33:26 +02:00
parent 0fc5cedae6
commit af238a6ed6
7 changed files with 146 additions and 58 deletions

View File

@ -1,5 +1,6 @@
import project_constants as const import project_constants as const
import json import json
from pygame import transform, Surface, SRCALPHA
# Class of our agent, initialization of it # 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)); # values that are later used by another function called 'is_valid_move' (which is defined in Minefield));
class Agent: class Agent:
last_action = None
new_action = None
def __init__(self, json_path): def __init__(self, json_path):
with open(json_path) as json_data: with open(json_path) as json_data:
data = json.load(json_data) data = json.load(json_data)
self.row, self.column = data["agents_initial_state"]["position"].split(",") self.row, self.column = data["agents_initial_state"]["position"].split(",")
self.position = [int(self.row), int(self.column)] 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()
self.direction = const.Direction(data["agents_initial_state"]["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): def rotate_left(self):
self.direction = self.direction.previous() self.direction = self.direction.previous()
@ -50,3 +116,6 @@ class Agent:
def go_down(self): def go_down(self):
return self.position[0] + 1, self.position[1] return self.position[0] + 1, self.position[1]
def reset_actions(self):
self.going_forward = self.rotating_right = self.rotating_left = False

View File

@ -69,14 +69,15 @@ def blit_graphics(minefield):
elif isinstance(tile.mine, TimeMine): elif isinstance(tile.mine, TimeMine):
display_time_mine(tile.position, "0" + str(tile.mine.timer)) display_time_mine(tile.position, "0" + str(tile.mine.timer))
# sapper # changed sapper's movement from jumping to continuous movement (moved everything to Agent's class)
display_sapper( # # sapper
minefield.agent.position, # display_sapper(
minefield.agent.direction # minefield.agent.position,
) # minefield.agent.direction
# )
# update graphics after blitting # update graphics after blitting
pygame.display.update() # pygame.display.update()
# moved here from minefield # moved here from minefield
@ -216,12 +217,12 @@ def display_chained_mine(coords, parent_coords=None):
display_mine(coords) display_mine(coords)
if parent_coords is not None: const.SCREEN.blit(
const.SCREEN.blit( const.ASSET_CHAINS,
const.ASSET_CHAINS, calculate_screen_position(coords)
calculate_screen_position(coords) )
)
if parent_coords is not None:
display_number(const.Coords.X, parent_coords[0]) display_number(const.Coords.X, parent_coords[0])
display_number(const.Coords.Y, parent_coords[1]) display_number(const.Coords.Y, parent_coords[1])

54
main.py
View File

@ -27,10 +27,14 @@ def main():
# create an instance of Minefield, pass necessary data # create an instance of Minefield, pass necessary data
minefield = mf.Minefield(const.MAP_RANDOM_10x10) minefield = mf.Minefield(const.MAP_RANDOM_10x10)
# getting agent's instance
agent = minefield.agent
# setting flags for program # setting flags for program
running = True running = True
in_menu = True in_menu = True
# creating list storing all ui components
ui_components_list = UiComponentsList([INPUT_ROW, INPUT_COLUMN, RANDOM_BUTTON, OK_BUTTON]) ui_components_list = UiComponentsList([INPUT_ROW, INPUT_COLUMN, RANDOM_BUTTON, OK_BUTTON])
# initializing goal position # initializing goal position
@ -41,6 +45,10 @@ def main():
while running: while running:
# ============== #
# ==== MENU ==== #
# ============== #
while running and in_menu: while running and in_menu:
events = pygame.event.get() events = pygame.event.get()
@ -51,11 +59,10 @@ def main():
# graphics (from display_assets) # graphics (from display_assets)
blit_graphics(minefield) blit_graphics(minefield)
agent.update_and_draw(const.SCREEN, 0)
# drawing gui # drawing gui
INPUT_ROW.run(const.SCREEN, pygame.mouse.get_pos(), events) ui_components_list.run_all(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())
# highlighting chosen tile destination (if exists) # highlighting chosen tile destination (if exists)
if not(INPUT_ROW.empty() or INPUT_COLUMN.empty()): if not(INPUT_ROW.empty() or INPUT_COLUMN.empty()):
@ -79,6 +86,10 @@ def main():
clock.tick(const.V_FPS) clock.tick(const.V_FPS)
# ========================== #
# ==== BEFORE GAME LOOP ==== #
# ========================== #
if running: if running:
for component in ui_components_list.selectable_ui_components: for component in ui_components_list.selectable_ui_components:
component.set_flags(is_active=False) component.set_flags(is_active=False)
@ -90,6 +101,13 @@ def main():
direction=minefield.agent.direction), direction=minefield.agent.direction),
minefield=minefield, tox=row, toy=column) minefield=minefield, tox=row, toy=column)
action = None
in_game_timer = 0
# =================== #
# ==== GAME LOOP ==== #
# =================== #
while running and not in_menu: while running and not in_menu:
for event in pygame.event.get(): for event in pygame.event.get():
@ -98,11 +116,15 @@ def main():
# FPS control # FPS control
clock.tick(const.V_FPS) clock.tick(const.V_FPS)
delta_time = clock.get_time() / 1000
action_delta_time = delta_time / const.ACTION_INTERVAL
# graphics (from display_assets) # graphics (from display_assets)
blit_graphics(minefield) blit_graphics(minefield)
const.SCREEN.blit(HIGHLIGHT, const.get_tile_coordinates((row, column))) 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" # drawing ui components so they don't "disappear"
ui_components_list.draw_all(const.SCREEN, pygame.mouse.get_pos()) ui_components_list.draw_all(const.SCREEN, pygame.mouse.get_pos())
@ -110,27 +132,31 @@ def main():
pygame.display.flip() pygame.display.flip()
# make the next move from sequence of actions # 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) action = action_sequence.pop(0)
if action == const.Action.ROTATE_LEFT: agent.animate(action)
minefield.agent.rotate_left()
elif action == const.Action.ROTATE_RIGHT:
minefield.agent.rotate_right()
elif action == const.Action.GO:
minefield.agent.go()
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 in_menu = True
if not any([x.is_selected for x in ui_components_list.ui_components]): if not any([x.is_selected for x in ui_components_list.ui_components]):
INPUT_ROW.set_is_selected(True) INPUT_ROW.set_is_selected(True)
time.sleep(const.ACTION_INTERVAL)
for component in ui_components_list.selectable_ui_components: for component in ui_components_list.selectable_ui_components:
component.set_flags(is_active=True) component.set_flags(is_active=True)
in_game_timer += delta_time
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -5,6 +5,7 @@ class TimeMine(Mine):
def __init__(self, position, timer, active=True): def __init__(self, position, timer, active=True):
self.type = "time" self.type = "time"
self.timer = timer self.timer = timer
self.starting_time = timer
super().__init__(position, active) super().__init__(position, active)
def disarm(self): def disarm(self):

View File

@ -1,9 +1,7 @@
import agent as ag import agent as ag
import project_constants as const import project_constants as const
import tile as tl import tile as tl
from mine_models import standard_mine as sm from mine_models.time_mine import TimeMine
from mine_models import time_mine as tm
from mine_models import chained_mine as cm
import json_generator as jg import json_generator as jg
@ -37,6 +35,16 @@ class Minefield:
predecessor = self.matrix[predecessor_row][predecessor_column] predecessor = self.matrix[predecessor_row][predecessor_column]
self.matrix[successor_row][successor_column].mine.predecessor = predecessor 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 === # # === MOVEMENT === #
# ================ # # ================ #
@ -50,31 +58,3 @@ class Minefield:
return True return True
return False 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

View File

@ -22,7 +22,7 @@ V_NAME_OF_WINDOW = "MineFusion TM"
DIR_ASSETS = os.path.join("resources", "assets") DIR_ASSETS = os.path.join("resources", "assets")
V_FPS = 60 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_TILE_SIZE = 60
V_GRID_VER_TILES = 10 # vertical (number of rows) V_GRID_VER_TILES = 10 # vertical (number of rows)
@ -164,7 +164,7 @@ INPUT_ROW = InputBox(
position=(_gui_x, _gui_y), position=(_gui_x, _gui_y),
dimensions=(_gui_width, _ib_height), dimensions=(_gui_width, _ib_height),
text="row", text="row",
valid_input_characters="123456890", valid_input_characters="1234567890",
input_centered=True, input_centered=True,
clear_input_on_click=True clear_input_on_click=True
) )

View File

@ -1,5 +1,6 @@
import pygame import pygame
from ui.button import Button from ui.button import Button
from ui.input_box import InputBox
class UiComponentsList: class UiComponentsList:
@ -18,6 +19,16 @@ class UiComponentsList:
self.selectable_ui_components[0].set_is_selected(select_first_item) 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): def draw_all(self, window, mouse_position):
for component in filter(lambda x: x not in self.selectable_ui_components, self.ui_components): for component in filter(lambda x: x not in self.selectable_ui_components, self.ui_components):
component.draw(window) component.draw(window)