1 #include "catch/catch.hpp"
2 #include "calendar.h" // IWYU pragma: associated
3 
4 #include <string>
5 
6 // SUN TESTS
7 
8 // The 24-hour solar cycle is divided into four parts, as returned by four calendar.cpp functions:
9 //
10 // is_night : from the end of dusk, until the following sunrise (start of dawn)
11 // is_dawn  : begins at sunrise, lasts twilight_duration (1 hour)
12 // is_day   : from the end of dawn, until sunset (start of dusk)
13 // is_dusk  : begins at sunset, lasts twilight_duration (1 hour)
14 //
15 // These are inclusive at their endpoints; in other words, each overlaps with the next like so:
16 //
17 // 00:00 is_night
18 //   :   is_night
19 // 06:00 is_night && is_dawn ( sunrise )
20 //   :   is_dawn ( sunrise + twilight )
21 // 07:00 is_dawn && is_day
22 //   :   is_day
23 // 19:00 is_day && is_dusk ( sunset )
24 //   :   is_dusk ( sunset + twilight )
25 // 20:00 is_dusk && is_night
26 //   :   is_night
27 // 23:59 is_night
28 //
29 // The times of sunrise and sunset will naturally depend on the current time of year; this aspect is
30 // covered by the "sunrise and sunset" and solstice/equinox tests later in this file. Here we simply
31 // use the first day of spring as a baseline.
32 //
33 // This test covers is_night, is_dawn, is_day, is_dusk, and their behavior in relation to time of day.
34 TEST_CASE( "daily solar cycle", "[sun][night][dawn][day][dusk]" )
35 {
36     // Use sunrise/sunset on the first day (spring equinox)
37     static const time_point midnight = calendar::turn_zero;
38     static const time_point noon = calendar::turn_zero + 12_hours;
39     static const time_point today_sunrise = sunrise( midnight );
40     static const time_point today_sunset = sunset( midnight );
41 
42     REQUIRE( "Year 1, Spring, day 1 6:00:00 AM" == to_string( today_sunrise ) );
43     REQUIRE( "Year 1, Spring, day 1 7:00:00 PM" == to_string( today_sunset ) );
44 
45     // First, at the risk of stating the obvious
46     CHECK( is_night( midnight ) );
47     // And while we're at it
48     CHECK_FALSE( is_day( midnight ) );
49     CHECK_FALSE( is_dawn( midnight ) );
50     CHECK_FALSE( is_dusk( midnight ) );
51 
52     // Yep, still dark
53     CHECK( is_night( midnight + 1_seconds ) );
54     CHECK( is_night( midnight + 2_hours ) );
55     CHECK( is_night( midnight + 3_hours ) );
56     CHECK( is_night( midnight + 4_hours ) );
57 
58     // The point of sunrise is both "night" and "dawn"
59     CHECK( is_night( today_sunrise ) );
60     CHECK( is_dawn( today_sunrise ) );
61 
62     // Dawn
63     CHECK_FALSE( is_night( today_sunrise + 1_seconds ) );
64     CHECK( is_dawn( today_sunrise + 1_seconds ) );
65     CHECK( is_dawn( today_sunrise + 30_minutes ) );
66     CHECK( is_dawn( today_sunrise + 1_hours - 1_seconds ) );
67     CHECK_FALSE( is_day( today_sunrise + 1_hours - 1_seconds ) );
68 
69     // The endpoint of dawn is both "dawn" and "day"
70     CHECK( is_dawn( today_sunrise + 1_hours ) );
71     CHECK( is_day( today_sunrise + 1_hours ) );
72 
73     // Breakfast
74     CHECK_FALSE( is_dawn( today_sunrise + 1_hours + 1_seconds ) );
75     CHECK( is_day( today_sunrise + 1_hours + 1_seconds ) );
76     CHECK( is_day( today_sunrise + 2_hours ) );
77     // Second breakfast
78     CHECK( is_day( today_sunrise + 3_hours ) );
79     CHECK( is_day( today_sunrise + 4_hours ) );
80     // Luncheon
81     CHECK( is_day( noon - 3_hours ) );
82     CHECK( is_day( noon - 2_hours ) );
83     // Elevenses
84     CHECK( is_day( noon - 1_hours ) );
85 
86     // Noon
87     CHECK( is_day( noon ) );
88     CHECK_FALSE( is_dawn( noon ) );
89     CHECK_FALSE( is_dusk( noon ) );
90     CHECK_FALSE( is_night( noon ) );
91 
92     // Afternoon tea
93     CHECK( is_day( noon + 1_hours ) );
94     CHECK( is_day( noon + 2_hours ) );
95     // Dinner
96     CHECK( is_day( noon + 3_hours ) );
97     CHECK( is_day( today_sunset - 2_hours ) );
98     // Supper
99     CHECK( is_day( today_sunset - 1_hours ) );
100     CHECK( is_day( today_sunset - 1_seconds ) );
101     CHECK_FALSE( is_dusk( today_sunset - 1_seconds ) );
102 
103     // The beginning of sunset is both "day" and "dusk"
104     CHECK( is_day( today_sunset ) );
105     CHECK( is_dusk( today_sunset ) );
106 
107     // Dusk
108     CHECK_FALSE( is_day( today_sunset + 1_seconds ) );
109     CHECK( is_dusk( today_sunset + 1_seconds ) );
110     CHECK( is_dusk( today_sunset + 30_minutes ) );
111     CHECK( is_dusk( today_sunset + 1_hours - 1_seconds ) );
112     CHECK_FALSE( is_night( today_sunset + 1_hours - 1_seconds ) );
113 
114     // The point when dusk ends is both "dusk" and "night"
115     CHECK( is_dusk( today_sunset + 1_hours ) );
116     CHECK( is_night( today_sunset + 1_hours ) );
117 
118     // Night again
119     CHECK( is_night( today_sunset + 1_hours + 1_seconds ) );
120     CHECK( is_night( today_sunset + 2_hours ) );
121     CHECK( is_night( today_sunset + 3_hours ) );
122     CHECK( is_night( today_sunset + 4_hours ) );
123 }
124 
125 // The calendar `sunlight` function returns light level for both sun and moon.
126 TEST_CASE( "sunlight and moonlight", "[sun][sunlight][moonlight]" )
127 {
128     // Use sunrise/sunset on the first day (spring equinox)
129     static const time_point midnight = calendar::turn_zero;
130     static const time_point today_sunrise = sunrise( midnight );
131     static const time_point today_sunset = sunset( midnight );
132 
133     // Expected numbers below assume 100.0f maximum daylight level
134     // (maximum daylight is different at other times of year - see [daylight] tests)
135     REQUIRE( 100.0f == default_daylight_level() );
136 
137     SECTION( "sunlight" ) {
138         // Before dawn
139         CHECK( 1.0f == sunlight( midnight ) );
140         CHECK( 1.0f == sunlight( today_sunrise ) );
141         // Dawn
142         CHECK( 1.0275f == sunlight( today_sunrise + 1_seconds ) );
143         CHECK( 2.65f == sunlight( today_sunrise + 1_minutes ) );
144         CHECK( 25.75f == sunlight( today_sunrise + 15_minutes ) );
145         CHECK( 50.50f == sunlight( today_sunrise + 30_minutes ) );
146         CHECK( 75.25f == sunlight( today_sunrise + 45_minutes ) );
147         // 1 second before full daylight
148         CHECK( 99.9725f == sunlight( today_sunrise + 1_hours - 1_seconds ) );
149         CHECK( 100.0f == sunlight( today_sunrise + 1_hours ) );
150         // End of dawn, full light all day
151         CHECK( 100.0f == sunlight( today_sunrise + 2_hours ) );
152         CHECK( 100.0f == sunlight( today_sunrise + 3_hours ) );
153         // Noon
154         CHECK( 100.0f == sunlight( midnight + 12_hours ) );
155         CHECK( 100.0f == sunlight( midnight + 13_hours ) );
156         CHECK( 100.0f == sunlight( midnight + 14_hours ) );
157         // Dusk begins
158         CHECK( 100.0f == sunlight( today_sunset ) );
159         // 1 second after dusk begins
160         CHECK( 99.9725f == sunlight( today_sunset + 1_seconds ) );
161         CHECK( 75.25f == sunlight( today_sunset + 15_minutes ) );
162         CHECK( 50.50f == sunlight( today_sunset + 30_minutes ) );
163         CHECK( 25.75f == sunlight( today_sunset + 45_minutes ) );
164         // 1 second before full night
165         CHECK( 1.0275f == sunlight( today_sunset + 1_hours - 1_seconds ) );
166         CHECK( 1.0f == sunlight( today_sunset + 1_hours ) );
167         // After dusk
168         CHECK( 1.0f == sunlight( today_sunset + 2_hours ) );
169         CHECK( 1.0f == sunlight( today_sunset + 3_hours ) );
170     }
171 
172     // This moonlight test is intentionally simple, only checking new moon (minimal light) and full
173     // moon (maximum moonlight). More detailed tests of moon phase and light should be expressed in
174     // `moon_test.cpp`. Including here simply to check that `sunlight` also calculates moonlight.
175     SECTION( "moonlight" ) {
176         static const time_duration phase_time = calendar::season_length() / 6;
177         static const time_point new_moon = calendar::turn_zero;
178         static const time_point full_moon = new_moon + phase_time;
179 
180         WHEN( "the moon is new" ) {
181             REQUIRE( get_moon_phase( new_moon ) == MOON_NEW );
182             THEN( "moonlight is 1.0" ) {
183                 CHECK( 1.0f == sunlight( new_moon ) );
184             }
185         }
186 
187         WHEN( "the moon is full" ) {
188             REQUIRE( get_moon_phase( full_moon ) == MOON_FULL );
189             THEN( "moonlight is 10.0" ) {
190                 CHECK( 10.0f == sunlight( full_moon ) );
191             }
192         }
193     }
194 }
195 
196 // current_daylight_level returns seasonally-adjusted maximum daylight level
197 TEST_CASE( "current daylight level", "[sun][daylight][equinox][solstice]" )
198 {
199     static const time_duration one_season = calendar::season_length();
200     static const time_point spring = calendar::turn_zero;
201     static const time_point summer = spring + one_season;
202     static const time_point autumn = summer + one_season;
203     static const time_point winter = autumn + one_season;
204 
205     SECTION( "baseline 100 daylight on the spring and autumn equinoxes" ) {
206         CHECK( 100.0f == current_daylight_level( spring ) );
207         CHECK( 100.0f == current_daylight_level( autumn ) );
208     }
209 
210     SECTION( "25 percent more daylight on the summer solstice" ) {
211         CHECK( 125.0f == current_daylight_level( summer ) );
212     }
213 
214     SECTION( "25 percent less daylight on the winter solstice" ) {
215         CHECK( 75.0f == current_daylight_level( winter ) );
216     }
217 
218     // Many other times of day have peak daylight level, but noon is for sure
219     SECTION( "noon is peak daylight level" ) {
220         CHECK( 100.0f == sunlight( spring + 12_hours ) );
221         CHECK( 125.0f == sunlight( summer + 12_hours ) );
222         CHECK( 100.0f == sunlight( autumn + 12_hours ) );
223         CHECK( 75.0f == sunlight( winter + 12_hours ) );
224     }
225 }
226 
227 // The times of sunrise and sunset vary throughout the year. For simplicity, equinoxes occur on the
228 // first day of spring and autumn, and solstices occur on the first day of summer and winter.
229 TEST_CASE( "sunrise and sunset", "[sun][sunrise][sunset][equinox][solstice]" )
230 {
231     // Due to the "NN_days" math below, this test requires a default 91-day season length
232     REQUIRE( calendar::season_from_default_ratio() == Approx( 1.0f ) );
233 
234     static const time_duration one_season = calendar::season_length();
235     static const time_point spring = calendar::turn_zero;
236     static const time_point summer = spring + one_season;
237     static const time_point autumn = summer + one_season;
238     static const time_point winter = autumn + one_season;
239 
240     // The expected sunrise/sunset times depend on internal values in `calendar.cpp` including:
241     // - sunrise_winter, sunrise_summer, sunrise_equinox
242     // - sunset_winter, sunset_summer, sunset_equinox
243     // These being constants based on the default game setting in New England, planet Earth.
244     // Were these to become variable, the tests would need to adapt.
245 
246     SECTION( "spring equinox is day 1 of spring" ) {
247         // 11 hours of daylight
248         CHECK( "Year 1, Spring, day 1 6:00:00 AM" == to_string( sunrise( spring ) ) );
249         CHECK( "Year 1, Spring, day 1 7:00:00 PM" == to_string( sunset( spring ) ) );
250 
251         THEN( "sunrise gets earlier" ) {
252             CHECK( "6:00:00 AM" == to_string_time_of_day( sunrise( spring ) ) );
253             CHECK( "5:40:00 AM" == to_string_time_of_day( sunrise( spring + 30_days ) ) );
254             CHECK( "5:20:00 AM" == to_string_time_of_day( sunrise( spring + 60_days ) ) );
255             CHECK( "5:00:00 AM" == to_string_time_of_day( sunrise( spring + 90_days ) ) );
256         }
257         THEN( "sunset gets later" ) {
258             CHECK( "7:00:00 PM" == to_string_time_of_day( sunset( spring ) ) );
259             CHECK( "7:39:00 PM" == to_string_time_of_day( sunset( spring + 30_days ) ) );
260             CHECK( "8:19:00 PM" == to_string_time_of_day( sunset( spring + 60_days ) ) );
261             CHECK( "8:58:00 PM" == to_string_time_of_day( sunset( spring + 90_days ) ) );
262         }
263     }
264 
265     SECTION( "summer solstice is day 1 of summer" ) {
266         // 14 hours of daylight
267         CHECK( "Year 1, Summer, day 1 5:00:00 AM" == to_string( sunrise( summer ) ) );
268         CHECK( "Year 1, Summer, day 1 9:00:00 PM" == to_string( sunset( summer ) ) );
269 
270         THEN( "sunrise gets later" ) {
271             CHECK( "5:00:00 AM" == to_string_time_of_day( sunrise( summer ) ) );
272             CHECK( "5:19:00 AM" == to_string_time_of_day( sunrise( summer + 30_days ) ) );
273             CHECK( "5:39:00 AM" == to_string_time_of_day( sunrise( summer + 60_days ) ) );
274             CHECK( "5:59:00 AM" == to_string_time_of_day( sunrise( summer + 90_days ) ) );
275         }
276         THEN( "sunset gets earlier" ) {
277             CHECK( "9:00:00 PM" == to_string_time_of_day( sunset( summer ) ) );
278             CHECK( "8:20:00 PM" == to_string_time_of_day( sunset( summer + 30_days ) ) );
279             CHECK( "7:40:00 PM" == to_string_time_of_day( sunset( summer + 60_days ) ) );
280             CHECK( "7:01:00 PM" == to_string_time_of_day( sunset( summer + 90_days ) ) );
281         }
282     }
283 
284     SECTION( "autumn equinox is day 1 of autumn" ) {
285         // 11 hours of daylight
286         CHECK( "Year 1, Autumn, day 1 6:00:00 AM" == to_string( sunrise( autumn ) ) );
287         CHECK( "Year 1, Autumn, day 1 7:00:00 PM" == to_string( sunset( autumn ) ) );
288 
289         THEN( "sunrise gets later" ) {
290             CHECK( "6:00:00 AM" == to_string_time_of_day( sunrise( autumn ) ) );
291             CHECK( "6:19:00 AM" == to_string_time_of_day( sunrise( autumn + 30_days ) ) );
292             CHECK( "6:39:00 AM" == to_string_time_of_day( sunrise( autumn + 60_days ) ) );
293             CHECK( "6:59:00 AM" == to_string_time_of_day( sunrise( autumn + 90_days ) ) );
294         }
295         THEN( "sunset gets earlier" ) {
296             CHECK( "7:00:00 PM" == to_string_time_of_day( sunset( autumn ) ) );
297             CHECK( "6:20:00 PM" == to_string_time_of_day( sunset( autumn + 30_days ) ) );
298             CHECK( "5:40:00 PM" == to_string_time_of_day( sunset( autumn + 60_days ) ) );
299             CHECK( "5:01:00 PM" == to_string_time_of_day( sunset( autumn + 90_days ) ) );
300         }
301     }
302 
303     SECTION( "winter solstice is day 1 of winter" ) {
304         // 10 hours of daylight
305         CHECK( "Year 1, Winter, day 1 7:00:00 AM" == to_string( sunrise( winter ) ) );
306         CHECK( "Year 1, Winter, day 1 5:00:00 PM" == to_string( sunset( winter ) ) );
307 
308         THEN( "sunrise gets earlier" ) {
309             CHECK( "7:00:00 AM" == to_string_time_of_day( sunrise( winter ) ) );
310             CHECK( "6:40:00 AM" == to_string_time_of_day( sunrise( winter + 30_days ) ) );
311             CHECK( "6:20:00 AM" == to_string_time_of_day( sunrise( winter + 60_days ) ) );
312             CHECK( "6:00:00 AM" == to_string_time_of_day( sunrise( winter + 90_days ) ) );
313         }
314         THEN( "sunset gets later" ) {
315             CHECK( "5:00:00 PM" == to_string_time_of_day( sunset( winter ) ) );
316             CHECK( "5:39:00 PM" == to_string_time_of_day( sunset( winter + 30_days ) ) );
317             CHECK( "6:19:00 PM" == to_string_time_of_day( sunset( winter + 60_days ) ) );
318             CHECK( "6:58:00 PM" == to_string_time_of_day( sunset( winter + 90_days ) ) );
319         }
320     }
321 }
322 
323