c++入门小程序:4.俄罗斯方块

该代码实现了一个简单的俄罗斯方块游戏,包括方块的生成、移动、旋转、碰撞检测和消除行等功能。游戏中,玩家可以通过键盘控制方块的移动和旋转,当方块落地后会变为不可移动的堆积块。游戏还包括得分系统和游戏结束条件。
摘要由CSDN通过智能技术生成
/*Shape和Board两个类互为友元,其中Shape类中的函数定义要放在Board类后面
用 system("color 0A"); 
其中color后面的0是背景色代号,A是前景色代号。各颜色代码如下: 
0=黑色 ;1=蓝色 ;2=绿色 ;3=湖蓝色 ;4=红色 ;5=紫色 ;6=黄色 ;7=白色 ;8=灰色 ;9=淡蓝色 ;A=淡绿色 ;B=淡浅绿色 
C=淡红色 ;D=淡紫色 ;E=淡黄色 ;F=亮白色 */
#include<iostream>
#include<windows.h>
#include<time.h>//要使用函数srand(time(0)), 使程序有随机性
#include<conio.h>

using namespace std;
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"
};
char rotate_hinder[19][17] =
{
	/*-------定义方块旋转受阻位置--------*/
	/*Z型0-1*/
	"0010000001100000","1100000000100000",
	/*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"
};

short Alter(short n)//图形旋转
{
	switch (n)
	{
	case 1:case 3:case 5:return n - 1; break;
	case 6:return 6; break;
	case 10:case 14:case 18:return n - 3; break;
	default:return n + 1; break;
	}
}
void SetPos(short x, short y)//设置坐标
{
	crd.X = x; crd.Y = y;
	SetConsoleCursorPosition(handle, crd);
}
void SetColor(short front, short back)//前景色,背景色
{
	SetConsoleTextAttribute(handle, front + back * 0x10);
}
short Menu()//选中开始游戏返回1,离开返回2
{
	short choice = 1;//用来跟踪用户选中的选项
	char c;//用来记录用户的按键信息

	system("cls");
	SetPos(9, 12);
	cout << "┌────────────────┐";
	SetPos(9, 13);
	cout << "│ 俄 罗 斯 方 块 │";
	SetPos(9, 14);
	cout << "└────────────────┘";
	SetPos(7, 21);
	cout << "按方向键控制方块";
	SetPos(7, 23);
	cout << "按空格键暂停/继续";
	while (1)
	{
		SetPos(15, 16);
		if (choice == 1)
		{
			SetColor(2, 7);
		}
		else
			SetColor(2, 0);
		cout << " 开始游戏 ";
		SetPos(15, 18);
		if (choice == 2)
		{
			SetColor(2, 7);
		}
		else
			SetColor(2, 0);
		cout << " 离   开 ";
		c = _getch();
		if (c == VK_RETURN)
			break;//用户按下了回车,离开循环体
		/*else
			if (c == 0)
				c = _getch();//假如用户按下的是方向键,c的值还为0,重新获得按键信息*/
		if (c == 72 || c == 80)
			choice = choice == 1 ? 2 : 1;//用户按了上,下键
	}
	SetColor(7, 0);//恢复成黑底白字
	return choice;
}
void PrintGameOver()
{
	SetPos(12, 9);
	SetColor(5, 6);
	cout << " Game Over! ";
	SetColor(7, 0);
}

class Score
{
private:
	short ln;//得分
public:
	void Reset() { ln = 0; }//重置分数
	void Print()
	{
		SetPos(4, 1);
		SetColor(2, 7);
		cout << " Lines: " << ln << " ";
		SetColor(7, 0);
	}
	Score() { Reset(); }
	void Update(short n)
	{
		ln += n;
	}
};
class Shape//图形形状
{
	friend class Board;
private:
	short present;//当前形状(取值0-18)
	short color;
	short x;//左上角横坐标
	short y;//左上角纵坐标
public:
	Shape(short, short, short);//第一个参数传入方块的颜色,后两个参数是x,y坐标;只声明,在后面定义
	void Draw(bool, bool);//第一个参数为真执行绘制操作,为假执行擦除操作,第二个参数为真按方块的颜色画,为假画成白色
	bool Rotate(Board&);//判断一个方块能否旋转,如果能,把旋转操作完成并返回真
	short Collision(Board&);//左侧,右侧,下方受阻时,返回值分别是“位或”1,2,4
	bool Move(short direction, Board&brd);//左移第一个参数为1,右移动为2
	bool Fall(Board& brd);//能下落返回true,不能下落返回false
	void DrawElsewhere(short _x, short _y);//在操作区外显示一个方块的初始形状(下一个方块)
};
class Board//游戏框类
{
	friend class Shape;
private:
	bool wall[22][14];//取值为真,表明该位置已被占据,为假表明该位置是空的
	//保存当前可以消除的最下面一行的行号,重绘时只需要处理行号0~bottom_line的部分,bottom_line+1~19的部分不用管
	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)
					wall[i][j] = 0;
				else
					wall[i][j] = 1;
		}
	}
	Board() { Clear(); }//构造函数
	void DrawBoard()
	{
		short i;
		SetColor(0xe, 0xe);
		for (i = 0; i < 21; i++)
		{
			SetPos(LMARGIN - 2, TMARGIN + i - 1);
			cout << "口";
		}
		for (i = 0; i < 21; i++)
		{
			SetPos(LMARGIN +20, TMARGIN + i - 1);
			cout << "口";
		}
		for (i = 0; i < 10; i++)
		{
			SetPos(LMARGIN +i*2, TMARGIN + 19);
			cout << "口";
		}
		SetColor(7, 0);
	}
	short SetBlockValue(Shape& shp)//对wall数组的值进行更新(堆积)
	{
		for (short i = 0; i < 16; i++)
		{
			if (type[shp.present][i] == '1') 
			{
				if (shp.y == 0) return -1;//game over
				wall[shp.y + i / 4][shp.x + i % 4] = true;
			}
		}
		return 0;
	}
	short Drop(Shape& shp) //更新wall数组,但没有在屏幕上显示
	{
		short i, n;//i用于遍历方块算在的16个字符,n用来保存检测过的方块占用的格子数
		short y, _y = -1;//y用于保存当前格子的行号,_y保存前一个格子的行号,初始化为一个不合理值-1
		short j;//j用于遍历一整行,检查是否有wall数组的元素为false
		short k;//用于从下往上逐行往下覆盖
		short lines = 0;//保存消除了多少排
		short top;//检测有没有遍历到堆积块顶部
		short l;//在覆盖操作中从左到右进行遍历
		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--)//覆盖:首先是从下往上逐行遍历,在同一行,又需要从左往右逐个遍历
					{
						top = 0;
						for (l = 2;  l< 12; l++)
						{
							wall[k][l] = wall[k - 1][l];
							top += wall[k - 1][l];//用来判断这一行是否没有堆积块
						}
						if (top == 0)
							break;//已经到了堆积部顶部,中止从下往上的遍历
					}
				}
			}
		}
		bottom_line = _y;
		return lines;
	}
	void DrawPile()//重绘堆积块,由bottom_line指定需要重绘的最下面一行
	{
		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 << "口";
				}
				else
				{
					SetColor(0, 0);//黑色,用于擦除
					cout << "  ";//两个空格,"口"内存占两个字符
				}
			}
		}
	}
	
};

//Shape类的函数定义
Shape::Shape(short _color, short _x=5, short _y=0) :color(_color), x(_x), y(_y) //第一个参数颜色值将成为决定方块形状的因素
{
	switch (_color)
	{
	case 1:present = 0; break;
	case 2:present = 2; break;
	case 3:present = 4; break;
	case 4:present = 6; break;
	case 5:present = 7; break;
	case 6:present = 11; break;
	default:
		color = 8; present = 15;
		break;
	}
}
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')
		{
			SetPos(LMARGIN-4  + (x + i % 4)*2 , TMARGIN + y + i / 4 - 1);
			cout << "口";
		}
	SetColor(7, 0);
}
bool Shape:: Rotate(Board& brd)
{
	for (short i = 0; i < 16; i++)
	{
		if (brd.wall[y + i / 4][x + i % 4] && rotate_hinder[present][i] == '1')
			//如果格局中的一个格子为真且该位置在方块的自身坐标中是能够阻碍旋转的
			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')
		{
			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);
		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);//绘制落地方块
		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++)//此方块初始状态只绘制前三行,故此处写i<12,此for循环用于擦掉前一次绘出的方块
	{
		SetPos(_x + i % 4 * 2, _y + i / 4);
		cout << "  ";
	}
	SetColor(color, color);//设置绘制颜色
	_y -= (present == 6 || present == 4);
	for (i = 0; i < 12; i++)//此for循环用于画出下一个方块
	{
		if (type[present][i] == '1')
		{
			SetPos(_x + i % 4 * 2, _y + i / 4);
			cout << "口";
		}
	}
}


int main()
{
	system("mode con cols=35 lines=27");//设置窗口大小
	handle = GetStdHandle(STD_OUTPUT_HANDLE);

	Board board;
	short tmp;
	Shape* shp, * shp_next;//指向每次产生的方块
	short times;//用来保存执行sleep()函数的次数
	srand(time(0));//使游戏每一次运行时产生的方块不一样
	char ch;//用来清空输入流文件
	Score score;

	while (Menu()==1)//每执行一遍循环就是一个方块从产生到着地的一个过程
	{
		system("cls");//擦掉菜单内容
		board.Clear();
		board.DrawBoard();
		score.Reset();//得分归零
		score.Print();
		shp_next= new Shape(rand() % 7 + 1);
		while (true)
		{
			shp = shp_next;//将下一方块赋给当前方块
			shp_next = new Shape(rand() % 7 + 1);//产生下一个新方块
			shp_next->DrawElsewhere(17, 1);
			times = 0;
			while (1)//每执行一遍循环就是捕捉一次按键行为的过程
			{
				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)//向上方向键,执行旋转
					shp->Rotate(board);
				if (GetAsyncKeyState(VK_DOWN) == -32767)//加速掉落
					shp->Fall(board);
				if (GetAsyncKeyState(VK_SPACE)==-32767)//空格暂停
				{
					while (!(GetAsyncKeyState(VK_SPACE) == -32767))
						Sleep(25);
				}
				Sleep(25);//延时
				times++;
				if (times == 10)//每循环十次,发生一次下落行为(给捕捉按键行为留下充足时间)
				{
					times = 0;
					if (!shp->Fall(board))
						break;
				}
			}
			if (board.SetBlockValue(*shp) == -1)
			{
				PrintGameOver();
				//fflush(stdin);//清空输入输出流
				while ((ch = _getch()) != VK_RETURN && ch != EOF);//清空输入流(ch=getchar())!= '\n'
				delete shp_next;
				break;
			}
			if (tmp = board.Drop(*shp))
			{
				board.DrawPile();
				score.Update(tmp);//累加得分
				score.Print();//显示得分
			}
			//...
			delete shp;

		}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值