If you’re old like me, you might remember screen mates or desktop pets. These were programs you’d run on your computer that put little interactive creatures on your screen. The first one I ever encountered was eSheep, back in the Windows 95/98 days. You can find a recreation on the Microsoft Store, and also on the author’s github page.
Well, I made a Santa Claus one for fun.
Santa.bat.txt (remove the .txt extension). Middle-click on Santa to close the script, left-click to pick him up, and right-click for a few catch phrases.
START POWERSHELL -COMMAND "Get-Content """%~f0""" | Select -Skip 1 | Out-String | Invoke-Expression" & EXIT /B
Add-Type -AssemblyName System.Windows.Forms, System.Speech
Add-Type -namespace Win32 -name UI -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
[void][Win32.UI]::ShowWindowAsync((Get-Process -PID $PID).MainWindowHandle, 0) # Hide console window
$ImageStream = [System.IO.MemoryStream][System.Convert]::FromBase64String('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')
class Sprite {
[Windows.Forms.Form]$Form
[Windows.Forms.PictureBox]$PictureBox
[System.Drawing.Image]$Image
[System.Drawing.Size]$CellSize
[System.Drawing.Size]$SheetSize
[Hashtable]$Animations
Sprite([System.Drawing.Image]$Image, [System.Drawing.Size]$Size) {
$this.Form = New-Object Windows.Forms.Form -Property @{
MinimumSize = $Size
ClientSize = $Size
TopMost = $True
Location = '0,0'
StartPosition = 0
FormBorderStyle = 0
ShowInTaskbar = $False
}
$this.Form.TransparencyKey = $this.Form.BackColor
$this.Image = $Image
$this.CellSize = $Size
$this.SheetSize = $Image.Size
$this.PictureBox = New-Object Windows.Forms.PictureBox -Property @{
Size = $Image.Size
Location = '0,0'
Image = $Image
}
$this.Form.Controls.Add($this.PictureBox)
}
Cell($Index) {
$Width = $this.CellSize.Width
$Height = $this.CellSize.Height
$IndexMultiplier = $Index * $Width
$X = $IndexMultiplier % $this.SheetSize.Width
$YIndex = [int][Math]::Floor($IndexMultiplier / $this.SheetSize.Width)
$Y = $YIndex * $Height
$this.PictureBox.Location = "-$X,-$Y"
}
}
Function ToolTip-Popup {
param ($Text, $Control, $X, $Y, $Time, $IsBalloon = $True)
$ToolTip = New-Object Windows.Forms.ToolTip -Property @{ IsBalloon = $IsBalloon }
$ToolTip.SetToolTip($Control, ' ')
$ToolTip.Show($Text, $Control, $X, $Y, $Time)
$ToolTip.add_popup({ $this.dispose() })
}
$Speaker = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$Speaker.Rate = -6
$SpriteSize = '21,20'
$SpriteSize = '42,40'
$LoadImage = [Drawing.Image]::FromStream($ImageStream)
$Santa = New-Object Sprite($LoadImage, $SpriteSize)
$Santa.PictureBox.add_Mousedown({
If ($_.Button -eq 'Left') {
$global:Holding = $_.Location
$Santa.cell(20)
}
})
$Santa.PictureBox.add_Mousemove({
If ($global:Holding) {
$Mouse = [System.Windows.Forms.Cursor]::Position
$Santa.Form.SetDesktopLocation(
($Mouse.X - $Santa.Form.Width / 2),
($Mouse.Y - $Santa.Form.Height / 2)
)
}
})
$Santa.PictureBox.add_Mouseup({
$global:Holding = $null
})
$Santa.PictureBox.Add_Click({
If ($_.Button -eq 'Middle') {
$Santa.Form.Close()
Stop-Process $PID
}
If ($_.Button -eq 'Right') {
$ClickSequence = Get-Random -Max 3
Switch ($ClickSequence) {
0 {
$global:AnimState = 'laugh'
$global:Sequence = $Santa.Animations['laugh']
$global:AnimChangeCountdown = 6
$Message = 'Ho ho ho!'
}
1 {
$global:AnimState = 'wave'
$global:Sequence = $Santa.Animations['wave']
$global:AnimChangeCountdown = 10
$Message = 'Merry Christmas!'
}
2 {
$global:AnimState = 'wave'
$global:Sequence = $Santa.Animations['wave']
$global:AnimChangeCountdown = 10
$Message = 'Happy New Year!'
}
}
ToolTip-Popup $Message $Santa.Form 21 15 ($global:AnimChangeCountdown * 200)
$Speaker.SpeakAsync($Message)
}
})
$Santa.Animations = @{
stand_down = 0
stand_up = 1
stand_left = 2
stand_right = 3
walk_down = 0,4,0,8
walk_up = 1,5,1,9
walk_left = 2,6,2,10
walk_right = 3,7,3,11
laugh = 16,17
wave = 18,19
}
# 7 0 1
# \ | /
# 6 -+- 2
# / | \
# 5 4 3
$WalkAnimationMap = @('walk_up', 'walk_right', 'walk_right', 'walk_right', 'walk_down', 'walk_left', 'walk_left', 'walk_left')
$StandAnimationMap = @('stand_up', 'stand_right', 'stand_right', 'stand_right', 'stand_down', 'stand_left', 'stand_left', 'stand_left')
$SpriteDirection = Get-Random -Max 8
$AnimState = 'walk'
$AnimChangeCountdown = Get-Random -Min 10 -Max 20
$Sequence = $Santa.Animations[$WalkAnimationMap[$SpriteDirection]]
$AnimationTimer = New-Object Windows.Forms.Timer -Property @{
Interval = 20
Add_Tick = {
If ($global:Holding) { Return }
$global:TICKS += 20
If ($TICKS % 200 -eq 0) {
$global:AnimFrame++
$global:AnimChangeCountdown--
If ($AnimChangeCountdown -le 0) {
Switch -regex ($AnimState) {
'walk' {
$global:AnimState = 'stand'
$global:Sequence = $Santa.Animations[$StandAnimationMap[$SpriteDirection]]
$global:AnimChangeCountdown = Get-Random -Min 5 -Max 10
}
default {
$global:AnimState = 'walk'
$global:SpriteDirection = Get-Random -Max 8
$global:Sequence = $Santa.Animations[$WalkAnimationMap[$SpriteDirection]]
$global:AnimChangeCountdown = Get-Random -Min 10 -Max 20
}
}
}
}
$Santa.cell(($Sequence[($global:AnimFrame) % $Sequence.count]))
Switch ($SpriteDirection) {
0 { $MoveY = -1 }
1 { $MoveX = 1; $MoveY = -1 }
2 { $MoveX = 1 }
3 { $MoveX = 1; $MoveY = 1 }
4 { $MoveY = 1 }
5 { $MoveX = -1; $MoveY = 1 }
6 { $MoveX = -1 }
7 { $MoveX = -1; $MoveY = -1 }
default { $MoveX = 0; $MoveY = 0 }
}
$SpriteX = $Santa.Form.Location.X
$SpriteY = $Santa.Form.Location.Y
If ($AnimState -eq 'walk') {
$SpriteX += $MoveX * 2
$SpriteY += $MoveY * 2
}
$Screen = [System.Windows.Forms.SystemInformation]::VirtualScreen
$Edge = @{
left = $Screen.Left - $Santa.Form.Size.Width
right = $Screen.Right
top = $Screen.Top - $Santa.Form.Size.Height
bottom = $Screen.Bottom
}
If ($SpriteX -gt $Edge.right) { $SpriteX = $Edge.left }
If ($SpriteX -lt $Edge.left) { $SpriteX = $Edge.right }
If ($SpriteY -gt $Edge.bottom) { $SpriteY = $Edge.top }
If ($SpriteY -lt $Edge.top) { $SpriteY = $Edge.bottom }
$Santa.Form.Location = @{
X = $SpriteX
Y = $SpriteY
}
}
}
$Santa.Form.add_Load({ $AnimationTimer.Start() })
$Santa.Form.ShowDialog()
Over the years I’ve made a few little similar applications/scripts. I made some with Javascript that made little wandering video game characters on a webpage. I made one in AutoIt that had a baby Metroid that would follow your cursor. I never got as interactive as the original eSheep though, which could detect window edges and interact with them.
Anyway, over the past several days I’ve been tinkering with Powershell, and I made a little Santa Claus. I created a sprite sheet from a Bomberman sprite that I obtained from The Spriters Resource. I then converted the image into a base64 string to include directly in the script (that’s the extremely long string in the $ImageStream variable). I added on the Batch Powershell launcher snippet to the top of the script and saved it as .bat.
As usual, this started as experimenting with different Powershell functions and commands. I learned how to create a custom class with methods, how to create a window/form with transparency, and how to load an image via base64 string.