//! Experiments with writing magister card code
use std::{collections::HashMap, rc::Rc, str::MatchIndices};
use magister::Color;
use steel::{
steel_vm::{engine::Engine, register_fn::RegisterFn},
SteelVal,
};
use steel_derive::Steel;
#[derive(thiserror::Error, Debug)]
pub enum MagisterSteelError {}
/// What can a card be played as?
#[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, String> {
if let Some(sym) = val.as_symbol() {
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(format!("invalid played-as specifier: {}", sym)),
}
} else {
Err(format!("expected symbol as input, found {}", val))
}
}
}
// TODO Revome from this crate and put in `magister` once we've determined how this should be shaped
/// A struct that is given input board state, event input (if applicable), and variables (X and O if applicable)
/// and outputs a list of actions.
#[derive(Clone, Steel)]
pub struct Effect {
/// The number of required costs that *must* be satisfied
// required: usize,
// In the input cost array, all required costs are specfied first, then any optional costs.
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")
// .field("required", &self.required)
.finish_non_exhaustive()
}
}
impl Effect {
fn draw(amount: usize) -> Self {
Effect {
// required: 1, // this is
implementation: Rc::new(Box::new(move |_costs| {
(0..amount).map(|_| Action::Draw).collect()
})),
}
}
}
// TODO instead of "moveing to magister", create a separate magister type that Into's into the script representation,
// and have the "magister_reducer" take in anything that can Into into the script version, as that's what'll be
// running the game.
/// A possible game action
#[derive(Debug, Clone, Steel)]
pub enum Action {
Draw,
}
/// Representaions of mana for scripting
#[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,
}
}
/// Any type of mana suffices
fn any(amount: usize) -> Self {
Self::Any(amount)
}
/// Only mana of a specific color suffices
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(notation: String) -> Vec<Mana> {
let mut mana = HashMap::new();
let mut start_index = 0;
let mut end_index = 0;
let notation = notation.to_ascii_lowercase();
while start_index < notation.len() {
// inc end_index
end_index += 1;
let slice = ¬ation[start_index..end_index];
match slice {
"m" => {
*mana.entry(Some(Color::Mundane)).or_insert(0) += 1;
start_index = end_index;
}
"d" => {
*mana.entry(Some(Color::Divine)).or_insert(0) += 1;
start_index = end_index;
}
"r" => {
*mana.entry(Some(Color::Revelation)).or_insert(0) += 1;
start_index = end_index;
}
"a" => {
*mana.entry(Some(Color::Grace)).or_insert(0) += 1;
start_index = end_index;
}
"g" => {
*mana.entry(Some(Color::Growth)).or_insert(0) += 1;
start_index = end_index;
}
"u" => {
*mana.entry(Some(Color::Crusade)).or_insert(0) += 1;
start_index = end_index;
}
"s" => {
*mana.entry(Some(Color::Suffering)).or_insert(0) += 1;
start_index = end_index;
}
_ => {
while end_index <= notation.len()
&& notation[start_index..end_index]
.chars()
.all(|c| c.is_digit(10))
{
end_index += 1;
}
// End index is 1 too high, sub 1
end_index -= 1;
let number: usize = notation[start_index..end_index]
.parse()
.expect("expected parseable base-10 digit string");
*mana.entry(None).or_insert(0) += number;
start_index = end_index;
}
}
}
mana.into_iter()
.map(|(k, v)| {
if let Some(color) = k {
Mana::Color(v, color)
} else {
Mana::Any(v)
}
})
.collect()
}
}
/// Represents a cost that can be paid.
#[derive(Clone, Debug, Steel)]
pub enum Cost {
Mana(Vec<Mana>),
PerTurn(usize),
}
impl Cost {
/// Specify a cost that can only be paid in mana
fn mana(mana: Vec<Mana>) -> Self {
Self::Mana(mana)
}
/// Specfiy a cost that can only be paid a certain number of times per turn
fn per_turn(times: usize) -> Self {
Self::PerTurn(times)
}
}
#[derive(Clone, Debug, Steel)]
pub enum Ability {
Trigger {},
Activated {},
/// This is the "ability" of a Fragment
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 {}
}
// The cost of this kind of ability is the cost of the card itself
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>("card-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("card-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)
}