I'm trying to build a min-max algorithm for a Tic-Tac-Toe that never lose...
I try to build it from reading few sources:
http://neverstopbuilding.com/minimax
http://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-3-tic-tac-toe-ai-finding-optimal-move/ (I built something very similar to this one).
Here is the code:
class tree:
def find_best_move(self,board,depth,myTurn,sign):
"""
:param board:
:return:
"""
if (board.empty==[]): return None
best_move=-(2**(board.s**2))
m=board.empty[0]
for move in board.empty:
b=copy.deepcopy(board)
b.ins(move,sign)
if (self.is_win(b,sign) or self.is_win(b,xo.opp_sign(sign))):
return move
curr_move=self.minimax(b,depth,myTurn,sign)
if (curr_move > best_move):
best_move = curr_move
m=move
print(curr_move,best_move,m)
return m #This should be the right move to do....
# *****************************************************************************************************#
def minimax(self,board,depth,myTurn,sign):
"""
:param depth:
:param myTurn:
:return:
"""
#print(depth,end='\n')
if (self.is_win(board,sign)):
#print("I win!")
return (board.s**2+1) - depth
elif (self.is_win(board,xo.opp_sign(sign))):
#print("You win!")
return -(board.s**2+1) + depth
elif (board.is_full()):
return 0
if (myTurn):
bestVal=-(2**700)
for move in board.empty: #empty - the empty squares at the board
b = copy.deepcopy(board)
b.ins(move, sign)
value=self.minimax(b,depth+1,not myTurn, xo.opp_sign(sign))
#xo.opp_sign(sign) - if function for the opposite sign: x=>o and o=>x
bestVal = max([bestVal,value])
return bestVal
else:
bestVal = (2**700)
for move in board.empty:
b = copy.deepcopy(board)
b.ins(move, xo.opp_sign(sign))
value = self.minimax(b, depth + 1, not myTurn, xo.opp_sign(sign))
#print("opp val: ",value)
bestVal = min([bestVal, value])
return bestVal
# *****************************************************************************************************#
def is_win(self,board, sign):
"""
The function gets a board and a sign.
:param board: The board.
:param sign: The sign (There are only two options: x/o).
:return: True if sign "wins" the board, i.e. some row or col or diag are all with then sing. Else return False.
"""
temp=board.s
wins = [] # The options to win at the game.
for i in range(1, temp + 1):
wins.append(board.get_col(i))
wins.append(board.get_row(i))
wins.append(board.get_diag1())
wins.append(board.get_diag2())
for i in wins:
if (self.is_same(i, sign)):
return True
return False
# *****************************************************************************************************#
def is_same(self, l, sign):
"""
The function get a list l and returns if ALL the list have the same sign.
:param l: The list.
:param sign: The sign.
:return: True or false
"""
for i in l:
if (i != sign):
return False
return True
If something is wrong at my code please tell me!
But, I always can beat this - I just need to make a "fork"
.e.g.: (I'm x, the algorithm is o)
xx-
xo-
-o-
And I win...
There is algorithm for making a tree that can block forks?
You have three errors.
1. In your minimax method the sign is swapped one time too many
You swap the sign in the else block -- for the case where myTurn is False -- but you shouldn't. You already swap the sign with each recursive call. Because of this bug, you always puts the same sign on the board during your minimax search, never the opposite one. Obviously you therefore miss all the threats of the opponent.
So change:
else:
bestVal = (2**700)
for move in board.empty:
b = copy.deepcopy(board)
b.ins(move, error xo.opp_sign(sign)) # <-- bug
to:
else:
bestVal = (2**700)
for move in board.empty:
b = copy.deepcopy(board)
b.ins(move, sign) # <-- corrected
2. In find_best_move you should swap the move as you call minimax
And a similar bug occurs in find_best_move. As you go through each move, you must swap the sign when calling minimax in the new board, otherwise you let the same player play twice.
So change this:
for move in board.empty:
b=copy.deepcopy(board)
b.ins(move,sign)
if (self.is_win(b,sign) or self.is_win(b,xo.opp_sign(sign))):
return move
curr_move=self.minimax(b,depth,myTurn,sign) # <-- bug
to:
for move in board.empty:
b=copy.deepcopy(board)
b.ins(move,sign)
if (self.is_win(b,sign) or self.is_win(b,xo.opp_sign(sign))):
return move
curr_move=self.minimax(b,depth,not myTurn,xo.opp_sign(sign)) # <-- corrected
Note that the second condition should not be necessary: if you are the one who just moved, it is not logical that the other should come in a winning position.
3. In minimax you don't consider the value of myTurn when there is win
Although you consider the value of myTurn to determine whether to minimise or maximise, you don't perform this operation when checking for a win.
You currently have this:
if (self.is_win(board,sign)):
#print("I win!")
return (board.s**2+1) - depth
elif (self.is_win(board,xo.opp_sign(sign))):
#print("You win!")
return -(board.s**2+1) + depth
elif (board.is_full()):
return 0
First of all, the first if condition should not be true ever, because the most recent move was for the opposite sign, so that could never lead to a win for sign.
But to the issue: the second if does not look to myTurn to determine whether the return value should be negative or positive. It should do so to be consistent. So change the above code to this:
if self.is_win(board,xo.opp_sign(sign)):
if myTurn:
return -(board.s**2+1) + depth
else:
return (board.s**2+1) - depth
elif board.is_full():
return 0
How to call find_best_move
Finally, the above works if you always call find_best_move with the myTurn argument as True, because find_best_move maximises the result as can be seen from if (curr_move > best_move). So, to avoid that you call it with False, you would better remove this argument and pass False to minimax. So:
def find_best_move(self,board,depth,sign): # <-- myTurn removed as argument
# ... etc ...
curr_move=self.minimax(b,depth,False,xo.opp_sign(sign)) # pass False
This way, the argument myTurn indicates whether the turn is to the same player as the player for which find_best_move was called.
Working Solution
With minimal code added to make it work (Board and XO classes added), the program can be seen to run on repl.it.
Note that this algorithm is not optimal. It is just brute force. You could look into storing results of previously evaluated positions, doing alpha-beta pruning, etc...
Related
Problem Statement:
You are given an integer array nums. You are initially positioned at the array's first index, and each element in the array represents your maximum jump length at that position. Return true if you can reach the last index, or false otherwise.
How can I change my code so that it returns immediately when I have found a path that works for this problem instead of going through all the recursive calls that I have made previously
def canJump(self, nums: List[int]) -> bool:
solve = [False]
def backtrack(i):
if solve[0] == True:
return
if i == len(nums)-1:
solve[0] = True
return
if i >= len(nums) or nums[i] == 0:
return
for x in range(1, nums[i]+1):
backtrack(i+x)
backtrack(0)
return solve[0]
General Form of a Recursive Function
The general form of a recursive function has two mutually exclusive types of conditions that can be met on each iteration of the recursion. These are either:
terminal conditions, or
non-terminal conditions.
Both types of condition contain a return statement.
Terminal Conditions
The return statement in terminal conditions typically takes the form return <value>.
The solution to the problem you are trying to solve requires two possible terminal conditions:
The case where you know you can reach the last index. return True
The case where you know you can NOT reach the last index. return False
Non-Terminal Conditions
The non-terminal condition will occur on iterations where neither of the terminal cases are met. In this situation, you will call the recursive function and return what it returns.
This answer covers terminal and non-terminal conditions in more detail.
Example
Consider a recursive function that sums the numbers of an array.
def sum(position, array, end):
if(position == end): # terminal condition
return 0
else: # non-terminal condition
return array[position] + sum(position+1, array, end)
Another Example
Depending on any constraints to your problem that I might have missed, a solution might be the following:
def jump(current_position, nums, finish_line):
"""
greedy algorithm:
choose the next position with the largest sum of (jump_range + index)
"""
jump_range = nums[current_position]
choice = current_position + jump_range
if(jump_range == 0): # terminal condition
return False
if(choice >= finish_line): # terminal condition
return True
else: # non-terminal condition
utility = 0
for next_position in range(current_position+1, jump_range+1):
next_jump_range = nums[next_position]
if(utility <= next_position + next_jump_range):
utility = next_position + next_jump_range
choice = next_position
return jump(choice, nums, finish_line)
input1 = [2,0,0,10,3]
input2 = [2,3,0,10,3]
current_position = 0
finish_line = len(input1)
print(jump(0, input1, finish_line)) # False
finish_line = len(input2)
print(jump(0, input2, finish_line)) # True
The most noteworthy difference from your solution is that return statements always return a value.
How can I change my code so that it returns immediately when I have found a path that works for this problem instead of going through all the recursive calls that I have made previously
One particularly straightforward way is to throw an exception, which will immediately unwind the stack.
def can_jump(nums: list[int]) -> bool:
if not nums:
return False
class _Success(Exception):
pass
def backtrack(i):
if i >= len(nums):
return
if i == len(nums) - 1:
raise _Success()
for x in range(1, nums[i] + 1):
backtrack(i + x)
try:
backtrack(0)
return False
except _Success:
return True
We create a local exception type called _Success that the backtracking search can throw to indicate that it found a solution.
If it never finds a solution, the backtrack function will simply return and the return False line will run. Otherwise, it will raise _Success() and then the return True line will run.
I'd like to the first value that outputs True for my function. I currently have a search that works fine, but I think is still a bit inefficient. Could anyone suggest a better binary search? My code is below, simplified.
guess = 2
limits = [2, 2**35] #The search area
while True:
if myFunction(guess) == False:
limits[0] = max(limits[0], guess) #Limit the search area
guess *= 2
else:
limits[1] = min(limits[1], guess) #Limit the search area
guess = int((limits[0] + limits[1])/2) #The guess is the midpoint of the search area
if myFunction(guess) == True and myFunction(guess-1) == False:
return guess
This is the classical problem of finding a level-crossing of a monotonically increasing or decreasing function. As you guessed, it is solvable by binary search. Your code has some bugs, which is not surprising:
Although the idea is simple, implementing binary search correctly requires attention to some subtleties about its exit conditions and midpoint calculation.
So, you should avoid writing your own binary search when possible. Fortunately, Python offers a library module bisect which can do the job for you.
from bisect import bisect_left
MIN = 2
MAX = 2**35
def search(f):
# Finds the first position of `True` in `f`
return bisect_left(f, True, lo=MIN, hi=MAX + 1)
Don't be confused by the fact that bisect only works with indexable objects: there is no need to create a list with 2**35 elements. You can use a generator object instead using the __getitem__ syntax. To do that, encapsulate your function in a class and define the getter method that would return False for all argument values on the left side of the point of interest and True otherwise.
def myFunction1(index):
return index >= 1456
def myFunction2(index):
return index >= 2
def myFunction3(index):
return index >= MAX - 1
class F:
def __init__(self, f):
self.f = f
def __getitem__(self, index):
return self.f(index)
# testing code
print(search(F(myFunction1))) # prints 1456
print(search(F(myFunction2))) # prints 2
print(search(F(myFunction3))) # prints MAX - 1
Problem
I am aware that somewhere in my function, I am not returning something I should.
I am returning the recursive call, but it appears that I am not returning "all the way out"
Context
I am doing a depth first search of every single combination in a list. Once I reach a combination that reaches a condition, I want to return.
I am maintaining "state" of my combination and am backtracking where I should be (I think).
What am I doing wrong?
class Combo:
def __init__(self, list):
self.staples = list
Combo has a property called "staples", consisting of a list of staple classes. I want to iterate over the list of staples in a decision tree to find an optimal number.
In this case, the optimal number is summed across the quantities of each staple instance in the list and stored/recalculated as a property on the Combo instance.
def IterateStaples(combo, target):
#Exit condition for combo dictionary
if all(combo.diff[macro] < 2 for macro in combo.diff):
return combo;
#iterate through all items in list
for staple in combo.staples:
#Increment and calc conditions
staple.increment()
combo.calcTotals()
combo.findDiff(target)
#If exceeds target value, backtrack
if combo.findConflict(target):
staple.decrement()
combo.calcTotals()
combo.findDiff(target)
#Redundant exit condition to try and return
elif all(combo.diff[macro] < 2 for macro in combo.diff):
return combo
#Recursive call
else:
return IterateStaples(combo, target)
staple.decrement()
combo.calcTotals()
combo.findDiff(target)
If I understand your code correctly (which is more difficult than usual, since you've not shown what most of the methods you're calling on combo and staple are), this should be what you want:
def IterateStaples(combo, target):
# base case
if all(combo.diff[macro] < 2 for macro in combo.diff): # iterate on combo.diff.values()?
return combo # returning combo indicates success!
for staple in combo.staples:
staple.increment() # update state
combo.calcTotals()
combo.findDiff(target)
if not combo.findConflict(target): # skip recursing on invalid states
result = IterateStaples(combo, target) # recursive case
if result is not None: # if the recursion was successful, return the result
return result
staple.decrement() # otherwise, undo the change to the state (backtrack)
combo.calcTotals() # these two lines might not be necessary when backtracking
combo.findDiff(target) # since other branches will call them after staple.increment()
return None # if we got to the end of the loop, explicitly return None to signal failure
The return None at the end is not strictly necessary, since None is the default return value if you don't return anything else. I just think that it's better to be explicit about it.
I'm following your code in returning combo on success (and extending it to returning None on failure). Since the code mutates combo in place, you could just as well return True for success (in the base case at the top of the function) and False for failure (at the bottom of the function, after the end of the loop). The recursive logic would pass on True results, and backtrack after False results. The top-level caller would need to check the combo instance they'd passed in for the actual solution if they got a True return value:
combo = Combo(something)
if IterateStaples(combo, target):
do_stuff(combo) # success!
Your first if statement inside the for loop doesn't return anything. What it should return depends on your algorithm's logic:
#If exceeds target value, backtrack
if combo.findConflict(target):
staple.decrement()
combo.calcTotals()
combo.findDiff(target)
return SOMETHING
Additionally, the last 3 lines won't ever get executed, they're after the return statement.
I was able to pass my own test case by incorporating a helper function in the following:
Is this not backtracking? I implemented N-queens with a similar approach
def IterateStaples(combo, target):
#iterate through all items in list
bestCombo = []
def helper(combo):
for staple in combo.staples:
#Increment and calc conditions
staple.increment()
combo.calcTotals()
combo.findDiff(target)
#If exceeds target value, backtrack
if combo.findConflict(target):
staple.decrement()
combo.calcTotals()
combo.findDiff(target)
#Redundant exit condition to try and return
elif all(combo.diff[macro] < 2 for macro in combo.diff):
bestCombo.append(deepcopy(combo))
return
#Recursive call
else:
helper(combo)
staple.decrement()
combo.calcTotals()
combo.findDiff(target)
helper(combo)
return bestCombo
I am pretty new so I apologize if I make any errors in posting here... I did a search but didn't come up with much that would help me. I am writing a miniMax algorithm for a variation on Tic Tac Toe. This variation allows either player to put an X or an O anywhere on the board. I am having trouble with the recursion and was hoping I could get a bit of guidance.
class TicTacToeBoard:
def __init__(self, initGameBoard):
#initGameBoard is a string
self.board = initGameBoard
def getEmptySpaces(self):
return self.board.count("-")
def markX(self, index):
self.board = self.board[:index] + "x" + self.board[index+1:]
def markO(self, index):
self.board = self.board[:index] + "o" + self.board[index+1:]
def endGame(self):
#determines if someone has won
endGameStates = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
for x in range(len(endGameStates)):
trySlice = self.board[endGameStates[x][0]] + self.board[endGameStates[x][1]] + \
self.board[endGameStates[x][2]]
if trySlice[0] == trySlice[1] == trySlice[2] and "-" not in trySlice:
return True
return False
def draw(self):
#determines if there has been a draw
if "-" not in self.board:
endGameStates = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]
for x in range(len(endGameStates)):
trySlice = self.board[endGameStates[x][0]] + self.board[endGameStates[x][1]] + \
self.board[endGameStates[x][2]]
if trySlice[0] == trySlice[1] == trySlice[2] and "-" not in trySlice:
return False
return True
else:
return False
def __str__(self):
boardStr = ""
for char in self.board:
boardStr += char
return boardStr
Above is my board class. I'm just using strings, not doing anything too fancy. I'm also using a very simple Node class that just stores data (though I suppose I might be able to just use strings too I guess...)
from tic_tac_toe_board import TicTacToeBoard
from node import Node
nodeQueue = []
def fitnessFunction(gameBoard):
#only runs if end game or if all full
if gameBoard.draw():
return 0
else:
emptySpaces = gameBoard.getEmptySpaces()
if emptySpaces %2 == 0:
#max won
return (emptySpaces + 1) *1
else:
#max lost
return (emptySpaces + 1) *-1
def miniMax(gameBoard):
if gameBoard.endGame() or if "-" not in gameBoard:
#end game checks for winner, second clause checks for full/draw
return fitnessFunction(gameBoard)
else:
emptyIndexes = [] #keeps track of which indexes are empty
count = 0
for char in gameBoard:
if char == "-":
emptyIndexes.append(count)
count +=1
if len(emptyIndexes) %2 != 0:
#max's turn
for index in emptyIndexes:
childNode = Node(gameBoard.markX(index))
nodeQueue.append(childNode)
childNode = Node(gameBoard.markO(index))
nodeQueue.append(childNode)
return miniMax()
The fitnessFunction returns a score based on the number of empty spaces left. I'm having trouble with my recursive miniMax method. What I need to do is check for the base cases (either player winning, or a draw) and if those base cases are not true, I figure out whose move it is based on the number of empty spaces left. I think I've gotten that far, but I don't know what to do next (the recursive part). I also need to be able to get the min or max of children, depending on whose turn it is. I guess I am lost with the recursion. I'm new to CS and haven't touched much on it. Any hints would be greatly appreciated! :)
If you are looking for a mini-max algorithm, you don't need an example upon which to apply it. Most games require a mini-max algorithm.
So if you want one, decide how it must behave. Then write a test for at least one example of that behavior.
Post your test. (Not a game, but just a test of the data that would result from a game.)
If your algorithm does not work, your game program can't work.
Hint: A mini-max algorithm depends only upon the evaluations of game paths, not upon the game being played.
My sudoku solver does exactly what it's supposed to do - except returning the correct thing. It prints what it should right before the return (a correctly solved grid), but then it seems to keep running for a bit and returns None. I can't figure out what's going on.
Grid is a list of lists. Assume that check_sudoku returns True if the grid is valid (solved or not), and False otherwise.
def solve_sudoku(grid, row=0, col=0):
"Searches for valid numbers to put in blank cells until puzzle is solved."
# Sanity check
if not check_sudoku(grid):
return None
# Sudoku is solved
if row > 8:
return grid
# not a blank, try next cell
elif grid[row][col] != 0:
next_cell(grid, row, col)
else:
# try every number from 1-9 for current cell until one is valid
for n in range(1, 10):
grid[row][col] = n
if check_sudoku(grid):
next_cell(grid, row, col)
else:
# Sudoku is unsolvable at this point, clear cell and backtrack
grid[row][col] = 0
return
def next_cell(grid, row, col):
"Increments column if column is < 8 otherwise increments row"
return solve_sudoku(grid, row, col+1) if col < 8 else solve_sudoku(grid, row+1, 0)
You're calling next_cell in the recursion, but never returning its value.
It seems to me like this will never actually return something useful. The first time we enter your solve_sudoku, you check to see if the grid is solved, and if so you return it. After that, you begin a bunch of recursion which ultimately will end up coming back out towards the end of the function and returning None. No matter what, you are returning None all the time. The only thing that you end up with is a modified grid argument that you passed in to start.
def solve_sudoku(grid, row=0, col=0):
# possible return at the first entry point
if not check_sudoku(grid):
return None
# only time you would ever NOT get None
if row > 8:
return grid
...recurse...
# come back out when the stack unwinds,
# and there is no return value other than None
What I speculate is happening, is that you are printing the values along the way, and you do properly see a solved grid at the moment it happens, but your function isn't set up to properly completely break out when that is done. It continues to loop around until it exhausts some range and you see a bunch of extra work.
The important thing to do is to check the return value of the recursive call. You would either return None if its not solved, or grid if it is. As it stands, you never care about the result of your recursions in the calling scope.
Because I don't have all the details about your specific code, here is a simple equivalent:
def solver(aList):
if aList[0] == 10:
print "SOLVED!"
return None
print "NOT SOLVED..."
return next(aList)
def next(aList):
# do some stuff
# check some stuff
aList[0] += 1
return solver(aList)
if __name__ == "__main__":
data = [0]
solver(data)
print data
Notice that the indirectly recursive call to checker() -> solver() has its value returned all the way up the chain. In this case we are saying None means solved, and otherwise it should keep recursing. You know that at some point deep in your recursion stack, the solution will be solved. You will need to communicate that back up to the top right away.
The communication looks like this:
aList == [0]
solver(aList)
next(...)
next(...)
next(...)
next(...)
#SOLVED
<- next(...)
<- next(...)
<- next(...)
<- next(...)
<-solver(aList)
aList == [10]
And here is what your version might look like if applied to my simple example:
aList == [0]
solver(aList)
next(...)
next(...)
# SOLVED
next(...)
next(...)
...
<- next(...)
<- next(...)
<- next(...)
<- next(...)
<-solver(aList)
aList == [10]