俄罗斯方块游戏设计与实现(Python)

Posted biyezuopinvip

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了俄罗斯方块游戏设计与实现(Python)相关的知识,希望对你有一定的参考价值。

目录
第一章 绪论 1
1.1 任务概述 1
1.1.1 软件功能 1
1.1.2 运行环境 1
1.2 需求分析 3
1.2.1 游戏界面 3
1.2.2 菜单操作 3
1.2.3 游戏操作 3
1.3 设计目的 4
第二章 相关技术及开发工具 5
2.1 python介绍 5
2.2 python发展历史 6
2.3 python特点 6
2.4 python开发环境构建 7
第三章 概要设计 8
3.1 程序流程 9
3.1.1 程序主流程 9
3.1.2 游戏视图 10
3.1.3 游戏控制流程 10
3.2 模块说明 11
3.2.1 游戏模块 11
3.2.2 辅助模块 12
3.3 重要数据 12
3.3.1 存储型 12
3.3.2 控制型 12
第四章 详细设计 13
4.1 界面设计 13
4.1.1 窗口创建 13
4.1.2 菜单设计 13
4.1.3 区域着色 13
4.1.4 方块设计 13
4.2 常量变量 13
4.2.1 常量. 13
4.2.2 变量. 14
4.3 重要函数 14
4.3.1 游戏状态 14
4.3.2 游戏控制 14
4.4 重要算法 14
4.4.1 判定 14
4.4.2 变换 14
第五章 调试与测试 15
5.1 调试分析 15
5.1.1 问题与解决 15
5.1.2 性能分析 15
5.1.3 程序不足 15
5.2 测试结果 16
5.2.1 游戏截图 16
第六章 结论 19
参考文献 20
第一章绪论
1.1任务概述
1.1.1软件功能
该俄罗斯方块是有Python编写而成的。它具有对游戏的正常操作,可以控制方块
下落位置、下落时改变方向,以及对方块的直接下落。该游戏分左右两个界面,左边显示游戏的运行状态,右边显示游戏下一个即将出现的方块,以及游戏的等级类别和当前分数、消过得方块行数等。
1.1.2运行环境
程序运行于Anoconda,将Anoconda安装于Windows系统上。然后在Anoconda Prompt 上安装pygame组件。
Anaconda是一个用于科学计算的Python发行版,支持 Linux, Mac, Windows系统,提供了包管理与环境管理的功能,可以很方便地解决多版本python并存、切换以及各种第三方包安装问题。Anaconda利用工具/命令conda来进行package和environment的管理,并且已经包含了Python和相关的配套工具。
这里先解释下conda、anaconda这些概念的差别。conda可以理解为一个工具,也是一个可执行命令,其核心功能是包管理与环境管理。包管理与pip的使用类似,环境管理则允许用户方便地安装不同版本的python并可以快速切换。Anaconda则是一个打包的集合,里面预装好了conda、某个版本的python、众多packages、科学计算工具等等,所以也称为Python的一种发行版。其实还有Miniconda,顾名思义,它只包含最基本的内容——python与conda,以及相关的必须依赖项,对于空间要求严格的用户,Miniconda是一种选择。
进入下文之前,说明一下conda的设计理念——conda将几乎所有的工具、第三方包都当做package对待,甚至包括python和conda自身!因此,conda打破了包管理与环境管理的约束,能非常方便地安装各种版本python、各种package并方便地切换。
Anaconda的下载页参见官网下载,Linux、Mac、Windows均支持。
安装时,会发现有两个不同版本的Anaconda,分别对应Python 2.7和Python 3.5,两个版本其实除了这点区别外其他都一样。后面我们会看到,安装哪个版本并不本质,因为通过环境管理,我们可以很方便地切换运行时的Python版本。(由于我常用的Python是2.7和3.4,因此倾向于直接安装Python 2.7对应的Anaconda)
下载后直接按照说明安装即可。这里想提醒一点:尽量按照Anaconda默认的行为安装——不使用root权限,仅为个人安装,安装目录设置在个人主目录下(Windows就无所谓了)。这样的好处是,同一台机器上的不同用户完全可以安装、配置自己的Anaconda,不会互相影响。
对于Mac、Linux系统,Anaconda安装好后,实际上就是在主目录下多了个文件夹(/anaconda)而已,Windows会写入注册表。安装时,安装程序会把bin目录加入PATH(Linux/Mac写入/.bashrc,Windows添加到系统变量PATH),这些操作也完全可以自己完成。以Linux/Mac为例,安装完成后设置PATH的操作是

将anaconda的bin目录加入PATH,根据版本不同,也可能是~/anaconda3/bin

echo ‘export PATH=“~/anaconda2/bin:$PATH”’ >> ~/.bashrc

更新bashrc以立即生效

source ~/.bashrc
配置好PATH后,可以通过which conda或conda --version命令检查是否正确。假如安装的是Python 2.7对应的版本,运行python --version或python -V可以得到Python 2.7.12 :: Anaconda 4.1.1 (64-bit),也说明该发行版默认的环境是Python 2.7。
Pygame是跨平台Python模块,专为电子游戏设计,包含图像、声音。建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚。包含图像、声音。
Pygame建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚。基于这样一个设想,本文转载自http://www.biyezuopin.vip/onews.asp?id=13886所有需要的游戏功能和理念都(主要是图像方面)都完全简化为游戏逻辑本身,所有的资源结构都可以由高级语言提供,如Python。

1.2需求分析
1.2.1 游戏界面

图1.1
1.2.2 菜单操作
打开Anoconda Prompt ,安装pygame组件。安装完成后就会显示 如图5.1所示的界面。点击Play!,将进入游戏,点击Quit将退出程序。
1.2.3 游戏操作
进入游戏后,可以用键盘上下左右键控制方块的下落方向,速度。当选好下落的位置后,按下空格键将快速下降到需要填充的地方。当游戏中方块积累的和游戏最上面相平时,此局游戏将结束。
1.3设计目的
这款游戏设计的主要目的是为了应用自己已学过的编程语言更好的去应运到实际中,同时对小游戏的开发有助于提升自己的编程能力,还能更好的找出自身存在的一些缺陷问题,加以及时的弥补。这款游戏也能供玩家适当缓解压力,提高思维能力和反应能力。

import os

scorefile = os.path.join(os.path.dirname(__file__), ".highscores")

def load_score():
    """ Returns the highest score, or 0 if no one has scored yet """
    try:
        with open(scorefile) as file:
            scores = sorted([int(score.strip())
                             for score in file.readlines()
                             if score.strip().isdigit()], reverse=True)
    except IOError:
        scores = []

    return scores[0] if scores else 0

def write_score(score):
    assert str(score).isdigit()
    with open(scorefile, 'a') as file:
        file.write("\\n".format(score))







C#之四十八 俄罗斯方块设计

1        系统设计要求

1.1   需求分析

本系统为一个用C#实现的为我们所熟悉的简单的俄罗斯方块游戏,该系统的具体功能如下:

1).       能简便的开始游戏,游戏中的方块的功能与日常我们所熟悉的游戏的功能一致,各种块的设置也一致,包括块的旋转,加速下降,平移,满行消去,到顶游戏结束功能;

2).       能够自定义游戏中功能键的具体按键,显示下一方块提示信息,以及游戏数据的统计;

3).       考虑需要解决的问题:怎么样设置图形显示;怎样获取鍵盘输入;怎样控制方块的移动;怎样控制时间间隔(用于游戏中控制形状的下落);游戏中的各种形状及整个游戏空间怎么用数据表示;游戏中怎么判断左右及向下移动的可能性;游戏中怎么判断某一形状旋转的可能性;按向下方向键时加速某一形状下落速度的处理;怎么判断某一形状已经到底;怎么判断某一已经被填满;怎么消去已经被填满的一行怎么消去某一形状落到底后能够消去的所有的行;(如长条最多可以消去四行)怎样判断游戏结束,关于“下一个”形状取法的问题。

2        设计思路

2.1   用面向对象的方法分析系统

从游戏的基本玩法出发,主要就是俄罗斯方块的形状和旋转,在设计中在一个图片框中构造了一个20*20(像素)的小块,由这些小块组合成新的形状,每四个小块连接在一起就可以构造出一种造型,总共设计了7中造型,每种造型又可以通过旋转而变化出2到4种形状,在游戏窗体中用户就可以使用键盘的方向键来控制方块的运动,然后对每一行进行判断,如果有某行的方块是满的,则消除这行的方块,并且使上面的方块自由下落,其中,方块向下的速度是有时钟控件控制的。俄罗斯方块游戏设计主要包括以下10个方面:

1).         游戏界面的设计。

2).         俄罗斯方块的实现。

3).         键盘输入信息的获取。

4).         俄罗斯方块的移动(向左,向右和向下)。

5).         俄罗斯方块的变换。

6).         方块自动下落与速度的选择。

7).         慢行的判断与消行。

8).         游戏结束判断。

9).         用户配置保存。

 

在主窗口中,通过调用俄罗斯方块类来实现程序的表示层,在该窗口中通过两个Panel控件来实现方块叠放窗口和下一方块信息窗口;调用设置窗口,保存设计窗口类传回的信息,并设置到游戏中去,保存在配置文件中;

在设置窗口中,以良好的界面提供用户自定义快捷键的接口,保存相应设置参数,以提供给调用窗口。

2.2运用的控件和主要对象

在设计过程中主要用到的控件有:PictureBox控件,MenuStrip控件,Button控件,Label控件,Timer控件,winmm组件,DirectSound等等。

3        系统功能实现

3.1   屏幕信息初始化

用来显示状态信息的框

privateSystem.Windows.Forms.GroupBox statusBox;

开始按钮

privateSystem.Windows.Forms.Button btnStart;

显示“下一块”的标签

privateSystem.Windows.Forms.Label label3;

显示“分数”的标签

privateSystem.Windows.Forms.Label label2;

显示“等级”的标签

privateSystem.Windows.Forms.Label label1;

用来画下一块方块的区域

privateSystem.Windows.Forms.PictureBox panel1;

游戏区域

privateSystem.Windows.Forms.PictureBox gameArea;

实现如下主界面效果图(图3-1):




1.1   方块的实现

在程序中每一个方块都是一个Block类的实例。Block包括的参数有方块的宽度,高度,最左端横坐标,最上端纵坐标,方块的数组表示。其中一共有7中形状的方块,以数组表示为:

     11     11    1       11     010    10     01

01     10    1       11     111     11    11

01     10    1                      01     10

                   1

方块的7种形状分别以数字0-6来代表,在构造函数中,随机生成0-6中数字,以此来随机生成方块的形状。用来在界面上显示方块的贴图也以0-6的数字来代表,同样以随机数的形式来随机的现实方块的颜色。

1.2   键盘输入事件处理

因为在界面上有一个按钮,并且只有一个按钮,所以该按钮在通常情况下都是默认为焦点。在这种情况下按下某些键,比如空格,就会产生出发按钮事件的情况。因此必须重载整个WinForm的ProcessCmdKey来避免这样的问题。

当按向左,向右及旋转按钮时,只要相应的处理方块的位置或者形状即可,但是当按向下或者立即下落时,需要不同的处理。向下移动时,如果移动到最底部但还未固定,则需要重新设置计时器间隔时间,从而使自动下落时,底部未固定的方块到固定的时间相同。如果方块在最底部而未固定的时候,向下移动,则立即固定。这两种情况,当方块固定后,都需要判断是否消行,立即下落时,需要判断是否消行。

1.3   方块的移动

游戏中方块的移动分为向左移动,向右移动,向下移动和立即落下。

        向左移动:

        public void MoveLeft() {

            int xPos =runBlock.XPos-1;

            intyPos = runBlock.YPos;

            for(int i = 0; i < runBlock.Length; i++)

            {

                if(xPos + runBlock[i].X <0)//如果超出左边界则失败

                {

                    return;

                }

                if(!coorArr[xPos + runBlock[i].X, yPos - runBlock[i].Y].IsEmpty)//如果左边有东西挡则失败

                {

                    return;

                }

            }

            runBlock.erase(gpPaltte);//擦除原来位置的转块

            runBlock.XPos--;

            runBlock.Paint(gpPaltte);//在新位置上画转块

}

向右移动:

public void MoveRight()

        {

            intxPos = runBlock.XPos + 1;

            intyPos = runBlock.YPos;

            for(int i = 0; i < runBlock.Length; i++)

            {

                if(xPos + runBlock[i].X >_width-1)//如果超出右边界则失败

                {

                    return;

                }

                if(!coorArr[xPos + runBlock[i].X, yPos - runBlock[i].Y].IsEmpty)//如果右边有东西挡则失败

                {

                    return;

                }

            }

            runBlock.erase(gpPaltte);//擦除原来位置的转块

            runBlock.XPos++;

            runBlock.Paint(gpPaltte);//在新位置上画转块

        }

向下移动:

public void Drop() {

            timerBlock.Stop();

            while(Down()) ;

            timerBlock.Start();

        }

1.4   方块的变换

        public voidDeasilRotate() //顺时针旋转

        {

            for (int i = 0; i< runBlock.Length;i++ )

            {

                int x = runBlock.XPos + runBlock[i].Y;

                int y = runBlock.YPos + runBlock[i].X;

                if (x < 0 || x > _width - 1)//如果超出左右边界,则失败

                   return;

                if (y < 0 || y > _height - 1)//如果超出上下边界,则失败

                   return;

                if (!coorArr[x, y].IsEmpty)//如果旋转后的位置上有转块,则失败

                    return;

            }

           runBlock.erase(gpPaltte);//擦除原来位置的转块

           runBlock.DeasilRotate();

           runBlock.Paint(gpPaltte);//在新位置上画转块

        }

        public voidContraRotate() //逆时针旋转

        {

            for (int i = 0; i< runBlock.Length; i++)

            {

                int x = runBlock.XPos - runBlock[i].Y;

                int y = runBlock.YPos - runBlock[i].X;

               if (x < 0 || x > _width - 1)//如果超出左右边界,则失败

                   return;

                if (y < 0|| y > _height - 1)//如果超出上下边界,则失败

                   return;

                if (!coorArr[x, y].IsEmpty)//如果旋转后的位置上有转块,则失败

                   return;

            }

           runBlock.erase(gpPaltte);//擦除原来位置的转块

           runBlock.ContraRotate();

           runBlock.Paint(gpPaltte);//在新位置上画转块

        }

 

1.5   判断方块是否到底

public voidCheckAndOverBlock()//检查转块是否到底,如果到底则把当前转块归入coorArr,并产生新的转块

        {

            boolover = false;//设置一个当前运行转块是否到底的标志

            for(int i = 0; i < runBlock.Length;i++ )//遍历当前运行转块的所有小方块

            {

                intx = runBlock.XPos + runBlock[i].X;

                inty = runBlock.YPos - runBlock[i].Y;

                if(y == _height - 1)//如果到达下边界,则结束当前转块

                {

                   over = true;

                    break;

                }

                if(!coorArr[x, y+1].IsEmpty) //如果下面有转块,则当前转块结束

                {

                   over = true;

                   break;

                }

            }

            if(over)

            {

                for(int i = 0; i < runBlock.Length; i++)//把当前转块归入coordinateArr

               {

                    coorArr[runBlock.XPos +runBlock[i].X, runBlock.YPos - runBlock[i].Y] = runBlock.BlockColor;

               }

               //检查是否有满行情况,如果有则删除

               CheckAndDelFullRow();

               //产生新转块

               runBlock = readyBlock;//新的转块为准备好的转块

               runBlock.XPos = _width / 2;//确定当前运行转块的出生位置

               int y = 0;//确定转块Ypos,确定刚出生的转块顶上没空行

               for (int i = 0; i < runBlock.Length; i++)

               {

                    if (runBlock[i].Y > y)

                        y = runBlock[i].Y;

               }

               runBlock.YPos = y;

               //检查新生成的转块所占用的地方是否已经有转块存在,如果有,游戏结束

               for (int i = 0; i <runBlock.Length; i++)

               {

                    if (!coorArr[runBlock.XPos+ runBlock[i].X, runBlock.YPos - runBlock[i].Y].IsEmpty)

                    {

                        StringFormat drawFormat= new StringFormat();

                        drawFormat.Alignment =StringAlignment.Center;

                       gpPaltte.DrawString("GAME OVER",

                                            newFont("Arial Black", 25f),

                                            newSolidBrush(Color.White),

                                            newRectangleF(0, _height * rectPix / 2 - 100, _width * rectPix, 100),

                                           drawFormat);

                        timerBlock.Stop();//关闭定时器

                       return;

                    }

               }

               runBlock.Paint(gpPaltte);

               //获取新的准备转块

               readyBlock = bGroup.GetABlock();

               readyBlock.XPos = 2;

               readyBlock.YPos = 2;

                gpReady.Clear(Color.Black);

               readyBlock.Paint(gpReady);

           }

        }

1.6   满行判断并消行

        privatevoid CheckAndDelFullRow() //检查并删除满行

        {

           //找出当前转块所在行的范围

           int lowRow = runBlock.YPos - runBlock[0].Y;//lowRow代表当前转块的y轴的最小值

           int highRow = lowRow;//highRow代表当前转块y轴的最大值

           for (int i = 0; i < runBlock.Length; i++)//找出当前转块所占行的范围,放入low和high变量内

           {

               int y = runBlock.YPos - runBlock[i].Y;

               if (y < lowRow)

                    lowRow = y;

               if (y > highRow)

                    highRow = y;

           }

           bool repaint = false;//判断是否重画标志

           for (int i = lowRow; i <= highRow; i++) //检查是否满行,如果有,则删除

           {

                bool rowFull = true;

               for (int j = 0; j < _width;j++ )

               {

                    if (coorArr[j,i].IsEmpty)//如果有一行为空,则说明这行不满

                    {

                        rowFull = false;

                        break;

                    }

               }

               if (rowFull) //如果是满行,则删除这一行

               {

                    repaint = true;//如果有要删除的行,则需要重画

                    for (int k = i; k > 0;k--) {//把第n行的值用n-1行的值来代替

                        for (int j = 0; j <_width; j++) {

                            coorArr[j, k] =coorArr[j, k - 1];

                        }

                    }

                    for (int j = 0; j <_width;j++ )//清空第0行

                    {

                        coorArr[j, 0] = Color.Empty;

                    }

               }

           }

           if(repaint)//重画

           {

               PaintBackground(gpPaltte);

           }

        }

