atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/tests/rolls.rsRust256 lines7.6 KB
1
use super::*;
2
3
#[tokio::test]
4
async fn holding_calms_and_sometimes_reflects() {
5
    let mut calmed = 0;
6
    let mut reflected = 0;
7
    let mut ruptured = 0;
8
    for seed in 0..600u64 {
9
        let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
10
        run.body.eq = 75;
11
        match run.hold() {
12
            Ok(_) if run.body.eq == 0 => reflected += 1,
13
            Ok(_) => calmed += 1,
14
            Err(_) => ruptured += 1,
15
        }
16
    }
17
    assert!(
18
        (25..=80).contains(&reflected),
19
        "one HOLD in twelve should reflect, about 50 of 600, got {reflected} and {calmed} calm"
20
    );
21
    assert!(
22
        ruptured <= 7,
23
        "the flat 1 in 250 ruptured {ruptured} of 600 holds, about 2.4 expected"
24
    );
25
}
26
27
#[tokio::test]
28
async fn the_burn_shifts_the_rupture_tiers() {
29
    async fn ruptures(burned: bool) -> u32 {
30
        let mut gone = 0;
31
        for seed in 0..400u64 {
32
            let app = app_with(Floor::Bedroom, seed, |run| {
33
                run.body.eq = 60;
34
                run.items.lubes.baby_oil = 9;
35
                run.body.conditions.friction_burn = burned;
36
            });
37
            let lube = [("content-type", "application/baby-oil")];
38
            let jelq = send(
39
                &app,
40
                "JELQ",
41
                "/runs/cafebabe?technique=wet&intensity=5",
42
                &lube,
43
                "",
44
            )
45
            .await;
46
            if jelq.status() == StatusCode::INTERNAL_SERVER_ERROR {
47
                gone += 1;
48
            }
49
        }
50
        gone
51
    }
52
    let burned = ruptures(true).await;
53
    let calm = ruptures(false).await;
54
    assert!(
55
        burned >= 8 && calm <= 4,
56
        "burned {burned} ruptures against {calm}"
57
    );
58
}
59
60
#[tokio::test]
61
async fn the_top_tier_rolls_a_single_third() {
62
    let mut ruptures = 0;
63
    let mut early = 0;
64
    for seed in 0..400u64 {
65
        let app = app_with(Floor::Bedroom, seed, |run| {
66
            run.body.eq = 96;
67
            run.items.lubes.baby_oil = 9;
68
            run.items.kit.blinds = true;
69
        });
70
        let lube = [("content-type", "application/baby-oil")];
71
        let jelq = send(
72
            &app,
73
            "JELQ",
74
            "/runs/cafebabe?technique=wet&intensity=5",
75
            &lube,
76
            "",
77
        )
78
        .await;
79
        match jelq.status() {
80
            StatusCode::INTERNAL_SERVER_ERROR => ruptures += 1,
81
            StatusCode::TOO_EARLY => early += 1,
82
            other => panic!("seed {seed} ended in {other}"),
83
        }
84
    }
85
    assert!(
86
        (90..=175).contains(&ruptures),
87
        "ruptures {ruptures} of 400 against {early} early"
88
    );
89
    assert!(
90
        (225..=310).contains(&early),
91
        "early {early} of 400 against {ruptures} ruptures"
92
    );
93
}
94
95
#[tokio::test]
96
async fn holding_rolls_a_flat_rupture_chance() {
97
    async fn ruptures(eq: i32, stamina: i32) -> u32 {
98
        let mut gone = 0;
99
        for seed in 0..600u64 {
100
            let app = app_with(Floor::Bedroom, seed, |run| {
101
                run.body.eq = eq;
102
                run.body.stamina = stamina;
103
            });
104
            let held = send(&app, "HOLD", "/runs/cafebabe", &[], "").await;
105
            if held.status() == StatusCode::INTERNAL_SERVER_ERROR {
106
                gone += 1;
107
            }
108
        }
109
        gone
110
    }
111
    let high = ruptures(90, 10).await;
112
    let low = ruptures(30, 2).await;
113
    assert!((0..=7).contains(&high), "high holds ruptured {high}/600");
114
    assert!((0..=7).contains(&low), "low holds ruptured {low}/600");
115
}
116
117
#[tokio::test]
118
async fn the_kitchen_pays_out_at_the_documented_odds() {
119
    let mut oil = 0;
120
    let mut drinks = 0;
121
    let mut packs = 0;
122
    let mut raids = 0;
123
    for seed in 0..800u64 {
124
        let mut run = Run::new("cafebabe".into(), "cafe".into(), seed);
125
        let held = (
126
            run.items.lubes.coconut_oil,
127
            run.items.stores.energy_drinks,
128
            run.items.stores.ice_packs,
129
        );
130
        if run.raid_kitchen().is_err() {
131
            continue;
132
        }
133
        raids += 1;
134
        oil += u32::from(run.items.lubes.coconut_oil > held.0);
135
        drinks += u32::from(run.items.stores.energy_drinks > held.1);
136
        packs += u32::from(run.items.stores.ice_packs > held.2);
137
    }
138
    let rate = |found: u32| f64::from(found) / f64::from(raids);
139
    assert!(
140
        (0.28..=0.39).contains(&rate(oil)),
141
        "coconut {oil} in {raids}"
142
    );
143
    assert!(
144
        (0.15..=0.25).contains(&rate(drinks)),
145
        "energy {drinks} in {raids}"
146
    );
147
    assert!(
148
        (0.08..=0.17).contains(&rate(packs)),
149
        "ice {packs} in {raids}"
150
    );
151
}
152
153
#[tokio::test]
154
async fn the_kitchen_rolls_the_normal_tiers() {
155
    async fn ruptures(eq: i32) -> u32 {
156
        let mut gone = 0;
157
        for seed in 0..600u64 {
158
            let app = app_with(Floor::Bedroom, seed, |run| run.body.eq = eq);
159
            let raid = send(&app, "POST", "/runs/cafebabe/kitchen", &[], "").await;
160
            if raid.status() == StatusCode::INTERNAL_SERVER_ERROR {
161
                gone += 1;
162
            }
163
        }
164
        gone
165
    }
166
    let hot = ruptures(96).await;
167
    let cool = ruptures(40).await;
168
    assert!((40..=80).contains(&hot), "hot kitchen ruptured {hot}/600");
169
    assert!((0..=9).contains(&cool), "cool kitchen ruptured {cool}/600");
170
}
171
172
#[tokio::test]
173
async fn degloving_takes_one_hard_dry_stroke_in_twenty() {
174
    let mut degloved = 0;
175
    for seed in 0..1200u64 {
176
        let app = app_with(Floor::Hotel, seed, |run| {
177
            run.here.wifi_accepted = true;
178
            run.body.eq = 55;
179
            run.body.foreskin = Some(1.0);
180
        });
181
        let dry = send(
182
            &app,
183
            "JELQ",
184
            "/runs/cafebabe?technique=dry&intensity=8",
185
            &[],
186
            "",
187
        )
188
        .await;
189
        if dry.status() == StatusCode::GONE
190
            && json(dry).await["type"]
191
                .as_str()
192
                .unwrap()
193
                .ends_with("degloved")
194
        {
195
            degloved += 1;
196
        }
197
    }
198
    assert!(
199
        (35..=90).contains(&degloved),
200
        "{degloved} degloved in 1200 hard dry strokes, about 60 expected"
201
    );
202
}
203
204
#[tokio::test]
205
async fn degloving_takes_one_cold_stroke_in_twenty() {
206
    let mut degloved = 0;
207
    for seed in 0..1200u64 {
208
        let app = app_with(Floor::Hotel, seed, |run| {
209
            run.here.wifi_accepted = true;
210
            run.body.eq = 20;
211
            run.body.foreskin = Some(1.0);
212
        });
213
        let cold = send(&app, "JELQ", "/runs/cafebabe?technique=stretch", &[], "").await;
214
        if cold.status() != StatusCode::GONE {
215
            continue;
216
        }
217
        let bare = header(&cold, "x-foreskin") == "gone";
218
        let problem = json(cold).await;
219
        if problem["type"].as_str().unwrap().ends_with("degloved") {
220
            assert!(bare, "degloving takes the foreskin with it");
221
            assert_eq!(problem["foreskin"], -1.0);
222
            degloved += 1;
223
        }
224
    }
225
    assert!(
226
        (35..=90).contains(&degloved),
227
        "{degloved} degloved in 1200 cold strokes, about 60 expected"
228
    );
229
}
230
231
#[tokio::test]
232
async fn a_turn_cannot_rupture_for_heat_it_only_just_reached() {
233
    let mut ruptured = 0;
234
    for seed in 0..300u64 {
235
        let app = app_with(Floor::Bedroom, seed, |run| run.body.eq = 70);
236
        let turn = send(
237
            &app,
238
            "JELQ",
239
            "/runs/cafebabe?technique=stretch&intensity=10",
240
            &[],
241
            "",
242
        )
243
        .await;
244
        if turn
245
            .headers()
246
            .get("x-run-over")
247
            .is_some_and(|v| v == "ruptured")
248
        {
249
            ruptured += 1;
250
        }
251
    }
252
    assert!(
253
        ruptured <= 6,
254
        "Ideal at the start of the turn kept the in-band 1 in 250, but {ruptured} of 300 ruptured"
255
    );
256
}