C#之四十八 俄羅斯方塊設計

1        系統設計要求算法

1.1   需求分析編程

本系統爲一個用C#實現的爲咱們所熟悉的簡單的俄羅斯方塊遊戲,該系統的具體功能以下:數組

1).       能簡便的開始遊戲,遊戲中的方塊的功能與平常咱們所熟悉的遊戲的功能一致,各類塊的設置也一致,包括塊的旋轉,加速降低,平移,滿行消去,到頂遊戲結束功能;數據結構

2).       可以自定義遊戲中功能鍵的具體按鍵,顯示下一方塊提示信息,以及遊戲數據的統計;框架

3).       考慮須要解決的問題:怎麼樣設置圖形顯示;怎樣獲取鍵盤輸入;怎樣控制方塊的移動;怎樣控制時間間隔(用於遊戲中控制形狀的下落);遊戲中的各類形狀及整個遊戲空間怎麼用數據表示;遊戲中怎麼判斷左右及向下移動的可能性;遊戲中怎麼判斷某一形狀旋轉的可能性;按向下方向鍵時加速某一形狀下落速度的處理;怎麼判斷某一形狀已經到底;怎麼判斷某一已經被填滿;怎麼消去已經被填滿的一行怎麼消去某一形狀落到底後可以消去的全部的行;(如長條最多能夠消去四行)怎樣判斷遊戲結束,關於「下一個」形狀取法的問題。模塊化

2        設計思路函數

2.1   用面向對象的方法分析系統學習

從遊戲的基本玩法出發,主要就是俄羅斯方塊的形狀和旋轉,在設計中在一個圖片框中構造了一個20*20(像素)的小塊,由這些小塊組合成新的形狀,每四個小塊鏈接在一塊兒就能夠構造出一種造型,總共設計了7中造型,每種造型又能夠經過旋轉而變化出2到4種形狀,在遊戲窗體中用戶就可使用鍵盤的方向鍵來控制方塊的運動,而後對每一行進行判斷,若是有某行的方塊是滿的,則消除這行的方塊,而且使上面的方塊自由下落,其中,方塊向下的速度是有時鐘控件控制的。俄羅斯方塊遊戲設計主要包括如下10個方面:this

1).         遊戲界面的設計。spa

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#實用教程>>雖然簡單明瞭,可是對於機制原理的解釋和說明過少,所以,學習的時候應該不僅知足於這本書中的內容,應該多找一些書籍進行知識的擴展了加深。

開發一個工程時,應該先制定好程序的框架,規劃好相應的功能模塊,使程序模塊化,易於往後的擴展和完善。其次是程序的數據結構,良好的數據結構能使程序高效化,功能強大。本次實現中最重要的是方塊類的編寫,其定義的好壞和封裝性的良好是整個程序運行的基礎,屬於程序的業務邏輯功能塊,主框架中經過調用該類,實現程序的表示層。再之,優秀的算法能提升程序的效率。
相關文章
相關標籤/搜索