C语言俄罗斯方块(VS2022版)

news2024/11/22 15:12:04

C语言俄罗斯方块

  • 演示视频
  • 一、前置知识
    • 1.Win32 API 的使用
    • 2.宽字符的使用
  • 二、封装核心数据与框架介绍
  • 三、核心操作介绍
    • 旋转操作
    • 检测操作
      • 水平检测
      • 竖直检测
      • 代码化简
  • 四、源码展示
    • 在 tetris.h 中:
    • 在 tetris.c 中:
    • 在 test.c 中:

以下代码环境为 VS2022 C语言

演示视频

俄罗斯方块演示视频

一、前置知识

1.Win32 API 的使用

俄罗斯方块会用到Win32 API的键盘、光标操作。Win32 API 光标隐藏定位和键盘读取等常用函数 读者可参考这篇博客。

2.宽字符的使用

为解决横纵字符坐标统一,需要使用宽字符。C语言宽字符 wchar_t 类型 这篇博客已经整理完毕,这里就不赘述了。

二、封装核心数据与框架介绍

在 tetris.h 中:

typedef enum GAME_STATUS
{
	NORMAL = 1,					// 正常运行
	END_NORMAL,					// 正常退出
	BEYOND_WALL					// 超过墙体
} GAME_STATUS;

typedef struct BOX
{
	int _curNum;				// 当前方块号码
	int _cur[4][4];				// 当前方块

	int _nexNum;				// 下一个方块号码
	int _nex[4][4];				// 下一个方块

	int _spinJudge[4][4];		// 临时判断旋转框架
} BOX;

typedef struct POS				// 界面定位
{
	int _x;
	int _y;
} POS;

typedef struct Tetris
{
	GAME_STATUS _status;		// 游戏状态

	BOX _Box;					// 方块准备
	POS _pos;					// 方块定位

	int _frame[HIGH][WIGHT];	// 屏幕
	int _spinAngle;				// 旋转方向
	int _sleep_time;			// 间隔时间
	int _score;					// 总分数
	int _get_score;				// 消除一行的分数
	int _fast_fall;				// 是否快速下落
	int _fall_time;				// 下落时间
	bool _check_block;			// 检查非主要方块
	bool _block_other;			// 是否打印非主要方块

	char* _block_color[7];		// 方块颜色

} Tetris, * pTetris;

游戏中方块可移动范围为高 20 格,宽 10 格的框架。为确保后续旋转后的水平检测、竖直检测和方块的颜色处理,这里实际采用高 26 格,宽 14 格的数字框架,存储方块颜色,并且映射到 Windows 的控制台画面上。

_Box 中 _cur 存储当前玩家操控的方块(主要方块),_nex 存储玩家下一个得到的方块, _spinJudge 用来存储旋转时判断的临时方块。

这里使用 _pos 来定位 _cur 数组的最左上角位置,方便后续移动与判断。蓝色表示被定位的方框。
在这里插入图片描述

三、核心操作介绍

由于篇幅问题,这里只讲解俄罗斯方块核心操作,包括:

  1. 旋转

  2. 检测

旋转操作

使用 4 * 4 的数组保存 7 个方块形状与对应颜色的数字编号,使用公式旋转框架中的数字编号,从而得到旋转的状态。

公式来源:【百万好评】国外技术大神C++游戏编程实战教程,油管580W收藏,新手10小时入门,并快速达到游戏开发能力(中英字幕)P1 1.俄罗斯方块

游戏中只需要 顺时针 90 度 和逆时针 90 度,则:

void block_number_spin(int arr[4][4], int spinAngle)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			switch (spinAngle)
			{
			case 1:		// 旋转90°
				arr[i][j] = 12 + i - (j * 4);
				break;
			case 2:		// 旋转270°	也就是逆时针90°
				arr[i][j] = 3 - i + (j * 4);
				break;
			}
		}
	}
}

操作如图:
在这里插入图片描述
我们只需计算坐标便得到对应顺时针 90 度的编号,如: i = 0, j = 0 时,数组[0][0] 原来编号元素为 0,通过公式 12 + i - (j * 4) 即 12 + 0 - (0 * 4) 得 12。

i = 3, j = 3 时,数组[3][3] 原来编号元素为 15,通过公式 有 12 + 3 - (3 * 4) 得 3。
在这里插入图片描述
并且我们发现,最初的编号数组和顺时针 90 度后的数组可以连续使用:
在这里插入图片描述

这意味着不用公式,只需准备好 最初编号数组、顺时针旋转 90 度的数组、逆时针旋转 90 度的数组,便可完成旋转操作。

在 7 个方块 7 * 4 = 28 个状态,只需要 每个方块准备一个,其他 21 个状态都可以用旋转来处理。

void getSpinArr(int(*judgeArr)[4], int(*dirArr)[4], int* login, int shape)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (login[0] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[1] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[2] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[3] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
		}
	}
}

