--- /dev/null
+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)
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};
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);
}
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
}
// 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";
}
///////////////////
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'; }
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 ''; }
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)
// 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)
// 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
{
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;
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
+ },
+ },
})
},
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">
<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">
`,
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";
},
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;
+ });
},
},
})
// 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) {
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);
}
static get ANTIKING() { return 'a'; }
-
+
+ static get PIECES() {
+ return ChessRules.PIECES.concat([V.ANTIKING]);
+ }
+
initVariables(fen)
{
super.initVariables(fen);
}
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
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)
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;
}
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)
{
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
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]};
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:
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)
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)
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);
res.json({});
});
-
module.exports = router;