抽丝剥茧C语言(中阶)三子棋(上)

简介: 抽丝剥茧C语言(中阶)三子棋

导语

我们参照平时玩的三子棋来逐步分析,然后用C语言分布实现。

这里分模块写。

1. 分析三子棋游戏的游戏逻辑

1.1 棋盘

三子棋,我们平时在纸上就可以玩,画出一个#一样的棋盘就可以了,就像这样。

1.2 游戏规则

玩家1和玩家2,其中一名玩家先下棋,然后是另一名玩家下棋,以此循环,直到游戏结束位置(一个位置不能重复落子)。

只要三个相同的棋子连成一条线就可以获得胜利,包括横着,竖着,斜着。

还有一种就是平局,棋盘放满了,但是没有任何一方胜利。

2. C语言实现游戏菜单。

还有一点我们考虑的就是,游戏菜单,而且我们需要那种可以反复玩,然后开始游戏和退出游戏的功能。

那么,首先考虑的就是需要一个输出函数,因为需要选择开始游戏和结束游戏,还需要写一个菜单,并且我们需要用循环来重复这些动作,因为至少要进行打印菜单一次,但是完成游戏之后还不能不打印菜单,也就是说游戏菜单也要循环。

首先我们创建一个后缀为.c的文件(源文件),这个文件里面放main函数的主体,也就是整个代码的入口。

我们假设,打印游戏菜单后,输入1是开始游戏,输入0是游戏结束,如果既不是1也不是0,那么提示输入错误,请重新输入。

参考代码如下:

#include <stdio.h>
void menu()//游戏菜单主体
{
  printf("************************\n");
  printf("******1.三子棋游戏******\n");
  printf("******0.游戏结束  ******\n");
  printf("************************\n");
}
int main()
{
  int option = 0;
  do
  {
    menu();//调用菜单
    scanf("%d", &option);//输入选项
    if (option == 1)
    {
      printf("游戏开始\n");
    }
    else if (option == 0)
    {
      printf("游戏结束\n");
      break;
    }
    else
    {
      printf("选择错误,请重新选择\n");
    }
  } while (1);//一直循环,玩完一局可以继续选择玩或者是不玩
  return 0;
}

我们先运行一下看看,代码最好是完成一个逻辑就运行一下,不然这里出现一个BUG,一会你写完另一个逻辑出现一个BUG,到时积累的BUG越来越多,你调试起来很麻烦。

代码运行结果我们来看一下:

代码没问题。

3. 三子棋游戏主体——棋盘

我们在输入1的if语句里面实现游戏逻辑,但是这是一个庞大的工程,写上去之后会让你的代码看起来杂乱无章。

所以用自定义函数来实现更好。

这里我们要创建一个game的源文件和game.h(头文件)。

我们首先需要打印出来一个棋盘,然后在里面才能下棋。

打印棋盘是一个#,并且还需要向里面放元素,那么只能是数组了,一维数组操作起来应该非常繁琐,所以我们用二维数组。

先定义一个二维数组:

char chessboard[3][3];//先定义字符数组

因为我们落棋的地方是在空白处,所以我们初始化棋盘,让落子的地方变成空白。

先说一下game.c的文件里需要实现游戏逻辑主体,game.h是声明函数和添加头文件的地方。

首先我们考虑一下,棋盘如果默认成3是不是等于棋盘恒定是3了,想更改很困难,能不能换个更好的写法?

这里可以在game.h的文件里用define定义常量标识符:

#define ROW 3//如果想改变行和列,只需要改变这个数字就可以了
#define COL 3

然后数组就可以这样了:

char chessboard[ROW][COL];

可是这里我们发现编译器同不过去,因为并不是在同一个文件里,这时在test.c的引用头文件的地方引用一下你自己创建的头文件就可以了

#include “game.h”

至于原来test.c文件里面的引用库函数的头文件,可以给放到game.h里面,这样在test.c文件里面也可以用库函数printf了,如果你需要引用库函数,那么直接在game.h里引用就可以了,这样就没有那么麻烦了,也提高了这个工程的可读性,这里顺便在game.c文件里也引用一下自定义的game.h。

现在代码是这样的:

