From f2dab8a71cd163c29ee4ef6fa27e7604c76c3672 Mon Sep 17 00:00:00 2001 From: Markus Wagner Date: Wed, 20 Jan 2021 20:11:11 +0100 Subject: [PATCH] Add workshop and remodel effects --- src/main.rs | 173 +++++++++++++++++++++++++++++++++++++++++++---- static/game.html | 89 ++++++++++++++++++++---- 2 files changed, 233 insertions(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index 037b209..543653d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ enum ClientMessage { DrawCard, Discard { index: usize }, TrashHand { index: usize }, - HandCardsChosen { choice: Vec }, + 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 }, + 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, @@ -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 => {} } diff --git a/static/game.html b/static/game.html index 3ce056c..498c0c6 100644 --- a/static/game.html +++ b/static/game.html @@ -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);