diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py index d48fbe810e0f63b09b6cf1d702245e4ae5842964..77ab32f890e3c0a464b168f11b5739fca438978f 100644 --- a/overcooked_simulator/counters.py +++ b/overcooked_simulator/counters.py @@ -2,8 +2,8 @@ import numpy as np class Counter: - """Simple class for a counter at a specified position (center of counter). Can hold things on top. - """ + """Simple class for a counter at a specified position (center of counter). Can hold things on top.""" + def __init__(self, pos: np.array): self.pos = pos self.occupied_by = None diff --git a/overcooked_simulator/main.py b/overcooked_simulator/main.py index e7765a91eb06c3568131d6d6ceeee8d2699ce634..2ce280ede7ee3265e9e3fa82c87161ee090bfab5 100644 --- a/overcooked_simulator/main.py +++ b/overcooked_simulator/main.py @@ -1,9 +1,9 @@ -from overcooked_simulator.player import Player import sys from pathlib import Path from overcooked_simulator.player import Player from overcooked_simulator.pygame_gui import PyGameGUI +from overcooked_simulator.simulation_runner import Simulator def main(): diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 8d64bbf76a128a8dfccd026d05cb3462d3110dfb..8fe744c7667809e596b96ac759c9bbaffeab79f4 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -1,4 +1,5 @@ from __future__ import annotations + from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -10,12 +11,16 @@ from overcooked_simulator.counters import Counter class Action: - """Action class, specifies player, action type and action itself. - """ + """Action class, specifies player, action type and action itself.""" + def __init__(self, player, act_type, action): self.player = player self.act_type = act_type - assert self.act_type in ["movement", "pickup", "interact"], "Unknown action type" + assert self.act_type in [ + "movement", + "pickup", + "interact", + ], "Unknown action type" self.action = action @@ -89,7 +94,9 @@ class Environment: Returns: The closest counter for the given point. """ - counter_distances = distance_matrix([point], [counter.pos for counter in self.counters])[0] + counter_distances = distance_matrix( + [point], [counter.pos for counter in self.counters] + )[0] closest_counter_idx = np.argmin(counter_distances) return self.counters[closest_counter_idx] @@ -177,8 +184,11 @@ class Environment: Returns: True if the player is intersecting with any object in the environment. """ - return (self.detect_player_collision(player) or self.detect_collision_counters(player) or - self.detect_collision_world_bounds(player)) + return ( + self.detect_player_collision(player) + or self.detect_collision_counters(player) + or self.detect_collision_world_bounds(player) + ) def detect_player_collision(self, player: Player): """Detects collisions between the queried player and other players. @@ -207,7 +217,12 @@ class Environment: Returns: True if the player collides with any counter, False if not. """ - return any(map(lambda counter: self.detect_collision_player_counter(player, counter), self.counters)) + return any( + map( + lambda counter: self.detect_collision_player_counter(player, counter), + self.counters, + ) + ) def detect_collision_player_counter(self, player: Player, counter: Counter): """Checks if the player and counter collide (overlap). @@ -239,12 +254,14 @@ class Environment: Returns: True if the player touches the world bounds, False if not. """ collisions_lower = any((player.pos - player.radius) < 0) - collisions_upper = any((player.pos + player.radius) > [self.world_width, self.world_height]) + collisions_upper = any( + (player.pos + player.radius) > [self.world_width, self.world_height] + ) return collisions_lower or collisions_upper def step(self): """Performs a step of the environment. Affects time based events such as cooking or cutting things, orders - and timelimits. + and time limits. """ pass @@ -254,9 +271,7 @@ 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.score} def get_state_json(self): """Get the current state of the game environment as a json-like nested dictionary. diff --git a/overcooked_simulator/player.py b/overcooked_simulator/player.py index 670480b50d8d31a65091d078e0c671615eba57ca..c4b11115671a69b6df4a1526ef6d9cd7731ae9d4 100644 --- a/overcooked_simulator/player.py +++ b/overcooked_simulator/player.py @@ -1,4 +1,5 @@ import numpy as np + from overcooked_simulator.counters import Counter @@ -8,6 +9,7 @@ class Player: This class handles interactions with counters and objects. """ + def __init__(self, name, pos): self.name = name self.pos = np.array(pos, dtype=float) diff --git a/overcooked_simulator/pygame_gui.py b/overcooked_simulator/pygame_gui.py index 1ae75ed612ecf1502bc6e69c0817a8afbff73bf1..9d5e22e263ce38c02b008f7dd2ec5726ea5f9ec4 100644 --- a/overcooked_simulator/pygame_gui.py +++ b/overcooked_simulator/pygame_gui.py @@ -1,9 +1,9 @@ -import pygame import numpy as np +import pygame + from overcooked_simulator.overcooked_environment import Action from overcooked_simulator.simulation_runner import Simulator - WHITE = (255, 255, 255) GREY = (190, 190, 190) BLACK = (0, 0, 0) @@ -14,6 +14,7 @@ RED = (255, 0, 0) BLUE = (0, 0, 255) YELLOW = (255, 255, 0) BACKGROUND_COLOR = GREY +BACKGROUND_LINES = (200, 200, 200) class PlayerKeyset: @@ -22,28 +23,44 @@ class PlayerKeyset: 5th key is for interacting with counters. 6th key ist for picking up things or dropping them. """ + def __init__(self, keys: list[pygame.key]): self.player_keys = keys self.move_vectors = [[-1, 0], [1, 0], [0, -1], [0, 1]] - self.key_to_movement = {key: vec for (key, vec) in zip(self.player_keys[:-2], self.move_vectors)} + self.key_to_movement = { + key: vec for (key, vec) in zip(self.player_keys[:-2], self.move_vectors) + } self.interact_key = self.player_keys[-2] self.pickup_key = self.player_keys[-1] class PyGameGUI: - """Visualisation of the overcooked environmnent and reading keyboard inputs using pygame. - """ + """Visualisation of the overcooked environmnent and reading keyboard inputs using pygame.""" + def __init__(self, simulator: Simulator): self.FPS = 60 self.simulator = simulator self.counter_size = self.simulator.env.counter_side_length - self.window_width, self.window_height = simulator.env.world_width, simulator.env.world_height + self.window_width, self.window_height = ( + simulator.env.world_width, + simulator.env.world_height, + ) self.GET_CONTINOUS_INTERACT_AND_PICKUP = False - keys1 = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN, pygame.K_SPACE, pygame.K_i] + keys1 = [ + pygame.K_LEFT, + pygame.K_RIGHT, + pygame.K_UP, + pygame.K_DOWN, + pygame.K_SPACE, + pygame.K_i, + ] keys2 = [pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s, pygame.K_f, pygame.K_e] - self.player_keysets: list[PlayerKeyset] = [PlayerKeyset(keys1), PlayerKeyset(keys2)] + self.player_keysets: list[PlayerKeyset] = [ + PlayerKeyset(keys1), + PlayerKeyset(keys2), + ] def send_action(self, action: Action): """Sends an action to the game environment. @@ -68,14 +85,14 @@ class PyGameGUI: if np.linalg.norm(move_vec) != 0: move_vec = move_vec / np.linalg.norm(move_vec) - action = Action(f"p{player_idx+1}", "movement", move_vec) + action = Action(f"p{player_idx + 1}", "movement", move_vec) self.send_action(action) if self.GET_CONTINOUS_INTERACT_AND_PICKUP: if relevant_keys[-2]: - action = Action(f"p{player_idx+1}", "interact", "interact") + action = Action(f"p{player_idx + 1}", "interact", "interact") self.send_action(action) if relevant_keys[-1]: - action = Action(f"p{player_idx+1}", "pickup", "pickup") + action = Action(f"p{player_idx + 1}", "pickup", "pickup") self.send_action(action) def handle_interact_single_send(self, event): @@ -94,17 +111,15 @@ class PyGameGUI: self.send_action(action) def draw_background(self): - """Visualizes a game background. - """ - BACKGROUND_LINES = (200, 200, 200) - block_size = self.counter_size//2 # Set the size of the grid block + """Visualizes a game background.""" + block_size = self.counter_size // 2 # Set the size of the grid block for x in range(0, self.window_width, block_size): for y in range(0, self.window_height, block_size): rect = pygame.Rect(x, y, block_size, block_size) pygame.draw.rect(self.screen, BACKGROUND_LINES, rect, 1) def draw_players(self, state): - """Visualizes the players as circles with an triangle for the facing diretion. + """Visualizes the players as circles with a triangle for the facing diretion. Args: state: The game state returned by the environment. @@ -121,9 +136,15 @@ class PyGameGUI: facing = player.facing_direction - pygame.draw.polygon(self.screen, BLUE, - ((pos[0]+(facing[1]*5), pos[1]-(facing[0]*5)), - (pos[0]-(facing[1]*5), pos[1]+(facing[0]*5)), player.pos + (facing * 20))) + pygame.draw.polygon( + self.screen, + BLUE, + ( + (pos[0] + (facing[1] * 5), pos[1] - (facing[0] * 5)), + (pos[0] - (facing[1] * 5), pos[1] + (facing[0] * 5)), + player.pos + (facing * 20), + ), + ) def draw_counters(self, state): """Visualizes the counters in the environment. @@ -132,9 +153,12 @@ class PyGameGUI: state: The game state returned by the environment. """ for idx, counter in enumerate(state["counters"]): - counter_rect_outline = pygame.Rect(counter.pos[0] - (self.counter_size / 2), - counter.pos[1] - (self.counter_size / 2), self.counter_size, - self.counter_size) + counter_rect_outline = pygame.Rect( + counter.pos[0] - (self.counter_size / 2), + counter.pos[1] - (self.counter_size / 2), + self.counter_size, + self.counter_size, + ) pygame.draw.rect(self.screen, COUNTERCOLOR, counter_rect_outline) @@ -153,8 +177,7 @@ class PyGameGUI: pygame.display.flip() def start_pygame(self): - """Starts pygame and the gui loop. Each frame the gamestate is visualized and keyboard inputs are read. - """ + """Starts pygame and the gui loop. Each frame the gamestate is visualized and keyboard inputs are read.""" pygame.init() pygame.font.init() @@ -167,7 +190,6 @@ class PyGameGUI: # Game loop running = True while running: - for event in pygame.event.get(): if event.type == pygame.QUIT: running = False diff --git a/overcooked_simulator/simulation_runner.py b/overcooked_simulator/simulation_runner.py index df168a27fa700896401c90596d3e9475b8ea96a4..470330eb36097d8371c1c429dfb42f8c2fb2b97c 100644 --- a/overcooked_simulator/simulation_runner.py +++ b/overcooked_simulator/simulation_runner.py @@ -1,5 +1,6 @@ -from threading import Thread import time +from threading import Thread + from overcooked_simulator.overcooked_environment import Environment, Action from overcooked_simulator.player import Player @@ -27,68 +28,67 @@ class Simulator(Thread): super().__init__() def step(self): - """One simulation step of the environment. - """ + """One simulation step of the environment.""" self.env.step() def enter_action(self, action: Action): """Takes an action and executes it in the environment. - Args: - action (Action): The action object to be executed. - """ + Args: + action (Action): The action object to be executed. + """ self.env.perform_action(action) def get_state(self): """Get the current gamestate as python objects. - Returns: - The current state of the game. Currently as dict with lists of environment objects. - """ + Returns: + The current state of the game. Currently as dict with lists of environment objects. + """ return self.env.get_state() def get_state_json(self): """Get the current gamestate in json-like dict. - Returns: - The gamestate encoded in a json style nested dict. - """ + Returns: + The gamestate encoded in a json style nested dict. + """ return self.env.get_state_json() def register_player(self, player: Player): - print(f"Added player {player.name} to the game.") """Adds a player to the environment. - Args: - player: The player to be added. - """ + Args: + player: The player to be added. + """ + print(f"Added player {player.name} to the game.") self.env.players[player.name] = player def register_players(self, players: list[Player]): """Registers multiple players from a list - Args: - players: List of players to be added. - """ + Args: + players: List of players to be added. + """ for p in players: self.register_player(p) def run(self): - """Starts the simulator thread. Runs in a loop until stopped. - """ + """Starts the simulator thread. Runs in a loop until stopped.""" overslept_in_ns = 0 while not self.finished: - step_start = time.time_ns() self.step() step_duration = time.time_ns() - step_start - time_to_sleep_ns = self.prefered_sleeptime_ns - (step_duration + overslept_in_ns) + time_to_sleep_ns = self.prefered_sleeptime_ns - ( + step_duration + overslept_in_ns + ) sleep_start = time.time_ns() time.sleep(max(time_to_sleep_ns / 1e9, 0)) @@ -96,7 +96,6 @@ class Simulator(Thread): overslept_in_ns = sleep_function_duration - time_to_sleep_ns def stop(self): - """Stops the simulator - """ + """Stops the simulator""" print("Stopping the simulation.") self.finished = True diff --git a/tests/test_start.py b/tests/test_start.py index 5c465bd5588ac5349b082a42eec99baa9bf345d0..e9d0d80df42275fb9b9bfa24666b37a4a5f43322 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -1,13 +1,11 @@ -from overcooked_simulator.simulation_runner import Simulator -from overcooked_simulator.player import Player -from overcooked_simulator.overcooked_environment import Environment, Action -import numpy as np import time from pathlib import Path import numpy as np from overcooked_simulator.counters import Counter +from overcooked_simulator.player import Player +from overcooked_simulator.simulation_runner import Simulator def test_player_registration(): @@ -51,8 +49,8 @@ def test_simulator_frequency(): print(sim.env.c) accepted_tolerance = 0.02 - lower = frequency * running_time_seconds * (1-accepted_tolerance) - upper = frequency * running_time_seconds * (1+accepted_tolerance) + lower = frequency * running_time_seconds * (1 - accepted_tolerance) + upper = frequency * running_time_seconds * (1 + accepted_tolerance) assert lower < sim.env.c < upper, "Timing error in the environment at 1000hz" @@ -73,11 +71,16 @@ def test_collision_detection(): assert not sim.env.detect_collision(player1), "Does not collide yet." player1.move_abs(counter_pos) - assert sim.env.detect_collision_counters(player1), "Player and counter at same pos. Not detected." + assert sim.env.detect_collision_counters( + player1 + ), "Player and counter at same pos. Not detected." player2.move_abs(counter_pos) - assert sim.env.detect_player_collision(player1), "Players at same pos. Not detected." + assert sim.env.detect_player_collision( + player1 + ), "Players at same pos. Not detected." player1.move_abs(np.array([0, 0])) - assert sim.env.detect_collision_world_bounds(player1), "Player collides with world bounds." - + assert sim.env.detect_collision_world_bounds( + player1 + ), "Player collides with world bounds." sim.stop()