use std::{collections::HashMap, rc::Rc};
use chumsky::Parser;
use magister::Color;
use steel::{
rerrs,
steel_vm::{engine::Engine, register_fn::RegisterFn},
SteelErr, SteelVal,
};
use steel_derive::Steel;
#[derive(thiserror::Error, Debug)]
pub enum MagisterSteelError {}
#[derive(Clone, Copy, Steel, PartialEq, Eq, Hash, Debug)]
pub enum PlayedAs {
Magister,
Aide,
Fragment { is_set: bool },
}
#[derive(Clone, Debug, Steel)]
pub enum CardEvent {
PlayedAs(PlayedAs),
}
impl CardEvent {
pub(crate) fn played_as(val: SteelVal) -> Result<Self, SteelErr> {
let sym = val.symbol_or_else(|| {
SteelErr::new(
rerrs::ErrorKind::TypeMismatch,
format!("expected symbol, found {}", val),
)
})?;
match sym.to_ascii_lowercase().as_str() {
"magister" => Ok(Self::PlayedAs(PlayedAs::Magister)),
"aide" => Ok(Self::PlayedAs(PlayedAs::Aide)),
"fragment" => Ok(Self::PlayedAs(PlayedAs::Fragment { is_set: false })),
"set-fragment" => Ok(Self::PlayedAs(PlayedAs::Fragment { is_set: true })),
_ => Err(SteelErr::new(rerrs::ErrorKind::Generic, format!("invalid spec, expected one of ['magister, 'aide, 'fragment, 'set-fragment], found '{}", sym))),
}
}
}
#[derive(Clone, Steel)]
pub struct Effect {
implementation: Rc<Box<dyn Fn(&[()]) -> Vec<Action>>>,
}
impl std::fmt::Debug for Effect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Effect")
.finish_non_exhaustive()
}
}
impl Effect {
fn draw(amount: usize) -> Self {
Effect {
implementation: Rc::new(Box::new(move |_costs| {
(0..amount).map(|_| Action::Draw).collect()
})),
}
}
}
#[derive(Debug, Clone, Steel)]
pub enum Action {
Draw,
}
#[derive(Debug, Clone, Copy, Steel, PartialEq, Eq)]
pub enum Mana {
Color(usize, Color),
Any(usize),
}
impl Mana {
fn from_symbol_to_color<S: AsRef<str>>(s: S) -> Option<Color> {
let symbol = s.as_ref().to_ascii_lowercase();
match symbol.as_str() {
"mundane" | "m" | "grey" | "gray" => Some(Color::Mundane),
"divine" | "d" | "gold" => Some(Color::Divine),
"revelation" | "r" | "blue" => Some(Color::Revelation),
"grace" | "a" | "white" => Some(Color::Grace),
"growth" | "g" | "green" => Some(Color::Growth),
"crusade" | "u" | "red" => Some(Color::Crusade),
"suffering" | "s" | "black" => Some(Color::Suffering),
_ => None,
}
}
fn any(amount: usize) -> Self {
Self::Any(amount)
}
fn color(amount: usize, val: SteelVal) -> Result<Self, String> {
if let Some(color) = val
.as_symbol()
.and_then(|s| Self::from_symbol_to_color(s.as_str()))
{
Ok(Self::Color(amount, color))
} else {
Err(format!("expected mana symbol, found {}", val))
}
}
fn compact_parser<'a>() -> impl Parser<
'a,
&'a str,
Vec<(Option<Color>, usize)>,
chumsky::extra::Err<chumsky::prelude::Rich<'a, char>>,
> {
use chumsky::prelude::*;
choice((
just('m')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Mundane), s.len())),
just('d')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Divine), s.len())),
just('r')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Revelation), s.len())),
just('a')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Grace), s.len())),
just('g')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Growth), s.len())),
just('u')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Crusade), s.len())),
just('s')
.repeated()
.at_least(1)
.collect::<String>()
.map(|s: String| (Some(Color::Suffering), s.len())),
text::int(10).try_map(|i: &str, span| {
i.parse::<usize>()
.map(|i| (None, i))
.map_err(|e| Rich::custom(span, e))
}),
))
.repeated()
.collect()
}
fn compact(notation: String) -> Result<Vec<Mana>, String> {
let notation_input = notation.to_ascii_lowercase();
let (output, errs) = Self::compact_parser()
.parse(¬ation_input)
.into_output_errors();
match output {
Some(outputs) => {
let mut mana = HashMap::new();
for (maybe_color, count) in outputs.into_iter() {
*mana.entry(maybe_color).or_insert(0) += count;
}
Ok(mana
.into_iter()
.map(|(k, v)| {
if let Some(color) = k {
Mana::Color(v, color)
} else {
Mana::Any(v)
}
})
.collect())
}
None => {
errs.into_iter().for_each(|e| {
ariadne::Report::build(ariadne::ReportKind::Error, (), e.span().start)
.with_message(e.to_string())
.with_label(
ariadne::Label::new(e.span().into_range())
.with_message(e.reason().to_string())
.with_color(ariadne::Color::Red),
)
.finish()
.eprint(ariadne::Source::from(¬ation))
.unwrap()
});
Err(format!("Failed to parse compact mana string: {notation}"))
}
}
}
}
#[derive(Clone, Debug, Steel)]
pub enum Cost {
Mana(Vec<Mana>),
PerTurn(usize),
}
impl Cost {
fn mana(mana: Vec<Mana>) -> Self {
Self::Mana(mana)
}
fn per_turn(times: usize) -> Self {
Self::PerTurn(times)
}
}
#[derive(Clone, Debug, Steel)]
pub enum Ability {
Trigger {},
Activated {},
Invoked {},
}
impl Ability {
fn trigger(card_event: CardEvent, effect: Effect) -> Self {
dbg!(card_event);
dbg!(&effect);
dbg!((effect.implementation)(&[]));
Ability::Trigger {}
}
fn activated(cost: Cost, effect: Effect) -> Self {
dbg!(&cost);
dbg!(&effect);
dbg!((effect.implementation)(&[]));
Ability::Activated {}
}
fn invoked(effect: Effect) -> Self {
dbg!(&effect);
dbg!((effect.implementation)(&[]));
Ability::Invoked {}
}
}
pub fn expose_steel() -> Result<Engine, MagisterSteelError> {
let mut engine = Engine::new();
engine.register_type::<CardEvent>("event?");
engine.register_type::<Effect>("effect?");
engine.register_type::<Action>("action?");
engine.register_type::<Cost>("cost?");
engine.register_type::<Mana>("mana?");
engine.register_type::<Ability>("ability?");
engine
.register_fn("ability/trigger", Ability::trigger)
.register_fn("ability/activated", Ability::activated)
.register_fn("ability/invoked", Ability::invoked)
.register_fn("event/played-as", CardEvent::played_as)
.register_fn("effect/draw", Effect::draw)
.register_fn("cost/mana", Cost::mana)
.register_fn("cost/per-turn", Cost::per_turn)
.register_fn("mana/any", Mana::any)
.register_fn("mana/color", Mana::color)
.register_fn("mana/compact", Mana::compact);
Ok(engine)
}