Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
03f6c63
fix: `Turtle.get_Position` missing parenthesis on value ( Fixes #85 )
ninmonkey Jul 30, 2025
e12837a
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
StartAutomating Jul 30, 2025
602111e
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
e6f5f5f
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
afe78a8
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
d79e54c
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
adfdb54
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
a8b05d8
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
29edf4c
feat: `Turtle.LSystem` produces data attributes ( Fixes #87 )
Jul 30, 2025
d6fcefa
Merge pull request #86 from ninmonkey/main
StartAutomating Jul 30, 2025
5bd9662
Merge pull request #86 from ninmonkey/main
Jul 30, 2025
a15689d
feat: `Turtle.get/set_ID` ( Fixes #84 )
StartAutomating Jul 30, 2025
dbb7d55
feat: `Turtle.get/set_ID` ( Fixes #84 )
Jul 30, 2025
081f5b6
feat: `Turtle.ToString()` ( Fixes #88 )
StartAutomating Jul 30, 2025
c939f7c
feat: `Turtle.ToString()` ( Fixes #88 )
Jul 30, 2025
c8e2db9
feat: `Turtle.ToString()` ( Fixes #88 )
Jul 30, 2025
23408ef
fix: Fixing GoTo/Teleport position tracking ( Fixes #90 )
StartAutomating Jul 30, 2025
7427d38
fix: Fixing GoTo/Teleport position tracking ( Fixes #90 )
StartAutomating Jul 30, 2025
3b10a33
fix: Fixing GoTo/Teleport position tracking ( Fixes #90 )
Jul 30, 2025
44fa089
feat: `Turtle.Push()` ( Fixes #91 )
StartAutomating Jul 30, 2025
7764053
feat: `Turtle.Push()` ( Fixes #91 )
Jul 30, 2025
324201f
feat: `Turtle.Push()` ( Fixes #91 )
Jul 30, 2025
a05d068
feat: `Turtle.Pop()` ( Fixes #92 )
StartAutomating Jul 30, 2025
1f8492e
feat: `Turtle.Pop()` ( Fixes #92 )
Jul 30, 2025
b91de65
chore: `Turtle.HilbertCurve`
StartAutomating Jul 30, 2025
5a38fc1
feat: `Turtle.get_Stack` ( Fixes #93 )
StartAutomating Jul 30, 2025
81e46e4
feat: `Turtle.get_Stack` ( Fixes #93 )
Jul 30, 2025
dbf0b40
feat: `Turtle.get_Stack` ( Fixes #93 )
Jul 30, 2025
70fc93c
feat: `Turtle.get/set_ID` ( Fixes #84 )
StartAutomating Jul 30, 2025
15aa433
feat: `Turtle.get/set_ID` ( Fixes #84 )
Jul 30, 2025
bb9826e
feat: `Turtle.BinaryTree()` ( Fixes #94 )
StartAutomating Jul 30, 2025
f62e32c
feat: `Turtle.BinaryTree()` ( Fixes #94 )
Jul 30, 2025
2d1cf97
feat: `Turtle.FractalPlant()` ( Fixes #95 )
StartAutomating Jul 30, 2025
8f67c2c
feat: `Turtle.FractalPlant()` ( Fixes #95 )
Jul 30, 2025
e27425a
fix: Fixing Turtle Action ID ( Fixes #89 )
StartAutomating Jul 30, 2025
cdc46c7
release: Turtle 0.1.1
StartAutomating Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Build/Turtle.GitHubAction.PSDevOps.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ Import-BuildStep -SourcePath (

$PSScriptRoot | Split-Path | Push-Location

New-GitHubAction -Name "TurtlePower" -Description 'Turtles in a PowerShell' -Action TurtleAction -Icon chevron-right -OutputPath .\action.yml
New-GitHubAction -Name "TurtlePowerShell" -Description 'Turtles in a PowerShell' -Action TurtleAction -Icon chevron-right -OutputPath .\action.yml

Pop-Location
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
## Turtle 0.1.1:

* Updates:
* `Turtle.get/set_ID` allows for turtle identifiers
* `Turtle.ToString()` stringifies the SVG
* Fixes:
* Fixing GoTo/Teleport (#90)
* Fixing Position default (#85) (thanks @ninmonkey !)
* Fixing Turtle Action ID (#89)
* New:
* `Turtle.Push()` pushes position/heading to a stack (#91)
* `Turtle.Pop()` pops position/heading from a stack (#92)
* `Turtle.get_Stack` gets the position stack (#93)
* New Fractals:
* `BinaryTree()` (#94)
* `FractalPlant()` (#95)

---

## Turtle 0.1:

* Initial Release
* Builds a Turtle Graphics engine in PowerShell
* Core commands
* `Get-Turtle` (alias `turtle`) runs multiple moves
* `New-Turtle` create a turtle
* `Move-Turtle` performas a single move
* `Set-Turtle` changes a turtle
* `Save-Turtle` saves a turtle

~~~PowerShell
turtle Forward 10 Rotate 120 Forward 10 Roate 120 Forward 10 Rotate 120 |
Set-Turtle Stroke '#4488ff' |
Save-Turtle ./Triangle.svg
~~~

* Core Object
* `.Heading` controls the turtle heading
* `.Steps` stores a list of moves as an SVG path
* `.IsPenDown` controls the pen
* `.Forward()` moves forward at heading
* `.Rotate()` rotates the heading
* `.Square()` draws a square
* `.Polygon()` draws a polygon
* `.Circle()` draws a circle (or partial circle)
* LSystems
* Turtle can draw a L system. Several are included:
* `BoxFractal`
* `GosperCurve`
* `HilbertCurve`
* `KochCurve`
* `KochIsland`
* `KochSnowflake`
* `MooreCurve`
* `PeanoCurve`
* `SierpinskiTriangle`
* `SierpinskiCurve`
* `SierpinskiSquareCurve`
* `SierpinskiArrowheadCurve`
* `TerdragonCurve`
* `TwinDragonCurve`

~~~PowerShell
turtle SierpinskiTriangle 10 4 |
Set-Turtle Stroke '#4488ff' |
Save-Turtle ./SierpinskiTriangle.svg
~~~

2 changes: 1 addition & 1 deletion Examples/BoxFractal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Examples/EndlessBoxFractal.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Examples/EndlessHilbert.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Examples/EndlessSierpinskiTrianglePattern.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Examples/EndlessSnowflake.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Examples/SierpinskiTriangle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 20 additions & 1 deletion Turtle.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
# Version number of this module.
ModuleVersion = '0.1'
ModuleVersion = '0.1.1'
# Description of the module
Description = "Turtles in a PowerShell"
# Script module or binary module file associated with this manifest.
Expand Down Expand Up @@ -30,6 +30,25 @@
# A URL to the license for this module.
LicenseURI = 'https://github.com/PowerShellWeb/Turtle/blob/main/LICENSE'
ReleaseNotes = @'
## Turtle 0.1.1:

* Updates:
* `Turtle.get/set_ID` allows for turtle identifiers
* `Turtle.ToString()` stringifies the SVG
* Fixes:
* Fixing GoTo/Teleport (#90)
* Fixing Position default (#85) (thanks @ninmonkey !)
* Fixing Turtle Action ID (#89)
* New:
* `Turtle.Push()` pushes position/heading to a stack (#91)
* `Turtle.Pop()` pops position/heading from a stack (#92)
* `Turtle.get_Stack` gets the position stack (#93)
* New Fractals:
* `BinaryTree()` (#94)
* `FractalPlant()` (#95)

---

## Turtle 0.1:

* Initial Release
Expand Down
138 changes: 124 additions & 14 deletions Turtle.types.ps1xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ $Distance = 10

$this.Forward($Distance * -1)

</Script>
</ScriptMethod>
<ScriptMethod>
<Name>BinaryTree</Name>
<Script>
param(
[double]$Size = 20,
[int]$Order = 4,
[double]$Angle = 45
)
return $this.Rotate(-90).LSystem('0', [Ordered]@{
'1' = '11'
'0' = '1[0]0'
}, $Order, [Ordered]@{
'[01]' = { $this.Forward($Size) }
'\[' = { $this.Rotate($Angle * -1).Push() }
'\]' = { $this.Pop().Rotate($Angle) }
})


</Script>
</ScriptMethod>
<ScriptMethod>
Expand Down Expand Up @@ -285,6 +305,26 @@ if ($This.IsPenDown) {
return $this
</Script>
</ScriptMethod>
<ScriptMethod>
<Name>FractalPlant</Name>
<Script>
param(
[double]$Size = 20,
[int]$Order = 4,
[double]$Angle = 25
)
return $this.Rotate(-90).LSystem('-X', [Ordered]@{
'X' = 'F+[[X]-X]-F[-FX]+X'
'F' = 'FF'
}, $Order, [Ordered]@{
'F' = { $this.Forward($Size) }
'\[' = { $this.Rotate($Angle * -1).Push() }
'\]' = { $this.Pop().Rotate($Angle) }
})


</Script>
</ScriptMethod>
<ScriptMethod>
<Name>GosperCurve</Name>
<Script>
Expand Down Expand Up @@ -352,7 +392,7 @@ if ($this.IsPenDown) {
} else {
$this.Steps += " m $deltaX $deltaY"
}
$this.Position = $x, $y
$this.Position = $deltaX, $deltaY
return $this
</Script>
</ScriptMethod>
Expand All @@ -365,10 +405,10 @@ return $this
[double]$Angle = 90
)

return $this.LSystem('A', @{
return $this.LSystem('A', [Ordered]@{
A = '+BF-AFA-FB+'
B = '-AF+BFB+FA-'
}, $Order, @{
}, $Order, [Ordered]@{
'F' = { $this.Forward($Size) }
'\+' = { $this.Rotate($Angle) }
'\-' = { $this.Rotate($Angle * -1) }
Expand Down Expand Up @@ -682,6 +722,12 @@ $null = foreach ($character in $finalState.ToCharArray()) {
}
}
}
$this.PathAttribute = [Ordered]@{
"data-l-order" = $N
"data-l-axiom" = $Axiom
"data-l-rules" = ConvertTo-Json $Rule
"data-l-expanded" = $finalState
}
return $this

</Script>
Expand Down Expand Up @@ -798,6 +844,37 @@ $null = foreach ($n in 1..$SideCount) {
$this.Forward($Size)
$this.Rotate(360 / $SideCount)
}
return $this
</Script>
</ScriptMethod>
<ScriptMethod>
<Name>Pop</Name>
<Script>
if ($this.'.Stack' -isnot [Collections.Stack]) {
return
}

if ($this.'.Stack'.Count -eq 0) {
return
}

$popped = $this.'.Stack'.Pop()
$this.PenUp().Goto($popped.Position.X, $popped.Position.Y).PenDown()
$this.Heading = $popped.Heading
return $this
</Script>
</ScriptMethod>
<ScriptMethod>
<Name>Push</Name>
<Script>
if (-not $this.'.Stack') {
$this | Add-Member NoteProperty '.Stack' ([Collections.Stack]::new()) -Force
}

$this.'.Stack'.Push(@{
Position = [Ordered]@{X=$this.Position.X;Y=$this.Position.Y}
Heading = $this.Heading
})
return $this
</Script>
</ScriptMethod>
Expand Down Expand Up @@ -1057,7 +1134,7 @@ $Y
$deltaX = $x - $this.X
$deltaY = $y - $this.Y
$this.Steps += "m $deltaX $deltaY"
$this.Position = $x, $y
$this.Position = $deltaX, $deltaY
return $this
</Script>
</ScriptMethod>
Expand Down Expand Up @@ -1104,6 +1181,14 @@ return $this.LSystem('F', [Ordered]@{

</Script>
</ScriptMethod>
<ScriptMethod>
<Name>ToString</Name>
<Script>
param()

return "$($this.SVG.OuterXml)"
</Script>
</ScriptMethod>
<ScriptMethod>
<Name>TwinDragonCurve</Name>
<Script>
Expand Down Expand Up @@ -1236,12 +1321,12 @@ $this | Add-Member NoteProperty -Name '.BackgroundColor' -Value $value -Force
$viewBox = $this.ViewBox
$null, $null, $viewX, $viewY = $viewBox
"&lt;style&gt;canvas {max-width: 100%; height: 100%}&lt;/style&gt;"
"&lt;canvas id='turtle-canvas' width='$($viewX + 1)' height='$($viewY + 1)'&gt;&lt;/canvas&gt;"
"&lt;canvas id='$($this.ID)-canvas' width='$($viewX + 1)' height='$($viewY + 1)'&gt;&lt;/canvas&gt;"

"&lt;script&gt;"
@"
window.onload = async function() {
var canvas = document.getElementById('turtle-canvas');
var canvas = document.getElementById('$($this.ID)-canvas');
var ctx = canvas.getContext('2d');
ctx.strokeStyle = '$($this.Stroke)'
ctx.lineWidth = '$(
Expand Down Expand Up @@ -1351,6 +1436,20 @@ if ($VerbosePreference -ne 'SilentlyContinue') {
}
</SetScriptBlock>
</ScriptProperty>
<ScriptProperty>
<Name>ID</Name>
<GetScriptBlock>
if ($this.'.ID') { return $this.'.ID'}
return 'turtle'

</GetScriptBlock>
<SetScriptBlock>
param([string]$Value)

$this | Add-Member NoteProperty '.ID' $Value -Force

</SetScriptBlock>
</ScriptProperty>
<ScriptProperty>
<Name>IsPenDown</Name>
<GetScriptBlock>
Expand Down Expand Up @@ -1429,7 +1528,7 @@ if ($chromeOutput -match '&lt;img\ssrc="data:image/\w+;base64,(?&lt;b64&gt;[^"]+
$segments = @(
"&lt;svg xmlns='http://www.w3.org/2000/svg' width='0%' height='0%'&gt;"
"&lt;defs&gt;"
"&lt;mask id='turtle-mask'&gt;"
"&lt;mask id='$($this.Id)-mask'&gt;"
$this.Symbol.OuterXml -replace '\&lt;\?[^\&gt;]+\&gt;'
"&lt;/mask&gt;"
"&lt;/defs&gt;"
Expand Down Expand Up @@ -1546,7 +1645,7 @@ $this | Add-Member -MemberType NoteProperty -Force -Name '.PathClass' -Value @(
<Name>PathElement</Name>
<GetScriptBlock>
@(
"&lt;path id='turtle-path' d='$($this.PathData)' stroke='$(
"&lt;path id='$($this.id)-path' d='$($this.PathData)' stroke='$(
if ($this.Stroke) { $this.Stroke } else { 'currentColor' }
)' stroke-width='$(
if ($this.StrokeWidth) { $this.StrokeWidth } else { '0.1%' }
Expand All @@ -1569,7 +1668,7 @@ $viewBox = $this.ViewBox
$null, $null, $viewX, $viewY = $viewBox
"&lt;svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'&gt;"
"&lt;defs&gt;"
"&lt;pattern id='turtle-pattern' patternUnits='userSpaceOnUse' width='$viewX' height='$viewY' transform-origin='50% 50%'$(
"&lt;pattern id='$($this.ID)-pattern' patternUnits='userSpaceOnUse' width='$viewX' height='$viewY' transform-origin='50% 50%'$(
if ($this.PatternTransform) {
" patternTransform='" + (
@(foreach ($key in $this.PatternTransform.Keys) {
Expand All @@ -1587,7 +1686,7 @@ $(
"&lt;rect width='10000%' height='10000%' x='-5000%' y='-5000%' fill='$($this.BackgroundColor)' transform-origin='50% 50%' /&gt;"
}
)
"&lt;rect width='10000%' height='10000%' x='-5000%' y='-5000%' fill='url(#turtle-pattern)' transform-origin='50% 50%' /&gt;"
"&lt;rect width='10000%' height='10000%' x='-5000%' y='-5000%' fill='url(#$($this.ID)-pattern)' transform-origin='50% 50%' /&gt;"
"&lt;/svg&gt;")

$segments -join '' -as [xml]
Expand Down Expand Up @@ -1657,7 +1756,7 @@ $b64 = [Convert]::ToBase64String($OutputEncoding.GetBytes($thisPattern.outerXml)
$segments = @(
"&lt;svg xmlns='http://www.w3.org/2000/svg' width='0%' height='0%'&gt;"
"&lt;defs&gt;"
"&lt;mask id='turtle-mask'&gt;"
"&lt;mask id='$($this.ID)-mask'&gt;"
$this.Pattern.OuterXml -replace '\&lt;\?[^\&gt;]+\&gt;'
"&lt;/mask&gt;"
"&lt;/defs&gt;"
Expand Down Expand Up @@ -1744,9 +1843,10 @@ if ($chromeOutput -match '&lt;img\ssrc="data:image/png;base64,(?&lt;b64&gt;[^"]+
<Name>Position</Name>
<GetScriptBlock>
if (-not $this.'.Position') {
$this | Add-Member -MemberType NoteProperty -Force -Name '.Position' -Value [pscustomobject]@{ X = 0; Y = 0 }
$this | Add-Member -MemberType NoteProperty -Force -Name '.Position' -Value ([pscustomobject]@{ X = 0; Y = 0 })
}
return $this.'.Position'

</GetScriptBlock>
<SetScriptBlock>
param([double[]]$xy)
Expand Down Expand Up @@ -1777,6 +1877,16 @@ if ($posY -gt $this.'.Maximum'.Y) {
}
</SetScriptBlock>
</ScriptProperty>
<ScriptProperty>
<Name>Stack</Name>
<GetScriptBlock>
if ($null -ne $this.'.Stack'.Count) {
$this | Add-Member NoteProperty '.Stack' ([Collections.Stack]::new()) -Force
}
$this.'.Stack'

</GetScriptBlock>
</ScriptProperty>
<ScriptProperty>
<Name>Steps</Name>
<GetScriptBlock>
Expand Down Expand Up @@ -1865,15 +1975,15 @@ param()

@(
"&lt;svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' transform-origin='50% 50%'&gt;"
"&lt;symbol id='turtle-symbol' viewBox='$($this.ViewBox)' transform-origin='50% 50%'&gt;"
"&lt;symbol id='$($this.ID)-symbol' viewBox='$($this.ViewBox)' transform-origin='50% 50%'&gt;"
$this.PathElement.OuterXml
"&lt;/symbol&gt;"
$(
if ($this.BackgroundColor) {
"&lt;rect width='10000%' height='10000%' x='-5000%' y='-5000%' fill='$($this.BackgroundColor)' transform-origin='50% 50%' /&gt;"
}
)
"&lt;use href='#turtle-symbol' width='100%' height='100%' transform-origin='50% 50%' /&gt;"
"&lt;use href='#$($this.ID)-symbol' width='100%' height='100%' transform-origin='50% 50%' /&gt;"
"&lt;/svg&gt;"
) -join '' -as [xml]
</GetScriptBlock>
Expand Down
Loading
Loading