Refactor player command processing.

This commit is contained in:
Markus Wagner 2021-01-27 20:02:39 +01:00
parent c94876c54c
commit 246962ca96
2 changed files with 198 additions and 211 deletions

View File

@ -18,15 +18,7 @@ enum ClientMessage {
Chat { message: String }, Chat { message: String },
CreateGame { name: String }, CreateGame { name: String },
JoinGame { name: String }, JoinGame { name: String },
StartGame, Command { command: Command },
EndTurn,
PlayCard { name: String, index: usize },
GainCard { name: String, index: usize },
BuyCard { index: usize },
DrawCard,
Discard { index: usize },
TrashHand { index: usize },
ResolveReply { reply: ResolveReply },
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -57,7 +49,7 @@ enum ServerMessage {
id: usize, id: usize,
}, },
Notification { Notification {
text: String, event: Event,
}, },
GameOver { GameOver {
score: Vec<(usize, u32)>, 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::copper(), sets::base::copper(),
sets::base::copper(),
sets::base::estate(), sets::base::estate(),
sets::base::estate(), sets::base::estate(),
sets::base::estate(), sets::base::estate(),
@ -227,6 +220,32 @@ enum CardFilter {
type ResolvingEffectHandler = type ResolvingEffectHandler =
fn(&mut Game, &ResolveReply, usize, &ResolveRequest, &mut EffectState); 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 { pub struct Game {
effects: Vec<Effect>, effects: Vec<Effect>,
players: Vec<Player>, players: Vec<Player>,
@ -237,6 +256,7 @@ pub struct Game {
supply: Vec<(Card, usize)>, supply: Vec<(Card, usize)>,
trash: Vec<Card>, trash: Vec<Card>,
turn_state: TurnState, turn_state: TurnState,
debug_mode: bool,
/// 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.
@ -262,6 +282,7 @@ impl Game {
trash: vec![], trash: vec![],
turn_state: TurnState::default(), turn_state: TurnState::default(),
resolving_effect: None, resolving_effect: None,
debug_mode: true,
} }
} }
@ -330,6 +351,10 @@ impl Game {
self.turn_state = TurnState::default(); self.turn_state = TurnState::default();
self.effects.clear(); 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, // 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 { fn is_active_player(&self, player: usize) -> bool {
match self.players.get(self.active_player) { self.active_player == player
None => false,
Some(p) => p.id == *player_id,
}
} }
fn get_active_player(&mut self) -> &mut 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)] #[derive(Clone, Default)]
@ -489,8 +611,8 @@ struct State {
games: Arc<RwLock<HashMap<Uuid, Game>>>, games: Arc<RwLock<HashMap<Uuid, Game>>>,
} }
async fn notify_players(game: &Game, text: String) { async fn notify_players(game: &Game, event: Event) {
let sm = ServerMessage::Notification { text }; let sm = ServerMessage::Notification { event };
broadcast(game, &sm).await; broadcast(game, &sm).await;
} }
@ -773,168 +895,15 @@ async fn main() -> Result<(), std::io::Error> {
broadcast(&game, &sm).await; 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::CreateGame { .. } => {}
ClientMessage::JoinGame { .. } => {} ClientMessage::JoinGame { .. } => {}
ClientMessage::PlayCard { name: _, index } => { ClientMessage::Command { command } => {
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();
let card_name = game.players[player_number].hand[index].name.clone(); game.handle_command(player_number, command);
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 => {}
}
}
broadcast_state(&game).await; broadcast_state(&game).await;
} }

View File

