Add basic player actions
This commit is contained in:
		
							
								
								
									
										317
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										317
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,13 +1,7 @@
 | 
				
			|||||||
use async_std::{
 | 
					use async_std::{prelude::*, sync::RwLock};
 | 
				
			||||||
    prelude::*,
 | 
					 | 
				
			||||||
    sync::RwLock
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use rand::{seq::SliceRandom, thread_rng};
 | 
					use rand::{seq::SliceRandom, thread_rng};
 | 
				
			||||||
use std::{
 | 
					 | 
				
			||||||
    collections::HashMap,
 | 
					 | 
				
			||||||
    sync::{Arc}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					use std::{collections::HashMap, sync::Arc};
 | 
				
			||||||
use tide::{Body, Redirect, Request, Response};
 | 
					use tide::{Body, Redirect, Request, Response};
 | 
				
			||||||
use tide_websockets::{Message, WebSocket, WebSocketConnection};
 | 
					use tide_websockets::{Message, WebSocket, WebSocketConnection};
 | 
				
			||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
@@ -17,14 +11,16 @@ type Card = String;
 | 
				
			|||||||
#[derive(Deserialize)]
 | 
					#[derive(Deserialize)]
 | 
				
			||||||
#[serde(tag = "type")]
 | 
					#[serde(tag = "type")]
 | 
				
			||||||
enum ClientMessage {
 | 
					enum ClientMessage {
 | 
				
			||||||
    Chat {
 | 
					    Chat { message: String },
 | 
				
			||||||
        message: String,
 | 
					    CreateGame { name: String },
 | 
				
			||||||
    },
 | 
					    JoinGame { name: String },
 | 
				
			||||||
    CreateGame {
 | 
					 | 
				
			||||||
        name: String,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    StartGame,
 | 
					    StartGame,
 | 
				
			||||||
    EndTurn,
 | 
					    EndTurn,
 | 
				
			||||||
 | 
					    PlayCard { name: String, index: usize },
 | 
				
			||||||
 | 
					    GainCard { name: String, index: usize },
 | 
				
			||||||
 | 
					    DrawCard,
 | 
				
			||||||
 | 
					    Discard { index: usize },
 | 
				
			||||||
 | 
					    TrashHand { index: usize },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize)]
 | 
					#[derive(Serialize)]
 | 
				
			||||||
@@ -41,6 +37,9 @@ enum ServerMessage {
 | 
				
			|||||||
        state: GameState,
 | 
					        state: GameState,
 | 
				
			||||||
        players: Vec<PlayerState>,
 | 
					        players: Vec<PlayerState>,
 | 
				
			||||||
        active_player: usize,
 | 
					        active_player: usize,
 | 
				
			||||||
 | 
					        supply: Vec<PileState>,
 | 
				
			||||||
 | 
					        turn_state: TurnState,
 | 
				
			||||||
 | 
					        trash: Vec<Card>,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    GameSetup {
 | 
					    GameSetup {
 | 
				
			||||||
        setup: GameSetup,
 | 
					        setup: GameSetup,
 | 
				
			||||||
@@ -48,6 +47,12 @@ enum ServerMessage {
 | 
				
			|||||||
    PlayerHand {
 | 
					    PlayerHand {
 | 
				
			||||||
        hand: Vec<Card>,
 | 
					        hand: Vec<Card>,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    PlayerId {
 | 
				
			||||||
 | 
					        id: usize,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Notification {
 | 
				
			||||||
 | 
					        text: String,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Serialize)]
 | 
					#[derive(Clone, Serialize)]
 | 
				
			||||||
@@ -55,7 +60,6 @@ struct GameSetup {
 | 
				
			|||||||
    deck: Vec<Card>,
 | 
					    deck: Vec<Card>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Serialize)]
 | 
					#[derive(Serialize)]
 | 
				
			||||||
struct PlayerState {
 | 
					struct PlayerState {
 | 
				
			||||||
    id: usize,
 | 
					    id: usize,
 | 
				
			||||||
@@ -63,8 +67,21 @@ struct PlayerState {
 | 
				
			|||||||
    draw_pile_count: usize,
 | 
					    draw_pile_count: usize,
 | 
				
			||||||
    hand_count: usize,
 | 
					    hand_count: usize,
 | 
				
			||||||
    discard_pile: Option<Card>,
 | 
					    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 {
 | 
					struct Player {
 | 
				
			||||||
    id: String,
 | 
					    id: String,
 | 
				
			||||||
@@ -72,6 +89,7 @@ struct Player {
 | 
				
			|||||||
    draw_pile: Vec<Card>,
 | 
					    draw_pile: Vec<Card>,
 | 
				
			||||||
    hand: Vec<Card>,
 | 
					    hand: Vec<Card>,
 | 
				
			||||||
    discard_pile: Vec<Card>,
 | 
					    discard_pile: Vec<Card>,
 | 
				
			||||||
 | 
					    played_cards: Vec<Card>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Player {
 | 
					impl Player {
 | 
				
			||||||
@@ -82,6 +100,7 @@ impl Player {
 | 
				
			|||||||
            draw_pile: vec![],
 | 
					            draw_pile: vec![],
 | 
				
			||||||
            hand: vec![],
 | 
					            hand: vec![],
 | 
				
			||||||
            discard_pile: 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 {
 | 
					enum GameState {
 | 
				
			||||||
    Setup,
 | 
					    Setup,
 | 
				
			||||||
    InProgress,
 | 
					    InProgress,
 | 
				
			||||||
@@ -108,8 +135,18 @@ enum GameState {
 | 
				
			|||||||
impl Default for GameSetup {
 | 
					impl Default for GameSetup {
 | 
				
			||||||
    fn default() -> GameSetup {
 | 
					    fn default() -> GameSetup {
 | 
				
			||||||
        GameSetup {
 | 
					        GameSetup {
 | 
				
			||||||
            deck: vec!["Copper".into(), "Copper".into(), "Copper".into(), "Copper".into(), "Copper".into(),
 | 
					            deck: vec![
 | 
				
			||||||
                       "Copper".into(), "Copper".into(), "Estate".into(), "Estate".into(), "Estate".into()]
 | 
					                "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,
 | 
					    setup: GameSetup,
 | 
				
			||||||
    connections: HashMap<Uuid, (String, WebSocketConnection)>,
 | 
					    connections: HashMap<Uuid, (String, WebSocketConnection)>,
 | 
				
			||||||
    active_player: usize,
 | 
					    active_player: usize,
 | 
				
			||||||
 | 
					    supply: Vec<(String, usize)>,
 | 
				
			||||||
 | 
					    trash: Vec<Card>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Game {
 | 
					impl Game {
 | 
				
			||||||
    fn new(player: Player) -> Self {
 | 
					    fn new(player: Player) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
@@ -131,6 +169,8 @@ impl Game {
 | 
				
			|||||||
            setup: GameSetup::default(),
 | 
					            setup: GameSetup::default(),
 | 
				
			||||||
            connections: HashMap::new(),
 | 
					            connections: HashMap::new(),
 | 
				
			||||||
            active_player: 0,
 | 
					            active_player: 0,
 | 
				
			||||||
 | 
					            supply: vec![],
 | 
				
			||||||
 | 
					            trash: vec![],
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -143,15 +183,37 @@ impl Game {
 | 
				
			|||||||
                    (*p).discard_pile = self.setup.deck.clone();
 | 
					                    (*p).discard_pile = self.setup.deck.clone();
 | 
				
			||||||
                    p.draw(5);
 | 
					                    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
 | 
					            _ => {} // Ignore if game is not in setup state
 | 
				
			||||||
        } 
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn end_turn(&mut self) {
 | 
					    fn end_turn(&mut self) {
 | 
				
			||||||
        match self.players.get_mut(self.active_player) {
 | 
					        match self.players.get_mut(self.active_player) {
 | 
				
			||||||
            None => {}
 | 
					            None => {}
 | 
				
			||||||
            Some(p) => {
 | 
					            Some(p) => {
 | 
				
			||||||
 | 
					                p.discard_pile.append(&mut p.played_cards);
 | 
				
			||||||
                p.discard_pile.append(&mut p.hand);
 | 
					                p.discard_pile.append(&mut p.hand);
 | 
				
			||||||
                p.draw(5);
 | 
					                p.draw(5);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -164,11 +226,14 @@ impl Game {
 | 
				
			|||||||
    fn is_active_player(&self, player_id: &str) -> bool {
 | 
					    fn is_active_player(&self, player_id: &str) -> bool {
 | 
				
			||||||
        match self.players.get(self.active_player) {
 | 
					        match self.players.get(self.active_player) {
 | 
				
			||||||
            None => false,
 | 
					            None => false,
 | 
				
			||||||
            Some(p) => {
 | 
					            Some(p) => p.id == *player_id,
 | 
				
			||||||
                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)]
 | 
					#[derive(Clone, Default)]
 | 
				
			||||||
@@ -176,6 +241,11 @@ struct State {
 | 
				
			|||||||
    games: Arc<RwLock<HashMap<Uuid, Game>>>,
 | 
					    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) {
 | 
					async fn broadcast(game: &Game, sm: &ServerMessage) {
 | 
				
			||||||
    for (_, (_, con)) in game.connections.iter() {
 | 
					    for (_, (_, con)) in game.connections.iter() {
 | 
				
			||||||
        con.send_json(&sm).await.unwrap();
 | 
					        con.send_json(&sm).await.unwrap();
 | 
				
			||||||
@@ -185,14 +255,34 @@ async fn broadcast(game: &Game, sm: &ServerMessage) {
 | 
				
			|||||||
async fn broadcast_state(game: &Game) {
 | 
					async fn broadcast_state(game: &Game) {
 | 
				
			||||||
    let sm = ServerMessage::GameState {
 | 
					    let sm = ServerMessage::GameState {
 | 
				
			||||||
        state: game.state,
 | 
					        state: game.state,
 | 
				
			||||||
        players: game.players.iter().enumerate().map(|(i, p)| PlayerState {
 | 
					        players: game
 | 
				
			||||||
           id: i,
 | 
					            .players
 | 
				
			||||||
           name: html_escape::encode_text(&p.name).into(),
 | 
					            .iter()
 | 
				
			||||||
           draw_pile_count: p.draw_pile.len(),
 | 
					            .enumerate()
 | 
				
			||||||
           hand_count: p.hand.len(),
 | 
					            .map(|(i, p)| PlayerState {
 | 
				
			||||||
           discard_pile: p.discard_pile.last().map(|c| c.clone()),
 | 
					                id: i,
 | 
				
			||||||
        }).collect(),
 | 
					                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,
 | 
					        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;
 | 
					    broadcast(&game, &sm).await;
 | 
				
			||||||
@@ -214,7 +304,6 @@ async fn send_msg(game: &Game, player: &Player, sm: &ServerMessage) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#[async_std::main]
 | 
					#[async_std::main]
 | 
				
			||||||
async fn main() -> Result<(), std::io::Error> {
 | 
					async fn main() -> Result<(), std::io::Error> {
 | 
				
			||||||
    tide::log::with_level(tide::log::LevelFilter::Debug);
 | 
					    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());
 | 
					    let mut app = tide::with_state(State::default());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.with(tide::sessions::SessionMiddleware::new(
 | 
					    app.with(tide::sessions::SessionMiddleware::new(
 | 
				
			||||||
            tide::sessions::MemoryStore::new(),
 | 
					        tide::sessions::MemoryStore::new(),
 | 
				
			||||||
            "1234567890abcdef1234567890abcdef".as_bytes(),
 | 
					        "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/")?;
 | 
					    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());
 | 
					                let res: tide::Result = Ok(Redirect::new(url).into());
 | 
				
			||||||
                res
 | 
					                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 games = req.state().games.read().await;
 | 
				
			||||||
        let id = req.param("id")?;
 | 
					        let id = req.param("id")?;
 | 
				
			||||||
        let game_id = Uuid::parse_str(&id).unwrap();
 | 
					        let game_id = Uuid::parse_str(&id).unwrap();
 | 
				
			||||||
 | 
					        let session = req.session();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match games.get(&game_id) {
 | 
					        match games.get(&game_id) {
 | 
				
			||||||
            None => Ok(Response::new(404)),
 | 
					            None => return Ok(Response::new(404)),
 | 
				
			||||||
            Some(_) => {
 | 
					            Some(_) => (),
 | 
				
			||||||
                let mut res = Response::new(200);
 | 
					        };
 | 
				
			||||||
                res.set_body(Body::from_file("static/game.html").await?);
 | 
					
 | 
				
			||||||
                Ok(res)
 | 
					        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")
 | 
					            match msg {
 | 
				
			||||||
        .get(WebSocket::new(|req: Request<State>, mut stream| async move {
 | 
					                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 id = req.param("id")?;
 | 
				
			||||||
            let game_id = Uuid::parse_str(&id).unwrap();
 | 
					            let game_id = Uuid::parse_str(&id).unwrap();
 | 
				
			||||||
            let mut games = req.state().games.write().await;
 | 
					            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 player_id: String = session.id().clone().into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let client_id = Uuid::new_v4();
 | 
					            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") {
 | 
					            let player_name: String = match session.get("player_name") {
 | 
				
			||||||
                Some(p) => p,
 | 
					                Some(p) => p,
 | 
				
			||||||
                None => return Ok(())
 | 
					                None => return Ok(()),
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if !game.players.iter().any(|p| p.id == player_id) {
 | 
					            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()));
 | 
					                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
 | 
					            //Ensure the write locks are freed, possibly better to move to a function
 | 
				
			||||||
            //with implicit drop?
 | 
					            //with implicit drop?
 | 
				
			||||||
@@ -352,9 +477,93 @@ async fn main() -> Result<(), std::io::Error> {
 | 
				
			|||||||
                            game.end_turn();
 | 
					                            game.end_turn();
 | 
				
			||||||
                            broadcast_state(&game).await;
 | 
					                            broadcast_state(&game).await;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                     }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    ClientMessage::CreateGame { .. } => {}
 | 
					                    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);
 | 
					            game.connections.remove(&client_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Ok(())
 | 
					            Ok(())
 | 
				
			||||||
        }));
 | 
					        },
 | 
				
			||||||
 | 
					    ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.listen("0.0.0.0:5000").await?;
 | 
					    app.listen("0.0.0.0:5000").await?;
 | 
				
			||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										393
									
								
								static/game.html
									
									
									
									
									
								
							
							
						
						
									
										393
									
								
								static/game.html
									
									
									
									
									
								
							@@ -6,6 +6,12 @@
 | 
				
			|||||||
    <title>DnD</title>
 | 
					    <title>DnD</title>
 | 
				
			||||||
    <link rel="stylesheet" href="/static/main.css">
 | 
					    <link rel="stylesheet" href="/static/main.css">
 | 
				
			||||||
<style type="text/css">
 | 
					<style type="text/css">
 | 
				
			||||||
 | 
					#game {
 | 
				
			||||||
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    grid-template-columns: auto auto;
 | 
				
			||||||
 | 
					    user-select: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.pile-counter {
 | 
					.pile-counter {
 | 
				
			||||||
    position: absolute;
 | 
					    position: absolute;
 | 
				
			||||||
    bottom: 5px;
 | 
					    bottom: 5px;
 | 
				
			||||||
@@ -42,14 +48,16 @@
 | 
				
			|||||||
    width: 90px;
 | 
					    width: 90px;
 | 
				
			||||||
    margin: 1px;
 | 
					    margin: 1px;
 | 
				
			||||||
    height: 144px;
 | 
					    height: 144px;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.player-hand {
 | 
					.player-hand {
 | 
				
			||||||
    border: 1px solid red;
 | 
					    border: 1px solid red;
 | 
				
			||||||
    height: 100%;
 | 
					    height: 160px;
 | 
				
			||||||
    margin: 0;
 | 
					    margin: 0;
 | 
				
			||||||
    box-sizing: border-box;
 | 
					    box-sizing: border-box;
 | 
				
			||||||
    top: 0;
 | 
					    overflow-x: scroll;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.player-hand img.card:hover {
 | 
					.player-hand img.card:hover {
 | 
				
			||||||
@@ -59,16 +67,20 @@
 | 
				
			|||||||
.chat-window {
 | 
					.chat-window {
 | 
				
			||||||
    border: 1px solid black;
 | 
					    border: 1px solid black;
 | 
				
			||||||
    width: 300px;
 | 
					    width: 300px;
 | 
				
			||||||
    position: absolute;
 | 
					    grid-column-start: 2;
 | 
				
			||||||
    top: 0;
 | 
					}
 | 
				
			||||||
    right: 0;
 | 
					
 | 
				
			||||||
    z-index: 100;
 | 
					#chat li[data-repeat]::after {
 | 
				
			||||||
 | 
					    content: "x" attr(data-repeat);
 | 
				
			||||||
 | 
					    margin-left: 10px;
 | 
				
			||||||
 | 
					    background-color: deepskyblue;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.supply-area {
 | 
					.supply-area {
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    padding-left: 30px;
 | 
					    padding-left: 30px;
 | 
				
			||||||
    padding-right: 300px;
 | 
					 | 
				
			||||||
    background-color: dimgray;
 | 
					    background-color: dimgray;
 | 
				
			||||||
    box-sizing: border-box;
 | 
					    box-sizing: border-box;
 | 
				
			||||||
    margin: 5px 0;
 | 
					    margin: 5px 0;
 | 
				
			||||||
@@ -87,10 +99,54 @@
 | 
				
			|||||||
    width: 0;
 | 
					    width: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.supply-pile {
 | 
				
			||||||
 | 
					    width: 90px;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    height: 145px;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					    margin: 1px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.supply-pile::after {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: 5px;
 | 
				
			||||||
 | 
					    right: 5px;
 | 
				
			||||||
 | 
					    color: wheat;
 | 
				
			||||||
 | 
					    background-color: darkred;
 | 
				
			||||||
 | 
					    border-radius: 5px;
 | 
				
			||||||
 | 
					    height: 24px;
 | 
				
			||||||
 | 
					    width: 35px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 22px;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					    line-height: 24px;
 | 
				
			||||||
 | 
					    content: attr(data-count);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.draw-pile {
 | 
					.draw-pile {
 | 
				
			||||||
    width: 90px;
 | 
					    width: 90px;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
    height: 145px;
 | 
					    height: 145px;
 | 
				
			||||||
 | 
					    background-image: url(/static/images/cards/Card_back.jpg);
 | 
				
			||||||
 | 
					    background-size: 100% 100%;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.draw-pile::after {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    bottom: 5px;
 | 
				
			||||||
 | 
					    right: 5px;
 | 
				
			||||||
 | 
					    color: wheat;
 | 
				
			||||||
 | 
					    background-color: darkred;
 | 
				
			||||||
 | 
					    border-radius: 5px;
 | 
				
			||||||
 | 
					    height: 24px;
 | 
				
			||||||
 | 
					    width: 35px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    font-size: 22px;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					    line-height: 24px;
 | 
				
			||||||
 | 
					    content: attr(data-count);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.opponent-hand {
 | 
					.opponent-hand {
 | 
				
			||||||
@@ -99,6 +155,11 @@
 | 
				
			|||||||
    height: 145px;
 | 
					    height: 145px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.opponent-hand img.card {
 | 
				
			||||||
 | 
					    margin-top: 0;
 | 
				
			||||||
 | 
					    margin-left: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.setup-screen {
 | 
					.setup-screen {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -112,6 +173,7 @@
 | 
				
			|||||||
    height: 144px;
 | 
					    height: 144px;
 | 
				
			||||||
    box-sizing: border-box;
 | 
					    box-sizing: border-box;
 | 
				
			||||||
    position: relative;
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    border-radius: 5px;
 | 
				
			||||||
} 
 | 
					} 
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
.discard-pile img.card {
 | 
					.discard-pile img.card {
 | 
				
			||||||
@@ -146,6 +208,7 @@
 | 
				
			|||||||
    height: 0;
 | 
					    height: 0;
 | 
				
			||||||
    transition: height 0.4s ease;
 | 
					    transition: height 0.4s ease;
 | 
				
			||||||
    overflow: hidden;
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					    padding-top: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.turn-status {
 | 
					.turn-status {
 | 
				
			||||||
@@ -169,6 +232,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.opponent-area {
 | 
					.opponent-area {
 | 
				
			||||||
    display: flex;
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.opponent-status {
 | 
					.opponent-status {
 | 
				
			||||||
@@ -176,7 +240,7 @@
 | 
				
			|||||||
    grid-template-columns: auto auto auto;
 | 
					    grid-template-columns: auto auto auto;
 | 
				
			||||||
    box-sizing: border-box;
 | 
					    box-sizing: border-box;
 | 
				
			||||||
    margin: 0 5px;
 | 
					    margin: 0 5px;
 | 
				
			||||||
    border: 1px solid saddlebrown;
 | 
					    border: 1px solid dimgray;
 | 
				
			||||||
    display: grid;
 | 
					    display: grid;
 | 
				
			||||||
    grid-gap: 2px;
 | 
					    grid-gap: 2px;
 | 
				
			||||||
    padding: 2px;
 | 
					    padding: 2px;
 | 
				
			||||||
@@ -186,6 +250,10 @@
 | 
				
			|||||||
    box-shadow: 0 0 10px red;
 | 
					    box-shadow: 0 0 10px red;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.opponent-status:first-child {
 | 
				
			||||||
 | 
					    margin-left: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.opponent-status .name {
 | 
					.opponent-status .name {
 | 
				
			||||||
    grid-column-start: 1;
 | 
					    grid-column-start: 1;
 | 
				
			||||||
    grid-column-end: span 3;
 | 
					    grid-column-end: span 3;
 | 
				
			||||||
@@ -218,6 +286,29 @@
 | 
				
			|||||||
.game-screen {
 | 
					.game-screen {
 | 
				
			||||||
    padding: 10px;
 | 
					    padding: 10px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.trash {
 | 
				
			||||||
 | 
					    height: 145px;
 | 
				
			||||||
 | 
					    border: 1px solid saddlebrown;
 | 
				
			||||||
 | 
					    width: 90px;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    text-align: right;
 | 
				
			||||||
 | 
					    right: 0;
 | 
				
			||||||
 | 
					    border-radius: 6px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.trash::after {
 | 
				
			||||||
 | 
					    content: "Trash";
 | 
				
			||||||
 | 
					    color: saddlebrown;
 | 
				
			||||||
 | 
					    font-size: 14px;
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 50%;
 | 
				
			||||||
 | 
					    left: 50%;
 | 
				
			||||||
 | 
					    transform: translate(-50%, -50%);
 | 
				
			||||||
 | 
					    white-space: nowrap;
 | 
				
			||||||
 | 
					    z-index: -1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
<script src="/static/mithril.js"></script>
 | 
					<script src="/static/mithril.js"></script>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
@@ -225,6 +316,27 @@
 | 
				
			|||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <div id="game"></div>
 | 
					    <div id="game"></div>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
 | 
					        function handle_dnd(data) {
 | 
				
			||||||
 | 
					            if (data.source == "Hand" && data.dest == "InPlay") {
 | 
				
			||||||
 | 
					                var msg = { type: "PlayCard", name: "", index: data.index};
 | 
				
			||||||
 | 
					                webSocket.send(JSON.stringify(msg));
 | 
				
			||||||
 | 
					            } else if (data.source == "Hand" && data.dest == "Discard") {
 | 
				
			||||||
 | 
					                var msg = { type: "Discard", index: data.index};
 | 
				
			||||||
 | 
					                webSocket.send(JSON.stringify(msg));
 | 
				
			||||||
 | 
					            } else if (data.source == "Supply" && data.dest == "Discard") {
 | 
				
			||||||
 | 
					                var msg = { type: "GainCard", name: data.name, index: parseInt(data.index) };
 | 
				
			||||||
 | 
					                webSocket.send(JSON.stringify(msg));
 | 
				
			||||||
 | 
					            } else if (data.source == "DrawPile" && data.dest == "Hand") {
 | 
				
			||||||
 | 
					                var msg = { type: "DrawCard" };
 | 
				
			||||||
 | 
					                webSocket.send(JSON.stringify(msg));
 | 
				
			||||||
 | 
					            } else if (data.source == "Hand" && data.dest == "Trash") {
 | 
				
			||||||
 | 
					                var msg = { type: "TrashHand", index: data.index };
 | 
				
			||||||
 | 
					                webSocket.send(JSON.stringify(msg));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                console.log("handle_dnd: unhandled data", data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function Chat(initialVnode) {
 | 
					        function Chat(initialVnode) {
 | 
				
			||||||
            var keyup = function(e) {
 | 
					            var keyup = function(e) {
 | 
				
			||||||
                if (e.key == "Enter" && e.srcElement.value.length > 0) {
 | 
					                if (e.key == "Enter" && e.srcElement.value.length > 0) {
 | 
				
			||||||
@@ -254,11 +366,31 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function SupplyPile(initialVnode) {
 | 
					        function SupplyPile(initialVnode) {
 | 
				
			||||||
 | 
					            var dragStart = function(ev) {
 | 
				
			||||||
 | 
					                console.log(ev);
 | 
				
			||||||
 | 
					                let data = {
 | 
				
			||||||
 | 
					                    source: "Supply",
 | 
				
			||||||
 | 
					                    name: ev.target.dataset.name,
 | 
				
			||||||
 | 
					                    index: ev.target.dataset.index,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    return m(".card",
 | 
					                    return m(".supply-pile",
 | 
				
			||||||
                        m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg"}),
 | 
					                        {
 | 
				
			||||||
                        m("span", {class: "pile-counter" }, vnode.attrs.count)
 | 
					                            "data-count": vnode.attrs.count,
 | 
				
			||||||
 | 
					                            "data-name": vnode.attrs.name,
 | 
				
			||||||
 | 
					                            "data-index": vnode.attrs.index,
 | 
				
			||||||
 | 
					                            ondragstart: dragStart,
 | 
				
			||||||
 | 
					                            draggable: true,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        m("img", {
 | 
				
			||||||
 | 
					                            class: "card",
 | 
				
			||||||
 | 
					                            src: "/static/images/cards/" + vnode.attrs.name.toLowerCase() + ".jpg",
 | 
				
			||||||
 | 
					                            draggable: false,
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -270,44 +402,146 @@
 | 
				
			|||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    return m(".supply-area", 
 | 
					                    return m(".supply-area", 
 | 
				
			||||||
                        m("h3", "Supply"),
 | 
					                        m("h3", "Supply"),
 | 
				
			||||||
                        vnode.attrs.supply.map(function(pile) {
 | 
					                        vnode.attrs.supply.map(function(pile, i) {
 | 
				
			||||||
                            return m(SupplyPile, pile)
 | 
					                            return m(SupplyPile, {...pile, index: i})
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        function TrashPile(initialVnode) {
 | 
				
			||||||
 | 
					            var dragover = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var drop = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					                var data = {
 | 
				
			||||||
 | 
					                    dest: "Trash",
 | 
				
			||||||
 | 
					                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                handle_dnd(data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return {
 | 
				
			||||||
 | 
					                view: function(vnode) {
 | 
				
			||||||
 | 
					                    return m(".trash",
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            ondragover: dragover,
 | 
				
			||||||
 | 
					                            ondrop: drop,
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function DiscardPile(initialVnode) {
 | 
					        function DiscardPile(initialVnode) {
 | 
				
			||||||
 | 
					            var dragover = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var drop = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					                var data = {
 | 
				
			||||||
 | 
					                    dest: "Discard",
 | 
				
			||||||
 | 
					                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                handle_dnd(data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    var c;
 | 
					                    var c;
 | 
				
			||||||
                    if (vnode.attrs.card) {
 | 
					                    if (vnode.attrs.card) {
 | 
				
			||||||
                        c = m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.card.toLowerCase() + ".jpg"});
 | 
					                        c = m("img", {class: "card", src: "/static/images/cards/" + vnode.attrs.card.toLowerCase() + ".jpg"});
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    return m(".discard-pile", c)
 | 
					                    return m(".discard-pile",
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            ondragover: dragover,
 | 
				
			||||||
 | 
					                            ondrop: drop,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        c)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function DrawPile(initialVnode) {
 | 
					        function DrawPile(initialVnode) {
 | 
				
			||||||
 | 
					            var dragStart = function(ev) {
 | 
				
			||||||
 | 
					                //console.log(ev);
 | 
				
			||||||
 | 
					                let data = {
 | 
				
			||||||
 | 
					                    source: "DrawPile",
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dragover = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var drop = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					                console.log("drop", e);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    return m(".draw-pile",
 | 
					                    return m(".draw-pile",
 | 
				
			||||||
                        m("img", {class: "card", src: "/static/images/cards/Card_back.jpg"}),
 | 
					                        {
 | 
				
			||||||
                        m("span", {class: "pile-counter" }, vnode.attrs.count)
 | 
					                            draggable: vnode.attrs.draggable ? true : false,
 | 
				
			||||||
 | 
					                            ondragstart: dragStart,
 | 
				
			||||||
 | 
					                            ondragover: dragover,
 | 
				
			||||||
 | 
					                            ondrop: drop,
 | 
				
			||||||
 | 
					                            "data-count": vnode.attrs.count,
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function PlayerHand(initialVnode) {
 | 
					        function PlayerHand(initialVnode) {
 | 
				
			||||||
 | 
					            var dragStart = function(ev) {
 | 
				
			||||||
 | 
					                //console.log(ev);
 | 
				
			||||||
 | 
					                let data = {
 | 
				
			||||||
 | 
					                    source: "Hand",
 | 
				
			||||||
 | 
					                    index: parseInt(ev.target.dataset.index),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                ev.dataTransfer.setData("text", JSON.stringify(data));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var dragover = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var drop = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					                var data = {
 | 
				
			||||||
 | 
					                    dest: "Hand",
 | 
				
			||||||
 | 
					                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                handle_dnd(data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    return m(".player-hand", "Player Hand",
 | 
					                    return m(".player-hand",
 | 
				
			||||||
                        vnode.attrs.hand.map(function(card) {
 | 
					                        {
 | 
				
			||||||
                            return m("img", {class: "card", src: "/static/images/cards/" + card.toLowerCase() + ".jpg"})
 | 
					                            ondragover: dragover,
 | 
				
			||||||
 | 
					                            ondrop: drop,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        "Player Hand",
 | 
				
			||||||
 | 
					                        vnode.attrs.hand.map(function(card, i) {
 | 
				
			||||||
 | 
					                            return m("img", {
 | 
				
			||||||
 | 
					                                class: "card",
 | 
				
			||||||
 | 
					                                src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
 | 
				
			||||||
 | 
					                                draggable: true,
 | 
				
			||||||
 | 
					                                ondragstart: dragStart,
 | 
				
			||||||
 | 
					                                "data-index": i,
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                } 
 | 
					                } 
 | 
				
			||||||
@@ -326,16 +560,44 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        function InPlayArea(initialVnode) {
 | 
					        function InPlayArea(initialVnode) {
 | 
				
			||||||
 | 
					            var dragover = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            var drop = function(e) {
 | 
				
			||||||
 | 
					                e.preventDefault();
 | 
				
			||||||
 | 
					                var data = {
 | 
				
			||||||
 | 
					                    dest: "InPlay",
 | 
				
			||||||
 | 
					                    ...JSON.parse(e.dataTransfer.getData("text")),
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                handle_dnd(data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    var active = vnode.attrs.active ? "" : "inactive";
 | 
					                    var active = vnode.attrs.active ? "" : "inactive";
 | 
				
			||||||
                    return m(".inplay-area", {class: active}, 
 | 
					                    var cards = game_state.players[game_state.active_player].played_cards || [];
 | 
				
			||||||
 | 
					                    return m(".inplay-area",
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            class: active,
 | 
				
			||||||
 | 
					                            ondragover: dragover,
 | 
				
			||||||
 | 
					                            ondrop: drop,
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
                        "Player play area",
 | 
					                        "Player play area",
 | 
				
			||||||
                        m(".turn-status",
 | 
					                        m(".turn-status",
 | 
				
			||||||
                            "Actions: " + vnode.attrs.actions + " | Buys: " + vnode.attrs.buys + " | ",
 | 
					                            "Actions: " + vnode.attrs.actions + " | Buys: " + vnode.attrs.buys + " | ",
 | 
				
			||||||
                            m("img", {src: "/static/images/64px-Coin.png", style: "height: 16px;"}),
 | 
					                            m("img", {src: "/static/images/64px-Coin.png", style: "height: 16px;"}),
 | 
				
			||||||
                            ": " + vnode.attrs.coin
 | 
					                            ": " + vnode.attrs.coin
 | 
				
			||||||
                        )
 | 
					                        ),
 | 
				
			||||||
 | 
					                        cards.map(function(card) {
 | 
				
			||||||
 | 
					                            return m("img", {
 | 
				
			||||||
 | 
					                                class: "card",
 | 
				
			||||||
 | 
					                                src: "/static/images/cards/" + card.toLowerCase() + ".jpg",
 | 
				
			||||||
 | 
					                                draggable: true,
 | 
				
			||||||
 | 
					                                //ondragstart: dragStart,
 | 
				
			||||||
 | 
					                            })
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -351,7 +613,7 @@
 | 
				
			|||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
                    var local_player = vnode.attrs.players[my_player_id];
 | 
					                    var local_player = vnode.attrs.players[my_player_id];
 | 
				
			||||||
                    return m(".player-area",
 | 
					                    return m(".player-area",
 | 
				
			||||||
                        m(DrawPile, {count: local_player.draw_pile_count}),
 | 
					                        m(DrawPile, {count: local_player.draw_pile_count, draggable: true}),
 | 
				
			||||||
                        m(PlayerHand, vnode.attrs.player),
 | 
					                        m(PlayerHand, vnode.attrs.player),
 | 
				
			||||||
                        m(DiscardPile, {card: local_player.discard_pile}),
 | 
					                        m(DiscardPile, {card: local_player.discard_pile}),
 | 
				
			||||||
                        m(".buttons",
 | 
					                        m(".buttons",
 | 
				
			||||||
@@ -384,7 +646,8 @@
 | 
				
			|||||||
                            .filter((p, i) => i != my_player_id)
 | 
					                            .filter((p, i) => i != my_player_id)
 | 
				
			||||||
                            .map(function(o) {
 | 
					                            .map(function(o) {
 | 
				
			||||||
                                return m(OpponentStatus, o)
 | 
					                                return m(OpponentStatus, o)
 | 
				
			||||||
                        })
 | 
					                        }),
 | 
				
			||||||
 | 
					                        m(TrashPile),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                } 
 | 
					                } 
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -393,12 +656,21 @@
 | 
				
			|||||||
        function GameScreen(initialVnode) {
 | 
					        function GameScreen(initialVnode) {
 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                view: function(vnode) {
 | 
					                view: function(vnode) {
 | 
				
			||||||
 | 
					                    var player_state = {
 | 
				
			||||||
 | 
					                        ...vnode.attrs.turn_state,
 | 
				
			||||||
 | 
					                        active: vnode.attrs.active_player == my_player_id,
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    var opponent_state = {
 | 
				
			||||||
 | 
					                        ...vnode.attrs.turn_state,
 | 
				
			||||||
 | 
					                        active: vnode.attrs.active_player != my_player_id,
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var cls = vnode.attrs.active ? "" : "hidden";
 | 
					                    var cls = vnode.attrs.active ? "" : "hidden";
 | 
				
			||||||
                    return m(".game-screen", {class: cls},
 | 
					                    return m(".game-screen", {class: cls},
 | 
				
			||||||
                        m(OpponentArea, vnode.attrs),
 | 
					                        m(OpponentArea, vnode.attrs),
 | 
				
			||||||
                        m(InPlayArea, vnode.attrs.opponent_turn_state),
 | 
					                        m(InPlayArea, opponent_state),
 | 
				
			||||||
                        m(SupplyArea, vnode.attrs),
 | 
					                        m(SupplyArea, vnode.attrs),
 | 
				
			||||||
                        m(InPlayArea, vnode.attrs.player_turn_state),
 | 
					                        m(InPlayArea, player_state),
 | 
				
			||||||
                        m(PlayerArea, vnode.attrs)
 | 
					                        m(PlayerArea, vnode.attrs)
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -466,61 +738,19 @@
 | 
				
			|||||||
                setup_state.starting_deck = data.deck;
 | 
					                setup_state.starting_deck = data.deck;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
           game_state = {
 | 
					            game_state = {
 | 
				
			||||||
                supply: [
 | 
					                supply: [],
 | 
				
			||||||
                    { name: "Copper", count: 46 },
 | 
					                turn_state: {
 | 
				
			||||||
                    { name: "Silver", count: 38 },
 | 
					 | 
				
			||||||
                    { name: "Gold", count: 30 },
 | 
					 | 
				
			||||||
                    { name: "Estate", count: 8 },
 | 
					 | 
				
			||||||
                    { name: "Duchery", count: 8 },
 | 
					 | 
				
			||||||
                    { name: "Province", count: 8 },
 | 
					 | 
				
			||||||
                    { name: "Curse", count: 10 },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    { name: "Cellar", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Moat", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Village", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Merchant", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Workshop", count: 10},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    { name: "Smithy", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Remodel", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Militia", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Market", count: 10},
 | 
					 | 
				
			||||||
                    { name: "Mine", count: 10}
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
                opponent_turn_state: {
 | 
					 | 
				
			||||||
                    actions: 1,
 | 
					                    actions: 1,
 | 
				
			||||||
                    buys: 1,
 | 
					                    buys: 1,
 | 
				
			||||||
                    coin: 0,
 | 
					                    coin: 0,
 | 
				
			||||||
                    active: false
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                player_turn_state: {
 | 
					 | 
				
			||||||
                    actions: 1,
 | 
					 | 
				
			||||||
                    buys: 1,
 | 
					 | 
				
			||||||
                    coin: 0,
 | 
					 | 
				
			||||||
                    active: true
 | 
					 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                player: {
 | 
					                player: {
 | 
				
			||||||
                    hand: []
 | 
					                    hand: []
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                players: [
 | 
					                players: [
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        name: "YOU",
 | 
					                        name: "You",
 | 
				
			||||||
                        draw_pile_count: 10,
 | 
					 | 
				
			||||||
                        hand_count: 0
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        name: "Alice",
 | 
					 | 
				
			||||||
                        draw_pile_count: 10,
 | 
					 | 
				
			||||||
                        hand_count: 5,
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        name: "Bob",
 | 
					 | 
				
			||||||
                        draw_pile_count: 8,
 | 
					 | 
				
			||||||
                        hand_count: 5
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        name: "Mallory",
 | 
					 | 
				
			||||||
                        draw_pile_count: 10,
 | 
					                        draw_pile_count: 10,
 | 
				
			||||||
                        hand_count: 3
 | 
					                        hand_count: 3
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@@ -538,8 +768,6 @@
 | 
				
			|||||||
                    ...game_state,
 | 
					                    ...game_state,
 | 
				
			||||||
                    ...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;
 | 
					                chat_state.players = state.players;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (state.state == "Setup") {
 | 
					                if (state.state == "Setup") {
 | 
				
			||||||
@@ -565,19 +793,32 @@
 | 
				
			|||||||
                    let newmsg = document.createElement("li");
 | 
					                    let newmsg = document.createElement("li");
 | 
				
			||||||
                    newmsg.innerHTML = msg.sender + ": " + msg.message;
 | 
					                    newmsg.innerHTML = msg.sender + ": " + msg.message;
 | 
				
			||||||
                    chatDiv.append(newmsg);
 | 
					                    chatDiv.append(newmsg);
 | 
				
			||||||
                    //newmsg.scrollIntoView();
 | 
					                    newmsg.scrollIntoView();
 | 
				
			||||||
                } else if (msg.type == "PlayerJoined") {
 | 
					                } else if (msg.type == "Notification") {
 | 
				
			||||||
 | 
					                    let chatDiv = document.getElementById("chat");
 | 
				
			||||||
 | 
					                    let last_element = document.querySelector("#chat li:last-child");
 | 
				
			||||||
 | 
					                    if (last_element.innerText == msg.text) {
 | 
				
			||||||
 | 
					                        last_element.dataset.repeat = (parseInt(last_element.dataset.repeat || 1)) + 1;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        let newmsg = document.createElement("li");
 | 
				
			||||||
 | 
					                        newmsg.innerHTML = msg.text;
 | 
				
			||||||
 | 
					                        chatDiv.append(newmsg);
 | 
				
			||||||
 | 
					                        newmsg.scrollIntoView();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                 } else if (msg.type == "PlayerJoined") {
 | 
				
			||||||
                    let chatDiv = document.getElementById("chat");
 | 
					                    let chatDiv = document.getElementById("chat");
 | 
				
			||||||
                    let newmsg = document.createElement("li");
 | 
					                    let newmsg = document.createElement("li");
 | 
				
			||||||
                    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.type == "GameState") {
 | 
					                } else if (msg.type == "GameState") {
 | 
				
			||||||
                    handle_game_state(msg);
 | 
					                    handle_game_state(msg);
 | 
				
			||||||
                } else if (msg.type == "GameSetup") {
 | 
					                } else if (msg.type == "GameSetup") {
 | 
				
			||||||
                    handle_setup(msg.setup);
 | 
					                    handle_setup(msg.setup);
 | 
				
			||||||
                } else if (msg.type == "PlayerHand") {
 | 
					                } else if (msg.type == "PlayerHand") {
 | 
				
			||||||
                    game_state.player.hand = msg.hand;
 | 
					                    game_state.player.hand = msg.hand;
 | 
				
			||||||
 | 
					                } else if (msg.type == "PlayerId") {
 | 
				
			||||||
 | 
					                    my_player_id = msg.id;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    console.log("event?");
 | 
					                    console.log("event?");
 | 
				
			||||||
                    console.log(event.data);
 | 
					                    console.log(event.data);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user