//test.c文件
#include "game.h"
void menu()
{
  printf("************************\n");
  printf("******1.三子棋游戏******\n");
  printf("******0.游戏结束  ******\n");
  printf("************************\n");
}
void game()
{
  char chessboard[ROW][COL];
}
int main()
{
  int option = 0;
  do
  {
    menu();
    scanf("%d", &option);
    if (option == 1)
    {
      printf("游戏开始\n");
      game();
    }
    else if (option == 0)
    {
      printf("游戏结束\n");
      break;
    }
    else
    {
      printf("选择错误,请重新选择\n");
    }
  } while (1);
  return 0;
}
//game.h文件
#define ROW 3
#define COL 3
#include <stdio.h>
//game.c文件
#include "game.h"

我们发现test.c文件里game函数里面有一个数组,之前说了,要分模块写,game.c文件按是实现游戏逻辑,因为函数有外部链接属性,那么game这个函数里面还能放进去函数。

那么首先实现棋盘的初始化。

因为我们要实现3*3的棋盘,所以ROW定义为3,COL定义为3。

//test.c文件
void game()
{
  char arr[ROW][COL];//棋盘
  chessboard(arr,ROW, COL);//初始化棋盘
}

函数的声明是这样的:

void chessboard(char arr[ROW][COL], int row, int col);

函数的实现是这样的:

void chessboard(char arr[ROW][COL], int row, int col)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      arr[i][j] = ' ';//给arr这个二维数组赋值空格
    }
  }
}

我们如果不给赋值,那么打印出来的不知道是什么了。

这里我么你还需要一个打印棋盘的函数:

//test.c文件
void game()
{
  char arr[ROW][COL];//棋盘
  chessboard(arr,ROW, COL);//初始化棋盘
  print_chessboard(arr, ROW, COL);//打印棋盘
}
//game.h文件
#define ROW 3
#define COL 3
#include <stdio.h>
void chessboard(char arr[ROW][COL], int row, int col);//初始化棋盘
void print_chessboard(char arr[ROW][COL], int row, int col);//打印棋盘
//game.c文件
void print_chessboard(char arr[ROW][COL], int row, int col)
{
  int i = 0;
  int j = 0;
  int a = 0;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      printf(" %c ", arr[i][j]);//打印棋盘空格
      if (j < col - 1)
        printf("|");//打印棋盘分割线的列
    }
    printf("\n");
    if (i < row - 1) 
    {
      for (a = 0; a < col; a++)
      {
        printf("---");//打印棋盘分割线的行
        if (a < col - 1)
          printf("|");//打印棋盘分割线的列
      }
    }
    printf("\n");
  }
}

我们打印是这个样子了:

棋盘的初始化和打印棋盘的函数就完成了,下面来实现游戏逻辑。

4. 三子棋游戏主体——落子

我们先不考虑谁输谁赢。

这里实现我们玩家1VS玩家2的逻辑,不过这里我们就用玩家VS电脑吧,虽然电脑下棋没有任何的逻辑。

我们规定,玩家落子为 *,电脑落子为#,玩家先手。

首先我们考虑,玩家先落子,那么玩家的函数在电脑函数的上面,并且还要继续打印棋盘,要让玩家看到棋盘才可以,因为不可能落子一次,所以这是个循环。

实现玩家的逻辑

//test.c文件
void game()
{
  char arr[ROW][COL];//棋盘
  chessboard(arr,ROW, COL);//初始化棋盘
  print_chessboard(arr, ROW, COL);//打印棋盘
  while (1) 
  {
    player(arr, ROW, COL);//玩家走
    print_chessboard(arr, ROW, COL);//打印棋盘
    computer(arr, ROW, COL);//电脑走
    print_chessboard(arr, ROW, COL);//打印棋盘
  }
}

那么我们怎么实现落子的逻辑呢,当然是替换二维数组中的空格。

还有一点要注意,落子的地方要合法,只能在规定的范围内,落子不能是重复位置,不能越界。

玩家的落子函数电泳,声明,主体:

