【C语言】实现至臻扫雷游戏(含为0展开、含源码)

目录

前言

一、准备工作

二、线实现菜单逻辑

三、游戏逻辑的实现

3.1、数组的实现.

3.2、数组的显示

3.3、随机雷的存入

3.4、显示坐标附近的雷

3.6、坐标输入

3.7、页面清空

3.8判断输赢

3.9递归实现展开

四、至臻扫雷源码



前言

在我们学习了这么久的C语言后,我们可以尝试的做做小游戏,达成一下自己的小成就,满足一下自己该死的虚荣心

一、准备工作

首先在源文件中创建两个源文件

  • caidan.c文件:用于实现游戏菜单
  • game.c文件:负责实现游戏领逻辑

在头文件中创建game.h文件,实现在caidan.c文件中调用game.c文件的逻辑。


二、线实现菜单逻辑

在caidan.c文件中先实现游戏菜单逻辑

#include <stdio.h>

void caidan() {
	printf("--------------扫雷--------------\n");
	printf("-------    1.开始游戏      -----\n");
	printf("-------    0.退出          -----\n");
	printf("--------------------------------\n");
}

int main() {
	int xz = 0;
	do {
		caidan();
		printf("请输入你的选择:>");
		scanf("%d", xz);
		switch (xz)
		{
		case 1:
			printf("game\n");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误!!\n");
			break;
		}
	} while(xz);
	return 0;
}

我们先在case 1中打印game,以便我们检查逻辑是否正确。

三、游戏逻辑的实现

在菜单实现后,我们来重点研究扫雷是这么实现的。

在我们游玩扫雷游戏是后,我们点击一个空白块后,可能是雷,也可能是安全地,如果是安全地的话会返回附近周围的雷的个数。

如上图中我点击了右上角的一个空白块之后这个空白块之后,并不是雷,所以返回这个安全块周围8个空白块的雷的个数。

既是我如果选择了黄色框中的安全空白地后它就会返回橙色框中的地区的雷数

但是如果是刚刚我们选择在左上角,应该这么查看周围八个位置空地呢?

那么我们就必须把游戏区间限制在我们创定的二维数组的限制下了,只要上下左右各限制一行就可以实现我们想要实现的逻辑。(即为行少两行,列少两列)

那么基础逻辑搞明白后我们便开始实操


3.1、数组的实现.

我们先在caidan.c中创建我们需要的数组(以10行10列为例子),存储10个雷。

char boom[10][10] = { 0 }; //存雷数组
char game[10][10] = { 0 }; //游戏数组

为什么需要创建两个数组呢?因为我们显示附近有多少各雷时候需要改变数组的内容,那么为了不影响雷的存放,我们需要用两个数组。

在caidan.c中创建好了后,我们也需要在实现初始化数组

在game.h中定义函数

void init_Content(char arr[10][10],int rows, int cols ,char a);//初始内容 a是需要初始化的格式

这样我们发现行与列都是需要大面积使用的,若我们后期需要修改行列就会非常麻烦,我们就需要在game.h中定义上两个defin常量就可以解决这个问题了

#define ROWS 5 //总体行
#define COLS 5 //总体列

#define ROW ROWS-2 //游戏界面行
#define COL COLS-2 //游戏界面列

#define BOOM 1 //雷的个数

void init_Content(char arr[10][10],int rows, int cols ,char a);//初始内容 a是需要初始化的格式

在game.h中定义后,需要在game.c中实现,但是在实现之前我们需要在caidan.c中传入我们需要初始话的数组。

但是我们为了获取到init_Content函数,我们需要引入game.h的头文件(自定义的头文件用双引号引用)

#include <stdio.h>

#include "game.h"

void caidan() {
	printf("--------------扫雷--------------\n");
	printf("-------    1.开始游戏      -----\n");
	printf("-------    0.退出          -----\n");
	printf("--------------------------------\n");
}

int main() {
	int xz = 0;

    char boom[ROWS][COLS] = { 0 }; //存雷数组
	char game[ROWS][COLS] = { 0 }; //游戏数组

	do {
		caidan();
		printf("请输入你的选择:>");
		scanf("%d", xz);
		switch (xz)
		{
		case 1:
			init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
			init_Content(game, ROWS, COLS, '*');//初始化 游戏数组
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误!!\n");
			break;
		}
	} while(xz);
	return 0;
}

在game.c中实现init_Content函数

因为init_Content函数在game.c中未定义所以也需要在game.c文件中引用game.h头文件

#include "game.h"

void init_Content(char arr[ROWS][COLS], int rows, int cols,char a) { //初始化数组内容为字符0
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			arr[i][j] = a;
		}
	}
}

3.2、数组的显示

在初始话结束后,我们需要查看一下是否全部初始化成功

所以我们也需要创建一个查看数组内容的函数

game.h 中

#define ROWS 5 //总体行
#define COLS 5 //总体列

#define ROW ROWS-2 //游戏界面行
#define COL COLS-2 //游戏界面列

#define BOOM 1 //雷的个数

void init_Content(char arr[ROWS][COLS],int rows, int cols ,char a);//初始内容 a是需要初始化的格式

void print_interface(char arr[ROWS][COLS], int rows, int cols);//打印扫雷界面

caidan.c中(减少篇幅,只写出case 1)

case 1:
    init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
    init_Content(game, ROWS, COLS, '*');//初始化 游戏数组

    print_interface(boom, ROWS, COLS);//打印 存雷数组
    printf("\n");
    print_interface(game, ROWS, COLS);//打印 游戏数组

    break;

game.c中

void print_interface(char arr[ROWS][COLS], int rows, int cols) {//打印数组
	int i = 0;
	for (i = 0; i <= cols - 2; i++) {//打印格式化数列
		printf("%2d| ", i);
	}

	printf("\n");
	for (int p = 0; p <= cols - 2; p++) {//打印美化横线
		if (p < cols - 2) {
			printf("-- -");
		}
		else {
			printf("--");
		}
	}

	printf("\n");

	for (i = 1; i < rows - 1; i++) { 
		int j = 0;
		printf("%2d| ", i);//打印有多少行

		for (j = 1; j < cols - 1; j++) {//打印数组内容
			printf("%2c| ", arr[i][j]);
		}
		printf("\n");

		for (int p = 0; p <= cols - 2; p++) {//打印美化横线
			if (p < cols - 2) {
				printf("-- -");
			}
			else {
				printf("--");
			}
		}

		printf("\n");
	}
}

这样我们就把基础的数组完成了


3.3、随机雷的存入

我们需要每次游玩雷的存储位置都不同,不然这游戏就很鸡肋。

这时候我们就需要用到C语言的随机数组生成rand()函数,相关细节可以查询cplusplus官网文档🔗:cplusplus.com - The C++ Resources Network

我们就利用rand()函数来设计一个set_boom()随机纯如雷的函数

game.h 中,因为我们发现两个.c文件都系要stdio.h头文件,我们就直接放入到game.h头文件中,这样就可以只引用game.h文件就可以完成对stdio.h头文件的引用

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ROWS 5 //总体行
#define COLS 5 //总体列

#define ROW ROWS-2 //游戏界面行
#define COL COLS-2 //游戏界面列

#define BOOM 1 //雷的个数

void init_Content(char arr[ROWS][COLS],int rows, int cols ,char a);//初始内容 a是需要初始化的格式

void print_interface(char arr[ROWS][COLS], int rows, int cols);//打印扫雷界面

void set_boom(char arr[ROWS][COLS], int rows, int cols);//随机分布雷

caidan.c中(减少篇幅,只写出case 1)

case 1:
    init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
    init_Content(game, ROWS, COLS, '*');//初始化 游戏数组

    set_boom(boom, ROW, COL);//随机分布雷

    print_interface(boom, ROWS, COLS);//打印 存雷数组
    printf("\n");
    print_interface(game, ROWS, COLS);//打印 游戏数组

    break;

game.c中

void set_boom(char arr[ROWS][COLS], int row, int col) {
	int count = 0;
	while (count < BOOM) {
		int i = rand() % row + 1;
		int j = rand() % col + 1;
		if (arr[i][j] == '0') {
			arr[i][j] = '1';
			count++;
		}
	}

}

这样我们就完成了随机雷的存储问题了。


3.4、显示坐标附近的雷

我们只需要判断周围数组和有多少,那雷就有多少嘛(因为‘1’是雷呀🐵)

game.c中

void print_boom(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y, int row, int col) {  //显示坐标附近的雷
	int count = (boom[x - 1][y + 1] + boom[x][y + 1] + boom[x + 1][y + 1] + 
		boom[x + 1][y] + boom[x - 1][y] + 
		boom[x - 1][y - 1] + boom[x][y - 1] + boom[x + 1][y - 1]) - (8 * '0');	
	game[x][y] = count + '0';
}

这样之后我们下次打印game数组后就可以看到刚刚我们坐标周围的雷的个数了


3.6、坐标输入

caidan.h

case 1:
			
    init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
    init_Content(game, ROWS, COLS, '*');//初始化 游戏数组

    set_boom(boom, ROW, COL);//随机分布雷

    print_interface(boom, ROWS, COLS);//打印 存雷数组
    printf("\n");
    print_interface(game, ROWS, COLS);//打印 游戏数组
	do {
	    printf("请你输入坐标:>");
		scanf("%d %d", &x, &y);
				
		if (game[x][y] != '*') {
				printf("非法坐标\n");
				continue; // 如果不是0返回1表示坐标已经排查过了
		    }

		print_boom(boom, game, x, y, ROW, COL);//显示坐标附近的雷
    }while(1);
    
    break;

但是我们这样每次输入都会出现一次新的棋盘在原来的棋盘下面,这样很难看,我们就需要用到清空屏幕使得每次出现的棋盘都是唯一的棋盘


3.7、页面清空

我们只需要在需要清空页面逻辑的地方加上system("cls"),即可清空页面

system是使用Windows指令的函数,"cls"是Windows指令清屏的意思,需要调用<Windows.h>头文件,更多细节可以查询cplusplus官网文档🔗:cplusplus.com - The C++ Resources Network

如我们需要在选择菜单后我们就不需要再显示菜单页面我们就在一进入case 1就加上system("cls")就可以达成效果,而且我们也需要再每次输入后也需要清空棋盘那么也加在显示附近的雷后买面加上system("cls")即可以完成啦

case 1:
	system("cls");	//清屏	
    init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
    init_Content(game, ROWS, COLS, '*');//初始化 游戏数组

    set_boom(boom, ROW, COL);//随机分布雷

    print_interface(boom, ROWS, COLS);//打印 存雷数组
    printf("\n");
    print_interface(game, ROWS, COLS);//打印 游戏数组
	do {
	    printf("请你输入坐标:>");
		scanf("%d %d", &x, &y);
		
        		
		if (game[x][y] != '*') {
				printf("非法坐标\n");
				continue; // 如果不是0返回1表示坐标已经排查过了
		    }

		print_boom(boom, game, x, y, ROW, COL);//显示坐标附近的雷

        system("cls");//清屏
    }while(1);
    
    break;

在game.h中调用头文件

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>

#define ROWS 5 //总体行
#define COLS 5 //总体列

#define ROW ROWS-2 //游戏界面行
#define COL COLS-2 //游戏界面列

#define BOOM 1 //雷的个数

void print_interface(char arr[ROWS][COLS], int rows, int cols);//打印扫雷界面

void init_Content(char arr[ROWS][COLS],int rows, int cols ,char a);//初始内容 a是需要初始化的格式

void set_boom(char arr[ROWS][COLS], int rows, int cols);//随机分布雷

void print_boom(char boom[ROWS][COLS],char game[ROWS][COLS], int x, int y, int row, int col);//显示坐标附近的雷

int is_win(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y, int count);//判断输赢

3.8判断输赢

判断输赢我们只需要计算还有多少个空位没点击,如果剩下和雷的数量一致则win,若选择到雷就直接炸死

game.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>

#define ROWS 5 //总体行
#define COLS 5 //总体列

#define ROW ROWS-2 //游戏界面行
#define COL COLS-2 //游戏界面列

#define BOOM 1 //雷的个数

void print_interface(char arr[ROWS][COLS], int rows, int cols);//打印扫雷界面

void init_Content(char arr[ROWS][COLS],int rows, int cols ,char a);//初始内容 a是需要初始化的格式

void set_boom(char arr[ROWS][COLS], int rows, int cols);//随机分布雷

void print_boom(char boom[ROWS][COLS],char game[ROWS][COLS], int x, int y, int row, int col);//显示坐标附近的雷

int is_win(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y, int count);//判断输赢

game.c

int is_win(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y, int count) {
	int i = 0;
	int j = 0;
	int cou = 0;
	//int sum = (ROW) * (COL) - BOOM;
	if (boom[x][y] == '1') {// 如果是1 返回 -1 表示输了
		return -1;
	}
	
	for (i = 1; i < ROWS - 1; i++) {
		int j = 0;
		for (j = 1; j < COLS - 1; j++) {//打印数组内容
			if (game[i][j] != '*') {
				cou = cou + 1;
			}
		}
	}
	if (cou == (ROW) * (COL)-BOOM) {
		return 0;
	}
}

caidan.h

case 1:
	system("cls");	//清屏		
    init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
    init_Content(game, ROWS, COLS, '*');//初始化 游戏数组

    set_boom(boom, ROW, COL);//随机分布雷

    print_interface(boom, ROWS, COLS);//打印 存雷数组
    printf("\n");
   do {
				printf("请你输入坐标:>");
				scanf("%d %d", &x, &y);
				
				if (game[x][y] != '*') {
					printf("非法坐标\n");
					continue; // 如果不是0返回1表示坐标已经排查过了
				}

				print_boom(boom, game, x, y, ROW, COL);//显示坐标附近的雷

                system("cls");	//清屏

				iswin = is_win(boom, game, x, y, count);//判断输赢与坐标合法性
				if (iswin == 0) {
					printf("恭喜你!你赢了!\n");
					print_interface(boom, ROWS, COLS);
					break;
				}
				else if (iswin == -1) {
					printf("很遗憾,你被炸死了\n");
					print_interface(boom, ROWS, COLS);
					break;
				}

				print_interface(game, ROWS, COLS);//打印 游戏数组
				printf("\n");
				//print_interface(boom, ROWS, COLS);//打印 存雷数组
			} while (1);
    
    break;

这样我们一个建议的扫雷就实现啦!💖🥂不过这个扫雷也是很鸡肋,因为如果是周围没有雷的话他不会自动展开还需要手动一个一个去敲。

我们也需要实现和上图一样,如果如今没有雷就自动展开,知道附近有雷显示出附近的雷截至。

这时候就用到了我们递归调用啦!(ps:若是不了解递归的看官们可以去看看不才的递归文章哟🔗:【C语言】函数递归-CSDN博客


3.9递归实现展开

我们只需要把上面的print_boom()改为递归模式就可以实现自动展开功能了,我们需要把为0的改为' '空格既可以避免出现死递归的情况,这也是把数组定义为char的原因

if (x > 0 && y > 0 && x < ROWS - 1 && y < COLS - 1 && game[x][y] != ' ' && boom[x][y] != '1') {
		int count = (boom[x - 1][y + 1] + boom[x][y + 1] + boom[x + 1][y + 1] +
			boom[x + 1][y] + boom[x - 1][y] +
			boom[x - 1][y - 1] + boom[x][y - 1] + boom[x + 1][y - 1]) - (8 * '0');
		game[x][y] = count + '0';
		//boom[x][y] = ' ';//把以输入的坐标变为' '空
		if (game[x][y] == '0' && game[x][y] != ' ') {
			game[x][y] = ' ';
			for (int i = x - 1; i <= x + 1; i++) {
				for (int j = y - 1; j <= y + 1; j++) {
					print_boom(boom, game, i, j);
				}
			}
		}

四、至臻扫雷源码

game.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void print_interface(char arr[ROWS][COLS], int rows, int cols) {//打印数组
	int i = 0;
	for (i = 0; i <= cols - 2; i++) {//打印格式化数列
		printf("%2d| ", i);
	}

	printf("\n");
	for (int p = 0; p <= cols - 2; p++) {//打印美化横线
		if (p < cols - 2) {
			printf("-- -");
		}
		else {
			printf("--");
		}
	}

	printf("\n");

	for (i = 1; i < rows - 1; i++) {
		int j = 0;
		printf("%2d| ", i);//打印有多少行

		for (j = 1; j < cols - 1; j++) {//打印数组内容
			printf("%2c| ", arr[i][j]);
		}
		printf("\n");

		for (int p = 0; p <= cols - 2; p++) {//打印美化横线
			if (p < cols - 2) {
				printf("-- -");
			}
			else {
				printf("--");
			}
		}

		printf("\n");
	}
}


void init_Content(char arr[ROWS][COLS], int rows, int cols, char a) { //初始化数组内容为字符0
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			arr[i][j] = a;
		}
	}
}


void set_boom(char arr[ROWS][COLS], int row, int col) {
	int count = 0;
	while (count < BOOM) {
		int i = rand() % row + 1;
		int j = rand() % col + 1;
		if (arr[i][j] == '0') {
			arr[i][j] = '1';
			count++;
		}
	}

}

void print_boom(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y) {  //显示坐标附近的雷
	if (x > 0 && y > 0 && x < ROWS - 1 && y < COLS - 1 && game[x][y] != ' ' && boom[x][y] != '1') {//判断是否越界,且是否为雷
		int count = (boom[x - 1][y + 1] + boom[x][y + 1] + boom[x + 1][y + 1] +
			boom[x + 1][y] + boom[x - 1][y] +
			boom[x - 1][y - 1] + boom[x][y - 1] + boom[x + 1][y - 1]) - (8 * '0');
		game[x][y] = count + '0';
		//boom[x][y] = ' ';//把以输入的坐标变为' '空
		if (game[x][y] == '0' && game[x][y] != ' ') {//如果附近八个相连位置没有雷且不是开始位置,则进入递归
			game[x][y] = ' ';
			for (int i = x - 1; i <= x + 1; i++) {
				for (int j = y - 1; j <= y + 1; j++) {
					print_boom(boom, game, i, j); //如果是‘0’递归查找附近八个内存位置
				}
			}
		}
	}
}


