Optimizing a robot vacuum's path - python

I'm trying to solve the Botclean problem on Hackerrank:
https://www.hackerrank.com/challenges/botclean
The solution I came up with will scan the board for the dirty tile with the shortest distance then navigate to it, clean it, and repeat until it's all clean.
Code:
nextD = []
def next_move(posr, posc, board):
global nextD
if nextD == []:
nextD = next_d(posr,posc,board)
if (nextD[0] == posr) and (nextD[1] == posc):
print("CLEAN")
board[posr][posc] = "-"
nextD = []
return
if posc < nextD[1]:
print("RIGHT")
return
#posc += 1
elif posc > nextD[1]:
print("LEFT")
return
#posc -= 1
if posr < nextD[0]:
print("DOWN")
return
#posr += 1
elif posr > nextD[0]:
print("UP")
return
#posr -= 1
#find closest d
def next_d(posr, posc, board):
arr = []
for i in range(len(board)):
try:
#print("Distance to: ", i, board[i].index('d'), abs(i-posc) + abs(board[i].index('d')))
vals = [abs(i-posr) + abs(board[i].index('d')-posc), i, board[i].index('d')]
arr.append(vals)
except ValueError:
pass
arr.sort()
return [arr[0][1], arr[0][2]]
# Tail starts here
if __name__ == "__main__":
pos = [int(i) for i in input().strip().split()]
board = [[j for j in input().strip()] for i in range(5)]
next_move(pos[0], pos[1], board)
I'm stuck at 17.60/17.82. My bot gets 16, 20, 34, 26 on the test cases. The best scores are 16, 25, 28 and 18. The discussion says to implement a greedy algorithm though I'm not entirely sure how in this context. Any suggestions for optimizing this problem? Did I go about it completely wrong?
edit: Time isn't a criteria. So scanning the board repeatedly isn't necessarily a problem.
If you want to watch it in action:
https://www.hackerrank.com/showgame/4843664
Thank you!

Got it
nextD = []
def next_move(posr, posc, board):
global nextD
if nextD == []:
nextD = next_d(posr,posc,board)
if (nextD[0] == posr) and (nextD[1] == posc):
print("CLEAN")
board[posr][posc] = "-"
nextD = []
elif posc < nextD[1]:
print("RIGHT")
elif posc > nextD[1]:
print("LEFT")
elif posr < nextD[0]:
print("DOWN")
elif posr > nextD[0]:
print("UP")
#find closest d
def next_d(posr, posc, board):
val = len(board) * 2
nextD = []
for i in range(len(board)):
for j in range(len(board[i])):
if board[i][j] == 'd' and abs(i - posr) + abs(j - posc) < val:
val = abs(i - posr) + abs(j - posc)
nextD = [i, j]
return nextD
# Tail starts here
if __name__ == "__main__":
pos = [int(i) for i in input().strip().split()]
board = [[j for j in input().strip()] for i in range(5)]
next_move(pos[0], pos[1], board)

Related

Trouble with minimax algorithm in tic tac toe game with adjustable game size

