Convert all remaining tabs by 2spaces
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 4 Feb 2020 22:38:53 +0000 (23:38 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 4 Feb 2020 22:38:53 +0000 (23:38 +0100)
52 files changed:
TODO
client/public/index.html
client/src/components/ChallengeList.vue
client/src/components/ContactForm.vue
client/src/components/GameList.vue
client/src/components/Language.vue
client/src/components/MoveList.vue
client/src/components/Settings.vue
client/src/components/UpsertUser.vue
client/src/data/challengeCheck.js
client/src/data/userCheck.js
client/src/parameters.js.dist
client/src/router.js
client/src/utils/ajax.js
client/src/utils/datetime.js
client/src/utils/printDiagram.js
client/src/utils/squareId.js
client/src/variants/Alice.js
client/src/variants/Antiking.js
client/src/variants/Atomic.js
client/src/variants/Baroque.js
client/src/variants/Berolina.js
client/src/variants/Checkered.js
client/src/variants/Chess960.js
client/src/variants/Crazyhouse.js
client/src/variants/Dark.js
client/src/variants/Extinction.js
client/src/variants/Grand.js
client/src/variants/Losers.js
client/src/variants/Magnetic.js
client/src/variants/Marseille.js
client/src/variants/Upsidedown.js
client/src/variants/Wildebeest.js
client/src/variants/Zen.js
client/src/views/Hall.vue
client/src/views/MyGames.vue
server/app.js
server/bin/www
server/config/parameters.js.dist
server/db/populate.sql
server/gulpfile.js
server/models/Game.js
server/models/User.js
server/models/Variant.js
server/routes/games.js
server/routes/messages.js
server/routes/users.js
server/routes/variants.js
server/utils/access.js
server/utils/database.js
server/utils/mailer.js
server/utils/tokenGenerator.js

diff --git a/TODO b/TODO
index 53eab35..d8f28b4 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,2 +1 @@
-MarseilleChess: revise bot and test
 Translations (including About page), refresh rules
index 0ab70d5..12eb803 100644 (file)
@@ -2,14 +2,14 @@
 <html>
   <head>
     <meta charset="utf-8">
-               <title>vchess - club</title>
-               <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>vchess - club</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-               <link rel="stylesheet"
+    <link rel="stylesheet"
       href="//cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
-               <link rel="stylesheet"
-                       href="//fonts.googleapis.com/css?family=Open+Sans:400,700">
+    <link rel="stylesheet"
+      href="//fonts.googleapis.com/css?family=Open+Sans:400,700">
     <style>
       body {
         --fore-color: #2c3e50;
index 890ec1d..c57073c 100644 (file)
@@ -20,7 +20,7 @@ import { store } from "@/store";
 
 export default {
   name: "my-challenge-list",
-       props: ["challenges"],
+  props: ["challenges"],
   data: function() {
     return {
       st: store.state,
index 734d7f1..45b7c01 100644 (file)
@@ -34,7 +34,7 @@ export default {
     };
   },
   methods: {
-               // Note: not using Vue here, but would be possible
+    // Note: not using Vue here, but would be possible
     trySendMessage: function() {
       let email = document.getElementById("userEmail");
       let subject = document.getElementById("mailSubject");
@@ -65,7 +65,7 @@ export default {
         }
       );
     },
-       },
+  },
 };
 </script>
 
index 5ab61cc..0d2fbf4 100644 (file)
@@ -23,14 +23,14 @@ import { store } from "@/store";
 
 export default {
   name: "my-game-list",
-       props: ["games"],
+  props: ["games"],
   data: function() {
     return {
       st: store.state,
       showResult: false,
     };
   },
-       computed: {
+  computed: {
     sortedGames: function() {
       // Show in order: games where it's my turn, my running games, my games, other games
       this.showResult = this.games.some(g => g.score != "*");
index c701e59..d35d8e2 100644 (file)
@@ -40,6 +40,6 @@ export default {
       localStorage["lang"] = e.target.value;
       store.setLanguage(e.target.value);
     },
-       },
+  },
 };
 </script>
index e50047d..90b76b4 100644 (file)
@@ -2,7 +2,7 @@
 // Component for moves list on the right
 export default {
   name: 'my-move-list',
-       props: ["moves","cursor","score","message","firstNum"],
+  props: ["moves","cursor","score","message","firstNum"],
   watch: {
     cursor: function(newCursor) {
       if (window.innerWidth <= 767)
@@ -32,75 +32,75 @@ export default {
       });
     },
   },
-       render(h) {
-               if (this.moves.length == 0)
-                       return;
-               let tableContent = [];
-               let moveCounter = 0;
-               let tableRow = undefined;
-               let moveCells = undefined;
-               let curCellContent = "";
-               let firstIndex = 0;
+  render(h) {
+    if (this.moves.length == 0)
+      return;
+    let tableContent = [];
+    let moveCounter = 0;
+    let tableRow = undefined;
+    let moveCells = undefined;
+    let curCellContent = "";
+    let firstIndex = 0;
     for (let i=0; i<this.moves.length; i++)
-               {
-                       if (this.moves[i].color == "w")
-                       {
-                               if (i == 0 || i>0 && this.moves[i-1].color=="b")
-                               {
-                                       if (!!tableRow)
-                                       {
-                                               tableRow.children = moveCells;
-                                               tableContent.push(tableRow);
-                                       }
-                                       moveCells = [
-                                               h(
-                                                       "td",
-                                                       { domProps: { innerHTML: (++moveCounter) + "." } }
-                                               )
-                                       ];
-                                       tableRow = h(
-                                               "tr",
-                                               { }
-                                       );
-                                       curCellContent = "";
+    {
+      if (this.moves[i].color == "w")
+      {
+        if (i == 0 || i>0 && this.moves[i-1].color=="b")
+        {
+          if (!!tableRow)
+          {
+            tableRow.children = moveCells;
+            tableContent.push(tableRow);
+          }
+          moveCells = [
+            h(
+              "td",
+              { domProps: { innerHTML: (++moveCounter) + "." } }
+            )
+          ];
+          tableRow = h(
+            "tr",
+            { }
+          );
+          curCellContent = "";
           firstIndex = i;
-                               }
-                       }
+        }
+      }
       // Next condition is fine because even if the first move is black,
       // there will be the "..." which count as white move.
       else if (this.moves[i].color == "b" && this.moves[i-1].color == "w")
         firstIndex = i;
-                       curCellContent += this.moves[i].notation;
-                       if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
-                               curCellContent += ",";
-                       else //color change
-                       {
-                               moveCells.push(
-                                       h(
-                                               "td",
-                                               {
-                                                       domProps: { innerHTML: curCellContent },
-                                                       on: { click: () => this.gotoMove(i) },
+      curCellContent += this.moves[i].notation;
+      if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
+        curCellContent += ",";
+      else //color change
+      {
+        moveCells.push(
+          h(
+            "td",
+            {
+              domProps: { innerHTML: curCellContent },
+              on: { click: () => this.gotoMove(i) },
               "class": { "highlight-lm":
                 this.cursor >= firstIndex && this.cursor <= i },
-                                               }
-                                       )
-                               );
-                               curCellContent = "";
-                       }
-               }
-               // Complete last row, which might not be full:
-               if (moveCells.length-1 == 1)
-               {
+            }
+          )
+        );
+        curCellContent = "";
+      }
+    }
+    // Complete last row, which might not be full:
+    if (moveCells.length-1 == 1)
+    {
       moveCells.push(
         h(
           "td",
           { domProps: { innerHTML: "" } }
         )
       );
-               }
-               tableRow.children = moveCells;
-               tableContent.push(tableRow);
+    }
+    tableRow.children = moveCells;
+    tableContent.push(tableRow);
     let rootElements = [];
     if (this.score != "*")
     {
@@ -127,18 +127,18 @@ export default {
           },
         },
         tableContent
-                 )
+      )
     );
-               return h(
+    return h(
       "div",
       { },
       rootElements);
-       },
+  },
   methods: {
-               gotoMove: function(index) {
-                       this.$emit("goto-move", index);
-               },
-       },
+    gotoMove: function(index) {
+      this.$emit("goto-move", index);
+    },
+  },
 };
 </script>
 
index 92b463e..1cfbdc8 100644 (file)
@@ -67,7 +67,7 @@ export default {
       }
     });
   },
-       methods: {
+  methods: {
     updateSettings: function(event) {
       const propName =
         event.target.id.substr(3).replace(/^\w/, c => c.toLowerCase())
@@ -90,6 +90,6 @@ export default {
       document.getElementById("gameContainer").style.width =
         (boardSize + movesWidth) + "px";
     },
-       },
+  },
 };
 </script>
index 51edba3..ecd51fa 100644 (file)
@@ -46,20 +46,20 @@ export default {
       enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy
     };
   },
-       watch: {
-               nameOrEmail: function(newValue) {
-                       if (newValue.indexOf('@') >= 0)
-                       {
-                               this.user.email = newValue;
-                               this.user.name = "";
-                       }
-                       else
-                       {
-                               this.user.name = newValue;
-                               this.user.email = "";
-                       }
-               },
-       },
+  watch: {
+    nameOrEmail: function(newValue) {
+      if (newValue.indexOf('@') >= 0)
+      {
+        this.user.email = newValue;
+        this.user.name = "";
+      }
+      else
+      {
+        this.user.name = newValue;
+        this.user.email = "";
+      }
+    },
+  },
   computed: {
     submitMessage: function() {
       switch (this.stage)
index c270000..1a8b9fe 100644 (file)
@@ -2,21 +2,21 @@ import { extractTime } from "@/utils/timeControl";
 
 export function checkChallenge(c)
 {
-       const vid = parseInt(c.vid);
-       if (isNaN(vid) || vid <= 0)
-               return "Please select a variant";
+  const vid = parseInt(c.vid);
+  if (isNaN(vid) || vid <= 0)
+    return "Please select a variant";
 
   const tc = extractTime(c.timeControl);
   if (!tc)
     return "Wrong time control";
 
-       // Basic alphanumeric check for opponent name
-       if (!!c.to)
-       {
+  // Basic alphanumeric check for opponent name
+  if (!!c.to)
+  {
      // TODO: slightly redundant (see data/userCheck.js)
-               if (!c.to.match(/^[\w]+$/))
-                       return "Wrong characters in opponent name";
-       }
+    if (!c.to.match(/^[\w]+$/))
+      return "Wrong characters in opponent name";
+  }
 
   // Allow custom FEN (and check it) only for individual challenges
   if (c.fen.length > 0 && !!c.to)
index 4c714a6..9eb8562 100644 (file)
@@ -1,17 +1,17 @@
 export function checkNameEmail(o)
 {
-       if (typeof o.name === "string")
-       {
-               if (o.name.length == 0)
-                       return "Empty name";
-               if (!o.name.match(/^[\w]+$/))
-                       return "Bad characters in name";
-       }
-       if (typeof o.email === "string")
-       {
-               if (o.email.length == 0)
-                       return "Empty email";
-               if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
-                       return "Bad characters in email";
-       }
+  if (typeof o.name === "string")
+  {
+    if (o.name.length == 0)
+      return "Empty name";
+    if (!o.name.match(/^[\w]+$/))
+      return "Bad characters in name";
+  }
+  if (typeof o.email === "string")
+  {
+    if (o.email.length == 0)
+      return "Empty email";
+    if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+      return "Bad characters in email";
+  }
 }
index 5710bac..edad3bb 100644 (file)
@@ -1,10 +1,10 @@
 const Parameters =
 {
-       // URL of your socket server
-       socketUrl: "ws://localhost:3000",
+  // URL of your socket server
+  socketUrl: "ws://localhost:3000",
 
-       // URL of the server (leave blank for 1-server case)
-       serverUrl: "http://localhost:3000",
+  // URL of the server (leave blank for 1-server case)
+  serverUrl: "http://localhost:3000",
 
   // true if the server is at a different address
   cors: true,
index 1eb88f9..599cfcb 100644 (file)
@@ -5,7 +5,7 @@ import Hall from "./views/Hall.vue";
 Vue.use(Router);
 
 function loadView(view) {
-       return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
+  return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
 }
 
 import { ajax } from "@/utils/ajax";
@@ -46,8 +46,8 @@ const router = new Router({
               localStorage["myname"] = res.name;
               localStorage["myid"] = res.id;
             }
-                                               // TODO: I don't like these 2 lines, "next('/')" should be enough
-                                               window.location = "/";
+            // TODO: I don't like these 2 lines, "next('/')" should be enough
+            window.location = "/";
             next();
           }
         );
index eb30330..adfba46 100644 (file)
@@ -7,64 +7,64 @@ import params from "../parameters"; //for server URL
 // From JSON (encoded string values!) to "arg1=...&arg2=..."
 function toQueryString(data)
 {
-       let data_str = "";
-       Object.keys(data).forEach(k => {
-               data_str += k + "=" + encodeURIComponent(data[k]) + "&";
-       });
-       return data_str.slice(0, -1); //remove last "&"
+  let data_str = "";
+  Object.keys(data).forEach(k => {
+    data_str += k + "=" + encodeURIComponent(data[k]) + "&";
+  });
+  return data_str.slice(0, -1); //remove last "&"
 }
 
 // data, error: optional
 export function ajax(url, method, data, success, error)
 {
-       let xhr = new XMLHttpRequest();
-       if (data === undefined || typeof(data) === "function") //no data
-       {
-               error = success;
-               success = data;
-               data = {};
-       }
+  let xhr = new XMLHttpRequest();
+  if (data === undefined || typeof(data) === "function") //no data
+  {
+    error = success;
+    success = data;
+    data = {};
+  }
   if (!success)
     success = () => {}; //by default, do nothing
-       if (!error)
-               error = errmsg => { alert(errmsg); };
-       xhr.onreadystatechange = function() {
-               if (this.readyState == 4 && this.status == 200)
-               {
+  if (!error)
+    error = errmsg => { alert(errmsg); };
+  xhr.onreadystatechange = function() {
+    if (this.readyState == 4 && this.status == 200)
+    {
       let res_json = "";
-                       try {
-                               res_json = JSON.parse(xhr.responseText);
+      try {
+        res_json = JSON.parse(xhr.responseText);
       } catch (e) {
-                               // Plain text (e.g. for rules retrieval)
-                               return success(xhr.responseText);
+        // Plain text (e.g. for rules retrieval)
+        return success(xhr.responseText);
       }
       if (!res_json.errmsg && !res_json.errno)
         success(res_json);
-                       else
+      else
       {
         if (!!res_json.errmsg)
-                                 error(res_json.errmsg);
+          error(res_json.errmsg);
         else
           error(res_json.code + ". errno = " + res_json.errno);
       }
-               }
-       };
+    }
+  };
 
-       if (["GET","DELETE"].includes(method) && !!data)
-       {
-               // Append query params to URL
-               url += "/?" + toQueryString(data);
-       }
-       xhr.open(method, params.serverUrl + url, true);
-       xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
-       // Next line to allow cross-domain cookies in dev mode (TODO: if...)
+  if (["GET","DELETE"].includes(method) && !!data)
+  {
+    // Append query params to URL
+    url += "/?" + toQueryString(data);
+  }
+  xhr.open(method, params.serverUrl + url, true);
+  xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
+  // Next line to allow cross-domain cookies in dev mode (TODO: if...)
   if (params.cors)
     xhr.withCredentials = true;
   if (["POST","PUT"].includes(method))
-       {
-               xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
-               xhr.send(JSON.stringify(data));
-       }
-       else
-               xhr.send();
+  {
+    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
+    xhr.send(JSON.stringify(data));
+  }
+  else
+    xhr.send();
 }
index 49b70ca..5addd25 100644 (file)
@@ -1,17 +1,17 @@
 function zeroPad(x)
 {
-       return (x<10 ? "0" : "") + x;
+  return (x<10 ? "0" : "") + x;
 }
 
 export function getDate(d)
 {
-       return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate());
+  return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate());
 }
 
 export function getTime(d)
 {
-       return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" +
-               zeroPad(d.getSeconds());
+  return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" +
+    zeroPad(d.getSeconds());
 }
 
 function padDigits(x)
index 0d8d9ae..bf078ce 100644 (file)
@@ -3,109 +3,109 @@ import { ArrayFun } from "@/utils/array";
 // Turn (human) marks into coordinates
 function getMarkArray(marks)
 {
-       if (!marks || marks == "-")
-               return [];
-       let markArray = ArrayFun.init(V.size.x, V.size.y, false);
-       const squares = marks.split(",");
-       for (let i=0; i<squares.length; i++)
-       {
-               const coords = V.SquareToCoords(squares[i]);
-               markArray[coords.x][coords.y] = true;
-       }
-       return markArray;
+  if (!marks || marks == "-")
+    return [];
+  let markArray = ArrayFun.init(V.size.x, V.size.y, false);
+  const squares = marks.split(",");
+  for (let i=0; i<squares.length; i++)
+  {
+    const coords = V.SquareToCoords(squares[i]);
+    markArray[coords.x][coords.y] = true;
+  }
+  return markArray;
 }
 
 // Turn (human) shadow indications into coordinates
 function getShadowArray(shadow)
 {
-       if (!shadow || shadow == "-")
-               return [];
-       let shadowArray = ArrayFun.init(V.size.x, V.size.y, false);
-       const squares = shadow.split(",");
-       for (let i=0; i<squares.length; i++)
-       {
-               const rownum = V.size.x - parseInt(squares[i]);
-               if (!isNaN(rownum))
-               {
-                       // Shadow a full row
-                       for (let i=0; i<V.size.y; i++)
-                               shadowArray[rownum][i] = true;
-                       continue;
-               }
-               if (squares[i].length == 1)
-               {
-                       // Shadow a full column
-                       const colnum = V.ColumnToCoord(squares[i]);
-                       for (let i=0; i<V.size.x; i++)
-                               shadowArray[i][colnum] = true;
-                       continue;
-               }
-               if (squares[i].indexOf("-") >= 0)
-               {
-                       // Shadow a range of squares, horizontally or vertically
-                       const firstLastSq = squares[i].split("-");
-                       const range =
-                       [
-                               V.SquareToCoords(firstLastSq[0]),
-                               V.SquareToCoords(firstLastSq[1])
-                       ];
-                       const step =
-                       [
-                               range[1].x == range[0].x
-                                       ? 0
-                                       : (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x),
-                               range[1].y == range[0].y
-                                       ? 0
-                                       : (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y)
-                       ];
-                       // Convention: range always from smaller to larger number
-                       for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y;
-                               x += step[0], y += step[1])
-                       {
-                               shadowArray[x][y] = true;
-                       }
-                       continue;
-               }
-               // Shadow just one square:
-               const coords = V.SquareToCoords(squares[i]);
-               shadowArray[coords.x][coords.y] = true;
-       }
-       return shadowArray;
+  if (!shadow || shadow == "-")
+    return [];
+  let shadowArray = ArrayFun.init(V.size.x, V.size.y, false);
+  const squares = shadow.split(",");
+  for (let i=0; i<squares.length; i++)
+  {
+    const rownum = V.size.x - parseInt(squares[i]);
+    if (!isNaN(rownum))
+    {
+      // Shadow a full row
+      for (let i=0; i<V.size.y; i++)
+        shadowArray[rownum][i] = true;
+      continue;
+    }
+    if (squares[i].length == 1)
+    {
+      // Shadow a full column
+      const colnum = V.ColumnToCoord(squares[i]);
+      for (let i=0; i<V.size.x; i++)
+        shadowArray[i][colnum] = true;
+      continue;
+    }
+    if (squares[i].indexOf("-") >= 0)
+    {
+      // Shadow a range of squares, horizontally or vertically
+      const firstLastSq = squares[i].split("-");
+      const range =
+      [
+        V.SquareToCoords(firstLastSq[0]),
+        V.SquareToCoords(firstLastSq[1])
+      ];
+      const step =
+      [
+        range[1].x == range[0].x
+          ? 0
+          : (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x),
+        range[1].y == range[0].y
+          ? 0
+          : (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y)
+      ];
+      // Convention: range always from smaller to larger number
+      for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y;
+        x += step[0], y += step[1])
+      {
+        shadowArray[x][y] = true;
+      }
+      continue;
+    }
+    // Shadow just one square:
+    const coords = V.SquareToCoords(squares[i]);
+    shadowArray[coords.x][coords.y] = true;
+  }
+  return shadowArray;
 }
 
 // args: object with position (mandatory), and
 // orientation, marks, shadow (optional)
 export function getDiagram(args)
 {
-       // Obtain the array of pieces images names:
-       const board = V.GetBoard(args.position);
-       const orientation = args.orientation || "w";
-       const markArray = getMarkArray(args.marks);
-       const shadowArray = getShadowArray(args.shadow);
-       let boardDiv = "";
-       const [startX,startY,inc] = orientation == 'w'
-               ? [0, 0, 1]
-               : [V.size.x-1, V.size.y-1, -1];
-       for (let i=startX; i>=0 && i<V.size.x; i+=inc)
-       {
-               boardDiv += "<div class='row'>";
-               for (let j=startY; j>=0 && j<V.size.y; j+=inc)
-               {
-                       boardDiv += "<div class='board board" + V.size.y + " " +
-                               ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") +
-                               (shadowArray.length > 0 && shadowArray[i][j] ? " in-shadow" : "") +
-                               "'>";
-                       if (board[i][j] != V.EMPTY)
-                       {
-                               boardDiv += "<img " +
+  // Obtain the array of pieces images names:
+  const board = V.GetBoard(args.position);
+  const orientation = args.orientation || "w";
+  const markArray = getMarkArray(args.marks);
+  const shadowArray = getShadowArray(args.shadow);
+  let boardDiv = "";
+  const [startX,startY,inc] = orientation == 'w'
+    ? [0, 0, 1]
+    : [V.size.x-1, V.size.y-1, -1];
+  for (let i=startX; i>=0 && i<V.size.x; i+=inc)
+  {
+    boardDiv += "<div class='row'>";
+    for (let j=startY; j>=0 && j<V.size.y; j+=inc)
+    {
+      boardDiv += "<div class='board board" + V.size.y + " " +
+        ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") +
+        (shadowArray.length > 0 && shadowArray[i][j] ? " in-shadow" : "") +
+        "'>";
+      if (board[i][j] != V.EMPTY)
+      {
+        boardDiv += "<img " +
           "src='/images/pieces/" + V.getPpath(board[i][j]) + ".svg' " +
           "class='piece'/>";
-                       }
-                       if (markArray.length > 0 && markArray[i][j])
-                               boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
-                       boardDiv += "</div>";
-               }
-               boardDiv += "</div>";
-       }
-       return boardDiv;
+      }
+      if (markArray.length > 0 && markArray[i][j])
+        boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
+      boardDiv += "</div>";
+    }
+    boardDiv += "</div>";
+  }
+  return boardDiv;
 }
index a68b51f..6469bc3 100644 (file)
@@ -1,13 +1,13 @@
 // Get the identifier of a HTML square from its numeric coordinates o.x,o.y.
 export function getSquareId(o)
 {
-       // NOTE: a separator is required to allow any size of board
-       return  "sq-" + o.x + "-" + o.y;
+  // NOTE: a separator is required to allow any size of board
+  return  "sq-" + o.x + "-" + o.y;
 }
 
 // Inverse function
 export function getSquareFromId(id)
 {
-       const idParts = id.split('-');
-       return [parseInt(idParts[1]), parseInt(idParts[2])];
+  const idParts = id.split('-');
+  return [parseInt(idParts[1]), parseInt(idParts[2])];
 }
index e81d3fc..d7e775c 100644 (file)
@@ -5,355 +5,355 @@ import { ArrayFun} from "@/utils/array";
 // TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations)
 export const VariantRules = class AliceRules extends ChessRules
 {
-       static get ALICE_PIECES()
-       {
-               return {
-                       's': 'p',
-                       't': 'q',
-                       'u': 'r',
-                       'c': 'b',
-                       'o': 'n',
-                       'l': 'k',
-               };
-       }
-       static get ALICE_CODES()
-       {
-               return {
-                       'p': 's',
-                       'q': 't',
-                       'r': 'u',
-                       'b': 'c',
-                       'n': 'o',
-                       'k': 'l',
-               };
-       }
+  static get ALICE_PIECES()
+  {
+    return {
+      's': 'p',
+      't': 'q',
+      'u': 'r',
+      'c': 'b',
+      'o': 'n',
+      'l': 'k',
+    };
+  }
+  static get ALICE_CODES()
+  {
+    return {
+      'p': 's',
+      'q': 't',
+      'r': 'u',
+      'b': 'c',
+      'n': 'o',
+      'k': 'l',
+    };
+  }
 
-       static getPpath(b)
-       {
-               return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
-       }
+  static getPpath(b)
+  {
+    return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
+  }
 
-       static get PIECES()
-       {
-               return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
-       }
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
+  }
 
-       setOtherVariables(fen)
-       {
-               super.setOtherVariables(fen);
-               const rows = V.ParseFen(fen).position.split("/");
-               if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
-               {
-                       // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
-                       for (let i=0; i<rows.length; i++)
-                       {
-                               let k = 0; //column index on board
-                               for (let j=0; j<rows[i].length; j++)
-                               {
-                                       switch (rows[i].charAt(j))
-                                       {
-                                               case 'l':
-                                                       this.kingPos['b'] = [i,k];
-                                                       break;
-                                               case 'L':
-                                                       this.kingPos['w'] = [i,k];
-                                                       break;
-                                               default:
-                                                       const num = parseInt(rows[i].charAt(j));
-                                                       if (!isNaN(num))
-                                                               k += (num-1);
-                                       }
-                                       k++;
-                               }
-                       }
-               }
-       }
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const rows = V.ParseFen(fen).position.split("/");
+    if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
+    {
+      // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
+      for (let i=0; i<rows.length; i++)
+      {
+        let k = 0; //column index on board
+        for (let j=0; j<rows[i].length; j++)
+        {
+          switch (rows[i].charAt(j))
+          {
+            case 'l':
+              this.kingPos['b'] = [i,k];
+              break;
+            case 'L':
+              this.kingPos['w'] = [i,k];
+              break;
+            default:
+              const num = parseInt(rows[i].charAt(j));
+              if (!isNaN(num))
+                k += (num-1);
+          }
+          k++;
+        }
+      }
+    }
+  }
 
-       // Return the (standard) color+piece notation at a square for a board
-       getSquareOccupation(i, j, mirrorSide)
-       {
-               const piece = this.getPiece(i,j);
-               if (mirrorSide==1 && Object.keys(V.ALICE_CODES).includes(piece))
-                       return this.board[i][j];
-               else if (mirrorSide==2 && Object.keys(V.ALICE_PIECES).includes(piece))
-                       return this.getColor(i,j) + V.ALICE_PIECES[piece];
-               return "";
-       }
+  // Return the (standard) color+piece notation at a square for a board
+  getSquareOccupation(i, j, mirrorSide)
+  {
+    const piece = this.getPiece(i,j);
+    if (mirrorSide==1 && Object.keys(V.ALICE_CODES).includes(piece))
+      return this.board[i][j];
+    else if (mirrorSide==2 && Object.keys(V.ALICE_PIECES).includes(piece))
+      return this.getColor(i,j) + V.ALICE_PIECES[piece];
+    return "";
+  }
 
-       // Build board of the given (mirror)side
-       getSideBoard(mirrorSide)
-       {
-               // Build corresponding board from complete board
-               let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
-               for (let i=0; i<V.size.x; i++)
-               {
-                       for (let j=0; j<V.size.y; j++)
-                               sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
-               }
-               return sideBoard;
-       }
+  // Build board of the given (mirror)side
+  getSideBoard(mirrorSide)
+  {
+    // Build corresponding board from complete board
+    let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+        sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
+    }
+    return sideBoard;
+  }
 
-       // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
-       getPotentialMovesFrom([x,y], sideBoard)
-       {
-               const pieces = Object.keys(V.ALICE_CODES);
-               const codes = Object.keys(V.ALICE_PIECES);
-               const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
+  // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
+  getPotentialMovesFrom([x,y], sideBoard)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    const codes = Object.keys(V.ALICE_PIECES);
+    const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
     if (!sideBoard)
-           sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
-               const color = this.getColor(x,y);
+      sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
+    const color = this.getColor(x,y);
 
-               // Search valid moves on sideBoard
-               const saveBoard = this.board;
-               this.board = sideBoard[mirrorSide-1];
-               const moves = super.getPotentialMovesFrom([x,y])
-                       .filter(m => {
-                               // Filter out king moves which result in under-check position on
-                               // current board (before mirror traversing)
-                               let aprioriValid = true;
-                               if (m.appear[0].p == V.KING)
-                               {
-                                       this.play(m);
-                                       if (this.underCheck(color, sideBoard))
-                                               aprioriValid = false;
-                                       this.undo(m);
-                               }
-                               return aprioriValid;
-                       });
-               this.board = saveBoard;
+    // Search valid moves on sideBoard
+    const saveBoard = this.board;
+    this.board = sideBoard[mirrorSide-1];
+    const moves = super.getPotentialMovesFrom([x,y])
+      .filter(m => {
+        // Filter out king moves which result in under-check position on
+        // current board (before mirror traversing)
+        let aprioriValid = true;
+        if (m.appear[0].p == V.KING)
+        {
+          this.play(m);
+          if (this.underCheck(color, sideBoard))
+            aprioriValid = false;
+          this.undo(m);
+        }
+        return aprioriValid;
+      });
+    this.board = saveBoard;
 
-               // Finally filter impossible moves
-               const res = moves.filter(m => {
-                       if (m.appear.length == 2) //castle
-                       {
-                               // appear[i] must be an empty square on the other board
-                               for (let psq of m.appear)
-                               {
-                                       if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY)
-                                               return false;
-                               }
-                       }
-                       else if (this.board[m.end.x][m.end.y] != V.EMPTY)
-                       {
-                               // Attempt to capture
-                               const piece = this.getPiece(m.end.x,m.end.y);
-                               if ((mirrorSide==1 && codes.includes(piece))
-                                       || (mirrorSide==2 && pieces.includes(piece)))
-                               {
-                                       return false;
-                               }
-                       }
-                       // If the move is computed on board1, m.appear change for Alice pieces.
-                       if (mirrorSide==1)
-                       {
-                               m.appear.forEach(psq => { //forEach: castling taken into account
-                                       psq.p = V.ALICE_CODES[psq.p]; //goto board2
-                               });
-                       }
-                       else //move on board2: mark vanishing pieces as Alice
-                       {
-                               m.vanish.forEach(psq => {
-                                       psq.p = V.ALICE_CODES[psq.p];
-                               });
-                       }
-                       // Fix en-passant captures
-                       if (m.vanish[0].p == V.PAWN && m.vanish.length == 2
-                               && this.board[m.end.x][m.end.y] == V.EMPTY)
-                       {
-                               m.vanish[1].c = V.GetOppCol(this.getColor(x,y));
-                               // In the special case of en-passant, if
-                               //  - board1 takes board2 : vanish[1] --> Alice
-                               //  - board2 takes board1 : vanish[1] --> normal
-                               let van = m.vanish[1];
-                               if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
-                                       van.p = V.ALICE_CODES[van.p];
-                               else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
-                                       van.p = V.ALICE_PIECES[van.p];
-                       }
-                       return true;
-               });
-               return res;
-       }
+    // Finally filter impossible moves
+    const res = moves.filter(m => {
+      if (m.appear.length == 2) //castle
+      {
+        // appear[i] must be an empty square on the other board
+        for (let psq of m.appear)
+        {
+          if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY)
+            return false;
+        }
+      }
+      else if (this.board[m.end.x][m.end.y] != V.EMPTY)
+      {
+        // Attempt to capture
+        const piece = this.getPiece(m.end.x,m.end.y);
+        if ((mirrorSide==1 && codes.includes(piece))
+          || (mirrorSide==2 && pieces.includes(piece)))
+        {
+          return false;
+        }
+      }
+      // If the move is computed on board1, m.appear change for Alice pieces.
+      if (mirrorSide==1)
+      {
+        m.appear.forEach(psq => { //forEach: castling taken into account
+          psq.p = V.ALICE_CODES[psq.p]; //goto board2
+        });
+      }
+      else //move on board2: mark vanishing pieces as Alice
+      {
+        m.vanish.forEach(psq => {
+          psq.p = V.ALICE_CODES[psq.p];
+        });
+      }
+      // Fix en-passant captures
+      if (m.vanish[0].p == V.PAWN && m.vanish.length == 2
+        && this.board[m.end.x][m.end.y] == V.EMPTY)
+      {
+        m.vanish[1].c = V.GetOppCol(this.getColor(x,y));
+        // In the special case of en-passant, if
+        //  - board1 takes board2 : vanish[1] --> Alice
+        //  - board2 takes board1 : vanish[1] --> normal
+        let van = m.vanish[1];
+        if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
+          van.p = V.ALICE_CODES[van.p];
+        else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
+          van.p = V.ALICE_PIECES[van.p];
+      }
+      return true;
+    });
+    return res;
+  }
 
-       filterValid(moves, sideBoard)
-       {
-               if (moves.length == 0)
-                       return [];
-               if (!sideBoard)
+  filterValid(moves, sideBoard)
+  {
+    if (moves.length == 0)
+      return [];
+    if (!sideBoard)
       sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
-               const color = this.turn;
-               return moves.filter(m => {
-                       this.playSide(m, sideBoard); //no need to track flags
-                       const res = !this.underCheck(color, sideBoard);
-                       this.undoSide(m, sideBoard);
-                       return res;
-               });
-       }
+    const color = this.turn;
+    return moves.filter(m => {
+      this.playSide(m, sideBoard); //no need to track flags
+      const res = !this.underCheck(color, sideBoard);
+      this.undoSide(m, sideBoard);
+      return res;
+    });
+  }
 
