From cd1a156d3efdf5d2e936a76f904330d3a28b6c8d Mon Sep 17 00:00:00 2001 From: Khadeer Date: Sat, 5 Oct 2024 14:52:41 +0530 Subject: [PATCH] feat: end screen --- app/src/lib/state.svelte.ts | 14 +++++++++++--- app/src/routes/+page.svelte | 31 ++++++++++++++++++++++++++----- migrations/0001_schema.sql | 2 +- src/board.rs | 5 +++++ src/game.rs | 27 +++++++++++++++++++++------ src/main.rs | 6 +++--- 6 files changed, 67 insertions(+), 18 deletions(-) diff --git a/app/src/lib/state.svelte.ts b/app/src/lib/state.svelte.ts index 4e85bea..dc0d48e 100644 --- a/app/src/lib/state.svelte.ts +++ b/app/src/lib/state.svelte.ts @@ -1,6 +1,6 @@ import { io, Socket } from "socket.io-client"; -export type Phase = 'placement' | 'waiting' | 'selfturn' | 'otherturn'; +export type Phase = 'placement' | 'waiting' | 'selfturn' | 'otherturn' | 'gameover'; export type CellType = 'e' | 's' | 'h' | 'm'; // empty, ship, hit, miss export class State { @@ -10,8 +10,11 @@ export class State { users = $state(0); room = $state(''); turn = $state(-1); // -1 not my turn, 0 might be, 1 is + game_over = $state(false); socket: Socket; + play_again_phase = $state(false); + constructor() { const url = import.meta.env.DEV ? 'ws://localhost:3000' : 'wss://battleship.icyground-d91964e0.centralindia.azurecontainerapps.io'; this.socket = io(url, { @@ -37,7 +40,7 @@ export class State { this.turn = (id == this.socket.id) ? 1 : -1; this.phase = this.turn ? 'selfturn' : 'otherturn'; }); - this.socket.on('attacked', ({ by, at, hit, sunk }) => { + this.socket.on('attacked', ({ by, at, hit, sunk, game_over }) => { const [i, j]: [number, number] = at; const board = by == this.socket.id ? this.opponentBoard : this.playerBoard; if (by == this.socket.id) { @@ -70,13 +73,18 @@ export class State { } } } + + this.game_over = game_over; + }); - this.socket.on('restore', ({ turn, player, opponent }: { turn: boolean, player: string[], opponent: string[] }) => { + this.socket.on('restore', ({ turn, player, opponent, game_over }: { turn: boolean, player: string[], opponent: string[], game_over: boolean }) => { this.turn = turn ? 1 : -1; this.phase = this.turn ? 'selfturn' : 'otherturn'; this.playerBoard.board = player.map((s) => s.split('').map(c => c as CellType)); this.opponentBoard.board = opponent.map((s) => s.split('').map(c => c as CellType)); + + this.game_over = game_over; }) } diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index fad9909..c873a02 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -43,10 +43,7 @@
{gameState.users}
- + {/if} @@ -69,13 +66,37 @@ board={gameState.opponentBoard} callback={(i, j) => gameState.attack(i, j)} /> + + {#if gameState.game_over} +
+
+

Game Over

+

+ {gameState.turn >= 0 ? 'You win!' : 'You lose!'} +

+ + +
+
+ {/if} {#if gameState.hasNotStarted()} gameState.createRoom()} joinRoom={(code) => gameState.joinRoom(code)} - leaveRoom={leaveRoom} + {leaveRoom} /> {/if} diff --git a/migrations/0001_schema.sql b/migrations/0001_schema.sql index ebb0ec4..a7cd0d8 100644 --- a/migrations/0001_schema.sql +++ b/migrations/0001_schema.sql @@ -1,5 +1,5 @@ -- DROP OWNED BY CURRENT_USER CASCADE; -CREATE TYPE STAT AS ENUM ('waiting', 'p1turn', 'p2turn'); +CREATE TYPE STAT AS ENUM ('waiting', 'p1turn', 'p2turn', 'gameover'); CREATE TABLE IF NOT EXISTS players ( id CHAR(16) PRIMARY KEY, diff --git a/src/board.rs b/src/board.rs index 0cb83e8..770dac6 100644 --- a/src/board.rs +++ b/src/board.rs @@ -134,6 +134,11 @@ impl Board { self } + pub fn is_game_over(&self) -> bool { + self.iter() + .all(|row| row.iter().all(|cell| matches!(cell, 'e' | 'm' | 'h'))) + } + // fn validate_syntax(&self) -> bool { // self // .iter() diff --git a/src/game.rs b/src/game.rs index ce99ce9..5c033b2 100644 --- a/src/game.rs +++ b/src/game.rs @@ -33,6 +33,7 @@ pub enum Status { Waiting, P1Turn, P2Turn, + GameOver } pub async fn room_if_player_exists(sid: &str, pool: &sqlx::PgPool) -> Result> { @@ -158,7 +159,7 @@ pub async fn get_game_state( sid: &str, room: &str, pool: &sqlx::PgPool, -) -> Result<(bool, Vec, Vec)> { +) -> Result<(bool, Vec, Vec, bool)> { let room_details = sqlx::query!( r#"SELECT player1_id, player2_id, stat AS "stat: Status" FROM rooms WHERE code = $1"#, room @@ -188,7 +189,6 @@ pub async fn get_game_state( .board .unwrap() .into(); - let player_board: Vec = player_board.mark_redundant().into(); let opponent_board: Board = sqlx::query!( r#"SELECT board FROM players WHERE id = $1 AND room_code = $2"#, @@ -200,6 +200,11 @@ pub async fn get_game_state( .board .unwrap() .into(); + + let game_over = player_board.is_game_over() || opponent_board.is_game_over(); + + let player_board: Vec = player_board.mark_redundant().into(); + let opponent_board: Vec = opponent_board.mark_redundant().into(); let opponent_board: Vec = opponent_board .into_iter() @@ -210,7 +215,7 @@ pub async fn get_game_state( }) .collect::>(); - Ok((turn, player_board, opponent_board)) + Ok((turn, player_board, opponent_board, game_over)) } pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> { @@ -247,7 +252,7 @@ pub async fn attack( sid: Sid, (i, j): (usize, usize), pool: &sqlx::PgPool, -) -> Result<(bool, Option<[(usize, usize); 2]>)> { +) -> Result<(bool, Option<[(usize, usize); 2]>, bool)> { let player = sqlx::query!(r"SELECT room_code FROM players WHERE id = $1", sid.as_str()) .fetch_one(pool) .await?; @@ -302,9 +307,19 @@ pub async fn attack( .execute(&mut *txn) .await?; } - + let game_over = board.is_game_over(); + if game_over { + sqlx::query!( + r#"UPDATE rooms SET stat = $1 WHERE code = $2"#, + Status::GameOver as Status, + player.room_code + ) + .execute(&mut *txn) + .await?; + } + txn.commit().await?; - Ok((hit, if hit { board.has_sunk((i, j)) } else { None })) + Ok((hit, if hit { board.has_sunk((i, j)) } else { None }, game_over)) } pub async fn update_sid(oldsid: &str, newsid: &str, pool: &sqlx::PgPool) -> Result<()> { diff --git a/src/main.rs b/src/main.rs index 39b5a7c..80e9ee7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ async fn on_connect(socket: SocketRef, Data(auth): Data, pool: Stat socket .emit( "restore", - serde_json::json!({"turn": data.0, "player": data.1, "opponent": data.2}), + serde_json::json!({"turn": data.0, "player": data.1, "opponent": data.2, "game_over": data.3}), ) .unwrap(); socket.join(room.clone()).unwrap(); @@ -170,7 +170,7 @@ async fn on_connect(socket: SocketRef, Data(auth): Data, pool: Stat socket.on( "attack", |socket: SocketRef, Data::<[usize; 2]>([i, j]), pool: State| async move { - let (hit, sunk) = match attack(socket.id, (i, j), &pool).await { + let (hit, sunk, game_over) = match attack(socket.id, (i, j), &pool).await { Ok(res) => res, Err(e) => { tracing::error!("{:?}", e); @@ -182,7 +182,7 @@ async fn on_connect(socket: SocketRef, Data(auth): Data, pool: Stat .within(socket.rooms().unwrap().first().unwrap().clone()) .emit( "attacked", - serde_json::json!({"by": socket.id.as_str(), "at": [i, j], "hit": hit, "sunk": sunk}), + serde_json::json!({"by": socket.id.as_str(), "at": [i, j], "hit": hit, "sunk": sunk, "game_over": game_over}), ) .unwrap(); },