@Override
    public final Move takeTurn(final Board board,
                               final Piece.Side currentSide) {
        side = currentSide;

        /* Gather up every move. */
        MoveList moves = board.allMoves(side, true);
        moves.shuffle();

        /* Initialize the shared structures. */
        if (game != null) {
            game.setProgress(0);
            game.setStatus("Thinking ...");
        }
        long startTime = System.currentTimeMillis();

        /* Spin off threads to evaluate each move's tree. */
        CompletionService<Move> service =
            new ExecutorCompletionService<Move>(executor);
        int submitted = 0;
        bestMove = null;
        for (final Move move : moves) {
            final Board callboard = board.copy();
            service.submit(new Callable<Move>() {
                public Move call() {
                    callboard.move(move);
                    double beta = Double.POSITIVE_INFINITY;
                    if (bestMove != null) {
                        beta = -bestMove.getScore();
                    }
                    double v = search(callboard, maxDepth - 1,
                                      Piece.opposite(side),
                                      Double.NEGATIVE_INFINITY, beta);
                    move.setScore(-v);
                    return move;
                }
            });
            submitted++;
        }

        /* Gather up results and pick the best move. */
        for (int i = 0; i < submitted; i++) {
            try {
                Move m = service.take().get();
                if (bestMove == null || m.getScore() > bestMove.getScore()) {
                    bestMove = m;
                }
            } catch (ExecutionException e) {
                LOG.warning("move went unevaluated: " + e.getMessage());
            } catch (InterruptedException e) {
                LOG.warning("move went unevaluated: " + e.getMessage());
            }
            if (game != null) {
                game.setProgress(i / (1.0f * (submitted - 1)));
            }
        }

        long time = (System.currentTimeMillis() - startTime);
        LOG.info("AI took " + (time / MILLI) + " seconds (" +
                 NTHREADS + " threads, " + maxDepth + " plies)");
        return bestMove;
    }