Implement game layer from protobuf to redux

This commit is contained in:
seavor 2026-04-12 05:05:16 -05:00
parent d96d5e1589
commit 74803442d2
82 changed files with 2455 additions and 88 deletions

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { AttachCardParams } from 'types';
export function attachCard(gameId: number, params: AttachCardParams): void {
BackendService.sendGameCommand(gameId, 'Command_AttachCard', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { ChangeZonePropertiesParams } from 'types';
export function changeZoneProperties(gameId: number, params: ChangeZonePropertiesParams): void {
BackendService.sendGameCommand(gameId, 'Command_ChangeZoneProperties', params);
}

View file

@ -0,0 +1,5 @@
import { BackendService } from '../../services/BackendService';
export function concede(gameId: number): void {
BackendService.sendGameCommand(gameId, 'Command_Concede', {});
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { CreateArrowParams } from 'types';
export function createArrow(gameId: number, params: CreateArrowParams): void {
BackendService.sendGameCommand(gameId, 'Command_CreateArrow', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { CreateCounterParams } from 'types';
export function createCounter(gameId: number, params: CreateCounterParams): void {
BackendService.sendGameCommand(gameId, 'Command_CreateCounter', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { CreateTokenParams } from 'types';
export function createToken(gameId: number, params: CreateTokenParams): void {
BackendService.sendGameCommand(gameId, 'Command_CreateToken', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { DeckSelectParams } from 'types';
export function deckSelect(gameId: number, params: DeckSelectParams): void {
BackendService.sendGameCommand(gameId, 'Command_DeckSelect', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { DelCounterParams } from 'types';
export function delCounter(gameId: number, params: DelCounterParams): void {
BackendService.sendGameCommand(gameId, 'Command_DelCounter', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { DeleteArrowParams } from 'types';
export function deleteArrow(gameId: number, params: DeleteArrowParams): void {
BackendService.sendGameCommand(gameId, 'Command_DeleteArrow', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { DrawCardsParams } from 'types';
export function drawCards(gameId: number, params: DrawCardsParams): void {
BackendService.sendGameCommand(gameId, 'Command_DrawCards', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { DumpZoneParams } from 'types';
export function dumpZone(gameId: number, params: DumpZoneParams): void {
BackendService.sendGameCommand(gameId, 'Command_DumpZone', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { FlipCardParams } from 'types';
export function flipCard(gameId: number, params: FlipCardParams): void {
BackendService.sendGameCommand(gameId, 'Command_FlipCard', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { GameSayParams } from 'types';
export function gameSay(gameId: number, params: GameSayParams): void {
BackendService.sendGameCommand(gameId, 'Command_GameSay', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { IncCardCounterParams } from 'types';
export function incCardCounter(gameId: number, params: IncCardCounterParams): void {
BackendService.sendGameCommand(gameId, 'Command_IncCardCounter', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { IncCounterParams } from 'types';
export function incCounter(gameId: number, params: IncCounterParams): void {
BackendService.sendGameCommand(gameId, 'Command_IncCounter', params);
}

View file

@ -0,0 +1,31 @@
export { leaveGame } from './leaveGame';
export { kickFromGame } from './kickFromGame';
export { gameSay } from './gameSay';
export { readyStart } from './readyStart';
export { concede } from './concede';
export { nextTurn } from './nextTurn';
export { setActivePhase } from './setActivePhase';
export { reverseTurn } from './reverseTurn';
export { moveCard } from './moveCard';
export { flipCard } from './flipCard';
export { attachCard } from './attachCard';
export { createToken } from './createToken';
export { setCardAttr } from './setCardAttr';
export { setCardCounter } from './setCardCounter';
export { incCardCounter } from './incCardCounter';
export { drawCards } from './drawCards';
export { undoDraw } from './undoDraw';
export { createArrow } from './createArrow';
export { deleteArrow } from './deleteArrow';
export { createCounter } from './createCounter';
export { setCounter } from './setCounter';
export { incCounter } from './incCounter';
export { delCounter } from './delCounter';
export { shuffle } from './shuffle';
export { dumpZone } from './dumpZone';
export { revealCards } from './revealCards';
export { changeZoneProperties } from './changeZoneProperties';
export { deckSelect } from './deckSelect';
export { setSideboardPlan } from './setSideboardPlan';
export { setSideboardLock } from './setSideboardLock';
export { mulligan } from './mulligan';

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { KickFromGameParams } from 'types';
export function kickFromGame(gameId: number, params: KickFromGameParams): void {
BackendService.sendGameCommand(gameId, 'Command_KickFromGame', params);
}

View file

@ -0,0 +1,5 @@
import { BackendService } from '../../services/BackendService';
export function leaveGame(gameId: number): void {
BackendService.sendGameCommand(gameId, 'Command_LeaveGame', {});
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { MoveCardParams } from 'types';
export function moveCard(gameId: number, params: MoveCardParams): void {
BackendService.sendGameCommand(gameId, 'Command_MoveCard', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { MulliganParams } from 'types';
export function mulligan(gameId: number, params: MulliganParams): void {
BackendService.sendGameCommand(gameId, 'Command_Mulligan', params);
}

View file

@ -0,0 +1,5 @@
import { BackendService } from '../../services/BackendService';
export function nextTurn(gameId: number): void {
BackendService.sendGameCommand(gameId, 'Command_NextTurn', {});
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { ReadyStartParams } from 'types';
export function readyStart(gameId: number, params: ReadyStartParams): void {
BackendService.sendGameCommand(gameId, 'Command_ReadyStart', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { RevealCardsParams } from 'types';
export function revealCards(gameId: number, params: RevealCardsParams): void {
BackendService.sendGameCommand(gameId, 'Command_RevealCards', params);
}

View file

@ -0,0 +1,5 @@
import { BackendService } from '../../services/BackendService';
export function reverseTurn(gameId: number): void {
BackendService.sendGameCommand(gameId, 'Command_ReverseTurn', {});
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { SetActivePhaseParams } from 'types';
export function setActivePhase(gameId: number, params: SetActivePhaseParams): void {
BackendService.sendGameCommand(gameId, 'Command_SetActivePhase', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { SetCardAttrParams } from 'types';
export function setCardAttr(gameId: number, params: SetCardAttrParams): void {
BackendService.sendGameCommand(gameId, 'Command_SetCardAttr', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { SetCardCounterParams } from 'types';
export function setCardCounter(gameId: number, params: SetCardCounterParams): void {
BackendService.sendGameCommand(gameId, 'Command_SetCardCounter', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { SetCounterParams } from 'types';
export function setCounter(gameId: number, params: SetCounterParams): void {
BackendService.sendGameCommand(gameId, 'Command_SetCounter', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { SetSideboardLockParams } from 'types';
export function setSideboardLock(gameId: number, params: SetSideboardLockParams): void {
BackendService.sendGameCommand(gameId, 'Command_SetSideboardLock', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { SetSideboardPlanParams } from 'types';
export function setSideboardPlan(gameId: number, params: SetSideboardPlanParams): void {
BackendService.sendGameCommand(gameId, 'Command_SetSideboardPlan', params);
}

View file

@ -0,0 +1,6 @@
import { BackendService } from '../../services/BackendService';
import { ShuffleParams } from 'types';
export function shuffle(gameId: number, params: ShuffleParams): void {
BackendService.sendGameCommand(gameId, 'Command_Shuffle', params);
}

View file

@ -0,0 +1,5 @@
import { BackendService } from '../../services/BackendService';
export function undoDraw(gameId: number): void {
BackendService.sendGameCommand(gameId, 'Command_UndoDraw', {});
}

View file

@ -1,4 +1,5 @@
export * as AdminCommands from './admin';
export * as GameCommands from './game';
export * as ModeratorCommands from './moderator';
export * as RoomCommands from './room';
export * as SessionCommands from './session';

View file

@ -1,6 +1,3 @@
import { ProtobufEvents } from '../../services/ProtobufService';
import { playerPropertiesChanged } from './playerPropertiesChanged';
export const CommonEvents: ProtobufEvents = {
'.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged,
}
export const CommonEvents: ProtobufEvents = {};

View file

@ -1,6 +1,3 @@
import { PlayerGamePropertiesData } from '../session/interfaces';
import { SessionPersistence } from '../../persistence';
export function playerPropertiesChanged(payload: PlayerGamePropertiesData): void {
SessionPersistence.playerPropertiesChanged(payload);
}
// Event_PlayerPropertiesChanged is handled as a game event in websocket/events/game/playerPropertiesChanged.ts
// This file is retained for reference but is no longer registered in CommonEvents.
export {};

View file

@ -0,0 +1,6 @@
import { AttachCardData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function attachCard(data: AttachCardData, meta: GameEventMeta): void {
GamePersistence.cardAttached(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { ChangeZonePropertiesData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function changeZoneProperties(data: ChangeZonePropertiesData, meta: GameEventMeta): void {
GamePersistence.zonePropertiesChanged(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { CreateArrowData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function createArrow(data: CreateArrowData, meta: GameEventMeta): void {
GamePersistence.arrowCreated(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { CreateCounterData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function createCounter(data: CreateCounterData, meta: GameEventMeta): void {
GamePersistence.counterCreated(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { CreateTokenData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function createToken(data: CreateTokenData, meta: GameEventMeta): void {
GamePersistence.tokenCreated(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { DelCounterData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function delCounter(data: DelCounterData, meta: GameEventMeta): void {
GamePersistence.counterDeleted(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { DeleteArrowData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function deleteArrow(data: DeleteArrowData, meta: GameEventMeta): void {
GamePersistence.arrowDeleted(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { DestroyCardData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function destroyCard(data: DestroyCardData, meta: GameEventMeta): void {
GamePersistence.cardDestroyed(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { DrawCardsData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function drawCards(data: DrawCardsData, meta: GameEventMeta): void {
GamePersistence.cardsDrawn(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { DumpZoneData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function dumpZone(data: DumpZoneData, meta: GameEventMeta): void {
GamePersistence.zoneDumped(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { FlipCardData, GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function flipCard(data: FlipCardData, meta: GameEventMeta): void {
GamePersistence.cardFlipped(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function gameClosed(_data: {}, meta: GameEventMeta): void {
GamePersistence.gameClosed(meta.gameId);
}

View file

@ -1,29 +1,31 @@
jest.mock('../../persistence', () => ({
GamePersistence: {
joinGame: jest.fn(),
leaveGame: jest.fn(),
playerJoined: jest.fn(),
playerLeft: jest.fn(),
},
}));
import { GamePersistence } from '../../persistence';
import { joinGame } from './joinGame';
import { leaveGame } from './leaveGame';
beforeEach(() => jest.clearAllMocks());
describe('joinGame event', () => {
const { joinGame } = jest.requireActual('./joinGame');
it('delegates to GamePersistence.joinGame', () => {
const data = { gameId: 5, player: { playerId: 1 } } as any;
joinGame(data);
expect(GamePersistence.joinGame).toHaveBeenCalledWith(data);
it('delegates to GamePersistence.playerJoined with gameId from meta', () => {
const playerProperties = { playerId: 1 };
const data = { playerProperties } as any;
const meta = { gameId: 5, playerId: 1, context: null, secondsElapsed: 0, forcedByJudge: 0 };
joinGame(data, meta);
expect(GamePersistence.playerJoined).toHaveBeenCalledWith(5, playerProperties);
});
});
describe('leaveGame event', () => {
const { leaveGame } = jest.requireActual('./leaveGame');
it('delegates to GamePersistence.leaveGame', () => {
leaveGame(42 as any);
expect(GamePersistence.leaveGame).toHaveBeenCalledWith(42);
it('delegates to GamePersistence.playerLeft with gameId/playerId from meta', () => {
const data = { reason: 3 };
const meta = { gameId: 5, playerId: 2, context: null, secondsElapsed: 0, forcedByJudge: 0 };
leaveGame(data, meta);
expect(GamePersistence.playerLeft).toHaveBeenCalledWith(5, 2, 3);
});
});

View file

@ -0,0 +1,10 @@
import { GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
/**
* Event_GameHostChanged carries no payload fields.
* The new host is identified by GameEvent.player_id (meta.playerId).
*/
export function gameHostChanged(_data: {}, meta: GameEventMeta): void {
GamePersistence.gameHostChanged(meta.gameId, meta.playerId);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, GameSayData } from 'types';
import { GamePersistence } from '../../persistence';
export function gameSay(data: GameSayData, meta: GameEventMeta): void {
GamePersistence.gameSay(meta.gameId, meta.playerId, data.message);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, GameStateChangedData } from 'types';
import { GamePersistence } from '../../persistence';
export function gameStateChanged(data: GameStateChangedData, meta: GameEventMeta): void {
GamePersistence.gameStateChanged(meta.gameId, data);
}

View file

@ -1,36 +1,62 @@
import { ProtobufEvents } from '../../services/ProtobufService';
import { attachCard } from './attachCard';
import { changeZoneProperties } from './changeZoneProperties';
import { createArrow } from './createArrow';
import { createCounter } from './createCounter';
import { createToken } from './createToken';
import { delCounter } from './delCounter';
import { deleteArrow } from './deleteArrow';
import { destroyCard } from './destroyCard';
import { drawCards } from './drawCards';
import { dumpZone } from './dumpZone';
import { flipCard } from './flipCard';
import { gameClosed } from './gameClosed';
import { gameHostChanged } from './gameHostChanged';
import { gameSay } from './gameSay';
import { gameStateChanged } from './gameStateChanged';
import { joinGame } from './joinGame';
import { kicked } from './kicked';
import { leaveGame } from './leaveGame';
import { moveCard } from './moveCard';
import { playerPropertiesChanged } from './playerPropertiesChanged';
import { revealCards } from './revealCards';
import { reverseTurn } from './reverseTurn';
import { rollDie } from './rollDie';
import { setActivePhase } from './setActivePhase';
import { setActivePlayer } from './setActivePlayer';
import { setCardAttr } from './setCardAttr';
import { setCardCounter } from './setCardCounter';
import { setCounter } from './setCounter';
import { shuffle } from './shuffle';
export const GameEvents: ProtobufEvents = {
'.Event_Join.ext': joinGame,
'.Event_Leave.ext': leaveGame,
'.Event_GameClosed.ext': () => console.log('Event_GameClosed.ext'),
'.Event_GameHostChanged.ext': () => console.log('Event_GameHostChanged.ext'),
'.Event_Kicked.ext': () => console.log('Event_Kicked.ext'),
'.Event_GameStateChanged.ext': () => console.log('Event_GameStateChanged.ext'),
// '.Event_PlayerPropertiesChanged.ext': () => console.log("Event_PlayerProperties.ext"),
'.Event_GameSay.ext': () => console.log('Event_GameSay.ext'),
'.Event_CreateArrow.ext': () => console.log('Event_CreateArrow.ext'),
'.Event_DeleteArrow.ext': () => console.log('Event_DeleteArrow.ext'),
'.Event_CreateCounter.ext': () => console.log('Event_CreateCounter.ext'),
'.Event_SetCounter.ext': () => console.log('Event_SetCounter.ext'),
'.Event_DelCounter.ext': () => console.log('Event_DelCounter.ext'),
'.Event_DrawCards.ext': () => console.log('Event_DrawCards.ext'),
'.Event_RevealCards.ext': () => console.log('Event_RevealCards.ext'),
'.Event_Shuffle.ext': () => console.log('Event_Shuffle.ext'),
'.Event_RollDie.ext': () => console.log('Event_Roll.ext'),
'.Event_MoveCard.ext': () => console.log('Event_MoveCard.ext'),
'.Event_FlipCard.ext': () => console.log('Event_FlipCard.ext'),
'.Event_DestroyCard.ext': () => console.log('Event_DestroyCard.ext'),
'.Event_AttachCard.ext': () => console.log('Event_AttachCard.ext'),
'.Event_CreateToken.ext': () => console.log('Event_CreateToken.ext'),
'.Event_SetCardAttribute.ext': () => console.log('Event_SetCardAttribute.ext'),
'.Event_SetCardCounter.ext': () => console.log('Event_SetCardCounter.ext'),
'.Event_SetActivePlayer.ext': () => console.log('Event_SetActivePlayer.ext'),
'.Event_SetActivePhase.ext': () => console.log('Event_SetActivePhase.ext'),
'.Event_DumpZone.ext': () => console.log('Event_DumpZone.ext'),
'.Event_ChangeZoneProperties.ext': () => console.log('Event_ChangeZoneProperties.ext'),
'.Event_ReverseTurn.ext': () => console.log('Event_ReverseTurn.ext'),
'.Event_GameClosed.ext': gameClosed,
'.Event_GameHostChanged.ext': gameHostChanged,
'.Event_Kicked.ext': kicked,
'.Event_GameStateChanged.ext': gameStateChanged,
'.Event_PlayerPropertiesChanged.ext': playerPropertiesChanged,
'.Event_GameSay.ext': gameSay,
'.Event_CreateArrow.ext': createArrow,
'.Event_DeleteArrow.ext': deleteArrow,
'.Event_CreateCounter.ext': createCounter,
'.Event_SetCounter.ext': setCounter,
'.Event_DelCounter.ext': delCounter,
'.Event_DrawCards.ext': drawCards,
'.Event_RevealCards.ext': revealCards,
'.Event_Shuffle.ext': shuffle,
'.Event_RollDie.ext': rollDie,
'.Event_MoveCard.ext': moveCard,
'.Event_FlipCard.ext': flipCard,
'.Event_DestroyCard.ext': destroyCard,
'.Event_AttachCard.ext': attachCard,
'.Event_CreateToken.ext': createToken,
'.Event_SetCardAttr.ext': setCardAttr,
'.Event_SetCardCounter.ext': setCardCounter,
'.Event_SetActivePlayer.ext': setActivePlayer,
'.Event_SetActivePhase.ext': setActivePhase,
'.Event_DumpZone.ext': dumpZone,
'.Event_ChangeZoneProperties.ext': changeZoneProperties,
'.Event_ReverseTurn.ext': reverseTurn,
};

View file

@ -1,6 +1,6 @@
import { GamePersistence } from '../../persistence';
import { PlayerGamePropertiesData } from '../session/interfaces';
import { GameEventMeta, PlayerProperties } from 'types';
export function joinGame(playerGamePropertiesData: PlayerGamePropertiesData): void {
GamePersistence.joinGame(playerGamePropertiesData);
export function joinGame(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void {
GamePersistence.playerJoined(meta.gameId, data.playerProperties);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function kicked(_data: {}, meta: GameEventMeta): void {
GamePersistence.kicked(meta.gameId);
}

View file

@ -1,7 +1,6 @@
import { LeaveGameReason } from 'types';
import { GameEventMeta } from 'types';
import { GamePersistence } from '../../persistence';
export function leaveGame(reason: LeaveGameReason): void {
GamePersistence.leaveGame(reason);
export function leaveGame(data: { reason: number }, meta: GameEventMeta): void {
GamePersistence.playerLeft(meta.gameId, meta.playerId, data.reason ?? 1);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, MoveCardData } from 'types';
import { GamePersistence } from '../../persistence';
export function moveCard(data: MoveCardData, meta: GameEventMeta): void {
GamePersistence.cardMoved(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, PlayerProperties } from 'types';
import { GamePersistence } from '../../persistence';
export function playerPropertiesChanged(data: { playerProperties: PlayerProperties }, meta: GameEventMeta): void {
GamePersistence.playerPropertiesChanged(meta.gameId, meta.playerId, data.playerProperties);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, RevealCardsData } from 'types';
import { GamePersistence } from '../../persistence';
export function revealCards(data: RevealCardsData, meta: GameEventMeta): void {
GamePersistence.cardsRevealed(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, ReverseTurnData } from 'types';
import { GamePersistence } from '../../persistence';
export function reverseTurn(data: ReverseTurnData, meta: GameEventMeta): void {
GamePersistence.turnReversed(meta.gameId, data.reversed);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, RollDieData } from 'types';
import { GamePersistence } from '../../persistence';
export function rollDie(data: RollDieData, meta: GameEventMeta): void {
GamePersistence.dieRolled(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, SetActivePhaseData } from 'types';
import { GamePersistence } from '../../persistence';
export function setActivePhase(data: SetActivePhaseData, meta: GameEventMeta): void {
GamePersistence.activePhaseSet(meta.gameId, data.phase);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, SetActivePlayerData } from 'types';
import { GamePersistence } from '../../persistence';
export function setActivePlayer(data: SetActivePlayerData, meta: GameEventMeta): void {
GamePersistence.activePlayerSet(meta.gameId, data.activePlayerId);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, SetCardAttrData } from 'types';
import { GamePersistence } from '../../persistence';
export function setCardAttr(data: SetCardAttrData, meta: GameEventMeta): void {
GamePersistence.cardAttrChanged(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, SetCardCounterData } from 'types';
import { GamePersistence } from '../../persistence';
export function setCardCounter(data: SetCardCounterData, meta: GameEventMeta): void {
GamePersistence.cardCounterChanged(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, SetCounterData } from 'types';
import { GamePersistence } from '../../persistence';
export function setCounter(data: SetCounterData, meta: GameEventMeta): void {
GamePersistence.counterSet(meta.gameId, meta.playerId, data);
}

View file

@ -0,0 +1,6 @@
import { GameEventMeta, ShuffleData } from 'types';
import { GamePersistence } from '../../persistence';
export function shuffle(data: ShuffleData, meta: GameEventMeta): void {
GamePersistence.zoneShuffled(meta.gameId, meta.playerId, data);
}

View file

@ -1,18 +1,25 @@
import { GamePersistence } from './GamePersistence';
jest.mock('store', () => ({
GameDispatch: {
playerJoined: jest.fn(),
playerLeft: jest.fn(),
},
}));
import { GameDispatch } from 'store';
beforeEach(() => jest.clearAllMocks());
describe('GamePersistence', () => {
it('joinGame logs to console', () => {
const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
it('playerJoined dispatches via GameDispatch', () => {
const data = { playerId: 1 } as any;
GamePersistence.joinGame(data);
expect(spy).toHaveBeenCalledWith('joinGame', data);
spy.mockRestore();
GamePersistence.playerJoined(5, data);
expect(GameDispatch.playerJoined).toHaveBeenCalledWith(5, data);
});
it('leaveGame logs to console', () => {
const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
GamePersistence.leaveGame(0 as any);
expect(spy).toHaveBeenCalledWith('leaveGame', 0);
spy.mockRestore();
it('playerLeft dispatches via GameDispatch', () => {
GamePersistence.playerLeft(5, 1, 3);
expect(GameDispatch.playerLeft).toHaveBeenCalledWith(5, 1, 3);
});
});

View file

@ -1,12 +1,142 @@
import { PlayerGamePropertiesData } from '../events/session/interfaces';
import { LeaveGameReason } from '../../types';
import { GameDispatch } from 'store';
import {
AttachCardData,
ChangeZonePropertiesData,
CreateArrowData,
CreateCounterData,
CreateTokenData,
DelCounterData,
DeleteArrowData,
DestroyCardData,
DrawCardsData,
DumpZoneData,
FlipCardData,
GameStateChangedData,
MoveCardData,
PlayerProperties,
RevealCardsData,
RollDieData,
SetCardAttrData,
SetCardCounterData,
SetCounterData,
ShuffleData,
} from 'types';
export class GamePersistence {
static joinGame(playerGamePropertiesData: PlayerGamePropertiesData) {
console.log('joinGame', playerGamePropertiesData);
static gameStateChanged(gameId: number, data: GameStateChangedData): void {
GameDispatch.gameStateChanged(gameId, data);
}
static leaveGame(reason: LeaveGameReason) {
console.log('leaveGame', reason);
static playerJoined(gameId: number, playerProperties: PlayerProperties): void {
GameDispatch.playerJoined(gameId, playerProperties);
}
static playerLeft(gameId: number, playerId: number, reason: number): void {
GameDispatch.playerLeft(gameId, playerId, reason);
}
static playerPropertiesChanged(gameId: number, playerId: number, properties: PlayerProperties): void {
GameDispatch.playerPropertiesChanged(gameId, playerId, properties);
}
static gameClosed(gameId: number): void {
GameDispatch.gameClosed(gameId);
}
static gameHostChanged(gameId: number, hostId: number): void {
GameDispatch.gameHostChanged(gameId, hostId);
}
static kicked(gameId: number): void {
GameDispatch.kicked(gameId);
}
static gameSay(gameId: number, playerId: number, message: string): void {
GameDispatch.gameSay(gameId, playerId, message);
}
static cardMoved(gameId: number, playerId: number, data: MoveCardData): void {
GameDispatch.cardMoved(gameId, playerId, data);
}
static cardFlipped(gameId: number, playerId: number, data: FlipCardData): void {
GameDispatch.cardFlipped(gameId, playerId, data);
}
static cardDestroyed(gameId: number, playerId: number, data: DestroyCardData): void {
GameDispatch.cardDestroyed(gameId, playerId, data);
}
static cardAttached(gameId: number, playerId: number, data: AttachCardData): void {
GameDispatch.cardAttached(gameId, playerId, data);
}
static tokenCreated(gameId: number, playerId: number, data: CreateTokenData): void {
GameDispatch.tokenCreated(gameId, playerId, data);
}
static cardAttrChanged(gameId: number, playerId: number, data: SetCardAttrData): void {
GameDispatch.cardAttrChanged(gameId, playerId, data);
}
static cardCounterChanged(gameId: number, playerId: number, data: SetCardCounterData): void {
GameDispatch.cardCounterChanged(gameId, playerId, data);
}
static arrowCreated(gameId: number, playerId: number, data: CreateArrowData): void {
GameDispatch.arrowCreated(gameId, playerId, data);
}
static arrowDeleted(gameId: number, playerId: number, data: DeleteArrowData): void {
GameDispatch.arrowDeleted(gameId, playerId, data);
}
static counterCreated(gameId: number, playerId: number, data: CreateCounterData): void {
GameDispatch.counterCreated(gameId, playerId, data);
}
static counterSet(gameId: number, playerId: number, data: SetCounterData): void {
GameDispatch.counterSet(gameId, playerId, data);
}
static counterDeleted(gameId: number, playerId: number, data: DelCounterData): void {
GameDispatch.counterDeleted(gameId, playerId, data);
}
static cardsDrawn(gameId: number, playerId: number, data: DrawCardsData): void {
GameDispatch.cardsDrawn(gameId, playerId, data);
}
static cardsRevealed(gameId: number, playerId: number, data: RevealCardsData): void {
GameDispatch.cardsRevealed(gameId, playerId, data);
}
static zoneShuffled(gameId: number, playerId: number, data: ShuffleData): void {
GameDispatch.zoneShuffled(gameId, playerId, data);
}
static dieRolled(gameId: number, playerId: number, data: RollDieData): void {
GameDispatch.dieRolled(gameId, playerId, data);
}
static activePlayerSet(gameId: number, activePlayerId: number): void {
GameDispatch.activePlayerSet(gameId, activePlayerId);
}
static activePhaseSet(gameId: number, phase: number): void {
GameDispatch.activePhaseSet(gameId, phase);
}
static turnReversed(gameId: number, reversed: boolean): void {
GameDispatch.turnReversed(gameId, reversed);
}
static zoneDumped(gameId: number, playerId: number, data: DumpZoneData): void {
GameDispatch.zoneDumped(gameId, playerId, data);
}
static zonePropertiesChanged(gameId: number, playerId: number, data: ChangeZonePropertiesData): void {
GameDispatch.zonePropertiesChanged(gameId, playerId, data);
}
}

View file

@ -54,6 +54,10 @@ jest.mock('store', () => ({
replayModifyMatch: jest.fn(),
replayDeleteMatch: jest.fn(),
},
GameDispatch: {
gameJoined: jest.fn(),
playerPropertiesChanged: jest.fn(),
},
}));
jest.mock('websocket/utils', () => ({
@ -68,7 +72,7 @@ jest.mock('../utils/NormalizeService', () => ({
}));
import { SessionPersistence } from './SessionPersistence';
import { ServerDispatch } from 'store';
import { ServerDispatch, GameDispatch } from 'store';
import { sanitizeHtml } from 'websocket/utils';
import NormalizeService from '../utils/NormalizeService';
import { StatusEnum } from 'types';
@ -318,11 +322,9 @@ describe('SessionPersistence', () => {
expect(ServerDispatch.notifyUser).toHaveBeenCalledWith(notif);
});
it('playerPropertiesChanged logs to console', () => {
const spy = jest.spyOn(console, 'log').mockImplementation(() => {});
SessionPersistence.playerPropertiesChanged({} as any);
expect(spy).toHaveBeenCalled();
spy.mockRestore();
it('playerPropertiesChanged dispatches via GameDispatch', () => {
SessionPersistence.playerPropertiesChanged(5, 1, {} as any);
expect(GameDispatch.playerPropertiesChanged).toHaveBeenCalledWith(5, 1, {});
});
it('serverShutdown passes data', () => {

View file

@ -1,5 +1,6 @@
import { ServerDispatch } from 'store';
import { GameDispatch, ServerDispatch } from 'store';
import { DeckList, DeckStorageTreeItem, ReplayMatch, StatusEnum, User, WebSocketConnectOptions } from 'types';
import { GameEntry } from 'store/game/game.interfaces';
import { sanitizeHtml } from 'websocket/utils';
import {
@ -180,15 +181,32 @@ export class SessionPersistence {
}
static gameJoined(gameJoinedData: GameJoinedData): void {
console.log('gameJoined', gameJoinedData);
const { gameInfo, hostId, playerId, spectator, judge } = gameJoinedData;
const gameEntry: GameEntry = {
gameId: gameInfo.gameId,
roomId: gameInfo.roomId,
description: gameInfo.description,
hostId,
localPlayerId: playerId,
spectator,
judge,
started: gameInfo.started,
activePlayerId: -1,
activePhase: -1,
secondsElapsed: 0,
reversed: false,
players: {},
messages: [],
};
GameDispatch.gameJoined(gameInfo.gameId, gameEntry);
}
static notifyUser(notification: NotifyUserData): void {
ServerDispatch.notifyUser(notification);
}
static playerPropertiesChanged(payload: PlayerGamePropertiesData): void {
console.log('playerPropertiesChanged', payload);
static playerPropertiesChanged(gameId: number, playerId: number, payload: PlayerGamePropertiesData): void {
GameDispatch.playerPropertiesChanged(gameId, playerId, payload);
}
static serverShutdown(data: ServerShutdownData): void {

View file

@ -10,6 +10,16 @@ export interface CommandOptions {
}
export class BackendService {
static sendGameCommand(gameId: number, commandName: string, params: any, options: CommandOptions = {}): void {
const command = ProtoController.root[commandName].create(params || {});
const gc = ProtoController.root.GameCommand.create({
[`.${commandName}.ext`]: command,
});
webClient.protobuf.sendGameCommand(gameId, gc, (raw: any) => {
BackendService.handleResponse(commandName, raw, options);
});
}
static sendSessionCommand(commandName: string, params: any, options: CommandOptions): void {
const command = ProtoController.root[commandName].create(params || {});
const sc = ProtoController.root.SessionCommand.create({

View file

@ -2,6 +2,7 @@ import { CommonEvents, GameEvents, RoomEvents, SessionEvents } from '../events';
import { WebClient } from '../WebClient';
import { SessionCommands } from 'websocket';
import { ProtoController } from './ProtoController';
import { GameEventMeta } from 'types';
export interface ProtobufEvents {
[event: string]: Function;
@ -23,6 +24,15 @@ export class ProtobufService {
this.pendingCommands = {};
}
public sendGameCommand(gameId: number, gameCmd: any, callback?: Function) {
const cmd = ProtoController.root.CommandContainer.create({
gameId,
gameCommand: [gameCmd],
});
this.sendCommand(cmd, (raw: any) => callback && callback(raw));
}
public sendRoomCommand(roomId: number, roomCmd: any, callback?: Function) {
const cmd = ProtoController.root.CommandContainer.create({
'roomId': roomId,
@ -88,7 +98,7 @@ export class ProtobufService {
this.processSessionEvent(msg.sessionEvent, msg);
break;
case ProtoController.root.ServerMessage.MessageType.GAME_EVENT_CONTAINER:
this.processGameEvent(msg.gameEvent, msg);
this.processGameEvent(msg.gameEventContainer, msg);
break;
default:
console.log(msg);
@ -121,8 +131,42 @@ export class ProtobufService {
this.processEvent(response, SessionEvents, raw);
}
private processGameEvent(response: any, raw: any): void {
this.processEvent(response, GameEvents, raw);
private processGameEvent(container: any, raw: any): void {
if (!container?.eventList?.length) {
return;
}
const { gameId, context, secondsElapsed, forcedByJudge } = container;
for (const event of container.eventList) {
const meta: GameEventMeta = {
gameId: gameId ?? -1,
playerId: event.playerId ?? -1,
context,
secondsElapsed: secondsElapsed ?? 0,
forcedByJudge: forcedByJudge ?? 0,
};
// Try registered game event handlers first, then common event handlers
let handled = false;
for (const key of Object.keys(GameEvents)) {
const payload = event[key];
if (payload !== undefined && payload !== null) {
(GameEvents[key] as Function)(payload, meta);
handled = true;
break;
}
}
if (!handled) {
for (const key of Object.keys(CommonEvents)) {
const payload = event[key];
if (payload !== undefined && payload !== null) {
(CommonEvents[key] as Function)(payload, meta);
break;
}
}
}
}
}
private processEvent(response: any, events: ProtobufEvents, raw: any) {