--> selon le mode de déplacement standard (donc tout droit pour les pions)
Pas de notion d'échec ou de mat (?)
Si une pièce est mat elle donne le ballon (?)
+
+Landing pieces from empty board:
+https://www.chessvariants.com/diffsetup.dir/unachess.html
+
+Rugby http://www.echecspourtous.com/?page_id=7945
https://www.flaticon.com/free-icon/download_724933?term=download&page=1&position=3
https://www.flaticon.com/free-icon/resize_512182?term=resize&page=1&position=49
https://www.flaticon.com/free-icon/clear_565313?term=delete&page=1&position=33
+https://www.flaticon.com/free-icon/clear_1632708?term=delete&page=1&position=3
--- /dev/null
+<svg height="511.992pt" viewBox="0 0 511.992 511.992" width="511.992pt" xmlns="http://www.w3.org/2000/svg"><path d="m415.402344 495.421875-159.40625-159.410156-159.40625 159.410156c-22.097656 22.09375-57.921875 22.09375-80.019532 0-22.09375-22.097656-22.09375-57.921875 0-80.019531l159.410157-159.40625-159.410157-159.40625c-22.09375-22.097656-22.09375-57.921875 0-80.019532 22.097657-22.09375 57.921876-22.09375 80.019532 0l159.40625 159.410157 159.40625-159.410157c22.097656-22.09375 57.921875-22.09375 80.019531 0 22.09375 22.097657 22.09375 57.921876 0 80.019532l-159.410156 159.40625 159.410156 159.40625c22.09375 22.097656 22.09375 57.921875 0 80.019531-22.097656 22.09375-57.921875 22.09375-80.019531 0zm0 0" fill="#e76e54"/></svg>
\ No newline at end of file
display: inline-block
#controls
+ user-select: none
margin: 0 auto
text-align: center
display: flex
possibleMoves: [], //filled after each valid click/dragstart
choices: [], //promotion pieces, or checkered captures... (as moves)
selectedPiece: null, //moving piece (or clicked piece)
- start: {}, //pixels coordinates + id of starting square (click or drag)
+ start: null, //pixels coordinates + id of starting square (click or drag)
+ click: "",
settings: store.state.settings
};
},
},
methods: {
mousedown: function(e) {
- // Abort if a piece is already being processed, or target is not a piece.
- // NOTE: just looking at classList[0] because piece is the first assigned class
- if (!!this.selectedPiece || e.target.classList[0] != "piece") return;
- e.preventDefault(); //disable native drag & drop
- let parent = e.target.parentNode; //the surrounding square
- // Next few lines to center the piece on mouse cursor
- let rect = parent.getBoundingClientRect();
- this.start = {
- x: rect.x + rect.width / 2,
- y: rect.y + rect.width / 2,
- id: parent.id
- };
- this.selectedPiece = e.target.cloneNode();
- let spStyle = this.selectedPiece.style;
- spStyle.position = "absolute";
- spStyle.top = 0;
- spStyle.display = "inline-block";
- spStyle.zIndex = 3000;
- const startSquare = getSquareFromId(parent.id);
- this.possibleMoves = [];
- const color = this.analyze ? this.vr.turn : this.userColor;
- if (this.vr.canIplay(color, startSquare))
- this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
- // Next line add moving piece just after current image
- // (required for Crazyhouse reserve)
- parent.insertBefore(this.selectedPiece, e.target.nextSibling);
+ e.preventDefault();
+ if (!this.start) {
+ // Start square must contain a piece.
+ // NOTE: classList[0] is enough: 'piece' is the first assigned class
+ if (e.target.classList[0] != "piece") return;
+ let parent = e.target.parentNode; //surrounding square
+ // Show possible moves if current player allowed to play
+ const startSquare = getSquareFromId(parent.id);
+ this.possibleMoves = [];
+ const color = this.analyze ? this.vr.turn : this.userColor;
+ if (this.vr.canIplay(color, startSquare))
+ this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
+ // For potential drag'n drop, remember start coordinates
+ // (to center the piece on mouse cursor)
+ let rect = parent.getBoundingClientRect();
+ this.start = {
+ x: rect.x + rect.width / 2,
+ y: rect.y + rect.width / 2,
+ id: parent.id
+ };
+ // Add the moving piece to the board, just after current image
+ this.selectedPiece = e.target.cloneNode();
+ Object.assign(
+ this.selectedPiece.style,
+ {
+ position: "absolute",
+ top: 0,
+ display: "inline-block",
+ zIndex: 3000
+ }
+ );
+ parent.insertBefore(this.selectedPiece, e.target.nextSibling);
+ } else {
+ this.processMoveAttempt(e);
+ }
},
mousemove: function(e) {
if (!this.selectedPiece) return;
+ e.preventDefault();
// There is an active element: move it around
const [offsetX, offsetY] =
this.mobileBrowser
? [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
: [e.clientX, e.clientY];
- this.selectedPiece.style.left = offsetX - this.start.x + "px";
- this.selectedPiece.style.top = offsetY - this.start.y + "px";
+ Object.assign(
+ this.selectedPiece.style,
+ {
+ left: offsetX - this.start.x + "px",
+ top: offsetY - this.start.y + "px"
+ }
+ );
},
mouseup: function(e) {
if (!this.selectedPiece) return;
- // There is an active element: obtain the move from start and end squares
- this.selectedPiece.style.zIndex = -3000; //HACK to find square from final coords
+ e.preventDefault();
+ // Drag'n drop. Selected piece is no longer needed:
+ this.selectedPiece.parentNode.removeChild(this.selectedPiece);
+ delete this.selectedPiece;
+ this.selectedPiece = null;
+ this.processMoveAttempt(e);
+ },
+ processMoveAttempt: function(e) {
+ // Obtain the move from start and end squares
const [offsetX, offsetY] =
this.mobileBrowser
? [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
: [e.clientX, e.clientY];
let landing = document.elementFromPoint(offsetX, offsetY);
- this.selectedPiece.style.zIndex = 3000;
// Next condition: classList.contains(piece) fails because of marks
while (landing.tagName == "IMG") landing = landing.parentNode;
- if (this.start.id == landing.id)
- // One or multi clicks on same piece
+ if (this.start.id == landing.id) {
+ if (this.click == landing.id) {
+ // Second click on same square: cancel current move
+ this.possibleMoves = [];
+ this.start = null;
+ this.click = "";
+ } else this.click = landing.id;
return;
+ }
+ this.start = null;
// OK: process move attempt, landing is a square node
let endSquare = getSquareFromId(landing.id);
let moves = this.findMatchingMoves(endSquare);
this.possibleMoves = [];
if (moves.length > 1) this.choices = moves;
else if (moves.length == 1) this.play(moves[0]);
- // Else: impossible move
- this.selectedPiece.parentNode.removeChild(this.selectedPiece);
- delete this.selectedPiece;
- this.selectedPiece = null;
+ // else: forbidden move attempt
},
findMatchingMoves: function(endSquare) {
// Run through moves list and return the matching set (if promotions...)
- let moves = [];
- this.possibleMoves.forEach(function(m) {
- if (endSquare[0] == m.end.x && endSquare[1] == m.end.y) moves.push(m);
- });
- return moves;
+ return (
+ this.possibleMoves.filter(m => {
+ return (endSquare[0] == m.end.x && endSquare[1] == m.end.y);
+ })
+ );
},
play: function(move) {
this.$emit("play-move", move);
// NOTE: no variants with reserve of size != 8
.game
+ user-select: none
width: 100%
margin: 0
.board
cursor: pointer
#choices
+ user-select: none
margin: 0
position: absolute
z-index: 300
img.ghost
position: absolute
- opacity: 0.4
+ opacity: 0.5
top: 0
-.highlight-light
- background-color: rgba(0, 204, 102, 0.7) !important
-.highlight-dark
- background-color: rgba(0, 204, 102, 0.9) !important
-
.incheck-light
background-color: rgba(204, 51, 0, 0.7) !important
.incheck-dark
background-color: #6f8f57;
.light-square.chesstempo
- background-color: #fdfdfd;
+ background-color: #dfdfdf;
.dark-square.chesstempo
- background-color: #88a0a8;
+ background-color: #7287b6;
+
+// TODO: no predefined highlight colors, but layers. How?
+
+.light-square.lichess.highlight-light
+ background-color: #cdd26a !important
+.dark-square.lichess.highlight-dark
+ background-color: #aaa23a !important
+
+.light-square.chesscom.highlight-light
+ background-color: #f7f783 !important
+.dark-square.chesscom.highlight-dark
+ background-color: #bacb44 !important
+
+.light-square.chesstempo.highlight-light
+ background-color: #9f9fff !important
+.dark-square.chesstempo.highlight-dark
+ background-color: #557fff !important
+
</style>
remGames.forEach(g => {
if (g.created < minCreated) minCreated = g.created;
if (g.created > maxCreated) maxCreated = g.created;
+ g.priority = 0;
+ if (g.score == "*") {
+ g.priority++;
+ if (!!g.myColor) g.priority++;
+ if (!!g.myTurn) g.priority++;
+ }
});
const deltaCreated = maxCreated - minCreated;
return remGames.sort((g1, g2) => {
<style lang="sass" scoped>
.moves-list
+ user-select: none
cursor: pointer
min-height: 1px
max-height: 500px
Cadence: "Cadence",
Cancel: "Cancel",
Challenge: "Challenge",
+ "Challenge already exists": "Challenge already exists",
"Challenge declined": "Challenge declined",
"Chat here": "Chat here",
"Clear history": "Clear history",
Login: "Login",
Logout: "Logout",
"Logout successful!": "Logout successful!",
+ "Memorize?": "Memorize?",
"Mispelled variant name": "Mispelled variant name",
"Missing email": "Missing email",
"Missing FEN": "Missing FEN",
"Please select a variant": "Please select a variant",
Practice: "Practice",
"Prefix?": "Prefix?",
+ "Preset challenges": "Preset challenges",
Previous: "Previous",
"Processing... Please wait": "Processing... Please wait",
Problems: "Problems",
Register: "Register",
"Registration complete! Please check your emails now": "Registration complete! Please check your emails now",
Rematch: "Rematch",
+ "Rematch in progress:": "Rematch in progress",
"Remove game?": "Remove game?",
Resign: "Resign",
"Resign the game?": "Resign the game?",
Cadence: "Cadencia",
Cancel: "Anular",
Challenge: "Desafiar",
+ "Challenge already exists": "El desafío ya existe",
"Challenge declined": "Desafío rechazado",
"Chat here": "Chat aquí",
"Clear history": "Clara historia",
Login: "Login",
Logout: "Logout",
"Logout successful!": "¡Desconexión exitosa!",
+ "Memorize?": "¿Memorizar?",
"Mispelled variant name": "Variante mal escrita",
"Missing email": "Email falta",
"Missing FEN": "FEN falta",
"Please select a variant": "Por favor seleccione una variante",
Practice: "Práctica",
"Prefix?": "¿Prefijo?",
+ "Preset challenges": "Desafíos registrados",
Previous: "Anterior",
"Processing... Please wait": "Procesando... por favor espere",
Problems: "Problemas",
Register: "Registrarse",
"Registration complete! Please check your emails now": "¡Registro completo! Revise sus correos electrónicos ahora",
Rematch: "Revancha",
+ "Rematch in progress:": "Revancha en progreso:",
"Remove game?": "¿Eliminar la partida?",
Resign: "Abandonar",
"Resign the game?": "¿Abandonar la partida?",
Cadence: "Cadence",
Cancel: "Annuler",
Challenge: "Défier",
+ "Challenge already exists": "Le défi existe déjà",
"Challenge declined": "Défi refusé",
"Chat here": "Chattez ici",
"Clear history": "Effacer l'historique",
Login: "Login",
Logout: "Logout",
"Logout successful!": "Déconnection réussie !",
+ "Memorize?": "Mémoriser ?",
"Mispelled variant name": "Variante mal orthographiée",
"Missing email": "Email manquant",
"Missing FEN": "FEN manquante",
"Please select a variant": "Sélectionnez une variante SVP",
Practice: "Pratiquer",
"Prefix?": "Préfixe ?",
+ "Preset challenges": "Défis enregistrés",
Previous: "Précédent",
"Processing... Please wait": "Traitement en cours... Attendez SVP",
Problems: "Problèmes",
Register: "S'enregistrer",
"Registration complete! Please check your emails now": "Enregistrement terminé ! Allez voir vos emails maintenant",
Rematch: "Rejouer",
+ "Rematch in progress:": "Revanche en cours :",
"Remove game?": "Supprimer la partie ?",
Resign: "Abandonner",
"Resign the game?": "Abandonner la partie ?",
return ChessRules.PIECES.concat([V.JAILER, V.SENTRY, V.LANCER]);
}
+ static get LANCER_DIRS() {
+ return {
+ 'c': [-1, 0], //north
+ 'd': [-1, 1], //N-E
+ 'e': [0, 1], //east
+ 'f': [1, 1], //S-E
+ 'g': [1, 0], //south
+ 'h': [1, -1], //S-W
+ 'm': [0, -1], //west
+ 'o': [-1, -1] //N-W
+ };
+ }
+
+ getPiece(i, j) {
+ const piece = this.board[i][j].charAt(1);
+ // Special lancer case: 8 possible orientations
+ if (Object.keys(V.LANCER_DIRS).includes(piece)) return V.LANCER;
+ return piece;
+ }
+
getPpath(b) {
- // TODO: more subtle, path depends on the orientations
- // lancerOrientations should probably be a 8x8 array, for speed.
return (
([V.JAILER, V.SENTRY, V.LANCER].includes(b[1])
? "Eightpieces/" : "") + b
);
}
- static IsGoodFen(fen) {
- if (!ChessRules.IsGoodFen(fen)) return false;
- const fenParsed = V.ParseFen(fen);
- // 5) Check lancers orientations (if there are any left)
- if (
- !fenParsed.lancers ||
- (
- fenParsed.lancers != "-" &&
- !fenParsed.lancers.match(/^([a-h][1-8][0-7],?)+$/)
- )
- ) {
- return false;
- }
- return true;
+ setOtherVariables(fen) {
+ super.setOtherVariables(fen);
+ // subTurn == 2 only when a sentry moved, and is about to push something
+ this.subTurn = 1;
+ // Stack pieces' forbidden squares after a sentry move at each turn
+ this.sentryPath = [];
}
- static ParseFen(fen) {
- const fenParts = fen.split(" ");
- return Object.assign(ChessRules.ParseFen(fen), {
- lancers: fenParts[5],
- });
+ canTake([x1,y1], [x2, y2]) {
+ if (this.subTurn == 2)
+ // Sentry push: pieces can capture own color (only)
+ return this.getColor(x1, y1) == this.getColor(x2, y2);
+ return super.canTake([x1,y1], [x2, y2]);
}
static GenRandInitFen(randomness) {
// TODO: special conditions
}
- getFen() {
- return (
- super.getFen() + " " + this.getLancersFen()
- );
+ // TODO: rook + jailer
+ scanKingsRooks(fen) {
+ this.kingPos = { w: [-1, -1], b: [-1, -1] };
+ const fenRows = V.ParseFen(fen).position.split("/");
+ for (let i = 0; i < fenRows.length; i++) {
+ let k = 0; //column index on board
+ for (let j = 0; j < fenRows[i].length; j++) {
+ switch (fenRows[i].charAt(j)) {
+ case "k":
+ case "l":
+ this.kingPos["b"] = [i, k];
+ break;
+ case "K":
+ case "L":
+ this.kingPos["w"] = [i, k];
+ break;
+ default: {
+ const num = parseInt(fenRows[i].charAt(j));
+ if (!isNaN(num)) k += num - 1;
+ }
+ }
+ k++;
+ }
+ }
}
- getFenForRepeat() {
- return (
- this.getBaseFen() + "_" +
- this.getTurnFen() + "_" +
- this.getFlagsFen() + "_" +
- this.getEnpassantFen() + "_" +
- this.getLancersFen()
- );
+ getPotentialMovesFrom([x,y]) {
+ // if subTurn == 2, allow only
}
- getLancersFen() {
- let res = "";
- this.lancerOrientations.forEach(o => {
- res += V.CoordsToSquare(o.sq) + o.dir + ",";
- });
- res = res.slice(0, -1);
- return res || "-";
+ // getPotentialMoves, isAttacked: TODO
+ getPotentialCastleMoves(sq) { //TODO: adapt, with jailer
}
- setOtherVariables(fen) {
- super.setOtherVariables(fen);
- const fenParsed = V.ParseFen(fen);
- // Also init lancer orientations (from FEN):
- this.lancerOrientations = 32; // TODO
+ updateVariables(move) {
+ // TODO: stack sentryPath if subTurn == 2 --> all squares between move.start et move.end, sauf si c'est un pion
}
- // getPotentialMoves, isAttacked: TODO
-
- // updatedVariables: update lancers' orientations
+ // TODO: special pass move: take jailer with king
- // subTurn : if sentry moved to some enemy piece.
+ // subTurn : if sentry moved to some enemy piece --> enregistrer déplacement sentry, subTurn == 2, puis déplacer pièce adverse --> 1st 1/2 of turn, vanish sentry tout simplement.
+ // --> le turn ne change pas !
+ // 2nd half: move only
+ // FEN flag: sentryPath from init pushing to final enemy square --> forbid some moves (getPotentialMoves)
static get VALUES() {
return Object.assign(
- { l: 5, s: 4, j: 5 }, //experimental
+ { l: 4.8, s: 2.8, j: 3.8 }, //Jeff K. estimations
ChessRules.VALUES
);
}
)
img(src="/images/icons/resign.svg")
button.tooltip(
- v-else-if="!!game.mycolor"
+ v-else
@click="clickRematch()"
:class="{['rematch-' + rematchOffer]: true}"
:aria-label="st.tr['Rematch']"
)
.card
label#closeNewgame.modal-close(for="modalNewgame")
- div(@keyup.enter="newChallenge()")
+ div(@keyup.enter="issueNewChallenge()")
fieldset
label(for="selectVariant") {{ st.tr["Variant"] }} *
select#selectVariant(
option(value="0") {{ st.tr["Deterministic"] }}
option(value="1") {{ st.tr["Symmetric random"] }}
option(value="2") {{ st.tr["Asymmetric random"] }}
+ fieldset
+ label(for="memorizeChall") {{ st.tr["Memorize?"] }}
+ input#memorizeChall(
+ type="checkbox"
+ v-model="newchallenge.memorize"
+ )
fieldset(v-if="st.user.id > 0")
label(for="selectPlayers") {{ st.tr["Play with?"] }}
input#selectPlayers(
v-model="newchallenge.fen"
)
.diagram(v-html="newchallenge.diag")
- button(@click="newChallenge()") {{ st.tr["Send challenge"] }}
+ button(@click="issueNewChallenge()") {{ st.tr["Send challenge"] }}
input#modalPeople.modal(
type="checkbox"
@click="resetSocialColor()"
| {{ st.tr["Who's there?"] }}
button(@click="showNewchallengeForm()")
| {{ st.tr["New game"] }}
+ .row(v-if="presetChalls.length > 0")
+ .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
+ h4.text-center {{ st.tr["Preset challenges"] }}
+ table
+ thead
+ tr
+ th {{ st.tr["Variant"] }}
+ th {{ st.tr["Cadence"] }}
+ th {{ st.tr["Random?"] }}
+ th
+ tbody
+ tr(
+ v-for="pc in presetChalls"
+ @click="newChallFromPreset(pc)"
+ )
+ td {{ pc.vname }}
+ td {{ pc.cadence }}
+ td(:class="getRandomnessClass(pc)")
+ td.remove-preset(@click="removePresetChall($event, pc)")
+ img(src="/images/icons/delete.svg")
.row
.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2
div#div2
// diagrams of targetted challenges:
V: null,
vname: "",
- diag: "" //visualizing FEN
+ diag: "", //visualizing FEN
+ memorize: false //put settings in localStorage
},
tchallDiag: "",
curChallToAccept: {from: {}},
+ presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"),
newChat: "",
conn: null,
connexionString: "",
g,
{
type: type,
- vname: vname,
- priority: g.score == "*" ? 1 : 0 //for display
+ vname: vname
}
);
})
this.send("disconnect");
},
methods: {
+ getRandomnessClass: function(pc) {
+ return {
+ ["random-" + pc.randomness]: true
+ };
+ },
visibilityChange: function() {
// TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27
this.send(
this.newchallenge.to = "";
this.newchallenge.fen = "";
this.newchallenge.diag = "";
+ this.newchallenge.memorize = false;
},
showNewchallengeForm: function() {
this.partialResetNewchallenge();
window.doClick("modalNewgame");
},
+ addPresetChall: function(chall) {
+ // Add only if not already existing:
+ if (this.presetChalls.some(c =>
+ c.vid == chall.vid &&
+ c.cadence == chall.cadence &&
+ c.randomness == chall.randomness
+ )) {
+ return;
+ }
+ const L = this.presetChalls.length;
+ this.presetChalls.push({
+ index: L,
+ vid: chall.vid,
+ vname: chall.vname, //redundant, but easier
+ cadence: chall.cadence,
+ randomness: chall.randomness
+ });
+ localStorage.setItem("presetChalls", JSON.stringify(this.presetChalls));
+ },
+ removePresetChall: function(e, pchall) {
+ e.stopPropagation();
+ const pchallIdx = this.presetChalls.findIndex(pc => pc.index == pchall.index);
+ this.presetChalls.splice(pchallIdx, 1);
+ localStorage.setItem("presetChalls", JSON.stringify(this.presetChalls));
+ },
tchallButtonsMargin: function() {
if (!!this.curChallToAccept.fen) return { "margin-top": "10px" };
return {};
return this.games.filter(g => g.type == type);
},
classifyObject: function(o) {
- //challenge or game
+ // o: challenge or game
return o.cadence.indexOf("d") === -1 ? "live" : "corr";
},
setDisplay: function(letter, type, e) {
this.partialResetNewchallenge();
// Available, in Hall
this.newchallenge.to = this.people[sid].name;
+ // TODO: also store target sid to not re-search for it
document.getElementById("modalPeople").checked = false;
window.doClick("modalNewgame");
},
this.people[s.sid].pages.push({ path: page, focus: true });
if (!s.page)
// Peer is in Hall
- this.send("askchallenge", { target: s.sid });
+ this.send("askchallenges", { target: s.sid });
// Peer is in Game
else this.send("askgame", { target: s.sid, page: page });
});
case "connect":
case "gconnect": {
const page = data.page || "/";
- // Only ask game / challenge if first connexion:
+ // Only ask game / challenges if first connexion:
if (!this.people[data.from]) {
this.people[data.from] = { pages: [{ path: page, focus: true }] };
if (data.code == "connect")
- this.send("askchallenge", { target: data.from });
+ this.send("askchallenges", { target: data.from });
else this.send("askgame", { target: data.from, page: page });
} else {
// Append page if not already in list
if (!this.people[data.from]) return;
// Disconnect means no more tmpIds:
if (data.code == "disconnect") {
- // Remove the live challenge sent by this player:
+ // Remove the live challenges sent by this player:
ArrayFun.remove(
this.challenges,
- c => c.type == "live" && c.from.sid == data.from
+ c => c.type == "live" && c.from.sid == data.from,
+ "all"
);
} else {
// Remove the matching live game if now unreachable
}
break;
}
- case "askchallenge": {
- // Send my current live challenge (if any)
- const cIdx = this.challenges.findIndex(
- c => c.from.sid == this.st.user.sid && c.type == "live"
- );
- if (cIdx >= 0) {
- const c = this.challenges[cIdx];
- // NOTE: in principle, should only send targeted challenge to the target.
- // But we may not know yet the identity of the target (just name),
- // so cannot decide if data.from is the target or not.
- const myChallenge = {
- id: c.id,
- from: this.st.user.sid,
- to: c.to,
- randomness: c.randomness,
- fen: c.fen,
- vid: c.vid,
- cadence: c.cadence,
- added: c.added
- };
- this.send("challenge", { data: myChallenge, target: data.from });
- }
+ case "askchallenges": {
+ // Send my current live challenges (if any)
+ const myChallenges = this.challenges
+ .filter(c =>
+ c.from.sid == this.st.user.sid && c.type == "live"
+ )
+ .map(c => {
+ // NOTE: in principle, should only send targeted challenge to the target.
+ // But we may not know yet the identity of the target (just name),
+ // so cannot decide if data.from is the target or not.
+ return {
+ id: c.id,
+ from: this.st.user.sid,
+ to: c.to,
+ randomness: c.randomness,
+ fen: c.fen,
+ vid: c.vid,
+ cadence: c.cadence,
+ added: c.added
+ };
+ });
+ if (myChallenges.length > 0)
+ this.send("challenges", { data: myChallenges, target: data.from });
break;
}
- case "challenge": //after "askchallenge"
- case "newchallenge": {
- // NOTE about next condition: see "askchallenge" case.
- const chall = data.data;
- if (
- !chall.to ||
- (this.people[chall.from].id > 0 &&
- (chall.from == this.st.user.sid || chall.to == this.st.user.name))
- ) {
- let newChall = Object.assign({}, chall);
- newChall.type = this.classifyObject(chall);
- newChall.randomness = chall.randomness;
- newChall.added = Date.now();
- let fromValues = Object.assign({}, this.people[chall.from]);
- delete fromValues["pages"]; //irrelevant in this context
- newChall.from = Object.assign({ sid: chall.from }, fromValues);
- newChall.vname = this.getVname(newChall.vid);
- this.challenges.push(newChall);
- if (
- (newChall.type == "live" && this.cdisplay == "corr") ||
- (newChall.type == "corr" && this.cdisplay == "live")
- ) {
- document
- .getElementById("btnC" + newChall.type)
- .classList.add("somethingnew");
- }
- }
+ case "challenges": //after "askchallenges"
+ data.data.forEach(this.addChallenge);
+ break;
+ case "newchallenge":
+ this.addChallenge(data.data);
break;
- }
case "refusechallenge": {
const cid = data.data;
ArrayFun.remove(this.challenges, c => c.id == cid);
alert(this.st.tr["Challenge declined"]);
break;
}
- case "deletechallenge": {
- // NOTE: the challenge may be already removed
- const cid = data.data;
- ArrayFun.remove(this.challenges, c => c.id == cid);
+ case "deletechallenge_s": {
+ // NOTE: the challenge(s) may be already removed
+ const cref = data.data;
+ if (!!cref.cid) ArrayFun.remove(this.challenges, c => c.id == cref.cid);
+ else if (!!cref.sids) {
+ cref.sids.forEach(s => {
+ ArrayFun.remove(
+ this.challenges,
+ c => c.type == "live" && c.from.sid == s,
+ "all"
+ );
+ });
+ }
break;
}
case "game": //individual request
let newGame = game;
newGame.type = this.classifyObject(game);
newGame.vname = this.getVname(game.vid);
- newGame.priority = 0;
if (!game.score)
// New game from Hall
newGame.score = "*";
- if (newGame.score == "*") newGame.priority++;
newGame.rids = [game.rid];
delete newGame["rid"];
this.games.push(newGame);
}
case "result": {
let g = this.games.find(g => g.id == data.gid);
- if (!!g) {
- g.score = data.score;
- g.priority = 0;
- }
+ if (!!g) g.score = data.score;
break;
}
case "startgame": {
this.conn.addEventListener("close", this.socketCloseListener);
},
// Challenge lifecycle:
+ addChallenge: function(chall) {
+ // NOTE about next condition: see "askchallenges" case.
+ if (
+ !chall.to ||
+ (this.people[chall.from].id > 0 &&
+ (chall.from == this.st.user.sid || chall.to == this.st.user.name))
+ ) {
+ let newChall = Object.assign({}, chall);
+ newChall.type = this.classifyObject(chall);
+ newChall.randomness = chall.randomness;
+ newChall.added = Date.now();
+ let fromValues = Object.assign({}, this.people[chall.from]);
+ delete fromValues["pages"]; //irrelevant in this context
+ newChall.from = Object.assign({ sid: chall.from }, fromValues);
+ newChall.vname = this.getVname(newChall.vid);
+ this.challenges.push(newChall);
+ if (
+ (newChall.type == "live" && this.cdisplay == "corr") ||
+ (newChall.type == "corr" && this.cdisplay == "live")
+ ) {
+ document
+ .getElementById("btnC" + newChall.type)
+ .classList.add("somethingnew");
+ }
+ }
+ },
loadNewchallVariant: async function(cb) {
const vname = this.getVname(this.newchallenge.vid);
const vModule = await import("@/variants/" + vname + ".js");
});
}
},
- newChallenge: async function() {
+ newChallFromPreset(pchall) {
+ this.partialResetNewchallenge();
+ this.newchallenge.vid = pchall.vid;
+ this.newchallenge.cadence = pchall.cadence;
+ this.newchallenge.randomness = pchall.randomness;
+ this.issueNewChallenge();
+ },
+ issueNewChallenge: async function() {
if (!!(this.newchallenge.cadence.match(/^[0-9]+$/)))
this.newchallenge.cadence += "+0"; //assume minutes, no increment
const ctype = this.classifyObject(this.newchallenge);
}
// NOTE: "from" information is not required here
let chall = Object.assign({}, this.newchallenge);
+ // Add only if not already issued (not counting target or FEN):
+ if (this.challenges.some(c =>
+ (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id) &&
+ c.vid == chall.vid &&
+ c.cadence == chall.cadence &&
+ c.randomness == chall.randomness
+ )) {
+ alert(this.st.tr["Challenge already exists"]);
+ return;
+ }
+ if (this.newchallenge.memorize) this.addPresetChall(this.newchallenge);
delete chall["V"];
delete chall["diag"];
const finishAddChallenge = cid => {
chall.id = cid || "c" + getRandString();
- // Remove old challenge if any (only one at a time of a given type):
- const cIdx = this.challenges.findIndex(
- c =>
- (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id) &&
- c.type == ctype
- );
- if (cIdx >= 0) {
- // Delete current challenge (will be replaced now)
- this.send("deletechallenge", { data: this.challenges[cIdx].id });
+ const MAX_ALLOWED_CHALLS = 3;
+ // Remove oldest challenge if 3 found: only 3 at a time of a given type
+ let countMyChalls = 0;
+ let challToDelIdx = 0;
+ let oldestAdded = Number.MAX_SAFE_INTEGER;
+ for (let i=0; i<this.challenges.length; i++) {
+ const c = this.challenges[i];
+ if (
+ c.type == ctype &&
+ (c.from.sid == this.st.user.sid || c.from.id == this.st.user.id)
+ ) {
+ countMyChalls++;
+ if (c.added < oldestAdded) {
+ challToDelIdx = i;
+ oldestAdded = c.added;
+ }
+ }
+ }
+ if (countMyChalls >= MAX_ALLOWED_CHALLS) {
+ this.send(
+ "deletechallenge_s",
+ { data: { cid: this.challenges[challToDelIdx].id } }
+ );
if (ctype == "corr") {
ajax(
"/challenges",
"DELETE",
- { data: { id: this.challenges[cIdx].id } }
+ { data: { id: this.challenges[challToDelIdx].id } }
);
}
- this.challenges.splice(cIdx, 1);
+ this.challenges.splice(challToDelIdx, 1);
}
this.send("newchallenge", {
data: Object.assign({ from: this.st.user.sid }, chall)
name: this.st.user.name
};
this.launchGame(c);
+ if (c.type == "live")
+ // Remove all live challenges of both players
+ this.send("deletechallenge_s", { data: { sids: [c.from.sid, c.seat.sid] } });
+ else
+ // Corr challenge: just remove the challenge
+ this.send("deletechallenge_s", { data: { cid: c.id } });
} else {
const oppsid = this.getOppsid(c);
if (!!oppsid)
{ data: { id: c.id } }
);
}
+ this.send("deletechallenge_s", { data: { cid: c.id } });
}
- this.send("deletechallenge", { data: c.id });
},
// TODO: if several players click same challenge at the same time: problem
clickChallenge: async function(c) {
{ data: { id: c.id } }
);
}
- this.send("deletechallenge", { data: c.id });
+ this.send("deletechallenge_s", { data: { cid: c.id } });
}
// In all cases, the challenge is consumed:
ArrayFun.remove(this.challenges, ch => ch.id == c.id);
@media screen and (max-width: 767px)
#div2, #div3
margin-top: 0
+
+tr > td
+ &.random-0
+ background-color: #FF5733
+ &.random-1
+ background-color: #2B63B4
+ &.random-2
+ background-color: #33B42B
+
+td.remove-preset
+ background-color: lightgrey
+ text-align: center
+ & > img
+ height: 1em
</style>
.classList.add("somethingnew");
}
},
- // Called at loading to augment games with priority + myTurn infos
+ // Called at loading to augment games with myColor + myTurn infos
decorate: function(games) {
games.forEach(g => {
- g.priority = 0;
+ // If game is over, myColor and myTurn are ignored:
if (g.score == "*") {
- g.priority++;
- const myColor =
+ g.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')) {
+ if ((rem == 0 && g.myColor == 'w') || (rem == 1 && g.myColor == 'b')) {
g.myTurn = true;
- g.priority++;
}
}
});
// "notifything" --> "thing":
const thing = data.code.substr(6);
game[thing] = info[thing];
- if (thing == "score") game.priority = 0;
- else {
- game.priority = 3 - game.priority; //toggle turn
- game.myTurn = !game.myTurn;
- }
+ if (thing == "turn") game.myTurn = !game.myTurn;
this.$forceUpdate();
this.tryShowNewsIndicator(type);
break;
},
gameInfo
);
- // Compute priority:
- game.priority = 1; //at least: my running game
- if (
+ game.myTurn =
(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;
- }
+ (type == "live" && game.players[0].sid == this.st.user.sid);
gamesArrays[type].push(game);
this.$forceUpdate();
this.tryShowNewsIndicator(type);
// but the requested resource can be from any tmpId (except current!)
case "askidentity":
case "asklastate":
- case "askchallenge":
+ case "askchallenges":
case "askgame":
case "askfullgame": {
const pg = obj.page || page; //required for askidentity and askgame
// Notify all room: mostly game events
case "newchat":
case "newchallenge":
- case "deletechallenge":
+ case "deletechallenge_s":
case "newgame":
case "resign":
case "abort":
// Passing, relaying something: from isn't needed,
// but target is fully identified (sid + tmpId)
- case "challenge":
+ case "challenges":
case "fullgame":
case "game":
case "identity":