// Holidays of the real date: the dungeon and the title menu dress up for them (pumpkins, // fir trees and presents, painted eggs; see render3d's room decor and menu's draw_title). // Every holiday runs from about two weeks before its day to a few days after it. pub const NONE: u8 = 0; pub const HALLOWEEN: u8 = 1; pub const CHRISTMAS: u8 = 2; pub const EASTER: u8 = 3; // days since 1970-01-01 of a date (proleptic Gregorian) fn days_from_civil(y: i64, m: i64, d: i64) -> i64 { let y = if m <= 2 { y - 1 } else { y }; let era = y.div_euclid(400); let yoe = y - era * 400; let mp = (m + 9) % 12; let doy = (153 * mp + 2) / 5 + d - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; era * 146097 + doe - 719468 } // Easter Sunday of a year (month, day): the anonymous Gregorian algorithm pub fn easter(y: i64) -> (i64, i64) { let a = y % 19; let b = y / 100; let c = y % 100; let d = b / 4; let e = b % 4; let f = (b + 8) / 25; let g = (b - f + 1) / 3; let h = (19 * a + b - d - g + 15) % 30; let i = c / 4; let k = c % 4; let l = (32 + 2 * e + 2 * i - h - k) % 7; let m = (a + 11 * h + 22 * l) / 451; let n = h + l - 7 * m + 114; (n / 31, n % 31 + 1) } // the holiday on a date: Halloween 17 Oct - 3 Nov, Christmas 10 Dec - 1 Jan, Easter from two // weeks before Easter Sunday to the Wednesday after it pub fn holiday_on(y: i64, m: i64, d: i64) -> u8 { match (m, d) { (10, 17..=31) | (11, 1..=3) => return HALLOWEEN, (12, 10..=31) | (1, 1) => return CHRISTMAS, _ => {} } let (em, ed) = easter(y); let off = days_from_civil(y, m, d) - days_from_civil(y, em, ed); if (-14..=3).contains(&off) { EASTER } else { NONE } } thread_local! { // the debug menu's choice: 0 by the date, else the holiday + 1 static OVERRIDE: std::cell::Cell = const { std::cell::Cell::new(0) }; } // the debug menu's holiday setting (Settings::debug_holiday: 0 by the date, 1 none, 2 Halloween, // 3 Christmas, 4 Easter) pub fn set_override(v: u8) { OVERRIDE.with(|o| o.set(v.min(4))); } // today's holiday (worked out once) unless the debug menu picks one; on the desktop // DUNGEONCRAFT_HOLIDAY=halloween / christmas / easter / none shows one at any time pub fn current() -> u8 { thread_local! { static HOLIDAY: std::cell::Cell = const { std::cell::Cell::new(255) }; } let forced = OVERRIDE.with(|o| o.get()); if forced > 0 { return forced - 1; } HOLIDAY.with(|h| { if h.get() == 255 { #[cfg(not(target_arch = "wasm32"))] let forced = std::env::var("DUNGEONCRAFT_HOLIDAY").ok().and_then(|v| match v.to_lowercase().as_str() { "halloween" => Some(HALLOWEEN), "christmas" | "xmas" => Some(CHRISTMAS), "easter" => Some(EASTER), "none" => Some(NONE), _ => None, }); #[cfg(target_arch = "wasm32")] let forced: Option = None; let (y, m, d, _, _) = crate::cloud::civil(macroquad::miniquad::date::now() as i64); h.set(forced.unwrap_or_else(|| holiday_on(y, m, d))); } h.get() }) } #[cfg(test)] mod tests { use super::*; #[test] fn easter_dates() { assert_eq!(easter(2024), (3, 31)); assert_eq!(easter(2025), (4, 20)); assert_eq!(easter(2026), (4, 5)); assert_eq!(easter(2027), (3, 28)); } #[test] fn holiday_windows() { assert_eq!(holiday_on(2026, 10, 16), NONE); assert_eq!(holiday_on(2026, 10, 17), HALLOWEEN); assert_eq!(holiday_on(2026, 11, 3), HALLOWEEN); assert_eq!(holiday_on(2026, 11, 7), NONE); assert_eq!(holiday_on(2026, 12, 9), NONE); assert_eq!(holiday_on(2026, 12, 10), CHRISTMAS); assert_eq!(holiday_on(2027, 1, 1), CHRISTMAS); assert_eq!(holiday_on(2027, 1, 2), NONE); // Easter 2026: 5 April assert_eq!(holiday_on(2026, 3, 21), NONE); assert_eq!(holiday_on(2026, 3, 22), EASTER); assert_eq!(holiday_on(2026, 4, 8), EASTER); assert_eq!(holiday_on(2026, 4, 9), NONE); } }