diff --git a/src/main.rs b/src/main.rs index a2d6d71..47feab0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,15 +18,7 @@ enum ClientMessage { Chat { message: String }, CreateGame { name: String }, JoinGame { name: String }, - StartGame, - EndTurn, - PlayCard { name: String, index: usize }, - GainCard { name: String, index: usize }, - BuyCard { index: usize }, - DrawCard, - Discard { index: usize }, - TrashHand { index: usize }, - ResolveReply { reply: ResolveReply }, + Command { command: Command }, } #[derive(Serialize)] @@ -57,7 +49,7 @@ enum ServerMessage { id: usize, }, Notification { - text: String, + event: Event, }, GameOver { score: Vec<(usize, u32)>, @@ -164,6 +156,7 @@ impl Default for GameSetup { sets::base::copper(), sets::base::copper(), sets::base::copper(), + sets::base::copper(), sets::base::estate(), sets::base::estate(), sets::base::estate(), @@ -227,6 +220,32 @@ enum CardFilter { type ResolvingEffectHandler = fn(&mut Game, &ResolveReply, usize, &ResolveRequest, &mut EffectState); +#[derive(Deserialize)] +#[serde(tag = "type")] +enum Command { + BuyCard { index: usize }, + Discard { index: usize }, + DrawCard, + EndTurn, + GainCard { index: usize }, + PlayCard { index: usize }, + ResolveReply { reply: ResolveReply }, + StartGame, + TrashHand { index: usize }, +} + +#[derive(Debug, Serialize)] +#[serde(tag = "type")] +enum Event { + CardBought { player: usize, name: &'static str }, + CardDrawn { player: usize }, + CardGained { player: usize, index: usize }, + CardDiscarded { player: usize }, + CardTrashed { player: usize, name: &'static str }, + CardPlayed { player: usize, name: &'static str }, + TurnStarted { player: usize }, +} + pub struct Game { effects: Vec, players: Vec, @@ -237,6 +256,7 @@ pub struct Game { supply: Vec<(Card, usize)>, trash: Vec, turn_state: TurnState, + debug_mode: bool, /// Any effect from a card that requires further input from players /// and blocks the game until fully resolved. @@ -262,6 +282,7 @@ impl Game { trash: vec![], turn_state: TurnState::default(), resolving_effect: None, + debug_mode: true, } } @@ -330,6 +351,10 @@ impl Game { self.turn_state = TurnState::default(); self.effects.clear(); + + self.emit(Event::TurnStarted { + player: self.active_player, + }); } // Check if the end game condition is reached and finish the game if so, @@ -352,11 +377,8 @@ impl Game { } } - fn is_active_player(&self, player_id: &str) -> bool { - match self.players.get(self.active_player) { - None => false, - Some(p) => p.id == *player_id, - } + fn is_active_player(&self, player: usize) -> bool { + self.active_player == player } fn get_active_player(&mut self) -> &mut Player { @@ -482,6 +504,106 @@ impl Game { } } } + + fn handle_command(&mut self, player: usize, command: Command) { + match command { + Command::DrawCard => { + if !self.debug_mode { + return; + } + + self.players[player].draw(1); + self.emit(Event::CardDrawn { player }); + } + + Command::BuyCard { index } => { + if self.buy_card(player, index) { + self.emit(Event::CardBought { + player, + name: self.supply[index].0.name, + }); + } + } + + Command::EndTurn => { + if self.is_active_player(player) { + self.end_turn(); + } + } + + Command::GainCard { index } => { + if !self.debug_mode { + return; + } + + self.supply.get_mut(index).unwrap().1 = self.supply.get(index).unwrap().1 - 1; + let card = self.supply[index].0.clone(); + + self.players[player].discard_pile.push(card.clone()); + self.emit(Event::CardGained { player, index }) + } + + Command::Discard { index } => { + if !self.debug_mode { + return; + } + + //let card_name = self.players[player].hand[index].clone(); + self.players[player].discard(index); + self.emit(Event::CardDiscarded { player }); + } + + Command::StartGame => { + self.start(); + } + + Command::TrashHand { index } => { + let name = self.players[player].hand[index].name.clone(); + self.trash_hand(player, index); + self.emit(Event::CardTrashed { player, name }); + } + + Command::PlayCard { index } => { + let name = self.players[player].hand[index].name.clone(); + if self.play_card(player, index) { + self.emit(Event::CardPlayed { player, name }); + } + } + + Command::ResolveReply { reply } => { + if let Some((card, request, effect, resolve_player, mut state)) = + self.resolving_effect.take() + { + match resolve_player { + ResolvingPlayer::ActivePlayer => { + if player == self.active_player { + effect(self, &reply, self.active_player, &request, &mut state); + } + } + + ResolvingPlayer::AllNonActivePlayers => { + if player != self.active_player { + effect(self, &reply, player, &request, &mut state); + } + } + } + + match state.resolved { + false => { + self.resolving_effect = + Some((card, request, effect, resolve_player, state)); + } + true => {} + } + } + } + } + } + + fn emit(&mut self, event: Event) { + println!("Emitting event: {:?}", event); + async_std::task::block_on(notify_players(self, event)); + } } #[derive(Clone, Default)] @@ -489,8 +611,8 @@ struct State { games: Arc>>, } -async fn notify_players(game: &Game, text: String) { - let sm = ServerMessage::Notification { text }; +async fn notify_players(game: &Game, event: Event) { + let sm = ServerMessage::Notification { event }; broadcast(game, &sm).await; } @@ -773,168 +895,15 @@ async fn main() -> Result<(), std::io::Error> { broadcast(&game, &sm).await; } - ClientMessage::StartGame => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - game.start(); - - broadcast_state(&game).await; - } - - ClientMessage::EndTurn => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - if game.is_active_player(&player_id) { - game.end_turn(); - broadcast_state(&game).await; - } - } - ClientMessage::CreateGame { .. } => {} ClientMessage::JoinGame { .. } => {} - ClientMessage::PlayCard { name: _, index } => { + ClientMessage::Command { command } => { let mut games = req.state().games.write().await; let game = games.get_mut(&game_id).unwrap(); - let card_name = game.players[player_number].hand[index].name.clone(); - if game.play_card(player_number, index) { - notify_players( - &game, - format!( - "{} spielt {}", - game.players[player_number].name, card_name - ), - ) - .await; - broadcast_state(&game).await; - } - } - - ClientMessage::BuyCard { index } => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - if game.buy_card(player_number, index) { - if let Some(card) = - game.players[player_number].discard_pile.last().as_deref() - { - notify_players( - &game, - format!( - "{} kauft {}", - game.players[player_number].name, card.name - ), - ) - .await; - broadcast_state(&game).await; - } - } - } - - ClientMessage::GainCard { name: _, index } => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - game.supply.get_mut(index).unwrap().1 = - game.supply.get(index).unwrap().1 - 1; - let card = game.supply[index].0.clone(); - - game.players[player_number].discard_pile.push(card.clone()); - - notify_players( - &game, - format!("{} nimmt {}", game.players[player_number].name, card.name), - ) - .await; - broadcast_state(&game).await; - } - - ClientMessage::DrawCard => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - game.players[player_number].draw(1); - - notify_players( - &game, - format!("{} zieht eine Karte", game.players[player_number].name), - ) - .await; - broadcast_state(&game).await; - } - - ClientMessage::Discard { index } => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - let card_name = game.players[player_number].hand[index].clone(); - game.players[player_number].discard(index); - - notify_players( - &game, - format!("{} legt {} ab", game.players[player_number].name, card_name), - ) - .await; - broadcast_state(&game).await; - } - - ClientMessage::TrashHand { index } => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - let card_name = game.players[player_number].hand[index].clone(); - game.trash_hand(player_number, index); - - notify_players( - &game, - format!( - "{} entsorgt {}", - game.players[player_number].name, card_name - ), - ) - .await; - broadcast_state(&game).await; - } - - ClientMessage::ResolveReply { reply } => { - let mut games = req.state().games.write().await; - let game = games.get_mut(&game_id).unwrap(); - - if let Some((card, request, effect, player, mut state)) = - game.resolving_effect.take() - { - match player { - ResolvingPlayer::ActivePlayer => { - if player_number == game.active_player { - effect( - game, - &reply, - game.active_player, - &request, - &mut state, - ); - } - } - - ResolvingPlayer::AllNonActivePlayers => { - if player_number != game.active_player { - effect(game, &reply, player_number, &request, &mut state); - } - } - } - - match state.resolved { - false => { - game.resolving_effect = - Some((card, request, effect, player, state)); - } - true => {} - } - } + game.handle_command(player_number, command); broadcast_state(&game).await; } diff --git a/static/game.html b/static/game.html index 22a6cd1..49c6633 100644 --- a/static/game.html +++ b/static/game.html @@ -488,6 +488,13 @@ img.card:hover { document.querySelectorAll(".supply-area .supply-pile")[index].classList.toggle("selected"); } + function send_command(command, data) { + var payload = data || {}; + payload.type = command; + var msg = { type: "Command", "command": payload }; + webSocket.send(JSON.stringify(msg)); + } + function dialog_confirm(ev) { if (resolve_request.request.type == "GainCard") { var selected = document.querySelector(".supply-pile.selected"); @@ -501,7 +508,7 @@ img.card:hover { var reply = { type: "SupplyCardChosen", choice: selected }; var msg = { type: "ResolveReply", reply: reply }; - webSocket.send(JSON.stringify(msg)); + send_command("ResolveReply", msg); enable_supply_selection = false; } else { var selected = []; @@ -513,7 +520,7 @@ img.card:hover { var reply = { type: "HandCardsChosen", choice: selected }; var msg = { type: "ResolveReply", reply: reply }; - webSocket.send(JSON.stringify(msg)); + send_command("ResolveReply", msg); enable_hand_selection = false; } document.querySelector("#dialog").style.visibility = "hidden"; @@ -533,20 +540,15 @@ img.card:hover { function handle_dnd(data) { if (data.source == "Hand" && data.dest == "InPlay") { - var msg = { type: "PlayCard", name: "", index: data.index}; - webSocket.send(JSON.stringify(msg)); + send_command("PlayCard", { index: data.index }); } else if (data.source == "Hand" && data.dest == "Discard") { - var msg = { type: "Discard", index: data.index}; - webSocket.send(JSON.stringify(msg)); + send_command("Discard", { index: data.index }); } else if (data.source == "Supply" && data.dest == "Discard") { - var msg = { type: "GainCard", name: data.name, index: parseInt(data.index) }; - webSocket.send(JSON.stringify(msg)); + send_command("GainCard", { index: parseInt(data.index)}); } else if (data.source == "DrawPile" && data.dest == "Hand") { - var msg = { type: "DrawCard" }; - webSocket.send(JSON.stringify(msg)); + send_command("DrawCard", null); } else if (data.source == "Hand" && data.dest == "Trash") { - var msg = { type: "TrashHand", index: data.index }; - webSocket.send(JSON.stringify(msg)); + send_command("TrashHand", { index: data.index }); } else { console.log("handle_dnd: unhandled data", data); } @@ -600,12 +602,7 @@ img.card:hover { } var doubleclick = function(ev) { - let msg = { - type: "BuyCard", - index: parseInt(ev.srcElement.parentElement.dataset.index), - } - - webSocket.send(JSON.stringify(msg)); + send_command("BuyCard", { index: parseInt(ev.srcElement.parentElement.dataset.index) }); } return { @@ -856,8 +853,7 @@ img.card:hover { function PlayerArea(initialVnode) { var end_turn_click = function(e) { - var msg = { type: "EndTurn" }; - webSocket.send(JSON.stringify(msg)); + send_command("EndTurn", null); } return { @@ -931,10 +927,9 @@ img.card:hover { function SetupScreen(initialVnode) { var start_click = function(e) { - let msg = { type: "StartGame" }; + let msg = { type: "Command", command: { type: "StartGame" }}; initialVnode.attrs.socket.send(JSON.stringify(msg)); } - return { view: function(vnode) { @@ -1061,12 +1056,6 @@ img.card:hover { setup_state.active = false; game_state.active = true; } - - if (last_player != game_state.active_player) { - if (game_state.active_player == my_player_id) { - turnStartSound.play(); - } - } } var handle_resolve_request = function(request) { @@ -1099,6 +1088,44 @@ img.card:hover { modal.style.display = "block"; m.mount(modal, EndScreen); } + + var handle_notification = function(msg) { + if (msg.event.type == "CardPlayed") { + append_chat(player_name(msg.event.player) + " plays " + msg.event.name); + } else if (msg.event.type == "CardBought") { + append_chat(player_name(msg.event.player) + " buys " + msg.event.name); + } else if (msg.event.type == "CardGained") { + let card_name = game_state.supply[msg.event.index].name; + append_chat(player_name(msg.event.player) + " gains " + card_name); + } else if (msg.event.type == "CardDiscarded") { + append_chat(player_name(msg.event.player) + " discards a card."); + } else if (msg.event.type == "CardTrashed") { + append_chat(player_name(msg.event.player) + " trashes " + msg.event.name); + } else if (msg.event.type == "TurnStarted") { + if (msg.event.player == my_player_id) { + turnStartSound.play(); + } + } else { + console.log(msg); + } + } + + var player_name = function(index) { + return game_state.players[index].name; + } + + var append_chat = function(text) { + let chatDiv = document.getElementById("chat"); + let last_element = document.querySelector("#chat li:last-child"); + if (last_element.innerText == text) { + last_element.dataset.repeat = (parseInt(last_element.dataset.repeat || 1)) + 1; + } else { + let newmsg = document.createElement("li"); + newmsg.innerHTML = text; + chatDiv.append(newmsg); + newmsg.scrollIntoView(); + } + } webSocket.onopen = function(event) { console.log("ws open"); @@ -1114,17 +1141,8 @@ img.card:hover { chatDiv.append(newmsg); newmsg.scrollIntoView(); } else if (msg.type == "Notification") { - let chatDiv = document.getElementById("chat"); - let last_element = document.querySelector("#chat li:last-child"); - if (last_element.innerText == msg.text) { - last_element.dataset.repeat = (parseInt(last_element.dataset.repeat || 1)) + 1; - } else { - let newmsg = document.createElement("li"); - newmsg.innerHTML = msg.text; - chatDiv.append(newmsg); - newmsg.scrollIntoView(); - } - } else if (msg.type == "PlayerJoined") { + handle_notification(msg); + } else if (msg.type == "PlayerJoined") { let chatDiv = document.getElementById("chat"); let newmsg = document.createElement("li"); newmsg.innerHTML = msg.player + " joined the game.";