diff --git a/src/common/botwrapper.ts b/src/common/botwrapper.ts index f765a140..802fae51 100644 --- a/src/common/botwrapper.ts +++ b/src/common/botwrapper.ts @@ -1,21 +1,27 @@ -import { State } from 'boardgame.io'; +import { Game, State } from 'boardgame.io'; import { Bot } from 'boardgame.io/ai'; import { BotAction } from 'boardgame.io/dist/types/src/ai/bot'; +import { PlayerIDType } from './types'; // Determine the next move for the bot and which move function to use. type BotStrategy = (state: State, botID: string) => [T_Move | undefined, string]; + +export type SpecificBot = ReturnType> + /// wraps a convenient strategy to a full Boardgame.io Bot class /// @param strategy Must calculate the move to be made or `undefined` if a random move is to be made /// @result a Boardgame.io Bot class -export default function botWrapper(botstrategy: BotStrategy) { +export default function botWrapper(botstrategy: BotStrategy) + { + //this actually results in a somewhat correct type, altough it doesn't implements some of the private methods, for some reason ts doesn't complains about that class _Bot extends Bot { // waits 400 ms for UX - async wait(): Promise { + private async wait(): Promise { await new Promise(resolve => setTimeout(resolve, 400)); } - async play(state: State, playerID: string): Promise<{ action: BotAction; metadata?: any; }> { + async play(state: State, playerID: PlayerIDType): Promise<{ action: BotAction; metadata?: any; }> { await this.wait(); const [move, moveName] = botstrategy(state, playerID); if (move === undefined) { @@ -37,4 +43,21 @@ export default function botWrapper(botstrategy: Bot } return _Bot; } -// TODO: accept more than one move \ No newline at end of file +// TODO: accept more than one move + +//Heresy in the name of ''''''type safety''''' +export type MergedWrappedBots = T extends (SpecificBot|SpecificBot)[] ? SpecificBot[] : never; + +//Most unholiest possible trick in the name of ''''''type safety''''' +export type ChangeReturnType = T extends (...args: infer Args) => any ? (...args: Args) => BotAction[] : never; + +export function makeWrappedBot( + wrappedFactory:SpecificBot, + enumerate: NonNullable['enumerate'], //<- this is in fact inconsistent with itself for matter of fact this is not equal to Bot['enumerate] + seed?: Game['seed']): + InstanceType>{ + return new (wrappedFactory)({ + enumerate: enumerate, + seed: seed, + }) +} \ No newline at end of file diff --git a/src/common/game_for_testing.tsx b/src/common/game_for_testing.tsx index 893009ef..4d3249b7 100644 --- a/src/common/game_for_testing.tsx +++ b/src/common/game_for_testing.tsx @@ -1,19 +1,20 @@ import { Ctx } from "boardgame.io"; import { RandomAPI } from "boardgame.io/dist/types/src/plugins/random/random"; -import { GameStateMixin, GameType, SetupFunction, StartingPositionFunction } from "./types"; +import { BruteForcedPlayerIDExtension, GameStateMixin, GameType, PlayerIDType, SetupFunction, StartingPositionFunction } from "./types"; type G = { data: string }; export function createGameWithMoveWithoutStartingPosition(setup: SetupFunction, - move: ({ G, ctx, playerID, random }: { G: G, ctx: Ctx; playerID: string; random: RandomAPI; }, ...args: any[]) => GameStateMixin & G): GameType { + move: ({ G, ctx, playerID, random }: { G: G, ctx: Ctx, playerID: string, random: RandomAPI, }, ...args: any[]) => GameStateMixin & G + ): GameType { // Wraps move in a function so that it is registered as function (solves `invalid move object` error) const game: GameType = { name: "stub-game", setup, moves: { - move: (...args) => move(...args), + move: (...args) => move(...args), // TODO make the as type here to work, so it can be used with the proper signature }, - possibleMoves: () => [null, "move"], + possibleMoves: () => [], }; return game; } @@ -24,11 +25,11 @@ export function createGameWithMove(setup: SetupFunction, startingPosition: St const game: GameType = { name: "stub-game", setup, - startingPosition: (...args) => startingPosition(...args), + startingPosition: (...args) => startingPosition(...args), // TODO make the as type here to work, so it can be used with the proper signature moves: { move: (...args) => move(...args), }, - possibleMoves: () => [null, "move"], + possibleMoves: () => [], }; return game; } diff --git a/src/common/gamewrapper.ts b/src/common/gamewrapper.ts index 0105b52f..0c0cc42a 100644 --- a/src/common/gamewrapper.ts +++ b/src/common/gamewrapper.ts @@ -1,12 +1,15 @@ -import { Ctx, Game } from 'boardgame.io'; +import { Ctx, Game} from 'boardgame.io'; import { INVALID_MOVE, TurnOrder } from 'boardgame.io/core'; -import { GameStateMixin, GameType } from './types'; +import { GameStateMixin, GameType, PlayerIDType } from './types'; +import { BotAction } from 'boardgame.io/dist/types/src/ai/bot'; -function chooseRole({ G, ctx, playerID }: any, firstPlayer: string):void { // TODO: type + +//HMM, type casting there may be not ideal, let's check it later TODO: check it later +function chooseRole({ G, ctx, playerID }:{G:any,ctx:any,playerID:string}, firstPlayer: PlayerIDType):void { // TODO: solve type G.firstPlayer = firstPlayer; } -function chooseNewGameType({ G, ctx, playerID, random, events }: any, difficulty: string) { +function chooseNewGameType({ G, ctx, playerID, random, events }: any, difficulty: string) { // TODO: solve type if (playerID !== "0") { return INVALID_MOVE; }; @@ -23,7 +26,7 @@ function chooseNewGameType({ G, ctx, playerID, random, events }: any, difficulty } }; -function setStartingPosition({ G, ctx, playerID, random, events }: any, startingPosition: any) { // TODO: type +function setStartingPosition({ G, ctx, playerID, random, events }: any, startingPosition: any) { // TODO: solve type if (playerID !== "1") { return INVALID_MOVE; }; @@ -34,7 +37,7 @@ function setStartingPosition({ G, ctx, playerID, random, events }: any, starting }; }; -function getTime({ G, ctx, playerID, events }:any){ +function getTime({ G, ctx, playerID, events }:any){// TODO: solve types if (playerID !== "0") { return INVALID_MOVE; }; @@ -43,7 +46,7 @@ function getTime({ G, ctx, playerID, events }:any){ -export function gameWrapper(game: GameType): Game { // TODO: solve types +export function gameWrapper(game: GameType): Game { return { setup: () => ({ ...game.setup(), firstPlayer: null, difficulty: null, winner: null, numberOfTries: 0, numberOfLoss: 0, winningStreak: 0, points: 0}), turn: { @@ -88,7 +91,7 @@ export function gameWrapper(game: GameTypeany[] } + // conflict with boardgameio type, where id is string, instead of playerIDType, do manual type cast + ai: { enumerate: game.possibleMoves as (G:T_SpecificGameState,ctx:Ctx,playerID:string)=>BotAction[] } }; }; diff --git a/src/common/types.ts b/src/common/types.ts index 7abc0c02..77217404 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,7 +1,19 @@ -import { Ctx, MoveMap, TurnConfig } from "boardgame.io"; +import { Ctx, Game, MoveMap, TurnConfig } from "boardgame.io"; export type PlayerIDType = "0" | "1"; + +//these are maybe usefull +type ChangeKeyType = Omit & { [K in Key]: NewType }; +export type BruteForcedPropertyExtension = T extends Record + ? ChangeKeyType + : T; + +//or these +export type BruteForcedPlayerIDExtension = T extends {playerID:PlayerIDType} +? Omit&{playerID:string} +:T + export interface GameStateMixin { firstPlayer: null | 0 | 1; winner: PlayerIDType | "draw" | null; @@ -15,10 +27,12 @@ export interface GameStateMixin { export type SetupFunction = () => G; export type StartingPositionFunction = (_: {G: G & GameStateMixin; ctx: Ctx; playerID: PlayerIDType; random: any}) => G; +type PossibleMovesReturnType = ReturnType['enumerate']> + /// GameWrapper's mixin. /// setup() is defined here, as it returns G instead of G & WrapperState interface GameMixin { - possibleMoves: (G: G, ctx: Ctx, playerID: PlayerIDType) => any[]; + possibleMoves: (G: G, ctx: Ctx, playerID: PlayerIDType) => PossibleMovesReturnType; setup: SetupFunction, startingPosition?: StartingPositionFunction; } @@ -53,6 +67,6 @@ export interface Game< export type GameType = WrappableGame & GameMixin; /// Allows typing: change ctx.currentPlayer -> currentPlayer(ctx) -export function currentPlayer(ctx: Ctx): "0" | "1" { - return ctx.currentPlayer as "0" | "1"; +export function currentPlayer(ctx: Ctx): PlayerIDType { + return ctx.currentPlayer as PlayerIDType; } diff --git a/src/games/15oc/game.ts b/src/games/15oc/game.ts index d097a9cc..4b529d4a 100644 --- a/src/games/15oc/game.ts +++ b/src/games/15oc/game.ts @@ -41,13 +41,13 @@ export const MyGame: GameType = { // TOOO: solve type }, possibleMoves: (G, ctx, playerID) => { - let moves = [1]; + let moves: any[] = []; return moves; }, }; -function getWinner(): string { - return ""; +function getWinner(): PlayerIDType|'draw'|null { + return null; } \ No newline at end of file diff --git a/src/games/15od/game.ts b/src/games/15od/game.ts index 747dfd66..1534d891 100644 --- a/src/games/15od/game.ts +++ b/src/games/15od/game.ts @@ -41,7 +41,7 @@ export const MyGame: GameType = { // TOOO: solve type }, possibleMoves: (G, ctx, playerID) => { - let moves = [1]; + let moves: any[] = []; return moves; }, diff --git a/src/games/15oe/game.ts b/src/games/15oe/game.ts index 691df71b..1e89c83f 100644 --- a/src/games/15oe/game.ts +++ b/src/games/15oe/game.ts @@ -41,7 +41,7 @@ export const MyGame: GameType = { // TOOO: solve type }, possibleMoves: (G, ctx, playerID) => { - let moves = [1]; + let moves:any[] = []; return moves; }, diff --git a/src/games/relay/game.ts b/src/games/relay/game.ts index 5fd0330b..9959ec47 100644 --- a/src/games/relay/game.ts +++ b/src/games/relay/game.ts @@ -1,5 +1,6 @@ import { Game } from "boardgame.io"; import { INVALID_MOVE, TurnOrder } from "boardgame.io/core"; +import { BotAction } from "boardgame.io/dist/types/src/ai/bot"; type Answer = { answer: number; @@ -166,7 +167,7 @@ export const GameRelay: Game = { }, ai: { - enumerate: (G, ctx, playerID) => { + enumerate: (G:MyGameState, ctx, playerID):BotAction[] => { return []; } } diff --git a/src/games/relay/strategy.ts b/src/games/relay/strategy.ts index 4cba88d3..a5a528ec 100644 --- a/src/games/relay/strategy.ts +++ b/src/games/relay/strategy.ts @@ -163,7 +163,7 @@ const problems : RelayProblems ={ } export function strategy(category: "C" | "D" | "E"){ - return (state: State, botID: string): [any[] | undefined, string] => { + return (state: State, botID: string): [(Number|String|boolean)[] | undefined, string] => { if (state.G.numberOfTry === 0) { let url = problems[category][state.G.currentProblem].url; if(url === undefined){ diff --git a/src/server.ts b/src/server.ts index b9cfae60..51af61ad 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,7 +5,7 @@ import { argv, env, exit } from 'process'; import { gameWrapper } from './common/gamewrapper'; import { SocketIOButBotMoves } from './socketio_botmoves'; import { Server } from 'boardgame.io/server'; -import botWrapper from './common/botwrapper'; +import botWrapper, { ChangeReturnType as MergedWrappedBots, makeWrappedBot } from './common/botwrapper'; import { strategy as RelayStrategy } from './games/relay/strategy'; import { strategyWrapper as TenCoinsStrategy } from './games/ten-coins/strategy'; import { configureTeamsRouter } from './server/router'; @@ -74,12 +74,12 @@ export const strategyNames = { } const games = [ - { ...GameRelay, name: relayNames.C }, - { ...GameRelay, name: relayNames.D }, - { ...GameRelay, name: relayNames.E }, - { ...gameWrapper(TenCoinsGame), name: strategyNames.C }, - { ...gameWrapper(TenCoinsGame), name: strategyNames.D }, - { ...gameWrapper(TenCoinsGame), name: strategyNames.E }, + { ...GameRelay, name: relayNames.C } as typeof GameRelay & {name:string}, + { ...GameRelay, name: relayNames.D } as typeof GameRelay & {name:string}, + { ...GameRelay, name: relayNames.E } as typeof GameRelay & {name:string}, + { ...gameWrapper(TenCoinsGame), name: strategyNames.C } as ReturnType> & {name:string}, + { ...gameWrapper(TenCoinsGame), name: strategyNames.D } as ReturnType> & {name:string}, + { ...gameWrapper(TenCoinsGame), name: strategyNames.E } as ReturnType> & {name:string}, ]; @@ -111,10 +111,11 @@ if (argv[2] === "import") { const botSetup = Object.fromEntries( games.map((game, idx) => [game.name, - new (bot_factories[idx])({ - enumerate: game.ai?.enumerate, - seed: game.seed, - })] + makeWrappedBot( + (bot_factories as MergedWrappedBots)[idx], + game.ai!.enumerate, + game.seed, + )] )); const server = Server({ diff --git a/src/server/router.ts b/src/server/router.ts index f5ea6e87..2ce6abf5 100644 --- a/src/server/router.ts +++ b/src/server/router.ts @@ -1,5 +1,5 @@ -import type Router from '@koa/router'; import koaBody from 'koa-body'; +import * as Router from '@koa/router'; import type { Game, LobbyAPI, Server, StorageAPI } from 'boardgame.io'; import { TeamsRepository } from './db'; import { createMatch } from 'boardgame.io/internal'; @@ -8,6 +8,7 @@ import { InProgressMatchStatus, TeamModel } from './entities/model'; import { BOT_ID, fetch } from '../socketio_botmoves'; import { getBotCredentials, getGameStartAndEndTime } from '../server'; import { closeMatch, getNewGame, checkStaleMatch, startMatchStatus } from './team_manage'; +import { PlayerIDType } from '../common/types'; /** Joins a player to a match where the bot's side is not connected. * @param db: Database context @@ -21,7 +22,7 @@ const injectPlayer = async (db: StorageAPI.Async | StorageAPI.Sync, matchId: str name, credentials }:{ - playerID: any, //TODO: fix to correct type + playerID: PlayerIDType, name: string, credentials: string, } @@ -41,7 +42,7 @@ const injectPlayer = async (db: StorageAPI.Async | StorageAPI.Sync, matchId: str * This should be in line with boardgame.io/src/server/api.ts * path would be '/games/:name/:id/join'. */ - const injectBot = async (db: StorageAPI.Async | StorageAPI.Sync, matchId: string, bot_id: string) => { + const injectBot = async (db: StorageAPI.Async | StorageAPI.Sync, matchId: string, bot_id: PlayerIDType) => { await injectPlayer(db, matchId, { playerID: bot_id, name: 'Bot', @@ -49,7 +50,11 @@ const injectPlayer = async (db: StorageAPI.Async | StorageAPI.Sync, matchId: str }); } -//TODO: check if export is needed +/** + * Cheks if the status of the global timer + * TODO: check if export is needed & implement usage + * @returns {"WAITING"|"FINISHED"|undefined} - status of global game + */ function checkGlobalTime():"WAITING"|"FINISHED"|undefined{ const now = new Date() const {globalStartAt,globalEndAt} = getGameStartAndEndTime(); @@ -63,6 +68,14 @@ function checkGlobalTime():"WAITING"|"FINISHED"|undefined{ return undefined } +/** + * Creates a game based on context, and given Game. + * This is the interface between the API, and BGio components. + * + * @param {Game, any>} game - Game object + * @param {Server.AppCtx} ctx - Context of the Koa & BGio call + * @returns {LobbyAPI.CreatedMatch} - MatchID for the created game + */ async function createGame( game: Game, any>, ctx: Server.AppCtx @@ -80,13 +93,20 @@ async function createGame( return body; }; +/** + * + * Big factory to set up the Router for the API, anso contains API function implementations. + * + * @param router - Koa Router + * @param teams - List of teams, provided as a TeamsRepository + * @param games - List of possible games for teams + */ export function configureTeamsRouter(router: Router, teams: TeamsRepository, games: Game, any>[]) { /** - * Get data about a specific match. + * Get the log data about a specific match. * - * @param {string} name - The name of the game. * @param {string} id - The ID of the match. - * @return - A match object. + * @returns {LogEntry[]} - A list of log objects. */ router.get('/team/admin/:id/logs', async (ctx) => { //It is already authenticated by the admin mount routing @@ -101,11 +121,10 @@ export function configureTeamsRouter(router: Router, teams: }); /** - * Get data about a specific match. + * Get the state data of a specific match. * - * @param {string} name - The name of the game. * @param {string} id - The ID of the match. - * @return - A match object. + * @returns {State} - A match state object object. */ router.get('/team/admin/:id/state', async (ctx) => { const matchID = ctx.params.id; @@ -119,11 +138,10 @@ export function configureTeamsRouter(router: Router, teams: }); /** - * Get data about a specific match. + * Get metadata about a specific match. * - * @param {string} name - The name of the game. * @param {string} id - The ID of the match. - * @return - A match object. + * @returns {Server.MatchData} - A match object. */ router.get('/team/admin/:id/metadata', async (ctx) => { const matchID = ctx.params.id; @@ -136,19 +154,35 @@ export function configureTeamsRouter(router: Router, teams: ctx.body = metadata; }); - router.get('/team/admin/filter', koaBody(), async (ctx: Server.AppCtx) => { - const filter_string = ctx.request.query['filter']; - let filters; + /** + * Run a user defined filter query on teams + * + * @param {string|string[]} filter - Get parameter to pass the filter + * @returns {TeamModel[]} - List of the selected teams + */ + router.get('/team/admin/filter', koaBody(), async (ctx) => { + const filter_string:string|string[]|undefined = ctx.request.query['filter']; + let filters:string[]; if (filter_string === undefined) { - filters = []; - } else { + filters = [] + } else if (typeof(filter_string) === 'string' ) { filters = filter_string.split(','); } + else{ + filters = filter_string; + } + //TODO: fix the return type and value ctx.body = await teams.fetch(filters); // ctx.body = ['8eae8669-125c-42e5-8b49-89afbac31679', '18c3a69d-c477-4578-8dc1-6e430fbb4e80', '48df4969-a834-4131-ab75-24069a56d2d6']; }) - router.get('/team/join/:token', koaBody(), async (ctx: Server.AppCtx) => { + /** + * Get team ID based on login token + * + * @param {string} token: Login token + * @returns {srting } - TeamId for the team + */ + router.get('/team/join/:token', koaBody(), async (ctx) => { const connect_token: string = ctx.params.token ?? 'no-token'; const team = await teams.getTeam({ joinCode: connect_token }); ctx.body = team?.teamId; @@ -156,8 +190,15 @@ export function configureTeamsRouter(router: Router, teams: ctx.throw(404, "Team not found!") }) - //ROUTING FOR TEAM DATA - router.get(/^\/team\/(?[^-]{8}-[^-]{4}-[^-]{4}-[^-]{4}-[^-]{12}$)/, koaBody(), async (ctx: Server.AppCtx) => { + /** + * ROUTING FOR TEAM DATA is handled here. If wrong team then returns 400 + * This is the main middleware to catch wrong team id-s + * + * @param {string} GUID - TeamId + * @returns {TeamModel} - raw TeamModell if called directly + * + */ + router.get(/^\/team\/(?[^-]{8}-[^-]{4}-[^-]{4}-[^-]{4}-[^-]{12}$)/, koaBody(), async (ctx) => { const GUID = ctx.params.GUID ?? ctx.throw(400) console.log(GUID); const team = await teams.getTeam({ teamId: GUID }) ?? ctx.throw(404, `Team with {teamId:${GUID}} not found.`) @@ -170,8 +211,12 @@ export function configureTeamsRouter(router: Router, teams: }); - - router.get('/team/:GUID/relay/play', koaBody(), async (ctx: Server.AppCtx) => { + /** + * Let a team strat a RELAY match. + * + * @param {string} GUID - TeamId + */ + router.get('/team/:GUID/relay/play', koaBody(), async (ctx) => { const GUID = ctx.params.GUID; //check if in progress, it is not allowed to play //check if it can be started, throw error if not @@ -196,7 +241,12 @@ export function configureTeamsRouter(router: Router, teams: ctx.body = body; }) - router.get('/team/:GUID/strategy/play', koaBody(), async (ctx: Server.AppCtx) => { + /** + * Let a team strat a STRATEGY match. + * + * @param {string} GUID - TeamId + */ + router.get('/team/:GUID/strategy/play', koaBody(), async (ctx) => { const GUID = ctx.params.GUID; //check if in progress, it is not allowed to play //check if it can be started, throw error if not @@ -215,12 +265,17 @@ export function configureTeamsRouter(router: Router, teams: ctx.body = body; }) - router.get('/team/:GUID/gohome', koaBody(), async (ctx: Server.AppCtx) => { + /** + * Let a team set their PageState to HOME + * + * @param {string} GUID - TeamId + */ + router.get('/team/:GUID/gohome', koaBody(), async (ctx) => { const GUID = ctx.params.GUID; //check if in progress, it is not allowed to play //check if it can be started, throw error if not const team: TeamModel = await teams.getTeam({ teamId: GUID }) ?? ctx.throw(404, `Team with {id:${GUID}} not found.`) - if (team.relayMatch.state == 'IN PROGRESS' || team.strategyMatch.state == 'IN PROGRESS') + if (team.relayMatch.state === 'IN PROGRESS' || team.strategyMatch.state === 'IN PROGRESS') ctx.throw(403, "Not allowed, match in progress.") //update team state to go home @@ -230,15 +285,33 @@ export function configureTeamsRouter(router: Router, teams: ctx.body = team; }) - router.get('/team/:GUID/relay/result', koaBody(), async (ctx: Server.AppCtx) => { + /** + * Let a team get their relay results + * TODO: implement + * + * @param {string} GUID - TeamId + */ + router.get('/team/:GUID/relay/result', koaBody(), async (ctx) => { ctx.throw(501, "Not implemented yet."); }) - router.get('/team/:GUID/strategy/result', koaBody(), async (ctx: Server.AppCtx) => { + /** + * Let a team get their strategy results + * TODO: implement + * + * @param {string} GUID - TeamId + */ + router.get('/team/:GUID/strategy/result', koaBody(), async (ctx) => { ctx.throw(501, "Not implemented yet."); }) - /** This should be in line with boardgame.io/src/server/api.ts */ + /** + * Create a new BGio game + * + * @param {string} nameid - game ID to create + * + * This should be in line with boardgame.io/src/server/api.ts + */ router.post('/games/:nameid/create', async (ctx, next) => { await next(); //Figured out where match id is stored diff --git a/src/socketio_botmoves.ts b/src/socketio_botmoves.ts index b6dd8866..3786c943 100644 --- a/src/socketio_botmoves.ts +++ b/src/socketio_botmoves.ts @@ -1,11 +1,12 @@ // Demultiplexes to real transport or bots - -import type { Game, State, StorageAPI } from "boardgame.io"; +import type IOTypes from 'socket.io'; +import type { Game, Server, State, StorageAPI } from "boardgame.io"; import { getFilterPlayerView } from "boardgame.io/internal"; import { Master } from "boardgame.io/master"; import { SocketIO } from "boardgame.io/server"; import { currentPlayer } from "./common/types"; import { getBotCredentials } from "./server"; +import { CorsOptionsDelegate } from "cors"; /** Copied from boardgame.io/dist/src/client/transport/local.ts */ function GetBotPlayer(state: State, bots: Record) { @@ -54,7 +55,7 @@ const TransportAPI = ( filterPlayerView: any, pubSub: any ): any => { - const send : (arg1: any, ...arg2: any) => void = ({ playerID, ...data }) => { + const send: (arg1: any, ...arg2: any) => void = ({ playerID, ...data }) => { emit(socket, filterPlayerView(playerID, data)); }; @@ -68,8 +69,8 @@ const TransportAPI = ( /** Copied from boardgame.io/dist/src/master/master.ts */ export async function fetch(db: StorageAPI.Async | StorageAPI.Sync, matchID: string, partial: Partial<{ state: boolean, metadata: boolean, logs: boolean, initialState: boolean }>) { return isSynchronous(db) - ? db.fetch(matchID, partial) - : await db.fetch(matchID, partial); + ? db.fetch(matchID, partial) + : await db.fetch(matchID, partial); } /// Bot's playerID is '1', because the gameWrapper uses player '0' for the human player. @@ -85,21 +86,29 @@ export const BOT_ID = '1'; export class SocketIOButBotMoves extends SocketIO { bots: Record; onFinishedMatch: (matchID: string) => void; - constructor(anything: any, bots: Record, onFinishedMatch: (matchID: string)=>void = ()=>{}) { + constructor(anything: any, bots: Record, onFinishedMatch: (matchID: string) => void = () => { }) { super(anything); this.bots = bots; this.onFinishedMatch = onFinishedMatch; } init( - app: any, + app: Server.App & { _io?: IOTypes.Server; }, games: Game[], - origins: any - ) { + origins?: Exclude['origin'] + ): void { super.init(app, games, origins); for (const game of games) { - const nsp = app._io.of(game.name); - const bot = this.bots[game.name!]; + if (game.name === undefined) { + console.log(`There was a game with no name. This is the game object:${JSON.stringify(game)}.\n We skipped the gameobject, you should fix this!".`) + continue + } + const nsp = app._io?.of(game.name); + const bot = this.bots[game.name]; + if (nsp === undefined) { + console.log(`The network interface of game ${game.name} is undefined, and thus socket bot move initialisation was skipped. You should fix this!`) + continue + } /** This should be in sync with how socket data is communicated. * See boardgame.io/dist/src/server/transport/socketio.ts @@ -110,7 +119,7 @@ export class SocketIOButBotMoves extends SocketIO { // But we are on the same API that reacts to it // Basically we assume that a socket.on('update', ...) // already updated the gamestate, making StateID and PlayerID stale - const [_, staleStateID, matchID, stalePlayerID] : any[] = args; + const [_, staleStateID, matchID, stalePlayerID]: any[] = args; const matchQueue = this.getMatchQueue(matchID); await matchQueue.add(async () => { // These happen after the player stepped. @@ -124,21 +133,21 @@ export class SocketIOButBotMoves extends SocketIO { // Do not react to bot's turn return; } - const {state} = await fetch(app.context.db, matchID, {state: true}); + const { state } = await fetch(app.context.db, matchID, { state: true }); if (currentPlayer(state.ctx) !== BOT_ID) { // Not a real action, possibly a failed move. return; } let botAction = undefined; - if (state.ctx.phase === 'play' || state.ctx.phase === 'startNewGame'){ + if (state.ctx.phase === 'play' || state.ctx.phase === 'startNewGame') { botAction = await bot.play( state, - GetBotPlayer(state, {[BOT_ID]: bot}) as any + GetBotPlayer(state, { [BOT_ID]: bot }) as any ); } else { return; } - + const master = new Master( game, app.context.db, @@ -147,11 +156,11 @@ export class SocketIOButBotMoves extends SocketIO { ); // TODO: is staleStateID+1 always valid? - let nextStateID = staleStateID+1; - await master.onUpdate({type: 'MAKE_MOVE', payload: {...botAction.action.payload, credentials: getBotCredentials()}}, nextStateID, matchID, BOT_ID); + let nextStateID = staleStateID + 1; + await master.onUpdate({ type: 'MAKE_MOVE', payload: { ...botAction.action.payload, credentials: getBotCredentials() } }, nextStateID, matchID, BOT_ID); }); await matchQueue.add(async () => { - const {state} = await fetch(app.context.db, matchID, {state: true}); + const { state } = await fetch(app.context.db, matchID, { state: true }); if (state.ctx.gameover) { this.onFinishedMatch(matchID); } diff --git a/tsconfig.json b/tsconfig.json index 3ba34dfe..932100ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,8 @@ { "ts-node": { "compilerOptions": { - "module": "CommonJS" + "module": "CommonJS", + "noErrorTruncation": true } }, "compilerOptions": {