From eb2d61de8d569470fa329a484efe9bab420b2b82 Mon Sep 17 00:00:00 2001 From: Benjamin Auder <benjamin.auder@somewhere> Date: Thu, 8 Apr 2021 21:41:58 +0200 Subject: [PATCH] Experimental change: options replacing randomness (more general) --- TODO | 11 + client/public/images/pieces/Fugue/wl.svg | 19 +- client/src/App.vue | 4 - client/src/base_rules.js | 30 +- client/src/components/ChallengeList.vue | 13 +- client/src/components/ComputerGame.vue | 4 +- client/src/components/GameList.vue | 2 +- client/src/components/Settings.vue | 8 +- client/src/main.js | 4 +- client/src/store.js | 4 - client/src/translations/about/en.pug | 2 + client/src/translations/about/es.pug | 2 + client/src/translations/about/fr.pug | 2 + client/src/translations/en.js | 17 +- client/src/translations/es.js | 17 +- client/src/translations/fr.js | 17 +- .../rules/{Allmate1 => Allmate}/en.pug | 2 +- .../rules/{Allmate1 => Allmate}/es.pug | 2 +- .../rules/{Allmate1 => Allmate}/fr.pug | 2 +- client/src/translations/rules/Allmate2/en.pug | 22 - client/src/translations/rules/Allmate2/es.pug | 24 - client/src/translations/rules/Allmate2/fr.pug | 24 - .../rules/{Checkered1 => Checkered}/en.pug | 3 + .../rules/{Checkered1 => Checkered}/es.pug | 4 + .../rules/{Checkered1 => Checkered}/fr.pug | 4 + .../src/translations/rules/Checkered2/en.pug | 10 - .../src/translations/rules/Checkered2/es.pug | 10 - .../src/translations/rules/Checkered2/fr.pug | 10 - .../rules/{Chess => Chess960}/en.pug | 2 +- .../rules/{Chess => Chess960}/es.pug | 2 +- .../rules/{Chess => Chess960}/fr.pug | 2 +- .../src/variants/{Allmate1.js => Allmate.js} | 2 +- client/src/variants/Allmate2.js | 236 --------- .../variants/{Checkered1.js => Checkered.js} | 42 +- client/src/variants/Checkered2.js | 472 ------------------ client/src/variants/Chess.js | 1 - client/src/variants/Chess960.js | 23 + client/src/views/Analyse.vue | 3 +- client/src/views/Game.vue | 26 +- client/src/views/Hall.vue | 117 +++-- client/src/views/Rules.vue | 94 +++- client/src/views/Variants.vue | 4 +- server/db/create.sql | 4 +- server/db/populate.sql | 8 +- server/models/Challenge.js | 13 +- server/models/Game.js | 13 +- server/routes/challenges.js | 1 + 47 files changed, 375 insertions(+), 963 deletions(-) rename client/src/translations/rules/{Allmate1 => Allmate}/en.pug (95%) rename client/src/translations/rules/{Allmate1 => Allmate}/es.pug (99%) rename client/src/translations/rules/{Allmate1 => Allmate}/fr.pug (96%) delete mode 100644 client/src/translations/rules/Allmate2/en.pug delete mode 100644 client/src/translations/rules/Allmate2/es.pug delete mode 100644 client/src/translations/rules/Allmate2/fr.pug rename client/src/translations/rules/{Checkered1 => Checkered}/en.pug (97%) rename client/src/translations/rules/{Checkered1 => Checkered}/es.pug (97%) rename client/src/translations/rules/{Checkered1 => Checkered}/fr.pug (97%) delete mode 100644 client/src/translations/rules/Checkered2/en.pug delete mode 100644 client/src/translations/rules/Checkered2/es.pug delete mode 100644 client/src/translations/rules/Checkered2/fr.pug rename client/src/translations/rules/{Chess => Chess960}/en.pug (99%) rename client/src/translations/rules/{Chess => Chess960}/es.pug (99%) rename client/src/translations/rules/{Chess => Chess960}/fr.pug (99%) rename client/src/variants/{Allmate1.js => Allmate.js} (99%) delete mode 100644 client/src/variants/Allmate2.js rename client/src/variants/{Checkered1.js => Checkered.js} (95%) delete mode 100644 client/src/variants/Checkered2.js delete mode 120000 client/src/variants/Chess.js create mode 100644 client/src/variants/Chess960.js diff --git a/TODO b/TODO index d3b62270..924b6d0e 100644 --- a/TODO +++ b/TODO @@ -20,4 +20,15 @@ CWDA : need game options (also useful at least for Monster) PizzaKings https://www.chessvariants.com/unequal.dir/pizza-kings.html https://en.m.wikipedia.org/wiki/Chess_with_different_armies#Pizza_Kings%5B11%5D_(John_Lawson) +Coin Chess +https://msoworld.com/product/chess-variants/ +Background: Unknown inventor +Rules: +Normal rules apply except for the introduction of a coin (or counter) +Black starts by placing the coin on any unoccupied square. Play then continues with players alternating turns as in normal chess. +On their turn, a player makes any legal move but may not move onto the square where the coin has been placed. The player may move over (but not onto), the coin square. +The player ends their turn by leaving the coin where it stands or moving the coin to a different unoccupied square. +The coin can never be placed on an occupied square, and therefore cannot be used to protect a piece from capture +A player wins by checkmating the opponent. Note that the coin can be used to remove escape squares from the king. + https://www.chessvariants.com/other.dir/nemoroth.html :-) diff --git a/client/public/images/pieces/Fugue/wl.svg b/client/public/images/pieces/Fugue/wl.svg index d73d3bdb..cc2b9749 100644 --- a/client/public/images/pieces/Fugue/wl.svg +++ b/client/public/images/pieces/Fugue/wl.svg @@ -13,7 +13,7 @@ version="1.1" id="svg12" sodipodi:docname="wl.svg" - inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)"> + inkscape:version="1.0.2 (e86c870879, 2021-01-15)"> <metadata id="metadata18"> <rdf:RDF> @@ -36,17 +36,18 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="1920" + inkscape:window-width="960" inkscape:window-height="1060" id="namedview14" showgrid="false" inkscape:zoom="1.7246094" inkscape:cx="256" - inkscape:cy="255.42016" + inkscape:cy="254.84032" inkscape:window-x="0" inkscape:window-y="20" inkscape:window-maximized="0" - inkscape:current-layer="svg12" /> + inkscape:current-layer="svg12" + inkscape:document-rotation="0" /> <g id="010---Frog" transform="matrix(0.8,0,0,0.8,6.0000003,6)"> @@ -86,4 +87,14 @@ d="m 337.81831,445.85023 c -10.4388,-2.80716 -18.34008,-11.95076 -19.75585,-22.86208 -0.3099,-2.38844 -0.57152,-7.14629 -0.58136,-10.573 l -0.0179,-6.23038 2.94262,-3.62692 c 3.89557,-4.80148 8.76783,-12.99552 11.31926,-19.03639 3.94316,-9.33604 6.61242,-22.77207 6.61242,-33.28449 0,-3.51978 0.11123,-3.78972 3.5586,-8.63619 5.97066,-8.39384 12.04985,-15.26538 22.90012,-25.88492 18.58016,-18.18504 33.22365,-27.66968 49.66778,-32.16997 2.66295,-0.72877 3.59096,-1.30322 4.49161,-2.78035 2.51574,-4.12599 0.70172,-9.36723 -3.75061,-10.83664 -4.93204,-1.62772 -21.05317,4.81069 -34.44901,13.75814 -9.69646,6.47653 -16.23253,12.10004 -29.43403,25.32449 l -12.81849,12.84076 -0.37277,-7.36534 c -0.20502,-4.05093 -0.51019,-7.72345 -0.67815,-8.16114 -0.67475,-1.75837 15.29792,-17.9046 26.1136,-26.39736 18.48353,-14.51375 38.21698,-23.34861 52.15124,-23.34861 6.54713,0 10.13163,1.11515 13.08827,4.07179 2.89575,2.89576 4.15016,6.83571 4.13303,12.98141 -0.0522,18.7298 -14.70341,45.11564 -38.04471,68.51594 -9.49806,9.52206 -16.97344,15.60664 -28.544,23.23334 -8.35929,5.51 -10.87871,7.93729 -12.7369,12.2711 -4.26923,9.95703 0.0894,22.23736 9.5458,26.89528 l 2.72108,1.34031 37.97962,0.28993 c 36.08008,0.27541 38.06085,0.34497 39.60381,1.39075 3.07868,2.08662 4.02373,5.39369 2.42488,8.48553 -1.82084,3.52113 -1.18172,3.46007 -36.22011,3.46007 -35.03251,0 -35.03094,-1.5e-4 -36.79159,3.40457 -2.21954,4.29214 -0.22935,9.26049 4.20261,10.49143 1.27878,0.35517 10.71904,0.59487 23.5043,0.5968 l 21.36715,0.003 1.93729,1.93728 c 2.46615,2.46616 2.95099,4.36523 1.75687,6.88163 -1.96428,4.13942 0.28881,3.94194 -44.48849,3.89944 -33.22344,-0.0315 -40.75306,-0.18432 -43.33798,-0.87945 z" id="path26" transform="scale(0.1171875)" /> + <path + style="fill:#ffffff;stroke:#000000;stroke-width:0.579841" + d="m 194.60655,118.19666 c -2.41356,-1.22724 -5.07782,-4.80585 -5.93454,-7.97119 -1.03527,-3.82506 -0.3344,-9.24725 1.59667,-12.35241 2.34703,-3.774029 4.34459,-5.098424 7.68987,-5.098424 3.50397,0 6.34607,2.039401 8.26069,5.927609 1.92065,3.900445 1.91919,10.324345 -0.003,14.237645 -2.51645,5.12252 -7.46596,7.36366 -11.60946,5.25677 z" + id="path16" + transform="scale(0.1171875)" /> + <path + style="fill:#ffffff;stroke:#000000;stroke-width:0.579841" + d="m 310.57484,118.19666 c -5.83545,-2.96721 -8.15717,-12.49955 -4.75579,-19.525881 1.86272,-3.84785 4.71914,-5.896143 8.2224,-5.896143 3.37415,0 5.34355,1.325238 7.77055,5.228919 1.58018,2.541625 1.73953,3.253555 1.73953,7.772045 0,4.38385 -0.18949,5.29646 -1.5968,7.69048 -2.88247,4.90343 -7.3707,6.76917 -11.37989,4.73058 z" + id="path18" + transform="scale(0.1171875)" /> </svg> diff --git a/client/src/App.vue b/client/src/App.vue index 67dc225b..b17133d3 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -44,10 +44,6 @@ a.menuitem(href="https://github.com/yagu0/vchess") span {{ st.tr["Code"] }} img(src="/images/icons/github.svg") - //a.menuitem(href="https://www.facebook.com/Variants-Chess-Club-112565840437886") - img(src="/images/icons/facebook.svg") - //a.menuitem(href="https://twitter.com/VchessC") - img(src="/images/icons/twitter.svg") </template> <script> diff --git a/client/src/base_rules.js b/client/src/base_rules.js index f7a2fee1..43f9f266 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -34,6 +34,30 @@ export const ChessRules = class ChessRules { ////////////// // MISC UTILS + static get Options() { + return { + select: [ + { + label: "Randomness", + variable: "randomness", + defaut: 2, + options: [ + { label: "Deterministic", value: 0 }, + { label: "Symmetric random", value: 1 }, + { label: "Asymmetric random", value: 2 } + ] + } + ], + check: [] + }; + } + + static AbbreviateOptions(opts) { + return ""; + // Randomness is a special option: (TODO?) + //return "R" + opts.randomness; + } + // Some variants don't have flags: static get HasFlags() { return true; @@ -343,8 +367,8 @@ export const ChessRules = class ChessRules { // FEN UTILS // Setup the initial random (asymmetric) position - static GenRandInitFen(randomness) { - if (randomness == 0) + static GenRandInitFen(options) { + if (!options.randomness || options.randomness == 0) // Deterministic: return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -"; @@ -352,7 +376,7 @@ export const ChessRules = class ChessRules { let flags = ""; // Shuffle pieces on first (and last rank if randomness == 2) for (let c of ["w", "b"]) { - if (c == 'b' && randomness == 1) { + if (c == 'b' && options.randomness == 1) { pieces['b'] = pieces['w']; flags += flags; break; diff --git a/client/src/components/ChallengeList.vue b/client/src/components/ChallengeList.vue index 1107b58c..a354203b 100644 --- a/client/src/components/ChallengeList.vue +++ b/client/src/components/ChallengeList.vue @@ -6,7 +6,7 @@ div th {{ st.tr["Variant"] }} th {{ st.tr["With"] }} th {{ st.tr["Cadence"] }} - th {{ st.tr["Random?"] }} + th {{ st.tr["Options"] }} tbody tr( v-for="c in sortedChallenges" @@ -16,7 +16,7 @@ div td {{ c.vname }} td {{ withWho(c) }} td {{ c.cadence }} - td(:class="getRandomnessClass(c)") + td(:class="getRandomnessClass(c)") {{ c.options.abridged }} p(v-else) | {{ st.tr["No challenges found :( Click on 'New game'!"] }} </template> @@ -63,8 +63,9 @@ export default { return c.from.name || "@nonymous"; }, getRandomnessClass: function(c) { + if (!c.options.randomness) return {}; return { - ["random-" + c.randomness]: true + ["random-" + c.options.randomness]: true }; } } @@ -84,9 +85,9 @@ tr.toyou > td tr > td:last-child &.random-0 - background-color: #FF5733 + background-color: #FEAF9E &.random-1 - background-color: #2B63B4 + background-color: #9EB2FE &.random-2 - background-color: #33B42B + background-color: #A5FE9E </style> diff --git a/client/src/components/ComputerGame.vue b/client/src/components/ComputerGame.vue index 91689229..b212d0fd 100644 --- a/client/src/components/ComputerGame.vue +++ b/client/src/components/ComputerGame.vue @@ -63,12 +63,12 @@ export default { }; }, methods: { - launchGame: function(game) { + launchGame: function(game, options) { this.compWorker.postMessage(["scripts", this.gameInfo.vname]); if (!game) { game = { vname: this.gameInfo.vname, - fenStart: V.GenRandInitFen(this.st.settings.randomness), + fenStart: V.GenRandInitFen(options), moves: [] }; game.fen = game.fenStart; diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue index d81a1de7..ed6b99fa 100644 --- a/client/src/components/GameList.vue +++ b/client/src/components/GameList.vue @@ -13,7 +13,7 @@ div @click="$emit('show-game',g)" :class="{'my-turn': !!g.myTurn}" ) - td {{ g.vname }} + td {{ g.vname + '-' + g.options.abridged }} td {{ player_s(g) }} td(v-if="showCadence") {{ g.cadence }} td( diff --git a/client/src/components/Settings.vue b/client/src/components/Settings.vue index 415e9d40..514e7049 100644 --- a/client/src/components/Settings.vue +++ b/client/src/components/Settings.vue @@ -60,12 +60,6 @@ div type="checkbox" v-model="st.settings.gotonext" ) - fieldset - label(for="setRandomness") {{ st.tr["Randomness"] }} - select#setRandomness(v-model="st.settings.randomness") - option(value="0") {{ st.tr["Deterministic"] }} - option(value="1") {{ st.tr["Symmetric random"] }} - option(value="2") {{ st.tr["Asymmetric random"] }} </template> <script> @@ -100,7 +94,7 @@ export default { const propName = event.target.id .substr(3) .replace(/^\w/, c => c.toLowerCase()); - const value = ["bcolor","randomness"].includes(propName) + const value = propName == "bcolor" ? event.target.value : event.target.checked; store.updateSetting(propName, value); diff --git a/client/src/main.js b/client/src/main.js index 3577b155..b0f9ddcc 100644 --- a/client/src/main.js +++ b/client/src/main.js @@ -22,8 +22,8 @@ new Vue({ modalBoxes.forEach(m => { if ( m.checked && - !["modalAccept", "modalConfirm", "modalChat", "modalPeople"] - .includes(m.id) + !["Accept", "Confirm", "Chat", "People"] + .includes(m.id.substr(5)) //modalThing --> Thing ) { m.checked = false; } diff --git a/client/src/store.js b/client/src/store.js index 45535f84..13c600b7 100644 --- a/client/src/store.js +++ b/client/src/store.js @@ -90,11 +90,7 @@ export const store = { hints: getItemDefaultTrue("hints"), highlight: getItemDefaultTrue("highlight"), gotonext: getItemDefaultTrue("gotonext"), - randomness: parseInt(localStorage.getItem("randomness"), 10) }; - if (isNaN(this.state.settings.randomness)) - // Default: random asymmetric - this.state.settings.randomness = 2; const supportedLangs = ["en", "es", "fr"]; const navLanguage = navigator.language.substr(0, 2); this.state.lang = diff --git a/client/src/translations/about/en.pug b/client/src/translations/about/en.pug index 49795754..82fabd0e 100644 --- a/client/src/translations/about/en.pug +++ b/client/src/translations/about/en.pug @@ -50,8 +50,10 @@ h3 Related links a(href="https://mindsports.nl/index.php") mindsports.nl a(href="https://www.jocly.com/#/games") jocly.com a(href="http://www.iggamecenter.com/") iggamecenter.com + a(href="https://boardspace.net/english/index.shtml") boardspace.net a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com + a(href="https://boardgamearena.com/") boardgamearena.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net a(href="https://brainking.com/") brainking.com diff --git a/client/src/translations/about/es.pug b/client/src/translations/about/es.pug index 4b08b48c..95f69c06 100644 --- a/client/src/translations/about/es.pug +++ b/client/src/translations/about/es.pug @@ -49,8 +49,10 @@ h3 Enlaces relacionados a(href="https://mindsports.nl/index.php") mindsports.nl a(href="https://www.jocly.com/#/games") jocly.com a(href="http://www.iggamecenter.com/") iggamecenter.com + a(href="https://boardspace.net/english/index.shtml") boardspace.net a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com + a(href="https://boardgamearena.com/") boardgamearena.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net a(href="https://brainking.com/") brainking.com diff --git a/client/src/translations/about/fr.pug b/client/src/translations/about/fr.pug index cf58ca77..eb47ccd3 100644 --- a/client/src/translations/about/fr.pug +++ b/client/src/translations/about/fr.pug @@ -50,8 +50,10 @@ h3 Liens connexes a(href="https://mindsports.nl/index.php") mindsports.nl a(href="https://www.jocly.com/#/games") jocly.com a(href="http://www.iggamecenter.com/") iggamecenter.com + a(href="https://boardspace.net/english/index.shtml") boardspace.net a(href="https://musketeerchess.net/home/index.html") musketeerchess.net a(href="https://schemingmind.com/") schemingmind.com + a(href="https://boardgamearena.com/") boardgamearena.com a(href="https://echekk.fr/spip.php?page=rubrique&id_rubrique=1") echekk.fr a(href="http://www.strategems.net/sections/fairy_defs.html") strategems.net a(href="https://brainking.com/") brainking.com diff --git a/client/src/translations/en.js b/client/src/translations/en.js index b8c1cd94..133a0de6 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -90,7 +90,7 @@ export const translations = { "No more problems": "No more problems", "No subject. Send anyway?": "No subject. Send anyway?", "Notifications by email": "Notifications by email", - Number: "Number", + "Number": "Number", Observe: "Observe", "Offer draw?": "Offer draw?", "Opponent action": "Opponent action", @@ -108,8 +108,6 @@ export const translations = { Previous_p: "Previous", "Processing... Please wait": "Processing... Please wait", Problems: "Problems", - "Random?": "Random?", - "Randomness": "Randomness", Refuse: "Refuse", Register: "Register", "Registration complete! Please check your emails now": "Registration complete! Please check your emails now", @@ -260,8 +258,7 @@ export const translations = { "Lose all pieces": "Lose all pieces", "Malagasy Draughts": "Malagasy Draughts", "Mandatory captures": "Mandatory captures", - "Mate any piece (v1)": "Mate any piece (v1)", - "Mate any piece (v2)": "Mate any piece (v2)", + "Mate any piece": "Mate any piece", "Mate the knight (v1)": "Mate the knight (v1)", "Mate the knight (v2)": "Mate the knight (v2)", "Meet the Mammoth": "Meet the Mammoth", @@ -313,8 +310,7 @@ export const translations = { "Run forward": "Run forward", "Score a goal": "Score a goal", "Seirawan-Harper Chess": "Seirawan-Harper Chess", - "Shared pieces (v1)": "Shared pieces (v1)", - "Shared pieces (v2)": "Shared pieces (v2)", + "Shared pieces": "Shared pieces", "Shogi 5 x 5": "Shogi 5 x 5", "Shoot pieces": "Shoot pieces", "Spartan versus Persians": "Spartan versus Persians", @@ -343,7 +339,7 @@ export const translations = { // Variants by categories: "What is a chess variant?": "What is a chess variant?", "Why play chess variants?": "Why play chess variants?", - "chess_v": ": to play under standard rules, with a random (or not) symmetric (or not) initial position.", + "chess960_v": ": to play under standard rules, with a random symmetric (or not) initial position.", "vt0": "Simplified games to learn chess", "vg0": "Variants with very few different pieces, and a simplified goal.", "vt1": "Forced captures", @@ -408,4 +404,9 @@ export const translations = { "vg30": "Pieces can temporarily borrow powers from others.", "vt31": "Miscelleanous", "vg31": "These variants are not classified yet, generally because they are the only one of their kind on this website.", + + // Variants' options + "Options": "Options", + "Randomness": "Randomness", + "With switch": "With switch", }; diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 55dd3045..d3be2c1f 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -90,7 +90,7 @@ export const translations = { "No more problems": "No mas problemas", "No subject. Send anyway?": "Sin asunto. ¿Enviar sin embargo?", "Notifications by email": "Notificaciones por email", - Number: "Número", + "Number": "Número", "Offer draw?": "¿Ofrecer tablas?", Observe: "Observar", "Opponent action": "Acción del adversario", @@ -108,8 +108,6 @@ export const translations = { Previous_n: "Anterior", "Processing... Please wait": "Procesando... por favor espere", Problems: "Problemas", - "Random?": "Aleatorio?", - "Randomness": "Grado de azar", Refuse: "Rechazar", Register: "Inscribirse", "Registration complete! Please check your emails now": "¡Registro completo! Revise sus correos electrónicos ahora", @@ -260,8 +258,7 @@ export const translations = { "Lose all pieces": "Perder todas las piezas", "Malagasy Draughts": "Damas malgaches", "Mandatory captures": "Capturas obligatorias", - "Mate any piece (v1)": "Matar cualquier pieza (v1)", - "Mate any piece (v2)": "Matar cualquier pieza (v2)", + "Mate any piece": "Matar cualquier pieza", "Mate the knight (v1)": "Matar el caballo (v1)", "Mate the knight (v2)": "Matar el caballo (v2)", "Meet the Mammoth": "Conoce al Mamut", @@ -313,8 +310,7 @@ export const translations = { "Run forward": "Correr hacia adelante", "Score a goal": "Marcar una meta", "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper", - "Shared pieces (v1)": "Piezas compartidas (v1)", - "Shared pieces (v2)": "Piezas compartidas (v2)", + "Shared pieces": "Piezas compartidas", "Shogi 5 x 5": "Shogi 5 x 5", "Shoot pieces": "Tirar de las piezas", "Spartan versus Persians": "Espartanos contra Persas", @@ -343,7 +339,7 @@ export const translations = { // Variants by categories: "What is a chess variant?": "¿Qué es una variante?", "Why play chess variants?": "¿Por qué jugar las variantes?", - "chess_v": ": para jugar con reglas estándar, desde una posición inicial aleatorio (o no) simétrico (o no).", + "chess960_v": ": para jugar con reglas estándar, desde una posición inicial aleatorio simétrico (o no).", "vt0": "Juegos simplificados para aprender ajedrez", "vg0": "Variantes con muy pocas piezas diferentes y un propósito simplificado.", "vt1": "Capturas obligatorias", @@ -408,4 +404,9 @@ export const translations = { "vg30": "Las piezas pueden temporalmente tomar prestados poderes de otras", "vt31": "Varios", "vg31": "Estas variantes aún no están clasificadas, en general porque son el único representante de su tipo en este sitio.", + + // Variants' options + "Options": "Optiones", + "Randomness": "Grado de azar", + "With switch": "Con switch", }; diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 7f5f8fd6..3cef19e5 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -90,7 +90,7 @@ export const translations = { "No more problems": "Plus de problèmes", "No subject. Send anyway?": "Pas de sujet. Envoyer quand-même ??", "Notifications by email": "Notifications par email", - Number: "Numéro", + "Number": "Numéro", "Offer draw?": "Proposer nulle ?", Observe: "Observer", "Opponent action": "Action de l'adversaire", @@ -108,8 +108,6 @@ export const translations = { Previous_n: "Précédente", "Processing... Please wait": "Traitement en cours... Attendez SVP", Problems: "Problèmes", - "Random?": "Aléatoire?", - "Randomness": "Degré d'aléa", Refuse: "Refuser", Register: "S'inscrire", "Registration complete! Please check your emails now": "Enregistrement terminé ! Allez voir vos emails maintenant", @@ -260,8 +258,7 @@ export const translations = { "Lose all pieces": "Perdez toutes les pièces", "Malagasy Draughts": "Dames malgaches", "Mandatory captures": "Captures obligatoires", - "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)", - "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)", + "Mate any piece": "Matez n'importe quelle pièce", "Mate the knight (v1)": "Matez le cavalier (v1)", "Mate the knight (v2)": "Matez le cavalier (v2)", "Meet the Mammoth": "Rencontrez le Mammouth", @@ -313,8 +310,7 @@ export const translations = { "Run forward": "Courir vers l'avant", "Score a goal": "Marquer un but", "Seirawan-Harper Chess": "Ãchecs Seirawan-Harper", - "Shared pieces (v1)": "Pièces partagées (v1)", - "Shared pieces (v2)": "Pièces partagées (v2)", + "Shared pieces": "Pièces partagées", "Shogi 5 x 5": "Shogi 5 x 5", "Shoot pieces": "Tirez sur les pièces", "Spartan versus Persians": "Spartiates contre Perses", @@ -343,7 +339,7 @@ export const translations = { // Variants by categories: "What is a chess variant?": "Qu'est-ce qu'une variante ?", "Why play chess variants?": "Pourquoi jouer aux variantes ?", - "chess_v": " : pour jouer avec les règles standard, depuis une position initiale aléatoire (ou non) symétrique (ou non).", + "chess960_v": " : pour jouer avec les règles standard, depuis une position initiale aléatoire symétrique (ou non).", "vt0": "Jeux simplifiés pour apprendre les échecs", "vg0": "Variantes avec très peu de pièces différentes, et un but simplifié.", "vt1": "Captures obligatoires", @@ -408,4 +404,9 @@ export const translations = { "vg30": "Les pièces peuvent temporairement emprunter des pouvoir aux autres", "vt31": "Divers", "vg31": "Ces variantes ne sont pas encore classées, en général car elles sont l'unique représentant de leur type sur ce site.", + + // Variants' options + "Options": "Options", + "Randomness": "Degré d'aléa", + "With switch": "Avec switch", }; diff --git a/client/src/translations/rules/Allmate1/en.pug b/client/src/translations/rules/Allmate/en.pug similarity index 95% rename from client/src/translations/rules/Allmate1/en.pug rename to client/src/translations/rules/Allmate/en.pug index fabc8af2..adf8d0d4 100644 --- a/client/src/translations/rules/Allmate1/en.pug +++ b/client/src/translations/rules/Allmate/en.pug @@ -36,7 +36,7 @@ figure.diagram-container p. Note about moves notation: since captures do not occur on final squares, an "X" mark is appended to the move to indicate a capture. - To keep notation short the potential list of captured squares is not written. + To keep notation short the list of captured squares is not written. h3 "Suicide" note diff --git a/client/src/translations/rules/Allmate1/es.pug b/client/src/translations/rules/Allmate/es.pug similarity index 99% rename from client/src/translations/rules/Allmate1/es.pug rename to client/src/translations/rules/Allmate/es.pug index d6ba11ba..4365f4fb 100644 --- a/client/src/translations/rules/Allmate1/es.pug +++ b/client/src/translations/rules/Allmate/es.pug @@ -37,7 +37,7 @@ figure.diagram-container p. Nota sobre la notación de las jugadas: ya que las capturas no se realizan en la casilla de llegada, se agrega una marca "X" al movimiento para indicar - la captura. Para evitar las notaciones demasiado largas, la lista potencial + la captura. Para evitar las notaciones demasiado largas, la lista de casillas de captura no está escrita. h3 Nota "suicidio" diff --git a/client/src/translations/rules/Allmate1/fr.pug b/client/src/translations/rules/Allmate/fr.pug similarity index 96% rename from client/src/translations/rules/Allmate1/fr.pug rename to client/src/translations/rules/Allmate/fr.pug index d3962336..8b6619f0 100644 --- a/client/src/translations/rules/Allmate1/fr.pug +++ b/client/src/translations/rules/Allmate/fr.pug @@ -38,7 +38,7 @@ figure.diagram-container p. Note sur la notation des coups: puisque les captures ne s'effectuent pas sur la case d'arrivée, une marque "X" est ajoutée au coup pour indiquer la - capture. Afin d'éviter les notations à rallonge la liste potentielle des + capture. Afin d'éviter les notations à rallonge la liste des cases de capture n'est pas écrite. h3 Note "suicide" diff --git a/client/src/translations/rules/Allmate2/en.pug b/client/src/translations/rules/Allmate2/en.pug deleted file mode 100644 index 70fdafee..00000000 --- a/client/src/translations/rules/Allmate2/en.pug +++ /dev/null @@ -1,22 +0,0 @@ -p.boxed - | An attacked piece is taken if the capture cannot - | be prevented with non-capturing moves. - -p. - This is the Allmate1 variant, with a weaker mating condition: - capturing moves to escape from checkmate are not considered. - (Mate-)Capturing is thus easier: on the next diagram, 1.Qe6 captures - the d7, e7 and f7 pawns. - -figure.diagram-container - .diagram.diag12 - | fen:4k3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3: - .diagram.diag22 - | fen:4k3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3: - figcaption Before and after 1.Qe6X - -p Since captures are much easier, they don't cascade as in Allmate1. - -h3 Source - -p See Allmate1 variant. diff --git a/client/src/translations/rules/Allmate2/es.pug b/client/src/translations/rules/Allmate2/es.pug deleted file mode 100644 index df2defda..00000000 --- a/client/src/translations/rules/Allmate2/es.pug +++ /dev/null @@ -1,24 +0,0 @@ -p.boxed - | Se captura una pieza atacada si no se puede evitar - | la toma por movimientos sin captura. - -p. - Esta es la variante Allmate1, con una condición de mate más - débil: no se consideran las capturas para escapar de un jaque mate. - Por lo tanto, las (mate-)capturas son más fáciles: en el siguiente diagrama, - 1.Qe6 toma los peones d7, e7 y f7. - -figure.diagram-container - .diagram.diag12 - | fen:4K3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3: - .diagram.diag22 - | fen:4K3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3: - figcaption Antes y después 1.Qe6X - -p. - Como las capturas son mucho más fáciles, no tienen lugar - en cascada como en Allmate1. - -h3 Fuente - -p Ver la variante Allmate1. diff --git a/client/src/translations/rules/Allmate2/fr.pug b/client/src/translations/rules/Allmate2/fr.pug deleted file mode 100644 index 1ea1a05a..00000000 --- a/client/src/translations/rules/Allmate2/fr.pug +++ /dev/null @@ -1,24 +0,0 @@ -p.boxed - | Une pièce attaquée est capturée si la prise ne peut être - | empêchée par des coups non capturants. - -p. - C'est la variante Allmate1, avec une condition de mat plus - faible : les coups capturants pour échapper à un mat ne sont pas considérés. - Les (mat-)captures sont donc plus faciles : sur le diagramme suivant, - 1.Qe6 prend les pions d7, e7 et f7. - -figure.diagram-container - .diagram.diag12 - | fen:4k3/pppppppp/8/8/4Q3/8/PPP2PPP/4K3: - .diagram.diag22 - | fen:4k3/ppp3pp/4Q3/8/8/8/PPP2PPP/4K3: - figcaption Avant et après 1.Qe6X - -p. - Puisque les captures sont beaucoup plus faciles, elles ne s'effectuent pas - en cascade comme dans Allmate1. - -h3 Source - -p Voir la variante Allmate1. diff --git a/client/src/translations/rules/Checkered1/en.pug b/client/src/translations/rules/Checkered/en.pug similarity index 97% rename from client/src/translations/rules/Checkered1/en.pug rename to client/src/translations/rules/Checkered/en.pug index 68d39681..1c33f908 100644 --- a/client/src/translations/rules/Checkered1/en.pug +++ b/client/src/translations/rules/Checkered/en.pug @@ -77,6 +77,9 @@ p. h2.stageDelimiter Stage 2 +p.italic. + This stage can be disabled by unselecting "With switch" at game creation. + p. During the game one of the two players can decide to take control of the checkered pieces. diff --git a/client/src/translations/rules/Checkered1/es.pug b/client/src/translations/rules/Checkered/es.pug similarity index 97% rename from client/src/translations/rules/Checkered1/es.pug rename to client/src/translations/rules/Checkered/es.pug index 403a20f7..1d696137 100644 --- a/client/src/translations/rules/Checkered1/es.pug +++ b/client/src/translations/rules/Checkered/es.pug @@ -75,6 +75,10 @@ p. h2.stageDelimiter Fase 2 +p.italic. + Si anula la selección de "Con switch" al crear la partida, entonces + el juego permanecerá en la fase 1. + p. Durante el juego, uno de los dos jugadores puede decidir tomar las piezas a cuadros. Estos luego se vuelven autónomos diff --git a/client/src/translations/rules/Checkered1/fr.pug b/client/src/translations/rules/Checkered/fr.pug similarity index 97% rename from client/src/translations/rules/Checkered1/fr.pug rename to client/src/translations/rules/Checkered/fr.pug index 9e848c34..cf309445 100644 --- a/client/src/translations/rules/Checkered1/fr.pug +++ b/client/src/translations/rules/Checkered/fr.pug @@ -79,6 +79,10 @@ p. h2.stageDelimiter Phase 2 +p.italic. + Si vous désélectionnez "Avec switch" lors de la création de la partie, + alors le jeu restera en phase 1. + p. Au cours de la partie l'un des deux joueurs peut décider de prendre le contrôle des pièces échiquetées. Celles-ci deviennent alors autonomes diff --git a/client/src/translations/rules/Checkered2/en.pug b/client/src/translations/rules/Checkered2/en.pug deleted file mode 100644 index 0d0af195..00000000 --- a/client/src/translations/rules/Checkered2/en.pug +++ /dev/null @@ -1,10 +0,0 @@ -p.boxed - | The capture of an enemy piece produces a new "checkered" piece belonging - | to both players. - -p - | This is the - a(href="/#/variants/Checkered1") Checkered1 variant - | , without the stage 2. - | Probably more drawish, but also quite different. - | I think both are interesting. diff --git a/client/src/translations/rules/Checkered2/es.pug b/client/src/translations/rules/Checkered2/es.pug deleted file mode 100644 index a38eaf2c..00000000 --- a/client/src/translations/rules/Checkered2/es.pug +++ /dev/null @@ -1,10 +0,0 @@ -p.boxed - | La captura de una pieza enemiga da lugar al nacimiento de una pieza - | "a cuadros", que pertenece a ambos campos. - -p - | Esta la - a(href="/#/variants/Checkered1") variante Checkered1 - | , sin fase 2. - | Probablemente más canceladoras, pero también bastante diferente. - | Creo que ambos tienen interés. diff --git a/client/src/translations/rules/Checkered2/fr.pug b/client/src/translations/rules/Checkered2/fr.pug deleted file mode 100644 index 4b0b92a2..00000000 --- a/client/src/translations/rules/Checkered2/fr.pug +++ /dev/null @@ -1,10 +0,0 @@ -p.boxed - | La capture d'une pièce ennemie donne lieu à la naissance d'une pièce - | "échiquetée", qui appartient aux deux camps. - -p - | C'est la - a(href="/#/variants/Checkered1") variante Checkered1 - | , sans la phase 2. - | Sans doute plus annulant, mais aussi assez différente. - | Je pense que les deux ont un intérêt. diff --git a/client/src/translations/rules/Chess/en.pug b/client/src/translations/rules/Chess960/en.pug similarity index 99% rename from client/src/translations/rules/Chess/en.pug rename to client/src/translations/rules/Chess960/en.pug index e7645eff..f978917b 100644 --- a/client/src/translations/rules/Chess/en.pug +++ b/client/src/translations/rules/Chess960/en.pug @@ -1,5 +1,5 @@ p.boxed - | Orthodox rules (with potentially shuffled starting position). + | Orthodox rules with shuffled starting position. p. Chess is played between two players, one moving the white pieces and the diff --git a/client/src/translations/rules/Chess/es.pug b/client/src/translations/rules/Chess960/es.pug similarity index 99% rename from client/src/translations/rules/Chess/es.pug rename to client/src/translations/rules/Chess960/es.pug index b60b7b3c..205285b3 100644 --- a/client/src/translations/rules/Chess/es.pug +++ b/client/src/translations/rules/Chess960/es.pug @@ -1,5 +1,5 @@ p.boxed - | Juego ortodoxo (con una posición inicial potencialmente aleatoria). + | Juego ortodoxo con una posición inicial aleatoria. p. El ajedrez es un juego entre dos jugadores, uno que mueve las piezas blancas diff --git a/client/src/translations/rules/Chess/fr.pug b/client/src/translations/rules/Chess960/fr.pug similarity index 99% rename from client/src/translations/rules/Chess/fr.pug rename to client/src/translations/rules/Chess960/fr.pug index 9a54a917..fdbd2cfb 100644 --- a/client/src/translations/rules/Chess/fr.pug +++ b/client/src/translations/rules/Chess960/fr.pug @@ -1,5 +1,5 @@ p.boxed - | Jeu orthodoxe (avec une position de départ potentiellement aléatoire). + | Jeu orthodoxe avec une position de départ aléatoire. p. Les échecs sont un jeu entre deux joueurs, l'un déplaçant les pièces blanches diff --git a/client/src/variants/Allmate1.js b/client/src/variants/Allmate.js similarity index 99% rename from client/src/variants/Allmate1.js rename to client/src/variants/Allmate.js index 88599e49..86409c25 100644 --- a/client/src/variants/Allmate1.js +++ b/client/src/variants/Allmate.js @@ -1,6 +1,6 @@ import { ChessRules, PiPo, Move } from "@/base_rules"; -export class Allmate1Rules extends ChessRules { +export class AllmateRules extends ChessRules { static get HasEnpassant() { return false; diff --git a/client/src/variants/Allmate2.js b/client/src/variants/Allmate2.js deleted file mode 100644 index f4abbe33..00000000 --- a/client/src/variants/Allmate2.js +++ /dev/null @@ -1,236 +0,0 @@ -import { ChessRules, PiPo, Move } from "@/base_rules"; - -export class Allmate2Rules extends ChessRules { - - static get HasEnpassant() { - return false; - } - - getCheckSquares() { - // No notion of check - return []; - } - - static GenRandInitFen(randomness) { - return ChessRules.GenRandInitFen(randomness).slice(0, -2); - } - - getPotentialMovesFrom([x, y]) { - let moves = super.getPotentialMovesFrom([x, y]); - // Remove standard captures (without removing castling): - moves = moves.filter(m => { - return m.vanish.length == 1 || m.appear.length == 2; - }); - - // Augment moves with "mate-captures": - // TODO: this is coded in a highly inefficient way... - const color = this.turn; - const oppCol = V.GetOppCol(this.turn); - moves.forEach(m => { - this.play(m); - - // 1) What is attacked? - let attacked = {}; - for (let i=0; i<V.size.x; i++) { - for (let j=0; j<V.size.y; j++) { - if (this.getColor(i,j) == oppCol && this.isAttacked([i,j], color)) - attacked[i+"_"+j] = [i,j]; - } - } - - // 2) Among attacked pieces, which cannot escape capture? - // --> without (normal-)capturing: difference with Allmate1 variant - // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion - outerLoop: for (let i=0; i<V.size.x; i++) { - for (let j=0; j<V.size.y; j++) { - if (this.getColor(i,j) == oppCol) { - let oppMoves = []; - switch (this.getPiece(i, j)) { - case V.PAWN: - oppMoves = this.getPotentialPawnMoves([i, j]); - break; - case V.ROOK: - oppMoves = this.getPotentialRookMoves([i, j]); - break; - case V.KNIGHT: - oppMoves = this.getPotentialKnightMoves([i, j]); - break; - case V.BISHOP: - oppMoves = this.getPotentialBishopMoves([i, j]); - break; - case V.QUEEN: - oppMoves = this.getPotentialQueenMoves([i, j]); - break; - case V.KING: - // Do not allow castling to escape from check - oppMoves = super.getSlideNJumpMoves( - [i, j], - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), - "oneStep" - ); - break; - } - for (let om of oppMoves) { - if (om.vanish.length == 2) - // Skip captures: forbidden in this mode - continue; - V.PlayOnBoard(this.board, om); - Object.values(attacked).forEach(sq => { - const origSq = [sq[0], sq[1]]; - if (om.start.x == sq[0] && om.start.y == sq[1]) - // Piece moved: - sq = [om.appear[0].x, om.appear[0].y]; - if (!this.isAttacked(sq, color)) - delete attacked[origSq[0]+"_"+origSq[1]]; - }); - V.UndoOnBoard(this.board, om); - if (Object.keys(attacked).length == 0) - // No need to explore more moves - break outerLoop; - } - } - } - } - this.undo(m); - - // 3) Add mate-captures: - Object.values(attacked).forEach(sq => { - m.vanish.push(new PiPo({ - x: sq[0], - y: sq[1], - c: oppCol, - p: this.getPiece(sq[0], sq[1]) - })); - }); - }); - - return moves; - } - - // No "under check" conditions in castling - getCastleMoves(sq) { - return super.getCastleMoves(sq, null, "castleInCheck"); - } - - // TODO: allow pieces to "commit suicide"? (Currently yes except king) - filterValid(moves) { - // Remove moves which let the king mate-captured: - if (moves.length == 0) return []; - const color = this.turn; - const oppCol = V.GetOppCol(color); - return moves.filter(m => { - let res = true; - this.play(m); - if (this.underCheck(color)) { - res = false; - const attacked = this.kingPos[color]; - // Try to find a move to escape check - // TODO: very inefficient method. - outerLoop: for (let i=0; i<V.size.x; i++) { - for (let j=0; j<V.size.y; j++) { - if (this.getColor(i,j) == color) { - let emoves = []; - // Artficial turn change to "play twice": - this.turn = color; - switch (this.getPiece(i, j)) { - case V.PAWN: - emoves = this.getPotentialPawnMoves([i, j]); - break; - case V.ROOK: - emoves = this.getPotentialRookMoves([i, j]); - break; - case V.KNIGHT: - emoves = this.getPotentialKnightMoves([i, j]); - break; - case V.BISHOP: - emoves = this.getPotentialBishopMoves([i, j]); - break; - case V.QUEEN: - emoves = this.getPotentialQueenMoves([i, j]); - break; - case V.KING: - emoves = this.getPotentialKingMoves([i, j]); - break; - } - this.turn = oppCol; - for (let em of emoves) { - V.PlayOnBoard(this.board, em); - let sq = attacked; - if (em.start.x == attacked[0] && em.start.y == attacked[1]) - // King moved: - sq = [em.appear[0].x, em.appear[0].y]; - if (!this.isAttacked(sq, oppCol)) - res = true; - V.UndoOnBoard(this.board, em); - if (res) - // No need to explore more moves - break outerLoop; - } - } - } - } - } - this.undo(m); - return res; - }); - } - - postPlay(move) { - super.postPlay(move); - if (move.vanish.length >= 2 && move.appear.length == 1) { - for (let i = 1; i<move.vanish.length; i++) { - const v = move.vanish[i]; - // Did opponent king disappeared? - if (v.p == V.KING) - this.kingPos[this.turn] = [-1, -1]; - // Or maybe a rook? - else if (v.p == V.ROOK) { - if (v.y < this.kingPos[v.c][1]) - this.castleFlags[v.c][0] = 8; - else - // v.y > this.kingPos[v.c][1] - this.castleFlags[v.c][1] = 8; - } - } - } - } - - preUndo(move) { - super.preUndo(move); - const oppCol = this.turn; - if (move.vanish.length >= 2 && move.appear.length == 1) { - // Did opponent king disappeared? - const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol) - if (psq) - this.kingPos[psq.c] = [psq.x, psq.y]; - } - } - - getCurrentScore() { - const color = this.turn; - const kp = this.kingPos[color]; - if (kp[0] < 0) - // King disappeared - return color == "w" ? "0-1" : "1-0"; - if (this.atLeastOneMove()) return "*"; - // Kings still there, no moves: - return "1/2"; - } - - static get SEARCH_DEPTH() { - return 1; - } - - getNotation(move) { - let notation = super.getNotation(move); - // Add a capture mark (not describing what is captured...): - if (move.vanish.length > 1 && move.appear.length == 1) { - if (!!(notation.match(/^[a-h]x/))) - // Pawn capture: remove initial "b" in bxc4 for example - notation = notation.substr(1); - notation = notation.replace("x","") + "X"; - } - return notation; - } - -}; diff --git a/client/src/variants/Checkered1.js b/client/src/variants/Checkered.js similarity index 95% rename from client/src/variants/Checkered1.js rename to client/src/variants/Checkered.js index 2451f9b5..5036a82d 100644 --- a/client/src/variants/Checkered1.js +++ b/client/src/variants/Checkered.js @@ -1,6 +1,6 @@ import { ChessRules, Move, PiPo } from "@/base_rules"; -export class Checkered1Rules extends ChessRules { +export class CheckeredRules extends ChessRules { static board2fen(b) { const checkered_codes = { @@ -41,6 +41,26 @@ export class Checkered1Rules extends ChessRules { return (b[0] == "c" ? "Checkered/" : "") + b; } + static get Options() { + return Object.assign( + {}, + ChessRules.Options, + { + check: [ + { + label: "With switch", + defaut: true, + variable: "switch" + } + ] + } + ); + } + + static AbbreviateOptions(opts) { + return (!opts["switch"] ? "NS" : ""); + } + setOtherVariables(fen) { super.setOtherVariables(fen); // Local stack of non-capturing checkered moves: @@ -56,6 +76,7 @@ export class Checkered1Rules extends ChessRules { // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous const stageInfo = V.ParseFen(fen).stage; this.stage = parseInt(stageInfo[0], 10); + this.canSwitch = (this.stage == 1 && stageInfo[1] != '-'); this.sideCheckered = (this.stage == 2 ? stageInfo[1] : undefined); } @@ -65,7 +86,7 @@ export class Checkered1Rules extends ChessRules { if (fenParts.length != 7) return false; if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) return false; - if (!fenParts[6].match(/^[12][wb]?$/)) return false; + if (!fenParts[6].match(/^[12][wb-]?$/)) return false; return true; } @@ -143,7 +164,7 @@ export class Checkered1Rules extends ChessRules { if (this.getPiece(x, y) == V.KING) { // If at least one checkered piece, allow switching: if ( - !noswitch && + this.canSwitch && !noswitch && this.board.some(b => b.some(cell => cell[0] == 'c')) ) { const oppCol = V.GetOppCol(color); @@ -592,10 +613,13 @@ export class Checkered1Rules extends ChessRules { return evaluation; } - static GenRandInitFen(randomness) { - // Add 16 pawns flags + empty cmove + stage == 1: - return ChessRules.GenRandInitFen(randomness) - .slice(0, -2) + "1111111111111111 - - 1"; + static GenRandInitFen(options) { + const baseFen = ChessRules.GenRandInitFen(options.randomness); + return ( + // Add 16 pawns flags + empty cmove + stage == 1: + baseFen.slice(0, -2) + "1111111111111111 - - 1" + + (!options["switch"] ? '-' : "") + ); } static ParseFen(fen) { @@ -620,7 +644,9 @@ export class Checkered1Rules extends ChessRules { } getStageFen() { - return (this.stage == 1 ? "1" : "2" + this.sideCheckered); + if (this.stage == 1) return "1" + (!this.canSwitch ? '-' : ""); + // Stage == 2: + return "2" + this.sideCheckered; } getFen() { diff --git a/client/src/variants/Checkered2.js b/client/src/variants/Checkered2.js deleted file mode 100644 index 997c1c7a..00000000 --- a/client/src/variants/Checkered2.js +++ /dev/null @@ -1,472 +0,0 @@ -import { ChessRules, Move, PiPo } from "@/base_rules"; - -export class Checkered2Rules extends ChessRules { - - static board2fen(b) { - const checkered_codes = { - p: "s", - q: "t", - r: "u", - b: "c", - n: "o" - }; - if (b[0] == "c") return checkered_codes[b[1]]; - return ChessRules.board2fen(b); - } - - 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"]); - } - - getPpath(b) { - return (b[0] == "c" ? "Checkered/" : "") + b; - } - - setOtherVariables(fen) { - super.setOtherVariables(fen); - // Local stack of non-capturing checkered moves: - this.cmoves = []; - const cmove = V.ParseFen(fen).cmove; - if (cmove == "-") this.cmoves.push(null); - else { - this.cmoves.push({ - start: ChessRules.SquareToCoords(cmove.substr(0, 2)), - end: ChessRules.SquareToCoords(cmove.substr(2)) - }); - } - } - - static IsGoodFen(fen) { - if (!ChessRules.IsGoodFen(fen)) return false; - const fenParts = fen.split(" "); - if (fenParts.length != 6) return false; - if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/)) - return false; - return true; - } - - static IsGoodFlags(flags) { - // 4 for castle + 16 for pawns - return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/); - } - - setFlags(fenflags) { - super.setFlags(fenflags); //castleFlags - this.pawnFlags = { - w: [...Array(8)], //pawns can move 2 squares? - b: [...Array(8)] - }; - const flags = fenflags.substr(4); //skip first 4 letters, for castle - for (let c of ["w", "b"]) { - for (let i = 0; i < 8; i++) - this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1"; - } - } - - aggregateFlags() { - return [this.castleFlags, this.pawnFlags]; - } - - disaggregateFlags(flags) { - this.castleFlags = flags[0]; - this.pawnFlags = flags[1]; - } - - getEpSquare(moveOrSquare) { - if (typeof moveOrSquare !== "object" || moveOrSquare.appear[0].c != 'c') - return super.getEpSquare(moveOrSquare); - // Checkered move: no en-passant - return undefined; - } - - getCmove(move) { - if (move.appear[0].c == "c" && move.vanish.length == 1) - return { start: move.start, end: move.end }; - return null; - } - - canTake([x1, y1], [x2, y2]) { - const color1 = this.getColor(x1, y1); - const color2 = this.getColor(x2, y2); - // Checkered aren't captured - return ( - color1 != color2 && - color2 != "c" && - (color1 != "c" || color2 != this.turn) - ); - } - - // Post-processing: apply "checkerization" of standard moves - getPotentialMovesFrom([x, y]) { - let standardMoves = super.getPotentialMovesFrom([x, y]); - const lastRank = this.turn == "w" ? 0 : 7; - // King is treated differently: it never turn checkered - if (this.getPiece(x, y) == V.KING) return standardMoves; - let moves = []; - standardMoves.forEach(m => { - if (m.vanish[0].p == V.PAWN) { - if ( - Math.abs(m.end.x - m.start.x) == 2 && - !this.pawnFlags[this.turn][m.start.y] - ) { - return; //skip forbidden 2-squares jumps - } - if ( - this.board[m.end.x][m.end.y] == V.EMPTY && - m.vanish.length == 2 && - this.getColor(m.start.x, m.start.y) == "c" - ) { - return; //checkered pawns cannot take en-passant - } - } - if (m.vanish.length == 1) - // No capture - moves.push(m); - else { - // A capture occured (m.vanish.length == 2) - m.appear[0].c = "c"; - moves.push(m); - if ( - // Avoid promotions (already treated): - m.appear[0].p != m.vanish[1].p && - (m.vanish[0].p != V.PAWN || m.end.x != lastRank) - ) { - // Add transformation into captured piece - let m2 = JSON.parse(JSON.stringify(m)); - m2.appear[0].p = m.vanish[1].p; - moves.push(m2); - } - } - }); - return moves; - } - - getPotentialPawnMoves([x, y]) { - let moves = super.getPotentialPawnMoves([x, y]); - // Post-process: set right color for checkered moves - if (this.getColor(x, y) == 'c') { - moves.forEach(m => { - m.appear[0].c = 'c'; //may be done twice if capture - m.vanish[0].c = 'c'; - }); - } - return moves; - } - - canIplay(side, [x, y]) { - return side == this.turn && [side, "c"].includes(this.getColor(x, y)); - } - - // Does m2 un-do m1 ? (to disallow undoing checkered moves) - oppositeMoves(m1, m2) { - return ( - !!m1 && - m2.appear[0].c == "c" && - m2.appear.length == 1 && - m2.vanish.length == 1 && - m1.start.x == m2.end.x && - m1.end.x == m2.start.x && - m1.start.y == m2.end.y && - m1.end.y == m2.start.y - ); - } - - filterValid(moves) { - if (moves.length == 0) return []; - const color = this.turn; - const L = this.cmoves.length; //at least 1: init from FEN - return moves.filter(m => { - if (this.oppositeMoves(this.cmoves[L - 1], m)) return false; - this.play(m); - const res = !this.underCheck(color); - this.undo(m); - return res; - }); - } - - getAllValidMoves() { - const oppCol = V.GetOppCol(this.turn); - let potentialMoves = []; - for (let i = 0; i < V.size.x; i++) { - for (let j = 0; j < V.size.y; j++) { - // NOTE: just testing == color isn't enough because of checkered pieces - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { - Array.prototype.push.apply( - potentialMoves, - this.getPotentialMovesFrom([i, j]) - ); - } - } - } - return this.filterValid(potentialMoves); - } - - atLeastOneMove() { - const oppCol = V.GetOppCol(this.turn); - for (let i = 0; i < V.size.x; i++) { - for (let j = 0; j < V.size.y; j++) { - // NOTE: just testing == color isn't enough because of checkered pieces - if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) { - const moves = this.getPotentialMovesFrom([i, j]); - if (moves.length > 0) { - for (let k = 0; k < moves.length; k++) { - if (this.filterValid([moves[k]]).length > 0) return true; - } - } - } - } - } - return false; - } - - // colors: array, generally 'w' and 'c' or 'b' and 'c' - isAttacked(sq, colors) { - if (!Array.isArray(colors)) colors = [colors]; - return ( - this.isAttackedByPawn(sq, colors) || - this.isAttackedByRook(sq, colors) || - this.isAttackedByKnight(sq, colors) || - this.isAttackedByBishop(sq, colors) || - this.isAttackedByQueen(sq, colors) || - this.isAttackedByKing(sq, colors) - ); - } - - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - const color = (c == "c" ? this.turn : c); - let pawnShift = color == "w" ? 1 : -1; - if (x + pawnShift >= 0 && x + pawnShift < 8) { - for (let i of [-1, 1]) { - if ( - y + i >= 0 && - y + i < 8 && - this.getPiece(x + pawnShift, y + i) == V.PAWN && - this.getColor(x + pawnShift, y + i) == c - ) { - return true; - } - } - } - } - return false; - } - - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { - for (let step of steps) { - let rx = x + step[0], - ry = y + step[1]; - while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { - rx += step[0]; - ry += step[1]; - } - if ( - V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) - ) { - return true; - } - } - return false; - } - - isAttackedByRook(sq, colors) { - return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]); - } - - isAttackedByKnight(sq, colors) { - return this.isAttackedBySlideNJump( - sq, - colors, - V.KNIGHT, - V.steps[V.KNIGHT], - "oneStep" - ); - } - - isAttackedByBishop(sq, colors) { - return this.isAttackedBySlideNJump( - sq, colors, V.BISHOP, V.steps[V.BISHOP]); - } - - isAttackedByQueen(sq, colors) { - return this.isAttackedBySlideNJump( - sq, - colors, - V.QUEEN, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]) - ); - } - - isAttackedByKing(sq, colors) { - return this.isAttackedBySlideNJump( - sq, - colors, - V.KING, - V.steps[V.ROOK].concat(V.steps[V.BISHOP]), - "oneStep" - ); - } - - underCheck(color) { - return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]); - } - - getCheckSquares() { - const color = this.turn; - // Artifically change turn, for checkered pawns - this.turn = V.GetOppCol(color); - const kingAttacked = - this.isAttacked( - this.kingPos[color], - [this.turn, 'c'] - ); - let res = kingAttacked - ? [JSON.parse(JSON.stringify(this.kingPos[color]))] - : []; - this.turn = color; - return res; - } - - postPlay(move) { - super.postPlay(move); - // Does this move turn off a 2-squares pawn flag? - if ( - [1, 6].includes(move.start.x) && - move.vanish[0].p == V.PAWN && - Math.abs(move.end.x - move.start.x) == 2 - ) { - this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false; - } - this.cmoves.push(this.getCmove(move)); - } - - postUndo(move) { - super.postUndo(move); - this.cmoves.pop(); - } - - getCurrentScore() { - if (this.atLeastOneMove()) return "*"; - const color = this.turn; - // Artifically change turn, for checkered pawns - this.turn = V.GetOppCol(this.turn); - const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]) - ? color == "w" - ? "0-1" - : "1-0" - : "1/2"; - this.turn = V.GetOppCol(this.turn); - return res; - } - - evalPosition() { - let evaluation = 0; - // Just count material for now, considering checkered neutral (...) - for (let i = 0; i < V.size.x; i++) { - for (let j = 0; j < V.size.y; j++) { - if (this.board[i][j] != V.EMPTY) { - const sqColor = this.getColor(i, j); - if (["w","b"].includes(sqColor)) { - const sign = sqColor == "w" ? 1 : -1; - evaluation += sign * V.VALUES[this.getPiece(i, j)]; - } - } - } - } - return evaluation; - } - - static GenRandInitFen(randomness) { - // Add 16 pawns flags + empty cmove: - return ChessRules.GenRandInitFen(randomness) - .slice(0, -2) + "1111111111111111 - -"; - } - - static ParseFen(fen) { - return Object.assign( - ChessRules.ParseFen(fen), - { cmove: fen.split(" ")[5] } - ); - } - - getCmoveFen() { - const L = this.cmoves.length; - return ( - !this.cmoves[L - 1] - ? "-" - : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + - ChessRules.CoordsToSquare(this.cmoves[L - 1].end) - ); - } - - getFen() { - return super.getFen() + " " + this.getCmoveFen(); - } - - getFenForRepeat() { - return super.getFenForRepeat() + "_" + this.getCmoveFen(); - } - - getFlagsFen() { - let fen = super.getFlagsFen(); - // Add pawns flags - for (let c of ["w", "b"]) - for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0"); - return fen; - } - - static get SEARCH_DEPTH() { - return 2; - } - - getNotation(move) { - if (move.appear.length == 2) { - // Castle - if (move.end.y < move.start.y) return "0-0-0"; - return "0-0"; - } - - const finalSquare = V.CoordsToSquare(move.end); - const piece = this.getPiece(move.start.x, move.start.y); - let notation = ""; - if (piece == V.PAWN) { - // Pawn move - if (move.vanish.length > 1) { - // Capture - const startColumn = V.CoordToColumn(move.start.y); - notation = startColumn + "x" + finalSquare; - } else notation = finalSquare; - } else { - // Piece movement - notation = - piece.toUpperCase() + - (move.vanish.length > 1 ? "x" : "") + - finalSquare; - } - if (move.appear[0].p != move.vanish[0].p) - notation += "=" + move.appear[0].p.toUpperCase(); - return notation; - } - -}; diff --git a/client/src/variants/Chess.js b/client/src/variants/Chess.js deleted file mode 120000 index 8e6b27cd..00000000 --- a/client/src/variants/Chess.js +++ /dev/null @@ -1 +0,0 @@ -../base_rules.js \ No newline at end of file diff --git a/client/src/variants/Chess960.js b/client/src/variants/Chess960.js new file mode 100644 index 00000000..968ec1c3 --- /dev/null +++ b/client/src/variants/Chess960.js @@ -0,0 +1,23 @@ +import { ChessRules } from "@/base_rules"; + +export class Chess960Rules extends ChessRules { + + // Do not allow standard chess: + static get Options() { + return { + select: [ + { + label: "Randomness", + variable: "randomness", + defaut: 2, + options: [ + { label: "Symmetric random", value: 1 }, + { label: "Asymmetric random", value: 2 } + ] + } + ], + check: [] + }; + } + +}; diff --git a/client/src/views/Analyse.vue b/client/src/views/Analyse.vue index 06ef7901..5c525a31 100644 --- a/client/src/views/Analyse.vue +++ b/client/src/views/Analyse.vue @@ -44,7 +44,8 @@ export default { rulesContent: "", gameRef: { vname: "", - fen: "" + fen: "", + options: {} }, game: { players: [{ name: "Analyse" }, { name: "Analyse" }], diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue index 3d73893e..b13e3832 100644 --- a/client/src/views/Game.vue +++ b/client/src/views/Game.vue @@ -78,7 +78,10 @@ main .row #aboveBoard.col-sm-12 span.variant-cadence(v-if="game.type!='import'") {{ game.cadence }} - span.variant-name {{ game.vname }} + span.variant-name + | {{ game.vname }} + | - + | {{ vr.constructor.AbbreviateOptions(game.options) }} span#nextGame( v-if="nextIds.length > 0" @click="showNextGame()" @@ -408,8 +411,15 @@ export default { this.conn.onopen = () => callback(); }; this.fetchGame((game) => { - if (!!game) + if (!!game) { + if (!game.options) { + // Patch for retro-compatibility (TODO: remove it) + game.options = { randomness: game.randomness }; + delete game["randomness"]; + } + else game.options = JSON.parse(game.options); this.loadVariantThenGame(game, () => socketInit(this.roomInit)); + } else // Live game stored remotely: need socket to retrieve it // NOTE: the callback "roomInit" will be lost, so it's not provided. @@ -707,7 +717,7 @@ export default { const gameToSend = Object.keys(this.game) .filter(k => [ - "id","fen","players","vid","cadence","fenStart", + "id","fen","players","vid","cadence","fenStart","options", "moves","clocks","score","drawOffer","rematchOffer" ].includes(k)) .reduce( @@ -1048,8 +1058,8 @@ export default { // Start a new game! let gameInfo = { id: getRandString(), //ignored if corr - fen: V.GenRandInitFen(this.game.randomness), - randomness: this.game.randomness, + fen: V.GenRandInitFen(this.game.options), + options: this.game.options, players: [this.game.players[1], this.game.players[0]], vid: this.game.vid, cadence: this.game.cadence @@ -1082,7 +1092,11 @@ export default { "/games", "POST", { - data: { gameInfo: gameInfo }, + data: Object.assign( + {}, + gameInfo, + { options: JSON.stringify(this.game.options) } + ), success: (response) => { gameInfo.id = response.id; notifyNewGame(); diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 7749dfad..6d675a15 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -12,7 +12,9 @@ main div#acceptDiv(role="dialog") .card p.text-center - span.variantName {{ curChallToAccept.vname }} + span.variantName + | {{ curChallToAccept.vname }} + | {{ curChallToAccept.options.abridged }} span {{ curChallToAccept.cadence }} span {{ st.tr["with"] + " " + curChallToAccept.from.name }} p.text-center(v-if="!!curChallToAccept.color") @@ -49,6 +51,23 @@ main :selected="newchallenge.vid==v.id" ) | {{ v.display }} + // Variant-specific options (often at least randomness) + fieldset(v-if="!!newchallenge.V") + div(v-for="select of newchallenge.V.Options.select") + label(:for="select.variable + '_opt'") {{ st.tr[select.label] }} * + select(:id="select.variable + '_opt'") + option( + v-for="o of select.options" + :value="o.value" + :selected="o.value == select.defaut" + ) + | {{ st.tr[o.label] }} + div(v-for="check of newchallenge.V.Options.check") + label(:for="check.variable + '_opt'") {{ st.tr[check.label] }} * + input( + :id="check.variable + '_opt'" + type="checkbox" + :checked="check.defaut") fieldset label(for="cadence") {{ st.tr["Cadence"] }} * div#predefinedCadences @@ -61,12 +80,6 @@ main v-model="newchallenge.cadence" placeholder="5+0, 1h+30s, 5d ..." ) - fieldset - label(for="selectRandomLevel") {{ st.tr["Randomness"] }} * - select#selectRandomLevel(v-model="newchallenge.randomness") - 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( @@ -156,7 +169,7 @@ main tr th {{ st.tr["Variant"] }} th {{ st.tr["Cadence"] }} - th {{ st.tr["Random?"] }} + th {{ st.tr["Options"] }} th tbody tr( @@ -165,7 +178,7 @@ main ) td {{ pc.vname }} td {{ pc.cadence }} - td(:class="getRandomnessClass(pc)") + td(:class="getRandomnessClass(pc)") {{ pc.options.abridged }} td.remove-preset(@click="removePresetChall($event, pc)") img(src="/images/icons/delete.svg") .row @@ -253,11 +266,9 @@ export default { to: "", //name of challenged player (if any) color: '', cadence: localStorage.getItem("cadence") || "", - randomness: - // Warning: randomness can be 0, then !!randomness is false - (parseInt(localStorage.getItem("challRandomness"),10)+1 || 3) - 1, + options: {}, // VariantRules object, stored to not interfere with - // diagrams of targetted challenges: + // diagrams of targeted challenges: V: null, vname: "", diag: "", //visualizing FEN @@ -265,7 +276,7 @@ export default { }, focus: true, tchallDiag: "", - curChallToAccept: { from: {} }, + curChallToAccept: { from: {}, options: {} }, presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"), conn: null, connexionString: "", @@ -326,13 +337,11 @@ export default { to: this.$route.query["challenge"], color: this.$route.query["color"] || '', cadence: this.$route.query["cadence"], - // Tournament: no randomness (TODO: for now at least) - randomness: 0, + options: {}, memorize: false } ); window.doClick("modalNewgame"); - //this.issueNewChallenge(); //NOTE: doesn't work yet. }, this.$route.query["variant"] ); @@ -417,15 +426,16 @@ export default { response.challenges.map(c => { const from = { name: names[c.uid], id: c.uid }; //or just name const type = this.classifyObject(c); - const vname = this.getVname(c.vid); + this.setVname(c); return Object.assign( + {}, + c, { type: type, - vname: vname, from: from, - to: c.target ? names[c.target] : "" - }, - c + to: c.target ? names[c.target] : "", + options: JSON.parse(c.options) + } ); }) ); @@ -465,8 +475,9 @@ export default { this.conn = null; }, getRandomnessClass: function(pc) { + if (!pc.options.randomness) return {}; return { - ["random-" + pc.randomness]: true + ["random-" + pc.options.randomness]: true }; }, anonymousCount: function() { @@ -506,12 +517,22 @@ export default { this.partialResetNewchallenge(); window.doClick("modalNewgame"); }, + sameOptions: function(opt1, opt2) { + const keys1 = Object.keys(opt1), + keys2 = Object.keys(opt2); + if (keys1.length != keys2.length) return false; + for (const key1 of keys1) { + if (!keys2.includes(key1)) return false; + if (opt1[key1] != opt2[key1]) return false; + } + return true; + }, 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 + this.sameOptions(c.options, chall.options) )) { return; } @@ -521,7 +542,7 @@ export default { vid: chall.vid, vname: chall.vname, //redundant, but easier cadence: chall.cadence, - randomness: chall.randomness + options: chall.options }); localStorage.setItem("presetChalls", JSON.stringify(this.presetChalls)); }, @@ -825,7 +846,7 @@ export default { id: c.id, from: this.st.user.sid, to: c.to, - randomness: c.randomness, + options: c.options, fen: c.fen, vid: c.vid, cadence: c.cadence, @@ -969,7 +990,7 @@ export default { ) { let newChall = Object.assign({}, chall); newChall.type = this.classifyObject(chall); - newChall.randomness = chall.randomness; + newChall.options = chall.options; newChall.added = Date.now(); let fromValues = Object.assign({}, this.people[chall.from]); delete fromValues["pages"]; //irrelevant in this context @@ -1028,7 +1049,7 @@ export default { this.partialResetNewchallenge(); this.newchallenge.vid = pchall.vid; this.newchallenge.cadence = pchall.cadence; - this.newchallenge.randomness = pchall.randomness; + this.newchallenge.options = pchall.options; this.loadNewchallVariant(this.issueNewChallenge); }, issueNewChallenge: async function() { @@ -1071,6 +1092,17 @@ export default { } // NOTE: "from" information is not required here let chall = Object.assign({}, this.newchallenge); + chall.options = {}; + // Get/set options variables (if any) / TODO: v-model?! + for (const check of this.newchallenge.V.Options.check) { + const elt = document.getElementById(check.variable + "_opt"); + if (elt.checked) chall.options[check.variable] = true; + } + for (const select of this.newchallenge.V.Options.select) { + const elt = document.getElementById(select.variable + "_opt"); + chall.options[select.variable] = elt.value; + } + chall.options.abridged = V.AbbreviateOptions(chall.options); // Add only if not already issued (not counting FEN): if (this.challenges.some(c => ( @@ -1085,7 +1117,7 @@ export default { && c.vid == chall.vid && c.cadence == chall.cadence && - c.randomness == chall.randomness + this.sameOptions(c.options, chall.options) )) { alert(this.st.tr["Challenge already exists"]); return; @@ -1146,10 +1178,9 @@ export default { chall.type = ctype; chall.vname = this.newchallenge.vname; this.challenges.push(chall); - // Remember cadence + vid for quicker further challenges: + // Remember cadence + vid for quicker further challenges: localStorage.setItem("cadence", chall.cadence); localStorage.setItem("vid", chall.vid); - localStorage.setItem("challRandomness", chall.randomness); document.getElementById("modalNewgame").checked = false; // Show the challenge if not on current display if ( @@ -1169,7 +1200,13 @@ export default { "/challenges", "POST", { - data: { chall: chall }, + data: { + chall: Object.assign( + {}, + chall, + { options: JSON.stringify(chall.options) } + ) + }, success: (response) => { finishAddChallenge(response.id); } @@ -1275,8 +1312,8 @@ export default { // These game informations will be shared let gameInfo = { id: getRandString(), - fen: c.fen || V.GenRandInitFen(c.randomness), - randomness: c.randomness, //for rematch + fen: c.fen || V.GenRandInitFen(c.options), + options: c.options, //for rematch players: players, vid: c.vid, cadence: c.cadence @@ -1326,7 +1363,11 @@ export default { { // cid is useful to delete the challenge: data: { - gameInfo: gameInfo, + gameInfo: Object.assign( + {}, + gameInfo, + { options: JSON.stringify(gameInfo.options) } + ), cid: c.id }, success: (response) => { @@ -1478,11 +1519,11 @@ button.refuseBtn tr > td &.random-0 - background-color: #FF5733 + background-color: #FEAF9E &.random-1 - background-color: #2B63B4 + background-color: #9EB2FE &.random-2 - background-color: #33B42B + background-color: #A5FE9E @media screen and (max-width: 767px) h4 diff --git a/client/src/views/Rules.vue b/client/src/views/Rules.vue index 09d5e06a..fc3a0cd9 100644 --- a/client/src/views/Rules.vue +++ b/client/src/views/Rules.vue @@ -1,5 +1,30 @@ <template lang="pug"> main + input#modalOptions.modal(type="checkbox") + div#optionsDiv( + role="dialog" + data-checkbox="modalOptions" + ) + .card + label.modal-close(for="modalOptions") + h3 {{ st.tr["Options"] }} + fieldset(v-if="!!V") + div(v-for="select of V.Options.select") + label(:for="select.variable + '_opt'") {{ st.tr[select.label] }} * + select(:id="select.variable + '_opt'") + option( + v-for="o of select.options" + :value="o.value" + :selected="o.value == select.defaut" + ) + | {{ st.tr[o.label] }} + div(v-for="check of V.Options.check") + label(:for="check.variable + '_opt'") {{ st.tr[check.label] }} * + input( + :id="check.variable + '_opt'" + type="checkbox" + :checked="check.defaut") + button(@click="setOptions()") {{ st.tr["Validate"] }} .row .col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 .button-group @@ -44,6 +69,7 @@ import ComputerGame from "@/components/ComputerGame.vue"; import { store } from "@/store"; import { replaceByDiag } from "@/utils/printDiagram"; import { CompgameStorage } from "@/utils/compgameStorage"; +import { processModalClick } from "@/utils/modalClick"; import afterRawLoad from "@/utils/afterRawLoad"; export default { name: "my-rules", @@ -58,9 +84,9 @@ export default { // variables passed to ComputerGame: gameInfo: { vname: "", - mode: "versus", + mode: "versus" }, - V: null, + V: null }; }, watch: { @@ -72,6 +98,10 @@ export default { // NOTE: variant cannot be set before store is initialized this.re_setVariant(this.$route.params["vname"]); }, + mounted: function() { + document.getElementById("optionsDiv") + .addEventListener("click", processModalClick); + }, computed: { showAnalyzeBtn: function() { return !!this.V && this.V.CanAnalyze; @@ -117,12 +147,38 @@ export default { }, 500); }); }, - startGame: function(mode) { + setOptions: function() { + let options = {}; + // Get/set options variables / TODO: v-model?! + for (const check of this.V.Options.check) { + const elt = document.getElementById(check.variable + "_opt"); + if (elt.checked) options[check.variable] = true; + } + for (const select of this.V.Options.select) { + const elt = document.getElementById(select.variable + "_opt"); + options[select.variable] = elt.value; + } + document.getElementById("modalOptions").checked = false; + if (this.whatNext == "analyze") this.gotoAnalyze(options); + else this.startGame(this.whatNext, options); + }, + startGame: function(mode, options) { if (this.gameInProgress) return; - this.gameInProgress = true; - this.display = "computer"; - this.gameInfo.mode = mode; - if (this.gameInfo.mode == "versus") { + const next = (game, options) => { + this.gameInProgress = true; + this.display = "computer"; + this.gameInfo.mode = mode; + this.$refs["compgame"].launchGame(game, options); + }; + if (!!options) { + next(null, options); + return; + } + const askOptions = () => { + this.whatNext = mode; + doClick("modalOptions"); + }; + if (mode == "versus") { CompgameStorage.get(this.gameInfo.vname, (game) => { // NOTE: game might be null (if none stored yet) if (!!game && !V.IsGoodFen(game.fen)) { @@ -130,10 +186,14 @@ export default { CompgameStorage.remove(game.vname); game = null; } - this.$refs["compgame"].launchGame(game); + if (!!game || Object.keys(V.Options).length == 0) next(game); + else askOptions(); }); } - else this.$refs["compgame"].launchGame(); + else { + if (Object.keys(V.Options).length == 0) next(); + else askOptions(); + } }, // The user wants to stop the game: stopGame: function() { @@ -145,11 +205,17 @@ export default { if (this.gameInfo.mode == "versus") CompgameStorage.remove(this.gameInfo.vname); }, - gotoAnalyze: function() { - this.$router.push( - "/analyse/" + this.gameInfo.vname + - "/?fen=" + V.GenRandInitFen(this.st.settings.randomness) - ); + gotoAnalyze: function(options) { + if (!options && Object.keys(V.Options).length > 0) { + this.whatNext = "analyze"; + doClick("modalOptions"); + } + else { + this.$router.push( + "/analyse/" + this.gameInfo.vname + + "/?fen=" + V.GenRandInitFen(options) + ); + } } } }; diff --git a/client/src/views/Variants.vue b/client/src/views/Variants.vue index 8e28e500..f60e5f1d 100644 --- a/client/src/views/Variants.vue +++ b/client/src/views/Variants.vue @@ -10,8 +10,8 @@ main a(href="https://www.chessvariants.com/why.html") | {{ st.tr["Why play chess variants?"] }} p - a(href="/#/variants/Chess") Chess - | {{ st.tr["chess_v"] }} + a(href="/#/variants/Chess960") Chess960 + | {{ st.tr["chess960_v"] }} div(v-for="g of sortedGroups") h3 {{ st.tr["vt" + g] }} p {{ st.tr["vg" + g] }} diff --git a/server/db/create.sql b/server/db/create.sql index e26b03b9..248e5970 100644 --- a/server/db/create.sql +++ b/server/db/create.sql @@ -39,7 +39,7 @@ create table Challenges ( uid integer, target integer, vid integer, - randomness integer, + options varchar, fen varchar, cadence varchar, foreign key (uid) references Users(id), @@ -62,7 +62,7 @@ create table Games ( score varchar default '*', scoreMsg varchar, cadence varchar, - randomness integer, --for rematch + options varchar, --for rematch created datetime, drawOffer character default '', rematchOffer character default '', diff --git a/server/db/populate.sql b/server/db/populate.sql index 6f67cc11..1cb54e6a 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -17,8 +17,7 @@ insert or ignore into Variants (name, description, groupe, display) values ('Alapo', 'Geometric Chess', 27, 'Alapo'), ('Alice', 'Both sides of the mirror', 31, 'Alice Chess'), ('Align4', 'Align four pawns', 31, 'Align4'), - ('Allmate1', 'Mate any piece (v1)', 11, 'Allmate1'), - ('Allmate2', 'Mate any piece (v2)', 11, 'Allmate2'), + ('Allmate', 'Mate any piece', 11, 'Allmate'), ('Ambiguous', 'Play opponent''s pieces', 29, 'Ambiguous'), ('Antiking1', 'Keep antiking in check (v1)', 9, 'Anti-King 1'), ('Antiking2', 'Keep antiking in check (v2)', 9, 'Anti-King 2'), @@ -44,10 +43,9 @@ insert or ignore into Variants (name, description, groupe, display) values ('Capablanca', 'Capablanca Chess', 7, 'Capablanca Chess'), ('Capture', 'Mandatory captures', 1, 'Capture'), ('Castle', 'Win by castling long', 27, 'Castle'), - ('Checkered1', 'Shared pieces (v1)', 12, 'Checkered 1'), - ('Checkered2', 'Shared pieces (v2)', 12, 'Checkered 2'), + ('Checkered', 'Shared pieces', 12, 'Checkered'), ('Checkless', 'No-check mode', 18, 'Checkless'), - ('Chess', 'Standard rules', -1, 'Chess'), + ('Chess960', 'Standard rules', -1, 'Chess960'), ('Circular', 'Run forward', 3, 'Circular Chess'), ('Clorange', 'A Clockwork Orange', 20, 'Clockwork Orange'), ('Colorbound', 'The colorbound clobberers', 5, 'Colorbound Clobberers'), diff --git a/server/models/Challenge.js b/server/models/Challenge.js index a528ca67..e32cd682 100644 --- a/server/models/Challenge.js +++ b/server/models/Challenge.js @@ -8,9 +8,10 @@ const UserModel = require("./User"); * uid: user id (int) * target: recipient id (optional) * vid: variant id (int) - * randomness: integer in 0..2 + * options: varchar * fen: varchar (optional) * cadence: string (3m+2s, 7d ...) + * options: string (js object) */ const ChallengeModel = { @@ -19,7 +20,6 @@ const ChallengeModel = { return ( c.vid.toString().match(/^[0-9]+$/) && c.cadence.match(/^[0-9dhms +]+$/) && - c.randomness.toString().match(/^[0-2]$/) && c.fen.match(/^[a-zA-Z0-9, /-]*$/) && (!c.to || UserModel.checkNameEmail({ name: c.to })) ); @@ -30,11 +30,10 @@ const ChallengeModel = { const query = "INSERT INTO Challenges " + "(added, uid, " + (c.to ? "target, " : "") + - "vid, randomness, fen, cadence) " + - "VALUES " + - "(" + Date.now() + "," + c.uid + "," + (c.to ? c.to + "," : "") + - c.vid + "," + c.randomness + ",'" + c.fen + "','" + c.cadence + "')"; - db.run(query, function(err) { + "vid, options, fen, cadence) " + + "VALUES (" + Date.now() + "," + c.uid + "," + (c.to ? c.to + "," : "") + + c.vid + ",?,'" + c.fen + "','" + c.cadence + "')"; + db.run(query, c.options, function(err) { cb(err, { id: this.lastID }); }); }); diff --git a/server/models/Game.js b/server/models/Game.js index 53046930..8cc526fe 100644 --- a/server/models/Game.js +++ b/server/models/Game.js @@ -15,7 +15,7 @@ const UserModel = require("./User"); * created: datetime * drawOffer: char ('w','b' or '' for none) * rematchOffer: char (similar to drawOffer) - * randomness: integer + * options: varchar * deletedByWhite: boolean * deletedByBlack: boolean * chatReadWhite: datetime @@ -40,7 +40,6 @@ const GameModel = { return ( g.vid.toString().match(/^[0-9]+$/) && g.cadence.match(/^[0-9dhms +]+$/) && - g.randomness.toString().match(/^[0-2]$/) && g.fen.match(/^[a-zA-Z0-9, /-]*$/) && g.players.length == 2 && g.players.every(p => p.id.toString().match(/^[0-9]+$/)) @@ -57,22 +56,22 @@ const GameModel = { }); }, - create: function(vid, fen, randomness, cadence, players, cb) { + create: function(vid, fen, options, cadence, players, cb) { db.serialize(function() { let query = "INSERT INTO Games " + "(" + - "vid, fenStart, fen, randomness, " + + "vid, fenStart, fen, options, " + "white, black, " + "cadence, created" + ") " + "VALUES " + "(" + - vid + ",'" + fen + "','" + fen + "'," + randomness + "," + + vid + ",'" + fen + "','" + fen + "',?," + players[0].id + "," + players[1].id + "," + "'" + cadence + "'," + Date.now() + ")"; - db.run(query, function(err) { + db.run(query, options, function(err) { cb(err, { id: this.lastID }); }); }); @@ -85,7 +84,7 @@ const GameModel = { let query = "SELECT " + "id, vid, fen, fenStart, cadence, created, " + - "white, black, randomness, score, scoreMsg, " + + "white, black, options, score, scoreMsg, " + "chatReadWhite, chatReadBlack, drawOffer, rematchOffer " + "FROM Games " + "WHERE id = " + id; diff --git a/server/routes/challenges.js b/server/routes/challenges.js index fe10ee30..1f626f20 100644 --- a/server/routes/challenges.js +++ b/server/routes/challenges.js @@ -9,6 +9,7 @@ router.post("/challenges", access.logged, access.ajax, (req,res) => { let challenge = { fen: req.body.chall.fen, cadence: req.body.chall.cadence, + options: req.body.chall.options, randomness: req.body.chall.randomness, vid: req.body.chall.vid, uid: req.userId, -- 2.44.0