void spin(pTetris tetris)
{
	int(*pcur)[4] = tetris->_Box._cur;
	// 临时用的计算数组
	static int temp[4][4] = { { 0, 1, 2, 3 },
							  { 4, 5, 6, 7 },
							  { 8, 9, 10, 11 },
							  { 12, 13, 14, 15 } };

	// 顺时针 也就是 右转
	static int rightArr[4][4] = { { 12, 8, 4, 0 },
								  { 13, 9, 5, 1 },
								  { 14, 10, 6, 2 },
								  { 15, 11, 7, 3 } };

	// 逆时针 也就是 左转
	static int leftArr[4][4] = { { 3, 7, 11, 15 },
								 { 2, 6, 10, 14 },
								 { 1, 5, 9, 13 },
								 { 0, 4, 8, 12 } };

	// 临时用的记录下标
	int login[4] = { 0 };

	// 记录
	int num = 0;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (pcur[i][j])
			{
				login[num++] = temp[i][j];
			}
		}
	}

	// 旋转形状记录
	int shape = tetris->_Box._curNum;

	/*
	* 先用数组(judgeArr) 保存旋转后的状态,
	* 然后将数组进行水平、竖直检查
	* 若水平方向既要左移又要右移,说明碰到不能旋转的情况,
	* 需要取消旋转,也就是 pcur 不拷贝 judgeArr
	*/
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	// 放下
	if (tetris->_spinAngle == 1) // 顺时针 
	{
		getSpinArr(judgeArr, rightArr, login, shape);
	}
	else
	{
		getSpinArr(judgeArr, leftArr, login, shape);
	}
}

当然,也可以枚举 7 个方块的所有状态,这里省略。

检测操作

水平检测

旋转后的水平空间不够时需要水平(左右)移动,出现以下情况则不能旋转:

  1. 当前水平空间不够
    在这里插入图片描述

黑色为非主要方块,红色为玩家操控的方块,蓝色方框为旋转后的情况。

  1. 方块移动后水平空间在相反方向再次移动
    在这里插入图片描述
    如图,绿色方框执行右移动,需要再次检测(蓝色),但发现需要左移动,也就是第二次移动与第一次移动方向相反。

并且需要注意什么时候左右移动,经过我个人单独测试(不一定对),当非主要方块出现在 4 * 4 数组右边时,覆盖需要左移动,反之右移动:
在这里插入图片描述

使用递归可以简洁处理水平操作:

int judgeSpinLevelMove(pTetris tetris, int moveDirection)
{
	int x = tetris->_pos._x + moveDirection;
	int y = tetris->_pos._y;

	int leftMove = 0;
	int rightMove = 0;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] < 0 && judgeArr[i][j]) // 检测是否占位 
			{
				if (x + j >= x + 2)		// 检查在 右半 框架		
				{
					if (frame[y + i][x + j + 1 - 1] >= 0) 
					{
						leftMove = 1;
					}
				}

				if (x + j < x + 2) 		// 检查在 左半 框架
				{
					if (frame[y + i][x + j + 1 + 1] >= 0) 	
					{
						rightMove = 1;
					}
				}
			}
		}
	}

	int lastXDir = x - tetris->_pos._x;		// 上一次移动的方向,大于0表示右移动,为负表示左移动。
	if (leftMove && !rightMove && lastXDir <= 0)
	{
		return judgeSpinLevelMove(tetris, moveDirection - 1) - 1;
	}
	else if (rightMove && !leftMove && lastXDir >= 0)
	{
		return judgeSpinLevelMove(tetris, moveDirection + 1) + 1;
	}
	else if ((rightMove && leftMove) || (rightMove && lastXDir < 0) || (leftMove && lastXDir > 0))
	{
		// 此时不能旋转,递归深度不会超过 5 次,移动范围也就是[-5, 5](左移5次,右移5次), 
		// 以负20为标记表示不能旋转
		return -20;					
	}
	return 0;
}

竖直检测

竖直检测可以类比水平检测,有:

  1. 当前竖直空间不够
    在这里插入图片描述
    黑色为非主要方块,红色为玩家操控的方块,蓝色方框为旋转后的情况。

  2. 方块移动后竖直空间在相反方向再次移动
    在这里插入图片描述
    如图,绿色方框执行下移动,需要再次检测(蓝色),但发现需要上移动,也就是第二次移动与第一次移动方向相反。

并且需要注意什么时候上下移动,经过我个人单独测试(不一定对),当非主要方块出现在 4 * 4 数组上边时,覆盖需要下移动,反之上移动:
在这里插入图片描述
使用递归处理竖直检测操作:

int judgeSpinVerticalMove(pTetris tetris, int moveDirection)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y + moveDirection;

	int upMove = 0;
	int downMove = 0;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] < 0 && judgeArr[i][j]) // 检测是否占位 
			{
				if (y + i >= y + 2)	// 检查超出 上半 框架		
				{
					if (frame[y + i - 1][x + j + 1] >= 0) 
					{
						upMove = 1;
					}
				}

				if (y + i < y + 2)	// 检查超出 下半 框架
				{
					if (frame[y + i + 1][x + j + 1] >= 0) 
					{
						downMove = 1;
					}
				}
			}
		}
	}

	int lastYDir = y - tetris->_pos._y;
	if (upMove && !downMove && lastYDir <= 0)
	{
		return judgeSpinVerticalMove(tetris, moveDirection - 1) - 1;
	}
	else if (downMove && !upMove && lastYDir >= 0)
	{
		return judgeSpinVerticalMove(tetris, moveDirection + 1) + 1;
	}
	else if ((downMove && upMove) || (downMove && lastYDir > 0) || (upMove && lastYDir < 0))
	{
		return -20;					// 此时不能旋转,以负20为标记
	}
	return 0;
}

代码化简

水平检测和竖直检测重复代码较多,可以简化:

