zoukankan      html  css  js  c++  java
  • 我罗斯方块最终篇

    这个作业属于哪个课程 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.还有一个大问题!那就是玩游戏的时候千万不要把窗口调节得太小
    如果比游戏画面还小的话游戏会崩溃。还有不要最大化,因为最大化后光标就出现了。
    关于窗口的问题之后会慢慢解决。

    四、收获与心得

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

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

  • 相关阅读:
    Java 8 Lambda 表达式
    OSGi 系列(十二)之 Http Service
    OSGi 系列(十三)之 Configuration Admin Service
    OSGi 系列(十四)之 Event Admin Service
    OSGi 系列(十六)之 JDBC Service
    OSGi 系列(十)之 Blueprint
    OSGi 系列(七)之服务的监听、跟踪、声明等
    OSGi 系列(六)之服务的使用
    OSGi 系列(三)之 bundle 事件监听
    OSGi 系列(三)之 bundle 详解
  • 原文地址:https://www.cnblogs.com/ying-hua/p/13096075.html
Copyright © 2011-2022 走看看