1. 程式人生 > >貪吃蛇C#實現

貪吃蛇C#實現

貪吃蛇相於其它小遊戲,算是簡單的一個。沒學GDIWPF啥的,也不想學,恰好在C#程式設計課中學過WinForm,所以就用WinForm做了個簡單的貪吃蛇。

完整程式碼在此:資源連結

遊戲介面如下:

貪吃蛇

灰色邊框為一圈灰色的Button,設定Enable屬性為false,避免滑鼠對它有影響(顏色變化)。
中間的空白地圖為24*24個PictureBox,即pictureBox0~pictureBox575。
遊戲地圖可用一個圖類Graph表示,而單個位置又可通過一個點類Point表示。有了Graph類和Point類,則可建立一個Snake類,通過Graph類和Point類的支援,來模擬蛇的各種操作。

Point類的建立可方便對單一網格或圖中對應元素的操作,如可通過Point物件p來操作橫座標為p.X,縱座標為p.YPictureBox背景色,達到某些效果。也可通過p.Xp.Y獲取對應圖的元素資訊。

//程式碼只用於說明思路,沒有完整實現,具體實現詳見資源連結
public class Point {
    int x;
    int y;

    //判斷點是否合法,只有獲取valid值時才進行設定
    bool valid;  

    //無參建構函式
    public Point() {}

    //有參建構函式
    public Point(int posX,int
posY) {} //複製建構函式 public Point(Point p) {} //判斷兩點座標是否相同 public bool equal(Point p) {} //設定點資訊,通過座標 (posX, posY) public void setPoint(int posX,int posY) {} //設定點資訊,通過索引 index public void setPoint(int index) {} //x的get和set public int X {} //y的get和set public int
Y {} //valid的get,不能set,因為它標誌著點是否合法 public bool Valid {} }

Graph類記錄遊戲網格背後的資料,蛇的身體就是一些點組成,而這些點就反應在圖中連續(上下左右)元素值不為零,此外,還記錄了遊戲中蘋果的位置(通過與蛇身體的值不同的值,來標記蘋果,如蛇身體標對應元素值為1,蘋果為2,圖中無任何內容的地方為0)。

public class Graph {
    int[,] graph;  //圖物件

    //每個元素可取值0、1、2,
    //0: 此處為 空
    //1: 此處有 蛇身體
    //2: 此處有 蘋果

    //建構函式
    public Graph() {}

    //重置圖資訊
    public void resetGraph() {}

    //設定值,通過點和值 (p, value)
    public void setValue(Point p,int value) {}

    //設定值,通過座標和值
    public void setValue(int x, int y, int value) {}

    //獲取值,通過點
    public int getValue(Point p) {}

    //獲取值,通過座標
    public int getValue(int x,int y) {}
}

Snake類通過Graph類和Point類的協助,進行蛇的設定、模擬蛇的移動以及吃掉蘋果等操作。蛇在移動的過程中,需要判斷它正前方的點的資訊,如果是蘋果,則吃掉;如果是牆或自己的身體,則掛掉;如果是空,則更新蛇的身體,整體向前走一步。

class Snake {
    Point[] snake;  //snake陣列,0下標為snake尾,大下標為snake頭
    int count;  //snake身體長度

    //建構函式
    public Snake() {
        snake = new Point[576];  //蛇身長最多為遊戲介面的網格個數
        count = 0;
    }

    //重置snake資訊
    public void resetSnake() {
        count = 0;  //只需重置蛇身體長度資訊,不必刪除各個點資訊
    }

    //獲取snake頭
    public Point getHead() {
        Point p = new Point(snake[count - 1]);
        return p;
    }

    //新增點到末尾,即吃了apple
    public void append(Point p) {
        if (!p.Valid) throw new Exception("點不合法");
        snake[count++] = new Point(p.X, p.Y);
    }

    //移動,pos可取 0:  上    1: 下    2: 左    3: 右
    //返回值 0:  掛了    1: 已移動    2: 吃了apple    3: 反方向移動
    public int move(Graph graph,int pos) {
        int x = snake[count - 1].X;  //snake頭橫座標
        int y = snake[count - 1].Y;  //snake頭縱座標
        Point p;  //記錄蛇即將要走的點
        switch (pos) {  //根據蛇即將要走的方向,獲取p點
            case 0:
                p = new Point(x - 1, y); break;
            case 1:
                p = new Point(x + 1, y); break;
            case 2:
                p = new Point(x, y - 1); break;
            case 3:
                p = new Point(x, y + 1); break;
            default:
                throw new Exception("方向資訊錯誤");
        }

        //撞牆
        if (!p.Valid) return 0;

        //沒撞牆,但反方向走
        //必須先判斷是否反方向走,再判斷是否撞上了身體。因為反方向走時,下一個點為蛇身的
        //第二個點,會被誤判為撞上了身體
        if (p.equal(snake[count - 2])) return 3;

        //沒撞牆,也沒反方向走,但是撞上了身體(非snake第二個點)
        if (graph.getValue(p.X,p.Y) ==1) return 0;

        //有apple,吃掉
        if (graph.getValue(p.X,p.Y) == 2) {  //此處有apple
            snake[count++] = new Point(p.X, p.Y);  //將apple加入到snake頭
            graph.setValue(p, 1);  //將apple新增到圖中
            return 2;
        }

        //可移動,更新snake資訊及圖資訊
        else {
            //snake尾在圖上消失
            graph.setValue(snake[0], 0);
            //身體往“前”移
            for (int i = 0; i < count - 1; i++) {
                snake[i].X = snake[i + 1].X;
                snake[i].Y = snake[i + 1].Y;
            }
            //snake頭更新
            snake[count - 1].X = p.X;
            snake[count - 1].Y = p.Y;
            graph.setValue(p, 1);  //將snake頭新增到圖中
            return 1;
        }
    }
}

遊戲主介面GameForm類:

public partial class GameForm : Form {
    Random random;
    Graph graph;  //圖物件
    Snake snake;  //snake物件
    int score;  //遊戲分數
    int record;  //遊戲最高分
    bool inGame;  //遊戲中
    bool canPress;  //每次計時間隔內第一次按鍵有效,避免玩家頻繁操作
    bool isGameOver;  //遊戲結束標記
    int pos;  //記錄snake當前前進方向,0: 上    1: 下    2: 左    3: 右
    Point apple;  //apple點

    public GameForm() {
        //值初始化
    }

    //snake重生
    public void snakeBorn() {
        //設定預設的snake出生的三點
        //將三點新增到snake身體中
        //將三點新增到圖中
    }

    //開始遊戲
    public void start() {
        //重置圖資訊
        //重置snake資訊
        //snake重生
        //顯示snake
        //重新整理apple
        //重置分數
        //更新記錄標籤
        //更新分數標籤
        //開始計時
        //進入遊戲狀態inGame=true
        //可按鍵canPress = true;
        //更新遊戲結束標記isGameOver=false
        //初始化方向
    }
    //獲取新的apple並新增到圖中
    public void getNewApple() {}

    //遊戲結束處理
    public void gameOver() {}

    //重置PictureBox背景色為白色
    public void resetPBBackColor() {}

    //根據圖資訊重新整理PictureBox的背景色,只重新整理snake身體,即graph中元素值為1
    public void showSnake() {
        //重置PictureBox背景色
        //顯示蛇身體
        //顯示蛇頭,設定顏色為DodgerBlue
    }

    //將apple顯示在圖中
    public void showApple() {}

    //根據座標獲取PictureBox
    public PictureBox getPictureBox(int x, int y) {}

    //計時器事件觸發
    private void timer1_Tick(object sender, EventArgs e) {
        //假如遊戲結束,進入結束處理
        //遊戲未結束,且玩家按鍵,則根據玩家按鍵來更新PictureBox背景色
        //玩家沒按鍵或按鍵失效,則進入相應操作

        //顯示分數
        //重新整理為可按鍵狀態
    }

    //點選記錄按鈕
    private void recordBtn_Click(object sender, EventArgs e) {
        //遊戲暫停
        //顯示遊戲紀錄窗體
        //可能玩家已經重置記錄,需要更新recordLabel
        //點選關閉後,遊戲繼續
    }

    //點選開始按鈕
    private void startBtn_Click(object sender, EventArgs e) {
        //每次點選開始,遊戲暫停
        //假如在遊戲中,顯示確認放棄當前遊戲的視窗
    }

    //按鍵時的操作,鍵盤按鍵事件的輔助函式
    public void keyPress(int n) {}

    //鍵盤按鍵事件,因為上下左右鍵會被窗體提前捕獲,因此用WASD來操控方向
    private void GameForm_KeyDown(object sender, KeyEventArgs e) {
        if (inGame && canPress) {
            canPress = false;  //計時器觸發之前都按鍵無效,避免玩家頻繁操作
            switch (e.KeyCode) {
                case Keys.W: keyPress(0); break;
                case Keys.S: keyPress(1); break;
                case Keys.A: keyPress(2); break;
                case Keys.D: keyPress(3); break;
                default: break;
            }
        }
    }
}

需要注意的是,為了遊戲的健壯性,需要判斷各種玩家的可能操作,如正在遊戲中,玩家點選了開始按鈕,為避免玩家的誤點選,可提示玩家是否放棄當前遊戲。同時,需要停止計時器,以避免在此期間蛇因為計時器導致的自動向前移動而掛掉。點選記錄標籤頁是一樣。

在設定點和圖類的時候,需要自身進行輸入判斷,如傳入的點是否有效,值是否有效等,在輸入錯誤時執行某些預定操作。僅依賴外界提供正確資訊,往往導致出錯後除錯困難。

當遊戲背景中網格很多時,通過for迴圈找到某個控制元件會導致效能低下,也可以通過switch-case進行定位,不過程式碼量也會隨之增長。