void judgeVertical(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x, y = tetris->_pos._y + lastMoveDir;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (i >= 2 && tetris->_frame[y + i - 1][x + j + 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (i < 2 && tetris->_frame[y + i + 1][x + j + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

void judgeLevel(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x + lastMoveDir, y = tetris->_pos._y;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (j >= 2 && tetris->_frame[y + i][x + j + 1 - 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (j < 2 && tetris->_frame[y + i][x + j + 1 + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

int judgeSpinMove(pTetris tetris, int (*judgeMove)(pTetris, int, int, int, int*, int*), int lastMoveDir)
{
	int positiveDir = 0, negativeDir = 0;
	for (int i = 0; i < 16; ++i)
	{
		judgeMove(tetris, i / 4, i % 4, lastMoveDir, &positiveDir, &negativeDir);
	}

	if ((positiveDir && negativeDir) || (positiveDir && lastMoveDir < 0) || (negativeDir && lastMoveDir > 0))
	{
		return -20;
	}
	if (!positiveDir && !negativeDir)
	{
		return 0;
	}

	int moveDir = (positiveDir == 1 ? 1 : -1);
	return judgeSpinMove(tetris, judgeMove, lastMoveDir + moveDir) + moveDir;
}
  1. 将水平检测和竖直检测不同部分用函数分开,用回调函数的方法进行简化。

  2. positiveDir 表示 向右(或向下)移动,negativeDir 表示 向左(或向上)移动。

则旋转加判断函数可写为:

void spin(pTetris tetris)
{
	int(*pcur)[4] = tetris->_Box._cur;
	// 临时用的计算数组
	static int temp[4][4] = { { 0, 1, 2, 3 },
							  { 4, 5, 6, 7 },
							  { 8, 9, 10, 11 },
							  { 12, 13, 14, 15 } };

	// 顺时针 也就是 右转
	static int rightArr[4][4] = { { 12, 8, 4, 0 },
								  { 13, 9, 5, 1 },
								  { 14, 10, 6, 2 },
								  { 15, 11, 7, 3 } };

	// 逆时针 也就是 左转
	static int leftArr[4][4] = { { 3, 7, 11, 15 },
								 { 2, 6, 10, 14 },
								 { 1, 5, 9, 13 },
								 { 0, 4, 8, 12 } };

	// 临时用的记录下标
	int login[4] = { 0 };

	// 记录
	int num = 0;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (pcur[i][j])
			{
				login[num++] = temp[i][j];
			}
		}
	}

	// 旋转形状记录
	int shape = tetris->_Box._curNum;

	// 用数组(judgeArr) 保存旋转后的状态,
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	// 放下
	if (tetris->_spinAngle == 1) // 顺时针 
	{
		getSpinArr(judgeArr, rightArr, login, shape);
	}
	else
	{
		getSpinArr(judgeArr, leftArr, login, shape);
	}

	// 判断旋转后位置的合理性
	// 水平方向
	//int levelCondition = judgeSpinLevelMove(tetris, 0);
	// 竖直方向
	//int uprightCondition = judgeSpinVerticalMove(tetris, 0);

	int levelCondition = judgeSpinMove(tetris, judgeLevel, 0);
	
	int uprightCondition = 0;
	if (levelCondition >= -10)	// 当水平检测不过时,不用竖直检测
	{
		uprightCondition = judgeSpinMove(tetris, judgeVertical, 0);
	}

	if (levelCondition >= -10 && uprightCondition >= -10)
	{
		tetris->_pos._x += levelCondition;		// 改变水平坐标
		tetris->_pos._y += uprightCondition;	// 改变竖直坐标

		memcpy(pcur, judgeArr, sizeof(int) * 4 * 4);	
	}
	memset(judgeArr, 0, sizeof(int) * 4 * 4);
}

四、源码展示

在 tetris.h 中:

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 1) ? 1 : 0)

#include <stdbool.h>
#include <windows.h>
#include <assert.h>
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include <time.h>

#define HIGH 26				// 存储高
#define WIGHT 14			// 存储宽

#define WALL L'█'
//#define WALL L'■'

typedef enum GAME_STATUS
{
	NORMAL = 1,					// 正常运行
	END_NORMAL,					// 正常退出
	BEYOND_WALL					// 超过墙体
} GAME_STATUS;

typedef struct BOX
{
	int _curNum;				// 当前方块号码
	int _cur[4][4];				// 当前方块

	int _nexNum;				// 下一个方块号码
	int _nex[4][4];				// 下一个方块

	int _spinJudge[4][4];		// 临时判断旋转框架
} BOX;

typedef struct POS				// 界面定位
{
	int _x;
	int _y;
} POS;

typedef struct Tetris
{
	GAME_STATUS _status;		// 游戏状态

	BOX _Box;					// 方块准备
	POS _pos;					// 方块定位

	int _frame[HIGH][WIGHT];	// 屏幕
	int _spinAngle;				// 旋转方向
	int _sleep_time;			// 间隔时间
	int _score;					// 总分数
	int _get_score;				// 消除一行的分数
	int _fast_fall;				// 是否快速下落
	int _fall_time;				// 下落时间
	bool _check_block;			// 检查非主要方块
	bool _block_other;			// 是否打印非主要方块

	char* _block_color[7];		// 方块颜色

} Tetris, * pTetris;

void SetPos(int x, int y);		// 固定画面

void HideCursor();				// 隐藏光标

void blockCopy(int arr[4][4], int num); // 方块复制

void mapPrint();				// 地图打印

void initFrame(pTetris tetris);	// 初始化逻辑面板

void GameStart(pTetris tetris);	// 游戏初始化

void print_main_block_to_screen(pTetris tetris);

void print_other_block_to_screen(pTetris tetris);

void clean_main_block_to_screen(pTetris tetris);

void login_cur_to_frame(pTetris tetris);

void clean_cur_to_frame(pTetris tetris);

bool levelMoveJudge(pTetris tetris, int dir);

void GameRun(pTetris tetris);	// 游戏运行

void GameOver(pTetris tetris);

在 tetris.c 中:

#include "tetris.h"

void SetPos(int x, int y)  // 光标移动到(x,y)的位置,可以让画面刷新
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x, y };
	SetConsoleCursorPosition(handle, pos);
}

void HideCursor()  // 隐藏光标
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursor_info;  // 第二个值为0表示隐藏光标
	GetConsoleCursorInfo(handle, &cursor_info);
	cursor_info.bVisible = false;
	SetConsoleCursorInfo(handle, &cursor_info);
}

void FontSize()
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_FONT_INFOEX fontInfo;						// 字体大小设置
	fontInfo.cbSize = sizeof(CONSOLE_FONT_INFOEX);
	GetCurrentConsoleFontEx(handle, FALSE, &fontInfo);	// 获得字体信息

	fontInfo.dwFontSize.Y = 25;

	SetCurrentConsoleFontEx(handle, FALSE, &fontInfo);	// 设置字体信息
}

void blockCopy(int arr[4][4], int num) // 方块复制
{
	static int arr1[7][4][4] = { { {0, 0, 0, 0},  // 0.长方形
							{1, 1, 1, 1},
							{0, 0, 0, 0},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 1.正方形
							{0, 2, 2, 0},
							{0, 2, 2, 0},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 2.T形
							{0, 0, 3, 0},
							{0, 3, 3, 3},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 3.L形
							{0, 0, 4, 0},
							{4, 4, 4, 0},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 4.反L形
							{0, 5, 0, 0},
							{0, 5, 5, 5},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 5.Z形
							{0, 6, 6, 0},
							{0, 0, 6, 6},
							{0, 0, 0, 0} },
						  { {0, 0, 0, 0},  // 6.反Z形
							{0, 7, 7, 0},
							{7, 7, 0, 0},
							{0, 0, 0, 0} } };
	// 复制
	memcpy(arr, arr1[num], sizeof(int) * 4 * 4);
}

void mapPrint()				// 地图打印
{
	for (int i = 0; i < 24; i += 2)		// 上
	{
		SetPos(i, 0);
		wprintf(L"%lc", WALL);
	}
	for (int i = 0; i < 24; i += 2)		// 下
	{
		SetPos(i, 21);
		wprintf(L"%lc", WALL);
	}
	for (int i = 1; i <= 20; ++i)		// 左
	{
		SetPos(0, i);
		wprintf(L"%lc\n", WALL);
	}
	for (int i = 1; i <= 20; ++i)		// 右
	{
		SetPos(22, i);
		wprintf(L"%lc\n", WALL);
	}
}

void initFrame(pTetris tetris)
{
	for (int i = 0; i < HIGH - 1; ++i)	// 行
	{
		tetris->_frame[i][1] = -10;
		tetris->_frame[i][WIGHT - 2] = -10;
	}
	for (int i = 1; i < WIGHT - 1; ++i)	// 列
	{
		tetris->_frame[HIGH - 2][i] = -10;
	}
}

void createBlockNumber(pTetris tetris)
{
	tetris->_pos._x = 4;
	tetris->_pos._y = 1;

	tetris->_Box._curNum = tetris->_Box._nexNum;
	tetris->_Box._nexNum = rand() % 7;
}

void currentCopyNext(pTetris tetris)
{
	memcpy(tetris->_Box._cur, tetris->_Box._nex, sizeof(int) * 4 * 4);
}

void printCurrentBlock(pTetris tetris)
{
	SetPos(26, 2);

	printf("当前方块:");
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			SetPos(26 + (j * 2), 3 + i);
			if (tetris->_Box._cur[i][j] > 0)
			{
				char* getColor = tetris->_block_color[tetris->_Box._curNum];

				printf("%s", getColor);
				wprintf(L"%lc", WALL);
				printf("\033[0m");
			}
			else
			{
				printf("  ");
			}
		}
	}
}

void printNextBlock(pTetris tetris)
{
	SetPos(26, 8);

	printf("下一个方块:");
	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			SetPos(26 + (j * 2), 9 + i);
			if (tetris->_Box._nex[i][j] > 0)
			{
				char* getColor = tetris->_block_color[tetris->_Box._nexNum];

				printf("%s", getColor);
				wprintf(L"%lc", WALL);
				printf("\033[0m");
			}
			else
			{
				printf("  ");
			}
		}
	}
}

void printGetScore(pTetris tetris)
{
	SetPos(26, 14);
	printf("当前分数:%d", tetris->_score);
}

void printOperatorInfo()
{
	SetPos(26, 16);

	printf("顺时针旋转: ↑");
	SetPos(26, 17);
	printf("逆时针旋转: ↓");
	SetPos(26, 18);
	printf("左移动:     ←");
	SetPos(26, 19);
	printf("右移动:     →");
	SetPos(26, 20);
	printf("空格加速开关");
}

void initInfo(pTetris tetris)
{
	printCurrentBlock(tetris);
	printNextBlock(tetris);
	printGetScore(tetris);
	printOperatorInfo();
}

void initBlockColor(pTetris tetris)
{
	static char* block_color[7] = { "\033[31m", "\033[33m", "\033[36m" , "\033[34m", "\033[35m" , "\033[32m" ,"\033[36;0m" };	// 方块颜色

	memcpy(tetris->_block_color, block_color, sizeof(char*) * 7);
}

