1#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7pub enum Hue {
8 Black,
9 Red,
10 Green,
11 Yellow,
12 Blue,
13 Magenta,
14 Cyan,
15 White,
16}
17
18#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub struct Look {
21 pub hue: Hue,
22 pub bold: bool,
23 pub reversed: bool,
24 pub blink: bool,
25}
26
27impl Look {
28 pub const fn new(hue: Hue) -> Look {
29 Look {
30 hue,
31 bold: false,
32 reversed: false,
33 blink: false,
34 }
35 }
36 pub const fn bold(mut self) -> Look {
37 self.bold = true;
38 self
39 }
40 pub const fn reversed(mut self) -> Look {
41 self.reversed = true;
42 self
43 }
44 pub const fn blink(mut self) -> Look {
45 self.blink = true;
46 self
47 }
48}
49
50pub const SPARK: f64 = 10.0;
52pub const WARM: f64 = 50.0;
53pub const HOT: f64 = 250.0;
54pub const ERUPTING: f64 = 1000.0;
55pub const MYTHIC: f64 = 5000.0;
56
57pub const HALFLIFE_MS: f64 = 20_000.0;
62pub const INCREMENT: f64 = 2.0;
63
64pub fn decay(heat: f64, dt_ms: f64) -> f64 {
66 if dt_ms <= 0.0 {
67 return heat;
68 }
69 heat * 0.5_f64.powf(dt_ms / HALFLIFE_MS)
70}
71
72#[derive(Clone, Copy, PartialEq, Eq, Debug)]
73pub enum Tier {
74 Zero,
75 Cold,
76 Spark,
77 Warm,
78 Hot,
79 Erupting,
80 Mythic,
81}
82
83impl Tier {
84 pub fn of(heat: f64) -> Tier {
85 match heat {
86 h if h >= MYTHIC => Tier::Mythic,
87 h if h >= ERUPTING => Tier::Erupting,
88 h if h >= HOT => Tier::Hot,
89 h if h >= WARM => Tier::Warm,
90 h if h >= SPARK => Tier::Spark,
91 h if h >= 1.0 => Tier::Cold,
92 _ => Tier::Zero,
93 }
94 }
95
96 pub fn look(self) -> Look {
99 match self {
100 Tier::Zero | Tier::Cold => Look::new(Hue::White),
101 Tier::Spark => Look::new(Hue::White).bold(),
102 Tier::Warm => Look::new(Hue::Yellow),
103 Tier::Hot => Look::new(Hue::Yellow).bold(),
104 Tier::Erupting => Look::new(Hue::Red).bold(),
105 Tier::Mythic => Look::new(Hue::Red).reversed().blink(),
106 }
107 }
108
109 pub fn marker(self) -> &'static str {
111 match self {
112 Tier::Mythic => "???",
113 _ => "",
114 }
115 }
116}
117
118pub fn look(heat: f64) -> Look {
120 Tier::of(heat).look()
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn boundaries_are_inclusive_lower() {
129 assert_eq!(Tier::of(0.0), Tier::Zero);
130 assert_eq!(Tier::of(0.9), Tier::Zero);
131 assert_eq!(Tier::of(1.0), Tier::Cold);
132 assert_eq!(Tier::of(9.9), Tier::Cold);
133 assert_eq!(Tier::of(10.0), Tier::Spark);
134 assert_eq!(Tier::of(50.0), Tier::Warm);
135 assert_eq!(Tier::of(250.0), Tier::Hot);
136 assert_eq!(Tier::of(1000.0), Tier::Erupting);
137 assert_eq!(Tier::of(5000.0), Tier::Mythic);
138 assert_eq!(Tier::of(999999.0), Tier::Mythic);
139 }
140
141 #[test]
142 fn ladder_escalates_and_blinks_only_at_the_top() {
143 assert_eq!(Tier::Cold.look(), Look::new(Hue::White));
144 assert_eq!(Tier::Erupting.look(), Look::new(Hue::Red).bold());
145 for t in [
146 Tier::Zero,
147 Tier::Cold,
148 Tier::Spark,
149 Tier::Warm,
150 Tier::Hot,
151 Tier::Erupting,
152 ] {
153 assert!(!t.look().blink && !t.look().reversed);
154 }
155 assert!(Tier::Mythic.look().blink && Tier::Mythic.look().reversed);
156 }
157
158 #[test]
159 fn only_mythic_has_a_marker() {
160 assert_eq!(Tier::Mythic.marker(), "???");
161 assert_eq!(Tier::Warm.marker(), "");
162 }
163}