361 lines
8.4 KiB
JavaScript
361 lines
8.4 KiB
JavaScript
const fetch = require('node-fetch');
|
|
const WebSocket = require('ws');
|
|
const fs = require('fs').promises;
|
|
|
|
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];
|
|
let session = undefined;
|
|
const name = process.argv[4];
|
|
|
|
const game = {};
|
|
|
|
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
|
|
|
|
const error = (e) => {
|
|
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}`
|
|
}
|
|
});
|
|
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', (data) => { message(ws, data); });
|
|
});
|
|
};
|
|
|
|
const createPlayer = (ws) => {
|
|
const send = (data) => {
|
|
ws.send(JSON.stringify(data));
|
|
};
|
|
|
|
if (game.name === '') {
|
|
send({ type: 'player-name', name });
|
|
return;
|
|
}
|
|
|
|
if (game.state !== 'lobby') {
|
|
return;
|
|
}
|
|
|
|
if (game.unselected.indexOf(name) === -1) {
|
|
return;
|
|
}
|
|
|
|
const slots = [];
|
|
for (let color in game.players) {
|
|
if (game.players[color].status === 'Not active') {
|
|
slots.push(color);
|
|
}
|
|
}
|
|
if (slots.length === 0) {
|
|
return;
|
|
}
|
|
const index = Math.floor(Math.random() * slots.length);
|
|
console.log(`Requesting to play as ${slots[index]}.`);
|
|
game.unselected = game.unselected.filter(
|
|
color => color === slots[index]);
|
|
send({
|
|
type: 'set',
|
|
field: 'color',
|
|
value: slots[index]
|
|
});
|
|
send({
|
|
type: 'chat',
|
|
message: `Woohoo! Robot AI ${version} is alive!`
|
|
});
|
|
};
|
|
|
|
const tryBuild = (ws) => {
|
|
const send = (data) => {
|
|
console.log(`ws - send`);
|
|
ws.send(JSON.stringify(data));
|
|
};
|
|
let trying = false;
|
|
if (game.private.wood
|
|
&& game.private.brick
|
|
&& game.private.sheep
|
|
&& game.private.wheat) {
|
|
send({
|
|
type: 'buy-settlement'
|
|
});
|
|
trying = true;
|
|
}
|
|
|
|
if (game.private.wood && game.private.brick) {
|
|
send({
|
|
type: 'buy-road'
|
|
});
|
|
trying = true;
|
|
}
|
|
|
|
return trying;
|
|
};
|
|
|
|
const message = (ws, data) => {
|
|
const send = (data) => {
|
|
console.log(`ws - send: ${data.type}`);
|
|
ws.send(JSON.stringify(data));
|
|
};
|
|
|
|
data = JSON.parse(data);
|
|
switch (data.type) {
|
|
case 'game-update':
|
|
console.log(`ws - receive - `,
|
|
Object.assign({}, data.update, {
|
|
activities: 'filtered out',
|
|
chat: 'filtered out'
|
|
})
|
|
);
|
|
|
|
Object.assign(game, data.update);
|
|
console.log(`state - ${game.state}`);
|
|
|
|
switch (game.state) {
|
|
case undefined:
|
|
case 'lobby':
|
|
createPlayer(ws);
|
|
break;
|
|
|
|
case 'game-order':
|
|
if (!game.color) {
|
|
console.log(`game-order - player not active`);
|
|
return;
|
|
}
|
|
console.log(`game-order - `, {
|
|
color: game.color,
|
|
players: game.players
|
|
});
|
|
if (!game.players[game.color].orderRoll) {
|
|
console.log(`Time to roll as ${game.color}`);
|
|
send({ type: 'roll' });
|
|
}
|
|
break;
|
|
|
|
case 'initial-placement': {
|
|
console.log({ color: game.color, state: game.state, turn: game.turn });
|
|
if (game.turn.color !== game.color) {
|
|
break;
|
|
}
|
|
|
|
let index;
|
|
const type = game.turn.actions[0];
|
|
if (type === 'place-road') {
|
|
console.log({ roads: game.turn.limits.roads });
|
|
index = game.turn.limits.roads[Math.floor(
|
|
Math.random() * game.turn.limits.roads.length)];
|
|
} else if (type === 'place-settlement') {
|
|
console.log({ corners: game.turn.limits.corners });
|
|
index = game.turn.limits.corners[Math.floor(
|
|
Math.random() * game.turn.limits.corners.length)];
|
|
}
|
|
console.log(`Selecting ${type} at ${index}`);
|
|
send({
|
|
type, index
|
|
});
|
|
} break;
|
|
|
|
case 'normal':
|
|
if (game.turn.color !== game.color) {
|
|
return;
|
|
}
|
|
if (game.turn.actions && game.turn.actions.indexOf('place-road') !== -1) {
|
|
index = game.turn.limits.roads[Math.floor(
|
|
Math.random() * game.turn.limits.roads.length)];
|
|
send({
|
|
type: 'place-road', index
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (game.turn.actions && game.turn.actions.indexOf('place-settlement') !== -1) {
|
|
console.log({ corners: game.turn.limits.corners });
|
|
index = game.turn.limits.corners[Math.floor(
|
|
Math.random() * game.turn.limits.corners.length)];
|
|
send({
|
|
type: 'place-settlement', index
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!game.dice) {
|
|
console.log(`Rolling...`);
|
|
send({
|
|
type: 'roll'
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (game.private.mustDiscard) {
|
|
let mustDiscard = game.private.mustDiscard;
|
|
const cards = [],
|
|
discards = {};
|
|
const types = ['wheat', 'sheep', 'stone', 'brick', 'wood'];
|
|
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
|
|
});
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (game.turn.actions && game.turn.actions.indexOf('steal-resource') !== -1) {
|
|
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 });
|
|
} else {
|
|
console.log({
|
|
turn: game.turn,
|
|
wheat: game.private.wheat,
|
|
sheep: game.private.sheep,
|
|
stone: game.private.stone,
|
|
brick: game.private.brick,
|
|
wood: game.private.wood,
|
|
});
|
|
if (!tryBuild(ws)) {
|
|
send({
|
|
type: 'pass'
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
console.log({ state: game.state, turn: game.turn });
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 'ping':
|
|
if (!game.state) {
|
|
console.log(`ping received with no game. Sending update request`);
|
|
ws.send(JSON.stringify({
|
|
type: 'game-update'
|
|
}));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
console.log(data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const ai = async (ws) => {
|
|
}
|
|
|
|
connect().then((ws) => {
|
|
ai(ws)
|
|
.catch((error) => {
|
|
console.error(error);
|
|
ws.close();
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
});
|