-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathCns.cs
128 lines (112 loc) · 4.65 KB
/
Cns.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
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace Loaf
{
// The Cns class handles console output for Loaf. This consists of two pieces of functionality.
//
// Cns.Out prints lines and does the slow-modem-transfer effect.
// Cns.Choice is much more interesting; it uses the Dec system to make it easy to print out prompts.
//
// Using the Dec system for prompt elements seems like massive overkill, but it has advantages.
// First, it makes prompts very easy to mod.
// Dec doesn't (yet) support mods, but Locations could be modded by just making more LocationDec's, while other UI elements could be modded with use of new Dec's and with Harmony.
// Second, it allows you to attach information to prompts easily.
// During the development of Loaf, I added custom labels and keypresses to prompts; on a GUI game, one could easily add tooltips or other scriptable elements.
public static class Cns
{
// This is our base Choice class.
// Note that it's tagged Dec.Abstract so that choices for different UI elements end up in different Dec hierarchies and can share DecNames.
// Otherwise you'd need a unique name for every item that inherits from ChoiceDec.
[Dec.Abstract]
public abstract class ChoiceDec : Dec.Dec
{
private string label;
private char key = '\0';
public string Label
{
get
{
return label ?? DecName;
}
}
public char Key
{
get
{
return (key != '\0') ? key : Label[0];
}
}
}
// I also want to support choices for things that aren't ChoiceDecs, so this is a thin adapter to make ChoiceDecs work transparently.
// Note that, if it isn't given an explicit list of items, it just yanks every possible item out of the Dec database.
internal static T Choice<T>(T[] items = null, bool longForm = false) where T : ChoiceDec
{
return Choice(items ?? Dec.Database<T>.List, choice => choice.Label, key: choice => choice.Key, longForm: longForm);
}
internal static T Choice<T>(T[] items, Func<T, string> label, Func<T, char> key = null, bool longForm = false)
{
string separator;
string prompt;
if (longForm)
{
separator = "\n";
prompt = "\n";
}
else
{
separator = ", ";
prompt = "? ";
}
if (key == null)
{
key = item => label(item)[0];
}
T[] choices = items;
string choiceList = string.Join(separator, choices.Select(choice =>
{
string choiceLabel = label(choice);
int index = choiceLabel.IndexOf(key(choice), StringComparison.InvariantCultureIgnoreCase);
return $"{choiceLabel.Substring(0, index)}({char.ToUpper(key(choice))}){choiceLabel.Substring(index + 1)}";
}));
Out($"{choiceList}{prompt}", crlf: false);
while (true)
{
// get rid of pending input
while (Console.KeyAvailable)
{
Console.ReadKey(true);
}
var userKey = Console.ReadKey(true);
var success = choices.Where(c => char.ToLower(key(c)) == char.ToLower(userKey.KeyChar));
var choice = success.SingleOrDefault();
if (success.Any())
{
Out(""); // we need a CRLF
return choice;
}
}
}
internal static void Out(string str, bool crlf = true, ConsoleColor color = ConsoleColor.Gray)
{
Console.ForegroundColor = color;
if (crlf)
{
str = str + "\n";
}
var stopwatch = Stopwatch.StartNew();
// Print it slow to make it feel more like a game coming over a modem.
for (int idx = 0; idx < str.Length; ++idx)
{
while (!Config.Global.suppressDelay && stopwatch.ElapsedTicks < (idx * Stopwatch.Frequency * 10 / Config.Global.baud)) { }
Console.Write(str[idx]);
}
if (!Config.Global.suppressDelay && crlf)
{
Thread.Sleep((int)(Config.Global.crlfDelay * 1000));
}
Console.ForegroundColor = ConsoleColor.White;
}
}
}