diff options
Diffstat (limited to 'interactor')
-rw-r--r-- | interactor/.gitignore | 1 | ||||
-rw-r--r-- | interactor/board.js | 160 | ||||
-rw-r--r-- | interactor/game.css | 28 | ||||
-rw-r--r-- | interactor/game.html | 25 | ||||
-rw-r--r-- | interactor/game.js | 345 | ||||
-rw-r--r-- | interactor/package-lock.json | 652 | ||||
-rw-r--r-- | interactor/package.json | 16 | ||||
-rwxr-xr-x | interactor/server.js | 85 |
8 files changed, 1312 insertions, 0 deletions
diff --git a/interactor/.gitignore b/interactor/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/interactor/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/interactor/board.js b/interactor/board.js new file mode 100644 index 0000000..35ba93a --- /dev/null +++ b/interactor/board.js @@ -0,0 +1,160 @@ +function colourValue(value) { + return value < 4; +} + +function parsePosition(str) { + if (str.length != 2) return null; + var a = str.toUpperCase().charCodeAt(0), b = str.charCodeAt(1); + if (a < 65 || a > 65+25) return null; + if (b < 48 || b > 48+9) return null; + return N * (N - (b - 48)) + a - 65; +} + +function parseMove(str) { + if (str.length != 5) return null; + if (str[2] != "-" && str[2] != " ") return null; + + var from, to; + from = parsePosition(str.substr(0, 2)); + if (from != null) { + to = parsePosition(str.substr(3, 2)); + if (to != null) { + return {from: from, to: to}; + } + } + + return null; +} + +function stringifyPosition(pos) { + return String.fromCharCode(65 + pos % N) + String.fromCharCode(48 + N - ~~(pos / N)); +} + +function stringifyMove(mv) { + return stringifyPosition(mv.from) + '-' + stringifyPosition(mv.to); +} + +function stringifyMoveExt(mv, captures) { + var s = stringifyMove(mv); + if (captures.length > 0) { + for (var i = 0; i < captures.length; i++) { + s += (i == 0 ? "x" : "/") + stringifyPosition(captures[i]); + } + } + return s; +} + + +function stoneFlankedH(pos, by) { + if (by & (WHITE|KING)) by = WHITE|KING; + return ((board[pos-1] & by) || (pos-1 == BOARDMID && board[BOARDMID] == EMPTY)) && + ((board[pos+1] & by) || (pos+1 == BOARDMID && board[BOARDMID] == EMPTY)); +} + +function stoneFlankedV(pos, by) { + if (by & (WHITE|KING)) by = WHITE|KING; + return ((board[pos-N] & by) || (pos-N == BOARDMID && board[BOARDMID] == EMPTY)) && + ((board[pos+N] & by) || (pos+N == BOARDMID && board[BOARDMID] == EMPTY)); +} + +function kingEncircled(pos) { + if (pos == BOARDMID) { + return board[pos-1] == BLACK && board[pos+1] == BLACK && + board[pos-N] == BLACK && board[pos+N] == BLACK; + } else if (pos == BOARDMID-1) { + return board[pos-1] == BLACK && board[pos-N] == BLACK && board[pos+N] == BLACK; + } else if (pos == BOARDMID+1) { + return board[pos+1] == BLACK && board[pos-N] == BLACK && board[pos+N] == BLACK; + } else if (pos == BOARDMID-N) { + return board[pos-1] == BLACK && board[pos+1] == BLACK && board[pos-N] == BLACK; + } else if (pos == BOARDMID+N) { + return board[pos-1] == BLACK && board[pos+1] == BLACK && board[pos+N] == BLACK; + } else { + var x = pos % N, y = ~~(pos / N); + return (x > 0 && x < N-1 && board[pos-1] == BLACK && board[pos+1] == BLACK) || + (y > 0 && y < N-1 && board[pos-N] == BLACK && board[pos+N] == BLACK); + } +} + +function applyMove(mv) { + board[mv.to] = board[mv.from]; + board[mv.from] = EMPTY; + var i = mv.to; + var x = i % N, y = ~~(i / N); + + var captures = []; + + if (x > 1 && board[i-1] != EMPTY && colourValue(board[i-1]) != colourValue(board[i])) { + if (board[i-1] == KING ? kingEncircled(i-1) : stoneFlankedH(i-1, board[i])) { + board[i-1] = EMPTY; + captures.push(i-1); + } + } + + if (y > 1 && board[i-N] != EMPTY && colourValue(board[i-N]) != colourValue(board[i])) { + if (board[i-N] == KING ? kingEncircled(i-N) : stoneFlankedV(i-N, board[i])) { + board[i-N] = EMPTY; + captures.push(i-N); + } + } + + if (x < N-2 && board[i+1] != EMPTY && colourValue(board[i+1]) != colourValue(board[i])) { + if (board[i+1] == KING ? kingEncircled(i+1) : stoneFlankedH(i+1, board[i])) { + board[i+1] = EMPTY; + captures.push(i+1); + } + } + + if (y < N-2 && board[i+N] != EMPTY && colourValue(board[i+N]) != colourValue(board[i])) { + if (board[i+N] == KING ? kingEncircled(i+N) : stoneFlankedV(i+N, board[i])) { + board[i+N] = EMPTY; + captures.push(i+N); + } + } + + return captures; +} + +function checkWin() { + for (var x = 0; x < N; x++) if (board[x] == KING) return 1; + for (var y = 0; y < N; y++) if (board[N * y] == KING) return 1; + for (var x = 0; x < N; x++) if (board[N * (N-1) + x] == KING) return 1; + for (var y = 0; y < N; y++) if (board[N * y + N-1] == KING) return 1; + + for (var i = 0; i < N * N; i++) { + if (board[i] == KING) return 0; + } + + return -1; +} + +function isValid(mv) { + if (mv.from < 0 || mv.from >= N * N || mv.to < 0 || mv.to >= N * N) return false; + if (mv.from == mv.to) return false; + if (board[mv.from] == EMPTY || board[mv.to] != EMPTY) return false; + + if (mv.to == BOARDMID) return false; + + var x1 = mv.from % N, y1 = ~~(mv.from / N); + var x2 = mv.to % N, y2 = ~~(mv.to / N); + if (x1 != x2 && y1 != y2) return false; + + if (x1 == x2) { + var delta = y2 < y1 ? -1 : 1; + for (var y = y1 + delta; y != y2; y += delta) { + if (board[N * y + x1] != EMPTY) return false; + } + } else { + var delta = x2 < x1 ? -1 : 1; + for (var x = x1 + delta; x != x2; x += delta) { + if (board[N * y1 + x] != EMPTY) return false; + } + } + + return true; +} + +function isValidForPlayer(mv, player) { + var mask = player == 1 ? WHITE|KING : BLACK; + return isValid(mv) && (board[mv.from] & mask) != 0; +} diff --git a/interactor/game.css b/interactor/game.css new file mode 100644 index 0000000..bce55f8 --- /dev/null +++ b/interactor/game.css @@ -0,0 +1,28 @@ +#cvs { + border: 1px #473d32 solid; +} + +.invisible { + display: none; +} + +#serverlog > div { + font-family: monospace; +} + +#movelog_container { + width: 500px; + height: 200px; + overflow-y: scroll; +} + +#movelog { + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + +#movelog td { + border: 1px #000000 solid; + width: 50%; +} diff --git a/interactor/game.html b/interactor/game.html new file mode 100644 index 0000000..20def4f --- /dev/null +++ b/interactor/game.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> +<head> +<title>Hnefatafl interactor</title> +<meta charset="utf-8"> +<script src="/socket.io/socket.io.js"></script> +<script src="/game.js"></script> +<script src="/board.js"></script> +<link rel="stylesheet" href="/game.css"> +</head> +<body> +<canvas id="cvs" width="370" height="370"></canvas> <br> + +<div id="status"></div> + +<input type="button" id="aifirst" class="invisible" value="First move for AI"> + +<div id="serverlog"></div> +<br> + +<div id="movelog_container"> + <table id="movelog"><tbody id="movelog_tbody"></tbody></table> +</div> +</body> +</html> diff --git a/interactor/game.js b/interactor/game.js new file mode 100644 index 0000000..85674b1 --- /dev/null +++ b/interactor/game.js @@ -0,0 +1,345 @@ +"use strict"; + +var N = 9; +var EMPTY = 0, WHITE = 1, KING = 2, BLACK = 4; +var BOARDMID = N * ~~(N/2) + ~~(N/2); + +var cvs, ctx; +var cellSize = 40, cvsSize = N * cellSize + N+1; +var socket; +var playing = false, onturn = null, aiplayer = null; +var lastMove = null; + +var board; + +function rotateIndex(index, nturns) { + for (var i = 0; i < nturns % 4; i++) { + var x = index % N, y = ~~(index / N); + index = N * x + N-1 - y; + } + return index; +} + +function makeBoard() { + var bd = new Array(N * N); + for (var i = 0; i < N * N; i++) bd[i] = EMPTY; + + for (var i = 0; i < 4; i++) { + bd[rotateIndex(~~(N/2) - 1, i)] = BLACK; + bd[rotateIndex(~~(N/2) + 0, i)] = BLACK; + bd[rotateIndex(~~(N/2) + 1, i)] = BLACK; + bd[rotateIndex(N + ~~(N/2), i)] = BLACK; + + bd[rotateIndex(BOARDMID - 2 * N, i)] = WHITE; + bd[rotateIndex(BOARDMID - 1 * N, i)] = WHITE; + } + + bd[BOARDMID] = KING; + return bd; +} + +function setupBoard() { + board = makeBoard(); +} + +function cellX(i) {return (cellSize + 1) * (i % N);} +function cellY(i) {return (cellSize + 1) * ~~(i / N);} +function cellMidX(i) {return (cellSize + 1) * (i % N + 0.5);} +function cellMidY(i) {return (cellSize + 1) * (~~(i / N) + 0.5);} + +function redraw() { + ctx.fillStyle = "#e0c29f"; + ctx.fillRect(0, 0, cvsSize, cvsSize); + + ctx.fillStyle = "#c6ac8d"; + ctx.fillRect(cellX(BOARDMID), cellY(BOARDMID), cellSize, cellSize); + + ctx.strokeStyle = "#7a6a58"; + ctx.beginPath(); + for (var i = 1; i < N; i++) { + ctx.moveTo(0, cellY(N * i) - 0.5); + ctx.lineTo(cvsSize, cellY(N * i) - 0.5); + + ctx.moveTo(cellX(i) - 0.5, 0); + ctx.lineTo(cellX(i) - 0.5, cvsSize); + } + ctx.stroke(); + + var drawers = [ + function() {}, + function(px, py) { drawCircle(px, py, "#ffffff"); }, + function(px, py) { drawKing(px, py); }, + function() {}, + function(px, py) { drawCircle(px, py, "#000000"); }, + ]; + + for (var i = 0; i < N * N; i++) { + drawers[board[i]](cellMidX(i), cellMidY(i)); + } + + if (lastMove != null) { + var thickness = 3; + for (var i = 0; i < thickness; i++) { + var alpha = (thickness - i) / thickness; + alpha *= alpha; + ctx.strokeStyle = "rgba(165, 165, 247, " + alpha + ")"; + ctx.beginPath(); + var sz = cellSize - 2*i - 1; + ctx.rect(cellX(lastMove.from) + i + 0.5, cellY(lastMove.from) + i + 0.5, sz, sz); + ctx.rect(cellX(lastMove.to) + i + 0.5, cellY(lastMove.to) + i + 0.5, sz, sz); + ctx.stroke(); + } + } +} + +function drawCircle(x, y, clr) { + ctx.fillStyle = clr; + ctx.beginPath(); + ctx.arc(x, y, cellSize * 0.3, 0, 2 * Math.PI); + ctx.fill(); +} + +function drawKing(x, y) { + ctx.fillStyle = "#ffffff"; + ctx.strokeStyle = "#7a6a58"; + ctx.beginPath(); + + ctx.moveTo(x - cellSize * 0.25, y + cellSize * 0.25); + ctx.lineTo(x - cellSize * 0.3, y - cellSize * 0.25); + ctx.lineTo(x - cellSize * 0.15, y - cellSize * 0.05); + ctx.lineTo(x, y - cellSize * 0.25); + ctx.lineTo(x + cellSize * 0.15, y - cellSize * 0.05); + ctx.lineTo(x + cellSize * 0.3, y - cellSize * 0.25); + ctx.lineTo(x + cellSize * 0.25, y + cellSize * 0.25); + ctx.closePath(); + + ctx.stroke(); + ctx.fill(); +} + +function displayStatus(msg) { + document.getElementById("status").innerHTML = msg; +} + +function addMoveLog(player, mv, captures) { + if (captures == undefined) captures = []; + + var log = document.getElementById("movelog_tbody"); + var tr = document.createElement("tr"); + + var who = player == 1 ? "White" : "Black"; + if (player == aiplayer) who += " (AI)"; + + var td = document.createElement("td"); + td.appendChild(document.createTextNode(who)); + tr.appendChild(td); + + var desc = stringifyMoveExt(mv, captures); + if (checkWin() != 0) desc += "++"; + + td = document.createElement("td"); + td.appendChild(document.createTextNode(desc)); + tr.appendChild(td); + + log.appendChild(tr); + + tr.scrollIntoView(false); +} + +function addServerLog(msg) { + var div = document.createElement("div"); + div.appendChild(document.createTextNode(msg)); + document.getElementById("serverlog").appendChild(div); +} + +function openGame() { + socket = io(); + socket.on("message", function(msg) { + addServerLog(msg); + }); + + socket.on("open", function() { + playing = true; + onturn = -1; + aiplayer = null; + lastMove = null; + + document.getElementById("aifirst").classList.remove("invisible"); + + setupBoard(); + redraw(); + + addServerLog("Game opened."); + displayStatus("Make first move, or let the AI take the first move."); + }); + + socket.on("close", function() { + playing = false; + addServerLog("Game closed."); + displayStatus("Game closed."); + }); + + socket.on("line", function(line) { + var mv = parseMove(line); + if (mv != null) { + processMove(mv); + displayStatus("Your turn."); + } else { + var msg = "Invalid move received from server:\n" + line; + addServerLog(msg); + alert(msg); + } + }); + + socket.emit("open"); +} + +function humanMove(mv) { + if (aiplayer != null && onturn != -aiplayer) return; + if (!isValidForPlayer(mv, onturn)) { + displayStatus("That's an invalid move."); + return; + } + + if (aiplayer == null) { + aiplayer = -onturn; + } + + processMove(mv); + + socket.emit("line", stringifyMove(mv)); + displayStatus("Waiting for AI..."); +} + +function processMove(mv) { + var captures = applyMove(mv); + addMoveLog(onturn, mv, captures); + onturn = -onturn; + lastMove = mv; + redraw(); + + var win = checkWin(); + var msg = win == 1 ? "White won." : win == -1 ? "Black won." : null; + if (msg != null) { + var suffix = win != aiplayer ? "Congratulations!" : "Too bad."; + displayStatus(msg + " " + suffix); + addServerLog(msg); + } +} + +function hookListeners() { + var startx = null, starty = null, endx = null, endy = null; + var starti = null, endi = null; + + function calcI(x, y) { + x = Math.round(x / (cellSize + 1) - 0.5); + y = Math.round(y / (cellSize + 1) - 0.5); + if (x >= N) x = N - 1; + if (y >= N) y = N - 1; + return N * y + x; + } + + function updateStart(ev) { + var bbox = cvs.getBoundingClientRect(); + startx = ev.clientX - bbox.left; + starty = ev.clientY - bbox.top; + starti = calcI(startx, starty); + } + + function updateEnd(ev) { + var bbox = cvs.getBoundingClientRect(); + endx = ev.clientX - bbox.left; + endy = ev.clientY - bbox.top; + endi = calcI(endx, endy); + if (endi % N != starti % N && ~~(endi / N) != ~~(starti / N)) { + var dx = Math.abs(endi % N - starti % N); + var dy = Math.abs(~~(endi / N) - ~~(starti / N)); + if (dx >= dy) endi = N * ~~(starti / N) + endi % N; + else endi = N * ~~(endi / N) + starti % N; + } + } + + function reset() { + startx = starty = endx = endy = null; + starti = endi = null; + } + + function drawArrow() { + ctx.beginPath(); + ctx.moveTo(-cellSize * 0.2, -cellSize * 0.15); + ctx.lineTo(0, 0); + ctx.lineTo(-cellSize * 0.2, cellSize * 0.15); + ctx.stroke(); + } + + cvs.addEventListener("mousedown", function(ev) { + if (!playing || (aiplayer != null && onturn == aiplayer)) return; + updateStart(ev); + if (board[starti] == EMPTY || (onturn == 1 ? board[starti] & BLACK : board[starti] & (WHITE|KING))) { + reset(); + } + }); + + cvs.addEventListener("mouseleave", function() { + if (startx == null) return; + reset(); + redraw(); + }); + + cvs.addEventListener("mousemove", function(ev) { + if (startx == null) return; + updateEnd(ev); + redraw(); + + ctx.strokeStyle = "#ff0000"; + ctx.lineWidth = 2; + + ctx.beginPath(); + var x1 = (starti % N + 0.5) * (cellSize + 1), y1 = (~~(starti / N) + 0.5) * (cellSize + 1); + var x2 = (endi % N + 0.5) * (cellSize + 1), y2 = (~~(endi / N) + 0.5) * (cellSize + 1); + x1 = ~~x1 + 1; y1 = ~~y1 + 1; x2 = ~~x2 + 1; y2 = ~~y2 + 1; + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + + ctx.save(); + ctx.translate(x2, y2); + if (endi % N > starti % N) ctx.rotate(0); + else if (~~(endi / N) > ~~(starti / N)) ctx.rotate(Math.PI / 2); + else if (endi % N < starti % N) ctx.rotate(Math.PI); + else if (~~(endi / N) < ~~(starti / N)) ctx.rotate(Math.PI * 3 / 2); + drawArrow(); + ctx.restore(); + + ctx.lineWidth = 1; + }); + + cvs.addEventListener("mouseup", function(ev) { + if (startx == null) return; + updateEnd(ev); + console.log(starti, endi); + humanMove({from: starti, to: endi}); + reset(); + redraw(); + }); + + + document.getElementById("aifirst").addEventListener("click", function() { + if (aiplayer == null) { // paranoia + aiplayer = -1; + socket.emit("line", "Start"); + displayStatus("Requested first move from AI..."); + } + document.getElementById("aifirst").classList.add("invisible"); + }); +} + +window.addEventListener("load", function() { + cvs = document.getElementById("cvs"); + ctx = cvs.getContext("2d"); + + setupBoard(); + redraw(); + hookListeners(); + openGame(); +}); diff --git a/interactor/package-lock.json b/interactor/package-lock.json new file mode 100644 index 0000000..1718395 --- /dev/null +++ b/interactor/package-lock.json @@ -0,0 +1,652 @@ +{ + "name": "interactor", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", + "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=" + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "blob": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", + "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=" + }, + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "engine.io": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.2.0.tgz", + "integrity": "sha512-mRbgmAtQ4GAlKwuPnnAvXXwdPhEx+jkc0OBCLrXuD/CRvwNK3AxRSnqK4FSqmAMRRHryVJP8TopOvmEaA64fKw==", + "requires": { + "accepts": "~1.3.4", + "base64id": "1.0.0", + "cookie": "0.3.1", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.0", + "ws": "~3.3.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-client": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz", + "integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==", + "requires": { + "component-emitter": "1.2.1", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.1.1", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~3.3.1", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.1.2.tgz", + "integrity": "sha512-dInLFzr80RijZ1rGpx1+56/uFoH7/7InhH3kZt+Ms6hT8tNx3NGW/WNSA/f8As1WkOfkuyb3tnRyuXGxusclMw==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.4", + "has-binary2": "~1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.6.0" + } + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "socket.io": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.1.1.tgz", + "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", + "requires": { + "debug": "~3.1.0", + "engine.io": "~3.2.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.1.1", + "socket.io-parser": "~3.2.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", + "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=" + }, + "socket.io-client": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.1.1.tgz", + "integrity": "sha512-jxnFyhAuFxYfjqIgduQlhzqTcOEQSn+OHKVfAxWaNWa7ecP7xSNk2Dx/3UEsDcY7NcFafxvNvKPmmO7HTwTxGQ==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "engine.io-client": "~3.2.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.2.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "socket.io-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz", + "integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/interactor/package.json b/interactor/package.json new file mode 100644 index 0000000..b0508c6 --- /dev/null +++ b/interactor/package.json @@ -0,0 +1,16 @@ +{ + "name": "interactor", + "version": "0.1.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "Tom Smeding <tom.smeding@gmail.com> (https://tomsmeding.com)", + "license": "MIT", + "dependencies": { + "express": "^4.16.3", + "socket.io": "^2.1.1" + } +} diff --git a/interactor/server.js b/interactor/server.js new file mode 100755 index 0000000..88e9f10 --- /dev/null +++ b/interactor/server.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node +const http = require("http"); +const express = require("express"); +const socketio = require("socket.io"); +const child_process = require("child_process"); + +const PORT = 8080; + +const app = express(); +const server = http.Server(app); +const io = socketio(server); + +// gameid -> {pl: [socket]*{1,2}, started: Bool, nextServe: Int, score: [Int, Int]} +const games = new Map(); + +app.get("/", (req, res) => { + res.sendFile(__dirname + "/game.html"); +}); + +const staticFiles = [ + "/game.css", + "/game.js", + "/board.js", +]; + +app.get(staticFiles, (req, res) => { + res.sendFile(__dirname + req.path); +}); + +io.on("connection", (conn) => { + let proc = null; + + conn.on("open", () => { + console.log("open"); + + proc = child_process.spawn("../main", { + stdio: ["pipe", "pipe", process.stderr] + }); + + conn.emit("open"); + + let lineBuffer = ""; + + proc.stdout.on("data", (data) => { + lineBuffer += data; + let idx; + while ((idx = lineBuffer.indexOf("\n")) != -1) { + conn.emit("line", lineBuffer.slice(0, idx)); + lineBuffer = lineBuffer.slice(idx + 1); + } + }); + + proc.on("close", () => { + if (proc != null) { + console.log("close"); + conn.emit("close"); + proc.kill(); + proc = null; + } + }); + + proc.on("error", (err) => { + if (proc != null) { + console.log("close"); + conn.emit("close"); + conn.emit("message", "The AI process encountered an error."); + proc = null; + } + }); + }); + + conn.on("disconnect", () => { + if (proc != null) { + console.log("close"); + proc.kill(); + proc = null; + } + }); + + conn.on("line", (line) => { + proc.stdin.write(line + "\n"); + }); +}); + +server.listen(PORT, () => console.log("Listening on port " + PORT)); |