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#实用教程>>虽然简单明了,但是对于机制原理的解释和说明过少,因此,学习的时候应该不只满足于这本书中的内容,应该多找一些书籍进行知识的扩展了加深。

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

C#之四十八 俄罗斯方块设计相关推荐

  1. SAP UI5 应用开发教程之四十八 - 如何在 SAP UI5 应用里开发条形码扫描功能试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  2. 48. 数据结构笔记之四十八的有向无环图的应用关键路径

    48. 数据结构笔记之四十八的有向无环图的应用关键路径 "富贵不淫贫贱乐 , 男儿到此是豪雄.-- 程颢" 来看下有向无环图的另一个应用关键路径. 1.  关键路径 与AOV-网相 ...

  3. hardfault常见原因_XMC实验分享之四十八: Cortex M0的Hard Fault发生原因

    凡是在Cortex M系列内核上写过程序的程序员, 没有不知道Hard Fault的. 大多数程序出现问题的表现就是进入Hard Fault. 但是进入Hard Fault的原因是甚么, 为了查明这个 ...

  4. 一步一步SharePoint 2007之四十八:实现Excel Service(3)——调用Excel Service

    摘要 本篇文章将介绍实现Excel Service的最后一部分--调用Excel Service. 为了方便您的学习,您可以下载本篇文章所创建的工程.单击此处下载(asp.net1.0). asp.n ...

  5. SAP UI5 应用开发教程之四十八 - 如何在 SAP UI5 应用里开发条形码扫描功能

    先看实现效果: 从 Github 下载本步骤的源代码: https://github.com/wangzixi-diablo/ui5-tutorial/tree/main/48 运行命令行 npm i ...

  6. SAP UI5 应用开发教程之五十八 - 使用工厂方法在运行时动态创建不同类型的列表行项目控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  7. SAP UI5 应用开发教程之四十九 - 如何在桌面电脑端调试运行在手机上的 SAP UI5 应用试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

  8. ROS探索总结(十六)(十七)(十八)(十九)——HRMRP机器人的设计 构建完整的机器人应用系统 重读tf 如何配置机器人的导航功能

    ROS探索总结(十六)--HRMRP机器人的设计 1. HRMRP简介         HRMRP(Hybrid Real-time Mobile Robot Platform,混合实时移动机器人平台 ...

  9. MySQL二十八规范数据库设计

    MySQL二十八:规范数据库设计 糟糕的数据库设计: ●数据冗余,浪费空间 ●数据库插入和删除都会麻烦.异常[ 屏蔽使用物理外键] ●程序的性能差 良好的数据库设计: ●节省内存空间 ●保证数据库的完 ...

最新文章

  1. python爬虫教程pdf-Python 爬虫:把廖雪峰教程转换成 PDF 电子书
  2. 小白都能看懂的网络性能测试
  3. C语言socket connect()函数(初始化套接字上的连接)(未完)(如何测试socket是否已经断开,如何判断socket是否断开)
  4. 浅析C#发送短信的原理
  5. 关于SparkMLlib的基础数据结构Spark-MLlib-Basics
  6. vue传值到后端_Vue.js快速入门就从这儿开始特别是后端程序员
  7. HTML meter控件
  8. Python学习第二天----网络基础及操作系统简介(安装linux系统)
  9. 分布式服务的幂等性设计,值得学习!
  10. 从零基础入门Tensorflow2.0 ----五、26TF1.0tf_data,make_initializable_iteror()
  11. mysql安装包设置本地yum源安装包_mysql 5.7.29 在centos7.6下超简单的本地yum源安装与配置...
  12. 零基础学会三菱FX3UPLC编程调试
  13. 2、Scala下载、安装、环境搭建、及基本用法
  14. 有Pytz时区列表吗?
  15. 线元法输入曲线要素_交点法、线元法
  16. 用PS做自己的个人LOGO
  17. 做自媒体短视频,最简单的赚钱方法,就是做流量收益
  18. 【NLP】自然语言处理学习笔记(一)语音识别
  19. 阻容感基础05:电容器原理(1)-电容器模型
  20. Proxy(代理) ARP作用及原理

热门文章

  1. verilog中数组的定义_verilog数组定义及其初始化
  2. 作为字节跳动面试官,这篇文章可以满足你80%日常工作!附小技巧
  3. 算作自我监督的第一篇博客
  4. linux的lvcreate语句,lvcreate
  5. 查看Docker中的nginx latest具体版本
  6. 最重要的会计期间是_会计期间通常分为会计年度和会计中期,会计中期包括( )。...
  7. Ubuntu ifconfig命令eth0没有IP 解决方法
  8. 终端神器 iterm
  9. java 除法取商_java除法怎样取小数部分
  10. java简单的除法运算_Java除法运算的陷阱