我罗斯方块最终篇

这个作业属于哪个课程 2020年面向对象程序设计
这个作业要求在哪里 面向对象程序设计寒假作业2
这个作业的目标 做出能运行的我罗斯方块、分享代码要点、收获与心得
小组成员 031902207-黄新成
github代码 https://github.com/ying-hua/MyGame/tree/master

一、游戏截图

我罗斯方块最终篇
我罗斯方块最终篇
我罗斯方块最终篇

二、代码要点

我在代码中写了很多注释,在github看会更清楚一点
把一些重要的代码写在这里

方块类

方块的表示

我开始想用一个二维数组来表示方块的形状,数值为1表示有方块,数值为0表示无方块
但这样要定义很多变量名不同的数组,使用时不方便
最后决定用三维数组来表示,第一维表示方块的形状
方块类中只需要一个属性shape就可以知道是什么方块了
下面这张图是方块的编号
我罗斯方块最终篇

//只选择了部分代码做示例
static int stdblock[30][4][4] = { //标准方块
	{ {1,1,1,1},{1,1,1,1 },{1,1,1,1},{1,1,1,1} },//0//无
        { {0,0,0,0},{0,0,0,0 },{1,1,1,1},{0,0,0,0} },//1
	{ {0,0,1,0},{0,0,1,0 },{0,0,1,0},{0,0,1,0} },//2
	{ {0,0,0,0},{0,0,0,0 },{0,0,0,0},{0,0,0,0} },//3//无
	{ {0,0,0,0},{0,0,0,0 },{0,0,0,0},{0,0,0,0} },//4//无
};

随机生成方块

随机生成方块函数我用了系统自带的rand()函数
每一种方块对应一种颜色
先从七种方块中随机选择一种,再随机选择这种方块的形态,这样每种方块产生的概率相同

//srand()函数在别的地方
//方块的颜色事先用了宏定义
void Block::roundBlock() {
	int random;//随机数
	random = rand() % 7;//产生0-6的随机数,随机一种方块
	random = random * 4 + 1;
	switch (random) {//随机方块形状和颜色
		case 1:color = GREEN | INTENSITY; random += rand() % 2; break;//亮绿
		case 5:color = RED | INTENSITY;break;//红
		case 9:color = RED | BLUE | INTENSITY; random += rand() % 4; break;//品红
		case 13:color = RED | GREEN | INTENSITY; random += rand() % 4; break;//黄
		case 17:color = BLUE | GREEN | INTENSITY; random += rand() % 4; break;//青
		case 21:color = BLUE | INTENSITY; random += rand() % 2; break;//蓝
		case 25:color = GREEN; random += rand() % 2; break;//绿
		default:break;
	}
	shape = random;
}

渲染类

class Render {
public:
	static void initialPrint1(HANDLE hOut,string name);  //初始化单人界面
	static void initialPrint2(HANDLE hOut,string name1,string name2);//初始化双人界面
	static void gotoXY(HANDLE hOut, int x, int y);  //移动光标
	static void printBlock(HANDLE hOut, int block[4][4],int x,int y,int color);//打印方块
	static void clearBlock(HANDLE hOut, int block[4][4],int x,int y);//消除方块
	static void printScore(HANDLE hOut, int score, int x, int y);//更新分数
	static void printMap(HANDLE hOut, Player player);//打印玩家地图
	static void printWin(HANDLE hOut, string name);//双人模式玩家获胜画面
};

打印方块函数

/*===========================
打印方块
传入需要打印的方块数组、坐标和颜色
在指定位置打印方块
方块超出屏幕上边的部分不会打印
方块的坐标不是在玩家地图上的坐标,而是在屏幕上的坐标
=============================*/
void Render::printBlock(HANDLE hOut, int block[4][4], int x,int y,int color) {
    SetConsoleTextAttribute(hOut, color); //设置颜色
    for (int i = 0; i < 4; i++) { //开始打印方块
        if (y + i < 0) //忽略超出地图上边的部分
            continue;
        for (int j = 0; j < 4; j++) {
            if (block[i][j] == 1) {
                Render::gotoXY(hOut, x + 2 * j, y + i); //移动光标打印方块
                cout << "■";
            }
        }
    }
}

打印玩家地图

为了区分双人模式两个玩家地图的位置
在玩家类定义了一个mapX属性
光标移动时加上该玩家的mapX就能移动到指定位置