I am trying to create a tic tac toe game with an adjustable game size and a computer that uses the minimax algorithm. The game sizes can only be odd numbers, to make sure that diagonal wins are always possible. The game runs with no errors, but I know that the minimax algorithm isn't working 100% correct because I can still beat the computer. I've looked extensively over my code and cannot find where the algorithm is going wrong. Here is my code:
Main.py
import TicTacToe
import Minimax
if (__name__ == "__main__"):
t = TicTacToe.ttt(3)
m = Minimax.Minimax(3, t)
while (t.winner == None):
if (t.turn == 1):
playerInputI = int(input("Input row: "))
playerInputJ = int(input("Input column: "))
bestIndex = (playerInputI, playerInputJ)
else:
winner, bestIndex = m.minimax(t.grid, (-1, -1), 15, -1)
t.winner = None
t.findWinner(bestIndex, t.grid)
t.updateGameGrid(bestIndex)
print(t.grid)
print(t.grid)
Minimax.py
class Minimax:
def __init__(self, gs, t):
self.gridSize = gs
self.ttt = t
def minimax(self, state, currIndex, depth, turn):
if (currIndex[0] != -1 and currIndex[1] != -1):
winner = self.ttt.findWinner(currIndex, state)
if (winner == -1):
return winner - depth, currIndex
elif (winner == -1):
return winner + depth, currIndex
elif (winner == 0):
return 0, currIndex
if (depth==0 and winner==None):
return 0, currIndex
evalLimit = -turn * 1000
bestIndex = None
for i in range(self.gridSize):
for j in range(self.gridSize):
if (state[i][j] == 0):
state[i][j] = turn
eval, newIndex = self.minimax(state, (i, j), depth-1, -turn)
state[i][j] = 0
if (turn > 0 and eval > evalLimit):
bestIndex = newIndex
evalLimit = eval
elif (turn < 0 and eval < evalLimit):
bestIndex = newIndex
evalLimit = eval
return evalLimit, bestIndex
Tictactoe.py
from random import randint
class ttt:
def __init__(self, size):
self.gridSize = size
self.grid = self.createGrid()
# If using minimax algorithm, user is maximizer(1) and computer is minimizer(-1)
# If single player, then user is 1, computer is -1
# If multiplayer, user1 is 1, user2 = -1
self.turn = 1
self.winner = None
def createGrid(self):
grid = []
for i in range(self.gridSize):
grid.append([])
for j in range(self.gridSize):
grid[i].append(0)
# grid = [[-1, 1, 0], [0, -1, 0], [0, 0, 0]]
return grid
def updateGameGrid(self, index):
if (self.grid[index[0]][index[1]] != 0):
return
self.grid[index[0]][index[1]] = self.turn
winner = self.findWinner(index, self.grid)
self.turn = -self.turn
def randomIndex(self):
x = randint(0, self.gridSize-1)
y = randint(0, self.gridSize-1)
while (self.grid[x][y] != 0):
x = randint(0, self.gridSize-1)
y = randint(0, self.gridSize-1)
return (x, y)
def findWinner(self, index, grid):
# Row
found = True
for j in range(self.gridSize-1):
if (grid[index[0]][j] != grid[index[0]][j+1] or grid[index[0]][j] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Column
found = True
for i in range(self.gridSize-1):
if (grid[i][index[1]] != grid[i+1][index[1]] or grid[i][index[1]] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Top Left to Bottom Right Diagonal
if (index[0] == index[1]):
found = True
for i in range(self.gridSize-1):
if (grid[i][i] != grid[i+1][i+1] or grid[i][i] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
# Top Right to Bottom Left Diagonal
if (index[0] + index[1] == self.gridSize-1):
found = True
for i in range(self.gridSize-1):
if (grid[self.gridSize-i-1][i] != grid[self.gridSize-i-2][i+1] or grid[self.gridSize-i-1][i] == 0):
found = False
break
if (found):
self.winner = self.turn
return self.turn
tie = True
for i in range(self.gridSize):
for j in range(self.gridSize):
if (grid[i][j] == 0):
tie = False
if (tie):
self.winner = 0
return 0
return None
The grid is represented as a 2d array, with each element being either a -1 for O, 1 for X and 0 for nothing. The player is 1 and the computer is -1. Who's turn it is is represented as a -1 or 1, corresponding to O or X. If anyone is able to find where the error in my code is that would be a great.
I see two troubles :
a) the two following conditions are the same in minimax
if (winner == -1):
return winner - depth, currIndex
elif (winner == -1):
return winner + depth, currIndex
b) When you return bestIndex, you actually return the winning move, the one that completes a line or a row or a diagonal, at one of the leaves of the game tree. What you really want is the next move to play. Write instead bestIndex = (i, j) in the condition if eval > evalLimit
I didn't check for everything but that is a good start. Run your code at depth=1 or 2 with many printf inside the minimax function, and look at the different moves, with the corresponding score, if they look correct or not.

N Puzzle with Depth First Search

I'm trying solve N Puzzle with Depth First Search using python 3.
With 3 x 3 puzzle it run good and fast but with 4 x 4 puzzle, it runs too slow and can't find solution with error: "MemoryError".
I also use "h(n) = depth + number of wrong tiles" to evaluate priority of each node.
I'm a newbie to python so hope you can help me with this
Here is my code:
import sys
import getopt
import random
import time
class State:
def __init__(self, parent, board, move, depth):
self.parent = parent
self.previousMove = move
self.board = board
self.map = ''.join(str(e) for e in board)
self.depth = depth
self.cost = self.calculateCost()
def calculateCost(self):
pos = 1
count = 0
for tile in self.board:
if tile == pos:
count += 1
pos += 1
return self.depth + 8 - count
class Puzzle:
def __init__(self, k, customBoard = None):
self.k = k
self.n = k*k - 1
self.sizeOfBoard = k*k
self.timeOfSolving = 0
self.timeOfGenerateSuccessors = 0
self.maxDeepSearch = 0
self.inititalState = State(None, self.createInitialBoard(customBoard), 'Start', 0)
self.goalBoard = self.createGoalBoard()
self.finalState = None
self.stateStorage = set() # Store states that have visited
self.path = [] # Store states that lead to goalstate
self.stack = []
def isSolvable(self, board):
# count invertion in puzzle's board
invCount = 0
for i in range(0, self.sizeOfBoard - 1):
if board[i] == 0:
continue
for j in range(i+1, self.sizeOfBoard):
if board[j] == 0:
continue
if board[i] > board[j]:
invCount += 1
# print(invCount)
if (invCount % 2 == 0):
return True
return False
def createInitialBoard(self, customBoard):
print("Creating initial state")
if customBoard is None:
board = []
lstAddSuccess = []
while 1:
board.clear()
lstAddSuccess.clear()
for count in range(0, self.k*self.k):
newTile = random.randint(0, self.n)
while newTile in lstAddSuccess:
newTile = random.randint(0, self.n)
lstAddSuccess += [newTile]
board += [newTile]
if self.isSolvable(board):
break
else:
board = [int(e) for e in customBoard]
if not self.isSolvable(board):
print("Cant find solution with this puzzle! Exiting...")
exit(-1)
return board
def createGoalBoard(self):
board = []
for count in range(1, self.n + 1):
board += [count]
board += [0]
return board
def printBoard(self, board):
for row in range(0, self.sizeOfBoard, self.k):
# for col in range(row, row + self.k):
print(board[row:row + self.k])
def generateSuccessors(self, currentState):
indexOfZero = currentState.board.index(0)
rowIndexOfZero = indexOfZero % self.k
colIndexOfZero = indexOfZero // self.k
lstSuccessors = []
# Slide to zero to up
if colIndexOfZero != 0:
newState = currentState.board.copy()
newState[indexOfZero] = newState[indexOfZero - self.k]
newState[indexOfZero - self.k] = 0
lstSuccessors.append(
State(currentState, newState, 'up', currentState.depth + 1))
# Slide zero to down
if colIndexOfZero != self.k - 1:
newState = currentState.board.copy()
newState[indexOfZero] = newState[indexOfZero + self.k]
newState[indexOfZero + self.k] = 0
lstSuccessors.append(
State(currentState, newState, 'down', currentState.depth + 1))
# slide zero to left
if rowIndexOfZero != 0:
newState = currentState.board.copy()
newState[indexOfZero] = newState[indexOfZero - 1]
newState[indexOfZero - 1] = 0
lstSuccessors.append(
State(currentState, newState, 'left', currentState.depth + 1))
# Slide zero to right
if rowIndexOfZero != self.k - 1:
newState = currentState.board.copy()
newState[indexOfZero] = newState[indexOfZero + 1]
newState[indexOfZero + 1] = 0
lstSuccessors.append(
State(currentState, newState, 'right', currentState.depth + 1))
lstSuccessorsCost = [ele.cost for ele in lstSuccessors]
lstSuccessorsInOrderOfCost = []
for i in range(0, len(lstSuccessorsCost)):
lstSuccessorsInOrderOfCost.append(lstSuccessors[lstSuccessorsCost.index(min(lstSuccessorsCost))])
lstSuccessorsCost[lstSuccessorsCost.index(min(lstSuccessorsCost))] = 100
return lstSuccessorsInOrderOfCost
def solvePuzzle(self, currentState):
self.stack.append(currentState)
self.stateStorage.add(currentState.map)
while len(self.stack) > 0:
currentState = self.stack.pop()
if currentState.board == self.goalBoard:
# find path
# self.printBoard(currentState.board)
self.finalState = currentState
print("Solving " + str(self.n) + " puzzle done!")
return
start_time_gen = time.time()
lstSuccessor = self.generateSuccessors(currentState)
end_time_gen = time.time()
timeOfGen = end_time_gen - start_time_gen
self.timeOfGenerateSuccessors += timeOfGen
for successor in lstSuccessor[::-1]:
if successor.map not in self.stateStorage:
self.stack.append(successor)
self.stateStorage.add(successor.map)
if successor.depth > self.maxDeepSearch:
self.maxDeepSearch += 1
print("Cant solve puzzle! Exiting...")
exit(-1)
def solve(self):
start_time = time.time()
self.solvePuzzle(self.inititalState)
end_time = time.time()
self.timeOfSolving = end_time - start_time
print("Running time: " + str(self.timeOfSolving))
print("Max Search Dept: " + str(self.maxDeepSearch))
print("Final State Dept: " + str(self.finalState.depth))
def printInitialBoard(self):
self.printBoard(self.inititalState.board)
def printPath(self):
if self.finalState is None:
print("No solution found!")
return
path = []
state = self.finalState
while (state is not None):
if state.previousMove is not None:
path.append(state.previousMove)
state = state.parent
print("path: "),
print(path[::-1])
def main(argv):
# if (len(argv) != 1 or int(argv[0]) not in range(1, 10000)):
# print("Input must be k of integer, which is k*k matrix of puzzle")
# exit()
# eight_puzzle = Puzzle(int(argv[0]))
k = int(input("Enter size of k * k puzzle, k = "))
while k not in range(2, 100):
print("k must be in range 2 - 100")
k = int(input("Enter size of k * k puzzle, k = "))
print("""
Choose:
1. Randome puzzle
2. Custome puzzle
""")
file = input()
if int(file) == 1:
puzzle = Puzzle(k)
elif int(file) == 2:
board = input("Enter puzzle: ")
puzzle = Puzzle(k ,list(board.split(" ")))
puzzle.printInitialBoard()
puzzle.solve()
puzzle.printPath()
if __name__ == "__main__":
main(sys.argv[1:])

Python BFS program not returning grid and path, on maze with obstacles and solutions

I have imported a text file with numbers as the following example:
3 0 0 0 0 1 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 0 3 3 3 0 3 0 0 0 0 2 3 3 3 0 3 0 0 0 0 3 3 3 3 0 3 0 0 0 0 3 3 3 3 0 3 2 2 0 0 3 3 3 3 3 3 3 3 2 0 3 3 3
The goal is to read in the text file, format it as a grid (i.e a 10 by 10 grid) which I am able to do, and then sort through the list of lists to reach the solution where the number 3 is an obstacle, number 1 is start point and number 2 is the solution, I am attempting to use a BFS algorithm where the agent can move UP, DOWN, LEFT, RIGHT.
I am trying to print the sequence of steps that is taken to reach the closest solution (i.e 2) from the beginning point(i.e 1). The numbers are formatted as strings/ text. The program I have written seems to be running but it never prints a solution or terminates. The move sequence that is to be printed as a solutions is in the format of:
'Move Down'
'Move UP'
ETC. where each move is on a newline
I am attaching my code below and any help that can be offered would be greatly appreciated
import queue
def read_user_input():
file_name = input('Enter the name of your file :\n')
return file_name
def read_to_grid():
file_name = read_user_input()
for nums in open(file_name):
line = list(nums.split())
result = []
for _ in range(0, len(line), 10):
result.append(line[_:_ + 10])
return result
file_name.close()
def print_grid(result, path=''):
for x, pos in enumerate(result[0]):
if pos == '0':
start = x
i = start
j = 0
pos = set()
for move in path:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
j -= 1
elif move == 'Move Down':
j += 1
pos.add((j, i))
for j, row in enumerate(result):
for i, col in enumerate(row):
if (j, i) in pos:
print('#', end='')
else:
print(col + ' ', end='')
print()
def valid(result, moves):
for x, pos in enumerate(result[0]):
if pos == '0':
start = x
i = start
j = 0
for move in moves:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
j -= 1
elif move == 'Move Down':
j += 1
if not (0 <= i < len(result[0]) and 0 <= j < len(result)):
return False
elif (result[i][j] == '3'):
return False
return True
def find_goal(result, moves):
for x, pos in enumerate(result[0]):
if pos == '0':
start = x
i = start
j = 0
for move in moves:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
j -= 1
elif move == 'Move Down':
j += 1
if result[j][i] == '2':
print('Found: ' + moves)
print_grid(result, moves)
return True
return False
nums = queue.Queue()
nums.put('')
add = ''
result = read_to_grid()
while not find_goal(result, add):
add = nums.get()
for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
put = add + j
if valid(result, put):
nums.put(put)
Ok Ryan answer already says everything, here however is your code working though is not efficient in anyway, the only things I changed that is worth is that instead of using a list of list you can just use a list, and the valid function now check the traveled path so that it can know where it has been so it won't loop.
import queue
# Read name file from user
def read_user_input():
file_name = input('Enter the name of your file :\n')
return file_name
# Read file and return list of list[10]
def read_to_grid():
with open(read_user_input()) as file:
for nums in file:
line = list(nums.split())
return line
# Shows a text grid
def print_grid(result, path=[]):
for x, pos in enumerate(result):
if pos == '1':
start = x
i = start
#j = 0
pos = set()
for move in path:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
i -= 10
elif move == 'Move Down':
i += 10
pos.add(i)
for i, celd in enumerate(result):
if i % 10 == 0:
print()
if i in pos:
print('# ', end='')
else:
print(celd + ' ', end='')
# Validates coordinates and traveled path
def valid(result, moves):
for x, pos in enumerate(result):
if pos == '1':
start = x
i = start % 10
j = start // 10
# Where we start
travel = [(j,i)]
for move in moves:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
j -= 1
elif move == 'Move Down':
j += 1
# Check if we have already been there
if (j, i) in travel:
return False
else:
travel += [(j,i)]
# Check coordinates
if i >= 10 or i < 0 or j >= len(result) // 10 or j < 0:
return False
elif result[i+j*10] == '3':
return False
return True
# Return true if 2 is reached
def find_goal(result, moves):
for x, pos in enumerate(result):
if pos == '1':
start = x
i = start
#j = 0
for move in moves:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
i -= 10
elif move == 'Move Down':
i += 10
if result[i] == '2':
print('Found: ',' '.join(moves))
print_grid(result, moves[0:-1])
return True
return False
nums = queue.Queue()
result = read_to_grid()
add = []
while not find_goal(result, add):
if not nums.empty():
add = nums.get()
for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
put = add + [j]
if valid(result, put):
nums.put(put)
EDIT:
I cleaned up a little:
import queue
# Read name file from user
def read_user_input():
file_name = input('Enter the name of your file :\n')
return file_name
# Read file and return list of list[10]
def read_to_grid():
with open(read_user_input()) as file:
for nums in file:
line = list(nums.split())
return line
# Shows a text grid
def print_grid(result, path=[]):
pos = set()
for (x,y), _ in path:
i = x + y*10
pos.add(i)
for i, celd in enumerate(result):
if i % 10 == 0:
print()
if i in pos:
print('# ', end='')
else:
print(celd + ' ', end='')
# Validates coordinates and traveled path
def valid(result, moves):
# Unpack
(i,j), _ = moves[-1]
# Check if already traveled
if any(x == i and y == j for (x,y), __ in moves[:-1]):
return False
# Check coordinates
if i >= 10 or i < 0 or j >= len(result) // 10 or j < 0:
return False
elif result[i+j*10] == '3':
return False
return True
# Return true if 2 is reached
def find_goal(result, moves):
# Unpack
(i,j), _ = moves[-1]
if result[i+j*10] == '2':
#Print moves
output = 'Found: '
for (x,y), _ in moves:
output += " "+_
print(output)
#Print grid
print_grid(result, moves[1:-1])
return True
return False
# Return new position and which movement was done.
def move(pos, dir):
(x, y), _ = pos
if dir == 'Move Left':
x -= 1
elif dir == 'Move Right':
x += 1
elif dir == 'Move Up':
y -= 1
elif dir == 'Move Down':
y += 1
return (x, y), dir
nums = queue.Queue()
result = read_to_grid()
# Find the starting position
for x, pos in enumerate(result):
if pos == '1':
start = x
add = [((start % 10, start // 10),'')]
while not find_goal(result, add):
if not nums.empty():
add = nums.get()
for j in ['Move Left', 'Move Right', 'Move Up', 'Move Down']:
put = add + [move(add[-1],j)]
if valid(result, put):
nums.put(put)
Whilst debugging your code I ran into some endless loops and other bugs when it came to your 'valid' and 'find_goal' functions.
In my experience with breadth first search its best to treat each point as a node (coordinates in this case) and to have your queue consist of lists of paths that are currently being tried. Where each path is a list of each node thats transversed. Typically you don't want to visit the same node more than once in a given path so you'll have to keep track of this information rather than soley 'left', 'right', etc...
All that said, I built-off your code and created a function that would return the valid adjacent nodes when given a node accounting for grid bound, not being a 3 and wether the node has been visited or not. Then for the BFS part the queue starts with a list containing the starting node (I made a function to find where the 1 was). Then while a queue exists the BFS will pop off the current path, get the last node in that path, find all valid adjacent nodes. With each valid adjacent node a new path entry will be added to the queue consisting of the old path + the adjacent node. If one of the adjacent nodes is a goal it will end the search and return the path. I've included the directional information in the path so that you can parse that out.
This should print off a path to the nearest 2 as such:
[((5, 0), ''), ((5, 1), 'Down'), ((6, 1), 'Right'), ((6, 2), 'Down'), ((7, 2), 'Right'), ((7, 3), 'Down'), ((7, 4), 'Down'), ((7, 5), 'Down')]
You'll see the ...sorted(path_queue, key=lambda... that line isn't needed but is a lazy way to prioritize the queue, always trying the shortest current path. If you remove it you'll see you still get a valid path but its much longer.
def read_user_input():
file_name = input('Enter the name of your file :\n')
return file_name
def read_to_grid():
file_name = read_user_input()
for nums in open(file_name):
line = list(nums.split())
result = []
for _ in range(0, len(line), 10):
result.append(line[_:_ + 10])
int_result = []
for i, row in enumerate(result):
int_result.append([])
for col in row:
int_result[i].append(int(col))
return int_result
def print_grid(result, path=''):
for x, pos in enumerate(result[0]):
if pos == 0:
start = x
i = start
j = 0
pos = set()
for move in path:
if move == 'Move Left':
i -= 1
elif move == 'Move Right':
i += 1
elif move == 'Move Up':
j -= 1
elif move == 'Move Down':
j += 1
pos.add((j, i))
for j, row in enumerate(result):
for i, col in enumerate(row):
if (j, i) in pos:
print('#', end='')
else:
print(str(col) + ' ', end='')
print()
def find_start_node(grid):
for i, row in enumerate(grid):
if 1 in row:
return ((row.index(1), i), '')
return (None, None)
def valid_adj(cur_node, grid, visited):
x = cur_node[0][0]
y = cur_node[0][1]
adj = []
if ((y + 1) < 10) and (grid[y + 1][x] != 3) and not (any((x, y + 1) in node for node in visited)):
adj.append(((x, y + 1), 'Down'))
if ((x + 1) < 10) and (grid[y][x + 1] != 3) and not (any((x + 1, y) in node for node in visited)):
adj.append(((x + 1, y), 'Right'))
if ((y - 1) >= 0) and (grid[y - 1][x] != 3) and not (any((x, y - 1) in node for node in visited)):
adj.append(((x, y - 1), 'Up'))
if ((x - 1) >= 0) and (grid[y][x - 1] != 3) and not (any((x - 1, y) in node for node in visited)):
adj.append(((x - 1, y), "Left"))
return adj
def BFS(grid):
start_node = find_start_node(grid)
path_queue = [[start_node]]
while path_queue:
path_queue = sorted(path_queue, key=lambda x: len(x), reverse=True) # More optimized to guarantee shortest path, not needed
cur_path = path_queue.pop()
cur_node = cur_path[-1]
if cur_node not in cur_path[:].pop():
adj = valid_adj(cur_node, grid, cur_path)
for node in adj:
new_path = list(cur_path)
new_path.append(node)
path_queue.append(new_path)
if grid[node[0][1]][node[0][0]] == 2:
print('path found')
return new_path
return -1
grid = read_to_grid()
print_grid(grid)
print(BFS(grid))

Connect 4 implemented with python AI alpha-beta-pruning (Recursion/MemoryError)?

I'm trying to create a game called Connect Four with AI which uses the alpha-beta pruning algorithm in python. Here is the code I have managed to write:
# -*- coding: utf-8 -*-
import sys
class ConnectFour:
def __init__(self):
self.board = [[],[],[],[],[],[]]
for i in range(7):
for j in range(6):
self.board[j].append(" ")
self.moves = 0
self.colstack = [0,0,0,0,0,0,0]
self.node = 0
self.move = 0
def PrintGameBoard(self):
print(' 0 1 2 3 4 5 6')
for i in range(5, -1, -1):
print('|---|---|---|---|---|---|---|')
print("| ",end="")
for j in range(7):
print(self.board[i][j],end="")
if j != 6:
print(" | ",end="")
else:
print(" |")
print('`---------------------------ยด')
def CanPlay(self, col):
return self.colstack[col] < 6
def Play(self, col, board):
board[self.colstack[col]][col] = 2
self.colstack[col] += 1
self.moves += 1
return board
def IsWinning(self, currentplayer):
for i in range(6):
for j in range(4):
if self.board[i][j] == currentplayer and self.board[i][j+1] == currentplayer and self.board[i][j+2] == currentplayer and self.board[i][j+3] == currentplayer:
return True
for i in range(3):
for j in range(7):
if self.board[i][j] == currentplayer and self.board[i+1][j] == currentplayer and self.board[i+2][j] == currentplayer and self.board[i+3][j] == currentplayer:
return True
for i in range(3):
for j in range(4):
if self.board[i][j] == currentplayer and self.board[i+1][j+1] == currentplayer and self.board[i+2][j+2] == currentplayer and self.board[i+3][j+3] == currentplayer:
return True
for i in range(3,6):
for j in range(4):
if self.board[i][j] == currentplayer and self.board[i-1][j+1] == currentplayer and self.board[i-2][j+2] == currentplayer and self.board[i-3][j+3] == currentplayer:
return True
return False
def AlphaBeta(self, alpha, beta):
self.node += 1
if self.moves == 42:
return 0
for col in range(7):
if self.CanPlay(col) and self.IsWinning(2):
return (43 - self.moves)/2
max = (41 - self.moves)/2
if beta > max:
beta = max
if alpha >= beta:
return beta
for col in range(7):
if self.CanPlay(col):
self.board[self.colstack[col]][col] = 2
self.move = col
score = -self.AlphaBeta(-alpha, -beta)
if score >= beta:
return score
elif score > alpha:
alpha = score
self.board[self.colstack[col]][col] = " "
def Solve(self, table, week=False):
self.node = 0
self.board = table
if week:
self.AlphaBeta(-1, 1)
self.board = self.Play(self.move, table)
return self.board
else:
self.AlphaBeta(-21, 21)
self.board = self.Play(self.move, table)
return self.board
def PlayerMove(self, table):
self.board = table
try:
allowedmove = False
while not allowedmove:
print("Choose a column where you want to make your move (0-6): ",end="")
col =input()
if self.CanPlay(int(col)):
self.board[self.colstack[int(col)]][int(col)] = 1
self.moves += 1
self.colstack[int(col)] += 1
allowedmove = True
else:
print("This column is full")
except (NameError, ValueError, IndexError, TypeError, SyntaxError) as e:
print("Give a number as an integer between 0-6!")
else:
return self.board
def PlayerMark():
print("Player 1 starts the game")
mark = ''
while not (mark == "1" or mark == "2"):
print('Do you want to be 1 or 2: ',end="")
mark = input()
if mark == "1":
return 1
else:
return 2
def PlayAgain():
print('Do you want to play again? (yes or no) :',end="")
return input().lower().startswith('y')
def main():
sys.setrecursionlimit(2000)
print("Connect4")
while True:
mark = PlayerMark()
connectfour = ConnectFour()
if mark==1:
print("You are going to start the game\r\n\r\n")
else:
print("Computer (negamax) starts the game")
gameisgoing = True
table = [[],[],[],[],[],[]]
for i in range(7):
for j in range(6):
table[j].append(" ")
while gameisgoing:
connectfour.PrintGameBoard()
if mark == 1:
table = connectfour.PlayerMove(table)
if connectfour.IsWinning(1):
connectfour.PrintGameBoard()
print('You won the game!')
gameisgoing = False
else:
if connectfour.moves==42:
connectfour.PrintGameBoard()
print('Game is tie')
break
else:
mark = 2
else:
move = connectfour.Solve(table)
if connectfour.IsWinning(2):
connectfour.PrintGameBoard()
print('Computer won the game')
gameisgoing = False
else:
if connectfour.moves==42:
connectfour.PrintGameBoard()
print('Game is tie')
break
else:
mark = 1
if not PlayAgain():
print("Game ended")
break
if __name__ == '__main__':
main()
I'm not sure if the algorithm is working properly, but the problem is that I get RecursionError: maximum recursion depth exceeded in comparison. If I increase recursionlimit I get in around 10000 MemoryError: Stack Overflow. I think that the problem is that there is too many states to handle for my computer. That's why I changed the negamax algorithm to alpha-beta pruning, but there seem to be still to many states. Is there a effective technique that does algorithmic search, for example max depth 10 and still the computer is almost invincible? I'm waiting your solutions.

Python minimax for tictactoe

After completely failing the minimax implementation for tic tac toe, I fail to see what's wrong. Right now, my AI just goes around in a circle...
import collections
class InvalidLocationError(Exception): pass
import copy
class Board(object):
def __init__(self, board=None):
if board is None:
self.clear()
else:
self._board = board[:]
def place(self, i, row, column):
if not ((0 <= row <= 2) and (0 <= column <= 2)):
raise InvalidLocationError("Invalid Location.")
if self._board[row][column]:
raise InvalidLocationError("There's already a piece there")
self._board[row][column] = i
return self.checkVictory()
def check(self, row, column):
return self._board[row][column]
def checkVictory(self, board=None):
if board is None:
board = self._board
draw = True
for i in xrange(3):
r = self.rowcount(i)
c = self.colcount(i)
if i < 3:
d = self.diagcount(i)
else:
d = {0: 0, 1: 0, 2: 0}
for j in xrange(1, 3):
if d[j] == 3 or r[j] == 3 or c[j] == 3:
return j
if r[0] > 0 or c[0] > 0:
draw = False
if draw:
return -1
return 0
def rowcount(self, row):
return collections.Counter(self._board[row])
def colcount(self, col):
return collections.Counter([self._board[i][col] for i in xrange(3)])
def diagcount(self, left=True):
if left:
a = [self._board[0][0], self._board[1][1], self._board[2][2]]
else:
a = [self._board[0][2], self._board[1][1], self._board[2][0]]
return collections.Counter(a)
def clear(self):
self._board = ([0, 0, 0], [0, 0, 0], [0, 0, 0])
def __str__(self):
return "\n".join(map(lambda x: " ".join(map(lambda y : str(y), x)), self._board))
#staticmethod
def flipPiece(p):
return int(not (p - 1)) + 1
class AI(object):
class Node(object):
def __init__(self, board, nextMove):
self.board = board
self.nextMove = nextMove
self.paths = []
self.score = None
template = self.board._board[:]
for r, row in enumerate(template):
for c, val in enumerate(row):
if val == 0:
template[r][c] = nextMove
self.paths.append(copy.deepcopy(template))
template[r][c] = 0
def __init__(self, mypiece, depth=8):
self.mypiece = mypiece
self.enemypiece = Board.flipPiece(mypiece)
self.depth = depth
def decide(self, board):
startNode = self.Node(board, self.mypiece)
best = self.minimax(startNode, self.depth)
for node in startNode.paths:
if node.value == best:
break
found = False
for row in xrange(3):
for col in xrange(3):
if board.check(row, col) != node.board.check(row, col):
found = True
break
if found:
break
print row, col
return row, col
def minimax(self, node, depth):
victory = node.board.checkVictory()
if victory:
if victory == self.mypiece:
h = 1
elif victory == -1:
h = 0
else:
h = -1
node.value = h
return h
if depth <= 0:
# h = self.heuristic(node.board, node.nextMove) # This is to the heuristic, which uses nextMove to evalate.
node.value = 0
return 0
h = -1
flip = Board.flipPiece(node.nextMove)
for i, board in enumerate(node.paths):
node.paths[i] = self.Node(Board(board), flip) # This is to the Node, which takes the nextMove of itself (which translates to the next next move from the current node)
score = self.minimax(node.paths[i], depth-1)
h = max(h, score) if node.nextMove == self.mypiece else min(h, score)
node.value = h
return h
Why is this happening?

Categories