【C语言俄罗斯方块】

【 C语言俄罗斯方块 】

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

我们预计界面如下:

在这里插入图片描述

游戏运行时界面如下:

在这里插入图片描述

思路已有,每一步的实现通过代码备注:

《 C语言俄罗斯方块 》


#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <windows.h>
#include <conio.h>
#include <time.h>
using namespace std;

#define Block "■"
#define block "□"		//由于 "■" 出现白边,吃边界的情况, 于是 又定义了一个"□"	
#define White "  "
HANDLE handle;			//输出坐标
COORD crd;				//输出坐标
const short LMARGIN = 7;//方块掉落范围的 左边界 
const short TMARGIN = 5;//方块掉落范围的 上边界

char type[19][17] = 
{
	/*------ - 定义方块种类,以下形状均采用逆时针旋转-------- */
	//Z 型 0 - 1 (两种)
	"1100011000000000", "0010011001000000",
	//S 型 2 - 3 (两种)
	"0110110000000000", "0100011000100000",
	//I 型 4 - 5 (两种)
	"0000111100000000" ,"0100010001000100",
	//方型     6  (一种)
	"0000011001100000",
	//T 型 7 - 10 (四种)
	"0100111000000000", "0100110001000000", "1110010000000000", "1000110010000000",
	//L 型11 - 14 (四种)
	"0010111000000000", "0110001000100000", "1110100000000000", "0100010001100000",
	//J 型15 - 18 (四种)
	"1000111000000000", "0010001001100000", "1110001000000000", "0110010001000000"
};

//旋转路径上,不能有阻挡物。
//用字符串来表示:”0010 0000 0110 0000”
//我们都以逆时针旋转为例:

//————————————————————————————————————————————————————
//受阻方式 二选一:①旋转受阻,②只是变形受阻

char rotate_hinder[19][17] =
{
/*-------—— ①定义方块旋转受阻位置 ------———*/
	//Z 型 0 - 1 (两种)
	"0010000001100000", "1100000000000000",
	//S 型 2 - 3 (两种)
	"0000001011100000", "0010100011000000",
	//I 型 4 - 5 (两种)
	"1111000001110111", "1011101100110011",
	//方型     6  (一种)
	"0000000000000000",
	//T 型 7 - 10 (四种)
	"1010000011000000", "1010001000100000", "0000100010000000", "0100001001100000",
	//L 型11 - 14 (四种)
	"0100000011100000", "1000110000000000", "0000010011100000", "1010101000000000",
	//J 型15 - 18 (四种)
	"0110000011100000", "1010101000100000", "0000110011000000", "1000101000100000"
};
//char rotate_hinder[19][17] =
//{
//	/*-------—— ②定义方块旋变形位置 ------———*/
//		//Z 型 0 - 1 (两种)
//		"0010000001000000", "1100000000000000",
//		//S 型 2 - 3 (两种)
//		"0000001000100000", "0010100000000000",
//		//I 型 4 - 5 (两种)
//		"0100000001000100", "0000101100000000",
//		//方型     6  (一种)
//		"0000000000000000",
//		//T 型 7 - 10 (四种)
//		"0000000001000000", "0000001000000000", "0000001001000000", "0100001000000000",
//		//L 型11 - 14 (四种)
//		"0100000000100000", "1000100000000000", "0000010001100000", "0010101000000000",
//		//J 型15 - 18 (四种)
//		"0100000011000000", "0000110000000000", "0000010001000000", "1000100000000000",
//};
//————————————————————————————————————————————————————

//记录旋转之后的数字
short Alter(short n)
{
	switch (n)
	{
	case 1:
	case 3:
	case 5:	//1 3 5 类型旋转之后的结果刚好等于他的前一种结果。 1→0,3→2,5→4
		return n - 1;	
		break;
	case 6:	
		return 6;		//方形■ 不发生变化
		break;
	case 10:
	case 14:
	case 18:	
		return n - 3;	//10→7,14→11,18→15
		break;
	default:
		return n + 1;	//其余情况等于它的下一个下标结果。
		break;
	}
}

void SetPos(short x, short y)				//传入坐标
{
	crd.X = x;
	crd.Y = y;
	SetConsoleCursorPosition(handle, crd);	//句柄要先使用 GetStdHandle 函数,只使用一次,包含在主函数体内部运行一次就可以了。
}

//颜色:【0黑 1蓝 2绿 3浅蓝 4红 5紫 6黄 7白 8灰 9浅蓝 10绿 11淡蓝 12粉 13粉紫 14黄 15白 16灰】 
void SetColor(short front, short back)		//前景色,背景色
{
	SetConsoleTextAttribute(handle, front + back * 0x10);	//前景色(文字颜色),背景色 [0~15的范围],背景色比前景色大16倍
}

//游戏结束
void PrintGameOver()
{
	SetPos(12, 9);
	SetColor(4, 6);
	cout << " Game Over! ";
	SetColor(7, 0);
}

//得分
class Score
{
private:
	short ln;	//得分
public:
	void Reset()
	{
		ln = 0;
	}
	void Print()
	{
		SetPos(19, 2);
		SetColor(2, 7);
		cout << " Lines: " << ln << " ";
		SetColor(7, 0);
	}
	Score()
	{
		Reset();
	}
	void Update(short n)
	{
		ln += n;
	}
};

//管理各种形状的类, Board Shape 要互为友元
class Shape
{
private:
	short present;	//当前形状:char type【19】[17](取值0~18)
	short color;
	short x;		//左上角横坐标
	short y;		//左上角纵坐标
public:
	Shape(short, short, short);		//传入方块的(颜色,x, y)
	void Draw(bool, bool);			//第一个参数为真:绘制操作,为假:擦除操作。 第二个参数:为真:按方块的颜色画,为假:画成白色(如果已经到底了需要同意涂成白色)。
	friend class Board;				//声明:后面才有 Board
	bool Rotate(Board& brd);		//判断方块是否可以旋转,如果能:完成旋转,并返回真。 brd 是会随时变化的,所以需要 &brd
	short Collision(Board& brd);	//左侧、右侧、下方受阻时,分别返回,二进制[0000 0111]末尾对应的,"位或"==> 1 2 4 , [0101]=5 左有阻挡物又 着地,[0110]=6 右侧阻挡物又 着地
	bool Move(short, Board& brd);	//左移参数为:1, 右移参数为:2 
	bool Fall(Board& brd);			//能否下落? 能:true, 不能返回:false
	void DrawElseWhere(short _x, short _y);	//提示下一个出现的方块。
};

//游戏框类
class Board	
{
private:
	bool wall[22][14];	//值为真,表示该位置已被占用,为假表示该位置为空。 
	short bottom_line;	//保存可以进行消除位置的末行行号。
public:				
	void Clear()		//重置数组元素的值,初始化
	{
		for (short i = 0; i < 22; i++)			//行遍历
		{
			for (short j = 0; j < 14; j++)		//列遍历
			{
				if (j > 1 && j < 12 && i < 20)	//除去边框,实际可操控的空间为:[19][10]
				{
					wall[i][j] = 0;
				}
				else
					wall[i][j] = 1;
	 		}
		}
	}
	Board()
	{
		Clear();
	}
	void DrawBoard()
	{
		short i;
		SetColor(0xe, 0xe);		// 0xe = 14
		for (i = 0; i < 21; i++)//绘制边框左边界
		{
			SetPos(LMARGIN - 2, TMARGIN + i - 1);	//LMARGIN -2 掉落范围 -2的位置
			cout << Block;
		}
		for (i = 0; i < 21; i++)//绘制边框右边界
		{
			SetPos(LMARGIN + 20, TMARGIN + i - 1);
			cout << Block;
		}
		for (i = 0; i < 10; i++)
		{
			SetPos(LMARGIN + i * 2, TMARGIN + 19);	// * 2 两个横坐的标宽 = 一个宽字符
			cout << Block;
		}
		SetColor(7, 0);	//颜色恢复 白字 黑背景
	}

	//对wall数组值进行更新(更新堆积块)
	short SetBlockValue(Shape& shp)
	{
		for (short i = 0; i < 16; i++)
		{
			if (type[shp.present][i] == '1')				//表示4*4的格子中方块对应占据位置
			{
				if (shp.y == 0)								//方块到顶了。游戏结束
					return -1;
				wall[shp.y + i / 4][shp.x + i % 4] = true;	// 方块在实际游戏画面所在位置 + 方块在4*4 格子的位置 = 实际位置
			}
		}
		return 0;
	}
	//返回可消除的行数
	short Drop(Shape& shp)
	{
		//消除后,重绘范围:下落物体下方的堆积快不用重绘
		//遍历4*4=16个格子,每个图形方块只占据4格,只要方块格子达到4就可以跳出循环。
		short i = 0;	//遍历 方块图形 Z S L... 的 4*4 格子
		short n = 0;	//组成图形的方块 一共4个
		short y = 0;	//当前格子行号
		short _y = -1;	//y前一格行号,初始化为一个不合理的值 -1,方便对比。
		short j = 0;	//j 用于遍历一整行,是否全满,如果有 false 表示未满。
		short lines = 0;//记录消除多少行。
		short k = 0;	//从下往上 逐行覆盖
		short top = 0;	//检查是否遍历到 堆积快顶部。
		short l = 0;	//从左往右 逐列遍历(在覆盖的过程中)
		for (i = 0, n = 0; n < 4 && i < 16; i++)
		{
			if (type[shp.present][i] == '1')
			{
				y = shp.y + i / 4;
				n++;
				if (y == _y)						//连续两个格子位于同一排,继续对比下一个格子,一次就可以判断,不必判断两次
					continue;
				_y = y;								//如果不一样,将当前行的行号赋值给 _y 方便下一行进行判断。
			//扫描行,除去左右边界
				for (j = 2; j < 12; j++)
				{
					if (!wall[y][j])
						break;
				}
				if (j == 12)						//一整行已经填满,消除该行
				{
					lines++;						//消除的行数更新
					for (k = y; k > 0; k--)
					{
						for (l = 2; l < 12; l++)	//除去边界的一行
						{
							wall[k][l] = wall[k - 1][l];//上一行 ==> 覆盖下一行
							top += wall[k - 1][l];
						}
						if (top == 0)				//如果一行遍历完,top 的值还是0,就证明这一行已经没有堆积块,已经到堆积块顶部了。那么上面的空间就不要再进行判断了。break 跳出循环。
							break;
					}
				}
			}
		}
		bottom_line = _y;	//要消除行的 下面不需要进行重绘。只需要重绘上面
		return lines;		//最后返回要消除的行号
	}
	
	//重绘堆积块
	void DrawPile()			//重绘堆积块,由 bottom_line 指定需要重绘的最下面一行,已经在 Drop 函数定义。
	{
		for (short i = 0; i <= bottom_line; i++)//行遍历
		{
			for (short j = 2; j < 12; j++)	//遍历列
			{
				SetPos(LMARGIN - 4 + j * 2, TMARGIN - 1 + i);
				if (wall[i][j])
				{
					SetColor(7, 7);			//下落下来的堆积块设为白色
					cout << block;
				}
				else
				{
					SetColor(0, 0);			//黑色,用于擦除
					cout << White;
				}
			}
		}
	}
	friend class Shape;						//wall 为私有,要访问私有,声明友元
};
  
//构造函数:在类定义的外部实现类的成员,类名+::   x=5 y=0 方块出现比较合适的默认值
Shape::Shape(short _color, short _x = 5, short _y = 0) :color(_color), x(_x), y(_y)	//根据第一个参数的值来确定出现什么方块(这里方块与颜色进行固定绑定了)
{
	switch (_color)	
	{
	case 1: present = 0; break;				//Z [0<==>1]  方块除去旋转后的结果,一共只有 7 种不同的形状。
	case 2: present = 2; break;				//S [2<==>3]  方块旋转时,位置会发生改变,但是方块样式不会发生改变(方块样式与颜色进行了绑定)
	case 3: present = 4; break;				//I [4<==>5]  注意:要改变 present 的值,只能用 Alter() 函数对其赋值,确保赋值的合法性。
	case 4: present = 6; break;				//O [  6   ]  不发生改变
	case 5: present = 7; break;				//T [7<==>10]
	case 6: present = 11; break;			//L [11<==>14]
	default:color = 8; present = 15; break;	//J [15<==>18]其余情况,颜色统一赋值为8,方块初始形状为15
	}
}

void Shape::Draw(bool flag = true, bool active = true)	//默认情况按自身颜色进行绘制
{
	if (!flag)								//擦除
		SetColor(0, 0);						//前景色,背景色都设为白色
	else
		if (active) SetColor(color, color);	//前景色,背景色设为自身颜色
		else SetColor(7, 7);				//到底的方块画成白色
	for (short i = 0; i < 16; i++)			//遍历方块形状
	{
		if (type[present][i] == '1')		//方块种类:type 中对应的字符串 ==> 对应 Shape 中的 present 成员 (0~18范围)。
		{
			//LMARGIN -4(两个字符宽度才是显示屏上的一个正方形)
			//x 坐标的:下标位, 但是由于两个宽的=一个正方格子宽度,实际宽度需要*2
			//i%4 ,不同形状 Z S L... 在4*4的格子,%4 得到他在这个4*4格子中的 下标位置(从0开始)
			// 格子中的 [坐标位] + [x] 的坐标位 = 这个方块在整个游戏框当中的坐标。
			//y + i / 4 - 1	 同理: -1防止穿过地面, i/4:4*4格子的横坐标, + y 游戏框  的坐标 
			SetPos(LMARGIN - 4 + (x + i % 4) * 2, TMARGIN + y + i / 4 - 1);
			cout << Block;		//位置和颜色都确定了之后输出方格。
		}
	}
	SetColor(7, 16);	//颜色恢复 白字 黑背景 
}

bool Shape::Rotate(Board& brd)		//旋转,成员函数::
{
	for (short i = 0; i < 16; i++)	//遍历图形方块 4*4的格子,是否有阻挡物,导致无法旋转 
	{
		if (brd.wall[y + i / 4][x + i % 4] && rotate_hinder[present][i] == '1')	//i/4:4*4格子的横坐标 ,&&能够阻碍旋转
		{
			//if()					//待完善:L J I S Z //如果是靠墙,旋转,要退回一格在进行旋转
			return false;
		}
	}
	//所有旋转位置都没有阻挡物,就返回真,并旋转该图形。  
	Draw(false);	
	present = Alter(present);
	Draw();
	return true;
}

//检查格子周围是否受阻
short Shape::Collision(Board& brd)
{
	short result = 0;
	for (short i = 0; i < 16; i++)
	{
		if (type[present][i] == '1')					//该格子是否被当前方块占用,为'1'被占用
		{
			if (brd.wall[y + i / 4][x + i % 4 - 1])
				result |= 1;							// 左边受阻
			if (brd.wall[y + i / 4][x + i % 4 + 1])
				result |= 2;							// 右边受阻
			if (brd.wall[y + i / 4 + 1][x + i % 4])
				result |= 4;							// 下方受阻
		}
	}
	//四周都检查完碰撞后
	return result;										//返回受阻情况
}

//移动
bool Shape::Move(short direction, Board& brd)
{
	if (direction == 1 && !(Collision(brd) & 1))	//按的左, 并且可以 左移(没有阻挡物)
	{
		Draw(false, true);		//false 是擦除,第二个true表示画自身的颜色。(第一个为false时,可以不考虑第二个参数的值)。
		x--;
		Draw();
		return true;
	}
	if (direction == 2 && !(Collision(brd) & 2))	//按的右, 并且可以 右移(没有阻挡物)
	{
		Draw(false, true);		
		x++;
		Draw();
		return true;
	}
	return false;
}

//下落
bool Shape::Fall(Board& brd)
{
	if (Collision(brd) & 4)		//已经着地
	{
		Draw(true, false);		//画成堆积块,第一个参数为true:绘制, 第二个参数为false:已经是堆积块了。 如果能进判断直接可以
		return false;
	}
	//可以下落
	Draw(false, true);			//擦除 ,第二个参数已经没有意义。
	y++;
	Draw();						//重新绘制图形
	return true;
}

void Shape::DrawElseWhere(short _x, short _y)
{
	short i;		//循环变量
	SetPos(_x, _y);	//设定输出坐标
	SetColor(7, 0);	
	cout << " Next: ";
	_x += 6;
	SetColor(0, 0);
	for (i = 0; i < 12; i++)//擦除前一次的方块,初始状态,最多只会占据前3行。无需变量4*4 =>16
	{
		SetPos(_x + i % 4 * 2, _y + i / 4);
		cout << White; 
	}
	SetColor(color, color);	//设置绘制颜色
	_y -= (present == 6 || present == 4);//当出现的图形为 6或4 为真,逻辑表达式结果为 1,否则为0。 y轴就达到了是否 y-1 的效果。(方型□,和长条型I 存在比其他图形存在低一行的问题)
	for (i = 0; i < 12; i++)//绘出下一次出现的方块
	{
		if (type[present][i] == '1')
		{
			SetPos(_x + i % 4 * 2, _y + i / 4);
			cout << Block;
		}
	}
}

//菜单:选中开始游戏返回1, 退出游戏返回0
short Menu()
{
	//控制台窗口设置
	//system("mode con cols=35 lines=27");	//窗口大小
	system("title 俄罗斯方块");				//标题
	//隐藏光标
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获得句柄(光标的信息)
	//隐藏光标操作
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
	CursorInfo.bVisible = false;			   //隐藏控制台光标
	SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

	short choice = 1;					//跟踪用户选项
	char c;								//记录用户按键信息
	system("cls");						//清屏
	SetPos(8, 5);						//输出坐标	//┏ ━┃┗ ┛┓ 
	cout << "┏━━━━━━━━━━━━━━━━┓";	// 16个 ━
	SetPos(8, 6);						//定位新坐标
	cout << "┃ 俄 罗 斯 方 块 ┃";
	SetPos(8, 7);					
	cout << "┗━━━━━━━━━━━━━━━━┛";	 
	
	SetPos(7, 18);
	cout << "控制提示:← → 移动";
	SetPos(7, 20);
	cout << "↑ 旋转, ↓ 快速下落";
	SetPos(8, 22);
	cout << "空格键:暂停/继续";
	SetPos(8, 24);
	cout << "Esc:强制退出游戏";

	while (1)							//等待用户选择
	{
		SetPos(12, 13);
		if (choice == 1) SetColor(2, 7);//被选中时:前景色:2(绿色), 背景色:7(白色)
		else SetColor(2, 0);			//未选中时:前景色:2(绿色), 背景色:0(黑色)
		cout << " 开始游戏 ";

		SetPos(12, 15);
		if (choice == 2) SetColor(2, 7);//被选中时:前景色:2(绿色), 背景色:7(白色)
		else SetColor(2, 0);			//未选中时:前景色:2(绿色), 背景色:0(黑色)
		cout << " 退出游戏 ";
		c = _getch();					//包涵头文件 #include <conio.h>
		if (c == VK_RETURN) break;		//用户按下了回车,离开循环体
		else 
			if (c == 0) choice = _getch();
		//如果按键为 ↓ 或 ↑ ,重新赋值 choice ,原本为 1 改为 2 ,否则改为 1 。开始游戏,退出游戏 上下交换着选择
		if (c == 72 || c == 80) choice = choice == 1 ? 2 : 1;	//用户按了上下键的反馈:让 coice 在1,2之间切换选择。
	}
	SetColor(7, 0);						//回复黑底白字颜色
	return choice;						//返回选择结果
}

//int main()
//{
//	//控制台窗口高度和宽度
//	system("mode con cols=35 lines=27");
//	handle = GetStdHandle(STD_OUTPUT_HANDLE);
//	
//
//	Board board;
//	board.DrawBoard();
//	//Shape shp(6, 5, 0);
//	Shape shp(6);			//有了默认值,不指定坐标也可以
//	shp.Draw(); 
//	
//	//测试变形
//	//Sleep(2000);
//	//shp.Rotate(board);
//
//	//测试移动
//	while (shp.Move(1, board))	// 左移
//		Sleep(250);
//	while (shp.Move(2, board))	// 右移
//		Sleep(250);
//
//	//测试下落
//	while (shp.Fall(board))
//		Sleep(250);
//
//	return 0;
//}


int main()
{
	//控制台窗口高度和宽度
	system("mode con cols=35 lines=27");
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	Board board;
	
	short tmp;				//用来保存一个方块消去的行数
	Score score;			//建立分数类 实例化 
	
	Shape* shp;				//指向当前方块的 指针。
	Shape* shp_next;		//指向下一方块的 指针。

	short times;			//用来保存执行 Sleep() 函数的次数
	srand(time(NULL));		//使游戏运行时,每次产生方块随机。

	char ch;				//用来清空输入流文件
	while (Menu() == 1)		//开始游戏
	{
		system("cls");		//清屏
		board.Clear();		//初始化界面,防止之前的游戏记录存在
		board.DrawBoard();	//框边界
		
		score.Reset();		//得分归 0 ,可能保留上一次的分数。
		score.Print();		//显示得分

	 	shp_next = new Shape(rand() % 7 + 1);		//下一个方块的实例化
		while (1)	//每次循环代表 出现一次方块,从产生到着地的过程
		{
			shp = shp_next;							//下一个出现的方块赋值给当前方块
			shp_next = new Shape(rand() % 7 + 1);	//重新产生一个方块					//得到0~6 + 1, 默认参数 5, 0,其余颜色直接 default:color = 8;
			shp_next->DrawElseWhere(4, 2);			//显示出下一个出现的方块 
			times = 0;
			while (1)	//每执行一遍,循环体就是捕捉一次按键行为的过程。每10次循环,方块下落一格,直到低跳出循环。
			{
				shp->Draw();
				if (GetAsyncKeyState(VK_LEFT) == -32767)		//VK_LEFT  是左方向键 ← 的虚拟键码
					shp->Move(1, board);						//向左移动
				if (GetAsyncKeyState(VK_RIGHT) == -32767)		//VK_RIGHT 是右方向键 → 的虚拟键码
					shp->Move(2, board);						//向右移动
				if (GetAsyncKeyState(VK_UP) == -32767)			//VK_UP	   向上方向键 ↑ 执行旋转 方块
					shp->Rotate(board);
				if (GetAsyncKeyState(VK_DOWN) == -32767)		//VK_DOWN  向下方向键 ↓ 快速降落
					shp->Fall(board);
				if (GetAsyncKeyState(VK_SPACE) == -32767)		//VK_SPACE 空格键:暂停/继续游戏
				{
					while (!(GetAsyncKeyState(VK_SPACE) == -32767))//如果没有再次按下空格键就继续等待。
						Sleep(25);
				}
				if (GetAsyncKeyState(VK_ESCAPE) == -32767)		//VK_ESCAPE  ESC键:退出游戏
				{
					SetPos(13, 10);
					SetColor(4, 6);
					cout << "退出游戏!" << endl;
					SetPos(1, 22);
					return 0;
				}

				Sleep(25);	//延时
				times++;
				if (times == 10)								//每250毫秒 方块 下落一次
				{
					times = 0;									//重置时间记录次数。
					if (!shp->Fall(board))						//是否可以往下落。 Fall 函数已经封装了擦除,重绘行为。
						break;
				}
			}
			//判断是否结束
			if (board.SetBlockValue(*shp) == -1)				//更新堆积块
			{
				PrintGameOver();								//打印 游戏结束 图标
				fflush(stdin);									//清空:输入输出流
				while ((ch = getchar()) != '\n' && ch != EOF)	//getch();
				{ ; }	//不是回车,并且不是以文件结尾结束,就继续读
				delete shp_next;
				break;
			}
			//根据消行得分
			if (tmp = board.Drop(*shp))							//当有堆积块被消除时才进行重绘 (消除行!=0)
			{
				board.DrawPile();	
				score.Update(tmp);	//累加得分,tmp = 当前消除的行数
				score.Print();		//显示得分
			}
			//...是否消行
			delete shp;											//释放已经堆叠的方块。然后重新产生新的方块。
		}
	}

	return 0;
}
  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值