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

    作业描述 详情
    这个作业属于哪个课程 班级链接
    这个作业要求在哪里 作业要求
    这个作业的目标 代码的 git 仓库链接。
    运行截图/运行视频。代码要点。
    收获与心得。
    依然存在的问题。
    作业正文 我罗斯方块最终篇汇报
    其他参考文献
    项目地址 项目GitHub地址
    小组成员 031902517-田剑心
    031902637-廖晓玲
    061900414-廖智炫

    仓库链接

    代码仓库链接:https://github.com/JustinRochester/World-Tetris


    运行视频


    代码要点、收获、心得

    田剑心

    在本次的大作业中,我主要负责Birck类是设计,以及后期对Render类中添加了渲染方块的方法

    代码要点

    Brick
    • Brick 作为一个游戏块,由4个小方块构成,通过数组保存坐标信息

      int BrickPos[9];//坐标集合
      
    • 为了便于坐标的推导以及移动计算,采用给定中心坐标的方法进而推导其他块的坐标

      int CenterX, CenterY;//中心点坐标
      
    • Player类交互,经过协商后只需要将颜色信息以及坐标以九元组形势返回

      int* getInformation();//返回信息
      
    • 在实现这部分的时候还未学习继承,因此代码采用选择结构来实现多种块的坐标推导

    • 旋转一个方块等于在中心坐标不变的情况将其变形成另一种形状,因此直接通过void brickSet(int x, int y);方法构造

      void Brick::rotateBrick() {
      	int x = CenterX;
      	int y = CenterY;
      
      	int CountRotate;
      	if (IsSym) CountRotate = 3;
      	else CountRotate = 1;
      
      	for (int i = 1; i <= CountRotate; i++) {
      		BrickType--;
      		if (BrickType >= 8) BrickType += 3;
      		BrickType = ((BrickType >> 2) << 2) | ((BrickType + 1) & 3);
      		if (BrickType > 8) BrickType -= 3;
      		BrickType++;
      		}
      	brickSet(x, y);
      }
      

      其他方法的具体实现可以在github上看源代码

    Render

    通过Player传来的Map[][]信息,再调用windows的SetConsoleCursorPosition()方法来获取光标的位置,即可避免system("cls")在一定程度上减少卡屏的效果,主要是双缓冲有点难搞

    之后就是循环填充输出图形

    具体实现见github

    收获和心得

    学了快1年的程序设计,一直只是做题目,感觉好像对自己的成长并未有很大的帮助,都不知道自己能干啥

    借助这个机会,强迫自己去开发,可以说是收获良多

    能够将简单的循环,选择等知识运用,借助搜索引擎,他人的开发经验,开发出一款团队合作的简单小游戏,可以说是肯定了自己的学习成果,虽然不咋地

    通过本次的大作业,第一次将c++运用到项目开发,能够开发出自己的小游戏,其实还是挺开心的

    在今后的学习中,应该多注重项目的制作,从小项目起手,完成一个项目不仅可以学到很多的知识点,而且还会有点小成就感。。。



    廖晓玲

    在本次的大作业中,我主要负责Render类的设计

    (但是写的都是比较基础的部分)

    代码要点

    Render
    • Render 是用来对各个模块的处理的一个模块

    • 这是为了给界面以及各个模式用来上色的函数

      void Render::SetColor(int color_num)//设置颜色
    {
    	int n;
    	switch(color_num)
    	{
    		case 0: n = 0x08; break;
    		case 1: n = 0x0C; break;
    		case 2: n = 0x0D; break;
    		case 3: n = 0x0E; break;
    		case 4: n = 0x0A; break;
    		case 5: n = 0x0F; break;
    		case 6: n = 0x09; break;
    		case 7: n = 0x0B; break;
    		case 8: n = 0x05; break;
    		case 9: n = 0x03; break;
    		case 10: n= 0x00; break;
    	}
    	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), n);
    }
    
    • 选择使用光标对各个数据进行移动
    void SetPos(int i, int j)			//控制光标位置, 列, 行
    {
       COORD pos={i,j};
       SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
    }
    
    • 然后是单人地图和双人地图
    	void DrawMap1();			//游戏界面
       void DrawMap2();
    

    收获和心得

    通过这次的我罗斯方块的项目,我学习到了一些东西。这是我第一次接触到了渲染这个部分,由于我对渲染这个部分理解不够到位,经常出现错误,但是我还是学习到了不少东西,以后会更加努力。我希望以后自己能够有更好的代码能力,为此我也会更加努力的去学习,不断完善自我。我也十分感谢能够加入我所在的小组,我的代码能力不足,对项目的贡献较小,而在这次的开发过程中,组长的领导和组织的能力令我敬佩,让我看到了团队齐心协力的所能迸发出的能量是巨大的。



    廖智炫

    本次大作业中,本人负责 Player类、Game类、FileRecoder类的开发,以及部分Brick类和Render类的优化


    代码要点

    Player
    • 该类主要负责的是执行指令、规则判定、后续变换。其中包括执行给定的指令(如果指令合法且有效)、命令Brick类进行变换、边界的判定、地图的储存、方块与地图的合并、加行操作以及分数的记录

    • 为了记录地图、积分以及玩家的个人信息,开通了以下三个属性来储存

        int CountScore;
        bool MapSqure[32][12];
        bool GameOver;
        std::string Name;
      
    • 同时,为了方便后续的可修改性,地图的边界采用 static const int 来定义。而为了方便后续代码的实现,将当前方块与下一方块设置为该类的成员

        static const int UP_LIM, DOWN_LIM, LEFT_LIM, RIGHT_LIM;
        Brick NowBrick, NextBrick;
      
    • 为了进可能减少码量,定义了方法 bool isOverlap() 来判定当前方块是否与地图重叠

      bool Player::isOverlap(){
        /*
        This method is used to check whether the working brick is overlapping the map.
        */
        const int* Tmp = NowBrick.getInformation();
      
        for (int i = 1; i < 9; i+=2)
        	if (Tmp[i]<UP_LIM || Tmp[i]>DOWN_LIM)
        		return true;
        for (int i = 2; i < 9; i+=2)
        	if (Tmp[i]<LEFT_LIM || Tmp[i]>RIGHT_LIM)
        		return true;
        /*
        The brick is out of the map. We considered that it is a case of overlap.
        (Considered that there are some walls around the map.)
        */
      
        for (int i = 1; i < 9; i+=2)
        	if (MapSqure[Tmp[i]][Tmp[i + 1]])
        		return true;
        return false;
      }
      
    • 定义上述方法后,其余操作即可高效的进行:每次输入指令,先直接命令Brick类变换(包括上下左右移动、旋转)。若移动过程中重叠则可终止操作。

    • Brick类加入地图后,定义方法 int delLine() 来实现消除满行的操作,修改地图信息并且返回消除的行数,以方便下一步操作。实现方法比较简单:考虑到可能一次性消除多行,先在第一次扫描时,消除满的行,并记录消除的行数;其次将下一行为空行的行下移

    • 为了实现此消彼长,定义方法 int addLine(int CountLine) 来实现操作:给该玩家的地图从底部开始,加入指定行数。由于设定中,每一行的个数为 (10) 个方块,每个方块在加行后都是有或者没有的状态,共记 (2^{10}=1024) 种状态;再扣除全空和全满两个非法状态,故本人使用随机数 rand()%1022+1 的方法来生成所加行的信息。其他的一些操作主要就是上移以及判断是否消行:

      int Player::addLine(int CountLine) {
        static int Bas = (1 << RIGHT_LIM - LEFT_LIM + 1) - 2;
        if (GameOver)
        	return 0;
        for (int i = UP_LIM; i <= DOWN_LIM - CountLine; i++)
        	for (int j = LEFT_LIM; j <= RIGHT_LIM; j++)
        		MapSqure[i][j] = MapSqure[i + CountLine][j];
        /*
        Move all of the map up.
        */
        for (int i = DOWN_LIM, j = 1; j <= CountLine; i--, j++) {
        	int State = rand() % Bas + 1;
        	for (int j = LEFT_LIM; j <= RIGHT_LIM; j++, State >>= 1)
        		MapSqure[i][j] = (State & 1);
        }
        /*
        Built the first CounLine lines randomly.
        */
        int CountDeleteLine = 0;
        if (isOverlap()) {
        	while (isOverlap())
        		NowBrick.Operation(Brick::Up);
        }
        if (touchBottom()) {
        	addToMap();
        	CountDeleteLine = delLine();
        	renewBrick();
        }
        /*
        Move the working brick up if it overlaps the map.
        And check out whether it is touches the bottom.
        */
        if (touchCeiling()) {
        	GameOver = 1;
        	return 0;
        }
        return CountDeleteLine;
      }
      
    • 其余的实现细节可以参考本人传到GitHub上的代码


    Game
    • 这是整个程序中最复杂的类,且是设定中唯一一个从键盘读入指令的类

    • 该类主要负责界面信息的处理、调用Player类进行游戏的执行、调用Render类进行界面的绘制、调用FileRecoder类进行本地信息的读入、调用PlaySound类进行声音的播放

    • 开 int 型的三个变量,GameMode,CountPlayer,OperationMode 分别表示游戏模式(共10个)、玩家数量(1-2个)、操作模式(是否允许连按)

    • 由于选择的渲染方式为利用 conio.h 中的输出方式进行绘制。故我们设定,对于允许连按的模式下,监听操作的9个键(上下左右、WSAD与ESC)。前八个累计到一定数额时视为一次有效操作。开变量 int FramesCount 来记录上次渲染到目前位置经过的帧数,并固定每一帧为 statit const int FramesTime=25ms 。每当记录的帧数达到 40 帧,即 1s ,此时让所有玩家的方块下落一次(加速模式除外)

    • 而对于不可连按操作模式下,我们记录9个键上一次的状态和现在的状态。当且仅当上一次状态为未按下,且当前状态为按下状态,执行指令。并且使用 clock() 函数在每一次监听后,判断是否达到一帧时长。累计达到 40 帧时长时,让所有玩家的方块下落一次(加速模式除外)

    • 开通方法 void renderMap() 作为Player类的友元函数,复制整个Player的地图,并交由Render类进行绘制

    • 为方便地实现一些界面中的光标移动操作,定义方法 void moveCur(int &NowCur,char c) 识别相关指令,并移动光标:

      void Game::moveCur(int& NowCur, char Command) {
        if (0);
        else if (Command >= '0' && Command <= '9')
        	NowCur = (Command - 48);
        else if (Command == '-' || Command == 'W' || Command == 'w')
        	NowCur--;
        else if (Command == '+' || Command == 'S' || Command == 's')
        	NowCur++;
        else if (Command == DIRECTIONS) {
        	Command = _getch();
        	if (Command == UP)
        		NowCur--;
        	else if (Command == DOWN)
        		NowCur++;
        }
      }
      
    • 其余的界面实现与其他细节同样可参见本人上传至 GitHub 的代码


    FileRecoder
    • 作为最后加入的两个类之一,该类只负责从文档读入信息,以及将信息输出至文档。故采用了文件流的方法进行快速的操作。

    • 定义文件名与自定义的文件后缀为 string 类,方便操作:

      static const std::string Suffix, FileName[11];
      
    • 从文档中读入的记录最高分、记录保持者、以及玩家姓名、相关设定储存如下:

      std::string NamePlayer[2], NameRecoder[10];
      int ScoreRecoder[10];
      int OperationMode;
      
    • 读入时,若发现文档不存在,设定其自己建立文件,并初始化内容:

      FileRecoder::FileRecoder() {
        std::fstream iofile;
        for (int i = 0; i < 10; i++) {
        	iofile.open((FileName[i] + Suffix).c_str(), std::ios::in);
        	if (!iofile.is_open()) {
        		iofile.open((FileName[i] + Suffix).c_str(), std::ios::out);
        		iofile << "Nobody" << std::endl << -1 << std::endl;
        		iofile.close();
        		iofile.open((FileName[i] + Suffix).c_str(), std::ios::in);
        	}
        	getline(iofile, NameRecoder[i]);
        	iofile >> ScoreRecoder[i];
        	iofile.close();
        }
      
        iofile.open((FileName[10] + Suffix).c_str(), std::ios::in);
        if (!iofile.is_open()) {
        	iofile.open((FileName[10] + Suffix).c_str(), std::ios::out);
        	iofile << "Player1" << std::endl << "Player2" << std::endl << 0 << std::endl;
        	iofile.close();
        	iofile.open((FileName[10] + Suffix).c_str(), std::ios::in);
        }
        getline(iofile, NamePlayer[0]);
        getline(iofile, NamePlayer[1]);
        iofile >> OperationMode;
        iofile.close();
      }
      
    • 同样处理输出:用文件流打开,并输出至改文件。同时,设置每次修改玩家名称、清除记录、退出游戏操作都会自动输出。具体实现可参考GitHub代码。


    Brick
    • 本人进行的优化主要是保证了方块更新的随机性,以及通过对称实现L型和Z型方块的对称方块的设置

    • 由于原本的设定中,包含了 17 种方块,且除了第 9 种田字型方块,所有方块都是4个连续的元素表示其旋转到不同角度的状态。该种随机数生成方式会导致田字型的生成概率为其余的 ({1over 4}) ,且无法生成两种对称的方块。
      故更改设定为:先随机生成一个7以内的数字,表明方块大类;再设定如果为第6、7种方块,即对称的方块,则识别为原方块的对称。同时,第二次生成一个4以内的随机数,表明出场时旋转的次数。最后根据这些情况,生成相应的数据。这样即可保证方块的等概率性。

    • 对称的实现原理在于:由中点坐标公式可得:({x_1+x_2over 2}=x_M) 反演出对称点坐标 (x_2=2x_M-x_1) 。该类的生成中本身即使用了中点的信息,故加入新方法进行操作:如果是对称的,则执行操作变换为对称点。
      同时,由于对称方块与原方块满足镜像对称的性质,故原方块的顺时针旋转会导致镜像方块的逆时针旋转。故设置如果为对称方块,旋转3次抵消镜像问题。


    Render
    • 本人和南理工大佬樱落三千共同进行了部分界面的设计、美化、开发与测试

    • 关于上述两种操作方案,唯一需要克服的问题便为渲染的时间问题。采用原渲染方式,会导致每次渲染时长为:单人 45ms;双人 120ms;远超过一帧的设定时间。
      故采用新型地图渲染方式:先输出整个地图边框。每次输出地图信息时,先设定为黑色背景,此后只需要将每一行的地图信息转化为输出信息,再设定起始位置后统一用高效的fwrite函数输出。成功将时间优化至 20ms 以内


    PlaySound
    • 此类不为本组三位人员开发。为南理工大佬樱落三千觉得游戏基本成型,欠缺音频,自愿加入开发。

    • 该类可实现音频、音效的播放、暂停


    收获和心得

    本人从高一学习编程至今,第一次开发这种比较大型的程序,确实与以前的算法竞赛的编程具有很大的不同点。

    这种开发情况下,对于整体架构的把握、细节的考虑、内容的封装、消息的传递具有很高的要求,且对于效率要求、时间的优化不比算法竞赛低。

    开发过程中,最重要的两件事就是抬头多学,低头多调。

    开发中的友元函数、友元类、文件读写、控制台设定、键盘监听、音乐播放、程序图标设置,我们都是第一次学习,并进入开发的。学习是非常重要的,且仅仅在课堂上的学习是远远不够的。善于利用现在强大的网络,将帮助我们更快的进步。

    而另一个重点就是多调。代码写出来是很难不出现 bug 的。因此,本组的我罗斯方块共计发布了5次内测版和5次公测版,不完全统计,经本组人员与组外人员发现 bug 约几十例。每一次的 debug 都是让我们的程序更加的严谨。

    另一个很深的感触在于面向对象编程对于功能修改的方便。在面向对象编程的情况下,如果真遇上我这样经常修改方案啊产品经理 (那好惨) ,每次修改也只需要修改相关的类即可。对于已经测试通过的类,甚至完全不需要改动。

    这一点尤其在后期,格外的突出。当我们完成第二版公测版时,已经解决了所有游戏运行的 bug 。后面的每次公测版都是增添功能或者修改界面,真就只需要修改到总控制的Game类、输出的Render类,以及其他部分相关的类即可。基本完成的Player类与Brick类后期基本没有再动过。开发效率十分高效。

    今后我会在学习之余,加强这种程序开发的能力。


    依然存在的问题

    1. 在允许长按模式下,可能存在吞键的情况。即按键不响应。
    2. 无法克服控制台被鼠标误触后暂停的情况。
    3. 部分界面的刷新可能存在闪屏问题
    4. 建立的快捷方式存在无法使用的问题,仍需要点集文件夹内的原程序启动。
    5. 仅允许更改音效,理论上不允许更改背景音乐。
    6. 界面仍可以美化。
    7. 存在部分操作系统的人无法打开的情况。
  • 相关阅读:
    背包问题_模板
    hihoCoder week14 无间道之并查集
    hihoCoder week13 最近公共祖先·一
    图片处理工具类 util
    算法导论 第二章 递归与分治
    hihoCoder week12 刷油漆
    hihoCoder week11 树中的最长路
    hihoCoder week10 后序遍历
    hihoCoder week8 状态压缩·一
    hihoCoder week7 完全背包
  • 原文地址:https://www.cnblogs.com/JustinRochester/p/13063291.html
Copyright © 2011-2022 走看看