Initial commit
This commit is contained in:
238
src/main.rs
Normal file
238
src/main.rs
Normal file
@@ -0,0 +1,238 @@
|
||||
extern crate html_escape;
|
||||
|
||||
use async_std::{
|
||||
prelude::*,
|
||||
sync::RwLock
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc}
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum ClientMessage {
|
||||
Chat {
|
||||
message: String,
|
||||
},
|
||||
StartGame,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
enum ServerMessage {
|
||||
Chat {
|
||||
sender: String,
|
||||
message: String,
|
||||
},
|
||||
PlayerJoined {
|
||||
player: String,
|
||||
},
|
||||
}
|
||||
|
||||
struct Player {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Serialize)]
|
||||
enum GameState {
|
||||
Setup,
|
||||
InProgress,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
struct GameSetup {
|
||||
deck: Vec<String>,
|
||||
}
|
||||
|
||||
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()]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Game {
|
||||
players: Vec<Player>,
|
||||
state: GameState,
|
||||
setup: GameSetup,
|
||||
connections: HashMap<Uuid, WebSocketConnection>,
|
||||
}
|
||||
|
||||
|
||||
impl Game {
|
||||
fn new(player: Player) -> Self {
|
||||
Self {
|
||||
players: vec![player],
|
||||
state: GameState::Setup,
|
||||
setup: GameSetup::default(),
|
||||
connections: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct State {
|
||||
games: Arc<RwLock<HashMap<Uuid, Game>>>,
|
||||
}
|
||||
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
tide::log::with_level(tide::log::LevelFilter::Debug);
|
||||
|
||||
let mut app = tide::with_state(State::default());
|
||||
|
||||
app.with(tide::sessions::SessionMiddleware::new(
|
||||
tide::sessions::MemoryStore::new(),
|
||||
"1234567890abcdef1234567890abcdef".as_bytes(),
|
||||
));
|
||||
|
||||
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 session = req.session_mut();
|
||||
|
||||
session.insert("player_name", msg.name.clone()).unwrap();
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let mut games = req.state().games.write().await;
|
||||
let player = Player { name: msg.name };
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
app.at("/game/:id").get(|req: Request<State>| async move {
|
||||
let games = req.state().games.read().await;
|
||||
let id = req.param("id")?;
|
||||
let game_id = Uuid::parse_str(&id).unwrap();
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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;
|
||||
let game = games.get_mut(&game_id).unwrap();
|
||||
let session = req.session();
|
||||
|
||||
let client_id = Uuid::new_v4();
|
||||
game.connections.insert(client_id, stream.clone());
|
||||
|
||||
//Ensure the write locks are freed, possibly better to move to a function
|
||||
//with implicit drop?
|
||||
drop(game);
|
||||
drop(games);
|
||||
|
||||
let games = req.state().games.read().await;
|
||||
let game = games.get(&game_id).unwrap();
|
||||
|
||||
let msg = GameStateMessage {
|
||||
id: "state".into(),
|
||||
state: game.state,
|
||||
};
|
||||
|
||||
stream.send_json(&msg).await?;
|
||||
|
||||
let msg = GameSetupMessage {
|
||||
id: "setup".into(),
|
||||
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(),
|
||||
};
|
||||
|
||||
for (_, con) in game.connections.iter() {
|
||||
con.send_json(&sm).await?;
|
||||
}
|
||||
|
||||
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 sm = ServerMessage::Chat {
|
||||
sender: html_escape::encode_text(&sender).into(),
|
||||
message: html_escape::encode_text(&message).into(),
|
||||
};
|
||||
|
||||
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?;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut games = req.state().games.write().await;
|
||||
let game = games.get_mut(&game_id).unwrap();
|
||||
|
||||
game.connections.remove(&client_id);
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
|
||||
app.listen("0.0.0.0:5000").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user