Allow client to switch kingdom cards in setup phase
This commit is contained in:
parent
246962ca96
commit
73bce1bc6d
16
src/card.rs
16
src/card.rs
@ -47,12 +47,26 @@ pub enum CardType {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Card {
|
pub struct Card {
|
||||||
pub name: &'static str,
|
|
||||||
pub cost: u32,
|
pub cost: u32,
|
||||||
|
pub name: &'static str,
|
||||||
pub types: Vec<CardType>,
|
pub types: Vec<CardType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Card {
|
impl Card {
|
||||||
|
pub fn new(name: &'static str, cost: u32) -> Card {
|
||||||
|
Card {
|
||||||
|
name,
|
||||||
|
cost,
|
||||||
|
types: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_type(&self, card_type: CardType) -> Card {
|
||||||
|
let mut card = self.clone();
|
||||||
|
card.types.push(card_type);
|
||||||
|
card
|
||||||
|
}
|
||||||
|
|
||||||
pub fn action(&self) -> Option<fn(&mut Game)> {
|
pub fn action(&self) -> Option<fn(&mut Game)> {
|
||||||
for t in &self.types {
|
for t in &self.types {
|
||||||
match t {
|
match t {
|
||||||
|
227
src/main.rs
227
src/main.rs
@ -64,6 +64,7 @@ enum ServerMessage {
|
|||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
struct GameSetup {
|
struct GameSetup {
|
||||||
|
supply: Vec<Card>,
|
||||||
deck: Vec<Card>,
|
deck: Vec<Card>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,25 +149,39 @@ enum GameState {
|
|||||||
|
|
||||||
impl Default for GameSetup {
|
impl Default for GameSetup {
|
||||||
fn default() -> GameSetup {
|
fn default() -> GameSetup {
|
||||||
|
let set = sets::base::base();
|
||||||
|
|
||||||
GameSetup {
|
GameSetup {
|
||||||
|
supply: vec![
|
||||||
|
set.get("Chapel").unwrap(),
|
||||||
|
set.get("Workshop").unwrap(),
|
||||||
|
set.get("Bureaucrat").unwrap(),
|
||||||
|
set.get("Gardens").unwrap(),
|
||||||
|
set.get("Throne Room").unwrap(),
|
||||||
|
set.get("Bandit").unwrap(),
|
||||||
|
set.get("Festival").unwrap(),
|
||||||
|
set.get("Sentry").unwrap(),
|
||||||
|
set.get("Witch").unwrap(),
|
||||||
|
set.get("Artisan").unwrap(),
|
||||||
|
],
|
||||||
deck: vec![
|
deck: vec![
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::copper(),
|
set.get("Copper").unwrap(),
|
||||||
sets::base::estate(),
|
set.get("Estate").unwrap(),
|
||||||
sets::base::estate(),
|
set.get("Estate").unwrap(),
|
||||||
sets::base::estate(),
|
set.get("Estate").unwrap(),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which player's input is requested to resolve the current effect
|
/// Which player's input is requested to resolve the current effect
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize, PartialEq)]
|
||||||
enum ResolvingPlayer {
|
enum ResolvingPlayer {
|
||||||
ActivePlayer,
|
ActivePlayer,
|
||||||
AllNonActivePlayers,
|
AllNonActivePlayers,
|
||||||
@ -188,6 +203,11 @@ enum ResolveRequest {
|
|||||||
GainCard { filter: CardFilter },
|
GainCard { filter: CardFilter },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ResolveResult {
|
||||||
|
Resolved,
|
||||||
|
NotResolved,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Effect {
|
enum Effect {
|
||||||
/// Trigger effect if another card is played while effect is active
|
/// Trigger effect if another card is played while effect is active
|
||||||
@ -213,25 +233,71 @@ struct EffectState {
|
|||||||
//#[serde(tag = "type")]
|
//#[serde(tag = "type")]
|
||||||
enum CardFilter {
|
enum CardFilter {
|
||||||
Any,
|
Any,
|
||||||
|
And(Vec<CardFilter>),
|
||||||
MaxCost(u32),
|
MaxCost(u32),
|
||||||
Type(CardType),
|
Type(CardType),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CardFilter {
|
||||||
|
fn match_card(&self, card: &Card) -> bool {
|
||||||
|
match &self {
|
||||||
|
CardFilter::Any => true,
|
||||||
|
CardFilter::And(filter) => filter.iter().all(|f| f.match_card(&card)),
|
||||||
|
CardFilter::MaxCost(cost) => card.cost <= *cost,
|
||||||
|
CardFilter::Type(card_type) => match card_type {
|
||||||
|
CardType::Action(_) => card.action().is_some(),
|
||||||
|
CardType::Attack => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
CardType::Curse => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
CardType::Reaction(_) => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
CardType::Treasure(_) => card.treasure().is_some(),
|
||||||
|
CardType::Victory(_) => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type ResolvingEffectHandler =
|
type ResolvingEffectHandler =
|
||||||
fn(&mut Game, &ResolveReply, usize, &ResolveRequest, &mut EffectState);
|
fn(&mut Game, &ResolveReply, usize, &ResolveRequest, &mut EffectState) -> ResolveResult;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
enum Command {
|
enum Command {
|
||||||
BuyCard { index: usize },
|
BuyCard {
|
||||||
Discard { index: usize },
|
index: usize,
|
||||||
|
},
|
||||||
|
Discard {
|
||||||
|
index: usize,
|
||||||
|
},
|
||||||
DrawCard,
|
DrawCard,
|
||||||
EndTurn,
|
EndTurn,
|
||||||
GainCard { index: usize },
|
GainCard {
|
||||||
PlayCard { index: usize },
|
index: usize,
|
||||||
ResolveReply { reply: ResolveReply },
|
},
|
||||||
|
PlayCard {
|
||||||
|
index: usize,
|
||||||
|
},
|
||||||
|
ResolveReply {
|
||||||
|
reply: ResolveReply,
|
||||||
|
},
|
||||||
StartGame,
|
StartGame,
|
||||||
TrashHand { index: usize },
|
TrashHand {
|
||||||
|
index: usize,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Replace a kingdom card in the supply by another one,
|
||||||
|
/// only viable during Setup state
|
||||||
|
ChangeSupply {
|
||||||
|
index: usize,
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -243,6 +309,7 @@ enum Event {
|
|||||||
CardDiscarded { player: usize },
|
CardDiscarded { player: usize },
|
||||||
CardTrashed { player: usize, name: &'static str },
|
CardTrashed { player: usize, name: &'static str },
|
||||||
CardPlayed { player: usize, name: &'static str },
|
CardPlayed { player: usize, name: &'static str },
|
||||||
|
CardRevealed { player: usize, name: &'static str },
|
||||||
TurnStarted { player: usize },
|
TurnStarted { player: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,6 +353,15 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn supply_name_to_index(&self, name: &String) -> Option<usize> {
|
||||||
|
for i in 0..self.supply.len() {
|
||||||
|
if self.supply[i].0.name == name {
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn start(&mut self) {
|
fn start(&mut self) {
|
||||||
match self.state {
|
match self.state {
|
||||||
GameState::Setup => {
|
GameState::Setup => {
|
||||||
@ -306,24 +382,24 @@ impl Game {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.supply = vec![
|
self.supply = vec![
|
||||||
(sets::base::copper(), 60 - self.players.len() * 7),
|
(
|
||||||
(sets::base::silver(), 40),
|
sets::base::base().get("Copper").unwrap(),
|
||||||
(sets::base::gold(), 30),
|
60 - self.players.len() * 7,
|
||||||
(sets::base::estate(), victory_qty),
|
),
|
||||||
(sets::base::duchy(), victory_qty),
|
(sets::base::base().get("Silver").unwrap(), 40),
|
||||||
(sets::base::province(), victory_qty),
|
(sets::base::base().get("Gold").unwrap(), 30),
|
||||||
(sets::base::curse(), 10),
|
(sets::base::base().get("Estate").unwrap(), victory_qty),
|
||||||
(sets::base::cellar(), 10),
|
(sets::base::base().get("Duchy").unwrap(), victory_qty),
|
||||||
(sets::base::moat(), 10),
|
(sets::base::base().get("Province").unwrap(), victory_qty),
|
||||||
(sets::base::village(), 10),
|
(sets::base::base().get("Curse").unwrap(), 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),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
self.setup
|
||||||
|
.supply
|
||||||
|
.sort_by(|a, b| a.cost.cmp(&b.cost).then(a.name.cmp(&b.name)));
|
||||||
|
for supply_card in self.setup.supply.iter() {
|
||||||
|
self.supply.push((supply_card.clone(), 10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {} // Ignore if game is not in setup state
|
_ => {} // Ignore if game is not in setup state
|
||||||
@ -424,7 +500,12 @@ impl Game {
|
|||||||
if !self.can_play(player_number, index) {
|
if !self.can_play(player_number, index) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let card = self.players[player_number].hand.remove(index);
|
let card = self.players[player_number].hand.remove(index);
|
||||||
|
self.emit(Event::CardPlayed {
|
||||||
|
player: player_number,
|
||||||
|
name: card.name,
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(coin) = card.treasure() {
|
if let Some(coin) = card.treasure() {
|
||||||
self.turn_state.coin += coin;
|
self.turn_state.coin += coin;
|
||||||
@ -457,6 +538,31 @@ impl Game {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gain_card(&mut self, player: usize, index: &usize, filter: &CardFilter) -> Result<(), ()> {
|
||||||
|
if let Some((card, count)) = self.supply.get(*index) {
|
||||||
|
if *count < 1 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filter.match_card(&card) {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.supply.get_mut(*index).unwrap().1 = self.supply.get(*index).unwrap().1 - 1;
|
||||||
|
let card = self.supply[*index].0.clone();
|
||||||
|
|
||||||
|
self.players[player].discard_pile.push(card.clone());
|
||||||
|
|
||||||
|
self.emit(Event::CardGained {
|
||||||
|
player,
|
||||||
|
index: *index,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn buy_card(&mut self, player_number: usize, index: usize) -> bool /*-> Result<(), &'static str>*/
|
pub fn buy_card(&mut self, player_number: usize, index: usize) -> bool /*-> Result<(), &'static str>*/
|
||||||
{
|
{
|
||||||
if let Some(_) = self.resolving_effect {
|
if let Some(_) = self.resolving_effect {
|
||||||
@ -499,6 +605,10 @@ impl Game {
|
|||||||
effect,
|
effect,
|
||||||
player,
|
player,
|
||||||
} => {
|
} => {
|
||||||
|
if player == ResolvingPlayer::AllNonActivePlayers && self.players.len() == 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let state = EffectState::default();
|
let state = EffectState::default();
|
||||||
self.resolving_effect = Some((card, request, effect, player, state));
|
self.resolving_effect = Some((card, request, effect, player, state));
|
||||||
}
|
}
|
||||||
@ -507,6 +617,14 @@ impl Game {
|
|||||||
|
|
||||||
fn handle_command(&mut self, player: usize, command: Command) {
|
fn handle_command(&mut self, player: usize, command: Command) {
|
||||||
match command {
|
match command {
|
||||||
|
Command::ChangeSupply { index, name } => {
|
||||||
|
if let GameState::Setup = self.state {
|
||||||
|
if let Some(card) = sets::base::base().get(name.as_ref()) {
|
||||||
|
self.setup.supply[index] = card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Command::DrawCard => {
|
Command::DrawCard => {
|
||||||
if !self.debug_mode {
|
if !self.debug_mode {
|
||||||
return;
|
return;
|
||||||
@ -536,11 +654,7 @@ impl Game {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.supply.get_mut(index).unwrap().1 = self.supply.get(index).unwrap().1 - 1;
|
self.gain_card(player, &index, &CardFilter::Any);
|
||||||
let card = self.supply[index].0.clone();
|
|
||||||
|
|
||||||
self.players[player].discard_pile.push(card.clone());
|
|
||||||
self.emit(Event::CardGained { player, index })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::Discard { index } => {
|
Command::Discard { index } => {
|
||||||
@ -564,10 +678,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Command::PlayCard { index } => {
|
Command::PlayCard { index } => {
|
||||||
let name = self.players[player].hand[index].name.clone();
|
self.play_card(player, index);
|
||||||
if self.play_card(player, index) {
|
|
||||||
self.emit(Event::CardPlayed { player, name });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::ResolveReply { reply } => {
|
Command::ResolveReply { reply } => {
|
||||||
@ -577,13 +688,26 @@ impl Game {
|
|||||||
match resolve_player {
|
match resolve_player {
|
||||||
ResolvingPlayer::ActivePlayer => {
|
ResolvingPlayer::ActivePlayer => {
|
||||||
if player == self.active_player {
|
if player == self.active_player {
|
||||||
effect(self, &reply, self.active_player, &request, &mut state);
|
match effect(self, &reply, self.active_player, &request, &mut state)
|
||||||
|
{
|
||||||
|
ResolveResult::Resolved => state.resolved = true,
|
||||||
|
ResolveResult::NotResolved => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResolvingPlayer::AllNonActivePlayers => {
|
ResolvingPlayer::AllNonActivePlayers => {
|
||||||
if player != self.active_player {
|
if player != self.active_player {
|
||||||
effect(self, &reply, player, &request, &mut state);
|
match effect(self, &reply, player, &request, &mut state) {
|
||||||
|
ResolveResult::Resolved => {
|
||||||
|
state.players_responded.push(player);
|
||||||
|
}
|
||||||
|
ResolveResult::NotResolved => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.players_responded.len() == self.players.len() - 1 {
|
||||||
|
state.resolved = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -600,7 +724,7 @@ impl Game {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit(&mut self, event: Event) {
|
fn emit(&self, event: Event) {
|
||||||
println!("Emitting event: {:?}", event);
|
println!("Emitting event: {:?}", event);
|
||||||
async_std::task::block_on(notify_players(self, event));
|
async_std::task::block_on(notify_players(self, event));
|
||||||
}
|
}
|
||||||
@ -618,7 +742,7 @@ async fn notify_players(game: &Game, event: Event) {
|
|||||||
|
|
||||||
async fn broadcast(game: &Game, sm: &ServerMessage) {
|
async fn broadcast(game: &Game, sm: &ServerMessage) {
|
||||||
for (_, (_, con)) in game.connections.iter() {
|
for (_, (_, con)) in game.connections.iter() {
|
||||||
con.send_json(&sm).await.unwrap();
|
con.send_json(&sm).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,6 +781,13 @@ async fn broadcast_state(game: &Game) {
|
|||||||
|
|
||||||
broadcast(&game, &sm).await;
|
broadcast(&game, &sm).await;
|
||||||
|
|
||||||
|
if let GameState::Setup = game.state {
|
||||||
|
let sm = ServerMessage::GameSetup {
|
||||||
|
setup: game.setup.clone(),
|
||||||
|
};
|
||||||
|
broadcast(&game, &sm).await;
|
||||||
|
}
|
||||||
|
|
||||||
for p in game.players.iter() {
|
for p in game.players.iter() {
|
||||||
let sm = ServerMessage::PlayerHand {
|
let sm = ServerMessage::PlayerHand {
|
||||||
hand: p.hand.clone(),
|
hand: p.hand.clone(),
|
||||||
|
764
src/sets/base.rs
764
src/sets/base.rs
@ -1,377 +1,443 @@
|
|||||||
use crate::card::{Card, CardType};
|
use crate::card::{Card, CardType};
|
||||||
use crate::{CardFilter, Effect, ResolveReply, ResolveRequest, ResolvingPlayer};
|
use crate::{
|
||||||
|
CardFilter, Effect, Event, ResolveReply, ResolveRequest, ResolveResult::*, ResolvingPlayer,
|
||||||
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
pub fn copper() -> Card {
|
pub struct CardSet {
|
||||||
Card {
|
name: &'static str,
|
||||||
name: "Copper",
|
cards: Vec<Card>,
|
||||||
cost: 0,
|
}
|
||||||
types: vec![CardType::Treasure(1)],
|
|
||||||
|
impl CardSet {
|
||||||
|
pub fn get(&self, name: &str) -> Option<Card> {
|
||||||
|
for card in self.cards.iter() {
|
||||||
|
if card.name == name {
|
||||||
|
return Some(card.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn silver() -> Card {
|
pub fn base() -> CardSet {
|
||||||
Card {
|
CardSet {
|
||||||
name: "Silver",
|
name: "Dominion",
|
||||||
cost: 3,
|
cards: vec![
|
||||||
types: vec![CardType::Treasure(2)],
|
copper(),
|
||||||
}
|
silver(),
|
||||||
}
|
gold(),
|
||||||
|
estate(),
|
||||||
pub fn gold() -> Card {
|
duchy(),
|
||||||
Card {
|
province(),
|
||||||
name: "Gold",
|
curse(),
|
||||||
cost: 6,
|
artisan(),
|
||||||
types: vec![CardType::Treasure(3)],
|
bandit(),
|
||||||
}
|
bureaucrat(),
|
||||||
}
|
chapel(),
|
||||||
|
cellar(),
|
||||||
pub fn estate() -> Card {
|
gardens(),
|
||||||
Card {
|
festival(),
|
||||||
name: "Estate",
|
market(),
|
||||||
cost: 2,
|
militia(),
|
||||||
types: vec![CardType::Victory(1)],
|
mine(),
|
||||||
}
|
merchant(),
|
||||||
}
|
moat(),
|
||||||
|
remodel(),
|
||||||
pub fn duchy() -> Card {
|
sentry(),
|
||||||
Card {
|
smithy(),
|
||||||
name: "Duchy",
|
throne_room(),
|
||||||
cost: 5,
|
village(),
|
||||||
types: vec![CardType::Victory(3)],
|
witch(),
|
||||||
}
|
workshop(),
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
fn copper() -> Card {
|
||||||
Card {
|
Card::new("Copper", 0).with_type(CardType::Treasure(1))
|
||||||
name: "Village",
|
|
||||||
cost: 3,
|
|
||||||
types: vec![CardType::Action(|game| {
|
|
||||||
draw!(game, 1);
|
|
||||||
action!(game, 2);
|
|
||||||
})],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn merchant() -> Card {
|
fn silver() -> Card {
|
||||||
Card {
|
Card::new("Silver", 3).with_type(CardType::Treasure(2))
|
||||||
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 {
|
fn gold() -> Card {
|
||||||
Card {
|
Card::new("Gold", 6).with_type(CardType::Treasure(3))
|
||||||
name: "Workshop",
|
}
|
||||||
cost: 3,
|
|
||||||
types: vec![CardType::Action(|game| {
|
fn estate() -> Card {
|
||||||
|
Card::new("Estate", 2).with_type(CardType::Victory(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn duchy() -> Card {
|
||||||
|
Card::new("Duchy", 5).with_type(CardType::Victory(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn province() -> Card {
|
||||||
|
Card::new("Province", 8).with_type(CardType::Victory(6))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn curse() -> Card {
|
||||||
|
Card::new("Curse", 8).with_type(CardType::Curse)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn artisan() -> Card {
|
||||||
|
Card::new("Artisan", 6).with_type(CardType::Action(|_game| {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bandit() -> Card {
|
||||||
|
Card::new("Bandit", 5)
|
||||||
|
.with_type(CardType::Attack)
|
||||||
|
.with_type(CardType::Action(|_game| {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bureaucrat() -> Card {
|
||||||
|
Card::new("Bureaucrat", 4)
|
||||||
|
.with_type(CardType::Attack)
|
||||||
|
.with_type(CardType::Action(|game| {
|
||||||
|
if let Some(index) = game.supply_name_to_index(&"Silver".into()) {
|
||||||
|
game.gain_card(game.active_player, &index, &CardFilter::Any);
|
||||||
|
let card = game.players[game.active_player].discard_pile.pop().unwrap();
|
||||||
|
game.players[game.active_player].draw_pile.push(card);
|
||||||
|
}
|
||||||
|
|
||||||
game.add_effect(Effect::Resolving {
|
game.add_effect(Effect::Resolving {
|
||||||
card: "Workshop".into(),
|
card: "Bureaucrat".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 {
|
request: ResolveRequest::ChooseHandCardsToDiscard {
|
||||||
filter: CardFilter::Any,
|
filter: CardFilter::Type(CardType::Victory(0)),
|
||||||
},
|
},
|
||||||
player: ResolvingPlayer::ActivePlayer,
|
player: ResolvingPlayer::AllNonActivePlayers,
|
||||||
effect: |game, message, _player, _request, state| {
|
effect: |game, message, player, _request, _state| {
|
||||||
if let ResolveReply::HandCardsChosen { choice } = message {
|
if let ResolveReply::HandCardsChosen { choice } = message {
|
||||||
if choice.len() != 1 {
|
if choice.len() > 1 {
|
||||||
return;
|
return NotResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
let card = game.players[game.active_player].hand.remove(choice[0]);
|
if choice.len() == 0 {
|
||||||
let cost = card.cost;
|
if game.players[player].hand.iter().any(|c| c.name == "Moat") {
|
||||||
|
game.emit(Event::CardRevealed {
|
||||||
|
player,
|
||||||
|
name: "Moat",
|
||||||
|
});
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
|
||||||
game.trash.push(card);
|
if !game.players[player]
|
||||||
|
.hand
|
||||||
state.resolved = true;
|
.iter()
|
||||||
game.add_effect(Effect::Resolving {
|
.any(|c| c.victory().is_some())
|
||||||
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 card in game.players[player].hand.iter() {
|
||||||
}
|
game.emit(Event::CardRevealed {
|
||||||
|
player,
|
||||||
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
name: card.name,
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
return Resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(card) = game.players[player].hand.get(choice[0]) {
|
||||||
|
if card.victory().is_none() {
|
||||||
|
return NotResolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let card = game.players[player].hand.remove(choice[0]);
|
||||||
|
game.emit(Event::CardRevealed {
|
||||||
|
player,
|
||||||
|
name: card.name,
|
||||||
});
|
});
|
||||||
|
game.players[player].draw_pile.push(card);
|
||||||
|
|
||||||
|
return Resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotResolved
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})],
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn cellar() -> Card {
|
||||||
|
Card::new("Cellar", 2).with_type(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| match message {
|
||||||
|
ResolveReply::HandCardsChosen { choice } => {
|
||||||
|
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);
|
||||||
|
Resolved
|
||||||
|
}
|
||||||
|
_ => NotResolved,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chapel() -> Card {
|
||||||
|
Card::new("Chapel", 2).with_type(CardType::Action(|game| {
|
||||||
|
game.add_effect(Effect::Resolving {
|
||||||
|
card: "Chapel".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() > 4 {
|
||||||
|
return NotResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
||||||
|
let card = game.players[game.active_player].hand.remove(*c);
|
||||||
|
game.trash.push(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn festival() -> Card {
|
||||||
|
Card::new("Festival", 5).with_type(CardType::Action(|game| {
|
||||||
|
action!(game, 2);
|
||||||
|
buy!(game, 1);
|
||||||
|
coin!(game, 2);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gardens() -> Card {
|
||||||
|
Card::new("Gardens", 4).with_type(CardType::Victory(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moat() -> Card {
|
||||||
|
Card::new("Moat", 2)
|
||||||
|
.with_type(CardType::Action(|game| {
|
||||||
|
draw!(game, 2);
|
||||||
|
}))
|
||||||
|
.with_type(CardType::Reaction(|_| {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sentry() -> Card {
|
||||||
|
Card::new("Sentry", 5).with_type(CardType::Action(|game| {
|
||||||
|
draw!(game, 1);
|
||||||
|
action!(game, 1);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn throne_room() -> Card {
|
||||||
|
Card::new("Throne Room", 4).with_type(CardType::Action(|_game| {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn village() -> Card {
|
||||||
|
Card::new("Village", 3).with_type(CardType::Action(|game| {
|
||||||
|
draw!(game, 1);
|
||||||
|
action!(game, 2);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merchant() -> Card {
|
||||||
|
Card::new("Merchant", 3).with_type(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
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn witch() -> Card {
|
||||||
|
Card::new("Witch", 5)
|
||||||
|
.with_type(CardType::Attack)
|
||||||
|
.with_type(CardType::Action(|game| {
|
||||||
|
draw!(game, 2);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workshop() -> Card {
|
||||||
|
Card::new("Workshop", 3).with_type(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 ResolveRequest::GainCard { filter } = request {
|
||||||
|
if let Ok(()) = game.gain_card(player, choice, filter) {
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn smithy() -> Card {
|
||||||
|
Card::new("Smithy", 4).with_type(CardType::Action(|game| draw!(game, 3)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remodel() -> Card {
|
||||||
|
Card::new("Remodel", 4).with_type(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 NotResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
let card = game.players[game.active_player].hand.remove(choice[0]);
|
||||||
|
let cost = card.cost;
|
||||||
|
|
||||||
|
game.trash.push(card);
|
||||||
|
|
||||||
|
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 ResolveRequest::GainCard { filter } = request {
|
||||||
|
if let Ok(()) = game.gain_card(player, choice, filter) {
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn militia() -> Card {
|
||||||
|
Card::new("Militia", 4)
|
||||||
|
.with_type(CardType::Attack)
|
||||||
|
.with_type(CardType::Action(|game| {
|
||||||
|
coin!(game, 2);
|
||||||
|
|
||||||
|
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 Resolved;
|
||||||
|
}
|
||||||
|
} else if game.players[player].hand.len() > 3
|
||||||
|
&& choice.len() != game.players[player].hand.len() - 3
|
||||||
|
{
|
||||||
|
return NotResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
|
||||||
|
game.players[player].discard(*c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn market() -> Card {
|
||||||
|
Card::new("Market", 5).with_type(CardType::Action(|game| {
|
||||||
|
draw!(game, 1);
|
||||||
|
action!(game, 1);
|
||||||
|
buy!(game, 1);
|
||||||
|
coin!(game, 1);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mine() -> Card {
|
||||||
|
Card::new("Mine", 5).with_type(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 NotResolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(card) = game.get_active_player().hand.get(choice[0]) {
|
||||||
|
if let None = card.treasure() {
|
||||||
|
return NotResolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::And(vec![
|
||||||
|
CardFilter::Type(CardType::Treasure(0)),
|
||||||
|
CardFilter::MaxCost(cost + 3),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
player: ResolvingPlayer::ActivePlayer,
|
||||||
|
effect: |game, message, player, request, _state| {
|
||||||
|
if let ResolveReply::SupplyCardChosen { choice } = message {
|
||||||
|
if let ResolveRequest::GainCard { filter } = request {
|
||||||
|
if let Ok(()) = game.gain_card(player, choice, filter) {
|
||||||
|
return Resolved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
NotResolved
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
@ -471,6 +471,10 @@ img.card:hover {
|
|||||||
</div>
|
</div>
|
||||||
<div id="game"></div>
|
<div id="game"></div>
|
||||||
<script>
|
<script>
|
||||||
|
var set_data = [
|
||||||
|
["Cellar", "Market", "Merchant", "Militia", "Mine", "Moat", "Remodel", "Smithy", "Village", "Workshop"],
|
||||||
|
["Artisan", "Bandit", "Bureaucrat", "Chapel", "Festival", "Gardens", "Sentry", "Throne Room", "Witch", "Workshop"],
|
||||||
|
];
|
||||||
var turnStartSound = new Audio("/static/audio/chipsStack1.ogg");
|
var turnStartSound = new Audio("/static/audio/chipsStack1.ogg");
|
||||||
|
|
||||||
var resolve_request;
|
var resolve_request;
|
||||||
@ -619,7 +623,7 @@ img.card:hover {
|
|||||||
},
|
},
|
||||||
m("img", {
|
m("img", {
|
||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg",
|
src: card_url(vnode.attrs.name),
|
||||||
draggable: false,
|
draggable: false,
|
||||||
onmouseenter: mouseenter,
|
onmouseenter: mouseenter,
|
||||||
onmouseleave: mouseleave,
|
onmouseleave: mouseleave,
|
||||||
@ -690,7 +694,7 @@ img.card:hover {
|
|||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
var c;
|
var c;
|
||||||
if (vnode.attrs.card) {
|
if (vnode.attrs.card) {
|
||||||
c = m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.card.toLowerCase() + ".jpg"});
|
c = m("img", {class: "card", src: card_url(vnode.attrs.card)});
|
||||||
}
|
}
|
||||||
return m(".discard-pile",
|
return m(".discard-pile",
|
||||||
{
|
{
|
||||||
@ -777,7 +781,7 @@ img.card:hover {
|
|||||||
vnode.attrs.hand.map(function(card, i) {
|
vnode.attrs.hand.map(function(card, i) {
|
||||||
return m("img", {
|
return m("img", {
|
||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
src: card_url(card),
|
||||||
draggable: true,
|
draggable: true,
|
||||||
ondragstart: dragStart,
|
ondragstart: dragStart,
|
||||||
"data-index": i,
|
"data-index": i,
|
||||||
@ -795,7 +799,7 @@ img.card:hover {
|
|||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".opponent-hand",
|
return m(".opponent-hand",
|
||||||
m("img", {class: "card", src: "/static/images/cards/Card_back.jpg"}),
|
m("img", {class: "card", src: "/static/images/cards/Card_back.jpg" }),
|
||||||
m("span", {class: "pile-counter" }, vnode.attrs.count)
|
m("span", {class: "pile-counter" }, vnode.attrs.count)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -839,7 +843,7 @@ img.card:hover {
|
|||||||
cards.map(function(card) {
|
cards.map(function(card) {
|
||||||
return m("img", {
|
return m("img", {
|
||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
src: card_url(card),
|
||||||
draggable: true,
|
draggable: true,
|
||||||
//ondragstart: dragStart,
|
//ondragstart: dragStart,
|
||||||
onmouseenter: mouseenter,
|
onmouseenter: mouseenter,
|
||||||
@ -924,6 +928,10 @@ img.card:hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function card_url(name) {
|
||||||
|
return "/static/images/cards/" + name.toLowerCase().replace(" ", "-") + ".jpg";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function SetupScreen(initialVnode) {
|
function SetupScreen(initialVnode) {
|
||||||
var start_click = function(e) {
|
var start_click = function(e) {
|
||||||
@ -931,6 +939,14 @@ img.card:hover {
|
|||||||
initialVnode.attrs.socket.send(JSON.stringify(msg));
|
initialVnode.attrs.socket.send(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var set_changed = function(e) {
|
||||||
|
let choice = parseInt(e.srcElement.value);
|
||||||
|
for (card in set_data[choice]) {
|
||||||
|
let name = set_data[choice][card];
|
||||||
|
send_command("ChangeSupply", {index: parseInt(card), name: name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
var cls = vnode.attrs.active ? "" : "hidden";
|
var cls = vnode.attrs.active ? "" : "hidden";
|
||||||
@ -941,18 +957,27 @@ img.card:hover {
|
|||||||
vnode.attrs.basic_cards.map(function(card) {
|
vnode.attrs.basic_cards.map(function(card) {
|
||||||
return m("img", {
|
return m("img", {
|
||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
src: card_url(card),
|
||||||
onmouseenter: mouseenter,
|
onmouseenter: mouseenter,
|
||||||
onmouseleave: mouseleave,
|
onmouseleave: mouseleave,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
m("h4", "Kingdom Cards"),
|
m("h4", "Kingdom Cards"),
|
||||||
|
m("select",
|
||||||
|
{
|
||||||
|
name: "set",
|
||||||
|
onchange: set_changed,
|
||||||
|
},[
|
||||||
|
m("option", { value: 0, style: "background-image: url(/static/images/sets/Dominion.png);"}, "First Game"),
|
||||||
|
m("option", { value: 1}, "Size Distortion"),
|
||||||
|
]
|
||||||
|
),
|
||||||
m(".kingdom-cards",
|
m(".kingdom-cards",
|
||||||
vnode.attrs.kingdom_cards.map(function(card) {
|
vnode.attrs.kingdom_cards.map(function(card) {
|
||||||
return m("img", {
|
return m("img", {
|
||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
src: card_url(card),
|
||||||
onmouseenter: mouseenter,
|
onmouseenter: mouseenter,
|
||||||
onmouseleave: mouseleave,
|
onmouseleave: mouseleave,
|
||||||
})
|
})
|
||||||
@ -963,7 +988,7 @@ img.card:hover {
|
|||||||
vnode.attrs.starting_deck.map(function(card) {
|
vnode.attrs.starting_deck.map(function(card) {
|
||||||
return m("img", {
|
return m("img", {
|
||||||
class: "card",
|
class: "card",
|
||||||
src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
|
src: card_url(card),
|
||||||
onmouseenter: mouseenter,
|
onmouseenter: mouseenter,
|
||||||
onmouseleave: mouseleave,
|
onmouseleave: mouseleave,
|
||||||
})
|
})
|
||||||
@ -1008,11 +1033,12 @@ img.card:hover {
|
|||||||
setup_state = {
|
setup_state = {
|
||||||
starting_deck: [],
|
starting_deck: [],
|
||||||
basic_cards: ["Copper", "Silver", "Gold", "Estate", "Duchy", "Province", "Curse"],
|
basic_cards: ["Copper", "Silver", "Gold", "Estate", "Duchy", "Province", "Curse"],
|
||||||
kingdom_cards: ["Cellar", "Moat", "Village", "Merchant", "Workshop", "Smithy", "Remodel", "Militia", "Market", "Mine"],
|
kingdom_cards: [],
|
||||||
socket: webSocket
|
socket: webSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
var handle_setup = function(data) {
|
var handle_setup = function(data) {
|
||||||
|
setup_state.kingdom_cards = data.supply;
|
||||||
setup_state.starting_deck = data.deck;
|
setup_state.starting_deck = data.deck;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1062,7 +1088,7 @@ img.card:hover {
|
|||||||
var dlg = document.querySelector("#dialog");
|
var dlg = document.querySelector("#dialog");
|
||||||
resolve_request = request;
|
resolve_request = request;
|
||||||
|
|
||||||
document.querySelector("#dialog img").src = "/static/images/cards/" + request.card.toLowerCase() + ".jpg";
|
document.querySelector("#dialog img").src = card_url(request.card);
|
||||||
|
|
||||||
if (request.request.type == "GainCard") {
|
if (request.request.type == "GainCard") {
|
||||||
let cost = request.request?.filter?.MaxCost;
|
let cost = request.request?.filter?.MaxCost;
|
||||||
@ -1101,6 +1127,8 @@ img.card:hover {
|
|||||||
append_chat(player_name(msg.event.player) + " discards a card.");
|
append_chat(player_name(msg.event.player) + " discards a card.");
|
||||||
} else if (msg.event.type == "CardTrashed") {
|
} else if (msg.event.type == "CardTrashed") {
|
||||||
append_chat(player_name(msg.event.player) + " trashes " + msg.event.name);
|
append_chat(player_name(msg.event.player) + " trashes " + msg.event.name);
|
||||||
|
} else if (msg.event.type == "CardRevealed") {
|
||||||
|
append_chat(player_name(msg.event.player) + " reveals " + msg.event.name);
|
||||||
} else if (msg.event.type == "TurnStarted") {
|
} else if (msg.event.type == "TurnStarted") {
|
||||||
if (msg.event.player == my_player_id) {
|
if (msg.event.player == my_player_id) {
|
||||||
turnStartSound.play();
|
turnStartSound.play();
|
||||||
|
Loading…
Reference in New Issue
Block a user