Move card definition to new module
This commit is contained in:
parent
d18942feda
commit
c94876c54c
@ -2,62 +2,6 @@ pub use super::Game;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
pub fn copper() -> Card {
|
||||
Card {
|
||||
name: "Copper".into(),
|
||||
cost: 0,
|
||||
types: vec![CardType::Treasure(1)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn silver() -> Card {
|
||||
Card {
|
||||
name: "Silver".into(),
|
||||
cost: 3,
|
||||
types: vec![CardType::Treasure(2)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gold() -> Card {
|
||||
Card {
|
||||
name: "Gold".into(),
|
||||
cost: 6,
|
||||
types: vec![CardType::Treasure(3)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estate() -> Card {
|
||||
Card {
|
||||
name: "Estate".into(),
|
||||
cost: 2,
|
||||
types: vec![CardType::Victory(1)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duchy() -> Card {
|
||||
Card {
|
||||
name: "Duchy".into(),
|
||||
cost: 5,
|
||||
types: vec![CardType::Victory(3)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn province() -> Card {
|
||||
Card {
|
||||
name: "Province".into(),
|
||||
cost: 8,
|
||||
types: vec![CardType::Victory(6)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn curse() -> Card {
|
||||
Card {
|
||||
name: "Curse".into(),
|
||||
cost: 0,
|
||||
types: vec![CardType::Curse],
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! draw {
|
||||
($g:ident, $e:expr) => {
|
||||
$g.players[$g.active_player].draw($e)
|
||||
@ -93,6 +37,7 @@ where
|
||||
pub enum CardType {
|
||||
#[serde(serialize_with = "serialize_card_type")]
|
||||
Action(fn(&mut Game)),
|
||||
Attack,
|
||||
Curse,
|
||||
#[serde(serialize_with = "serialize_card_type")]
|
||||
Reaction(fn(&mut Game)),
|
||||
@ -102,7 +47,7 @@ pub enum CardType {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Card {
|
||||
pub name: String,
|
||||
pub name: &'static str,
|
||||
pub cost: u32,
|
||||
pub types: Vec<CardType>,
|
||||
}
|
480
src/main.rs
480
src/main.rs
@ -1,8 +1,9 @@
|
||||
#[macro_use]
|
||||
mod cards;
|
||||
mod card;
|
||||
mod sets;
|
||||
|
||||
use async_std::{prelude::*, sync::RwLock};
|
||||
use cards::*;
|
||||
use card::*;
|
||||
use itertools::Itertools;
|
||||
use rand::{seq::SliceRandom, thread_rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -157,16 +158,15 @@ impl Default for GameSetup {
|
||||
fn default() -> GameSetup {
|
||||
GameSetup {
|
||||
deck: vec![
|
||||
copper(),
|
||||
copper(),
|
||||
copper(),
|
||||
copper(),
|
||||
copper(),
|
||||
copper(),
|
||||
copper(),
|
||||
estate(),
|
||||
estate(),
|
||||
estate(),
|
||||
sets::base::copper(),
|
||||
sets::base::copper(),
|
||||
sets::base::copper(),
|
||||
sets::base::copper(),
|
||||
sets::base::copper(),
|
||||
sets::base::copper(),
|
||||
sets::base::estate(),
|
||||
sets::base::estate(),
|
||||
sets::base::estate(),
|
||||
],
|
||||
}
|
||||
}
|
||||
@ -176,6 +176,7 @@ impl Default for GameSetup {
|
||||
#[derive(Clone, Serialize)]
|
||||
enum ResolvingPlayer {
|
||||
ActivePlayer,
|
||||
AllNonActivePlayers,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -190,14 +191,8 @@ enum ResolveReply {
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum ResolveRequest {
|
||||
ChooseHandCardsToDiscard {
|
||||
player: ResolvingPlayer,
|
||||
filter: CardFilter,
|
||||
},
|
||||
GainCard {
|
||||
player: ResolvingPlayer,
|
||||
filter: CardFilter,
|
||||
},
|
||||
ChooseHandCardsToDiscard { filter: CardFilter },
|
||||
GainCard { filter: CardFilter },
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -207,7 +202,18 @@ enum Effect {
|
||||
OnCardPlayed(fn(&mut Game, &Card) -> bool),
|
||||
/// Effect that blocks further processing of the game state until
|
||||
/// some user input is received that allows to fully resolve the effect
|
||||
Resolving(String, ResolveRequest, ResolvingEffect),
|
||||
Resolving {
|
||||
card: String,
|
||||
player: ResolvingPlayer,
|
||||
request: ResolveRequest,
|
||||
effect: ResolvingEffectHandler,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EffectState {
|
||||
resolved: bool,
|
||||
players_responded: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
@ -218,7 +224,8 @@ enum CardFilter {
|
||||
Type(CardType),
|
||||
}
|
||||
|
||||
type ResolvingEffect = fn(&mut Game, &ResolveReply);
|
||||
type ResolvingEffectHandler =
|
||||
fn(&mut Game, &ResolveReply, usize, &ResolveRequest, &mut EffectState);
|
||||
|
||||
pub struct Game {
|
||||
effects: Vec<Effect>,
|
||||
@ -233,7 +240,13 @@ pub struct Game {
|
||||
|
||||
/// Any effect from a card that requires further input from players
|
||||
/// and blocks the game until fully resolved.
|
||||
resolving_effect: Option<(String, ResolveRequest, ResolvingEffect)>,
|
||||
resolving_effect: Option<(
|
||||
String,
|
||||
ResolveRequest,
|
||||
ResolvingEffectHandler,
|
||||
ResolvingPlayer,
|
||||
EffectState,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
@ -272,334 +285,23 @@ impl Game {
|
||||
};
|
||||
|
||||
self.supply = vec![
|
||||
(copper(), 60 - self.players.len() * 7),
|
||||
(silver(), 40),
|
||||
(gold(), 30),
|
||||
(estate(), victory_qty),
|
||||
(duchy(), victory_qty),
|
||||
(province(), victory_qty),
|
||||
(curse(), 10),
|
||||
(
|
||||
Card {
|
||||
name: "Cellar".into(),
|
||||
cost: 2,
|
||||
types: vec![CardType::Action(|game| {
|
||||
action!(game, 1);
|
||||
game.add_effect(Effect::Resolving(
|
||||
"Cellar".into(),
|
||||
ResolveRequest::ChooseHandCardsToDiscard {
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
filter: CardFilter::Any,
|
||||
},
|
||||
|game, message| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
let mut discarded = 0;
|
||||
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
||||
game.get_active_player().discard(*c);
|
||||
//game.players[game.active_player].discard(*c);
|
||||
discarded += 1;
|
||||
}
|
||||
|
||||
game.players[game.active_player].draw(discarded);
|
||||
game.resolving_effect = None;
|
||||
}
|
||||
},
|
||||
));
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Moat".into(),
|
||||
cost: 2,
|
||||
types: vec![
|
||||
CardType::Action(|game| {
|
||||
draw!(game, 2);
|
||||
}),
|
||||
CardType::Reaction(|_| {}),
|
||||
],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Village".into(),
|
||||
cost: 3,
|
||||
types: vec![CardType::Action(|game| {
|
||||
draw!(game, 1);
|
||||
action!(game, 2);
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Merchant".into(),
|
||||
cost: 3,
|
||||
types: vec![CardType::Action(|game| {
|
||||
draw!(game, 1);
|
||||
action!(game, 1);
|
||||
game.add_effect(Effect::OnCardPlayed(|game, card| {
|
||||
if card.name.as_str() == "Silver" {
|
||||
coin!(game, 1);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}));
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Workshop".into(),
|
||||
cost: 3,
|
||||
types: vec![CardType::Action(|game| {
|
||||
game.add_effect(Effect::Resolving(
|
||||
"Workshop".into(),
|
||||
ResolveRequest::GainCard {
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
filter: CardFilter::MaxCost(4),
|
||||
},
|
||||
|game, message| {
|
||||
if let ResolveReply::SupplyCardChosen { choice } = message {
|
||||
if let Some((card, count)) = game.supply.get(*choice) {
|
||||
if *count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if card.cost > 4 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
game.supply.get_mut(*choice).unwrap().1 =
|
||||
game.supply.get(*choice).unwrap().1 - 1;
|
||||
let card = game.supply[*choice].0.clone();
|
||||
|
||||
game.players[game.active_player]
|
||||
.discard_pile
|
||||
.push(card.clone());
|
||||
game.resolving_effect = None;
|
||||
}
|
||||
},
|
||||
));
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Smithy".into(),
|
||||
cost: 4,
|
||||
types: vec![CardType::Action(|game| draw!(game, 3))],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Remodel".into(),
|
||||
cost: 4,
|
||||
types: vec![CardType::Action(|game| {
|
||||
game.add_effect(Effect::Resolving(
|
||||
"Remodel".into(),
|
||||
ResolveRequest::ChooseHandCardsToDiscard {
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
filter: CardFilter::Any,
|
||||
},
|
||||
|game, message| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
if choice.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let card = game.players[game.active_player]
|
||||
.hand
|
||||
.remove(choice[0]);
|
||||
let cost = card.cost;
|
||||
|
||||
game.trash.push(card);
|
||||
|
||||
game.add_effect(Effect::Resolving(
|
||||
"Remodel".into(),
|
||||
ResolveRequest::GainCard {
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
filter: CardFilter::MaxCost(cost + 2),
|
||||
},
|
||||
|game, message| {
|
||||
if let ResolveReply::SupplyCardChosen {
|
||||
choice,
|
||||
} = message
|
||||
{
|
||||
if let Some((card, count)) =
|
||||
game.supply.get(*choice)
|
||||
{
|
||||
if *count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((
|
||||
_,
|
||||
ResolveRequest::GainCard {
|
||||
filter:
|
||||
CardFilter::MaxCost(cost),
|
||||
..
|
||||
},
|
||||
_,
|
||||
)) = game.resolving_effect
|
||||
{
|
||||
if card.cost > cost {
|
||||
return;
|
||||
}
|
||||
|
||||
game.supply
|
||||
.get_mut(*choice)
|
||||
.unwrap()
|
||||
.1 = game
|
||||
.supply
|
||||
.get(*choice)
|
||||
.unwrap()
|
||||
.1
|
||||
- 1;
|
||||
let card =
|
||||
game.supply[*choice].0.clone();
|
||||
|
||||
game.players[game.active_player]
|
||||
.discard_pile
|
||||
.push(card.clone());
|
||||
game.resolving_effect = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
));
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Militia".into(),
|
||||
cost: 4,
|
||||
types: vec![CardType::Action(|game| {
|
||||
coin!(game, 2);
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Market".into(),
|
||||
cost: 5,
|
||||
types: vec![CardType::Action(|game| {
|
||||
draw!(game, 1);
|
||||
action!(game, 1);
|
||||
buy!(game, 1);
|
||||
coin!(game, 1);
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(
|
||||
Card {
|
||||
name: "Mine".into(),
|
||||
cost: 5,
|
||||
types: vec![CardType::Action(|game| {
|
||||
game.add_effect(Effect::Resolving(
|
||||
"Mine".into(),
|
||||
ResolveRequest::ChooseHandCardsToDiscard {
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
filter: CardFilter::Type(CardType::Treasure(0)),
|
||||
},
|
||||
|game, message| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
if choice.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(card) =
|
||||
game.get_active_player().hand.get(choice[0])
|
||||
{
|
||||
if let None = card.treasure() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let card = game.players[game.active_player]
|
||||
.hand
|
||||
.remove(choice[0]);
|
||||
let cost = card.cost;
|
||||
|
||||
game.trash.push(card);
|
||||
|
||||
game.add_effect(Effect::Resolving(
|
||||
"Mine".into(),
|
||||
ResolveRequest::GainCard {
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
filter: CardFilter::MaxCost(cost + 3),
|
||||
},
|
||||
|game, message| {
|
||||
if let ResolveReply::SupplyCardChosen {
|
||||
choice,
|
||||
} = message
|
||||
{
|
||||
if let Some((card, count)) =
|
||||
game.supply.get(*choice)
|
||||
{
|
||||
if *count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((
|
||||
_,
|
||||
ResolveRequest::GainCard {
|
||||
filter:
|
||||
CardFilter::MaxCost(cost),
|
||||
..
|
||||
},
|
||||
_,
|
||||
)) = game.resolving_effect
|
||||
{
|
||||
if card.cost > cost {
|
||||
return;
|
||||
}
|
||||
|
||||
if let None = card.treasure() {
|
||||
return;
|
||||
}
|
||||
|
||||
game.supply
|
||||
.get_mut(*choice)
|
||||
.unwrap()
|
||||
.1 = game
|
||||
.supply
|
||||
.get(*choice)
|
||||
.unwrap()
|
||||
.1
|
||||
- 1;
|
||||
let card =
|
||||
game.supply[*choice].0.clone();
|
||||
|
||||
game.players[game.active_player]
|
||||
.discard_pile
|
||||
.push(card.clone());
|
||||
game.resolving_effect = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
},
|
||||
));
|
||||
})],
|
||||
},
|
||||
10,
|
||||
),
|
||||
(sets::base::copper(), 60 - self.players.len() * 7),
|
||||
(sets::base::silver(), 40),
|
||||
(sets::base::gold(), 30),
|
||||
(sets::base::estate(), victory_qty),
|
||||
(sets::base::duchy(), victory_qty),
|
||||
(sets::base::province(), victory_qty),
|
||||
(sets::base::curse(), 10),
|
||||
(sets::base::cellar(), 10),
|
||||
(sets::base::moat(), 10),
|
||||
(sets::base::village(), 10),
|
||||
(sets::base::merchant(), 10),
|
||||
(sets::base::workshop(), 10),
|
||||
(sets::base::simthy(), 10),
|
||||
(sets::base::remodel(), 10),
|
||||
(sets::base::militia(), 10),
|
||||
(sets::base::market(), 10),
|
||||
(sets::base::mine(), 10),
|
||||
];
|
||||
}
|
||||
|
||||
@ -722,7 +424,7 @@ impl Game {
|
||||
}
|
||||
}
|
||||
|
||||
Effect::Resolving(_, _, _) => {}
|
||||
Effect::Resolving { .. } => {}
|
||||
}
|
||||
|
||||
true
|
||||
@ -769,8 +471,14 @@ impl Game {
|
||||
self.effects.push(effect);
|
||||
}
|
||||
|
||||
Effect::Resolving(card_name, request, effect) => {
|
||||
self.resolving_effect = Some((card_name, request, effect));
|
||||
Effect::Resolving {
|
||||
card,
|
||||
request,
|
||||
effect,
|
||||
player,
|
||||
} => {
|
||||
let state = EffectState::default();
|
||||
self.resolving_effect = Some((card, request, effect, player, state));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -804,8 +512,12 @@ async fn broadcast_state(game: &Game) {
|
||||
name: html_escape::encode_text(&p.name).into(),
|
||||
draw_pile_count: p.draw_pile.len(),
|
||||
hand_count: p.hand.len(),
|
||||
discard_pile: p.discard_pile.last().map(|c| c.name.clone()),
|
||||
played_cards: p.played_cards.iter().map(|c| c.name.clone()).collect(),
|
||||
discard_pile: p.discard_pile.last().map(|c| c.name.clone().into()),
|
||||
played_cards: p
|
||||
.played_cards
|
||||
.iter()
|
||||
.map(|c| c.name.clone().into())
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
active_player: game.active_player,
|
||||
@ -855,9 +567,9 @@ async fn broadcast_state(game: &Game) {
|
||||
broadcast(&game, &sm).await;
|
||||
}
|
||||
|
||||
if let Some((card_name, request, _)) = &game.resolving_effect {
|
||||
if let Some((card_name, request, _, ref player, _)) = &game.resolving_effect {
|
||||
match request {
|
||||
ResolveRequest::ChooseHandCardsToDiscard { ref player, .. } => match player {
|
||||
ResolveRequest::ChooseHandCardsToDiscard { .. } => match player {
|
||||
ResolvingPlayer::ActivePlayer => {
|
||||
let p = game.players.get(game.active_player).unwrap();
|
||||
let sm = ServerMessage::ResolveRequest {
|
||||
@ -866,9 +578,22 @@ async fn broadcast_state(game: &Game) {
|
||||
};
|
||||
send_msg(&game, &p, &sm).await;
|
||||
}
|
||||
|
||||
ResolvingPlayer::AllNonActivePlayers => {
|
||||
for (id, player) in game.players.iter().enumerate() {
|
||||
let sm = ServerMessage::ResolveRequest {
|
||||
card: card_name.clone(),
|
||||
request: request.clone(),
|
||||
};
|
||||
|
||||
if id != game.active_player {
|
||||
send_msg(&game, &player, &sm).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ResolveRequest::GainCard { ref player, .. } => match player {
|
||||
ResolveRequest::GainCard { .. } => match player {
|
||||
ResolvingPlayer::ActivePlayer => {
|
||||
let p = game.players.get(game.active_player).unwrap();
|
||||
let sm = ServerMessage::ResolveRequest {
|
||||
@ -877,6 +602,8 @@ async fn broadcast_state(game: &Game) {
|
||||
};
|
||||
send_msg(&game, &p, &sm).await;
|
||||
}
|
||||
|
||||
_ => unimplemented!("GainCard for non active players?!"),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1177,16 +904,39 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
let mut games = req.state().games.write().await;
|
||||
let game = games.get_mut(&game_id).unwrap();
|
||||
|
||||
if player_number == game.active_player {
|
||||
match game.resolving_effect {
|
||||
Some((_, _, ref effect)) => {
|
||||
effect(game, &reply);
|
||||
if let Some((card, request, effect, player, mut state)) =
|
||||
game.resolving_effect.take()
|
||||
{
|
||||
match player {
|
||||
ResolvingPlayer::ActivePlayer => {
|
||||
if player_number == game.active_player {
|
||||
effect(
|
||||
game,
|
||||
&reply,
|
||||
game.active_player,
|
||||
&request,
|
||||
&mut state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ResolvingPlayer::AllNonActivePlayers => {
|
||||
if player_number != game.active_player {
|
||||
effect(game, &reply, player_number, &request, &mut state);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
broadcast_state(&game).await;
|
||||
match state.resolved {
|
||||
false => {
|
||||
game.resolving_effect =
|
||||
Some((card, request, effect, player, state));
|
||||
}
|
||||
true => {}
|
||||
}
|
||||
}
|
||||
|
||||
broadcast_state(&game).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
377
src/sets/base.rs
Normal file
377
src/sets/base.rs
Normal file
@ -0,0 +1,377 @@
|
||||
use crate::card::{Card, CardType};
|
||||
use crate::{CardFilter, Effect, ResolveReply, ResolveRequest, ResolvingPlayer};
|
||||
use itertools::Itertools;
|
||||
|
||||
pub fn copper() -> Card {
|
||||
Card {
|
||||
name: "Copper",
|
||||
cost: 0,
|
||||
types: vec![CardType::Treasure(1)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn silver() -> Card {
|
||||
Card {
|
||||
name: "Silver",
|
||||
cost: 3,
|
||||
types: vec![CardType::Treasure(2)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gold() -> Card {
|
||||
Card {
|
||||
name: "Gold",
|
||||
cost: 6,
|
||||
types: vec![CardType::Treasure(3)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn estate() -> Card {
|
||||
Card {
|
||||
name: "Estate",
|
||||
cost: 2,
|
||||
types: vec![CardType::Victory(1)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duchy() -> Card {
|
||||
Card {
|
||||
name: "Duchy",
|
||||
cost: 5,
|
||||
types: vec![CardType::Victory(3)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn province() -> Card {
|
||||
Card {
|
||||
name: "Province",
|
||||
cost: 8,
|
||||
types: vec![CardType::Victory(6)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn curse() -> Card {
|
||||
Card {
|
||||
name: "Curse",
|
||||
cost: 0,
|
||||
types: vec![CardType::Curse],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cellar() -> Card {
|
||||
Card {
|
||||
name: "Cellar",
|
||||
cost: 2,
|
||||
types: vec![CardType::Action(|game| {
|
||||
action!(game, 1);
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Cellar".into(),
|
||||
request: ResolveRequest::ChooseHandCardsToDiscard {
|
||||
filter: CardFilter::Any,
|
||||
},
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
effect: |game, message, _player, _request, state| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
let mut discarded = 0;
|
||||
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
||||
game.get_active_player().discard(*c);
|
||||
discarded += 1;
|
||||
}
|
||||
|
||||
game.players[game.active_player].draw(discarded);
|
||||
state.resolved = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn moat() -> Card {
|
||||
Card {
|
||||
name: "Moat",
|
||||
cost: 2,
|
||||
types: vec![
|
||||
CardType::Action(|game| {
|
||||
draw!(game, 2);
|
||||
}),
|
||||
CardType::Reaction(|_| {}),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn village() -> Card {
|
||||
Card {
|
||||
name: "Village",
|
||||
cost: 3,
|
||||
types: vec![CardType::Action(|game| {
|
||||
draw!(game, 1);
|
||||
action!(game, 2);
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn merchant() -> Card {
|
||||
Card {
|
||||
name: "Merchant",
|
||||
cost: 3,
|
||||
types: vec![CardType::Action(|game| {
|
||||
draw!(game, 1);
|
||||
action!(game, 1);
|
||||
game.add_effect(Effect::OnCardPlayed(|game, card| {
|
||||
if card.name == "Silver" {
|
||||
coin!(game, 1);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}));
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workshop() -> Card {
|
||||
Card {
|
||||
name: "Workshop",
|
||||
cost: 3,
|
||||
types: vec![CardType::Action(|game| {
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Workshop".into(),
|
||||
request: ResolveRequest::GainCard {
|
||||
filter: CardFilter::MaxCost(4),
|
||||
},
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
effect: |game, message, _player, _request, state| {
|
||||
if let ResolveReply::SupplyCardChosen { choice } = message {
|
||||
if let Some((card, count)) = game.supply.get(*choice) {
|
||||
if *count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if card.cost > 4 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
game.supply.get_mut(*choice).unwrap().1 =
|
||||
game.supply.get(*choice).unwrap().1 - 1;
|
||||
let card = game.supply[*choice].0.clone();
|
||||
|
||||
game.players[game.active_player]
|
||||
.discard_pile
|
||||
.push(card.clone());
|
||||
state.resolved = true;
|
||||
}
|
||||
},
|
||||
});
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simthy() -> Card {
|
||||
Card {
|
||||
name: "Smithy",
|
||||
cost: 4,
|
||||
types: vec![CardType::Action(|game| draw!(game, 3))],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remodel() -> Card {
|
||||
Card {
|
||||
name: "Remodel",
|
||||
cost: 4,
|
||||
types: vec![CardType::Action(|game| {
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Remodel".into(),
|
||||
request: ResolveRequest::ChooseHandCardsToDiscard {
|
||||
filter: CardFilter::Any,
|
||||
},
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
effect: |game, message, _player, _request, state| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
if choice.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let card = game.players[game.active_player].hand.remove(choice[0]);
|
||||
let cost = card.cost;
|
||||
|
||||
game.trash.push(card);
|
||||
|
||||
state.resolved = true;
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Remodel".into(),
|
||||
request: ResolveRequest::GainCard {
|
||||
filter: CardFilter::MaxCost(cost + 2),
|
||||
},
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
effect: |game, message, _player, request, state| {
|
||||
if let ResolveReply::SupplyCardChosen { choice } = message {
|
||||
if let Some((card, count)) = game.supply.get(*choice) {
|
||||
if *count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ResolveRequest::GainCard {
|
||||
filter: CardFilter::MaxCost(cost),
|
||||
..
|
||||
} = request
|
||||
{
|
||||
if card.cost > *cost {
|
||||
return;
|
||||
}
|
||||
|
||||
game.supply.get_mut(*choice).unwrap().1 =
|
||||
game.supply.get(*choice).unwrap().1 - 1;
|
||||
let card = game.supply[*choice].0.clone();
|
||||
|
||||
game.players[game.active_player]
|
||||
.discard_pile
|
||||
.push(card.clone());
|
||||
state.resolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn militia() -> Card {
|
||||
Card {
|
||||
name: "Militia",
|
||||
cost: 4,
|
||||
types: vec![
|
||||
CardType::Attack,
|
||||
CardType::Action(|game| {
|
||||
coin!(game, 2);
|
||||
|
||||
if game.players.len() == 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Militia".into(),
|
||||
request: ResolveRequest::ChooseHandCardsToDiscard {
|
||||
filter: CardFilter::Any,
|
||||
},
|
||||
player: ResolvingPlayer::AllNonActivePlayers,
|
||||
effect: |game, message, player, _request, state| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
if choice.len() == 0 {
|
||||
if !game.players[player].hand.iter().any(|c| c.name == "Moat") {
|
||||
return;
|
||||
}
|
||||
} else if game.players[player].hand.len() > 3
|
||||
&& choice.len() != game.players[player].hand.len() - 3
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
||||
game.players[player].discard(*c);
|
||||
}
|
||||
|
||||
state.players_responded.push(player);
|
||||
|
||||
if state.players_responded.len() == game.players.len() - 1 {
|
||||
state.resolved = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn market() -> Card {
|
||||
Card {
|
||||
name: "Market",
|
||||
cost: 5,
|
||||
types: vec![CardType::Action(|game| {
|
||||
draw!(game, 1);
|
||||
action!(game, 1);
|
||||
buy!(game, 1);
|
||||
coin!(game, 1);
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mine() -> Card {
|
||||
Card {
|
||||
name: "Mine",
|
||||
cost: 5,
|
||||
types: vec![CardType::Action(|game| {
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Mine".into(),
|
||||
request: ResolveRequest::ChooseHandCardsToDiscard {
|
||||
filter: CardFilter::Type(CardType::Treasure(0)),
|
||||
},
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
effect: |game, message, _player, _request, state| {
|
||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||
if choice.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(card) = game.get_active_player().hand.get(choice[0]) {
|
||||
if let None = card.treasure() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let card = game.players[game.active_player].hand.remove(choice[0]);
|
||||
let cost = card.cost;
|
||||
|
||||
game.trash.push(card);
|
||||
|
||||
state.resolved = true;
|
||||
game.add_effect(Effect::Resolving {
|
||||
card: "Mine".into(),
|
||||
request: ResolveRequest::GainCard {
|
||||
filter: CardFilter::MaxCost(cost + 3),
|
||||
},
|
||||
player: ResolvingPlayer::ActivePlayer,
|
||||
effect: |game, message, _player, request, state| {
|
||||
if let ResolveReply::SupplyCardChosen { choice } = message {
|
||||
if let Some((card, count)) = game.supply.get(*choice) {
|
||||
if *count < 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let ResolveRequest::GainCard {
|
||||
filter: CardFilter::MaxCost(cost),
|
||||
..
|
||||
} = request
|
||||
{
|
||||
if card.cost > *cost {
|
||||
return;
|
||||
}
|
||||
|
||||
if let None = card.treasure() {
|
||||
return;
|
||||
}
|
||||
|
||||
game.supply.get_mut(*choice).unwrap().1 =
|
||||
game.supply.get(*choice).unwrap().1 - 1;
|
||||
let card = game.supply[*choice].0.clone();
|
||||
|
||||
game.players[game.active_player]
|
||||
.discard_pile
|
||||
.push(card.clone());
|
||||
state.resolved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
})],
|
||||
}
|
||||
}
|
1
src/sets/mod.rs
Normal file
1
src/sets/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod base;
|
Loading…
Reference in New Issue
Block a user