程式的發展與軟體的構築在今日都以經成為專門學問,有許多不同的見解與主張,也有許多的方法論,甚至由方法論擴展不同程式語言的設計哲學。我們的選擇是Python,原因不外乎是規劃、測試與除錯的程式發展流程更為簡便,同時Python也提供了例外處理機制。
然而程式可以不僅僅是去做我們要程式做的事情,針對許多未知的情況,由以往的經驗與累積的資料,經過計算我們可以預測可能發生的結果,這就是電腦模擬。
例如對颱風路徑的推測,需要收集地面及高空的氣壓、溫度、溼度、風速等等資料,匯集製作圖表,也依往年的氣象資料與颱風的實際路徑,然後統合所有的資料,藉由電腦快速的計算能力,模擬出可能的情況。
商業上也廣泛利用電腦模擬,譬如保險公司要推出一項產品,訴求客戶、理賠事項、保費收取等等,在推出之前都經過嚴密的試算,當然,保險公司以賺錢為主要目標,同時也要讓消費者感到有所保障,製造雙贏的局面。
實務上的電腦模擬有很多種,常見的試算表軟體Excel的目的就是模擬試算之用。其實電腦遊戲也是利用模擬的方式與使用者互動,使用者的操作符合這種情境,電腦也隨即做出相對的反應。
假設我們要設計球類的遊戲,我們就需要寫出模擬球移動的程式,如下圖假設球移動到視窗的邊緣會以隨機的角度彈回。

嗯,我們還沒正式介紹Python的圖形使用者介面。Pygame是Python關於遊戲設計的第三方模組庫,我們在下一篇中才會正式介紹,雖說如此,我們已經寫了個簡易的鬥獸棋遊戲。現在,來模擬看看吧!
鬥獸棋的模擬
因為鬥獸棋需要使用者輸入要操作的棋子以及方向,棋子共有四種,方向也有四種,假設是隨機的進行過程,那麼我們可以借用random模組中的randint()函數,以1及4為參數,藉此隨機產生數字1、2、3、4中的任一個。
但是輸入的都必須是字母,因此我們先用字典設定字母與數字間的對應。
1 2 3 4 5 6 7 | #《電腦做什麼事》的範例程式碼 #動物棋子對應的編號 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函數。
1 2 3 4 5 | #《電腦做什麼事》的範例程式碼 #隨機選取一隻動物與一個方向 handle(animal[randint( 1 , 4 )], direction[randint( 1 , 4 )]) |
因為不在要求使用者輸入,所以我們再把以下兩行刪除。
1 2 3 4 5 6 | #《電腦做什麼事》的範例程式碼 #需要刪除的兩行程式碼........ print "操作「象」鍵入e,「虎」鍵入t,「貓」鍵入c,「鼠」鍵入m。" print "往「上」鍵入u,往「下」鍵入d,往「左」鍵入l,往「右」鍵入r。" |
然後把handle()函數中的“請輸入正確的方向!!”改為“不可能的情況!!”,而“請輸入正確的棋子!!”改為“該棋子已不存在!!”。同時我們利用系統清除螢幕的指令,在程式中加進上一章寫過的clear()函數,然後放在遊戲迴圈的開頭。
每一輪迴我們再借用time模組中的sleep()函數,放到遊戲迴圈的結束,讓整個畫面顯示一秒,好讓我們目睹該輪迴做了什麼事情。所有改寫過的程式碼摘錄如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | #《電腦做什麼事》的範例程式碼 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中,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #《電腦做什麼事》的範例程式碼 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 "輸入的並非整數!!" |
因為沒有打算讓這個模組成為可執行程式,所以沒有
的部份。
1234
第二項計算整數平均值儲存在average.py之中,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #《電腦做什麼事》的範例程式碼 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之中,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #《電腦做什麼事》的範例程式碼 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,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #《電腦做什麼事》的範例程式碼 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之中,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #《電腦做什麼事》的範例程式碼 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之中,然後依上一章所完成骨架程式,視情況呼叫所需函數或其他定義,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | #《電腦做什麼事》的範例程式碼 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,這是專門為遊戲所開發的模組庫,也是我們將會介紹的第一個圖形使用者介面。