C语言结构体指针与结构体变量作形参的区别

区别

结构体变量

  • 结构体变量作为形参,传递的是结构体变量本身,是一种值传递
  • 形参结构体变量成员值的改变不影响对应的实参构体变量成员值的改变

结构体指针

  • 结构体指针作为函数参数,传递的是指向结构体变量的本身
  • 结构体指针指向的变量成员值的改变影响对应的实参构体变量成员值的改变

代码

直接说有些抽象难懂,敲代码演示一遍就很清楚了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct stru{
int num;
};

//形参为结构体变量
void addNum(struct stru p, int num2)
{
p.num += num2;
}

//形参为结构体指针
void addNum2(struct stru *p, int num2)
{
if(!p) return; //确保指针不为空指针
p->num += num2;
}

int main(){

struct stru t;
t.num = 50;

addNum(t,5000);
printf("形参为结构体变量得到的结果为: %d\n", t.num);

addNum2(&t,5000);
printf("形参为结构体指针得到的结果为: %d\n", t.num);

return 0;

}

输出结果

1
2
形参为结构体变量得到的结果为: 50
形参为结构体指针得到的结果为: 5050

CuteBot智能小车

原因

近期,别人送了我一个CuteBot智能小车,拆开一看做工挺精致的,但是这东西是积木图形编程,显然不适合我这个年龄,所以打算给家里的小孩玩。

那么,你可能会问了,为什么要写这篇文章呢?答案当然是用来水的啊

