一、项目简介这是一个可以单人游玩的黑白棋小游戏。采用鼠标左键点击的方式下子。下子之后,处于该点和原本同颜色棋子之间的棋子会转变颜色。本游戏代码设置了可以调整难度的AI(改变内部的difficult参数),可以随自己的喜好调整。编译环境:visual c++ 6.0第三方库:Easyx2017二、运行截图
【瓜分奖池】C语言黑白棋游戏教程及完整源码 第5张插图

【瓜分奖池】C语言黑白棋游戏教程及完整源码 第6张插图
三、源码解析首先看游戏的主体部分,也就是其运行逻辑。void play(void) // 游戏过程{ MOUSEMSG m; int x, y; // 初始化棋子 for(x = 0; x < 8; x++) for(y = 0; y < 8; y++) map[x][y] = 0; map[3][4] = map[4][3] = 'B'; map[3][3] = map[4][4] = 'W'; // 开始游戏 print(); mciSendString("play 音乐\背景音乐.wma from 0 repeat", NULL, 0, NULL); do { if (Canput('B')) // 如果玩家有下子位置 { while(true) { while(true) { m = GetMouseMsg(); // 获取鼠标消息 if(m.uMsg == WM_LBUTTONDOWN && m.x - 26 < 37 8 && m.y - 26 < 37 8) // 如果左键点击 break; } x = (m.y - 26) / 37; y = (m.x - 26) / 37; if(judge(x, y, 'B')) // 如果当前位置有效 { draw(x, y, 'B'); // 下子 mciSendString("play 音乐\下子.wma from 0", NULL, 0, NULL); print(); putimage(37 y, 37 x, &img[3]); // 标识下子点 break; } else continue; } if (quit('W')) // 计算机是否失败 break; } if (Canput('W')) // 如果计算机有下子位置 { clock_t start; start = clock(); D('W', 1); // 搜索解法 while (clock() - start < CLOCKS_PER_SEC); draw(X, Y, 'W'); print(); mciSendString("play 音乐\下子.wma from 0", NULL, 0, NULL); putimage(37 Y, 37 X, &img[4]); // 标识下子点 if (quit('B')) // 玩家是否失败 break; } }while (Canput('B') || Canput ('W'));我们定义了运算用的变量x,y,以及鼠标变量m。MOUSEMSG是Easyx中的结构体,用于保存鼠标消息。然后初始化棋盘,即在中间的四个位置放上黑白各两颗棋子。map[x][y]是一个二维字符串组,用”B”和”W”分别表示黑棋和白棋。之后进入Do-while循环,循环条件为两方至少有一方可以下子。首先玩家(黑)先行动。我们想要达成的目的是,如果我们点击棋盘的一个点,这里允许下子则下子,不能下子则继续检测。这个结构采取一个双层循环来完成,内层不断调用GetMouseMsg获取鼠标信息(GetMouseMsg是Easyx中的函数,其返回值为之前提到过的MOUSEMSG结构,.x和.y分别表示鼠标点击的横纵坐标位置),如果点击,则确定该点的位置,内层循环结束。判定此次落子是否有效,有效则下子并终止外层循环,无效则返回外层循环的头部。然后白方下子。这里利用一个动态规划函数算出一个较好的落子点(与难度相关),并在计算开始之前计时。计算结束判定是否经过一秒(CLOCKS_PER_SEC表示一秒钟内CPU运行的时钟周期数),如果不到一秒则延迟到一秒后落子,防止影响人类棋手心态。然后同样执行落子程序以及音乐播放程序。在整个Play函数外部,前方应该还有一个初始化函数load(),之后还有胜利处理函数。 之后,我们看其中的每一个函数应当如何实现。这是全局当中声明的函数。void load(void); // 加载素材void print(void); // 画棋盘void draw(int, int, char); // 下当前子int judge(int, int, char); // 判断当前是否可以落下bool Canput(char); // 判断是否有棋可吃bool quit(char); // 判断是否有棋存活bool ask(void); // 弹出对话框int D(char, int); // 动态规划void play(void); // 游戏过程下面的是全局变量。const int difficult = 6; // 难度const int move[8][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}}; // 八个方向扩展char map[8][8]; // 棋盘IMAGE img[5]; // 保存图片int black, white; // 双方的棋子数int X, Y; // 白棋的下子点 加载素材并且初始化变量:void load(void) // 加载素材{ // 加载图片 loadimage(&img[0], "图片\空位.bmp"); loadimage(&img[1], "图片\黑子.bmp"); loadimage(&img[2], "图片\白子.bmp"); loadimage(&img[3], "图片\黑子1.bmp"); loadimage(&img[4], "图片\白子1.bmp"); // 加载音乐 mciSendString("open 音乐\背景音乐.wma", NULL, 0, NULL); mciSendString("open 音乐\和局.wma", NULL, 0, NULL); mciSendString("open 音乐\胜利.wma", NULL, 0, NULL); mciSendString("open 音乐\失败.wma", NULL, 0, NULL); mciSendString("open 音乐\下子.wma", NULL, 0, NULL); // 初始化棋盘 initgraph(340, 340); IMAGE qipan; loadimage(&qipan, "图片\棋盘.bmp"); putimage(0, 0, &qipan); setorigin(26, 26); SetWindowText(GetHWnd(), "黑白棋AI版");}loadimage是Easyx库中的函数,用于加载图像。本案例中第一个参数是保存图像的 IMAGE 对象指针,第二个是图像地址。这个函数还可以拉伸图片,或者自动适应IMAGE的大小,具体用法参见Easyx的官方文档。mciSendString是当中的函数,用来播放多媒体文件的API指令。initgraph这个函数用于初始化绘图窗口。putimage用于在当前设备上绘制指定图像。本案例当中代表在(0,0)处绘制棋盘图形。setorigin也在Easyx当中,用于设置坐标原点。SetWindowText是Windows API宏,声明在WinUser.h当中,用于设定窗口文本 绘制棋盘:void print(void) // 画棋盘{ int x, y; black = white = 0; for(x = 0; x < 8; x++) for(y = 0; y < 8; y++) switch(map[x][y]) { case 0: putimage(37 y, 37 x, &img[0]); break; case 'B': putimage(37 y, 37 x, &img[1]); black++; break; case 'W': putimage(37 y, 37 x, &img[2]); white++; break; }}利用双层循环遍历棋盘,根据map[x][y]中的字符,在对应位置绘制棋子就可以了。 落子:void draw(int x, int y, char a) // 下当前子{ char b = T(a); // 敌方子 int i, x1, y1, x2, y2; bool sign; for (i = 0; i < 8; i++) { sign = false; x1 = x + move[i][0]; y1 = y + move[i][1]; while (0