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


    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 作者:新晓·故知

  • 相关阅读:
    读取resources 目录资源文件的方法
    MySQL—优化数据库
    数字孪生与GIS数据为何高度互补?二者融合后能达到什么样的效果?
    C# 串口通信简单示例
    Monkey测试
    华为机试真题 C++ 实现【处理器问题】【2022.11 Q4 新题】
    浏览器扩展V3开发系列之 chrome.commands 快捷键的用法和案例
    基于uniapp的商城外卖小程序
    LIO-SAM源码解析(二):代码结构
    MySQL主从复制原理图
  • 原文地址:https://blog.csdn.net/m0_57859086/article/details/125580515