C语言——数组(三子棋与扫雷)

目录

一.数组

1.数组

2.数组的创建

3.数组的初始化

 4.数组的使用

5.数组在内存中的存储

二.二维数组

 1.二维数组的创建

2.二维数组的初始化 

3.二维数组的使用

4.二维数组在内存中的存储 

三数组越界

四数组传参

4.1问题

4.2数组名

4.3冒泡排序的正确设计

五.三子棋

5.1打印棋盘 

5.2玩家下棋

5.3电脑下棋

5.4输赢的判断 

5.5输赢的优化 

5.6AI下棋

六.扫雷

6.1棋盘的打印

6.2埋雷

6.3排雷

6.4排雷的优化


一.数组

1.数组

数组是相同元素的集合体,在这里,我们说的数组通常指的是一维数组。

2.数组的创建

//数组的类型 数组名[数组元素个数]
int arr1[10];

注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数 组的概念。 

3.数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值。

数组在创建的时候如果想不指定数组的确定的大小就得一个一个初始化。数组的元素个数根据初始化的内容来确定。

int arr1[10] = {1,2,3};//10个元素
int arr2[] = {1,2,3,4};//4个元素
int arr[10]={0};//数组10个元素全为0

对于下面的数组,要分清它们各自在内存中的存储

char arr1[] = "abc";//c的后面有\0
char arr2[3] = {'a','b','c'};//c的后面无\0

 4.数组的使用

在数组中,数组的下标(第一个)是从0开始的,为了形成arr[0]=*(arr+0)。

在用for循环进行数组的读入与打印时,一定要控制好初始变量的值与判断条件,稍不注意就会出现溢出。

#include <stdio.h>
int main()
{
 int arr[10] = {0};//数组的不完全初始化
    //计算数组的元素个数
    int sz = sizeof(arr)/sizeof(int);//整个数组的大小/类型大小
 //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
 int i = 0;//做下标
 for(i=0; i<10; i++)
 {
 arr[i] = i;
 } 
 //输出数组的内容
 for(i=0; i<10; ++i)
 {
 printf("%d ", arr[i]);
 }
 return 0;
}

5.数组在内存中的存储

看一下代码:

#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    
 for(i=0; i<sz; ++i)
 {
 printf("&arr[%d] = %p\n", i, &arr[i]);
 }
 return 0;
}

输出的结果:

 从上面我们可以看出:数组的下标增大,元素的地址也在有规律(char为1字节,(+1))递增。

由此可以得出结论:数组在内存中是连续存放的

二.二维数组

 1.二维数组的创建

//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];

2.二维数组的初始化 

//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};

注意:二维数组如果有初始化,行可以省略,列不能省略 !

3.二维数组的使用

二维数组的使用也可以用下标来实现。

#include <stdio.h>
int main()
{
 int arr[3][4] = {0};
 int i = 0;
 for(i=0; i<3; i++)
 {
 int j = 0;
 for(j=0; j<4; j++)
 {
 arr[i][j] = i*4+j;
 }
 }
 for(i=0; i<3; i++)
 {
 int j = 0;
 for(j=0; j<4; j++)
 {
 printf("%d ", arr[i][j]);
 }
 }
 return 0;
}

4.二维数组在内存中的存储 

#include <stdio.h>
int main()
{
 int arr[3][4];
 int i = 0;
 for(i=0; i<3; i++)
 {
 int j = 0;
 for(j=0; j<4; j++)
 {
 printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
 }
 }
 return 0;
}

打印结果:

它们的分布如下:

说明:二维数组在内存中也是连续存储的。 

三数组越界

数组的下标是有范围限制的。 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。

数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的!

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    for(i=0; i<=10; i++)
   {
        printf("%d\n", arr[i]);//当i等于10的时候,越界访问了
   }
 return 0;
}

所以,作为一名合格的程序员,要做好越界的检查。

四数组传参

4.1问题

