三子棋简单实现—C语言
我简单的介绍一下三子棋,只讲思路,当然也附带简单的代码实现。
一、思路分析
三子棋总体思路很简单,判定逻辑也不难,需要实现的关键点大致有三个。
-
棋盘的显示
如果用字符界面实现,那么非常简单。
- 比如,我们可以用字符
*
作为顶点,字符-
作为上下边框,字符|
作为左右边框,然后用 空格 或者 字符-
填充棋子所在位置,我更倾向于第一种,看个人偏好。
*---*---*---* *---*---*---* | - | - | - | | | | | *---*---*---* *---*---*---* | - | - | - | | | | | *---*---*---* *---*---*---* | - | - | - | | | | | *---*---*---* *---*---*---*
因为棋子的字符会变动,所以我们用一个字符数组存储这 9 个字符。
- 调用
printf
时格式化输出这些字符,并在原来位置上的 空格 或者-
的填充物,换成%c
,并对应上数组中各个元素,我们可以打印多行,每一行后面用\n
换行。
由于只需要九个棋子,玩家可以用序号 1-9 标识这些位置,但我们实际对棋子进行操作推荐统一用 0-8 的下标,即对用户输入的序号的数值-1。
- 比如,我们可以用字符
-
棋子的放置
在第一个关键点中我们用字符数组存储了棋子所代表的字符,那么棋子的放置需要注意什么。
-
玩家选择放置位置时,玩家只能输入 1-9 的序号,不能超出该范围。
-
系统放置棋子前,应当检测玩家选择的位置是否已放置有棋子,即是否为默认填充字符,为默认填充字符才可放置,否则请用户重新选择。
-
系统放置棋子时,也就是给字符数组中对应的棋子赋值,比如我们用棋子
O
、X
表示棋子,那么就用这两个字符赋值。
-
-
游戏的判定
游戏判定也就是对棋子获胜的判定,相对前面两点比较难一些,但思路清晰就不难,三子棋需要判定三子连珠,其实也就是一条线上的所有棋子。
我们可以梳理出以下几点:
-
棋子的标识为下标 0-8 。
-
棋子的判定中心为当前放置成功的下标。
-
棋子的判定如下,满足一条,那么该棋子获胜:
-
同行的棋子相同。
-
同列的棋子相同。
-
主对角线(左上角到右下角)的棋子相同。
-
次对角线(右上角到左下角)的棋子相同。
-
-
棋子布满棋盘,那么游戏结束。
棋子的判定,最少需要判定两个方向(在十字线内,除了棋盘中心),最多四个方向(棋盘中心),其中行、列方向的是必须进行判定的。
-
二、粗略实现
思路分析只需要有想法,但粗略实现就需要结合编程了。
我们接下来使用 C 语言进行实现,采用分文件编写的方法,将具体实现拆分成各个模块,方便维护。
-
各个模块里有着对应职责的函数,比如一个模块的函数声明写在头文件里,而函数实现写在与头文件同名的源文件里,方便区分。
-
头文件里应该用这样的预处理语句包裹起来,避免头文件的重复包含,重复包含会导致函数的重复声明。
/* test.h */ #ifndef 宏 #define 宏 //这个宏通常命名为 _大写的文件名_H_ //如文件名为 test.h,该宏为 _TEST_H_ //这里写上所有的函数声明 #endif
-
源文件里应该用双引号包含对应的头文件,且与头文件同名,对应函数声明写下函数实现。
/* test.c */ #include "test.h" //那么该文件名应为 test.c //这里写上所有的函数实现
-
如果用到了该模块对应的函数,编译的时候要将对应的源文件一起编译。
打印棋盘、放置棋子、棋子判定分别写成单独的函数实现,写到棋盘模块里。
棋盘实现和游戏实现分开编写,互相都不知道对方的存在,互不依赖,最后由主函数调用它们的函数。我之前写的第一个版本就互相包含了对方的头文件,尽量避免这样做。
棋盘的实现分析了三点,那么粗略实现也可以分成三点关键点。
-
棋盘实现
棋盘所做的就是打印边框和棋子,所以不需要额外存储这些字符,初始化棋盘也就是用特定字符填充棋子数组。
- 初始化棋盘
- 打印棋盘
棋盘中的棋子用字符数组存储,将作用域限定在该棋盘实现内部,只提供对外的函数接口。
- 获取指定位置棋子
- 指定位置放置棋子
判定有棋子下标合法判定、棋子获胜判定。
- 当前下标是否有棋子
- 当前下标对应棋子是否获胜
-
游戏实现
玩家信息作用域限定在内部,初始化游戏也就是记录玩家和电脑的棋子和先手信息。
- 初始化游戏
- 获取选择
放置位置需要获取玩家放置位置、获取电脑放置位置,这两个函数都限定在内部,并提供一个函数开放给外部使用统一返回放置位置。
- 获取玩家选择位置
- 获取电脑选择位置
- 获取选择位置
玩家切换可以灵活的切换玩家和电脑的模式,如果当前选手是玩家,上面的函数就调用获取玩家选择,否则就获取电脑选择,然后返回选择信息。
- 获取当前选手棋子
- 切换到下一位选手
最后是返回获胜的选手。
- 返回获胜选手信息
-
主文件
主菜单提供给玩家选择,比如开始游戏、退出选项。
- 打印主菜单
游戏开始便调用相关函数。
- 初始化棋盘
- 初始化游戏
- 循环执行,条件为真
- 打印棋盘
- 获取选择
- 判定该位置是否有棋子,有便重新获取选择
- 获取指定位置棋子
- 切换到下一位选手
- 放置棋子
- 判定该位置棋子是否胜利,胜利或棋盘已满跳出循环
- 游戏结束
三、具体实现
-
棋盘实现
- 变量定义
- 棋子数
- 棋子字符数组
- 棋子默认填充
- 初始化棋盘
initalizeChessBoard(char filler);
- 传入字符 filler。
- 将填充字符置为 filler。
- 填充棋子数组中所有字符为填充字符。
- 打印棋盘
void printChessBoard();
- 打印边框、棋子。
- 获取指定位置的棋子
char getChequer(int pos);
- 传入下标 pos。
- 返回棋子字符数组中下标对应字符。
- 放置棋子
void addChequer(int pos,char chequer);
- 传入下标 pos 和棋子 chequer。
- 在棋子字符串数组中对应下标字符置为传入的棋子。
- 棋子放置的合法性判定
bool isInvaild(int pos);
- 传入下标 pos。
- 判定当前下标范围是否合法,不合法返回 true,合法继续判定。
- 判定对应下标是否有棋子,有棋子返回 true,否则返回 false。
- 判定某个棋子的胜利
int isWin(int pos);
- 传入下标 pos。
- 判定下标对应棋子的字符是否胜利。
- 胜利返回 1,棋盘已满返回 0,未胜利返回 -1。
- 变量定义
-
游戏实现
- 变量定义
- 当前棋子
- 玩家棋子
- 电脑棋子
- 初始化游戏
void initalizeGame(char firstChequer,char secondChequer);
- 传入两个字符。
- 这两个字符作为玩家选择的棋子。
- 获取选手选择,设置好玩家棋子为选择的字符,电脑棋子设置为另一个字符,存储在内部,将当前棋子设置为先手的那个选手的棋子,产生随机数种子。
- 获取选手选择
int playChess();
- 判定当前棋子是否为玩家棋子,若为真,获取玩家放置位置,否则获取电脑放置位置。
- 返回获取的放置位置
- 获取玩家放置位置
static int getPlayerOption();
- 等待玩家输入放置位置。
- 返回放置位置
- 获取电脑放置位置
static int getComputerOption();
- 对 9 取余随机产生一个位置,或者根据玩家上次放置位置来决定。
- 返回放置位置。
- 获取当前选手的棋子
char getCurrChequer();
- 返回当前选手棋子的字符
- 切换到下一位玩家
void switchToNextPlayer();
- 若当前选手为玩家,当前棋子置为电脑的字符,否则置为玩家的字符。
- 判定游戏获胜者
const char* getWinner(char chequer);
- 传入字符
- 若为玩家对应的棋子,返回玩家获胜信息。
- 若为电脑对应的棋子,返回电脑获胜信息。
- 若都不是,返回无人获胜信息。
- 变量定义
-
主文件
- 打印菜单
int printMenu(char* tips,char* options[],unsigned int n);
- 传入标题字符串、菜单项字符串数组,菜单项个数。
- 打印标题,打印所有菜单项,等待用户输入选择。
- 返回用户选择。
- 主函数
- 定义变量
- 填充字符 filler
- 棋子字符 chequer
- 棋子下标 pos
- 判定用户选择。
- 若选择为开始游戏,调用以下函数。
initalizeChessBoard(filler);
initalizeGame('O','X');
printChessBoard();
while(1){
do{
pos=playChess();
}while(isInvaild(pos));
chequer=getCurrChequer();
switchToNextPlayer();
addChequer(pos,chequer);
switch(isWin(pos)){
case -1:
break;
case 1:
printf("%s\n",getWinner(chequer));
return 0;
case 0:
printf("%s\n",getWinner(filler));
return 0;
}
}
- 退出游戏
- 定义变量
- 打印菜单
具体代码在 github 上,感兴趣的可以看看,这里面有一些我用 C 或 C++/Qt写的经典小游戏,思路都很简单。
如果想试试,可以用 git 工具拉取我的代码
git clone https://github.com/azh-1415926/SmallGames.git
目录结构
SmallGames
——ThreeChess
————BoardAction.c
————BoardAction.h
————CMakeLists.txt
————GameControl.c
————GameControl.h
————main.c
————ThreeChess.md
里面有个 CMakeLists.txt,可以用 cmake 来构建编译
# cd .\SmallGames\ThreeChess
# 先切换到 .\SmallGames\ThreeChess 目录下
mkdir build
cd build
cmake ..
make
./threechess
或者
gcc *.c -o threechess
./threechess
小游戏 SmallGames
C 语言三子棋 ThreeChess