use rand::RngExt;
use ratatui::layout::Rect;
use crate::game::golden::{self, GoldenVariant};
use crate::game::green_coin;
use crate::game::state::{GameState, TICK_DT};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BuyQty {
One,
Ten,
Max,
}
#[derive(Clone, Debug)]
pub enum Action {
Click {
col: u16,
row: u16,
},
ClickCenter,
CatchGolden,
BuyFingerer {
idx: usize,
qty: BuyQty,
},
BuyUpgrade(usize),
PrestigeReset,
UpdateGeometry {
biscuit: Rect,
},
DevAddCuques(f64),
DevForceGolden(GoldenVariant),
DevSpawnGreenCoin,
Misclick {
col: u16,
row: u16,
},
}
#[derive(Clone, Copy, Default)]
pub struct SimGeometry {
pub biscuit: Rect,
}
pub fn apply_action(state: &mut GameState, action: Action, geom: &mut SimGeometry) {
match action {
Action::Click { col, row } => {
let r = geom.biscuit;
if r.width > 0
&& col >= r.x
&& col < r.x + r.width
&& row >= r.y
&& row < r.y + r.height
{
state.click((col, row), r);
}
}
Action::ClickCenter => {
let r = geom.biscuit;
if r.width > 0 && r.height > 0 {
state.click((r.x + r.width / 2, r.y + r.height / 2), r);
}
state.space_pressed_this_tick = true;
}
Action::CatchGolden => {
state.catch_golden();
state.catch_green_coin();
}
Action::BuyFingerer { idx, qty } => match qty {
BuyQty::One => {
state.buy(idx);
}
BuyQty::Ten => {
state.buy_n(idx, 10);
}
BuyQty::Max => {
state.buy_max(idx);
}
},
Action::BuyUpgrade(idx) => {
state.buy_upgrade(idx);
}
Action::PrestigeReset => {
state.prestige_reset();
}
Action::UpdateGeometry { biscuit } => {
*geom = SimGeometry { biscuit };
}
Action::DevAddCuques(n) => {
state.dev_add_cuques(n);
}
Action::DevForceGolden(variant) => {
force_spawn_golden(state, geom, variant);
}
Action::DevSpawnGreenCoin => {
force_spawn_green_coin(state, geom);
}
Action::Misclick { col, row } => {
state.spawn_misclick(col, row);
}
}
}
pub fn sim_tick(state: &mut GameState, geom: &SimGeometry) {
state.tick();
state.tick_golden();
state.tick_green_coin();
maybe_spawn_golden(state, geom);
maybe_spawn_auto_particle(state, geom);
maybe_idle_clench(state);
}
fn maybe_idle_clench(state: &mut GameState) {
if state.clench_ticks > 0 {
return;
}
if rand::rng().random::<f64>() < 1.0 / 900.0 {
state.trigger_clench();
}
}
fn maybe_spawn_auto_particle(state: &mut GameState, geom: &SimGeometry) {
let fps = state.fps();
if fps <= 0.0 || geom.biscuit.width < 4 || geom.biscuit.height < 4 {
return;
}
let target_rate = fps.sqrt().clamp(0.5, 8.0);
let prob = target_rate * TICK_DT;
let mut rng = rand::rng();
if rng.random::<f64>() >= prob {
return;
}
let frac_x = rng.random_range(0.05_f32..=0.95);
let frac_y = rng.random_range(0.10_f32..=0.95);
state.spawn_auto_particle(frac_x, frac_y);
}
fn maybe_spawn_golden(state: &mut GameState, geom: &SimGeometry) {
if state.golden.is_some() || state.golden_cooldown > 0 {
return;
}
if geom.biscuit.width < 8 || geom.biscuit.height < 5 {
return;
}
state.golden = Some(golden::spawn_in(geom.biscuit));
state.goldens_since_green_coin = state.goldens_since_green_coin.saturating_add(1);
if state.green_coin.is_none() {
let p = state.goldens_since_green_coin as f64 * 0.01;
if rand::rng().random::<f64>() < p {
state.green_coin = Some(green_coin::spawn_in(geom.biscuit));
state.goldens_since_green_coin = 0;
}
}
}
fn force_spawn_golden(state: &mut GameState, geom: &SimGeometry, variant: GoldenVariant) {
if geom.biscuit.width < 8 || geom.biscuit.height < 5 {
return;
}
let mut g = golden::spawn_in(geom.biscuit);
g.variant = variant;
state.golden = Some(g);
}
fn force_spawn_green_coin(state: &mut GameState, geom: &SimGeometry) {
if state.green_coin.is_some() {
return;
}
if geom.biscuit.width < 8 || geom.biscuit.height < 5 {
return;
}
state.green_coin = Some(green_coin::spawn_in(geom.biscuit));
}