<template lang="pug">
main
+ input#modalRules.modal(type="checkbox")
+ div#rulesDiv(
+ role="dialog"
+ data-checkbox="modalRules"
+ )
+ .card
+ label.modal-close(for="modalRules")
+ a#variantNameInGame(:href="'/#/variants/'+game.vname") {{ game.vname }}
+ div(v-html="rulesContent")
input#modalScore.modal(type="checkbox")
div#scoreDiv(
role="dialog"
)
.card.text-center
label.modal-close(for="modalScore")
- p
+ p.score-section
span.score {{ game.score }}
| :
span.score-msg {{ st.tr[game.scoreMsg] }}
button.refuseBtn(@click="cancelMove()")
span {{ st.tr["Cancel"] }}
.row
- #aboveBoard.col-sm-12.col-md-9.col-md-offset-3.col-lg-10.col-lg-offset-2
+ #aboveBoard.col-sm-12
span.variant-cadence(v-if="game.type!='import'") {{ game.cadence }}
span.variant-name {{ game.vname }}
span#nextGame(
@click="showNextGame()"
)
| {{ st.tr["Next_g"] }}
- button#chatBtn.tooltip(
+ button#chatBtn(
+ :class="btnTooltipClass()"
onClick="window.doClick('modalChat')"
aria-label="Chat"
)
img(src="/images/icons/chat.svg")
#actions(v-if="game.score=='*'")
- button.tooltip(
+ button(
@click="clickDraw()"
- :class="{['draw-' + drawOffer]: true}"
+ :class="btnTooltipClass('draw')"
:aria-label="st.tr['Draw']"
)
img(src="/images/icons/draw.svg")
- button.tooltip(
+ button(
v-if="!!game.mycolor"
+ :class="btnTooltipClass()"
@click="abortGame()"
:aria-label="st.tr['Abort']"
)
img(src="/images/icons/abort.svg")
- button.tooltip(
+ button(
v-if="!!game.mycolor"
+ :class="btnTooltipClass()"
@click="resign()"
:aria-label="st.tr['Resign']"
)
img(src="/images/icons/resign.svg")
- button.tooltip(
+ button(
v-else
+ :class="btnTooltipClass('rematch')"
@click="clickRematch()"
- :class="{['rematch-' + rematchOffer]: true}"
:aria-label="st.tr['Rematch']"
)
img(src="/images/icons/rematch.svg")
#playersInfo
- p(v-if="isLargeScreen()")
+ div(v-if="isLargeScreen()")
span.name(:class="{connected: isConnected(0)}")
| {{ game.players[0].name || "@nonymous" }}
span.time(
span.time-separator(v-if="!!virtualClocks[1][1]") :
span.time-right(v-if="!!virtualClocks[1][1]")
| {{ virtualClocks[1][1] }}
- p(v-else)
+ div(v-else)
span.name(:class="{connected: isConnected(0)}")
| {{ game.players[0].name || "@nonymous" }}
span.split-names -
span.name(:class="{connected: isConnected(1)}")
| {{ game.players[1].name || "@nonymous" }}
- br
- span.time(
- v-if="game.score=='*'"
- :class="{yourturn: !!vr && vr.turn == 'w'}"
- )
- span.time-left {{ virtualClocks[0][0] }}
- span.time-separator(v-if="!!virtualClocks[0][1]") :
- span.time-right(v-if="!!virtualClocks[0][1]")
- | {{ virtualClocks[0][1] }}
- span.time(
- v-if="game.score=='*'"
- :class="{yourturn: !!vr && vr.turn == 'b'}"
- )
- span.time-left {{ virtualClocks[1][0] }}
- span.time-separator(v-if="!!virtualClocks[1][1]") :
- span.time-right(v-if="!!virtualClocks[1][1]")
- | {{ virtualClocks[1][1] }}
+ div(v-if="game.score=='*'")
+ span.time(:class="{yourturn: !!vr && vr.turn == 'w'}")
+ span.time-left {{ virtualClocks[0][0] }}
+ span.time-separator(v-if="!!virtualClocks[0][1]") :
+ span.time-right(v-if="!!virtualClocks[0][1]")
+ | {{ virtualClocks[0][1] }}
+ span.separator
+ span.time(:class="{yourturn: !!vr && vr.turn == 'b'}")
+ span.time-left {{ virtualClocks[1][0] }}
+ span.time-separator(v-if="!!virtualClocks[1][1]") :
+ span.time-right(v-if="!!virtualClocks[1][1]")
+ | {{ virtualClocks[1][1] }}
BaseGame(
ref="basegame"
:game="game"
import { getRandString } from "@/utils/alea";
import { getScoreMessage } from "@/utils/scoring";
import { getFullNotation } from "@/utils/notation";
-import { getDiagram } from "@/utils/printDiagram";
+import { getDiagram, replaceByDiag } from "@/utils/printDiagram";
import { processModalClick } from "@/utils/modalClick";
import { playMove, getFilteredMove } from "@/utils/playUndo";
import { ArrayFun } from "@/utils/array";
// virtualClocks will be initialized from true game.clocks
virtualClocks: [],
vr: null, //"variant rules" object initialized from FEN
+ rulesContent: "",
drawOffer: "",
rematchId: "",
rematchOffer: "",
this.toggleChat("close")
});
});
- ["rematchDiv", "scoreDiv"].forEach(
+ ["rulesDiv", "rematchDiv", "scoreDiv"].forEach(
(eltName) => {
document.getElementById(eltName)
.addEventListener("click", processModalClick);
}
);
- if ("ontouchstart" in window) {
- // Disable tooltips on smartphones:
- document.querySelectorAll("#aboveBoard .tooltip").forEach(elt => {
- elt.classList.remove("tooltip");
- });
- }
},
beforeDestroy: function() {
this.cleanBeforeDestroy();
visibilityChange: function() {
// TODO: Use document.hidden? https://webplatform.news/issues/2019-03-27
this.focus = (document.visibilityState == "visible");
- if (!this.focus && !!this.rematchOffer) {
- this.rematchOffer = "";
- this.send("rematchoffer", { data: false });
- // Do not remove rematch offer from (local) storage
- }
this.send(this.focus ? "getfocus" : "losefocus");
},
onFocus: function() {
},
onBlur: function() {
this.focus = false;
- if (!!this.rematchOffer) {
- this.rematchOffer = "";
- this.send("rematchoffer", { data: false });
- }
this.send("losefocus");
},
isLargeScreen: function() {
- return window.innerWidth >= 500;
+ return window.innerWidth >= 768;
+ },
+ btnTooltipClass: function(thing) {
+ let append = {};
+ if (!!thing) append = { [thing + "-" + this[thing + "Offer"]]: true };
+ return (
+ Object.assign(
+ { tooltip: !("ontouchstart" in window) },
+ append
+ )
+ );
},
participateInChat: function(p) {
return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name;
if (!!chatComp) chatComp.chats = [];
this.virtualClocks = [[0,0], [0,0]];
this.vr = null;
+ this.rulesContent = "";
this.drawOffer = "";
this.lastateAsked = false;
this.rematchOffer = "";
isConnected: function(index) {
const player = this.game.players[index];
// Is it me ? In this case no need to bother with focus
- if (this.st.user.sid == player.sid || this.st.user.id == player.id)
+ if (
+ this.st.user.sid == player.sid ||
+ (!!player.name && this.st.user.id == player.id)
+ ) {
// Still have to check for name (because of potential multi-accounts
// on same browser, although this should be rare...)
return (!this.st.user.name || this.st.user.name == player.name);
+ }
// Try to find a match in people:
return (
(
)
||
(
- !!player.id &&
+ player.id > 0 &&
Object.values(this.people).some(p => {
return (
p.id == player.id &&
!this.gotLastate &&
!!this.game.mycolor &&
this.game.type == "live" &&
- this.game.score == "*" &&
this.game.players.some(p => p.sid == user.sid)
) {
this.send("asklastate", { target: user.sid });
if (!this.game || !this.game.moves) this.lastateAsked = true;
else this.sendLastate(data.from);
break;
+ // TODO: possible bad scenario: reload page while oppponent sends a
+ // move => get both lastate and newmove, process both, add move twice.
+ // Confirm scenario? Fix?
case "lastate": {
// Got opponent infos about last move
this.gotLastate = true;
GameStorage.update(this.gameRef, { drawOffer: "" });
}
}
- this.$refs["basegame"].play(
- movePlus.move, "received", null, true);
- this.game.clocks[moveColIdx] = movePlus.clock;
- this.processMove(
- movePlus.move,
- { receiveMyMove: receiveMyMove }
+ this.$refs["basegame"].play(movePlus.move, "received");
+ // Freeze time while the move is being play
+ // (TODO: a callback would be cleaner here)
+ clearInterval(this.clockUpdate);
+ this.clockUpdate = null;
+ const freezeDuration = ["all", "highlight"].includes(V.ShowMoves)
+ // 250 = length of animation, 500 = delay between sub-moves
+ ? 250 + 750 *
+ (Array.isArray(movePlus.move) ? movePlus.move.length - 1 : 0)
+ // Incomplete information: no move animation
+ : 0;
+ setTimeout(
+ () => {
+ this.game.clocks[moveColIdx] = movePlus.clock;
+ this.processMove(
+ movePlus.move,
+ { receiveMyMove: receiveMyMove }
+ );
+ },
+ freezeDuration
);
}
}
if (!!this.game.mycolor && this.game.type == "live") {
GameStorage.update(
this.gameRef,
- { rematchOffer: V.GetOppCol(this.game.mycolor) }
+ { rematchOffer: data.data ? V.GetOppCol(this.game.mycolor) : "" }
);
}
break;
this.addAndGotoLiveGame(gameInfo);
} else if (
gameType == "corr" &&
+ this.st.user.id > 0 &&
gameInfo.players.some(p => p.id == this.st.user.id)
) {
this.$router.push("/game/" + gameInfo.id);
} else {
this.rematchId = gameInfo.id;
+ document.getElementById("modalRules").checked = false;
+ document.getElementById("modalScore").checked = false;
document.getElementById("modalRematch").checked = true;
}
break;
clock: this.game.clocks[myIdx],
// Since we played a move (or abort or resign),
// only drawOffer=="sent" is possible
- drawSent: this.drawOffer == "sent",
- rematchSent: this.rematchOffer == "sent",
+ drawSent: this.drawOffer == "sent" ? true : undefined,
+ rematchSent: this.rematchOffer == "sent" ? true : undefined,
score: this.game.score != "*" ? this.game.score : undefined,
scoreMsg: this.game.score != "*" ? this.game.scoreMsg : undefined,
movesCount: L
processLastate: function() {
const data = this.lastate;
this.lastate = undefined; //security...
- const L = this.game.moves.length;
- const oppIdx = 1 - ["w", "b"].indexOf(this.game.mycolor);
- this.game.clocks[oppIdx] = data.clock;
- if (data.movesCount > L) {
- // Just got last move from him
- this.$refs["basegame"].play(data.lastMove, "received", null, true);
- this.processMove(data.lastMove);
- } else {
- if (!!this.clockUpdate) clearInterval(this.clockUpdate);
- this.re_setClocks();
- }
- if (data.drawSent) this.drawOffer = "received";
- if (data.rematchSent) this.rematchOffer = "received";
if (!!data.score) {
- this.drawOffer = "";
- if (this.game.score == "*")
- this.gameOver(data.score, data.scoreMsg);
+ const oppCol = V.GetOppCol(this.game.mycolor);
+ if (!!data.rematchSent) {
+ if (this.game.rematchOffer != oppCol) {
+ // Opponent sended rematch offer while we were offline:
+ this.rematchOffer = "received";
+ GameStorage.update(
+ this.gameRef,
+ { rematchOffer: oppCol }
+ );
+ }
+ }
+ else {
+ if (this.game.rematchOffer == oppCol) {
+ // Opponent cancelled rematch offer while we were offline:
+ this.rematchOffer = "";
+ GameStorage.update(
+ this.gameRef,
+ { rematchOffer: "" }
+ );
+ }
+ }
+ }
+ else {
+ const L = this.game.moves.length;
+ const oppIdx = 1 - ["w", "b"].indexOf(this.game.mycolor);
+ this.game.clocks[oppIdx] = data.clock;
+ if (data.movesCount > L) {
+ // Just got last move from him
+ this.$refs["basegame"].play(data.lastMove, "received");
+ this.processMove(data.lastMove);
+ } else {
+ if (!!this.clockUpdate) clearInterval(this.clockUpdate);
+ this.re_setClocks();
+ }
+ if (!!data.drawSent) this.drawOffer = "received";
+ if (!!data.score) {
+ this.drawOffer = "";
+ if (this.game.score == "*")
+ this.gameOver(data.score, data.scoreMsg);
+ }
}
},
clickDraw: function() {
let gameInfo = {
id: getRandString(), //ignored if corr
fen: V.GenRandInitFen(this.game.randomness),
- players: this.game.players.reverse(),
+ players: [this.game.players[1], this.game.players[0]],
vid: this.game.vid,
cadence: this.game.cadence
};
const gtype = game.type || this.getGameType(game);
const tc = extractTime(game.cadence);
const myIdx = game.players.findIndex(p => {
- return p.sid == this.st.user.sid || p.id == this.st.user.id;
+ return (
+ p.sid == this.st.user.sid ||
+ (!!p.name && p.id == this.st.user.id)
+ );
});
// Sometimes the name isn't stored yet (TODO: why?)
if (
if (
(game.rematchOffer == "w" && myIdx == 0) ||
(game.rematchOffer == "b" && myIdx == 1)
- )
+ ) {
this.rematchOffer = "sent";
+ }
else this.rematchOffer = "received";
}
}
window.V = vModule[game.vname + "Rules"];
this.loadGame(game, callback);
});
+ // (AJAX) Request to get rules content (plain text, HTML)
+ this.rulesContent =
+ require(
+ "raw-loader!@/translations/rules/" +
+ game.vname + "/" +
+ this.st.lang + ".pug"
+ )
+ // Next two lines fix a weird issue after last update (2019-11)
+ .replace(/\\n/g, " ")
+ .replace(/\\"/g, '"')
+ .replace('module.exports = "', "")
+ .replace(/"$/, "")
+ .replace(/(fen:)([^:]*):/g, replaceByDiag);
},
// 3 cases for loading a game:
// - from indexedDB (running or completed live game I play)
this.game.score = score;
if (!scoreMsg) scoreMsg = getScoreMessage(score);
this.game.scoreMsg = scoreMsg;
+ document.getElementById("modalRules").checked = false;
// Display result in a un-missable way:
document.getElementById("modalScore").checked = true;
this.$set(this.game, "scoreMsg", scoreMsg);
const myIdx = this.game.players.findIndex(p => {
- return p.sid == this.st.user.sid || p.id == this.st.user.id;
+ return (
+ p.sid == this.st.user.sid ||
+ (!!p.name && p.id == this.st.user.id)
+ );
});
if (myIdx >= 0) {
// OK, I play in this game
<style lang="sass" scoped>
#scoreDiv > .card, #rematchDiv > .card
- padding: 15px 0
+ padding: 10px 0
max-width: 430px
-span.score
- font-weight: bold
+#rulesDiv > .card
+ padding: 5px 0
+ max-width: 50%
+ max-height: 100%
+ @media screen and (max-width: 1500px)
+ max-width: 67%
+ @media screen and (max-width: 1024px)
+ max-width: 85%
+ @media screen and (max-width: 767px)
+ max-width: 100%
+
+p.score-section
+ margin: 0
+ font-size: 1.3em
+ span.score
+ font-weight: bold
.connected
background-color: lightgreen
#playersInfo > p
margin: 0
-@media screen and (min-width: 768px)
- #actions
- width: 300px
@media screen and (max-width: 767px)
.game
width: 100%
@media screen and (max-width: 767px)
height: 18px
-@media screen and (max-width: 767px)
- #aboveBoard
- text-align: center
-@media screen and (min-width: 768px)
- #aboveBoard
- margin-left: 30%
+#aboveBoard
+ text-align: center
.variant-cadence
padding-right: 10px
display: inline-block
margin-right: 10px
+span.separator
+ display: inline-block
+ margin: 0
+ padding: 0
+ width: 10px
+
span.name
font-size: 1.5rem
+ @media screen and (max-width: 767px)
+ font-size: 1.2rem
padding: 0 3px
span.time
font-size: 2rem
+ @media screen and (max-width: 767px)
+ font-size: 1.5rem
display: inline-block
.time-left
margin-left: 10px
background-color: lightyellow
.draw-received, .draw-received:hover
- background-color: lightgreen
+ background-color: #73C6B6
.draw-threerep, .draw-threerep:hover
- background-color: #e4d1fc
+ background-color: #D2B4DE
.rematch-sent, .rematch-sent:hover
background-color: lightyellow
.rematch-received, .rematch-received:hover
- background-color: lightgreen
+ background-color: #48C9B0
.somethingnew
- background-color: #c5fefe
+ background-color: #D2B4DE
.diagram
margin: 0 auto
background-color: lightgreen
button.refuseBtn
background-color: red
+
+a#variantNameInGame
+ color: var(--card-fore-color)
+ text-align: center
+ font-weight: bold
+ font-size: calc(1rem * var(--heading-ratio))
+ line-height: 1.2
+ margin: calc(1.5 * var(--universal-margin))
+</style>
+
+<style lang="sass">
+@import "@/styles/_rules.sass"
+@import "@/styles/_board_squares_img.sass"
</style>