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

Added basic class structure. Main simulation runner, basic environment...

Added basic class structure. Main simulation runner, basic environment structure, players, actions, counters.
parent 889a68c4
No related branches found
No related tags found
1 merge request!1Issue #6 Base simulator class and methods
Pipeline #41141 passed
EEEEEEEEEEE
ECCCCCCCCCE
ECEEEEEEECE
ECEEEEEEECE
ECEEEEEEECE
ECEEEEEEECE
ECEEEEEEECE
ECEEEEEEECE
ECEEEEEEECE
ECCCCCCCCCE
EEEEEEEEEEE
from player import Player
import sys
from simulation_runner import Simulator
def main():
simulator = Simulator()
simulator.register_player(Player("p1", [100, 200]))
simulator.register_player(Player("p2", [200, 100]))
simulator.start()
print(simulator.get_state())
simulator.stop()
sys.exit()
if __name__ == "__main__":
main()
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from player import Player
from pathlib import Path
import numpy as np
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 __repr__(self):
return f"Counter(pos:{str(self.pos)},holds:{self.occupied_by})"
class Action:
"""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"
self.action = action
class Environment:
"""Environment class which handles the game logic for the overcooked-inspired environment.
Handles player movement, collision-detection, counters, cooking processes, recipes, incoming orders, time.
"""
def __init__(self):
self.players = {}
self.counter_side_length = 40
self.layout_path = Path("overcooked_simulator/layouts/basic.layout")
self.counters = self.create_counters(self.layout_path)
self.score = 0
def create_counters(self, layout_file: Path):
"""Creates layout of kitchen counters in the environment based on layout file.
Counters are arranged in a fixed size grid starting at [0,0]. The center of the first counter is at
[counter_size/2, counter_size/2], counters are directly next to each other (of no empty space is specified
in layout).
Args:
layout_file: Path to the layout file.
"""
current_y = self.counter_side_length / 2
counters = []
with open(layout_file, "r") as layout_file:
lines = layout_file.readlines()
for line in lines:
line = line.replace("\n", "").replace(" ", "") # remove newline char
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)
current_x += self.counter_side_length
elif character == "E":
pass
current_y += self.counter_side_length
return counters
def perform_action(self, action: Action):
"""Performs an action of a player in the environment. Maps different types of action inputs to the
correct execution of the players.
Possible action types are movement, pickup and interact actions.
Args:
action: The action to be performed
"""
assert action.player in self.players.keys(), "Unknown player."
player = self.players[action.player]
if action.act_type == "movement":
self.perform_movement(player, action.action)
elif action.act_type == "pickup":
self.perform_pickup(player)
elif action.act_type == "interact":
self.perform_interact(player)
def get_closest_counter(self, player: Player):
"""Determines the closest counter in the environment of a player.
Args:
player: The player for which to find the closest counter
Returns: The closest counter for the given player.
"""
pass
def perform_pickup(self, player: Player):
"""Performs the game action corresponding to picking up an item
Args:
player: The player which performs the pickup action.
Returns: TODO?
"""
pass
def perform_interact(self, player: Player):
"""Performs the game action corresponding to interacting with a counter or other object.
Args:
player: The player which performs the interaction.
Returns: TODO?
"""
pass
def perform_movement(self, player: Player, action):
"""Moves a player in the direction specified in the action.action. If the player collides with a
counter or other player through this movement, then they are not moved.
Args:
player: The player to move.
action: The Action which now contains a unit-2d-vector of the movement direction
"""
pass
def detect_collision(self, player: Player):
"""Detect collisions between the player and other players or counters.
Args:
player: The player for which to check collisions.
Returns: True if the player is intersecting with any object in the environment.
"""
pass
def step(self):
"""Performs a step of the environment. Affects time based events such as cooking or cutting things, orders
and timelimits.
"""
pass
def get_state(self):
"""Get the current state of the game environment. The state here is accessible by the current python objects.
Returns: Dict of lists of the current relevant game objects.
"""
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.
Returns: Json-like string of the current game state.
"""
pass
import numpy as np
from overcooked_environment import Counter
class Player:
"""Class representing a player in the game environment. A player consists of a name, their position and what
the player is currently holding in the hands.
This class handles interactions with counters and objects.
"""
def __init__(self, name, pos):
self.name = name
self.pos = np.array(pos, dtype=float)
self.holding = None
self.move_dist = 5
self.facing_direction = np.array([0, 1])
def move(self, movement: np.array):
"""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
"""
pass
def move_abs(self, new_pos: np.array):
"""Overwrites the player location by the new_pos 2d-vector. Absolute movement.
Mostly needed for resetting the player after a collision.
Args:
new_pos: 2D-Vector of the new player position.
"""
pass
def turn(self, direction: np.array):
"""Turns the player in the given direction. Overwrites the facing_direction by a given 2d-vector.
facing_direction is normalized to length 1.
Args:
direction: 2D-Vector of the direction for the player to face.
"""
pass
def pick_action(self, counter: Counter):
"""Performs the pickup-action with the counter. Handles the logic of what the player is currently holding,
what is currently on the counter and what can be picked up or combined in hand.
Args:
counter: The counter to pick things up from or put things down.
"""
pass
def interact(self, counter: Counter):
"""Performs the interact-action with the counter. Handles logic of starting processes
like for e.g. cutting onions. TODO holding the button vs pressing once?
Args:
counter: The counter to interact with.
"""
pass
def __repr__(self):
return f"Player(name:{self.name},pos:{str(self.pos)},holds:{self.holding})"
from threading import Thread
import time
from overcooked_environment import Environment, Action
from player import Player
class Simulator(Thread):
"""Simulator main class which runs manages the environment and player inputs and gamestate outputs.
Main Simulator class which runs the game environment. Players can be registered in the game.
The simulator is run as its own thread.
Typical usage example:
sim = Simulator()
sim.register_player(Player("p1", [x,y]))
sim.start()
"""
def __init__(self):
self.finished: bool = False
self.step_frequency: float = 300
self.prefered_sleeptime_ns: float = 1e9 / self.step_frequency
self.env: Environment = Environment()
super().__init__()
def step(self):
"""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.
"""
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.
"""
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.
"""
return self.env.get_state_json()
def register_player(self, player: Player):
"""Adds a player to the environment.
Args:
player: The player to be added.
"""
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.
"""
for p in players:
self.register_player(p)
def run(self):
"""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)
sleep_start = time.time_ns()
time.sleep(max(time_to_sleep_ns / 1e9, 0))
sleep_function_duration = time.time_ns() - sleep_start
overslept_in_ns = sleep_function_duration - time_to_sleep_ns
def stop(self):
"""Stops the simulator
"""
self.finished = True
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