先来看一段代码:

//冒泡排序
#include <stdio.h>
void bubble_sort(int arr[])
{
 int sz = sizeof(arr)/sizeof(arr[0]);//这样对吗?
    int i = 0;
 for(i=0; i<sz-1; i++)
   {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
       {
            if(arr[j] > arr[j+1])
           {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
           }
       }
   }
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    bubble_sort(arr);//是否可以正常排序?
    for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
   {
        printf("%d ", arr[i]);
   }
    return 0;
}

执行结果:

为什么结果没有变呢?别急,让我们先来了解数组名。

4.2数组名

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5};
 printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    printf("%d\n", *arr);
    //输出结果
    return 0;
}

从上方我们可以看到,arr与&arr[0]的地址相同。而且,解引用arr后,打印的时1,这不就是arr[0]吗!

我们就可以这样说,数组名是首元素地址 。

int arr[10] = {0};
printf("%d\n", sizeof(arr));

 那么,上面代码的打印不是4吗,怎么是40?

说明:

1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。

2. &数组名,取出的是数组的地址,这时的数组名表示整个数组。

除了上面的特殊情况,一般数组名表示的时首元素的地址!

4.3冒泡排序的正确设计

当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。

所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。 那么,函数内部的 sizeof(arr) 结果是4。自然,sz计算出来是1,遇到for循环直接就跳过了。

解决:先把sz计算出来,再把sz进行传参。

void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
 //代码同上面函数
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);//是否可以正常排序?
    for(i=0; i<sz; i++)
   {
        printf("%d ", arr[i]);
   }
    return 0;
}

补充:关于sizeof与strlen的使用

先来看段代码:

#include<stdio.h>
int main()
{
	char acX[] = "abcdefg";
	char acY[] = { 'a','b','c','d','e','f','g' };
	printf("%d %d %d %d", sizeof(acX), sizeof(acY), strlen(acX), strlen(acY));
	return 0;
}

上面的输出结果会是什么呢?

解释:sizeof计算的是数组所占内存的大小,单位是字节。不限制类型。在acX中,结束的末尾有‘\0’,而acY则没有,在sizeof看来,这也是数组的一部分,所以计算时把'\0'也算进来。

strlen统计的是‘\0’之前字符串的个数。只有char类型能统计。在acX中能算出结果,而在acY中,没有‘\0’,统计的随机值。

五.三子棋

学习完数组的相关知识,我们可以来试着实现一个简单版三子棋。

以下只讲游戏思路实现。完整的代码放在最下方!

5.1打印棋盘 

我们需要用到一个char数组,里面存储空格,来让我们下棋。

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

在屏幕上要打印如下的形式:

画圆圈的分隔符只需要打印2行就行,即比打印空格的行数少1行。

画长方形的|型分隔符,在每一行打印到第三个空格时后面就不打印|,即比空格少打印一次。

void DisplayBoard(char board[][COL], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			//打印空格时要%c左右加空格
			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");
		}
	}
}

5.2玩家下棋

玩家落棋的位置我们用‘*’来表示。在得到玩家要下棋的坐标时,要进行判断:

该坐标是否在有效范围内(即在3*3的坐标内),该坐标的位置有没有被下过。如果符合,都要重新进行坐标的输入。

还有一个点,玩家下的坐标是从1开始的,在实现落子时坐标要减1。

void PlayBoard(char board[][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//玩家输入的坐标与程序的下标相差1
			if (board[x-1][y-1] == ' ')
			{
				board[x-1][y-1] = '*';
				break;
			}
			else
			{
				printf("该坐标已经被下过,请重新输入:>");
			}
		}
		else
		{
			printf("坐标非法,请重新输入:>");
		}
	}
}

5.3电脑下棋

在棋盘里随机落子‘#’,落子的位置为空才下,否则重新选择随机坐标。

随机数的生成用到函数 rand()->要限制范围与srand((unsigned int)time(NULL))的搭配

void ComputerBoard(char board[][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		if (AI(board, row, col))
			break;
		x = rand() % row;//rand()%row的随机范围0到3
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

5.4输赢的判断 

 在竖,横,斜的位置上,有3个相同的棋子相连(‘*’或‘#’)就算那方赢。那方赢了就返回那方的棋子。

如果直到棋盘上满棋子都没有出现上面的情况就算平局,返回”Q“

继续博弈就返回”C“

int Isfull(char board[][COL], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
				return 0;
		}
	}
	return 1;
}
char Iswin(char board[][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 (Isfull(board, row, col) == 1)
		return 'Q';
	return 'C';
}

5.5输赢的优化 

如果我们想在5*5的棋盘里玩三子棋,上面的输赢判断不就不适用了吗?

用两个for循环来控制坐标,判断条件控制好就没问题。

char Is_win(char board[ROW][COL], int row, int col)//优化
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)//判断每行
	{
		for (j = 0; j + 2 < col; j++)//j+2<col防止溢出
		{
			if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] != ' ')
				//判断每行是否有相连的3个元素,并且不是空格
			{
				return board[i][j];
			}
		}
	}
	for (j = 0; j < col; j++)//判断每列
	{
		for (i = 0; i + 2 < row; i++)
		{
			if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
				//判断每列是否有相连的3个元素,并且不是空格
			{
				return board[i][j];
			}
		}
	}
	for (i = 0; i + 2 < row; i++)//判断对角线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')
				return board[i][j];
		}
	}
	for (i = 2; i < row; i++)//判断反对角线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i - 1][j + 1] && board[i - 1][j + 1] == board[i - 2][j + 2] && board[i][j] != ' ')
				return board[i][j];
		}
	}
	if (1 == Isfull(board, row,col))
	{
		return 'Q';
	}
	else
		return 'C';
}

5.6AI下棋

在上面实现的电脑下棋中,电脑是选择随机下棋的。那如果想要让电脑下棋变成AI下棋,即让电脑变聪明,那应该怎么做呢?

无非就是在电脑落子时先判断棋盘上的情况,在得知你快要赢的时候把你的路堵死,让你赢不了。

bool AI(char board[][COL], int row, int col)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)//行
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i][j + 1]  && board[i][j+2] == ' '&&board[i][j]=='*')
			{
				board[i][j + 2] = '#';
				return true;
			}
			if (board[i][j] == board[i][j + 2] && board[i][j+1] == ' '&&board[i][j]=='*')
			{
				board[i][j + 1] = '#';
				return true;
			}
			if (board[i][j + 1] == board[i][j + 2] && board[i][j] == ' '&&board[i][j+1]=='*')
			{
				board[i][j] = '#';
				return true;
			}
		}
	}
	for (j = 0; j < col; j++)//列
	{
		for (i = 0; i + 2 < row; i++)
		{
			if (board[i][j] == board[i + 1][j] && board[i+2][j] == ' '&&board[i][j]=='*')
			{
				board[i + 2][j] = '#';
				return true;
			}
			if (board[i][j] == board[i + 2][j] && board[i+1][j] == ' '&&board[i][j]=='*')
			{
				board[i + 1][j] = '#';
				return true;
			}
			if (board[i + 1][j] == board[i + 2][j] && board[i][j] == ' '&&board[i+1][j]=='*')
			{
				board[i][j] = '#';
				return true;
			}
		}
	}
	for (i = 0; i + 2 < row; i++)//对角线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i + 1][j + 1] && board[i+2][j+2] == ' '&&board[i][j]=='*')
			{
				board[i + 2][j + 2] = '#';
				return true;
			}
			if (board[i+1][j+1] == board[i + 2][j + 2] && board[i][j] == ' '&&board[i+1][j+1]=='*')
			{
				board[i][j] = '#';
				return true;
			}
			if (board[i][j] == board[i + 2][j + 2] && board[i+1][j+1] == ' '&&board[i][j]=='*')
			{
				board[i+1][j+1] = '#';
				return true;
			}
		}
	}
	for (i = 2; i < row; i++)//反对角线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i - 1][j + 1] && board[i-2][j+2] == ' '&&board[i][j]=='*')
			{
				board[i - 2][j + 2] = '#';
				return true;
			}
			if (board[i-1][j+1] == board[i-2][j + 2] && board[i][j] == ' '&&board[i-1][j+1]=='*')
			{
				board[i][j] = '#';
				return true;
			}
			if (board[i][j] == board[i - 2][j + 2] && board[i-1][j+1] == ' '&&board[i][j]=='*')
			{
				board[i-1][j+1] = '#';
				return true;
			}
		}
	}
	return false;
}

