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::{ |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 5 | Cause, Floor, Item, Notice, Outcome, Run, Story, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 6 | balance::{ |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 7 | CAUGHT_MELONED, COCONUT_ODDS, ENERGY_ODDS, KETTLE_ODDS, KITCHEN_ICE_ODDS, KITCHEN_TOLD, |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 8 | MELON_SHAME, RAID_NOISE, RUPTURE_LINE, 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 { |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 14 | /// The household keeps count, so every raid makes noise and the noise makes the next telling likelier. |
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 | } |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 23 | if self.here.kitchen.closed { |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 24 | return Err(self.off_limits()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 25 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 26 | if self.online.story == Story::Pending { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 27 | return Ok(self.branch_offer()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 28 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 29 | self.begin_turn(); |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 30 | if self.chance( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 31 | self.decision("rupture", "Does this raid of the fridge rupture something?"), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 32 | self.rupture_odds(), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 33 | ) { |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 34 | return Err(self.rupture()); |
b85a0d4536c3 | atagen | 2026-10-11 08:50 UTC | 35 | } |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 36 | self.make_noise(RAID_NOISE); |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 37 | // One draw of 120 keeps the marginals at the documented 1 in 3, 1 in 5, 1 in 8 and 1 in 6. |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 38 | let loot = self.oracle.pick( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 39 | self.decision("loot", "What does the fridge give up on this raid?"), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 40 | &[ |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 41 | 120 / COCONUT_ODDS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 42 | 120 / ENERGY_ODDS, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 43 | 120 / KITCHEN_ICE_ODDS, |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 44 | 120 / KETTLE_ODDS, |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 45 | 120 - (120 / COCONUT_ODDS |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 46 | + 120 / ENERGY_ODDS |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 47 | + 120 / KITCHEN_ICE_ODDS |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 48 | + 120 / KETTLE_ODDS), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 49 | ], |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 50 | &[ |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 51 | "A jar of coconut oil, half of it already gone.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 52 | "An energy drink, cold and faintly alarming.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 53 | "An ice pack, buried under the peas.", |
a6caa2f99786 | atagen | 2026-10-12 10:38 UTC | 54 | "The kettle has just boiled.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 55 | "Nothing. The fridge is ordinary.", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 56 | ], |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 57 | ); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 58 | let melon = self.body.eq > RUPTURE_LINE |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 59 | && self.oracle.ratio( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 60 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 61 | "watermelon", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 62 | "Is there a watermelon in the fridge, exactly the right shape?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 63 | ), |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 64 | WATERMELON.0, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 65 | WATERMELON.1, |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 66 | ); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 67 | self.settle(); |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 68 | if self.oracle.ratio( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 69 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 70 | "kitchen-told", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 71 | "Has the household had enough of the raiding?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 72 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 73 | u32::from(self.standing.suspicion) + 1, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 74 | KITCHEN_TOLD, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 75 | ) { |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 76 | self.here.kitchen.closed = true; |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 77 | self.end_kitchen_visit(); |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 78 | self.notices.push(if self.floor == Floor::Nans { |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 79 | Notice::StopRaidingTheFridge |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 80 | } else { |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 81 | Notice::StopRaidingTheKitchen |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 82 | }); |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 83 | return Err(self.off_limits()); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 84 | } |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 85 | if melon { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 86 | self.here.kitchen.melon_offered = true; |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 87 | return Ok(self.melon_offer()); |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 88 | } |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 89 | Ok(self.unpack(loot)) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 90 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 91 | |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 92 | fn off_limits(&self) -> Problem { |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 93 | Problem::new( |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 94 | StatusCode::GONE, |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 95 | "kitchen-off-limits", |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 96 | "The kitchen is off limits.", |
aef6f6498ea2 | atagen | 2026-10-13 11:23 UTC | 97 | ) |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 98 | } |
f16ab17c8ff6 | atagen | 2026-10-13 11:14 UTC | 99 | |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 100 | fn unpack(&mut self, loot: usize) -> Outcome { |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 101 | let (item, detail) = match loot { |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 102 | 0 => ( |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 103 | Item::CoconutOil, |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 104 | "A jar of coconut oil, half of it already gone.", |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 105 | ), |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 106 | 1 => ( |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 107 | Item::EnergyDrink, |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 108 | "An energy drink, cold and faintly alarming.", |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 109 | ), |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 110 | 2 => (Item::IcePack, "An ice pack, buried under the peas."), |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 111 | 3 => { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 112 | self.here.kitchen.kettle_boiled = true; |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 113 | return Outcome::json( |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 114 | StatusCode::OK, |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 115 | json!({ "detail": "The kettle has just boiled. The towel can be warmed once more." }), |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 116 | ); |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 117 | } |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 118 | _ => return Outcome::empty(StatusCode::NO_CONTENT), |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 119 | }; |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 120 | self.items.grant(item); |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 121 | Outcome::json(StatusCode::OK, json!({ "detail": detail })) |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 122 | } |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 123 | |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 124 | fn melon_offer(&self) -> Outcome { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 125 | let commit = self.path("/kitchen/watermelon"); |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 126 | Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 127 | StatusCode::MULTIPLE_CHOICES, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 128 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 129 | "detail": "The fridge holds a watermelon. It is exactly the right shape.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 130 | "choices": [ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 131 | { "href": commit, "method": "POST", "title": "Commit to the watermelon" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 132 | { "href": self.path(""), "method": "GET", "title": "Close the fridge" }, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 133 | ], |
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 | .header( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 137 | header::LINK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 138 | format!( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 139 | "<{commit}>; rel=\"alternate\"; title=\"Commit to the watermelon\"; allow=\"POST\"" |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 140 | ), |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 141 | ) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 142 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 143 | |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 144 | /// A bail-out that costs no turn. The more the household has already heard, the likelier Mum is in the doorway. |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 145 | pub fn commit_melon(&mut self) -> Result<Outcome, Problem> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 146 | if !self.here.kitchen.melon_offered { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 147 | return Err(Problem::new( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 148 | StatusCode::CONFLICT, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 149 | "no-melon-on-offer", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 150 | "There is no watermelon on offer.", |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 151 | )); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 152 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 153 | self.here.kitchen.melon_offered = false; |
14de3e1e8dc2 | atagen | 2026-10-11 06:19 UTC | 154 | self.reset_eq(0); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 155 | self.standing.shame += MELON_SHAME; |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 156 | self.touch(); |
c4d70621d377 | atagen | 2026-10-12 11:56 UTC | 157 | if self.oracle.ratio( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 158 | self.decision( |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 159 | "caught-meloned", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 160 | "Is Mum standing in the doorway as you look up?", |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 161 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 162 | u32::from(self.standing.suspicion) + 2, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 163 | CAUGHT_MELONED, |
3f9091557ec3 | atagen | 2026-10-11 11:43 UTC | 164 | ) { |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 165 | return Err(self.end(Cause::Meloned)); |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 166 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 167 | Ok(Outcome::json( |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 168 | StatusCode::OK, |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 169 | json!({ |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 170 | "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 | 171 | }), |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 172 | )) |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 173 | } |
0a0bfee8329e | atagen | 2026-10-11 05:11 UTC | 174 | } |