atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
2d9074e0b4de549e73159752d1805e3c2399cbb1
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/game/measure.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use axum::http::StatusCode;
8b8e83329a1datagen2026-10-12 12:01 UTC2
use serde::Deserialize;
8b8e83329a1datagen2026-10-12 12:01 UTC3
use serde_json::{Value, json};
8b8e83329a1datagen2026-10-12 12:01 UTC4
8b8e83329a1datagen2026-10-12 12:01 UTC5
use super::{
8b8e83329a1datagen2026-10-12 12:01 UTC6
    Outcome, Run,
8b8e83329a1datagen2026-10-12 12:01 UTC7
    balance::{EXAGGERATION, HONEST_TOLERANCE, PATCH_EQ, PUT_GAIN},
8b8e83329a1datagen2026-10-12 12:01 UTC8
};
8b8e83329a1datagen2026-10-12 12:01 UTC9
use crate::problem::Problem;
8b8e83329a1datagen2026-10-12 12:01 UTC10
8b8e83329a1datagen2026-10-12 12:01 UTC11
pub fn round2(x: f64) -> f64 {
8b8e83329a1datagen2026-10-12 12:01 UTC12
    (x * 100.0).round() / 100.0
8b8e83329a1datagen2026-10-12 12:01 UTC13
}
8b8e83329a1datagen2026-10-12 12:01 UTC14
8b8e83329a1datagen2026-10-12 12:01 UTC15
pub(super) fn round3(x: f64) -> f64 {
8b8e83329a1datagen2026-10-12 12:01 UTC16
    (x * 1000.0).round() / 1000.0
8b8e83329a1datagen2026-10-12 12:01 UTC17
}
8b8e83329a1datagen2026-10-12 12:01 UTC18
8b8e83329a1datagen2026-10-12 12:01 UTC19
pub(super) fn honest(claimed: f64, actual: f64) -> bool {
8b8e83329a1datagen2026-10-12 12:01 UTC20
    (claimed - actual).abs() < HONEST_TOLERANCE
8b8e83329a1datagen2026-10-12 12:01 UTC21
}
8b8e83329a1datagen2026-10-12 12:01 UTC22
8b8e83329a1datagen2026-10-12 12:01 UTC23
/// A cylinder whose circumference is the girth.
8b8e83329a1datagen2026-10-12 12:01 UTC24
pub fn volume(length: f64, girth: f64) -> f64 {
8b8e83329a1datagen2026-10-12 12:01 UTC25
    girth * girth * length / (4.0 * std::f64::consts::PI)
8b8e83329a1datagen2026-10-12 12:01 UTC26
}
8b8e83329a1datagen2026-10-12 12:01 UTC27
8b8e83329a1datagen2026-10-12 12:01 UTC28
pub fn shaft(mm: usize) -> String {
8b8e83329a1datagen2026-10-12 12:01 UTC29
    format!("8{}D", "=".repeat(mm.saturating_sub(2)))
8b8e83329a1datagen2026-10-12 12:01 UTC30
}
8b8e83329a1datagen2026-10-12 12:01 UTC31
8b8e83329a1datagen2026-10-12 12:01 UTC32
pub(super) fn exaggeration() -> Problem {
8b8e83329a1datagen2026-10-12 12:01 UTC33
    Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC34
        StatusCode::BAD_REQUEST,
8b8e83329a1datagen2026-10-12 12:01 UTC35
        "exaggeration",
8b8e83329a1datagen2026-10-12 12:01 UTC36
        "The server has measured. Lying is not a technique.",
8b8e83329a1datagen2026-10-12 12:01 UTC37
    )
