/** Test of insert method, of class TranspositionTable. */ @Test public void testInsert() throws ChessParseError { System.out.println("insert"); TranspositionTable tt = new TranspositionTable(16); Position pos = TextIO.readFEN(TextIO.startPosFEN); String[] moves = { "e4", "e5", "Nf3", "Nc6", "Bb5", "a6", "Ba4", "b5", "Bb3", "Nf6", "O-O", "Be7", "Re1" }; UndoInfo ui = new UndoInfo(); for (int i = 0; i < moves.length; i++) { Move m = TextIO.stringToMove(pos, moves[i]); pos.makeMove(m, ui); int score = i * 17 + 3; m.score = score; int type = TranspositionTable.TTEntry.T_EXACT; int ply = i + 1; int depth = i * 2 + 5; tt.insert(pos.historyHash(), m, type, ply, depth, score * 2 + 3); } pos = TextIO.readFEN(TextIO.startPosFEN); for (int i = 0; i < moves.length; i++) { Move m = TextIO.stringToMove(pos, moves[i]); pos.makeMove(m, ui); TranspositionTable.TTEntry ent = tt.probe(pos.historyHash()); assertEquals(TranspositionTable.TTEntry.T_EXACT, ent.type); int score = i * 17 + 3; int ply = i + 1; int depth = i * 2 + 5; assertEquals(score, ent.getScore(ply)); assertEquals(depth, ent.getDepth()); assertEquals(score * 2 + 3, ent.evalScore); Move tmpMove = new Move(0, 0, 0); ent.getMove(tmpMove); assertEquals(m, tmpMove); } }
/** Some nodes may have their biases updated. */ @Override public void descend(McRunnable runnable) { SearchNode node = getRoot(); assert node != null : "Fancy hash code: " + board.getFancyHash(); while (runnable.getBoard().getPasses() < 2) { selectAndPlayMove(node, runnable); final SearchNode child = table.findIfPresent(runnable.getBoard().getFancyHash()); if (child == null) { return; // No child } if (child.getTotalRuns() > biasDelay && !child.biasUpdated()) { child.updateBias(runnable); } node = child; } }
@Override public void fakeDescend(McRunnable runnable, short... moves) { runnable.copyDataFrom(board); final SearchNode node = getRoot(); assert node != null : "Fancy hash code: " + board.getFancyHash(); for (final short move : moves) { System.out.println("Passing " + move + " to runnable"); runnable.acceptMove(move); final SearchNode child = table.findIfPresent(runnable.getBoard().getFancyHash()); if (child == null) { return; // No child } if (child.getTotalRuns() > biasDelay && !child.biasUpdated()) { child.updateBias(runnable); } } }
/** Returns the root node (creating it if necessary). */ SearchNode getRoot() { return table.findOrAllocate(board.getFancyHash()); }
/** * Negamax algorithm with alpha-beta pruning. * * @param s the root of this subtree * @param depth the depth to which the subtree below this node will be searched * @param alpha alpha (lower) bound * @param beta beta (upper) bound * @param color the negamax "color", signifying which player's turn it is * @param startTime system time at which the iterative deepening cycle started * @return */ protected Move alphaBeta(State s, int depth, long alpha, long beta, int color, long startTime) { /* Iterative deepening time limit has run out */ if (System.currentTimeMillis() - startTime >= timeLimit * 1000) { return new TimeOutMove(); } boolean insufficientDepth = false; highestForcedSearchDepth = Math.max(depth, highestForcedSearchDepth); long origAlpha = alpha; Move ttMove = null; TranspositionTable.TableCheckResultTypes ttentryflag = tt.checkTable(s); /* storedEntry is the entry returned by checking the transposition table. If the primary hash key was not present in the table, storedEntry is a new table entry with default values except for its hash code. */ TableEntry storedEntry = tt.getCachedEntry(); if (ttentryflag == TranspositionTable.TableCheckResultTypes.VALID) { /* This value had already been stored in TT. Check searched depth */ if (storedEntry.getSearchDepth() >= this.iterationSearchDepth - depth) { /* Exact value for this state was stored in TT at an appropriate search depth. Adapt current search bounds according to stored values. */ if (storedEntry.getValueFlag() == TranspositionTable.TableValueFlagTypes.EXACT) { /* No need to adapt bounds: already have exact value for this state */ return new NullMove(storedEntry.getStateValue()); } else if (storedEntry.getValueFlag() == TranspositionTable.TableValueFlagTypes.LOWER) { /* Stored value is a lower bound and exceeds lower bound in this search line: adapt current alpha accordingly. */ alpha = Math.max(alpha, storedEntry.getStateValue()); } else if (storedEntry.getValueFlag() == TranspositionTable.TableValueFlagTypes.UPPER) { /* Stored value is an upper bound and is lower than upper bound in this search line: adapt current beta accordingly. */ beta = Math.min(beta, storedEntry.getStateValue()); } if (alpha >= beta) { /* By looking at the stored bounds for this nodes, we have determined an exact value for this node. Return this value. */ return new NullMove(storedEntry.getStateValue()); } } /* Can't use retrieved value directly: node in table has not been investigated as deeply as current search line. Need to search using current parameters. However, can use stored best move as first try. */ insufficientDepth = true; ttMove = storedEntry.getBestMove(); } long score = Integer.MIN_VALUE; List<Move> moves = this.negamaxMoveGeneration(s, depth); nodesExpanded++; Move bestMove = null; if (insufficientDepth) { int removeIndex = 0; for (int i = 0; i < moves.size(); i++) { if (moves.get(i).hashCode() == ttMove.hashCode()) { removeIndex = i; } } moves.remove(removeIndex); moves.add(0, ttMove); } // Don't perform search if there is only one legal move if (depth == 0 && moves.size() == 1) { iterationSearchDepth = IDNegamax.maxSearchDepth; Move m = moves.iterator().next(); return m; } // No legal moves in this state, current player loses game if (moves.isEmpty()) { return new NullMove(color * Integer.MIN_VALUE); } // Opponent has no more moves, win for current player. if (s.getOpponentPieces().isEmpty()) { return new NullMove(color * Integer.MAX_VALUE); } // Leaf node reached, return evaluation of current state if (depth >= iterationSearchDepth) { return new NullMove(color * evaluator.evaluate(s)); } else { for (Move m : moves) { /* Move to next layer of tree by executing this move on copy of current state and performing alpha-beta search on new state. */ State copyState = new State(s); m.execute(copyState); copyState.switchPlayers(); Move valueMove = alphaBeta(copyState, depth + 1, -beta, -alpha, -color, startTime); if (valueMove instanceof TimeOutMove) { return valueMove; } m.setValue(-valueMove.getValue()); /* For iterative deepening, save root moves for ordering in next iteration. */ if (depth == 0) this.disposeOfRootMove(m); /* Update bounds and perform cutoff. */ if (-valueMove.getValue() > score) { bestMove = m; score = -valueMove.getValue(); } if (score > alpha) alpha = score; if (score >= beta) break; } } /* TODO: need to check when this situation occurs */ if (bestMove instanceof NullMove || bestMove == null) { bestMove = moves.iterator().next(); bestMove.setValue(color * Integer.MIN_VALUE); } /* Precompute some values to be stored in transposition table entry */ TranspositionTable.TableValueFlagTypes entryFlag = null; if (bestMove.getValue() <= origAlpha) { entryFlag = TranspositionTable.TableValueFlagTypes.UPPER; } else if (bestMove.getValue() >= beta) { entryFlag = TranspositionTable.TableValueFlagTypes.LOWER; } else { entryFlag = TranspositionTable.TableValueFlagTypes.EXACT; } final int entryDepth = iterationSearchDepth - depth; storedEntry.setStateValue(bestMove.getValue()); storedEntry.setValueType(entryFlag); storedEntry.setSearchDepth(entryDepth); storedEntry.setBestMove(bestMove); return bestMove; }