cards/server/src/game_handler.rs
2024-08-17 15:57:18 -04:00

490 lines
14 KiB
Rust

use crate::user_handler::*;
use crate::AppState;
use crate::GameHandlerMessage::*;
use crate::User;
use anyhow::{Context, Result};
use lib::*;
use rand::prelude::IteratorRandom;
use rand::thread_rng;
use serde::Deserialize;
use std::{
collections::HashMap,
fs::read_to_string,
net::SocketAddr,
sync::{Arc, RwLock},
};
use uuid::Uuid;
/// For interacting with the game handler
pub enum GameHandlerMessage {
NewGame {
addr: SocketAddr,
new_game: NewGameRequest,
},
JoinGame {
addr: SocketAddr,
id: String,
},
}
/// Handles game stuff
pub struct GameHandler {
/// Global state pointer
state: Arc<AppState>,
}
impl GameHandler {
/// Returns a new game handler
pub fn new(state: Arc<AppState>) -> Self {
GameHandler { state }
}
/// Handles incoming messages
pub async fn handle(&self, message: GameHandlerMessage) {
match message {
NewGame { addr, new_game } => self.new_game(addr, new_game).await,
JoinGame { addr, id } => self.join_game(addr, id).await,
}
}
/// Puts a user in a game
async fn join_game(&self, addr: SocketAddr, id: String) {
// Get pointers
let this_game = self.state.games.read().unwrap().get(&id).unwrap().clone();
let this_user = self
.state
.online_users
.read()
.unwrap()
.get(&addr)
.unwrap()
.clone();
// Create player
this_game.write().unwrap().create_player(this_user);
// Send updates for all players
for player in this_game.read().unwrap().players.values() {
// Create update for user's game view
let meta = GameStateMeta {
uuid: id.clone(),
name: this_game.read().unwrap().name.clone(),
host: this_game.read().unwrap().host.read().unwrap().name.clone(),
players: this_game
.read()
.unwrap()
.players
.values()
.map(|player| player.user.read().unwrap().name.clone())
.collect(),
czar: this_game.read().unwrap().host.read().unwrap().name.clone(),
black: (
this_game.read().unwrap().current_black.text.clone(),
this_game.read().unwrap().current_black.pick,
),
white: player.white.iter().map(|card| card.text.clone()).collect(),
packs: this_game.read().unwrap().packs.clone(),
};
// Send user's update
let msg = serde_json::to_string(&meta).unwrap();
let user_tx = player.user.read().unwrap().tx.clone();
tokio::spawn(async move { user_tx.send(msg).await });
}
// Broadcast game browser update
self.state
.broadcast_tx
.send(meta_games_browser_update(&self.state))
.unwrap();
// Broadcast server meta update
self.state
.broadcast_tx
.send(meta_server_summary_update(&self.state))
.unwrap();
}
/// Creates a new game
async fn new_game(&self, addr: SocketAddr, new_game: NewGameRequest) {
if new_game.packs.is_empty() {
tracing::error!("New game cards are empty!");
return;
} else if new_game.name.is_empty() {
tracing::error!("New game name is empty!");
return;
}
// Get host pointer
let host = self
.state
.online_users
.read()
.unwrap()
.get(&addr)
.unwrap()
.clone();
// Create manifest
let manifest = NewGameManifest {
name: new_game.name,
host: host.clone(),
packs: new_game.packs,
};
tracing::debug!("{:#?}", host.clone().read().unwrap().uuid);
// Create game using manifest
let mut new_game_object = Game::new(self.state.clone(), manifest);
// Don't forget to create the host player!!!
new_game_object.create_player(host.clone());
// Create update for user's game view
let meta = GameStateMeta {
uuid: new_game_object.uuid.to_string(),
name: new_game_object.name.clone(),
host: new_game_object.host.read().unwrap().name.clone(),
players: new_game_object
.players
.iter()
.map(|player| {
self.state
.user_uuid
.read()
.unwrap()
.get(player.0)
.unwrap()
.read()
.unwrap()
.name
.clone()
})
.collect(),
czar: new_game_object.host.read().unwrap().name.clone(),
black: (
new_game_object.current_black.text.clone(),
new_game_object.current_black.pick,
),
white: new_game_object
.players
.get(&new_game_object.host.read().unwrap().uuid)
.unwrap()
.white
.iter()
.map(|card| card.text.clone())
.collect(),
packs: new_game_object.packs.clone(),
};
// Send user's update
let tx = host.read().unwrap().tx.clone();
tx.send(serde_json::to_string(&meta).unwrap())
.await
.unwrap();
// Add game to active list
self.state.games.write().unwrap().insert(
new_game_object.uuid.to_string(),
Arc::new(RwLock::new(new_game_object)),
);
// Broadcast game browser update
self.state
.broadcast_tx
.send(meta_games_browser_update(&self.state))
.unwrap();
// Broadcast server meta update
self.state
.broadcast_tx
.send(meta_server_summary_update(&self.state))
.unwrap();
}
}
/// Card Set
#[derive(Debug)]
struct CardSet {
white: Option<Vec<Arc<CardWhiteWithID>>>,
black: Option<Vec<Arc<CardBlackWithID>>>,
}
/// Card Packs
#[derive(Debug)]
pub struct CardPacks {
official: HashMap<u8, CardSet>,
unofficial: HashMap<u8, CardSet>,
}
/// A raw white card as it exists in the source json
#[derive(Debug, Deserialize)]
struct CardWhiteFromJSON {
/// Card text
text: String,
/// ID of the pack it came from
pack: u8,
}
/// A raw black card as it exists in the source json
#[derive(Debug, Deserialize)]
struct CardBlackFromJSON {
/// Card text
text: String,
/// Amount of cards to submit for judging
pick: u8,
/// ID of the pack it came from
pack: u8,
}
/// A processed white card for use server-side
#[derive(Debug)]
struct CardWhiteWithID {
/// Unique identifier
uuid: Uuid,
/// Card text
text: String,
}
/// A processed black card for use server-side
#[derive(Debug)]
struct CardBlackWithID {
/// Unique identifier
uuid: Uuid,
/// Card text
text: String,
/// Amount of cards to submit for judging
pick: u8,
}
/// A card pack
#[derive(Debug, Deserialize)]
struct CardPack {
/// Name of the pack
name: String,
/// Whether or not this is an official card pack
official: bool,
/// White card data
white: Option<Vec<CardWhiteFromJSON>>,
/// Black card data
black: Option<Vec<CardBlackFromJSON>>,
}
/// Internal manifest for making a new game
#[derive(Debug)]
struct NewGameManifest {
/// Game name
name: String,
/// Game host
host: Arc<RwLock<User>>,
/// Selected game packs
packs: Vec<u8>,
}
/// A player
#[derive(Debug)]
pub struct Player {
/// Pointer to user
user: Arc<RwLock<User>>,
/// The player's hand
white: Vec<Arc<CardWhiteWithID>>,
/// The player's wins
black: Vec<Arc<CardBlackWithID>>,
}
/// The game object
#[derive(Debug)]
pub struct Game {
/// Game's UUID
pub uuid: Uuid,
/// The name of the game
pub name: String,
/// The host user of the game
pub host: Arc<RwLock<User>>,
/// White draw pile
pub white: Vec<Arc<CardWhiteWithID>>,
/// Black draw pile
black: Vec<Arc<CardBlackWithID>>,
pub players: HashMap<Uuid, Player>,
/// Black card for the current round
current_black: Arc<CardBlackWithID>,
pub packs: Vec<u8>,
}
impl Game {
/// Returns a new game object
fn new(state: Arc<AppState>, request: NewGameManifest) -> Self {
// Build the decks
let mut white = vec![];
let mut black = vec![];
for pack_num in &request.packs {
if let Some(pack) = state.packs.official.get(&pack_num) {
if let Some(card) = &pack.white {
white.extend(card.clone())
}
if let Some(card) = &pack.black {
black.extend(card.clone())
}
} else if let Some(pack) = state.packs.unofficial.get(&pack_num) {
if let Some(card) = &pack.white {
white.extend(card.clone())
}
if let Some(card) = &pack.black {
black.extend(card.clone())
}
}
}
// Draw first black card
let current_black = black.swap_remove(
(0..black.len())
.choose(&mut thread_rng())
.expect("No black cards to draw from!"),
);
// These are at the largest size they should ever be
white.shrink_to_fit();
black.shrink_to_fit();
// Return game object
Game {
uuid: Uuid::now_v7(),
name: request.name,
host: request.host.clone(),
white,
black,
players: HashMap::new(),
current_black,
packs: request.packs.clone(),
}
}
/// Draw one white card at random from play deck.
fn draw_one_white(&mut self) -> Option<Arc<CardWhiteWithID>> {
let deck = &mut self.white;
if let Some(index) = (0..deck.len()).choose(&mut thread_rng()) {
Some(deck.swap_remove(index))
} else {
tracing::error!("Tried to draw white card that doesn't exist!");
None
}
}
/// Create a new player and add them to the game.
pub fn create_player(&mut self, user: Arc<RwLock<User>>) {
// Build hand of 10 white cards
let mut white = vec![];
for _ in 0..10 {
if let Some(card) = self.draw_one_white() {
white.push(card);
}
}
// New player object
let new_player = Player {
user: user.clone(),
white,
black: vec![],
};
// Add player to game
self.players
.insert(user.read().unwrap().uuid.clone(), new_player);
}
}
/// Parse json for card data
pub fn load_cards_from_json(path: &str) -> Result<(CardPacks, CardPacksMeta)> {
// Load in json
let data: String =
read_to_string(path).with_context(|| format!("Invalid JSON path: \"{}\"", path))?;
let jayson: Vec<CardPack> = serde_json::from_str(&data)
.with_context(|| format!("The contents of \"{path}\" is not valid JSON."))?;
// For global state
let mut official: HashMap<u8, CardSet> = HashMap::new();
let mut unofficial: HashMap<u8, CardSet> = HashMap::new();
let mut official_meta: Vec<CardPackMeta> = vec![];
let mut unofficial_meta: Vec<CardPackMeta> = vec![];
// Unpack the json
for sets in jayson {
let mut num_white = 0;
let mut num_black = 0;
let mut newset = CardSet {
white: Option::None,
black: Option::None,
};
// No safe default for this so make it an Option
let mut pack: Option<u8> = Option::None;
// Process white cards if there are any
if let Some(ref white) = sets.white {
num_white = white.len();
if num_white > 0 {
pack = Some(white[0].pack);
let mut white_buf = vec![];
for card in sets.white.unwrap() {
white_buf.push(Arc::new(CardWhiteWithID {
uuid: Uuid::now_v7(),
text: card.text,
}));
}
newset.white = Some(white_buf);
}
}
// Process black cards if there are any
if let Some(ref black) = sets.black {
num_black = black.len();
if num_black > 0 {
pack = Some(black[0].pack);
let mut black_buf = vec![];
for card in sets.black.unwrap() {
black_buf.push(Arc::new(CardBlackWithID {
uuid: Uuid::now_v7(),
text: card.text,
pick: card.pick,
}));
}
newset.black = Some(black_buf);
}
}
// Start repackaging
let meta = CardPackMeta {
name: sets.name,
pack: pack.expect("No card pack number!"),
num_white,
num_black,
};
if sets.official {
official_meta.push(meta);
official.insert(pack.unwrap(), newset);
} else {
unofficial_meta.push(meta);
unofficial.insert(pack.unwrap(), newset);
}
}
// These are now the largest size they should ever be
official.shrink_to_fit();
unofficial.shrink_to_fit();
official_meta.shrink_to_fit();
unofficial_meta.shrink_to_fit();
// Package for export
let packs = CardPacks {
official,
unofficial,
};
let packs_meta = CardPacksMeta {
official_meta,
unofficial_meta,
};
Ok((packs, packs_meta))
}