MiniMax Recursive Algorithm (Python) - python

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.

Related

I am having issues with my code executing a the boolean operation correctly and limiting the value of a variable as define in the operation

Here is my code-
board=['','']
from IPython.display import clear_output
def display_board(board):
print(board[7]+'|'+board[8]+'|'+board[9])
print(board[4]+'|'+board[5]+'|'+board[6])
print(board[1]+'|'+board[2]+'|'+board[3])
test_board=['']*10
display_board(test_board)
def player_input():
marker=''
# keep asking ot select X or O t oplayer one
while not(marker=='X'or marker== 'O'):
marker= input('Player one, select either X or O :').upper()
if marker =='X':
return ('X', 'O')
else:
marker=='O'
return('O','X')
player_input()
test_board=['','','','','','','','','','']
def place_marker(board,marker,position):
board[position]= marker
place_marker(test_board,'$',8)
display_board(test_board)
def win_check(board, mark):
# checking who wins tic tac to base on the input X or O or marker
# check all columns, to make sure the marker is the same
# check diagonals.
return ((board[7]==board[8]==board[9]==mark) or
(board[4]==mark and board[5]==mark and board [6]==mark) or
(board[1]==board[2]==board[3]==mark) or
(board[7]==mark and board[5]==mark and board [3]==mark) or
(board[1]==mark and board[5]==mark and board [9]==mark) or
(board[7]==board[4]==board[1]==mark) or
(board[8]==board[5]==board[2]==mark) or
(board[9]==board[6]==board[3]==mark))
display_board(test_board)
win_check(test_board,'X')
#random module
import random
def choose_first():
if random.randint(0,1) == 0:
return 'player1'
else:
return 'player2'
choose_first()
def space_check(test_board,position):
return test_board[position]==' '
#Here is where I encounter the problem, based on what I have before, all the spaces are empty. Therefore, I write in my program the following condition, if all spaces are full from 1 to 9 return "true" other wise return "False", I should get a "false" since there are not values in test_board but regardless on how I am writting it I always get a true. I am not sire what I am missing?
def full_board_check(test_board):
for i in range(1,10):
if space_check==''in test_board:
return False
else:
return True
full_board_check(test_board)
$Second problem here I am using a "in range" of 1 to 10, this is to represent the spaces I have available for the users to enter their option (in Test_board) but I can put in 10 or 11> and it will take it. How can I limit this to the 9 spaces I have available?
def player_choice (test_board):
position=0
while position not in [1,2,3,4,5,6,7,8,9] or not space_check(test_board,position):
position= int(input('Select a position (1-9) ' ))
return position
This line doesn't look right: if space_check==''in test_board:, and I think the whole function is wrong, as you're returning True before checking all the values. Also space_check is actually looking for a space, but you initialize the board with empty strings, not spaces. Try this:
def space_check(test_board,position):
return test_board[position]==''
def full_board_check(test_board):
"""Returns True if all spaces are filled"""
for i in range(1,10):
if space_check(test_board, i):
return False # We found an empty slot
return True # All slots are filled

How to solve a basic maze using functions in python?

I am trying to write a code that solves a basic maze made up of a list of lists with '#' symbolizing walls, '.' are free spaces, S is the start and E is the end. My algorithm consists of first checking to see if the space to the right is free, if not it then checks the space up, then down, and finally to the left. I also realize that this creates a problem if there is a dead end but I'll worry about that later. So far I have code that prints the list and finds the index value of the start point which is (2,0). Basically what I am trying to do is take that starting value as the argument for my def solve function and then iterate over my algorithm while marking visited positions as 'x'... if that makes any sense. When I run the code I keep getting
if maze[r+1][c] == '.':
NameError: name 'r' is not defined
Also I can't seem to print my maze correctly on this website so
here is my maze.
def main():
print_maze()
start()
solve(start())
print_maze()
def print_maze():
for r in range(0,len(maze)):
for c in range(0,len(maze)):
print(maze[r][c], end='')
print('')
def start():
find_value = 'S'
for r in range(0,len(maze)):
for c in range (0,len(maze)):
if find_value in maze[r][c]:
return(r,c)
break
def solve(position):
if maze[r+1][c] == '.':
maze[r][c] = 'x'
return (r,c)
elif maze[r][c+1] == '.':
maze[r][c] = 'x'
return (r,c)
elif maze[r][c-1] == '.':
maze[r][c] = 'x'
return (r,c)
elif maze[r-1][c] == '.':
maze[r][c] = 'x'
return (r,c)
else:
print('Route Error')
main()
The error you are encountering occurs because main calls solve without a required argument.
def main():
print_maze()
start()
solve() # Error! Missing required arg
print_maze()
Since solve redefines start_point right away you can just remove that required argument:
def solve():
start_point = start()
Finally, you didn't provide maze so anybody trying to help you beyond this problem won't be able to
You need to pass the value returned from start() into the solve() function because you listed it as an argument. You can then remove the start_point= portion from the solve() function.
def main():
print_maze()
start = start()
solve(start)
print_maze()
Try to change the def slove format eg; == "X"
= "x²"

Finding the turning point of a function

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

Minmax tic-tac-toe algorithm that never loses

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...

How do I keep a list of numbers I have already enountered?

I am currently working on a BASIC simulator in Python, as the title suggests. Here is my code for this problem:
def getBASIC():
l = []
x = 1
while x == 1:
i = input()
l.append(i)
if len(i.split()) != 3:
x = 0
return l
def findLine(prog, target):
for l in range(0, len(prog)):
progX = prog[l].split()
if progX[0] == target:
return l
def execute(prog):
location = 0
visited = [False] * len(prog)
while True:
T = prog[location].split()[2]
location = findLine(prog, T)
visited[location] = True
if visited[len(visited)-1] == False:
return "infinite loop"
else:
return "success"
The first function does what it is intended to do -- convert input of BASIC code into a list. The second function, findLine also does what it is intended to do, in that it finds the item which contains the string equal to the input. The last function, however, I cannot get to work. I know what I have to do, and that is to check whether or not a part of it has been visited twice. I cannot figure out how to do this, due to the existence of the while loop. As a result of this, the second half of that function is just placeholder. If you could help me figure out how to solve this, it would be greatly appreciated. Thanks.
You keep a list of places that have been visited (you already do this) and then when you encounter a goto, you check if it does to a line that already have been visited, and if it has been visited, you exit.
One mistake right now is that you make a list that is as long as the program is. That's pretty pointless. Just keep a list of the visited line numbers instead, and check with
if current_line in visited:
Try adding an if statement declaring a line in the visited list to be true when it is encountered in the loop. This is my solution:
def execute(prog):
location = 0
visited=[False]*len(prog)
while True:
if location==len(prog)-1:
return "success"
if visited[location]==True:
return "infinite loop"
if visited[location]==False:
visited[location]=True
line2strings=prog[location].split()
T=line2strings[-1]
location=findLine(prog, T)

Categories