我罗斯方块最终篇

作业描述     详情
作业属于     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的程序员”,而不是一个能够编写项目的、合格的程序员。

 

posted @ 2020-06-10 23:51  Es-war  阅读(310)  评论(7编辑  收藏  举报