Skip to main content

cuqueclicker_lib/game/
golden.rs

1use rand::RngExt;
2use ratatui::layout::Rect;
3
4/// A Golden Cuque variant. The on-screen marker color + label differ per
5/// variant, but all are caught through the same path (mouse click on the
6/// marker, or the `g` hotkey). See `GameState::catch_golden` for effects.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum GoldenVariant {
9    /// Instant flat reward: `max(10, fps * 60s)` cuques. No buff, no duration.
10    Lucky,
11    /// 13-second `ClickFrenzy` buff: manual clicks produce x777 cuques.
12    Frenzy,
13    /// 60-second per-fingerer modifier on a random owned fingerer: that
14    /// fingerer's passive FPS is multiplied x7. Implemented as a
15    /// `ModifierSource::PurpleCoin` `MulFactor(7.0)` with `Ticks(1200)`.
16    Buff,
17}
18
19impl GoldenVariant {
20    /// All variants, in stable discriminant order. `GoldenVariant as usize`
21    /// indexes into `state.goldens` — keep the order in sync with that
22    /// array's slot semantics.
23    pub const ALL: [Self; 3] = [Self::Lucky, Self::Frenzy, Self::Buff];
24
25    pub fn random() -> Self {
26        let r: f64 = rand::rng().random();
27        if r < 0.6 {
28            Self::Lucky
29        } else if r < 0.8 {
30            Self::Frenzy
31        } else {
32            Self::Buff
33        }
34    }
35}
36
37/// Position is stored as a fraction of the biscuit rect ([0.0, 1.0] on each
38/// axis) so the marker stays anchored to its spot on the biscuit when the
39/// terminal resizes or the user zooms (`+`/`-`). The renderer resolves these
40/// fractions against the *current* biscuit rect every frame.
41#[derive(Clone)]
42pub struct GoldenCuque {
43    pub frac_x: f32,
44    pub frac_y: f32,
45    pub life_ticks: u32,
46    pub variant: GoldenVariant,
47}
48
49pub const GOLDEN_LIFE_TICKS: u32 = 220; // ~11s at 20Hz
50pub const GOLDEN_COOLDOWN_MIN: u32 = 400; // 20s
51pub const GOLDEN_COOLDOWN_MAX: u32 = 1600; // 80s
52// Inset (in fractional units) the spawn lottery away from the biscuit edges
53// so the 5x3 marker has room to render without bumping into the border.
54const SPAWN_INSET_X: f32 = 0.08;
55const SPAWN_INSET_Y: f32 = 0.10;
56
57pub fn next_cooldown() -> u32 {
58    rand::rng().random_range(GOLDEN_COOLDOWN_MIN..=GOLDEN_COOLDOWN_MAX)
59}
60
61/// Pick a random fractional position inside the biscuit, away from the edges.
62/// `_biscuit` is taken so the signature documents intent (the spawn area is
63/// "inside this rect"); the actual fractions are rect-independent.
64pub fn spawn_in(_biscuit: Rect) -> GoldenCuque {
65    let mut r = rand::rng();
66    let frac_x = r.random_range(SPAWN_INSET_X..=(1.0 - SPAWN_INSET_X));
67    let frac_y = r.random_range(SPAWN_INSET_Y..=(1.0 - SPAWN_INSET_Y));
68    GoldenCuque {
69        frac_x,
70        frac_y,
71        life_ticks: GOLDEN_LIFE_TICKS,
72        variant: GoldenVariant::random(),
73    }
74}