Thai Notations (#563)
authorAda Joule <ada.fulmina@gmail.com>
Mon, 26 Dec 2022 17:19:58 +0000 (00:19 +0700)
committerGitHub <noreply@github.com>
Mon, 26 Dec 2022 17:19:58 +0000 (18:19 +0100)
src/apiutil.h
src/ffishjs.cpp
src/pyffish.cpp
test.py

index 35983b5..86bd208 100644 (file)
@@ -46,6 +46,9 @@ enum Notation {
     NOTATION_JANGGI,
     // https://en.wikipedia.org/wiki/Xiangqi#Notation
     NOTATION_XIANGQI_WXF,
+    // https://web.archive.org/web/20180817205956/http://bgsthai.com/2018/05/07/lawofthaichessc/
+    NOTATION_THAI_SAN,
+    NOTATION_THAI_LAN,
 };
 
 inline Notation default_notation(const Variant* v) {
@@ -64,6 +67,9 @@ enum Termination {
     VARIANT_END,
 };
 
+const std::array<std::string, 12> THAI_FILES = {"ก", "ข", "ค", "ง", "จ", "ฉ", "ช", "ญ", "ต", "ถ", "ธ", "น"};
+const std::array<std::string, 12> THAI_RANKS = {"๑", "๒", "๓", "๔", "๕", "๖", "๗", "๘", "๙", "๑๐", "๑๑", "๑๒"};
+
 namespace SAN {
 
 enum Disambiguation {
@@ -77,6 +83,10 @@ inline bool is_shogi(Notation n) {
     return n == NOTATION_SHOGI_HOSKING || n == NOTATION_SHOGI_HODGES || n == NOTATION_SHOGI_HODGES_NUMBER;
 }
 
+inline bool is_thai(Notation n) {
+    return n == NOTATION_THAI_SAN || n == NOTATION_THAI_LAN;
+}
+
 // is there more than one file with a pair of pieces?
 inline bool multi_tandem(Bitboard b) {
     int tandems = 0;
@@ -86,13 +96,34 @@ inline bool multi_tandem(Bitboard b) {
     return tandems >= 2;
 }
 
+inline std::string piece_to_thai_char(Piece pc, bool promoted) {
+    switch(type_of(pc)) {
+        case KING:
+            return "ข";
+        case KHON:
+            return "ค";
+        case FERS:
+            return promoted ? "ง" : "ม็";
+        case KNIGHT:
+            return "ม";
+        case ROOK:
+            return "ร";
+        case PAWN:
+            return "บ";
+        case AIWOK:
+            return "ว";
+        default:
+            return "X";
+    }
+}
+
 inline std::string piece(const Position& pos, Move m, Notation n) {
     Color us = pos.side_to_move();
     Square from = from_sq(m);
     Piece pc = pos.moved_piece(m);
     PieceType pt = type_of(pc);
     // Quiet pawn moves
-    if ((n == NOTATION_SAN || n == NOTATION_LAN) && type_of(pc) == PAWN && type_of(m) != DROP)
+    if ((n == NOTATION_SAN || n == NOTATION_LAN || n == NOTATION_THAI_SAN) && type_of(pc) == PAWN && type_of(m) != DROP)
         return "";
     // Tandem pawns
     else if (n == NOTATION_XIANGQI_WXF && popcount(pos.pieces(us, pt) & file_bb(from)) >= 3 - multi_tandem(pos.pieces(us, pt)))
@@ -103,6 +134,8 @@ inline std::string piece(const Position& pos, Move m, Notation n) {
     // Promoted drops
     else if (is_shogi(n) && type_of(m) == DROP && dropped_piece_type(m) != in_hand_piece_type(m))
         return "+" + std::string(1, toupper(pos.piece_to_char()[in_hand_piece_type(m)]));
+    else if (is_thai(n))
+        return piece_to_thai_char(pc, pos.is_promoted(from));
     else if (pos.piece_to_char_synonyms()[pc] != ' ')
         return std::string(1, toupper(pos.piece_to_char_synonyms()[pc]));
     else
@@ -120,6 +153,9 @@ inline std::string file(const Position& pos, Square s, Notation n) {
         return std::to_string(file_of(s) + 1);
     case NOTATION_XIANGQI_WXF:
         return std::to_string((pos.side_to_move() == WHITE ? pos.max_file() - file_of(s) : file_of(s)) + 1);
+    case NOTATION_THAI_SAN:
+    case NOTATION_THAI_LAN:
+        return THAI_FILES[file_of(s)];
     default:
         return std::string(1, char('a' + file_of(s)));
     }
@@ -145,6 +181,9 @@ inline std::string rank(const Position& pos, Square s, Notation n) {
         else
             return "+";
     }
+    case NOTATION_THAI_SAN:
+    case NOTATION_THAI_LAN:
+        return THAI_RANKS[rank_of(s)];
     default:
         return std::to_string(rank_of(s) + 1);
     }
@@ -166,7 +205,7 @@ inline Disambiguation disambiguation_level(const Position& pos, Move m, Notation
         return NO_DISAMBIGUATION;
 
     // NOTATION_LAN and Janggi always use disambiguation
-    if (n == NOTATION_LAN || n == NOTATION_JANGGI)
+    if (n == NOTATION_LAN || n == NOTATION_THAI_LAN || n == NOTATION_JANGGI)
         return SQUARE_DISAMBIGUATION;
 
     Color us = pos.side_to_move();
@@ -190,7 +229,7 @@ inline Disambiguation disambiguation_level(const Position& pos, Move m, Notation
     }
 
     // Pawn captures always use disambiguation
-    if (n == NOTATION_SAN && pt == PAWN)
+    if ((n == NOTATION_SAN || n == NOTATION_THAI_SAN) && pt == PAWN)
     {
         if (pos.capture(m))
             return FILE_DISAMBIGUATION;
@@ -263,6 +302,9 @@ inline const std::string move_to_san(Position& pos, Move m, Notation n) {
         // Piece
         san += piece(pos, m, n);
 
+        if (n == NOTATION_THAI_LAN)
+            san += " ";
+
         // Origin square, disambiguation
         Disambiguation d = disambiguation_level(pos, m, n);
         san += disambiguation(pos, from, n, d);
@@ -281,7 +323,7 @@ inline const std::string move_to_san(Position& pos, Move m, Notation n) {
         }
         else if (pos.capture(m))
             san += 'x';
-        else if (n == NOTATION_LAN || (is_shogi(n) && (n != NOTATION_SHOGI_HOSKING || d == SQUARE_DISAMBIGUATION)) || n == NOTATION_JANGGI)
+        else if (n == NOTATION_LAN || n == NOTATION_THAI_LAN || (is_shogi(n) && (n != NOTATION_SHOGI_HOSKING || d == SQUARE_DISAMBIGUATION)) || n == NOTATION_JANGGI || (n == NOTATION_THAI_SAN && type_of(pos.moved_piece(m)) != PAWN))
             san += '-';
 
         // Destination square
index bf1f342..0e01ddf 100644 (file)
@@ -720,7 +720,9 @@ EMSCRIPTEN_BINDINGS(ffish_js) {
     .value("SHOGI_HODGES", NOTATION_SHOGI_HODGES)
     .value("SHOGI_HODGES_NUMBER", NOTATION_SHOGI_HODGES_NUMBER)
     .value("JANGGI", NOTATION_JANGGI)
-    .value("XIANGQI_WXF", NOTATION_XIANGQI_WXF);
+    .value("XIANGQI_WXF", NOTATION_XIANGQI_WXF)
+    .value("THAI_SAN", NOTATION_THAI_SAN)
+    .value("THAI_LAN", NOTATION_THAI_LAN);
   // usage: e.g. ffish.Termination.CHECKMATE
   enum_<Termination>("Termination")
     .value("ONGOING", ONGOING)
index 593bda0..a84be05 100644 (file)
@@ -406,6 +406,8 @@ PyMODINIT_FUNC PyInit_pyffish() {
     PyModule_AddObject(module, "NOTATION_SHOGI_HODGES_NUMBER", PyLong_FromLong(NOTATION_SHOGI_HODGES_NUMBER));
     PyModule_AddObject(module, "NOTATION_JANGGI", PyLong_FromLong(NOTATION_JANGGI));
     PyModule_AddObject(module, "NOTATION_XIANGQI_WXF", PyLong_FromLong(NOTATION_XIANGQI_WXF));
+    PyModule_AddObject(module, "NOTATION_THAI_SAN", PyLong_FromLong(NOTATION_THAI_SAN));
+    PyModule_AddObject(module, "NOTATION_THAI_LAN", PyLong_FromLong(NOTATION_THAI_LAN));
 
     // validation
     PyModule_AddObject(module, "FEN_OK", PyLong_FromLong(FEN::FEN_OK));
diff --git a/test.py b/test.py
index ba56ad1..add64bc 100644 (file)
--- a/test.py
+++ b/test.py
@@ -724,20 +724,59 @@ class TestPyffish(unittest.TestCase):
         result = sf.get_san("janggi", fen, "f8f10", False, sf.NOTATION_SAN)
         self.assertEqual(result, "Cfxf10")
 
+        result = sf.get_san("makruk", MAKRUK, "e3e4")
+        self.assertEqual(result, "e4")
+        result = sf.get_san("makruk", MAKRUK, "e3e4", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "จ๔")
+        result = sf.get_san("makruk", MAKRUK, "e3e4", False, sf.NOTATION_THAI_LAN)
+        self.assertEqual(result, "บ จ๓-จ๔")
+
+        fen = "r1smksnr/3n4/pppp1ppp/4p3/4PP2/PPPP2PP/8/RNSKMSNR w - - 0 1"
+        result = sf.get_san("makruk", fen, "f4e5")
+        self.assertEqual(result, "fxe5")
+        result = sf.get_san("makruk", fen, "f4e5", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "ฉxจ๕")
+        result = sf.get_san("makruk", fen, "f4e5", False, sf.NOTATION_THAI_LAN)
+        self.assertEqual(result, "บ ฉ๔xจ๕")
+
         fen = "rnsm1s1r/4n1k1/1ppppppp/p7/2PPP3/PP3PPP/4N2R/RNSKMS2 b - - 1 5"
         result = sf.get_san("makruk", fen, "f8f7")
         self.assertEqual(result, "Sf7")
+        result = sf.get_san("makruk", fen, "f8f7", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "ค-ฉ๗")
+        result = sf.get_san("makruk", fen, "f8f7", False, sf.NOTATION_THAI_LAN)
+        self.assertEqual(result, "ค ฉ๘-ฉ๗")
 
         fen = "4k3/8/8/4S3/8/2S5/8/4K3 w - - 0 1"
         result = sf.get_san("makruk", fen, "e5d4")
         self.assertEqual(result, "Sed4")
-
         result = sf.get_san("makruk", fen, "c3d4")
         self.assertEqual(result, "Scd4")
+        result = sf.get_san("makruk", fen, "e5d4", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "คจ-ง๔")
+        result = sf.get_san("makruk", fen, "c3d4", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "คค-ง๔")
+        result = sf.get_san("makruk", fen, "e5d4", False, sf.NOTATION_THAI_LAN)
+        self.assertEqual(result, "ค จ๕-ง๔")
+        result = sf.get_san("makruk", fen, "c3d4", False, sf.NOTATION_THAI_LAN)
+        self.assertEqual(result, "ค ค๓-ง๔")
+
+        # Distinction between the regular met and the promoted pawn
+        fen = "4k3/8/4M3/4S3/8/2S5/8/4K3 w - - 0 1"
+        result = sf.get_san("makruk", fen, "e6d5", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "ม็-ง๕")
+        fen = "4k3/8/4M~3/4S3/8/2S5/8/4K3 w - - 0 1"
+        result = sf.get_san("makruk", fen, "e6d5", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "ง-ง๕")
 
         fen = "4k3/8/8/3S4/8/3S4/8/4K3 w - - 0 1"
         result = sf.get_san("makruk", fen, "d3d4")
         self.assertEqual(result, "Sd4")
+        result = sf.get_san("makruk", fen, "d3d4", False, sf.NOTATION_THAI_SAN)
+        self.assertEqual(result, "ค-ง๔")
+        result = sf.get_san("makruk", fen, "d3d4", False, sf.NOTATION_THAI_LAN)
+        self.assertEqual(result, "ค ง๓-ง๔")
+
 
         UCI_moves = ["e2e4", "e7e5", "g1f3", "b8c6h", "f1c4", "f8c5e"]
         SAN_moves = ["e4", "e5", "Nf3", "Nc6/H", "Bc4", "Bc5/E"]