-       getAllValidMoves()
-       {
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               let potentialMoves = [];
-               const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
-               for (var i=0; i<V.size.x; i++)
-               {
-                       for (var 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], sideBoard));
-                               }
-                       }
-               }
-               return this.filterValid(potentialMoves, sideBoard);
-       }
+  getAllValidMoves()
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let potentialMoves = [];
+    const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
+    for (var i=0; i<V.size.x; i++)
+    {
+      for (var 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], sideBoard));
+        }
+      }
+    }
+    return this.filterValid(potentialMoves, sideBoard);
+  }
 
-       // Play on sideboards [TODO: only one sideBoard required]
-       playSide(move, sideBoard)
-       {
-               const pieces = Object.keys(V.ALICE_CODES);
-               move.vanish.forEach(psq => {
-                       const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-                       sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
-               });
-               move.appear.forEach(psq => {
-                       const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-                       const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
-                       sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
-                       if (piece == V.KING)
-                               this.kingPos[psq.c] = [psq.x,psq.y];
-               });
-       }
+  // Play on sideboards [TODO: only one sideBoard required]
+  playSide(move, sideBoard)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    move.vanish.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
+    });
+    move.appear.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
+      if (piece == V.KING)
+        this.kingPos[psq.c] = [psq.x,psq.y];
+    });
+  }
 
-       // Undo on sideboards
-       undoSide(move, sideBoard)
-       {
-               const pieces = Object.keys(V.ALICE_CODES);
-               move.appear.forEach(psq => {
-                       const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-                       sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
-               });
-               move.vanish.forEach(psq => {
-                       const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-                       const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
-                       sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
-                       if (piece == V.KING)
-                               this.kingPos[psq.c] = [psq.x,psq.y];
-               });
-       }
+  // Undo on sideboards
+  undoSide(move, sideBoard)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    move.appear.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
+    });
+    move.vanish.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
+      if (piece == V.KING)
+        this.kingPos[psq.c] = [psq.x,psq.y];
+    });
+  }
 
   // sideBoard: arg containing both boards (see getAllValidMoves())
-       underCheck(color, sideBoard)
-       {
-               const kp = this.kingPos[color];
-               const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2);
-               let saveBoard = this.board;
-               this.board = sideBoard[mirrorSide-1];
-               let res = this.isAttacked(kp, [V.GetOppCol(color)]);
-               this.board = saveBoard;
-               return res;
-       }
+  underCheck(color, sideBoard)
+  {
+    const kp = this.kingPos[color];
+    const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2);
+    let saveBoard = this.board;
+    this.board = sideBoard[mirrorSide-1];
+    let res = this.isAttacked(kp, [V.GetOppCol(color)]);
+    this.board = saveBoard;
+    return res;
+  }
 
-       getCheckSquares(color)
-       {
-               const pieces = Object.keys(V.ALICE_CODES);
-               const kp = this.kingPos[color];
-               const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
-               let sideBoard = this.getSideBoard(mirrorSide);
-               let saveBoard = this.board;
-               this.board = sideBoard;
-               let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
-                       ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
-                       : [ ];
-               this.board = saveBoard;
-               return res;
-       }
+  getCheckSquares(color)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    const kp = this.kingPos[color];
+    const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
+    let sideBoard = this.getSideBoard(mirrorSide);
+    let saveBoard = this.board;
+    this.board = sideBoard;
+    let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
+      ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
+      : [ ];
+    this.board = saveBoard;
+    return res;
+  }
 
-       updateVariables(move)
-       {
-               super.updateVariables(move); //standard king
-               const piece = move.vanish[0].p;
-               const c = move.vanish[0].c;
-               // "l" = Alice king
-               if (piece == "l")
-               {
-                       this.kingPos[c][0] = move.appear[0].x;
-                       this.kingPos[c][1] = move.appear[0].y;
-                       this.castleFlags[c] = [false,false];
-               }
-       }
+  updateVariables(move)
+  {
+    super.updateVariables(move); //standard king
+    const piece = move.vanish[0].p;
+    const c = move.vanish[0].c;
+    // "l" = Alice king
+    if (piece == "l")
+    {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+      this.castleFlags[c] = [false,false];
+    }
+  }
 
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               const c = move.vanish[0].c;
-               if (move.vanish[0].p == "l")
-                       this.kingPos[c] = [move.start.x, move.start.y];
-       }
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    if (move.vanish[0].p == "l")
+      this.kingPos[c] = [move.start.x, move.start.y];
+  }
 
-       getCurrentScore()
-       {
+  getCurrentScore()
+  {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
     const pieces = Object.keys(V.ALICE_CODES);
-               const color = this.turn;
-               const kp = this.kingPos[color];
-               const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
-               let sideBoard = this.getSideBoard(mirrorSide);
-               let saveBoard = this.board;
-               this.board = sideBoard;
-               let res = "*";
-               if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
-                       res = "1/2";
-               else
-                       res = (color == "w" ? "0-1" : "1-0");
-               this.board = saveBoard;
-               return res;
-       }
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
+    let sideBoard = this.getSideBoard(mirrorSide);
+    let saveBoard = this.board;
+    this.board = sideBoard;
+    let res = "*";
+    if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
+      res = "1/2";
+    else
+      res = (color == "w" ? "0-1" : "1-0");
+    this.board = saveBoard;
+    return res;
+  }
 
-       static get VALUES()
-       {
-               return Object.assign(
-                       ChessRules.VALUES,
-                       {
-                               's': 1,
-                               'u': 5,
-                               'o': 3,
-                               'c': 3,
-                               't': 9,
-                               'l': 1000,
-                       }
-               );
-       }
+  static get VALUES()
+  {
+    return Object.assign(
+      ChessRules.VALUES,
+      {
+        's': 1,
+        'u': 5,
+        'o': 3,
+        'c': 3,
+        't': 9,
+        'l': 1000,
+      }
+    );
+  }
 
-       getNotation(move)
-       {
-               if (move.appear.length == 2 && move.appear[0].p == V.KING)
-               {
-                       if (move.end.y < move.start.y)
-                               return "0-0-0";
-                       else
-                               return "0-0";
-               }
+  getNotation(move)
+  {
+    if (move.appear.length == 2 && move.appear[0].p == V.KING)
+    {
+      if (move.end.y < move.start.y)
+        return "0-0-0";
+      else
+        return "0-0";
+    }
 
-               const finalSquare = V.CoordsToSquare(move.end);
-               const piece = this.getPiece(move.start.x, move.start.y);
+    const finalSquare = V.CoordsToSquare(move.end);
+    const piece = this.getPiece(move.start.x, move.start.y);
 
-               const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
-               let pawnMark = "";
-               if (["p","s"].includes(piece) && captureMark.length == 1)
-                       pawnMark = V.CoordToColumn(move.start.y); //start column
+    const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
+    let pawnMark = "";
+    if (["p","s"].includes(piece) && captureMark.length == 1)
+      pawnMark = V.CoordToColumn(move.start.y); //start column
 
-               // Piece or pawn movement
-               let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
-               if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
-               {
-                       // Promotion
-                       notation += "=" + move.appear[0].p.toUpperCase();
-               }
-               return notation;
-       }
+    // Piece or pawn movement
+    let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
+    if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
+    {
+      // Promotion
+      notation += "=" + move.appear[0].p.toUpperCase();
+    }
+    return notation;
+  }
 }
index 7b57e74..610dd25 100644 (file)
@@ -4,204 +4,204 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class AntikingRules extends ChessRules
 {
-       static getPpath(b)
-       {
-               return b[1]=='a' ? "Antiking/"+b : b;
-       }
-
-       static get ANTIKING() { return 'a'; }
-
-       static get PIECES()
-       {
-               return ChessRules.PIECES.concat([V.ANTIKING]);
-       }
-
-       setOtherVariables(fen)
-       {
-               super.setOtherVariables(fen);
-               this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
-               const rows = V.ParseFen(fen).position.split("/");
-               for (let i=0; i<rows.length; i++)
-               {
-                       let k = 0;
-                       for (let j=0; j<rows[i].length; j++)
-                       {
-                               switch (rows[i].charAt(j))
-                               {
-                                       case 'a':
-                                               this.antikingPos['b'] = [i,k];
-                                               break;
-                                       case 'A':
-                                               this.antikingPos['w'] = [i,k];
-                                               break;
-                                       default:
-                                               const num = parseInt(rows[i].charAt(j));
-                                               if (!isNaN(num))
-                                                       k += (num-1);
-                               }
-                               k++;
-                       }
-               }
-       }
-
-       canTake([x1,y1], [x2,y2])
-       {
-               const piece1 = this.getPiece(x1,y1);
-               const piece2 = this.getPiece(x2,y2);
-               const color1 = this.getColor(x1,y1);
-               const color2 = this.getColor(x2,y2);
-               return piece2 != "a" &&
-                       ((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
-       }
-
-       getPotentialMovesFrom([x,y])
-       {
-               switch (this.getPiece(x,y))
-               {
-                       case V.ANTIKING:
-                               return this.getPotentialAntikingMoves([x,y]);
-                       default:
-                               return super.getPotentialMovesFrom([x,y]);
-               }
-       }
-
-       getPotentialAntikingMoves(sq)
-       {
-               return this.getSlideNJumpMoves(sq,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-       }
-
-       isAttacked(sq, colors)
-       {
-               return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
-       }
-
-       isAttackedByKing([x,y], colors)
-       {
-               if (this.getPiece(x,y) == V.ANTIKING)
-                       return false; //antiking is not attacked by king
-               return this.isAttackedBySlideNJump([x,y], colors, V.KING,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-       }
-
-       isAttackedByAntiking([x,y], colors)
-       {
-               if ([V.KING,V.ANTIKING].includes(this.getPiece(x,y)))
-                       return false; //(anti)king is not attacked by antiking
-               return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-       }
-
-       underCheck(color)
-       {
-               const oppCol = V.GetOppCol(color);
-               let res = this.isAttacked(this.kingPos[color], [oppCol])
-                       || !this.isAttacked(this.antikingPos[color], [oppCol]);
-               return res;
-       }
-
-       getCheckSquares(color)
-       {
-               let res = super.getCheckSquares(color);
-               if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)]))
-                       res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
-               return res;
-       }
-
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               const piece = move.vanish[0].p;
-               const c = move.vanish[0].c;
-               // Update antiking position
-               if (piece == V.ANTIKING)
-               {
-                       this.antikingPos[c][0] = move.appear[0].x;
-                       this.antikingPos[c][1] = move.appear[0].y;
-               }
-       }
-
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               const c = move.vanish[0].c;
-               if (move.vanish[0].p == V.ANTIKING)
-                       this.antikingPos[c] = [move.start.x, move.start.y];
-       }
-
-       getCurrentScore()
-       {
+  static getPpath(b)
+  {
+    return b[1]=='a' ? "Antiking/"+b : b;
+  }
+
+  static get ANTIKING() { return 'a'; }
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.ANTIKING]);
+  }
+
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
+    const rows = V.ParseFen(fen).position.split("/");
+    for (let i=0; i<rows.length; i++)
+    {
+      let k = 0;
+      for (let j=0; j<rows[i].length; j++)
+      {
+        switch (rows[i].charAt(j))
+        {
+          case 'a':
+            this.antikingPos['b'] = [i,k];
+            break;
+          case 'A':
+            this.antikingPos['w'] = [i,k];
+            break;
+          default:
+            const num = parseInt(rows[i].charAt(j));
+            if (!isNaN(num))
+              k += (num-1);
+        }
+        k++;
+      }
+    }
+  }
+
+  canTake([x1,y1], [x2,y2])
+  {
+    const piece1 = this.getPiece(x1,y1);
+    const piece2 = this.getPiece(x2,y2);
+    const color1 = this.getColor(x1,y1);
+    const color2 = this.getColor(x2,y2);
+    return piece2 != "a" &&
+      ((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
+  }
+
+  getPotentialMovesFrom([x,y])
+  {
+    switch (this.getPiece(x,y))
+    {
+      case V.ANTIKING:
+        return this.getPotentialAntikingMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y]);
+    }
+  }
+
+  getPotentialAntikingMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  isAttacked(sq, colors)
+  {
+    return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
+  }
+
+  isAttackedByKing([x,y], colors)
+  {
+    if (this.getPiece(x,y) == V.ANTIKING)
+      return false; //antiking is not attacked by king
+    return this.isAttackedBySlideNJump([x,y], colors, V.KING,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  isAttackedByAntiking([x,y], colors)
+  {
+    if ([V.KING,V.ANTIKING].includes(this.getPiece(x,y)))
+      return false; //(anti)king is not attacked by antiking
+    return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  underCheck(color)
+  {
+    const oppCol = V.GetOppCol(color);
+    let res = this.isAttacked(this.kingPos[color], [oppCol])
+      || !this.isAttacked(this.antikingPos[color], [oppCol]);
+    return res;
+  }
+
+  getCheckSquares(color)
+  {
+    let res = super.getCheckSquares(color);
+    if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)]))
+      res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
+    return res;
+  }
+
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    const piece = move.vanish[0].p;
+    const c = move.vanish[0].c;
+    // Update antiking position
+    if (piece == V.ANTIKING)
+    {
+      this.antikingPos[c][0] = move.appear[0].x;
+      this.antikingPos[c][1] = move.appear[0].y;
+    }
+  }
+
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    if (move.vanish[0].p == V.ANTIKING)
+      this.antikingPos[c] = [move.start.x, move.start.y];
+  }
+
+  getCurrentScore()
+  {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
     const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               if (!this.isAttacked(this.kingPos[color], [oppCol])
-                       && this.isAttacked(this.antikingPos[color], [oppCol]))
-               {
-                       return "1/2";
-               }
-               return color == "w" ? "0-1" : "1-0";
-       }
-
-       static get VALUES() {
-               return Object.assign(
-                       ChessRules.VALUES,
-                       { 'a': 1000 }
-               );
-       }
-
-       static GenRandInitFen()
-       {
-               let pieces = { "w": new Array(8), "b": new Array(8) };
-               let antikingPos = { "w": -1, "b": -1 };
-               for (let c of ["w","b"])
-               {
-                       let positions = ArrayFun.range(8);
-
-                       // Get random squares for bishops, but avoid corners; because,
-                       // if an antiking blocks a cornered bishop, it can never be checkmated
-                       let randIndex = 2 * randInt(1,4);
-                       const bishop1Pos = positions[randIndex];
-                       let randIndex_tmp = 2 * randInt(3) + 1;
-                       const bishop2Pos = positions[randIndex_tmp];
-                       positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-                       positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
-                       randIndex = randInt(6);
-                       const knight1Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-                       randIndex = randInt(5);
-                       const knight2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(4);
-                       const queenPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       const rook1Pos = positions[0];
-                       const kingPos = positions[1];
-                       const rook2Pos = positions[2];
-
-                       // Random squares for antikings
-                       antikingPos[c] = randInt(8);
-
-                       pieces[c][rook1Pos] = 'r';
-                       pieces[c][knight1Pos] = 'n';
-                       pieces[c][bishop1Pos] = 'b';
-                       pieces[c][queenPos] = 'q';
-                       pieces[c][kingPos] = 'k';
-                       pieces[c][bishop2Pos] = 'b';
-                       pieces[c][knight2Pos] = 'n';
-                       pieces[c][rook2Pos] = 'r';
-               }
-               const ranks23_black = "pppppppp/" + (antikingPos["w"]>0?antikingPos["w"]:"")
-                       + "A" + (antikingPos["w"]<7?7-antikingPos["w"]:"");
-               const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a"
-                       + (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP";
-               return pieces["b"].join("") + "/" + ranks23_black +
-                       "/8/8/" +
-                       ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
-                       " w 0 1111 -";
-       }
+    const oppCol = V.GetOppCol(color);
+    if (!this.isAttacked(this.kingPos[color], [oppCol])
+      && this.isAttacked(this.antikingPos[color], [oppCol]))
+    {
+      return "1/2";
+    }
+    return color == "w" ? "0-1" : "1-0";
+  }
+
+  static get VALUES() {
+    return Object.assign(
+      ChessRules.VALUES,
+      { 'a': 1000 }
+    );
+  }
+
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    let antikingPos = { "w": -1, "b": -1 };
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
+
+      // Get random squares for bishops, but avoid corners; because,
+      // if an antiking blocks a cornered bishop, it can never be checkmated
+      let randIndex = 2 * randInt(1,4);
+      const bishop1Pos = positions[randIndex];
+      let randIndex_tmp = 2 * randInt(3) + 1;
+      const bishop2Pos = positions[randIndex_tmp];
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+      randIndex = randInt(6);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(4);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      const rook1Pos = positions[0];
+      const kingPos = positions[1];
+      const rook2Pos = positions[2];
+
+      // Random squares for antikings
+      antikingPos[c] = randInt(8);
+
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    const ranks23_black = "pppppppp/" + (antikingPos["w"]>0?antikingPos["w"]:"")
+      + "A" + (antikingPos["w"]<7?7-antikingPos["w"]:"");
+    const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a"
+      + (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP";
+    return pieces["b"].join("") + "/" + ranks23_black +
+      "/8/8/" +
+      ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
+      " w 0 1111 -";
+  }
 }
index e1b1c16..f42d593 100644 (file)
@@ -2,145 +2,145 @@ import { ChessRules, PiPo } from "@/base_rules";
 
 export const VariantRules = class AtomicRules extends ChessRules
 {
-       getPotentialMovesFrom([x,y])
-       {
-               let moves = super.getPotentialMovesFrom([x,y]);
+  getPotentialMovesFrom([x,y])
+  {
+    let moves = super.getPotentialMovesFrom([x,y]);
 
-               // Handle explosions
-               moves.forEach(m => {
-                       if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
-                       {
-                               // Explosion! TODO(?): drop moves which explode our king here
-                               let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ];
-                               for (let step of steps)
-                               {
-                                       let x = m.end.x + step[0];
-                                       let y = m.end.y + step[1];
-                                       if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY
-                                               && this.getPiece(x,y) != V.PAWN)
-                                       {
-                                               m.vanish.push(
-                                                       new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
-                                       }
-                               }
-                               m.end = {x:m.appear[0].x, y:m.appear[0].y};
-                               m.appear.pop(); //Nothin appears in this case
-                       }
-               });
+    // Handle explosions
+    moves.forEach(m => {
+      if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
+      {
+        // Explosion! TODO(?): drop moves which explode our king here
+        let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ];
+        for (let step of steps)
+        {
+          let x = m.end.x + step[0];
+          let y = m.end.y + step[1];
+          if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY
+            && this.getPiece(x,y) != V.PAWN)
+          {
+            m.vanish.push(
+              new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
+          }
+        }
+        m.end = {x:m.appear[0].x, y:m.appear[0].y};
+        m.appear.pop(); //Nothin appears in this case
+      }
+    });
 
-               return moves;
-       }
+    return moves;
+  }
 
-       getPotentialKingMoves([x,y])
-       {
-               // King cannot capture:
-               let moves = [];
-               const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               for (let step of steps)
-               {
-                       const i = x + step[0];
-                       const j = y + step[1];
-                       if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-                               moves.push(this.getBasicMove([x,y], [i,j]));
-               }
-               return moves.concat(this.getCastleMoves([x,y]));
-       }
+  getPotentialKingMoves([x,y])
+  {
+    // King cannot capture:
+    let moves = [];
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    for (let step of steps)
+    {
+      const i = x + step[0];
+      const j = y + step[1];
+      if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+        moves.push(this.getBasicMove([x,y], [i,j]));
+    }
+    return moves.concat(this.getCastleMoves([x,y]));
+  }
 
-       isAttacked(sq, colors)
-       {
-               if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors))
-                       return false; //king cannot take...
-               return (this.isAttackedByPawn(sq, colors)
-                       || this.isAttackedByRook(sq, colors)
-                       || this.isAttackedByKnight(sq, colors)
-                       || this.isAttackedByBishop(sq, colors)
-                       || this.isAttackedByQueen(sq, colors));
-       }
+  isAttacked(sq, colors)
+  {
+    if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors))
+      return false; //king cannot take...
+    return (this.isAttackedByPawn(sq, colors)
+      || this.isAttackedByRook(sq, colors)
+      || this.isAttackedByKnight(sq, colors)
+      || this.isAttackedByBishop(sq, colors)
+      || this.isAttackedByQueen(sq, colors));
+  }
 
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               const color = move.vanish[0].c;
-               if (move.appear.length == 0) //capture
-               {
-                       const firstRank = {"w": 7, "b": 0};
-                       for (let c of ["w","b"])
-                       {
-                               // Did we explode king of color c ? (TODO: remove move earlier)
-                               if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1
-                                       && Math.abs(this.kingPos[c][1]-move.end.y) <= 1)
-                               {
-                                       this.kingPos[c] = [-1,-1];
-                                       this.castleFlags[c] = [false,false];
-                               }
-                               else
-                               {
-                                       // Now check if init rook(s) exploded
-                                       if (Math.abs(move.end.x-firstRank[c]) <= 1)
-                                       {
-                                               if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1)
-                                                       this.castleFlags[c][0] = false;
-                                               if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1)
-                                                       this.castleFlags[c][1] = false;
-                                       }
-                               }
-                       }
-               }
-       }
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    const color = move.vanish[0].c;
+    if (move.appear.length == 0) //capture
+    {
+      const firstRank = {"w": 7, "b": 0};
+      for (let c of ["w","b"])
+      {
+        // Did we explode king of color c ? (TODO: remove move earlier)
+        if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1
+          && Math.abs(this.kingPos[c][1]-move.end.y) <= 1)
+        {
+          this.kingPos[c] = [-1,-1];
+          this.castleFlags[c] = [false,false];
+        }
+        else
+        {
+          // Now check if init rook(s) exploded
+          if (Math.abs(move.end.x-firstRank[c]) <= 1)
+          {
+            if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1)
+              this.castleFlags[c][0] = false;
+            if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1)
+              this.castleFlags[c][1] = false;
+          }
+        }
+      }
+    }
+  }
 
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               const c = move.vanish[0].c;
-               const oppCol = V.GetOppCol(c);
-               if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; }))
-               {
-                       // There is a chance that last move blowed some king away..
-                       for (let psq of move.vanish)
-                       {
-                               if (psq.p == 'k')
-                                       this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y];
-                       }
-               }
-       }
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; }))
+    {
+      // There is a chance that last move blowed some king away..
+      for (let psq of move.vanish)
+      {
+        if (psq.p == 'k')
+          this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y];
+      }
+    }
+  }
 
-       underCheck(color)
-       {
-               const oppCol = V.GetOppCol(color);
-               let res = undefined;
-               // If our king disappeared, move is not valid
-               if (this.kingPos[color][0] < 0)
-                       res = true;
-               // If opponent king disappeared, move is valid
-               else if (this.kingPos[oppCol][0] < 0)
-                       res = false;
-               // Otherwise, if we remain under check, move is not valid
-               else
-                       res = this.isAttacked(this.kingPos[color], [oppCol]);
-               return res;
-       }
+  underCheck(color)
+  {
+    const oppCol = V.GetOppCol(color);
+    let res = undefined;
+    // If our king disappeared, move is not valid
+    if (this.kingPos[color][0] < 0)
+      res = true;
+    // If opponent king disappeared, move is valid
+    else if (this.kingPos[oppCol][0] < 0)
+      res = false;
+    // Otherwise, if we remain under check, move is not valid
+    else
+      res = this.isAttacked(this.kingPos[color], [oppCol]);
+    return res;
+  }
 
-       getCheckSquares(color)
-       {
-               let res = [ ];
-               if (this.kingPos[color][0] >= 0 //king might have exploded
-                       && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
-               {
-                       res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
-               }
-               return res;
-       }
+  getCheckSquares(color)
+  {
+    let res = [ ];
+    if (this.kingPos[color][0] >= 0 //king might have exploded
+      && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
+    {
+      res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
+    }
+    return res;
+  }
 
-       getCurrentScore()
-       {
-               const color = this.turn;
-               const kp = this.kingPos[color];
-               if (kp[0] < 0) //king disappeared
-                       return color == "w" ? "0-1" : "1-0";
+  getCurrentScore()
+  {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0) //king disappeared
+      return color == "w" ? "0-1" : "1-0";
     if (this.atLeastOneMove()) // game not over
       return "*";
-               if (!this.isAttacked(kp, [V.GetOppCol(color)]))
-                       return "1/2";
-               return color == "w" ? "0-1" : "1-0"; //checkmate
-       }
+    if (!this.isAttacked(kp, [V.GetOppCol(color)]))
+      return "1/2";
+    return color == "w" ? "0-1" : "1-0"; //checkmate
+  }
 }
index 9b5a3cd..88c993a 100644 (file)
@@ -4,617 +4,617 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class BaroqueRules extends ChessRules
 {
-       static get HasFlags() { return false; }
-
-       static get HasEnpassant() { return false; }
-
-       static getPpath(b)
-       {
-               if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
-                       return "Baroque/" + b;
-               return b; //usual piece
-       }
-
-       static get PIECES()
-       {
-               return ChessRules.PIECES.concat([V.IMMOBILIZER]);
-       }
-
-       // No castling, but checks, so keep track of kings
-       setOtherVariables(fen)
-       {
-               this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
-               const fenParts = fen.split(" ");
-               const position = fenParts[0].split("/");
-               for (let i=0; i<position.length; i++)
-               {
-                       let k = 0;
-                       for (let j=0; j<position[i].length; j++)
-                       {
-                               switch (position[i].charAt(j))
-                               {
-                                       case 'k':
-                                               this.kingPos['b'] = [i,k];
-                                               break;
-                                       case 'K':
-                                               this.kingPos['w'] = [i,k];
-                                               break;
-                                       default:
-                                               let num = parseInt(position[i].charAt(j));
-                                               if (!isNaN(num))
-                                                       k += (num-1);
-                               }
-                               k++;
-                       }
-               }
-       }
-
-       static get IMMOBILIZER() { return 'm'; }
-       // Although other pieces keep their names here for coding simplicity,
-       // keep in mind that:
-       //  - a "rook" is a coordinator, capturing by coordinating with the king
-       //  - a "knight" is a long-leaper, capturing as in draughts
-       //  - a "bishop" is a chameleon, capturing as its prey
-       //  - a "queen" is a withdrawer, capturing by moving away from pieces
-
-       // Is piece on square (x,y) immobilized?
-       isImmobilized([x,y])
-       {
-               const piece = this.getPiece(x,y);
-               const color = this.getColor(x,y);
-               const oppCol = V.GetOppCol(color);
-               const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               outerLoop:
-               for (let step of adjacentSteps)
-               {
-                       const [i,j] = [x+step[0],y+step[1]];
-                       if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY
-                               && this.getColor(i,j) == oppCol)
-                       {
-                               const oppPiece = this.getPiece(i,j);
-                               if (oppPiece == V.IMMOBILIZER)
-                               {
-                                       // Moving is impossible only if this immobilizer is not neutralized
-                                       for (let step2 of adjacentSteps)
-                                       {
-                                               const [i2,j2] = [i+step2[0],j+step2[1]];
-                                               if (i2 == x && j2 == y)
-                                                       continue; //skip initial piece!
-                                               if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY
-                                                       && this.getColor(i2,j2) == color)
-                                               {
-                                                       if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2)))
-                                                               return false;
-                                               }
-                                       }
-                                       return true; //immobilizer isn't neutralized
-                               }
-                               // Chameleons can't be immobilized twice, because there is only one immobilizer
-                               if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)
-                                       return true;
-                       }
-               }
-               return false;
-       }
-
-       getPotentialMovesFrom([x,y])
-       {
-               // Pre-check: is thing on this square immobilized?
-               if (this.isImmobilized([x,y]))
-                       return [];
-               switch (this.getPiece(x,y))
-               {
-                       case V.IMMOBILIZER:
-                               return this.getPotentialImmobilizerMoves([x,y]);
-                       default:
-                               return super.getPotentialMovesFrom([x,y]);
-               }
-       }
-
-       getSlideNJumpMoves([x,y], steps, oneStep)
-       {
-               const color = this.getColor(x,y);
-               const piece = this.getPiece(x,y);
-               let moves = [];
-               outerLoop:
-               for (let step of steps)
-               {
-                       let i = x + step[0];
-                       let j = y + step[1];
-                       while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-                       {
-                               moves.push(this.getBasicMove([x,y], [i,j]));
-                               if (oneStep !== undefined)
-                                       continue outerLoop;
-                               i += step[0];
-                               j += step[1];
-                       }
-                       // Only king can take on occupied square:
-                       if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j]))
-                               moves.push(this.getBasicMove([x,y], [i,j]));
-               }
-               return moves;
-       }
-
-       // Modify capturing moves among listed pawn moves
-       addPawnCaptures(moves, byChameleon)
-       {
-               const steps = V.steps[V.ROOK];
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               moves.forEach(m => {
-                       if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y)
-                               return; //chameleon not moving as pawn
-                       // Try capturing in every direction
-                       for (let step of steps)
-                       {
-                               const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]];
-                               if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY
-                                       && this.getColor(sq2[0],sq2[1]) == color)
-                               {
-                                       // Potential capture
-                                       const sq1 = [m.end.x+step[0],m.end.y+step[1]];
-                                       if (this.board[sq1[0]][sq1[1]] != V.EMPTY
-                                               && this.getColor(sq1[0],sq1[1]) == oppCol)
-                                       {
-                                               const piece1 = this.getPiece(sq1[0],sq1[1]);
-                                               if (!byChameleon || piece1 == V.PAWN)
-                                               {
-                                                       m.vanish.push(new PiPo({
-                                                               x:sq1[0],
-                                                               y:sq1[1],
-                                                               c:oppCol,
-                                                               p:piece1
-                                                       }));
-                                               }
-                                       }
-                               }
-                       }
-               });
-       }
-
-       // "Pincer"
-       getPotentialPawnMoves([x,y])
-       {
-               let moves = super.getPotentialRookMoves([x,y]);
-               this.addPawnCaptures(moves);
-               return moves;
-       }
-
-       addRookCaptures(moves, byChameleon)
-       {
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               const kp = this.kingPos[color];
-               moves.forEach(m => {
-                       // Check piece-king rectangle (if any) corners for enemy pieces
-                       if (m.end.x == kp[0] || m.end.y == kp[1])
-                               return; //"flat rectangle"
-                       const corner1 = [m.end.x, kp[1]];
-                       const corner2 = [kp[0], m.end.y];
-                       for (let [i,j] of [corner1,corner2])
-                       {
-                               if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol)
-                               {
-                                       const piece = this.getPiece(i,j);
-                                       if (!byChameleon || piece == V.ROOK)
-                                       {
-                                               m.vanish.push( new PiPo({
-                                                       x:i,
-                                                       y:j,
-                                                       p:piece,
-                                                       c:oppCol
-                                               }) );
-                                       }
-                               }
-                       }
-               });
-       }
-
-       // Coordinator
-       getPotentialRookMoves(sq)
-       {
-               let moves = super.getPotentialQueenMoves(sq);
-               this.addRookCaptures(moves);
-               return moves;
-       }
-
-       // Long-leaper
-       getKnightCaptures(startSquare, byChameleon)
-       {
-               // Look in every direction for captures
-               const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               let moves = [];
-               const [x,y] = [startSquare[0],startSquare[1]];
-               const piece = this.getPiece(x,y); //might be a chameleon!
-               outerLoop:
-               for (let step of steps)
-               {
-                       let [i,j] = [x+step[0], y+step[1]];
-                       while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY)
-                       {
-                               i += step[0];
-                               j += step[1];
-                       }
-                       if (!V.OnBoard(i,j) || this.getColor(i,j)==color
-                               || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT))
-                       {
-                               continue;
-                       }
-                       // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
-                       // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
-                       // add move until cur square; if cur is occupied then stop if !!byChameleon and
-                       // the square not occupied by a leaper.
-                       let last = [i,j];
-                       let cur = [i+step[0],j+step[1]];
-                       let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ];
-                       while (V.OnBoard(cur[0],cur[1]))
-                       {
-                               if (this.board[last[0]][last[1]] != V.EMPTY)
-                               {
-                                       const oppPiece = this.getPiece(last[0],last[1]);
-                                       if (!!byChameleon && oppPiece != V.KNIGHT)
-                                               continue outerLoop;
-                                       // Something to eat:
-                                       vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) );
-                               }
-                               if (this.board[cur[0]][cur[1]] != V.EMPTY)
-                               {
-                                       if (this.getColor(cur[0],cur[1]) == color
-                                               || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test
-                                       {
-                                               continue outerLoop;
-                                       }
-                               }
-                               else
-                               {
-                                       moves.push(new Move({
-                                               appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ],
-                                               vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
-                                               start: {x:x,y:y},
-                                               end: {x:cur[0],y:cur[1]}
-                                       }));
-                               }
-                               last = [last[0]+step[0],last[1]+step[1]];
-                               cur = [cur[0]+step[0],cur[1]+step[1]];
-                       }
-               }
-               return moves;
-       }
-
-       // Long-leaper
-       getPotentialKnightMoves(sq)
-       {
-               return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
-       }
-
-       getPotentialBishopMoves([x,y])
-       {
-               let moves = super.getPotentialQueenMoves([x,y])
-                       .concat(this.getKnightCaptures([x,y],"asChameleon"));
-               // No "king capture" because king cannot remain under check
-               this.addPawnCaptures(moves, "asChameleon");
-               this.addRookCaptures(moves, "asChameleon");
-               this.addQueenCaptures(moves, "asChameleon");
-               // Post-processing: merge similar moves, concatenating vanish arrays
-               let mergedMoves = {};
-               moves.forEach(m => {
-                       const key = m.end.x + V.size.x * m.end.y;
-                       if (!mergedMoves[key])
-                               mergedMoves[key] = m;
-                       else
-                       {
-                               for (let i=1; i<m.vanish.length; i++)
-                                       mergedMoves[key].vanish.push(m.vanish[i]);
-                       }
-               });
-               // Finally return an array
-               moves = [];
-               Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); });
-               return moves;
-       }
-
-       // Withdrawer
-       addQueenCaptures(moves, byChameleon)
-       {
-               if (moves.length == 0)
-                       return;
-               const [x,y] = [moves[0].start.x,moves[0].start.y];
-               const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               let capturingDirections = [];
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               adjacentSteps.forEach(step => {
-                       const [i,j] = [x+step[0],y+step[1]];
-                       if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol
-                               && (!byChameleon || this.getPiece(i,j) == V.QUEEN))
-                       {
-                               capturingDirections.push(step);
-                       }
-               });
-               moves.forEach(m => {
-                       const step = [
-                               m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0,
-                               m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0
-                       ];
-                       // NOTE: includes() and even _.isEqual() functions fail...
-                       // TODO: this test should be done only once per direction
-                       if (capturingDirections.some(dir =>
-                               { return (dir[0]==-step[0] && dir[1]==-step[1]); }))
-                       {
-                               const [i,j] = [x-step[0],y-step[1]];
-                               m.vanish.push(new PiPo({
-                                       x:i,
-                                       y:j,
-                                       p:this.getPiece(i,j),
-                                       c:oppCol
-                               }));
-                       }
-               });
-       }
-
-       getPotentialQueenMoves(sq)
-       {
-               let moves = super.getPotentialQueenMoves(sq);
-               this.addQueenCaptures(moves);
-               return moves;
-       }
-
-       getPotentialImmobilizerMoves(sq)
-       {
-               // Immobilizer doesn't capture
-               return super.getPotentialQueenMoves(sq);
-       }
-
-       getPotentialKingMoves(sq)
-       {
-               return this.getSlideNJumpMoves(sq,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-       }
-
-       // isAttacked() is OK because the immobilizer doesn't take
-
-       isAttackedByPawn([x,y], colors)
-       {
-               // Square (x,y) must be surroundable by two enemy pieces,
-               // and one of them at least should be a pawn (moving).
-               const dirs = [ [1,0],[0,1] ];
-               const steps = V.steps[V.ROOK];
-               for (let dir of dirs)
-               {
-                       const [i1,j1] = [x-dir[0],y-dir[1]]; //"before"
-                       const [i2,j2] = [x+dir[0],y+dir[1]]; //"after"
-                       if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2))
-                       {
-                               if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1))
-                                       && this.board[i2][j2]==V.EMPTY)
-                                               ||
-                                       (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2))
-                                       && this.board[i1][j1]==V.EMPTY))
-                               {
-                                       // Search a movable enemy pawn landing on the empty square
-                                       for (let step of steps)
-                                       {
-                                               let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]);
-                                               let [i3,j3] = [ii+step[0],jj+step[1]];
-                                               while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY)
-                                               {
-                                                       i3 += step[0];
-                                                       j3 += step[1];
-                                               }
-                                               if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3))
-                                                       && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3]))
-                                               {
-                                                       return true;
-                                               }
-                                       }
-                               }
-                       }
-               }
-               return false;
-       }
-
-       isAttackedByRook([x,y], colors)
-       {
-               // King must be on same column or row,
-               // and a rook should be able to reach a capturing square
-               // colors contains only one element, giving the oppCol and thus king position
-               const sameRow = (x == this.kingPos[colors[0]][0]);
-               const sameColumn = (y == this.kingPos[colors[0]][1]);
-               if (sameRow || sameColumn)
-               {
-                       // Look for the enemy rook (maximum 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.EMPTY && colors.includes(this.getColor(i,j))
-                                               && this.getPiece(i,j) == V.ROOK)
-                                       {
-                                               if (this.isImmobilized([i,j]))
-                                                       return false; //because only one rook
-                                               // Can it reach a capturing square?
-                                               // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
-                                               const moves = this.getPotentialMovesFrom([i,j]);
-                                               for (let move of moves)
-                                               {
-                                                       if (sameRow && move.end.y == y || sameColumn && move.end.x == x)
-                                                               return true;
-                                               }
-                                       }
-                               }
-                       }
-               }
-               return false;
-       }
-
-       isAttackedByKnight([x,y], colors)
-       {
-               // Square (x,y) must be on same line as a knight,
-               // and there must be empty square(s) behind.
-               const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               outerLoop:
-               for (let step of steps)
-               {
-                       const [i0,j0] = [x+step[0],y+step[1]];
-                       if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY)
-                       {
-                               // Try in opposite direction:
-                               let [i,j] = [x-step[0],y-step[1]];
-                               while (V.OnBoard(i,j))
-                               {
-                                       while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-                                       {
-                                               i -= step[0];
-                                               j -= step[1];
-                                       }
-                                       if (V.OnBoard(i,j))
-                                       {
-                                               if (colors.includes(this.getColor(i,j)))
-                                               {
-                                                       if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j]))
-                                                               return true;
-                                                       continue outerLoop;
-                                               }
-                                               // [else] Our color, could be captured *if there was an empty space*
-                                               if (this.board[i+step[0]][j+step[1]] != V.EMPTY)
-                                                       continue outerLoop;
-                                               i -= step[0];
-                                               j -= step[1];
-                                       }
-                               }
-                       }
-               }
-               return false;
-       }
-
-       isAttackedByBishop([x,y], colors)
-       {
-               // We cheat a little here: since this function is used exclusively for king,
-               // it's enough to check the immediate surrounding of the square.
-               const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               for (let step of adjacentSteps)
-               {
-                       const [i,j] = [x+step[0],y+step[1]];
-                       if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY
-                               && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP)
-                       {
-                               return true; //bishops are never immobilized
-                       }
-               }
-               return false;
-       }
-
-       isAttackedByQueen([x,y], colors)
-       {
-               // Square (x,y) must be adjacent to a queen, and the queen must have
-               // some free space in the opposite direction from (x,y)
-               const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-               for (let step of adjacentSteps)
-               {
-                       const sq2 = [x+2*step[0],y+2*step[1]];
-                       if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY)
-                       {
-                               const sq1 = [x+step[0],y+step[1]];
-                               if (this.board[sq1[0]][sq1[1]] != V.EMPTY
-                                       && colors.includes(this.getColor(sq1[0],sq1[1]))
-                                       && this.getPiece(sq1[0],sq1[1]) == V.QUEEN
-                                       && !this.isImmobilized(sq1))
-                               {
-                                       return true;
-                               }
-                       }
-               }
-               return false;
-       }
-
-       static get VALUES()
-       {
-               // TODO: totally experimental!
-               return {
-                       'p': 1,
-                       'r': 2,
-                       'n': 5,
-                       'b': 3,
-                       'q': 3,
-                       'm': 5,
-                       'k': 1000
-               };
-       }
-
-       static get SEARCH_DEPTH() { return 2; } //TODO?
-
-       static GenRandInitFen()
-       {
-               let pieces = { "w": new Array(8), "b": new Array(8) };
-               // Shuffle pieces on first and last rank
-               for (let c of ["w","b"])
-               {
-                       let positions = ArrayFun.range(8);
-                       // Get random squares for every piece, totally freely
-
-                       let randIndex = randInt(8);
-                       const bishop1Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(7);
-                       const bishop2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(6);
-                       const knight1Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(5);
-                       const knight2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(4);
-                       const queenPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(3);
-                       const kingPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       randIndex = randInt(2);
-                       const rookPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-                       const immobilizerPos = positions[0];
-
-                       pieces[c][bishop1Pos] = 'b';
-                       pieces[c][bishop2Pos] = 'b';
-                       pieces[c][knight1Pos] = 'n';
-                       pieces[c][knight2Pos] = 'n';
-                       pieces[c][queenPos] = 'q';
-                       pieces[c][kingPos] = 'k';
-                       pieces[c][rookPos] = 'r';
-                       pieces[c][immobilizerPos] = 'm';
-               }
-               return pieces["b"].join("") +
-                       "/pppppppp/8/8/8/8/PPPPPPPP/" +
-                       pieces["w"].join("").toUpperCase() +
-                       " w 0";
-       }
-
-       getNotation(move)
-       {
-               const initialSquare = V.CoordsToSquare(move.start);
-               const finalSquare = V.CoordsToSquare(move.end);
-               let notation = undefined;
-               if (move.appear[0].p == V.PAWN)
-               {
-                       // Pawn: generally ambiguous short notation, so we use full description
-                       notation = "P" + initialSquare + finalSquare;
-               }
-               else if (move.appear[0].p == V.KING)
-                       notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare;
-               else
-                       notation = move.appear[0].p.toUpperCase() + finalSquare;
-               if (move.vanish.length > 1 && move.appear[0].p != V.KING)
-                       notation += "X"; //capture mark (not describing what is captured...)
-               return notation;
-       }
+  static get HasFlags() { return false; }
+
+  static get HasEnpassant() { return false; }
+
+  static getPpath(b)
+  {
+    if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
+      return "Baroque/" + b;
+    return b; //usual piece
+  }
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.IMMOBILIZER]);
+  }
+
+  // No castling, but checks, so keep track of kings
+  setOtherVariables(fen)
+  {
+    this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
+    const fenParts = fen.split(" ");
+    const position = fenParts[0].split("/");
+    for (let i=0; i<position.length; i++)
+    {
+      let k = 0;
+      for (let j=0; j<position[i].length; j++)
+      {
+        switch (position[i].charAt(j))
+        {
+          case 'k':
+            this.kingPos['b'] = [i,k];
+            break;
+          case 'K':
+            this.kingPos['w'] = [i,k];
+            break;
+          default:
+            let num = parseInt(position[i].charAt(j));
+            if (!isNaN(num))
+              k += (num-1);
+        }
+        k++;
+      }
+    }
+  }
+
+  static get IMMOBILIZER() { return 'm'; }
+  // Although other pieces keep their names here for coding simplicity,
+  // keep in mind that:
+  //  - a "rook" is a coordinator, capturing by coordinating with the king
+  //  - a "knight" is a long-leaper, capturing as in draughts
+  //  - a "bishop" is a chameleon, capturing as its prey
+  //  - a "queen" is a withdrawer, capturing by moving away from pieces
+
+  // Is piece on square (x,y) immobilized?
+  isImmobilized([x,y])
+  {
+    const piece = this.getPiece(x,y);
+    const color = this.getColor(x,y);
+    const oppCol = V.GetOppCol(color);
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    outerLoop:
+    for (let step of adjacentSteps)
+    {
+      const [i,j] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY
+        && this.getColor(i,j) == oppCol)
+      {
+        const oppPiece = this.getPiece(i,j);
+        if (oppPiece == V.IMMOBILIZER)
+        {
+          // Moving is impossible only if this immobilizer is not neutralized
+          for (let step2 of adjacentSteps)
+          {
+            const [i2,j2] = [i+step2[0],j+step2[1]];
+            if (i2 == x && j2 == y)
+              continue; //skip initial piece!
+            if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY
+              && this.getColor(i2,j2) == color)
+            {
+              if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2)))
+                return false;
+            }
+          }
+          return true; //immobilizer isn't neutralized
+        }
+        // Chameleons can't be immobilized twice, because there is only one immobilizer
+        if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)
+          return true;
+      }
+    }
+    return false;
+  }
+
+  getPotentialMovesFrom([x,y])
+  {
+    // Pre-check: is thing on this square immobilized?
+    if (this.isImmobilized([x,y]))
+      return [];
+    switch (this.getPiece(x,y))
+    {
+      case V.IMMOBILIZER:
+        return this.getPotentialImmobilizerMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y]);
+    }
+  }
+
+  getSlideNJumpMoves([x,y], steps, oneStep)
+  {
+    const color = this.getColor(x,y);
+    const piece = this.getPiece(x,y);
+    let moves = [];
+    outerLoop:
+    for (let step of steps)
+    {
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [i,j]));
+        if (oneStep !== undefined)
+          continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      // Only king can take on occupied square:
+      if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j]))
+        moves.push(this.getBasicMove([x,y], [i,j]));
+    }
+    return moves;
+  }
+
+  // Modify capturing moves among listed pawn moves
+  addPawnCaptures(moves, byChameleon)
+  {
+    const steps = V.steps[V.ROOK];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    moves.forEach(m => {
+      if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y)
+        return; //chameleon not moving as pawn
+      // Try capturing in every direction
+      for (let step of steps)
+      {
+        const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]];
+        if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY
+          && this.getColor(sq2[0],sq2[1]) == color)
+        {
+          // Potential capture
+          const sq1 = [m.end.x+step[0],m.end.y+step[1]];
+          if (this.board[sq1[0]][sq1[1]] != V.EMPTY
+            && this.getColor(sq1[0],sq1[1]) == oppCol)
+          {
+            const piece1 = this.getPiece(sq1[0],sq1[1]);
+            if (!byChameleon || piece1 == V.PAWN)
+            {
+              m.vanish.push(new PiPo({
+                x:sq1[0],
+                y:sq1[1],
+                c:oppCol,
+                p:piece1
+              }));
+            }
+          }
+        }
+      }
+    });
+  }
+
+  // "Pincer"
+  getPotentialPawnMoves([x,y])
+  {
+    let moves = super.getPotentialRookMoves([x,y]);
+    this.addPawnCaptures(moves);
+    return moves;
+  }
+
+  addRookCaptures(moves, byChameleon)
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const kp = this.kingPos[color];
+    moves.forEach(m => {
+      // Check piece-king rectangle (if any) corners for enemy pieces
+      if (m.end.x == kp[0] || m.end.y == kp[1])
+        return; //"flat rectangle"
+      const corner1 = [m.end.x, kp[1]];
+      const corner2 = [kp[0], m.end.y];
+      for (let [i,j] of [corner1,corner2])
+      {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol)
+        {
+          const piece = this.getPiece(i,j);
+          if (!byChameleon || piece == V.ROOK)
+          {
+            m.vanish.push( new PiPo({
+              x:i,
+              y:j,
+              p:piece,
+              c:oppCol
+            }) );
+          }
+        }
+      }
+    });
+  }
+
+  // Coordinator
+  getPotentialRookMoves(sq)
+  {
+    let moves = super.getPotentialQueenMoves(sq);
+    this.addRookCaptures(moves);
+    return moves;
+  }
+
+  // Long-leaper
+  getKnightCaptures(startSquare, byChameleon)
+  {
+    // Look in every direction for captures
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let moves = [];
+    const [x,y] = [startSquare[0],startSquare[1]];
+    const piece = this.getPiece(x,y); //might be a chameleon!
+    outerLoop:
+    for (let step of steps)
+    {
+      let [i,j] = [x+step[0], y+step[1]];
+      while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY)
+      {
+        i += step[0];
+        j += step[1];
+      }
+      if (!V.OnBoard(i,j) || this.getColor(i,j)==color
+        || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT))
+      {
+        continue;
+      }
+      // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
+      // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
+      // add move until cur square; if cur is occupied then stop if !!byChameleon and
+      // the square not occupied by a leaper.
+      let last = [i,j];
+      let cur = [i+step[0],j+step[1]];
+      let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ];
+      while (V.OnBoard(cur[0],cur[1]))
+      {
+        if (this.board[last[0]][last[1]] != V.EMPTY)
+        {
+          const oppPiece = this.getPiece(last[0],last[1]);
+          if (!!byChameleon && oppPiece != V.KNIGHT)
+            continue outerLoop;
+          // Something to eat:
+          vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) );
+        }
+        if (this.board[cur[0]][cur[1]] != V.EMPTY)
+        {
+          if (this.getColor(cur[0],cur[1]) == color
+            || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test
+          {
+            continue outerLoop;
+          }
+        }
+        else
+        {
+          moves.push(new Move({
+            appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ],
+            vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
+            start: {x:x,y:y},
+            end: {x:cur[0],y:cur[1]}
+          }));
+        }
+        last = [last[0]+step[0],last[1]+step[1]];
+        cur = [cur[0]+step[0],cur[1]+step[1]];
+      }
+    }
+    return moves;
+  }
+
+  // Long-leaper
+  getPotentialKnightMoves(sq)
+  {
+    return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
+  }
+
+  getPotentialBishopMoves([x,y])
+  {
+    let moves = super.getPotentialQueenMoves([x,y])
+      .concat(this.getKnightCaptures([x,y],"asChameleon"));
+    // No "king capture" because king cannot remain under check
+    this.addPawnCaptures(moves, "asChameleon");
+    this.addRookCaptures(moves, "asChameleon");
+    this.addQueenCaptures(moves, "asChameleon");
+    // Post-processing: merge similar moves, concatenating vanish arrays
+    let mergedMoves = {};
+    moves.forEach(m => {
+      const key = m.end.x + V.size.x * m.end.y;
+      if (!mergedMoves[key])
+        mergedMoves[key] = m;
+      else
+      {
+        for (let i=1; i<m.vanish.length; i++)
+          mergedMoves[key].vanish.push(m.vanish[i]);
+      }
+    });
+    // Finally return an array
+    moves = [];
+    Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); });
+    return moves;
+  }
+
+  // Withdrawer
+  addQueenCaptures(moves, byChameleon)
+  {
+    if (moves.length == 0)
+      return;
+    const [x,y] = [moves[0].start.x,moves[0].start.y];
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    let capturingDirections = [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    adjacentSteps.forEach(step => {
+      const [i,j] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol
+        && (!byChameleon || this.getPiece(i,j) == V.QUEEN))
+      {
+        capturingDirections.push(step);
+      }
+    });
+    moves.forEach(m => {
+      const step = [
+        m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0,
+        m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0
+      ];
+      // NOTE: includes() and even _.isEqual() functions fail...
+      // TODO: this test should be done only once per direction
+      if (capturingDirections.some(dir =>
+        { return (dir[0]==-step[0] && dir[1]==-step[1]); }))
+      {
+        const [i,j] = [x-step[0],y-step[1]];
+        m.vanish.push(new PiPo({
+          x:i,
+          y:j,
+          p:this.getPiece(i,j),
+          c:oppCol
+        }));
+      }
+    });
+  }
+
+  getPotentialQueenMoves(sq)
+  {
+    let moves = super.getPotentialQueenMoves(sq);
+    this.addQueenCaptures(moves);
+    return moves;
+  }
+
+  getPotentialImmobilizerMoves(sq)
+  {
+    // Immobilizer doesn't capture
+    return super.getPotentialQueenMoves(sq);
+  }
+
+  getPotentialKingMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  // isAttacked() is OK because the immobilizer doesn't take
+
+  isAttackedByPawn([x,y], colors)
+  {
+    // Square (x,y) must be surroundable by two enemy pieces,
+    // and one of them at least should be a pawn (moving).
+    const dirs = [ [1,0],[0,1] ];
+    const steps = V.steps[V.ROOK];
+    for (let dir of dirs)
+    {
+      const [i1,j1] = [x-dir[0],y-dir[1]]; //"before"
+      const [i2,j2] = [x+dir[0],y+dir[1]]; //"after"
+      if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2))
+      {
+        if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1))
+          && this.board[i2][j2]==V.EMPTY)
+            ||
+          (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2))
+          && this.board[i1][j1]==V.EMPTY))
+        {
+          // Search a movable enemy pawn landing on the empty square
+          for (let step of steps)
+          {
+            let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]);
+            let [i3,j3] = [ii+step[0],jj+step[1]];
+            while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY)
+            {
+              i3 += step[0];
+              j3 += step[1];
+            }
+            if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3))
+              && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3]))
+            {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByRook([x,y], colors)
+  {
+    // King must be on same column or row,
+    // and a rook should be able to reach a capturing square
+    // colors contains only one element, giving the oppCol and thus king position
+    const sameRow = (x == this.kingPos[colors[0]][0]);
+    const sameColumn = (y == this.kingPos[colors[0]][1]);
+    if (sameRow || sameColumn)
+    {
+      // Look for the enemy rook (maximum 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.EMPTY && colors.includes(this.getColor(i,j))
+            && this.getPiece(i,j) == V.ROOK)
+          {
+            if (this.isImmobilized([i,j]))
+              return false; //because only one rook
+            // Can it reach a capturing square?
+            // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
+            const moves = this.getPotentialMovesFrom([i,j]);
+            for (let move of moves)
+            {
+              if (sameRow && move.end.y == y || sameColumn && move.end.x == x)
+                return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByKnight([x,y], colors)
+  {
+    // Square (x,y) must be on same line as a knight,
+    // and there must be empty square(s) behind.
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    outerLoop:
+    for (let step of steps)
+    {
+      const [i0,j0] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY)
+      {
+        // Try in opposite direction:
+        let [i,j] = [x-step[0],y-step[1]];
+        while (V.OnBoard(i,j))
+        {
+          while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+          {
+            i -= step[0];
+            j -= step[1];
+          }
+          if (V.OnBoard(i,j))
+          {
+            if (colors.includes(this.getColor(i,j)))
+            {
+              if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j]))
+                return true;
+              continue outerLoop;
+            }
+            // [else] Our color, could be captured *if there was an empty space*
+            if (this.board[i+step[0]][j+step[1]] != V.EMPTY)
+              continue outerLoop;
+            i -= step[0];
+            j -= step[1];
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByBishop([x,y], colors)
+  {
+    // We cheat a little here: since this function is used exclusively for king,
+    // it's enough to check the immediate surrounding of the square.
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    for (let step of adjacentSteps)
+    {
+      const [i,j] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY
+        && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP)
+      {
+        return true; //bishops are never immobilized
+      }
+    }
+    return false;
+  }
+
+  isAttackedByQueen([x,y], colors)
+  {
+    // Square (x,y) must be adjacent to a queen, and the queen must have
+    // some free space in the opposite direction from (x,y)
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    for (let step of adjacentSteps)
+    {
+      const sq2 = [x+2*step[0],y+2*step[1]];
+      if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY)
+      {
+        const sq1 = [x+step[0],y+step[1]];
+        if (this.board[sq1[0]][sq1[1]] != V.EMPTY
+          && colors.includes(this.getColor(sq1[0],sq1[1]))
+          && this.getPiece(sq1[0],sq1[1]) == V.QUEEN
+          && !this.isImmobilized(sq1))
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  static get VALUES()
+  {
+    // TODO: totally experimental!
+    return {
+      'p': 1,
+      'r': 2,
+      'n': 5,
+      'b': 3,
+      'q': 3,
+      'm': 5,
+      'k': 1000
+    };
+  }
+
+  static get SEARCH_DEPTH() { return 2; } //TODO?
+
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    // Shuffle pieces on first and last rank
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
+      // Get random squares for every piece, totally freely
+
+      let randIndex = randInt(8);
+      const bishop1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(7);
+      const bishop2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(6);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(5);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(4);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(3);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(2);
+      const rookPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      const immobilizerPos = positions[0];
+
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][rookPos] = 'r';
+      pieces[c][immobilizerPos] = 'm';
+    }
+    return pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0";
+  }
+
+  getNotation(move)
+  {
+    const initialSquare = V.CoordsToSquare(move.start);
+    const finalSquare = V.CoordsToSquare(move.end);
+    let notation = undefined;
+    if (move.appear[0].p == V.PAWN)
+    {
+      // Pawn: generally ambiguous short notation, so we use full description
+      notation = "P" + initialSquare + finalSquare;
+    }
+    else if (move.appear[0].p == V.KING)
+      notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare;
+    else
+      notation = move.appear[0].p.toUpperCase() + finalSquare;
+    if (move.vanish.length > 1 && move.appear[0].p != V.KING)
+      notation += "X"; //capture mark (not describing what is captured...)
+    return notation;
+  }
 }
index 05152b4..592b25a 100644 (file)
@@ -2,136 +2,136 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class  BerolinaRules extends ChessRules
 {
-       // En-passant after 2-sq jump
-       getEpSquare(moveOrSquare)
-       {
-               if (!moveOrSquare)
-                       return undefined;
-               if (typeof moveOrSquare === "string")
-               {
-                       const square = moveOrSquare;
-                       if (square == "-")
-                               return undefined;
-                       // Enemy pawn initial column must be given too:
-                       let res = [];
-                       const epParts = square.split(",");
-                       res.push(V.SquareToCoords(epParts[0]));
-                       res.push(V.ColumnToCoord(epParts[1]));
-                       return res;
-               }
-               // Argument is a move:
-               const move = moveOrSquare;
-               const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
-               if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
-               {
-                       return
-                       [
-                               {
-                                       x: (ex + sx)/2,
-                                       y: (move.end.y + sy)/2
-                               },
-                               move.end.y
-                       ];
-               }
-               return undefined; //default
-       }
+  // En-passant after 2-sq jump
+  getEpSquare(moveOrSquare)
+  {
+    if (!moveOrSquare)
+      return undefined;
+    if (typeof moveOrSquare === "string")
+    {
+      const square = moveOrSquare;
+      if (square == "-")
+        return undefined;
+      // Enemy pawn initial column must be given too:
+      let res = [];
+      const epParts = square.split(",");
+      res.push(V.SquareToCoords(epParts[0]));
+      res.push(V.ColumnToCoord(epParts[1]));
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
+    if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
+    {
+      return
+      [
+        {
+          x: (ex + sx)/2,
+          y: (move.end.y + sy)/2
+        },
+        move.end.y
+      ];
+    }
+    return undefined; //default
+  }
 
-       // Special pawns movements
-       getPotentialPawnMoves([x,y])
-       {
-               const color = this.turn;
-               let moves = [];
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               const shiftX = (color == "w" ? -1 : 1);
-               const firstRank = (color == 'w' ? sizeX-1 : 0);
-               const startRank = (color == "w" ? sizeX-2 : 1);
-               const lastRank = (color == "w" ? 0 : sizeX-1);
-               const finalPieces = x + shiftX == lastRank
-                       ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
-                       : [V.PAWN];
+  // Special pawns movements
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const firstRank = (color == 'w' ? sizeX-1 : 0);
+    const startRank = (color == "w" ? sizeX-2 : 1);
+    const lastRank = (color == "w" ? 0 : sizeX-1);
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+      : [V.PAWN];
 
-               // One square diagonally
-               for (let shiftY of [-1,1])
-               {
-                       if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
-                       {
-                               for (let piece of finalPieces)
-                               {
-                                       moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-                                               {c:color,p:piece}));
-                               }
-                               if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
-                                       && this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
-                               {
-                                       // Two squares jump
-                                       moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]));
-                               }
-                       }
-               }
-               // Capture
-               if (this.board[x+shiftX][y] != V.EMPTY
-                       && this.canTake([x,y], [x+shiftX,y]))
-               {
-                       for (let piece of finalPieces)
-                               moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
-               }
+    // One square diagonally
+    for (let shiftY of [-1,1])
+    {
+      if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+        if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
+          && this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
+        {
+          // Two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]));
+        }
+      }
+    }
+    // Capture
+    if (this.board[x+shiftX][y] != V.EMPTY
+      && this.canTake([x,y], [x+shiftX,y]))
+    {
+      for (let piece of finalPieces)
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+    }
 
-               // En passant
-               const Lep = this.epSquares.length;
-               const epSquare = this.epSquares[Lep-1]; //always at least one element
-               if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
-                       && Math.abs(epSquare[1] - y) == 1)
-               {
-                       let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
-                       enpassantMove.vanish.push({
-                               x: x,
-                               y: epSquare[1],
-                               p: 'p',
-                               c: this.getColor(x,epSquare[1])
-                       });
-                       moves.push(enpassantMove);
-               }
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep-1]; //always at least one element
+    if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
+      && Math.abs(epSquare[1] - y) == 1)
+    {
+      let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare[1],
+        p: 'p',
+        c: this.getColor(x,epSquare[1])
+      });
+      moves.push(enpassantMove);
+    }
 
-               return moves;
-       }
+    return moves;
+  }
 
