程式的發展與軟體的構築在今日都以經成為專門學問,有許多不同的見解與主張,也有許多的方法論,甚至由方法論擴展不同程式語言的設計哲學。我們的選擇是Python,原因不外乎是規劃、測試與除錯的程式發展流程更為簡便,同時Python也提供了例外處理機制。
然而程式可以不僅僅是去做我們要程式做的事情,針對許多未知的情況,由以往的經驗與累積的資料,經過計算我們可以預測可能發生的結果,這就是電腦模擬。
例如對颱風路徑的推測,需要收集地面及高空的氣壓、溫度、溼度、風速等等資料,匯集製作圖表,也依往年的氣象資料與颱風的實際路徑,然後統合所有的資料,藉由電腦快速的計算能力,模擬出可能的情況。
商業上也廣泛利用電腦模擬,譬如保險公司要推出一項產品,訴求客戶、理賠事項、保費收取等等,在推出之前都經過嚴密的試算,當然,保險公司以賺錢為主要目標,同時也要讓消費者感到有所保障,製造雙贏的局面。
實務上的電腦模擬有很多種,常見的試算表軟體Excel的目的就是模擬試算之用。其實電腦遊戲也是利用模擬的方式與使用者互動,使用者的操作符合這種情境,電腦也隨即做出相對的反應。
假設我們要設計球類的遊戲,我們就需要寫出模擬球移動的程式,如下圖假設球移動到視窗的邊緣會以隨機的角度彈回。
嗯,我們還沒正式介紹Python的圖形使用者介面。Pygame是Python關於遊戲設計的第三方模組庫,我們在下一篇中才會正式介紹,雖說如此,我們已經寫了個簡易的鬥獸棋遊戲。現在,來模擬看看吧!
鬥獸棋的模擬
因為鬥獸棋需要使用者輸入要操作的棋子以及方向,棋子共有四種,方向也有四種,假設是隨機的進行過程,那麼我們可以借用random模組中的randint()函數,以1及4為參數,藉此隨機產生數字1、2、3、4中的任一個。
但是輸入的都必須是字母,因此我們先用字典設定字母與數字間的對應。
#《電腦做什麼事》的範例程式碼 #http://pydoing.blogspot.com/ #動物棋子對應的編號 animal = {1:"e", 2:"t", 3:"c", 4:"m"} #方向對應的編號 direction = {1:"u", 2:"d", 3:"r", 4:"l"}
這樣一來,在遊戲迴圈中便能以randint(1,4)當作animal及direction的key,然後直接呼叫handle函數。
#《電腦做什麼事》的範例程式碼 #http://pydoing.blogspot.com/ #隨機選取一隻動物與一個方向 handle(animal[randint(1,4)], direction[randint(1,4)])
因為不在要求使用者輸入,所以我們再把以下兩行刪除。
#《電腦做什麼事》的範例程式碼 #http://pydoing.blogspot.com/ #需要刪除的兩行程式碼........ print "操作「象」鍵入e,「虎」鍵入t,「貓」鍵入c,「鼠」鍵入m。" print "往「上」鍵入u,往「下」鍵入d,往「左」鍵入l,往「右」鍵入r。"
然後把handle()函數中的“請輸入正確的方向!!”改為“不可能的情況!!”,而“請輸入正確的棋子!!”改為“該棋子已不存在!!”。同時我們利用系統清除螢幕的指令,在程式中加進上一章寫過的clear()函數,然後放在遊戲迴圈的開頭。
每一輪迴我們再借用time模組中的sleep()函數,放到遊戲迴圈的結束,讓整個畫面顯示一秒,好讓我們目睹該輪迴做了什麼事情。所有改寫過的程式碼摘錄如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- """簡化版鬥獸棋遊戲。""" from time import sleep from random import randint from os import system from sys import platform from vector.point import Point from jungle.checker import Checker, Jungle #初始條件設定 #參與遊戲的動物棋子 players = {"e":[Jungle("E"),Point(1,1)], "t":[Jungle("T"),Point(1,2)], "c":[Jungle("C"),Point(2,2)], "m":[Jungle("M"),Point(2,1)]} #動物棋子對應的編號 animal = {1:"e", 2:"t", 3:"c", 4:"m"} #方向對應的編號 direction = {1:"u", 2:"d", 3:"r", 4:"l"} #印出棋盤的函數 def status(square): """以4×4的方格顯示棋盤。""" print "棋盤狀態顯示" print for j in range(square): for i in range(square): if players.has_key("e") and i == players["e"][1].x and j == players["e"][1].y: print "象", elif players.has_key("t") and i == players["t"][1].x and j == players["t"][1].y: print "虎", elif players.has_key("c") and i == players["c"][1].x and j == players["c"][1].y: print "貓", elif players.has_key("m") and i == players["m"][1].x and j == players["m"][1].y: print "鼠", else: print "口", print #往上移動 def up(name): if players[name][1] - Point(0,1) in players[name][0].board: players[name][1] = players[name][1] - Point(0,1) else: print "超過邊界囉!" #往下移動 def down(name): if players[name][1] + Point(0,1) in players[name][0].board: players[name][1] = players[name][1] + Point(0,1) else: print "超過邊界囉!" #往左移動 def left(name): if players[name][1] - Point(1,0) in players[name][0].board: players[name][1] = players[name][1] - Point(1,0) else: print "超過邊界囉!" #往右移動 def right(name): if players[name][1] + Point(1,0) in players[name][0].board: players[name][1] = players[name][1] + Point(1,0) else: print "超過邊界囉!" #從字典型態變數players的value找相對應的key def findkey(p): j = 0 for i in players.items(): if players.items()[j][1][1] == p: return players.items()[j][0] j = j + 1 return 0 #處理兩棋子的相遇情況 def encounter(first, second): if players[first][0].capture(players[second][0]): del players[second] return True else: return False #控制遊戲的函數 def handle(name, direction): try: if direction == "u": if findkey(players[name][1] - Point(0,1)): if encounter(name, findkey(players[name][1] - Point(0,1))): up(name) else: up(name) elif direction == "d": if findkey(players[name][1] + Point(0,1)): if encounter(name, findkey(players[name][1] + Point(0,1))): down(name) else: down(name) elif direction == "l": if findkey(players[name][1] - Point(1,0)): if encounter(name, findkey(players[name][1] - Point(1,0))): left(name) else: left(name) elif direction == "r": if findkey(players[name][1] + Point(1,0)): if encounter(name, findkey(players[name][1] + Point(1,0))): right(name) else: right(name) else: print "不可能的情況!!" except KeyError: print "該棋子已不存在!!" def clear(): if platform == "win32": return "cls" elif platform == "linux2" or "darwin": return "clear" else: return False def run(): while len(players) > 1: system(clear()) status(4) #操作提示 print handle(animal[randint(1,4)], direction[randint(1,4)]) #印出間隔線 print "*"*50 print sleep(1) #印出遊戲勝利者 for winner in players.values(): if winner[0].alive == True: print winner[0].name, "是最後的存活者!" if __name__ == "__main__": run() print raw_input("請按<Enter>來結束視窗。")
實際來跑跑看吧!
「虎」很快就被吃掉了,到結束會是怎麼樣的呢?
顯然這次的模擬「象」是最後遊戲的勝利者。
幕後運作
從整個鬥獸棋遊戲的模擬過程,每一次的輪迴程式就對畫面作一次更新,實際上我們所見到螢幕顯示出的畫面,也是在非常短的時間內不斷的重複輸出目前的作業階段。大體上,我們所用的軟體包括作業系統,程式都用類似的方式設計。
我們學習寫程式的目的除了能自己寫程式,不論解決問題或是完成某項工作,其餘更是要了解電腦幕後運作的道理,曉得這道理,就不會對龐雜的電腦系統內部感到困惑,進而掌握個中奧妙。
選單的互動模式
接下來我們稍做複習,利用上一章寫的選單程式,我們把寫過的範例整合在一起,程式開始執行時印出現在時間,另外選單預計包含下列五種功能。
- 計算費伯納西數列
- 計算平均值
- 讀取文字檔
- 猜數字遊戲
- 簡易鬥獸棋遊戲
這五種功能我們都已經寫過程式,現在則是要利用選單程式整合一起。選單程式儲存在menu.py,其餘都以模組,也就是把定義放在其他的Python檔案之中。第一項計算費伯納西數列儲存在fibonacci.py中,程式碼如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- #計算費伯納西數列 def fi(n): f = [0, 1] #儲存費伯納西數列 m = 2 #計算的迴圈 while m <= n: r = f[m-1] + f[m-2] f = f + [r] m = m + 1 return f def print_fi(): try: n = int(raw_input("請輸入所要計算的費伯納西數: ")) print "此數為", fi(n)[n] except ValueError: print "輸入的並非整數!!"
因為沒有打算讓這個模組成為可執行程式,所以沒有
#《電腦做什麼事》的範例程式碼 #http://pydoing.blogspot.com/ if __name__ == "__main__":的部份。
第二項計算整數平均值儲存在average.py之中,程式碼如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- #依使用者輸入的整數計算平均值 def average(): count = 0 total = 0 state = True print "鍵入quit就離開輸入。" while state: try: r = raw_input("請輸入整數: ") if r != "quit": n = int(r) if n <= 0: raise ValueError total = total + n count = count + 1 else: state = False except ValueError: print "請不要輸入小於或等於零的整數,或是除了整數以外的符號。" print print "輸入數字的平均為", total/count print
第三項讀取文字檔儲存在readfile.py之中,程式碼如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- #讀純文字檔 def read(): try: f = open(raw_input("請輸入檔名: "), "r") print "*"*50 print f.read() print "*"*50 f.close() print except IOError: print "沒有這個檔案喔!"
第四項猜數字遊戲儲存在guess.py,程式碼如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- from random import randint def guess(): answer = randint(1,99) print "**猜數字遊戲**" print state = True while state: try: guess = int(raw_input("請輸入1到99的數字: ")) if guess == answer: print print "正確答案!" print state = False elif answer < guess < 100: print "太大囉!再試一次。" elif 0 < guess < answer: print "太小囉!再試一次。" else: raise ValueError except ValueError: print "請不要輸入小於1或大於99的整數,或是除了整數以外的符號。"
第五項簡易鬥獸棋遊戲儲存在board4.py之中,程式碼如前所述。印出現在時間部份的模組則放在now.py之中,程式碼如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- from time import sleep, asctime weeks = {"Mon":"星期一", "Tue":"星期二", "Wed":"星期三", "Thu":"星期四", "Fri":"星期五", "Sat":"星期六", "Sun":"星期日"} months = {"Jan":"一月", "Feb":"二月", "Mar":"三月", "Apr":"四月", "May":"五月", "Jun":"六月", "Jul":"七月", "Aug":"八月", "Sep":"九月", "Oct":"十月", "Nov":"十一月", "Dec":"十二月"} moment = asctime() def now(): print "今天是西元",moment[20:], "年", months[moment[4:7]], print moment[8:10], "號", weeks[moment[:3]] print "現在的時間是", moment[11:19] print def seconds(): now() print print "倒數十秒開始....." i = 10 while i > 0: print "時間還有", i, "秒", sleep(1) print "\r", i = i - 1 print " "*20, print print "十秒過後.....你看見消失的時間了嗎?" print "現在的時間是", asctime()[11:19] print
接者我們繼續完成menu.py,這時候我們所要做的事情就是把定義都引入menu.py之中,然後依上一章所完成骨架程式,視情況呼叫所需函數或其他定義,程式碼如下。
#《電腦做什麼事》的範例程式碼 http://pydoing.blogspot.com/ #-*- coding: UTF-8 -*- from sys import exit, platform, version from os import system from fibonacci import fi, print_fi from average import average from readfile import read from guess import guess from board4 import * from time import sleep, asctime from now import now, seconds def whatplatform(): if platform == "win32": print "您所使用的是MS-Windows作業系統。" elif platform == "linux2": print "您所使用的是Linux作業系統。" elif platform == "darwin": print "您所使用的是Mac作業系統。" else: print "程式未能偵測到您所使用的作業系統種類。" def clear(): if platform == "win32": return "cls" elif platform == "linux2" or "darwin": return "clear" else: return False def fun1(): try: system(clear()) print_fi() raw_input("請按<Enter>繼續。") except TypeError: print "系統並無清除螢幕的指令,程式仍繼續運作......." print_fi() raw_input("請按<Enter>繼續。") def fun2(): try: system(clear()) average() raw_input("請按<Enter>繼續。") except TypeError: print "系統並無清除螢幕的指令,程式仍繼續運作......." average() raw_input("請按<Enter>繼續。") def fun3(): try: system(clear()) read() raw_input("請按<Enter>繼續。") except TypeError: print "系統並無清除螢幕的指令,程式仍繼續運作......." read() raw_input("請按<Enter>繼續。") def fun4(): try: system(clear()) guess() raw_input("請按<Enter>繼續。") except TypeError: print "系統並無清除螢幕的指令,程式仍繼續運作......." guess() raw_input("請按<Enter>繼續。") def fun5(): try: system(clear()) run() raw_input("請按<Enter>繼續。") except TypeError: print "系統並無清除螢幕的指令,程式仍繼續運作......." run() raw_input("請按<Enter>繼續。") def main(): while True: try: system(clear()) print "(1) 計算費伯那西數列" print "(2) 計算平均值" print "(3) 讀取文字檔" print "(4) 猜數字遊戲" print "(5) 簡易鬥獸棋遊戲" c = raw_input("請輸入選擇,或按q離開程式: ") if c == "1": fun1() elif c == "2": fun2() elif c == "3": fun3() elif c == "4": fun4() elif c == "5": fun5() elif c == "q": exit() else: print "您所輸入的不是1或2或q。" raw_input("請按<Enter>繼續。") except TypeError: print "系統並無清除螢幕的指令,程式仍繼續運作......." print "(1) 計算費伯那西數列" print "(2) 計算平均值" print "(3) 讀取文字檔" print "(4) 猜數字遊戲" print "(5) 簡易鬥獸棋遊戲" c = raw_input("請輸入選擇,或按q離開程式: ") if c == "1": fun1() elif c == "2": fun2() elif c == "3": fun3() elif c == "4": fun4() elif c == "5": fun5() elif c == "q": exit() else: print "您所輸入的不是1或2或q。" raw_input("請按<Enter>繼續。") if __name__ == "__main__": whatplatform() print "您的Python版本為", version[:5] seconds() raw_input("請按<Enter>繼續。") main()
程式一開始,自然是印出目前時間。
倒數結束按下Enter,就進入了主選單。
輸入1,就可以計算費伯納西數。
按下Enter回主選單,輸入2,就可以計算平均值。
再一次按下Enter回主選單,輸入3,讀取純文字檔。
再一次按下Enter回主選單,輸入4,進行猜數字遊戲。
再一次按下Enter回主選單,輸入5,模擬簡化的鬥獸棋遊戲。
最後的結果是...
顯然,這次是「虎」贏得了最後的勝利。
下一步
這就是以使用者互動的模式運作,我們可以持續發展menu.py,精巧妥善的利用印出符號美化顯示,也可以多加入一些給使用者的訊息,因為一個好的應用程式介面,不僅僅容易讓使用者理解程式的功能,也便於利用程式。
然而這仍是在命令列模式底下,事實上我們日常所用的電腦都已經是圖形介面,圖形更給人直覺,容易了解。下一篇開始,我們介紹第三方模組庫中的Pygame,這是專門為遊戲所開發的模組庫,也是我們將會介紹的第一個圖形使用者介面。