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

Started decoupling pygame gui from simulator

parent c752b748
No related branches found
No related tags found
1 merge request!26Resolve "api"
##########
#________#
#________#
#________#
#________#
#________#
#________#
#________#
#########P
#QU#T###NLB#
#__________M
#____A_____#
W__________#
############
C__________#
C_____A____#
#__________X
#P#S+####S+#
\ No newline at end of file
import asyncio
import json
import logging
import os
import sys
import threading
from datetime import datetime
import numpy as np
from websockets.server import serve
from overcooked_simulator import ROOT_DIR
from overcooked_simulator.overcooked_environment import Action
from overcooked_simulator.simulation_runner import Simulator
log = logging.getLogger(__name__)
PORT = 8765
class Connector:
def __init__(self, simulator: Simulator):
self.simulator: Simulator = simulator
super().__init__()
async def process_message(self, websocket):
async for message in websocket:
if message.replace('"', "") != "get_state":
message_dict = json.loads(message)
if message_dict["act_type"] == "movement":
value = np.array(message_dict["value"])
else:
value = None
action = Action(
message_dict["player_name"], message_dict["act_type"], value
)
self.simulator.enter_action(action)
json_answer = self.simulator.get_state_simple_json()
# print("json:", json_answer, type(json_answer))
await websocket.send(json_answer)
async def connection_server(self):
async with serve(self.process_message, "localhost", PORT):
await asyncio.Future() # run forever
def set_sim(self, simulation_runner: Simulator):
self.simulator = simulation_runner
def start_connector(self):
asyncio.run(self.connection_server())
def setup_logging():
path_logs = ROOT_DIR.parent / "logs"
os.makedirs(path_logs, exist_ok=True)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(levelname)-8s %(name)-50s %(message)s",
handlers=[
logging.FileHandler(
path_logs / f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_debug.log",
encoding="utf-8",
),
logging.StreamHandler(sys.stdout),
],
)
def main():
simulator = Simulator(
ROOT_DIR / "game_content" / "environment_config.yaml",
ROOT_DIR / "game_content" / "layouts" / "basic.layout",
600,
)
number_player = 2
for i in range(number_player):
player_name = f"p{i}"
simulator.register_player(player_name)
connector = Connector(simulator)
connector.start_connector()
if __name__ == "__main__":
setup_logging()
try:
main()
except Exception as e:
log.exception(e)
for thread in threading.enumerate():
if isinstance(thread, Simulator):
thread.stop()
thread.join()
sys.exit(1)
import colorsys
import json
import logging
import math
import sys
......@@ -11,6 +12,7 @@ import pygame
import pygame_gui
import yaml
from scipy.spatial import KDTree
from websockets.sync.client import connect
from overcooked_simulator import ROOT_DIR
from overcooked_simulator.counters import Counter
......@@ -26,9 +28,9 @@ from overcooked_simulator.gui_2d_vis.game_colors import colors, Color
from overcooked_simulator.overcooked_environment import Action
from overcooked_simulator.simulation_runner import Simulator
USE_PLAYER_COOK_SPRITES = True
USE_PLAYER_COOK_SPRITES = False
SHOW_INTERACTION_RANGE = False
SHOW_COUNTER_CENTERS = False
SHOW_COUNTER_CENTERS = True
class MenuStates(Enum):
......@@ -90,13 +92,11 @@ class PyGameGUI:
def __init__(
self,
simulator: Simulator,
player_names: list[str],
player_keys: list[pygame.key],
):
self.game_screen = None
self.FPS = 60
self.simulator: Simulator = simulator
self.running = True
self.player_names = player_names
......@@ -109,6 +109,8 @@ class PyGameGUI:
)
]
self.websocket_url = "ws://localhost:8765"
# TODO cache loaded images?
with open(ROOT_DIR / "gui_2d_vis" / "visualization.yaml", "r") as file:
self.visualization_config = yaml.safe_load(file)
......@@ -134,22 +136,35 @@ class PyGameGUI:
self.manager: pygame_gui.UIManager
def init_window_sizes(self):
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
)
game_height = int(game_width * 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,
)
else:
game_width, game_height = 0, 0
grid_size = 0
state = self.request_state()
print("THIS:", state["counters"])
counter_positions = np.array([c["pos"] for c in state["counters"]])
print(counter_positions)
kitchen_width = np.max(counter_positions[:, 0]) + 0.5
kitchen_height = np.max(counter_positions[:, 1]) + 0.5
print(kitchen_width, kitchen_height)
# 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
# )
# game_height = int(game_width * 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,
# )
# else:
# game_width, game_height = 0, 0
# grid_size = 0
grid_size = 40
game_width = kitchen_width * grid_size
game_height = kitchen_height * grid_size
window_width, window_height = (
game_width + (2 * self.screen_margin),
......@@ -159,7 +174,7 @@ class PyGameGUI:
return window_width, window_height, game_width, game_height, grid_size
def create_player_colors(self) -> list[Color]:
number_player = len(self.simulator.env.players)
number_player = len(self.player_keys)
hue_values = np.linspace(0, 1, number_player + 1)
colors_vec = np.array([col for col in colors.values()])
......@@ -177,14 +192,6 @@ class PyGameGUI:
return player_colors
def send_action(self, action: Action):
"""Sends an action to the game environment.
Args:
action: The action to be sent. Contains the player, action type and move direction if action is a movement.
"""
self.simulator.enter_action(action)
def handle_keys(self):
"""Handles keyboard inputs. Sends action for the respective players. When a key is held down, every frame
an action is sent in this function.
......@@ -230,8 +237,8 @@ class PyGameGUI:
self.game_screen.fill(
colors[self.visualization_config["Kitchen"]["ground_tiles_color"]]
)
for x in range(0, self.window_width, block_size):
for y in range(0, self.window_height, block_size):
for x in range(0, int(self.window_width), block_size):
for y in range(0, int(self.window_height), block_size):
rect = pygame.Rect(x, y, block_size, block_size)
pygame.draw.rect(
self.game_screen,
......@@ -257,7 +264,7 @@ class PyGameGUI:
rect.center = pos
self.game_screen.blit(image, rect)
def draw_players(self, state):
def draw_players(self, state, state_dict):
"""Visualizes the players as circles with a triangle for the facing direction.
If the player holds something in their hands, it is displayed
......@@ -265,11 +272,14 @@ class PyGameGUI:
state: The game state returned by the environment.
"""
for p_idx, player in enumerate(state["players"].values()):
pos = player.pos * self.grid_size
# pos = player.pos * self.grid_size
pos = np.array(state_dict["players"][p_idx]["pos"]) * self.grid_size
facing = np.array(state_dict["players"][p_idx]["facing"])
if USE_PLAYER_COOK_SPRITES:
img_path = self.visualization_config["Cook"]["parts"][0]["path"]
rel_x, rel_y = player.facing_direction
rel_x, rel_y = pos = facing
angle = -np.rad2deg(math.atan2(rel_y, rel_x)) + 90
size = (
self.visualization_config["Cook"]["parts"][0]["size"]
......@@ -286,7 +296,7 @@ class PyGameGUI:
pygame.draw.circle(self.game_screen, BLUE, pos, size, width=1)
pygame.draw.circle(self.game_screen, colors[color1], pos, size // 2)
facing = player.facing_direction
facing = facing
pygame.draw.polygon(
self.game_screen,
BLUE,
......@@ -475,9 +485,11 @@ class PyGameGUI:
for counter in state["counters"]:
self.draw_counter(counter)
if SHOW_COUNTER_CENTERS:
pygame.draw.circle(self.game_screen, colors["green1"], counter.pos, 3)
pygame.draw.circle(
self.game_screen, colors["green1"], counter.pos * self.grid_size, 3
)
def draw(self, state):
def draw(self, state, state_dict):
"""Main visualization function.
Args:
......@@ -487,7 +499,7 @@ class PyGameGUI:
self.draw_background()
self.draw_counters(state)
self.draw_players(state)
self.draw_players(state, state_dict)
self.manager.draw_ui(self.game_screen)
def init_ui_elements(self):
......@@ -654,6 +666,8 @@ class PyGameGUI:
self.init_ui_elements()
log.debug("Pressed start button")
# self.api.set_sim(self.simulator)
def back_button_press(self):
self.simulator.stop()
self.menu_state = MenuStates.Start
......@@ -669,6 +683,35 @@ class PyGameGUI:
self.menu_state = MenuStates.End
log.debug("Pressed finished button")
def send_action(self, action: Action):
"""Sends an action to the game environment.
Args:
action: The action to be sent. Contains the player, action type and move direction if action is a movement.
"""
if isinstance(action.action, np.ndarray):
value = [float(action.action[0]), float(action.action[1])]
else:
value = action.action
message_dict = {
"player_name": action.player,
"act_type": action.act_type,
"value": value,
}
_ = self.websocket_communicate(message_dict)
def websocket_communicate(self, message_dict: dict | str):
with connect(self.websocket_url) as websocket:
print(message_dict)
websocket.send(json.dumps(message_dict))
answer = websocket.recv()
print(answer)
return json.loads(answer)
def request_state(self):
state_dict = self.websocket_communicate("get_state")
return state_dict
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")
......@@ -727,8 +770,9 @@ class PyGameGUI:
self.handle_keys()
state_dict = self.request_state()
state = self.simulator.get_state()
self.draw(state)
self.draw(state, state_dict)
game_screen_rect = self.game_screen.get_rect()
game_screen_rect.center = [
......@@ -753,3 +797,20 @@ class PyGameGUI:
self.simulator.stop()
pygame.quit()
sys.exit()
if __name__ == "__main__":
# 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]
number_players = 2
gui = PyGameGUI([f"p{i}" for i in range(number_players)], [keys1, keys2])
gui.start_pygame()
......@@ -51,10 +51,14 @@ def main():
]
keys2 = [pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s, pygame.K_f, pygame.K_e]
gui = PyGameGUI(simulator, [f"p{i}" for i in range(number_player)], [keys1, keys2])
gui = PyGameGUI(
simulator, None, [f"p{i}" for i in range(number_player)], [keys1, keys2]
)
gui.start_pygame()
sys.exit()
print("HERE?")
if __name__ == "__main__":
setup_logging()
......
from __future__ import annotations
import json
import logging
import random
from datetime import timedelta
......@@ -177,6 +178,7 @@ class Environment:
for character in line:
character = character.capitalize()
pos = np.array([current_x, current_y])
print(pos)
counter_class = self.SYMBOL_TO_CHARACTER_MAP[character]
if not isinstance(counter_class, str):
counter = counter_class(pos)
......@@ -450,13 +452,31 @@ class Environment:
"""
return {"players": self.players, "counters": self.counters, "score": self.score}
def get_state_json(self):
def get_state_simple_json(self):
"""Get the current state of the game environment as a json-like nested dictionary.
Returns: Json-like string of the current game state.
"""
pass
players = [
{
"pos": [float(p.pos[0]), float(p.pos[1])],
"facing": [float(p.facing_direction[0]), float(p.facing_direction[1])],
}
for p in self.players.values()
]
counters = [
{"type": str(c.__class__), "pos": [float(c.pos[0]), float(c.pos[1])]}
for c in self.counters
]
gamestate_dict = {
"players": players,
"counters": counters,
}
answer = json.dumps(gamestate_dict)
return answer
def init_counters(self):
plate_dispenser = self.get_counter_of_type(PlateDispenser)
......
......@@ -66,14 +66,14 @@ class Simulator(Thread):
return self.env.get_state()
def get_state_json(self):
def get_state_simple_json(self):
"""Get the current game state in json-like dict.
Returns:
The gamest ate encoded in a json style nested dict.
"""
return self.env.get_state_json()
return self.env.get_state_simple_json()
def register_player(self, player_name: str, pos=None):
"""Adds a player to the environment.
......
......@@ -10,7 +10,15 @@ with open("README.md") as readme_file:
with open("CHANGELOG.md") as history_file:
history = history_file.read()
requirements = ["numpy", "pygame", "scipy", "pytest>=3", "pyyaml", "pygame-gui"]
requirements = [
"numpy",
"pygame",
"scipy",
"pytest>=3",
"pyyaml",
"pygame-gui",
"websockets",
]
test_requirements = [
"pytest>=3",
......
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