1. 程式人生 > >海龜作圖—用Python 繪圖

海龜作圖—用Python 繪圖

原文地址:http://www.epubit.com.cn/book/onlinechapter/37788

在本章中,我們將編寫簡短的、簡單的程式來建立漂亮的、複雜的視覺效果。為了做到這一點,我們可以使用海龜作圖軟體。在海龜作圖中,我們可以編寫指令讓一個虛擬的(想象中的)海龜在螢幕上來回移動。這個海龜帶著一隻鋼筆,我們可以讓海龜無論移動到哪都使用這隻鋼筆來繪製線條。通過編寫程式碼,以各種很酷的模式移動海龜,我們可以繪製出令人驚奇的圖片。

使用海龜作圖,我們不僅能夠只用幾行程式碼就創建出令人印象深刻的視覺效果,而且還可以跟隨海龜看看每行程式碼如何影響到它的移動。這能夠幫助我們理解程式碼的邏輯。

2.1 第一個海龜程式

讓我們使用海龜作圖來編寫第一個程式。在一個新的IDLE視窗中輸入如下的程式碼並將其儲存為SquareSpiral1.py(你也可以通過http://www.nostarch. com/teachkids/下載該程式以及本書中的所有其他的程式)。

SquareSpiral1.py

# SquareSpiral1.py - Draws a square spiralimport turtle
t = turtle.Pen()for x in range(100):
   t.forward(x)
   t.left(90)

當執行這段程式碼的時候,我們會得到一幅漂亮整齊的圖片(如圖2-1所示)。

tyktc02_01.psd

圖2-1 用簡短的SquareSpiral1.py程式建立的一個炫目的正方形螺旋線

2.1.1 程式是如何工作的

讓我們一行一行地分析這個程式,看看它是如何工作的。SquareSpiral1.py的第1行是註釋。正如我們在第1章中所學過的,註釋以一個井號(#)開頭。註釋允許我們在程式中寫入給自己或以後可能閱讀該程式的其他人一些提示。計算機不會閱讀或試圖理解井號之後的任何內容;註釋只是讓我們寫出關於程式是做什麼的一些說明。在這個例子中,我們將程式的名稱以及針對其做什麼的一個簡單說明放入到註釋之中。

第2行匯入(import)了繪製海龜圖形的功能。匯入已經編寫過的程式碼,這是程式設計工作的最酷的事情之一。如果我們編寫了一些有趣並有用的程式,可以將其與其他的人分享,同時也可以自己重用它。儘管海龜作圖最初源自20世紀60年代的Logo程式語言

[1],但一些很酷的Python程式設計師構建了一個庫(library,庫就是可以重用的程式碼的一個集合),來幫助其他程式設計師在Python中使用海龜作圖。當我們輸入了import turtle,就表示我們的程式能夠使用那些Python程式設計師所編寫的程式碼。圖2-1中的小的黑色箭頭表示海龜,它在螢幕上移動的時候會使用鋼筆繪圖。

11486.png

程式的第3行是t = turtle.Pen(),它告訴計算機,我們將使用字母t表示海龜的鋼筆。這使得我們只需要錄入t.forward(),而不是turtle.Pen().forward(),就可以讓海龜在螢幕上移動的時候用海龜的鋼筆進行繪製。字母t是告訴海龜做什麼的一種快捷方式。

第4行最為複雜。在這裡,我們建立了一個迴圈(loop),它重複一組指令很多次(一次又一次地迴圈這些程式碼行)。這個特定的迴圈設定了一個範圍(range,或列表),其中擁有從0~99的100個數字(計算機幾乎總是從0開始計數,而不是像我們通常那樣從1開始)。在該迴圈中,字母x遍歷了範圍中的每一個數字。因此,x從0開始,然後變為1,然後是2,依次類推,直到99,一共100個步驟。

x叫作變數(variable)[2](在第1章中的YourName.py程式中,name就是變數)。變數儲存了在程式進行的過程中可以修改(變化)的一個值。我們在所編寫的幾乎每一個程式中,都要使用變數,因此,早點認識變數為好。

接下來的兩行程式碼縮進了,或者說,在左邊留出了空格。這意味著,它們位於該迴圈之中(in the loop)並且和上面的那一行程式碼一起,每次x從0~99的範圍中獲取一個新的數字的時候,這些程式碼行都會重複,直到達到100次。

2.1.2 發生了什麼

讓我們看看Python初次讀取這一組指令的時候發生了什麼。命令t.forward(x)讓海龜的鋼筆在螢幕上向前移動x個點。因為x是0,鋼筆根本不會移動。最後一行程式碼t.left(90)讓海龜向左轉90°,或者說轉四分之一個圈。

11511.png

由於這個for迴圈,程式繼續執行並且回到了迴圈的開始位置。計算機加1後將x移動到範圍中的下一個值,因為1仍然位於從0~99的範圍中,迴圈繼續。現在x是1,因此,鋼筆向前移動1個點。然後,鋼筆向左移動90個點,因為程式碼是t.left(90)。這樣一次一次地繼續執行,當x到達99,即迴圈的最後一次迭代,鋼筆圍繞著正方形螺旋線的外圍畫了一條長長的線條。

下面我們隨著x從0增加到100,將迴圈的每一步視覺化地表示出來。

for x in range(100):
   t.forward(x)
   t.left(90)

tyktc02_01a_cropped.psd迴圈0到4:繪製了前4條線(在x = 4之後)。

tyktc02_01b_cropped.psd迴圈5到8:繪製了另外4條線;正方形出現了。

tyktc02_01c_cropped.psd迴圈9到12:正方形螺旋線變為了12條線(3個正方形)。

計算機螢幕上的點或畫素可能太小了,以至於我們無法很好地看到它們。但是,隨著x變得越來越接近100,海龜繪製的線條包含了越來越多的畫素。換句話說,當x變得越來越大,t.forward(x)繪製的線條越來越長。螢幕上的海龜箭頭,繪製一會兒,然後向左轉,再繪製一會兒,再向左轉,這樣一次又一次地繪製,每次線條都變得越來越長。

最後,我們有了一個炫目的正方形形狀。連續4次向左轉90°,就可以得到一個正方形,就像是圍繞一棟建築連續4次左轉的話,將會帶著我們繞建築轉一圈並且回到起點一樣。

在這個示例中,我們之所以得到一個螺旋線,是因為每次左轉的時候,都走得更遠一點。繪製的第一個線條只是1步長(x = 1的時候),然後是2(迴圈的下一次迭代),然後是3,然後是4,以此類推,直到達到100步長,這時候,線條的長度為99畫素。再一次強調下,螢幕上的畫素可能太小了,以至於我們無法很容易地看到單個的點,但是,它們是存在的,而且我們會看到隨著程式包含更多的畫素,線條會變得越來越長。

通過完成所有的90°角的旋轉,我們得到了完美的正方形。

2.2 旋轉的海龜

讓我們看看當修改了程式中某一個數值的時候,會發生什麼?學習和程式相關的新知識的一種方法是,當我們修改其某一個部分的時候,看看發生了什麼。我們不會總是得到一個很好的結果,但是,即使是某些地方出錯的時候,我們也能學到東西。

我們只是將程式的最後一行修改為t.left(91),將其儲存為SquareSpiral2.py。

SquareSpiral2.py

import turtle
t = turtle.Pen()for x in range(100):
   t.forward(x)
   t.left(91)

我們提到了向左轉90°會建立一個完美的正方形。每次向左轉的比90°多一點點的話(在這個例子中,是91°),會將正方形略微向外丟擲一點點。由於我們進行下一次旋轉的時候,已經偏離了一點點,隨著程式繼續進行,新的圖形越來越不像是一個正方形。實際上,它建立了一個開始向左旋轉的、漂亮的螺旋形,就像是樓梯一樣,如圖2-2所示。

tyktc02_02.psd

圖2-2 正方形螺旋執行緒序略作修改後變成了一個螺旋形的樓梯

這也是一個漂亮的圖形,可以幫助我們理解如何只略微修改一個數字,就顯著地改變程式的結果。1°似乎並不是一個很大的偏差,除非我們偏離1° 100次(這加起來就是100°),或者1000次,或者,如果我們使用的是飛機著陸程式……

11534.png

如果還不知道度是如何工作的,現在先不要擔心,我們只要嘗試修改數字,看看發生了什麼就好了。我們通過修改range後面的圓括號中的值,讓程式繪製的線條數達到200或500,或者50。

我們再嘗試將最後一行的角度修改為91、46、61或121等。記住每次都儲存程式,然後,我們執行它,看看所做的修改會如何影響到程式的繪製。年齡大一點的讀者瞭解一些幾何知識,可能會根據不同的角度看到一些熟悉的形狀,甚至能夠在程式執行之前根據角度來預測出形狀。較小的讀者則只能夠感受修改帶來的變化,等他們某一天上了幾何課之後,可以再回頭來看這個練習。

2.3 海龜畫圓

說到幾何,海龜作圖可以繪製很多有趣的形狀,而不只是直線。我們將在2.4節中再次回到正方形,但現在,讓我們來更多地瞭解一下Python Turtle庫。

我們再來修改一行程式碼:t.forward(x)。我們在前面看到了這條命令或函式,它將海龜的鋼筆向前移動x個畫素並且繪製一條筆直的線段;然後,海龜轉向並且再次繪製。如果我們修改這行程式碼來繪製更為複雜一點的圖形,例如圓,那會怎麼樣呢?

好在,繪製一個固定大小(或半徑)的圓的命令,和繪製一條直線的命令一樣簡單。我們將t.forward(x)修改為t.circle(x),如下面的程式碼所示。

CircleSpiral1.py

import turtle
t = turtle.Pen()for x in range(100):
   t.circle(x) 
   t.left(91)

哦,將一條命令從t.forward修改為t.circle,會得到一個複雜得多的形狀,如圖2-3所示。t.circle(x)函式讓程式在當前位置繪製了一個半徑為x的圓。注意,這個繪製和簡單的正方形螺旋線有一些相同點:它也有4組圓形的螺旋線,就像是正方形的螺旋線有4個邊一樣。這是因為我們使用t.left(91)命令,每次向左旋轉都將超過90°一點點。如果我們學習過幾何就知道,圍繞一個點轉一圈有360°,就像是一個正方形有4個90°的角(4×90 = 360)。海龜通過每次圍繞圖形旋轉的比90°多一點點,從而繪製出這個螺旋線的形狀。

tyktc02_03.psd

圖2-3 只需在改動一點就得到一組漂亮的4個螺旋線的圓

我們將會看到的一個區別是,圓形螺旋線比正方形螺旋線要大一些,實際上,大約是前者兩倍那麼大。這是因為t.circle(x)使用x作為圓的半徑,而這是從圓心到邊緣的距離,大概是圓的寬度的一半。

半徑為x意味著,圓的直徑,也就是說總的寬度是x的兩倍。換句話說,t.circle(x)繪製的圓,當x等於1的時候,總寬度為2個畫素;當x為2的時候總寬度為4個畫素;按照這種方式,直到x等於99的時候,其寬度為198個畫素。這幾乎是200個畫素寬了,或者說是正方形邊最大的時候的兩倍,因此,圓螺旋線看上去是正方形螺旋線的兩倍的大小,當然,也會加倍的酷!

2.4 新增顏色

這些螺旋線的形狀不錯,但是,如果它們能夠更多彩一些,是不是更酷呢?讓我們回到正方形螺旋線程式碼,在t = turtle.Pen()這一行的後面再新增一行程式碼,從而將鋼筆顏色設定為紅色。

SquareSpiral3.py

import turtle
t = turtle.Pen()
t.pencolor(“red”)for x in range(100):
    t.forward(x)
    t.left(91)

執行該程式,我們將會看到正方形螺旋線的一個更多色彩的版本,如圖2-4所示。

tyktc02_04.psd

圖2-4 正方形螺旋線變得更多彩一些了

我們嘗試用另一種常用的顏色(如“blue”或“green”)來替換掉“red”或“green”並且再次執行該程式。我們可以通過Turtle庫使用數百種不同的顏色,包括一些奇怪的顏色,如“salmon”和“lemon chiffon”(訪問http://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm可以檢視完整的列表)。讓整個螺旋線呈現一種不同的顏色是很不錯的一步,但是,如果想要讓每一邊都顯示一種不同的顏色,我們該怎麼辦呢?這需要對程式做一些更多的修改。

2.4.1 一個四色螺旋線

讓我們來考慮一下演算法(algorithm)。演算法就是一系列的步驟,它可以將單色的螺旋線變為4色的螺旋線。大多數的步驟和之前的螺旋執行緒序中相同,但是,這裡還增加了一些調整:

(1)匯入turtle模組並且設定一個海龜;

(2)告訴計算機應該使用何種顏色;

(3)設定一個迴圈,繪製螺旋線中的100條線段;

(4)為螺旋線的每一邊選取一種不同的鋼筆顏色;

(5)向前移動海龜以繪製每一邊;

(6)將海龜向左轉,以準備好繪製下一邊。

首先,我們需要顏色名稱的一個列表,而不是單個的顏色,因此,我們要建立一個名為colors的列表變數並且在列表中放置4種顏色,如下所示。

colors =[“red”,yellow”,blue”,green”]

這個4種顏色的列表,將會針對正方形的每一邊給出一種顏色。注意,我們將顏色的列表放在了方括號“[”和“]”之間。這裡要確保引號中的每一種顏色名都像我們在第1章中打印出來的單詞一樣,因為這些顏色名都是字串(string)或文字值,這是我們稍後要傳遞給pencolor函式的值。正如前面所提到的,我們使用一個名為colors的變數來儲存4種顏色的列表。因此,任何時候,當想要從列表中獲取顏色的時候,我們都要使用colors變數來表示鋼筆的顏色。記住,變數儲存的值是變化的,這正如同其名稱一樣,變數嘛。

11558.png

我們需要做的下一件事情是,每次遍歷繪製迴圈的時候修改鋼筆顏色。為了做到這一點,我們需要將t.pencolor()函式移入到for迴圈下的一組指令之中,還需要告訴pencolor函式,我們想要使用列表中的哪一種顏色。

我們輸入如下的程式碼並執行它。

ColorSquareSpiral.py

import turtle
t = turtle.Pen()
colors =[“red”,yellow”,blue”,green”]for x in range(100):
    t.pencolor(colors[x%4])
    t.forward(x)
    t.left(91)

4種顏色的列表起作用了,我們在這個執行的示例中看到了它們(如圖2-5所示)。到目前為止,一切還不錯。

tyktc02_05.psd

圖2-5 正方形螺旋執行緒序的一個更加多彩的版本

pencolor函式中唯一的新增部分是(colors[x%4])。這條語句中的x和我們在程式中其他地方所使用的x是同一個變數,因此,x將持續從0~99增加,就像我們前面所見到的那樣。圓括號中的colors變數名告訴Python,從我們在程式前面所新增的、名為colors的顏色名稱列表中選取一種顏色。

[x%4]告訴Python我們將使用colors列表中的前4種顏色,即編號從0~3的顏色並且每當x變化的時候就遍歷它們。在這個例子中,我們的顏色列表只有4種顏色,因此,我們需要一次又一次地遍歷這4種顏色。

colors =[“red”,yellow”,blue”,green”]0123

[x%4]中的“%”叫作模除操作符(modulo operator),表示一次除法運算中的餘數(remainder)(5÷4商1餘1,因此,5可以包含4一次並且還剩下1;6÷4餘2,以此類推)。當我們想要遍歷列表中一定數目的項時,例如我們對4種顏色列表所做的操作,模除操作符很有用。

在100步中,colors[x%4]將遍歷4種顏色(0、1、2和3,分別表示紅色、黃色、藍色和綠色)整整25次。如果我們有時間(並且有一個放大鏡),可以數一數圖2-5中有25條紅色的、25條黃色的、25條藍色的和25條綠色的線段。第1次遍歷繪製迴圈的時候,Python使用列表中的第一種顏色,紅色;第2次遍歷的時候,它使用黃色,以此類推。第15次遍歷迴圈的時候,Python又回過頭來使用紅色,然後是黃色,等等;每通過迴圈4次之後,總是又回過頭來使用紅色。

11842.png

2.4.2 修改背景顏色

讓我們再次加入一點內容,創造出比圖2-5更漂亮一些的內容。正如我5歲的兒子Alex所指出來的那樣,黃色部分太難以識別出來了。這就像是在白色的繪畫紙上使用黃色的蠟筆一樣,螢幕上的黃色畫素無法在白色背景上明顯地顯示出來。讓我們把背景顏色修改為黑色,來修正這個問題。我們在程式中的import行之後的任何位置,輸入如下的程式碼行。

turtle.bgcolor(“black”)

新增這一行之後,圖片更加漂亮,所有的顏色現在都處在一個黑色的背景之上。注意,海龜鋼筆(在程式中由變數t表示)沒有任何變化。相反,我們修改了海龜螢幕的一些內容,也就是背景顏色。turtle.bgcolor()命令允許我們將整個繪製螢幕修改為Python中指定的任何顏色。在turtle.bgcolor(“black”)這一行中,我們選擇了黑色作為螢幕顏色,因此,紅色、黃色、藍色和綠色都顯示得很好。

此外,我們可以將迴圈中的range()修改為200甚至更大,以使得螺旋線中的正方形更大。在黑色背景上顯示200個線段的新版本的圖片,如圖2-6
所示。

Fig2-6.psd

圖2-6 螺旋執行緒序的路還很長(這是一個簡單的開始)

Alex總是想幫助我的程式變得更為驚人,他要求再做一項修改:如果現在把線段替換為圓,那會怎麼樣呢?那會不會是最酷的圖片呢?好吧,我必須承認,這甚至會更酷。完整的程式碼如下所示。

ColorCircleSpiral.py

import turtle
t = turtle.Pen()
turtle.bgcolor(“black”)
colors =[“red”,yellow”,blue”,green”]for x in range(100):
    t.pencolor(colors[x%4])
    t.circle(x)
    t.left(91)

我們可以在圖2-7中看到結果。

Fig2-7.psd

圖2-7 Alex的驚人的圓螺旋線— 一共8行程式碼,簡單而優雅

2.5 一個變數搞定一切

到目前為止,我們已經使用變數來修改顏色、大小以及螺旋線形狀的旋轉角度。讓我們再新增一個sides變數,來表示形狀的邊數。這個新的變數如何改變我們的螺旋線呢?如果要搞清楚這一點,我們嘗試這個新的程式ColorSpiral.py。

ColorSpiral.py

import turtle
t = turtle.Pen()
turtle.bgcolor(“black”)# You can choose between 2 and 6 sides for some cool shapes!
sides =6
colors =[“red”,yellow”,blue”,orange”,green”,purple”]for x in range(360):
    t.pencolor(colors[x%sides])
    t.forward(x *3/sides + x)
    t.left(360/sides +1)
    t.width(x*sides/200)

我們可以將sides的值從6改為2(1個邊並不是很有趣,也不能使用太大的數字,除非我們在程式的第6行中的列表中,新增更多的顏色),然後儲存該程式並且可以執行任意多次。圖2-8展示了用sides=6、sides=5,一直到sides=2所建立的影象,其中sides=2的影象很奇怪,這就是圖2-8(e)所顯示的扁平的螺旋線。我們可以改變列表中的顏色的順序,也可以在繪製迴圈之中的任意函式中,使用較大一些或較小一點的數字。如果把程式給搞亂了,我們只需要返回到最初的ColorSpiral.py程式重新來玩就好了。

f28a.psd

a)

f28b.psd

b)

f28c.psd

c)

f28d.psd

d)

f28e.psd

e)

圖2-8 通過把變數sides從6(a)修改為2(e)所建立的5種彩色的形狀

ColorSpiral.py程式使用了一條新的命令t.width(),它修改了海龜鋼筆的寬度。在我們的程式中,隨著鋼筆繪製的形狀越來越大,鋼筆變得越來越寬(其線條變得更粗)。在第3章和第4章,我們學習建立程式所需的其他技能的時候,還會再次遇到這個程式以及其他類似的程式。

2.6 本章小結

在本章中,我們使用Turtle庫的工具繪製了令人印象深刻的彩色形狀。我們使用import命令把這個庫匯入到自己的程式中,同時瞭解到,以這種方式來重用程式碼是程式設計的最強大的功能之一。一旦編寫了有用的內容,或者借用某些人慷慨分享的程式碼,我們不僅能夠節省時間,而且能夠使用這些匯入的程式碼做全新的事情。

我們還介紹了程式中像x和sides這樣的變數。這些變數儲存或記住一個數字或值,以便我們能夠在程式中多次使用它,甚至修改其值。在第3章中,我們將學習變數的作用以及Python如何能夠幫助你完成數學作業。

現在,我們應該能夠做如下這些事情:

  • 用Turtle庫繪製簡單的圖形;

  • 使用變數來儲存簡單的數值和字串;

  • 在IDLE中修改、儲存和執行程式。

2.7 程式設計挑戰

#1:修改邊數

在ColorSpiral.py程式中,我們使用了一個變數sides,但是我們並沒有改變它或修改其值,只是再次編輯、儲存和執行程式。我們嘗試將sides的值改為另一個數字,例如5,儲存並執行程式,看看這會對繪製有何影響;現在,試一試4、3、2甚至是1。現在,我們在程式的第6行,向顏色列表中新增兩種或更多的顏色,顏色名用引號括起來,用逗號隔開。我們可以增加sides的值,來使用這些新的顏色,嘗試一下8或者10甚至更大。

#2:有多少邊

如果想要在程式執行的時候由使用者來決定邊數,我們該怎麼做呢?使用我們在第1章中學習的內容,可以讓使用者輸入邊數並且將其儲存到sides變數中。唯一額外的步驟是,計算(evaluate)使用者所輸入的數字。我們可以使用eval()函式得到使用者輸入的數字,如下所示。

sides =eval(input(“Enter a number of sides between 2and6:“))

我們使用前面這一行,替換掉ColorSpiral.py中的sides = 6這一行。新的程式將會問使用者想要看到有多少個邊。然後,程式將繪製使用者所要求的形狀。嘗試一下!

#3:橡皮筋球體

我們嘗試將ColorSpiral.py程式修改為一個更大的角度,而且通過在繪製迴圈的末尾新增一個額外的轉向來扭曲形狀。我們在for迴圈的末尾新增諸如t.left(90)的一行,使得角度更加尖銳(記住縮排,或者說留下空格,以保證該語句位於迴圈之中)。結果如圖2-9所示,看上去像是一個幾何玩具,或者是用彩色的橡皮筋製作的球體。

tyktc02_09.psd

圖2-9 在ColorSpiral.py程式的每一輪迴圈中新增一個額外的90°將其變為RubberBandBall.py程式

我們把這個新的版本儲存為RubberBandBall.py,或者訪問http://www.nostarch.com/teachkids/並且在Chapter2的原始碼中找到該程式。

[1] Logo程式語言創建於1967年,這是一種教育程式語言,在50年之後的今天,它仍然用來教授基本的程式設計。這很酷,是不是?

[2] 小讀者可能會把x當作未知數,就像當他們求解x + 4 = 6以求得未知的x一樣。年齡大一點的讀者可能會通過代數課或其他的數學課程認識x,早期的程式設計師正是從代數和數學中借用了變數的概念。編寫程式碼的過程中會有很多數學的典型例子,我們甚至會在後面見到一些很酷的幾何示例。