diff --git a/.DS_Store b/.DS_Store index c1b992f..b8334b6 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f2994f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode/ +*__pycache__* \ No newline at end of file diff --git a/Q_Learning_agent.py b/Q_Learning_agent.py deleted file mode 100644 index 37b8ef9..0000000 --- a/Q_Learning_agent.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -import pandas as pd -import torch -import torch.nn.functional as F -import json # Import json for dictionary serialization - -class QLAgent: - # here are some default parameters, you can use different ones - def __init__(self, action_space, alpha=0.5, gamma=0.8, epsilon=0.1, mini_epsilon=0.01, decay=0.999): - self.action_space = action_space - self.alpha = alpha # learning rate - self.gamma = gamma # discount factor - self.epsilon = epsilon # exploit vs. explore probability - self.mini_epsilon = mini_epsilon # threshold for stopping the decay - self.decay = decay # value to decay the epsilon over time - self.qtable = pd.DataFrame(columns=[i for i in range(self.action_space)]) # generate the initial table - - def trans(self, state, granularity=0.5): - # You should design a function to transform the huge state into a learnable state for the agent - # It should be simple but also contains enough information for the agent to learn - - pass - - def learning(self, action, rwd, state, next_state): - # implement the Q-learning function - - def choose_action(self, state): - # implement the action selection for the fully trained agent - diff --git a/enums/direction.py b/enums/direction.py old mode 100755 new mode 100644 index 54712c4..8150fcd --- a/enums/direction.py +++ b/enums/direction.py @@ -2,8 +2,8 @@ class Direction(Enum): - NONE = 0, - NORTH = 1, - SOUTH = 2, - EAST = 3, - WEST = 4 + NORTH = 0 + SOUTH = 1 + EAST = 2 + WEST = 3 + NONE = 4 diff --git a/final_proj/README.md b/final_proj/README.md new file mode 100644 index 0000000..733542a --- /dev/null +++ b/final_proj/README.md @@ -0,0 +1,19 @@ +## Installing +Clone the repo + +``` +git clone https://github.com/marlow-fawn/propershopper.git +``` + +## Running keyboard input +Running the simulation with keyboard input is a good way to get a sense of the physics of the simulation and how interaction with objects works. + +To run keyboard input, while in the "proppershopper" directory, run + +``` + socket_env.py --keyboard_input --random_start --num_players 5 +``` +## Run our agent +``` + socket_agent_proj.py +``` diff --git "a/final_proj/Screenshot 2024-04-25 at 6.43.24\342\200\257PM.png" "b/final_proj/Screenshot 2024-04-25 at 6.43.24\342\200\257PM.png" new file mode 100644 index 0000000..a9c839e Binary files /dev/null and "b/final_proj/Screenshot 2024-04-25 at 6.43.24\342\200\257PM.png" differ diff --git a/final_proj/astar_dynamic.py b/final_proj/astar_dynamic.py new file mode 100644 index 0000000..8123635 --- /dev/null +++ b/final_proj/astar_dynamic.py @@ -0,0 +1,123 @@ +import math +from copy import deepcopy + +import numpy as np +from PIL import Image + +from enums.direction import Direction +from helper import project_collision_dyn, project_collision, round_float + +STEP = 0.15 + + +class Node: + """A node class for A* Pathfinding""" + + def __init__(self, parent=None, position=None): + self.parent = parent + self.position = position + + self.g = 0 + self.h = 0 + self.f = 0 + + def __eq__(self, other): + return self.position == other.position + + +# By Nicholas Swift via https://medium.com/@nicholas.w.swift/easy-a-star-pathfinding-7e6689c7f7b2 +# Modifications by Marlow Fawn +def astar(goal, player, state): + """Returns a list of tuples as a path from the given start to the given end in the given maze""" + player_copy = deepcopy(player) + goal_copy = deepcopy(goal) + + start = (round(player_copy['position'][1] / STEP), round(player_copy['position'][0] / STEP)) + end = (round(goal_copy['position'][1] / STEP), round(goal_copy['position'][0] / STEP)) + # Create start and end node + start_node = Node(None, start) + start_node.g = start_node.h = start_node.f = 0 + end_node = Node(None, end) + end_node.g = end_node.h = end_node.f = 0 + + # Initialize both open and closed list + open_list = [] + closed_list = [] + + # Add the start node + open_list.append(start_node) + + # Loop until you find the end + while len(open_list) > 0: + + # Get the current node + current_node = open_list[0] + current_index = 0 + for index, item in enumerate(open_list): + if item.f < current_node.f: + current_node = item + current_index = index + + # Pop current off open list, add to closed list + open_list.pop(current_index) + closed_list.append(current_node) + + # Found the goal + if current_node == end_node: + path = [] + current = current_node + while current is not None: + path.append(current.position) + current = current.parent + return path[::-1] # Return reversed path + + # Generate children + children = [] + for new_position in [(0, -1), (0, 1), (-1, 0), (1, 0)]: # Adjacent squares + + # Get node position + node_position = (current_node.position[0] + new_position[0], current_node.position[1] + new_position[1]) + + # Make sure walkable terrain + # todo: If holding cart, even bigger? + # todo: update based on new_position + player_copy['position'][0] = convert_to_real(node_position[0]) + player_copy['position'][1] = convert_to_real(node_position[1]) + if project_collision(player_copy, state, Direction.NONE): + continue + + # Create new node + new_node = Node(current_node, node_position) + + # Append + children.append(new_node) + + # Loop through children + for child in children: + + # Child is on the closed list + for closed_child in closed_list: + if child == closed_child: + continue + + # Create the f, g, and h values + child.g = current_node.g + 1 + child.h = ((child.position[0] - end_node.position[0]) ** 2) + ( + (child.position[1] - end_node.position[1]) ** 2) + child.f = child.g + child.h + + # Child is already in the open list + for open_node in open_list: + if child == open_node and child.g > open_node.g: + continue + + # Add the child to the open list + open_list.append(child) + + +def convert_to_real(loc): + return loc * STEP + + +def convert_to_astar(loc): + return round(loc / STEP) diff --git a/final_proj/astar_static.py b/final_proj/astar_static.py new file mode 100644 index 0000000..ae0ae25 --- /dev/null +++ b/final_proj/astar_static.py @@ -0,0 +1,192 @@ +import math +from copy import deepcopy + +import numpy as np +from PIL import Image + +from enums.direction import Direction +from helper import project_collision_dyn, project_collision + +from warnings import warn +import heapq + +STEP = 0.3 +BUFFER = 0.3 + + +def init_map(obs): + map_ = [] + tile = { + "position": [0, 0], + "width": 0.6, + "height": 0.4, + "index": 0 + } + max_width = 25 + max_height = 25 + while tile["position"][1] < max_height: + map_.append([]) + while tile["position"][0] < max_width: + map_[len(map_) - 1].append(project_collision_dyn(tile, obs, Direction.NONE, buffer=BUFFER)) + tile["position"][0] += STEP + + tile["position"][0] = 0 + tile["position"][1] += STEP + + # map_ = update_dyn(map_, obs) + + x = np.asarray(map_, np.uint8) + im = Image.fromarray(x, 'P') + im.putpalette([0, 0, 0, # Black + 255, 0, 0, # Red + 0, 255, 0, # g + 0, 255, 255]) # b + im.show() + + return map_ + + +def update_dyn(map_: [[]], state, index=0): + for player in state['observation']['players']: + if player['index'] == index: + continue + x = round((player['position'][0] - BUFFER) / STEP) + y = round((player['position'][1] - BUFFER) / STEP) + for i in range(math.ceil((player['width'] + BUFFER) / STEP)): + for j in range(math.ceil((player['height'] + BUFFER) / STEP)): + map_[y + j][x + i] = -1 + return map_ + + +class Node: + """ + A node class for A* Pathfinding + """ + + def __init__(self, parent=None, position=None): + self.parent = parent + self.position = position + + self.g = 0 + self.h = 0 + self.f = 0 + + def __eq__(self, other): + return self.position == other.position + + def __repr__(self): + return f"{self.position} - g: {self.g} h: {self.h} f: {self.f}" + + # defining less than for purposes of heap queue + def __lt__(self, other): + return self.f < other.f + + # defining greater than for purposes of heap queue + def __gt__(self, other): + return self.f > other.f + + +def return_path(current_node): + path = [] + current = current_node + while current is not None: + path.append(current.position) + current = current.parent + return path[::-1] # Return reversed path + + +def astar(goal, player, maze): + start = (round(player['position'][1] / STEP), round(player['position'][0] / STEP)) + end = (round(goal['position'][1] / STEP), round(goal['position'][0] / STEP)) + + # Create start and end node + start_node = Node(None, start) + start_node.g = start_node.h = start_node.f = 0 + end_node = Node(None, end) + end_node.g = end_node.h = end_node.f = 0 + + # Initialize both open and closed list + open_list = [] + closed_list = [] + + # Heapify the open_list and Add the start node + heapq.heapify(open_list) + heapq.heappush(open_list, start_node) + + # Adding a stop condition + outer_iterations = 0 + max_iterations = (len(maze[0]) * len(maze) // 2) + + # what squares do we search + adjacent_squares = ((0, -1), (0, 1), (-1, 0), (1, 0),) + + # Loop until you find the end + while len(open_list) > 0: + outer_iterations += 1 + + if outer_iterations > max_iterations: + # if we hit this point return the path such as it is + # it will not contain the destination + warn("giving up on pathfinding too many iterations") + return return_path(current_node) + + # Get the current node + current_node = heapq.heappop(open_list) + closed_list.append(current_node) + + # Found the goal + if current_node == end_node: + return return_path(current_node) + + # Generate children + children = [] + + for new_position in adjacent_squares: # Adjacent squares + + # Get node position + node_position = (current_node.position[0] + new_position[0], current_node.position[1] + new_position[1]) + + # Make sure walkable terrain + if maze[node_position[0]][node_position[1]] != 0: + continue + + # Create new node + new_node = Node(current_node, node_position) + + # Append + children.append(new_node) + + # Loop through children + for child in children: + # Child is on the closed list + if len([closed_child for closed_child in closed_list if closed_child == child]) > 0: + continue + + # Create the f, g, and h values + child.g = current_node.g + 1 + child.h = ((child.position[0] - end_node.position[0]) ** 2) + ( + (child.position[1] - end_node.position[1]) ** 2) + child.f = child.g + child.h + + # Child is already in the open list + if len([open_node for open_node in open_list if + child.position == open_node.position and child.g > open_node.g]) > 0: + continue + + # Add the child to the open list + heapq.heappush(open_list, child) + + warn("Couldn't get a path to destination") + return None + + +def convert_to_real(loc): + return loc * STEP + + +def convert_to_index(loc): + return round(loc / STEP) + + +def get_pathfind_index(obj): + return f"{convert_to_index(obj['position'][1])},{convert_to_index(obj['position'][0])}" diff --git a/final_proj/box_regions.py b/final_proj/box_regions.py new file mode 100644 index 0000000..e6b58bf --- /dev/null +++ b/final_proj/box_regions.py @@ -0,0 +1,198 @@ +from util import * + +class BoxRegion: + """Box Region class for fast high level A star""" + + def __init__(self, name:str=None, parent=None, box:dict=None, neighbors:set=None): + self.name = name + self.parent = parent + if box is None: + self.midpoint = None + else: + self.midpoint = (abs(box['westmost'] - box['eastmost']) / 2.0, abs(box['southmost'] - box['northmost']) / 2.0) + self.box = box + self.neighbors = neighbors + + + def __eq__(self, other): + return self.midpoint == other.midpoint + + def add_neighbors(self, neighbors): + self.neighbors.update(neighbors) + + def contains(self, point:list|tuple[float, float]) -> bool: + """Check whether a point is in the box region + + Args: + point (list | tuple[float, float]): (x, y) + Returns: + bool: T for in region + """ + return (self.box['westmost'] <= point[0] <= self.box['eastmost']) and (self.box['northmost'] <= point[1] <= self.box['southmost']) + +NW_corner = BoxRegion( + name = "NW_corner", + box = { + 'westmost':-0.6, + 'eastmost':3.25, + 'northmost':1.5, + 'southmost':4.5 + } +) +W_corner = BoxRegion( + name = "W_corner", + box = { + 'westmost':-0.6, + 'eastmost':3.25, + 'northmost':7.0, + 'southmost':9.5 + } +) +SW_corner = BoxRegion( + name = "SW_corner", + box = { + 'westmost':-0.6, + 'eastmost':3.25, + 'northmost':3.5, + 'southmost':21.5 + } +) +NE_corner = BoxRegion( + name = "NE_corner", + box = { + 'westmost':18.25, + 'eastmost':19.75, + 'northmost':1.5, + 'southmost':4.75 + } +) +E_corner = BoxRegion( + name = "E_corner", + box = { + 'westmost':18.25, + 'eastmost':19.75, + 'northmost':7.0, + 'southmost':10.75 + } +) +SE_corner = BoxRegion( + name = "SE_corner", + box = { + 'westmost':18.25, + 'eastmost':19.75, + 'northmost':13.0, + 'southmost':25.0 + } +) + +aisle1 = BoxRegion( + name = "aisle1", + box = { + 'westmost':5.5, + 'eastmost':15.5, + 'northmost':2.5, + 'southmost':5.5 + } +) +aisle2 = BoxRegion( + name = "aisle2", + box = { + 'westmost':5.5, + 'eastmost':15.5, + 'northmost':6.5, + 'southmost':9.5 + } +) +aisle3 = BoxRegion( + name = "aisle3", + box = { + 'westmost':5.5, + 'eastmost':15.5, + 'northmost':10.5, + 'southmost':13.5 + } +) +aisle4 = BoxRegion( + name = "aisle4", + box = { + 'westmost':5.5, + 'eastmost':15.5, + 'northmost':14.5, + 'southmost':17.5 + } +) +aisle5 = BoxRegion( + name = "aisle5", + box = { + 'westmost':5.5, + 'eastmost':15.5, + 'northmost':18.5, + 'southmost':21.5 + } +) +aisle6 = BoxRegion( + name = "aisle6", + box = { + 'westmost':5.5, + 'eastmost':15.5, + 'northmost':22.5, + 'southmost':25 + } +) + +W_walkway = BoxRegion( + name = "W_walkway", + box = { + 'westmost':3.25, + 'eastmost':5.5, + 'northmost':1.5, + 'southmost':25.0 + } +) +E_walkway = BoxRegion( + name = "E_walkway", + box = { + 'westmost':15.5, + 'eastmost':18.25, + 'northmost':1.5, + 'southmost':25.0 + } +) + +NW_corner.add_neighbors(neighbors=[W_walkway]) +W_corner.add_neighbors(neighbors=[W_walkway]) +SW_corner.add_neighbors(neighbors=[W_walkway]) +W_walkway.add_neighbors(neighbors=[ + NW_corner, W_corner, SW_corner, aisle1, aisle2, aisle3, aisle4, aisle5, aisle6 +]) +aisle1.add_neighbors(neighbors=[W_walkway, E_walkway]) +aisle2.add_neighbors(neighbors=[W_walkway, E_walkway]) +aisle3.add_neighbors(neighbors=[W_walkway, E_walkway]) +aisle4.add_neighbors(neighbors=[W_walkway, E_walkway]) +aisle5.add_neighbors(neighbors=[W_walkway, E_walkway]) +aisle6.add_neighbors(neighbors=[W_walkway, E_walkway]) + +E_walkway.add_neighbors(neighbors=[ + NE_corner, E_corner, SE_corner, aisle1, aisle2, aisle3, aisle4, aisle5, aisle6 +]) + +NE_corner.add_neighbors(neighbors=[E_walkway]) +E_corner.add_neighbors(neighbors=[E_walkway]) +SE_corner.add_neighbors(neighbors=[E_walkway]) + +regions = [ + NE_corner, + W_corner, + SW_corner, + NE_corner, + E_corner, + SE_corner, + W_walkway, + E_walkway, + aisle1, + aisle2, + aisle3, + aisle4, + aisle5, + aisle6, +] \ No newline at end of file diff --git a/final_proj/constants.py b/final_proj/constants.py new file mode 100644 index 0000000..56003fa --- /dev/null +++ b/final_proj/constants.py @@ -0,0 +1,92 @@ +###########################Helen: ################################################ +##some constants that were useful during single agent norm-conforming navigation## +################################################################################## +cartReturns = [2, 18.5] +basketReturns = [3.5, 18.5] +registerReturns_1 = [2, 4.5] +registerReturns_2 = [2, 9.5] +exit_pos = [-0.6, 3.0] +default_start_pos = [1.5, 15.6] +interact_distance = 0.25 +counter = [] +loc = [1, 17.5] +register_region_x = 1 + 2.25 +vertical_walkway_west_x = 4.2 +vertical_walkway_east_x = 17.1 # if east of this, walk west past it first +west_east_walkway_y = 15 +west_region_x = 5.5 +east_region_x = 15.5 +isle_1 = [3.8, 3.2] +isle_2 = [] +player_directions = {0:"NORTH", 1:"SOUTH", 2:"EAST", 3:"WEST"} # direction the player is facing +left_cartReturns = { + "height": 6, + "width": 0.7, + "position": [ + 1, + 18.5 + ], + "quantity": 5, + "interact_boxes": [ + { + "northmost": 18.15, + "westmost": 1, + "southmost": 18.5, + "eastmost": 1.7, + "player_needs_to_face": "SOUTH" + } + ] + } + +example_cart = {'position': [1.2720096174545914, 18.013690800070798], 'direction': 3, 'capacity': 12, 'owner': 0, 'last_held': 0, 'contents': [], 'contents_quant': [], 'purchased_contents': [], 'purchased_quant': [], 'width': 0.75, 'height': 0.4} +cartReturns = [2, 18.5] +basketReturns = [3.5, 18.5] +registerReturns_1 = [2, 4.5] +registerReturns_2 = [2, 9.5] + +offset = 1 + +STEP = 0.15 # the size of the player's step +MAP_WIDTH, MAP_HEIGHT = 20, 25 +LOCATION_TOLERANCE = 0.15 +BACKTRACK_TOLERANCE = 3 * LOCATION_TOLERANCE + +objs = [ + {'height': 2.5, 'width': 3, 'position': [0.2, 4.5], 're_centered_position': [2.125, 5.75]}, + {'height': 2.5, 'width': 3, 'position': [0.2, 9.5], 're_centered_position': [2.125, 10.75]}, + {'height': 1, 'width': 2, 'position': [5.5, 1.5], 're_centered_position': [6.5, 2]}, + {'height': 1, 'width': 2, 'position': [7.5, 1.5], 're_centered_position': [8.5, 2]}, + {'height': 1, 'width': 2, 'position': [9.5, 1.5], 're_centered_position': [10.5, 2]}, + {'height': 1, 'width': 2, 'position': [11.5, 1.5], 're_centered_position': [12.5, 2]}, + {'height': 1, 'width': 2, 'position': [13.5, 1.5], 're_centered_position': [14.5, 2]}, + {'height': 1, 'width': 2, 'position': [5.5, 5.5], 're_centered_position': [6.5, 6]}, + {'height': 1, 'width': 2, 'position': [7.5, 5.5], 're_centered_position': [8.5, 6]}, + {'height': 1, 'width': 2, 'position': [9.5, 5.5], 're_centered_position': [10.5, 6]}, + {'height': 1, 'width': 2, 'position': [11.5, 5.5], 're_centered_position': [12.5, 6]}, + {'height': 1, 'width': 2, 'position': [13.5, 5.5], 're_centered_position': [14.5, 6]}, + {'height': 1, 'width': 2, 'position': [5.5, 9.5], 're_centered_position': [6.5, 10]}, + {'height': 1, 'width': 2, 'position': [7.5, 9.5], 're_centered_position': [8.5, 10]}, + {'height': 1, 'width': 2, 'position': [9.5, 9.5], 're_centered_position': [10.5, 10]}, + {'height': 1, 'width': 2, 'position': [11.5, 9.5], 're_centered_position': [12.5, 10]}, + {'height': 1, 'width': 2, 'position': [13.5, 9.5], 're_centered_position': [14.5, 10]}, + {'height': 1, 'width': 2, 'position': [5.5, 13.5], 're_centered_position': [6.5, 14]}, + {'height': 1, 'width': 2, 'position': [7.5, 13.5], 're_centered_position': [8.5, 14]}, + {'height': 1, 'width': 2, 'position': [9.5, 13.5], 're_centered_position': [10.5, 14]}, + {'height': 1, 'width': 2, 'position': [11.5, 13.5], 're_centered_position': [12.5, 14]}, + {'height': 1, 'width': 2, 'position': [13.5, 13.5], 're_centered_position': [14.5, 14]}, + {'height': 1, 'width': 2, 'position': [5.5, 17.5], 're_centered_position': [6.5, 18]}, + {'height': 1, 'width': 2, 'position': [7.5, 17.5], 're_centered_position': [8.5, 18]}, + {'height': 1, 'width': 2, 'position': [9.5, 17.5], 're_centered_position': [10.5, 18]}, + {'height': 1, 'width': 2, 'position': [11.5, 17.5], 're_centered_position': [12.5, 18]}, + {'height': 1, 'width': 2, 'position': [13.5, 17.5], 're_centered_position': [14.5, 18]}, + {'height': 1, 'width': 2, 'position': [5.5, 21.5], 're_centered_position': [6.5, 22]}, + {'height': 1, 'width': 2, 'position': [7.5, 21.5], 're_centered_position': [8.5, 22]}, + {'height': 1, 'width': 2, 'position': [9.5, 21.5], 're_centered_position': [10.5, 22]}, + {'height': 1, 'width': 2, 'position': [11.5, 21.5], 're_centered_position': [12.5, 22]}, + {'height': 1, 'width': 2, 'position': [13.5, 21.5], 're_centered_position': [14.5, 22]}, + {'height': 6, 'width': 0.7, 'position': [1, 18.5], 're_centered_position': [1.35, 21.5]}, + {'height': 6, 'width': 0.7, 'position': [2, 18.5], 're_centered_position': [2.35, 21.5]}, + {'height': 0.8, 'width': 0.8, 'position': [3.5, 18.5], 're_centered_position': [4.15, 19.4]}, + {'height': 2.25, 'width': 1.5, 'position': [18.25, 4.75], 're_centered_position': [19.125, 5.875]}, + {'height': 2.25, 'width': 1.5, 'position': [18.25, 10.75], 're_centered_position': [19.125, 11.875]} +] \ No newline at end of file diff --git a/final_proj/env.json b/final_proj/env.json new file mode 100644 index 0000000..e394b7c --- /dev/null +++ b/final_proj/env.json @@ -0,0 +1,551 @@ +{ + "carts": [], + "baskets": [], + "registers": [ + { + "height": 2.5, + "width": 2.25, + "position": [ + 1, + 4.5 + ], + "num_items": 0, + "foods": [], + "food_quantities": [], + "food_images": [], + "capacity": 12, + "image": "images/Registers/registersA.png", + "curr_player": null + }, + { + "height": 2.5, + "width": 2.25, + "position": [ + 1, + 9.5 + ], + "num_items": 0, + "foods": [], + "food_quantities": [], + "food_images": [], + "capacity": 12, + "image": "images/Registers/registersB.png", + "curr_player": null + } + ], + "shelves": [ + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 1.5 + ], + "food": "milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "milk" + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 1.5 + ], + "food": "milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "milk" + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 1.5 + ], + "food": "chocolate milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk_chocolate.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "chocolate milk" + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 1.5 + ], + "food": "chocolate milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk_chocolate.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "chocolate milk" + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 1.5 + ], + "food": "strawberry milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk_strawberry.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "strawberry milk" + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 5.5 + ], + "food": "apples", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/apples.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "apples" + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 5.5 + ], + "food": "oranges", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/oranges.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "oranges" + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 5.5 + ], + "food": "banana", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/banana.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "banana" + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 5.5 + ], + "food": "strawberry", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/strawberry.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "strawberry" + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 5.5 + ], + "food": "raspberry", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/raspberry.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "raspberry" + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 9.5 + ], + "food": "sausage", + "price": 4, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/sausage.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "sausage" + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 9.5 + ], + "food": "steak", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/meat_01.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "steak" + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 9.5 + ], + "food": "steak", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/meat_02.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "steak" + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 9.5 + ], + "food": "chicken", + "price": 6, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/meat_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "chicken" + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 9.5 + ], + "food": "ham", + "price": 6, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/ham.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "ham" + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 13.5 + ], + "food": "brie cheese", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_01.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "brie cheese" + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 13.5 + ], + "food": "swiss cheese", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_02.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "swiss cheese" + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 13.5 + ], + "food": "cheese wheel", + "price": 15, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cheese wheel" + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 13.5 + ], + "food": "cheese wheel", + "price": 15, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cheese wheel" + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 13.5 + ], + "food": "cheese wheel", + "price": 15, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cheese wheel" + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 17.5 + ], + "food": "garlic", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/garlic.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "garlic" + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 17.5 + ], + "food": "leek", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/leek_onion.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "leek" + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 17.5 + ], + "food": "red bell pepper", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/bell_pepper_red.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "red bell pepper" + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 17.5 + ], + "food": "carrot", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/carrot.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "carrot" + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 17.5 + ], + "food": "lettuce", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/lettuce.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "lettuce" + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 21.5 + ], + "food": "avocado", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/avocado.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "avocado" + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 21.5 + ], + "food": "broccoli", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/broccoli.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "broccoli" + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 21.5 + ], + "food": "cucumber", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cucumber.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cucumber" + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 21.5 + ], + "food": "yellow bell pepper", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/bell_pepper_yellow.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "yellow bell pepper" + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 21.5 + ], + "food": "onion", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/onion.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "onion" + } + ], + "cartReturns": [ + { + "height": 6, + "width": 0.7, + "position": [ + 1, + 18.5 + ], + "quantity": 5 + }, + { + "height": 6, + "width": 0.7, + "position": [ + 2, + 18.5 + ], + "quantity": 6 + } + ], + "basketReturns": [ + { + "height": 0.2, + "width": 0.3, + "position": [ + 3.5, + 18.5 + ], + "quantity": 12 + } + ], + "counters": [ + { + "height": 2.25, + "width": 1.5, + "position": [ + 18.25, + 4.75 + ], + "food": "prepared foods", + "price": 15 + }, + { + "height": 2.25, + "width": 1.5, + "position": [ + 18.25, + 10.75 + ], + "food": "fresh fish", + "price": 12 + }, + { + "height": 2.25, + "width": 1.5, + "position": [ + 18.25, + 10.75 + ], + "food": "fresh fish", + "price": 12 + } + ] +} \ No newline at end of file diff --git a/final_proj/env_interact_boxes.json b/final_proj/env_interact_boxes.json new file mode 100644 index 0000000..12907bb --- /dev/null +++ b/final_proj/env_interact_boxes.json @@ -0,0 +1,1229 @@ +{ + "carts": [], + "baskets": [], + "registers": [ + { + "height": 2.5, + "width": 2.25, + "position": [ + 1, + 4.5 + ], + "num_items": 0, + "foods": [], + "food_quantities": [], + "food_images": [], + "capacity": 12, + "image": "images/Registers/registersA.png", + "curr_player": null, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 4.25, + "westmost": 1, + "southmost": 4.5, + "eastmost": 3.25, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 7.0, + "westmost": 1, + "southmost": 7.25, + "eastmost": 3.25, + "player_needs_to_face": "NORTH" + }, + "WEST_BOX": { + "northmost": 4.5, + "westmost": 0.75, + "southmost": 7.0, + "eastmost": 1, + "player_needs_to_face": "EAST" + }, + "EAST_BOX": { + "northmost": 4.5, + "westmost": 3.25, + "southmost": 7.0, + "eastmost": 3.5, + "player_needs_to_face": "WEST" + } + } + }, + { + "height": 2.5, + "width": 2.25, + "position": [ + 1, + 9.5 + ], + "num_items": 0, + "foods": [], + "food_quantities": [], + "food_images": [], + "capacity": 12, + "image": "images/Registers/registersB.png", + "curr_player": null, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 9.25, + "westmost": 1, + "southmost": 9.5, + "eastmost": 3.25, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 12.0, + "westmost": 1, + "southmost": 12.25, + "eastmost": 3.25, + "player_needs_to_face": "NORTH" + }, + "WEST_BOX": { + "northmost": 9.5, + "westmost": 0.75, + "southmost": 12.0, + "eastmost": 1, + "player_needs_to_face": "EAST" + }, + "EAST_BOX": { + "northmost": 9.5, + "westmost": 3.25, + "southmost": 12.0, + "eastmost": 3.5, + "player_needs_to_face": "WEST" + } + } + } + ], + "shelves": [ + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 1.5 + ], + "food": "milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "milk", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 1.25, + "westmost": 5.5, + "southmost": 1.5, + "eastmost": 7.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 2.5, + "westmost": 5.5, + "southmost": 2.75, + "eastmost": 7.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 1.5 + ], + "food": "milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "milk", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 1.25, + "westmost": 7.5, + "southmost": 1.5, + "eastmost": 9.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 2.5, + "westmost": 7.5, + "southmost": 2.75, + "eastmost": 9.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 1.5 + ], + "food": "chocolate milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk_chocolate.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "chocolate milk", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 1.25, + "westmost": 9.5, + "southmost": 1.5, + "eastmost": 11.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 2.5, + "westmost": 9.5, + "southmost": 2.75, + "eastmost": 11.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 1.5 + ], + "food": "chocolate milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk_chocolate.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "chocolate milk", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 1.25, + "westmost": 11.5, + "southmost": 1.5, + "eastmost": 13.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 2.5, + "westmost": 11.5, + "southmost": 2.75, + "eastmost": 13.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 1.5 + ], + "food": "strawberry milk", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/milk_strawberry.png", + "shelf_image": "images/Shelves/fridge.png", + "food_name": "strawberry milk", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 1.25, + "westmost": 13.5, + "southmost": 1.5, + "eastmost": 15.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 2.5, + "westmost": 13.5, + "southmost": 2.75, + "eastmost": 15.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 5.5 + ], + "food": "apples", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/apples.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "apples", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 5.25, + "westmost": 5.5, + "southmost": 5.5, + "eastmost": 7.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 6.5, + "westmost": 5.5, + "southmost": 6.75, + "eastmost": 7.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 5.5 + ], + "food": "oranges", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/oranges.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "oranges", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 5.25, + "westmost": 7.5, + "southmost": 5.5, + "eastmost": 9.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 6.5, + "westmost": 7.5, + "southmost": 6.75, + "eastmost": 9.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 5.5 + ], + "food": "banana", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/banana.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "banana", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 5.25, + "westmost": 9.5, + "southmost": 5.5, + "eastmost": 11.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 6.5, + "westmost": 9.5, + "southmost": 6.75, + "eastmost": 11.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 5.5 + ], + "food": "strawberry", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/strawberry.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "strawberry", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 5.25, + "westmost": 11.5, + "southmost": 5.5, + "eastmost": 13.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 6.5, + "westmost": 11.5, + "southmost": 6.75, + "eastmost": 13.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 5.5 + ], + "food": "raspberry", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/raspberry.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "raspberry", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 5.25, + "westmost": 13.5, + "southmost": 5.5, + "eastmost": 15.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 6.5, + "westmost": 13.5, + "southmost": 6.75, + "eastmost": 15.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 9.5 + ], + "food": "sausage", + "price": 4, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/sausage.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "sausage", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 9.25, + "westmost": 5.5, + "southmost": 9.5, + "eastmost": 7.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 10.5, + "westmost": 5.5, + "southmost": 10.75, + "eastmost": 7.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 9.5 + ], + "food": "steak", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/meat_01.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "steak", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 9.25, + "westmost": 7.5, + "southmost": 9.5, + "eastmost": 9.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 10.5, + "westmost": 7.5, + "southmost": 10.75, + "eastmost": 9.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 9.5 + ], + "food": "steak", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/meat_02.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "steak", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 9.25, + "westmost": 9.5, + "southmost": 9.5, + "eastmost": 11.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 10.5, + "westmost": 9.5, + "southmost": 10.75, + "eastmost": 11.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 9.5 + ], + "food": "chicken", + "price": 6, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/meat_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "chicken", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 9.25, + "westmost": 11.5, + "southmost": 9.5, + "eastmost": 13.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 10.5, + "westmost": 11.5, + "southmost": 10.75, + "eastmost": 13.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 9.5 + ], + "food": "ham", + "price": 6, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/ham.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "ham", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 9.25, + "westmost": 13.5, + "southmost": 9.5, + "eastmost": 15.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 10.5, + "westmost": 13.5, + "southmost": 10.75, + "eastmost": 15.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 13.5 + ], + "food": "brie cheese", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_01.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "brie cheese", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 13.25, + "westmost": 5.5, + "southmost": 13.5, + "eastmost": 7.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 14.5, + "westmost": 5.5, + "southmost": 14.75, + "eastmost": 7.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 13.5 + ], + "food": "swiss cheese", + "price": 5, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_02.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "swiss cheese", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 13.25, + "westmost": 7.5, + "southmost": 13.5, + "eastmost": 9.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 14.5, + "westmost": 7.5, + "southmost": 14.75, + "eastmost": 9.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 13.5 + ], + "food": "cheese wheel", + "price": 15, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cheese wheel", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 13.25, + "westmost": 9.5, + "southmost": 13.5, + "eastmost": 11.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 14.5, + "westmost": 9.5, + "southmost": 14.75, + "eastmost": 11.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 13.5 + ], + "food": "cheese wheel", + "price": 15, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cheese wheel", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 13.25, + "westmost": 11.5, + "southmost": 13.5, + "eastmost": 13.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 14.5, + "westmost": 11.5, + "southmost": 14.75, + "eastmost": 13.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 13.5 + ], + "food": "cheese wheel", + "price": 15, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cheese_03.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cheese wheel", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 13.25, + "westmost": 13.5, + "southmost": 13.5, + "eastmost": 15.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 14.5, + "westmost": 13.5, + "southmost": 14.75, + "eastmost": 15.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 17.5 + ], + "food": "garlic", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/garlic.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "garlic", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 17.25, + "westmost": 5.5, + "southmost": 17.5, + "eastmost": 7.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 18.5, + "westmost": 5.5, + "southmost": 18.75, + "eastmost": 7.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 17.5 + ], + "food": "leek", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/leek_onion.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "leek", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 17.25, + "westmost": 7.5, + "southmost": 17.5, + "eastmost": 9.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 18.5, + "westmost": 7.5, + "southmost": 18.75, + "eastmost": 9.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 17.5 + ], + "food": "red bell pepper", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/bell_pepper_red.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "red bell pepper", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 17.25, + "westmost": 9.5, + "southmost": 17.5, + "eastmost": 11.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 18.5, + "westmost": 9.5, + "southmost": 18.75, + "eastmost": 11.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 17.5 + ], + "food": "carrot", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/carrot.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "carrot", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 17.25, + "westmost": 11.5, + "southmost": 17.5, + "eastmost": 13.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 18.5, + "westmost": 11.5, + "southmost": 18.75, + "eastmost": 13.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 17.5 + ], + "food": "lettuce", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/lettuce.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "lettuce", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 17.25, + "westmost": 13.5, + "southmost": 17.5, + "eastmost": 15.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 18.5, + "westmost": 13.5, + "southmost": 18.75, + "eastmost": 15.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 5.5, + 21.5 + ], + "food": "avocado", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/avocado.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "avocado", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 21.25, + "westmost": 5.5, + "southmost": 21.5, + "eastmost": 7.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 22.5, + "westmost": 5.5, + "southmost": 22.75, + "eastmost": 7.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 7.5, + 21.5 + ], + "food": "broccoli", + "price": 1, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/broccoli.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "broccoli", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 21.25, + "westmost": 7.5, + "southmost": 21.5, + "eastmost": 9.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 22.5, + "westmost": 7.5, + "southmost": 22.75, + "eastmost": 9.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 9.5, + 21.5 + ], + "food": "cucumber", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/cucumber.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "cucumber", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 21.25, + "westmost": 9.5, + "southmost": 21.5, + "eastmost": 11.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 22.5, + "westmost": 9.5, + "southmost": 22.75, + "eastmost": 11.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 11.5, + 21.5 + ], + "food": "yellow bell pepper", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/bell_pepper_yellow.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "yellow bell pepper", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 21.25, + "westmost": 11.5, + "southmost": 21.5, + "eastmost": 13.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 22.5, + "westmost": 11.5, + "southmost": 22.75, + "eastmost": 13.5, + "player_needs_to_face": "NORTH" + } + } + }, + { + "height": 1, + "width": 2, + "position": [ + 13.5, + 21.5 + ], + "food": "onion", + "price": 2, + "capacity": 12, + "quantity": 12, + "food_image": "images/food/onion.png", + "shelf_image": "images/Shelves/shelf.png", + "food_name": "onion", + "interact_boxes": { + "NORTH_BOX": { + "northmost": 21.25, + "westmost": 13.5, + "southmost": 21.5, + "eastmost": 15.5, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 22.5, + "westmost": 13.5, + "southmost": 22.75, + "eastmost": 15.5, + "player_needs_to_face": "NORTH" + } + } + } + ], + "cartReturns": [ + { + "height": 6, + "width": 0.7, + "position": [ + 1, + 18.5 + ], + "quantity": 5, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 18.25, + "westmost": 1, + "southmost": 18.5, + "eastmost": 1.7, + "player_needs_to_face": "SOUTH" + } + } + }, + { + "height": 6, + "width": 0.7, + "position": [ + 2, + 18.5 + ], + "quantity": 6, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 18.25, + "westmost": 2, + "southmost": 18.5, + "eastmost": 2.7, + "player_needs_to_face": "SOUTH" + } + } + } + ], + "basketReturns": [ + { + "height": 0.2, + "width": 0.3, + "position": [ + 3.5, + 18.5 + ], + "quantity": 12, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 18.25, + "westmost": 3.5, + "southmost": 18.5, + "eastmost": 3.8, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 18.7, + "westmost": 3.5, + "southmost": 18.95, + "eastmost": 3.8, + "player_needs_to_face": "NORTH" + }, + "WEST_BOX": { + "northmost": 18.5, + "westmost": 3.25, + "southmost": 18.7, + "eastmost": 3.5, + "player_needs_to_face": "EAST" + }, + "EAST_BOX": { + "northmost": 18.5, + "westmost": 3.8, + "southmost": 18.7, + "eastmost": 4.05, + "player_needs_to_face": "WEST" + } + } + } + ], + "counters": [ + { + "height": 2.25, + "width": 1.5, + "position": [ + 18.25, + 4.75 + ], + "food": "prepared foods", + "price": 15, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 4.5, + "westmost": 18.25, + "southmost": 4.75, + "eastmost": 19.75, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 7.0, + "westmost": 18.25, + "southmost": 7.25, + "eastmost": 19.75, + "player_needs_to_face": "NORTH" + }, + "WEST_BOX": { + "northmost": 4.75, + "westmost": 18.0, + "southmost": 7.0, + "eastmost": 18.25, + "player_needs_to_face": "EAST" + }, + "EAST_BOX": { + "northmost": 4.75, + "westmost": 19.75, + "southmost": 7.0, + "eastmost": 20.0, + "player_needs_to_face": "WEST" + } + } + }, + { + "height": 2.25, + "width": 1.5, + "position": [ + 18.25, + 10.75 + ], + "food": "fresh fish", + "price": 12, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 10.5, + "westmost": 18.25, + "southmost": 10.75, + "eastmost": 19.75, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 13.0, + "westmost": 18.25, + "southmost": 13.25, + "eastmost": 19.75, + "player_needs_to_face": "NORTH" + }, + "WEST_BOX": { + "northmost": 10.75, + "westmost": 18.0, + "southmost": 13.0, + "eastmost": 18.25, + "player_needs_to_face": "EAST" + }, + "EAST_BOX": { + "northmost": 10.75, + "westmost": 19.75, + "southmost": 13.0, + "eastmost": 20.0, + "player_needs_to_face": "WEST" + } + } + }, + { + "height": 2.25, + "width": 1.5, + "position": [ + 18.25, + 10.75 + ], + "food": "fresh fish", + "price": 12, + "interact_boxes": { + "NORTH_BOX": { + "northmost": 10.5, + "westmost": 18.25, + "southmost": 10.75, + "eastmost": 19.75, + "player_needs_to_face": "SOUTH" + }, + "SOUTH_BOX": { + "northmost": 13.0, + "westmost": 18.25, + "southmost": 13.25, + "eastmost": 19.75, + "player_needs_to_face": "NORTH" + }, + "WEST_BOX": { + "northmost": 10.75, + "westmost": 18.0, + "southmost": 13.0, + "eastmost": 18.25, + "player_needs_to_face": "EAST" + }, + "EAST_BOX": { + "northmost": 10.75, + "westmost": 19.75, + "southmost": 13.0, + "eastmost": 20.0, + "player_needs_to_face": "WEST" + } + } + } + ] +} \ No newline at end of file diff --git a/final_proj/fast_high_level_astar.py b/final_proj/fast_high_level_astar.py new file mode 100644 index 0000000..a53525b --- /dev/null +++ b/final_proj/fast_high_level_astar.py @@ -0,0 +1,287 @@ + +import numpy as np +import json +from queue import PriorityQueue + +import json +import socket + + +from util import * +from box_regions import * + +cartReturns = [2, 18.5] +basketReturns = [3.5, 18.5] +registerReturns_1 = [2, 4.5] +registerReturns_2 = [2, 9.5] + +offset = 1 + +objs = [ + {'height': 2.5, 'width': 3, 'position': [0.2, 4.5], 're_centered_position': [2.125, 5.75]}, + {'height': 2.5, 'width': 3, 'position': [0.2, 9.5], 're_centered_position': [2.125, 10.75]}, + {'height': 1, 'width': 2, 'position': [5.5, 1.5], 're_centered_position': [6.5, 2]}, + {'height': 1, 'width': 2, 'position': [7.5, 1.5], 're_centered_position': [8.5, 2]}, + {'height': 1, 'width': 2, 'position': [9.5, 1.5], 're_centered_position': [10.5, 2]}, + {'height': 1, 'width': 2, 'position': [11.5, 1.5], 're_centered_position': [12.5, 2]}, + {'height': 1, 'width': 2, 'position': [13.5, 1.5], 're_centered_position': [14.5, 2]}, + {'height': 1, 'width': 2, 'position': [5.5, 5.5], 're_centered_position': [6.5, 6]}, + {'height': 1, 'width': 2, 'position': [7.5, 5.5], 're_centered_position': [8.5, 6]}, + {'height': 1, 'width': 2, 'position': [9.5, 5.5], 're_centered_position': [10.5, 6]}, + {'height': 1, 'width': 2, 'position': [11.5, 5.5], 're_centered_position': [12.5, 6]}, + {'height': 1, 'width': 2, 'position': [13.5, 5.5], 're_centered_position': [14.5, 6]}, + {'height': 1, 'width': 2, 'position': [5.5, 9.5], 're_centered_position': [6.5, 10]}, + {'height': 1, 'width': 2, 'position': [7.5, 9.5], 're_centered_position': [8.5, 10]}, + {'height': 1, 'width': 2, 'position': [9.5, 9.5], 're_centered_position': [10.5, 10]}, + {'height': 1, 'width': 2, 'position': [11.5, 9.5], 're_centered_position': [12.5, 10]}, + {'height': 1, 'width': 2, 'position': [13.5, 9.5], 're_centered_position': [14.5, 10]}, + {'height': 1, 'width': 2, 'position': [5.5, 13.5], 're_centered_position': [6.5, 14]}, + {'height': 1, 'width': 2, 'position': [7.5, 13.5], 're_centered_position': [8.5, 14]}, + {'height': 1, 'width': 2, 'position': [9.5, 13.5], 're_centered_position': [10.5, 14]}, + {'height': 1, 'width': 2, 'position': [11.5, 13.5], 're_centered_position': [12.5, 14]}, + {'height': 1, 'width': 2, 'position': [13.5, 13.5], 're_centered_position': [14.5, 14]}, + {'height': 1, 'width': 2, 'position': [5.5, 17.5], 're_centered_position': [6.5, 18]}, + {'height': 1, 'width': 2, 'position': [7.5, 17.5], 're_centered_position': [8.5, 18]}, + {'height': 1, 'width': 2, 'position': [9.5, 17.5], 're_centered_position': [10.5, 18]}, + {'height': 1, 'width': 2, 'position': [11.5, 17.5], 're_centered_position': [12.5, 18]}, + {'height': 1, 'width': 2, 'position': [13.5, 17.5], 're_centered_position': [14.5, 18]}, + {'height': 1, 'width': 2, 'position': [5.5, 21.5], 're_centered_position': [6.5, 22]}, + {'height': 1, 'width': 2, 'position': [7.5, 21.5], 're_centered_position': [8.5, 22]}, + {'height': 1, 'width': 2, 'position': [9.5, 21.5], 're_centered_position': [10.5, 22]}, + {'height': 1, 'width': 2, 'position': [11.5, 21.5], 're_centered_position': [12.5, 22]}, + {'height': 1, 'width': 2, 'position': [13.5, 21.5], 're_centered_position': [14.5, 22]}, + {'height': 6, 'width': 0.7, 'position': [1, 18.5], 're_centered_position': [1.35, 21.5]}, + {'height': 6, 'width': 0.7, 'position': [2, 18.5], 're_centered_position': [2.35, 21.5]}, + {'height': 0.8, 'width': 0.8, 'position': [3.5, 18.5], 're_centered_position': [4.15, 19.4]}, + {'height': 2.25, 'width': 1.5, 'position': [18.25, 4.75], 're_centered_position': [19.125, 5.875]}, + {'height': 2.25, 'width': 1.5, 'position': [18.25, 10.75], 're_centered_position': [19.125, 11.875]} +] + + +def update_position_to_center(obj_pose): + """ + Update the position of objects to their re_centered_position if their current position matches obj_pose. + + Parameters: + objects (list of dicts): List of objects with details including position and re_centered_position. + obj_pose (list): The position to match for updating to re_centered_position. + + Returns: + None: Objects are modified in place. + """ + global objs + for obj in objs: + # Compare current position with obj_pose + if obj['position'] == obj_pose: + # If they match, update position to re_centered_position + obj_pose = obj['re_centered_position'] + break + return obj_pose + + +class HighLevelPlanner: + def __init__(self, socket_game, env): + self.shopping_list = env['observation']['players'][0]['shopping_list'] + self.shopping_quant = env['observation']['players'][0]['list_quant'] + self.game = socket_game + self.map_width = 20 + self.map_height = 25 + self.obs = env['observation'] + self.cart = None + self.basket = None + self.player = self.obs['players'][0] + self.last_action = "NOP" + self.current_direction = self.player['direction'] + self.size = [0.6, 0.4] + + + + # def collision(self, x, y, width, height, obj): + # """ + # Check if a rectangle defined by (x, y, width, height) does NOT intersect with an object + # and ensure the rectangle stays within the map boundaries. + + # Parameters: + # x (float): The x-coordinate of the rectangle's top-left corner. + # y (float): The y-coordinate of the rectangle's top-left corner. + # width (float): The width of the rectangle. + # height (float): The height of the rectangle. + # obj (dict): An object with 'position', 'width', and 'height'. + + # Returns: + # bool: Returns True if there is NO collision (i.e., no overlap) and the rectangle is within map boundaries, + # False if there is a collision or the rectangle goes outside the map boundaries. + # """ + # # Define map boundaries + # min_x = 0.5 + # max_x = 24 + # min_y = 2.5 + # max_y = 19.5 + + # # Calculate the boundaries of the rectangle + # rectangle = { + # 'northmost': y, + # 'southmost': y + height, + # 'westmost': x, + # 'eastmost': x + width + # } + + # # Ensure the rectangle is within the map boundaries + # if not (min_x <= rectangle['westmost'] and rectangle['eastmost'] <= max_x and + # min_y <= rectangle['northmost'] and rectangle['southmost'] <= max_y): + # return False # The rectangle is out of the map boundaries + + # # Calculate the boundaries of the object + # obj_box = { + # 'northmost': obj['position'][1], + # 'southmost': obj['position'][1] + obj['height'], + # 'westmost': obj['position'][0], + # 'eastmost': obj['position'][0] + obj['width'] + # } + + # # Check if there is no overlap using the specified cardinal bounds + # no_overlap = not ( + # (obj_box['northmost'] <= rectangle['northmost'] <= obj_box['southmost'] or + # obj_box['northmost'] <= rectangle['southmost'] <= obj_box['southmost']) and ( + # (obj_box['westmost'] <= rectangle['westmost'] <= obj_box['eastmost'] or + # obj_box['westmost'] <= rectangle['eastmost'] <= obj_box['eastmost']) + # ) + # ) + + # return no_overlap + + # # The function will return False if the rectangle is outside the map boundaries or intersects with the object. + + + # def hits_wall(self, x, y): + # wall_width = 0.4 + # return (y <= 2 or y + self.size[1] >= self.map_height - wall_width or \ + # x + self.size[0] >= self.map_width - wall_width) + + + def is_close_enough(self, current, goal:tuple|dict, tolerance=0.15, is_item = True): + """Check if the current position is within tolerance of the goal position.""" + if is_item: + tolerance = 0.6 + return (abs(current[0] - goal[0]) < tolerance - 0.15 and abs(current[1] - goal[1]) < tolerance +0.05 ) + + else: + return (abs(current[0] - goal[0]) < tolerance and abs(current[1] - goal[1]) < tolerance) + + + def which_region(self, location:list|tuple) -> BoxRegion: + """Find which region the location is in + + Args: + location (list | tuple): (x, y) + + Returns: + BoxRegion: the region location is in + """ + for box_region in regions: + if box_region.contains(location): + return box_region + raise Exception("location not on map") + + + def normalized_manhattan_dist(self, start:tuple|list, goal:tuple|list) -> float: + """Return the normalized manhattan dist from start to goal + + Args: + start (tuple | list): (x, y) + goal (tuple | list): (x, y) + + Returns: + float: between 0 and 1 + """ + longest_possible_manhattan_dist = MAP_WIDTH + MAP_HEIGHT + dist = manhattan_distance(pos1=start, pos2=goal) + return dist / longest_possible_manhattan_dist + + def heuristic(self, box_region:BoxRegion, goal:list|tuple) -> float: + return self.normalized_manhattan_dist(start=box_region.midpoint, goal=goal) + + + def astar(self, player, start, goal, obs): + """Perform high level planning to find a path from start location to goal region.""" + start_region = self.which_region(start) # figure out which BoxRegion start is in + frontier = PriorityQueue() + frontier.put(start_region, 0) + came_from = {} + cost_so_far = {} + came_from[start] = None + cost_so_far[start_region] = 0 + + + while not frontier.empty(): + curr_region:BoxRegion = frontier.get() + if curr_region.contains(goal): + break # found goal region + for neighbor_region in curr_region.neighbors: + cost_to_neighbor = cost_so_far[curr_region] + 1 + calculate_crowdedness_factor(player, neighbor_region, obs) + if neighbor_region not in cost_so_far or cost_so_far[neighbor_region] > cost_to_neighbor: + cost_so_far[neighbor_region] = cost_to_neighbor + priority = cost_to_neighbor + self.heuristic(neighbor_region, goal) + frontier.put(neighbor_region, priority) + came_from[neighbor_region] = curr_region + + # Reconstruct path + path = [] + while current: + path.append(current) + current = came_from[current] + if current == start_region: + break + path.reverse() + return path + + +def find_item_position(data, item_name): + """ + Finds the position of an item based on its name within the shelves section of the data structure. + + Parameters: + data (dict): The complete data structure containing various game elements including shelves. + item_name (str): The name of the item to find. + + Returns: + list or None: The position of the item as [x, y] or None if the item is not found. + """ + # Loop through each shelf in the data + for shelf in data['observation']['shelves']: + if shelf['food_name'] == item_name: + return shelf['position'] + return None + +# {'command_result': {'command': 'RESET', 'result': 'SUCCESS', 'message': '', 'stepCost': 0}, 'observation': {'players': [{'index': 0, 'position': [1.2, 15.6], 'width': 0.6, 'height': 0.4, 'sprite_path': None, 'direction': 2, 'curr_cart': -1, 'shopping_list': ['chocolate milk'], 'list_quant': [1], 'holding_food': None, 'bought_holding_food': False, 'budget': 100, 'bagged_items': [], 'bagged_quant': []}], 'carts': [], 'baskets': [], 'registers': [{'height': 2.5, 'width': 2.25, 'position': [1, 4.5], 'num_items': 0, 'foods': [], 'food_quantities': [], 'food_images': [], 'capacity': 12, 'image': 'images/Registers/registersA.png', 'curr_player': None}, {'height': 2.5, 'width': 2.25, 'position': [1, 9.5], 'num_items': 0, 'foods': [], 'food_quantities': [], 'food_images': [], 'capacity': 12, 'image': 'images/Registers/registersB.png', 'curr_player': None}], 'shelves': [{'height': 1, 'width': 2, 'position': [5.5, 1.5], 'food': 'milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'milk'}, {'height': 1, 'width': 2, 'position': [7.5, 1.5], 'food': 'milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'milk'}, {'height': 1, 'width': 2, 'position': [9.5, 1.5], 'food': 'chocolate milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk_chocolate.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'chocolate milk'}, {'height': 1, 'width': 2, 'position': [11.5, 1.5], 'food': 'chocolate milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk_chocolate.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'chocolate milk'}, {'height': 1, 'width': 2, 'position': [13.5, 1.5], 'food': 'strawberry milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk_strawberry.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'strawberry milk'}, {'height': 1, 'width': 2, 'position': [5.5, 5.5], 'food': 'apples', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/apples.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'apples'}, {'height': 1, 'width': 2, 'position': [7.5, 5.5], 'food': 'oranges', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/oranges.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'oranges'}, {'height': 1, 'width': 2, 'position': [9.5, 5.5], 'food': 'banana', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/banana.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'banana'}, {'height': 1, 'width': 2, 'position': [11.5, 5.5], 'food': 'strawberry', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/strawberry.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'strawberry'}, {'height': 1, 'width': 2, 'position': [13.5, 5.5], 'food': 'raspberry', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/raspberry.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'raspberry'}, {'height': 1, 'width': 2, 'position': [5.5, 9.5], 'food': 'sausage', 'price': 4, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/sausage.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'sausage'}, {'height': 1, 'width': 2, 'position': [7.5, 9.5], 'food': 'steak', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/meat_01.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'steak'}, {'height': 1, 'width': 2, 'position': [9.5, 9.5], 'food': 'steak', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/meat_02.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'steak'}, {'height': 1, 'width': 2, 'position': [11.5, 9.5], 'food': 'chicken', 'price': 6, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/meat_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'chicken'}, {'height': 1, 'width': 2, 'position': [13.5, 9.5], 'food': 'ham', 'price': 6, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/ham.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'ham'}, {'height': 1, 'width': 2, 'position': [5.5, 13.5], 'food': 'brie cheese', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_01.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'brie cheese'}, {'height': 1, 'width': 2, 'position': [7.5, 13.5], 'food': 'swiss cheese', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_02.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'swiss cheese'}, {'height': 1, 'width': 2, 'position': [9.5, 13.5], 'food': 'cheese wheel', 'price': 15, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cheese wheel'}, {'height': 1, 'width': 2, 'position': [11.5, 13.5], 'food': 'cheese wheel', 'price': 15, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cheese wheel'}, {'height': 1, 'width': 2, 'position': [13.5, 13.5], 'food': 'cheese wheel', 'price': 15, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cheese wheel'}, {'height': 1, 'width': 2, 'position': [5.5, 17.5], 'food': 'garlic', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/garlic.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'garlic'}, {'height': 1, 'width': 2, 'position': [7.5, 17.5], 'food': 'leek', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/leek_onion.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'leek'}, {'height': 1, 'width': 2, 'position': [9.5, 17.5], 'food': 'red bell pepper', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/bell_pepper_red.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'red bell pepper'}, {'height': 1, 'width': 2, 'position': [11.5, 17.5], 'food': 'carrot', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/carrot.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'carrot'}, {'height': 1, 'width': 2, 'position': [13.5, 17.5], 'food': 'lettuce', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/lettuce.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'lettuce'}, {'height': 1, 'width': 2, 'position': [5.5, 21.5], 'food': 'avocado', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/avocado.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'avocado'}, {'height': 1, 'width': 2, 'position': [7.5, 21.5], 'food': 'broccoli', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/broccoli.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'broccoli'}, {'height': 1, 'width': 2, 'position': [9.5, 21.5], 'food': 'cucumber', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cucumber.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cucumber'}, {'height': 1, 'width': 2, 'position': [11.5, 21.5], 'food': 'yellow bell pepper', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/bell_pepper_yellow.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'yellow bell pepper'}, {'height': 1, 'width': 2, 'position': [13.5, 21.5], 'food': 'onion', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/onion.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'onion'}], 'cartReturns': [{'height': 6, 'width': 0.7, 'position': [1, 18.5], 'quantity': 6}, {'height': 6, 'width': 0.7, 'position': [2, 18.5], 'quantity': 6}], 'basketReturns': [{'height': 0.2, 'width': 0.3, 'position': [3.5, 18.5], 'quantity': 12}], 'counters': [{'height': 2.25, 'width': 1.5, 'position': [18.25, 4.75], 'food': 'prepared foods', 'price': 15}, {'height': 2.25, 'width': 1.5, 'position': [18.25, 10.75], 'food': 'fresh fish', 'price': 12}, {'height': 2.25, 'width': 1.5, 'position': [18.25, 10.75], 'food': 'fresh fish', 'price': 12}]}, 'step': 1, 'gameOver': False, 'violations': ''} +# [11.5, 17.5] +# hang@jstaley-XPS-8940:~/TA/propershopper-1$ python astar_path_planner.py +# action_commands: ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT'] +# ['sausage', 'milk', 'prepared foods', 'onion', 'fresh fish', 'apples', 'oranges', 'steak'] +# {'command_result': {'command': 'RESET', 'result': 'SUCCESS', 'message': '', 'stepCost': 0}, 'observation': {'players': [{'index': 0, 'position': [1.2, 15.6], 'width': 0.6, 'height': 0.4, 'sprite_path': None, 'direction': 2, 'curr_cart': -1, 'shopping_list': ['sausage', 'milk', 'prepared foods', 'onion', 'fresh fish', 'apples', 'oranges', 'steak'], 'list_quant': [2, 3, 1, 1, 1, 1, 1, 1], 'holding_food': None, 'bought_holding_food': False, 'budget': 100, 'bagged_items': [], 'bagged_quant': []}], 'carts': [], 'baskets': [], 'registers': [{'height': 2.5, 'width': 2.25, 'position': [1, 4.5], 'num_items': 0, 'foods': [], 'food_quantities': [], 'food_images': [], 'capacity': 12, 'image': 'images/Registers/registersA.png', 'curr_player': None}, {'height': 2.5, 'width': 2.25, 'position': [1, 9.5], 'num_items': 0, 'foods': [], 'food_quantities': [], 'food_images': [], 'capacity': 12, 'image': 'images/Registers/registersB.png', 'curr_player': None}], 'shelves': [{'height': 1, 'width': 2, 'position': [5.5, 1.5], 'food': 'milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'milk'}, {'height': 1, 'width': 2, 'position': [7.5, 1.5], 'food': 'milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'milk'}, {'height': 1, 'width': 2, 'position': [9.5, 1.5], 'food': 'chocolate milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk_chocolate.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'chocolate milk'}, {'height': 1, 'width': 2, 'position': [11.5, 1.5], 'food': 'chocolate milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk_chocolate.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'chocolate milk'}, {'height': 1, 'width': 2, 'position': [13.5, 1.5], 'food': 'strawberry milk', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/milk_strawberry.png', 'shelf_image': 'images/Shelves/fridge.png', 'food_name': 'strawberry milk'}, {'height': 1, 'width': 2, 'position': [5.5, 5.5], 'food': 'apples', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/apples.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'apples'}, {'height': 1, 'width': 2, 'position': [7.5, 5.5], 'food': 'oranges', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/oranges.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'oranges'}, {'height': 1, 'width': 2, 'position': [9.5, 5.5], 'food': 'banana', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/banana.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'banana'}, {'height': 1, 'width': 2, 'position': [11.5, 5.5], 'food': 'strawberry', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/strawberry.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'strawberry'}, {'height': 1, 'width': 2, 'position': [13.5, 5.5], 'food': 'raspberry', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/raspberry.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'raspberry'}, {'height': 1, 'width': 2, 'position': [5.5, 9.5], 'food': 'sausage', 'price': 4, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/sausage.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'sausage'}, {'height': 1, 'width': 2, 'position': [7.5, 9.5], 'food': 'steak', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/meat_01.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'steak'}, {'height': 1, 'width': 2, 'position': [9.5, 9.5], 'food': 'steak', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/meat_02.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'steak'}, {'height': 1, 'width': 2, 'position': [11.5, 9.5], 'food': 'chicken', 'price': 6, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/meat_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'chicken'}, {'height': 1, 'width': 2, 'position': [13.5, 9.5], 'food': 'ham', 'price': 6, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/ham.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'ham'}, {'height': 1, 'width': 2, 'position': [5.5, 13.5], 'food': 'brie cheese', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_01.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'brie cheese'}, {'height': 1, 'width': 2, 'position': [7.5, 13.5], 'food': 'swiss cheese', 'price': 5, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_02.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'swiss cheese'}, {'height': 1, 'width': 2, 'position': [9.5, 13.5], 'food': 'cheese wheel', 'price': 15, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cheese wheel'}, {'height': 1, 'width': 2, 'position': [11.5, 13.5], 'food': 'cheese wheel', 'price': 15, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cheese wheel'}, {'height': 1, 'width': 2, 'position': [13.5, 13.5], 'food': 'cheese wheel', 'price': 15, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cheese_03.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cheese wheel'}, {'height': 1, 'width': 2, 'position': [5.5, 17.5], 'food': 'garlic', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/garlic.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'garlic'}, {'height': 1, 'width': 2, 'position': [7.5, 17.5], 'food': 'leek', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/leek_onion.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'leek'}, {'height': 1, 'width': 2, 'position': [9.5, 17.5], 'food': 'red bell pepper', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/bell_pepper_red.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'red bell pepper'}, {'height': 1, 'width': 2, 'position': [11.5, 17.5], 'food': 'carrot', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/carrot.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'carrot'}, {'height': 1, 'width': 2, 'position': [13.5, 17.5], 'food': 'lettuce', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/lettuce.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'lettuce'}, {'height': 1, 'width': 2, 'position': [5.5, 21.5], 'food': 'avocado', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/avocado.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'avocado'}, {'height': 1, 'width': 2, 'position': [7.5, 21.5], 'food': 'broccoli', 'price': 1, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/broccoli.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'broccoli'}, {'height': 1, 'width': 2, 'position': [9.5, 21.5], 'food': 'cucumber', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/cucumber.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'cucumber'}, {'height': 1, 'width': 2, 'position': [11.5, 21.5], 'food': 'yellow bell pepper', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/bell_pepper_yellow.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'yellow bell pepper'}, {'height': 1, 'width': 2, 'position': [13.5, 21.5], 'food': 'onion', 'price': 2, 'capacity': 12, 'quantity': 12, 'food_image': 'images/food/onion.png', 'shelf_image': 'images/Shelves/shelf.png', 'food_name': 'onion'}], 'cartReturns': [{'height': 6, 'width': 0.7, 'position': [1, 18.5], 'quantity': 6}, {'height': 6, 'width': 0.7, 'position': [2, 18.5], 'quantity': 6}], 'basketReturns': [{'height': 0.2, 'width': 0.3, 'position': [3.5, 18.5], 'quantity': 12}], 'counters': [{'height': 2.25, 'width': 1.5, 'position': [18.25, 4.75], 'food': 'prepared foods', 'price': 15}, {'height': 2.25, 'width': 1.5, 'position': [18.25, 10.75], 'food': 'fresh fish', 'price': 12}, {'height': 2.25, 'width': 1.5, 'position': [18.25, 10.75], 'food': 'fresh fish', 'price': 12}]}, 'step': 1, 'gameOver': False, 'violations': ''} +# [11.5, 17.5] + +if __name__=="__main__": + + action_commands = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT'] + + print("action_commands: ", action_commands) + + # Connect to Supermarket + HOST = '127.0.0.1' + PORT = 9000 + sock_game = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock_game.connect((HOST, PORT)) + sock_game.send(str.encode("0 RESET")) # reset the game + state = recv_socket_data(sock_game) + game_state = json.loads(state) + shopping_list = game_state['observation']['players'][0]['shopping_list'] + shopping_quant = game_state['observation']['players'][0]['list_quant'] + agent = HighLevelPlanner(socket_game=sock_game, env=game_state) + print(shopping_list) + + + # shopping_list = ['fresh fish', 'prepared foods'] + + + diff --git a/final_proj/map_box_regions.pdf b/final_proj/map_box_regions.pdf new file mode 100644 index 0000000..55a5270 Binary files /dev/null and b/final_proj/map_box_regions.pdf differ diff --git a/final_proj/socket_agent_proj.py b/final_proj/socket_agent_proj.py new file mode 100755 index 0000000..107d963 --- /dev/null +++ b/final_proj/socket_agent_proj.py @@ -0,0 +1,525 @@ + +import json +import socket +import random +from copy import deepcopy + +import pandas as pd +import pathfind.graph.transform + +from enums.direction import Direction +from final_proj.util import get_geometry +from helper import project_collision +from util import * +from final_proj.fast_high_level_astar import * +from box_regions import * + +# todo: update with Helen's method for making interaction areas? +def populate_locs(observation): + # add interaction areas to objects in the observation + obs_with_boxes = add_interact_boxes_to_obs(obs=observation) + locs: dict = {} + + for idx, obj in enumerate(obs_with_boxes['registers']): + geometry = get_geometry(obj) + geometry['position'][0] += geometry['width'] + 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs[f'register {idx}'] = geometry + + for idx, obj in enumerate(obs_with_boxes['cartReturns']): + geometry = get_geometry(obj) + geometry['position'][1] -= 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs[f'cartReturn {idx}'] = geometry + + for idx, obj in enumerate(obs_with_boxes['basketReturns']): + geometry = get_geometry(obj) + geometry['position'][1] -= 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs[f'basketReturn {idx}'] = geometry + + for obj in obs_with_boxes['counters']: + geometry = get_geometry(obj) + geometry['position'][0] -= 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs[obj['food']] = geometry + + for obj in obs_with_boxes['shelves']: + geometry = get_geometry(obj) + geometry['position'][1] += geometry['height'] + 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs[obj['food']] = geometry + + for idx, obj in enumerate(obs_with_boxes['carts']): + geometry = get_geometry(obj) + geometry['position'][1] += geometry['height'] + 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs['cart {idx}'] = geometry + + for idx, obj in enumerate(obs_with_boxes['baskets']): + geometry = get_geometry(obj) + geometry['position'][1] += geometry['height'] + 1 + geometry['interact_boxes'] = obj['interact_boxes'] + locs['basket {idx}'] = geometry + + return locs + + +class Agent: + + def __init__(self, conn, agent_id, env): + self.socket = conn + self.agent_id = agent_id + self.env = env + self.list_quant:list = env['observation']['players'][self.agent_id]['list_quant'] + self.shopping_list:list[tuple] = [(item, quant) for item, quant in zip(env['observation']['players'][self.agent_id]['shopping_list'], self.list_quant)] + self.goal = "" + self.done = False + self.container_id = -1 + self.container_type = '' + self.holding_container = False + self.holding_food = None + self.planner = HighLevelPlanner(socket_game=conn, env=env) + + def transition(self): + self.execute(action='NOP') # this updates self.env + if self.done: # If we've left the store + self.execute("NOP") # Do nothing + + elif self.container_id == -1: # If we don't have a container + self.get_container() # Get one! + + elif self.goal == "": # If we currently don't have a goal + if not self.shopping_list: # Check if there's anything left on our list + self.exit() # Leave the store (includes checkout), might need to return basket + else: # We've still got something on our shopping list + item, quantity = self.strategically_choose_from_shopping_list(self.shopping_list) + for _ in range(quantity): + self.goal = item # Set our goal to the next item on our list + self.get_item() # Go to our goal + self.transition() + elif self.goal == 'add_to_container': # If we have a goal and we're here, that means we're at the goal! + self.add_to_container() + else: # this shouldn't happen, just exit + self.exit() + + + def get_item(self): + """get `quantitiy` number of `item` + + Args: + item (str): an item on the shopping list + """ + print(f"Agent {self.agent_id} going to {self.goal}") + #TODO:go to the item and get it. Look at the implementation of `get_container` for reference. target item in stored self.goal + #change goal after getting item. + self.holding_food = self.env['observation']['players'][self.agent_id]['holding_food'] + if self.holding_food is not None: + self.holding_container = False #sanity check: can't hold both food and container + if self.holding_food == self.goal:# successfully got item + self.goal = "add_to_container" + + # Agent retrieves a container + def get_container(self): + if sum(self.list_quant) <= 6: + self.update_container(container='basket') + print(f"Agent {self.agent_id} getting a basket") + if self.container_id == -1: # has never gotten a container + self.goal = 'basketReturn 0' + self.goto(goal='basketReturn 0', is_item=True) + else: + self.goal = 'basket'# we have gotten a basket before, it's somewhere in the environment + self.goto(goal=f'basket {self.container_id}', is_item=True) + self.execute('INTERACT') + self.execute('INTERACT') + self.update_container('basket') + self.holding_container = True #we have to make this assumption, it's not reflected in the env + else: + print(f"Agent {self.agent_id} getting a cart") + self.update_container(container='cart') + if self.container_id == -1: # has never gotten a container + self.goto(goal='cartReturn 0', is_item=True) + else: + self.goal = 'cart' + self.goto(goal=f'cart {self.container_id}', is_item=True) + self.execute('INTERACT') + self.execute('INTERACT') + self.update_container('cart') + self.goal = "" + + def strategically_choose_from_shopping_list(self, shopping_list): + """Strategically choose an item from the shopping list + + Args: + shopping_list (list[tuple]): shopping list of (item, quantity) + """ + #TODO: replace with optimization strategy + return shopping_list.pop(0) + + def update_container(self, container='basket'): + """Check if we are responsible for any `container` and update container related status. Either a cart or a basket + + Args: + container (_type_): either a cart or a basket + """ + if self.env['observation']['players'][self.agent_id]['curr_cart'] != -1:#currently holding a cart + self.container_type = 'cart' + self.container_id = self.env['observation']['players'][self.agent_id]['curr_cart'] + self.holding_container = True + return + for i, c in enumerate(self.env['observation'][container+'s']): + if c['owner'] == self.agent_id: + self.container_id = i + self.container_type = container + return + + + def goto(self, goal:list|tuple|str, is_item=True): + """go to the goal, either a (x, y) of a string such as 'basket', 'register' + + Args: + goal (list | tuple | str): (x, y) or strings such as 'strawberry', 'basket' + is_item (bool, optional): if the goal is an item. Set False for (x, y). Defaults to True. + """ + if goal is None: + goal = self.goal + + if is_item:#goal is an item in the env, not a (x, y) + populate_locs(self.env['observation']) + if goal in ('cartReturn 0', 'cartReturn 1', 'basketReturn 0'): # access these from the North + interact_box = locs[goal]['interact_boxes']['NORTH_BOX'] + goal = self.interact_box_to_goal_location(box=interact_box) + elif goal in ('cart', 'basket'): + if self.container_type != goal:# current container and goal doesn't match, get current container instead + self.goto(goal=self.container_type) + return + elif self.holding_container:#already holding container + return + else: + interact_boxes:dict = locs[f'{goal} {self.container_id}'] + if goal == 'cart': + interact_box = list(interact_boxes.values())[0]#cart only has one interact box, go to that interact box + goal= self.interact_box_to_goal_location(box=interact_box) + else: + interact_box = locs[f'{goal} {self.container_id}']['interact_boxes']['SOUTH_BOX'] + goal= self.interact_box_to_goal_location(box=interact_box) + else:# access everything else from the SOUTH + interact_box = locs[goal]['interact_boxes']['SOUTH_BOX'] + goal = self.interact_box_to_goal_location(box=interact_box) + + if self.holding_container and self.container_type == 'cart': + print(f"Agent {self.agent_id} going to location {goal} with cart") + path = [] + pass # TODO: goto with cart. TA says it might be as simple as changing the player's shape as long as they are holding a cart and change it back once they are not. If it's too complicated we will skip it + else: + print(f"Agent {self.agent_id} planning a path to {goal} without cart") + path = self.planner.astar( + player_id=self.agent_id, + start=self.env['observation']['players'][self.agent_id], + goal=goal, + obs=self.env['observation'] + ) + print(f"Agent {self.agent_id} going to location {goal} without cart") + + for box_region in path: + self.reactive_nav(goal=box_region.midpoint, is_box=False) + + # we should now be in the same region as the goal (x, y) location + if is_item: + self.reactive_nav(goal=interact_box, is_box=True) + else: + self.reactive_nav(goal=goal, is_box=False) + + + def interact_box_to_goal_location(self, box:dict) -> tuple[float, float]: + """Given an interact box, determine which goal location within the box to aim for + + Args: + box (dict): interact box + + Returns: + tuple[float, float]: the goal location + """ + player_needs_to_face = box['player_needs_to_face'] + if player_needs_to_face == Direction.SOUTH: + top_left = (box['westmost'],box['northmost']) + return top_left + elif player_needs_to_face == Direction.NORTH: + bot_left = (box['westmost'],box['southmost']) + return bot_left + elif player_needs_to_face == Direction.WEST: + bot_right = (box['eastmost'],box['southmost']) + return bot_right + else: + bot_left = (box['westmost'],box['southmost']) + return bot_left + + + + def reactive_nav(self, goal, is_box=False): + """Purely reactie navigation + + Args: + goal (_type_): (x, y) or interact_box + """ + ############################################## + ## The old reactive navigation code is below## + ############################################## + target = "x" + reached_x = False + reached_y = False + stuck = 0 # stuck for timestep + while True: + player = self.env['observation']['players'][self.agent_id] + + if is_box: + goal_loc = self.interact_box_to_goal_location(box=goal) + x_dist = player['position'][0] - goal_loc['westmost'] + y_dist = player['position'][1] - goal_loc['northmost'] + if can_interact_in_box(player=player, interact_box=goal): + break + else: + x_dist = player['position'][0] - goal[0] + y_dist = player['position'][1] - goal[1] + + if abs(x_dist) < STEP: + reached_x = True + if abs(y_dist) < STEP: + reached_y = True + if reached_x and reached_y: + break + + if target == "x": + if x_dist < -STEP: + command = Direction.EAST + elif x_dist > STEP: + command = Direction.WEST + else: + reached_y = False + target = "y" + continue + else: + if y_dist < -STEP: + command = Direction.SOUTH + elif y_dist > STEP: + command = Direction.NORTH + else: + reached_x = False + target = "x" + continue + original_command = command + while project_collision(player, self.env, command, dist=STEP): + command = Direction(self._turn_ninety_degrees(dir=command)) # take the 90 degrees action instead + stuck += 1 + if stuck >= 10:#been stuck for too long, it's probably a corner, F it, take the 270 degree action + command = self._turn_ninety_degrees(self._turn_opposite_dir(original_command)) + if player['direction'] == command.value: + self.execute(action=command.name)# execute once if already facing that direction + else: + self.execute(action=command.name) + self.execute(action=command.name) + + def _turn_opposite_dir(self, dir:Direction) -> Direction: + """Turn 180 degrees with respect to the given + + Args: + command (Direction): the direction command whose ninety degree direction we want to find + Returns: + returns the 90 degrees direction + """ + if dir == Direction.NORTH: + return Direction.SOUTH + if dir == Direction.SOUTH: + return Direction.NORTH + if dir == Direction.EAST: + return Direction.WEST + else: + return Direction.EAST + + def _turn_ninety_degrees(self, dir:Direction) -> Direction: + """Turn 90 degrees clockwise with respect to the given + + Args: + command (Direction): the direction command whose ninety degree direction we want to find + Returns: + returns the 90 degrees direction + """ + turned_dir = Direction((dir.value + 2) % 5) + if turned_dir == Direction.NONE: + return Direction.SOUTH + return turned_dir + + + + def step(self, step_location:list|tuple, player_id:int, backtrack:list): + """Keep locally adjusting and stepping in the right direction so that `player` ends up `close_enough` to `step_location`. `step_location` should be only one step away + + Args: + step_location (list | tuple): a (x, y) that is assumed to be one step away from the player's current location + player_id (int): the player id for the player that needs to be at `step_location` + backtrack (list): a list of locations visited by the player + """ + goal_x, goal_y = step_location + player_x, player_y = self.env['observation']["players"][player_id]['position'] + while not self.planner.is_close_enough(current=self.env['observation']["players"][player_id]['position'], goal=step_location, tolerance=LOCATION_TOLERANCE, is_item=False):#deals with stochasticity: keep locally adjusting to the right location until it's close enough + # compare previous position with current position to determine if a location needs to be saved in the player's backtracking trace + prev_x = player_x + prev_y = player_y + player_x, player_y = self.env['observation']["players"][player_id]['position'] + if player_x != prev_x or player_y != prev_y:#player has moved, record its prev position for potential backtracking + backtrack.append((prev_x, prev_y)) + + if manhattan_distance(self.env['observation']["players"][player_id]['position'], step_location) >= BACKTRACK_TOLERANCE:#player has wandered too far due to stochasticity, there could be an object between the player and the goal `step_location` now. The player needs to backtrack to the starting location, otherwise it could be banging its head against the object forever + self.step(step_location=backtrack[-1], player_id=player_id, backtrack=backtrack[:-1]) + del backtrack[-1] + elif player_x < goal_x and abs(player_x - goal_x) >= LOCATION_TOLERANCE:# player should go EAST + #self.execute(Direction.EAST.name) + self.reactive_nav(goal=step_location, is_box=False) + elif player_x > goal_x and abs(player_x - goal_x) >= LOCATION_TOLERANCE:#player should go WEST + #self.execute(Direction.WEST.name) + self.reactive_nav(goal=step_location, is_box=False) + elif player_y < goal_y and abs(player_y - goal_y) >= LOCATION_TOLERANCE:#player should go SOUTH + #self.execute(Direction.SOUTH.name) + self.reactive_nav(goal=step_location, is_box=False) + elif player_y > goal_y and abs(player_y - goal_y) >= LOCATION_TOLERANCE:#player should go NORTH + #self.execute(Direction.NORTH.name) + self.reactive_nav(goal=step_location, is_box=False) + + # def infer_actual_action(player_state_before_execution:tuple[float, float, Direction], player_state_after_execution:tuple[float, float, Direction]): + # prev_x, prev_y, prev_orientation = player_state_before_execution + # after_x, after_y, after_orientation = player_state_after_execution + # if after_orientation != prev_orientation:#player turned + # return after_orientation + # elif prev_x < after_x:#player went EAST + # return Direction.EAST + + + # TODO: Agent picks up an item and adds it to the cart + def add_to_container(self): + #self.env + #a function to be used if the item is north of the player + violation=self.env['violations'] + #moving the cart south of the player + action = "0 " + "SOUTH" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + #leaving the cart + action = "0 " + "TOGGLE_CART" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + #moving the player till it hits the shelf + while not any("shelf" or "counter" in v for v in violation): #I've added shelf because the player might hit something else (a player of a cart) + action = "0 " + "NORTH" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + current_p_y=self.env['observation']['players'][0]['position'][1] + violation=self.env['violations'] + #picking up the item + action = "0 " + "INTERACT" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + #clearing the message + action = "0 " + "INTERACT" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + action = "0 " + "WEST" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + action = "0 " + "WEST" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + action = "0 " + "WEST" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + action = "0 " + "WEST" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + violation=[] + len(violation) + #moving back to the cart + while not any("basket" in v for v in violation): + action = "0 " + "SOUTH" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + current_p_y=self.env['observation']['players'][0]['position'][1] + violation=self.env['violations'] + #placing the item to the cart + action = "0 " + "INTERACT" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + #clearing the message + action = "0 " + "INTERACT" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + #re-picking the cart + action = "0 " + "TOGGLE_CART" + sock_game.send(str.encode(action)) # send action to env + output = recv_socket_data(sock_game) # get observation from env + self.env = json.loads(output) + + return self.env + + pass # TODO + + self.goal = "" + + # TODO use other functions to complete checkout + def exit(self): + print(f"Agent {self.agent_id} exiting") + self.goto(goal='register 0', is_item=True) + + + self.goto([-0.6, 3.0], is_item=False)#upper exit + + self.done = True + + # Reads the shopping list + def init_list(self): + print(f"Agent {self.agent_id} reading list") + shopping_list = [] + self.execute("NOP") + shopping_list += self.env['observation']["players"][self.agent_id]["shopping_list"].copy() + return shopping_list + + # Given an action, executes it for this agent + def execute(self, action): + action = f"{self.agent_id} {action}" + self.socket.send(str.encode(action)) # send action to env + output = recv_socket_data(self.socket) # get observation from env + self.env = json.loads(output) + + +if __name__ == "__main__": + + action_commands = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT'] + + print("action_commands: ", action_commands) + + # Connect to Supermarket + HOST = '127.0.0.1' + PORT = 9000 + sock_game = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock_game.connect((HOST, PORT)) + sock_game.send(str.encode("0 RESET")) # reset the game + state = recv_socket_data(sock_game) + sock_game.send(str.encode("0 NOP")) # send action to env + output = recv_socket_data(sock_game) # get observation from env + output = json.loads(output) + locs = populate_locs(output['observation']) + agents = [Agent(sock_game, 0, env=output)] + # agents = [Agent(sock_game, 0), Agent(sock_game, 1), Agent(sock_game, 2)] + while True: + for agent in agents: + agent.transition() diff --git a/final_proj/util.py b/final_proj/util.py new file mode 100644 index 0000000..02fdd1c --- /dev/null +++ b/final_proj/util.py @@ -0,0 +1,265 @@ +import time +import json +import copy +from pathlib import Path +from constants import * +from copy import deepcopy +from constants import * +from enums.direction import Direction +from helper import * + + +def get_geometry(obj): + obj_copy = deepcopy(obj) + return { + "position": obj_copy["position"], + "height": obj_copy["height"], + "width": obj_copy["width"] + } + + +def recv_socket_data(sock): + BUFF_SIZE = 4096 # 4 KiB + data = b'' + while True: + time.sleep(0.00001) + part = sock.recv(BUFF_SIZE) + data += part + if len(part) < BUFF_SIZE: + # either 0 or end of data + break + + return data + +def manhattan_distance(pos1, pos2): + # Calculate the Manhattan distance from pos1 to pos2 + return abs(pos2[0] - pos1[0]) + abs(pos2[1] - pos2[1]) + +def euclidean_distance(pos1, pos2): + # Calculate Euclidean distance between two points + return ((pos1[0] - pos2[0])**2 + (pos1[1] - pos2[1])**2)**0.5 + +def calculate_crowdedness_factor(player_id:int, box:dict, observation:dict) -> float: + """Given an observation, a player, and a box region, calculate how crowded that region is + + Args: + player_id (int): the agent excluded in the calculation + box (dict): the region of interest {"westmost":float, "southmost":float, "eastmost":float, "northmost":float} + observation (dict): observation containing the players in the map + Returns: + how much empty space per unit area + """ + area = abs(box['westmost'] - box['eastmost']) * abs(box['northmost'] - box['southmost']) + occupied_area = 0 + for p in observation['players']: + if p['index'] == player_id: # don't count our player + continue + if obj_overlap_with_box(p, box): + occupied_area += p['width']*p['height'] + for c in observation['carts']: + if c['owner'] == player_id: # don't count our cart + continue + if obj_overlap_with_box(c, box): + occupied_area += c['width'] * c['height'] + for b in observation['baskets']: + if b['owner'] == player_id: # don't count our basket + continue + if obj_overlap_with_box(b, box): + occupied_area += b['width'] * b['height'] + return 1.0 - (occupied_area / area) + + # divide number of players by area of the box region + return player_count / (abs(box['westmost'] - box['eastmost']) * abs(box['northmost'] - box['southmost'])) + +def bounding_box(place:dict) -> dict: + """find the bounding box for a place. + + Args: + place (dict): a dictionary containing height, width and position of the object + + Returns: + dict: a dictionary containing the northmost y, the southmost y, and westmost x and the eastmost x + """ + upper_left = place['position'] + height = place['height'] + width = place['width'] + return {"northmost":upper_left[1], "westmost":upper_left[0], "southmost":upper_left[1]+height, "eastmost":upper_left[0]+width} + +def find_midpoint(interact_box:dict) -> tuple[float, float]: + """find the midpoint along an edge of an interact box. Which edge it is depends on the direction the player needs to face in the box + + Args: + interact_box (dict): an interact box for an object + + Returns: + tuple[float, float]: (x, y) + """ + player_needs_to_face = interact_box['player_needs_to_face'] + if player_needs_to_face == Direction.NORTH.name:#this box is to the SOUTH of the interaction object + edge_y = interact_box['southmost'] + edge_x = (interact_box['eastmost'] - interact_box['westmost']) / 2.0 + interact_box['westmost'] + return (edge_x, edge_y) + elif player_needs_to_face == Direction.SOUTH.name:#this box is to the NORTH of the interaction object + edge_y = interact_box['northmost'] + edge_x = (interact_box['eastmost'] - interact_box['westmost']) / 2.0 + interact_box['westmost'] + return (edge_x, edge_y) + elif player_needs_to_face == Direction.WEST.name:#this box is to the EAST of the interaction object + edge_x = interact_box['eastmost'] + edge_y = (interact_box['southmost'] - interact_box['northmost']) / 2.0 + interact_box['northmost'] + return (edge_x, edge_y) + else:#this box is to the WEST of the interaction object + edge_x = interact_box['westmost'] + edge_y = (interact_box['southmost'] - interact_box['northmost']) / 2.0 + interact_box['northmost'] + return (edge_x, edge_y) + +def player_interact_area(player) -> list: + interact_boxes = four_side_interact_area(player) + dir = player['direction'] + player['interact_box'] = interact_boxes[dir] + return player['interact_box'] + +def one_side_interact_area(place_obj) -> list: + place_bounding_box = bounding_box(place_obj) + # player needs to be within one of the interact boxes and facing the object to interact + # these place objects can only be accessed from the north side e.g. cart returns + interact_box = copy.deepcopy(place_bounding_box) + interact_box['southmost'] = interact_box['northmost'] + interact_box['northmost'] -= interact_distance + interact_box['player_needs_to_face'] = Direction.SOUTH + return {'NORTH_BOX':interact_box} + +def two_side_interact_area(place_obj) -> list: + place_bounding_box = bounding_box(place_obj) + # player needs to be within one of the interact boxes and facing the object to interact + # these place objects can only be accessed from two sides + interact_boxes = one_side_interact_area(place_obj) + south_interact_box = copy.deepcopy(place_bounding_box) + south_interact_box['northmost'] = place_bounding_box['southmost'] + south_interact_box['southmost'] += interact_distance + south_interact_box['player_needs_to_face'] = Direction.NORTH + interact_boxes['SOUTH_BOX'] = south_interact_box + return interact_boxes + +def four_side_interact_area(place_obj:dict) -> dict: + place_bounding_box = bounding_box(place_obj) + # player needs to be within interact box to interact + interact_boxes = two_side_interact_area(place_obj) + west_interact_box = copy.deepcopy(place_bounding_box) + west_interact_box['eastmost'] = place_bounding_box['westmost'] + west_interact_box['westmost'] -= interact_distance + west_interact_box['player_needs_to_face'] = Direction.EAST + east_interact_box = copy.deepcopy(place_bounding_box) + east_interact_box['westmost'] = place_bounding_box['eastmost'] + east_interact_box['eastmost'] += interact_distance + east_interact_box['player_needs_to_face'] = Direction.WEST + interact_boxes['WEST_BOX'] = west_interact_box + interact_boxes['EAST_BOX'] = east_interact_box + return interact_boxes + +def obj_overlap_with_box(obj:dict, box:dict): + obj['bounding_box'] = bounding_box(place=obj) + return not ( + obj['bounding_box']['westmost'] > box['eastmost'] or\ + obj['bounding_box']['northmost'] > box['southmost'] or\ + obj['bounding_box']['eastmost'] < box['westmost'] or\ + obj['bounding_box']['southmost'] < box['northmost'] + ) + + +def can_interact_in_box(player, interact_box) -> bool: + """Returns whether `player` overlaps with interact_box + + Args: + player (dict): player + interact_box (dict): interact box + + Returns: + bool: Whether the player can interact in box + """ + player['bounding_box'] = bounding_box(place=player) + if obj_overlap_with_box(obj=player, box=interact_box) and player['direction'] == interact_box['player_needs_to_face'].value: + return True + return False + +def can_interact_player(player, place_obj) -> bool: + """Returns whether the `player` object can interact with the `place_obj` based on their interact boxes + + Args: + player (dict): a player dictionary containing the player's interact boxes + place_obj (dict): a place object containing the place's interact boxes + + Returns: + bool: whether the player and the place can interact + """ + for interact_box in place_obj['interact_boxes']: + if obj_overlap_with_box(obj=player, box=interact_box) and player_directions[player['direction']] == interact_box['player_needs_to_face']: + return True + return False + +def loc_in_interact_box(box, loc) -> bool: + return ( + box['northmost'] <= loc[1] <= box['southmost'] and box['westmost'] <= loc[0] <= box['eastmost'] + ) + +def x_in_interact_box(box, x) -> bool: + return ( + box['westmost'] <= x <= box['eastmost'] + ) + +def y_in_interact_box(box, y) -> bool: + return ( + box['northmost'] <= y <= box['southmost'] + ) + + +def add_interact_boxes_to_obs(obs) -> dict: + """Add interact boxes to objects in the observation + + Args: + obs (dict): the observation dictionary + + Returns: + dict: obs with interact boxes added to its obejcts + """ + for object_name in obs: + object_list = obs[object_name] + for _, obj_dict in enumerate(object_list): + if object_name == 'players': + obj_dict['bounding_box'] = bounding_box(obj_dict) + elif object_name == "cartReturns": + obj_dict["interact_boxes"] = one_side_interact_area(obj_dict) + elif object_name == "baskets": + obj_dict["interact_boxes"] = four_side_interact_area(obj_dict) + elif object_name == "carts": + possible_boxes = four_side_interact_area(obj_dict) + obj_dir = obj_dict['direction'] + if obj_dir == Direction.NORTH:#can be interacted with from the SOUTH + obj_dict["interact_boxes"] = {'SOUTH_BOX':possible_boxes['SOUTH_BOX']} + elif obj_dir == Direction.SOUTH:#can be interacted with from the NORTH + obj_dict["interact_boxes"] = {'NORTH_BOX':possible_boxes['NORTH_BOX']} + elif obj_dir == Direction.WEST:#can be interacted with from the EAST + obj_dict["interact_boxes"] = {'EAST_BOX':possible_boxes['EAST_BOX']} + else:#can be interacted with from the WEST + obj_dict["interact_boxes"] = {'WEST_BOX':possible_boxes['SOUTH_BOX']} + elif object_name == "shelves": + obj_dict['interact_boxes'] = two_side_interact_area(obj_dict) + else: + obj_dict["interact_boxes"] = four_side_interact_area(obj_dict) + return obs + +if __name__ == "__main__": + # run this to test adding interact boxes to objects in env.json (json version of the env object) + # Get the path to the current file + current_file_path = Path(__file__) + + # Get the folder containing the current file + dir = current_file_path.parent + f = open(dir / "env.json") + obs = json.load(f) + # test adding interact boxes to each object in the environment + obs = add_interact_boxes_to_obs(obs) + with open(dir / 'env_interact_boxes.json', 'w') as file: + # Write the dictionary to the JSON file + json.dump(obs, file, indent=4) + + diff --git a/helper.py b/helper.py old mode 100755 new mode 100644 index 1fd0483..847d95c --- a/helper.py +++ b/helper.py @@ -1,9 +1,13 @@ +import decimal +import math +from copy import deepcopy +from final_proj.constants import * from enums.direction import Direction -def obj_collision(obj, x_position, y_position, x_margin=0.55, y_margin=0.55): +def obj_collision(obj, x_position, y_position, x_margin=0.55, y_margin=0.55): return obj.position[0] - x_margin < x_position < obj.position[0] + obj.width + x_margin and \ - obj.position[1] - y_margin < y_position < obj.position[1] + obj.height + y_margin + obj.position[1] - y_margin < y_position < obj.position[1] + obj.height + y_margin def overlap(x1, y1, width_1, height_1, x2, y2, width_2, height_2): @@ -11,11 +15,11 @@ def overlap(x1, y1, width_1, height_1, x2, y2, width_2, height_2): def objects_overlap(obj1, obj2): - return overlap(obj1.position[0], obj1.position[1], obj1.width, obj1.height, - obj2.position[0], obj2.position[1], obj2.width, obj2.height) + return overlap(obj1['position'][0], obj1['position'][1], obj1['width'], obj1['height'], + obj2['position'][0], obj2['position'][1], obj2['width'], obj2['height']) -def pos_collision(x1, y1, x2, y2, x_margin, y_margin): +def pos_collision(x1, y1, x2, y2, x_margin, y_margin): return x1 - x_margin < x2 < x1 + x_margin and y1 - y_margin < y2 < y1 + y_margin @@ -28,4 +32,164 @@ def can_interact_default(obj, player, range=0.5): return obj.collision(player, player.position[0] - range, player.position[1]) elif player.direction == Direction.EAST: return obj.collision(player, player.position[0] + range, player.position[1]) - return False \ No newline at end of file + return False + + + +def project_collision_with_orientation(obj, state, direction: Direction, dist=0.4, buffer=0.0):#orientation matters + """Project collision while taking the obj's orientation into account. This should only be used when the player is very close to the target item they want to interact with. Otherwise, the player might get stuck turning back and forth in a corner formed by static obstacles + + Args: + obj (dict): most likely the player + state (dict): game state + direction (Direction): directional command + dist (float, optional): distance the obj is about to travel. Defaults to 0.4. + buffer (float, optional): buffer between objects in the env. Defaults to 0.0. + + Returns: + _type_: _description_ + """ + obj_copy = deepcopy(obj) + + if direction == Direction.NORTH: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][1] -= dist + if obj_copy['position'][1] < 2.1: + return True + elif direction == Direction.EAST: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][0] += dist + if obj_copy['position'][0] > 18.5: + return True + elif direction == Direction.SOUTH: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][1] += dist + if obj_copy['position'][1] > 24: + return True + elif direction == Direction.WEST: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][0] -= dist + if obj_copy['position'][0] < 0.55: + return True + + for key, value in state['observation'].items(): + for item in value: + if key == 'players':#for players, pretend that they are wider and taller than they actually are to stay away + if (overlap(obj_copy['position'][0], obj_copy['position'][1], obj_copy['width'], obj_copy['height'], + item['position'][0], item['position'][1], item['width'] + buffer + 2* STEP, item['height'] + buffer + 2* STEP)): + if not (obj_copy == item or ( + 'index' in item.keys() and 'index' in obj_copy.keys() and item['index'] == obj_copy['index'])): + + print("projected collision with: ", {key}) + return True + else: + if (overlap(obj_copy['position'][0], obj_copy['position'][1], obj_copy['width'], obj_copy['height'], + item['position'][0], item['position'][1], item['width'] + buffer, item['height'] + buffer)): + if not (obj_copy == item or ( + 'index' in item.keys() and 'index' in obj_copy.keys() and item['index'] == obj_copy['index'])): + + print("projected collision with: ", {key}) + return True + return False + +def project_collision(obj:dict|list|tuple, state, direction: Direction, dist=0.4, buffer=0.0): + """Project collision. This should only be used when the player is likely far from the target item they want to interact with. Otherwise, the player might get stuck turning back and forth in a corner formed by static obstacles + + Args: + obj (dict): most likely the player + state (dict): game state + direction (Direction): directional command + dist (float, optional): distance the obj is about to travel. Defaults to 0.4. + buffer (float, optional): buffer between objects in the env. Defaults to 0.0. + + Returns: + _type_: _description_ + """ + obj_copy = deepcopy(obj) + + if direction == Direction.NORTH: + obj_copy['position'][1] -= dist + if obj_copy['position'][1] < 2.1: + return True + elif direction == Direction.EAST: + obj_copy['position'][0] += dist + if obj_copy['position'][0] > 18.5: + return True + elif direction == Direction.SOUTH: + obj_copy['position'][1] += dist + if obj_copy['position'][1] > 24: + return True + elif direction == Direction.WEST: + obj_copy['position'][0] -= dist + if obj_copy['position'][0] < 0.55: + return True + + for key, value in state['observation'].items(): + for item in value: + if key == 'players':#for players, pretend that they are wider and taller than they actually are to stay away + if (overlap(obj_copy['position'][0], obj_copy['position'][1], obj_copy['width'], obj_copy['height'], + item['position'][0], item['position'][1], item['width'] + buffer + 2* STEP, item['height'] + buffer + 2* STEP)): + if not (obj_copy == item or ( + 'index' in item.keys() and 'index' in obj_copy.keys() and item['index'] == obj_copy['index'])): + + print("projected collision with: ", {key}) + return True + else: + + if (overlap(obj_copy['position'][0], obj_copy['position'][1], obj_copy['width'], obj_copy['height'], + item['position'][0], item['position'][1], item['width'] + buffer, item['height'] + buffer)): + if not (obj_copy == item or ( + 'index' in item.keys() and 'index' in obj_copy.keys() and item['index'] == obj_copy['index'])): + + print("projected collision with: ", {key}) + return True + return False + + +def project_collision_dyn(obj, state, direction: Direction, dist=0.4, buffer=0): + obj_copy = deepcopy(obj) + + if direction == Direction.NORTH: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][1] -= dist + if obj_copy['position'][1] < 2.1: + return True + elif direction == Direction.EAST: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][0] += dist + if obj_copy['position'][0] > 18.5: + return True + elif direction == Direction.SOUTH: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][1] += dist + if obj_copy['position'][1] > 24: + return True + elif direction == Direction.WEST: + if obj_copy['direction'] == direction.value:#object moves + obj_copy['position'][0] -= dist + if obj_copy['position'][0] < 0.55: + return True + + for key, value in state['observation'].items(): + for item in value: + if (overlap(obj_copy['position'][0], obj_copy['position'][1], obj_copy['width'], obj_copy['height'], + item['position'][0] - buffer, item['position'][1] - buffer, item['width'] + buffer, + item['height'] + buffer)): + if not (obj_copy == item or ( + 'index' in item.keys() and 'index' in obj_copy.keys() and item['index'] == obj_copy['index'])): + if key == "players": + return -1 + else: + return -1 + return 0 + + +def round_float(n, granularity): + d = decimal.Decimal(str(granularity)) + precision = -1 * d.as_tuple().exponent + return round(round(n / granularity) * granularity, precision) + + +def euclidean_distance(pos1, pos2): + # Calculate Euclidean distance between two points + return ((pos1[0] - pos2[0]) ** 2 + (pos1[1] - pos2[1]) ** 2) ** 0.5 diff --git a/socket_agent.py b/socket_agent.py deleted file mode 100755 index f868679..0000000 --- a/socket_agent.py +++ /dev/null @@ -1,42 +0,0 @@ -# Author: Gyan Tatiya -# Email: Gyan.Tatiya@tufts.edu - -import json -import random -import socket - -from env import SupermarketEnv -from utils import recv_socket_data - - -if __name__ == "__main__": - - # Make the env - # env_id = 'Supermarket-v0' - # env = gym.make(env_id) - - action_commands = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT'] - - print("action_commands: ", action_commands) - - # Connect to Supermarket - HOST = '127.0.0.1' - PORT = 9000 - sock_game = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock_game.connect((HOST, PORT)) - - while True: - # action = str(random.randint(0, 1)) - # action += " " + random.choice(action_commands) # random action - - # assume this is the only agent in the game - action = "0 " + "SOUTH" - - print("Sending action: ", action) - sock_game.send(str.encode(action)) # send action to env - - output = recv_socket_data(sock_game) # get observation from env - output = json.loads(output) - - print("Observations: ", output["observation"]) - print("Violations", output["violations"]) diff --git a/socket_agent_performing.py b/socket_agent_performing.py deleted file mode 100644 index 23815f6..0000000 --- a/socket_agent_performing.py +++ /dev/null @@ -1,62 +0,0 @@ -#Author: Hang Yu - -import json -import random -import socket - -import gymnasium as gym -from env import SupermarketEnv -from utils import recv_socket_data - -from Q_Learning_agnet import QLAgent # Make sure to import your QLAgent class -import pickle -import pandas as pd - - -if __name__ == "__main__": - - - action_commands = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT', 'RESET'] - # Initialize Q-learning agent - action_space = len(action_commands) - 1 # Assuming your action space size is equal to the number of action commands - agent = QLAgent(action_space) - agent.qtable = pd.read_json('qtable.json') - - - - # Connect to Supermarket - HOST = '127.0.0.1' - PORT = 9000 - sock_game = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock_game.connect((HOST, PORT)) - - training_time = 100 - episode_length = 1000 - for i in range(training_time): - sock_game.send(str.encode("0 RESET")) # reset the game - state = recv_socket_data(sock_game) - state = json.loads(state) - cnt = 0 - while not state['gameOver']: - cnt += 1 - # Choose an action based on the current state - action_index = agent.choose_action(state) - action = "0 " + action_commands[action_index] - - print("Sending action: ", action) - sock_game.send(str.encode(action)) # send action to env - - next_state = recv_socket_data(sock_game) # get observation from env - next_state = json.loads(next_state) - - # Update state - state = next_state - agent.qtable.to_json('qtable.json') - - if cnt > episode_length: - break - # Additional code for end of episode if needed - - # Close socket connection - sock_game.close() - diff --git a/socket_agent_training.py b/socket_agent_training.py deleted file mode 100644 index 269f275..0000000 --- a/socket_agent_training.py +++ /dev/null @@ -1,96 +0,0 @@ -#Author Hang Yu - -import json -import random -import socket - -import gymnasium as gym -from env import SupermarketEnv -from utils import recv_socket_data - -from Q_Learning_agnet import QLAgent # Make sure to import your QLAgent class -import pickle -import pandas as pd - - -cart = False -exit_pos = [-0.8, 15.6] # The position of the exit in the environment from [-0.8, 15.6] in x, and y = 15.6 -cart_pos_left = [1, 18.5] # The position of the cart in the environment from [1, 2] in x, and y = 18.5 -cart_pos_right = [2, 18.5] - -def distance_to_cart(state): - agent_position = state['observation']['players'][0]['position'] - if agent_position[0] > 1.5: - cart_distances = [euclidean_distance(agent_position, cart_pos_right)] - else: - cart_distances = [euclidean_distance(agent_position, cart_pos_left)] - return min(cart_distances) - -def euclidean_distance(pos1, pos2): - # Calculate Euclidean distance between two points - return ((pos1[0] - pos2[0])**2 + (pos1[1] - pos2[1])**2)**0.5 - - -def calculate_reward(previous_state, current_state): - # design your own reward function here - # You should design a function to calculate the reward for the agent to guide the agent to do the desired task - pass - -if __name__ == "__main__": - - - action_commands = ['NOP', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'TOGGLE_CART', 'INTERACT', 'RESET'] - # Initialize Q-learning agent - action_space = len(action_commands) - 1 # Assuming your action space size is equal to the number of action commands - agent = QLAgent(action_space) - -#################### - #Once you have your agent trained, or you want to continue training from a previous training session, you can load the qtable from a json file - #agent.qtable = pd.read_json('qtable.json') -#################### - - - # Connect to Supermarket - HOST = '127.0.0.1' - PORT = 9000 - sock_game = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock_game.connect((HOST, PORT)) - - training_time = 100 - episode_length = 1000 - for i in range(training_time): - sock_game.send(str.encode("0 RESET")) # reset the game - state = recv_socket_data(sock_game) - state = json.loads(state) - cnt = 0 - while not state['gameOver']: - cnt += 1 - # Choose an action based on the current state - action_index = agent.choose_action(state) - action = "0 " + action_commands[action_index] - - print("Sending action: ", action) - sock_game.send(str.encode(action)) # send action to env - - next_state = recv_socket_data(sock_game) # get observation from env - next_state = json.loads(next_state) - - # Define the reward based on the state and next_state - reward = calculate_reward(state, next_state) # You need to define this function - print("------------------------------------------") - print(reward, action_commands[action_index]) - print("------------------------------------------") - # Update Q-table - agent.learning(action_index, reward, state, next_state) - - # Update state - state = next_state - agent.qtable.to_json('qtable.json') - - if cnt > episode_length: - break - # Additional code for end of episode if needed - - # Close socket connection - sock_game.close() -