improve ui
This commit is contained in:
58
app/src/lib/join.svelte
Normal file
58
app/src/lib/join.svelte
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ClipboardCopy } from 'lucide-svelte';
|
||||||
|
|
||||||
|
let joinCode = $state('');
|
||||||
|
|
||||||
|
let {
|
||||||
|
class: className = '',
|
||||||
|
roomCode,
|
||||||
|
createRoom,
|
||||||
|
joinRoom
|
||||||
|
}: {
|
||||||
|
roomCode: string;
|
||||||
|
createRoom: () => void;
|
||||||
|
joinRoom: (code: string) => void;
|
||||||
|
class: string;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="{className} rounded-lg glass bg-primary flex flex-col items-center justify-center font-mono"
|
||||||
|
>
|
||||||
|
<div class="space-y-4 max-w-[70%]">
|
||||||
|
{#if roomCode}
|
||||||
|
<div class="space-x-2 flex flex-row justify-center items-center">
|
||||||
|
<div class="text-3xl font-bold tracking-widest font-mono bg-accent py-3 rounded-full px-12">
|
||||||
|
{roomCode}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-accent btn-circle size-16"
|
||||||
|
onclick={() => navigator.clipboard.writeText(roomCode)}
|
||||||
|
>
|
||||||
|
<ClipboardCopy />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button onclick={() => createRoom()} class="w-full btn btn-neutral text-xl">
|
||||||
|
Create Room
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<div class="text-center text-lg text-primary-content">OR</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter code"
|
||||||
|
maxlength="4"
|
||||||
|
bind:value={joinCode}
|
||||||
|
class="input input-bordered input-primary uppercase tracking-widest placeholder-primary text-neutral text-center font-bold text-xl lg:text-3xl w-full glass"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onclick={() => joinRoom(joinCode)}
|
||||||
|
class="w-full btn btn-outline btn-neutral text-neutral hover:text-neutral hover:bg-transparent text-xl"
|
||||||
|
>
|
||||||
|
Join Room
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -1,6 +1,6 @@
|
|||||||
import { io, Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
|
|
||||||
export type Phase = 'placement' | 'battle' | 'gameover';
|
export type Phase = 'placement' | 'waiting' | 'selfturn' | 'otherturn';
|
||||||
export type CellType = 'e' | 's' | 'h' | 'm'; // empty, ship, hit, miss
|
export type CellType = 'e' | 's' | 'h' | 'm'; // empty, ship, hit, miss
|
||||||
|
|
||||||
export class State {
|
export class State {
|
||||||
@@ -16,14 +16,17 @@ export class State {
|
|||||||
transports: ['websocket']
|
transports: ['websocket']
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('created-room', (room: string) => {
|
this.socket.on('joined-room', (room: string) => {
|
||||||
|
this.phase = 'waiting';
|
||||||
this.room = room;
|
this.room = room;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('upload', (_, callback) => {
|
this.socket.on('upload', (_, callback) => {
|
||||||
callback(this.playerBoard.board);
|
callback(this.playerBoard.board);
|
||||||
});
|
});
|
||||||
this.socket.on('turnover', (id) => {
|
this.socket.on('turnover', (id) => {
|
||||||
this.turn = id != this.socket.id;
|
this.turn = id != this.socket.id;
|
||||||
|
this.phase = this.turn ? 'selfturn' : 'otherturn';
|
||||||
});
|
});
|
||||||
this.socket.on('attacked', ({ by, at, hit, sunk }) => {
|
this.socket.on('attacked', ({ by, at, hit, sunk }) => {
|
||||||
const [i, j]: [number, number] = at;
|
const [i, j]: [number, number] = at;
|
||||||
@@ -33,7 +36,17 @@ export class State {
|
|||||||
} else {
|
} else {
|
||||||
this.turn = !hit;
|
this.turn = !hit;
|
||||||
}
|
}
|
||||||
board.board[i][j] = hit ? 'h' : 'm';
|
if (hit) {
|
||||||
|
board.board[i][j] = 'h';
|
||||||
|
for (let [x, y] of [[-1, -1], [1, 1], [1, -1], [-1, 1]]) {
|
||||||
|
const [tx, ty] = [i + x, j + y];
|
||||||
|
if (tx < 0 || tx >= 10 || ty < 0 || ty >= 10) continue;
|
||||||
|
if (board.board[tx][ty] == 'e')
|
||||||
|
board.board[tx][ty] = 'm';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
board.board[i][j] = 'm';
|
||||||
|
}
|
||||||
if (sunk) {
|
if (sunk) {
|
||||||
const [[minx, miny], [maxx, maxy]] = sunk;
|
const [[minx, miny], [maxx, maxy]] = sunk;
|
||||||
const x1 = Math.max(0, minx - 1);
|
const x1 = Math.max(0, minx - 1);
|
||||||
@@ -63,9 +76,14 @@ export class State {
|
|||||||
this.socket.emit('create');
|
this.socket.emit('create');
|
||||||
}
|
}
|
||||||
|
|
||||||
joinRoom() {
|
joinRoom(code: string) {
|
||||||
if (this.room.length != 4) return;
|
code = code.toUpperCase();
|
||||||
this.socket.emit('join', this.room);
|
if (code.length != 4 || code == this.room) return;
|
||||||
|
this.socket.emit('join', code);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNotStarted() {
|
||||||
|
return this.phase == 'placement' || this.phase == 'waiting';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Board from '$lib/board.svelte';
|
import Board from '$lib/board.svelte';
|
||||||
import Header from '$lib/header.svelte';
|
import Header from '$lib/header.svelte';
|
||||||
|
import Join from '$lib/join.svelte';
|
||||||
import { State } from '$lib/state.svelte';
|
import { State } from '$lib/state.svelte';
|
||||||
|
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
@@ -14,8 +15,8 @@
|
|||||||
<main class="bg-base-100 shadow-xl rounded-xl overflow-hidden">
|
<main class="bg-base-100 shadow-xl rounded-xl overflow-hidden">
|
||||||
<div class="p-6 space-y-6">
|
<div class="p-6 space-y-6">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h2 class="text-2xl font-semibold">
|
<h2 class="text-2xl font-semibold rounded-full bg-base-300 py-3 px-6">
|
||||||
{gameState.phase === 'placement' ? 'Place Your Ships' : 'Battle Phase'}
|
{gameState.hasNotStarted() ? 'Place your ships' : 'Battle Phase'}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="flex space-x-4">
|
<div class="flex space-x-4">
|
||||||
<div class="text-blue-600">Your Ships: {5}</div>
|
<div class="text-blue-600">Your Ships: {5}</div>
|
||||||
@@ -30,28 +31,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-lg font-medium mb-2">Opponent's Board</h3>
|
<h3 class="text-lg font-medium mb-2">Opponent's Board</h3>
|
||||||
<Board board={gameState.opponentBoard} callback={(i, j) => gameState.attack(i, j)} />
|
<div class="relative">
|
||||||
|
<Board board={gameState.opponentBoard} callback={(i, j) => gameState.attack(i, j)} />
|
||||||
|
{#if gameState.hasNotStarted()}
|
||||||
|
<Join
|
||||||
|
class="absolute top-[24px] left-[15px] w-[calc(100%-15px)] h-[calc(100%-24px)]"
|
||||||
|
roomCode={gameState.room}
|
||||||
|
createRoom={() => gameState.createRoom()}
|
||||||
|
joinRoom={(code) => gameState.joinRoom(code)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center space-x-4">
|
|
||||||
{#if gameState.phase === 'placement'}
|
|
||||||
<button class="btn btn-primary" onclick={() => gameState.playerBoard.randomize()}
|
|
||||||
>Randomize</button
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<button class="btn btn-primary">Fire!</button>
|
|
||||||
{/if}
|
|
||||||
<button class="btn btn-outline" onclick={() => gameState.createRoom()}>Create Room</button
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
bind:value={gameState.room}
|
|
||||||
placeholder="Code"
|
|
||||||
class="input input-bordered w-full max-w-24 text-center"
|
|
||||||
/>
|
|
||||||
<button class="btn btn-outline" onclick={() => gameState.joinRoom()}>Join Room</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ export default {
|
|||||||
],
|
],
|
||||||
|
|
||||||
daisyui: {
|
daisyui: {
|
||||||
themes: ["cupcake", "night"], // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"]
|
themes: true, // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"]
|
||||||
darkTheme: "cupcake", // name of one of the included themes for dark mode
|
darkTheme: "cupcake", // name of one of the included themes for dark mode
|
||||||
base: true, // applies background color and foreground color for root element by default
|
base: true, // applies background color and foreground color for root element by default
|
||||||
styled: true, // include daisyUI colors and design decisions for all components
|
styled: true, // include daisyUI colors and design decisions for all components
|
||||||
|
@@ -35,6 +35,7 @@ pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> {
|
pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> {
|
||||||
|
let code = code.to_uppercase();
|
||||||
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
|
||||||
@@ -108,9 +109,9 @@ pub async fn start(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let status = if sid.as_str() == player1 {
|
let status = if sid.as_str() == player1 {
|
||||||
Status::P2Turn
|
|
||||||
} else if sid.as_str() == player2 {
|
|
||||||
Status::P1Turn
|
Status::P1Turn
|
||||||
|
} else if sid.as_str() == player2 {
|
||||||
|
Status::P2Turn
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::NotInRoom); // not in room
|
return Err(Error::NotInRoom); // not in room
|
||||||
};
|
};
|
||||||
|
@@ -66,7 +66,7 @@ fn on_connect(socket: SocketRef) {
|
|||||||
}
|
}
|
||||||
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("joined-room", &room).unwrap();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -83,6 +83,8 @@ fn on_connect(socket: SocketRef) {
|
|||||||
}
|
}
|
||||||
socket.leave_all().unwrap();
|
socket.leave_all().unwrap();
|
||||||
socket.join(room.clone()).unwrap();
|
socket.join(room.clone()).unwrap();
|
||||||
|
socket.emit("joined-room", &room).unwrap();
|
||||||
|
|
||||||
if socket.within(room.clone()).sockets().unwrap().len() != 2 {
|
if socket.within(room.clone()).sockets().unwrap().len() != 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user