fireworks; script
7 November 2010 — 16 November 2010
  Winter Style | Home » fireworks
 
 

This was a fun mini-project to exercise my PowerShell scripting skills ;) I set out with the goal of creating some sort of quick, interactive, “custom drawing” script that you could copy-and-paste from an email to a console window and start playing. The “fireworks” program is what I came up with.

Sadly, despite my manual minification, the script is well over two thousand characters long, and email clients simply prefer inserting unrequested line breaks to radically expanding the screen width. Still, all the other aspects are there: you can interact with the number keys to launch fireworks, it fills the whole console window with custom ASCII art, and it uses all sorts of cheap hacks and shortcuts to try and save space ;)

But line length is no obstacle to HTML! Simply copy the gibberish below, open a PowerShell console window, and paste it in (system menu » Edit » Paste). Come on, would the internet lie to you? :)

$u=$Host.UI.RawUI;$s=$u.WindowSize;$w=$s.Width-1;$h=$s.Height-3;$o=$u.WindowPosition;$ns='Management.Automation.Host';$nsc="$ns.Coordinates";$nsb="$ns.BufferCell";[int[]]$ps=,0*9;$sp=$w/$ps.Length;0..($ps.Length-1)|%{$ps[$_]=$sp/2+$_*$sp};$n=0;$b=[Array]::CreateInstance([Management.Automation.Host.BufferCell],$h+3,$w+1);0..$w|%{$c=$b[($h+1),$_];$d=$b[($h+2),$_];$c.ForegroundColor=$d.ForegroundColor=7;$c.Character='_';$p=$ps[$n];if($_-eq$p-1){$d.Character='‘}elseif($_-eq$p){$d.Character="$($n+1)"}elseif($_-eq$p+1){++$n;$d.Character='‘};$b[($h+1),$_]=$c;$b[($h+2),$_]=$d};$u.SetBufferContents($o,$b);$fps="z.'`n:_\","z*'`n*_\","z.::`n::::","zzz:`n'.\'`n-=zo",".\'`n>zo”%{$_.Replace('z',' ')};function ra($i,$a){get-random -mi $i -ma $a}function fv($ch){switch($ch){'.'{"'"}"'"{'.'}'_'{' '}'\'{'/'}'/'{'\'}default{$ch}}}function fh($ch){switch($ch){'\'{'/'}'/'{'\'}'>'{'<'}'<'{'>'}default{$ch}}}function flh($l,$i){$a=$l.ToCharArray()|%{fh $_};[Array]::Reverse($a);$s=new-object String(,$a);$(if($i-ge2){$s.Substring(1)}else{$s})}function flv($l){$a=$l.ToCharArray()|%{fv $_};new-object String(,$a)}function s($i){$fp=$fps[$i].Split("`n")|%{$_+(flh $_ $i)};$l=$fp.Length-1;$fp+=0..$l|%{@(if($_-eq0-and$i-ge3){}else{flv $fp[$l-$_]})};$fp}function v($ch){!'_|/\<>=-o'.Contains($ch)};$yu=[Math]::Round($h*3/80+3.6);function nf($i){--$i;$vx=$(if($i-ge6){-1}elseif($i-lt3){1}elseif(ra 0 2){1}else{-1})*(ra 2($yu-1));$vy=(ra 4 $yu);@{ex=0;x=$ps[$i];y=0;vx=$vx;vy=$vy;t=0}};$bct=[Enum]::Parse([Management.Automation.Host.BufferCellType],'3');[int]$ln=[Math]::Max(1,[DateTime]::Today.Day%10);$fs=,(nf $ln);$ss=@{};do{$gs=@();$fs|%{$k=new-object $nsc($_.x,($h-$_.y));if($_.ex){$fp=s(ra 0($fps.Length-1));$fph=$fp.Length-1;[int]$fpho=[Math]::Ceiling($fp.Length/2);$fpw=$fp[0].Length-1;[int]$fpwo=[Math]::Floor($fp[0].Length/2);$cl=ra 9 14;0..$fpw|%{$x=$_;0..$fph|%{$y=$_;$fk=new-object $nsc(($k.x+$x-$fpwo),($k.y+$y-$fpho));$ch=$fp[$y][$x];$lcl=$(if(v $ch){$cl}else{7});$ss[$fk]=new-object $nsb($ch,$lcl,0,2)}}}else{$ss[$k]=new-object $nsb('ยท',7,0,$bct);$_.t+=0.4;$_.x+=$_.vx;$oy=$_.y;$_.y+=$_.vy*$_.t-3*$_.t*$_.t/2;if($_.y-le$oy){$_.ex=1};$gs+=$_}};$fs=$gs;$ks=@($ss.Keys);$ks|%{$c=$ss[$_];$r=[int]$c.BufferCellType-1;$rm=0;if($r-ge0){$c.BufferCellType=$r}else{switch($c.Character){{v $_}{$c.ForegroundColor=$(if($c.ForegroundColor-eq15){ra 2 4}elseif($c.ForegroundColor-ge9){15}else{$rm=1;0})}default{if(($c.ForegroundColor=[int]$c.ForegroundColor+1)-gt8){$c.ForegroundColor=0;$rm=1};break}}};$ss[$_]=$c;if($_.x-ge0-and$_.y-ge0-and$_.x-le$w-and$_.y-le$h){$b[$_.y,$_.x]=$c};if($rm){$ss.Remove($_)}};$u.SetBufferContents($o,$b);sleep -m 200;if($u.KeyAvailable){$ki=$u.ReadKey(14);$u.FlushInputBuffer();if($ki.KeyDown){$ch=$ki.Character;if($ln=$(if([char]::IsDigit($ch)){[int]"$ch"}else{0})){$fs+=nf $ln}}}}while($fs-or$ss.Count-or$ln)
 

Or, if you prefer, read through the long-hand version. Honestly, though, it's not much better ;) I wrote it with minification in mind, so while you get proper spacing and indenting, all the terse variable names are the same. Plus I actually made some tweaks to the program in its condensed form, so what you see is not quite what you get, but they are largely the same.

So how does it work? At a high level, it follows a normal game loop pattern: on every iteraton, we update our state (a list of fireworks), display it on screen (an array of BufferCells), and check for user input. If we get a non-numeric character, and all the current fireworks have finished, we exit.

Of course, what would be the fun in keeping things all nice and clean like that? :) In reality, fireworks do start out in a list, each holding on to randomly-initialized x and y position and vx and vy velocity variables for the very, very simple physics simulation (adjusted to accomodate the console’s blocky nature). On each iteration, we add a dot for the firework’s current postion, calculate the new x and y and, if we've reached the top of the parabola, set exploded to true :)

Once a firework has exploded, we switch from updating the physics simulation to displaying the starburst. As all the patterns are symmetrical, we only store the top-left portion, flipping it horizontally and vertically to display the full burst on screen. The outer parts are given a random color, while the inner structural parts are given the same grey as the dots from the launch trail. After the explosion is in the screen buffer, the firework object is removed from the update list, and the visual is left to decay.

Every time a cell is modified in the screen buffer, we also save off the cell’s coordinates in a list. During the iteraton, after the fireworks have been processed, we go through each cell and update its state. A cell will stay on screen for two iterations before undergoing a transition to a darker color (borrowing BufferCellType to store the countdown). After the first color transition, the only darker color is black, at which point the cell is officially finished and removed from the list.

And there you have it! A fine way to waste a few hours over the course of a couple weekends :)

 
 
Go back a page |  fireworks.ps1 | Resume Entry | @ | Copyright © 2010-2011 Paul A Hansen. Some rights reserved.