var chalk = require('chalk') var generate = require('project-name-generator') var Bot = function() { this.pastPlayers = 0 this.playerIndex = 0 this.colours = { "empty": chalk.gray, "mountain": chalk.white, "unknown": chalk.yellow, "friendly": chalk.green, "enemy": chalk.red, "friendlyGeneral": chalk.blue, "enemyGeneral": chalk.magenta, "city": chalk.cyan } this.character = { "empty": "E", "mountain": "M", "unknown": "?", "friendly": "F", "enemy": "B", "friendlyGeneral": "FG", "enemyGeneral": "EG", "city": "C" } this.width = 0 this.height = 0 this.ignoreEvents = ['game_lost', 'game_won', 'queue_update', 'game_start', 'game_update'] this.ourGeneral = [-1, -1] this.generals = [] this.movementOptions = [[-1, 0], [1, 0], [0, -1], [0, 1]] this.attackIndex = 0 this.lastMap = [] this.lastCities = [] this.firstUpdate = true } Bot.prototype.connect = function(url) { this.socket = require('socket.io-client')(url, { transports: ['websocket'] }) var onevent = this.socket.onevent this.socket.onevent = function(packet) { var args = packet.data || [] onevent.call(this, packet) packet.data = ["*"].concat(args) onevent.call(this, packet) } } Bot.prototype.play = function(name, user_id, type) { if (type == undefined) { this.socket.emit('play', name, user_id) } else { this.socket.emit('join_private', type, name, user_id) } } Bot.prototype.forceStart = function(start) { this.socket.emit('set_force_start', null, start) } Bot.prototype.onConnect = function(callback) { this.socket.on('connect', callback) } Bot.prototype.onDisconnect = function(callback) { this.socket.on('disconnect', callback) } Bot.prototype.onLose = function(callback) { this.socket.on('game_lost', callback) } Bot.prototype.onWin = function(callback) { this.socket.on('game_won', callback) } Bot.prototype.onQueueUpdate = function(callback) { this.socket.on('queue_update', function(playerCount) { if (playerCount != this.pastPlayers) { callback(playerCount) this.pastPlayers = playerCount } }) } Bot.prototype.onGameStart = function(callback) { this.socket.on('game_start', function(data) { Bot.playerIndex = data.playerIndex callback(data) }) } Bot.prototype.onChat = function(callback) { this.socket.on('chat_message', function(room, data) { callback(data) }) } Bot.prototype.onGameUpdate = function(callback) { this.socket.on('game_update', function(data) { if (this.firstUpdate) { data.lastMap = [] for (var i = 0; i < data.map_diff[2] * data.map_diff[3]; i++) { data.lastMap.push(0) } this.firstUpdate = false } data.map = this.patch(this.lastMap, data.map_diff) this.lastMap = data.map.concat([]) data.cities = this.patch(this.lastCities, data.cities_diff) this.lastCities = data.cities.concat([]) console.log(JSON.stringify(data)) this.width = parseInt(data.map[0]) this.height = parseInt(data.map[1]) var map_data = this.patch(this.lastMap, data.map.slice(2, 2 + this.width * this.height)) var map = [] var mountain_data = data.map.slice(2 + this.width * this.height, 3 + 2 * this.width * this.height) var counter = 0 for (var j = 0; j < this.height; j++) { var line = [] for (var i = 0; i < this.width; i++) { line.push([map_data[counter]]) counter++ } map.push(line) } var counter = 0 var y = 0 for (var j = 0; j < this.height; j++) { var x = 0 for (var i = 0; i < this.width; i++) { if (mountain_data[counter] == -1) { map[y][x].push("empty") } else if (mountain_data[counter] == -2 || mountain_data[counter] == -4) { map[y][x].push("mountain") } else if (mountain_data[counter] == -3) { map[y][x].push("unknown") } else if (mountain_data[counter] == Bot.playerIndex) { map[y][x].push("friendly") } else { map[y][x].push("enemy") } counter++ x++ } y++ } this.generals = [] for (var i = 0; i < data.generals.length; i++) { if (data.generals[i] != -1 && i != Bot.playerIndex) { var generalXY = this.toXY(data.generals[i]) if (i == Bot.playerIndex) { map[generalXY[1]][generalXY[0]][1] = "friendlyGeneral" this.ourGeneral = generalXY } else { map[generalXY[1]][generalXY[0]][1] = "enemyGeneral" this.generals.push(generalXY) } } } for (var i = 0; i < data.cities.length; i++) { if (data.cities[i] != -1) { var mapXY = this.toXY(data.cities[i]) map[mapXY[1]][mapXY[0]][1] = "city" } } callback(data.turn, map) }.bind(this)) } Bot.prototype.onOther = function(callback) { var that = this this.socket.on("*", function(event, data) { if(that.ignoreEvents.indexOf(event) == -1) { callback(event, data) } }) } // patch(a, delta(a, b)) === b Bot.prototype.patch = function(old, diff) { var out = []; var i = 0; while(i < diff.length) { if (diff[i]) { // matching Array.prototype.push.apply(out, old.slice(out.length, out.length + diff[i])); } i++; if (i < diff.length && diff[i]) { // mismatching Array.prototype.push.apply(out, diff.slice(i + 1, i + 1 + diff[i])); i += diff[i]; } i++; } return out; } Bot.prototype.attack = function(from, to) { var from = this.toPoint(from[0], from[1]) var to = this.toPoint(to[0], to[1]) // console.log(this.attackIndex) console.log("Attacking point", to, "with army from", from) this.socket.emit('attack', from, to, false, this.attackIndex) this.attackIndex++ } Bot.prototype.printMap = function(map) { for (var y = 0; y < map.length; y++) { for (var x = 0; x < map[y].length; x++) { var mapValue = map[y][x][0] var mapType = map[y][x][1] if (mapValue != 0) { map[y][x] = this.colours[mapType]((mapValue + " ").substring(0, 4)) } else { map[y][x] = this.colours[mapType]((this.character[mapType] + " ").substring(0, 4)) } } console.log(map[y].join("")) } } Bot.prototype.frontDistance = function(map) { var frontDistance = [...Array(map.length).keys()].map(i => Array(map[0].length)) for (var y = 0; y < map.length; y++) { for (var x = 0; x < map[y].length; x++) { var mapValue = map[y][x][0] var mapType = map[y][x][1] if (mapType == 'empty' || mapType == 'unknown' || mapType == 'enemy' || mapType == 'enemyGeneral') { frontDistance[y][x] = 1 } else if (mapType == 'friendly' || mapType == 'friendlyGeneral') { frontDistance[y][x] = -1 } else { frontDistance[y][x] = -2 } } } var emptySquares = this.countValues(frontDistance, -1) var counter = 1 var more = true while (emptySquares) { for (var y = 0; y < map.length; y++) { for (var x = 0; x < map[y].length; x++) { if (frontDistance[y][x] == -1) { // console.log("Found our square at: " + x + ", " + y) var around = false for (var i = 0; i < this.movementOptions.length; i++) { var newCoords = [x + this.movementOptions[i][0], y + this.movementOptions[i][1]] if (this.inBounds(newCoords[0], newCoords[1])) { if (frontDistance[newCoords[1]][newCoords[0]] == counter) { around = true break } } } if (around) { // console.log("Incrementing it to: " + (counter + 1)) frontDistance[y][x] = counter + 1 } } } } var emptySquares = this.countValues(frontDistance, -1) if (counter > 100) { emptySquares = 0 more = false } counter++ } // console.log(frontDistance) return [more, frontDistance] } Bot.prototype.findLowestHeight = function(frontDistance, map) { lowestHeight = [9999, [-1, -1], [-1, -1]] for (var y = 0; y < map.length; y++) { for (var x = 0; x < map[y].length; x++) { var mapValue = map[y][x][0] var mapType = map[y][x][1] if (mapValue > 1 && (mapType == "friendly" || mapType == "friendlyGeneral")) { if (frontDistance[y][x] < lowestHeight[0]) { var neighbour = [-1, -1] for (var i = 0; i < this.movementOptions.length; i++) { var newCoords = [x + this.movementOptions[i][0], y + this.movementOptions[i][1]] if (this.inBounds(newCoords[0], newCoords[1])) { // console.log(newCoords) // console.log(frontDistance[newCoords[1]][newCoords[0]]) if (frontDistance[newCoords[1]][newCoords[0]] == frontDistance[y][x] - 1) { if (map[newCoords[1]][newCoords[0]][1] == "enemy" || map[newCoords[1]][newCoords[0]][1] == "enemyGeneral") { if (map[y][x][0] - 1 > map[newCoords[1]][newCoords[0]][0]) { // console.log("Setting Neighbour") neighbour = newCoords } } else { neighbour = newCoords } } } } if (!this.arrayEquals(neighbour, [-1, -1])) { lowestHeight = [frontDistance[y][x], [x, y], neighbour] } } } } } return lowestHeight } // TODO: IMPLEMENT Bot.prototype.collectAround = function(x, y) { console.log("Collecting around: " + x + ", " + y) } Bot.prototype.toPoint = function(x, y) { return y * this.width + x } Bot.prototype.toXY = function(point) { return [point % this.width, Math.floor(point / this.width)] } Bot.prototype.toGeneral = function(x, y) { x = x - this.ourGeneral[0] } Bot.prototype.inBounds = function(x, y) { if (x < 0 || y < 0 || x >= this.width || y >= this.height) { return false } return true } Bot.prototype.randomID = function() { return (Math.random().toString(36)+'00000000000000000').slice(2, 11) } Bot.prototype.randomName = function() { return generate().spaced.replace(/\w\S*/g, function(x){return x.charAt(0).toUpperCase() + x.substr(1).toLowerCase()}) } Bot.prototype.add = function(a, b) { return a + b } Bot.prototype.arrayEquals = function(a, b) { if (a === b) return true if (a == null || b == null) return false if (a.length != b.length) return false for (var i = 0; i < a.length; ++i) { if (a[i] !== b[i]) return false } return true } Bot.prototype.countValues = function(array, value) { return array.map(function(row) { return row.reduce(function(n, val) { return n + (val == value) }, 0) }).reduce(this.add, 0) } Bot.prototype.shuffle = function(array) { var currentIndex = array.length, temporaryValue, randomIndex while (0 !== currentIndex) { randomIndex = Math.floor(Math.random() * currentIndex) currentIndex -= 1 temporaryValue = array[currentIndex] array[currentIndex] = array[randomIndex] array[randomIndex] = temporaryValue } return array } module.exports = new Bot()