use core::fmt;
pub type TernaryValue = u8;
fn valid_ternary(v: TernaryValue) -> bool {
matches!(v, 0 | 1 | 2)
}
#[derive(Clone, PartialEq)]
pub struct TernaryAgent {
pub strategy: Vec<TernaryValue>,
pub fitness: f64,
}
impl TernaryAgent {
pub fn new(strategy: Vec<TernaryValue>) -> Option<Self> {
if strategy.iter().all(|&v| valid_ternary(v)) {
Some(Self { strategy, fitness: 0.0 })
} else {
None
}
}
pub fn new_unchecked(strategy: Vec<TernaryValue>) -> Self {
Self { strategy, fitness: 0.0 }
}
pub fn len(&self) -> usize {
self.strategy.len()
}
pub fn is_empty(&self) -> bool {
self.strategy.is_empty()
}
}
impl fmt::Debug for TernaryAgent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Agent({:?}, fit={:.3})", self.strategy, self.fitness)
}
}
#[derive(Clone)]
pub struct TernaryPopulation {
pub agents: Vec<TernaryAgent>,
pub strategy_len: usize,
}
impl TernaryPopulation {
pub fn new(agents: Vec<TernaryAgent>) -> Option<Self> {
if agents.is_empty() {
return None;
}
let len = agents[0].len();
if !agents.iter().all(|a| a.len() == len) {
return None;
}
Some(Self { agents, strategy_len: len })
}
pub fn random(n: usize, strategy_len: usize) -> Self {
let mut rng = SimpleRng::from_seed(42);
let agents = (0..n)
.map(|_| {
let strategy: Vec<TernaryValue> = (0..strategy_len)
.map(|_| (rng.next_u32() % 3) as TernaryValue)
.collect();
TernaryAgent::new_unchecked(strategy)
})
.collect();
Self { agents, strategy_len }
}
pub fn random_seeded(n: usize, strategy_len: usize, seed: u64) -> Self {
let mut rng = SimpleRng::from_seed(seed);
let agents = (0..n)
.map(|_| {
let strategy: Vec<TernaryValue> = (0..strategy_len)
.map(|_| (rng.next_u32() % 3) as TernaryValue)
.collect();
TernaryAgent::new_unchecked(strategy)
})
.collect();
Self { agents, strategy_len }
}
pub fn size(&self) -> usize {
self.agents.len()
}
pub fn sort_by_fitness(&mut self) {
self.agents.sort_by(|a, b| b.fitness.partial_cmp(&a.fitness).unwrap_or(std::cmp::Ordering::Equal));
}
pub fn best(&self) -> &TernaryAgent {
self.agents.iter().max_by(|a, b| a.fitness.partial_cmp(&b.fitness).unwrap_or(std::cmp::Ordering::Equal)).unwrap()
}
pub fn avg_fitness(&self) -> f64 {
if self.agents.is_empty() { return 0.0; }
self.agents.iter().map(|a| a.fitness).sum::<f64>() / self.agents.len() as f64
}
pub fn worst(&self) -> &TernaryAgent {
self.agents.iter().min_by(|a, b| a.fitness.partial_cmp(&b.fitness).unwrap_or(std::cmp::Ordering::Equal)).unwrap()
}
}
#[derive(Clone)]
pub struct SimpleRng {
state: u64,
}
impl SimpleRng {
pub fn from_seed(seed: u64) -> Self {
Self { state: if seed == 0 { 0xDEAD_BEEF_CAFE_BABE } else { seed } }
}
pub fn next_u32(&mut self) -> u32 {
self.state ^= self.state << 13;
self.state ^= self.state >> 7;
self.state ^= self.state << 17;
self.state as u32
}
pub fn next_u64(&mut self) -> u64 {
let hi = self.next_u32() as u64;
let lo = self.next_u32() as u64;
(hi << 32) | lo
}
pub fn next_f64(&mut self) -> f64 {
(self.next_u32() as f64) / (u32::MAX as f64)
}
}