当前位置:   article > 正文

三子棋游戏(利用基础语法实现人工智能)_人工智能三盘棋怎么设计教学活动

人工智能三盘棋怎么设计教学活动

通过基础的C语言知识,来实现三子棋的人工智能,让电脑可以实现自由落子、堵棋以及赢棋的功能。整体代码650行左右,适合刚接触编程的同学,在实现程序的同时,增加自己的编程知识,并且锻炼用逻辑思维思考的能力。

目录

整体结构组成

大致框架

游戏部分

生成棋盘

定义数组

初始化

打印棋盘

玩家下棋

电脑下棋(智能下棋)

自由落子

堵棋

赢棋

判断输赢

判定横三连

判断竖三连

判断斜三连(左下右上)

判断斜三连(左下右上)

判断平局

测试

改进

整体代码展示

text.c

game.h

game.c


整体结构组成

游戏代码主要由三部分组成,text.c存放主函数,game.c存放游戏实现部分的函数,game.h声明在game.c中定义的函数。

text.c(菜单、主函数)

game.c(游戏内容)

game.h(声明游戏内容中所包含的函数)

大致框架

为了避免有输入错误的情况,以及玩完一遍游戏还可以玩第二遍,这里整个游戏框架用do while循环,保证整个流程至少可以运行一次。

  1. #include"game.h"
  2. void menu()
  3. {
  4. printf("******************\n");
  5. printf("***** 1.paly *****\n");
  6. printf("***** 0.exit *****\n");
  7. printf("******************\n");
  8. }
  9. int main()
  10. {
  11. int input = 0;
  12. do
  13. {
  14. menu();
  15. printf("请选择(1/0):>");
  16. scanf("%d", &input);
  17. } while (input);
  18. return 0;
  19. }

随后,针对输入的值不同,来判定是进行游戏还是退出游戏,或是输入错误。这里可以用switch 选择语句完成该部分的判断。

  1. switch (input)
  2. {
  3. case 0:
  4. printf("退出游戏\n");
  5. break;
  6. case 1:
  7. printf("开始游戏\n");
  8. game();//游戏
  9. Sleep(500);//等500毫秒
  10. system("cls");//清空屏幕
  11. break;
  12. default:
  13. printf("输入错误,请重新输入\n");
  14. }

当我们完成一次游戏后,为了方便进行下一次玩,用system("cls")将我们的屏幕清空,但cls清空的速度非常快,这里用Sleep(500)让系统休眠500毫秒,方便我们看到游戏的结果。system和Sleep的使用均需要包含头文件<windows.h>

当完成了这部分的内容,整个游戏的大致框架就完成了,将game()先打上注释,这里测试一下能否正常运行。

游戏部分

游戏的部分整体流程如下:

生成棋盘

  • 定义数组

三子棋的棋盘一共三行三列,共有九个空可以下棋,这用一个三行三列的数组来存放每个空的数据。

为了代码的灵活性,这里不要把行和列的值写死,用define定义的常量来写,方便以后代码的更新。

  1. //game.h
  2. #define ROW 3
  3. #define COL 3
  4. //text.c
  5. #include"game.h"
  6. void game()
  7. {
  8. char board[ROW][COL] = { 0 };
  9. }
  • 初始化

定义完数组后,为了更加贴合棋盘的样式,我们需要对数组进行初始化,将空格赋给每个元素。这里定义一个初始化棋盘的函数InitBoard,将数组,行和列的参数传给函数,这个函数的部分拿到game.c中完成,并且在game.h中声明一下。

  1. //text.c
  2. #include"game2.h"
  3. void game()
  4. {
  5. char board[ROW][COL] = { 0 };
  6. InitBoard(board, ROW, COL);//初始化
  7. }
  8. //game.h
  9. //初始化部分
  10. void InitBoard(char board[ROW][COL], int row, int col);
  11. //game.c
  12. void InitBoard(char board[ROW][COL], int row, int col)
  13. {
  14. int i = 0;
  15. int j = 0;
  16. for (i = 0; i < row; i++)
  17. {
  18. for (j = 0; j < col; j++)
  19. {
  20. board[i][j] = ' ';//将空格赋给每个元素
  21. }
  22. }
  23. }
  • 打印棋盘

打印棋盘时注意也不要把棋盘写死,这里我们定义DisPaly_Board函数,用for循环来打印棋盘,打印“   |   |   |   ”时,将“   |”设定为一组,遍历到最后一列时,只打印“   ”;同理,打印“---|---|---”时,将“---|”设定为一组进行打印,遍历到最后一列时,只打印“---”,遍历到最后一行时都不打印。

  1. //text.c
  2. void game()
  3. {
  4. char board[ROW][COL] = {0};
  5. //初始化
  6. InitBoard(board, ROW, COL);
  7. //打印棋盘
  8. DisPaly_Board(board, ROW, COL);
  9. }
  10. //game.h
  11. void DisPaly_Board(char board[ROW][COL], int row, int col);
  12. //game.c
  13. void DisPaly_Board(char board[ROW][COL], int row, int col)
  14. {
  15. int i = 0;
  16. int j = 0;
  17. for (i = 0; i < row; i++)
  18. {
  19. for (j = 0; j < col; j++)
  20. {
  21. printf(" %c ", board[i][j]);
  22. if (j < col - 1)
  23. printf("|");
  24. }
  25. printf("\n");
  26. if (i < row - 1)
  27. {
  28. int j = 0;
  29. for (j = 0; j < col; j++)
  30. {
  31. printf("---");
  32. if (j < col - 1)
  33. {
  34. printf("|");
  35. }
  36. }
  37. printf("\n");
  38. }
  39. }
  40. }

这样写的好处是:当我们改变define定义的常量时,棋盘的大小也会随之改变。

例如将ROW和COL都改成10:

玩家下棋

在玩家下棋这部分中,我们定义玩家通过输入坐标来进行下棋。需要注意的是:数组所定义的下标是从0开始的,而玩家认知的坐标是从1开始的(例如:玩家想输入第一行第一列的坐标是,输入的是(1,1),而不是(0,0)),这里需要将玩家输入的横、纵坐标减1,转换成数组的坐标。

在玩家输入完坐标后,我们还需要判断该坐标是否合法,如果玩家输入的坐标超限,或者该坐标已经下过棋了,那么需要重新输入坐标。

这里玩家下的棋,用 * 表示。定义PlayerMove用来实现玩家下棋的这部分。

  1. //text.c
  2. void game()
  3. {
  4. int ret = 0;
  5. char board[ROW][COL] = {0};
  6. //初始化
  7. InitBoard(board, ROW, COL);
  8. //打印棋盘
  9. DisPaly_Board(board, ROW, COL);
  10. while (1)
  11. {
  12. //玩家下棋
  13. PlayerMove(board, ROW, COL);
  14. //打印棋盘
  15. DisPaly_Board(board, ROW, COL);
  16. }
  17. }
  18. //game.h
  19. void PlayerMove(char board[ROW][COL], int row, int col);
  20. //game.c
  21. void PlayerMove(char board[ROW][COL], int row, int col)
  22. {
  23. int x = 0;
  24. int y = 0;
  25. while (1)
  26. {
  27. printf("玩家下棋:>\n");
  28. scanf("%d%d", &x, &y);
  29. if (x > 0 && x <= row && y > 0 && y <= col)
  30. {
  31. if (board[x - 1][y - 1] == ' ')
  32. {
  33. board[x - 1][y - 1] = '*';
  34. break;
  35. }
  36. }
  37. printf("坐标非法,请重新输入\n");
  38. }
  39. }

当玩家下完棋后,我们需要再将棋盘打印一下。并且下棋是一个有来有回的过程,这里用一个循环将整个下棋的过程包含起来,直到分出胜负或平局跳出循环。

电脑下棋(智能下棋)

电脑下棋分为三个部分:赢棋堵棋自由落子

这里的优先级是赢棋>堵棋>自由落子,在写代码的时候要遵循:能赢则赢,不能赢则堵,以上情况都不符合的时候下新棋的原则。(自由落子不等于下废棋,不能彻底随机)

定义三个函数:

赢棋函数:TryWin(如果函数执行并落子,返回0;否则返回1)

堵棋函数:Cut(如果函数执行并落子,返回0;否则返回1)

自由落子函数:ComputerMove(构建整体框架,根据前两个函数的返回值来判断是否落子)

  • 自由落子

需要注意的是,在下新棋的时候,为了不让该棋变成废棋,需要判定电脑落子附近的8个单元格内至少有一个棋子是玩家的落子。

具体方法如下:先让电脑随机生成一个坐标,然后判定该坐标是否同时满足条件1和条件2,如果满足就落子,如果不满足则重新生成新的坐标,所以这里需要用到while循环,根据优先度的排序,while循环的表达式则是Cut函数的返回值。

条件1:该位置是空地。

条件2:(x,y)相邻的8个元素中,至少有一个格子有玩家的落子。(条件2是为了防止电脑随机下棋下到角落里,该棋便成了废棋)

ComputerMove代码如下:

  1. //game.h
  2. //电脑下棋
  3. void ComputerMove(char board[ROW][COL], int row, int col);
  4. //电脑尝试三连
  5. int TryWin(char board[ROW][COL], int row, int col);
  6. //堵棋
  7. int Cut(char board[ROW][COL], int row, int col);
  8. //text.c
  9. srand((unsigned int)time(NULL));//使用rand()需要在主函数中用srand修饰,提供随机标准。
  10. //game.c
  11. //电脑下棋
  12. void ComputerMove(char board[ROW][COL], int row, int col)
  13. {
  14. printf("电脑下棋:>\n");
  15. if (TryWin(board, ROW, COL)==1)//先尝试三连
  16. {
  17. while (Cut(board, ROW, COL))//不能三连尝试堵棋
  18. {
  19. int x = rand() % row;
  20. int y = rand() % col;
  21. //控制范围,不要下的太偏了,新棋下的位置要在玩家落子的位置附近,以免变成废棋
  22. if (board[x][y] == ' ' && (board[x - 1][y - 1] == '*' || board[x - 1][y] == '*' || board[x - 1][y - 1] == '*' || board[x][y - 1] == '*' || board[x][y + 1] == '*' || board[x + 1][y - 1] == '*' || board[x + 1][y] == '*' || board[x + 1][y + 1] == '*'))
  23. {
  24. board[x][y] = '#';
  25. break;
  26. }
  27. }
  28. }
  29. }
  • 堵棋

堵棋的优先度在下新棋之上,在赢棋的优先度之下。

堵棋共有四种堵法:横着堵竖着堵左斜堵右斜堵

将堵棋的大致框架写出,并且为了代码的适用性,这里的代码也不要写死,既要适用三行三列的棋盘,也要同样适用于更大的棋盘。(五行五列、十行十列等等)

当棋盘中不需要堵棋时,返回1;而只要当其中一种情况成立,电脑便落子堵棋,返回0。

堵棋框架:

  1. //game.c
  2. //堵棋
  3. int Cut(char board[ROW][COL], int row, int col)
  4. {
  5. int i = 0;
  6. int j = 0;
  7. for (i = 0; i < row; i++)
  8. {
  9. for (j = 0; j < col; j++)
  10. {
  11. //堵横着
  12. //堵竖着的
  13. //右斜(左上右下)
  14. //左斜(左下右上)
  15. }
  16. }
  17. return 1;
  18. }

横着堵:

横着堵有四种情况:XXO、OXX,XOX,以及OXXO(在大棋盘中)。

这里采用for循环遍历来寻找棋盘中是否存在以上四种情况,针对不同的情况,坐标的搜索范围也不一致,这里注意数组的访问不要超出限制。

  1. //堵横着
  2. if (j < col - 2)//XOX型
  3. {
  4. if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
  5. {
  6. board[i][j + 1] = '#';
  7. return 0;
  8. }
  9. }
  10. if (j < col - 1)//XXO或OXX型
  11. {
  12. if (board[i][j] == board[i][j + 1] && board[i][j] == '*')
  13. {
  14. if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
  15. {
  16. int ch = rand() % 2;//两种情况都符合时,随机下
  17. switch (ch)
  18. {
  19. case 0:
  20. board[i][j + 2] = '#';
  21. return 0;
  22. case 1:
  23. board[i][j - 1] = '#';
  24. return 0;
  25. }
  26. }
  27. //只符合其中一种情况
  28. if (j < col - 2 && board[i][j + 2] == ' ')//XXO
  29. {
  30. board[i][j + 2] = '#';
  31. return 0;
  32. }
  33. if (board[i][j - 1] == ' ')//OXX
  34. {
  35. board[i][j - 1] = '#';
  36. return 0;
  37. }
  38. }
  39. }

竖着堵:

竖着堵与横着堵的情况一致,也是四种情况,将横纵坐标的要求对调一下即可。

  1. //堵竖着的
  2. //X
  3. //0
  4. //X型
  5. if (i < row - 2)
  6. {
  7. if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '#')
  8. {
  9. board[i + 1][j] = '#';
  10. return 0;
  11. }
  12. }
  13. //X O
  14. //X X
  15. //O型 或 X型
  16. if (i < row - 1)
  17. {
  18. if (board[i][j] == board[i + 1][j] && board[i][j] == '#')
  19. {
  20. if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
  21. {
  22. int ch = rand() % 2;
  23. switch (ch)
  24. {
  25. case 0:
  26. board[i + 2][j] = '#';
  27. return 0;
  28. case 1:
  29. board[i - 1][j] = '#';
  30. return 0;
  31. }
  32. }
  33. if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
  34. {
  35. board[i + 2][j] = '#';
  36. return 0;
  37. }
  38. if (board[i - 1][j] == ' ')//只有上方可以堵
  39. {
  40. board[i - 1][j] = '#';
  41. return 0;
  42. }
  43. }
  44. }

左斜堵:

左斜堵的情况也是四种,但是需要注意的是,这里符合条件的横纵坐标范围与右斜有很大区别。

  1. //堵斜着(左下右上)
  2. // X
  3. // 0
  4. //X 型
  5. if (i < row - 2 && j > 1)
  6. {
  7. if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '#')
  8. {
  9. board[i + 1][j - 1] = '#';
  10. return 0;
  11. }
  12. }
  13. // X O
  14. // X X
  15. //O 型 或 X 型
  16. if (i < row - 1 && j > 0)
  17. {
  18. if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '#')
  19. {
  20. if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
  21. {
  22. int ch = rand() % 2;
  23. switch (ch)
  24. {
  25. case 0:
  26. board[i + 2][j - 2] = '#';
  27. return 0;
  28. case 1:
  29. board[i - 1][j + 1] = '#';
  30. return 0;
  31. }
  32. }
  33. if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
  34. {
  35. board[i + 2][j - 2] = '#';
  36. return 0;
  37. }
  38. if (board[i - 1][j + 1] == ' ')//只有上方可以堵
  39. {
  40. board[i - 1][j + 1] = '#';
  41. return 0;
  42. }
  43. }
  44. }

右斜堵:

  1. //堵斜着(左上右下)
  2. //X
  3. // 0
  4. // X型
  5. if (i < row - 2 && j < col - 2)
  6. {
  7. if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '#')
  8. {
  9. board[i + 1][j + 1] = '#';
  10. return 0;
  11. }
  12. }
  13. // X O
  14. // X X
  15. // O型 或 X型
  16. if (i < row - 1 && j < col - 1)
  17. {
  18. if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '#')
  19. {
  20. if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
  21. {
  22. int ch = rand() % 2;
  23. switch (ch)
  24. {
  25. case 0:
  26. board[i + 2][j + 2] = '#';
  27. return 0;
  28. case 1:
  29. board[i - 1][j - 1] = '#';
  30. return 0;
  31. }
  32. }
  33. if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
  34. {
  35. board[i + 2][j + 2] = '#';
  36. return 0;
  37. }
  38. if (board[i - 1][j - 1] == ' ')//只有上方可以堵
  39. {
  40. board[i - 1][j - 1] = '#';
  41. return 0;
  42. }
  43. }
  44. }

当我们把上述共十六种的情况都罗列出来后,电脑便会逢棋必堵,并且堵棋函数中所运用到的知识和方法都是C语言初阶的内容,并没有很高深的内容。

  • 赢棋

为了让我们的电脑更进一步,可以抓住玩家露出的破绽,我们再赋予电脑一个赢棋的函数。

而赢棋的优先级也是最高的,毕竟如果游戏都赢了,那就没有堵棋和下棋的事儿了。

赢棋的函数其实和堵棋的函数十分类似,可以说是99.9%都是一致的。

想象一下:在判定是否需要堵棋时,我们其实判定的是玩家是否会赢,如果出现玩家赢的可能,那么我们就要执行堵棋。我们只需要改变一下条件,改成判定电脑是否会赢,如果出现电脑赢的可能,那么就执行赢棋。

例如:

  1. //堵棋
  2. if (j < col - 2)//XOX型
  3. {
  4. if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
  5. {
  6. board[i][j + 1] = '#';
  7. return 0;
  8. }
  9. }
  10. //赢棋
  11. if (j < col - 2)//XOX型
  12. {
  13. if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '#')
  14. {
  15. board[i][j + 1] = '#';
  16. return 0;
  17. }
  18. }

将堵棋的代码完全拷贝下来,并将表达式中的*改成#即可。

判断输赢

三子棋的输赢判定方式很简单,横、竖、斜三棋相连,为了不把代码写死,这里将分别对横、竖、斜(左下右上)、斜(右下左上)这四种进行分别判定。

将框架写好,随后在for循环的内部加入判定部分即可。

  1. //game.c
  2. int Judgment(char board[ROW][COL], int row, int col)
  3. {
  4. int i = 0;
  5. int j = 0;
  6. for (i = 0; i < row; i++)
  7. {
  8. for (j = 0; j < col; j++)
  9. {
  10. //判定部分
  11. }
  12. }
  13. }

因为是三子棋,所以靠近边界的两行没有判断的价值,必然不会有第三个棋与之相连。

当判定为三个相连且不是空格时,只需返回其中一个元素即可,当接收函数返回值时,发现返回值是*,是玩家赢;返回值是#,则是电脑赢。

  • 判定横三连

横着三个相等且不等于空格,将第一个元素返回。

  1. if (i < row && j < col - 2)//三子棋,所以靠边界的两行没必要判断
  2. {
  3. if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] && board[i][j] != ' ')
  4. {
  5. return board[i][j];
  6. }
  7. }
  • 判断竖三连

竖着三个相等且不等于空格,将第一个元素返回。

  1. if (i < row - 2 && j < col)
  2. {
  3. if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
  4. {
  5. return board[i][j];
  6. }
  7. }
  • 判断斜三连(左下右上)

斜三连的情况稍微复杂一点,正斜和反斜针对i和j的取值范围不同,并且坐标的变换也不一样。

  1. if (i < row-2 && j > 1)
  2. {
  3. if (board[i][j] == board[i + 1][j - 1] && board[i + 1][j - 1] == board[i + 2][j - 2] && board[i][j] != ' ')
  4. {
  5. return board[i][j];
  6. }
  7. }
  • 判断斜三连(左下右上)

  1. if (i < row-2 && j < col-2 )
  2. {
  3. if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
  4. {
  5. return board[i][j];
  6. }
  7. }
  • 判断平局

除了输赢,还会有平局的情况发生,判断平局的方法很简单,遍历一遍整个数组,当发现没有‘ ’时,即平局了。这里如果平局则返回q,没有平局则返回c,判断平局的部分要放在判断输赢的后面。

  1. //判断平局
  2. int IsFull(char board[ROW][COL], int row, int col)
  3. {
  4. int i = 0;
  5. int j = 0;
  6. for (i = 0; i < row; i++)
  7. {
  8. for (j = 0; j < col; j++)
  9. {
  10. if (board[i][j] == ' ')
  11. {
  12. return 0;
  13. }
  14. }
  15. }
  16. return 1;
  17. }
  18. if (IsFull(board, row, col))
  19. {
  20. return 'q';
  21. }
  22. return 'c';

测试

当完成了所有代码后,这里我们用5×5的棋盘可以更好的测试一下。

当玩家先手下棋后,电脑在堵截玩家这方面还是咬的很死的,现在电脑有一个三连的机会,我们卖一个破绽,看看电脑能否抓住。

电脑不负众望抓住了机会,踏上了人工智能面向世界的第一步。我们这里设定的是玩家先手,玩家的优势稍微大一点。

如果想要设置电脑先手的话,还需要对电脑下棋部分的代码再加一点东西:即当整个棋盘是空的时候,电脑在中心区域随机下棋。(我们之前的代码一直是电脑负责对玩家的落子进行围追堵截,没有先手的情况

改进

其实整个代码还是有很大的优化空间的:

1、受到三子棋的限制,在3×3的棋盘太过无趣,但当三子棋在大棋盘中进行游玩时,先手一方必赢。

这里可以将三子棋改成五子棋,本质框架不变,但是需要堵棋的情况会比三子棋要多出一些。

2、电脑在堵棋时,如果是10×10的大棋盘,那么会出现可以两头堵的情况,在最初设定时,我们设置的是两头随机堵,但是现实中这样往往会错失良机。电脑的下棋也是如此,我们只是设置了一个随机坐标,保证在玩家落子的附近,但不能保证该坐标就是最优的选择。那么如何改进呢?

我们可以对整个棋盘设置权重P

当该坐标周围一圈附近8个元素中每多一颗电脑的棋子,P+10;

附近8个元素中每多一颗玩家的棋子,P+5;

该坐标周围第二圈附近16个元素中每多一颗电脑的棋子,P+5;

该坐标周围第二圈附近16个元素中每多一颗玩家的棋子,P+2。

以此类推,离坐标越远权重越低,每次电脑下棋前,遍历一遍棋盘,选择权重最高的方法下,如果有权重相同的情况,再选择周围空位最多的坐标。

除以上两点之外,其实还有更多细节之处可以优化,有兴趣的小伙伴可以自己尝试一下。

整体代码展示

这里为了方便大家的测试与改进,我把全部的代码放在下面。如果有不足之处,还请大家多多指点。

text.c

  1. //三子棋
  2. #include"game.h"
  3. void menu()
  4. {
  5. printf("******************\n");
  6. printf("***** 1.paly *****\n");
  7. printf("***** 0.exit *****\n");
  8. printf("******************\n");
  9. }
  10. void game()
  11. {
  12. int ret = 0;
  13. char board[ROW][COL] = {0};
  14. //初始化
  15. InitBoard(board, ROW, COL);
  16. //打印棋盘
  17. DisPaly_Board(board, ROW, COL);
  18. while (1)
  19. {
  20. //玩家下棋
  21. PlayerMove(board, ROW, COL);
  22. //打印棋盘
  23. DisPaly_Board(board, ROW, COL);
  24. //判断输赢
  25. ret = Judgment(board, ROW, COL);
  26. if (ret != 'c')
  27. {
  28. break;
  29. }
  30. //电脑下棋
  31. ComputerMove(board, ROW, COL);
  32. //打印棋盘
  33. DisPaly_Board(board, ROW, COL);
  34. //判断输赢
  35. ret = Judgment(board, ROW, COL);
  36. if (ret != 'c')
  37. {
  38. break;
  39. }
  40. }
  41. if (ret == '*')
  42. {
  43. printf("玩家赢了\n");
  44. }
  45. else if (ret == '#')
  46. {
  47. printf("电脑赢了\n");
  48. }
  49. else
  50. printf("平局\n");
  51. }
  52. int main()
  53. {
  54. int input = 0;
  55. srand((unsigned int)time(NULL));
  56. do
  57. {
  58. menu();
  59. printf("请选择(1/0):>");
  60. scanf("%d", &input);
  61. switch (input)
  62. {
  63. case 0:
  64. printf("退出游戏\n");
  65. break;
  66. case 1:
  67. printf("开始游戏\n");
  68. game();//游戏
  69. Sleep(500);
  70. system("cls");//清空屏幕
  71. break;
  72. default:
  73. printf("输入错误,请重新输入\n");
  74. }
  75. } while (input);
  76. return 0;
  77. }

game.h

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<time.h>
  4. #include<windows.h>
  5. #define ROW 5//在测试的时候,建议将行和列设置稍微大一点
  6. #define COL 5
  7. //初始化
  8. void InitBoard(char board[ROW][COL], int row,int col);
  9. //打印棋盘
  10. void DisPaly_Board(char board[ROW][COL], int row, int col);
  11. //玩家下棋
  12. void PlayerMove(char board[ROW][COL], int row, int col);
  13. //电脑尝试三连
  14. int TryWin(char board[ROW][COL], int row, int col);
  15. //堵棋
  16. int Cut(char board[ROW][COL], int row, int col);
  17. //电脑下棋
  18. void ComputerMove(char board[ROW][COL], int row, int col);
  19. //判断输赢
  20. int Judgment(char board[ROW][COL], int row, int col);

game.c

  1. #include"game.h"
  2. //初始化
  3. void InitBoard(char board[ROW][COL], int row, int col)
  4. {
  5. int i = 0;
  6. int j = 0;
  7. for (i = 0; i < row; i++)
  8. {
  9. for (j = 0; j < col; j++)
  10. {
  11. board[i][j] = ' ';
  12. }
  13. }
  14. }
  15. //打印棋盘
  16. void DisPaly_Board(char board[ROW][COL], int row, int col)
  17. {
  18. int i = 0;
  19. int j = 0;
  20. for (i = 0; i < row; i++)
  21. {
  22. for (j = 0; j < col; j++)
  23. {
  24. printf(" %c ", board[i][j]);
  25. if (j < col - 1)
  26. printf("|");
  27. }
  28. printf("\n");
  29. if (i < row - 1)
  30. {
  31. int j = 0;
  32. for (j = 0; j < col; j++)
  33. {
  34. printf("---");
  35. if (j < col - 1)
  36. {
  37. printf("|");
  38. }
  39. }
  40. printf("\n");
  41. }
  42. }
  43. }
  44. //玩家下棋
  45. void PlayerMove(char board[ROW][COL], int row, int col)
  46. {
  47. int x = 0;
  48. int y = 0;
  49. while (1)
  50. {
  51. printf("玩家下棋:>\n");
  52. scanf("%d%d", &x, &y);
  53. if (x > 0 && x <= row && y > 0 && y <= col)
  54. {
  55. if (board[x - 1][y - 1] == ' ')
  56. {
  57. board[x - 1][y - 1] = '*';
  58. break;
  59. }
  60. }
  61. printf("坐标非法,请重新输入\n");
  62. }
  63. }
  64. //电脑下棋(堵棋)
  65. int Cut(char board[ROW][COL], int row, int col)
  66. {
  67. int i = 0;
  68. int j = 0;
  69. for (i = 0; i < row; i++)
  70. {
  71. for (j = 0; j < col; j++)
  72. {
  73. //堵横着
  74. if (j < col - 2)//XOX型
  75. {
  76. if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '*')
  77. {
  78. board[i][j + 1] = '#';
  79. return 0;
  80. }
  81. }
  82. if (j < col - 1)//XXO或OXX型
  83. {
  84. if (board[i][j] == board[i][j + 1] && board[i][j] == '*')
  85. {
  86. if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
  87. {
  88. int ch = rand() % 2;//两种情况都符合时,随机下
  89. switch (ch)
  90. {
  91. case 0:
  92. board[i][j + 2] = '#';
  93. return 0;
  94. case 1:
  95. board[i][j - 1] = '#';
  96. return 0;
  97. }
  98. }
  99. //只符合其中一种情况
  100. if (j < col - 2 && board[i][j + 2] == ' ')//XXO
  101. {
  102. board[i][j + 2] = '#';
  103. return 0;
  104. }
  105. if (board[i][j - 1] == ' ')//OXX
  106. {
  107. board[i][j - 1] = '#';
  108. return 0;
  109. }
  110. }
  111. }
  112. //堵竖着的
  113. //X
  114. //0
  115. //X型
  116. if (i < row - 2)
  117. {
  118. if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '*')
  119. {
  120. board[i + 1][j] = '#';
  121. return 0;
  122. }
  123. }
  124. //X O
  125. //X X
  126. //O型 或 X型
  127. if (i < row - 1)
  128. {
  129. if (board[i][j] == board[i + 1][j] && board[i][j] == '*')
  130. {
  131. if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
  132. {
  133. int ch = rand() % 2;
  134. switch (ch)
  135. {
  136. case 0:
  137. board[i + 2][j] = '#';
  138. return 0;
  139. case 1:
  140. board[i - 1][j] = '#';
  141. return 0;
  142. }
  143. }
  144. if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
  145. {
  146. board[i + 2][j] = '#';
  147. return 0;
  148. }
  149. if (board[i - 1][j] == ' ')//只有上方可以堵
  150. {
  151. board[i - 1][j] = '#';
  152. return 0;
  153. }
  154. }
  155. }
  156. //堵斜着(左上右下)
  157. //X
  158. // 0
  159. // X型
  160. if (i < row - 2 && j < col - 2)
  161. {
  162. if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '*')
  163. {
  164. board[i + 1][j + 1] = '#';
  165. return 0;
  166. }
  167. }
  168. // X O
  169. // X X
  170. // O型 或 X型
  171. if (i < row - 1 && j < col - 1)
  172. {
  173. if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '*')
  174. {
  175. if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
  176. {
  177. int ch = rand() % 2;
  178. switch (ch)
  179. {
  180. case 0:
  181. board[i + 2][j + 2] = '#';
  182. return 0;
  183. case 1:
  184. board[i - 1][j - 1] = '#';
  185. return 0;
  186. }
  187. }
  188. if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
  189. {
  190. board[i + 2][j + 2] = '#';
  191. return 0;
  192. }
  193. if (board[i - 1][j - 1] == ' ')//只有上方可以堵
  194. {
  195. board[i - 1][j - 1] = '#';
  196. return 0;
  197. }
  198. }
  199. }
  200. //堵斜着(左下右上)
  201. // X
  202. // 0
  203. //X 型
  204. if (i < row - 2 && j > 1)
  205. {
  206. if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '*')
  207. {
  208. board[i + 1][j - 1] = '#';
  209. return 0;
  210. }
  211. }
  212. // X O
  213. // X X
  214. //O 型 或 X 型
  215. if (i < row - 1 && j > 0)
  216. {
  217. if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '*')
  218. {
  219. if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
  220. {
  221. int ch = rand() % 2;
  222. switch (ch)
  223. {
  224. case 0:
  225. board[i + 2][j - 2] = '#';
  226. return 0;
  227. case 1:
  228. board[i - 1][j + 1] = '#';
  229. return 0;
  230. }
  231. }
  232. if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
  233. {
  234. board[i + 2][j - 2] = '#';
  235. return 0;
  236. }
  237. if (board[i - 1][j + 1] == ' ')//只有上方可以堵
  238. {
  239. board[i - 1][j + 1] = '#';
  240. return 0;
  241. }
  242. }
  243. }
  244. }
  245. }
  246. return 1;
  247. }
  248. //电脑下棋(尝试三连)
  249. int TryWin(char board[ROW][COL], int row, int col)
  250. {
  251. int i = 0;
  252. int j = 0;
  253. for (i = 0; i < row; i++)
  254. {
  255. for (j = 0; j < col; j++)
  256. {
  257. //堵横着
  258. if (j < col - 2)
  259. {
  260. if (board[i][j] == board[i][j + 2] && board[i][j + 1] == ' ' && board[i][j] == '#')//XOX型
  261. {
  262. board[i][j + 1] = '#';
  263. return 0;
  264. }
  265. }
  266. if (j < col - 1)//XXO或OXX型
  267. {
  268. if (board[i][j] == board[i][j + 1] && board[i][j] == '#')
  269. {
  270. if (j < col - 2 && board[i][j + 2] == ' ' && board[i][j - 1] == ' ')//两种情况都符合
  271. {
  272. int ch = rand() % 2;
  273. switch (ch)
  274. {
  275. case 0:
  276. board[i][j + 2] = '#';
  277. return 0;
  278. case 1:
  279. board[i][j - 1] = '#';
  280. return 0;
  281. }
  282. }
  283. if (j < col - 2 && board[i][j + 2] == ' ')
  284. {
  285. board[i][j + 2] = '#';
  286. return 0;
  287. }
  288. if (board[i][j - 1] == ' ')
  289. {
  290. board[i][j - 1] = '#';
  291. return 0;
  292. }
  293. }
  294. }
  295. //堵竖着的
  296. //X
  297. //0
  298. //X型
  299. if (i < row - 2)
  300. {
  301. if (board[i][j] == board[i + 2][j] && board[i + 1][j] == ' ' && board[i][j] == '#')
  302. {
  303. board[i + 1][j] = '#';
  304. return 0;
  305. }
  306. }
  307. //X O
  308. //X X
  309. //O型 或 X型
  310. if (i < row - 1)
  311. {
  312. if (board[i][j] == board[i + 1][j] && board[i][j] == '#')
  313. {
  314. if (i < row - 2 && board[i + 2][j] == ' ' && board[i - 1][j] == ' ')//符合两种情况随机堵
  315. {
  316. int ch = rand() % 2;
  317. switch (ch)
  318. {
  319. case 0:
  320. board[i + 2][j] = '#';
  321. return 0;
  322. case 1:
  323. board[i - 1][j] = '#';
  324. return 0;
  325. }
  326. }
  327. if (i < row - 2 && board[i + 2][j] == ' ')//只有下方可以堵
  328. {
  329. board[i + 2][j] = '#';
  330. return 0;
  331. }
  332. if (board[i - 1][j] == ' ')//只有上方可以堵
  333. {
  334. board[i - 1][j] = '#';
  335. return 0;
  336. }
  337. }
  338. }
  339. //堵斜着(左上右下)
  340. //X
  341. // 0
  342. // X型
  343. if (i < row - 2 && j < col - 2)
  344. {
  345. if (board[i][j] == board[i + 2][j + 2] && board[i + 1][j + 1] == ' ' && board[i][j] == '#')
  346. {
  347. board[i + 1][j + 1] = '#';
  348. return 0;
  349. }
  350. }
  351. // X O
  352. // X X
  353. // O型 或 X型
  354. if (i < row - 1 && j < col - 1)
  355. {
  356. if (board[i][j] == board[i + 1][j + 1] && board[i][j] == '#')
  357. {
  358. if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ' && board[i - 1][j - 1] == ' ')//符合两种情况随机堵
  359. {
  360. int ch = rand() % 2;
  361. switch (ch)
  362. {
  363. case 0:
  364. board[i + 2][j + 2] = '#';
  365. return 0;
  366. case 1:
  367. board[i - 1][j - 1] = '#';
  368. return 0;
  369. }
  370. }
  371. if (i < row - 2 && j < col - 2 && board[i + 2][j + 2] == ' ')//只有下方可以堵
  372. {
  373. board[i + 2][j + 2] = '#';
  374. return 0;
  375. }
  376. if (board[i - 1][j - 1] == ' ')//只有上方可以堵
  377. {
  378. board[i - 1][j - 1] = '#';
  379. return 0;
  380. }
  381. }
  382. }
  383. //堵斜着(左下右上)
  384. // X
  385. // 0
  386. //X 型
  387. if (i < row - 2 && j > 1)
  388. {
  389. if (board[i][j] == board[i + 2][j - 2] && board[i + 1][j - 1] == ' ' && board[i][j] == '#')
  390. {
  391. board[i + 1][j - 1] = '#';
  392. return 0;
  393. }
  394. }
  395. // X O
  396. // X X
  397. //O 型 或 X 型
  398. if (i < row - 1 && j > 0)
  399. {
  400. if (board[i][j] == board[i + 1][j - 1] && board[i][j] == '#')
  401. {
  402. if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ' && board[i - 1][j + 1] == ' ')//符合两种情况随机堵
  403. {
  404. int ch = rand() % 2;
  405. switch (ch)
  406. {
  407. case 0:
  408. board[i + 2][j - 2] = '#';
  409. return 0;
  410. case 1:
  411. board[i - 1][j + 1] = '#';
  412. return 0;
  413. }
  414. }
  415. if (i < row - 2 && j > 1 && board[i + 2][j - 2] == ' ')//只有下方可以堵
  416. {
  417. board[i + 2][j - 2] = '#';
  418. return 0;
  419. }
  420. if (board[i - 1][j + 1] == ' ')//只有上方可以堵
  421. {
  422. board[i - 1][j + 1] = '#';
  423. return 0;
  424. }
  425. }
  426. }
  427. }
  428. }
  429. return 1;
  430. }
  431. //电脑下棋
  432. void ComputerMove(char board[ROW][COL], int row, int col)
  433. {
  434. printf("电脑下棋:>\n");
  435. if (TryWin(board, ROW, COL) == 1)//先尝试三连
  436. {
  437. while (Cut(board, ROW, COL))//不能三连尝试堵棋
  438. {
  439. int x = rand() % row;
  440. int y = rand() % col;
  441. //控制范围,不要下的太偏了
  442. if (board[x][y] == ' ' && (board[x - 1][y - 1] == '*' || board[x - 1][y] == '*' || board[x - 1][y - 1] == '*' || board[x][y - 1] == '*' || board[x][y + 1] == '*' || board[x + 1][y - 1] == '*' || board[x + 1][y] == '*' || board[x + 1][y + 1] == '*'))
  443. {
  444. board[x][y] = '#';
  445. break;
  446. }
  447. }
  448. }
  449. }
  450. //判断平局
  451. int IsFull(char board[ROW][COL], int row, int col)
  452. {
  453. int i = 0;
  454. int j = 0;
  455. for (i = 0; i < row; i++)
  456. {
  457. for (j = 0; j < col; j++)
  458. {
  459. if (board[i][j] == ' ')
  460. {
  461. return 0;
  462. }
  463. }
  464. }
  465. return 1;
  466. }
  467. //判断输赢
  468. int Judgment(char board[ROW][COL], int row, int col)
  469. {
  470. int i = 0;
  471. int j = 0;
  472. for (i = 0; i < row; i++)
  473. {
  474. for (j = 0; j < col; j++)
  475. {
  476. //判断横行是否相连
  477. if (i < row && j < col - 2)//三子棋,所以靠边界的两行没必要判断
  478. {
  479. if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] && board[i][j] != ' ')
  480. {
  481. return board[i][j];
  482. }
  483. }
  484. //判断竖行是否相连
  485. if (i < row - 2 && j < col)
  486. {
  487. if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
  488. {
  489. return board[i][j];
  490. }
  491. }
  492. //判断斜行是否相连(左下右上)
  493. if (i < row - 2 && j > 1)
  494. {
  495. if (board[i][j] == board[i + 1][j - 1] && board[i + 1][j - 1] == board[i + 2][j - 2] && board[i][j] != ' ')
  496. {
  497. return board[i][j];
  498. }
  499. }
  500. //判断斜行是否相连(右下左上)
  501. if (i < row - 2 && j < col - 2)
  502. {
  503. if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
  504. {
  505. return board[i][j];
  506. }
  507. }
  508. }
  509. }
  510. if (IsFull(board, row, col))//判断平局
  511. {
  512. return 'q';
  513. }
  514. return 'c';
  515. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/1017756
推荐阅读
相关标签
  

闽ICP备14008679号