qt实现俄罗斯方块

这是一个使用Qt库编写的俄罗斯方块游戏程序,主要包含Tetris类用于处理游戏逻辑,以及继承自QWidget的游戏窗口和“下一个方块”窗口,使用QLabel显示分数和控制提示。游戏中的方块由Block结构体表示,包含了位置、中心点和ID等信息。代码中还包括了方块的创建、旋转、移动和消行等功能实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虽然界面做的不美观,但是总算是实现了基本的功能。

首先写了一个俄罗斯方块的类Tetris,通过这个类来进行这个游戏的数据的处理;然后游戏窗口是继承的QWidget类,用来显示游戏的方块;“下一个方块”窗口也是继承的QWidget类,用来显示下一个方块;控制提示和分数的显示用的QLabel。然后将将这些控件整合到继承自QMainWindow的mainWindow类。

Tetris类:一共有7中不同形状的方块(如下图),每个方块由四个方格组成,Block结构体用来存储方块的方格坐、中心方格坐标、ID等数据,移动中的方块和下移个方块都是通过这个结构体来操作的;已经落下的方块的方格存储在二维数组box[][]中,x坐标从左到右为正方向,y坐标从上到下为正方向

  • 项目源文件

  • tetris.h
  • tetris.cpp
  • tetrisbox.h
  • tetrisbox.cpp
  • nexttetris.h
  • nexttetris.cpp
  • mainwindow.h
  • mainwindow.cpp

项目源文件

form.h

form.cpp

nexttetris.h

nexttetris.cpp

nexttetrisbox.h

tetri.h

tetri.cpp

tetrisbox.h

tetrisbox.cpp


form.h

#ifndef FORM_H
#define FORM_H

#include <QWidget>
#include <QPushButton>

namespace Ui {
class Form;
}

class Form : public QWidget
{
    Q_OBJECT

public:
    explicit Form(QWidget *parent = nullptr);
    ~Form();

signals:
    void myReturn();

private slots:
    void Return();

private:
    Ui::Form *ui;
    QPushButton *button;
};

#endif // FORM_H

form.cpp

#include "form.h"
#include "ui_form.h"

#include <QDebug>
Form::Form(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Form)
{
    ui->setupUi(this);
    this->button = new QPushButton();
    this->button->setGeometry(10,10,100,100);
    this->button->setText("RETURN");
    this->button->setParent(this);
    this->button->show();

    connect(this->button,SIGNAL(clicked(bool)), this, SLOT(Return()));
}

void Form::Return()
{
    emit myReturn();
    this->hide();
    //qDebug() << "return" << endl;
}
Form::~Form()
{
    delete ui;
}

nexttetris.h

#include "nexttetrisbox.h"

NextTetrisBox::NextTetrisBox(QWidget *parent) : QWidget(parent)
{

    //初始化nextBlock
    for (int i = 0; i < COUNT; i++)
    {
        nextBlock.x[i] = -1;
        nextBlock.y[i] = -1;
    }
    nextBlock.centerX = -1;
    nextBlock.centerY = -1;
    nextBlock.ID = 0;

    //设置本“下一个”窗口的宽度和高度
    //并设置背景为黑色
    int w = Tetris::getNextWidth();
    int h = Tetris::getNextHeight();
    setFixedSize(w, h);
    setPalette(QPalette(Qt::black));
    setAutoFillBackground(true);
}


void NextTetrisBox::updateNextTetris(Tetris tetris)
{
    nextBlock = tetris.getNextBlock();
    for (int i = 0; i < COUNT; i++)
    {
        nextBlock.x[i] -= RESTX;
        nextBlock.y[i] += RESTY;
    }
    //重新绘制
    repaint();
}


void NextTetrisBox::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPen pen;
    QBrush brush;
    pen.setStyle(Qt::SolidLine);
    pen.setColor(QColor(255, 255, 255));
    brush.setStyle(Qt::SolidPattern);
    brush.setColor(QColor(255, 255, 255));
    painter.setPen(pen);
    painter.setBrush(brush);

    //绘制nextBlock中的内容
    for (int i = 0; i < COUNT; i++)
    {
        int x = nextBlock.x[i];
        int y = nextBlock.y[i];
        int x1 = x * WIDTH + x * INTERVAL;
        int y1 = y * HEIGHT + y * INTERVAL;
        painter.drawRect(x1, y1, WIDTH, HEIGHT);
    }
}

nexttetris.cpp

#include "nexttetrisbox.h"
 
NextTetrisBox::NextTetrisBox(QWidget *parent) : QWidget(parent)
{
 
    //初始化nextBlock
    for (int i = 0; i < COUNT; i++)
    {
        nextBlock.x[i] = -1;
        nextBlock.y[i] = -1;
    }
    nextBlock.centerX = -1;
    nextBlock.centerY = -1;
    nextBlock.ID = 0;
 
    //设置本“下一个”窗口的宽度和高度
    //并设置背景为黑色
    int w = Tetris::getNextWidth();
    int h = Tetris::getNextHeight();
    setFixedSize(w, h);
    setPalette(QPalette(Qt::black));
    setAutoFillBackground(true);
}
 
 
void NextTetrisBox::updateNextTetris(Tetris tetris)
{
    nextBlock = tetris.getNextBlock();
    for (int i = 0; i < COUNT; i++)
    {
        nextBlock.x[i] -= RESTX;
        nextBlock.y[i] += RESTY;
    }
    //重新绘制
    repaint();
}
 
 
void NextTetrisBox::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPen pen;
    QBrush brush;
    pen.setStyle(Qt::SolidLine);
    pen.setColor(QColor(255, 255, 255));
    brush.setStyle(Qt::SolidPattern);
    brush.setColor(QColor(255, 255, 255));
    painter.setPen(pen);
    painter.setBrush(brush);
 
    //绘制nextBlock中的内容
    for (int i = 0; i < COUNT; i++)
    {
        int x = nextBlock.x[i];
        int y = nextBlock.y[i];
        int x1 = x * WIDTH + x * INTERVAL;
        int y1 = y * HEIGHT + y * INTERVAL;
        painter.drawRect(x1, y1, WIDTH, HEIGHT);
    }
}

nexttetrisbox.h

#ifndef NEXTTETRISBOX_H
#define NEXTTETRISBOX_H
 
#include <QWidget>
#include <QWidget>
#include <QPaintEvent>
#include <QPen>
#include <QBrush>
#include <QPainter>
#include <QColor>
#include "tetris.h"
 
#define RESTX (MAXX - NEXTMAXX) / 2     //方块x坐标的转换常数
#define RESTY 4                         //方块y坐标的转换常数
 
class NextTetrisBox : public QWidget
{
    Q_OBJECT
public:
    explicit NextTetrisBox(QWidget *parent = nullptr);
    void updateNextTetris(Tetris tetris);   //更新“下一个”的数据和视图
    void paintEvent(QPaintEvent *event);    //绘制视图
 
signals:
 
public slots:
 
private:
    Block nextBlock;    //“下一个”方块
};
 
#endif // NEXTTETRISBOX_H

tetri.h

#ifndef TETRIS_H
#define TETRIS_H
 
//为了获得随机数
#include <cstdlib>
#include <ctime>
 
#define MAXX 10     //显示窗口的横向格数
#define MAXY 20     //显示窗口的竖向格数
#define NEXTMAXX 6  //“下一个”显示窗口的横向格数
#define NEXTMAXY 6  //“下一个”显示窗口的竖向格数
#define WIDTH 30    //单格的宽度
#define HEIGHT 30   //单格的高度
#define INTERVAL 4  //单格之间的间隔
#define COUNT 4     //每个方块的格数
 
//Block结构体:一个方块
struct Block
{
    int x[COUNT];   //方块单格的x坐标
    int y[COUNT];   //方块单格的y坐标
    int centerX;    //方块的中心x坐标
    int centerY;    //方块的中心y坐标
    int ID;         //方块的ID
};
 
 
class Tetris
{
public:
    Tetris();
    void createBlock();             //创建当前方块
    Block getNextBlock();           //获得下一个方块
    Block getBlock();               //获得当前方块
    int getScore();                 //获得分数
    int getBox(int x, int y);       //获得相应坐标的状态
    bool rotate();                  //旋转
    bool moveToLeft();              //向左移动
    bool moveToRight();             //向右移动
    bool moveToBottom();            //向下移动
    bool isEnd();                   //判断是否结束
    void killLines();               //消去整行
    void clear();                   //重新初始化
 
    static int getWidth();          //获得窗口的宽度
    static int getHeight();         //获得窗口的高度
    static int getNextWidth();      //获得“下一个”窗口的宽度
    static int getNextHeight();     //获得“下一个”窗口的高度
 
 
 
private:
    void createNextBlock();         //创建下一个方块
    bool move(int dx, int dy);      //是否可以移动
    void blockToBox();              //将block中的数据转移到box中
    bool isRotatable();             //是否可以旋转
    int getFirstFullLine();         //获得第一个整行
 
private:
    int score;          //分数
    Block block;        //当前方块
    Block nextBlock;    //下一个方块
    int box[MAXX][MAXY];//方格的坐标系 1表示右方格,0表示没有方格
 
 
};
 
#endif // TETRIS_H

tetri.cpp

#include "tetris.h"
 
Tetris::Tetris()
{
    //初始化随机数发生器
    srand(unsigned(time(NULL)));
 
    //初始化成员变量
    score = 0;
    for (int i = 0; i < MAXX; i++)
    {
        for (int j = 0; j < MAXY; j++)
        {
            box[i][j] = 0;
        }
    }
    for (int i = 0; i < COUNT; i++)
    {
        block.x[i] = -1;
        block.y[i] = -1;
    }
    block.centerX = -1;
    block.centerY = -1;
    block.ID = 0;
    //创建下一个方块
    createNextBlock();
 
}
 
 
//创建当前方块
//将上一次生成的下一个方块nextBlock复制给block
//并创建下一个nextBlock
void Tetris::createBlock()
{
    //nextBlock复制给block
    for (int i = 0; i < COUNT; i++)
    {
        block.x[i] = nextBlock.x[i];
        block.y[i] = nextBlock.y[i];
    }
    block.centerX = nextBlock.centerX;
    block.centerY = nextBlock.centerY;
    block.ID = nextBlock.ID;
 
    //创建下一个nextblock
    createNextBlock();
}
 
 
//返回下一个方块
Block Tetris::getNextBlock()
{
    return nextBlock;
}
 
 
//返回当前方块
Block Tetris::getBlock()
{
    return block;
}
 
 
//返回当前分数
int Tetris::getScore()
{
    return score;
}
 
 
//返回坐标(x,y)的值,以判断是否右方格
int Tetris::getBox(int x, int y)
{
    return box[x][y];
}
 
 
//旋转当前方块
//旋转成功返回true,否则返回false
bool Tetris::rotate()
{
    if (isRotatable())
    {
        return true;
    }
    else
    {
        return false;
    }
}
 
 
//将当前方块向左移动一格
//成功返回true,否则返回false
bool Tetris::moveToLeft()
{
    if (move(-1, 0))
    {
        return true;
    }
    else
    {
        return false;
    }
}
 
 
//将当前方块向右移动一格
//成功返回true,否则返回false
bool Tetris::moveToRight()
{
    if (move(1, 0))
    {
        return true;
    }
    else
    {
        return false;
    }
}
 
 
//将方块向下移动一格
//成功返回true, 游戏结束返回false
bool Tetris::moveToBottom()
{
    if (!move(0, 1))
    {
        //移动不成功
 
        blockToBox();   //将当前方块复制到box中
        killLines();    //消行
 
        //判断是否结束
        //否则创建新的方块
        if(isEnd())
        {
            return false;
        }
        else
        {
            createBlock();
        }
    }
    return true;
}
 
 
//判断游戏是否结束
//结束条件为第一行有方格
bool Tetris::isEnd()
{
    int j = 0;
    for (int i = 0; i < MAXX; i++)
    {
        if (box[i][j] == 1)
        {
            return true;
        }
    }
    return false;
}
 
 
//消掉整行并进行分数奖励
void Tetris::killLines()
{
    int count = 0;  //一次消掉的行数
    //通过getFirstFullLine()函数获得从上到下第一个整行
    //并将其上的行向下平移一行,达到消行的效果
    int temp = 0;
    while ((temp = getFirstFullLine()) != -1)
    {
        for (int j = temp; j >0; j--)
        {
            for (int i = 0; i < MAXX; i++)
            {
                box[i][j] = box[i][j-1];
            }
        }
        count++;
    }
    //消行的分数奖励
    score += count * count * 10;
}
 
 
//对成员变量进行初始化,重新开始游戏
void Tetris::clear()
{
    //初始化
    score = 0;
    srand(unsigned(time(NULL)));
    for (int i = 0; i < MAXX; i++)
    {
        for (int j = 0; j < MAXY; j++)
        {
            box[i][j] = 0;
        }
    }
    for (int i = 0; i < COUNT; i++)
    {
        block.x[i] = -1;
        block.y[i] = -1;
    }
    block.centerX = -1;
    block.centerY = -1;
    block.ID = 0;
    //创建下一个方块
    createNextBlock();
}
 
 
//获得游戏窗口的宽度
int Tetris::getWidth()
{
    return MAXX * WIDTH + (MAXX - 1) * INTERVAL;
}
 
 
//获得游戏窗口的高度
int Tetris::getHeight()
{
    return MAXY * HEIGHT + (MAXY - 1) * INTERVAL;
}
 
 
//获得“下一个”窗口的宽度
int Tetris::getNextWidth()
{
    return NEXTMAXX * WIDTH + (NEXTMAXX - 1) * INTERVAL;
}
 
 
//获得“下一个”窗口的高度
int Tetris::getNextHeight()
{
    return NEXTMAXY * WIDTH + (NEXTMAXY - 1) * INTERVAL;
}
 
 
//创建“下一个”方块
void Tetris::createNextBlock()
{
    int centerX = (MAXX - 1) / 2;   //中心x坐标
    int ID = rand() % 7;            //获得0 - 6的随机数
    //根据不同的随机数创建方块
    switch (ID)
    {
    case 0:
        //##
        //##
        nextBlock.x[0] = centerX;
        nextBlock.x[1] = centerX;
        nextBlock.x[2] = centerX + 1;
        nextBlock.x[3] = centerX + 1;
        nextBlock.y[0] = -2;
        nextBlock.y[1] = -1;
        nextBlock.y[2] = -2;
        nextBlock.y[3] = -1;
        nextBlock.centerX = 0;
        nextBlock.centerY = 0;
        nextBlock.ID = 0;
        break;
    case 1:
        //####
        //
        nextBlock.x[0] = centerX - 1;
        nextBlock.x[1] = centerX;
        nextBlock.x[2] = centerX + 1;
        nextBlock.x[3] = centerX + 2;
        nextBlock.y[0] = -1;
        nextBlock.y[1] = -1;
        nextBlock.y[2] = -1;
        nextBlock.y[3] = -1;
        nextBlock.centerX = centerX;
        nextBlock.centerY = -1;
        nextBlock.ID = 1;
        break;
    case 2:
        //##
        // ##
        nextBlock.x[0] = centerX - 1;
        nextBlock.x[1] = centerX;
        nextBlock.x[2] = centerX;
        nextBlock.x[3] = centerX + 1;
        nextBlock.y[0] = -2;
        nextBlock.y[1] = -2;
        nextBlock.y[2] = -1;
        nextBlock.y[3] = -1;
        nextBlock.centerX = centerX;
        nextBlock.centerY = -2;
        nextBlock.ID = 2;
        break;
    case 3:
        // ##
        //##
        nextBlock.x[0] = centerX;
        nextBlock.x[1] = centerX + 1;
        nextBlock.x[2] = centerX - 1;
        nextBlock.x[3] = centerX;
        nextBlock.y[0] = -2;
        nextBlock.y[1] = -2;
        nextBlock.y[2] = -1;
        nextBlock.y[3] = -1;
        nextBlock.centerX = centerX;
        nextBlock.centerY = -2;
        nextBlock.ID = 3;
        break;
    case 4:
        //#
        //###
        nextBlock.x[0] = centerX - 1;
        nextBlock.x[1] = centerX - 1;
        nextBlock.x[2] = centerX;
        nextBlock.x[3] = centerX + 1;
        nextBlock.y[0] = -2;
        nextBlock.y[1] = -1;
        nextBlock.y[2] = -1;
        nextBlock.y[3] = -1;
        nextBlock.centerX = centerX;
        nextBlock.centerY = -1;
        nextBlock.ID = 4;
        break;
    case 5:
        //  #
        //###
        nextBlock.x[0] = centerX + 1;
        nextBlock.x[1] = centerX - 1;
        nextBlock.x[2] = centerX;
        nextBlock.x[3] = centerX + 1;
        nextBlock.y[0] = -2;
        nextBlock.y[1] = -1;
        nextBlock.y[2] = -1;
        nextBlock.y[3] = -1;
        nextBlock.centerX = centerX;
        nextBlock.centerY = -1;
        nextBlock.ID = 5;
        break;
    case 6:
        // #
        //###
        nextBlock.x[0] = centerX;
        nextBlock.x[1] = centerX - 1;
        nextBlock.x[2] = centerX;
        nextBlock.x[3] = centerX + 1;
        nextBlock.y[0] = -2;
        nextBlock.y[1] = -1;
        nextBlock.y[2] = -1;
        nextBlock.y[3] = -1;
        nextBlock.centerX = centerX;
        nextBlock.centerY = -1;
        nextBlock.ID = 6;
        break;
    default:
        break;
    }
}
 
 
//可以移动就对block进行变换,返回true
//否则返回false
bool Tetris::move(int dx, int dy)
{
    int newX[COUNT];
    int newY[COUNT];
    int newCenterX;
    int newCenterY;
    for (int i = 0; i < COUNT; i++)
    {
        newX[i] = block.x[i] + dx;
        newY[i] = block.y[i] + dy;
 
        //对变换后的坐标进行判定
 
        //x坐标超出范围返回false
        if (newX[i] < 0 || newX[i] >= MAXX)
        {
            return false;
        }
        //y坐标在0 - MAXY之间就对box中的状态进行判定
        //box中为1则返回false
        if (newY[i] >=0 && newY[i] < MAXY)
        {
            if (box[newX[i]][newY[i]] == 1)
            {
                return false;
            }
        }//y坐标超出最大值返回false
        else if (newY[i] >= MAXY)
        {
            return false;
        }
 
    }
    newCenterX = block.centerX + dx;
    newCenterY = block.centerY + dy;
 
    //满足条件就将新的x和y坐标赋值给block
    for (int i = 0; i < COUNT; i++)
    {
        block.x[i] = newX[i];
        block.y[i] = newY[i];
    }
    block.centerX = newCenterX;
    block.centerY = newCenterY;
 
    return true;
}
 
 
//可以旋转就对block进行变换,返回true
//否则返回false
bool Tetris::isRotatable()
{
    int newX[COUNT];
    int newY[COUNT];
    int newCenterX;
    int newCenterY;
 
    if (block.ID == 0)
    {
        return false;
    }
 
    for (int i = 0; i < COUNT; i++)
    {
        int nx = block.x[i] - block.centerX;
        int ny = block.y[i] - block.centerY;
        newX[i] = nx * 0 + ny * (-1) + block.centerX;
        newY[i] = nx * 1 + ny * 0 + block.centerY;
 
        //对变换后的坐标进行判定
 
        //x坐标超出范围返回false
        if (newX[i] < 0 || newX[i] >= MAXX)
        {
            return false;
        }
        //y坐标在0 - MAXY 之间就对box中的状态进行判定
        //box中为1则返回false
        if (newY[i] >=0 && newY[i] < MAXY)
        {
            if (box[newX[i]][newY[i]] == 1)
            {
                return false;
            }
        }//y坐标超过最大值返回false
        else if (newY[i] >= MAXY)
        {
            return false;
        }
    }
    newCenterX = block.centerX;
    newCenterY = block.centerY;
 
    //满足条件后进行block的赋值
    for (int i = 0; i < COUNT; i++)
    {
        block.x[i] = newX[i];
        block.y[i] = newY[i];
    }
    block.centerX = newCenterX;
    block.centerY = newCenterY;
 
    return true;
}
 
 
//将block中数据复制到box中
void Tetris::blockToBox()
{
    for (int i = 0; i < COUNT; i++)
    {
        int x = block.x[i];
        int y = block.y[i];
        if (y >= 0)
        {
            box[x][y] = 1;
        }
    }
}
 
 
//获得第一个整行的行数,并返回
int Tetris::getFirstFullLine()
{
    //这里j从1开始就好
    for (int j = 0; j < MAXY; j++)
    {
        bool judgement = true;
        for (int i = 0; i < MAXX; i++)
        {
            if (box[i][j] == 0)
            {
                judgement = false;
                break;
            }
        }
        if (judgement)
        {
            return j;
        }
    }
    return -1;
}

tetrisbox.h

#ifndef TETRISBOX_H
#define TETRISBOX_H
 
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QPalette>
#include <QPen>
#include <QBrush>
#include <QColor>
//为了使用Block
#include "tetris.h"
 
class TetrisBox : public QWidget
{
    Q_OBJECT
public:
    explicit TetrisBox(QWidget *parent = nullptr);
    void updateTetris(Tetris tetris);       //更新数据和视图
    void paintEvent(QPaintEvent *event);    //绘制视图
 
signals:
 
public slots:
 
private:
    Block block;            //用来储存Tetris中block的数据
    int box[MAXX][MAXY];    //用来存储Tetris中box的数据
};
 
#endif // TETRISBOX_H

tetrisbox.cpp

#include "tetrisbox.h"
 
TetrisBox::TetrisBox(QWidget *parent) : QWidget(parent)
{
    //对block初始化
    for (int i = 0; i < COUNT; i++)
    {
        block.x[i] = -1;
        block.y[i] = -1;
    }
    block.centerX = -1;
    block.centerY = -1;
    block.ID = -1;
    //对box初始化
    for (int i = 0; i < MAXX; i++)
    {
        for (int j = 0; j < MAXY; j++)
        {
            box[i][j] = 0;
        }
    }
 
    //设置本游戏窗口的宽度和高度
    //并设置背景为黑色
    int w = Tetris::getWidth();
    int h = Tetris::getHeight();
    setFixedSize(w, h);
    setPalette(QPalette(Qt::black));
    setAutoFillBackground(true);
}
 
 
void TetrisBox::updateTetris(Tetris tetris)
{
    //更新block
    block = tetris.getBlock();
    //更新box
    for (int i = 0; i < MAXX; i++)
    {
        for (int j = 0; j < MAXY; j++)
        {
            box[i][j] = tetris.getBox(i, j);
        }
    }
    repaint();
}
 
 
void TetrisBox::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QPen pen;
    QBrush brush;
    pen.setStyle(Qt::SolidLine);
    pen.setColor(QColor(255, 255, 255));
    brush.setStyle(Qt::SolidPattern);
    brush.setColor(QColor(255, 255, 255));
    painter.setPen(pen);
    painter.setBrush(brush);
 
    //绘制box中的内容
    for (int i = 0; i < MAXX; i++)
    {
        for (int j = 0; j < MAXY; j++)
        {
            if (box[i][j] == 1)
            {
                int x = i * WIDTH + i * INTERVAL;
                int y = j * HEIGHT + j * INTERVAL;
                painter.drawRect(x, y, WIDTH, HEIGHT);
            }
        }
    }
    //绘制block中的内容
    for (int i = 0; i < COUNT; i++)
    {
        int x = block.x[i];
        int y = block.y[i];
        int x1 = x * WIDTH + x * INTERVAL;
        int y1 = y * HEIGHT + y * INTERVAL;
        painter.drawRect(x1, y1, WIDTH, HEIGHT);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

511511511

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值