Created
March 26, 2023 16:27
-
-
Save gourytch/ffe937ac736ae1e7eb7e6c4afd7dd434 to your computer and use it in GitHub Desktop.
a quick-n-dirty implementation of game kalaha (mancala)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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