C语言实现入门级小游戏——三子棋(万字长篇)

Posted Living_Amethyst

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言实现入门级小游戏——三子棋(万字长篇)相关的知识,希望对你有一定的参考价值。

       今天博主将为大家带来C语言入门级小游戏——三子棋(井字棋)的详细介绍,希望这篇文章对大家能有帮助 。  

    在编程的学习过程中,编写一些中这样的小游戏,是非常有乐趣的事情,完成之后会非常非常有成就感,我们需要这样的小项目来提高对编程学习的信心,培养对编程的兴趣,由于博主是一个编程学习的小白,所以这只是三子棋的初级版,此版本只能实现人机对战,并且电脑随机下棋电脑的算法比较低级,在后续的学习中,我会持续更新,不断对这个小游戏进行优化,期待大家的关注,还有,这篇文章中若有错误或不当的地方,欢迎大家指正! 

关于这个项目的代码,欢迎访问我的gitee代码仓库:

 Living_Amethyst/code2021 - Gitee.com

  那么正文开始咯!

目录

一.游戏的整体思路

二.创建游戏菜单 

三.游戏主体的实现 

1.棋盘的初始化 

2.打印棋盘 

3.玩家下棋 

4.电脑自动下棋 

5.游戏胜负的判断

四.全部的代码 

五.游戏的运行实况 


一.游戏的整体思路

为了让代码的可读性更高,思维性更强,我们需要创建三个文件来完成这个项目

  1. test.c   —— 测试游戏
  2. game.h—— 游戏函数的声明
  3. game.c—— 游戏函数的实现

二.创建游戏菜单 

作为游戏,菜单是肯定少不了的,这里我们也为三子棋小游戏准备了游戏的菜单

由于一进入游戏,我们首先就要看到菜单,所以这里我们采用do...while循环

我们的菜单需要实现的功能有:

  1. 游戏的进入
  2. 游戏的退出
  3.  非法输入的返回提示和说明
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:
			game();
			break;
		case 0:
			printf("退出游戏\\n");
			break;
		default:
			printf("选择错误\\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}

  这里我们创建了test函数和menu函数,为的是让逻辑更清晰,现在我们的菜单部分就完成了

对于这个游戏菜单,我们能实现的游戏功能有: 

  • 输入1进入游戏
  • 输入0退出游戏
  • 输入其他数字提示选择错错误,将重新输入

 

                                             这是菜单的运行效果

三.游戏主体的实现 

1.棋盘的初始化 

    这张图大概就是棋盘的样子,为了实现棋盘,我们需要用到二维数组,在下棋时,我们要将数据存储到一个字符的二维数组中,我们设定玩家下棋是 ' * ',电脑下棋是 ‘ # ’

  通过这个函数,我们实现了棋盘的初始化,我们给数组中每个元素初始化为一个空格

代码如下 

char board[ROW][COL] = { 0 };
//函数声明
void InitBoard(char board[ROW][COL], int row, int col);
//函数定义
void InitBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0;i < row; i++)
	{
		for (j = 0;j < col;j++)
		{
			board[i][j] = ' ';
		}
	}
}
//函数调用
InitBoard(board, ROW, COL);

   这里有一个小细节,在数组的初始化过程中,我并没有在[ ]中输入具体的数字,而是用ROW,COL取代,这是因为通过这样操作,我们可以用define定义它们为常量,这样如果我们想修改成五子棋或n子棋,就不用一个一个改了,直接在define定义常量时修改即可 

 

2.打印棋盘 

 对棋盘的打印,我我们使用DisplayBoard这一函数来实现,代码如下:

//函数声明
void DisplayBoard(char board[ROW][COL], int row, int col);
//函数定义
void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0;i < row;i++)
	{
		//数据
		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");
		}
	}
}
//函数调用
DisplayBoard(board,ROW,COL);

 值得一提的是,在函数调用部分我们一开始写的代码并不是这个,而是

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0;i < row;i++)
	{
		//数据
	    printf(" %c | %c | %c \\n", board[i][0],board[i][1],board[i][2]);
	    //分割行
		if (i < row - 1)  //第三行数据不用打印分割行
		{
			printf("---|---|---\\n");
		}

但很快,我们就发现了这个代码的局限性

         版本1,不方便代码的改动,如要打印10*10的棋盘,无法完成,只能10*3,也就是我们把这个代码写死了

所以我们做了如下的改动,希望大家好好体会两者的区别

下面展示一下棋盘打印出来的效果! 

 

 是不是有点那个意思了?

3.玩家下棋 

接下来我们来实现玩家下棋这一功能。

为了完成这一功能,我们需要注意以下几点: 

  1. 接收玩家所输入的地址位置(我们所判断的数组元素应该是玩家输入值-1
  2. 判断玩家所输入的地址所在是否已有落子
  3. 将玩家的棋子落入对应处

代码如下:

//函数的声明
void player_move(char board[ROW][COL],int row,int col);
//函数的定义
void player_move(char board[ROW][COL], int row, int col)
{
	printf("玩家下棋:\\n");
	int x = 0;
	int y = 0;
	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");
		}
	}
}
//函数的调用
 //玩家下棋
	  player_move(board,ROW,COL);

	 //棋下完要打印
	  DisplayBoard(board, ROW, COL);
	  ret = is_win(board, ROW, COL);

4.电脑自动下棋 

与前面的玩家下棋类似,只是这里我们需要电脑随机下棋,所以我们要用到rand函数获取随机值

我们需要创建随机数,这就需要调用以下头文件:

<stdlib.h>
<time.h>

同时,我们还需要使用: 

srand((unsigned int)time(NULL)); 来进行初始化
x = rand() % row;
y = rand() %  col;     来进行创建坐标的随机数 

 代码实现如下

//函数的声明
void player_move(char board[ROW][COL],int row,int col);

//函数的定义
void computer_move(char board[ROW][COL], int row, int col)
	{
		int x = 0;
		int y = 0;
		printf("电脑下棋\\n");
		while (1)
		{
			x = rand() % ROW;//取余数3后范围是0~2
			y = rand() % COL;//0~2
			if (board[x][y] == ' ')
			{
				board[x][y] = '#';
				break;
			}
		}
	}
//函数的调用
 //电脑下棋
	  computer_move(board, ROW, COL);
	  DisplayBoard(board, ROW, COL); 

//用时间戳生成随机数
srand((unsigned int)time(NULL));

5.游戏胜负的判断

在我们下棋的过程中,由于规则的特殊性,我们需要在玩家和电脑下的每一步后都对比赛的结果进行判断,确保程序的逻辑正确性。

在这里,我们使用 Is_win()函数 来实现这一功能。

在这一函数中,我们需要实现以下四个逻辑:

  1. 返回四种不同的状态
  2. 玩家赢     :  返回'*'
  3. 电脑赢     :  返回'#'
  4. 平局         :  返回'Q'
  5. 游戏继续  :  返回'C'

首先我们要确立判断函数再整个游戏主体中的位置和作用,逻辑如下:

void game()
{
  //数据存储到一个字符的二维数组中,玩家下棋是' * ',电脑下棋是' # ' ,
  char board[ROW][COL] = { 0 };//数组的内容应该是全部空格(二维数组初始化一般是{})
  //初始化棋盘
  InitBoard(board, ROW, COL);
  //打印棋盘
  DisplayBoard(board,ROW,COL);
  //下棋
  char ret = is_win(board, ROW, COL);
  while (1)
   {
	  //玩家下棋
	  player_move(board,ROW,COL);

	 //棋下完要打印
	  DisplayBoard(board, ROW, COL);
	  ret = is_win(board, ROW, COL);
	  if (ret != 'C')
	  {
		  break;
	  }

	  //电脑下棋
	  computer_move(board, ROW, COL);
	  DisplayBoard(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");
  }
  
}

我们在实现Iswin()这一函数时,需要注意以下几点

  1. 判断行
  2. 判断列
  3. 判断对角线
  4. 判断棋盘是否已经放满   -- >  确认游戏是否继续(需要一个Is_full()函数来实现这一功能)

以下是Is-win()函数的实现:

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[0][2] == board[1][1]&&board[1][1] == board[2][0] && board[1][1] != ' ')
			{
				return board[1][1];
			}
		//判断平局
		if ( is_full(board, row, col)==1)
			{
				return 'Q';
			}
    
		//继续
	
		return 'C';
	
    }
	

 这个代码的缺陷是也只适用于三子棋,后续会进行改进

下面时is_full函数的实现

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

最后再看一下游戏整体的逻辑实现

void game()
{
  //数据存储到一个字符的二维数组中,玩家下棋是' * ',电脑下棋是' # ' ,
  char board[ROW][COL] = { 0 };//数组的内容应该是全部空格(二维数组初始化一般是{})
  //初始化棋盘
  InitBoard(board, ROW, COL);
  //打印棋盘
  DisplayBoard(board,ROW,COL);
  //下棋
  char ret = is_win(board, ROW, COL);
  while (1)
   {
	  //玩家下棋
	  player_move(board,ROW,COL);

	 //棋下完要打印
	  DisplayBoard(board, ROW, COL);
	  ret = is_win(board, ROW, COL);
	  if (ret != 'C')
	  {
		  break;
	  }

	  //电脑下棋
	  computer_move(board, ROW, COL);
	  DisplayBoard(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.h—— 游戏函数的声明

#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 player_move(char board[ROW][COL],int row,int col);

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

//判断输赢的代码
//玩家赢- '*'
//电脑赢- '#'
//平局---'Q'
//继续---'C'
char is_win(char board[ROW][COL], int row, int col);
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"


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



// (把每一格看成一组数据,一组一组地打印)
//这样代码的可扩展性就高了
void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0;i < row;i++)
	{
		//数据
		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");
		}
	}
}


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


	//电脑下棋
	void computer_move(char board[ROW][COL], int row, int col)
	{
		int x = 0;
		int y = 0;
		printf("电脑下棋\\n");
		while (1)
		{
			x = rand() % ROW;//取余数3后范围是0~2
			y = rand() % COL;//0~2
			if (board[x][y] == ' ')
			{
				board[x][y] = '#';
				break;
			}
		}
	}


	int is_full(char board[ROW][COL],int row,int col)
	{
		int i = 0;
		int j = 0;
		for (i = 0;i < row;i++)
		{
			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[0][2] == board[1][1]&&board[1][1] == board[2][0] && board[1][1] != ' ')
			{
				return board[1][1];
			}
		//判断平局
		if ( is_full(board, row, col)==1)
			{
				return 'Q';
			}
    
		//继续
	
		return 'C';
	
    }

 test.c   —— 测试游戏

#define _CRT_SECURE_NO_WARNINGS

#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 = is_win(board, ROW, COL);
  while (1)
   {
	  //玩家下棋
	  player_move(board,ROW,COL);

	 //棋下完要打印
	  DisplayBoard(board, ROW, COL);
	  ret = is_win(board, ROW, COL);
	  if (ret != 'C')
	  {
		  break;
	  }

	  //电脑下棋
	  computer_move(board, ROW, COL);
	  DisplayBoard(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");
  }
  
}

void test()
{
	int input = 0;
	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);
}
int main()
{
	test();
	return 0;
}

五.游戏的运行实况 

你看,这个电脑玩家是不是有点不太聪明的样子~

    其实,电脑的”聪明程度“取决于我们程序员的技术水平程度,如果我们给电脑设计更优秀的算法,那它也会更聪明哦 

 


 以上就是三子棋游戏的全部内容,相信大家看到这里应该也能够独立的完成属于自己专有的三子棋游戏了,也相信你在这过程中对编程有了更进一步的认知。

好了,这篇文章就到这里了,欢迎大家点赞评论哦! 

以上是关于C语言实现入门级小游戏——三子棋(万字长篇)的主要内容,如果未能解决你的问题,请参考以下文章

C语言实现入门级小游戏——扫雷

C语言—实现三子棋小游戏

C语言三子棋游戏与多子棋 (保姆级的实现过程)

万字解析:带你走进五子棋(普通版)和三子棋的世界(C语言)

C语言实现三子棋游戏

C语言从入门到入土(入门特别篇)三子棋小游戏及如何让朋友也能玩自己写的游戏(程序)(exe如何附带动态链接库dll)