Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sudoku chess variant #827

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,11 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
parse_attribute("flyingGeneral", v->flyingGeneral);
parse_attribute("soldierPromotionRank", v->soldierPromotionRank);
parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces);
parse_attribute("sudoku", v->sudoku);
parse_attribute("sudokuBoxWidth", v->sudokuBoxWidth);
parse_attribute("sudokuBoxHeight", v->sudokuBoxHeight);
parse_attribute("sudokuAllowedPawns", v->sudokuAllowedPawns);
parse_attribute("sudokuRoyalConflict", v->sudokuRoyalConflict);
// game end
parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar);
parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar);
Expand Down Expand Up @@ -640,6 +645,24 @@ Variant* VariantParser<DoCheck>::parse(Variant* v) {
if (v->flagPieceSafe && v->blastOnCapture)
std::cerr << "Can not use flagPieceSafe with blastOnCapture (flagPieceSafe uses simple assessment that does not see blast)." << std::endl;
}
// Check invalid sudoku box sizes
if (v->sudoku && v->sudokuBoxWidth && v->sudokuBoxHeight)
{
int boxesCount = (v->maxFile / v->sudokuBoxWidth + 1) * (v->maxRank / v->sudokuBoxHeight + 1);
if (DoCheck)
{
int width = v->maxFile + 1, height = v->maxRank + 1;
if (width % v->sudokuBoxWidth || height % v->sudokuBoxHeight)
std::cerr << "Sudoku boxes don't fit the board size" << std::endl;
if (boxesCount > width && boxesCount > height)
std::cerr << "Too many sudoku boxes" << std::endl;
}
// Ensure that boxes' count doesn't exceed the allocated array size for tracking the conflicts
// (see StateInfo::pieceCountInSudokuHouse).
// Do this safety measure even when DoCheck is false to avoid memory access issues.
if (boxesCount > FILE_NB)
v->sudokuBoxWidth = v->sudokuBoxHeight = 0;
}
return v;
}

Expand Down
77 changes: 76 additions & 1 deletion src/position.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,75 @@ void Position::set_check_info(StateInfo* si) const {
}


/// Position::set_sudoku_conflicts_info() sets piece count for each sudoku house
/// and sudoku conflicts count for each player

void Position::set_sudoku_conflicts_info(StateInfo* si) const {
if (!var->sudoku) return;

si->sudokuConflictsCount[WHITE] = si->sudokuConflictsCount[BLACK] = 0;
memset(si->pieceCountInSudokuHouse, 0, sizeof si->pieceCountInSudokuHouse);

for (Bitboard b = pieces(); b; )
{
Square s = pop_lsb(b);
Piece pc = piece_on(s);

Color c = color_of(pc);
PieceType pt = piece_type_for_sudoku(pc);

if (is_initial_pawn(pc, s)) continue;
int allowed = allowed_sudoku_conflicts(pt);

auto count = si->pieceCountInSudokuHouse[c][pt];
int newFileCount = ++count[SH_FILE][file_of(s)];
int newRankCount = ++count[SH_RANK][rank_of(s)];
int newBoxCount = sudoku_boxes() ? ++count[SH_BOX][sudoku_box_of(s)] : 0;
if (newFileCount > allowed || newRankCount > allowed || newBoxCount > allowed) {
++si->sudokuConflictsCount[c];
}
}
}


/// Position::move_adds_sudoku_conflicts() checks if performing a move leads to a new sudoku conflict

bool Position::move_adds_sudoku_conflicts(Move m) const {
// Note: currently the function is called only for capture moves.
// That's why it doesn't check for special move types like castling, dropping, etc.
assert(capture(m));

if (!var->sudoku) return false;

Square from = from_sq(m);
Square to = to_sq(m);
Piece pc = moved_piece(m);
Color c = color_of(pc);
assert(c == sideToMove);
PieceType pt = piece_type_for_sudoku(pc);
int allowed = allowed_sudoku_conflicts(pt);

auto count = st->pieceCountInSudokuHouse[c][pt];

File fromFile = file_of(from);
File toFile = file_of(to);
if (fromFile != toFile && count[SH_FILE][toFile] >= allowed) return true;

Rank fromRank = rank_of(from);
Rank toRank = rank_of(to);
if (fromRank != toRank && count[SH_RANK][toRank] >= allowed) return true;

if (sudoku_boxes())
{
int fromBox = sudoku_box_of(from);
int toBox = sudoku_box_of(to);
if (fromBox != toBox && count[SH_BOX][toBox] >= allowed) return true;
}

return false;
}