其实在装的时候遇到了点坑,所以记录下来(说了半天还是水文章(滑稽)

部件

首先看下一个具有完整功能的 CuteBot小车(不算拓展)分为几个部分吧

  • MicroBit主板

这可以说是最核心的部件了,给小车烧录程序也是通过它。Microbit是由英国BBC公司推出的面向青少年编程教育的微型计算机,集成了加速度传感器,磁力传感器,两个可编程按钮,25个单色led,蓝牙等常用传感器设备,有一个micro usb接口用于连接数据线烧写程序和供电,可外接电池盒,底部有多个环孔连接器用于控制外接设备。这个板子不仅仅可以用于小车,官方还有许多套件,如智能家居等。当然也可以仅用这个板子跑程序,亮Led,放音乐等。总之它的功能非常强大。

MicroBit正面 MicroBit反面
  • 电池盒
电池盒
  • SR04超声波模块
超声波模块
  • 小车主体 (电池盒是粘上去的,实在是弄不下来了)
CuteBot Car

组装

  • 首先将电池盒的线接到小车主板上并将电池盒粘到小车上

  • 然后插入超声波模块(注意:超声波模块插槽为8Pin,要插入到插槽的前4个孔上,千万不要插入后四个插孔,否则小车无法跑起来,超声波模块会发烫

    如图:

SR04
  • 最后将MicroBit插入的小车上的蓝色插槽里(MicroBit背面朝电池盒)
  • 组装完成
CuteBotCar

编程

环境

新建项目
  • 输入项目名
  • “高级” -> “扩展”
扩展
  • 搜索CuteBot ,并点击
SearchCute
  • 返回页面你就会发现有了“酷比特小车”一行了
CuteBit
  • 点击“…” -> Connect 根据提示连接小车即可
Connect

Hello World

  • 现在开始编写第一个程序吧,你可以选择图形化的“方块”编程,也可以用JavaScriptPython编程(这里我用JS)
JS
  • 输入代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
这部分是开机执行
*/

//在MicroBit显示屏(Led)上显示字符串 Hello,World!
basic.showString("Hello,World!")

//显示完后,以50%的速度向前方行驶5秒
cuteBot.moveTime(cuteBot.Direction.forward,50,5)

basic.forever(function () {
/*
这里是无限循环的代码块
*/

})
  • 点击“下载” ,代码就开始执行了
  • (注意:如果该网站没有连接到MicroBit,那么你需要下载文件并右键手动发送到MicroBit里)

结束

该小车的优点是不需要焊接电路,并且无需搭配环境,仅需要组装好小车并通过在线编程即可运行

由于篇幅有限,关于API使用可以参考官方文档

使用Visual Studio 2019开发Qt程序

安装Qt

  • 如标题,你首先需要到 http://download.qt.io/ 去下载并安装Qt,并在引导下安装MSVC组件(这里不做过多解释)
QtDownload

Visual Studio 2019 配置

  • 打开VS2019
  • 点击“继续但无需代码”
VS2019
  • “扩展(X)” -> “管理扩展”
VS2019
  • 搜索“Qt”
SearchQt
  • 找到“Qt Visual Studio Tools”,点击“下载”按钮(图中是已经安装好的所以不显示下载)

  • 安装过程中会弹出 VSIX Installer窗口,依据引导安装即可

检查

  • 当我们再次打开 Visual Studio 2019 -> “创建新项目” -> 搜索“Qt”,发现已经能够创建Qt项目了
VS_Qt
  • 但是这还没有完,因为我们还没有添加 Qt Version

添加 Qt Version

  • 点击“上一步” -> “继续但无需代码” -> “扩展(X)” -> “Qt VS Tools” -> “Qt Versions”
  • 点击“add new Qt version”,然后点击Path列下的类似于文件夹的图标
Options
  • 找到你的Qt安装目录,在里面找MSVCqmake.exe (例如我的的Qt安装在D盘根目录,Qt版本是5.14.2,那么对应的就应该为 D:\Qt\Qt5.14.2\5.14.2\msvc2017_64\bin\qmake.exe)
  • 最后点击“确定”按钮保存即可
  • 注:图片中第一行显示的版本是我之前已经配置好的Qt版本,如果你是第一次配置,是不会自动添加Qt版本的

C++五子棋(七)——main函数以及项目总结

main函数

  • main.cpp 代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    int main(void){
    init();

    while(1){
    //一直检测鼠标点击
    MOUSEMSG msg == GetMouseMsg();
    if(msg.uMsg == WM_LBUTTONDOWN){
    manGo();
    if(checkOver()){
    init();
    continue;
    }

    AI_GO();
    if(checkOver()){
    init();
    continue;
    }

    }

    }

    closegraph();
    return 0;

    }

项目总结

  • 学习了c语言模块化开发
  • 设计了判断鼠标点击的算法
  • 掌握了AI走棋的写法
  • ……

不足之处

  • 代码缺乏优化,vector没有充分使用
  • 玩家不能选择棋子颜色
  • 无法进行玩家对战
  • AI算法效率不够高

进阶

  • 继续学习数据结构与算法对AI进行优化
  • 尝试建立服务器实现网络对战等

C++五子棋(六)——游戏结束

规则原理

如图

1 2 3 4 5

判断游戏结束

  • chessData.h
1
2
//row,col	表示当前落子
bool checkWin(ChessData* game, int row, int col);
  • 横、竖、斜(斜有两种)共四种情况,每种情况根据当前落子往后遍历5个子,有一种符合就胜利
  • chessData.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
bool checkWin(ChessData* game, int row, int col){
//横
for(int i = 0; i < 5; i++){
if(col - i >= 0 &&
col - i + 4 < BOARD_GRAD_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(int i = 0; i < 5; i++){
if(row - i >= 0 &&
row - i + 4 < BOARD_GRAD_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(int i = 0; i < 5; i++){
if(row + i < BOARD_GRAD_SIZE &&
row + i - 4 >= 0 &&
col - i >= 0 &&
col - i + 4 < BOARD_GRAD_SIZE &&
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(int i = 0; i < 5; i++){
if(row - i >= 0 &&
row - i - 4 < BOARD_GRAD_SIZE &&
col - i >= 0 &&
col - i + 4 < BOARD_GRAD_SIZE &&
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;

}

调用接口

  • main.cpp
1
#include <stdio.h>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
bool checkOver(){
if(checkWin(&game, clickPosRow, clickPosCol)){
Sleep(1500);
if(game.playFlag = false){
//黑棋胜利,此时标记已经转为白棋落子
mciSendString("play res/不错.mp3", 0, 0, 0);
loadimage(0, "res/胜利.jpg");
score += 100; //更新分数
}else{
mciSendString("play res/失败.mp3", 0, 0, 0);
loadimage(0, "res/失败.jpg");
score -= 100; //同理
}


//用于显示分数
char scoreText[64];
sprintf(scoreText, "当前分数:%d", score);
outtextxy(310, 800, scoreText);

//记录分数
FILE* fp = fopen("score.data", "wb");
fwrite(&score, sizeof(score), 1, fp);
fclose(fp);


getch();
return true;

}

return false;

}

显示分数

  • main.cpp
1
2
#define INIT_SCORE 1000
int score; //全局变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void initScore(){
//分数字体设置
settextcolor(WHITE); //color
settextstyle(50, 0, "微软雅黑"); //style

FILE *fp = fopen("score.data", "rb");
if(fp == NULL){
score = INIT_SCORE;
}else{
fread(&score, sizeof(score), 1, fp);
}
if (fp)fclose(fp);

}
  • 然后在main.cpp文件的 init() 函数定义中 继续 添加代码
1
initScore();	//这一行添加到init()函数定义中

结束

到这里五子棋的全部功能已经实现了,但是你会发现程序无法运行。这是理所当然的,因为我们的main函数还没有写,在下一篇文章(也就是本项目的最后一章)我们将完善main函数并做该项目的总结。

C++五子棋(五)——实现AI落子

AI思考落子点

在之前我们已经实现计算权值了,现在要想让AI落子,应根据之前的计算结果使棋子落在分值最大点上。当然可能会出现多个分值相同的最大点,这时在其中随机取一个点落下即可。

  • chessData.h
1
2
3
4
5
6
7
typedef struct point{
int row;
int col;
} point_t;

//机器下棋
point_t actionAI(ChessData* data);
  • chessData.cpp
1
2
3
#include <time.h>
#include <stdlib.h>
#include <vector>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
point_t actionAI(ChessData* data){
//计算评分
calcScore(data);

//找出最大分数位置
int maxScore = 0;
std::vector<std::pair<int, int>> maxPoints;

int k = 0;

for(int row = 0; row < BOARD_GRAD_SIZE; row++){
for(int col = 0; col < BOARD_GRAD_SIZE; col++){

//若该坐标为空
if(data->chessMap[row][col] == 0){
//找出最大数和坐标
if(data->scoreMap[row][col] > maxScore){
maxScore.clear();
k = 0;
maxScore.push_back(std::make_pair(row, col));
k++;

}else if(data->scoreMap[row][col] == maxScore){
maxPoints.push_back(std::make_pair(row, col));
k++;
}
}

}
}

//如果有多个点随机落子
srand((unsigned)time(0));
int index = rend() % k;
return maxPoints[index];

}

实现AI落子

  • main.cpp
1
2
3
4
5
6
7
8
9
10
void AI_GO(){
point_t point = actionAI(&game);
clickPosRow = point.row;
clickPosCol = point.col;

Sleep(1000);
chessDown(clickPosRow, clickPosCol, CHESS_WHITE);
updateGameMap(&game, clickPosRow, clickPosCol);

}

C++五子棋(四)——走棋原理及权值计算

原理

计算

  • 计算每个落子点的“权值”,找到权值最大的落子点
  • 对于每个空白点,分别计算周围的八个方向
  • 不妨以该空白点作为参照原点,以水平向右作为X轴正方向,以竖直向下Y轴正方向建立平面直角坐标系
  • 因为在计算某个方向时,正向和反向需同时考虑,实际上只需要四个方向,即向量(1,0)的方向向量(1,1)方向向量(0,1)方向向量(-1,1)方向,图示如下(灵魂画图,请勿吐槽 滑稽)
灵魂画图

走棋原理

连2 活3 死3 活4 死4 连5

产生效果

  • 黑棋走这个点
产生效果 评分
连2 10
死3 30
活3 40
死4 60
活4 200
连5 20000

  • 如果白棋(AI)走这个点
产生效果 评分
连1 5
连2 10
死3 25
活3 50
死4 55
活4 300
连5 30000

权值计算

  • chessData.h
1
void calcScore(ChessData* data);
  • chessData.cpp
1
#include <string>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
void calcScore(ChessData* data){
if(!data) return;

//统计玩家或AI连子
int personNum = 0; //玩家
int botNum = 0; //AI
int emptyNum = 0; //各方向空白位数


//清空评分数组
memset(data->scoreMap, 0, sizeof(data->scoreMap));
for (int row = 0; row < BOARD_GRAD_SIZE; row++){
for(int col = 0; col < BOARD_GRAD_SIZE; col++){
//空白点计算
if(row >= 0 && col >= 0 && data->chessMap[row][col] == 0){
//遍历四个方向,然后分别计算正反四个方向
int directs[4][2] = {{1,0}, {1,1}, {0,1}, {-1,1}};

for(int k = 0; k < 4; k++){
int x = directs[k][0];
int y = directs[k][1];

//重置
personNum = 0;
botNum = 0;
emptyNum = 0;

//对黑棋评分(正向)
for(int i = 1; i <= 4; i++){
if(row + i * y >= 0 && row + i * y < BOARD_GRAD_SIZE &&
col + i * x >= 0 && col + i * x < BOARD_GRAD_SIZE &&
data->chessMap[row + i * y][col + i * x] == 1){
//玩家的子
personNum++;

}else if(row + i * y >= 0 && row + i * y < BOARD_GRAD_SIZE &&
col + i * x >= 0 && col + i * x < BOARD_GRAD_SIZE && data->chessMap[row + i * y][col + i * x] == 0){
//空白位
emptyNum++;
break; //遇到空白位置停止该方向搜索

}else{
break; //出边界或遇到白棋停止搜索
}


}

//对黑棋评分(反向)
for(int i = 1; i<= 4; i++){
if(row - i * y >= 0 && row - i * y < BOARD_GRAD_SIZE &&
col - i * x >= 0 && col - i * x <BOARD_GRAD_SIZE &&
data->chessMap[row - i * y][col - i * x] == 1){
personNum++;
}else if(row - i * y >= 0 && row - i * y <BOARD_GRAD_SIZE &&
col - i * x >= 0 && col - i * x < BOARD_GRAD_SIZE &&
data->chessMap[row -i * y][col - i * x] == 0){
emptyNum++;
break;
}else{
break;
}
}

if(personNum == 1){
data->scoreMap[row][col] += 10;
}else if(personNum == 2){
if(emptyNum == 1){
//死3
data->scoreMap[row][col] += 30;
}else if(emptyNum == 2){
//活3
data->scoreMap[row][col] += 40;
}
}else if(personNum == 3){
if(empty == 1){
//死4
data->scoreMap[row][col] += 60;
}else if (emptyNum == 2){
//活4
data->scoreMap[row][col] += 200;
}
}else if(personNum == 4){
data->scoreMap[row][col] += 20000;
}
emptyNum = 0; //清空

//对白棋评分(正向)
for(int i = 1; i <= 4; i++){
if(row + i * y > 0 && row + i * y < BOARD_GRAD_SIZE &&
col + i * x > 0 && col + i * x < BOARD_GARD_SIZE &&
data->chessMap[row + i * y][col + i * x == -1]){
botNum++;
}else if(row + i * y >0 && row + i * y < BOARD_GRAD_SIZE &&
col + i * x > 0 && col + i * x < BOARD_GRAD_SIZE &&
data->chessMap[row + i * y][col + i *x] == 0){
emptyNum++;
break;
}else{
break;
}
}
//白棋评分(反向)
for(int i = 1; i <= 4; i++){
if(row - i * y > 0 && row - i * y <BOARD_GRAD_SIZE &&
col - i * x > 0 && col - i * x < BOARD_GRAD_SIZE &&
data->chessMap[row - i * y][col -i * x] == -1){
botNum++;
}else if (row - i * y >0 && row - i * y < BOARD_GRAD_SIZE &&
col - i * x > 0 && col - i * x < BOARD_GRAD_SIZE &&
data->chessMap[row - i * y][col - i * x] == 0){
emptyNum++;
break;
}else{
break; //出边界
}
}

if(botNum == 0){
//连1
data->scoreMap[row][col] += 5;
}else if(botNum == 1){
//活2
data->scoreMap[row][col] += 10;
}else if(botNum == 2){
if(emptyNum == 1){
//死3
data->scoreMap[row][col] += 25;
}else if(emptyNum == 2){
//活3
data->scoreMap[row][col] += 50;
}
}else if(botNum == 3){
if(emptyNum == 1){
//死4
data->scoreMap[row][col] += 55;
}else if(botNum == 2){
//活4
data->scoreMap[row][col] += 300;
}
}else if(botNum >= 4){
//活5
data->scoreMap[row][col] += 30000;
}

}

}
}
}
}

解决hexo报错spwan failed

报错1

1
2
3
4
5
6
7
8
9
10

FATAL {
err: Error: Spawn failed
at ChildProcess.<anonymous> (/usr/local/src/hexo/cairbin/node_modules/hexo-util/lib/spawn.js:51:21)
at ChildProcess.emit (events.js:376:20)
at Process.ChildProcess._handle.onexit (internal/child_process.js:277:12) {
code: 128
}
} Something's wrong. Maybe you can find the solution here: %s https://hexo.io/docs/troubleshooting.html

解决方案

  • 进行以下处理
1
2
3
4
5
6
7
8
##进入博客根目录(以我的为例)
cd /usr/local/src/hexo/cairbin/

##删除git提交文件夹
rm -rf .deploy_git/

git config --global core.autocrlf false

  • 最后重新生成提交
1
hexo clean && hexo g && hexo d

在提交的过程可能又出现以下报错

报错2

1
! [remote rejected] master -> master (push declined due to email privacy restrictions)

解决方案

  • 这是因为你的github设置出了问题
  • 浏览器进入github.com
  • 登陆github -> “+” ->settings
  • 后续操作如下图

  • 将下方这两个设置取消勾选

  • 重新提交
1
hexo clean && hexo g && hexo d

大功告成

如果不报错,重新访问页面,就发现已经提交成功了

C++五子棋(三)——判断鼠标有效点击

分析

在鼠标左键点击时,我们不能让新棋子在已有棋子的位置落下,同时我们还要让棋子在规定位置落下——棋盘线的交点处。

功能实现

创建数据类型

  • 创建头文件chessData.h和源文件chessData.cpp
  • chessData.cppmain.cpp分别引用头文件chessData.h
1
#include "chessData.h"
  • 将之前在main.cpp中写的棋盘数据剪贴chessData.h
1
2
const float BLOCKSIZE = 67.4;
const int BLOCK_GRAD_SIZE = 13;
  • chessData.h中定义常量 POS_OFFSET,即鼠标有效点击距离上限
1
const int POS_OFFSET = BLOCKSIZE * 0.4;
  • chessData.h定义结构体 ChessData
1
2
3
4
5
6
7
8
9
struct ChessData{
//储存当前游戏棋盘的情况,空白为0,黑棋为1,白棋为-1
int chessMap[BLOCK_GRAD_SIZE][BLOCK_GRAD_SIZE];
//储存各点的评分情况,用于之后的AI走棋
int scoreMap[BLOCK_GRAD_SIZE][BLOCK_GRAD_SIZE];

bool playFlag; //表示下棋放,true黑棋,false白棋(AI)

};
  • main.cpp中添加全局变量game
1
ChessData game;

初始化数据类型

  • chessData.h中添加函数声明
1
void initChessData(ChessData*);
  • chessData.cpp
1
2
3
4
5
6
7
void initChessData(ChessData *data){
if(!data) return;
memset(data->chessMap,0,sizeof(data->chessMap));
memset(data->scoreMap,0,sizeof(data->scoreMap));
data->playFlag = true;

}

判断有效点击

算法原理

原理

实现

  • main.cpp中添加全局变量
1
int clickPosRow, clickPosCol;	//用于储存点击位置
  • 定义函数clickBoard()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//有效点击返回true,无效点击返回false
//MOUSEMSG为鼠标信息类型
bool clickBoard(MOUSEMSG msg){

//(鼠标点击坐标 - 边界长度)/ 格宽 = 行(列)数

//计算列数并取整
int col = (msg.x - MARGIN_X) / BLOCKSIZE;
//计算行数取整
int row = (msg.y - MARGIN_Y) / BLOCKSIZE;

//计算棋子正确坐标,即格子左上角棋子应在的棋盘格线交点处坐标
int leftTopPosX = MARGIN_X + BLOCKSIZE * col;
int leftTopPosY = MARGIN_Y + BLOCKSIZE * row;


}
  • 在文件最上方引用头文件math.h用于后期计算
1
#include <math.h>
  • 之后要判断棋子应在四个交点中具体哪一点上,这里我们用一个do-while循环。继续添加**clickBoard()**函数的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
int len;	//用于计算两点见的距离(根据中学所学两点间距离公式)
int selectPos = false; //作为返回值

do{
//左上角
len = sqrt((msg.x - leftTopPosX)*(msg.x - leftTopPosX) + (y - leftTopPosY)*(msg.y - leftTopPosY));
if(len < POS_OFFSET){
clickPosRow = row;
clickPosCol = col;
if ( game.chessMap[clickPosRow][clickPosCol] == 0 ){
selectPos = true;
}
break;
}

//右上角
len = sqrt((msg.x - leftTopPosX - BLOCKSIZE)*(msg.x - leftTopPosX - BLOCKSIZE) + (y - leftTopPosY)*(msg.y - leftTopPosY));
if(len < POS_OFFSET){
clickPosRow = row;
clickPosCol = col + 1;
if ( game.chessMap[clickPosRow][clickPosCol] == 0 ){
selectPos = true;
}
break;
}

//左下角
len = sqrt((msg.x - leftTopPosX)*(msg.x - leftTopPosX) + (y - leftTopPosY - BLOCKSIZE)*(msg.y - leftTopPosY - BLOCKSIZE));
if(len < POS_OFFSET){
clickPosRow = row + 1;
clickPosCol = col;
if ( game.chessMap[clickPosRow][clickPosCol] == 0 ){
selectPos = true;
}
break;
}

//右下角

len = sqrt((msg.x - leftTopPosX - BLOCKSIZE)*(msg.x - leftTopPosX - BLOCKSIZE) + (y - leftTopPosY - BLOCKSIZE)*(msg.y - leftTopPosY - BLOCKSIZE));
if(len < POS_OFFSET){
clickPosRow = row + 1;
clickPosCol = col + 1;
if ( game.chessMap[clickPosRow][clickPosCol] == 0 ){
selectPos = true;
}
break;
}


}while(0);

return selectPos;

更新底层数据

  • chessData.h
1
void updateGameMap(ChessData* data, int row, int col);
  • chessData.cpp
1
2
3
4
5
6
7
8
9
10
11
12
void updateGameMap(ChessData* data, int row, int col){
if(!data) return;

if(data->playFlag){
data->chessMap[row][col] = 1;
}else{
data->chessMap[row][col] = -1;
}

data->playFlag = !data->playFlag; //换下棋方

}
  • main.cpp
1
2
3
4
5
6
//玩家走棋
void manGo(){
chessDown(clickPosRow,clickPosCol,CHESS_BLACK);
updateGameMap(&game, clickPosRow, clickPosCol);

}

C++五子棋(二)——游戏界面与棋子渲染

准备

我们首先要在程序中定义一个名为drawPNG的函数,用于输出png格式图片并使背景透明

  • 引入头文件(需要提前安装EasyX)
1
#include <graphics.h>
  • 定义函数 drawPNG
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void drawPNG(IMAGE* picture, int  picture_x, int picture_y) //x为载入图片的X坐标,y为Y坐标
{

// 变量初始化
DWORD* dst = GetImageBuffer(); // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
DWORD* draw = GetImageBuffer();
DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
int graphWidth = getwidth(); //获取绘图区的宽度,EASYX自带
int graphHeight = getheight(); //获取绘图区的高度,EASYX自带
int dstX = 0; //在显存里像素的角标

// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
for (int iy = 0; iy < picture_height; iy++)
{
for (int ix = 0; ix < picture_width; ix++)
{
int srcX = ix + iy * picture_width; //在显存里像素的角标
int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
int sg = ((src[srcX] & 0xff00) >> 8); //G
int sb = src[srcX] & 0xff; //B
if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
{
dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
int dr = ((dst[dstX] & 0xff0000) >> 16);
int dg = ((dst[dstX] & 0xff00) >> 8);
int db = dst[dstX] & 0xff;
draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16) //公式: Cp=αp*FP+(1-αp)*BP ; αp=sa/255 , FP=sr , BP=dr
| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8) //αp=sa/255 , FP=sg , BP=dg
| (sb * sa / 255 + db * (255 - sa) / 255); //αp=sa/255 , FP=sb , BP=db
}
}
}
}

初始化游戏

创建游戏界面

素材大小

  • 通过查看res素材文件夹下的棋盘2.jpg可知,图片大小为897*895
  • 通过图片大小计算可得一个格子的大小为67.4
  • 先记下这些参数,后面对我们开发特别重要

创建窗口

  • 首先,定义 float类型常量 BLOCKSIZE,即格子大小
1
const float BLOCKSIZE = 67.4;
  • 然后定义 init() 如下
1
2
3
4
void init(){
initgraph(897,895); //创建897*895大小的窗口,与棋盘2.jpg大小对应
loadimage(0,"res/棋盘2.jpg"); //加载图片到窗口
}

加载音乐

  • 引入播放音乐的 头文件 mmsystem.h
1
#include <mmsystem.h>
  • 加载库 winmm.lib
1
#pragma comment(lib,"winmm.lib");
  • 继续在 init() 函数中添加播放 提示语音(res/start.wav) 的语句(注意添加位置)
1
mciSendString("play res/start.wav",0,0,0);	//提示下棋语音

棋子渲染

加载素材

  • 定义 IMAGE 类型的全局变量 chessWhitechessBlack
1
2
IMAGE chessWhite; //黑棋子变量
IMAGE chessBlack; //白棋子变量
  • init() 函数定义中添加加载图片语句如下(将black.png白棋子素材white.png黑棋子素材加载到变量)
1
2
3
//长和宽都是BLOCKSIZE,最后一个true参数表示原比例缩放防止图片被截断
loadimage(&chessBlack, "res/black.png",BLOCKSIZE,BLOCKSIZE,true);
loadimage(&chessWhite, "res/white.png", BLOCKSIZE, BLOCKSIZE, true);

实现渲染

  • 定义棋子种类
1
2
3
4
typedef enum{
CHESS_WHITE = -1,
CHESS_BLACK = 1
} chess_kind_t;
  • 在实现输出棋子之前我们需要来看几个数据

  • MARGIN_X为上边界大小,MARGIN_Y为左边界大小,因此我们定义同名全局常量
1
2
3
4
const int MARGIN_X = 44;
const int MARGIN_Y = 43;

const int BOARD_GRAD_SIZE = 13; //13*13棋盘大小
  • 之后定义函数 chessDown() 用于打印棋子图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void chessDown(int row, int col, chess_kind_t kind){
mciSendString("play res/down7.wav",0,0,0); //播放下棋音乐

//EasyX是以窗口左上角为坐标原点

//定义棋子x横坐标
int x = MARGIN_X + col * BLOCKSIZE - 0.5 * BLOCKSIZE;
//定义棋子y纵坐标
int y = MARGIN_Y + row * BLOCKSIZE - 0.5 * BLOCKSIZE;

//判断棋子种类并打印
if(kind == CHESS_WHITE){
drawPNG(&chessWhite,x,y);
}else{
drawPNG(&chessBlack,x,y);
}


}

结束

到了这里,我们已经实现了游戏界面的初始化和棋子渲染了,接下来我们就要实现获取鼠标信息来判断有效点击