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

Type hints, read player names from 1 location, match statement for reading counter config

parent 4e956c74
Branches
Tags
1 merge request!3Resolve "Interaction with objects"
Pipeline #41392 passed
from typing import Optional
import numpy as np import numpy as np
from overcooked_simulator.game_items import CuttableItem from overcooked_simulator.game_items import CuttableItem, HoldableItem
class Counter: 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): def __init__(self, pos: np.ndarray):
self.pos = pos self.pos: np.ndarray = pos
self.occupied_by = None self.occupied_by: Optional[HoldableItem] = None
def pick_up(self): def pick_up(self):
"""Gets called upon a player performing the pickup action. If the counter can give something to """Gets called upon a player performing the pickup action. If the counter can give something to
...@@ -21,29 +23,29 @@ class Counter: ...@@ -21,29 +23,29 @@ class Counter:
self.occupied_by = None self.occupied_by = None
return give_player return give_player
def drop_off(self, item): def can_drop_off(self, item: HoldableItem):
"""Takes the thing dropped of by the player. """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: 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: return self.occupied_by is None or self.occupied_by.can_combine(item)
self.occupied_by = item
def can_drop_off(self, item): def drop_off(self, item: HoldableItem):
"""Checks whether an item by the player can be dropped of. More relevant for example with """Takes the thing dropped of by the player.
ingredient dispensers, which should always be occupied and cannot take an item.
Args: 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): def interact_start(self):
"""Starts an interaction by the player. Nothing happens for the standard counter.""" """Starts an interaction by the player. Nothing happens for the standard counter."""
...@@ -58,7 +60,7 @@ class Counter: ...@@ -58,7 +60,7 @@ class Counter:
class CuttingBoard(Counter): class CuttingBoard(Counter):
def __init__(self, pos): def __init__(self, pos: np.ndarray):
self.progressing = False self.progressing = False
super().__init__(pos) super().__init__(pos)
......
import sys import sys
from pathlib import Path from pathlib import Path
import numpy as np
import pygame
from overcooked_simulator.game_items import Tomato from overcooked_simulator.game_items import Tomato
from overcooked_simulator.player import Player from overcooked_simulator.player import Player
from overcooked_simulator.pygame_gui.pygame_gui import PyGameGUI from overcooked_simulator.pygame_gui.pygame_gui import PyGameGUI
...@@ -8,13 +11,26 @@ from overcooked_simulator.simulation_runner import Simulator ...@@ -8,13 +11,26 @@ from overcooked_simulator.simulation_runner import Simulator
def main(): def main():
simulator = Simulator(Path("layouts/basic.layout"), 600) simulator = Simulator(Path("overcooked_simulator/layouts/basic.layout"), 600)
simulator.register_player(Player("p1", [100, 200])) player_one_name = "p1"
simulator.register_player(Player("p2", [200, 100])) 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() 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() simulator.start()
gui.start_pygame() gui.start_pygame()
......
...@@ -62,14 +62,15 @@ class Environment: ...@@ -62,14 +62,15 @@ class Environment:
current_x = self.counter_side_length / 2 current_x = self.counter_side_length / 2
for character in line: for character in line:
character = character.capitalize() character = character.capitalize()
if character == "C": match character:
counter = Counter(np.array([current_x, current_y])) case "C":
counters.append(counter) counter = Counter(np.array([current_x, current_y]))
elif character == "B": counters.append(counter)
counter = CuttingBoard(np.array([current_x, current_y])) case "B":
counters.append(counter) counter = CuttingBoard(np.array([current_x, current_y]))
elif character == "E": counters.append(counter)
pass case "E":
pass
current_x += self.counter_side_length current_x += self.counter_side_length
current_y += self.counter_side_length current_y += self.counter_side_length
return counters return counters
......
from typing import Optional
import numpy as np import numpy as np
from overcooked_simulator.counters import Counter from overcooked_simulator.counters import Counter
from overcooked_simulator.game_items import HoldableItem
class Player: class Player:
...@@ -10,17 +13,17 @@ class Player: ...@@ -10,17 +13,17 @@ class Player:
""" """
def __init__(self, name, pos): def __init__(self, name: str, pos: np.ndarray):
self.name = name self.name: str = name
self.pos = np.array(pos, dtype=float) self.pos: np.ndarray = np.array(pos, dtype=float)
self.holding = None self.holding: Optional[HoldableItem] = None
self.radius = 18 self.radius: int = 18
self.move_dist = 5 self.move_dist: int = 5
self.interaction_range = 60 self.interaction_range: int = 60
self.facing_direction = np.array([0, 1]) 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. """Moves the player position by the given movement vector.
A unit direction vector multiplied by move_dist is added to the player position. A unit direction vector multiplied by move_dist is added to the player position.
...@@ -31,7 +34,7 @@ class Player: ...@@ -31,7 +34,7 @@ class Player:
if np.linalg.norm(movement) != 0: if np.linalg.norm(movement) != 0:
self.turn(movement) 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. """Overwrites the player location by the new_pos 2d-vector. Absolute movement.
Mostly needed for resetting the player after a collision. Mostly needed for resetting the player after a collision.
...@@ -40,7 +43,7 @@ class Player: ...@@ -40,7 +43,7 @@ class Player:
""" """
self.pos = new_pos 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. """Turns the player in the given direction. Overwrites the facing_direction by a given 2d-vector.
facing_direction is normalized to length 1. facing_direction is normalized to length 1.
...@@ -50,7 +53,7 @@ class Player: ...@@ -50,7 +53,7 @@ class Player:
if np.linalg.norm(direction) != 0: if np.linalg.norm(direction) != 0:
self.facing_direction = direction / np.linalg.norm(direction) 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 """Checks wether the player can reach the counter in question. Simple check if the distance is not larger
than the player interaction range. than the player interaction range.
...@@ -71,6 +74,7 @@ class Player: ...@@ -71,6 +74,7 @@ class Player:
""" """
if self.holding is None: if self.holding is None:
self.holding = counter.pick_up() self.holding = counter.pick_up()
elif counter.can_drop_off(self.holding): elif counter.can_drop_off(self.holding):
counter.drop_off(self.holding) counter.drop_off(self.holding)
self.holding = None self.holding = None
......
...@@ -23,12 +23,20 @@ KNIFE_COLOR = (120, 120, 120) ...@@ -23,12 +23,20 @@ KNIFE_COLOR = (120, 120, 120)
class PlayerKeyset: class PlayerKeyset:
"""Set of keyboard keys for controlling a player. """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. 5th key is for interacting with counters.
6th key ist for picking up things or dropping them. 6th key ist for picking up things or dropping them.
""" """
def __init__(self, player_name: str, keys: list[pygame.key]): 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.name = player_name
self.player_keys = keys self.player_keys = keys
self.move_vectors = [[-1, 0], [1, 0], [0, -1], [0, 1]] self.move_vectors = [[-1, 0], [1, 0], [0, -1], [0, 1]]
...@@ -42,7 +50,12 @@ class PlayerKeyset: ...@@ -42,7 +50,12 @@ class PlayerKeyset:
class PyGameGUI: 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): def __init__(
self,
simulator: Simulator,
player_names: list[str],
player_keys: list[pygame.key],
):
self.FPS = 60 self.FPS = 60
self.simulator = simulator self.simulator = simulator
self.counter_size = self.simulator.env.counter_side_length self.counter_size = self.simulator.env.counter_side_length
...@@ -51,20 +64,15 @@ class PyGameGUI: ...@@ -51,20 +64,15 @@ class PyGameGUI:
simulator.env.world_height, 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] = [ self.player_keysets: list[PlayerKeyset] = [
PlayerKeyset("p1", keys1), PlayerKeyset(player_name, keys)
PlayerKeyset("p2", keys2), for player_name, keys in zip(self.player_names, self.player_keys)
] ]
def send_action(self, action: Action): def send_action(self, action: Action):
...@@ -92,13 +100,6 @@ class PyGameGUI: ...@@ -92,13 +100,6 @@ class PyGameGUI:
action = Action(f"p{player_idx + 1}", "movement", move_vec) action = Action(f"p{player_idx + 1}", "movement", move_vec)
self.send_action(action) 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): def handle_key_event(self, event):
"""Handles key events for the pickup and interaction keys. Pickup is a single action, """Handles key events for the pickup and interaction keys. Pickup is a single action,
...@@ -112,6 +113,7 @@ class PyGameGUI: ...@@ -112,6 +113,7 @@ class PyGameGUI:
if event.key == keyset.pickup_key and event.type == pygame.KEYDOWN: if event.key == keyset.pickup_key and event.type == pygame.KEYDOWN:
action = Action(keyset.name, "pickup", "pickup") action = Action(keyset.name, "pickup", "pickup")
self.send_action(action) self.send_action(action)
if event.key == keyset.interact_key: if event.key == keyset.interact_key:
if event.type == pygame.KEYDOWN: if event.type == pygame.KEYDOWN:
action = Action(keyset.name, "interact", "keydown") action = Action(keyset.name, "interact", "keydown")
...@@ -164,16 +166,17 @@ class PyGameGUI: ...@@ -164,16 +166,17 @@ class PyGameGUI:
"""Visualisation of an item at the specified position. On a counter or in the hands of the player.""" """Visualisation of an item at the specified position. On a counter or in the hands of the player."""
if isinstance(item, Tomato): if isinstance(item, Tomato):
if item.finished: if item.finished:
IMAGE = pygame.image.load( image = pygame.image.load(
"overcooked_simulator/pygame_gui/images/tomato_cut.png" "overcooked_simulator/pygame_gui/images/tomato_cut.png"
).convert_alpha() # or .convert_alpha() ).convert_alpha() # or .convert_alpha()
else: else:
IMAGE = pygame.image.load( image = pygame.image.load(
"overcooked_simulator/pygame_gui/images/tomato.png" "overcooked_simulator/pygame_gui/images/tomato.png"
).convert_alpha() # or .convert_alpha() ).convert_alpha() # or .convert_alpha()
rect = IMAGE.get_rect() rect = image.get_rect()
rect.center = pos rect.center = pos
self.screen.blit(IMAGE, rect) self.screen.blit(image, rect)
if isinstance(item, ProgressibleItem) and not item.finished: if isinstance(item, ProgressibleItem) and not item.finished:
self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed) self.draw_progress_bar(pos, item.progressed_steps, item.steps_needed)
...@@ -261,7 +264,7 @@ class PyGameGUI: ...@@ -261,7 +264,7 @@ class PyGameGUI:
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
running = False 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_key_event(event)
self.handle_keys() self.handle_keys()
......
...@@ -87,7 +87,7 @@ class Simulator(Thread): ...@@ -87,7 +87,7 @@ class Simulator(Thread):
step_duration = time.time_ns() - step_start step_duration = time.time_ns() - step_start
time_to_sleep_ns = self.prefered_sleeptime_ns - ( time_to_sleep_ns = self.prefered_sleeptime_ns - (
step_duration + overslept_in_ns step_duration + overslept_in_ns
) )
sleep_start = time.time_ns() sleep_start = time.time_ns()
......
...@@ -78,7 +78,7 @@ def test_movement(): ...@@ -78,7 +78,7 @@ def test_movement():
expected = start_pos + do_moves_number * (move_direction * player1.move_dist) expected = start_pos + do_moves_number * (move_direction * player1.move_dist)
assert ( 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?" ), "Should be here?"
...@@ -159,7 +159,7 @@ def test_pickup(): ...@@ -159,7 +159,7 @@ def test_pickup():
sim.enter_action(pick) sim.enter_action(pick)
assert ( assert (
player.holding is not None player.holding is not None
), "Player should be too far away to put tomato down." ), "Player should be too far away to put tomato down."
sim.enter_action(move_down) sim.enter_action(move_down)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment