MTK平台俄罗斯方块游戏评审

游戏评审

我二月份来到现在这个公司,现在转眼间过去了三个月,今天我也要做游戏评审了,评审通过了,我也就能正式转正了。这三个月好像从头开始学习一样。从MTK平台的搭建、程序的编译、Source Insight编写和浏览代码、vs调试代码(打函数断点、监视、跟踪)、使用SVN从公司服务器拉去代码、Beyond Compare比较代码的修改、C语言宏开关的使用、MTK的资源使用、以及程序和资源的独立。然后用一个月开发了一个小游戏,这个游戏于其说是自己写的,倒不如说是拼凑和修改的。

游戏的代码逻辑主要参考的是这篇文章——C++【EasyX】俄罗斯方块

游戏的MTK菜单框架menu-cui以及菜单处理函数。以及定时器参考的本地电脑中2019年的前辈写的程序写出来的。

游戏的存储,本来是要用nvram写的,但是本人比较内向,也不主动问,然后玩自己移植游戏的时候发现有人写过俄罗斯方块了,而且源码也可以用,也能调试。不过他用的不是nv而是用的是类似于在文件夹中写个txt这种方法来存储文件的。并且游戏声音的播放也是来源于这里

本人的作用就是拆分组装,以及修改一项bug,让程序正常运行。

游戏效果的展示

现在用一张我制作的动图展示我的游戏效果:
在这里插入图片描述
上面两个蓝色的方块,左边的是记录游戏分数的方块,右边的是记录游戏时间的方块。游戏得分的规则比较简单,消一行得一分。然后一个是游戏的主屏幕,一个是显示下一个方块的屏幕。 这个屏幕里面的方块显示不是很正。还需要画时间调整。下面两个按键是绘制出来,并且绑定函数来达到按键的效果。

游戏按键,中间键,是控制方块顺时针旋转的,向上的按键是方块硬着陆,向下的按键是加快方块下落速度。

游戏菜单中,有排行榜,继续游戏,新的游戏、声音等

游戏介绍

我制作的游戏主要做的工作是以下六点加上游戏背景的制作(画方格、填充颜色)、游戏按键的绑定。
在这里插入图片描述

游戏流程图

在这里插入图片描述
游戏初始化的过程大致如下:进入游戏屏幕,初始化全局变量,读取本地游戏文件、根据本地文件得到是否要继续上次的游戏的标志位的情况,绘制分数、绘制时间、绘制左按键、绘制右按键,绑定按键函数,开始游戏和时间两个定时器。

//进入游戏
void enter_my_tetris_screen()
{
    S32 x1, y1;
    mmi_frm_scrn_enter(GRP_ID_MY_TETRIS_GROUP, SCR_ID_TETRIS_SCREEN_NEW_GAME, exit_my_tetris_screen, enter_my_tetris_screen, MMI_FRM_FULL_SCRN);
    clear_screen();
	
	Init_Set_Global_Var();
	tetris_new_or_resume_flag = 1;
	My_Tetris_ReadLog(&MY_Tetris_Log);
	MY_Tetris_Log.new_or_resume_flag = tetris_new_or_resume_flag;
	My_Tetris_WriteLog(&MY_Tetris_Log);
	
    tetris_draw_time();
    tetris_draw_score();
    tetris_draw_left();
    SetKeyHandler(tetris_draw_left, KEY_LSK, KEY_EVENT_DOWN);
    tetris_draw_right();
    SetKeyHandler(tetris_draw_right, KEY_RSK, KEY_EVENT_DOWN);
	draw_game_timer();
	draw_sec_timer();
    gui_BLT_double_buffer(0, 0, UI_device_width -1, UI_device_height - 1);
	if(game_status == STATUS_START || game_status == STATUS_PAUSE)
	{
		Tetris_show_start_popup_text();
	}
	tetris_SetKeyHandler();	
}

游戏的设计

接下来讲解的是我的游戏的设计思路,基本生都来源于C++【EasyX】俄罗斯方块。他的主要问题是俄罗斯边界控制不好,就是刚开始左右移动方块会发生卡块的现象。我改善了一下。

俄罗斯方块的设计

俄罗斯方块类型实际形状个数
山型4
L型4
反L型4
Z型2
反Z型2
I型2
田型1

从右到左

