用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
#include
//棋子 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()函数就能发挥非常大的用处了。