void GameStart(pTetris tetris)
{
	tetris->_sleep_time = 100;
	tetris->_status = NORMAL;
	tetris->_get_score = 10;
	tetris->_score = 0;
	tetris->_Box._curNum = 2;
	tetris->_fall_time = 10;

	initBlockColor(tetris);

	// 方块准备
	tetris->_Box._nexNum = rand() % 7;
	blockCopy(tetris->_Box._nex, tetris->_Box._nexNum);

	createBlockNumber(tetris);
	currentCopyNext(tetris);
	blockCopy(tetris->_Box._nex, tetris->_Box._nexNum);

	FontSize();				// 字体大小

	mapPrint();				// 地图打印
	initFrame(tetris);		// 初始化逻辑面板
	initInfo(tetris);		// 打印提示信息
	print_other_block_to_screen(tetris);
}

void print_other_block_to_screen(pTetris tetris)
{
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 4; i < HIGH - 2; ++i)
	{
		for (int j = 2; j < WIGHT - 2; ++j)
		{
			if (frame[i][j] < 0)
			{
				SetPos((j - 1) * 2, i - 3);

				int theNumberColor = -frame[i][j] - 1;
				char* getColor = tetris->_block_color[theNumberColor];

				printf("%s", getColor);
				wprintf(L"%lc\033[0m", WALL);
			}
			if (frame[i][j] == 0)
			{
				SetPos((j - 1) * 2, i - 3);
				printf("  ");
			}
		}
	}
}

void print_main_block_to_screen(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0 && y + i > 3)
			{
				SetPos((x + j) * 2, y + i - 3);

				char* getColor = tetris->_block_color[tetris->_Box._curNum];
				printf("%s", getColor);
				wprintf(L"%lc\033[0m", WALL);
			}
		}
	}
}

void clean_main_block_to_screen(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0 && y + i > 3)
			{
				SetPos((x + j) * 2, y + i - 3);
				printf("  ");
			}
		}
	}
}

void login_cur_to_frame(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0)
			{
				tetris->_frame[y + i][x + j + 1] = tetris->_Box._cur[i][j];	
			}
		}
	}
}

void clean_cur_to_frame(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (tetris->_Box._cur[i][j] > 0)
			{
				tetris->_frame[y + i][x + j + 1] = 0;						
			}
		}
	}
}

bool levelMoveJudge(pTetris tetris, int dir)
{
	// 检测移动是否可行
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 0; i < 4; ++i)
	{
		for (int j = 0; j < 4; ++j)
		{
			if (frame[y + i][x + j + 1] > 0 && frame[y + i][x + j + 1 + dir] < 0)
			{
				return false;
			}
		}
	}
	return true;
}

void judgeVertical(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x, y = tetris->_pos._y + lastMoveDir;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (i >= 2 && tetris->_frame[y + i - 1][x + j + 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (i < 2 && tetris->_frame[y + i + 1][x + j + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

void judgeLevel(pTetris tetris, int i, int j, int lastMoveDir, int* posiDir, int* negaDir)
{
	int x = tetris->_pos._x + lastMoveDir, y = tetris->_pos._y;
	if (tetris->_frame[y + i][x + j + 1] < 0 && tetris->_Box._spinJudge[i][j])
	{
		if (j >= 2 && tetris->_frame[y + i][x + j + 1 - 1] >= 0)
		{
			*negaDir = 1;
		}
		else if (j < 2 && tetris->_frame[y + i][x + j + 1 + 1] >= 0)
		{
			*posiDir = 1;
		}
	}
}

int judgeSpinMove(pTetris tetris, int (*judgeMove)(pTetris, int, int, int, int*, int*), int lastMoveDir)
{
	int positiveDir = 0, negativeDir = 0;
	for (int i = 0; i < 16; ++i)
	{
		judgeMove(tetris, i / 4, i % 4, lastMoveDir, &positiveDir, &negativeDir);
	}

	if ((positiveDir && negativeDir) || (positiveDir && lastMoveDir < 0) || (negativeDir && lastMoveDir > 0))
	{
		return -20;
	}
	if (!positiveDir && !negativeDir)
	{
		return 0;
	}

	int moveDir = (positiveDir == 1 ? 1 : -1);
	return judgeSpinMove(tetris, judgeMove, lastMoveDir + moveDir) + moveDir;
}

void getSpinArr(int(*judgeArr)[4], int(*dirArr)[4], int* login, int shape)
{
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (login[0] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[1] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[2] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
			else if (login[3] == dirArr[i][j])
			{
				judgeArr[i][j] = 1 + shape;
			}
		}
	}
}

void spin(pTetris tetris)
{
	int(*pcur)[4] = tetris->_Box._cur;
	// 临时用的计算数组
	static int temp[4][4] = { { 0, 1, 2, 3 },
							  { 4, 5, 6, 7 },
							  { 8, 9, 10, 11 },
							  { 12, 13, 14, 15 } };

	// 顺时针 也就是 右转
	static int rightArr[4][4] = { { 12, 8, 4, 0 },
								  { 13, 9, 5, 1 },
								  { 14, 10, 6, 2 },
								  { 15, 11, 7, 3 } };

	// 逆时针 也就是 左转
	static int leftArr[4][4] = { { 3, 7, 11, 15 },
								 { 2, 6, 10, 14 },
								 { 1, 5, 9, 13 },
								 { 0, 4, 8, 12 } };

	// 临时用的记录下标
	int login[4] = { 0 };

	// 记录
	int num = 0;
	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (pcur[i][j])
			{
				login[num++] = temp[i][j];
			}
		}
	}

	// 旋转形状记录
	int shape = tetris->_Box._curNum;

	// 用数组(judgeArr) 保存旋转后的状态,
	int(*judgeArr)[4] = tetris->_Box._spinJudge;

	// 放下
	if (tetris->_spinAngle == 1) // 顺时针 
	{
		getSpinArr(judgeArr, rightArr, login, shape);
	}
	else
	{
		getSpinArr(judgeArr, leftArr, login, shape);
	}

	// 判断旋转后位置的合理性
	// 水平方向
	//int levelCondition = judgeSpinLevelMove(tetris, 0);
	// 竖直方向
	//int uprightCondition = judgeSpinVerticalMove(tetris, 0);

	int levelCondition = judgeSpinMove(tetris, judgeLevel, 0);
	
	int uprightCondition = 0;
	if (levelCondition >= -10)	// 当水平检测不过时,不用竖直检测
	{
		uprightCondition = judgeSpinMove(tetris, judgeVertical, 0);
	}

	if (levelCondition >= -10 && uprightCondition >= -10)
	{
		tetris->_pos._x += levelCondition;		// 改变水平坐标
		tetris->_pos._y += uprightCondition;	// 改变竖直坐标

		memcpy(pcur, judgeArr, sizeof(int) * 4 * 4);	
	}
	memset(judgeArr, 0, sizeof(int) * 4 * 4);
}

void numSwap(pTetris tetris) // 将数字转为对应的负数然后重新生成新方块
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] > 0 && x + j < WIGHT - 2 && y + i < HIGH - 2)
			{
				frame[y + i][x + j + 1] = -frame[y + i][x + j + 1];
			}
		}
	}
	tetris->_block_other = true;	// 在屏幕打印非主要方块
}

void blockLock(pTetris tetris) // 锁定接触到非移动数字的方块
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 0; i < 4; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			if (frame[y + i][x + j + 1] > 0 && frame[y + i + 1][x + j + 1] < 0)// 方块检测 
			{
				numSwap(tetris);
				return;
			}
		}
	}
}

bool is_ridBlock(pTetris tetris) // 判断是否需要消除方块
{
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = HIGH - 3; i > 0; --i)
	{
		int countBlock = 0;
		for (int j = 2; j < WIGHT - 2; ++j)
		{
			if (frame[i][j] < 0)	
			{
				++countBlock;
			}
		}
		if (countBlock == 10)
			return true;
	}
	return false;
}

void ridBlock(pTetris tetris) // 消除方块判定
{
	int(*frame)[WIGHT] = tetris->_frame;
	int rowRid[HIGH - 1] = { 0 };

	for (int i = HIGH - 3; i > 0; --i)
	{
		int countBlock = 0;
		int countBlank = 0;
		for (int j = 2; j < WIGHT - 2; ++j)	
		{
			if (frame[i][j] < 0)
			{
				countBlock++;
			}
			if (frame[i][j] == 0)
			{
				++countBlank;
			}
		}
		if (countBlank == 10)	// 当前行若为空行,代表上面都没有方块,直接退出
		{
			break;
		}
		if (countBlock == 10)
		{
			rowRid[i] = rowRid[i + 1] + 1;
			// 清除当前行
			for (int j = 2; j < WIGHT - 2; ++j)
				frame[i][j] = 0;
		}
		else
		{
			rowRid[i] = rowRid[i + 1];
		}
	}

	int theRidScore = 0;
	for (int i = HIGH - 3; i > 0; --i)
	{
		int moveDown = rowRid[i];
		theRidScore = theRidScore > moveDown ? theRidScore : moveDown;
		if (rowRid[i - 1] != moveDown)
		{
			continue;
		}

		// rowRid 保存当前行需要向下移动多少行,注意 i - k 中 当 i > 4 时,可能会出现越界情况
		for (int k = 0; k < moveDown; ++k)
		{
			for (int j = 2; j < WIGHT - 2; ++j)	
			{
				frame[i + k][j] = frame[i + k - 1][j];
				frame[i + k - 1][j] = 0;
			}
		}
	}

	for (int i = 1; i <= theRidScore; ++i)
	{
		tetris->_score += i * tetris->_get_score;
	}
}

void fallJudge(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*pcur)[4] = tetris->_Box._cur;

	for (int i = 3; i >= 0; --i)
	{
		for (int j = 3; j >= 0; --j)
		{
			if (pcur[i][j] > 0 && frame[y + i + 1][x + j + 1] < 0)	
			{
				tetris->_check_block = true;
				break;
			}
		}
	}
}

void fall(pTetris tetris)
{
	int x = tetris->_pos._x;
	int y = tetris->_pos._y;

	int(*frame)[WIGHT] = tetris->_frame;
	int(*pcur)[4] = tetris->_Box._cur;

	for (int i = 3; i >= 0; --i)
	{
		for (int j = 3; j >= 0; --j)
		{
			if (pcur[i][j] > 0)	
			{
				frame[y + i][x + j + 1] = pcur[i][j];	
			}
		}
	}
}

void checkBeyondWall(pTetris tetris)
{
	int(*frame)[WIGHT] = tetris->_frame;

	for (int i = 3; i >= 0; --i)
	{
		for (int j = 2; j < WIGHT - 2; ++j)
		{
			if (frame[i][j] < 0)
			{
				tetris->_status = BEYOND_WALL;
				return;
			}
		}
	}
}

void GameRun(pTetris tetris)
{
	print_main_block_to_screen(tetris);
	login_cur_to_frame(tetris);							// 记入逻辑

	int game_fall = 0;

	do
	{
		if (KEY_PRESS(VK_UP))					// 顺时针转动
		{
			tetris->_spinAngle = 1;			
			clean_main_block_to_screen(tetris);
			clean_cur_to_frame(tetris);
			spin(tetris);
			printCurrentBlock(tetris);
			login_cur_to_frame(tetris);
			print_main_block_to_screen(tetris);
		}
		else if (KEY_PRESS(VK_DOWN))			// 逆时针转动
		{
			tetris->_spinAngle = 2;
			clean_main_block_to_screen(tetris);
			clean_cur_to_frame(tetris);
			spin(tetris);
			printCurrentBlock(tetris);
			login_cur_to_frame(tetris);
			print_main_block_to_screen(tetris);
		}
		else if (KEY_PRESS(VK_LEFT))			// 左移动
		{
			if (levelMoveJudge(tetris, -1))			// 需要检测
			{
				clean_main_block_to_screen(tetris);
				clean_cur_to_frame(tetris);
				tetris->_pos._x -= 1;
				print_main_block_to_screen(tetris);
				login_cur_to_frame(tetris);
			}
		}
		else if (KEY_PRESS(VK_RIGHT))			// 右移动
		{
			if (levelMoveJudge(tetris, 1))		// 需要检测
			{
				clean_main_block_to_screen(tetris);
				clean_cur_to_frame(tetris);
				tetris->_pos._x += 1;
				print_main_block_to_screen(tetris);
				login_cur_to_frame(tetris);
			}
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			if (tetris->_sleep_time == 100)
				tetris->_sleep_time = 5;
			else
				tetris->_sleep_time = 100;
		}
		//else if (KEY_PRESS(0x46))
		//{
		//	while (KEY_PRESS(0x46) == 0)
		//	{
		//		Sleep(tetris->_sleep_time);
		//	}
		//}

		++game_fall;

		if (game_fall > tetris->_fall_time)
		{
			game_fall = 0;

			// 下落判断
			fallJudge(tetris);

			// 检查是否接触非主要逻辑方块
			if (tetris->_check_block == true)
			{
				tetris->_check_block = false;
				blockLock(tetris);	// 将方块逻辑转成负数
				if (is_ridBlock(tetris))
				{
					ridBlock(tetris); // 消除一行方块判定
				}

				checkBeyondWall(tetris);

				createBlockNumber(tetris);
				currentCopyNext(tetris);
				blockCopy(tetris->_Box._nex, tetris->_Box._nexNum);
				printCurrentBlock(tetris);
				printNextBlock(tetris);
				printGetScore(tetris);
			}
			else
			{
				clean_main_block_to_screen(tetris);
				clean_cur_to_frame(tetris);
				++tetris->_pos._y;
				fall(tetris);
				print_main_block_to_screen(tetris);
			}
		}

		if (tetris->_block_other == true)
		{
			print_other_block_to_screen(tetris);
			tetris->_block_other = false;
		}

		Sleep(tetris->_sleep_time);

	} while (tetris->_status == NORMAL);
}

void GameOver(pTetris tetris)
{
	SetPos(10, 15);
	printf("游戏结束");
	SetPos(11, 16);
	system("pause");
}

在 test.c 中:

#include "tetris.h"

void menu()
{
	printf("***********************\n");
	printf("**** 1.play 0.exit ****\n");
	printf("***********************\n");
}

void game()
{
	Tetris tetris = { 0 };

	GameStart(&tetris);

	GameRun(&tetris);

	GameOver(&tetris);
}

