Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customers #35

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Binary file added assets/customer_temp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/customerspawn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/customertable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
219 changes: 219 additions & 0 deletions backend/customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
from backend.object import Object
from backend.predicate import Predicate
from robotouille.env_utils import build_goal

class Customer(object):
"""
The Customer class represents a customer in Robotouille.

Customers are non-player characters that require a player to deliver some
order to them.
"""

customers = {}
id_counter = 0
customer_queue = []

def __init__(self, name, pos, direction, order, time_to_serve, enter_time):
"""
Initializes the customer object.

Args:
name (str): The name of the customer.
pos (tuple): The position of the customer in the form (x, y).
direction (tuple): The unit vector of the customer's direction.
order (List[Predicate]): The order the customer requires.
time_to_serve (int): The time left that the player has to serve the
customer in milliseconds.
enter_time (int): The time at which the customer enters the game
in milliseconds.
"""
self.id = Customer.id_counter
self.name = name
self.pos = pos
self.direction = direction
self.order = order
self.time_to_serve = time_to_serve
self.enter_time = enter_time
self.has_been_served = False
self.in_game = False
self.has_left_queue = False
self.sprite_value = 0
self.action = None
Customer.id_counter += 1
Customer.customers[name] = self

def build_order(order_name, environment_json, recipe_json):
"""
Builds an order depending on the recipe json by creating a list of
predicates.

Args:
order_name (str): The name of the recipe
environment_json (dict): The environment json.
recipe_json (dict): The recipe json

Returns:
order (List[Predicate]): The order the customer requires.
"""
return build_goal(environment_json, recipe_json["recipes"][order_name])[0]

def build_customers(environment_json, recipe_json):
"""
Builds the customers in the environment.

Args:
environment_json (dict): The environment json.
recipe_json (dict): The recipe_json

Returns:
customers (list[Object]): A list of customer objects to be added to
the state.

Modifies:
Adds customers to Customer.customers
"""
customers = []
if environment_json.get("customers"):
for customer in environment_json["customers"]:
name = customer["name"]
pos = (customer["x"], customer["y"])
direction = (customer["direction"][0], customer["direction"][1])
order = Customer.build_order(customer["order"], environment_json, recipe_json)
time_to_serve = customer["time_to_serve"]
enter_time = customer["enter_time"]
customer = Customer(name, pos, direction, order, time_to_serve, enter_time)
customer_obj = Object(customer.name, "customer")
customers.append(customer_obj)
return customers

def get_order(self):
"""
Gets the order of the customer.

Returns:
order (List[Predicate]): The order the customer requires.
"""
return self.order

def order_is_satisfied(self, state):
"""
Checks if the order is satisfied.

Args:
state (State): The state of the game.

Returns:
bool: True if the order is satisfied, False otherwise.
"""
result = True
for predicate in self.order:
result &= state.get_predicate_value(predicate)
return result

def _get_empty_table(self, state):
"""
Checks if a table is available for the customer.

Args:
state (State): The state of the game.

Returns:
table (Object): Returns the first empty table or None if no tables
are available.
"""
customer_tables = []

for predicate, value in state.predicates.items():
if predicate.name == "iscustomertable" and value:
customer_tables.append(predicate.params[0])

for predicate, value in state.predicates.items():
if predicate.name == "table_occupied" and not value and predicate.params[0] in customer_tables:
return predicate.params[0]
return None

def _is_at_table(self, state):
"""
Checks if the customer is at a table.

Args:
state (State): The state of the game.

Returns:
bool: True if the customer is at a table, False otherwise.
"""
result = False
for predicate, value in state.predicates.items():
if predicate.name == "customer_loc" and predicate.params[0] == Object(self.name, "customer") and value:
result = True
return result

def step(self, time, state):
"""
Steps the customer.

If the customer has not entered the game, checks if the customer should
enter the game and into a queue.

Checks if there are any available tables for the customer to sit at. If
there are, the customer moves to the table.

If the customer has sat down at a table, decrements the
time left to serve the customer. Checks if the customer has been served,
and if the time to serve the customer has expired.

Updates GameMode class according to whether the customer has been served,
if they have been served the right order, and if the time to serve the
customer has expired.

Args:
time (pygame.time): The time object.
state (State): The game state.

Returns:
action (Tuple[Action, Dictionary[str, Object]]): The action performed
by the customer.
"""
table_to_move_to = None

if self.action:
return None

if self._is_at_table(state):
clock = time.Clock()
self.time_to_serve -= clock.get_time()
if self.order_is_satisfied(state):
self.has_been_served = True

