125 lines
4.1 KiB
Rust
125 lines
4.1 KiB
Rust
// 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<u8> = 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<u8> = 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<u8> = 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);
|
|
}
|
|
}
|