三子棋简单实现---C语言

三子棋简单实现—C语言

我简单的介绍一下三子棋,只讲思路,当然也附带简单的代码实现。

一、思路分析

三子棋总体思路很简单,判定逻辑也不难,需要实现的关键点大致有三个。

  1. 棋盘的显示

    如果用字符界面实现,那么非常简单。

    • 比如,我们可以用字符 * 作为顶点,字符 - 作为上下边框,字符 | 作为左右边框,然后用 空格 或者 字符 - 填充棋子所在位置,我更倾向于第一种,看个人偏好。
    *---*---*---*   *---*---*---*
    | - | - | - |   |   |   |   |
    *---*---*---*   *---*---*---*
    | - | - | - |   |   |   |   |
    *---*---*---*   *---*---*---*
    | - | - | - |   |   |   |   |
    *---*---*---*   *---*---*---*
    

    因为棋子的字符会变动,所以我们用一个字符数组存储这 9 个字符。

    • 调用 printf 时格式化输出这些字符,并在原来位置上的 空格 或者 - 的填充物,换成 %c,并对应上数组中各个元素,我们可以打印多行,每一行后面用 \n 换行。

    由于只需要九个棋子,玩家可以用序号 1-9 标识这些位置,但我们实际对棋子进行操作推荐统一用 0-8 的下标,即对用户输入的序号的数值-1。

  2. 棋子的放置

    在第一个关键点中我们用字符数组存储了棋子所代表的字符,那么棋子的放置需要注意什么。

    • 玩家选择放置位置时,玩家只能输入 1-9 的序号,不能超出该范围。

    • 系统放置棋子前,应当检测玩家选择的位置是否已放置有棋子,即是否为默认填充字符,为默认填充字符才可放置,否则请用户重新选择。

    • 系统放置棋子时,也就是给字符数组中对应的棋子赋值,比如我们用棋子 OX 表示棋子,那么就用这两个字符赋值。

  3. 游戏的判定

    游戏判定也就是对棋子获胜的判定,相对前面两点比较难一些,但思路清晰就不难,三子棋需要判定三子连珠,其实也就是一条线上的所有棋子。

    我们可以梳理出以下几点:

    1. 棋子的标识为下标 0-8 。

    2. 棋子的判定中心为当前放置成功的下标。

    3. 棋子的判定如下,满足一条,那么该棋子获胜:

      • 同行的棋子相同。

      • 同列的棋子相同。

      • 主对角线(左上角到右下角)的棋子相同。

      • 次对角线(右上角到左下角)的棋子相同。

    4. 棋子布满棋盘,那么游戏结束。

    棋子的判定,最少需要判定两个方向(在十字线内,除了棋盘中心),最多四个方向(棋盘中心),其中行、列方向的是必须进行判定的。

二、粗略实现

思路分析只需要有想法,但粗略实现就需要结合编程了。

我们接下来使用 C 语言进行实现,采用分文件编写的方法,将具体实现拆分成各个模块,方便维护。

  • 各个模块里有着对应职责的函数,比如一个模块的函数声明写在头文件里,而函数实现写在与头文件同名的源文件里,方便区分。

  • 头文件里应该用这样的预处理语句包裹起来,避免头文件的重复包含,重复包含会导致函数的重复声明。

    /* test.h */
    
    #ifndef 
    #define 
    //这个宏通常命名为 _大写的文件名_H_
    //如文件名为 test.h,该宏为 _TEST_H_
    
    //这里写上所有的函数声明
    #endif
    
  • 源文件里应该用双引号包含对应的头文件,且与头文件同名,对应函数声明写下函数实现。

    /* test.c */
    
    #include "test.h"
    //那么该文件名应为 test.c
    //这里写上所有的函数实现
    
  • 如果用到了该模块对应的函数,编译的时候要将对应的源文件一起编译。

打印棋盘、放置棋子、棋子判定分别写成单独的函数实现,写到棋盘模块里。

