• 我罗斯方块最终篇


    作业描述     详情
    作业属于     2020面向对象程序设计
    作业要求
    • 代码的 git 仓库链接
    • 运行截图/运行视频
    • 代码要点
    • 收获与心得
    • 依然存在的问题
    作业目标
    • 实现我罗斯的正常运行
    • 发表博客
    作业正文      https://www.cnblogs.com/Es-war/p/13090414.html
    小组成员

        

    代码仓库     https://github.com/Es-war/Tetris

    一、运行效果

                游戏过程

            游戏结束
    • 玩家一获胜

    • 玩家二获胜

    • 平局

             游戏运行视频     

    视频链接

    二、代码要点

           界面渲染
    • 采用Easy_x进行界面渲染,将渲染功能打包在pait类中
    class paint
    {
    public:
        void endGame();                          //绘制结束界面
        void initEnviroment();                   //初始化环境
        void drawGameBG();                       //绘制游戏背景
        void drawLeftSide();                     //绘制游戏左侧边
        void drawRightSide();                    //绘制游戏右侧边
        void drawLeftMap();                      //绘制玩家一的地图
        void drawRightMap();                     //绘制玩家二的地图
        void drawSquareNext(int num);            //绘制预览框内的方块
        void drawItem(int x, int y, COLORREF c); //绘制方块
        void drawSquareNow(int num);             //绘制当前“尘埃落定”的全体方块
    };
            初始化
    • 游戏初始环境的初始化
    void paint::initEnviroment()
    {
        // 窗口设置
        initgraph(1210, 540);
        HWND hwnd = GetHWnd();
        SetWindowText(hwnd, L"我罗斯");
        SetWindowPos(hwnd, HWND_TOP, 0, 20, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW);
    
        // 绘图模式设置
        setbkmode(TRANSPARENT);
    
        // 随机数种子
        srand(time(NULL));
    }
    • 每一轮游戏的初始化:分数、储存方块信息的“地图”,结束标志重置
    • 每一个新方块的初始化:获取方块所需信息——颜色、类型、形状,生成下一方块标志的重置,预览框方块信息的设置

              提前将各种方块的信息储存在数组中,通过随机数取模的方式来随机生成方块

        now_c_idx[num] = next_c_idx[num];
        now_s_idx[num] = next_s_idx[num];
        now_d_idx[num] = next_d_idx[num];
        next_c_idx[num] = rand() % 7;
        next_s_idx[num] = rand() % 7;
        next_d_idx[num] = rand() % 4;
          各类操作
    • 游戏过程中的方块左移、右移、旋转、下降均需要事先判断操作是否可行,若不可行,则不执行
    • 长时间未执行操作,方块将自动下降利用GetTickCount()函数来获取时间)
    bool player::checkPut(int mp_x, int mp_y, int dir_idx)
    {
        int sq_x[4];
        int sq_y[4];
        for (int i = 0; i < 4; ++i)
        {
            sq_x[i] = mp_x + squares[now_s_idx[num]].dir[dir_idx][i][0];
            sq_y[i] = mp_y + squares[now_s_idx[num]].dir[dir_idx][i][1];
        }
    
        // 【左右越界、下方越界、重复占格】
        for (int i = 0; i < 4; ++i)
        {
            if (sq_x[i] < 0 || sq_x[i] > 9 || sq_y[i] > 14)
                return false;
            if (sq_y[i] < 0) // 检查坐标合法性
                continue;
            if (mp[num][sq_x[i]][sq_y[i]])
                return false;
        }
        return true;
    }
            玩家类
    • 玩家类的各种操作大同小异,但是在下降、消行、给对方增加行、检查是否结束差异较大,所以将这部分剥离出来,构造玩家一、二类公有继承于玩家类,分别编写上述成员函数,便于进行相关操作
            默认下降速度的改变
    • 随着玩家分数的增加,游戏难度需相应提高,设置了默认下降速度改变的机制,但是最终会稳定在某个最大速度值
        //检查玩家一是否长时间未执行正确指令
        int speed1, speed2;
        if (score[0] <= 3000) 
          speed1 = 1000 - score[0] / 10;
        else
          speed1 = 700;
        if (time_tmp[0] - time_now[0] >= speed1)
        {
          time_now[0] = time_tmp[0];
          one.moveDown();
          over = true;
        }
        //检查玩家二是否长时间未执行正确指令
        if (score[1] <= 3000)
          speed2 = 1000 - score[1] / 10;
        else
          speed2 = 700;
        if (time_tmp[1] - time_now[1] >= speed2)
        {
          time_now[1] = time_tmp[1];
          two.moveDown();
          over = true;
        }
            玩家分数的计算
    • 玩家在游戏过程中消除的行数不同所得分数也不相同,并且多行消除后的得分不是简单的一行消除得分的多次叠加
    score[num] += 100.0 * clearNum * (1 + clearNum * 0.25); 
            键盘敲击事件监听
    • 利用_kbhit()来监听键盘敲击,并且在每次执行完操作后 Sleep(20)来降低CPU占用
            //接受指令
            if (_kbhit())
            {
                //兼顾大小写
                switch (_getch())
                {
                case 'W':
                case 'w':
                    one.moveRotate();
                    break;
                case 'A':
                case 'a':
                    one.moveLeft();
                    break;
                case 'D':
                case 'd':
                    one.moveRight();
                    break;
                case 'S':
                case 's':
                    one.moveDown();
                    break;
                case 72:
                    two.moveRotate();
                    break;
                case 75:
                    two.moveLeft();
                    break;
                case 77:
                    two.moveRight();
                    break;
                case 80:
                    two.moveDown();
                    break;
                }
            }
    
            // 降低CPU占用
            Sleep(20);
               游戏结束后的弹窗
    • 每局游戏结束后在屏幕上显示本局游戏的赢家(或者出现平局),并显示询问是否“再来亿局”的弹窗
            starpaint.endGame();
            if (MessageBox(GetHWnd(), over_tips, L"再来亿局?", MB_ICONQUESTION | MB_YESNO) == IDNO)
                break;
               最终效果
    • 可以保证游戏的正常运行,实现方块左移、右移、旋转、下降、长时间不操作自动下降、消行操作,并且补充消行后给对方增加相应行数的随机方块功能。
    • 侧边方块预览功能正常实现。
    • 随着等级的提升方块下降速度加快。
    • 每局游戏结束后在屏幕上显示本局游戏的赢家(或者出现平局),实现了“再来亿局”的功能正常使用。

    三、依然存在的问题

    • 游戏流畅度的进一步优化
    • 使用了较多全局变量,未能很好地实现代码的封装性,所以在最后未能分离出类代码文件

    四、收获与心得

    学习了之前没有接触过的Easy_x的操作,了解到如何利用它来进行页面的渲染。明白了要想真正写出这样一个小游戏并不容易,在一开始,就应该利用流程图、思维导图等工具来对思路进行处理。在实现过程中会碰到各种各样意料之外的问题,不要急于实现全部功能,应该是在已有的、正确的代码基础上进行功能的完善和补充。适时进行代码功能的测试,以便及时修改,否则bug堆积多了,修改时也无从下手。在这个过程中,与组员的分工合作也十分重要,及时与组员保持联系,确认编码上的一些修改等问题。在这次制作“我罗斯”的过程中,慢慢体会到做项目与平常写PTA上的作业的深刻不同,不要只满足于完成PTA这类作业上,而不学习、实践项目的编写,否则将来只会成为一名“面向PTA的程序员”,而不是一个能够编写项目的、合格的程序员。

  • 相关阅读:
    java发送邮件
    MySQL查询表结构的SQL语句
    Jquery的toggle()方法
    jQuery为图片添加链接(创建新的元素来包裹选中的元素)
    mysql修改存储过程的权限
    php中接收参数,不论是来自GET还是POST方法
    解决php中文乱码
    MySQL的视图view,视图创建后,会随着表的改变而自动改变数据
    选项卡面向对象练习
    对数组的操作splice() 和slice() 用法和区别
  • 原文地址:https://www.cnblogs.com/Es-war/p/13090414.html
Copyright © 2020-2023  润新知