mirror of
https://github.com/danbulant/slightlyComplicatedTicTacToe
synced 2026-06-24 01:01:49 +00:00
working room creation and list
This commit is contained in:
parent
7246ddea7b
commit
2206e2774f
17 changed files with 1787 additions and 26 deletions
|
|
@ -24,5 +24,9 @@
|
|||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.0"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"howler": "^2.2.3",
|
||||
"phaser": "^3.55.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,24 @@
|
|||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<style>
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
font-family: "Square", "Squareo", monospace;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Square';
|
||||
src: url("/assets/Square.ttf") format("truetype");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Squareo';
|
||||
src: url("/assets/Squareo.ttf") format("truetype");
|
||||
}
|
||||
</style>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ class ConnectedClient extends EventTarget {
|
|||
}
|
||||
}
|
||||
|
||||
class WebsocketConnection extends EventTarget {
|
||||
export class WebsocketConnection extends EventTarget {
|
||||
ws: WebSocket;
|
||||
fast: Map<string, ConnectedClient> = new Map();
|
||||
roomName: string | null = null;
|
||||
|
|
@ -140,9 +140,20 @@ class WebsocketConnection extends EventTarget {
|
|||
}
|
||||
|
||||
connect() {
|
||||
this.ws = new WebSocket("ws://" + location.hostname + ":8080");
|
||||
this.ws.addEventListener("open", () => {
|
||||
this.ws = new WebSocket("ws://" + location.hostname + ":8080/?name=" + encodeURIComponent(this.name));
|
||||
this.ws.addEventListener("open", (e) => {
|
||||
console.log("WS ready");
|
||||
this.refreshList();
|
||||
});
|
||||
this.ws.addEventListener("close", (e) => {
|
||||
console.log("WS closed");
|
||||
lastError.set(e.reason || "Connection closed");
|
||||
connection.set(null);
|
||||
});
|
||||
this.ws.addEventListener("error", (e) => {
|
||||
console.error("WS error");
|
||||
lastError.set("Connection error");
|
||||
connection.set(null);
|
||||
});
|
||||
this.ws.addEventListener("message", (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
|
|
@ -193,32 +204,54 @@ class WebsocketConnection extends EventTarget {
|
|||
}
|
||||
this.fast.set(client, fast);
|
||||
}
|
||||
// missing break on purpose
|
||||
}
|
||||
case "create": {
|
||||
this.roomName = msg.name;
|
||||
this.roomId = msg.id;
|
||||
room.set({
|
||||
name: msg.name,
|
||||
id: msg.id
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "leave": {
|
||||
const fast = this.fast.get(msg.name);
|
||||
if (!fast) return;
|
||||
fast.conn.close();
|
||||
this.fast.delete(msg.name);
|
||||
break;
|
||||
}
|
||||
case "left": {
|
||||
console.log("Left room successfully");
|
||||
this.roomName = null;
|
||||
this.roomId = null;
|
||||
room.set(null);
|
||||
this.fast.forEach(connection => connection.conn.close());
|
||||
this.fast = new Map();
|
||||
break;
|
||||
}
|
||||
case "list": {
|
||||
list.set(msg.rooms);
|
||||
listLoading.set(false);
|
||||
break;
|
||||
}
|
||||
case "error": {
|
||||
console.error(msg.e);
|
||||
lastError.set(msg.e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createGame(name: string) {
|
||||
this.ws.send(JSON.stringify({ t: "create", name: name }));
|
||||
}
|
||||
|
||||
refreshList() {
|
||||
this.ws.send(JSON.stringify({ t: "list" }));
|
||||
listLoading.set(true);
|
||||
}
|
||||
|
||||
send(data: any) {
|
||||
|
|
@ -227,4 +260,7 @@ class WebsocketConnection extends EventTarget {
|
|||
}
|
||||
|
||||
export const connection: Writable<WebsocketConnection|null> = writable(null);
|
||||
export const list: Writable<{ id: string, name: string, count: number }[]|null> = writable(null);
|
||||
export const list: Writable<{ id: string, name: string, count: number }[]|null> = writable(null);
|
||||
export const listLoading = writable(true);
|
||||
export const lastError: Writable<string> = writable("");
|
||||
export const room: Writable<{ name: string, id: string }|null> = writable(null);
|
||||
0
client/src/lib/view/game/game.svelte
Normal file
0
client/src/lib/view/game/game.svelte
Normal file
49
client/src/lib/view/game/init.ts
Normal file
49
client/src/lib/view/game/init.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { CANVAS, Game, Scale, WEBGL } from "phaser";
|
||||
import { GameScene } from "./scene";
|
||||
|
||||
var ratio = window.devicePixelRatio || 1;
|
||||
export function resize() {
|
||||
if(!game || !htmlcanvas) return;
|
||||
try {
|
||||
game.scale.resize(htmlcanvas.parentElement!.clientWidth * ratio, htmlcanvas.parentElement!.clientHeight * ratio);
|
||||
} catch(e) {
|
||||
// @ts-ignore
|
||||
console.error(e, new ErrorEvent(e.type, { colno: e.colno, error: e, lineno: e.lineno, message: e.message, filename: e.filename }));
|
||||
window.dispatchEvent(new ErrorEvent("error", e as any));
|
||||
}
|
||||
// console.log("size", htmlcanvas.parentElement!.clientWidth * ratio, htmlcanvas.parentElement!.clientHeight * ratio);
|
||||
}
|
||||
|
||||
/** @type {HTMLCanvasElement} */
|
||||
var htmlcanvas: HTMLCanvasElement;
|
||||
/** @type {Game} */
|
||||
var game: Game;
|
||||
/** @type {GameScene} */
|
||||
var gs: GameScene;
|
||||
export function setCanvas(canvas: HTMLCanvasElement) {
|
||||
htmlcanvas = canvas;
|
||||
var ctx = canvas.getContext("webgl2") || canvas.getContext("webgl");
|
||||
gs = new GameScene();
|
||||
game = new Game({
|
||||
canvas: canvas,
|
||||
url: window.location.host,
|
||||
hideBanner: true,
|
||||
type: ctx ? WEBGL : CANVAS,
|
||||
// @ts-ignore
|
||||
context: ctx || canvas.getContext("2d"),
|
||||
customEnvironment: false,
|
||||
width: window.innerWidth * ratio,
|
||||
height: window.innerHeight * ratio,
|
||||
scale: {
|
||||
mode: Scale.RESIZE
|
||||
},
|
||||
physics: {
|
||||
default: "arcade",
|
||||
},
|
||||
title: "Multidie",
|
||||
version: "0",
|
||||
scene: [gs],
|
||||
backgroundColor: "#01021B",
|
||||
banner: false
|
||||
});
|
||||
}
|
||||
28
client/src/lib/view/game/scene.ts
Normal file
28
client/src/lib/view/game/scene.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import Phaser, { Animations } from "phaser";
|
||||
|
||||
var fpsBuffer: number[] = [];
|
||||
|
||||
export class GameScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({
|
||||
key: "GameScene",
|
||||
active: true,
|
||||
physics: {
|
||||
default: "arcade"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unload() {}
|
||||
preload() {}
|
||||
|
||||
create() {}
|
||||
|
||||
update(time: number, delta: number) {
|
||||
fpsBuffer.push(delta);
|
||||
if (fpsBuffer.length > 10) {
|
||||
fpsBuffer.shift();
|
||||
}
|
||||
const fps = fpsBuffer.reduce((a, b) => a + b, 0) / fpsBuffer.length;
|
||||
}
|
||||
}
|
||||
151
client/src/lib/view/menu/list.svelte
Normal file
151
client/src/lib/view/menu/list.svelte
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<script lang="ts">
|
||||
import { connection, lastError, list, listLoading } from "$lib/Websocket";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
onMount(() => {
|
||||
let i = setInterval(() => {
|
||||
$connection?.refreshList();
|
||||
}, 2000);
|
||||
|
||||
return () => clearInterval(i);
|
||||
});
|
||||
|
||||
let creatingGame = false;
|
||||
let newGameName = "";
|
||||
let error = "";
|
||||
|
||||
$: {
|
||||
if (newGameName.length > 64) {
|
||||
error = "Name is too long";
|
||||
} else {
|
||||
error = "";
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if($lastError === "room_name_used") {
|
||||
error = $lastError;
|
||||
$lastError = "";
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (newGameName.length < 2) return error = "Name is too short";
|
||||
if (newGameName.length > 64) return error = "Name is too long";
|
||||
error = "Creating game...";
|
||||
$connection!.createGame(newGameName);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if creatingGame}
|
||||
<dialog open>
|
||||
<input on:submit={submit} type="text" name="name" placeholder="GAME NAME" bind:value={newGameName}>
|
||||
<div class="error">
|
||||
{error}
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button on:click={() => creatingGame = false}>CANCEL</button>
|
||||
<button on:click={submit}>SUBMIT</button>
|
||||
</div>
|
||||
</dialog>
|
||||
{/if}
|
||||
|
||||
<div class="container">
|
||||
<main>
|
||||
<div class="flex">
|
||||
<h1>Games - {$connection?.name}</h1>
|
||||
<button on:click={() => creatingGame = true}>CREATE</button>
|
||||
</div>
|
||||
<div class="status">
|
||||
{#if $listLoading} Loading... {/if}
|
||||
</div>
|
||||
<ul>
|
||||
{#if $list}
|
||||
{#each $list as $game}
|
||||
<li class="flex">
|
||||
<span>
|
||||
{$game.name}
|
||||
</span>
|
||||
<span>
|
||||
{$game.count}
|
||||
</span>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
dialog {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(20px) saturate(150%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
gap: 0.25em;
|
||||
}
|
||||
dialog input {
|
||||
font-size: 1.5rem;
|
||||
background-color: #85E65C;
|
||||
color: #bd5ce6;
|
||||
border: 10px solid #bd5ce6;
|
||||
border-radius: 0;
|
||||
}
|
||||
.container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #85E65C;
|
||||
color: #bd5ce6;
|
||||
}
|
||||
main {
|
||||
max-width: 700px;
|
||||
margin: auto;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.status {
|
||||
height: 1.5em;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
li {
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
padding: 0.5em;
|
||||
border: 1px solid #bd5ce6;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
button {
|
||||
width: 10em;
|
||||
height: 3em;
|
||||
border: 1px solid #bd5ce6;
|
||||
border-radius: 0;
|
||||
padding: 0.5em;
|
||||
font-size: 1.5rem;
|
||||
border: 10px solid #bd5ce6;
|
||||
background-color: #85E65C;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:active {
|
||||
background-color: #bd5ce6;
|
||||
color: #85E65C;
|
||||
}
|
||||
</style>
|
||||
76
client/src/lib/view/menu/nameChoose.svelte
Normal file
76
client/src/lib/view/menu/nameChoose.svelte
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<script>
|
||||
import { connection, lastError, WebsocketConnection } from "$lib/Websocket";
|
||||
|
||||
var name = "";
|
||||
var error = "";
|
||||
|
||||
$: {
|
||||
if(name.length > 64) {
|
||||
error = "Name is too long";
|
||||
} else {
|
||||
error = "";
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (name.length < 2) return error = "Name is too short";
|
||||
if (name.length > 64) return error = "Name is too long";
|
||||
$connection = new WebsocketConnection(name);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<main>
|
||||
<input on:submit={submit} type="text" name="name" placeholder="PLAYER NAME" bind:value={name}>
|
||||
<div class="error">
|
||||
{error || $lastError}
|
||||
</div>
|
||||
<button on:click={submit}>SUBMIT</button>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
.container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #85E65C;
|
||||
color: #bd5ce6;
|
||||
}
|
||||
.error {
|
||||
height: 1.5em;
|
||||
}
|
||||
input {
|
||||
width: 20em;
|
||||
height: 2em;
|
||||
border: 10px solid #bd5ce6;
|
||||
border-radius: 0;
|
||||
padding: 0.5em;
|
||||
font-size: 1.5em;
|
||||
background-color: #85E65C;
|
||||
color: #bd5ce6;
|
||||
font-family: inherit;
|
||||
}
|
||||
button {
|
||||
width: 10em;
|
||||
height: 3em;
|
||||
border: 10px solid #bd5ce6;
|
||||
background-color: #85E65C;
|
||||
color: #bd5ce6;
|
||||
border-radius: 0;
|
||||
padding: 0.5em;
|
||||
font-size: 1.5em;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:active {
|
||||
background-color: #bd5ce6;
|
||||
color: #85E65C;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,2 +1,14 @@
|
|||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
|
||||
<script>
|
||||
import Game from "$lib/view/game/game.svelte";
|
||||
import List from "$lib/view/menu/list.svelte";
|
||||
import NameChoose from "$lib/view/menu/nameChoose.svelte";
|
||||
import { connection, room } from "$lib/Websocket";
|
||||
</script>
|
||||
|
||||
{#if !$connection}
|
||||
<NameChoose />
|
||||
{:else if !$room}
|
||||
<List />
|
||||
{:else}
|
||||
<Game />
|
||||
{/if}
|
||||
|
|
@ -8,6 +8,11 @@
|
|||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
|
||||
"paths": {
|
||||
"$lib": ["src/lib"],
|
||||
"$lib/*": ["src/lib/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1381
pnpm-lock.yaml
1381
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -33,23 +33,19 @@ const clients = new Map();
|
|||
require("uWebSockets.js")
|
||||
.App({})
|
||||
.ws("/", {
|
||||
idleTimeout: 30,
|
||||
idleTimeout: 32,
|
||||
maxPayloadLength: 16 * 1024 * 1024,
|
||||
upgrade: (res, req, context) => {
|
||||
console.log(
|
||||
`An Http connection wants to become WebSocket, URL: ${req.getUrl()}!`
|
||||
);
|
||||
|
||||
const url = req.getUrl();
|
||||
const parsed = new URL(url);
|
||||
let name = parsed.searchParams.get("name");
|
||||
let name = req.getQuery("name").trim().toLocaleLowerCase();
|
||||
if (!name || typeof name !== "string" || name.length < 2 || name.length > 64 || !name.trim()) return res.end("invalid_name");
|
||||
name = name.trim();
|
||||
if (clients.forEach(client => client.name === name)) return res.end("name_used");
|
||||
if ([...clients.values()].find(client => client.name === name)) return res.end("name_used");
|
||||
/* This immediately calls open handler, you must not use res after this call */
|
||||
res.upgrade(
|
||||
{
|
||||
url,
|
||||
name
|
||||
},
|
||||
/* Spell these correctly */
|
||||
|
|
@ -62,13 +58,13 @@ require("uWebSockets.js")
|
|||
open: (ws) => {
|
||||
console.log(
|
||||
"CON",
|
||||
ws.url,
|
||||
ws.name,
|
||||
decoder.decode(ws.getRemoteAddressAsText())
|
||||
);
|
||||
clients.set(ws, {
|
||||
connection: ws,
|
||||
name: ws.name,
|
||||
ipa: decoder.decode(ws.getRemoteAddressAsText()),
|
||||
room: null
|
||||
});
|
||||
},
|
||||
|
|
@ -83,9 +79,10 @@ require("uWebSockets.js")
|
|||
}
|
||||
case "create": {
|
||||
const client = clients.get(ws);
|
||||
if(client.room) return ws.send(JSON.stringify({t: "error", m: "already_in_room"}));
|
||||
const name = data.name.trim();
|
||||
if (!name || typeof name !== "string" || name.length < 2 || name.length > 64 || !name.trim()) return res.send(JSON.stringify({t: "error", m: "invalid_room_name"}));
|
||||
if(client.room) return ws.send(JSON.stringify({t: "error", e: "already_in_room"}));
|
||||
const name = data.name.trim().toLocaleLowerCase();
|
||||
if (!name || typeof name !== "string" || name.length < 2 || name.length > 64 || !name.trim()) return res.send(JSON.stringify({t: "error", e: "invalid_room_name"}));
|
||||
if ([...rooms.values()].find(room => room.name === name)) return ws.send(JSON.stringify({t: "error", e: "room_name_used"}));
|
||||
const room = {
|
||||
name: name,
|
||||
host: ws,
|
||||
|
|
@ -99,8 +96,8 @@ require("uWebSockets.js")
|
|||
case "leave": {
|
||||
const client = clients.get(ws);
|
||||
const room = client.room;
|
||||
if (!room) return ws.send(JSON.stringify({ t: "error", m: "room_not_found" }));
|
||||
if (!room.clients.includes(client)) return ws.send(JSON.stringify({ t: "error", m: "not_in_room" }));
|
||||
if (!room) return ws.send(JSON.stringify({ t: "error", e: "room_not_found" }));
|
||||
if (!room.clients.includes(client)) return ws.send(JSON.stringify({ t: "error", e: "not_in_room" }));
|
||||
room.clients.splice(room.clients.indexOf(client), 1);
|
||||
if (room.clients.length === 0) {
|
||||
rooms.delete(room.id);
|
||||
|
|
@ -116,13 +113,15 @@ require("uWebSockets.js")
|
|||
case "join": {
|
||||
const client = clients.get(ws);
|
||||
if (!client) return ws.end();
|
||||
const room = client.room;
|
||||
const room = rooms.get(msg.room);
|
||||
if (!room) return ws.send(JSON.stringify({ t: "error", e: "room_not_found" }));
|
||||
if(client.room) return ws.send(JSON.stringify({ t: "error", e: "already_in_other_room" }));
|
||||
if (room.clients.includes(client)) return ws.send(JSON.stringify({ t: "error", e: "already_in_room" }));
|
||||
if(room.clients.length > 5) return ws.send(JSON.stringify({ t: "error", e: "room_full" }));
|
||||
room.clients.push(ws);
|
||||
ws.room = room;
|
||||
room.clients.slice(0, -2).forEach(client => client.connection.send(JSON.stringify({ t: "join", id: room.id, client: client.name })));
|
||||
ws.send(JSON.stringify({ t: "joined", id: room.id, client: client.name, clients: room.clients.map(t => t.name) }));
|
||||
ws.send(JSON.stringify({ t: "joined", id: room.id, name: room.name, client: client.name, clients: room.clients.map(t => t.name) }));
|
||||
break;
|
||||
}
|
||||
case "cand":
|
||||
|
|
@ -138,7 +137,7 @@ require("uWebSockets.js")
|
|||
targetClient.connection.send(JSON.stringify({ t: msg.t, id: room.id, source: client.name, d: msg.d }));
|
||||
}
|
||||
case "list": {
|
||||
ws.send(JSON.stringify({ t: "list", rooms: [...rooms.values()].map(t => ({ id: t.id, name: t.name, count: t.clients.length }))}));
|
||||
ws.send(JSON.stringify({ t: "list", rooms: [...rooms.values()].filter(t => t.clients.length < 5).map(t => ({ id: t.id, name: t.name, count: t.clients.length }))}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -147,9 +146,11 @@ require("uWebSockets.js")
|
|||
}
|
||||
},
|
||||
close: (ws, code, message) => {
|
||||
console.log("DIS", decoder.decode(ws.getRemoteAddressAsText()));
|
||||
console.log("DIS1", ws);
|
||||
if (clients.get(ws)) {
|
||||
let room = rooms.get(clients.get(ws));
|
||||
const client = clients.get(ws);
|
||||
console.log("DIS", client.name, client.ipa);
|
||||
let room = client.room;
|
||||
if (room) {
|
||||
room.clients.splice(room.clients.indexOf(ws), 1);
|
||||
if (room.clients.length === 0) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue