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 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 13 | impl Run { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 14 | /// Raid the fridge. It costs the floor turn like anything else, and the household keeps count. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 15 | pub fn raid_kitchen(&mut self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 16 | if !self.floor.has_kitchen() { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 17 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 18 | StatusCode::NOT_FOUND, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 19 | "no-kitchen", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 20 | "There is no kitchen on this floor.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 21 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 22 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 23 | if self.kitchen_tellings >= KITCHEN_TELLINGS { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 24 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 25 | StatusCode::GONE, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 26 | "kitchen-off-limits", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 27 | "The kitchen is off limits. You have been told twice.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 28 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 29 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 30 | if self.story == Story::Pending { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 31 | return Ok(self.branch_offer()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 32 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 33 | self.touch(); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 34 | self.turn += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 35 | self.here.turns += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 36 | self.realise(); |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 37 | if self.chance( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 38 | self.decision("rupture", "Does this raid of the fridge rupture something?"), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 39 | self.rupture_odds(), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 40 | ) { |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 41 | return Err(self.rupture()); |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 42 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 43 | let was_below = self.suspicion < SUSPICION_LIMIT; |
139164489032 | atagen | 2026-10-11 05:46 UTC | 44 | // One draw of 120 keeps the marginals at the documented 1 in 3, 1 in 5 and 1 in 8. |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 45 | let loot = self.oracle.pick( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 46 | self.decision("loot", "What does the fridge give up on this raid?"), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 47 | &[ |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 48 | 120 / COCONUT_ODDS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 49 | 120 / ENERGY_ODDS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 50 | 120 / KITCHEN_ICE_ODDS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 51 | 120 - (120 / COCONUT_ODDS + 120 / ENERGY_ODDS + 120 / KITCHEN_ICE_ODDS), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 52 | ], |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 53 | &[ |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 54 | "A jar of coconut oil, half of it already gone.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 55 | "An energy drink, cold and faintly alarming.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 56 | "An ice pack, buried under the peas.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 57 | "Nothing. The fridge is ordinary.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 58 | ], |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 59 | ); |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 60 | let outcome = if self.eq > RUPTURE_LINE |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 61 | && self.oracle.ratio( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 62 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 63 | "watermelon", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 64 | "Is there a watermelon in the fridge, exactly the right shape?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 65 | ), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 66 | WATERMELON.0, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 67 | WATERMELON.1, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 68 | ) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 69 | self.here.melon_offered = true; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 70 | self.melon_offer() |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 71 | } else if loot == 0 { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 72 | self.items.grant(Item::CoconutOil); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 73 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 74 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 75 | json!({ "detail": "A jar of coconut oil, half of it already gone." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 76 | )) |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 77 | } else if loot == 1 { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 78 | self.items.grant(Item::EnergyDrink); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 79 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 80 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 81 | json!({ "detail": "An energy drink, cold and faintly alarming." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 82 | )) |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 83 | } else if loot == 2 { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 84 | self.items.grant(Item::IcePack); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 85 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 86 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 87 | json!({ "detail": "An ice pack, buried under the peas." }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 88 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 89 | } else { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 90 | Ok(Outcome::empty(StatusCode::NO_CONTENT)) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 91 | }; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 92 | self.settle(was_below); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 93 | let outcome = outcome?; |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 94 | if self.chance( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 95 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 96 | "kitchen-told", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 97 | "Has the household had enough of the raiding?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 98 | ), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 99 | KITCHEN_TOLD, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 100 | ) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 101 | self.kitchen_tellings += 1; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 102 | if self.kitchen_tellings >= KITCHEN_TELLINGS { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 103 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 104 | StatusCode::GONE, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 105 | "kitchen-off-limits", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 106 | "The kitchen is off limits. You have been told twice.", |
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 | self.notices.push(if self.floor == Floor::Nans { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 110 | "Stop raiding the fridge." |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 111 | } else { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 112 | "Stop raiding the kitchen." |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 113 | }); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 114 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 115 | Ok(outcome) |
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 | fn melon_offer(&self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 119 | let commit = self.path("/kitchen/watermelon"); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 120 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 121 | StatusCode::MULTIPLE_CHOICES, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 122 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 123 | "detail": "The fridge holds a watermelon. It is exactly the right shape.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 124 | "choices": [ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 125 | { "href": commit, "method": "POST", "title": "Commit to the watermelon" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 126 | { "href": self.path(""), "method": "GET", "title": "Close the fridge" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 127 | ], |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 128 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 129 | ) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 130 | .header( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 131 | header::LINK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 132 | format!( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 133 | "<{commit}>; rel=\"alternate\"; title=\"Commit to the watermelon\"; allow=\"POST\"" |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 134 | ), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 135 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 136 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 137 | |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 138 | /// The watermelon. It empties EQ, keeps the gains, costs shame, and the household may notice. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 139 | pub fn commit_melon(&mut self) -> Result<Outcome, Problem> { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 140 | if !self.here.melon_offered { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 141 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 142 | StatusCode::CONFLICT, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 143 | "no-melon-on-offer", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 144 | "There is no watermelon on offer.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 145 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 146 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 147 | self.here.melon_offered = false; |
14de3e1e8dc2 | atagen | 2026-10-11 06:19 UTC | 148 | self.reset_eq(0); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 149 | self.shame += MELON_SHAME; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 150 | self.touch(); |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 151 | if self.chance( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 152 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 153 | "caught-meloned", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 154 | "Is Mum standing in the doorway as you look up?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 155 | ), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 156 | CAUGHT_MELONED, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 157 | ) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 158 | return Err(self.end(Cause::Meloned)); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 159 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 160 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 161 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 162 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 163 | "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 | 164 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 165 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 166 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 167 | } |