Merge WI-1 Injectable RNG (PSP1, issue #1) into main

This commit is contained in:
Jan-Peter von Hunnius 2026-10-03 10:14:24 +02:00
commit 899fde8fc3
3 changed files with 69 additions and 7 deletions

25
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,25 @@
name: server-ci
on: [push, pull_request]
jobs:
build-test:
runs-on: self-hosted
steps:
- name: Checkout server
run: |
git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-server.git" server
cd server && git checkout "$GITHUB_SHA"
- name: Checkout common (sibling, Originalname wegen include_str!-Pfaden)
run: git clone --depth 1 "$GITHUB_SERVER_URL/jan/skara-brae-common.git" skara-brae-common
- name: sb-common-Pfade fuer CI umschreiben (alle Cargo.toml)
run: |
grep -rl "/home/jan/Development/skara-brae/skara-brae-common/rust" server --include=Cargo.toml | xargs -r sed -i "s|/home/jan/Development/skara-brae/skara-brae-common/rust|$GITHUB_WORKSPACE/skara-brae-common/rust|g"
grep -rn "sb-common" server --include=Cargo.toml
- name: Build
run: cargo build --workspace --manifest-path server/Cargo.toml
- name: Test
run: cargo test --workspace --manifest-path server/Cargo.toml

View file

@ -25,6 +25,11 @@ use sb_shared::{
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
fn request_rng() -> rand::rngs::StdRng {
use rand::SeedableRng;
rand::rngs::StdRng::from_os_rng()
}
pub(crate) struct AppState { pub(crate) struct AppState {
users: RwLock<Vec<User>>, users: RwLock<Vec<User>>,
items: Vec<Item>, items: Vec<Item>,
@ -285,7 +290,10 @@ async fn create_character(
return Err(axum::http::StatusCode::BAD_REQUEST); return Err(axum::http::StatusCode::BAD_REQUEST);
} }
let mut char = character::create_character(user_id, name, input.race, input.class); let mut char = {
let mut rng = request_rng();
character::create_character(&mut rng, user_id, name, input.race, input.class)
};
{ {
let mut id = state.next_char_id.write().await; let mut id = state.next_char_id.write().await;
@ -424,6 +432,8 @@ async fn run_adventure(
.unwrap() .unwrap()
.as_millis() as i64; .as_millis() as i64;
let mut rng = request_rng();
let (char, enemy_def) = { let (char, enemy_def) = {
let mut chars = state.characters.write().await; let mut chars = state.characters.write().await;
let char = chars.iter_mut().find(|c| c.id == char_id) let char = chars.iter_mut().find(|c| c.id == char_id)
@ -439,7 +449,6 @@ async fn run_adventure(
} }
char.nerve -= zone.nerve_cost; char.nerve -= zone.nerve_cost;
let mut rng = rand::rng();
let enemy = domain::pick_enemy(&mut rng, &zone.encounters, &state.enemies); let enemy = domain::pick_enemy(&mut rng, &zone.encounters, &state.enemies);
(char.clone(), enemy) (char.clone(), enemy)
}; };
@ -494,14 +503,13 @@ async fn run_adventure(
songs::BARD_SONGS.iter().find(|s| s.id == sid).map(|s| s.combat_effect) songs::BARD_SONGS.iter().find(|s| s.id == sid).map(|s| s.combat_effect)
}), }),
spell_effect.as_ref(), spell_effect.as_ref(),
&mut rand::rng(), &mut rng,
); );
let (loot_names, updated_char) = { let (loot_names, updated_char) = {
let mut chars = state.characters.write().await; let mut chars = state.characters.write().await;
let dropped_ids: Vec<String> = if let Some(c) = chars.iter_mut().find(|c| c.id == char_id) { let dropped_ids: Vec<String> = if let Some(c) = chars.iter_mut().find(|c| c.id == char_id) {
use rand::Rng; use rand::Rng;
let mut rng = rand::rng();
if log.winner == c.name { if log.winner == c.name {
c.xp += log.xp_gained as i64; c.xp += log.xp_gained as i64;

View file

@ -126,10 +126,15 @@ pub fn training_cost(stat: i64) -> i32 {
(5.0_f64 * 1.3_f64.powi(scaled)) as i32 (5.0_f64 * 1.3_f64.powi(scaled)) as i32
} }
pub fn create_character(user_id: u32, name: String, race: Race, class: Class) -> Character { pub fn create_character(
let mut rng = rand::rng(); rng: &mut impl rand::Rng,
user_id: u32,
name: String,
race: Race,
class: Class,
) -> Character {
let class_str = class.as_str().to_string(); let class_str = class.as_str().to_string();
let stats = roll_stats(&mut rng, &race); let stats = roll_stats(rng, &race);
let max_hp = calc_max_hp(&class, &stats); let max_hp = calc_max_hp(&class, &stats);
let max_energy = calc_max_energy(&class); let max_energy = calc_max_energy(&class);
let max_nerve = calc_max_nerve(&class); let max_nerve = calc_max_nerve(&class);
@ -412,4 +417,28 @@ mod tests {
assert!(dwarf.strength > human.strength); assert!(dwarf.strength > human.strength);
assert!(dwarf.intelligence < human.intelligence); assert!(dwarf.intelligence < human.intelligence);
} }
#[test]
fn create_character_is_deterministic_under_seeded_rng() {
use rand::SeedableRng;
let first = create_character(
&mut rand::rngs::StdRng::seed_from_u64(42),
7,
"Twin".into(),
Race::Human,
Class::Warrior,
);
let second = create_character(
&mut rand::rngs::StdRng::seed_from_u64(42),
7,
"Twin".into(),
Race::Human,
Class::Warrior,
);
assert_eq!(format!("{:?}", first.stats), format!("{:?}", second.stats));
assert_eq!(first.hp, second.hp);
assert_eq!(first.max_hp, second.max_hp);
assert_eq!(first.energy, second.energy);
assert_eq!(first.nerve, second.nerve);
}
} }