Basic turn structure
This commit is contained in:
parent
bb8162e4c1
commit
a6659fd538
76
Cargo.lock
generated
76
Cargo.lock
generated
@ -208,7 +208,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"hmac 0.8.1",
|
"hmac 0.8.1",
|
||||||
"kv-log-macro",
|
"kv-log-macro",
|
||||||
"rand",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
@ -473,7 +473,7 @@ dependencies = [
|
|||||||
"hkdf",
|
"hkdf",
|
||||||
"hmac 0.10.1",
|
"hmac 0.10.1",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rand",
|
"rand 0.7.3",
|
||||||
"sha2",
|
"sha2",
|
||||||
"time 0.2.23",
|
"time 0.2.23",
|
||||||
"version_check",
|
"version_check",
|
||||||
@ -580,6 +580,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"html-escape",
|
"html-escape",
|
||||||
|
"rand 0.8.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tide",
|
"tide",
|
||||||
@ -735,6 +736,17 @@ dependencies = [
|
|||||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghash"
|
name = "ghash"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -841,7 +853,7 @@ dependencies = [
|
|||||||
"futures-lite",
|
"futures-lite",
|
||||||
"infer",
|
"infer",
|
||||||
"pin-project-lite 0.1.11",
|
"pin-project-lite 0.1.11",
|
||||||
"rand",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_qs",
|
"serde_qs",
|
||||||
@ -1119,11 +1131,23 @@ version = "0.7.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.1.15",
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.2.2",
|
||||||
"rand_core",
|
"rand_core 0.5.1",
|
||||||
"rand_hc",
|
"rand_hc 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha 0.3.0",
|
||||||
|
"rand_core 0.6.0",
|
||||||
|
"rand_hc 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1133,7 +1157,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core",
|
"rand_core 0.5.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1142,7 +1176,16 @@ version = "0.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.1.15",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8b34ba8cfb21243bd8df91854c830ff0d785fff2e82ebd4434c2644cb9ada18"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1151,7 +1194,16 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_core",
|
"rand_core 0.5.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1530,7 +1582,7 @@ dependencies = [
|
|||||||
"httparse",
|
"httparse",
|
||||||
"input_buffer",
|
"input_buffer",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand 0.7.3",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
"url",
|
"url",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
@ -1607,7 +1659,7 @@ version = "0.8.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
|
checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand",
|
"rand 0.7.3",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
async-std = { version = "1.8.0", features = ["attributes"] }
|
async-std = { version = "1.8.0", features = ["attributes"] }
|
||||||
html-escape = "0.2.6"
|
html-escape = "0.2.6"
|
||||||
|
rand = "0.8.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0.60"
|
serde_json = "1.0.60"
|
||||||
tide = "0.15.0"
|
tide = "0.15.0"
|
||||||
|
254
src/main.rs
254
src/main.rs
@ -1,9 +1,8 @@
|
|||||||
extern crate html_escape;
|
|
||||||
|
|
||||||
use async_std::{
|
use async_std::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sync::RwLock
|
sync::RwLock
|
||||||
};
|
};
|
||||||
|
use rand::{seq::SliceRandom, thread_rng};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{Arc}
|
sync::{Arc}
|
||||||
@ -13,22 +12,7 @@ use tide::{Body, Redirect, Request, Response};
|
|||||||
use tide_websockets::{Message, WebSocket, WebSocketConnection};
|
use tide_websockets::{Message, WebSocket, WebSocketConnection};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
type Card = String;
|
||||||
struct CreateGameMessage {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct GameStateMessage {
|
|
||||||
id: String,
|
|
||||||
state: GameState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct GameSetupMessage {
|
|
||||||
id: String,
|
|
||||||
setup: GameSetup,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@ -36,7 +20,11 @@ enum ClientMessage {
|
|||||||
Chat {
|
Chat {
|
||||||
message: String,
|
message: String,
|
||||||
},
|
},
|
||||||
|
CreateGame {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
StartGame,
|
StartGame,
|
||||||
|
EndTurn,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -49,10 +37,66 @@ enum ServerMessage {
|
|||||||
PlayerJoined {
|
PlayerJoined {
|
||||||
player: String,
|
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,
|
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)]
|
#[derive(Copy, Clone, Serialize)]
|
||||||
@ -61,11 +105,6 @@ enum GameState {
|
|||||||
InProgress,
|
InProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
|
||||||
struct GameSetup {
|
|
||||||
deck: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for GameSetup {
|
impl Default for GameSetup {
|
||||||
fn default() -> GameSetup {
|
fn default() -> GameSetup {
|
||||||
GameSetup {
|
GameSetup {
|
||||||
@ -79,7 +118,8 @@ struct Game {
|
|||||||
players: Vec<Player>,
|
players: Vec<Player>,
|
||||||
state: GameState,
|
state: GameState,
|
||||||
setup: GameSetup,
|
setup: GameSetup,
|
||||||
connections: HashMap<Uuid, WebSocketConnection>,
|
connections: HashMap<Uuid, (String, WebSocketConnection)>,
|
||||||
|
active_player: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -90,6 +130,43 @@ impl Game {
|
|||||||
state: GameState::Setup,
|
state: GameState::Setup,
|
||||||
setup: GameSetup::default(),
|
setup: GameSetup::default(),
|
||||||
connections: HashMap::new(),
|
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>>>,
|
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_std::main]
|
||||||
async fn main() -> Result<(), std::io::Error> {
|
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("/").get(|_| async { Ok(Body::from_file("static/index.html").await?) });
|
||||||
|
|
||||||
app.at("/static").serve_dir("static/")?;
|
app.at("/static").serve_dir("static/")?;
|
||||||
|
|
||||||
app.at("/game").post(|mut req: Request<State>| async move {
|
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();
|
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 id = Uuid::new_v4();
|
||||||
let mut games = req.state().games.write().await;
|
let mut games = req.state().games.write().await;
|
||||||
let player = Player { name: msg.name };
|
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 url = format!("/game/{}", id.to_simple().to_string());
|
||||||
let res: tide::Result = Ok(Redirect::new(url).into());
|
let res: tide::Result = Ok(Redirect::new(url).into());
|
||||||
res
|
res
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Ok(Response::new(400))
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.at("/game/:id").get(|req: Request<State>| async move {
|
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 game = games.get_mut(&game_id).unwrap();
|
||||||
let session = req.session();
|
let session = req.session();
|
||||||
|
|
||||||
|
let player_id: String = session.id().clone().into();
|
||||||
|
|
||||||
let client_id = Uuid::new_v4();
|
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
|
//Ensure the write locks are freed, possibly better to move to a function
|
||||||
//with implicit drop?
|
//with implicit drop?
|
||||||
@ -166,37 +302,28 @@ async fn main() -> Result<(), std::io::Error> {
|
|||||||
let games = req.state().games.read().await;
|
let games = req.state().games.read().await;
|
||||||
let game = games.get(&game_id).unwrap();
|
let game = games.get(&game_id).unwrap();
|
||||||
|
|
||||||
let msg = GameStateMessage {
|
broadcast_state(&game).await;
|
||||||
id: "state".into(),
|
|
||||||
state: game.state,
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.send_json(&msg).await?;
|
let msg = ServerMessage::GameSetup {
|
||||||
|
|
||||||
let msg = GameSetupMessage {
|
|
||||||
id: "setup".into(),
|
|
||||||
setup: game.setup.clone(),
|
setup: game.setup.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
stream.send_json(&msg).await?;
|
stream.send_json(&msg).await?;
|
||||||
|
|
||||||
let player: String = session.get("player_name").unwrap_or("SOMEONE".into());
|
|
||||||
let sm = ServerMessage::PlayerJoined {
|
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() {
|
broadcast(&game, &sm).await;
|
||||||
con.send_json(&sm).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(game); drop(games);
|
drop(game);
|
||||||
|
drop(games);
|
||||||
|
|
||||||
while let Some(Ok(Message::Text(input))) = stream.next().await {
|
while let Some(Ok(Message::Text(input))) = stream.next().await {
|
||||||
let msg: ClientMessage = serde_json::from_str(&input)?;
|
let msg: ClientMessage = serde_json::from_str(&input)?;
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
ClientMessage::Chat { message } => {
|
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 {
|
let sm = ServerMessage::Chat {
|
||||||
sender: html_escape::encode_text(&sender).into(),
|
sender: html_escape::encode_text(&sender).into(),
|
||||||
message: html_escape::encode_text(&message).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 games = req.state().games.read().await;
|
||||||
let game = games.get(&game_id).unwrap();
|
let game = games.get(&game_id).unwrap();
|
||||||
|
|
||||||
for (_, con) in game.connections.iter() {
|
broadcast(&game, &sm).await;
|
||||||
con.send_json(&sm).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientMessage::StartGame => {
|
ClientMessage::StartGame => {
|
||||||
let mut games = req.state().games.write().await;
|
let mut games = req.state().games.write().await;
|
||||||
let game = games.get_mut(&game_id).unwrap();
|
let game = games.get_mut(&game_id).unwrap();
|
||||||
|
|
||||||
match game.state {
|
game.start();
|
||||||
GameState::Setup => {
|
|
||||||
game.state = GameState::InProgress;
|
broadcast_state(&game).await;
|
||||||
}
|
|
||||||
_ => {} //Ignore, if game not in setup state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
130
static/game.html
130
static/game.html
@ -23,18 +23,25 @@
|
|||||||
|
|
||||||
.player-area {
|
.player-area {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100px auto 100px;
|
grid-template-columns: 100px auto 90px;
|
||||||
border: 1px solid black;
|
grid-template-rows: auto auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 145px;
|
height: 145px;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-area .buttons {
|
||||||
|
grid-column-start: 3;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 90px;
|
width: 90px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
|
height: 144px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.player-hand {
|
.player-hand {
|
||||||
@ -45,6 +52,10 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.player-hand img.card:hover {
|
||||||
|
box-shadow: 0 0 5px blue;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-window {
|
.chat-window {
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
@ -89,14 +100,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.setup-screen {
|
.setup-screen {
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discard-pile {
|
.discard-pile {
|
||||||
width: 90px;
|
width: 90px;
|
||||||
border: 1px dotted green;
|
border: 1px dotted green;
|
||||||
height: 145px;
|
height: 144px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discard-pile img.card {
|
||||||
|
margin-top: -1px;
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discard-pile::after {
|
||||||
|
color: dimgray;
|
||||||
|
content: "Discard Pile";
|
||||||
|
font-size: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inplay-area {
|
.inplay-area {
|
||||||
@ -136,7 +168,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.opponent-area {
|
.opponent-area {
|
||||||
height: 180px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +179,7 @@
|
|||||||
border: 1px solid saddlebrown;
|
border: 1px solid saddlebrown;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 2px;
|
grid-gap: 2px;
|
||||||
|
padding: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opponent-status.active {
|
.opponent-status.active {
|
||||||
@ -206,7 +238,9 @@
|
|||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".chat-window",
|
return m(".chat-window",
|
||||||
m("h4", "Players"),
|
m("h4", "Players"),
|
||||||
m("ul", m("li", "Markus (you)")),
|
m("ul", vnode.attrs.players.map(function(p) {
|
||||||
|
return m("li", p.name);
|
||||||
|
})),
|
||||||
m("h4", "Messages"),
|
m("h4", "Messages"),
|
||||||
m("ul", {id: "chat"}),
|
m("ul", {id: "chat"}),
|
||||||
m("input", {
|
m("input", {
|
||||||
@ -248,7 +282,11 @@
|
|||||||
function DiscardPile(initialVnode) {
|
function DiscardPile(initialVnode) {
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".discard-pile", "Discard Pile")
|
var c;
|
||||||
|
if (vnode.attrs.card) {
|
||||||
|
c = m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.card.toLowerCase() + ".jpg"});
|
||||||
|
}
|
||||||
|
return m(".discard-pile", c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,12 +342,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PlayerArea(initialVnode) {
|
function PlayerArea(initialVnode) {
|
||||||
|
var end_turn_click = function(e) {
|
||||||
|
var msg = { type: "EndTurn" };
|
||||||
|
webSocket.send(JSON.stringify(msg));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
|
var local_player = vnode.attrs.players[my_player_id];
|
||||||
return m(".player-area",
|
return m(".player-area",
|
||||||
m(DrawPile, {count: vnode.attrs.player_drawpile_count}),
|
m(DrawPile, {count: local_player.draw_pile_count}),
|
||||||
m(PlayerHand, vnode.attrs.player),
|
m(PlayerHand, vnode.attrs.player),
|
||||||
m(DiscardPile)
|
m(DiscardPile, {card: local_player.discard_pile}),
|
||||||
|
m(".buttons",
|
||||||
|
m("button", {onclick: end_turn_click}, "Pass turn")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,10 +365,10 @@
|
|||||||
function OpponentStatus(initialVnode) {
|
function OpponentStatus(initialVnode) {
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
var active = vnode.attrs.active ? "active" : "";
|
var active = vnode.attrs.id == game_state.active_player ? "active" : "";
|
||||||
return m(".opponent-status", {class: active },
|
return m(".opponent-status", {class: active },
|
||||||
m(".name", vnode.attrs.name),
|
m(".name", vnode.attrs.name),
|
||||||
m(DiscardPile),
|
m(DiscardPile, {card: vnode.attrs.discard_pile}),
|
||||||
m(DrawPile, {count: vnode.attrs.draw_pile_count}),
|
m(DrawPile, {count: vnode.attrs.draw_pile_count}),
|
||||||
m(OpponentHand, {count: vnode.attrs.hand_count })
|
m(OpponentHand, {count: vnode.attrs.hand_count })
|
||||||
)
|
)
|
||||||
@ -333,8 +380,10 @@
|
|||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".opponent-area",
|
return m(".opponent-area",
|
||||||
vnode.attrs.opponents.map(function(o) {
|
vnode.attrs.players
|
||||||
return m(OpponentStatus, o)
|
.filter((p, i) => i != my_player_id)
|
||||||
|
.map(function(o) {
|
||||||
|
return m(OpponentStatus, o)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -344,7 +393,8 @@
|
|||||||
function GameScreen(initialVnode) {
|
function GameScreen(initialVnode) {
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".game-screen",
|
var cls = vnode.attrs.active ? "" : "hidden";
|
||||||
|
return m(".game-screen", {class: cls},
|
||||||
m(OpponentArea, vnode.attrs),
|
m(OpponentArea, vnode.attrs),
|
||||||
m(InPlayArea, vnode.attrs.opponent_turn_state),
|
m(InPlayArea, vnode.attrs.opponent_turn_state),
|
||||||
m(SupplyArea, vnode.attrs),
|
m(SupplyArea, vnode.attrs),
|
||||||
@ -358,16 +408,15 @@
|
|||||||
|
|
||||||
function SetupScreen(initialVnode) {
|
function SetupScreen(initialVnode) {
|
||||||
var start_click = function(e) {
|
var start_click = function(e) {
|
||||||
console.log("start_click", e);
|
let msg = { type: "StartGame" };
|
||||||
|
|
||||||
let msg = { id: "start_game" };
|
|
||||||
initialVnode.attrs.socket.send(JSON.stringify(msg));
|
initialVnode.attrs.socket.send(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
view: function(vnode) {
|
view: function(vnode) {
|
||||||
return m(".setup-screen",
|
var cls = vnode.attrs.active ? "" : "hidden";
|
||||||
|
return m(".setup-screen", {class: cls},
|
||||||
m("h3", "Setup"),
|
m("h3", "Setup"),
|
||||||
m("h4", "Basic cards"),
|
m("h4", "Basic cards"),
|
||||||
m(".basic-cards",
|
m(".basic-cards",
|
||||||
@ -394,6 +443,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var game_state;
|
var game_state;
|
||||||
|
var setup_state;
|
||||||
|
var my_player_id = 0;
|
||||||
|
var webSocket;
|
||||||
|
|
||||||
function App(initialVnode) {
|
function App(initialVnode) {
|
||||||
if (document.location.protocol == "https:") {
|
if (document.location.protocol == "https:") {
|
||||||
@ -401,9 +453,9 @@
|
|||||||
} else {
|
} else {
|
||||||
url = document.location.href.replace("http://", "ws://") + "/ws";
|
url = document.location.href.replace("http://", "ws://") + "/ws";
|
||||||
}
|
}
|
||||||
const webSocket = new WebSocket(url);
|
webSocket = new WebSocket(url);
|
||||||
|
|
||||||
var setup_state = {
|
setup_state = {
|
||||||
starting_deck: [],
|
starting_deck: [],
|
||||||
basic_cards: ["Copper", "Silver", "Gold", "Estate", "Duchery", "Province", "Curse"],
|
basic_cards: ["Copper", "Silver", "Gold", "Estate", "Duchery", "Province", "Curse"],
|
||||||
kingdom_cards: ["Cellar", "Moat", "Village", "Merchant", "Workshop", "Smithy", "Remodel", "Militia", "Market", "Mine"],
|
kingdom_cards: ["Cellar", "Moat", "Village", "Merchant", "Workshop", "Smithy", "Remodel", "Militia", "Market", "Mine"],
|
||||||
@ -414,7 +466,7 @@
|
|||||||
setup_state.starting_deck = data.deck;
|
setup_state.starting_deck = data.deck;
|
||||||
}
|
}
|
||||||
|
|
||||||
game_state = {
|
game_state = {
|
||||||
supply: [
|
supply: [
|
||||||
{ name: "Copper", count: 46 },
|
{ name: "Copper", count: 46 },
|
||||||
{ name: "Silver", count: 38 },
|
{ name: "Silver", count: 38 },
|
||||||
@ -448,16 +500,19 @@
|
|||||||
coin: 0,
|
coin: 0,
|
||||||
active: true
|
active: true
|
||||||
},
|
},
|
||||||
player_drawpile_count: 10,
|
|
||||||
player: {
|
player: {
|
||||||
hand: []
|
hand: []
|
||||||
},
|
},
|
||||||
opponents: [
|
players: [
|
||||||
|
{
|
||||||
|
name: "YOU",
|
||||||
|
draw_pile_count: 10,
|
||||||
|
hand_count: 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Alice",
|
name: "Alice",
|
||||||
draw_pile_count: 10,
|
draw_pile_count: 10,
|
||||||
hand_count: 5,
|
hand_count: 5,
|
||||||
active: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Bob",
|
name: "Bob",
|
||||||
@ -469,13 +524,34 @@
|
|||||||
draw_pile_count: 10,
|
draw_pile_count: 10,
|
||||||
hand_count: 3
|
hand_count: 3
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
active_player: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var chat_state = {
|
var chat_state = {
|
||||||
|
players: [],
|
||||||
socket: webSocket
|
socket: webSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var handle_game_state = function(state) {
|
||||||
|
game_state = {
|
||||||
|
...game_state,
|
||||||
|
...state,
|
||||||
|
};
|
||||||
|
game_state.opponent_turn_state.active = game_state.active_player != my_player_id;
|
||||||
|
game_state.player_turn_state.active = game_state.active_player == my_player_id;
|
||||||
|
chat_state.players = state.players;
|
||||||
|
|
||||||
|
if (state.state == "Setup") {
|
||||||
|
setup_state.active = true;
|
||||||
|
game_state.active = false;
|
||||||
|
} else {
|
||||||
|
setup_state.active = false;
|
||||||
|
game_state.active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
webSocket.onopen = function(event) {
|
webSocket.onopen = function(event) {
|
||||||
console.log("ws open");
|
console.log("ws open");
|
||||||
//webSocket.send("HALLO");
|
//webSocket.send("HALLO");
|
||||||
@ -496,8 +572,12 @@
|
|||||||
newmsg.innerHTML = msg.player + " joined the game.";
|
newmsg.innerHTML = msg.player + " joined the game.";
|
||||||
chatDiv.append(newmsg);
|
chatDiv.append(newmsg);
|
||||||
//newmsg.scrollIntoView();
|
//newmsg.scrollIntoView();
|
||||||
} else if (msg.id == "setup") {
|
} else if (msg.type == "GameState") {
|
||||||
|
handle_game_state(msg);
|
||||||
|
} else if (msg.type == "GameSetup") {
|
||||||
handle_setup(msg.setup);
|
handle_setup(msg.setup);
|
||||||
|
} else if (msg.type == "PlayerHand") {
|
||||||
|
game_state.player.hand = msg.hand;
|
||||||
} else {
|
} else {
|
||||||
console.log("event?");
|
console.log("event?");
|
||||||
console.log(event.data);
|
console.log(event.data);
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<form id="login_form" method="post" action="/game">
|
<form id="login_form" method="post" action="/game">
|
||||||
Your name: <input type="text" name="name"> <br />
|
Your name: <input type="text" name="name"> <br />
|
||||||
|
<input type="hidden" name="type" value="CreateGame">
|
||||||
<input type="submit" value="Create game">
|
<input type="submit" value="Create game">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user