學習編程,我個人覺得最好的辦法就是根據自己的水平不斷的給自己設定一個小目標。而這個小目標就是一個有意思的項目,通過完成這個項目,對自己的成果(也包括失敗的)進行分析總結,從中提煉出對應的技術并分享出來,不斷的往復,如此,為的就是讓我們永遠保持編寫程序的興趣和熱情,完了,還提高我們的技術。而本文就是總結自己的一個小目標(基于控制臺實現的貪吃蛇游戲而寫的總結)
大家小時候一定玩過貪吃蛇的游戲。貪吃蛇游戲的控制過程其實也不復雜。簡單的可以概括為以下4個部分。
在本程序中,食物以及蛇的組成都用一個對象表示,因為它們的作用都是一樣,僅僅只需要一個坐標對。以及提供一個靜態方法,即可以通過在游戲的地圖內生隨機產生一個坐標對并返回Block對象。
程序的主體主要“蛇”這個對象的屬性以及方法的設計。現在我們想想在游戲中這個對象需要有哪些屬性以及行為,首先“蛇”有長度以及“蛇”的組成,這就是蛇的屬性,可以由一個相鄰的Block類型的集合snakeList來表示;其次,蛇能移動(上下左右),能吃食物,并且不能碰到邊框以及頭部不能觸碰自己的身體,這就可以抽象為蛇的三個行為,Move(),IsEatFood (),IsOver(),分別為移動,吃食物,檢測自身是否滿足游戲的規則。
1、Move()
//蛇移動的關鍵代碼如下。
public void Move(Direction dir) { Block head = this.snakeLIst[0];//獲取蛇頭 this.snakeLIst.RemoveAt(this.snakeLIst .Count -1);//移除末尾項 Block newBlock=null; switch (dir)//獲取蛇當前運行的方向,然后把根據蛇頭的位置計算出新的蛇頭的位置。相當于把蛇尾的坐標進行計算插入到蛇頭。 { case Direction.Top : newBlock = new Block(head.Row-1, head.Col); break; case Direction .Bottom : newBlock = new Block(head.Row+1, head.Col); break; case Direction .Left : newBlock = new Block(head.Row, head.Col-1); break; case Direction .Right: newBlock = new Block(head.Row , head.Col+1); break; } this.snakeLIst.Insert(0, newBlock);//將新的位置插入到蛇頭 }
蛇移動的程序的動態過程如下圖:(以向左走為例)
2、IsEatFood ()
//代碼如下
/// <summary> /// 判斷蛇是否達到食物的位置,如果到達這eat /// </summary> /// <param name="b">食物對象</param> /// <returns>返回bool,好讓調用方知道是否需要產生新的食物</returns> public bool IsEatFood(Block b) { Block head = this.snakeLIst[0];//獲取蛇頭 if (head.IsEqual(b))//是食物的位置一致 { this.snakeLIst.Add(b);//添加一個block到蛇的集合中,并這下一次move中移動到蛇頭,保證有序。 return true ; } return false ; }
3、IsOver()
//代碼如下
public bool IsOver() { Block head = this.snakeLIst[0]; if (head.Row == 0 || head.Col == 0 || head.Row == 25 || head.Col == 80)//是否遇到邊界 { return true; } for (int i = 1; i < this.snakeLIst.Count; i++)//是否遇到自身 { if (head.IsEqual(this.snakeLIst[i])) { return true ; } } return false; }
我們都知道游戲都是畫面不斷變化的一個過程,所以我們必須在極短的時間內去更新游戲的畫面,所以就離不開定時器。然后就可以實時的去繪制游戲的當前的狀態,那么一系列的狀態連起來就一個動態的游戲畫面。
其中System.Threading中的Timer類,具體的用戶可以查看其它資料,也可以看后面程序中是如何使用Timer類的
程序將用戶交互的接口放在了MapManager,主要功能為,啟動計時器去管理游戲的繪制與運行,然后另外就是處理用戶的輸入去改變游戲運行狀態。
其中,需要注意的是,由于是基于控制臺的實現,用戶的輸入肯定是不能按enter之后然后程序才能接受,而是實時的接受用戶的輸入且還不能將用戶的輸入顯示到控制中,還好c#提供了Console.Readkey(true),可以滿足程序的要求。
注意:
其中需要注意的是,最后輸出賦值好的二維數組(地圖)時候,不是遍歷二維數組(地圖)遍歷一項就輸出一項,而是用StringBuilding對象去添加,直到遍歷完了,一次性輸出StringBuilding對象,達到雙緩存的效果,使得控制臺繪制不會閃爍。
本程序是基于c#控制臺實現的,開發工具為2013
public class Block { PRivate int x; private int y; public Block() { } public Block(int x, int y) { this.x = x; this.y = y; } public int Row { get { return this.x; } set { this.x = value; } } public int Col { get { return this.y; } set { this.y = value; } } public bool IsEqual(Block b) { if (this.x == b.Row&& this.y == b.Col) { return true; } return false; } public static Block ProvideFood() { Random r=new Random (); Block b = new Block(r.Next(1, 25), r.Next(1, 80)); return b; } }
public class Snake { List<Block> snakeLIst = new List<Block>();//存儲蛇的結構 public List<Block> SnakeLIst { get { return snakeLIst; } } public Snake() { InitSnake(); } private void InitSnake() { int rowStart = 2; int colStart=5; int lenth = 20+colStart ; Block b; for (int i = colStart; i < lenth; i++) { b = new Block(rowStart ,i); this.snakeLIst.Insert(0, b); } } /// <summary> /// 判斷蛇是否達到食物的位置,如果到達這eat /// </summary> /// <param name="b">食物對象</param> /// <returns>返回bool,好讓調用方知道是否需要產生新的食物</returns> public bool IsEatFood(Block b) { Block head = this.snakeLIst[0];//獲取蛇頭 if (head.IsEqual(b))//是食物的位置一致 { this.snakeLIst.Add(b);//添加一個block到蛇的集合中,并這下一次move中移動到蛇頭,保證有序。 return true ; } return false ; } public void Move(Direction dir) { Block head = this.snakeLIst[0];//獲取蛇頭 this.snakeLIst.RemoveAt(this.snakeLIst .Count -1);//移除末尾項 Block newBlock=null; switch (dir)//獲取蛇當前運行的方向,然后把根據蛇頭的位置計算出新的蛇頭的位置。相當于把蛇尾的坐標進行計算插入到蛇頭。 { case Direction.Top : newBlock = new Block(head.Row-1, head.Col); break; case Direction .Bottom : newBlock = new Block(head.Row+1, head.Col); break; case Direction .Left : newBlock = new Block(head.Row, head.Col-1); break; case Direction .Right: newBlock = new Block(head.Row , head.Col+1); break; } this.snakeLIst.Insert(0, newBlock);//將新的位置插入到蛇頭 } public bool IsOver() { Block head = this.snakeLIst[0]; if (head.Row == 0 || head.Col == 0 || head.Row == 25 || head.Col == 80)//是否遇到邊界 { return true; } for (int i = 1; i < this.snakeLIst.Count; i++)//是否遇到自身 { if (head.IsEqual(this.snakeLIst[i])) { return true ; } } return false; } }
public class MapManager { const int row = 25; const int col = 80; Snake snake;//蛇 Block b;//食物 Timer t;//定時器 int[,] gameMap = new int[row, col];//地圖 int count=0; StringBuilder mapBuffer;//緩存區 bool isNormal = true; //初始化地圖+繪制邊界 private void InitMap() { for (int i = 0; i < row; i++) { if (i == row - 1 || i == 0) { for (int j = 0; j < col; j++) { this.gameMap[i, j] = 1; } } else { for (int j = 0; j < col; j++) { if (j == col - 1 || j == 0) { this.gameMap[i, j] = 1; } else { this.gameMap[i, j] = 0; } } } } } //繪制蛇 private void InitSnake() { foreach (var s in snake.SnakeLIst) { gameMap[s.Row, s.Col] = 1; } } //繪制食物 private void InitFood() { gameMap[this.b.Row, this.b.Col] = 1; } //輸出控制臺(游戲畫面) private void DrawMap() { mapBuffer.Clear(); Console.Clear(); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (gameMap[i, j] == 1) { mapBuffer.Append("*"); } else { mapBuffer.Append(" "); } } mapBuffer.Append("/n"); } Console.WriteLine("/n-----------------------------當前得分{0}------------------------/n", count); Console .Write(mapBuffer .ToString ());//從緩存區中輸出整個游戲畫面 } //游戲運行管理 private void GameRun(object o) { InitMap(); InitSnake(); InitFood(); if (snake.IsEatFood(b)) { b = Block.ProvideFood();//產生新的食物 count++;//得分 } snake.Move(GlobalVar.dir); if (snake.IsOver()) { GameOver(); } DrawMap(); } private void GameOver() { Console.Clear(); Console.WriteLine("Game Over"); isNormal = false; t.Dispose(); } private void GameInit() { Console.WriteLine("按[w,s.a.d]作為上下左右,按[q]退出游戲?。?!"); Console.WriteLine("按任何鍵進入游戲"); Console.ReadKey(true); } //程序開始,該方法包括啟動定時器,以及與用戶的交互 public void Start() { GameInit(); snake = new Snake(); b = Block.ProvideFood(); mapBuffer = new StringBuilder(); //GameRun(null); t = new Timer(GameRun, null, 200, 100); char c; while (isNormal) { c=Console .ReadKey(true ).KeyChar ; switch (c) { case 's': if (GlobalVar.dir != Direction.Top) { GlobalVar.dir = Direction.Bottom; } break; case 'w': if (GlobalVar.dir != Direction.Bottom) { GlobalVar.dir = Direction.Top; } break; case 'a': if (GlobalVar.dir != Direction.Right) { GlobalVar.dir = Direction.Left; } break; case 'd': if (GlobalVar.dir != Direction.Left) { GlobalVar.dir = Direction.Right; } break; case 'q': GameOver(); break; } } Console.ReadLine(); } }
public enum Direction { Left,Right,Top,Bottom }
static void Main(string[] args) { MapManager mm = new MapManager(); mm.Start(); }
本來想插入視頻,但是不可以直接上傳,就截幾個圖吧。
其實程序的最重要的部分是設計思路而不是編碼,就這個程序也可以使用c、python等語言實現,都不是很那難。一旦程序的流程清晰了,編碼的過程自然也會浮現出來啦。
源碼:http://download.csdn.net/detail/mingge38/9220243 。
新聞熱點
疑難解答