C语言实现三子棋,可拓展为n子棋的版本

简介: C语言实现三子棋,可拓展为n子棋的版本

1.运行环境的配置

在这里为什么要分为3个文件呢?

这是因为在一个具体的程序设计中,我们遵循着分而治之的思想,将一个复杂的游戏功能和具体调用的实现具体分为三个文件去实现。

每个文件只需要做好自己该做的任务即可,体现了高内聚的设计思想

通过头文件的包含来为每个文件建立起联系,每个文件的修改不会引起其他文件的改动(即:不会牵一发而动全身),体现了低耦合的设计思想

2.基本游戏流程

1.实现菜单界面供玩家选择

2.游戏开始后,玩家和电脑每走一步打印一次棋盘

3.当有一方将3个棋子连成一条线时,则该方胜利,游戏结束

4.当棋盘已满可是还未分出胜负时,判断为平局,游戏结束

5.说明:在这里我们采用‘*’作为玩家使用的棋子, 采用‘#’作为电脑使用的棋子,

6.提前说明:我们在game.h中使用了宏定义常量的方式,便于将代码写活,更好地实现n子棋的游戏

#define ROW 3
#define COL 3

3.各种函数功能的实现

3.1 创建游戏菜单页面

void menu()
{
  printf("**************************\n");
  printf("*****     1.play     *****\n");
  printf("*****     0.exit     *****\n");
  printf("**************************\n");
}

玩家选择1进入游戏,选择0退出游戏

int main()
{
  int input = 0;//注意:input不能放在do while循环里面,因为while条件判断中,使用了input作为条件判断语句
  srand((unsigned int)time(NULL));//在电脑下棋环节会提到这个函数的作用,目前在创建游戏界面这里没有用处
  do
  {
    menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      game();//在test.c中实现游戏调用的函数
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("选择错误,请重新选择!\n");
      break;
    }
  } while (input);
  return 0;
}

3.2 初始化board数组

在game.h中声明,在game.c中实现

game.h

void InitBoard(char board[ROW][COL], int row, int col);

game.c

void InitBoard(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      board[i][j] = ' ';
    }
  }
}

即:遍历二维数组,将每个元素置为空格

3.3 打印棋盘

因为这是棋盘,所以我们不妨打印一个这样的棋盘,注意在这里我们可以改变ROW 和COL 的值,来让该函数打印出n阶的棋盘

代码实现如下:

在这里我们将棋盘的存放数据的一行与下面打印行之间分隔线的一行看为一组.

同理,我们将存放数据的一列与右边打印列之间分隔线的一列看为一组,

而且该棋盘为"井"字形. 所以下面的代码中有col-1和row-1来调节打印个数

game.h

void DisplayBoard(char board[ROW][COL], int row, int col);

game.c

void DisplayBoard(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      //打印棋盘有效数据
      printf(" %c ", board[i][j]);
      if (j < col - 1)
      {
        //打印每一列之间的分割线
        printf("|");
      }
    }
    printf("\n");
    if (i < row - 1)
    {
      for (j = 0; j < col; j++)
      {
        //打印每一行之间的分割线
        printf("---");
        if (j < col - 1)
        {
          //打印每一列之间的分割线
          printf("|");
        }
      }
      printf("\n");
    }
  }
}

3.4 玩家下棋

game.h

void PlayMove(char board[ROW][COL], int row, int col);

其实在这里大家应该就能够看出将函数的声明定义在game.h中的好处了,如果我们想要使用某个已经编写好了的函数,只需要在头文件中观看作用注释和返回值类型与参数类型即可,省去了很大一部分精力.

大家也可以在接下来的代码中有更好的体会

game.c

void PlayMove(char board[ROW][COL], int row, int col)
{
  int x = 0;
  int y = 0;
  printf("玩家下棋>:\n");
  while (1)
  {
    printf("请输入下棋坐标,中间输入空格>:");
    scanf("%d %d", &x, &y);
    if (x >= 1 && x <= row && y >= 1 && y <= col) //坐标合法
    {
      if (board[x - 1][y - 1] == ' ')//可以落子
      {
        board[x - 1][y - 1] = '*';
        break;
      }
      else   //不能落子
      {
        printf("坐标被占用,不能落子,请重新输入坐标\n");
      }
    }
    else   //坐标非法
    {
      printf("坐标非法,请重新输入\n");
    }
  }
}

3.5 电脑下棋

game.h

void ComputerMove(char board[ROW][COL], int row, int col);

game.c

在这里,我们需要让电脑产生随机数来下棋,需要用到rand生成随机数函数,而rand函数的功能的充分实现又需要用到srand函数来生成随机数生成器

在cplusplus这个网站上我们搜索到了rand函数的用法,以及rand通过取模运算来得到某一区间内的随机值的方法,大家可以看一下

srand函数通常是和time函数搭配使用的,我们可以这样来使用这两个函数生成随机数生成器

srand((unsigned int)time(NULL));

但是大家要注意,在这个三子棋游戏中,srand只需要使用一次,所以放到main函数中了,因为在一个程序中main函数是只会调用一次的.大家可以看上面的3.1的部分

第二点:srand函数的参数类型是unsigned int类型,而time函数的返回值类型是time_t类型(即:long

long类型的一个别名),所以需要强制类型转换 而time函数的参数部分需要传入一个指针,我们直接传入NULL就行

//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
  int x = 0;//  0--row-1
  int y = 0;//  0--col-1
  printf("电脑下棋:>\n");
  while (1)
  {
    x = rand() % row;  //x对row取模,保证x在0到row-1范围内(左闭右闭)
    y = rand() % col;   //y对col取模,保证y在0到col-1范围内(左闭右闭)
    if (board[x][y] == ' ')
    {
      board[x][y] = '#';
      break;
    }
  }
}

3.6 判断胜负

注意!!!:在这里我们做了如下规定:

1.Iswin函数返回’*‘,则代表玩家获胜,返回’#',则代表电脑获胜 返回’Q’则代表平局,返回’C’则代表继续

2.正因如此:我们才规定Iswin函数返回值的类型为char 字符型

3.在这里我们还没有扩展到n阶棋盘,大家先了解一下3阶棋盘判断输赢的方式

game.h

char Iswin(char board[ROW][COL], int row, int col);

game.c

char Iswin(char board[ROW][COL], int row, int col)
{
  //赢
  //行
  int i = 0;
  for (i = 0; i < row; i++)
  {
    if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
    {
      return board[i][1];
    }
  }
  //列
  for (i = 0; i < col; i++)
  {
    if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
    {
      return board[1][i];
    }
  }
//主对角线
  if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1]!=' ')
  {
    return board[1][1];
  }
//次对角线
  if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
  {
    return board[1][1];
  }
  //平局
  if (IsFull(board,row,col))
  {
    return 'Q';
  }
  //继续
  return 'C';
}

3.7 判断棋盘是否为满

上面3.6中判断平局时我们需要用到一个判断棋盘是否满了的函数

game.c

int IsFull(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      if (board[i][j] == ' ')
      {
        return 0;
      }
    }
  }
  return 1;
}

即遍历一遍,有空格则返回0,没有则返回1

4.程序优化,改装为n子棋

上面已经实现了3子棋的游戏函数了

下面我们先看一下n子棋的优化改进,其实我们只需要改判断输赢函数即可,因为打印棋盘我们在打印棋盘函数中就已经改好了.

我作为一个刚接触C语言不久的萌新,可能没有更好的办法去改装为n子棋,欢迎各位大佬来指点我一下,这里献上我的关于改为n子棋的想法

4.1 二维数组转置函数的实现

void square_matrix_transform(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row - 1; i++)
  {
    int j = 0;
    for (j = col - 1; j > 0; j--)
    {
      if (i != j)
      {
        int tmp = board[i][j];
        board[i][j] = board[j][i];
        board[j][i] = tmp;
      }
    }
  }
}

4.2 iswin函数的改装

在这里我们采用了假设按行/列/主对角线/此对角线算有获胜的成员的方法建立了flag去标记,并且利用了方针的转置思想

char Iswin(char board[ROW][COL], int row, int col)
{
  //获胜
  //1.行
  int i = 0;
  int flag = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    flag = 1;//假设按行算有获胜的
    for (j = 0; j < col-1; j++)
    {
      if (board[i][j] != board[i][j + 1])
      {
        flag = 0;//有不相等的
      }
      if (board[i][j] == ' ' || board[i][j + 1] == ' ')
      {
        flag = 0;//有空
      }
    }
    if (flag)//该行有获胜的
    {
      return board[i][0];//不妨返回改行第一个值
    }
  }
  //2.列
  //不妨通过方针转置来做
  //最后一定要再转置回去
  square_matrix_transform(board, row, col);
  flag = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    flag = 1;//假设按行算有获胜的
    for (j = 0; j < col - 1; j++)
    {
      if (board[i][j] != board[i][j + 1])
      {
        flag = 0;//有不相等的
      }
      if (board[i][j] == ' '|| board[i][j+1] == ' ')
      {
        flag = 0;//有空
      }
    }
    if (flag)//该行有获胜的
    {
      return board[i][0];//不妨返回改行第一个值
    }
  }
  square_matrix_transform(board, row, col);//转置回去
  //3.主对角线
  i = 0;
  flag = 1;//假设主对角线有获胜的
  for (i = 0; i < row-1; i++)
  {
    if (board[i][i] != board[i + 1][i + 1])
    {
      flag = 0;
    }
    if (board[i][i] == ' '||board[i+1][i+1] == ' ')
    {
      flag = 0;
    }
  }
  if (flag)
  {
    return board[0][0];
  }
  //4.副对角线
  i = 0;
  flag = 1;//假设副对角线有获胜的
  for (i = 0; i < row-1; i++)
  {
    if (board[i][row - 1 - i] != board[i + 1][row - 2 - i])
    {
      flag = 0;
    }
    if (board[i][row - 1 - i] == ' ' || board[i + 1][row - 2 - i] == ' ')
    {
      flag = 0;
    }
  }
  if (flag)
  {
    return board[0][row - 1];
  }
  if (IsFull(board, row, col))
  {
    return 'Q';//平局
  }
  return 'C';//继续
}

