@corsaircoalition/sergeant-socket
Advanced tools
Comparing version 2.0.0 to 2.1.0
804
out/app.js
@@ -10,346 +10,506 @@ import io from 'socket.io-client'; | ||
export class App { | ||
botId; | ||
gameConfig; | ||
socket; | ||
keyspace; | ||
gamePhase = "initializing"; | ||
gameType; | ||
gameState; | ||
replay_id = ""; | ||
queueNumPlayers = 0; | ||
forceStartSet = false; | ||
customOptionsSet = false; | ||
deconflicted = false; | ||
moveCount = 0; | ||
constructor(gameConfig, redisConfig) { | ||
this.botId = gameConfig.BOT_ID_PREFIX + '-' + hashUserId(gameConfig.userId); | ||
this.gameConfig = gameConfig; | ||
this.initializeSocketConnection(); | ||
this.initializeRedisConnection(redisConfig).then(() => { | ||
this.deconflict(); | ||
Object.defineProperty(this, "botId", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
} | ||
initializeSocketConnection = () => { | ||
this.socket = io(this.gameConfig.GAME_SERVER_URL, { | ||
rejectUnauthorized: false, | ||
Object.defineProperty(this, "gameConfig", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
this.socket.on('connect', this.handleConnect); | ||
this.socket.on("error", (error) => Log.stderr(`[socket.io] ${error}`)); | ||
this.socket.on("connect_error", (error) => Log.stderr(`[socket.io] ${error}`)); | ||
this.socket.on('error_set_username', this.handleErrorSetUsername); | ||
this.socket.on('queue_update', this.handleQueueUpdate); | ||
this.socket.on('game_start', this.handleGameStart); | ||
this.socket.on('game_won', this.handleGameWon); | ||
this.socket.on('game_lost', this.handleGameLost); | ||
this.socket.on('game_update', this.handleGameUpdate); | ||
this.socket.on('disconnect', this.handleDisconnect); | ||
}; | ||
initializeRedisConnection = async (redisConfig) => { | ||
Redis.initilize(redisConfig); | ||
Redis.subscribe(this.botId, "action", this.handleAction); | ||
await Redis.subscribe(this.botId, "command", this.handleCommand).then((channel) => { | ||
Log.stdout('[Redis] subscribed: ' + channel); | ||
Object.defineProperty(this, "socket", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
}; | ||
handleCommand = (command) => { | ||
Log.stdout(`[command] ${JSON.stringify(command)}`); | ||
if (command.join) { | ||
this.joinGame(command.join); | ||
return; | ||
} | ||
if (command.leave) { | ||
if (this.gamePhase === "playing" || this.gamePhase === "joined_lobby") { | ||
this.leaveGame(); | ||
Object.defineProperty(this, "keyspace", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "gamePhase", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: "initializing" | ||
}); | ||
Object.defineProperty(this, "gameType", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "gameState", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "replay_id", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: "" | ||
}); | ||
Object.defineProperty(this, "queueNumPlayers", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: 0 | ||
}); | ||
Object.defineProperty(this, "forceStartSet", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: false | ||
}); | ||
Object.defineProperty(this, "customOptionsSet", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: false | ||
}); | ||
Object.defineProperty(this, "deconflicted", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: false | ||
}); | ||
Object.defineProperty(this, "moveCount", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: 0 | ||
}); | ||
Object.defineProperty(this, "initializeSocketConnection", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
this.socket = io(this.gameConfig.GAME_SERVER_URL, { | ||
rejectUnauthorized: false, | ||
}); | ||
this.socket.on('connect', this.handleConnect); | ||
this.socket.on("error", (error) => Log.stderr(`[socket.io] ${error}`)); | ||
this.socket.on("connect_error", (error) => Log.stderr(`[socket.io] ${error}`)); | ||
this.socket.on('error_set_username', this.handleErrorSetUsername); | ||
this.socket.on('queue_update', this.handleQueueUpdate); | ||
this.socket.on('game_start', this.handleGameStart); | ||
this.socket.on('game_won', this.handleGameWon); | ||
this.socket.on('game_lost', this.handleGameLost); | ||
this.socket.on('game_update', this.handleGameUpdate); | ||
this.socket.on('disconnect', this.handleDisconnect); | ||
} | ||
else { | ||
Log.stderr(`[leave] not in a game`); | ||
}); | ||
Object.defineProperty(this, "initializeRedisConnection", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: async (redisConfig) => { | ||
Redis.initilize(redisConfig); | ||
Redis.subscribe(this.botId, "action", this.handleAction); | ||
await Redis.subscribe(this.botId, "command", this.handleCommand).then((channel) => { | ||
Log.stdout('[Redis] subscribed: ' + channel); | ||
}); | ||
} | ||
return; | ||
} | ||
if (command.options) { | ||
if (!command.options.customGameSpeed) | ||
return; | ||
this.gameConfig.customGameSpeed = command.options.customGameSpeed; | ||
if (this.gamePhase === "joined_lobby") { | ||
this.customOptionsSet = false; | ||
later(100).then(() => { | ||
this.setCustomOptions(); | ||
}); | ||
Object.defineProperty(this, "handleCommand", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (command) => { | ||
Log.stdout(`[command] ${JSON.stringify(command)}`); | ||
if (command.join) { | ||
this.joinGame(command.join); | ||
return; | ||
} | ||
if (command.leave) { | ||
if (this.gamePhase === "playing" || this.gamePhase === "joined_lobby") { | ||
this.leaveGame(); | ||
} | ||
else { | ||
Log.stderr(`[leave] not in a game`); | ||
} | ||
return; | ||
} | ||
if (command.options) { | ||
if (!command.options.customGameSpeed) | ||
return; | ||
this.gameConfig.customGameSpeed = command.options.customGameSpeed; | ||
if (this.gamePhase === "joined_lobby") { | ||
this.customOptionsSet = false; | ||
later(100).then(() => { | ||
this.setCustomOptions(); | ||
}); | ||
} | ||
else { | ||
Log.stderr(`[options] not in lobby`); | ||
} | ||
return; | ||
} | ||
if (command.forceStart) { | ||
this.forceStartSet = false; | ||
setTimeout(this.setForceStart, 200); | ||
return; | ||
} | ||
if (command.status) { | ||
Redis.publish(this.botId, "state", this.getCurrentState()); | ||
return; | ||
} | ||
} | ||
}); | ||
Object.defineProperty(this, "getCurrentState", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
if (!this.socket.connected) { | ||
return { disconnected: 'unknown' }; | ||
} | ||
switch (this.gamePhase) { | ||
case "initializing": | ||
return { disconnected: 'initializing' }; | ||
case "connected": | ||
return { connected: this.gameConfig.username }; | ||
case "joined_lobby": | ||
return { | ||
joined: { | ||
gameType: this.gameType, | ||
gameId: this.gameConfig.customGameId | ||
} | ||
}; | ||
case "playing": | ||
return { playing: true }; | ||
} | ||
} | ||
}); | ||
Object.defineProperty(this, "handleAction", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (data) => { | ||
if (this.gamePhase !== "playing") { | ||
Log.stderr(`[action] not in game`); | ||
return; | ||
} | ||
if (data.interrupt) { | ||
this.socket.emit('clear_moves'); | ||
} | ||
Log.debug("[action]", JSON.stringify(data)); | ||
for (let i = 0; i < data.actions.length; i++) { | ||
const action = data.actions[i]; | ||
this.socket.emit('attack', action.start, action.end, action.is50); | ||
this.moveCount++; | ||
} | ||
} | ||
}); | ||
Object.defineProperty(this, "handleConnect", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
Log.stdout(`[connected] ${this.gameConfig.username}`); | ||
Log.stdout(`READY TO PLAY`); | ||
this.gamePhase = "connected"; | ||
Redis.publish(this.botId, "state", { connected: this.gameConfig.username }); | ||
if (this.gameConfig.setUsername) { | ||
this.socket.emit('set_username', this.gameConfig.userId, this.gameConfig.username); | ||
Log.debug(`sent: set_username, ${this.gameConfig.userId}, ${this.gameConfig.username}`); | ||
} | ||
} | ||
}); | ||
Object.defineProperty(this, "handleDisconnect", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (reason) => { | ||
this.gamePhase = "initializing"; | ||
Redis.publish(this.botId, "state", { disconnected: reason }); | ||
switch (reason) { | ||
case 'io server disconnect': | ||
Log.stderr("disconnected: " + reason); | ||
process.exit(3); | ||
case 'io client disconnect': | ||
process.exit(0); | ||
default: | ||
Log.stderr("disconnected: " + reason); | ||
} | ||
} | ||
}); | ||
Object.defineProperty(this, "handleErrorSetUsername", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (message) => { | ||
if (message === '') { | ||
Log.stdout(`[set_username] username set to ${this.gameConfig.username}`); | ||
return; | ||
} | ||
Log.stdout(`[error_set_username] ${message}`); | ||
} | ||
}); | ||
Object.defineProperty(this, "handleGameStart", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: async (data) => { | ||
this.replay_id = data.replay_id; | ||
this.keyspace = `${this.botId}-${this.replay_id}`; | ||
this.moveCount = 0; | ||
Log.stdout(`[game_start] replay: ${this.replay_id}, users: ${data.usernames}`); | ||
Redis.publish(this.botId, "state", { game_start: data }); | ||
this.gameState = new GameState(data); | ||
Log.debug(`[game_start] setKeys: ${this.keyspace}`); | ||
Redis.setKeys(this.keyspace, data); | ||
Log.debug(`[game_start] listPush: ${this.botId}-${"replays"} ${this.replay_id}`); | ||
Redis.listPush(this.botId, "replays", this.replay_id); | ||
for (let i = 0; i < this.gameConfig.warCry.length; i++) { | ||
later(random(i * 3000, (i + 1) * 3000)).then(() => { | ||
this.socket.emit('chat_message', data.chat_room, this.gameConfig.warCry[i]); | ||
Log.debug(`sent: [chat_message] ${this.gameConfig.warCry[i]}`); | ||
}); | ||
} | ||
this.gamePhase = "playing"; | ||
} | ||
}); | ||
Object.defineProperty(this, "handleGameUpdate", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: async (data) => { | ||
if (data.turn > this.gameConfig.MAX_TURNS) { | ||
Log.stdout(`[game_update] ${this.replay_id}, turn: ${data.turn}, max turns reached`); | ||
this.leaveGame(); | ||
return null; | ||
} | ||
Redis.publish(this.botId, "gameUpdate", data); | ||
this.gameState.update(data); | ||
if (data.turn === 1) { | ||
Log.debug(`[game_update] setKeys: ${this.keyspace}`); | ||
await Redis.setKeys(this.keyspace, { | ||
["width"]: this.gameState.width, | ||
["height"]: this.gameState.height, | ||
["size"]: this.gameState.size, | ||
["ownGeneral"]: this.gameState.ownGeneral, | ||
}); | ||
} | ||
Log.debug(`[game_update] setKeys: ${this.keyspace}`); | ||
await Redis.setKeys(this.keyspace, { | ||
["turn"]: this.gameState.turn, | ||
["cities"]: this.gameState.cities, | ||
["discoveredTiles"]: this.gameState.discoveredTiles, | ||
["armies"]: this.gameState.armies, | ||
["terrain"]: this.gameState.terrain, | ||
["enemyGeneral"]: this.gameState.enemyGeneral, | ||
["ownTiles"]: Array.from(this.gameState.ownTiles.entries()), | ||
["enemyTiles"]: Array.from(this.gameState.enemyTiles.entries()), | ||
}); | ||
let maxArmyOnTile = 0; | ||
for (let [, value] of this.gameState.ownTiles) { | ||
if (value > maxArmyOnTile) { | ||
maxArmyOnTile = value; | ||
} | ||
} | ||
Log.debug(`[game_update] listPush: ${this.keyspace}-${"scores"} ${data.scores}`); | ||
Log.debug(`[game_update] listPush: ${this.keyspace}-${"maxArmyOnTile"} ${maxArmyOnTile}`); | ||
Log.debug(`[game_update] listPush: ${this.keyspace}-${"moveCount"} ${this.moveCount}`); | ||
Redis.listPush(this.keyspace, "scores", data.scores); | ||
Redis.listPush(this.keyspace, "maxArmyOnTile", maxArmyOnTile); | ||
Redis.listPush(this.keyspace, "moveCount", this.moveCount); | ||
return Redis.publish(this.botId, "turn", { | ||
turn: data.turn, | ||
replay_id: this.replay_id | ||
}); | ||
} | ||
else { | ||
Log.stderr(`[options] not in lobby`); | ||
}); | ||
Object.defineProperty(this, "handleGameLost", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (data) => { | ||
Log.stdout(`[game_lost] ${this.replay_id}, killer: ${this.gameState.usernames[data.killer]}`); | ||
Redis.publish(this.botId, "state", { | ||
game_lost: { | ||
replay_id: this.replay_id, | ||
killer: data.killer, | ||
killer_name: this.gameState.usernames[data.killer] | ||
} | ||
}); | ||
this.leaveGame(); | ||
} | ||
return; | ||
} | ||
if (command.forceStart) { | ||
this.forceStartSet = false; | ||
setTimeout(this.setForceStart, 200); | ||
return; | ||
} | ||
if (command.status) { | ||
Redis.publish(this.botId, "state", this.getCurrentState()); | ||
return; | ||
} | ||
}; | ||
getCurrentState = () => { | ||
if (!this.socket.connected) { | ||
return { disconnected: 'unknown' }; | ||
} | ||
switch (this.gamePhase) { | ||
case "initializing": | ||
return { disconnected: 'initializing' }; | ||
case "connected": | ||
return { connected: this.gameConfig.username }; | ||
case "joined_lobby": | ||
return { | ||
joined: { | ||
gameType: this.gameType, | ||
gameId: this.gameConfig.customGameId | ||
}); | ||
Object.defineProperty(this, "handleGameWon", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
Log.stdout(`[game_won] ${this.replay_id}`); | ||
Redis.publish(this.botId, "state", { | ||
game_won: { | ||
replay_id: this.replay_id | ||
} | ||
}; | ||
case "playing": | ||
return { playing: true }; | ||
} | ||
}; | ||
handleAction = (data) => { | ||
if (this.gamePhase !== "playing") { | ||
Log.stderr(`[action] not in game`); | ||
return; | ||
} | ||
if (data.interrupt) { | ||
this.socket.emit('clear_moves'); | ||
} | ||
Log.debug("[action]", JSON.stringify(data)); | ||
for (let i = 0; i < data.actions.length; i++) { | ||
const action = data.actions[i]; | ||
this.socket.emit('attack', action.start, action.end, action.is50); | ||
this.moveCount++; | ||
} | ||
}; | ||
handleConnect = () => { | ||
Log.stdout(`[connected] ${this.gameConfig.username}`); | ||
Log.stdout(`READY TO PLAY`); | ||
this.gamePhase = "connected"; | ||
Redis.publish(this.botId, "state", { connected: this.gameConfig.username }); | ||
if (this.gameConfig.setUsername) { | ||
this.socket.emit('set_username', this.gameConfig.userId, this.gameConfig.username); | ||
Log.debug(`sent: set_username, ${this.gameConfig.userId}, ${this.gameConfig.username}`); | ||
} | ||
}; | ||
handleDisconnect = (reason) => { | ||
this.gamePhase = "initializing"; | ||
Redis.publish(this.botId, "state", { disconnected: reason }); | ||
switch (reason) { | ||
case 'io server disconnect': | ||
Log.stderr("disconnected: " + reason); | ||
process.exit(3); | ||
case 'io client disconnect': | ||
process.exit(0); | ||
default: | ||
Log.stderr("disconnected: " + reason); | ||
} | ||
}; | ||
handleErrorSetUsername = (message) => { | ||
if (message === '') { | ||
Log.stdout(`[set_username] username set to ${this.gameConfig.username}`); | ||
return; | ||
} | ||
Log.stdout(`[error_set_username] ${message}`); | ||
}; | ||
handleGameStart = async (data) => { | ||
this.replay_id = data.replay_id; | ||
this.keyspace = `${this.botId}-${this.replay_id}`; | ||
this.moveCount = 0; | ||
Log.stdout(`[game_start] replay: ${this.replay_id}, users: ${data.usernames}`); | ||
Redis.publish(this.botId, "state", { game_start: data }); | ||
this.gameState = new GameState(data); | ||
Log.debug(`[game_start] setKeys: ${this.keyspace}`); | ||
Redis.setKeys(this.keyspace, data); | ||
Log.debug(`[game_start] listPush: ${this.botId}-${"replays"} ${this.replay_id}`); | ||
Redis.listPush(this.botId, "replays", this.replay_id); | ||
for (let i = 0; i < this.gameConfig.warCry.length; i++) { | ||
later(random(i * 3000, (i + 1) * 3000)).then(() => { | ||
this.socket.emit('chat_message', data.chat_room, this.gameConfig.warCry[i]); | ||
Log.debug(`sent: [chat_message] ${this.gameConfig.warCry[i]}`); | ||
}); | ||
} | ||
this.gamePhase = "playing"; | ||
}; | ||
handleGameUpdate = async (data) => { | ||
if (data.turn > this.gameConfig.MAX_TURNS) { | ||
Log.stdout(`[game_update] ${this.replay_id}, turn: ${data.turn}, max turns reached`); | ||
this.leaveGame(); | ||
return null; | ||
} | ||
Redis.publish(this.botId, "gameUpdate", data); | ||
this.gameState.update(data); | ||
if (data.turn === 1) { | ||
Log.debug(`[game_update] setKeys: ${this.keyspace}`); | ||
await Redis.setKeys(this.keyspace, { | ||
["width"]: this.gameState.width, | ||
["height"]: this.gameState.height, | ||
["size"]: this.gameState.size, | ||
["ownGeneral"]: this.gameState.ownGeneral, | ||
}); | ||
} | ||
Log.debug(`[game_update] setKeys: ${this.keyspace}`); | ||
await Redis.setKeys(this.keyspace, { | ||
["turn"]: this.gameState.turn, | ||
["cities"]: this.gameState.cities, | ||
["discoveredTiles"]: this.gameState.discoveredTiles, | ||
["armies"]: this.gameState.armies, | ||
["terrain"]: this.gameState.terrain, | ||
["enemyGeneral"]: this.gameState.enemyGeneral, | ||
["ownTiles"]: Array.from(this.gameState.ownTiles.entries()), | ||
["enemyTiles"]: Array.from(this.gameState.enemyTiles.entries()), | ||
}); | ||
this.leaveGame(); | ||
} | ||
}); | ||
let maxArmyOnTile = 0; | ||
for (let [, value] of this.gameState.ownTiles) { | ||
if (value > maxArmyOnTile) { | ||
maxArmyOnTile = value; | ||
Object.defineProperty(this, "handleQueueUpdate", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (data) => { | ||
if (!data.isForcing) { | ||
this.forceStartSet = false; | ||
setTimeout(this.setForceStart, 1000); | ||
} | ||
if (this.gameType === "custom" | ||
&& data.usernames[0] === this.gameConfig.username | ||
&& data.numPlayers != this.queueNumPlayers | ||
&& data.options.game_speed != this.gameConfig.customGameSpeed) { | ||
this.customOptionsSet = false; | ||
later(100).then(() => { | ||
this.setCustomOptions(); | ||
}); | ||
} | ||
this.queueNumPlayers = data.numPlayers; | ||
} | ||
} | ||
Log.debug(`[game_update] listPush: ${this.keyspace}-${"scores"} ${data.scores}`); | ||
Log.debug(`[game_update] listPush: ${this.keyspace}-${"maxArmyOnTile"} ${maxArmyOnTile}`); | ||
Log.debug(`[game_update] listPush: ${this.keyspace}-${"moveCount"} ${this.moveCount}`); | ||
Redis.listPush(this.keyspace, "scores", data.scores); | ||
Redis.listPush(this.keyspace, "maxArmyOnTile", maxArmyOnTile); | ||
Redis.listPush(this.keyspace, "moveCount", this.moveCount); | ||
return Redis.publish(this.botId, "turn", { | ||
turn: data.turn, | ||
replay_id: this.replay_id | ||
}); | ||
}; | ||
handleGameLost = (data) => { | ||
Log.stdout(`[game_lost] ${this.replay_id}, killer: ${this.gameState.usernames[data.killer]}`); | ||
Redis.publish(this.botId, "state", { | ||
game_lost: { | ||
replay_id: this.replay_id, | ||
killer: data.killer, | ||
killer_name: this.gameState.usernames[data.killer] | ||
Object.defineProperty(this, "joinGame", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: (data) => { | ||
this.gameType = data.gameType; | ||
switch (data.gameType) { | ||
case "ffa": | ||
this.socket.emit('play', this.gameConfig.userId); | ||
Log.stdout('[joined] FFA'); | ||
break; | ||
case "1v1": | ||
this.socket.emit('join_1v1', this.gameConfig.userId); | ||
Log.stdout('[joined] 1v1'); | ||
break; | ||
case "custom": | ||
if (data.gameId) { | ||
this.gameConfig.customGameId = data.gameId; | ||
} | ||
this.socket.emit('join_private', data.gameId, this.gameConfig.userId, process.env['AUTH_TOKEN']); | ||
setTimeout(this.setCustomOptions, 100); | ||
setTimeout(this.setForceStart, 2000); | ||
Log.stdout(`[joined] custom: ${this.gameConfig.customGameId}`); | ||
break; | ||
default: | ||
Log.stderr(`[join] invalid gameType: ${data.gameType}`); | ||
return; | ||
} | ||
Redis.publish(this.botId, "state", { joined: data }); | ||
this.gamePhase = "joined_lobby"; | ||
} | ||
}); | ||
this.leaveGame(); | ||
}; | ||
handleGameWon = () => { | ||
Log.stdout(`[game_won] ${this.replay_id}`); | ||
Redis.publish(this.botId, "state", { | ||
game_won: { | ||
replay_id: this.replay_id | ||
Object.defineProperty(this, "leaveGame", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
if (this.gamePhase == "joined_lobby") { | ||
this.socket.emit('cancel'); | ||
Log.debug('sent: cancel'); | ||
} | ||
else if (this.gamePhase == "playing") { | ||
this.socket.emit('leave_game'); | ||
Log.debug('sent: leave_game'); | ||
} | ||
else { | ||
Log.stderr(`[leaveGame] Invalid Request, Current State: ${this.gamePhase}`); | ||
return; | ||
} | ||
Redis.publish(this.botId, "state", { left: true }); | ||
this.gamePhase = "connected"; | ||
this.forceStartSet = false; | ||
this.customOptionsSet = false; | ||
} | ||
}); | ||
this.leaveGame(); | ||
}; | ||
handleQueueUpdate = (data) => { | ||
if (!data.isForcing) { | ||
this.forceStartSet = false; | ||
setTimeout(this.setForceStart, 1000); | ||
} | ||
if (this.gameType === "custom" | ||
&& data.usernames[0] === this.gameConfig.username | ||
&& data.numPlayers != this.queueNumPlayers | ||
&& data.options.game_speed != this.gameConfig.customGameSpeed) { | ||
this.customOptionsSet = false; | ||
later(100).then(() => { | ||
this.setCustomOptions(); | ||
}); | ||
} | ||
this.queueNumPlayers = data.numPlayers; | ||
}; | ||
joinGame = (data) => { | ||
this.gameType = data.gameType; | ||
switch (data.gameType) { | ||
case "ffa": | ||
this.socket.emit('play', this.gameConfig.userId); | ||
Log.stdout('[joined] FFA'); | ||
break; | ||
case "1v1": | ||
this.socket.emit('join_1v1', this.gameConfig.userId); | ||
Log.stdout('[joined] 1v1'); | ||
break; | ||
case "custom": | ||
if (data.gameId) { | ||
this.gameConfig.customGameId = data.gameId; | ||
Object.defineProperty(this, "setForceStart", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
if (!this.forceStartSet) { | ||
this.forceStartSet = true; | ||
this.socket.emit('set_force_start', this.gameConfig.customGameId, true); | ||
Log.debug('sent: set_force_start'); | ||
} | ||
this.socket.emit('join_private', data.gameId, this.gameConfig.userId, process.env['AUTH_TOKEN']); | ||
setTimeout(this.setCustomOptions, 100); | ||
setTimeout(this.setForceStart, 2000); | ||
Log.stdout(`[joined] custom: ${this.gameConfig.customGameId}`); | ||
break; | ||
default: | ||
Log.stderr(`[join] invalid gameType: ${data.gameType}`); | ||
return; | ||
} | ||
Redis.publish(this.botId, "state", { joined: data }); | ||
this.gamePhase = "joined_lobby"; | ||
}; | ||
leaveGame = () => { | ||
if (this.gamePhase == "joined_lobby") { | ||
this.socket.emit('cancel'); | ||
Log.debug('sent: cancel'); | ||
} | ||
else if (this.gamePhase == "playing") { | ||
this.socket.emit('leave_game'); | ||
Log.debug('sent: leave_game'); | ||
} | ||
else { | ||
Log.stderr(`[leaveGame] Invalid Request, Current State: ${this.gamePhase}`); | ||
return; | ||
} | ||
Redis.publish(this.botId, "state", { left: true }); | ||
this.gamePhase = "connected"; | ||
this.forceStartSet = false; | ||
this.customOptionsSet = false; | ||
}; | ||
setForceStart = () => { | ||
if (!this.forceStartSet) { | ||
this.forceStartSet = true; | ||
this.socket.emit('set_force_start', this.gameConfig.customGameId, true); | ||
Log.debug('sent: set_force_start'); | ||
} | ||
}; | ||
setCustomOptions = () => { | ||
if (this.gameType != "custom") | ||
return; | ||
if (!this.customOptionsSet) { | ||
this.customOptionsSet = true; | ||
this.socket.emit('set_custom_options', this.gameConfig.customGameId, { | ||
"game_speed": this.gameConfig.customGameSpeed | ||
}); | ||
Log.debug('sent: set_custom_options'); | ||
} | ||
}; | ||
deconflict = async () => { | ||
if (this.deconflicted) | ||
return; | ||
this.deconflicted = true; | ||
let countResponses = 0; | ||
let startPing = Date.now(); | ||
let handlePing = (message) => { | ||
if (message === MESSAGE.PING) { | ||
Redis.publish(this.botId, "deconflict", MESSAGE.PONG); | ||
} | ||
else if (message === MESSAGE.PONG) { | ||
countResponses++; | ||
let ping = Date.now() - startPing; | ||
if (countResponses === 2 && ping < 10000) { | ||
Log.stderr("Redis channel conflict. Select a unique userId and try again."); | ||
process.exit(4); | ||
}); | ||
Object.defineProperty(this, "setCustomOptions", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: () => { | ||
if (this.gameType != "custom") | ||
return; | ||
if (!this.customOptionsSet) { | ||
this.customOptionsSet = true; | ||
this.socket.emit('set_custom_options', this.gameConfig.customGameId, { | ||
"game_speed": this.gameConfig.customGameSpeed | ||
}); | ||
Log.debug('sent: set_custom_options'); | ||
} | ||
} | ||
}; | ||
await Redis.subscribe(this.botId, "deconflict", handlePing); | ||
Redis.publish(this.botId, "deconflict", MESSAGE.PING); | ||
}; | ||
quit = async () => { | ||
switch (this.gamePhase) { | ||
case "joined_lobby": | ||
case "playing": | ||
this.socket.emit('leave_game'); | ||
Log.debug('sent: leave_game'); | ||
} | ||
await this.socket.disconnect(); | ||
await Redis.quit(); | ||
}; | ||
}); | ||
Object.defineProperty(this, "deconflict", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: async () => { | ||
if (this.deconflicted) | ||
return; | ||
this.deconflicted = true; | ||
let countResponses = 0; | ||
let startPing = Date.now(); | ||
let handlePing = (message) => { | ||
if (message === MESSAGE.PING) { | ||
Redis.publish(this.botId, "deconflict", MESSAGE.PONG); | ||
} | ||
else if (message === MESSAGE.PONG) { | ||
countResponses++; | ||
let ping = Date.now() - startPing; | ||
if (countResponses === 2 && ping < 10000) { | ||
Log.stderr("Redis channel conflict. Select a unique userId and try again."); | ||
process.exit(4); | ||
} | ||
} | ||
}; | ||
await Redis.subscribe(this.botId, "deconflict", handlePing); | ||
Redis.publish(this.botId, "deconflict", MESSAGE.PING); | ||
} | ||
}); | ||
Object.defineProperty(this, "quit", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: async () => { | ||
switch (this.gamePhase) { | ||
case "joined_lobby": | ||
case "playing": | ||
this.socket.emit('leave_game'); | ||
Log.debug('sent: leave_game'); | ||
} | ||
await this.socket.disconnect(); | ||
await Redis.quit(); | ||
} | ||
}); | ||
this.botId = gameConfig.BOT_ID_PREFIX + '-' + hashUserId(gameConfig.userId); | ||
this.gameConfig = gameConfig; | ||
this.initializeSocketConnection(); | ||
this.initializeRedisConnection(redisConfig).then(() => { | ||
this.deconflict(); | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=app.js.map |
import { patch } from './gameUtils.js'; | ||
export class GameState { | ||
playerIndex; | ||
replay_id; | ||
usernames; | ||
map = []; | ||
turn; | ||
width; | ||
height; | ||
size; | ||
armies; | ||
terrain; | ||
cities = []; | ||
ownGeneral = -1; | ||
enemyGeneral = -1; | ||
ownTiles = new Map(); | ||
enemyTiles = new Map(); | ||
discoveredTiles; | ||
initialized = false; | ||
constructor(data) { | ||
Object.defineProperty(this, "playerIndex", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "replay_id", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "usernames", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "map", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: [] | ||
}); | ||
Object.defineProperty(this, "turn", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "width", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "height", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "size", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "armies", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "terrain", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "cities", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: [] | ||
}); | ||
Object.defineProperty(this, "ownGeneral", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: -1 | ||
}); | ||
Object.defineProperty(this, "enemyGeneral", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: -1 | ||
}); | ||
Object.defineProperty(this, "ownTiles", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: new Map() | ||
}); | ||
Object.defineProperty(this, "enemyTiles", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: new Map() | ||
}); | ||
Object.defineProperty(this, "discoveredTiles", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: void 0 | ||
}); | ||
Object.defineProperty(this, "initialized", { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
value: false | ||
}); | ||
this.playerIndex = data.playerIndex; | ||
@@ -22,0 +107,0 @@ this.replay_id = data.replay_id; |
{ | ||
"name": "@corsaircoalition/sergeant-socket", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "a modular generals.io bot that implements advanced learning techniques", | ||
@@ -8,3 +8,3 @@ "main": "out/index.js", | ||
"dependencies": { | ||
"@corsaircoalition/common": "^1.2.0", | ||
"@corsaircoalition/common": "^1.3.2", | ||
"commander": "^10.0.1", | ||
@@ -14,4 +14,4 @@ "socket.io-client": "^4.7.2" | ||
"devDependencies": { | ||
"@sindresorhus/tsconfig": "^3.0.1", | ||
"@types/node": "^18.17.1", | ||
"@sindresorhus/tsconfig": "^4.0.0", | ||
"@types/node": "^20.4.10", | ||
"typescript": "^5.1.6" | ||
@@ -18,0 +18,0 @@ }, |
# Sergeant Socket | ||
A message broker that connects to generals.io via socket.io, publishes all game updates to Redis, and receives command and actions from other components on Redis. | ||
[Generally Genius](https://corsaircoalition.github.io/) (GG) is a modular generals.io bot framework for development and analysis of game strategies and actions. [CorsairCoalition](https://corsaircoalition.github.io/) is a collection of components that form the GG framework. | ||
## Installation | ||
SergeantSocket is an IO module that connects to generals.io via socket.io, publishes all game updates to the Redis message broker, and receives command and actions from other components on Redis. | ||
## Configuration | ||
Download `config.example.json` from the [documentation repository](https://github.com/CorsairCoalition/docs) and make desired changes. | ||
To setup other components, see the [detailed instructions](https://corsaircoalition.github.io/setup/) on the [project website](https://corsaircoalition.github.io/). | ||
## Execution | ||
Install and run the executable: | ||
```sh | ||
npm install -g @corsaircoalition/sergeant-socket | ||
sergeant-socket config.json | ||
``` | ||
npm install | ||
npm run build | ||
or run directly from npm library: | ||
```sh | ||
npx @corsaircoalition/sergeant-socket config.json | ||
``` | ||
## Configuration | ||
or use docker: | ||
See `[config.json.example](https://github.com/CorsairCoalition/docs/blob/main/config.json.example)`. | ||
```sh | ||
docker run -it ./config.json:/config.json ghcr.io/corsaircoalition/sergeantsocket:latest | ||
``` | ||
@@ -19,3 +37,11 @@ ## Usage | ||
``` | ||
node . config.json | ||
Usage: @corsaircoalition/sergeant-socket [options] <configFile> | ||
a modular generals.io bot that implements advanced learning techniques | ||
Options: | ||
-V, --version output the version number | ||
-d, --debug enable debugging (default: false) | ||
-s, --set-username attempt to set username (default: false) | ||
-h, --help display help for command | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
63344
800
47