From fbd55700e0826e55c7f4b2eeb1018d50c0c089ec Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Mon, 17 Jul 2023 14:26:08 +0200 Subject: [PATCH] Chess: Verify position history in debug mode --- src/chess.lua | 161 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 47 deletions(-) diff --git a/src/chess.lua b/src/chess.lua index 38e8e16..2065aba 100644 --- a/src/chess.lua +++ b/src/chess.lua @@ -140,14 +140,13 @@ local function send_message_2(playerName1, playerName2, message, botColor, isDeb end end -local letters = {'a','b','c','d','e','f','g','h'} - +local notation_letters = {'a','b','c','d','e','f','g','h'} local function index_to_notation(idx) local x, y = index_to_xy(idx) if not x or not y then return "??" end - local xstr = letters[x+1] or "?" + local xstr = notation_letters[x+1] or "?" local ystr = tostring(9 - (y+1)) or "?" return xstr .. ystr end @@ -161,7 +160,6 @@ local function board_to_table(inv) return t end - local piece_values = { pawn = 10, knight = 30, @@ -1165,6 +1163,63 @@ local function add_special_to_moves_list(meta, special) add_move_to_moves_list(meta, "", "", "", "", special) end + +-- abbreviation for each piece for the string +-- representation of the board (like in FEN) +local piece_letters = { + white = { + pawn = "P", + knight = "N", + bishop = "B", + rook = "R", + queen = "Q", + king = "K", + }, + black = { + pawn = "p", + knight = "n", + bishop = "b", + rook = "r", + queen = "q", + king = "k", + }, +} + +local function piece_to_letter(piece) + if piece == "" then + return "." + elseif piece:find("white") then + for k,v in pairs(piece_letters.white) do + if piece:find(k) then + return v + end + end + elseif piece:find("black") then + for k,v in pairs(piece_letters.black) do + if piece:find(k) then + return v + end + end + end +end +local function letter_to_piece(letter) + if letter == "." then + return "" + else + for k,v in pairs(piece_letters.white) do + if v == letter then + return v + end + end + for k,v in pairs(piece_letters.black) do + if v == letter then + return v + end + end + end + return nil +end + -- Returns a list of all positions so far, for the purposes -- of determining position equality under the "same position -- repeated X times" draw rule. @@ -1199,37 +1254,7 @@ local function get_positions_history(meta) for b=1, #board do local piece = board[b] local append - if piece == "" then - str = str .. "." - elseif piece:find("white") then - if piece:find("pawn") then - str = str .. "P" - elseif piece:find("bishop") then - str = str .. "B" - elseif piece:find("knight") then - str = str .. "N" - elseif piece:find("rook") then - str = str .. "R" - elseif piece:find("queen") then - str = str .. "Q" - elseif piece:find("king") then - str = str .. "K" - end - elseif piece:find("black") then - if piece:find("pawn") then - str = str .. "p" - elseif piece:find("bishop") then - str = str .. "b" - elseif piece:find("knight") then - str = str .. "n" - elseif piece:find("rook") then - str = str .. "r" - elseif piece:find("queen") then - str = str .. "q" - elseif piece:find("king") then - str = str .. "k" - end - end + str = str .. piece_to_letter(piece) end return str end @@ -1839,9 +1864,12 @@ local function update_game_result(meta) minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw via the 75-move rule") end - local repetitionDraw = false + -- Draw if same position appeared 5 times + -- First, generate the position history + local forceRepetitionDraw = false local positions = get_positions_history(meta) local positions_counter = {} + -- Count how often each position occurred for p = 1, #positions do local position = positions[p] if positions_counter[position] == nil then @@ -1850,21 +1878,60 @@ local function update_game_result(meta) positions_counter[position] = positions_counter[position] + 1 end - if CHESS_DEBUG and p == #positions then - local msg = "Current position: \"" .. position .. "\"" - if positions_counter[position] > 1 then - msg = msg .. " (occurred "..positions_counter[position].." times)" - end - --send_message_2(playerWhite, playerBlack, msg, botColor) - end - if positions_counter[position] == 5 then - repetitionDraw = true + forceRepetitionDraw = true break end end + if CHESS_DEBUG then + -- Show message if last position occurred at least 2 times + local last_position = positions[#positions] + local msg = "Current position: \"" .. last_position .. "\"" + if positions_counter[last_position] >= 2 then + msg = msg .. " (occurred "..positions_counter[last_position].." times)" + end + send_message_2(playerWhite, playerBlack, msg, botColor) - if repetitionDraw then + -- Compare the last position with the actual chessboard + -- to automatically test if the position history is still valid + local pos_split = last_position:split(" ") + local p_board = pos_split[1] + local p_player = pos_split[2] + local p_castling = pos_split[3] + local p_en_passant = pos_split[4] + local errors = 0 + for b=1, #board_t do + local piece = board_t[b] + local letter_real = piece_to_letter(piece) + local letter_pos = string.sub(p_board, b, b) + if letter_real ~= letter_pos then + minetest.log("error", "[xdecor] Chess: Position history inconsistency on board index "..b..": '"..letter_pos.."' seen but '"..letter_real.."' expected") + end + end + -- Compare current player + if (lastMove == "black" or lastMove == "") and p_player ~= "w" then + minetest.log("error", "[xdecor] Chess: Position history inconsistency: Wrong player! '"..p_player.."' seen but 'w' expected") + elseif (lastMove == "white") and p_player ~= "b" then + minetest.log("error", "[xdecor] Chess: Position history inconsistency: Wrong player! '"..p_player.."' seen but 'b' expected") + end + -- Compare castling rights + local d_castling = castling_to_string( + meta:get_int("castlingWhiteR") == 1, + meta:get_int("castlingWhiteL") == 1, + meta:get_int("castlingBlackR") == 1, + meta:get_int("castlingBlackL") == 1) + if d_castling ~= p_castling then + minetest.log("error", "[xdecor] Chess: Position history inconsistency: Castling rights mismatch! '"..p_castling.."' seen but '"..d_castling.."' expected") + end + -- Compare en passant status + local double_step = meta:get_int("prevDoublePawnStepTo") + local d_en_passant = en_passant_to_string(double_step) + if d_en_passant ~= p_en_passant then + minetest.log("error", "[xdecor] Chess: Position history inconsistency: En passant status mismatch! '"..p_en_passant.."' seen but '"..d_en_passant.."' expected") + end + end + + if forceRepetitionDraw then meta:set_string("gameResult", "draw") meta:set_string("gameResultReason", "same_position_5") add_special_to_moves_list(meta, "draw") @@ -2608,7 +2675,7 @@ local function bot_move(inv, meta) end local function bot_promote(inv, meta, pawnIndex) - minetest.after(BOT_DELAY_MOVE, function() + minetest.after(BOT_DELAY_PROMOTE, function() local lastMove = meta:get_string("lastMove") local color if lastMove == "black" or lastMove == "" then