Skip to content

Commit bdbf54d

Browse files
committed
Feature: Lemmings screensaver.
1 parent 8d07b07 commit bdbf54d

File tree

2 files changed

+164
-1
lines changed

2 files changed

+164
-1
lines changed

DTC.Core

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Code authored by Dean Edis (DeanTheCoder).
2+
// Anyone is free to copy, modify, use, compile, or distribute this software,
3+
// either in source code form or as a compiled binary, for any non-commercial
4+
// purpose.
5+
//
6+
// If you modify the code, please retain this copyright header,
7+
// and consider contributing back to the repository or letting us know
8+
// about your modifications. Your contributions are valued!
9+
//
10+
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Diagnostics;
14+
using DTC.Core;
15+
using DTC.Core.Extensions;
16+
using G33kShell.Desktop.Console.Controls;
17+
using JetBrains.Annotations;
18+
using SkiaSharp;
19+
20+
namespace G33kShell.Desktop.Console.Screensavers;
21+
22+
/// <summary>
23+
/// A canvas to displaying walking lemmings.
24+
/// </summary>
25+
[DebuggerDisplay("LemmingsCanvas:{X},{Y} {Width}x{Height}")]
26+
[UsedImplicitly]
27+
public class LemmingsCanvas : ScreensaverBase
28+
{
29+
private const int MaxSprites = 5;
30+
private readonly Stopwatch m_spawnTimer = Stopwatch.StartNew();
31+
private readonly List<Sprite> m_sprites = [];
32+
private double[,,] m_frames;
33+
private int m_frameWidth;
34+
private int m_frameHeight;
35+
private double m_spawnTimeSecs;
36+
37+
public LemmingsCanvas(int screenWidth, int screenHeight) : base(screenWidth, screenHeight)
38+
{
39+
Name = "lemmings";
40+
}
41+
42+
public override void BuildScreen(ScreenData screen)
43+
{
44+
base.BuildScreen(screen);
45+
46+
LoadFrames(out m_frameWidth, out m_frameHeight, out m_frames);
47+
}
48+
49+
private static void LoadFrames(out int frameWidth, out int frameHeight, out double[,,] frames)
50+
{
51+
const string pngData =
52+
"iVBORw0KGgoAAAANSUhEUgAAAA4AAACgCAYAAAA8Xl1PAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAABF1JREFUaN7Fma124zAQhcc+9oJ9gGIHhJcXbFBBSIqXxLwgRn2IIheUt2SxQxYUNSA8vMDGfoQEaIEtZTQa/Tnt1ufkpE18M5Y8+nRnnACAgAlHZnzSkP/veGGqCUZR/o7eG4C8cgmRgIpPC1OYAIDAlycFx2U9nHBbuS8VR8OH6Fv280TOqhyHjKSO69Vw4tWMn9VTTSZhFLgOgV/irRbFXKgX/V6+Uph4sML2+QkAAH7dABRzESgMGJs1oozmimoKD9ugqAldHetS//XdfnjvPhJ7RPFWsyLnGGlquUT6ejxsrQJ6mewY6eXaVocpxJc8XoVNrOdhAyKvHP+zudqQddnY1+n5UhsTE/n7sNzss9oQQMXk6mmhRzsua5MGhvBuiMRFo7eHvR15RZiDkp0yR5tVNRGBa5JlzrqMYI6NoX4CjCkWJZTRZvcbhQ2AGFgx3JE/jIeSuk421iYaSorvE14+WOSlnAQViw0ycZlL1H0kw7iYBZ3aAKU4Y6GAylX8yy5c8MJA1rCwumibixM2xBxZ/I3TdeSVnz0ac6ST4gyRM6IUyGiuH8hwRNZZgcdZSdLRKF5nxeIwlHKG+3CgRIso87X8s1FJz22qn5M5NNqnwcoQKnqRifAxJ8MCPCk4KscgNavYUckTqQjPsDFGfKLLJCWib4UNF9ymKr8fhNIxcmKZqwRk38kcypomxugik4SZw5mklCv/8vczAU4LTzXHUe24rK20yyjlqM85ju9JHUIA7HMcnkfzL+tSCNG38bXVy2MXZFvSKYvYrDuYBe1FBxbN7jd+dNAPqLOyo4OQuphv4pN8XQoNFy4CZHKhlg+FOtkGKPNSD1vY7TdR6zHjCy+h1coDLrgxXq80MHUfCbT7TstTuk/+f+ZktiYQRshpYTaEjKKMHpg/mD0ZF4H2c05BBimwtso4QFFsiH71ea5DY05eDbWV6FtvbWUWZX07iN9qsS4jYFU+FGpsQS0LG7CC7crLYxefchhY2CgVc2FYM02YXM2Mfk44Vyf1c9AND+/nOARh/ZzAGsu7P9pWx9f0c4JKJKOf05zLCrddCewd800S0s8RP+swIcuckEqH7eeMljS+nxPqcyQ6pjFnFEcZJLkzT9qtJLDa56dpJRK9LdZ+jg0bKipabhl34m4PMLspAGBjbcukvmgAMPiBGHRc1s+xICQ7J3JHWMNX41pE9WUga767nwMT+zka4ZqIfk6MyPA53N9RZSB1Vl9Hucv7OZhsDnfFVnPRsLoo5WY3E2EVVVtxUdv9StUefN3hEMP1it0/nP0cl2VJcWbs9nZjRMfp7OewmXTYQnJbjWOU2UFqLNG3RhapRtA311ac37kLiIjhpJ4qNT4CNHxH98ffim0GeQlwXNaOp/MQ/6zcpFygxzFrK1RXTaqtoh54XVQiqVLp91NcyuFqziV2OquoHXnSs/KYjq5yHdQ1emurswHaBAmM9fj6knjavwH9HMP3kO8BAP4B6I1KaTKZzKUAAAAASUVORK5CYII=";
53+
54+
// Load sprite image frames.
55+
using var img = SKBitmap.Decode(Convert.FromBase64String(pngData));
56+
const int frameCount = 8;
57+
frameHeight = img.Height / frameCount;
58+
frameWidth = img.Width;
59+
frames = new double[frameCount, frameWidth, frameHeight];
60+
61+
for (var frame = 0; frame < frameCount; frame++)
62+
{
63+
for (var y = 0; y < frameHeight; y++)
64+
{
65+
for (var x = 0; x < frameWidth; x++)
66+
{
67+
var pixel = img.GetPixel(x, y + frame * frameHeight);
68+
var luminosity = new Rgb(pixel.Red, pixel.Green, pixel.Blue).Luminosity();
69+
frames[frame, x, y] = Math.Pow(luminosity, 1.5);
70+
}
71+
}
72+
}
73+
}
74+
75+
public override void UpdateFrame(ScreenData screen)
76+
{
77+
screen.Clear(Foreground, Background);
78+
var highResScreen = new HighResScreen(screen);
79+
80+
// Remove any dead sprites.
81+
m_sprites.RemoveAll(w => w.IsDead);
82+
83+
// Display current sprites.
84+
foreach (var sprite in m_sprites)
85+
sprite.Draw(highResScreen, Foreground, Background, screen.Width);
86+
87+
// Spawn more sprites.
88+
if (m_spawnTimer.Elapsed.TotalSeconds >= m_spawnTimeSecs || m_sprites.Count == 0)
89+
{
90+
if (m_sprites.Count < MaxSprites)
91+
{
92+
var random = Random.Shared;
93+
m_sprites.Add(new Sprite(random, screen, m_frameWidth, m_frameHeight, m_frames));
94+
m_spawnTimeSecs = random.NextDouble() * 4 + 1;
95+
m_spawnTimer.Restart();
96+
}
97+
}
98+
}
99+
100+
private class Sprite
101+
{
102+
private readonly int m_frameWidth;
103+
private readonly int m_frameHeight;
104+
private Stopwatch m_stopwatch;
105+
private readonly double[,,] m_frames;
106+
private readonly int m_y;
107+
private readonly int m_direction;
108+
private readonly double m_speed;
109+
private readonly int m_frameCount;
110+
111+
public bool IsDead { get; private set; }
112+
113+
public Sprite(Random random, ScreenData screen, int frameWidth, int frameHeight, double[,,] frames) :
114+
this(random.Next(0, screen.Height * 2 - frameHeight),
115+
random.NextBool() ? 1 : -1,
116+
random.NextDouble() * 6 + 5,
117+
frameWidth,
118+
frameHeight,
119+
frames)
120+
{
121+
}
122+
123+
private Sprite(int y, int direction, double speed, int frameWidth, int frameHeight, double[,,] frames)
124+
{
125+
m_y = y;
126+
m_direction = direction;
127+
m_speed = speed;
128+
m_frameWidth = frameWidth;
129+
m_frameHeight = frameHeight;
130+
m_frames = frames;
131+
132+
m_frameCount = frames.GetLength(0);
133+
}
134+
135+
public void Draw(HighResScreen highResScreen, Rgb foreground, Rgb background, int screenWidth)
136+
{
137+
m_stopwatch ??= Stopwatch.StartNew();
138+
139+
var time = m_stopwatch.Elapsed.TotalSeconds * m_speed;
140+
var frame = (int)time % m_frameCount;
141+
var spriteX = (int)Math.Round(time / m_frameCount * (m_frameWidth + 2) * m_direction);
142+
if (m_direction == -1)
143+
spriteX += screenWidth;
144+
145+
for (var y = 0; y < m_frameHeight; y++)
146+
{
147+
for (var x = 0; x < m_frameWidth; x++)
148+
{
149+
if (m_frames[frame, x, y] < 0.001)
150+
continue;
151+
152+
var px = m_direction == -1 ? spriteX + m_frameWidth - x : spriteX + x;
153+
if (px < screenWidth)
154+
highResScreen.Plot(px, m_y + y, m_frames[frame, x, y].Lerp(background, foreground));
155+
}
156+
}
157+
158+
// Check if Sprite is off the screen and mark as dead.
159+
if ((m_direction == -1 && spriteX + m_frameWidth < 0) || (m_direction == 1 && spriteX > screenWidth))
160+
IsDead = true;
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)