用C语言实现简单的一字棋游戏
问题分析设计目录
- 棋盘显示和标记以及棋盘的设计
- 搜索树叶子节点设计
- 搜索树设计
- 节点静态估值计算
- 完整代码
- 总结
棋盘显示和标记以及棋盘的设计
用int一维数组表示一字棋的棋盘位置,0~8,数组位置i即棋盘位置,数组元素的值表示该位置的下棋情况。0表示未下棋,1表示用户选手下棋,2表示电脑下棋。比如
chessman[6] = 1; 表示第六个位置下了用户选手的棋。
//一字棋棋盘 一共9个格子
typedef struct{int chessman[9];
}ChessBoard;
搜索树叶子节点设计
定义一个BiTNode节点,只表示搜索树的叶子节点,里面存放了一个棋盘,这个棋盘是棋局中某一步棋的情况,里面包括了电脑可能下棋的位置和用户选手可能做的下一步棋的位置。Value值是该节点的静态估值,当该节点形成后,可以计算静态估值。
//搜索树节点的定义
typedef struct BiTNode{ChessBoard chessboard; //chessboard 一个棋盘以及所有棋子落子的情况 int CoordinateAI; //这一步棋子的位置 ChessMan[]0~8 int CoordinatePlayer; //这一步是选手可能落下棋子的位置 int Value; //节点的估价值
}BiTNode; BiTNode Head; //头结点
//BiTNode *TreeBoard;#define MAX 100#define MIN -100#define DOGFALL 50 //平局
搜索树设计
本次设计我没有用到数据结构中严格意义上的树,设计时觉得有点麻烦,没有用二叉树表示,但我又有自己的想法实现逻辑上的搜索树,并且这棵搜索树做两层就够了,这个想法目前只适用于一字棋。搜索树用节点二维数组表示。
Max节点的估价值用一维数组aFatherValue[9]表示。
BiTNode a[9][9]; //极大极小搜索树用数组表示
int aFatherValue[9]; //Max节点的估价函数值,选取该节点所有孩子节点的最小估价函数赋值
搜索树表示如图所示:
节点静态估值计算
在一盘棋中,又八种可能连成一线的的结果。在博弈当中的任何一方都考虑完他们八种情况的结局有几种是可能实现的就行了。横竖各三种,斜着两种一共八种。计算设计如下所示。用二维数组judge[8][[3]]列出八种情况对应的位置值,然后按着顺序遍历一组一组遍历八组就行了。当一组位置中的三个位置只要有一处是存在对手中的棋子,则这一组就不可能用自己的棋子连成三点一线。
//判断某一方可能获胜结果的个数
int checkValue(int x, int y, ChessBoard *chessboard){int value = 0;int judge[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6}};int coordinate ;for(int i=0; i<=7; i++){int j = 0;;for(j=0; j<=2; j++){coordinate = judge[i][j];//判断8个胜利的可能是否有对手的棋子,如果有,则可判定这个结果不可能胜算 if(chessboard->chessman[coordinate] == x ){ break;} }if(j>2){value++;} }return value;
}
因为只需要电脑的考虑,所以用电脑的减去用户选手可能胜利的个数,就能得到静态估值。在计算节点的静态估价值函数中,如果对手的棋连成了三点一线,则直接返回Min,如果是电脑的棋连成线,则返回Max。
//计算节点的静态估价值
int EvaluationFunction(ChessBoard *chessboard){// ChessBoard *p = &chessboard; int EvaluationAI = checkValue(1,2,chessboard); //AI的胜算 int EvaluationPlayer = checkValue(2,1,chessboard); //对手的胜算 int Evaluation = EvaluationAI - EvaluationPlayer;int judge[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6}};int coordinate ;int valueX;int valueY;for(int i=0; i<=7; i++){valueX = 0;valueY = 0;for(int j=0; j<=2;j++){coordinate = judge[i][j];if(chessboard->chessman[coordinate] == 1){valueX++;}if(chessboard->chessman[coordinate] == 2){valueY++;}}if(valueX == 3){return MIN;}if(valueY == 3){return MAX;}}return Evaluation;
}
完整代码
#include<stdio.h>
#include<windows.h>//棋子 x,y是棋子的坐标位置,value是判断该坐标是否落有棋子 0表示没有落棋 1表示X自己下的棋 2表示〇AI下的棋 //一字棋棋盘 一共9个格子
typedef struct{int chessman[9];
}ChessBoard; //搜索树节点的定义
typedef struct BiTNode{ChessBoard chessboard; //chessboard 一个棋盘以及所有棋子落子的情况 int CoordinateAI; //这一步棋子的位置 ChessMan[]0~8 int CoordinatePlayer; //这一步是选手可能落下棋子的位置 int Value; //节点的估价值
}BiTNode; BiTNode Head; //头结点
//BiTNode *TreeBoard;#define MAX 100#define MIN -100#define DOGFALL 50 //平局 void initialBoard(ChessBoard *chessboard); //初始化一个棋盘,对一字棋棋盘的每个格子规定坐标int EvaluationFunction(ChessBoard *chessboard); //计算节点的静态估价值 int checkValue(int x,int y, ChessBoard *chessboard); //判断某一方可能获胜结果的个数 void PlayChess(int x, int play, ChessBoard *chessboard); //下棋 int PlayAI(ChessBoard *chessboard); //AI考虑下棋 void DrawBoard(ChessBoard *chessboard); //绘画棋盘,给用户显示 int PlayAI(ChessBoard *chessboard); //电脑考虑下棋 int judgeBoard(ChessBoard *chessboard); 当某一方下棋后,判断结果 int main(){int first = 0;int play; int playNum = 0; //记录运行步数 如果大于9,说明平局 printf("欢迎来到一字棋游戏;x代表你下的棋,〇代表电脑下的棋 该棋盘9个棋子的位置对应坐标如下:\n");printf("-------------------\n") ;printf("| 0 | 1 | 2 |\n\n");printf("| 3 | 4 | 5 |\n\n");printf("| 6 | 7 | 8 |\n");printf("-------------------\n\n");printf("当你和机器对弈时,请输入你下的棋子位置对应的坐标,比如想下在最中间的位置就输入4\n");printf("现在请选择这局一字棋游戏谁先走第一步,请输入0或1,0代表机器先下 1代表你先下\n");scanf("%d",&first);ChessBoard playChessBoard; //整局游戏的一盘棋,同时也可当作头结点 ChessBoard *playBoard = &playChessBoard; //棋盘指针 initialBoard(playBoard); //传指针 if(first == 1){printf("-------------------\n") ;printf("| 0 | 1 | 2 |\n\n");printf("| 3 | 4 | 5 |\n\n");printf("| 6 | 7 | 8 |\n");printf("-------------------\n\n");printf("请输入你下棋的位置:");scanf("%d",&play);PlayChess(1,play, playBoard); //用户选手下棋 DrawBoard(playBoard);playNum++;}else{int AI = PlayAI(playBoard); //AI考虑下棋 printf("电脑下棋的位置: %d\n",AI); PlayChess(2,AI, playBoard); //电脑选手下棋 playNum++;DrawBoard(playBoard); //显示给用户下棋 printf("请你做下一步棋:");scanf("%d",&play);}int playNext = 1; //定义一个整形数据,决定下一步谁下棋 1表示用户选手下棋 2表示电脑下棋 if(first == 1){playNext = 2;}int finalResult = 0;while(playNum<9){if(playNext == 2){ int AI = PlayAI(playBoard);PlayChess(2,AI, playBoard); //电脑下棋 printf("电脑下棋的位置: %d\n",AI);DrawBoard(playBoard);finalResult = judgeBoard(playBoard);if(playNum != 8 && finalResult!= 2){printf("请你做下一步棋:");scanf("%d",&play);} playNext = 1; }else if(playNext == 1){PlayChess(1,play, playBoard); //用户选手下棋 DrawBoard(playBoard);playNext = 2; //下一步电脑下棋 }finalResult = judgeBoard(playBoard);if(finalResult == 1){printf("你赢了!\n");break;}if(finalResult == 2){printf("电脑赢了!");break;}playNum++;}if(playNum == 9){printf("结果平局\n");}printf("游戏结束!");return 0;
} //初始化一个棋盘,对一字棋棋盘的每个格子规定坐标
void initialBoard(ChessBoard *chessboard){for(int i=0; i<=8; i++){ //将棋盘的所有棋子标为0,表示为未落子 // *chessboard.chessman[i] = 0; chessboard->chessman[i] = 0;}
} //计算节点的静态估价值
int EvaluationFunction(ChessBoard *chessboard){// ChessBoard *p = &chessboard; int EvaluationAI = checkValue(1,2,chessboard); //AI的胜算 int EvaluationPlayer = checkValue(2,1,chessboard); //对手的胜算 int Evaluation = EvaluationAI - EvaluationPlayer;int judge[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6}};int coordinate ;int valueX;int valueY;for(int i=0; i<=7; i++){valueX = 0;valueY = 0;for(int j=0; j<=2;j++){coordinate = judge[i][j];if(chessboard->chessman[coordinate] == 1){valueX++;}if(chessboard->chessman[coordinate] == 2){valueY++;}}if(valueX == 3){return MIN;}if(valueY == 3){return MAX;}}return Evaluation;
}//判断某一方可能获胜结果的个数
int checkValue(int x, int y, ChessBoard *chessboard){int value = 0;int judge[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6}};int coordinate ;for(int i=0; i<=7; i++){int j = 0;;for(j=0; j<=2; j++){coordinate = judge[i][j];//判断8个胜利的可能是否有对手的棋子,如果有,则可判定这个结果不可能胜算 if(chessboard->chessman[coordinate] == x ){ break;} }if(j>2){value++;} }return value;
}//下棋
void PlayChess(int x, int play, ChessBoard *chessboard){chessboard->chessman[play] = x;
}//绘画棋盘,给用户显示
void DrawBoard(ChessBoard *chessboard){int i;int num = 0;printf("-------------------\n");for(i=0; i<=8; i++){if(chessboard->chessman[i] == 1){printf("| X ");}else if(chessboard->chessman[i] == 2){printf("| 〇 ");}else{printf("| %d ",i);}num++;if(num%3 == 0){printf("|\n\n");}}printf("-------------------\n\n");
} //AI考虑下棋
int PlayAI(ChessBoard *chessboard){for(int i=0; i<=8; i++){//给头结点棋盘赋值 当前结点棋盘的棋子保持情况 Head.chessboard.chessman[i] = chessboard->chessman[i]; } int aFatherValue[9]; //Max节点的估价函数值,选取该节点所有孩子节点的最小估价函数赋值 for(int i=0;i<=8;i++){aFatherValue[i] = MIN; //初始化 } BiTNode a[9][9]; //极大极小搜索树用数组表示 for(int i=0; i<=8; i++){ //这个for循环是遍历头结点的所有子节点,即max结点 for(int j=0; j<=8; j++){ //这个for循环是将max结点的所有子节点对局详情棋盘复写 for(int k=0; k<=8; k++){a[i][j].chessboard.chessman[k] = Head.chessboard.chessman[k]; //将当前对局的棋保存在结点棋盘里 }a[i][j].Value =MAX; //该节点估价函数赋值为MIN 初始为最大 a[i][j].CoordinateAI = -1;a[i][j].CoordinatePlayer = -1;} } //搜索树生成,并用极大极小值求出头结点的估价函数 求出电脑选手下棋的位置 int min = MAX;for(int i=0; i<=8; i++){if(Head.chessboard.chessman[i] == 0){min = MAX;for(int j=0; j<=8; j++){a[i][j].CoordinateAI = i;a[i][j].chessboard.chessman[i] = 2; //电脑可能下的棋 if(a[i][j].chessboard.chessman[j] == 0){a[i][j].CoordinatePlayer = j; //选手可能下的位置 a[i][j].chessboard.chessman[j] = 1; //选手可能下的棋 ChessBoard *p = &a[i][j].chessboard; //定义一个指针 用来(求估价函数 )判断结果 a[i][j].Value = EvaluationFunction(p); //求估价函数 求估价函数值还是需要传指针 if(a[i][j].Value <= min){ //求出max结点的估价函数值 min = a[i][j].Value; }} } aFatherValue[i] = min; //给有叶子节点的max节点赋值 求其所有叶子节点中最小的赋值 // printf("电脑下%d位置的max节点值为%d\n\n\n\n",i,aFatherValue[i]);
// printf(aFatherValue[i]); }}int max = MIN;for(int i=0; i<=8; i++){if(Head.chessboard.chessman[i] == 0){if(aFatherValue[i] > max){max = aFatherValue[i];Head.CoordinateAI = i; //给头结点中电脑应下的位置做标记 有利于返回结果 }} }return Head.CoordinateAI; }//当某一方下棋后,判断结果
int judgeBoard(ChessBoard *chessboard){int Co;int judge[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6}};int x = 1;int y = 2;int valueX = 0;int valueY = 0;for(int i=0; i<=7; i++){valueX =0;valueY = 0;for(int j=0; j<=2; j++){Co = judge[i][j];if(chessboard->chessman[Co] == 1){valueX++;}if(chessboard->chessman[Co] == 2){valueY++;}}if(valueX == 3){return x;}if(valueY == 3){return y;}}return 0;
}
总结
我的一字棋游戏实现落棋输入没有进行限制,用户违规下棋了没有进行相应的阻止和提示。这是本程序缺点中的一个。棋盘显示不是我个人原创的,但算法的实现是自己设计的。本次算法没有考虑是棋盘对称问题,我是把所有落子的情况都考虑了,最原始的搜索树是固定大小的。就是一个二维数组,把有棋的位置不做考虑。当游戏进行到后面时,逻辑上的搜索树是越来越小的。我写的搜索树在逻辑上是极大极小搜素树的,但是树的实际存储空间是同样大小的。在编程方面,模块化结构设计非常好用,本次写程序出现bug时,绘画棋盘DrawBoard()函数就能发挥非常大的用处了。
用C语言实现简单的一字棋游戏相关推荐
- Python基础编程案例:简单的井字棋游戏设计与制作
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 前言 python井字棋游戏虽然看上去非常简陋,但是却非常值得学习. 先看怎么玩 ...
- java——博弈算法实现井字棋游戏
通过java语言开发了一个简单的井字棋游戏.主要有6个类,其中有一个是主类(Main.java),一个是抽象类(PiecesMove.java)组成. 下面对各个类简单介绍一下: TicTicToe. ...
- python井字棋ai_[Python100行系列]-井字棋游戏
博客:Hzy的博客 | Hzy Bloghzeyuan.cn一些学习python的小项目,小游戏.python小项目github.com 话不多说,今天尝试用turtle库来写一个井字棋游戏.1. ...
- python井字棋_[Python100行系列]-井字棋游戏
博客:Hzy的博客 | Hzy Bloghzeyuan.cn一些学习python的小项目,小游戏.python小项目github.com 话不多说,今天尝试用turtle库来写一个井字棋游戏.1. ...
- Unity3D学习笔记(2)——用GUI制作井字棋游戏
本来拿gui来做游戏蛮怪的,但这却是一个熟悉gui的不错的途径.今天我就学着使用GUI.Button做了一个十分简单的井字棋游戏.上个成品图: 首先创建一个C#脚本文件,去掉Update方法,因为这里 ...
- MCTS蒙特卡洛搜索树实现井字棋游戏
利用蒙特卡洛搜索树实现简单的井字棋游戏,重点不是井字棋,是熟悉蒙特卡洛搜索树的应用,而且我们知道,MCTS可以应用到非常复杂的博弈游戏中,比如象棋,围棋,在搜索空间非常大的时候,普通的极大极小搜索树无 ...
- c语言编简单博弈小游戏,[2018年最新整理]实验二:利用α-β搜索过程的博弈树搜索算法编写一字棋游戏.doc...
[2018年最新整理]实验二:利用α-β搜索过程的博弈树搜索算法编写一字棋游戏 实验二:利用α-β搜索过程的博弈树搜索算法编写一字棋游戏 一.实验目的与要求 (1)了解极大极小算法的原理和使用方法,并 ...
- C语言第十课:编写井字棋游戏(综合练习1)
目录 前言: 一.文件建立: 1.头文件game.h: 2.函数定义文件game.c: 3.工程测试文件test.c: 二.编写井字棋游戏: 1.程序整体执行思路: 2.menu菜单函数实现: 3.g ...
- Py之tkinter:python最简单的猜字小游戏带你进入python的GUI世界
Py之tkinter:python最简单的猜字小游戏带你进入python的GUI世界 目录 输出结果 设计思路 输出结果 设计思路 from tkinter import * import tkint ...
最新文章
- 图像的打开、修改、显示和保存示例(OpenCV 2.0)
- 面试高频题: LRU缓存机制实现
- java创建activity视图_java-动态创建的视图id始终为null-findviewbyid不起作用
- python开源系统_搭建轻量级的开源推荐系统-Python-recsys
- 【PDF】PDF文件分页拆分(免费方法)
- MTK camera驱动浅析(1)
- 后台job批量停用和开启
- css背景颜色如何铺满屏幕
- 广数工业机器人五点法_盘点:国产工业机器人“四小龙”新业绩经营情况
- PHP输出JSON格式数据
- 内网穿透工具Ngrok
- 华为策略路由加等价路由_思科华为路由器如何利用route-map配置双wan口策略路由...
- 计算机学术论文shortessay,英语学术论文与研究方法-北京林业大学外语学院.doc
- 【C语言每日一练——第1练:字母大小写转换】
- 获取linux命令硬盘信息,Linux 下使用命令获取硬盘信息
- [Watermelon_book] Chapter 3 Linear Model
- 量化交易 实战第二课 金融时间序列分析 Part 1
- 06-App页面元素查看神器
- 我们做的货架结构计算到底有什么特点
- java在线订餐系统(1)
热门文章
- pycharm报错:Error configuring SDK: Accessing invalid virtual file: 解决办法
- ibm aix_IBM AIX设备驱动程序开发
- 论文笔记1:Fast and Robust Multi-Person 3D Pose Estimation from Multiple Views
- c语言移动光标到指定坐标,C语言实现光标移动
- openGL绘制带纹理地球,并实现鼠标键盘控制
- anbox 使用情况_最近比较火的Anbox项目
- 请编写一个函数void fun(int tt[M][N],int pp[N]),tt指向一个M行N列的二维数组,求出二维数组每列中最小元素,并依次放入pp所指一维数组中。
- @linux安装及使用(压缩|解压)工具RAR
- 无需SVIP,两步实现百度网盘不限速
- sql 计算法定假节假日解决方案一