@ -488,6 +488,13 @@ img.card:hover {
document.querySelectorAll(".supply-area .supply-pile")[index].classList.toggle("selected"); 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) { function dialog_confirm(ev) {
if (resolve_request.request.type == "GainCard") { if (resolve_request.request.type == "GainCard") {
var selected = document.querySelector(".supply-pile.selected"); var selected = document.querySelector(".supply-pile.selected");
@ -501,7 +508,7 @@ img.card:hover {
var reply = { type: "SupplyCardChosen", choice: selected }; var reply = { type: "SupplyCardChosen", choice: selected };
var msg = { type: "ResolveReply", reply: reply }; var msg = { type: "ResolveReply", reply: reply };
webSocket.send(JSON.stringify(msg)); send_command("ResolveReply", msg);
enable_supply_selection = false; enable_supply_selection = false;
} else { } else {
var selected = []; var selected = [];
@ -513,7 +520,7 @@ img.card:hover {
var reply = { type: "HandCardsChosen", choice: selected }; var reply = { type: "HandCardsChosen", choice: selected };
var msg = { type: "ResolveReply", reply: reply }; var msg = { type: "ResolveReply", reply: reply };
webSocket.send(JSON.stringify(msg)); send_command("ResolveReply", msg);
enable_hand_selection = false; enable_hand_selection = false;
} }
document.querySelector("#dialog").style.visibility = "hidden"; document.querySelector("#dialog").style.visibility = "hidden";
@ -533,20 +540,15 @@ img.card:hover {
function handle_dnd(data) { function handle_dnd(data) {
if (data.source == "Hand" && data.dest == "InPlay") { if (data.source == "Hand" && data.dest == "InPlay") {
var msg = { type: "PlayCard", name: "", index: data.index}; send_command("PlayCard", { index: data.index });
webSocket.send(JSON.stringify(msg));
} else if (data.source == "Hand" && data.dest == "Discard") { } else if (data.source == "Hand" && data.dest == "Discard") {
var msg = { type: "Discard", index: data.index}; send_command("Discard", { index: data.index });
webSocket.send(JSON.stringify(msg));
} else if (data.source == "Supply" && data.dest == "Discard") { } else if (data.source == "Supply" && data.dest == "Discard") {
var msg = { type: "GainCard", name: data.name, index: parseInt(data.index) }; send_command("GainCard", { index: parseInt(data.index)});
webSocket.send(JSON.stringify(msg));
} else if (data.source == "DrawPile" && data.dest == "Hand") { } else if (data.source == "DrawPile" && data.dest == "Hand") {
var msg = { type: "DrawCard" }; send_command("DrawCard", null);
webSocket.send(JSON.stringify(msg));
} else if (data.source == "Hand" && data.dest == "Trash") { } else if (data.source == "Hand" && data.dest == "Trash") {
var msg = { type: "TrashHand", index: data.index }; send_command("TrashHand", { index: data.index });
webSocket.send(JSON.stringify(msg));
} else { } else {
console.log("handle_dnd: unhandled data", data); console.log("handle_dnd: unhandled data", data);
} }
@ -600,12 +602,7 @@ img.card:hover {
} }
var doubleclick = function(ev) { var doubleclick = function(ev) {
let msg = { send_command("BuyCard", { index: parseInt(ev.srcElement.parentElement.dataset.index) });
type: "BuyCard",
index: parseInt(ev.srcElement.parentElement.dataset.index),
}
webSocket.send(JSON.stringify(msg));
} }
return { return {
@ -856,8 +853,7 @@ img.card:hover {
function PlayerArea(initialVnode) { function PlayerArea(initialVnode) {
var end_turn_click = function(e) { var end_turn_click = function(e) {
var msg = { type: "EndTurn" }; send_command("EndTurn", null);
webSocket.send(JSON.stringify(msg));
} }
return { return {
@ -931,10 +927,9 @@ img.card:hover {
function SetupScreen(initialVnode) { function SetupScreen(initialVnode) {
var start_click = function(e) { var start_click = function(e) {
let msg = { type: "StartGame" }; let msg = { type: "Command", command: { type: "StartGame" }};
initialVnode.attrs.socket.send(JSON.stringify(msg)); initialVnode.attrs.socket.send(JSON.stringify(msg));
} }
return { return {
view: function(vnode) { view: function(vnode) {
@ -1061,12 +1056,6 @@ img.card:hover {
setup_state.active = false; setup_state.active = false;
game_state.active = true; 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) { var handle_resolve_request = function(request) {
@ -1099,6 +1088,44 @@ img.card:hover {
modal.style.display = "block"; modal.style.display = "block";
m.mount(modal, EndScreen); 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) { webSocket.onopen = function(event) {
console.log("ws open"); console.log("ws open");
@ -1114,17 +1141,8 @@ img.card:hover {
chatDiv.append(newmsg); chatDiv.append(newmsg);
newmsg.scrollIntoView(); newmsg.scrollIntoView();
} else if (msg.type == "Notification") { } else if (msg.type == "Notification") {
let chatDiv = document.getElementById("chat"); handle_notification(msg);
let last_element = document.querySelector("#chat li:last-child"); } else if (msg.type == "PlayerJoined") {
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") {
let chatDiv = document.getElementById("chat"); let chatDiv = document.getElementById("chat");
let newmsg = document.createElement("li"); let newmsg = document.createElement("li");
newmsg.innerHTML = msg.player + " joined the game."; newmsg.innerHTML = msg.player + " joined the game.";