void ComputerBoard(char board[][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		if (AI(board, row, col))
			break;
		x = rand() % row;//rand()%row的随机范围0到3
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

5.7完整代码

//头文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<stdbool.h>
#define ROW 3
#define COL 3

void InitBoard(char board[][COL], int row, int col);
void DisplayBoard(char board[][COL], int row, int col);
void PlayBoard(char board[][COL], int row, int col);
void ComputerBoard(char board[][COL], int row, int col);
char Iswin(char board[][COL], int row, int col);

char who_win(char board[ROW][COL], int row, int col);
//测试代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"//包含头文件

void menu()
{
	printf("********************\n");
	printf("***** 1.game *******\n");
	printf("***** 0.exit *******\n");
	printf("********************\n");
}
void game()
{
	char board[ROW][COL];
	InitBoard(board, ROW, COL);
	DisplayBoard(board, ROW, COL);
	char ret = 0;
	while (1)
	{
		printf("玩家下棋:>");
		PlayBoard(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//判断情况 *玩家赢 #电脑赢 C继续 Q平局
		ret=who_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		printf("电脑下棋:>\n");
		ComputerBoard(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		ret = who_win(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
		printf("玩家赢\n");
	else if (ret == '#')
		printf("电脑赢\n");
	else
		printf("平局\n");
}
int main()
{
	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("选择错误,请重新选择:>");
			break;
		}
	} while (input);
	return 0;
}
//游戏各函数实现的代码
#include"game.h"//包含头文件

void InitBoard(char board[][COL], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}
void DisplayBoard(char board[][COL], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			//打印空格时要%c左右加空格
			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");
		}
	}
}
void PlayBoard(char board[][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			//玩家输入的坐标与程序的下标相差1
			if (board[x-1][y-1] == ' ')
			{
				board[x-1][y-1] = '*';
				break;
			}
			else
			{
				printf("该坐标已经被下过,请重新输入:>");
			}
		}
		else
		{
			printf("坐标非法,请重新输入:>");
		}
	}
}

bool AI(char board[][COL], int row, int col)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)//行
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i][j + 1]  && board[i][j+2] == ' '&&board[i][j]=='*')
			{
				board[i][j + 2] = '#';
				return true;
			}
			if (board[i][j] == board[i][j + 2] && board[i][j+1] == ' '&&board[i][j]=='*')
			{
				board[i][j + 1] = '#';
				return true;
			}
			if (board[i][j + 1] == board[i][j + 2] && board[i][j] == ' '&&board[i][j+1]=='*')
			{
				board[i][j] = '#';
				return true;
			}
		}
	}
	for (j = 0; j < col; j++)//列
	{
		for (i = 0; i + 2 < row; i++)
		{
			if (board[i][j] == board[i + 1][j] && board[i+2][j] == ' '&&board[i][j]=='*')
			{
				board[i + 2][j] = '#';
				return true;
			}
			if (board[i][j] == board[i + 2][j] && board[i+1][j] == ' '&&board[i][j]=='*')
			{
				board[i + 1][j] = '#';
				return true;
			}
			if (board[i + 1][j] == board[i + 2][j] && board[i][j] == ' '&&board[i+1][j]=='*')
			{
				board[i][j] = '#';
				return true;
			}
		}
	}
	for (i = 0; i + 2 < row; i++)//对角线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i + 1][j + 1] && board[i+2][j+2] == ' '&&board[i][j]=='*')
			{
				board[i + 2][j + 2] = '#';
				return true;
			}
			if (board[i+1][j+1] == board[i + 2][j + 2] && board[i][j] == ' '&&board[i+1][j+1]=='*')
			{
				board[i][j] = '#';
				return true;
			}
			if (board[i][j] == board[i + 2][j + 2] && board[i+1][j+1] == ' '&&board[i][j]=='*')
			{
				board[i+1][j+1] = '#';
				return true;
			}
		}
	}
	for (i = 2; i < row; i++)//反对角线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i - 1][j + 1] && board[i-2][j+2] == ' '&&board[i][j]=='*')
			{
				board[i - 2][j + 2] = '#';
				return true;
			}
			if (board[i-1][j+1] == board[i-2][j + 2] && board[i][j] == ' '&&board[i-1][j+1]=='*')
			{
				board[i][j] = '#';
				return true;
			}
			if (board[i][j] == board[i - 2][j + 2] && board[i-1][j+1] == ' '&&board[i][j]=='*')
			{
				board[i-1][j+1] = '#';
				return true;
			}
		}
	}
	return false;
}

void ComputerBoard(char board[][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	while (1)
	{
		if (AI(board, row, col))
			break;
		x = rand() % row;//rand()%row的随机范围0到3
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}
int Isfull(char board[][COL], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
				return 0;
		}
	}
	return 1;
}

char Iswin(char board[ROW][COL], int row, int col)//优化
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)//判断每行
	{
		for (j = 0; j + 2 < col; j++)//j+2<col防止溢出
		{
			if (board[i][j] == board[i][j + 1] && board[i][j + 1] == board[i][j + 2] && board[i][j] != ' ')
				//判断每行是否有相连的3个元素,并且不是空格
			{
				return board[i][j];
			}
		}
	}
	for (j = 0; j < col; j++)//判断每列
	{
		for (i = 0; i + 2 < row; i++)
		{
			if (board[i][j] == board[i + 1][j] && board[i + 1][j] == board[i + 2][j] && board[i][j] != ' ')
				//判断每列是否有相连的3个元素,并且不是空格
			{
				return board[i][j];
			}
		}
	}
	for (i = 0; i + 2 < row; i++)//判断主对角线和主对角线平行的线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i + 1][j + 1] && board[i + 1][j + 1] == board[i + 2][j + 2] && board[i][j] != ' ')//判断主对角线和主对角线平行的线是否有三个相连,且不是空格
				return board[i][j];
		}
	}
	for (i = 2; i < row; i++)//判断反对角线和与反对角线平行的线
	{
		for (j = 0; j + 2 < col; j++)
		{
			if (board[i][j] == board[i - 1][j + 1] && board[i - 1][j + 1] == board[i - 2][j + 2] && board[i][j] != ' ')//判断反对角线和反对角线平行的线是否有三个相连,且不是空格
				return board[i][j];
		}
	}
	if (1 == Isfull(board, row,col))
	{
		return 'Q';
	}
	else
		return 'C';
}

六.扫雷

6.1棋盘的打印

在初始化棋盘时,要初始化两个棋盘,一个用来展示(初始化为‘*’),一个用埋雷(初始化为‘0’)

void InitBoard(char board[][COLS], int rows, int cols,char set)//传set,表示要初始化的字符
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

接着把要展示的棋盘(初始化为‘*’)打印到屏幕上。(加上行数与列数更容易观察)

