Created a simple lobby containing all games

This commit is contained in:
Martin Berg Alstad 2023-07-18 13:50:46 +02:00
parent ac8560e61c
commit ad0d8c7d0a
13 changed files with 131 additions and 51 deletions

View File

@ -1,3 +1,5 @@
PORT=44435
HTTPS=true
VITE_API=wss://localhost:3000/api/game
VITE_API_URI=localhost:3000/api/game
VITE_API_HTTP=https://$VITE_API_URI
VITE_API_WS=wss://$VITE_API_URI/connect

View File

@ -1,6 +1,6 @@
import React from "react";
import {Route, Routes} from "react-router-dom";
import {Layout} from "./components/Layout";
import Layout from "./components/layout";
import AppRoutes from "./AppRoutes";
import "./index.css";

View File

@ -2,6 +2,7 @@ import React from "react";
import {Counter} from "./pages/counter";
import Home from "./pages/home";
import Game from "./pages/game";
import LobbyPage from "./pages/lobby";
const AppRoutes = [
{
@ -16,6 +17,10 @@ const AppRoutes = [
path: "/game",
element: <Game/>,
},
{
path: "/lobby",
element: <LobbyPage/>,
}
];
export default AppRoutes;

View File

@ -1,11 +0,0 @@
import React from "react";
import {NavMenu} from "./NavMenu";
export const Layout: Component<ChildProps> = ({children}) => (
<div>
<NavMenu/>
<main>
{children}
</main>
</div>
);

View File

@ -1,26 +0,0 @@
import React from "react";
import {Link} from "react-router-dom";
export const NavMenu = () => {
const [collapsed, setCollapsed] = React.useState(true);
function toggleNavbar() {
setCollapsed(!collapsed);
}
return (
<header>
<nav className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3">
<Link to="/">Pac-Man Board Game</Link>
<div onClick={toggleNavbar} className="mr-2"/>
<div className="d-sm-inline-flex flex-sm-row-reverse">
<ul className="navbar-nav flex-grow">
<Link className="text-dark" to="/">Home</Link>
<Link className="text-dark" to="/counter">Counter</Link>
</ul>
</div>
</nav>
</header>
);
};

View File

@ -11,7 +11,7 @@ import {diceAtom, ghostsAtom, playersAtom, rollDiceButtonAtom, selectedDiceAtom}
import {CharacterType} from "../game/character";
import GameButton from "./gameButton";
const wsService = new WebSocketService(import.meta.env.VITE_API);
const wsService = new WebSocketService(import.meta.env.VITE_API_WS);
// TODO bug, when taking player on last dice, the currentPlayer changes and the wrong character get to steal
// TODO bug, first player can sometimes roll dice twice (maybe only on firefox)

View File

@ -0,0 +1,13 @@
import React from "react";
import NavMenu from "./navMenu";
const Layout: Component<ChildProps> = ({children}) => (
<div>
<NavMenu/>
<main>
{children}
</main>
</div>
);
export default Layout;

View File

@ -0,0 +1,28 @@
import React from "react";
import {Link} from "react-router-dom";
const NavMenu: Component = () => {
const [collapsed, setCollapsed] = React.useState(true);
function toggleNavbar() {
setCollapsed(!collapsed);
}
return (
<header>
<nav className="navbar-expand-sm navbar-toggleable-sm ng-white border-bottom box-shadow mb-3">
<Link to="/">Pac-Man Board Game</Link>
<div onClick={toggleNavbar} className="mr-2"/>
<div className="d-sm-inline-flex flex-sm-row-reverse">
<ul className="navbar-nav flex-grow">
<Link className="text-dark" to="/">Home</Link>
<Link className="text-dark" to="/counter">Counter</Link>
</ul>
</div>
</nav>
</header>
);
};
export default NavMenu;

View File

@ -0,0 +1,52 @@
import React, {Suspense} from "react";
import {atom, useAtomValue} from "jotai";
import {Button} from "../components/Button";
const fetchAtom = atom(async () => {
const response = await fetch(import.meta.env.VITE_API_HTTP + "/allGames");
return await response.json() as GameGroup[];
});
const LobbyPage: Component = () => (
<Suspense fallback={"Please wait"}>
<GameTable className={"mx-auto"}/>
</Suspense>
)
export default LobbyPage;
const GameTable: Component = ({className}) => {
const data = useAtomValue(fetchAtom);
function joinGame(gameId: string): void {
console.log("Joining game " + gameId); // TODO: Implement
}
return (
<table className={`rounded overflow-hidden ${className}`}>
<thead className={"bg-gray-500 text-white"}>
<tr className={"my-5"}>
<th>Id</th>
<th>Count</th>
<th className={"p-2"}>State</th>
<th>Join</th>
</tr>
</thead>
<tbody>
{data?.map(game =>
<tr key={game.id} className={"even:bg-gray-200"}>
<td className={"p-2"}>{game.id}</td>
<td className={"text-center"}>{game.count}</td>
<td>{game.isGameStarted ? "Closed" : "Open"}</td>
<td className={"p-2"}>
<Button disabled={game.isGameStarted} onClick={() => joinGame(game.id)}>
Join
</Button>
</td>
</tr>
)}
</tbody>
</table>
);
}

View File

@ -34,3 +34,9 @@ type Path = {
End: Position,
Direction: import("../game/direction").Direction
}
type GameGroup = {
readonly id: string,
readonly count: number,
readonly isGameStarted: boolean,
}

View File

@ -1,7 +1,9 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API: string;
readonly VITE_API_URI: string,
readonly VITE_API_HTTP: string,
readonly VITE_API_WS: string,
}
interface ImportMeta {

View File

@ -17,9 +17,17 @@ public class GameController : GenericController // TODO reconnect using player i
base(logger, wsService) =>
_actionService = actionService;
[HttpGet]
[HttpGet("connect")]
public override async Task Accept() => await base.Accept();
[HttpGet("allGames")]
public IEnumerable<GameGroup> GetAllGames()
{
Logger.Log(LogLevel.Information, "Returning all games");
return WsService.Games;
}
protected override ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data)
{
var stringResult = data.GetString(result.Count);

View File

@ -1,28 +1,28 @@
using System.Collections;
using System.Text.Json.Serialization;
using pacMan.Exceptions;
using pacMan.Game;
using pacMan.Game.Items;
namespace pacMan.Services;
public class GameGroup : IEnumerable<IPlayer> // TODO handle disconnects and reconnects
public class GameGroup // TODO handle disconnects and reconnects
{
private readonly Random _random = new();
private int _currentPlayerIndex;
public GameGroup(Queue<DirectionalPosition> spawns) => Spawns = spawns;
public List<IPlayer> Players { get; } = new();
private Queue<DirectionalPosition> Spawns { get; }
[JsonInclude] public Guid Id { get; } = Guid.NewGuid();
public int Count => Players.Count;
[JsonIgnore] public List<IPlayer> Players { get; } = new();
[JsonIgnore] private Queue<DirectionalPosition> Spawns { get; }
[JsonInclude] public int Count => Players.Count;
[JsonInclude]
public bool IsGameStarted => Count > 0 && Players.All(player => player.State is State.InGame or State.Disconnected);
public IEnumerator<IPlayer> GetEnumerator() => Players.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IPlayer NextPlayer()
{
try
@ -33,6 +33,7 @@ public class GameGroup : IEnumerable<IPlayer> // TODO handle disconnects and rec
{
throw new InvalidOperationException("There are no players in the game group.");
}
return Players[_currentPlayerIndex];
}