If new live game starts in background, "new game" notify OK but not first move.
NEW VARIANTS:
-https://www.chessvariants.com/crossover.dir/football.html
- ballon vers n'importe laquelle de nos pièces = 'j'ai terminé mes passes, à toi'
- Also: rename current Football into Squatter2 (and Squatter into Squatter1)
https://www.pychess.org/variant/shogun
https://www.chessvariants.com/incinf.dir/bario.html
https://www.chessvariants.com/index/listcomments.php?order=DESC&itemid=Bario
https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg
Non-chess: ( won't add draughts: https://lidraughts.org/ )
-Avalam, Fanorona https://fr.wikipedia.org/wiki/Fanorona
+Gomoku, Konane
+Fanorona https://fr.wikipedia.org/wiki/Fanorona
Yoté https://fr.wikipedia.org/wiki/Yot%C3%A9 http://www.zillionsofgames.com/cgi-bin/zilligames/submissions.cgi/92187?do=show;id=960
-Qoridor: reserve moves to place the walls, convention from left / from top
--- /dev/null
+https://commons.wikimedia.org/wiki/File:Soccer_ball.svg
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="800"
+ height="800"
+ viewBox="-105 -105 210 210"
+ version="1.1"
+ id="svg58"
+ sodipodi:docname="ball.svg"
+ inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07, custom)">
+ <metadata
+ id="metadata62">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1060"
+ id="namedview60"
+ showgrid="false"
+ inkscape:zoom="1.10375"
+ inkscape:cx="400"
+ inkscape:cy="400"
+ inkscape:window-x="0"
+ inkscape:window-y="20"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg58" />
+ <defs
+ id="defs42">
+ <clipPath
+ id="ball">
+ <circle
+ r="100"
+ stroke-width="0"
+ id="circle2" />
+ </clipPath>
+ <radialGradient
+ id="shadow1"
+ cx="-19.999999"
+ cy="-39.999998"
+ r="160"
+ fx="-19.999999"
+ fy="-39.999998"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="scale(0.9)">
+ <stop
+ offset="0"
+ stop-color="white"
+ stop-opacity="1"
+ id="stop5" />
+ <stop
+ offset=".4"
+ stop-color="white"
+ stop-opacity="1"
+ id="stop7" />
+ <stop
+ offset=".8"
+ stop-color="#EEEEEE"
+ stop-opacity="1"
+ id="stop9" />
+ </radialGradient>
+ <radialGradient
+ id="shadow2"
+ cx="0"
+ cy="0"
+ r="100"
+ fx="0"
+ fy="0"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="scale(0.9)">
+ <stop
+ offset="0"
+ stop-color="white"
+ stop-opacity="0"
+ id="stop12" />
+ <stop
+ offset=".8"
+ stop-color="white"
+ stop-opacity="0"
+ id="stop14" />
+ <stop
+ offset=".99"
+ stop-color="black"
+ stop-opacity=".3"
+ id="stop16" />
+ <stop
+ offset="1"
+ stop-color="black"
+ stop-opacity="1"
+ id="stop18" />
+ </radialGradient>
+ <g
+ id="black_stuff"
+ stroke-linejoin="round"
+ clip-path="url(#ball)">
+ <g
+ fill="black"
+ id="g35">
+ <path
+ d="M 6,-32 Q 26,-28 46,-19 Q 57,-35 64,-47 Q 50,-68 37,-76 Q 17,-75 1,-68 Q 4,-51 6,-32"
+ id="path21" />
+ <path
+ d="M -26,-2 Q -45,-8 -62,-11 Q -74,5 -76,22 Q -69,40 -50,54 Q -32,47 -17,39 Q -23,15 -26,-2"
+ id="path23" />
+ <path
+ d="M -95,22 Q -102,12 -102,-8 V 80 H -85 Q -95,45 -95,22"
+ id="path25" />
+ <path
+ d="M 55,24 Q 41,41 24,52 Q 28,65 31,79 Q 55,78 68,67 Q 78,50 80,35 Q 65,28 55,24"
+ id="path27" />
+ <path
+ d="M 0,120 L -3,95 Q -25,93 -42,82 Q -50,84 -60,81"
+ id="path29" />
+ <path
+ d="M -90,-48 Q -80,-52 -68,-49 Q -52,-71 -35,-77 Q -35,-100 -40,-100 H -100"
+ id="path31" />
+ <path
+ d="M 100,-55 L 87,-37 Q 98,-10 97,5 L 100,6"
+ id="path33" />
+ </g>
+ <g
+ fill="none"
+ id="g39">
+ <path
+ d="M 6,-32 Q -18,-12 -26,-2 M 46,-19 Q 54,5 55,24 M 64,-47 Q 77,-44 87,-37 M 37,-76 Q 39,-90 36,-100 M 1,-68 Q -13,-77 -35,-77 M -62,-11 Q -67,-25 -68,-49 M -76,22 Q -85,24 -95,22 M -50,54 Q -49,70 -42,82 M -17,39 Q 0,48 24,52 M 31,79 Q 20,92 -3,95 M 68,67 L 80,80 M 80,35 Q 90,25 97,5 "
+ id="path37" />
+ </g>
+ </g>
+ </defs>
+ <circle
+ r="90"
+ fill="#ffffff"
+ stroke="none"
+ id="circle44"
+ cx="-1.4210855e-14"
+ cy="-1.4210855e-14"
+ style="stroke-width:0.9" />
+ <circle
+ r="90"
+ fill="url(#shadow1)"
+ stroke="none"
+ id="circle46"
+ style="fill:url(#shadow1);stroke-width:0.9"
+ cx="-1.4210855e-14"
+ cy="-1.4210855e-14" />
+ <use
+ xlink:href="#black_stuff"
+ stroke="#eeeeee"
+ stroke-width="7"
+ id="use48"
+ transform="scale(0.9)"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <use
+ xlink:href="#black_stuff"
+ stroke="#dddddd"
+ stroke-width="4"
+ id="use50"
+ transform="scale(0.9)"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <use
+ xlink:href="#black_stuff"
+ stroke="#999999"
+ stroke-width="2"
+ id="use52"
+ transform="scale(0.9)"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <use
+ xlink:href="#black_stuff"
+ stroke="#000000"
+ stroke-width="1"
+ id="use54"
+ transform="scale(0.9)"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <circle
+ r="90"
+ fill="url(#shadow2)"
+ stroke="none"
+ id="circle56"
+ style="fill:url(#shadow2);stroke-width:0.9"
+ cx="-1.4210855e-14"
+ cy="-1.4210855e-14" />
+</svg>
"Shoot pieces": "Shoot pieces",
"Spartan versus Persians": "Spartan versus Persians",
"Squares disappear": "Squares disappear",
- "Squat last rank": "Squat last rank",
+ "Squat last rank (v1)": "Squat last rank (v1)",
+ "Squat last rank (v2)": "Squat last rank (v2)",
"Standard rules": "Standard rules",
"Stun & kick pieces": "Stun & kick pieces",
"Thai Chess (v1)": "Thai Chess (v1)",
"Shoot pieces": "Tirar de las piezas",
"Spartan versus Persians": "Espartanos contra Persas",
"Squares disappear": "Las casillas desaparecen",
- "Squat last rank": "Ocupa la última fila",
+ "Squat last rank (v1)": "Ocupa la última fila (v1)",
+ "Squat last rank (v2)": "Ocupa la última fila (v2)",
"Standard rules": "Reglas estandar",
"Stun & kick pieces": "Aturdir & patear piezas",
"Thai Chess (v1)": "Ajedrez tailandés (v1)",
"Shoot pieces": "Tirez sur les pièces",
"Spartan versus Persians": "Spartiates contre Perses",
"Squares disappear": "Les cases disparaissent",
- "Squat last rank": "Occupez la dernière rangée",
+ "Squat last rank (v1)": "Occupez la dernière rangée (v1)",
+ "Squat last rank (v2)": "Occupez la dernière rangée (v2)",
"Standard rules": "Règles usuelles",
"Stun & kick pieces": "Étourdissez & frappez les pièces",
"Thai Chess": "Échecs thai",
-p.boxed.
- Win by playing a move on the middle of last rank.
- The king has no royal status.
+p.boxed
+ | Pieces don't capture but can kick a ball.
+ | The object of the game is to send the ball into the enemy goal.
p.
- The goal for White (resp. Black) are the squares d8 and e8 (resp. d1 and
- e1) in the middle of the opposing rank. The first player to settle a
- piece there wins, even if it is attacked.
+ At each turn, a player can first make a normal (non capturing) move.
+ If none of his pieces are adjacent to the ball, such move is mandatory.
+ But, if any of his pieces stand next to the ball he may then or right
+ away kick it, if the step defined by the arrow piece --> ball is
+ compatible with the piece's movement.
+
+p.
+ To play a kick, click on the ball and then on the desired square.
+ The knight send the ball at any knight-step away from its initial position,
+ except on squares adjacent to the knight.
+ However, when the ball is in a corner an exception to this rule is allowed:
+ on the following diagram "kick from g1 to h3" is allowed".
figure.diagram-container
- .diagram
- | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
- figcaption 1...Nc3 and 2...N(x)d1# wins for Black.
+ .diagram.diag12
+ | fen:3q1k1br/r7R/2n2n3/3Q5/6b2/9/9/7N1/RNB2K1Ba:
+ .diagram.diag22
+ | fen:3q1k1br/r7R/2n2n3/3Q5/6b2/9/7a1/7N1/RNB2K1B1:
+ figcaption Before and after (kick) g1-h3
+
+h4 Some restrictions.
+
+p.
+ To avoid infinite cycles, each piece can kick the ball at most
+ once during a turn.
+
+p Pieces cannot stand in any goal square.
-h3 Source
+p The ball may never reach or cross the goal horizontally.
+
+h4 Complete a move on the interface.
+
+p.
+ If at the end of an initial or intermediate move one of your pieces is
+ adjacent to the ball, you'll need to "capture" any of your pieces
+ with the ball to indicate that the move is over.
+
+h3 More information
p
- | This variant is mentioned
- a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
- | on this page
- | . It isn't specified if the winning piece needs to be safe from attacks.
+ | See the
+ a(href="https://www.chessvariants.com/crossover.dir/football.html")
+ | chessvariants page
+ | , for example, and also the
+ a(href="https://www.jsbeasley.co.uk/encyc.htm")
+ | Classified Encyclopedia of Chess Variants
+ | .
+
+p Inventor: Joseph Boyer (1951)
-p.boxed.
- Gana haciendo un movimiento en el centro de la última fila.
- El rey no tiene estatus real.
+p.boxed
+ | Las piezas no capturan pero pueden golpear una bola.
+ | El objetivo del juego es enviar el balón a la portería contraria.
p.
- El objetivo de las blancas (resp. negras) son las casillas d8 y e8
- (resp. d1 y e1) en el medio de la fila opuesta.
- El primer jugador que instala una pieza ahí gana, incluso si es atacada.
+ En cada ronda, un jugador puede realizar primero un movimiento normal
+ (sin captura). Si ninguna de sus piezas está al lado de la bola, tal
+ movimiento es obligatorio.
+ Pero, si una de sus piezas toque la pelota, entonces puede golpearla
+ después de un desplazamiento inicial o inmediatamente, siempre que el paso
+ definido por la flecha pieza --> globo es compatible con los
+ movimientos de este último.
+
+p.
+ Para mover la bola, haga clic en ella y luego en la casilla de llegada.
+ El caballo puede enviar la pelota a cualquier casilla dentro
+ distancia del saltador desde la posición inicial del balón, excepto en las
+ casillas adyacentes al caballo.
+ Sin embargo, cuando la pelota está en una esquina permitimos una excepción
+ a esto regla: en el siguiente diagrama, el caballo puede enviar la pelota
+ de g1 a h3 por ejemplo.
figure.diagram-container
- .diagram
- | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
- figcaption 1...Nc3 y 2...N(x)d1# gana para las negras.
+ .diagram.diag12
+ | fen:3q1k1br/r7R/2n2n3/3Q5/6b2/9/9/7N1/RNB2K1Ba:
+ .diagram.diag22
+ | fen:3q1k1br/r7R/2n2n3/3Q5/6b2/9/7a1/7N1/RNB2K1B1:
+ figcaption Antes y después del golpe g1-h3
+
+h4 Algunas restricciones
+
+p.
+ Para evitar ciclos interminables, cada pieza puede golpear la pelota
+ como máximo una vez durante un turno.
+
+p Ninguna pieza puede estar en una casilla portería.
-h3 Fuente
+p El balón nunca puede alcanzar o cruzar la portería horizontalmente.
+
+h4 Completa una jugada en la interfaz.
+
+p.
+ Si al final de un movimiento inicial o intermedio una de tus piezas es al
+ lado de la bola, entonces tienes que "capturar" una de tus piezas con la
+ bola para indicar el final de la ronda.
+
+h3 Más información
p
- | Esta variante se menciona
- a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
- | en esta pagina
- | . No está claro si la pieza debe estar a salvo de los ataques.
+ | Ver la
+ a(href="https://www.chessvariants.com/crossover.dir/football.html")
+ | página chessvariants.com
+ | , por ejemplo, y también la
+ a(href="https://www.jsbeasley.co.uk/encyc.htm")
+ | Classified Encyclopedia of Chess Variants
+ | .
+
+p Inventor: Joseph Boyer (1951)
-p.boxed.
- Gagnez en jouant un coup au centre de la dernière rangée.
- Le roi n'a pas de statut royal.
+p.boxed
+ | Les pièces ne capturent pas mais peuvent frapper un ballon.
+ | L'objectif du jeu est d'envoyer le ballon dans le but adverse.
p.
- Le but des blancs (resp. des noirs) sont les cases d8 et e8 (resp. d1 et e1)
- au milieu de la rangée opposée. Le premier joueur à y installer une pièce
- gagne, même si celle-ci est attaquée.
+ À chaque tour, un jour peut d'abord effectuer un coup normal (non capturant).
+ Si aucune de ses pièces n'est à côté du ballon, un tel coup est obligatoire.
+ Mais, si une de ses pièces touche le ballon, alors il peut le frapper soit
+ après un déplacement initial soit tout de suite, à condition que le pas
+ défini par la flèche pièce --> ballon soit compatible avec les
+ mouvements de cette dernière.
+
+p.
+ Pour déplacer la balle, cliquez dessus puis sur la case d'arrivée.
+ Le cavalier peut envoyer le ballon vers n'importe quelle case située à
+ distance de cavalier de la position initiale du ballon, sauf sur les
+ cases adjacentes au cavalier.
+ Cependant, quand la balle est dans un coin on autorise une exception à cette
+ règle : sur le diagramme suivant, le cavalier peut envoyer le ballon
+ de g1 à h3 par exemple.
figure.diagram-container
- .diagram
- | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
- figcaption 1...Nc3 et 2...N(x)d1# gagne pour les noirs.
+ .diagram.diag12
+ | fen:3q1k1br/r7R/2n2n3/3Q5/6b2/9/9/7N1/RNB2K1Ba:
+ .diagram.diag22
+ | fen:3q1k1br/r7R/2n2n3/3Q5/6b2/9/7a1/7N1/RNB2K1B1:
+ figcaption Avant et après la frappe g1-h3
+
+h4 Quelques restrictions
+
+p.
+ Afin d'éviter des cycles infinis, chaque pièce peut frapper le ballon
+ au plus une fois pendant un tour.
+
+p Aucune pièce ne peut se trouver sur une case but.
-h3 Source
+p La balle ne peut jamais atteindre ou traverser le but horizontalement.
+
+h4 Compléter un coup sur l'interface.
+
+p.
+ Si à la fin d'un coup initial ou intermédiaire une de vos pièces est à côté
+ du ballon, alors vous devez "capturer" une de vos pièces avec la balle
+ pour indiquer la fin du tour.
+
+h3 Plus d'information
p
- | Cette variante est mentionnée
- a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
- | sur cette page
- | . Il n'est pas précisé si la pièce doit être à l'abri des attaques.
+ | Voir la
+ a(href="https://www.chessvariants.com/crossover.dir/football.html")
+ | page chessvariants.com
+ | , par exemple, et également la
+ a(href="https://www.jsbeasley.co.uk/encyc.htm")
+ | Classified Encyclopedia of Chess Variants
+ | .
+
+p Inventeur : Joseph Boyer (1951)
--- /dev/null
+p.boxed.
+ Win by playing a move on the middle of last rank.
+ The king has no royal status.
+
+p.
+ The goal for White (resp. Black) are the squares d8 and e8 (resp. d1 and
+ e1) in the middle of the opposing rank. The first player to settle a
+ piece there wins, even if it is attacked.
+
+figure.diagram-container
+ .diagram
+ | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+ figcaption 1...Nc3 and 2...N(x)d1# wins for Black.
+
+h3 Source
+
+p
+ | This variant is mentioned
+ a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+ | on this page
+ | . It isn't specified if the winning piece needs to be safe from attacks.
--- /dev/null
+p.boxed.
+ Gana haciendo un movimiento en el centro de la última fila.
+ El rey no tiene estatus real.
+
+p.
+ El objetivo de las blancas (resp. negras) son las casillas d8 y e8
+ (resp. d1 y e1) en el medio de la fila opuesta.
+ El primer jugador que instala una pieza ahí gana, incluso si es atacada.
+
+figure.diagram-container
+ .diagram
+ | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+ figcaption 1...Nc3 y 2...N(x)d1# gana para las negras.
+
+h3 Fuente
+
+p
+ | Esta variante se menciona
+ a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+ | en esta pagina
+ | . No está claro si la pieza debe estar a salvo de los ataques.
--- /dev/null
+p.boxed.
+ Gagnez en jouant un coup au centre de la dernière rangée.
+ Le roi n'a pas de statut royal.
+
+p.
+ Le but des blancs (resp. des noirs) sont les cases d8 et e8 (resp. d1 et e1)
+ au milieu de la rangée opposée. Le premier joueur à y installer une pièce
+ gagne, même si celle-ci est attaquée.
+
+figure.diagram-container
+ .diagram
+ | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+ figcaption 1...Nc3 et 2...N(x)d1# gagne pour les noirs.
+
+h3 Source
+
+p
+ | Cette variante est mentionnée
+ a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+ | sur cette page
+ | . Il n'est pas précisé si la pièce doit être à l'abri des attaques.
"Threechecks",
"Kinglet",
"Koth",
- "Squatter"
+ "Squatter1",
+ "Squatter2"
]
ul
for v in varlist
"Threechecks",
"Kinglet",
"Koth",
- "Squatter"
+ "Squatter1",
+ "Squatter2"
]
ul
for v in varlist
"Threechecks",
"Kinglet",
"Koth",
- "Squatter"
+ "Squatter1",
+ "Squatter2"
]
ul
for v in varlist
}
static get BALL() {
- // Ball is already taken:
+ // 'b' is already taken:
return "aa";
}
const rows = position.split("/");
if (rows.length != V.size.x) return false;
let pieces = { "w": 0, "b": 0 };
- const withBall = Object.keys(V.HAS_BALL_DECODE).concat([V.BALL]);
+ const withBall = Object.keys(V.HAS_BALL_DECODE).concat(['a']);
let ballCount = 0;
for (let row of rows) {
let sumElts = 0;
for (let i = 0; i < row.length; i++) {
const lowerRi = row[i].toLowerCase();
if (V.PIECES.includes(lowerRi)) {
- if (lowerRi != V.BALL) pieces[row[i] == lowerRi ? "b" : "w"]++;
+ if (lowerRi != 'a') pieces[row[i] == lowerRi ? "b" : "w"]++;
if (withBall.includes(lowerRi)) ballCount++;
sumElts++;
- } else {
+ }
+ else {
const num = parseInt(row[i], 10);
if (isNaN(num)) return false;
sumElts += num;
})
);
}
- } else if (mv.vanish[1].c == mv.vanish[0].c) {
+ }
+ else if (mv.vanish[1].c == mv.vanish[0].c) {
// Pass the ball: the passing unit does not disappear
mv.appear.push(JSON.parse(JSON.stringify(mv.vanish[0])));
mv.appear[0].p = V.HAS_BALL_CODE[mv.vanish[1].p];
import { ChessRules } from "@/base_rules";
-import { SuicideRules } from "@/variants/Suicide";
+import { randInt } from "@/utils/alea";
export class FootballRules extends ChessRules {
+ static get HasEnpassant() {
+ return false;
+ }
+
static get HasFlags() {
return false;
}
- static get PawnSpecs() {
- return Object.assign(
- {},
- ChessRules.PawnSpecs,
- { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) }
- );
+ static get size() {
+ return { x: 9, y: 9 };
}
static get Lines() {
return [
// White goal:
- [[0, 3], [0, 5]],
+ [[0, 4], [0, 5]],
[[0, 5], [1, 5]],
- [[1, 5], [1, 3]],
- [[1, 3], [0, 3]],
+ [[1, 4], [0, 4]],
// Black goal:
- [[8, 3], [8, 5]],
- [[8, 5], [7, 5]],
- [[7, 5], [7, 3]],
- [[7, 3], [8, 3]]
+ [[9, 4], [9, 5]],
+ [[9, 5], [8, 5]],
+ [[8, 4], [9, 4]]
];
}
+ static get BALL() {
+ // 'b' is already taken:
+ return "aa";
+ }
+
+ // Check that exactly one ball is on the board
+ // + at least one piece per color.
static IsGoodPosition(position) {
if (position.length == 0) return false;
const rows = position.split("/");
if (rows.length != V.size.x) return false;
- // Just check that at least one piece of each color is there:
let pieces = { "w": 0, "b": 0 };
+ let ballCount = 0;
for (let row of rows) {
let sumElts = 0;
for (let i = 0; i < row.length; i++) {
const lowerRi = row[i].toLowerCase();
- if (V.PIECES.includes(lowerRi)) {
- pieces[row[i] == lowerRi ? "b" : "w"]++;
+ if (!!lowerRi.match(/^[a-z]$/)) {
+ if (V.PIECES.includes(lowerRi))
+ pieces[row[i] == lowerRi ? "b" : "w"]++;
+ else if (lowerRi == 'a') ballCount++;
+ else return false;
sumElts++;
}
else {
}
if (sumElts != V.size.y) return false;
}
- if (Object.values(pieces).some(v => v == 0)) return false;
+ if (ballCount != 1 || Object.values(pieces).some(v => v == 0))
+ return false;
return true;
}
- scanKings() {}
+ static board2fen(b) {
+ if (b == V.BALL) return 'a';
+ return ChessRules.board2fen(b);
+ }
- filterValid(moves) {
+ static fen2board(f) {
+ if (f == 'a') return V.BALL;
+ return ChessRules.fen2board(f);
+ }
+
+ getPpath(b) {
+ if (b == V.BALL) return "Football/ball";
+ return b;
+ }
+
+ canIplay(side, [x, y]) {
+ return (
+ side == this.turn &&
+ (this.board[x][y] == V.BALL || this.getColor(x, y) == side)
+ );
+ }
+
+ // No checks or king tracking etc. But, track ball
+ setOtherVariables() {
+ // Stack of "kicked by" coordinates, to avoid infinite loops
+ this.kickedBy = [ {} ];
+ this.subTurn = 1;
+ this.ballPos = [-1, -1];
+ for (let i=0; i < V.size.x; i++) {
+ for (let j=0; j< V.size.y; j++) {
+ if (this.board[i][j] == V.BALL) {
+ this.ballPos = [i, j];
+ return;
+ }
+ }
+ }
+ }
+
+ static GenRandInitFen(randomness) {
+ if (randomness == 0)
+ return "rnbq1knbr/9/9/9/4a4/9/9/9/RNBQ1KNBR w 0";
+
+ // TODO: following is mostly copy-paste from Suicide variant
+ let pieces = { w: new Array(8), b: new Array(8) };
+ for (let c of ["w", "b"]) {
+ if (c == 'b' && randomness == 1) {
+ pieces['b'] = pieces['w'];
+ break;
+ }
+
+ // Get random squares for every piece, totally freely
+ let positions = shuffle(ArrayFun.range(8));
+ const composition = ['b', 'b', 'r', 'r', 'n', 'n', 'k', 'q'];
+ const rem2 = positions[0] % 2;
+ if (rem2 == positions[1] % 2) {
+ // Fix bishops (on different colors)
+ for (let i=2; i<8; i++) {
+ if (positions[i] % 2 != rem2)
+ [positions[1], positions[i]] = [positions[i], positions[1]];
+ }
+ }
+ for (let i = 0; i < 8; i++) pieces[c][positions[i]] = composition[i];
+ }
+ const piecesB = pieces["b"].join("") ;
+ const piecesW = pieces["w"].join("").toUpperCase();
+ return (
+ piecesB.substr(0, 4) + "1" + piecesB.substr(4) +
+ "/9/9/9/4a4/9/9/9/" +
+ piecesW.substr(0, 4) + "1" + piecesW.substr(4) +
+ " w 0"
+ );
+ }
+
+ tryKickFrom([x, y]) {
+ const bp = this.ballPos;
+ const emptySquare = (i, j) => {
+ return V.OnBoard(i, j) && this.board[i][j] == V.EMPTY;
+ };
+ // Kick the (adjacent) ball from x, y with current turn:
+ const step = [bp[0] - x, bp[1] - y];
+ const piece = this.getPiece(x, y);
+ let moves = [];
+ if (piece == V.KNIGHT) {
+ // The knight case is particular
+ V.steps[V.KNIGHT].forEach(s => {
+ const [i, j] = [bp[0] + s[0], bp[1] + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] == V.EMPTY &&
+ (
+ // In a corner? The, allow all ball moves
+ ([0, 8].includes(bp[0]) && [0, 8].includes(bp[1])) ||
+ // Do not end near the knight
+ (Math.abs(i - x) >= 2 || Math.abs(j - y) >= 2)
+ )
+ ) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ }
+ });
+ }
+ else {
+ let compatible = false,
+ oneStep = false;
+ switch (piece) {
+ case V.ROOK:
+ compatible = (step[0] == 0 || step[1] == 0);
+ break;
+ case V.BISHOP:
+ compatible = (step[0] != 0 && step[1] != 0);
+ break;
+ case V.QUEEN:
+ compatible = true;
+ break;
+ case V.KING:
+ compatible = true;
+ oneStep = true;
+ break;
+ }
+ if (!compatible) return [];
+ let [i, j] = [bp[0] + step[0], bp[1] + step[1]];
+ const horizontalStepOnGoalRow =
+ ([0, 8].includes(bp[0]) && step.some(s => s == 0));
+ if (emptySquare(i, j) && (!horizontalStepOnGoalRow || j != 4)) {
+ moves.push(super.getBasicMove(bp, [i, j]));
+ if (!oneStep) {
+ do {
+ i += step[0];
+ j += step[1];
+ if (!emptySquare(i, j)) break;
+ if (!horizontalStepOnGoalRow || j != 4)
+ moves.push(super.getBasicMove(bp, [i, j]));
+ } while (true);
+ }
+ }
+ }
+ const kickedFrom = x + "-" + y;
+ moves.forEach(m => m.by = kickedFrom)
return moves;
}
+ getPotentialMovesFrom([x, y], computer) {
+ if (V.PIECES.includes(this.getPiece(x, y))) {
+ if (this.subTurn > 1) return [];
+ return (
+ super.getPotentialMovesFrom([x, y])
+ .filter(m => m.end.y != 4 || ![0, 8].includes(m.end.x))
+ );
+ }
+ // Kicking the ball: look for adjacent pieces.
+ const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ const c = this.turn;
+ let moves = [];
+ for (let s of steps) {
+ const [i, j] = [x + s[0], y + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] != V.EMPTY &&
+ this.getColor(i, j) == c
+ ) {
+ Array.prototype.push.apply(moves, this.tryKickFrom([i, j]));
+ }
+ }
+ // And, always add the "end" move. For computer, keep only one
+ outerLoop: 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 && this.getColor(i, j) == c) {
+ moves.push(super.getBasicMove([x, y], [i, j]));
+ if (!!computer) break outerLoop;
+ }
+ }
+ }
+ return moves;
+ }
+
+ // No captures:
+ getSlideNJumpMoves([x, y], steps, oneStep) {
+ let moves = [];
+ outerLoop: for (let step of steps) {
+ let i = x + step[0];
+ let j = y + step[1];
+ let stepCount = 1;
+ while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+ moves.push(this.getBasicMove([x, y], [i, j]));
+ if (!!oneStep) continue outerLoop;
+ i += step[0];
+ j += step[1];
+ stepCount++;
+ }
+ }
+ return moves;
+ }
+
+ // Extra arg "computer" to avoid trimming all redundant pass moves:
+ getAllPotentialMoves(computer) {
+ const color = this.turn;
+ let potentialMoves = [];
+ 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 && this.getColor(i, j) == color) {
+ Array.prototype.push.apply(
+ potentialMoves,
+ this.getPotentialMovesFrom([i, j], computer)
+ );
+ }
+ }
+ }
+ return potentialMoves;
+ }
+
+ getAllValidMoves() {
+ return this.filterValid(this.getAllPotentialMoves("computer"));
+ }
+
+ filterValid(moves) {
+ const L = this.kickedBy.length;
+ const kb = this.kickedBy[L-1];
+ return moves.filter(m => !m.by || !kb[m.by]);
+ }
+
getCheckSquares() {
return [];
}
- // No variables update because no royal king + no castling
- prePlay() {}
- postPlay() {}
- preUndo() {}
- postUndo() {}
+ allowAnotherPass(color) {
+ // Two cases: a piece moved, or the ball moved.
+ // In both cases, check our pieces and ball proximity,
+ // so the move played doesn't matter (if ball position updated)
+ const bp = this.ballPos;
+ const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+ for (let s of steps) {
+ const [i, j] = [this.ballPos[0] + s[0], this.ballPos[1] + s[1]];
+ if (
+ V.OnBoard(i, j) &&
+ this.board[i][j] != V.EMPTY &&
+ this.getColor(i, j) == color
+ ) {
+ return true; //potentially...
+ }
+ }
+ return false;
+ }
+
+ prePlay(move) {
+ if (move.appear[0].p == 'a')
+ this.ballPos = [move.appear[0].x, move.appear[0].y];
+ }
+
+ play(move) {
+ // Special message saying "passes are over"
+ const passesOver = (move.vanish.length == 2);
+ if (!passesOver) {
+ this.prePlay(move);
+ V.PlayOnBoard(this.board, move);
+ }
+ move.turn = [this.turn, this.subTurn]; //easier undo
+ if (passesOver || !this.allowAnotherPass(this.turn)) {
+ this.turn = V.GetOppCol(this.turn);
+ this.subTurn = 1;
+ this.movesCount++;
+ this.kickedBy.push( {} );
+ }
+ else {
+ this.subTurn++;
+ if (!!move.by) {
+ const L = this.kickedBy.length;
+ this.kickedBy[L-1][move.by] = true;
+ }
+ }
+ }
+
+ undo(move) {
+ const passesOver = (move.vanish.length == 2);
+ if (move.turn[0] != this.turn) {
+ [this.turn, this.subTurn] = move.turn;
+ this.movesCount--;
+ this.kickedBy.pop();
+ }
+ else {
+ this.subTurn--;
+ if (!!move.by) {
+ const L = this.kickedBy.length;
+ delete this.kickedBy[L-1][move.by];
+ }
+ }
+ if (!passesOver) {
+ V.UndoOnBoard(this.board, move);
+ this.postUndo(move);
+ }
+ }
+
+ postUndo(move) {
+ if (move.vanish[0].p == 'a')
+ this.ballPos = [move.vanish[0].x, move.vanish[0].y];
+ }
getCurrentScore() {
- const oppCol = V.GetOppCol(this.turn);
- const goal = (oppCol == 'w' ? 0 : 7);
- if (this.board[goal].slice(3, 5).some(b => b[0] == oppCol))
- return oppCol == 'w' ? "1-0" : "0-1";
- if (this.atLeastOneMove()) return "*";
- return "1/2";
+ if (this.board[0][4] == V.BALL) return "1-0";
+ if (this.board[8][4] == V.BALL) return "0-1";
+ return "*";
}
- static GenRandInitFen(randomness) {
- return SuicideRules.GenRandInitFen(randomness);
+ getComputerMove() {
+ let initMoves = this.getAllValidMoves();
+ if (initMoves.length == 0) return null;
+ let moves = JSON.parse(JSON.stringify(initMoves));
+ let mvArray = [];
+ let mv = null;
+ // Just play random moves (for now at least. TODO?)
+ const c = this.turn;
+ while (moves.length > 0) {
+ mv = moves[randInt(moves.length)];
+ mvArray.push(mv);
+ this.play(mv);
+ if (mv.vanish.length == 1 && this.allowAnotherPass(c))
+ // Potential kick
+ moves = this.getPotentialMovesFrom(this.ballPos);
+ else break;
+ }
+ for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+ return (mvArray.length > 1 ? mvArray : mvArray[0]);
+ }
+
+ // NOTE: evalPosition() is wrong, but unused since bot plays at random
+
+ getNotation(move) {
+ if (move.vanish.length == 2) return "pass";
+ if (move.vanish[0].p != 'a') return super.getNotation(move);
+ // Kick: simple notation (TODO?)
+ return V.CoordsToSquare(move.end);
}
};
import { ChessRules } from "@/base_rules";
-export class SquatterRules extends ChessRules {
+export class Squatter1Rules extends ChessRules {
static get Lines() {
return [
--- /dev/null
+import { ChessRules } from "@/base_rules";
+import { SuicideRules } from "@/variants/Suicide";
+
+export class Squatter2Rules extends ChessRules {
+
+ static get HasFlags() {
+ return false;
+ }
+
+ static get PawnSpecs() {
+ return Object.assign(
+ {},
+ ChessRules.PawnSpecs,
+ { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) }
+ );
+ }
+
+ static get Lines() {
+ return [
+ // White goal:
+ [[0, 3], [0, 5]],
+ [[0, 5], [1, 5]],
+ [[1, 5], [1, 3]],
+ [[1, 3], [0, 3]],
+ // Black goal:
+ [[8, 3], [8, 5]],
+ [[8, 5], [7, 5]],
+ [[7, 5], [7, 3]],
+ [[7, 3], [8, 3]]
+ ];
+ }
+
+ static IsGoodPosition(position) {
+ if (position.length == 0) return false;
+ const rows = position.split("/");
+ if (rows.length != V.size.x) return false;
+ // Just check that at least one piece of each color is there:
+ let pieces = { "w": 0, "b": 0 };
+ for (let row of rows) {
+ let sumElts = 0;
+ for (let i = 0; i < row.length; i++) {
+ const lowerRi = row[i].toLowerCase();
+ if (V.PIECES.includes(lowerRi)) {
+ pieces[row[i] == lowerRi ? "b" : "w"]++;
+ sumElts++;
+ }
+ else {
+ const num = parseInt(row[i], 10);
+ if (isNaN(num)) return false;
+ sumElts += num;
+ }
+ }
+ if (sumElts != V.size.y) return false;
+ }
+ if (Object.values(pieces).some(v => v == 0)) return false;
+ return true;
+ }
+
+ scanKings() {}
+
+ filterValid(moves) {
+ return moves;
+ }
+
+ getCheckSquares() {
+ return [];
+ }
+
+ // No variables update because no royal king + no castling
+ prePlay() {}
+ postPlay() {}
+ preUndo() {}
+ postUndo() {}
+
+ getCurrentScore() {
+ const oppCol = V.GetOppCol(this.turn);
+ const goal = (oppCol == 'w' ? 0 : 7);
+ if (this.board[goal].slice(3, 5).some(b => b[0] == oppCol))
+ return oppCol == 'w' ? "1-0" : "0-1";
+ if (this.atLeastOneMove()) return "*";
+ return "1/2";
+ }
+
+ static GenRandInitFen(randomness) {
+ return SuicideRules.GenRandInitFen(randomness);
+ }
+
+};
('Shogi', 'Japanese Chess'),
('Sittuyin', 'Burmese Chess'),
('Spartan', 'Spartan versus Persians'),
- ('Squatter', 'Squat last rank'),
+ ('Squatter1', 'Squat last rank (v1)'),
+ ('Squatter2', 'Squat last rank (v2)'),
('Suicide', 'Lose all pieces'),
('Suction', 'Attract opposite king'),
('Swap', 'Dangerous captures'),