From: Benjamin Auder Date: Sat, 15 Dec 2018 17:22:14 +0000 (+0100) Subject: Almost finished problems logic. TODO: showProblem() part X-Git-Url: https://git.auder.net/?p=vchess.git;a=commitdiff_plain;h=7931e479adf93c87771ded1892a0873af72ae46d Almost finished problems logic. TODO: showProblem() part --- 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= 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; i0 ? 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: ` -
-
- {{ getDiagram(prob.fen) }} -
-
- {{ prob.instructions.substr(0,32) }} -
-
- {{ prob.added }} -
+
+
+
+
{{ timestamp2datetime(prob.added) }}
`, + 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: `
- - + +
@@ -23,7 +20,8 @@ Vue.component('my-problems', {
- +

@@ -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 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;