首页 > 学院 > 开发设计 > 正文

简易扫雷bot

2019-11-14 09:30:13
字体:
来源:转载
供稿:网友

简易扫雷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 40using namespace std;int mineCount;#ifdef TESTint fieldHeight = 3, fieldWidth = 3;int mineField[MAX_HEIGHT][MAX_WIDTH] = { { 10, 10, 10, 10}, { 10, 8, 10, 4}, { 10, 10, 10, 2}, };#elseint 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 TESTint 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("%3d%c", mineField[row][col], (col+1 == fieldWidth) ? '/n' : ' '); return 0;}#elseint 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
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表