summaryrefslogtreecommitdiff
path: root/ai_mm2.cpp
diff options
context:
space:
mode:
authortomsmeding <tom.smeding@gmail.com>2019-02-17 12:57:39 +0100
committertomsmeding <tom.smeding@gmail.com>2019-02-17 12:58:11 +0100
commit49baaba539169b5309c3176d9b5cdea2609664d8 (patch)
tree8ab0518bce6e319a8f278502970499eb9b516f00 /ai_mm2.cpp
parent2c5ec9426c0f55cf67a12b589a974ff64de061e4 (diff)
Work on mm ai's
Diffstat (limited to 'ai_mm2.cpp')
-rw-r--r--ai_mm2.cpp166
1 files changed, 166 insertions, 0 deletions
diff --git a/ai_mm2.cpp b/ai_mm2.cpp
new file mode 100644
index 0000000..93b40b2
--- /dev/null
+++ b/ai_mm2.cpp
@@ -0,0 +1,166 @@
+#include <iostream>
+#include <cstring>
+#include <cassert>
+#include <climits>
+#include "ai_mm2.h"
+#include "util.h"
+
+using namespace std;
+
+#define SCORE_WIN 10
+#define SCORE_LOSE (-10)
+#define SCORE_TIE 0
+#define INF (1e34)
+
+#define MAXDEPTH 3
+
+
+static int scoreTable[ipow(3, RLEN)];
+
+__attribute__((constructor))
+static void initScoreTable() {
+ int len = sizeof(scoreTable) / sizeof(scoreTable[0]);
+ uint8_t values[RLEN];
+ memset(values, 0, RLEN);
+
+ for (int i = 0; i < len; i++) {
+ int numempty = 0;
+ uint8_t clr = 0;
+ for (int j = 0; j < RLEN; j++) {
+ if (values[j] == 0) numempty++;
+ else if (clr == 0) clr = values[j];
+ else if (clr != values[j]) {scoreTable[i] = 0; goto nope;}
+ }
+
+ if (numempty == RLEN) {
+ scoreTable[i] = 0;
+ } else {
+ scoreTable[i] = (3 - 2 * clr) * (1 << (RLEN - 1 - numempty));
+ }
+
+ nope:
+ for (int j = 0; j < RLEN; j++) {
+ values[j]++;
+ if (values[j] == 3) values[j] = 0;
+ else break;
+ }
+ }
+}
+
+
+static int evaluate(Board &bd, uint8_t myclr) {
+ Bounds bounds = bd.computeBounds();
+
+ int score = 0;
+
+#define CHECK(_ident, _xlim1, _xlim2, _ylim1, _ylim2, _dx, _dy) \
+ do { \
+ for (int y = (_ylim1); y <= (_ylim2); y++) { \
+ for (int x = (_xlim1); x <= (_xlim2); x++) { \
+ int idx = BSZ * y + x; \
+ int scidx = 0; \
+ for (int i = 1; i <= RLEN; i++, idx += BSZ * (_dy) + (_dx)) { \
+ scidx *= 3; \
+ scidx += bd[idx]; \
+ } \
+ score += scoreTable[scidx]; \
+ } \
+ } \
+ } while (0)
+
+ CHECK(1, bounds.left , bounds.right - (RLEN-1), bounds.top, bounds.bottom , 1, 0);
+ CHECK(2, bounds.left , bounds.right - (RLEN-1), bounds.top, bounds.bottom - (RLEN-1), 1, 1);
+ CHECK(3, bounds.left , bounds.right , bounds.top, bounds.bottom - (RLEN-1), 0, 1);
+ CHECK(4, bounds.left + (RLEN-1), bounds.right , bounds.top, bounds.bottom - (RLEN-1), -1, 1);
+
+#undef CHECK
+
+ if (myclr != 1) score = -score;
+ return score;
+}
+
+static float chanceNode(
+ Board &bd, uint8_t myclr, int idx, uint8_t onturn,
+ int depth, float alpha, float beta);
+
+static float playNode(
+ Board &bd, uint8_t myclr, uint8_t onturn,
+ int depth, float alpha, float beta) {
+
+ if (depth == 0 || bd.bag.totalLeft() == 0) {
+ return evaluate(bd, myclr);
+ }
+
+ float value;
+
+ vector<int> edgeCells = bd.getEdgeCells();
+ if (onturn == myclr) {
+ value = -INF;
+ for (int idx : edgeCells) {
+ value = max(value, chanceNode(bd, myclr, idx, onturn, depth, alpha, beta));
+ alpha = max(alpha, value);
+ if (alpha >= beta) break;
+ }
+ } else {
+ value = +INF;
+ for (int idx : edgeCells) {
+ value = min(value, chanceNode(bd, myclr, idx, onturn, depth, alpha, beta));
+ beta = min(beta, value);
+ if (alpha >= beta) break;
+ }
+ }
+
+ return value;
+}
+
+static float chanceNode(
+ Board &bd, uint8_t myclr, int idx, uint8_t onturn,
+ int depth, float alpha, float beta) {
+
+ const float totalLeftF = bd.bag.totalLeft();
+
+ float score = 0;
+
+ for (uint8_t clr = 1; clr <= NC; clr++) {
+ int numLeft = bd.bag.numLeft(clr);
+ if (numLeft == 0) continue;
+
+ float probability = numLeft / totalLeftF;
+
+ bd.bag.drawColour(clr);
+ uint8_t win = bd.putCW(idx, clr);
+ if (win != 0) {
+ score += probability * (win == myclr ? SCORE_WIN : SCORE_LOSE);
+ } else {
+ score += probability * playNode(bd, myclr, NEXTTURN(onturn), depth - 1, alpha, beta);
+ }
+
+ bd.bag.replace(clr);
+ bd.undo(idx);
+ }
+
+ return score;
+}
+
+int MM2::calcMove(Board &bd, uint8_t myclr) {
+ assert(bd.bag.totalLeft() > 0);
+
+ // The usage of a single score variable instead of an array works only if
+ // there are only two players.
+ static_assert(NC == 2, "MM only works with 2 players");
+
+ float maxscore = INT_MIN;
+ int maxat = -1;
+
+ vector<int> edgeCells = bd.getEdgeCells();
+ for (int idx : edgeCells) {
+ float score = chanceNode(bd, myclr, idx, myclr, MAXDEPTH, -INF, +INF);
+
+ if (score > maxscore) {
+ maxscore = score;
+ maxat = idx;
+ }
+ }
+
+ return maxat;
+}