diff --git a/client-modes/bot-mode.js b/client-modes/bot-mode.js index ebf7945..583b191 100644 --- a/client-modes/bot-mode.js +++ b/client-modes/bot-mode.js @@ -3,6 +3,16 @@ if (process.argv.length < 3) { process.exit(1); } +var oldlog = console.log; +console.log = function(msg) { + return oldlog('[' + new Date() + '] ' + msg); +} + +//TODO: add a land claiming algo (with coefficient parameters) +//TODO: add weight to the max land area and last land area, and also the number +//of kills +//TODO: genetic gene pooling + var core = require("../game-core"); var client = require("../client"); @@ -15,27 +25,62 @@ var THRESHOLD = 10; var startFrame = -1; var endFrame = -1; -var coeffs = [1,3,1,1,3]; -var grid, others, user; +var coeffs = [1, -3, 1, -1, -3, 5, 4]; +var grid, others, user, playerPortion; var DIST_TYPES = { land: { check: function(loc) { return grid.get(loc.row, loc.col) === user; }, coeff: function() {return coeffs[0];} }, tail: { check: function(loc) {return tail(user, loc)}, - coeff: function() {return -coeffs[1];} + coeff: function() {return coeffs[1];} }, oTail: { check: foundProto(tail), coeff: function() {return AGGRESSIVE * coeffs[2];} }, other: { check: foundProto(function(other, loc) { return other.row === this.row && other.col === this.col; }), - coeff: function() {return -(1 - AGGRESSIVE) * coeffs[3];} + coeff: function() {return (1 - AGGRESSIVE) * coeffs[3];} }, edge: { check: function(loc) {return loc.row <= 1 || loc.col <= 1 || loc.row >= GRID_SIZE - 1 || loc.col >= GRID_SIZE - 1}, - coeff: function() {return -coeffs[4];} + coeff: function() {return coeffs[4];} } }; +function generateLandDirections() { + function mod(x) { + x %= 4; + if (x < 0) { + x += 4; + } + return x; + } + + var breadth = Math.floor(Math.random() * coeffs[5]) + 1; + var spread = Math.floor(Math.random() * coeffs[6]) + 1; + var extra = Math.floor(Math.random() * 2) + 1; + + var ccw = Math.floor(Math.random() * 2) * 2 - 1; + + var dir = user.currentHeading; + var turns = [dir, mod(dir + ccw), mod(dir + ccw * 2), mod(dir + ccw * 3)]; + var lengths = [breadth, spread, breadth + extra, spread]; + + var moves = []; + for (var i = 0; i < turns.length; i++) { + for (var j = 0; j < lengths[i]; j++) { + moves.push(turns[i]); + } + } +} + +var LAND_CLAIMS = { + rectDims: function() { + var + }, rectSpread: function() { + + } +} + function foundProto(func) { return function(loc) { return others.some(function(other) { @@ -145,8 +190,74 @@ function printGrid() { console.log(str); } +function update(frame) { + if (startFrame == -1) { + startFrame = frame; + } + endFrame = frame; + + if (frame % 6 == 1) { + grid = client.grid; + others = client.getOthers(); + + //printGrid(); + + var weights = [0, 0, 0, 0]; + for (var d of [3, 0, 1]) { + var weight = 0; + + d = (d + user.currentHeading) % 4; + distWeights = traverseGrid(d); + + var str = d + ": " + for (var distType in DIST_TYPES) { + var point = distWeights[distType] * DIST_TYPES[distType].coeff(); + weight += point; + str += distType + ": " + point + ", "; + } + //console.log(str); + weights[d] = weight; + } + + var low = Math.min(0, Math.min.apply(this, weights)); + var total = 0; + + weights[(user.currentHeading + 2) % 4] = low; + for (var i = 0; i < weights.length; i++) { + weights[i] -= low * (1 + Math.random()); + total += weights[i]; + } + + if (total == 0) { + for (var d of [-1, 0, 1]) { + d = (d + user.currentHeading) % 4; + while (d < 0) d += 4; + weights[d] = 1; + total++; + } + } + + //console.log(weights) + + //Choose a random direction from the weighted list + var choice = Math.random() * total; + var d = 0; + while (choice > weights[d]) { + choice -= weights[d++]; + } + client.changeHeading(d); + } +} + +function calcFavorability(params) { + return params.portion + params.kills * 50 + params.survival / 100; +} + client.allowAnimation = false; client.renderer = { + addPlayer: function(player) { + playerPortion[player.num] = 0; + }, disconnect: function() { var dt = (endFrame - startFrame); startFrame = -1; @@ -155,75 +266,23 @@ client.renderer = { console.log("I killed " + client.kills + " player(s)."); console.log("Coefficients: " + coeffs) - var mutation = Math.min(10, Math.pow(2, -dt / 150)); + var mutation = Math.min(10, Math.pow(2, calcFavorability(params))); for (var i = 0; i < coeffs.length; i++) { coeffs[i] += Math.random() * mutation * 2 - mutation; } connect(); }, - + removePlayer: function(player) { + delete playerPortion[player.num]; + }, setUser: function(u) { user = u; }, - - update: function(frame) { - if (startFrame == -1) { - startFrame = frame; - } - endFrame = frame; - - if (frame % 6 == 1) { - grid = client.grid; - others = client.getOthers(); - - //printGrid(); - - var weights = [0, 0, 0, 0]; - for (var d of [3, 0, 1]) { - var weight = 0; - - d = (d + user.currentHeading) % 4; - distWeights = traverseGrid(d); - - var str = d + ": " - for (var distType in DIST_TYPES) { - var point = distWeights[distType] * DIST_TYPES[distType].coeff(); - weight += point; - str += distType + ": " + point + ", "; - } - //console.log(str); - weights[d] = weight; - } - - var low = Math.min(0, Math.min.apply(this, weights)); - var total = 0; - - weights[(user.currentHeading + 2) % 4] = low; - for (var i = 0; i < weights.length; i++) { - weights[i] -= low * (1 + Math.random()); - total += weights[i]; - } - - if (total == 0) { - for (var d of [-1, 0, 1]) { - d = (d + user.currentHeading) % 4; - while (d < 0) d += 4; - weights[d] = 1; - total++; - } - } - - //console.log(weights) - - //Choose a random direction from the weighted list - var choice = Math.random() * total; - var d = 0; - while (choice > weights[d]) { - choice -= weights[d++]; - } - client.changeHeading(d); - } + update: update, + updateGrid: function(row, col, before, after) { + before && playerPortion[before.num]--; + after && playerPortion[after.num]++; } }; diff --git a/client-modes/paper-io-bot-mode.js b/client-modes/paper-io-bot-mode.js new file mode 100644 index 0000000..373c438 --- /dev/null +++ b/client-modes/paper-io-bot-mode.js @@ -0,0 +1,243 @@ +if (process.argv.length < 3) { + console.log("Usage: node game-client-bot.js []") + process.exit(1); +} + +var oldlog = console.log; +console.log = function(msg) { + return oldlog('[' + new Date() + '] ' + msg); +} + + +var core = require("../game-core"); +var client = require("../client"); + +var GRID_SIZE = core.GRID_SIZE; +var CELL_WIDTH = core.CELL_WIDTH; +var MOVES = [[-1, 0], [0, 1], [1, 0], [0, -1]] + +var THRESHOLD = 10; + +var startFrame = -1; +var endFrame = -1; +var grid, others, user, playerPortion = {}, claim = []; + +function mod(x) { + x %= 4; + if (x < 0) { + x += 4; + } + return x; +} + +function connect() { + client.connectGame(process.argv[2], process.argv[3] || '[PAPER-IO-BOT]', function(success, msg) { + if (!success) { + setTimeout(connect, 1000); + } + }); +} + +function Loc(row, col) { + if (this.constructor != Loc) { + return new Loc(row, col); + } + + this.row = row; + this.col = col; +} + +function update(frame) { + if (startFrame == -1) { + startFrame = frame; + } + endFrame = frame; + + if (frame % 6 == 1) { + grid = client.grid; + others = client.getOthers(); + + //Note: the code below isn't really my own code. This code is in fact the + //approximate algorithm used by the paper.io game. It has been modified from + //the original code (i.e. deobfuscating) and made more efficient in some + //areas, otherwise, the original logic is about the same. + var row = user.row, col = user.col, dir = user.currentHeading; + var thres = (1 + 2 * Math.random()) * .01 * GRID_SIZE * GRID_SIZE; + + if (row < 0 || col < 0 || row >= GRID_SIZE || col >= GRID_SIZE) { + return; + } + + if (grid.get(row, col) === user) { + //When we are inside our territory + weights = [25, 25, 25, 25]; + weights[dir] = 100; + weights[mod(dir + 2)] = -9999; + + for (var nd = 0; nd < 4; nd++) { + for (var S = 1; S < 20; S++) { + var nr = MOVES[nd][0] * S + row; + var nc = MOVES[nd][1] * S + col; + + if (nr < 0 || nc < 0 || nr >= GRID_SIZE || nc >= GRID_SIZE) { + if (S > 1) { + weights[nd]--; + } else { + weights[nd] = -9999; + } + } else { + if (grid.get(nr, nc) !== user) { + weights[nd]--; + } + + var tailed = undefined; + for (var o of others) { + if (o.tail.hitsTail(new Loc(nr, nc))) { + tailed = o; + break; + } + } + + if (tailed) { + if (o.name.indexOf("PAPER") != -1) { //Don't really try to kill our own kind + weights[nd] += 3 * (30 - S); + } else { + weights[nd] += 30 * (30 - S); + } + } + } + } + } + + //View a selection of choices based on the weights we computed + var choices = []; + for (var d = 0; d < 4; d++) { + for (var S = 1; S < weights[d]; S++) { + choices.push(d); + } + } + + if (choices.length === 0) { + choices.push(dir); + } + + dir = choices[Math.floor(Math.random() * choices.length)]; + } else if (playerPortion[user.num] < thres) { + //Claim some land if we are relatively tiny and have little to risk. + if (claim.length === 0) { + var breadth = 4 * Math.random() + 2; + var length = 4 * Math.random() + 2; + var ccw = 2 * Math.floor(2 * Math.random()) - 1; + + turns = [dir, mod(dir + ccw), mod(dir + ccw * 2), mod(dir + ccw * 3)]; + lengths = [breadth, length, breadth + 2 * Math.random() + 1, length]; + + for (var i = 0; i < turns.length; i++) { + for (var j = 0; j < lengths[i]; j++) { + claim.push(turns[i]); + } + } + } + + if (claim.length !== 0) { + dir = claim.shift(); + } + } else { + //We are playing a little bit more cautious when we are outside and have a + //lot of land + weights = [5, 5, 5, 5]; + weights[dir] = 50; + weights[mod(dir + 2)] = -9999; + + for (var nd = 0; nd < 4; nd++) { + for (var S = 1; S < 20; S++) { + var nr = MOVES[nd][0] * S + row; + var nc = MOVES[nd][1] * S + col; + + if (nr < 0 || nc < 0 || nr >= GRID_SIZE || nc >= GRID_SIZE) { + if (S > 1) { + weights[nd]--; + } else { + weights[nd] = -9999; + } + } else { + if (user.tail.hitsTail(new Loc(nr, nc))) { + if (S > 1) { + weights[nd] -= 50 - S; + } else { + weights[nd] = -9999; + } + } + + if (grid.get(nr, nc) === user) { + weights[nd] += 10 + S; + } + + var tailed = undefined; + for (var o of others) { + if (o.tail.hitsTail(new Loc(nr, nc))) { + tailed = o; + break; + } + } + + if (tailed) { + if (o.name.indexOf("PAPER") != -1) { //Don't really try to kill our own kind + weights[nd] += 3 * (30 - S); + } else { + weights[nd] += 30 * (30 - S); + } + } + } + } + } + + //View a selection of choices based on the weights we computed + var choices = []; + for (var d = 0; d < 4; d++) { + for (var S = 1; S < weights[d]; S++) { + choices.push(d); + } + } + + if (choices.length === 0) { + choices.push(dir); + } + + dir = choices[Math.floor(Math.random() * choices.length)]; + } + client.changeHeading(dir); + } +} + +function calcFavorability(params) { + return params.portion + params.kills * 50 + params.survival / 100; +} + +client.allowAnimation = false; +client.renderer = { + addPlayer: function(player) { + playerPortion[player.num] = 0; + }, + disconnect: function() { + var dt = (endFrame - startFrame); + startFrame = -1; + + console.log("I died... (survived for " + dt + " frames.)"); + console.log("I killed " + client.kills + " player(s)."); + connect(); + }, + removePlayer: function(player) { + delete playerPortion[player.num]; + }, + setUser: function(u) { + user = u; + }, + update: update, + updateGrid: function(row, col, before, after) { + before && playerPortion[before.num]--; + after && playerPortion[after.num]++; + } +}; + +connect(); diff --git a/client-modes/user-mode.js b/client-modes/user-mode.js index f5b512c..367c152 100644 --- a/client-modes/user-mode.js +++ b/client-modes/user-mode.js @@ -17,7 +17,6 @@ var MIN_BAR_WIDTH = 65; var BAR_HEIGHT = SHADOW_OFFSET + CELL_WIDTH; var BAR_WIDTH = 400; - var canvas, canvasWidth, canvasHeight, gameWidth, gameHeight, ctx, offctx, offscreenCanvas; $(function () {