diff --git a/client/src/App.tsx b/client/src/App.tsx index 691dd5d..18d5321 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,9 +1,16 @@ -import { Route, BrowserRouter as Router, Routes } from "react-router-dom"; +import { + Navigate, + Route, + BrowserRouter as Router, + Routes, +} from "react-router-dom"; import { Creator } from "./pages/creator"; import { Game } from "./pages/game"; import { Intro } from "./pages/intro"; +import { Lobby } from "./pages/lobby"; import { Playground } from "./pages/playground"; +import { Start } from "./pages/start"; export function App() { return ( @@ -11,9 +18,12 @@ export function App() { } /> + } /> + } /> } /> } /> } /> + } /> diff --git a/client/src/lib/room-provider.tsx b/client/src/lib/room-provider.tsx index c842f5a..ea4643b 100644 --- a/client/src/lib/room-provider.tsx +++ b/client/src/lib/room-provider.tsx @@ -3,6 +3,7 @@ import type { Room } from "@colyseus/sdk"; import React, { useEffect, useState } from "react"; import { RoomContext } from "./use-room"; +import type { ConnectOptions } from "./use-room"; const host = window.location.hostname; @@ -23,20 +24,48 @@ export function RoomProvider({ children }: { children: React.ReactNode }) { const [isConnected, setIsConnected] = useState(false); const [joinError, setJoinError] = useState(false); - const connect = async (playerName: string) => { + const connect = async ({ + playerName, + mode, + roomCode, + isPrivate, + }: ConnectOptions) => { try { - const newRoom = await client.joinOrCreate("game_room", { - name: playerName, - }); - setRoom(newRoom); + setJoinError(false); + let joinedRoom: Room; + + if (mode === "create") { + joinedRoom = await client.create("game_room", { + name: playerName, + isPrivate, + }); + } else if ( + mode === "join" && + roomCode !== undefined && + roomCode.length > 0 + ) { + joinedRoom = await client.joinById(roomCode, { + name: playerName, + }); + } else { + joinedRoom = await client.joinOrCreate("game_room", { + name: playerName, + isPrivate: false, + }); + } + + if (joinedRoom && joinedRoom.reconnectionToken) { + localStorage.setItem( + "reconnection", + JSON.stringify({ + token: joinedRoom.reconnectionToken, + playerName, + }), + ); + } + + setRoom(joinedRoom); setIsConnected(true); - localStorage.setItem( - "reconnection", - JSON.stringify({ - token: newRoom.reconnectionToken, - playerName, - }), - ); } catch (error) { console.error("Join error", error); setJoinError(true); @@ -64,7 +93,7 @@ export function RoomProvider({ children }: { children: React.ReactNode }) { isReconnecting = true; try { const parsed = JSON.parse(cached) as CachedReconnection; - const { token } = parsed; + const { token } = JSON.parse(cached); const reconnected = await client.reconnect(token, "game_room"); diff --git a/client/src/lib/use-room.ts b/client/src/lib/use-room.ts index 89a8096..b28ce58 100644 --- a/client/src/lib/use-room.ts +++ b/client/src/lib/use-room.ts @@ -1,11 +1,18 @@ import type { Room } from "@colyseus/sdk"; import { createContext, useContext } from "react"; +export interface ConnectOptions { + playerName: string; + mode: "join" | "create" | "random"; + roomCode?: string; + isPrivate?: boolean; +} + interface RoomContextType { room: Room | null; isConnected: boolean; joinError: boolean; - connect: (playerName: string) => Promise; + connect: (options: ConnectOptions) => Promise; disconnect: () => Promise; } diff --git a/client/src/pages/game.tsx b/client/src/pages/game.tsx index 59c96fa..6d50cb2 100644 --- a/client/src/pages/game.tsx +++ b/client/src/pages/game.tsx @@ -54,7 +54,7 @@ export function Game() { + + ); +} diff --git a/client/src/pages/start.tsx b/client/src/pages/start.tsx new file mode 100644 index 0000000..7f244ee --- /dev/null +++ b/client/src/pages/start.tsx @@ -0,0 +1,187 @@ +import { useCallback, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { Button } from "../components/button"; +import { CustomInput } from "../components/custom-input"; +import { ErrorContainer } from "../components/error-container"; +import { IntroContainer } from "../components/intro-container"; +import { TitleHeader } from "../components/title-header"; +import { useRoom } from "../lib/use-room"; + +export function Start() { + const navigate = useNavigate(); + const { connect, disconnect } = useRoom(); + + const [status, setStatus] = useState< + "idle" | "loading" | "error" | "success" | "reconnecting" + >(() => { + return localStorage.getItem("reconnection") === null + ? "idle" + : "reconnecting"; + }); + + const [name, setName] = useState(""); + const [mode, setMode] = useState<"join" | "create">("create"); + const [roomCode, setRoomCode] = useState(""); + const [isPrivate, setIsPrivate] = useState(false); + + const [countdown, setCountdown] = useState(3); + const [errorMessage, setErrorMessage] = useState(""); + + const handlePlay = useCallback(async () => { + if (name.trim() === "") { + setErrorMessage("Nazwa gracza nie może być pusta."); + setStatus("error"); + return; + } + + setStatus("loading"); + try { + await connect({ + playerName: name.trim(), + mode, + roomCode: mode === "join" ? roomCode.trim() : undefined, + isPrivate: mode === "create" ? isPrivate : undefined, + }); + await navigate("/lobby"); + } catch { + setErrorMessage("Nie udało się dołaczyć do gry. Spróbuj ponownie."); + setStatus("error"); + } + }, [connect, isPrivate, mode, name, navigate, roomCode]); + + useEffect(() => { + if (status !== "reconnecting") { + return; + } + + let remaining = 3; + setCountdown(remaining); + + const timerId = window.setInterval(() => { + remaining -= 1; + setCountdown(remaining); + + if (remaining > 0) { + return; + } + + window.clearInterval(timerId); + + const cachedReconnection = localStorage.getItem("reconnection"); + if (cachedReconnection === null) { + setStatus("idle"); + } else { + void navigate("/lobby"); + } + }, 1000); + + return () => { + window.clearInterval(timerId); + }; + }, [status, navigate]); + + useEffect(() => { + if (status === "reconnecting") { + return; + } + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && status !== "loading") { + void handlePlay(); + } + }; + + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [handlePlay, status]); + + const handleCancelReconnection = async () => { + localStorage.removeItem("reconnection"); + await disconnect(); + setStatus("idle"); + setCountdown(3); + }; + + return ( + + + + {status === "reconnecting" ? ( +
+

+ Próba ponownego połączenia za {countdown}... +
+

+ +
+ ) : ( + <> +
+ + +
+ { + setName(value.toUpperCase()); + }} + disabled={status === "loading"} + /> + {mode === "join" ? ( + { + setRoomCode(error.target.value); + }} + /> + ) : ( + + )} + + + + )} + + {status === "error" && } +
+ ); +} diff --git a/package-lock.json b/package-lock.json index f6cba4b..233a7a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -661,6 +661,25 @@ "@colyseus/ws-transport": "0.17.x" } }, + "node_modules/@colyseus/uwebsockets-transport": { + "version": "0.17.20", + "resolved": "https://registry.npmjs.org/@colyseus/uwebsockets-transport/-/uwebsockets-transport-0.17.20.tgz", + "integrity": "sha512-274nZvtkaNyJ2Cs6w6onepKZLACtrHDtu5WbM5Krp5M4ScvR9k3icrqtQgffaBdo/5nVMKPuOn4U51Ylmn/dWw==", + "license": "MIT", + "peer": true, + "dependencies": { + "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.57.0" + }, + "peerDependencies": { + "@colyseus/core": "0.17.x", + "uwebsockets-express": "^1.4.1 || ^2.0.1" + }, + "peerDependenciesMeta": { + "uwebsockets-express": { + "optional": true + } + } + }, "node_modules/@colyseus/ws-transport": { "version": "0.17.13", "resolved": "https://registry.npmjs.org/@colyseus/ws-transport/-/ws-transport-0.17.13.tgz", @@ -1066,6 +1085,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1082,6 +1102,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1098,6 +1119,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1114,6 +1136,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1130,6 +1153,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1146,6 +1170,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1162,6 +1187,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1178,6 +1204,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1194,6 +1221,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1210,6 +1238,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1226,6 +1255,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1242,6 +1272,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1258,6 +1289,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1274,6 +1306,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1290,6 +1323,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1306,6 +1340,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1322,6 +1357,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1338,6 +1374,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1354,6 +1391,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1370,6 +1408,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1386,6 +1425,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1402,6 +1442,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1418,6 +1459,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1434,6 +1476,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1450,6 +1493,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1466,6 +1510,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2390,6 +2435,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2406,6 +2452,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2422,6 +2469,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2438,6 +2486,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2454,6 +2503,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2470,6 +2520,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2486,6 +2537,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2502,6 +2554,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2518,6 +2571,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2534,6 +2588,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2550,6 +2605,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2566,6 +2622,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2579,6 +2636,7 @@ ], "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", @@ -2600,6 +2658,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -2616,6 +2675,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": "^20.19.0 || >=22.12.0" } @@ -4590,6 +4650,40 @@ "dev": true, "license": "MIT" }, + "node_modules/colyseus": { + "version": "0.17.10", + "resolved": "https://registry.npmjs.org/colyseus/-/colyseus-0.17.10.tgz", + "integrity": "sha512-RiVNY7LaLfI0vgVYdoc2GTPAYO6aumHWDw0xvbQuml4SA6naYGaAZI33R0vWzvVYtWFQIsGhTdkRDtp/N9FauQ==", + "license": "MIT", + "dependencies": { + "@colyseus/auth": "^0.17.9", + "@colyseus/core": "^0.17.42", + "@colyseus/monitor": "^0.17.8", + "@colyseus/playground": "^0.17.12", + "@colyseus/redis-driver": "^0.17.7", + "@colyseus/redis-presence": "^0.17.7", + "@colyseus/tools": "^0.17.19", + "@colyseus/ws-transport": "^0.17.13" + }, + "engines": { + "node": ">= 20.x" + }, + "peerDependencies": { + "@colyseus/auth": "0.17.x", + "@colyseus/core": "0.17.x", + "@colyseus/redis-driver": "0.17.x", + "@colyseus/redis-presence": "0.17.x", + "@colyseus/schema": "^4.0.7", + "@colyseus/uwebsockets-transport": "0.17.x", + "@colyseus/ws-transport": "0.17.x", + "vite": ">=6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/colyseus.js": { "version": "0.16.22", "resolved": "https://registry.npmjs.org/colyseus.js/-/colyseus.js-0.16.22.tgz", @@ -12131,6 +12225,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uWebSockets.js": { + "version": "20.57.0", + "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#fcfc622a4286909593b7f390056d89e0ca3b56b9", + "license": "Apache-2.0", + "peer": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -12749,6 +12849,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@colyseus/core": "^0.17.43", "@colyseus/monitor": "^0.17.8", "@colyseus/playground": "^0.17.12", "@colyseus/tools": "^0.17.19", @@ -12782,40 +12883,6 @@ "undici-types": "~7.16.0" } }, - "server/node_modules/colyseus": { - "version": "0.17.10", - "resolved": "https://registry.npmjs.org/colyseus/-/colyseus-0.17.10.tgz", - "integrity": "sha512-RiVNY7LaLfI0vgVYdoc2GTPAYO6aumHWDw0xvbQuml4SA6naYGaAZI33R0vWzvVYtWFQIsGhTdkRDtp/N9FauQ==", - "license": "MIT", - "dependencies": { - "@colyseus/auth": "^0.17.9", - "@colyseus/core": "^0.17.42", - "@colyseus/monitor": "^0.17.8", - "@colyseus/playground": "^0.17.12", - "@colyseus/redis-driver": "^0.17.7", - "@colyseus/redis-presence": "^0.17.7", - "@colyseus/tools": "^0.17.19", - "@colyseus/ws-transport": "^0.17.13" - }, - "engines": { - "node": ">= 20.x" - }, - "peerDependencies": { - "@colyseus/auth": "0.17.x", - "@colyseus/core": "0.17.x", - "@colyseus/redis-driver": "0.17.x", - "@colyseus/redis-presence": "0.17.x", - "@colyseus/schema": "^4.0.7", - "@colyseus/uwebsockets-transport": "0.17.x", - "@colyseus/ws-transport": "0.17.x", - "vite": ">=6.0.0" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "server/node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", diff --git a/server/package.json b/server/package.json index 89af7b4..49478d0 100644 --- a/server/package.json +++ b/server/package.json @@ -20,6 +20,7 @@ "test": "mocha -r tsx test/MyRoom_test.ts --exit --timeout 15000" }, "dependencies": { + "@colyseus/core": "^0.17.43", "@colyseus/monitor": "^0.17.8", "@colyseus/playground": "^0.17.12", "@colyseus/tools": "^0.17.19", diff --git a/server/src/app.config.ts b/server/src/app.config.ts index b0f93b6..d6a9e6d 100644 --- a/server/src/app.config.ts +++ b/server/src/app.config.ts @@ -27,6 +27,7 @@ export default config({ * Define your room handlers: */ gameServer.define("game_room", GameRoom); + // .filterBy(['isPrivate']); }, initializeExpress: (app) => { diff --git a/server/src/rooms/GameRoom.ts b/server/src/rooms/GameRoom.ts index 7a5ff85..9e2f19c 100644 --- a/server/src/rooms/GameRoom.ts +++ b/server/src/rooms/GameRoom.ts @@ -11,7 +11,7 @@ import { RoomState } from "./schema/RoomState"; // import room from "./json/examples/room3.json"; export class GameRoom extends Room<{ state: RoomState }> { - maxClients = 4; + maxClients = 2; state = new RoomState(); private roomData: any = fallbackRoom; @@ -19,9 +19,33 @@ export class GameRoom extends Room<{ state: RoomState }> { async onCreate(options: any) { this.roomData = await getRoomForGame(options?.levelSlug); this.maxClients = this.roomData.maxClients ?? this.maxClients; - this.state.loadRoomFromJson(this.roomData); + + this.setMetadata({ + isPrivate: !!options.isPrivate, + }); + + if (options.isPrivate) { + this.setPrivate(true); + } + + this.onMessage("toggle_ready", (client) => { + const player = this.state.playerState.players.get(client.sessionId); + if (player) { + player.ready = !player.ready; + } + + if (this.clients.length >= 1) { + const allReady = Array.from( + this.state.playerState.players.values(), + ).every((p) => p.ready); + if (allReady) { + this.startGame(); + } + } + }); + this.onMessage("move", (client, message) => { - if (this.state.isPaused) return; + if (!this.state.gameStarted || this.state.isPaused) return; const player = this.state.playerState.players.get(client.sessionId); if (!player) return; @@ -72,7 +96,7 @@ export class GameRoom extends Room<{ state: RoomState }> { }); this.setSimulationInterval((deltaTime) => { - if (this.state.isPaused) return; + if (!this.state.gameStarted || this.state.isPaused) return; const result = this.state.updateLasers(deltaTime); if (result.length > 0) { @@ -133,21 +157,38 @@ export class GameRoom extends Room<{ state: RoomState }> { }); } - onJoin(client: Client, options: any) { - this.state.spawnNewPlayer(client.sessionId, options.name); - const player = this.state.playerState.players.get(client.sessionId); + private startGame() { + this.state.gameStarted = true; + this.state.loadRoomFromJson(this.roomData); - this.broadcast("onAddPlayer", { - sessionId: client.sessionId, - playerName: player.name, - position: player.position, - index: player.index, + this.clients.forEach((client) => { + const p = this.state.playerState.players.get(client.sessionId); + if (p) { + const startPos = + this.state.startingPositions[ + p.index % this.state.startingPositions.length + ]; + if (startPos) { + p.position.x = startPos.x; + p.position.y = startPos.y; + } + } }); + } + + onJoin(client: Client, options: any) { + const nickname = options.name; + + this.state.spawnNewPlayer(client.sessionId, nickname); + + const player = this.state.playerState.players.get(client.sessionId); - console.log(client.sessionId, "joined!"); + console.log( + `Gracz ${player.name} dołączył jako ${player.index === 0 ? "Sol" : "Vron"}`, + ); } - async onLeave(client: Client, code?: number) { + async onLeave(client: Client, code: number) { if (code !== CloseCode.CONSENTED) { try { // allow disconnected client to reconnect into this room until 20 seconds diff --git a/server/src/rooms/schema/Player.ts b/server/src/rooms/schema/Player.ts index a9575ff..dfc0f23 100644 --- a/server/src/rooms/schema/Player.ts +++ b/server/src/rooms/schema/Player.ts @@ -6,5 +6,6 @@ export class Player extends Schema { @type("string") sessionId: string; @type("number") index: number; @type("string") name: string; - @type(Position) position: Position; + @type(Position) position: Position = new Position(); + @type("boolean") ready: boolean = false; } diff --git a/server/src/rooms/schema/PlayerState.ts b/server/src/rooms/schema/PlayerState.ts index 4d4acc8..9322a46 100644 --- a/server/src/rooms/schema/PlayerState.ts +++ b/server/src/rooms/schema/PlayerState.ts @@ -67,4 +67,9 @@ export class PlayerState extends Schema { getSessionIdByName(name: string): string | null { return this.playerSessionIds.get(name) || null; } + + toggleReady(sessionId: string) { + const player = this.players.get(sessionId); + player.ready = !player.ready; + } } diff --git a/server/src/rooms/schema/RoomState.ts b/server/src/rooms/schema/RoomState.ts index 0675038..ebf2abe 100644 --- a/server/src/rooms/schema/RoomState.ts +++ b/server/src/rooms/schema/RoomState.ts @@ -15,6 +15,7 @@ export class RoomState extends Schema { @type(["string"]) grid = new ArraySchema(); @type("number") width: number = 10; @type("number") height: number = 7; + @type("boolean") gameStarted: boolean = false; @type("boolean") isPaused: boolean = false; @type([Position]) startingPositions = new ArraySchema(); @@ -303,10 +304,14 @@ export class RoomState extends Schema { spawnNewPlayer(sessionId: string, name: string = null) { this.playerState.createPlayer(sessionId, name); const player = this.playerState.players.get(sessionId); - const startingPos = - this.startingPositions[player.index % this.startingPositions.length]; - player.position.x = startingPos.x; - player.position.y = startingPos.y; + if (this.startingPositions && this.startingPositions.length > 0) { + const startingPos = + this.startingPositions[player.index % this.startingPositions.length]; + if (startingPos) { + player.position.x = startingPos.x; + player.position.y = startingPos.y; + } + } } despawnPlayer(sessionId: string) { diff --git a/server/test/GameRoom_test.ts b/server/test/GameRoom_test.ts index b6af803..0376b19 100644 --- a/server/test/GameRoom_test.ts +++ b/server/test/GameRoom_test.ts @@ -50,22 +50,25 @@ describe("GameRoom (room1)", () => { it("connects a client and syncs room1 state (size, player spawn)", async () => { const room = await colyseus.createRoom("game_room", {}); - const client = await colyseus.connectTo(room, { name: "TestPlayer" }); - registerNoopMessageHandlers(client); + const c1 = await colyseus.connectTo(room, { name: "TestPlayer" }); + const c2 = await colyseus.connectTo(room, { name: "Bot" }); + registerNoopMessageHandlers(c1); assert.strictEqual( - client.sessionId, + c1.sessionId, room.clients[0].sessionId, "Client sessionId matches the first connected client in the room", ); + c1.send("toggle_ready"); + c2.send("toggle_ready"); await room.waitForNextPatch(); const s = room.state; assert.strictEqual(s.width, 10); assert.strictEqual(s.height, 8); - const player = s.playerState.players.get(client.sessionId); + const player = s.playerState.players.get(c1.sessionId); assert.ok(player, "Player exists after join"); assert.strictEqual(player.position.x, 1); assert.strictEqual(player.position.y, 2); @@ -78,6 +81,9 @@ describe("GameRoom (room1)", () => { const c2 = await colyseus.connectTo(room, { name: "Bravo" }); registerNoopMessageHandlers(c2); + c1.send("toggle_ready"); + c2.send("toggle_ready"); + await room.waitForNextPatch(); const s = room.state; @@ -97,20 +103,24 @@ describe("GameRoom (room1)", () => { it("moves the player after a move message (down from spawn)", async () => { const room = await colyseus.createRoom("game_room", {}); - const client = await colyseus.connectTo(room, { name: "Mover" }); - registerNoopMessageHandlers(client); + const c1 = await colyseus.connectTo(room, { name: "Mover" }); + const c2 = await colyseus.connectTo(room, { name: "Bot" }); + registerNoopMessageHandlers(c1); + + c1.send("toggle_ready"); + c2.send("toggle_ready"); await room.waitForNextPatch(); - const before = room.state.playerState.players.get(client.sessionId); + const before = room.state.playerState.players.get(c1.sessionId); assert.deepStrictEqual( { x: before.position.x, y: before.position.y }, { x: 1, y: 2 }, ); - client.send("move", { direction: "down" }); + c1.send("move", { direction: "down" }); await room.waitForNextPatch(); - const after = room.state.playerState.players.get(client.sessionId); + const after = room.state.playerState.players.get(c1.sessionId); assert.deepStrictEqual( { x: after.position.x, y: after.position.y }, { x: 1, y: 3 },