X-Git-Url: https://git.auder.net/?p=vchess.git;a=blobdiff_plain;f=public%2Fjavascripts%2Fcomponents%2Fgame.js;h=ca791257eb51360853575f0b208587c2e2a35cea;hp=297ccc9dc55b011830412e0941a4a0cc3bd9c94a;hb=fd08ab2c5b8931bb8c95cf7e9f2f95122647f991;hpb=8d7e2786f5a67a1b9a77c742d7951e0efbe8747d diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index 297ccc9d..ca791257 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -1,93 +1,89 @@ -// Game logic on a variant page +// TODO: envoyer juste "light move", sans FEN ni notation ...etc +// TODO: also "observers" prop, we should send moves to them too (in a web worker ? webRTC ?) + +// Game logic on a variant page: 3 modes, analyze, computer or human Vue.component('my-game', { - props: ["gameId"], //to find the game in storage (assumption: it exists) + // gameId: to find the game in storage (assumption: it exists) + props: ["gameId","fen","mode","allowChat","allowMovelist"], data: function() { return { - - // TODO: merge next variables into "game" // if oppid == "computer" then mode = "computer" (otherwise human) myid: "", //our ID, always set - //this.myid = localStorage.getItem("myid") + //this.myid = localStorage.getItem("myid") oppid: "", //opponent ID in case of HH game score: "*", //'*' means 'unfinished' mycolor: "w", - fromChallenge: false, //if true, show chat during game - - conn: null, //socket connection - oppConnected: false, - seek: false, - fenStart: "", + conn: null, //socket connection (et WebRTC connection ?!) + oppConnected: false, //TODO? pgnTxt: "", // sound level: 0 = no sound, 1 = sound only on newgame, 2 = always sound: parseInt(localStorage["sound"] || "2"), // 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 }; }, - computed: { - mode: function() { - return (this.game.oppid == "computer" ? "computer" ? "human"); + watch: { + fen: function(newFen) { + this.vr = new VariantRules(newFen); }, + }, + computed: { showChat: function() { - return this.mode=='human' && - (this.game.score != '*' || this.game.fromChallenge); + return this.allowChat && this.mode=='human' && this.score != '*'; }, showMoves: function() { - return window.innerWidth >= 768; + return this.allowMovelist && window.innerWidth >= 768; + }, + showFen: function() { + return variant.name != "Dark" || this.score != "*"; }, }, // Modal end of game, and then sub-components + // TODO: provide chat parameters (connection, players ID...) + // and alwo moveList parameters (just moves ?) + // TODO: connection + turn indicators en haut à droite (superposé au menu) + // TODO: controls: abort, clear, resign, draw (avec confirm box) + // et si partie terminée : (mode analyse) just clear, back / play + // + flip button toujours disponible + // gotoMove : vr = new VariantRules(fen stocké dans le coup [TODO]) template: `
- -

{{ endgameMessage }}

- - - //TODO: connection + turn indicators en haut à droite (superposé au menu) - - // TODO: controls: abort, clear, resign, draw (avec confirm box) - // et si partie terminée : (mode analyse) just clear, back / play - // + flip button toujours disponible - + +

+ {{ endgameMessage }} +

+
+
+ + + + +
+

+ {{ vr.getFen() }} +

+
- + + - - +
+ +
`, - computed: { - endgameMessage: function() { - let eogMessage = "Unfinished"; - switch (this.game.score) - { - case "1-0": - eogMessage = translations["White win"]; - break; - case "0-1": - eogMessage = translations["Black win"]; - break; - case "1/2": - eogMessage = translations["Draw"]; - break; - } - return eogMessage; - }, - }, created: function() { const url = socketUrl; this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant._id); -// const socketOpenListener = () => { -// }; - -// TODO: after game, archive in indexedDB - + // TODO: after game, archive in indexedDB // TODO: this events listener is central. Refactor ? How ? const socketMessageListener = msg => { const data = JSON.parse(msg.data); @@ -162,29 +158,11 @@ Vue.component('my-game', { const socketCloseListener = () => { this.conn = new WebSocket(url + "/?sid=" + this.myid + "&page=" + variant._id); - //this.conn.addEventListener('open', socketOpenListener); this.conn.addEventListener('message', socketMessageListener); this.conn.addEventListener('close', socketCloseListener); }; - //this.conn.onopen = socketOpenListener; this.conn.onmessage = socketMessageListener; this.conn.onclose = socketCloseListener; - - - // Listen to keyboard left/right to navigate in game - // TODO: also mouse wheel ! - document.onkeydown = event => { - if (["human","computer"].includes(this.mode) && - !!this.vr && this.vr.moves.length > 0 && [37,39].includes(event.keyCode)) - { - event.preventDefault(); - if (event.keyCode == 37) //Back - this.undo(); - else //Forward (39) - this.play(); - } - }; - // Computer moves web worker logic: (TODO: also for observers in HH games) this.compWorker.postMessage(["scripts",variant.name]); @@ -212,19 +190,41 @@ Vue.component('my-game', { }, delay); } }, - - + //TODO: conn pourrait être une prop, donnée depuis variant.js + //dans variant.js (plutôt room.js) conn gère aussi les challenges + // Puis en webRTC, repenser tout ça. methods: { + setEndgameMessage: function(score) { + let eogMessage = "Undefined"; + switch (score) + { + case "1-0": + eogMessage = translations["White win"]; + break; + case "0-1": + eogMessage = translations["Black win"]; + break; + case "1/2": + eogMessage = translations["Draw"]; + break; + case "?": + eogMessage = "Unfinished"; + break; + } + this.endgameMessage = eogMessage; + }, download: function() { // Variants may have special PGN structure (so next function isn't defined here) - const content = V.GetPGN(this.moves, this.mycolor, this.score, this.fenStart, this.mode); + // TODO: get fenStart from local game (using gameid) + const content = V.GetPGN(this.moves, this.mycolor, this.score, fenStart, this.mode); // Prepare and trigger download link let downloadAnchor = document.getElementById("download"); downloadAnchor.setAttribute("download", "game.pgn"); downloadAnchor.href = "data:text/plain;charset=utf-8," + encodeURIComponent(content); downloadAnchor.click(); }, - showScoreMsg: function() { + showScoreMsg: function(score) { + this.setEndgameMessage(score); let modalBox = document.getElementById("modal-eog"); modalBox.checked = true; setTimeout(() => { modalBox.checked = false; }, 2000); @@ -236,14 +236,15 @@ Vue.component('my-game', { const prefix = (this.mode=="computer" ? "comp-" : ""); localStorage.setItem(prefix+"score", score); } - this.showScoreMsg(); + this.showScoreMsg(score); if (this.mode == "human" && this.oppConnected) { // Send our nickname to opponent this.conn.send(JSON.stringify({ code:"myname", name:this.myname, oppid:this.oppid})); } - this.cursor = this.vr.moves.length; //to navigate in finished game + // TODO: what about cursor ? + //this.cursor = this.vr.moves.length; //to navigate in finished game }, resign: function(e) { this.getRidOfTooltip(e.currentTarget); @@ -261,96 +262,88 @@ Vue.component('my-game', { this.timeStart = Date.now(); this.compWorker.postMessage(["askmove"]); }, - // OK, these last functions can stay here (?!) - play: function(move, programmatic) { - if (!move) + animateMove: function(move) { + let startSquare = document.getElementById(this.getSquareId(move.start)); + let endSquare = document.getElementById(this.getSquareId(move.end)); + let rectStart = startSquare.getBoundingClientRect(); + let rectEnd = endSquare.getBoundingClientRect(); + let translation = {x:rectEnd.x-rectStart.x, y:rectEnd.y-rectStart.y}; + let movingPiece = + document.querySelector("#" + this.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"); + for (let i=0; i= this.moves.length) - return; //already at the end - move = this.moves[this.cursor++]; + let square = squares.item(i); + if (square.id != this.getSquareId(move.start)) + square.style.zIndex = "-1"; } + movingPiece.style.transform = "translate(" + translation.x + "px," + + translation.y + "px)"; + movingPiece.style.transitionDuration = "0.2s"; + movingPiece.style.zIndex = "3000"; + setTimeout( () => { + for (let i=0; i {}); + if (this.mode == "human") { - // Emergency check, if human game started "at the same time" - // TODO: robustify this... - if (this.mode == "human" && !!move.computer) - return; - this.vr.play(move, "ingame"); - // Is opponent in check? - this.incheck = this.vr.getCheckSquares(this.vr.turn); - if (this.sound == 2) - new Audio("/sounds/move.mp3").play().catch(err => {}); - if (this.mode == "computer") - { - // Send the move to web worker (TODO: including his own moves?!) - this.compWorker.postMessage(["newmove",move]); - } - const eog = this.vr.getCurrentScore(); - if (eog != "*") - { - if (["human","computer"].includes(this.mode)) - this.endGame(eog); - else - { - // Just show score on screen (allow undo) - this.score = eog; - this.showScoreMsg(); - } - } + updateStorage(move); //after our moves and opponent moves + if (this.vr.turn == this.userColor) + this.conn.send(JSON.stringify({code:"newmove", move:move, oppid:this.oppid})); } -// else -// { -// VariantRules.PlayOnBoard(this.vr.board, move); -// this.$forceUpdate(); //TODO: ?! -// } - if (["human","computer","friend"].includes(this.mode)) - this.updateStorage(); //after our moves and opponent moves - if (this.mode == "computer" && this.vr.turn != this.mycolor && this.score == "*") - this.playComputerMove(); - }, - // TODO: merge two next functions - undo: function() { - // Navigate after game is over - if (this.cursor == 0) - return; //already at the beginning - if (this.cursor == this.vr.moves.length) - this.incheck = []; //in case of... - const move = this.vr.moves[--this.cursor]; - VariantRules.UndoOnBoard(this.vr.board, move); - this.$forceUpdate(); //TODO: ?! - }, - undoInGame: function() { - const lm = this.vr.lastMove; - if (!!lm) + else if (this.mode == "computer") + { + // Send the move to web worker (including his own moves) + this.compWorker.postMessage(["newmove",move]); + } + if (this.score == "*" || this.mode == "analyze") { - this.vr.undo(lm); - if (this.sound == 2) - new Audio("/sounds/undo.mp3").play().catch(err => {}); - this.incheck = this.vr.getCheckSquares(this.vr.turn); + // Stack move on movesList + this.moves.push(move); } + // Is opponent in check? + this.incheck = this.vr.getCheckSquares(this.vr.turn); + const score = this.vr.getCurrentScore(); + if (score != "*") + { + if (["human","computer"].includes(this.mode)) + this.endGame(score); + else //just show score on screen (allow undo) + this.showScoreMsg(score); + // TODO: notify end of game (give score) + } + else if (this.mode == "computer" && this.vr.turn != this.userColor) + this.playComputerMove(); + }, + undo: function(move) { + this.vr.undo(move); + if (this.sound == 2) + new Audio("/sounds/undo.mp3").play().catch(err => {}); + this.incheck = this.vr.getCheckSquares(this.vr.turn); + if (this.mode == "analyze") + this.moves.pop(); }, }, }) - -//// TODO: keep moves list here -//get lastMove() -// { -// const L = this.moves.length; -// return (L>0 ? this.moves[L-1] : null); -// } -// -//// here too: -// move.notation = this.getNotation(move); +// cursor + ........ //TODO: confirm dialog with "opponent offers draw", avec possible bouton "prevent future offers" + bouton "proposer nulle" //+ bouton "abort" avec score == "?" + demander confirmation pour toutes ces actions, //comme sur lichess +// +//TODO: quand partie terminée (ci-dessus) passer partie dans indexedDB