/// Position::set_state() computes the hash keys of the position, and other
/// data that once computed is updated incrementally as moves are made.
/// The function is only used when a new position is set up, and to verify
Expand All @@ -614,6 +683,7 @@ void Position::set_state(StateInfo* si) const {
si->move = MOVE_NONE;

set_check_info(si);
set_sudoku_conflicts_info(si);

for (Bitboard b = pieces(); b; )
{
Expand Down Expand Up @@ -1053,6 +1123,10 @@ bool Position::legal(Move m) const {
if (must_capture() && !capture(m) && has_capture())
return false;

// Illegal captures
if (capture(m) && type_of(captured_piece(m)) != KING && (sudoku_conflicts(us) || move_adds_sudoku_conflicts(m)))
return false;

// Illegal non-drop moves
if (must_drop() && count_in_hand(us, var->mustDropType) > 0)
{
Expand Down Expand Up @@ -1560,7 +1634,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
Square from = from_sq(m);
Square to = to_sq(m);
Piece pc = moved_piece(m);
Piece captured = piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to);
Piece captured = captured_piece(m);
if (to == from)
{
assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && (pass(us) || var->wallOrMove )));
Expand Down Expand Up @@ -2093,6 +2167,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {

// Update king attacks used for fast check detection
set_check_info(st);
set_sudoku_conflicts_info(st);

// Calculate the repetition info. It is the ply distance from the previous
// occurrence of the same position, negative in the 3-fold case, or zero
Expand Down
49 changes: 49 additions & 0 deletions src/position.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ struct StateInfo {

// Not copied when making a move (will be recomputed anyhow)
Key key;
int sudokuConflictsCount[COLOR_NB];
int pieceCountInSudokuHouse[COLOR_NB][PIECE_TYPE_NB][SH_NB][FILE_NB];
Bitboard checkersBB;
Piece unpromotedCapturedPiece;
Piece unpromotedBycatch[SQUARE_NB];
Expand Down Expand Up @@ -223,6 +225,13 @@ class Position {
int count_with_hand(Color c, PieceType pt) const;
bool bikjang() const;
bool allow_virtual_drop(Color c, PieceType pt) const;
bool sudoku_boxes() const;
int sudoku_box_of(Square s) const;
PieceType piece_type_for_sudoku(Piece pc) const;
int allowed_sudoku_conflicts(PieceType pt) const;
int sudoku_conflicts(Color c) const;
void set_sudoku_conflicts_info(StateInfo* si) const;
bool move_adds_sudoku_conflicts(Move m) const;

// Position representation
Bitboard pieces(PieceType pt = ALL_PIECES) const;
Expand Down Expand Up @@ -279,6 +288,7 @@ class Position {
bool gives_check(Move m) const;
Piece moved_piece(Move m) const;
Piece captured_piece() const;
Piece captured_piece(Move m) const;
const std::string piece_to_partner() const;

// Piece specific
Expand Down Expand Up @@ -344,6 +354,7 @@ class Position {
void set_castling_right(Color c, Square rfrom);
void set_state(StateInfo* si) const;
void set_check_info(StateInfo* si) const;
bool is_initial_pawn(Piece pc, Square s) const;

// Other helpers
void move_piece(Square from, Square to);
Expand Down Expand Up @@ -1407,6 +1418,11 @@ inline Piece Position::captured_piece() const {
return st->capturedPiece;
}

inline Piece Position::captured_piece(Move m) const {
Square to = to_sq(m);
return piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to);
}

inline const std::string Position::piece_to_partner() const {
if (!st->capturedPiece) return std::string();
Color color = color_of(st->capturedPiece);
Expand Down Expand Up @@ -1447,6 +1463,10 @@ inline void Position::remove_piece(Square s) {
unpromotedBoard[s] = NO_PIECE;
}

inline bool Position::is_initial_pawn(Piece pc, Square s) const {
return type_of(pc) == PAWN && rank_of(s) == relative_rank(color_of(pc), RANK_2, max_rank());
}

inline void Position::move_piece(Square from, Square to) {

Piece pc = board[from];
Expand Down Expand Up @@ -1500,6 +1520,35 @@ inline bool Position::allow_virtual_drop(Color c, PieceType pt) const {
&& count_in_hand(c, QUEEN) >= 0);
}

inline bool Position::sudoku_boxes() const {
assert(var != nullptr);
return var->sudoku && var->sudokuBoxWidth && var->sudokuBoxHeight;
}

inline int Position::sudoku_box_of(Square s) const {
assert(var != nullptr);
assert(sudoku_boxes());
return rank_of(s) / var->sudokuBoxHeight * (files() / var->sudokuBoxWidth) + file_of(s) / var->sudokuBoxWidth;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use precalculated array instead of this, like sudoku_box_of[to]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but it's not a heavy calculation, and it depends on variant parameters, so I prefer not to over-complicate the code

}

inline PieceType Position::piece_type_for_sudoku(Piece pc) const {
assert(var != nullptr);
PieceType pt = type_of(pc);
if (var->sudokuRoyalConflict && (pt == KING || pt == COMMONER)) return QUEEN;
return pt;
}

inline int Position::allowed_sudoku_conflicts(PieceType pt) const {
assert(var != nullptr);
return pt == PAWN ? var->sudokuAllowedPawns : 1;
}

inline int Position::sudoku_conflicts(Color c) const {
assert(var != nullptr);
assert(st != nullptr);
return var->sudoku ? st->sudokuConflictsCount[c] : 0;
}

inline Value Position::material_counting_result() const {
auto weight_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); };
int materialCount;
Expand Down
7 changes: 7 additions & 0 deletions src/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,13 @@ struct DirtyPiece {
/// avoid left-shifting a signed int to avoid undefined behavior.
enum Score : int { SCORE_ZERO };

enum SudokuHouse : int {
SH_FILE,
SH_RANK,
SH_BOX,
SH_NB,
};

constexpr Score make_score(int mg, int eg) {
return Score((int)((unsigned int)eg << 16) + mg);
}
Expand Down
5 changes: 5 additions & 0 deletions src/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ struct Variant {
Rank soldierPromotionRank = RANK_1;
EnclosingRule flipEnclosedPieces = NO_ENCLOSING;
bool freeDrops = false;
bool sudoku = false;
int sudokuBoxWidth = 4;
int sudokuBoxHeight = 2;
int sudokuAllowedPawns = FILE_NB;
bool sudokuRoyalConflict = false;

// game end
PieceSet nMoveRuleTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)};
Expand Down
19 changes: 19 additions & 0 deletions src/variants.ini
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@
# adjudicateFullBoard: apply material counting immediately when board is full [bool] (default: false)
# countingRule: enable counting rules [CountingRule] (default: none)
# castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -)
# sudoku: enable sudoku variant - can't capture opponent's pieces when having sudoku conflicts on your pieces [bool] (default: false)
# sudokuBoxWidth: the width of sudoku box (0 to disable boxes) [int] (default: 4)
# sudokuBoxHeight: the height of sudoku box (0 to disable boxes) [int] (default: 2)
# sudokuAllowedPawns: how many pawns could be in a sudoku house without having a sudoku conflict [int] (default: unlimited)
# sudokuRoyalConflict: treat queen and king (or commoner) as one piece type for sudoku conflicts [bool] (default: false)

################################################
### Example for minishogi configuration that would be equivalent to the built-in variant:
Expand Down Expand Up @@ -2010,3 +2015,17 @@ passOnStalemate = true

[andersanti:antichess]
passOnStalemate = true

[sudoku:chess]
king = -
commoner = k
castlingKingPiece = k
extinctionValue = loss
extinctionPieceTypes = k
sudoku = true

[sudokupawns:sudoku]
sudokuAllowedPawns = 2

[sudokuroyal:sudokupawns]
sudokuRoyalConflict = true