Skip to content
Snippets Groups Projects
Commit 78a99f11 authored by Florian Schröder's avatar Florian Schröder
Browse files

Merge remote-tracking branch 'origin/main' into 87-more-maps

# Conflicts:
#	overcooked_simulator/overcooked_environment.py
parents 0e89e702 60ddf871
No related branches found
No related tags found
1 merge request!54Resolve "More maps"
Pipeline #46127 passed
......@@ -623,7 +623,7 @@ class Trashcan(Counter):
return None
def can_drop_off(self, item: Item) -> bool:
return True
return item.name != "Extinguisher"
class CookingCounter(Counter):
......
......@@ -98,7 +98,8 @@ player_config:
player_speed_units_per_seconds: 6
interaction_range: 1.6
restricted_view: False
view_angle: 95
view_angle: 70
view_range: 5.5 # in grid units, can be "null"
effect_manager:
FireManager:
......
#QU#F###O#T#################N###L###B#
#____________________________________#
#____________________________________M
#____________________________________#
#____________________________________#
#____________________________________K
W____________________________________I
#____________________________________#
#____________________________________#
#__A_____A___________________________D
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
C____________________________________E
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
#____________________________________#
C____________________________________G
#____________________________________#
#P#####S+####X#####S+#################
\ No newline at end of file
......@@ -45,11 +45,12 @@ def grayscale(img):
return surface
def create_polygon(n, length):
def create_polygon(n, start_vec):
if n == 1:
return np.array([0, 0])
vector = np.array([length, 0])
vector = start_vec.copy()
angle = (2 * np.pi) / n
rot_matrix = np.array(
......@@ -153,31 +154,66 @@ class Visualizer:
direction = pygame.math.Vector2(state["view_restriction"]["direction"])
pos = pygame.math.Vector2(state["view_restriction"]["position"])
angle = state["view_restriction"]["angle"] / 2
range = state["view_restriction"]["range"]
angle = min(angle, 180)
pos = pos * grid_size + pygame.math.Vector2([grid_size / 2, grid_size / 2])
rect_scale = max(width, height)
rect_scale = max(width, height) * 2
# rect_scale = 2 * grid_size
left_beam = pos + (direction.rotate(angle) * rect_scale * 2)
right_beam = pos + (direction.rotate(-angle) * rect_scale * 2)
pygame.draw.polygon(
screen,
colors["black"],
(
pos - (direction * grid_size * 0.6),
left_beam - (direction * grid_size * 0.6),
left_beam + (direction.rotate(90) * rect_scale),
offset_front = direction * grid_size * 0.7
if angle != 180:
pygame.draw.polygon(
screen,
colors["black"],
(
pos - offset_front,
left_beam - offset_front,
left_beam + (direction.rotate(90) * rect_scale),
pos
- (direction * rect_scale * 2)
+ (direction.rotate(90) * rect_scale),
pos
- (direction * rect_scale * 2)
+ (direction.rotate(-90) * rect_scale),
right_beam + (direction.rotate(-90) * rect_scale),
right_beam - offset_front,
),
)
if range:
n_circle_points = 40
start_vec = np.array(-direction * range)
points = (
np.array(create_polygon(n_circle_points, start_vec)) * grid_size
) + pos
circle_closed = np.concatenate([points, points[0:1]], axis=0)
corners = [
pos - (direction * rect_scale),
*circle_closed,
pos - (direction * rect_scale),
pos
- (direction * rect_scale * 2)
- (direction * rect_scale)
+ (direction.rotate(90) * rect_scale),
pos
- (direction * rect_scale * 2)
+ (direction * rect_scale)
+ (direction.rotate(90) * rect_scale),
pos
+ (direction * rect_scale)
+ (direction.rotate(-90) * rect_scale),
right_beam + (direction.rotate(-90) * rect_scale),
right_beam - (direction * grid_size * 0.6),
),
)
pos
- (direction * rect_scale)
+ (direction.rotate(-90) * rect_scale),
]
pygame.draw.polygon(screen, colors["black"], [*corners])
def draw_background(
self, surface: pygame.Surface, width: int, height: int, grid_size: int
......
......@@ -30,6 +30,7 @@ from overcooked_simulator.utils import (
url_and_port_arguments,
disable_websocket_logging_arguments,
add_list_of_manager_ids_arguments,
setup_logging,
)
......@@ -60,8 +61,11 @@ class PlayerKeySet:
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.
move_keys: The keys which control this players movement in the following order: Down, Up, Left, Right.
interact_key: The key to interact with objects in the game.
pickup_key: The key to pick items up or put them down.
switch_key: The key for switching through controllable players.
players: The player indices which this keyset can control.
"""
self.move_vectors: list[list[int]] = [[-1, 0], [1, 0], [0, -1], [0, 1]]
self.key_to_movement: dict[pygame.key, list[int]] = {
......@@ -100,6 +104,11 @@ class PyGameGUI:
port: int,
manager_ids: list[str],
):
pygame.init()
pygame.display.set_icon(
pygame.image.load(ROOT_DIR / "gui_2d_vis" / "images" / "fish3.png")
)
self.game_screen: pygame.Surface = None
self.FPS = 60
self.running = True
......@@ -119,72 +128,73 @@ class PyGameGUI:
self.screen_margin = self.visualization_config["GameWindow"]["screen_margin"]
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.order_bar_height = self.visualization_config["GameWindow"][
"order_bar_height"
]
self.window_width = self.min_width
self.window_height = self.min_height
self.main_window = pygame.display.set_mode(
(self.window_width, self.window_height)
)
# self.game_width, self.game_height = 0, 0
(
self.window_width_fullscreen,
self.window_height_fullscreen,
) = pygame.display.get_desktop_sizes()[0]
self.window_width_windowed = self.min_width
self.window_height_windowed = self.min_height
self.kitchen_width = 1
self.kitchen_height = 1
self.kitchen_aspect_ratio = 1
self.images_path = ROOT_DIR / "pygame_gui" / "images"
self.vis = Visualizer(self.visualization_config)
self.fullscreen = False
self.menu_state = MenuStates.Start
self.manager: pygame_gui.UIManager
self.vis = Visualizer(self.visualization_config)
self.sub_processes = []
def get_window_sizes(self, state: dict):
kitchen_width = state["kitchen"]["width"]
kitchen_height = state["kitchen"]["height"]
if self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_width":
game_width = self.visualization_config["GameWindow"]["size"]
kitchen_aspect_ratio = kitchen_height / kitchen_width
game_height = int(game_width * kitchen_aspect_ratio)
grid_size = int(game_width / (kitchen_width - 0.1))
elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_height":
game_height = self.visualization_config["GameWindow"]["size"]
kitchen_aspect_ratio = kitchen_width / kitchen_height
game_width = int(game_height * kitchen_aspect_ratio)
grid_size = int(game_width / (kitchen_width - 0.1))
elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "grid":
grid_size = self.visualization_config["GameWindow"]["size"]
game_width, game_height = (
kitchen_width * grid_size,
kitchen_height * grid_size,
)
def get_window_sizes_from_state(self, state: dict):
self.kitchen_width = state["kitchen"]["width"]
self.kitchen_height = state["kitchen"]["height"]
self.kitchen_aspect_ratio = self.kitchen_height / self.kitchen_width
game_width = self.visualization_config["GameWindow"]["min_width"] - (
2 * self.screen_margin
)
game_height = self.visualization_config["GameWindow"]["min_height"] - (
2 * self.screen_margin
)
if self.kitchen_width > game_width:
self.game_height = game_width * self.kitchen_aspect_ratio
self.grid_size = game_width / self.kitchen_width
else:
game_width, game_height = 0, 0
grid_size = 0
self.game_width = game_height / self.kitchen_aspect_ratio
self.grid_size = game_width / self.kitchen_width
window_width, window_height = (
game_width + (2 * self.screen_margin),
game_height + (2 * self.screen_margin), # bar with orders
)
self.window_width_windowed = self.min_width
self.window_height_windowed = self.min_height
window_width = max(window_width, self.min_width)
window_height = max(window_height, self.min_height)
return (
int(window_width),
int(window_height),
int(game_width),
int(game_height),
grid_size,
)
def recalc_game_size(self):
log.debug("Resizing game screen")
max_width = self.window_width - (2 * self.screen_margin)
max_height = self.window_height - (2 * self.screen_margin)
if max_width < max_height:
self.game_width = max_width
self.game_height = max_width * self.kitchen_aspect_ratio
self.grid_size = int(self.game_height / self.kitchen_height)
else:
self.game_height = max_height
self.game_width = max_height / self.kitchen_aspect_ratio
self.grid_size = int(self.game_width / self.kitchen_width)
self.game_width = max(self.game_width, 100)
self.game_height = max(self.game_height, 100)
self.grid_size = max(self.grid_size, 1)
residual_x = self.game_width - (self.kitchen_width * self.grid_size)
residual_y = self.game_height - (self.kitchen_height * self.grid_size)
self.game_width -= residual_x
self.game_height -= residual_y
def setup_player_keys(self, n=1, disjunct=False):
if n:
......@@ -239,7 +249,7 @@ class PyGameGUI:
current_player_name,
ActionType.MOVEMENT,
move_vec,
duration=1 / self.FPS,
duration=self.time_delta,
)
self.send_action(action)
......@@ -304,17 +314,27 @@ class PyGameGUI:
)
self.quit_button.can_hover()
fullscreen_button_rect = pygame.Rect(
(0, 0), (self.buttons_width * 0.7, self.buttons_height)
)
fullscreen_button_rect.topright = (-self.buttons_width, 0)
self.fullscreen_button = pygame_gui.elements.UIButton(
relative_rect=fullscreen_button_rect,
text="Fullscreen",
manager=self.manager,
object_id="#fullscreen_button",
anchors={"right": "right", "top": "top"},
)
self.fullscreen_button.can_hover()
reset_button_rect = pygame.Rect((0, 0), (self.screen_margin * 0.75, 50))
reset_button_rect.topright = (0, 2 * self.buttons_height)
self.reset_button = pygame_gui.elements.UIButton(
relative_rect=pygame.Rect(
(
self.window_width - (self.screen_margin * 3 // 4),
self.screen_margin,
),
(self.screen_margin - (self.screen_margin // 4), 50),
),
relative_rect=reset_button_rect,
text="RESET",
manager=self.manager,
object_id="#reset_button",
anchors={"right": "right", "top": "top"},
)
self.reset_button.can_hover()
......@@ -584,8 +604,8 @@ class PyGameGUI:
self.vis.draw_orders(
screen=self.main_window,
state=state,
grid_size=self.grid_size,
width=self.game_width,
grid_size=self.buttons_height,
width=self.window_width - self.buttons_width - (self.buttons_width * 0.7),
height=self.game_height,
screen_margin=self.screen_margin,
config=self.visualization_config,
......@@ -612,18 +632,28 @@ class PyGameGUI:
(
self.game_width,
self.game_height,
),
)
)
if self.fullscreen:
flags = pygame.FULLSCREEN
self.window_width = self.window_width_fullscreen
self.window_height = self.window_height_fullscreen
else:
flags = 0
self.window_width = self.window_width_windowed
self.window_height = self.window_height_windowed
self.main_window = pygame.display.set_mode(
(
self.window_width,
self.window_height,
)
),
flags=flags,
display=0,
)
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()
......@@ -799,13 +829,7 @@ class PyGameGUI:
)
state = json.loads(websocket.recv())
(
self.window_width,
self.window_height,
self.game_width,
self.game_height,
self.grid_size,
) = self.get_window_sizes(state)
self.get_window_sizes_from_state(state)
def start_button_press(self):
self.menu_state = MenuStates.Game
......@@ -826,13 +850,11 @@ class PyGameGUI:
self.setup_environment()
self.recalc_game_size()
self.set_window_size()
self.init_ui_elements()
log.debug("Pressed start button")
# self.api.set_sim(self.simulator)
def back_button_press(self):
self.menu_state = MenuStates.Start
self.reset_window_size()
......@@ -966,14 +988,13 @@ class PyGameGUI:
def start_pygame(self):
"""Starts pygame and the gui loop. Each frame the game state is visualized and keyboard inputs are read."""
log.debug(f"Starting pygame gui at {self.FPS} fps")
pygame.init()
pygame.font.init()
pygame.display.set_caption("Simple Overcooked Simulator")
clock = pygame.time.Clock()
self.reset_window_size()
self.init_ui_elements()
self.manage_button_visibility()
......@@ -983,13 +1004,22 @@ class PyGameGUI:
self.running = True
while self.running:
try:
time_delta = clock.tick(self.FPS) / 1000.0
self.time_delta = clock.tick(self.FPS) / 1000
# print(clock.get_time())
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
# UI Buttons:
# elif event.type == pygame.VIDEORESIZE:
# # scrsize = event.size
# self.window_width_windowed = event.w
# self.window_height_windowed = event.h
# self.recalc_game_size()
# self.set_window_size()
# self.init_ui_elements()
# self.manage_button_visibility()
if event.type == pygame_gui.UI_BUTTON_PRESSED:
match event.ui_element:
case self.start_button:
......@@ -999,6 +1029,7 @@ class PyGameGUI:
):
continue
self.start_button_press()
case self.back_button:
self.back_button_press()
self.disconnect_websockets()
......@@ -1006,9 +1037,11 @@ class PyGameGUI:
case self.finished_button:
self.finished_button_press()
self.disconnect_websockets()
case self.quit_button:
self.quit_button_press()
self.disconnect_websockets()
case self.reset_button:
self.reset_button_press()
self.disconnect_websockets()
......@@ -1039,6 +1072,18 @@ class PyGameGUI:
case self.xbox_controller_button:
print("xbox_controller_button pressed.")
case self.fullscreen_button:
self.fullscreen = not self.fullscreen
if self.fullscreen:
self.window_width = self.window_width_fullscreen
self.window_height = self.window_height_fullscreen
else:
self.window_width = self.window_width_windowed
self.window_height = self.window_height_windowed
self.recalc_game_size()
self.set_window_size()
self.init_ui_elements()
self.update_selection_elements()
self.manage_button_visibility()
......@@ -1086,7 +1131,7 @@ class PyGameGUI:
case MenuStates.End:
self.update_conclusion_label(state)
self.manager.update(time_delta)
self.manager.update(self.time_delta)
pygame.display.flip()
except (KeyboardInterrupt, SystemExit):
......@@ -1098,6 +1143,7 @@ class PyGameGUI:
def main(url: str, port: int, manager_ids: list[str]):
setup_logging()
gui = PyGameGUI(
url=url,
port=port,
......@@ -1117,4 +1163,4 @@ if __name__ == "__main__":
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
args = parser.parse_args()
main(args.url, args.port, args.manager_ids, args.enable_websocket_logging)
main(args.url, args.port, args.manager_ids)
# colors: https://www.webucator.com/article/python-color-constants-module/
GameWindow:
WhatIsFixed: grid # grid or window_width or window_height
size: 50
screen_margin: 100
min_width: 700
min_width: 900
min_height: 600
buttons_width: 180
buttons_height: 60
......
......@@ -167,6 +167,9 @@ class Environment:
self.player_view_angle = self.environment_config["player_config"][
"view_angle"
]
self.player_view_range = self.environment_config["player_config"][
"view_range"
]
self.extra_setup_functions()
......@@ -236,8 +239,6 @@ class Environment:
) = self.parse_layout_file()
self.hook(LAYOUT_FILE_PARSED)
self.counter_positions = np.array([c.pos for c in self.counters])
self.world_borders = np.array(
[[-0.5, self.kitchen_width - 0.5], [-0.5, self.kitchen_height - 0.5]],
dtype=float,
......@@ -247,6 +248,9 @@ class Environment:
"player_speed_units_per_seconds"
]
self.player_radius = self.environment_config["player_config"]["radius"]
self.player_interaction_range = self.environment_config["player_config"][
"interaction_range"
]
progress_counter_classes = list(
filter(
......@@ -266,6 +270,8 @@ class Environment:
)
"""Counters that needs to be called in the step function via the `progress` method."""
self.counter_positions = np.array([c.pos for c in self.counters])
self.order_and_score.create_init_orders(self.env_time)
self.start_time = self.env_time
"""The relative env time when it started."""
......@@ -287,6 +293,27 @@ class Environment:
env_start_time_worldtime=datetime.now(),
)
def overwrite_counters(self, counters):
self.counters = counters
self.counter_positions = np.array([c.pos for c in self.counters])
progress_counter_classes = list(
filter(
lambda cl: hasattr(cl, "progress"),
dict(
inspect.getmembers(
sys.modules["overcooked_simulator.counters"], inspect.isclass
)
).values(),
)
)
self.progressing_counters = list(
filter(
lambda c: c.__class__ in progress_counter_classes,
self.counters,
)
)
@property
def game_ended(self) -> bool:
"""Whether the game is over or not based on the calculated `Environment.env_time_end`"""
......@@ -665,9 +692,21 @@ class Environment:
for idx, p in enumerate(self.players.values()):
if not (new_positions[idx] == player_positions[idx]).all():
p.move_abs(new_positions[idx])
p.pos = new_positions[idx]
p.perform_interact_stop()
p.turn(player_movement_vectors[idx])
facing_distances = np.linalg.norm(
p.facing_point - self.counter_positions, axis=1
)
closest_counter = self.counters[facing_distances.argmin()]
p.current_nearest_counter = (
closest_counter
if facing_distances.min() <= self.player_interaction_range
else None
)
def add_player(self, player_name: str, pos: npt.NDArray = None):
"""Add a player to the environment.
......@@ -782,6 +821,7 @@ class Environment:
"position": self.players[player_id].pos.tolist(),
"angle": self.player_view_angle,
"counter_mask": None,
"range": self.player_view_range,
}
if self.player_view_restricted
else None,
......
......@@ -37,6 +37,9 @@ class PlayerConfig:
"""Whether or not the player can see the entire map at once or just a view frustrum."""
view_angle: int | None = None
"""Angle of the players view if restricted."""
view_range: float | None = None
"""Range of the players view if restricted. In grid units."""
class Player:
"""Class representing a player in the game environment. A player consists of a name, their position and what
......@@ -88,19 +91,7 @@ class Player:
function of the environment"""
self.current_movement = move_vector
self.movement_until = move_until
def move(self, movement: npt.NDArray[float]):
"""Moves the player position by the given movement vector.
A unit direction vector multiplied by move_dist is added to the player position.
Args:
movement: 2D-Vector of length 1
"""
if self.interacting and np.any(movement):
self.perform_interact_stop()
self.pos += movement
if np.linalg.norm(movement) != 0:
self.turn(movement)
self.perform_interact_stop()
def move_abs(self, new_pos: npt.NDArray[float]):
"""Overwrites the player location by the new_pos 2d-vector. Absolute movement.
......@@ -165,7 +156,7 @@ class Player:
self.holding.combine(returned_by_counter)
log.debug(
f"Self: {self.holding}, {counter.__class__.__name__}: {counter.occupied_by}"
f"Self: {self.holding.__class__.__name__}: {self.holding}, {counter.__class__.__name__}: {counter.occupied_by}"
)
# if isinstance(self.holding, Plate):
# log.debug(self.holding.clean)
......
......@@ -71,6 +71,7 @@ class ViewRestriction(BaseModel):
position: list[float]
angle: int # degrees
counter_mask: None | list[bool]
range: float | None
class StateRepresentation(BaseModel):
......
......@@ -124,7 +124,7 @@ def test_player_reach(env_config, layout_empty_config, item_info):
counter_pos = np.array([2, 2])
counter = Counter(pos=counter_pos, hook=Hooks(env))
env.counters = [counter]
env.overwrite_counters([counter])
env.add_player("1", np.array([2, 4]))
env.player_movement_speed = 1
player = env.players["1"]
......@@ -144,7 +144,7 @@ def test_pickup(env_config, layout_config, item_info):
counter_pos = np.array([2, 2])
counter = Counter(pos=counter_pos, hook=Hooks(env))
counter.occupied_by = Item(name="Tomato", item_info=None)
env.counters = [counter]
env.overwrite_counters([counter])
env.add_player("1", np.array([2, 3]))
player = env.players["1"]
......
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