Drop7 Research
approaches/lifetime-objective/leaf-evolution/decide.cppC++108 lines · 4.3 KB
// One-shot native decision for the benchmark playground and the competition.
//
//   decide --board <49 digits> --next <1-7> --rise <1-5>
//          [--weights file] [--depth 4] [--chance-samples 7] [--cache 60000]
//
// prints "bestmove <column>" (0-6) or "bestmove none" on a terminal board, and
// exits 0.  The board is the engine's serializeBoard encoding, row-major from
// the top: 0 empty, 1-7 numbered, 8 solid gray, 9 cracked gray -- the same
// string the D7P protocol carries (docs/d7p-protocol.md).
//
// The policy reads exactly the public state: visible board, visible next disc,
// moves until the next rise.  There is no seed, score, level or move number on
// the command line, so there is nothing else it could read.  Decisions are
// deterministic for a given board, weights and configuration, which the
// benchmark harness requires.
//
// With no --weights this is the frozen fast fair-D4 leaf; with a weights file
// it is the same search with that leaf.  The search is the gated
// WeightedFastSearch (see build.sh), so a column chosen here is the column the
// research evaluator would choose on the same public state.

#include "weighted-search.hpp"

#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>

namespace {

using namespace drop7;

std::uint64_t worstCaseWork(int maximumDepth, int strata) {
  const auto branches =
      static_cast<std::uint64_t>(kBoardSize) * static_cast<std::uint64_t>(strata);
  std::uint64_t total = 0;
  for (int depth = 1; depth <= maximumDepth; ++depth) {
    std::uint64_t power = 1;
    for (int level = 1; level <= depth; ++level) {
      power *= branches;
      total += power;
    }
    total += power;
  }
  return total;
}

}  // namespace

int main(int argc, char** argv) {
  try {
    std::string board;
    std::string weightsPath;
    int next = 0;
    int rise = 0;
    fastw::FastSearchParameters parameters;
    parameters.depth = 4;
    parameters.chance_samples = 7;
    parameters.maximum_cache_entries = 60'000;
    for (int i = 1; i < argc; ++i) {
      const std::string key = argv[i];
      if (i + 1 >= argc) throw std::invalid_argument("missing value for " + key);
      const std::string value = argv[++i];
      if (key == "--board") board = value;
      else if (key == "--next") next = std::stoi(value);
      else if (key == "--rise") rise = std::stoi(value);
      else if (key == "--weights") weightsPath = value;
      else if (key == "--depth") parameters.depth = std::stoi(value);
      else if (key == "--chance-samples") parameters.chance_samples = std::stoi(value);
      else if (key == "--cache") parameters.maximum_cache_entries = std::stoull(value);
      else throw std::invalid_argument("unknown option " + key);
    }
    if (board.size() != static_cast<std::size_t>(kCellCount)) {
      throw std::invalid_argument("--board must be 49 characters");
    }
    if (next < 1 || next > kBoardSize) throw std::invalid_argument("--next must be 1-7");
    if (rise < 1 || rise > kMovesPerLevel) throw std::invalid_argument("--rise must be 1-5");
    parameters.maximum_work = worstCaseWork(parameters.depth, parameters.chance_samples) + 1;

    State state;
    for (int cell = 0; cell < kCellCount; ++cell) {
      const char c = board[static_cast<std::size_t>(cell)];
      if (c < '0' || c > '9') throw std::invalid_argument("--board cells must be digits 0-9");
      state.board[static_cast<std::size_t>(cell)] = static_cast<std::uint8_t>(c - '0');
    }
    state.next_disc = static_cast<std::uint8_t>(next);
    state.moves_remaining = rise;
    state.game_over = false;

    fastw::LeafWeights weights;
    if (!weightsPath.empty()) weights = fastw::readWeightsFile(weightsPath);

    fastw::WeightedFastSearch search{parameters, weights};
    fastw::FastSearchMetrics metrics;
    const int action = search.chooseAction(state, metrics);
    if (action < 0 || !isLegal(state.board, action)) {
      std::cout << "bestmove none\n";
    } else {
      std::cout << "bestmove " << action << "\n";
    }
    std::cout << "info depth " << metrics.completed_depth << " work " << metrics.work
              << " nodes " << metrics.nodes << " frozen " << (weights.isFrozen() ? 1 : 0) << "\n";
    return 0;
  } catch (const std::exception& error) {
    std::cerr << "decide failed: " << error.what() << '\n';
    return 2;
  }
}