int is_win(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y, int count) {
	int i = 0;
	int j = 0;
	int cou = 0;
	//int sum = (ROW) * (COL) - BOOM;
	if (boom[x][y] == '1') {// 如果是1 返回 -1 表示输了
		return -1;
	}

	for (i = 1; i < ROWS - 1; i++) {
		int j = 0;
		for (j = 1; j < COLS - 1; j++) {//打印数组内容
			if (game[i][j] != '*') {
				cou = cou + 1;
			}
		}
	}
	if (cou == (ROW) * (COL)-BOOM) {
		return 0;
	}
}


void print_end(char arr[ROWS][COLS], int rows, int cols) {//打印数组
	int i = 0;
	for (i = 0; i <= cols - 2; i++) {//打印格式化数列
		printf("%2d| ", i);
	}

	printf("\n");
	for (int p = 0; p <= cols - 2; p++) {//打印美化横线
		if (p < cols - 2) {
			printf("-- -");
		}
		else {
			printf("--");
		}
	}

	printf("\n");

	for (i = 1; i < rows - 1; i++) {
		int j = 0;
		printf("%2d| ", i);//打印有多少行

		for (j = 1; j < cols - 1; j++) {//打印数组内容
			if (arr[i][j] == '1')
				printf("%2c| ", '*');
			else if (arr[i][j] == '0')
				printf("%2c| ", '/');
		}
		printf("\n");

		for (int p = 0; p <= cols - 2; p++) {//打印美化横线
			if (p < cols - 2) {
				printf("-- -");
			}
			else {
				printf("--");
			}
		}

		printf("\n");
	}
}

caidan.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void caidan() {
	printf("--------------扫雷--------------\n");
	printf("-------    1.开始游戏      -----\n");
	printf("-------    0.退出          -----\n");
	printf("--------------------------------\n");
}

int main() {
	int xz = 0;
	char boom[ROWS][COLS] = { 0 }; //存雷数组
	char game[ROWS][COLS] = { 0 }; //游戏数组

	int x = 0;
	int y = 0;

	int count = ROW * COL - BOOM;

	int iswin = 1;

	srand((unsigned int)time(NULL));
	do {
		caidan();
		printf("请输入你的选择:>");
		scanf("%d", &xz);
		switch (xz)
		{
		case 1:
			system("cls");
			init_Content(boom, ROWS, COLS, '0');//初始化 存雷数组
			init_Content(game, ROWS, COLS, '*');//初始化 游戏数组

			set_boom(boom, ROW, COL);//随机分布雷
			print_interface(boom, ROWS, COLS);//打印 存雷数组
			printf("\n");
			print_interface(game, ROWS, COLS);//打印 游戏数组
			do {
				printf("请你输入坐标:>");
				scanf("%d %d", &x, &y);

				if (game[x][y] != '*') {
					printf("非法坐标\n");
					continue; // 如果不是0返回1表示坐标已经排查过了
				}

				print_boom(boom, game, x, y);//显示坐标附近的雷

				system("cls");

				iswin = is_win(boom, game, x, y, count);//判断输赢与坐标合法性
				if (iswin == 0) {
					printf("恭喜你!你赢了!\n");
					print_end(boom, ROWS, COLS);
					break;
				}
				else if (iswin == -1) {
					printf("很遗憾,你被炸死了!\n");
					print_end(boom, ROWS, COLS);
					break;
				}

				print_interface(game, ROWS, COLS);//打印 游戏数组
				printf("\n");
				//print_interface(boom, ROWS, COLS);//打印 存雷数组
			} while (1);


			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误!!\n");
			break;
		}
	} while (xz);
	return 0;
}

game.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>

#define ROWS 10 //总体行
#define COLS 10 //总体列

#define ROW ROWS-2 //游戏界面行
#define COL COLS-2 //游戏界面列

#define BOOM 20 //雷的个数

void print_interface(char arr[ROWS][COLS], int rows, int cols);//打印扫雷界面

void init_Content(char arr[ROWS][COLS], int rows, int cols, char a);//初始内容 a是需要初始化的格式

void set_boom(char arr[ROWS][COLS], int rows, int cols);//随机分布雷

void print_boom(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y);//显示坐标附近的雷

int is_win(char boom[ROWS][COLS], char game[ROWS][COLS], int x, int y, int count);//判断输赢

以上就是本章所有内容。若有勘误请私信不才。万分感激!

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值