【C语言三子棋】——玩家VS电脑(C语言实现)

简介: 三子棋,你真的能战胜电脑吗

三子棋是黑白棋的一种。三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时候会出现和棋的情况。

image.png

好了话不多说,现在我就带大家用C语言来实现一下这个小游戏

1.游戏框架

游戏主要分三个模块实现:game.h、game.c和test.c

1.1 game.h

game.h用来写相关头文件,游戏中各部分函数的声明和预处理指令的实现

:dog:game.h的具体实现:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3

//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);

//打印棋盘
void print_board(char board[ROW][COL], int row, int col);

//玩家下棋
void player_move(char board[ROW][COL], int row, int col,char x);

//电脑下棋
void computer_board(char board[ROW][COL], int row, int col, char x);

//判断输赢
char is_win(char board[ROW][COL], int row, int col,char ch);

1.2 test.c

test.c主函数及游戏逻辑函数的实现

1.首先我们需要实现一个main函数,并将游戏菜单menu函数放入主函数中,使用一个循环来控制我们是否开始游戏,这里我们需要用到rand、srand和time函数来生成随机数,并将它运用于电脑下棋的坐标
image.png

2.接下来是游戏逻辑实现函数play_game函数:

  • 写一个二维数组来实现棋盘并将其初始化,棋盘的具体实现见后面的详解
    image.png

  • 接下来分别由玩家和电脑下棋,玩家下棋用字符’*‘,电脑下棋用字符’#‘,并使用一个循环来进行玩家和电脑间的对弈,这里我们需要注意的是每次玩家(或电脑)下棋后都需要一个判断游戏输赢的函数来判断一下游戏状态

  • 通过游戏输赢函数的返回值来决定游戏是否继续,若游戏继续,将此时棋盘上的状态打印出来并由对方(玩家或电脑)继续下棋
    image.png

  • 当棋盘已满或者有一方取得胜利时则退出循环并公布游戏结果
    image.png

test.c的具体实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

//游戏逻辑实现函数
void play_game()
{
   
   
    char result;
    char board[ROW][COL] = {
   
    0 };
    init_board(board, ROW, COL);//初始化棋盘
    print_board(board, ROW, COL);//打印棋盘
    while (1)
    {
   
   
        player_move(board, ROW, COL, '*');//玩家下棋
        result = is_win(board, ROW, COL, '*');//判断输赢
        if (result == '*' || result == 'q')
            break;
        print_board(board, ROW, COL);//打印棋盘
        computer_board(board, ROW, COL, '#');//电脑下棋
        result = is_win(board, ROW, COL, '#');//判断输赢
        if (result == '#' || result == 'q')
            break;
        print_board(board, ROW, COL);//打印棋盘
    }
    //判断输赢:  玩家: *  电脑: #  平局: q 
    if (result == '*')
        printf("恭喜您获得了胜利\n");
    else if (result == '#')
        printf("很遗憾,电脑赢了\n");
    else if (result == 'q')
        printf("平局\n");
    print_board(board, ROW, COL);//打印棋盘
}

//菜单函数
void menu()
{
   
   
    printf("|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^|\n");
    printf("|                         1.play                        |\n");
    printf("|                         0.exit                        |\n");
    printf("|_______________________________________________________|\n");
}

//主函数
int main()
{
   
   
    //随机数生成器
    srand((unsigned int)time(NULL));
    int input = 0;
    do
    {
   
   
        //游戏菜单的实现
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
   
   
        case 1:
            play_game();
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误,请重新选择:>");
            break;
        }
    } while (input);
    return 0;
}

1.3 game.c

game.c游戏中各个接口函数的实现

:dog:由于这个模块是实现游戏过程中各个功能函数的实现,我们把它放到游戏实现这个模块来一一讲述

2.游戏实现

2.1初始化棋盘

这里我们需要用到一个3*3的二维数组,并将其每个元素都置为空白字符,这里我们可以用字符 '空格' 来实现。

//初始化棋盘
void init_board(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] = ' ';
        }
    }
}

2.2打印棋盘

为了使棋盘的打印更为美观

  • 二维数组数组中的每一个元素都采用”空格 元素 空格“的方式打印
  • 采用符号” | “和”- - -“进行棋盘的部署
    //打印棋盘
    void print_board(char board[ROW][COL], int row, int col)
    {
         
         
      for (int i = 0; i < row; i++)
      {
         
         
          for (int j = 0; j < col; j++)
          {
         
         
              printf(" %c ", board[i][j]);
              if (j < col - 1)//最后一列不需要打印
                  printf("|");
          }
          printf("\n");
          if (i < row - 1)//最后一行不需要打印这两个字符
          {
         
         
              for (int j = 0; j < col; j++)
              {
         
         
                  printf("---");
                  if (j < col - 1)
                      printf("|");
              }
          }
          printf("\n");
      }
    }
    

2.3玩家下棋

  • 函数中的最后一个形参char x用来接收玩家下棋时的字符 #
  • 用一个循环来控制玩家的输入,若输入的坐标不符合游戏规则,则会提示玩家重新输入,如果坐标合法,则落子
  • 这里需要注意的是:
    由于数组下标是从0开始的,而我们输入的是真实的坐标,所以在判断坐标是否合理和落子时都必须将横纵坐标减一
    //玩家下棋
    void player_move(char board[ROW][COL], int row, int col, char x)
    {
         
         
      int a, b;
      printf("玩家下棋:\n");
      while (1)
      {
         
         
          printf("请输入坐标:>");
          scanf("%d%d", &a, &b);
          //判断坐标是否合法
          if ((a >= 1 && a <= row) && (b >= 1 && b <= col))
          {
         
         
              //判断坐标是否为空
              if (board[a - 1][b - 1] != ' ')
              {
         
         
                  printf("位置已被占用,请重新输入\n");
              }
              else
              {
         
         
                  board[a - 1][b - 1] = x;
                  break;
              }
          }
          else
          {
         
         
              printf("您的输入不合法,请重新输入\n");
          }
      }
      if ((a >= 1 && a <= row)&& (b >= 1 && b <= col))
      {
         
         
          board[a - 1][b - 1] = x;
      }
    }
    

2.3电脑下棋

这个就相对简单了,不过需要注意的是

  • 我们需要通过%ROW和%COL来控制生成的随机坐标在数组的大小范围内,若此坐标没被占用,则落子
    //电脑下棋
    void computer_board(char board[ROW][COL], int row, int col, char x)
    {
         
         
      printf("电脑下棋\n");
      while(1)
      {
         
         
          //生成随机数
          int a = rand() % row;
          int b = rand() % col;
          //判断坐标是否已被占用
          if (board[a][b] == ' ')
          {
         
         
              board[a][b] = x;
              break;
          }
      }
    }
    

2.4判断输赢

  • 为了能使我们的代码更加灵活,只需要修改二维数组的行和列就可以实现N子棋,使用行数形参char ch接收到无论是电脑还是玩家所落子的“==字符==”,并使用计数器的方式按照列——行——主对角线——副对角线的顺序依次进行判断,若满足三行三列或者对角线的字符都相同,则返回该字符
  • 如果在判断过程中无论玩家还是电脑都不能取胜,还需要分装一个函数来判断棋盘是否已满,在is_win函数中调用该函数,若棋盘已满,则返回“q”
//判断输赢
char is_win(char board[ROW][COL], int row, int col, char ch)
{
   
   
    int count = 0;
    int i;
        //判断每一列
        int j = 0;
        for (int i = 0, j = 0; j < col; j++)
        {
   
   
            if (board[i][j] == ch)
            {
   
   
                count++;
                for (int k = i + 1; k < row; k++)
                {
   
   
                    if (board[k][j] == ch)
                        count++;
                    else
                        break;
                }
            }
            if (count == row)
                return ch;
            else
                count = 0;
        }
        count = 0;
        //判断每一行
        for (int j = 0,i = 0; i < row; i++)
        {
   
   
            if (board[i][j] == ch)
            {
   
   
                count++;
                for (int k = j + 1; k < col; k++)
                {
   
   
                    if (board[i][k] == ch)
                        count++;
                    else
                        break;
                }
            }
            if (count == row)
                return ch;
            else
                count = 0;
        }
        count = 0;
    //判断主对角线
     i = 0;
     j = 0;
    while (i < row)
    {
   
   
        if(board[i++][j++] == ch)
            count++;
    }
    if (count == row)
        return ch;
    count = 0;
    //判断副对角线
    i = 0;
    while (i < row)
    {
   
   
        for (j = 0; j < col; j++)
        {
   
   
            if (i + j == row - 1)
            {
   
   
                if (board[i][j] == ch)
                    count++;
            }
        }
        i++;
    }
    if (count == row)
        return ch;
    int p = is_full(board, row, col);
    if (p == 0)
    {
   
   
        return 'q';
    }
}

判断棋盘是否已满

//判断棋盘是否已满
int is_full(char board[ROW][COL], int row, int col)
{
   
   
    for (int i = 0; i < row; i++)
    {
   
   
        for (int j = 0; j < col; j++)
        {
   
   
            if (board[i][j] == ' ')
                return 1;
        }
    }
    return 0;
}

2.5游戏结局

注:这是test.c文件中的一段代码

  • 调用is_win函数时,若是玩家下棋后判断输赢,传参时需要将玩家所下的“#”传过去,否则则将电脑所下的字符“*”进行传参

  • is_win函数会根据玩家或电脑落子后传参的不同返回三种不同的字符:“#”——电脑获胜、“*”——玩家获胜,若返回“q”,则说明棋盘已满,最后当然是平局喽,当然无论最后是哪一种结局,都需要将棋盘上的结果打印一下

    while (1)
      {
         
         
          player_move(board, ROW, COL, '*');//玩家下棋
          result = is_win(board, ROW, COL, '*');//判断输赢
          if (result == '*' || result == 'q')
              break;
          print_board(board, ROW, COL);//打印棋盘
          computer_board(board, ROW, COL, '#');//电脑下棋
          result = is_win(board, ROW, COL, '#');//判断输赢
          if (result == '#' || result == 'q')
              break;
          print_board(board, ROW, COL);//打印棋盘
      }
      //判断输赢:  玩家: *  电脑: #  平局: q 
      if (result == '*')
          printf("恭喜您获得了胜利\n");
      else if (result == '#')
          printf("很遗憾,电脑赢了\n");
      else if (result == 'q')
          printf("平局\n");
      print_board(board, ROW, COL);//打印棋盘
    

3.电脑下棋优化

