【C语言】三子棋小游戏(详解)

简介: 今天来放松一下, 一起来写一个简易的小游戏——三子棋:

1、设计思路

  • 首先得有一个棋盘
  • 玩家自行输入坐标,电脑随机下棋
  • 三种情况:玩家赢,电脑赢,平局

2、 代码实现

既然是小游戏,我们就可以设置一个简易的菜单,比如1为开始游戏,0为退出游戏:

void menu()
{
  printf("-------------------------------\n");
  printf("----------  1、play  ----------\n");
  printf("----------  0、exit  ----------\n");
  printf("-------------------------------\n");
}

有了菜单我们就要去实现我们的一个逻辑了,当然这里建议同学们不要把所有内容全部写到 main函数里面哦,我们今天采用多文件写法:

首先我们这个菜单在玩家运行时就要打印,需要玩家选择,所以我们这里可以采用 do while 循环,然后根据玩家输入的值进行 switch 分支判断,并把游戏实现部分封装到一个函数 game 里头:

int main()
{
  int input = 0;
  do
  {
    menu();
    printf("Please select :>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      game();
      break;
    case 0:
      break;
    default:
      printf("Input error,please select again!\n");
      break;
    }
  } while (input);
  return 0;
}

由上可见我们简单的 main 函数里头的内容写这么多就够啦,我们可以看到,进行一次游戏还可以重复玩哦!当玩家输入错误也会提醒他重新输入!主要游戏实现函数我们放到 game.c 里,函数的声明我们放到 game.h 里头。

准备工作做完啦,现在就要实现具体函数了!

既然我们是下棋,我们肯定要有一个棋盘,这里棋盘是设置成 3*3 还是 5*5 呢?如果后期我们要增大棋盘怎么办?所以我们干脆直接在头文件里 #define 定义一个常量,这样很方便我们后期要修改棋盘大小就不用一个个修改了!

#define ROW 3 // 行
#define COL 3 // 列

接着我们需要创建我们的棋盘并且初始化!InitBoard 函数会把我们棋盘内容全部初始化成空格。这里我们采用的是两个 for 循环遍历二维数组的方式进行初始化:

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

来到这我们棋盘得有一个简单的外观吧!这个小伙伴们可以随便设计了,那么我今天就简单的设计一个棋盘,比如:

那么我们如何用C语言打印出来呢?其实很简单:

void PrintBoard(char board[ROW][COL], int row, int col)
{
  int i = 0;
  while (i < row)
  {
    for (int j = 0; j < col; ++j)
    {
      if (j < col - 1)
        printf(" %c |", board[i][j]);
      else
        printf(" %c ", board[i][j]);
    }
    printf("\n");
    for (int j = 0; j < col; ++j)
    {
      if (i < row - 1)
      {
        if (j < col - 1)
          printf("---|");
        else
          printf("---");
      }
    }
    if(i < row - 1)
      printf("\n");
    ++i;
  }
}

效果图:(小伙伴可以按照自己喜欢的风格调整哦)

有了棋盘,我们就可以开始下棋了,那么我们肯定得给这个三子棋制定一个规则,比如玩家下的棋用' * '表示,电脑下的棋用 ' # ' 表示,既然我们约定好了,那么就可以开始实现代码了!

来到这我们要考虑几个问题:

  • 玩家理解中肯定是从1行开始的,没有0行,但是数组下标是从0开始的,这个我们要注意!
  • 如果下棋的位置已经被玩家或电脑之前下过了咋办呢?
  • 如果输入的下标越界了,也就是输入的下标不是有效的下标怎么办?

其实这三个问题非常好解决,首先我们使玩家输入下标 x,y 都减一就可以了,如果位置重复,我们让玩家或电脑重新输入新的位置就好,如果下标越界,肯定是提示玩家,并且重新输入!

代码实现:

void PlayerMove(char board[ROW][COL], int row, int col)
{
  int x = 0;
  int y = 0;
  while (1)
  {
    printf("Please enter coordinates (x,y) :>");
    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("Coordinates occupied!\n");
    }
    else
    {
      printf("Coordinate error!\n");
    }
  }
}

上面则是我们玩家下棋,那么电脑下棋呢?

首先我们要保证电脑下棋的随机性,同时也要限制电脑的有效下标范围,这里我们可以使用 srandrand 函数来生成随机数,大致思路跟玩家下棋逻辑一样:(srand 设置起始位置在后面会有)

void ComuterMove(char board[ROW][COL], int row, int col)
{
  printf("ComuterMove:\n");
  while (1)
  {
    // x, y = [0, 2]
    int x = rand() % row;
    int y = rand() % col;
    if (board[x][y] == ' ')
    {
      board[x][y] = '#';
      break;
    }
  }
}

我们已经解决了下棋的问题,剩下来就是判断输赢了,可想而知,这个游戏只有玩家赢,电脑赢,或者平局,那么这个我们该如何设计呢?

如果玩家赢了,那我们就返回 ' * ',如果电脑赢了,我们就返回 ' # ',当棋盘满的情况下就证明是平局了,我们就返回 ' d ',除了以上三种情况,我们游戏都需要继续,那么我们就返回 ' c ';

/* 
  return '*' PlayerWin
  return '#' comuterWin
  return 'c' GameContinue
  return 'd' GameDogfall
 */
char IsWin(char board[ROW][COL], int row, int col);

玩过三子棋的都知道规则,如果横着三个连成线,或者竖着三个连成线,以及斜着可以连成线都算成功,所以这个我们就分别得判断这四种情况了:行(row), 列(col),斜(tilted),平局(Dogfall)

