上个周末,突然莫名其妙的想做个什么小游戏娱乐 一下,就写了个简单的俄罗斯方块游戏,做到一半后由于较忙加上没心情去做,一直没时间完成,这个周末终于有点时间,总算是差不多了。贴到这里与大家一起分享一下。希望能起到抛砖引玉的作用,呵呵~。我最后会把源代码和把编译好之后的游戏程序贴到到这里,不感兴趣的也可以下载过去,偶尔玩一下小时候最幼稚的游戏来解解闷,呵呵。 先来个游戏截图:(点击源代码打包下载 )
我是在visual studio 2005下用C#做的,主要是觉得C#是C风格的语法,比较喜欢,就选它了。用的是.net的GDI+类库,当然也可以用其它语言工具来实现,如java中的swing,VC中的GDI都可以。 先介绍一下我对整个游戏的算法思想,估计是最容易想到的也是最笨的方法(没办法,我也是第一次写,呵呵)。大家都知道,这种游戏主要的规则就是,就是某个方块模型在一个规定的区域里面,固定的下落,在下落的过程中,游戏者会左右移动操作或是调整它的形状,其它的一些包括加分、升级都是一些后续的问题。 每一个方块模型都有一个共同的特点,那就是,它们都是由四个小矩形组成,排列成直线或是竖线或是T字形等等,这可以用四个矩形排列时的相对坐标位置不同来表示。我的总体做法就是把那个游戏区的黑色区域划分成很多个小矩形,目前是:12 X 21,当某个模型在区域中显示的时候,就把相应的矩形着成白色,同时打上标记。当模型方块在下落的过程中,每下移一行,就把原来的那一行重新着成黑色,去掉标记,把方块所在的新的一行所占用的矩形区域着成白色,同时打上标记。这样就达到了下落一行的效果,左移和右移与这个思想一样。再一个重要的问题就是变换的问题,例如方块模型从一个直线型变成一个竖线型,应该怎么办呢?这个问题从本质上来说仍然是对相应的矩形区域着色的问题,很自然的想法就是把原来的直线所在的矩形区域着成黑色,去掉标记,然后把竖线应该要显示的区域着成白色,同时打上标记,这样就达到了一个变换的效果。具体是怎么实现的,感兴趣的可以去看一下源代码,也许你会发现我的办法很笨拙,呵呵。那也是没办法的事。 最后一个问题就是如何判断下落的方块在什么情况下由于周围所在的区域已经被占用了,导致不能再下落、不能左右移动的问题。这个问题复杂一些,刚刚在着色的时候,我的做法就是把着成白色的做了标记,空白的地方没有做标记(其实就是一个布尔型变量),只要在移动时判断一下是否有标记就行了,具体也可以去看源代码。 还有一些问题,如一行被填满后,自动消失的问题,这应该比较简单,做法就是把此行先着成黑色,去掉标记,然后依次把相邻的上一行的信息 移到这一行来,相信用程序控制起来比较容易实现。 最后,方块的变换,左右移动的操作都是一些键盘消息,下落的过程用一个时间tick来完成,当然也可以单独用一个线程来处理。相信知道windows应用程序的人应该对这些不陌生。 最后会把生成好了的应用程序和整个VS2005工程文件夹压缩好后贴上来,下载后,直接开以打开,感兴趣的可以下载过来玩一下:) 下面看源代码: 一共有这么几个文件:Form1.cs,Form1.Designer.cs,Form2.cs,Form2.Designer.cs,Program.cs. 对VS2005的C#windows应用程序的文件组织熟悉的人应该知道它们分别代表什么。 Form1.cs:游戏逻辑的主程序 Form1.Designer.cs:游戏主界面的界面程序代码,由VS2005自动生成 Form2.cs:游戏键位设置的主程序 Form2.Designer.cs:游戏键位设置的界面,由VS2005自动生成 Program.cs:整个应用程序的启动入口程序,相当于main()函数,由VS2005自动生成。Form1.cs文件:
using
System;
using
System.Collections.Generic;
using
System.ComponentModel;
using
System.Data;
using
System.Drawing;
using
System.Text;
using
System.Windows.Forms;
namespace
RassionBlock
...
{
public partial class mainform : Form ... {
public Keys left = Keys.Left; /**/ /// /这几个为游戏的键位设置,因为是在Form2中设置,这里定义成public访问属性 public Keys right = Keys.Right; public Keys down = Keys.Down; public Keys change = Keys.Space; public Keys start_puase = Keys.F2; public Keys setkeys = Keys.F3; public int grade = 1 ; /**/ /// 游戏极别,这个级别是控制timer的tick周期的快慢 public Keys newgame = Keys.F4; private const int x1 = 20 ; // 黑色游戏区域的起始x坐标,以下类推 private const int x2 = 260 ; private const int y1 = 20 ; private const int y2 = 440 ; private const int rows = 21 ; // 行数 private const int cols = 12 ; // 列数 private struct unitrect // 每一个方块单元由一个矩形和一个标记位组成,标记位用于记录此单元是否被填充 ... {
public bool flag; /**/ /// /flag为true时,表明此区域已经被填充了 public Rectangle rect; } ; private unitrect[,] urect; // 二维数组,用于表示所有的矩形方框 private struct moder // 方块模型,每一个模型都是由四个小方块组成 ... {
public int x1; public int x2; public int x3; public int x4; public int y1; public int y2; public int y3; public int y4; } ; private moder[] mymoder; // 方块模型 private int currentIndex; // 记录当前下落的方块模型的索引 private bool rebuild; private enum zouyf_flag ... { DOWN, LEFT, RIGHT } ; // 任何方块模型在移动时,只能要向左、向右、向下三种操作,定义枚举类型 private int currentcols; // 当前方块下落所在的列 private int currentrows; // 当前方块下落所在的行 private Random rand; private bool sync = false ; // 用于控制当方块左右移动时与时间tick同步的问题,设计思想是,当方块正在判断能否下落时,锁定左右移动操作。 private int nextIndex; // 记录下一次将要出现的方块模型的索引 SolidBrush wbrush; // 白色画刷 SolidBrush bbrush; // 黑色 SolidBrush gbrush; // 背景色 private int totalscore = 0 ; // 总得分 private int totaltime = 0 ; // 总耗时 private float highest = 0 ; // 历史最高分数时间比 public mainform() ... {
InitializeComponent(); } private void Form1_Paint( object sender, PaintEventArgs e) ... {
Graphics formgs = e.Graphics; formgs.FillRegion(bbrush, new Region( new Rectangle(x1, y1, x2 - x1 + 1 , y2 - y1 + 1 ))); for ( int i = 0 ; i < rows; i ++ ) // 窗口被拖动、隐藏等时,需要重绘 for ( int j = 0 ; j < cols; j ++ ) ... {
if (urect[i, j].flag) formgs.FillRectangle(wbrush, urect[i, j].rect); } /**/ /// /重绘下一次将要出现的next方块 formgs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x1, 40 + 20 * mymoder[nextIndex].y1, 19 , 19 )); formgs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x2, 40 + 20 * mymoder[nextIndex].y2, 19 , 19 )); formgs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x3, 40 + 20 * mymoder[nextIndex].y3, 19 , 19 )); formgs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x4, 40 + 20 * mymoder[nextIndex].y4, 19 , 19 )); formgs.Dispose(); } private void Form1_Load( object sender, EventArgs e) ... {
initRect(); initMymoder(); wbrush = new SolidBrush(Color.White); bbrush = new SolidBrush(Color.Black); gbrush = new SolidBrush(Color.Gainsboro); mytimer.Enabled = true ; score_time_timer.Enabled = true ; rand = new Random(); nextIndex = rand.Next( 0 , 19 ); rebuild = true ; // 用于判断是不是最新构建的模型,在tick中会用到 } private void initRect() ... {
urect = new unitrect[rows, cols]; int unit = (x2 - x1) / cols; for ( int i = 0 ; i < rows; i ++ ) for ( int j = 0 ; j < cols; j ++ ) ... {
urect[i, j].flag = false ; urect[i, j].rect = new Rectangle(x1 + unit * j + 1 , y1 + unit * i + 1 , unit - 1 , unit - 1 ); } } private void initMymoder() /**/ /// /构建方块模型,一共有19种,每一个模型由四个小方块组成,x,y用于记录每个小方块的顶点坐标 ... {
mymoder = new moder[ 19 ]; /**/ /// /横直线的坐标分布 mymoder[ 0 ].x1 = 0 ; mymoder[ 0 ].x2 = 1 ; mymoder[ 0 ].x3 = 2 ; mymoder[ 0 ].x4 = 3 ; mymoder[ 0 ].y1 = 0 ; mymoder[ 0 ].y2 = 0 ; mymoder[ 0 ].y3 = 0 ; mymoder[ 0 ].y4 = 0 ; /**/ /// /竖直线的坐标分布,下面的类似 mymoder[ 1 ].x1 = 0 ; mymoder[ 1 ].x2 = 0 ; mymoder[ 1 ].x3 = 0 ; mymoder[ 1 ].x4 = 0 ; mymoder[ 1 ].y1 = 0 ; mymoder[ 1 ].y2 = 1 ; mymoder[ 1 ].y3 = 2 ; mymoder[ 1 ].y4 = 3 ; /**/ /// /L形的坐标分布,接下来的三个为它的旋转后的模型 mymoder[ 2 ].x1 = 0 ; mymoder[ 2 ].x2 = 0 ; mymoder[ 2 ].x3 = 0 ; mymoder[ 2 ].x4 = 1 ; mymoder[ 2 ].y1 = 0 ; mymoder[ 2 ].y2 = 1 ; mymoder[ 2 ].y3 = 2 ; mymoder[ 2 ].y4 = 2 ; mymoder[ 3 ].x1 = 0 ; mymoder[ 3 ].x2 = 1 ; mymoder[ 3 ].x3 = 2 ; mymoder[ 3 ].x4 = 0 ; mymoder[ 3 ].y1 = 0 ; mymoder[ 3 ].y2 = 0 ; mymoder[ 3 ].y3 = 0 ; mymoder[ 3 ].y4 = 1 ; mymoder[ 4 ].x1 = 0 ; mymoder[ 4 ].x2 = 1 ; mymoder[ 4 ].x3 = 1 ; mymoder[ 4 ].x4 = 1 ; mymoder[ 4 ].y1 = 0 ; mymoder[ 4 ].y2 = 0 ; mymoder[ 4 ].y3 = 1 ; mymoder[ 4 ].y4 = 2 ; mymoder[ 5 ].x1 = 2 ; mymoder[ 5 ].x2 = 0 ; mymoder[ 5 ].x3 = 1 ; mymoder[ 5 ].x4 = 2 ; mymoder[ 5 ].y1 = 0 ; mymoder[ 5 ].y2 = 1 ; mymoder[ 5 ].y3 = 1 ; mymoder[ 5 ].y4 = 1 ; mymoder[ 6 ].x1 = 1 ; mymoder[ 6 ].x2 = 1 ; mymoder[ 6 ].x3 = 0 ; mymoder[ 6 ].x4 = 1 ; mymoder[ 6 ].y1 = 0 ; mymoder[ 6 ].y2 = 1 ; mymoder[ 6 ].y3 = 2 ; mymoder[ 6 ].y4 = 2 ; mymoder[ 7 ].x1 = 0 ; mymoder[ 7 ].x2 = 0 ; mymoder[ 7 ].x3 = 1 ; mymoder[ 7 ].x4 = 2 ; mymoder[ 7 ].y1 = 0 ; mymoder[ 7 ].y2 = 1 ; mymoder[ 7 ].y3 = 1 ; mymoder[ 7 ].y4 = 1 ; mymoder[ 8 ].x1 = 0 ; mymoder[ 8 ].x2 = 1 ; mymoder[ 8 ].x3 = 0 ; mymoder[ 8 ].x4 = 0 ; mymoder[ 8 ].y1 = 0 ; mymoder[ 8 ].y2 = 0 ; mymoder[ 8 ].y3 = 1 ; mymoder[ 8 ].y4 = 2 ; mymoder[ 9 ].x1 = 0 ; mymoder[ 9 ].x2 = 1 ; mymoder[ 9 ].x3 = 2 ; mymoder[ 9 ].x4 = 2 ; mymoder[ 9 ].y1 = 0 ; mymoder[ 9 ].y2 = 0 ; mymoder[ 9 ].y3 = 0 ; mymoder[ 9 ].y4 = 1 ; /**/ /// /Z字形的坐标分布 mymoder[ 10 ].x1 = 0 ; mymoder[ 10 ].x2 = 1 ; mymoder[ 10 ].x3 = 1 ; mymoder[ 10 ].x4 = 2 ; mymoder[ 10 ].y1 = 0 ; mymoder[ 10 ].y2 = 0 ; mymoder[ 10 ].y3 = 1 ; mymoder[ 10 ].y4 = 1 ; mymoder[ 11 ].x1 = 1 ; mymoder[ 11 ].x2 = 0 ; mymoder[ 11 ].x3 = 1 ; mymoder[ 11 ].x4 = 0 ; mymoder[ 11 ].y1 = 0 ; mymoder[ 11 ].y2 = 1 ; mymoder[ 11 ].y3 = 1 ; mymoder[ 11 ].y4 = 2 ; mymoder[ 12 ].x1 = 1 ; mymoder[ 12 ].x2 = 2 ; mymoder[ 12 ].x3 = 0 ; mymoder[ 12 ].x4 = 1 ; mymoder[ 12 ].y1 = 0 ; mymoder[ 12 ].y2 = 0 ; mymoder[ 12 ].y3 = 1 ; mymoder[ 12 ].y4 = 1 ; mymoder[ 13 ].x1 = 0 ; mymoder[ 13 ].x2 = 0 ; mymoder[ 13 ].x3 = 1 ; mymoder[ 13 ].x4 = 1 ; mymoder[ 13 ].y1 = 0 ; mymoder[ 13 ].y2 = 1 ; mymoder[ 13 ].y3 = 1 ; mymoder[ 13 ].y4 = 2 ; mymoder[ 14 ].x1 = 0 ; mymoder[ 14 ].x2 = 1 ; mymoder[ 14 ].x3 = 0 ; mymoder[ 14 ].x4 = 1 ; mymoder[ 14 ].y1 = 0 ; mymoder[ 14 ].y2 = 0 ; mymoder[ 14 ].y3 = 1 ; mymoder[ 14 ].y4 = 1 ; mymoder[ 15 ].x1 = 1 ; mymoder[ 15 ].x2 = 0 ; mymoder[ 15 ].x3 = 1 ; mymoder[ 15 ].x4 = 1 ; mymoder[ 15 ].y1 = 0 ; mymoder[ 15 ].y2 = 1 ; mymoder[ 15 ].y3 = 1 ; mymoder[ 15 ].y4 = 2 ; mymoder[ 16 ].x1 = 1 ; mymoder[ 16 ].x2 = 0 ; mymoder[ 16 ].x3 = 1 ; mymoder[ 16 ].x4 = 2 ; mymoder[ 16 ].y1 = 0 ; mymoder[ 16 ].y2 = 1 ; mymoder[ 16 ].y3 = 1 ; mymoder[ 16 ].y4 = 1 ; mymoder[ 17 ].x1 = 0 ; mymoder[ 17 ].x2 = 0 ; mymoder[ 17 ].x3 = 1 ; mymoder[ 17 ].x4 = 0 ; mymoder[ 17 ].y1 = 0 ; mymoder[ 17 ].y2 = 1 ; mymoder[ 17 ].y3 = 1 ; mymoder[ 17 ].y4 = 2 ; mymoder[ 18 ].x1 = 0 ; mymoder[ 18 ].x2 = 1 ; mymoder[ 18 ].x3 = 2 ; mymoder[ 18 ].x4 = 1 ; mymoder[ 18 ].y1 = 0 ; mymoder[ 18 ].y2 = 0 ; mymoder[ 18 ].y3 = 0 ; mymoder[ 18 ].y4 = 1 ; } // 这个函数的作用是对于每一个方块模型,得到它最左边、最右边和最下边的坐标,在方块的变换和移动时要用到 private void getMinAndMaxXY(moder tempmoder, ref int minx, ref int maxx, ref int maxy) ... {
minx = maxx = tempmoder.x1; maxy = tempmoder.y1; if (minx > tempmoder.x2) minx = tempmoder.x2; if (minx > tempmoder.x3) minx = tempmoder.x3; if (minx > tempmoder.x4) minx = tempmoder.x4; if (maxx < tempmoder.x2) maxx = tempmoder.x2; if (maxx < tempmoder.x3) maxx = tempmoder.x3; if (maxx < tempmoder.x4) maxx = tempmoder.x4; if (maxy < tempmoder.y2) maxy = tempmoder.y2; if (maxy < tempmoder.y3) maxy = tempmoder.y3; if (maxy < tempmoder.y4) maxy = tempmoder.y4; } private void mytimer_Tick( object sender, EventArgs e) ... {
sync = false ; // 用于控制方块的移动操作与时间tick的同步问题,可以把tick看成是当独运行的一个线程 Graphics gs = this .CreateGraphics(); if (rebuild) // rebuild用于判断是否要构建新的方块,当前一个方块不能再下落时,构建新的方块 ... {
currentIndex = nextIndex; nextIndex = rand.Next( 0 , 19 ); // 得到下一次将要出现的方块模型的索引 /**/ /// /更新next方块提示 gs.FillRectangle(gbrush, new Rectangle( 300 + 20 * mymoder[currentIndex].x1, 40 + 20 * mymoder[currentIndex].y1, 20 , 20 )); gs.FillRectangle(gbrush, new Rectangle( 300 + 20 * mymoder[currentIndex].x2, 40 + 20 * mymoder[currentIndex].y2, 20 , 20 )); gs.FillRectangle(gbrush, new Rectangle( 300 + 20 * mymoder[currentIndex].x3, 40 + 20 * mymoder[currentIndex].y3, 20 , 20 )); gs.FillRectangle(gbrush, new Rectangle( 300 + 20 * mymoder[currentIndex].x4, 40 + 20 * mymoder[currentIndex].y4, 20 , 20 )); gs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x1, 40 + 20 * mymoder[nextIndex].y1, 19 , 19 )); gs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x2, 40 + 20 * mymoder[nextIndex].y2, 19 , 19 )); gs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x3, 40 + 20 * mymoder[nextIndex].y3, 19 , 19 )); gs.FillRectangle(bbrush, new Rectangle( 300 + 20 * mymoder[nextIndex].x4, 40 + 20 * mymoder[nextIndex].y4, 19 , 19 )); currentrows = 0 ; if (currentIndex == 0 || currentIndex == 3 || currentIndex == 5 || currentIndex == 7 || currentIndex == 9 || currentIndex == 10 || currentIndex == 12 || currentIndex == 16 || currentIndex == 18 ) currentcols = cols / 2 - 2 ; /**/ /// /这么做的目的是为了新出现的方块总是出现在顶部的中间,看起来美观一些