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.分别创建子文件:

touch game.htouch game.ctouch 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

#include "game.h"/*说明:1.定义全局变量,保证代码逻辑尽可能简化,传参不那么复杂2.代表玩家输入位置的最近的位置下标3.不是说一定要定义成全局变量,而是为了减少传参等,降低复杂度*/int x = 0;int y = 0;//按照x,y作为起点,按照特定的方向,求连续相对的最大格式int ChessCount(int board[][COL], int row, int col, enum Dir d){int _x = x - 1; //棋盘行标从1开始int _y = y - 1; //棋盘列标从1开始//统计以当前棋子为起始位置8个方向的棋子数int count = 0;while (1) {switch (d) {case LEFT:_y--;break;case RIGHT:_y++;break;case UP:_x--;break;case DOWN:_x++;break;case LEFT_UP:_x--, _y--;break;case LEFT_DOWN:_x++, _y--;break;case RIGHT_UP:_x--, _y++;break;case RIGHT_DOWN:_x++, _y++;break;default://Do Nothingbreak;}//坐标移动完成,一定要先保证没有越界if (_x  row - 1 || _y  col - 1) {break;}//合法//判断指定位置和原始位置的棋子是否相同,“连珠”就体现在这里if (board[x - 1][y - 1] == board[_x][_y]) {count++;}else {break;}}return count;}/*说明:IsOver的返回值有4种情况1.NEXT:表明要继续2.PLAYER1_WIN: 玩家1获胜3.PLAYER2_WIN:玩家2获胜4.DRAW: 平局*/int IsOver(int board[][COL], int row, int col){/*棋子统计逻辑1.以落子位置作为起点,进行8个方向的相同玩家方棋子统计落子位置下标与方向统计有很强的关联性2.当玩家2落子,说明玩家1没有赢,因此需要先走一步就先判断一次,走到当前才有可能赢3.+1是统计被忽略的当前落子*/int count1 = ChessCount(board, row, col, LEFT) + ChessCount(board, row, col, RIGHT) + 1; int count2 = ChessCount(board, row, col, UP) + ChessCount(board, row, col, DOWN) + 1; int count3 = ChessCount(board, row, col, LEFT_UP) + ChessCount(board, row, col, RIGHT_DOWN) + 1; int count4 = ChessCount(board, row, col, LEFT_DOWN) + ChessCount(board, row, col, RIGHT_UP) + 1; //判断五子连珠 if (count1 >= 5 || count2 >= 5 || count3 >= 5 || count4 >= 5) {//有五子连珠,则一定有人赢//x, y保存的是玩家最近一次落子if (board[x - 1][y - 1] == PLAYER1) {return PLAYER1_WIN;}else {return PLAYER2_WIN;}//这个if-else语句可换成 return board[x-1][y-1];}int i = 0;for (; i < row; i++) {int j = 0;for (; j < col; j++) {if (board[i][j] == 0) {return NEXT;}}}return DRAW;}void ShowBoard(int board[][COL], int row, int col){ //C语言清屏,减少每次棋盘打印,实现原地刷新效果 printf("\e[1;1H\e[2J");//Linux环境下,C语言实现清屏printf("\n");//将数组内容,进行可视化printf(""); //优化棋盘使其美观int i = 1;for (; i <= col; i++) {printf("%3d", i);}printf("\n");for (i = 0; i < row; i++) {int j = 0;printf("%2d ", i + 1);for (; j < col; j++) {if (board[i][j] == 0) {printf(" . ");}else if (board[i][j] == PLAYER1) {printf("● ");}else {printf("○ ");}}printf("\n");}}void PlayerMove(int board[][COL], int row, int col, int who){//判断玩家的落子位置:合法性、去重while (1) {printf("玩家[%d] 请输入你的落子坐标: ", who);scanf("%d %d", &x, &y);//判断输入坐标的合法性,行列if (x  row || y  col) {printf("位置错误!\n");continue;}//在数组当当中的下标值else if (board[x - 1][y - 1] != 0) {printf("此位置已被占用!\n");continue;}else {//合法性,去重board[x - 1][y - 1] = who;break;}}}void Game(){ int board[ROW][COL]; //在函数中定义数组,其实是一个随机数,需要清空以确定坐标数/*1.采用ROW,COL型的二维数组,进行游戏信息的保存2.全部清空有许多种做法:采用双循环,采用Init函数等3.注:memset按照字节为单位操作,menset内存设置4.使用memset清空,默认棋盘数据都是0*/memset(board, 0, sizeof(board));int result = NEXT;do {ShowBoard(board, ROW, COL);//显示棋盘PlayerMove(board, ROW, COL, PLAYER1);//玩家1先走,也可以随机选择先走——“先手”result = IsOver(board, ROW, COL);//判断游戏是否结束if (NEXT != result) {break;}ShowBoard(board, ROW, COL);PlayerMove(board, ROW, COL, PLAYER2);result = IsOver(board, ROW, COL);if (NEXT != result) {break;}} while (1);//Player1 win, Player2 win, Drawswitch (result) { case PLAYER1_WIN:printf("恭喜玩家1获胜!\n");break; case PLAYER2_WIN:printf("恭喜玩家2获胜!\n"); break; case DRAW:printf("游戏平局,和气生财!\n"); break; default://这种情况不会出现,do nothing!break;}//ShowBoard(board, ROW, COL);}

(2)main.c

#include "game.h"//游戏菜单void Menu(){printf("############################\n");printf("## 1. 开始0. 退出 ##\n");printf("############################\n");printf(" ————By 作者:新晓·故知\n");printf("请选择:");}int main(){int quit = 0; //用来判断游戏是否退出int select = 0;//使得游戏可以多次进行while (!quit) {Menu();//游戏菜单scanf("%d", &select);switch (select) {//根据用户选择,执行合适的游戏逻辑代码case 1:Game();break;case 0:quit = 1;printf("退出游戏,再见!\n");break;default:printf("输入错误,请重新输入!\n");break;}}return 0;}

(3)game.h

#pragma once#include #include //增强代码可维护性#define ROW 10#define COL 10#define PLAYER1 1//玩家1编号,默认棋盘数据是0,玩家1落子,该位置被改成1#define PLAYER2 2//玩家2编号,默认棋盘数据是0,玩家2落子,该位置被改成2#define NEXT0 //游戏继续#define PLAYER1_WIN 1 #define PLAYER2_WIN 2#define DRAW3//使用枚举,代表方向enum Dir {LEFT,RIGHT,UP,DOWN,LEFT_UP,LEFT_DOWN,RIGHT_UP,RIGHT_DOWN};void Menu(); //菜单功能声明void Game(); //游戏功能声明

(4)Makefile

game:game.c main.c ProcBar.c gcc $^ -o $@.PHONY:clean clean:rm -f game

 五、程序调试:

1.程序界面及说明:

(1)进入游戏主程序 

(2)默认玩家1为先手

 (3)输入玩家坐标

2.测试用例:

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

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

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

六、程序功能扩展:

1.扩展思路:

1.引入进度条

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

6.nucurses实现鼠标点击

等等

2.扩展实现:

引入进度条:

(1)更改代码:

Makefile: 

game:game.c main.c ProcBar.c gcc $^ -o $@.PHONY : cleanclean :rm - f game

main.c:

#include "game.h"#include "ProcBar.h"//游戏菜单void Menu(){printf("############################\n");printf("## 1. 开始0. 退出 ##\n");printf("############################\n");printf("请选择:");}

(2)扩展效果演示:

七、项目总结:

1.核心逻辑:

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

2.将二维数组可视化

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

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

2.项目体现思想:

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

八、项目改编:

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

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