15 Commits

Author SHA1 Message Date
5ec5eb224e update README 2023-10-07 11:51:45 +08:00
e4134689c5 update code style 2023-10-07 10:42:54 +08:00
b0c7ab0269 fix a minor bug 2023-10-02 18:06:19 +08:00
c2091d7cbf Exceed base line 2 2023-10-02 14:36:51 +08:00
4b209bba68 allow some randomness 2023-10-02 14:34:04 +08:00
aa717979b7 fix a serious buf 2023-10-02 10:13:00 +08:00
19820736a2 stablely reach 84pt 2023-10-01 23:21:01 +08:00
e46b6413e9 test next two values 2023-10-01 21:05:36 +08:00
8206cbccfd further fix the bug 2023-09-30 19:21:19 +08:00
173902a9ab fix the bug of improve stoping 2023-09-30 18:07:53 +08:00
bf73694175 adjust autoimprove 2023-09-30 17:16:09 +08:00
dbbd81af39 update autoimprove 2023-09-30 17:06:19 +08:00
5e8f7191a5 generate the autoimprove 2023-09-30 17:04:17 +08:00
b2d4b2f463 add first two dimension 2023-09-30 16:50:49 +08:00
a270369b18 manually fetch changes from central repo 2023-09-30 11:43:24 +08:00
8 changed files with 517 additions and 117 deletions

View File

@ -2,3 +2,5 @@ BasedOnStyle: Google
DerivePointerAlignment: false DerivePointerAlignment: false
PointerAlignment: Right PointerAlignment: Right
ColumnLimit: 80 ColumnLimit: 80
AllowCommentMovement: true
PenaltyBreakBeforeFirstCallParameter: 100

View File

@ -1,7 +1,27 @@
# 思路简介
## Server
相当于一道模拟题,注意时间效率不要太差以及细节问题
## Client
扫雷一般是采用如下思路:
1. 根据已翻开的数字推测周围的哪些格子里有多少雷在此基础上进行逻辑推理当推断出n个格子里有0个或n个雷时我们就可以确定了。优先点开确定的雷。
2. 当没有确定的雷点击时,进行一些猜测。
### 确定解
在尝试寻找确定解时我采用的方式是在每个待求解格子上设一个未知数解一个方程组在高斯——约旦消元的基础上特判一下n个格子里有0个或n个雷由于这个特判过程不是基本行变换对线性方程的影响可能不太可靠多求解几次。
### 猜解
1. 在猜测时对于一个已翻开的格子如果它周围的n个未知格子里应当有m个雷那么把$\frac{m}{n}$用幂函数$f(x)=x^k ([0,1]\to [0,1])$处理一下,并把处理结果塞进那些未知格。对于已知格和未知格为边相邻和角相邻,$f(x)$分两种,它们的幂次不同。这个步骤的目的是赋予边相邻和角相邻的情况不同的权重。
2. 当一个格没有收集到任何数时采用一个全局默认估计有雷频率初始为0.06,当已翻开格子足够多后用已知区实际计算比例)替代$\frac{m}{n}$,幂函数的幂次为两种幂次的算术平均数,把全局默认估计有雷频率用幂函数处理一下视作这个格子收到的数。
3. 随后对于每个未知格把它收集到的数取一个t次方平均数由此得到最终估计有雷概率。这个步骤的目的是赋予较大的值更大的权重这样更加慎重一点。
4. 在得到最终估计有雷概率后扫描整个地图找严格概率最小的那个格子。但是在还剩最后5%的格子没有被点开时,仿照模拟退火以一定可能性允许概率更大的格子替换当前解
___
# references目录下的参考信息来源 # references目录下的参考信息来源
~~虽然好像根本没看~~
- <https://github.com/DavidNHill/Minesweeper> - <https://github.com/DavidNHill/Minesweeper>
- <https://github.com/DavidNHill/Minesweeper2> - <https://github.com/DavidNHill/Minesweeper2>
- <https://dash.harvard.edu/bitstream/handle/1/14398552/BECERRA-SENIORTHESIS-2015.pdf> - <https://dash.harvard.edu/bitstream/handle/1/14398552/BECERRA-SENIORTHESIS-2015.pdf>
___ ___
# Minesweeper-2023 # Minesweeper-2023
> ACM 班 2023 级程序设计第一次大作业 > ACM 班 2023 级程序设计第一次大作业
@ -222,6 +242,10 @@ Execute(row, column):
请在 OnlineJudge 上直接提交你实现后的头文件。对于基础任务,提交 `server.h`,对于进阶任务,提交 `client.h`。 请在 OnlineJudge 上直接提交你实现后的头文件。对于基础任务,提交 `server.h`,对于进阶任务,提交 `client.h`。
### 数据范围
保证 `n` `m` 均不大于 30操作序列长度不大于 1000。
### 评分规则 ### 评分规则
A班、B班均有最多 10% 的 bonus 分数。 A班、B班均有最多 10% 的 bonus 分数。

214
src/autoimprove.py Executable file
View File

