Moved event to Game, added leave game button

This commit is contained in:
Martin Berg Alstad 2023-07-20 23:54:20 +02:00
parent 7af502f570
commit 554b8cff2a
7 changed files with 67 additions and 50 deletions

View File

@ -1,6 +1,5 @@
import React from "react";
import {Counter} from "./pages/counter";
import Home from "./pages/home";
import Game from "./pages/game";
import LobbyPage from "./pages/lobby";
import Login from "./pages/login";
@ -8,7 +7,7 @@ import Login from "./pages/login";
const AppRoutes = [
{
index: true,
element: <Home/>
element: <LobbyPage/>
},
{
path: "/counter",

View File

@ -9,19 +9,21 @@ import PlayerStats from "../components/playerStats";
import {useAtom, useAtomValue, useSetAtom} from "jotai";
import {diceAtom, ghostsAtom, playersAtom, rollDiceButtonAtom, selectedDiceAtom} from "../utils/state";
import GameButton from "./gameButton";
import {Button} from "./button";
import {useNavigate} from "react-router-dom";
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)
// TODO bug, when taking player on last dice, the currentPlayer changes and the wrong character gets to steal
// TODO bug, first player can sometimes roll dice twice
// TODO bug, when refreshing page, the player is still in disconnected state
// TODO bug, when refreshing page, the characters are reset for all players
// TODO bug, when refreshing page, some data is missing until other clients make a move
// TODO bug, teleportation doesn't work
// TODO store map in backend and save it in state on each client
// TODO add debug menu on dev, for testing and cheating
// TODO join/new game lobby
// TODO sign up player page
// TODO sign in page
// TODO show box with collected pellets
// TODO layout
@ -33,6 +35,8 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
const setActiveRollDiceButton = useSetAtom(rollDiceButtonAtom);
const ghosts = useAtomValue(ghostsAtom);
const navigate = useNavigate();
function rollDice(): void {
if (!player.isTurn()) return;
@ -79,6 +83,11 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
wsService.send({action: GameAction.nextPlayer});
}
function leaveGame(): void {
wsService.send({action: GameAction.disconnect});
navigate("/lobby");
}
useEffect(() => {
wsService.onReceive = doAction;
wsService.open();
@ -90,6 +99,7 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
return (
<>
<Button onClick={leaveGame}>Leave game</Button>
<div className={"flex justify-center"}>
{players?.map(p => <PlayerStats key={p.username} player={p}/>)}
</div>

View File

@ -50,6 +50,9 @@ export const doAction: MessageEventFunction<string> = (event): void => { // TODO
case GameAction.nextPlayer:
nextPlayer(message.data);
break;
case GameAction.disconnect:
updatePlayers(message.data);
break;
}
};
@ -61,14 +64,12 @@ type MoveCharacterData = { dice: number[], players: PlayerProps[], ghosts: Chara
function moveCharacter(data?: MoveCharacterData): void {
store.set(diceAtom, data?.dice);
updatePlayers(data);
updatePlayers(data?.players);
updateGhosts(data);
removeEatenPellets(data);
}
function updatePlayers(data?: MoveCharacterData): void {
const updatedPlayers = data?.players;
function updatePlayers(updatedPlayers?: PlayerProps[]): void {
if (updatedPlayers) {
const newList: Player[] = updatedPlayers.map(p => new Player(p));
store.set(playersAtom, newList);

View File

@ -66,6 +66,11 @@ public class GameController : GenericController
}
}
protected override Task Echo()
{
_actionService.WebSocket = WebSocket ?? throw new NullReferenceException("WebSocket is null");
return base.Echo();
}
protected override ArraySegment<byte> Run(WebSocketReceiveResult result, byte[] data)
{
@ -78,24 +83,7 @@ public class GameController : GenericController
return action.ToArraySegment();
}
protected override void Send(ArraySegment<byte> segment) => _gameService.SendToAll(segment);
protected override void Send(ArraySegment<byte> segment) => _actionService.SendToAll(segment);
protected override Task Echo()
{
_gameService.Connections += WsServiceOnFire; // TODO move to ActionService
// _actionService.Game.Connections += WsServiceOnFire;
return base.Echo();
}
protected override void Disconnect()
{
_gameService.Connections -= WsServiceOnFire;
_actionService.Disconnect();
}
private async Task WsServiceOnFire(ArraySegment<byte> segment)
{
if (WebSocket == null) return;
await GameService.Send(WebSocket, segment);
}
protected override void Disconnect() => _actionService.Disconnect();
}

View File

@ -31,7 +31,7 @@ public class Character : IEquatable<Character>
return obj.GetType() == GetType() && Equals((Character)obj);
}
public override int GetHashCode() => HashCode.Combine(Colour, Position, IsEatable, SpawnPosition, (int)Type);
public override int GetHashCode() => HashCode.Combine(Colour, Position, IsEatable, SpawnPosition, (int?)Type);
}
public enum CharacterType

View File

@ -1,3 +1,4 @@
using System.Net.WebSockets;
using System.Text.Json;
using System.Text.Json.Serialization;
using pacMan.GameStuff;
@ -9,12 +10,15 @@ public interface IActionService
{
Player Player { set; }
Game Game { set; }
WebSocket? WebSocket { set; }
void DoAction(ActionMessage message);
List<int> RollDice();
List<Player> SetPlayerInfo(JsonElement? jsonElement);
object? HandleMoveCharacter(JsonElement? jsonElement); // TODO test
object? HandleMoveCharacter(JsonElement? jsonElement);
object Ready();
string FindNextPlayer();
List<Player> LeaveGame();
void SendToAll(ArraySegment<byte> segment);
void Disconnect();
}
@ -29,6 +33,8 @@ public class ActionService : IActionService
_gameService = gameService;
}
public WebSocket? WebSocket { private get; set; }
public Game? Game { get; set; }
public Player? Player { get; set; }
@ -42,6 +48,7 @@ public class ActionService : IActionService
GameAction.PlayerInfo => SetPlayerInfo(message.Data),
GameAction.Ready => Ready(),
GameAction.NextPlayer => FindNextPlayer(),
GameAction.Disconnect => LeaveGame(),
_ => message.Data
};
}
@ -55,19 +62,28 @@ public class ActionService : IActionService
return rolls;
}
public object? HandleMoveCharacter(JsonElement? jsonElement)
{
if (Game != null && jsonElement.HasValue)
Game.Ghosts = jsonElement.Value.GetProperty("ghosts").Deserialize<List<Character>>() ??
throw new JsonException("Ghosts is null");
return jsonElement;
}
public List<Player> SetPlayerInfo(JsonElement? jsonElement) // TODO split up into two actions
{
var data = jsonElement?.Deserialize<PlayerInfoData>() ?? throw new NullReferenceException("Data is null");
Player = data.Player;
Game? group;
Game? game;
Player? player;
if ((group = _gameService.FindGameByUsername(Player.Username)) != null &&
(player = group.Players.Find(p => p.Username == Player.Username))?.State == State.Disconnected)
if ((game = _gameService.FindGameByUsername(Player.Username)) != null &&
(player = game.Players.Find(p => p.Username == Player.Username))?.State == State.Disconnected)
{
player.State = group.IsGameStarted ? State.InGame : State.WaitingForPlayers;
player.State = game.IsGameStarted ? State.InGame : State.WaitingForPlayers; // TODO doesn't work anymore
Player = player;
Game = group;
Game = game;
// TODO send missing data: Dices, CurrentPlayer, Ghosts
}
else
@ -75,6 +91,8 @@ public class ActionService : IActionService
Game = _gameService.AddPlayer(Player, data.Spawns);
}
Game.Connections += SendSegment;
return Game.Players;
}
@ -100,18 +118,26 @@ public class ActionService : IActionService
public string FindNextPlayer() => Game?.NextPlayer().Username ?? "Error: No group found";
public void Disconnect()
public List<Player> LeaveGame()
{
if (Player != null) Player.State = State.Disconnected;
if (Game == null || Player == null) throw new NullReferenceException("Game or Player is null");
Game.RemovePlayer(Player.Username);
return Game.Players;
}
public object? HandleMoveCharacter(JsonElement? jsonElement)
public void Disconnect()
{
if (Game != null && jsonElement.HasValue)
Game.Ghosts = jsonElement.Value.GetProperty("ghosts").Deserialize<List<Character>>() ??
throw new JsonException("Ghosts is null");
if (Player == null) return;
Player.State = State.Disconnected;
if (Game != null) Game.Connections -= SendSegment;
}
return jsonElement;
public void SendToAll(ArraySegment<byte> segment) => Game?.SendToAll(segment);
private async Task SendSegment(ArraySegment<byte> segment)
{
if (WebSocket != null) await _gameService.Send(WebSocket, segment);
else await Task.FromCanceled(new CancellationToken(true));
}
}

View File

@ -19,13 +19,6 @@ public class GameService : WebSocketService
/// </summary>
public SynchronizedCollection<Game> Games { get; } = new();
public event Func<ArraySegment<byte>, Task>? Connections; // TODO remove and use Game
public void SendToAll(ArraySegment<byte> segment)
{
Connections?.Invoke(segment);
}
public Game AddPlayer(Player player, Queue<DirectionalPosition> spawns)
{
var index = 0;