我们知道在前面的代码中,因为电脑是随机落子的,所以电脑获胜的可能性实在太小了,为了能让电脑下棋能够变得更加灵活,博主特地改编了电脑落子的方式

  • 先判断下一步棋是否能让玩家或者电脑任意一方获胜
  • 若玩家将要赢,则去赌玩家 若电脑将要赢,则落子到对应位置让自己获胜
  • 倘若两总情况都不满足,则自己随机落子
    //电脑下棋(智能下棋)
    void computer_board(char board[ROW][COL], int row, int col, char x)
    {
         
         
      printf("电脑下棋\n");
      //若玩家将要赢,则去赌玩家
      //主对角线
      int i =0, j = 0;
      if ((board[i][j] == '#' && board[i + 1][j + 1] == '#'&& board[i + 2][j + 2]==' ')
          ||(board[i][j] == '*' && board[i + 1][j + 1] == '*' && board[i + 2][j + 2] == ' '))
      {
         
         
          board[i + 2][j + 2] = x;
          return;
      }
      else if ((board[i][j] == '#' && board[i + 2][j + 2] == '#'&& board[i + 1][j + 1]==' ')
              || (board[i][j] == '*' && board[i + 2][j + 2] == '*' && board[i + 1][j + 1] == ' '))
           {
         
         
          board[i + 1][j + 1] = x;
          return;
           }
      else if ((board[i + 1][j + 1] == '#' && board[i + 2][j + 2] == '#'&& board[i][j]==' ')
          || (board[i + 1][j + 1] == '*' && board[i + 2][j + 2] == '*' && board[i][j] == ' '))
           {
         
         
          board[i][j] = x;
          return;
           }
      //副对角线
       i = 0, j = 2;
      if ((board[i][j] == '#' && board[j][i] == '#'&& board[i + 1][j - 1]==' ')
          || (board[i][j] == '*' && board[j][i] == '*' && board[i + 1][j - 1] == ' '))
      {
         
          
          board[i + 1][j - 1] = x;
          return;
      }
      else if ((board[i][j] == '#' && board[i + 1][j - 1] == '#'&& board[j][i]==' ')
          || (board[i][j] == '*' && board[i + 1][j - 1] == '*' && board[j][i] == ' '))
           {
         
         
          board[j][i] = x;
          return;
           }
      else if ((board[j][i] == '#' && board[i + 1][j - 1] == '#'&& board[i][j]==' ')
          || (board[j][i] == '*' && board[i + 1][j - 1] == '*' && board[i][j] == ' '))
           {
         
         
          board[i][j] = x;
          return;
           }
      //列
      j = 0;
      for (int i = 0, j = 0; j < col; j++)
      {
         
         
          if ((board[i][j] == '#' && board[i + 1][j] == '#' && board[i + 2][j] == ' ')
              || (board[i][j] == '*' && board[i + 1][j] == '*' && board[i + 2][j] == ' '))
          {
         
         
              board[i + 2][j] = x;
              return;
          }
          else if ((board[i][j] == '#' && board[i + 2][j] == '#' && board[i + 1][j] == ' ')
              || (board[i][j] == '*' && board[i + 2][j] == '*' && board[i + 1][j] == ' '))
               {
         
         
              board[i + 1][j] = x;
              return;
               }
          else if ((board[i + 1][j] == '#' && board[i + 2][j] == '#' && board[i][j] == ' ')
              || (board[i + 1][j] == '*' && board[i + 2][j] == '*' && board[i][j] == ' '))
               {
         
         
              board[i][j] = x;
              return;
               }
      }
      //行
          for (int j = 0, i = 0; i < row; i++)
          {
         
         
              if ((board[i][j] == '#' && board[i][j + 1] == '#' && board[i][j + 2] == ' ')
                  || (board[i][j] == '*' && board[i][j + 1] == '*' && board[i][j + 2] == ' '))
              {
         
         
                  board[i][j + 2] = x;
                  return;
              }
              else if ((board[i][j] == '#' && board[i][j + 2] == '#' && board[i][j + 1] == ' ')
                  || (board[i][j] == '*' && board[i][j + 2] == '*' && board[i][j + 1] == ' '))
                   {
         
         
                  board[i][j + 1] = x;
                  return;
                   }
              else if ((board[i][j + 1] == '#' && board[i][j + 2] == '#' && board[i][j] == ' ')
                  || (board[i][j + 1] == '*' && board[i][j + 2] == '*' && board[i][j] == ' '))
                   {
         
         
                  board[i][j] = x;
                  return;
                   }
          }
      //若下一步棋玩家赢不了,电脑也赢不了,则随机下棋
      while (1)
      {
         
         
          //生成随机数
          int a = rand() % row;
          int b = rand() % col;
          //判断坐标是否已被占用
          if (board[a][b] == ' ')
          {
         
         
              board[a][b] = x;
              break;
          }
      }
    }
    

    4.小结

    好了,到这里这个简单的C语言小游戏就实现了,其实代码量还是挺多的,如果大家觉得写的还不错的话还望给博主个三连哦!
相关文章
|
5月前
|
C语言
c语言简单三子棋
c语言简单三子棋
|
5月前
|
C语言
以c语言为基础实现的简易三子棋
以c语言为基础实现的简易三子棋
39 1
|
5月前
|
存储 编译器 C语言
【C语言】VS实⽤调试技巧&(Debug和Release)监视&内存2
【C语言】VS实⽤调试技巧&(Debug和Release)监视&内存
|
5月前
|
编译器 C语言 C++
【C语言】手把手教你配置VS的常见函数如何不报错!
【C语言】手把手教你配置VS的常见函数如何不报错!
|
3月前
|
前端开发 C语言 C++
C语言03----第一个程序HelloWorld(vs版)
C语言03----第一个程序HelloWorld(vs版)
|
4月前
|
C语言
【海贼王编程冒险 - C语言海上篇】C语言如何实现简单的三子棋游戏?
【海贼王编程冒险 - C语言海上篇】C语言如何实现简单的三子棋游戏?
26 1
|
4月前
|
安全 编译器 程序员
【C语言】:VS实用调试技巧和举例详解
【C语言】:VS实用调试技巧和举例详解
45 1
|
5月前
|
C语言
万字详解:C语言三子棋进阶 + N子棋递归动态判断输赢(二)
我们可以通过创建并定义符号常量NUMBER,来作为判断是否胜利的标准。如三子棋中,令NUMBER为3,则这八个方向中有任意一个方向达成3子连珠,则连珠的这个棋子所代表的玩家获胜。
59 1
|
5月前
|
算法 C语言 C++
万字详解:C语言三子棋进阶 + N子棋递归动态判断输赢(一)
三子棋游戏设计的核心是对二维数组的把握和运用。
63 1
|
4月前
|
存储 C语言
三子棋(C语言版)
三子棋(C语言版)