A few fixes. Draft Synchrone2 (not working at all right now)
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 7 Jan 2021 23:10:07 +0000 (00:10 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 7 Jan 2021 23:10:07 +0000 (00:10 +0100)
111 files changed:
TODO
client/public/images/pieces/Otage/ac.png [new file with mode: 0644]
client/public/images/pieces/Otage/ad.png [new file with mode: 0644]
client/public/images/pieces/Otage/ae.png [new file with mode: 0644]
client/public/images/pieces/Otage/af.png [new file with mode: 0644]
client/public/images/pieces/Otage/ag.png [new file with mode: 0644]
client/public/images/pieces/Otage/ai.png [new file with mode: 0644]
client/public/images/pieces/Otage/aj.png [new file with mode: 0644]
client/public/images/pieces/Otage/al.png [new file with mode: 0644]
client/public/images/pieces/Otage/am.png [new file with mode: 0644]
client/public/images/pieces/Otage/as.png [new file with mode: 0644]
client/public/images/pieces/Otage/at.png [new file with mode: 0644]
client/public/images/pieces/Otage/au.png [new file with mode: 0644]
client/public/images/pieces/Otage/aw.png [new file with mode: 0644]
client/public/images/pieces/Otage/ax.png [new file with mode: 0644]
client/public/images/pieces/Otage/az.png [new file with mode: 0644]
client/public/images/pieces/Otage/b_.png [new file with mode: 0644]
client/public/images/pieces/Otage/ba.png [new file with mode: 0644]
client/public/images/pieces/Otage/bb.png [new symlink]
client/public/images/pieces/Otage/bc.png [new file with mode: 0644]
client/public/images/pieces/Otage/bd.png [new file with mode: 0644]
client/public/images/pieces/Otage/be.png [new file with mode: 0644]
client/public/images/pieces/Otage/bf.png [new file with mode: 0644]
client/public/images/pieces/Otage/bg.png [new file with mode: 0644]
client/public/images/pieces/Otage/bh.png [new file with mode: 0644]
client/public/images/pieces/Otage/bi.png [new file with mode: 0644]
client/public/images/pieces/Otage/bj.png [new file with mode: 0644]
client/public/images/pieces/Otage/bk.png [new symlink]
client/public/images/pieces/Otage/bl.png [new file with mode: 0644]
client/public/images/pieces/Otage/bm.png [new file with mode: 0644]
client/public/images/pieces/Otage/bn.png [new symlink]
client/public/images/pieces/Otage/bo.png [new file with mode: 0644]
client/public/images/pieces/Otage/bp.png [new symlink]
client/public/images/pieces/Otage/bq.png [new symlink]
client/public/images/pieces/Otage/br.png [new symlink]
client/public/images/pieces/Otage/bs.png [new file with mode: 0644]
client/public/images/pieces/Otage/bt.png [new file with mode: 0644]
client/public/images/pieces/Otage/bu.png [new file with mode: 0644]
client/public/images/pieces/Otage/bv.png [new file with mode: 0644]
client/public/images/pieces/Otage/bw.png [new file with mode: 0644]
client/public/images/pieces/Otage/bx.png [new file with mode: 0644]
client/public/images/pieces/Otage/by.png [new file with mode: 0644]
client/public/images/pieces/Otage/bz.png [new file with mode: 0644]
client/public/images/pieces/Otage/script.sh [new file with mode: 0644]
client/public/images/pieces/Otage/vc.png [new file with mode: 0644]
client/public/images/pieces/Otage/vd.png [new file with mode: 0644]
client/public/images/pieces/Otage/ve.png [new file with mode: 0644]
client/public/images/pieces/Otage/vf.png [new file with mode: 0644]
client/public/images/pieces/Otage/vg.png [new file with mode: 0644]
client/public/images/pieces/Otage/vi.png [new file with mode: 0644]
client/public/images/pieces/Otage/vj.png [new file with mode: 0644]
client/public/images/pieces/Otage/vl.png [new file with mode: 0644]
client/public/images/pieces/Otage/vm.png [new file with mode: 0644]
client/public/images/pieces/Otage/vs.png [new file with mode: 0644]
client/public/images/pieces/Otage/vt.png [new file with mode: 0644]
client/public/images/pieces/Otage/vu.png [new file with mode: 0644]
client/public/images/pieces/Otage/vw.png [new file with mode: 0644]
client/public/images/pieces/Otage/vx.png [new file with mode: 0644]
client/public/images/pieces/Otage/vz.png [new file with mode: 0644]
client/public/images/pieces/Otage/w_.png [new file with mode: 0644]
client/public/images/pieces/Otage/wa.png [new file with mode: 0644]
client/public/images/pieces/Otage/wb.png [new symlink]
client/public/images/pieces/Otage/wc.png [new file with mode: 0644]
client/public/images/pieces/Otage/wd.png [new file with mode: 0644]
client/public/images/pieces/Otage/we.png [new file with mode: 0644]
client/public/images/pieces/Otage/wf.png [new file with mode: 0644]
client/public/images/pieces/Otage/wg.png [new file with mode: 0644]
client/public/images/pieces/Otage/wh.png [new file with mode: 0644]
client/public/images/pieces/Otage/wi.png [new file with mode: 0644]
client/public/images/pieces/Otage/wj.png [new file with mode: 0644]
client/public/images/pieces/Otage/wk.png [new symlink]
client/public/images/pieces/Otage/wl.png [new file with mode: 0644]
client/public/images/pieces/Otage/wm.png [new file with mode: 0644]
client/public/images/pieces/Otage/wn.png [new symlink]
client/public/images/pieces/Otage/wo.png [new file with mode: 0644]
client/public/images/pieces/Otage/wp.png [new symlink]
client/public/images/pieces/Otage/wq.png [new symlink]
client/public/images/pieces/Otage/wr.png [new symlink]
client/public/images/pieces/Otage/ws.png [new file with mode: 0644]
client/public/images/pieces/Otage/wt.png [new file with mode: 0644]
client/public/images/pieces/Otage/wu.png [new file with mode: 0644]
client/public/images/pieces/Otage/wv.png [new file with mode: 0644]
client/public/images/pieces/Otage/ww.png [new file with mode: 0644]
client/public/images/pieces/Otage/wx.png [new file with mode: 0644]
client/public/images/pieces/Otage/wy.png [new file with mode: 0644]
client/public/images/pieces/Otage/wz.png [new file with mode: 0644]
client/src/base_rules.js
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Otage/en.pug [new file with mode: 0644]
client/src/translations/rules/Otage/es.pug [new file with mode: 0644]
client/src/translations/rules/Otage/fr.pug [new file with mode: 0644]
client/src/translations/rules/Pacosako/en.pug
client/src/translations/rules/Pacosako/es.pug
client/src/translations/rules/Pacosako/fr.pug
client/src/translations/rules/Synchrone1/en.pug [moved from client/src/translations/rules/Synchrone/en.pug with 100% similarity]
client/src/translations/rules/Synchrone1/es.pug [moved from client/src/translations/rules/Synchrone/es.pug with 100% similarity]
client/src/translations/rules/Synchrone1/fr.pug [moved from client/src/translations/rules/Synchrone/fr.pug with 100% similarity]
client/src/translations/rules/Synchrone2/en.pug [new file with mode: 0644]
client/src/translations/rules/Synchrone2/es.pug [new file with mode: 0644]
client/src/translations/rules/Synchrone2/fr.pug [new file with mode: 0644]
client/src/translations/variants/en.pug
client/src/translations/variants/es.pug
client/src/translations/variants/fr.pug
client/src/variants/Otage.js [new file with mode: 0644]
client/src/variants/Pacosako.js
client/src/variants/Synchrone1.js [moved from client/src/variants/Synchrone.js with 98% similarity]
client/src/variants/Synchrone2.js [new file with mode: 0644]
client/src/views/Hall.vue
server/db/populate.sql

diff --git a/TODO b/TODO
index 555f57d..4153030 100644 (file)
--- a/TODO
+++ b/TODO
@@ -20,8 +20,6 @@ Squatter Chess: safe on last rank = win
 Companion Chess : pieces of same nature don't attack each others
 https://www.chessvariants.com/difftaking.dir/brotherhood.html
 Crossing Chess = win when the king cross half-board
-Medusa Chess = Isardam
---> à chaque déplacement, check attaques directes + découvertes autour de case de départ dans 8 directions (pas caval)
 Kingmaker: pawns can promote also into enemy king
 --> no king tracking, getCheckSquares + underCheck test all kings
 Eightkings: 8 pawns + 8 kings (non-royal until the last remains?)
diff --git a/client/public/images/pieces/Otage/ac.png b/client/public/images/pieces/Otage/ac.png
new file mode 100644 (file)
index 0000000..021203e
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 65e285390d1316a145caf59dba56082e84f89c97                 1796
diff --git a/client/public/images/pieces/Otage/ad.png b/client/public/images/pieces/Otage/ad.png
new file mode 100644 (file)
index 0000000..d97512c
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 58766b94570a490158e015089860d757dbe82709                 2114
diff --git a/client/public/images/pieces/Otage/ae.png b/client/public/images/pieces/Otage/ae.png
new file mode 100644 (file)
index 0000000..e78a71b
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 193a5ddafe4ef4d2eded12e288f69acda4774b78                 2136
diff --git a/client/public/images/pieces/Otage/af.png b/client/public/images/pieces/Otage/af.png
new file mode 100644 (file)
index 0000000..0369629
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 69b49bbc5cef4a38c06ba83d0b768109c9c3ec35                 2703
diff --git a/client/public/images/pieces/Otage/ag.png b/client/public/images/pieces/Otage/ag.png
new file mode 100644 (file)
index 0000000..af8a3fe
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 88e66486bbd6a0cd30323c5001065757d19f69bb                 2410
diff --git a/client/public/images/pieces/Otage/ai.png b/client/public/images/pieces/Otage/ai.png
new file mode 100644 (file)
index 0000000..2dffc6f
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 774cb529106591f7fe02aea5b7296d18b5fc59da                 2052
diff --git a/client/public/images/pieces/Otage/aj.png b/client/public/images/pieces/Otage/aj.png
new file mode 100644 (file)
index 0000000..4da175f
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 339260da4f0b7f479ea19a91e8b3dcefa9b08751                 2088
diff --git a/client/public/images/pieces/Otage/al.png b/client/public/images/pieces/Otage/al.png
new file mode 100644 (file)
index 0000000..b56798d
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 95edb0a871d5a69dc8793fd82107ddfd917d2036                 2652
diff --git a/client/public/images/pieces/Otage/am.png b/client/public/images/pieces/Otage/am.png
new file mode 100644 (file)
index 0000000..95026cd
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 8086b2787f75ce1df7742800097bca4877b53e1c                 2336
diff --git a/client/public/images/pieces/Otage/as.png b/client/public/images/pieces/Otage/as.png
new file mode 100644 (file)
index 0000000..f9246f5
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat c5e1df6ab7eaebc7e821f91627eb3b36d080e912                 2641
diff --git a/client/public/images/pieces/Otage/at.png b/client/public/images/pieces/Otage/at.png
new file mode 100644 (file)
index 0000000..0a9811b
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 13b756cdc30146754ed5d108ba2d1a8e04dd327a                 3601
diff --git a/client/public/images/pieces/Otage/au.png b/client/public/images/pieces/Otage/au.png
new file mode 100644 (file)
index 0000000..7eadfef
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 0426e2dc88bd35b1ac744113caa08ff96f3f07ca                 3396
diff --git a/client/public/images/pieces/Otage/aw.png b/client/public/images/pieces/Otage/aw.png
new file mode 100644 (file)
index 0000000..6651a47
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 0134aeb64a94399e30a77d26e140d2a749808d24                 3158
diff --git a/client/public/images/pieces/Otage/ax.png b/client/public/images/pieces/Otage/ax.png
new file mode 100644 (file)
index 0000000..fbc5505
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat ac11ef0357001f61184c15b9885f76f7d665b71f                 3015
diff --git a/client/public/images/pieces/Otage/az.png b/client/public/images/pieces/Otage/az.png
new file mode 100644 (file)
index 0000000..43da7c1
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat cef32ab03de7d74ffaea24d74d71fe16fbfa18ba                 4179
diff --git a/client/public/images/pieces/Otage/b_.png b/client/public/images/pieces/Otage/b_.png
new file mode 100644 (file)
index 0000000..76cb7e8
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat d22c1958114554822bfd3bb1e45f4e67f43beee4                 4356
diff --git a/client/public/images/pieces/Otage/ba.png b/client/public/images/pieces/Otage/ba.png
new file mode 100644 (file)
index 0000000..91d0a1c
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat d12f6fbdee0dfadf161e59769dc200c16cc79607                 1791
diff --git a/client/public/images/pieces/Otage/bb.png b/client/public/images/pieces/Otage/bb.png
new file mode 120000 (symlink)
index 0000000..6dfdf95
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/bb.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/bc.png b/client/public/images/pieces/Otage/bc.png
new file mode 100644 (file)
index 0000000..ec8575b
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat e120c1ec8fc61bc965ff8270a6b5aa65c2aef3cc                 1684
diff --git a/client/public/images/pieces/Otage/bd.png b/client/public/images/pieces/Otage/bd.png
new file mode 100644 (file)
index 0000000..1cef702
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat e02afb036bb850aeb8608e5a75da1b8beaa541ca                 2763
diff --git a/client/public/images/pieces/Otage/be.png b/client/public/images/pieces/Otage/be.png
new file mode 100644 (file)
index 0000000..7965808
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 9bce3cb99b6089f7cb131f44c4e64f9841ac7344                 2399
diff --git a/client/public/images/pieces/Otage/bf.png b/client/public/images/pieces/Otage/bf.png
new file mode 100644 (file)
index 0000000..cd8a0d8
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat cb6891f0ddb698cd67bdb4a5c2539ccc69de2c71                 3666
diff --git a/client/public/images/pieces/Otage/bg.png b/client/public/images/pieces/Otage/bg.png
new file mode 100644 (file)
index 0000000..2cfbf4e
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 6eb758f960e42c05bb45c7cabde713b08ec37d57                 3658
diff --git a/client/public/images/pieces/Otage/bh.png b/client/public/images/pieces/Otage/bh.png
new file mode 100644 (file)
index 0000000..68db883
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 0d28e9e17956e33cfbd29a0e09f8929f58843068                 1666
diff --git a/client/public/images/pieces/Otage/bi.png b/client/public/images/pieces/Otage/bi.png
new file mode 100644 (file)
index 0000000..7f1e839
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 4d1f04788eaa8a200e1a7e1e34bf2fd693d1ecb3                 2845
diff --git a/client/public/images/pieces/Otage/bj.png b/client/public/images/pieces/Otage/bj.png
new file mode 100644 (file)
index 0000000..e629b10
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat d82164152aa37cdd53608e393ea335355793684c                 2377
diff --git a/client/public/images/pieces/Otage/bk.png b/client/public/images/pieces/Otage/bk.png
new file mode 120000 (symlink)
index 0000000..5779820
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/bk.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/bl.png b/client/public/images/pieces/Otage/bl.png
new file mode 100644 (file)
index 0000000..76a9d93
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 2e89fd4980d48a02041db56fc40ebd325c1d8fd7                 3554
diff --git a/client/public/images/pieces/Otage/bm.png b/client/public/images/pieces/Otage/bm.png
new file mode 100644 (file)
index 0000000..78c377e
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 6c3e540858fcdcc65a2956444b373ce10af8a678                 3582
diff --git a/client/public/images/pieces/Otage/bn.png b/client/public/images/pieces/Otage/bn.png
new file mode 120000 (symlink)
index 0000000..45d6547
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/bn.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/bo.png b/client/public/images/pieces/Otage/bo.png
new file mode 100644 (file)
index 0000000..555b50a
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat f8fa77908672c97ab6b5f9b38373d55ece85495b                 3162
diff --git a/client/public/images/pieces/Otage/bp.png b/client/public/images/pieces/Otage/bp.png
new file mode 120000 (symlink)
index 0000000..9fa3172
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/bp.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/bq.png b/client/public/images/pieces/Otage/bq.png
new file mode 120000 (symlink)
index 0000000..ca064fd
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/bq.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/br.png b/client/public/images/pieces/Otage/br.png
new file mode 120000 (symlink)
index 0000000..cad7b69
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/br.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/bs.png b/client/public/images/pieces/Otage/bs.png
new file mode 100644 (file)
index 0000000..773f0c0
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 762cdf2994ae23d97926ba5165c6cfe8e6cc9ad0                 3240
diff --git a/client/public/images/pieces/Otage/bt.png b/client/public/images/pieces/Otage/bt.png
new file mode 100644 (file)
index 0000000..6ecf371
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 65661cb71ca2a39381645457a43bc3cd6eb6c004                 3900
diff --git a/client/public/images/pieces/Otage/bu.png b/client/public/images/pieces/Otage/bu.png
new file mode 100644 (file)
index 0000000..9298309
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat f82094d9a3c991f5e81457a7e90e5cf32feeb4c2                 3874
diff --git a/client/public/images/pieces/Otage/bv.png b/client/public/images/pieces/Otage/bv.png
new file mode 100644 (file)
index 0000000..7e45eee
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat a7d85e986bc2e0901bfcf6e84e40f27cef99ae07                 2803
diff --git a/client/public/images/pieces/Otage/bw.png b/client/public/images/pieces/Otage/bw.png
new file mode 100644 (file)
index 0000000..af76815
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat a5a2aecef097e46a1cb6c63bb3f4d02584624b66                 3972
diff --git a/client/public/images/pieces/Otage/bx.png b/client/public/images/pieces/Otage/bx.png
new file mode 100644 (file)
index 0000000..355825e
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat c020ed2347d1817d1046bc1c0eb632a1244a82d0                 4152
diff --git a/client/public/images/pieces/Otage/by.png b/client/public/images/pieces/Otage/by.png
new file mode 100644 (file)
index 0000000..aaa5e98
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 380e33b4d0ed9b7e12009c1d1fb8b60e4de9b808                 4419
diff --git a/client/public/images/pieces/Otage/bz.png b/client/public/images/pieces/Otage/bz.png
new file mode 100644 (file)
index 0000000..6f56f2c
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 8ac9cf1d541acbc1fd06316f16637912449fb265                 4612
diff --git a/client/public/images/pieces/Otage/script.sh b/client/public/images/pieces/Otage/script.sh
new file mode 100644 (file)
index 0000000..776540d
--- /dev/null
@@ -0,0 +1,11 @@
+taille=64
+for color in w b; do
+  [[ $color = 'w' ]] && oppCol='b' || oppCol='w'
+  for captured in p r n b q k; do
+    convert $color$captured.png -resize "$taille"x"$taille" $color"$captured"_small.png
+    for piece in p r n b q k; do
+      convert -composite -gravity center $oppCol$piece.png $color"$captured"_small.png $color$captured$piece.png
+    done
+  done
+done
+# Finally: manual renaming (TODO)
diff --git a/client/public/images/pieces/Otage/vc.png b/client/public/images/pieces/Otage/vc.png
new file mode 100644 (file)
index 0000000..0902e55
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 236752cfe7d08810ef3ebcadadd5fef4598a055c                 1802
diff --git a/client/public/images/pieces/Otage/vd.png b/client/public/images/pieces/Otage/vd.png
new file mode 100644 (file)
index 0000000..40fc1ff
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 2ffcc839afd07a0ab24044ad069d28a04cbeed58                 2983
diff --git a/client/public/images/pieces/Otage/ve.png b/client/public/images/pieces/Otage/ve.png
new file mode 100644 (file)
index 0000000..eedda80
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat df9c4fae98b2081ef9bc8fd1464f9157415c0119                 2961
diff --git a/client/public/images/pieces/Otage/vf.png b/client/public/images/pieces/Otage/vf.png
new file mode 100644 (file)
index 0000000..212b330
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat e2f005c73214ee900ee14c5dfadcde52ada05cbe                 4277
diff --git a/client/public/images/pieces/Otage/vg.png b/client/public/images/pieces/Otage/vg.png
new file mode 100644 (file)
index 0000000..3e822fb
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 67c43942a3a7e3cabc992b1dcbea0ed539aa0a65                 3141
diff --git a/client/public/images/pieces/Otage/vi.png b/client/public/images/pieces/Otage/vi.png
new file mode 100644 (file)
index 0000000..b5df554
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 81f9a3f4865dfbcc92ea4f198ca8ba4d9c284def                 3125
diff --git a/client/public/images/pieces/Otage/vj.png b/client/public/images/pieces/Otage/vj.png
new file mode 100644 (file)
index 0000000..4aec896
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 41eb0d17e29224bdfade80d9c9d7ac9d83da441d                 2990
diff --git a/client/public/images/pieces/Otage/vl.png b/client/public/images/pieces/Otage/vl.png
new file mode 100644 (file)
index 0000000..6f88954
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 446a4edf4046c1e82109531b9b8c5cff7f4880be                 4284
diff --git a/client/public/images/pieces/Otage/vm.png b/client/public/images/pieces/Otage/vm.png
new file mode 100644 (file)
index 0000000..5148cf8
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 811e84107a9a1ee6acf80564a4b46e91dadc64fd                 3090
diff --git a/client/public/images/pieces/Otage/vs.png b/client/public/images/pieces/Otage/vs.png
new file mode 100644 (file)
index 0000000..b554885
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 949a78814edadf22b0e664c5aff663d270bdccf5                 3326
diff --git a/client/public/images/pieces/Otage/vt.png b/client/public/images/pieces/Otage/vt.png
new file mode 100644 (file)
index 0000000..25dd7e8
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 5c6311a1cdf14cb4e215a0bb8c351d9ce2924f59                 4593
diff --git a/client/public/images/pieces/Otage/vu.png b/client/public/images/pieces/Otage/vu.png
new file mode 100644 (file)
index 0000000..4b46bd7
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat f8c087c9652f542a954d156a458f78d7772dba8d                 3629
diff --git a/client/public/images/pieces/Otage/vw.png b/client/public/images/pieces/Otage/vw.png
new file mode 100644 (file)
index 0000000..7e8745b
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat b5adbdcee6e9c589d105d62c97aecb86b65494ea                 4508
diff --git a/client/public/images/pieces/Otage/vx.png b/client/public/images/pieces/Otage/vx.png
new file mode 100644 (file)
index 0000000..fd04cf3
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 4f03b86e99ccbeb0be463607a0099ac3ce0d8b3b                 3506
diff --git a/client/public/images/pieces/Otage/vz.png b/client/public/images/pieces/Otage/vz.png
new file mode 100644 (file)
index 0000000..c3d1f0d
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 5ea91792aa06c1d1b63b8b07d023d23cede90808                 4056
diff --git a/client/public/images/pieces/Otage/w_.png b/client/public/images/pieces/Otage/w_.png
new file mode 100644 (file)
index 0000000..f239fea
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 05ab786ebb4fc25440b15ee766315c787d5786fc                 4396
diff --git a/client/public/images/pieces/Otage/wa.png b/client/public/images/pieces/Otage/wa.png
new file mode 100644 (file)
index 0000000..63ad031
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 026c80ccc3b3527c06d97be79954f90a785ea715                 2229
diff --git a/client/public/images/pieces/Otage/wb.png b/client/public/images/pieces/Otage/wb.png
new file mode 120000 (symlink)
index 0000000..36a1712
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/wb.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/wc.png b/client/public/images/pieces/Otage/wc.png
new file mode 100644 (file)
index 0000000..086de72
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat df49dd56b0bdaf77efb955d674c7f0d596e831d7                 2125
diff --git a/client/public/images/pieces/Otage/wd.png b/client/public/images/pieces/Otage/wd.png
new file mode 100644 (file)
index 0000000..6035099
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat a671d25cab689601ebc29528b7c3398deea867b9                 2457
diff --git a/client/public/images/pieces/Otage/we.png b/client/public/images/pieces/Otage/we.png
new file mode 100644 (file)
index 0000000..6756b8e
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 2662e08a587c44d74b999c97b4ca8274bd301d61                 2634
diff --git a/client/public/images/pieces/Otage/wf.png b/client/public/images/pieces/Otage/wf.png
new file mode 100644 (file)
index 0000000..eb0dd00
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 24cc6c2b2e241a134d0414b105489a6b4f48cad2                 3060
diff --git a/client/public/images/pieces/Otage/wg.png b/client/public/images/pieces/Otage/wg.png
new file mode 100644 (file)
index 0000000..f80dd0c
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 0cf7be0d77ff50a60b1d042f40d28f952780936e                 3269
diff --git a/client/public/images/pieces/Otage/wh.png b/client/public/images/pieces/Otage/wh.png
new file mode 100644 (file)
index 0000000..7766f32
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 066a4540c9232bec7fdd3635f2aca844814c1b4a                 1868
diff --git a/client/public/images/pieces/Otage/wi.png b/client/public/images/pieces/Otage/wi.png
new file mode 100644 (file)
index 0000000..beb8384
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 129ab825232e9a19c10b375536c1c1fb99534153                 2316
diff --git a/client/public/images/pieces/Otage/wj.png b/client/public/images/pieces/Otage/wj.png
new file mode 100644 (file)
index 0000000..241c02b
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 814246e6d014e16b9fd9d66f94fb2457075ffc4a                 2161
diff --git a/client/public/images/pieces/Otage/wk.png b/client/public/images/pieces/Otage/wk.png
new file mode 120000 (symlink)
index 0000000..acf5400
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/wk.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/wl.png b/client/public/images/pieces/Otage/wl.png
new file mode 100644 (file)
index 0000000..7733c53
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 5cca31f9cf96f0bb0ca89b7a9e280a1b7c9b3445                 2873
diff --git a/client/public/images/pieces/Otage/wm.png b/client/public/images/pieces/Otage/wm.png
new file mode 100644 (file)
index 0000000..49dd0e5
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 13fcad23431fd4e972675536f136b760ad644795                 3040
diff --git a/client/public/images/pieces/Otage/wn.png b/client/public/images/pieces/Otage/wn.png
new file mode 120000 (symlink)
index 0000000..4508036
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/wn.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/wo.png b/client/public/images/pieces/Otage/wo.png
new file mode 100644 (file)
index 0000000..e0a0058
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 150776276e067f50f8d8e9cdd10bd946582e9c12                 3541
diff --git a/client/public/images/pieces/Otage/wp.png b/client/public/images/pieces/Otage/wp.png
new file mode 120000 (symlink)
index 0000000..b1c5670
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/wp.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/wq.png b/client/public/images/pieces/Otage/wq.png
new file mode 120000 (symlink)
index 0000000..536649a
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/wq.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/wr.png b/client/public/images/pieces/Otage/wr.png
new file mode 120000 (symlink)
index 0000000..fb36f29
--- /dev/null
@@ -0,0 +1 @@
+../Eightpieces/tmp_png/wr.png
\ No newline at end of file
diff --git a/client/public/images/pieces/Otage/ws.png b/client/public/images/pieces/Otage/ws.png
new file mode 100644 (file)
index 0000000..5274172
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 12e4984af6227a93acca7d8ff1231e0ac558622c                 3401
diff --git a/client/public/images/pieces/Otage/wt.png b/client/public/images/pieces/Otage/wt.png
new file mode 100644 (file)
index 0000000..9982d94
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 49aa672eb86924d39f722a98ade88244eca0d5cb                 4161
diff --git a/client/public/images/pieces/Otage/wu.png b/client/public/images/pieces/Otage/wu.png
new file mode 100644 (file)
index 0000000..473da49
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 2244328474fd95e628f75821e5ef8dd505e59972                 4232
diff --git a/client/public/images/pieces/Otage/wv.png b/client/public/images/pieces/Otage/wv.png
new file mode 100644 (file)
index 0000000..f00d480
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat d26f408f59430cdadf663616927871b6f038edae                 3322
diff --git a/client/public/images/pieces/Otage/ww.png b/client/public/images/pieces/Otage/ww.png
new file mode 100644 (file)
index 0000000..c655b27
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 4b211057ba07ade56ea40dbe63b8cd2ab53facec                 3883
diff --git a/client/public/images/pieces/Otage/wx.png b/client/public/images/pieces/Otage/wx.png
new file mode 100644 (file)
index 0000000..25d8399
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 76d08fca30cbfcd5be55129aab7a60503f860a92                 4021
diff --git a/client/public/images/pieces/Otage/wy.png b/client/public/images/pieces/Otage/wy.png
new file mode 100644 (file)
index 0000000..a9c12cd
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 412a2f4b1000bdbc9d307b7467c17f7b6ebfb234                 4977
diff --git a/client/public/images/pieces/Otage/wz.png b/client/public/images/pieces/Otage/wz.png
new file mode 100644 (file)
index 0000000..7f2e974
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat b47b3b98b69cc245600e2727f6dcddd650b03ab7                 5293
index b8f0506..e9ea7bc 100644 (file)
@@ -528,6 +528,7 @@ export const ChessRules = class ChessRules {
   }
 
   // Scan board for kings positions
+  // TODO: should be done from board, no need for the complete FEN
   scanKings(fen) {
     // Squares of white and black king:
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
@@ -1073,7 +1074,7 @@ export const ChessRules = class ChessRules {
         this.board[rx][ry] != V.EMPTY &&
         this.getPiece(rx, ry) == piece &&
         this.getColor(rx, ry) == color &&
-        this.canTake([rx, ry], [x, y])
+        this.canTake([rx, ry], [x, y]) //for Paco-Sako (TODO: necessary?)
       ) {
         return true;
       }
index 0aebead..fc8bd3d 100644 (file)
@@ -179,6 +179,7 @@ export const translations = {
   "Both sides of the mirror": "Both sides of the mirror",
   "Burmese Chess": "Burmese Chess",
   "Capture all of a kind": "Capture all of a kind",
+  "Capture and release hostages": "Capture and release hostages",
   "Capture both colors": "Capture both colors",
   "Capture en passant": "Capture en passant",
   "Capture on the edge": "Capture on the edge",
@@ -251,7 +252,8 @@ export const translations = {
   "Pawns capture backward": "Pawns capture backward",
   "Pawns move diagonally": "Pawns move diagonally",
   "Pieces upside down": "Pieces upside down",
-  "Play at the same time": "Play at the same time",
+  "Play at the same time (v1)": "Play at the same time (v1)",
+  "Play at the same time (v2)": "Play at the same time (v2)",
   "Play more and more moves (v1)": "Play more and more moves (v1)",
   "Play more and more moves (v2)": "Play more and more moves (v2)",
   "Play opponent's pieces": "Play opponent's pieces",
index 2a9bb36..d0f87e0 100644 (file)
@@ -179,6 +179,7 @@ export const translations = {
   "Both sides of the mirror": "Ambos lados del espejo",
   "Burmese Chess": "Ajedrez birmano",
   "Capture all of a kind": "Capturar todo del mismo tipo",
+  "Capture and release hostages": "Captura y libera a los rehenes",
   "Capture both colors": "Captura ambos colores",
   "Capture en passant": "Capturar en passant",
   "Capture on the edge": "Capturar en el borde",
@@ -251,7 +252,8 @@ export const translations = {
   "Pawns capture backward": "Los peones capturan hacia atrás",
   "Pawns move diagonally": "Los peones se mueven en diagonal",
   "Pieces upside down": "Piezas al revés",
-  "Play at the same time": "Jugar al mismo tiempo",
+  "Play at the same time (v1)": "Jugar al mismo tiempo (v1)",
+  "Play at the same time (v2)": "Jugar al mismo tiempo (v2)",
   "Play more and more moves (v1)": "Jugar más y más movimientos (v1)",
   "Play more and more moves (v2)": "Jugar más y más movimientos (v2)",
   "Play opponent's pieces": "Jugar piezas opuestas",
index 7552b3a..3adb35e 100644 (file)
@@ -179,6 +179,7 @@ export const translations = {
   "Both sides of the mirror": "Les deux côté du miroir",
   "Burmese Chess": "Échecs birmans",
   "Capture all of a kind": "Capturez tout d'un même type",
+  "Capture and release hostages": "Capturez et libérez les otages",
   "Capture both colors": "Capturer les deux couleurs",
   "Capture en passant": "Capturer en passant",
   "Capture on the edge": "Capturer sur le bord",
@@ -251,7 +252,8 @@ export const translations = {
   "Pawns capture backward": "Les pions capturent en arrière",
   "Pawns move diagonally": "Les pions vont en diagonale",
   "Pieces upside down": "Pièces à l'envers",
-  "Play at the same time": "Jouer en même temps",
+  "Play at the same time (v1)": "Jouer en même temps (v1)",
+  "Play at the same time (v2)": "Jouer en même temps (v2)",
   "Play more and more moves (v1)": "Jouez de plus en plus de coups (v1)",
   "Play more and more moves (v2)": "Jouez de plus en plus de coups (v2)",
   "Play opponent's pieces": "Jouez les pièces adverses",
diff --git a/client/src/translations/rules/Otage/en.pug b/client/src/translations/rules/Otage/en.pug
new file mode 100644 (file)
index 0000000..ff50199
--- /dev/null
@@ -0,0 +1,13 @@
+p.boxed
+  | A piece "A" captured by another piece "B" remain on the board,
+  | following "B". It can be freed by capturing "B" at any moment.
+
+p
+  | This variant is inspired by 
+  a(href="/#/variants/Pacosako") Paco-Sako
+  | , where union pieces are controlled by both players.
+  | Here, an "union" can be moved only by the last player capturing it.
+
+p.
+  The king can thus capture without losing the game.
+  Everything else is exactly the same as in Paco-Sako.
diff --git a/client/src/translations/rules/Otage/es.pug b/client/src/translations/rules/Otage/es.pug
new file mode 100644 (file)
index 0000000..330abc3
--- /dev/null
@@ -0,0 +1,13 @@
+p.boxed
+  | Una pieza "A" capturada por otra pieza "B" permanece en el tablero,
+  | siguiendo "B". Puede ser liberada capturando "B" en cualquier momento.
+
+p
+  | Esta variante está inspirada de 
+  a(href="/#/variants/Pacosako") Paco-Sako
+  | , donde las piezas-unión son controladas por ambos jugadores.
+  | Aquí, una "unión" solo puede moverse por el último jugador que lo capturó.
+
+p.
+  El rey puede capturar sin perder el juego.
+  Todo lo demás ocurre exactamente como en Paco-Sako.
diff --git a/client/src/translations/rules/Otage/fr.pug b/client/src/translations/rules/Otage/fr.pug
new file mode 100644 (file)
index 0000000..51104b6
--- /dev/null
@@ -0,0 +1,14 @@
+p.boxed
+  | Une pièce "A" capturée par une autre pièce "B" reste sur l'échiquier,
+  | suivant "B". Elle peut être libérée en capturant "B" à tout moment.
+
+p
+  | Cette variante est inspirée de 
+  a(href="/#/variants/Pacosako") Paco-Sako
+  | , où les pièces-union sont contrôlées par les deux joueurs.
+  | Ici, une "union" ne peut être déplacée que par
+  | le dernier joueur l'ayant capturée.
+
+p.
+  Le roi peut alors capturer sans perdre la partie.
+  Tout le reste se déroule exactement comme à Paco-Sako.
index 51383e0..9e08caf 100644 (file)
@@ -92,8 +92,8 @@ ul
     dancing with a queen, and makes the move e5 to g3, the other player
     cannot move it back to e5 just after.
   li.
-    Pawns can advance two squares only if they never moved (by themselves
-    or as part of an union).
+    Pawns (or pawns in unions) can advance two squares from their initial
+    position, but you may only do that once per file.
   li.
     If you form an union with your king but end dancing with the
     other king on the other end of the chain, the game is a draw.
index 618f38d..3a41c52 100644 (file)
@@ -95,8 +95,8 @@ ul
     baila con una reina y hace un movimiento de e5 a g3, el otro jugador no
     puede lo reemplace en e5 inmediatamente después.
   li.
-    Los peones pueden avanzar dos espacios solo si nunca han
-    movido (por sí mismos o como miembro de una unión).
+    Los peones (o peones en unión) pueden avanzar dos espacios desde su
+    posición inicial, pero solo puede hacerlo una vez por columna.
   li.
     Si formas una unión con tu rey pero terminas bailando con
     el rey oponente en el otro extremo de la cadena, el juego se empata.
index de85735..e18cdd1 100644 (file)
@@ -95,8 +95,8 @@ ul
     une dame, et effectue un déplacement de e5 en g3, l'autre joueur ne peut
     pas la replacer en e5 immédiatement après.
   li.
-    Les pions peuvent avancer de deux cases seulement s'ils n'ont jamais
-    bougé (par eux-mêmes ou comme membre d'une union).
+    Les pions (ou pions en union) peuvent avancer de deux cases depuis leur
+    position initiale, mais vous ne pouvez le faire qu'une fois par colonne.
   li.
     Si vous formez une union avec votre roi mais terminez par danser avec
     le roi adverse à l'autre bout de la chaîne, la partie est nulle.
diff --git a/client/src/translations/rules/Synchrone2/en.pug b/client/src/translations/rules/Synchrone2/en.pug
new file mode 100644 (file)
index 0000000..15050ab
--- /dev/null
@@ -0,0 +1,9 @@
+p.boxed TODO
+
+p No en passant captures.
+
+p No anticipated recaptures.
+
+p But, a deterministic "capturing turn" added.
+
+p http://www.hexenspiel.de/engl/synchronous-chess/
diff --git a/client/src/translations/rules/Synchrone2/es.pug b/client/src/translations/rules/Synchrone2/es.pug
new file mode 100644 (file)
index 0000000..21203ba
--- /dev/null
@@ -0,0 +1 @@
+p.boxed TODO
diff --git a/client/src/translations/rules/Synchrone2/fr.pug b/client/src/translations/rules/Synchrone2/fr.pug
new file mode 100644 (file)
index 0000000..21203ba
--- /dev/null
@@ -0,0 +1 @@
+p.boxed TODO
index 9baec9d..7eeb43f 100644 (file)
@@ -202,7 +202,8 @@ p.
     "Dark",
     "Hidden",
     "Hiddenqueen",
-    "Synchrone"
+    "Synchrone1",
+    "Synchrone2"
   ]
 ul
   for v in varlist
@@ -402,6 +403,7 @@ p.
     "Hamilton",
     "Isardam",
     "Magnetic",
+    "Otage",
     "Pacosako",
     "Parachute",
     "Screen",
index e1eb190..ad4fa47 100644 (file)
@@ -209,7 +209,8 @@ p.
     "Dark",
     "Hidden",
     "Hiddenqueen",
-    "Synchrone"
+    "Synchrone1",
+    "Synchrone2"
   ]
 ul
   for v in varlist
@@ -413,6 +414,7 @@ p.
     "Hamilton",
     "Isardam",
     "Magnetic",
+    "Otage",
     "Pacosako",
     "Parachute",
     "Screen",
index 209211a..28d7876 100644 (file)
@@ -208,7 +208,8 @@ p.
     "Dark",
     "Hidden",
     "Hiddenqueen",
-    "Synchrone"
+    "Synchrone1",
+    "Synchrone2"
   ]
 ul
   for v in varlist
@@ -412,6 +413,7 @@ p.
     "Hamilton",
     "Isardam",
     "Magnetic",
+    "Otage",
     "Pacosako",
     "Parachute",
     "Screen",
diff --git a/client/src/variants/Otage.js b/client/src/variants/Otage.js
new file mode 100644 (file)
index 0000000..8497098
--- /dev/null
@@ -0,0 +1,809 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+import { ArrayFun } from "@/utils/array";
+
+export class OtageRules extends ChessRules {
+
+  static get IMAGE_EXTENSION() {
+    return ".png";
+  }
+
+  // Hostage / Capturer combinations
+  // + letter among a, b, v, w to indicate colors + config:
+  //   a: black first, black controls
+  //   b: white first, black controls
+  //   v: black first, white controls
+  //   w: white first, white controls
+  static get UNIONS() {
+    return {
+      a: ['p', 'p'],
+      c: ['p', 'r'],
+      d: ['p', 'n'],
+      e: ['p', 'b'],
+      f: ['p', 'q'],
+      g: ['p', 'k'],
+      h: ['r', 'r'],
+      i: ['r', 'n'],
+      j: ['r', 'b'],
+      l: ['r', 'q'],
+      m: ['r', 'k'],
+      o: ['n', 'n'],
+      s: ['n', 'b'],
+      t: ['n', 'q'],
+      u: ['n', 'k'],
+      v: ['b', 'b'],
+      w: ['b', 'q'],
+      x: ['b', 'k'],
+      y: ['q', 'q'],
+      z: ['q', 'k'],
+      '_': ['k', 'k']
+    };
+  }
+
+  static board2fen(b) {
+    if (ChessRules.PIECES.includes(b[1])) return ChessRules.board2fen(b);
+    // Show symbol first (no collisions)
+    return b[1] + b[0];
+  }
+
+  static fen2board(f) {
+    if (f.length == 1) return ChessRules.fen2board(f);
+    return f[1] + f[0]; //"color" first
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kingSymb = ['k', 'g', 'm', 'u', 'x', '_'];
+    let kings = { 'k': 0, 'K': 0 };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        const lowR = row[i].toLowerCase
+        const readNext = !(ChessRules.PIECES.includes(lowR));
+        if (!!(lowR.match(/[a-z_]/))) {
+          sumElts++;
+          if (kingSymb.includes(row[i])) kings['k']++;
+          // Not "else if", if two kings dancing together
+          if (kingSymb.some(s => row[i] == s.toUpperCase())) kings['K']++;
+          if (readNext) i++;
+        }
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num) || num <= 0) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // Both kings should be on board. Exactly one per color.
+    if (Object.values(kings).some(v => v != 1)) return false;
+    return true;
+  }
+
+  static GetBoard(position) {
+    const rows = position.split("/");
+    let board = ArrayFun.init(V.size.x, V.size.y, "");
+    for (let i = 0; i < rows.length; i++) {
+      let j = 0;
+      for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) {
+        const character = rows[i][indexInRow];
+        const num = parseInt(character, 10);
+        // If num is a number, just shift j:
+        if (!isNaN(num)) j += num;
+        else {
+          // Something at position i,j
+          const lowC = character.toLowerCase();
+          if (ChessRules.PIECES.includes(lowC))
+            board[i][j++] = V.fen2board(character);
+          else
+            board[i][j++] = V.fen2board(lowC + rows[i][++indexInRow]);
+        }
+      }
+    }
+    return board;
+  }
+
+  getPpath(b) {
+    return "Otage/" + b;
+  }
+
+  getPPpath(m) {
+    if (ChessRules.PIECES.includes(m.appear[0].p)) return super.getPPpath(m);
+    // For an "union", show only relevant piece:
+    // The color must be deduced from the move: reaching final rank of who?
+    const color = (m.appear[0].x == 0 ? 'w' : 'b');
+    const up = this.getUnionPieces(m.appear[0].c, m.appear[0].p);
+    return "Pacosako/" + color + up[color];
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    const p1 = this.board[x1][y1].charAt(1);
+    if (!(ChessRules.PIECES.includes(p1))) return false;
+    const p2 = this.board[x2][y2].charAt(1);
+    if (!(ChessRules.PIECES.includes(p2))) return true;
+    const c1 = this.board[x1][y1].charAt(0);
+    const c2 = this.board[x2][y2].charAt(0);
+    return (c1 != c2);
+  }
+
+  canIplay(side, [x, y]) {
+    const c = this.board[x][y].charAt(0);
+    const compSide = (side == 'w' ? 'v' : 'a');
+    return (this.turn == side && [side, compSide].includes(c));
+  }
+
+  scanKings(fen) {
+    this.kingPos = { w: [-1, -1], b: [-1, -1] };
+    const fenRows = V.ParseFen(fen).position.split("/");
+    const startRow = { 'w': V.size.x - 1, 'b': 0 };
+    const kingSymb = ['k', 'g', 'm', 'u', 'x', '_'];
+    for (let i = 0; i < fenRows.length; i++) {
+      let k = 0;
+      for (let j = 0; j < fenRows[i].length; j++) {
+        const c = fenRows[i].charAt(j);
+        const lowR = c.toLowerCase();
+        const readNext = !(ChessRules.PIECES.includes(lowR));
+        if (!!(lowR.match(/[a-z_]/))) {
+          if (kingSymb.includes(c))
+            this.kingPos["b"] = [i, k];
+          // Not "else if", in case of two kings dancing together
+          if (kingSymb.some(s => c == s.toUpperCase()))
+            this.kingPos["w"] = [i, k];
+          if (readNext) j++;
+        }
+        else {
+          const num = parseInt(fenRows[i].charAt(j), 10);
+          if (!isNaN(num)) k += num - 1;
+        }
+        k++;
+      }
+    }
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    // Stack of "last move" only for intermediate chaining
+    this.lastMoveEnd = [null];
+  }
+
+  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];
+  }
+
+  static GenRandInitFen(randomness) {
+    // Add 16 pawns flags:
+    return ChessRules.GenRandInitFen(randomness)
+      .slice(0, -2) + "1111111111111111 -";
+  }
+
+  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;
+  }
+
+  getPiece(i, j) {
+    const p = this.board[i][j].charAt(1);
+    if (ChessRules.PIECES.includes(p)) return p;
+    const c = this.board[i][j].charAt(0);
+    const idx = (['a', 'w'].includes(c) ? 0 : 1);
+    return V.UNIONS[p][idx];
+  }
+
+  getUnionPieces(color, code) {
+    const pieces = V.UNIONS[code];
+    return {
+      w: pieces[ ['b', 'w'].includes(color) ? 0 : 1 ],
+      b: pieces[ ['a', 'v'].includes(color) ? 0 : 1 ]
+    };
+  }
+
+  // p1: white piece, p2: black piece, capturer: (temporary) owner
+  getUnionCode(p1, p2, capturer) {
+    let uIdx = (
+      Object.values(V.UNIONS).findIndex(v => v[0] == p1 && v[1] == p2)
+    );
+    let c = '';
+    if (capturer == 'w') c = (uIdx >= 0 ? 'w' : 'v');
+    else c = (uIdx >= 0 ? 'b' : 'a');
+    if (uIdx == -1) {
+      uIdx = (
+        Object.values(V.UNIONS).findIndex(v => v[0] == p2 && v[1] == p1)
+      );
+    }
+    return { c: c, p: Object.keys(V.UNIONS)[uIdx] };
+  }
+
+  getBasicMove([sx, sy], [ex, ey], tr) {
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    const piece = (!!lm ? lm.p : null);
+    const initColor = (!!piece ? this.turn : this.board[sx][sy].charAt(0));
+    const initPiece = (piece || this.board[sx][sy].charAt(1));
+    const c = this.turn;
+    const oppCol = V.GetOppCol(c);
+    if (!!tr && !(ChessRules.PIECES.includes(initPiece))) {
+      // Transformation computed without taking union into account
+      const up = this.getUnionPieces(initColor, initPiece);
+      let args = [tr.p, up[oppCol]];
+      if (['a', 'v'].includes(initColor)) args = args.reverse();
+      const capturer = (['a', 'b'].includes(initColor) ? 'b' : 'w');
+      const cp = this.getUnionCode(args[0], args[1], capturer);
+      tr.c = cp.c;
+      tr.p = cp.p;
+    }
+    // 4 cases : moving
+    //  - union to free square (other cases are illegal: return null)
+    //  - normal piece to free square,
+    //                 to enemy normal piece, or
+    //                 to union (releasing our piece)
+    let mv = new Move({
+      start: { x: sx, y: sy },
+      end: { x: ex, y: ey },
+      vanish: []
+    });
+    if (!piece) {
+      mv.vanish = [
+        new PiPo({
+          x: sx,
+          y: sy,
+          c: initColor,
+          p: initPiece
+        })
+      ];
+    }
+    // Treat free square cases first:
+    if (this.board[ex][ey] == V.EMPTY) {
+      mv.appear = [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: !!tr ? tr.c : initColor,
+          p: !!tr ? tr.p : initPiece
+        })
+      ];
+      return mv;
+    }
+    // Now the two cases with union / release:
+    const destColor = this.board[ex][ey].charAt(0);
+    const destPiece = this.board[ex][ey].charAt(1);
+    mv.vanish.push(
+      new PiPo({
+        x: ex,
+        y: ey,
+        c: destColor,
+        p: destPiece
+      })
+    );
+    if (ChessRules.PIECES.includes(destPiece)) {
+      // Normal piece: just create union
+      let args = [!!tr ? tr.p : initPiece, destPiece];
+      if (c == 'b') args = args.reverse();
+      const cp = this.getUnionCode(args[0], args[1], c);
+      mv.appear = [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: cp.c,
+          p: cp.p
+        })
+      ];
+      return mv;
+    }
+    // Releasing a piece in an union: keep track of released piece
+    const up = this.getUnionPieces(destColor, destPiece);
+    let args = [!!tr ? tr.p : initPiece, up[oppCol]];
+    if (c == 'b') args = args.reverse();
+    const cp = this.getUnionCode(args[0], args[1], c);
+    mv.appear = [
+      new PiPo({
+        x: ex,
+        y: ey,
+        c: cp.c,
+        p: cp.p
+      })
+    ];
+    mv.end.released = up[c];
+    return mv;
+  }
+
+  // noCastle arg: when detecting king attacks
+  getPotentialMovesFrom([x, y], noCastle) {
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    if (!!lm && (x != lm.x || y != lm.y)) return [];
+    const piece = (!!lm ? lm.p : this.getPiece(x, y));
+    if (!!lm) {
+      var saveSquare = this.board[x][y];
+      this.board[x][y] = this.turn + piece;
+    }
+    let baseMoves = [];
+    const c = this.turn;
+    switch (piece || this.getPiece(x, y)) {
+      case V.PAWN: {
+        const firstRank = (c == 'w' ? 7 : 0);
+        baseMoves = this.getPotentialPawnMoves([x, y]).filter(m => {
+          // Skip forbidden 2-squares jumps (except from first rank)
+          // Also skip unions capturing en-passant (not allowed).
+          return (
+            (
+              m.start.x == firstRank ||
+              Math.abs(m.end.x - m.start.x) == 1 ||
+              this.pawnFlags[c][m.start.y]
+            )
+            &&
+            (
+              this.board[x][y].charAt(1) == V.PAWN ||
+              m.start.y == m.end.y
+            )
+          );
+        });
+        break;
+      }
+      case V.ROOK:
+        baseMoves = this.getPotentialRookMoves([x, y]);
+        break;
+      case V.KNIGHT:
+        baseMoves = this.getPotentialKnightMoves([x, y]);
+        break;
+      case V.BISHOP:
+        baseMoves = this.getPotentialBishopMoves([x, y]);
+        break;
+      case V.QUEEN:
+        baseMoves = this.getPotentialQueenMoves([x, y]);
+        break;
+      case V.KING:
+        baseMoves = this.getSlideNJumpMoves(
+          sq,
+          V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+          "oneStep"
+        );
+        if (!noCastle && this.castleFlags[this.turn].some(v => v < V.size.y))
+          baseMoves = baseMoves.concat(this.getCastleMoves(sq));
+        break;
+    }
+    // When a pawn in an union reaches final rank with a non-standard
+    // promotion move: apply promotion anyway
+    let moves = [];
+    const oppCol = V.GetOppCol(c);
+    const oppLastRank = (c == 'w' ? 7 : 0);
+    baseMoves.forEach(m => {
+      if (
+        m.end.x == oppLastRank &&
+        ['c', 'd', 'e', 'f', 'g'].includes(m.appear[0].p)
+      ) {
+        // Move to first rank, which is last rank for opponent's pawn.
+        // => Show promotion choices.
+        // Find our piece in union (not a pawn)
+        const up = this.getUnionPieces(m.appear[0].c, m.appear[0].p);
+        // merge with all potential promotion pieces + push (loop)
+        for (let promotionPiece of [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]) {
+          let args = [up[c], promotionPiece];
+          if (c == 'b') args = args.reverse();
+          const cp = this.getUnionCode(args[0], args[1], c);
+          let cpMove = JSON.parse(JSON.stringify(m));
+          cpMove.appear[0].c = cp.c;
+          cpMove.appear[0].p = cp.p;
+          moves.push(cpMove);
+        }
+      }
+      else {
+        if (
+          m.vanish.length > 0 &&
+          m.vanish[0].p == V.PAWN &&
+          m.start.y != m.end.y &&
+          this.board[m.end.x][m.end.y] == V.EMPTY
+        ) {
+          if (!!lm)
+            // No en-passant inside a chaining
+            return;
+          // Fix en-passant capture: union type, maybe released piece too
+          const cs = [m.end.x + (c == 'w' ? 1 : -1), m.end.y];
+          const code = this.board[cs[0]][cs[1]].charAt(1);
+          if (code == V.PAWN) {
+            // Simple en-passant capture (usual: just form union)
+            m.appear[0].c = c;
+            m.appear[0].p = 'a';
+          }
+          else {
+            // An union pawn + something just moved two squares
+            const color = this.board[cs[0]][cs[1]].charAt(0);
+            const up = this.getUnionPieces(color, code);
+            m.end.released = up[c];
+            let args = [V.PAWN, up[oppCol]];
+            if (c == 'b') args = args.reverse();
+            const cp = this.getUnionCode(args[0], args[1], c);
+            m.appear[0].c = cp.c;
+            m.appear[0].p = cp.p;
+          }
+        }
+        moves.push(m);
+      }
+    });
+    if (!!lm) this.board[x][y] = saveSquare;
+    return moves;
+  }
+
+  getEpSquare(moveOrSquare) {
+    if (typeof moveOrSquare === "string") {
+      const square = moveOrSquare;
+      if (square == "-") return undefined;
+      return V.SquareToCoords(square);
+    }
+    const move = moveOrSquare;
+    const s = move.start,
+          e = move.end;
+    if (
+      s.y == e.y &&
+      Math.abs(s.x - e.x) == 2 &&
+      this.getPiece(s.x, s.y) == V.PAWN
+    ) {
+      return {
+        x: (s.x + e.x) / 2,
+        y: s.y
+      };
+    }
+    return undefined;
+  }
+
+  getCastleMoves([x, y]) {
+    const c = this.turn;
+    const accepted = (c == 'w' ? ['v', 'w'] : ['a', 'b']);
+    const oppCol = V.GetOppCol(c);
+    let moves = [];
+    const finalSquares = [ [2, 3], [6, 5] ];
+    castlingCheck: for (let castleSide = 0; castleSide < 2; castleSide++) {
+      if (this.castleFlags[c][castleSide] >= 8) continue;
+      const rookPos = this.castleFlags[c][castleSide];
+      const castlingColor = this.board[x][rookPos].charAt(0);
+      const castlingPiece = this.board[x][rookPos].charAt(1);
+
+      // Nothing on the path of the king ?
+      const finDist = finalSquares[castleSide][0] - y;
+      let step = finDist / Math.max(1, Math.abs(finDist));
+      let i = y;
+      let kingSquares = [y];
+      do {
+        if (
+          this.board[x][i] != V.EMPTY &&
+          !accepted.includes(this.getColor(x, i))
+        ) {
+          continue castlingCheck;
+        }
+        i += step;
+        kingSquares.push(i);
+      } while (i != finalSquares[castleSide][0]);
+      // No checks on the path of the king ?
+      if (this.isAttacked(kingSquares, oppCol)) continue castlingCheck;
+
+      // Nothing on the path to the rook?
+      step = castleSide == 0 ? -1 : 1;
+      for (i = y + step; i != rookPos; i += step) {
+        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
+      }
+
+      // Nothing on final squares, except maybe king and castling rook?
+      for (i = 0; i < 2; i++) {
+        if (
+          finalSquares[castleSide][i] != rookPos &&
+          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
+          (
+            finalSquares[castleSide][i] != y ||
+            // TODO: next test seems superflu
+            !accepted.includes(this.getColor(x, finalSquares[castleSide][i]))
+          )
+        ) {
+          continue castlingCheck;
+        }
+      }
+
+      moves.push(
+        new Move({
+          appear: [
+            new PiPo({
+              x: x,
+              y: finalSquares[castleSide][0],
+              p: V.KING,
+              c: c
+            }),
+            new PiPo({
+              x: x,
+              y: finalSquares[castleSide][1],
+              p: castlingPiece,
+              c: castlingColor
+            })
+          ],
+          vanish: [
+            // King might be initially disguised (Titan...)
+            new PiPo({ x: x, y: y, p: V.KING, c: c }),
+            new PiPo({ x: x, y: rookPos, p: castlingPiece, c: castlingColor })
+          ],
+          end:
+            Math.abs(y - rookPos) <= 2
+              ? { x: x, y: rookPos }
+              : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
+        })
+      );
+    }
+
+    return moves;
+  }
+
+  getEnpassantCaptures(sq, shiftX) {
+    // HACK: when artificially change turn, do not consider en-passant
+    const mcMod2 = this.movesCount % 2;
+    const c = this.turn;
+    if ((c == 'w' && mcMod2 == 1) || (c == 'b' && mcMod2 == 0)) return [];
+    return super.getEnpassantCaptures(sq, shiftX);
+  }
+
+  isAttacked_aux(files, color, positions, fromSquare, released) {
+    // "positions" = array of FENs to detect infinite loops. Example:
+    // r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/P1eDP3/3B1PP1/R3K1NR,
+    // Bxd2 Bxc3 Bxb4 Bxc3 Bxb4 etc.
+    const newPos = { fen: super.getBaseFen(), piece: released };
+    if (positions.some(p => p.piece == newPos.piece && p.fen == newPos.fen))
+      // Start of an infinite loop: exit
+      return false;
+    positions.push(newPos);
+    const rank = (color == 'w' ? 0 : 7);
+    const moves = this.getPotentialMovesFrom(fromSquare);
+    if (moves.some(m => m.end.x == rank && files.includes(m.end.y)))
+      // Found an attack!
+      return true;
+    for (let m of moves) {
+      if (!!m.end.released) {
+        // Turn won't change since !!m.released
+        this.play(m);
+        const res = this.isAttacked_aux(
+          files, color, positions, [m.end.x, m.end.y], m.end.released);
+        this.undo(m);
+        if (res) return true;
+      }
+    }
+    return false;
+  }
+
+  isAttacked(files, color) {
+    const rank = (color == 'w' ? 0 : 7);
+    // Since it's too difficult (impossible?) to search from the square itself,
+    // let's adopt a suboptimal but working strategy: find all attacks.
+    const c = this.turn;
+    // Artificial turn change is required:
+    this.turn = color;
+    let res = false;
+    outerLoop: for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        // Attacks must start from a normal piece, not an union.
+        // Therefore, the following test is correct.
+        if (
+          this.board[i][j] != V.EMPTY &&
+          [V.KING, V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(
+            this.board[i][j].charAt(1)) &&
+          this.board[i][j].charAt(0) == color
+        ) {
+          // Try from here.
+          const moves = this.getPotentialMovesFrom([i, j], "noCastle");
+          if (moves.some(m => m.end.x == rank && files.includes(m.end.y))) {
+            res = true;
+            break outerLoop;
+          }
+          for (let m of moves) {
+            if (!!m.end.released) {
+              // Turn won't change since !!m.released
+              this.play(m);
+              let positions = [];
+              res = this.isAttacked_aux(
+                files, color, positions, [m.end.x, m.end.y], m.end.released);
+              this.undo(m);
+              if (res) break outerLoop;
+            }
+          }
+        }
+      }
+    }
+    this.turn = c;
+    return res;
+  }
+
+  // Do not consider checks, except to forbid castling
+  getCheckSquares() {
+    return [];
+  }
+  filterValid(moves) {
+    return moves;
+  }
+
+  updateCastleFlags(move, piece) {
+    const c = this.turn;
+    const firstRank = (c == "w" ? 7 : 0);
+    if (piece == V.KING && move.appear.length > 0)
+      this.castleFlags[c] = [V.size.y, V.size.y];
+    else if (
+      move.start.x == firstRank &&
+      this.castleFlags[c].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+      this.castleFlags[c][flagIdx] = V.size.y;
+    }
+    else if (
+      move.end.x == firstRank &&
+      this.castleFlags[c].includes(move.end.y)
+    ) {
+      // Move to our rook: necessary normal piece, to union, releasing
+      // (or the rook was moved before!)
+      const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
+      this.castleFlags[c][flagIdx] = V.size.y;
+    }
+  }
+
+  prePlay(move) {
+    // Easier before move is played in this case (flags are saved)
+    const c = this.turn;
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    const piece = (!!lm ? lm.p : move.vanish[0].p);
+    if (piece == V.KING)
+      this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
+    this.updateCastleFlags(move, piece);
+    const pawnFirstRank = (c == 'w' ? 6 : 1);
+    if (move.start.x == pawnFirstRank)
+      // This move (potentially) turns off a 2-squares pawn flag
+      this.pawnFlags[c][move.start.y] = false;
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    this.prePlay(move);
+    this.epSquares.push(this.getEpSquare(move));
+    // Check if the move is the last of the turn: all cases except releases
+    if (!move.end.released) {
+      // No more union releases available
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.lastMoveEnd.push(null);
+    }
+    else
+      this.lastMoveEnd.push(Object.assign({ p: move.end.released }, move.end));
+    V.PlayOnBoard(this.board, move);
+  }
+
+  undo(move) {
+    this.epSquares.pop();
+    this.disaggregateFlags(JSON.parse(move.flags));
+    V.UndoOnBoard(this.board, move);
+    this.lastMoveEnd.pop();
+    if (!move.end.released) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    if (this.getPiece(move.start.x, move.start.y) == V.KING)
+      this.kingPos[this.turn] = [move.start.x, move.start.y];
+  }
+
+  getCurrentScore() {
+    // Check kings: if one is captured, the side lost
+    for (let c of ['w', 'b']) {
+      const kp = this.kingPos[c];
+      const cell = this.board[kp[0]][kp[1]];
+      if (
+        cell[1] != V.KING &&
+        (
+          (c == 'w' && ['a', 'b'].includes(cell[0])) ||
+          (c == 'b' && ['v', 'w'].includes(cell[0]))
+        )
+      ) {
+        // King is captured
+        return (c == 'w' ? "0-1" : "1-0");
+      }
+    }
+    return "*";
+  }
+
+  getComputerMove() {
+    let initMoves = this.getAllValidMoves();
+    if (initMoves.length == 0) return null;
+    // Loop until valid move is found (no blocked pawn released...)
+    while (true) {
+      let moves = JSON.parse(JSON.stringify(initMoves));
+      let mvArray = [];
+      let mv = null;
+      // Just play random moves (for now at least. TODO?)
+      while (moves.length > 0) {
+        mv = moves[randInt(moves.length)];
+        mvArray.push(mv);
+        this.play(mv);
+        if (!!mv.end.released)
+          // A piece was just released from an union
+          moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
+        else break;
+      }
+      for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+      if (!mv.end.released) return (mvArray.length > 1 ? mvArray : mvArray[0]);
+    }
+  }
+
+  // NOTE: evalPosition() is wrong, but unused since bot plays at random
+
+  getNotation(move) {
+    if (move.appear.length == 2 && move.appear[0].p == V.KING)
+      return (move.end.y < move.start.y ? "0-0-0" : "0-0");
+
+    const c = this.turn;
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    let piece = null;
+    if (!lm && move.vanish.length == 0)
+      // When importing a game, the info move.released is lost
+      piece = move.appear[0].p;
+    else piece = (!!lm ? lm.p : move.vanish[0].p);
+    if (!(ChessRules.PIECES.includes(piece))) {
+      // Decode (moving) union
+      const up = this.getUnionPieces(
+        move.vanish.length > 0 ? move.vanish[0].c : move.appear[0].c, piece);
+      piece = up[c]
+    }
+
+    // Basic move notation:
+    let notation = piece.toUpperCase();
+    if (
+      this.board[move.end.x][move.end.y] != V.EMPTY ||
+      (piece == V.PAWN && move.start.y != move.end.y)
+    ) {
+      notation += "x";
+    }
+    const finalSquare = V.CoordsToSquare(move.end);
+    notation += finalSquare;
+
+    // Add potential promotion indications:
+    const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]);
+    if (move.end.x == firstLastRank[1] && piece == V.PAWN) {
+      const up = this.getUnionPieces(move.appear[0].c, move.appear[0].p);
+      notation += "=" + up[c].toUpperCase();
+    }
+    else if (
+      move.end.x == firstLastRank[0] &&
+      move.vanish.length > 0 &&
+      ['c', 'd', 'e', 'f', 'g'].includes(move.vanish[0].p)
+    ) {
+      // We promoted an opponent's pawn
+      const oppCol = V.GetOppCol(c);
+      const up = this.getUnionPieces(move.appear[0].c, move.appear[0].p);
+      notation += "=" + up[oppCol].toUpperCase();
+    }
+
+    return notation;
+  }
+
+};
index ae4fe6f..9f0abf8 100644 (file)
@@ -48,7 +48,6 @@ export class PacosakoRules extends ChessRules {
     for (let row of rows) {
       let sumElts = 0;
       for (let i = 0; i < row.length; i++) {
-        const lowR = row[i].toLowerCase
         if (!!(row[i].toLowerCase().match(/[a-z_]/))) {
           sumElts++;
           if (kingSymb.includes(row[i])) kings['k']++;
@@ -181,7 +180,8 @@ export class PacosakoRules extends ChessRules {
   getUmove(move) {
     if (
       move.vanish.length == 1 &&
-      !(ChessRules.PIECES.includes(move.appear[0].p))
+      !(ChessRules.PIECES.includes(move.appear[0].p)) &&
+      move.appear[0].p == move.vanish[0].p //not a promotion
     ) {
       // An union moving
       return { start: move.start, end: move.end };
@@ -354,7 +354,8 @@ export class PacosakoRules extends ChessRules {
         p: cp.p
       })
     ];
-    mv.released = up[c];
+    // In move.end, to be sent to the server
+    mv.end.released = up[c];
     return mv;
   }
 
@@ -443,7 +444,6 @@ export class PacosakoRules extends ChessRules {
             return;
           // Fix en-passant capture: union type, maybe released piece too
           const cs = [m.end.x + (c == 'w' ? 1 : -1), m.end.y];
-          const color = this.board[cs[0]][cs[1]].charAt(0);
           const code = this.board[cs[0]][cs[1]].charAt(1);
           if (code == V.PAWN) {
             // Simple en-passant capture (usual: just form union)
@@ -451,9 +451,10 @@ export class PacosakoRules extends ChessRules {
             m.appear[0].p = 'a';
           }
           else {
-            // An union pawn + something juste moved two squares
+            // An union pawn + something just moved two squares
+            const color = this.board[cs[0]][cs[1]].charAt(0);
             const up = this.getUnionPieces(color, code);
-            m.released = up[c];
+            m.end.released = up[c];
             let args = [V.PAWN, up[oppCol]];
             if (c == 'b') args = args.reverse();
             const cp = this.getUnionCode(args[0], args[1]);
@@ -477,11 +478,10 @@ export class PacosakoRules extends ChessRules {
     const move = moveOrSquare;
     const s = move.start,
           e = move.end;
-    const oppCol = V.GetOppCol(this.turn);
     if (
       s.y == e.y &&
       Math.abs(s.x - e.x) == 2 &&
-      this.getPiece(s.x, s.y, oppCol) == V.PAWN
+      this.getPiece(s.x, s.y, this.turn) == V.PAWN
     ) {
       return {
         x: (s.x + e.x) / 2,
@@ -497,7 +497,7 @@ export class PacosakoRules extends ChessRules {
       !!m1 &&
       !(ChessRules.PIECES.includes(m2.appear[0].p)) &&
       m2.vanish.length == 1 &&
-      !m2.released &&
+      !m2.end.released &&
       m1.start.x == m2.end.x &&
       m1.end.x == m2.start.x &&
       m1.start.y == m2.end.y &&
@@ -611,11 +611,11 @@ export class PacosakoRules extends ChessRules {
       // Found an attack!
       return true;
     for (let m of moves) {
-      if (!!m.released) {
+      if (!!m.end.released) {
         // Turn won't change since !!m.released
         this.play(m);
         const res = this.isAttacked_aux(
-          files, color, positions, [m.end.x, m.end.y], m.released);
+          files, color, positions, [m.end.x, m.end.y], m.end.released);
         this.undo(m);
         if (res) return true;
       }
@@ -649,12 +649,12 @@ export class PacosakoRules extends ChessRules {
             break outerLoop;
           }
           for (let m of moves) {
-            if (!!m.released) {
+            if (!!m.end.released) {
               // Turn won't change since !!m.released
               this.play(m);
               let positions = [];
               res = this.isAttacked_aux(
-                files, color, positions, [m.end.x, m.end.y], m.released);
+                files, color, positions, [m.end.x, m.end.y], m.end.released);
               this.undo(m);
               if (res) break outerLoop;
             }
@@ -719,13 +719,19 @@ export class PacosakoRules extends ChessRules {
     this.prePlay(move);
     this.epSquares.push(this.getEpSquare(move));
     // Check if the move is the last of the turn: all cases except releases
-    if (!move.released) {
+    if (!move.end.released) {
       // No more union releases available
       this.turn = V.GetOppCol(this.turn);
       this.movesCount++;
       this.lastMoveEnd.push(null);
     }
-    else this.lastMoveEnd.push(Object.assign({ p: move.released }, move.end));
+    else {
+      this.lastMoveEnd.push({
+        p: move.end.released,
+        x: move.end.x,
+        y: move.end.y
+      });
+    }
     V.PlayOnBoard(this.board, move);
     this.umoves.push(this.getUmove(move));
   }
@@ -735,7 +741,7 @@ export class PacosakoRules extends ChessRules {
     this.disaggregateFlags(JSON.parse(move.flags));
     V.UndoOnBoard(this.board, move);
     this.lastMoveEnd.pop();
-    if (!move.released) {
+    if (!move.end.released) {
       this.turn = V.GetOppCol(this.turn);
       this.movesCount--;
     }
@@ -775,13 +781,13 @@ export class PacosakoRules extends ChessRules {
         mv = moves[randInt(moves.length)];
         mvArray.push(mv);
         this.play(mv);
-        if (!!mv.released)
+        if (!!mv.end.released)
           // A piece was just released from an union
           moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
         else break;
       }
       for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
-      if (!mv.released) return (mvArray.length > 1 ? mvArray : mvArray[0]);
+      if (!mv.end.released) return (mvArray.length > 1 ? mvArray : mvArray[0]);
     }
   }
 
similarity index 98%
rename from client/src/variants/Synchrone.js
rename to client/src/variants/Synchrone1.js
index 365dd9a..ba9cf9f 100644 (file)
@@ -1,7 +1,7 @@
 import { ChessRules } from "@/base_rules";
 import { randInt } from "@/utils/alea";
 
-export class SynchroneRules extends ChessRules {
+export class Synchrone1Rules extends ChessRules {
 
   static get CanAnalyze() {
     return false;
@@ -48,8 +48,8 @@ export class SynchroneRules extends ChessRules {
   static ParseFen(fen) {
     const fenParts = fen.split(" ");
     return Object.assign(
-      ChessRules.ParseFen(fen),
-      { whiteMove: fenParts[5] }
+      { whiteMove: fenParts[5] },
+      ChessRules.ParseFen(fen)
     );
   }
 
@@ -71,7 +71,7 @@ export class SynchroneRules extends ChessRules {
     const epArray = parsedFen.enpassant.split(",");
     this.epSquares = [];
     epArray.forEach(epsq => this.epSquares.push(this.getEpSquare(epsq)));
-    super.scanKings(fen);
+    this.scanKings();
     // Also init whiteMove
     this.whiteMove =
       parsedFen.whiteMove != "-"
@@ -299,7 +299,8 @@ export class SynchroneRules extends ChessRules {
         // Castle
         smove.appear.push(m1.appear[1]);
         smove.vanish.push(m1.vanish[1]);
-      } else if (
+      }
+      else if (
         m1.vanish.length == 2 &&
         (
           m1.vanish[1].x != m2.start.x ||
@@ -312,7 +313,8 @@ export class SynchroneRules extends ChessRules {
         // Castle
         smove.appear.push(m2.appear[1]);
         smove.vanish.push(m2.vanish[1]);
-      } else if (
+      }
+      else if (
         m2.vanish.length == 2 &&
         (
           m2.vanish[1].x != m1.start.x ||
@@ -321,7 +323,8 @@ export class SynchroneRules extends ChessRules {
       ) {
         smove.vanish.push(m2.vanish[1]);
       }
-    } else {
+    }
+    else {
       // Collision:
       if (m1.vanish.length == 1 && m2.vanish.length == 1) {
         // Easy case: both disappear except if one is a king
@@ -335,7 +338,8 @@ export class SynchroneRules extends ChessRules {
             c: (p1 == V.KING ? 'w' : 'b')
           });
         }
-      } else {
+      }
+      else {
         // One move is a self-capture and the other a normal capture:
         // only the self-capture appears
         const selfCaptureMove =
@@ -434,7 +438,8 @@ export class SynchroneRules extends ChessRules {
       this.scanKings();
       // Also reset whiteMove
       this.whiteMove = null;
-    } else this.whiteMove = move.whiteMove;
+    }
+    else this.whiteMove = move.whiteMove;
   }
 
   getCheckSquares() {
diff --git a/client/src/variants/Synchrone2.js b/client/src/variants/Synchrone2.js
new file mode 100644 (file)
index 0000000..7f62398
--- /dev/null
@@ -0,0 +1,122 @@
+import { ChessRules } from "@/base_rules";
+import { Synchrone1Rules } from "@/variants/Synchrone1";
+import { randInt } from "@/utils/alea";
+
+export class Synchrone2Rules extends Synchrone1Rules {
+
+  static get CanAnalyze() {
+    return true;//false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static IsGoodFen(fen) {
+    if (!Synchrone1Rules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check initFen (not really... TODO?)
+    if (!fenParsed.initFen || fenParsed.initFen == "-") return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      {
+        initFen: fenParts[4],
+        whiteMove: fenParts[5]
+      },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  getInitfenFen() {
+    if (!this.whiteMove) return "-";
+    return JSON.stringify({
+      start: this.whiteMove.start,
+      end: this.whiteMove.end,
+      appear: this.whiteMove.appear,
+      vanish: this.whiteMove.vanish
+    });
+  }
+
+  getFen() {
+    return (
+      super.getFen() + " " +
+      this.getInitfenFen() + " " +
+      this.getWhitemoveFen()
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    const res = ChessRules.GenRandInitFen(randomness);
+    // Add initFen field:
+    return res.slice(0, -1) + " " + res.split(' ')[1] + " -";
+  }
+
+  setOtherVariables(fen) {
+    const parsedFen = V.ParseFen(fen);
+    this.setFlags(parsedFen.flags);
+    super.scanKings(fen);
+    // Also init whiteMove
+    this.whiteMove =
+      parsedFen.whiteMove != "-"
+        ? JSON.parse(parsedFen.whiteMove)
+        : null;
+    // And initFen (not empty)
+    this.initFen = parsedFen.initFen;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.movesCount % 4 <= 1) return super.getPotentialMovesFrom([x, y]);
+    // TODO: either add a "blackMove' field in FEN (bof...),
+    // or write an helper function to detect from diff positions,
+    // which piece moved (if not disappeared!), which moves are valid.
+    // + do not forget pass move (king 2 king): always possible at stage 2.
+    return [];
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    // Do not play on board (would reveal the move...)
+    this.turn = V.GetOppCol(this.turn);
+    this.movesCount++;
+    this.postPlay(move);
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    if (this.turn == 'w')
+      // Back to the middle of the move
+      V.UndoOnBoard(this.board, move.smove);
+    this.turn = V.GetOppCol(this.turn);
+    this.movesCount--;
+    this.postUndo(move);
+  }
+
+  getCurrentScore() {
+    if (this.movesCount % 4 != 0)
+      // Turn (2 x white + black) not over yet
+      return "*";
+    // Was a king captured?
+    if (this.kingPos['w'][0] < 0) return "0-1";
+    if (this.kingPos['b'][0] < 0) return "1-0";
+    const whiteCanMove = this.atLeastOneMove('w');
+    const blackCanMove = this.atLeastOneMove('b');
+    if (whiteCanMove && blackCanMove) return "*";
+    // Game over
+    const whiteInCheck = this.underCheck('w');
+    const blackInCheck = this.underCheck('b');
+    if (
+      (whiteCanMove && !this.underCheck('b')) ||
+      (blackCanMove && !this.underCheck('w'))
+    ) {
+      return "1/2";
+    }
+    // Checkmate: could be mutual
+    if (!whiteCanMove && !blackCanMove) return "1/2";
+    return (whiteCanMove ? "1-0" : "0-1");
+  }
+
+};
index 7d7f234..c91e127 100644 (file)
@@ -982,7 +982,7 @@ export default {
             .getElementById("btnC" + newChall.type)
             .classList.add("somethingnew");
         }
-        if (chall.to == this.st.user.name) {
+        if (!!chall.to && chall.to == this.st.user.name) {
           notify(
             "New challenge",
             // fromValues.name should exist since the player is online, but
index e1156e5..88a9003 100644 (file)
@@ -7,7 +7,8 @@ insert or ignore into Variants (name, description, noProblems) values
   ('Dice', 'Roll the dice', true),
   ('Hidden', 'Unidentified pieces', true),
   ('Hiddenqueen', 'Queen disguised as a pawn', true),
-  ('Synchrone', 'Play at the same time', true);
+  ('Synchrone1', 'Play at the same time (v1)', true);
+  ('Synchrone2', 'Play at the same time (v2)', true);
 
 insert or ignore into Variants (name, description) values
   ('Absorption', 'Absorb powers'),
@@ -88,6 +89,7 @@ insert or ignore into Variants (name, description) values
   ('Omega', 'A wizard in the corner'),
   ('Orda', 'Mongolian Horde (v1)'),
   ('Ordamirror', 'Mongolian Horde (v2)'),
+  ('Otage', 'Capture and release hostages'),
   ('Pacifist1', 'Convert & support (v1)'),
   ('Pacifist2', 'Convert & support (v2)'),
   ('Pacosako', 'Dance with the King'),