From 7952fae1a9a59d7168f5ca8340ebbdd9b40d1276 Mon Sep 17 00:00:00 2001 From: Fabian Fichter Date: Sun, 29 Mar 2020 23:36:27 +0200 Subject: [PATCH] Support Janggi (Korean chess) https://en.wikipedia.org/wiki/Janggi Closes #40. bench: 4801778 --- Readme.md | 1 + src/bitboard.cpp | 59 +++++++++++++++----------- src/bitboard.h | 8 +++- src/magic.h | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/movegen.cpp | 33 ++++++++++++++- src/parser.cpp | 3 + src/piece.cpp | 25 +++++++++++ src/position.cpp | 95 +++++++++++++++++++++++++++++++++++++----- src/position.h | 18 ++++++++ src/psqt.cpp | 4 +- src/types.h | 10 +++- src/variant.cpp | 30 +++++++++++++ src/variant.h | 3 + src/variants.ini | 10 ++++ tests/perft.sh | 2 + 15 files changed, 381 insertions(+), 42 deletions(-) diff --git a/Readme.md b/Readme.md index 95856a2..d6f012c 100644 --- a/Readme.md +++ b/Readme.md @@ -16,6 +16,7 @@ The games currently supported besides chess are listed below. Fairy-Stockfish ca ### Regional and historical games - [Xiangqi](https://en.wikipedia.org/wiki/Xiangqi), [Manchu](https://en.wikipedia.org/wiki/Manchu_chess), [Minixiangqi](http://mlwi.magix.net/bg/minixiangqi.htm), [Supply chess](https://en.wikipedia.org/wiki/Xiangqi#Variations) - [Shogi](https://en.wikipedia.org/wiki/Shogi), [Shogi variants](https://github.com/ianfab/Fairy-Stockfish#shogi-variants) +- [Janggi](https://en.wikipedia.org/wiki/Janggi) - [Makruk](https://en.wikipedia.org/wiki/Makruk), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Makpong, Ai-Wok - [Ouk Chatrang](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [Kar Ouk](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess) - [Sittuyin](https://en.wikipedia.org/wiki/Sittuyin) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 0489409..5efe8c8 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -46,6 +46,7 @@ Magic CannonMagicsH[SQUARE_NB]; Magic CannonMagicsV[SQUARE_NB]; Magic HorseMagics[SQUARE_NB]; Magic ElephantMagics[SQUARE_NB]; +Magic JanggiElephantMagics[SQUARE_NB]; namespace { @@ -57,6 +58,7 @@ namespace { Bitboard CannonTableV[0x4800]; // To store vertical cannon attacks Bitboard HorseTable[0x500]; // To store horse attacks Bitboard ElephantTable[0x400]; // To store elephant attacks + Bitboard JanggiElephantTable[0x1C000]; // To store janggi elephant attacks #else Bitboard RookTableH[0xA00]; // To store horizontal rook attacks Bitboard RookTableV[0xA00]; // To store vertical rook attacks @@ -65,6 +67,7 @@ namespace { Bitboard CannonTableV[0xA00]; // To store vertical cannon attacks Bitboard HorseTable[0x240]; // To store horse attacks Bitboard ElephantTable[0x1A0]; // To store elephant attacks + Bitboard JanggiElephantTable[0x5C00]; // To store janggi elephant attacks #endif enum MovementType { RIDER, HOPPER, LAME_LEAPER }; @@ -172,6 +175,18 @@ const std::string Bitboards::pretty(Bitboard b) { void Bitboards::init() { + // Piece moves + std::vector RookDirectionsV = { NORTH, SOUTH}; + std::vector RookDirectionsH = { EAST, WEST }; + std::vector BishopDirections = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; + std::vector HorseDirections = {2 * SOUTH + WEST, 2 * SOUTH + EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, + NORTH + 2 * WEST, NORTH + 2 * EAST, 2 * NORTH + WEST, 2 * NORTH + EAST }; + std::vector ElephantDirections = { 2 * NORTH_EAST, 2 * SOUTH_EAST, 2 * SOUTH_WEST, 2 * NORTH_WEST }; + std::vector JanggiElephantDirections = { NORTH + 2 * NORTH_EAST, EAST + 2 * NORTH_EAST, + EAST + 2 * SOUTH_EAST, SOUTH + 2 * SOUTH_EAST, + SOUTH + 2 * SOUTH_WEST, WEST + 2 * SOUTH_WEST, + WEST + 2 * NORTH_WEST, NORTH + 2 * NORTH_WEST }; + // Initialize rider types for (PieceType pt = PAWN; pt <= KING; ++pt) { @@ -181,51 +196,53 @@ void Bitboards::init() { { for (Direction d : pi->stepsCapture) { - if ( d == 2 * SOUTH + WEST || d == 2 * SOUTH + EAST || d == SOUTH + 2 * WEST || d == SOUTH + 2 * EAST - || d == NORTH + 2 * WEST || d == NORTH + 2 * EAST || d == 2 * NORTH + WEST || d == 2 * NORTH + EAST) + if (std::find(HorseDirections.begin(), HorseDirections.end(), d) != HorseDirections.end()) AttackRiderTypes[pt] |= RIDER_HORSE; - if (d == 2 * NORTH_EAST || d == 2 * SOUTH_EAST || d == 2 * SOUTH_WEST || d == 2 * NORTH_WEST) + if (std::find(ElephantDirections.begin(), ElephantDirections.end(), d) != ElephantDirections.end()) AttackRiderTypes[pt] |= RIDER_ELEPHANT; + if (std::find(JanggiElephantDirections.begin(), JanggiElephantDirections.end(), d) != JanggiElephantDirections.end()) + AttackRiderTypes[pt] |= RIDER_JANGGI_ELEPHANT; } for (Direction d : pi->stepsQuiet) { - if ( d == 2 * SOUTH + WEST || d == 2 * SOUTH + EAST || d == SOUTH + 2 * WEST || d == SOUTH + 2 * EAST - || d == NORTH + 2 * WEST || d == NORTH + 2 * EAST || d == 2 * NORTH + WEST || d == 2 * NORTH + EAST) + if (std::find(HorseDirections.begin(), HorseDirections.end(), d) != HorseDirections.end()) MoveRiderTypes[pt] |= RIDER_HORSE; - if (d == 2 * NORTH_EAST || d == 2 * SOUTH_EAST || d == 2 * SOUTH_WEST || d == 2 * NORTH_WEST) + if (std::find(ElephantDirections.begin(), ElephantDirections.end(), d) != ElephantDirections.end()) MoveRiderTypes[pt] |= RIDER_ELEPHANT; + if (std::find(JanggiElephantDirections.begin(), JanggiElephantDirections.end(), d) != JanggiElephantDirections.end()) + MoveRiderTypes[pt] |= RIDER_JANGGI_ELEPHANT; } } for (Direction d : pi->sliderCapture) { - if (d == NORTH_EAST || d == SOUTH_WEST || d == NORTH_WEST || d == SOUTH_EAST) + if (std::find(BishopDirections.begin(), BishopDirections.end(), d) != BishopDirections.end()) AttackRiderTypes[pt] |= RIDER_BISHOP; - if (d == EAST || d == WEST) + if (std::find(RookDirectionsH.begin(), RookDirectionsH.end(), d) != RookDirectionsH.end()) AttackRiderTypes[pt] |= RIDER_ROOK_H; - if (d == NORTH || d == SOUTH) + if (std::find(RookDirectionsV.begin(), RookDirectionsV.end(), d) != RookDirectionsV.end()) AttackRiderTypes[pt] |= RIDER_ROOK_V; } for (Direction d : pi->sliderQuiet) { - if (d == NORTH_EAST || d == SOUTH_WEST || d == NORTH_WEST || d == SOUTH_EAST) + if (std::find(BishopDirections.begin(), BishopDirections.end(), d) != BishopDirections.end()) MoveRiderTypes[pt] |= RIDER_BISHOP; - if (d == EAST || d == WEST) + if (std::find(RookDirectionsH.begin(), RookDirectionsH.end(), d) != RookDirectionsH.end()) MoveRiderTypes[pt] |= RIDER_ROOK_H; - if (d == NORTH || d == SOUTH) + if (std::find(RookDirectionsV.begin(), RookDirectionsV.end(), d) != RookDirectionsV.end()) MoveRiderTypes[pt] |= RIDER_ROOK_V; } for (Direction d : pi->hopperCapture) { - if (d == EAST || d == WEST) + if (std::find(RookDirectionsH.begin(), RookDirectionsH.end(), d) != RookDirectionsH.end()) AttackRiderTypes[pt] |= RIDER_CANNON_H; - if (d == NORTH || d == SOUTH) + if (std::find(RookDirectionsV.begin(), RookDirectionsV.end(), d) != RookDirectionsV.end()) AttackRiderTypes[pt] |= RIDER_CANNON_V; } for (Direction d : pi->hopperQuiet) { - if (d == EAST || d == WEST) + if (std::find(RookDirectionsH.begin(), RookDirectionsH.end(), d) != RookDirectionsH.end()) MoveRiderTypes[pt] |= RIDER_CANNON_H; - if (d == NORTH || d == SOUTH) + if (std::find(RookDirectionsV.begin(), RookDirectionsV.end(), d) != RookDirectionsV.end()) MoveRiderTypes[pt] |= RIDER_CANNON_V; } } @@ -244,14 +261,6 @@ void Bitboards::init() { for (Square s2 = SQ_A1; s2 <= SQ_MAX; ++s2) SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - // Piece moves - std::vector RookDirectionsV = { NORTH, SOUTH}; - std::vector RookDirectionsH = { EAST, WEST }; - std::vector BishopDirections = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; - std::vector HorseDirections = {2 * SOUTH + WEST, 2 * SOUTH + EAST, SOUTH + 2 * WEST, SOUTH + 2 * EAST, - NORTH + 2 * WEST, NORTH + 2 * EAST, 2 * NORTH + WEST, 2 * NORTH + EAST }; - std::vector ElephantDirections = { 2 * NORTH_EAST, 2 * SOUTH_EAST, 2 * SOUTH_WEST, 2 * NORTH_WEST }; - #ifdef PRECOMPUTED_MAGICS init_magics(RookTableH, RookMagicsH, RookDirectionsH, RookMagicHInit); init_magics(RookTableV, RookMagicsV, RookDirectionsV, RookMagicVInit); @@ -260,6 +269,7 @@ void Bitboards::init() { init_magics(CannonTableV, CannonMagicsV, RookDirectionsV, CannonMagicVInit); init_magics(HorseTable, HorseMagics, HorseDirections, HorseMagicInit); init_magics(ElephantTable, ElephantMagics, ElephantDirections, ElephantMagicInit); + init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections, JanggiElephantMagicInit); #else init_magics(RookTableH, RookMagicsH, RookDirectionsH); init_magics(RookTableV, RookMagicsV, RookDirectionsV); @@ -268,6 +278,7 @@ void Bitboards::init() { init_magics(CannonTableV, CannonMagicsV, RookDirectionsV); init_magics(HorseTable, HorseMagics, HorseDirections); init_magics(ElephantTable, ElephantMagics, ElephantDirections); + init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections); #endif for (Color c : { WHITE, BLACK }) diff --git a/src/bitboard.h b/src/bitboard.h index 8c6d434..3e8f05a 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -147,6 +147,7 @@ extern Magic CannonMagicsH[SQUARE_NB]; extern Magic CannonMagicsV[SQUARE_NB]; extern Magic HorseMagics[SQUARE_NB]; extern Magic ElephantMagics[SQUARE_NB]; +extern Magic JanggiElephantMagics[SQUARE_NB]; inline Bitboard square_bb(Square s) { assert(s >= SQ_A1 && s <= SQ_MAX); @@ -353,13 +354,14 @@ template inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) { assert(R == RIDER_BISHOP || R == RIDER_ROOK_H || R == RIDER_ROOK_V || R == RIDER_CANNON_H || R == RIDER_CANNON_V - || R == RIDER_HORSE || R == RIDER_ELEPHANT); + || R == RIDER_HORSE || R == RIDER_ELEPHANT || R == RIDER_JANGGI_ELEPHANT); const Magic& m = R == RIDER_ROOK_H ? RookMagicsH[s] : R == RIDER_ROOK_V ? RookMagicsV[s] : R == RIDER_CANNON_H ? CannonMagicsH[s] : R == RIDER_CANNON_V ? CannonMagicsV[s] : R == RIDER_HORSE ? HorseMagics[s] : R == RIDER_ELEPHANT ? ElephantMagics[s] + : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s] : BishopMagics[s]; return m.attacks[m.index(occupied)]; } @@ -391,6 +393,8 @@ inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) { b |= rider_attacks_bb(s, occupied); if (AttackRiderTypes[pt] & RIDER_ELEPHANT) b |= rider_attacks_bb(s, occupied); + if (AttackRiderTypes[pt] & RIDER_JANGGI_ELEPHANT) + b |= rider_attacks_bb(s, occupied); return b & PseudoAttacks[c][pt][s]; } @@ -410,6 +414,8 @@ inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) { b |= rider_attacks_bb(s, occupied); if (MoveRiderTypes[pt] & RIDER_ELEPHANT) b |= rider_attacks_bb(s, occupied); + if (MoveRiderTypes[pt] & RIDER_JANGGI_ELEPHANT) + b |= rider_attacks_bb(s, occupied); return b & PseudoMoves[c][pt][s]; } diff --git a/src/magic.h b/src/magic.h index 478f3f7..d24fe46 100644 --- a/src/magic.h +++ b/src/magic.h @@ -877,6 +877,128 @@ B(0x224404002004268C, 0x202500204C7D254), B(0x290080000000, 0xA41297838F40D), }; + Bitboard JanggiElephantMagicInit[SQUARE_NB] = { + B(0xC502282200061400, 0x2D07081241D90200), + B(0xC502282200061400, 0x2D07081241D90200), + B(0x8084810022440C2, 0x81402202004), + B(0x80204010A800500, 0x5000021001740218), + B(0x8048100401208000, 0x2001000390000044), + B(0x202080020000000, 0x4010800010090424), + B(0x4081A0480073200, 0x100000A010406000), + B(0x4081A0480073200, 0x100000A010406000), + B(0x2040450004000C40, 0x8400000006302), + B(0x84010410018201, 0xA00A00000100000), + B(0x840091030C00040, 0x1400008200023400), + B(0x801058C0A0022, 0xC1920480010034), + B(0x80B4004800840800, 0x4080210A42040010), + B(0x400402221000445, 0x80321200408040), + B(0x4028142401012A00, 0x4005009000104448), + B(0x1440102040800220, 0x82800010A082000), + B(0x4100040300C00200, 0x800805100120000), + B(0x8200080061100, 0x2000101400000), + B(0x2000100410070001, 0x40818200B0900410), + B(0x400088020080000, 0x4A000402000CA0), + B(0x1402040410004000, 0x9840044504040), + B(0x20800088A00A0400, 0x1000020100180), + B(0x2001820520308201, 0x2008003404349000), + B(0x4004808022100, 0x8001000008081080), + B(0x102041041100425, 0x840400180B100104), + B(0x8806446000800214, 0x404402100010000), + B(0x8200141409C04101, 0x209030004A00D00), + B(0x8806004800880080, 0x1560004201000A01), + B(0x4200050600200090, 0x1CD0000000000421), + B(0x4820100022408100, 0x101404080320), + B(0x2A000A0A08080080, 0x1C02808000C2C0), + B(0x8808425040040014, 0x2021000100020), + B(0x5282104044A0020, 0x6B402104200008), + B(0x4001091040068120, 0x202000004003031), + B(0x4001091040068120, 0x202000004003031), + B(0x98040200A0214344, 0xA00300840010), + B(0x82508040A40808A, 0x40010000110042), + B(0x4400100101023, 0x450C8480040022), + B(0x210588880010800, 0x800A000108018102), + B(0x9400010144400, 0xC00010100018000), + B(0x20A0400100040004, 0x1242000101002040), + B(0x8022900040001001, 0x100000014000260), + B(0x51004124000A080, 0x40098400000002), + B(0x2158040001080022, 0x80009238401222), + B(0xA0103A0000802220, 0x20000200400010), + B(0x1101001208240, 0x100000800001064), + B(0x821020002090081, 0x5840D0010290280), + B(0x821020002090081, 0x5840D0010290280), + B(0x10400C1042000400, 0x4005000000440200), + B(0x844022008804820, 0x1000800100118000), + B(0x10802A9800800139, 0x4802840100842200), + B(0x4000A008200081, 0x4001100200402000), + B(0x200000008108400, 0x1000C00008080020), + B(0x120C11500100081, 0x440300308041100), + B(0x8080040080060100, 0xC00101B0040028), + B(0x901420A00110000, 0x8200010044700280), + B(0x140080080410000, 0x808040000C001001), + B(0x80210C0200A0008, 0x88088004600201), + B(0x8000004202020301, 0x2100142104002000), + B(0x1101011210004880, 0x8500840400000000), + B(0x40208802004800, 0x8080806009011240), + B(0x800000140408880, 0xC001018004060040), + B(0xC008080420500, 0x8024A10000000000), + B(0x2800000000400010, 0x44001C00400408), + B(0xA804008001200408, 0x202000020001000), + B(0xC08288805004080, 0x200042000800004), + B(0xA40A01000080012, 0x8800080042408), + B(0x2200100000100810, 0x800200010000100), + B(0x9881800004040001, 0x8058100100884004), + B(0x820000044020014, 0x4AA00010245012), + B(0x820000044020014, 0x4AA00010245012), + B(0x4000080240000808, 0x10100022054000), + B(0x5002000840101, 0x202020004000A00), + B(0x1188008200008402, 0x8088100020A2204), + B(0x304012004044080, 0x8028108818006010), + B(0x102210000008400, 0x1008000200380002), + B(0x51410E114200, 0x100C00084000000), + B(0x5001242320218, 0x800025000040040), + B(0x4008000200008190, 0x400020021000000), + B(0x10910022F0040, 0x450084400040001), + B(0x180010810000040, 0x4004100040040), + B(0x1088801424062010, 0x400084010030401), + B(0x3000120408000040, 0x10802001080A4051), + B(0x200008420, 0x40C0100020008804), + B(0x1048C000004000, 0x4220120804004000), + B(0x404A180000000E, 0x4C30412008110102), + B(0x400000404202005, 0x800808550EC40044), + B(0x282000200212010, 0x8001C0C102000210), + B(0x9012240000008100, 0x280CA04010040000), + B(0x2000C04001020C00, 0x2002010101042000), + B(0x1010000204408408, 0x8008004800E0C4A), + B(0x800286801000025, 0x8402401040050088), + B(0x40002000A0880000, 0x8400300108082086), + B(0x2080004404011, 0x20C080400100001), + B(0xB0010218100800, 0x8040200482C14103), + B(0x8011035000000C20, 0x4200044043200040), + B(0x804008000040050, 0x41044890228000), + B(0x80000400A0020020, 0x5308022021000000), + B(0x2118200000008004, 0x4141014004423D00), + B(0x90C0000200008040, 0x41041062000082), + B(0x1D000100941204, 0x12402001200420), + B(0x8C0040400400065, 0x22300B408100000), + B(0x8C0040400400065, 0x22300B408100000), + B(0x802802044600000, 0x1210100401030082), + B(0x9400488010000000, 0x8005404902040000), + B(0x2214020200001, 0x40102100820200), + B(0x2022000000800000, 0x6400440108480), + B(0x110000400100028, 0x24052304508004), + B(0x848820140010000, 0x201012500A000), + B(0x848820140010000, 0x201012500A000), + B(0x100100000000C4, 0x208004084048201), + B(0x100500000000290, 0x10102818208000), + B(0x2800414000C000, 0x20004005001301), + B(0x698180005101241, 0x10002014800210), + B(0x20000080000009, 0x440340C040), + B(0x1C0220200290020, 0x42100004004011C0), + B(0x200E620018320208, 0x440410402), + B(0xD04101010004024, 0x20000121104010A4), + B(0x220400000A80040, 0x806080020810010C), + B(0xA000200000000080, 0x1040801A0081208), + }; #undef B #endif diff --git a/src/movegen.cpp b/src/movegen.cpp index efea6ae..38dc282 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -268,6 +268,11 @@ namespace { // Xiangqi soldier if (pt == SOLDIER && pos.unpromoted_soldier(us, from)) b1 &= file_bb(file_of(from)); + if (pt == JANGGI_CANNON) + { + b1 &= ~pos.pieces(pt); + b1 &= attacks_bb(us, pt, from, pos.pieces() ^ pos.pieces(pt)); + } PieceType prom_pt = pos.promoted_piece_type(pt); Bitboard b2 = prom_pt && (!pos.promotion_limit(prom_pt) || pos.promotion_limit(prom_pt) > pos.count(us, prom_pt)) ? b1 : Bitboard(0); Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0); @@ -341,6 +346,10 @@ namespace { while (b) moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(&b)); + // Passing move by king + if (pos.pass_on_stalemate()) + *moveList++ = make(ksq, ksq); + if (Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO))) { if (!pos.castling_impeded(OO) && pos.can_castle(OO)) @@ -384,6 +393,28 @@ namespace { } } + // Janggi palace moves + if (pos.diagonal_lines()) + { + Bitboard diags = pos.pieces(Us) & pos.diagonal_lines(); + while (diags) + { + Square from = pop_lsb(&diags); + PieceType pt = type_of(pos.piece_on(from)); + PieceType movePt = pt == KING ? pos.king_type() : pt; + Bitboard b = 0; + PieceType diagType = movePt == WAZIR ? FERS : movePt == SOLDIER ? PAWN : movePt == ROOK ? BISHOP : NO_PIECE_TYPE; + if (diagType) + b |= attacks_bb(Us, diagType, from, pos.pieces()); + else if (movePt == JANGGI_CANNON) + // TODO: fix for longer diagonals + b |= attacks_bb(Us, ALFIL, from, pos.pieces()) & ~attacks_bb(Us, ELEPHANT, from, pos.pieces() ^ pos.pieces(JANGGI_CANNON)); + b &= pos.board_bb(Us, pt) & target & pos.diagonal_lines(); + while (b) + moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(&b)); + } + } + return moveList; } @@ -464,7 +495,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { Bitboard sliders = pos.checkers(); // Consider all evasion moves for special pieces - if (sliders & (pos.pieces(CANNON, BANNER) | pos.pieces(HORSE, ELEPHANT))) + if (sliders & (pos.pieces(CANNON, BANNER) | pos.pieces(HORSE, ELEPHANT) | pos.pieces(JANGGI_CANNON, JANGGI_ELEPHANT))) { Bitboard target = pos.board_bb() & ~pos.pieces(us); Bitboard b = ( (pos.attacks_from(us, KING, ksq) & pos.pieces()) diff --git a/src/parser.cpp b/src/parser.cpp index 01d9051..b8b9ebd 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -241,6 +241,8 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("gating", v->gating); parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); + parse_attribute("diagonalLines", v->diagonalLines); + parse_attribute("passOnStalemate", v->passOnStalemate); parse_attribute("makpongRule", v->makpongRule); parse_attribute("flyingGeneral", v->flyingGeneral); parse_attribute("xiangqiSoldier", v->xiangqiSoldier); @@ -254,6 +256,7 @@ Variant* VariantParser::parse(Variant* v) { parse_attribute("checkmateValue", v->checkmateValue); parse_attribute("shogiPawnDropMateIllegal", v->shogiPawnDropMateIllegal); parse_attribute("shatarMateRule", v->shatarMateRule); + parse_attribute("bikjangRule", v->bikjangRule); parse_attribute("bareKingValue", v->bareKingValue); parse_attribute("extinctionValue", v->extinctionValue); parse_attribute("bareKingMove", v->bareKingMove); diff --git a/src/piece.cpp b/src/piece.cpp index 82f938c..258959f 100644 --- a/src/piece.cpp +++ b/src/piece.cpp @@ -298,6 +298,14 @@ namespace { p->hopperCapture = {NORTH, EAST, SOUTH, WEST}; return p; } + PieceInfo* janggi_cannon_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "janggi_cannon"; + p->betza = "pR"; + p->hopperQuiet = {NORTH, EAST, SOUTH, WEST}; + p->hopperCapture = {NORTH, EAST, SOUTH, WEST}; + return p; + } PieceInfo* soldier_piece() { PieceInfo* p = new PieceInfo(); p->name = "soldier"; @@ -320,6 +328,21 @@ namespace { p->lameLeaper = true; return p; } + PieceInfo* janggi_elephant_piece() { + PieceInfo* p = new PieceInfo(); + p->name = "janggi_elephant"; + p->betza = "nZ"; + p->stepsQuiet = {SOUTH + 2 * SOUTH_WEST, SOUTH + 2 * SOUTH_EAST, + WEST + 2 * SOUTH_WEST, EAST + 2 * SOUTH_EAST, + WEST + 2 * NORTH_WEST, EAST + 2 * NORTH_EAST, + NORTH + 2 * NORTH_WEST, NORTH + 2 * NORTH_EAST}; + p->stepsCapture = {SOUTH + 2 * SOUTH_WEST, SOUTH + 2 * SOUTH_EAST, + WEST + 2 * SOUTH_WEST, EAST + 2 * SOUTH_EAST, + WEST + 2 * NORTH_WEST, EAST + 2 * NORTH_EAST, + NORTH + 2 * NORTH_WEST, NORTH + 2 * NORTH_EAST}; + p->lameLeaper = true; + return p; + } PieceInfo* banner_piece() { PieceInfo* p = rook_piece(); p->name = "banner"; @@ -371,9 +394,11 @@ void PieceMap::init() { add(BREAKTHROUGH_PIECE, breakthrough_piece()); add(IMMOBILE_PIECE, immobile_piece()); add(CANNON, cannon_piece()); + add(JANGGI_CANNON, janggi_cannon_piece()); add(SOLDIER, soldier_piece()); add(HORSE, horse_piece()); add(ELEPHANT, elephant_piece()); + add(JANGGI_ELEPHANT, janggi_elephant_piece()); add(BANNER, banner_piece()); add(WAZIR, wazir_piece()); add(COMMONER, commoner_piece()); diff --git a/src/position.cpp b/src/position.cpp index 08d1336..ab44621 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -499,6 +499,7 @@ void Position::set_check_info(StateInfo* si) const { si->checkSquares[pt] = ksq != SQ_NONE ? attacks_from(~sideToMove, pt, ksq) : Bitboard(0); si->checkSquares[KING] = 0; si->shak = si->checkersBB & (byTypeBB[KNIGHT] | byTypeBB[ROOK] | byTypeBB[BERS]); + si->bikjang = ksq != SQ_NONE ? bool(attacks_from(sideToMove, ROOK, ksq) & pieces(sideToMove, KING)) : false; } @@ -740,7 +741,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners /// Position::attackers_to() computes a bitboard of all pieces which attack a /// given square. Slider attacks use the occupied bitboard to indicate occupancy. -Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { +Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c, Bitboard janggiCannons) const { Bitboard b = 0; for (PieceType pt : piece_types()) @@ -758,6 +759,8 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { b |= s2; } } + else if (pt == JANGGI_CANNON) + b |= attacks_bb(~c, move_pt, s, occupied) & attacks_bb(~c, move_pt, s, occupied & ~janggiCannons) & pieces(c, JANGGI_CANNON); else b |= attacks_bb(~c, move_pt, s, occupied) & pieces(c, pt); } @@ -770,6 +773,20 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { b |= pieces(c, FERS) & gates(c) & fers_sq; } + // Janggi palace moves + if (diagonal_lines() & s) + { + Bitboard diags = 0; + if (king_type() == WAZIR) + diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, KING); + diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, WAZIR); + diags |= attacks_bb(~c, PAWN, s, occupied) & pieces(c, SOLDIER); + diags |= attacks_bb(~c, BISHOP, s, occupied) & pieces(c, ROOK); + // TODO: fix for longer diagonals + diags |= attacks_bb(~c, ALFIL, s, occupied) & ~attacks_bb(~c, ELEPHANT, s, occupied & ~janggiCannons) & pieces(c, JANGGI_CANNON); + b |= diags & diagonal_lines(); + } + if (unpromoted_soldier(c, s)) b ^= b & pieces(SOLDIER) & ~PseudoAttacks[~c][SHOGI_PAWN][s]; @@ -856,6 +873,14 @@ bool Position::legal(Move m) const { if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(moves_bb(us, type_of(moved_piece(m)), to, 0) & board_bb())) return false; + // Illegal passing move + if (pass_on_stalemate() && type_of(m) == SPECIAL && from == to && !checkers()) + { + for (const auto& move : MoveList(*this)) + if (!(type_of(move) == SPECIAL && from == to) && legal(move)) + return false; + } + // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. @@ -897,11 +922,13 @@ bool Position::legal(Move m) const { || !(attackers_to(to, pieces() ^ to_sq(m), ~us)); } + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; + // Flying general rule if (var->flyingGeneral && count(us)) { Square s = type_of(moved_piece(m)) == KING ? to : square(us); - if (attacks_bb(~us, ROOK, s, (pieces() ^ from) | to) & pieces(~us, KING) & ~square_bb(to)) + if (attacks_bb(~us, ROOK, s, occupied) & pieces(~us, KING) & ~square_bb(to)) return false; } @@ -913,10 +940,16 @@ bool Position::legal(Move m) const { // square is attacked by the opponent. Castling moves are checked // for legality during move generation. if (type_of(moved_piece(m)) == KING) - return type_of(m) == CASTLING || !attackers_to(to, (type_of(m) != DROP ? pieces() ^ from : pieces()) | to, ~us); + return type_of(m) == CASTLING || !attackers_to(to, occupied, ~us); + + Bitboard janggiCannons = pieces(JANGGI_CANNON); + if (type_of(moved_piece(m)) == JANGGI_CANNON) + janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; + else if (janggiCannons & to) + janggiCannons ^= to; // A non-king move is legal if the king is not under attack after the move. - return !count(us) || !(attackers_to(square(us), (type_of(m) != DROP ? pieces() ^ from : pieces()) | to, ~us) & ~SquareBB[to]); + return !count(us) || !(attackers_to(square(us), occupied, ~us, janggiCannons) & ~SquareBB[to]); } @@ -994,10 +1027,14 @@ bool Position::pseudo_legal(const Move m) const { else if (!((capture(m) ? attacks_from(us, type_of(pc), from) : moves_from(us, type_of(pc), from)) & to)) return false; + // Janggi cannon + if (type_of(pc) == JANGGI_CANNON && (pieces(JANGGI_CANNON) & (between_bb(from, to) | to))) + return false; + // Evasions generator already takes care to avoid some kind of illegal moves // and legal() relies on this. We therefore have to take care that the same // kind of moves are filtered out here. - if (checkers() & ~(pieces(CANNON, BANNER) | pieces(HORSE, ELEPHANT))) + if (checkers() & ~(pieces(CANNON, BANNER) | pieces(HORSE, ELEPHANT) | pieces(JANGGI_CANNON, JANGGI_ELEPHANT))) { if (type_of(pc) != KING) { @@ -1041,17 +1078,24 @@ bool Position::gives_check(Move m) const { PieceType pt = type_of(moved_piece(m)); if (AttackRiderTypes[pt] & (HOPPING_RIDERS | ASYMMETRICAL_RIDERS)) { - if (attacks_bb(sideToMove, pt, to, (pieces() ^ from) | to) & square(~sideToMove)) + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; + if (attacks_bb(sideToMove, pt, to, occupied) & square(~sideToMove)) return true; } else if (st->checkSquares[pt] & to) return true; } + Bitboard janggiCannons = pieces(JANGGI_CANNON); + if (type_of(moved_piece(m)) == JANGGI_CANNON) + janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; + else if (janggiCannons & to) + janggiCannons ^= to; + // Is there a discovered check? - if ( type_of(m) != DROP - && ((st->blockersForKing[~sideToMove] & from) || (pieces(sideToMove, CANNON, BANNER) | pieces(HORSE, ELEPHANT))) - && attackers_to(square(~sideToMove), (pieces() ^ from) | to, sideToMove)) + if ( ((type_of(m) != DROP && (st->blockersForKing[~sideToMove] & from)) || pieces(sideToMove, CANNON, BANNER) + || pieces(HORSE, ELEPHANT) || pieces(JANGGI_CANNON, JANGGI_ELEPHANT)) + && attackers_to(square(~sideToMove), (type_of(m) == DROP ? pieces() : pieces() ^ from) | to, sideToMove, janggiCannons)) return true; // Is there a check by gated pieces? @@ -1059,6 +1103,19 @@ bool Position::gives_check(Move m) const { && attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square(~sideToMove)) return true; + // Is there a check by special diagonal moves? + if (more_than_one(diagonal_lines() & (to | square(~sideToMove)))) + { + PieceType pt = type_of(moved_piece(m)); + PieceType diagType = pt == WAZIR ? FERS : pt == SOLDIER ? PAWN : pt == ROOK ? BISHOP : NO_PIECE_TYPE; + Bitboard occupied = type_of(m) == DROP ? pieces() : pieces() ^ from; + if (diagType && (attacks_bb(sideToMove, diagType, to, occupied) & square(~sideToMove))) + return true; + // TODO: fix for longer diagonals + else if (pt == JANGGI_CANNON && (attacks_bb(sideToMove, ALFIL, to, occupied) & ~attacks_bb(sideToMove, ELEPHANT, to, occupied) & square(~sideToMove))) + return true; + } + switch (type_of(m)) { case NORMAL: @@ -1138,7 +1195,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); if (to == from) { - assert(type_of(m) == PROMOTION && sittuyin_promotion()); + assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (type_of(m) == SPECIAL && pass_on_stalemate())); captured = NO_PIECE; } st->capturedpromoted = is_promoted(to); @@ -1420,7 +1477,8 @@ void Position::undo_move(Move m) { Square to = to_sq(m); Piece pc = piece_on(to); - assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) || (type_of(m) == PROMOTION && sittuyin_promotion())); + assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) + || (type_of(m) == PROMOTION && sittuyin_promotion()) || (type_of(m) == SPECIAL && pass_on_stalemate())); assert(type_of(st->capturedPiece) != KING); // Remove gated piece @@ -1881,6 +1939,21 @@ bool Position::is_immediate_game_end(Value& result, int ply) const { } } } + // Check for bikjang rule (Janggi) + if (var->bikjangRule && st->pliesFromNull > 0 && st->bikjang && st->previous->bikjang) + { + // material counting + auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); }; + int materialCount = weigth_count(ROOK, 13) + + weigth_count(JANGGI_CANNON, 7) + + weigth_count(HORSE, 5) + + weigth_count(JANGGI_ELEPHANT, 3) + + weigth_count(WAZIR, 3) + + weigth_count(SOLDIER, 2) + - 1; + result = (sideToMove == WHITE) == (materialCount > 0) ? mate_in(ply) : mated_in(ply); + return true; + } // Tsume mode: Assume that side with king wins when not in check if (!count(~sideToMove) && count(sideToMove) && !checkers() && Options["TsumeMode"]) { diff --git a/src/position.h b/src/position.h index 31a9a63..b458136 100644 --- a/src/position.h +++ b/src/position.h @@ -62,6 +62,7 @@ struct StateInfo { Bitboard checkSquares[PIECE_TYPE_NB]; bool capturedpromoted; bool shak; + bool bikjang; int repetition; }; @@ -140,6 +141,8 @@ public: bool gating() const; bool seirawan_gating() const; bool cambodian_moves() const; + Bitboard diagonal_lines() const; + bool pass_on_stalemate() const; bool unpromoted_soldier(Color c, Square s) const; bool makpong() const; // winning conditions @@ -203,6 +206,7 @@ public: Bitboard attackers_to(Square s, Color c) const; Bitboard attackers_to(Square s, Bitboard occupied) const; Bitboard attackers_to(Square s, Bitboard occupied, Color c) const; + Bitboard attackers_to(Square s, Bitboard occupied, Color c, Bitboard janggiCannons) const; Bitboard attacks_from(Color c, PieceType pt, Square s) const; template Bitboard attacks_from(Square s, Color c) const; Bitboard moves_from(Color c, PieceType pt, Square s) const; @@ -569,6 +573,16 @@ inline bool Position::cambodian_moves() const { return var->cambodianMoves; } +inline Bitboard Position::diagonal_lines() const { + assert(var != nullptr); + return var->diagonalLines; +} + +inline bool Position::pass_on_stalemate() const { + assert(var != nullptr); + return var->passOnStalemate; +} + inline bool Position::unpromoted_soldier(Color c, Square s) const { assert(var != nullptr); return var->xiangqiSoldier && relative_rank(c, s, var->maxRank) <= RANK_5; @@ -838,6 +852,10 @@ inline Bitboard Position::attackers_to(Square s, Color c) const { return attackers_to(s, byTypeBB[ALL_PIECES], c); } +inline Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { + return attackers_to(s, occupied, c, byTypeBB[JANGGI_CANNON]); +} + inline Bitboard Position::checkers() const { return st->checkersBB; } diff --git a/src/psqt.cpp b/src/psqt.cpp index a483ea4..b805247 100644 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@ -30,14 +30,14 @@ Value PieceValue[PHASE_NB][PIECE_NB] = { ArchbishopValueMg, ChancellorValueMg, AmazonValueMg, KnibisValueMg, BiskniValueMg, KnirooValueMg, RookniValueMg, ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, EuroShogiKnightValueMg, GoldValueMg, DragonHorseValueMg, ClobberPieceValueMg, BreakthroughPieceValueMg, ImmobilePieceValueMg, - CannonPieceValueMg, SoldierValueMg, HorseValueMg, ElephantValueMg, BannerValueMg, + CannonPieceValueMg, JanggiCannonPieceValueMg, SoldierValueMg, HorseValueMg, ElephantValueMg, JanggiElephantValueMg, BannerValueMg, WazirValueMg, CommonerValueMg, CentaurValueMg }, { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, FersValueEg, AlfilValueEg, FersAlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ArchbishopValueMg, ChancellorValueEg, AmazonValueEg, KnibisValueMg, BiskniValueMg, KnirooValueEg, RookniValueEg, ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, EuroShogiKnightValueEg, GoldValueEg, DragonHorseValueEg, ClobberPieceValueEg, BreakthroughPieceValueEg, ImmobilePieceValueEg, - CannonPieceValueEg, SoldierValueEg, HorseValueEg, ElephantValueEg, BannerValueEg, + CannonPieceValueEg, JanggiCannonPieceValueEg, SoldierValueEg, HorseValueEg, ElephantValueEg, JanggiElephantValueEg, BannerValueEg, WazirValueEg, CommonerValueEg, CentaurValueEg } }; diff --git a/src/types.h b/src/types.h index d3b7662..c643ca6 100644 --- a/src/types.h +++ b/src/types.h @@ -338,9 +338,11 @@ enum Value : int { BreakthroughPieceValueMg = 300, BreakthroughPieceValueEg = 300, ImmobilePieceValueMg = 100, ImmobilePieceValueEg = 100, CannonPieceValueMg = 800, CannonPieceValueEg = 700, + JanggiCannonPieceValueMg = 800, JanggiCannonPieceValueEg = 600, SoldierValueMg = 150, SoldierValueEg = 300, HorseValueMg = 500, HorseValueEg = 800, ElephantValueMg = 300, ElephantValueEg = 300, + JanggiElephantValueMg = 350, JanggiElephantValueEg = 350, BannerValueMg = 3400, BannerValueEg = 3500, WazirValueMg = 400, WazirValueEg = 400, CommonerValueMg = 700, CommonerValueEg = 900, @@ -356,7 +358,8 @@ enum PieceType { FERS, MET = FERS, ALFIL, FERS_ALFIL, SILVER, KHON = SILVER, AIWOK, BERS, DRAGON = BERS, ARCHBISHOP, CHANCELLOR, AMAZON, KNIBIS, BISKNI, KNIROO, ROOKNI, SHOGI_PAWN, LANCE, SHOGI_KNIGHT, EUROSHOGI_KNIGHT, GOLD, DRAGON_HORSE, - CLOBBER_PIECE, BREAKTHROUGH_PIECE, IMMOBILE_PIECE, CANNON, SOLDIER, HORSE, ELEPHANT, BANNER, + CLOBBER_PIECE, BREAKTHROUGH_PIECE, IMMOBILE_PIECE, CANNON, JANGGI_CANNON, + SOLDIER, HORSE, ELEPHANT, JANGGI_ELEPHANT, BANNER, WAZIR, COMMONER, CENTAUR, KING, ALL_PIECES = 0, @@ -382,8 +385,9 @@ enum RiderType { RIDER_CANNON_V = 1 << 4, RIDER_HORSE = 1 << 5, RIDER_ELEPHANT = 1 << 6, + RIDER_JANGGI_ELEPHANT = 1 << 7, HOPPING_RIDERS = RIDER_CANNON_H | RIDER_CANNON_V, - ASYMMETRICAL_RIDERS = RIDER_HORSE, + ASYMMETRICAL_RIDERS = RIDER_HORSE | RIDER_JANGGI_ELEPHANT, }; extern Value PieceValue[PHASE_NB][PIECE_NB]; @@ -726,7 +730,7 @@ constexpr PieceType in_hand_piece_type(Move m) { } inline bool is_ok(Move m) { - return from_sq(m) != to_sq(m) || type_of(m) == PROMOTION; // Catch MOVE_NULL and MOVE_NONE + return from_sq(m) != to_sq(m) || type_of(m) == PROMOTION || type_of(m) == SPECIAL; // Catch MOVE_NULL and MOVE_NONE } inline int dist(Direction d) { diff --git a/src/variant.cpp b/src/variant.cpp index 3dfcf4a..c1fe12e 100644 --- a/src/variant.cpp +++ b/src/variant.cpp @@ -850,6 +850,35 @@ namespace { v->blackDropRegion = v->mobilityRegion[BLACK][ELEPHANT]; return v; } + // Janggi (Korean chess) + // https://en.wikipedia.org/wiki/Janggi + Variant* janggi_variant() { + Variant* v = xiangqi_variant(); + v->remove_piece(FERS); + v->remove_piece(CANNON); + v->remove_piece(ELEPHANT); + v->add_piece(WAZIR, 'a'); + v->add_piece(JANGGI_CANNON, 'c'); + v->add_piece(JANGGI_ELEPHANT, 'b', 'e'); + v->startFen = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"; + Bitboard white_castle = make_bitboard(SQ_D1, SQ_E1, SQ_F1, + SQ_D2, SQ_E2, SQ_F2, + SQ_D3, SQ_E3, SQ_F3); + Bitboard black_castle = make_bitboard(SQ_D8, SQ_E8, SQ_F8, + SQ_D9, SQ_E9, SQ_F9, + SQ_D10, SQ_E10, SQ_F10); + v->mobilityRegion[WHITE][WAZIR] = white_castle; + v->mobilityRegion[BLACK][WAZIR] = black_castle; + v->xiangqiSoldier = false; + v->flyingGeneral = false; + v->bikjangRule = true; + v->diagonalLines = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3, + SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10); + v->passOnStalemate = true; + v->nFoldValue = -VALUE_MATE; + v->perpetualCheckIllegal = true; + return v; + } #endif } // namespace @@ -933,6 +962,7 @@ void VariantMap::init() { add("xiangqi", xiangqi_variant()); add("manchu", manchu_variant()); add("supply", supply_variant()); + add("janggi", janggi_variant()); #endif } diff --git a/src/variant.h b/src/variant.h index 0ee06d3..682d38e 100644 --- a/src/variant.h +++ b/src/variant.h @@ -86,6 +86,8 @@ struct Variant { bool gating = false; bool seirawanGating = false; bool cambodianMoves = false; + Bitboard diagonalLines = 0; + bool passOnStalemate = false; bool makpongRule = false; bool flyingGeneral = false; bool xiangqiSoldier = false; @@ -100,6 +102,7 @@ struct Variant { Value checkmateValue = -VALUE_MATE; bool shogiPawnDropMateIllegal = false; bool shatarMateRule = false; + bool bikjangRule = false; Value bareKingValue = VALUE_NONE; Value extinctionValue = VALUE_NONE; bool bareKingMove = false; diff --git a/src/variants.ini b/src/variants.ini index 399f0ed..f481323 100644 --- a/src/variants.ini +++ b/src/variants.ini @@ -74,9 +74,11 @@ # breakthrough # immobile (piece without moves) # cannon +# janggi_cannon # soldier # horse # elephant +# janggi_elephant # banner (=rook+cannon+horse) # wazir # commoner (non-royal king) @@ -143,6 +145,8 @@ # gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) # seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false) # cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false) +# diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard] +# passOnStalemate: allow passing by king in case of stalemate [bool] (default: false) # makpongRule: the king may not move away from check [bool] (default: false) # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) # xiangqiSoldier: restrict soldier to shogi pawn movements on first five ranks [bool] (default: false) @@ -155,6 +159,7 @@ # checkmateValue: result in case of checkmate [Value] (default: loss) # shogiPawnDropMateIllegal: prohibit checkmate via shogi pawn drops [bool] (default: false) # shatarMateRule: enable shatar mating rules [bool] (default: false) +# bikjangRule: enable Janggi bikjang rule (facing kings) [bool] (default: false) # bareKingValue: result when player only has a lone/bare king [Value] (default: ) # extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: ) # bareKingMove: allow additional move by opponent after lone/bare king position [bool] (default: false) @@ -224,6 +229,11 @@ dropChecks = false whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *6 *7 *8 *9 *10 +# Hybrid variant of janggi and crazyhouse +[janggihouse:janggi] +pieceDrops = true +capturesToHand = true + # Hybrid variant of antichess and losalamos [anti-losalamos:losalamos] king = - diff --git a/tests/perft.sh b/tests/perft.sh index 1baba94..f1eab99 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -74,6 +74,8 @@ if [[ $1 == "largeboard" ]]; then expect perft.exp courier startpos 4 500337 > /dev/null expect perft.exp grand startpos 3 259514 > /dev/null expect perft.exp xiangqi startpos 4 3290240 > /dev/null + expect perft.exp janggi startpos 4 948462 > /dev/null + expect perft.exp janggi "fen 1n1kaabn1/cr2N4/5C1c1/p1pNp3p/9/9/P1PbP1P1P/3r1p3/4A4/R1BA1KB1R b - - 0 1" 4 70254 > /dev/null fi rm perft.exp -- 1.7.0.4