Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/gameplay/gameEngine/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class Game extends Room {
missionNum = 0;
pickNum = 0;
roomCreationType: RoomCreationType;
options: string[] = [];

// Game misc variables
winner: Alliance = '';
Expand Down Expand Up @@ -1029,7 +1030,7 @@ class Game extends Room {
}
}

getGameData() {
getGameData(): Record<number, Object> {
if (this.gameStarted == true) {
const data = {};
const playerRoles = this.playersInGame;
Expand Down
102 changes: 68 additions & 34 deletions src/sockets/bot.js → src/sockets/bot.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import axios from 'axios';
import axios, { AxiosResponse, AxiosError, Method } from 'axios';
import Game from '../gameplay/gameEngine/game';
import { Phase } from '../gameplay/gameEngine/phases/types';
import { RoomPlayer } from '../gameplay/gameEngine/types';
import { Role } from '../gameplay/gameEngine/roles/types';

export const enabledBots = [];
interface BotAPI {
name: string;
urlBase: string | undefined;
authorizationKey: string | undefined;
}

interface Capability {
numPlayers: number[];
roles: string[];
cards: string[];
}

export const enabledBots: BotAPI[] = [];
enabledBots.push({
name: 'SimpleBot',
urlBase: undefined,
Expand All @@ -22,7 +37,16 @@ enabledBots.push({
// }

export class SimpleBotSocket {
constructor(username) {
isBotSocket: boolean;
request: {
user: {
username: string;
bot: boolean;
}
}
botAPI: BotAPI;

constructor(username: string) {
this.isBotSocket = true;
this.request = {
user: {
Expand All @@ -38,15 +62,15 @@ export class SimpleBotSocket {
// handleReadyNotReady: Called when the game is about to start.
// if the bot is ready, call callback(true)
// if the bot isn't ready, call callback(false) or callback(false, "<reason>")
handleReadyNotReady(game, callback) {
handleReadyNotReady(_game: Game, callback: Function) {
// Simple bots are always ready.
callback(true);
}

// handleGameStart: Called when the game has commenced.
// if the bot initialized successfully, call callback(true)
// if the bot failed to initialize, call callback(false) or callback(false, "<reason>")
handleGameStart(game, callback) {
handleGameStart(_game: Game, callback: Function) {
// Simple bots are always initialized.
callback(true);
}
Expand All @@ -55,11 +79,11 @@ export class SimpleBotSocket {
// When you have a move available, call callback with the selected button and players
// If you errored, call callback(false)
handleRequestAction(
game,
availableButtons,
availablePlayers,
numOfTargets,
callback,
_game: Game,
availableButtons: string[],
availablePlayers: string[],
numOfTargets: number,
callback: Function,
) {
// Simple bots play randomly
const buttonPressed =
Expand Down Expand Up @@ -92,12 +116,12 @@ export class SimpleBotSocket {
// handleGameOver: Called when the game finishes or closes
// If you want to leave the room, call callback(true)
// Otherwise, call callback(false)
handleGameOver(game, reason, callback) {
handleGameOver(_game: Game, _reason: string, callback: Function) {
callback(true);
}
}

export function makeBotAPIRequest(botAPI, method, endpoint, data, timeout) {
export function makeBotAPIRequest(botAPI: BotAPI, method: Method, endpoint: string, data: any, timeout: number) {
return axios.request({
method,
url: botAPI.urlBase + endpoint,
Expand All @@ -110,16 +134,16 @@ export function makeBotAPIRequest(botAPI, method, endpoint, data, timeout) {
});
}

function checkBotCapabilities(game, capabilities) {
function checkBotCapabilities(game: Game, capabilities: Capability[]) {
// Check if any single capability matches.
return capabilities.some((capability) => {
return capabilities.some((capability: Capability) => {
const numPlayers = game.socketsOfPlayers.length;
if (capability.numPlayers.indexOf(numPlayers) === -1) {
return false;
}

return game.options.every(
(option) =>
(option: Role) =>
[Role.Assassin, Role.Merlin].indexOf(option) !== -1 ||
capability.roles.indexOf(option) !== -1 ||
capability.cards.indexOf(option) !== -1,
Expand All @@ -128,7 +152,17 @@ function checkBotCapabilities(game, capabilities) {
}

export class APIBotSocket {
constructor(username, botAPI) {
botAPI: BotAPI;
isBotSocket: boolean;
request: {
user: {
username: string;
bot: boolean;
}
}
sessionID: string | undefined;

constructor(username: string, botAPI: BotAPI) {
this.isBotSocket = true;
this.request = {
user: {
Expand All @@ -145,10 +179,10 @@ export class APIBotSocket {
// handleReadyNotReady: Called when the game is about to start.
// if the bot is ready, call callback(true)
// if the bot isn't ready, call callback(false) or callback(false, "<reason>")
handleReadyNotReady(game, callback) {
handleReadyNotReady(game: Game, callback: Function) {
// Check if the API supports this game type. If yes, ready up.
makeBotAPIRequest(this.botAPI, 'GET', '/v0/info', {}, 4000)
.then((response) => {
.then((response: AxiosResponse<{ capabilities: any }>) => {
if (response.status !== 200) {
callback(false, 'Bot returned an invalid response.');
return;
Expand All @@ -162,7 +196,7 @@ export class APIBotSocket {
callback(true);
}
})
.catch((error) => {
.catch((error: AxiosError) => {
if (error.response) {
callback(false, 'The bot crashed during request.');
} else {
Expand All @@ -174,18 +208,18 @@ export class APIBotSocket {
// handleGameStart: Called when the game has commenced.
// if the bot initialized successfully, call callback(true)
// if the bot failed to initialize, call callback(false) or callback(false, "<reason>")
handleGameStart(game, callback) {
handleGameStart(game: Game, callback: Function) {
const thisSocket = this;
const playerIndex = game.playersInGame.findIndex(
(player) => player.username == thisSocket.request.user.username,
(player: RoomPlayer) => player.username == thisSocket.request.user.username,
);
// console.log("Player " + thisSocket.request.user.username + " is at index: " + playerIndex); //Don't worry, the above line works perfectly...!
const gameData = game.getGameData()[playerIndex];
const gameData: any = game.getGameData()[playerIndex];

const apiData = {
numPlayers: gameData.playerUsernamesOrderedReversed.length,
roles: gameData.roles.filter(
(role) => role != Role.Assassin && role != Role.Merlin,
(role: string) => role != Role.Assassin && role != Role.Merlin,
), // TODO: Is this needed?
cards: gameData.cards,
teamLeader: gameData.teamLeaderReversed,
Expand All @@ -196,7 +230,7 @@ export class APIBotSocket {
};

makeBotAPIRequest(this.botAPI, 'POST', '/v0/session', apiData, 3000)
.then((response) => {
.then((response: AxiosResponse<{ sessionID: string }>) => {
if (response.status !== 200 || !response.data.sessionID) {
callback(false, 'Bot returned an invalid response.');
return;
Expand All @@ -205,7 +239,7 @@ export class APIBotSocket {
thisSocket.sessionID = response.data.sessionID;
callback(true);
})
.catch((error) => {
.catch((error: AxiosError) => {
if (error.response) {
callback(false, 'The bot crashed during request.');
} else {
Expand All @@ -218,15 +252,15 @@ export class APIBotSocket {
// When you have a move available, call callback with the selected button and players
// If you errored, call callback(false)
handleRequestAction(
game,
availableButtons,
availablePlayers,
numOfTargets,
callback,
game: Game,
availableButtons: string[],
availablePlayers: string[],
numOfTargets: number,
callback: Function,
) {
const thisSocket = this;
const playerIndex = game.playersInGame.findIndex(
(player) => player.username == thisSocket.request.user.username,
(player: RoomPlayer) => player.username == thisSocket.request.user.username,
);
const gameData = game.getGameData()[playerIndex];

Expand All @@ -236,15 +270,15 @@ export class APIBotSocket {
};

makeBotAPIRequest(this.botAPI, 'POST', '/v0/session/act', apiData, 20000)
.then((response) => {
.then((response: AxiosResponse<{ buttonPressed: string, selectedPlayers: string[] }>) => {
if (response.status !== 200) {
callback(false, 'Bot returned an invalid response.');
return;
}

callback(response.data);
})
.catch((error) => {
.catch((error: AxiosError) => {
if (error.response) {
callback(false, 'The bot crashed during request.');
// console.log(error.response);
Expand All @@ -257,7 +291,7 @@ export class APIBotSocket {
// handleGameOver: Called when the game finishes or closes
// If you want to leave the room, call callback(true)
// Otherwise, call callback(false)
handleGameOver(game, reason, callback) {
handleGameOver(game: Game, reason: string, callback: Function) {
const thisSocket = this;
const playerIndex = game.playersInGame.findIndex(
(player) => player.username == thisSocket.request.user.username,
Expand Down