小言_互联网的博客

Linux项目实战——五子棋(单机人人对战版)

502人阅读  评论(0)

Linux操作系统项目实战——五子棋

GIF:

 

目录

           Linux操纵系统项目——五子棋

一、问题导引:

二、实现要求:

三、五子棋原理:

1.落子数据信息保存载体:

2.落子思路:

3.判断“五子连珠”

四、项目实现步骤:

Ⅰ.创建目录及文件:

1.在Linux环境下创建名为Gobang的文件目录:

Ⅱ、输入Linux环境指令进入目录及文件:

Ⅲ、Linux环境编程实现:

1.顶层逻辑设计:

2.模块化设计:

3.联动模块组合封装:

        完整代码:

(1)game.c

(2)main.c

(3)game.h

(4)Makefile

 五、程序调试:

1.程序界面及说明:

2.测试用例:

六、程序功能扩展:

1.扩展思路:

2.扩展实现:

引入进度条:​

(1)更改代码:

(2)扩展效果演示:​

七、项目总结:

1.核心逻辑:

2.项目体现思想:

八、项目改编:

后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!                                                       ——By 作者:新晓·故知


一、问题导引:

在进行Linux入门学习后,通过运用Linux环境指令操作、Vim编辑器、学习五子棋的基本原理等,进行Linux环境下的简单的五子棋项目实现。

生活中的五子棋:

   

从上图可以看出,五子棋(这类棋)有先手、后手之分(当然也有禁手、俗手、妙手等),这里只进行简单的交互式、窗口化项目实现。(上图涉及GUI技术、图像渲染技术等,太过高深)

二、实现要求:

  • 熟悉Linux环境指令操作

  • Linux中相关开发工具的基本使用

  • 熟悉在Linux环境当中进行C编程

  • 掌握五子棋的基本原理

  • 编写五子棋程序的上层基本调用框架

  • 等等

说明:本篇博客采用VMware Workstation 16+CentOS7作为实现演示示例工具

三、五子棋原理:

1.落子数据信息保存载体:

需要记录每个玩家的落子位置,用来判断谁赢谁输。

可采用二维数组保存棋盘信息,棋盘上面的任何一个位置,里面可以放置三类信息:

  • 空(没有落子)

  • 玩家1的落子(黑子)

  • 玩家2的落子(白子)

2.落子思路:

1.如果下一步能赢,就走这一步

2.如果下一步会输,就阻止对方赢

3.统计棋子数决定在哪里落子。

其中 阻止对方赢,就是在对方能赢的点上落子,一般会有1到3个点。

3.判断“五子连珠”

下棋就是在二维数组中找对应的空位置,进行落子

落完子之后下来就要考虑该落子位置是否有”五子连珠“,进而进行输赢判定,每一次走棋,多会有四种情况:

  • 玩家1获胜

  • 玩家2获胜

  • 平局(棋盘满了等)

  • 未出结果(游戏继续)其中,“未出结果”游戏要继续,其他三种情况,游戏不用继续

四、项目实现步骤:

Ⅰ.创建目录及文件:

1.在Linux环境下创建名为Gobang的文件目录:

mkdir Gobang

(目录名随意,这里采用五子棋的英文命名目录)

2.创建多文本编辑:

touch Makefile

3.分别创建子文件:


   
  1. touch game.h
  2. touch game.c
  3. touch main.c

 Ⅱ、输入Linux环境指令进入目录及文件:

Ⅲ、Linux环境编程实现:

1.顶层逻辑设计:

输入Linux指令:

(1)

vim Makefile

(2)

vim game.h

 (3)

vim game.c

(4)

vim main.c

 

2.模块化设计:

第一步. main构建游戏起始逻辑
第二步 . 构建游戏入口 Game() 函数

第三步 . 编写核心函数,定义全局游戏信息

第四步 . 编写 Makefifile ,完成第一步项目构建

第五步 . 实现游戏落子逻辑

第六步 . 实现游戏的显示逻辑

第七步 . 判定五子连珠然后判定游戏结果

3.联动模块组合封装:

完整代码:

(1)game.c


       
  1. #include "game.h"
  2. /*说明:
  3. 1.定义全局变量,保证代码逻辑尽可能简化,传参不那么复杂
  4. 2.代表玩家输入位置的最近的位置下标
  5. 3.不是说一定要定义成全局变量,而是为了减少传参等,降低复杂度*/
  6. int x = 0;
  7. int y = 0;
  8. //按照x,y作为起点,按照特定的方向,求连续相对的最大格式
  9. int ChessCount(int board[][COL], int row, int col, enum Dir d)
  10. {
  11. int _x = x - 1; //棋盘行标从1开始
  12. int _y = y - 1; //棋盘列标从1开始
  13. //统计以当前棋子为起始位置8个方向的棋子数
  14. int count = 0;
  15. while ( 1) {
  16. switch (d) {
  17. case LEFT:
  18. _y--;
  19. break;
  20. case RIGHT:
  21. _y++;
  22. break;
  23. case UP:
  24. _x--;
  25. break;
  26. case DOWN:
  27. _x++;
  28. break;
  29. case LEFT_UP:
  30. _x--, _y--;
  31. break;
  32. case LEFT_DOWN:
  33. _x++, _y--;
  34. break;
  35. case RIGHT_UP:
  36. _x--, _y++;
  37. break;
  38. case RIGHT_DOWN:
  39. _x++, _y++;
  40. break;
  41. default:
  42. //Do Nothing
  43. break;
  44. }
  45. //坐标移动完成,一定要先保证没有越界
  46. if (_x < 0 || _x > row - 1 || _y < 0 || _y > col - 1) {
  47. break;
  48. }
  49. //合法
  50. //判断指定位置和原始位置的棋子是否相同,“连珠”就体现在这里
  51. if (board[x - 1][y - 1] == board[_x][_y]) {
  52. count++;
  53. }
  54. else {
  55. break;
  56. }
  57. }
  58. return count;
  59. }
  60. /*说明:IsOver的返回值有4种情况
  61. 1.NEXT:表明要继续
  62. 2.PLAYER1_WIN: 玩家1获胜
  63. 3.PLAYER2_WIN:玩家2获胜
  64. 4.DRAW: 平局*/
  65. int IsOver(int board[][COL], int row, int col)
  66. {
  67. /*棋子统计逻辑
  68. 1.以落子位置作为起点,进行8个方向的相同玩家方棋子统计
  69. 落子位置下标与方向统计有很强的关联性
  70. 2.当玩家2落子,说明玩家1没有赢,因此需要先走一步就先判断一次,走到当前才有可能赢
  71. 3.+1是统计被忽略的当前落子*/
  72. int count1 = ChessCount(board, row, col, LEFT) + ChessCount(board, row, col, RIGHT) + 1;
  73. int count2 = ChessCount(board, row, col, UP) + ChessCount(board, row, col, DOWN) + 1;
  74. int count3 = ChessCount(board, row, col, LEFT_UP) + ChessCount(board, row, col, RIGHT_DOWN) + 1;
  75. int count4 = ChessCount(board, row, col, LEFT_DOWN) + ChessCount(board, row, col, RIGHT_UP) + 1;
  76. //判断五子连珠
  77. if (count1 >= 5 || count2 >= 5 || count3 >= 5 || count4 >= 5) {
  78. //有五子连珠,则一定有人赢
  79. //x, y保存的是玩家最近一次落子
  80. if (board[x - 1][y - 1] == PLAYER1) {
  81. return PLAYER1_WIN;
  82. }
  83. else {
  84. return PLAYER2_WIN;
  85. }
  86. //这个if-else语句可换成 return board[x-1][y-1];
  87. }
  88. int i = 0;
  89. for (; i < row; i++) {
  90. int j = 0;
  91. for (; j < col; j++) {
  92. if (board[i][j] == 0) {
  93. return NEXT;
  94. }
  95. }
  96. }
  97. return DRAW;
  98. }
  99. void ShowBoard(int board[][COL], int row, int col)
  100. {
  101. //C语言清屏,减少每次棋盘打印,实现原地刷新效果
  102. printf( "\e[1;1H\e[2J"); //Linux环境下,C语言实现清屏
  103. printf( "\n");
  104. //将数组内容,进行可视化
  105. printf( " "); //优化棋盘使其美观
  106. int i = 1;
  107. for (; i <= col; i++) {
  108. printf( "%3d", i);
  109. }
  110. printf( "\n");
  111. for (i = 0; i < row; i++) {
  112. int j = 0;
  113. printf( "%2d ", i + 1);
  114. for (; j < col; j++) {
  115. if (board[i][j] == 0) {
  116. printf( " . ");
  117. }
  118. else if (board[i][j] == PLAYER1) {
  119. printf( "● ");
  120. }
  121. else {
  122. printf( "○ ");
  123. }
  124. }
  125. printf( "\n");
  126. }
  127. }
  128. void PlayerMove(int board[][COL], int row, int col, int who)
  129. {
  130. //判断玩家的落子位置:合法性、去重
  131. while ( 1) {
  132. printf( "玩家[%d] 请输入你的落子坐标: ", who);
  133. scanf( "%d %d", &x, &y);
  134. //判断输入坐标的合法性,行列
  135. if (x < 1 || x > row || y < 1 || y > col) {
  136. printf( "位置错误!\n");
  137. continue;
  138. }
  139. //在数组当当中的下标值
  140. else if (board[x - 1][y - 1] != 0) {
  141. printf( "此位置已被占用!\n");
  142. continue;
  143. }
  144. else {
  145. //合法性,去重
  146. board[x - 1][y - 1] = who;
  147. break;
  148. }
  149. }
  150. }
  151. void Game()
  152. {
  153. int board[ROW][COL]; //在函数中定义数组,其实是一个随机数,需要清空以确定坐标数
  154. /*1.采用ROW,COL型的二维数组,进行游戏信息的保存
  155. 2.全部清空有许多种做法:采用双循环,采用Init函数等
  156. 3.注:memset按照字节为单位操作,menset内存设置
  157. 4.使用memset清空,默认棋盘数据都是0*/
  158. memset(board, 0, sizeof(board));
  159. int result = NEXT;
  160. do {
  161. ShowBoard(board, ROW, COL); //显示棋盘
  162. PlayerMove(board, ROW, COL, PLAYER1); //玩家1先走,也可以随机选择先走——“先手”
  163. result = IsOver(board, ROW, COL); //判断游戏是否结束
  164. if (NEXT != result) {
  165. break;
  166. }
  167. ShowBoard(board, ROW, COL);
  168. PlayerMove(board, ROW, COL, PLAYER2);
  169. result = IsOver(board, ROW, COL);
  170. if (NEXT != result) {
  171. break;
  172. }
  173. } while ( 1);
  174. //Player1 win, Player2 win, Draw
  175. switch (result) {
  176. case PLAYER1_WIN:
  177. printf( "恭喜玩家1获胜!\n");
  178. break;
  179. case PLAYER2_WIN:
  180. printf( "恭喜玩家2获胜!\n");
  181. break;
  182. case DRAW:
  183. printf( "游戏平局,和气生财!\n");
  184. break;
  185. default:
  186. //这种情况不会出现,do nothing!
  187. break;
  188. }
  189. //ShowBoard(board, ROW, COL);
  190. }

