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