C語言,作為大多數人的第一門程式語言,重要性不言而喻,很多程式設計習慣,邏輯方式在此時就已經形成了。這個是我在大一學習 C語言 後寫的推箱子小遊戲,自己的邏輯能力得到了提升,在這裡同大家分享這個推箱子小遊戲專案。
GitHub 倉庫地址:github.com/weizhiwen/C…
先來看看最後的執行的效果。
這是一個在 Windows Dos 介面的小遊戲,介面上有推箱子的地圖,使用 # 來代表地圖的邊界,P 來代表推箱子的小人,X 來代表箱子,O 來代表箱子要推到的目標位置。
W(w)、S(s)、A(a)、D(d) 分別對應小人向上、下、左、右移動。
要寫這個小遊戲,我們面臨的問題有以下幾個。
-
1、遊戲地圖怎麼儲存?
-
2、遊戲怎麼執行?
-
3、遊戲地圖怎樣在位置固定的情況下不斷變化?
-
4、小人的移動邏輯怎麼寫?
-
5、遊戲怎麼結束?
1、遊戲地圖怎麼儲存?
C語言中只有基本的資料型別,遊戲地圖是二維的平面結構,很容易想到使用二維陣列來儲存遊戲地圖,程式碼詳情見 GitHub 倉庫中的 關卡.h 檔案。
2、遊戲怎麼執行?
因為推箱子游戲在遊戲結束之前要不斷接受使用者的輸入,所以我們可以設定一個標誌來判斷遊戲是否結束,把這個標誌設定為一個 while 迴圈的條件。在每次迴圈中,都要接收使用者的輸入,根據使用者輸入的值,來進行下一步的操作,在遊戲中就是小人的移動方向,上下左右,這裡我們可以用一個 switch 語句判斷。每一次迴圈,對應一次使用者輸入。
3、遊戲地圖怎樣在位置固定的情況下不斷變化?
在每次迴圈中,首先要把當前的地圖顯示出來,便於使用者下一次的移動輸入。我們將遊戲地圖設定為一個全域性變數,這樣在小人移動後,地圖上的字元改變就是永久的,然後列印區域性改變的新地圖。這樣程式不斷迴圈,一遍遍的列印地圖,遊戲地圖上的字元是可以不斷改變了,但是地圖的位置並不能固定下來。如果我們能重新整理介面上的值,不就可以在位置固定的情況下不斷變化了。重新整理本質就是除舊迎新,即把原來的除去,迎來新的。在程式中,我們可以把原來的介面清除,再把新的介面顯示在原來的位置。C語言中可以用 system("cls")
函式來清除控制檯的內容,然後我們再把新的地圖內容顯示出來。
小人的移動邏輯屬於具體的程式實現,我們放到下面再說,先來說說程式怎麼結束。
4、遊戲怎麼結束?
前面我們說設定一個標誌來判斷遊戲是否結束,但是遊戲什麼時候結束呢?推箱子的遊戲目標是將每個箱子推到目標位置,這是一種遊戲結束的情況,由於每次迴圈都要判斷,可以將其寫成一個函式。另外,如果使用者不想玩了想退出,這也是一種遊戲結束的情況,這裡我只考慮了這兩種情況,至於其他情況,讀者可自行考慮。
到目前位置我們可以寫出程式大致的框架了,外部一個大迴圈,每次迴圈都是先重新整理介面,接收使用者輸入,處理使用者的輸入,判斷遊戲是否結束。
5、小人的移動邏輯怎麼寫?
在上面的程式截圖中,可以看到我把小人的上下左右移動分別寫到了四個函式中,分別是 MoveToUp()、MoveToDown()、MoveToLeft()、MoveToRight()。以 MoveToUp() 函式為例,我們來分析小人移動的邏輯。
理論上,小人是可以上下左右的移動的,但是,由於有地圖的限制,小人不能穿牆的,只能在允許的道路上移動,比如下面這種情況,小人想向上移動,肯定是不允許的。
而下面這種的情況,小人是可以向上移動的,因為小人上面一格並沒有限制物。
所以我們要對小人理論上可以移動到的那格(下一位置)進行判斷,如果不是限制物(箱子和箱子要移動到的位置下面在詳細說),小人就可以移動,如果有限制物就不能移動。所以我們需要記錄一個座標點的值,這裡“下一位置”的參照物可以選取小人當前的位置,遊戲開始時,把小人的開始位置作為當前位置。小人向上移動,“下一位置”的橫座標就是小人當前位置的橫座標減一,縱座標就是小人當前位置的縱座標。然後我們就可以根據“下一位置”的橫縱座標找到具體的字元值,如果是空的,就可以移動,如果是箱子要移動的目標位置,小人也可以移動,還有一種情況是“下一位置”是箱子,我們還要考慮箱子的“下一位置”,箱子的下一位置也很好得到。因為小人和箱子是在一條線上移動的,所以在小人向上移動時,箱子的“下一位置”的橫座標就是小人“下一位置”的橫座標減一,兩者的縱座標相同。同樣我們也要對箱子“下一位置”的字元值進行判斷,如果字元值是空格和箱子可以移動的位置,就是可以移動的。小人向上移動的程式碼如下:
小人向下、向左、向右移動的程式碼也是類似的,無非就是把小人移動的下一座標改一改,向下移動,“下一位置”的橫座標就是小人的橫座標位置加一,兩者縱座標相同,程式碼詳情見 GitHub 倉庫中的 控制.cpp 檔案。
到這裡整個程式就算是完成了,可以執行整個程式效果如下,能發現哪裡有 Bug 嗎?
相信細心的你已經發現了,當小人移動到箱子要移動的目標位置,再移出,這個位置就會“消失”,為什麼出現這種情況呢?我們在前面總是關注小人要移動的”下一位置“和箱子要移動的“下一位置”,卻沒有關注在移動之前,這個位置(上一位置)原本的值,我們可以記錄這個“上一位置”的值,但是這樣考慮的問題就比較多了,尤其是箱子和小人都在箱子要移動的目標位置時,情況很複雜,那麼有木有簡單的方法呢?其實到現在為止,我們的程式大體上是沒什麼問題的,只是箱子要移動的目標位置會出現“字元消失”。這只是個小 Bug,把使用者當測試的微軟是怎麼做的呢?系統發行後不停的釋出補丁,我們也可以像這樣給這個程式打個“補丁”。箱子要移動的位置是不變的,我們可以能不能用一個二維陣列來存放這些特殊位置呢?這些特殊位置的值也是特殊的,要不就是目標位置,要不就是箱子,要不就是小人,而不能是空白字元,所以我們可以寫一個“補丁”——修復這個 Bug 的函式。當小人移動後,在每個方向的移動函式結尾加上下面這個修復函式。這裡判斷特殊位置是不是空白字元,如果是空白字元,就將特殊位置的值改為目標位置的字元值,這裡是字元 “O”,這樣就“修復”了程式的 Bug,“字元消失”的問題也被解決了。
我將程式劃分成了不同的檔案,GitHub倉庫也有程式目錄的說明檔案,讀者在閱讀程式碼時,會注意到 extern 關鍵字的使用,這個關鍵字是為了拆分的多個檔案之間共用某個變數或者函式。將關卡中的遊戲地圖更換,就可以實現推箱子的多個關卡,讀者有興趣可自己嘗試改進,本文也是起到一個拋磚引玉的作用。
最後想說的是,寫程式很注重邏輯,無論用什麼語言,程式的邏輯都是一樣的,無非就是哪種語言更方便,更快捷。寫程式真正玩的是邏輯,只有邏輯清晰,程式碼才能寫得好,否則頂多也是程式碼的搬運工。
歡迎關注我的微信公眾號:“程式設計心路”,在程式設計的路上,我們一起成長,回覆任意關鍵字會有驚喜哦!