Add workshop and remodel effects
This commit is contained in:
		
							
								
								
									
										173
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										173
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -25,7 +25,7 @@ enum ClientMessage {
 | 
			
		||||
    DrawCard,
 | 
			
		||||
    Discard { index: usize },
 | 
			
		||||
    TrashHand { index: usize },
 | 
			
		||||
    HandCardsChosen { choice: Vec<usize> },
 | 
			
		||||
    ResolveReply { reply: ResolveReply },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Serialize)]
 | 
			
		||||
@@ -62,7 +62,10 @@ enum ServerMessage {
 | 
			
		||||
        score: Vec<(usize, u32)>,
 | 
			
		||||
    },
 | 
			
		||||
    ResolveRequest {
 | 
			
		||||
        /// Request type and context
 | 
			
		||||
        request: ResolveRequest,
 | 
			
		||||
        /// The card that is the source of the request to display to the player
 | 
			
		||||
        card: String,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -175,11 +178,25 @@ enum ResolvingPlayer {
 | 
			
		||||
    ActivePlayer,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
enum ResolveReply {
 | 
			
		||||
    HandCardsChosen { choice: Vec<usize> },
 | 
			
		||||
    SupplyCardChosen { choice: usize },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Sent by the server to tell the client what information is requested
 | 
			
		||||
/// from the player to resolve the current effect
 | 
			
		||||
#[derive(Clone, Serialize)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
enum ResolveRequest {
 | 
			
		||||
    ChooseHandCardsToDiscard { player: ResolvingPlayer },
 | 
			
		||||
    ChooseHandCardsToDiscard {
 | 
			
		||||
        player: ResolvingPlayer,
 | 
			
		||||
    },
 | 
			
		||||
    GainCard {
 | 
			
		||||
        player: ResolvingPlayer,
 | 
			
		||||
        filter: CardFilter,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
@@ -187,10 +204,19 @@ enum Effect {
 | 
			
		||||
    /// Trigger effect if another card is played while effect is active
 | 
			
		||||
    /// return true to indicate the event is resolved and should be removed
 | 
			
		||||
    OnCardPlayed(fn(&mut Game, &Card) -> bool),
 | 
			
		||||
    Resolving(ResolveRequest, ResolvingEffect),
 | 
			
		||||
    /// 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),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResolvingEffect = fn(&mut Game, &ClientMessage);
 | 
			
		||||
#[derive(Clone, Serialize)]
 | 
			
		||||
//#[serde(tag = "type")]
 | 
			
		||||
enum CardFilter {
 | 
			
		||||
    Any,
 | 
			
		||||
    MaxCost(u32),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ResolvingEffect = fn(&mut Game, &ResolveReply);
 | 
			
		||||
 | 
			
		||||
pub struct Game {
 | 
			
		||||
    effects: Vec<Effect>,
 | 
			
		||||
@@ -205,7 +231,7 @@ pub struct Game {
 | 
			
		||||
 | 
			
		||||
    /// Any effect from a card that requires further input from players
 | 
			
		||||
    /// and blocks the game until fully resolved.
 | 
			
		||||
    resolving_effect: Option<(ResolveRequest, ResolvingEffect)>,
 | 
			
		||||
    resolving_effect: Option<(String, ResolveRequest, ResolvingEffect)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Game {
 | 
			
		||||
@@ -254,11 +280,12 @@ impl Game {
 | 
			
		||||
                            types: vec![CardType::Action(|game| {
 | 
			
		||||
                                action!(game, 1);
 | 
			
		||||
                                game.add_effect(Effect::Resolving(
 | 
			
		||||
                                    "Cellar".into(),
 | 
			
		||||
                                    ResolveRequest::ChooseHandCardsToDiscard {
 | 
			
		||||
                                        player: ResolvingPlayer::ActivePlayer,
 | 
			
		||||
                                    },
 | 
			
		||||
                                    |game, message| {
 | 
			
		||||
                                        if let ClientMessage::HandCardsChosen { choice } = message {
 | 
			
		||||
                                        if let ResolveReply::HandCardsChosen { choice } = message {
 | 
			
		||||
                                            let mut discarded = 0;
 | 
			
		||||
                                            for c in choice.iter().sorted_by(|a, b| b.cmp(a)) {
 | 
			
		||||
                                                game.players[game.active_player].discard(*c);
 | 
			
		||||
@@ -321,7 +348,36 @@ impl Game {
 | 
			
		||||
                        Card {
 | 
			
		||||
                            name: "Workshop".into(),
 | 
			
		||||
                            cost: 3,
 | 
			
		||||
                            types: vec![CardType::Action(|_| {})],
 | 
			
		||||
                            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,
 | 
			
		||||
                    ),
 | 
			
		||||
@@ -337,7 +393,82 @@ impl Game {
 | 
			
		||||
                        Card {
 | 
			
		||||
                            name: "Remodel".into(),
 | 
			
		||||
                            cost: 4,
 | 
			
		||||
                            types: vec![CardType::Action(|_| {})],
 | 
			
		||||
                            types: vec![CardType::Action(|game| {
 | 
			
		||||
                                game.add_effect(Effect::Resolving(
 | 
			
		||||
                                    "Remodel".into(),
 | 
			
		||||
                                    ResolveRequest::ChooseHandCardsToDiscard {
 | 
			
		||||
                                        player: ResolvingPlayer::ActivePlayer,
 | 
			
		||||
                                    },
 | 
			
		||||
                                    |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,
 | 
			
		||||
                    ),
 | 
			
		||||
@@ -482,7 +613,7 @@ impl Game {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Effect::Resolving(_, _) => {}
 | 
			
		||||
                Effect::Resolving(_, _, _) => {}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            true
 | 
			
		||||
@@ -525,8 +656,8 @@ impl Game {
 | 
			
		||||
                self.effects.push(effect);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Effect::Resolving(request, effect) => {
 | 
			
		||||
                self.resolving_effect = Some((request, effect));
 | 
			
		||||
            Effect::Resolving(card_name, request, effect) => {
 | 
			
		||||
                self.resolving_effect = Some((card_name, request, effect));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -611,12 +742,24 @@ async fn broadcast_state(game: &Game) {
 | 
			
		||||
        broadcast(&game, &sm).await;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if let Some((request, _)) = &game.resolving_effect {
 | 
			
		||||
    if let Some((card_name, request, _)) = &game.resolving_effect {
 | 
			
		||||
        match request {
 | 
			
		||||
            ResolveRequest::ChooseHandCardsToDiscard { ref player } => match player {
 | 
			
		||||
                ResolvingPlayer::ActivePlayer => {
 | 
			
		||||
                    let p = game.players.get(game.active_player).unwrap();
 | 
			
		||||
                    let sm = ServerMessage::ResolveRequest {
 | 
			
		||||
                        card: card_name.clone(),
 | 
			
		||||
                        request: request.clone(),
 | 
			
		||||
                    };
 | 
			
		||||
                    send_msg(&game, &p, &sm).await;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            ResolveRequest::GainCard { ref player, .. } => match player {
 | 
			
		||||
                ResolvingPlayer::ActivePlayer => {
 | 
			
		||||
                    let p = game.players.get(game.active_player).unwrap();
 | 
			
		||||
                    let sm = ServerMessage::ResolveRequest {
 | 
			
		||||
                        card: card_name.clone(),
 | 
			
		||||
                        request: request.clone(),
 | 
			
		||||
                    };
 | 
			
		||||
                    send_msg(&game, &p, &sm).await;
 | 
			
		||||
@@ -917,14 +1060,14 @@ async fn main() -> Result<(), std::io::Error> {
 | 
			
		||||
                        broadcast_state(&game).await;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    ClientMessage::HandCardsChosen { .. } => {
 | 
			
		||||
                    ClientMessage::ResolveReply { reply } => {
 | 
			
		||||
                        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, &msg);
 | 
			
		||||
                                Some((_, _, ref effect)) => {
 | 
			
		||||
                                    effect(game, &reply);
 | 
			
		||||
                                }
 | 
			
		||||
                                None => {}
 | 
			
		||||
                            }
 | 
			
		||||
 
 | 
			
		||||
@@ -86,6 +86,10 @@ img.card:hover {
 | 
			
		||||
    box-shadow: 0 0 10px lime;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.supply-pile.selected {
 | 
			
		||||
    box-shadow: 0 0 10px lime;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.chat-window {
 | 
			
		||||
    border: 1px solid black;
 | 
			
		||||
    width: 300px;
 | 
			
		||||
@@ -431,6 +435,15 @@ img.card:hover {
 | 
			
		||||
    color: white;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
    grid-template-rows: 5fr auto;
 | 
			
		||||
    transition: top 0.1s ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog.supply {
 | 
			
		||||
    top: 80%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog.hand {
 | 
			
		||||
    top: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dialog img {
 | 
			
		||||
@@ -462,23 +475,47 @@ img.card:hover {
 | 
			
		||||
 | 
			
		||||
        var resolve_request;
 | 
			
		||||
        var enable_hand_selection = false;
 | 
			
		||||
        var enable_supply_selection = false;
 | 
			
		||||
 | 
			
		||||
        var toggle_hand_selection = function(index) {
 | 
			
		||||
            document.querySelectorAll(".player-hand img.card")[index].classList.toggle("selected");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var toggle_supply_selection = function(index) {
 | 
			
		||||
            document.querySelectorAll(".supply-area .supply-pile")[index].classList.toggle("selected");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        function dialog_confirm(ev) {
 | 
			
		||||
            var selected = [];
 | 
			
		||||
            
 | 
			
		||||
            document.querySelectorAll(".player-hand img.card.selected").forEach((c) => {
 | 
			
		||||
                selected.push(parseInt(c.dataset.index));
 | 
			
		||||
                c.classList.remove("selected");
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            var msg = { type: "HandCardsChosen", choice: selected };
 | 
			
		||||
            webSocket.send(JSON.stringify(msg));
 | 
			
		||||
            enable_hand_selection = false;
 | 
			
		||||
            if (resolve_request.request.type == "GainCard") {
 | 
			
		||||
                var selected = document.querySelector(".supply-pile.selected");
 | 
			
		||||
                if (selected) {
 | 
			
		||||
                    selected = parseInt(selected.dataset.index);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                document.querySelectorAll(".supply-pile.selected").forEach((c) => {
 | 
			
		||||
                    c.classList.remove("selected");
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                var reply = { type: "SupplyCardChosen", choice: selected };
 | 
			
		||||
                var msg = { type: "ResolveReply", reply: reply };
 | 
			
		||||
                webSocket.send(JSON.stringify(msg));
 | 
			
		||||
                enable_supply_selection = false;
 | 
			
		||||
             } else {
 | 
			
		||||
                var selected = [];
 | 
			
		||||
                
 | 
			
		||||
                document.querySelectorAll(".player-hand img.card.selected").forEach((c) => {
 | 
			
		||||
                    selected.push(parseInt(c.dataset.index));
 | 
			
		||||
                    c.classList.remove("selected");
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                var reply = { type: "HandCardsChosen", choice: selected };
 | 
			
		||||
                var msg = { type: "ResolveReply", reply: reply };
 | 
			
		||||
                webSocket.send(JSON.stringify(msg));
 | 
			
		||||
                enable_hand_selection = false;
 | 
			
		||||
            }
 | 
			
		||||
            document.querySelector("#dialog").style.visibility = "hidden";
 | 
			
		||||
            dialog.classList.remove("hand");
 | 
			
		||||
            dialog.classList.remove("supply");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
         var mouseenter = function(ev) {
 | 
			
		||||
@@ -552,6 +589,13 @@ img.card:hover {
 | 
			
		||||
                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var click = function(e) {
 | 
			
		||||
                e.preventDefault();
 | 
			
		||||
                if (enable_supply_selection) {
 | 
			
		||||
                    toggle_supply_selection(parseInt(e.srcElement.parentElement.dataset.index));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
            var doubleclick = function(ev) {
 | 
			
		||||
                let msg = {
 | 
			
		||||
                    type: "BuyCard",
 | 
			
		||||
@@ -570,6 +614,7 @@ img.card:hover {
 | 
			
		||||
                            "data-index": vnode.attrs.index,
 | 
			
		||||
                            ondragstart: dragStart,
 | 
			
		||||
                            draggable: true,
 | 
			
		||||
                            onclick: click,
 | 
			
		||||
                            ondblclick: doubleclick,
 | 
			
		||||
                        },
 | 
			
		||||
                        m("img", {
 | 
			
		||||
@@ -722,7 +767,6 @@ img.card:hover {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                view: function(vnode) {
 | 
			
		||||
                    return m(".player-hand",
 | 
			
		||||
@@ -1024,9 +1068,26 @@ img.card:hover {
 | 
			
		||||
 | 
			
		||||
            var handle_resolve_request = function(request) {
 | 
			
		||||
                var dlg = document.querySelector("#dialog");
 | 
			
		||||
                dlg.style.visibility = "visible";
 | 
			
		||||
                resolve_request = request;
 | 
			
		||||
 | 
			
		||||
                enable_hand_selection = true;
 | 
			
		||||
                document.querySelector("#dialog img").src = "/static/images/cards/" + request.card.toLowerCase() + ".jpg";
 | 
			
		||||
 | 
			
		||||
                if (request.request.type == "GainCard") {
 | 
			
		||||
                    let cost = request.request?.filter?.MaxCost;
 | 
			
		||||
                    if (cost) {
 | 
			
		||||
                        document.querySelector("#dialog p").innerHTML = "Choose a card to gain with max cost of " + cost + ".";
 | 
			
		||||
                    } else {
 | 
			
		||||
                        document.querySelector("#dialog p").innerHTML = "Choose a card to gain.";
 | 
			
		||||
                    }
 | 
			
		||||
                    enable_supply_selection = true;
 | 
			
		||||
                    dialog.classList.add("supply");
 | 
			
		||||
                } else if (request.request.type == "ChooseHandCardsToDiscard") {
 | 
			
		||||
                    document.querySelector("#dialog p").innerHTML = "Choose any number of cards to discard.";
 | 
			
		||||
                    enable_hand_selection = true;
 | 
			
		||||
                    dialog.classList.add("hand");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                dlg.style.visibility = "visible";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var handle_gameover = function(msg) {
 | 
			
		||||
@@ -1077,7 +1138,7 @@ img.card:hover {
 | 
			
		||||
                } else if (msg.type == "PlayerId") {
 | 
			
		||||
                    my_player_id = msg.id;
 | 
			
		||||
                } else if (msg.type == "ResolveRequest") {
 | 
			
		||||
                    handle_resolve_request(msg.request);
 | 
			
		||||
                    handle_resolve_request(msg);
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.log("event?");
 | 
			
		||||
                    console.log(event.data);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user