Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ import { 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 (
<div className="arcade-font flex min-h-screen items-center justify-center bg-violet-950 text-white">
<Router>
<Routes>
<Route path="/" element={<Intro />} />
<Route path="/start" element={<Start />} />
<Route path="/lobby" element={<Lobby />} />
<Route path="/game" element={<Game />} />
<Route path="/creator" element={<Creator />} />
<Route path="/playground" element={<Playground />} />
Expand Down
45 changes: 33 additions & 12 deletions client/src/lib/room-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

przed mergem

Suggested change
console.warn({ playerName, mode, roomCode, isPrivate });
//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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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:

localStorage.setItem("reconnection", JSON.stringify({
  token: joinedRoom.reconnectionToken,
  playerName,
}));

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);
Expand Down
9 changes: 8 additions & 1 deletion client/src/lib/use-room.ts
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";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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>;
}

Expand Down
4 changes: 4 additions & 0 deletions client/src/pages/game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export function Game() {
return (
<div className="flex h-[560px] w-[800px] items-center justify-center overflow-hidden rounded-2xl bg-violet-950">
<PhaserGame room={room} />
<div className="absolute top-4 left-4 rounded bg-black/50 p-2 text-white">
KOD POKOJU:{" "}
<span className="font-bold text-amber-400">{room.roomId}</span>
</div>
</div>
);
}
5 changes: 4 additions & 1 deletion client/src/pages/intro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setErrorMessage("Nie udało się dołaczyć do gry. Spróbuj ponownie.");
setErrorMessage("Nie udało się dołączyć do gry. Spróbuj ponownie.");

Expand Down
64 changes: 64 additions & 0 deletions client/src/pages/lobby.tsx
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) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleStateChange = (state: any) => {
const handleStateChange = (state: RoomState) => {

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>
);
}
187 changes: 187 additions & 0 deletions client/src/pages/start.tsx
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>
);
}
1 change: 1 addition & 0 deletions server/src/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default config({
* Define your room handlers:
*/
gameServer.define("game_room", GameRoom);
// .filterBy(['isPrivate']);
},

initializeExpress: (app) => {
Expand Down
Loading
Loading