Basic turn structure

This commit is contained in:
2020-12-31 16:54:38 +01:00
parent bb8162e4c1
commit a6659fd538
5 changed files with 365 additions and 97 deletions

View File

@@ -1,9 +1,8 @@
extern crate html_escape;
use async_std::{
prelude::*,
sync::RwLock
};
use rand::{seq::SliceRandom, thread_rng};
use std::{
collections::HashMap,
sync::{Arc}
@@ -13,22 +12,7 @@ use tide::{Body, Redirect, Request, Response};
use tide_websockets::{Message, WebSocket, WebSocketConnection};
use uuid::Uuid;
#[derive(Deserialize, Clone)]
struct CreateGameMessage {
name: String,
}
#[derive(Serialize)]
struct GameStateMessage {
id: String,
state: GameState,
}
#[derive(Serialize)]
struct GameSetupMessage {
id: String,
setup: GameSetup,
}
type Card = String;
#[derive(Deserialize)]
#[serde(tag = "type")]
@@ -36,7 +20,11 @@ enum ClientMessage {
Chat {
message: String,
},
CreateGame {
name: String,
},
StartGame,
EndTurn,
}
#[derive(Serialize)]
@@ -49,10 +37,66 @@ enum ServerMessage {
PlayerJoined {
player: String,
},
GameState {
state: GameState,
players: Vec<PlayerState>,
active_player: usize,
},
GameSetup {
setup: GameSetup,
},
PlayerHand {
hand: Vec<Card>,
},
}
struct Player {
#[derive(Clone, Serialize)]
struct GameSetup {
deck: Vec<Card>,
}
#[derive(Serialize)]
struct PlayerState {
id: usize,
name: String,
draw_pile_count: usize,
hand_count: usize,
discard_pile: Option<Card>,
}
struct Player {
id: String,
name: String,
draw_pile: Vec<Card>,
hand: Vec<Card>,
discard_pile: Vec<Card>,
}
impl Player {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
draw_pile: vec![],
hand: vec![],
discard_pile: vec![],
}
}
pub fn draw(&mut self, count: usize) {
for _ in 0..count {
if self.draw_pile.len() == 0 {
self.draw_pile.append(&mut self.discard_pile);
self.draw_pile.shuffle(&mut thread_rng());
}
if self.draw_pile.len() > 0 {
self.hand.push(self.draw_pile.pop().unwrap());
}
}
}
}
#[derive(Copy, Clone, Serialize)]
@@ -61,11 +105,6 @@ enum GameState {
InProgress,
}
#[derive(Clone, Serialize)]
struct GameSetup {
deck: Vec<String>,
}
impl Default for GameSetup {
fn default() -> GameSetup {
GameSetup {
@@ -79,7 +118,8 @@ struct Game {
players: Vec<Player>,
state: GameState,
setup: GameSetup,
connections: HashMap<Uuid, WebSocketConnection>,
connections: HashMap<Uuid, (String, WebSocketConnection)>,
active_player: usize,
}
@@ -90,6 +130,43 @@ impl Game {
state: GameState::Setup,
setup: GameSetup::default(),
connections: HashMap::new(),
active_player: 0,
}
}
fn start(&mut self) {
match self.state {
GameState::Setup => {
self.state = GameState::InProgress;
for p in self.players.iter_mut() {
(*p).discard_pile = self.setup.deck.clone();
p.draw(5);
}
}
_ => {} // 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.hand);
p.draw(5);
}
}
self.active_player += 1;
self.active_player %= self.players.len();
}
fn is_active_player(&self, player_id: &str) -> bool {
match self.players.get(self.active_player) {
None => false,
Some(p) => {
p.id == *player_id
}
}
}
}
@@ -99,6 +176,44 @@ struct State {
games: Arc<RwLock<HashMap<Uuid, Game>>>,
}
async fn broadcast(game: &Game, sm: &ServerMessage) {
for (_, (_, con)) in game.connections.iter() {
con.send_json(&sm).await.unwrap();
}
}
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(),
active_player: game.active_player,
};
broadcast(&game, &sm).await;
for p in game.players.iter() {
let sm = ServerMessage::PlayerHand {
hand: p.hand.clone(),
};
send_msg(&game, &p, &sm).await;
}
}
async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
for (_, (player_id, con)) in game.connections.iter() {
if *player_id == (*player).id {
con.send_json(&sm).await.unwrap();
}
}
}
#[async_std::main]
async fn main() -> Result<(), std::io::Error> {
@@ -112,23 +227,32 @@ async fn main() -> Result<(), std::io::Error> {
));
app.at("/").get(|_| async { Ok(Body::from_file("static/index.html").await?) });
app.at("/static").serve_dir("static/")?;
app.at("/game").post(|mut req: Request<State>| async move {
let msg: CreateGameMessage = req.body_form().await?;
let msg: ClientMessage = req.body_form().await?;
let session = req.session_mut();
session.insert("player_name", msg.name.clone()).unwrap();
match msg {
ClientMessage::CreateGame { name } => {
session.insert("player_name", name.clone())?;
let player_id: String = session.id().clone().into();
let id = Uuid::new_v4();
let mut games = req.state().games.write().await;
let player = Player { name: msg.name };
let id = Uuid::new_v4();
let mut games = req.state().games.write().await;
let player = Player::new(player_id, name);
games.insert(id, Game::new(player));
games.insert(id, Game::new(player));
let url = format!("/game/{}", id.to_simple().to_string());
let res: tide::Result = Ok(Redirect::new(url).into());
res
let url = format!("/game/{}", id.to_simple().to_string());
let res: tide::Result = Ok(Redirect::new(url).into());
res
}
_ => {
Ok(Response::new(400))
}
}
});
app.at("/game/:id").get(|req: Request<State>| async move {
@@ -155,8 +279,20 @@ async fn main() -> Result<(), std::io::Error> {
let game = games.get_mut(&game_id).unwrap();
let session = req.session();
let player_id: String = session.id().clone().into();
let client_id = Uuid::new_v4();
game.connections.insert(client_id, 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(())
};
if !game.players.iter().any(|p| p.id == player_id) {
game.players.push(Player::new(player_id.clone(), player_name.clone()));
}
//Ensure the write locks are freed, possibly better to move to a function
//with implicit drop?
@@ -166,37 +302,28 @@ async fn main() -> Result<(), std::io::Error> {
let games = req.state().games.read().await;
let game = games.get(&game_id).unwrap();
let msg = GameStateMessage {
id: "state".into(),
state: game.state,
};
broadcast_state(&game).await;
stream.send_json(&msg).await?;
let msg = GameSetupMessage {
id: "setup".into(),
let msg = ServerMessage::GameSetup {
setup: game.setup.clone(),
};
stream.send_json(&msg).await?;
let player: String = session.get("player_name").unwrap_or("SOMEONE".into());
let sm = ServerMessage::PlayerJoined {
player: html_escape::encode_text(&player).into(),
player: html_escape::encode_text(&player_name).into(),
};
for (_, con) in game.connections.iter() {
con.send_json(&sm).await?;
}
broadcast(&game, &sm).await;
drop(game); drop(games);
drop(game);
drop(games);
while let Some(Ok(Message::Text(input))) = stream.next().await {
let msg: ClientMessage = serde_json::from_str(&input)?;
match msg {
ClientMessage::Chat { message } => {
let sender: String = session.get("player_name").unwrap_or("SOMEONE".into());
let sender: String = session.get("player_name").unwrap();
let sm = ServerMessage::Chat {
sender: html_escape::encode_text(&sender).into(),
message: html_escape::encode_text(&message).into(),
@@ -205,22 +332,29 @@ async fn main() -> Result<(), std::io::Error> {
let games = req.state().games.read().await;
let game = games.get(&game_id).unwrap();
for (_, con) in game.connections.iter() {
con.send_json(&sm).await?;
}
broadcast(&game, &sm).await;
}
ClientMessage::StartGame => {
let mut games = req.state().games.write().await;
let game = games.get_mut(&game_id).unwrap();
match game.state {
GameState::Setup => {
game.state = GameState::InProgress;
}
_ => {} //Ignore, if game not in setup state
}
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 { .. } => {}
}
}