From fdbdeee291481c05dad6baf2e07176b806b2fdb5 Mon Sep 17 00:00:00 2001 From: Ada Joule Date: Tue, 27 Dec 2022 00:19:58 +0700 Subject: [PATCH] Thai Notations (#563) --- src/apiutil.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- src/ffishjs.cpp | 4 +++- src/pyffish.cpp | 2 ++ test.py | 41 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/apiutil.h b/src/apiutil.h index 35983b5..86bd208 100644 --- a/src/apiutil.h +++ b/src/apiutil.h @@ -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 THAI_FILES = {"ก", "ข", "ค", "ง", "จ", "ฉ", "ช", "ญ", "ต", "ถ", "ธ", "น"}; +const std::array 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 diff --git a/src/ffishjs.cpp b/src/ffishjs.cpp index bf1f342..0e01ddf 100644 --- a/src/ffishjs.cpp +++ b/src/ffishjs.cpp @@ -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") .value("ONGOING", ONGOING) diff --git a/src/pyffish.cpp b/src/pyffish.cpp index 593bda0..a84be05 100644 --- a/src/pyffish.cpp +++ b/src/pyffish.cpp @@ -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 --- 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"] -- 1.7.0.4