F#编程语言与海龟绘图

海龟绘图是一种通过给定相对于海龟当前位置的简单命令(如左转、右转和前进)来绘制线条的技术。这些简单的命令可以作为绘制复杂形状(如螺旋和分形)的构建块。F#的交互式提示提供了一种有用的手段来控制海龟。本文将解释如何使用海龟绘图程序,并展示实现它的F#和WPF代码,并提供一些使用L-系统文法生成分形形状的示例代码。

如何使用海龟

假设正在使用Visual Studio中的F#交互式,无论是Visual Studio 2010还是免费的外壳。关于安装F#的信息可以在找到。打开文件FSharpTurtle.fsx,使用Ctrl-A选择所有内容,然后按Alt-Enter将脚本加载到F#交互式中。一个带有海龟的画布将会出现。

海龟响应命令leftrightforwardmove或者它们的缩写ltrtfdmv。每个命令后面跟着一个浮点数和两个分号(例如:left 90.0;;)。leftright命令使海龟转动给定的角度,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 PointAngle 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)

下面定义的leftright函数改变了海龟的角度,然后使用它来旋转箭头多边形。

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-系统文法有一组符号、一个起始符号字符串和描述符号如何被其他符号替换的生产规则。符号可以是变量或常量;常量在每次生成中保持不变,变量被替换为其他符号。一些符号代表海龟绘图命令。

这个海龟程序包括可以用来绘制最简单的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)与下面定义的函数中的绘图命令匹配。pushTurtlepopTurtle函数将当前状态的海龟添加和移除出栈,允许绘制分支形状。

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
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485