Search code examples
algorithmminimax

Negamax Algorithm - Make/Unmake vs Copy


I'm a bit confused about Negamax algorithm and how to apply it in real cases. On the net i have found the following C/C++ code (ref: https://chessprogramming.wikispaces.com/Unmake+Move)

int negaMax( int depth ) {
    if ( depth == 0 ) return evaluate();
    int max = -oo;
    generateMoves(...);
    while ( m = getNextMove(...) )  {
        makeMove(m); 
        score = -negaMax( depth - 1 );
        unmakeMove(m); 
        if( score > max )
            max = score;
    }
    return max;
}

As far as I can see the "board" instance changes every time makeMove(m) and unmakeMove(m) are called, but no copies of board seem to be created.

[1] Am I right about this concept or i miss something?

Now, I found a Negamax python implementation too (ref: http://blogs.skicelab.com/maurizio/connect-four.html)

def negamax(board, depth):
    if check_end(board) is not None:
        return endscore(board)

    if depth <= 0:
        return [], evaluate(board)

    bestmove = []
    bestscore = -INF
    for m in board.moves():
        nextmoves, score = negamax(board.move(m), depth-1)
        score = -score
        if not bestmove or score >= bestscore:
            bestscore = score
            bestmove = [m] + nextmoves

    return bestmove, bestscore

In this second case I suppose the board.move(m) call returns a copy (so a new instance of the board object). That's why Negamax function requires 2 arguments instead of one.

[2] Am I right again?

[3] If both [1] and [2] are correct, which way is generally faster? If board representation is very simple (i suppose a Board class as an array wrapper) i guess a copy is preferred, but otherwise I'll go for make/unmake.

Thanks in advance!

*************** EDIT ***************

[4] In make / unmake solution

makeMove(m); 
score = -negaMax( depth - 1 );
unmakeLastMove(); 

has a different behaviour than

makeMove(m); 
score = -negaMax( depth - 1 );
unmakeMove(m);

code?

Since my board class stores the list of moves, I thought unmakeLastMove() worked as well as unmakeMove(m). But that's not a good thought, right?


Solution

  • It depends on the game, what details are you storing with the board, etc.

    Basically, the true answer is going to be: implement both and profile the solutions.

    Make/Unmake may get complicated in case you have some extra information attached to the board, if your scoring is not trivial and you carry some precalculated values, if the moves have complicated rules and your undo information is around the same side as the board itself, etc.

    Cloning the board may also be simpler in case of some language which encourages immutable data sharing, because you don't copy everything. Cloning won't preserve your moves history however so you'll have to keep that on a side if you're interested.

    Both ways work differently for different cases. Just think about what implementation each way implies and/or compare the two results.