-1

So, I'm writing a really simple TicTacToe program that uses alpha-beta pruning to search for the next move against the player but ran into a problem anytime I want to run it. I've tried anything that came to mind to solve it and even made a Java line by line equivalent of the logic that runs perfectly but the python one just doesn't work.

This is my code:

#encoding: UTF-8
#Andres de Lago Gomez
#A01371779

from random import randint
from utils import *


def getEnemy(who):
    return 1 if who==2 else 2


def getLetter(who):
    return "X" if who==1 else "O"


class TicTacToe:
    winningCombos =[[0, 1, 2], [3, 4, 5],
                    [6, 7, 8], [0, 3, 6],
                    [1, 4, 7], [2, 5, 8],
                    [0, 4, 8], [2, 4, 6]]
    board = [0 for i in range(9)]
    player = 1
    cpu = 2

    def __init__(self):
        pass

    def makeMove(self, who, pos):
        self.board[pos] = who

    def setCpu(self, who):
        self.player = who
        self.cpu = getEnemy(who)

    def availableMoves(self):
        return [i for i in range(9) if self.board[i]==0]

    def isOver(self):
        if 0 not in self.board:
            return True
        if self.Winner():
            return True
        return False

    def Winner(self):
        for i in [1,2]:
            positions = self.getSquares(i)
            for combo in self.winningCombos:
                win = True
                for pos in combo:
                    if pos not in positions:
                        win = False
                if win:
                    return i
        return False

    def getSquares(self, player):
        return [i for i in range(9) if self.board[i]==player]

    def alphaBeta(self, player, alpha, beta):
        if self.isOver():
            return self.evaluateNode()
        for move in self.availableMoves():
            self.makeMove(move, player)
            value = self.alphaBeta(getEnemy(player),alpha,beta)
            self.makeMove(move, 0)
            if player == self.cpu:
                if alpha < value:
                    alpha = value
                if alpha >=beta:
                    return beta
            else:
                if beta > value:
                    beta = value
                if beta <= alpha:
                    return alpha
        return alpha if player==self.cpu else beta

    def evaluateNode(self):
        tmp = self.Winner()
        if tmp==self.cpu:
            return 1
        elif tmp==self.player:
            return -1
        else:
            return 0

    def getNextMove(self, player):
        test = -5
        possibleMoves = []
        for move in self.availableMoves():
            self.makeMove(player, move)
            value = self.alphaBeta(getEnemy(player), -5, 5)
            self.makeMove(0, move)
            if value>test:
                test = value
                possibleMoves = [move]
            elif value == test:
                possibleMoves.append(move)
        return possibleMoves[randint(0, len(possibleMoves)-1)]

class Tile(pygame.sprite.Sprite):

    def __init__(self, rect, screen, player="X"):
        pygame.sprite.Sprite.__init__(self)
        self.rect = rect
        self.images = [load_png(get_path()+"\\data\\TicTacToe\\"+player+"Tile\\{0:0>2d}.png".format(x)) for x in range(21)]
        clock = pygame.time.Clock()
        for i in range(21):
            self.image = self.images[i]
            screen.blit(self.image, self.rect)
            pygame.display.update(self.rect)
            clock.tick(80)

class GUI:

    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode([100,100])
        self.dir = get_path()
        #get background
        self.background = pygame.image.load(self.dir+"\\data\\TicTacToe\\Background.png")
        self.background.convert()
        self.tiles = pygame.sprite.Group()
        #blit to screen
        self.screen = pygame.display.set_mode(self.background.get_size())
        self.screen.blit(self.background,(0,0))
        pygame.display.update()
        #create tiles
        self.places = [pygame.Rect((15+165*x, 110+165*y), (150,150) )for x in range(3) for y in range (3)]
        #create game
        self.board = TicTacToe()
        done = False
        goFirstI = load_png(get_path()+"\\data\\TicTacToe\\GoFirst.png")
        goFirstR = pygame.Rect((0,0), (400,200))
        goFirstR.center = self.places[4].center
        self.screen.blit(goFirstI,goFirstR)
        pygame.display.update(goFirstR)
        yesR = pygame.Rect((97,382),(115,45))
        noR = pygame.Rect((297,382),(115,45))
        while not done:
            for event in pygame.event.get():
                if event.type == pygame.MOUSEBUTTONUP:
                    if event.button == 1:
                        if yesR.collidepoint(event.pos):
                            done = True
                            self.board.setCpu(1)
                        elif noR.collidepoint(event.pos):
                            done = True
                            self.board.setCpu(2)
        self.screen.blit(self.background,(0,0))
        pygame.display.update(goFirstR)
        #start clock
        clock = pygame.time.Clock()
        done = False
        turn = 1
        bdown = 0
        while not done:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    done = True
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        pass    #pause menu?
                elif event.type == pygame.MOUSEBUTTONUP:
                    if event.button == 1:
                        bdown = event
            if turn == self.board.player:
                if bdown != 0:
                    index = 0
                    for rect in self.places:
                        if rect.collidepoint(bdown.pos):
                            moves = self.board.availableMoves()
                            if index in moves:
                                self.board.makeMove(turn, index)
                                self.tiles.add(Tile(rect, self.screen, getLetter(self.board.player)))
                                turn = getEnemy(turn)
                        index += 1
            else:
                pos = self.board.getNextMove(self.board.cpu)
                self.board.makeMove(turn, pos)
                self.tiles.add(Tile(self.tiles[pos], self.screen, getLetter(self.board.cpu)))

            clock.tick(60)

a = GUI()

The error I get is: RuntimeError: maximum recursion depth exceeded

I would really apreciate an insight of what is causing it.

Andres de Lago
  • 111
  • 1
  • 9
  • you can figure this out fairly easily by printing or logging the function name as the first line of every function. – cmd Oct 28 '15 at 20:21
  • @cmd I do know what function causes the error, the thing is it shouldn't. The function is alphaBeta, the only one that has recursion – Andres de Lago Oct 29 '15 at 14:46
  • then print the state along with the function name and see why it is behaving that way – cmd Oct 29 '15 at 19:15
  • @cmd Actually I found the problem, I was handling wrong the moves inside the alphaBeta function in such a way that it neverreached a win, lose or tie and that was it. Fixed it and runs smoothly. Thank you – Andres de Lago Oct 30 '15 at 20:03

1 Answers1

1

Python and Java handle recursion differently. Python has rather large stackframes, so does a hard limit on how many recursions, or how many times your code can call itself, before crashing and throwing that error. In your code, this is your def alphaBeta(self, player, alpha, beta) method which itself calls: value = self.alphaBeta(getEnemy(player),alpha,beta)

In Java, this is a bit different, as Java does a bit more optimization. This means you can have more recusive calls before getting a Java Stack Overflow error.

Your best bet would be to change your code from a recursive function into an iterative one. I.E., change your alphaBeta method so that instead of calling itself, it uses a for or while loop to do the same thing

Darendal
  • 843
  • 9
  • 29
  • Actually I just fixed it. I had a problem with the way I was handling moves and fixed it accidentally while converting it to java. But thank you anyways – Andres de Lago Oct 29 '15 at 18:33