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