//记录作图形状的偏移量,总共有7种形状,每种形状有四个方向,每个形状用两位数记录基准坐标
struct Square squares[7] = { 
	{ 0, -2, 0, -1, 0, 0, 1, 0, 0, -1, 1, -1, 2, -1, 0, 0, 0, -2, 1, -2, 1, -1, 1, 0, 0, 0, 1, 0, 2, 0, 2, -1 },       //  L 型
	{ 0, 0, 1, 0, 1, -1, 1, -2, 0, -1, 0, 0, 1, 0, 2, 0, 0, -2, 0, -1, 0, 0, 1, -2, 0, -1, 1, -1, 2, -1, 2, 0 },       //  L 型(反)
	{ 0, -1, 0, 0, 1, -1, 1, 0, 0, -1, 0, 0, 1, -1, 1, 0, 0, -1, 0, 0, 1, -1, 1, 0, 0, -1, 0, 0, 1, -1, 1, 0 },        // 田 型
	{ 0, 0, 1, -1, 1, 0, 2, 0, 1, -2, 1, -1, 1, 0, 2, -1, 0, -1, 1, -1, 1, 0, 2, -1, 0, -1, 1, -2, 1, -1, 1, 0 },      // 山 型
	{ 0, -3, 0, -2, 0, -1, 0, 0, 0, -3, 1, -3, 2, -3, 3, -3, 0, -3, 0, -2, 0, -1, 0, 0, 0, -3, 1, -3, 2, -3, 3, -3 },  //  | 型
	{ 0, -1, 1, -1, 1, 0, 2, 0, 0, -1, 0, 0, 1, -2, 1, -1, 0, -1, 1, -1, 1, 0, 2, 0, 0, -1, 0, 0, 1, -2, 1, -1 },      //  Z 型
	{ 0, 0, 1, -1, 1, 0, 2, -1, 1, -2, 1, -1, 2, -1, 2, 0, 0, 0, 1, -1, 1, 0, 2, -1, 1, -2, 1, -1, 2, -1, 2, 0 }       //  Z 型(反)
};

7种类型的俄罗斯方块,每种方块有上下左右四种方向,一个俄罗斯方块有四个基本
方块组成每个方向只要知道基准方块的位置(x坐标和y坐标)即可绘制,所以数组元素
单位有:4 × 4 × 2 =32个元素

俄罗斯方块设计的最终结果:
在这里插入图片描述

初始方块的随机生成

在如上所述的设计中,一个俄罗斯方块的全部属性仅仅由以下三者共同唯一决定:

  • 俄罗斯方块类型索引s_idx
  • 俄罗斯方块形状索引d_idx
  • 俄罗斯方块颜色索引c_idx

因此,如果要随机生成一个方块,只需要令其
在这里插入图片描述
要确定当前俄罗斯方块和下一个俄罗斯方块,我们需要下面6个变量
在这里插入图片描述
当需要切换到下一个俄罗斯方块时,将下一个俄罗斯方块的全部索引赋值给当前俄罗斯方块,然后重新随机生成下一个俄罗斯方块。

now_c_idx = next_c_idx;
now_s_idx = next_s_idx;
now_d_idx = next_d_idx;
next_c_idx = rand() % 7;
next_s_idx = rand() % 7;
next_d_idx = rand() % 4;

游戏中的具体实现

/*****************************************************************************\
* Funcation: initDatasPerSquare
*
* Purpose  : 初始化每次掉落的俄罗斯方块数据
*
* Explain  : 初始化每次掉落的俄罗斯方块数据
\*****************************************************************************/

//每次方块的初始化
void initDatasPerSquare()
{
	My_Tetris_ReadLog(&MY_Tetris_Log);
	
	MY_Tetris_Log.now_mp_x = SQUARE_X_START;
	MY_Tetris_Log.now_mp_y = SQUARE_Y_START;
	MY_Tetris_Log.flag_next = 0;
	MY_Tetris_Log.now_c_idx = MY_Tetris_Log.next_c_idx;//俄罗斯方块的颜色索引
	MY_Tetris_Log.now_s_idx = MY_Tetris_Log.next_s_idx;//现在的俄罗斯方块类型索引 
	MY_Tetris_Log.now_d_idx = MY_Tetris_Log.next_d_idx;//俄罗斯方块形状索引,记录俄罗斯方块的朝向
	MY_Tetris_Log.next_c_idx = rand() % 7;
	MY_Tetris_Log.next_s_idx = rand() % 7;
	MY_Tetris_Log.next_d_idx = rand() % 4;

	if(MY_Tetris_Log.now_s_idx == 4 && MY_Tetris_Log.now_d_idx) MY_Tetris_Log.now_mp_y += 2;
	My_Tetris_WriteLog(&MY_Tetris_Log);

	speedUp = 0;
}

检查方块的是否可以移动

分别读取四个方块的左边,然后在遍历,看每个坐标是否左右和下面越界。返回一个布尔类型的变量。

/*****************************************************************************\
* Funcation: checkPut
*
* Purpose  : 游戏块是否到达边界
*
* Explain  : 游戏块是否到达边界
\*****************************************************************************/

MMI_BOOL checkPut(int mp_x, int mp_y, int dir_idx)
{
	int i;
	//存储四个方块坐标的数组
	int sq_x[4];
	int sq_y[4];
	My_Tetris_ReadLog(&MY_Tetris_Log);
	for (i = 0; i < 4; ++i)
	{
		sq_x[i] = mp_x + squares[now_s_idx].dir[dir_idx][i][0];  //现在方块坐标的x轴
		sq_y[i] = mp_y + squares[now_s_idx].dir[dir_idx][i][1];  //现在方块坐标的y轴
	}

	// 【左右越界、下方越界、重复占格】
	for (i = 0; i < 4; ++i)
	{
		if (sq_x[i] < 0 || sq_x[i] > SQUARE_X_NUM - 1 || sq_y[i] > SQUARE_Y_NUM - 1)
			return MMI_FALSE;
		if (sq_y[i] < 0) // 检查坐标合法性
			continue;
		if (MY_Tetris_Log.MAP[sq_x[i]][sq_y[i]])
			return MMI_FALSE;
	}
	return MMI_TRUE;
}

方块下移

/*****************************************************************************\
* Funcation: moveDown
*
* Purpose  : 游戏块向下
*
* Explain  : 游戏块向下
\*****************************************************************************/

void moveDown()
{	
	My_Tetris_ReadLog(&MY_Tetris_Log);
	// 尝试能否下移
	if (checkPut(MY_Tetris_Log.now_mp_x, MY_Tetris_Log.now_mp_y + 1, MY_Tetris_Log.now_d_idx))
	{
		++MY_Tetris_Log.now_mp_y;
		My_Tetris_WriteLog(&MY_Tetris_Log);
		return;
	}
	// 不能下移则说明这块方块“触礁”了,执行以下操作
	// 1、提示可以开始下一个方块
	// 2、将方块记录到map地图中
	// 3、判断是否可以消行
	// 4、判断消行后游戏是否结束
	MY_Tetris_Log.flag_next = 1;
	My_Tetris_WriteLog(&MY_Tetris_Log);
	recordSquareNow();
	execClear();
	if (checkOver())
	{
		flag_over = 1;
		MY_Tetris_Log.flag_over = flag_over;
		My_Tetris_WriteLog(&MY_Tetris_Log);
		if(MY_Tetris_Log.game_status == STATUS_PLAYING)
			My_Tetris_PlaySound((U8*)Tetris_Lose_Sound,sizeof(Tetris_Lose_Sound),FALSE);		
	}
	
}

就是每个方块y轴坐标加一个单位。

方块左右移

就是每个方块x轴坐标加加减一个单位。

方块旋转

/*****************************************************************************\
* Funcation: moveRotate
*
* Purpose  : 游戏块旋转
*
* Explain  : 游戏块旋转
\*****************************************************************************/

void moveRotate()
{
	int i;
	My_Tetris_ReadLog(&MY_Tetris_Log);
	now_mp_x = MY_Tetris_Log.now_mp_x;
	now_mp_y = MY_Tetris_Log.now_mp_y;
	now_d_idx = MY_Tetris_Log.now_d_idx;
	// 尝试剩余所有形状,可以旋转即调整旋转状态
	for (i = 1; i <= 3; ++i)
		if (checkPut(now_mp_x, now_mp_y, (now_d_idx + i) % 4))
		{
			now_d_idx = (now_d_idx + i) % 4;
			MY_Tetris_Log.now_d_idx = now_d_idx;
			My_Tetris_WriteLog(&MY_Tetris_Log);
			break;
		}
}

一个俄罗斯方块由dir的三维数组表示,右边的第一个维度代表方向,我们遍历剩下的三种形态,并判度是否可以旋转,然后进行旋转。

方块消行

/*****************************************************************************\
* Funcation: execClear
*
* Purpose  : 删去成行游戏块并加分
*
* Explain  : 删去成行游戏块并加分
\*****************************************************************************/

void execClear()
{
	int i;
	int j;
	int cnt_j = SQUARE_Y_NUM;
	My_Tetris_ReadLog(&MY_Tetris_Log);

	memcpy(mp, MY_Tetris_Log.MAP, SQUARE_X_NUM * SQUARE_Y_NUM*sizeof(int));
	memcpy(mp_c, MY_Tetris_Log.MAP_C, SQUARE_X_NUM * SQUARE_Y_NUM*sizeof(int));
	
	memset(mp_tmp, 0, sizeof(mp_tmp));
	memset(mp_c_tmp, 0, sizeof(mp_c_tmp));

	//j代表地图中的第几行
	//game_height/p 代表地图中的函数
	for (j = SQUARE_Y_NUM; j >= 0; --j)
	{
		int cnt = 0;//cnt统计一行中的方块个数
		//i代表地图中的第几列
		for (i = 0; i < SQUARE_X_NUM; ++i)
			if (mp[i][j])
				++cnt;
		if (cnt != SQUARE_X_NUM)     //行不满时
		{
			for (i = 0; i < SQUARE_X_NUM; ++i)
			{
				mp_tmp[i][cnt_j] = mp[i][j];
				mp_c_tmp[i][cnt_j] = mp_c[i][j];
				//疑问:flag_over的值在这里改变了,当mp_tmp[8][14]=0到mp_tmp[9][14]=1时,flag_over的值从0变为1
				MY_Tetris_Log.flag_over = 0;
			}
			--cnt_j;
		}
		else{
			++tetris_player_score_count;  //行满时,增加分数
			MY_Tetris_Log.tetris_player_score_count = tetris_player_score_count;
			My_Tetris_PlaySound((U8*)Tetris_Clear_Sound,sizeof(Tetris_Clear_Sound),FALSE);
		}
		My_Tetris_WriteLog(&MY_Tetris_Log);
			     
	}
	//更新地图
	for (j = 0; j < SQUARE_Y_NUM ; ++j)
		for (i = 0; i < SQUARE_X_NUM; ++i)
		{
			MY_Tetris_Log.MAP[i][j] = mp_tmp[i][j];
			MY_Tetris_Log.MAP_C[i][j] = mp_c_tmp[i][j];
		}

	My_Tetris_WriteLog(&MY_Tetris_Log);
}

从底部第一行开始遍历,累加一行中的方块个数,如果它满行了,进行销行处理。
销行的操作,主要是定义了一个cnt_j变量,和空的地图数组,一行地图循环遍历时,行不满的时候cnt_j上移一行,行满的时候cnt_j,不变,以达到销行的目的。

定时器

在这里插入图片描述

功能模块

本游戏主要有六个功能模块组成:
在这里插入图片描述

在这里插入图片描述
继续菜单隐藏了。

新游戏

游戏运行所涉及到的核心是:

  1. 对T卡中的数据文件进行读写的操作
  2. 游戏地图的绘制
  3. 菜单高亮函数的设置
  4. 游戏逻辑
  5. 功能按键的实现

游戏数据的保存

把游戏中所有要中途保存的数据写入一个结构体中,方便将数据批量写入T卡中。

//写入手机内存的数组,记录游戏数据
typedef struct Grades{
	char name[20];
	int score;
}Grades;

typedef struct Tetris_RECORD{    

	int MAP[SQUARE_X_NUM][SQUARE_Y_NUM];
	color MAP_C[SQUARE_X_NUM][SQUARE_Y_NUM];
	int level;
	int time;
	int new_or_resume_flag;
	int now_mp_y;
	int now_mp_x;
	int now_s_idx;
	int now_d_idx;
	int now_c_idx;
	int next_s_idx;
	int next_d_idx;
	int next_c_idx;
	int flag_over;
	int flag_next;
	int CurElapse;
	int tetris_player_score_count;
	int game_status;
	MMI_BOOL sound;
	Grades Tetris_grades[6]; 
}Tetris_RECORD;

