-
Notifications
You must be signed in to change notification settings - Fork 3
/
Column.cs
276 lines (250 loc) · 12.6 KB
/
Column.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
using System;
namespace MatrixScreen
{
/// <summary>
/// A column here is a single line/column in our matrix style display. It will draw a string of random characters, from top to bottom of the screen, at a random speed such that it is pleasantly observable.
/// The top of the screen is not always the actual top of the screen, and the bottom of the screen is not always the actual bottom of the screen.
/// Also, it's not always from top to bottom, sometimes its from bottom to top.
///
/// The charecters will be in green, except for the lead character which will sometimes be white.
/// While the column is diplayed on screen, some random charecters inside it will continue to change, randomly.
///
/// The top and bottom areas are randomly calculated. The buffer of characters will always be equal to the number rows that exist in the display.
///
/// Once an instance of a column is complete, it is discarded by the caller.
/// </summary>
internal class Column : IRenderable
{
private int _windowHeight = System.Console.WindowHeight;
private int _totalNodeCount; // length of the string for this column
private int _currentNodeCount; // How many nodes have been drawn.
private int _eraseThreshold; // Erase threshold must be equal to or lower than nodeCount. When the eraseThreshold number of charecters have been drawn, the tail of the string begins to erase.
private long _addRemoveTransitionDelay; // How long to wait after adding chars has completed before starting to removing them.
private long _addRemoveDelay; // Milliseconds between each add/remove of characters.
private long _addRemoveUpdateTime; // When do we actually add/removoe a character
private int _addNodeIndex; // The current node index where the next character is added while drawing.
private int _removeNodeIndex; // The current node index where the next charecter is erased while erasing.
private bool _highlightLeadNode; // Do we want th lead charecter to be white or the same as all others?
private bool _goingUp = false; // We go down by default, but we can go up.
private bool _erasing = false; // When we reach the erase threshold, we flip this flag and erase until we're done.
// Active nodes are random nodes which randomly change while on screen.
private int _activeNodeCount; // The number of nodes which remain actively changing after initially being drawn.
private int[] _activeNodeIndexes; // indexes to the column members which continue to be actively changing. The rest stay the charecter that it was originally assigned.
private int[] _activeNodeDelaysMax; // Max milliseconds between changes in the charecters in the string that do change. Actual number is a random number at runtime with this as the max.
private long[] _activeNodeUpdateTime;
private ColumnRenderer _renderer; // Manages drawing the column
private ColumnNode[] _nodeBuffer; // The array of characters to display in our column
private int _bufferSize;
private int _topIndex;
private int _bottomIndex;
public bool IsDone { get; private set; }
public int ColumnIndex { get; private set; } // Column position
private static readonly Random _random = new Random((int)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 42)); // 42 because hitch-hikers
internal static int GetRandomNumber(int minInclusive, int maxExclusive)
{
return _random.Next(minInclusive, maxExclusive);
}
private static char _getRandomChar()
{
return (char)GetRandomNumber(33, 127);
}
// Constants
private const char NULLCHAR = '\0';
private const char SPACECHAR = ' ';
private const int MINNODES = 10;
private const float ERASETHRESHOLDFACTOR = 0.5f;
private const int MINACTIVENODES = 1;
private const float ACTIVENODESCOUNTFACTOR = 0.15f;
private const int MINADDREMOVEDELAY = 0;
private const int MAXADDREMOVEDELAY = 150;
private const float HIGHLIGHTLEADNODECHANCE = 0.5f;
private const int ACTIVENODEMAXDELAYLOW = 500;
private const int ACTIVENODEMAXDELAYHIGH = 4000;
private const int ACTIVENODEMINDELAY = 10;
private const float GOUPCHANCE = 0.01f;
private const float PARTIALSCREENCHANCE = 0.75f;
private const float PARTIALSCREENSIZE = 0.75f;
private const int ADDTOERASEDELAYMAX = 4000; // Max
internal Column(int columnIndex)
{
ColumnIndex = columnIndex;
_addRemoveDelay = GetRandomNumber(MINADDREMOVEDELAY, MAXADDREMOVEDELAY);
_highlightLeadNode = GetRandomNumber(0, 100) < (100 * HIGHLIGHTLEADNODECHANCE);
_goingUp = GetRandomNumber(0, 100) < (100 * GOUPCHANCE);
// Screen line indexes are 1 based, not zero based like arrays.
_topIndex = 1;
_bottomIndex = _windowHeight + 1;
_bufferSize = _bottomIndex + 1;
// See about doing a partial screen hight column
if (_windowHeight > MINNODES && GetRandomNumber(0, 100) < (100 * PARTIALSCREENCHANCE))
{
// We're going with a partial screen sized column
// Get a random index for the top position
int tmpFactoredWindowHeight = (int)(_windowHeight * PARTIALSCREENSIZE);
_topIndex = GetRandomNumber(-tmpFactoredWindowHeight, tmpFactoredWindowHeight);
if (_topIndex > _windowHeight - MINNODES)
{
_topIndex = _windowHeight - MINNODES;
}
if (_topIndex < 1)
{
_topIndex = 1;
}
// Then for the bottom position, just use a random length...
_bottomIndex = _topIndex + GetRandomNumber(0, (int)(_windowHeight * PARTIALSCREENSIZE)) + MINNODES;
if (_bottomIndex > _windowHeight)
{
_bottomIndex = _windowHeight;
}
}
_totalNodeCount = (_bottomIndex - _topIndex) +1;
_activeNodeCount = GetRandomNumber(0, (int)(_totalNodeCount * ACTIVENODESCOUNTFACTOR)) + MINACTIVENODES;
// We don't want the erase threshold to always be lower than the totalNodeCount so a good proportion of the time the column does not start erasing before it is done drawing.
int tmpFactoredTotal = (int)(_totalNodeCount * ERASETHRESHOLDFACTOR);
_eraseThreshold = _totalNodeCount - GetRandomNumber(-tmpFactoredTotal, tmpFactoredTotal);
if (_eraseThreshold > _totalNodeCount)
{
_eraseThreshold = _totalNodeCount;
_addRemoveTransitionDelay = GetRandomNumber(0, ADDTOERASEDELAYMAX);
}
if (_goingUp)
{
_addNodeIndex = _bottomIndex;
_removeNodeIndex = _bottomIndex;
}
else
{
_addNodeIndex = _topIndex;
_removeNodeIndex = _topIndex;
}
_activeNodeIndexes = new int[_activeNodeCount];
_activeNodeDelaysMax = new int[_activeNodeCount];
_activeNodeUpdateTime = new long[_activeNodeCount];
for (int x = 0; x < _activeNodeCount; x++)
{
_activeNodeIndexes[x] = GetRandomNumber(_topIndex, _bottomIndex + 1);
_activeNodeDelaysMax[x] = GetRandomNumber(ACTIVENODEMAXDELAYLOW, ACTIVENODEMAXDELAYHIGH);
_activeNodeUpdateTime[x] = GetRandomNumber(ACTIVENODEMINDELAY, _activeNodeDelaysMax[x]);
}
_nodeBuffer = new ColumnNode[_bufferSize];
_renderer = new ColumnRenderer(columnIndex, _bufferSize);
}
private void AddNode()
{
bool updated = false;
if (_goingUp)
{
if (_addNodeIndex < (_bufferSize - 1))
{
// Turn off the highlight on the previous one, being careful not to go beyond the array bounds
_nodeBuffer[_addNodeIndex + 1].highLight = false;
updated = true;
}
if (_addNodeIndex >= _topIndex)
{
_nodeBuffer[_addNodeIndex] = new ColumnNode() { character = _getRandomChar(), highLight = _highlightLeadNode };
_currentNodeCount++;
_addNodeIndex--;
updated = true;
}
}
else
{
// Going down
if (_addNodeIndex > 0)
{
// Turn off the highlight on the previous one, being careful not to go beyond the array bounds
_nodeBuffer[_addNodeIndex - 1].highLight = false;
updated = true;
}
if (_addNodeIndex <= _bottomIndex)
{
_nodeBuffer[_addNodeIndex] = new ColumnNode() { character = _getRandomChar(), highLight = _highlightLeadNode };
_currentNodeCount++;
_addNodeIndex++;
updated = true;
}
}
if (updated)
{
_renderer.UpdateColumnData(_nodeBuffer);
}
}
private bool RemoveNode()
{
if (_currentNodeCount > 0)
{
if (_goingUp)
{
// going up
if (_removeNodeIndex >= _topIndex)
{
_nodeBuffer[_removeNodeIndex--].character = SPACECHAR; //space character for delete?
_currentNodeCount--;
}
}
else
{
// going down
if (_removeNodeIndex <= _bottomIndex)
{
_nodeBuffer[_removeNodeIndex++].character = SPACECHAR; //space character for delete?
_currentNodeCount--;
}
}
_renderer.UpdateColumnData(_nodeBuffer);
return true;
}
return false;
}
public void Update(long elapsedTime)
{
// handle updating existing active nodes.
for (int x = 0; x < _activeNodeCount; x++)
{
_activeNodeUpdateTime[x] -= elapsedTime;
if (_activeNodeUpdateTime[x] <= 0)
{
_activeNodeUpdateTime[x] = GetRandomNumber(10, _activeNodeDelaysMax[x]);
int i = _activeNodeIndexes[x];
char currentChar = _nodeBuffer[i].character;
if (currentChar != NULLCHAR && currentChar != SPACECHAR)
{
_nodeBuffer[i].character = _getRandomChar();
_renderer.UpdateColumnData(_nodeBuffer);
}
}
}
// handle adding/removing nodes
_addRemoveUpdateTime += elapsedTime;
if (_addRemoveUpdateTime > _addRemoveDelay)
{
AddNode();
if (!_erasing && _currentNodeCount >= _eraseThreshold)
{
if (_addRemoveTransitionDelay > 0)
{
_addRemoveTransitionDelay -= _addRemoveUpdateTime;
}
else
{
_erasing = true;
}
}
if (_erasing)
{
if (!RemoveNode())
{
IsDone = true;
}
}
_addRemoveUpdateTime = 0;
}
_renderer.Update(elapsedTime);
}
public void Draw(long elapsedTime)
{
_renderer.Draw(elapsedTime);
}
}
}