void DisplayBoard(char board[][COLS], int row, int col)
{
	printf("------游戏区域------\n");
	///加行数
	for (int i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	printf(" -------------------");
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		//加列数
		printf("%d|", i);
		for (int j = 1; j <= row; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("------游戏区域------\n");
}

6.2埋雷

在上面初始化为‘0’的棋盘中进行随机埋雷。注意坐标如果重复就重新生成随机坐标。

void ArrageMine(char board[][COLS], int row, int col)
{
	int Mine_Num = EASY_MINE;
	while (Mine_Num)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			Mine_Num--;
		}
	}
}

6.3排雷

1.对玩家输入的坐标进行判断:该坐标是否在有效范围内:

2.该处是否是雷,如果是,游戏结束:如果不是,就显示周围雷的个数,继续游戏。

3.游戏结束的判断:一.玩家输入的坐标是雷:失败;二.玩家输入的坐标次数=该棋盘的总步数-雷的个数:胜利。

int Gain_Mine(char mine[][COLS], int x, int y)
{
	return mine[x - 1][y - 1] + mine[x - 1][y] +
		   mine[x - 1][y + 1] + mine[x][y + 1] +
		   mine[x + 1][y + 1] + mine[x + 1][y] +
		   mine[x + 1][y - 1] + mine[x][y - 1]-8*'0';

}