//test.c文件
void game()
{
  char arr[ROW][COL];//棋盘
  chessboard(arr,ROW, COL);//初始化棋盘
  print_chessboard(arr, ROW, COL);//打印棋盘
  while (1) 
  {
    player(arr, ROW, COL);//玩家走
    print_chessboard(arr, ROW, COL);//打印棋盘
    computer(arr, ROW, COL);//电脑走
    print_chessboard(arr, ROW, COL);//打印棋盘
  }
}
//game.h文件
#define ROW 3
#define COL 3
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void chessboard(char arr[ROW][COL], int row, int col);//初始化棋盘
void print_chessboard(char arr[ROW][COL], int row, int col);//打印棋盘
void player(char arr[ROW][COL], int row, int col);//玩家走
void computer(char arr[ROW][COL], int row, int col);//电脑走
//game.c文件
void player(char arr[ROW][COL], int row, int col)
{
  int i, j;
  printf("玩家下棋\n");
  while (1)
  {
    printf("请输入坐标>");
    scanf("%d %d", &i, &j);
    if (i >= 1 && i <= row && j >= 1 && j <= col)//判断是否坐标合法
    {
      if (arr[i - 1][j - 1] == ' ')//判断位置是否有棋子
      {
        arr[i - 1][j - 1] = '*';//玩家落子
        break;
      }
      else
      {
        printf("坐标已被占用,请重新输入\n");
      }
    }
    else
    {
      printf("坐标非法请重新输入\n");
    }
  }
}

我们因为是玩家玩,让你输入坐标,有些玩家不一定知道是二维数组的下标是从零开始,所以我们就要做到输入1 1,棋子落到二维数组arr的[0][0]位置,所以我们调整了j和i的范围。

逻辑就是先输入坐标,然后判断是否合法,最后进行落子。

实现一下电脑的逻辑。

我们如果想让电脑非常的智能,那需要大量的精力,这里只是做一下简单的三子棋游戏,并不需要很繁琐的算法,只需要生成随机坐标即可。

说到随即坐标,大家也都想到了随机数,也就是说要用到rand和time这两个函数。

那么我们直接判断落子的地方是否有棋子就好了,因为生成的随机数被%3就会生成0到2的余数,不用像玩家一样繁琐。

电脑落子的主体函数:

//game.c文件
void computer(char arr[ROW][COL], int row, int col)
{
  printf("电脑下棋\n");
  while (1)
  {
    int x = rand() % row;//随机生成1到3的数
    int y = rand() % col;
    if (arr[x][y] == ' ')//判断位置是否有棋子
    {
      arr[x][y] = '#';//电脑落子
      break;
    }
  }
}

我们还有一个地方没看,就是设置随机数的起点:

#include "game.h"
void menu()
{
  printf("************************\n");
  printf("******1.三子棋游戏******\n");
  printf("******0.游戏结束  ******\n");
  printf("************************\n");
}
void game()
{
  char arr[ROW][COL];//棋盘
  chessboard(arr,ROW, COL);//初始化棋盘
  print_chessboard(arr, ROW, COL);//打印棋盘
  while (1) 
  {
    player(arr, ROW, COL);//玩家走
    print_chessboard(arr, ROW, COL);//打印棋盘
    computer(arr, ROW, COL);//电脑走
    print_chessboard(arr, ROW, COL);//打印棋盘
  }
}
int main()
{
  int option = 0;
  srand((unsigned int)time(NULL));//随机数的起点
  do
  {
    menu();
    scanf("%d", &option);
    if (option == 1)
    {
      printf("游戏开始\n");
      game();
    }
    else if (option == 0)
    {
      printf("游戏结束\n");
      break;
    }
    else
    {
      printf("选择错误,请重新选择\n");
    }
  } while (1);
  return 0;
}

让我们看一下效果如何:

最后程序死循环了,因为我们还没有写判断的逻辑。

5. 三子棋游戏主体——判断输赢

我们之前就说过,三子棋的结局有三种,玩家赢,电脑赢,平局。

现在实现一下判断,在我们test.c这个文件里的game函数里调用玩家和电脑落子的函数下方在调用两个判断输赢的函数就好了。

那么利用函数判断输赢,就一定要利用好返回值。

我们规定玩家赢返回 * ,电脑赢返回 # ,平局返回C。

并且,玩家赢了电脑就一定输了,同理电脑也是,那么在判断函数下面判断返回值就可以了,在玩家下面判断一次返回值是否为 * 或者是C,在电脑下面判断是否为 # 或者是C,如果有一个条件达成那么就进入 if 语句之后打印谁输谁赢然后跳出去就可以了,那么我们的game函数也走到了尽头。

