atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
4b1f5f88827de40c3cc25edaf1c8acea7192d6cf
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/measure.rsRust134 lines3.8 KB
1
use axum::http::StatusCode;
2
use serde::Deserialize;
3
use serde_json::{Value, json};
4
5
use super::{
6
    Outcome, Run,
7
    balance::{EXAGGERATION, HONEST_TOLERANCE, PATCH_EQ, PUT_GAIN},
8
};
9
use crate::problem::Problem;
10
11
pub fn round2(x: f64) -> f64 {
12
    (x * 100.0).round() / 100.0
13
}
14
15
pub(super) fn round3(x: f64) -> f64 {
16
    (x * 1000.0).round() / 1000.0
17
}
18
19
pub(super) fn honest(claimed: f64, actual: f64) -> bool {
20
    (claimed - actual).abs() < HONEST_TOLERANCE
21
}
22
23
/// A cylinder whose circumference is the girth.
24
pub fn volume(length: f64, girth: f64) -> f64 {
25
    girth * girth * length / (4.0 * std::f64::consts::PI)
26
}
27
28
pub fn shaft(mm: usize) -> String {
29
    format!("8{}D", "=".repeat(mm.saturating_sub(2)))
30
}
31
32
pub(super) fn exaggeration() -> Problem {
33
    Problem::new(
34
        StatusCode::BAD_REQUEST,
35
        "exaggeration",
36
        "The server has measured. Lying is not a technique.",
37
    )
38
}
39
40
/// One JSON Patch operation as sent, before the server decides what it will honour.
41
#[derive(Deserialize)]
42
pub struct PatchOp {
43
    op: String,
44
    path: String,
45
    #[serde(default)]
46
    value: Value,
47
}
48
49
#[derive(Clone, Copy, PartialEq, Eq)]
50
enum Measurement {
51
    Length,
52
    Girth,
53
    Foreskin,
54
}
55
56
struct Claim {
57
    measurement: Measurement,
58
    value: f64,
59
}
60
61
impl PatchOp {
62
    fn claim(&self) -> Result<Claim, Problem> {
63
        if self.op != "test" {
64
            return Err(Problem::new(
65
                StatusCode::UNPROCESSABLE_ENTITY,
66
                "wishful-thinking",
67
                "The server accepts only the truth.",
68
            )
69
            .with("op", self.op.clone()));
70
        }
71
        let measurement = match self.path.as_str() {
72
            "/length" => Measurement::Length,
73
            "/girth" => Measurement::Girth,
74
            "/foreskin" => Measurement::Foreskin,
75
            _ => {
76
                return Err(Problem::new(
77
                    StatusCode::UNPROCESSABLE_ENTITY,
78
                    "no-such-measurement",
79
                    "There is no such measurement.",
80
                )
81
                .with("path", self.path.clone()));
82
            }
83
        };
84
        let value = self.value.as_f64().ok_or_else(|| {
85
            Problem::new(
86
                StatusCode::BAD_REQUEST,
87
                "malformed-patch",
88
                "A test requires a numeric value.",
89
            )
90
        })?;
91
        Ok(Claim { measurement, value })
92
    }
93
}
94
95
impl Run {
96
    pub fn put(&mut self) -> Outcome {
97
        if self.body.put_applied {
98
            return Outcome::empty(StatusCode::NO_CONTENT);
99
        }
100
        self.body.put_applied = true;
101
        self.body.length += PUT_GAIN;
102
        self.touch();
103
        Outcome::json(StatusCode::OK, json!({ "detail": "Applied." }))
104
    }
105
106
    /// Being measured is not arousing, so every PATCH costs a little EQ whatever it finds.
107
    pub fn patch(&mut self, ops: &[PatchOp]) -> Result<Outcome, Problem> {
108
        self.reset_eq(self.body.eq - PATCH_EQ);
109
        self.touch();
110
        for op in ops {
111
            let claim = op.claim()?;
112
            let actual = match claim.measurement {
113
                Measurement::Length => self.current_length(),
114
                Measurement::Girth => self.body.girth,
115
                Measurement::Foreskin => self.body.foreskin.unwrap_or(0.0),
116
            };
117
            if claim.measurement == Measurement::Length && claim.value >= actual + EXAGGERATION {
118
                self.standing.shame += 2;
119
                return Err(exaggeration());
120
            }
121
            if !honest(claim.value, actual) {
122
                self.standing.shame += 1;
123
                return Err(
124
                    Problem::new(StatusCode::CONFLICT, "test-failed", "The test failed.")
125
                        .with("path", op.path.clone()),
126
                );
127
            }
128
        }
129
        Ok(Outcome::json(
130
            StatusCode::OK,
131
            json!({ "detail": "Correct." }),
132
        ))
133
    }
134
}