Refactored setPlayerInfo to joinGame and some other refactoring and comments
This commit is contained in:
parent
697a4ddd6d
commit
5ee2d99d42
@ -12,43 +12,45 @@ public class ActionServiceTests
|
||||
{
|
||||
private readonly Player _blackPlayer = Players.Create("black");
|
||||
private readonly Player _redPlayer = Players.Create("red");
|
||||
|
||||
private readonly Player _whitePlayer = Players.Create("white");
|
||||
private ActionMessage _blackMessage = null!;
|
||||
private pacMan.Services.Game _game = null!;
|
||||
private GameService _gameService = null!;
|
||||
private ActionMessage _redMessage = null!;
|
||||
private IActionService _service = null!;
|
||||
|
||||
private Queue<DirectionalPosition> _spawns = null!;
|
||||
|
||||
private ActionMessage _whiteMessage = null!;
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_spawns = CreateQueue();
|
||||
_game = new pacMan.Services.Game(_spawns);
|
||||
_whiteMessage = new ActionMessage
|
||||
{
|
||||
Action = GameAction.PlayerInfo,
|
||||
Data = SerializeData(_whitePlayer)
|
||||
Action = GameAction.JoinGame,
|
||||
Data = SerializeData(_whitePlayer.Username)
|
||||
};
|
||||
_blackMessage = new ActionMessage
|
||||
{
|
||||
Action = GameAction.PlayerInfo,
|
||||
Data = SerializeData(_blackPlayer)
|
||||
Action = GameAction.JoinGame,
|
||||
Data = SerializeData(_blackPlayer.Username)
|
||||
};
|
||||
_redMessage = new ActionMessage
|
||||
{
|
||||
Action = GameAction.PlayerInfo,
|
||||
Data = SerializeData(_redPlayer)
|
||||
Action = GameAction.JoinGame,
|
||||
Data = SerializeData(_redPlayer.Username)
|
||||
};
|
||||
_gameService = Substitute.For<GameService>(Substitute.For<ILogger<GameService>>());
|
||||
_service = new ActionService(Substitute.For<ILogger<ActionService>>(), _gameService);
|
||||
}
|
||||
|
||||
private static JsonElement SerializeData(Player player) =>
|
||||
private JsonElement SerializeData(string username) =>
|
||||
JsonDocument.Parse(JsonSerializer.Serialize(
|
||||
new PlayerInfoData { Player = player, Spawns = CreateQueue() })
|
||||
new JoinGameData { Username = username, GameId = _game.Id }
|
||||
)
|
||||
).RootElement;
|
||||
|
||||
private static Queue<DirectionalPosition> CreateQueue() =>
|
||||
@ -81,34 +83,34 @@ public class ActionServiceTests
|
||||
[Test]
|
||||
public void PlayerInfo_DataIsNull()
|
||||
{
|
||||
var message = new ActionMessage { Action = GameAction.PlayerInfo, Data = "null" };
|
||||
var message = new ActionMessage { Action = GameAction.JoinGame, Data = "null" };
|
||||
var serialized = JsonDocument.Parse(JsonSerializer.Serialize(message.Data));
|
||||
Assert.Throws<JsonException>(() => _service.SetPlayerInfo(serialized.RootElement));
|
||||
Assert.Throws<JsonException>(() => _service.FindGame(serialized.RootElement));
|
||||
message.Data = null;
|
||||
Assert.Throws<NullReferenceException>(() => _service.SetPlayerInfo(message.Data));
|
||||
Assert.Throws<NullReferenceException>(() => _service.FindGame(message.Data));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayerInfo_DataIsNotPlayer()
|
||||
public void PlayerInfo_DataIsNotJoinGameData()
|
||||
{
|
||||
var serialized =
|
||||
JsonDocument.Parse(JsonSerializer.Serialize(new Box { Colour = "white" }));
|
||||
var message = new ActionMessage
|
||||
{
|
||||
Action = GameAction.PlayerInfo,
|
||||
Action = GameAction.JoinGame,
|
||||
Data = serialized.RootElement
|
||||
};
|
||||
Assert.Throws<JsonException>(() => _service.SetPlayerInfo(message.Data));
|
||||
Assert.Throws<JsonException>(() => _service.FindGame(message.Data));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PlayerInfo_DataIsPlayer()
|
||||
public void PlayerInfo_DataIsUsernameAndGameId()
|
||||
{
|
||||
var players = _service.SetPlayerInfo(_whiteMessage.Data);
|
||||
_game.AddPlayer(_whitePlayer);
|
||||
_gameService.Games.Add(_game);
|
||||
var players = _service.FindGame(_whiteMessage.Data);
|
||||
|
||||
var pos = _spawns.Dequeue();
|
||||
_whitePlayer.PacMan.Position = pos;
|
||||
_whitePlayer.PacMan.SpawnPosition = pos;
|
||||
Assert.That(players, Is.InstanceOf<IEnumerable<Player>>());
|
||||
Assert.That(new List<Player> { _whitePlayer }, Is.EqualTo(players));
|
||||
}
|
||||
|
||||
@ -148,9 +150,11 @@ public class ActionServiceTests
|
||||
[Test]
|
||||
public void Ready_NotAllReady()
|
||||
{
|
||||
var game = _gameService.CreateAndJoin(_whitePlayer, _spawns);
|
||||
_gameService.Games.Add(_game);
|
||||
var game = _gameService.JoinById(_game.Id, _whitePlayer);
|
||||
_gameService.JoinById(game.Id, _blackPlayer);
|
||||
_service.SetPlayerInfo(_whiteMessage.Data);
|
||||
|
||||
_service.FindGame(_whiteMessage.Data); // Sets white to ready
|
||||
|
||||
var result = _service.Ready();
|
||||
if (result is ReadyData r1)
|
||||
@ -159,7 +163,7 @@ public class ActionServiceTests
|
||||
Assert.Fail("Result should be ReadyData");
|
||||
|
||||
_gameService.JoinById(game.Id, _redPlayer);
|
||||
_service.SetPlayerInfo(_redMessage.Data);
|
||||
_service.FindGame(_redMessage.Data); // Sets red to ready
|
||||
|
||||
result = _service.Ready();
|
||||
if (result is ReadyData r2)
|
||||
@ -171,7 +175,10 @@ public class ActionServiceTests
|
||||
[Test]
|
||||
public void Ready_OneReady()
|
||||
{
|
||||
_service.SetPlayerInfo(_whiteMessage.Data);
|
||||
_gameService.Games.Add(_game);
|
||||
_gameService.JoinById(_game.Id, _whitePlayer);
|
||||
_service.FindGame(_whiteMessage.Data); // Sets white to ready
|
||||
|
||||
var result = _service.Ready();
|
||||
// If selected the state is changed to InGame
|
||||
_whitePlayer.State = State.InGame;
|
||||
|
@ -60,7 +60,7 @@ public class GameServiceTests
|
||||
public void JoinById_WhenIdNotExists()
|
||||
{
|
||||
var player = Players.Create("white");
|
||||
_service.AddPlayer(player, _spawns);
|
||||
_service.AddPlayer(player, _spawns); // TODO obsolete
|
||||
|
||||
Assert.Throws<GameNotFoundException>(() => _service.JoinById(Guid.NewGuid(), player));
|
||||
}
|
||||
@ -69,7 +69,7 @@ public class GameServiceTests
|
||||
public void JoinById_WhenIdExists()
|
||||
{
|
||||
var player = Players.Create("white");
|
||||
var group = _service.AddPlayer(player, _spawns);
|
||||
var group = _service.AddPlayer(player, _spawns); // TODO obsolete
|
||||
|
||||
var player2 = Players.Create("black");
|
||||
var result = _service.JoinById(group.Id, player2);
|
||||
@ -90,7 +90,7 @@ public class GameServiceTests
|
||||
public void AddPlayer_ToEmptyGroup()
|
||||
{
|
||||
var player = Players.Create("white");
|
||||
var group = _service.AddPlayer(player, _spawns);
|
||||
var group = _service.AddPlayer(player, _spawns); // TODO obsolete
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@ -106,7 +106,7 @@ public class GameServiceTests
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var player = Players.Create(i.ToString());
|
||||
_service.AddPlayer(player, _spawns);
|
||||
_service.AddPlayer(player, _spawns); // TODO obsolete
|
||||
}
|
||||
|
||||
var player5 = Players.Create("white");
|
||||
|
@ -3,14 +3,13 @@ import {AllDice} from "./dice";
|
||||
import {doAction, GameAction} from "../utils/actions";
|
||||
import GameBoard from "./gameBoard";
|
||||
import WebSocketService from "../websockets/WebSocketService";
|
||||
import {getPacManSpawns} from "../game/map";
|
||||
import Player from "../game/player";
|
||||
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";
|
||||
import {useNavigate, useParams} from "react-router-dom";
|
||||
|
||||
const wsService = new WebSocketService(import.meta.env.VITE_API_WS);
|
||||
|
||||
@ -19,6 +18,7 @@ const wsService = new WebSocketService(import.meta.env.VITE_API_WS);
|
||||
// TODO bug, when refreshing page, some data is missing until other clients make a move
|
||||
// TODO bug, stolen pellets are only updated on the client that stole them
|
||||
// TODO bug, when navigating to lobby from the navbar while not logged in, the page is blank instead of redirecting to login
|
||||
// TODO bug, when refreshing page, the player's button show ready, instead of roll dice or waiting
|
||||
|
||||
// TODO spawns should be the same color as the player
|
||||
// TODO better front page
|
||||
@ -29,6 +29,9 @@ const wsService = new WebSocketService(import.meta.env.VITE_API_WS);
|
||||
// TODO sign up player page
|
||||
// TODO show box with collected pellets
|
||||
// TODO layout
|
||||
// TODO end game when all pellets are eaten
|
||||
// TODO store stats in backend
|
||||
// TODO check if game exists on load, if not redirect to lobby
|
||||
|
||||
export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map}) => {
|
||||
|
||||
@ -39,7 +42,11 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
|
||||
const ghosts = useAtomValue(ghostsAtom);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const {id} = useParams();
|
||||
|
||||
/**
|
||||
* Rolls the dice for the current player's turn.
|
||||
*/
|
||||
function rollDice(): void {
|
||||
if (!player.isTurn()) return;
|
||||
|
||||
@ -48,6 +55,10 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
|
||||
setActiveRollDiceButton(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the character moves.
|
||||
* @param {Position[]} eatenPellets - An array of positions where the pellets have been eaten.
|
||||
*/
|
||||
function onCharacterMove(eatenPellets: Position[]): void {
|
||||
if (dice && selectedDice) {
|
||||
dice.splice(selectedDice.index, 1);
|
||||
@ -69,23 +80,37 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
|
||||
}
|
||||
}
|
||||
|
||||
function sendPlayer(): void {
|
||||
wsService.send({
|
||||
action: GameAction.playerInfo,
|
||||
/**
|
||||
* Joins a game by sending a WebSocket request to the server.
|
||||
*/
|
||||
function joinGame(): void {
|
||||
wsService.send({ // TODO if returns exception, navigate to lobby
|
||||
action: GameAction.joinGame,
|
||||
data: {
|
||||
player: player, spawns: getPacManSpawns(map)
|
||||
} as PlayerInfoData
|
||||
username: player.username,
|
||||
gameId: id,
|
||||
} as JoinGameData
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a ready action to the WebSocket service.
|
||||
*/
|
||||
function sendReady(): void {
|
||||
wsService.send({action: GameAction.ready});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the current turn and sends a message to the web socket service
|
||||
* to advance to the next player in the game.
|
||||
*/
|
||||
function endTurn(): void {
|
||||
wsService.send({action: GameAction.nextPlayer});
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaves the current game and navigates to the lobby.
|
||||
*/
|
||||
function leaveGame(): void {
|
||||
wsService.send({action: GameAction.disconnect});
|
||||
navigate("/lobby");
|
||||
@ -95,7 +120,7 @@ export const GameComponent: FC<{ player: Player, map: GameMap }> = ({player, map
|
||||
wsService.onReceive = doAction;
|
||||
wsService.open();
|
||||
|
||||
wsService.waitForOpen().then(() => sendPlayer());
|
||||
wsService.waitForOpen().then(() => joinGame());
|
||||
|
||||
return () => wsService.close();
|
||||
}, []);
|
||||
|
@ -20,7 +20,7 @@ const LobbyPage: FC = () => {
|
||||
async function createGame(): Promise<void> {
|
||||
|
||||
const response = await postData("/game/create", {
|
||||
body: {player: thisPlayer, spawns: getPacManSpawns(map)} as PlayerInfoData
|
||||
body: {player: thisPlayer, spawns: getPacManSpawns(map)} as CreateGameData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
|
@ -2,6 +2,8 @@ type MessageEventFunction<T = any> = (data: MessageEvent<T>) => void;
|
||||
|
||||
type Setter<T> = React.Dispatch<React.SetStateAction<T>>;
|
||||
|
||||
type GUID = `${string}-${string}-${string}-${string}-${string}`;
|
||||
|
||||
type WebSocketData = string | ArrayBufferLike | Blob | ArrayBufferView;
|
||||
|
||||
type ActionMessage<T = any> = {
|
||||
@ -54,7 +56,12 @@ type ApiRequest = {
|
||||
body?: any
|
||||
}
|
||||
|
||||
type PlayerInfoData = {
|
||||
type JoinGameData = {
|
||||
readonly username: string,
|
||||
readonly gameId: GUID,
|
||||
}
|
||||
|
||||
type CreateGameData = {
|
||||
readonly player: PlayerProps,
|
||||
readonly spawns: DirectionalPosition[],
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import {Colour} from "../game/colour";
|
||||
export enum GameAction {
|
||||
rollDice,
|
||||
moveCharacter,
|
||||
playerInfo, // TODO rename to joinGame
|
||||
joinGame,
|
||||
ready,
|
||||
nextPlayer,
|
||||
disconnect,
|
||||
@ -32,7 +32,7 @@ const ghosts = ghostsProps.map(props => new Ghost(props));
|
||||
|
||||
store.set(ghostsAtom, ghosts);
|
||||
|
||||
export const doAction: MessageEventFunction<string> = (event): void => { // TODO divide into smaller functions
|
||||
export const doAction: MessageEventFunction<string> = (event): void => {
|
||||
const message: ActionMessage = JSON.parse(event.data);
|
||||
console.debug("Received message:", message);
|
||||
|
||||
@ -43,8 +43,8 @@ export const doAction: MessageEventFunction<string> = (event): void => { // TODO
|
||||
case GameAction.moveCharacter:
|
||||
moveCharacter(message.data);
|
||||
break;
|
||||
case GameAction.playerInfo:
|
||||
playerInfo(message.data);
|
||||
case GameAction.joinGame:
|
||||
joinGame(message.data);
|
||||
break;
|
||||
case GameAction.ready:
|
||||
ready(message.data);
|
||||
@ -95,7 +95,7 @@ function removeEatenPellets(data?: MoveCharacterData): void {
|
||||
}
|
||||
}
|
||||
|
||||
function playerInfo(data?: PlayerProps[]): void { // TODO missing data when refreshing page
|
||||
function joinGame(data?: PlayerProps[]): void { // TODO missing data when refreshing page
|
||||
const playerProps = data ?? [];
|
||||
spawns = getCharacterSpawns(map).filter(spawn => spawn.type === CharacterType.pacMan);
|
||||
store.set(playersAtom, playerProps.map(p => new Player(p)));
|
||||
|
@ -52,7 +52,7 @@ public class GameController : GenericController
|
||||
}
|
||||
|
||||
[HttpPost("create")]
|
||||
public IActionResult CreateGame([FromBody] PlayerInfoData data)
|
||||
public IActionResult CreateGame([FromBody] CreateGameData data)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug, "Creating game");
|
||||
try
|
||||
|
@ -7,7 +7,7 @@ public enum GameAction
|
||||
{
|
||||
RollDice,
|
||||
MoveCharacter,
|
||||
PlayerInfo,
|
||||
JoinGame,
|
||||
Ready,
|
||||
NextPlayer,
|
||||
Disconnect
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using pacMan.Exceptions;
|
||||
using pacMan.GameStuff;
|
||||
using pacMan.GameStuff.Items;
|
||||
|
||||
@ -13,7 +14,7 @@ public interface IActionService
|
||||
WebSocket? WebSocket { set; }
|
||||
void DoAction(ActionMessage message);
|
||||
List<int> RollDice();
|
||||
List<Player> SetPlayerInfo(JsonElement? jsonElement);
|
||||
List<Player> FindGame(JsonElement? jsonElement);
|
||||
object? HandleMoveCharacter(JsonElement? jsonElement);
|
||||
object Ready();
|
||||
string FindNextPlayer();
|
||||
@ -45,7 +46,7 @@ public class ActionService : IActionService
|
||||
{
|
||||
GameAction.RollDice => RollDice(),
|
||||
GameAction.MoveCharacter => HandleMoveCharacter(message.Data),
|
||||
GameAction.PlayerInfo => SetPlayerInfo(message.Data),
|
||||
GameAction.JoinGame => FindGame(message.Data),
|
||||
GameAction.Ready => Ready(),
|
||||
GameAction.NextPlayer => FindNextPlayer(),
|
||||
GameAction.Disconnect => LeaveGame(),
|
||||
@ -75,29 +76,22 @@ public class ActionService : IActionService
|
||||
return jsonElement;
|
||||
}
|
||||
|
||||
public List<Player> SetPlayerInfo(JsonElement? jsonElement) // TODO split up into two actions, join and create
|
||||
public List<Player> FindGame(JsonElement? jsonElement)
|
||||
{
|
||||
var data = jsonElement?.Deserialize<PlayerInfoData>() ?? throw new NullReferenceException("Data is null");
|
||||
Player = data.Player;
|
||||
// TODO Receive Username and GameId
|
||||
var data = jsonElement?.Deserialize<JoinGameData>() ?? throw new NullReferenceException("Data is null");
|
||||
|
||||
Game? game;
|
||||
if ((game = _gameService.FindGameByUsername(Player.Username)) != null)
|
||||
{
|
||||
var player = game.Players.Find(p => p.Username == Player.Username);
|
||||
if (player is null) throw new NullReferenceException("Player is null");
|
||||
var game = _gameService.Games.FirstOrDefault(game => game.Id == data.GameId) ??
|
||||
throw new GameNotFoundException();
|
||||
|
||||
player.State = game.IsGameStarted ? State.InGame : State.WaitingForPlayers; // TODO doesn't work anymore
|
||||
Player = player;
|
||||
Game = game;
|
||||
// TODO send missing data: Dices, CurrentPlayer, Ghosts
|
||||
}
|
||||
else
|
||||
{
|
||||
Game = _gameService.CreateAndJoin(Player, data.Spawns);
|
||||
}
|
||||
var player = game.Players.Find(p => p.Username == data.Username)
|
||||
?? throw new PlayerNotFoundException("Player was not found in game");
|
||||
|
||||
player.State = game.IsGameStarted ? State.InGame : State.WaitingForPlayers; // TODO doesn't work anymore
|
||||
Player = player;
|
||||
Game = game;
|
||||
// TODO send missing data: Dices, CurrentPlayer, Ghosts | Return Game instead?
|
||||
Game.Connections += SendSegment;
|
||||
|
||||
return Game.Players;
|
||||
}
|
||||
|
||||
@ -147,7 +141,18 @@ public class ActionService : IActionService
|
||||
}
|
||||
}
|
||||
|
||||
public struct PlayerInfoData
|
||||
public struct JoinGameData
|
||||
{
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("username")]
|
||||
public required string Username { get; init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("gameId")]
|
||||
public required Guid GameId { get; init; }
|
||||
}
|
||||
|
||||
public struct CreateGameData
|
||||
{
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("player")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user