學(xué)習(xí)編程,我個(gè)人覺(jué)得最好的辦法就是根據(jù)自己的水平不斷的給自己設(shè)定一個(gè)小目標(biāo)。而這個(gè)小目標(biāo)就是一個(gè)有意思的項(xiàng)目,通過(guò)完成這個(gè)項(xiàng)目,對(duì)自己的成果(也包括失敗的)進(jìn)行分析總結(jié),從中提煉出對(duì)應(yīng)的技術(shù)并分享出來(lái),不斷的往復(fù),如此,為的就是讓我們永遠(yuǎn)保持編寫程序的興趣和熱情,完了,還提高我們的技術(shù)。而本文就是總結(jié)自己的一個(gè)小目標(biāo)(基于控制臺(tái)實(shí)現(xiàn)的貪吃蛇游戲而寫的總結(jié))
大家小時(shí)候一定玩過(guò)貪吃蛇的游戲。貪吃蛇游戲的控制過(guò)程其實(shí)也不復(fù)雜。簡(jiǎn)單的可以概括為以下4個(gè)部分。
在本程序中,食物以及蛇的組成都用一個(gè)對(duì)象表示,因?yàn)樗鼈兊淖饔枚际且粯樱瑑H僅只需要一個(gè)坐標(biāo)對(duì)。以及提供一個(gè)靜態(tài)方法,即可以通過(guò)在游戲的地圖內(nèi)生隨機(jī)產(chǎn)生一個(gè)坐標(biāo)對(duì)并返回Block對(duì)象。
程序的主體主要“蛇”這個(gè)對(duì)象的屬性以及方法的設(shè)計(jì)。現(xiàn)在我們想想在游戲中這個(gè)對(duì)象需要有哪些屬性以及行為,首先“蛇”有長(zhǎng)度以及“蛇”的組成,這就是蛇的屬性,可以由一個(gè)相鄰的Block類型的集合snakeList來(lái)表示;其次,蛇能移動(dòng)(上下左右),能吃食物,并且不能碰到邊框以及頭部不能觸碰自己的身體,這就可以抽象為蛇的三個(gè)行為,Move(),IsEatFood (),IsOver(),分別為移動(dòng),吃食物,檢測(cè)自身是否滿足游戲的規(guī)則。
1、Move()
//蛇移動(dòng)的關(guān)鍵代碼如下。
public void Move(Direction dir) { Block head = this.snakeLIst[0];//獲取蛇頭 this.snakeLIst.RemoveAt(this.snakeLIst .Count -1);//移除末尾項(xiàng) Block newBlock=null; switch (dir)//獲取蛇當(dāng)前運(yùn)行的方向,然后把根據(jù)蛇頭的位置計(jì)算出新的蛇頭的位置。相當(dāng)于把蛇尾的坐標(biāo)進(jìn)行計(jì)算插入到蛇頭。 { 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);//將新的位置插入到蛇頭 }
蛇移動(dòng)的程序的動(dòng)態(tài)過(guò)程如下圖:(以向左走為例)
2、IsEatFood ()
//代碼如下
/// <summary> /// 判斷蛇是否達(dá)到食物的位置,如果到達(dá)這eat /// </summary> /// <param name="b">食物對(duì)象</param> /// <returns>返回bool,好讓調(diào)用方知道是否需要產(chǎn)生新的食物</returns> public bool IsEatFood(Block b) { Block head = this.snakeLIst[0];//獲取蛇頭 if (head.IsEqual(b))//是食物的位置一致 { this.snakeLIst.Add(b);//添加一個(gè)block到蛇的集合中,并這下一次move中移動(dòng)到蛇頭,保證有序。 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; }
我們都知道游戲都是畫面不斷變化的一個(gè)過(guò)程,所以我們必須在極短的時(shí)間內(nèi)去更新游戲的畫面,所以就離不開(kāi)定時(shí)器。然后就可以實(shí)時(shí)的去繪制游戲的當(dāng)前的狀態(tài),那么一系列的狀態(tài)連起來(lái)就一個(gè)動(dòng)態(tài)的游戲畫面。
其中System.Threading中的Timer類,具體的用戶可以查看其它資料,也可以看后面程序中是如何使用Timer類的
程序?qū)⒂脩艚换サ慕涌诜旁诹薓apManager,主要功能為,啟動(dòng)計(jì)時(shí)器去管理游戲的繪制與運(yùn)行,然后另外就是處理用戶的輸入去改變游戲運(yùn)行狀態(tài)。
其中,需要注意的是,由于是基于控制臺(tái)的實(shí)現(xiàn),用戶的輸入肯定是不能按enter之后然后程序才能接受,而是實(shí)時(shí)的接受用戶的輸入且還不能將用戶的輸入顯示到控制中,還好c#提供了Console.Readkey(true),可以滿足程序的要求。
注意:
其中需要注意的是,最后輸出賦值好的二維數(shù)組(地圖)時(shí)候,不是遍歷二維數(shù)組(地圖)遍歷一項(xiàng)就輸出一項(xiàng),而是用StringBuilding對(duì)象去添加,直到遍歷完了,一次性輸出StringBuilding對(duì)象,達(dá)到雙緩存的效果,使得控制臺(tái)繪制不會(huì)閃爍。
本程序是基于c#控制臺(tái)實(shí)現(xiàn)的,開(kāi)發(fā)工具為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>();//存儲(chǔ)蛇的結(jié)構(gòu) 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> /// 判斷蛇是否達(dá)到食物的位置,如果到達(dá)這eat /// </summary> /// <param name="b">食物對(duì)象</param> /// <returns>返回bool,好讓調(diào)用方知道是否需要產(chǎn)生新的食物</returns> public bool IsEatFood(Block b) { Block head = this.snakeLIst[0];//獲取蛇頭 if (head.IsEqual(b))//是食物的位置一致 { this.snakeLIst.Add(b);//添加一個(gè)block到蛇的集合中,并這下一次move中移動(dòng)到蛇頭,保證有序。 return true ; } return false ; } public void Move(Direction dir) { Block head = this.snakeLIst[0];//獲取蛇頭 this.snakeLIst.RemoveAt(this.snakeLIst .Count -1);//移除末尾項(xiàng) Block newBlock=null; switch (dir)//獲取蛇當(dāng)前運(yùn)行的方向,然后把根據(jù)蛇頭的位置計(jì)算出新的蛇頭的位置。相當(dāng)于把蛇尾的坐標(biāo)進(jìn)行計(jì)算插入到蛇頭。 { 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;//定時(shí)器 int[,] gameMap = new int[row, col];//地圖 int count=0; StringBuilder mapBuffer;//緩存區(qū) 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; } //輸出控制臺(tái)(游戲畫面) 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-----------------------------當(dāng)前得分{0}------------------------/n", count); Console .Write(mapBuffer .ToString ());//從緩存區(qū)中輸出整個(gè)游戲畫面 } //游戲運(yùn)行管理 private void GameRun(object o) { InitMap(); InitSnake(); InitFood(); if (snake.IsEatFood(b)) { b = Block.ProvideFood();//產(chǎn)生新的食物 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("按任何鍵進(jìn)入游戲"); Console.ReadKey(true); } //程序開(kāi)始,該方法包括啟動(dòng)定時(shí)器,以及與用戶的交互 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(); }
本來(lái)想插入視頻,但是不可以直接上傳,就截幾個(gè)圖吧。
其實(shí)程序的最重要的部分是設(shè)計(jì)思路而不是編碼,就這個(gè)程序也可以使用c、python等語(yǔ)言實(shí)現(xiàn),都不是很那難。一旦程序的流程清晰了,編碼的過(guò)程自然也會(huì)浮現(xiàn)出來(lái)啦。
源碼:http://download.csdn.net/detail/mingge38/9220243 。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注