EW2S6QYPETMVAQJYM3OIUQNI5A7USN3QLQFAWQC6GEAF4YF7BITAC
use std::fmt;
use crate::deck::Deck;
use crate::card::Card;
#[derive(Debug, Default)]
pub struct Turn {
pub master_index: Option<usize>,
pub fool_played: bool,
cards: Deck,
}
impl Turn {
pub fn take(&mut self) -> Deck {
Deck(self.cards.0.drain(..).collect())
}
pub fn put(&mut self, card: Card) {
self.cards.push(card);
}
pub fn len(&self) -> usize {
self.cards.len()
}
pub fn is_empty(&self) -> bool {
self.cards.is_empty()
}
pub fn called(&self) -> Option<&Card> {
for c in &self.cards.0 {
if c.is_fool() {
continue
} else {
return Some(c)
}
}
None
}
pub fn master_card(&self) -> Option<&Card> {
if let Some(index) = self.master_index {
Some(&self.cards.0[index])
} else {
None
}
}
}
impl fmt::Display for Turn {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Turn cards: {}", &self.cards)?;
if let Some(called) = self.called() {
write!(f, "\nCalled: {}", &called)?;
}
if let Some(master) = self.master_card() {
write!(f, "\nMaster: {}", &master)?;
}
Ok(())
}
}
use std::fmt;
use crate::traits::*;
pub const TRUMP_COLOR : char = '🂠';
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, EnumIter)]
pub enum TrumpValue {
Fool = 0,
Petit = 1,
_2 = 2,
_3 = 3,
_4 = 4,
_5 = 5,
_6 = 6,
_7 = 7,
_8 = 8,
_9 = 9,
_10 = 10,
_11 = 11,
_12 = 12,
_13 = 13,
_14 = 14,
_15 = 15,
_16 = 16,
_17 = 17,
_18 = 18,
_19 = 19,
_20 = 20,
_21 = 21
}
impl fmt::Display for TrumpValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Fool => write!(f, "🃏"),
_ => write!(f, "{0} : {1: <2}", TRUMP_COLOR, *self as usize)
}
}
}
impl TrumpValue {
pub fn is_oudler(self) -> bool {
match self {
Self::Fool | Self::Petit | Self::_21 => true,
_ => false
}
}
}
impl Discardable for TrumpValue {
fn discardable(&self) -> bool {
// RULE: cant discard trumps
false
}
fn discardable_forced(&self) -> bool {
// RULE: if we have 4 kings and x trumps, we must discard some trumps, except oudlers
!self.is_oudler()
}
}
impl Points for TrumpValue {
fn points(&self) -> f64 {
if self.is_oudler() {
4.5
} else {
0.5
}
}
}
pub trait Points {
fn points(&self) -> f64;
}
pub trait Power {
fn power(&self) -> usize;
}
pub trait Discardable {
fn discardable(&self) -> bool;
fn discardable_forced(&self) -> bool;
}
use std::fmt;
#[derive(Eq, PartialEq, Clone, Debug)]
pub enum Team {
Defense,
Attack,
}
impl fmt::Display for Team {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Self::Defense => write!(f, "defense"),
Self::Attack => write!(f, "attack"),
}
}
}
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub enum Role {
Taker,
Ally,
Defenser,
}
impl fmt::Display for Role {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Taker => write!(f, "taker"),
Self::Ally => write!(f, "ally of taker"),
Self::Defenser => write!(f, "defenser"),
}
}
}
use std::fmt;
use rand::Rng;
use failure::Error;
use strum::IntoEnumIterator;
use crate::itertools::Itertools;
use crate::traits::*;
use crate::contract::*;
use crate::deck::*;
use crate::color::*;
use crate::trump::*;
use crate::card::*;
use crate::errors::*;
use crate::team::*;
use crate::role::*;
use crate::mode::*;
use crate::turn::*;
use crate::handle::*;
use crate::helpers::*;
//pub struct PlayerState<'a> {
// pub contract: Option<Contract>,
// pub team: Option<Team>,
// pub role: Option<Role>,
// pub callee: Option<Card>,
// pub handle : Option<Handle>,
// pub slam: bool,
// pub hand: Deck<'a>,
// pub owned: Deck<'a>,
//}
//
//#[derive(Default, Clone, Debug)]
//pub struct Player {
// pub name: String,
// pub total: f64,
// pub mode: Mode,
// random: bool,
//}
#[derive(Default, Clone, Debug)]
pub struct Player {
pub name: String,
pub contract: Option<Contract>,
pub slam: bool,
pub team: Option<Team>,
pub role: Option<Role>,
pub hand: Deck,
pub owned: Deck,
pub callee: Option<Card>,
pub total: f64,
pub mode: Mode,
pub handle : Option<Handle>,
random: bool,
}
pub fn default_name(index: usize) -> Result<String, Error> {
match index {
0 => Ok("East".to_string()),
1 => Ok("North".to_string()),
2 => Ok("South".to_string()),
3 => Ok("West".to_string()),
4 => Ok("Compass".to_string()),
_ => Err(TarotErrorKind::InvalidCase.into())
}
}
impl fmt::Display for Player {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self.contract {
Some(contract) => {
write!(f, "{}, total: {}, contract: {}, slam: {}", &self.name, &self.total, &contract, &self.slam)?;
},
None => {
write!(f, "{}, total: {}, no contract yet, slam: {}", self.name, &self.total, &self.slam)?;
},
}
if let Some(role) = &self.role {
write!(f, ", Role : {}", role)?;
}
if let Some(team) = &self.team {
write!(f, ", Team : {}", team)?;
}
if let Some(callee) = &self.callee {
write!(f, ", Callee : {}", callee)?;
}
Ok(())
}
}
impl Player
{
pub fn new(mode: Mode, random: bool) -> Player {
Player {
mode,
random,
..Player::default()
}
}
pub fn prepare(&mut self) {
self.contract = None;
self.slam = false;
self.team = None;
self.role = None;
self.callee = None;
self.handle = None;
}
pub fn slam_bonus(&self) -> f64 {
if self.slam {
if self.owned.is_chelem() {
400.0
} else {
-200.0
}
} else if self.owned.is_chelem() {
200.0
} else if self.owned.is_empty() || (self.owned.len() == 1 && self.owned.has_fool()) {
-200.0
} else {
0.0
}
}
pub fn announce_slam(&mut self) -> bool {
let slams = vec![
false,
true,
];
self.slam = if self.random {
slams[rand::thread_rng().gen_range(0, slams.len())]
} else {
loop {
println!("Hand of {} : {}", &self, &self.hand);
println!("Slam ? : ");
for (i, s) in slams.iter().enumerate() {
println!("{} : press {}", s, i);
}
let slam_index = read_index();
if slam_index < slams.len() {
break slams[slam_index]
} else {
println!("Error, please retry")
}
}
};
self.slam
}
pub fn announce_handle(&mut self) {
let mut trumps = self.hand.trumps();
let discarded_trumps = self.owned.trumps();
let mut total_trumps = trumps.len() + discarded_trumps.len();
let handle = Handle::new(total_trumps, self.mode);
self.handle = match handle {
None => None,
Some(mut handle) => {
let handles = match handle {
Handle::Simple => vec![Handle::Refused, Handle::Simple],
Handle::Double => vec![Handle::Refused, Handle::Simple, Handle::Double],
Handle::Triple => vec![Handle::Refused, Handle::Simple, Handle::Double, Handle::Triple],
Handle::Refused => vec![]
};
handle = if self.random {
handles[rand::thread_rng().gen_range(0, handles.len())].clone()
} else {
loop {
for &a in trumps.iter() {
println!("\t{}", &a);
}
println!("You have {} trumps, you can declare a handle : ", trumps.len());
for (i, h) in handles.iter().enumerate() {
println!("{} (limit: {}) : press {}", h, h.limit(self.mode), i);
}
let handle_index = read_index();
if handle_index < handles.len() {
break handles[handle_index].clone()
} else {
println!("Error, please retry")
}
}
};
if handle != Handle::Refused {
trumps.retain(|&c| !c.is_fool());
// RULE: cant use fool as trump when you have too much trumps for the handle
if total_trumps != trumps.len() + discarded_trumps.len() {
println!("You can't use fool as trumps in a handle");
}
trumps.extend(discarded_trumps.iter());
total_trumps = trumps.len();
let limit = handle.limit(self.mode);
if total_trumps > limit {
let mut to_discard = total_trumps - limit;
while to_discard > 0 {
loop {
for (i, a) in trumps.iter().enumerate() {
println!("\t{0} : {1}", &i, &a);
}
println!("You must discards {} trumps to present only {}", &to_discard, &limit);
if self.random {
let index_to_remove = rand::thread_rng().gen_range(0, trumps.len());
trumps.remove(index_to_remove);
break
} else {
let trump_index = read_index();
if trump_index < trumps.len() {
trumps.remove(trump_index);
} else {
println!("Error, please retry")
}
}
}
to_discard -= 1;
}
} else {
println!("You have exactly the good number of trumps");
}
println!("Final handle : ");
for a in trumps.iter() {
println!("\t{}", &a);
}
}
Some(handle)
}
};
}
pub fn last_turn(&self) -> bool {
self.hand.is_empty()
}
pub fn before_last_turn(&self) -> bool {
self.hand.len() == 1
}
pub fn owe_card(&self) -> bool {
self.owned.has_fool() && self.owned.len() != 1 && (self.owned.len() % self.mode as usize) == 1
}
pub fn missing_card(&self) -> bool {
!self.owned.has_fool() && self.owned.len() != 1 && (self.owned.len() % self.mode as usize) == (self.mode as usize - 1)
}
pub fn give_low(&mut self) -> Option<Card> {
self.owned.give_low()
}
pub fn give_one(&mut self, index: usize) -> Card {
self.hand.0.remove(index)
}
pub fn points(&self) -> f64 {
self.owned.points()
}
pub fn count_oudlers(&self) -> usize {
self.owned.count_oudlers()
}
pub fn contract_points(&self) -> Result<f64, Error> {
let points = self.points();
let contract_points = self.owned.points_for_oudlers()?;
println!("Taker owned points: {}", &points);
println!("Contract todo: {}", &contract_points);
println!("Contract difference: {}", points - contract_points);
match self.contract {
Some(Contract::Pass) | None => Err(TarotErrorKind::NoContract.into()),
Some(contract) => {
println!("Taker contract: {}", &contract);
if points >= contract_points {
println!("Contract total: {}", points - contract_points + 25.0);
Ok((points - contract_points + 25.0) * f64::from(contract as u8))
} else {
println!("Contract total: {}", points - contract_points - 25.0);
Ok((points - contract_points - 25.0) * f64::from(contract as u8))
}
}
}
}
pub fn is_first_turn(&self) -> bool {
match self.mode {
Mode::Three => self.hand.len() == 24,
Mode::Four => self.hand.len() == 18,
Mode::Five => self.hand.len() == 15,
}
}
pub fn call(&self) -> Result<Card, Error> {
if self.mode != Mode::Five {
return Err(TarotErrorKind::InvalidMode.into());
}
let mut value_callable : Vec<ColorValue> = Vec::new();
value_callable.push(ColorValue::King);
if self.hand.count_tete(ColorValue::King) == 4 {
value_callable.push(ColorValue::Queen);
if self.hand.count_tete(ColorValue::Queen) == 4 {
value_callable.push(ColorValue::Knight);
if self.hand.count_tete(ColorValue::Knight) == 4 {
value_callable.push(ColorValue::Jack);
if self.hand.count_tete(ColorValue::Jack) == 4 {
println!("Case too rare, taker has all kings, all queens and all knights");
return Err(TarotErrorKind::InvalidCase.into())
}
}
}
}
let choices : Vec<Card> = Color::iter().cartesian_product(value_callable.iter()).map(|(c, cv)| Card::Color(c, *cv)).collect();
if self.random {
Ok(choices[rand::thread_rng().gen_range(0, choices.len())])
} else {
loop {
println!("Hand of taker {}", &self.hand);
println!("Taker must choose a card to call his partner :");
println!("Possibilities:");
for (i, c) in choices.iter().enumerate() {
println!("\t{0: <3} : press {1}", c, i);
}
let choice_index = read_index();
if choice_index < choices.len() {
break Ok(choices[choice_index])
} else {
println!("Error, please retry")
}
}
}
}
pub fn discard (&mut self, discard: usize) {
println!("{}", &self);
println!("You must discard {} cards", discard);
for _ in 0..discard {
let discardables_indexes = self.hand.discardables(discard);
let discard_index = if self.random {
discardables_indexes[rand::thread_rng().gen_range(0, discardables_indexes.len())]
} else {
loop {
println!("Hand of taker {}", &self.hand);
println!("Possibilities:");
for &i in &discardables_indexes {
println!("\t{0: <4} : press {1}", self.hand.0[i], i);
}
let discard_index = read_index();
if discardables_indexes.contains(&discard_index) {
break discard_index
} else {
println!("Error, please retry")
}
}
};
self.owned.push(self.hand.0.remove(discard_index));
}
for c in self.owned.trumps() {
println!("This trump was discarded: {}", &c);
}
self.hand.sort();
}
pub fn choices(&self, turn: &Turn) -> Result<Vec<usize>, Error> {
let mut and_fool : Option<usize> = None;
let mut trumps = Vec::new();
let mut trumps_less = Vec::new();
let mut trumps_more = Vec::new();
let mut other_colors = Vec::new();
let mut same_color = Vec::new();
let mut compatibles = match (turn.called(), turn.master_card()) {
(Some(Card::Color(called_color, _)), Some(Card::Color(_, _))) => {
for (i, card) in self.hand.0.iter().enumerate() {
match card {
Card::Trump(card_trump_value) => {
if card_trump_value == &TrumpValue::Fool {
and_fool = Some(i);
} else {
trumps.push(i);
}
},
Card::Color(card_color, _) => {
if card_color == called_color {
same_color.push(i);
} else {
other_colors.push(i);
}
}
}
}
if !same_color.is_empty() {
same_color
} else if !trumps.is_empty() {
trumps
} else {
other_colors
}
},
(Some(Card::Color(called_color, _)), Some(Card::Trump(master_trump_value))) => {
for (i, card) in self.hand.0.iter().enumerate() {
match card {
Card::Trump(card_trump_value) => {
if card_trump_value == &TrumpValue::Fool {
and_fool = Some(i);
} else if card_trump_value > master_trump_value {
trumps_more.push(i);
} else {
trumps_less.push(i);
}
},
Card::Color(card_color, _) => {
if card_color == called_color {
same_color.push(i);
} else {
other_colors.push(i);
}
}
}
}
if !same_color.is_empty() {
same_color
} else if !trumps_more.is_empty() {
trumps_more
} else if !trumps_less.is_empty() {
trumps_less
} else {
other_colors
}
},
(Some(Card::Trump(_)), Some(Card::Trump(master_trump_value))) => {
for (i, card) in self.hand.0.iter().enumerate() {
if let Card::Trump(card_trump_value) = card {
if card_trump_value == &TrumpValue::Fool {
and_fool = Some(i);
} else {
trumps.push(i);
if card_trump_value > master_trump_value {
trumps_more.push(i);
} else {
trumps_less.push(i);
other_colors.push(i);
}
}
} else {
other_colors.push(i)
}
}
if !trumps_more.is_empty() {
trumps_more
} else if !trumps_less.is_empty() {
trumps_less
} else {
other_colors
}
},
(Some(Card::Color(_, _)), None) => {
println!("There cannot be a called color and no master card, impossible case!");
return Err(TarotErrorKind::InvalidCase.into())
}
(Some(Card::Trump(_)), Some(Card::Color(_, _))) => {
println!("There cannot be a called trump and a master color, impossible case!");
return Err(TarotErrorKind::InvalidCase.into())
}
(Some(Card::Trump(_)), None) => {
println!("There cannot be a called trump and not master, impossible case!");
return Err(TarotErrorKind::InvalidCase.into())
}
(None, Some(_)) => {
println!("There cannot be no called color and a master, impossible case!");
return Err(TarotErrorKind::InvalidCase.into())
},
// RULE: first player can put the callee but no any other card in the same color
(None, None) => match (self.is_first_turn(), self.mode) {
(true, Mode::Five) => {
self.hand.0.iter().enumerate().filter(|(_, &card)| {
match (card, self.callee) {
(Card::Color(color, value), Some(Card::Color(callee_color, callee_value))) => callee_color != color || value == callee_value,
_ => true
}
}).map(|(i, _)| i).collect()
},
_ => (0..self.hand.len()).collect()
},
};
if let Some(fool_index) = and_fool {
compatibles.push(fool_index);
}
Ok(compatibles)
}
}
#[test]
fn player_tests() {
use std::f64::EPSILON;
let looser = Player {
name: "Player looser".to_string(),
contract: Some(Contract::Petite),
mode: Mode::Four,
..Player::default()
};
println!("looser: {}", &looser);
assert!(looser.points() == 0.0);
assert!(looser.count_oudlers() == 0);
println!("looser points: {}", looser.contract_points().unwrap());
assert!((looser.contract_points().unwrap() - -81.0).abs() < EPSILON);
let mut winner = Player {
name: "Player looser".to_string(),
contract: Some(Contract::GardeContre),
owned: Deck::build_deck(),
mode: Mode::Five,
random: true,
..Player::default()
};
winner.callee = Some(winner.call().unwrap());
let turn = Turn::default();
println!("{}", &winner.hand);
let choices = &winner.choices(&turn).unwrap();
println!("Choices :");
for &i in choices {
println!("\t{0: <2} : {1}", &i, &winner.hand.0[i]);
}
assert!((winner.points() - 91.0).abs() < EPSILON);
assert!(winner.count_oudlers() == 3);
println!("winner points: {}", winner.contract_points().unwrap());
assert!((winner.contract_points().unwrap() - 480.0).abs() < EPSILON);
let mut handle_owner = Player {
name: "Player looser".to_string(),
contract: Some(Contract::GardeContre),
callee: Some(Card::Color(Color::Club, ColorValue::King)),
mode: Mode::Five,
random: true,
..Player::default()
};
handle_owner.announce_handle();
}
use std::fmt;
use std::str::FromStr;
use crate::errors::TarotErrorKind;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, EnumIter)]
pub enum Mode {
Three = 3,
Four = 4,
Five = 5
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Three => write!(f, "{} players, 1 vs 2", Mode::Three as usize),
Self::Four => write!(f, "{} players, 1 vs 3", Mode::Four as usize),
Self::Five => write!(f, "{} players, 2 vs 3", Mode::Five as usize),
}
}
}
impl FromStr for Mode {
type Err = TarotErrorKind;
fn from_str(s: &str) -> Result<Mode, TarotErrorKind> {
match s {
"3" => Ok(Self::Three),
"4" => Ok(Self::Four),
"5" => Ok(Self::Five),
_ => Err(TarotErrorKind::InvalidPlayers),
}
}
}
impl Mode {
pub fn dog_size(self) -> usize {
match self {
Self::Five => 3,
_ => 6
}
}
pub fn cards_per_turn(self) -> usize {
match self {
Self::Three=> 4,
_ => 3
}
}
pub fn cards_per_player(self) -> usize {
match self {
Self::Three => 24,
Self::Four => 18,
Self::Five => 15,
}
}
}
impl Default for Mode {
fn default() -> Mode { Mode::Four }
}
#[test]
fn mode_tests() {
let mode = Mode::default();
println!("mode: {}", &mode);
let three = Mode::from_str("3");
assert!(three == Ok(Mode::Three));
let four = Mode::from_str("4");
assert!(four == Ok(Mode::Four));
let five = Mode::from_str("5");
assert!(five == Ok(Mode::Five));
}
extern crate strum;
extern crate rand;
extern crate num_cpus;
extern crate itertools;
#[macro_use] extern crate log;
#[macro_use] extern crate strum_macros;
#[macro_use] extern crate failure;
#[macro_use] extern crate lazy_static;
pub mod helpers;
pub mod traits;
pub mod errors;
pub mod color;
pub mod trump;
pub mod card;
pub mod deck;
pub mod player;
pub mod role;
pub mod team;
pub mod turn;
pub mod handle;
pub mod mode;
pub mod contract;
pub mod game;
use std::error;
use std::thread;
use structopt::StructOpt;
use strum::IntoEnumIterator;
use crate::mode::Mode;
lazy_static! {
static ref DEFAULT_CONCURRENCY: String = num_cpus::get().to_string();
}
#[derive(StructOpt, Debug)]
#[structopt(name = "RTarot", about = "Tarot simulation", version = "1.0", author = "Adrien P. <crunchengine@gmail.com>")]
struct Opt {
/// Players mode
#[structopt(default_value = "4", possible_values = &["3", "4", "5"])]
players: mode::Mode,
/// Random playing mode
#[structopt(short = "r", long = "random")]
random: bool,
/// Auto playing mode when possible
#[structopt(short = "a", long = "auto")]
auto: bool,
/// Test mode
#[structopt(short = "t", long = "test")]
test: bool,
/// Concurrency in test mode, default is number of cpu on this machine
#[structopt(short, default_value = DEFAULT_CONCURRENCY.as_str())]
concurrency: usize
}
fn main() -> Result<(), Box<dyn error::Error>> {
let opt = Opt::from_args();
if opt.test {
let mut children = vec![];
for _ in 0..opt.concurrency {
children.push(thread::spawn(move || {
#[allow(clippy::infinite_iter)]
Mode::iter().cycle().for_each(helpers::test_game);
}));
}
for child in children {
let _ = child.join();
}
} else {
let mut game = game::Game::new(opt.players, opt.random, opt.auto);
loop {
game.distribute()?;
game.bidding()?;
if game.passed() {
println!("Everyone passed !");
continue
}
game.discard()?;
while !game.finished() {
game.play()?;
}
game.count_points()?;
break
}
println!("GAME ENDED");
println!("{}", &game);
}
Ok(())
}
use crate::mode::Mode;
use std::io;
#[allow(clippy::redundant_closure)]
pub fn read_index() -> usize {
let mut input = String::new();
loop {
if io::stdin().read_line(&mut input).is_ok() {
return input.trim().parse::<usize>().unwrap();
}
}
}
pub fn wait_input() {
use std::io::prelude::*;
let mut stdin = io::stdin();
let _ = stdin.read(&mut [0u8]).unwrap();
}
pub fn test_game(mode: Mode) {
use crate::game::Game;
use crate::errors::TarotErrorKind;
loop {
let mut game = Game::new(mode, true, true);
if let Err(fail) = game.distribute() {
if let Some(cause) = fail.find_root_cause().downcast_ref::<TarotErrorKind>() {
if cause == &TarotErrorKind::PetitSec {
continue
} else {
panic!("Error is not PetitSec")
}
}
}
assert!(game.bidding().is_ok());
if game.passed() {
continue
}
assert!(game.discard().is_ok());
while !game.finished() {
assert!(game.play().is_ok());
}
assert!(game.count_points().is_ok());
break
}
}
pub fn binomial(mut n: usize, mut k: usize) -> usize {
if k > n {
return 0;
}
if k > (n / 2) {
k = n - k;
}
let mut result = 1;
for d in 1..=k {
result *= n;
n -= 1;
result /= d;
}
result
}
#[test]
fn helpers_tests() {
assert_eq!(binomial(24, 6), 134_596);
}
use std::fmt;
use crate::mode::Mode;
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum Handle {
Refused = 0,
Simple = 20,
Double = 30,
Triple = 40,
}
impl fmt::Display for Handle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Refused => write!(f, "refuse handle"),
Self::Simple => write!(f, "simple handle"),
Self::Double => write!(f, "double handle"),
Self::Triple => write!(f, "triple handle"),
}
}
}
impl Handle {
pub fn new(count: usize, mode: Mode) -> Option<Handle> {
match mode {
Mode::Three => {
match count {
0 ..= 12 => None,
13 ..= 14 => Some(Self::Simple),
15 ..= 17 => Some(Self::Double),
_ => Some(Self::Triple)
}
},
Mode::Four => {
match count {
0 ..= 9 => None,
10 ..= 12 => Some(Self::Simple),
13 ..= 14 => Some(Self::Double),
_ => Some(Self::Triple)
}
},
Mode::Five => {
match count {
0 ..= 7 => None,
8 ..= 9 => Some(Self::Simple),
10 ..= 12 => Some(Self::Double),
_ => Some(Self::Triple)
}
}
}
}
pub fn limit(&self, mode: Mode) -> usize {
match self {
Self::Refused => 0 as usize,
Self::Simple => {
match mode {
Mode::Three => 13 as usize,
Mode::Four => 10 as usize,
Mode::Five => 8 as usize
}
},
Self::Double => {
match mode {
Mode::Three => 15 as usize,
Mode::Four => 13 as usize,
Mode::Five => 10 as usize
}
}
Self::Triple => {
match mode {
Mode::Three => 18 as usize,
Mode::Four => 15 as usize,
Mode::Five => 13 as usize
}
}
}
}
}
use failure::Error;
use std::fmt;
use rand::{Rng, thread_rng};
use rand::seq::SliceRandom;
use strum::IntoEnumIterator;
use crate::deck::*;
use crate::mode::*;
use crate::contract::*;
use crate::player::*;
use crate::errors::*;
use crate::turn::*;
use crate::card::*;
use crate::role::*;
use crate::team::*;
use crate::helpers::*;
#[derive(Debug)]
pub struct Game {
dog: Deck,
deck: Deck,
mode: Mode,
players: Vec<Player>,
random: bool,
auto: bool,
petit_au_bout: Option<Team>,
defense_cards: usize,
attack_cards: usize,
}
impl fmt::Display for Game {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Game with dog {} and players : ", self.dog)?;
for p in &self.players {
writeln!(f, "\t{}", p)?;
}
Ok(())
}
}
impl<'a> Default for Game {
fn default() -> Game
{
Game {
random: false,
auto: false,
petit_au_bout: None,
defense_cards: 0,
attack_cards: 0,
dog: Deck::default(),
deck: Deck::build_deck(),
players: vec![Player::new(Mode::default(), false); Mode::default() as usize],
mode: Mode::default(),
}
}
}
impl Game
{
pub fn new(mode: Mode, random: bool, auto: bool) -> Game {
let mut game = Game {
random,
auto,
players: vec![Player::new(mode, random); mode as usize],
mode,
..Game::default()
};
for (i, p) in game.players.iter_mut().enumerate() {
if let Ok(name) = default_name(i) {
p.name = name;
}
p.mode = mode;
}
game
}
fn is_consistent(&self) {
let sum = self.players.iter().map(|ref p| p.total).sum::<f64>();
debug!("Current points sum : {}", sum);
assert!(sum == 0.0)
}
pub fn distribute(&mut self) -> Result<(), Error> {
let mut decks : Vec<Deck> = Vec::new();
let dog = self.dog.give_all();
decks.push(dog);
for p in self.players.iter_mut() {
let hand = p.hand.give_all();
decks.push(hand);
let deck = p.owned.give_all();
decks.push(deck);
p.prepare();
}
let mut rng = thread_rng();
decks.shuffle(&mut rng);
for mut d in decks {
self.deck.append(&mut d.give_all());
}
self.petit_au_bout = None;
self.defense_cards = 0;
self.attack_cards = 0;
self.dog = self.deck.give(self.mode.dog_size());
self.dog.sort();
for p in self.players.iter_mut() {
p.hand.append(&mut self.deck.give(self.mode.cards_per_player()))
}
for p in self.players.iter_mut() {
if p.hand.petit_sec() {
// RULE: PetitSec cancel the game
return Err(TarotErrorKind::PetitSec.into())
}
p.hand.sort();
}
Ok(())
}
pub fn bidding(&mut self) -> Result<(), Error> {
let mut contracts: Vec<Contract> = Contract::iter().collect();
let mut slam_index : Option<usize> = None;
for (i, p) in self.players.iter_mut().enumerate() {
if self.auto && contracts.len() == 1 {
p.contract = Some(Contract::Pass);
println!("Auto pass");
continue
}
p.contract = if self.random {
Some(contracts[rand::thread_rng().gen_range(0, contracts.len())])
} else {
loop {
println!("{} must play : {}", &p, &p.hand);
println!("Choose a contract, possibilities :");
for (i, c) in contracts.iter().enumerate() {
println!("\t{} : press {}", c, i);
}
let contract_index = read_index();
if contract_index < contracts.len() {
break Some(contracts[contract_index])
} else {
println!("Error, please retry");
}
}
};
contracts = match p.contract {
Some(contract) => {
println!("Player {} has chosen contract {}", p.name, contract);
contracts.into_iter().filter(|other_contract| other_contract == &Contract::Pass || *other_contract as usize > contract as usize).collect()
},
_ => {
println!("A contract must be available for everyone!");
return Err(TarotErrorKind::InvalidCase.into())
}
};
if p.contract != Some(Contract::Pass) && p.announce_slam() {
slam_index = Some(i);
}
}
// RULE: player who slammed must start
if let Some(slammer) = slam_index {
self.players.rotate_left(slammer);
}
Ok(())
}
pub fn passed(&self) -> bool {
self.players.iter().all(|p| p.contract == Some(Contract::Pass))
}
pub fn finished(&self) -> bool {
self.players.iter().all(|p| p.hand.is_empty())
}
pub fn discard (&mut self) -> Result<(), Error> {
if self.passed() {
return Err(TarotErrorKind::NoTaker.into());
}
let mut callee: Option<Card> = None;
let contract: Option<Contract> = if let Some(taker) = self.players.iter_mut().max_by(|c1, c2| c1.contract.unwrap().cmp(&c2.contract.unwrap())) {
println!("{} has taken", taker);
if let Mode::Five = self.mode {
callee = Some(taker.call()?);
}
taker.contract
} else {
return Err(TarotErrorKind::NoTaker.into());
};
for p in &mut self.players {
println!("Player before : {}", p);
p.callee = callee;
p.team = Some(Team::Defense);
p.role = Some(Role::Defenser);
if p.contract == contract {
p.team = Some(Team::Attack);
p.role = Some(Role::Taker);
} else if let Some(ref card) = callee {
if p.hand.0.contains(&&card) {
p.team = Some(Team::Attack);
p.role = Some(Role::Ally);
}
}
p.contract = contract;
println!("Player after : {}", p);
}
let team_partitioner = |p: &'_ &mut Player| -> bool {
match &p.team {
Some(team) => team == &Team::Attack,
_ => false
}
};
let (takers, others): (Vec<_>, Vec<_>) = self.players.iter_mut().partition(team_partitioner);
for taker in takers {
if taker.role != Some(Role::Taker) {
continue
}
match taker.contract {
Some(Contract::Pass) => continue,
Some(Contract::GardeSans) => {
taker.owned.append(&mut self.dog.give_all());
return Ok(())
}
Some(Contract::GardeContre) => {
for o in others {
o.owned.append(&mut self.dog.give_all());
}
return Ok(())
},
_ => {
let discard = self.dog.len();
println!("In the dog, there was : {}", &self.dog);
taker.hand.append(&mut self.dog.give_all());
taker.hand.sort();
taker.discard(discard);
},
}
}
Ok(())
}
pub fn play (&mut self) -> Result<(), Error> {
let mut turn = Turn::default();
let mut master_player: usize = 0;
for (i, p) in self.players.iter_mut().enumerate() {
if p.is_first_turn() {
p.announce_handle();
}
println!("{}", &turn);
println!("Hand of {} : {}", &p, &p.hand);
println!("Choices :");
let choices = &p.choices(&turn)?;
if choices.is_empty() {
println!("No choices available, invalid case.");
return Err(TarotErrorKind::InvalidCase.into())
}
for &i in choices {
println!("\t{0: <2} : {1}", &i, p.hand.0[i]);
}
if let Some(master) = turn.master_card() {
println!("{} must play color {}", &p.name, &master)
} else {
println!("{} is first to play:", &p.name)
}
let index = if self.auto && choices.len() == 1 {
choices[0]
} else if self.random {
choices[rand::thread_rng().gen_range(0, choices.len())]
} else {
loop {
let choice_index = read_index();
if choices.contains(&choice_index) {
break choice_index
} else {
println!("Error, please retry")
}
}
};
let card = p.give_one(index);
if card.is_fool() {
if !p.last_turn() {
// RULE: the fool is always preserved to his owner
p.owned.push(card);
// we must mark as the fool was played
turn.fool_played = true;
} else {
// RULE: exception in the last turn, the fool is in game and can be lost
turn.put(card);
match p.team {
Some(Team::Attack) => {
if self.attack_cards == MAX_CARDS - self.mode.dog_size() {
turn.master_index = Some(turn.len()-1);
master_player = i;
}
},
Some(Team::Defense) => {
if self.defense_cards == MAX_CARDS - self.mode.dog_size() {
turn.master_index = Some(turn.len()-1);
master_player = i;
}
},
_ => {
return Err(TarotErrorKind::NoTeam.into())
}
}
}
} else {
turn.put(card);
if let Some(master) = turn.master_card() {
if master.master(card) {
println!("Master card is {}, so player {} stays master", master, master_player);
} else {
println!("Master card is {}, so player {} becomes master", card, i);
master_player = i;
turn.master_index = Some(turn.len()-1);
}
} else {
println!("First card is {}, so player {} becomes master", card, i);
master_player = i;
turn.master_index = Some(turn.len()-1);
}
}
}
let mut cards = turn.take();
println!("Winner is player {}", self.players[master_player]);
// RULE: petit au bout works for last turn, or before last turn if a slam is occuring
if cards.has_petit() &&
(self.players[master_player].last_turn() ||
(self.players[master_player].before_last_turn() &&
((self.attack_cards == MAX_CARDS - self.mode.dog_size() - self.mode as usize ) || (self.defense_cards == MAX_CARDS - self.mode.dog_size() - self.mode as usize)))) {
println!("{} has Petit in last turn (Petit au bout) : +10 points", self.players[master_player]);
self.petit_au_bout = self.players[master_player].team.clone();
}
match self.players[master_player].team {
Some(Team::Attack) => self.attack_cards += cards.len(),
Some(Team::Defense) => self.defense_cards += cards.len(),
_ => return Err(TarotErrorKind::NoTeam.into())
}
self.players[master_player].owned.append(&mut cards);
self.players.rotate_left(master_player);
Ok(())
}
pub fn count_points(&mut self) -> Result<(), Error> {
if self.passed() {
return Err(TarotErrorKind::NoTaker.into());
}
let mut taker_index : Option<usize> = None;
let mut ally_index : Option<usize> = None;
let mut defense : Vec<usize> = Vec::new();
let mut owning_card_player_index : Option<usize> = None;
let mut missing_card_player_index : Option<usize> = None;
let mut handle_bonus = 0.0;
for (i, p) in self.players.iter().enumerate() {
if p.owe_card() {
owning_card_player_index = Some(i);
}
if p.missing_card() {
missing_card_player_index = Some(i);
}
if let Some(handle) = &p.handle {
handle_bonus += f64::from(handle.clone() as u8);
println!("Handle bonus: {}", handle_bonus);
}
match p.role {
Some(Role::Taker) => {
assert!(taker_index.is_none());
taker_index = Some(i)
}
Some(Role::Ally) => {
assert!(ally_index.is_none());
ally_index = Some(i)
}
Some(Role::Defenser) => {
defense.push(i)
}
None => return Err(TarotErrorKind::InvalidCase.into()),
}
}
match self.mode {
Mode::Three => assert!(defense.len() == 2),
Mode::Four => assert!(defense.len() == 3),
Mode::Five => {
if ally_index.is_some() {
assert!(defense.len() == 3)
} else {
assert!(defense.len() == 4)
}
}
};
// give a low card if someone one a card to someone else
if let Some(owning_index) = owning_card_player_index {
let low_card = self.players[owning_index].give_low();
if let Some(low) = low_card {
if let Some(missing_index) = missing_card_player_index {
self.players[missing_index].owned.push(low);
}
}
}
if let Some(ally_index) = ally_index {
let mut ally_cards = self.players[ally_index].owned.give_all();
if let Some(taker_index) = taker_index {
self.players[taker_index].owned.append(&mut ally_cards)
} else {
println!("Cant merge cards of ally if no taker");
return Err(TarotErrorKind::NoTaker.into());
}
}
if let Some(taker_index) = taker_index {
let slam_bonus = self.players[taker_index].slam_bonus();
println!("Taker slam bonus: {}", &slam_bonus);
let contract_points = self.players[taker_index].contract_points()?;
println!("Taker contract points: {}", &contract_points);
let petit_au_bout_bonus = if let Some(contract) = self.players[taker_index].contract {
match self.petit_au_bout {
Some(Team::Defense) => {
println!("Petit au bout for defense: {}", -10.0 * f64::from(contract as u8));
-10.0 * f64::from(contract as u8)
},
Some(Team::Attack) => {
println!("Petit au bout for attack: {}", 10.0 * f64::from(contract as u8));
10.0 * f64::from(contract as u8)
},
None => 0.0
}
} else {
return Err(TarotErrorKind::NoContract.into())
};
let ratio = match self.mode {
Mode::Three => 2.0,
Mode::Four => 3.0,
Mode::Five => if ally_index.is_none() { 4.0 } else { 2.0 },
};
if contract_points < 0.0 {
handle_bonus *= -1.0;
}
println!("Attack handle bonus: {}", &handle_bonus);
let points = contract_points + petit_au_bout_bonus + handle_bonus + slam_bonus;
println!("Taker points: {}", &points);
self.players[taker_index].total = ratio * points;
println!("Taker total points: {}", &self.players[taker_index].total);
if let Some(ally_index) = ally_index {
self.players[ally_index].total = points;
println!("Ally total points: {}", &self.players[ally_index].total);
}
println!("Defense indexes : {:?}", defense);
for defenser_index in defense {
self.players[defenser_index].total = -1.0 * points;
println!("Defenser {} total points: {}", defenser_index, &self.players[defenser_index].total);
}
//if handle_bonus != 0.0 && petit_au_bout_bonus != 0.0 && slam_bonus != 0.0 && ratio == 4.0 {
// helpers::wait_input();
//}
} else {
println!("Cant count points if no taker");
return Err(TarotErrorKind::NoTaker.into());
}
self.is_consistent();
Ok(())
}
}
#[test]
fn game_tests() {
use crate::mode::*;
test_game(Mode::Three);
test_game(Mode::Four);
test_game(Mode::Five);
}
use failure::Context;
#[derive(Debug)]
pub struct TarotError {
inner: Context<TarotErrorKind>,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum TarotErrorKind {
#[fail(display = "A deck contains only one outsider: the petit.")]
PetitSec,
#[fail(display = "Card is invalid")]
InvalidCard,
#[fail(display = "Invalid number of players")]
InvalidPlayers,
#[fail(display = "No contract")]
NoContract,
#[fail(display = "Invalid mode")]
InvalidMode,
#[fail(display = "Invalid contract")]
InvalidContract,
#[fail(display = "Invalid case")]
InvalidCase,
#[fail(display = "Invalid color")]
InvalidColor,
#[fail(display = "No taker or auctions not finished")]
NoTaker,
#[fail(display = "A player shoud belongs to a team")]
NoTeam,
}
use std::fmt;
use std::f64::EPSILON;
use strum::IntoEnumIterator;
use itertools::Itertools;
use rand::thread_rng;
use rand::seq::SliceRandom;
use failure::Error;
use crate::card::*;
use crate::color::*;
use crate::traits::*;
use crate::trump::*;
use crate::errors::TarotErrorKind;
#[derive(Default, Clone, Debug)]
pub struct Deck (pub Vec<Card>);
pub const MAX_CARDS : usize = 78;
const MAX_POINTS : f64 = 91.0;
const MAX_POINTS_WITHOUT_FOOL : f64 = 87.0;
impl<'a> fmt::Display for Deck {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut last_color : Option<&Color> = None;
let (trumps, colors): (Vec<_>, Vec<_>) = self.0.iter().partition(|c| c.is_trump());
let trumps_value : Vec<usize> = trumps.iter().filter_map(|t| match t {Card::Trump(v) => Some(*v as usize), _ => None } ).collect();
if !trumps.is_empty() {
write!(f, "\n\t{} : {}", TRUMP_COLOR, trumps_value.iter().join(" "))?;
}
for colored in colors.iter() {
if let Card::Color(c, cv) = colored {
if last_color == Some(&c) {
write!(f, "{} ", cv)?
} else {
last_color = Some(&c);
match last_color {
None => {
write!(f, "\t{} : {} ", c, cv)?
},
_ => write!(f, "\n\t{} : {} ", c, cv)?,
}
}
}
}
Ok(())
}
}
impl Points for Deck {
fn points(&self) -> f64 {
// RULE: if a slam is occuring and player has only fool or everyting except fool, fool = 4 points
if self.0.len() == MAX_CARDS - 1 && !self.has_fool() {
MAX_POINTS_WITHOUT_FOOL
} else if self.len() == 1 && self.has_fool() {
4.0
} else {
self.0.iter().map(Points::points).sum()
}
}
}
impl Deck {
pub fn build_deck() -> Deck {
let mut d : Vec<Card> = TrumpValue::iter().map(Card::Trump).
chain(Color::iter().cartesian_product(ColorValue::iter()).map(|(c, cv)| Card::Color(c, cv))).
collect();
let mut rng = thread_rng();
d.shuffle(&mut rng);
Deck(d)
}
pub fn trumps(&self) -> Vec<&Card> {
self.0.iter().filter(|&card| card.is_trump()).collect()
}
pub fn has_fool(&self) -> bool {
self.0.contains(&Card::Trump(TrumpValue::Fool))
}
pub fn has_petit(&self) -> bool {
self.0.contains(&Card::Trump(TrumpValue::Petit))
}
pub fn is_chelem(&self) -> bool {
// RULE: deck is a chelem if all cards are there or fool is missing
self.points() == MAX_POINTS || self.points() == MAX_POINTS_WITHOUT_FOOL
}
pub fn points_for_oudlers(&self) -> Result<f64, Error> {
match self.count_oudlers() {
0 => Ok(56.0),
1 => Ok(51.0),
2 => Ok(41.0),
3 => Ok(36.0),
_ => Err(TarotErrorKind::InvalidCase.into()),
}
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn petit_sec(&self) -> bool {
self.0.iter().fold(0, |acc, c| match &c { Card::Trump(c) => {acc + *c as usize}, _ => acc}) == 1
}
pub fn discardables(&self, discard: usize) -> Vec<usize> {
let choices : Vec<usize> = self.0.iter().enumerate().filter(|(_, card)| card.discardable()).map(|(i, _)| i).collect();
if choices.len() < discard {
self.0.iter().enumerate().filter(|(_, card)| card.discardable_forced()).map(|(i, _)| i).collect()
} else {
choices
}
}
pub fn count_trumps(&self) -> usize {
self.0.iter().filter(|card| card.is_trump()).count()
}
pub fn count_oudlers(&self) -> usize {
self.0.iter().filter(|card| card.is_oudler()).count()
}
pub fn count_tete(&self, cv: ColorValue) -> usize {
self.0.iter().filter(|card| match card { Card::Color(_, v) => v == &cv, _ => false }).count()
}
pub fn misere_tete(&self) -> bool {
!self.0.iter().any(|card| match card { Card::Color(_, v) => v.points() - 0.5 < EPSILON, _ => false })
}
pub fn give(&mut self, size: usize) -> Deck {
Deck(self.0.drain(0..size).collect())
}
pub fn give_all(&mut self) -> Deck {
Deck(self.0.drain(..).collect())
}
pub fn give_low(&mut self) -> Option<Card> {
let low_index = &self.0.iter().enumerate().filter_map(|(i, c)| if c.points() - 0.5 < EPSILON { Some(i) } else { None }).next();
if let Some(index) = low_index {
Some(self.0.remove(index.to_owned()))
} else {
None
}
}
pub fn append(&mut self, deck: &mut Deck) {
self.0.append(&mut deck.0);
}
pub fn push(&mut self, card: Card){
self.0.push(card);
}
pub fn sort(&mut self) {
self.0.sort();
}
}
#[test]
fn deck_tests() {
let stack = Deck::build_deck();
assert!(stack.len() == MAX_CARDS);
assert!(stack.points() == MAX_POINTS);
let empty = Deck::default();
assert!(empty.is_empty());
}
use std::fmt;
use std::str::FromStr;
use crate::errors::TarotErrorKind;
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, EnumIter)]
pub enum Contract {
Pass = 0,
Petite = 1,
Garde = 2,
GardeSans = 4,
GardeContre = 6,
}
impl fmt::Display for Contract {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Pass => write!(f, "Passe. (x0)"),
Self::Petite => write!(f, "Petite (x1)"),
Self::Garde => write!(f, "Garde (x2)"),
Self::GardeSans => write!(f, "Garde Sans (x4)"),
Self::GardeContre => write!(f, "Garde Contre (x6)"),
}
}
}
impl FromStr for Contract {
type Err = TarotErrorKind;
fn from_str(s: &str) -> Result<Contract, TarotErrorKind> {
match s {
"0" => Ok(Self::Pass),
"1" => Ok(Self::Petite),
"2" => Ok(Self::Garde),
"4" => Ok(Self::GardeSans),
"6" => Ok(Self::GardeContre),
_ => Err(TarotErrorKind::InvalidContract),
}
}
}
#[test]
fn card_contracts() {
let pass = Contract::from_str("0");
assert!(pass == Ok(Contract::Pass) );
let petite = Contract::from_str("1");
assert!(petite == Ok(Contract::Petite) );
let garde = Contract::from_str("2");
assert!(garde == Ok(Contract::Garde) );
let garde_sans = Contract::from_str("4");
assert!(garde_sans == Ok(Contract::GardeSans) );
let garde_contre = Contract::from_str("6");
assert!(garde_contre == Ok(Contract::GardeContre) );
}
use std::fmt;
use std::str::FromStr;
use crate::errors::TarotErrorKind;
use crate::traits::*;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, EnumIter)]
pub enum Color {
Heart,
Spade,
Diamond,
Club,
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Spade => write!(f, "♠"),
Self::Diamond => write!(f, "♦"),
Self::Club => write!(f, "♣"),
Self::Heart => write!(f, "♥"),
}
}
}
impl FromStr for Color {
type Err = TarotErrorKind;
fn from_str(s: &str) -> Result<Color, TarotErrorKind> {
match s {
"1" => Ok(Self::Heart),
"2" => Ok(Self::Spade),
"3" => Ok(Self::Diamond),
"4" => Ok(Self::Club),
_ => Err(TarotErrorKind::InvalidColor),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, EnumIter)]
pub enum ColorValue {
_1 = 1,
_2 = 2,
_3 = 3,
_4 = 4,
_5 = 5,
_6 = 6,
_7 = 7,
_8 = 8,
_9 = 9,
_10 = 10,
Jack = 11,
Knight = 12,
Queen = 13,
King = 14,
}
impl fmt::Display for ColorValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Jack => write!(f, "V"),
Self::Knight => write!(f, "C"),
Self::Queen => write!(f, "Q"),
Self::King => write!(f, "K"),
_ => write!(f, "{}", *self as usize),
}
}
}
impl Discardable for ColorValue {
fn discardable(&self) -> bool {
// RULE: cant discard kings
self != &Self::King
}
fn discardable_forced(&self) -> bool {
// RULE: cant discard kings
self != &Self::King
}
}
impl Points for ColorValue {
fn points(&self) -> f64 {
match self {
Self::Jack => 1.5,
Self::Knight => 2.5,
Self::Queen => 3.5,
Self::King => 4.5,
_ => 0.5
}
}
}
use std::fmt;
use crate::traits::*;
use crate::color::*;
use crate::trump::*;
#[derive(Copy, Ord, Clone, Debug, Eq, PartialEq, PartialOrd)]
pub enum Card {
Trump(TrumpValue),
Color(Color, ColorValue)
}
impl fmt::Display for Card {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Trump(v) => write!(f, "{}", v),
Self::Color(c, v) => write!(f, "{} : {}", c, v)
}
}
}
impl Points for Card {
fn points(&self) -> f64 {
match self {
Self::Trump(v) => v.points(),
Self::Color(_, v) => v.points()
}
}
}
impl Power for Card {
fn power(&self) -> usize {
match self {
Self::Trump(v) => *v as usize + ColorValue::King as usize,
Self::Color(_, v) => *v as usize,
}
}
}
impl Discardable for Card {
fn discardable(&self) -> bool {
match self {
Self::Trump(t) => t.discardable(),
Self::Color(_, v) => v.discardable()
}
}
fn discardable_forced(&self) -> bool {
match self {
Self::Trump(t) => t.discardable_forced(),
Self::Color(_, v) => v.discardable_forced()
}
}
}
impl Card {
pub fn is_fool(self) -> bool {
match self {
Self::Trump(v) => v == TrumpValue::Fool,
_ => false
}
}
pub fn is_trump(self) -> bool {
match self {
Self::Trump(_) => true,
_ => false
}
}
pub fn is_oudler(self) -> bool {
match self {
Self::Trump(c) => c.is_oudler(),
_ => false
}
}
pub fn master(self, arg: Card) -> bool {
match (&self, &arg) {
(Self::Trump(c), Self::Color(_, _)) => c != &TrumpValue::Fool,
(Self::Color(_, _), Self::Trump(c)) => c == &TrumpValue::Fool,
(Self::Color(c1, v1), Self::Color(c2, v2)) => c1 != c2 || v1 > v2,
(Self::Trump(v1), Self::Trump(v2)) => v1 > v2,
}
}
}
#[test]
fn card_tests() {
use std::f64::EPSILON;
let trump_2 = Card::Trump(TrumpValue::_2);
let petit = Card::Trump(TrumpValue::Petit);
let fool = Card::Trump(TrumpValue::Fool);
let unassailable = Card::Trump(TrumpValue::_21);
let spade_1 = Card::Color(Color::Spade, ColorValue::_1);
let spade_2 = Card::Color(Color::Spade, ColorValue::_2);
let spade_3 = Card::Color(Color::Spade, ColorValue::_3);
let spade_10 = Card::Color(Color::Spade, ColorValue::_10);
let diamond_3 = Card::Color(Color::Diamond, ColorValue::_3);
assert!(!spade_3.master(spade_10));
assert!(!petit.master(trump_2));
assert!(petit.master(spade_1));
assert!(!spade_1.master(petit));
assert!(spade_2.master(spade_1));
assert!(diamond_3.master(spade_2));
assert!(diamond_3.master(fool));
assert!(!fool.master(spade_1));
assert!(!petit.discardable());
assert!(!fool.discardable());
assert!(!unassailable.discardable());
assert!(!petit.discardable_forced());
assert!(!fool.discardable_forced());
assert!(!unassailable.discardable_forced());
assert!(unassailable.points() - 4.5 < EPSILON);
}
cognitive-complexity-threshold = 40
French Tarot
Implemented with :
- rules : http://www.fftarot.fr/assets/documents/R-RO201206.pdf
- traductions : https://en.wikipedia.org/wiki/French_tarot
Usage :
- coverage :
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo tarpaulin -v
- test : cargo test -- --nocapture
- clippy : cargo clippy --all-targets --all-features -- -D warnings
Todo :
- type games : defense, attack, petit hunt, full assets
- add colors
- game managing
- flag on/off
- cut game
- random reunion
- duplicate
[package]
name = "rtarot"
default-run = "rtarot"
version = "0.1.0"
authors = ["Adrien Pensart <crunchengine@gmail.com>"]
edition = "2018"
[dependencies]
log = "0.4"
lazy_static = "1.4.0"
structopt = "0.3"
failure = "0.1.5"
rand = "0.7"
itertools = "0.8"
strum = "0.15"
strum_macros = "0.15"
num_cpus = "1.0"
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "addr2line"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gimli 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"addr2line 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)",
"miniz_oxide 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "getrandom"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "gimli"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "miniz_oxide"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "object"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ppv-lite86"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ppv-lite86 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rtarot"
version = "0.1.0"
dependencies = [
"failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
"strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt-derive 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "structopt-derive"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strum"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "strum_macros"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum addr2line 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
"checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
"checksum backtrace 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)" = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
"checksum either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
"checksum failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
"checksum failure_derive 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
"checksum getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
"checksum gimli 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151"
"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.78 (registry+https://github.com/rust-lang/crates.io-index)" = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
"checksum miniz_oxide 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9"
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
"checksum object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
"checksum ppv-lite86 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20"
"checksum proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
"checksum proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "a33f6461027d7f08a13715659b2948e1602c31a3756aeae9378bfe7518c72e82"
"checksum structopt-derive 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c92e775028122a4b3dd55d58f14fc5120289c69bee99df1d117ae30f84b225c9"
"checksum strum 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e5d1c33039533f051704951680f1adfd468fd37ac46816ded0d9ee068e60f05f"
"checksum strum_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47cd23f5c7dee395a00fa20135e2ec0fffcdfa151c56182966d7a3261343432e"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228"
"checksum synstructure 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"