C语言_循环+数组+函数_三子棋游戏_+电脑AI设计(讲解)+扩展思路(讲解)

完成三子棋的构思

1,打印菜单,是否进入游戏
2,生成二维数组储存下棋棋盘,而且棋盘要易改变
3,将棋盘初始化成空格
4,打印棋盘
5,玩家下棋
6,判断输赢
7,电脑下棋
8,判断输赢
9,打印看电脑下棋的信息
10,在判断输赢后打印谁获胜了
11,打印菜单,选择是否再次进行游戏
这里生成game.h头文件放函数声明 , game.c文件放函数的实现 , test.c文件放工程的main函数

在这里插入图片描述

这里实现每一步

1,打印菜单,是否进入游戏 11,完成游戏后,选择是否再次进行游戏
这里我们规定当玩家选择1时玩游戏并执行game函数之后再打印一次菜单让玩家选择,当选择0时退出游戏,当选择1,0以外的数字时提醒选择错误并再一次打印菜单。

通过上面的分析发现,这个过程是我们玩游戏的总流程,所以在main函数中可以通过do{…}while();循环来进行,
进入游戏先打印菜单,用input来接受玩家选择的数字。
当玩家选择0时为假跳出主函数
当玩家选择1时为真进行game()函数,再打印菜单
当玩家选择非1,0时也为真,这时提醒玩家选择错误,在打印一次菜单
(用switch case来实现) (do{…}while(input))
(注意接收玩家选择的数字应该在循环内,每一次打印菜单都要接受一次玩家的选择)
代码实现

//在test.c文件中实现
#include<game.h>//引我们的函数声明头文件
void menu()
{
    
    
	printf("***********************\n");
	printf("******   1.进入  ******\n");
	printf("******   0.退出  ******\n");
	printf("***********************\n");
}
void game()
{
    
    
	printf("三子棋游戏\n");//这里先进行测试看一看主逻辑有没有写对
}
int main()
{
    
    
    srand((unsigned int)time(NULL))//通过时间戳设置随机数生成起点
	int input = 0;
	do
	{
    
    
		menu();
		printf("请选择\n");
		scanf("%d",&input);
		switch (input)
		{
    
    
		case 1:
		{
    
    
			game();//负责完成游戏逻辑
			break;
		}
		case 0:
		{
    
    
			printf("退出游戏\n");
			break;
		}
		default:
		{
    
    
			printf("输入错误请重新选择\n");
			break;
		}
		}
	} while (input);//判断input只要input不是0就要进行循环
	return 0;
}

输出样例
在这里插入图片描述
从2到11步我们这里全部放到game()里去实现

2到11步要循环进行下棋直到判断出输赢。

这里我们用字符ret来接受判断输赢函数的返回值。
如果返回 ‘ #’则电脑赢 break
如果返回 ‘ * ’ 则玩家赢 break
如果返回 ‘ q ’则是平局 break
如果返回 ‘ c ’ 则还没有分出高下,棋盘也没有满。

//在test.c文件中实现
#include"game.h"
void menu()
{
    
    
	printf("***********************\n");
	printf("******   1.进入  ******\n");
	printf("******   0.退出  ******\n");
	printf("***********************\n");
}
void game()
{
    
    
	char board[ROW][COL];//实现第二步生成二维数组
//,为了方便改变棋盘大小这里行ROW列COL的值放到game.h去定义
     InitBoard(board, ROW, COL);//实现第三步初始化棋盘
     DisplayBoard(board, ROW, COL);//打印棋盘实现第四步
     while(1)//循环下棋直到分出胜负或平局
     {
    
    
     PlayerMove(board, ROW, COL);//玩家输入坐标实现下棋操作实现第五步
     ret = CheckWin(board, ROW, COL);//玩家下棋后,判断当前输赢实现第六步
     if(ret !='C')//如果返回是c说明游戏继续,返回不是c先出循环在打印谁赢了或平局
     {
    
    
      break;
     }
     ComputerMove(board, ROW, COL);//电脑下棋实现第七步
     ret = CheckWin(board, ROW, COL);//电脑下棋后,判断当前输赢实现第八步
      if(ret !='C')//如果返回是c说明游戏继续,返回不是c先出循环在打印谁赢了或平局
     {
    
    
      break;
     }
     DisplayBoard(board, ROW, COL);//打印电脑下棋后棋盘信息实现第九步
     }
     //出了循环可能是玩家胜利或者电脑胜利,或者平局
     //打印获胜信息实现第十步
     if(ret =='*')
     {
    
    
     printf("玩家胜利");
     }
     if(ret=='#')
     {
    
    
     printf("电脑胜利");
     }
     if(ret=='q')
     {
    
    
     printf("平局没有赢家");
     }
     DisplayBoard(board, ROW, COL);//再打印一次棋盘显示获胜棋盘信息
}
int main()
{
    
    
	int input = 0;
	do
	{
    
    
		menu();
		printf("请选择\n");
		scanf("%d",&input);
		switch (input)
		{
    
    
		case 1:
		{
    
    
			game();//负责完成游戏逻辑
			break;
		}
		case 0:
		{
    
    
			printf("退出游戏\n");
			break;
		}
		default:
		{
    
    
			printf("输入错误请重新选择\n");
			break;
		}
		}
	} while (input);//判断input只要input不是0就要进行循环
	return 0;
}