void PlayMine(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int Is_Win = ROW * COL - EASY_MINE;
	while (Is_Win)
	{
		printf("输入玩家要排雷的坐标>:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] != '1')
			{
				show[x][y] = Gain_Mine(mine, x, y)+'0';
				Is_Win--;
			}
			else
			{
				printf("此地有雷,游戏结束\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
		}
		else
		{
			printf("坐标非法,重新输入>:\n");
		}
		/*Sleep(1000);
		system("cls");*/
		DisplayBoard(show, ROW, COL);
	}
	if (Is_Win == 0)
	{
		printf("恭喜,排雷成功\n");
	}
	
}

注意:我们在计算周围雷的个数时,计算出来的时数字是字符数,但我们要的是真真正正的数字。所以每个字符减‘0’变成数字。

显示雷的数量在棋盘上的是字符,所以就加上+‘0’!

这两点要注意!! 

6.4排雷的优化

通过上面的游戏函数的实现,我们就大致实现扫雷的游戏逻辑。

但在实际的扫雷过程中:玩家在选择坐标时,如果周围8个坐标都不是雷,会展开一片。

如下,周围没有雷,周围的8个坐标都会依次往外递归,遇到雷就停止递归,展示雷的量。 

 

void Expand(char mine[][COLS], char show[][COLS], int x, int y,int *win)
{
	int count = Gain_Mine(mine,x,y);
	(*win)--;
	if (count == 0)
	{
		show[x][y] = ' ';
		if (show[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x - 1, y - 1,win);
		if (show[x - 1][y] == '*' && x - 1 > 0 && x - 1 < ROWS && y > 0 && y < COLS)
			Expand(mine, show, x - 1, y,win);
		if (show[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x - 1, y + 1,win);
		if (show[x][y - 1] == '*' && x > 0 && x < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x, y - 1,win);
		if (show[x][y + 1] == '*' && x > 0 && x < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x, y + 1,win);
		if (show[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x + 1, y - 1,win);
		if (show[x + 1][y] == '*' && x + 1 > 0 && x + 1 < ROWS && y > 0 && y < COLS)
			Expand(mine, show, x + 1, y,win);
		if (show[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x + 1, y + 1,win);
	}
	else
	{
		show[x][y] = count+'0';
	}
}

但以上递归并不是最优解。在往外递归的同时有可能会进行重复递归,影响效率。

现在的知识量不足以解决该问题,等以后在知识足够了在来解决!先在这挖个坑。

6.5完整源码

//头文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_MINE 10
void InitBoard(char board[][COLS], int rows, int cols,char set);
void DisplayBoard(char board[][COLS], int row, int col);
void ArrageMine(char board[][COLS], int row, int col);
void PlayMine(char mine[][COLS], char show[][COLS], int row, int col);
//游戏实现的函数
#include"game.h"//包含头文件
void InitBoard(char board[][COLS], int rows, int cols,char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}
void DisplayBoard(char board[][COLS], int row, int col)
{
	printf("------游戏区域------\n");
	///加列数
	for (int i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	printf(" -------------------");
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		//加行数
		printf("%d|", i);
		for (int j = 1; j <= row; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("------游戏区域------\n");
}
void ArrageMine(char board[][COLS], int row, int col)
{
	int Mine_Num = EASY_MINE;
	while (Mine_Num)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0')
		{
			board[x][y] = '1';
			Mine_Num--;
		}
	}
}
int Gain_Mine(char mine[][COLS], int x, int y)
{
	return mine[x - 1][y - 1] + mine[x - 1][y] +
		   mine[x - 1][y + 1] + mine[x][y + 1] +
		   mine[x + 1][y + 1] + mine[x + 1][y] +
		   mine[x + 1][y - 1] + mine[x][y - 1]-8*'0';

}
//感觉递归可优化
void Expand(char mine[][COLS], char show[][COLS], int x, int y,int *win)
{
	int count = Gain_Mine(mine,x,y);
	(*win)--;
	if (count == 0)
	{
		show[x][y] = ' ';
		if (show[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x - 1, y - 1,win);
		if (show[x - 1][y] == '*' && x - 1 > 0 && x - 1 < ROWS && y > 0 && y < COLS)
			Expand(mine, show, x - 1, y,win);
		if (show[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x - 1, y + 1,win);
		if (show[x][y - 1] == '*' && x > 0 && x < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x, y - 1,win);
		if (show[x][y + 1] == '*' && x > 0 && x < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x, y + 1,win);
		if (show[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y - 1 > 0 && y - 1 < COLS)
			Expand(mine, show, x + 1, y - 1,win);
		if (show[x + 1][y] == '*' && x + 1 > 0 && x + 1 < ROWS && y > 0 && y < COLS)
			Expand(mine, show, x + 1, y,win);
		if (show[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 < ROWS && y + 1 > 0 && y + 1 < COLS)
			Expand(mine, show, x + 1, y + 1,win);
	}
	else
	{
		show[x][y] = count+'0';
	}
}
void PlayMine(char mine[][COLS], char show[][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int Is_Win = ROW * COL - EASY_MINE;
	while (Is_Win)
	{
		printf("输入玩家要排雷的坐标>:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] != '1')
			{
				Expand(mine, show, x, y,&Is_Win);
				//show[x][y] = Gain_Mine(mine, x, y)+'0';
				//Is_Win--;
			}
			else
			{
				printf("此地有雷,游戏结束\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
		}
		else
		{
			printf("坐标非法,重新输入>:\n");
		}
		Sleep(1000);
		system("cls");
		DisplayBoard(show, ROW, COL);
	}
	if (Is_Win == 0)
	{
		printf("恭喜,排雷成功\n");
	}
}
//测试
#include"game.h"

void menu()
{
	printf("***************************\n");
	printf("*******  1.play  **********\n");
	printf("*******  0.exit  **********\n");
	printf("***************************\n");
}
void game()
{
	char mine[ROWS][COLS];
	char show[ROWS][COLS];
	InitBoard(mine, ROWS, COLS,'0');
	InitBoard(show, ROWS, COLS,'*');
	//打印
	DisplayBoard(show, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	//在mine数组里布置雷
	ArrageMine(mine, ROW, COL);
	DisplayBoard(mine, ROW, COL);//上帝视角
	//玩家排雷
	PlayMine(mine, show, ROW, COL);
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();
		printf("请选择>:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			break;
		default:
			printf("选择错误,请重新选择>:");
			break;
		}
	} while (input);
	return 0;
}

七最后

看到最后,不妨来个三连支持一下,你的支持时我更新动力的源泉,谢谢!

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值