From 7931e479adf93c87771ded1892a0873af72ae46d Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Sat, 15 Dec 2018 18:22:14 +0100 Subject: [PATCH] Almost finished problems logic. TODO: showProblem() part --- TODO | 2 + public/javascripts/base_rules.js | 99 +++++++++++++++---- public/javascripts/components/game.js | 5 +- .../javascripts/components/problemSummary.js | 37 +++++-- public/javascripts/components/problems.js | 56 +++++++---- public/javascripts/components/rules.js | 6 +- public/javascripts/variants/Alice.js | 4 + public/javascripts/variants/Antiking.js | 6 +- public/javascripts/variants/Checkered.js | 25 ++++- public/javascripts/variants/Grand.js | 4 + public/javascripts/variants/Loser.js | 5 + public/javascripts/variants/Ultima.js | 9 ++ public/javascripts/variants/Wildebeest.js | 4 + routes/all.js | 16 ++- 14 files changed, 210 insertions(+), 68 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 00000000..e6641b48 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +Finish showProblem() in components/problemSummary.js (send event with infos, and pass message to game component) +Add new mode in game component: "problem", in which we show description + hidden solution (reveal on click) diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js index 51c9dda8..5763af7e 100644 --- a/public/javascripts/base_rules.js +++ b/public/javascripts/base_rules.js @@ -54,12 +54,15 @@ class ChessRules constructor(fen, moves) { this.moves = moves; - // Use fen string to initialize variables, flags and board - this.board = V.GetBoard(fen); - this.setFlags(fen); + // Use fen string to initialize variables, flags, turn and board + const fenParts = fen.split(" "); + this.board = V.GetBoard(fenParts[0]); + this.setFlags(fenParts[1]); //NOTE: fenParts[1] might be undefined + this.setTurn(fenParts[2]); //Same note this.initVariables(fen); } + // Some additional variables from FEN (variant dependant) initVariables(fen) { this.INIT_COL_KING = {'w':-1, 'b':-1}; @@ -95,7 +98,7 @@ class ChessRules this.INIT_COL_ROOK['w'][1] = k; break; default: - let num = parseInt(position[i].charAt(j)); + const num = parseInt(position[i].charAt(j)); if (!isNaN(num)) k += (num-1); } @@ -106,18 +109,68 @@ class ChessRules this.epSquares = [ epSq ]; } + // Check if FEN describe a position + static IsGoodFen(fen) + { + const fenParts = fen.split(" "); + if (fenParts.length== 0 || fenParts.length > 3) + return false; + // 1) Check position + const position = fenParts[0]; + const rows = position.split("/"); + if (rows.length != V.size.x) + return false; + for (let row of rows) + { + let sumElts = 0; + for (let i=0; i<row.length; i++) + { + if (V.PIECES.includes(row[i].toLowerCase())) + sumElts++; + else + { + const num = parseInt(row[i]); + if (isNaN(num)) + return false; + sumElts += num; + } + } + if (sumElts != V.size.y) + return false; + } + // 2) Check flags (if present) + if (fenParts.length >= 2) + { + if (!V.IsGoodFlags(fenParts[1])) + return false; + } + // 3) Check turn (if present) + if (fenParts.length == 3) + { + if (!["w","b"].includes(fenParts[2])) + return false; + } + return true; + } + + // For FEN checking + static IsGoodFlags(flags) + { + return !!flags.match(/^[01]{4,4}$/); + } + // Turn diagram fen into double array ["wb","wp","bk",...] static GetBoard(fen) { - let rows = fen.split(" ")[0].split("/"); + const rows = fen.split(" ")[0].split("/"); let board = doubleArray(V.size.x, V.size.y, ""); for (let i=0; i<rows.length; i++) { let j = 0; for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) { - let character = rows[i][indexInRow]; - let num = parseInt(character); + const character = rows[i][indexInRow]; + const num = parseInt(character); if (!isNaN(num)) j += num; //just shift j else //something at position i,j @@ -128,13 +181,20 @@ class ChessRules } // Extract (relevant) flags from fen - setFlags(fen) + setFlags(fenflags) { // white a-castle, h-castle, black a-castle, h-castle - this.castleFlags = {'w': new Array(2), 'b': new Array(2)}; - let flags = fen.split(" ")[1]; //flags right after position + this.castleFlags = {'w': [true,true], 'b': [true,true]}; + if (!fenflags) + return; for (let i=0; i<4; i++) - this.castleFlags[i < 2 ? 'w' : 'b'][i%2] = (flags.charAt(i) == '1'); + this.castleFlags[i < 2 ? 'w' : 'b'][i%2] = (fenflags.charAt(i) == '1'); + } + + // Initialize turn (white or black) + setTurn(turnflag) + { + this.turn = turnflag || "w"; } /////////////////// @@ -154,10 +214,6 @@ class ChessRules return (L>0 ? this.moves[L-1] : null); } - get turn() { - return (this.moves.length%2==0 ? 'w' : 'b'); - } - // Pieces codes static get PAWN() { return 'p'; } static get ROOK() { return 'r'; } @@ -166,6 +222,11 @@ class ChessRules static get QUEEN() { return 'q'; } static get KING() { return 'k'; } + // For FEN checking: + static get PIECES() { + return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.KING]; + } + // Empty square static get EMPTY() { return ''; } @@ -483,9 +544,7 @@ class ChessRules canIplay(side, [x,y]) { - return ((side=='w' && this.moves.length%2==0) - || (side=='b' && this.moves.length%2==1)) - && this.getColor(x,y) == side; + return (this.turn == side && this.getColor(x,y) == side); } getPossibleMovesFrom(sq) @@ -717,7 +776,7 @@ class ChessRules // Hash of position+flags+turn after a move is played (to detect repetitions) getHashState() { - return hex_md5(this.getFen() + " " + this.turn); + return hex_md5(this.getFen()); } play(move, ingame) @@ -1054,7 +1113,7 @@ class ChessRules // Return current fen according to pieces+colors state getFen() { - return this.getBaseFen() + " " + this.getFlagsFen(); + return this.getBaseFen() + " " + this.getFlagsFen() + " " + this.turn; } // Position part of the FEN string diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js index 278e0782..49db6188 100644 --- a/public/javascripts/components/game.js +++ b/public/javascripts/components/game.js @@ -1024,10 +1024,7 @@ Vue.component('my-game', { { const storageVariant = localStorage.getItem("variant"); if (!!storageVariant && storageVariant !== variant) - { - alert("Finish your " + storageVariant + " game first!"); - return; - } + return alert("Finish your " + storageVariant + " game first!"); // Send game request and wait.. localStorage["newgame"] = variant; this.seek = true; diff --git a/public/javascripts/components/problemSummary.js b/public/javascripts/components/problemSummary.js index 6003f085..48a42533 100644 --- a/public/javascripts/components/problemSummary.js +++ b/public/javascripts/components/problemSummary.js @@ -2,16 +2,33 @@ Vue.component('my-problem-summary', { props: ['prob'], template: ` - <div class="problem col-sm-12"> - <div class="diagram"> - {{ getDiagram(prob.fen) }} - </div> - <div class="problem-instructions"> - {{ prob.instructions.substr(0,32) }} - </div> - <div class="problem-time"> - {{ prob.added }} - </div> + <div class="problem col-sm-12" @click="showProblem()"> + <div class="diagram" v-html="getDiagram(prob.fen)"></div> + <div class="problem-instructions" v-html="prob.instructions.substr(0,32)"></div> + <div class="problem-time">{{ timestamp2datetime(prob.added) }}</div> </div> `, + methods: { + getDiagram: function(fen) { + const fenParts = fen.split(" "); + return getDiagram({ + position: fenParts[0], + // No need for flags here + turn: fenParts[2], + }); + }, + timestamp2datetime(ts) { + // TODO + return ts; + }, + showProblem: function() { + alert("show problem"); + //.......... + //TODO: send event with object prob.fen, prob.instructions, prob.solution + //Event should propagate to game, which set mode=="problem" + other variables + //click on a problem ==> land on variant page with mode==friend, FEN prefilled... ok + // click on problem ==> masque problems, affiche game tab, launch new game Friend with + // FEN + turn + flags + rappel instructions / solution on click sous l'échiquier + }, + }, }) diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js index 594d6e64..dbd8340c 100644 --- a/public/javascripts/components/problems.js +++ b/public/javascripts/components/problems.js @@ -6,14 +6,11 @@ Vue.component('my-problems', { }, template: ` <div> - <button>Previous</button> - <button>Next</button> + <button @click="fetchProblems('backward')">Previous</button> + <button @click="fetchProblems('forward')">Next</button> <button @click="showNewproblemModal">New</button> <my-problem-summary - v-for="(p,idx) in sortedProblems", - v-bind:prob="p", - v-bind:key="idx", - @click="showProblem(p)" + v-for="(p,idx) in sortedProblems" v-bind:prob="p" v-bind:key="idx"> </my-problem-summary> <input type="checkbox" id="modal-newproblem" class="modal"> <div role="dialog" aria-labelledby="newProblemTxt"> @@ -23,7 +20,8 @@ Vue.component('my-problems', { <form @submit.prevent="postNewProblem"> <fieldset> <label for="newpbFen">Fen</label> - <input type="text" id="newpbFen" placeholder="Position [+ flags [+ turn]]"/> + <input type="text" id="newpbFen" + placeholder="Position [+ flags [+ turn]]"/> </fieldset> <fieldset> <p class="emphasis"> @@ -47,8 +45,9 @@ Vue.component('my-problems', { `, computed: { sortedProblems: function() { + console.log("call"); // Newest problem first - return problems.sort((p1,p2) => { return p2.added - p1.added; }); + return this.problems.sort((p1,p2) => { return p2.added - p1.added; }); }, mailErrProblem: function() { return "mailto:contact@vchess.club?subject=[" + variant + " problems] error"; @@ -56,26 +55,43 @@ Vue.component('my-problems', { }, methods: { fetchProblems: function(direction) { - // TODO: ajax call return list of max 10 problems - // Do not do anything if no older problems (and store this result in cache!) - // TODO: ajax call return list of max 10 problems - // Do not do anything if no newer problems - }, - showProblem: function(prob) { - //TODO: send event with object prob.fen, prob.instructions, prob.solution - //Event should propagate to game, which set mode=="problem" + other variables - //click on a problem ==> land on variant page with mode==friend, FEN prefilled... ok - // click on problem ==> masque problems, affiche game tab, launch new game Friend with - // FEN + turn + flags + rappel instructions / solution on click sous l'échiquier + return; //TODO: re-activate after server side is implemented (see routes/all.js) + if (this.problems.length == 0) + return; //what could we do?! + // Search for newest date (or oldest) + let last_dt = this.problems[0].added; + for (let i=0; i<this.problems.length; i++) + { + if ((direction == "forward" && this.problems[i].added > last_dt) || + (direction == "backward" && this.problems[i].added < last_dt)) + { + last_dt = this.problems[i].added; + } + } + ajax("/problems/" + variant, "GET", { + direction: direction, + last_dt: last_dt, + }, response => { + if (response.problems.length > 0) + this.problems = response.problems; + }); }, showNewproblemModal: function() { document.getElementById("modal-newproblem").checked = true; }, postNewProblem: function() { const fen = document.getElementById("newpbFen").value; + if (!V.IsGoodFen(fen)) + return alert("Bad FEN string"); const instructions = document.getElementById("newpbInstructions").value; const solution = document.getElementById("newpbSolution").value; - + ajax("/problems/" + variant, "POST", { + fen: fen, + instructions: instructions, + solution: solution, + }, response => { + document.getElementById("modal-newproblem").checked = false; + }); }, }, }) diff --git a/public/javascripts/components/rules.js b/public/javascripts/components/rules.js index 660f0be9..829bf3b1 100644 --- a/public/javascripts/components/rules.js +++ b/public/javascripts/components/rules.js @@ -8,11 +8,11 @@ Vue.component('my-rules', { // AJAX request to get rules content (plain text, HTML) ajax("/rules/" + variant, "GET", response => { let replaceByDiag = (match, p1, p2) => { - const args = self.parseFen(p2); + const args = this.parseFen(p2); return getDiagram(args); }; - self.content = response.replace(/(fen:)([^:]*):/g, replaceByDiag); - } + this.content = response.replace(/(fen:)([^:]*):/g, replaceByDiag); + }); }, methods: { parseFen(fen) { diff --git a/public/javascripts/variants/Alice.js b/public/javascripts/variants/Alice.js index 5e10a059..ece59cdc 100644 --- a/public/javascripts/variants/Alice.js +++ b/public/javascripts/variants/Alice.js @@ -29,6 +29,10 @@ class AliceRules extends ChessRules return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b; } + static get PIECES() { + return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES)); + } + initVariables(fen) { super.initVariables(fen); diff --git a/public/javascripts/variants/Antiking.js b/public/javascripts/variants/Antiking.js index e7411684..2821f3f5 100644 --- a/public/javascripts/variants/Antiking.js +++ b/public/javascripts/variants/Antiking.js @@ -6,7 +6,11 @@ class AntikingRules extends ChessRules } static get ANTIKING() { return 'a'; } - + + static get PIECES() { + return ChessRules.PIECES.concat([V.ANTIKING]); + } + initVariables(fen) { super.initVariables(fen); diff --git a/public/javascripts/variants/Checkered.js b/public/javascripts/variants/Checkered.js index 2d62d4b8..585d20ff 100644 --- a/public/javascripts/variants/Checkered.js +++ b/public/javascripts/variants/Checkered.js @@ -19,18 +19,34 @@ class CheckeredRules extends ChessRules } static fen2board(f) { + // Tolerate upper-case versions of checkered pieces (why not?) const checkered_pieces = { 's': 'p', + 'S': 'p', 't': 'q', + 'T': 'q', 'u': 'r', + 'U': 'r', 'c': 'b', + 'C': 'b', 'o': 'n', + 'O': 'n', }; if (Object.keys(checkered_pieces).includes(f)) return 'c'+checkered_pieces[f]; return ChessRules.fen2board(f); } + static get PIECES() { + return ChessRules.PIECES.concat(['s','t','u','c','o']); + } + + static IsGoodFlags(flags) + { + // 4 for castle + 16 for pawns + return !!flags.match(/^[01]{20,20}$/); + } + setFlags(fen) { super.setFlags(fen); //castleFlags @@ -108,9 +124,7 @@ class CheckeredRules extends ChessRules canIplay(side, [x,y]) { - return ((side=='w' && this.moves.length%2==0) - || (side=='b' && this.moves.length%2==1)) - && [side,'c'].includes(this.getColor(x,y)); + return (side == this.turn && [side,'c'].includes(this.getColor(x,y))); } // Does m2 un-do m1 ? (to disallow undoing checkered moves) @@ -194,11 +208,12 @@ class CheckeredRules extends ChessRules checkGameEnd() { const color = this.turn; - this.moves.length++; //artifically change turn, for checkered pawns (TODO) + // Artifically change turn, for checkered pawns + this.turn = this.getOppCol(this.turn); const res = this.isAttacked(this.kingPos[color], [this.getOppCol(color),'c']) ? (color == "w" ? "0-1" : "1-0") : "1/2"; - this.moves.length--; + this.turn = this.getOppCol(this.turn); return res; } diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js index f5ae0653..cafdadab 100644 --- a/public/javascripts/variants/Grand.js +++ b/public/javascripts/variants/Grand.js @@ -18,6 +18,10 @@ class GrandRules extends ChessRules static get MARSHALL() { return 'm'; } //rook+knight static get CARDINAL() { return 'c'; } //bishop+knight + static get PIECES() { + return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]); + } + // En-passant after 2-sq or 3-sq jumps getEpSquare(move) { diff --git a/public/javascripts/variants/Loser.js b/public/javascripts/variants/Loser.js index 3def40a9..98bd9443 100644 --- a/public/javascripts/variants/Loser.js +++ b/public/javascripts/variants/Loser.js @@ -6,6 +6,11 @@ class LoserRules extends ChessRules this.epSquares = [ epSq ]; } + static IsGoodFlags(flags) + { + return true; //anything is good: no flags + } + setFlags(fen) { // No castling, hence no flags; but flags defined for compatibility diff --git a/public/javascripts/variants/Ultima.js b/public/javascripts/variants/Ultima.js index 04ef29b0..d6fecb70 100644 --- a/public/javascripts/variants/Ultima.js +++ b/public/javascripts/variants/Ultima.js @@ -7,6 +7,15 @@ class UltimaRules extends ChessRules return b; //usual piece } + static get PIECES() { + return ChessRules.PIECES.concat([V.IMMOBILIZER]); + } + + static IsGoodFlags(flags) + { + return true; //anything is good: no flags + } + initVariables(fen) { this.kingPos = {'w':[-1,-1], 'b':[-1,-1]}; diff --git a/public/javascripts/variants/Wildebeest.js b/public/javascripts/variants/Wildebeest.js index bb478cc2..dcf2d582 100644 --- a/public/javascripts/variants/Wildebeest.js +++ b/public/javascripts/variants/Wildebeest.js @@ -10,6 +10,10 @@ class WildebeestRules extends ChessRules static get CAMEL() { return 'c'; } static get WILDEBEEST() { return 'w'; } + static get PIECES() { + return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]); + } + static get steps() { return Object.assign( ChessRules.steps, //add camel moves: diff --git a/routes/all.js b/routes/all.js index f3e184e6..b1e0fda3 100644 --- a/routes/all.js +++ b/routes/all.js @@ -28,6 +28,7 @@ router.get("/:vname([a-zA-Z0-9]+)", (req,res,next) => { return next(err); if (!variant || variant.length==0) return next(createError(404)); + // TODO (later...) get only n=100(?) most recent problems db.all("SELECT * FROM Problems WHERE variant='" + vname + "'", (err2,problems) => { if (!!err2) @@ -55,6 +56,9 @@ router.get("/problems/:variant([a-zA-Z0-9]+)", (req,res) => { if (!req.xhr) return res.json({errmsg: "Unauthorized access"}); // TODO: next or previous: in params + timedate (of current oldest or newest) + db.serialize(function() { + //TODO + }); }); // Upload a problem (AJAX) @@ -62,10 +66,13 @@ router.post("/problems/:variant([a-zA-Z0-9]+)", (req,res) => { if (!req.xhr) return res.json({errmsg: "Unauthorized access"}); const vname = req.params["variant"]; - - // TODO: get parameters and sanitize them - sanitizeHtml(req.body["fen"]); // [/a-z0-9 ]* - sanitizeHtml(req.body["instructions"]); + const timestamp = Date.now(); + // Sanitize them + const fen = req.body["fen"]; + if (!fen.match(/^[a-zA-Z0-9 /]*$/)) + return res.json({errmsg: "Bad characters in FEN string"}); + const instructions = sanitizeHtml(req.body["instructions"]); + const solution = sanitizeHtml(req.body["solution"]); db.serialize(function() { let stmt = db.prepare("INSERT INTO Problems VALUES (?,?,?,?,?)"); stmt.run(timestamp, vname, fen, instructions, solution); @@ -74,5 +81,4 @@ router.post("/problems/:variant([a-zA-Z0-9]+)", (req,res) => { res.json({}); }); - module.exports = router; -- 2.44.0