Tetris_RECORD MY_Tetris_Log;

写此结构体写入和读取T卡中的函数如下:

//写入到手机存储器成为log的方法
void My_Tetris_InitLog(){  
	memset(&MY_Tetris_Log,0,sizeof(MY_Tetris_Log));
	MY_Tetris_Log.sound = MMI_TRUE;
	MY_Tetris_Log.new_or_resume_flag = 0;
	MY_Tetris_Log.level = 1;
}    
    

void My_Tetris_WriteLog(){
	S32 size;
	FS_HANDLE fd ;
	fd= FS_Open(L"D:\\MY_Tetris_Log.txt",FS_CREATE);	
	if(fd >= 0){
	  FS_Write(fd, (void*)&MY_Tetris_Log, sizeof(MY_Tetris_Log), (UINT*)&size);			
	  FS_Close(fd);
	}
} 


void My_Tetris_ReadLog(){

	S32 size; 
	FS_HANDLE fd ;
	fd= FS_Open(L"D:\\MY_Tetris_Log.txt",FS_READ_ONLY); 
	if(fd >= 0 ){
	  if(FS_GetFileSize(fd, (UINT *)&size)==FS_NO_ERROR){
		 FS_Read( fd,(void*)&MY_Tetris_Log,sizeof(MY_Tetris_Log),(UINT*)&size );
	   }
		 FS_Close(fd);	  
	}
	else{
		 My_Tetris_InitLog();
		 My_Tetris_WriteLog();
	}
}

游戏地图的绘制

游戏中的地图绘制,是将数组中的元素以绘制方块的形式绘制到屏幕上,数组元素便是绘制矩形框的位置。
在这里插入图片描述
一个数组储存位置,一个数组储存颜色。

菜单高亮函数

屏幕结构以及相应的处理函数
在这里插入图片描述

游戏逻辑

游戏详细的流程图如下:
在这里插入图片描述

功能按键的实现


//俄罗斯方块按键注册
void tetris_SetKeyHandler(){
	SetKeyHandler(Press_key_enter,  KEY_ENTER, KEY_EVENT_DOWN); 
	SetKeyHandler(Press_key_enter,  KEY_5, KEY_EVENT_DOWN);

	SetKeyHandler(press_moveDown, KEY_8, KEY_EVENT_DOWN);
	SetKeyHandler(press_moveDown, KEY_DOWN_ARROW, KEY_EVENT_DOWN);    

	SetKeyHandler(moveLeft, KEY_4, KEY_EVENT_DOWN);
	SetKeyHandler(moveLeft, KEY_LEFT_ARROW, KEY_EVENT_DOWN);
	
	SetKeyHandler(moveRight, KEY_6, KEY_EVENT_DOWN);
	SetKeyHandler(moveRight, KEY_RIGHT_ARROW, KEY_EVENT_DOWN);

	//SetKeyHandler(tetris_reset_timer, KEY_2, KEY_EVENT_DOWN);
	SetKeyHandler(moveDown_Fast, KEY_UP_ARROW, KEY_EVENT_DOWN);
	SetKeyHandler(moveDown_Fast, KEY_2, KEY_EVENT_DOWN);
	
	SetKeyHandler(highlight_tetris_option, KEY_LSK, KEY_EVENT_UP);
    SetKeyHandler(close_tetris_screen, KEY_RSK, KEY_EVENT_UP);
	
}

多功能按键的注册可以设置一个游戏状态变量,让后用swich语句来调整。

继续游戏

  1. 读取My_Tetris_WriteLog(&MY_Tetris_Log),读取MY_Tetris_Log

  2. 判断MY_Tetris_Log 的new_or_resume_flag是否为真

  3. 如果为真则隐藏Resume菜单,为假则显示Resume菜单

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

排行榜

排行榜功能主要包括数据的获取和界面的实现。

  1. 数据获取通过My_Tetris_ReadLog这个函数来读取T卡中数据文件中保存的数据
  2. 界面实现是通过ShowCategory153Screen模板来实现

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

帮助菜单

