1、 计算机科学与技术学院 《C高级语言程序设计》课程设计报告 ( 2016 / 2017 学年 第 1 学期) 学生姓名: 肖磊 学生专业: 物联网工程 学生班级: 物联网工程152002 学生学号: 201520050228 指导教师: 张荣国 2016年 12 月 26 日 计算机科学与技术学院 课程设计任务书 课程设计名称 C高级语言程序设计课程设计 课
2、程设计题目 三子棋搜索算法树的实现 学生姓名 肖磊 专业班级 物联网152002 学号 201520050228 课程设计任务内容 [问题描述] 针对三子棋,应用C语言程序设计的基本理论和方法,从对问题的分析研究开始,到编程调试结束的整个过程进行分析和设计,具体包括以下几点。 [基本要求] (1)了解程序设计的方法和步骤,对三子棋进行分析研究。 (2) 系统的工作可以进行:人类走棋功能、电脑走棋、判断两方输赢、棋盘界面函数(选择先后手、选择人人对战、人机对战)、搜索树的实现等。 (3)画流程图:将主函数和每个功能模块的函数的流程图分别画出来; (4) 编写程序
3、代码,对每个模块实现的功能进行详细的说明, 对程序中使用的变量予以说明,对程序中主要语句的功能予以说明; (5)提交课程设计报告。 [测试要求] (1)设计的程序能够方便地运行,达到设计的目的; (2)用户界面友好,功能明确,操作方便。 指导教师: 张荣国 时 间:2016 年 12 月 1 日 目录 第1章 设计过程总结与分析 1.1关于三子棋问题的描述…………………………..
4、….…1 1.2关于三子棋问题的分析………………………….………1 1.3程序运行环境…………………………………..……..….1 第2章 算法设计与流程图 2.1主控模块的算法设计与流程图...........................2 2.2图形界面模块算法设计与流程图.........................4 2.3人类走棋模块算法设计与流程图.........................5 2.4判断输赢模块算法设计与流程图.........................5 2.5电脑走棋模块算法设计与流程图.................
5、6 第3章 程序设计编码与测试 3.1主控模块程序设计编码与测试...........................12 3.2图形界面模块程序设计编码与测试......................17 3.3人类走棋模块程序设计编码与测试......................19 3.4判断输赢模块程序设计编码与测试......................25 3.5电脑走棋模块程序设计编码与测试......................28 第4章 设计过程总结与分析 4.1三子棋设计过程中的总结与分析...................
6、32 附录:程序流程图及程序代码 5.1程序流程图..............................................33 5.2程序源码..........................................。.....42 第1章 设计过程总结与分析 1.1关于三子棋问题的描述 “三子棋”游戏(又叫“井字棋”),是一款十分经典的益智 小游戏,想必很多玩家都有玩过。“三子棋”的棋盘很简单,是一个 3×3 的格子, 很像中国文字中的“井”字,所以得名“井字棋”。“三子棋”游戏的规则与“五子棋”十 分类似,“五子棋”的规则是一方首先五
7、子连成一线就胜利;“三子棋”是一方首先 三子连成一线就胜利。 游戏时一方是电脑,另一方是玩家。所以,这类游戏在 开始时有两种方式:一种是玩家先走;另一种是电脑先走。 1.2关于三子棋问题的分析 这是一道人工智能的题目,关键是找出电脑的思维。而电脑的思维必是一个算法。本题用的蒙特卡洛算法。进行一定次数的随机模拟的下棋,之后选择出最有利的一点为落子点。正是因为电脑有思维,电脑能赢就赢,同时也要干扰玩家赢(力求和局) 1.3程序运行环境 程序是在vs2013的环境下运行的,并且需要配置ege文件。 配置ege文件: 1. 把ege的include目录下的头文件拷贝到C:\Progra
8、m Files (x86)\Microsoft Visual Studio 12.0\VC\include下/ 2、把ege的lib\vc2013目录下的链接文件拷贝到C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\lib下 第2章 算法设计与流程图 2.1主控模块算法设计与流程图 设计思路:函数首先调用棋盘界面函数face()产生一个棋盘,之后函数继续读取鼠标点击位置的坐标,读取后首先判断坐标在哪一个if—else if语句内。如果函数进入人人对战,则函数在界面上显示“人人对战开始”;如果函数进
9、入到人机对战中,则在界面出显示出“人机对战开始”和两个选项“人先手”和“机器先手”,再一次进行鼠标点击的选择。 在选择完毕之后函数进入到对战当中,如果选择的是“人先手人机对战”,则会进入到一个while(1)的无限循环当中 首先调用人类走棋play()函数,之后调用判断胜负的winner()函数,。如果判断胜负winner()函数返回先手胜利(1),先手失败(0),平局(3),则跳出while循环。如果判断胜负winner()函数没有返回上述的数值,函数就会在调用电脑下棋函数。之后在一次执行while循环,直到判断出胜负或者和棋为止。
10、 图2.1主控模块流程图 2.2图形界面模块算法设计与流程图 设计思路:函数分别调用八次划线函数line(),分别画出四条竖线和四条横线,交织出九宫格。之后函数调用输出函数,在棋盘界面显示“人机对战”和“人人对战”。 图2.2图形界面模块流程图 2.3人类走棋模块算法设计与流程图 设计思路:函数读取鼠标点击位置的坐标,读取后首先判断,:坐标是否在棋盘内,不是继续读取下一个坐标是进入到下一项的判断。坐标处是否有棋子,如果坐标处有棋子,返回
11、到读取坐标处;如果没有,在相应的位置显示一个棋子。 图2.3人类走棋模块流程图 2.4判断输赢模块算法设计与流程图 设计思路:函数列出了先手和后手胜利的八种情况。首先,函数检查先手是否符合胜利的条件,之后函数在检查后手是否符合胜利的条件,最后函数判断是否符合平局条件 图2.4判断输赢模块流程图 2.5电脑走棋模块算法设计与流程图 设计思路:函数首先设置蒙特卡洛相应的模拟次数,作为for循环的判断条件。在for循环中函数调用复制当前局面assignment()函数,之后调用创建节点create-node()函数,最后调用保存函
12、数。在跳出for循环之后,函数给每一个结果的进行估值,胜利获得三分,失败获得三分,平局获得两分。在保存函数的结构数组中挑选出一个估值最大的点,传递给play_chess()函数中,在界面上显示一个棋子 图2.5电脑走棋模块流程图 子函数:复制当前局面函数的算法设计与流程图 设计思路:函数根据一个for的嵌套循环把现在的局面复制到另一个测试的二维数组当中 图2.6复制当前局面函数流程图 子函数:创造节点函数的算法设计与流程图 设计思路:函数接受一个节点和二维数组作为参数,之后随机的产生随机数作为
13、函数的横纵坐标赋值(此时函数为while(1)的无限循环,如果产生的坐标成功则跳出循环,不成功则进行再一次赋值)。之后函数使用new创建一个节点,并未为该节点进行赋值(包括父节点,子节点,row、line等)和更新测试局面,只有调用判断输赢的winner()函数,如果此时判断输赢函数返回胜利失败和平局的结果则函数结束退出。如果判断输赢函数没有返回结果则进行函数的递归,知道出现结果为止。 图2.7创造节函数流程图 子函数:保存函数的算法设计与流程图 设计思路:函数接受winner()函数的结果作为参数,然后设置两个for的嵌
14、套循环搜索测试棋局局面,把测试棋局局面中为空的位置坐标分别保存到电脑走棋MC()函数中动态分配的结构数组中。之后使用一个for循环把判断输赢winner()函数的结果保存到匹配的结构数组中。 图2.8保存函数流程图 子函数:落子函数的算法设计与流程图 设计思路:函数根据传入的坐标row和line从第一个if(else if)语句进行匹配,如果匹配成功,就会进入到if语句中,并且更新棋局局面和在对应的位置显示棋子,最后跳出函数。 图2.9落子函数流程图 第3章 程序设计
15、编码与测试 3.1主控模块程序设计编码与测试 功能:调用其他函数完成博弈运算。 测试:人先手的人机对战中,先手第一次落子在row=1.line=1处,电脑落子第一次在row=0.line=0,先手第二次落子在row=0.line=2处,电脑落子第二次在row=2.line=0,先手第一次落子在row=0.line=1处,电脑落子第三次在row=1.line=0。电脑胜利, 运行结果:如图3.1 3.2 3.3 分析:人先手落子在点row=1.line=1,电脑经过设定次数的模拟之后选择此时最有可能胜利的点row=0.line=0,人第二次落子在row=0.line=2,如果
16、人再一次落在在row=2.line=0点处人就会获的胜利,所以电脑第二步落子在row=2.line=0,阻止人胜利,人第三次落子在row=0.line=1,电脑只需落子在row=1.line=0处,就可以取得胜利,所以在经过一定次数的模拟和估值时候电脑落子在点row=0.line=1处取得胜利。 图3.1主控模块第一步测试图 图3.2主控模块第二步测试图 图3.3主控模块第三步测试图 源代码:int main() { initgraph(840, 480); face(); srand((unsigned)time(NULL)); m
17、ouse_msg msg = { 0 }; for (; is_run(); delay_fps(60)) { while (mousemsg()) { msg = getmouse(); } if ((int)msg.is_down())//如果鼠标点下才会进入if语句 { if (msg.x >= 599 && msg.x <= 712 && msg.y >= 100 && msg.y <= 127)//人人对战 { outtextxy(600, 250, "人人对战开始"); play(50,'O');
18、 if (winner(array, size, 'O', '#') == 1 || winner(array, size, 'O', '#') == 0 || winner(array, size, 'O', '#') == 3) { main_judge('O', '#'); getch(); return 0; } play(10, '#'); if (winner(array, size, 'O', '#') == 1 || winner(array, size, 'O', '#') ==
19、0 || winner(array, size, 'O', '#') == 3) { main_judge('O', '#'); getch(); return 0; } } else if (msg.x >= 600 && msg.x <= 713 && msg.y >= 199 && msg.y <= 227)//人机对战 { outtextxy(600, 250, "人机对战开始"); outtextxy(600, 300, "选择人先手"); outtextxy(600, 35
20、0, "选择电脑先手"); for (; is_run(); delay_fps(60)) { while (mousemsg()) { msg = getmouse(); } if ((int)msg.is_down())//如果鼠标点下才会进入if语句 { if (msg.x >= 599 && msg.x <= 739 && msg.y >= 299 && msg.y <= 326)//选择人先手 { outtextxy(600, 400, "人机对战
21、人先手开始"); while (1) { node temp; temp = play(50, 'O'); if (winner(array, size, 'O', '#') == 1 || winner(array, size, 'O', '#') == 0 || winner(array, size, 'O', '#') == 3) { main_judge('O', '#'); getch(); return 0;
22、 } MC(&temp); if (winner(array, size, 'O', '#') == 1 || winner(array, size, 'O', '#') == 0 || winner(array, size, 'O', '#') == 3) { main_judge('O', '#'); getch(); return 0; } } } else if (msg.x >= 599 && msg.x
23、 <= 763 && msg.y >= 350 && msg.y <= 377)//选择电脑先手 { outtextxy(600, 400, "人机对战电脑先手开始"); array[1][1] = '#'; size--; circle(320, 240, 10); while (1) { node temp; temp = play(50, 'O'); if (winner(array, size, 'O', '#') == 1
24、 || winner(array, size, 'O', '#') == 0 || winner(array, size, 'O', '#') == 3) { main_judge('O', '#'); getch(); return 0; } MC(&temp); if (winner(array, size, 'O', '#') == 1 || winner(array, size, 'O', '#') == 0 || winner(array, size,
25、'O', '#') == 3) { main_judge('O', '#'); getch(); return 0; } } } } } } } } getch(); return 0; } 3.2图形界面模块程序设计编码与测试 功能:函数可以显示一个界面,左边是九宫格,右边是两个选项:人机对战、人人对战。 运行结果:如图3.4 结果分析:显示界面函数分别调用两组八次line()函数,分成4—4两组,一组构成横
26、线,一组构成竖线,从而形成九个格子。在棋盘的最右面函数调用两次显示outtextxy()函数,显示两个选项:人人对战、人机对战。 图3.4图形界面模块测试图 程序源代码: int face() { setcolor(GREEN); //设置画图颜色 line(125, 45, 515, 45); //行 line(125, 175, 515, 175); line(125, 305, 515, 305); line(125, 435, 515, 435); line(255, 45, 255, 43
27、5); //竖 line(125, 45, 125, 435); line(385, 45, 385, 435); line(515, 45, 515, 435); setfont(28, 0, "宋体")// outtextxy(600, 100, "人人对战"); outtextxy(600, 200, "人机对战"); return 0; } 3.3人类走棋模块程序设计编码与测试 功能:在对应的位置显示棋子 测试:点击坐标为0 0的位置 运行结果:如图3.5 如图3.6 结果分析:函数根据鼠标点击位置的坐标,判断是否符合落子的条件(在棋盘内且此
28、处不存在棋子为空),如果不符合就会继续读取下一个鼠标点击的位置,直到符合为止。 图3.5人类走棋模块测试图 源代码:node play(int radious,char symbol) { //处理鼠标信息 mouse_msg msg = { 0 }; for (; is_run(); delay_fps(60)) { while (mousemsg()) { msg = getmouse(); } if (msg.x >= 125 && msg.x <= 255 && msg.y >
29、 45 && msg.y <= 175) { if (array[0][0] = ' ')//判断是否可以落子 { array[0][0] = symbol; circle(190, 110, radious); size--; human.item = 'O'; human.line = 0; human.row = 0; human.child = NULL; break;//跳出for循环,执行下一条语句 } else contin
30、ue;//继续执行for循环 } else if (msg.x >= 255 && msg.x <= 385 && msg.y >= 45 && msg.y <= 175)//第一行 { if (array[0][1] = ' ') { circle(320, 110, radious); array[0][1] = symbol; size--; human.item = 'O'; human.row = 0; human.line = 1; human.child
31、 = NULL; break; } else continue; } else if (msg.x >= 385 && msg.x <= 515 && msg.y >= 45 && msg.y <= 175) { if (array[0][2] = ' ') { circle(450, 110, radious); array[0][2] = symbol; size--; human.item = 'O'; human.row = 0;
32、 human.line = 2; human.child = NULL; break; } else continue; } else if (msg.x >= 125 && msg.x <= 255 && msg.y >= 175 && msg.y <= 305) { if (array[1][0] = ' ') { circle(190, 240, radious); array[1][0] = symbol; size--; human.i
33、tem = 'O'; human.row = 1; human.line = 0; human.child = NULL; break; } else continue; } else if (msg.x >= 255 && msg.x <= 385 && msg.y >= 175 && msg.y <= 305)//第二行 { if (array[1][1] = ' ') { circle(320, 240, radious); array[1]
34、[1] = symbol; size--; human.item = 'O'; human.row = 1; human.line = 1; human.child = NULL; break; } else continue; } else if (msg.x >= 385 && msg.x <= 515 && msg.y >= 175 && msg.y <= 305) { if (array[1][2] = ' ') { circl
35、e(450, 240, radious); array[1][2] = symbol; size--; human.item = 'O'; human.row = 1; human.line = 2; human.child = NULL; break; } else continue; } else if (msg.x >= 125 && msg.x <= 255 && msg.y >= 305 && msg.y <= 435) { if
36、array[2][0] = ' ') { circle(190, 370, radious); array[2][0] = symbol; size--; human.item = 'O'; human.row = 2; human.line = 0; human.child = NULL; break; } else continue; } else if (msg.x >= 255 && msg.x <= 385 && msg.y >=
37、 305 && msg.y <= 435)//第三行 { if (array[2][0] = ' ') { circle(320, 370, radious); array[2][1] = symbol; size--; human.item = 'O'; human.row = 2; human.line = 1; human.child = NULL; break; } else continue; } else if
38、msg.x >= 385 && msg.x <= 515 && msg.y >= 305 && msg.y <= 435) { if (array[2][2] = ' ') { circle(450, 370, radious); array[2][2] = symbol; size--; human.item = 'O'; human.row = 2; human.line = 2; human.child = NULL; break; } els
39、e continue; } else if (1) { continue;//当下棋的位置在不允许的范围内 } } } return human; } 3.4判断输赢模块程序设计编码与测试 功能:函数在检测棋子的排列,根据棋子的排列返回三种结果(先手胜利、先手失败、平局) 测试:1先手胜利 2和棋 运行结果:如图3.6 如图3.7 分析:图3.6对角线的三个先手棋子连成一条线符合先手胜利的条件,函数返回先手胜利。图3.7函数检查此时的棋子排列符合和棋的条件,函数返回和棋。 图3.6判断输赢模块先手胜利测试图
40、 图3.7判断输赢模块和棋测试图 源代码: int winner(char array[3][3],int count,char people_symbol,char computer_symbol) { if ((array[0][0] == array[0][1] && array[0][1] == array[0][2] && array[0][2] == people_symbol) || (array[1][0] == array[1][1] && array[1][1] == array[1][2] && array[1][2] == people_s
41、ymbol) || (array[2][0] == array[2][1] && array[2][1] == array[2][2] && array[2][2] == people_symbol) || (array[0][0] == array[1][0] && array[1][0] == array[2][0] && array[2][0] == people_symbol) || (array[0][1] == array[1][1] && array[1][1] == array[2][1] && array[2][1] == people_symbol) ||
42、 (array[0][2] == array[1][2] && array[1][2] == array[2][2] && array[2][2] == people_symbol) || (array[0][0] == array[1][1] && array[1][1] == array[2][2] && array[2][2] == people_symbol) || (array[0][2] == array[1][1] && array[1][1] == array[2][0] && array[2][0] == people_symbol)) return
43、1; //玩家获胜 else if ((array[0][0] == array[0][1] && array[0][1] == array[0][2] && array[0][2] == computer_symbol) || (array[1][0] == array[1][1] && array[1][1] == array[1][2] && array[1][2] == computer_symbol) || (array[2][0] == array[2][1] && array[2][1] == array[2][2] && array[2][2] == com
44、puter_symbol) || (array[0][0] == array[1][0] && array[1][0] == array[2][0] && array[2][0] == computer_symbol) || (array[0][1] == array[1][1] && array[1][1] == array[2][1] && array[2][1] == computer_symbol) || (array[0][2] == array[1][2] && array[1][2] == array[2][2] && array[2][2] == comput
45、er_symbol) || (array[0][0] == array[1][1] && array[1][1] == array[2][2] && array[2][2] == computer_symbol) || (array[0][2] == array[1][1] && array[1][1] == array[2][0] && array[2][0] == computer_symbol)) return 0; //电脑获胜 else if (0 == count)//和棋 return 3; return 2; } 3.5电脑走棋模块程序设
46、计编码与测试 功能:在剩余的空格中选择一个对电脑最有利的位置落子。 测试:1当人落子在row=0,line=0时,2当人有两个棋子连在一起时 运行结果:如图3.8 3.9 分析:1在人先手的人机对战中人落子在row=0,line=0时,电脑通过一定次数模拟下棋之后,再经过估值选择出最有利的点,即:row=1,line=1。 2在人先手的人机对战中人已经有两个棋子连在一条线,如果三个棋子连在一起,人就会胜利,所以电脑通过一定次数模拟下棋之后,选择出目前最有利的点,即:row=2,line=0。 图3.9电脑走棋模块测试图 图3.10电脑走棋模块测试图 源代码:
47、 int MC(node * root) { using namespace std; int index; store * p_store_result = new store[size];//声明一个 for (index = 0; index < size; index++) { (p_store_result + index)->line = -1; (p_store_result + index)->row = -1; (p_store_result + index)->count_lose = 0;//初始化为0 (p_store_r
48、esult + index)->count_win = 0; (p_store_result + index)->count_peace = 0; } for (index = 0; index < 22200; index++) { int result;//结果 node * root_child;//初代子节点 int count = size; assignment(temp_array, array);//复制当前的局面 result =create_node(count, root, temp_array);//创建节点,root
49、 root_child = root->child; //保存数据 store_array(root_child, size, result, p_store_result);//保存数 } //选择出胜率最大的位置落子 int max_win = -1000000; index = 0; int special_point = 0; for (index = 0; index < size; index++) { int temp = 0; temp = 3 * (p_store_result + index)->count_wi
50、n - 3 * (p_store_result + index)->count_lose + 2*(p_store_result + index)->count_peace;//函数的估值 if (temp > max_win) { max_win = temp; special_point = index; } } int newrow = (p_store_result + special_point)->row; int newline = (p_st