5.整体程序的实现梳理

5.1 test.c的实现

#include "game.h"
void menu()
{
  printf("**************************\n");
  printf("*****     1.play     *****\n");
  printf("*****     0.exit     *****\n");
  printf("**************************\n");
}
void game()
{
  char board[ROW][COL] = { 0 };
  InitBoard(board, ROW, COL);
  //打印棋盘
  DisplayBoard(board, ROW, COL);
  char ret = 0;
  while (1)
  {
    //下棋
    //玩家下棋
    PlayerMove(board, ROW, COL);
    DisplayBoard(board, ROW, COL);
    ret = Iswin(board, ROW, COL);
    if (ret != 'C')//'C':代表继续游戏,这样做的话可以有效简化我们的代码,尽可能地使代码更加简洁明了
    {
      break;
    }
    //电脑下棋
    ComputerMove(board, ROW, COL);
    DisplayBoard(board, ROW, COL);
    ret = Iswin(board, ROW, COL);
    if (ret != 'C')//'C':代表继续游戏
    {
      break;
    }
  }
  if (ret == '*')
  {
    printf("玩家获胜\n");
  }
  else if (ret == '#')
  {
    printf("电脑获胜\n");
  }
  else
  {
    printf("平局\n");
  }
}
int main()
{
  int input = 0;//注意:input不能放在do while循环里面,因为while条件判断中,使用了input作为条件判断语句
  srand((unsigned int)time(NULL));
  do
  {
    menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      game();
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("选择错误,请重新选择!\n");
      break;
    }
  } while (input);
  return 0;
}

5.2 game.h的实现

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>  //srand()函数需要的头文件
#include <time.h>    //time函数需要的头文件
#define ROW 3
#define COL 3
//test.c   -测试游戏的
//game.c   -游戏函数的实现
//game.h   -游戏函数的声明
void InitBoard(char board[ROW][COL], int row, int col);
void DisplayBoard(char board[ROW][COL], int row, int col);
void PlayerMove(char board[ROW][COL], int row, int col);
void ComputerMove(char board[ROW][COL], int row, int col);
char Iswin(char board[ROW][COL], int row, int col);

5.3 game.c 的实现

