Move ordering for chess engine - python

Part 3 of my python programming project. Find it here.
Since the last post, I've managed to get work out the transposition tables and started creating the move ordering function.
The move function starts by checking if there's a move in the opening books, if there isn't one then it executes the move ordering function and lastly if there was no move found, it calculates the best move in the position.
This is what I have so far:
def domove(depth):
try:
move = chess.polyglot.MemoryMappedReader("C:/Users/bruno/Desktop/chess/books/pecg_book.bin").weighted_choice(board).move()
move = chess.polyglot.MemoryMappedReader("C:/Users/bruno/Desktop/chess/books/human.bin").weighted_choice(board).move()
move = chess.polyglot.MemoryMappedReader("C:/Users/bruno/Desktop/chess/books/computer.bin").weighted_choice(board).move()
movehistory.append(move)
return move
except:
orderMoves(move)
bestMove = move
movehistory.append(bestMove)
return bestMove
finally:
bestMove = chess.Move.null()
bestValue = -9999
alpha = -10000
beta = 10000
for move in board.legal_moves:
make_move(move)
boardValue = -alphabeta(-beta, -alpha, depth-1)
if boardValue > bestValue:
bestValue = boardValue
bestMove = move
if( boardValue > alpha ):
alpha = boardValue
unmake_move()
movehistory.append(bestMove)
return bestMove
The orderMoves function checks for 3 different things in the current position:
negamax function - searches the transposition tables
Moves that lead to checkmate
Captures that win material
.
def orderMoves(board, bestValue, material, move):
try:
negamax(board)
bestMove = move
movehistory.append(bestMove)
return bestMove
except:
for move in board.legal_moves:
if board.is_checkmate():
bestValue
return bestValue
finally:
for move in board.legal_moves:
if move == board.is_capture():
if newmaterial >= material:
newmaterial = material
return bestValue
The negamax function works via storing and looking up previously stored hashes.
def negamax(node, depth, alpha, beta, score, bestValue):
alphaOrig = alpha
EXACT = score
LOWERBOUND = alpha
UPPERBOUND = beta
## Transposition Table Lookup; node is the lookup key for ttEntry
ttEntry = transpositionTableLookup(node)
if ttEntry.is_valid is True :
if ttEntry.depth >= depth:
if ttEntry.flag == EXACT :
return ttEntry.value
elif ttEntry.flag == LOWERBOUND:
alpha = max(alpha, ttEntry.value)
elif ttEntry.flag == UPPERBOUND:
beta = min(beta, ttEntry.value)
if alpha >= beta:
return ttEntry.value
elif depth == 0 or node == terminal_node():
return bestValue
childNodes = domove(node)
childNodes = orderMoves(childNodes)
bestValue = -99999
for child in childNodes:
bestValue = max(bestValue, -negamax(child, depth - 1, -beta, -alpha))
alpha = max(alpha, bestValue)
if alpha >= beta:
break
##Transposition Table Store; node is the lookup key for ttEntry
ttEntry.value = bestValue
if bestValue <= alphaOrig:
ttEntry.flag = UPPERBOUND
if bestValue >= beta:
ttEntry.flag = LOWERBOUND
else:
ttEntry.flag = EXACT
ttEntry.depth = depth
transpositionTableStore(node, ttEntry)
return bestValue
There's probably a better way of implementing this function, but this was the best I could manage.
After testing this for a few hours of running the code, the results were the same as when I didn't have move ordering. 7 out of the 24 test positions were correct.
What changes could I make to get a cleaner implementation and make it work properly?

Great question.
The Andoma Python chess engine uses this move ordering function in movegeneration.py, that I've also use for my Ramses Chess engine (Python) :
def get_ordered_moves(board: chess.Board) -> List[chess.Move]:
"""
Get legal moves.
Attempt to sort moves by best to worst.
Use piece values (and positional gains/losses) to weight captures.
"""
end_game = check_end_game(board)
def orderer(move):
return move_value(board, move, end_game)
in_order = sorted(
board.legal_moves, key=orderer, reverse=(board.turn == chess.WHITE)
)
return list(in_order)

Related

Negamax Not Working For Python Chess Engine

I'm making a very simple Python chess engine using the standard Python chess library with a very simple evaluation function; the sum of the total black piece weights (positive) plus the sum of the total white piece weights (negative). The engine always plays as black.
I used the Negamax Wikipedia page for guidance and the depth is to the fourth ply. I don't expect grandmaster performance, but the engine makes very questionable moves, for example: e2e4 and f1c4 for white causes the engine to freely give up it's pawn via b7b5.
Can anyone help me out? I'm completely lost as to what I did wrong. The negamax (called search) and the evaluation function is shown below:
import chess
import time
import math
from time import sleep
from chessboard import display
scoreMovePair = {}
def colorMap(color):
if color == True:
return -1
return 1
def pieceMap(pieceNum):
if pieceNum == 1:
return 1
elif pieceNum == 2:
return 3
elif pieceNum == 3:
return 3
elif pieceNum == 4:
return 5
elif pieceNum == 5:
return 9
return pieceNum
def posEval(board):
score = 0
for i in range(0, 64):
piece = board.piece_at(i)
if piece != None:
score = score + pieceMap(piece.piece_type)*colorMap(piece.color)
return score
def search(board, level, a, b, moveSet, color):
if level == 4:
score = posEval(board)
scoreMovePair[score] = moveSet[0]
return score*color
if board.is_checkmate():
return 1000*colorMap(board.turn)
value = -10000
for move in board.legal_moves:
board.push(move)
moveSet.append(move)
value = max(value, -search(board, level + 1, -b, -a, moveSet, -color))
a = max(a, value)
moveSet.pop()
board.pop()
if (a >= b):
break
return value
def main():
global scoreMovepair
board = chess.Board()
display.start(board.fen())
while not display.checkForQuit():
validMoves = list(board.legal_moves)
if len(validMoves) == 0:
break
else:
move = input("Enter move: ")
t0 = time.time()
move = str(move)
myMove = chess.Move.from_uci(move)
if myMove in validMoves:
board.push_san(move)
value = search(board, 0, -10000, 10000, [], 1)
move = scoreMovePair[value]
print(scoreMovePair)
print("FINAL -> "+str(value))
board.push(move)
print(board.fen())
display.update(board.fen())
sleep(1)
t1 = time.time()
print(t1-t0)
else:
continue
display.terminate()
if __name__ == "__main__":
main()
Just based on a first glance, I would say you may be missing a "quiescence search" (meaning a search for quietness). Also called "captures only search".
https://www.chessprogramming.org/Quiescence_Search
This is a search that is called instead of an evaluation function on your leaf nodes (nodes where max depth is reached). The search makes only capture moves until there are no more captures (with unlimited depth).
In short, without this search, whoever gets the last move in the search (determined by depth) will be able to do anything without consequences. This can lead to some weird results.

Minimax algorithm in python using tic tac toe

I'm trying to make tic tac toe AI, which plays the game optimally by using minimax algorithm. I got it to work only to notice it does not make optimal moves and putting it against itself results always win for 'X' player (It should result in draw).
Here is my code for algorithm:
def getBestMove(state, player):
'''
Minimax Algorithm
'''
winner_loser , done = check_current_state(state)
if done == "Done" and winner_loser == 'O': # If AI won
return 1
elif done == "Done" and winner_loser == 'X': # If Human won
return -1
elif done == "Draw": # Draw condition
return 0
moves = []
empty_cells = []
for i in range(3):
for j in range(3):
if state[i][j] is ' ':
empty_cells.append(i*3 + (j+1))
for empty_cell in empty_cells:
move = {}
move['index'] = empty_cell
new_state = copy_game_state(state)
play_move(new_state, player, empty_cell)
if player == 'O': # If AI
result = getBestMove(new_state, 'X') # make more depth tree for human
move['score'] = result
else:
result = getBestMove(new_state, 'O') # make more depth tree for AI
move['score'] = result
moves.append(move)
# Find best move
best_move = None
if player == 'O': # If AI player
best = -infinity
for move in moves:
if move['score'] > best:
best = move['score']
best_move = move['index']
else:
best = infinity
for move in moves:
if move['score'] < best:
best = move['score']
best_move = move['index']
return best_move
What can I do here to fix it?
I think it is easier if you follow the standard minimax algorithm which you can find for example here. I also suggest adding alpha-beta pruning to make it a bit faster, even though it is not really necessary in Tic Tac Toe. Here is an example of a game I made long ago that you can use for inspiration, it is basicall taken from the linked Wikipedia page, with some minor tweaks like if beta <= alpha for the alpha-beta pruning:
move, evaluation = minimax(board, 8, -math.inf, math.inf, True)
def minimax(board, depth, alpha, beta, maximizing_player):
if depth == 0 or board.is_winner() or board.is_board_full():
return None, evaluate(board)
children = board.get_possible_moves(board)
best_move = children[0]
if maximizing_player:
max_eval = -math.inf
for child in children:
board_copy = copy.deepcopy(board)
board_copy.board[child[0]][child[1]].player = 'O'
current_eval = minimax(board_copy, depth - 1, alpha, beta, False)[1]
if current_eval > max_eval:
max_eval = current_eval
best_move = child
alpha = max(alpha, current_eval)
if beta <= alpha:
break
return best_move, max_eval
else:
min_eval = math.inf
for child in children:
board_copy = copy.deepcopy(board)
board_copy.board[child[0]][child[1]].player = 'X'
current_eval = minimax(board_copy, depth - 1, alpha, beta, True)[1]
if current_eval < min_eval:
min_eval = current_eval
best_move = child
beta = min(beta, current_eval)
if beta <= alpha:
break
return best_move, min_eval
def evaluate(board):
if board.is_winner('X'):
return -1
if board.is_winner('O'):
return 1
return 0
Note that it is important to make a deepcopy of the board (or an unmake move function after the recursive minimax call), otherwise you are changing the state of the original board and will get some strange behaviours.