(2)main.c


       
  1. #include "game.h"
  2. //游戏菜单
  3. void Menu()
  4. {
  5. printf( "############################\n");
  6. printf( "## 1. 开始 0. 退出 ##\n");
  7. printf( "############################\n");
  8. printf( " ————By 作者:新晓·故知\n");
  9. printf( "请选择:");
  10. }
  11. int main()
  12. {
  13. int quit = 0; //用来判断游戏是否退出
  14. int select = 0;
  15. //使得游戏可以多次进行
  16. while (!quit) {
  17. Menu(); //游戏菜单
  18. scanf( "%d", &select);
  19. switch (select) { //根据用户选择,执行合适的游戏逻辑代码
  20. case 1:
  21. Game();
  22. break;
  23. case 0:
  24. quit = 1;
  25. printf( "退出游戏,再见!\n");
  26. break;
  27. default:
  28. printf( "输入错误,请重新输入!\n");
  29. break;
  30. }
  31. }
  32. return 0;
  33. }

(3)game.h


       
  1. #pragma once
  2. #include <stdio.h>
  3. #include <string.h>
  4. //增强代码可维护性
  5. #define ROW 10
  6. #define COL 10
  7. #define PLAYER1 1 //玩家1编号,默认棋盘数据是0,玩家1落子,该位置被改成1
  8. #define PLAYER2 2 //玩家2编号,默认棋盘数据是0,玩家2落子,该位置被改成2
  9. #define NEXT 0 //游戏继续
  10. #define PLAYER1_WIN 1
  11. #define PLAYER2_WIN 2
  12. #define DRAW 3
  13. //使用枚举,代表方向
  14. enum Dir {
  15. LEFT,
  16. RIGHT,
  17. UP,
  18. DOWN,
  19. LEFT_UP,
  20. LEFT_DOWN,
  21. RIGHT_UP,
  22. RIGHT_DOWN
  23. };
  24. void Menu(); //菜单功能声明
  25. void Game(); //游戏功能声明