到这里我们把tese.c文件中游戏主程序写完

之后要实现在game()中函数的作用

先在game.h中进行函数声明

每一个函数实现功能的时候都需要给函数数组信息,整形row(行),整形col(列)的信息
在CheckWin()中函数有字符返回值char
在 IsFull()中函数有数字返回值int
在Computer_AI() 中函数有数字返回值int

扫描二维码关注公众号,回复: 12334145 查看本文章

#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 3
#define COL 3
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 CheckWin(char board[ROW][COL], int row, int col);
int IsFull(char board[ROW][COL], int row, int col);
//IsFull函数将在判断棋盘是否被填满中来使用(后面解释)只要知到有这个功能。
int  Computer_AI(char board[ROW][COL], int row, int col);
//这个函数是来实现电脑的智能下棋的(后面会解释)只要知道有这个功能。

到这里game.h文件已经写完
(包括函数声明,要引的头文件,行列数字的定义)

在game.c文件中实现我们自己定义的函数

InitBoard函数的实现

//InitBoard函数初始化棋盘
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]=' ';//数组所有元素被初始化成空格
        }
    }
}

DisplayBoard函数的实现
在这里插入图片描述

观察第一行先打印了空格,再打印数组内容,再打印空格,再打印 |

发现空格+数组内容+空格是一组打印次数和列相同

‘ | ’在最后一次不打印

再看第一行下面_ _ _是一组打印完后打印 ‘ | ’

’ | ‘在最后一次不会打印。

一共打印了三行且最后一行没有_ _ _

实现代码如下

//DisplayBoard函数打印棋盘
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");//换行开始第二行的打印
		}
     }
}

打印的棋盘如下
在这里插入图片描述

当改变ROW COL 的值时棋盘也会正常打印。为我们五子棋设计打基础。
当ROW COL 的值为5时打印棋盘为
在这里插入图片描述
PlayerMove()函数的实现
玩家下棋是通过坐标的形式来下棋的

玩家不知道数组下标为0,所以玩家输入坐标的x y值要减1才是他想下的数组位置

当玩家下棋输入坐标时首先判断输入坐标的合法性(行坐标要大于1小于ROW)
(列坐标要大于1小于COL)

其次坐标所在的数组位置不能被占用

玩家一直输入坐标直到坐标合法后,将该坐标下数组的空格换成玩家下的’ * ‘
所以这个过程是一个循环

//PlayerMove()函数是玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
    
    
    int x = 0;
	int y = 0;
	
	printf("玩家走:>\n");
	while (1)//循环判断玩家下棋的合法性
	{
    
    
		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");
		}
		printf("输入你要下的坐标\n");
	}
}

输出结果
在这里插入图片描述
ComputerMove()函数的实现
电脑和人一样我们在我们的棋子有两个的时候会尽可能的连成三个

在敌方棋子有两个的时候会堵截敌方

这两种情况共同点是当有相同的棋子(不论是玩家的还是电脑的而且不能是空格)电脑在下一个位置下棋就可以起到攻击或防御的目的

在没有两个连起来的棋子时就随机下棋。

这里我们设计Computer_AI()函数当符合有两个相同的棋子时返回1结束下棋,当没有两个相同的棋子时返回0;这是随机下棋。
(要注意有两个相同棋子时必须还有一个空格给电脑下棋,否则电脑可能就始终无法生成一个未被占用的棋子)

除此之外电脑随机下棋直到符合有效范围而且没有被占用。这个是一个循环

//
//ComputerMove()是电脑下棋
//Computer_AI()是我们设计的电脑的下棋方法
int Computer_AI(char board[ROW][COL], int row, int col)
{
    
    
  int i=0;
  int j=0;
  for(i=0;i<row;i++)//看每一行是不是有两个相同元素
  {
    
    
        if(board[i][0]==board[i][1] && board[i][0]!=' '&& board[i][2]==' ')
        {
    
    
         board[i][2] = '#';
//这种情况是搜索是否有每一行的第一二列有相同的棋子且第三列可以下棋
         return 1//搜索到就返回值防止多下
        }
        else if(board[i][1]==board[i][2] && board[i][1]!=' '&& board[i][0]==' ')
        {
    
    
         board[i][0] = '#';
//这种情况是搜索是否有第二三列相同的棋子而且可以下棋
         return 1;
        }
        else if(board[i][2]==board[i][0] && board[i][0]!=' '&& board[i][1]==' ')
        {
    
    
        board[i][1]='#';
//这种情况是搜索是否有第一三列相同的棋子而且可以下棋
        return 1;
        }
  }


   for(i=0;i<col;i++)//看每一列是不是有相同的棋子
   {
    
    
         if(board[0][i]==board[1][i] && board[1][i]!=' ' && board[2][i]==' ')
         {
    
    
         board[2][i]='#';
//这种情况是搜索是否有第一二行形同而且可以下棋的地方
         retuen 1;
         }
         if(board[1][i]==board[2][i] && board[1][i]!=' '&& board[0][i]==' ')
         {
    
    
         board[0][i]='#';
//这种情况是搜索是否有第二三相同的棋子且可以下棋的地方
         return 1;
         }
         if(board[0][i]==board[2][i] && board[0][i]!=' '&& board[1][i]==' ')
         {
    
    
         board[1][i]='#';
//这种情况是搜索是否有第一三相同的棋子且可以下棋的地方
         return 1;
         }
 }



//以下是在搜索对角线上是否有相同的棋子且可以下棋的地方
         if(board[0][0]==board[1][1] && board[1][1]!=' '&& board[2][2]==' '{
    
    
          board[2][2] = '#';
          return 1;
         }
         if(board[2][2]==board[1][1] && board[1][1]!=' '&& board[0][0]==' ')
         {
    
    
          board[0][0] = '#';
          return 1;
         }
          if(board[0][0]==board[2][2] && board[0][0]!=' '&& board[1][1]==' ')
          {
    
    
          board[1][1] = '#';
           return 1;
          }
          if(board[0][2]==board[1][1] && board[1][1]!=' '&& board[2][0]==' ')
          {
    
    
          board[2][0] = '#';
           return 1;
          }
          if(board[2][0]==board[1][1] && board[1][1]!=' '&& board[0][2]==' ')
          {
    
    
           board[0][2] = '#';
           return 1;
          }
          if(board[2][0]==board[0][2] && board[2][0]!=' '&& board[1][1]==' ')
          {
    
    
          board[1][1] = '#';
          return 1;
          }
          return 0;//没有相同的棋子时通过随机值来下棋
}
// ComputerMove电脑下棋。
void ComputerMove(char board[ROW][COL], int row, int col)
{
    
    
  while(1)//循环生成在范围内而且没有被占用的棋子
  {
    
    
     int output=Computer_AI(char board, int row, int col)==1
           if(output==1)
           //返回值为1时用我们的写的下棋方式
           {
    
    
            break;
           }
           else//否则随机生成合法坐标
           {
    
    
           int x=rand()%row;//x的范围是0到row-1
           int y=rand()%col;//y的范围是0到col-1
              if(board[x][y] == ' ')//未被占用
              {
    
    
              board[x][y]=='#';
              break;//防止电脑多次下棋
              }
           }
  }
}

CheckWin()函数的实现

在判断输赢函数中我们还需要一个判断棋盘是否被装满函数

当棋盘被装满的时候返回1,在CheckWin()函数中接受到1时返回 ’ q ‘代表平局

//CheckWin()函数判断输赢
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;//到这里说明棋盘已满返回1
   }
  char CheckWin(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][0] != ' ')
         {
    
    
         return board[i][0];//返回值正好可以判断谁胜利(*代表人 #代表电脑)
         }
     }     

     for (i = 0; i < col;i++)//列
     {
    
    
        if(board[0][i]== board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
        {
    
    
        return board[0][i];
        }
     }
     
//之后判断两条对角线
     
   if(board[0][0]==board[1][1] && board[1][1] == board[2][2] && board[0][0]!= ' ')
   {
    
    
      return board[0][0];
   }
     if(board[0][2]==board[1][1] && board[1][1]==board[2][0] && board[0][2] !=' ')
     {
    
    
            return board[0][2];
     }

  if(IsFull(board,row,col)==1)
  {
    
    
  return ' q'; 
  }
 return 'C';//代表还要继续进行
}

将这些函数连接起来,至此我们game.c文件已经写完

运行代码就可以玩游戏了。

从三子棋到五子棋的拓展

从三子棋到五子棋大部分函数不需要动
要改的点
1,ROW,COL在头文件的值改变棋盘的大小
2,判断输赢的条件需要改动
3,电脑下棋的Computer_AI函数

这里只写出判断输赢的改动,电脑Computer_AI种类太多不易一一列出

CheckWin函数在五子棋下的实现

从三子棋类比到五子棋发现一一列举情况太过复杂

这里通过for循环的方式来进行相同字符的比较

这里的两个for循环可以看成扫描

这里写出对角线上的扫描

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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;//到这里说明棋盘已满返回1
   }
 char CheckWin(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-4;j++)//一次就要检查5个元素的值最后一次检查就是数组倒数第五个元素
        {
    
    
       if (board[i][j] == board[i][j+1] && board[i][j+1] == board[i][j+2] && board[i][j+2] == board[i][j+3] && board[i][j+3] == board[i][j+4] && board[i][j] != ' '
            return board[i][j];//原理与三子棋的相似
        }
       }
       
 
      for(i=0;i<col;i++)//判断列
      {
    
    
      int j=0;
       for(j=0;j<row-4;j++)
       {
    
    
         if(board[j][i]==board[j+1][i]&&board[j+1][i]==board[j+2][i]&&board[j+2][i]==board[j+3][i]&&board[j+3][i]==board[j+4][i]&&board[j][i]!=' ')
         {
    
    
         return board[j][i];
         }
       }
      }

//判断对角线元素是否相同(有两种对角线)
//这里对角线上的元素不一定正好是棋盘的对角线所以用for循环的嵌套
//有两种情况
//1.从第一条对角线开始每一次检测完后向下平移1,行不变继续检测。
//2.从第一条对角线开始每一次检测完后向右平移1,列不变继续检测
     for(i=0;i<row-4;i++)
     {
    
    
         for(j=0;j<col-4;j++)
         {
    
    
         if (board[i+j][j] == board[i + j + 1][j + 1] && board[i + j + 1][j + 1] == board[i +j+ 2][j+ 2] && board[i + j+ 2][j + 2] == board[i +j+ 3][j + 3] && board[i +j+ 3][j+ 3] == board[i +j+ 4][j + 4] && board[i+j][j] != ' ')
         //从第一条对角线开始每一次检测完后向下平移1,列不变继续检测
         {
    
    
         return board[i+j][j];
         }
          if (board[i][i+j] == board[i + 1][i + j + 1] && board[i + 1][i + j + 1] == board[i+ 2][i +j+ 2] && board[i + 2][i + j+ 2]== board[i + 3][i +j+ 3] && board[i + 3][i +j+ 3] == board[i + 4][i +j+ 4] && board[i][i+j] != ' ')
          //从第一条对角线开始每一次检测完后向右平移1,行不变继续检测
          {
    
    
          return board[i][i+j];
          }
         }
      }

//第二种对角线思路类似
       for(i=0;i<row-4;i++)
       {
    
    
       int j=0;
        for(j=0;j<row-4;j++)
        {
    
    
         if (board[i][row - i - 1-j] == board[i+ 1][row - i - 2-j] && board[i + 1][row - i - 2-j] == board[i + 2][row - i - 3-j] && board[i + 2][row - i - 3-j] == board[i + 3][row - i - 4-j] && board[i + 3][row - i - 4-j] == board[i + 4][row - i - 5-j] && board[i][row - i - 1-j] != ' ')
         {
    
    
         //左平移来扫描
         return board[i][row-i-j-1];
         }
          if (board[i+j][row - i - 1-j] == board[i + 1+j][row - i - 2-j] && board[i + 1+j][row - i - 2-j] == board[i + 2+j][row - i - 3-j] && board[i + j+2][row - i - 3-j] == board[i +j+ 3][row - i - 4-j] && board[i +j+ 3][row - i - 4-j] == board[i + j+4][row - i - 5-j] && board[i+j][row - i - 1-j] != ' ')
          //下平移来扫描
          {
    
    
          return board[i+j][row - i - 1-j];
          }
          
        }
       }

    if (IsFull(board, row, col) == 1)
    {
    
    
    return 'q';
    }
  return 'C';//原理与三子棋相同
 }

这篇文章是我总结来的,或许有一些问题,还麻烦大家斧正

猜你喜欢

转载自blog.csdn.net/dodamce/article/details/113131782