From 6276804bb810e7e6ad508b229d7dbf8ae5fe4506 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 26 Jan 2023 16:05:34 -0500 Subject: [PATCH 01/30] push to transfer live share device --- teamNN/project1/variant1.py | 24 ++++++------- teamNN/testcharacter.py | 71 +++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index 54389be8..3dc1bc40 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -10,10 +10,10 @@ sys.path.insert(1, '../teamNN') # Uncomment this if you want the empty test character -#from testcharacter import TestCharacter +from testcharacter import TestCharacter # Uncomment this if you want the interactive character -from interactivecharacter import InteractiveCharacter +# from interactivecharacter import InteractiveCharacter # Create the game g = Game.fromfile('map.txt') @@ -21,21 +21,21 @@ # TODO Add your character # Uncomment this if you want the test character -# g.add_character(TestCharacter("me", # name -# "C", # avatar -# 0, 0 # position -# )) +g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position +)) # Uncomment this if you want the interactive character -g.add_character(InteractiveCharacter("me", # name - "C", # avatar - 0, 0 # position -)) +# g.add_character(InteractiveCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) # Run! # Use this if you want to press ENTER to continue at each step -# g.go(0) +g.go(0) # Use this if you want to proceed automatically -g.go(1) +# g.go(1) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 6f13216f..2fecbf0c 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -6,7 +6,74 @@ from colorama import Fore, Back class TestCharacter(CharacterEntity): + def evaluate(self, wrld): + print("Doing evaluation based on the current map") + #Read the current map (including the current position, walls and monster) + #First stage: If wall then -1, empty then 1, portal then 100 and if monster then -10 + + def a_star(self, wrld): + + def look_for_monster(self, wrld): + for dx in range(-self.rnge, self.rnge+1): + # Avoid out-of-bounds access + if ((self.x + dx >= 0) and (self.x + dx < wrld.width())): + for dy in range(-self.rnge, self.rnge+1): + # Avoid out-of-bounds access + if ((self.y + dy >= 0) and (self.y + dy < wrld.height())): + # Is a character at this position? + if (wrld.characters_at(self.x + dx, self.y + dy)): + return (True, dx, dy) + # Nothing found + return (False, 0, 0) + + def must_change_direction(self, wrld): + # Get next desired position + (nx, ny) = self.nextpos() + # If next pos is out of bounds, must change direction + if ((nx < 0) or (nx >= wrld.width()) or + (ny < 0) or (ny >= wrld.height())): + return True + # If these cells are an explosion, a wall, or a monster, go away + return (wrld.explosion_at(nx, ny) or + wrld.wall_at(nx, ny) or + wrld.monsters_at(nx, ny) or + wrld.exit_at(nx, ny)) + + def look_for_empty_cell(self, wrld): + # List of empty cells + cells = [] + # Go through neighboring cells + for dx in [-1, 0, 1]: + # Avoid out-of-bounds access + if ((self.x + dx >= 0) and (self.x + dx < wrld.width())): + for dy in [-1, 0, 1]: + # Avoid out-of-bounds access + if ((self.y + dy >= 0) and (self.y + dy < wrld.height())): + # Is this cell safe? + if(wrld.exit_at(self.x + dx, self.y + dy) or + wrld.empty_at(self.x + dx, self.y + dy)): + # Yes + cells.append((dx, dy)) + # All done + return cells def do(self, wrld): - # Your code here - pass + """Pick an action for the agent""" + # If a monster is in the neighborhood, run away + (found, dx, dy) = self.look_for_monster(wrld) #change this to A* star + if found and not self.must_change_direction(wrld): + self.move(dx, dy) + return + # If I'm idle or must change direction, change direction + if ((self.dx == 0 and self.dy == 0) or + self.must_change_direction(wrld)): + # Get list of safe moves + safe = self.look_for_empty_cell(wrld) + if not safe: #todo : Fix this to avoid death + # Accept death + self.move(0,0) + else: + # Pick a move at random + (dx, dy) = random.choice(safe) + self.move(dx, dy) + From a792e7697351c715ea86e8a17691191eb74f9254 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 27 Jan 2023 00:45:43 -0500 Subject: [PATCH 02/30] Basic world params printed --- teamNN/PriorityQueue.py | 42 ++++++++++++++++++++++++++++ teamNN/testcharacter.py | 61 ++++++++--------------------------------- 2 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 teamNN/PriorityQueue.py diff --git a/teamNN/PriorityQueue.py b/teamNN/PriorityQueue.py new file mode 100644 index 00000000..1e9c5a84 --- /dev/null +++ b/teamNN/PriorityQueue.py @@ -0,0 +1,42 @@ +import heapq + +class PriorityQueue: + + def __init__(self): + """ + Class constructor. + """ + self.elements = [] + + def empty(self): + """ + Returns True if the queue is empty, False otherwise. + """ + return len(self.elements) == 0 + + def put(self, element, priority): + """ + Puts an element in the queue. + :param element [any type] The element. + :param priority [int or float] The priority. + """ + for i in range(0, len(self.elements)): + it = self.elements[i] + if (it[1] == element): + if (it[0] > priority): + self.elements[i] = (priority, element) + heapq.heapify(self.elements) + return + heapq.heappush(self.elements, (priority, element)) + + def get(self): + """ + Returns the element with the top priority. + """ + return heapq.heappop(self.elements)[1] + + def get_queue(self): + """ + Returns the content of the queue as a list. + """ + return self.elements \ No newline at end of file diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 2fecbf0c..e8646d4d 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -4,42 +4,14 @@ # Import necessary stuff from entity import CharacterEntity from colorama import Fore, Back +from PriorityQueue import PriorityQueue class TestCharacter(CharacterEntity): - def evaluate(self, wrld): - print("Doing evaluation based on the current map") - #Read the current map (including the current position, walls and monster) - #First stage: If wall then -1, empty then 1, portal then 100 and if monster then -10 - - def a_star(self, wrld): - - def look_for_monster(self, wrld): - for dx in range(-self.rnge, self.rnge+1): - # Avoid out-of-bounds access - if ((self.x + dx >= 0) and (self.x + dx < wrld.width())): - for dy in range(-self.rnge, self.rnge+1): - # Avoid out-of-bounds access - if ((self.y + dy >= 0) and (self.y + dy < wrld.height())): - # Is a character at this position? - if (wrld.characters_at(self.x + dx, self.y + dy)): - return (True, dx, dy) - # Nothing found - return (False, 0, 0) - def must_change_direction(self, wrld): - # Get next desired position - (nx, ny) = self.nextpos() - # If next pos is out of bounds, must change direction - if ((nx < 0) or (nx >= wrld.width()) or - (ny < 0) or (ny >= wrld.height())): - return True - # If these cells are an explosion, a wall, or a monster, go away - return (wrld.explosion_at(nx, ny) or - wrld.wall_at(nx, ny) or - wrld.monsters_at(nx, ny) or - wrld.exit_at(nx, ny)) + def a_star(self, wrld): + pQueue = PriorityQueue() - def look_for_empty_cell(self, wrld): + def look_for_empty_cells(self, wrld): # List of empty cells cells = [] # Go through neighboring cells @@ -59,21 +31,12 @@ def look_for_empty_cell(self, wrld): def do(self, wrld): """Pick an action for the agent""" - # If a monster is in the neighborhood, run away - (found, dx, dy) = self.look_for_monster(wrld) #change this to A* star - if found and not self.must_change_direction(wrld): - self.move(dx, dy) - return - # If I'm idle or must change direction, change direction - if ((self.dx == 0 and self.dy == 0) or - self.must_change_direction(wrld)): - # Get list of safe moves - safe = self.look_for_empty_cell(wrld) - if not safe: #todo : Fix this to avoid death - # Accept death - self.move(0,0) - else: - # Pick a move at random - (dx, dy) = random.choice(safe) - self.move(dx, dy) + print("Doing something") + print("Character at", self.x, self.y) + print("Exit at", wrld.exitcell) + print("Explosions:", wrld.explosions) + print("Monsters:", wrld.monsters) + print("Empty cells:", self.look_for_empty_cells(wrld)) + + From f6c1293670210cdc816351aeff387ed613072496 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 27 Jan 2023 14:24:17 -0500 Subject: [PATCH 03/30] working A* --- teamNN/testcharacter.py | 99 ++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 20 deletions(-) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index e8646d4d..359e7a74 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -9,25 +9,77 @@ class TestCharacter(CharacterEntity): def a_star(self, wrld): - pQueue = PriorityQueue() - - def look_for_empty_cells(self, wrld): - # List of empty cells - cells = [] - # Go through neighboring cells - for dx in [-1, 0, 1]: - # Avoid out-of-bounds access - if ((self.x + dx >= 0) and (self.x + dx < wrld.width())): - for dy in [-1, 0, 1]: - # Avoid out-of-bounds access - if ((self.y + dy >= 0) and (self.y + dy < wrld.height())): - # Is this cell safe? - if(wrld.exit_at(self.x + dx, self.y + dy) or - wrld.empty_at(self.x + dx, self.y + dy)): - # Yes - cells.append((dx, dy)) - # All done - return cells + start = (self.x, self.y) + goal = wrld.exitcell + cost_so_far = {start: 0} + came_from = {start: None} + + frontier = PriorityQueue() + frontier.put(start, 0) + + while not frontier.empty(): + current = frontier.get() + # print() + # print("------ Iteration ------ ") + # print("current: ", current) + # print("goal: ", goal) + # print("cost_so_far: ", cost_so_far) + # print("came_from: ", came_from) + # print("frontier: ", frontier.get_queue()) + + if current == goal: + break + + for neighbor in self.eight_neighbors(wrld, current[0], current[1]): + new_cost = cost_so_far[current] + euclidean_dist(current, neighbor) + + #Update cost if lower + if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: + cost_so_far[neighbor] = new_cost + priority = new_cost + euclidean_dist(neighbor, goal) + frontier.put(neighbor, priority) + # print("Added ",neighbor, " to Frontiner") + came_from[neighbor] = current + + print("A* is done") + currPos = goal + finalPath = [] + finalPath.append(goal) + while currPos != start: + currPos = came_from[currPos] + finalPath.append(currPos) + + finalPath.reverse() + return finalPath + + def is_cell_walkable(self, wrld, x, y): + return wrld.exit_at(x, y) or wrld.empty_at(x, y) + + + def eight_neighbors(self, wrld, x, y): + """ + Returns the walkable 8-neighbors cells of (x,y) in the occupancy grid. + """ + return_list = [] + + if x != 0 and self.is_cell_walkable(wrld, x - 1, y): + return_list.append((x - 1, y)) + if x != wrld.width() - 1 and self.is_cell_walkable(wrld, x + 1, y): + return_list.append((x + 1, y)) + if y != 0 and self.is_cell_walkable(wrld, x, y - 1): + return_list.append((x, y - 1)) + if y != wrld.height() - 1 and self.is_cell_walkable(wrld, x, y + 1): + return_list.append((x, y + 1)) + if x != 0 and y != 0 and self.is_cell_walkable(wrld, x - 1, y - 1): + return_list.append((x - 1, y - 1)) + if x != wrld.width() - 1 and y != 0 and self.is_cell_walkable(wrld, x + 1, y - 1): + return_list.append((x + 1, y - 1)) + if y != wrld.height() - 1 and x != 0 and self.is_cell_walkable(wrld, x - 1, y + 1): + return_list.append((x - 1, y + 1)) + if x != wrld.width() - 1 and y != wrld.height() - 1 and self.is_cell_walkable(wrld, x + 1, y + 1): + return_list.append((x + 1, y + 1)) + + return return_list def do(self, wrld): """Pick an action for the agent""" @@ -36,7 +88,14 @@ def do(self, wrld): print("Exit at", wrld.exitcell) print("Explosions:", wrld.explosions) print("Monsters:", wrld.monsters) - print("Empty cells:", self.look_for_empty_cells(wrld)) + # print("Empty cells (None, None):", self.eight_neighbors(wrld)) + # print("Empty cells (0,0):", self.eight_neighbors(wrld, 0, 0)) + # print("Empty cells (1,0):", self.eight_neighbors(wrld, 1, 0)) + # print("Empty cells (1,0):", self.eight_neighbors(wrld, 2, 0)) + # print("Empty cells (1,1):", self.eight_neighbors(wrld, 1, 1)) + print("A* path:", self.a_star(wrld)) +def euclidean_dist(point_one, point_two): + return ((point_one[0] - point_two[0]) ** 2 + (point_one[1] - point_two[1]) ** 2) ** 0.5 \ No newline at end of file From 8ea79cb09191d667978c2a204dd522db972f1c91 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 27 Jan 2023 14:38:05 -0500 Subject: [PATCH 04/30] A* character movement done! --- teamNN/project1/variant1.py | 4 +-- teamNN/testcharacter.py | 59 +++++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index 3dc1bc40..1dcb9e8f 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -35,7 +35,7 @@ # Run! # Use this if you want to press ENTER to continue at each step -g.go(0) +# g.go(0) # Use this if you want to proceed automatically -# g.go(1) +g.go(1000) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 359e7a74..b5631239 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -7,41 +7,39 @@ from PriorityQueue import PriorityQueue class TestCharacter(CharacterEntity): + firstTime = True + a_star_path = [] def a_star(self, wrld): - start = (self.x, self.y) - goal = wrld.exitcell - cost_so_far = {start: 0} - came_from = {start: None} + start = (self.x, self.y) #Start at current position + goal = wrld.exitcell #Goal is exit cell + cost_so_far = {start: 0} #Dictionary of costs to get to each cell + came_from = {start: None} #Dictionary of where each cell came from - frontier = PriorityQueue() + frontier = PriorityQueue() #Priority queue of cells to visit frontier.put(start, 0) while not frontier.empty(): current = frontier.get() - # print() - # print("------ Iteration ------ ") - # print("current: ", current) - # print("goal: ", goal) - # print("cost_so_far: ", cost_so_far) - # print("came_from: ", came_from) - # print("frontier: ", frontier.get_queue()) if current == goal: break + #Check all walkable neighbors of current cell for neighbor in self.eight_neighbors(wrld, current[0], current[1]): + #Calculate cost to get to neighbor - 1 or 1.4 new_cost = cost_so_far[current] + euclidean_dist(current, neighbor) - #Update cost if lower + #If neighbor has no path or new path is better, update path if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: cost_so_far[neighbor] = new_cost priority = new_cost + euclidean_dist(neighbor, goal) frontier.put(neighbor, priority) - # print("Added ",neighbor, " to Frontiner") came_from[neighbor] = current print("A* is done") + + #Reconstruct path using came_from dictionary currPos = goal finalPath = [] finalPath.append(goal) @@ -58,7 +56,7 @@ def is_cell_walkable(self, wrld, x, y): def eight_neighbors(self, wrld, x, y): """ - Returns the walkable 8-neighbors cells of (x,y) in the occupancy grid. + Returns the walkable 8-neighbors cells of (x,y) in wrld """ return_list = [] @@ -83,17 +81,26 @@ def eight_neighbors(self, wrld, x, y): def do(self, wrld): """Pick an action for the agent""" - print("Doing something") - print("Character at", self.x, self.y) - print("Exit at", wrld.exitcell) - print("Explosions:", wrld.explosions) - print("Monsters:", wrld.monsters) - # print("Empty cells (None, None):", self.eight_neighbors(wrld)) - # print("Empty cells (0,0):", self.eight_neighbors(wrld, 0, 0)) - # print("Empty cells (1,0):", self.eight_neighbors(wrld, 1, 0)) - # print("Empty cells (1,0):", self.eight_neighbors(wrld, 2, 0)) - # print("Empty cells (1,1):", self.eight_neighbors(wrld, 1, 1)) - print("A* path:", self.a_star(wrld)) + + + if self.firstTime: + print("Doing something") + print("Character at", self.x, self.y) + print("Exit at", wrld.exitcell) + print("Explosions:", wrld.explosions) + print("Monsters:", wrld.monsters) + self.a_star_path = self.a_star(wrld) + print("A* path:", self.a_star_path) + for point in self.a_star_path: + #Mark path on world + self.set_cell_color(point[0],point[1], Fore.RED + Back.GREEN) + self.firstTime = False + else: + nextCell = self.a_star_path.pop(0) + # print("Next cell:", nextCell) + self.move(nextCell[0] - self.x, nextCell[1] - self.y) + + From 4b4a00d42647f9a4f851e529842dcbc07c76f115 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 27 Jan 2023 14:39:28 -0500 Subject: [PATCH 05/30] cleanup --- teamNN/testcharacter.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index b5631239..692187a5 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -80,15 +80,12 @@ def eight_neighbors(self, wrld, x, y): return return_list def do(self, wrld): - """Pick an action for the agent""" - - if self.firstTime: - print("Doing something") print("Character at", self.x, self.y) print("Exit at", wrld.exitcell) print("Explosions:", wrld.explosions) print("Monsters:", wrld.monsters) + self.a_star_path = self.a_star(wrld) print("A* path:", self.a_star_path) for point in self.a_star_path: @@ -97,11 +94,7 @@ def do(self, wrld): self.firstTime = False else: nextCell = self.a_star_path.pop(0) - # print("Next cell:", nextCell) self.move(nextCell[0] - self.x, nextCell[1] - self.y) - - - def euclidean_dist(point_one, point_two): From ef4acd66cec6174204f80a2a03160a5ea965b479 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 27 Jan 2023 16:08:26 -0500 Subject: [PATCH 06/30] testing window placement --- Bomberman/game.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Bomberman/game.py b/Bomberman/game.py index 9370d941..98c12aa3 100644 --- a/Bomberman/game.py +++ b/Bomberman/game.py @@ -4,6 +4,11 @@ import pygame import math +x = 1350 +y = 0 +import os +os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y) + class Game: """Game class""" From 04b60d905395de82e3e3ef1474d85eb6f87682cb Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Mon, 30 Jan 2023 13:16:59 -0500 Subject: [PATCH 07/30] helper funcs, dist to monster, dist to goal --- Bomberman/game.py | 2 +- teamNN/project1/variant1.py | 4 +-- teamNN/testcharacter.py | 52 ++++++++++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Bomberman/game.py b/Bomberman/game.py index 98c12aa3..6442b488 100644 --- a/Bomberman/game.py +++ b/Bomberman/game.py @@ -4,7 +4,7 @@ import pygame import math -x = 1350 +x = 1330 y = 0 import os os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y) diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index 1dcb9e8f..0eeb7e1a 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -35,7 +35,7 @@ # Run! # Use this if you want to press ENTER to continue at each step -# g.go(0) +g.go(0) # Use this if you want to proceed automatically -g.go(1000) +# g.go(1000) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 692187a5..3fe9be3f 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -10,9 +10,12 @@ class TestCharacter(CharacterEntity): firstTime = True a_star_path = [] - def a_star(self, wrld): - start = (self.x, self.y) #Start at current position - goal = wrld.exitcell #Goal is exit cell + def a_star(self, wrld, goal=None, start=None): + if start is None: + start = (self.x, self.y) #Start at current position + if goal is None: + goal = wrld.exitcell #Goal is exit cell + cost_so_far = {start: 0} #Dictionary of costs to get to each cell came_from = {start: None} #Dictionary of where each cell came from @@ -37,8 +40,6 @@ def a_star(self, wrld): frontier.put(neighbor, priority) came_from[neighbor] = current - print("A* is done") - #Reconstruct path using came_from dictionary currPos = goal finalPath = [] @@ -51,8 +52,7 @@ def a_star(self, wrld): return finalPath def is_cell_walkable(self, wrld, x, y): - return wrld.exit_at(x, y) or wrld.empty_at(x, y) - + return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) def eight_neighbors(self, wrld, x, y): """ @@ -79,15 +79,49 @@ def eight_neighbors(self, wrld, x, y): return return_list + def manhattan_distance_to_exit(self, wrld): + return abs(self.x - wrld.exitcell[0]) + abs(self.y - wrld.exitcell[1]) + + def euclidean_distance_to_exit(self, wrld): + return ((self.x - wrld.exitcell[0])**2 + (self.y - wrld.exitcell[1])**2)**0.5 + + def a_star_distance_to_exit(self, wrld): + return len(self.a_star(wrld)) + + def manhattan_distance_to_monster(self, wrld): + if len(wrld.monsters) == 0: + return 0 + else: + return min([abs(self.x - monster[1][0].x) + abs(self.y - monster[1][0].y) for monster in wrld.monsters.items()]) + + def euclidean_distance_to_monster(self, wrld): + if len(wrld.monsters) == 0: + return 0 + else: + return min([((self.x - monster[1][0].x)**2 + (self.y - monster[1][0].y)**2)**0.5 for monster in wrld.monsters.items()]) + def a_star_distance_to_monster(self, wrld): + + if len(wrld.monsters) == 0: + return 0 + else: + return min([len(self.a_star(wrld, (monster[1][0].x,monster[1][0].y))) for monster in wrld.monsters.items()]) + + def do(self, wrld): if self.firstTime: print("Character at", self.x, self.y) print("Exit at", wrld.exitcell) print("Explosions:", wrld.explosions) print("Monsters:", wrld.monsters) - + print("Euclidean distance to exit:", self.euclidean_distance_to_exit(wrld)) + print("Manhattan distance to exit:", self.manhattan_distance_to_exit(wrld)) + print("A* distance to exit:", self.a_star_distance_to_exit(wrld)) + print("Euclidean distance to monster:", self.euclidean_distance_to_monster(wrld)) + print("Manhattan distance to monster:", self.manhattan_distance_to_monster(wrld)) + print("A* distance to monster:", self.a_star_distance_to_monster(wrld)) self.a_star_path = self.a_star(wrld) - print("A* path:", self.a_star_path) + print("A* path to goal:", self.a_star_path) + for point in self.a_star_path: #Mark path on world self.set_cell_color(point[0],point[1], Fore.RED + Back.GREEN) From 424709637b932379da11ec3ed3012de83c5f17e7 Mon Sep 17 00:00:00 2001 From: ndenda Date: Mon, 30 Jan 2023 21:19:33 -0500 Subject: [PATCH 08/30] --- teamNN/project1/minimax.py | 105 +++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 teamNN/project1/minimax.py diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py new file mode 100644 index 00000000..90d88c97 --- /dev/null +++ b/teamNN/project1/minimax.py @@ -0,0 +1,105 @@ +# This is necessary to find the main code +import sys +sys.path.insert(0, '../bomberman') +# Import necessary stuff +from testcharacter import CharacterEntity +from colorama import Fore, Back +from PriorityQueue import PriorityQueue +from sys import maxsize + +class Node(object): + + + + firstTime = True + + def __init__(self,depth,player,distance,value,x,y,wrld): + self.depth = depth + self.player = player + self.distance = distance + self.value = value + self.x = x + self.y = y + self.wrld = wrld + self.children = [] + self.makechildren() + + def makechildren(self): + if self.depth >= 0: + neighbordlist = self.eight_neighbors(self.x,self.y) + for neighbord in neighbordlist : + if self.is_cell_walkable(neighbord): + if self.look_for_monster(self.wrld): + self.value = -100000000 + else: + self.value = 10000 - self.euclidean_dist(self.wrld.exitcell,neighbord) - self.depth + self.distance = self.euclidean_dist(self.wrld.exitcell,neighbord) + else: + self.value = 0 + self.children.append(Node (self.depth-1,-self.player,self.distance,self.value,neighbord.x,neighbord.y,self.wrld)) + + + def look_for_monster(self, wrld): + for dx in range(-self.rnge, self.rnge+1): + # Avoid out-of-bounds access + if ((self.x + dx >= 0) and (self.x + dx < wrld.width())): + for dy in range(-self.rnge, self.rnge+1): + # Avoid out-of-bounds access + if ((self.y + dy >= 0) and (self.y + dy < wrld.height())): + # Is a character at this position? + if (wrld.characters_at(self.x + dx, self.y + dy)): + return (True) + # Nothing found + return (False) + + def euclidean_dist(point_one, point_two): + return ((point_one[0] - point_two[0]) ** 2 + (point_one[1] - point_two[1]) ** 2) ** 0.5 + + def eight_neighbors(self, wrld, x, y): + """ + Returns the walkable 8-neighbors cells of (x,y) in wrld + """ + return_list = [] + + if x != 0 and self.is_cell_walkable(wrld, x - 1, y): + return_list.append((x - 1, y)) + if x != wrld.width() - 1 and self.is_cell_walkable(wrld, x + 1, y): + return_list.append((x + 1, y)) + if y != 0 and self.is_cell_walkable(wrld, x, y - 1): + return_list.append((x, y - 1)) + if y != wrld.height() - 1 and self.is_cell_walkable(wrld, x, y + 1): + return_list.append((x, y + 1)) + if x != 0 and y != 0 and self.is_cell_walkable(wrld, x - 1, y - 1): + return_list.append((x - 1, y - 1)) + if x != wrld.width() - 1 and y != 0 and self.is_cell_walkable(wrld, x + 1, y - 1): + return_list.append((x + 1, y - 1)) + if y != wrld.height() - 1 and x != 0 and self.is_cell_walkable(wrld, x - 1, y + 1): + return_list.append((x - 1, y + 1)) + if x != wrld.width() - 1 and y != wrld.height() - 1 and self.is_cell_walkable(wrld, x + 1, y + 1): + return_list.append((x + 1, y + 1)) + + return return_list + + def is_cell_walkable(self, wrld, x, y): + return wrld.exit_at(x, y) or wrld.empty_at(x, y) + + + + def minimax(node,depth,player): + if (depth == 0) or (node.value == -100000000): #or game wine, game lose + return node.value + bestvalue = maxsize * -player + + for child in node.children: + child = node.children[child] + value = minimax(child,depth -1, -player) + if(abs(maxsize * player - value)< abs(maxsize*player - bestvalue)): + bestvalue = value + return bestvalue + + + + + + + From 075f96b5aa5596b071fb5da8e978a6f11694cca1 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 2 Feb 2023 14:42:55 -0500 Subject: [PATCH 09/30] Refactor to move helper functions to utility use: from teamNN.utility import * --- teamNN/testcharacter.py | 98 ++++++++------------------------------ teamNN/utility.py | 101 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 78 deletions(-) create mode 100644 teamNN/utility.py diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 3fe9be3f..f5bc05f2 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -1,25 +1,29 @@ # This is necessary to find the main code import sys + +from teamNN.utility import * + sys.path.insert(0, '../bomberman') # Import necessary stuff from entity import CharacterEntity from colorama import Fore, Back from PriorityQueue import PriorityQueue + class TestCharacter(CharacterEntity): firstTime = True a_star_path = [] def a_star(self, wrld, goal=None, start=None): if start is None: - start = (self.x, self.y) #Start at current position + start = (self.x, self.y) # Start at current position if goal is None: - goal = wrld.exitcell #Goal is exit cell + goal = wrld.exitcell # Goal is exit cell - cost_so_far = {start: 0} #Dictionary of costs to get to each cell - came_from = {start: None} #Dictionary of where each cell came from + cost_so_far = {start: 0} # Dictionary of costs to get to each cell + came_from = {start: None} # Dictionary of where each cell came from - frontier = PriorityQueue() #Priority queue of cells to visit + frontier = PriorityQueue() # Priority queue of cells to visit frontier.put(start, 0) while not frontier.empty(): @@ -28,19 +32,19 @@ def a_star(self, wrld, goal=None, start=None): if current == goal: break - #Check all walkable neighbors of current cell - for neighbor in self.eight_neighbors(wrld, current[0], current[1]): - #Calculate cost to get to neighbor - 1 or 1.4 + # Check all walkable neighbors of current cell + for neighbor in eight_neighbors(wrld, current[0], current[1]): + # Calculate cost to get to neighbor - 1 or 1.4 new_cost = cost_so_far[current] + euclidean_dist(current, neighbor) - #If neighbor has no path or new path is better, update path + # If neighbor has no path or new path is better, update path if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: cost_so_far[neighbor] = new_cost priority = new_cost + euclidean_dist(neighbor, goal) frontier.put(neighbor, priority) came_from[neighbor] = current - #Reconstruct path using came_from dictionary + # Reconstruct path using came_from dictionary currPos = goal finalPath = [] finalPath.append(goal) @@ -51,85 +55,23 @@ def a_star(self, wrld, goal=None, start=None): finalPath.reverse() return finalPath - def is_cell_walkable(self, wrld, x, y): - return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) - - def eight_neighbors(self, wrld, x, y): - """ - Returns the walkable 8-neighbors cells of (x,y) in wrld - """ - return_list = [] - - if x != 0 and self.is_cell_walkable(wrld, x - 1, y): - return_list.append((x - 1, y)) - if x != wrld.width() - 1 and self.is_cell_walkable(wrld, x + 1, y): - return_list.append((x + 1, y)) - if y != 0 and self.is_cell_walkable(wrld, x, y - 1): - return_list.append((x, y - 1)) - if y != wrld.height() - 1 and self.is_cell_walkable(wrld, x, y + 1): - return_list.append((x, y + 1)) - if x != 0 and y != 0 and self.is_cell_walkable(wrld, x - 1, y - 1): - return_list.append((x - 1, y - 1)) - if x != wrld.width() - 1 and y != 0 and self.is_cell_walkable(wrld, x + 1, y - 1): - return_list.append((x + 1, y - 1)) - if y != wrld.height() - 1 and x != 0 and self.is_cell_walkable(wrld, x - 1, y + 1): - return_list.append((x - 1, y + 1)) - if x != wrld.width() - 1 and y != wrld.height() - 1 and self.is_cell_walkable(wrld, x + 1, y + 1): - return_list.append((x + 1, y + 1)) - - return return_list - - def manhattan_distance_to_exit(self, wrld): - return abs(self.x - wrld.exitcell[0]) + abs(self.y - wrld.exitcell[1]) - - def euclidean_distance_to_exit(self, wrld): - return ((self.x - wrld.exitcell[0])**2 + (self.y - wrld.exitcell[1])**2)**0.5 - - def a_star_distance_to_exit(self, wrld): - return len(self.a_star(wrld)) - - def manhattan_distance_to_monster(self, wrld): - if len(wrld.monsters) == 0: - return 0 - else: - return min([abs(self.x - monster[1][0].x) + abs(self.y - monster[1][0].y) for monster in wrld.monsters.items()]) - - def euclidean_distance_to_monster(self, wrld): - if len(wrld.monsters) == 0: - return 0 - else: - return min([((self.x - monster[1][0].x)**2 + (self.y - monster[1][0].y)**2)**0.5 for monster in wrld.monsters.items()]) - def a_star_distance_to_monster(self, wrld): - - if len(wrld.monsters) == 0: - return 0 - else: - return min([len(self.a_star(wrld, (monster[1][0].x,monster[1][0].y))) for monster in wrld.monsters.items()]) - - def do(self, wrld): if self.firstTime: print("Character at", self.x, self.y) print("Exit at", wrld.exitcell) print("Explosions:", wrld.explosions) print("Monsters:", wrld.monsters) - print("Euclidean distance to exit:", self.euclidean_distance_to_exit(wrld)) - print("Manhattan distance to exit:", self.manhattan_distance_to_exit(wrld)) - print("A* distance to exit:", self.a_star_distance_to_exit(wrld)) - print("Euclidean distance to monster:", self.euclidean_distance_to_monster(wrld)) - print("Manhattan distance to monster:", self.manhattan_distance_to_monster(wrld)) - print("A* distance to monster:", self.a_star_distance_to_monster(wrld)) + print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) + print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) + print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) + print("Manhattan distance to monster:", manhattan_distance_to_monster(wrld)) self.a_star_path = self.a_star(wrld) print("A* path to goal:", self.a_star_path) for point in self.a_star_path: - #Mark path on world - self.set_cell_color(point[0],point[1], Fore.RED + Back.GREEN) + # Mark path on world + self.set_cell_color(point[0], point[1], Fore.RED + Back.GREEN) self.firstTime = False else: nextCell = self.a_star_path.pop(0) self.move(nextCell[0] - self.x, nextCell[1] - self.y) - - -def euclidean_dist(point_one, point_two): - return ((point_one[0] - point_two[0]) ** 2 + (point_one[1] - point_two[1]) ** 2) ** 0.5 \ No newline at end of file diff --git a/teamNN/utility.py b/teamNN/utility.py new file mode 100644 index 00000000..a11aa258 --- /dev/null +++ b/teamNN/utility.py @@ -0,0 +1,101 @@ +"""Utility functions for the teamNN package.""" + + +def euclidean_dist(point_one, point_two): + """Returns the euclidean distance between two points. + point_one: (x, y) tuple + point_two: (x, y) tuple + returns: float""" + return ((point_one[0] - point_two[0]) ** 2 + (point_one[1] - point_two[1]) ** 2) ** 0.5 + + +def is_cell_walkable(wrld, x, y): + """Returns True if the cell at (x, y) is walkable. + wrld: World object + x: int + y: int + returns: bool""" + return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) + + +def eight_neighbors(wrld, x, y): + """ + Returns the walkable 8-neighbors cells of (x,y) in wrld + wrld: World object + x: int + y: int + returns: list of (x, y) tuples + """ + return_list = [] + + if x != 0 and is_cell_walkable(wrld, x - 1, y): + return_list.append((x - 1, y)) + if x != wrld.width() - 1 and is_cell_walkable(wrld, x + 1, y): + return_list.append((x + 1, y)) + if y != 0 and is_cell_walkable(wrld, x, y - 1): + return_list.append((x, y - 1)) + if y != wrld.height() - 1 and is_cell_walkable(wrld, x, y + 1): + return_list.append((x, y + 1)) + if x != 0 and y != 0 and is_cell_walkable(wrld, x - 1, y - 1): + return_list.append((x - 1, y - 1)) + if x != wrld.width() - 1 and y != 0 and is_cell_walkable(wrld, x + 1, y - 1): + return_list.append((x + 1, y - 1)) + if y != wrld.height() - 1 and x != 0 and is_cell_walkable(wrld, x - 1, y + 1): + return_list.append((x - 1, y + 1)) + if x != wrld.width() - 1 and y != wrld.height() - 1 and is_cell_walkable(wrld, x + 1, y + 1): + return_list.append((x + 1, y + 1)) + + return return_list + + +def character_location(wrld): + """Returns the location of the character in wrld. + wrld: World object + returns: (x, y) tuple""" + if len(wrld.characters) == 0: + Exception("No character in world") + return wrld.characters[0][0].x, wrld.characters[0][0].y + + +def manhattan_distance_to_exit(wrld): + """Returns the manhattan distance to the exit. + wrld: World object + returns: float""" + + self = character_location(wrld) + return abs(self[0] - wrld.exitcell[0]) + abs(self[1] - wrld.exitcell[1]) + + +def euclidean_distance_to_exit(wrld): + """Returns the euclidean distance to the exit. + wrld: World object + returns: float""" + + self = character_location(wrld) + return ((self[0] - wrld.exitcell[0]) ** 2 + (self[1] - wrld.exitcell[1]) ** 2) ** 0.5 + + +def manhattan_distance_to_monster(wrld): + """Returns the manhattan distance to the closest monster. + wrld: World object + returns: float""" + + self = character_location(wrld) + if len(wrld.monsters) == 0: + return 999 + else: + return min( + [abs(self[0] - monster[1][0].x) + abs(self[1] - monster[1][0].y) for monster in wrld.monsters.items()]) + + +def euclidean_distance_to_monster(wrld): + """Returns the euclidean distance to the closest monster. + wrld: World object + returns: float""" + + self = character_location(wrld) + if len(wrld.monsters) == 0: + return 999 + else: + return min([((self[0] - monster[1][0].x) ** 2 + (self[1] - monster[1][0].y) ** 2) ** 0.5 for monster in + wrld.monsters.items()]) From fa529f2ebef29c445baa06a802e4c95d8a52e1e5 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 2 Feb 2023 14:50:56 -0500 Subject: [PATCH 10/30] monster location --- teamNN/project1/minimax.py | 79 ++++++++------------------------------ teamNN/testcharacter.py | 1 + teamNN/utility.py | 9 +++++ 3 files changed, 26 insertions(+), 63 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 90d88c97..3ca15abe 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../bomberman') # Import necessary stuff from testcharacter import CharacterEntity @@ -7,13 +8,11 @@ from PriorityQueue import PriorityQueue from sys import maxsize -class Node(object): - - - firstTime = True +class Node(object): + firstTime = True - def __init__(self,depth,player,distance,value,x,y,wrld): + def __init__(self, depth, player, distance, value, x, y, wrld): self.depth = depth self.player = player self.distance = distance @@ -26,74 +25,28 @@ def __init__(self,depth,player,distance,value,x,y,wrld): def makechildren(self): if self.depth >= 0: - neighbordlist = self.eight_neighbors(self.x,self.y) - for neighbord in neighbordlist : - if self.is_cell_walkable(neighbord): + neighbordlist = self.eight_neighbors(self.x, self.y) + for neighbord in neighbordlist: + if self.is_cell_walkable(neighbord): if self.look_for_monster(self.wrld): self.value = -100000000 - else: - self.value = 10000 - self.euclidean_dist(self.wrld.exitcell,neighbord) - self.depth - self.distance = self.euclidean_dist(self.wrld.exitcell,neighbord) + else: + self.value = 10000 - self.euclidean_dist(self.wrld.exitcell, neighbord) - self.depth + self.distance = self.euclidean_dist(self.wrld.exitcell, neighbord) else: self.value = 0 - self.children.append(Node (self.depth-1,-self.player,self.distance,self.value,neighbord.x,neighbord.y,self.wrld)) - - - def look_for_monster(self, wrld): - for dx in range(-self.rnge, self.rnge+1): - # Avoid out-of-bounds access - if ((self.x + dx >= 0) and (self.x + dx < wrld.width())): - for dy in range(-self.rnge, self.rnge+1): - # Avoid out-of-bounds access - if ((self.y + dy >= 0) and (self.y + dy < wrld.height())): - # Is a character at this position? - if (wrld.characters_at(self.x + dx, self.y + dy)): - return (True) - # Nothing found - return (False) - - def euclidean_dist(point_one, point_two): - return ((point_one[0] - point_two[0]) ** 2 + (point_one[1] - point_two[1]) ** 2) ** 0.5 - - def eight_neighbors(self, wrld, x, y): - """ - Returns the walkable 8-neighbors cells of (x,y) in wrld - """ - return_list = [] - - if x != 0 and self.is_cell_walkable(wrld, x - 1, y): - return_list.append((x - 1, y)) - if x != wrld.width() - 1 and self.is_cell_walkable(wrld, x + 1, y): - return_list.append((x + 1, y)) - if y != 0 and self.is_cell_walkable(wrld, x, y - 1): - return_list.append((x, y - 1)) - if y != wrld.height() - 1 and self.is_cell_walkable(wrld, x, y + 1): - return_list.append((x, y + 1)) - if x != 0 and y != 0 and self.is_cell_walkable(wrld, x - 1, y - 1): - return_list.append((x - 1, y - 1)) - if x != wrld.width() - 1 and y != 0 and self.is_cell_walkable(wrld, x + 1, y - 1): - return_list.append((x + 1, y - 1)) - if y != wrld.height() - 1 and x != 0 and self.is_cell_walkable(wrld, x - 1, y + 1): - return_list.append((x - 1, y + 1)) - if x != wrld.width() - 1 and y != wrld.height() - 1 and self.is_cell_walkable(wrld, x + 1, y + 1): - return_list.append((x + 1, y + 1)) - - return return_list - - def is_cell_walkable(self, wrld, x, y): - return wrld.exit_at(x, y) or wrld.empty_at(x, y) - - + self.children.append( + Node(self.depth - 1, -self.player, self.distance, self.value, neighbord.x, neighbord.y, self.wrld)) - def minimax(node,depth,player): - if (depth == 0) or (node.value == -100000000): #or game wine, game lose + def minimax(node, depth, player): + if (depth == 0) or (node.value == -100000000): # or game wine, game lose return node.value bestvalue = maxsize * -player for child in node.children: child = node.children[child] - value = minimax(child,depth -1, -player) - if(abs(maxsize * player - value)< abs(maxsize*player - bestvalue)): + value = minimax(child, depth - 1, -player) + if (abs(maxsize * player - value) < abs(maxsize * player - bestvalue)): bestvalue = value return bestvalue diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index f5bc05f2..f26d784d 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -61,6 +61,7 @@ def do(self, wrld): print("Exit at", wrld.exitcell) print("Explosions:", wrld.explosions) print("Monsters:", wrld.monsters) + print("Monster Location", monster_location(wrld)) print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) diff --git a/teamNN/utility.py b/teamNN/utility.py index a11aa258..8ab8f2d0 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -57,6 +57,15 @@ def character_location(wrld): return wrld.characters[0][0].x, wrld.characters[0][0].y +def monster_location(wrld): + """Returns the location of the nearest monster in wrld. + wrld: World object + returns: (x, y) tuple""" + if len(wrld.monsters) == 0: + Exception("No monster in world") + return next(iter(wrld.monsters.items()))[1][0].x, next(iter(wrld.monsters.items()))[1][0].y + + def manhattan_distance_to_exit(wrld): """Returns the manhattan distance to the exit. wrld: World object From 930127ceb1b9f48c0bb19b626c0c83accf255395 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 2 Feb 2023 15:21:43 -0500 Subject: [PATCH 11/30] New docs for utility --- teamNN/utility.py | 85 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/teamNN/utility.py b/teamNN/utility.py index 8ab8f2d0..9bedde41 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -1,4 +1,20 @@ -"""Utility functions for the teamNN package.""" +""" +Utility functions for the teamNN package. +Import to any file using: from teamNN.utility import * + +Includes: +float euclidean_dist(point_one:tuple, point_two:tuple) +bool is_cell_walkable(wrld:World, x:int, y:int) +tuple[] eight_neighbors(wrld:World, x:int, y:int) +tuple character_location(wrld:World) //Returns First Character +tuple monster_location(wrld:World) //Returns First Monster +int manhattan_distance_to_exit(wrld:World) +int manhattan_distance_to_monster(wrld:World) //Returns Nearest Monster +int euclidean_distance_to_exit(wrld:World) +int euclidean_distance_to_monster(wrld:World) //Returns Nearest Monster + +""" +from teamNN.PriorityQueue import PriorityQueue def euclidean_dist(point_one, point_two): @@ -48,6 +64,48 @@ def eight_neighbors(wrld, x, y): return return_list +def a_star(wrld, goal=None, start=None): + if start is None: + start = character_location(wrld) # Start at current position + if goal is None: + goal = wrld.exitcell # Goal is exit cell + + cost_so_far = {start: 0} # Dictionary of costs to get to each cell + came_from = {start: None} # Dictionary of where each cell came from + + frontier = PriorityQueue() # Priority queue of cells to visit + frontier.put(start, 0) + + while not frontier.empty(): + current = frontier.get() + + if current == goal: + break + + # Check all walkable neighbors of current cell + for neighbor in eight_neighbors(wrld, current[0], current[1]): + # Calculate cost to get to neighbor - 1 or 1.4 + new_cost = cost_so_far[current] + euclidean_dist(current, neighbor) + + # If neighbor has no path or new path is better, update path + if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: + cost_so_far[neighbor] = new_cost + priority = new_cost + euclidean_dist(neighbor, goal) + frontier.put(neighbor, priority) + came_from[neighbor] = current + + # Reconstruct path using came_from dictionary + currPos = goal + finalPath = [] + finalPath.append(goal) + while currPos != start: + currPos = came_from[currPos] + finalPath.append(currPos) + + finalPath.reverse() + return finalPath + + def character_location(wrld): """Returns the location of the character in wrld. wrld: World object @@ -57,8 +115,15 @@ def character_location(wrld): return wrld.characters[0][0].x, wrld.characters[0][0].y +def exit_location(wrld): + """Returns the location of the exit in wrld. + wrld: World object + returns: (x, y) tuple""" + return wrld.exitcell[0], wrld.exitcell[1] + + def monster_location(wrld): - """Returns the location of the nearest monster in wrld. + """Returns the location of the first monster in wrld. wrld: World object returns: (x, y) tuple""" if len(wrld.monsters) == 0: @@ -108,3 +173,19 @@ def euclidean_distance_to_monster(wrld): else: return min([((self[0] - monster[1][0].x) ** 2 + (self[1] - monster[1][0].y) ** 2) ** 0.5 for monster in wrld.monsters.items()]) + + +def a_star_distance_to_exit(wrld): + """Returns the a* distance to the exit. + wrld: World object + returns: float""" + + return len(a_star(wrld, goal=wrld.exitcell)) + + +def a_star_distance_to_monster(wrld): + """Returns the a* distance to the closest monster. + wrld: World object + returns: float""" + + return len(a_star(wrld, goal=wrld.exitcell, start=monster_location(wrld))) From c7592e5069e6f9064455c75e1e424db44a640d53 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 2 Feb 2023 15:30:01 -0500 Subject: [PATCH 12/30] Added optional start param for distance_to functions --- teamNN/utility.py | 51 +++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/teamNN/utility.py b/teamNN/utility.py index 9bedde41..720a50a4 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -131,61 +131,78 @@ def monster_location(wrld): return next(iter(wrld.monsters.items()))[1][0].x, next(iter(wrld.monsters.items()))[1][0].y -def manhattan_distance_to_exit(wrld): +def manhattan_distance_to_exit(wrld, start=None): """Returns the manhattan distance to the exit. wrld: World object + start (optional): (x, y) tuple to start from, defaults to character location returns: float""" + if start is None: + start = character_location(wrld) - self = character_location(wrld) - return abs(self[0] - wrld.exitcell[0]) + abs(self[1] - wrld.exitcell[1]) + return abs(start[0] - wrld.exitcell[0]) + abs(start[1] - wrld.exitcell[1]) -def euclidean_distance_to_exit(wrld): +def euclidean_distance_to_exit(wrld, start=None): """Returns the euclidean distance to the exit. wrld: World object + start (optional): (x, y) tuple to start from, defaults to character location returns: float""" + if start is None: + start = character_location(wrld) - self = character_location(wrld) - return ((self[0] - wrld.exitcell[0]) ** 2 + (self[1] - wrld.exitcell[1]) ** 2) ** 0.5 + return ((start[0] - wrld.exitcell[0]) ** 2 + (start[1] - wrld.exitcell[1]) ** 2) ** 0.5 -def manhattan_distance_to_monster(wrld): +def manhattan_distance_to_monster(wrld, start=None): """Returns the manhattan distance to the closest monster. wrld: World object + start (optional): (x, y) tuple to start from, defaults to character location returns: float""" - self = character_location(wrld) + if start is None: + start = character_location(wrld) + if len(wrld.monsters) == 0: return 999 else: return min( - [abs(self[0] - monster[1][0].x) + abs(self[1] - monster[1][0].y) for monster in wrld.monsters.items()]) + [abs(start[0] - monster[1][0].x) + abs(start[1] - monster[1][0].y) for monster in wrld.monsters.items()]) -def euclidean_distance_to_monster(wrld): +def euclidean_distance_to_monster(wrld, start=None): """Returns the euclidean distance to the closest monster. wrld: World object + start (optional): (x, y) tuple to start from, defaults to character location returns: float""" - self = character_location(wrld) + if start is None: + start = character_location(wrld) + if len(wrld.monsters) == 0: return 999 else: - return min([((self[0] - monster[1][0].x) ** 2 + (self[1] - monster[1][0].y) ** 2) ** 0.5 for monster in + return min([((start[0] - monster[1][0].x) ** 2 + (start[1] - monster[1][0].y) ** 2) ** 0.5 for monster in wrld.monsters.items()]) -def a_star_distance_to_exit(wrld): +def a_star_distance_to_exit(wrld, start=None): """Returns the a* distance to the exit. wrld: World object + start (optional): (x, y) tuple to start from, defaults to character location returns: float""" - - return len(a_star(wrld, goal=wrld.exitcell)) + if start is None: + return len(a_star(wrld, goal=wrld.exitcell)) + else: + return len(a_star(wrld, goal=wrld.exitcell, start=start)) -def a_star_distance_to_monster(wrld): +def a_star_distance_to_monster(wrld, start=None): """Returns the a* distance to the closest monster. wrld: World object + start (optional): (x, y) tuple to start from, defaults to character location returns: float""" - return len(a_star(wrld, goal=wrld.exitcell, start=monster_location(wrld))) + if start is None: + return len(a_star(wrld, goal=monster_location(wrld))) + else: + return len(a_star(wrld, goal=monster_location(wrld), start=start)) From 027ce6943632814a675cd0739b3409a4aefcfe9f Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 2 Feb 2023 15:52:16 -0500 Subject: [PATCH 13/30] next character location utility func --- teamNN/utility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teamNN/utility.py b/teamNN/utility.py index 720a50a4..ede5429f 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -112,7 +112,7 @@ def character_location(wrld): returns: (x, y) tuple""" if len(wrld.characters) == 0: Exception("No character in world") - return wrld.characters[0][0].x, wrld.characters[0][0].y + return next(iter(wrld.characters.items()))[1][0].x, next(iter(wrld.characters.items()))[1][0].y def exit_location(wrld): From e27b0f5f4bc3796dbd0e8d4a8daeee2587cea441 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Thu, 2 Feb 2023 16:05:16 -0500 Subject: [PATCH 14/30] Not working minmax --- teamNN/project1/minimax.py | 98 +++++++++++++++++-------------------- teamNN/project1/variant2.py | 17 ++++--- teamNN/project1/variant3.py | 19 +++---- teamNN/project1/variant4.py | 19 +++---- teamNN/project1/variant5.py | 27 +++++----- teamNN/testcharacter.py | 85 +++++++++----------------------- 6 files changed, 113 insertions(+), 152 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 3ca15abe..2dbe445a 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -1,58 +1,52 @@ # This is necessary to find the main code import sys +from teamNN.utility import * sys.path.insert(0, '../bomberman') -# Import necessary stuff -from testcharacter import CharacterEntity -from colorama import Fore, Back -from PriorityQueue import PriorityQueue from sys import maxsize - -class Node(object): - firstTime = True - - def __init__(self, depth, player, distance, value, x, y, wrld): - self.depth = depth - self.player = player - self.distance = distance - self.value = value - self.x = x - self.y = y - self.wrld = wrld - self.children = [] - self.makechildren() - - def makechildren(self): - if self.depth >= 0: - neighbordlist = self.eight_neighbors(self.x, self.y) - for neighbord in neighbordlist: - if self.is_cell_walkable(neighbord): - if self.look_for_monster(self.wrld): - self.value = -100000000 - else: - self.value = 10000 - self.euclidean_dist(self.wrld.exitcell, neighbord) - self.depth - self.distance = self.euclidean_dist(self.wrld.exitcell, neighbord) - else: - self.value = 0 - self.children.append( - Node(self.depth - 1, -self.player, self.distance, self.value, neighbord.x, neighbord.y, self.wrld)) - - def minimax(node, depth, player): - if (depth == 0) or (node.value == -100000000): # or game wine, game lose - return node.value - bestvalue = maxsize * -player - - for child in node.children: - child = node.children[child] - value = minimax(child, depth - 1, -player) - if (abs(maxsize * player - value) < abs(maxsize * player - bestvalue)): - bestvalue = value - return bestvalue - - - - - - - +reccursionDepth = 2 + + +def getNextMove_MiniMax(wrld): + # Get the next move using minimax + possibleMoves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + possibleMoves.append(character_location(wrld)) + # Get the value of each possible move + values = list(map(lambda move: getValue_of_State(wrld, move, 0), possibleMoves)) + # Get the max value + maxValue = max(values) + # Get the index of the max value + maxIndex = values.index(maxValue) + # Return the location to move to + return possibleMoves[maxIndex] + + +def getValue_of_State(wrld, pos, depth): + # Base case, depth has reached limit + if depth == reccursionDepth: + return evaluateState(wrld, pos) + # If depth is even, then it is a min node + possibleMoves = eight_neighbors(wrld, pos[0], pos[1]) + possibleMoves.append(pos) + return max(map(lambda move: getValue_of_State(wrld, move, depth + 1), possibleMoves)) + # if depth % 2 == 0: + # # Find the smallest value of the possible moves using map reduce + # return min(map(lambda move: getValue_of_State(wrld, move, depth + 1), possibleMoves)) + # else: + # # Find the largest value of the possible moves using map reduce + # return max(map(lambda move: getValue_of_State(wrld, move, depth + 1), possibleMoves)) + + +def evaluateState(wrld, pos): + # Calculate the value of the state based on the distance to the exit and proximity to monsters + # The closer to the exit, the better, the closer to monsters, the worse + exitDist = a_star_distance_to_exit(wrld, start=pos) + mosnterDist = euclidean_distance_to_monster(wrld, start=pos) + print("Pos: " + str(pos)) + print("Exit Dist: " + str(exitDist)) + print("Monster Dist: " + str(mosnterDist)) + print("Value: " + str((mosnterDist * 0.7) - exitDist)) + if exitDist is 0: + return maxsize + return (mosnterDist * 0.7) - exitDist diff --git a/teamNN/project1/variant2.py b/teamNN/project1/variant2.py index 306f08e8..5ab97085 100644 --- a/teamNN/project1/variant2.py +++ b/teamNN/project1/variant2.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -13,18 +14,18 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices +random.seed(123) # TODO Change this if you want different random choices g = Game.fromfile('map.txt') -g.add_monster(StupidMonster("stupid", # name - "S", # avatar - 3, 9 # position -)) +g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 9 # position + )) # TODO Add your character -g.add_character(TestCharacter("me", # name +g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position -)) + )) # Run! -g.go() +g.go(1) diff --git a/teamNN/project1/variant3.py b/teamNN/project1/variant3.py index 3d229181..314a8259 100644 --- a/teamNN/project1/variant3.py +++ b/teamNN/project1/variant3.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -13,19 +14,19 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices +random.seed(123) # TODO Change this if you want different random choices g = Game.fromfile('map.txt') -g.add_monster(SelfPreservingMonster("selfpreserving", # name - "S", # avatar - 3, 9, # position - 1 # detection range -)) +g.add_monster(SelfPreservingMonster("selfpreserving", # name + "S", # avatar + 3, 9, # position + 1 # detection range + )) # TODO Add your character -g.add_character(TestCharacter("me", # name +g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position -)) + )) # Run! -g.go() +g.go(1) diff --git a/teamNN/project1/variant4.py b/teamNN/project1/variant4.py index 81e4a631..c11eb86d 100644 --- a/teamNN/project1/variant4.py +++ b/teamNN/project1/variant4.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -13,19 +14,19 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices +random.seed(123) # TODO Change this if you want different random choices g = Game.fromfile('map.txt') -g.add_monster(SelfPreservingMonster("aggressive", # name - "A", # avatar - 3, 13, # position - 2 # detection range -)) +g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 2 # detection range + )) # TODO Add your character -g.add_character(TestCharacter("me", # name +g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position -)) + )) # Run! -g.go() +g.go(1) diff --git a/teamNN/project1/variant5.py b/teamNN/project1/variant5.py index 807d4942..b24edcd6 100644 --- a/teamNN/project1/variant5.py +++ b/teamNN/project1/variant5.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -14,23 +15,23 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices +random.seed(123) # TODO Change this if you want different random choices g = Game.fromfile('map.txt') -g.add_monster(StupidMonster("stupid", # name - "S", # avatar - 3, 5, # position -)) -g.add_monster(SelfPreservingMonster("aggressive", # name - "A", # avatar - 3, 13, # position - 1 # detection range -)) +g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 5, # position + )) +g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 1 # detection range + )) # TODO Add your character -g.add_character(TestCharacter("me", # name +g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position -)) + )) # Run! -g.go() +g.go(1) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index f26d784d..d6897d16 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -1,6 +1,7 @@ # This is necessary to find the main code import sys +from teamNN.project1.minimax import getNextMove_MiniMax from teamNN.utility import * sys.path.insert(0, '../bomberman') @@ -14,65 +15,27 @@ class TestCharacter(CharacterEntity): firstTime = True a_star_path = [] - def a_star(self, wrld, goal=None, start=None): - if start is None: - start = (self.x, self.y) # Start at current position - if goal is None: - goal = wrld.exitcell # Goal is exit cell - - cost_so_far = {start: 0} # Dictionary of costs to get to each cell - came_from = {start: None} # Dictionary of where each cell came from - - frontier = PriorityQueue() # Priority queue of cells to visit - frontier.put(start, 0) - - while not frontier.empty(): - current = frontier.get() - - if current == goal: - break - - # Check all walkable neighbors of current cell - for neighbor in eight_neighbors(wrld, current[0], current[1]): - # Calculate cost to get to neighbor - 1 or 1.4 - new_cost = cost_so_far[current] + euclidean_dist(current, neighbor) - - # If neighbor has no path or new path is better, update path - if neighbor not in cost_so_far or new_cost < cost_so_far[neighbor]: - cost_so_far[neighbor] = new_cost - priority = new_cost + euclidean_dist(neighbor, goal) - frontier.put(neighbor, priority) - came_from[neighbor] = current - - # Reconstruct path using came_from dictionary - currPos = goal - finalPath = [] - finalPath.append(goal) - while currPos != start: - currPos = came_from[currPos] - finalPath.append(currPos) - - finalPath.reverse() - return finalPath - def do(self, wrld): - if self.firstTime: - print("Character at", self.x, self.y) - print("Exit at", wrld.exitcell) - print("Explosions:", wrld.explosions) - print("Monsters:", wrld.monsters) - print("Monster Location", monster_location(wrld)) - print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) - print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) - print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) - print("Manhattan distance to monster:", manhattan_distance_to_monster(wrld)) - self.a_star_path = self.a_star(wrld) - print("A* path to goal:", self.a_star_path) - - for point in self.a_star_path: - # Mark path on world - self.set_cell_color(point[0], point[1], Fore.RED + Back.GREEN) - self.firstTime = False - else: - nextCell = self.a_star_path.pop(0) - self.move(nextCell[0] - self.x, nextCell[1] - self.y) + print(getNextMove_MiniMax(wrld)) + nextCell = getNextMove_MiniMax(wrld) + self.move(nextCell[0] - self.x, nextCell[1] - self.y) + # if self.firstTime: + # print("Character at", self.x, self.y) + # print("Exit at", wrld.exitcell) + # print("Explosions:", wrld.explosions) + # print("Monsters:", wrld.monsters) + # print("Monster Location", monster_location(wrld)) + # print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) + # print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) + # print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) + # print("Manhattan distance to monster:", manhattan_distance_to_monster(wrld)) + # self.a_star_path = a_star(wrld) + # print("A* path to goal:", self.a_star_path) + # + # for point in self.a_star_path: + # # Mark path on world + # self.set_cell_color(point[0], point[1], Fore.RED + Back.GREEN) + # self.firstTime = False + # else: + # nextCell = self.a_star_path.pop(0) + # self.move(nextCell[0] - self.x, nextCell[1] - self.y) From b060ef5d4fb8982c786a05fca1dfbdd14b1353a9 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 3 Feb 2023 18:45:15 -0500 Subject: [PATCH 15/30] fixed most imports --- teamNN/interactivecharacter.py | 2 ++ teamNN/project1/map.txt | 4 ++-- teamNN/project1/variant1.py | 11 ++++++----- teamNN/project1/variant2.py | 10 +++++++++- teamNN/project1/variant3.py | 8 +++++++- teamNN/project1/variant4.py | 13 ++++++++++--- teamNN/project1/variant5.py | 8 +++++++- teamNN/testcharacter.py | 10 ++++++---- teamNN/utility.py | 35 ++++++++++++++++++++++++++++++++-- 9 files changed, 82 insertions(+), 19 deletions(-) diff --git a/teamNN/interactivecharacter.py b/teamNN/interactivecharacter.py index cd1c4acd..a53be832 100644 --- a/teamNN/interactivecharacter.py +++ b/teamNN/interactivecharacter.py @@ -1,10 +1,12 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../bomberman') # Import necessary stuff from entity import CharacterEntity from colorama import Fore, Back + class InteractiveCharacter(CharacterEntity): def do(self, wrld): diff --git a/teamNN/project1/map.txt b/teamNN/project1/map.txt index 35db2451..643952c1 100644 --- a/teamNN/project1/map.txt +++ b/teamNN/project1/map.txt @@ -1,6 +1,6 @@ max_time 5000 -bomb_time 10 -expl_duration 2 +bomb_time 1 +expl_duration 1 expl_range 4 +--------+ | | diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index 0eeb7e1a..e584106c 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -13,7 +14,7 @@ from testcharacter import TestCharacter # Uncomment this if you want the interactive character -# from interactivecharacter import InteractiveCharacter +from interactivecharacter import InteractiveCharacter # Create the game g = Game.fromfile('map.txt') @@ -21,16 +22,16 @@ # TODO Add your character # Uncomment this if you want the test character -g.add_character(TestCharacter("me", # name +g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position -)) + )) # Uncomment this if you want the interactive character -# g.add_character(InteractiveCharacter("me", # name +# g.add_character(InteractiveCharacter("me", # name # "C", # avatar # 0, 0 # position -# )) +# )) # Run! diff --git a/teamNN/project1/variant2.py b/teamNN/project1/variant2.py index 5ab97085..ad540cf9 100644 --- a/teamNN/project1/variant2.py +++ b/teamNN/project1/variant2.py @@ -1,6 +1,8 @@ # This is necessary to find the main code import sys +from teamNN.interactivecharacter import InteractiveCharacter + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -22,10 +24,16 @@ )) # TODO Add your character +# g.add_character(InteractiveCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) + +# Uncomment this if you want the test character g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position )) # Run! -g.go(1) +g.go(0) diff --git a/teamNN/project1/variant3.py b/teamNN/project1/variant3.py index 314a8259..f4548b3d 100644 --- a/teamNN/project1/variant3.py +++ b/teamNN/project1/variant3.py @@ -1,6 +1,8 @@ # This is necessary to find the main code import sys +from teamNN.interactivecharacter import InteractiveCharacter + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -27,6 +29,10 @@ "C", # avatar 0, 0 # position )) +# g.add_character(InteractiveCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) # Run! -g.go(1) +g.go(0) diff --git a/teamNN/project1/variant4.py b/teamNN/project1/variant4.py index c11eb86d..652424f0 100644 --- a/teamNN/project1/variant4.py +++ b/teamNN/project1/variant4.py @@ -1,6 +1,8 @@ # This is necessary to find the main code import sys +from teamNN.interactivecharacter import InteractiveCharacter + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -14,7 +16,7 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices +random.seed(2) # TODO Change this if you want different random choices g = Game.fromfile('map.txt') g.add_monster(SelfPreservingMonster("aggressive", # name "A", # avatar @@ -23,10 +25,15 @@ )) # TODO Add your character +# g.add_character(InteractiveCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) + +# Uncomment this if you want the test character g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position )) - # Run! -g.go(1) +g.go(0) diff --git a/teamNN/project1/variant5.py b/teamNN/project1/variant5.py index b24edcd6..b7b17b00 100644 --- a/teamNN/project1/variant5.py +++ b/teamNN/project1/variant5.py @@ -28,10 +28,16 @@ )) # TODO Add your character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) + +# Uncomment this if you want the test character g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position )) # Run! -g.go(1) +g.go(0) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index d6897d16..4fe5ff6b 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -1,23 +1,25 @@ # This is necessary to find the main code import sys -from teamNN.project1.minimax import getNextMove_MiniMax -from teamNN.utility import * - sys.path.insert(0, '../bomberman') # Import necessary stuff from entity import CharacterEntity from colorama import Fore, Back from PriorityQueue import PriorityQueue +sys.path.insert(1, '../teamNN') +from utility import * +from project1.minimax import getNextMove_MiniMax + class TestCharacter(CharacterEntity): firstTime = True a_star_path = [] def do(self, wrld): - print(getNextMove_MiniMax(wrld)) + # print(getNextMove_MiniMax(wrld)) nextCell = getNextMove_MiniMax(wrld) + print("Selected Move: ", nextCell) self.move(nextCell[0] - self.x, nextCell[1] - self.y) # if self.firstTime: # print("Character at", self.x, self.y) diff --git a/teamNN/utility.py b/teamNN/utility.py index ede5429f..c9215da1 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -14,7 +14,7 @@ int euclidean_distance_to_monster(wrld:World) //Returns Nearest Monster """ -from teamNN.PriorityQueue import PriorityQueue +from PriorityQueue import PriorityQueue def euclidean_dist(point_one, point_two): @@ -31,7 +31,7 @@ def is_cell_walkable(wrld, x, y): x: int y: int returns: bool""" - return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) + return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) or wrld.characters_at(x, y) def eight_neighbors(wrld, x, y): @@ -206,3 +206,34 @@ def a_star_distance_to_monster(wrld, start=None): return len(a_star(wrld, goal=monster_location(wrld))) else: return len(a_star(wrld, goal=monster_location(wrld), start=start)) + + +def a_star_distance(wrld, start, goal): + """Returns the a* distance between two points. + wrld: World object + start: (x, y) tuple to start from + goal: (x, y) tuple to end at + returns: float""" + if not is_cell_walkable(wrld, goal[0], goal[1]): + print("Goal is not walkable!") + raise Exception("Goal is not walkable!", goal) + if not is_cell_walkable(wrld, start[0], start[1]): + print("Start is not walkable!") + raise Exception("Start is not walkable!", start) + + return len(a_star(wrld, goal=goal, start=start)) + + +def evaluate_state(wrld, characterLocation=None, monsterLocation=None): + """Returns a value for the current world state. + wrld: World object + returns: float""" + # print("Evaluating state with character location: " + str(characterLocation) + " and monster location: " + str(monsterLocation)) + if characterLocation is None: + characterLocation = character_location(wrld) + if monsterLocation is None: + monsterLocation = monster_location(wrld) + + distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) + distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) + return (distance_to_monster * 0.7) - distance_to_exit From a319c6b9a2fab8aca4478146cb2dc062774023f9 Mon Sep 17 00:00:00 2001 From: ndenda Date: Fri, 3 Feb 2023 18:48:26 -0500 Subject: [PATCH 16/30] --- teamNN/project1/minimaxnode.py | 65 ++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 teamNN/project1/minimaxnode.py diff --git a/teamNN/project1/minimaxnode.py b/teamNN/project1/minimaxnode.py new file mode 100644 index 00000000..67235b39 --- /dev/null +++ b/teamNN/project1/minimaxnode.py @@ -0,0 +1,65 @@ +# This is necessary to find the main code +import sys + +sys.path.insert(0, '../bomberman') +# Import necessary stuff +from testcharacter import CharacterEntity +from colorama import Fore, Back +from PriorityQueue import PriorityQueue +from sys import maxsize +from teamNN.utility import * + + +class Node(object): + + def __init__(self, depth, player,wrld,position=None,value=0): + self.depth = depth + self.player = player + self.value = value + self.wrld = wrld + self.distancetogoal = a_star_distance_to_exit(wrld,position) + self.children = [] + self.makechildren() + + def makechildren(self,wrld): + if self.depth >= 0: + neighbordlist = self.eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + for neighbord in neighbordlist: + newdistance = self.distancetogoal - a_star_distance_to_exit(wrld,neighbord) + newpostion = (neighbord.x, neighbord.y) + self.children.append( Node(self.depth - 1, - self.player, wrld,newpostion, self.evaluateState(wrld,newdistance,newpostion))) + + def evaluateState(wrld, distance,pos): + if distance is 0: + return maxsize + return - maxsize + + + def minimax(self,node, depth, player): + if (depth == 0) or (abs(node.value == maxsize)): # or game wine, game lose + return node.value + bestvalue = maxsize * -player + + for child in node.children: + child = node.children[child] + value = self.minimax(child, depth - 1, -player) + if (abs(maxsize * player - value) < abs(maxsize * player - bestvalue)): + bestvalue = value + return bestvalue + +depthserach = 4 +currentplayer = 1 #monster + +def getNextMove_MiniMax2(wrld): + if character_location != monster_location: + currentplayer *= -1 + node = Node(depthserach,currentplayer,wrld,character_location) + bestmove = -100 + bestvalue = - currentplayer * maxsize + for i in range(len(node.children)): + child = node.children[i] + value = node.minimax(child,depthserach,-currentplayer) + if ( abs(currentplayer * maxsize - value)<= abs(currentplayer*maxsize-bestvalue)): + bestvalue = value + bestmove = i + return bestmove From 925812edb95d6d2369c3622bd4b3ae56d3fa24f3 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 3 Feb 2023 19:02:42 -0500 Subject: [PATCH 17/30] somewhat working minimax --- .vscode/settings.json | 3 ++ teamNN/project1/minimax.py | 75 ++++++++++++++++++++----------------- teamNN/project1/variant1.py | 2 +- 3 files changed, 45 insertions(+), 35 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b8d7c525 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.extraPaths": ["./Bomberman"] +} diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 2dbe445a..144648d2 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -1,10 +1,12 @@ # This is necessary to find the main code import sys -from teamNN.utility import * sys.path.insert(0, '../bomberman') from sys import maxsize +sys.path.insert(1, '../') +from utility import * + reccursionDepth = 2 @@ -12,41 +14,46 @@ def getNextMove_MiniMax(wrld): # Get the next move using minimax possibleMoves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) possibleMoves.append(character_location(wrld)) - # Get the value of each possible move - values = list(map(lambda move: getValue_of_State(wrld, move, 0), possibleMoves)) - # Get the max value - maxValue = max(values) - # Get the index of the max value - maxIndex = values.index(maxValue) - # Return the location to move to - return possibleMoves[maxIndex] + print("get next move called! self move options: ", possibleMoves) + values = list(map(lambda move: getValue_of_State(wrld, move, monster_location(wrld), 0), possibleMoves)) + print("Options Evaluated: ", possibleMoves) + print("Option Scores: ", values) + return possibleMoves[values.index(max(values))] -def getValue_of_State(wrld, pos, depth): +def getValue_of_State(wrld, self_pos, monster_pos, depth): # Base case, depth has reached limit if depth == reccursionDepth: - return evaluateState(wrld, pos) + sys.stdout.write("\t" * depth) + print("base case! (Rec-depth) self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth, "score: ", + evaluate_state(wrld, self_pos, monster_pos)) + return evaluate_state(wrld, self_pos, monster_pos) + + if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]): + sys.stdout.write("\t" * depth) + print("base case! (Monster) self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth, "score: ", + -maxsize / 2) + return -maxsize / 2 + + if self_pos == wrld.exitcell: + sys.stdout.write("\t" * depth) + print("base case! (Exit) self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth, "score: ", + maxsize / 2) + return maxsize / 2 # If depth is even, then it is a min node - possibleMoves = eight_neighbors(wrld, pos[0], pos[1]) - possibleMoves.append(pos) - return max(map(lambda move: getValue_of_State(wrld, move, depth + 1), possibleMoves)) - # if depth % 2 == 0: - # # Find the smallest value of the possible moves using map reduce - # return min(map(lambda move: getValue_of_State(wrld, move, depth + 1), possibleMoves)) - # else: - # # Find the largest value of the possible moves using map reduce - # return max(map(lambda move: getValue_of_State(wrld, move, depth + 1), possibleMoves)) - - -def evaluateState(wrld, pos): - # Calculate the value of the state based on the distance to the exit and proximity to monsters - # The closer to the exit, the better, the closer to monsters, the worse - exitDist = a_star_distance_to_exit(wrld, start=pos) - mosnterDist = euclidean_distance_to_monster(wrld, start=pos) - print("Pos: " + str(pos)) - print("Exit Dist: " + str(exitDist)) - print("Monster Dist: " + str(mosnterDist)) - print("Value: " + str((mosnterDist * 0.7) - exitDist)) - if exitDist is 0: - return maxsize - return (mosnterDist * 0.7) - exitDist + if depth % 2 == 1: # Max Node (self) + possible_moves = eight_neighbors(wrld, self_pos[0], self_pos[1]) + possible_moves.append(self_pos) + sys.stdout.write("\t" * depth) + print("max node called! self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth) + sys.stdout.write("\t" * depth) + print("max node called! self move options: ", possible_moves) + return max(map(lambda self_move: getValue_of_State(wrld, self_move, monster_pos, depth + 1), possible_moves)) + else: # Min Node (monster) + possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) + possible_moves.append(monster_pos) + sys.stdout.write("\t" * depth) + print("min node called! self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth) + sys.stdout.write("\t" * depth) + print("min node called! monster move options: ", possible_moves) + return min(map(lambda monster_move: getValue_of_State(wrld, self_pos, monster_move, depth + 1), possible_moves)) diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index e584106c..3c554ceb 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -8,7 +8,7 @@ from game import Game # TODO This is your code! -sys.path.insert(1, '../teamNN') +sys.path.insert(2, '../teamNN') # Uncomment this if you want the empty test character from testcharacter import TestCharacter From 211a97e2091e2946344a138882aeccd917ad3976 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Fri, 3 Feb 2023 20:36:08 -0500 Subject: [PATCH 18/30] no sm yet but better minimax --- teamNN/project1/minimax.py | 67 ++++++++++++++++++------------------- teamNN/project1/variant2.py | 2 +- teamNN/project1/variant3.py | 2 +- teamNN/project1/variant4.py | 2 +- teamNN/project1/variant5.py | 2 +- teamNN/testcharacter.py | 52 ++++++++++++++++------------ teamNN/utility.py | 4 ++- 7 files changed, 70 insertions(+), 61 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 144648d2..d8ef9be5 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -7,53 +7,50 @@ sys.path.insert(1, '../') from utility import * -reccursionDepth = 2 +reccursionDepth = 3 -def getNextMove_MiniMax(wrld): - # Get the next move using minimax +def getNextMove_MiniMax(wrld, alpha=-float("inf"), beta=float("inf")): + # Get the next move using minimax with alpha-beta pruning possibleMoves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) possibleMoves.append(character_location(wrld)) - print("get next move called! self move options: ", possibleMoves) - values = list(map(lambda move: getValue_of_State(wrld, move, monster_location(wrld), 0), possibleMoves)) - print("Options Evaluated: ", possibleMoves) - print("Option Scores: ", values) + values = [] + for move in possibleMoves: + value = getValue_of_State(wrld, move, monster_location(wrld), 0, alpha, beta) + values.append(value) + alpha = max(alpha, value) + if alpha >= beta: + break return possibleMoves[values.index(max(values))] -def getValue_of_State(wrld, self_pos, monster_pos, depth): - # Base case, depth has reached limit - if depth == reccursionDepth: - sys.stdout.write("\t" * depth) - print("base case! (Rec-depth) self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth, "score: ", - evaluate_state(wrld, self_pos, monster_pos)) - return evaluate_state(wrld, self_pos, monster_pos) +def getValue_of_State(wrld, self_pos, monster_pos, depth, alpha, beta): + if self_pos == wrld.exitcell: + return 1000 - depth - if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]): - sys.stdout.write("\t" * depth) - print("base case! (Monster) self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth, "score: ", - -maxsize / 2) - return -maxsize / 2 + if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]) or self_pos == monster_pos: + return -1000 - depth + + if depth == reccursionDepth: + return evaluate_state(wrld, self_pos, monster_pos) - depth - if self_pos == wrld.exitcell: - sys.stdout.write("\t" * depth) - print("base case! (Exit) self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth, "score: ", - maxsize / 2) - return maxsize / 2 - # If depth is even, then it is a min node if depth % 2 == 1: # Max Node (self) + value = -float("inf") possible_moves = eight_neighbors(wrld, self_pos[0], self_pos[1]) possible_moves.append(self_pos) - sys.stdout.write("\t" * depth) - print("max node called! self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth) - sys.stdout.write("\t" * depth) - print("max node called! self move options: ", possible_moves) - return max(map(lambda self_move: getValue_of_State(wrld, self_move, monster_pos, depth + 1), possible_moves)) + for self_move in possible_moves: + value = max(value, getValue_of_State(wrld, self_move, monster_pos, depth + 1, alpha, beta)) + alpha = max(alpha, value) + if alpha >= beta: + break + return value else: # Min Node (monster) + value = float("inf") possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) possible_moves.append(monster_pos) - sys.stdout.write("\t" * depth) - print("min node called! self pos: ", self_pos, "monster pos: ", monster_pos, "depth: ", depth) - sys.stdout.write("\t" * depth) - print("min node called! monster move options: ", possible_moves) - return min(map(lambda monster_move: getValue_of_State(wrld, self_pos, monster_move, depth + 1), possible_moves)) + for monster_move in possible_moves: + value = min(value, getValue_of_State(wrld, self_pos, monster_move, depth + 1, alpha, beta)) + beta = min(beta, value) + if alpha >= beta: + break + return value diff --git a/teamNN/project1/variant2.py b/teamNN/project1/variant2.py index ad540cf9..df8c85f8 100644 --- a/teamNN/project1/variant2.py +++ b/teamNN/project1/variant2.py @@ -36,4 +36,4 @@ )) # Run! -g.go(0) +g.go(200) diff --git a/teamNN/project1/variant3.py b/teamNN/project1/variant3.py index f4548b3d..f79dcfe2 100644 --- a/teamNN/project1/variant3.py +++ b/teamNN/project1/variant3.py @@ -35,4 +35,4 @@ # )) # Run! -g.go(0) +g.go(200) diff --git a/teamNN/project1/variant4.py b/teamNN/project1/variant4.py index 652424f0..5bfc01c0 100644 --- a/teamNN/project1/variant4.py +++ b/teamNN/project1/variant4.py @@ -36,4 +36,4 @@ 0, 0 # position )) # Run! -g.go(0) +g.go(200) diff --git a/teamNN/project1/variant5.py b/teamNN/project1/variant5.py index b7b17b00..087ba0a1 100644 --- a/teamNN/project1/variant5.py +++ b/teamNN/project1/variant5.py @@ -40,4 +40,4 @@ )) # Run! -g.go(0) +g.go(200) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 4fe5ff6b..8ff44ef8 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys +from enum import Enum sys.path.insert(0, '../bomberman') # Import necessary stuff @@ -12,32 +13,41 @@ from project1.minimax import getNextMove_MiniMax +class State(Enum): + START = 1 + MOVE_TO_EXIT = 2 + RUN_FROM_MONSTER = 3 + MOVE_TO_WALL = 4 + PLACE_BOMB = 5 + RUN_FROM_BOMB = 6 + DONE = 7 + + class TestCharacter(CharacterEntity): - firstTime = True - a_star_path = [] + stateMachine = State.START def do(self, wrld): + # match self.stateMachine: + # case State.START: + # self.stateMachine = State.MOVE_TO_EXIT + # case State.MOVE_TO_EXIT: + # self.move_to_exit(wrld) + # case State.RUN_FROM_MONSTER: + # self.run_from_monster(wrld) + # case State.MOVE_TO_WALL: + # self.move_to_wall(wrld) + # case State.PLACE_BOMB: + # self.place_bomb(wrld) + # case State.RUN_FROM_BOMB: + # self.run_from_bomb(wrld) + # case State.DONE: + # return + # print(getNextMove_MiniMax(wrld)) + print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) nextCell = getNextMove_MiniMax(wrld) print("Selected Move: ", nextCell) + self.move(nextCell[0] - self.x, nextCell[1] - self.y) + # if self.firstTime: - # print("Character at", self.x, self.y) - # print("Exit at", wrld.exitcell) - # print("Explosions:", wrld.explosions) - # print("Monsters:", wrld.monsters) - # print("Monster Location", monster_location(wrld)) - # print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) - # print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) - # print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) - # print("Manhattan distance to monster:", manhattan_distance_to_monster(wrld)) - # self.a_star_path = a_star(wrld) - # print("A* path to goal:", self.a_star_path) - # - # for point in self.a_star_path: - # # Mark path on world - # self.set_cell_color(point[0], point[1], Fore.RED + Back.GREEN) - # self.firstTime = False - # else: - # nextCell = self.a_star_path.pop(0) - # self.move(nextCell[0] - self.x, nextCell[1] - self.y) diff --git a/teamNN/utility.py b/teamNN/utility.py index c9215da1..d94c2b70 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -234,6 +234,8 @@ def evaluate_state(wrld, characterLocation=None, monsterLocation=None): if monsterLocation is None: monsterLocation = monster_location(wrld) + number_of_move_options = len(eight_neighbors(wrld, characterLocation[0], characterLocation[1])) + distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) - return (distance_to_monster * 0.7) - distance_to_exit + return int((distance_to_monster * 3) - distance_to_exit * 5) + number_of_move_options From 413101a043b19f9f5921737dde585fc4fa080d7f Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Sat, 4 Feb 2023 16:58:22 -0500 Subject: [PATCH 19/30] Added state machine --- teamNN/project1/minimax.py | 141 +++++++++++++++++++++++++----------- teamNN/project1/variant4.py | 2 +- teamNN/project1/variant5.py | 43 +++++------ teamNN/testcharacter.py | 105 ++++++++++++++++++++------- teamNN/utility.py | 30 ++++---- 5 files changed, 210 insertions(+), 111 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index d8ef9be5..67a7ce00 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -7,50 +7,103 @@ sys.path.insert(1, '../') from utility import * -reccursionDepth = 3 - - -def getNextMove_MiniMax(wrld, alpha=-float("inf"), beta=float("inf")): - # Get the next move using minimax with alpha-beta pruning - possibleMoves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) - possibleMoves.append(character_location(wrld)) - values = [] - for move in possibleMoves: - value = getValue_of_State(wrld, move, monster_location(wrld), 0, alpha, beta) - values.append(value) - alpha = max(alpha, value) - if alpha >= beta: - break - return possibleMoves[values.index(max(values))] - - -def getValue_of_State(wrld, self_pos, monster_pos, depth, alpha, beta): - if self_pos == wrld.exitcell: - return 1000 - depth - - if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]) or self_pos == monster_pos: - return -1000 - depth - - if depth == reccursionDepth: - return evaluate_state(wrld, self_pos, monster_pos) - depth - - if depth % 2 == 1: # Max Node (self) - value = -float("inf") - possible_moves = eight_neighbors(wrld, self_pos[0], self_pos[1]) - possible_moves.append(self_pos) - for self_move in possible_moves: - value = max(value, getValue_of_State(wrld, self_move, monster_pos, depth + 1, alpha, beta)) + +class AI(): + isExpectimax: bool = False + reccursionDepth: int = 3 + reward_max: int = 50 + reward_min: int = -50 + nodes_explored_count: int = 0 + + def get_next_move(self, wrld, alpha=-float("inf"), beta=float("inf")): + # Get the next move using minimax with alpha-beta pruning + possible_moves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + # possible_moves.append(character_location(wrld)) + prioritize_moves_for_self(wrld, possible_moves) + values = [] + for move in possible_moves: + value = self.get_value_of_state(wrld, move, monster_location(wrld), 0, alpha, beta) + values.append(value) alpha = max(alpha, value) if alpha >= beta: break - return value - else: # Min Node (monster) - value = float("inf") - possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) - possible_moves.append(monster_pos) - for monster_move in possible_moves: - value = min(value, getValue_of_State(wrld, self_pos, monster_move, depth + 1, alpha, beta)) - beta = min(beta, value) - if alpha >= beta: - break - return value + print("Pruned", round(self.nodes_explored_count / 9 ** (self.reccursionDepth + 1) * 100), "% of the tree.", + self.nodes_explored_count, "nodes explored.") + self.nodes_explored_count = 0 + return possible_moves[values.index(max(values))] + + def get_value_of_state(self, wrld, self_pos, monster_pos, depth, alpha, beta): + self.nodes_explored_count += 1 + if self_pos == wrld.exitcell: + return 300 - depth + + if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]) or self_pos == monster_pos: + return -100 - depth + + if depth == self.reccursionDepth: + return evaluate_state(wrld, self_pos, monster_pos) - depth + + if depth % 2 == 1: # Max Node (self) + value = -float("inf") + possible_moves = eight_neighbors(wrld, self_pos[0], self_pos[1]) + possible_moves.append(self_pos) + for self_move in possible_moves: + value = max(value, self.get_value_of_state(wrld, self_move, monster_pos, depth + 1, alpha, beta)) + alpha = max(alpha, value) + if alpha >= beta: + break + return value + else: + + if not self.isExpectimax: # If modeling monster as Minimax + value = float("inf") + possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) + possible_moves.append(monster_pos) + for monster_move in possible_moves: + value = min(value, self.get_value_of_state(wrld, self_pos, monster_move, depth + 1, alpha, beta)) + beta = min(beta, value) + if alpha >= beta: + break + return value + else: # If Expectimax + value = 0 + probability_total = 0 + possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) + possible_moves.append(monster_pos) + for monster_move in possible_moves: + probability = 1 / len(possible_moves) + probability_total += probability + value += self.get_value_of_state(wrld, self_pos, monster_move, depth + 1, alpha, + beta) * probability + remaining_probability = 1 - probability_total + if value + (remaining_probability * self.reward_max) < alpha: + # print("Pruned expected node with remaining probability", remaining_probability) + break + return value + + +def prioritize_moves_for_self(wrld, possible_moves): + # Prioritize moves that are closer to the exit to prune the tree more + possible_moves.sort(key=lambda move: euclidean_distance_to_exit(wrld, move)) + + +def prioritize_moves_for_monster(wrld, possible_moves): + # Prioritize moves that are closer to the exit to prune the tree more + possible_moves.sort(key=lambda move: euclidean_dist(move, character_location(wrld))) + + +def evaluate_state(wrld, characterLocation=None, monsterLocation=None): + """Returns a value for the current world state. + wrld: World object + returns: float""" + # print("Evaluating state with character location: " + str(characterLocation) + " and monster location: " + str(monsterLocation)) + if characterLocation is None: + characterLocation = character_location(wrld) + if monsterLocation is None: + monsterLocation = monster_location(wrld) + + number_of_move_options = len(eight_neighbors(wrld, characterLocation[0], characterLocation[1])) + + distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) + distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) + return int((distance_to_monster * 5) - distance_to_exit * 6) + number_of_move_options * 20 diff --git a/teamNN/project1/variant4.py b/teamNN/project1/variant4.py index 5bfc01c0..3e67a849 100644 --- a/teamNN/project1/variant4.py +++ b/teamNN/project1/variant4.py @@ -16,7 +16,7 @@ from testcharacter import TestCharacter # Create the game -random.seed(2) # TODO Change this if you want different random choices +random.seed(3) # TODO Change this if you want different random choices g = Game.fromfile('map.txt') g.add_monster(SelfPreservingMonster("aggressive", # name "A", # avatar diff --git a/teamNN/project1/variant5.py b/teamNN/project1/variant5.py index 087ba0a1..dad834f2 100644 --- a/teamNN/project1/variant5.py +++ b/teamNN/project1/variant5.py @@ -15,29 +15,26 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(StupidMonster("stupid", # name - "S", # avatar - 3, 5, # position - )) -g.add_monster(SelfPreservingMonster("aggressive", # name - "A", # avatar - 3, 13, # position - 1 # detection range - )) +scores = [] +for i in range(100): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 5, # position + )) + # g.add_monster(SelfPreservingMonster("aggressive", # name + # "A", # avatar + # 3, 13, # position + # 1 # detection range + # )) -# TODO Add your character -# g.add_character(TestCharacter("me", # name -# "C", # avatar -# 0, 0 # position -# )) + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) -# Uncomment this if you want the test character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position - )) + g.go(200) + scores.append(g.scores['me']) -# Run! -g.go(200) +average_score = sum(scores) / len(scores) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 8ff44ef8..cea7222b 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -10,44 +10,95 @@ sys.path.insert(1, '../teamNN') from utility import * -from project1.minimax import getNextMove_MiniMax +from project1.minimax import * class State(Enum): START = 1 - MOVE_TO_EXIT = 2 - RUN_FROM_MONSTER = 3 - MOVE_TO_WALL = 4 - PLACE_BOMB = 5 - RUN_FROM_BOMB = 6 - DONE = 7 + PLACE_BOMB = 2 + PLACE_BOMB_R = 3 + WAIT_FOR_BOMB = 4 + FAR_FROM_MONSTER = 5 + CLOSE_TO_MONSTER = 6 class TestCharacter(CharacterEntity): + waitCount = 0 + bombCount = 0 + bombLimit = 5 + bombCoolDown = 0 stateMachine = State.START + ai = AI() def do(self, wrld): - # match self.stateMachine: - # case State.START: - # self.stateMachine = State.MOVE_TO_EXIT - # case State.MOVE_TO_EXIT: - # self.move_to_exit(wrld) - # case State.RUN_FROM_MONSTER: - # self.run_from_monster(wrld) - # case State.MOVE_TO_WALL: - # self.move_to_wall(wrld) - # case State.PLACE_BOMB: - # self.place_bomb(wrld) - # case State.RUN_FROM_BOMB: - # self.run_from_bomb(wrld) - # case State.DONE: - # return + print("Current State: ", self.stateMachine) + self.bombCoolDown -= 1 + match self.stateMachine: + case State.START: + self.move(0, 1) + if self.x == 0 and self.y == 2: + self.stateMachine = State.PLACE_BOMB + case State.PLACE_BOMB: + self.place_bomb() + self.move(1, -1) + self.waitCount = 0 + self.stateMachine = State.WAIT_FOR_BOMB + case State.PLACE_BOMB_R: + self.place_bomb() + self.move(-1, -1) + self.waitCount = 0 + self.stateMachine = State.WAIT_FOR_BOMB + case State.WAIT_FOR_BOMB: + self.move(0, 0) + self.waitCount += 1 + if self.waitCount > 1: + self.bombCount += 1 + self.bombCoolDown = 5 + self.stateMachine = State.FAR_FROM_MONSTER + case State.FAR_FROM_MONSTER: + self.ai.reccursionDepth = 2 + self.ai.isExpectimax = False + nextCell = self.ai.get_next_move(wrld) + self.move(nextCell[0] - self.x, nextCell[1] - self.y) + print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) + print("Selected Move: ", nextCell) + if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( + (nextCell[0], nextCell[1]), True): + self.stateMachine = State.PLACE_BOMB_R + if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( + (nextCell[0], nextCell[1]), False): + self.stateMachine = State.PLACE_BOMB + if euclidean_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 7: + self.stateMachine = State.CLOSE_TO_MONSTER + case State.CLOSE_TO_MONSTER: + self.ai.reccursionDepth = 3 + self.ai.isExpectimax = True + if len(wrld.monsters.values()) > 1: + self.ai.isExpectimax = False + nextCell = self.ai.get_next_move(wrld) + self.move(nextCell[0] - self.x, nextCell[1] - self.y) + print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) + print("Selected Move: ", nextCell) + if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( + (nextCell[0], nextCell[1]), True): + self.stateMachine = State.PLACE_BOMB_R + if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( + (nextCell[0], nextCell[1]), False): + self.stateMachine = State.PLACE_BOMB + if euclidean_distance_to_monster(wrld, (nextCell[0], nextCell[1])) > 10: + self.stateMachine = State.FAR_FROM_MONSTER - # print(getNextMove_MiniMax(wrld)) - print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) - nextCell = getNextMove_MiniMax(wrld) - print("Selected Move: ", nextCell) + def valid_bomb_location(self, location, isRight): + if isRight: + return (location[0] == 7 or location[0] == 6) and (location[1] == 6 or location[1] == 14) + else: + return (location[0] == 0 or location[0] == 1) and (location[1] == 2 or location[1] == 10) - self.move(nextCell[0] - self.x, nextCell[1] - self.y) + # print(getNextMove_MiniMax(wrld)) + # print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) + # nextCell = get_next_move_expectimax(wrld) + # print("Selected Move: ", nextCell) + # + # self.move(nextCell[0] - self.x, nextCell[1] - self.y) # if self.firstTime: diff --git a/teamNN/utility.py b/teamNN/utility.py index d94c2b70..aa10aacd 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -123,12 +123,18 @@ def exit_location(wrld): def monster_location(wrld): - """Returns the location of the first monster in wrld. + """Returns the location of the nearest monster in wrld. wrld: World object returns: (x, y) tuple""" if len(wrld.monsters) == 0: Exception("No monster in world") - return next(iter(wrld.monsters.items()))[1][0].x, next(iter(wrld.monsters.items()))[1][0].y + realMonsters = [] + monsters = list(wrld.monsters.values()) + for monster in monsters: + realMonsters.append(monster[0]) + monsters = realMonsters + monsters.sort(key=lambda m: euclidean_dist(character_location(wrld), (m.x, m.y))) + return monsters[0].x, monsters[0].y def manhattan_distance_to_exit(wrld, start=None): @@ -224,18 +230,10 @@ def a_star_distance(wrld, start, goal): return len(a_star(wrld, goal=goal, start=start)) -def evaluate_state(wrld, characterLocation=None, monsterLocation=None): - """Returns a value for the current world state. +def monster_travel_direction(wrld): + """Returns the direction the monster is moving in. wrld: World object - returns: float""" - # print("Evaluating state with character location: " + str(characterLocation) + " and monster location: " + str(monsterLocation)) - if characterLocation is None: - characterLocation = character_location(wrld) - if monsterLocation is None: - monsterLocation = monster_location(wrld) - - number_of_move_options = len(eight_neighbors(wrld, characterLocation[0], characterLocation[1])) - - distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) - distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) - return int((distance_to_monster * 3) - distance_to_exit * 5) + number_of_move_options + returns: (x, y) tuple""" + if len(wrld.monsters) == 0: + Exception("No monster in world") + return next(iter(wrld.monsters.items()))[1][0].dx, next(iter(wrld.monsters.items()))[1][0].dy From bd81656414b41fbe498e70c1faba7497e4eb43c4 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Sat, 4 Feb 2023 17:47:42 -0500 Subject: [PATCH 20/30] auto-testing all variants and edge cases fixes --- teamNN/project1/minimax.py | 11 ++++-- teamNN/project1/variant1.py | 2 +- teamNN/project1/variant2.py | 66 +++++++++++++++++++++++------------ teamNN/project1/variant3.py | 65 ++++++++++++++++++++++++----------- teamNN/project1/variant4.py | 68 +++++++++++++++++++++++++------------ teamNN/project1/variant5.py | 21 +++++++----- teamNN/testcharacter.py | 4 +-- teamNN/utility.py | 12 +++++-- 8 files changed, 172 insertions(+), 77 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 67a7ce00..dfc3e931 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -16,7 +16,12 @@ class AI(): nodes_explored_count: int = 0 def get_next_move(self, wrld, alpha=-float("inf"), beta=float("inf")): - # Get the next move using minimax with alpha-beta pruning + # if there are no monsters, just go to the exit + if len(wrld.monsters) == 0: + path = a_star(wrld, (wrld.exitcell[0], wrld.exitcell[1]), + (character_location(wrld)[0], character_location(wrld)[1])) + return path[1] + # Get the next move using minimax with alpha-beta pruning possible_moves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) # possible_moves.append(character_location(wrld)) prioritize_moves_for_self(wrld, possible_moves) @@ -105,5 +110,7 @@ def evaluate_state(wrld, characterLocation=None, monsterLocation=None): number_of_move_options = len(eight_neighbors(wrld, characterLocation[0], characterLocation[1])) distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) + if len(wrld.monsters) == 0: + return int(distance_to_exit * 5) + number_of_move_options * 10 distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) - return int((distance_to_monster * 5) - distance_to_exit * 6) + number_of_move_options * 20 + return int((distance_to_monster * 5) - distance_to_exit * 6) + number_of_move_options * 10 diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index 3c554ceb..cd3ef3be 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -36,7 +36,7 @@ # Run! # Use this if you want to press ENTER to continue at each step -g.go(0) +g.go(300) # Use this if you want to proceed automatically # g.go(1000) diff --git a/teamNN/project1/variant2.py b/teamNN/project1/variant2.py index df8c85f8..777b6cce 100644 --- a/teamNN/project1/variant2.py +++ b/teamNN/project1/variant2.py @@ -16,24 +16,48 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(StupidMonster("stupid", # name - "S", # avatar - 3, 9 # position - )) - -# TODO Add your character -# g.add_character(InteractiveCharacter("me", # name -# "C", # avatar -# 0, 0 # position -# )) - -# Uncomment this if you want the test character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position - )) - -# Run! -g.go(200) +# random.seed(123) # TODO Change this if you want different random choices +# g = Game.fromfile('map.txt') +# g.add_monster(StupidMonster("stupid", # name +# "S", # avatar +# 3, 9 # position +# )) +# +# # TODO Add your character +# # g.add_character(InteractiveCharacter("me", # name +# # "C", # avatar +# # 0, 0 # position +# # )) +# +# # Uncomment this if you want the test character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) +# +# # Run! +# g.go(200) + + +numberOfGames = 10 +scores = [] +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 9 # position + )) + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(100) + scores.append(g.world.scores['me']) + +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/project1/variant3.py b/teamNN/project1/variant3.py index f79dcfe2..ffd12be0 100644 --- a/teamNN/project1/variant3.py +++ b/teamNN/project1/variant3.py @@ -16,23 +16,48 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(SelfPreservingMonster("selfpreserving", # name - "S", # avatar - 3, 9, # position - 1 # detection range - )) - -# TODO Add your character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position - )) -# g.add_character(InteractiveCharacter("me", # name -# "C", # avatar -# 0, 0 # position -# )) - -# Run! -g.go(200) +# random.seed(123) # TODO Change this if you want different random choices +# g = Game.fromfile('map.txt') +# g.add_monster(SelfPreservingMonster("selfpreserving", # name +# "S", # avatar +# 3, 9, # position +# 1 # detection range +# )) +# +# # TODO Add your character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) +# # g.add_character(InteractiveCharacter("me", # name +# # "C", # avatar +# # 0, 0 # position +# # )) +# +# # Run! +# g.go(200) + +numberOfGames = 10 +scores = [] +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("selfpreserving", # name + "S", # avatar + 3, 9, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(100) + scores.append(g.world.scores['me']) + +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/project1/variant4.py b/teamNN/project1/variant4.py index 3e67a849..edcdf06a 100644 --- a/teamNN/project1/variant4.py +++ b/teamNN/project1/variant4.py @@ -16,24 +16,50 @@ from testcharacter import TestCharacter # Create the game -random.seed(3) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(SelfPreservingMonster("aggressive", # name - "A", # avatar - 3, 13, # position - 2 # detection range - )) - -# TODO Add your character -# g.add_character(InteractiveCharacter("me", # name -# "C", # avatar -# 0, 0 # position -# )) - -# Uncomment this if you want the test character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position - )) -# Run! -g.go(200) +# random.seed(3) # TODO Change this if you want different random choices +# g = Game.fromfile('map.txt') +# g.add_monster(SelfPreservingMonster("aggressive", # name +# "A", # avatar +# 3, 13, # position +# 2 # detection range +# )) +# +# # TODO Add your character +# # g.add_character(InteractiveCharacter("me", # name +# # "C", # avatar +# # 0, 0 # position +# # )) +# +# # Uncomment this if you want the test character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) +# # Run! +# g.go(200) + +# Create the game +numberOfGames = 10 +scores = [] +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 2 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(100) + scores.append(g.world.scores['me']) + +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/project1/variant5.py b/teamNN/project1/variant5.py index dad834f2..25680284 100644 --- a/teamNN/project1/variant5.py +++ b/teamNN/project1/variant5.py @@ -15,26 +15,31 @@ from testcharacter import TestCharacter # Create the game +numberOfGames = 10 scores = [] -for i in range(100): +for i in range(numberOfGames): random.seed(i) g = Game.fromfile('map.txt') g.add_monster(StupidMonster("stupid", # name "S", # avatar 3, 5, # position )) - # g.add_monster(SelfPreservingMonster("aggressive", # name - # "A", # avatar - # 3, 13, # position - # 1 # detection range - # )) + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 1 # detection range + )) g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position )) - g.go(200) - scores.append(g.scores['me']) + g.go(100) + scores.append(g.world.scores['me']) average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index cea7222b..b279faae 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -51,9 +51,9 @@ def do(self, wrld): case State.WAIT_FOR_BOMB: self.move(0, 0) self.waitCount += 1 - if self.waitCount > 1: + if self.waitCount > 2: self.bombCount += 1 - self.bombCoolDown = 5 + self.bombCoolDown = 7 self.stateMachine = State.FAR_FROM_MONSTER case State.FAR_FROM_MONSTER: self.ai.reccursionDepth = 2 diff --git a/teamNN/utility.py b/teamNN/utility.py index aa10aacd..02f1c3e0 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -65,6 +65,7 @@ def eight_neighbors(wrld, x, y): def a_star(wrld, goal=None, start=None): + found = False if start is None: start = character_location(wrld) # Start at current position if goal is None: @@ -80,6 +81,7 @@ def a_star(wrld, goal=None, start=None): current = frontier.get() if current == goal: + found = True break # Check all walkable neighbors of current cell @@ -94,6 +96,8 @@ def a_star(wrld, goal=None, start=None): frontier.put(neighbor, priority) came_from[neighbor] = current + if not found: + return None # Reconstruct path using came_from dictionary currPos = goal finalPath = [] @@ -127,7 +131,8 @@ def monster_location(wrld): wrld: World object returns: (x, y) tuple""" if len(wrld.monsters) == 0: - Exception("No monster in world") + print("No monster in world") + return 1, 0 realMonsters = [] monsters = list(wrld.monsters.values()) for monster in monsters: @@ -227,7 +232,10 @@ def a_star_distance(wrld, start, goal): print("Start is not walkable!") raise Exception("Start is not walkable!", start) - return len(a_star(wrld, goal=goal, start=start)) + try: + return len(a_star(wrld, goal=goal, start=start)) + except: # When A* fails and returns None, return a large number + return 999 def monster_travel_direction(wrld): From f6c226ea81c4bbbd888433e8f3c850470dff2c08 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Sat, 4 Feb 2023 18:58:23 -0500 Subject: [PATCH 21/30] Better testall printing --- teamNN/project1/minimax.py | 1 - teamNN/project1/testall.py | 144 +++++++++++++++++++++++++++++++++++++ teamNN/testcharacter.py | 9 --- 3 files changed, 144 insertions(+), 10 deletions(-) create mode 100644 teamNN/project1/testall.py diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index dfc3e931..4aa17d9f 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -2,7 +2,6 @@ import sys sys.path.insert(0, '../bomberman') -from sys import maxsize sys.path.insert(1, '../') from utility import * diff --git a/teamNN/project1/testall.py b/teamNN/project1/testall.py new file mode 100644 index 00000000..93fe57c0 --- /dev/null +++ b/teamNN/project1/testall.py @@ -0,0 +1,144 @@ +# This is necessary to find the main code +import sys + +import pygame + +from monsters.selfpreserving_monster import SelfPreservingMonster +from teamNN.interactivecharacter import InteractiveCharacter + +sys.path.insert(0, '../../bomberman') +sys.path.insert(1, '..') + +# Import necessary stuff +import random +from game import Game +from monsters.stupid_monster import StupidMonster + +# TODO This is your code! +sys.path.insert(1, '../teamNN') +from testcharacter import TestCharacter + +numberOfGames = 10 # Number of games to play for each variant +seedOffset = 10 # Offset for the random seed +waitTimeMS = 100 # Wait time between frames in ms + +pygame.display.set_caption('V1 G1 LastS: ' + str(0)) +g = Game.fromfile('map.txt') +g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) +g.go(waitTimeMS) +score = g.world.scores['me'] +pygame.display.set_caption('V2 G1 S: ' + str(score)) +scores2 = [] +for i in range(numberOfGames): + random.seed(seedOffset + i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 9 # position + )) + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V2 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores2.append(g.world.scores['me']) + +scores3 = [] +pygame.display.set_caption('V3 G1 S: ' + str(scores2[numberOfGames - 1])) +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("selfpreserving", # name + "S", # avatar + 3, 9, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V3 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores3.append(g.world.scores['me']) + +scores4 = [] +pygame.display.set_caption('V4 G1 S: ' + str(scores3[numberOfGames - 1])) +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 2 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V4 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores4.append(g.world.scores['me']) + +scores5 = [] +pygame.display.set_caption('V5 G1 S: ' + str(scores4[numberOfGames - 1])) +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 5, # position + )) + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V5 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores5.append(g.world.scores['me']) + +print("--- Variant 1 ---") +print("Score: ", score) +print() + +average_score = sum(scores2) / len(scores2) +print("--- Variant 2 ---") +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores2 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores2) +print() + +print("--- Variant 3 ---") +average_score = sum(scores3) / len(scores3) +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores3 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores3) +print() + +print("--- Variant 4 ---") +average_score = sum(scores4) / len(scores4) +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores4 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores4) +print() + +print("--- Variant 5 ---") +average_score = sum(scores5) / len(scores5) +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores5 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores5) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index b279faae..353bdbc5 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -93,12 +93,3 @@ def valid_bomb_location(self, location, isRight): return (location[0] == 7 or location[0] == 6) and (location[1] == 6 or location[1] == 14) else: return (location[0] == 0 or location[0] == 1) and (location[1] == 2 or location[1] == 10) - - # print(getNextMove_MiniMax(wrld)) - # print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) - # nextCell = get_next_move_expectimax(wrld) - # print("Selected Move: ", nextCell) - # - # self.move(nextCell[0] - self.x, nextCell[1] - self.y) - - # if self.firstTime: From 252d38a2c97c07812da70e328bd43fb77df48321 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Yen Nhi Date: Sat, 4 Feb 2023 19:24:05 -0500 Subject: [PATCH 22/30] fix imports -> runable --- teamNN/project1/minimax.py | 2 +- teamNN/testcharacter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 2dbe445a..d2ae4ded 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -1,6 +1,6 @@ # This is necessary to find the main code import sys -from teamNN.utility import * +from utility import * sys.path.insert(0, '../bomberman') from sys import maxsize diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 4fe5ff6b..cec3c3a8 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -9,7 +9,7 @@ sys.path.insert(1, '../teamNN') from utility import * -from project1.minimax import getNextMove_MiniMax +from minimax import getNextMove_MiniMax class TestCharacter(CharacterEntity): From a37665c0286505130dad9bb0a751624a59605b8c Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Yen Nhi Date: Sat, 4 Feb 2023 19:33:14 -0500 Subject: [PATCH 23/30] change do to minimax2 --- teamNN/project1/minimaxnode.py | 2 +- teamNN/testcharacter.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/teamNN/project1/minimaxnode.py b/teamNN/project1/minimaxnode.py index 67235b39..28c4a845 100644 --- a/teamNN/project1/minimaxnode.py +++ b/teamNN/project1/minimaxnode.py @@ -7,7 +7,7 @@ from colorama import Fore, Back from PriorityQueue import PriorityQueue from sys import maxsize -from teamNN.utility import * +from utility import * class Node(object): diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index cec3c3a8..c65a5004 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -9,7 +9,7 @@ sys.path.insert(1, '../teamNN') from utility import * -from minimax import getNextMove_MiniMax +from minimaxnode import getNextMove_MiniMax2 class TestCharacter(CharacterEntity): @@ -18,7 +18,7 @@ class TestCharacter(CharacterEntity): def do(self, wrld): # print(getNextMove_MiniMax(wrld)) - nextCell = getNextMove_MiniMax(wrld) + nextCell = getNextMove_MiniMax2(wrld) print("Selected Move: ", nextCell) self.move(nextCell[0] - self.x, nextCell[1] - self.y) # if self.firstTime: From 580bab5251fec6a8cba2d4a373f36f8f3ad5ab6a Mon Sep 17 00:00:00 2001 From: ndenda Date: Mon, 6 Feb 2023 01:35:54 -0500 Subject: [PATCH 24/30] minimax, have move, get values but is to slow and does not escape on time --- teamNN/project1/minimaxnode.py | 68 +++++++++++++++++++++++----------- teamNN/project1/variant1.py | 4 +- teamNN/project1/variant2.py | 5 ++- teamNN/project1/variant3.py | 4 +- teamNN/testcharacter.py | 57 ++++++++++++++++------------ teamNN/utility.py | 1 + 6 files changed, 89 insertions(+), 50 deletions(-) diff --git a/teamNN/project1/minimaxnode.py b/teamNN/project1/minimaxnode.py index 28c4a845..626b8c7f 100644 --- a/teamNN/project1/minimaxnode.py +++ b/teamNN/project1/minimaxnode.py @@ -12,54 +12,80 @@ class Node(object): - def __init__(self, depth, player,wrld,position=None,value=0): + def __init__(self, depth, player,wrld,position,value=0): self.depth = depth self.player = player self.value = value self.wrld = wrld + self.position = position self.distancetogoal = a_star_distance_to_exit(wrld,position) + #print("checkone " + str(position)) + #print("checktwo: " + str(player)) self.children = [] - self.makechildren() + self.makechildren(self.wrld) def makechildren(self,wrld): if self.depth >= 0: - neighbordlist = self.eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + neighbordlist = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + #print("check3 " + str(character_location(wrld)[0])) + #print("check3 " + str(character_location(wrld)[1])) for neighbord in neighbordlist: + newdistance = self.distancetogoal - a_star_distance_to_exit(wrld,neighbord) - newpostion = (neighbord.x, neighbord.y) - self.children.append( Node(self.depth - 1, - self.player, wrld,newpostion, self.evaluateState(wrld,newdistance,newpostion))) + #print(neighbord) + #print(newdistance) + newpostion = (neighbord[0], neighbord[1]) + self.children.append(Node(self.depth - 1, - self.player, wrld,newpostion, evaluateState(wrld,newpostion))) - def evaluateState(wrld, distance,pos): - if distance is 0: - return maxsize - return - maxsize + +def evaluateState(wrld, pos): + exitDist = a_star_distance_to_exit(wrld,pos) + mosnterDist = euclidean_distance_to_monster(wrld,pos) + print("Pos: " + str(pos)) + print("Exit Dist: " + str(exitDist)) + print("Monster Dist: " + str(mosnterDist)) + print("Value: " + str((mosnterDist * 0.7) - exitDist)) + if exitDist == 0: + + return maxsize + return (mosnterDist * 0.7) - exitDist - def minimax(self,node, depth, player): +def minimax(node, depth, player): + + #print(node) if (depth == 0) or (abs(node.value == maxsize)): # or game wine, game lose return node.value bestvalue = maxsize * -player - for child in node.children: - child = node.children[child] - value = self.minimax(child, depth - 1, -player) + for i in range(len(node.children)): + child = node.children[i] + value = minimax(child, depth - 1, -player) if (abs(maxsize * player - value) < abs(maxsize * player - bestvalue)): bestvalue = value + return bestvalue -depthserach = 4 -currentplayer = 1 #monster - def getNextMove_MiniMax2(wrld): - if character_location != monster_location: + depthserach = 2 + currentplayer = -1 #monster + location = character_location(wrld) + #print("movemove") + + #print(character_location(wrld)) + #print(monster_location(wrld)) + + if character_location(wrld) != monster_location(wrld): + currentplayer *= -1 - node = Node(depthserach,currentplayer,wrld,character_location) - bestmove = -100 + node = Node(depthserach,currentplayer,wrld,location) + bestmove = node.position bestvalue = - currentplayer * maxsize for i in range(len(node.children)): child = node.children[i] - value = node.minimax(child,depthserach,-currentplayer) + value = minimax(child,depthserach,-currentplayer) if ( abs(currentplayer * maxsize - value)<= abs(currentplayer*maxsize-bestvalue)): bestvalue = value - bestmove = i + bestmove = child.position + print("what is returning: " + str(bestmove)) return bestmove diff --git a/teamNN/project1/variant1.py b/teamNN/project1/variant1.py index e584106c..cf8e73f5 100644 --- a/teamNN/project1/variant1.py +++ b/teamNN/project1/variant1.py @@ -36,7 +36,7 @@ # Run! # Use this if you want to press ENTER to continue at each step -g.go(0) +#g.go(0) # Use this if you want to proceed automatically -# g.go(1000) +g.go(10) diff --git a/teamNN/project1/variant2.py b/teamNN/project1/variant2.py index ad540cf9..73c1068e 100644 --- a/teamNN/project1/variant2.py +++ b/teamNN/project1/variant2.py @@ -1,7 +1,6 @@ # This is necessary to find the main code import sys -from teamNN.interactivecharacter import InteractiveCharacter sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -14,6 +13,8 @@ # TODO This is your code! sys.path.insert(1, '../teamNN') from testcharacter import TestCharacter +from interactivecharacter import InteractiveCharacter + # Create the game random.seed(123) # TODO Change this if you want different random choices @@ -36,4 +37,4 @@ )) # Run! -g.go(0) +g.go(1) diff --git a/teamNN/project1/variant3.py b/teamNN/project1/variant3.py index f4548b3d..39a52166 100644 --- a/teamNN/project1/variant3.py +++ b/teamNN/project1/variant3.py @@ -1,7 +1,7 @@ # This is necessary to find the main code import sys -from teamNN.interactivecharacter import InteractiveCharacter +#from teamNN.interactivecharacter import InteractiveCharacter sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -35,4 +35,4 @@ # )) # Run! -g.go(0) +g.go(100) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index c65a5004..ae9ae3e6 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -9,35 +9,46 @@ sys.path.insert(1, '../teamNN') from utility import * -from minimaxnode import getNextMove_MiniMax2 +from minimax import getNextMove_MiniMax +from minimaxnode import * class TestCharacter(CharacterEntity): firstTime = True - a_star_path = [] + a_star_path = [] + def do(self, wrld): - # print(getNextMove_MiniMax(wrld)) + + + print("on do function") + print(getNextMove_MiniMax2(wrld)) nextCell = getNextMove_MiniMax2(wrld) print("Selected Move: ", nextCell) self.move(nextCell[0] - self.x, nextCell[1] - self.y) - # if self.firstTime: - # print("Character at", self.x, self.y) - # print("Exit at", wrld.exitcell) - # print("Explosions:", wrld.explosions) - # print("Monsters:", wrld.monsters) - # print("Monster Location", monster_location(wrld)) - # print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) - # print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) - # print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) - # print("Manhattan distance to monster:", manhattan_distance_to_monster(wrld)) - # self.a_star_path = a_star(wrld) - # print("A* path to goal:", self.a_star_path) - # - # for point in self.a_star_path: - # # Mark path on world - # self.set_cell_color(point[0], point[1], Fore.RED + Back.GREEN) - # self.firstTime = False - # else: - # nextCell = self.a_star_path.pop(0) - # self.move(nextCell[0] - self.x, nextCell[1] - self.y) + + """ + if self.firstTime: + print("Character at", self.x, self.y) + print("Exit at", wrld.exitcell) + print("Explosions:", wrld.explosions) + print("Monsters:", wrld.monsters) + print("Monster Location", monster_location(wrld)) + print("Euclidean distance to exit:", euclidean_distance_to_exit(wrld)) + print("Manhattan distance to exit:", manhattan_distance_to_exit(wrld)) + print("Euclidean distance to monster:", euclidean_distance_to_monster(wrld)) + print("Manhattan distance to monster:", manhattan_distance_to_monster(wrld)) + self.a_star_path = a_star(wrld) + print("A* path to goal:", self.a_star_path) + + for point in self.a_star_path: + #Mark path on world + self.set_cell_color(point[0], point[1], Fore.RED + Back.GREEN) + self.firstTime = False + else: + nextCell = self.a_star_path.pop(0) + print("nextcell") + print(nextCell) + self.move(nextCell[0] - self.x, nextCell[1] - self.y) + + """ \ No newline at end of file diff --git a/teamNN/utility.py b/teamNN/utility.py index c9215da1..60623133 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -193,6 +193,7 @@ def a_star_distance_to_exit(wrld, start=None): if start is None: return len(a_star(wrld, goal=wrld.exitcell)) else: + return len(a_star(wrld, goal=wrld.exitcell, start=start)) From f986c0779e5b78b9023288b53fe21aaca53617f6 Mon Sep 17 00:00:00 2001 From: Kohmei Kadoya Date: Mon, 6 Feb 2023 02:51:51 -0500 Subject: [PATCH 25/30] this version uses a lot more bombs for defense --- teamNN/project1/minimax.py | 15 +++++++- teamNN/project1/testall.py | 8 ++-- teamNN/project1/variant3.py | 6 +-- teamNN/project1/variant4.py | 6 +-- teamNN/project1/variant5.py | 6 +-- teamNN/testcharacter.py | 77 +++++++++++++++++++++++-------------- teamNN/utility.py | 11 +++++- 7 files changed, 84 insertions(+), 45 deletions(-) diff --git a/teamNN/project1/minimax.py b/teamNN/project1/minimax.py index 4aa17d9f..0670e57b 100644 --- a/teamNN/project1/minimax.py +++ b/teamNN/project1/minimax.py @@ -19,6 +19,8 @@ def get_next_move(self, wrld, alpha=-float("inf"), beta=float("inf")): if len(wrld.monsters) == 0: path = a_star(wrld, (wrld.exitcell[0], wrld.exitcell[1]), (character_location(wrld)[0], character_location(wrld)[1])) + if path is None: # Blocked in by explosion, wait until it goes away + return character_location(wrld) return path[1] # Get the next move using minimax with alpha-beta pruning possible_moves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) @@ -34,6 +36,11 @@ def get_next_move(self, wrld, alpha=-float("inf"), beta=float("inf")): print("Pruned", round(self.nodes_explored_count / 9 ** (self.reccursionDepth + 1) * 100), "% of the tree.", self.nodes_explored_count, "nodes explored.") self.nodes_explored_count = 0 + if len(values) == 0: + return character_location(wrld) + print(max(values), values) + if min(values) < 0: + print("No good moves") return possible_moves[values.index(max(values))] def get_value_of_state(self, wrld, self_pos, monster_pos, depth, alpha, beta): @@ -41,6 +48,9 @@ def get_value_of_state(self, wrld, self_pos, monster_pos, depth, alpha, beta): if self_pos == wrld.exitcell: return 300 - depth + if wrld.explosion_at(self_pos[0], self_pos[1]): + return -100 - depth + if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]) or self_pos == monster_pos: return -100 - depth @@ -107,9 +117,10 @@ def evaluate_state(wrld, characterLocation=None, monsterLocation=None): monsterLocation = monster_location(wrld) number_of_move_options = len(eight_neighbors(wrld, characterLocation[0], characterLocation[1])) - distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) if len(wrld.monsters) == 0: return int(distance_to_exit * 5) + number_of_move_options * 10 distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) - return int((distance_to_monster * 5) - distance_to_exit * 6) + number_of_move_options * 10 + if distance_to_monster <= 2: # The monster is within one tile away + return -100 + return int((distance_to_monster * 5) - distance_to_exit * 6) + number_of_move_options * 5 diff --git a/teamNN/project1/testall.py b/teamNN/project1/testall.py index 93fe57c0..587be71f 100644 --- a/teamNN/project1/testall.py +++ b/teamNN/project1/testall.py @@ -20,7 +20,7 @@ numberOfGames = 10 # Number of games to play for each variant seedOffset = 10 # Offset for the random seed -waitTimeMS = 100 # Wait time between frames in ms +waitTimeMS = 1000 # Wait time between frames in ms pygame.display.set_caption('V1 G1 LastS: ' + str(0)) g = Game.fromfile('map.txt') @@ -51,7 +51,7 @@ scores3 = [] pygame.display.set_caption('V3 G1 S: ' + str(scores2[numberOfGames - 1])) for i in range(numberOfGames): - random.seed(i) + random.seed(seedOffset + i) g = Game.fromfile('map.txt') g.add_monster(SelfPreservingMonster("selfpreserving", # name "S", # avatar @@ -71,7 +71,7 @@ scores4 = [] pygame.display.set_caption('V4 G1 S: ' + str(scores3[numberOfGames - 1])) for i in range(numberOfGames): - random.seed(i) + random.seed(seedOffset + i) g = Game.fromfile('map.txt') g.add_monster(SelfPreservingMonster("aggressive", # name "A", # avatar @@ -91,7 +91,7 @@ scores5 = [] pygame.display.set_caption('V5 G1 S: ' + str(scores4[numberOfGames - 1])) for i in range(numberOfGames): - random.seed(i) + random.seed(seedOffset + i) g = Game.fromfile('map.txt') g.add_monster(StupidMonster("stupid", # name "S", # avatar diff --git a/teamNN/project1/variant3.py b/teamNN/project1/variant3.py index ffd12be0..1b3f20cb 100644 --- a/teamNN/project1/variant3.py +++ b/teamNN/project1/variant3.py @@ -37,10 +37,10 @@ # # Run! # g.go(200) -numberOfGames = 10 +numberOfGames = 1 scores = [] for i in range(numberOfGames): - random.seed(i) + random.seed(14) g = Game.fromfile('map.txt') g.add_monster(SelfPreservingMonster("selfpreserving", # name "S", # avatar @@ -53,7 +53,7 @@ 0, 0 # position )) - g.go(100) + g.go(1000) scores.append(g.world.scores['me']) average_score = sum(scores) / len(scores) diff --git a/teamNN/project1/variant4.py b/teamNN/project1/variant4.py index edcdf06a..9331fc58 100644 --- a/teamNN/project1/variant4.py +++ b/teamNN/project1/variant4.py @@ -39,10 +39,10 @@ # g.go(200) # Create the game -numberOfGames = 10 +numberOfGames = 1 scores = [] for i in range(numberOfGames): - random.seed(i) + random.seed(17) g = Game.fromfile('map.txt') g.add_monster(SelfPreservingMonster("aggressive", # name "A", # avatar @@ -55,7 +55,7 @@ 0, 0 # position )) - g.go(100) + g.go(1000) scores.append(g.world.scores['me']) average_score = sum(scores) / len(scores) diff --git a/teamNN/project1/variant5.py b/teamNN/project1/variant5.py index 25680284..ab93f5a3 100644 --- a/teamNN/project1/variant5.py +++ b/teamNN/project1/variant5.py @@ -15,10 +15,10 @@ from testcharacter import TestCharacter # Create the game -numberOfGames = 10 +numberOfGames = 1 scores = [] for i in range(numberOfGames): - random.seed(i) + random.seed(14) g = Game.fromfile('map.txt') g.add_monster(StupidMonster("stupid", # name "S", # avatar @@ -35,7 +35,7 @@ 0, 0 # position )) - g.go(100) + g.go(500) scores.append(g.world.scores['me']) average_score = sum(scores) / len(scores) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index 353bdbc5..bb7271d8 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -16,7 +16,6 @@ class State(Enum): START = 1 PLACE_BOMB = 2 - PLACE_BOMB_R = 3 WAIT_FOR_BOMB = 4 FAR_FROM_MONSTER = 5 CLOSE_TO_MONSTER = 6 @@ -24,8 +23,6 @@ class State(Enum): class TestCharacter(CharacterEntity): waitCount = 0 - bombCount = 0 - bombLimit = 5 bombCoolDown = 0 stateMachine = State.START ai = AI() @@ -40,19 +37,13 @@ def do(self, wrld): self.stateMachine = State.PLACE_BOMB case State.PLACE_BOMB: self.place_bomb() - self.move(1, -1) - self.waitCount = 0 - self.stateMachine = State.WAIT_FOR_BOMB - case State.PLACE_BOMB_R: - self.place_bomb() - self.move(-1, -1) + self.dodge_bomb_away_from_monster(wrld) self.waitCount = 0 self.stateMachine = State.WAIT_FOR_BOMB case State.WAIT_FOR_BOMB: self.move(0, 0) self.waitCount += 1 - if self.waitCount > 2: - self.bombCount += 1 + if self.waitCount > 1: self.bombCoolDown = 7 self.stateMachine = State.FAR_FROM_MONSTER case State.FAR_FROM_MONSTER: @@ -62,34 +53,62 @@ def do(self, wrld): self.move(nextCell[0] - self.x, nextCell[1] - self.y) print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) print("Selected Move: ", nextCell) - if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( - (nextCell[0], nextCell[1]), True): - self.stateMachine = State.PLACE_BOMB_R - if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( - (nextCell[0], nextCell[1]), False): + if self.can_place_bomb(nextCell): + self.stateMachine = State.PLACE_BOMB + if self.can_place_bomb(nextCell): self.stateMachine = State.PLACE_BOMB - if euclidean_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 7: + if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 8: self.stateMachine = State.CLOSE_TO_MONSTER case State.CLOSE_TO_MONSTER: self.ai.reccursionDepth = 3 - self.ai.isExpectimax = True + self.ai.isExpectimax = True # True if len(wrld.monsters.values()) > 1: self.ai.isExpectimax = False nextCell = self.ai.get_next_move(wrld) self.move(nextCell[0] - self.x, nextCell[1] - self.y) print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) print("Selected Move: ", nextCell) - if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( - (nextCell[0], nextCell[1]), True): - self.stateMachine = State.PLACE_BOMB_R - if self.bombCount < self.bombLimit and self.bombCoolDown <= 0 and self.valid_bomb_location( - (nextCell[0], nextCell[1]), False): + # The monster is one tile away, so we need to place a bomb to try to escape + # if len(a_star(wrld, (self.x, self.y), monster_location(wrld))) <= 3: + # self.stateMachine = State.PLACE_BOMB + if evaluate_state(wrld, character_location(wrld), monster_location(wrld)) < -20: self.stateMachine = State.PLACE_BOMB - if euclidean_distance_to_monster(wrld, (nextCell[0], nextCell[1])) > 10: + if self.can_place_bomb(nextCell): + self.stateMachine = State.PLACE_BOMB + if self.can_place_bomb(nextCell): + self.stateMachine = State.PLACE_BOMB + if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) > 8: self.stateMachine = State.FAR_FROM_MONSTER - def valid_bomb_location(self, location, isRight): - if isRight: - return (location[0] == 7 or location[0] == 6) and (location[1] == 6 or location[1] == 14) - else: - return (location[0] == 0 or location[0] == 1) and (location[1] == 2 or location[1] == 10) + def can_place_bomb(self, location, ): + if not self.bombCoolDown <= 0: + return False + return ((location[0] == 6) and (location[1] == 6 or location[1] == 14)) or ( + (location[0] == 1) and (location[1] == 2 or location[1] == 10)) + + def dodge_bomb_away_from_monster(self, wrld): + left_up_score = None + right_up_score = None + left_down_score = None + right_down_score = None + if is_cell_in_range(wrld, self.x + 1, self.y - 1) and is_cell_walkable(wrld, self.x + 1, self.y - 1): + right_up_score = len(a_star(wrld, (self.x + 1, self.y - 1), monster_location(wrld))) + if is_cell_in_range(wrld, self.x - 1, self.y - 1) and is_cell_walkable(wrld, self.x - 1, self.y - 1): + left_up_score = len(a_star(wrld, (self.x - 1, self.y - 1), monster_location(wrld))) + if is_cell_in_range(wrld, self.x + 1, self.y + 1) and is_cell_walkable(wrld, self.x + 1, self.y + 1): + right_down_score = len(a_star(wrld, (self.x + 1, self.y + 1), monster_location(wrld))) + if is_cell_in_range(wrld, self.x - 1, self.y + 1) and is_cell_walkable(wrld, self.x - 1, self.y + 1): + left_down_score = len(a_star(wrld, (self.x - 1, self.y + 1), monster_location(wrld))) + dodge_options = [left_up_score, right_up_score, left_down_score, right_down_score] + dodge_options = [x for x in dodge_options if x is not None] + print(dodge_options) + if len(dodge_options) > 0: + best_move = max(dodge_options) + if best_move == left_up_score: + self.move(-1, -1) + elif best_move == right_up_score: + self.move(1, -1) + elif best_move == left_down_score: + self.move(-1, 1) + elif best_move == right_down_score: + self.move(1, 1) diff --git a/teamNN/utility.py b/teamNN/utility.py index 02f1c3e0..6f6c942c 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -34,6 +34,15 @@ def is_cell_walkable(wrld, x, y): return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) or wrld.characters_at(x, y) +def is_cell_in_range(wrld, x, y): + """Returns True if the cell at (x, y) is in range. + wrld: World object + x: int + y: int + returns: bool""" + return wrld.width() > x >= 0 and wrld.height() > y >= 0 + + def eight_neighbors(wrld, x, y): """ Returns the walkable 8-neighbors cells of (x,y) in wrld @@ -97,7 +106,7 @@ def a_star(wrld, goal=None, start=None): came_from[neighbor] = current if not found: - return None + return [(start), (start)] # Reconstruct path using came_from dictionary currPos = goal finalPath = [] From 89f047d163a3ed9878fe8485e7c105c0c43ce7e4 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Yen Nhi Date: Mon, 6 Feb 2023 19:43:46 -0500 Subject: [PATCH 26/30] add comments and clean code --- teamNN/testcharacter.py | 45 ++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index bb7271d8..fd7d44f9 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -5,14 +5,12 @@ sys.path.insert(0, '../bomberman') # Import necessary stuff from entity import CharacterEntity -from colorama import Fore, Back -from PriorityQueue import PriorityQueue sys.path.insert(1, '../teamNN') from utility import * from project1.minimax import * - +#Organizing all states class State(Enum): START = 1 PLACE_BOMB = 2 @@ -27,50 +25,70 @@ class TestCharacter(CharacterEntity): stateMachine = State.START ai = AI() + #Function starts here def do(self, wrld): print("Current State: ", self.stateMachine) self.bombCoolDown -= 1 + #State Machines starts here match self.stateMachine: + + #Start state -> When game begins, character will jump into this state case State.START: self.move(0, 1) + #If at corner if self.x == 0 and self.y == 2: self.stateMachine = State.PLACE_BOMB + + #Place bomb, dodge and move onto WaitForBomb state case State.PLACE_BOMB: self.place_bomb() self.dodge_bomb_away_from_monster(wrld) self.waitCount = 0 self.stateMachine = State.WAIT_FOR_BOMB + + #Wait till the bomb explodes (stay still) then move on the FarFromMonster state case State.WAIT_FOR_BOMB: self.move(0, 0) self.waitCount += 1 if self.waitCount > 1: self.bombCoolDown = 7 self.stateMachine = State.FAR_FROM_MONSTER + #State where the monster is >= 8 distance from the character case State.FAR_FROM_MONSTER: + #Running minimax, depth = 2 self.ai.reccursionDepth = 2 self.ai.isExpectimax = False + + #Generate AI move nextCell = self.ai.get_next_move(wrld) + #Perform the move self.move(nextCell[0] - self.x, nextCell[1] - self.y) print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) print("Selected Move: ", nextCell) + + #If can place bomb -> placebomb if self.can_place_bomb(nextCell): self.stateMachine = State.PLACE_BOMB if self.can_place_bomb(nextCell): self.stateMachine = State.PLACE_BOMB + #If distance to monster < 8 -> close to monster if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 8: self.stateMachine = State.CLOSE_TO_MONSTER + case State.CLOSE_TO_MONSTER: + #Using expectimax self.ai.reccursionDepth = 3 - self.ai.isExpectimax = True # True + self.ai.isExpectimax = True + #If more than 1 monsters -> run if len(wrld.monsters.values()) > 1: self.ai.isExpectimax = False nextCell = self.ai.get_next_move(wrld) self.move(nextCell[0] - self.x, nextCell[1] - self.y) + print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) print("Selected Move: ", nextCell) - # The monster is one tile away, so we need to place a bomb to try to escape - # if len(a_star(wrld, (self.x, self.y), monster_location(wrld))) <= 3: - # self.stateMachine = State.PLACE_BOMB + + # Evaluating states and current position to place bomb if possible if evaluate_state(wrld, character_location(wrld), monster_location(wrld)) < -20: self.stateMachine = State.PLACE_BOMB if self.can_place_bomb(nextCell): @@ -81,16 +99,26 @@ def do(self, wrld): self.stateMachine = State.FAR_FROM_MONSTER def can_place_bomb(self, location, ): + """ + Checking if the prev bomb has exploded yet to place a new bomb in an empty location + """ if not self.bombCoolDown <= 0: return False return ((location[0] == 6) and (location[1] == 6 or location[1] == 14)) or ( (location[0] == 1) and (location[1] == 2 or location[1] == 10)) + def dodge_bomb_away_from_monster(self, wrld): + """ + Move away the position where bomb can explode or meet the monster + """ + #Score that the bomb can reach -> hit wall or hit monster left_up_score = None right_up_score = None left_down_score = None right_down_score = None + + #Calculating each direction score if is_cell_in_range(wrld, self.x + 1, self.y - 1) and is_cell_walkable(wrld, self.x + 1, self.y - 1): right_up_score = len(a_star(wrld, (self.x + 1, self.y - 1), monster_location(wrld))) if is_cell_in_range(wrld, self.x - 1, self.y - 1) and is_cell_walkable(wrld, self.x - 1, self.y - 1): @@ -99,9 +127,12 @@ def dodge_bomb_away_from_monster(self, wrld): right_down_score = len(a_star(wrld, (self.x + 1, self.y + 1), monster_location(wrld))) if is_cell_in_range(wrld, self.x - 1, self.y + 1) and is_cell_walkable(wrld, self.x - 1, self.y + 1): left_down_score = len(a_star(wrld, (self.x - 1, self.y + 1), monster_location(wrld))) + + #Summarizing up the dodge options to choose the best one dodge_options = [left_up_score, right_up_score, left_down_score, right_down_score] dodge_options = [x for x in dodge_options if x is not None] print(dodge_options) + #Return the move based on the best dodge option (which option that brings the most score) if len(dodge_options) > 0: best_move = max(dodge_options) if best_move == left_up_score: From 322c015b11be57dac694b28e63644233fa0c6b34 Mon Sep 17 00:00:00 2001 From: NhiNguyencmt8 <61450663+NhiNguyencmt8@users.noreply.github.com> Date: Mon, 6 Feb 2023 20:01:50 -0500 Subject: [PATCH 27/30] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9c543f00..3102a917 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +# Overview # +We are some Robotics Engineers trying to make an AI for the classical game Bomberman. Was it fun? Yeah. Did it work? Just find out! + # Required Software # To run Bomberman, you'll need Python 3 with the `colorama` and `pygame` From f2a259a7daa1561dfba4a08f4501da602861ccaa Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Yen Nhi Date: Fri, 24 Feb 2023 17:35:36 -0500 Subject: [PATCH 28/30] add jump point method --- teamNN/project2/README.md | 5 +- teamNN/project2/map.txt | 12 +-- teamNN/project2/minimax.py | 126 +++++++++++++++++++++++++++++ teamNN/project2/minimaxnode.py | 91 +++++++++++++++++++++ teamNN/project2/testall.py | 144 +++++++++++++++++++++++++++++++++ teamNN/project2/variant1.py | 26 +++++- teamNN/project2/variant2.py | 64 +++++++++++---- teamNN/project2/variant3.py | 64 +++++++++++---- teamNN/project2/variant4.py | 66 +++++++++++---- teamNN/project2/variant5.py | 45 ++++++----- teamNN/testcharacter.py | 9 +-- teamNN/utility.py | 144 ++++++++++++++++++++++++++++++++- 12 files changed, 708 insertions(+), 88 deletions(-) create mode 100644 teamNN/project2/minimax.py create mode 100644 teamNN/project2/minimaxnode.py create mode 100644 teamNN/project2/testall.py diff --git a/teamNN/project2/README.md b/teamNN/project2/README.md index 8cd05c2e..89e47f20 100644 --- a/teamNN/project2/README.md +++ b/teamNN/project2/README.md @@ -1,15 +1,14 @@ # Your goal # In this scenario, you must plan the route of your agent from the top-left -corner to the exit. However, your route is obstructed - you need to use the bomb -to create a path to the exit. +corner to the exit. ## Variant 1: Alone in the world ## In the first variant of this scenario, the world is deterministic and your agent is alone in the environment. -## Variant 2: Random monster ## +## Variant 2: Stupid monster ## In the second variant of this scenario, a stupid monster is present. The monster chooses its next cell uniformly at random among the possible reachable cells. diff --git a/teamNN/project2/map.txt b/teamNN/project2/map.txt index 62dab47c..643952c1 100644 --- a/teamNN/project2/map.txt +++ b/teamNN/project2/map.txt @@ -1,24 +1,24 @@ max_time 5000 -bomb_time 10 -expl_duration 2 +bomb_time 1 +expl_duration 1 expl_range 4 +--------+ | | | | | | -|WWWWWWWW| +|WWWW | | | | | | | -|WWWWWWWW| +| WWWW| | | | | | | -|WWWWWWWW| +|WWWW | | | | | | | -|WWWWWWWW| +| WWWW| | | | | | E| diff --git a/teamNN/project2/minimax.py b/teamNN/project2/minimax.py new file mode 100644 index 00000000..0670e57b --- /dev/null +++ b/teamNN/project2/minimax.py @@ -0,0 +1,126 @@ +# This is necessary to find the main code +import sys + +sys.path.insert(0, '../bomberman') + +sys.path.insert(1, '../') +from utility import * + + +class AI(): + isExpectimax: bool = False + reccursionDepth: int = 3 + reward_max: int = 50 + reward_min: int = -50 + nodes_explored_count: int = 0 + + def get_next_move(self, wrld, alpha=-float("inf"), beta=float("inf")): + # if there are no monsters, just go to the exit + if len(wrld.monsters) == 0: + path = a_star(wrld, (wrld.exitcell[0], wrld.exitcell[1]), + (character_location(wrld)[0], character_location(wrld)[1])) + if path is None: # Blocked in by explosion, wait until it goes away + return character_location(wrld) + return path[1] + # Get the next move using minimax with alpha-beta pruning + possible_moves = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + # possible_moves.append(character_location(wrld)) + prioritize_moves_for_self(wrld, possible_moves) + values = [] + for move in possible_moves: + value = self.get_value_of_state(wrld, move, monster_location(wrld), 0, alpha, beta) + values.append(value) + alpha = max(alpha, value) + if alpha >= beta: + break + print("Pruned", round(self.nodes_explored_count / 9 ** (self.reccursionDepth + 1) * 100), "% of the tree.", + self.nodes_explored_count, "nodes explored.") + self.nodes_explored_count = 0 + if len(values) == 0: + return character_location(wrld) + print(max(values), values) + if min(values) < 0: + print("No good moves") + return possible_moves[values.index(max(values))] + + def get_value_of_state(self, wrld, self_pos, monster_pos, depth, alpha, beta): + self.nodes_explored_count += 1 + if self_pos == wrld.exitcell: + return 300 - depth + + if wrld.explosion_at(self_pos[0], self_pos[1]): + return -100 - depth + + if self_pos in eight_neighbors(wrld, monster_pos[0], monster_pos[1]) or self_pos == monster_pos: + return -100 - depth + + if depth == self.reccursionDepth: + return evaluate_state(wrld, self_pos, monster_pos) - depth + + if depth % 2 == 1: # Max Node (self) + value = -float("inf") + possible_moves = eight_neighbors(wrld, self_pos[0], self_pos[1]) + possible_moves.append(self_pos) + for self_move in possible_moves: + value = max(value, self.get_value_of_state(wrld, self_move, monster_pos, depth + 1, alpha, beta)) + alpha = max(alpha, value) + if alpha >= beta: + break + return value + else: + + if not self.isExpectimax: # If modeling monster as Minimax + value = float("inf") + possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) + possible_moves.append(monster_pos) + for monster_move in possible_moves: + value = min(value, self.get_value_of_state(wrld, self_pos, monster_move, depth + 1, alpha, beta)) + beta = min(beta, value) + if alpha >= beta: + break + return value + else: # If Expectimax + value = 0 + probability_total = 0 + possible_moves = eight_neighbors(wrld, monster_pos[0], monster_pos[1]) + possible_moves.append(monster_pos) + for monster_move in possible_moves: + probability = 1 / len(possible_moves) + probability_total += probability + value += self.get_value_of_state(wrld, self_pos, monster_move, depth + 1, alpha, + beta) * probability + remaining_probability = 1 - probability_total + if value + (remaining_probability * self.reward_max) < alpha: + # print("Pruned expected node with remaining probability", remaining_probability) + break + return value + + +def prioritize_moves_for_self(wrld, possible_moves): + # Prioritize moves that are closer to the exit to prune the tree more + possible_moves.sort(key=lambda move: euclidean_distance_to_exit(wrld, move)) + + +def prioritize_moves_for_monster(wrld, possible_moves): + # Prioritize moves that are closer to the exit to prune the tree more + possible_moves.sort(key=lambda move: euclidean_dist(move, character_location(wrld))) + + +def evaluate_state(wrld, characterLocation=None, monsterLocation=None): + """Returns a value for the current world state. + wrld: World object + returns: float""" + # print("Evaluating state with character location: " + str(characterLocation) + " and monster location: " + str(monsterLocation)) + if characterLocation is None: + characterLocation = character_location(wrld) + if monsterLocation is None: + monsterLocation = monster_location(wrld) + + number_of_move_options = len(eight_neighbors(wrld, characterLocation[0], characterLocation[1])) + distance_to_exit = a_star_distance(wrld, characterLocation, wrld.exitcell) + if len(wrld.monsters) == 0: + return int(distance_to_exit * 5) + number_of_move_options * 10 + distance_to_monster = a_star_distance(wrld, characterLocation, monsterLocation) + if distance_to_monster <= 2: # The monster is within one tile away + return -100 + return int((distance_to_monster * 5) - distance_to_exit * 6) + number_of_move_options * 5 diff --git a/teamNN/project2/minimaxnode.py b/teamNN/project2/minimaxnode.py new file mode 100644 index 00000000..7fbb8ff3 --- /dev/null +++ b/teamNN/project2/minimaxnode.py @@ -0,0 +1,91 @@ +# This is necessary to find the main code +import sys + +sys.path.insert(0, '../bomberman') +# Import necessary stuff +from testcharacter import CharacterEntity +from colorama import Fore, Back +from PriorityQueue import PriorityQueue +from sys import maxsize +from utility import * + + +class Node(object): + + def __init__(self, depth, player,wrld,position,value=0): + self.depth = depth + self.player = player + self.value = value + self.wrld = wrld + self.position = position + self.distancetogoal = a_star_distance_to_exit(wrld,position) + #print("checkone " + str(position)) + #print("checktwo: " + str(player)) + self.children = [] + self.makechildren(self.wrld) + + def makechildren(self,wrld): + if self.depth >= 0: + neighbordlist = eight_neighbors(wrld, character_location(wrld)[0], character_location(wrld)[1]) + #print("check3 " + str(character_location(wrld)[0])) + #print("check3 " + str(character_location(wrld)[1])) + for neighbord in neighbordlist: + + newdistance = self.distancetogoal - a_star_distance_to_exit(wrld,neighbord) + #print(neighbord) + #print(newdistance) + newpostion = (neighbord[0], neighbord[1]) + self.children.append(Node(self.depth - 1, - self.player, wrld,newpostion, evaluateState(wrld,newpostion))) + + + +def evaluateState(wrld, pos): + exitDist = a_star_distance_to_exit(wrld, pos) + mosnterDist = euclidean_distance_to_monster(wrld,pos) + print("Pos: " + str(pos)) + print("Exit Dist: " + str(exitDist)) + print("Monster Dist: " + str(mosnterDist)) + print("Value: " + str((mosnterDist * 0.7) - exitDist)) + if exitDist == 0: + + return maxsize + return (mosnterDist * 0.7) - exitDist + +def minimax(node, depth, player): + + #print(node) + if (depth == 0) or (abs(node.value == maxsize)): # or game wine, game lose + return node.value + bestvalue = maxsize * -player + + for i in range(len(node.children)): + child = node.children[i] + value = minimax(child, depth - 1, -player) + if (abs(maxsize * player - value) < abs(maxsize * player - bestvalue)): + bestvalue = value + + return bestvalue + +def getNextMove_MiniMax2(wrld): + depthserach = 2 + currentplayer = -1 #monster + location = character_location(wrld) + #print("movemove") + + #print(character_location(wrld)) + #print(monster_location(wrld)) + + if character_location(wrld) != monster_location(wrld): + + currentplayer *= -1 + node = Node(depthserach,currentplayer,wrld,location) + bestmove = node.position + bestvalue = - currentplayer * maxsize + for i in range(len(node.children)): + child = node.children[i] + value = minimax(child,depthserach,-currentplayer) + if ( abs(currentplayer * maxsize - value)<= abs(currentplayer*maxsize-bestvalue)): + bestvalue = value + bestmove = child.position + print("what is returning: " + str(bestmove)) + return bestmove diff --git a/teamNN/project2/testall.py b/teamNN/project2/testall.py new file mode 100644 index 00000000..587be71f --- /dev/null +++ b/teamNN/project2/testall.py @@ -0,0 +1,144 @@ +# This is necessary to find the main code +import sys + +import pygame + +from monsters.selfpreserving_monster import SelfPreservingMonster +from teamNN.interactivecharacter import InteractiveCharacter + +sys.path.insert(0, '../../bomberman') +sys.path.insert(1, '..') + +# Import necessary stuff +import random +from game import Game +from monsters.stupid_monster import StupidMonster + +# TODO This is your code! +sys.path.insert(1, '../teamNN') +from testcharacter import TestCharacter + +numberOfGames = 10 # Number of games to play for each variant +seedOffset = 10 # Offset for the random seed +waitTimeMS = 1000 # Wait time between frames in ms + +pygame.display.set_caption('V1 G1 LastS: ' + str(0)) +g = Game.fromfile('map.txt') +g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) +g.go(waitTimeMS) +score = g.world.scores['me'] +pygame.display.set_caption('V2 G1 S: ' + str(score)) +scores2 = [] +for i in range(numberOfGames): + random.seed(seedOffset + i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 9 # position + )) + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V2 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores2.append(g.world.scores['me']) + +scores3 = [] +pygame.display.set_caption('V3 G1 S: ' + str(scores2[numberOfGames - 1])) +for i in range(numberOfGames): + random.seed(seedOffset + i) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("selfpreserving", # name + "S", # avatar + 3, 9, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V3 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores3.append(g.world.scores['me']) + +scores4 = [] +pygame.display.set_caption('V4 G1 S: ' + str(scores3[numberOfGames - 1])) +for i in range(numberOfGames): + random.seed(seedOffset + i) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 2 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V4 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores4.append(g.world.scores['me']) + +scores5 = [] +pygame.display.set_caption('V5 G1 S: ' + str(scores4[numberOfGames - 1])) +for i in range(numberOfGames): + random.seed(seedOffset + i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 5, # position + )) + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(waitTimeMS) + pygame.display.set_caption("V5 G%i S:%i" % (i + 2, g.world.scores['me'])) + scores5.append(g.world.scores['me']) + +print("--- Variant 1 ---") +print("Score: ", score) +print() + +average_score = sum(scores2) / len(scores2) +print("--- Variant 2 ---") +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores2 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores2) +print() + +print("--- Variant 3 ---") +average_score = sum(scores3) / len(scores3) +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores3 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores3) +print() + +print("--- Variant 4 ---") +average_score = sum(scores4) / len(scores4) +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores4 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores4) +print() + +print("--- Variant 5 ---") +average_score = sum(scores5) / len(scores5) +print("Played ", numberOfGames, " games with an average score of ", average_score, "Won ", + len([score for score in scores5 if score > 0]), " games / ", numberOfGames) +print("Scores: ", scores5) diff --git a/teamNN/project2/variant1.py b/teamNN/project2/variant1.py index 6d1d284c..1770f806 100644 --- a/teamNN/project2/variant1.py +++ b/teamNN/project2/variant1.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -7,18 +8,35 @@ from game import Game # TODO This is your code! -sys.path.insert(1, '../teamNN') +sys.path.insert(2, '../teamNN') + +# Uncomment this if you want the empty test character from testcharacter import TestCharacter +# Uncomment this if you want the interactive character +from interactivecharacter import InteractiveCharacter # Create the game g = Game.fromfile('map.txt') # TODO Add your character -g.add_character(TestCharacter("me", # name + +# Uncomment this if you want the test character +g.add_character(TestCharacter("me", # name "C", # avatar 0, 0 # position -)) + )) + +# Uncomment this if you want the interactive character +# g.add_character(InteractiveCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) # Run! -g.go() + +# Use this if you want to press ENTER to continue at each step +#g.go(0) + +# Use this if you want to proceed automatically +g.go(300) diff --git a/teamNN/project2/variant2.py b/teamNN/project2/variant2.py index 306f08e8..ee863f10 100644 --- a/teamNN/project2/variant2.py +++ b/teamNN/project2/variant2.py @@ -1,5 +1,7 @@ # This is necessary to find the main code import sys + + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -11,20 +13,52 @@ # TODO This is your code! sys.path.insert(1, '../teamNN') from testcharacter import TestCharacter +from interactivecharacter import InteractiveCharacter + # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(StupidMonster("stupid", # name - "S", # avatar - 3, 9 # position -)) - -# TODO Add your character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position -)) - -# Run! -g.go() +# random.seed(123) # TODO Change this if you want different random choices +# g = Game.fromfile('map.txt') +# g.add_monster(StupidMonster("stupid", # name +# "S", # avatar +# 3, 9 # position +# )) +# +# # TODO Add your character +# # g.add_character(InteractiveCharacter("me", # name +# # "C", # avatar +# # 0, 0 # position +# # )) +# +# # Uncomment this if you want the test character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) +# +# # Run! +# g.go(200) + + +numberOfGames = 10 +scores = [] +for i in range(numberOfGames): + random.seed(i) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 9 # position + )) + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(100) + scores.append(g.world.scores['me']) + +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/project2/variant3.py b/teamNN/project2/variant3.py index 3d229181..8f1897dc 100644 --- a/teamNN/project2/variant3.py +++ b/teamNN/project2/variant3.py @@ -1,5 +1,8 @@ # This is necessary to find the main code import sys + +#from teamNN.interactivecharacter import InteractiveCharacter + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -13,19 +16,48 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(SelfPreservingMonster("selfpreserving", # name - "S", # avatar - 3, 9, # position - 1 # detection range -)) - -# TODO Add your character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position -)) - -# Run! -g.go() +# random.seed(123) # TODO Change this if you want different random choices +# g = Game.fromfile('map.txt') +# g.add_monster(SelfPreservingMonster("selfpreserving", # name +# "S", # avatar +# 3, 9, # position +# 1 # detection range +# )) +# +# # TODO Add your character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) +# # g.add_character(InteractiveCharacter("me", # name +# # "C", # avatar +# # 0, 0 # position +# # )) +# +# # Run! +# g.go(200) + +numberOfGames = 1 +scores = [] +for i in range(numberOfGames): + random.seed(14) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("selfpreserving", # name + "S", # avatar + 3, 9, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(100) + scores.append(g.world.scores['me']) + +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/project2/variant4.py b/teamNN/project2/variant4.py index 81e4a631..9331fc58 100644 --- a/teamNN/project2/variant4.py +++ b/teamNN/project2/variant4.py @@ -1,5 +1,8 @@ # This is necessary to find the main code import sys + +from teamNN.interactivecharacter import InteractiveCharacter + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -13,19 +16,50 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(SelfPreservingMonster("aggressive", # name - "A", # avatar - 3, 13, # position - 2 # detection range -)) - -# TODO Add your character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position -)) - -# Run! -g.go() +# random.seed(3) # TODO Change this if you want different random choices +# g = Game.fromfile('map.txt') +# g.add_monster(SelfPreservingMonster("aggressive", # name +# "A", # avatar +# 3, 13, # position +# 2 # detection range +# )) +# +# # TODO Add your character +# # g.add_character(InteractiveCharacter("me", # name +# # "C", # avatar +# # 0, 0 # position +# # )) +# +# # Uncomment this if you want the test character +# g.add_character(TestCharacter("me", # name +# "C", # avatar +# 0, 0 # position +# )) +# # Run! +# g.go(200) + +# Create the game +numberOfGames = 1 +scores = [] +for i in range(numberOfGames): + random.seed(17) + g = Game.fromfile('map.txt') + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 2 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) + + g.go(1000) + scores.append(g.world.scores['me']) + +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/project2/variant5.py b/teamNN/project2/variant5.py index 96f1503b..21d19182 100644 --- a/teamNN/project2/variant5.py +++ b/teamNN/project2/variant5.py @@ -1,5 +1,6 @@ # This is necessary to find the main code import sys + sys.path.insert(0, '../../bomberman') sys.path.insert(1, '..') @@ -14,23 +15,31 @@ from testcharacter import TestCharacter # Create the game -random.seed(123) # TODO Change this if you want different random choices -g = Game.fromfile('map.txt') -g.add_monster(StupidMonster("stupid", # name - "S", # avatar - 3, 5, # position -)) -g.add_monster(SelfPreservingMonster("aggressive", # name - "A", # avatar - 3, 13, # position - 2 # detection range -)) +numberOfGames = 1 +scores = [] +for i in range(numberOfGames): + random.seed(14) + g = Game.fromfile('map.txt') + g.add_monster(StupidMonster("stupid", # name + "S", # avatar + 3, 5, # position + )) + g.add_monster(SelfPreservingMonster("aggressive", # name + "A", # avatar + 3, 13, # position + 1 # detection range + )) + + g.add_character(TestCharacter("me", # name + "C", # avatar + 0, 0 # position + )) -# TODO Add your character -g.add_character(TestCharacter("me", # name - "C", # avatar - 0, 0 # position -)) + g.go(1000) + scores.append(g.world.scores['me']) -# Run! -g.go() +average_score = sum(scores) / len(scores) +print("Played ", numberOfGames, " games with an average score of ", average_score) +# Print the number of score where the score is over 0 +print("Number of games won: ", len([score for score in scores if score > 0])) +print("Scores: ", scores) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index fd7d44f9..b93d77fc 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -24,9 +24,9 @@ class TestCharacter(CharacterEntity): bombCoolDown = 0 stateMachine = State.START ai = AI() - #Function starts here def do(self, wrld): + monsterNum = len(wrld.monsters.values()) print("Current State: ", self.stateMachine) self.bombCoolDown -= 1 #State Machines starts here @@ -78,10 +78,7 @@ def do(self, wrld): case State.CLOSE_TO_MONSTER: #Using expectimax self.ai.reccursionDepth = 3 - self.ai.isExpectimax = True - #If more than 1 monsters -> run - if len(wrld.monsters.values()) > 1: - self.ai.isExpectimax = False + self.ai.isExpectimax = False nextCell = self.ai.get_next_move(wrld) self.move(nextCell[0] - self.x, nextCell[1] - self.y) @@ -93,8 +90,6 @@ def do(self, wrld): self.stateMachine = State.PLACE_BOMB if self.can_place_bomb(nextCell): self.stateMachine = State.PLACE_BOMB - if self.can_place_bomb(nextCell): - self.stateMachine = State.PLACE_BOMB if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) > 8: self.stateMachine = State.FAR_FROM_MONSTER diff --git a/teamNN/utility.py b/teamNN/utility.py index 7bb2b30c..59b3417a 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -16,6 +16,8 @@ """ from PriorityQueue import PriorityQueue +prevMove = [0, 0] + def euclidean_dist(point_one, point_two): """Returns the euclidean distance between two points. @@ -31,7 +33,9 @@ def is_cell_walkable(wrld, x, y): x: int y: int returns: bool""" - return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) or wrld.characters_at(x, y) + if is_cell_in_range(wrld, x, y): + return wrld.exit_at(x, y) or wrld.empty_at(x, y) or wrld.monsters_at(x, y) or wrld.characters_at(x, y) + return False def is_cell_in_range(wrld, x, y): @@ -74,6 +78,140 @@ def eight_neighbors(wrld, x, y): def a_star(wrld, goal=None, start=None): + print("Astar is thinking") + found = False + + if start is None: + start = character_location(wrld) # Start at current position + if goal is None: + goal = wrld.exitcell # Goal is exit cell + + cost_so_far = {start: 0} # Dictionary of costs to get to each cell + came_from = {start: None} # Dictionary of where each cell came from + + frontier = PriorityQueue() # Priority queue of cells to visit + frontier.put(start, 0) + + while not frontier.empty(): + current = frontier.get() + if current == goal: + found = True + break + + successors = identifySuccessors(current, wrld, goal) + + # Check all walkable neighbors of current cell + for successor in successors: + # Calculate cost to get to neighbor - 1 or 1.4 + diagonal = 0 + if successor in cost_so_far and \ + successor[0] - current[0] != 0 and successor[1] - current[1] != 0: + diagonal += 2 + new_cost = cost_so_far[current] + euclidean_dist(current, successor) + diagonal + + # If neighbor has no path or new path is better, update path + if successor not in cost_so_far or new_cost < cost_so_far[successor]: + cost_so_far[successor] = new_cost + priority = new_cost + euclidean_dist(successor, goal) + frontier.put(successor, priority) + came_from[successor] = current + + if not found: + return [(start), (start)] + # Reconstruct path using came_from dictionary + currPos = goal + finalPath = [] + finalPath.append(goal) + while currPos != start: + currPos = came_from[currPos] + finalPath.append(currPos) + + finalPath.reverse() + return finalPath + + +def identifySuccessors(current, wrld, goal): + succesors = [] + for neighbour in eight_neighbors(wrld, current[0], current[1]): + dx = neighbour[0] - current[0] + dy = neighbour[1] - current[1] + direction = [dx, dy] + pointfump = jump(current, direction[0], direction[1], wrld, goal) + if pointfump: + succesors.append(pointfump) + return succesors + + +def jump(current, dx, dy, wrld, goal): + nx = current[0] + dx + ny = current[1] + dy + + if (nx, ny) == goal: + return (nx, ny) + + # Original position + ox = nx + oy = ny + original = (ox, oy) + + if dx != 0 and dy != 0: + while True: + if is_cell_walkable(wrld, ox - dx, oy + dy) \ + and not is_cell_walkable(wrld, ox + dx, oy - dy) \ + or is_cell_walkable(wrld, ox + dx, oy - dy) \ + and not is_cell_walkable(wrld, ox - dx, oy + dy): + return ox, oy + if jump(original, dx, 0, wrld, goal) or jump(original, 0, dy, wrld, goal): + return ox, oy + + ox += dx + oy += dy + + if not is_cell_walkable(wrld, ox, oy): + return None + if (ox,oy) == goal: + return (ox,oy) + else: + #Moving in y-axis + if dx != 0: + while True: + if is_cell_walkable(wrld, ox + dx, ny + 1) \ + and not is_cell_walkable(wrld, ox, ny + 1) \ + or is_cell_walkable(wrld, ox + dx, ny - 1) \ + and not is_cell_walkable(wrld, ox, ny - 1): + return ox, ny + ox += dx + if (ox,ny) == goal: + return (ox,ny) + if not is_cell_walkable(wrld,ox,ny): + return None + #Moving in x-axis + else: + while True: + if is_cell_walkable(wrld, nx + 1, oy + dy) \ + and not is_cell_walkable(wrld, nx + 1, oy) \ + or is_cell_walkable(wrld, nx - 1, oy + dy) \ + and not is_cell_walkable(wrld, nx - 1, oy): + return nx, oy + oy += dy + if (nx,oy) == goal: + return (nx,oy) + if not is_cell_walkable(wrld,nx,oy): + return None + return jump(current, dx, dy, wrld, goal) + + +def checkinsamedirection(prevmove, current, neighbor): + if abs(prevmove[0] - current[0]) == abs(current[0] - neighbor[0]): + return True + elif abs(prevmove[1] - current[1]) == abs(current[1] - neighbor[1]): + return True + else: + return False + + +def a_star2(wrld, goal=None, start=None): + # Original astar found = False if start is None: start = character_location(wrld) # Start at current position @@ -107,6 +245,7 @@ def a_star(wrld, goal=None, start=None): if not found: return [(start), (start)] + # Reconstruct path using came_from dictionary currPos = goal finalPath = [] @@ -114,7 +253,6 @@ def a_star(wrld, goal=None, start=None): while currPos != start: currPos = came_from[currPos] finalPath.append(currPos) - finalPath.reverse() return finalPath @@ -213,7 +351,7 @@ def a_star_distance_to_exit(wrld, start=None): if start is None: return len(a_star(wrld, goal=wrld.exitcell)) else: - + return len(a_star(wrld, goal=wrld.exitcell, start=start)) From 248fd9453fd5360838b7c4f52df58b04057cad84 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Yen Nhi Date: Thu, 2 Mar 2023 15:30:12 -0500 Subject: [PATCH 29/30] it's working --- Bomberman/world.py | 2 ++ teamNN/project2/map.txt | 10 ++++---- teamNN/project2/variant1.py | 2 +- teamNN/project2/variant5.py | 2 +- teamNN/testcharacter.py | 48 ++++++++++++++++++++++++++++++++----- teamNN/utility.py | 34 ++++++++++++++------------ 6 files changed, 70 insertions(+), 28 deletions(-) diff --git a/Bomberman/world.py b/Bomberman/world.py index 9a4458d2..e121248a 100644 --- a/Bomberman/world.py +++ b/Bomberman/world.py @@ -389,3 +389,5 @@ def update_scores(self): for k,clist in self.characters.items(): for c in clist: self.scores[c.name] = self.scores[c.name] + 1 + + diff --git a/teamNN/project2/map.txt b/teamNN/project2/map.txt index 643952c1..c502e27c 100644 --- a/teamNN/project2/map.txt +++ b/teamNN/project2/map.txt @@ -1,24 +1,24 @@ max_time 5000 bomb_time 1 expl_duration 1 -expl_range 4 +expl_range 10 +--------+ | | | | | | -|WWWW | +|WWWWWWWW| | | | | | | -| WWWW| +|WWWWWWWW| | | | | | | -|WWWW | +|WWWWWWWW| | | | | | | -| WWWW| +|WWWWWWWW| | | | | | E| diff --git a/teamNN/project2/variant1.py b/teamNN/project2/variant1.py index 1770f806..c27991a7 100644 --- a/teamNN/project2/variant1.py +++ b/teamNN/project2/variant1.py @@ -39,4 +39,4 @@ #g.go(0) # Use this if you want to proceed automatically -g.go(300) +g.go(100) diff --git a/teamNN/project2/variant5.py b/teamNN/project2/variant5.py index 21d19182..dfcf85ba 100644 --- a/teamNN/project2/variant5.py +++ b/teamNN/project2/variant5.py @@ -35,7 +35,7 @@ 0, 0 # position )) - g.go(1000) + g.go(10) scores.append(g.world.scores['me']) average_score = sum(scores) / len(scores) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index b93d77fc..af7d4749 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -17,6 +17,7 @@ class State(Enum): WAIT_FOR_BOMB = 4 FAR_FROM_MONSTER = 5 CLOSE_TO_MONSTER = 6 + EXPLORATION = 7; class TestCharacter(CharacterEntity): @@ -54,11 +55,42 @@ def do(self, wrld): self.bombCoolDown = 7 self.stateMachine = State.FAR_FROM_MONSTER #State where the monster is >= 8 distance from the character + case State.EXPLORATION: + # print("Exploring") + # w = wrld.width + # h = wrld.height + # print(w) + # print(h) + # + # for x in range(7): + # for y in range(17): + # + # if wrld.wall_at(x, y): + # print(x, y) + # path = a_star(wrld, (x, y), character_location(wrld)) + # if path.pop(len(path) - 1) != wrld.exitcell: + # goal = (x, y) + # break + if a_star_distance_to_monster(wrld, (self.x, self.y + 1)) < 8 and len(wrld.monsters) > 0: + self.stateMachine = State.CLOSE_TO_MONSTER + + if is_cell_walkable(wrld, self.x, self.y + 1): + print("cell walkable") + self.move(self.x, self.y + 1) + else: + self.stateMachine = State.PLACE_BOMB + case State.FAR_FROM_MONSTER: #Running minimax, depth = 2 self.ai.reccursionDepth = 2 self.ai.isExpectimax = False + print("a_star distance", a_star_distance_to_exit(wrld, character_location(wrld))) + + if a_star_distance_to_exit(wrld, character_location(wrld)) < 0: + print("No path to exit") + self.stateMachine = State.EXPLORATION + #Generate AI move nextCell = self.ai.get_next_move(wrld) #Perform the move @@ -66,19 +98,22 @@ def do(self, wrld): print("Score of current world", evaluate_state(wrld, character_location(wrld), monster_location(wrld))) print("Selected Move: ", nextCell) - #If can place bomb -> placebomb - if self.can_place_bomb(nextCell): - self.stateMachine = State.PLACE_BOMB - if self.can_place_bomb(nextCell): - self.stateMachine = State.PLACE_BOMB + # #If can place bomb -> placebomb + # if self.can_place_bomb(nextCell): + # self.stateMachine = State.PLACE_BOMB #If distance to monster < 8 -> close to monster - if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 8: + if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 8 and len(wrld.monsters) > 0: self.stateMachine = State.CLOSE_TO_MONSTER case State.CLOSE_TO_MONSTER: #Using expectimax self.ai.reccursionDepth = 3 self.ai.isExpectimax = False + print("a_star distance", a_star_distance_to_exit(wrld, character_location(wrld))) + + if a_star_distance_to_exit(wrld, character_location(wrld)) < 0: + print("No path to exit") + self.stateMachine = State.PLACE_BOMB nextCell = self.ai.get_next_move(wrld) self.move(nextCell[0] - self.x, nextCell[1] - self.y) @@ -93,6 +128,7 @@ def do(self, wrld): if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) > 8: self.stateMachine = State.FAR_FROM_MONSTER + def can_place_bomb(self, location, ): """ Checking if the prev bomb has exploded yet to place a new bomb in an empty location diff --git a/teamNN/utility.py b/teamNN/utility.py index 59b3417a..9464d65a 100644 --- a/teamNN/utility.py +++ b/teamNN/utility.py @@ -117,11 +117,10 @@ def a_star(wrld, goal=None, start=None): came_from[successor] = current if not found: - return [(start), (start)] + return [start, start] # Reconstruct path using came_from dictionary currPos = goal - finalPath = [] - finalPath.append(goal) + finalPath = [goal] while currPos != start: currPos = came_from[currPos] finalPath.append(currPos) @@ -169,10 +168,10 @@ def jump(current, dx, dy, wrld, goal): if not is_cell_walkable(wrld, ox, oy): return None - if (ox,oy) == goal: - return (ox,oy) + if (ox, oy) == goal: + return (ox, oy) else: - #Moving in y-axis + # Moving in y-axis if dx != 0: while True: if is_cell_walkable(wrld, ox + dx, ny + 1) \ @@ -181,11 +180,11 @@ def jump(current, dx, dy, wrld, goal): and not is_cell_walkable(wrld, ox, ny - 1): return ox, ny ox += dx - if (ox,ny) == goal: - return (ox,ny) - if not is_cell_walkable(wrld,ox,ny): + if (ox, ny) == goal: + return (ox, ny) + if not is_cell_walkable(wrld, ox, ny): return None - #Moving in x-axis + # Moving in x-axis else: while True: if is_cell_walkable(wrld, nx + 1, oy + dy) \ @@ -194,9 +193,9 @@ def jump(current, dx, dy, wrld, goal): and not is_cell_walkable(wrld, nx - 1, oy): return nx, oy oy += dy - if (nx,oy) == goal: - return (nx,oy) - if not is_cell_walkable(wrld,nx,oy): + if (nx, oy) == goal: + return (nx, oy) + if not is_cell_walkable(wrld, nx, oy): return None return jump(current, dx, dy, wrld, goal) @@ -351,8 +350,13 @@ def a_star_distance_to_exit(wrld, start=None): if start is None: return len(a_star(wrld, goal=wrld.exitcell)) else: - - return len(a_star(wrld, goal=wrld.exitcell, start=start)) + path = a_star(wrld, goal=wrld.exitcell, start=start); + # print("astar is") + # print(path.pop(len(path)-1)) + if path.pop(len(path) - 1) != wrld.exitcell: + return -1 + else: + return len(path) def a_star_distance_to_monster(wrld, start=None): From aaf8a71867034128607a9098d3d9e64fc1061238 Mon Sep 17 00:00:00 2001 From: Nguyen Ngoc Yen Nhi Date: Fri, 3 Mar 2023 14:43:13 -0500 Subject: [PATCH 30/30] minor edits in the placebomb --- teamNN/testcharacter.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/teamNN/testcharacter.py b/teamNN/testcharacter.py index af7d4749..55462d08 100644 --- a/teamNN/testcharacter.py +++ b/teamNN/testcharacter.py @@ -36,9 +36,9 @@ def do(self, wrld): #Start state -> When game begins, character will jump into this state case State.START: self.move(0, 1) - #If at corner - if self.x == 0 and self.y == 2: - self.stateMachine = State.PLACE_BOMB + # #If at corner + # if self.x == 0 and self.y == 2: + self.stateMachine = State.PLACE_BOMB #Place bomb, dodge and move onto WaitForBomb state case State.PLACE_BOMB: @@ -99,8 +99,8 @@ def do(self, wrld): print("Selected Move: ", nextCell) # #If can place bomb -> placebomb - # if self.can_place_bomb(nextCell): - # self.stateMachine = State.PLACE_BOMB + if self.can_place_bomb(nextCell): + self.stateMachine = State.PLACE_BOMB #If distance to monster < 8 -> close to monster if a_star_distance_to_monster(wrld, (nextCell[0], nextCell[1])) < 8 and len(wrld.monsters) > 0: self.stateMachine = State.CLOSE_TO_MONSTER