Skip to content
Snippets Groups Projects
Commit 030e1ee2 authored by Fabian Heinrich's avatar Fabian Heinrich
Browse files

Merge branch '59-game-time-limit' into 'main'

Resolve "Game time limit"

Closes #59

See merge request scs/cocosy/overcooked-simulator!28
parents 276cc639 75b12f44
No related branches found
No related tags found
1 merge request!28Resolve "Game time limit"
Pipeline #43943 passed
plates:
clean_plates: 3
dirty_plates: 2
plate_delay: [ 5, 10 ]
\ No newline at end of file
plate_delay: [ 5, 10 ]
game:
time_limit_seconds: 90
......@@ -44,6 +44,35 @@
},
"misc": {
"tool_tip_delay": "1.5"
},
"font": {
"size": 15,
"bold": 1
}
},
"#timer_label": {
"colours": {
"normal_text": "#000000"
},
"font": {
"size": 20,
"bold": 1
}
},
"#score_label": {
"colours": {
"normal_text": "#000000"
},
"font": {
"size": 20,
"bold": 1
}
},
"#quit_button": {
"colours": {
"normal_bg": "#f71b29",
"hovered_bg": "#bf0310",
"normal_border": "#DDDDDD"
}
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ import logging
import math
import sys
from collections import deque
from datetime import timedelta
from enum import Enum
import numpy as np
......@@ -114,8 +115,14 @@ class PyGameGUI:
self.visualization_config = yaml.safe_load(file)
self.screen_margin = self.visualization_config["GameWindow"]["screen_margin"]
self.window_width = self.visualization_config["GameWindow"]["start_width"]
self.window_height = self.visualization_config["GameWindow"]["start_height"]
self.min_width = self.visualization_config["GameWindow"]["min_width"]
self.min_height = self.visualization_config["GameWindow"]["min_height"]
self.buttons_width = self.visualization_config["GameWindow"]["buttons_width"]
self.buttons_height = self.visualization_config["GameWindow"]["buttons_height"]
self.window_width = self.min_width
self.window_height = self.min_height
self.main_window = pygame.display.set_mode(
(
......@@ -124,7 +131,7 @@ class PyGameGUI:
)
)
self.game_width, self.game_height = 0, 0
# self.game_width, self.game_height = 0, 0
self.images_path = ROOT_DIR / "pygame_gui" / "images"
......@@ -133,37 +140,47 @@ class PyGameGUI:
self.menu_state = MenuStates.Start
self.manager: pygame_gui.UIManager
def init_window_sizes(self):
def get_window_sizes(self, state: dir):
counter_positions = np.array([c.pos for c in state["counters"]])
kitchen_width = counter_positions[:, 0].max() + 0.5
kitchen_height = counter_positions[:, 1].max() + 0.5
if self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_width":
game_width = self.visualization_config["GameWindow"]["size"]
kitchen_aspect_ratio = (
self.simulator.env.kitchen_height / self.simulator.env.kitchen_width
)
kitchen_aspect_ratio = kitchen_height / kitchen_width
game_height = int(game_width * kitchen_aspect_ratio)
grid_size = int(game_width / self.simulator.env.kitchen_width)
elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_height":
game_height = self.visualization_config["GameWindow"]["size"]
kitchen_aspect_ratio = (
self.simulator.env.kitchen_width / self.simulator.env.kitchen_height
)
kitchen_aspect_ratio = kitchen_width / kitchen_height
game_width = int(game_height * kitchen_aspect_ratio)
grid_size = int(game_width / self.simulator.env.kitchen_width)
elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "grid":
grid_size = self.visualization_config["GameWindow"]["size"]
game_width, game_height = (
self.simulator.env.kitchen_width * grid_size,
self.simulator.env.kitchen_height * grid_size,
kitchen_width * grid_size,
kitchen_height * grid_size,
)
else:
game_width, game_height = 0, 0
grid_size = 0
window_width, window_height = (
game_width + (2 * self.screen_margin),
game_height + (2 * self.screen_margin),
game_height + (2 * self.screen_margin), # bar with orders
)
return window_width, window_height, game_width, game_height, grid_size
window_width = max(window_width, self.min_width)
window_height = max(window_height, self.min_height)
return (
int(window_width),
int(window_height),
game_width,
game_height,
grid_size,
)
def create_player_colors(self) -> list[Color]:
number_player = len(self.simulator.env.players)
......@@ -484,6 +501,16 @@ class PyGameGUI:
if SHOW_COUNTER_CENTERS:
pygame.draw.circle(self.game_screen, colors["green1"], counter.pos, 3)
def update_score_label(self, state):
score = state["score"]
self.score_label.set_text(f"Your score is {score}")
def update_remaining_time(self, remaining_time: timedelta):
hours, rem = divmod(remaining_time.seconds, 3600)
minutes, seconds = divmod(rem, 60)
display_time = f"{minutes}:{'%02d' % seconds}"
self.timer_label.set_text(f"Time remaining: {display_time}")
def draw(self, state):
"""Main visualization function.
......@@ -495,20 +522,20 @@ class PyGameGUI:
self.draw_counters(state)
self.draw_players(state)
self.manager.draw_ui(self.game_screen)
self.manager.draw_ui(self.main_window)
self.update_remaining_time(state["remaining_time"])
def init_ui_elements(self):
self.manager = pygame_gui.UIManager((self.window_width, self.window_height))
self.manager.get_theme().load_theme(ROOT_DIR / "gui_2d_vis" / "gui_theme.json")
button_width, button_height = 200, 60
self.start_button = pygame_gui.elements.UIButton(
relative_rect=pygame.Rect(
(
(self.window_width // 2) - button_width // 2,
(self.window_height / 2) - button_height // 2,
(self.window_width // 2) - self.buttons_width // 2,
(self.window_height / 2) - self.buttons_height // 2,
),
(button_width, button_height),
(self.buttons_width, self.buttons_height),
),
text="Start Game",
manager=self.manager,
......@@ -518,23 +545,24 @@ class PyGameGUI:
self.quit_button = pygame_gui.elements.UIButton(
relative_rect=pygame.Rect(
(
(self.window_width - button_width),
(self.window_width - self.buttons_width),
0,
),
(button_width, button_height),
(self.buttons_width, self.buttons_height),
),
text="Quit Game",
manager=self.manager,
object_id="#quit_button",
)
self.quit_button.can_hover()
self.finished_button = pygame_gui.elements.UIButton(
relative_rect=pygame.Rect(
(
(self.window_width - button_width),
(self.window_height - button_height),
(self.window_width - self.buttons_width),
(self.window_height - self.buttons_height),
),
(button_width, button_height),
(self.buttons_width, self.buttons_height),
),
text="End screen",
manager=self.manager,
......@@ -545,21 +573,21 @@ class PyGameGUI:
relative_rect=pygame.Rect(
(
(0),
(self.window_height - button_height),
(self.window_height - self.buttons_height),
),
(button_width, button_height),
(self.buttons_width, self.buttons_height),
),
text="Back to Start",
text="Back to menu",
manager=self.manager,
)
self.back_button.can_hover()
self.score_rect = pygame.Rect(
(
(self.window_width // 2) - button_width // 2,
(self.window_height / 2) - button_height // 2,
(self.window_width // 2) - self.buttons_width,
(self.window_height // 2) - self.buttons_height // 2,
),
(button_width, button_height),
(self.buttons_width * 2, self.buttons_height),
)
self.score_label = pygame_gui.elements.UILabel(
......@@ -588,29 +616,37 @@ class PyGameGUI:
starting_option=layout_file_paths[-1],
)
def setup_windows(self):
(
self.window_width,
self.window_height,
self.game_width,
self.game_height,
self.grid_size,
) = self.init_window_sizes()
self.timer_label = pygame_gui.elements.UILabel(
text="GAMETIME",
relative_rect=pygame.Rect(
(0, 0),
(self.buttons_width * 1.5, self.buttons_height),
),
manager=self.manager,
object_id="#timer_label",
)
def set_window_size(self, window_width, window_height, game_width, game_height):
self.game_screen = pygame.Surface(
(
self.game_width,
self.game_height,
game_width,
game_height,
),
)
self.main_window = pygame.display.set_mode(
(
self.window_width,
self.window_height,
window_width,
window_height,
)
)
self.player_colors = self.create_player_colors()
def reset_window_size(self):
self.window_width = self.min_width
self.window_height = self.min_height
self.game_width = 0
self.game_height = 0
self.set_window_size(self.min_width, self.min_height, 0, 0)
self.init_ui_elements()
def setup_simulation(self, config_path, layout_path):
self.simulator = Simulator(config_path, layout_path, 600)
......@@ -619,6 +655,14 @@ class PyGameGUI:
player_name = f"p{i}"
self.simulator.register_player(player_name)
self.simulator.start()
(
self.window_width,
self.window_height,
self.game_width,
self.game_height,
self.grid_size,
) = self.get_window_sizes(self.simulator.get_state())
self.player_colors = self.create_player_colors()
def manage_button_visibility(self):
match self.menu_state:
......@@ -629,21 +673,21 @@ class PyGameGUI:
self.score_label.hide()
self.finished_button.hide()
self.layout_selection.show()
self.timer_label.hide()
case MenuStates.Game:
self.start_button.hide()
self.back_button.show()
self.score_label.hide()
self.finished_button.show()
self.layout_selection.hide()
self.timer_label.show()
case MenuStates.End:
self.start_button.hide()
self.back_button.show()
self.score_label.show()
self.score_label.set_text(
f"Your Score is {self.simulator.env.game_score.score}"
)
self.finished_button.hide()
self.layout_selection.hide()
self.timer_label.hide()
def start_button_press(self):
self.menu_state = MenuStates.Game
......@@ -657,13 +701,16 @@ class PyGameGUI:
config_path = ROOT_DIR / "game_content" / "environment_config.yaml"
self.setup_simulation(config_path, layout_path)
self.setup_windows()
self.set_window_size(*(self.get_window_sizes(self.simulator.get_state()))[:-1])
self.init_ui_elements()
log.debug("Pressed start button")
def back_button_press(self):
self.simulator.stop()
self.menu_state = MenuStates.Start
self.reset_window_size()
self.simulator.stop()
log.debug("Pressed back button")
def quit_button_press(self):
......@@ -672,8 +719,9 @@ class PyGameGUI:
log.debug("Pressed quit button")
def finished_button_press(self):
self.simulator.stop()
self.menu_state = MenuStates.End
self.reset_window_size()
self.simulator.stop()
log.debug("Pressed finished button")
def start_pygame(self):
......@@ -682,13 +730,11 @@ class PyGameGUI:
pygame.init()
pygame.font.init()
self.setup_windows()
self.init_ui_elements()
pygame.display.set_caption("Simple Overcooked Simulator")
clock = pygame.time.Clock()
self.init_ui_elements()
self.manage_button_visibility()
# Game loop
......@@ -700,6 +746,8 @@ class PyGameGUI:
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
# UI Buttons:
if event.type == pygame_gui.UI_BUTTON_PRESSED:
match event.ui_element:
case self.start_button:
......@@ -723,6 +771,8 @@ class PyGameGUI:
# drawing:
state = self.simulator.get_state()
self.main_window.fill(colors["lemonchiffon1"])
self.manager.draw_ui(self.main_window)
......@@ -734,7 +784,10 @@ class PyGameGUI:
self.handle_keys()
state = self.simulator.get_state()
if state["ended"]:
self.finished_button_press()
self.manage_button_visibility()
self.draw(state)
game_screen_rect = self.game_screen.get_rect()
......@@ -746,9 +799,7 @@ class PyGameGUI:
self.main_window.blit(self.game_screen, game_screen_rect)
case MenuStates.End:
pygame.draw.rect(
self.game_screen, colors["cornsilk1"], self.score_rect
)
self.update_score_label(state)
self.manager.update(time_delta)
pygame.display.flip()
......
# colors: https://www.webucator.com/article/python-color-constants-module/
GameWindow:
WhatIsFixed: window_height # entweder grid oder window_width
size: 500
WhatIsFixed: window_height # grid or window_width or window_height
size: 400
screen_margin: 100
start_width: 600
start_height: 600
min_width: 700
min_height: 600
buttons_width: 180
buttons_height: 60
Kitchen:
ground_tiles_color: sgigray76
......
......@@ -58,18 +58,6 @@ class Action:
return f"Action({self.player},{self.act_type},{self.action})"
class GameScore:
def __init__(self):
self.score = 0
def increment_score(self, score: int):
self.score += score
log.debug(f"Score: {self.score}")
def read_score(self):
return self.score
class Environment:
"""Environment class which handles the game logic for the overcooked-inspired environment.
......@@ -131,9 +119,16 @@ class Environment:
self.init_counters()
self.score: int = 0
self.env_time = create_init_env_time()
self.beginning_time = self.env_time
self.env_time_end = self.env_time + timedelta(
seconds=environment_config["game"]["time_limit_seconds"]
)
log.debug(f"End time: {self.env_time_end}")
@property
def game_ended(self) -> bool:
return self.env_time >= self.env_time_end
def load_item_info(self) -> dict[str, ItemInfo]:
with open(self.item_info_path, "r") as file:
......@@ -437,6 +432,7 @@ class Environment:
and time limits.
"""
self.env_time += passed_time
with self.lock:
for counter in self.counters:
if isinstance(counter, (CuttingBoard, Stove, Sink, PlateDispenser)):
......@@ -448,7 +444,13 @@ class Environment:
Returns: Dict of lists of the current relevant game objects.
"""
return {"players": self.players, "counters": self.counters, "score": self.score}
return {
"players": self.players,
"counters": self.counters,
"score": self.game_score.read_score(),
"ended": self.game_ended,
"remaining_time": max(self.env_time_end - self.env_time, timedelta(0)),
}
def get_state_json(self):
"""Get the current state of the game environment as a json-like nested dictionary.
......
......@@ -98,9 +98,11 @@ class Simulator(Thread):
overslept_in_ns = 0
self.env.reset_env_time()
last_step_start = time.time_ns()
while not self.finished:
step_start = time.time_ns()
self.step(timedelta(seconds=overslept_in_ns / 1_000_000_000))
self.step(timedelta(seconds=(step_start - last_step_start) / 1_000_000_000))
last_step_start = step_start
step_duration = time.time_ns() - step_start
time_to_sleep_ns = self.preferred_sleep_time_ns - (
......
......@@ -268,3 +268,25 @@ def test_time_passed():
assert (
env.env_time == create_init_env_time() + passed_time + passed_time_2
), "Env time needs to be updated via the step function"
def test_time_limit():
np.random.seed(42)
env = Environment(
ROOT_DIR / "game_content" / "environment_config.yaml",
layouts_folder / "empty.layout",
ROOT_DIR / "game_content" / "item_info.yaml",
)
env.reset_env_time()
assert not env.game_ended, "Game has not ended yet"
passed_time = timedelta(seconds=10)
env.step(passed_time)
assert not env.game_ended, "Game has not ended yet"
passed_time_2 = timedelta(seconds=80)
env.step(passed_time_2)
assert env.game_ended, "Game has ended now."
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment