Merry Christmas; Santa Claus screen mate

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.

This entry was posted in Coding and tagged , , , , , . Bookmark the permalink.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.