请选择 进入手机版 | 继续访问电脑版

【C++大作业】实现俄罗斯方块(附代码+实现思路带详细注释)

[复制链接]
冰宇 发表于 2020-12-31 19:00:06 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
由于最近C++大作业需要,所以来记录下自己写大作业的记录(自留)
效果图

有目的才有动力
先来看下最终的效果图吧

代码加上注释总计不敷三百行,简化版本200行左右
情况设置SFML

首先由于我们想要最终的效果不但仅是一个简朴的终端用文字输出的界面,希望有一个图形化的界面,所以我们决定接纳一个图像-----SFML
将这个库搭载到VS上的过程和我们之前说过的讲OPENCV搭载到VS上的操纵过程是类似的,我们在这里就简朴的提一下
首先去官网下载这个库
百度里直接搜索SFML就可以了

下载到电脑中,而且解压
然后就可以打开VS而且新建一个c++项目文件了
而且在管理方案资源管理器举行右键->属性->VC++目次->包含文件这里加上刚刚下载压缩包的include文件的目次
在库目次内里加上lib文件


这是我电脑中的位置,每个人下载解压的位置不一样这个路径也是不一样的
然后我们在链接器->输入->附加依赖项中添加
  1. sfml-graphics-d.libsfml-audio-d.libsfml-window-d.libsfml-system-d.lib
复制代码
到这里有些同学的电脑上大概已经可以实现调用库了
但是在我的电脑上照旧不可
于是颠末查找资料之后我发现管理的方法是解说压文件内里的lib文件拷贝到对应的debug文件内即可。

在完成情况的设置之后,我们就可以愉快的开始写代码了。
代码书写

首先举行最常见最常见的操纵------导入库和床照定名空间
  1. #include//处置惩罚图像的头文件#include//处置惩罚声音文件#include//处置惩罚时间文件using namespace sf;
复制代码
要做一个小游戏,首先得有游戏界面临不,所以我们先把游戏界面显示出来
  1. int main(void) {        srand(time(0));//用当前时间生成随机种子        RenderWindow window(VideoMode(320, 416),"HEY");//创建窗口        Texture bg;//加载配景图片        bg.loadFromFile("C:/Users/Hey/Desktop/俄罗斯方块/ConsoleApplication1/image/bg.jpg");        Sprite spriteBg(bg);//根据图片创造对象        window.draw(spriteBg);        system("pause");        return 0;}
复制代码
这样就可以展现出一个简朴的配景图像了
有配景之后我们要一步步丰富我们的代码,就是要开始编写游戏循环了
  1. //进入游戏循环        //进入游戏        while (window.isOpen()) {//如果窗口没有被关闭                window.draw(spriteBg);                window.draw(spriteFrame);                //绘制方块,绘制游戏                drawBlocks(&window,&spriteBlock);                //渲染方块                window.draw(textScore);//显示分数                window.display();//        }        system("pause");        return 0;}
复制代码
  1. //启动到现在的时间                float time = clock.getElapsedTime().asSeconds();                clock.restart();                timer += time;                                //期待用户按键                keyEvent(&window);//左右移动和旋转                if (timer > delay)                {                        //降落                        drop();//下降一个位置                        timer = 0;                }                for (int i = 0; i < 10; i++) {                        if (table[0][i]||table[1][0]|| table[1][1] || table[2][0]||table[2][1]||table[3][0]||table[3][1] ) {                                printf("游戏竣事,最终得分为%d\n", score);                                system("pause");                                return -1;                        }                }                //消分处置惩罚                clearLine();                delay = SPEED_NORMAL;//速度还原
复制代码
在游戏界面中展示方块,这时候就出现了问题,俄罗斯方块中一共有7中形状的方块,我们应该如何让公道的用盘算机语言把他体现出来呢?
一波分析之后我们决定用矩阵来将其表述
实际用图形来体现如下图所示

在盘算机代码语言内里
  1. int blocks[7][4] = {//界说七种方块的数组        {1,3,5,7},//I        {2,4,5,7},//Z        {3,5,4,6},//Z        {3,5,4,7},//T        {2,3,5,7},//L        {3,5,7,6},//J        {2,3,4,5}//田};
复制代码
与此同时我们还界说了一个全局变量
  1. int blockIndex;//当前方块的种类
复制代码
来保存当前代码块的种类
就是对应数组的简朴体现方法,现在我们让各种模式的方块都影象到数组内里了,但是离我们可以或许将他图形化的体现出来另有一定的间隔。
于是我们开始了我们第一个函数编写,就是将我们的代码块体现到图像上
我们把我们整个图形界面划出一块游戏区域,游戏区域用数组表述
  1. int table[ROW_COUNT][COL_COUNT] = { 0 };//游戏区域的体现                                  //若table[i][j]=0则下标为ij的空缺,并用值体现颜色取值
复制代码
若为0,则体现该块没有俄罗斯方块,若不为0,则该位置的数字表述其颜色和方块选项。
然后我们让其产生方块newBlock()
  1. struct Point {//俄罗斯方块的体现(都是4小块的)位置不断发生厘革        int x;        int y;} curBlock[4];void newBlock() {//生成方块        //随机生成        blockIndex = 1 + rand() % 7;        int n = blockIndex - 1;        for (int i = 0; i < 4; i++) {                curBlock[i].x = blocks[n][i] % 2;                curBlock[i].y = blocks[n][i] / 2;        }}
复制代码
newblock之后我们要将其画到界面上,就有了drawblock(),
  1. void drawBlocks(RenderWindow* window, Sprite* spriteBlock) {        // 绘制已降落完毕的方块        for (int i = 0; i < ROW_COUNT; i++)                for (int j = 0; j < COL_COUNT; j++)                {                        if (table[i][j] == 0) continue;                        spriteBlock->setTextureRect(IntRect(table[i][j] * 18, 0, 18, 18));                        spriteBlock->setPosition(j * 18, i * 18);                        spriteBlock->move(28, 31); //调解边框位置                        window->draw(*spriteBlock);                }        // 绘制当前方块        for (int i = 0; i < 4; i++)        {                spriteBlock->setTextureRect(IntRect(blockIndex * 18, 0, 18, 18));                spriteBlock->setPosition(curBlock[i].x * 18, curBlock[i].y * 18);//用其切割一个小方块                spriteBlock->move(28, 31); //调解边框位置                window->draw(*spriteBlock);        }}
复制代码
这个函数主要分两大块来绘制,一个是已经降落了的小方块,一个是还在降落过程中的小方块,这边先把代码都写出来了
然后需要说明的是,由于我原有配景图游戏区域并不明显,于是我又加了一个边框,所以这里有矫正到边框位置的代码,然后我绘制俄罗斯方块的小方框是从其他图片上裁剪下来的(图片素材放到了github上,所以这里也有相像素裁剪的部门。
实在两个循环的去边就在于,一个是在变动往下落的,而一个是已经固定下来的,而往下落的就需要判断现在的格子是否为0,若为0,则回退一步,所以这也就是为什么最终代码内里我有备份的操纵。
绘制小方块的步调写完了,既然小方块画出来了,我们就要让他动起来呀
于是就有了drop()函数
  1. void drop() {        //y坐标加一就可以        for (int i = 0; i < 4; i++) {                bakBlock[i] = curBlock[i];                curBlock[i].y += 1;        }        //不能穿过地上        if (check() == false) {                //固定处置惩罚                for (int i = 0; i < 4; i++) {                        table[bakBlock[i].y][bakBlock[i].x] = blockIndex;                }                //产生新方块                newBlock();        }}
复制代码
就是每次把现在的小方块举行备份然后把它的y坐标加一就可了
然后这里要加一个判断小方块有没有穿过边框,如果有就把他的备份(也就是上一步固定下来)
这里也就产生了一个check()函数
  1. bool check() {//查抄是不是编写        for (int i = 0; i < 4; i++) {                if (curBlock[i].x < 0 || curBlock[i].x >= COL_COUNT || curBlock[i].y >= ROW_COUNT || curBlock[i].y pollEvent(e)) {                if (e.type == Event::Closed)//事件是关闭窗口                {                        window->close();                }                if (e.type == Event::KeyPressed)                {                        switch (e.key.code) {                        case Keyboard::Up:                                rotate = true;                                break;                        case Keyboard::Left:                                dx = -1;                                break;                        case Keyboard::Right:                                dx = 1;                                break;                        default:                                break;                        }                }                        if (dx != 0) {                        moveLeftRight(dx);                }                //旋转操纵                if (rotate) {                        doRotate();                }        }}
复制代码
差别的按钮使用体现不一样的意思。至于旋转和移动,我们单独写两个函数来实现
旋转
  1. void doRotate() {        if (blockIndex == 7) {  // 田字形,不需要旋转                return;        }        //备份当前方块,出问题再回退        for (int i = 0; i < 4; i++) {                bakBlock[i] = curBlock[i];          }        Point p = curBlock[1]; //旋转中心        //a[i].x=p.x-a[i].y+p.x        //a[i].y=p.y+a[i].x-p.x;        for (int i = 0; i < 4; i++)        {                struct Point tmp = curBlock[i];                curBlock[i].x = p.x - tmp.y + p.y;                curBlock[i].y = p.y + tmp.x - p.x;        }        if (!check()) {                for (int i = 0; i < 4; i++) {                        curBlock[i] = bakBlock[i];                }        }}
复制代码
左右移动
  1. void moveLeftRight(int dx) {        for (int i = 0; i < 4; i++) {                bakBlock[i] = curBlock[i];//备份                curBlock[i].x += dx;        }        if (!check()) {                for (int i = 0; i < 4; i++) {                        curBlock[i] = bakBlock[i];//备份                }        }}
复制代码
我注意到我们寻常我玩的俄罗斯方块的游戏内里长按加速键会有加速下降的功能于是我界说了全局变量,并在事件控制的代码里加上了相应的代码。
  1. const float SPEED_NORMAL = 0.3;const float SPEED_QUICK = 0.05;float delay = SPEED_NORMAL;        //一直下降                if (Keyboard::isKeyPressed(Keyboard::Down)) {                        delay = SPEED_QUICK;//快速                }
复制代码
这里有一个小的注意点,如果只在事件控制内里加上相应的代码而不在其他位置做相应的改变就会使方块一直加速下降而不会回到原来速度,所以要在main函数内里加一句
  1.                 delay = SPEED_NORMAL;//速度还原
复制代码
然后我们要实现消除行这一个操纵
于是我们又写了一个函数
  1. void clearLine() {        int k = ROW_COUNT - 1;//重新写方块        for (int i = ROW_COUNT - 1; i > 0; i--) {                int count = 0;                for (int j = 0; j < COL_COUNT; j++) {                        if (table[i][j]) {                                count++;                        }                        table[k][j] = table[i][j];//一遍统计一边写一编                }                if (count < COL_COUNT) {                        k--;//拿去覆盖                }                else {                        score += 10;                }        }}
复制代码
我们接纳的方法使从下往上一行行写,在写的时候顺便计数,如果有满行,就k–,重新计数。用覆盖的方法实现了消行。
到这里实在俄罗斯方块的主要部门已经写好了,接下来就是一些优化和完善部门了
玩游戏的时候音乐是灵魂
所以我给他加上了配景音乐
  1.         //游戏配景音        Music music;        if (!music.openFromFile("C:/Users/Hey/Desktop/俄罗斯方块/ConsoleApplication1/bg2.wav"))        {                return -1;        }        music.setLoop(true);//循环播放        music.play();        SoundBuffer xiaochu;        if(!xiaochu.loadFromFile("C:/Users/Hey/Desktop/俄罗斯方块/ConsoleApplication1/bg.wav")){                return -1;        }
复制代码
并在对应需要消行音效的地方家里对应语句
然后玩游戏嘛,有竞技才有意思,于是我又参加了计分系统
就是参加一个函数,将效果显示出来
  1. void initScore() {        if (!font.loadFromFile("C:/Users/Hey/Desktop/俄罗斯方块/ConsoleApplication1/Sansation.ttf")) {                exit(1);        }        textScore.setFont(font); // font is a sf::Font        textScore.setCharacterSize(30);// set the character size        textScore.setFillColor(sf::Color::Black); // set the color        textScore.setStyle(sf::Text::Bold); // set the text style        textScore.setPosition(255, 175);        textScore.setString("0");}
复制代码
好了
到这里这个俄罗斯方块就都完成了
完整代码的链接请点击这里
代码一共包含10个函数,共计200行+


来源:https://blog.csdn.net/m0_46480441/article/details/111976092
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

发布主题

专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )