From 233160e17ff451e52621b10d5d00d99e7800c2db Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Sun, 1 Jul 2018 21:12:11 +0200 Subject: Interactor --- interactor/game.js | 345 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 interactor/game.js (limited to 'interactor/game.js') 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(); +}); -- cgit v1.2.3-54-g00ecf