-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/room matchmaking logic #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
de519ff
9bd845f
c1707a3
8b64149
9df653e
0593066
486f48a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import type { Room } from "colyseus.js"; | |
| 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,40 @@ 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; | ||
|
|
||
| console.warn({ playerName, mode, roomCode, isPrivate }); | ||
| if (mode === "create") { | ||
| joinedRoom = await client.create("game_room", { | ||
| name: playerName, | ||
| isPrivate, | ||
| }); | ||
| } else if ( | ||
|
Comment on lines
+38
to
+43
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aktualnie gdy próbujemy odświeżać przeglądarkę lub gracza rozłączy na ułamek sekundy, to połączenie nie zostanie odnowione i wyrzuci go z pokoju. Z tego co pamiętam, to działa to na podstawie tokenu w localStorage, który jest wyciągany, by wrócić do pokoju w określonym czasie: |
||
| mode === "join" && | ||
| roomCode !== undefined && | ||
| roomCode.length > 0 | ||
| ) { | ||
| joinedRoom = await client.joinById(roomCode, { | ||
| name: playerName, | ||
| //isPrivate: true, | ||
| }); | ||
| } else { | ||
| joinedRoom = await client.joinOrCreate("game_room", { | ||
| name: playerName, | ||
| isPrivate: false, | ||
| }); | ||
| } | ||
|
|
||
| setRoom(joinedRoom); | ||
| setIsConnected(true); | ||
| localStorage.setItem( | ||
| "reconnection", | ||
| JSON.stringify({ | ||
| token: newRoom.reconnectionToken, | ||
| playerName, | ||
| }), | ||
| ); | ||
| } catch (error) { | ||
| console.error("Join error", error); | ||
| setJoinError(true); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,18 @@ | ||
| import type { Room } from "colyseus.js"; | ||
| import { createContext, useContext } from "react"; | ||
|
|
||
| export interface ConnectOptions { | ||
| playerName: string; | ||
| mode: "join" | "create" | "random"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dla czytelności wyrzućmy random i zostawmy tylko create jako domyślny tryb, gdyby nie został wybrany. |
||
| roomCode?: string; | ||
| isPrivate?: boolean; | ||
| } | ||
|
|
||
| interface RoomContextType { | ||
| room: Room | null; | ||
| isConnected: boolean; | ||
| joinError: boolean; | ||
| connect: (playerName: string) => Promise<void>; | ||
| connect: (options: ConnectOptions) => Promise<void>; | ||
| disconnect: () => Promise<void>; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -34,7 +34,10 @@ export function Intro() { | |||||
|
|
||||||
| setStatus("loading"); | ||||||
| try { | ||||||
| await connect(name.trim()); | ||||||
| await connect({ | ||||||
| playerName: name.trim(), | ||||||
| mode: "create", | ||||||
| }); | ||||||
| await navigate("/game"); | ||||||
| } catch { | ||||||
| setErrorMessage("Nie udało się dołaczyć do gry. Spróbuj ponownie."); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,64 @@ | ||||||
| import { useEffect, useState } from "react"; | ||||||
| import { useNavigate } from "react-router-dom"; | ||||||
|
|
||||||
| import { useRoom } from "../lib/use-room"; | ||||||
|
|
||||||
| export function Lobby() { | ||||||
| const { room, isConnected } = useRoom(); | ||||||
| const navigate = useNavigate(); | ||||||
| const [players, setPlayers] = useState<any[]>([]); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| if (!room) return; | ||||||
|
|
||||||
| const handleStateChange = (state: any) => { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| if (state.playerState?.players) { | ||||||
| setPlayers(Array.from(state.playerState.players.values())); | ||||||
| } | ||||||
|
|
||||||
| if (state.gameStarted) { | ||||||
| navigate("/game"); | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| room.onStateChange(handleStateChange); | ||||||
|
|
||||||
| handleStateChange(room.state); | ||||||
|
|
||||||
| return () => { | ||||||
| room.onStateChange.remove(handleStateChange); | ||||||
| }; | ||||||
| }, [room, navigate]); | ||||||
|
|
||||||
| if (!isConnected || !room) return <div>Łączenie...</div>; | ||||||
|
|
||||||
| const myPlayer = players.find((p) => p.sessionId === room.sessionId); | ||||||
|
|
||||||
| return ( | ||||||
| <div> | ||||||
| <h2>Pokój: {room.roomId}</h2> | ||||||
|
|
||||||
| <ul> | ||||||
| {players.map((p) => ( | ||||||
| <li key={p.sessionId} style={{ margin: "10px 0", fontSize: "20px" }}> | ||||||
| <strong>{p.name || "Anonim"}</strong> - Postać:{" "} | ||||||
| {p.index === 0 ? "Sol" : "Vron"} - | ||||||
| {p.ready ? " ✅ GOTOWY" : " ❌ CZEKA"} | ||||||
| </li> | ||||||
| ))} | ||||||
| </ul> | ||||||
|
|
||||||
| <button | ||||||
| onClick={() => room.send("toggle_ready")} | ||||||
| style={{ | ||||||
| padding: "15px 30px", | ||||||
| fontSize: "18px", | ||||||
| marginTop: "20px", | ||||||
| background: "green", | ||||||
| }} | ||||||
| > | ||||||
| {myPlayer?.ready ? "Cofnij gotowość" : "Daj gotowość"} | ||||||
| </button> | ||||||
| </div> | ||||||
| ); | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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">("join"); | ||
| 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 ( | ||
| <IntroContainer> | ||
| <TitleHeader title="Capybara Escape" /> | ||
|
|
||
| {status === "reconnecting" ? ( | ||
| <div className="flex flex-col items-center gap-4 text-center"> | ||
| <p className="text-lg text-amber-200"> | ||
| Próba ponownego połączenia za {countdown}... | ||
| <br /> | ||
| </p> | ||
| <Button disabled={false} onClick={handleCancelReconnection}> | ||
| Anuluj ponowne połączenie. | ||
| </Button> | ||
| </div> | ||
| ) : ( | ||
| <> | ||
| <div className="flex gap-2"> | ||
| <button | ||
| onClick={() => { | ||
| setMode("join"); | ||
| }} | ||
| className={`flex-1 rounded p-2 ${mode === "join" ? "bg-violet-600" : "bg-violet-800"}`} | ||
| > | ||
| Join | ||
| </button> | ||
| <button | ||
| onClick={() => { | ||
| setMode("create"); | ||
| }} | ||
| className={`flex-1 rounded p-2 ${mode === "create" ? "bg-violet-600" : "bg-violet-800"}`} | ||
| > | ||
| Create | ||
| </button> | ||
| </div> | ||
| <CustomInput | ||
| value={name} | ||
| placeholder="Elek..." | ||
| setValue={(value) => { | ||
| setName(value.toUpperCase()); | ||
| }} | ||
| disabled={status === "loading"} | ||
| /> | ||
| {mode === "join" ? ( | ||
| <input | ||
| type="text" | ||
| placeholder="KOD POKOJU" | ||
| className="rounded border border-violet-700 bg-violet-950 p-2 text-white outline-none placeholder:text-violet-400 focus:border-amber-400" | ||
| value={roomCode} | ||
| onChange={(error) => { | ||
| setRoomCode(error.target.value); | ||
| }} | ||
| /> | ||
| ) : ( | ||
| <label className="flex cursor-pointer items-center gap-2 text-white"> | ||
| <input | ||
| type="checkbox" | ||
| className="h-4 w-4 accent-violet-600" | ||
| checked={isPrivate} | ||
| onChange={(error) => { | ||
| setIsPrivate(error.target.checked); | ||
| }} | ||
| /> | ||
| Pokój prywatny | ||
| </label> | ||
| )} | ||
|
|
||
| <Button | ||
| onClick={handlePlay} | ||
| disabled={status === "loading" || name.trim() === ""} | ||
| > | ||
| {status === "loading" ? "Ładowanie..." : "Graj"} | ||
| </Button> | ||
| </> | ||
| )} | ||
|
|
||
| {status === "error" && <ErrorContainer errorMessage={errorMessage} />} | ||
| </IntroContainer> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
przed mergem