thiserror
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -164,6 +164,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"socketioxide",
|
"socketioxide",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@@ -12,6 +12,7 @@ serde = { version = "1.0.210", features = ["derive"] }
|
|||||||
serde_json = "1.0.128"
|
serde_json = "1.0.128"
|
||||||
socketioxide = { version = "0.14.1", features = ["state", "tracing"] }
|
socketioxide = { version = "0.14.1", features = ["state", "tracing"] }
|
||||||
sqlx = { version = "0.8.2", features = ["macros", "postgres", "runtime-tokio"] }
|
sqlx = { version = "0.8.2", features = ["macros", "postgres", "runtime-tokio"] }
|
||||||
|
thiserror = "1.0.63"
|
||||||
tokio = { version = "1.40.0", features = ["full"] }
|
tokio = { version = "1.40.0", features = ["full"] }
|
||||||
tower-http = { version = "0.5.2", features = ["cors"] }
|
tower-http = { version = "0.5.2", features = ["cors"] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
{:else if cell === 'h'}
|
{:else if cell === 'h'}
|
||||||
<Crosshair class="size-3/5 text-red-500" />
|
<Crosshair class="size-3/5 text-red-500" />
|
||||||
{:else if cell === 'm'}
|
{:else if cell === 'm'}
|
||||||
<div class="size-3/5 bg-gray-300 rounded-full"></div>
|
<div class="size-3/5 bg-blue-400 rounded-full"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
54
src/game.rs
54
src/game.rs
@@ -1,11 +1,30 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use socketioxide::socket::Sid;
|
use socketioxide::socket::Sid;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub const ROOM_CODE_LENGTH: usize = 4;
|
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<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[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!(
|
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",
|
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(),
|
sid.as_str(),
|
||||||
@@ -16,7 +35,7 @@ pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(),
|
|||||||
Ok(())
|
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!(
|
let room = sqlx::query!(
|
||||||
r#"SELECT player1_id, player2_id FROM rooms WHERE code = $1"#,
|
r#"SELECT player1_id, player2_id FROM rooms WHERE code = $1"#,
|
||||||
code
|
code
|
||||||
@@ -27,16 +46,16 @@ pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()
|
|||||||
let sid = sid.as_str();
|
let sid = sid.as_str();
|
||||||
|
|
||||||
if room.player1_id.is_some() && room.player2_id.is_some() {
|
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 let Some(id) = room.player1_id.as_ref() {
|
||||||
if id == sid {
|
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 let Some(id) = room.player2_id.as_ref() {
|
||||||
if id == sid {
|
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(())
|
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!(
|
let query = format!(
|
||||||
"UPDATE players SET board = ARRAY[{}] WHERE id = '{}'",
|
"UPDATE players SET board = ARRAY[{}] WHERE id = '{}'",
|
||||||
board
|
board
|
||||||
@@ -84,11 +103,11 @@ pub async fn add_board(sid: Sid, board: Board, pool: &sqlx::PgPool) -> Result<()
|
|||||||
.join(","),
|
.join(","),
|
||||||
sid.as_str()
|
sid.as_str()
|
||||||
);
|
);
|
||||||
sqlx::query(&query).execute(pool).await.unwrap();
|
sqlx::query(&query).execute(pool).await?;
|
||||||
Ok(())
|
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!(
|
let room = sqlx::query!(
|
||||||
r"SELECT player1_id, player2_id FROM rooms WHERE code = $1",
|
r"SELECT player1_id, player2_id FROM rooms WHERE code = $1",
|
||||||
code
|
code
|
||||||
@@ -97,7 +116,7 @@ pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sq
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let (Some(player1), Some(player2)) = (room.player1_id, room.player2_id) else {
|
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 {
|
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 {
|
} else if sid.as_str() == player2 {
|
||||||
Status::P1Turn
|
Status::P1Turn
|
||||||
} else {
|
} else {
|
||||||
return Err(sqlx::Error::RowNotFound); // not in room
|
return Err(Error::NotInRoom); // not in room
|
||||||
};
|
};
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@@ -118,11 +137,7 @@ pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<(), sq
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn attack(
|
pub async fn attack(sid: Sid, (i, j): (usize, usize), pool: &sqlx::PgPool) -> Result<bool> {
|
||||||
sid: Sid,
|
|
||||||
(i, j): (usize, usize),
|
|
||||||
pool: &sqlx::PgPool,
|
|
||||||
) -> Result<bool, sqlx::Error> {
|
|
||||||
let player = sqlx::query!(r"SELECT room_code FROM players WHERE id = $1", sid.as_str())
|
let player = sqlx::query!(r"SELECT room_code FROM players WHERE id = $1", sid.as_str())
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -141,7 +156,7 @@ pub async fn attack(
|
|||||||
(Some(p1), Some(p2)) if p2 == sid.as_str() && room.stat == Status::P2Turn => {
|
(Some(p1), Some(p2)) if p2 == sid.as_str() && room.stat == Status::P2Turn => {
|
||||||
(p2, p1, Status::P1Turn)
|
(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?;
|
let mut txn = pool.begin().await?;
|
||||||
@@ -182,11 +197,10 @@ pub async fn attack(
|
|||||||
Ok(turn.hit.unwrap() == "s")
|
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())
|
sqlx::query!(r"DELETE FROM players WHERE id = $1", sid.as_str())
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +246,7 @@ impl Board {
|
|||||||
for i in -1..2 {
|
for i in -1..2 {
|
||||||
for j in -1..=length {
|
for j in -1..=length {
|
||||||
let (tx, ty) = if dir { (x + i, y + j) } else { (x + j, y + i) };
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if self.0[tx as usize][ty as usize] != 'e' {
|
if self.0[tx as usize][ty as usize] != 'e' {
|
||||||
|
52
src/main.rs
52
src/main.rs
@@ -1,16 +1,12 @@
|
|||||||
mod game;
|
mod game;
|
||||||
use std::{str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use futures_util::stream::StreamExt;
|
use futures_util::stream::StreamExt;
|
||||||
use game::{add_board, add_room, attack, disconnect, join_room, start, Board, ROOM_CODE_LENGTH};
|
use game::{add_board, add_room, attack, disconnect, join_room, start, Board, ROOM_CODE_LENGTH};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use serde_json::Value;
|
|
||||||
use socketioxide::{
|
use socketioxide::{
|
||||||
adapter::Room,
|
extract::{Data, SocketRef, State},
|
||||||
extract::{AckSender, Data, SocketRef, State},
|
|
||||||
socket::Sid,
|
|
||||||
SocketIo,
|
SocketIo,
|
||||||
};
|
};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
@@ -43,16 +39,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_connect(socket: SocketRef, io: SocketIo) {
|
fn on_connect(socket: SocketRef) {
|
||||||
tracing::info!("Connected: {:?}", socket.id);
|
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(
|
socket.on(
|
||||||
"create",
|
"create",
|
||||||
|socket: SocketRef, pool: State<PgPool>| async move {
|
|socket: SocketRef, pool: State<PgPool>| async move {
|
||||||
@@ -70,7 +58,11 @@ fn on_connect(socket: SocketRef, io: SocketIo) {
|
|||||||
.map(|x| char::to_ascii_uppercase(&(x as char)))
|
.map(|x| char::to_ascii_uppercase(&(x as char)))
|
||||||
.collect();
|
.collect();
|
||||||
tracing::info!("Creating room: {:?}", room);
|
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.leave_all().unwrap();
|
||||||
socket.join(room.clone()).unwrap();
|
socket.join(room.clone()).unwrap();
|
||||||
socket.emit("created-room", &room).unwrap();
|
socket.emit("created-room", &room).unwrap();
|
||||||
@@ -84,7 +76,10 @@ fn on_connect(socket: SocketRef, io: SocketIo) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tracing::info!("Joining room: {:?}", room);
|
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.leave_all().unwrap();
|
||||||
socket.join(room.clone()).unwrap();
|
socket.join(room.clone()).unwrap();
|
||||||
if socket.within(room.clone()).sockets().unwrap().len() != 2 {
|
if socket.within(room.clone()).sockets().unwrap().len() != 2 {
|
||||||
@@ -100,14 +95,21 @@ fn on_connect(socket: SocketRef, io: SocketIo) {
|
|||||||
async move {
|
async move {
|
||||||
match ack {
|
match ack {
|
||||||
Ok(mut 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),
|
Err(err) => tracing::error!("Ack error, {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await;
|
.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");
|
tracing::info!("Game started");
|
||||||
socket
|
socket
|
||||||
.within(room.clone())
|
.within(room.clone())
|
||||||
@@ -119,7 +121,13 @@ fn on_connect(socket: SocketRef, io: SocketIo) {
|
|||||||
socket.on(
|
socket.on(
|
||||||
"attack",
|
"attack",
|
||||||
|socket: SocketRef, Data::<[usize; 2]>([i, j]), pool: State<PgPool>| async move {
|
|socket: SocketRef, Data::<[usize; 2]>([i, j]), pool: State<PgPool>| 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);
|
tracing::info!("Attacking at: ({}, {}), result: {}", i, j, res);
|
||||||
socket
|
socket
|
||||||
.within(socket.rooms().unwrap().first().unwrap().clone())
|
.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<PgPool>| async move {
|
socket.on_disconnect(|socket: SocketRef, pool: State<PgPool>| async move {
|
||||||
tracing::info!("Disconnecting: {:?}", socket.id);
|
tracing::info!("Disconnecting: {:?}", socket.id);
|
||||||
socket.leave_all().unwrap();
|
socket.leave_all().unwrap();
|
||||||
disconnect(socket.id, &pool).await.unwrap();
|
if let Err(e) = disconnect(socket.id, &pool).await {
|
||||||
// TODO: Delete room
|
tracing::error!("{:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user