/*===========================
打印玩家地图
打印指定玩家的地图
双人模式中两玩家地图的位置不同,用mapX的值区分
=============================*/
void Render::printMap(HANDLE hOut, Player player) {
    SetConsoleTextAttribute(hOut, RED | GREEN | BLUE | INTENSITY); //设置颜色为亮白色
    for (int i = 1; i <= 20; i++) { //开始打印地图
        for (int j = 1; j <= 10; j++) {
            if (player.map[i][j] == 1) { //有方块的地方
                gotoXY(hOut, 2 * j + player.mapX, i - 1);
                cout << "■";
            }
            else { //无方块的地方
                gotoXY(hOut, 2 * j + player.mapX, i - 1);
                cout << "  ";
            }
        }
    }
}

玩家类

由于不是每个方块都需要变形,只有玩家正在下落的方块可以变形
为了方便,我把方块移动和变形的函数都放在了玩家类
这样可能导致玩家类的函数太多

class Player {
public:
	Player();//构造函数
	void setName(string n);//设置玩家姓名
	void setScore(int s);//设置分数
	void setMapX(int x);//设置地图位置,玩家1为0,玩家2为50
	string getName();//获取玩家名字
	int getScore();//获取玩家分数
	int eliminateRow(HANDLE hOut);//判断并消行,加分,返回消去的行数
	void addRow(int x);//增加随机的x行
	bool collisionDetection(Block block);//检测方块是否卡墙或超出地图
	void goLeft(HANDLE hOut);//下落中的方块左移
	void goRight(HANDLE hOut);//下落中的方块右移右移
	void transform(HANDLE hOut);//下落中的方块变形
	void goDown(HANDLE hOut,Player &opponent);//加速下落,双人模式使用
	void goDown(HANDLE hOut);//加速下落,单人模式使用
	void buildMap(int x,int y,int shape);//将正在下落的方块固定在地图中
private:
	string name;//玩家名字
	int score;//玩家分数
	int map[25][15];//玩家当前的地图,从(1,1)到(20,10) 数值为1表示有方块,0表示没方块
	int mapX;//地图位置
	Block nextBlock;//下一个方块
	Block nowBlock;//正在下落的方块
	friend class Block;//这三个类为友元类
	friend class Render;
	friend class Game;
};

接下来是一些比较重要的函数

加行函数

增加的一行要有随机性,且不能是满行
于是我想先随机这一行小方块的个数
再随机它们的位置

/*===========================
加行
仅在双人模式使用
某玩家消行时,对手便在底部增加一行
增加的一行不可能是满行,其他情况都有可能
传入的参数x为增加的行数
=============================*/
void Player::addRow(int x) { 
	int n, pos[15] = { 0 }; //pos为方块的位置,n为方块个数
	while(x--){
		n = rand() % 10;//随机方块个数 0~9
		for (int i = 1; i <= n; i++) { //生成不同的n个位置
			pos[i] = rand() % 10 + 1;  //pos[i]的取值为1~10
			for (int j = 1; j < i; j++) { //判断是否重复,执行后保证所有方块的位置不重复
				if (pos[i] == pos[j]) {
					i--;
					break;
				}
			}
		}
		for (int i = 2; i <= 20; i++) { //地图上移
			for (int j = 1; j <= 10; j++) {
				map[i - 1][j] = map[i][j];
			}
		}
		for (int j = 1; j <= 10; j++) { //底层置零
			map[20][j] = 0;
		}
		for (int i = 1; i <= n; i++) { //放置方块
			map[20][pos[i]] = 1;
		}
	}
}

方块变形

刚开始写方块变形函数的时候遇到了一个问题
那就是有些方块贴在地图边缘时变形会卡出墙外
这时方块就不能变形,非常影响游戏体验
于是我想方块在变形之前就检测一下是否会超出地图范围
如果会超出就自动左移或右移若干格
但是对于每个方块移动的格数不同,就要分很多类,导致函数很长
我只截取了一部分放在这里

/*===========================
方块变形
先进行碰撞检测
若能变形就变形,否则跳过
有些方块靠近地图边缘变形时会超出地图
该函数将会自动将方块左右移动,使方块恰好不会超出地图
左右移动多少格与方块形状有关
因此对不同的方块要进行不同的处理,代码较长
而有些方块可以进行相同处理
因此要对方块特点分类
=============================*/
void Player::transform(HANDLE hOut) {
	int x, y, shape, color; //x,y为下落中的方块在地图上的坐标
	Block tempBlock;//临时方块
	x = nowBlock.getX();//变量获取数据
	y = nowBlock.getY();
	shape = nowBlock.getShape();
	color = nowBlock.getColor();
	tempBlock = nowBlock;
	if (shape == 5) //立方体形方块不会变形
		return;
	if (shape == 2) { //竖条形方块
		tempBlock.setShape(1); //变形为横条形
		if (!collisionDetection(tempBlock)) { //若能够变形
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1); //就把当前形状的方块清除
			nowBlock.setShape(1); //正在下落的方块变形
			return; //退出函数
		}
		tempBlock.setY(y - 1); //直接变形方块可能卡出地图外,那就试试向左移动一格能不能变形
		if (!collisionDetection(tempBlock)) {
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1);
			nowBlock.setShape(1);
			nowBlock.setY(y - 1); //自动往左移动一格
			return;
		}
		tempBlock.setY(y + 1); //再试试向右移动一格能不能变形
		if (!collisionDetection(tempBlock)) {
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1);
			nowBlock.setShape(1);
			nowBlock.setY(y + 1);
			return;
		}
		tempBlock.setY(y + 2); //再试试向右移动两格能不能变形
		if (!collisionDetection(tempBlock)) {
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1);
			nowBlock.setShape(1);
			nowBlock.setY(y + 2);
			return;
		}
	}
	else if (shape % 2 == 1) { //编号为奇数的方块有共同特点(除了编号为5的立方体形方块)
		tempBlock.setShape(shape + 1); //变形为下一个形状
		if (!collisionDetection(tempBlock)) { //可以直接变形
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1);
			nowBlock.setShape(shape + 1);
			return;
		}
	}
	else if (shape % 4 == 0) { //编号为4的倍数的方块
		tempBlock.setShape(shape - 3);
		if (!collisionDetection(tempBlock)) {
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1);
			nowBlock.setShape(shape - 3);
			return;
		}
		tempBlock.setY(y + 1);
		if (!collisionDetection(tempBlock)) {
			Render::clearBlock(hOut, stdblock[shape], 2 * y + mapX, x - 1);
			nowBlock.setShape(shape - 3);
			nowBlock.setY(y + 1);
			return;
		}
	}
        //之后的代码省略
}

游戏类

按键检测方法

我用了系统自带的_kbhit()函数和_getch()函数
检测到按键就调用相应的函数

if (_kbhit()) { //按键检测
			key = _getch(); //获取按键
			switch (key) {
			case 97: //←键
				player1.goLeft(hOut); break;
			case 100://→键
				player1.goRight(hOut); break;
			case 119://↑键
				player1.transform(hOut); break;
			case 115://↓键
				player1.goDown(hOut, p2); break;
			case 75://A键
				player2.goLeft(hOut); break;
			case 77://D键
				player2.goRight(hOut); break;
			case 72://W键
				player2.transform(hOut); break;
			case 80://S键
				player2.goDown(hOut, p1); break;
			case 112://P键
				gamePause(hOut); break;
			case 27://Esc键
				exitGame(hOut);break;
			default:break;
			}
		}

三、依然存在的问题

1.最大的问题是双人模式两个玩家不能同时长按,这样只有一个玩家能正常游戏,比较影响游戏体验
我准备之后再试试其他按键检测函数
2.代码过于冗长,特别是方块变形函数,还不知道该怎么简化
3.遇到一个小bug:一个玩家的方块落到底部,但还没有固定,这时增加了一行,下落的方块就会和地图原来的方块重合。
4.两个玩家的地图同时满了的时候可能会有bug,因为没有平手的设定。(这个概率应该很低吧?)

四、收获与心得

游戏第一次运行的时候特别兴奋,玩自己的游戏有不一样的感觉。
刚开始的时候特别迷茫,不知道怎么开始,特别是渲染的问题。
可是到后来就越来越清晰了,各种想法也是在这个时候出现的。
刚开始的时候不应该想整个游戏怎么写,这样容易把自己吓到。
如果就从一个函数开始,把问题细化,就更有信心解决了。
渐渐的代码上的红色波浪线就消失了。

我并没有花很长时间调试,最后稍微修改一下就差不多了。
因为每写完一个函数我就想办法测试一下,虽然花了一些时间。
但这样能让我更安心,这样后面的工作量就不会很大。

上一篇:控制台游戏5-贪吃蛇


下一篇:卷积和反卷积详细说明