8b8e83329a1datagen2026-10-12 12:01 UTC38
}
8b8e83329a1datagen2026-10-12 12:01 UTC39
8b8e83329a1datagen2026-10-12 12:01 UTC40
/// One JSON Patch operation as sent, before the server decides what it will honour.
8b8e83329a1datagen2026-10-12 12:01 UTC41
#[derive(Deserialize)]
8b8e83329a1datagen2026-10-12 12:01 UTC42
pub struct PatchOp {
8b8e83329a1datagen2026-10-12 12:01 UTC43
    op: String,
8b8e83329a1datagen2026-10-12 12:01 UTC44
    path: String,
8b8e83329a1datagen2026-10-12 12:01 UTC45
    #[serde(default)]
8b8e83329a1datagen2026-10-12 12:01 UTC46
    value: Value,
8b8e83329a1datagen2026-10-12 12:01 UTC47
}
8b8e83329a1datagen2026-10-12 12:01 UTC48
8b8e83329a1datagen2026-10-12 12:01 UTC49
#[derive(Clone, Copy, PartialEq, Eq)]
8b8e83329a1datagen2026-10-12 12:01 UTC50
enum Measurement {
8b8e83329a1datagen2026-10-12 12:01 UTC51
    Length,
8b8e83329a1datagen2026-10-12 12:01 UTC52
    Girth,
8b8e83329a1datagen2026-10-12 12:01 UTC53
    Foreskin,
8b8e83329a1datagen2026-10-12 12:01 UTC54
}
8b8e83329a1datagen2026-10-12 12:01 UTC55
8b8e83329a1datagen2026-10-12 12:01 UTC56
struct Claim {
8b8e83329a1datagen2026-10-12 12:01 UTC57
    measurement: Measurement,
8b8e83329a1datagen2026-10-12 12:01 UTC58
    value: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC59
}
8b8e83329a1datagen2026-10-12 12:01 UTC60
8b8e83329a1datagen2026-10-12 12:01 UTC61
impl PatchOp {
8b8e83329a1datagen2026-10-12 12:01 UTC62
    fn claim(&self) -> Result<Claim, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC63
        if self.op != "test" {
8b8e83329a1datagen2026-10-12 12:01 UTC64
            return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC65
                StatusCode::UNPROCESSABLE_ENTITY,
8b8e83329a1datagen2026-10-12 12:01 UTC66
                "wishful-thinking",
8b8e83329a1datagen2026-10-12 12:01 UTC67
                "The server accepts only the truth.",
8b8e83329a1datagen2026-10-12 12:01 UTC68
            )
8b8e83329a1datagen2026-10-12 12:01 UTC69
            .with("op", self.op.clone()));
8b8e83329a1datagen2026-10-12 12:01 UTC70
        }
8b8e83329a1datagen2026-10-12 12:01 UTC71
        let measurement = match self.path.as_str() {
8b8e83329a1datagen2026-10-12 12:01 UTC72
            "/length" => Measurement::Length,
8b8e83329a1datagen2026-10-12 12:01 UTC73
            "/girth" => Measurement::Girth,
8b8e83329a1datagen2026-10-12 12:01 UTC74
            "/foreskin" => Measurement::Foreskin,
8b8e83329a1datagen2026-10-12 12:01 UTC75
            _ => {
8b8e83329a1datagen2026-10-12 12:01 UTC76
                return Err(Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC77
                    StatusCode::UNPROCESSABLE_ENTITY,
8b8e83329a1datagen2026-10-12 12:01 UTC78
                    "no-such-measurement",
8b8e83329a1datagen2026-10-12 12:01 UTC79
                    "There is no such measurement.",
8b8e83329a1datagen2026-10-12 12:01 UTC80
                )
8b8e83329a1datagen2026-10-12 12:01 UTC81
                .with("path", self.path.clone()));
8b8e83329a1datagen2026-10-12 12:01 UTC82
            }
8b8e83329a1datagen2026-10-12 12:01 UTC83
        };
8b8e83329a1datagen2026-10-12 12:01 UTC84
        let value = self.value.as_f64().ok_or_else(|| {
8b8e83329a1datagen2026-10-12 12:01 UTC85
            Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC86
                StatusCode::BAD_REQUEST,
8b8e83329a1datagen2026-10-12 12:01 UTC87
                "malformed-patch",
8b8e83329a1datagen2026-10-12 12:01 UTC88
                "A test requires a numeric value.",
8b8e83329a1datagen2026-10-12 12:01 UTC89
            )
8b8e83329a1datagen2026-10-12 12:01 UTC90
        })?;
8b8e83329a1datagen2026-10-12 12:01 UTC91
        Ok(Claim { measurement, value })
8b8e83329a1datagen2026-10-12 12:01 UTC92
    }