//test.c文件
void game()
{
  char arr[ROW][COL];//棋盘
  char c;//储存判断输赢的返回值
  chessboard(arr,ROW, COL);//初始化棋盘
  print_chessboard(arr, ROW, COL);//打印棋盘
  while (1) 
  {
    player(arr, ROW, COL);//玩家走
    print_chessboard(arr, ROW, COL);//打印棋盘
    c = verdict(arr, ROW, COL);//判断玩家输赢
    computer(arr, ROW, COL);//电脑走
    print_chessboard(arr, ROW, COL);//打印棋盘
    c = verdict(arr, ROW, COL);//判断电脑输赢
  }
}
//game.h
#define ROW 3
#define COL 3
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
void chessboard(char arr[ROW][COL], int row, int col);//初始化棋盘
void print_chessboard(char arr[ROW][COL], int row, int col);//打印棋盘
void player(char arr[ROW][COL], int row, int col);//玩家走
void computer(char arr[ROW][COL], int row, int col);//电脑走
cahr verdict(char arr[ROW][COL], int row, int col);//判断输赢

现在实现判断输赢的主体函数。

//game.c文件
int dogfall(char arr[ROW][COL], int row, int col)//判断是否平局的函数
{
  int i, j;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      if (arr[i][j] == ' ')//判断棋盘是否还有空位
        return 0;//如果有返回0
    }
  }
  return 1;//没有返回1
}
char verdict(char arr[ROW][COL], int row, int col)
{
  int i;
  for (i = 0; i < row; i++)
  {
    if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ')//判断一行是否有三个棋子相等
    {
      return arr[i][1];//返回arr[i][1]这个元素
    }
  }
  for (i = 0; i < col; i++)
  {
    if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ')//判断一列是否有三个棋子相等
    {
      return arr[1][i];//返回arr[1][i]这个元素
    }
  }
  if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ')//判断X这种位置是否有三个棋子相同
  {
    return arr[1][1];//这里返回的是arr[1][1]这个元素
  }
  if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')//同上
  {
    return arr[1][1];//同上
  }
  int b = dogfall(arr, row, col);
  if (b == 1)//判断是否相等
    return 'C';
}

我们判断的方式是,一行是否有三个相同的字符,一列是否有三个相同的字符,X的位置是否有三个相同的字符。

这里我们发现,返回值为什么是数组元素里面的内容呢,是因为更加的方便,上面的语句判断成功后,返回三个相连的任意一个地方元素就可以了。

这样就能很快的判断出来谁输谁赢。

那么,还有一种情况就是平局,只需要判断还有没有空格就好了。

上面书写的dogfall函数就数,最后利用返回值来判断是否平局。

相关文章
|
6月前
|
C语言
【海贼王编程冒险 - C语言海上篇】C语言如何实现简单的三子棋游戏?
【海贼王编程冒险 - C语言海上篇】C语言如何实现简单的三子棋游戏?
33 1
|
7月前
|
C语言
万字详解:C语言三子棋进阶 + N子棋递归动态判断输赢(二)
我们可以通过创建并定义符号常量NUMBER,来作为判断是否胜利的标准。如三子棋中,令NUMBER为3,则这八个方向中有任意一个方向达成3子连珠,则连珠的这个棋子所代表的玩家获胜。
84 1
|
7月前
|
算法 C语言 C++
万字详解:C语言三子棋进阶 + N子棋递归动态判断输赢(一)
三子棋游戏设计的核心是对二维数组的把握和运用。
100 1
|
7月前
|
编译器 C语言
【C语言入门小游戏】三子棋
【C语言入门小游戏】三子棋
59 0
【C语言入门小游戏】三子棋
|
6月前
|
存储 C语言
三子棋(C语言版)
三子棋(C语言版)
|
6月前
|
程序员 C语言 索引
【️C语言-游戏设置】---三子棋(N满)
【️C语言-游戏设置】---三子棋(N满)
|
7月前
|
人工智能 算法 数据可视化
C语言”三子棋“升级版(模式选择+”模拟智能“下棋)
C语言”三子棋“升级版(模式选择+”模拟智能“下棋)
|
7月前
|
存储 C语言
C语言实现三子棋
C语言实现三子棋
37 0
|
7月前
|
C语言
C语言实现三子棋
C语言实现三子棋
38 0
|
7月前
|
C语言
C语言:三子棋的实现。
C语言:三子棋的实现。
37 0

相关实验场景

更多