後知後覺發現了繪圖等級考試 40 關的圖。
如何看待這些圖?
代碼實現
版本 1
版本 2
平移並旋轉
平移
正多邊形
正方形函數的泛化
旋轉 2 :不規則圖形的旋轉
對稱和旋轉
總結
我拿到的這個圖並沒有粗細、顏色、填充的變化,所以我不考慮這部分內容。
logo 語言本身並不是作為專門的繪圖庫開發的,而是希望為利用計算機學習提供一個可以探索的環境,作為繼承者的 turtle 也不是專門為了繪圖開發的,雖然可以繪製各種幾何圖形。
利用 turtle 繪圖,更多的是從運動的觀點描述圖形,比如繪製正方形一般是前進特定距離,旋轉 90°,重複四次得到正方形,需要學生理解正方形四條邊相等、每個角都是 90° 的概念。我們也可以用另外的方式描述正方形:
這三種觀點對於圖形的認識相對而言比重複四次前進、旋轉的方法更加抽象。某種程度上, turtle 的設計初衷與編程基礎的學習,是不完全重合的。從繪圖的角度,我們既可以以運動的觀點來看到這 40 幅圖,也可以以靜止的觀點來描述。這取決於你面對的那種類型的初學者。
小學數學中涉及到了方格中的圖形變換(平移、旋轉、對稱、縮放),這是相對基礎的變換。就 turtle 本身而言,並沒有一個足夠的工具來描述圖形的變換。
比如,在 turtle 中繪製兩個正方形很容易,但是要表達右側圖形是左側圖形平移之後的結果,就比較困難。scratch 亦然。
但是 processing 及其衍生品中是有這樣的工具的,比如 pushMatrix 和 popMatrix ,用這個工具,可以非常簡單的對簡單圖形、複合圖形進行變換。
rect(0, 0, 100, 100);
pushMatrix();
translate(200, 0); // 表示水平方向平移 200 像素
popMatrix();分析圖
processing 作為專業的繪圖庫、語言提供了豐富的繪圖功能和開箱即用的工具,降低了繪圖的門檻。當我看到這 40 張圖的時候,我首先想到的就是用 processing 的思維方式來繪製。這就需要把 processing 提供的工具,有限的遷移到 turtle 中,簡單的說就是對 turtle 進行二次開發,這麼說有點嚇人,或者說在 turtle 的基礎上定義一些函數,對 processing 的繪圖方式進行模仿,如果是用圖形化程式語言繪製,同樣的思路,圖形化程式語言,可以考慮編程貓的源碼編輯器,相比於 scratch ,提供了更加友好的編程調試體驗,可以在積木塊附近直接給出錯誤提示,降低了代碼調試的難度,避免學生限於錯誤中,找不到解決方案,從而產生沮喪的心理。即便是我自己,寫程序中卡住,也是一個相對負面的體驗。基本圖形
從基本圖形和圖形變換的角度來考慮這些圖形。
基本圖形:矩形(正方形),正多邊形(正三角形、四邊形、N 邊型)
圖形變換:旋轉。
複雜一點的圖形可能涉及到多種基本圖形的旋轉。
那麼我們可以定義函數完成這些任務,這就涉及到函數(子程序、自定義命令/積木)的知識。其實自定義函數(命令)很容易理解。劍術中最基本的動作是持劍,然後是基本劍術動作(劈、砍、刺、撩、雲),基本動作可以組合成招式(招包含式,比如推窗望月等),然後是套路,不同的套路其實可能包含相同的招式,前後左右有不同。API 或者 第三方庫也是如此。
在 turtle 和 scratch 中,其實可以按照如下對圖形進行分類:代碼實現版本 1
多邊形可以看做是一些列點連接起來構成的圖形,對於正多邊形,也可以看做小烏龜或者角色前進、旋轉的重複。
turtle 中並未單獨定義 line 函數,移動函數,我們需要自行定義。
上面的這些圖中,涉及的變化,其實只有一種旋轉。只要找到基礎圖形,就可以繪製出這些圖形了。只要我們堅持每次繪製完基礎圖形(我們可以稱之為圖元),回到最初始的狀態,就可以用利用 lt 和 rt 實現圖形的旋轉,不需要單獨定義 pushMatrix (因為難,哈哈哈哈)。所以 40 圖中,後面的一些圖,本質上,是同一種圖,不過是基礎圖元的不同而已,這個時候考察的就不是編程的技巧了,而是觀察能力,和找出圖形中的基本圖形的能力。
而且 40 圖的設計,前面的幾個圖形,後面是會用到的。34 其實跟 5 有一點差別,但是類似。
自己定義函數的過程,是考察對於將基礎命令組合成複雜命令也就是命令抽象、過程的抽象,只要能定義出來,這些圖就可以繪製了。當然可以定義一個長方形函數 rect 正方形函數 square ,但是這些只是語法糖,本質上,在 square 內部可以調用 rect 完成繪製,在 rect 內部則調用 polyon (繪製多邊形),這個涉及到 泛化 的概念,從 square 到 rect 功能更加廣泛,所以可以看做是功能的泛化,就像 python 中並沒有平方函數,有的只是 math 模塊中的 pow 函數, pow 函數第二個參數是 2 就是平方。from turtle import *
speed(0)
def move(x, y=None):
"""
移動畫筆
40 圖中其實用不太到
"""
if not y and len(x) == 2:
x, y = x
pu()
goto(x, y)
def line_to(x, y):
"""
繪製多邊形邊用到
"""
pd()
goto(x, y)
def polyon(points: list):
start_point = points[0]
move(start_point)
points.append(start_point)
for point in points:
if len(point) != 2:
raise('坐標需要有兩個參數')
x, y = point
line_to(x, y)
def rect(x, y, w, h):
"""
效果等同於
for i in range(2):
fd(w)
rt(90)
fd(h)
rt(90)
給學生講,這種方法更簡單
"""
points = []
points.append((x, y))
points.append((x+w, y))
points.append((x+w, y-h))
points.append((x, y-h))
polyon(points)
def square(x, y, w):
rect(x, y, w, w)
square(0, 0, 100)
rect(-100, 100, 200, 200)
done()版本 2
這個版本實現了矩形繪製的泛化,但是需要指定左上角頂點和邊長,圖形旋轉的繪製對於學生會困難,因為無法計算矩形頂點坐標,所以這樣不合適。from turtle import *
speed(0)
def move(x, y=None):
"""
移動畫筆
40 圖中其實用不太到
"""
if not y and type(x) == 'tuple':
x, y = x
pu()
goto(x, y)
def line_to(x, y):
"""
繪製多邊形邊用到
"""
pd()
goto(x, y)
def regular_polyon(start_point, side_length, sides: int):
"""
start_points: 起點
sides: 邊數
"""
move(start_point)
pd()
for i in range(sides):
fd(side_length)
lt(360//sides)
def rect(x, y, w, h):
move(x, y)
pd()
for i in range(2):
fd(w)
lt(90)
fd(h)
lt(90)
def square(x, y, w):
# rect(x, y, w, w)
regular_polyon((x, y), w, 4)
def flower(count=3, func=None, *args):
"""
繪製繁花,無需給學生講,可以直接繪製
"""
if not func:
return
for i in range(count):
func(*args)
rt(360//count)
square(20, 0, 100)
rect(-100, 100, 200, 200)
flower(3, square, 100, 0, 100)
flower(6, rect, 0, -100, 20, 100)
done()
def flower(count=3, func=None, *args):
"""
繪製繁花,無需給學生講,可以直接繪製
"""
if not func:
return
for i in range(count):
func(*args)
rt(360//count)*args 的作用,是記錄關鍵字參數之後的所有參數,並原封不動的作為繪圖函數 func 的參數。這樣 flower 函數就泛化成了,繪製任意瓣數任意圖形的繁花函數,將函數作為參數的函數是高階函數,將命令作為參數,當然 scratch 和 源碼編輯器 都是不支持把函數作為參數的,但是,可以定義繪製特定繁花圖案的函數。或者把函數體拿出來就好了。
這裡 func 就是繪製基本圖元的函數了。
對於圖形 20,繪製的代碼就是:# Figura 20
flower(5, square, 0, 0, 200)這裡很多圖案,在 code.org 中的繪圖課程中都有所涉及的。
# Figura 30
flower(6, regular_polyon, (0, 0), 90, 6)
反而 Figura 14 需要動動腦筋,我畫的每個邊生成正三角形,原圖是直角三角形。
side_length = 210
third = side_length // 3
angles = [0, 45, -90, 45]
for i in range(4):
for angle in angles:
lt(angle)
fd(third)
rt(90)這個圖形其實跟科赫雪花的繪製是類似的。
Figura 14
from math import pow
speed(1)
def figura_14(w):
lt(45)
l = w * pow(2, 0.5) / 2
# l = w * 0.7
pu()
fd(l)
pd()
rt(135)
# fd(w)
third = w//3
angles = [0, 45, -90, 45]
for angle in angles:
lt(angle)
fd(third)
rt(135)
pu()
fd(l)
pd()
rt(135)
# figura_14(120)
flower(4, figura_14, 120)平移並旋轉
Figura 14 也可以像上面那麼畫,不過相對來說,代碼複雜了很多。image.png平移Figura 8
兩個正方形的平移。
square(0, 0, 200)
square(-100, 100, 200)可以看到,在前面定義的 API 的基礎上,繪製 40 圖中的圖案變得簡單了許多。
# Figura 6
square(0, 0, 200)
square(20, -20, 160)image.png正多邊形
Figura 1/9/10/11/12/13/15 都是正多邊形,用 regular_polyon 函數繪製即可。
正方形函數的泛化
其中 Figura 13 是圓,也可以用 turtle 庫的 circle 函數, circle 函數的實現其實也是用正多邊形模擬圓。
前文提到過,給定中心點和邊長可以界定矩形。processing 中,繪製矩形有 CORNER 模式和 CENTER 模式,對於 Figura 6 來說,屬於中心對稱圖形,所以我們也可以用正方形的中心點模式繪製。def square(x, y, w, mode='conner'):
# rect(x, y, w, w) square 也可以基於 rect 函數實現
if mode == 'center':
x -= w/2
y += w/2
regular_polyon((x, y), w, 4)
square(0, 0, 400, 'center')
square(0, 0, 420, 'center')當然了,泛化的 square 函數的實現其實並不完善,但是作為 demo 使用足夠了。Figura 7 在 processing 中是可以用 CENTER 模式繪製的,背後的實現略複雜,這裡不寫了。
旋轉 2 :不規則圖形的旋轉
像 Figura 22 看上去很複雜,其實調用 flower 函數可以直接繪製。image.png
不規則圖形的旋轉,可以解釋為,flash 中的元件圍繞不同的中心點旋轉,Scratch 也有中心點的概念,涉及到這種圖形,只要在 Scratch 中設計正確的中心點,用 Scratch 中的旋轉 + 印章就可以繪製了。turtle 中也有 stamp函數,只要把烏龜的形狀設置為不規則圖形就可以了,這是另外一種系統的畫法了,代碼實現起來比用 regular_polyon 簡單的多,需要理解 turtle 的 addShape 函數,這需要單獨另外寫一篇文章了。
# 圖 4
def figura_4(x=0, y=0, side=80):
"""
需要注意的是 x, y 是繪製的起點
也就是 flash 和 Scratch 中的
中心點
"""
move(x, y)
pd()
fd(side)
lt(90)
fd(side/8)
lt(90)
fd(side-side/8)
rt(90)
fd(side-side*2/8)
rt(90)
fd(side-side/8)
lt(90)
fd(side/8)
lt(90)
fd(side)
lt(90)
fd(side)
lt(90)
注意 figura_4 的函數定義中使用了默認值參數,這樣在不指定尺寸的時候,調用 flower 函數的代碼中更加簡潔。
# 在圖 4 的基礎上繪製圖 18
flower(4, figura_4)image.png
from random import randint
for i in range(-20, 20, 1):
SIZE = 32
x = i // 4 * SIZE * 2 + SIZE / 2
y = i % 4 * SIZE * 2 + SIZE / 2
flower(4, figura_4, x, y, SIZE)image.png
from random import randint
for i in range(-20, 20, 1):
SIZE = 32
angle = randint(0, 360)
rt(angle)
x = i // 4 * SIZE * 2 + SIZE / 2
y = i % 4 * SIZE * 2 + SIZE / 2
flower(4, figura_4, x, y, SIZE)image.png
from random import randint
for i in range(-20, 20, 1):
SIZE = randint(8, 32)
angle = randint(0, 360)
rt(angle)
x = i // 4 * SIZE * 2 + SIZE / 2
y = i % 4 * SIZE * 2 + SIZE / 2
flower(4, figura_4, x, y, SIZE)由於定義了 flower 函數,所以,其他圖形也可以用這種方式繪製。
對稱和旋轉
這個圖跟圖 4 是有關聯的。總結
圖 3 到 圖 37 有所縮放,並且中心點也不同,所以有點不是很好理解。雖然 turtle 提供了切變函數,但是對於小學生來說並不好理解。
圖 27 其實也是 圖 5 變化而來,只不過繪製的起始點不同。** 小烏龜從哪個點開始繪製,哪個點就是中心點** 。不想寫了。。。。
圖 40 前面的幾個簡單的圖是後面圖的基本圖形(圖元)可以參考 processing 自定義繪製圖形的函數,再次基礎上定義高階函數 flower 簡化由旋轉得來的圖形的繪製繪製基本圖形的函數可以從不同的點開始畫,並旋轉不同次數,得到不同的圖案其實原本的主旨是函數的定義,抽象,用簡單的命令構建複雜的系統。比如我在 fd 等基礎上構建了 square ,在 square 的基礎上構建了 flower,增強了 turtle 的表達能力,從而實現一行代碼繪製由基本圖形旋轉得到的圖案繪製這些圖形並不需要複雜的 python 基礎知識
反正就是: