Add basic player actions
This commit is contained in:
317
src/main.rs
317
src/main.rs
@@ -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(())
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user