-       isAttackedByPawn([x,y], colors)
-       {
-               for (let c of colors)
-               {
-                       let pawnShift = (c=="w" ? 1 : -1);
-                       if (x+pawnShift>=0 && x+pawnShift<V.size.x)
-                       {
-                               if (this.getPiece(x+pawnShift,y)==V.PAWN
-                                       && this.getColor(x+pawnShift,y)==c)
-                               {
-                                       return true;
-                               }
-                       }
-               }
-               return false;
-       }
+  isAttackedByPawn([x,y], colors)
+  {
+    for (let c of colors)
+    {
+      let pawnShift = (c=="w" ? 1 : -1);
+      if (x+pawnShift>=0 && x+pawnShift<V.size.x)
+      {
+        if (this.getPiece(x+pawnShift,y)==V.PAWN
+          && this.getColor(x+pawnShift,y)==c)
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
 
-       getNotation(move)
-       {
-               const piece = this.getPiece(move.start.x, move.start.y);
-               if (piece == V.PAWN)
-               {
-                       // Pawn move
-                       const finalSquare = V.CoordsToSquare(move.end);
-                       let notation = "";
-                       if (move.vanish.length == 2) //capture
-                               notation = "Px" + finalSquare;
-                       else
-                       {
-                               // No capture: indicate the initial square for potential ambiguity
-                               const startSquare = V.CoordsToSquare(move.start);
-                               notation = startSquare + finalSquare;
-                       }
-                       if (move.appear[0].p != V.PAWN) //promotion
-                               notation += "=" + move.appear[0].p.toUpperCase();
-                       return notation;
-               }
-               return super.getNotation(move); //all other pieces are orthodox
-       }
+  getNotation(move)
+  {
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN)
+    {
+      // Pawn move
+      const finalSquare = V.CoordsToSquare(move.end);
+      let notation = "";
+      if (move.vanish.length == 2) //capture
+        notation = "Px" + finalSquare;
+      else
+      {
+        // No capture: indicate the initial square for potential ambiguity
+        const startSquare = V.CoordsToSquare(move.start);
+        notation = startSquare + finalSquare;
+      }
+      if (move.appear[0].p != V.PAWN) //promotion
+        notation += "=" + move.appear[0].p.toUpperCase();
+      return notation;
+    }
+    return super.getNotation(move); //all other pieces are orthodox
+  }
 }
index 586c88e..afbf21e 100644 (file)
@@ -2,49 +2,49 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class CheckeredRules extends ChessRules
 {
-       static getPpath(b)
-       {
-               return b[0]=='c' ? "Checkered/"+b : b;
-       }
-
-       static board2fen(b)
-       {
-               const checkered_codes = {
-                       'p': 's',
-                       'q': 't',
-                       'r': 'u',
-                       'b': 'c',
-                       'n': 'o',
-               };
-               if (b[0]=="c")
-                       return checkered_codes[b[1]];
-               return ChessRules.board2fen(b);
-       }
-
-       static fen2board(f)
-       {
-               // Tolerate upper-case versions of checkered pieces (why not?)
-               const checkered_pieces = {
-                       's': 'p',
-                       'S': 'p',
-                       't': 'q',
-                       'T': 'q',
-                       'u': 'r',
-                       'U': 'r',
-                       'c': 'b',
-                       'C': 'b',
-                       'o': 'n',
-                       'O': 'n',
-               };
-               if (Object.keys(checkered_pieces).includes(f))
-                       return 'c'+checkered_pieces[f];
-               return ChessRules.fen2board(f);
-       }
-
-       static get PIECES()
-       {
-               return ChessRules.PIECES.concat(['s','t','u','c','o']);
-       }
+  static getPpath(b)
+  {
+    return b[0]=='c' ? "Checkered/"+b : b;
+  }
+
+  static board2fen(b)
+  {
+    const checkered_codes = {
+      'p': 's',
+      'q': 't',
+      'r': 'u',
+      'b': 'c',
+      'n': 'o',
+    };
+    if (b[0]=="c")
+      return checkered_codes[b[1]];
+    return ChessRules.board2fen(b);
+  }
+
+  static fen2board(f)
+  {
+    // Tolerate upper-case versions of checkered pieces (why not?)
+    const checkered_pieces = {
+      's': 'p',
+      'S': 'p',
+      't': 'q',
+      'T': 'q',
+      'u': 'r',
+      'U': 'r',
+      'c': 'b',
+      'C': 'b',
+      'o': 'n',
+      'O': 'n',
+    };
+    if (Object.keys(checkered_pieces).includes(f))
+      return 'c'+checkered_pieces[f];
+    return ChessRules.fen2board(f);
+  }
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat(['s','t','u','c','o']);
+  }
 
   setOtherVariables(fen)
   {
@@ -65,7 +65,7 @@ export const VariantRules = class CheckeredRules extends ChessRules
 
   static IsGoodFen(fen)
   {
-         if (!ChessRules.IsGoodFen(fen))
+    if (!ChessRules.IsGoodFen(fen))
       return false;
     const fenParts = fen.split(" ");
     if (fenParts.length != 6)
@@ -75,40 +75,40 @@ export const VariantRules = class CheckeredRules extends ChessRules
     return true;
   }
 
-       static IsGoodFlags(flags)
-       {
-               // 4 for castle + 16 for pawns
-               return !!flags.match(/^[01]{20,20}$/);
-       }
-
-       setFlags(fenflags)
-       {
-               super.setFlags(fenflags); //castleFlags
-               this.pawnFlags =
-               {
-                       "w": [...Array(8).fill(true)], //pawns can move 2 squares?
-                       "b": [...Array(8).fill(true)],
-               };
-               if (!fenflags)
-                       return;
-               const flags = fenflags.substr(4); //skip first 4 digits, 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 IsGoodFlags(flags)
+  {
+    // 4 for castle + 16 for pawns
+    return !!flags.match(/^[01]{20,20}$/);
+  }
+
+  setFlags(fenflags)
+  {
+    super.setFlags(fenflags); //castleFlags
+    this.pawnFlags =
+    {
+      "w": [...Array(8).fill(true)], //pawns can move 2 squares?
+      "b": [...Array(8).fill(true)],
+    };
+    if (!fenflags)
+      return;
+    const flags = fenflags.substr(4); //skip first 4 digits, 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];
+  }
 
   getCmove(move)
   {
@@ -117,171 +117,171 @@ export const VariantRules = class CheckeredRules extends ChessRules
     return null;
   }
 
-       canTake([x1,y1], [x2,y2])
-       {
-               const color1 = this.getColor(x1,y1);
-               const color2 = this.getColor(x2,y2);
-               // Checkered aren't captured
-               return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
-       }
-
-       // Post-processing: apply "checkerization" of standard moves
-       getPotentialMovesFrom([x,y])
-       {
-               let standardMoves = super.getPotentialMovesFrom([x,y]);
-               const lastRank = this.turn == "w" ? 0 : 7;
-               if (this.getPiece(x,y) == V.KING)
-                       return standardMoves; //king has to be treated differently (for castles)
-               let moves = [];
-               standardMoves.forEach(m => {
-                       if (m.vanish[0].p == V.PAWN)
-                       {
-                               if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
-                                       return; //skip forbidden 2-squares jumps
-                               if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
-                                       && this.getColor(m.start.x,m.start.y) == 'c')
-                               {
-                                       return; //checkered pawns cannot take en-passant
-                               }
-                       }
-                       if (m.vanish.length == 1)
-                               moves.push(m); //no capture
-                       else
-                       {
-                               // A capture occured (m.vanish.length == 2)
-                               m.appear[0].c = "c";
-                               moves.push(m);
-                               if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
-                                       && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
-                               {
-                                       // Add transformation into captured piece
-                                       let m2 = JSON.parse(JSON.stringify(m));
-                                       m2.appear[0].p = m.vanish[1].p;
-                                       moves.push(m2);
-                               }
-                       }
-               });
-               return moves;
-       }
-
-       canIplay(side, [x,y])
-       {
-               return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
-       }
-
-       // Does m2 un-do m1 ? (to disallow undoing checkered moves)
-       oppositeMoves(m1, m2)
-       {
-               return (!!m1 && m2.appear[0].c == 'c'
+  canTake([x1,y1], [x2,y2])
+  {
+    const color1 = this.getColor(x1,y1);
+    const color2 = this.getColor(x2,y2);
+    // Checkered aren't captured
+    return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
+  }
+
+  // Post-processing: apply "checkerization" of standard moves
+  getPotentialMovesFrom([x,y])
+  {
+    let standardMoves = super.getPotentialMovesFrom([x,y]);
+    const lastRank = this.turn == "w" ? 0 : 7;
+    if (this.getPiece(x,y) == V.KING)
+      return standardMoves; //king has to be treated differently (for castles)
+    let moves = [];
+    standardMoves.forEach(m => {
+      if (m.vanish[0].p == V.PAWN)
+      {
+        if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
+          return; //skip forbidden 2-squares jumps
+        if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
+          && this.getColor(m.start.x,m.start.y) == 'c')
+        {
+          return; //checkered pawns cannot take en-passant
+        }
+      }
+      if (m.vanish.length == 1)
+        moves.push(m); //no capture
+      else
+      {
+        // A capture occured (m.vanish.length == 2)
+        m.appear[0].c = "c";
+        moves.push(m);
+        if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
+          && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
+        {
+          // Add transformation into captured piece
+          let m2 = JSON.parse(JSON.stringify(m));
+          m2.appear[0].p = m.vanish[1].p;
+          moves.push(m2);
+        }
+      }
+    });
+    return moves;
+  }
+
+  canIplay(side, [x,y])
+  {
+    return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
+  }
+
+  // Does m2 un-do m1 ? (to disallow undoing checkered moves)
+  oppositeMoves(m1, m2)
+  {
+    return (!!m1 && m2.appear[0].c == 'c'
       && m2.appear.length == 1 && m2.vanish.length == 1
-                       && m1.start.x == m2.end.x && m1.end.x == m2.start.x
-                       && m1.start.y == m2.end.y && m1.end.y == m2.start.y);
-       }
-
-       filterValid(moves)
-       {
-               if (moves.length == 0)
-                       return [];
-               const color = this.turn;
-               return moves.filter(m => {
-                       const L = this.cmoves.length; //at least 1: init from FEN
-                       if (this.oppositeMoves(this.cmoves[L-1], m))
-                               return false;
-                       this.play(m);
-                       const res = !this.underCheck(color);
-                       this.undo(m);
-                       return res;
-               });
-       }
-
-       isAttackedByPawn([x,y], colors)
-       {
-               for (let c of colors)
-               {
-                       const color = (c=="c" ? this.turn : c);
-                       let pawnShift = (color=="w" ? 1 : -1);
-                       if (x+pawnShift>=0 && x+pawnShift<8)
-                       {
-                               for (let i of [-1,1])
-                               {
-                                       if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
-                                               && this.getColor(x+pawnShift,y+i)==c)
-                                       {
-                                               return true;
-                                       }
-                               }
-                       }
-               }
-               return false;
-       }
-
-       underCheck(color)
-       {
-               return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
-       }
-
-       getCheckSquares(color)
-       {
-               // Artifically change turn, for checkered pawns
-               this.turn = V.GetOppCol(color);
-               const kingAttacked = this.isAttacked(
-                       this.kingPos[color], [V.GetOppCol(color),'c']);
-               let res = kingAttacked
-                       ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
-                       : [];
-               this.turn = color;
-               return res;
-       }
-
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               // Does this move turn off a 2-squares pawn flag?
-               const secondRank = [1,6];
-               if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
-                       this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
-       }
-
-       getCurrentScore()
-       {
+      && m1.start.x == m2.end.x && m1.end.x == m2.start.x
+      && m1.start.y == m2.end.y && m1.end.y == m2.start.y);
+  }
+
+  filterValid(moves)
+  {
+    if (moves.length == 0)
+      return [];
+    const color = this.turn;
+    return moves.filter(m => {
+      const L = this.cmoves.length; //at least 1: init from FEN
+      if (this.oppositeMoves(this.cmoves[L-1], m))
+        return false;
+      this.play(m);
+      const res = !this.underCheck(color);
+      this.undo(m);
+      return res;
+    });
+  }
+
+  isAttackedByPawn([x,y], colors)
+  {
+    for (let c of colors)
+    {
+      const color = (c=="c" ? this.turn : c);
+      let pawnShift = (color=="w" ? 1 : -1);
+      if (x+pawnShift>=0 && x+pawnShift<8)
+      {
+        for (let i of [-1,1])
+        {
+          if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
+            && this.getColor(x+pawnShift,y+i)==c)
+          {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  underCheck(color)
+  {
+    return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
+  }
+
+  getCheckSquares(color)
+  {
+    // Artifically change turn, for checkered pawns
+    this.turn = V.GetOppCol(color);
+    const kingAttacked = this.isAttacked(
+      this.kingPos[color], [V.GetOppCol(color),'c']);
+    let res = kingAttacked
+      ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+      : [];
+    this.turn = color;
+    return res;
+  }
+
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    // Does this move turn off a 2-squares pawn flag?
+    const secondRank = [1,6];
+    if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
+      this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
+  }
+
+  getCurrentScore()
+  {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
     const color = this.turn;
-               // Artifically change turn, for checkered pawns
-               this.turn = V.GetOppCol(this.turn);
-               const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
-                       ? (color == "w" ? "0-1" : "1-0")
-                       : "1/2";
-               this.turn = V.GetOppCol(this.turn);
-               return res;
-       }
-
-       evalPosition()
-       {
-               let evaluation = 0;
-               //Just count material for now, considering checkered neutral (...)
-               for (let i=0; i<V.size.x; i++)
-               {
-                       for (let j=0; j<V.size.y; j++)
-                       {
-                               if (this.board[i][j] != V.EMPTY)
-                               {
-                                       const sqColor = this.getColor(i,j);
-                                       const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
-                                       evaluation += sign * V.VALUES[this.getPiece(i,j)];
-                               }
-                       }
-               }
-               return evaluation;
-       }
-
-       static GenRandInitFen()
-       {
-               const randFen = ChessRules.GenRandInitFen();
-               // Add 16 pawns flags + empty cmove:
-               return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
-       }
+    // Artifically change turn, for checkered pawns
+    this.turn = V.GetOppCol(this.turn);
+    const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
+      ? (color == "w" ? "0-1" : "1-0")
+      : "1/2";
+    this.turn = V.GetOppCol(this.turn);
+    return res;
+  }
+
+  evalPosition()
+  {
+    let evaluation = 0;
+    //Just count material for now, considering checkered neutral (...)
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY)
+        {
+          const sqColor = this.getColor(i,j);
+          const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
+          evaluation += sign * V.VALUES[this.getPiece(i,j)];
+        }
+      }
+    }
+    return evaluation;
+  }
+
+  static GenRandInitFen()
+  {
+    const randFen = ChessRules.GenRandInitFen();
+    // Add 16 pawns flags + empty cmove:
+    return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
+  }
 
   static ParseFen(fen)
   {
@@ -301,17 +301,17 @@ export const VariantRules = class CheckeredRules extends ChessRules
     return super.getFen() + " " + cmoveFen;
   }
 
-       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;
-       }
+  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;
+  }
 
   // TODO (design): this cmove update here or in (un)updateVariables ?
   play(move)
@@ -326,46 +326,46 @@ export const VariantRules = class CheckeredRules extends ChessRules
     super.undo(move);
   }
 
-       getNotation(move)
-       {
-               if (move.appear.length == 2)
-               {
-                       // Castle
-                       if (move.end.y < move.start.y)
-                               return "0-0-0";
-                       else
-                               return "0-0";
-               }
-
-               // Translate final square
-               const finalSquare = V.CoordsToSquare(move.end);
-
-               const piece = this.getPiece(move.start.x, move.start.y);
-               if (piece == V.PAWN)
-               {
-                       // Pawn move
-                       let notation = "";
-                       if (move.vanish.length > 1)
-                       {
-                               // Capture
-                               const startColumn = V.CoordToColumn(move.start.y);
-                               notation = startColumn + "x" + finalSquare +
-                                       "=" + move.appear[0].p.toUpperCase();
-                       }
-                       else //no capture
-                       {
-                               notation = finalSquare;
-                               if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
-                                       notation += "=" + move.appear[0].p.toUpperCase();
-                       }
-                       return notation;
-               }
-
-               else
-               {
-                       // Piece movement
-                       return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
-                               + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
-               }
-       }
+  getNotation(move)
+  {
+    if (move.appear.length == 2)
+    {
+      // Castle
+      if (move.end.y < move.start.y)
+        return "0-0-0";
+      else
+        return "0-0";
+    }
+
+    // Translate final square
+    const finalSquare = V.CoordsToSquare(move.end);
+
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN)
+    {
+      // Pawn move
+      let notation = "";
+      if (move.vanish.length > 1)
+      {
+        // Capture
+        const startColumn = V.CoordToColumn(move.start.y);
+        notation = startColumn + "x" + finalSquare +
+          "=" + move.appear[0].p.toUpperCase();
+      }
+      else //no capture
+      {
+        notation = finalSquare;
+        if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
+          notation += "=" + move.appear[0].p.toUpperCase();
+      }
+      return notation;
+    }
+
+    else
+    {
+      // Piece movement
+      return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
+        + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
+    }
+  }
 }
index 36397f6..74f4d1d 100644 (file)
@@ -1,5 +1,5 @@
 import { ChessRules } from "@/base_rules";
 export const VariantRules = class Chess960Rules extends ChessRules
 {
-       // Standard rules
+  // Standard rules
 }
index ec903c2..8299727 100644 (file)
@@ -3,284 +3,284 @@ import { ArrayFun} from "@/utils/array";
 
 export const VariantRules = class CrazyhouseRules extends ChessRules
 {
-       static IsGoodFen(fen)
-       {
-               if (!ChessRules.IsGoodFen(fen))
-                       return false;
-               const fenParsed = V.ParseFen(fen);
-               // 5) Check reserves
-               if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
-                       return false;
-               // 6) Check promoted array
-               if (!fenParsed.promoted)
-                       return false;
-               if (fenParsed.promoted == "-")
-                       return true; //no promoted piece on board
-               const squares = fenParsed.promoted.split(",");
-               for (let square of squares)
-               {
-                       const c = V.SquareToCoords(square);
-                       if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
-                               return false;
-               }
-               return true;
-       }
+  static IsGoodFen(fen)
+  {
+    if (!ChessRules.IsGoodFen(fen))
+      return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check reserves
+    if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
+      return false;
+    // 6) Check promoted array
+    if (!fenParsed.promoted)
+      return false;
+    if (fenParsed.promoted == "-")
+      return true; //no promoted piece on board
+    const squares = fenParsed.promoted.split(",");
+    for (let square of squares)
+    {
+      const c = V.SquareToCoords(square);
+      if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
+        return false;
+    }
+    return true;
+  }
 
-       static ParseFen(fen)
-       {
-               const fenParts = fen.split(" ");
-               return Object.assign(
-                       ChessRules.ParseFen(fen),
-                       {
-                               reserve: fenParts[5],
-                               promoted: fenParts[6],
-                       }
-               );
-       }
+  static ParseFen(fen)
+  {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      {
+        reserve: fenParts[5],
+        promoted: fenParts[6],
+      }
+    );
+  }
 
-       static GenRandInitFen()
-       {
-               return ChessRules.GenRandInitFen() + " 0000000000 -";
-       }
+  static GenRandInitFen()
+  {
+    return ChessRules.GenRandInitFen() + " 0000000000 -";
+  }
 
-       getFen()
-       {
-               return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
-       }
+  getFen()
+  {
+    return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
+  }
 
-       getReserveFen()
-       {
-               let counts = new Array(10);
-               for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
-               {
-                       counts[i] = this.reserve["w"][V.PIECES[i]];
-                       counts[5+i] = this.reserve["b"][V.PIECES[i]];
-               }
-               return counts.join("");
-       }
+  getReserveFen()
+  {
+    let counts = new Array(10);
+    for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
+    {
+      counts[i] = this.reserve["w"][V.PIECES[i]];
+      counts[5+i] = this.reserve["b"][V.PIECES[i]];
+    }
+    return counts.join("");
+  }
 
-       getPromotedFen()
-       {
-               let res = "";
-               for (let i=0; i<V.size.x; i++)
-               {
-                       for (let j=0; j<V.size.y; j++)
-                       {
-                               if (this.promoted[i][j])
-                                       res += V.CoordsToSquare({x:i,y:j});
-                       }
-               }
-               if (res.length > 0)
-                       res = res.slice(0,-1); //remove last comma
-               else
-                       res = "-";
-               return res;
-       }
+  getPromotedFen()
+  {
+    let res = "";
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.promoted[i][j])
+          res += V.CoordsToSquare({x:i,y:j});
+      }
+    }
+    if (res.length > 0)
+      res = res.slice(0,-1); //remove last comma
+    else
+      res = "-";
+    return res;
+  }
 
-       setOtherVariables(fen)
-       {
-               super.setOtherVariables(fen);
-               const fenParsed = V.ParseFen(fen);
-               // Also init reserves (used by the interface to show landable pieces)
-               this.reserve =
-               {
-                       "w":
-                       {
-                               [V.PAWN]: parseInt(fenParsed.reserve[0]),
-                               [V.ROOK]: parseInt(fenParsed.reserve[1]),
-                               [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
-                               [V.BISHOP]: parseInt(fenParsed.reserve[3]),
-                               [V.QUEEN]: parseInt(fenParsed.reserve[4]),
-                       },
-                       "b":
-                       {
-                               [V.PAWN]: parseInt(fenParsed.reserve[5]),
-                               [V.ROOK]: parseInt(fenParsed.reserve[6]),
-                               [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
-                               [V.BISHOP]: parseInt(fenParsed.reserve[8]),
-                               [V.QUEEN]: parseInt(fenParsed.reserve[9]),
-                       }
-               };
-               this.promoted = ArrayFun.init(V.size.x, V.size.y, false);
-               if (fenParsed.promoted != "-")
-               {
-                       for (let square of fenParsed.promoted.split(","))
-                       {
-                               const [x,y] = V.SquareToCoords(square);
-                               promoted[x][y] = true;
-                       }
-               }
-       }
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Also init reserves (used by the interface to show landable pieces)
+    this.reserve =
+    {
+      "w":
+      {
+        [V.PAWN]: parseInt(fenParsed.reserve[0]),
+        [V.ROOK]: parseInt(fenParsed.reserve[1]),
+        [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[3]),
+        [V.QUEEN]: parseInt(fenParsed.reserve[4]),
+      },
+      "b":
+      {
+        [V.PAWN]: parseInt(fenParsed.reserve[5]),
+        [V.ROOK]: parseInt(fenParsed.reserve[6]),
+        [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[8]),
+        [V.QUEEN]: parseInt(fenParsed.reserve[9]),
+      }
+    };
+    this.promoted = ArrayFun.init(V.size.x, V.size.y, false);
+    if (fenParsed.promoted != "-")
+    {
+      for (let square of fenParsed.promoted.split(","))
+      {
+        const [x,y] = V.SquareToCoords(square);
+        promoted[x][y] = true;
+      }
+    }
+  }
 
-       getColor(i,j)
-       {
-               if (i >= V.size.x)
-                       return (i==V.size.x ? "w" : "b");
-               return this.board[i][j].charAt(0);
-       }
+  getColor(i,j)
+  {
+    if (i >= V.size.x)
+      return (i==V.size.x ? "w" : "b");
+    return this.board[i][j].charAt(0);
+  }
 
-       getPiece(i,j)
-       {
-               if (i >= V.size.x)
-                       return V.RESERVE_PIECES[j];
-               return this.board[i][j].charAt(1);
-       }
+  getPiece(i,j)
+  {
+    if (i >= V.size.x)
+      return V.RESERVE_PIECES[j];
+    return this.board[i][j].charAt(1);
+  }
 
-       // Used by the interface:
-       getReservePpath(color, index)
-       {
-               return color + V.RESERVE_PIECES[index];
-       }
+  // Used by the interface:
+  getReservePpath(color, index)
+  {
+    return color + V.RESERVE_PIECES[index];
+  }
 
-       // Ordering on reserve pieces
-       static get RESERVE_PIECES()
-       {
-               return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-       }
+  // Ordering on reserve pieces
+  static get RESERVE_PIECES()
+  {
+    return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+  }
 
-       getReserveMoves([x,y])
-       {
-               const color = this.turn;
-               const p = V.RESERVE_PIECES[y];
-               if (this.reserve[color][p] == 0)
-                       return [];
-               let moves = [];
-               const pawnShift = (p==V.PAWN ? 1 : 0);
-               for (let i=pawnShift; i<V.size.x-pawnShift; i++)
-               {
-                       for (let j=0; j<V.size.y; j++)
-                       {
-                               if (this.board[i][j] == V.EMPTY)
-                               {
-                                       let mv = new Move({
-                                               appear: [
-                                                       new PiPo({
-                                                               x: i,
-                                                               y: j,
-                                                               c: color,
-                                                               p: p
-                                                       })
-                                               ],
-                                               vanish: [],
-                                               start: {x:x, y:y}, //a bit artificial...
-                                               end: {x:i, y:j}
-                                       });
-                                       moves.push(mv);
-                               }
-                       }
-               }
-               return moves;
-       }
+  getReserveMoves([x,y])
+  {
+    const color = this.turn;
+    const p = V.RESERVE_PIECES[y];
+    if (this.reserve[color][p] == 0)
+      return [];
+    let moves = [];
+    const pawnShift = (p==V.PAWN ? 1 : 0);
+    for (let i=pawnShift; i<V.size.x-pawnShift; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] == V.EMPTY)
+        {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: p
+              })
+            ],
+            vanish: [],
+            start: {x:x, y:y}, //a bit artificial...
+            end: {x:i, y:j}
+          });
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
 
-       getPotentialMovesFrom([x,y])
-       {
-               if (x >= V.size.x)
-               {
-                       // Reserves, outside of board: x == sizeX(+1)
-                       return this.getReserveMoves([x,y]);
-               }
-               // Standard moves
-               return super.getPotentialMovesFrom([x,y]);
-       }
+  getPotentialMovesFrom([x,y])
+  {
+    if (x >= V.size.x)
+    {
+      // Reserves, outside of board: x == sizeX(+1)
+      return this.getReserveMoves([x,y]);
+    }
+    // Standard moves
+    return super.getPotentialMovesFrom([x,y]);
+  }
 
-       getAllValidMoves()
-       {
-               let moves = super.getAllValidMoves();
-               const color = this.turn;
-               for (let i=0; i<V.RESERVE_PIECES.length; i++)
-                       moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
-               return this.filterValid(moves);
-       }
+  getAllValidMoves()
+  {
+    let moves = super.getAllValidMoves();
+    const color = this.turn;
+    for (let i=0; i<V.RESERVE_PIECES.length; i++)
+      moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
+    return this.filterValid(moves);
+  }
 
-       atLeastOneMove()
-       {
-               if (!super.atLeastOneMove())
-               {
-                       const color = this.turn;
-                       // Search one reserve move
-                       for (let i=0; i<V.RESERVE_PIECES.length; i++)
-                       {
-                               let moves = this.filterValid(
-                                       this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
-                               if (moves.length > 0)
-                                       return true;
-                       }
-                       return false;
-               }
-               return true;
-       }
+  atLeastOneMove()
+  {
+    if (!super.atLeastOneMove())
+    {
+      const color = this.turn;
+      // Search one reserve move
+      for (let i=0; i<V.RESERVE_PIECES.length; i++)
+      {
+        let moves = this.filterValid(
+          this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
+        if (moves.length > 0)
+          return true;
+      }
+      return false;
+    }
+    return true;
+  }
 
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               if (move.vanish.length == 2 && move.appear.length == 2)
-                       return; //skip castle
-               const color = move.appear[0].c;
-               if (move.vanish.length == 0)
-               {
-                       this.reserve[color][move.appear[0].p]--;
-                       return;
-               }
-               move.movePromoted = this.promoted[move.start.x][move.start.y];
-               move.capturePromoted = this.promoted[move.end.x][move.end.y]
-               this.promoted[move.start.x][move.start.y] = false;
-               this.promoted[move.end.x][move.end.y] = move.movePromoted
-                       || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
-               if (move.capturePromoted)
-                       this.reserve[color][V.PAWN]++;
-               else if (move.vanish.length == 2)
-                       this.reserve[color][move.vanish[1].p]++;
-       }
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 2)
+      return; //skip castle
+    const color = move.appear[0].c;
+    if (move.vanish.length == 0)
+    {
+      this.reserve[color][move.appear[0].p]--;
+      return;
+    }
+    move.movePromoted = this.promoted[move.start.x][move.start.y];
+    move.capturePromoted = this.promoted[move.end.x][move.end.y]
+    this.promoted[move.start.x][move.start.y] = false;
+    this.promoted[move.end.x][move.end.y] = move.movePromoted
+      || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
+    if (move.capturePromoted)
+      this.reserve[color][V.PAWN]++;
+    else if (move.vanish.length == 2)
+      this.reserve[color][move.vanish[1].p]++;
+  }
 
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               if (move.vanish.length == 2 && move.appear.length == 2)
-                       return;
-               const color = this.turn;
-               if (move.vanish.length == 0)
-               {
-                       this.reserve[color][move.appear[0].p]++;
-                       return;
-               }
-               if (move.movePromoted)
-                       this.promoted[move.start.x][move.start.y] = true;
-               this.promoted[move.end.x][move.end.y] = move.capturePromoted;
-               if (move.capturePromoted)
-                       this.reserve[color][V.PAWN]--;
-               else if (move.vanish.length == 2)
-                       this.reserve[color][move.vanish[1].p]--;
-       }
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 2)
+      return;
+    const color = this.turn;
+    if (move.vanish.length == 0)
+    {
+      this.reserve[color][move.appear[0].p]++;
+      return;
+    }
+    if (move.movePromoted)
+      this.promoted[move.start.x][move.start.y] = true;
+    this.promoted[move.end.x][move.end.y] = move.capturePromoted;
+    if (move.capturePromoted)
+      this.reserve[color][V.PAWN]--;
+    else if (move.vanish.length == 2)
+      this.reserve[color][move.vanish[1].p]--;
+  }
 
-       static get SEARCH_DEPTH() { return 2; } //high branching factor
+  static get SEARCH_DEPTH() { return 2; } //high branching factor
 
-       evalPosition()
-       {
-               let evaluation = super.evalPosition();
-               // Add reserves:
-               for (let i=0; i<V.RESERVE_PIECES.length; i++)
-               {
-                       const p = V.RESERVE_PIECES[i];
-                       evaluation += this.reserve["w"][p] * V.VALUES[p];
-                       evaluation -= this.reserve["b"][p] * V.VALUES[p];
-               }
-               return evaluation;
-       }
+  evalPosition()
+  {
+    let evaluation = super.evalPosition();
+    // Add reserves:
+    for (let i=0; i<V.RESERVE_PIECES.length; i++)
+    {
+      const p = V.RESERVE_PIECES[i];
+      evaluation += this.reserve["w"][p] * V.VALUES[p];
+      evaluation -= this.reserve["b"][p] * V.VALUES[p];
+    }
+    return evaluation;
+  }
 
-       getNotation(move)
-       {
-               if (move.vanish.length > 0)
-                       return super.getNotation(move);
-               // Rebirth:
-               const piece =
-                       (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
-               return piece + "@" + V.CoordsToSquare(move.end);
-       }
+  getNotation(move)
+  {
+    if (move.vanish.length > 0)
+      return super.getNotation(move);
+    // Rebirth:
+    const piece =
+      (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
+    return piece + "@" + V.CoordsToSquare(move.end);
+  }
 
-       getLongNotation(move)
-       {
-               if (move.vanish.length > 0)
-                       return super.getLongNotation(move);
-               return "@" + V.CoordsToSquare(move.end);
-       }
+  getLongNotation(move)
+  {
+    if (move.vanish.length > 0)
+      return super.getLongNotation(move);
+    return "@" + V.CoordsToSquare(move.end);
+  }
 }
index bc14cfa..c0bfb09 100644 (file)
@@ -4,295 +4,295 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class DarkRules extends ChessRules
 {
-       // Standard rules, in the shadow
-       setOtherVariables(fen)
-       {
-               super.setOtherVariables(fen);
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               this.enlightened = {
-                       "w": ArrayFun.init(sizeX,sizeY),
-                       "b": ArrayFun.init(sizeX,sizeY)
-               };
-               // Setup enlightened: squares reachable by each side
-               // (TODO: one side would be enough ?)
-               this.updateEnlightened();
-       }
+  // Standard rules, in the shadow
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    this.enlightened = {
+      "w": ArrayFun.init(sizeX,sizeY),
+      "b": ArrayFun.init(sizeX,sizeY)
+    };
+    // Setup enlightened: squares reachable by each side
+    // (TODO: one side would be enough ?)
+    this.updateEnlightened();
+  }
 
-       updateEnlightened()
-       {
-               for (let i=0; i<V.size.x; i++)
-               {
-                       for (let j=0; j<V.size.y; j++)
-                       {
-                               this.enlightened["w"][i][j] = false;
-                               this.enlightened["b"][i][j] = false;
-                       }
-               }
-               const pawnShift = {"w":-1, "b":1};
-               // Initialize with pieces positions (which are seen)
-               for (let i=0; i<V.size.x; i++)
-               {
-                       for (let j=0; j<V.size.y; j++)
-                       {
-                               if (this.board[i][j] != V.EMPTY)
-                               {
-                                       const color = this.getColor(i,j);
-                                       this.enlightened[color][i][j] = true;
-                                       // Add potential squares visible by "impossible pawn capture"
-                                       if (this.getPiece(i,j) == V.PAWN)
-                                       {
-                                               for (let shiftY of [-1,1])
-                                               {
-                                                       if (V.OnBoard(i+pawnShift[color],j+shiftY)
-                                                               && this.board[i+pawnShift[color]][j+shiftY] == V.EMPTY)
-                                                       {
-                                                               this.enlightened[color][i+pawnShift[color]][j+shiftY] = true;
-                                                       }
-                                               }
-                                       }
-                               }
-                       }
-               }
-               const currentTurn = this.turn;
-               this.turn = "w";
-               const movesWhite = this.getAllValidMoves();
-               this.turn = "b";
-               const movesBlack = this.getAllValidMoves();
-               this.turn = currentTurn;
-               for (let move of movesWhite)
-                       this.enlightened["w"][move.end.x][move.end.y] = true;
-               for (let move of movesBlack)
-                       this.enlightened["b"][move.end.x][move.end.y] = true;
-       }
+  updateEnlightened()
+  {
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        this.enlightened["w"][i][j] = false;
+        this.enlightened["b"][i][j] = false;
+      }
+    }
+    const pawnShift = {"w":-1, "b":1};
+    // Initialize with pieces positions (which are seen)
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY)
+        {
+          const color = this.getColor(i,j);
+          this.enlightened[color][i][j] = true;
+          // Add potential squares visible by "impossible pawn capture"
+          if (this.getPiece(i,j) == V.PAWN)
+          {
+            for (let shiftY of [-1,1])
+            {
+              if (V.OnBoard(i+pawnShift[color],j+shiftY)
+                && this.board[i+pawnShift[color]][j+shiftY] == V.EMPTY)
+              {
+                this.enlightened[color][i+pawnShift[color]][j+shiftY] = true;
+              }
+            }
+          }
+        }
+      }
+    }
+    const currentTurn = this.turn;
+    this.turn = "w";
+    const movesWhite = this.getAllValidMoves();
+    this.turn = "b";
+    const movesBlack = this.getAllValidMoves();
+    this.turn = currentTurn;
+    for (let move of movesWhite)
+      this.enlightened["w"][move.end.x][move.end.y] = true;
+    for (let move of movesBlack)
+      this.enlightened["b"][move.end.x][move.end.y] = true;
+  }
 
-       // Has to be redefined to avoid an infinite loop
-       getAllValidMoves()
-       {
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               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]));
-                       }
-               }
-               return potentialMoves; //because there are no checks
-       }
+  // Has to be redefined to avoid an infinite loop
+  getAllValidMoves()
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    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]));
+      }
+    }
+    return potentialMoves; //because there are no checks
+  }
 
-       atLeastOneMove()
-       {
-               if (this.kingPos[this.turn][0] < 0)
-                       return false;
-               return true; //TODO: is it right?
-       }
+  atLeastOneMove()
+  {
+    if (this.kingPos[this.turn][0] < 0)
+      return false;
+    return true; //TODO: is it right?
+  }
 
-       underCheck(color)
-       {
-               return false; //there is no check
-       }
+  underCheck(color)
+  {
+    return false; //there is no check
+  }
 
-       getCheckSquares(color)
-       {
-               return [];
-       }
+  getCheckSquares(color)
+  {
+    return [];
+  }
 
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
-               {
-                       // We took opponent king ! (because if castle vanish[1] is a rook)
-                       this.kingPos[this.turn] = [-1,-1];
-               }
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
+    {
+      // We took opponent king ! (because if castle vanish[1] is a rook)
+      this.kingPos[this.turn] = [-1,-1];
+    }
 
-               // Update lights for both colors:
-               this.updateEnlightened();
-       }
+    // Update lights for both colors:
+    this.updateEnlightened();
+  }
 
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               const c = move.vanish[0].c;
-               const oppCol = V.GetOppCol(c);
-               if (this.kingPos[oppCol][0] < 0)
-               {
-                       // Last move took opponent's king
-                       for (let psq of move.vanish)
-                       {
-                               if (psq.p == 'k')
-                               {
-                                       this.kingPos[oppCol] = [psq.x, psq.y];
-                                       break;
-                               }
-                       }
-               }
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if (this.kingPos[oppCol][0] < 0)
+    {
+      // Last move took opponent's king
+      for (let psq of move.vanish)
+      {
+        if (psq.p == 'k')
+        {
+          this.kingPos[oppCol] = [psq.x, psq.y];
+          break;
+        }
+      }
+    }
 
-               // Update lights for both colors:
-               this.updateEnlightened();
-       }
+    // Update lights for both colors:
+    this.updateEnlightened();
+  }
 
   getCurrentScore()
   {
-               const color = this.turn;
-               const kp = this.kingPos[color];
-               if (kp[0] < 0) //king disappeared
-                       return (color == "w" ? "0-1" : "1-0");
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0) //king disappeared
+      return (color == "w" ? "0-1" : "1-0");
     if (this.atLeastOneMove()) // game not over
       return "*";
     return "1/2"; //no moves but kings still there (seems impossible)
-       }
+  }
 
-       static get THRESHOLD_MATE()
-       {
-               return 500; //checkmates evals may be slightly below 1000
-       }
+  static get THRESHOLD_MATE()
+  {
+    return 500; //checkmates evals may be slightly below 1000
+  }
 
-       // In this special situation, we just look 1 half move ahead
-       getComputerMove()
-       {
-               const maxeval = V.INFINITY;
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               const pawnShift = (color == "w" ? -1 : 1);
+  // In this special situation, we just look 1 half move ahead
+  getComputerMove()
+  {
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const pawnShift = (color == "w" ? -1 : 1);
 
-               // Do not cheat: the current enlightment is all we can see
-               const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
+    // Do not cheat: the current enlightment is all we can see
+    const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
 
-               // Can a slider on (i,j) apparently take my king?
-               // NOTE: inaccurate because assume yes if some squares are shadowed
-               const sliderTake = ([i,j], piece) => {
-                 const kp = this.kingPos[color];
-                       let step = undefined;
-                       if (piece == V.BISHOP)
-                       {
-                               if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j))
-                               {
-                                       step =
-                                       [
-                                               (i-kp[0]) / Math.abs(i-kp[0]),
-                                               (j-kp[1]) / Math.abs(j-kp[1])
-                                       ];
-                               }
-                       }
-                       else if (piece == V.ROOK)
-                       {
-                               if (kp[0] == i)
-                                       step = [0, (j-kp[1]) / Math.abs(j-kp[1])];
-                               else if (kp[1] == j)
-                                       step = [(i-kp[0]) / Math.abs(i-kp[0]), 0];
-                       }
-                       if (!step)
-                               return false;
-                       // Check for obstacles
-                       let obstacle = false;
-                       for (
+    // Can a slider on (i,j) apparently take my king?
+    // NOTE: inaccurate because assume yes if some squares are shadowed
+    const sliderTake = ([i,j], piece) => {
+      const kp = this.kingPos[color];
+      let step = undefined;
+      if (piece == V.BISHOP)
+      {
+        if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j))
+        {
+          step =
+          [
+            (i-kp[0]) / Math.abs(i-kp[0]),
+            (j-kp[1]) / Math.abs(j-kp[1])
+          ];
+        }
+      }
+      else if (piece == V.ROOK)
+      {
+        if (kp[0] == i)
+          step = [0, (j-kp[1]) / Math.abs(j-kp[1])];
+        else if (kp[1] == j)
+          step = [(i-kp[0]) / Math.abs(i-kp[0]), 0];
+      }
+      if (!step)
+        return false;
+      // Check for obstacles
+      let obstacle = false;
+      for (
         let x=kp[0]+step[0], y=kp[1]+step[1];
-                         x != i && y != j;
-                               x += step[0], y += step[1])
+        x != i && y != j;
+        x += step[0], y += step[1])
       {
-                               if (myLight[x][y] && this.board[x][y] != V.EMPTY)
-                               {
-                                       obstacle = true;
-                                       break;
-                               }
-                       }
-                       if (!obstacle)
-                               return true;
-                       return false;
-               };
+        if (myLight[x][y] && this.board[x][y] != V.EMPTY)
+        {
+          obstacle = true;
+          break;
+        }
+      }
+      if (!obstacle)
+        return true;
+      return false;
+    };
 
