diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py index 63e159036d0230caf63610221905564f80578f1d..8955bc66b91c36afe46337d196161f89758ab3e8 100644 --- a/overcooked_simulator/counters.py +++ b/overcooked_simulator/counters.py @@ -1,14 +1,16 @@ +from typing import Optional + import numpy as np -from overcooked_simulator.game_items import CuttableItem +from overcooked_simulator.game_items import CuttableItem, HoldableItem class Counter: """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 + def __init__(self, pos: np.ndarray): + self.pos: np.ndarray = pos + self.occupied_by: Optional[HoldableItem] = None def pick_up(self): """Gets called upon a player performing the pickup action. If the counter can give something to @@ -21,29 +23,29 @@ class Counter: self.occupied_by = None return give_player - def drop_off(self, item): - """Takes the thing dropped of by the player. + def can_drop_off(self, item: HoldableItem): + """Checks whether an item by the player can be dropped of. More relevant for example with + ingredient dispensers, which should always be occupied and cannot take an item. Args: - item: The item to be placed on the counter. + item: The item for which to check, if it can be placed on the counter. - Returns: TODO Return information, wether the score is affected (Serving Window?) + Returns: True if the item can be placed on the counter, False if not. """ - if self.occupied_by is None: - self.occupied_by = item + return self.occupied_by is None or self.occupied_by.can_combine(item) - def can_drop_off(self, item): - """Checks whether an item by the player can be dropped of. More relevant for example with - ingredient dispensers, which should always be occupied and cannot take an item. + def drop_off(self, item: HoldableItem): + """Takes the thing dropped of by the player. Args: - item: The item for which to check, if it can be placed on the counter. + item: The item to be placed on the counter. - Returns: True if the item can be placed on the counter, False if not. + Returns: TODO Return information, wether the score is affected (Serving Window?) """ - return self.occupied_by is None + if self.occupied_by is None: + self.occupied_by = item def interact_start(self): """Starts an interaction by the player. Nothing happens for the standard counter.""" @@ -58,7 +60,7 @@ class Counter: class CuttingBoard(Counter): - def __init__(self, pos): + def __init__(self, pos: np.ndarray): self.progressing = False super().__init__(pos) diff --git a/overcooked_simulator/main.py b/overcooked_simulator/main.py index eae75879d87a387522270e1868027723c0376d93..ea66b93ab47e97284f48254bf3dc981a2a957e94 100644 --- a/overcooked_simulator/main.py +++ b/overcooked_simulator/main.py @@ -1,6 +1,9 @@ import sys from pathlib import Path +import numpy as np +import pygame + from overcooked_simulator.game_items import Tomato from overcooked_simulator.player import Player from overcooked_simulator.pygame_gui.pygame_gui import PyGameGUI @@ -8,13 +11,26 @@ from overcooked_simulator.simulation_runner import Simulator def main(): - simulator = Simulator(Path("layouts/basic.layout"), 600) - simulator.register_player(Player("p1", [100, 200])) - simulator.register_player(Player("p2", [200, 100])) + simulator = Simulator(Path("overcooked_simulator/layouts/basic.layout"), 600) + player_one_name = "p1" + player_two_name = "p2" + simulator.register_player(Player(player_one_name, np.array([100, 200]))) + simulator.register_player(Player(player_two_name, np.array([200, 100]))) + simulator.env.counters[3].occupied_by = Tomato() simulator.env.counters[4].occupied_by = Tomato() - gui = PyGameGUI(simulator) + # TODO maybe read the player names and keyboard keys from config file? + 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] + gui = PyGameGUI(simulator, [player_one_name, player_two_name], [keys1, keys2]) simulator.start() gui.start_pygame() diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 209485bfe9f1295cb607eedbe9e19d97c51afc76..3c8c036d040fb2d85f06c71db88e83471dd9523f 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -62,14 +62,15 @@ class Environment: current_x = self.counter_side_length / 2 for character in line: character = character.capitalize() - if character == "C": - counter = Counter(np.array([current_x, current_y])) - counters.append(counter) - elif character == "B": - counter = CuttingBoard(np.array([current_x, current_y])) - counters.append(counter) - elif character == "E": - pass + match character: + case "C": + counter = Counter(np.array([current_x, current_y])) + counters.append(counter) + case "B": + counter = CuttingBoard(np.array([current_x, current_y])) + counters.append(counter) + case "E": + pass current_x += self.counter_side_length current_y += self.counter_side_length return counters diff --git a/overcooked_simulator/player.py b/overcooked_simulator/player.py index 28f812bd291b9db13f214419933a496146f9ea16..9ae0c9f2b72d702ffe69bb0e2cc64a955b8175d6 100644 --- a/overcooked_simulator/player.py +++ b/overcooked_simulator/player.py @@ -1,6 +1,9 @@ +from typing import Optional + import numpy as np from overcooked_simulator.counters import Counter +from overcooked_simulator.game_items import HoldableItem class Player: @@ -10,17 +13,17 @@ class Player: """ - def __init__(self, name, pos): - self.name = name - self.pos = np.array(pos, dtype=float) - self.holding = None + def __init__(self, name: str, pos: np.ndarray): + self.name: str = name + self.pos: np.ndarray = np.array(pos, dtype=float) + self.holding: Optional[HoldableItem] = None - self.radius = 18 - self.move_dist = 5 - self.interaction_range = 60 - self.facing_direction = np.array([0, 1]) + self.radius: int = 18 + self.move_dist: int = 5 + self.interaction_range: int = 60 + self.facing_direction: np.ndarray = np.array([0, 1]) - def move(self, movement: np.array): + def move(self, movement: np.ndarray): """Moves the player position by the given movement vector. A unit direction vector multiplied by move_dist is added to the player position. @@ -31,7 +34,7 @@ class Player: if np.linalg.norm(movement) != 0: self.turn(movement) - def move_abs(self, new_pos: np.array): + def move_abs(self, new_pos: np.ndarray): """Overwrites the player location by the new_pos 2d-vector. Absolute movement. Mostly needed for resetting the player after a collision. @@ -40,7 +43,7 @@ class Player: """ self.pos = new_pos - def turn(self, direction: np.array): + def turn(self, direction: np.ndarray): """Turns the player in the given direction. Overwrites the facing_direction by a given 2d-vector. facing_direction is normalized to length 1. @@ -50,7 +53,7 @@ class Player: if np.linalg.norm(direction) != 0: self.facing_direction = direction / np.linalg.norm(direction) - def can_reach(self, counter): + def can_reach(self, counter: Counter): """Checks wether the player can reach the counter in question. Simple check if the distance is not larger than the player interaction range. @@ -71,6 +74,7 @@ class Player: """ if self.holding is None: self.holding = counter.pick_up() + elif counter.can_drop_off(self.holding): counter.drop_off(self.holding) self.holding = None diff --git a/overcooked_simulator/pygame_gui/pygame_gui.py b/overcooked_simulator/pygame_gui/pygame_gui.py index f33a49b5817d5a68af0359c4e564ced4d0bcca2a..eff1e36452a21bdddd6c3564f7d3aa7d69c80a2b 100644 --- a/overcooked_simulator/pygame_gui/pygame_gui.py +++ b/overcooked_simulator/pygame_gui/pygame_gui.py @@ -23,12 +23,20 @@ KNIFE_COLOR = (120, 120, 120) class PlayerKeyset: """Set of keyboard keys for controlling a player. - First four keys are for movement, + First four keys are for movement. Order: Down, Up, Left, Right. 5th key is for interacting with counters. 6th key ist for picking up things or dropping them. + """ def __init__(self, player_name: str, keys: list[pygame.key]): + """Creates a player keyset which contains information about which keyboard keys control the player. + Movement keys in the following order: Down, Up, Left, Right + + Args: + player_name: The name of the player to control. + keys: The keys which control this player in the following order: Down, Up, Left, Right, Interact, Pickup. + """ self.name = player_name self.player_keys = keys self.move_vectors = [[-1, 0], [1, 0], [0, -1], [0, 1]] @@ -42,7 +50,12 @@ class PlayerKeyset: class PyGameGUI: """Visualisation of the overcooked environmnent and reading keyboard inputs using pygame.""" - def __init__(self, simulator: Simulator): + def __init__( + self, + simulator: Simulator, + player_names: list[str], + player_keys: list[pygame.key], + ): self.FPS = 60 self.simulator = simulator self.counter_size = self.simulator.env.counter_side_length @@ -51,20 +64,15 @@ class PyGameGUI: simulator.env.world_height, ) - self.GET_CONTINOUS_INTERACT_AND_PICKUP = False + self.player_names = player_names + self.player_keys = player_keys + assert len(self.player_names) == len( + self.player_keys + ), "Number of players and keysets should match." - 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("p1", keys1), - PlayerKeyset("p2", keys2), + PlayerKeyset(player_name, keys) + for player_name, keys in zip(self.player_names, self.player_keys) ] def send_action(self, action: Action): @@ -92,13 +100,6 @@ class PyGameGUI: 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") - self.send_action(action) - if relevant_keys[-1]: - action = Action(f"p{player_idx + 1}", "pickup", "pickup") - self.send_action(action) def handle_key_event(self, event): """Handles key events for the pickup and interaction keys. Pickup is a single action, @@ -112,6 +113,7 @@ class PyGameGUI: if event.key == keyset.pickup_key and event.type == pygame.KEYDOWN: action = Action(keyset.name, "pickup", "pickup") self.send_action(action) + if event.key == keyset.interact_key: if event.type == pygame.KEYDOWN: action = Action(keyset.name, "interact", "keydown") @@ -164,16 +166,17 @@ class PyGameGUI: """Visualisation of an item at the specified position. On a counter or in the hands of the player.""" if isinstance(item, Tomato): if item.finished: - IMAGE = pygame.image.load( + image = pygame.image.load( "overcooked_simulator/pygame_gui/images/tomato_cut.png" ).convert_alpha() # or .convert_alpha() else: - IMAGE = pygame.image.load( + image = pygame.image.load( "overcooked_simulator/pygame_gui/images/tomato.png" ).convert_alpha() # or .convert_alpha() - rect = IMAGE.get_rect() + rect = image.get_rect() rect.center = pos - self.screen.blit(IMAGE, rect) + self.screen.blit(image, rect) + if isinstance(item, ProgressibleItem) and not item.finished: self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed) @@ -261,7 +264,7 @@ class PyGameGUI: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False - if event.type == pygame.KEYDOWN or event.type == pygame.KEYUP: + if event.type in [pygame.KEYDOWN, pygame.KEYUP]: self.handle_key_event(event) self.handle_keys() diff --git a/overcooked_simulator/simulation_runner.py b/overcooked_simulator/simulation_runner.py index 74b5b492d57fd51798f8386624bc3b5f0e974f01..3738a2391bb0283228f9545037e237ca65e1f943 100644 --- a/overcooked_simulator/simulation_runner.py +++ b/overcooked_simulator/simulation_runner.py @@ -87,7 +87,7 @@ class Simulator(Thread): step_duration = time.time_ns() - step_start time_to_sleep_ns = self.prefered_sleeptime_ns - ( - step_duration + overslept_in_ns + step_duration + overslept_in_ns ) sleep_start = time.time_ns() diff --git a/tests/test_start.py b/tests/test_start.py index bae42abb9e8159ee18168d94bbc34e5e18c8c0c4..406ea8a3487908d22695f3fa769facdbf5c61d95 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -78,7 +78,7 @@ def test_movement(): expected = start_pos + do_moves_number * (move_direction * player1.move_dist) assert ( - np.linalg.norm(expected - sim.env.players[player_name].pos) == 0 + np.linalg.norm(expected - sim.env.players[player_name].pos) == 0 ), "Should be here?" @@ -159,7 +159,7 @@ def test_pickup(): sim.enter_action(pick) assert ( - player.holding is not None + player.holding is not None ), "Player should be too far away to put tomato down." sim.enter_action(move_down)