Add basic player actions

This commit is contained in:
2021-01-03 16:11:58 +01:00
parent a6659fd538
commit a67aa2c0d9
2 changed files with 580 additions and 130 deletions

View File

@@ -1,13 +1,7 @@
use async_std::{
prelude::*,
sync::RwLock
};
use async_std::{prelude::*, sync::RwLock};
use rand::{seq::SliceRandom, thread_rng};
use std::{
collections::HashMap,
sync::{Arc}
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc};
use tide::{Body, Redirect, Request, Response};
use tide_websockets::{Message, WebSocket, WebSocketConnection};
use uuid::Uuid;
@@ -17,14 +11,16 @@ type Card = String;
#[derive(Deserialize)]
#[serde(tag = "type")]
enum ClientMessage {
Chat {
message: String,
},
CreateGame {
name: String,
},
Chat { message: String },
CreateGame { name: String },
JoinGame { name: String },
StartGame,
EndTurn,
PlayCard { name: String, index: usize },
GainCard { name: String, index: usize },
DrawCard,
Discard { index: usize },
TrashHand { index: usize },
}
#[derive(Serialize)]
@@ -41,6 +37,9 @@ enum ServerMessage {
state: GameState,
players: Vec<PlayerState>,
active_player: usize,
supply: Vec<PileState>,
turn_state: TurnState,
trash: Vec<Card>,
},
GameSetup {
setup: GameSetup,
@@ -48,6 +47,12 @@ enum ServerMessage {
PlayerHand {
hand: Vec<Card>,
},
PlayerId {
id: usize,
},
Notification {
text: String,
},
}
#[derive(Clone, Serialize)]
@@ -55,7 +60,6 @@ struct GameSetup {
deck: Vec<Card>,
}
#[derive(Serialize)]
struct PlayerState {
id: usize,
@@ -63,8 +67,21 @@ struct PlayerState {
draw_pile_count: usize,
hand_count: usize,
discard_pile: Option<Card>,
played_cards: Vec<Card>,
}
#[derive(Serialize)]
struct PileState {
name: String,
count: usize,
}
#[derive(Serialize)]
struct TurnState {
actions: u32,
buys: u32,
coin: u32,
}
struct Player {
id: String,
@@ -72,6 +89,7 @@ struct Player {
draw_pile: Vec<Card>,
hand: Vec<Card>,
discard_pile: Vec<Card>,
played_cards: Vec<Card>,
}
impl Player {
@@ -82,6 +100,7 @@ impl Player {
draw_pile: vec![],
hand: vec![],
discard_pile: vec![],
played_cards: vec![],
}
}
@@ -97,9 +116,17 @@ impl Player {
}
}
}
pub fn play_card(&mut self, index: usize) {
self.played_cards.push(self.hand.remove(index));
}
pub fn discard(&mut self, index: usize) {
self.discard_pile.push(self.hand.remove(index));
}
}
#[derive(Copy, Clone, Serialize)]
#[derive(Copy, Clone, PartialEq, Serialize)]
enum GameState {
Setup,
InProgress,
@@ -108,8 +135,18 @@ enum GameState {
impl Default for GameSetup {
fn default() -> GameSetup {
GameSetup {
deck: vec!["Copper".into(), "Copper".into(), "Copper".into(), "Copper".into(), "Copper".into(),
"Copper".into(), "Copper".into(), "Estate".into(), "Estate".into(), "Estate".into()]
deck: vec![
"Copper".into(),
"Copper".into(),
"Copper".into(),
"Copper".into(),
"Copper".into(),
"Copper".into(),
"Copper".into(),
"Estate".into(),
"Estate".into(),
"Estate".into(),
],
}
}
}
@@ -120,9 +157,10 @@ struct Game {
setup: GameSetup,
connections: HashMap<Uuid, (String, WebSocketConnection)>,
active_player: usize,
supply: Vec<(String, usize)>,
trash: Vec<Card>,
}
impl Game {
fn new(player: Player) -> Self {
Self {
@@ -131,6 +169,8 @@ impl Game {
setup: GameSetup::default(),
connections: HashMap::new(),
active_player: 0,
supply: vec![],
trash: vec![],
}
}
@@ -143,15 +183,37 @@ impl Game {
(*p).discard_pile = self.setup.deck.clone();
p.draw(5);
}
self.supply = vec![
("Copper".into(), 46),
("Silver".into(), 38),
("Gold".into(), 30),
("Estate".into(), 8),
("Duchery".into(), 8),
("Province".into(), 8),
("Curse".into(), 10),
("Cellar".into(), 10),
("Moat".into(), 10),
("Village".into(), 10),
("Merchant".into(), 10),
("Workshop".into(), 10),
("Smithy".into(), 10),
("Remodel".into(), 10),
("Militia".into(), 10),
("Market".into(), 10),
("Mine".into(), 10),
];
}
_ => {} // Ignore if game is not in setup state
}
}
}
fn end_turn(&mut self) {
match self.players.get_mut(self.active_player) {
None => {}
Some(p) => {
p.discard_pile.append(&mut p.played_cards);
p.discard_pile.append(&mut p.hand);
p.draw(5);
}
@@ -164,11 +226,14 @@ 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
}
Some(p) => p.id == *player_id,
}
}
pub fn trash_hand(&mut self, player_number: usize, index: usize) {
self.trash
.push(self.players[player_number].hand.remove(index));
}
}
#[derive(Clone, Default)]
@@ -176,6 +241,11 @@ struct State {
games: Arc<RwLock<HashMap<Uuid, Game>>>,
}
async fn notify_players(game: &Game, text: String) {
let sm = ServerMessage::Notification { text };
broadcast(game, &sm).await;
}
async fn broadcast(game: &Game, sm: &ServerMessage) {
for (_, (_, con)) in game.connections.iter() {
con.send_json(&sm).await.unwrap();
@@ -185,14 +255,34 @@ async fn broadcast(game: &Game, sm: &ServerMessage) {
async fn broadcast_state(game: &Game) {
let sm = ServerMessage::GameState {
state: game.state,
players: game.players.iter().enumerate().map(|(i, p)| PlayerState {
id: i,
name: html_escape::encode_text(&p.name).into(),
draw_pile_count: p.draw_pile.len(),
hand_count: p.hand.len(),
discard_pile: p.discard_pile.last().map(|c| c.clone()),
}).collect(),
players: game
.players
.iter()
.enumerate()
.map(|(i, p)| PlayerState {
id: i,
name: html_escape::encode_text(&p.name).into(),
draw_pile_count: p.draw_pile.len(),
hand_count: p.hand.len(),
discard_pile: p.discard_pile.last().map(|c| c.clone()),
played_cards: p.played_cards.clone(),
})
.collect(),
active_player: game.active_player,
supply: game
.supply
.iter()
.map(|(name, count)| PileState {
name: name.into(),
count: count.clone(),
})
.collect(),
turn_state: TurnState {
actions: 1,
buys: 1,
coin: 0,
},
trash: game.trash.clone(),
};
broadcast(&game, &sm).await;
@@ -214,7 +304,6 @@ async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
}
}
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
tide::log::with_level(tide::log::LevelFilter::Debug);
@@ -222,11 +311,12 @@ async fn main() -> Result<(), std::io::Error> {
let mut app = tide::with_state(State::default());
app.with(tide::sessions::SessionMiddleware::new(
tide::sessions::MemoryStore::new(),
"1234567890abcdef1234567890abcdef".as_bytes(),
));
tide::sessions::MemoryStore::new(),
"1234567890abcdef1234567890abcdef".as_bytes(),
));
app.at("/").get(|_| async { Ok(Body::from_file("static/index.html").await?) });
app.at("/")
.get(|_| async { Ok(Body::from_file("static/index.html").await?) });
app.at("/static").serve_dir("static/")?;
@@ -249,9 +339,7 @@ async fn main() -> Result<(), std::io::Error> {
let res: tide::Result = Ok(Redirect::new(url).into());
res
}
_ => {
Ok(Response::new(400))
}
_ => Ok(Response::new(400)),
}
});
@@ -259,20 +347,47 @@ async fn main() -> Result<(), std::io::Error> {
let games = req.state().games.read().await;
let id = req.param("id")?;
let game_id = Uuid::parse_str(&id).unwrap();
let session = req.session();
match games.get(&game_id) {
None => Ok(Response::new(404)),
Some(_) => {
let mut res = Response::new(200);
res.set_body(Body::from_file("static/game.html").await?);
Ok(res)
None => return Ok(Response::new(404)),
Some(_) => (),
};
match session.get::<String>("player_name") {
None => {
let url = format!("/game/{}/join", id);
return Ok(Redirect::new(url).into());
}
}
Some(_) => (),
};
let mut res = Response::new(200);
res.set_body(Body::from_file("static/game.html").await?);
Ok(res)
});
app.at("/game/:id/join")
.get(|_| async { Ok(Body::from_file("static/join.html").await?) })
.post(|mut req: Request<State>| async move {
let msg: ClientMessage = req.body_form().await?;
let id: String = { req.param("id").unwrap().into() };
let session = req.session_mut();
app.at("/game/:id/ws")
.get(WebSocket::new(|req: Request<State>, mut stream| async move {
match msg {
ClientMessage::JoinGame { name } => {
session.insert("player_name", name.clone())?;
let url = format!("/game/{}", id);
let res: tide::Result = Ok(Redirect::new(url).into());
res
}
_ => Ok(Response::new(500)),
}
});
app.at("/game/:id/ws").get(WebSocket::new(
|req: Request<State>, mut stream| async move {
let id = req.param("id")?;
let game_id = Uuid::parse_str(&id).unwrap();
let mut games = req.state().games.write().await;
@@ -282,17 +397,27 @@ async fn main() -> Result<(), std::io::Error> {
let player_id: String = session.id().clone().into();
let client_id = Uuid::new_v4();
game.connections.insert(client_id, (player_id.clone(), stream.clone()));
game.connections
.insert(client_id, (player_id.clone(), stream.clone()));
let player_name: String = match session.get("player_name") {
Some(p) => p,
None => return Ok(())
None => return Ok(()),
};
if !game.players.iter().any(|p| p.id == player_id) {
game.players.push(Player::new(player_id.clone(), player_name.clone()));
if game.state == GameState::Setup && !game.players.iter().any(|p| p.id == player_id) {
game.players
.push(Player::new(player_id.clone(), player_name.clone()));
}
let player_number = game
.players
.iter()
.enumerate()
.filter(|(_, p)| p.id == player_id)
.fold(0, |_, (i, _)| i);
let msg = ServerMessage::PlayerId { id: player_number };
stream.send_json(&msg).await?;
//Ensure the write locks are freed, possibly better to move to a function
//with implicit drop?
@@ -352,9 +477,93 @@ async fn main() -> Result<(), std::io::Error> {
game.end_turn();
broadcast_state(&game).await;
}
}
}
ClientMessage::CreateGame { .. } => {}
ClientMessage::JoinGame { .. } => {}
ClientMessage::PlayCard { name: _, 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].play_card(index);
notify_players(
&game,
format!("{} spielt {}", 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_name = game.supply[index].0.clone();
game.players[player_number]
.discard_pile
.push(card_name.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;
}
}
}
@@ -364,9 +573,9 @@ async fn main() -> Result<(), std::io::Error> {
game.connections.remove(&client_id);
Ok(())
}));
},
));
app.listen("0.0.0.0:5000").await?;
Ok(())
}