8b8e83329a1datagen2026-10-12 12:01 UTC93
}
8b8e83329a1datagen2026-10-12 12:01 UTC94
8b8e83329a1datagen2026-10-12 12:01 UTC95
impl Run {
8b8e83329a1datagen2026-10-12 12:01 UTC96
    pub fn put(&mut self) -> Outcome {
8b8e83329a1datagen2026-10-12 12:01 UTC97
        if self.body.put_applied {
8b8e83329a1datagen2026-10-12 12:01 UTC98
            return Outcome::empty(StatusCode::NO_CONTENT);
8b8e83329a1datagen2026-10-12 12:01 UTC99
        }
8b8e83329a1datagen2026-10-12 12:01 UTC100
        self.body.put_applied = true;
8b8e83329a1datagen2026-10-12 12:01 UTC101
        self.body.length += PUT_GAIN;
8b8e83329a1datagen2026-10-12 12:01 UTC102
        self.touch();
8b8e83329a1datagen2026-10-12 12:01 UTC103
        Outcome::json(StatusCode::OK, json!({ "detail": "Applied." }))
8b8e83329a1datagen2026-10-12 12:01 UTC104
    }
8b8e83329a1datagen2026-10-12 12:01 UTC105
8b8e83329a1datagen2026-10-12 12:01 UTC106
    /// Being measured is not arousing, so every PATCH costs a little EQ whatever it finds.
8b8e83329a1datagen2026-10-12 12:01 UTC107
    pub fn patch(&mut self, ops: &[PatchOp]) -> Result<Outcome, Problem> {
8b8e83329a1datagen2026-10-12 12:01 UTC108
        self.reset_eq(self.body.eq - PATCH_EQ);
8b8e83329a1datagen2026-10-12 12:01 UTC109
        self.touch();
8b8e83329a1datagen2026-10-12 12:01 UTC110
        for op in ops {
8b8e83329a1datagen2026-10-12 12:01 UTC111
            let claim = op.claim()?;
8b8e83329a1datagen2026-10-12 12:01 UTC112
            let actual = match claim.measurement {
8b8e83329a1datagen2026-10-12 12:01 UTC113
                Measurement::Length => self.current_length(),
8b8e83329a1datagen2026-10-12 12:01 UTC114
                Measurement::Girth => self.body.girth,
8b8e83329a1datagen2026-10-12 12:01 UTC115
                Measurement::Foreskin => self.body.foreskin.unwrap_or(0.0),
8b8e83329a1datagen2026-10-12 12:01 UTC116
            };
8b8e83329a1datagen2026-10-12 12:01 UTC117
            if claim.measurement == Measurement::Length && claim.value >= actual + EXAGGERATION {
8b8e83329a1datagen2026-10-12 12:01 UTC118
                self.standing.shame += 2;
8b8e83329a1datagen2026-10-12 12:01 UTC119
                return Err(exaggeration());
8b8e83329a1datagen2026-10-12 12:01 UTC120
            }
8b8e83329a1datagen2026-10-12 12:01 UTC121
            if !honest(claim.value, actual) {
8b8e83329a1datagen2026-10-12 12:01 UTC122
                self.standing.shame += 1;
8b8e83329a1datagen2026-10-12 12:01 UTC123
                return Err(
8b8e83329a1datagen2026-10-12 12:01 UTC124
                    Problem::new(StatusCode::CONFLICT, "test-failed", "The test failed.")
8b8e83329a1datagen2026-10-12 12:01 UTC125
                        .with("path", op.path.clone()),
8b8e83329a1datagen2026-10-12 12:01 UTC126
                );
8b8e83329a1datagen2026-10-12 12:01 UTC127
            }
8b8e83329a1datagen2026-10-12 12:01 UTC128
        }
8b8e83329a1datagen2026-10-12 12:01 UTC129
        Ok(Outcome::json(
8b8e83329a1datagen2026-10-12 12:01 UTC130
            StatusCode::OK,
8b8e83329a1datagen2026-10-12 12:01 UTC131
            json!({ "detail": "Correct." }),
8b8e83329a1datagen2026-10-12 12:01 UTC132
        ))
8b8e83329a1datagen2026-10-12 12:01 UTC133
    }
8b8e83329a1datagen2026-10-12 12:01 UTC134
}