void test()
{
	setlocale(LC_ALL, "");
	system("mode con cols=50 lines=30");
	system("title 俄罗斯方块");

	HideCursor();
	srand((unsigned int)time(NULL));
	int input = 2;
	do
	{
		menu();
		printf("请输入操作:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			system("cls");
			game();
			system("cls");
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
}

int main()
{
	test();

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118801.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小阿轩yx-Zabbix企业级分布式监控环境部署

小阿轩yx-Zabbix企业级分布式监控环境部署 前言 “运筹帷幄之中&#xff0c;决胜千里之外”监控在 IT 运维中占据着重要地位&#xff0c;按比例说占 30% 也不为过在监控系统开源软件中有很多可选择的工具&#xff0c;但是真正符合要求的、能够真正解决业务问题的监控系统软件…

W外链微信推广短连接怎么做?

制作微信推广链接的难点分析 一、内容创作难度 制作微信推广链接时&#xff0c;首先需要创作有吸引力的内容。这不仅要求内容本身有趣、有价值&#xff0c;还要能够激起人们的分享欲望。对于许多企业和个人来说&#xff0c;尤其是那些缺乏创意和写作能力的人来说&#xff0c;…

OpenHarmony鸿蒙开发( Beta5.0)智能甲醛检测系统实践

样例简介 本项目是基于BearPi套件开发的智能甲醛检测系统Demo&#xff0c;该设备硬件部分主要由小熊派单板套件和和甲醛检测传感器组成。智能甲醛检测系统可以通过云和手机建立连接&#xff0c;可以在手机上设置甲醛浓度阈值&#xff0c;传感器感知到的甲醛浓度超过阈值之后&a…

QQ邮箱“已发送”邮件竟然无法一键清空?看我操作,怎么删除12万+已发送邮件

最近遇到了一个问题&#xff0c;QQ邮箱提示我空间已满&#xff0c;所以我就专门去看看有哪些邮件可以删除&#xff0c;释放点空间。 我直接暴力删除了很多文件夹的邮件&#xff0c;在文件夹管理界面 有“清空”按钮&#xff0c;点一个即可清空。 但是。。。不出意外的话要出意…

南卡、韶音、墨觉:精选三款旗舰骨传导耳机全面对比评测!

在科技日新月异的今天&#xff0c;耳机作为我们日常生活中不可或缺的音频伴侣&#xff0c;正经历着前所未有的变革。特别是骨传导耳机&#xff0c;凭借其独特的声音传导方式和出色的佩戴体验&#xff0c;逐渐成为了运动爱好者和户外探索者的首选。在众多品牌中&#xff0c;南卡…

Pycharm的安装与Conda环境的配置

目录 第一步&#xff1a;下载并安装 PyCharm 社区版 第二步&#xff1a;创建新项目并配置 Python 解释器 第三步&#xff1a;配置 Conda 环境 第四步&#xff1a;验证环境 第五步&#xff1a;测试 PyTorch 第六步&#xff1a;测试基本 PyTorch 代码 第一步&#xff1a;下…

替代区块链

随着比特币的成功&#xff0c;人们逐渐意识到区块链技术的潜力&#xff0c;并随之出现了迅速的发展&#xff0c;各种区块链协议、应用程序和平台相应产生。 需要指出的是&#xff0c;在这种多元的局面下&#xff0c;很多项目迅速失去了它们的吸引力。事实上&#xff0c;有不少项…

深圳MES系统在制造业的应用与发展

深圳MES在制造业的应用与发展呈现以下几个特点&#xff1a; 应用范围广泛&#xff1a;深圳制造业涵盖了电子、通信、汽车、机械等多个领域&#xff0c;MES系统在这些领域的应用非常广泛。不同行业的企业可以根据自身的需求和特点&#xff0c;定制化地应用MES系统来实现生产管理…

测试即服务(TaaS):概念、优势及应用场景!

引言 随着数字化转型的深入发展&#xff0c;软件质量和用户体验变得愈发重要。传统的软件测试方法已经难以满足现代企业对于快速迭代和高质量交付的需求。在此背景下&#xff0c;“测试即服务”(Testing as a Service, TaaS) 模式应运而生&#xff0c;为软件测试带来了新的解决…

基于SpringBoot+Vue+MySQL的足球俱乐部管理系统

系统展示 用户前台界面 管理员后台界面 系统背景 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统足球俱乐部管理…

Gtest(Google Test)使用

下面Gtest是在arm-linux下运行的 https://download.csdn.net/download/qq_31868891/89729426 一、下载编译 1.下载gtest代码 https://github.com/google/googletest 2.配置编译 vscode安装CMake Tools 将上面下载的gtest代码文件夹拖到vscode里&#xff0c;然后选择对应的…

SAP 凭证的替代传输GGB1

SAP 凭证的替代传输GGB1 之前没有留意过&#xff0c;前人一直是直接改的&#xff0c;搜索了一下是可以这样弄得 1.一般通过OBBH&#xff0c;配置的凭证替代&#xff0c;产生的请求号&#xff0c;从开发机传输不到生产机。只能通过GGB1来传输。在GGB1里面选择要传输的替代 选中…

BookStack在线文档管理系统本地Docker部署与远程访问详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

LEAN类型系统属性之规范性(Regularity)注解

在 《小结》 中&#xff0c;列出LEAN类型系统所定义的全部规律&#xff0c;下面 关于 LEAN 属性 的一些推论&#xff08;Lemma&#xff09;进行注解。主要是其规范性&#xff08;Regularity&#xff09;&#xff0c;以说明LEAN类型系统是完备构建的&#xff08;well founded&am…

Java+selenium+chrome+linux/windows实现数据获取

背景&#xff1a;在进行业务数据获取或者自动化测试时&#xff0c;通常会使用模拟chrome方式启动页面&#xff0c;然后获取页面的数据。在本地可以使用windows的chromedriver.exe进行打开chrome页面、点击等操作。在linux 下通常使用无界面无弹窗的方式进行操作。接下来是实现方…

【AI绘画】Midjourney之Lighting详解

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;为什么要学习光影控制光影控制的作用 &#x1f4af;强化主题hard lighting&#xff08;硬光 &#xff09;soft lighting&#xff08;软光/柔光&#xff09;测试 &…

宠物浮毛怎么办,用专业工具——希喂、小米、美的宠物空气净化器

前段时间被医生科普&#xff0c;原来猫咪的毛发也分好几种的&#xff0c;我不是最后一个知道的吧...事情的起因是这样的&#xff0c;上次我去朋友家和她的猫咪玩&#xff0c;实在太可爱了所以自己也养了一只猫。一人一猫的幸福生活没过多久&#xff0c;我就发现自己的鼻炎好像复…

如何用AI先行者2.0轻松画出美图?我的亲身体验分享给你看!

越来越多的AI绘画工具开始出现在市场上。AI先行者2.0是一款备受关注的AI绘画软件。本文将为大家详细介绍这款软件的功能特点&#xff0c;并分享一些使用心得。 AI先行者2.0拥有强大的图像处理能力。 它支持多种AI模型&#xff0c;包括Midjourney、Niji、DallE3和Stable Diffu…

【C++】——string类的使用

目录 一.为什么学习string类&#xff1f; 1.1 C语言的字符串 二. 标准库中的string类 2.1 string类(了解) 2.2 string类成员函数 ● string类对象的常见构造 ● string类析构函数 ● 赋值重载 2.3 string的迭代器 <1>正向迭代器 Iterator <2> 反向迭代器…

接口测试工具:Postman详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、前言 在前后端分离开发时&#xff0c;后端工作人员完成系统接口开发后&#xff0c;需要与前端人员对接&#xff0c;测试调试接口&#xff0c;验证接口的正确性…