Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Opt astar #34

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6276804
push to transfer live share device
KohmeiK Jan 26, 2023
a792e76
Basic world params printed
KohmeiK Jan 27, 2023
f6c1293
working A*
KohmeiK Jan 27, 2023
8ea79cb
A* character movement done!
KohmeiK Jan 27, 2023
4b4a00d
cleanup
KohmeiK Jan 27, 2023
ef4acd6
testing window placement
KohmeiK Jan 27, 2023
04b60d9
helper funcs, dist to monster, dist to goal
KohmeiK Jan 30, 2023
4247096
<minimax>
Jan 31, 2023
075f96b
Refactor to move helper functions to utility
KohmeiK Feb 2, 2023
fa529f2
monster location
KohmeiK Feb 2, 2023
930127c
New docs for utility
KohmeiK Feb 2, 2023
c7592e5
Added optional start param for distance_to functions
KohmeiK Feb 2, 2023
027ce69
next character location utility func
KohmeiK Feb 2, 2023
e27b0f5
Not working minmax
KohmeiK Feb 2, 2023
b060ef5
fixed most imports
KohmeiK Feb 3, 2023
a319c6b
<minimax with node next test>
ndenda27 Feb 3, 2023
728f22f
Merge branch 'master' of https://github.com/NhiNguyencmt8/Bomerman
ndenda27 Feb 3, 2023
925812e
somewhat working minimax
KohmeiK Feb 4, 2023
211a97e
no sm yet but better minimax
KohmeiK Feb 4, 2023
413101a
Added state machine
KohmeiK Feb 4, 2023
bd81656
auto-testing all variants and edge cases fixes
KohmeiK Feb 4, 2023
f6c226e
Better testall printing
KohmeiK Feb 4, 2023
252d38a
fix imports -> runable
Feb 5, 2023
a37665c
change do to minimax2
Feb 5, 2023
580bab5
minimax, have move, get values but is to slow and does not escape on …
ndenda27 Feb 6, 2023
f986c07
this version uses a lot more bombs for defense
KohmeiK Feb 6, 2023
3b1d739
Merge branch 'more_bombs'
KohmeiK Feb 6, 2023
89f047d
add comments and clean code
Feb 7, 2023
322c015
Update README.md
NhiNguyencmt8 Feb 7, 2023
f2a259a
add jump point method
Feb 24, 2023
248fd94
it's working
Mar 2, 2023
aaf8a71
minor edits in the placebomb
Mar 3, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.analysis.extraPaths": ["./Bomberman"]
}
5 changes: 5 additions & 0 deletions Bomberman/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import pygame
import math

x = 1330
y = 0
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y)

class Game:
"""Game class"""

Expand Down
2 changes: 2 additions & 0 deletions Bomberman/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
42 changes: 42 additions & 0 deletions teamNN/PriorityQueue.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions teamNN/interactivecharacter.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
4 changes: 2 additions & 2 deletions teamNN/project1/map.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
max_time 5000
bomb_time 10
expl_duration 2
bomb_time 1
expl_duration 1
expl_range 4
+--------+
| |
Expand Down
126 changes: 126 additions & 0 deletions teamNN/project1/minimax.py
Original file line number Diff line number Diff line change
@@ -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
91 changes: 91 additions & 0 deletions teamNN/project1/minimaxnode.py
Original file line number Diff line number Diff line change
@@ -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
Loading