如果你首次接觸程式設計,或者想對程式設計經驗為零的人進行教學,多半會接觸或介紹海龜繪圖法。因為它不需要懂任何的程式設計知識,只要重複地對海龜發送前進、轉彎等指令,就可以在電腦上繪製出複雜而美麗的圖樣。
電腦繪圖實際上還需要數學,然而,海龜卻必須不懂數學,試著實作一隻小海龜的話,你就會明白這是怎麼一回事。
程式初學者的海龜繪圖
在人人學程式、從小學程式的風潮中,有著不少的程式入門管道,其中有些管道就看得到海龜繪圖,像是在一小時的程式設計(Hour of Code)中,為了吸引小朋友學程式而設計的〈Code with Anna and Elsa〉,就是海龜繪圖。
而這當中想要傳達的主要概念是,複雜往往來自簡單的規則,而觀察並尋找模式這種能力的培養,並不需要瞭解複雜的程式指令。
想要觀察並尋找模式,最具體的方式就是透過圖樣,海龜繪圖就是想像有一隻海龜在沙灘上爬行,爬過的路線會留下痕跡,因此只要命令海龜前進、轉彎就可以了,程式碼方面的細節必須被隱藏起來,不然就會嚇跑想跟Elsa玩的小孩。
實際上,海龜繪圖最初在1960年代時,就是為了讓一個像小海龜的機械裝置,可以在地板上前進、轉彎,而發展出來,然而,不是每個人都會有這隻機械海龜。
不過,後來個人電腦普及了,因此,這隻小海龜就從地板移民到電腦螢幕上了。談到海龜繪圖,就得談到第一個內置海龜繪圖實作,也是當初用來操作機械海龜的Logo程式語言了。
如果有一定程式設計經驗的,或許對這種教育用程式語言不感興趣,但為了讓你更集中注意力一些,這邊必須揭露一件事實,Logo的原型是來自於Lisp,繼承了list的概念,就像是沒有括號的Lisp(如果沒聽過Lisp,請參考我先前專欄〈可程式的程式語言〉)。
這隻小海龜可以這麼嗆辣而受歡迎,其實在於只要讓它專心地做些看似無腦的事,就可以畫出美麗的圖案,像是前進個10公分,隨意轉個136度之類的,重複個幾次,就會出現美麗的多角星。
你也可以只拿隻筆來當成海龜進行畫圖,只不過可能重複個幾次,就會感到厭煩了,而重複這種是電腦擅長的,我們可以學習程式設計,來讓電腦愉快地做這件事。
實作沙灘上的海龜
如果你用過任何程式語言內建的繪圖程式庫,會知道就算想要畫一條線,至少須懂得繪圖時的座標系統、基本的繪圖物件初始化作法,以及如何呼叫畫線函式等。
而使用海龜繪圖,可以讓程式初學者在不懂數學,連繪圖程式庫也不用瞭解的情況下,只要利用程式語言中基本的重複結構,就能得到立即的圖樣回饋。就這方面來說,海龜繪圖其實是封裝的絕佳範例,將一切隱藏在海龜體內。
如果想實作一隻在沙灘上移動的海龜,海龜體內須藏有目前位置的座標,以及目前相對於直角座標中某個軸的角度(通常是X軸),這樣才能知道海龜的頭朝向哪個方向。
而如果海龜轉彎了,那就是目前角度加上轉彎角度,以作為海龜轉彎後的新角度。
如果海龜移動L長度,新座標可以用基本的三角函式來計算,這並不難,X方向移動的長度,會是L乘上目前角度的餘弦值,Y方向移動長度,會是L乘上目前角度的正弦值,兩個值各與目前x、y座標值相加,就好了。
為了能在移動過的路徑上畫線,移動前,要記錄目前座標值,接著,取得移動後的座標值,然後,用這兩個座標值呼叫繪製直線的函式。
無論海龜要畫的圖案有多複雜,基本上,就是重複地運用以上的流程及數學運算了。
如果想要體驗一下,Python從2.5版之後就內建了turtle模組,想要畫個三角形的話,基本上只要使用以下的程式碼,完全不用理會座標,也不用親自進行三角函式運算,更沒有繪圖程式庫的初始化,這一切都被封裝起來:
from turtle import * for _ in range(3): forward(10) left(120)
實作海洋中的海龜
Python的turtle模組中,forward的別名是fd,而left的別名是lt,實際上,模組中的部分,就是Logo程式語言中海龜繪圖的移植版本;同樣的三角形,在Logo中,可以用repeat 3[fd 10 rt 120]來繪製。
當然,Python中還實作了更多簡便的函式,想要瞭解海龜繪圖如何實作,turtle模組的原始碼是個不錯的參考,不過,若想要實作的是海洋中悠游的海龜呢?
海洋中悠游的海龜,就不再是在二維平面,而是在三維空間中移動了。
若瞭解2D的海龜繪圖,在實作3D海龜繪圖時,很容易產生一個簡單的構想,也就是除了左右轉動的角度之外,再多記錄一個海龜抬頭的角度(與XY平面的夾角),只不過若海龜想用這種方式,它必須懂點數學,這樣一來,在想要游個與海底夾30度的三角形平面時,才能算出自己在向前游一段長度後,接下來,必須轉125.264度,才能達成目標,但實際上,海龜不懂數學,因此它算不出這個角度。
此時,我們必須改用向量,來思考海龜繪圖的實作!如果海龜往前移動長度為1,X方向移動長度,就是1在X軸上的分量,而Y方向長度,是1在Y軸的分量。如果海龜移動長度為L,那麼X與Y方向上的分量,就是移動1後在X軸與Y軸的分量各乘上L;實際上,1在X軸與Y軸的分量值,就是海龜往前移動長度為1時的單位向量表示,而海龜轉向時,新的單位向量必須套用繞自身z軸轉動的公式來求得。
如果你稍微計算一下就會發現,其實,2D的海龜繪圖實作時所使用的公式,就只是向量觀念的簡化罷了。
類似地,如果想實作3D海龜繪圖,可以記錄海龜自身三個軸的單位向量表示,當海龜需要左右轉、上下抬頭或翻身時,就是分別在這三個軸的單位向量上,套用繞自身z軸、y軸與x軸的轉動公式,來求得新的單位向量,如果海龜向前移動長度為L,那麼X、Y、Z方向上的分量,就是海龜本身三個軸的單位向量各乘上L,接著分別與海龜目前的座標相加,就會是移動後的新座標了。
當一隻好用的海龜
站在不懂程式的海龜角度來看,要使用程式碼來實作單位向量、繞軸旋轉的座標計算,看來是有點複雜呢?
然而,每次海龜的某個動作,都會是某種重複的計算流程,只要將這些流程定義完成,再複雜的圖案,就可以重複地運用這些定義來構造。
進一步地,若站在3D海龜實作者的角度來看,在使用程式碼實作向量運算、繞軸轉動之類的數學公式,不也就是在重複地運用數學方面的模式嗎?
想想看,若想用數學公式一次地描述3D樹木曲線,會是多麼困難的一件事。因此,若將3D樹木曲線分解為前進、旋轉等動作,而這些動作又分解為向量、繞軸座標計算等計算,接下來就可以用簡單的規則來描述3D樹木曲線了。
2D海龜繪圖,對大多數程式人來說應該很簡單,試著實現看看3D海龜繪圖吧!(有興趣可以參考我的實作https://goo.gl/5djc6N)除了能在實作的過程中,認識與應用一些數學之外,更重要的是記得:使用程式庫的人其實就是海龜,而海龜不懂數學,這樣你寫出來的海龜,才會是一隻好用的海龜。