Add workshop and remodel effects
This commit is contained in:
parent
d6e2543b9c
commit
f2dab8a71c
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 = [];
|
||||
if (resolve_request.request.type == "GainCard") {
|
||||
var selected = document.querySelector(".supply-pile.selected");
|
||||
if (selected) {
|
||||
selected = parseInt(selected.dataset.index);
|
||||
}
|
||||
|
||||
document.querySelectorAll(".player-hand img.card.selected").forEach((c) => {
|
||||
selected.push(parseInt(c.dataset.index));
|
||||
c.classList.remove("selected");
|
||||
});
|
||||
document.querySelectorAll(".supply-pile.selected").forEach((c) => {
|
||||
c.classList.remove("selected");
|
||||
});
|
||||
|
||||
var msg = { type: "HandCardsChosen", choice: selected };
|
||||
webSocket.send(JSON.stringify(msg));
|
||||
enable_hand_selection = false;
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user