pub const VARIANT_NAMESPACE: uuid::Uuid = uuid::Uuid::from_bytes([
0x72, 0x6f, 0x62, 0x6f, 0x70, 0x6f, 0x6b, 0x65, 0x72, 0x2e, 0x62, 0x6f, 0x74, 0x73, 0x2e, 0x76,
]);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Config {
pub depth: bool,
pub world: bool,
pub dirac: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
#[derive(Default)]
pub enum Variant {
#[default]
Fish,
Bot {
depth: bool,
world: bool,
dirac: bool,
},
}
impl Variant {
#[rustfmt::skip]
pub fn label(self) -> &'static str {
match self {
Self::Fish => "fish",
Self::Bot { depth: false, world: false, dirac: false } => "base",
Self::Bot { depth: true, world: false, dirac: false } => "depth",
Self::Bot { depth: false, world: true, dirac: false } => "world",
Self::Bot { depth: false, world: false, dirac: true } => "dirac",
Self::Bot { depth: true, world: true, dirac: false } => "depth+world",
Self::Bot { depth: true, world: false, dirac: true } => "depth+dirac",
Self::Bot { depth: false, world: true, dirac: true } => "world+dirac",
Self::Bot { depth: true, world: true, dirac: true } => "depth+world+dirac",
}
}
pub fn config(self) -> Option<Config> {
match self {
Self::Fish => None,
Self::Bot { depth, world, dirac } => Some(Config { depth, world, dirac }),
}
}
#[rustfmt::skip]
pub fn description(self) -> &'static str {
match self {
Self::Fish => "Random actions",
Self::Bot { depth: false, world: false, dirac: false } => "Sampled blueprint",
Self::Bot { depth: true, world: false, dirac: false } => "Depth-limited subgame",
Self::Bot { depth: false, world: true, dirac: false } => "World-partitioned subgame",
Self::Bot { depth: false, world: false, dirac: true } => "Argmax blueprint",
Self::Bot { depth: true, world: true, dirac: false } => "Depth + world subgame",
Self::Bot { depth: true, world: false, dirac: true } => "Argmax + depth",
Self::Bot { depth: false, world: true, dirac: true } => "Argmax + world",
Self::Bot { depth: true, world: true, dirac: true } => "Full subgame solver",
}
}
pub fn username(self) -> String {
format!("bot:{}", self.label())
}
pub fn uuid(self) -> uuid::Uuid {
uuid::Uuid::new_v5(&VARIANT_NAMESPACE, self.username().as_bytes())
}
pub fn requires_blueprint(self) -> bool {
matches!(self, Self::Bot { .. })
}
pub fn live_spawnable(self) -> bool {
true
}
#[rustfmt::skip]
pub const fn all() -> &'static [Self] {
&[
Self::Bot { depth: false, world: false, dirac: false },
Self::Bot { depth: true, world: false, dirac: false },
Self::Bot { depth: false, world: true, dirac: false },
Self::Bot { depth: false, world: false, dirac: true },
Self::Bot { depth: true, world: true, dirac: false },
Self::Bot { depth: true, world: false, dirac: true },
Self::Bot { depth: false, world: true, dirac: true },
Self::Bot { depth: true, world: true, dirac: true },
Self::Fish,
]
}
pub fn parse(token: &str) -> Option<Self> {
match token.trim() {
"fish" => Some(Self::Fish),
"base" => Some(Self::Bot {
depth: false,
world: false,
dirac: false,
}),
other => parse_flags(other),
}
}
}
fn parse_flags(token: &str) -> Option<Variant> {
let mut depth = false;
let mut world = false;
let mut dirac = false;
let mut last = 0u8;
for flag in token.split('+') {
let pos: u8 = match flag {
"depth" if !depth => {
depth = true;
1
}
"world" if !world => {
world = true;
2
}
"dirac" if !dirac => {
dirac = true;
3
}
_ => return None,
};
if pos <= last {
return None;
}
last = pos;
}
Some(Variant::Bot { depth, world, dirac })
}
pub fn slumbot_opponent_username() -> &'static str {
"bot:slumbot"
}
pub fn slumbot_opponent_uuid() -> uuid::Uuid {
uuid::Uuid::new_v5(&VARIANT_NAMESPACE, slumbot_opponent_username().as_bytes())
}