stensootla
stensootla

Reputation: 14845

Chess negamax function

Hi!

I'm trying to write a negamax search algorithm for my chess engine, but i can't seem to get it working. I'm using wikipedias pseudocode as an example, but somehow it doesn't produce the expected results. When i run it with a ply of 2, it changes my board data structure, although it shouldn't. After the function is finished running with ply 2, all of the white's (or black's. depends what player calls the function.) pawns are moved 2 spaces forward from starting position.

My make and unmake move functions are working perfectly , as i tested them with a non-recursive function that searches up to 5-ply. Then, it worked perfectly. There must be something wrong with my negamax implementation.

Thank you very much for your help!

def negaMax(self, board, rules, ply, player):
        """ Implements a minimax algorithm. """
        if ply == 0:
            return self.positionEvaluation()

        self.max_eval = float('-infinity')

        self.move_list = board.generateMoves(rules, player)
        for self.move in self.move_list:
            board.makeMove(self.move, player)
            self.eval = -self.negaMax(board, rules, ply - 1, board.getOtherPlayer(player))
            board.unmakeMove(self.move, player)

            if self.eval > self.max_eval:
                self.max_eval = self.eval

        return self.max_eval

Upvotes: 4

Views: 1832

Answers (1)

amit
amit

Reputation: 178431

The main issue here is I believe the usage of object variables instead of local variable.

self.move is an object variable, every time you change it - every level of the recursion "sees" the change, which is usually a bad thing for recursive algorithms.

Recursive algorithms should be self contained, and do minimal if any change on the calling environment - it makes it much easier to walk over the flow of the algorithm.

The two main issues I see in this code are:

  1. self.move should be a local variable (declare it as move). When you later do: board.unmakeMove(self.move, player) - I suspect that the board is undoing a different move, which was set deeper in the recursion tree, and not the one you intended. Using a local variable will eliminate this undesired behavior.
  2. Each level of the recursive call is setting self.max_eval = float('-infinity') - so you constantly change it, though it is probably not what you want to do.

The solution should be something like that:

def negaMax(self, board, rules, ply, player):
        """ Implements a minimax algorithm. """
        if ply == 0:
            return self.positionEvaluation()

        max_eval = float('-infinity')

        move_list = board.generateMoves(rules, player)
        for move in move_list:
            board.makeMove(move, player)
            currentEval = -self.negaMax(board, rules, ply - 1, board.getOtherPlayer(player))
            board.unmakeMove(move, player)

            if currentEval > max_eval:
                max_eval = currentEval 
        return max_eval

I am not 100% certain it will indeed solve everything in the code (but it will solve some of it) but I am 100% certain avoiding object variables will make your code much easier to understand and debug.

Upvotes: 4

Related Questions