Commands/Get-Turtle.ps1
function Get-Turtle { <# .SYNOPSIS Gets Turtles .DESCRIPTION Gets turtles in a PowerShell. .NOTES Turtle Graphics are pretty groovy. They have been kicking it since 1966, and they are how computers first learned to draw. They kicked off the first computer-aided design boom and inspired generations of artists, engineers, mathematicians, and physicists. They are also _incredibly_ easy to build. A Turtle graphic is described with a series of moves. Let's start with the core three moves: Imagine you are a Turtle holding a pen. * You can turn `rotate` * You can move `forward` * You can lift the pen These are the three basic moves a turtle can make. We can describe more complex moves by combining these steps. Each argument can be the name of a move of the turtle object. After a member name is encountered, subsequent arguments will be passed to the member as parameters. .EXAMPLE # We can write shapes as a series of steps turtle " rotate 120 forward 42 rotate 120 forward 42 rotate 120 forward 42 " .EXAMPLE # We can also use a method. # Polygon will draw an an N-sided polygon. turtle polygon 10 5 .EXAMPLE # A simple case of this is a square turtle square 42 .EXAMPLE # If we rotate 45 degrees first, our square becomes a rhombus turtle rotate 45 square 42 .EXAMPLE # We can draw a circle turtle circle 10 .EXAMPLE # Or a pair of half-circles turtle circle 10 0.5 rotate 90 circle 10 0.5 .EXAMPLE # We can multiply arrays in PowerShell # this can make composing complex shapes easier. # Let's take the previous example and repeat it 8 times. turtle @('circle',42,0.5,'rotate',90 * 8) .EXAMPLE # Let's make a triangle by multiplying steps turtle ('forward', 10, 'rotate', 120 * 3) .EXAMPLE # We can also write this with a polygon turtle polygon 10 3 .EXAMPLE # Let's make a series of polygons, decreasing in size turtle polygon 10 6 polygon 10 5 polygon 10 4 .EXAMPLE # We can also use a loop to produce a series of steps # Let's extend our previous example and make 9 polygons turtle @( foreach ($n in 12..3) { 'polygon' 42 $n } ) .EXAMPLE # We can use the same trick to make successively larger polygons turtle @( $sideCount = 3..8 | Get-Random foreach ($n in 1..5) { 'polygon' $n * 10 $sideCount } ) .EXAMPLE # We can reflect a shape by drawing it with a negative number turtle polygon 42 3 polygon -42 3 .EXAMPLE # We can change the angle of reflection by rotating first turtle rotate 60 polygon 42 3 polygon -42 3 .EXAMPLE # We can morph any N shapes with the same number of points. turtle square 42 morph @( turtle square 42 turtle rotate 45 square 42 turtle square 42 ) .EXAMPLE # Reflections always have the same number of points. # # Morphing a shape into its reflection will zoom out, flip, and zoom back in. turtle polygon 42 6 morph @( turtle polygon -42 6 turtle polygon 42 6 turtle polygon -42 6 ) .EXAMPLE # If we want to morph a smaller shape into a bigger shape, # # we can duplicate lines turtle polygon 21 6 morph @( turtle @('forward', 21,'backward', 21 * 3) turtle polygon 21 6 turtle @('forward', 21,'backward', 21 * 3) ) .EXAMPLE # We can repeat steps by multiplying arrays. # Lets repeat a hexagon three times with a rotation turtle ('polygon', 23, 6, 'rotate', -120 * 3) .EXAMPLE # Let's change the angle a bit and see how they overlap turtle ('polygon', 23, 6, 'rotate', -60 * 6) .EXAMPLE # Let's do the same thing, but with a smaller angle turtle ('polygon', 23, 6, 'rotate', -40 * 9) .EXAMPLE # A flower is a series of repeated polygons and rotations turtle Flower .EXAMPLE # Flowers look pretty with any number of polygons turtle Flower 50 10 (3..12 | Get-Random) 36 .EXAMPLE # Flowers get less dense as we increase the angle and decrease the repetitions turtle Flower 50 15 (3..12 | Get-Random) 24 .EXAMPLE # Flowers get more dense as we decrease the angle and increase the repetitions. turtle Flower 50 5 (3..12 | Get-Random) 72 .EXAMPLE # Flowers look especially beautiful as they morph $sideCount = (3..12 | Get-Random) turtle Flower 50 15 $sideCount 36 morph @( turtle Flower 50 10 $sideCount 72 turtle rotate (Get-Random -Max 360 -Min 180) Flower 50 5 $sideCount 72 turtle Flower 50 10 $sideCount 72 ) .EXAMPLE # We can draw a pair of arcs and turn back after each one. # We call this a 'petal'. turtle rotate -30 Petal 42 60 .EXAMPLE # We can construct a flower out of petals turtle FlowerPetal .EXAMPLE # Adjusting the angle of the petal makes our petal wider or thinner turtle FlowerPetal 42 15 (20..60 | Get-Random) 24 .EXAMPLE # Flower Petals get more dense as we decrease the angle and increase repetitions turtle FlowerPetal 42 10 (10..50 | Get-Random) 36 .EXAMPLE # Flower Petals get less dense as we increase the angle and decrease repetitions turtle FlowerPetal 50 20 (20..72 | Get-Random) 18 .EXAMPLE # Flower Petals look amazing when morphed $Radius = 23..42 | Get-Random $flowerAngle = 30..60 | Get-Random $AngleFactor = 2..6 | Get-Random $StepCount = 36 $flowerPetals = turtle rotate ( (Get-Random -Max 180) * -1 ) flowerPetal $radius 10 $flowerAngle $stepCount $flowerPetals2 = turtle rotate ( (Get-Random -Max 180) ) flowerPetal $radius ( 10 * $AngleFactor ) $flowerAngle $stepCount turtle flowerPetal $radius 10 $flowerAngle $stepCount morph ( $flowerPetals, $flowerPetals2, $flowerPetals ) .EXAMPLE # We can construct a 'scissor' by drawing two lines at an angle turtle Scissor 42 60 .EXAMPLE # Drawing a scissor does not change the heading # So we can create a zig-zag pattern by multiply scissors turtle @('Scissor',42,60 * 4) .EXAMPLE # Getting a bit more interesting, we can create a polygon out of scissors # We will continually rotate until we have turned a multiple of 360 degrees. Turtle ScissorPoly 23 90 120 .EXAMPLE Turtle ScissorPoly 23 60 72 .EXAMPLE # This can get very chaotic, if it takes a while to reach a multiple of 360 # Build N scissor polygons foreach ($n in 60..72) { Turtle ScissorPoly 16 $n $n } .EXAMPLE Turtle ScissorPoly 16 69 69 .EXAMPLE # We can draw an outward spiral by growing a bit each step turtle StepSpiral .EXAMPLE turtle StepSpiral 42 120 4 18 .EXAMPLE # Because Step Spirals are a fixed number of steps, # they are easy to morph. turtle StepSpiral 42 120 4 18 morph @( turtle StepSpiral 42 90 4 24 turtle StepSpiral 42 120 4 24 turtle StepSpiral 42 90 4 24 ) .EXAMPLE turtle @('StepSpiral',3, 120, 'rotate',60 * 6) .EXAMPLE turtle @('StepSpiral',3, 90, 'rotate',90 * 4) .EXAMPLE # Step spirals look lovely when morphed # # (especially when reversing angles) turtle @('StepSpiral',3, 120, 'rotate',60 * 6) morph @( turtle @('StepSpiral',3, 120, 'rotate',60 * 6) turtle @('StepSpiral',6, -120, 'rotate',120 * 6) turtle @('StepSpiral',3, 120, 'rotate',60 * 6) ) .EXAMPLE # When we reverse the spiral angle, the step spiral curve flips turtle @('StepSpiral',3, 90, 'rotate',90 * 4) morph @( turtle @('StepSpiral',3, 90, 'rotate',90 * 4) turtle @('StepSpiral',3, -90, 'rotate',90 * 4) turtle @('StepSpiral',3, 90, 'rotate',90 * 4) ) .EXAMPLE # When we reverse the rotation, the step spiral curve slides turtle @('StepSpiral',3, 90, 'rotate',90 * 4) morph @( turtle @('StepSpiral',3, 90, 'rotate',90 * 4) turtle @('StepSpiral',3, 90, 'rotate',-90 * 4) turtle @('StepSpiral',3, 90, 'rotate',90 * 4) ) .EXAMPLE # We we alternate, it looks amazing turtle @('StepSpiral',3, 90, 'rotate',90 * 4) morph @( turtle @('StepSpiral',3, 90, 'rotate',90 * 4) turtle @('StepSpiral',3, 90, 'rotate',-90 * 4) turtle @('StepSpiral',3, 90, 'rotate',90 * 4) turtle @('StepSpiral',3, -90, 'rotate',90 * 4) turtle @('StepSpiral',3, 90, 'rotate',90 * 4) ) .EXAMPLE turtle @('StepSpiral',3, 120, 'rotate',60 * 6) morph @( turtle @('StepSpiral',3, 120, 'rotate',60 * 6) turtle @('StepSpiral',6, -120, 'rotate',120 * 6) turtle @('StepSpiral',3, 120, 'rotate',60 * 6) turtle @('StepSpiral',6, 120, 'rotate',-120 * 6) turtle @('StepSpiral',3, 120, 'rotate',60 * 6) ) .EXAMPLE turtle spirolateral .EXAMPLE turtle spirolateral 50 60 10 .EXAMPLE turtle spirolateral 50 120 6 @(1,3) .EXAMPLE turtle spirolateral 23 144 8 .EXAMPLE turtle spirolateral 23 72 8 .EXAMPLE # Turtle can draw a number of fractals turtle BoxFractal 42 4 .EXAMPLE # We can make a Board Fractal turtle BoardFractal 42 4 .EXAMPLE # We can make a Crystal Fractal turtle CrystalFractal 42 4 .EXAMPLE # We can make ring fractals turtle RingFractal 42 4 .EXAMPLE # We can make a Pentaplexity turtle Pentaplexity 42 3 .EXAMPLE # We can make a Triplexity turtle Triplexity 42 4 .EXAMPLE # We can draw the Koch Island turtle KochIsland 42 4 .EXAMPLE # Or we can draw the Koch Curve turtle KochCurve 42 .EXAMPLE # We can make a Koch Snowflake turtle KochSnowflake 42 .EXAMPLE # We can draw the Levy Curve turtle LevyCurve 42 6 .EXAMPLE # We can use a Hilbert Curve to fill a space Turtle HilbertCurve 42 4 .EXAMPLE # We can use a Moore Curve to fill a space with a bit more density. turtle MooreCurve 42 4 .EXAMPLE # We can show a binary tree turtle BinaryTree 42 4 .EXAMPLE # We can also mimic plant growth turtle FractalPlant 42 4 .EXAMPLE # The SierpinskiArrowHead Curve is pretty turtle SierpinskiArrowheadCurve 42 4 .EXAMPLE # The SierpinskiTriangle is a Fractal classic turtle SierpinskiTriangle 42 4 .EXAMPLE # Let's draw two reflected Sierpinski Triangles turtle rotate 60 SierpinskiTriangle 42 4 SierpinskiTriangle -42 4 .EXAMPLE # We can draw a 'Sierpinski Snowflake' with multiple Sierpinski Triangles. turtle @('rotate', 30, 'SierpinskiTriangle',42,4 * 12) .EXAMPLE turtle @('rotate', 45, 'SierpinskiTriangle',42,4 * 24) #> [CmdletBinding(PositionalBinding=$false)] [Alias('turtle')] param( # The arguments to pass to turtle. [ArgumentCompleter({ param ( $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters ) if (-not $script:TurtleTypeData) { $script:TurtleTypeData = Get-TypeData -TypeName Turtle } $memberNames = @($script:TurtleTypeData.Members.Keys) if ($wordToComplete) { return $memberNames -like "$wordToComplete*" } else { return $memberNames } })] [Parameter(ValueFromRemainingArguments)] [PSObject[]] $ArgumentList, # Any input object to process. # If this is already a turtle object, the arguments will be applied to this object. # If the input object is not a turtle object, it will be ignored and a new turtle object will be created. [Parameter(ValueFromPipeline)] [PSObject] $InputObject ) begin { # Get information about our turtle pseudo-type. $turtleType = Get-TypeData -TypeName Turtle # any member name is a potential command $memberNames = $turtleType.Members.Keys # We want to sort the member names by length, in case we need them in a pattern or want to sort quickly. $memberNames = $memberNames | Sort-Object @{Expression={ $_.Length };Descending=$true}, name # Create a new turtle object in case we have no turtle input. $currentTurtle = [PSCustomObject]@{PSTypeName='Turtle'} } process { if ($PSBoundParameters.InputObject -and $PSBoundParameters.InputObject.pstypenames -eq 'Turtle') { $currentTurtle = $PSBoundParameters.InputObject } elseif ($PSBoundParameters.InputObject) { # If input was passed, and it was not a turtle, pass it through. return $PSBoundParameters.InputObject } # First we want to split each argument into words. # This way, it is roughly the same if you say: # * `turtle 'forward 10'` # * `turtle forward 10` # * `turtle 'forward', 10` $wordsAndArguments = @(foreach ($arg in $ArgumentList) { # If the argument is a string, split it by whitespace. if ($arg -is [string]) { $arg -split '\s{1,}' } else { # otherwise, leave the argument alone. $arg } }) # Now that we have a series of words, we can process them. # We want to keep track of the current member, # and continue to the next word until we find a member name. $currentMember = $null # We want to output the turtle by default, in case we were called with no parameters. $outputTurtle = $true # To do this in one pass, we will iterate through the words and arguments. # We use an indexed loop so we can skip past claimed arguments. for ($argIndex =0; $argIndex -lt $wordsAndArguments.Length; $argIndex++) { $arg = $wordsAndArguments[$argIndex] # If the argument is not in the member names list, we can complain about it. if ($arg -notin $memberNames) { if (-not $currentMember -and $arg -is [string] -and "$arg".Trim()) { Write-Warning "Unknown command '$arg'." } continue } # If we have a current member, we can invoke it or get it. $currentMember = $arg # We can also begin looking for arguments for ( # at the next index. $methodArgIndex = $argIndex + 1; # We will continue until we reach the end of the words and arguments, $methodArgIndex -lt $wordsAndArguments.Length -and $wordsAndArguments[$methodArgIndex] -notin $memberNames; $methodArgIndex++) { } # Now we know how long it took to get to the next member name. # And we can determine if we have any parameters. # (it is important that we always force any parameters into an array) $argList = @(if ($methodArgIndex -ne ($argIndex + 1)) { $wordsAndArguments[($argIndex + 1)..($methodArgIndex - 1)] $argIndex = $methodArgIndex - 1 }) # Look up the member information for the current member. $memberInfo = $turtleType.Members[$currentMember] # If it's an alias if ($memberInfo.ReferencedMemberName) { # try to resolve it. $currentMember = $memberInfo.ReferencedMemberName $memberInfo = $turtleType.Members[$currentMember] } # Now we want to get the output from the step. $stepOutput = if ( # If the member is a method, let's invoke it. $memberInfo -is [Management.Automation.Runspaces.ScriptMethodData] -or $memberInfo -is [Management.Automation.PSMethod] ) { # If we have arguments, if ($argList) { # and we have a script method if ($memberInfo -is [Management.Automation.Runspaces.ScriptMethodData]) { # set this to the current turtle $this = $currentTurtle # and call the script, splatting positional parameters # (this allows more complex binding, like ValueFromRemainingArguments) . $currentTurtle.$currentMember.Script @argList } else { # Otherwise, we pass the parameters directly to the method $currentTurtle.$currentMember.Invoke($argList) } } else { # otherwise, just invoke the method with no arguments. $currentTurtle.$currentMember.Invoke() } } else { # If the member is a property, we can get it or set it. # If we have any arguments, if ($argList) { # lets try to set it. $currentTurtle.$currentMember = $argList } else { # otherwise, lets get the property $currentTurtle.$currentMember } } # If the output is not a turtle object, we can output it. # NOTE: This may lead to multiple types of output in the pipeline. # Luckily, this should be one of the few cases where this does not annoy too much. # Properties being returned will largely be strings or numbers, and these will always output directly. if ($null -ne $stepOutput -and -not ($stepOutput.pstypenames -eq 'Turtle')) { # Output the step $stepOutput # and set the output turtle to false. $outputTurtle = $false } elseif ($null -ne $stepOutput) { # Set the current turtle to the step output. $currentTurtle = $stepOutput # and output it later (presumably). $outputTurtle = $true } } # If the last members returned a turtle object, we can output it. if ($outputTurtle) { return $currentTurtle } } } |