-               // Do I see something which can take my king ?
-               const kingThreats = () => {
-                 const kp = this.kingPos[color];
-                       for (let i=0; i<V.size.x; i++)
-                       {
-                               for (let j=0; j<V.size.y; j++)
-                               {
-                                       if (myLight[i][j] && this.board[i][j] != V.EMPTY
-                                               && this.getColor(i,j) != color)
-                                       {
-                                               switch (this.getPiece(i,j))
-                                               {
-                                                       case V.PAWN:
-                                                               if (kp[0] + pawnShift == i && Math.abs(kp[1]-j) == 1)
-                                                                       return true;
-                                                               break;
-                                                       case V.KNIGHT:
-                                                               if ((Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
-                                                                       (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2))
-                                                               {
-                                                                       return true;
-                                                               }
-                                                               break;
-                                                       case V.KING:
-                                                               if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
-                                                                       return true;
-                                                               break;
-                                                       case V.BISHOP:
-                                                               if (sliderTake([i,j], V.BISHOP))
-                                                                       return true;
-                                                               break;
-                                                       case V.ROOK:
-                                                               if (sliderTake([i,j], V.ROOK))
-                                                                       return true;
-                                                               break;
-                                                       case V.QUEEN:
-                                                               if (sliderTake([i,j], V.BISHOP) || sliderTake([i,j], V.ROOK))
-                                                                       return true;
-                                                               break;
-                                               }
-                                       }
-                               }
-                       }
-                       return false;
-               };
+    // Do I see something which can take my king ?
+    const kingThreats = () => {
+      const kp = this.kingPos[color];
+      for (let i=0; i<V.size.x; i++)
+      {
+        for (let j=0; j<V.size.y; j++)
+        {
+          if (myLight[i][j] && this.board[i][j] != V.EMPTY
+            && this.getColor(i,j) != color)
+          {
+            switch (this.getPiece(i,j))
+            {
+              case V.PAWN:
+                if (kp[0] + pawnShift == i && Math.abs(kp[1]-j) == 1)
+                  return true;
+                break;
+              case V.KNIGHT:
+                if ((Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
+                  (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2))
+                {
+                  return true;
+                }
+                break;
+              case V.KING:
+                if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
+                  return true;
+                break;
+              case V.BISHOP:
+                if (sliderTake([i,j], V.BISHOP))
+                  return true;
+                break;
+              case V.ROOK:
+                if (sliderTake([i,j], V.ROOK))
+                  return true;
+                break;
+              case V.QUEEN:
+                if (sliderTake([i,j], V.BISHOP) || sliderTake([i,j], V.ROOK))
+                  return true;
+                break;
+            }
+          }
+        }
+      }
+      return false;
+    };
 
-               let moves = this.getAllValidMoves();
-               for (let move of moves)
-               {
-                       this.play(move);
-                       if (this.kingPos[oppCol][0] >= 0 && kingThreats())
-                       {
-                               // We didn't take opponent king, and our king will be captured: bad
-                               move.eval = -maxeval;
-                       }
-                       this.undo(move);
+    let moves = this.getAllValidMoves();
+    for (let move of moves)
+    {
+      this.play(move);
+      if (this.kingPos[oppCol][0] >= 0 && kingThreats())
+      {
+        // We didn't take opponent king, and our king will be captured: bad
+        move.eval = -maxeval;
+      }
+      this.undo(move);
 
       if (!!move.eval)
-                               continue;
+        continue;
 
-                       move.eval = 0; //a priori...
+      move.eval = 0; //a priori...
 
-                       // Can I take something ? If yes, do it if it seems good...
-                       if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle
-                       {
-                               const myPieceVal = V.VALUES[move.appear[0].p];
-                               const hisPieceVal = V.VALUES[move.vanish[1].p];
-                               if (myPieceVal <= hisPieceVal)
-                                       move.eval = hisPieceVal - myPieceVal + 2; //favor captures
-                               else
-                               {
-                                       // Taking a pawn with minor piece,
-                                       // or minor piece or pawn with a rook,
-                                       // or anything but a queen with a queen,
-                                       // or anything with a king.
-                                       // ==> Do it at random, although
-                                       //     this is clearly inferior to what a human can deduce...
-                                       move.eval = (Math.random() < 0.5 ? 1 : -1);
-                               }
-                       }
-               }
+      // Can I take something ? If yes, do it if it seems good...
+      if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle
+      {
+        const myPieceVal = V.VALUES[move.appear[0].p];
+        const hisPieceVal = V.VALUES[move.vanish[1].p];
+        if (myPieceVal <= hisPieceVal)
+          move.eval = hisPieceVal - myPieceVal + 2; //favor captures
+        else
+        {
+          // Taking a pawn with minor piece,
+          // or minor piece or pawn with a rook,
+          // or anything but a queen with a queen,
+          // or anything with a king.
+          // ==> Do it at random, although
+          //     this is clearly inferior to what a human can deduce...
+          move.eval = (Math.random() < 0.5 ? 1 : -1);
+        }
+      }
+    }
 
-               // TODO: also need to implement the case when an opponent piece (in light)
-               // is threatening something - maybe not the king, but e.g. pawn takes rook.
+    // TODO: also need to implement the case when an opponent piece (in light)
+    // is threatening something - maybe not the king, but e.g. pawn takes rook.
 
-               moves.sort((a,b) => b.eval - a.eval);
-               let candidates = [0];
-               for (let j=1; j<moves.length && moves[j].eval == moves[0].eval; j++)
-                       candidates.push(j);
-               return moves[candidates[randInt(candidates.length)]];
-       }
+    moves.sort((a,b) => b.eval - a.eval);
+    let candidates = [0];
+    for (let j=1; j<moves.length && moves[j].eval == moves[0].eval; j++)
+      candidates.push(j);
+    return moves[candidates[randInt(candidates.length)]];
+  }
 }
index 748f9e4..8d860ae 100644 (file)
@@ -2,130 +2,130 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class  ExtinctionRules extends ChessRules
 {
-       setOtherVariables(fen)
-       {
-               super.setOtherVariables(fen);
-               const pos = V.ParseFen(fen).position;
-               // NOTE: no need for safety "|| []", because each piece type must be present
-               // (otherwise game is already over!)
-               this.material =
-               {
-                       "w":
-                       {
-                               [V.KING]: pos.match(/K/g).length,
-                               [V.QUEEN]: pos.match(/Q/g).length,
-                               [V.ROOK]: pos.match(/R/g).length,
-                               [V.KNIGHT]: pos.match(/N/g).length,
-                               [V.BISHOP]: pos.match(/B/g).length,
-                               [V.PAWN]: pos.match(/P/g).length
-                       },
-                       "b":
-                       {
-                               [V.KING]: pos.match(/k/g).length,
-                               [V.QUEEN]: pos.match(/q/g).length,
-                               [V.ROOK]: pos.match(/r/g).length,
-                               [V.KNIGHT]: pos.match(/n/g).length,
-                               [V.BISHOP]: pos.match(/b/g).length,
-                               [V.PAWN]: pos.match(/p/g).length
-                       }
-               };
-       }
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const pos = V.ParseFen(fen).position;
+    // NOTE: no need for safety "|| []", because each piece type must be present
+    // (otherwise game is already over!)
+    this.material =
+    {
+      "w":
+      {
+        [V.KING]: pos.match(/K/g).length,
+        [V.QUEEN]: pos.match(/Q/g).length,
+        [V.ROOK]: pos.match(/R/g).length,
+        [V.KNIGHT]: pos.match(/N/g).length,
+        [V.BISHOP]: pos.match(/B/g).length,
+        [V.PAWN]: pos.match(/P/g).length
+      },
+      "b":
+      {
+        [V.KING]: pos.match(/k/g).length,
+        [V.QUEEN]: pos.match(/q/g).length,
+        [V.ROOK]: pos.match(/r/g).length,
+        [V.KNIGHT]: pos.match(/n/g).length,
+        [V.BISHOP]: pos.match(/b/g).length,
+        [V.PAWN]: pos.match(/p/g).length
+      }
+    };
+  }
 
-       getPotentialPawnMoves([x,y])
-       {
-               let moves = super.getPotentialPawnMoves([x,y]);
-               // Add potential promotions into king
-               const color = this.turn;
-               const shift = (color == "w" ? -1 : 1);
-               const lastRank = (color == "w" ? 0 : V.size.x-1);
+  getPotentialPawnMoves([x,y])
+  {
+    let moves = super.getPotentialPawnMoves([x,y]);
+    // Add potential promotions into king
+    const color = this.turn;
+    const shift = (color == "w" ? -1 : 1);
+    const lastRank = (color == "w" ? 0 : V.size.x-1);
 
-               if (x+shift == lastRank)
-               {
-                       // Normal move
-                       if (this.board[x+shift][y] == V.EMPTY)
-                               moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
-                       // Captures
-                       if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-                               && this.canTake([x,y], [x+shift,y-1]))
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
-                       }
-                       if (y<V.size.y-1 && this.board[x+shift][y+1] != V.EMPTY
-                               && this.canTake([x,y], [x+shift,y+1]))
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
-                       }
-               }
+    if (x+shift == lastRank)
+    {
+      // Normal move
+      if (this.board[x+shift][y] == V.EMPTY)
+        moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+      // Captures
+      if (y>0 && this.board[x+shift][y-1] != V.EMPTY
+        && this.canTake([x,y], [x+shift,y-1]))
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
+      }
+      if (y<V.size.y-1 && this.board[x+shift][y+1] != V.EMPTY
+        && this.canTake([x,y], [x+shift,y+1]))
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
+      }
+    }
 
-               return moves;
-       }
+    return moves;
+  }
 
-       // TODO: verify this assertion
-       atLeastOneMove()
-       {
-               return true; //always at least one possible move
-       }
+  // TODO: verify this assertion
+  atLeastOneMove()
+  {
+    return true; //always at least one possible move
+  }
 
-       underCheck(color)
-       {
-               return false; //there is no check
-       }
+  underCheck(color)
+  {
+    return false; //there is no check
+  }
 
-       getCheckSquares(color)
-       {
-               return [];
-       }
+  getCheckSquares(color)
+  {
+    return [];
+  }
 
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               // Treat the promotion case: (not the capture part)
-               if (move.appear[0].p != move.vanish[0].p)
-               {
-                       this.material[move.appear[0].c][move.appear[0].p]++;
-                       this.material[move.appear[0].c][V.PAWN]--;
-               }
-               if (move.vanish.length==2 && move.appear.length==1) //capture
-                       this.material[move.vanish[1].c][move.vanish[1].p]--;
-       }
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    // Treat the promotion case: (not the capture part)
+    if (move.appear[0].p != move.vanish[0].p)
+    {
+      this.material[move.appear[0].c][move.appear[0].p]++;
+      this.material[move.appear[0].c][V.PAWN]--;
+    }
+    if (move.vanish.length==2 && move.appear.length==1) //capture
+      this.material[move.vanish[1].c][move.vanish[1].p]--;
+  }
 
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               if (move.appear[0].p != move.vanish[0].p)
-               {
-                       this.material[move.appear[0].c][move.appear[0].p]--;
-                       this.material[move.appear[0].c][V.PAWN]++;
-               }
-               if (move.vanish.length==2 && move.appear.length==1)
-                       this.material[move.vanish[1].c][move.vanish[1].p]++;
-       }
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    if (move.appear[0].p != move.vanish[0].p)
+    {
+      this.material[move.appear[0].c][move.appear[0].p]--;
+      this.material[move.appear[0].c][V.PAWN]++;
+    }
+    if (move.vanish.length==2 && move.appear.length==1)
+      this.material[move.vanish[1].c][move.vanish[1].p]++;
+  }
 
-       getCurrentScore()
-       {
-               if (this.atLeastOneMove()) // game not over?
-               {
-                       const color = this.turn;
-                       if (Object.keys(this.material[color]).some(
-                               p => { return this.material[color][p] == 0; }))
-                       {
-                               return (this.turn == "w" ? "0-1" : "1-0");
-                       }
-                       return "*";
-               }
+  getCurrentScore()
+  {
+    if (this.atLeastOneMove()) // game not over?
+    {
+      const color = this.turn;
+      if (Object.keys(this.material[color]).some(
+        p => { return this.material[color][p] == 0; }))
+      {
+        return (this.turn == "w" ? "0-1" : "1-0");
+      }
+      return "*";
+    }
 
-               return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable...
-       }
+    return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable...
+  }
 
-       evalPosition()
-       {
-               const color = this.turn;
-               if (Object.keys(this.material[color]).some(
-                       p => { return this.material[color][p] == 0; }))
-               {
-                       // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
-                       return (color=="w"?-1:1) * V.INFINITY;
-               }
-               return super.evalPosition();
-       }
+  evalPosition()
+  {
+    const color = this.turn;
+    if (Object.keys(this.material[color]).some(
+      p => { return this.material[color][p] == 0; }))
+    {
+      // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
+      return (color=="w"?-1:1) * V.INFINITY;
+    }
+    return super.evalPosition();
+  }
 }
index 6ddfbd7..a53f59e 100644 (file)
@@ -6,388 +6,388 @@ import { randInt } from "@/utils/alea";
 // https://www.chessvariants.com/large.dir/freeling.html
 export const VariantRules = class GrandRules extends ChessRules
 {
-       static getPpath(b)
-       {
-               return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
-       }
-
-       static IsGoodFen(fen)
-       {
-               if (!ChessRules.IsGoodFen(fen))
-                       return false;
-               const fenParsed = V.ParseFen(fen);
-               // 5) Check captures
-               if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
-                       return false;
-               return true;
-       }
-
-       static IsGoodEnpassant(enpassant)
-       {
-               if (enpassant != "-")
-               {
-                       const squares = enpassant.split(",");
-                       if (squares.length > 2)
-                               return false;
-                       for (let sq of squares)
-                       {
-                               const ep = V.SquareToCoords(sq);
-                               if (isNaN(ep.x) || !V.OnBoard(ep))
-                                       return false;
-                       }
-               }
-               return true;
-       }
-
-       static ParseFen(fen)
-       {
-               const fenParts = fen.split(" ");
-               return Object.assign(
-                       ChessRules.ParseFen(fen),
-                       { captured: fenParts[5] }
-               );
-       }
-
-       getFen()
-       {
-               return super.getFen() + " " + this.getCapturedFen();
-       }
-
-       getCapturedFen()
-       {
-               let counts = [...Array(14).fill(0)];
-               let i = 0;
-               for (let j=0; j<V.PIECES.length; j++)
-               {
-                       if (V.PIECES[j] == V.KING) //no king captured
-                               continue;
-                       counts[i] = this.captured["w"][V.PIECES[i]];
-                       counts[7+i] = this.captured["b"][V.PIECES[i]];
-                       i++;
-               }
-               return counts.join("");
-       }
-
-       setOtherVariables(fen)
-       {
-               super.setOtherVariables(fen);
-               const fenParsed = V.ParseFen(fen);
-               // Initialize captured pieces' counts from FEN
-               this.captured =
-               {
-                       "w":
-                       {
-                               [V.PAWN]: parseInt(fenParsed.captured[0]),
-                               [V.ROOK]: parseInt(fenParsed.captured[1]),
-                               [V.KNIGHT]: parseInt(fenParsed.captured[2]),
-                               [V.BISHOP]: parseInt(fenParsed.captured[3]),
-                               [V.QUEEN]: parseInt(fenParsed.captured[4]),
-                               [V.MARSHALL]: parseInt(fenParsed.captured[5]),
-                               [V.CARDINAL]: parseInt(fenParsed.captured[6]),
-                       },
-                       "b":
-                       {
-                               [V.PAWN]: parseInt(fenParsed.captured[7]),
-                               [V.ROOK]: parseInt(fenParsed.captured[8]),
-                               [V.KNIGHT]: parseInt(fenParsed.captured[9]),
-                               [V.BISHOP]: parseInt(fenParsed.captured[10]),
-                               [V.QUEEN]: parseInt(fenParsed.captured[11]),
-                               [V.MARSHALL]: parseInt(fenParsed.captured[12]),
-                               [V.CARDINAL]: parseInt(fenParsed.captured[13]),
-                       }
-               };
-       }
-
-       static get size() { return {x:10,y:10}; }
-
-       static get MARSHALL() { return 'm'; } //rook+knight
-       static get CARDINAL() { return 'c'; } //bishop+knight
-
-       static get PIECES()
-       {
-               return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
-       }
-
-       // There may be 2 enPassant squares (if pawn jump 3 squares)
-       getEnpassantFen()
-       {
-               const L = this.epSquares.length;
-               if (!this.epSquares[L-1])
-                       return "-"; //no en-passant
-               let res = "";
-               this.epSquares[L-1].forEach(sq => {
-                       res += V.CoordsToSquare(sq) + ",";
-               });
-               return res.slice(0,-1); //remove last comma
-       }
-
-       // En-passant after 2-sq or 3-sq jumps
-       getEpSquare(moveOrSquare)
-       {
-               if (!moveOrSquare)
-                       return undefined;
-               if (typeof moveOrSquare === "string")
-               {
-                       const square = moveOrSquare;
-                       if (square == "-")
-                               return undefined;
-                       let res = [];
-                       square.split(",").forEach(sq => {
-                               res.push(V.SquareToCoords(sq));
-                       });
-                       return res;
-               }
-               // Argument is a move:
-               const move = moveOrSquare;
-               const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
-               if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
-               {
-                       const step = (ex-sx) / Math.abs(ex-sx);
-                       let res = [{
-                               x: sx + step,
-                               y: sy
-                       }];
-                       if (sx + 2*step != ex) //3-squares move
-                       {
-                               res.push({
-                                       x: sx + 2*step,
-                                       y: sy
-                               });
-                       }
-                       return res;
-               }
-               return undefined; //default
-       }
-
-       getPotentialMovesFrom([x,y])
-       {
-               switch (this.getPiece(x,y))
-               {
-                       case V.MARSHALL:
-                               return this.getPotentialMarshallMoves([x,y]);
-                       case V.CARDINAL:
-                               return this.getPotentialCardinalMoves([x,y]);
-                       default:
-                               return super.getPotentialMovesFrom([x,y])
-               }
-       }
-
-       // Special pawn rules: promotions to captured friendly pieces,
-       // optional on ranks 8-9 and mandatory on rank 10.
-       getPotentialPawnMoves([x,y])
-       {
-               const color = this.turn;
-               let moves = [];
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               const shiftX = (color == "w" ? -1 : 1);
-               const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
-               const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
-               const promotionPieces =
-                       [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
-
-               // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
-               let finalPieces = undefined;
-               if (lastRanks.includes(x + shiftX))
-               {
-                       finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
-                       if (x + shiftX != lastRanks[0])
-                               finalPieces.push(V.PAWN);
-               }
-               else
-                       finalPieces = [V.PAWN];
-               if (this.board[x+shiftX][y] == V.EMPTY)
-               {
-                       // One square forward
-                       for (let piece of finalPieces)
-                               moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
-                       if (startRanks.includes(x))
-                       {
-                               if (this.board[x+2*shiftX][y] == V.EMPTY)
-                               {
-                                       // Two squares jump
-                                       moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
-                                       if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
-                                       {
-                                               // Three squares jump
-                                               moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
-                                       }
-                               }
-                       }
-               }
-               // Captures
-               for (let shiftY of [-1,1])
-               {
-                       if (y + shiftY >= 0 && y + shiftY < sizeY
-                               && this.board[x+shiftX][y+shiftY] != V.EMPTY
-                               && this.canTake([x,y], [x+shiftX,y+shiftY]))
-                       {
-                               for (let piece of finalPieces)
-                               {
-                                       moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-                                               {c:color,p:piece}));
-                               }
-                       }
-               }
-
-               // En passant
-               const Lep = this.epSquares.length;
-               const epSquare = this.epSquares[Lep-1];
-               if (!!epSquare)
-               {
-                       for (let epsq of epSquare)
-                       {
-                               // TODO: some redundant checks
-                               if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
-                               {
-                                       var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
-                                       // WARNING: the captured pawn may be diagonally behind us,
-                                       // if it's a 3-squares jump and we take on 1st passing square
-                                       const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
-                                       enpassantMove.vanish.push({
-                                               x: px,
-                                               y: epsq.y,
-                                               p: 'p',
-                                               c: this.getColor(px,epsq.y)
-                                       });
-                                       moves.push(enpassantMove);
-                               }
-                       }
-               }
-
-               return moves;
-       }
-
-       // TODO: different castle?
-
-       getPotentialMarshallMoves(sq)
-       {
-               return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
-                       this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
-       }
-
-       getPotentialCardinalMoves(sq)
-       {
-               return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
-                       this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
-       }
-
-       isAttacked(sq, colors)
-       {
-               return super.isAttacked(sq, colors)
-                       || this.isAttackedByMarshall(sq, colors)
-                       || this.isAttackedByCardinal(sq, colors);
-       }
-
-       isAttackedByMarshall(sq, colors)
-       {
-               return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
-                       || this.isAttackedBySlideNJump(
-                               sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
-       }
-
-       isAttackedByCardinal(sq, colors)
-       {
-               return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
-                       || this.isAttackedBySlideNJump(
-                               sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
-       }
-
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               if (move.vanish.length == 2 && move.appear.length == 1)
-               {
-                       // Capture: update this.captured
-                       this.captured[move.vanish[1].c][move.vanish[1].p]++;
-               }
-               if (move.vanish[0].p != move.appear[0].p)
-               {
-                       // Promotion: update this.captured
-                       this.captured[move.vanish[0].c][move.appear[0].p]--;
-               }
-       }
-
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               if (move.vanish.length == 2 && move.appear.length == 1)
-                       this.captured[move.vanish[1].c][move.vanish[1].p]--;
-               if (move.vanish[0].p != move.appear[0].p)
-                       this.captured[move.vanish[0].c][move.appear[0].p]++;
-       }
-
-       static get VALUES()
-       {
-               return Object.assign(
-                       ChessRules.VALUES,
-                       {'c': 5, 'm': 7} //experimental
-               );
-       }
-
-       static get SEARCH_DEPTH() { return 2; }
-
-       // TODO: this function could be generalized and shared better (how ?!...)
-       static GenRandInitFen()
-       {
-               let pieces = { "w": new Array(10), "b": new Array(10) };
-               // Shuffle pieces on first and last rank
-               for (let c of ["w","b"])
-               {
-                       let positions = ArrayFun.range(10);
-
-                       // Get random squares for bishops
-                       let randIndex = 2 * randInt(5);
-                       let bishop1Pos = positions[randIndex];
-                       // The second bishop must be on a square of different color
-                       let randIndex_tmp = 2 * randInt(5) + 1;
-                       let bishop2Pos = positions[randIndex_tmp];
-                       // Remove chosen squares
-                       positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-                       positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
-                       // Get random squares for knights
-                       randIndex = randInt(8);
-                       let knight1Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-                       randIndex = randInt(7);
-                       let knight2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // Get random square for queen
-                       randIndex = randInt(6);
-                       let queenPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // ...random square for marshall
-                       randIndex = randInt(5);
-                       let marshallPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // ...random square for cardinal
-                       randIndex = randInt(4);
-                       let cardinalPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // Rooks and king positions are now fixed, because of the ordering rook-king-rook
-                       let rook1Pos = positions[0];
-                       let kingPos = positions[1];
-                       let rook2Pos = positions[2];
-
-                       // Finally put the shuffled pieces in the board array
-                       pieces[c][rook1Pos] = 'r';
-                       pieces[c][knight1Pos] = 'n';
-                       pieces[c][bishop1Pos] = 'b';
-                       pieces[c][queenPos] = 'q';
-                       pieces[c][marshallPos] = 'm';
-                       pieces[c][cardinalPos] = 'c';
-                       pieces[c][kingPos] = 'k';
-                       pieces[c][bishop2Pos] = 'b';
-                       pieces[c][knight2Pos] = 'n';
-                       pieces[c][rook2Pos] = 'r';
-               }
-               return pieces["b"].join("") +
-                       "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
-                       pieces["w"].join("").toUpperCase() +
-                       " w 0 1111 - 00000000000000";
-       }
+  static getPpath(b)
+  {
+    return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
+  }
+
+  static IsGoodFen(fen)
+  {
+    if (!ChessRules.IsGoodFen(fen))
+      return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check captures
+    if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
+      return false;
+    return true;
+  }
+
+  static IsGoodEnpassant(enpassant)
+  {
+    if (enpassant != "-")
+    {
+      const squares = enpassant.split(",");
+      if (squares.length > 2)
+        return false;
+      for (let sq of squares)
+      {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep))
+          return false;
+      }
+    }
+    return true;
+  }
+
+  static ParseFen(fen)
+  {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { captured: fenParts[5] }
+    );
+  }
+
+  getFen()
+  {
+    return super.getFen() + " " + this.getCapturedFen();
+  }
+
+  getCapturedFen()
+  {
+    let counts = [...Array(14).fill(0)];
+    let i = 0;
+    for (let j=0; j<V.PIECES.length; j++)
+    {
+      if (V.PIECES[j] == V.KING) //no king captured
+        continue;
+      counts[i] = this.captured["w"][V.PIECES[i]];
+      counts[7+i] = this.captured["b"][V.PIECES[i]];
+      i++;
+    }
+    return counts.join("");
+  }
+
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Initialize captured pieces' counts from FEN
+    this.captured =
+    {
+      "w":
+      {
+        [V.PAWN]: parseInt(fenParsed.captured[0]),
+        [V.ROOK]: parseInt(fenParsed.captured[1]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[2]),
+        [V.BISHOP]: parseInt(fenParsed.captured[3]),
+        [V.QUEEN]: parseInt(fenParsed.captured[4]),
+        [V.MARSHALL]: parseInt(fenParsed.captured[5]),
+        [V.CARDINAL]: parseInt(fenParsed.captured[6]),
+      },
+      "b":
+      {
+        [V.PAWN]: parseInt(fenParsed.captured[7]),
+        [V.ROOK]: parseInt(fenParsed.captured[8]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[9]),
+        [V.BISHOP]: parseInt(fenParsed.captured[10]),
+        [V.QUEEN]: parseInt(fenParsed.captured[11]),
+        [V.MARSHALL]: parseInt(fenParsed.captured[12]),
+        [V.CARDINAL]: parseInt(fenParsed.captured[13]),
+      }
+    };
+  }
+
+  static get size() { return {x:10,y:10}; }
+
+  static get MARSHALL() { return 'm'; } //rook+knight
+  static get CARDINAL() { return 'c'; } //bishop+knight
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
+  }
+
+  // There may be 2 enPassant squares (if pawn jump 3 squares)
+  getEnpassantFen()
+  {
+    const L = this.epSquares.length;
+    if (!this.epSquares[L-1])
+      return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L-1].forEach(sq => {
+      res += V.CoordsToSquare(sq) + ",";
+    });
+    return res.slice(0,-1); //remove last comma
+  }
+
+  // En-passant after 2-sq or 3-sq jumps
+  getEpSquare(moveOrSquare)
+  {
+    if (!moveOrSquare)
+      return undefined;
+    if (typeof moveOrSquare === "string")
+    {
+      const square = moveOrSquare;
+      if (square == "-")
+        return undefined;
+      let res = [];
+      square.split(",").forEach(sq => {
+        res.push(V.SquareToCoords(sq));
+      });
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
+    if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
+    {
+      const step = (ex-sx) / Math.abs(ex-sx);
+      let res = [{
+        x: sx + step,
+        y: sy
+      }];
+      if (sx + 2*step != ex) //3-squares move
+      {
+        res.push({
+          x: sx + 2*step,
+          y: sy
+        });
+      }
+      return res;
+    }
+    return undefined; //default
+  }
+
+  getPotentialMovesFrom([x,y])
+  {
+    switch (this.getPiece(x,y))
+    {
+      case V.MARSHALL:
+        return this.getPotentialMarshallMoves([x,y]);
+      case V.CARDINAL:
+        return this.getPotentialCardinalMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y])
+    }
+  }
+
+  // Special pawn rules: promotions to captured friendly pieces,
+  // optional on ranks 8-9 and mandatory on rank 10.
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
+    const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
+    const promotionPieces =
+      [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
+
+    // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
+    let finalPieces = undefined;
+    if (lastRanks.includes(x + shiftX))
+    {
+      finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
+      if (x + shiftX != lastRanks[0])
+        finalPieces.push(V.PAWN);
+    }
+    else
+      finalPieces = [V.PAWN];
+    if (this.board[x+shiftX][y] == V.EMPTY)
+    {
+      // One square forward
+      for (let piece of finalPieces)
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+      if (startRanks.includes(x))
+      {
+        if (this.board[x+2*shiftX][y] == V.EMPTY)
+        {
+          // Two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+          if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
+          {
+            // Three squares jump
+            moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
+          }
+        }
+      }
+    }
+    // Captures
+    for (let shiftY of [-1,1])
+    {
+      if (y + shiftY >= 0 && y + shiftY < sizeY
+        && this.board[x+shiftX][y+shiftY] != V.EMPTY
+        && this.canTake([x,y], [x+shiftX,y+shiftY]))
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+      }
+    }
+
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep-1];
+    if (!!epSquare)
+    {
+      for (let epsq of epSquare)
+      {
+        // TODO: some redundant checks
+        if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
+        {
+          var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
+          // WARNING: the captured pawn may be diagonally behind us,
+          // if it's a 3-squares jump and we take on 1st passing square
+          const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
+          enpassantMove.vanish.push({
+            x: px,
+            y: epsq.y,
+            p: 'p',
+            c: this.getColor(px,epsq.y)
+          });
+          moves.push(enpassantMove);
+        }
+      }
+    }
+
+    return moves;
+  }
+
+  // TODO: different castle?
+
+  getPotentialMarshallMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+  }
+
+  getPotentialCardinalMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+  }
+
+  isAttacked(sq, colors)
+  {
+    return super.isAttacked(sq, colors)
+      || this.isAttackedByMarshall(sq, colors)
+      || this.isAttackedByCardinal(sq, colors);
+  }
+
+  isAttackedByMarshall(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
+      || this.isAttackedBySlideNJump(
+        sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
+  }
+
+  isAttackedByCardinal(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
+      || this.isAttackedBySlideNJump(
+        sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
+  }
+
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 1)
+    {
+      // Capture: update this.captured
+      this.captured[move.vanish[1].c][move.vanish[1].p]++;
+    }
+    if (move.vanish[0].p != move.appear[0].p)
+    {
+      // Promotion: update this.captured
+      this.captured[move.vanish[0].c][move.appear[0].p]--;
+    }
+  }
+
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 1)
+      this.captured[move.vanish[1].c][move.vanish[1].p]--;
+    if (move.vanish[0].p != move.appear[0].p)
+      this.captured[move.vanish[0].c][move.appear[0].p]++;
+  }
+
+  static get VALUES()
+  {
+    return Object.assign(
+      ChessRules.VALUES,
+      {'c': 5, 'm': 7} //experimental
+    );
+  }
+
+  static get SEARCH_DEPTH() { return 2; }
+
+  // TODO: this function could be generalized and shared better (how ?!...)
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(10), "b": new Array(10) };
+    // Shuffle pieces on first and last rank
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(10);
+
+      // Get random squares for bishops
+      let randIndex = 2 * randInt(5);
+      let bishop1Pos = positions[randIndex];
+      // The second bishop must be on a square of different color
+      let randIndex_tmp = 2 * randInt(5) + 1;
+      let bishop2Pos = positions[randIndex_tmp];
+      // Remove chosen squares
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+      // Get random squares for knights
+      randIndex = randInt(8);
+      let knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(7);
+      let knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for queen
+      randIndex = randInt(6);
+      let queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // ...random square for marshall
+      randIndex = randInt(5);
+      let marshallPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // ...random square for cardinal
+      randIndex = randInt(4);
+      let cardinalPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Rooks and king positions are now fixed, because of the ordering rook-king-rook
+      let rook1Pos = positions[0];
+      let kingPos = positions[1];
+      let rook2Pos = positions[2];
+
+      // Finally put the shuffled pieces in the board array
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][marshallPos] = 'm';
+      pieces[c][cardinalPos] = 'c';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["b"].join("") +
+      "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 1111 - 00000000000000";
+  }
 }
index 86b00f9..d815ee7 100644 (file)
@@ -4,106 +4,106 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class LosersRules extends ChessRules
 {
-       static get HasFlags() { return false; }
-
-       getPotentialPawnMoves([x,y])
-       {
-               let moves = super.getPotentialPawnMoves([x,y]);
-
-               // Complete with promotion(s) into king, if possible
-               const color = this.turn;
-               const shift = (color == "w" ? -1 : 1);
-               const lastRank = (color == "w" ? 0 : V.size.x-1);
-               if (x+shift == lastRank)
-               {
-                       // Normal move
-                       if (this.board[x+shift][y] == V.EMPTY)
-                               moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
-                       // Captures
-                       if (y>0 && this.canTake([x,y], [x+shift,y-1])
-                               && this.board[x+shift][y-1] != V.EMPTY)
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
-                       }
-                       if (y<V.size.y-1 && this.canTake([x,y], [x+shift,y+1])
-                               && this.board[x+shift][y+1] != V.EMPTY)
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
-                       }
-               }
-
-               return moves;
-       }
-
-       getPotentialKingMoves(sq)
-       {
-               // No castle:
-               return this.getSlideNJumpMoves(sq,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-       }
-
-       // Stop at the first capture found (if any)
-       atLeastOneCapture()
-       {
-               const color = this.turn;
-               const oppCol = V.GetOppCol(color);
-               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) != oppCol)
-                               {
-                                       const moves = this.getPotentialMovesFrom([i,j]);
-                                       if (moves.length > 0)
-                                       {
-                                               for (let k=0; k<moves.length; k++)
-                                               {
-                                                       if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
-                                                               return true;
-                                               }
-                                       }
-                               }
-                       }
-               }
-               return false;
-       }
-
-       // Trim all non-capturing moves
-       static KeepCaptures(moves)
-       {
-               return moves.filter(m => { return m.vanish.length == 2; });
-       }
-
-       getPossibleMovesFrom(sq)
-       {
-               let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
-               // This is called from interface: we need to know if a capture is possible
-               if (this.atLeastOneCapture())
-                       moves = V.KeepCaptures(moves);
-               return moves;
-       }
-
-       getAllValidMoves()
-       {
-               let moves = super.getAllValidMoves();
-               if (moves.some(m => { return m.vanish.length == 2; }))
-                       moves = V.KeepCaptures(moves);
-               return moves;
-       }
-
-       underCheck(color)
-       {
-               return false; //No notion of check
-       }
-
-       getCheckSquares(move)
-       {
-               return [];
-       }
-
-       // No variables update because no royal king + no castling
-       updateVariables(move) { }
-       unupdateVariables(move) { }
+  static get HasFlags() { return false; }
+
+  getPotentialPawnMoves([x,y])
+  {
+    let moves = super.getPotentialPawnMoves([x,y]);
+
+    // Complete with promotion(s) into king, if possible
+    const color = this.turn;
+    const shift = (color == "w" ? -1 : 1);
+    const lastRank = (color == "w" ? 0 : V.size.x-1);
+    if (x+shift == lastRank)
+    {
+      // Normal move
+      if (this.board[x+shift][y] == V.EMPTY)
+        moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+      // Captures
+      if (y>0 && this.canTake([x,y], [x+shift,y-1])
+        && this.board[x+shift][y-1] != V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
+      }
+      if (y<V.size.y-1 && this.canTake([x,y], [x+shift,y+1])
+        && this.board[x+shift][y+1] != V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
+      }
+    }
+
+    return moves;
+  }
+
+  getPotentialKingMoves(sq)
+  {
+    // No castle:
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  // Stop at the first capture found (if any)
+  atLeastOneCapture()
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    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) != oppCol)
+        {
+          const moves = this.getPotentialMovesFrom([i,j]);
+          if (moves.length > 0)
+          {
+            for (let k=0; k<moves.length; k++)
+            {
+              if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
+                return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  // Trim all non-capturing moves
+  static KeepCaptures(moves)
+  {
+    return moves.filter(m => { return m.vanish.length == 2; });
+  }
+
+  getPossibleMovesFrom(sq)
+  {
+    let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
+    // This is called from interface: we need to know if a capture is possible
+    if (this.atLeastOneCapture())
+      moves = V.KeepCaptures(moves);
+    return moves;
+  }
+
+  getAllValidMoves()
+  {
+    let moves = super.getAllValidMoves();
+    if (moves.some(m => { return m.vanish.length == 2; }))
+      moves = V.KeepCaptures(moves);
+    return moves;
+  }
+
+  underCheck(color)
+  {
+    return false; //No notion of check
+  }
+
+  getCheckSquares(move)
+  {
+    return [];
+  }
+
+  // No variables update because no royal king + no castling
+  updateVariables(move) { }
+  unupdateVariables(move) { }
 
   getCurrentScore()
   {
@@ -111,82 +111,82 @@ export const VariantRules = class LosersRules extends ChessRules
       return "*";
 
     // No valid move: the side who cannot move wins
-               return (this.turn == "w" ? "1-0" : "0-1");
-       }
-
-       static get VALUES()
-       {
-               // Experimental...
-               return {
-                       'p': 1,
-                       'r': 7,
-                       'n': 3,
-                       'b': 3,
-                       'q': 5,
-                       'k': 5
-               };
-       }
-
-       static get SEARCH_DEPTH() { return 4; }
-
-       evalPosition()
-       {
-               return - super.evalPosition(); //better with less material
-       }
-
-       static GenRandInitFen()
-       {
-               let pieces = { "w": new Array(8), "b": new Array(8) };
-               // Shuffle pieces on first and last rank
-               for (let c of ["w","b"])
-               {
-                       let positions = ArrayFun.range(8);
-
-                       // Get random squares for bishops
-                       let randIndex = 2 * randInt(4);
-                       let bishop1Pos = positions[randIndex];
-                       // The second bishop must be on a square of different color
-                       let randIndex_tmp = 2 * randInt(4) + 1;
-                       let bishop2Pos = positions[randIndex_tmp];
-                       // Remove chosen squares
-                       positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-                       positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
-                       // Get random squares for knights
-                       randIndex = randInt(6);
-                       let knight1Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-                       randIndex = randInt(5);
-                       let knight2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // Get random square for queen
-                       randIndex = randInt(4);
-                       let queenPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // Random square for king (no castle)
-                       randIndex = randInt(3);
-                       let kingPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-
-                       // Rooks positions are now fixed
-                       let rook1Pos = positions[0];
-                       let rook2Pos = positions[1];
-
-                       // Finally put the shuffled pieces in the board array
-                       pieces[c][rook1Pos] = 'r';
-                       pieces[c][knight1Pos] = 'n';
-                       pieces[c][bishop1Pos] = 'b';
-                       pieces[c][queenPos] = 'q';
-                       pieces[c][kingPos] = 'k';
-                       pieces[c][bishop2Pos] = 'b';
-                       pieces[c][knight2Pos] = 'n';
-                       pieces[c][rook2Pos] = 'r';
-               }
-               return pieces["b"].join("") +
-                       "/pppppppp/8/8/8/8/PPPPPPPP/" +
-                       pieces["w"].join("").toUpperCase() +
-                       " w 0 -"; //en-passant allowed, but no flags
-       }
+    return (this.turn == "w" ? "1-0" : "0-1");
+  }
+
+  static get VALUES()
+  {
+    // Experimental...
+    return {
+      'p': 1,
+      'r': 7,
+      'n': 3,
+      'b': 3,
+      'q': 5,
+      'k': 5
+    };
+  }
+
+  static get SEARCH_DEPTH() { return 4; }
+
+  evalPosition()
+  {
+    return - super.evalPosition(); //better with less material
+  }
+
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    // Shuffle pieces on first and last rank
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
+
+      // Get random squares for bishops
+      let randIndex = 2 * randInt(4);
+      let bishop1Pos = positions[randIndex];
+      // The second bishop must be on a square of different color
+      let randIndex_tmp = 2 * randInt(4) + 1;
+      let bishop2Pos = positions[randIndex_tmp];
+      // Remove chosen squares
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+      // Get random squares for knights
+      randIndex = randInt(6);
+      let knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      let knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for queen
+      randIndex = randInt(4);
+      let queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Random square for king (no castle)
+      randIndex = randInt(3);
+      let kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Rooks positions are now fixed
+      let rook1Pos = positions[0];
+      let rook2Pos = positions[1];
+
+      // Finally put the shuffled pieces in the board array
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 -"; //en-passant allowed, but no flags
+  }
 }
index ea745dd..3789de3 100644 (file)
@@ -2,210 +2,210 @@ import { ChessRules, PiPo } from "@/base_rules";
 
 export const VariantRules = class  MagneticRules extends ChessRules
 {
-       static get HasEnpassant() { return false; }
+  static get HasEnpassant() { return false; }
 
-       getPotentialMovesFrom([x,y])
-       {
-               let standardMoves = super.getPotentialMovesFrom([x,y]);
-               let moves = [];
-               standardMoves.forEach(m => {
-                       let newMove_s = this.applyMagneticLaws(m);
-                       if (newMove_s.length == 1)
-                               moves.push(newMove_s[0]);
-                       else //promotion
-                               moves = moves.concat(newMove_s);
-               });
-               return moves;
-       }
+  getPotentialMovesFrom([x,y])
+  {
+    let standardMoves = super.getPotentialMovesFrom([x,y]);
+    let moves = [];
+    standardMoves.forEach(m => {
+      let newMove_s = this.applyMagneticLaws(m);
+      if (newMove_s.length == 1)
+        moves.push(newMove_s[0]);
+      else //promotion
+        moves = moves.concat(newMove_s);
+    });
+    return moves;
+  }
 
-       // Complete a move with magnetic actions
-       // TODO: job is done multiple times for (normal) promotions.
-       applyMagneticLaws(move)
-       {
-               if (move.appear[0].p == V.KING && move.appear.length==1)
-                       return [move]; //kings are not charged
-               const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
-               const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
-               const color = this.turn;
-               const lastRank = (color=="w" ? 0 : 7);
-               const standardMove = JSON.parse(JSON.stringify(move));
-               this.play(standardMove);
-               for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
-               {
-                       let [i,j] = [x+step[0],y+step[1]];
-                       while (V.OnBoard(i,j))
-                       {
-                               if (this.board[i][j] != V.EMPTY)
-                               {
-                                       // Found something. Same color or not?
-                                       if (this.getColor(i,j) != color)
-                                       {
-                                               // Attraction
-                                               if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
-                                               {
-                                                       move.vanish.push(
-                                                               new PiPo({
-                                                                       p:this.getPiece(i,j),
-                                                                       c:this.getColor(i,j),
-                                                                       x:i,
-                                                                       y:j
-                                                               })
-                                                       );
-                                                       move.appear.push(
-                                                               new PiPo({
-                                                                       p:this.getPiece(i,j),
-                                                                       c:this.getColor(i,j),
-                                                                       x:x+step[0],
-                                                                       y:y+step[1]
-                                                               })
-                                                       );
-                                               }
-                                       }
-                                       else
-                                       {
-                                               // Repulsion
-                                               if (this.getPiece(i,j) != V.KING)
-                                               {
-                                                       // Push it until we meet an obstacle or edge of the board
-                                                       let [ii,jj] = [i+step[0],j+step[1]];
-                                                       while (V.OnBoard(ii,jj))
-                                                       {
-                                                               if (this.board[ii][jj] != V.EMPTY)
-                                                                       break;
-                                                               ii += step[0];
-                                                               jj += step[1];
-                                                       }
-                                                       ii -= step[0];
-                                                       jj -= step[1];
-                                                       if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
-                                                       {
-                                                               move.vanish.push(
-                                                                       new PiPo({
-                                                                               p:this.getPiece(i,j),
-                                                                               c:this.getColor(i,j),
-                                                                               x:i,
-                                                                               y:j
-                                                                       })
-                                                               );
-                                                               move.appear.push(
-                                                                       new PiPo({
-                                                                               p:this.getPiece(i,j),
-                                                                               c:this.getColor(i,j),
-                                                                               x:ii,
-                                                                               y:jj
-                                                                       })
-                                                               );
-                                                       }
-                                               }
-                                       }
-                                       break;
-                               }
-                               i += step[0];
-                               j += step[1];
-                       }
-               }
-               this.undo(standardMove);
-               let moves = [];
-               // Scan move for pawn (max 1) on 8th rank
-               for (let i=1; i<move.appear.length; i++)
-               {
-                       if (move.appear[i].p==V.PAWN && move.appear[i].c==color
-                               && move.appear[i].x==lastRank)
-                       {
-                               move.appear[i].p = V.ROOK;
-                               moves.push(move);
-                               for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
-                               {
-                                       let cmove = JSON.parse(JSON.stringify(move));
-                                       cmove.appear[i].p = piece;
-                                       moves.push(cmove);
-                               }
-                               // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
-                               moves.forEach(m => {
-                                       let tmp = m.appear[0];
-                                       m.appear[0] = m.appear[i];
-                                       m.appear[i] = tmp;
-                               });
-                               break;
-                       }
-               }
-               if (moves.length == 0) //no pawn on 8th rank
-                       moves.push(move);
-               return moves;
-       }
+  // Complete a move with magnetic actions
+  // TODO: job is done multiple times for (normal) promotions.
+  applyMagneticLaws(move)
+  {
+    if (move.appear[0].p == V.KING && move.appear.length==1)
+      return [move]; //kings are not charged
+    const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
+    const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
+    const color = this.turn;
+    const lastRank = (color=="w" ? 0 : 7);
+    const standardMove = JSON.parse(JSON.stringify(move));
+    this.play(standardMove);
+    for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
+    {
+      let [i,j] = [x+step[0],y+step[1]];
+      while (V.OnBoard(i,j))
+      {
+        if (this.board[i][j] != V.EMPTY)
+        {
+          // Found something. Same color or not?
+          if (this.getColor(i,j) != color)
+          {
+            // Attraction
+            if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
+            {
+              move.vanish.push(
+                new PiPo({
+                  p:this.getPiece(i,j),
+                  c:this.getColor(i,j),
+                  x:i,
+                  y:j
+                })
+              );
+              move.appear.push(
+                new PiPo({
+                  p:this.getPiece(i,j),
+                  c:this.getColor(i,j),
+                  x:x+step[0],
+                  y:y+step[1]
+                })
+              );
+            }
+          }
+          else
+          {
+            // Repulsion
+            if (this.getPiece(i,j) != V.KING)
+            {
+              // Push it until we meet an obstacle or edge of the board
+              let [ii,jj] = [i+step[0],j+step[1]];
+              while (V.OnBoard(ii,jj))
+              {
+                if (this.board[ii][jj] != V.EMPTY)
+                  break;
+                ii += step[0];
+                jj += step[1];
+              }
+              ii -= step[0];
+              jj -= step[1];
+              if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
+              {
+                move.vanish.push(
+                  new PiPo({
+                    p:this.getPiece(i,j),
+                    c:this.getColor(i,j),
+                    x:i,
+                    y:j
+                  })
+                );
+                move.appear.push(
+                  new PiPo({
+                    p:this.getPiece(i,j),
+                    c:this.getColor(i,j),
+                    x:ii,
+                    y:jj
+                  })
+                );
+              }
+            }
+          }
+          break;
+        }
+        i += step[0];
+        j += step[1];
+      }
+    }
+    this.undo(standardMove);
+    let moves = [];
+    // Scan move for pawn (max 1) on 8th rank
+    for (let i=1; i<move.appear.length; i++)
+    {
+      if (move.appear[i].p==V.PAWN && move.appear[i].c==color
+        && move.appear[i].x==lastRank)
+      {
+        move.appear[i].p = V.ROOK;
+        moves.push(move);
+        for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN])
+        {
+          let cmove = JSON.parse(JSON.stringify(move));
+          cmove.appear[i].p = piece;
+          moves.push(cmove);
+        }
+        // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
+        moves.forEach(m => {
+          let tmp = m.appear[0];
+          m.appear[0] = m.appear[i];
+          m.appear[i] = tmp;
+        });
+        break;
+      }
+    }
+    if (moves.length == 0) //no pawn on 8th rank
+      moves.push(move);
+    return moves;
+  }
 
-       atLeastOneMove()
-       {
-               if (this.kingPos[this.turn][0] < 0)
-                       return false;
-               return true; //TODO: is it right?
-       }
+  atLeastOneMove()
+  {
+    if (this.kingPos[this.turn][0] < 0)
+      return false;
+    return true; //TODO: is it right?
+  }
 
-       underCheck(color)
-       {
-               return false; //there is no check
-       }
+  underCheck(color)
+  {
+    return false; //there is no check
+  }
 
-       getCheckSquares(move)
-       {
-               return [];
-       }
+  getCheckSquares(move)
+  {
+    return [];
+  }
 
-       updateVariables(move)
-       {
-               super.updateVariables(move);
-               const c = move.vanish[0].c;
-               if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
-               {
-                       // We took opponent king !
-                       const oppCol = V.GetOppCol(c);
-                       this.kingPos[oppCol] = [-1,-1];
-                       this.castleFlags[oppCol] = [false,false];
-               }
-               // Did we magnetically move our (init) rooks or opponents' ones ?
-               const firstRank = (c == "w" ? 7 : 0);
-               const oppFirstRank = 7 - firstRank;
-               const oppCol = V.GetOppCol(c);
-               move.vanish.forEach(psq => {
-                       if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
-                               this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
-                       else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
-                               this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
-               });
-       }
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    const c = move.vanish[0].c;
+    if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
+    {
+      // We took opponent king !
+      const oppCol = V.GetOppCol(c);
+      this.kingPos[oppCol] = [-1,-1];
+      this.castleFlags[oppCol] = [false,false];
+    }
+    // Did we magnetically move our (init) rooks or opponents' ones ?
+    const firstRank = (c == "w" ? 7 : 0);
+    const oppFirstRank = 7 - firstRank;
+    const oppCol = V.GetOppCol(c);
+    move.vanish.forEach(psq => {
+      if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
+        this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
+      else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
+        this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
+    });
+  }
 
-       unupdateVariables(move)
-       {
-               super.unupdateVariables(move);
-               const c = move.vanish[0].c;
-               const oppCol = V.GetOppCol(c);
-               if (this.kingPos[oppCol][0] < 0)
-               {
-                       // Last move took opponent's king
-                       for (let psq of move.vanish)
-                       {
-                               if (psq.p == 'k')
-                               {
-                                       this.kingPos[oppCol] = [psq.x, psq.y];
-                                       break;
-                               }
-                       }
-               }
-       }
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if (this.kingPos[oppCol][0] < 0)
+    {
+      // Last move took opponent's king
+      for (let psq of move.vanish)
+      {
+        if (psq.p == 'k')
+        {
+          this.kingPos[oppCol] = [psq.x, psq.y];
+          break;
+        }
+      }
+    }
+  }
 
-       getCurrentScore()
-       {
-               const color = this.turn;
-               const kp = this.kingPos[color];
-               if (kp[0] < 0) //king disappeared
-                       return (color == "w" ? "0-1" : "1-0");
+  getCurrentScore()
+  {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0) //king disappeared
+      return (color == "w" ? "0-1" : "1-0");
     if (this.atLeastOneMove()) // game not over
       return "*";
     return "1/2"; //no moves but kings still there
-       }
+  }
 
-       static get THRESHOLD_MATE()
-       {
-               return 500; //checkmates evals may be slightly below 1000
-       }
+  static get THRESHOLD_MATE()
+  {
+    return 500; //checkmates evals may be slightly below 1000
+  }
 }
index ac1782f..31c363d 100644 (file)
@@ -3,297 +3,297 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class MarseilleRules extends ChessRules
 {
-       static IsGoodEnpassant(enpassant)
-       {
-               if (enpassant != "-")
-               {
-                       const squares = enpassant.split(",");
-                       if (squares.length > 2)
-                               return false;
-                       for (let sq of squares)
-                       {
-                               const ep = V.SquareToCoords(sq);
-                               if (isNaN(ep.x) || !V.OnBoard(ep))
-                                       return false;
-                       }
-               }
-               return true;
-       }
+  static IsGoodEnpassant(enpassant)
+  {
+    if (enpassant != "-")
+    {
+      const squares = enpassant.split(",");
+      if (squares.length > 2)
+        return false;
+      for (let sq of squares)
+      {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep))
+          return false;
+      }
+    }
+    return true;
+  }
 
-       getTurnFen()
-       {
-               return this.turn + this.subTurn;
-       }
+  getTurnFen()
+  {
+    return this.turn + this.subTurn;
+  }
 
-       // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
-       getEnpassantFen()
-       {
-               const L = this.epSquares.length;
-               if (this.epSquares[L-1].every(epsq => epsq === undefined))
-                       return "-"; //no en-passant
-               let res = "";
-               this.epSquares[L-1].forEach(epsq => {
-                       if (!!epsq)
-                               res += V.CoordsToSquare(epsq) + ",";
-               });
-               return res.slice(0,-1); //remove last comma
-       }
+  // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
+  getEnpassantFen()
+  {
+    const L = this.epSquares.length;
+    if (this.epSquares[L-1].every(epsq => epsq === undefined))
+      return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L-1].forEach(epsq => {
+      if (!!epsq)
+        res += V.CoordsToSquare(epsq) + ",";
+    });
+    return res.slice(0,-1); //remove last comma
+  }
 
-       setOtherVariables(fen)
-       {
-               const parsedFen = V.ParseFen(fen);
-               this.setFlags(parsedFen.flags);
-               if (parsedFen.enpassant == "-")
-                       this.epSquares = [ [undefined] ];
-               else
-               {
-                       let res = [];
-                       const squares = parsedFen.enpassant.split(",");
-                       for (let sq of squares)
-                               res.push(V.SquareToCoords(sq));
-                       this.epSquares = [ res ];
-               }
-               this.scanKingsRooks(fen);
-               // Extract subTurn from turn indicator: "w" (first move), or
-               // "w1" or "w2" white subturn 1 or 2, and same for black
-               const fullTurn = V.ParseFen(fen).turn;
-               this.turn = fullTurn[0];
-               this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
-       }
+  setOtherVariables(fen)
+  {
+    const parsedFen = V.ParseFen(fen);
+    this.setFlags(parsedFen.flags);
+    if (parsedFen.enpassant == "-")
+      this.epSquares = [ [undefined] ];
+    else
+    {
+      let res = [];
+      const squares = parsedFen.enpassant.split(",");
+      for (let sq of squares)
+        res.push(V.SquareToCoords(sq));
+      this.epSquares = [ res ];
+    }
+    this.scanKingsRooks(fen);
+    // Extract subTurn from turn indicator: "w" (first move), or
+    // "w1" or "w2" white subturn 1 or 2, and same for black
+    const fullTurn = V.ParseFen(fen).turn;
+    this.turn = fullTurn[0];
+    this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
+  }
 
-       getPotentialPawnMoves([x,y])
-       {
-               const color = this.turn;
-               let moves = [];
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               const shiftX = (color == "w" ? -1 : 1);
-               const firstRank = (color == 'w' ? sizeX-1 : 0);
-               const startRank = (color == "w" ? sizeX-2 : 1);
-               const lastRank = (color == "w" ? 0 : sizeX-1);
-               const finalPieces = x + shiftX == lastRank
-                       ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
-                       : [V.PAWN];
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const firstRank = (color == 'w' ? sizeX-1 : 0);
+    const startRank = (color == "w" ? sizeX-2 : 1);
+    const lastRank = (color == "w" ? 0 : sizeX-1);
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+      : [V.PAWN];
 
-               // One square forward
-               if (this.board[x+shiftX][y] == V.EMPTY)
-               {
-                       for (let piece of finalPieces)
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shiftX,y],
-                                       {c:color,p:piece}));
-                       }
-                       // Next condition because pawns on 1st rank can generally jump
-                       if ([startRank,firstRank].includes(x)
-                               && this.board[x+2*shiftX][y] == V.EMPTY)
-                       {
-                               // Two squares jump
-                               moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
-                       }
-               }
-               // Captures
-               for (let shiftY of [-1,1])
-               {
-                       if (y + shiftY >= 0 && y + shiftY < sizeY
-                               && this.board[x+shiftX][y+shiftY] != V.EMPTY
-                               && this.canTake([x,y], [x+shiftX,y+shiftY]))
-                       {
-                               for (let piece of finalPieces)
-                               {
-                                       moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-                                               {c:color,p:piece}));
-                               }
-                       }
-               }
+    // One square forward
+    if (this.board[x+shiftX][y] == V.EMPTY)
+    {
+      for (let piece of finalPieces)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y],
+          {c:color,p:piece}));
+      }
+      // Next condition because pawns on 1st rank can generally jump
+      if ([startRank,firstRank].includes(x)
+        && this.board[x+2*shiftX][y] == V.EMPTY)
+      {
+        // Two squares jump
+        moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+      }
+    }
+    // Captures
+    for (let shiftY of [-1,1])
+    {
+      if (y + shiftY >= 0 && y + shiftY < sizeY
+        && this.board[x+shiftX][y+shiftY] != V.EMPTY
+        && this.canTake([x,y], [x+shiftX,y+shiftY]))
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+      }
+    }
 
-               // En passant: always OK if subturn 1,
-               // OK on subturn 2 only if enPassant was played at subturn 1
-               // (and if there are two e.p. squares available).
-               const Lep = this.epSquares.length;
-               const epSquares = this.epSquares[Lep-1]; //always at least one element
-               let epSqs = [];
-               epSquares.forEach(sq => {
-                       if (!!sq)
-                               epSqs.push(sq);
-               });
-               if (epSqs.length == 0)
-                       return moves;
-               const oppCol = V.GetOppCol(color);
-               for (let sq of epSqs)
-               {
-                       if (this.subTurn == 1 || (epSqs.length == 2 &&
-                               // Was this en-passant capture already played at subturn 1 ?
-                               // (Or maybe the opponent filled the en-passant square with a piece)
-                               this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
-                       {
-                               if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
-                                       // Add condition "enemy pawn must be present"
-                                       && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
-                               {
-                                       let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
-                                       epMove.vanish.push({
-                                               x: x,
-                                               y: sq.y,
-                                               p: 'p',
-                                               c: oppCol
-                                       });
-                                       moves.push(epMove);
-                               }
-                       }
-               }
+    // En passant: always OK if subturn 1,
+    // OK on subturn 2 only if enPassant was played at subturn 1
+    // (and if there are two e.p. squares available).
+    const Lep = this.epSquares.length;
+    const epSquares = this.epSquares[Lep-1]; //always at least one element
+    let epSqs = [];
+    epSquares.forEach(sq => {
+      if (!!sq)
+        epSqs.push(sq);
+    });
+    if (epSqs.length == 0)
+      return moves;
+    const oppCol = V.GetOppCol(color);
+    for (let sq of epSqs)
+    {
+      if (this.subTurn == 1 || (epSqs.length == 2 &&
+        // Was this en-passant capture already played at subturn 1 ?
+        // (Or maybe the opponent filled the en-passant square with a piece)
+        this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
+      {
+        if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
+          // Add condition "enemy pawn must be present"
+          && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
+        {
+          let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
+          epMove.vanish.push({
+            x: x,
+            y: sq.y,
+            p: 'p',
+            c: oppCol
+          });
+          moves.push(epMove);
+        }
+      }
+    }
 
-               return moves;
-       }
+    return moves;
+  }
 
-       play(move)
-       {
-               move.flags = JSON.stringify(this.aggregateFlags());
-               move.turn = this.turn + this.subTurn;
-               V.PlayOnBoard(this.board, move);
-               const epSq = this.getEpSquare(move);
-               if (this.subTurn == 0) //first move in game
-               {
-                       this.turn = "b";
+  play(move)
+  {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    move.turn = this.turn + this.subTurn;
+    V.PlayOnBoard(this.board, move);
+    const epSq = this.getEpSquare(move);
+    if (this.subTurn == 0) //first move in game
+    {
+      this.turn = "b";
       this.subTurn = 1;
-                       this.epSquares.push([epSq]);
-               }
-               // Does this move give check on subturn 1? If yes, skip subturn 2
-               else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
-               {
-                       this.turn = V.GetOppCol(this.turn);
-                       this.epSquares.push([epSq]);
-                       move.checkOnSubturn1 = true;
-               }
-               else
-               {
-                       if (this.subTurn == 2)
-                       {
-                               this.turn = V.GetOppCol(this.turn);
-                               let lastEpsq = this.epSquares[this.epSquares.length-1];
-                               lastEpsq.push(epSq);
-                       }
-                       else
-                               this.epSquares.push([epSq]);
-                       this.subTurn = 3 - this.subTurn;
-               }
-               this.updateVariables(move);
-       }
+      this.epSquares.push([epSq]);
+    }
+    // Does this move give check on subturn 1? If yes, skip subturn 2
+    else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
+    {
+      this.turn = V.GetOppCol(this.turn);
+      this.epSquares.push([epSq]);
+      move.checkOnSubturn1 = true;
+    }
+    else
+    {
+      if (this.subTurn == 2)
+      {
+        this.turn = V.GetOppCol(this.turn);
+        let lastEpsq = this.epSquares[this.epSquares.length-1];
+        lastEpsq.push(epSq);
+      }
+      else
+        this.epSquares.push([epSq]);
+      this.subTurn = 3 - this.subTurn;
+    }
+    this.updateVariables(move);
+  }
 
-       undo(move)
-       {
-               this.disaggregateFlags(JSON.parse(move.flags));
-               V.UndoOnBoard(this.board, move);
-               if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
-                       this.epSquares.pop();
-               else //this.subTurn == 1
-               {
-                       let lastEpsq = this.epSquares[this.epSquares.length-1];
-                       lastEpsq.pop();
-               }
-               this.turn = move.turn[0];
-               this.subTurn = parseInt(move.turn[1]);
-               this.unupdateVariables(move);
-       }
+  undo(move)
+  {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    V.UndoOnBoard(this.board, move);
+    if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
+      this.epSquares.pop();
+    else //this.subTurn == 1
+    {
+      let lastEpsq = this.epSquares[this.epSquares.length-1];
+      lastEpsq.pop();
+    }
+    this.turn = move.turn[0];
+    this.subTurn = parseInt(move.turn[1]);
+    this.unupdateVariables(move);
+  }
 
-       // NOTE:  GenRandInitFen() is OK,
-       // since at first move turn indicator is just "w"
+  // NOTE:  GenRandInitFen() is OK,
+  // since at first move turn indicator is just "w"
 
-       static get VALUES()
-       {
-               return {
-                       'p': 1,
-                       'r': 5,
-                       'n': 3,
-                       'b': 3,
-                       'q': 7, //slightly less than in orthodox game
-                       'k': 1000
-               };
-       }
+  static get VALUES()
+  {
+    return {
+      'p': 1,
+      'r': 5,
+      'n': 3,
+      'b': 3,
+      'q': 7, //slightly less than in orthodox game
+      'k': 1000
+    };
+  }
 
-       // No alpha-beta here, just adapted min-max at depth 2(+1)
-       getComputerMove()
-       {
-               if (this.subTurn == 2)
-                       return null; //TODO: imperfect interface setup
+  // No alpha-beta here, just adapted min-max at depth 2(+1)
+  getComputerMove()
+  {
+    if (this.subTurn == 2)
+      return null; //TODO: imperfect interface setup
 
-               const maxeval = V.INFINITY;
-               const color = this.turn;
-               const oppCol = V.GetOppCol(this.turn);
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const oppCol = V.GetOppCol(this.turn);
 
-               // Search best (half) move for opponent turn
-               const getBestMoveEval = () => {
-                       const turnBefore = this.turn + this.subTurn;
-                       let score = this.getCurrentScore();
-                       if (score != "*")
-                       {
-                               if (score == "1/2")
-                                       return 0;
-                               return maxeval * (score == "1-0" ? 1 : -1);
-                       }
-                       let moves = this.getAllValidMoves();
-                       let res = (oppCol == "w" ? -maxeval : maxeval);
-                       for (let m of moves)
-                       {
-                               this.play(m);
-                               score = this.getCurrentScore();
-                               // Now turn is oppCol,2 if m doesn't give check
-                               // Otherwise it's color,1. In both cases the next test makes sense
-                               if (score != "*")
-                               {
-                                       if (score == "1/2")
-                                               res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
-                                       else
-                                       {
-                                               // Found a mate
-                                               this.undo(m);
-                                               return maxeval * (score == "1-0" ? 1 : -1);
-                                       }
-                               }
-                               const evalPos = this.evalPosition();
-                               res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos));
-                               this.undo(m);
-                       }
-                       return res;
-               };
+    // Search best (half) move for opponent turn
+    const getBestMoveEval = () => {
+      const turnBefore = this.turn + this.subTurn;
+      let score = this.getCurrentScore();
+      if (score != "*")
+      {
+        if (score == "1/2")
+          return 0;
+        return maxeval * (score == "1-0" ? 1 : -1);
+      }
+      let moves = this.getAllValidMoves();
+      let res = (oppCol == "w" ? -maxeval : maxeval);
+      for (let m of moves)
+      {
+        this.play(m);
+        score = this.getCurrentScore();
+        // Now turn is oppCol,2 if m doesn't give check
+        // Otherwise it's color,1. In both cases the next test makes sense
+        if (score != "*")
+        {
+          if (score == "1/2")
+            res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
+          else
+          {
+            // Found a mate
+            this.undo(m);
+            return maxeval * (score == "1-0" ? 1 : -1);
+          }
+        }
+        const evalPos = this.evalPosition();
+        res = (oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos));
+        this.undo(m);
+      }
+      return res;
+    };
 
-               let moves11 = this.getAllValidMoves();
-               let doubleMoves = [];
-               // Rank moves using a min-max at depth 2
-               for (let i=0; i<moves11.length; i++)
-               {
-                       this.play(moves11[i]);
-                       if (this.turn != color)
-                       {
-                               // We gave check with last move: search the best opponent move
-                               doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
-                       }
-                       else
-                       {
-                               let moves12 = this.getAllValidMoves();
-                               for (let j=0; j<moves12.length; j++)
-                               {
-                                       this.play(moves12[j]);
-                                       doubleMoves.push({
-                                               moves:[moves11[i],moves12[j]],
-                                               eval:getBestMoveEval()});
-                                       this.undo(moves12[j]);
-                               }
-                       }
-                       this.undo(moves11[i]);
-               }
+    let moves11 = this.getAllValidMoves();
+    let doubleMoves = [];
+    // Rank moves using a min-max at depth 2
+    for (let i=0; i<moves11.length; i++)
+    {
+      this.play(moves11[i]);
+      if (this.turn != color)
+      {
+        // We gave check with last move: search the best opponent move
+        doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
+      }
+      else
+      {
+        let moves12 = this.getAllValidMoves();
+        for (let j=0; j<moves12.length; j++)
+        {
+          this.play(moves12[j]);
+          doubleMoves.push({
+            moves:[moves11[i],moves12[j]],
+            eval:getBestMoveEval()});
+          this.undo(moves12[j]);
+        }
+      }
+      this.undo(moves11[i]);
+    }
 
-               doubleMoves.sort( (a,b) => {
-                       return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
-               let candidates = [0]; //indices of candidates moves
-               for (let i=1;
-                       i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
-                       i++)
-               {
-                       candidates.push(i);
-               }
+    doubleMoves.sort( (a,b) => {
+      return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
+    let candidates = [0]; //indices of candidates moves
+    for (let i=1;
+      i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
+      i++)
+    {
+      candidates.push(i);
+    }
 
-               const selected = doubleMoves[randInt(candidates.length)].moves;
-               if (selected.length == 1)
-                       return selected[0];
-               return selected;
-       }
+    const selected = doubleMoves[randInt(candidates.length)].moves;
+    if (selected.length == 1)
+      return selected[0];
+    return selected;
+  }
 }
index de72216..bf3e37c 100644 (file)
@@ -4,71 +4,71 @@ import { ArrayFun } from "@/utils/array";
 
 export const VariantRules = class UpsidedownRules extends ChessRules
 {
-       static get HasFlags() { return false; }
+  static get HasFlags() { return false; }
 
-       static get HasEnpassant() { return false; }
+  static get HasEnpassant() { return false; }
 
-       getPotentialKingMoves(sq)
-       {
-               // No castle
-               return this.getSlideNJumpMoves(sq,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-       }
+  getPotentialKingMoves(sq)
+  {
+    // No castle
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
 
-       static GenRandInitFen()
-       {
-               let pieces = { "w": new Array(8), "b": new Array(8) };
-               for (let c of ["w","b"])
-               {
-                       let positions = ArrayFun.range(8);
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
 
-                       let randIndex = randInt(8);
-                       const kingPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
+      let randIndex = randInt(8);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-                       // At least a knight must be next to the king:
-                       let knight1Pos = undefined;
-                       if (kingPos == 0)
-                               knight1Pos = 1;
-                       else if (kingPos == V.size.y-1)
-                               knight1Pos = V.size.y-2;
-                       else
-                               knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
-                       // Search for knight1Pos index in positions and remove it
-                       const knight1Index = positions.indexOf(knight1Pos);
-                       positions.splice(knight1Index, 1);
+      // At least a knight must be next to the king:
+      let knight1Pos = undefined;
+      if (kingPos == 0)
+        knight1Pos = 1;
+      else if (kingPos == V.size.y-1)
+        knight1Pos = V.size.y-2;
+      else
+        knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
+      // Search for knight1Pos index in positions and remove it
+      const knight1Index = positions.indexOf(knight1Pos);
+      positions.splice(knight1Index, 1);
 
-                       // King+knight1 are on two consecutive squares: one light, one dark
-                       randIndex = 2 * randInt(3);
-                       const bishop1Pos = positions[randIndex];
-                       let randIndex_tmp = 2 * randInt(3) + 1;
-                       const bishop2Pos = positions[randIndex_tmp];
-                       positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-                       positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+      // King+knight1 are on two consecutive squares: one light, one dark
+      randIndex = 2 * randInt(3);
+      const bishop1Pos = positions[randIndex];
+      let randIndex_tmp = 2 * randInt(3) + 1;
+      const bishop2Pos = positions[randIndex_tmp];
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
 
-                       randIndex = randInt(4);
-                       const knight2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
+      randIndex = randInt(4);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-                       randIndex = randInt(3);
-                       const queenPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
+      randIndex = randInt(3);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-                       const rook1Pos = positions[0];
-                       const rook2Pos = positions[1];
+      const rook1Pos = positions[0];
+      const rook2Pos = positions[1];
 
-                       pieces[c][rook1Pos] = 'r';
-                       pieces[c][knight1Pos] = 'n';
-                       pieces[c][bishop1Pos] = 'b';
-                       pieces[c][queenPos] = 'q';
-                       pieces[c][kingPos] = 'k';
-                       pieces[c][bishop2Pos] = 'b';
-                       pieces[c][knight2Pos] = 'n';
-                       pieces[c][rook2Pos] = 'r';
-               }
-               return pieces["w"].join("").toUpperCase() +
-                       "/PPPPPPPP/8/8/8/8/pppppppp/" +
-                       pieces["b"].join("") +
-                       " w 0"; //no castle, no en-passant
-       }
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["w"].join("").toUpperCase() +
+      "/PPPPPPPP/8/8/8/8/pppppppp/" +
+      pieces["b"].join("") +
+      " w 0"; //no castle, no en-passant
+  }
 }
index 20a32cc..a30e7f9 100644 (file)
@@ -4,293 +4,293 @@ import { sample, randInt } from "@/utils/alea";
 
 export const VariantRules = class  WildebeestRules extends ChessRules
 {
-       static getPpath(b)
-       {
-               return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
-       }
+  static getPpath(b)
+  {
+    return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
+  }
 
-       static get size() { return {x:10,y:11}; }
+  static get size() { return {x:10,y:11}; }
 
-       static get CAMEL() { return 'c'; }
-       static get WILDEBEEST() { return 'w'; }
+  static get CAMEL() { return 'c'; }
+  static get WILDEBEEST() { return 'w'; }
 
-       static get PIECES()
-       {
-               return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
-       }
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
+  }
 
-       static get steps()
-       {
-               return Object.assign(
-                       ChessRules.steps, //add camel moves:
-                       {'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
-               );
-       }
+  static get steps()
+  {
+    return Object.assign(
+      ChessRules.steps, //add camel moves:
+      {'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
+    );
+  }
 
-       static IsGoodEnpassant(enpassant)
-       {
-               if (enpassant != "-")
-               {
-                       const squares = enpassant.split(",");
-                       if (squares.length > 2)
-                               return false;
-                       for (let sq of squares)
-                       {
-                               const ep = V.SquareToCoords(sq);
-                               if (isNaN(ep.x) || !V.OnBoard(ep))
-                                       return false;
-                       }
-               }
-               return true;
-       }
+  static IsGoodEnpassant(enpassant)
+  {
+    if (enpassant != "-")
+    {
+      const squares = enpassant.split(",");
+      if (squares.length > 2)
+        return false;
+      for (let sq of squares)
+      {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep))
+          return false;
+      }
+    }
+    return true;
+  }
 
-       // There may be 2 enPassant squares (if pawn jump 3 squares)
-       getEnpassantFen()
-       {
-               const L = this.epSquares.length;
-               if (!this.epSquares[L-1])
-                       return "-"; //no en-passant
-               let res = "";
-               this.epSquares[L-1].forEach(sq => {
-                       res += V.CoordsToSquare(sq) + ",";
-               });
-               return res.slice(0,-1); //remove last comma
-       }
+  // There may be 2 enPassant squares (if pawn jump 3 squares)
+  getEnpassantFen()
+  {
+    const L = this.epSquares.length;
+    if (!this.epSquares[L-1])
+      return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L-1].forEach(sq => {
+      res += V.CoordsToSquare(sq) + ",";
+    });
+    return res.slice(0,-1); //remove last comma
+  }
 
-       // En-passant after 2-sq or 3-sq jumps
-       getEpSquare(moveOrSquare)
-       {
-               if (!moveOrSquare)
-                       return undefined;
-               if (typeof moveOrSquare === "string")
-               {
-                       const square = moveOrSquare;
-                       if (square == "-")
-                               return undefined;
-                       let res = [];
-                       square.split(",").forEach(sq => {
-                               res.push(V.SquareToCoords(sq));
-                       });
-                       return res;
-               }
-               // Argument is a move:
-               const move = moveOrSquare;
-               const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
-               if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
-               {
-                       const step = (ex-sx) / Math.abs(ex-sx);
-                       let res = [{
-                               x: sx + step,
-                               y: sy
-                       }];
-                       if (sx + 2*step != ex) //3-squares move
-                       {
-                               res.push({
-                                       x: sx + 2*step,
-                                       y: sy
-                               });
-                       }
-                       return res;
-               }
-               return undefined; //default
-       }
+  // En-passant after 2-sq or 3-sq jumps
+  getEpSquare(moveOrSquare)
+  {
+    if (!moveOrSquare)
+      return undefined;
+    if (typeof moveOrSquare === "string")
+    {
+      const square = moveOrSquare;
+      if (square == "-")
+        return undefined;
+      let res = [];
+      square.split(",").forEach(sq => {
+        res.push(V.SquareToCoords(sq));
+      });
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
+    if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
+    {
+      const step = (ex-sx) / Math.abs(ex-sx);
+      let res = [{
+        x: sx + step,
+        y: sy
+      }];
+      if (sx + 2*step != ex) //3-squares move
+      {
+        res.push({
+          x: sx + 2*step,
+          y: sy
+        });
+      }
+      return res;
+    }
+    return undefined; //default
+  }
 
-       getPotentialMovesFrom([x,y])
-       {
-               switch (this.getPiece(x,y))
-               {
-                       case V.CAMEL:
-                               return this.getPotentialCamelMoves([x,y]);
-                       case V.WILDEBEEST:
-                               return this.getPotentialWildebeestMoves([x,y]);
-                       default:
-                               return super.getPotentialMovesFrom([x,y])
-               }
-       }
+  getPotentialMovesFrom([x,y])
+  {
+    switch (this.getPiece(x,y))
+    {
+      case V.CAMEL:
+        return this.getPotentialCamelMoves([x,y]);
+      case V.WILDEBEEST:
+        return this.getPotentialWildebeestMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y])
+    }
+  }
 
-       // Pawns jump 2 or 3 squares, and promote to queen or wildebeest
-       getPotentialPawnMoves([x,y])
-       {
-               const color = this.turn;
-               let moves = [];
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               const shiftX = (color == "w" ? -1 : 1);
-               const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
-               const lastRank = (color == "w" ? 0 : sizeX-1);
-               const finalPieces = x + shiftX == lastRank
-                       ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
-                       : [V.PAWN];
+  // Pawns jump 2 or 3 squares, and promote to queen or wildebeest
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
+    const lastRank = (color == "w" ? 0 : sizeX-1);
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+      : [V.PAWN];
 
-               if (this.board[x+shiftX][y] == V.EMPTY)
-               {
-                       // One square forward
-                       for (let piece of finalPieces)
-                               moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
-                       if (startRanks.includes(x))
-                       {
-                               if (this.board[x+2*shiftX][y] == V.EMPTY)
-                               {
-                                       // Two squares jump
-                                       moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
-                                       if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
-                                       {
-                                               // Three squares jump
-                                               moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
-                                       }
-                               }
-                       }
-               }
-               // Captures
-               for (let shiftY of [-1,1])
-               {
-                       if (y + shiftY >= 0 && y + shiftY < sizeY
-                               && this.board[x+shiftX][y+shiftY] != V.EMPTY
-                               && this.canTake([x,y], [x+shiftX,y+shiftY]))
-                       {
-                               for (let piece of finalPieces)
-                               {
-                                       moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-                                               {c:color,p:piece}));
-                               }
-                       }
-               }
+    if (this.board[x+shiftX][y] == V.EMPTY)
+    {
+      // One square forward
+      for (let piece of finalPieces)
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+      if (startRanks.includes(x))
+      {
+        if (this.board[x+2*shiftX][y] == V.EMPTY)
+        {
+          // Two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+          if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
+          {
+            // Three squares jump
+            moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
+          }
+        }
+      }
+    }
+    // Captures
+    for (let shiftY of [-1,1])
+    {
+      if (y + shiftY >= 0 && y + shiftY < sizeY
+        && this.board[x+shiftX][y+shiftY] != V.EMPTY
+        && this.canTake([x,y], [x+shiftX,y+shiftY]))
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+      }
+    }
 
-               // En passant
-               const Lep = this.epSquares.length;
-               const epSquare = this.epSquares[Lep-1];
-               if (!!epSquare)
-               {
-                       for (let epsq of epSquare)
-                       {
-                               // TODO: some redundant checks
-                               if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
-                               {
-                                       var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
-                                       // WARNING: the captured pawn may be diagonally behind us,
-                                       // if it's a 3-squares jump and we take on 1st passing square
-                                       const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
-                                       enpassantMove.vanish.push({
-                                               x: px,
-                                               y: epsq.y,
-                                               p: 'p',
-                                               c: this.getColor(px,epsq.y)
-                                       });
-                                       moves.push(enpassantMove);
-                               }
-                       }
-               }
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep-1];
+    if (!!epSquare)
+    {
+      for (let epsq of epSquare)
+      {
+        // TODO: some redundant checks
+        if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
+        {
+          var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
+          // WARNING: the captured pawn may be diagonally behind us,
+          // if it's a 3-squares jump and we take on 1st passing square
+          const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
+          enpassantMove.vanish.push({
+            x: px,
+            y: epsq.y,
+            p: 'p',
+            c: this.getColor(px,epsq.y)
+          });
+          moves.push(enpassantMove);
+        }
+      }
+    }
 
-               return moves;
-       }
+    return moves;
+  }
 
-       // TODO: wildebeest castle
+  // TODO: wildebeest castle
 
-       getPotentialCamelMoves(sq)
-       {
-               return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
-       }
+  getPotentialCamelMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
+  }
 
-       getPotentialWildebeestMoves(sq)
-       {
-               return this.getSlideNJumpMoves(
-                       sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
-       }
+  getPotentialWildebeestMoves(sq)
+  {
+    return this.getSlideNJumpMoves(
+      sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+  }
 
-       isAttacked(sq, colors)
-       {
-               return super.isAttacked(sq, colors)
-                       || this.isAttackedByCamel(sq, colors)
-                       || this.isAttackedByWildebeest(sq, colors);
-       }
+  isAttacked(sq, colors)
+  {
+    return super.isAttacked(sq, colors)
+      || this.isAttackedByCamel(sq, colors)
+      || this.isAttackedByWildebeest(sq, colors);
+  }
 
-       isAttackedByCamel(sq, colors)
-       {
-               return this.isAttackedBySlideNJump(sq, colors,
-                       V.CAMEL, V.steps[V.CAMEL], "oneStep");
-       }
+  isAttackedByCamel(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors,
+      V.CAMEL, V.steps[V.CAMEL], "oneStep");
+  }
 
-       isAttackedByWildebeest(sq, colors)
-       {
-               return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
-                       V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
-       }
+  isAttackedByWildebeest(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
+      V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+  }
 
   getCurrentScore()
   {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
-               // No valid move: game is lost (stalemate is a win)
-               return (this.turn == "w" ? "0-1" : "1-0");
-       }
+    // No valid move: game is lost (stalemate is a win)
+    return (this.turn == "w" ? "0-1" : "1-0");
+  }
 
-       static get VALUES() {
-               return Object.assign(
-                       ChessRules.VALUES,
-                       {'c': 3, 'w': 7} //experimental
-               );
-       }
+  static get VALUES() {
+    return Object.assign(
+      ChessRules.VALUES,
+      {'c': 3, 'w': 7} //experimental
+    );
+  }
 
-       static get SEARCH_DEPTH() { return 2; }
+  static get SEARCH_DEPTH() { return 2; }
 
-       static GenRandInitFen()
-       {
-               let pieces = { "w": new Array(10), "b": new Array(10) };
-               for (let c of ["w","b"])
-               {
-                       let positions = ArrayFun.range(11);
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(10), "b": new Array(10) };
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(11);
 
-                       // Get random squares for bishops + camels (different colors)
-                       let randIndexes = sample(ArrayFun.range(6), 2)
+      // Get random squares for bishops + camels (different colors)
+      let randIndexes = sample(ArrayFun.range(6), 2)
         .map(i => { return 2*i; });
-                       let bishop1Pos = positions[randIndexes[0]];
-                       let camel1Pos = positions[randIndexes[1]];
-                       // The second bishop (camel) must be on a square of different color
-                       let randIndexes_tmp = sample(ArrayFun.range(5), 2)
+      let bishop1Pos = positions[randIndexes[0]];
+      let camel1Pos = positions[randIndexes[1]];
+      // The second bishop (camel) must be on a square of different color
+      let randIndexes_tmp = sample(ArrayFun.range(5), 2)
         .map(i => { return 2*i+1; });
-                       let bishop2Pos = positions[randIndexes_tmp[0]];
-                       let camel2Pos = positions[randIndexes_tmp[1]];
-                       for (let idx of randIndexes.concat(randIndexes_tmp)
-                               .sort((a,b) => { return b-a; })) //largest indices first
-                       {
-                               positions.splice(idx, 1);
-                       }
+      let bishop2Pos = positions[randIndexes_tmp[0]];
+      let camel2Pos = positions[randIndexes_tmp[1]];
+      for (let idx of randIndexes.concat(randIndexes_tmp)
+        .sort((a,b) => { return b-a; })) //largest indices first
+      {
+        positions.splice(idx, 1);
+      }
 
-                       let randIndex = randInt(7);
-                       let knight1Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
-                       randIndex = randInt(6);
-                       let knight2Pos = positions[randIndex];
-                       positions.splice(randIndex, 1);
+      let randIndex = randInt(7);
+      let knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(6);
+      let knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-                       randIndex = randInt(5);
-                       let queenPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      let queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-                       // Random square for wildebeest
-                       randIndex = randInt(4);
-                       let wildebeestPos = positions[randIndex];
-                       positions.splice(randIndex, 1);
+      // Random square for wildebeest
+      randIndex = randInt(4);
+      let wildebeestPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-                       let rook1Pos = positions[0];
-                       let kingPos = positions[1];
-                       let rook2Pos = positions[2];
+      let rook1Pos = positions[0];
+      let kingPos = positions[1];
+      let rook2Pos = positions[2];
 
-                       pieces[c][rook1Pos] = 'r';
-                       pieces[c][knight1Pos] = 'n';
-                       pieces[c][bishop1Pos] = 'b';
-                       pieces[c][queenPos] = 'q';
-                       pieces[c][camel1Pos] = 'c';
-                       pieces[c][camel2Pos] = 'c';
-                       pieces[c][wildebeestPos] = 'w';
-                       pieces[c][kingPos] = 'k';
-                       pieces[c][bishop2Pos] = 'b';
-                       pieces[c][knight2Pos] = 'n';
-                       pieces[c][rook2Pos] = 'r';
-               }
-               return pieces["b"].join("") +
-                       "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
-                       pieces["w"].join("").toUpperCase() +
-                       " w 0 1111 -";
-       }
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][camel1Pos] = 'c';
+      pieces[c][camel2Pos] = 'c';
+      pieces[c][wildebeestPos] = 'w';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["b"].join("") +
+      "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 1111 -";
+  }
 }
index db2146a..b8ce071 100644 (file)
@@ -2,228 +2,228 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class ZenRules extends ChessRules
 {
-       // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
-       static get HasEnpassant() { return false; }
-
-       // TODO(?): some duplicated code in 2 next functions
-       getSlideNJumpMoves([x,y], steps, oneStep)
-       {
-               const color = this.getColor(x,y);
-               let moves = [];
-               outerLoop:
-               for (let loop=0; loop<steps.length; loop++)
-               {
-                       const step = steps[loop];
-                       let i = x + step[0];
-                       let j = y + step[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];
-                       }
-                       // No capture check: handled elsewhere (next method)
-               }
-               return moves;
-       }
-
-       // follow steps from x,y until something is met.
-       // if met piece is opponent and same movement (asA): eat it!
-       findCaptures_aux([x,y], asA)
-       {
-               const color = this.getColor(x,y);
-               var moves = [];
-               const steps = asA != V.PAWN
-                       ? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
-                       : color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
-               const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
-               const lastRank = (color == 'w' ? 0 : V.size.x-1);
-               const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-               outerLoop:
-               for (let loop=0; loop<steps.length; loop++)
-               {
-                       const step = steps[loop];
-                       let i = x + step[0];
-                       let j = y + step[1];
-                       while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-                       {
-                               if (oneStep)
-                                       continue outerLoop;
-                               i += step[0];
-                               j += step[1];
-                       }
-                       if (V.OnBoard(i,j) && this.getColor(i,j) == V.GetOppCol(color)
-                               && this.getPiece(i,j) == asA)
-                       {
-                               // eat!
-                               if (this.getPiece(x,y) == V.PAWN && i == lastRank)
-                               {
-                                       // Special case of promotion:
-                                       promotionPieces.forEach(p => {
-                                               moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p}));
-                                       });
-                               }
-                               else
-                               {
-                                       // All other cases
-                                       moves.push(this.getBasicMove([x,y], [i,j]));
-                               }
-                       }
-               }
-               return moves;
-       }
-
-       // Find possible captures from a square: look in every direction!
-       findCaptures(sq)
-       {
-               let moves = [];
-
-               Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN));
-               Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.ROOK));
-               Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.KNIGHT));
-               Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.BISHOP));
-               Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.QUEEN));
-
-               return moves;
-       }
-
-       getPotentialPawnMoves([x,y])
-       {
-               const color = this.getColor(x,y);
-               let moves = [];
-               const [sizeX,sizeY] = [V.size.x,V.size.y];
-               const shift = (color == 'w' ? -1 : 1);
-               const startRank = (color == 'w' ? sizeY-2 : 1);
-               const firstRank = (color == 'w' ? sizeY-1 : 0);
-               const lastRank = (color == "w" ? 0 : sizeY-1);
-
-               if (x+shift != lastRank)
-               {
-                       // Normal moves
-                       if (this.board[x+shift][y] == V.EMPTY)
-                       {
-                               moves.push(this.getBasicMove([x,y], [x+shift,y]));
-                               if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
-                               {
-                                       //two squares jump
-                                       moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
-                               }
-                       }
-               }
-
-               else //promotion
-               {
-                       let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-                       promotionPieces.forEach(p => {
-                               // Normal move
-                               if (this.board[x+shift][y] == V.EMPTY)
-                                       moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
-                       });
-               }
-
-               // No en passant here
-
-               // Add "zen" captures
-               Array.prototype.push.apply(moves, this.findCaptures([x,y]));
-
-               return moves;
-       }
-
-       getPotentialRookMoves(sq)
-       {
-               let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
-               let captures = this.findCaptures(sq);
-               return noCaptures.concat(captures);
-       }
-
-       getPotentialKnightMoves(sq)
-       {
-               let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
-               let captures = this.findCaptures(sq);
-               return noCaptures.concat(captures);
-       }
-
-       getPotentialBishopMoves(sq)
-       {
-               let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
-               let captures = this.findCaptures(sq);
-               return noCaptures.concat(captures);
-       }
-
-       getPotentialQueenMoves(sq)
-       {
-               let noCaptures = this.getSlideNJumpMoves(
-                       sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
-               let captures = this.findCaptures(sq);
-               return noCaptures.concat(captures);
-       }
-
-       getPotentialKingMoves(sq)
-       {
-               // Initialize with normal moves
-               let noCaptures = this.getSlideNJumpMoves(sq,
-                       V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-               let captures = this.findCaptures(sq);
-               return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
-       }
-
-       getNotation(move)
-       {
-               // Recognize special moves first
-               if (move.appear.length == 2)
-               {
-                       // castle
-                       if (move.end.y < move.start.y)
-                               return "0-0-0";
-                       else
-                               return "0-0";
-               }
-
-               // Translate initial square (because pieces may fly unusually in this variant!)
-               const initialSquare = V.CoordsToSquare(move.start);
-
-               // Translate final square
-               const finalSquare = V.CoordsToSquare(move.end);
-
-               let notation = "";
-               const piece = this.getPiece(move.start.x, move.start.y);
-               if (piece == V.PAWN)
-               {
-                       // pawn move (TODO: enPassant indication)
-                       if (move.vanish.length > 1)
-                       {
-                               // capture
-                               notation = initialSquare + "x" + finalSquare;
-                       }
-                       else //no capture
-                               notation = finalSquare;
-                       if (piece != move.appear[0].p) //promotion
-                               notation += "=" + move.appear[0].p.toUpperCase();
-               }
-
-               else
-               {
-                       // Piece movement
-                       notation = piece.toUpperCase();
-                       if (move.vanish.length > 1)
-                               notation += initialSquare + "x";
-                       notation += finalSquare;
-               }
-               return notation;
-       }
-
-       static get VALUES()
-       {
-               // TODO: experimental
-               return {
-                       'p': 1,
-                       'r': 3,
-                       'n': 2,
-                       'b': 2,
-                       'q': 5,
-                       'k': 1000
-               }
-       }
+  // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
+  static get HasEnpassant() { return false; }
+
+  // TODO(?): some duplicated code in 2 next functions
+  getSlideNJumpMoves([x,y], steps, oneStep)
+  {
+    const color = this.getColor(x,y);
+    let moves = [];
+    outerLoop:
+    for (let loop=0; loop<steps.length; loop++)
+    {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[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];
+      }
+      // No capture check: handled elsewhere (next method)
+    }
+    return moves;
+  }
+
+  // follow steps from x,y until something is met.
+  // if met piece is opponent and same movement (asA): eat it!
+  findCaptures_aux([x,y], asA)
+  {
+    const color = this.getColor(x,y);
+    var moves = [];
+    const steps = asA != V.PAWN
+      ? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
+      : color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
+    const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
+    const lastRank = (color == 'w' ? 0 : V.size.x-1);
+    const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+    outerLoop:
+    for (let loop=0; loop<steps.length; loop++)
+    {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+      {
+        if (oneStep)
+          continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i,j) && this.getColor(i,j) == V.GetOppCol(color)
+        && this.getPiece(i,j) == asA)
+      {
+        // eat!
+        if (this.getPiece(x,y) == V.PAWN && i == lastRank)
+        {
+          // Special case of promotion:
+          promotionPieces.forEach(p => {
+            moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p}));
+          });
+        }
+        else
+        {
+          // All other cases
+          moves.push(this.getBasicMove([x,y], [i,j]));
+        }
+      }
+    }
+    return moves;
+  }
+
+  // Find possible captures from a square: look in every direction!
+  findCaptures(sq)
+  {
+    let moves = [];
+
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.ROOK));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.KNIGHT));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.BISHOP));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.QUEEN));
+
+    return moves;
+  }
+
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.getColor(x,y);
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shift = (color == 'w' ? -1 : 1);
+    const startRank = (color == 'w' ? sizeY-2 : 1);
+    const firstRank = (color == 'w' ? sizeY-1 : 0);
+    const lastRank = (color == "w" ? 0 : sizeY-1);
+
+    if (x+shift != lastRank)
+    {
+      // Normal moves
+      if (this.board[x+shift][y] == V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y]));
+        if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+        {
+          //two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
+        }
+      }
+    }
+
+    else //promotion
+    {
+      let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+      promotionPieces.forEach(p => {
+        // Normal move
+        if (this.board[x+shift][y] == V.EMPTY)
+          moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
+      });
+    }
+
+    // No en passant here
+
+    // Add "zen" captures
+    Array.prototype.push.apply(moves, this.findCaptures([x,y]));
+
+    return moves;
+  }
+
+  getPotentialRookMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialKnightMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialBishopMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialQueenMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(
+      sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialKingMoves(sq)
+  {
+    // Initialize with normal moves
+    let noCaptures = this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
+  }
+
+  getNotation(move)
+  {
+    // Recognize special moves first
+    if (move.appear.length == 2)
+    {
+      // castle
+      if (move.end.y < move.start.y)
+        return "0-0-0";
+      else
+        return "0-0";
+    }
+
+    // Translate initial square (because pieces may fly unusually in this variant!)
+    const initialSquare = V.CoordsToSquare(move.start);
+
+    // Translate final square
+    const finalSquare = V.CoordsToSquare(move.end);
+
+    let notation = "";
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN)
+    {
+      // pawn move (TODO: enPassant indication)
+      if (move.vanish.length > 1)
+      {
+        // capture
+        notation = initialSquare + "x" + finalSquare;
+      }
+      else //no capture
+        notation = finalSquare;
+      if (piece != move.appear[0].p) //promotion
+        notation += "=" + move.appear[0].p.toUpperCase();
+    }
+
+    else
+    {
+      // Piece movement
+      notation = piece.toUpperCase();
+      if (move.vanish.length > 1)
+        notation += initialSquare + "x";
+      notation += finalSquare;
+    }
+    return notation;
+  }
+
+  static get VALUES()
+  {
+    // TODO: experimental
+    return {
+      'p': 1,
+      'r': 3,
+      'n': 2,
+      'b': 2,
+      'q': 5,
+      'k': 1000
+    }
+  }
 }
