五子棋游戏人机对战 C/C++ (图形界面)
准备:找好游戏图片、音乐等基本素材。另外,代码中的图片尺寸数据根据各自的实际素材调整。
注:本人的素材均不做盈利用,不涉及版权。
tools文件是从相关网站学习课程上获得的,此处对相关内容不做明示。可以告知的是tools做的工作是使PNG图片背景透明。
tools.h
主函数:
#define _CRT_SECURE_NO_WARNINGS 1
/*
* 设计一个人机交互程序——五子棋 也可参考设计军棋、跳棋、围棋、象棋
*/
#include "ChessData.h"
int main() { //_wuziqi2
board_Init();
//主要功能区
while (1) { //~while(true) C/C++最好用while(1)
MOUSEMSG msg = GetMouseMsg(); //获取光标信息 如果报错4996,项目名右键->属性->c/c++->sdl检查选否
if (msg.uMsg == WM_LBUTTONDOWN && clickBoard(msg)) { //鼠标左键点击落子 且是有效点击
man_Go();
if (checkOver()) {
board_Init();
continue;
}
AI_Go();
if (checkOver()) {
board_Init();
continue;
}
}
}
closegraph();
system("pause");
return 0;
}
“ChessData.h”文件内容:(需要graphics.h)
说明:AI走子算法是关键。
#pragma once
#include <stdlib.h>
#include <windows.h>
#include "tools.h" //外部写的头文件
#include <mmsystem.h> //音乐播放
#pragma comment (lib, "winmm.lib") //表示接入winmm.lib静态链接库,和在工程设置中写上链入winmm.lib的效果相同
const float BLOCK_SIZE = 50.4; //const float BLOCK_SIZE = 50.4; //25.2
const int BOARD_GRADE_SIZE = 13;
const int POS_OFFSET = BLOCK_SIZE * 0.4; //位置模糊,一定要小于0.5
const int margin_x = 32;
const int margin_y = 31;
typedef enum {
CHESS_WHITE = -1,
CHESS_BLACK = 1
} chess_kind_t;
struct ChessData {
//0:空白 1:黑子 -1:白子
int chessMap[BOARD_GRADE_SIZE][BOARD_GRADE_SIZE]; //使用宏,好
//各个落子点评分
int scoreMap[BOARD_GRADE_SIZE][BOARD_GRADE_SIZE];
//轮流下棋,true黑方,flase白方
bool playerFlag;
};
typedef struct point {
int row;
int col;
}point_t;
void board_Init(); //棋盘初始化
void initChessData(struct ChessData* data);
bool clickBoard(MOUSEMSG msg); //判断有效点击
void chessDown(int row, int col, chess_kind_t kind);
void updateGameMap(ChessData* data, int row, int col);
bool checkWin(ChessData* game, int row, int col); //row和col表示当前落子
bool checkOver();
void man_Go();
void caculateScore(ChessData* data);
point_t action_AI(ChessData* data); //机器执行下棋
void AI_Go();
“ChessData.cpp”文件内容:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> //NULL
#include <math.h> //sqrt
#include <string.h>
#include <memory.h> //memset //string.h
#include <conio.h> //getch
#include <time.h>
#include "ChessData.h"
//加载图片到内存,提高效率
IMAGE chessBlackImg; //黑白子需要不断输出
IMAGE chessWhiteImg;
int clickPosRow, clickPosCol; //表示有效点击的实际位置(行列)
struct ChessData game;
void initChessData(struct ChessData* data) {
if (!data) { //data == NULL
return;
}
memset(data->chessMap, 0, sizeof(data->chessMap)); //比双重for更高级
memset(data->scoreMap, 0, sizeof(data->scoreMap));
data->playerFlag = true;
}
void board_Init() {
initgraph(673, 672); //根据图片尺寸确定
loadimage(0, "res/棋盘.jpg"); //加载报错时,项目名右键->属性->高级->字符集选择多字节字符集
//添加对战开始背景音
mciSendString("play res/start.wav", 0, 0, 0); //可以添加背景音乐,mciSendString("play 背景音乐 repeat", 0, 0, 0);
loadimage(&chessBlackImg, "res/black.png", BLOCK_SIZE, BLOCK_SIZE, true); //图片太大时true等比缩放
loadimage(&chessWhiteImg, "res/white.png", BLOCK_SIZE, BLOCK_SIZE, true);
initChessData(&game);
}
bool clickBoard(MOUSEMSG msg) {
//上下边界 x32 y31,定义在ChessData.h中
int col = (msg.x - margin_x) / BLOCK_SIZE;
int row = (msg.y - margin_y) / BLOCK_SIZE;
int leftTopPosX = margin_x + col * BLOCK_SIZE;
int leftTopPosY = margin_y + row * BLOCK_SIZE;
int selectPos = false;
do {
//左上角
int len = sqrt((msg.x - leftTopPosX) * (msg.x - leftTopPosX) +
(msg.y - leftTopPosY) * (msg.y - leftTopPosY));
if (len < POS_OFFSET) {
clickPosRow = row;
clickPosCol = col;
selectPos = true;
break;
}
//右上角 leftTopPosX+BLOCK_SIZE leftTopPosY,其余同理
len = sqrt((msg.x - (leftTopPosX + BLOCK_SIZE)) * (msg.x - (leftTopPosX + BLOCK_SIZE)) +
(msg.y - leftTopPosY) * (msg.y - leftTopPosY));
if (len < POS_OFFSET) {
clickPosRow = row;
clickPosCol = col + 1;
selectPos = true;
break;
}
//右下角
len = sqrt((msg.x - (leftTopPosX + BLOCK_SIZE)) * (msg.x - (leftTopPosX + BLOCK_SIZE)) +
(msg.y - (leftTopPosY + BLOCK_SIZE)) * (msg.y - (leftTopPosY + BLOCK_SIZE)));
if (len < POS_OFFSET) {
clickPosRow = row + 1;
clickPosCol = col + 1;
selectPos = true;
break;
}
//左下角
len = sqrt((msg.x - leftTopPosX) * (msg.x - leftTopPosX) +
(msg.y - (leftTopPosY + BLOCK_SIZE)) * (msg.y - (leftTopPosY + BLOCK_SIZE)));
if (len < POS_OFFSET) {
clickPosRow = row + 1;
clickPosCol = col;
selectPos = true;
break;
}
} while (0); //只循环一次,关键作用是break避免使用return
return selectPos;
}
void chessDown(int row, int col, chess_kind_t kind) {
mciSendString("play res/down7.WAV", 0, 0, 0);
int x = margin_x + clickPosCol * BLOCK_SIZE - 0.5 * BLOCK_SIZE;
int y = margin_y + clickPosRow * BLOCK_SIZE - 0.5 * BLOCK_SIZE;
if (kind == CHESS_BLACK) {
//putimage(msg.x, msg.y, &chessBlackImg); //在鼠标点击处放图片,从左上角开始铺图
drawPNG(x, y, &chessBlackImg); //tools.h中的函数
//还需要模糊算法处理摆正位置,模块化,clickBoard
}
else {
drawPNG(x, y, &chessWhiteImg);
}
}
void updateGameMap(ChessData* data, int row, int col) {
if (!data) {
return;
}
if (data->playerFlag) {
data->chessMap[row][col] = 1;
}
else {
data->chessMap[row][col] = -1;
}
data->playerFlag = !data->playerFlag; //换手
}
bool checkWin(ChessData* game, int row, int col) {
//8个方向,本质是4个方向只要有5连就算赢
int i;
//水平
for (i = 0; i < 5; i++) {
//左、右各匹配
if (col - i >= 0 &&
col - i + 4 < BOARD_GRADE_SIZE &&
game->chessMap[row][col - i] == game->chessMap[row][col - i + 1] &&
game->chessMap[row][col - i] == game->chessMap[row][col - i + 2] &&
game->chessMap[row][col - i] == game->chessMap[row][col - i + 3] &&
game->chessMap[row][col - i] == game->chessMap[row][col - i + 4] )
{
return true;
}
}
//竖直
for (i = 0; i < 5; i++) {
if (row - i >= 0 &&
row - i + 4 < BOARD_GRADE_SIZE &&
game->chessMap[row - i][col] == game->chessMap[row - i + 1][col] &&
game->chessMap[row - i][col] == game->chessMap[row - i + 2][col] &&
game->chessMap[row - i][col] == game->chessMap[row - i + 3][col] &&
game->chessMap[row - i][col] == game->chessMap[row - i + 4][col] )
{
return true;
}
}
// '/'
for (i = 0; i < 5; i++) {
if (row + i < BOARD_GRADE_SIZE &&
row + i - 4 >= 0 &&
col - i >= 0 &&
col - i + 4 < BOARD_GRADE_SIZE &&
//第[row+i]行,第[col-i]列的棋子,与右上方连续4子相同
game->chessMap[row + i][col - i] == game->chessMap[row + i - 1][col - i + 1] &&
game->chessMap[row + i][col - i] == game->chessMap[row + i - 2][col - i + 2] &&
game->chessMap[row + i][col - i] == game->chessMap[row + i - 3][col - i + 3] &&
game->chessMap[row + i][col - i] == game->chessMap[row + i - 4][col - i + 4] )
{
return true;
}
}
// '\'
for (i = 0; i < 5; i++) {
if (row - i >= 0 &&
row - i + 4 < BOARD_GRADE_SIZE &&
col - i >= 0 &&
col - i + 4 < BOARD_GRADE_SIZE &&
//第[row-i]行,第[col-i]列的棋子,与右下方连续4子相同
game->chessMap[row - i][col - i] == game->chessMap[row - i + 1][col - i + 1] &&
game->chessMap[row - i][col - i] == game->chessMap[row - i + 2][col - i + 2] &&
game->chessMap[row - i][col - i] == game->chessMap[row - i + 3][col - i + 3] &&
game->chessMap[row - i][col - i] == game->chessMap[row - i + 4][col - i + 4] )
{
return true;
}
}
return false;
}
bool checkOver() {
if (checkWin(&game, clickPosRow, clickPosCol)) {
//Sleep(1500);
if (game.playerFlag == false) {
mciSendString("play res/不错.mp3", 0, 0, 0);
loadimage(0, "res/胜利.jpg");
}
else {
mciSendString("play res/失败.mp3", 0, 0, 0);
loadimage(0, "res/失败.jpg");
}
getch();
return true;
}
return false;
}
void man_Go() {
chessDown(clickPosRow, clickPosCol, CHESS_BLACK);
updateGameMap(&game, clickPosRow, clickPosCol);
}
//*************************系统评分函数,是关键*******************************
/*
* 黑子 白子(连1/普通~~~5)
* 连2 10 10
* 死3 30 25
* 活3 40 50
* 死4 60 55
* 活4 200 300
* 连5 20000 30000
*/
void caculateScore(ChessData* data) {
if (!data) {
return;
}
int row, col, i, k;
//统计玩家或电脑连子个数
int man_Num = 0; //玩家连子个数
int AI_Num = 0; //AI连子个数
int emptyNum = 0; //空白位个数
//清空评分数组
memset(data->scoreMap, 0, sizeof(data->scoreMap));
for (row = 0; row < BOARD_GRADE_SIZE; row++) {
for (col = 0; col < BOARD_GRADE_SIZE; col++) {
//空白点就算
if (row >= 0 && col >= 0 && data->chessMap[row][col] == 0) {
//遍历周围4个方向,考虑正反
int direction[4][2] = { {1,0}, {1,1}, {0,1}, {-1,1} }; //
for (k = 0; k < 4; k++) {
int x = direction[k][0];
int y = direction[k][1]; //
//重置
man_Num = 0;
AI_Num = 0;
emptyNum = 0;
//黑方正向计数
for (i = 1; i <= 4; i++) {
if (row + i * y >= 0 &&
row + i * y < BOARD_GRADE_SIZE &&
col + i * x >= 0 &&
col + i * x < BOARD_GRADE_SIZE &&
data->chessMap[row + i * y][col + i * x] == 1)
{
man_Num++;
}
else if (row + i * y >= 0 &&
row + i * y < BOARD_GRADE_SIZE &&
col + i * x >= 0 &&
col + i * x < BOARD_GRADE_SIZE &&
data->chessMap[row + i * y][col + i * x] == 0)
{
emptyNum++;
break; //遇到空白位,停止搜索
}
else { //出边界或者遇到白棋,停止搜索
break;
}
}
//黑反
for (i = 1; i <= 4; i++) {
if (row - i * y >= 0 &&
row - i * y < BOARD_GRADE_SIZE &&
col - i * x >= 0 &&
col - i * x < BOARD_GRADE_SIZE &&
data->chessMap[row - i * y][col - i * x] == 1)
{
man_Num++;
}
else if (row - i * y >= 0 &&
row - i * y < BOARD_GRADE_SIZE &&
col - i * x >= 0 &&
col - i * x < BOARD_GRADE_SIZE &&
data->chessMap[row - i * y][col - i * x] == 0)
{
emptyNum++;
break; //遇到空白位,停止搜索
}
else { //出边界或者遇到白棋,停止搜索
break;
}
}
//黑,数->分 //elseif else
if (man_Num == 1) { //杀2
data->scoreMap[row][col] += 10;
}
else if (man_Num == 2) { //杀3
if (emptyNum == 1) { //死3
data->scoreMap[row][col] += 30;
}
else if (emptyNum == 2) { //活3
data->scoreMap[row][col] += 40;
}
}
else if (man_Num == 3) { //杀4
if (emptyNum == 1) {
data->scoreMap[row][col] += 60;
}
else if (emptyNum == 2) {
data->scoreMap[row][col] += 200;
}
}
else if (man_Num == 4) { //杀5
data->scoreMap[row][col] += 20000;
}
//进行一次清空
emptyNum = 0;
//对白棋评分
for (i = 1; i <= 4; i++) { //正
if (row + i * y > 0 &&
row + i * y < BOARD_GRADE_SIZE &&
col + i * x > 0 &&
col + i * x < BOARD_GRADE_SIZE &&
data->chessMap[row + i * y][col + i * x] == -1)
{
AI_Num++;
}
else if (row + i * y > 0 &&
row + i * y < BOARD_GRADE_SIZE &&
col + i * x > 0 &&
col + i * x < BOARD_GRADE_SIZE &&
data->chessMap[row + i * y][col + i * x] == 0)
{
emptyNum++;
break;
}
else {
break;
}
}
for (i = 1; i <= 4; i++) { //反
if (row - i * y > 0 &&
row - i * y < BOARD_GRADE_SIZE &&
col - i * x > 0 &&
col - i * x < BOARD_GRADE_SIZE &&
data->chessMap[row - i * y][col - i * x] == -1)
{
AI_Num++;
}
else if (row - i * y > 0 &&
row - i * y < BOARD_GRADE_SIZE &&
col - i * x > 0 &&
col - i * x < BOARD_GRADE_SIZE &&
data->chessMap[row - i * y][col - i * x] == 0) //空白位
{
emptyNum++;
break;
}
else { //出边界
break;
}
}
if (AI_Num == 0) { //普通下子
data->scoreMap[row][col] += 5;
}
else if (AI_Num == 1) { //活2
data->scoreMap[row][col] += 10;
}
else if (AI_Num == 2) {
if (emptyNum == 1) {
data->scoreMap[row][col] += 25;
}
else if (emptyNum == 2) {
data->scoreMap[row][col] += 50;
}
}
else if (AI_Num == 3) {
if (emptyNum == 1) {
data->scoreMap[row][col] += 55;
}
else if (emptyNum == 2) {
data->scoreMap[row][col] += 300;
}
}
else if (AI_Num >= 4) {
data->scoreMap[row][col] += 30000;
}
}
}
}
}
}
point_t action_AI(ChessData* data) {
caculateScore(data);
int maxScore = 0;
//std::vector<std::pair<int, int>>maxPoints;
point_t maxPoints[BOARD_GRADE_SIZE * BOARD_GRADE_SIZE] = { 0, };
int k = 0;
for (int row = 0; row < BOARD_GRADE_SIZE; row++) { //面向对象语言
for (int col = 0; col < BOARD_GRADE_SIZE; col++) {
//前提是坐标为空
if (data->chessMap[row][col] == 0) {
if (data->scoreMap[row][col] > maxScore) {
//maxPoints.clear();
memset(maxPoints, 0, sizeof(maxPoints));
k = 0;
maxScore = data->scoreMap[row][col];
//maxPoints.push_back(std::make_pair(row, col));
maxPoints[k].row = row;
maxPoints[k].col = col;
k++;
}
else if (data->scoreMap[row][col] == maxScore) {
//maxPoints.push_back(std::make_pair(row, col));
maxPoints[k].row = row;
maxPoints[k].col = col;
k++;
}
}
}
}
srand((unsigned)time(0));
int index = rand() % k;
return maxPoints[index];
}
void AI_Go() {
point_t point = action_AI(&game);
clickPosRow = point.row;
clickPosCol = point.col;
//Sleep(1000);
chessDown(clickPosRow, clickPosCol, CHESS_WHITE);
updateGameMap(&game, clickPosRow, clickPosCol);
}
附结果图:
补充:已发现的漏洞有黑子可覆盖白子,需要在玩家走子函数中添加判断条件。