Chess: Fix bot failing to move king out of check

This commit is contained in:
Wuzzy 2023-07-17 13:26:48 +02:00
parent 063e346e7b
commit 5f113037e3

View File

@ -884,37 +884,51 @@ end
-- Given a table of theoretical moves and the king of the player is attacked, -- Given a table of theoretical moves and the king of the player is attacked,
-- returns true if the player still has at least one move left, -- returns true if the player still has at least one move left,
-- return false otherwise. -- return false otherwise.
-- 2nd return value ist table of save moves -- 2nd return value is table of save moves
-- * theoretical_moves: moves table returned by get_theoretical_moves_for() -- * theoretical_moves: moves table returned by get_theoretical_moves_for()
-- * board: board table -- * board: board table
-- * player: player color ("white" or "black") -- * player: player color ("white" or "black")
local function has_king_safe_move(theoretical_moves, board, player) local function has_king_safe_move(theoretical_moves, board, player)
local save_moves = {} local safe_moves = {}
local s_board = table.copy(board) -- create a virtual board
local v_board = table.copy(board)
for from_idx, _ in pairs(theoretical_moves) do for from_idx, _ in pairs(theoretical_moves) do
for to_idx, value in pairs(_) do for to_idx, value in pairs(_) do
from_idx = tonumber(from_idx) from_idx = tonumber(from_idx)
s_board[to_idx] = s_board[from_idx]
s_board[from_idx] = "" -- save the old board values before manipulating them
local black_king_idx, white_king_idx = locate_kings(s_board) local bak_to = v_board[to_idx]
local bak_from = v_board[from_idx]
-- move the piece on the virtual board
v_board[to_idx] = v_board[from_idx]
v_board[from_idx] = ""
local black_king_idx, white_king_idx = locate_kings(v_board)
if not black_king_idx or not white_king_idx then
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
return false
end
local king_idx local king_idx
if player == "black" then if player == "black" then
king_idx = black_king_idx king_idx = black_king_idx
else else
king_idx = white_king_idx king_idx = white_king_idx
end end
if king_idx then local playerAttacked = attacked(player, king_idx, v_board)
local playerAttacked = attacked(player, king_idx, s_board)
if not playerAttacked then if not playerAttacked then
save_moves[from_idx] = save_moves[from_idx] or {} safe_moves[from_idx] = safe_moves[from_idx] or {}
save_moves[from_idx][to_idx] = value safe_moves[from_idx][to_idx] = value
end
end end
-- restore the old state of the virtual board
v_board[to_idx] = bak_to
v_board[from_idx] = bak_from
end end
end end
if next(save_moves) then if next(safe_moves) then
return true, save_moves return true, safe_moves
else else
return false return false
end end
@ -1711,15 +1725,11 @@ local function update_game_result(meta)
local blackMoves = get_theoretical_moves_for(meta, board_t, "black") local blackMoves = get_theoretical_moves_for(meta, board_t, "black")
local whiteMoves = get_theoretical_moves_for(meta, board_t, "white") local whiteMoves = get_theoretical_moves_for(meta, board_t, "white")
local b = 0 if next(blackMoves) then
for k,v in pairs(blackMoves) do
blackCanMove = true blackCanMove = true
b = b+1
end end
b = 0 if next(whiteMoves) then
for k,v in pairs(whiteMoves) do
whiteCanMove = true whiteCanMove = true
b = b+1
end end
-- assume lastMove was updated *after* the player moved -- assume lastMove was updated *after* the player moved
@ -1727,18 +1737,19 @@ local function update_game_result(meta)
local black_king_idx, white_king_idx = locate_kings(board_t) local black_king_idx, white_king_idx = locate_kings(board_t)
if not black_king_idx or not white_king_idx then if not black_king_idx or not white_king_idx then
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
return return
end end
local checkPlayer, king_idx, checkMoves local checkPlayer, king_idx, checkMoves
if lastMove == "white" then if lastMove == "black" or lastMove == "" then
checkPlayer = "black"
checkMoves = blackMoves
king_idx = black_king_idx
else
checkPlayer = "white" checkPlayer = "white"
checkMoves = whiteMoves checkMoves = whiteMoves
king_idx = white_king_idx king_idx = white_king_idx
else
checkPlayer = "black"
checkMoves = blackMoves
king_idx = black_king_idx
end end
-- King attacked? This reduces the list of available moves, -- King attacked? This reduces the list of available moves,
@ -2402,6 +2413,7 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
local black_king_idx, white_king_idx = locate_kings(board) local black_king_idx, white_king_idx = locate_kings(board)
if not black_king_idx or not white_king_idx then if not black_king_idx or not white_king_idx then
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
return false return false
end end
local blackAttacked = attacked("black", black_king_idx, board) local blackAttacked = attacked("black", black_king_idx, board)
@ -2513,22 +2525,21 @@ local function bot_move(inv, meta)
local pieceFrom = inv:get_stack("board", choice_from):get_name() local pieceFrom = inv:get_stack("board", choice_from):get_name()
local pieceTo = inv:get_stack("board", choice_to):get_name() local pieceTo = inv:get_stack("board", choice_to):get_name()
local board = board_to_table(inv) local black_king_idx, white_king_idx = locate_kings(board_t)
local black_king_idx, white_king_idx = locate_kings(board)
local bot_king_idx local bot_king_idx
if currentBotColor == "black" then if currentBotColor == "black" then
bot_king_idx = black_king_idx bot_king_idx = black_king_idx
else else
bot_king_idx = white_king_idx bot_king_idx = white_king_idx
end end
local botAttacked = attacked(currentBotColor, bot_king_idx, board) local botAttacked = attacked(currentBotColor, bot_king_idx, board_t)
local kingSafe = true local kingSafe = true
local bestMoveSaveFrom, bestMoveSaveTo local bestMoveSaveFrom, bestMoveSaveTo
if botAttacked then if botAttacked then
kingSafe = false kingSafe = false
meta:set_string(currentBotColor.."Attacked", "true") meta:set_string(currentBotColor.."Attacked", "true")
local is_safe, safe_moves = has_king_safe_move(moves, board, currentBotColor) local is_safe, safe_moves = has_king_safe_move(moves, board_t, currentBotColor)
if is_safe then if is_safe then
bestMoveSaveFrom, bestMoveSaveTo = best_move(safe_moves) bestMoveSaveFrom, bestMoveSaveTo = best_move(safe_moves)
end end
@ -2561,6 +2572,7 @@ local function bot_move(inv, meta)
index_to_notation(bestMoveSaveFrom).." to "..index_to_notation(bestMoveSaveTo)) index_to_notation(bestMoveSaveFrom).." to "..index_to_notation(bestMoveSaveTo))
end end
else else
-- No safe move left: checkmate or stalemate
return return
end end
else else
@ -2846,6 +2858,7 @@ function realchess.update_state(meta, from_index, to_index, thisMove, promoteFro
local black_king_idx, white_king_idx = locate_kings(board) local black_king_idx, white_king_idx = locate_kings(board)
if not black_king_idx or not white_king_idx then if not black_king_idx or not white_king_idx then
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
return return
end end
local blackAttacked = attacked("black", black_king_idx, board) local blackAttacked = attacked("black", black_king_idx, board)