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