mirror of
https://github.com/danbulant/slightlyComplicatedTicTacToe
synced 2026-06-22 00:02:21 +00:00
working multiplayer for desktop
This commit is contained in:
parent
9dc60925f0
commit
c7df814200
9 changed files with 318 additions and 48 deletions
|
|
@ -1,5 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { quadOut } from "svelte/easing";
|
||||
import { fly } from "svelte/transition";
|
||||
import Move from "./move.svelte";
|
||||
|
||||
export var self: 1 | 2 = 1;
|
||||
|
|
@ -72,6 +74,24 @@
|
|||
updateContainerStates();
|
||||
}
|
||||
|
||||
function addPlayerMove(player: 1 | 2, i: number, j: number) {
|
||||
if(moves.find(move => move.i == i && move.j == j)) {
|
||||
console.error("DESYNC! Repeated move", player, i, j);
|
||||
return;
|
||||
}
|
||||
if(currentContainer !== i) {
|
||||
console.error("DESYNC! Invalid move (container not active)!", player, i, j, "current container:", i);
|
||||
return;
|
||||
}
|
||||
|
||||
moves.push({ p: player, i, j });
|
||||
moves = moves;
|
||||
|
||||
updateContainerStates();
|
||||
}
|
||||
|
||||
export { addPlayerMove };
|
||||
|
||||
function updateContainerStates() {
|
||||
for(var i in containerStates) {
|
||||
if(containerStates[i]) continue;
|
||||
|
|
@ -138,9 +158,15 @@
|
|||
moves = [];
|
||||
containerStates = new Array(9).fill(0);
|
||||
}
|
||||
|
||||
function check(e: MouseEvent) {
|
||||
if(twoPlayer) return;
|
||||
var confirmed = confirm("Are you sure you want to quit?");
|
||||
if(!confirmed) return e.preventDefault() || false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<a href="/" class="arrow-back fixed top-0 left-0 w-4 h-4 m-4 p-2 transform transition-transform hover:-translate-x-1">
|
||||
<a href="/" on:click={check} class="arrow-back fixed top-0 left-0 w-4 h-4 m-4 p-2 transform transition-transform hover:-translate-x-1">
|
||||
<svg width="16" height="16">
|
||||
<line y1="50%" x1="0" y2="50%" x2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="100%" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
|
|
@ -148,18 +174,20 @@
|
|||
</svg>
|
||||
</a>
|
||||
|
||||
<div on:click={reset} 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">
|
||||
<svg fill="currentColor" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 489.645 489.645" xml:space="preserve" class="w-full h-full">
|
||||
<g>
|
||||
<path d="M460.656,132.911c-58.7-122.1-212.2-166.5-331.8-104.1c-9.4,5.2-13.5,16.6-8.3,27c5.2,9.4,16.6,13.5,27,8.3
|
||||
c99.9-52,227.4-14.9,276.7,86.3c65.4,134.3-19,236.7-87.4,274.6c-93.1,51.7-211.2,17.4-267.6-70.7l69.3,14.5
|
||||
c10.4,2.1,21.8-4.2,23.9-15.6c2.1-10.4-4.2-21.8-15.6-23.9l-122.8-25c-20.6-2-25,16.6-23.9,22.9l15.6,123.8
|
||||
c1,10.4,9.4,17.7,19.8,17.7c12.8,0,20.8-12.5,19.8-23.9l-6-50.5c57.4,70.8,170.3,131.2,307.4,68.2
|
||||
C414.856,432.511,548.256,314.811,460.656,132.911z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
{#if !twoPlayer}
|
||||
<div on:click={reset} 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">
|
||||
<svg fill="currentColor" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 489.645 489.645" xml:space="preserve" class="w-full h-full">
|
||||
<g>
|
||||
<path d="M460.656,132.911c-58.7-122.1-212.2-166.5-331.8-104.1c-9.4,5.2-13.5,16.6-8.3,27c5.2,9.4,16.6,13.5,27,8.3
|
||||
c99.9-52,227.4-14.9,276.7,86.3c65.4,134.3-19,236.7-87.4,274.6c-93.1,51.7-211.2,17.4-267.6-70.7l69.3,14.5
|
||||
c10.4,2.1,21.8-4.2,23.9-15.6c2.1-10.4-4.2-21.8-15.6-23.9l-122.8-25c-20.6-2-25,16.6-23.9,22.9l15.6,123.8
|
||||
c1,10.4,9.4,17.7,19.8,17.7c12.8,0,20.8-12.5,19.8-23.9l-6-50.5c57.4,70.8,170.3,131.2,307.4,68.2
|
||||
C414.856,432.511,548.256,314.811,460.656,132.911z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<main class:disabled={overallState} class="flex flex-wrap min-h-100vh min-w-full items-center">
|
||||
<div class="board relative p-8">
|
||||
|
|
@ -242,28 +270,45 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="absolute top-200 left-0 right-0 text-center">
|
||||
{#if currentPlayer == 1}
|
||||
<svg width="16" height="16" class="text-red-500">
|
||||
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
{:else}
|
||||
<svg width="16" height="16" class="text-blue-500">
|
||||
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
{/if}
|
||||
is on turn.
|
||||
{#if twoPlayer && self == currentPlayer}
|
||||
<b>It is <span class:text-red-500={currentPlayer == 1} class:text-blue-500={currentPlayer == 2}>YOUR</span> {selfName ? "(" + selfName + ")" : ""} turn.</b>
|
||||
{:else if twoPlayer && self != currentPlayer}
|
||||
Waiting for {opponentName || "opponent"}...
|
||||
{/if}
|
||||
</div>
|
||||
{#key currentPlayer}
|
||||
<div class="absolute top-200 left-0 right-0 text-center" in:fly={{ delay: 300, duration: 300, easing: quadOut, opacity: 0, y: 30 }} out:fly={{ delay: 0, duration: 300, easing: quadOut, opacity: 0, y: -30 }}>
|
||||
{#if currentPlayer == 1}
|
||||
<svg width="16" height="16" class="text-red-500">
|
||||
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
{:else}
|
||||
<svg width="16" height="16" class="text-blue-500">
|
||||
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
{/if}
|
||||
is on turn.
|
||||
{#if twoPlayer && self == currentPlayer}
|
||||
<b>It is <span class:text-red-500={currentPlayer == 1} class:text-blue-500={currentPlayer == 2}>YOUR</span> {selfName ? "(" + selfName + ")" : ""} turn.</b>
|
||||
{:else if twoPlayer && self != currentPlayer}
|
||||
Waiting for <b class:text-red-500={currentPlayer == 1} class:text-blue-500={currentPlayer == 2}>{opponentName || "opponent"}</b>...
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<div class="info min-w-38 px-4 h-full overflow-y-auto <md:w-full">
|
||||
<div class="moves">
|
||||
{#if twoPlayer}
|
||||
<div class="move text-red-500">
|
||||
<svg width="16" height="16">
|
||||
<line x1="0" y1="0" x2="100%" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line x1="100%" y1="0" x2="0" y2="100%" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
{self == 1 ? (selfName || "you") : (opponentName || "opponent")}
|
||||
</div>
|
||||
<div class="move text-blue-500">
|
||||
<svg width="16" height="16">
|
||||
<circle cx="50%" cy="50%" r="45%" stroke="currentColor" stroke-width="2" fill="none" />
|
||||
</svg>
|
||||
{self == 2 ? (selfName || "you") : (opponentName || "opponent")}
|
||||
</div>
|
||||
{/if}
|
||||
{#each moves as move}
|
||||
<Move player={move.p} board={move.i} piece={move.j} on:mouseover={() => 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { quadOut } from "svelte/easing";
|
||||
import { fade, fly } from "svelte/transition";
|
||||
import { connection, list } from "./websocket";
|
||||
|
|
@ -6,16 +7,54 @@
|
|||
const duration = 400;
|
||||
|
||||
let createRoomOpen = false;
|
||||
let roomName = "";
|
||||
let roomName = $connection?.name ? `${$connection.name}'s room` : "";
|
||||
|
||||
function create() {
|
||||
console.log("create room", roomName);
|
||||
$connection!.createGame(roomName);
|
||||
}
|
||||
|
||||
function join(roomName: string) {
|
||||
console.log("join room", roomName);
|
||||
$connection!.join(roomName);
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
$connection?.leave();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
let i = setInterval(() => {
|
||||
$connection?.refreshList();
|
||||
}, 10000); // should usually be synchronized just fine, this is to prevent desynchronization (and also works as a kind of ping)
|
||||
|
||||
return () => clearInterval(i);
|
||||
})
|
||||
</script>
|
||||
|
||||
<a href="/multiplayer" on:click={disconnect} class="arrow-back fixed top-0 left-0 w-4 h-4 m-4 mt-8 p-2 transform transition-transform hover:-translate-x-1">
|
||||
<svg width="16" height="16">
|
||||
<line y1="50%" x1="0" y2="50%" x2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="100%" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="0" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div on:click={() => $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">
|
||||
<svg fill="currentColor" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 489.645 489.645" xml:space="preserve" class="w-full h-full">
|
||||
<g>
|
||||
<path d="M460.656,132.911c-58.7-122.1-212.2-166.5-331.8-104.1c-9.4,5.2-13.5,16.6-8.3,27c5.2,9.4,16.6,13.5,27,8.3
|
||||
c99.9-52,227.4-14.9,276.7,86.3c65.4,134.3-19,236.7-87.4,274.6c-93.1,51.7-211.2,17.4-267.6-70.7l69.3,14.5
|
||||
c10.4,2.1,21.8-4.2,23.9-15.6c2.1-10.4-4.2-21.8-15.6-23.9l-122.8-25c-20.6-2-25,16.6-23.9,22.9l15.6,123.8
|
||||
c1,10.4,9.4,17.7,19.8,17.7c12.8,0,20.8-12.5,19.8-23.9l-6-50.5c57.4,70.8,170.3,131.2,307.4,68.2
|
||||
C414.856,432.511,548.256,314.811,460.656,132.911z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<main class="flex flex-col w-100vw h-100vh">
|
||||
<div class="p-4 flex justify-between border-b border-b-black border-b-solid">
|
||||
<div class="p-4 pl-16 flex justify-between border-b border-b-black border-b-solid">
|
||||
<h1 in:fly={{ delay: 0, duration, opacity: 0, y: 100, easing: quadOut }}>Rooms</h1>
|
||||
<div class="right">
|
||||
<button in:fly={{ delay: duration * 0.5, duration, opacity: 0, y: 100, easing: quadOut }} disabled>Quick match</button>
|
||||
|
|
@ -26,7 +65,7 @@
|
|||
{#if $list}
|
||||
<ul>
|
||||
{#each $list as room}
|
||||
<li>{room.name}</li>
|
||||
<li on:click={() => join(room.name)}>{room.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
|
|
@ -51,6 +90,15 @@
|
|||
</dialog>
|
||||
|
||||
<style>
|
||||
a {
|
||||
@apply text-black;
|
||||
}
|
||||
ul {
|
||||
@apply list-none;
|
||||
}
|
||||
li {
|
||||
@apply cursor-pointer border-b border-b-solid border-b-gray-300 p-4;
|
||||
}
|
||||
button {
|
||||
@apply w-64 h-10 px-2 border border-gray-300 bg-white mt-4 my-0;
|
||||
}
|
||||
|
|
@ -72,4 +120,7 @@
|
|||
dialog[open] {
|
||||
@apply block fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 p-0 m-0;
|
||||
}
|
||||
* {
|
||||
@apply box-border select-none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -18,6 +18,14 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<a href="/" class="arrow-back fixed top-0 left-0 w-4 h-4 m-4 p-2 transform transition-transform hover:-translate-x-1">
|
||||
<svg width="16" height="16">
|
||||
<line y1="50%" x1="0" y2="50%" x2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="100%" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="0" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
{#if shown}
|
||||
<main>
|
||||
<div class="top">
|
||||
|
|
@ -33,6 +41,9 @@
|
|||
{/if}
|
||||
|
||||
<style>
|
||||
a {
|
||||
@apply text-black;
|
||||
}
|
||||
* {
|
||||
@apply box-border;
|
||||
}
|
||||
|
|
|
|||
35
client/src/lib/wait.svelte
Normal file
35
client/src/lib/wait.svelte
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<script>
|
||||
import { quadOut } from "svelte/easing";
|
||||
import { fly } from "svelte/transition";
|
||||
import { room } from "./websocket";
|
||||
|
||||
const duration = 400;
|
||||
</script>
|
||||
|
||||
<a href="/multiplayer" class="arrow-back fixed top-0 left-0 w-4 h-4 m-4 p-2 transform transition-transform hover:-translate-x-1">
|
||||
<svg width="16" height="16">
|
||||
<line y1="50%" x1="0" y2="50%" x2="100%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="100%" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
<line y1="50%" x1="0" y2="0" x2="50%" stroke="currentColor" stroke-width="2" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<main>
|
||||
<h1 in:fly={{ delay: 0, duration, opacity: 0, y: 100, easing: quadOut }}>Waiting for an opponent...</h1>
|
||||
<p in:fly={{ delay: duration * 0.5, duration, opacity: 0, y: 100, easing: quadOut }}>Currently in room <code>{$room?.name}</code>.</p>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
@apply w-100vw h-100vh flex flex-col items-center justify-center;
|
||||
}
|
||||
p {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
a {
|
||||
@apply text-black;
|
||||
}
|
||||
code {
|
||||
@apply text-black;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -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<WebsocketConnection | null> = 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);
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
<svelte:head>
|
||||
<title>Slightly complicated tictactoe</title>
|
||||
</svelte:head>
|
||||
|
||||
<main class="flex items-center justify-center flex-col">
|
||||
<div class="chooser">
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,8 @@
|
|||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Slightly complicated tictactoe</title>
|
||||
</svelte:head>
|
||||
|
||||
<Game />
|
||||
|
|
@ -1,14 +1,61 @@
|
|||
<script>
|
||||
<script lang="ts">
|
||||
import Game from "$lib/game.svelte";
|
||||
import List from "$lib/list.svelte";
|
||||
import NameChoose from "$lib/nameChoose.svelte";
|
||||
import { connection, room } from "$lib/websocket";
|
||||
import Wait from "$lib/wait.svelte";
|
||||
import { connection, MoveEvent, room } from "$lib/websocket";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
var addPlayerMove: (p: number, i: number, j: number) => void;
|
||||
|
||||
function addSelfMove(e: any) {
|
||||
$connection?.broadcast({
|
||||
t: "move",
|
||||
i: e.detail.i,
|
||||
j: e.detail.j
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
return () => {
|
||||
$connection?.leave();
|
||||
}
|
||||
});
|
||||
|
||||
let addedEventListener = false;
|
||||
$: {
|
||||
if($connection && !addedEventListener) {
|
||||
function addOtherMove(event: MoveEvent) {
|
||||
console.log("Received other move", event.i, event.j);
|
||||
// note the changed order between this and self - we need to set the opponent here!
|
||||
addPlayerMove($room?.host === $connection?.name ? 2 : 1, event.i, event.j);
|
||||
}
|
||||
console.log("Adding event listener");
|
||||
$connection.addEventListener("move", addOtherMove as any);
|
||||
addedEventListener = true;
|
||||
} else if(!$connection && addedEventListener) {
|
||||
addedEventListener = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Slightly complicated tictactoe</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if !$connection}
|
||||
<NameChoose />
|
||||
{:else if !$room}
|
||||
<List />
|
||||
{:else if $room.count < 2}
|
||||
<Wait />
|
||||
{:else}
|
||||
<Game />
|
||||
<Game
|
||||
twoPlayer
|
||||
self={$room.host === $connection.name ? 1 : 2}
|
||||
opponentName={$connection.name}
|
||||
selfName={[...$connection.players.values()].find(t => t !== $connection?.name)}
|
||||
on:move={addSelfMove}
|
||||
bind:addPlayerMove
|
||||
/>
|
||||
{/if}
|
||||
|
|
@ -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) =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue