From e27329232b83700d63c8fb52af6f4c2eec9a569c Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Wed, 6 Feb 2019 00:25:09 +0100 Subject: [PATCH] Play against computer almost OK: need to fix Board component --- client/package-lock.json | 10 +++++++ client/package.json | 3 ++- client/src/base_rules.js | 15 ++++++----- client/src/components/Board.vue | 18 ++++++++----- client/src/components/Game.vue | 25 +++++++++++++----- client/src/playCompMove.js | 13 +++++----- client/src/utils/alea.js | 2 +- client/src/utils/array.js | 43 +++++++++++++++++-------------- client/src/utils/printDiagram.js | 6 +++-- client/src/variants/Atomic_OLD.js | 3 ++- client/src/variants/Chess960.js | 3 ++- client/src/views/Rules.vue | 19 +++++++------- 12 files changed, 98 insertions(+), 62 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index fe7eae3d..020d47e0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11774,6 +11774,16 @@ "errno": "~0.1.7" } }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", diff --git a/client/package.json b/client/package.json index 7f51be97..62781668 100644 --- a/client/package.json +++ b/client/package.json @@ -24,7 +24,8 @@ "pug-plain-loader": "^1.0.0", "raw-loader": "^1.0.0", "sass-loader": "^7.0.1", - "vue-template-compiler": "^2.5.21" + "vue-template-compiler": "^2.5.21", + "worker-loader": "^2.0.0" }, "eslintConfig": { "root": true, diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 98c27af7..bb2edc43 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -1,7 +1,10 @@ // (Orthodox) Chess rules are defined in ChessRules class. // Variants generally inherit from it, and modify some parts. -class PiPo //Piece+Position +import { ArrayFun } from "@/utils/array"; +import { random, sample, shuffle } from "@/utils/alea"; + +export const PiPo = class PiPo //Piece+Position { // o: {piece[p], color[c], posX[x], posY[y]} constructor(o) @@ -14,7 +17,7 @@ class PiPo //Piece+Position } // TODO: for animation, moves should contains "moving" and "fading" maybe... -class Move +export const Move = class Move { // o: {appear, vanish, [start,] [end,]} // appear,vanish = arrays of PiPo @@ -29,7 +32,7 @@ class Move } // NOTE: x coords = top to bottom; y = left to right (from white player perspective) -class ChessRules +export const ChessRules = class ChessRules { ////////////// // MISC UTILS @@ -236,7 +239,7 @@ class ChessRules // Shuffle pieces on first and last rank for (let c of ["w","b"]) { - let positions = range(8); + let positions = ArrayFun.range(8); // Get random squares for bishops let randIndex = 2 * random(4); @@ -374,7 +377,7 @@ class ChessRules static GetBoard(position) { const rows = position.split("/"); - let board = doubleArray(V.size.x, V.size.y, ""); + let board = ArrayFun.init(V.size.x, V.size.y, ""); for (let i=0; i<rows.length; i++) { let j = 0; @@ -1132,7 +1135,7 @@ class ChessRules let moves1 = this.getAllValidMoves("computer"); // Can I mate in 1 ? (for Magnetic & Extinction) - for (let i of shuffle(range(moves1.length))) + for (let i of shuffle(ArrayFun.range(moves1.length))) { this.play(moves1[i]); let finish = (Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE); diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue index 51632312..32738829 100644 --- a/client/src/components/Board.vue +++ b/client/src/components/Board.vue @@ -1,13 +1,17 @@ <script> // This can work for squared boards (2 or 4 players), with some adaptations (TODO) // TODO: for 3 players, write a "board3.js" + +import { getSquareId, getSquareFromId } from "@/utils/squareId"; +import { ArrayFun } from "@/utils/array"; + export default { name: 'my-board', // Last move cannot be guessed from here, and is required to highlight squares // vr: object to check moves, print board... // mode: HH, HC or analyze // userColor: for mode HH or HC - props: ["vr","lastMove","mode","orientation","userColor"], + props: ["vr","lastMove","mode","orientation","userColor","vname"], data: function () { return { hints: (!localStorage["hints"] ? true : localStorage["hints"] === "1"), @@ -24,10 +28,10 @@ export default { return; const [sizeX,sizeY] = [V.size.x,V.size.y]; // Precompute hints squares to facilitate rendering - let hintSquares = doubleArray(sizeX, sizeY, false); + let hintSquares = ArrayFun.init(sizeX, sizeY, false); this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; }); // Also precompute in-check squares - let incheckSq = doubleArray(sizeX, sizeY, false); + let incheckSq = ArrayFun.init(sizeX, sizeY, false); this.incheck.forEach(sq => { incheckSq[sq[0]][sq[1]] = true; }); const squareWidth = 40; //TODO: compute this const choices = h( @@ -71,7 +75,7 @@ export default { ); // Create board element (+ reserves if needed by variant or mode) const lm = this.lastMove; - const showLight = this.hints && variant.name != "Dark"; + const showLight = this.hints && this.vname != "Dark"; const gameDiv = h( 'div', { @@ -93,7 +97,7 @@ export default { [...Array(sizeY).keys()].map(j => { let cj = (this.orientation=='w' ? j : sizeY-j-1); let elems = []; - if (this.vr.board[ci][cj] != V.EMPTY && (variant.name!="Dark" + if (this.vr.board[ci][cj] != V.EMPTY && (this.vname!="Dark" || this.gameOver || this.mode == "analyze" || this.vr.enlightened[this.userColor][ci][cj])) { @@ -107,7 +111,7 @@ export default { && this.selectedPiece.parentNode.id == "sq-"+ci+"-"+cj, }, attrs: { - src: "/images/pieces/" + + src: "@/assets/images/pieces/" + V.getPpath(this.vr.board[ci][cj]) + ".svg", }, } @@ -139,7 +143,7 @@ export default { 'light-square': (i+j)%2==0, 'dark-square': (i+j)%2==1, [this.bcolor]: true, - 'in-shadow': variant.name=="Dark" && !this.gameOver + 'in-shadow': this.vname=="Dark" && !this.gameOver && this.mode != "analyze" && !this.vr.enlightened[this.userColor][ci][cj], 'highlight': showLight && !!lm && lm.end.x == ci && lm.end.y == cj, diff --git a/client/src/components/Game.vue b/client/src/components/Game.vue index 73c37695..678b914d 100644 --- a/client/src/components/Game.vue +++ b/client/src/components/Game.vue @@ -8,7 +8,7 @@ h3#eogMessage.section {{ endgameMessage }} //Chat(:opponents="opponents" :people="people") Board(:vr="vr" :last-move="lastMove" :mode="mode" :user-color="mycolor" - :orientation="orientation" @play-move="play") + :orientation="orientation" :vname="variant.name" @play-move="play") .button-group button(@click="() => play()") Play button(@click="() => undo()") Undo @@ -46,8 +46,16 @@ import Board from "@/components/Board.vue"; //import Chat from "@/components/Chat.vue"; //import MoveList from "@/components/MoveList.vue"; import { store } from "@/store"; + +import { getSquareId } from "@/utils/squareId"; + +import Worker from 'worker-loader!@/playCompMove'; + export default { name: 'my-game', + components: { + Board, + }, // gameId: to find the game in storage (assumption: it exists) // fen: to start from a FEN without identifiers (analyze mode) // subMode: "auto" (game comp vs comp) or "corr" (correspondance game), @@ -58,7 +66,6 @@ export default { return { st: store.state, // Web worker to play computer moves without freezing interface: - compWorker: new Worker('/javascripts/playCompMove.js'), timeStart: undefined, //time when computer starts thinking vr: null, //VariantRules object, describing the game state + rules endgameMessage: "", @@ -75,6 +82,7 @@ export default { moves: [], //all moves played in current game cursor: -1, //index of the move just played lastMove: null, + compWorker: null, }; }, watch: { @@ -84,6 +92,8 @@ export default { return this.$emit("computer-think"); this.launchGame(); }, + variant: function(newVar) { + }, }, computed: { showMoves: function() { @@ -200,7 +210,7 @@ export default { this.conn.onclose = socketCloseListener; } // Computer moves web worker logic: (TODO: also for observers in HH games ?) - this.compWorker.postMessage(["scripts",this.variant.name]); + this.compWorker = new Worker(); //'/javascripts/playCompMove.js'), this.compWorker.onmessage = e => { this.lockCompThink = true; //to avoid some ghost moves let compMove = e.data; @@ -271,7 +281,8 @@ export default { }, launchGame: async function() { const vModule = await import("@/variants/" + this.variant.name + ".js"); - window.V = tModule.VariantRules; + window.V = vModule.VariantRules; + this.compWorker.postMessage(["scripts",this.variant.name]); if (this.gidOrFen.indexOf('/') >= 0) this.newGameFromFen(this.gidOrFen); else @@ -400,7 +411,7 @@ export default { document.querySelector("#" + getSquareId(move.start) + " > img.piece"); // HACK for animation (with positive translate, image slides "under background") // Possible improvement: just alter squares on the piece's way... - squares = document.getElementsByClassName("board"); + const squares = document.getElementsByClassName("board"); for (let i=0; i<squares.length; i++) { let square = squares.item(i); @@ -453,7 +464,7 @@ export default { this.lastMove = move; if (!move.fen) move.fen = this.vr.getFen(); - if (this.settings.sound == 2) + if (this.st.settings.sound == 2) new Audio("/sounds/move.mp3").play().catch(err => {}); if (this.mode == "human") { @@ -505,7 +516,7 @@ export default { this.vr.undo(move); this.cursor--; this.lastMove = (this.cursor >= 0 ? this.moves[this.cursor] : undefined); - if (this.settings.sound == 2) + if (this.st.settings.sound == 2) new Audio("/sounds/undo.mp3").play().catch(err => {}); this.incheck = this.vr.getCheckSquares(this.vr.turn); if (navigate) diff --git a/client/src/playCompMove.js b/client/src/playCompMove.js index f58f9987..ddaeba38 100644 --- a/client/src/playCompMove.js +++ b/client/src/playCompMove.js @@ -1,20 +1,19 @@ // TODO: https://github.com/webpack-contrib/worker-loader // https://stackoverflow.com/questions/48713072/how-to-get-js-function-into-webworker-via-importscripts // For asynchronous computer move search -onmessage = function(e) + +//self.addEventListener('message', (e) => +onmessage = async function(e) { switch (e.data[0]) { case "scripts": - self.importScripts( - '@/base_rules.js', - '@/utils/array.js', - '@/variants/' + e.data[1] + '.js'); - self.V = eval("VariantRules"); + const vModule = await import("@/variants/" + e.data[1] + ".js"); + self.V = vModule.VariantRules; break; case "init": const fen = e.data[1]; - self.vr = new VariantRules(fen); + self.vr = new self.V(fen); break; case "newmove": self.vr.play(e.data[1]); diff --git a/client/src/utils/alea.js b/client/src/utils/alea.js index 337b25fa..76d9c38e 100644 --- a/client/src/utils/alea.js +++ b/client/src/utils/alea.js @@ -22,7 +22,7 @@ export function sample (arr, n) let cpArr = arr.map(e => e); for (let index = 0; index < n; index++) { - const rand = getRandInt(index, n); + const rand = random(index, n); const temp = cpArr[index]; cpArr[index] = cpArr[rand]; cpArr[rand] = temp; diff --git a/client/src/utils/array.js b/client/src/utils/array.js index b438eaae..1b92e350 100644 --- a/client/src/utils/array.js +++ b/client/src/utils/array.js @@ -1,29 +1,32 @@ // Remove item(s) in array (if present) -export function remove(array, rfun, all) +export const ArrayFun = { - const index = array.findIndex(rfun); - if (index >= 0) + remove: function(array, rfun, all) { - array.splice(index, 1); - if (!!all) + const index = array.findIndex(rfun); + if (index >= 0) { - // Reverse loop because of the splice below - for (let i=array.length-1; i>=index; i--) + array.splice(index, 1); + if (!!all) { - if (rfun(array[i])) - array.splice(i, 1); + // Reverse loop because of the splice below + for (let i=array.length-1; i>=index; i--) + { + if (rfun(array[i])) + array.splice(i, 1); + } } } - } -} + }, -// Double array intialization -export function init(size1, size2, initElem) -{ - return [...Array(size1)].map(e => Array(size2).fill(initElem)); -} + // Double array intialization + init: function(size1, size2, initElem) + { + return [...Array(size1)].map(e => Array(size2).fill(initElem)); + }, -export function range(max) -{ - return [...Array(max).keys()]; -} + range: function(max) + { + return [...Array(max).keys()]; + }, +}; diff --git a/client/src/utils/printDiagram.js b/client/src/utils/printDiagram.js index f3f77d8a..6e095c99 100644 --- a/client/src/utils/printDiagram.js +++ b/client/src/utils/printDiagram.js @@ -1,9 +1,11 @@ +import { ArrayFun } from "@/utils/array"; + // Turn (human) marks into coordinates function getMarkArray(marks) { if (!marks || marks == "-") return []; - let markArray = doubleArray(V.size.x, V.size.y, false); + let markArray = ArrayFun.init(V.size.x, V.size.y, false); const squares = marks.split(","); for (let i=0; i<squares.length; i++) { @@ -18,7 +20,7 @@ function getShadowArray(shadow) { if (!shadow || shadow == "-") return []; - let shadowArray = doubleArray(V.size.x, V.size.y, false); + let shadowArray = ArrayFun.init(V.size.x, V.size.y, false); const squares = shadow.split(","); for (let i=0; i<squares.length; i++) { diff --git a/client/src/variants/Atomic_OLD.js b/client/src/variants/Atomic_OLD.js index 87c98097..1ee5b98f 100644 --- a/client/src/variants/Atomic_OLD.js +++ b/client/src/variants/Atomic_OLD.js @@ -1,4 +1,5 @@ -class AtomicRules extends ChessRules +import { ChessRules } from "@/base_rules"; +export const VariantRules = class AtomicRules extends ChessRules { getPotentialMovesFrom([x,y]) { diff --git a/client/src/variants/Chess960.js b/client/src/variants/Chess960.js index 1c8292ce..36397f69 100644 --- a/client/src/variants/Chess960.js +++ b/client/src/variants/Chess960.js @@ -1,4 +1,5 @@ -class Chess960Rules extends ChessRules +import { ChessRules } from "@/base_rules"; +export const VariantRules = class Chess960Rules extends ChessRules { // Standard rules } diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index b9f51279..8406843e 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -18,6 +18,7 @@ <script> import Game from "@/components/Game.vue"; import { store } from "@/store"; +import { getDiagram } from "@/utils/printDiagram"; export default { name: 'my-rules', components: { @@ -26,7 +27,7 @@ export default { data: function() { return { st: store.state, - variant: null, + variant: {id: 0, name: "Unknown"}, //...yet content: "", display: "rules", mode: "computer", @@ -36,13 +37,14 @@ export default { fen: "", }; }, - // TODO: variant is initialized before store initializes, so remain null (I think) - created: function() { + mounted: async function() { + // NOTE: variant cannot be set before store is initialized const vname = this.$route.params["vname"]; - const idxOfVar = this.st.variants.indexOf(e => e.name == vname); - this.variant = this.st.variants[idxOfVar]; - }, - mounted: function() { + //const idxOfVar = this.st.variants.indexOf(e => e.name == vname); + //this.variant = this.st.variants[idxOfVar]; //TODO: is it the right timing?! + this.variant.name = vname; + const vModule = await import("@/variants/" + vname + ".js"); + window.V = vModule.VariantRules; // Method to replace diagrams in loaded HTML const replaceByDiag = (match, p1, p2) => { const args = this.parseFen(p2); @@ -51,8 +53,7 @@ export default { // (AJAX) Request to get rules content (plain text, HTML) this.content = // TODO: why doesn't this work? require("raw-loader!pug-plain-loader!@/rules/"...) - require("raw-loader!@/rules/" + - this.$route.params["vname"] + "/" + this.st.lang + ".pug") + require("raw-loader!@/rules/" + vname + "/" + this.st.lang + ".pug") .replace(/(fen:)([^:]*):/g, replaceByDiag); }, methods: { -- 2.44.0