@ -0,0 +1,214 @@
#!/usr/bin/python3
import sys
import os
import random
built_in_test_cases = [
"""10 10
........X.
..........
..........
......X...
..........
.X.......X
..........
X........X
..........
X....XX...
0 0""",
"""10 10
.....X...X
.........X
X..X......
X.X.......
..X.......
..........
..........
..........
..........
X.........
0 0""",
"""10 10
.XX.......
...X...XX.
..........
...X......
.X......X.
..X....X..
XX....X...
..X...X...
..X.X.X.X.
....X.....
0 9""",
"""10 10
.......X..
X.....X...
X...X.....
...X......
..........
.X.X.X....
..X.X..X..
.X.X.....X
....X.....
.....X....
4 9""",
"""20 20
X...........X.......
..XX.............X.X
.X......X.........X.
....X.X...X...X.....
....X.X.........X...
X.XX...........X.X..
...X.............XX.
...XX...X..X..X.X...
...X.X...X...X......
.............XX.X...
.X............X.....
.X..X.........X....X
X.X.X..X...X..X.....
.X....X.X......X....
...X.........X..X...
...X.X...X..........
..X.XX.X......XXXX..
.X.X...............X
XXX..X....X.XX..X...
.X...X.XX........X.X
0 10""",
"""10 10
.XX.......
...X...XX.
..........
...X......
.X......X.
..X....X..
XX....X...
..X...X...
..X.X.X.X.
....X.....
3 6""",
"""20 20
X...........X.......
..XX.............X.X
.X......X.........X.
....X.X...X...X.....
....X.X.........X...
X.XX...........X.X..
...X.............XX.
...XX...X..X..X.X...
...X.X...X...X......
.............XX.X...
.X............X.....
.X..X.........X....X
X.X.X..X...X..X.....
.X....X.X......X....
...X.........X..X...
...X.X...X..........
..X.XX.X......XXXX..
.X.X...............X
XXX..X....X.XX..X...
.X...X.XX........X.X
4 12"""
]
mine_rate=0.133
def ATest():
total_round=0
win_round=0
for i in range(0,10):
for data in built_in_test_cases:
fn=open("tmp/test.in","w")
fn.write(data)
fn.close()
os.system("tmp/client < tmp/test.in > tmp/test.out")
fn=open("tmp/test.out","r")
lines=fn.readlines()
fn.close()
print(lines)
total_round+=1
if len(lines)>0:
if lines[0]=='YOU WIN!\n':
win_round+=1
print("win rate: "+str(win_round/total_round),win_round,total_round)
#input("Press Enter to continue...")
for loop_cnt in range(10000):
# randomly generate n,m in [2,30]
n=random.randint(20,30)
m=random.randint(20,30)
# print(n,m)
# randomly generate mine_rate in [0,1]
#mine_rate=random.random()
# generate a random map
map=[]
for i in range(n):
map.append([])
for j in range(m):
if random.random()<mine_rate:
map[i].append('X')
else:
map[i].append('.')
# generate a random start point with no mine
sx=random.randint(0,n-1)
sy=random.randint(0,m-1)
while map[sx][sy]=='X':
sx=random.randint(0,n-1)
sy=random.randint(0,m-1)
# output the map
fn=open("tmp/test.in","w")
fn.write(str(n)+" "+str(m)+"\n")
for i in range(n):
for j in range(m):
fn.write(map[i][j])
fn.write("\n")
fn.write(str(sx)+" "+str(sy))
fn.close()
# run the program
os.system("tmp/client < tmp/test.in > tmp/test.out")
# read the output
fn=open("tmp/test.out","r")
lines=fn.readlines()
fn.close()
# check the output
print(lines)
total_round+=1
if len(lines)>0:
if lines[0]=='YOU WIN!\n':
win_round+=1
print("win rate: "+str(win_round/total_round),win_round,total_round)
return win_round/total_round
value1_list=[0.25,0.5,0.75,1,1.25,1.5,1.75,2,2.25,2.5,3]
value2_list=[0.25,0.5,0.75,1,1.25,1.5,1.75,2,2.25,2.5,3]
def GenerateSource(value1,value2):
# read src/include/client.h
fn=open("src/include/client.h","r")
lines=fn.readlines()
fn.close()
# change the 441 th line to "const double m_pow_edge = "+str(value1)+";"
lines[440]="const double m_pow_edge = "+str(value1)+";\n"
# change the 442 th line to "const double m_pow_coner = "+str(value2)+";"
lines[441]="const double m_pow_coner = "+str(value2)+";\n"
# write the source to "tmp/client.h"
fn=open("tmp/client.h","w")
fn.writelines(lines)
fn.close()
# copy src/include/server.h to tmp/server.h
os.system("cp src/include/server.h tmp/server.h")
# copy src/advanced.cpp to tmp/advanced.cpp
os.system("cp src/advanced.cpp tmp/advanced.cpp")
# compile the source tmp/advanced.cpp to tmp/client
os.system("g++ -std=c++11 -g -O2 -Wall -Wextra tmp/advanced.cpp -o tmp/client")
for value1 in value1_list:
for value2 in value2_list:
GenerateSource(value1,value2)
print("value1="+str(value1)+", value2="+str(value2))
ratio=ATest()
print("win rate: "+str(ratio))
print("")
# write to tmp/log.txt
fn=open("tmp/log.txt","a")
fn.write("value1="+str(value1)+", value2="+str(value2)+"\n")
fn.write("win rate: "+str(ratio)+"\n")
fn.write("\n")
fn.close()

View File

@ -72,7 +72,41 @@ X.X.X..X...X..X.....
.X.X...............X .X.X...............X
XXX..X....X.XX..X... XXX..X....X.XX..X...
.X...X.XX........X.X .X...X.XX........X.X
0 10""" 0 10""",
"""10 10
.XX.......
...X...XX.
..........
...X......
.X......X.
..X....X..
XX....X...
..X...X...
..X.X.X.X.
....X.....
3 6""",
"""20 20
X...........X.......
..XX.............X.X
.X......X.........X.
....X.X...X...X.....
....X.X.........X...
X.XX...........X.X..
...X.............XX.
...XX...X..X..X.X...
...X.X...X...X......
.............XX.X...
.X............X.....
.X..X.........X....X
X.X.X..X...X..X.....
.X....X.X......X....
...X.........X..X...
...X.X...X..........
..X.XX.X......XXXX..
.X.X...............X
XXX..X....X.XX..X...
.X...X.XX........X.X
4 12"""
] ]
mine_rate=0.133 mine_rate=0.133
total_round=0 total_round=0
@ -88,6 +122,7 @@ for i in range(0,10):
fn.close() fn.close()
print(lines) print(lines)
total_round+=1 total_round+=1
if len(lines)>0:
if lines[0]=='YOU WIN!\n': if lines[0]=='YOU WIN!\n':
win_round+=1 win_round+=1
print("win rate: "+str(win_round/total_round),win_round,total_round) print("win rate: "+str(win_round/total_round),win_round,total_round)
@ -134,6 +169,7 @@ while True:
# check the output # check the output
print(lines) print(lines)
total_round+=1 total_round+=1
if len(lines)>0:
if lines[0]=='YOU WIN!\n': if lines[0]=='YOU WIN!\n':
win_round+=1 win_round+=1
print("win rate: "+str(win_round/total_round),win_round,total_round) print("win rate: "+str(win_round/total_round),win_round,total_round)

View File

