Refactor player command processing.
This commit is contained in:
parent
c94876c54c
commit
246962ca96
313
src/main.rs
313
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<Effect>,
|
||||
players: Vec<Player>,
|
||||
@ -237,6 +256,7 @@ pub struct Game {
|
||||
supply: Vec<(Card, usize)>,
|
||||
trash: Vec<Card>,
|
||||
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<RwLock<HashMap<Uuid, Game>>>,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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,11 +927,10 @@ 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) {
|
||||
var cls = vnode.attrs.active ? "" : "hidden";
|
||||
@ -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) {
|
||||
@ -1100,6 +1089,44 @@ img.card:hover {
|
||||
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.";
|
||||
|
Loading…
Reference in New Issue
Block a user