这个函数的实现相较于上边会有点难度,因为我们不能直接把判断条件写固定住,比如说: boare[0][0] == boare[0][1] &&  boare[0][1] == boare[0][2],如果真这样写的话,如果我们要使用 5*5 的棋盘或者 8*8 的棋盘呢?所以这里我们可以采用遍历二维数组的思路:

代码实现:

char IsWin(char board[ROW][COL], int row, int col)
{
  //row
  for (int i = 0; i < row; ++i)
  {
    for (int j = 0; j < col; ++j)
    {
      if (j < col - 2 && board[i][j] != ' ' && board[i][j] == board[i][j + 1] 
        && board[i][j + 1] == board[i][j + 2])
        return board[i][j];
    }
  }
  //col
  for (int i = 0; i < col; ++i)
  {
    for (int j = 0; j < row; ++j)
    {
      if (j < row - 2 && board[j][i] != ' ' && board[j][i] == board[j + 1][i] 
        && board[j + 1][i] == board[j + 2][i])
        return board[j][i];
    }
  }
  //tilted
  for (int i = 0; i < row ; ++i)
  {
    for (int j = 0; j < col; ++j)
    {
      if (j < col - 2 && board[i][j] != ' ' && board[i][j] == board[i + 1][j + 1]
        && board[i + 1][j + 1] == board[i + 2][j + 2])
        return board[i][j];
    }
  }
  for (int i = 0; i < row; ++i)
  {
    for (int j = 0; j < col; ++j)
    {
      if (j > 1 && board[i][j] != ' ' && board[i][j] == board[i + 1][j - 1]
        && board[i + 1][j - 1] == board[i + 2][j - 2])
        return board[i][j];
    }
  }
  //Dogfall
  while (1)
  {
    int count = 0;
    for (int i = 0; i < row; ++i)
    {
      for (int j = 0; j < col; ++j)
      {
        if (board[i][j] == ' ')
        {
          count = 1;
        }
      }
    }
    if (count == 0)
      return 'd';
    else
      return 'c';
  }
}

其实以上代码有一个点需要说一下:首先判断行(row)的时候我们要防止下标越界访问,所以需要 j < col - 2,如果不这样写,我们想象一下,如果我们只有 3*3 的棋盘,我们有必要从第二列开始往后判断那些坐标位置吗?没这个必要,因为从第二列开始最多只有两个有效坐标!

相信后面的代码你们靠着上面的解释也能看懂吧!有不明白或者有更好的建议都可以告诉我哦!

接下来就是在 game 函数里头来调用我们刚刚写的那些函数了:

void game()
{
  char ret = 0;
  //The starting value
  srand((unsigned)time(NULL));
  char board[ROW][COL] = { 0 };
  InitBoard(board, ROW, COL);
  printf("Start the game.\n");
  PrintBoard(board, ROW, COL);
  while (1)
  {
    PlayerMove(board, ROW, COL);
    ret = IsWin(board, ROW, COL);
    if (ret != 'c')
      break;
    PrintBoard(board, ROW, COL);
    ComuterMove(board, ROW, COL);
    ret = IsWin(board, ROW, COL);
    if (ret != 'c')
      break;
    PrintBoard(board, ROW, COL);
  }
  if (ret == '*')
    printf("PlayerWin!\n");
  else if (ret == '#')
    printf("comuterWin!\n");
  else
    printf("GameDogfall!\n");
  PrintBoard(board, ROW, COL);
}

以及我们 game.h 的头文件包含以及函数声明部分:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 5
#define COL 5
void InitBoard(char board[ROW][COL], int row, int col);
void PrintBoard(char board[ROW][COL], int row, int col);
void PlayerMove(char board[ROW][COL], int row, int col);
void ComuterMove(char board[ROW][COL], int row, int col);
/* 
  return '*' PlayerWin
  return '#' comuterWin
  return 'c' GameContinue
  return 'd' GameDogfall
 */
char IsWin(char board[ROW][COL], int row, int col);

好了,代码都实现完了,那我们就玩两把试试吧:

这个三子棋电脑下棋可能有点憨憨的,它只会随机下棋,感兴趣的小伙伴可以下来把他优化成智能下棋的哈,这次我没有用图片放代码哈,也可以直接 copy 我的代码下去玩俩吧哈!

其实这个游戏不是主要的,主要的是希望小伙伴们通过这个小案例来锻炼自己的编程逻辑,像递归一样,写多了,自然都会了,有句话说得好,眼看十遍不如手敲一遍,赶紧拿出电脑来敲敲代码吧!

相关文章
|
4月前
|
算法 C语言
【C语言】猜数字小游戏的一步一步实现1
【C语言】猜数字小游戏的一步一步实现
|
4月前
|
C语言
【C语言】猜数字小游戏的一步一步实现2
【C语言】猜数字小游戏的一步一步实现
|
4月前
|
C语言
C语言之三子棋小游戏
C语言之三子棋小游戏
|
3月前
|
C语言
C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
C语言实现2048小游戏---粤嵌GE6818嵌入式系统实训
158 0
|
2月前
|
存储 编译器 C语言
|
2月前
|
存储 C语言
【C语言】猜数字小游戏
C语言实现猜数字小游戏
30 2
【C语言】猜数字小游戏
|
3月前
|
程序员 C语言
C语言小游戏之猜数字
C语言小游戏之猜数字
28 5
|
3月前
|
C语言
C语言小游戏的源代码
C语言小游戏的源代码
|
4月前
|
C语言
【C语言入门小游戏】扫雷
【C语言入门小游戏】扫雷
38 1
|
4月前
|
编译器 C语言
【C语言入门小游戏】三子棋
【C语言入门小游戏】三子棋
39 0
【C语言入门小游戏】三子棋