use super::types::*;
use crate::state::State;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReputationState {
entries: HashMap<String, ReputationEntry>,
}
impl State for ReputationState {}
impl ReputationState {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
fn make_key(subject_id: &SubjectId, category: Option<&str>) -> String {
match category {
Some(cat) => format!("{}->{}:{}", subject_id.observer, subject_id.target, cat),
None => format!("{}->{}", subject_id.observer, subject_id.target),
}
}
pub fn get(&self, subject_id: &SubjectId) -> Option<f32> {
let key = Self::make_key(subject_id, None);
self.entries.get(&key).map(|entry| entry.score)
}
pub fn get_entry(&self, subject_id: &SubjectId) -> Option<&ReputationEntry> {
let key = Self::make_key(subject_id, None);
self.entries.get(&key)
}
pub fn get_entry_mut(&mut self, subject_id: &SubjectId) -> Option<&mut ReputationEntry> {
let key = Self::make_key(subject_id, None);
self.entries.get_mut(&key)
}
pub fn set(&mut self, subject_id: &SubjectId, score: f32) {
let key = Self::make_key(subject_id, None);
self.entries
.entry(key)
.and_modify(|e| e.score = score)
.or_insert_with(|| ReputationEntry::new(subject_id.clone(), score));
}
pub fn adjust(&mut self, subject_id: &SubjectId, delta: f32, default_score: f32) -> (f32, f32) {
let key = Self::make_key(subject_id, None);
let entry = self
.entries
.entry(key)
.or_insert_with(|| ReputationEntry::new(subject_id.clone(), default_score));
let old_score = entry.score;
entry.adjust(delta);
(old_score, entry.score)
}
pub fn remove(&mut self, subject_id: &SubjectId) -> Option<ReputationEntry> {
let key = Self::make_key(subject_id, None);
self.entries.remove(&key)
}
pub fn get_category(&self, subject_id: &SubjectId, category: &str) -> Option<f32> {
let key = Self::make_key(subject_id, Some(category));
self.entries.get(&key).map(|entry| entry.score)
}
pub fn get_category_entry(
&self,
subject_id: &SubjectId,
category: &str,
) -> Option<&ReputationEntry> {
let key = Self::make_key(subject_id, Some(category));
self.entries.get(&key)
}
pub fn get_category_entry_mut(
&mut self,
subject_id: &SubjectId,
category: &str,
) -> Option<&mut ReputationEntry> {
let key = Self::make_key(subject_id, Some(category));
self.entries.get_mut(&key)
}
pub fn set_category(&mut self, subject_id: &SubjectId, category: String, score: f32) {
let key = Self::make_key(subject_id, Some(&category));
self.entries
.entry(key)
.and_modify(|e| e.score = score)
.or_insert_with(|| {
ReputationEntry::new(subject_id.clone(), score).with_category(category)
});
}
pub fn adjust_category(
&mut self,
subject_id: &SubjectId,
category: String,
delta: f32,
default_score: f32,
) -> (f32, f32) {
let key = Self::make_key(subject_id, Some(&category));
let entry = self.entries.entry(key).or_insert_with(|| {
ReputationEntry::new(subject_id.clone(), default_score).with_category(category)
});
let old_score = entry.score;
entry.adjust(delta);
(old_score, entry.score)
}
pub fn remove_category(
&mut self,
subject_id: &SubjectId,
category: &str,
) -> Option<ReputationEntry> {
let key = Self::make_key(subject_id, Some(category));
self.entries.remove(&key)
}
pub fn clamp_score(
&mut self,
subject_id: &SubjectId,
min: f32,
max: f32,
) -> Option<(f32, f32)> {
let key = Self::make_key(subject_id, None);
self.entries.get_mut(&key).map(|entry| {
let old = entry.score;
entry.clamp(min, max);
(old, entry.score)
})
}
pub fn clamp_score_category(
&mut self,
subject_id: &SubjectId,
category: &str,
min: f32,
max: f32,
) -> Option<(f32, f32)> {
let key = Self::make_key(subject_id, Some(category));
self.entries.get_mut(&key).map(|entry| {
let old = entry.score;
entry.clamp(min, max);
(old, entry.score)
})
}
pub fn apply_decay(&mut self, default_score: f32, decay_rate: f32) {
for entry in self.entries.values_mut() {
let diff = default_score - entry.score;
entry.score += diff * decay_rate;
}
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &ReputationEntry> {
self.entries.values()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ReputationEntry> {
self.entries.values_mut()
}
}
impl Default for ReputationState {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_state_new() {
let state = ReputationState::new();
assert!(state.is_empty());
assert_eq!(state.len(), 0);
}
#[test]
fn test_set_and_get() {
let mut state = ReputationState::new();
let id = SubjectId::new("player", "kingdom");
assert!(state.get(&id).is_none());
state.set(&id, 75.0);
assert_eq!(state.get(&id), Some(75.0));
state.set(&id, 90.0);
assert_eq!(state.get(&id), Some(90.0));
}
#[test]
fn test_adjust() {
let mut state = ReputationState::new();
let id = SubjectId::new("player", "kingdom");
let (old, new) = state.adjust(&id, 10.0, 0.0);
assert_eq!(old, 0.0);
assert_eq!(new, 10.0);
let (old, new) = state.adjust(&id, 15.0, 0.0);
assert_eq!(old, 10.0);
assert_eq!(new, 25.0);
let (old, new) = state.adjust(&id, -5.0, 0.0);
assert_eq!(old, 25.0);
assert_eq!(new, 20.0);
}
#[test]
fn test_category() {
let mut state = ReputationState::new();
let id = SubjectId::new("player", "npc_alice");
state.set_category(&id, "romance".into(), 10.0);
state.set_category(&id, "friendship".into(), 20.0);
assert_eq!(state.get_category(&id, "romance"), Some(10.0));
assert_eq!(state.get_category(&id, "friendship"), Some(20.0));
assert!(state.get(&id).is_none());
}
#[test]
fn test_clamp() {
let mut state = ReputationState::new();
let id = SubjectId::new("player", "kingdom");
state.set(&id, 150.0);
let result = state.clamp_score(&id, -100.0, 100.0);
assert_eq!(result, Some((150.0, 100.0)));
assert_eq!(state.get(&id), Some(100.0));
state.set(&id, -150.0);
let result = state.clamp_score(&id, -100.0, 100.0);
assert_eq!(result, Some((-150.0, -100.0)));
assert_eq!(state.get(&id), Some(-100.0));
}
#[test]
fn test_decay() {
let mut state = ReputationState::new();
let id = SubjectId::new("player", "kingdom");
state.set(&id, 100.0);
state.apply_decay(0.0, 0.1);
assert_eq!(state.get(&id), Some(90.0));
state.apply_decay(0.0, 0.1);
assert_eq!(state.get(&id), Some(81.0));
}
#[test]
fn test_remove() {
let mut state = ReputationState::new();
let id = SubjectId::new("player", "kingdom");
state.set(&id, 50.0);
assert!(state.get(&id).is_some());
let removed = state.remove(&id);
assert!(removed.is_some());
assert_eq!(removed.unwrap().score, 50.0);
assert!(state.get(&id).is_none());
}
#[test]
fn test_reverse_relationship() {
let mut state = ReputationState::new();
let player_to_kingdom = SubjectId::new("player", "kingdom");
let kingdom_to_player = player_to_kingdom.reverse();
state.set(&player_to_kingdom, 75.0);
state.set(&kingdom_to_player, 50.0);
assert_eq!(state.get(&player_to_kingdom), Some(75.0));
assert_eq!(state.get(&kingdom_to_player), Some(50.0));
}
}