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