棋盘实现和游戏实现分开编写,互相都不知道对方的存在,互不依赖,最后由主函数调用它们的函数。我之前写的第一个版本就互相包含了对方的头文件,尽量避免这样做。

棋盘的实现分析了三点,那么粗略实现也可以分成三点关键点。

  1. 棋盘实现

    棋盘所做的就是打印边框和棋子,所以不需要额外存储这些字符,初始化棋盘也就是用特定字符填充棋子数组。

    • 初始化棋盘
    • 打印棋盘

    棋盘中的棋子用字符数组存储,将作用域限定在该棋盘实现内部,只提供对外的函数接口。

    • 获取指定位置棋子
    • 指定位置放置棋子

    判定有棋子下标合法判定、棋子获胜判定。

    • 当前下标是否有棋子
    • 当前下标对应棋子是否获胜
  2. 游戏实现

    玩家信息作用域限定在内部,初始化游戏也就是记录玩家和电脑的棋子和先手信息。

    • 初始化游戏
    • 获取选择

    放置位置需要获取玩家放置位置、获取电脑放置位置,这两个函数都限定在内部,并提供一个函数开放给外部使用统一返回放置位置。

    • 获取玩家选择位置
    • 获取电脑选择位置
    • 获取选择位置

    玩家切换可以灵活的切换玩家和电脑的模式,如果当前选手是玩家,上面的函数就调用获取玩家选择,否则就获取电脑选择,然后返回选择信息。

    • 获取当前选手棋子
    • 切换到下一位选手

    最后是返回获胜的选手。

    • 返回获胜选手信息
  3. 主文件

    主菜单提供给玩家选择,比如开始游戏、退出选项。

    • 打印主菜单

    游戏开始便调用相关函数。

    • 初始化棋盘
    • 初始化游戏
    • 循环执行,条件为真
      • 打印棋盘
      • 获取选择
      • 判定该位置是否有棋子,有便重新获取选择
      • 获取指定位置棋子
      • 切换到下一位选手
      • 放置棋子
      • 判定该位置棋子是否胜利,胜利或棋盘已满跳出循环
    • 游戏结束

三、具体实现

  1. 棋盘实现

    • 变量定义
      • 棋子数
      • 棋子字符数组
      • 棋子默认填充
    • 初始化棋盘
      • 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。
  2. 游戏实现

    • 变量定义
      • 当前棋子
      • 玩家棋子
      • 电脑棋子
    • 初始化游戏
      • void initalizeGame(char firstChequer,char secondChequer);
      • 传入两个字符。
      • 这两个字符作为玩家选择的棋子。
      • 获取选手选择,设置好玩家棋子为选择的字符,电脑棋子设置为另一个字符,存储在内部,将当前棋子设置为先手的那个选手的棋子,产生随机数种子。
    • 获取选手选择
      • int playChess();
      • 判定当前棋子是否为玩家棋子,若为真,获取玩家放置位置,否则获取电脑放置位置。
      • 返回获取的放置位置
    • 获取玩家放置位置
      • static int getPlayerOption();
      • 等待玩家输入放置位置。
      • 返回放置位置
    • 获取电脑放置位置
      • static int getComputerOption();
      • 对 9 取余随机产生一个位置,或者根据玩家上次放置位置来决定。
      • 返回放置位置。
    • 获取当前选手的棋子
      • char getCurrChequer();
      • 返回当前选手棋子的字符
    • 切换到下一位玩家
      • void switchToNextPlayer();
      • 若当前选手为玩家,当前棋子置为电脑的字符,否则置为玩家的字符。
    • 判定游戏获胜者
      • const char* getWinner(char chequer);
      • 传入字符
      • 若为玩家对应的棋子,返回玩家获胜信息。
      • 若为电脑对应的棋子,返回电脑获胜信息。
      • 若都不是,返回无人获胜信息。
  3. 主文件

    • 打印菜单
      • 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值