此菜单主要是对游戏的按键的说明,以及游戏得分的规则,按右键可返回至游戏列表界面。使用模板ShowCategory74Screen实现的。
在这里插入图片描述

声音

从其它游戏中移植过来 声音的二进制文件

//一行被清除时播放的音乐
#define  TETRIS_MID_CLEAR_LEN 306 //513
  
const U8 Tetris_Clear_Sound[TETRIS_MID_CLEAR_LEN] = {

	0x4D,0x54,0x68,0x64,0x00,0x00,0x00,0x06,0x00,0x01,0x00,0x04,0x03,0xC0,0x4D,0x54,0x72,0x6B,0x00,0x00,
	0x00,0x50,0x00,0xFF,0x03,0x08,0x75,0x6E,0x74,0x69,0x74,0x6C,0x65,0x64,0x00,0xFF,0x02,0x1B,0x43,0x6F,
	0x70,0x79,0x72,0x69,0x67,0x68,0x74,0x20,0x3F,0x32,0x30,0x30,0x33,0x20,0x62,0x79,0x20,0x47,0x61,0x6D,
	0x65,0x6C,0x6F,0x66,0x74,0x00,0xFF,0x01,0x08,0x4D,0x61,0x74,0x68,0x69,0x65,0x75,0x0A,0x00,0xFF,0x58,
	0x04,0x04,0x02,0x18,0x08,0x00,0xFF,0x59,0x02,0x00,0x00,0x00,0xFF,0x51,0x03,0x07,0xA1,0x20,0x00,0xFF,
	0x2F,0x00,0x4D,0x54,0x72,0x6B,0x00,0x00,0x00,0x72,0x00,0xFF,0x03,0x07,0x54,0x72,0x61,0x63,0x6B,0x20,
	0x31,0x00,0xC0,0x11,0x00,0xB0,0x07,0x7F,0x2D,0x65,0x00,0x01,0x64,0x00,0x01,0x06,0x04,0x01,0x26,0x58,
	0x36,0xE0,0x67,0x06,0x08,0x90,0x38,0x7D,0x00,0x41,0x6B,0x15,0xE0,0x47,0x07,0x10,0x7E,0x08,0x10,0x49,
	0x0A,0x10,0x6B,0x0B,0x10,0x35,0x0D,0x10,0x35,0x0F,0x10,0x06,0x11,0x10,0x09,0x13,0x10,0x69,0x15,0x10,
	0x05,0x1A,0x10,0x1C,0x21,0x10,0x0D,0x27,0x10,0x70,0x2D,0x10,0x03,0x36,0x10,0x3E,0x3E,0x10,0x66,0x49,
	0x10,0x2D,0x55,0x10,0x1F,0x61,0x10,0x0B,0x6D,0x06,0x90,0x41,0x00,0x00,0x38,0x00,0x0A,0xE0,0x2E,0x76,
	0x00,0xFF,0x2F,0x00,0x4D,0x54,0x72,0x6B,0x00,0x00,0x00,0x2B,0x00,0xFF,0x03,0x07,0x54,0x72,0x61,0x63,
	0x6B,0x20,0x32,0x00,0xC1,0x76,0x00,0x91,0x3B,0x57,0x1E,0x3B,0x00,0x0F,0x3D,0x63,0x1A,0x3D,0x00,0x02,
	0x3F,0x69,0x1B,0x3F,0x00,0x04,0x42,0x7B,0x1E,0x42,0x00,0x00,0xFF,0x2F,0x00,0x4D,0x54,0x72,0x6B,0x00,
	0x00,0x00,0x17,0x00,0xFF,0x03,0x07,0x54,0x72,0x61,0x63,0x6B,0x20,0x33,0x6E,0x99,0x26,0x64,0x82,0x3B,
	0x26,0x00,0x00,0xFF,0x2F,0x00	
};
  
//游戏进行时的背景音乐  

//游戏失败音乐      

不足

本次制作的游戏还存在很多的不足。

  1. 游戏界面设计的还是比较难看。
    在这里插入图片描述
    这个是手机中原本存在的俄罗斯方块的设计界面,相比较之下我的游戏界面还比较丑陋。
  2. 游戏不太顺畅,由于定时器设置的不好,控制方块左右移动,有种延迟的感觉。
  3. 读写T卡的频率很高。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值