index a2ce34d..2180509 100644 (file)
@@ -466,7 +466,7 @@ export default {
     },
     newChallenge: async function() {
       if (this.newchallenge.vid == "")
-                   return alert("Please select a variant");
+        return alert("Please select a variant");
       const vname = this.getVname(this.newchallenge.vid);
       const vModule = await import("@/variants/" + vname + ".js");
       window.V = vModule.VariantRules;
index c24e720..0973d32 100644 (file)
@@ -25,7 +25,7 @@ export default {
   data: function() {
     return {
       st: store.state,
-                       display: "live",
+      display: "live",
       games: [],
     };
   },
index f97d925..1fc03c5 100644 (file)
@@ -12,17 +12,17 @@ app.use(favicon(path.join(__dirname, "static", "favicon.ico")));
 
 if (app.get('env') === 'development')
 {
-       // Full logging in development mode
-       app.use(logger('dev'));
+  // Full logging in development mode
+  app.use(logger('dev'));
 }
 else
 {
-       // http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
-       app.set('trust proxy', true);
-       // In prod, only log error responses (https://github.com/expressjs/morgan)
-       app.use(logger('combined', {
-               skip: function (req, res) { return res.statusCode < 400 }
-       }));
+  // http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
+  app.set('trust proxy', true);
+  // In prod, only log error responses (https://github.com/expressjs/morgan)
+  app.use(logger('combined', {
+    skip: function (req, res) { return res.statusCode < 400 }
+  }));
 }
 
 app.use(express.json());
@@ -33,14 +33,14 @@ app.use(express.static(path.join(__dirname, 'static'))); //client "prod" files
 // In development stage the client side has its own server
 if (params.cors.enable)
 {
-       app.use(function(req, res, next) {
-               res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
-               res.header("Access-Control-Allow-Credentials", true); //for cookies
+  app.use(function(req, res, next) {
+    res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
+    res.header("Access-Control-Allow-Credentials", true); //for cookies
     res.header("Access-Control-Allow-Headers",
       "Origin, X-Requested-With, Content-Type, Accept");
-         res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
+    res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
     next();
-       });
+  });
 }
 
 // Routing (AJAX-only)
@@ -60,11 +60,11 @@ app.use(function(err, req, res, next) {
   // render the error page
   res.status(err.status || 500);
   res.send(`
-               <!doctype html>
-               <h1>= message</h1>
-               <h2>= error.status</h2>
-               <pre>#{error.stack}</pre>
-       `);
+    <!doctype html>
+    <h1>= message</h1>
+    <h2>= error.status</h2>
+    <pre>#{error.stack}</pre>
+  `);
 });
 
 module.exports = app;
index 4475199..b3a2c73 100755 (executable)
@@ -30,10 +30,10 @@ var UserModel = require("../models/User");
 var ChallengeModel = require("../models/Challenge");
 var GameModel = require("../models/Game");
 cron.schedule('0 0 0 * * *', function() {
-       // Remove some old users, challenges and games every 24h
-       UserModel.cleanUsersDb();
-       ChallengeModel.removeOld();
-       GameModel.cleanGamesDb();
+  // Remove some old users, challenges and games every 24h
+  UserModel.cleanUsersDb();
+  ChallengeModel.removeOld();
+  GameModel.cleanGamesDb();
 });
 
 /**
index 5b4301a..264fac5 100644 (file)
@@ -1,34 +1,34 @@
 module.exports =
 {
-       // For mail sending. NOTE: *no trailing slash*
-       siteURL: "http://localhost:8080",
+  // For mail sending. NOTE: *no trailing slash*
+  siteURL: "http://localhost:8080",
 
-       // To know in which environment the code run
-       env: process.env.NODE_ENV || 'development',
-       
-       // CORS: cross-origin resource sharing options
-       cors: {
-               enable: true,
-               allowedOrigin: "http://localhost:8080",
-       },
+  // To know in which environment the code run
+  env: process.env.NODE_ENV || 'development',
+  
+  // CORS: cross-origin resource sharing options
+  cors: {
+    enable: true,
+    allowedOrigin: "http://localhost:8080",
+  },
 
-       // Lifespan of a (login) cookie
-       cookieExpire: 183*24*60*60*1000, //6 months in milliseconds
+  // Lifespan of a (login) cookie
+  cookieExpire: 183*24*60*60*1000, //6 months in milliseconds
 
-       // Characters in a login token, and period of validity (in milliseconds)
-       token: {
-               length: 16,
-               expire: 30*60*1000, //30 minutes in milliseconds
-       },
+  // Characters in a login token, and period of validity (in milliseconds)
+  token: {
+    length: 16,
+    expire: 30*60*1000, //30 minutes in milliseconds
+  },
 
-       // Email settings
-       mail: {
-               host: "mail_host_address",
-               port: 465, //if secure; otherwise use 587
-               secure: true, //...or false
-               user: "mail_user_name",
-               pass: "mail_password",
-               noreply: "some_noreply_email",
-               contact: "some_contact_email",
-       },
+  // Email settings
+  mail: {
+    host: "mail_host_address",
+    port: 465, //if secure; otherwise use 587
+    secure: true, //...or false
+    user: "mail_user_name",
+    pass: "mail_password",
+    noreply: "some_noreply_email",
+    contact: "some_contact_email",
+  },
 };
index 4659704..b770c57 100644 (file)
@@ -1,21 +1,21 @@
 -- Re-run this script after variants are added
 
 insert or ignore into Variants (name,description) values
-       ('Alice', 'Both sides of the mirror'),
-       ('Antiking', 'Keep antiking in check'),
-       ('Atomic', 'Explosive captures'),
-       ('Baroque', 'Exotic captures'),
-       ('Berolina', 'Pawns move diagonally'),
-       ('Checkered', 'Shared pieces'),
-       ('Chess960', 'Standard rules'),
-       ('Crazyhouse', 'Captures reborn'),
-       ('Dark', 'In the shadow'),
-       ('Extinction', 'Capture all of a kind'),
-       ('Grand', 'Big board'),
-       ('Losers', 'Lose all pieces'),
-       ('Magnetic', 'Laws of attraction'),
-       ('Marseille', 'Move twice'),
-       ('Switching', 'Exchange pieces positions'),
-       ('Upsidedown', 'Head upside down'),
-       ('Wildebeest', 'Balanced sliders & leapers'),
-       ('Zen', 'Reverse captures');
+  ('Alice', 'Both sides of the mirror'),
+  ('Antiking', 'Keep antiking in check'),
+  ('Atomic', 'Explosive captures'),
+  ('Baroque', 'Exotic captures'),
+  ('Berolina', 'Pawns move diagonally'),
+  ('Checkered', 'Shared pieces'),
+  ('Chess960', 'Standard rules'),
+  ('Crazyhouse', 'Captures reborn'),
+  ('Dark', 'In the shadow'),
+  ('Extinction', 'Capture all of a kind'),
+  ('Grand', 'Big board'),
+  ('Losers', 'Lose all pieces'),
+  ('Magnetic', 'Laws of attraction'),
+  ('Marseille', 'Move twice'),
+  ('Switching', 'Exchange pieces positions'),
+  ('Upsidedown', 'Head upside down'),
+  ('Wildebeest', 'Balanced sliders & leapers'),
+  ('Zen', 'Reverse captures');
index ae739d9..99c58ab 100644 (file)
@@ -2,16 +2,16 @@ var gulp = require('gulp');
 var nodemon = require('gulp-nodemon'); //reload server on changes
 
 var nodemonOptions = {
-       script: 'bin/www',
-       ext: 'js',
-       env: { 'NODE_ENV': 'development' },
-       verbose: true,
-       watch: ['./','routes','bin']
+  script: 'bin/www',
+  ext: 'js',
+  env: { 'NODE_ENV': 'development' },
+  verbose: true,
+  watch: ['./','routes','bin']
 };
 
 gulp.task('start', function () {
-       nodemon(nodemonOptions)
-       .on('restart', function () {
-               console.log('restarted!')
-       });
+  nodemon(nodemonOptions)
+  .on('restart', function () {
+    console.log('restarted!')
+  });
 });
index 5e1c799..da59b4a 100644 (file)
@@ -49,11 +49,11 @@ const GameModel =
     return "";
   },
 
-       create: function(vid, fen, timeControl, players, cb)
-       {
-               db.serialize(function() {
-                       let query =
-                               "INSERT INTO Games"
+  create: function(vid, fen, timeControl, players, cb)
+  {
+    db.serialize(function() {
+      let query =
+        "INSERT INTO Games"
         + " (vid, fenStart, fen, score, timeControl, created, drawOffer)"
         + " VALUES (" + vid + ",'" + fen + "','" + fen + "','*','"
         + timeControl + "'," + Date.now() + "," + false + ")";
@@ -68,51 +68,51 @@ const GameModel =
           db.run(query);
         });
         cb(null, {gid: this.lastID});
-                       });
-               });
-       },
+      });
+    });
+  },
 
-       // TODO: queries here could be async, and wait for all to complete
-       getOne: function(id, cb)
-       {
-               db.serialize(function() {
+  // TODO: queries here could be async, and wait for all to complete
+  getOne: function(id, cb)
+  {
+    db.serialize(function() {
       // TODO: optimize queries?
-                       let query =
+      let query =
         // NOTE: g.scoreMsg can be NULL
         // (in this case score = "*" and no reason to look at it)
-                               "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
+        "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
           "g.scoreMsg, v.name AS vname " +
-                               "FROM Games g " +
+        "FROM Games g " +
         "JOIN Variants v " +
         "  ON g.vid = v.id " +
-                               "WHERE g.id = " + id;
-                       db.get(query, (err,gameInfo) => {
-                               if (!!err)
-                                       return cb(err);
-                               query =
-                                       "SELECT p.uid, p.color, u.name " +
-                                       "FROM Players p " +
+        "WHERE g.id = " + id;
+      db.get(query, (err,gameInfo) => {
+        if (!!err)
+          return cb(err);
+        query =
+          "SELECT p.uid, p.color, u.name " +
+          "FROM Players p " +
           "JOIN Users u " +
           "  ON p.uid = u.id " +
-                                       "WHERE p.gid = " + id;
-                               db.all(query, (err2,players) => {
-                                       if (!!err2)
-                                               return cb(err2);
-                                       query =
-                                               "SELECT squares, played, idx " +
-                                               "FROM Moves " +
-                                               "WHERE gid = " + id;
-                                       db.all(query, (err3,moves) => {
-                                               if (!!err3)
-                                                       return cb(err3);
-                             query =
+          "WHERE p.gid = " + id;
+        db.all(query, (err2,players) => {
+          if (!!err2)
+            return cb(err2);
+          query =
+            "SELECT squares, played, idx " +
+            "FROM Moves " +
+            "WHERE gid = " + id;
+          db.all(query, (err3,moves) => {
+            if (!!err3)
+              return cb(err3);
+            query =
               "SELECT msg, name, added " +
               "FROM Chats " +
               "WHERE gid = " + id;
-                             db.all(query, (err4,chats) => {
-                                                 if (!!err4)
-                                                         return cb(err4);
-                                                 const game = Object.assign({},
+            db.all(query, (err4,chats) => {
+              if (!!err4)
+                return cb(err4);
+              const game = Object.assign({},
                 gameInfo,
                 {
                   players: players,
@@ -120,41 +120,41 @@ const GameModel =
                   chats: chats,
                 }
               );
-                                                 return cb(null, game);
+              return cb(null, game);
             });
-                                       });
-                               });
-                       });
-               });
-       },
+          });
+        });
+      });
+    });
+  },
 
-       getByUser: function(uid, excluded, cb)
-       {
-               db.serialize(function() {
-                       // Next query is fine because a player appear at most once in a game
-                       const query =
-                               "SELECT gid " +
-                               "FROM Players " +
-                               "WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
-                       db.all(query, (err,gameIds) => {
-                               if (!!err)
-                                       return cb(err);
+  getByUser: function(uid, excluded, cb)
+  {
+    db.serialize(function() {
+      // Next query is fine because a player appear at most once in a game
+      const query =
+        "SELECT gid " +
+        "FROM Players " +
+        "WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
+      db.all(query, (err,gameIds) => {
+        if (!!err)
+          return cb(err);
         gameIds = gameIds || []; //might be empty
-                               let gameArray = [];
-                               for (let i=0; i<gameIds.length; i++)
-                               {
-                                       GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
-                                               if (!!err2)
-                                                       return cb(err2);
-                                               gameArray.push(game);
-                                               // Call callback function only when gameArray is complete:
-                                               if (i == gameIds.length - 1)
-                                                       return cb(null, gameArray);
-                                       });
-                               }
-                       });
-               });
-       },
+        let gameArray = [];
+        for (let i=0; i<gameIds.length; i++)
+        {
+          GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
+            if (!!err2)
+              return cb(err2);
+            gameArray.push(game);
+            // Call callback function only when gameArray is complete:
+            if (i == gameIds.length - 1)
+              return cb(null, gameArray);
+          });
+        }
+      });
+    });
+  },
 
   getPlayers: function(id, cb)
   {
@@ -193,7 +193,7 @@ const GameModel =
   // obj can have fields move, chat, fen, drawOffer and/or score
   update: function(id, obj)
   {
-               db.parallelize(function() {
+    db.parallelize(function() {
       let query =
         "UPDATE Games " +
         "SET ";
@@ -224,35 +224,35 @@ const GameModel =
       }
       if (!!obj.chat)
       {
-                         query =
-               "INSERT INTO Chats (gid, msg, name, added) VALUES ("
+        query =
+          "INSERT INTO Chats (gid, msg, name, added) VALUES ("
             + id + ",?,'" + obj.chat.name + "'," + Date.now() + ")";
         db.run(query, obj.chat.msg);
       }
     });
   },
 
-       remove: function(id)
-       {
-               db.parallelize(function() {
-                       let query =
-                               "DELETE FROM Games " +
-                               "WHERE id = " + id;
-                       db.run(query);
-                       query =
-                               "DELETE FROM Players " +
-                               "WHERE gid = " + id;
-                       db.run(query);
-                       query =
-                               "DELETE FROM Moves " +
-                               "WHERE gid = " + id;
-                       db.run(query);
-                       query =
-                               "DELETE FROM Chats " +
-                               "WHERE gid = " + id;
-                       db.run(query);
-               });
-       },
+  remove: function(id)
+  {
+    db.parallelize(function() {
+      let query =
+        "DELETE FROM Games " +
+        "WHERE id = " + id;
+      db.run(query);
+      query =
+        "DELETE FROM Players " +
+        "WHERE gid = " + id;
+      db.run(query);
+      query =
+        "DELETE FROM Moves " +
+        "WHERE gid = " + id;
+      db.run(query);
+      query =
+        "DELETE FROM Chats " +
+        "WHERE gid = " + id;
+      db.run(query);
+    });
+  },
 
   cleanGamesDb: function()
   {
index c051615..1a5397d 100644 (file)
@@ -17,53 +17,53 @@ var sendEmail = require('../utils/mailer');
 
 const UserModel =
 {
-       checkNameEmail: function(o)
-       {
-               if (typeof o.name === "string")
-               {
-                       if (o.name.length == 0)
-                               return "Empty name";
-                       if (!o.name.match(/^[\w]+$/))
-                               return "Bad characters in name";
-               }
-               if (typeof o.email === "string")
-               {
-                       if (o.email.length == 0)
-                               return "Empty email";
-                       if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
-                               return "Bad characters in email";
-               }
+  checkNameEmail: function(o)
+  {
+    if (typeof o.name === "string")
+    {
+      if (o.name.length == 0)
+        return "Empty name";
+      if (!o.name.match(/^[\w]+$/))
+        return "Bad characters in name";
+    }
+    if (typeof o.email === "string")
+    {
+      if (o.email.length == 0)
+        return "Empty email";
+      if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+        return "Bad characters in email";
+    }
     return ""; //NOTE: not required, but more consistent... (?!)
-       },
+  },
 
-       // NOTE: parameters are already cleaned (in controller), thus no sanitization here
-       create: function(name, email, notify, callback)
-       {
-               db.serialize(function() {
-                       const insertQuery =
-                               "INSERT INTO Users " +
-                               "(name, email, notify, created) VALUES " +
-                               "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
-                       db.run(insertQuery, err => {
-                               if (!!err)
-                                       return callback(err);
-                               db.get("SELECT last_insert_rowid() AS rowid", callback);
-                       });
-               });
-       },
+  // NOTE: parameters are already cleaned (in controller), thus no sanitization here
+  create: function(name, email, notify, callback)
+  {
+    db.serialize(function() {
+      const insertQuery =
+        "INSERT INTO Users " +
+        "(name, email, notify, created) VALUES " +
+        "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
+      db.run(insertQuery, err => {
+        if (!!err)
+          return callback(err);
+        db.get("SELECT last_insert_rowid() AS rowid", callback);
+      });
+    });
+  },
 
-       // Find one user (by id, name, email, or token)
-       getOne: function(by, value, cb)
-       {
-               const delimiter = (typeof value === "string" ? "'" : "");
-               db.serialize(function() {
-                       const query =
-                               "SELECT * " +
-                               "FROM Users " +
-                               "WHERE " + by + " = " + delimiter + value + delimiter;
-                       db.get(query, cb);
-               });
-       },
+  // Find one user (by id, name, email, or token)
+  getOne: function(by, value, cb)
+  {
+    const delimiter = (typeof value === "string" ? "'" : "");
+    db.serialize(function() {
+      const query =
+        "SELECT * " +
+        "FROM Users " +
+        "WHERE " + by + " = " + delimiter + value + delimiter;
+      db.get(query, cb);
+    });
+  },
 
   getByIds: function(ids, cb) {
     db.serialize(function() {
@@ -75,65 +75,65 @@ const UserModel =
     });
   },
 
-       /////////
-       // MODIFY
+  /////////
+  // MODIFY
 
-       setLoginToken: function(token, uid, cb)
-       {
-               db.serialize(function() {
-                       const query =
-                               "UPDATE Users " +
-                               "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
-                               "WHERE id = " + uid;
-                       db.run(query, cb);
-               });
-       },
+  setLoginToken: function(token, uid, cb)
+  {
+    db.serialize(function() {
+      const query =
+        "UPDATE Users " +
+        "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
+        "WHERE id = " + uid;
+      db.run(query, cb);
+    });
+  },
 
-       // Set session token only if empty (first login)
-       // NOTE: weaker security (but avoid to re-login everywhere after each logout)
-       // TODO: option would be to reset all tokens periodically, e.g. every 3 months
+  // Set session token only if empty (first login)
+  // NOTE: weaker security (but avoid to re-login everywhere after each logout)
+  // TODO: option would be to reset all tokens periodically, e.g. every 3 months
   trySetSessionToken: function(uid, cb)
-       {
-               // Also empty the login token to invalidate future attempts
-               db.serialize(function() {
-                       const querySessionToken =
-                               "SELECT sessionToken " +
-                               "FROM Users " +
-                               "WHERE id = " + uid;
-                       db.get(querySessionToken, (err,ret) => {
-                               if (!!err)
-                                       return cb(err);
-                               const token = ret.sessionToken || genToken(params.token.length);
-                               const queryUpdate =
-                                       "UPDATE Users " +
-                                       "SET loginToken = NULL" +
-                                       (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
-                                       "WHERE id = " + uid;
-                               db.run(queryUpdate);
-                               cb(null, token);
-                       });
-               });
-       },
+  {
+    // Also empty the login token to invalidate future attempts
+    db.serialize(function() {
+      const querySessionToken =
+        "SELECT sessionToken " +
+        "FROM Users " +
+        "WHERE id = " + uid;
+      db.get(querySessionToken, (err,ret) => {
+        if (!!err)
+          return cb(err);
+        const token = ret.sessionToken || genToken(params.token.length);
+        const queryUpdate =
+          "UPDATE Users " +
+          "SET loginToken = NULL" +
+          (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
+          "WHERE id = " + uid;
+        db.run(queryUpdate);
+        cb(null, token);
+      });
+    });
+  },
 
-       updateSettings: function(user, cb)
-       {
-               db.serialize(function() {
-                       const query =
-                               "UPDATE Users " +
-                               "SET name = '" + user.name + "'" +
-                               ", email = '" + user.email + "'" +
-                               ", notify = " + user.notify + " " +
-                               "WHERE id = " + user.id;
-                       db.run(query, cb);
-               });
-       },
+  updateSettings: function(user, cb)
+  {
+    db.serialize(function() {
+      const query =
+        "UPDATE Users " +
+        "SET name = '" + user.name + "'" +
+        ", email = '" + user.email + "'" +
+        ", notify = " + user.notify + " " +
+        "WHERE id = " + user.id;
+      db.run(query, cb);
+    });
+  },
 
   /////////////////
   // NOTIFICATIONS
 
   tryNotify: function(oppId, message)
   {
-               UserModel.getOne("id", oppId, (err,opp) => {
+    UserModel.getOne("id", oppId, (err,opp) => {
       if (!err || !opp.notify)
         return; //error is ignored here (TODO: should be logged)
       const subject = "vchess.club - notification";
index 20bce4a..dccc89b 100644 (file)
@@ -9,17 +9,17 @@ var db = require("../utils/database");
 
 const VariantModel =
 {
-       getAll: function(callback)
-       {
-               db.serialize(function() {
-                       const query =
-                               "SELECT * " +
-                               "FROM Variants";
-                       db.all(query, callback);
-               });
-       },
+  getAll: function(callback)
+  {
+    db.serialize(function() {
+      const query =
+        "SELECT * " +
+        "FROM Variants";
+      db.all(query, callback);
+    });
+  },
 
-       //create, update, delete: directly in DB
+  //create, update, delete: directly in DB
 }
 
 module.exports = VariantModel;
index e9d9ab4..24bfc82 100644 (file)
@@ -9,10 +9,10 @@ var params = require("../config/parameters");
 // From main hall, start game between players 0 and 1
 router.post("/games", access.logged, access.ajax, (req,res) => {
   const gameInfo = req.body.gameInfo;
-       if (!Array.isArray(gameInfo.players) ||
+  if (!Array.isArray(gameInfo.players) ||
     !gameInfo.players.some(p => p.id == req.userId))
   {
-               return res.json({errmsg: "Cannot start someone else's game"});
+    return res.json({errmsg: "Cannot start someone else's game"});
   }
   const cid = req.body.cid;
   // Check all entries of gameInfo + cid:
@@ -25,29 +25,29 @@ router.post("/games", access.logged, access.ajax, (req,res) => {
   if (!!error)
     return res.json({errmsg:error});
   ChallengeModel.remove(cid);
-       GameModel.create(
+  GameModel.create(
     gameInfo.vid, gameInfo.fen, gameInfo.timeControl, gameInfo.players,
-               (err,ret) => {
-                       access.checkRequest(res, err, ret, "Cannot create game", () => {
+    (err,ret) => {
+      access.checkRequest(res, err, ret, "Cannot create game", () => {
         const oppIdx = (gameInfo.players[0].id == req.userId ? 1 : 0);
         const oppId = gameInfo.players[oppIdx].id;
         UserModel.tryNotify(oppId,
           "New game: " + params.siteURL + "/game/" + ret.gid);
-                               res.json({gameId: ret.gid});
-                       });
-               }
-       );
+        res.json({gameId: ret.gid});
+      });
+    }
+  );
 });
 
 router.get("/games", access.ajax, (req,res) => {
-       const gameId = req.query["gid"];
-       if (!!gameId)
+  const gameId = req.query["gid"];
+  if (!!gameId)
   {
     GameModel.getOne(gameId, (err,game) => {
-                 access.checkRequest(res, err, game, "Game not found", () => {
+      access.checkRequest(res, err, game, "Game not found", () => {
         res.json({game: game});
-                 });
-         });
+      });
+    });
   }
   else
   {
@@ -55,10 +55,10 @@ router.get("/games", access.ajax, (req,res) => {
     const userId = req.query["uid"];
     const excluded = !!req.query["excluded"];
     GameModel.getByUser(userId, excluded, (err,games) => {
-                       if (!!err)
+      if (!!err)
         return res.json({errmsg: err.errmsg || err.toString()});
-                       res.json({games: games});
-         });
+      res.json({games: games});
+    });
   }
 });
 
@@ -70,11 +70,11 @@ router.put("/games", access.logged, access.ajax, (req,res) => {
   if (!gid.toString().match(/^[0-9]+$/))
     error = "Wrong game ID";
   const obj = req.body.newObj;
-       error = GameModel.checkGameUpdate(obj);
+  error = GameModel.checkGameUpdate(obj);
   if (!!error)
     return res.json({errmsg: error});
-       GameModel.update(gid, obj, (err) => {
-               if (!!err)
+  GameModel.update(gid, obj, (err) => {
+    if (!!err)
       return res.json(err);
     // Notify opponent if he enabled notifications:
     GameModel.getPlayers(gid, (err2,players) => {
@@ -85,7 +85,7 @@ router.put("/games", access.logged, access.ajax, (req,res) => {
         "New move in game: " + params.siteURL + "/game/" + gid);
     });
     res.json({});
-       });
+  });
 });
 
 module.exports = router;
index b3c158e..cd93b9f 100644 (file)
@@ -6,19 +6,19 @@ const params = require(__dirname.replace("/routes", "/config/parameters"));
 
 // Send a message through contact form
 router.post("/messages", (req,res,next) => {
-       if (!req.xhr)
-               return res.json({errmsg: "Unauthorized access"});
+  if (!req.xhr)
+    return res.json({errmsg: "Unauthorized access"});
   const from = req.body["email"];
-       const subject = req.body["subject"];
-       const body = req.body["content"];
+  const subject = req.body["subject"];
+  const body = req.body["content"];
 
-       // TODO: sanitize ?
-       mailer(from, params.mail.contact, subject, body, err => {
-               if (!!err)
-                       return res.json({errmsg:err});
-               // OK, everything fine
-               res.json({}); //ignored
-       });
+  // TODO: sanitize ?
+  mailer(from, params.mail.contact, subject, body, err => {
+    if (!!err)
+      return res.json({errmsg:err});
+    // OK, everything fine
+    res.json({}); //ignored
+  });
 });
 
 module.exports = router;
index 1d553db..129cda2 100644 (file)
@@ -18,7 +18,7 @@ router.get("/whoami", access.ajax, (req,res) => {
     });
   };
   const anonymous = {name:"", email:"", id:0, notify:false};
-       if (!req.cookies.token)
+  if (!req.cookies.token)
     return callback(anonymous);
   UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
     if (!!err || !user)
@@ -41,101 +41,101 @@ router.get("/users", access.ajax, (req,res) => {
 // to: object user (to who we send an email)
 function setAndSendLoginToken(subject, to, res)
 {
-       // Set login token and send welcome(back) email with auth link
-       const token = genToken(params.token.length);
-       UserModel.setLoginToken(token, to.id, err => {
-               if (!!err)
-                       return res.json({errmsg: err.toString()});
-               const body =
-                       "Hello " + to.name + "!\\n" +
-                       "Access your account here: " +
-                       params.siteURL + "/#/authenticate/" + token + "\\n" +
-                       "Token will expire in " + params.token.expire/(1000*60) + " minutes."
-               sendEmail(params.mail.noreply, to.email, subject, body, err => {
-                       res.json(err || {});
-               });
-       });
+  // Set login token and send welcome(back) email with auth link
+  const token = genToken(params.token.length);
+  UserModel.setLoginToken(token, to.id, err => {
+    if (!!err)
+      return res.json({errmsg: err.toString()});
+    const body =
+      "Hello " + to.name + "!\\n" +
+      "Access your account here: " +
+      params.siteURL + "/#/authenticate/" + token + "\\n" +
+      "Token will expire in " + params.token.expire/(1000*60) + " minutes."
+    sendEmail(params.mail.noreply, to.email, subject, body, err => {
+      res.json(err || {});
+    });
+  });
 }
 
 router.post('/register', access.unlogged, access.ajax, (req,res) => {
-       const name = req.body.name;
-       const email = req.body.email;
-       const notify = !!req.body.notify;
-       const error = UserModel.checkNameEmail({name: name, email: email});
-       if (!!error)
-               return res.json({errmsg: error});
-       UserModel.create(name, email, notify, (err,uid) => {
-               if (!!err)
-                       return res.json({errmsg: err.toString()});
-               const user = {
-                       id: uid["rowid"],
-                       name: name,
-                       email: email,
-               };
-               setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
-       });
+  const name = req.body.name;
+  const email = req.body.email;
+  const notify = !!req.body.notify;
+  const error = UserModel.checkNameEmail({name: name, email: email});
+  if (!!error)
+    return res.json({errmsg: error});
+  UserModel.create(name, email, notify, (err,uid) => {
+    if (!!err)
+      return res.json({errmsg: err.toString()});
+    const user = {
+      id: uid["rowid"],
+      name: name,
+      email: email,
+    };
+    setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
+  });
 });
 
 router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => {
-       const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
-       const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
-       const error = UserModel.checkNameEmail({[type]: nameOrEmail});
-       if (!!error)
-               return res.json({errmsg: error});
-       UserModel.getOne(type, nameOrEmail, (err,user) => {
-               access.checkRequest(res, err, user, "Unknown user", () => {
-                       setAndSendLoginToken("Token for " + params.siteURL, user, res);
-               });
-       });
+  const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
+  const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
+  const error = UserModel.checkNameEmail({[type]: nameOrEmail});
+  if (!!error)
+    return res.json({errmsg: error});
+  UserModel.getOne(type, nameOrEmail, (err,user) => {
+    access.checkRequest(res, err, user, "Unknown user", () => {
+      setAndSendLoginToken("Token for " + params.siteURL, user, res);
+    });
+  });
 });
 
 router.get('/authenticate', access.unlogged, access.ajax, (req,res) => {
   UserModel.getOne("loginToken", req.query.token, (err,user) => {
-               access.checkRequest(res, err, user, "Invalid token", () => {
+    access.checkRequest(res, err, user, "Invalid token", () => {
       // If token older than params.tokenExpire, do nothing
-                       if (Date.now() > user.loginTime + params.token.expire)
-                               return res.json({errmsg: "Token expired"});
-                       // Generate session token (if not exists) + destroy login token
-                       UserModel.trySetSessionToken(user.id, (err,token) => {
-                               if (!!err)
-                                       return res.json({errmsg: err.toString()});
-                               // Set cookie
+      if (Date.now() > user.loginTime + params.token.expire)
+        return res.json({errmsg: "Token expired"});
+      // Generate session token (if not exists) + destroy login token
+      UserModel.trySetSessionToken(user.id, (err,token) => {
+        if (!!err)
+          return res.json({errmsg: err.toString()});
+        // Set cookie
         res.cookie("token", token, {
-                                       httpOnly: true,
-                                       secure: !!params.siteURL.match(/^https/),
-                                       maxAge: params.cookieExpire,
-                               });
-                               res.json({
+          httpOnly: true,
+          secure: !!params.siteURL.match(/^https/),
+          maxAge: params.cookieExpire,
+        });
+        res.json({
           id: user.id,
           name: user.name,
           email: user.email,
           notify: user.notify,
         });
-                       });
-               });
-       });
+      });
+    });
+  });
 });
 
 router.put('/update', access.logged, access.ajax, (req,res) => {
-       const name = req.body.name;
-       const email = req.body.email;
-       const error = UserModel.checkNameEmail({name: name, email: email});
-       if (!!error)
-               return res.json({errmsg: error});
-       const user = {
-               id: req.userId,
-               name: name,
-               email: email,
-               notify: !!req.body.notify,
-       };
-       UserModel.updateSettings(user, err => {
-               res.json(err ? {errmsg: err.toString()} : {});
-       });
+  const name = req.body.name;
+  const email = req.body.email;
+  const error = UserModel.checkNameEmail({name: name, email: email});
+  if (!!error)
+    return res.json({errmsg: error});
+  const user = {
+    id: req.userId,
+    name: name,
+    email: email,
+    notify: !!req.body.notify,
+  };
+  UserModel.updateSettings(user, err => {
+    res.json(err ? {errmsg: err.toString()} : {});
+  });
 });
 
 router.get('/logout', access.logged, access.ajax, (req,res) => {
-       res.clearCookie("token");
-       res.json({});
+  res.clearCookie("token");
+  res.json({});
 });
 
 module.exports = router;
index f52c6a4..8153c7a 100644 (file)
@@ -6,11 +6,11 @@ const VariantModel = require("../models/Variant");
 const access = require("../utils/access");
 
 router.get('/variants', access.ajax, function(req, res, next) {
-       VariantModel.getAll((err,variants) => {
-               if (!!err)
-                       return next(err);
-               res.json({variantArray:variants});
-       });
+  VariantModel.getAll((err,variants) => {
+    if (!!err)
+      return next(err);
+    res.json({variantArray:variants});
+  });
 });
 
 module.exports = router;
index 2e2fa92..d51c4b7 100644 (file)
@@ -2,65 +2,65 @@ var UserModel = require("../models/User");
 
 module.exports =
 {
-       // Prevent access to "users pages"
-       logged: function(req, res, next) {
-               const callback = () => {
-                       if (!loggedIn)
-                               return res.json({errmsg: "Not logged in"});
-                       next();
-               };
-               let loggedIn = undefined;
-               if (!req.cookies.token)
-               {
-                       loggedIn = false;
-                       callback();
-               }
-               else
-               {
-                       UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
-                               if (!!user)
-                               {
-                                       req.userId = user.id;
-                                       req.userName = user.name;
-                                       loggedIn = true;
-                               }
-                               else
-                               {
-                                       // Token in cookies presumably wrong: erase it
-                                       res.clearCookie("token");
-                                       loggedIn = false;
-                               }
-                               callback();
-                       });
-               }
-       },
+  // Prevent access to "users pages"
+  logged: function(req, res, next) {
+    const callback = () => {
+      if (!loggedIn)
+        return res.json({errmsg: "Not logged in"});
+      next();
+    };
+    let loggedIn = undefined;
+    if (!req.cookies.token)
+    {
+      loggedIn = false;
+      callback();
+    }
+    else
+    {
+      UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
+        if (!!user)
+        {
+          req.userId = user.id;
+          req.userName = user.name;
+          loggedIn = true;
+        }
+        else
+        {
+          // Token in cookies presumably wrong: erase it
+          res.clearCookie("token");
+          loggedIn = false;
+        }
+        callback();
+      });
+    }
+  },
 
-       // Prevent access to "anonymous pages"
-       unlogged: function(req, res, next) {
-               // Just a quick heuristic, which should be enough
-               const loggedIn = !!req.cookies.token;
-               if (loggedIn)
-                       return res.json({errmsg: "Already logged in"});
-               next();
-       },
+  // Prevent access to "anonymous pages"
+  unlogged: function(req, res, next) {
+    // Just a quick heuristic, which should be enough
+    const loggedIn = !!req.cookies.token;
+    if (loggedIn)
+      return res.json({errmsg: "Already logged in"});
+    next();
+  },
 
-       // Prevent direct access to AJAX results
-       ajax: function(req, res, next) {
+  // Prevent direct access to AJAX results
+  ajax: function(req, res, next) {
     if (!req.xhr)
-                       return res.json({errmsg: "Unauthorized access"});
-               next();
-       },
+      return res.json({errmsg: "Unauthorized access"});
+    next();
+  },
 
-       // Check for errors before callback (continue page loading). TODO: better name.
-       checkRequest: function(res, err, out, msg, cb) {
-               if (!!err)
-                       return res.json({errmsg: err.errmsg || err.toString()});
-               if (!out
-                       || (Array.isArray(out) && out.length == 0)
-                       || (typeof out === "object" && Object.keys(out).length == 0))
-               {
-                       return res.json({errmsg: msg});
-               }
-               cb();
-       },
+  // Check for errors before callback (continue page loading). TODO: better name.
+  checkRequest: function(res, err, out, msg, cb) {
+    if (!!err)
+      return res.json({errmsg: err.errmsg || err.toString()});
+    if (!out
+      || (Array.isArray(out) && out.length == 0)
+      || (typeof out === "object" && Object.keys(out).length == 0))
+    {
+      return res.json({errmsg: msg});
+    }
+    cb();
+  },
 }
index ae7c7a6..2904f2a 100644 (file)
@@ -2,7 +2,7 @@ const sqlite3 = require('sqlite3');
 const params = require("../config/parameters")
 
 if (params.env == "development")
-       sqlite3.verbose();
+  sqlite3.verbose();
 
 const DbPath = __dirname.replace("/utils", "/db/vchess.sqlite");
 const db = new sqlite3.Database(DbPath);
index 86e647c..9a42a17 100644 (file)
@@ -3,42 +3,42 @@ const params = require("../config/parameters");
 
 module.exports = function(from, to, subject, body, cb)
 {
-       // Avoid the actual sending in development mode
-       if (params.env === 'development')
-       {
-               console.log("New mail: from " + from + " / to " + to);
-               console.log("Subject: " + subject);
-               let msgText = body.split('\\n');
-               msgText.forEach(msg => { console.log(msg); });
-               return cb();
-       }
+  // Avoid the actual sending in development mode
+  if (params.env === 'development')
+  {
+    console.log("New mail: from " + from + " / to " + to);
+    console.log("Subject: " + subject);
+    let msgText = body.split('\\n');
+    msgText.forEach(msg => { console.log(msg); });
+    return cb();
+  }
 
   // Create reusable transporter object using the default SMTP transport
-       const transporter = nodemailer.createTransport({
-               host: params.mail.host,
-               port: params.mail.port,
-               secure: params.mail.secure,
-               auth: {
-                       user: params.mail.user,
-                       pass: params.mail.pass
-               }
-       });
+  const transporter = nodemailer.createTransport({
+    host: params.mail.host,
+    port: params.mail.port,
+    secure: params.mail.secure,
+    auth: {
+      user: params.mail.user,
+      pass: params.mail.pass
+    }
+  });
 
-       // Setup email data with unicode symbols
-       const mailOptions = {
-               from: params.mail.noreply,
-               to: to,
-               subject: subject,
-               text: body,
+  // Setup email data with unicode symbols
+  const mailOptions = {
+    from: params.mail.noreply,
+    to: to,
+    subject: subject,
+    text: body,
     replyTo: from,
   };
 
-       // Send mail with the defined transport object
-       transporter.sendMail(mailOptions, (error, info) => {
-               if (!!error)
-                       return cb(error);
+  // Send mail with the defined transport object
+  transporter.sendMail(mailOptions, (error, info) => {
+    if (!!error)
+      return cb(error);
     // Ignore info. Option:
-               //console.log('Message sent: %s', info.messageId);
-               return cb();
+    //console.log('Message sent: %s', info.messageId);
+    return cb();
   });
 }
index b549198..858bc3b 100644 (file)
@@ -1,13 +1,13 @@
 function randString()
 {
-       return Math.random().toString(36).substr(2); // remove `0.`
+  return Math.random().toString(36).substr(2); // remove `0.`
 }
 
 module.exports = function(tlen)
 {
-       let res = "";
-       let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
-       for (let i = 0; i < nbRands; i++)
-               res += randString();
-       return res.substr(0, tlen);
+  let res = "";
+  let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
+  for (let i = 0; i < nbRands; i++)
+    res += randString();
+  return res.substr(0, tlen);
 }