• 【入门级小游戏】C语言数组函数:解析三(N)子棋


    在这里插入图片描述

    C语言也学习一段时间了,为了巩固我们学习的知识,今天我们写一个三子棋的小游戏。这对初学者是个大工程,跟着我一起开始吧。

    一.逻辑思维梳理

    我们今天写的是三子棋小游戏,说到游戏肯定就有很多模块组成,所以为了提升游戏的可维护性和移植性
    ,我们应该采用模块化编写程序,将不同的版块分装在不同文件下。
    在这里插入图片描述
    上图为不同文件的任务,接下来我们就开始逐步讲解我们三字棋的实现。

    二.代码板块的实现

    1.游戏主逻辑实现

    在c语言程序中 最重要的就是main函数,所以我们现在main函数中引用test函数,实现代码分装 使得我们的编写更有层次。
    接下来我们开始实现test函数。 在游戏的开始界面我们应该需要一个入口 可以选择玩游戏 或者退出游戏。do while循环可以很好的实现我们的需求。

    void test()
    {
    	int i = 0;
    	do
    	{
    		menu();
    		printf("请选择>");
    		scanf("%d", &i);
    		switch (i)
    		{
    		case 1:
    			game();
    			break;
    		case 0:
    			printf("退出游戏\n");
    			break;
    		default:
    			printf("选择错误,请重新选择:>\n");
    			break;
    		}
    	} while (i);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    switch在游戏界面选择中非常实用,在上图的代码逻辑实现中我们又引用了菜单meua函数和游戏主体函数game。
    meua菜单需要实现一个界面 提供玩游戏和 退出游戏的人两个选择 代码和效果图如下

    void menu()
    {
    	printf("*****************************\n");
    	printf("********  1.play    *********\n");
    	printf("********  0.exit    *********\n");
    	printf("*****************************\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    接下来我们着重讲解game函数。

    2.棋盘的打印的数组的创建

    在菜单选择开始游戏后我们进入到游戏后,我们应该可以看到三子棋的棋盘。在打印棋盘前,我们应该创建一个二维数组对应三子棋盘上的9个位置 接受棋子。

    #define ROW 3
    #define COL 3
    
    • 1
    • 2

    首先我们.h文件中定义好数组的行和列,而不是直接对数组规定行和列,这样有利于后续我们对三子棋升级优化

    	char board[ROW][COL];//创建3*3数组存放字符
    
    
    • 1
    • 2

    接着我们创建一个二维数组,用来接受存放旗子。
    在这里插入图片描述

    这是我们想要实现的棋盘效果,但是我们并没有对数组board初始化,所以数组中默认储存的是0,这就达不到上图的效果。
    所以在打印棋盘之前,我们应当先对棋盘初始化,将数组的元素都定义为空格。
    我们在test中引用函数 Int_board(board, ROW, COL);
    在game.h声明函数后 在game.c文件中编写这个函数。

    void Int_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++)
    		{
    			board[i][j] = ' ';
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里只需要用一个简单遍历 就可以实现。
    接下来我们就要打印棋盘。棋盘的每两行可以看成一个部分,一共有3个部分
    在这里插入图片描述
    具体的解释,我们依据代码窥探``

    void Print_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)
    		{
    			for (j = 0; j < col; j++)
    			{
    				printf("---");
    				if (j < col - 1)
    				{
    					printf("|");
    				}
    			}
    			printf("\n");
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    上面我们吧棋盘分为了三个部分,所以在整个打印代码段 都是被一个for套嵌的。之后只要编写一部分的打印就可以了。

    2.玩家下棋的实现

    在编写玩家下棋的代码部分时 我们需要注意一下几点:
    1.数组接受玩家下的棋子
    2.判断玩家坐标输入是否合法
    3.玩家输入坐标后 将棋盘打印出来

    void player_move(char board[ROW][COL], int row, int  col)
    {
    	printf("玩家下棋\n");
    	while (1)
    	{
    		printf("玩家输入坐标:>");
    		int a, b;
    		scanf("%d %d", &a, &b);
    		if (a >= 1 && a <= row && b >= 1 && b <= col)
    		{
    			if (board[a - 1][b - 1] == ' ')
    			{
    				board[a - 1][b - 1] = '*';
    				break;
    			}
    			else
    			{
    				printf("该坐标已被占用\n");
    			}
    		}
    		else
    		{
    			printf("坐标非法\n");
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    当玩家scanf输入坐标后,应该要判断坐标输入是否合法,如果玩家输入的坐标已经被占用或者超出了二维数组的范围,就应该循环再次进入玩家输入坐标的地方,知道坐标输入正确break跳出循环。
    当玩家正确输入坐标后,我们要讲新的棋盘打印出来。所以我们需要再次在test.c文件中引用 Print_board(board, ROW, COL); 函数。

    3.电脑随机下棋的实现
    void comper_move(char board[ROW][COL], int row, int  col)
    {
    	srand((unsigned int)time(NULL));
    	while (1)
    	{
    		printf("电脑下棋\n");
    		int x = rand() % row;
    		int y = rand() % col;
    		if (board[x][y] == ' ')
    		{
    			board[x][y] = '#';
    			break;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里需要注意的是电脑随机坐标应该如何生成,还是运用我们熟知的rand函数,srand函数他们需要引用头文件**#include **,rand函数借助srand种子由time强制转化为unsigned int类型时间戳生成的随机数应该不能超过二维数组行列的范围,所以我们写了

    		int x = rand() % row;
    		int y = rand() % col;
    
    • 1
    • 2

    这里我们有个结论可以 理解记忆一下
    在这里插入图片描述
    接着我们再将电脑随机下的棋 再打印出来。

    4.判断输赢

    游戏的结果判断 我们现在test.c文件中编写出逻辑 再去编写具体函数
    在这里我们规定 输赢平局的返回值
    在这里插入图片描述

    while (1)
    	{
    		player_move(board, ROW, COL);
    		print_board(board, ROW, COL);
    		//判断输赢
    		ret = is_win(board, ROW, COL);
    		if (ret != 'C')
    		{
    			break;
    		}
    		computer_move(board, ROW, COL);
    		print_board(board, ROW, COL);
    		//判断输赢
    		ret = is_win(board, ROW, COL);
    		if (ret != 'C')
    		{
    			break;
    		}
    	}
    	if (ret == '#')
    		printf("电脑赢了\n");
    	else if (ret == '*')
    		printf("玩家赢了\n");
    	else if (ret == 'Q')
    		printf("平局\n");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    用ret接受 is_win函数的返回值
    接下来我们主要编写is_win函数 以及对他的优化升级
    在这里插入图片描述
    在三子棋中有这几种赢法

    static 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][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[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';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    我们将所有情况列举出来 再写出平局的情况将 字符返回到ret
    判断游戏是否需要继续。

    三.分析优化和升级

    在上面判断三子棋是否输赢时,我们是将所有的情况都一一列举出来。这样就限制的游戏的升级 所以我们能不能优化is_win函数呢?

    char is_win(char board[ROW][COL], int row, int col)
    {
    	int flag1 = 0;///玩家棋数
    	int flag2 = 0;//电脑棋数
    	int i, j;
    	for (i = 0; i < row; i++)///行  计算
    	{
    		flag1 = flag2 = 0;
    		for (j = 0; j < col; j++)
    		{
    			if (board[i][j] == '*')
    			{
    				flag1++;
    			}
    			if (board[i][j] == '#')
    			{
    				flag2++;
    			}
    		}
    		if (flag1 == col)///修改怎么应该实现N子棋
    		{
    			return '*';
    		}
    		if (flag2 == col)///修改怎么应该实现N子棋
    		{
    			return '#';
    		}
    	} 
    	for (j = 0; j < col; j++)///列  计算
    	{
    		flag1 = flag2 = 0;
    		for (i = 0; i < row; i++)
    		{
    			if (board[i][j] == '*')
    			{
    				flag1++;
    			}
    			if (board[i][j] == '#')
    			{
    				flag2++;
    			}
    		}
    		if (flag1 == col)///修改怎么应该实现N子棋
    		{
    			return '*';
    		}
    		if (flag2 == col)///修改怎么应该实现N子棋
    		{
    			return '#';
    		}
    	}
    	flag1 = flag2 = 0;
    
    	for (i = 0, j = 0; i < row, j < col; i++, j++)//正对角线
    	{
    		if (board[i][j] == '*')
    		{
    			flag1++;
    		}
    		if (board[i][j] == '#')
    		{
    			flag2++;
    		}
    	}
    	if (flag1 == col)///修改怎么应该实现N子棋
    	{
    		return '*';
    	}
    	if (flag2 == col)///修改怎么应该实现N子棋
    	{
    		return '#';
    	}	
    	flag1 = flag2 = 0;
    	for (i = 0, j = col-1; i<row, j >=0; i++, j--)//逆对角线
    	{
    		if (board[i][j] == '*')
    		{
    			flag1++;
    		}
    		if (board[i][j] == '#')
    		{
    			flag2++;
    		}
    	}
    	if (flag1 == col)///修改怎么应该实现N子棋
    	{
    		return '*';
    	}
    	if (flag2 == col)///修改怎么应该实现N子棋
    	{
    		return '#';
    	}
    	if (is_full(board, row, col) == 1)
    	{
    		return 'Q';
    	}
    
    	//继续
    	//没有玩家或者电脑赢,也没有平局,游戏继续
    	return 'C';
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    上图我用计数器写了 判断棋盘输赢的代码,虽看起来没有原来简单,但是如果升级到N子棋时,这种写法就比较方便
    在这里插入图片描述
    在我玩了七七九十九局后 感觉这个电脑实在太蠢了,所以我们还有什么方法能将电脑下棋更智能些呢? 我们在下篇讲解。

    关于三子棋游戏的分享就到这里。
    感谢相遇,共同进步。

  • 相关阅读:
    Windows 11 企业版新功能介绍
    jdk21(最新版) download 配置(linux window mac)
    java并发编程,lock(),trylock(),lockInterruptibly()的区别
    APP应用加固实战案例:贪玩蓝月
    什么是space-around
    10、MyBatis的缓存
    C/C++教程 从入门到精通《第十六章》—— 网络编程详解
    VUE(5) : vue-element-admin[5] : 打包及nginx部署
    js刷题常用基础函数&常用快捷键
    Vue3父子组件数据传递
  • 原文地址:https://blog.csdn.net/qq_43289447/article/details/127773465