海龟绘图是一种通过给定相对于海龟当前位置的简单命令(如左转、右转和前进)来绘制线条的技术。这些简单的命令可以作为绘制复杂形状(如螺旋和分形)的构建块。F#的交互式提示提供了一种有用的手段来控制海龟。本文将解释如何使用海龟绘图程序,并展示实现它的F#和WPF代码,并提供一些使用L-系统文法生成分形形状的示例代码。
假设正在使用Visual Studio中的F#交互式,无论是Visual Studio 2010还是免费的外壳。关于安装F#的信息可以在找到。打开文件FSharpTurtle.fsx
,使用Ctrl-A选择所有内容,然后按Alt-Enter将脚本加载到F#交互式中。一个带有海龟的画布将会出现。
海龟响应命令left
、right
、forward
和move
或者它们的缩写lt
、rt
、fd
和mv
。每个命令后面跟着一个浮点数和两个分号(例如:left 90.0;;
)。left
和right
命令使海龟转动给定的角度,forward
绘制线条,而move
移动海龟而不绘制线条。输入clear();;
清除画布并将海龟返回到原始位置。可以使用settings
变量更改默认颜色(白色背景,黑色线条,红色箭头)。F#使用<-
运算符来更改变量的值。
settings.BackgroundColour <- Brushes.Black;;
settings.ArrowColour <- Brushes.Green;;
settings.LineColour <- Brushes.Red;;
在F#交互式中输入以下命令会在画布上绘制一个正方形。
for i = 1 to 4 do
forward 200.0
left 90.0;;
海龟绘制一条线并左转,直到它返回到原始位置。
F#中的函数声明和递归函数调用允许创建复杂的螺旋图案。以下是从《海龟几何学》(第18-19页)改编的螺旋图案绘制函数。
let rec polyspi (angle:float)(inc:float)(side:float)(times:int) =
if times > 0 then
forward side
right angle
polyspi angle inc (side + inc)(times - 1)
else
None |> ignore
调用这个函数并使用不同的参数可以创建不同形状的螺旋,因此可以使用偏函数应用(也称为柯里化)创建更专业的polyspi函数版本。下面声明的函数已经预填充了前三个polyspi参数,并且只接受最后一个参数(times:int)作为参数。
let polyspi90 = polyspi 90.5 length
let polyspi95 = polyspi 95.1 length
let polyspi117 = polyspi 117.3 length
以下称为记录的类型定义用于存储海龟的状态。海龟在画布上有一个x y点和一个角度,由记录标签P
of type Point
和Angle
of type float表示。关键字mutable
用于指定变量的值可以更改。
type Point = {
mutable X : float
mutable Y : float}
type Turtle = {
mutable P : Point
mutable Angle : float }
下面的代码构建了海龟记录,并将其放在窗口的中间。类型Turtle
可以从记录标签推断出来,因此不需要指定。
let turtle = {P = {X = w.ActualWidth/2.;Y = w.ActualHeight/2.}; Angle = 0.}
海龟命令使用从起点的方向和距离来绘制线条。程序使用三角学来找到线条终点的xy坐标。下面定义的nextPoint
函数接受forward
给出的距离,并将其视为直角三角形的斜边。这与海龟角度结合使用,以找到三角形的邻边(x)和对边(y)的长度,并将它们加到起点上,给出线条的终点。
let nextPoint hypotenuse =
let newX = turtle.P.X + hypotenuse * Math.Cos(degreesRadians(turtle.Angle))
let newY = turtle.P.Y + hypotenuse * Math.Sin(degreesRadians(turtle.Angle))
let newPoint = {X = newX;Y = newY}
newPoint
海龟图形是在WPF画布上绘制的。WPF画布通常不是可滚动的,但在这里找到了一些C#代码,用于创建可滚动的Canvas子类,并将其重写为F#。下面的代码创建了一个窗口、一个可滚动的画布、一个滚动查看器和一个代表海龟的箭头。
let w = new Window(Topmost=true)
w.Show()
let c = new ScrollableCanvas()
c.Background <- Brushes.White
let scrollViewer = new ScrollViewer()
scrollViewer.HorizontalScrollBarVisibility <- ScrollBarVisibility.Auto
scrollViewer.VerticalScrollBarVisibility <- ScrollBarVisibility.Auto
w.Content <- scrollViewer
scrollViewer.Content <- c
let makeArrow() =
let arrow = new Polygon()
arrow.Fill <- Brushes.Red
let p1 = new System.Windows.Point(0.,20.)
let p2 = new System.Windows.Point(25.,10.)
let p3 = new System.Windows.Point(0.,0.)
let centrePoint = new System.Windows.Point(0.5,0.5)
let pCollection = new PointCollection()
pCollection.Add p1
pCollection.Add p2
pCollection.Add p3
arrow.RenderTransformOrigin <- centrePoint
arrow.Points <- pCollection
arrow
let mutable arrow = makeArrow()
c.Children.Add(arrow)
下面定义的left
和right
函数改变了海龟的角度,然后使用它来旋转箭头多边形。
let left deg = turtle.Angle <- turtle.Angle - deg
arrow.RenderTransform <- new RotateTransform(Angle = turtle.Angle)
let right deg = turtle.Angle <- turtle.Angle + deg
arrow.RenderTransform <- new RotateTransform(Angle = turtle.Angle)
moveTo
函数将箭头移动到WPF画布上给定的xy坐标。
let moveTo x y = turtle.P.X <- x
turtle.P.Y <- y
Canvas.SetLeft(arrow, turtle.P.X - 12.5)
Canvas.SetTop(arrow, turtle.P.Y - 10.0)
forward
函数使用nextPoint
获取线条的终点,绘制线条,然后将箭头光标移动到终点。
let forward distance =
let next = nextPoint distance
let l = new Line()
l.X1 <- turtle.P.X
l.Y1 <- turtle.P.Y
l.X2 <- next.X
l.Y2 <- next.Y
l.Stroke <- settings.LineColour
c.Children.Add(l) |> ignore
moveTo next.X next.Y
分形是几何形状,由自相似部分组成。可以使用L-系统文法和海龟图形来模拟分形形状的生长,包括复杂的、有机的外观结构。L-系统文法有一组符号、一个起始符号字符串和描述符号如何被其他符号替换的生产规则。符号可以是变量或常量;常量在每次生成中保持不变,变量被替换为其他符号。一些符号代表海龟绘图命令。
这个海龟程序包括可以用来绘制最简单的L-系统(称为DOL-系统)的代码,它们是确定性的(每个符号只有一个生产规则)和上下文无关的(一个生产规则只依赖于一个符号,而不是它的邻近符号)。
下面定义的数据类型可以用来描述一个简单的L-系统。它有一个起始符号字符串Start
,一个生产规则的Dictionary
,其中char
键是符号,string
值是结果的符号字符串。海龟在绘制形状时向左或向右转动某个Angle
。
type LSystem = { Start : string
Rules : Dictionary />
Angle : float}
上面的植物图像可以使用下面定义的L-系统绘制。
let branching =
let rules = new Dictionary()
rules.Add('F',
"FF")
rules.Add('X',
"F-[[X]+X]+F[+FX]-X")
rules.Add('+',
"+")
rules.Add('-',
"-")
rules.Add('[',
"[")
rules.Add(']',
"]")
{Start =
"X"
;Rules = rules;Angle=22.5}
这个L-系统文法中的符号(除了X)与下面定义的函数中的绘图命令匹配。pushTurtle
和popTurtle
函数将当前状态的海龟添加和移除出栈,允许绘制分支形状。
let drawCommand (lsys:LSystem)(symbol:char) =
match symbol with
| 'F' -> forward length
| 'G' -> forward length
| 'f' -> move length
| '+' -> left (lsys.Angle)
| '-' -> right (lsys.Angle)
| '[' -> pushTurtle()
| ']' -> popTurtle()
| _ -> None |> ignore
drawLSystem
函数在生成了n次迭代的指令字符串后绘制一个L-系统lsys
。
let drawLSystem (lsys:LSystem) (n:int) =
let instructions = generateString lsys n
in
for command in instructions do
drawCommand lsys command