/** * Somewhat multi-threaded depth-first search. Performs a DFS of the subtrees from the current * node in parallel. * * @param s The tile sequence * @param b The board to search * @param pool The thread pool in which to submit jobs to. * @return The board with the highest evaluation or null if no board can continue. */ private Board solve_pdfs(Board b, ExecutorService pool) { List<Future<Board>> rets = new ArrayList<>(BOARD_WIDTH); Board best = null; int best_score = -1; for (Direction d : directions) { Board n = new Board(b); if (n.move(tileSequence, d)) { rets.add(pool.submit(new ParallelDFS(n))); } } for (Future<Board> ret : rets) { try { Board c = ret.get(); if (c != null) { int score = evaluate(c); if (score > best_score) { best = c; best_score = score; } } } catch (InterruptedException | ExecutionException e) { System.err.println("Error: " + e.getMessage()); } } return best; }
private Board solve_dfs(Board b, int depthLimit, int depth) { if (depth >= depthLimit) { // Cutoff test return b; } Board best = null; int best_score = -1; for (int i = 0; i < BOARD_WIDTH; i++) { Board next = new Board(b); if (next.move(tileSequence, directions[i])) { Board candidate = solve_dfs(next, depthLimit, depth + 1); if (candidate == null && next.finished()) { candidate = next; } if (candidate != null) { if (candidate.finished()) { updateBest(candidate); } else { int score = evaluate(candidate); if (score > best_score) { best_score = score; best = candidate; } } } } } return best; }
private synchronized void updateBest(Board b) { int score = b.score(); if (score > fbest_score) { fbest_score = score; fbest = b; } }
/** * Single threaded depth-limited depth first search, with no backtracking. * * @param s The tile sequence * @param b The board to search * @return The final best board state that it can find. */ private Board solve_ldfs(Board b) { Board input = b; fbest_score = -1; fbest = null; currentfactors = factors; while (b != null && !b.finished()) { b = solve_dfs(b, MAX_DEPTH, 0); if (b != null) { log_info(b); } } // Super edge-case: The input board can't be moved... return fbest == null ? input : fbest; }
/** * Multi-threaded depth-limited depth first search, with no backtracking. * * @param s The tile sequence * @param b The board to search * @param pool The thread pool * @return The final best board state that it can find. */ private Board solve_pndfs(Board b, ExecutorService pool) { Board input = b; fbest_score = -1; fbest = null; currentfactors = factors; while (b != null && !b.finished()) { b = solve_pdfs(b, pool); if (b != null) { // log_info(b); } } // Super edge-case: The input board can't be moved... return fbest == null ? input : fbest; }
// Edge case: As we're approaching the end of a sequence, try to maximise score... // Possible change to ncombinable: Weight combinables that increase the score significantly private int evaluate(Board b) { int[] thefactors = currentfactors; // We are close to the end of the sequence! Use different weights! if (b.nMoves() + MAX_DEPTH * 2 >= tileSequence.length) { // System.err.println(b.nMoves()); thefactors = closefactors; } return ((int) Math.pow(4, b.dof())) + thefactors[0] * b.zeros() + thefactors[1] * b.checkerboarding3() + thefactors[2] * b.smoothness() + thefactors[3] * b.nCombinable(); }
/** * 'Learns' the factors that are good. More of, just iterate over all possible heuristic weight * combinations and you have to observe which ones are good. * * <p>This learning does not utilise backtracking - it sticks to using one weight only. * * @param b The board to learn against * @param learnClose Whether we are learning for the 'close' weight factors or not. */ public void learn_factors(Board b, boolean learnClose) { int[] fl = learnClose ? closefactors : factors; int[] best = new int[fl.length]; int best_score = -1; Board best_board = null; ExecutorService pool = Executors.newFixedThreadPool(nThreads); for (int i = learning_starts[0]; i < 19; i++) { for (int j = learning_starts[1]; j < 19; j++) { for (int k = learning_starts[2]; k < 8; k++) { for (int l = learning_starts[3]; l < 17; l++) { fl[0] = i; fl[1] = j; fl[2] = k; fl[3] = l; Board n = solve_pndfs(b, pool); int score = n.score(); if (score > best_score) { System.arraycopy(factors, 0, best, 0, best.length); best_score = score; best_board = n; } System.out.printf("Current score: %d (%d moves)\n", score, n.nMoves()); for (int v : fl) { System.out.printf("%d ", v); } System.out.println(); } if (best_board != null) { System.out.println("Current best:"); System.out.println(best_board); System.out.printf("%d (%d moves)\n", best_board.score(), best_board.nMoves()); } for (int v : best) { System.out.printf("%d ", v); } System.out.println(); } } } for (int v : best) System.out.printf("%d ", v); System.out.println(); pool.shutdown(); }
/** * Solves a board game using a depth-limited depth first search. Also makes use of some * backtracking to further optimise the result. Will also attempt to run multithreadedly. * * @param s The tile sequence * @param b The board to search * @return The final best board state that it can find. */ private Board solve_mdfs(Board b) { Ringbuffer<Board> rb = new Ringbuffer<>(6); Board current = b, choke_best = null; char fc = 0, foff = 0; // VTEC just kicked in yo ExecutorService pool = Executors.newFixedThreadPool(nThreads); fbest_score = -1; fbest = null; while (current != null && !current.finished()) { rb.push(current); current = solve_pdfs(current, pool); if (choke_best != null && choke_best != fbest) { log_info( "Recovery!: [%d:%s] %d(%d) --> %d(%d)", (int) fc, Arrays.toString(currentfactors), choke_best.score(), choke_best.nMoves(), fbest.score(), fbest.nMoves()); choke_best = null; // Test: Is it always best to stick to 18,2,2,9 where possible? // Maybe not, but some factors shouldn't be used for extended periods of time. if (fc > 3) { log_info( "Volatile weights were used; switching back to %s", Arrays.toString(choicefactors[0])); fc = 0; currentfactors = choicefactors[0]; } } if (current != null) { log_info(current); } else if (fbest != null && fbest.nMoves() < tileSequence.length) { current = rb.pop(); if (choke_best != fbest) { choke_best = fbest; foff = 1; fc = (char) ((fc + 1) % choicefactors.length); currentfactors = choicefactors[fc]; log_info("Dead-end, back-tracking two steps and trying with different weights!"); log_info( "Starting index: %d (current score %d/%d)", (int) fc, current.score(), current.nMoves()); } else if (foff++ < choicefactors.length - 1) { fc = (char) ((fc + 1) % choicefactors.length); currentfactors = choicefactors[fc]; log_info("No improvement, trying factor index %d...", (int) fc); } else { current = null; log_info("Factor exhaustion"); } } } pool.shutdown(); return fbest == null ? b : fbest; }