From 447bdb63e3b5aa71dbee8648e7d1ed51b8ee915f Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Tue, 28 Aug 2018 01:22:34 +0200 Subject: Initial --- .gitignore | 4 ++ .gitmodules | 3 ++ beta-decay.github.io | 1 + package-lock.json | 34 +++++++++++++ package.json | 15 ++++++ run_game.js | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ run_tour.js | 122 ++++++++++++++++++++++++++++++++++++++++++++ tour_worker.js | 52 +++++++++++++++++++ 8 files changed, 371 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 160000 beta-decay.github.io create mode 100644 package-lock.json create mode 100644 package.json create mode 100755 run_game.js create mode 100755 run_tour.js create mode 100644 tour_worker.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e19765 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +logs_dir +output.txt +seeds.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fc1e638 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "beta-decay.github.io"] + path = beta-decay.github.io + url = https://github.com/beta-decay/beta-decay.github.io diff --git a/beta-decay.github.io b/beta-decay.github.io new file mode 160000 index 0000000..dd66143 --- /dev/null +++ b/beta-decay.github.io @@ -0,0 +1 @@ +Subproject commit dd661437e864ca5946bc066175d0792a0bc92a2e diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4ca99b6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,34 @@ +{ + "name": "tour", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "nan": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", + "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" + }, + "xorshift128plus": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/xorshift128plus/-/xorshift128plus-0.0.1.tgz", + "integrity": "sha1-j+z5WZwWD2RKnyMfKWOPwFaRdUc=", + "requires": { + "nan": "^2.2.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a055e78 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "tour", + "version": "1.0.0", + "description": "", + "main": "run_tour.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Tom Smeding (https://tomsmeding.com)", + "license": "MIT", + "dependencies": { + "mkdirp": "^0.5.1", + "xorshift128plus": "0.0.1" + } +} diff --git a/run_game.js b/run_game.js new file mode 100755 index 0000000..20fd8c0 --- /dev/null +++ b/run_game.js @@ -0,0 +1,140 @@ +#!/usr/bin/env node + +if (process.argv.length != 4) { + console.log("Usage: ./run_game.js "); + console.log("Seed needs to be a 32-digit hex number"); + process.exit(1); +} + +process.__state = {}; +process.__state.seed = process.argv[2]; +process.__state.num_turns = +process.argv[3]; + +// -------------------- SHIMS -------------------- + +window = {}; +localStorage = (function() { + const backing = new Map(); + + const getItem = function(key) { + if (backing.has(key)) return backing.get(key); + else return null; + }; + const setItem = function(key, value) { + backing.set(key, value + ""); + }; + const removeItem = function(key) { + backing.delete(key); + }; + const clear = function() { + backing.clear(); + }; + + const proxy = new Proxy(backing, { + get: function(obj, prop) { + if (prop == "length") return backing.size; + if (prop == "key") throw new Error("localStorage.key() not supported"); + if (prop == "getItem") return getItem; + if (prop == "setItem") return setItem; + if (prop == "removeItem") return removeItem; + if (prop == "clear") return clear; + + return getItem(prop); + }, + set: function(obj, prop, value) { + setItem(prop, value); + return true; + } + }); + + return proxy; +})(); + +window.localStorage = localStorage; + +document = { + addEventListener: function(evname) { + if (evname == "DOMContentLoaded") {} + else { + console.log("Foreign addEventListener call!"); + process.exit(1); + } + }, + getElementById: function(id) { + // console.log("gEBI(" + id + ")"); + if (id == "gameInput") return {value: process.__state.num_turns}; + if (id == "fpsInput") return {value: Infinity}; + return {}; + } +}; + + +// -------------------- MAKE MATH.RANDOM REPEATABLE -------------------- + +Math.random = (function() { + const xorshift128plus = require("xorshift128plus"); + const rng = xorshift128plus.fromHex(process.__state.seed); + + return function() { + return rng.next(); + }; +})(); + + +// -------------------- LOAD BASE CONTROLLER -------------------- + +for (const file of [ + "./beta-decay.github.io/art_attack/botData.js", + "./beta-decay.github.io/art_attack/controller.js"]) { + eval(require("fs").readFileSync(file).toString()); +} + + +// -------------------- REMOVE ANNOYING ENTRIES -------------------- + +for (let i = 0; i < botData.length; i++) { + if (botData[i].name == "Muncher") { + botData.splice(i, 1); + i--; + } +} + + +// -------------------- PATCH UP THINGS -------------------- + +updateBoard = function() { + console.log("-- TURN: " + turnNumber + " --"); +}; +drawGrid = function() {}; +drawBots = function() {}; +colourGrid = function() {}; +findWinner = function() { + const arr = findArea(); + for (let i = 0; i < arr.length; i++) { + if (arr[i] === undefined) arr[i] = 0; + } + + /* const winners = []; + const maxScore = Math.max.apply(Math, arr); + + for (let i = 0; i < arr.length; i++) { + if (arr[i] == maxScore && !botData[i].eliminated) { + winners.push(botData[i].name); + } + } + + console.log(arr); + console.log(JSON.stringify(winners)); */ + + for (let i = 0; i < arr.length; i++) { + arr[i] = [botData[i].name, arr[i]]; + } + + console.log(JSON.stringify(arr)); +}; + + +// -------------------- RUN THE GAME -------------------- + +initialise(); +runGame(); diff --git a/run_tour.js b/run_tour.js new file mode 100755 index 0000000..ef6492f --- /dev/null +++ b/run_tour.js @@ -0,0 +1,122 @@ +#!/usr/bin/env node + +if (process.argv.length != 7) { + console.log("Usage: ./run_tour.js "); + console.log(" should contain a 32-hex-digit seed on each line. One game will be run for each seed. The games will all have turns."); + console.log("The tournament will be run using parallel workers.") + console.log("Output results will be written to .") + console.log("Game log files will be written to the directory ."); + process.exit(1); +} + +const fs = require("fs"); +const child_process = require("child_process"); +const mkdirp = require("mkdirp"); + +const seed_file = process.argv[2]; +const num_turns = +process.argv[3]; +const num_workers = +process.argv[4]; +const output_file = process.argv[5]; +const logs_dir = process.argv[6]; + +mkdirp.sync(logs_dir); + +const output_stream = fs.createWriteStream(output_file); + +const seeds = + fs.readFileSync(seed_file).toString() + .split("\n").filter(s => s.length > 0); + + +console.log(seeds.length + " games to play using " + num_workers + " workers."); + +function register_result(result) { + output_stream.write(result + "\n"); + + const seed = result.split(" ", 1)[0]; + console.log("Finished game with seed " + seed); +} + +const workers = []; +const current_seed = new Array(num_workers).fill(null); +const start_at = new Array(num_workers).fill(null); + +function distribute_work() { + if (seeds.length == 0) return; + + for (let i = 0; i < num_workers; i++) { + if (current_seed[i] == null) { + const seed = seeds.shift(); + current_seed[i] = seed; + start_at[i] = new Date(); + workers[i].stdin.write(seed + "\n"); + + console.log("Started game with seed " + seed); + } + } +} + +function check_finished() { + for (let i = 0; i < num_workers; i++) { + if (current_seed[i] != null) return false; + } + + console.log("\nFINISHED! Shutting down..."); + + output_stream.end(); + for (const worker of workers) { + worker.stdin.end(); + } + + console.log("All done."); + setTimeout(() => process.exit(0), 500); +} + +function spawn_worker(index) { + const worker = child_process.spawn( + "node", + ["tour_worker.js", num_turns.toString(), logs_dir], + { stdio: ["pipe", "pipe", "inherit"] } + ); + + let buffer = ""; + worker.stdout.on("data", (data) => { + buffer += data; + let idx = buffer.indexOf("\n"); + while (idx != -1) { + const line = buffer.slice(0, idx); + buffer = buffer.slice(idx + 1); + idx = buffer.indexOf("\n"); + + register_result(line); + current_seed[index] = null; + start_at[index] = null; + distribute_work(); + if (check_finished()) return; + } + }); + + return worker; +} + +for (let i = 0; i < num_workers; i++) { + workers.push(spawn_worker(workers.length)); +} + +distribute_work(); + +// Check for stuck games once in a while +setInterval(function() { + const max_game_time = 1350 * num_turns; + const now = new Date(); + + for (let i = 0; i < num_workers; i++) { + if (current_seed[i] != null && now - start_at[i] > max_game_time) { + console.log("Game for seed " + current_seed[i] + " is taking too long, KILLING!"); + workers[i].kill("SIGKILL"); + + console.log("Spawning new worker and hoping everything's fine..."); + workers[i] = spawn_worker(i); + } + } +}, 60 * 1000); diff --git a/tour_worker.js b/tour_worker.js new file mode 100644 index 0000000..2516375 --- /dev/null +++ b/tour_worker.js @@ -0,0 +1,52 @@ +#!/usr/bin/env node + +if (process.argv.length != 4) { + console.log("Usage: ./tour_worker.js "); + console.log("Runs a game with each of the seeds given on stdin with the specified number of turns."); + console.log("Game logs will be written to hopefully uniquely named files in the pre-existing directory ."); + console.log("Writes results to stdout, in the format 'seed result', where is the game result."); + console.log("Quits on EOF on stdin."); + process.exit(1); +} + +const fs = require("fs"); +const child_process = require("child_process"); + +const num_turns = +process.argv[2]; +const logs_dir = process.argv[3]; + +let buffer = ""; +process.stdin.on("data", function(data) { + buffer += data; + let idx = buffer.indexOf("\n"); + while (idx != -1) { + const line = buffer.slice(0, idx); + buffer = buffer.slice(idx + 1); + idx = buffer.indexOf("\n"); + + handle_line(line); + } +}); + +function handle_line(seed) { + const logfile = logs_dir + "/" + seed + "-" + new Date().getTime() + ".txt"; + const file = fs.openSync(logfile, "w"); + + const proc = child_process.spawn( + "node", + ["run_game.js", seed, num_turns + ""], + {stdio: ["inherit", file, "inherit"]} + ); + fs.closeSync(file); + + proc.on("exit", function(code, signal) { + if (code !== 0) { + console.log(seed + " crash"); + } else { + let result = fs.readFileSync(logfile).toString().trim().split("\n"); + result = result[result.length - 1]; + + console.log(seed + " " + result); + } + }); +} -- cgit v1.2.3-54-g00ecf