简易扫雷bot

    xiaoxiao2021-03-26  24

    简易扫雷bot


    扫雷

    扫雷中,你需要在不点错雷的情况下尽可能快的将所有的雷都标记出来,如果你点错,就得重新开始,所以扫雷也有一定的运气成分。但是在botzone中,你点错并不重新开始,而是只是在最后的分中扣分。

    扫雷中数字的含义表示九宫格内雷的数目。

    这就是扫雷规则。

    实际游戏中还有操作技巧问题,例如双击和定式。我们写bot不考虑这一点,我们采取盲扫的方式来实现一个轻量级的扫雷bot。这很适合初学者来练习。


    botzone

    首先给大家推荐一下botzone,botzone平台为很多种游戏bot对战提供了技术支持,使用json实现交互。例如北大计算概论(A)的黑白棋就是在这个平台上完成的。最后我给出的完整代码可以提交在botzone上直接运行。


    思路

    首先扫雷的规则大家都很清楚了。我们平时玩扫雷的时候关键是背定式。但是我们的bot可以直接枚举不考虑时间代价。

    那么我们先想一个粗放的框架:先计算出每个点是空白的概率,然后挑选一个概率最大的点点击。事实上其实在游戏的大部分情况下几乎每一步都不需要猜,甚至都不需要考虑剩余雷的问题,直到最后才需要猜一下。所以既然是扫雷的简易bot,我们可以做以下简化:我们仅仅挑选出一定不是雷的地点点击,如果不存在这样的地点,我们在可能不是雷的地点任意挑选一个点击。

    那么为了完成以上功能,我们至少要做以下几步:1.在地图中找到一定是雷的地方,2.在地图中找到一定不是雷的地方。

    也就是说,首先要实现在以下格式求解1.2.两个问题的函数,数据格式为:

    int fieldHeight, fieldWidth; int mineField[MAX_HEIGHT][MAX_WIDTH]; //[row][col] /* * 0-8: digits * 9: mine * 10: unrevealed */

    解答

    这个问题其实十分简单,可以作为初学者的思考题,但是很有意思。下面是一个简单的解答,但不是很美妙:

    int surround_min_mines(int row, int col) { int r, c, dr, dc, count = 0; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && ((dr!= 0) || (dc!= 0))) { if (mineField[r][c] == 9) count++; } } return count; } int surround_max_mines(int row, int col) { int r, c, dr, dc, count = 0; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && ((dr!= 0) || (dc!= 0))) { if ((mineField[r][c] == 9) or (mineField[r][c] == 10)) count++; } } return count; } bool must_not_a_mine(int row, int col) { int r, c, dr, dc; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && (mineField[r][c] < 9) && ((dr!= 0) || (dc!= 0))) { if (surround_min_mines(r, c) == mineField[r][c]) return true; } } return false; } bool must_be_a_mine(int row, int col) { int r, c, dr, dc; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && (mineField[r][c] < 9) && ((dr!= 0) || (dc!= 0))) { if (surround_max_mines(r, c) == mineField[r][c]) return true; } } return false; }

    有了这两个函数就很简单了,我们可以写出函数decide()决定点击位置,这是decide()的一个解答:

    void decide(int& decidedRow, int& decidedCol) { int unrevealedPos[MAX_HEIGHT * MAX_WIDTH][2], unrevealedScore[MAX_HEIGHT * MAX_WIDTH]; int unrevealedCount = 0; int row, col; for (row = 0; row < fieldHeight; row++) for (col = 0; col < fieldWidth; col++) if ((mineField[row][col] == 10) && must_be_a_mine(row, col)) { mineField[row][col] = 9; unrevealedPos[unrevealedCount][0] = row; unrevealedPos[unrevealedCount][1] = col; unrevealedScore[unrevealedCount] = -1; unrevealedCount++; } for (row = 0; row < fieldHeight; row++) { for (col = 0; col < fieldWidth; col++) { if (mineField[row][col] == 10) { unrevealedPos[unrevealedCount][0] = row; unrevealedPos[unrevealedCount][1] = col; unrevealedScore[unrevealedCount] = must_not_a_mine(row, col); unrevealedCount++; } } } int myChoice = 0; for (int i = 1; i < unrevealedCount; i++) if (unrevealedScore[i] > unrevealedScore[myChoice]) myChoice = i; decidedRow = unrevealedPos[myChoice][0]; decidedCol = unrevealedPos[myChoice][1]; return; }

    最终bot

    综合上面的函数,外加调试模块,我们可以写出最终解答,这个解答就是我的bot版本,经过测试,效果不错,基本只会在首雷或者最后要猜的情况下触雷,而这是不可避免的。以下为完整的最终bot,供参考。如果调试不对可以去botzone上跑一跑,还有图形界面。

    /* * minesweeper(botzone) * naive */ #define TESTx //TEST is test mode #include <iostream> #include <string> #include <cstdlib> #include <cstdio> #ifndef TEST #include "jsoncpp/json.h" #endif #define MAX_WIDTH 80 #define MAX_HEIGHT 40 using namespace std; int mineCount; #ifdef TEST int fieldHeight = 3, fieldWidth = 3; int mineField[MAX_HEIGHT][MAX_WIDTH] = { { 10, 10, 10, 10}, { 10, 8, 10, 4}, { 10, 10, 10, 2}, }; #else int fieldHeight, fieldWidth; int mineField[MAX_HEIGHT][MAX_WIDTH]; //[row][col] #endif /* * 0-8: digits * 9: mine * 10: unrevealed */ int surround_min_mines(int row, int col) { int r, c, dr, dc, count = 0; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && ((dr!= 0) || (dc!= 0))) { if (mineField[r][c] == 9) count++; } } return count; } int surround_max_mines(int row, int col) { int r, c, dr, dc, count = 0; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && ((dr!= 0) || (dc!= 0))) { if ((mineField[r][c] == 9) or (mineField[r][c] == 10)) count++; } } return count; } bool must_not_a_mine(int row, int col) { int r, c, dr, dc; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && (mineField[r][c] < 9) && ((dr!= 0) || (dc!= 0))) { if (surround_min_mines(r, c) == mineField[r][c]) return true; } } return false; } bool must_be_a_mine(int row, int col) { int r, c, dr, dc; for (dr = -1; dr <= 1; dr++) for (dc = -1; dc <= 1; dc++) { r = row + dr; c = col + dc; if ((r >= 0) && (r < fieldHeight) && (c >= 0) && (c < fieldWidth) && (mineField[r][c] < 9) && ((dr!= 0) || (dc!= 0))) { if (surround_max_mines(r, c) == mineField[r][c]) return true; } } return false; } void decide(int& decidedRow, int& decidedCol) { int unrevealedPos[MAX_HEIGHT * MAX_WIDTH][2], unrevealedScore[MAX_HEIGHT * MAX_WIDTH]; int unrevealedCount = 0; int row, col; for (row = 0; row < fieldHeight; row++) for (col = 0; col < fieldWidth; col++) if ((mineField[row][col] == 10) && must_be_a_mine(row, col)) { mineField[row][col] = 9; unrevealedPos[unrevealedCount][0] = row; unrevealedPos[unrevealedCount][1] = col; unrevealedScore[unrevealedCount] = -1; unrevealedCount++; } for (row = 0; row < fieldHeight; row++) { for (col = 0; col < fieldWidth; col++) { if (mineField[row][col] == 10) { unrevealedPos[unrevealedCount][0] = row; unrevealedPos[unrevealedCount][1] = col; unrevealedScore[unrevealedCount] = must_not_a_mine(row, col); unrevealedCount++; } } } int myChoice = 0; for (int i = 1; i < unrevealedCount; i++) if (unrevealedScore[i] > unrevealedScore[myChoice]) myChoice = i; decidedRow = unrevealedPos[myChoice][0]; decidedCol = unrevealedPos[myChoice][1]; return; } #ifdef TEST int main() { int row, col; printf("%d %d\n", surround_max_mines(1,1), must_be_a_mine(0,0)); for (int turn = 1; turn <= 10; turn++) { for (row = 0; row < fieldHeight; row++) for (col = 0; col < fieldWidth; col++) if ((mineField[row][col] == 10) && must_be_a_mine(row, col)) { mineField[row][col] = 9; } } for (row = 0; row < fieldHeight; row++) for (col = 0; col < fieldWidth; col++) printf("=%c", mineField[row][col], (col+1 == fieldWidth) ? '\n' : ' '); return 0; } #else int main() { int row, col; string str; getline(cin, str); Json::Reader reader; Json::Value input, output, lastInput; reader.parse(str, input); int len = input["requests"].size(); lastInput = input["requests"][len - 1]; fieldHeight = lastInput["height"].asInt(); fieldWidth = lastInput["width"].asInt(); mineCount = lastInput["minecount"].asInt(); for (row = 0; row < fieldHeight; row++) { for (col = 0; col < fieldWidth; col++) { mineField[row][col] = 10; } } for (int i = 0; i < len; i++) { Json::Value changed = input["requests"][i]["changed"]; if (changed.isArray()) { int changedLen = changed.size(); for (int j = 0; j < changedLen; j++) { mineField[changed[j]["row"].asInt()][changed[j]["col"].asInt()] = changed[j]["val"].asInt(); } } } //decide int decidedRow, decidedCol; decide(decidedRow, decidedCol); //output output["response"]["row"] = decidedRow; output["response"]["col"] = decidedCol; Json::FastWriter writer; cout << writer.write(output) << endl; } #endif
    转载请注明原文地址: https://ju.6miu.com/read-661247.html

    最新回复(0)