/** Returns the best move to make from here during a playout. */ short bestSearchMove(SearchNode node, McRunnable runnable) { final Board runnableBoard = runnable.getBoard(); final MersenneTwisterFast random = runnable.getRandom(); short result = node.getWinningMove(); if (result != NO_POINT && runnableBoard.isLegal(result)) { // The isLegal() check is necessary to avoid superko violations return result; } float bestSearchValue = searchValue(node, PASS); result = PASS; final ShortSet vacantPoints = runnableBoard.getVacantPoints(); int start; start = random.nextInt(vacantPoints.size()); int i = start; final int skip = PRIMES[random.nextInt(PRIMES.length)]; do { final short move = vacantPoints.get(i); final float searchValue = searchValue(node, move); if (searchValue > bestSearchValue) { if (runnable.isFeasible(move) && runnableBoard.isLegal(move)) { bestSearchValue = searchValue; result = move; } else { node.exclude(move); } } // Advancing by a random prime skips through the array // in a manner analogous to double hashing. i = (i + skip) % vacantPoints.size(); } while (i != start); return result; }
public McRunnable(Player player, CopiableStructure stuff) { LgrfTable table = null; try { table = stuff.get(LgrfTable.class); } catch (final IllegalArgumentException e) { // If we get here, we're not using LGRF } final CopiableStructure copy = stuff.copy(); board = copy.get(Board.class); coords = board.getCoordinateSystem(); suggesters = copy.get(Suggester[].class); weights = copy.get(int[].class); this.player = player; random = new MersenneTwisterFast(); mover = copy.get(Mover.class); if (table != null) { final LgrfSuggester lgrf = copy.get(LgrfSuggester.class); lgrf.setTable(table); } scorer = copy.get(ChinesePlayoutScorer.class); mercyObserver = copy.get(StoneCountObserver.class); historyObserver = copy.get(HistoryObserver.class); filter = copy.get(Predicate.class); fancyHashes = new long[coords.getMaxMovesPerGame() + 1]; playedPoints = new ShortSet(coords.getFirstPointBeyondBoard()); }
/** * Plays moves to the end of the game and returns the winner: BLACK, WHITE, or (in rare event of a * tie or a playout canceled because it hits the maximum number of moves) VACANT. * * @param mercy True if we should abandon the playout when one color has many more stones than the * other. */ public Color playout(boolean mercy) { do { if (board.getTurn() >= coords.getMaxMovesPerGame()) { // Playout ran out of moves, probably due to superko return VACANT; } if (board.getPasses() < 2) { selectAndPlayOneMove(); } if (board.getPasses() >= 2) { // Game ended return scorer.winner(); } final Color mercyWinner = mercyObserver.mercyWinner(); if (mercy && mercyWinner != null) { // One player has far more stones on the board return mercyWinner; } } while (true); }
public Color performMcRun(boolean mercy, Board originalBoard) { player.descend(this); Color winner; if (originalBoard.getPasses() == 2) { winner = scorer.winner(); } else { winner = playout(mercy); } player.updateTree(winner, this); playoutsCompleted++; return winner; }
@Override public short bestPlayMove() { double mostWins = 1; short result = PASS; final ShortSet vacantPoints = board.getVacantPoints(); final SearchNode root = getRoot(); do { mostWins = root.getWins(PASS); // If the move chosen on the previous pass through this loop was // illegal (e.g., because it was never actually tried in a playout), // throw it out if (result != PASS) { log("Rejected " + board.getCoordinateSystem().toString(result) + " as illegal"); root.exclude(result); result = PASS; } for (int i = 0; i < vacantPoints.size(); i++) { final short move = vacantPoints.get(i); if (root.getWins(move) > mostWins) { mostWins = root.getWins(move); result = move; } } } while (result != PASS && !board.isLegal(result)); // Consider resigning if (root.getWinRate(result) < RESIGN_PARAMETER) { return RESIGN; } log( "Selected " + board.getCoordinateSystem().toString(result) + " with " + root.getWins(result) + " wins in " + root.getRuns(result) + " runs"); return result; }
/** 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()); }
/** Returns the current turn number on this runnable's board. */ public int getTurn() { return board.getTurn(); }
/** Copies data from that (the player's real board) to the local board. */ public void copyDataFrom(Board that) { board.copyDataFrom(that); fancyHashes[board.getTurn()] = board.getFancyHash(); }
/** * Accepts (plays on on this McRunnable's own board) the given move. * * @see edu.lclark.orego.core.Board#play(short) */ public void acceptMove(short p) { final Legality legality = board.play(p); assert legality == OK : "Legality " + legality + " for move " + coords.toString(p) + "\n" + board; fancyHashes[board.getTurn()] = board.getFancyHash(); }