diff --git a/server/src/lib.rs b/server/src/lib.rs new file mode 100644 index 0000000..08c855f --- /dev/null +++ b/server/src/lib.rs @@ -0,0 +1,134 @@ +use rocket::serde::{Deserialize, Serialize}; +use rocket::tokio::sync::broadcast::Sender; +use rocket::tokio::sync::RwLock; +use std::collections::HashMap; +use std::sync::{LazyLock, Weak}; +use word_grid::api::{APIGame, ApiState, Update}; +use word_grid::dictionary::{Dictionary, DictionaryImpl}; +use word_grid::game::{Error, PlayedTile}; +use word_grid::player_interaction::ai::Difficulty; +use ws::frame::{CloseCode, CloseFrame}; + +pub static DICTIONARY: LazyLock = + LazyLock::new(|| DictionaryImpl::create_from_path("../resources/dictionary.csv")); + +pub fn escape_characters(x: &str) -> String { + let x = x + .replace("&", "&") + .replace("<", "<") + .replace(">", ">"); + + x +} + +#[derive(Clone, Debug, Serialize, PartialEq)] +pub struct Player { + pub name: String, +} + +#[derive(Clone, Serialize, Debug)] +pub struct PartyInfo { + pub ais: Vec, + pub players: Vec, +} + +impl PartyInfo { + fn new(host: Player) -> Self { + Self { + ais: Vec::new(), + players: vec![host], + } + } +} + +pub struct Room { + pub party_info: PartyInfo, + pub game: Option, + pub sender: Sender<(Option, InnerRoomMessage)>, +} + +impl Room { + pub fn new(host: Player) -> Self { + Self { + party_info: PartyInfo::new(host), + game: None, + sender: Sender::new(5), + } + } +} + +#[derive(Clone, Serialize, Debug)] +#[serde(tag = "type")] +pub enum RoomEvent { + PlayerJoined { player: Player }, + PlayerLeft { player: Player }, + AIJoined { difficulty: Difficulty }, + AILeft { index: usize }, +} + +#[derive(Clone, Serialize, Debug)] +#[serde(tag = "type")] +pub enum GameEvent { + TurnAction { state: ApiState, committed: bool }, +} + +#[derive(Clone, Serialize, Debug)] +#[serde(tag = "type")] +pub enum ServerToClientMessage { + RoomChange { event: RoomEvent, info: PartyInfo }, + GameEvent { event: GameEvent }, + GameError { error: Error }, + Invalid { reason: String }, +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +pub enum GameMove { + Pass, + Exchange { + tiles: Vec, + }, + Play { + played_tiles: Vec>, + commit_move: bool, + }, + AddToDictionary { + word: String, + }, +} + +#[derive(Deserialize, Debug)] +#[serde(tag = "type")] +pub enum ClientToServerMessage { + Load, + StartGame, + GameMove { r#move: GameMove }, + AddAI { difficulty: Difficulty }, + RemoveAI { index: usize }, +} + +#[derive(Clone, Debug)] +pub enum InnerRoomMessage { + PassThrough(ServerToClientMessage), + GameEvent(Option), +} + +pub type RoomMap = HashMap>>; + +pub fn reject_websocket_with_reason( + ws: ws::WebSocket, + close_code: CloseCode, + reason: String, +) -> ws::Channel<'static> { + ws.channel(move |mut stream| { + Box::pin(async move { + let closeframe = CloseFrame { + code: close_code, + reason: reason.into(), + }; + let _ = stream.close(Some(closeframe)).await; + + Ok(()) + }) + }) +} diff --git a/server/src/main.rs b/server/src/main.rs index 58239eb..9c8654c 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,125 +10,19 @@ use rocket::tokio::sync::broadcast::Sender; use rocket::tokio::sync::{Mutex, RwLock}; use rocket::tokio::time::interval; use rocket::{tokio, State}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::sync::{Arc, LazyLock, Weak}; +use server::{ + escape_characters, reject_websocket_with_reason, ClientToServerMessage, GameEvent, GameMove, + InnerRoomMessage, Player, Room, RoomEvent, RoomMap, ServerToClientMessage, DICTIONARY, +}; +use std::sync::Arc; use std::time::Duration; use tokio::select; -use word_grid::api::{APIGame, ApiState, Update}; -use word_grid::dictionary::{Dictionary, DictionaryImpl}; -use word_grid::game::{Error, Game, PlayedTile}; -use word_grid::player_interaction::ai::Difficulty; -use ws::frame::{CloseCode, CloseFrame}; +use word_grid::api::{APIGame, Update}; +use word_grid::game::Game; +use ws::frame::CloseCode; use ws::stream::DuplexStream; use ws::Message; -static DICTIONARY: LazyLock = - LazyLock::new(|| DictionaryImpl::create_from_path("../resources/dictionary.csv")); - -fn escape_characters(x: &str) -> String { - let x = x - .replace("&", "&") - .replace("<", "<") - .replace(">", ">"); - - x -} - -#[derive(Clone, Debug, Serialize, PartialEq)] -struct Player { - name: String, -} - -#[derive(Clone, Serialize, Debug)] -struct PartyInfo { - ais: Vec, - players: Vec, -} - -impl PartyInfo { - fn new(host: Player) -> Self { - Self { - ais: Vec::new(), - players: vec![host], - } - } -} - -struct Room { - party_info: PartyInfo, - game: Option, - sender: Sender<(Option, InnerRoomMessage)>, -} - -impl Room { - fn new(host: Player) -> Self { - Self { - party_info: PartyInfo::new(host), - game: None, - sender: Sender::new(5), - } - } -} - -#[derive(Clone, Serialize, Debug)] -#[serde(tag = "type")] -enum RoomEvent { - PlayerJoined { player: Player }, - PlayerLeft { player: Player }, - AIJoined { difficulty: Difficulty }, - AILeft { index: usize }, -} - -#[derive(Clone, Serialize, Debug)] -#[serde(tag = "type")] -enum GameEvent { - TurnAction { state: ApiState, committed: bool }, -} - -#[derive(Clone, Serialize, Debug)] -#[serde(tag = "type")] -enum ServerToClientMessage { - RoomChange { event: RoomEvent, info: PartyInfo }, - GameEvent { event: GameEvent }, - GameError { error: Error }, - Invalid { reason: String }, -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "type")] -enum GameMove { - Pass, - Exchange { - tiles: Vec, - }, - Play { - played_tiles: Vec>, - commit_move: bool, - }, - AddToDictionary { - word: String, - }, -} - -#[derive(Deserialize, Debug)] -#[serde(tag = "type")] -enum ClientToServerMessage { - Load, - StartGame, - GameMove { r#move: GameMove }, - AddAI { difficulty: Difficulty }, - RemoveAI { index: usize }, -} - -#[derive(Clone, Debug)] -enum InnerRoomMessage { - PassThrough(ServerToClientMessage), - GameEvent(Option), -} - -type RoomMap = HashMap>>; - async fn incoming_message_handler( message: Option>, sender: &Sender<(Option, InnerRoomMessage)>, @@ -357,24 +251,6 @@ async fn outgoing_message_handler( } } -fn reject_websocket_with_reason( - ws: ws::WebSocket, - close_code: CloseCode, - reason: String, -) -> ws::Channel<'static> { - ws.channel(move |mut stream| { - Box::pin(async move { - let closeframe = CloseFrame { - code: close_code, - reason: reason.into(), - }; - let _ = stream.close(Some(closeframe)).await; - - Ok(()) - }) - }) -} - #[get("/room/?")] async fn room( id: &str,