前几天和一个在读的本科生聊天,他一直在抱怨学校学习的理论知识太多,实践的机会太少。担心自己因此毕业后可能难以找到工作。我认为一个人要是想投入开发,他总是可以找到项目的。与其把自己的时间浪费在抱怨和指责上,为什么不现在就动手开始开发呢?他的回答是不知道开发啥,没有人指导自己,不知道从何入手。我想想了,那么我们就从俄罗斯方块开始下手吧。      “从俄罗斯方块开始,那个游戏太简单了。我即使可以开发一个俄罗斯方块,又会有那个公司要我呢?如果将来我的建立上写上我做过俄罗斯方块游戏。那也不大会有人要我吧!”       “一个俄罗斯方块无论是从算法上来讲,还是从其他方面上来讲,确实太简单了。但是很多人只能看到俄罗斯方块的简单,却很少有人能把简单做到极致。而一个成功的程序员就是要做到把简单的东西深入下去,把简单的事情做到极致,就不在是简单了。让我来带你开发个俄罗斯方块吧。”      那么也请各位看客跟着我们一起去深入俄罗斯方块,把这个简单的小玩意做到一个极致。这个系列的探讨适合有一定的C#的语法基础。但是尚未做过任何大的项目的童鞋一起学习。如果哪位大侠看到鄙人的东西,热烈欢迎拍砖。 首先说说咱们的学习的大概的规划:第一步、我们先简单的理解下俄罗斯游戏开发的大概算法和需要的知识,然后把这些知识组        合开发一个小小的俄罗斯游戏Demo。基本上实现俄罗斯方块游戏的控制和操作。
第二步、我们在我们的Demo的基础上,尽可能的提出对我们的算法进行优化。对我们的界面        进行美化,功能进行扩展。从一个简单的Demo变成一个可玩性很强的单机版俄罗斯        方块小游戏。第三步、我们来尝试把单机版俄罗斯方块做成一个俩人联机版游戏,利用SOCKET通信,让咱        们的俄罗斯方块实现可以双人玩。我们主要使用point-to-point方式来玩。一个当做        服务器端一个当做客户端来玩。第四步、我们把我们的简单联机版俄罗斯方块做成一个带游戏大厅的服务端和客户端多人连        接俄罗斯方块游戏。让更多的人加入我们来玩。第五步、从可盈利出发。我们研究深入研究俄罗斯游戏可能产生的盈利模式,我们将在俄罗        斯游戏中把我们的盈利模式实现,我们将在我们的游戏中增加其他的一些道具,而        这些道具有可能需要稍微改变下游戏的规则。至此为止,我们就可以依托一个点逐        渐的拓展成一个面,完成一个俄罗斯方块游戏商业模式。
      至此我们的游戏已经可以成为一个可盈利的商业模式。当然能不能依托这个简单的游戏收获金钱需要看咱们做的游戏的好坏。也需要看咱们的机会和宣传了。从极坏的情况下来讲解。咱们赚钱的可能性基本上为零。当然这个也是这次开发最最可能呈现的情况。但是对一个学习计算机的程序员来说。这样的开发过程绝对可以称得是一次工作经历了,而且有了这样的思维和学习的模式,我相信抱怨和指责应该可以远离你的生活了。
最新的代码和例子会第一时间在blog上放映!http://blog.csdn.net/aofengdaxia

解决方案 »

  1.   

    根据上节我们规划好的开发过程,这节我们来实现一个简单的俄罗斯方块程序。当然完成俄罗斯方块的Demo是我们这节课的最终目标。在实现这个目标的同时,我还要跟大家一起去分析俄罗斯方块的需求、算法、具体开发的步骤。并且最终选择C#语言来实现这个demo。下面废话少说,翠花,上内容!
    第一步、   思考俄罗斯方块的游戏规则:      任何时候我们去开发或者设计一个项目,首先需要思考的就是需求。      俄罗斯方块中,我们要做一个俄罗斯方块的游戏,那么俄罗斯方块游戏的游戏规则就是我们的需求。所以我们必须深入了解俄罗斯方块游戏的游戏规则。俄罗斯方块游戏可能产生下面五种形状的方块。这些方块有我们的方向键的控制可以产生旋转,左右移动和加速前进等,往下掉,只到遇到下面已经有方块挡住停下来。如果一行完全排列满,则会消掉,如果排列到了顶部,则会失败。第二步、把我们的“需求”从计算机的角度去分析:     我们对任何一个项目的最初的需求是从使用者的角度去描述这个项目的,这种描述基本上涵盖了使用者可能用到的功能和要求。我们在做一个项目,特别是为一个非计算机专业做一个项目的时候,客户的描述,或者业务人员承接到项目的时候,他们和客户的沟通,基本上是基于这种初级的需求的,这种初级的需求的有点是:客户和业务人员比较容易理解,沟通起来非常的方便。但是他的缺点是非计算机专业用语,不够规范和严谨。不能拿这个文档和需求来直接要求开发人员。需要一个对市场(客户)比较了解,同时又是资深的程序员把这个需求编程一个专业开发人员相对比较容易理解的需求。甚至直接编程开发的具体思路。然后有开发人员去实现。我们的小游戏实在是太小了,不可能分出来业务人员、项目规划人员、项目架构、程序开发。所以这一切也都有我们开发者一个人独揽了。不过麻雀虽小,五脏俱全哦。我们不是说了嘛,通过学习俄罗斯方块掌握项目开发的经验呢。所以希望各位看客认真的看这些个步骤,如果我哪里说错了,欢迎拍砖。     a、关于俄罗斯方块游戏中方块的产生。我们假设产生四种方块。实际上俄罗斯方块产生的方块要多余四个。但是咱们这节课是做一个Demo,所以先做四个,具体的四个请见图片1。(因为CSDN不能上传图片,我上传到下载中去了。大家可以下载使用,期待csdn可以正常的上传图片)。    b、俄罗斯方块当需要变化方块的时候,每个方块需要顺时针旋转90°,具体可能产生的形状也见图一。从数学,或者计算机的角度来看:我们根据图1来看,我们可以把所有的方块看成一个4*4的二维数组。把有“砖”的地方表示为1,没有“砖”的地方表示为0,所以所有的的方块都可以表示为图二的样式。    c、我们也可以把背景看成是14*20的二维数组。       那么俄罗斯方块的需求,站在程序员的角度上就可以变成:我们随机从方块的4个4*4的矩阵中挑选出来一个,并且随机的挑选一个他的初始化状态(关于状态变化,我们同样可以把他们表示在一个4*4的矩阵中)。然后这个被挑选的矩阵,在一个14*20的矩阵中不断的按照一种速度进行往下运动。我们同时可以使用方向键,对这个举证进行控制,使得它可以左右运动,同时可以循环的变化他的状态。如果这个矩阵在运动的方向上遇到了数值为1的时候则停止运动,在左右运动上表现为不能移动,在往下运动的时候则表现为这个方块运动的结束。把这个矩阵的数值复制到背景矩阵中去。这个时候检查背景矩阵,如果背景矩阵中有一个行全部为1,那么在y轴上比该行小的所有行向下移动一行,用户得分增加100。同理,检查所有的行,并且做同样动作。检查完成后,进入下个方块的随机挑选,下落。当某个方块下落完成的时候。他的y坐标在背景中为0的时候。游戏结束。作为游戏的界面,我们需要在游戏的状态发生改变的时候,把背景矩阵和运动矩阵都绘制出来。数值为0的地方不绘图,数值为1的地方绘制图片。本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/aofengdaxia/archive/2010/02/09/5301934.aspx
      

  2.   

    到此为止。我们的游戏已经用人类语言。从计算机的角度来说分析完成了。剩下的就是使用计算机语言表达出来了,不管你是学习的C#,C语言,C++,Delphi,Java,python,Js,VB,……(汇编我不懂,暂时不包括吧)。只要你能理解了上述的“思路”。那么你可以用任何语言来写出来。我真希望用所有的语言都给大家写写,把IT老祖宗的那句:“编程靠的是思想(想法,思路),语言不重要。"的真理来验证下,可怜CSDN的blog写起来太麻烦了,而且我老婆催着我赶紧回家过年的,所以这次就暂时使用C#来给大家实现下。我们就从一个C#盲来开始看看需要如何学习,如何实现。       首先、确定我们要用的语言是C#,矩阵的运算需要多是多维数组,数组的运算多需要循环。另外对多种情况的判断需要if,switch等分支判断语句,我们的方块定时运动,需要用到timer控件。所以在开始咱们的项目前,请确保,你已经对C#的语法有了基本的了解,尤其对数组的操作、if语句,for循环语句,switch语句要会用。同时对基本的button、timer控件进行基本的了解。如果你在使用其他语言编程,找你所使用的语言中的类似的东西。其他的东西至少这个项目中不会经常用到。即时用到,查查即可。这个可能就是所谓的“语言没有思想重要吧”。如果你还不懂,去看看<C#入门经典吧>.        其次、俄罗斯方块需要绘制图形,这个需要你对C# GDI+有所了解。在其他语言中可能是GDI或者API了,这个你只需要google下即可。如果GDI+编程你还不懂,那么来下载个教程吧。我已经给大家上传到:http://download.csdn.net/source/2057311
      

  3.   

    上面的两个条件如果你已经具有了。那么开始我们的C#俄罗斯方块的代码之旅途吧。       不过在代码开始之前,还是先看看我们的界面吧。请看界面图;       下面我们来看代码。首先定义我们的砖块和背景吧。#region 定义砖块int[i,j,y,x] Tricks:i为那块砖,j为状态,y为列,x为行   
           private int[, , ,] Tricks = {{   
                                         {   
                                             {1,0,0,0},   
                                             {1,0,0,0},   
                                             {1,0,0,0},   
                                             {1,0,0,0}   
                                         },   
                                         {   
                                             {1,1,1,1},   
                                             {0,0,0,0},   
                                             {0,0,0,0},   
                                             {0,0,0,0}   
                                         },   
                                         {   
                                             {1,0,0,0},   
                                             {1,0,0,0},   
                                             {1,0,0,0},   
                                             {1,0,0,0}   
                                         },   
                                         {   
                                             {1,1,1,1},   
                                             {0,0,0,0},   
                                             {0,0,0,0},   
                                             {0,0,0,0}   
                                         }   
                                     },   
                                     {   
                                          {   
                                              {1,1,0,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {1,1,0,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {1,1,0,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {1,1,0,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          }   
                                      },   
                                      {   
                                          {   
                                              {1,0,0,0},   
                                              {1,1,0,0},   
                                              {0,1,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {0,1,1,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {1,0,0,0},   
                                              {1,1,0,0},   
                                              {0,1,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {0,1,1,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          }   
                                      },   
                                      {   
                                          {   
                                              {1,1,0,0},   
                                              {0,1,0,0},   
                                              {0,1,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {0,0,1,0},   
                                              {1,1,1,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {1,0,0,0},   
                                              {1,0,0,0},   
                                              {1,1,0,0},   
                                              {0,0,0,0}   
                                          },   
                                          {   
                                              {1,1,1,0},   
                                              {1,0,0,0},   
                                              {0,0,0,0},   
                                              {0,0,0,0}   
                                          }   
                                      }};  
     
           #endregion  
           #region 定义背景   
           private int[,] bgGraoud ={   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0},   
                                        {0,0,0,0,0,0,0,0,0,0,0,0,0,0}   
                                    };  
           #endregion  
     
      

  4.   

           
    砖块定义好了,我们还需要定义几个系统在变化的时候需要的全局变量。我在注视中写清楚了,不絮叨了。
    private int[,] CurrentTrick = new int[4, 4]; //当前的砖块   
            //CurrentTrickNum当前砖块的数目, CurrentStatusNum当前状态, CurrentX当前x, CurrentY当前y, Sorce分数   
            private int CurrentTrickNum, CurrentStatusNum, CurrentX, CurrentY, Sorce;   
            private int TricksNum = 4;   
            private int StatusNum = 4;   
            private Image myImage;   
            private Random rand = new Random();  
    private int[,] CurrentTrick = new int[4, 4]; //当前的砖块
            //CurrentTrickNum当前砖块的数目, CurrentStatusNum当前状态, CurrentX当前x, CurrentY当前y, Sorce分数
            private int CurrentTrickNum, CurrentStatusNum, CurrentX, CurrentY, Sorce;
            private int TricksNum = 4;
            private int StatusNum = 4;
            private Image myImage;
            private Random rand = new Random(); 
    定义好了变量,我们来想想我们的几个函数吧。首先是变化砖块,变化砖块其实就是变化砖块的状态,把砖块数组中的状态位进行循环变化即可:
    /// <summary>
            /// 变化方块
            /// </summary>
            private void ChangeTricks()
            {
                if (CurrentStatusNum < 3)
                {
                    CurrentStatusNum++;
                }
                else
                {
                    CurrentStatusNum = 0;
                }
                for (int y = 0; y < 4; y++)
                {
                    for (int x = 0; x < 4; x++)
                    {
                        CurrentTrick[y, x] = Tricks[CurrentTrickNum, CurrentStatusNum, y, x];
                    }
                }
            } 
    再接着是检测砖块是否可以向下移动、向左移动、向右移动。思路很简单。看看他的要运动的方向的背景下个位置是不是为1.如果不是。那么就返回true.否则是false:/// <summary>   
            /// 检测是否可以向下了   
            /// </summary>   
            /// <returns></returns>   
            private bool CheckIsDown()   
            {   
                for (int y = 0; y < 4; y++)   
                {   
                    for (int x = 0; x < 4; x++)   
                    {   
                        if (CurrentTrick[y, x] == 1)   
                        {   
                            //超过了背景   
                            if (y + CurrentY + 1 >= 20)   
                            {   
                                return false;   
                            }   
                            if (x + CurrentX >= 14)   
                            {   
                                CurrentX = 13 - x;   
                            }   
                            if (bgGraoud[y + CurrentY + 1, x + CurrentX] == 1)   
                            {   
                                return false;   
                            }   
                        }   
                    }   
                }   
                return true;   
            }   
            /// <summary>   
            /// 检测是否可以左移   
            /// </summary>   
            /// <returns></returns>   
            private bool CheckIsLeft()   
            {   
                for (int y = 0; y < 4; y++)   
                {   
                    for (int x = 0; x < 4; x++)   
                    {   
                        if (CurrentTrick[y, x] == 1)   
                        {   
                            if (x + CurrentX - 1 < 0)   
                            {   
                                return false;   
                            }   
                            if (bgGraoud[y + CurrentY, x + CurrentX - 1] == 1)   
                            {   
                                return false;   
                            }   
                        }   
                    }   
                }   
                return true;   
            }   
            /// <summary>   
            /// 检测是否可以右移   
            /// </summary>   
            /// <returns></returns>   
            private bool CheckIsRight()   
            {   
                for (int y = 0; y < 4; y++)   
                {   
                    for (int x = 0; x < 4; x++)   
                    {   
                        if (CurrentTrick[y, x] == 1)   
                        {   
                            if (x + CurrentX + 1 >= 14)   
                            {   
                                return false;   
                            }   
                            if (bgGraoud[y + CurrentY, x + CurrentX+1] == 1)   
                            {   
                                return false;   
                            }   
                        }   
                    }   
                }   
                return true;   
            }  
    /// <summary>
            
      

  5.   

    -------------------------
    csdn上面有规定,不能连续回复3个帖子,每个帖子不能超过1000个字。郁闷啊。那啥时候可以回复完成啊。真累
    --------------------------------------下面是绘制函数:没啥思路,循环画出即可 
    private void Draw()   
            {   
                Graphics g = Graphics.FromImage(myImage);   
                g.Clear(this.BackColor);   
                for (int bgy = 0; bgy < 20; bgy++)   
                {   
                    for (int bgx = 0; bgx < 14; bgx++)   
                    {   
                        if (bgGraoud[bgy, bgx] == 1)   
                        {   
                              
                            g.FillRectangle(new SolidBrush(Color.Blue), bgx * 20, bgy * 20, 20, 20);   
                        }   
                    }   
                }   
                //绘制当前的图片   
                for (int y = 0; y < 4; y++)   
                {   
                    for (int x = 0; x < 4; x++)   
                    {   
                        if (CurrentTrick[y, x] == 1)   
                        {   
                            g.FillRectangle(new SolidBrush(Color.Blue), (x + CurrentX) * 20, (y + CurrentY) * 20, 20, 20);   
                        }   
                    }   
                }   
                Graphics gg = panel1.CreateGraphics();   
                
                gg.DrawImage(myImage, 0, 0);   
            }  
    下面的一个函数是比较重要的。向下运动函数。因为基本上向下运动的函数决定了下落的位置:基本思路其实很简单的。先检测是否可以向下下落,如果可以下落就下落了,如果不可以下落,那说明到底了。首先检测当前的坐标是不是为0,如果是,那么说明游戏结束了。否则。检测下。是否有满行的。如果有的,行平移然后并且可以增加积分,这一切完成以后,然后开始下个方块。
    /// <summary>   
            /// 下落方块   
            /// </summary>   
            private void DownTricks()   
            {   
                if (CheckIsDown())   
                {   
                    CurrentY++;   
                }   
                else  
                {   
                    if (CurrentY == 0)   
                    {   
                        timer1.Stop();   
                        MessageBox.Show("哈哈,你玩玩了");   
                           
                        return;   
                    }   
                    //下落完成,修改背景   
                    for (int y = 0; y < 4; y++)   
                    {   
                        for (int x = 0; x < 4; x++)   
                        {   
                            if (CurrentTrick[y, x] == 1)   
                            {   
                                bgGraoud[CurrentY + y, CurrentX + x] = CurrentTrick[y, x];   
                            }   
                        }   
                    }   
                    CheckSore();   
                    BeginTricks();   
                       
                }   
                Draw();   
            }   
    下面我们看看是如何检测方块的背景中是否有满行的,如果满行了把他上面的平移下去。这个是很多初学者感觉到难以解决的地方,其实就是循环。这个循环还有得优化,但是我想想还是把他放在下一个篇的优化篇上讲解吧。看代码,我们说思路。其实思路很简单的,循环看看是否有满行的,如果存在一个为0,说明没满行,继续下个循环,如果没有0,则说明满行了,增加分数,然后比这个y小的矩阵向下平移,当前的坐标=(x,y-1)即可。然后继续循环本行(因为本很是上一行又下来的)。 
    private void CheckSore()   
            {   
                for (int y = 19; y > -1; y--)   
                {   
                    bool isFull = true;   
                    for (int x = 13; x > -1; x--)   
                    {   
                        if (bgGraoud[y, x] == 0)   
                        {   
                            isFull = false;   
                            break;   
                        }   
                    }   
                    if (isFull)   
                    {   
                        //增加积分   
                        Sorce = Sorce + 100;   
                        for (int yy = y; yy > 0; yy--)   
                        {   
                            for (int xx = 0; xx < 14; xx++)   
                            {   
                                int temp = bgGraoud[yy - 1, xx];   
                                bgGraoud[yy, xx] = temp;   
                            }   
                        }   
                        y++;   
                        label1.Text = Sorce.ToString(); ;   
                        Draw();   
                    }   
                      
                }   
            }  
    还有几个函数大家都可以看懂的,我就不讲解了。我把所有的代码都贴一遍。另外把所有的图片和源文件打包后上传到csdn,请大家下载。代码下载地址:
    顺便嘟囔句:在这里发代码简直难受死了。如果您看了,这个帖子对您有那么一点点的意义,请留点东西吧。谢谢。
    另外这个帖子有100分。主要送给那些拍砖的人,欢迎拍砖。
    我博客上的排版和内容比这里的详尽一些,因为这里编辑困难的缘故,欢迎大家去看看
    [url=http://blog.csdn.net/aofengdaxia/archive/2010/02/09/5301934.aspx]http://blog.csdn.net/aofengdaxia/archive/2010/02/09/5301934.aspx
    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/aofengdaxia/archive/2010/02/09/5301934.aspx
      

  6.   

    O(∩_∩)O哈哈~,跟我入门有点像啊~~不过我编贪吃蛇啊,而且是用桌面图标玩。有空去看看我的作品吧:
    地址(forfree):http://download.csdn.net/source/1846539
      

  7.   

    写C#代码总是大块大块的for、 if else
    看得自己都烦了
      

  8.   

    刚学习编程的时候写了2000行代码俄罗斯方块。这是近期写的一个
    1、三种可选方块图案(中途可变换);
    2、支持录像功能,能把每一步操作记录下来进行回放(如果没有结束可继续操作);
    3、记录的每一步操作包括时间,可以调整回放速度(可以进行文件存储);
    4、支持扩展方块类型,参考范例自定义方块形状;
    5、绘制速度很快,不会出现闪烁的情况。
    http://download.csdn.net/source/162553
      

  9.   

    呵呵。这个貌似什么语言都少不了循环和判断吧。
    当然可以使用while,switch.或者使用工厂模式,反射来实现。语言不是问题。
      

  10.   

    有想法,好好学习一下,O(∩_∩)O~有想法,好好学习一下,O(∩_∩)O~有想法,好好学习一下,O(∩_∩)O~有想法,好好学习一下,O(∩_∩)O~
      

  11.   

    接下来的内容另外开辟新帖子了,欢迎大家继续关注:
    http://topic.csdn.net/u/20100218/15/d5c8a93f-5690-4efb-841e-5a290e7c183e.html
      

  12.   

    下学期要学C#啦。楼主这个就作为我的目标吧,哈哈……兴奋ing
      

  13.   

    期待 aofengdaxia单机,用户参与设计方块模型,颜色,背景,3D
    对战,P2P 人和网络中的2人都能玩;简单-->精致-->极致期待ing....
      

  14.   

    运行过代码,不太明白在checkisdown函数中为什么有y + CurrentY 要加一,然后在最初游戏运行时,方块不能掉到最底下一行