#include <windows.h>
#include <windowsx.h>
#include <ShObjIdl.h>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <algorithm>
#include <iostream>
#include <cstdio>
#define CELL_SIZE 50
#define BOARD_SIZE 15
using namespace std;
// 定义一些常量
const int MARGIN = 5; // 棋盘边缘空隙
const int LINE_WIDTH = 5;
const int SCORE_WIN = 30000000; // 赢得分数
const int SCORE_LIVE_FOUR = 700000; // 活四分数
const int SCORE_HALF_SLEEP_FOUR = 450000; // 半眠四分数
const int SCORE_SLEEP_FOUR = -1; // 眠四分数
const int SCORE_LIVE_THREE = 40000; // 活三分数
const int SCORE_HALF_SLEEP_THREE = 1; // 半眠三分数
const int SCORE_SLEEP_THREE = -5; // 眠三分数
const int SCORE_LIVE_TWO = 9000; // 活二分数
const int SCORE_HALF_SLEEP_TWO = 0; // 半眠二分数
const int SCORE_SLEEP_TWO = -5; // 眠二分数
const int SCORE_LIVE_ONE = 2000; // 活一分数
const int SCORE_HALF_SLEEP_ONE = -10; // 半眠一分数
const int SCORE_SLEEP_ONE = -10; // 眠一分数
// 定义一些枚举类型
enum Player {NONE, HUMAN, COMPUTER}; // 玩家类型,无、人类、机器
enum Direction {HORIZONTAL, VERTICAL, LEFT_DIAGONAL, RIGHT_DIAGONAL}; // 方向类型,水平、垂直、左斜、右斜
// 定义一个结构体,表示棋盘上的一个位置
struct Position {
int x; // 横坐标
int y; // 纵坐标
Position(int x = -2, int y = -2) : x(x), y(y) {} // 构造函数,初始化为-1表示无效位置
};
// 定义一个全局变量,表示上一次落子的位置,初始为无效位置
Position lastPos(-2, -2);
// 定义一个结构体,表示一个评分项,包括位置和分数
struct ScoreItem {
Position pos; // 位置
int score; // 分数
ScoreItem(Position pos, int score) : pos(pos), score(score) {} // 构造函数,初始化位置和分数
};
HINSTANCE hInstance;
Player board[BOARD_SIZE][BOARD_SIZE];
int currentPlayer = 1;
//1 human
//2 robot
bool gameOver = false;
Player starter[2] = {NONE,NONE}, winner = NONE;
bool ended = false;
int steps = 0;
int ManhattanDistance(int x_1,int y_1,int x_2,int y_2){
return abs(x_1-x_2)+abs(y_1-y_2);
}
void ComputerMove();
int Evaluate(Position pos, Player player);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
hInstance = hInst;
WNDCLASS wc = {0};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInst;
wc.lpszClassName = "Gobang";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClass(&wc);
HWND hwnd = CreateWindow("Gobang", "Gobang", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CELL_SIZE * BOARD_SIZE + 70, CELL_SIZE * BOARD_SIZE + 70, NULL, NULL, hInst, NULL);
SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX);
DeleteMenu(GetSystemMenu(hwnd, FALSE), SC_SIZE, MF_BYCOMMAND);
DeleteMenu(GetSystemMenu(hwnd, FALSE), SC_MAXIMIZE, MF_BYCOMMAND);
DeleteMenu(GetSystemMenu(hwnd, FALSE), SC_MINIMIZE, MF_BYCOMMAND);
DeleteMenu(GetSystemMenu(hwnd, FALSE), SC_RESTORE, MF_BYCOMMAND);
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// 从任务管理器获取图标
HICON hIcon;
ExtractIconEx("C:\\Windows\\System32\\taskmgr.exe", 0, NULL, &hIcon, 1);
// 设置程序图标
SendMessage(GetActiveWindow(), WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessage(GetActiveWindow(), WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CREATE:
break;
case WM_PAINT:
{
steps++;
if(steps==1){
// if(rand()%2)
//board[7][7]=COMPUTER,
//starter[0]=COMPUTER,starter[1]=HUMAN;
//else
starter[0]=HUMAN,starter[1]=COMPUTER;
}
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 创建一个黄色的画刷
HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 0));
// 将画刷选入设备上下文
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
// 绘制一个正方形
Rectangle(hdc, (lastPos.x)*CELL_SIZE+5, (lastPos.y)*CELL_SIZE+5, (lastPos.x+1)*CELL_SIZE+5, (lastPos.y+1)*CELL_SIZE+5);
// 恢复原来的画刷
SelectObject(hdc, hOldBrush);
// 删除创建的画刷
DeleteObject(hBrush);
for(int i = 1; i <= BOARD_SIZE + 1; i++)
{
MoveToEx(hdc, CELL_SIZE * (i - 1) + 5, 5, NULL);
LineTo(hdc, CELL_SIZE * (i - 1) + 5, CELL_SIZE * BOARD_SIZE + 5);
MoveToEx(hdc, 5, CELL_SIZE * (i - 1) + 5, NULL);
LineTo(hdc, CELL_SIZE * BOARD_SIZE + 5, CELL_SIZE * (i - 1) + 5);
}
for(int i = 0; i < BOARD_SIZE; i++)
{
for(int j = 0; j < BOARD_SIZE; j++)
{
RECT cellRect = {i * CELL_SIZE, j * CELL_SIZE, (i + 1) * CELL_SIZE, (j + 1) * CELL_SIZE};
if(board[i][j] == 1)
{
Ellipse(hdc, cellRect.left + 15, cellRect.top + 15, cellRect.right - 5, cellRect.bottom - 5);
}
else if(board[i][j] == 2)
{
MoveToEx(hdc, cellRect.left + 15, cellRect.top + 15, NULL);
LineTo(hdc, cellRect.right - 5, cellRect.bottom - 5);
MoveToEx(hdc, cellRect.right - 5, cellRect.top + 15, NULL);
LineTo(hdc, cellRect.left + 15, cellRect.bottom - 5);
}
}
}
EndPaint(hwnd, &ps);
break;
}
case WM_LBUTTONDOWN:
{
if(gameOver){
ended = true;
}
if(ended)
break;
int x = GET_X_LPARAM(lParam) / CELL_SIZE;
int y = GET_Y_LPARAM(lParam) / CELL_SIZE;
if(board[x][y] == 0)
{
board[x][y] = HUMAN;
lastPos=Position(x,y);
InvalidateRect(hwnd, NULL, TRUE);
gameOver = false;
for(int i = 0; i < BOARD_SIZE; i++)
for(int j = 0; j < BOARD_SIZE - 4; j++)
if(board[i][j] == currentPlayer && board[i][j+1] == currentPlayer
&& board[i][j+2] == currentPlayer && board[i][j+3] == currentPlayer
&& board[i][j+4] == currentPlayer)
gameOver = true;
for(int i = 0; i < BOARD_SIZE - 4; i++)
for(int j = 0; j < BOARD_SIZE; j++)
if(board[i][j] == currentPlayer && board[i+1][j] == currentPlayer
&& board[i+2][j] == currentPlayer && board[i+3][j] == currentPlayer
&& board[i+4][j] == currentPlayer)
gameOver = true;
for(int i = 0; i < BOARD_SIZE - 4; i++)
for(int j = 0; j < BOARD_SIZE - 4; j++)
if(board[i][j] == currentPlayer && board[i+1][j+1] == currentPlayer
&& board[i+2][j+2] == currentPlayer && board[i+3][j+3] == currentPlayer
&& board[i+4][j+4] == currentPlayer)
gameOver = true;
for(int i = 0; i < BOARD_SIZE - 4; i++)
for(int j = 4; j < BOARD_SIZE; j++)
if(board[i][j] == currentPlayer && board[i+1][j-1] == currentPlayer
&& board[i+2][j-2] == currentPlayer && board[i+3][j-3] == currentPlayer
&& board[i+4][j-4] == currentPlayer)
gameOver = true;
if(gameOver == true){
MessageBox(hwnd, "Game over!", "Gobang", MB_OK);
break;
}
currentPlayer = 2;
ComputerMove();
InvalidateRect(hwnd, NULL, TRUE);
gameOver = false;
for(int i = 0; i < BOARD_SIZE; i++)
for(int j = 0; j < BOARD_SIZE - 4; j++)
if(board[i][j] == currentPlayer && board[i][j+1] == currentPlayer
&& board[i][j+2] == currentPlayer && board[i][j+3] == currentPlayer
&& board[i][j+4] == currentPlayer)
gameOver = true;
for(int i = 0; i < BOARD_SIZE - 4; i++)
for(int j = 0; j < BOARD_SIZE; j++)
if(board[i][j] == currentPlayer && board[i+1][j] == currentPlayer
&& board[i+2][j] == currentPlayer && board[i+3][j] == currentPlayer
&& board[i+4][j] == currentPlayer)
gameOver = true;
for(int i = 0; i < BOARD_SIZE - 4; i++)
for(int j = 0; j < BOARD_SIZE - 4; j++)
if(board[i][j] == currentPlayer && board[i+1][j+1] == currentPlayer
&& board[i+2][j+2] == currentPlayer && board[i+3][j+3] == currentPlayer
&& board[i+4][j+4] == currentPlayer)
gameOver = true;
for(int i = 0; i < BOARD_SIZE - 4; i++)
for(int j = 4; j < BOARD_SIZE; j++)
if(board[i][j] == currentPlayer && board[i+1][j-1] == currentPlayer
&& board[i+2][j-2] == currentPlayer && board[i+3][j-3] == currentPlayer
&& board[i+4][j-4] == currentPlayer)
gameOver = true;
if(gameOver == true){
MessageBox(hwnd, "Game over!", "Gobang", MB_OK);
break;
}
currentPlayer = 1;
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
// 机器落子函数
void ComputerMove() {
// 定义一个评分项的向量,存储每个位置的评分
vector<ScoreItem> scores;
// 遍历棋盘上的每个位置
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
// 如果当前位置没有棋子,则评估该位置的分数
if (board[i][j] == NONE) {
// 定义一个位置结构体,存储当前位置
Position pos(i, j);
// 定义两个变量,分别存储机器玩家和人类玩家在该位置的分数
int computerScore = Evaluate(pos, COMPUTER);
int humanScore = Evaluate(pos, HUMAN);
// 定义一个变量,存储该位置的综合分数,取机器玩家和人类玩家分数中的较大者
int score = computerScore + humanScore;
// 在评分向量中压入该位置和分数的评分项
scores.push_back(ScoreItem(pos, score));
}
}
}
// 如果评分向量为空,则表示棋盘已满,直接返回
if (scores.empty()) {
return;
}
// 对评分向量进行排序,按照分数从大到小的顺序
sort(scores.begin(), scores.end(), [](const ScoreItem& a, const ScoreItem& b) {
return a.score > b.score;
});
// 定义一个变量,存储最佳位置,初始为评分向量中第一个位置(分数最高)
Position bestPos = scores[0].pos;
// 定义一个变量,存储最高分数
int maxScore = scores[0].score;
// 定义一个整数向量,存储所有最高分数的位置的下标
vector<int> maxIndices;
// 遍历评分向量,找出所有最高分数的位置的下标,并压入整数向量中
for (int i = 0; i < scores.size(); i++) {
if (scores[i].score == maxScore) {
maxIndices.push_back(i);
}
else {
break;
}
}
// 如果整数向量中有多个下标,则从中随机选择一个作为最佳位置的下标
if (maxIndices.size() > 1) {
int index = rand() % maxIndices.size();
bestPos = scores[maxIndices[index]].pos;
}
// 在棋盘数组中记录机器玩家的棋子
board[bestPos.x][bestPos.y] = COMPUTER;
lastPos=Position(bestPos.x,bestPos.y);
}
// 评估一个位置的分数函数,参数为位置和玩家类型,返回分数
int Evaluate(Position pos, Player player) {
int totalScore = 0;
// 定义一个变量,存储总分数,初始为0
// 定义一个数组,存储四个方向上的偏移量
int offset[4][2] = {{-1, 0}, {0, -1}, {-1, -1}, {-1, 1}};
// 遍历四个方向
for (int k = 0; k < 4; k++) {
// 定义两个变量,表示当前方向上的偏移量
int dx = offset[k][0];
int dy = offset[k][1];
// 定义两个变量,表示当前位置的坐标
int x = pos.x;
int y = pos.y;
// 定义两个变量,分别表示该方向上的活/眠状态和连子数,初始为活和1(包括自己)
int live = 0;
int count = 1;
int empty = 0;
// 沿着当前方向前进,直到遇到边界或不同颜色的棋子,累加连子数,判断活/眠状态
while (true) {
x += dx;
y += dy;
if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE || (board[x][y] != player)) {
if (board[x][y] != NONE) {
live ++; // 如果遇到边界或对手的棋子,则为眠
}
break;
}
count++;
}
// 沿着当前方向后退,直到遇到边界或不同颜色的棋子,累加连子数,判断活/眠状态
x = pos.x;
y = pos.y;
while (true) {
x -= dx;
y -= dy;
if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE || board[x][y] != player) {
if (board[x][y] != NONE) {
live ++; // 如果遇到边界或对手的棋子,则为眠
}
break;
}
count++;
}
if(count>=5) live = 0;
// 根据活/眠状态和连子数,给该方向上的分数赋值
int score = 0;
if (count >= 5) { // 如果连子数大于等于5,则为必胜分数
score = SCORE_WIN;
if(player == COMPUTER) score*=4;
}
else if (live==0) { // 如果是活的
switch (count) { // 根据连子数分别赋值
case 4:
score = SCORE_LIVE_FOUR;
if(player == COMPUTER) score*=2;
break;
case 3:
score = SCORE_LIVE_THREE;
break;
case 2:
score = SCORE_LIVE_TWO;
break;
case 1:
score = SCORE_LIVE_ONE;
break;
default:
break;
}
}
else if(live == 1){ // 如果是眠的
switch (count) { // 根据连子数分别赋值
case 4:
score = SCORE_HALF_SLEEP_FOUR;
if(player == COMPUTER) score*=3/2;
break;
case 3:
score = SCORE_HALF_SLEEP_THREE;
break;
case 2:
score = SCORE_HALF_SLEEP_TWO;
break;
case 1:
score = SCORE_HALF_SLEEP_ONE;
break;
default:
break;
}
}
else { // 如果是眠的
switch (count) { // 根据连子数分别赋值
case 4:
score = SCORE_SLEEP_FOUR;
break;
case 3:
score = SCORE_SLEEP_THREE;
break;
case 2:
score = SCORE_SLEEP_TWO;
break;
case 1:
score = SCORE_SLEEP_ONE;
break;
default:
break;
}
}
// 累加该方向上的分数到总分数
totalScore += score;
}
// 返回总分数
return totalScore;
}