Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
I have a problem. I am trying to build an artificial intelligence game and I am encountering a problem. I copied a player and made an inheritance to the previous one (class Player(SelectiveAlphaBeta.Player)). For now the search in the game tree which I am making is the same (it only print maximum and minimum scores of each level – just for helping me choosing a right threshold). However it crashes in:
class Player(SelectiveAlphaBeta.Player):
def __init__(self, setup_time, player_color, time_per_k_turns, k):
SelectiveAlphaBeta.Player.__init__(self, setup_time, player_color, time_per_k_turns, k, 0.25) # TODO: w
def get_move(self, board_state, possible_moves):
self.clock = time.process_time()
self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05
if len(possible_moves) == 1:
return possible_moves[0]
current_depth = 1
prev_alpha = -INFINITY
# Choosing an arbitrary move:
best_move = possible_moves[0]
if (self.w < 1):
minimax = MiniMaxWithAlphaBetaPruningWithWDeepeningUntilRestfulness
(self.utility, self.color, self.no_more_time, self.w)
else: # self.w == 1
minimax = MiniMaxWithAlphaBetaPruning(self.utility, self.color, self.no_more_time)
time_last_move = 0;
print('debugger - line 1')
# Iterative deepening until the time runs out.
while True:
print('{} going to depth: {}, remaining time: {}, prev_alpha: {}, best_move: {}'.format(
self.__repr__(), current_depth, self.time_for_current_move - (time.process_time() - self.clock),
prev_alpha, best_move))
time_before = time.process_time()
time_left = self.time_for_current_move - (time.process_time() - self.clock);
# if (time_last_move <= time_left):
try:
print('debugger - line 2')
(alpha, move), run_time = run_with_limited_time(
minimax.search, (board_state, current_depth, -INFINITY, INFINITY, True), {},
time_left)
print('debugger - line 3')
except (ExceededTimeError):
print('no more time')
break
except (MemoryError):
print('no more memory')
break
# else:
# print('{} has no enough time ({}) left to go deeper'.format(self.__repr__(), time_left))
# break;
time_after = time.process_time()
time_last_move = time_after - time_before
if self.no_more_time():
print('no more time')
break
prev_alpha = alpha
best_move = move
if alpha == INFINITY:
print('the move: {} will guarantee victory.'.format(best_move))
break
if alpha == -INFINITY:
print('all is lost')
break
current_depth += 1
if self.turns_remaining_in_round == 1:
self.turns_remaining_in_round = self.k
self.time_remaining_in_round = self.time_per_k_turns
else:
self.turns_remaining_in_round -= 1
self.time_remaining_in_round -= (time.process_time() - self.clock)
return best_move
def utility(self, state):
return SelectiveAlphaBeta.Player.utility(self, state)
def no_more_time(self):
return SelectiveAlphaBeta.Player.no_more_time(self)
def __repr__(self):
return '{} {}'.format(abstract.AbstractPlayer.__repr__(self), 'SelectiveAlphaBetaWithRestfulness{}'.format(str(self.w)))
nothing is missing because this the signature of the function:
class MiniMaxWithAlphaBetaPruningWithW(MiniMaxWithAlphaBetaPruning):
def __init__(self, utility, my_color, no_more_time, w):
MiniMaxWithAlphaBetaPruning.__init__(self, utility, my_color, no_more_time)
self.w = w
def search(self, state, depth, alpha, beta, maximizing_player):
"""Start the MiniMax algorithm.
:param state: The state to start from.
:param depth: The maximum allowed depth for the algorithm.
:param alpha: The alpha of the alpha-beta pruning.
:param alpha: The beta of the alpha-beta pruning.
:param maximizing_player: Whether this is a max node (True) or a min node (False).
:return: A tuple: (The alpha-beta algorithm value, The move in case of max node or None in min mode)
"""
if depth == 0 or self.no_more_time():
return self.utility(state), None
next_moves = state.legalMoves()
if not next_moves:
# This player has no moves. So the previous player is the winner.
return INFINITY if state.currPlayer != self.my_color else -INFINITY, None
list = []
for next_move in next_moves:
if (self.no_more_time()):
del list[:]
return self.utility(state), None
new_state = copy.deepcopy(state)
new_state.doMove(next_move)
list.append((new_state, next_move, self.utility(new_state)))
list.sort(key=itemgetter(2))
if (self.no_more_time()):
del list[:]
return self.utility(state), None
if maximizing_player:
selected_move = next_moves[0]
best_move_utility = -INFINITY
for i in range(int(len(list)) - 1, int(len(list)) - int(len(list) * self.w) - 1, -1):
minimax_value, _ = self.search(list[i][0], depth - 1, alpha, beta, False)
alpha = max(alpha, minimax_value)
if minimax_value > best_move_utility:
best_move_utility = minimax_value
selected_move = list[i][1]
if beta <= alpha or self.no_more_time():
break
del list[:]
return alpha, selected_move
else:
for i in range(0, int(len(list) * self.w)):
beta = min(beta, self.search(list[i][0], depth - 1, alpha, beta, True)[0])
if beta <= alpha or self.no_more_time():
break
del list[:]
return beta, None
class MiniMaxWithAlphaBetaPruningWithWDeepeningUntilRestfulness(MiniMaxWithAlphaBetaPruning):
def __init__(self, utility, my_color, no_more_time, w):
MiniMaxWithAlphaBetaPruningWithW.__init__(self, utility, my_color, no_more_time, w)
# self.treshold_restfulness = TODO
def search(self, state, depth, alpha, beta, maximizing_player):
"""Start the MiniMax algorithm.
:param state: The state to start from.
:param depth: The maximum allowed depth for the algorithm.
:param alpha: The alpha of the alpha-beta pruning.
:param alpha: The beta of the alpha-beta pruning.
:param maximizing_player: Whether this is a max node (True) or a min node (False).
:return: A tuple: (The alpha-beta algorithm value, The move in case of max node or None in min mode)
"""
print('debugger - line 4')
if depth == 0 or self.no_more_time():
return self.utility(state), None
next_moves = state.legalMoves()
if not next_moves:
# This player has no moves. So the previous player is the winner.
return INFINITY if state.currPlayer != self.my_color else -INFINITY, None
list = []
for next_move in next_moves:
if (self.no_more_time()):
del list[:]
return self.utility(state), None
new_state = copy.deepcopy(state)
new_state.doMove(next_move)
list.append((new_state, next_move, self.utility(new_state)))
list.sort(key=itemgetter(2))
if (self.no_more_time()):
del list[:]
return self.utility(state), None
if maximizing_player:
selected_move = next_moves[0]
best_move_utility = -INFINITY
for i in range(int(len(list)) - 1, int(len(list)) - int(len(list) * self.w) - 1, -1):
minimax_value, _ = self.search(list[i][0], depth - 1, alpha, beta, False)
alpha = max(alpha, minimax_value)
if minimax_value > best_move_utility:
best_move_utility = minimax_value
selected_move = list[i][1]
if beta <= alpha or self.no_more_time():
break
print('Utility of best Move in deepening in depth of {} is {}'.format(depth, minimax_value))
del list[:]
return alpha, selected_move
else:
for i in range(0, int(len(list) * self.w)):
beta = min(beta, self.search(list[i][0], depth - 1, alpha, beta, True)[0])
if beta <= alpha or self.no_more_time():
break
del list[:]
return beta, None
The error message is:
Exception in thread Thread-6:
Traceback (most recent call last):
File "C:\Python34\lib\threading.py", line 921, in _bootstrap_inner
self.run()
File "C:\Python34\lib\threading.py", line 869, in run
self._target(*self._args, **self._kwargs)
File "C:\Users\Eli\workspace\HW2\amazons\utils.py", line 36, in function_wrapper
result = func(*args, **kwargs)
TypeError: search() missing 1 required positional argument: 'maximizing_player'
for convenience the original player:
class Player(players.simple_player.Player):
def __init__(self, setup_time, player_color, time_per_k_turns, k, w):
players.simple_player.Player.__init__(self, setup_time, player_color, time_per_k_turns, k)
self.w = w;
def get_move(self, board_state, possible_moves):
self.clock = time.process_time()
self.time_for_current_move = self.time_remaining_in_round / self.turns_remaining_in_round - 0.05
if len(possible_moves) == 1:
return possible_moves[0]
current_depth = 1
prev_alpha = -INFINITY
# Choosing an arbitrary move:
best_move = possible_moves[0]
if (self.w < 1):
minimax = MiniMaxWithAlphaBetaPruningWithW(self.utility, self.color, self.no_more_time, self.w)
else: # self.w == 1
minimax = MiniMaxWithAlphaBetaPruning(self.utility, self.color, self.no_more_time)
time_last_move = 0;
# Iterative deepening until the time runs out.
while True:
print('{} going to depth: {}, remaining time: {}, prev_alpha: {}, best_move: {}'.format(
self.__repr__(), current_depth, self.time_for_current_move - (time.process_time() - self.clock),
prev_alpha, best_move))
time_before = time.process_time()
time_left = self.time_for_current_move - (time.process_time() - self.clock);
# if (time_last_move <= time_left):
try:
(alpha, move), run_time = run_with_limited_time(
minimax.search, (board_state, current_depth, -INFINITY, INFINITY, True), {},
time_left)
except (ExceededTimeError):
print('no more time')
break
except (MemoryError):
print('no more memory')
break
# else:
# print('{} has no enough time ({}) left to go deeper'.format(self.__repr__(), time_left))
# break;
time_after = time.process_time()
time_last_move = time_after - time_before
if self.no_more_time():
print('no more time')
break
prev_alpha = alpha
best_move = move
if alpha == INFINITY:
print('the move: {} will guarantee victory.'.format(best_move))
break
if alpha == -INFINITY:
print('all is lost')
break
current_depth += 1
if self.turns_remaining_in_round == 1:
self.turns_remaining_in_round = self.k
self.time_remaining_in_round = self.time_per_k_turns
else:
self.turns_remaining_in_round -= 1
self.time_remaining_in_round -= (time.process_time() - self.clock)
return best_move
and for convenience - run_with_limited_time:
def run_with_limited_time(func, args, kwargs, time_limit):
"""Runs a function with time limit
:param func: The function to run.
:param args: The functions args, given as tuple.
:param kwargs: The functions keywords, given as dict.
:param time_limit: The time limit in seconds (can be float).
:return: A tuple: The function's return value unchanged, and the running time for the function.
:raises PlayerExceededTimeError: If player exceeded its given time.
"""
q = Queue()
t = Thread(target=function_wrapper, args=(func, args, kwargs, q))
t.start()
# This is just for limiting the runtime of the other thread, so we stop eventually.
# It doesn't really measure the runtime.
t.join(time_limit)
if t.is_alive():
raise ExceededTimeError
q_get = q.get()
if isinstance(q_get, MemoryError):
raise q_get
return q_get
There is of course no mention to the object only to the functions of the games which run it. I don't know why it is happening. It must be very stupid but I have no idea… I had done only a simple copy of the code and I haven't changed this line…
Thanks in advance,
Eli
Your problem is here:
minimax = MiniMaxWithAlphaBetaPruningWithWDeepeningUntilRestfulness
(self.utility, self.color, self.no_more_time, self.w)
These are actually two separate lines, the second of which does nothing, while you intended it to be a single expression. It will assign minimax to be the class itself instead of an instance, which causes problems when calling a method on the class later.
You can put everything on one line, or just move the opening parenthesis to the first line (as Python allows expressions to continue on the next line when parentheses are left open):
minimax = MiniMaxWithAlphaBetaPruningWithWDeepeningUntilRestfulness(
self.utility, self.color, self.no_more_time, self.w)
Related
I am currently trying to fix my pathfinding system in my game. The A pathfinding python code is super slow, considering it has to calculate thousands of nodes each time for my grid. My grid is stored in a dictionary that has the positions of all of the walls and obstacles. Are there any ways I could speed this up signifigantly?
Here is my algorithm:
def findpath_subgrid(self, start, end):
self.subgrid_cache.clear()
start_subgrid = (start[0] // self.subgrid_size, start[1] // self.subgrid_size)
end_subgrid = (end[0] // self.subgrid_size, end[1] // self.subgrid_size)
if start_subgrid == end_subgrid:
return self.find_path(start, end)
else:
with self.lock:
if start_subgrid in self.subgrid_cache:
return self.subgrid_cache[start_subgrid]
else:
path = self.find_path(start, end)
self.subgrid_cache[start_subgrid] = path
return path
def heuristic(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def find_path(self, start, end):
queue = []
heapq.heappush(queue, (0, start))
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
while queue:
current = heapq.heappop(queue)[1]
if current == end:
break
for next in self.adjacent_cells(current):
new_cost = cost_so_far[current] + self.cost(current, next)
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + self.heuristic(end, next)
heapq.heappush(queue, (priority, next))
came_from[next] = current
return self.reconstruct_path(came_from, start, end)
def adjacent_cells(self, pos):
x, y = pos
results = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]
results = filter(self.in_bounds, results)
results = filter(self.passable, results)
return results
def in_bounds(self, pos):
x, y = pos
return 0 <= x < 2000 and 0 <= y < 2000
def passable(self, pos):
return self.grid.get(pos) != 1 # check if the cell is not an obstacle using the new grid dictionary
def cost(self, current, next):
if self.grid.get(next) == 2:
return 1000 # high cost for cells with enemies
else:
return 1 # otherwise, the cost is 1
def heuristic(self, a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def reconstruct_path(self, came_from, start, goal):
current = goal
path = []
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
I've tried subgrids, cache's but its still very very slow.
If a position is extracted from the queue, you do not need to consider them again in your search (this statement is true because you are working on a grid and using manhattan distance as heuristic function). I added a variable expanded_list to function find_path and adjacent_cells (my Python writing is not the best so you can write them better later). Also it would be better if you use a 2D array for holding your grid instead of a dictionary.
def find_path(self, start, end):
queue = []
heapq.heappush(queue, (0, start))
came_from = {}
cost_so_far = {}
came_from[start] = None
cost_so_far[start] = 0
expanded_list = {}
expanded_list[current] = False
while queue:
current = heapq.heappop(queue)[1]
expanded_list[current] = True
if current == end:
break
for next in self.adjacent_cells(current):
expanded_list[next] = False
new_cost = cost_so_far[current] + self.cost(current, next)
if next not in cost_so_far or new_cost < cost_so_far[next]:
cost_so_far[next] = new_cost
priority = new_cost + self.heuristic(end, next)
heapq.heappush(queue, (priority, next))
came_from[next] = current
return self.reconstruct_path(came_from, start, end)
def adjacent_cells(self, pos, expanded_list):
x, y = pos
results = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]
results = filter(self.in_bounds, results)
results = filter(self.passable, results)
results = filter(lambda pos:not(pos in expanded_list and expanded_list[pos]==True), results)
return results
I am trying to make an ultimate tic-tac-toe game in python which is a little different than the actual one in a way that this game ends when there is a win in any one sub-board. I am using minimax algorithm with alpha-beta pruning to find out the best move for the bot to play. The problem is that when i run the code and it is the time for bot to play its move, it runs endlessly without coming to a conclusion and returning a best_move.
The communication with the board is already handled. All i need is the best value and once i get that, i can retrieve the index from that state.
Initailly, once the game is started, the user is prompted to make a move from 1-9 which is then fed to the function:
Boards is a list of list which contains the state of each sub-board.
# choose a move to play
def play1(user_move):
# print_board(boards)
boards_list = main_boards.tolist()
player = 1
depth = 20
end_move = make_bot_move(boards_list, user_move, player, depth)
place(curr, end_move, 1)
return end_move
The make_bot_move function takes the position of the human and figures out in which sub-board it should play its best_move:
def make_bot_move(state, user_move, player, depth):
#sub_board = state[user_move]
# if suboptimal(state, user_move, player) != 0:
# return suboptimal(state, user_move, player)
pseudo_states = successors(state, player, user_move)
best_move = (-inf, None)
alpha = -inf
beta = inf
for x in pseudo_states:
state = x[0]
index = x[1]
val = minimax(state, index, opponent(player), depth-1, alpha, beta)
if val > best_move[0]:
best_move = (val, index)
# print("val = ", val)
# print_board(s[0])
return best_move[1]
The successors function returns the possible states where it can play its move:
def successors(boards, player, user_move):
sub_board = boards[user_move]
value_index = []
possible_states = []
for idx, value in enumerate(sub_board):
if value == 0 and idx != 0:
value_index.append(idx)
copied_board = deepcopy(boards)
possible_states.append(get_possible_state(copied_board, user_move, idx, player))
#print(possible_states)
return zip(possible_states, value_index)
Finally, every possible move is fed to minimax function which returns a val of the best move:
def minimax(state, last_move, player, depth, alpha, beta):
if depth <= 0 or get_game_status(state, player) != 0:
return evaluate(state, opponent(player))
if player == 1:
max_eval = -inf
pseudo_states = successors(state, player, last_move)
for x in pseudo_states:
state = x[0]
index = x[1]
print(depth)
#print_board(np.array(state))
eval = minimax(state, index, opponent(player), depth-1, alpha, beta)
max_eval = max(max_eval, eval)
alpha = max(alpha, eval)
if beta<= alpha:
break
#print_board(np.array(state))
return max_eval
if player == 2:
min_eval = inf
pseudo_states = successors(state, player, last_move)
for x in pseudo_states:
state = x[0]
index = x[1]
print(depth)
#print_board(np.array(state))
eval = minimax(state, index, opponent(player), depth - 1, alpha, beta)
min_eval = min(min_eval, eval)
beta = min(beta, eval)
if beta<= alpha:
break
#print_board(np.array(state))
return min_eval
To know whether someone has WON || LOSS || DRAW, get_game_status function is called inside minimax function:
def get_game_status(state, player):
other_player = opponent(player)
for each_box in state[1:10]:
win_state = [
[each_box[1], each_box[2], each_box[3]],
[each_box[4], each_box[5], each_box[6]],
[each_box[7], each_box[8], each_box[9]],
[each_box[1], each_box[4], each_box[7]],
[each_box[2], each_box[5], each_box[8]],
[each_box[3], each_box[6], each_box[9]],
[each_box[1], each_box[5], each_box[9]],
[each_box[3], each_box[5], each_box[7]],
]
if [player, player, player] in win_state:
return player
elif [other_player, other_player, other_player] in win_state:
return other_player
else:
return 0
And the scoring is handled using evaluate function:
def evaluate(state, player):
if(get_game_status(state, player) and player ==1) :
score = 10
elif(get_game_status(state, player) and player == 2):
score = -10
else:
score = 0
return score
The expected result is to get the best move but instead, it runs endlessly.
Kindly suggest what changes I should make, or where I am going wrong.
I have written a python code to solve the missionaries and cannibals problem using recursive dfs in python. However I keep getting this error:
RecursionError: maximum recursion depth exceeded
I have no idea what to do about it, and I have been stuck at it for so long.
Any help or suggestion will be life saving for me. Thanks.
Here is the code:
class State(object):
#left = 1
#right = 0 for boat
def __init__(self, missionaries, cannibals, boat):
self.missionaries = missionaries
self.cannibals = cannibals
self.boat = boat
#def __str__(self):
# return "%s, %s %s %s" % (self.by_move, self.missionaries, self.cannibals, self.boat)
def is_valid(self):
if self.missionaries < 0 or self.missionaries > 3:
return False
if self.cannibals < 0 or self.cannibals > 3:
return False
if self.boat > 1 or self.boat < 0:
return False
if self.missionaries < self.cannibals and self.missionaries > 0:
return False
# Check for the other side
if self.missionaries > self.cannibals and self.missionaries < 3:
return False
return True
def is_goal(self):
return self.missionaries == 0 and self.cannibals == 0 and self.boat == 0
def new_states(self):
op = -1 # Subtract
boat_move = "from left shore to right"
if self.boat == 0:
op = 1 # Add
boat_move = "from right shore to left"
for x in range(3):
for y in range(3):
by_move = "Move %s missionaries and %s cannibals %s" % (x, y, boat_move)
new_state = State(self.missionaries + op * x, self.cannibals + op * y, self.boat + op * 1)
if x + y >= 1 and x + y <= 2 and new_state.is_valid():
yield new_state
class Node(object):
def __init__(self, parent, state, depth):
self.parent = parent
self.state = state
self.depth = depth
def children(self):
for state in self.state.new_states():
yield Node(parent=self, state=state, depth=self.depth + 1)
def extract_solution(self):
print
"Extracting soln"
solution = []
node = self
solution.append(node)
while node.parent is not None:
solution.append(node.parent)
node = node.parent
solution.reverse()
return solution
def dfs(root,visited,sol = None):
if root in visited:
return
if root is None:
return
visited.append(root)
if root.state.is_goal():
sol = root
return
for child in root.children():
if child not in visited:
dfs(child,visited,sol)
def main():
initial_state = State(3,3,1)
root = Node(parent = None, state = initial_state,depth = 0)
visited = []
sol = Node(parent = None, state = initial_state,depth = 0)
dfs(root,visited,sol)
ans = sol.extract_solution()
print(ans)
if __name__ == '__main__':
main()
There were two issues:
1: your list 'visited' didn't properly keep track of all the states. This can easily be fixed by making visited a global variable (by putting it in front of the def main() as done in the final solution)
2: The program was searching possibilities that weren't going to ever help (eg: bringing the same guy back and forth), this
if root in visited:
return
if root is None:
return
didn't solve this because it's never the same root object (even if the root.state.missionaries, cannibals and boat are the same value), so I changed this using a dictionary object:
if root is None:
return
state = str(root.state.missionaries) + ',' + str(root.state.cannibals) + ',' + str(root.state.boat)
if state in routes:
if routes[state] < root.depth:
return
else:
routes[state] = root.depth
else:
routes[state] = root.depth
visited.append(root)
This results in the following code (it returns an answer, I'm not sure if it's the correct one because I don't know the missionaries and cannibals problem)
class State(object):
#left = 1
#right = 0 for boat
def __init__(self, missionaries, cannibals, boat):
self.missionaries = missionaries
self.cannibals = cannibals
self.boat = boat
#def __str__(self):
# return "%s, %s %s %s" % (self.by_move, self.missionaries, self.cannibals, self.boat)
def is_valid(self):
if self.missionaries < 0 or self.missionaries > 3:
return False
if self.cannibals < 0 or self.cannibals > 3:
return False
if self.boat > 1 or self.boat < 0:
return False
if self.missionaries < self.cannibals and self.missionaries > 0:
return False
# Check for the other side
if self.missionaries > self.cannibals and self.missionaries < 3:
return False
return True
def is_goal(self):
return self.missionaries == 0 and self.cannibals == 0 and self.boat == 0
def new_states(self):
op = -1 # Subtract
boat_move = "from left shore to right"
if self.boat == 0:
op = 1 # Add
boat_move = "from right shore to left"
for x in range(3):
for y in range(3):
by_move = "Move %s missionaries and %s cannibals %s" % (x, y, boat_move)
new_state = State(self.missionaries + op * x, self.cannibals + op * y, self.boat + op * 1)
if x + y >= 1 and x + y <= 2 and new_state.is_valid():
yield new_state
class Node(object):
def __init__(self, parent, state, depth):
self.parent = parent
self.state = state
self.depth = depth
def children(self):
for state in self.state.new_states():
yield Node(parent=self, state=state, depth=self.depth + 1)
def extract_solution(self):
print "Extracting soln"
solution = []
node = self
solution.append(node)
while node.parent is not None:
solution.append(node.parent)
node = node.parent
solution.reverse()
return solution
def dfs(root,sol = None):
if root is None:
return
state = str(root.state.missionaries) + ',' + str(root.state.cannibals) + ',' + str(root.state.boat)
if state in routes:
if routes[state] < root.depth:
return
else:
routes[state] = root.depth
else:
routes[state] = root.depth
visited.append(root)
if root.state.is_goal():
sol = root
return
for child in root.children():
if child not in visited:
dfs(child,sol)
visited = []
routes = {}
def main():
initial_state = State(3,3,1)
root = Node(parent = None, state = initial_state,depth = 0)
sol = Node(parent = None, state = initial_state,depth = 0)
dfs(root,sol)
ans = sol.extract_solution()
print(ans)
if __name__ == '__main__':
main()
PS. as #PM 2Ring said, for next time: please fix your indentation when asking questions, it makes reading your code easier to understand. You can do this by selecting all your code, adding a tab to all lines selected and then copying it. Before you paste it make sure there's an empty line. :)
My Iterative Deepening Depth-First Search (IDDFS) of the 8 puzzle game returns a path length greater than my BFS. The total number of visited Nodes is 42 for the IDDFS while my BFS returns a total of 26. Is there something wrong with my IDDFS algorithm or is that just how it should behave???
import collections
import queue
import time
import itertools
class Node:
def __init__(self, puzzle, last=None):
self.puzzle = puzzle
self.last = last
#property
def seq(self): # to keep track of the sequence used to get to the goal
node, seq = self, []
while node:
seq.append(node)
node = node.last
yield from reversed(seq)
#property
def state(self):
return str(self.puzzle.board) # hashable so it can be compared in sets
#property
def isSolved(self):
return self.puzzle.isSolved
#property
def getMoves(self):
return self.puzzle.getMoves
class Puzzle:
def __init__(self, startBoard):
self.board = startBoard
#property
def getMoves(self):
possibleNewBoards = []
zeroPos = self.board.index(0) # find the zero tile to determine possible moves
if zeroPos == 0:
possibleNewBoards.append(self.move(0,1))
possibleNewBoards.append(self.move(0,3))
elif zeroPos == 1:
possibleNewBoards.append(self.move(1,0))
possibleNewBoards.append(self.move(1,2))
possibleNewBoards.append(self.move(1,4))
elif zeroPos == 2:
possibleNewBoards.append(self.move(2,1))
possibleNewBoards.append(self.move(2,5))
elif zeroPos == 3:
possibleNewBoards.append(self.move(3,0))
possibleNewBoards.append(self.move(3,4))
possibleNewBoards.append(self.move(3,6))
elif zeroPos == 4:
possibleNewBoards.append(self.move(4,1))
possibleNewBoards.append(self.move(4,3))
possibleNewBoards.append(self.move(4,5))
possibleNewBoards.append(self.move(4,7))
elif zeroPos == 5:
possibleNewBoards.append(self.move(5,2))
possibleNewBoards.append(self.move(5,4))
possibleNewBoards.append(self.move(5,8))
elif zeroPos == 6:
possibleNewBoards.append(self.move(6,3))
possibleNewBoards.append(self.move(6,7))
elif zeroPos == 7:
possibleNewBoards.append(self.move(7,4))
possibleNewBoards.append(self.move(7,6))
possibleNewBoards.append(self.move(7,8))
else:
possibleNewBoards.append(self.move(8,5))
possibleNewBoards.append(self.move(8,7))
return possibleNewBoards # returns Puzzle objects (maximum of 4 at a time)
def move(self, current, to):
changeBoard = self.board[:] # create a copy
changeBoard[to], changeBoard[current] = changeBoard[current], changeBoard[to] # switch the tiles at the passed positions
return Puzzle(changeBoard) # return a new Puzzle object
def printPuzzle(self): # prints board in 8 puzzle style
copyBoard = self.board[:]
for i in range(9):
if i == 2 or i == 5:
print((str)(copyBoard[i]))
else:
print((str)(copyBoard[i])+" ", end="")
print('\n')
#property
def isSolved(self):
return self.board == [0,1,2,3,4,5,6,7,8] # goal board
class Solver:
def __init__(self, Puzzle):
self.puzzle = Puzzle
def IDDFS(self):
def DLS(currentNode, depth):
if depth == 0:
return None
if currentNode.isSolved:
return currentNode
elif depth > 0:
for board in currentNode.getMoves:
nextNode = Node(board, currentNode)
if nextNode.state not in visited:
visited.add(nextNode.state)
goalNode = DLS(nextNode, depth - 1)
if goalNode != None: # I thought this should be redundant but it never finds a soln if I take it out
if goalNode.isSolved: # same as above ^
return goalNode
for depth in itertools.count():
visited = set()
startNode = Node(self.puzzle)
print(startNode.isSolved)
goalNode = DLS(startNode, depth)
if goalNode != None:
if goalNode.isSolved:
return goalNode.seq
startingBoard = [7,2,4,5,0,6,8,3,1]
myPuzzle = Puzzle(startingBoard)
mySolver = Solver(myPuzzle)
start = time.time()
goalSeq = mySolver.IDDFS()
end = time.time()
counter = -1 # starting state doesn't count as a move
for node in goalSeq:
counter = counter + 1
node.puzzle.printPuzzle()
print("Total number of moves: " + str(counter))
totalTime = end - start
print("Total searching time: %.2f seconds" % (totalTime))
I'm trying to implement a computer player in a Connect Four type game. Alpha-beta pruning seemed like the best way to achieve this, but I cannot seem to figure out what I'm doing wrong.
The following is the code I've come up with. It starts with a initial root state. For every possible, valid move (and if no pruning occurs) the algorithm: makes a deep copy of the state, updates the state (increases depth, switches turns, adds a piece, sets a heuristic value), and adds this new state to the root's list of successors.
If the new state is not a leaf (i.e. at max depth) it recursively continues. If it is a leaf, the algorithm checks the root's value and appropriate local alpha/beta value and updates accordingly. After all possible valid options have been checked, the algorithm returns the appropriate local alpha/beta value.
At least, that is what I intended. Every run returns a value of 0. As requested here is the initialization code:
class GameState:
def __init__(self, parentState = None):
# copy constructor
if not(parentState == None):
self.matrix = copy.deepcopy(parentState.matrix)
self.successor = copy.deepcopy(parentState.successor)
self.depth = parentState.depth
self.turn = parentState.turn
self.alpha = parentState.alpha
self.beta = parentState.beta
self.connects = copy.deepcopy(parentState.connects)
self.value = parentState.value
self.algo_value = parentState.value
self.solution = parentState.solution
# new instance
else:
# empty board
self.matrix = [[0 for y in xrange(6)] for x in xrange(7)]
## USED WHEN GROWING TREE
self.successor = [] # empty list
self.depth = 0 # start at root
self.turn = 1 # game starts on user's turn
## USED WHEN SEARCHING FOR SOLUTION
self.alpha = float("-inf")
self.beta = float("+inf")
self.connects = [0, 0, 0] # connects in state
self.algo_value = float("-inf")
self.value = 0 # alpha-beta value of connects
self.solution = False # connect four
def alphabeta(root):
if root.depth < MAX_EXPANSION_DEPTH:
# pass down alpha/beta
alpha = root.alpha
beta = root.beta
# for each possible move
for x in range(7):
# ALPHA-BETA PRUNING
# if root is MAXIMIZER
if (root.turn == 2) and (root.algo_value > beta): print "beta prune"
# if root is MINIMIZER
elif (root.turn == 1) and (root.algo_value < alpha): print "alpha prune"
# CANNOT prune
else:
# if move legal
if (checkMove(root, x)):
# CREATE NEW STATE
root.successor.append(GameState(root))
working_state = root.successor[-1]
# update state
working_state.successor = []
working_state.depth += 1
working_state.turn = (working_state.turn % 2) + 1
cons = dropPiece(working_state, x, working_state.turn)
# update state values
# MAXIMIZER
if working_state.turn == 2:
working_state.value = ((cons[0]*TWO_VAL)+(cons[1]*THREE_VAL)+(cons[2]*FOUR_VAL)) + root.value
working_state.algo_value = float("-inf")
# MINIMIZER
else:
working_state.value = ((-1)*((cons[0]*TWO_VAL)+(cons[1]*THREE_VAL)+(cons[2]*FOUR_VAL))) + root.value
working_state.algo_value = float("inf")
# if NOT a leaf node
if (working_state.depth < MAX_EXPANSION_DEPTH):
# update alpha/beta values
working_state.alpha = alpha
working_state.beta = beta
ret = alphabeta(working_state)
# if MAXIMIZER
if (root.turn == 2):
if (ret > root.algo_value): root.algo_value = ret
if (ret > alpha): alpha = ret
# if MINIMIZER
else:
if (ret < root.algo_value): root.algo_value = ret
if (ret < beta): beta = ret
# if leaf, return value
else:
if root.turn == 2:
if (working_state.value > root.algo_value): root.algo_value = working_state.value
if working_state.value > alpha: alpha = working_state.value
else:
if (working_state.value < root.algo_value): root.algo_value = working_state.value
if working_state.value < beta: beta = working_state.value
if root.turn == 2: return alpha
else: return beta
Solved the issue. In the above algorithm, I check for pruning after the loop has moved on to the next successor (whose default algo_values are the respective max and min values).
Instead, the algorithm should check the first node in each list of successors, update its algo_value, and THEN check for pruning of the rest of the nodes in the list of successors.