A slight tweak to the ConEmu install script, plus my own settings file.
iex (irm https://gist.githubusercontent.com/Jaykul/6deda247c677d425862d7098d3ee34ae/raw/Install.ps1)
Sorry, I'll shorten that URL when I move this to a real repo 😉
using namespace System.Numerics | |
class Mandelbrot : System.Collections.IEnumerator { | |
# From the constructor? | |
# Sadly, PowerShell doesn't support optional parameters | |
[double]$HorizontalViewOffset = -0.5 | |
[double]$VerticalViewOffset = 0 | |
$Columns = 120 | |
$Rows = 28 | |
$ZoomViewDistance = 6.75 | |
# calculated during initialization | |
hidden [double[]]$x_range | |
hidden [double[]]$y_range | |
hidden [double]$xscale | |
hidden [double]$yscale | |
hidden [int]$iterations | |
hidden [Complex]$z0 | |
# These are the things which implement IEnumerator | |
hidden [int]$_ix = 0 | |
hidden [int]$_iy = 0 | |
hidden [int[]]$Actual = $null | |
hidden [int]$SkipLines = -1 | |
[void] Init() { | |
$aspect_ratio = 1/3 | |
# lowering the distance will zoom in | |
$factor = $this.ZoomViewDistance / $this.Columns | |
$this.xscale = $factor * $aspect_ratio | |
$this.yscale = $factor | |
$this.iterations = 170 | |
$this.z0 = [Complex]::Zero | |
$this.SkipLines = -1 | |
$xmin = $this.HorizontalViewOffset - (($this.xscale * $this.Columns) / 2) | |
$ymin = $this.VerticalViewOffset - (($this.yscale * $this.Rows) / 2) | |
$this.x_range = foreach($ix in 0..($this.Columns - 1)){ $xmin + ($this.xscale * $ix) } | |
$this.y_range = foreach($iy in 0..($this.Rows - 1)){ $ymin + ($this.yscale * $iy) } | |
} | |
[object] get_Current() { | |
return $this.Actual | |
} | |
[void] Reset() { | |
if($this.iterations -eq 0) { $this.Init() } | |
$this._ix = 0 | |
$this._iy = 0 | |
} | |
[bool] MoveNext() { | |
if($this.iterations -eq 0) { $this.Init() } | |
if($this._ix -ge $this.Columns) { | |
$this._ix = 0 | |
$this._iy++ | |
} | |
if($this._iy -ge $this.Rows) { | |
return $false | |
} | |
$c = [Complex]::new($this.x_range[$this._ix], $this.y_range[$this._iy]) | |
$z = $this.z0 | |
$color = 0 | |
$mind = 2 | |
foreach ($i in (0..($this.iterations - 1))) { | |
$z = $z * $z + $c | |
$d = [Complex]::Abs($z) | |
if ($d -ge 2) { | |
$color = [Math]::Min([int]($mind / 0.007), 254) + 1 | |
break | |
} else { | |
$mind = [Math]::Min($d, $mind) | |
} | |
} | |
# yield return ... | |
$this.Actual = @( $this._ix, $this._iy, $color ) | |
# obscure feature which would allow skipping lines, if you were using this as a "working" animation ... | |
# when you need to write output, you might skip as many lines of the mandelbrot as you had output lines | |
if ($this.SkipLines -ge 0) { | |
$this._iy += $this.SkipLines | |
$do_break = $this.SkipLines -gt 0 | |
$this.SkipLines = -1 | |
$this._ix = 0 | |
# NOTE: I still don't understand this | |
if ($do_break) { return $false } | |
} else { | |
$this._ix++ | |
} | |
return $true | |
} | |
} | |
$script:palette = @( | |
$Null | |
[ConsoleColor]::DarkBlue | |
[ConsoleColor]::DarkMagenta | |
[ConsoleColor]::DarkCyan | |
[ConsoleColor]::DarkRed | |
[ConsoleColor]::DarkYellow | |
[ConsoleColor]::DarkGreen | |
[ConsoleColor]::Gray | |
) | |
# These are some "favorite" locations | |
$script:MandelbrotFavoriteViews = @( | |
# x, y, "distance", max color range | |
[PSCustomObject]@{HorizontalViewOffset=-0.5; VerticalViewOffset= 0; ZoomViewDistance =6.75; ColorViewLimit=255} | |
[PSCustomObject]@{HorizontalViewOffset=0.37865401; VerticalViewOffset= 0.669227668; ZoomViewDistance =0.04; ColorViewLimit=111} | |
[PSCustomObject]@{HorizontalViewOffset=-1.2693; VerticalViewOffset= -0.4145; ZoomViewDistance =0.2; ColorViewLimit=105} | |
[PSCustomObject]@{HorizontalViewOffset=-1.2693; VerticalViewOffset= -0.4145; ZoomViewDistance =0.05; ColorViewLimit=97} | |
[PSCustomObject]@{HorizontalViewOffset=-1.2642; VerticalViewOffset= -0.4185; ZoomViewDistance =0.01; ColorViewLimit=95} | |
[PSCustomObject]@{HorizontalViewOffset=-1.15; VerticalViewOffset= -0.28; ZoomViewDistance =0.9; ColorViewLimit=94} | |
[PSCustomObject]@{HorizontalViewOffset=-1.15; VerticalViewOffset= -0.28; ZoomViewDistance =0.3; ColorViewLimit=58} | |
[PSCustomObject]@{HorizontalViewOffset=-1.15; VerticalViewOffset= -0.28; ZoomViewDistance =0.05; ColorViewLimit=26} | |
) | |
function Get-ParameterValues { | |
<# | |
.Synopsis | |
Get the actual values of parameters which have manually set (non-null) default values or values passed in the call | |
.Description | |
Unlike $PSBoundParameters, the hashtable returned from Get-ParameterValues includes non-empty default parameter values. | |
NOTE: Default values that are the same as the implied values are ignored (e.g.: empty strings, zero numbers, nulls). | |
.Example | |
function Test-Parameters { | |
[CmdletBinding()] | |
param( | |
$Name = $Env:UserName, | |
$Age | |
) | |
$Parameters = . Get-ParameterValues | |
# This WILL ALWAYS have a value... | |
Write-Host $Parameters["Name"] | |
# But this will NOT always have a value... | |
Write-Host $PSBoundParameters["Name"] | |
} | |
#> | |
[CmdletBinding()] | |
param( | |
# The $MyInvocation for the caller -- DO NOT pass this (dot-source Get-ParameterValues instead) | |
$Invocation = $MyInvocation, | |
# The $PSBoundParameters for the caller -- DO NOT pass this (dot-source Get-ParameterValues instead) | |
$BoundParameters = $PSBoundParameters | |
) | |
if($MyInvocation.Line[($MyInvocation.OffsetInLine - 1)] -ne '.') { | |
throw "Get-ParameterValues must be dot-sourced, like this: . Get-ParameterValues" | |
} | |
if($PSBoundParameters.Count -gt 0) { | |
throw "You should not pass parameters to Get-ParameterValues, just dot-source it like this: . Get-ParameterValues" | |
} | |
$ParameterValues = @{} | |
foreach($parameter in $Invocation.MyCommand.Parameters.GetEnumerator()) { | |
# gm -in $parameter.Value | Out-Default | |
try { | |
$key = $parameter.Key | |
if($null -ne ($value = Get-Variable -Name $key -ValueOnly -ErrorAction Ignore)) { | |
if($value -ne ($null -as $parameter.Value.ParameterType)) { | |
$ParameterValues[$key] = $value | |
} | |
} | |
if($BoundParameters.ContainsKey($key)) { | |
$ParameterValues[$key] = $BoundParameters[$key] | |
} | |
} finally {} | |
} | |
$ParameterValues | |
} | |
function Write-Pixel { | |
[CmdletBinding()] | |
param($color, $char, $ColorViewLimit = 256, [Switch]$invert) | |
$chars = @([char]183, "-", "+", "*", "%", "#") | |
$idx = {param($chars) [Math]::Abs(($color+1) * ($chars.Count - 1) / $ColorViewLimit) } | |
if(!$invert) { | |
$idx2 = $idx | |
$idx = {param($chars,$idx2=$idx2) $chars.Count - 1 - (&$idx2 $chars)} | |
} | |
if(!$char) { | |
$char = $chars[(&$idx $chars)] | |
} | |
$ansi_color = $script:palette[(&$idx $script:palette)] | |
if($null -eq $ansi_color) { | |
Write-Host $char -NoNewLine | |
} else { | |
Write-Host $char -Foreground $ansi_color -NoNewLine | |
} | |
if($DebugPreference -gt "SilentlyContinue") { | |
# When testing new locations, it's useful to be able to see the range of colors we *should* have used | |
if($null -eq $script:color_range) { | |
$script:color_range = $color, $color | |
} | |
else { | |
$old_color_range = $script:color_range | |
$script:color_range = @([Math]::Min($script:color_range[0], $color), [Math]::Max($script:color_range[1], $color)) | |
if (($old_color_range[0] - $script:color) -gt 3 -or ($script:color - $old_color_range[1]) -gt 3) { | |
return $true | |
} | |
} | |
} | |
return $false | |
} | |
$Script:LastFavorite = 0 | |
function New-Mandelbrot { | |
<# | |
.Synopsis | |
Generates an ASCII Mandelbrot in the console | |
.Description | |
Generates a Mandelbrot with the specified parameters using Write-Host to output it. | |
When called without parameters, cycles through a series of favorite views of the mandelbrot | |
.Example | |
New-Mandelbrot -Favorite 0 -Zoom 12 | |
Generates a default full-console mandelbrot, zoomed out so there's a border all around | |
.Example | |
New-Mandelbrot -Favorite 3 | |
Generates a highly zoomed in view from the favorites list | |
#> | |
[CmdletBinding(DefaultParameterSetName="Favorites")] | |
param( | |
# Select from a pre-defined set of good View values | |
# Combine with -Verbose if you want to see what the parameter values are | |
[ValidateScript({if($_ -ge 0 -and $_ -lt $MandelbrotFavoriteViews.Count) {$true}else { throw "FavoriteView out of range, there are only $($MandelbrotFavoriteViews.Count) favorites"}})] | |
[Parameter(ParameterSetName="Favorites", Position=0)] | |
$FavoriteView = $Script:LastFavorite, | |
# Set the width of the output Mandelbrot in console columns | |
$Columns = $(if($w=$host.UI.RawUI.WindowSize.Width){$W}else{80}), | |
# Set the height of the output Mandelbrot in console rows | |
$Rows = $(if($h=$host.UI.RawUI.WindowSize.Height){$h}else{30}), | |
# Controls the horizontal offset for the view (a good default would be -0.5) | |
[Parameter(ParameterSetName="ManualLocation")] | |
[double]$HorizontalViewOffset = $MandelbrotFavoriteViews[$FavoriteView].HorizontalViewOffset, | |
# Controls the vertical offset for the view (a good default would be 0) | |
[Parameter(ParameterSetName="ManualLocation")] | |
[double]$VerticalViewOffset = $MandelbrotFavoriteViews[$FavoriteView].VerticalViewOffset, | |
# Controls the zoom distance for the view (a good default would be 6.75) | |
# Lower values zoom in closer | |
[Parameter(ParameterSetName="ManualLocation")] | |
$ZoomViewDistance = $MandelbrotFavoriteViews[$FavoriteView].ZoomViewDistance, | |
# Limit the color display (a good default would be 255) | |
[Parameter(ParameterSetName="ManualLocation")] | |
$ColorViewLimit = $MandelbrotFavoriteViews[$FavoriteView].ColorViewLimit, | |
# If set, inverts the colors of the mandelbrot | |
[Switch]$invert | |
) | |
begin { | |
$ErrorActionPreference = "Stop" | |
$InterestingCoordinates = @() | |
$script:color_range = $Null | |
$mandelbrot = [Mandelbrot]::new() | |
$Parameters = . Get-ParameterValues | |
$null = $Parameters.Remove("InputObject") | |
$null = $Parameters.Remove("Invert") | |
Write-Verbose (($Parameters | Out-String -stream) -join "`n") | |
# Copy over whatever we can | |
foreach($key in (Get-Member -InputObject $mandelbrot -Name @($Parameters.Keys)).ForEach("Name")) { | |
$mandelbrot.$key = $Parameters[$key] | |
} | |
$null = $mandelbrot.Init() | |
} | |
process { | |
$x = 0 | |
$c = 0 | |
while($mandelbrot.MoveNext()) { | |
$x, $y, $c = $mandelbrot.Current | |
if(Write-Pixel $c -ColorViewLimit:$ColorViewLimit -Invert:$invert) { | |
$InterestingCoordinates += [PSCustomObject]@{x=@($x, $mandelbrot.x_range[$x]); y=@($y, $mandelbrot.y_range[$y])} | |
} | |
if($x -eq $Columns - 1) { | |
$x = 0 | |
Write-Host | |
} | |
} | |
} | |
end { | |
if(($DebugPreference -gt "SilentlyContinue") -and $InterestingCoordinates) { | |
Write-Debug "Interesting coordinates: $(($InterestingCoordinates | Out-String -Stream) -join "`n")" | |
$InterestingCoordinates = @() | |
} | |
$Script:LastFavorite = ($FavoriteView + 1) % $MandelbrotFavoriteViews.Count | |
$fav = $MandelbrotFavoriteViews[$Script:LastFavorite] | |
$Mandelbrot.HorizontalViewOffset = $fav.HorizontalViewOffset | |
$Mandelbrot.VerticalViewOffset = $fav.VerticalViewOffset | |
$Mandelbrot.ZoomViewDistance = $fav.ZoomViewDistance | |
$ColorViewLimit = $fav.ColorViewLimit | |
if($DebugPreference -gt "SilentlyContinue") { | |
# Only used for debugging new locations: | |
Write-Debug "color range: $color_range" | |
} | |
$color_range = $null | |
return | |
} | |
} | |
Export-ModuleMember New-Mandelbrot -Variable MandelbrotFavoriteViews |