3.8  产生下一方块

public bool GeneBlock(int shapeNO, Point firstPos, Colorcolor)//产生下一方块

        {

            this.SetLastPos();

            this.EraseLast();

            this.SetPos(shapeNO,firstPos);         

            if(!this.CanRotate(this.pos))

            {

                this.pos=null;

                returnfalse;

            }

            else

            {

                this.color=color;

                returntrue;

            }

        }

 

3.9        游戏设置

程序中游戏设置的保存方式为配置文件,配置文件中保存着游戏的按键设置,在打开程序时,会载入配置文件中的配置。用户可以在游戏中随时改变配置,改变后的配置将保存到配置文件中并且立即有效。游戏配置界面如下(图3-2):




保存配置

private void SaveSetting()

        {

            try

            {

                XmlDocumentdoc = new XmlDocument();

                XmlDeclarationxmlDec=doc.CreateXmlDeclaration ("1.0","gb2312",null);

 

                XmlElementsetting=doc.CreateElement("SETTING");

                doc.AppendChild(setting);

 

                XmlElementlevel=doc.CreateElement("LEVEL");

                level.InnerText=this.startLevel.ToString();

                setting.AppendChild(level);

 

                XmlElementtrans=doc.CreateElement("TRANSPARENT");

                trans.InnerText=this.trans.ToString();

                setting.AppendChild(trans);

   

                XmlElementkeys=doc.CreateElement("KEYS");   

                setting.AppendChild(keys);

                foreach(Keysk in this.keys)

                {

                    KeysConverterkc=new KeysConverter();  

                    XmlElementx=doc.CreateElement("SUBKEYS");

                    x.InnerText=kc.ConvertToString(k);

                    keys.AppendChild(x);

                }

 

                XmlElementroot=doc.DocumentElement;

                doc.InsertBefore(xmlDec,root);

                doc.Save("c:\\\\setting.cob");

            }

            catch(Exceptionxe)

            {

                MessageBox.Show(xe.Message);

            }

        }

4  总结

本设计通过Vusial Studio 方便的Windows表单设计界面,增加了相应的按钮单击响应函数,通过与用户的交互,反馈回用户所需要的信息。

设计出的程序符合设计需求,既有基本的游戏逻辑功能,又能保存用户的设置,取得较好的实验结果。

这个学期“C#程序设计”课程让我接触了面向对象的程序设计,Visual stdio的可视化编程环境让我们可以制作界面友好的Windows环境,利用IDE可以快捷地开发出所要的可视化的环境。C#是是一种完全面向对象的语言,使用对象的思想来编程,既可以对相应的数据进行保护,也可以相应的与其他的类共享,有利于程序的结构化,方面程序的编写。Viusal Studio下我们可以快速的进行开发。但是,也要看到其对WindowsApi函数的封装也导致了我们在学习的时候对Windows程序的运行机制不了解,导致了学习时候的迷惑。本学期配套的书籍<< C#实用教程>>虽然简单明了,但是对于机制原理的解释和说明过少,因此,学习的时候应该不只满足于这本书中的内容,应该多找一些书籍进行知识的扩展了加深。

开发一个工程时,应该先制定好程序的框架,规划好相应的功能模块,使程序模块化,易于日后的扩展和完善。其次是程序的数据结构,良好的数据结构能使程序高效化,功能强大。本次实现中最重要的是方块类的编写,其定义的好坏和封装性的良好是整个程序运行的基础,属于程序的业务逻辑功能块,主框架中通过调用该类,实现程序的表示层。再之,优秀的算法能提高程序的效率。



以上是关于俄罗斯方块游戏设计与实现(Python)的主要内容,如果未能解决你的问题,请参考以下文章

C#之四十八 俄罗斯方块设计

基于python的俄罗斯方块小游戏

Python游戏开发,pygame模块,Python实现俄罗斯方块小游戏

万字博文讲解如何用shell来实现一个俄罗斯方块小游戏

Python课程设计之俄罗斯方块

安卓开发: Jetpack compose + kotlin 实现 俄罗斯方块游戏