【C语言】三子棋(智能下棋 + 阻拦玩家)

简介: 【C语言】三子棋(智能下棋 + 阻拦玩家)

1. 前言


三子棋,也称井字棋,该游戏进行起来十分方便,只需要一支笔和一张纸就可以进行游戏,是上课摸鱼的好游戏(bushi),也是我的童年回忆。


本篇博客将采用C语言来模拟实现简单的三子棋游戏。

人机大战,谁能更胜一筹?


6141ae131aafce12d47ab3a5a711545e.jpeg



2. 游戏分工


该项目分为三个文件:

  1. test.c:游戏的逻辑
  2. game.h:函数声明,符号的定义
  3. game.c:游戏的实现



其中game.h,game.c为游戏模块,test.c为测试区域。




3. 游戏菜单


在进入游戏前,我们需要让玩家通过菜单选择开始游戏或退出游戏。


游戏菜单应该实现三个功能:


  1. 进入游戏
  2. 退出游戏
  3. 非法输入的返回提示和说明



而对于游戏,我们至少需要玩一次,应该用do...while循环来完成游戏的重复游玩功能。对于游戏选项,则可以用switch语句完成。

对应代码:

void menu()
{
  printf("**********************************\n");
  printf("************* 1.play *************\n");
  printf("************* 0.exit *************\n");
  printf("**********************************\n");
}
void test()
{
  int input = 0;
  do
  {
    menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case 1:
      printf("三子棋\n");
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("选择错误,请重新选择\n");
      break;
    }
  } while (input);
}
int main()
{
  test();
  return 0;
}


运行结果:


5b824763085a2dae4e445ca69e64bd40.png




4. 游戏过程



友情提示:

以下模块均用于实现游戏功能,对应的功能均在test.c的game()函数中调用、game.h中进行声明和相关常量的定义,函数功能在game.c中实现。

在.c文件中均需引自定义的头文件#include"game.h"。

对于函数的声明我会省略,以下模块展示的内容主要为在game.c文件中实现的游戏功能。



4.1 前提准备


为了提高代码的专业性和方便对代码进行修改,我们将对于行和列进行定义(game.h).

对应代码:

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



4.2 棋盘的初始化


既然是三子棋,那棋盘并不可少。在进行游戏前,我们需要将棋盘中的元素都置为空白,以便后续进行下棋的操作。

棋盘,无非就是一个3 * 3的二维数组,我们在game()函数中定义:

void game()
{
  //棋盘的创建
  char board[ROW][COL] = { 0 };
  //初始化棋盘
  init_board(board, ROW, COL);
}


对于棋盘的初始化,我们直接利用嵌套循环即可:

void init_board(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < col; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      board[i][j] = ' ';//置为空白
    }
  }
}



4.3 打印棋盘


在完成棋盘的初始化后,我们需要将初始化后的棋盘展示给玩家看,棋盘如图所示:


fe1781c753d096fef82782d2f0c41cdf.png


棋盘分为三块:


  1. 数据,一个数据表示为空格数据空格的形式.
  2. 分隔符|,分隔符的个数每行均为两个,边界不需要分隔符。
  3. 分隔线---,该分割线只存在于第一、二行数据的下方。


对于分隔符可以用列来进行限制,对于分隔线则是用行来限制:


void display_board(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)//最后一行没有
    {
      int j = 0;
      for (j = 0; j < col; j++)
      {
        printf("---");
        if (j < col - 1)
          printf("|");
      }
      printf("\n");
    }
  }
}



4.4 对弈过程


对弈过程不止一次,直到分出胜负为止,因此在game()函数中应用循环表示。

   玩家下棋

   思路:

       玩家下棋的输入我们用坐标表示,但是需要明确一点:并不是所有玩家都了解C语言数组下标,因此玩家输入的坐标需要进行转化。

       输入坐标的取值范围为:1~3,若不在范围内应提示输入错误。

       玩家棋子用*表示,若选择坐标为空白,则将坐标置为*,并退出,让电脑进行后续操作,若选择坐标不是空白,则操作为悔棋操作,应提示重新输入。


对应代码:

void player_move(char board[ROW][COL], int row, int col)
{
  int x = 0;
  int y = 0;
  while (1)
  {
    printf("玩家下棋\n");
    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");
    }
  }
}

   电脑下棋(简版,智能化下棋中会优化)


思路:


   电脑为自动下棋,并不需要手动输入坐标。

   自动随机值的提供需要利用时间戳来完成,和上次的猜数字小游戏相同,利用rand、srand、time函数来完成相关操作,srand函数在test中调用,rand函数在电脑下棋中用临时变量接收。

   随机值取值范围应在0~2之间,由行和列的值都为3,直接让rand()%3即可(数值1模数值2结果为0~数值2-1)

   电脑棋子用#表示,由于自动下棋并不需要人为操作,只要条件判断即可,不需要输入提示。


对应代码:

void computer_move(char board[ROW][COL], int row, int col)
{
  int x = 0;
  int y = 0;
  printf("电脑下棋\n");
  while (1)
  {
    x = rand() % row;//0-2
    y = rand() % col;//0-2
    if (board[x][y] == ' ')
    {
      board[x][y] = '#';
      break;//注意退出
    }
  }
}



4.5 判断输赢


我们用以下方式判定游戏情况:


   玩家赢 - *

   电脑赢 - #

   平局 - Q

   继续 - c



思路:


   赢取游戏,分为四种情况,行、列、主对角线、副对角线所占元素均相同且不为空。


   在判断输赢后未得出结果,则说明游戏平局,我们可以封装一个函数判断棋盘是否满了,用返回值为1则说明平局,且该函数仅为判断输赢做辅助作用,并不需要声明,且不需要被其他文件所发现。


   若输赢和平局并不满足,则说明游戏继续。


   在game()中应对相应的返回结果做出判断,且在玩家和电脑下棋后均应该判断棋盘是否下满,下满则直接进入判断部分。


结合对弈过程和判断在game()函数的对应代码:


void game()
{
  while (1)
  {
    //玩家下棋
    player_move(board, ROW, COL);
    //玩家下棋后展示棋盘
    display_board(board, ROW, COL);
    //判断输赢
    ret = is_win(board, ROW, COL);
    if (ret != 'C')
    {
      break;
    }
    //电脑下棋
    computer_move(board, ROW, COL);
    //电脑下棋后棋盘展示
    display_board(board, ROW, COL);
    ret = is_win(board, ROW, COL);
    if (ret != 'C')
    {
      break;
    }
  }
  if (ret == '*')
  {
    printf("玩家赢\n");
  }
  else if (ret == '#')
  {
    printf("电脑赢了\n");
  }
  else
  {
    printf("平局\n");
  }
}


game.c对应代码:

static int is_full(char board[ROW][COL], int row, int col)//static修饰为静态函数,只为is_win起辅助作用
{
  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;//下满了
}
char is_win(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][i] != ' ')
    {
      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[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
  {
    return board[1][1];
  }
  if (is_full(board, row, col) == 1)
  {
    return 'Q';
  }
  return 'C';
}



4.6 智能化下棋(※)


对于电脑下棋,原先的代码使得在下棋过程中,电脑的操作过于呆板,没有挑战性。所以我们可以对电脑下棋进行升级。

智能化下棋分为两部分:

   电脑判断是否能赢并下棋

   电脑阻拦玩家下棋

思路:

我们封装一个函数,被computer_move函数所调用,函数中实现这两个功能。

这两个操作都是对于行、列、主副对角线进行判断,它们的实现逻辑是相同的,所以我们可以把它们在同一函数中实现,电脑赢棋的取决于一条路线上是否有两个#,阻拦玩家下棋则取决于一条路线上是否有两个*。

所以我们在传参时,只需要把元素传入,这样就可以将两部分在一个函数中实现。


对应代码:

static int intelligent_computer(char board[ROW][COL], int row, int col, char ch)//ch为'#'进行下棋,ch为'*'进行阻拦
{
  //检查电脑能否获得胜利/阻拦玩家
  int i = 0;
  //行
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      //判断第一个和第二个的元素电脑是否已下棋,对第三个元素进行下棋/阻拦
      if (board[i][0] == board[i][1] && board[i][0] == ch && board[i][2] == ' ')
      {
        board[i][2] = '#';
        return 1;
      }
      //判断第一个和第三个的元素电脑是否已下棋,对第二个元素进行下棋/阻拦
      if (board[i][0] == board[i][2] && board[i][0] == ch && board[i][1] == ' ')
      {
        board[i][1] = '#';
        return 1;
      }
      //判断第二个和第三个的元素电脑是否已下棋,对第一个元素进行下棋/阻拦
      if (board[i][1] == board[i][2] && board[i][1] == ch && board[i][0] == ' ')
      {
        board[i][0] = '#';
        return 1;
      }
    }
  }
  //列
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      //第一和第二个已下,下/阻拦第三个
      if (board[0][j] == board[1][j] && board[0][j] == ch && board[2][j] == ' ')
      {
        board[2][j] = '#';
        return 1;
      }
      //第一和第三个已下,下/阻拦第二个
      if (board[0][j] == board[2][j] && board[0][j] == ch && board[1][j] == ' ')
      {
        board[1][j] = '#';
        return 1;
      }
      //第二和第三个已下,下/阻拦第一个
      if (board[1][j] == board[2][j] && board[1][j] == ch && board[0][j] == ' ')
      {
        board[0][j] = '#';
        return 1;
      }
    }
  }
  //主对角线
  if (board[0][0] == board[1][1] && board[1][1] == ch && board[2][2] == ' ')//落子/阻拦主对角线第三个元素
  {
    board[2][2] = '#';
    return 1;
  }
  if (board[0][0] == board[2][2] && board[0][0] == ch && board[1][1] == ' ')//落子/阻拦主对角线第二个元素
  {
    board[1][1] = '#';
    return 1;
  }
  if (board[1][1] == board[2][2] && board[1][1] == ch && board[0][0] == ' ')//落子/阻拦主对角线第一个元素
  {
    board[0][0] = '#';
    return 1;
  }
  //副对角线
  if (board[0][2] == board[1][1] && board[0][2] == ch && board[2][0] == ' ')//落子/阻拦副对角线第三个元素
  {
    board[2][0] = '#';
    return 1;
  }
  if (board[0][2] == board[2][0] && board[0][2] == ch && board[1][1] == ' ')//落子/阻拦副对角线第二个元素
  {
    board[1][1] = '#';
    return 1;
  }
  if (board[1][1] == board[2][0] && board[1][1] == ch && board[0][2] == ' ')//落子/阻拦副对角线第一个元素
  {
    board[0][2] = '#';
    return 1;
  }
  return 0;//若无法赢棋或无法阻拦,返回0
}



注:对于原先的computer_move函数也需要做出相应改进,改进内容在完整展示部分。




5. 完整展示


game.h


#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define ROW 3
#define COL 3
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void display_board(char board[ROW][COL], int row, int col);
//玩家下棋
void player_move(char board[ROW][COL], int row, int col);
//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);
//判断输赢
char is_win(char board[ROW][COL], int row, int col);

game.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include"game.h"
void init_board(char board[ROW][COL], int row, int col)
{
  int i = 0;
  for (i = 0; i < col; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      board[i][j] = ' ';
    }
  }
}
void display_board(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)
    {
      int j = 0;
      for (j = 0; j < col; j++)
      {
        printf("---");
        if (j < col - 1)
          printf("|");
      }
      printf("\n");
    }
  }
}
void player_move(char board[ROW][COL], int row, int col)
{
  int x = 0;
  int y = 0;
  while (1)
  {
    printf("玩家下棋\n");
    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");
    }
  }
}
static int intelligent_computer(char board[ROW][COL], int row, int col, char ch)//ch为'#'进行下棋,ch为'*'进行阻拦
{
  //检查电脑能否获得胜利/阻拦玩家
  int i = 0;
  //行
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      //判断第一个和第二个的元素电脑是否已下棋,对第三个元素进行下棋/阻拦
      if (board[i][0] == board[i][1] && board[i][0] == ch && board[i][2] == ' ')
      {
        board[i][2] = '#';
        return 1;
      }
      //判断第一个和第三个的元素电脑是否已下棋,对第二个元素进行下棋/阻拦
      if (board[i][0] == board[i][2] && board[i][0] == ch && board[i][1] == ' ')
      {
        board[i][1] = '#';
        return 1;
      }
      //判断第二个和第三个的元素电脑是否已下棋,对第一个元素进行下棋/阻拦
      if (board[i][1] == board[i][2] && board[i][1] == ch && board[i][0] == ' ')
      {
        board[i][0] = '#';
        return 1;
      }
    }
  }
  //列
  for (i = 0; i < row; i++)
  {
    int j = 0;
    for (j = 0; j < col; j++)
    {
      //第一和第二个已下,下/阻拦第三个
      if (board[0][j] == board[1][j] && board[0][j] == ch && board[2][j] == ' ')
      {
        board[2][j] = '#';
        return 1;
      }
      //第一和第三个已下,下/阻拦第二个
      if (board[0][j] == board[2][j] && board[0][j] == ch && board[1][j] == ' ')
      {
        board[1][j] = '#';
        return 1;
      }
      //第二和第三个已下,下/阻拦第一个
      if (board[1][j] == board[2][j] && board[1][j] == ch && board[0][j] == ' ')
      {
        board[0][j] = '#';
        return 1;
      }
    }
  }
  //主对角线
  if (board[0][0] == board[1][1] && board[1][1] == ch && board[2][2] == ' ')//落子/阻拦主对角线第三个元素
  {
    board[2][2] = '#';
    return 1;
  }
  if (board[0][0] == board[2][2] && board[0][0] == ch && board[1][1] == ' ')//落子/阻拦主对角线第二个元素
  {
    board[1][1] = '#';
    return 1;
  }
  if (board[1][1] == board[2][2] && board[1][1] == ch && board[0][0] == ' ')//落子/阻拦主对角线第一个元素
  {
    board[0][0] = '#';
    return 1;
  }
  //副对角线
  if (board[0][2] == board[1][1] && board[0][2] == ch && board[2][0] == ' ')//落子/阻拦副对角线第三个元素
  {
    board[2][0] = '#';
    return 1;
  }
  if (board[0][2] == board[2][0] && board[0][2] == ch && board[1][1] == ' ')//落子/阻拦副对角线第二个元素
  {
    board[1][1] = '#';
    return 1;
  }
  if (board[1][1] == board[2][0] && board[1][1] == ch && board[0][2] == ' ')//落子/阻拦副对角线第一个元素
  {
    board[0][2] = '#';
    return 1;
  }
  return 0;//若无法赢棋或无法阻拦,返回0
}
void computer_move(char board[ROW][COL], int row, int col)
{
  printf("电脑下棋\n");
  int sign1 = 0;
  int sign2 = 0;
  sign1 = intelligent_computer(board, ROW, COL, '#');//电脑智能赢棋
  if (sign1 == 0)//若电脑无法赢棋,则进行阻拦
  {
    sign2 = intelligent_computer(board, ROW, COL, '*');//阻拦玩家下棋
    if (sign2 == 0)//若赢棋和阻拦都不满足,电脑自行下棋
    {
      int x = 0;
      int y = 0;
      while (1)
      {
        x = rand() % row;
        y = rand() % col;
        if (board[x][y] == ' ')
        {
          board[x][y] = '#';
          break;
        }
      }
    }
  }
}
static int is_full(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;
}
char is_win(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[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
  {
    return board[1][1];
  }
  if (is_full(board, row, col) == 1)
  {
    return 'Q';
  }
  return 'C';
}

test.c

 
         

6. 动画展示

34eed6d22900310671a3e9bb662acfe9.gif



7. 结语


到这里,一个简易三子棋游戏也就实现成功了!


我们发现经过智能化的电脑下棋还是很厉害的嘛,我都输了好几次,不过经我挑战后,我赢的次数还是比电脑多的,所以本次人机大战,我宣布,由anduin我,获得胜利!


相关文章
|
9月前
|
人工智能 算法 数据可视化
C语言”三子棋“升级版(模式选择+”模拟智能“下棋)
C语言”三子棋“升级版(模式选择+”模拟智能“下棋)
|
C语言
C语言 6 下棋游戏 初阶版
C语言 6 下棋游戏 初阶版
35 0
|
人工智能 C语言
C语言实现三子棋(会堵棋,加强版)智能AI博弈
C语言实现三子棋(会堵棋,加强版)智能AI博弈
132 0
|
监控 供应链 安全
智能仓储物流管理系统(C语言实现)
智能仓储物流管理系统(C语言实现)
266 0
|
人工智能 C语言
大一新生必会的c语言五子棋!PVP,PVE,EVE模式都有,还有智能的AI部分,复盘等内容!一看就会的五子棋教程,确定不来看看吗?
大一新生必会的c语言五子棋!PVP,PVE,EVE模式都有,还有智能的AI部分,复盘等内容!一看就会的五子棋教程,确定不来看看吗?
183 0
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
62 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
66 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
60 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
63 16

热门文章

最新文章