#include "game.h"
void InitBoard(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      board[i][j] = ' ';
    }
  }
}
//改进版,可打印任意行列棋盘
void DisplayBoard(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      //打印棋盘有效数据
      printf(" %c ", board[i][j]);
      if (j < col - 1)
      {
        //打印每一列的分割线
        printf("|");
      }
    }
    printf("\n");
    if (i < row - 1)
    {
      for (j = 0; j < col; j++)
      {
        //打印每一行的分割线
        printf("---");
        if (j < col - 1)
        {
          //打印每一列的分割线
          printf("|");
        }
      }
      printf("\n");
    }
  }
}
int IsFull(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      if (board[i][j] == ' ')
      {
        return 0;
      }
    }
  }
  return 1;
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
  printf("玩家下棋\n");
  int x = 0;
  int y = 0;
  //因为玩家多为非程序员,所以都会认为数组下标从1开始,所以这里要站在玩家的角度上实现游戏
  while (1)
  {
    printf("请分别输入x和y的坐标,用空格分隔:>\n");
    scanf("%d %d", &x, &y);
    //先检查坐标是否非法
    if (x >= 1 && x <= row && y >= 1 && y <= col)
    {
      if (board[x - 1][y - 1] == ' ')
      {
        board[x - 1][y - 1] = '*';
        break;
      }
      else
      {
        printf("坐标已被占用,请重新输入\n");
      }
    }
    else
    {
      printf("坐标非法,请重新输入\n");
    }
  }
}
void ComputerMove(char board[ROW][COL], int row, int col)
{
  int x = 0;
  int y = 0;
  printf("电脑下棋\n");
  while (1)
  {
    x = rand() % row; //产生0~row-1
    y = rand() % col;
    if (board[x][y] == ' ')
    {
      board[x][y] = '#';
      break;
    }
  }
}
void square_matrix_transform(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < row - 1; i++)
  {
    int j = 0;
    for (j = col - 1; j > 0; j--)
    {
      if (i != j)
      {
        int tmp = board[i][j];
        board[i][j] = board[j][i];
        board[j][i] = tmp;
      }
    }
  }
}
char Iswin(char board[ROW][COL], int row, int col)
{
  //获胜
  //1.行
  int i = 0;
  int flag = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    flag = 1;//假设按行算有获胜的
    for (j = 0; j < col-1; j++)
    {
      if (board[i][j] != board[i][j + 1])
      {
        flag = 0;//有不相等的
      }
      if (board[i][j] == ' ' || board[i][j + 1] == ' ')
      {
        flag = 0;//有空
      }
    }
    if (flag)//该行有获胜的
    {
      return board[i][0];//不妨返回改行第一个值
    }
  }
  //2.列
  //不妨通过方针转置来做
  //最后一定要再转置回去
  square_matrix_transform(board, row, col);
  flag = 0;
  for (i = 0; i < row; i++)
  {
    int j = 0;
    flag = 1;//假设按行算有获胜的
    for (j = 0; j < col - 1; j++)
    {
      if (board[i][j] != board[i][j + 1])
      {
        flag = 0;//有不相等的
      }
      if (board[i][j] == ' '|| board[i][j+1] == ' ')
      {
        flag = 0;//有空
      }
    }
    if (flag)//该行有获胜的
    {
      return board[i][0];//不妨返回改行第一个值
    }
  }
  square_matrix_transform(board, row, col);//转置回去
  //3.主对角线
  i = 0;
  flag = 1;//假设主对角线有获胜的
  for (i = 0; i < row-1; i++)
  {
    if (board[i][i] != board[i + 1][i + 1])
    {
      flag = 0;
    }
    if (board[i][i] == ' '||board[i+1][i+1] == ' ')
    {
      flag = 0;
    }
  }
  if (flag)
  {
    return board[0][0];
  }
  //4.副对角线
  i = 0;
  flag = 1;//假设副对角线有获胜的
  for (i = 0; i < row-1; i++)
  {
    if (board[i][row - 1 - i] != board[i + 1][row - 2 - i])
    {
      flag = 0;
    }
    if (board[i][row - 1 - i] == ' ' || board[i + 1][row - 2 - i] == ' ')
    {
      flag = 0;
    }
  }
  if (flag)
  {
    return board[0][row - 1];
  }
  if (IsFull(board, row, col))
  {
    return 'Q';//平局
  }
  return 'C';//继续
}

感谢大家能够看到最后,以上就是C语言实现三子棋(n)子棋的全部过程,希望能对大家有所帮助

相关文章
|
3月前
|
存储 缓存 编译器
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(下篇)
scanf处理⽤⼾输⼊的原理是,⽤⼾的输⼊先放⼊缓存,等到按下回⻋键后,按照占位符对缓存进⾏解读。 解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌。
161 2
|
3月前
|
存储 C语言
【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)(上篇)
printf 的作⽤是将参数⽂本输出到屏幕。它名字⾥⾯的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。
75 1
|
4月前
|
前端开发 C语言
C语言04---第一个HelloWorld(vc版本)
C语言04---第一个HelloWorld(vc版本)
|
5月前
|
C语言
【海贼王编程冒险 - C语言海上篇】C语言如何实现简单的三子棋游戏?
【海贼王编程冒险 - C语言海上篇】C语言如何实现简单的三子棋游戏?
28 1
|
6月前
|
C语言
万字详解:C语言三子棋进阶 + N子棋递归动态判断输赢(二)
我们可以通过创建并定义符号常量NUMBER,来作为判断是否胜利的标准。如三子棋中,令NUMBER为3,则这八个方向中有任意一个方向达成3子连珠,则连珠的这个棋子所代表的玩家获胜。
64 1
|
6月前
|
算法 C语言 C++
万字详解:C语言三子棋进阶 + N子棋递归动态判断输赢(一)
三子棋游戏设计的核心是对二维数组的把握和运用。
76 1
|
5月前
|
存储 C语言
三子棋(C语言版)
三子棋(C语言版)
|
5月前
|
程序员 C语言 索引
【️C语言-游戏设置】---三子棋(N满)
【️C语言-游戏设置】---三子棋(N满)
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
32 3
|
4天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
19 6