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

    四、收获与心得

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

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

  • 相关阅读:
    Python【第五篇】模块、包、常用模块
    Python【第四篇】函数、内置函数、递归、装饰器、生成器和迭代器
    TCP三次握手、四次挥手
    分别用postman和python做post请求接口功能测试
    Python【第三篇】文件操作、字符编码
    Python【第二篇】运算符及优先级、数据类型及常用操作、深浅拷贝
    Python【第一篇】python安装、pip基本用法、变量、输入输出、流程控制、循环
    oracle在windows(含客户端工具pl/sql安装)下安装
    Python【初识篇】简介
    Web jsp开发自学——ajax+servlet+echarts+json+gson 实现ajax传输servlert和echarts的数据,可视化结果
  • 原文地址:https://www.cnblogs.com/ying-hua/p/13096075.html
Copyright © 2011-2022 走看看