From e727fe31742dfb3e40eb222c94f4199e2be98453 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 10 Mar 2020 01:26:33 +0100 Subject: [PATCH] Fix games ordering in MyGames, fix en-passant mistake in Rifle variant --- client/src/base_rules.js | 13 ++--- client/src/components/GameList.vue | 48 +++-------------- client/src/utils/alea.js | 4 +- client/src/variants/Atomic.js | 7 +++ client/src/variants/Checkered.js | 78 +++++++++++++++++++++++++++ client/src/variants/Crazyhouse.js | 7 +++ client/src/variants/Recycle.js | 7 +++ client/src/variants/Rifle.js | 7 +++ client/src/views/Hall.vue | 20 +++++-- client/src/views/MyGames.vue | 87 ++++++++++++++++++------------ 10 files changed, 188 insertions(+), 90 deletions(-) diff --git a/client/src/base_rules.js b/client/src/base_rules.js index aec119a0..f0fe9e6a 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -197,14 +197,10 @@ export const ChessRules = class ChessRules { const move = moveOrSquare; const s = move.start, e = move.end; - // NOTE: next conditions are first for Crazyhouse, and last for Checkered - // TODO: Checkered exceptions are too weird and should move in its own file. if ( - move.vanish.length > 0 && Math.abs(s.x - e.x) == 2 && s.y == e.y && - move.vanish[0].p == V.PAWN && - ["w", "b"].includes(move.vanish[0].c) + move.appear[0].p == V.PAWN ) { return { x: (s.x + e.x) / 2, @@ -645,7 +641,6 @@ export const ChessRules = class ChessRules { const firstRank = color == "w" ? sizeX - 1 : 0; const startRank = color == "w" ? sizeX - 2 : 1; const lastRank = color == "w" ? 0 : sizeX - 1; - const pawnColor = this.getColor(x, y); //can be different for checkered // NOTE: next condition is generally true (no pawn on last rank) if (x + shiftX >= 0 && x + shiftX < sizeX) { @@ -658,7 +653,7 @@ export const ChessRules = class ChessRules { for (let piece of finalPieces) { moves.push( this.getBasicMove([x, y], [x + shiftX, y], { - c: pawnColor, + c: color, p: piece }) ); @@ -683,7 +678,7 @@ export const ChessRules = class ChessRules { for (let piece of finalPieces) { moves.push( this.getBasicMove([x, y], [x + shiftX, y + shiftY], { - c: pawnColor, + c: color, p: piece }) ); @@ -1001,7 +996,7 @@ export const ChessRules = class ChessRules { // After move is played, update variables + flags updateVariables(move) { let piece = undefined; - // TODO: update variables before move is played, and just use this.turn ? + // TODO: update variables before move is played, and just use this.turn? // (doesn't work in general, think MarseilleChess) let c = undefined; if (move.vanish.length >= 1) { diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index 183d6463..71602028 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -11,7 +11,7 @@ div tr( v-for="g in sortedGames" @click="$emit('show-game',g)" - :class="{'my-turn': g.myTurn}" + :class="{'my-turn': !!g.myTurn}" ) td {{ g.vname }} td {{ player_s(g) }} @@ -54,52 +54,20 @@ export default { }, computed: { sortedGames: function() { - // Show in order: games where it's my turn, my running games, my games, other games + // Show in order: it's my turn, running games, completed games let minCreated = Number.MAX_SAFE_INTEGER; let maxCreated = 0; - const isMyTurn = (g, myColor) => { - const rem = g.movesCount % 2; - return ( - (rem == 0 && myColor == "w") || - (rem == 1 && myColor == "b") - ); - }; - let augmentedGames = this.games - .filter(g => !this.deleted[g.id]) - .map(g => { - let priority = 0; - let myColor = undefined; - if ( - g.players.some( - p => p.uid == this.st.user.id || p.sid == this.st.user.sid - ) - ) { - priority++; - myColor = - g.players[0].uid == this.st.user.id || - g.players[0].sid == this.st.user.sid - ? "w" - : "b"; - if (g.score == "*") { - priority++; - if (g.turn == myColor || isMyTurn(g, myColor)) priority++; - } - } - if (g.created < minCreated) minCreated = g.created; - if (g.created > maxCreated) maxCreated = g.created; - return Object.assign({}, g, { - priority: priority, - myTurn: priority == 3, - myColor: myColor - }); - }); + this.games.forEach(g => { + if (g.created < minCreated) minCreated = g.created; + if (g.created > maxCreated) maxCreated = g.created; + }); const deltaCreated = maxCreated - minCreated; - return augmentedGames.sort((g1, g2) => { + return this.games.sort((g1, g2) => { return ( g2.priority - g1.priority + (g2.created - g1.created) / deltaCreated ); }); - } + }, }, methods: { player_s: function(g) { diff --git a/client/src/utils/alea.js b/client/src/utils/alea.js index 9ff11593..1f1d1460 100644 --- a/client/src/utils/alea.js +++ b/client/src/utils/alea.js @@ -22,9 +22,7 @@ export function sample(arr, n) { let cpArr = arr.map(e => e); for (let index = 0; index < n; index++) { const rand = randInt(index, arr.length); - const temp = cpArr[index]; - cpArr[index] = cpArr[rand]; - cpArr[rand] = temp; + [ cpArr[index], cpArr[rand] ] = [ cpArr[rand], cpArr[index] ]; } return cpArr.slice(0, n); } diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js index 35def3fa..4aad7b72 100644 --- a/client/src/variants/Atomic.js +++ b/client/src/variants/Atomic.js @@ -1,6 +1,13 @@ import { ChessRules, PiPo } from "@/base_rules"; export const VariantRules = class AtomicRules extends ChessRules { + getEpSquare(moveOrSquare) { + if (typeof moveOrSquare !== "object" || move.appear.length > 0) + return super.getEpSquare(moveOrSquare); + // Capturing move: no en-passant + return undefined; + } + getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x, y]); diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index 5f9bf8f6..fa3a6b7e 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -90,6 +90,13 @@ export const VariantRules = class CheckeredRules extends ChessRules { this.pawnFlags = flags[1]; } + getEpSquare(moveOrSquare) { + if (typeof moveOrSquare !== "object" || move.appear[0].c != 'c') + return super.getEpSquare(moveOrSquare); + // Checkered move: no en-passant + return undefined; + } + getCmove(move) { if (move.appear[0].c == "c" && move.vanish.length == 1) return { start: move.start, end: move.end }; @@ -149,6 +156,77 @@ export const VariantRules = class CheckeredRules extends ChessRules { return moves; } + getPotentialPawnMoves([x, y]) { + const color = this.turn; + let moves = []; + const [sizeX, sizeY] = [V.size.x, V.size.y]; + const shiftX = color == "w" ? -1 : 1; + const startRank = color == "w" ? sizeX - 2 : 1; + const lastRank = color == "w" ? 0 : sizeX - 1; + const pawnColor = this.getColor(x, y); //can be checkered + + const finalPieces = + x + shiftX == lastRank + ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN] + : [V.PAWN]; + if (this.board[x + shiftX][y] == V.EMPTY) { + // One square forward + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y], { + c: pawnColor, + p: piece + }) + ); + } + if ( + x == startRank && + this.board[x + 2 * shiftX][y] == V.EMPTY + ) { + // Two squares jump + moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); + } + } + // Captures + for (let shiftY of [-1, 1]) { + if ( + y + shiftY >= 0 && + y + shiftY < sizeY && + this.board[x + shiftX][y + shiftY] != V.EMPTY && + this.canTake([x, y], [x + shiftX, y + shiftY]) + ) { + for (let piece of finalPieces) { + moves.push( + this.getBasicMove([x, y], [x + shiftX, y + shiftY], { + c: pawnColor, + p: piece + }) + ); + } + } + } + + // En passant + const Lep = this.epSquares.length; + const epSquare = this.epSquares[Lep - 1]; //always at least one element + if ( + !!epSquare && + epSquare.x == x + shiftX && + Math.abs(epSquare.y - y) == 1 + ) { + let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]); + enpassantMove.vanish.push({ + x: x, + y: epSquare.y, + p: "p", + c: this.getColor(x, epSquare.y) + }); + moves.push(enpassantMove); + } + + return moves; + } + canIplay(side, [x, y]) { return side == this.turn && [side, "c"].includes(this.getColor(x, y)); } diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js index 16db148d..ff705c73 100644 --- a/client/src/variants/Crazyhouse.js +++ b/client/src/variants/Crazyhouse.js @@ -28,6 +28,13 @@ export const VariantRules = class CrazyhouseRules extends ChessRules { }); } + getEpSquare(moveOrSquare) { + if (typeof moveOrSquare !== "object" || move.vanish.length > 0) + return super.getEpSquare(moveOrSquare); + // Landing move: no en-passant + return undefined; + } + static GenRandInitFen(randomness) { return ChessRules.GenRandInitFen(randomness) + " 0000000000 -"; } diff --git a/client/src/variants/Recycle.js b/client/src/variants/Recycle.js index 9f43ad80..73d91669 100644 --- a/client/src/variants/Recycle.js +++ b/client/src/variants/Recycle.js @@ -18,6 +18,13 @@ export const VariantRules = class RecycleRules extends ChessRules { }); } + getEpSquare(moveOrSquare) { + if (typeof moveOrSquare !== "object" || move.vanish.length > 0) + return super.getEpSquare(moveOrSquare); + // Landing move: no en-passant + return undefined; + } + static GenRandInitFen(randomness) { return ChessRules.GenRandInitFen(randomness) + " 0000000000"; } diff --git a/client/src/variants/Rifle.js b/client/src/variants/Rifle.js index ccec48ef..631fb13a 100644 --- a/client/src/variants/Rifle.js +++ b/client/src/variants/Rifle.js @@ -1,6 +1,13 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; export const VariantRules = class RifleRules extends ChessRules { + getEpSquare(moveOrSquare) { + if (typeof moveOrSquare !== "object" || move.appear.length > 0) + return super.getEpSquare(moveOrSquare); + // Capturing move: no en-passant + return undefined; + } + getBasicMove([sx, sy], [ex, ey], tr) { let mv = new Move({ appear: [], diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 5217c34d..92030e74 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -256,7 +256,15 @@ export default { response.games.map(g => { const type = this.classifyObject(g); const vname = this.getVname(g.vid); - return Object.assign({}, g, { type: type, vname: vname }); + return Object.assign( + {}, + g, + { + type: type, + vname: vname, + priority: g.score == "*" ? 1 : 0 //for display + } + ); }) ); } @@ -694,9 +702,11 @@ export default { let newGame = game; newGame.type = this.classifyObject(game); newGame.vname = this.getVname(game.vid); + newGame.priority = 0; if (!game.score) - //if new game from Hall + // New game from Hall newGame.score = "*"; + if (newGame.score == "*") newGame.priority++; newGame.rids = [game.rid]; delete newGame["rid"]; this.games.push(newGame); @@ -717,7 +727,10 @@ export default { } case "result": { let g = this.games.find(g => g.id == data.gid); - if (!!g) g.score = data.score; + if (!!g) { + g.score = data.score; + g.priority = 0; + } break; } case "startgame": { @@ -907,6 +920,7 @@ export default { } this.send("deletechallenge", { data: c.id }); }, + // TODO: if several players click same challenge at the same time: problem clickChallenge: async function(c) { const myChallenge = c.from.sid == this.st.user.sid || //live diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index 953c976e..88b69065 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -47,6 +47,7 @@ export default { created: function() { GameStorage.getAll(true, localGames => { localGames.forEach(g => g.type = "live"); + this.decorate(localGames); this.liveGames = localGames; }); if (this.st.user.id > 0) { @@ -64,6 +65,7 @@ export default { return !g["deletedBy" + mySide]; }); serverGames.forEach(g => g.type = "corr"); + this.decorate(serverGames); this.corrGames = serverGames; } } @@ -112,22 +114,47 @@ export default { .classList.add("somethingnew"); } }, + // Called at loading to augment games with priority + myTurn infos + decorate: function(games) { + games.forEach(g => { + g.priority = 0; + if (g.score == "*") { + g.priority++; + const myColor = + (g.type == "corr" && g.players[0].uid == this.st.user.id) || + (g.type == "live" && g.players[0].sid == this.st.user.sid) + ? 'w' + : 'b'; + const rem = g.movesCount % 2; + if ((rem == 0 && myColor == 'w') || (rem == 1 && myColor == 'b')) { + g.myTurn = true; + g.priority++; + } + } + }); + }, socketMessageListener: function(msg) { const data = JSON.parse(msg.data); + let gamesArrays = { + "corr": this.corrGames, + "live": this.liveGames + }; switch (data.code) { - // NOTE: no need to increment movesCount: unused if turn is provided case "notifyturn": case "notifyscore": { const info = data.data; - let games = - !!parseInt(info.gid) - ? this.corrGames - : this.liveGames; - let g = games.find(g => g.id == info.gid); + const type = (!!parseInt(info.gid) ? "corr" : "live"); + let game = gamesArrays[type].find(g => g.id == info.gid); // "notifything" --> "thing": const thing = data.code.substr(6); - this.$set(g, thing, info[thing]); - this.tryShowNewsIndicator(g.type); + game[thing] = info[thing]; + if (thing == "score") game.priority = 0; + else { + game.priority = 3 - game.priority; //toggle turn + game.myTurn = !game.myTurn; + } + this.$forceUpdate(); + this.tryShowNewsIndicator(type); break; } case "notifynewgame": { @@ -136,22 +163,27 @@ export default { // if unlucky and newgame right after connect: const v = this.st.variants.find(v => v.id == gameInfo.vid); const vname = !!v ? v.name : ""; - const type = gameInfo.cadence.indexOf('d') >= 0 ? "corr": "live"; - const game = Object.assign( + const type = (gameInfo.cadence.indexOf('d') >= 0 ? "corr": "live"); + let game = Object.assign( { vname: vname, type: type, score: "*", - turn: "w" + created: Date.now() }, gameInfo ); - // TODO: the new game isn't sorted. Maybe apply a different strategy: - // 1) Sort all at loading, - // 2) Insert in place when new games arrive, - // 3) Change position when score or turn change. - // And GameList just show list unsorted. - this[type + "Games"].unshift(game); + // Compute priority: + game.priority = 1; //at least: my running game + if ( + (type == "corr" && game.players[0].uid == this.st.user.id) || + (type == "live" && game.players[0].sid == this.st.user.sid) + ) { + game.priority++; + game.myTurn = true; + } + gamesArrays[type].push(game); + this.$forceUpdate(); this.tryShowNewsIndicator(type); break; } @@ -163,29 +195,14 @@ export default { this.conn.addEventListener("close", this.socketCloseListener); }, showGame: function(game) { - // TODO: "isMyTurn" is duplicated (see GameList component). myColor also - const isMyTurn = (g) => { - if (g.score != "*") return false; - const myColor = - g.players[0].uid == this.st.user.id || - g.players[0].sid == this.st.user.sid - ? "w" - : "b"; - if (!!g.turn) return g.turn == myColor; - const rem = g.movesCount % 2; - return ( - (rem == 0 && myColor == "w") || - (rem == 1 && myColor == "b") - ); - }; - if (game.type == "live" || !isMyTurn(game)) { + if (game.type == "live" || !game.myTurn) { this.$router.push("/game/" + game.id); return; } // It's my turn in this game. Are there others? let nextIds = ""; - let otherCorrGamesMyTurn = this.corrGames.filter( - g => g.id != game.id && isMyTurn(g)); + let otherCorrGamesMyTurn = this.corrGames.filter(g => + g.id != game.id && !!g.myTurn); if (otherCorrGamesMyTurn.length > 0) { nextIds += "/?next=["; otherCorrGamesMyTurn.forEach(g => { nextIds += g.id + ","; }); -- 2.44.0