Spaces:
Running on Zero
Running on Zero
File size: 4,750 Bytes
2cf2375 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | // Tablebase Explorer CLI.
// Usage: ./query K1 K2 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9
// The 12 fields describe the canonical state from the side-to-move perspective
// (K1/p0-p4 = mover, K2/p5-p9 = opponent). Emits a single JSON object.
#include "StateIndex.h"
#include "Oracle.h"
#include <iostream>
#include <string>
#include <sstream>
using namespace Bestemshe;
static const char* value_str(OracleValue v) {
switch (v) {
case OracleValue::WIN: return "WIN";
case OracleValue::LOSS: return "LOSS";
case OracleValue::DRAW: return "DRAW";
default: return "UNKNOWN";
}
}
static void emit_error(const std::string& msg) {
std::cout << "{\"error\": \"" << msg << "\"}\n";
}
// Mirrors the sowing in ExecuteMoveAndFlip to find the pit (0..9) where the
// last stone lands. Used only for move notation.
static int landing_pit(const State& s, int i) {
int pieces = s.board[i];
int current = i;
if (pieces == 1) {
current = (current + 1) % 10;
} else {
pieces--;
while (pieces > 0) { current = (current + 1) % 10; pieces--; }
}
return current;
}
static std::string board_json(const State& s) {
std::ostringstream o;
o << "[";
for (int i = 0; i < 10; ++i) o << (i ? "," : "") << static_cast<int>(s.board[i]);
o << "]";
return o.str();
}
int main(int argc, char* argv[]) {
StateIndex::InitCombinatorics();
if (argc != 13) {
emit_error("expected 12 integers: K1 K2 p0..p9");
return 1;
}
int vals[12];
for (int i = 0; i < 12; ++i) {
try { vals[i] = std::stoi(argv[i + 1]); }
catch (...) { emit_error("non-numeric argument"); return 1; }
if (vals[i] < 0 || vals[i] > 50) { emit_error("field out of range 0..50"); return 1; }
}
State s;
s.K_self = static_cast<uint8_t>(vals[0]);
s.K_opp = static_cast<uint8_t>(vals[1]);
s.M = s.K_self + s.K_opp;
int pit_sum = 0;
for (int i = 0; i < 10; ++i) {
s.board[i] = static_cast<uint8_t>(vals[i + 2]);
pit_sum += vals[i + 2];
}
if (pit_sum + s.M != 50) { emit_error("stones must total 50 (pits + kazans)"); return 1; }
if ((s.K_self % 2) || (s.K_opp % 2)) { emit_error("kazan counts must be even (captures are even)"); return 1; }
if (s.K_self > 24 || s.K_opp > 24) {
emit_error("game already decided (a kazan holds 26+) or kazan exceeds tablebase range");
return 1;
}
TablebaseOracle oracle;
OracleValue pos_val = oracle.query(s);
if (pos_val == OracleValue::ERROR) {
emit_error("tablebase lookup failed (missing/corrupt layer files in " + oracle.dir() + ")");
return 1;
}
std::ostringstream out;
out << "{\"position\": {\"K1\": " << (int)s.K_self << ", \"K2\": " << (int)s.K_opp
<< ", \"board\": " << board_json(s) << "},\n"
<< " \"value\": \"" << value_str(pos_val) << "\",\n"
<< " \"moves\": [";
bool first = true;
for (int pit = 0; pit < 5; ++pit) {
if (s.board[pit] == 0) continue; // illegal move: omit entirely
int land = landing_pit(s, pit);
int from_idx = pit + 1; // 1..5
int to_idx = (land % 5) + 1; // 1..5 (either side)
State child;
bool empties;
bool capture = ExecuteMoveAndFlip(s, pit, child, empties);
const char* result;
bool terminal = false;
if (empties || child.K_opp >= 26) {
// Terminal: the mover wins immediately.
result = "WIN";
terminal = true;
} else {
OracleValue v = oracle.query(child);
if (v == OracleValue::ERROR) { emit_error("lookup failed for move child"); return 1; }
// Child value is from the opponent's perspective; invert for the mover.
if (v == OracleValue::WIN) result = "LOSS";
else if (v == OracleValue::LOSS) result = "WIN";
else result = "DRAW";
}
if (!first) out << ",";
first = false;
out << "\n {\"from\": " << from_idx << ", \"to\": " << to_idx
<< ", \"capture\": " << (capture ? "true" : "false")
<< ", \"result\": \"" << result << "\""
<< ", \"terminal\": " << (terminal ? "true" : "false");
// Raw canonical child (opponent to move). Emitted even for terminal moves
// so the UI can show the final board before freezing the game.
out << ", \"child\": {\"K1\": " << (int)child.K_self
<< ", \"K2\": " << (int)child.K_opp << ", \"board\": " << board_json(child) << "}";
out << "}";
}
out << "\n]}";
std::cout << out.str() << "\n";
return 0;
}
|