@ -1,3 +1,36 @@
/**
* @file client.h
*
* @brief The definition of the client
* @details This file contains the definition of the client. When the function
* ReadMap() is called, the client will read the game map from stdin and trying
* to calculate which blocks definitely has no mine and which blocks definitely
* has mine. Then the client will push the blocks that definitely has no mine
* into a queue no_mine_block_to_be_clicked. When the function Decide() is
* called, the client will pop a block from no_mine_block_to_be_clicked and
* execute it. If no_mine_block_to_be_clicked is empty, the client will make a
* guess.
*
* @codesytle This file is written in a sytle mainly based on Google C++ Style
* Guide. What's sepecial is the comment:
* 1. Multi-line comments are always before the code they comment on.
* Usually the code they comment on is a complex procedure,like the definition
* of a function,a class or a variable with complex operation. If a multi-line
* comment is in one line, it will start with "/*" instead of "/**",otherwise it
* will start with "/**" and in the format of Doxygen.
* 2. Single-line comments are always after the code they comment on.
* Usually they are in the same line with the code they comment on,but sometimes
* they may come in the next lines. single-line comments shouldn't exceed 3
* lines as they are intended to be short and easy to understand.
* 3. Temporary commented code will be marked with "//" in the front of each
* 4. Some comments have special meanings,like "//TODO", "//FIXME", "//XXX","//
* clang-format off" and "// clang-format on". They are not controlled by the
* previous rules.
*
* As I use Clang-format to format my code, so the code style may be a little
* bit strange when parameters or phrases are too long,thus I'm try to manually
* format the code.
*/
#ifndef CLIENT_H #ifndef CLIENT_H
#define CLIENT_H #define CLIENT_H
@ -16,7 +49,7 @@
extern int rows; // The count of rows of the game map extern int rows; // The count of rows of the game map
extern int columns; // The count of columns of the game map extern int columns; // The count of columns of the game map
// You can not use any other external variables except for rows and columns. /*You can not use any other external variables except for rows and columns.*/
/** /**
* @brief The definition of function Execute(int, int) * @brief The definition of function Execute(int, int)
@ -55,29 +88,30 @@ void InitGame() {
* 01? * 01?
*/ */
namespace Client { namespace Client {
const unsigned int RndSeed = std::random_device{}(); const unsigned int kRndSeed = std::random_device{}();
std::mt19937 RawRnd(RndSeed); // a basic random generator std::mt19937 RawRnd(kRndSeed); // a basic random generator
const int max_size = 35; const double kRawRndMax = 4294967295.0;
char game_map[max_size][max_size]; // store the raw game map in format of char const int kMaxMapSize = 35;
std::queue<std::pair<int, int> > char game_map[kMaxMapSize][kMaxMapSize];
no_mine_block_to_be_clicked; // store the block that definitely has no mine // store the raw game map in format of char
// and not yet clicked std::queue<std::pair<int, int> > no_mine_block_to_be_clicked;
int map_status[max_size] // store the block that definitely has no mine and not yet clicked
[max_size]; // store the status of each block(processed version), /**
// 0 means unknown , -1 means has mine, 1 means no * @brief The definition of variable map_status
// mine and not yet clicked, and 2 means has been *
// clicked Note that if some block is found to be * @details map_status[kMaxMapSize][kMaxMapSize] stores the status of each
// definitely has no mine or has mine, it will be * block(processed version), 0 means unknown , -1 means has mine, 1 means no
// marked as known even if it is not clicked. In * mine and not yet clicked, and 2 means has been clicked Note that if some
// conclusion, if map_status[i][j] == 0, then * block is found to be definitely has no mine or has mine, it will be marked as
// game_map[i][j] == '?'. If map_status[i][j] == -1, * known even if it is not clicked. In conclusion, if map_status[i][j] == 0,
// then game_map[i][j] == '?', and it will never be * then game_map[i][j] == '?'. If map_status[i][j] == -1, then game_map[i][j] ==
// clicked. If map_status[i][j] == 1, then * '?', and it will never be clicked. If map_status[i][j]
// game_map[i][j] == '?', and it will be clicked * == 1, then game_map[i][j] == '?', and it will be clicked later. If
// later. If map_status[i][j] == 2, then * map_status[i][j] == 2, then game_map[i][j] == '0'-'8', and it has been
// game_map[i][j] == '0'-'8', and it has been clicked * clicked And when a block's status is updated from 0 to 1, it will be pushed
// And when a block's status is updated from 0 to 1, * into no_mine_block_to_be_clicked
// it will be pushed into no_mine_block_to_be_clicked */
int map_status[kMaxMapSize][kMaxMapSize];
/** /**
* @brief The definition of function ProcessSimpleCase() * @brief The definition of function ProcessSimpleCase()
* *
@ -87,21 +121,21 @@ void ProcessSimpleCase() {
for (int i = 0; i < rows; i++) for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) for (int j = 0; j < columns; j++)
if (map_status[i][j] == 2) { if (map_status[i][j] == 2) {
int nearby_mines = game_map[i][j] - '0', int nearby_mines = game_map[i][j] - '0', nearby_unkown = 0;
nearby_unkown = // nearby_mines is the number of mines in currently unknown blocks that
0; // nearby_mines is the number of mines in currently unknown // are adjacent to the block (i,j)
// blocks that are adjacent to the block (i,j)
const int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}, const int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1},
dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
for (int k = 0; k < 8; k++) { for (int k = 0; k < 8; k++) {
int x = i + dx[k], y = j + dy[k]; int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < rows && y >= 0 && y < columns) { if (x >= 0 && x < rows && y >= 0 && y < columns) {
if (map_status[x][y] == 0) if (map_status[x][y] == 0) {
nearby_unkown++; nearby_unkown++;
else if (map_status[x][y] == -1) } else if (map_status[x][y] == -1) {
nearby_mines--; nearby_mines--;
} }
} }
}
if (nearby_unkown != 0) { if (nearby_unkown != 0) {
if (nearby_mines == 0) { if (nearby_mines == 0) {
for (int k = 0; k < 8; k++) { for (int k = 0; k < 8; k++) {
@ -123,9 +157,8 @@ void ProcessSimpleCase() {
} }
} }
} }
std::map<std::pair<int, int>, int> std::map<std::pair<int, int>, int> position_to_variaID;
position_to_variaID; // convert the (row,column) to variable ID in the // convert the (row,column) to variable ID in the equations,0 based
// equations,0 based
std::vector<std::pair<int, int> > variaID_to_position; std::vector<std::pair<int, int> > variaID_to_position;
/** /**
* @brief The definition of function PrintEquations() * @brief The definition of function PrintEquations()
@ -133,21 +166,23 @@ std::vector<std::pair<int, int> > variaID_to_position;
* @details This function is designed to print the equations for debugging * @details This function is designed to print the equations for debugging
*/ */
void PrintEquations(std::vector<std::vector<double> > equations) { void PrintEquations(std::vector<std::vector<double> > equations) {
// return;
std::cout << "equations:" << std::endl; std::cout << "equations:" << std::endl;
for (int i = 0; i < equations.size(); i++) { for (int i = 0; i < equations.size(); i++) {
for (int j = 0; j < equations[i].size(); j++) for (int j = 0; j < equations[i].size(); j++)
std::cout << equations[i][j] << " "; std::cout << equations[i][j] << " ";
std::cout << std::endl; std::cout << std::endl;
} }
// use variaID_to_position to print the position of each variable /*use variaID_to_position to print the position of each variable*/
std::cout << "variaID_to_position:" << std::endl; std::cout << "variaID_to_position:" << std::endl;
// clang-format off
for (int i = 0; i < variaID_to_position.size(); i++) for (int i = 0; i < variaID_to_position.size(); i++)
std::cout << "(" << variaID_to_position[i].first << "," std::cout << "(" << variaID_to_position[i].first
<< variaID_to_position[i].second << ")" << ","
<< " "; << variaID_to_position[i].second
<< ") ";
// clang-format on
std::cout << std::endl; std::cout << std::endl;
// print map_status /*print map_status*/
std::cout << "map_status:" << std::endl; std::cout << "map_status:" << std::endl;
for (int i = 0; i < rows; i++) { for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) std::cout << map_status[i][j] << " "; for (int j = 0; j < columns; j++) std::cout << map_status[i][j] << " ";
@ -208,8 +243,9 @@ std::vector<std::vector<double> > GenerateEquations() {
if (map_status[x][y] == -1) nearby_mines--; if (map_status[x][y] == -1) nearby_mines--;
} }
} }
equations[equations.size() - 1][position_to_variaID.size()] = // clang-format off
nearby_mines; equations[equations.size() - 1][position_to_variaID.size()] = nearby_mines;
// clang-format on
for (int k = 0; k < 8; k++) { for (int k = 0; k < 8; k++) {
int nr = i + dx[k], nc = j + dy[k]; int nr = i + dx[k], nc = j + dy[k];
if (nr < 0 || nr >= rows || nc < 0 || nc >= columns) continue; if (nr < 0 || nr >= rows || nc < 0 || nc >= columns) continue;
@ -219,7 +255,7 @@ std::vector<std::vector<double> > GenerateEquations() {
} }
} }
// PrintEquations(equations); // PrintEquations(equations);
// randome shuffle lines of equations using RawRnd /*randome shuffle lines of equations using RawRnd*/
std::random_shuffle(equations.begin(), equations.end(), RandIntLessThan); std::random_shuffle(equations.begin(), equations.end(), RandIntLessThan);
return equations; return equations;
} }
@ -230,14 +266,15 @@ std::vector<std::vector<double> > GenerateEquations() {
* &equations * &equations
* @param vector<vector<double>> equations The equations to be solved * @param vector<vector<double>> equations The equations to be solved
*/ */
const double eps = 1e-6; const double kEps = 1e-6;
const int error_status_of_nearint = -0x3f3f3f3f; const int kErrorStatusOfNearbyInt = -0x3f3f3f3f;
inline int NearbyInt(double v) { inline int NearbyInt(double v) {
int raw = v + 0.5; int raw = v + 0.5;
if (abs(v - raw) < eps) if (abs(v - raw) < kEps) {
return raw; return raw;
else } else {
return error_status_of_nearint; return kErrorStatusOfNearbyInt;
}
} }
std::vector<std::vector<double> > GaussianJordanElimination( std::vector<std::vector<double> > GaussianJordanElimination(
std::vector<std::vector<double> > equations) { std::vector<std::vector<double> > equations) {
@ -248,7 +285,7 @@ std::vector<std::vector<double> > GaussianJordanElimination(
std::vector<double> equa_template; std::vector<double> equa_template;
equa_template.resize(m); equa_template.resize(m);
// assert(n + 1 == m); // assert(n + 1 == m);
for (int tot = 0; tot < 5; tot++) { for (int tot = 0; tot < 10; tot++) {
n = equations.size(); n = equations.size();
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
int pivot = i; int pivot = i;
@ -256,7 +293,7 @@ std::vector<std::vector<double> > GaussianJordanElimination(
for (int j = i + 1; j < n; j++) for (int j = i + 1; j < n; j++)
if (abs(equations[j][i]) > abs(equations[pivot][i])) pivot = j; if (abs(equations[j][i]) > abs(equations[pivot][i])) pivot = j;
std::swap(equations[i], equations[pivot]); std::swap(equations[i], equations[pivot]);
if (abs(equations[i][i]) < eps) continue; if (abs(equations[i][i]) < kEps) continue;
const double pivot_value = equations[i][i]; const double pivot_value = equations[i][i];
for (int j = 0; j < m; j++) equations[i][j] /= pivot_value; for (int j = 0; j < m; j++) equations[i][j] /= pivot_value;
for (int j = 0; j < n; j++) for (int j = 0; j < n; j++)
@ -265,13 +302,12 @@ std::vector<std::vector<double> > GaussianJordanElimination(
for (int k = 0; k < m; k++) equations[j][k] -= tmp * equations[i][k]; for (int k = 0; k < m; k++) equations[j][k] -= tmp * equations[i][k];
} }
} }
// continue;
for (int i = 0; i < equations.size(); i++) { for (int i = 0; i < equations.size(); i++) {
bool error_occur = false; bool error_occur = false;
int total_num = 0; int total_num = 0;
for (int j = 0; j < m - 1; j++) { for (int j = 0; j < m - 1; j++) {
int v = NearbyInt(equations[i][j]); int v = NearbyInt(equations[i][j]);
if (v == error_status_of_nearint || v < 0) { if (v == kErrorStatusOfNearbyInt || v < 0) {
error_occur = true; error_occur = true;
break; break;
} }
@ -335,18 +371,21 @@ void InterpretResult(std::vector<std::vector<double> > equations) {
if (NearbyInt(equations[i][j]) == 1) { if (NearbyInt(equations[i][j]) == 1) {
number_of_1++; number_of_1++;
vid = j; vid = j;
} else if (NearbyInt(equations[i][j]) != 0) } else if (NearbyInt(equations[i][j]) != 0) {
number_of_non1++; number_of_non1++;
}
if (number_of_non1) continue; if (number_of_non1) continue;
if (number_of_1 != 1) continue; if (number_of_1 != 1) continue;
int sol = NearbyInt(equations[i][m - 1]); int sol = NearbyInt(equations[i][m - 1]);
if (sol == error_status_of_nearint) continue; if (sol == kErrorStatusOfNearbyInt) continue;
if (sol != 0 && sol != 1) { if (sol != 0 && sol != 1) {
std::cerr << "sol=" << sol << std::endl; std::cerr << "sol=" << sol << std::endl;
std::cerr << "one=" << number_of_1 std::cerr << "one=" << number_of_1
<< " not one not zero=" << number_of_non1 << std::endl; << " not one not zero=" << number_of_non1 << std::endl;
std::cerr << NearbyInt(equations[i][m - 1]) << ' ' << equations[i][m - 1] // clang-format off
<< std::endl; std::cerr << NearbyInt(equations[i][m - 1]) << ' '
<< equations[i][m - 1] << std::endl;
// clang-format on
PrintEquations(equations); PrintEquations(equations);
} }
assert(sol == 0 || sol == 1); assert(sol == 0 || sol == 1);
@ -380,23 +419,24 @@ void InterpretResult(std::vector<std::vector<double> > equations) {
* it will be marked as known even if it is not clicked. * it will be marked as known even if it is not clicked.
*/ */
void PreProcessData() { void PreProcessData() {
using namespace Client; /*scan the game_map and mark clicked block in map_status*/
// scan the game_map and mark clicked block in map_status
for (int i = 0; i < rows; i++) for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) for (int j = 0; j < columns; j++)
if (game_map[i][j] != '?') { if (game_map[i][j] != '?') {
assert(game_map[i][j] >= '0' && game_map[i][j] <= '8'); assert(game_map[i][j] >= '0' && game_map[i][j] <= '8');
map_status[i][j] = 2; map_status[i][j] = 2;
} }
// scan the map and process the simplest case /*scan the map and process the simplest case*/
ProcessSimpleCase(); ProcessSimpleCase();
// 1.find all unkown blocks that are adjacnent to clicked blocks and prepare /**
// for Gaussian-Jordan Elimination. * 1.find all unkown blocks that are adjacnent to clicked blocks and prepare
// 2. start Gaussian-Jordan Elimination * for Gaussian-Jordan Elimination.
// 3. interpret the result of Gaussian-Jordan Elimination,store the result in * 2. start Gaussian-Jordan Elimination
// map_status and push the newly found block that definitely has no mine * 3. interpret the result of Gaussian-Jordan Elimination,store the result in
// into no_mine_block_to_be_clicked * map_status and push the newly found block that definitely has no mine
for (int i = 0; i < 8; i++) { * into no_mine_block_to_be_clicked
*/
for (int i = 0; i < 15; i++) {
std::vector<std::vector<double> > equations = GenerateEquations(); std::vector<std::vector<double> > equations = GenerateEquations();
equations = GaussianJordanElimination(equations); equations = GaussianJordanElimination(equations);
InterpretResult(equations); InterpretResult(equations);
@ -410,10 +450,10 @@ void PreProcessData() {
* just used temporarily before a better algorithm is designed. * just used temporarily before a better algorithm is designed.
*/ */
std::pair<int, int> TotalRandomGuess() { std::pair<int, int> TotalRandomGuess() {
using namespace Client;
// std::cout << "TotalRandomGuess" << std::endl; // std::cout << "TotalRandomGuess" << std::endl;
std::uniform_int_distribution<int> row_dis(0, rows - 1), // clang-format off
column_dis(0, columns - 1); std::uniform_int_distribution<int> row_dis(0, rows - 1), column_dis(0, columns - 1);
// clang-format on
int row = row_dis(RawRnd), column = column_dis(RawRnd); int row = row_dis(RawRnd), column = column_dis(RawRnd);
while (map_status[row][column] != 0) { while (map_status[row][column] != 0) {
row = row_dis(RawRnd); row = row_dis(RawRnd);
@ -424,10 +464,11 @@ std::pair<int, int> TotalRandomGuess() {
inline double GetProb(double default_p, const std::vector<double> &ps) { inline double GetProb(double default_p, const std::vector<double> &ps) {
if (ps.empty()) return default_p; if (ps.empty()) return default_p;
double res = 0; double res = 0;
const double v = const double kPowerMeanCoefficient = 1.75;
2; // use root mean square to estimate the probability in a cautious way // use root mean square to estimate the probability in a cautious way,and this
for (int i = 0; i < ps.size(); i++) res += pow(ps[i], v); // specific value is chosen by testing
return pow(res / ps.size(), 1.0 / v); for (int i = 0; i < ps.size(); i++) res += pow(ps[i], kPowerMeanCoefficient);
return pow(res / ps.size(), 1.0 / kPowerMeanCoefficient);
} }
/** /**
* @brief The definition of function SimpleGuess() * @brief The definition of function SimpleGuess()
@ -436,10 +477,12 @@ inline double GetProb(double default_p, const std::vector<double> &ps) {
* none-mine block to be clicked using simple algorithm. * none-mine block to be clicked using simple algorithm.
*/ */
std::pair<int, int> SimpleGuess() { std::pair<int, int> SimpleGuess() {
using namespace Client; const double kMappingParameterEdge = 0.5;
const double kMappingParameterCorner = 2.5;
// std::cout << "SimpleGuess" << std::endl; // std::cout << "SimpleGuess" << std::endl;
std::vector<double> probability[max_size][max_size]; std::vector<double> probability[kMaxMapSize][kMaxMapSize];
double default_probability = 0.06; double default_probability =
pow(0.06, (kMappingParameterEdge + kMappingParameterCorner) / 2);
int total_known = 0, total_known_with_mine = 0; int total_known = 0, total_known_with_mine = 0;
for (int i = 0; i < rows; i++) for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) for (int j = 0; j < columns; j++)
@ -448,37 +491,51 @@ std::pair<int, int> SimpleGuess() {
if (map_status[i][j] == -1) total_known_with_mine++; if (map_status[i][j] == -1) total_known_with_mine++;
} }
if (total_known > 5) if (total_known > 5)
default_probability = (double)(total_known_with_mine) / (total_known); default_probability =
pow((double)(total_known_with_mine) / (total_known),
(kMappingParameterCorner + kMappingParameterEdge) / 2);
// if((double)(total_known)/(rows*columns)<0.15) return TotalRandomGuess(); // if((double)(total_known)/(rows*columns)<0.15) return TotalRandomGuess();
for (int i = 0; i < rows; i++) for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) for (int j = 0; j < columns; j++)
if (map_status[i][j] == 2) { if (map_status[i][j] == 2) {
int nearby_mines = game_map[i][j] - '0', int nearby_mines = game_map[i][j] - '0', nearby_unkown = 0;
nearby_unkown = // nearby_mines is the number of mines in currently unknown blocks that
0; // nearby_mines is the number of mines in currently unknown // are adjacent to the block (i,j)
// blocks that are adjacent to the block (i,j)
const int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}, const int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1},
dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1}; dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
for (int k = 0; k < 8; k++) { for (int k = 0; k < 8; k++) {
int x = i + dx[k], y = j + dy[k]; int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < rows && y >= 0 && y < columns) { if (x >= 0 && x < rows && y >= 0 && y < columns) {
if (map_status[x][y] == 0) if (map_status[x][y] == 0) {
nearby_unkown++; nearby_unkown++;
else if (map_status[x][y] == -1) } else if (map_status[x][y] == -1) {
nearby_mines--; nearby_mines--;
} }
} }
}
if (nearby_unkown == 0) continue; if (nearby_unkown == 0) continue;
for (int k = 0; k < 8; k++) { for (int k = 0; k < 8; k++) {
int x = i + dx[k], y = j + dy[k]; int x = i + dx[k], y = j + dy[k];
if (x >= 0 && x < rows && y >= 0 && y < columns) { if (x >= 0 && x < rows && y >= 0 && y < columns) {
if (map_status[x][y] == 0) if (map_status[x][y] == 0)
probability[x][y].push_back((double)(nearby_mines) / if (abs(dx[k]) + abs(dy[k]) == 2) {
(nearby_unkown)); probability[x][y].push_back(
pow((double)(nearby_mines) / (nearby_unkown),
kMappingParameterCorner));
} else {
probability[x][y].push_back(
pow((double)(nearby_mines) / (nearby_unkown),
kMappingParameterEdge));
}
assert(abs(dx[k]) + abs(dy[k]) == 1 ||
abs(dx[k]) + abs(dy[k]) == 2);
} }
} }
} }
std::pair<int, int> best_guess = TotalRandomGuess(); std::pair<int, int> best_guess = TotalRandomGuess();
bool allow_a_guess = true;
const double kRandomBeginConsiderationRatio = 0.95;
const double kRandomTightnessParameter = 10;
for (int i = 0; i < rows; i++) for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) for (int j = 0; j < columns; j++)
if (map_status[i][j] == 0) { if (map_status[i][j] == 0) {
@ -489,6 +546,15 @@ std::pair<int, int> SimpleGuess() {
if (this_prob < current_prob) { if (this_prob < current_prob) {
best_guess.first = i; best_guess.first = i;
best_guess.second = j; best_guess.second = j;
} else if ((double)(total_known) / (rows * columns) >
kRandomBeginConsiderationRatio &&
allow_a_guess) {
if (exp(-(this_prob - current_prob) * kRandomTightnessParameter) >
RawRnd() / kRawRndMax) {
best_guess.first = i;
best_guess.second = j;
// allow_a_guess = false;
}
} }
} }
return best_guess; return best_guess;
@ -499,31 +565,25 @@ std::pair<int, int> SimpleGuess() {
* @details This function is designed to make the best guess when there is no * @details This function is designed to make the best guess when there is no
* definite none-mine block to be clicked. * definite none-mine block to be clicked.
*/ */
std::pair<int, int> MakeBestGuess() { inline std::pair<int, int> MakeBestGuess() { return SimpleGuess(); }
using namespace Client;
// just make a total random guess before a better algorithm is designed
// return TotalRandomGuess();
return SimpleGuess();
return std::make_pair(0, 0);
}
/** /**
* @brief The definition of function GenerateNextStep() * @brief The definition of function GenerateNextStep()
* *
* @details This function is designed to generate the next step when playing * @details This function is designed to generate the next step when playing
* the client's (or player's) role. * the client's (or player's) role.
*/ */
std::pair<int, int> GenerateNextStep() { inline std::pair<int, int> GenerateNextStep() {
using namespace Client;
if (!no_mine_block_to_be_clicked.empty()) { if (!no_mine_block_to_be_clicked.empty()) {
std::pair<int, int> next_step = no_mine_block_to_be_clicked.front(); std::pair<int, int> next_step = no_mine_block_to_be_clicked.front();
no_mine_block_to_be_clicked.pop(); no_mine_block_to_be_clicked.pop();
return next_step; return next_step;
} else } else {
return MakeBestGuess(); return MakeBestGuess();
}
} }
} // namespace Client } // namespace Client
void ReadMap() { void ReadMap() {
using namespace Client; using Client::PreProcessData, Client::game_map;
for (int i = 0; i < rows; i++) { for (int i = 0; i < rows; i++) {
std::cin >> game_map[i]; std::cin >> game_map[i];
assert(strlen(game_map[i]) == columns); assert(strlen(game_map[i]) == columns);
@ -538,11 +598,11 @@ void ReadMap() {
* client's (or player's) role. Open up your mind and make your decision here! * client's (or player's) role. Open up your mind and make your decision here!
*/ */
void Decide() { void Decide() {
using namespace Client; using Client::GenerateNextStep;
while (true) { while (true) {
std::pair<int, int> next_step = GenerateNextStep(); std::pair<int, int> next_step = GenerateNextStep();
Execute(next_step.first, next_step.second); Execute(next_step.first, next_step.second);
} }
} }
#endif #endif // CLIENT_H

View File

@ -1,3 +1,30 @@
/**
* @file server.h
*
* @brief The header file of the server.
* @details This file contains the definition of the functions that you need to
* call to run the game.
*
* @codesytle This file is written in a sytle mainly based on Google C++ Style
* Guide. What's sepecial is the comment:
* 1. Multi-line comments are always before the code they comment on.
* Usually the code they comment on is a complex procedure,like the definition
* of a function,a class or a variable with complex operation. If a multi-line
* comment is in one line, it will start with "/*" instead of "/**",otherwise it
* will start with "/**" and in the format of Doxygen.
* 2. Single-line comments are always after the code they comment on.
* Usually they are in the same line with the code they comment on,but sometimes
* they may come in the next lines. single-line comments shouldn't exceed 3
* lines as they are intended to be short and easy to understand.
* 3. Temporary commented code will be marked with "//" in the front of each
* 4. Some comments have special meanings,like "//TODO", "//FIXME", "//XXX","//
* clang-format off" and "// clang-format on". They are not controlled by the
* previous rules.
*
* As I use Clang-format to format my code, so the code style may be a little
* bit strange when parameters or phrases are too long,thus I'm try to manually
* format the code.
*/
#ifndef SERVER_H #ifndef SERVER_H
#define SERVER_H #define SERVER_H
@ -14,17 +41,17 @@
* yet. However, if you are member of A-class or have learnt the use of cpp * yet. However, if you are member of A-class or have learnt the use of cpp
* class, member functions, etc., you're free to modify this structure. * class, member functions, etc., you're free to modify this structure.
*/ */
// This server has passed the formal test, do not modify it if you are not sure
int rows; // The count of rows of the game map int rows; // The count of rows of the game map
int columns; // The count of columns of the game map int columns; // The count of columns of the game map
int game_state; // The state of the game, 0 for continuing, 1 for winning, -1 int game_state; // The state of the game, 0 for continuing, 1 for winning, -1
// for losing // for losing
namespace Server { namespace Server {
int visit_count, step_count; int visit_count, step_count;
const int max_size = 35; const int kMaxMapSize = 35;
char origin_map[max_size][max_size]; // The original map char origin_map[kMaxMapSize][kMaxMapSize]; // The original map
char visible_map[max_size][max_size]; // The map that the player can see char visible_map[kMaxMapSize][kMaxMapSize]; // The map that the player can see
int number_of_nearby_mines[max_size][max_size]; // The number of nearby mines int number_of_nearby_mines[kMaxMapSize][kMaxMapSize];
// The number of nearby mines
int number_of_all_mines; // The number of all mines int number_of_all_mines; // The number of all mines
} // namespace Server } // namespace Server
/** /**
@ -40,8 +67,9 @@ int number_of_all_mines; // The number of all mines
* blocks unvisited. * blocks unvisited.
*/ */
void InitMap() { void InitMap() {
using namespace std; using Server::origin_map, Server::visible_map, Server::number_of_nearby_mines,
using namespace Server; Server::number_of_all_mines;
using std::cin;
std::cin >> rows >> columns; std::cin >> rows >> columns;
assert(2 <= rows && rows <= 30 && 2 <= columns && columns <= 30); assert(2 <= rows && rows <= 30 && 2 <= columns && columns <= 30);
for (int i = 0; i < rows; i++) { for (int i = 0; i < rows; i++) {
@ -63,7 +91,7 @@ void InitMap() {
for (int i = 0; i < rows; i++) for (int i = 0; i < rows; i++)
for (int j = 0; j < columns; j++) for (int j = 0; j < columns; j++)
if (origin_map[i][j] == 'X') number_of_all_mines++; if (origin_map[i][j] == 'X') number_of_all_mines++;
// bug test: output the number of nearby mines /* bug test: output the number of nearby mines*/
// for (int i = 0; i < rows; i++) { // for (int i = 0; i < rows; i++) {
// for (int j = 0; j < columns; j++) { // for (int j = 0; j < columns; j++) {
// cout << number_of_nearby_mines[i][j] << ' '; // cout << number_of_nearby_mines[i][j] << ' ';
@ -98,9 +126,11 @@ void InitMap() {
* the game ends and the player loses. * the game ends and the player loses.
*/ */
void VisitBlock(int row, int column) { void VisitBlock(int row, int column) {
using namespace Server; using Server::step_count, Server::origin_map, Server::visible_map,
Server::number_of_nearby_mines, Server::number_of_all_mines,
Server::visit_count;
step_count++; step_count++;
using namespace std; using std::queue, std::make_pair, std::pair;
assert(0 <= row && row < rows && 0 <= column && column < columns); assert(0 <= row && row < rows && 0 <= column && column < columns);
if (origin_map[row][column] == 'X') { if (origin_map[row][column] == 'X') {
game_state = -1; game_state = -1;
@ -161,7 +191,7 @@ void VisitBlock(int row, int column) {
* the advanced task!!! * the advanced task!!!
*/ */
void PrintMap() { void PrintMap() {
using namespace Server; using Server::visible_map, Server::origin_map, Server::number_of_nearby_mines;
if (game_state != 1) { if (game_state != 1) {
for (int i = 0; i < rows; i++) { for (int i = 0; i < rows; i++) {
std::cout << visible_map[i] << std::endl; std::cout << visible_map[i] << std::endl;
@ -189,7 +219,7 @@ void PrintMap() {
* number of steps taken respectively. * number of steps taken respectively.
*/ */
void ExitGame() { void ExitGame() {
using namespace Server; using Server::visit_count, Server::step_count;
assert(game_state != 0); assert(game_state != 0);
if (game_state == 1) { if (game_state == 1) {
std::cout << "YOU WIN!" << std::endl; std::cout << "YOU WIN!" << std::endl;
@ -201,4 +231,4 @@ void ExitGame() {
exit(0); // Exit the game immediately exit(0); // Exit the game immediately
} }
#endif #endif // SERVER_H

View File

@ -0,0 +1,12 @@
10 10
.XX.......
...X...XX.
..........
...X......
.X......X.
..X....X..
XX....X...
..X...X...
..X.X.X.X.
....X.....
3 6

View File

@ -0,0 +1,22 @@
20 20
X...........X.......
..XX.............X.X
.X......X.........X.
....X.X...X...X.....
....X.X.........X...
X.XX...........X.X..
...X.............XX.
...XX...X..X..X.X...
...X.X...X...X......
.............XX.X...
.X............X.....
.X..X.........X....X
X.X.X..X...X..X.....
.X....X.X......X....
...X.........X..X...
...X.X...X..........
..X.XX.X......XXXX..
.X.X...............X
XXX..X....X.XX..X...
.X...X.XX........X.X
4 12