an http based roguelike rpg about jelqing
Blame: src/game/kitchen.rs
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 1 | use axum::http::{StatusCode, header}; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 2 | use serde_json::json; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 3 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 4 | use super::{ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 5 | Cause, Floor, Item, Outcome, Run, Story, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 6 | balance::{ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 7 | CAUGHT_MELONED, COCONUT_ODDS, ENERGY_ODDS, KITCHEN_ICE_ODDS, KITCHEN_TELLINGS, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 8 | KITCHEN_TOLD, MELON_SHAME, RUPTURE_LINE, SUSPICION_LIMIT, WATERMELON, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 9 | }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 10 | }; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 11 | use crate::problem::Problem; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 12 | use rand::Rng; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 13 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 14 | impl Run { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 15 | /// Raid the fridge. It costs the floor turn like anything else, and the household keeps count. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 16 | pub fn raid_kitchen(&mut self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 17 | if !self.floor.has_kitchen() { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 18 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 19 | StatusCode::NOT_FOUND, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 20 | "no-kitchen", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 21 | "There is no kitchen on this floor.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 22 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 23 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 24 | if self.kitchen_tellings >= KITCHEN_TELLINGS { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 25 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 26 | StatusCode::GONE, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 27 | "kitchen-off-limits", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 28 | "The kitchen is off limits. You have been told twice.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 29 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 30 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 31 | if self.story == Story::Pending { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 32 | return Ok(self.branch_offer()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 33 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 34 | self.touch(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 35 | self.turn += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 36 | self.here.turns += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 37 | self.realise(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 38 | let was_below = self.suspicion < SUSPICION_LIMIT; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 39 | // One draw of 120 keeps the marginals at the documented 1 in 3, 1 in 5 and 1 in 8. |
139164489032 | atagen | 2026-10-11 05:46 UTC | 40 | let draw = self.rng.random_range(0..120); |
139164489032 | atagen | 2026-10-11 05:46 UTC | 41 | let oil = 120 / COCONUT_ODDS; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 42 | let energy = oil + 120 / ENERGY_ODDS; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 43 | let ice = energy + 120 / KITCHEN_ICE_ODDS; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 44 | let outcome = if self.eq > RUPTURE_LINE && self.rng.random_ratio(WATERMELON.0, WATERMELON.1) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 45 | { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 46 | self.here.melon_offered = true; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 47 | self.melon_offer() |
139164489032 | atagen | 2026-10-11 05:46 UTC | 48 | } else if draw < oil { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 49 | self.items.grant(Item::CoconutOil); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 50 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 51 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 52 | json!({ "detail": "A jar of coconut oil, half of it already gone." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 53 | )) |
139164489032 | atagen | 2026-10-11 05:46 UTC | 54 | } else if draw < energy { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 55 | self.items.grant(Item::EnergyDrink); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 56 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 57 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 58 | json!({ "detail": "An energy drink, cold and faintly alarming." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 59 | )) |
139164489032 | atagen | 2026-10-11 05:46 UTC | 60 | } else if draw < ice { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 61 | self.items.grant(Item::IcePack); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 62 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 63 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 64 | json!({ "detail": "An ice pack, buried under the peas." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 65 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 66 | } else { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 67 | Ok(Outcome::empty(StatusCode::NO_CONTENT)) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 68 | }; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 69 | self.settle(was_below); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 70 | let outcome = outcome?; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 71 | if self.chance(KITCHEN_TOLD) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 72 | self.kitchen_tellings += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 73 | if self.kitchen_tellings >= KITCHEN_TELLINGS { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 74 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 75 | StatusCode::GONE, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 76 | "kitchen-off-limits", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 77 | "The kitchen is off limits. You have been told twice.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 78 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 79 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 80 | self.notices.push(if self.floor == Floor::Nans { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 81 | "Stop raiding the fridge." |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 82 | } else { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 83 | "Stop raiding the kitchen." |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 84 | }); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 85 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 86 | Ok(outcome) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 87 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 88 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 89 | fn melon_offer(&self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 90 | let commit = self.path("/kitchen/watermelon"); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 91 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 92 | StatusCode::MULTIPLE_CHOICES, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 93 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 94 | "detail": "The fridge holds a watermelon. It is exactly the right shape.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 95 | "choices": [ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 96 | { "href": commit, "method": "POST", "title": "Commit to the watermelon" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 97 | { "href": self.path(""), "method": "GET", "title": "Close the fridge" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 98 | ], |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 99 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 100 | ) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 101 | .header( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 102 | header::LINK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 103 | format!( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 104 | "<{commit}>; rel=\"alternate\"; title=\"Commit to the watermelon\"; allow=\"POST\"" |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 105 | ), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 106 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 107 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 108 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 109 | /// The watermelon. It empties EQ, keeps the gains, costs shame, and the household may notice. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 110 | pub fn commit_melon(&mut self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 111 | if !self.here.melon_offered { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 112 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 113 | StatusCode::CONFLICT, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 114 | "no-melon-on-offer", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 115 | "There is no watermelon on offer.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 116 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 117 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 118 | self.here.melon_offered = false; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 119 | self.eq = 0; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 120 | self.shame += MELON_SHAME; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 121 | self.touch(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 122 | if self.chance(CAUGHT_MELONED) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 123 | return Err(self.end(Cause::Meloned)); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 124 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 125 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 126 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 127 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 128 | "detail": "The watermelon did not survive, and neither did the mood. EQ is gone, the gains are kept, and the shame is considerable.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 129 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 130 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 131 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 132 | } |