diff --git a/Cargo.lock b/Cargo.lock index 805e95d..8ca753e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,7 @@ dependencies = [ "serde_json", "socketioxide", "sqlx", + "thiserror", "tokio", "tower-http", "tracing", diff --git a/Cargo.toml b/Cargo.toml index ec78f89..c0e54d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" socketioxide = { version = "0.14.1", features = ["state", "tracing"] } sqlx = { version = "0.8.2", features = ["macros", "postgres", "runtime-tokio"] } +thiserror = "1.0.63" tokio = { version = "1.40.0", features = ["full"] } tower-http = { version = "0.5.2", features = ["cors"] } tracing = "0.1.40" diff --git a/app/src/lib/board.svelte b/app/src/lib/board.svelte index 3d344aa..7273966 100644 --- a/app/src/lib/board.svelte +++ b/app/src/lib/board.svelte @@ -19,7 +19,7 @@ {:else if cell === 'h'} {:else if cell === 'm'} -
+
{/if} {/each} diff --git a/src/game.rs b/src/game.rs index ef4e838..fd5cbe6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,11 +1,30 @@ +use std::convert::Infallible; + use axum::Json; use rand::Rng; use serde::Deserialize; use socketioxide::socket::Sid; +use thiserror::Error; pub const ROOM_CODE_LENGTH: usize = 4; -pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> { +pub type Result = std::result::Result; + +#[derive(Debug, Error)] +pub enum Error { + #[error("Room full")] + RoomFull, + #[error("Room not full")] + RoomNotFull, + #[error("Already in room")] + AlreadyInRoom, + #[error("Not in room")] + NotInRoom, + #[error("SQL Error\n{0:?}")] + Sqlx(#[from] sqlx::Error), +} + +pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> { sqlx::query!( r"WITH new_user AS (INSERT INTO players (id, room_code) VALUES ($1, $2) RETURNING id) INSERT INTO rooms (player1_id, code) SELECT $1, $2 FROM new_user", sid.as_str(), @@ -16,7 +35,7 @@ pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), Ok(()) } -pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> { +pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> { let room = sqlx::query!( r#"SELECT player1_id, player2_id FROM rooms WHERE code = $1"#, code @@ -27,16 +46,16 @@ pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<() let sid = sid.as_str(); if room.player1_id.is_some() && room.player2_id.is_some() { - return Err(sqlx::Error::RowNotFound); // room full + return Err(Error::RoomFull); } if let Some(id) = room.player1_id.as_ref() { if id == sid { - return Err(sqlx::Error::RowNotFound); // already in room + return Err(Error::AlreadyInRoom); } } if let Some(id) = room.player2_id.as_ref() { if id == sid { - return Err(sqlx::Error::RowNotFound); // already in room + return Err(Error::AlreadyInRoom); } } @@ -65,7 +84,7 @@ pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<() Ok(()) } -pub async fn add_board(sid: Sid, board: Board, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> { +pub async fn add_board(sid: Sid, board: Board, pool: &sqlx::PgPool) -> Result<()> { let query = format!( "UPDATE players SET board = ARRAY[{}] WHERE id = '{}'", board @@ -84,11 +103,11 @@ pub async fn add_board(sid: Sid, board: Board, pool: &sqlx::PgPool) -> Result<() .join(","), sid.as_str() ); - sqlx::query(&query).execute(pool).await.unwrap(); + sqlx::query(&query).execute(pool).await?; Ok(()) } -pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> { +pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> { let room = sqlx::query!( r"SELECT player1_id, player2_id FROM rooms WHERE code = $1", code @@ -97,7 +116,7 @@ pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sq .await?; let (Some(player1), Some(player2)) = (room.player1_id, room.player2_id) else { - return Err(sqlx::Error::RowNotFound); // room not full + return Err(Error::RoomNotFull); // room not full }; let status = if sid.as_str() == player1 { @@ -105,7 +124,7 @@ pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sq } else if sid.as_str() == player2 { Status::P1Turn } else { - return Err(sqlx::Error::RowNotFound); // not in room + return Err(Error::NotInRoom); // not in room }; sqlx::query!( @@ -118,11 +137,7 @@ pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sq Ok(()) } -pub async fn attack( - sid: Sid, - (i, j): (usize, usize), - pool: &sqlx::PgPool, -) -> Result { +pub async fn attack(sid: Sid, (i, j): (usize, usize), pool: &sqlx::PgPool) -> Result { let player = sqlx::query!(r"SELECT room_code FROM players WHERE id = $1", sid.as_str()) .fetch_one(pool) .await?; @@ -141,7 +156,7 @@ pub async fn attack( (Some(p1), Some(p2)) if p2 == sid.as_str() && room.stat == Status::P2Turn => { (p2, p1, Status::P1Turn) } - _ => return Err(sqlx::Error::RowNotFound), // room not full + _ => return Err(Error::RoomNotFull), // room not full }; let mut txn = pool.begin().await?; @@ -182,11 +197,10 @@ pub async fn attack( Ok(turn.hit.unwrap() == "s") } -pub async fn disconnect(sid: Sid, pool: &sqlx::PgPool) -> Result<(), sqlx::Error> { +pub async fn disconnect(sid: Sid, pool: &sqlx::PgPool) -> Result<()> { sqlx::query!(r"DELETE FROM players WHERE id = $1", sid.as_str()) .execute(pool) - .await - .unwrap(); + .await?; Ok(()) } @@ -232,7 +246,7 @@ impl Board { for i in -1..2 { for j in -1..=length { let (tx, ty) = if dir { (x + i, y + j) } else { (x + j, y + i) }; - if tx < 0 || tx >= 10 || ty < 0 || ty >= 10 { + if !(0..10).contains(&tx) || !(0..10).contains(&ty) { continue; } if self.0[tx as usize][ty as usize] != 'e' { diff --git a/src/main.rs b/src/main.rs index f1ff68f..8c71bc2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,12 @@ mod game; -use std::{str::FromStr, sync::Arc}; use axum::Router; use dotenv::dotenv; use futures_util::stream::StreamExt; use game::{add_board, add_room, attack, disconnect, join_room, start, Board, ROOM_CODE_LENGTH}; use rand::Rng; -use serde_json::Value; use socketioxide::{ - adapter::Room, - extract::{AckSender, Data, SocketRef, State}, - socket::Sid, + extract::{Data, SocketRef, State}, SocketIo, }; use sqlx::PgPool; @@ -43,16 +39,8 @@ async fn main() -> Result<(), Box> { Ok(()) } -fn on_connect(socket: SocketRef, io: SocketIo) { +fn on_connect(socket: SocketRef) { tracing::info!("Connected: {:?}", socket.id); - // tracing::info!( - // "All rooms and sockets: {:?}", - // io.rooms() - // .unwrap() - // .iter() - // .map(|room| { (room, io.within(room.clone()).sockets().unwrap()) }) - // ); - socket.on( "create", |socket: SocketRef, pool: State| async move { @@ -70,7 +58,11 @@ fn on_connect(socket: SocketRef, io: SocketIo) { .map(|x| char::to_ascii_uppercase(&(x as char))) .collect(); tracing::info!("Creating room: {:?}", room); - add_room(socket.id, room.clone(), &pool).await.unwrap(); + + if let Err(e) = add_room(socket.id, room.clone(), &pool).await { + tracing::error!("{:?}", e); + return; + } socket.leave_all().unwrap(); socket.join(room.clone()).unwrap(); socket.emit("created-room", &room).unwrap(); @@ -84,7 +76,10 @@ fn on_connect(socket: SocketRef, io: SocketIo) { return; } tracing::info!("Joining room: {:?}", room); - join_room(socket.id, room.clone(), &pool).await.unwrap(); + if let Err(e) = join_room(socket.id, room.clone(), &pool).await { + tracing::error!("{:?}", e); + return; + } socket.leave_all().unwrap(); socket.join(room.clone()).unwrap(); if socket.within(room.clone()).sockets().unwrap().len() != 2 { @@ -100,14 +95,21 @@ fn on_connect(socket: SocketRef, io: SocketIo) { async move { match ack { Ok(mut ack) => { - add_board(id, ack.data.pop().unwrap(), &pool).await.unwrap(); + if let Err(e) = add_board(id, ack.data.pop().unwrap(), &pool).await + { + tracing::error!("{:?}", e); + return; + } } Err(err) => tracing::error!("Ack error, {}", err), } } }) .await; - start(socket.id, room.clone(), &pool).await.unwrap(); + if let Err(e) = start(socket.id, room.clone(), &pool).await { + tracing::error!("{:?}", e); + return; + } tracing::info!("Game started"); socket .within(room.clone()) @@ -119,7 +121,13 @@ fn on_connect(socket: SocketRef, io: SocketIo) { socket.on( "attack", |socket: SocketRef, Data::<[usize; 2]>([i, j]), pool: State| async move { - let res = attack(socket.id, (i, j), &pool).await.unwrap(); + let res = match attack(socket.id, (i, j), &pool).await { + Ok(res) => res, + Err(e) => { + tracing::error!("{:?}", e); + return; + } + }; tracing::info!("Attacking at: ({}, {}), result: {}", i, j, res); socket .within(socket.rooms().unwrap().first().unwrap().clone()) @@ -134,7 +142,9 @@ fn on_connect(socket: SocketRef, io: SocketIo) { socket.on_disconnect(|socket: SocketRef, pool: State| async move { tracing::info!("Disconnecting: {:?}", socket.id); socket.leave_all().unwrap(); - disconnect(socket.id, &pool).await.unwrap(); - // TODO: Delete room + if let Err(e) = disconnect(socket.id, &pool).await { + tracing::error!("{:?}", e); + return; + } }); }