997 lines
22 KiB
TypeScript
997 lines
22 KiB
TypeScript
// @ts-nocheck
|
|
import fetch from 'node-fetch';
|
|
import WebSocket from 'ws';
|
|
import fs from 'fs';
|
|
import calculateLongestRoad from './longest-road';
|
|
|
|
import { getValidRoads, getValidCorners } from '../util/validLocations';
|
|
import { layout, staticData } from '../util/layout';
|
|
|
|
const version = '0.0.1';
|
|
|
|
if (process.argv.length < 5) {
|
|
console.error(`
|
|
usage: npm start SERVER GAME-ID USER
|
|
|
|
For example:
|
|
|
|
npm start https://nuc.ketrenos.com:3000/ketr.ketran robot-wars ai-1
|
|
|
|
`);
|
|
process.exit(-1);
|
|
}
|
|
|
|
const server = process.argv[2];
|
|
const gameId = process.argv[3];
|
|
const name = process.argv[4];
|
|
|
|
const game: any = {};
|
|
const anyValue = undefined;
|
|
|
|
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
|
|
|
|
/* Do not use arrow function as this is rebound to have
|
|
* this as the WebSocket */
|
|
let send = function (this: WebSocket, data: any) {
|
|
if (data.type === 'get') {
|
|
console.log(`ws - send: get`, data.fields);
|
|
} else {
|
|
console.log(`ws - send: ${data.type}`);
|
|
}
|
|
this.send(JSON.stringify(data));
|
|
};
|
|
|
|
const error = (e: any) => {
|
|
console.log(`ws - error`, e);
|
|
};
|
|
|
|
const connect = async () => {
|
|
let loc = new URL(server), new_uri;
|
|
let player;
|
|
try {
|
|
const data = JSON.parse(await fs.readFile(`${name}.json`, 'utf-8'));
|
|
player = data.player;
|
|
} catch (_) {
|
|
const res = await fetch(`${server}/api/v1/games`, {
|
|
method: 'GET',
|
|
cache: 'no-cache',
|
|
credentials: 'same-origin', /* include cookies */
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
if (!res) {
|
|
throw new Error(`Unable to connect to ${server}`);
|
|
}
|
|
|
|
player = JSON.parse(await res.text()).player;
|
|
await fs.writeFile(`${name}.json`, JSON.stringify({
|
|
name,
|
|
player
|
|
}));
|
|
}
|
|
console.log(`Connecting to ${server} as ${player}`);
|
|
|
|
if (loc.protocol === "https:") {
|
|
new_uri = "wss";
|
|
} else {
|
|
new_uri = "ws";
|
|
}
|
|
new_uri = `${new_uri}://${loc.host}/ketr.ketran/api/v1/games/ws/${gameId}`;
|
|
const ws = new WebSocket(new_uri,
|
|
[],
|
|
{
|
|
'headers': {
|
|
'Cookie': `player=${player}`
|
|
}
|
|
});
|
|
send = send.bind(ws);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const headers = (e) => {
|
|
console.log(`ws - headers`);
|
|
};
|
|
|
|
const open = (e) => {
|
|
console.log(`ws - open`);
|
|
resolve(ws);
|
|
};
|
|
|
|
const connection = (ws) => {
|
|
console.log("connection request cookie: ", ws.upgradeReq.headers.cookie);
|
|
};
|
|
|
|
const close = (e) => {
|
|
console.log(`ws - close`);
|
|
};
|
|
|
|
ws.on('open', open);
|
|
ws.on('connect', () => { connect(ws); });
|
|
ws.on('headers', headers);
|
|
ws.on('close', close);
|
|
ws.on('error', error);
|
|
ws.on('message', async (data) => { await message(data); });
|
|
});
|
|
};
|
|
|
|
const createPlayer = () => {
|
|
};
|
|
|
|
const types = [ 'wheat', 'brick', 'stone', 'sheep', 'wood' ];
|
|
|
|
const tryBuild = () => {
|
|
let waitingFor = undefined;
|
|
|
|
if (!waitingFor
|
|
&& game.private.settlements
|
|
&& game.private.wood
|
|
&& game.private.brick
|
|
&& game.private.sheep
|
|
&& game.private.wheat) {
|
|
const corners = getValidCorners(game, game.color);
|
|
if (corners.length) {
|
|
send({
|
|
type: 'buy-settlement'
|
|
});
|
|
waitingFor = {
|
|
turn: {
|
|
actions: anyValue
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
if (!waitingFor
|
|
&& game.private.cities
|
|
&& game.private.stone >= 3
|
|
&& game.private.wheat >= 2) {
|
|
const corners = getValidCorners(game, game.color, 'settlement');
|
|
if (corners.length) {
|
|
send({
|
|
type: 'buy-city'
|
|
});
|
|
waitingFor = {
|
|
turn: {
|
|
actions: anyValue
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
if (!waitingFor
|
|
&& game.private.roads
|
|
&& game.private.wood
|
|
&& game.private.brick) {
|
|
const roads = getValidRoads(game, game.color);
|
|
if (roads.length) {
|
|
send({
|
|
type: 'buy-road'
|
|
});
|
|
waitingFor = {
|
|
turn: {
|
|
actions: anyValue
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
if (!waitingFor
|
|
&& game.private.wheat
|
|
&& game.private.stone
|
|
&& game.private.sheep) {
|
|
send({
|
|
type: 'buy-development'
|
|
});
|
|
waitingFor = {
|
|
private: {
|
|
development: anyValue
|
|
}
|
|
};
|
|
}
|
|
|
|
return waitingFor;
|
|
};
|
|
|
|
|
|
const tryProgress = () => {
|
|
let waitingFor = undefined;
|
|
|
|
if (!game.private.development) {
|
|
return;
|
|
}
|
|
|
|
let vps = 0;
|
|
for (let i = 0; i < game.private.development.length; i++) {
|
|
const card = game.private.development[i];
|
|
if (card.turn >= game.turns || card.played || card.type === 'vp') {
|
|
if (card.type === 'vp') vps++;
|
|
continue;
|
|
}
|
|
|
|
console.log(`${name} - Playing -`, card);
|
|
send({
|
|
type: 'play-card',
|
|
card
|
|
});
|
|
|
|
return {
|
|
turn: {
|
|
actions: anyValue
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vps) {
|
|
send({
|
|
type: 'chat',
|
|
message: `I have ${vps} VP cards!`
|
|
});
|
|
}
|
|
};
|
|
|
|
let sleeping = false;
|
|
let paused = false;
|
|
|
|
const sleep = async (delay) => {
|
|
if (sleeping) {
|
|
return;
|
|
}
|
|
sleeping = true;
|
|
return new Promise((resolve) => {
|
|
setTimeout(() => {
|
|
sleeping = false;
|
|
resolve();
|
|
}, delay);
|
|
});
|
|
};
|
|
|
|
|
|
const bestSettlementPlacement = (game) => {
|
|
const best = {
|
|
index: -1,
|
|
pips: 0
|
|
};
|
|
|
|
/* For each corner that is valid, find out which
|
|
* tiles are on that corner, and for each of those
|
|
* tiles, find the pip placement for that tile. */
|
|
game.turn.limits.corners.forEach(cornerIndex => {
|
|
const tiles = [];
|
|
layout.tiles.forEach((tile, index) => {
|
|
if (tile.corners.indexOf(cornerIndex) !== -1
|
|
&& tiles.indexOf(index) === -1) {
|
|
tiles.push({ tile, index });
|
|
}
|
|
});
|
|
|
|
let cornerScore = 0;
|
|
|
|
/* Find the tileOrder holding this tile */
|
|
tiles.forEach(tile => {
|
|
const index = tile.index;
|
|
// const tileIndex = game.tileOrder.indexOf(index);
|
|
const pipIndex = game.pipOrder[index];
|
|
const score = staticData.pips[pipIndex].pips;
|
|
cornerScore += score;
|
|
});
|
|
|
|
if (cornerScore > best.pips) {
|
|
best.index = cornerIndex;
|
|
best.pips = cornerScore;
|
|
}
|
|
});
|
|
|
|
console.log(`${name} - Corner ${best.index} gives ${best.pips} pips.`);
|
|
return best.index;
|
|
}
|
|
|
|
const bestRoadPlacement = (game) => {
|
|
const road = calculateLongestRoad(game);
|
|
console.log(`${name} - could make road ${road.segments + 1} long on ${road.index}`);
|
|
|
|
let attempt = -1;
|
|
if (road.index !== -1) {
|
|
layout.roads[road.index].corners.forEach(cornerIndex => {
|
|
if (attempt !== -1) {
|
|
return;
|
|
}
|
|
layout.corners[cornerIndex].roads.forEach(roadIndex => {
|
|
if (attempt !== -1) {
|
|
return;
|
|
}
|
|
const placedRoad = game.placements.roads[roadIndex];
|
|
if (!placedRoad || placedRoad.color) {
|
|
return;
|
|
}
|
|
attempt = roadIndex;
|
|
});
|
|
});
|
|
}
|
|
|
|
if (game.turn.limits.roads.indexOf(attempt) !== -1) {
|
|
console.log(`${name} - attempting to place on end of longest road`);
|
|
return attempt;
|
|
} else {
|
|
console.log(`${name} - selecting a random road location`);
|
|
return game.turn.limits.roads[Math.floor(
|
|
Math.random() * game.turn.limits.roads.length)];
|
|
}
|
|
}
|
|
|
|
const isMatch = (input, received) => {
|
|
for (let key in input) {
|
|
/* received update didn't contain this field */
|
|
if (!(key in received)) {
|
|
return false;
|
|
}
|
|
/* Received object had a value we were waiting to have set */
|
|
if (input[key] === anyValue && (key in received)) {
|
|
continue;
|
|
}
|
|
/* waitingFor field is an object, so recurse */
|
|
if (typeof input[key] === 'object') {
|
|
if (!isMatch(input[key], received[key])) {
|
|
return false
|
|
}
|
|
/* object matched; go to next field */
|
|
continue;
|
|
}
|
|
/* No match in requested key... */
|
|
if (input[key] !== received[key]) {
|
|
return false;
|
|
}
|
|
/* Value matches */
|
|
}
|
|
/* All fields set or matched */
|
|
return true;
|
|
};
|
|
|
|
const processLobby = (received) => {
|
|
if (game.name === '' && !received.name) {
|
|
send({ type: 'player-name', name });
|
|
/* Wait for the game.name to be set to 'name' and for unselected */
|
|
return { name, players: anyValue, unselected: anyValue };
|
|
}
|
|
|
|
if (!received.unselected) {
|
|
return {
|
|
unselected: anyValue
|
|
};
|
|
}
|
|
|
|
/* AI selected a Player. Wait for game-order */
|
|
if (received.unselected.indexOf(name) === -1) {
|
|
send({
|
|
type: 'chat',
|
|
message: `Woohoo! Robot AI ${version} is alive and playing as ${game.color}!`
|
|
});
|
|
return { state: 'game-order' };
|
|
}
|
|
|
|
const slots = [];
|
|
for (let color in game.players) {
|
|
if (game.players[color].status === 'Not active') {
|
|
slots.push(color);
|
|
}
|
|
}
|
|
|
|
if (slots.length === 0) {
|
|
send({
|
|
chat: `There are no slots for me to play :(. Waiting for one to open up.`
|
|
});
|
|
return { unselected: anyValue };
|
|
}
|
|
|
|
const index = Math.floor(Math.random() * slots.length);
|
|
console.log(`${name} - requesting to play as ${slots[index]}.`);
|
|
game.unselected = game.unselected.filter(
|
|
color => color === slots[index]);
|
|
send({
|
|
type: 'set',
|
|
field: 'color',
|
|
value: slots[index]
|
|
});
|
|
return { color: slots[index], state: 'game-order' };
|
|
};
|
|
|
|
const processGameOrder = async () => {
|
|
if (!game.color) {
|
|
console.log(`game-order - player not active`);
|
|
return { color };
|
|
}
|
|
console.log(`game-order - `, {
|
|
color: game.color,
|
|
players: game.players
|
|
});
|
|
if (!game.players[game.color].orderRoll || game.players[game.color].tied) {
|
|
console.log(`Time to roll as ${game.color}`);
|
|
send({ type: 'roll' });
|
|
}
|
|
|
|
return { turn: { color: game.color }};
|
|
};
|
|
|
|
const processInitialPlacement = async (received) => {
|
|
/* Fetch the various game order elements so we can make
|
|
* educated location selections */
|
|
if (!game.pipOrder
|
|
|| !game.tileOrder
|
|
|| !game.borderOrder) {
|
|
return {
|
|
pipOrder: anyValue,
|
|
tileOrder: anyValue,
|
|
borderOrder: anyValue
|
|
}
|
|
}
|
|
|
|
if (!game.turn || game.turn.color !== game.color) {
|
|
return {
|
|
turn: {
|
|
color: game.color,
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!game.placements) {
|
|
return {
|
|
turn: {
|
|
color: game.color,
|
|
},
|
|
placements: anyValue
|
|
};
|
|
}
|
|
|
|
if (!game.turn.actions) {
|
|
return {
|
|
turn: {
|
|
color: game.color,
|
|
actions: anyValue
|
|
},
|
|
placements: anyValue
|
|
};
|
|
}
|
|
|
|
let index;
|
|
const type = game.turn.actions[0];
|
|
if (type === 'place-road') {
|
|
index = bestRoadPlacement(game);
|
|
} else if (type === 'place-settlement') {
|
|
index = bestSettlementPlacement(game);
|
|
}
|
|
console.log(`Selecting ${type} at ${index}`);
|
|
send({
|
|
type, index
|
|
});
|
|
/* Wait for this player's turn again */
|
|
return { turn: { color: game.color } };
|
|
}
|
|
|
|
/* Start watching for a name entry */
|
|
let waitingFor = { name: anyValue }, received = {};
|
|
|
|
const reducedGame = (game) => {
|
|
const filters = [ 'chat', 'activities', 'placements', 'players', 'private', 'dice' ];
|
|
const value = {};
|
|
for (let key in game) {
|
|
if (filters.indexOf(key) === -1) {
|
|
value[key] = game[key];
|
|
} else {
|
|
if (Array.isArray(game[key])) {
|
|
value[key] = `length(${game[key].length})`;
|
|
} else {
|
|
value[key] = `...filtered`;
|
|
}
|
|
}
|
|
}
|
|
return value;
|
|
}
|
|
|
|
const processWaitingFor = (waitingFor) => {
|
|
const value = {
|
|
type: 'get',
|
|
fields: []
|
|
};
|
|
for (let key in waitingFor) {
|
|
value.fields.push(key);
|
|
}
|
|
send(value);
|
|
received = {};
|
|
}
|
|
|
|
|
|
const selectResources = async (received) => {
|
|
if (!game.turn) {
|
|
return { turn: anyValue };
|
|
}
|
|
|
|
if (!game.turn.actions || game.turn.actions.indexOf('select-resources') === -1) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const processDiscard = async (received) => {
|
|
if (!game.players) {
|
|
waitingFor = {
|
|
players: {}
|
|
};
|
|
waitingFor.players[game.color] = undefined;
|
|
return waitingFor;
|
|
}
|
|
|
|
let mustDiscard = game.players[game.color].mustDiscard;
|
|
|
|
if (!mustDiscard) {
|
|
return;
|
|
}
|
|
|
|
const cards = [],
|
|
discards = {};
|
|
types.forEach(type => {
|
|
for (let i = 0; i < game.private[type]; i++) {
|
|
cards.push(type);
|
|
}
|
|
});
|
|
while (mustDiscard--) {
|
|
const type = cards[Math.floor(Math.random() * cards.length)];
|
|
if (!(type in discards)) {
|
|
discards[type] = 1;
|
|
} else {
|
|
discards[type]++;
|
|
}
|
|
}
|
|
console.log(`discarding - `, discards);
|
|
send({
|
|
type: 'discard',
|
|
discards
|
|
});
|
|
waitingFor = {
|
|
turn: anyValue,
|
|
players: {}
|
|
}
|
|
waitingFor.players[game.color] = anyValue;
|
|
return waitingFor;
|
|
};
|
|
|
|
const processTrade = async (received) => {
|
|
const enough = [];
|
|
let shouldTrade = true;
|
|
|
|
/* Check and see which resources we have enough of */
|
|
types.forEach(type => {
|
|
if (game.private[type] >= 4) {
|
|
enough.push(type);
|
|
}
|
|
});
|
|
shouldTrade = enough.length > 0;
|
|
|
|
let least = { type: undefined, count: 0 };
|
|
|
|
if (shouldTrade) {
|
|
/* Find out which resource we have the least amount of */
|
|
types.forEach(type => {
|
|
if (game.private[type] <= least.count) {
|
|
least.type = type;
|
|
least.count = game.private[type];
|
|
}
|
|
});
|
|
if (least.count >= 4) {
|
|
shouldTrade = false;
|
|
}
|
|
}
|
|
|
|
/* If trade not active, see if it should be... */
|
|
if (shouldTrade
|
|
&& (!received.turn.actions
|
|
|| received.turn.actions.indexOf('trade') === -1)) {
|
|
/* Request trade mode, and wait for it... */
|
|
console.log(`${name} - starting trade negotiations`);
|
|
send({
|
|
type: 'trade'
|
|
});
|
|
return {
|
|
turn: { actions: anyValue }
|
|
}
|
|
}
|
|
|
|
/* If we do not have enough resources, and trade is active, cancel */
|
|
if (!shouldTrade
|
|
&& received.turn.actions
|
|
&& received.turn.actions.indexOf('trade') !== -1) {
|
|
console.log(`${name} - cancelling trade negotiations`);
|
|
send({
|
|
type: 'trade',
|
|
action: 'cancel'
|
|
});
|
|
return {
|
|
turn: anyValue
|
|
};
|
|
}
|
|
|
|
if (!shouldTrade) {
|
|
return;
|
|
}
|
|
|
|
const give = {
|
|
type: enough[Math.floor(Math.random() * enough.length)],
|
|
count: 4
|
|
}, get = {
|
|
type: least.type,
|
|
count: 1
|
|
};
|
|
const offer = {
|
|
gives: [give],
|
|
gets: [get]
|
|
};
|
|
|
|
if (received.turn.offer) {
|
|
send({
|
|
type: 'trade',
|
|
action: 'accept',
|
|
offer: {
|
|
name: 'The bank',
|
|
gets: [{ type: get.type, count: 1 }],
|
|
gives: [{ type: give.type, count: give.count }]
|
|
}
|
|
});
|
|
return {
|
|
turn: {
|
|
actions: anyValue
|
|
}
|
|
};
|
|
}
|
|
|
|
/* Initiate offer... */
|
|
|
|
if (!received.turn.offer) {
|
|
console.log(`trade - `, offer);
|
|
send({
|
|
type: 'trade',
|
|
action: 'offer',
|
|
offer
|
|
});
|
|
|
|
return {
|
|
private: { offerRejected: anyValue }
|
|
};
|
|
}
|
|
|
|
return {
|
|
turn: anyValue
|
|
};
|
|
}
|
|
|
|
const processVolcano = async (received) => {
|
|
if (!game.turn || !game.private) {
|
|
return {
|
|
turn: anyValue,
|
|
private: anyValue
|
|
}
|
|
};
|
|
|
|
if (game.turn.actions
|
|
&& game.turn.actions.indexOf('select-resources') !== -1) {
|
|
console.log(`${name} - TODO - select resources -`, game.turn.select);
|
|
return;
|
|
}
|
|
|
|
send({
|
|
type: 'roll'
|
|
});
|
|
|
|
return {
|
|
turn: anyValue
|
|
};
|
|
};
|
|
|
|
const processNormal = async (received) => {
|
|
let waitingFor = undefined;
|
|
|
|
if (!game.turn || !game.private) {
|
|
return {
|
|
turn: anyValue,
|
|
private: anyValue
|
|
}
|
|
};
|
|
|
|
if (!game.pipOrder) {
|
|
return {
|
|
pipOrder: anyValue
|
|
};
|
|
}
|
|
|
|
/* Process things that happen on everyone's turn */
|
|
waitingFor = await processDiscard(received);
|
|
if (waitingFor) {
|
|
return waitingFor;
|
|
}
|
|
|
|
waitingFor = await selectResources(received);
|
|
if (waitingFor) {
|
|
return waitingFor;
|
|
}
|
|
|
|
/* From here on it is only actions that occur on the player's turn */
|
|
if (!received.turn || received.turn.color !== game.color) {
|
|
console.log(`${name} - waiting for turn... ${game.players[game.turn.color].name} is active.`);
|
|
console.log({
|
|
wheat: game.private.wheat,
|
|
sheep: game.private.sheep,
|
|
stone: game.private.stone,
|
|
brick: game.private.brick,
|
|
wood: game.private.wood,
|
|
});
|
|
return {
|
|
turn: { color: game.color },
|
|
dice: anyValue
|
|
};
|
|
}
|
|
|
|
console.log(`${name}'s turn. Processing...`);
|
|
|
|
if (!game.dice) {
|
|
console.log(`${name} - rolling...`);
|
|
send({
|
|
type: 'roll'
|
|
});
|
|
return {
|
|
turn: {
|
|
color: game.color
|
|
},
|
|
dice: anyValue
|
|
};
|
|
}
|
|
|
|
if (received.turn.actions && received.turn.actions.indexOf('place-road') !== -1) {
|
|
index = bestRoadPlacement(game);
|
|
send({
|
|
type: 'place-road', index
|
|
});
|
|
return {
|
|
turn: { color: game.color }
|
|
};
|
|
}
|
|
|
|
if (game.turn.actions && game.turn.actions.indexOf('place-city') !== -1) {
|
|
index = game.turn.limits.corners[Math.floor(
|
|
Math.random() * game.turn.limits.corners.length)];
|
|
send({
|
|
type: 'place-city', index
|
|
});
|
|
return {
|
|
turn: { color: game.color }
|
|
};
|
|
}
|
|
|
|
if (game.turn.actions && game.turn.actions.indexOf('place-settlement') !== -1) {
|
|
const index = bestSettlementPlacement(game);
|
|
send({
|
|
type: 'place-settlement', index
|
|
});
|
|
return {
|
|
turn: { color: game.color }
|
|
};
|
|
}
|
|
|
|
if (game.turn.actions
|
|
&& game.turn.actions.indexOf('place-robber') !== -1) {
|
|
console.log({ pips: game.turn.limits.pips });
|
|
const index = game.turn.limits.pips[Math.floor(Math.random() * game.turn.limits.pips.length)];
|
|
console.log(`placing robber - ${index}`)
|
|
send({
|
|
type: 'place-robber',
|
|
index
|
|
});
|
|
return {
|
|
turn: { color: game.color }
|
|
};
|
|
}
|
|
|
|
if (game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1) {
|
|
if (!game.turn.limits.players) {
|
|
console.warn(`No players in limits with steal-resource`);
|
|
return;
|
|
}
|
|
const { color } = game.turn.limits.players[Math.floor(Math.random() * game.turn.limits.players.length)];
|
|
console.log(`stealing resouce from ${game.players[color].name}`);
|
|
send({
|
|
type: 'steal-resource',
|
|
color
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (game.turn.robberInAction) {
|
|
console.log({ turn: game.turn });
|
|
return;
|
|
}
|
|
|
|
console.log({
|
|
wheat: game.private.wheat,
|
|
sheep: game.private.sheep,
|
|
stone: game.private.stone,
|
|
brick: game.private.brick,
|
|
wood: game.private.wood,
|
|
});
|
|
|
|
waitingFor = await tryBuild(received);
|
|
if (waitingFor) {
|
|
return waitingFor;
|
|
}
|
|
|
|
waitingFor = await tryProgress(received);
|
|
if (waitingFor) {
|
|
return waitingFor;
|
|
}
|
|
|
|
waitingFor = await processTrade(received);
|
|
if (waitingFor) {
|
|
return waitingFor;
|
|
}
|
|
|
|
console.log(`${name} - passing`);
|
|
send({
|
|
type: 'pass'
|
|
});
|
|
|
|
return { turn: anyValue };
|
|
};
|
|
|
|
const message = async (data) => {
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (error) {
|
|
console.error(error);
|
|
console.log(data);
|
|
return;
|
|
}
|
|
|
|
switch (data.type) {
|
|
case 'warning':
|
|
if (game.turn.color === game.color && game.state !== 'lobby') {
|
|
console.log(`WARNING: ${data.warning}. Passing.`);
|
|
send({
|
|
type: 'pass'
|
|
});
|
|
waitingFor = {
|
|
turn: { color: game.color }
|
|
};
|
|
processWaitingFor(waitingFor);
|
|
}
|
|
break;
|
|
|
|
case 'game-update':
|
|
/* Keep game updated with the latest information */
|
|
Object.assign(game, data.update);
|
|
if (data.update.chat) {
|
|
let newState = paused;
|
|
const rePause = new RegExp(`${name}: pause`, 'i');
|
|
const reUnpause = new RegExp(`${name}: unpause`, 'i');
|
|
|
|
for (let i = 0; i < data.update.chat.length; i++) {
|
|
if (data.update.chat[i].message.match(rePause)) {
|
|
newState = true;
|
|
continue;
|
|
}
|
|
|
|
if (data.update.chat[i].message.match(reUnpause)) {
|
|
newState = false;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (newState !== paused) {
|
|
paused = newState;
|
|
send({
|
|
type: 'chat',
|
|
message: `Robot AI is now ${paused ? '' : 'un'}paused.`
|
|
});
|
|
}
|
|
}
|
|
|
|
if (paused) {
|
|
if (waitingFor) {
|
|
Object.assign(received, data.update);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (sleeping) {
|
|
if (waitingFor) {
|
|
Object.assign(received, data.update);
|
|
}
|
|
console.log(`${name} - sleeping`);
|
|
return;
|
|
}
|
|
|
|
if (waitingFor) {
|
|
Object.assign(received, data.update);
|
|
if (!isMatch(waitingFor, received)) {
|
|
console.log(`${name} - still waiting - waitingFor: `,
|
|
waitingFor);
|
|
if (game.turn && game.turn.robberInAction) {
|
|
console.log(`${name} - robber in action! Must check discards...`);
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
console.log(`${name} - received match - received: `,
|
|
reducedGame(received));
|
|
console.log(`${name} - going to sleep`);
|
|
await sleep(1000 + Math.random() * 500);
|
|
console.log(`${name} - waking up`);
|
|
waitingFor = undefined;
|
|
}
|
|
}
|
|
|
|
switch (game.state) {
|
|
case undefined:
|
|
case 'lobby':
|
|
waitingFor = await processLobby(received);
|
|
if (waitingFor) {
|
|
processWaitingFor(waitingFor);
|
|
}
|
|
return;
|
|
|
|
case 'game-order':
|
|
waitingFor = await processGameOrder(received);
|
|
if (waitingFor) {
|
|
processWaitingFor(waitingFor);
|
|
}
|
|
return;
|
|
|
|
case 'initial-placement':
|
|
waitingFor = await processInitialPlacement(received);
|
|
if (waitingFor) {
|
|
processWaitingFor(waitingFor);
|
|
}
|
|
return;
|
|
|
|
case 'volcano':
|
|
waitingFor = await processVolcano(received);
|
|
if (waitingFor) {
|
|
processWaitingFor(waitingFor);
|
|
}
|
|
return;
|
|
|
|
case 'normal':
|
|
waitingFor = await processNormal(received);
|
|
if (waitingFor) {
|
|
processWaitingFor(waitingFor);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'ping':
|
|
if (!game.state && !received.state) {
|
|
console.log(`ping received with no game. Sending update request`);
|
|
send({
|
|
type: 'game-update'
|
|
});
|
|
}
|
|
return;
|
|
|
|
default:
|
|
console.log(data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const ai = async () => {
|
|
send({
|
|
type: 'get',
|
|
fields: [ 'dice', 'name', 'color', 'state', 'placements' ]
|
|
});
|
|
}
|
|
|
|
connect().then(() => {
|
|
ai()
|
|
.catch((error) => {
|
|
console.error(error);
|
|
ws.close();
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|