Avoiding dead ends in snake game with moving food in python

I'm trying to do a snake game, where 2 snakes compete between each other. One snake simply follows the food, and avoids obstacles, the other, is the one, for which i'm writing the code, and is supposed to find the best way to get to the food. The food position, every bit of the map and the position of the other snake is known, and the position of the food changes, with every movement of the snakes.
If the map allows it, if there is no obstacle, the snake can traverse through the walls, to go to the other side of the map, like the map is a donut. The snake doesn't move diagonally, only vertically and horizontally, and it can't move backwards.
I'm using jump point search to find a way to the food, and it's working fine, although at 50fps some times the game slows down a bit.
The major problem i'm having, is finding a way to avoid dead ends. If the food gets in a dead end, i want to wait that it leaves the dead end, but what happens is that my snake, goes there, and then dies. Because i'm not avoiding dead ends, when my snake get's big enough, sometimes it crashes in its own body.
This is the code of the agent of my snake.
class AgentStudent(Snake, SearchDomain):
def __init__(self, body=[(0, 0)], direction=(1, 0), name="punkJD"):
super().__init__(body, direction, name=name)
self.count = 0;
#given the current state, and the next state, it returns a direction ( (1,0), (-1,0), (0,1), (0,-1) )
def dir(self, state, n_state):
if state[0] == 0 and n_state[0] == (self.mapsize[0] - 1):
return left
elif state[0] == (self.mapsize[0] - 1) and n_state[0] == 0:
return right
elif state[1] == 0 and n_state[1] == (self.mapsize[1] - 1):
return up
elif state[1] == (self.mapsize[1] - 1) and n_state == 0:
return down
return n_state[0] - state[0], n_state[1] - state[1]
#doesn't matter for the question
def update(self, points=None, mapsize=None, count=None, agent_time=None):
self.mapsize = mapsize
return None
#given current position and food position, it will create a class that will do the search. Seach code bellow
def search_food(self, pos, foodpos):
prob = SearchProblem(self, pos, foodpos, self.olddir)
my_tree = SearchTree(prob, self.mapsize, self.maze)
#doesn't matter, before i was using A*, but then i changed my whole search class
my_tree.strategy = 'A*'
return my_tree.search()
#given the current position and the direction the snake is faced it returns a list of all the possible directions the snake can take. If the current direction is still possible it will be put first in the list to be the first to be considered
def actions(self, pos, dir):
dirTemp = dir
invaliddir = [x for (x, y) in self.complement if y == dir]
validdir = [dir for dir in directions if not (dir in invaliddir)]
validdir = [dir for dir in validdir if
not (self.result(pos, dir) in self.maze.obstacles or self.result(pos, dir) in self.maze.playerpos)]
dirList = [dirTemp] if dirTemp in validdir else []
if dirList != []:
for a in range(len(validdir)):
if validdir[a] != dirTemp:
dirList.append(validdir[a])
return dirList
return validdir
#given the current position and the current direction, it returns the new position
def result(self, a, b):
n_pos = a[0] + b[0], a[1] + b[1]
if n_pos[0] == -1:
n_pos = (self.mapsize[0] - 1), a[1] + b[1]
if n_pos[1] == -1:
n_pos = a[0] + b[0], (self.mapsize[1] - 1)
if n_pos[0] == (self.mapsize[0]):
n_pos = 0, a[1] + b[1]
if n_pos[1] == (self.mapsize[1]):
n_pos = a[0] + b[0], 0
return n_pos
#given the current position and food position it returns the manhattan distance heuristic
def heuristic(self, position, foodpos):
distancex = min(abs(position[0] - foodpos[0]), self.mapsize[0] - abs(position[0] - foodpos[0]))
distancey = min(abs(position[1] - foodpos[1]), self.mapsize[1] - abs(position[1] - foodpos[1]))
return distancex + distancey
#this function is called by the main module of the game, to update the position of the snake
def updateDirection(self, maze):
# this is the brain of the snake player
self.olddir = self.direction
position = self.body[0]
self.maze = maze
# new direction can't be up if current direction is down...and so on
self.complement = [(up, down), (down, up), (right, left), (left, right)]
self.direction = self.search_food(position, self.maze.foodpos)
Bellow is the code to do the search.
I reused a file i had with some classes to do a tree search, and changed it to use jump point search. And for every jump point i find i expand a node in the tree.
class SearchDomain:
def __init__(self):
abstract
def actions(self, state):
abstract
def result(self, state, action):
abstract
def cost(self, state, action):
abstract
def heuristic(self, state, goal_state):
abstract
class SearchProblem:
def __init__(self, domain, initial, goal,dir):
self.domain = domain
self.initial = initial
self.goal = goal
self.dir = dir
def goal_test(self, state):
return state == self.goal
# class that defines the nodes in the tree. It has some attributes that are not used due to my old aproach.
class SearchNode:
def __init__(self,state,parent,heuristic,dir,cost=0,depth=0):
self.state = state
self.parent = parent
self.heuristic = heuristic
self.depth = depth
self.dir = dir
self.cost = cost
if parent!=None:
self.cost = cost + parent.cost
def __str__(self):
return "no(" + str(self.state) + "," + str(self.parent) + "," + str(self.heuristic) + ")"
def __repr__(self):
return str(self)
class SearchTree:
def __init__(self,problem, mapsize, maze, strategy='breadth'):
#attributes used to represent the map in a matrix
#represents obstacle
self.OBS = -1
#represents all the positions occupied by both snakes
self.PPOS = -2
#represents food position
self.FOODPOS = -3
#represents not explored
self.UNIN = -4
self.problem = problem
h = self.problem.domain.heuristic(self.problem.initial,self.problem.goal)
self.root = SearchNode(problem.initial, None,h,self.problem.dir)
self.open_nodes = [self.root]
self.strategy = strategy
self.blacklist = []
self.pqueue = FastPriorityQueue()
self.mapa = maze
#here i initialize the matrix to represent the map
self.field = []
for a in range(mapsize[0]):
self.field.append([])
for b in range(mapsize[1]):
self.field[a].append(self.UNIN)
for a,b in maze.obstacles:
self.field[a][b] = self.OBS
for a,b in maze.playerpos:
self.field[a][b] = self.PPOS
self.field[maze.foodpos[0]][maze.foodpos[1]] = self.FOODPOS
self.field[self.root.state[0]][self.root.state[1]] = self.UNIN
#function to add a jump point to the priority queue
def queue_jumppoint(self,node):
if node is not None:
self.pqueue.add_task(node, self.problem.domain.heuristic(node.state,self.problem.goal)+node.cost)
# given a node it returns the path until the root of the tree
def get_path(self,node):
if node.parent == None:
return [node]
path = self.get_path(node.parent)
path += [node]
return(path)
#Not used in this approach
def remove(self,node):
if node.parent != None:
a = self.problem.domain.actions(node.parent.state, node.dir)
self.blacklist+=node.state
if a == []:
self.remove(node.parent)
node = None
#Function that searches for the food
def search(self):
tempNode = self.root
self.queue_jumppoint(self.root)
count = 0
while not self.pqueue.empty():
node = self.pqueue.pop_task()
actions = self.problem.domain.actions(node.state,node.dir)
if count == 1:
tempNode = node
count+=1
#for every possible direction i call the explore function that finds a jump point in a given direction
for a in range(len(actions)):
print (a)
print (actions[a])
jumpPoint = self.explore(node,actions[a])
if jumpPoint != None:
newnode = SearchNode((jumpPoint[0],jumpPoint[1]),node,self.problem.domain.heuristic(node.state,self.problem.goal),actions[a],jumpPoint[2])
if newnode.state == self.problem.goal:
return self.get_path(newnode)[1].dir
self.queue_jumppoint(newnode)
dirTemp = tempNode.dir
return dirTemp
#Explores the given direction, starting in the position of the given node, to find a jump point
def explore(self,node,dir):
pos = node.state
cost = 0
while (self.problem.domain.result(node.state,dir)) != node.state:
pos = self.problem.domain.result(pos, dir)
cost += 1
#Marking a position as explored
if self.field[pos[0]][pos[1]] == self.UNIN or self.field[pos[0]][pos[1]] == self.PPOS:
self.field[pos[0]][pos[1]] = 20
elif pos[0] == self.problem.goal[0] and pos[1] == self.problem.goal[1]: # destination found
return pos[0],pos[1],cost
else:
return None
#if the snake is going up or down
if dir[0] == 0:
#if there is no obstacle/(or body of any snake) at the right but in the previous position there was, then this is a jump point
if (self.field [self.problem.domain.result(pos,(1,0))[0]] [pos[1]] != self.OBS and self.field [self.problem.domain.result(pos,(1,0))[0]] [self.problem.domain.result(pos,(1,-dir[1]))[1]] == self.OBS) or \
(self.field [self.problem.domain.result(pos,(1,0))[0]] [pos[1]] != self.PPOS and self.field [self.problem.domain.result(pos,(1,0))[0]] [self.problem.domain.result(pos,(1,-dir[1]))[1]] == self.PPOS):
return pos[0], pos[1],cost
#if there is no obstacle/(or body of any snake) at the left but in the previous position there was, then this is a jump point
if (self.field [self.problem.domain.result(pos,(-1,0))[0]] [pos[1]] != self.OBS and self.field [self.problem.domain.result(pos,(-1,0))[0]] [self.problem.domain.result(pos,(1,-dir[1]))[1]] == self.OBS) or \
(self.field [self.problem.domain.result(pos,(-1,0))[0]] [pos[1]] != self.PPOS and self.field [self.problem.domain.result(pos,(-1,0))[0]] [self.problem.domain.result(pos,(1,-dir[1]))[1]] == self.PPOS):
return pos[0], pos[1],cost
#if the snake is going right or left
elif dir[1] == 0:
#if there is no obstacle/(or body of any snake) at the upper part but in the previous position there was, then this is a jump point
if (self.field [pos[0]][self.problem.domain.result(pos,(1,1))[1]] != self.OBS and self.field [self.problem.domain.result(pos,(-dir[0],dir[1]))[0]] [self.problem.domain.result(pos,(1,1))[1]] == self.OBS) or \
(self.field [pos[0]][self.problem.domain.result(pos,(1,1))[1]] != self.PPOS and self.field [self.problem.domain.result(pos,(-dir[0],dir[1]))[0]] [self.problem.domain.result(pos,(1,1))[1]] == self.PPOS):
return pos[0], pos[1],cost
#if there is no obstacle/(or body of any snake) at the down part but in the previous position there was, then this is a jump point
if (self.field [pos[0]] [self.problem.domain.result(pos,(-1,-1))[1]] != self.OBS and self.field [self.problem.domain.result(pos,(-dir[0],dir[1]))[0]] [self.problem.domain.result(pos,(-1,-1))[1]] == self.OBS) or \
(self.field [pos[0]] [self.problem.domain.result(pos,(-1,-1))[1]] != self.PPOS and self.field [self.problem.domain.result(pos,(-dir[0],dir[1]))[0]] [self.problem.domain.result(pos,(-1,-1))[1]] == self.PPOS):
return pos[0], pos[1],cost
#if the food is aligned in some way with the snake head, then this is a jump point
if (pos[0] == self.mapa.foodpos[0] and node.state[0] != self.mapa.foodpos[0]) or \
(pos[1] == self.mapa.foodpos[1] and node.state[1] != self.mapa.foodpos[1]):
return pos[0], pos[1],cost
#if the food is in front of the head of the snake, right next to it, then this is a jump point
if self.field[self.problem.domain.result(pos,(dir[0],dir[1]))[0]][self.problem.domain.result(pos,(1,dir[1]))[1]] == self.FOODPOS:
return pos[0], pos[1],cost
##if an obstacle is in front of the head of the snake, right next to it, then this is a jump point
if self.field[self.problem.domain.result(pos,(dir[0],dir[1]))[0]][ self.problem.domain.result(pos,(1,dir[1]))[1]] == self.OBS:
return pos[0], pos[1],cost
return None
class FastPriorityQueue:
def __init__(self):
self.pq = [] # list of entries arranged in a heap
self.counter = 0 # unique sequence count
def add_task(self, task, priority=0):
self.counter+=1
entry = [priority, self.counter, task]
heapq.heappush(self.pq, entry)
def pop_task(self):
while self.pq:
priority, count, task = heapq.heappop(self.pq)
return task
raise KeyError('pop from an empty priority queue')
def empty(self):
return len(self.pq) == 0
This is my code. I would appreciate any help to be able to avoid dead ends.
I searched for similar problems but couldn't find any that helped me.
StackOverflow is not a coding service, so I'm not going to write your code for you, but I can most definitely tell you what steps you would need to take to solve your issue.
In your comments, you said it would be nice if you could check for dead ends before the game starts. A dead end can be classified as any point that has three or more orthogonally adjacent walls. I'm assuming you want every point leading up to a dead end that is inescapable. Here is how you would check:
Check every point starting from one corner and moving to the other, in either rows or columns, it doesn't matter. Once you reach a point that has three or more orthogonally adjacent walls, mark that point as a dead end, and go to 2.
Find the direction of the empty space next to this point (if any), and check every point in that direction. For each of those points: if it has two or more adjacent walls, mark it as a dead end. If it has only one wall, go to 3. If it has no walls, stop checking in this direction and continue with number 1.
In every direction that does not have a wall, repeat number 2.
Follow these steps until step 1 has checked every tile on the grid.
If you need a programming example, just ask for one in the comments. I didn't have time to make one, but I can make one later if needed. Also, if you need extra clarification, just ask!

Return a move from alpha-beta

I'm trying to use the alpha-beta minimax pruning algorithm to return a valid move from my board. The algorithm returns the correct value, but I have no idea how I would return the move as well. In the case of this code, I would want to return the child in get_successor_states when the value of bestValue is more than the current alpha. I thought about returning two values at the end of the max and min like return bestValue, child but I have no idea how I would get that to work with the other calls
def alpha_beta_pruning(board, depth, alpha, beta, isMaximizingPlayer):
if depth == 0:
return evaluate_state(board)
if isMaximizingPlayer:
bestValue = -sys.maxint - 1
bestMove = None
for child in get_successor_states(board):
temp_board = copy.deepcopy(board)
temp_board.move_queen(child[0], child[1])
temp_board.shoot_arrow(child[2])
bestValue = max(bestValue, alpha_beta_pruning(temp_board, depth-1, alpha, beta, False))
del temp_board
alpha = max(alpha, bestValue)
if beta <= alpha:
break
return bestValue
else:
bestValue = sys.maxint
for child in get_successor_states(board):
temp_board = copy.deepcopy(board)
temp_board.move_queen(child[0], child[1])
temp_board.shoot_arrow(child[2])
bestValue = min(bestValue, alpha_beta_pruning(temp_board, depth-1, alpha, beta, True))
del temp_board
beta = min(beta, bestValue)
if beta <= alpha:
break
return bestValue

