Move card definition to new module

This commit is contained in:
Markus Wagner 2021-01-24 17:15:06 +01:00
parent d18942feda
commit c94876c54c
4 changed files with 495 additions and 422 deletions

View File

@ -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>,
}

View File

@ -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,19 +904,42 @@ async fn main() -> Result<(), std::io::Error> {
let mut games = req.state().games.write().await;
let game = games.get_mut(&game_id).unwrap();
if let Some((card, request, effect, player, mut state)) =
game.resolving_effect.take()
{
match player {
ResolvingPlayer::ActivePlayer => {
if player_number == game.active_player {
match game.resolving_effect {
Some((_, _, ref effect)) => {
effect(game, &reply);
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);
}
}
}
match state.resolved {
false => {
game.resolving_effect =
Some((card, request, effect, player, state));
}
true => {}
}
None => {}
}
broadcast_state(&game).await;
}
}
}
}
let mut games = req.state().games.write().await;
let game = games.get_mut(&game_id).unwrap();

377
src/sets/base.rs Normal file
View 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
View File

@ -0,0 +1 @@
pub mod base;