Initial commit; parse game actions
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
commit
f51c9fe6da
7 changed files with 525 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "hanabi-repl"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "AGPL-3.0-or-later"
|
||||||
|
authors = ["Maxime “pep” Buquet <pep@bouah.net>"]
|
||||||
|
description = "Keep track of your Hanabi plays"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "3.2.16", features = ["derive"] }
|
||||||
|
nom = "7.1.1"
|
||||||
|
rustyline = "10.0.0"
|
30
src/args.rs
Normal file
30
src/args.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU Affero General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::types::Variant;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::default::Default;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Number of players
|
||||||
|
#[clap(short, long, value_parser = clap::value_parser!(u8).range(2..=5), default_value_t = 2u8)]
|
||||||
|
pub players: u8,
|
||||||
|
|
||||||
|
/// Game variant. Unused
|
||||||
|
#[clap(arg_enum, short, long, default_value_t = Default::default())]
|
||||||
|
pub variant: Variant,
|
||||||
|
}
|
63
src/error.rs
Normal file
63
src/error.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU Affero General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use nom;
|
||||||
|
use rustyline::error::ReadlineError;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
ParseColorError(String),
|
||||||
|
ParseDigitError(String),
|
||||||
|
NomError(nom::Err<nom::error::Error<String>>),
|
||||||
|
ReadlineError(ReadlineError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for Error {}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::ParseDigitError(err) => write!(f, "Parse digit error: {}", err),
|
||||||
|
Error::ParseColorError(err) => write!(f, "Parse color error: {}", err),
|
||||||
|
Error::NomError(err) => write!(f, "Nom error: {}", err),
|
||||||
|
Error::ReadlineError(err) => write!(f, "Readline error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<nom::Err<nom::error::Error<&'a str>>> for Error {
|
||||||
|
fn from(err: nom::Err<nom::error::Error<&'a str>>) -> Error {
|
||||||
|
let foo = match err {
|
||||||
|
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
|
||||||
|
nom::Err::Error(error) => nom::Err::Error(nom::error::Error::new(
|
||||||
|
String::from(error.input),
|
||||||
|
error.code,
|
||||||
|
)),
|
||||||
|
nom::Err::Failure(error) => nom::Err::Failure(nom::error::Error::new(
|
||||||
|
String::from(error.input),
|
||||||
|
error.code,
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
Error::NomError(foo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ReadlineError> for Error {
|
||||||
|
fn from(err: ReadlineError) -> Error {
|
||||||
|
Error::ReadlineError(err)
|
||||||
|
}
|
||||||
|
}
|
61
src/main.rs
Normal file
61
src/main.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU Affero General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
mod error;
|
||||||
|
mod parser;
|
||||||
|
mod types;
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::parser::parse_line;
|
||||||
|
use crate::types::Action;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use rustyline::{error::ReadlineError, Editor};
|
||||||
|
|
||||||
|
fn main() -> Result<(), Error> {
|
||||||
|
let args = Args::parse();
|
||||||
|
println!(
|
||||||
|
"Hanabi - Players: {} - Variants: {}",
|
||||||
|
args.players, args.variant
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
let mut rl = Editor::<()>::new()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let readline = rl.readline(">> ");
|
||||||
|
match readline {
|
||||||
|
Ok(line) => {
|
||||||
|
rl.add_history_entry(line.as_str());
|
||||||
|
if let Ok(action) = parse_line(line.as_str()) {
|
||||||
|
println!("Action: {:?}", action);
|
||||||
|
} else {
|
||||||
|
println!("Invalid command: {}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||||
|
println!("Quitting.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("Error: {:?}", err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
129
src/parser.rs
Normal file
129
src/parser.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU Affero General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::types::{Action, Color, ColorDigit, Digit};
|
||||||
|
use nom::{
|
||||||
|
branch::alt,
|
||||||
|
bytes::complete::tag,
|
||||||
|
character::complete::{digit1, space1},
|
||||||
|
error::{Error as NomError, ErrorKind},
|
||||||
|
multi::many0,
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
fn parse_color(i: &str) -> IResult<&str, ColorDigit> {
|
||||||
|
let (i, color) = alt((
|
||||||
|
tag("blue"),
|
||||||
|
tag("green"),
|
||||||
|
tag("purple"),
|
||||||
|
tag("red"),
|
||||||
|
tag("white"),
|
||||||
|
tag("yellow"),
|
||||||
|
))(i)?;
|
||||||
|
let color = Color::from_str(color).unwrap();
|
||||||
|
Ok((i, color.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_digit(i: &str) -> IResult<&str, ColorDigit> {
|
||||||
|
let (i, digit) = alt((
|
||||||
|
tag("1"),
|
||||||
|
tag("one"),
|
||||||
|
tag("2"),
|
||||||
|
tag("two"),
|
||||||
|
tag("3"),
|
||||||
|
tag("three"),
|
||||||
|
tag("4"),
|
||||||
|
tag("four"),
|
||||||
|
tag("5"),
|
||||||
|
tag("five"),
|
||||||
|
))(i)?;
|
||||||
|
let digit = Digit::from_str(digit).unwrap();
|
||||||
|
Ok((i, digit.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_play(i: &str) -> IResult<&str, Action> {
|
||||||
|
let (i, _) = alt((tag("play"), tag("p")))(i)?;
|
||||||
|
let (i, _) = space1(i)?;
|
||||||
|
let (i, slot) = digit1(i)?;
|
||||||
|
|
||||||
|
let slot = slot
|
||||||
|
.parse::<u8>()
|
||||||
|
.map_err(|_| nom::Err::Error(NomError::new("", ErrorKind::IsA)))?;
|
||||||
|
|
||||||
|
if slot < 1 || slot > 6 {
|
||||||
|
return Err(nom::Err::Error(NomError::new("", ErrorKind::IsA)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((i, Action::PlayCard(slot)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_slot(i: &str) -> IResult<&str, u8> {
|
||||||
|
let (i, slot) = digit1(i)?;
|
||||||
|
let slot = slot
|
||||||
|
.parse::<u8>()
|
||||||
|
.map_err(|_| nom::Err::Error(NomError::new("", ErrorKind::IsA)))?;
|
||||||
|
|
||||||
|
if slot < 1 || slot > 6 {
|
||||||
|
return Err(nom::Err::Error(NomError::new("", ErrorKind::IsA)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((i, slot))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_slot1(i: &str) -> IResult<&str, u8> {
|
||||||
|
let (i, _) = space1(i)?;
|
||||||
|
let (i, slot) = parse_slot(i)?;
|
||||||
|
|
||||||
|
Ok((i, slot))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_slots1(i: &str) -> IResult<&str, Vec<u8>> {
|
||||||
|
let (i, slot1) = parse_slot(i)?;
|
||||||
|
let (i, slots2) = many0(parse_slot1)(i)?;
|
||||||
|
|
||||||
|
let mut slots = vec![slot1];
|
||||||
|
slots.extend(slots2);
|
||||||
|
|
||||||
|
Ok((i, slots))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_drop(i: &str) -> IResult<&str, Action> {
|
||||||
|
let (i, _) = alt((tag("drop"), tag("d")))(i)?;
|
||||||
|
let (i, _) = space1(i)?;
|
||||||
|
let (i, slot) = parse_slot(i)?;
|
||||||
|
|
||||||
|
Ok((i, Action::DropCard(slot)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hint(i: &str) -> IResult<&str, Action> {
|
||||||
|
let (i, _) = alt((tag("hint"), tag("h")))(i)?;
|
||||||
|
let (i, _) = space1(i)?;
|
||||||
|
let (i, colordigit) = alt((parse_color, parse_digit))(i)?;
|
||||||
|
let (i, _) = space1(i)?;
|
||||||
|
let (i, slots) = parse_slots1(i)?;
|
||||||
|
|
||||||
|
Ok((i, Action::from((colordigit, slots))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_action(input: &str) -> IResult<&str, Action> {
|
||||||
|
let (i, action) = alt((parse_play, parse_drop, parse_hint))(input)?;
|
||||||
|
Ok((i, action))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_line(input: &str) -> Result<Action, Error> {
|
||||||
|
Ok(parse_action(input)?.1)
|
||||||
|
}
|
229
src/types.rs
Normal file
229
src/types.rs
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
// Copyright (C) 2022 Maxime “pep” Buquet <pep@bouah.net>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify it
|
||||||
|
// under the terms of the GNU Affero General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
use clap::ArgEnum;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, ArgEnum)]
|
||||||
|
pub enum Variant {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Multicolor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Variant {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Variant::Default => "default",
|
||||||
|
Variant::Multicolor => "multicolor",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Digit {
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
Four,
|
||||||
|
Five,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Digit {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Digit, Error> {
|
||||||
|
let s = s.trim().to_lowercase();
|
||||||
|
Ok(match s.as_str() {
|
||||||
|
"1" | "one" => Digit::One,
|
||||||
|
"2" | "two" => Digit::Two,
|
||||||
|
"3" | "three" => Digit::Three,
|
||||||
|
"4" | "four" => Digit::Four,
|
||||||
|
"5" | "five" => Digit::Five,
|
||||||
|
_ => return Err(Error::ParseDigitError(s.clone())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Color {
|
||||||
|
Blue,
|
||||||
|
Green,
|
||||||
|
Purple,
|
||||||
|
Red,
|
||||||
|
White,
|
||||||
|
Yellow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Color {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Color, Error> {
|
||||||
|
let s = s.trim().to_lowercase();
|
||||||
|
Ok(match s.as_str() {
|
||||||
|
"blue" => Color::Blue,
|
||||||
|
"green" => Color::Green,
|
||||||
|
"purple" => Color::Purple,
|
||||||
|
"red" => Color::Red,
|
||||||
|
"white" => Color::White,
|
||||||
|
"yellow" => Color::Yellow,
|
||||||
|
_ => return Err(Error::ParseColorError(s.clone())),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ColorDigit {
|
||||||
|
Color(Color),
|
||||||
|
Digit(Digit),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ColorDigit {
|
||||||
|
fn from(c: Color) -> Self {
|
||||||
|
ColorDigit::Color(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Digit> for ColorDigit {
|
||||||
|
fn from(d: Digit) -> Self {
|
||||||
|
ColorDigit::Digit(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Card {
|
||||||
|
pub digit: Digit,
|
||||||
|
pub color: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Slot(u8);
|
||||||
|
|
||||||
|
impl Deref for Slot {
|
||||||
|
type Target = u8;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Slot {
|
||||||
|
fn from(s: u8) -> Slot {
|
||||||
|
Slot(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Slot {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, " {}", *self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct Slots(Vec<Slot>);
|
||||||
|
|
||||||
|
impl Deref for Slots {
|
||||||
|
type Target = Vec<Slot>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Slots {
|
||||||
|
fn from(s: Vec<u8>) -> Slots {
|
||||||
|
Slots(s.into_iter().map(Slot).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<Slot>> for Slots {
|
||||||
|
fn from(s: Vec<Slot>) -> Slots {
|
||||||
|
Slots(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Slots {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.len() > 0 {
|
||||||
|
write!(f, "{}", self)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.len() > 1 {
|
||||||
|
for slot in self.iter() {
|
||||||
|
write!(f, " {}", slot)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum GameAction {
|
||||||
|
PlayCard(Slot),
|
||||||
|
DropCard(Slot),
|
||||||
|
ColorHint(Slots, Color),
|
||||||
|
DigitHint(Slots, Digit),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(ColorDigit, Slots)> for GameAction {
|
||||||
|
fn from((cd, slots): (ColorDigit, Slots)) -> Self {
|
||||||
|
match cd {
|
||||||
|
ColorDigit::Color(c) => GameAction::ColorHint(slots, c),
|
||||||
|
ColorDigit::Digit(d) => GameAction::DigitHint(slots, d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for GameAction {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
GameAction::PlayCard(c) => write!(f, "play {}", c),
|
||||||
|
GameAction::DropCard(c) => write!(f, "drop {}", c),
|
||||||
|
GameAction::ColorHint(slots, c) => write!(f, "hint {} {}", c, slots),
|
||||||
|
GameAction::DigitHint(slots, d) => write!(f, "hint {} {}", d, slots),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ReplAction {
|
||||||
|
CancelLast,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Action {
|
||||||
|
PlayCard(Slot),
|
||||||
|
DropCard(Slot),
|
||||||
|
ColorHint(Vec<Slot>, Color),
|
||||||
|
DigitHint(Vec<Slot>, Digit),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(ColorDigit, Vec<Slot>)> for Action {
|
||||||
|
fn from((cd, slots): (ColorDigit, Vec<Slot>)) -> Self {
|
||||||
|
match cd {
|
||||||
|
ColorDigit::Color(c) => Action::ColorHint(slots, c),
|
||||||
|
ColorDigit::Digit(d) => Action::DigitHint(slots, d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue