Support 10/11 Men's Morris

Qt: Set default rule as 9nm.
This commit is contained in:
Calcitem 2021-06-06 00:58:11 +08:00
parent cbf88aeed0
commit a350fd9641
12 changed files with 127 additions and 89 deletions

View File

@ -69,7 +69,7 @@
//#define DEBUG_MODE //#define DEBUG_MODE
#define DEFAULT_RULE_NUMBER 1 #define DEFAULT_RULE_NUMBER 2
#define DEPTH_ADJUST (0) #define DEPTH_ADJUST (0)

View File

@ -58,9 +58,6 @@ extern uint8_t PopCnt16[1 << 16];
extern Bitboard SquareBB[SQ_32]; extern Bitboard SquareBB[SQ_32];
extern Bitboard StarSquareBB9;
extern Bitboard StarSquareBB12;
inline Bitboard square_bb(Square s) noexcept inline Bitboard square_bb(Square s) noexcept
{ {
if (!(SQ_BEGIN <= s && s < SQ_END)) if (!(SQ_BEGIN <= s && s < SQ_END))

View File

@ -502,16 +502,16 @@ Depth get_search_depth(const Position *pos)
if (pos->phase == Phase::placing) { if (pos->phase == Phase::placing) {
const int index = rule.piecesCount * 2 - pos->count<IN_HAND>(WHITE) - pos->count<IN_HAND>(BLACK); const int index = rule.piecesCount * 2 - pos->count<IN_HAND>(WHITE) - pos->count<IN_HAND>(BLACK);
if (rule.piecesCount == 12) { if (rule.piecesCount == 9) {
assert(0 <= index && index <= 24); assert(0 <= index && index <= 19);
d = placingDepthTable_9[index];
} else {
assert(0 <= index && index <= rule.piecesCount * 2);
if (!rule.hasBannedLocations && !rule.hasDiagonalLines) { if (!rule.hasBannedLocations && !rule.hasDiagonalLines) {
d = placingDepthTable_12_special[index]; d = placingDepthTable_12_special[index];
} else { } else {
d = placingDepthTable_12[index]; d = placingDepthTable_12[index];
} }
} else {
assert(0 <= index && index <= 19);
d = placingDepthTable_9[index];
} }
} }

View File

@ -97,7 +97,7 @@ void MovePicker::score()
//cur->value += bannedCount; // placing phrase, place nearby ban point //cur->value += bannedCount; // placing phrase, place nearby ban point
// for 12 men's morris (has diagonal), black 2nd move place star point is as important as close mill (TODO) // If has Diagonal Lines, black 2nd move place star point is as important as close mill (TODO)
if (rule.hasDiagonalLines && if (rule.hasDiagonalLines &&
pos.count<ON_BOARD>(BLACK) < 2 && // patch: only when black 2nd move pos.count<ON_BOARD>(BLACK) < 2 && // patch: only when black 2nd move
Position::is_star_square(static_cast<Square>(m))) { Position::is_star_square(static_cast<Square>(m))) {

View File

@ -169,7 +169,7 @@ public:
Piece board[SQUARE_NB]; Piece board[SQUARE_NB];
Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard byTypeBB[PIECE_TYPE_NB];
Bitboard byColorBB[COLOR_NB]; Bitboard byColorBB[COLOR_NB];
int pieceInHandCount[COLOR_NB] { 0, 12, 12 }; int pieceInHandCount[COLOR_NB] { 0, 9, 9 };
int pieceOnBoardCount[COLOR_NB] { 0, 0, 0 }; int pieceOnBoardCount[COLOR_NB] { 0, 0, 0 };
int pieceToRemoveCount{ 0 }; int pieceToRemoveCount{ 0 };
int gamePly { 0 }; int gamePly { 0 };

View File

@ -21,24 +21,19 @@
#include "rule.h" #include "rule.h"
struct Rule rule = { struct Rule rule = {
"打三棋(12连棋)", // 打三 "Nine men's morris", // 莫里斯九子
// 规则说明 // 规则说明
"1. 双方各12颗子棋盘有斜线\n" "规则与成三棋基本相同只是在走子阶段当一方仅剩3子时他可以飞子到任意空位。",
"2. 摆棋阶段被提子的位置不能再摆子,直到走棋阶段;\n" 9, // 双方各9子
"3. 摆棋阶段,摆满棋盘算先手负;\n"
"4. 走棋阶段,后摆棋的一方先走;\n"
"5. 同时出现两个“三连”只能提一子;\n"
"6. 其它规则与成三棋基本相同。",
12, // 双方各12子
3, // 赛点子数为3 3, // 赛点子数为3
true, // 有斜线 false, // 没有斜线
true, // 有禁点,摆棋阶段被提子的点不能再摆子 false, // 没有禁点,摆棋阶段被提子的点可以再摆子
true, // 后摆棋者先行棋 false, // 先摆棋者先行棋
false, // 多个“三连”只能提一子 false, // 多个“三连”只能提一子
true, // 可以提对手的“三连”子 false, // 不能提对手的“三连”子,除非无子可提;
true, // 摆棋满子闷棋只有12子棋才出现算先手负 true, // 摆棋满子闷棋只有12子棋才出现算先手负
true, // 走棋阶段不能行动(被“闷”)算负 true, // 走棋阶段不能行动(被“闷”)算负
false, // 剩三子时不可以飞棋 true, // 剩三子时可以飞棋
99 // 99半步即50回合 99 // 99半步即50回合
}; };

View File

@ -37,8 +37,11 @@ namespace
{ {
// FEN string of the initial position, normal mill game // FEN string of the initial position, normal mill game
const char *StartFEN9 = "********/********/******** w p p 0 9 0 9 0 0 1";
const char *StartFEN10 = "********/********/******** w p p 0 10 0 10 0 0 1";
const char *StartFEN11 = "********/********/******** w p p 0 11 0 11 0 0 1";
const char *StartFEN12 = "********/********/******** w p p 0 12 0 12 0 0 1"; const char *StartFEN12 = "********/********/******** w p p 0 12 0 12 0 0 1";
const char *StartFEN9 = "********/********/******** w p p 0 9 0 9 0 0 1";
char StartFEN[BUFSIZ]; char StartFEN[BUFSIZ];
// position() is called when engine receives the "position" UCI command. // position() is called when engine receives the "position" UCI command.
@ -157,10 +160,22 @@ void UCI::loop(int argc, char *argv[])
Position *pos = new Position; Position *pos = new Position;
string token, cmd; string token, cmd;
if (rule.piecesCount == 9) { switch (rule.piecesCount) {
case 9:
strncpy(StartFEN, StartFEN9, BUFSIZ); strncpy(StartFEN, StartFEN9, BUFSIZ);
} else if (rule.piecesCount == 12) { break;
case 10:
strncpy(StartFEN, StartFEN10, BUFSIZ);
break;
case 11:
strncpy(StartFEN, StartFEN11, BUFSIZ);
break;
case 12:
strncpy(StartFEN, StartFEN12, BUFSIZ); strncpy(StartFEN, StartFEN12, BUFSIZ);
break;
default:
assert(0);
break;
} }
pos->set(StartFEN, Threads.main()); pos->set(StartFEN, Threads.main());

View File

@ -174,16 +174,16 @@ void init(OptionsMap &o)
o["DeveloperMode"] << Option(true, on_developerMode); o["DeveloperMode"] << Option(true, on_developerMode);
// Rules // Rules
o["PiecesCount"] << Option(12, 6, 12, on_piecesCount); o["PiecesCount"] << Option(9, 9, 12, on_piecesCount);
o["PiecesAtLeastCount"] << Option(3, 3, 5, on_piecesAtLeastCount); o["PiecesAtLeastCount"] << Option(3, 3, 5, on_piecesAtLeastCount);
o["HasDiagonalLines"] << Option(true, on_hasDiagonalLines); o["HasDiagonalLines"] << Option(false, on_hasDiagonalLines);
o["HasBannedLocations"] << Option(true, on_hasBannedLocations); o["HasBannedLocations"] << Option(false, on_hasBannedLocations);
o["IsDefenderMoveFirst"] << Option(true, on_isDefenderMoveFirst); o["IsDefenderMoveFirst"] << Option(false, on_isDefenderMoveFirst);
o["MayRemoveMultiple"] << Option(false, on_mayRemoveMultiple); o["MayRemoveMultiple"] << Option(false, on_mayRemoveMultiple);
o["MayRemoveFromMillsAlways"] << Option(true, on_mayRemoveFromMillsAlways); o["MayRemoveFromMillsAlways"] << Option(false, on_mayRemoveFromMillsAlways);
o["IsWhiteLoseButNotDrawWhenBoardFull"] << Option(true, on_isWhiteLoseButNotDrawWhenBoardFull); o["IsWhiteLoseButNotDrawWhenBoardFull"] << Option(true, on_isWhiteLoseButNotDrawWhenBoardFull);
o["IsLoseButNotChangeSideWhenNoWay"] << Option(true, on_isLoseButNotChangeSideWhenNoWay); o["IsLoseButNotChangeSideWhenNoWay"] << Option(true, on_isLoseButNotChangeSideWhenNoWay);
o["MayFly"] << Option(false, on_mayFly); o["MayFly"] << Option(true, on_mayFly);
o["MaxStepsLedToDraw"] << Option(50, 30, 50, on_maxStepsLedToDraw); o["MaxStepsLedToDraw"] << Option(50, 30, 50, on_maxStepsLedToDraw);
} }

View File

@ -22,54 +22,7 @@ import 'rule.dart';
class Mills { class Mills {
static void adjacentSquaresInit() { static void adjacentSquaresInit() {
// Note: Not follow order of MoveDirection array // Note: Not follow order of MoveDirection array
const adjacentSquares12 = [ const adjacentSquares = [
/* 0 */ [0, 0, 0, 0],
/* 1 */ [0, 0, 0, 0],
/* 2 */ [0, 0, 0, 0],
/* 3 */ [0, 0, 0, 0],
/* 4 */ [0, 0, 0, 0],
/* 5 */ [0, 0, 0, 0],
/* 6 */ [0, 0, 0, 0],
/* 7 */ [0, 0, 0, 0],
/* 8 */ [9, 15, 16, 0],
/* 9 */ [17, 8, 10, 0],
/* 10 */ [9, 11, 18, 0],
/* 11 */ [19, 10, 12, 0],
/* 12 */ [11, 13, 20, 0],
/* 13 */ [21, 12, 14, 0],
/* 14 */ [13, 15, 22, 0],
/* 15 */ [23, 8, 14, 0],
/* 16 */ [17, 23, 8, 24],
/* 17 */ [9, 25, 16, 18],
/* 18 */ [17, 19, 10, 26],
/* 19 */ [11, 27, 18, 20],
/* 20 */ [19, 21, 12, 28],
/* 21 */ [13, 29, 20, 22],
/* 22 */ [21, 23, 14, 30],
/* 23 */ [15, 31, 16, 22],
/* 24 */ [25, 31, 16, 0],
/* 25 */ [17, 24, 26, 0],
/* 26 */ [25, 27, 18, 0],
/* 27 */ [19, 26, 28, 0],
/* 28 */ [27, 29, 20, 0],
/* 29 */ [21, 28, 30, 0],
/* 30 */ [29, 31, 22, 0],
/* 31 */ [23, 24, 30, 0],
/* 32 */ [0, 0, 0, 0],
/* 33 */ [0, 0, 0, 0],
/* 34 */ [0, 0, 0, 0],
/* 35 */ [0, 0, 0, 0],
/* 36 */ [0, 0, 0, 0],
/* 37 */ [0, 0, 0, 0],
/* 38 */ [0, 0, 0, 0],
/* 39 */ [0, 0, 0, 0],
];
const adjacentSquares9 = [
/* 0 */ [0, 0, 0, 0], /* 0 */ [0, 0, 0, 0],
/* 1 */ [0, 0, 0, 0], /* 1 */ [0, 0, 0, 0],
/* 2 */ [0, 0, 0, 0], /* 2 */ [0, 0, 0, 0],
@ -116,15 +69,62 @@ class Mills {
/* 39 */ [0, 0, 0, 0], /* 39 */ [0, 0, 0, 0],
]; ];
const adjacentSquares_diagonal = [
/* 0 */ [0, 0, 0, 0],
/* 1 */ [0, 0, 0, 0],
/* 2 */ [0, 0, 0, 0],
/* 3 */ [0, 0, 0, 0],
/* 4 */ [0, 0, 0, 0],
/* 5 */ [0, 0, 0, 0],
/* 6 */ [0, 0, 0, 0],
/* 7 */ [0, 0, 0, 0],
/* 8 */ [9, 15, 16, 0],
/* 9 */ [17, 8, 10, 0],
/* 10 */ [9, 11, 18, 0],
/* 11 */ [19, 10, 12, 0],
/* 12 */ [11, 13, 20, 0],
/* 13 */ [21, 12, 14, 0],
/* 14 */ [13, 15, 22, 0],
/* 15 */ [23, 8, 14, 0],
/* 16 */ [17, 23, 8, 24],
/* 17 */ [9, 25, 16, 18],
/* 18 */ [17, 19, 10, 26],
/* 19 */ [11, 27, 18, 20],
/* 20 */ [19, 21, 12, 28],
/* 21 */ [13, 29, 20, 22],
/* 22 */ [21, 23, 14, 30],
/* 23 */ [15, 31, 16, 22],
/* 24 */ [25, 31, 16, 0],
/* 25 */ [17, 24, 26, 0],
/* 26 */ [25, 27, 18, 0],
/* 27 */ [19, 26, 28, 0],
/* 28 */ [27, 29, 20, 0],
/* 29 */ [21, 28, 30, 0],
/* 30 */ [29, 31, 22, 0],
/* 31 */ [23, 24, 30, 0],
/* 32 */ [0, 0, 0, 0],
/* 33 */ [0, 0, 0, 0],
/* 34 */ [0, 0, 0, 0],
/* 35 */ [0, 0, 0, 0],
/* 36 */ [0, 0, 0, 0],
/* 37 */ [0, 0, 0, 0],
/* 38 */ [0, 0, 0, 0],
/* 39 */ [0, 0, 0, 0],
];
if (rule.hasDiagonalLines) { if (rule.hasDiagonalLines) {
Position.adjacentSquares = adjacentSquares12; Position.adjacentSquares = adjacentSquares_diagonal;
} else { } else {
Position.adjacentSquares = adjacentSquares9; Position.adjacentSquares = adjacentSquares;
} }
} }
static void millTableInit() { static void millTableInit() {
const millTable9 = [ const millTable = [
/* 0 */ [ /* 0 */ [
[0, 0], [0, 0],
[0, 0], [0, 0],
@ -331,7 +331,7 @@ class Mills {
] ]
]; ];
const millTable12 = [ const millTable_diagonal = [
/* 0 */ [ /* 0 */ [
[0, 0], [0, 0],
[0, 0], [0, 0],
@ -539,9 +539,9 @@ class Mills {
]; ];
if (rule.hasDiagonalLines) { if (rule.hasDiagonalLines) {
Position.millTable = millTable12; Position.millTable = millTable_diagonal;
} else { } else {
Position.millTable = millTable9; Position.millTable = millTable;
} }
} }
} }

View File

@ -799,6 +799,17 @@ class Position {
setSideToMove(PieceColor.opponent(_sideToMove)); setSideToMove(PieceColor.opponent(_sideToMove));
st.key ^= Zobrist.side; st.key ^= Zobrist.side;
print("[position] $_sideToMove to move."); print("[position] $_sideToMove to move.");
/*
if (phase == Phase.moving &&
!rule.isLoseButNotChangeSideWhenNoWay &&
isAllSurrounded() &&
!rule.mayFly &&
pieceOnBoardCount[sideToMove()]! >= rule.piecesAtLeastCount) {
print("[position] $_sideToMove is no way to go.");
changeSideToMove();
}
*/
} }
int updateKey(int s) { int updateKey(int s) {

View File

@ -205,6 +205,8 @@ class _GamePageState extends State<GamePage>
return; return;
} }
// If nobody has placed, start to go.
// TODO // TODO
// WAR: Fix first tap response slow when piece count changed // WAR: Fix first tap response slow when piece count changed
if (position.phase == Phase.placing && if (position.phase == Phase.placing &&
@ -233,6 +235,8 @@ class _GamePageState extends State<GamePage>
Game.instance.start(); Game.instance.start();
} }
// Human to go
bool ret = false; bool ret = false;
Chain.capture(() { Chain.capture(() {
switch (position.action) { switch (position.action) {
@ -431,7 +435,7 @@ class _GamePageState extends State<GamePage>
Game.instance.sideToMove = position.sideToMove() ?? PieceColor.nobody; Game.instance.sideToMove = position.sideToMove() ?? PieceColor.nobody;
setState(() {}); setState(() {});
}); }); // Chain.capture
return ret; return ret;
} }

View File

@ -64,7 +64,7 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
context: context, context: context,
titleString: S.of(context).piecesCount, titleString: S.of(context).piecesCount,
subtitleString: S.of(context).piecesCount_Detail, subtitleString: S.of(context).piecesCount_Detail,
trailingString: Config.piecesCount == 9 ? '9' : '12', trailingString: Config.piecesCount.toString(),
onTap: setNTotalPiecesEachSide, onTap: setNTotalPiecesEachSide,
), ),
ListItemDivider(), ListItemDivider(),
@ -197,6 +197,22 @@ class _RuleSettingsPageState extends State<RuleSettingsPage> {
onChanged: callback, onChanged: callback,
), ),
ListItemDivider(), ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('10'),
groupValue: Config.piecesCount,
value: 10,
onChanged: callback,
),
ListItemDivider(),
RadioListTile(
activeColor: AppTheme.switchListTileActiveColor,
title: Text('11'),
groupValue: Config.piecesCount,
value: 11,
onChanged: callback,
),
ListItemDivider(),
RadioListTile( RadioListTile(
activeColor: AppTheme.switchListTileActiveColor, activeColor: AppTheme.switchListTileActiveColor,
title: Text('12'), title: Text('12'),