貪吃蛇C#實現
貪吃蛇相於其它小遊戲,算是簡單的一個。沒學GDI
或WPF
啥的,也不想學,恰好在C#程式設計課中學過WinForm
,所以就用WinForm
做了個簡單的貪吃蛇。
完整程式碼在此:資源連結
遊戲介面如下:
灰色邊框為一圈灰色的Button
,設定Enable
屬性為false
,避免滑鼠對它有影響(顏色變化)。
中間的空白地圖為24*24個PictureBox
,即pictureBox0~pictureBox575。
遊戲地圖可用一個圖類Graph
表示,而單個位置又可通過一個點類Point
表示。有了Graph
類和Point
類,則可建立一個Snake
類,通過Graph
類和Point
類的支援,來模擬蛇的各種操作。
Point
類的建立可方便對單一網格或圖中對應元素的操作,如可通過Point
物件p
來操作橫座標為p.X
,縱座標為p.Y
的PictureBox
背景色,達到某些效果。也可通過p.X
和p.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進行定位,不過程式碼量也會隨之增長。