(4)Makefile


       
  1. game:game.c main.c ProcBar.c
  2. gcc $^ -o $@
  3. .PHONY:clean
  4. clean:
  5. rm -f game

 五、程序调试:

1.程序界面及说明:

(1)进入游戏主程序 

(2)默认玩家1为先手

 (3)输入玩家坐标

2.测试用例:

(1)输入错误坐标测试:

(2)输入已被占用坐标测试

(3)实现“五子连珠”判断:

六、程序功能扩展:

1.扩展思路:

1.引入进度条

2.尝试人机对战
3.功能扩展:颜色提示,步数记录,先手随机交换等
4.网络版本
5. 将走过的步骤保存在文件里,实现自动下棋,以及扩展至数据库

6.nucurses实现鼠标点击

等等

2.扩展实现:

引入进度条:

(1)更改代码:

Makefile: 


   
  1. game:game.c main.c ProcBar.c
  2. gcc $^ -o $@
  3. .PHONY : clean
  4. clean :
  5. rm - f game

main.c:


   
  1. #include "game.h"
  2. #include "ProcBar.h"
  3. //游戏菜单
  4. void Menu()
  5. {
  6. printf( "############################\n");
  7. printf( "## 1. 开始 0. 退出 ##\n");
  8. printf( "############################\n");
  9. printf( "请选择:");
  10. }

(2)扩展效果演示:

七、项目总结:

1.核心逻辑:

1.基于二维数组保存数据信息

2.将二维数组可视化

3.每次落子存入数据都需要进行更新可视化

4.落子和判断是强相关的,换句话说,玩家1落子的时候说明上一步玩家二没有赢,每个玩家落子,立即判断输赢

2.项目体现思想:

从五子棋项目中体现出对大型项目先进行顶层逻辑设计,再将任务模块化,最后联动组合封装优化。

八、项目改编:

将Linux环境下的C语言项目改编在Windows环境下,实现运行!借助C语言的跨平台性,进行跨平台改动。

后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
                                                                                    ——By 作者:新晓·故知


转载:https://blog.csdn.net/m0_57859086/article/details/125580515
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场