Refactor chess code to access node meta less often
This commit is contained in:
parent
1156940f99
commit
08991e877c
@ -327,11 +327,10 @@ local function en_passant_to_string(double_step)
|
||||
return s_en_passant
|
||||
end
|
||||
|
||||
local function can_castle(meta, board, from_list, from_idx, to_idx)
|
||||
local function can_castle(board, from_idx, to_idx, castlingRights)
|
||||
local from_x, from_y = index_to_xy(from_idx)
|
||||
local to_x, to_y = index_to_xy(to_idx)
|
||||
local inv = meta:get_inventory()
|
||||
local kingPiece = inv:get_stack(from_list, from_idx):get_name()
|
||||
local kingPiece = board[from_idx]
|
||||
local kingColor
|
||||
if kingPiece:find("black") then
|
||||
kingColor = "black"
|
||||
@ -340,21 +339,21 @@ local function can_castle(meta, board, from_list, from_idx, to_idx)
|
||||
end
|
||||
local possible_castles = {
|
||||
-- white queenside
|
||||
{ y = 7, to_x = 2, rook_idx = 57, rook_goal = 60, acheck_dir = -1, color = "white", meta = "castlingWhiteL", rook_id = 1 },
|
||||
{ y = 7, to_x = 2, rook_idx = 57, rook_goal = 60, acheck_dir = -1, color = "white", rightName = "castlingWhiteL", rook_id = 1 },
|
||||
-- white kingside
|
||||
{ y = 7, to_x = 6, rook_idx = 64, rook_goal = 62, acheck_dir = 1, color = "white", meta = "castlingWhiteR", rook_id = 2 },
|
||||
{ y = 7, to_x = 6, rook_idx = 64, rook_goal = 62, acheck_dir = 1, color = "white", rightName = "castlingWhiteR", rook_id = 2 },
|
||||
-- black queenside
|
||||
{ y = 0, to_x = 2, rook_idx = 1, rook_goal = 4, acheck_dir = -1, color = "black", meta = "castlingBlackL", rook_id = 1 },
|
||||
{ y = 0, to_x = 2, rook_idx = 1, rook_goal = 4, acheck_dir = -1, color = "black", rightName = "castlingBlackL", rook_id = 1 },
|
||||
-- black kingside
|
||||
{ y = 0, to_x = 6, rook_idx = 8, rook_goal = 6, acheck_dir = 1, color = "black", meta = "castlingBlackR", rook_id = 2 },
|
||||
{ y = 0, to_x = 6, rook_idx = 8, rook_goal = 6, acheck_dir = 1, color = "black", rightName = "castlingBlackR", rook_id = 2 },
|
||||
}
|
||||
|
||||
for p=1, #possible_castles do
|
||||
local pc = possible_castles[p]
|
||||
if pc.color == kingColor and pc.to_x == to_x and to_y == pc.y and from_y == pc.y then
|
||||
local castlingMeta = meta:get_int(pc.meta)
|
||||
local rookPiece = inv:get_stack(from_list, pc.rook_idx):get_name()
|
||||
if castlingMeta == 1 and rookPiece == "realchess:rook_"..kingColor.."_"..pc.rook_id then
|
||||
local castlingRightVal = castlingRights[pc.rightName]
|
||||
local rookPiece = board[pc.rook_idx]
|
||||
if castlingRightVal == 1 and rookPiece == "realchess:rook_"..kingColor.."_"..pc.rook_id then
|
||||
-- Check if all squares between king and rook are empty
|
||||
local empty_start, empty_end
|
||||
if pc.acheck_dir == -1 then
|
||||
@ -367,7 +366,7 @@ local function can_castle(meta, board, from_list, from_idx, to_idx)
|
||||
empty_end = pc.rook_idx - 1
|
||||
end
|
||||
for i = empty_start, empty_end do
|
||||
if inv:get_stack(from_list, i):get_name() ~= "" then
|
||||
if board[i] ~= "" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
@ -389,15 +388,15 @@ end
|
||||
-- Checks if a square to check if there is a piece that can be captured en passant. Returns true if this
|
||||
-- is the case, false otherwise.
|
||||
-- Parameters:
|
||||
-- * meta: chessboard node metadata
|
||||
-- * board: chessboard table
|
||||
-- * victim_color: color of the opponent to capture a piece from. "white" or "black". (so in White's turn, pass "black" here)
|
||||
-- * victim_index: board index of the square where you expect the victim to be
|
||||
local function can_capture_en_passant(meta, victim_color, victim_index)
|
||||
local inv = meta:get_inventory()
|
||||
local victimPiece = inv:get_stack("board", victim_index)
|
||||
local double_step_index = meta:get_int("prevDoublePawnStepTo")
|
||||
local victim_name = victimPiece:get_name()
|
||||
if double_step_index ~= 0 and double_step_index == victim_index and victim_name:find(victim_color) and victim_name:sub(11,14) == "pawn" then
|
||||
-- * prevDoublePawnStepTo: if a pawn did a double-step in the previous halfmove, this is the board index of the destination.
|
||||
-- if no pawn made a double-step in the previous halfmove, this is nil or 0.
|
||||
local function can_capture_en_passant(board, victim_color, victim_index, prevDoublePawnStepTo)
|
||||
local victimPiece = board[victim_index]
|
||||
local double_step_index = prevDoublePawnStepTo or 0
|
||||
if double_step_index ~= 0 and double_step_index == victim_index and victimPiece:find(victim_color) and victimPiece:sub(11,14) == "pawn" then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
@ -413,7 +412,7 @@ end
|
||||
-- Any key with a numeric value is a possible destination.
|
||||
-- The numeric value is a move rating for the bot and is 0 by default.
|
||||
-- Example: { [4] = 0, [9] = 0 } -- can move to squares 4 and 9
|
||||
local function get_theoretical_moves_from(meta, board, from_idx)
|
||||
local function get_theoretical_moves_from(board, from_idx, prevDoublePawnStepTo, castlingRights)
|
||||
local piece, color = board[from_idx]:match(":(%w+)_(%w+)")
|
||||
if not piece then
|
||||
return {}
|
||||
@ -450,7 +449,7 @@ local function get_theoretical_moves_from(meta, board, from_idx)
|
||||
can_capture = true
|
||||
else
|
||||
-- en passant
|
||||
if can_capture_en_passant(meta, "black", xy_to_index(to_x, from_y)) then
|
||||
if can_capture_en_passant(board, "black", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
|
||||
can_capture = true
|
||||
en_passant = true
|
||||
end
|
||||
@ -505,7 +504,7 @@ local function get_theoretical_moves_from(meta, board, from_idx)
|
||||
can_capture = true
|
||||
else
|
||||
-- en passant
|
||||
if can_capture_en_passant(meta, "white", xy_to_index(to_x, from_y)) then
|
||||
if can_capture_en_passant(board, "white", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
|
||||
can_capture = true
|
||||
en_passant = true
|
||||
end
|
||||
@ -783,11 +782,10 @@ local function get_theoretical_moves_from(meta, board, from_idx)
|
||||
|
||||
-- KING
|
||||
elseif piece == "king" then
|
||||
local inv = meta:get_inventory()
|
||||
-- King can't move to any attacked square
|
||||
-- king_board simulates the board with the king moved already.
|
||||
-- Required for the attacked() check to work
|
||||
local king_board = realchess.board_to_table(inv)
|
||||
local king_board = table.copy(board)
|
||||
king_board[to_idx] = king_board[from_idx]
|
||||
king_board[from_idx] = ""
|
||||
if realchess.attacked(color, to_idx, king_board) then
|
||||
@ -805,7 +803,7 @@ local function get_theoretical_moves_from(meta, board, from_idx)
|
||||
end
|
||||
|
||||
if dx > 1 or dy > 1 then
|
||||
local cc = can_castle(meta, board, "board", from_idx, to_idx)
|
||||
local cc = can_castle(board, from_idx, to_idx, castlingRights)
|
||||
if not cc then
|
||||
moves[to_idx] = nil
|
||||
end
|
||||
@ -847,10 +845,10 @@ end
|
||||
-- origin_index is the board index for the square to start the piece from (as string)
|
||||
-- and this is the key for a list of destination indixes.
|
||||
-- r1, r2, r3 ... are numeric values (normally 0) to "rate" this square for the bot.
|
||||
function realchess.get_theoretical_moves_for(meta, board, player)
|
||||
function realchess.get_theoretical_moves_for(board, player, prevDoublePawnStepTo, castlingRights)
|
||||
local moves = {}
|
||||
for i = 1, 64 do
|
||||
local possibleMoves = get_theoretical_moves_from(meta, board, i)
|
||||
local possibleMoves = get_theoretical_moves_from(board, i, prevDoublePawnStepTo, castlingRights)
|
||||
if next(possibleMoves) then
|
||||
local stack_name = board[i]
|
||||
if stack_name:find(player) then
|
||||
@ -1899,13 +1897,20 @@ local function update_game_result(meta, lastMove)
|
||||
|
||||
local playerWhite = meta:get_string("playerWhite")
|
||||
local playerBlack = meta:get_string("playerBlack")
|
||||
local prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo")
|
||||
local castlingRights = {
|
||||
castlingWhiteR = meta:get_int("castlingWhiteR"),
|
||||
castlingWhiteL = meta:get_int("castlingWhiteL"),
|
||||
castlingBlackR = meta:get_int("castlingBlackR"),
|
||||
castlingBlackL = meta:get_int("castlingBlackL"),
|
||||
}
|
||||
|
||||
update_formspec(meta)
|
||||
local blackCanMove = false
|
||||
local whiteCanMove = false
|
||||
|
||||
local blackMoves = realchess.get_theoretical_moves_for(meta, board_t, "black")
|
||||
local whiteMoves = realchess.get_theoretical_moves_for(meta, board_t, "white")
|
||||
local blackMoves = realchess.get_theoretical_moves_for(board_t, "black", prevDoublePawnStepTo, castlingRights)
|
||||
local whiteMoves = realchess.get_theoretical_moves_for(board_t, "white", prevDoublePawnStepTo, castlingRights)
|
||||
if next(blackMoves) then
|
||||
blackCanMove = true
|
||||
end
|
||||
@ -2230,6 +2235,7 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
||||
local lastMove = meta:get_string("lastMove")
|
||||
local playerWhite = meta:get_string("playerWhite")
|
||||
local playerBlack = meta:get_string("playerBlack")
|
||||
local prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo")
|
||||
local kingMoved = false
|
||||
local thisMove -- Will replace lastMove when move is legal
|
||||
|
||||
@ -2346,7 +2352,8 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
||||
can_capture = true
|
||||
else
|
||||
-- en passant
|
||||
if can_capture_en_passant(meta, "black", xy_to_index(to_x, from_y)) then
|
||||
local board = realchess.board_to_table(inv)
|
||||
if can_capture_en_passant(board, "black", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
|
||||
can_capture = true
|
||||
en_passant_target = xy_to_index(to_x, from_y)
|
||||
end
|
||||
@ -2414,7 +2421,8 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
||||
can_capture = true
|
||||
else
|
||||
-- en passant
|
||||
if can_capture_en_passant(meta, "white", xy_to_index(to_x, from_y)) then
|
||||
local board = realchess.board_to_table(inv)
|
||||
if can_capture_en_passant(board, "white", xy_to_index(to_x, from_y), prevDoublePawnStepTo) then
|
||||
can_capture = true
|
||||
en_passant_target = xy_to_index(to_x, from_y)
|
||||
end
|
||||
@ -2666,9 +2674,15 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
||||
local check = true
|
||||
local inv = meta:get_inventory()
|
||||
local board = realchess.board_to_table(inv)
|
||||
local castlingRights = {
|
||||
castlingWhiteR = meta:get_int("castlingWhiteR"),
|
||||
castlingWhiteL = meta:get_int("castlingWhiteL"),
|
||||
castlingBlackR = meta:get_int("castlingBlackR"),
|
||||
castlingBlackL = meta:get_int("castlingBlackL"),
|
||||
}
|
||||
|
||||
-- Castling
|
||||
local cc, rook_start, rook_goal, rook_name = can_castle(meta, board, from_list, from_index, to_index)
|
||||
local cc, rook_start, rook_goal, rook_name = can_castle(board, from_index, to_index, castlingRights)
|
||||
if cc then
|
||||
inv:set_stack(from_list, rook_goal, rook_name)
|
||||
inv:set_stack(from_list, rook_start, "")
|
||||
|
169
src/chessbot.lua
169
src/chessbot.lua
@ -3,7 +3,7 @@ local chessbot = {}
|
||||
local realchess = xdecor.chess
|
||||
|
||||
-- Delay in seconds for a bot moving a piece (excluding choosing a promotion)
|
||||
local BOT_DELAY_MOVE = 1.0
|
||||
local BOT_DELAY_MOVE = 0.2
|
||||
-- Delay in seconds for a bot promoting a piece
|
||||
local BOT_DELAY_PROMOTE = 1.0
|
||||
|
||||
@ -36,15 +36,58 @@ local function best_move(moves)
|
||||
return tonumber(choice_from), choice_to
|
||||
end
|
||||
|
||||
function chessbot.move(inv, meta)
|
||||
local board_t = realchess.board_to_table(inv)
|
||||
local lastMove = meta:get_string("lastMove")
|
||||
local gameResult = meta:get_string("gameResult")
|
||||
local botColor = meta:get_string("botColor")
|
||||
function chessbot.choose_move(board_t, meta_t)
|
||||
local lastMove = meta_t["lastMove"]
|
||||
local gameResult = meta_t["gameResult"]
|
||||
local botColor = meta_t["botColor"]
|
||||
local prevDoublePawnStepTo = meta_t["prevDoublePawnStepTo"]
|
||||
local castlingRights = {
|
||||
castlingWhiteR = meta_t["castlingWhiteR"],
|
||||
castlingWhiteL = meta_t["castlingWhiteL"],
|
||||
castlingBlackR = meta_t["castlingBlackR"],
|
||||
castlingBlackL = meta_t["castlingBlackL"],
|
||||
}
|
||||
|
||||
if botColor == "" then
|
||||
return
|
||||
end
|
||||
local currentBotColor, opponentColor
|
||||
if botColor == "black" then
|
||||
currentBotColor = "black"
|
||||
opponentColor = "white"
|
||||
elseif botColor == "white" then
|
||||
currentBotColor = "white"
|
||||
opponentColor = "black"
|
||||
elseif botColor == "both" then
|
||||
opponentColor = lastMove
|
||||
if lastMove == "black" or lastMove == "" then
|
||||
currentBotColor = "white"
|
||||
else
|
||||
currentBotColor = "black"
|
||||
end
|
||||
end
|
||||
if (lastMove == opponentColor or ((botColor == "white" or botColor == "both") and lastMove == "")) and gameResult == "" then
|
||||
|
||||
local moves = realchess.get_theoretical_moves_for(board_t, currentBotColor, prevDoublePawnStepTo, castlingRights)
|
||||
local safe_moves, safe_moves_count = realchess.get_king_safe_moves(moves, board_t, currentBotColor)
|
||||
if safe_moves_count == 0 then
|
||||
-- No safe move: stalemate or checkmate
|
||||
end
|
||||
local choice_from, choice_to = best_move(safe_moves)
|
||||
if choice_from == nil then
|
||||
-- No best move: stalemate or checkmate
|
||||
return
|
||||
end
|
||||
|
||||
return choice_from, choice_to
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
chessbot.perform_move = function(choice_from, choice_to, meta)
|
||||
local lastMove = meta:get_string("lastMove")
|
||||
local botColor = meta:get_string("botColor")
|
||||
local currentBotColor, opponentColor
|
||||
local botName
|
||||
if botColor == "black" then
|
||||
currentBotColor = "black"
|
||||
@ -60,62 +103,56 @@ function chessbot.move(inv, meta)
|
||||
currentBotColor = "black"
|
||||
end
|
||||
end
|
||||
|
||||
-- Bot resigns if no move chosen
|
||||
if not choice_from or not choice_to then
|
||||
realchess.resign(meta, currentBotColor)
|
||||
return
|
||||
end
|
||||
|
||||
if currentBotColor == "white" then
|
||||
botName = meta:get_string("playerWhite")
|
||||
else
|
||||
botName = meta:get_string("playerBlack")
|
||||
end
|
||||
if (lastMove == opponentColor or ((botColor == "white" or botColor == "both") and lastMove == "")) and gameResult == "" then
|
||||
|
||||
local moves = realchess.get_theoretical_moves_for(meta, board_t, currentBotColor)
|
||||
local safe_moves, safe_moves_count = realchess.get_king_safe_moves(moves, board_t, currentBotColor)
|
||||
if safe_moves_count == 0 then
|
||||
-- No safe move: stalemate or checkmate
|
||||
end
|
||||
local choice_from, choice_to = best_move(safe_moves)
|
||||
if choice_from == nil then
|
||||
-- No best move: stalemate or checkmate
|
||||
return
|
||||
local gameResult = meta:get_string("gameResult")
|
||||
if gameResult ~= "" then
|
||||
return
|
||||
end
|
||||
local botColor = meta:get_string("botColor")
|
||||
if botColor == "" then
|
||||
return
|
||||
end
|
||||
local lastMove = meta:get_string("lastMove")
|
||||
local lastMoveTime = meta:get_int("lastMoveTime")
|
||||
if lastMoveTime > 0 or lastMove == "" then
|
||||
-- Set the bot name if not set already
|
||||
if currentBotColor == "black" and meta:get_string("playerBlack") == "" then
|
||||
meta:set_string("playerBlack", botName)
|
||||
elseif currentBotColor == "white" and meta:get_string("playerWhite") == "" then
|
||||
meta:set_string("playerWhite", botName)
|
||||
end
|
||||
|
||||
local pieceFrom = inv:get_stack("board", choice_from):get_name()
|
||||
local pieceTo = inv:get_stack("board", choice_to):get_name()
|
||||
|
||||
minetest.after(BOT_DELAY_MOVE, function()
|
||||
local gameResult = meta:get_string("gameResult")
|
||||
if gameResult ~= "" then
|
||||
return
|
||||
end
|
||||
local botColor = meta:get_string("botColor")
|
||||
if botColor == "" then
|
||||
return
|
||||
end
|
||||
local lastMove = meta:get_string("lastMove")
|
||||
local lastMoveTime = meta:get_int("lastMoveTime")
|
||||
if lastMoveTime > 0 or lastMove == "" then
|
||||
-- Set the bot name if not set already
|
||||
if currentBotColor == "black" and meta:get_string("playerBlack") == "" then
|
||||
meta:set_string("playerBlack", botName)
|
||||
elseif currentBotColor == "white" and meta:get_string("playerWhite") == "" then
|
||||
meta:set_string("playerWhite", botName)
|
||||
end
|
||||
|
||||
-- Make a move
|
||||
local moveOK = realchess.move(meta, "board", choice_from, "board", choice_to, botName)
|
||||
if not moveOK then
|
||||
minetest.log("error", "[xdecor] Chess: Bot tried to make an invalid move from "..
|
||||
realchess.index_to_notation(choice_from).." to "..realchess.index_to_notation(choice_to))
|
||||
end
|
||||
-- Bot resigns if it tried to make an invalid move
|
||||
if not moveOK then
|
||||
realchess.resign(meta, currentBotColor)
|
||||
end
|
||||
end
|
||||
end)
|
||||
-- Make a move
|
||||
local moveOK = realchess.move(meta, "board", choice_from, "board", choice_to, botName)
|
||||
if not moveOK then
|
||||
minetest.log("error", "[xdecor] Chess: Bot tried to make an invalid move from "..
|
||||
realchess.index_to_notation(choice_from).." to "..realchess.index_to_notation(choice_to))
|
||||
end
|
||||
-- Bot resigns if it tried to make an invalid move
|
||||
if not moveOK then
|
||||
realchess.resign(meta, currentBotColor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function chessbot.promote(inv, meta, pawnIndex)
|
||||
function chessbot.choose_promote(board_t, pawnIndex)
|
||||
-- Bot always promotes to queen
|
||||
return "queen"
|
||||
end
|
||||
|
||||
function chessbot.perform_promote(meta, promoteTo)
|
||||
minetest.after(BOT_DELAY_PROMOTE, function()
|
||||
local lastMove = meta:get_string("lastMove")
|
||||
local color
|
||||
@ -124,9 +161,35 @@ function chessbot.promote(inv, meta, pawnIndex)
|
||||
else
|
||||
color = "black"
|
||||
end
|
||||
-- Always promote to queen
|
||||
realchess.promote_pawn(meta, color, "queen")
|
||||
realchess.promote_pawn(meta, color, promoteTo)
|
||||
end)
|
||||
end
|
||||
|
||||
function chessbot.move(inv, meta)
|
||||
local board_t = realchess.board_to_table(inv)
|
||||
local meta_t = {
|
||||
lastMove = meta:get_string("lastMove"),
|
||||
gameResult = meta:get_string("gameResult"),
|
||||
botColor = meta:get_string("botColor"),
|
||||
prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo"),
|
||||
castlingWhiteL = meta:get_int("castlingWhiteL"),
|
||||
castlingWhiteR = meta:get_int("castlingWhiteR"),
|
||||
castlingBlackL = meta:get_int("castlingBlackL"),
|
||||
castlingBlackR = meta:get_int("castlingBlackR"),
|
||||
}
|
||||
local choice_from, choice_to = chessbot.choose_move(board_t, meta_t)
|
||||
minetest.after(BOT_DELAY_MOVE, function()
|
||||
chessbot.perform_move(choice_from, choice_to, meta)
|
||||
end)
|
||||
end
|
||||
|
||||
function chessbot.promote(inv, meta, pawnIndex)
|
||||
local board_t = realchess.board_to_table(inv)
|
||||
local promoteTo = chessbot.choose_promote(board_t, pawnIndex)
|
||||
if not promoteTo then
|
||||
promoteTo = "queen"
|
||||
end
|
||||
chessbot.perform_promote(meta, promoteTo)
|
||||
end
|
||||
|
||||
return chessbot
|
||||
|
Loading…
Reference in New Issue
Block a user