Python - Alpha-Beta Purning for Minimax

I want to implement an agent for 3-Men's Morris game-which is very similar to tic-tac-toe game- and i want to use Minimax strategy with Alpha-Beta Pruning, here's my code in Python based on this post and this post on StackOverflow , but it doesn't work!! it gives a wrong solution,even when one of successors of current state is solution
def alpha_beta(state,alpha,beta,turn,depth):
if int(terminal_test(state,turn)) == int(MY_NUMBER):
return 1 #win
elif (int(terminal_test(state,turn))!=0) and (int(terminal_test(state,turn))!=int(MY_NUMBER)) :
return -1 #loose
else:
if int(depth) == 13:
return 0 #reached limit
moves = successors(state,turn,int(depth))
#valid moves for player based on rules
for move in moves:
state = make_move(state,move,turn)
current_eval = -alpha_beta(state, -beta, -alpha, 2-int(turn),int(depth)+1)
state = undo_move(state,move,turn)
if current_eval >= beta:
return beta
if current_eval > alpha:
alpha = current_eval
return alpha
def rootAlphaBeta(state,depth, turn):
best_move = None
max_eval = float('-infinity')
moves = successors(state,turn,int(depth))
alpha = float('infinity')
for move in moves:
state = make_move(state,move,turn)
alpha = -alpha_beta(state, float('-infinity'), alpha, 2-int(turn),int(depth)+1)
state = undo_move(state,move,turn)
if alpha > max_eval:
max_eval = alpha
best_move = move
#best_move which is selected here is not really the best move!
return best_move

Categories

Resources