Skip to content

Instantly share code, notes, and snippets.

@gourytch
Created March 26, 2023 16:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gourytch/ffe937ac736ae1e7eb7e6c4afd7dd434 to your computer and use it in GitHub Desktop.
Save gourytch/ffe937ac736ae1e7eb7e6c4afd7dd434 to your computer and use it in GitHub Desktop.
a quick-n-dirty implementation of game kalaha (mancala)
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <array>
#include <algorithm>
#include <string>
#include <sstream>
#include <vector>
#include <cassert>
constexpr int numDigits(int n) {
return n < 0
? 1 + numDigits(-n)
: n < 10
? 1
: 1 + numDigits(n / 10);
}
enum class outcome {
next,
again,
gameover
};
constexpr const char *outcome_pch(outcome v) {
switch(v) {
case outcome::next: return "next";
case outcome::again: return "again";
case outcome::gameover: return "gameover";
default: return "unknown";
}
}
enum class player {
A,
B
};
constexpr player player_next(player p) {
return p == player::A ? player::B : player::A;
}
constexpr const char *player_pch(player p) {
constexpr const char *pA = "A";
constexpr const char *pB = "B";
return p == player::A ? pA : pB;
}
template <int _NCells, int _NStones>
struct Field {
constexpr static int ncells = _NCells;
constexpr static int nstones = _NStones;
constexpr static int maxval = _NCells * _NStones;
constexpr static int cycle = ncells * 2 + 1; // 6 * 2 + 1 = 13
constexpr static int width = numDigits(maxval) + 1; // sign-capable
typedef std::array<int, _NCells> Row;
int kal1;
int kal2;
Row row1;
Row row2;
player cur;
// +----+----+----+----+----+----+----+----+
// |B | 4 | 4 | 4 | 4 | 4 | 4 | |
// | 0 +----+----+----+----+----+----+ 0 |
// | | 4 | 4 | 4 | 4 | 4 | 4 | A|
// +----+----+----+----+----+----+----+----+
void reset() {
std::fill(std::begin(row1), std::end(row1), _NStones);
std::fill(std::begin(row2), std::end(row2), _NStones);
kal1 = 0;
kal2 = 0;
cur = player::A;
}
// +----+----+----+----+----+----+----+----+
// |B | 12 | 11 | 10 | 9 | 8 | 7 | |
// | 13 +----+----+----+----+----+----+ 6 |
// | | 0 | 1 | 2 | 3 | 4 | 5 | A|
// +----+----+----+----+----+----+----+----+
void circle() {
for (auto i = 0; i < _NCells; ++i) {
row1[i] = i;
row2[i] = i + _NCells + 1;
}
kal1 = _NCells;
kal2 = _NCells * 2 + 1;
}
// +----+----+----+----+----+----+----+----+ +----+----+----+----+----+----+----+----+
// |B | 12 | 11 | 10 | 9 | 8 | 7 | | |B | 5 | 4 | 3 | 2 | 1 | 0 | |
// | 13 +----+----+----+----+----+----+ 6 | -> | 6 +----+----+----+----+----+----+ 13 |
// | | 0 | 1 | 2 | 3 | 4 | 5 | A| | | 7 | 8 | 9 | 10 | 11 | 12 | A|
// +----+----+----+----+----+----+----+----+ +----+----+----+----+----+----+----+----+
void swap() {
std::swap(row1, row2);
std::swap(kal1, kal2);
}
void rotate() {
swap();
cur = player_next(cur);
}
// is the move from cell allowed
bool allowed(int ix) const {
if (ncells <= ix) return false;
if (row1[ix] == 0) return false;
return true;
}
static bool is_empty(const Row& row) {
for (auto v: row) if (v != 0) return false;
return true;
}
constexpr static int next(int ix) {
return (ix + 1) % (cycle); // 0->1 .. 11->12, 12->0
}
outcome step(int ix) { // return true if step continues
auto n = row1[ix];
row1[ix] = 0;
auto eix = ix;
while (n > 0) { // place stones from source cell to all cells by counterclockwise way
eix = next(eix);
--n;
if (eix < ncells) ++row1[eix]; // 0..5
else if (eix == ncells) ++kal1; // 6
else ++row2[eix - ncells - 1]; // 7 -> (7-6-1)=0 .. 12 -> (12-6-1)=5
}
outcome ret;
if (eix == ncells) { // on the player's kalaha
ret = outcome::again; // repeat step;
} else if (eix < ncells) { // on the player's cell
if (row1[eix] == 1) { // single stone ::= ends on the empty cell - capture opposite cell
auto oix = ncells - eix - 1; // 0->(6-0-1)=5, 1->(6-1-1)=4 ... 5->(6-5-1)=0
if (row2[oix] > 0) { // capture if not empty
kal1 += row2[oix] + row1[eix];
row1[eix] = 0;
row2[oix] = 0;
}
}
} else {
ret = outcome::next;
}
// check rows for emptiness. if one row is empty - game is over
if (is_empty(row1) || is_empty(row2)) {
for (auto& ref: row1) {
kal1 += ref;
ref = 0;
};
for (auto& ref: row2) {
kal2 += ref;
ref = 0;
};
ret = outcome::gameover;
}
return ret;
}
std::string hline(int n) const {
std::string chunk = std::string(2 + width, '-') + "+";
std::string ret("+");
for (auto i = 0; i < n; ++i) ret += chunk;
return ret;
}
// "| 4 | 4 | 4 | 4 | 4 | 4 "
std::string rowline(const Row& row, bool top) const {
char buf[16];
std::string ret;
for (auto v: row) {
sprintf(buf, "| %*d ", width, v);
if (top) {
ret = std::string(buf) + ret;
} else {
ret += std::string(buf);
}
}
return ret;
}
// "|B | 12 | 11 | 10 | 9 | 8 | 7 | |"
std::string rowtop() const {
char left[16];
char right[16];
sprintf(left, "|%s%*s", player_pch(player_next(cur)), width + 1, " "); // "|A "
sprintf(right, "|%*s|", width + 2, " ");
return std::string(left) + rowline(row2, true) + std::string(right);
}
// "| | 0 | 1 | 2 | 3 | 4 | 5 | A|"
std::string rowbottom() const {
char left[16];
char right[16];
sprintf(left, "|%*s", width + 2, " ");
sprintf(right, "|%*s%s|", width + 1, " ", player_pch(cur)); // "| B|"
return std::string(left) + rowline(row1, false) + std::string(right);
}
// "| 13 +----+----+----+----+----+----+ 6 |"
std::string kalline() const {
char buf1[16];
char buf2[16];
sprintf(buf1, "| %*d ", width, kal2); // k2 at the left side
sprintf(buf2, " %*d |", width, kal1); // k1 at the right side
return std::string(buf1) + hline(ncells) + std::string(buf2);
}
// +----+----+----+----+----+----+----+----+
// |B | 12 | 11 | 10 | 9 | 8 | 7 | |
// | 13 +----+----+----+----+----+----+ 6 |
// | | 0 | 1 | 2 | 3 | 4 | 5 | A|
// +----+----+----+----+----+----+----+----+
std::string dump() const {
std::string h = hline(ncells + 2);
std::string eol = std::string("\n");
return h + eol +
rowtop() + eol +
kalline() + eol +
rowbottom() + eol +
h;
}
std::string ruler() const {
char left[16];
sprintf(left, "%*s ", width + 2, " ");
char buf[16];
std::string ret(left);
for (auto i = 0; i < ncells; i++) {
sprintf(buf, "%c %*d ", i == 0 ? ' ' : ':', width, i);
ret += std::string(buf);
}
return ret;
}
int rand() const {
int s0 = std::rand() % _NCells; // little bit inaccurate
for (auto i = 0; i < _NCells; ++i) {
int s = (s0 + i) % _NCells;
if (allowed(s)) return s;
}
return -1;
}
};
std::vector<std::string> split(const std::string& v) {
std::vector<std::string> ret;
std::istringstream f(v);
std::string s;
while (getline(f, s, '\n')) {
ret.push_back(s);
}
return ret;
}
void showstep(const std::string& a, const std::string& b, int step, int move, outcome r) {
auto s1 = split(a);
auto s2 = split(b);
assert(s1.size() == 5);
assert(s2.size() == 5);
for (auto i = 0; i < 5; i++) {
printf("%s %s %s\n", s1[i].c_str(), i == 2 ? "->" : " ", s2[i].c_str());
}
printf("step #%d, move: %d, outcome: %s\n\n", step, move, outcome_pch(r));
}
typedef Field<6, 4> BaseGame;
//typedef Field<8, 6> BaseGame;
int input_move(const BaseGame& board) {
for (;;) {
printf("%s\n", board.dump().c_str());
printf("%s\n", board.ruler().c_str());
printf("step[");
for (auto i = 0; i < board.ncells; i++) {
if (board.allowed(i)) printf("%d", i);
}
printf("] ?");
fflush(stdout);
int move = -1;
scanf("%d", &move);
if (move == -1) return -1;
if (board.allowed(move)) return move;
printf("prohibited move: %d\n", move);
}
}
void randparty(BaseGame& board) {
int step = 0;
board.reset();
for (;;) {
++step;
auto move = board.cur == player::A ? input_move(board) : board.rand();
if (move == -1) break;
auto sPrev = board.dump();
auto result = board.step(move);
auto sNext = board.dump();
showstep(sPrev, sNext, step, move, result);
switch (result) {
case outcome::gameover:
return;
case outcome::next:
board.rotate();
break;
case outcome::again:
break;
}
}
}
int main(int, char**) {
uint64_t seed = std::time(nullptr);
std::srand(seed);
BaseGame board;
randparty(board);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment