// generate the children of state 'node' // state is the GameState after node's moves have been performed void UCTSearch::generateChildren(UCTNode & node, GameState & state) { // figure out who is next to move in the game const IDType playerToMove(getPlayerToMove(node, state)); // generate all the moves possible from this state state.generateMoves(_moveArray, playerToMove); _moveArray.shuffleMoveActions(); // generate the 'ordered moves' for move ordering generateOrderedMoves(state, _moveArray, playerToMove); // for each child of this state, add a child to the current node for (size_t child(0); (child < _params.maxChildren()) && getNextMove(playerToMove, _moveArray, child, _actionVec); ++child) { // add the child to the tree node.addChild(&node, playerToMove, getChildNodeType(node, state), _actionVec, _params.maxChildren(), _memoryPool ? _memoryPool->alloc() : NULL); _results.nodesCreated++; } }
AlphaBetaValue AlphaBeta::alphaBeta(GameState & state, size_t depth, const IDType lastPlayerToMove, const MoveTuple * prevSimMove, AlphaBetaScore alpha, AlphaBetaScore beta) { // update statistics _results.nodesExpanded++; if (searchTimeOut()) { throw 1; } if (terminalState(state, depth)) { // return the value, but the move will not be valid since none was performed AlphaBetaScore evalScore = state.eval(_params.maxPlayer(), _params.evalMethod(), _params.modelSimMethod()); return AlphaBetaValue(AlphaBetaScore(evalScore.val(), state.getNumMovements(_params.maxPlayer()) + evalScore.numMoves() ), AlphaBetaMove(0, false)); } // figure out which player is to move const IDType playerToMove(getPlayerToMove(state, depth, lastPlayerToMove, !prevSimMove)); // is the player to move the max player? bool maxPlayer = (playerToMove == _params.maxPlayer()); // Transposition Table Logic TTLookupValue TTval; if (isTranspositionLookupState(state, prevSimMove)) { TTval = TTlookup(state, alpha, beta, depth); // if this is a TT cut, return the proper value if (TTval.cut()) { return AlphaBetaValue(TTval.entry()->getScore(), getAlphaBetaMove(TTval, playerToMove)); } } bool bestMoveSet(false); // move generation MoveArray & moves = _allMoves[depth]; state.generateMoves(moves, playerToMove); generateOrderedMoves(state, moves, TTval, playerToMove, depth); // while we have more simultaneous move tuples AlphaBetaMove bestMove, bestSimResponse; MoveTuple numMoveTuples(getNumMoveTuples(moves, TTval, playerToMove, depth)); for (MoveTuple t(0); t < numMoveTuples; ++t) { // get the tuple that will be implemented const MoveTuple tuple = getNextMoveTuple(t, depth); // the value of the recursive AB we will call AlphaBetaValue val; // generate the child state GameState child(state); bool firstMove = true; // if this is the first player in a simultaneous move state if (state.bothCanMove() && !prevSimMove && (depth != 1)) { firstMove = true; // don't generate a child yet, just pass on the move we are investigating val = alphaBeta(state, depth-1, playerToMove, &tuple, alpha, beta); } else { firstMove = false; // if this is the 2nd move of a simultaneous move state if (prevSimMove) { // do the previous move tuple selected by the first player to move during this state doTupleMoves(child, _allMoves[depth+1], *prevSimMove); } // do the moves of the current player doTupleMoves(child, moves, tuple); child.finishedMoving(true); // get the alpha beta value val = alphaBeta(child, depth-1, playerToMove, NULL, alpha, beta); } // set alpha or beta based on maxplayer if (maxPlayer && (val.score() > alpha)) { alpha = val.score(); bestMove = AlphaBetaMove(tuple, true); bestMoveSet = true; if (state.bothCanMove() && !prevSimMove) { bestSimResponse = val.abMove(); } // if this is depth 1 of the first try at depth 1, store the best in results } else if (!maxPlayer && (val.score() < beta)) { beta = val.score(); bestMove = AlphaBetaMove(tuple, true); bestMoveSet = true; if (state.bothCanMove() && prevSimMove) { bestSimResponse = val.abMove(); } } if (alpha.val() == -10000000 && beta.val() == 10000000) { fprintf(stderr, "\n\nALPHA BETA ERROR, NO VALUE SET\n\n"); } // alpha-beta cut if (alpha >= beta) { break; } } if (isTranspositionLookupState(state, prevSimMove)) { TTsave(state, maxPlayer ? alpha : beta, alpha, beta, depth, playerToMove, bestMove, bestSimResponse); } return maxPlayer ? AlphaBetaValue(alpha, bestMove) : AlphaBetaValue(beta, bestMove); }
void AlphaBeta::generateOrderedMoves(GameState & state, MoveArray & moves, const TTLookupValue & TTval, const IDType & playerToMove, const size_t & depth) { // get the array where we will store the moves and clear it Array<MoveTuple, Search::Constants::Max_Ordered_Moves> & orderedMoves(_orderedMoves[depth]); orderedMoves.clear(); // if we are using opponent modeling, get the move and then return, we don't want to put any more moves in if (_params.usePlayerModel(playerToMove)) { MoveTuple playerModelMove = _params.getPlayer(playerToMove)->getMoveTuple(state, moves); orderedMoves.add(playerModelMove); return; } // if there is a transposition table entry for this state if (TTval.found()) { // get the abMove we stored for this player const AlphaBetaMove & abMove = getAlphaBetaMove(TTval, playerToMove); // here we get an incorrect move from the transposition table if (abMove.moveTuple() >= moves.numMoveTuples()) { HashType h0 = state.calculateHash(0); HashType h1 = state.calculateHash(1); MoveArray moves2; state.generateMoves(moves2, playerToMove); // figure out why //fprintf(stderr, "Something very wrong, this tuple (%d) doesn't exist, only (%d) moves\n", (int)abMove.moveTuple(), (int)moves.numMoveTuples()); } _results.ttFoundCheck++; // Two checks: // 1) Is the move 'valid' ie: was it actually set inside the TT // 2) Is it a valid tuple number for this move set? This guards against double // hash collision errors. Even if it is a collision, this is just a move // ordering, so no errors should occur. if (abMove.isValid() && (abMove.moveTuple() < moves.numMoveTuples())) { orderedMoves.add(abMove.moveTuple()); _results.ttMoveOrders++; return; } else { _results.ttFoundButNoMove++; } } // if we are using script modeling, insert the script moves we want if (_params.useScriptMoveFirst()) { for (size_t s(0); s<_allScripts.size(); s++) { MoveTuple scriptMove = _allScripts[s]->getMoveTuple(state, moves); orderedMoves.addUnique(scriptMove); } } }