elif not self.has_left_queue and self not in Customer.customer_queue \
and time.get_ticks() >= self.enter_time:
Customer.customer_queue.append(self)
for action, param_arg_dict_list in state.npc_actions.items():
if action.name == "customer_enter":
for param_arg_dict in param_arg_dict_list:
if param_arg_dict["c1"].name == self.name:
assert action.is_valid(state, param_arg_dict)
self.in_game = True
return (action, param_arg_dict)

elif len(Customer.customer_queue) > 0:
if Customer.customer_queue[0] == self:
table_to_move_to = self._get_empty_table(state)
if table_to_move_to:
Customer.customer_queue.pop()

# Customer can either leave or move to a table, but not both
assert not (self.has_been_served and table_to_move_to)

for action, param_arg_dict_list in state.npc_actions.items():
for param_arg_dict in param_arg_dict_list:
if self.has_been_served and self.in_game and action.name == "customer_leave" and param_arg_dict["c1"].name == self.name and param_arg_dict["s2"].name == "customerspawn1":
assert action.is_valid(state, param_arg_dict)
self.in_game = False
return (action, param_arg_dict)
if table_to_move_to and action.name == "customer_move" and param_arg_dict["c1"].name == self.name and param_arg_dict["s2"].name == table_to_move_to.name:
assert action.is_valid(state, param_arg_dict)
self.has_left_queue = True
return (action, param_arg_dict)
return None
21 changes: 18 additions & 3 deletions backend/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self):
self.object_types = []
self.predicates = []
self.actions = []
self.npc_actions = []

def are_valid_object_types(self, object_types):
"""
Expand Down Expand Up @@ -51,7 +52,7 @@ def _are_valid_types(self, object_types, type_definitions):
return True


def initialize(self, name, object_types, predicate_def, action_def):
def initialize(self, name, object_types, predicate_def, action_def, npc_action_def):
"""
Initializes a domain object and performs checks on all types in the
predicate and action definitions.
Expand All @@ -62,6 +63,7 @@ def initialize(self, name, object_types, predicate_def, action_def):
predicate_def (List[Predicate]): The predicate definitions in the
domain.
action_def (List[Action]): The action definitions in the domain.
npc_action_def (List[Action]): The NPC action definitions in the domain.

Returns:
Domain: The initialized domain object.
Expand All @@ -71,11 +73,23 @@ def initialize(self, name, object_types, predicate_def, action_def):
not valid.
"""
# Check if predicates and actions have valid parameter types
for pred in self.predicates:
for pred in predicate_def:
if not self._are_valid_types(pred.types, object_types):
raise ValueError(f"Predicate {pred.name} has invalid types.")

for action in self.actions:
for action in action_def:
for precon in action.precons:
if not self._are_valid_types(precon.types, object_types):
raise ValueError(f"Precondition {precon.name} has invalid types.")
for effect in action.immediate_effects:
if not self._are_valid_types(effect.types, object_types):
raise ValueError(f"Immediate effect {effect.name} has invalid types.")
for special_effect in action.special_effects:
for effect in special_effect.effects:
if not self._are_valid_types(effect.types, object_types):
raise ValueError(f"Special effect {special_effect.name} has invalid types.")

for action in npc_action_def:
for precon in action.precons:
if not self._are_valid_types(precon.types, object_types):
raise ValueError(f"Precondition {precon.name} has invalid types.")
Expand All @@ -91,6 +105,7 @@ def initialize(self, name, object_types, predicate_def, action_def):
self.object_types = object_types
self.predicates = predicate_def
self.actions = action_def
self.npc_actions = npc_action_def

return self

Expand Down
52 changes: 52 additions & 0 deletions backend/gamemode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from backend.customer import Customer
class GameMode(object):
"""
The GameMode class handles how the player achieves victory in the game,
and keeps track of their score.
"""

def __init__(self, state, environment_json, recipe_json, movement):
"""
Initializes the GameMode object.

Args:
state (State): The game state.
environment_json (dict): The environment dictionary.
recipe_json (dict): The recipe dictionary.
movement (Movement): The movement object.
"""
self.score = 0
self.win = False
self.state = state
self.movement = movement
Customer.build_customers(environment_json, recipe_json)


def check_if_player_has_won(self):
"""
Checks if the player has won the game.

Returns:
bool: True if the player has won, False otherwise.
"""
pass

def step(self, time, actions):
"""
Steps the game mode.

Args:
time (pygame.time): The time object.
actions (List[Tuple[Action, Dictionary[str, Object]]): A list of
tuples where the first element is the action to perform, and the
second element is a dictionary of arguments for the action. The
length of the list is the number of players, where actions[i] is
the action for player i. If player i is not performing an action,
actions[i] is None.

Returns:
new_state (State): The successor state.
done (bool): True if the goal is reached, False otherwise.
"""
pass

Loading