use std::collections::HashMap;
use rand::prelude::SliceRandom;
use rand::rngs::StdRng;
pub trait NameMapper {
fn begin_hand(&mut self);
fn map_name(&mut self, original: &str) -> String;
}
const DEFAULT_NAME_POOL: &[&str] = &[
"SilverEagle",
"CrimsonFox",
"QuietRiver",
"BlueHarbor",
"IronOak",
"NorthernPine",
"AmberWolf",
"CopperHawk",
"GoldenKite",
"JadeOtter",
"VioletFinch",
"BronzeLynx",
"StoneHeron",
"RustyAnchor",
"MysticOwl",
"ScarletBadger",
"PaleMoon",
"EmeraldCrow",
"MarbleDeer",
"HiddenVale",
"LoneCedar",
"MossyStone",
"WildMeadow",
"ThornyElm",
"LuckyHollow",
"BarrenRidge",
"OpenPlains",
"SwiftHare",
"BrightDawn",
"DuskRanger",
"HollowDrum",
"NorthStar",
"PaperTiger",
"SaltyHarbor",
"SpicyReef",
"BrassCompass",
"CrystalLake",
"DeepRiver",
"FrostyPeak",
"GlassOcean",
"HighBluff",
"InkyShade",
"JollyRoger",
"KindredPath",
"LazyRiver",
"MistyForest",
"NimbleFox",
"OldLighthouse",
"PrismCove",
"QuickSilver",
"RisingTide",
"SilentCanyon",
"TawnyOwl",
"UmberMesa",
"VelvetSky",
"WhiteSpruce",
"XenonGlow",
"YellowBirch",
"ZenGarden",
"AlderGrove",
"BirchTrail",
"ClayPot",
"DuneWalker",
"ElderWood",
"FernLake",
"GraniteCliff",
"HazelHill",
"IvyBend",
"JasmineKey",
"KelpGarden",
"LimePoint",
"MapleRidge",
"NettleField",
"OakHarbor",
"PinePass",
"QuartzCove",
"RowanHollow",
"SageCreek",
"ThistlePath",
"UrchinBay",
"VineyardRow",
"WillowBend",
"AmberLantern",
"BasaltRock",
"CedarGrove",
"DriftwoodBeach",
"EmberCoal",
"FallenLog",
"GraniteShore",
"HeronPoint",
"IronGate",
"JadeLantern",
"KaleidoscopeSky",
"LanternGlow",
"MoonlitBay",
"NeedlePoint",
"OakenShield",
"PebbleBeach",
"QuillFeather",
"RiverStone",
"SilverMist",
"TorchFlame",
"UnderGrove",
"VesperStar",
"WhisperWind",
"ArcticFern",
"BoulderPass",
"CinderPath",
"DewDrop",
"EclipseRidge",
"FrostVine",
"GlacierRun",
"HollyGrove",
"IndigoLake",
"JunkyardRose",
"KiteRunner",
"LotusBloom",
"MoltenCore",
"NightFrost",
"OrchidWisp",
"PlumePass",
"QuiverArrow",
"RootedTree",
"SeedlingPath",
"ThunderCloud",
"UphillClimb",
"VerdantMeadow",
"WanderingStar",
"XylemFlow",
"YonderHill",
"ZephyrBreeze",
"AlpineEcho",
"BramblePatch",
"CascadeFall",
"DuneCrest",
"ElmShadow",
"FieldStone",
"GoldenRod",
"HarborLight",
"IvoryGate",
"JuniperHill",
"KestrelKite",
"LichenStone",
"MistyHarbor",
"NightOrchid",
"OakenPath",
"PrairieWind",
"QuellingCalm",
"RiverBirch",
"SlateRock",
"TulipGarden",
"UmbraMoon",
"ValleyStream",
"WalnutGrove",
"XanaduRest",
"YewBranch",
"ZebraRock",
"AutumnLeaf",
"BrambleWood",
"CliffEdge",
"DaisyChain",
"EchoVale",
"FlintSpark",
"GrainField",
"HolyOak",
"IceBloom",
"JadeFern",
"KnotwoodBow",
"LoamEarth",
"MarshLily",
"NorthernGale",
"OatField",
"PineNeedle",
"QuietDusk",
"RosePetal",
"SnowDrift",
"TinderBox",
"UrsineTrack",
"VoltGust",
"WispFlame",
"XyloRhythm",
"YarrowRoot",
"ZinniaBloom",
"AshenVale",
"BarkGrain",
"ClearStream",
"DriftSand",
"EdenBloom",
"FrostWind",
"GoldMeadow",
"HollowReed",
"InkyDusk",
"JuteCord",
"KindleFire",
"LatticeVine",
"MallowField",
"NightingaleSong",
"OpalShore",
"PinePitch",
"QuailNest",
"RustyGate",
"SaltMarsh",
"ThistleDown",
"UplandMoor",
"VesselHarbor",
"WeatherVane",
"AmaranthBloom",
"BlueSpruce",
"CloverMead",
"DuskLark",
];
struct NamePool {
names: Vec<String>,
cursor: usize,
anon_counter: u64,
}
impl NamePool {
fn new(custom: Option<Vec<String>>, rng: &mut StdRng) -> Self {
let mut names: Vec<String> =
custom.unwrap_or_else(|| DEFAULT_NAME_POOL.iter().map(|s| (*s).to_string()).collect());
names.shuffle(rng);
Self {
names,
cursor: 0,
anon_counter: 0,
}
}
fn next(&mut self) -> String {
if self.cursor < self.names.len() {
let name = self.names[self.cursor].clone();
self.cursor += 1;
name
} else {
self.anon_counter += 1;
format!("Anon{}", self.anon_counter)
}
}
}
pub struct PerHandNameMapper {
pool: NamePool,
current: HashMap<String, String>,
}
impl PerHandNameMapper {
pub fn new(custom_pool: Option<Vec<String>>, rng: &mut StdRng) -> Self {
Self {
pool: NamePool::new(custom_pool, rng),
current: HashMap::new(),
}
}
}
impl NameMapper for PerHandNameMapper {
fn begin_hand(&mut self) {
self.current.clear();
}
fn map_name(&mut self, original: &str) -> String {
if let Some(name) = self.current.get(original) {
return name.clone();
}
let name = self.pool.next();
self.current.insert(original.to_string(), name.clone());
name
}
}
pub struct StableNameMapper {
pool: NamePool,
assigned: HashMap<String, String>,
}
impl StableNameMapper {
pub fn new(custom_pool: Option<Vec<String>>, rng: &mut StdRng) -> Self {
Self {
pool: NamePool::new(custom_pool, rng),
assigned: HashMap::new(),
}
}
}
impl NameMapper for StableNameMapper {
fn begin_hand(&mut self) {}
fn map_name(&mut self, original: &str) -> String {
if let Some(name) = self.assigned.get(original) {
return name.clone();
}
let name = self.pool.next();
self.assigned.insert(original.to_string(), name.clone());
name
}
}
pub struct KeepNameMapper;
impl NameMapper for KeepNameMapper {
fn begin_hand(&mut self) {}
fn map_name(&mut self, original: &str) -> String {
original.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
fn rng() -> StdRng {
StdRng::seed_from_u64(42)
}
#[test]
fn stable_mapper_gives_same_name_across_calls() {
let mut m = StableNameMapper::new(None, &mut rng());
m.begin_hand();
let a1 = m.map_name("alice");
m.begin_hand();
let a2 = m.map_name("alice");
assert_eq!(a1, a2);
}
#[test]
fn per_hand_mapper_resets_between_hands() {
let mut m = PerHandNameMapper::new(None, &mut rng());
m.begin_hand();
let a1 = m.map_name("alice");
m.begin_hand();
let a2 = m.map_name("alice");
assert_ne!(a1, a2);
}
#[test]
fn distinct_originals_get_distinct_replacements() {
let mut m = StableNameMapper::new(None, &mut rng());
m.begin_hand();
assert_ne!(m.map_name("alice"), m.map_name("bob"));
}
#[test]
fn exhausting_pool_falls_back_to_anon_counter() {
let custom = vec!["only".to_string()];
let mut m = StableNameMapper::new(Some(custom), &mut rng());
m.begin_hand();
assert_eq!(m.map_name("first"), "only");
assert_eq!(m.map_name("second"), "Anon1");
assert_eq!(m.map_name("third"), "Anon2");
}
#[test]
fn seed_produces_deterministic_output() {
let mut a = StableNameMapper::new(None, &mut StdRng::seed_from_u64(1));
let mut b = StableNameMapper::new(None, &mut StdRng::seed_from_u64(1));
a.begin_hand();
b.begin_hand();
assert_eq!(a.map_name("alice"), b.map_name("alice"));
}
#[test]
fn keep_mapper_is_identity() {
let mut m = KeepNameMapper;
m.begin_hand();
assert_eq!(m.map_name("alice"), "alice");
}
}