@@ -242,28 +270,45 @@
{/if}
-
- {#if currentPlayer == 1}
-
- {:else}
-
- {/if}
- is on turn.
- {#if twoPlayer && self == currentPlayer}
- It is YOUR {selfName ? "(" + selfName + ")" : ""} turn.
- {:else if twoPlayer && self != currentPlayer}
- Waiting for {opponentName || "opponent"}...
- {/if}
-
+ {#key currentPlayer}
+
+ {#if currentPlayer == 1}
+
+ {:else}
+
+ {/if}
+ is on turn.
+ {#if twoPlayer && self == currentPlayer}
+ It is YOUR {selfName ? "(" + selfName + ")" : ""} turn.
+ {:else if twoPlayer && self != currentPlayer}
+ Waiting for {opponentName || "opponent"}...
+ {/if}
+
+ {/key}
+ {#if twoPlayer}
+ hoveredPiece = { i: move.i, j: move.j }} on:mouseout={() => { if(hoveredPiece?.j == move.j && hoveredPiece.i == move.i) hoveredPiece = null }} />
{/each}
@@ -278,6 +323,9 @@
.info .moves {
columns: 9.5rem auto;
}
+ .move {
+ @apply p-1 flex gap-2 p-1 items-center leading-none;
+ }
.winner {
@apply absolute inset-4 pointer-events-none;
}
diff --git a/client/src/lib/list.svelte b/client/src/lib/list.svelte
index d950064..db8c8f9 100644
--- a/client/src/lib/list.svelte
+++ b/client/src/lib/list.svelte
@@ -1,4 +1,5 @@
+
+
+
+
+
-
+
+ {self == 1 ? (selfName || "you") : (opponentName || "opponent")}
+
+
+
+ {self == 2 ? (selfName || "you") : (opponentName || "opponent")}
+
+ {/if}
{#each moves as move}
$connection?.refreshList()} on:keydown={() => $connection?.refreshList()} class="reload fixed top-0 left-10 w-4 h-4 m-4 p-2 transform transition-transform rotate-180 hover:rotate-360 active:rotate-540">
+
+
+
+
Rooms
@@ -26,7 +65,7 @@
{#if $list}
-
{#each $list as room}
-
- {room.name} +
- join(room.name)}>{room.name} {/each}
@@ -33,6 +41,9 @@
{/if}
\ No newline at end of file
diff --git a/client/src/lib/websocket.ts b/client/src/lib/websocket.ts
index 87a9e48..cd22ba5 100644
--- a/client/src/lib/websocket.ts
+++ b/client/src/lib/websocket.ts
@@ -1,5 +1,11 @@
import { writable, type Writable } from "svelte/store";
+export class MoveEvent extends Event {
+ constructor(public i: number, public j: number) {
+ super("move");
+ }
+}
+
export class WebsocketConnection extends EventTarget {
ws: WebSocket;
roomName: string | null = null;
@@ -30,7 +36,7 @@ export class WebsocketConnection extends EventTarget {
list.set(null);
});
this.ws.addEventListener("error", (e) => {
- console.error("WS error");
+ console.error("WS error", e);
this.addError("Connection error");
connection.set(null);
room.set(null);
@@ -43,6 +49,7 @@ export class WebsocketConnection extends EventTarget {
case "join": {
messages.update(t => { t.push({ type: "system", content: `${msg.client} joined`});return t})
this.players.add(msg.client);
+ room.update(t => { t!.count = this.players.size; return t });
break;
}
case "joined": {
@@ -55,7 +62,8 @@ export class WebsocketConnection extends EventTarget {
this.roomHost = msg.host;
room.set({
name: msg.name,
- host: msg.host
+ host: msg.host,
+ count: clients.length
});
break;
}
@@ -64,7 +72,8 @@ export class WebsocketConnection extends EventTarget {
this.roomHost = this.name;
room.set({
name: msg.name,
- host: this.name
+ host: this.name,
+ count: 1
});
this.addMessage({ type: "system", content: `${msg.name} created the room`});
break;
@@ -75,6 +84,7 @@ export class WebsocketConnection extends EventTarget {
gameData.set(null);
}
this.players.delete(msg.client);
+ room.update(t => { t!.count = this.players.size; return t });
break;
}
case "host": {
@@ -96,6 +106,30 @@ export class WebsocketConnection extends EventTarget {
listLoading.set(false);
break;
}
+ case "room_created": {
+ list.update(t => { t?.push({ name: msg.name}); return t });
+ break;
+ }
+ case "room_deleted": {
+ list.update(t => {
+ if(!t) return t;
+ var i = t.findIndex(t => t.name == msg.name);
+ if(i == -1) return t;
+ t.splice(i, 1);
+ return t;
+ });
+ break;
+ }
+ case "broadcast": {
+ if(msg.client == this.name) break;
+ switch (msg.d.t) {
+ case "move": {
+ console.log("Dispatching move event", msg.d.i, msg.d.j)
+ this.dispatchEvent(new MoveEvent(msg.d.i, msg.d.j));
+ }
+ }
+ break;
+ }
case "error": {
console.error(msg.e);
this.addError(msg.e);
@@ -143,6 +177,10 @@ export class WebsocketConnection extends EventTarget {
send(data: any) {
this.ws.send(data);
}
+
+ leave() {
+ this.ws.close();
+ }
}
interface ErrorMessage {
@@ -162,6 +200,6 @@ interface SystemMessage {
export const connection: Writable = writable(null);
export const list: Writable<{ name: string }[] | null> = writable(null);
export const listLoading = writable(true);
-export const room: Writable<{ name: string, host: string } | null> = writable(null);
+export const room: Writable<{ name: string, host: string, count: number } | null> = writable(null);
export const messages: Writable<(UserMessage | ErrorMessage | SystemMessage)[]> = writable([]);
export const gameData: Writable<{ log: { p: string, i: number, j: number }[] }|null> = writable(null);
\ No newline at end of file
diff --git a/client/src/routes/+page.svelte b/client/src/routes/+page.svelte
index 19fe789..5f48696 100644
--- a/client/src/routes/+page.svelte
+++ b/client/src/routes/+page.svelte
@@ -1,3 +1,6 @@
+
+ Slightly complicated tictactoe
+
@@ -50,9 +53,18 @@
.chooser > a {
@apply text-black no-underline cursor-pointer w-full p-8 border rounded-lg border-gray-400 border-solid;
}
+ .chooser > a:hover {
+ @apply bg-black/10;
+ }
.rules {
@apply cursor-not-allowed text-gray-500 flex justify-center items-center w-full my-8 p-4 border rounded-lg border-gray-400 border-solid;
}
+ .rules:hover {
+ @apply bg-red-500/3;
+ }
+ .rules:active {
+ @apply bg-red-500/10;
+ }
.icon {
@apply h-20 w-20 mr-4;
}
diff --git a/client/src/routes/localplay/+page.svelte b/client/src/routes/localplay/+page.svelte
index 1e30fea..fde1e13 100644
--- a/client/src/routes/localplay/+page.svelte
+++ b/client/src/routes/localplay/+page.svelte
@@ -3,4 +3,8 @@
+
+ Slightly complicated tictactoe
+
+
\ No newline at end of file
diff --git a/client/src/routes/multiplayer/+page.svelte b/client/src/routes/multiplayer/+page.svelte
index 6390e8f..0a265ee 100644
--- a/client/src/routes/multiplayer/+page.svelte
+++ b/client/src/routes/multiplayer/+page.svelte
@@ -1,14 +1,61 @@
-
+
+ Slightly complicated tictactoe
+
+
{#if !$connection}
{:else if !$room}
{:else}
-
+ t !== $connection?.name)}
+ on:move={addSelfMove}
+ bind:addPlayerMove
+ />
{/if}
\ No newline at end of file
diff --git a/server/src/index.js b/server/src/index.js
index ef47226..f651b3b 100644
--- a/server/src/index.js
+++ b/server/src/index.js
@@ -23,6 +23,18 @@ const rooms = new Map();
*/
const clients = new Map();
+function deleteRoom(room) {
+ rooms.delete(room);
+
+ for(let [, client] of clients) {
+ if(!client.room) {
+ client.connection.send(
+ JSON.stringify({ t: "room_deleted", name: room })
+ )
+ }
+ }
+}
+
require("uWebSockets.js")
.App({})
.ws("/", {
@@ -39,11 +51,15 @@ require("uWebSockets.js")
name.length < 2 ||
name.length > 64 ||
!name.trim()
- )
+ ) {
+ console.log("Invalid name");
return res.end("invalid_name");
+ }
name = name.trim();
- if ([...clients.values()].find((client) => client.name === name))
+ if ([...clients.values()].find((client) => client.name === name)) {
+ console.log("Duplicate name");
return res.end("name_used");
+ }
/* This immediately calls open handler, you must not use res after this call */
res.upgrade(
{
@@ -120,8 +136,15 @@ require("uWebSockets.js")
};
rooms.set(room.name, room);
client.room = room;
+ for(let [, cclient] of clients) {
+ if(!cclient.room) {
+ cclient.connection.send(
+ JSON.stringify({ t: "room_created", name })
+ );
+ }
+ }
return ws.send(
- JSON.stringify({ t: "create", name: name })
+ JSON.stringify({ t: "create", name })
);
}
case "leave": {
@@ -139,7 +162,7 @@ require("uWebSockets.js")
);
room.clients.splice(room.clients.indexOf(client), 1);
if (room.clients.length === 0) {
- rooms.delete(room.name);
+ deleteRoom(room.name);
} else if (room.host == ws) {
room.host = room.clients[0];
room.clients.forEach((client) =>
@@ -233,12 +256,13 @@ require("uWebSockets.js")
JSON.stringify({ t: "error", e: "not_in_room" })
);
+ let clientName = client.name
room.clients.forEach((client) => {
client.connection.send(
JSON.stringify({
t: "broadcast",
- client: client.name,
- data: data.d,
+ client: clientName,
+ d: data.d,
})
);
});
@@ -274,7 +298,7 @@ require("uWebSockets.js")
if (room) {
room.clients.splice(room.clients.indexOf(client), 1);
if (room.clients.length === 0) {
- rooms.delete(room.name);
+ deleteRoom(room.name);
} else if (room.host == ws) {
room.host = room.clients[0];
room.clients.forEach((client) =>