Chess: Move bot code to its own file
This commit is contained in:
parent
fbd55700e0
commit
6a19130042
285
src/chess.lua
285
src/chess.lua
@ -1,25 +1,17 @@
|
|||||||
local realchess = {}
|
-- Init stuff
|
||||||
|
if minetest.get_modpath("realchess") ~= nil then
|
||||||
|
-- If the 'realchess' mod was found, don't use any of this mod's Chess code
|
||||||
|
minetest.log("action", "[xdecor] 'realchess' mod detected. Disabling X-Decor-libre's Chess module in favor of realchess")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
realchess = {}
|
||||||
|
local chessbot = dofile(minetest.get_modpath(minetest.get_current_modname()).."/src/chessbot.lua")
|
||||||
|
screwdriver = screwdriver or {}
|
||||||
|
|
||||||
|
-- Translation init
|
||||||
local S = minetest.get_translator("xdecor")
|
local S = minetest.get_translator("xdecor")
|
||||||
local NS = function(s) return s end
|
local NS = function(s) return s end
|
||||||
local FS = function(...) return minetest.formspec_escape(S(...)) end
|
local FS = function(...) return minetest.formspec_escape(S(...)) end
|
||||||
local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
|
|
||||||
|
|
||||||
-- Bot names
|
|
||||||
local BOT_NAME = NS("Weak Computer")
|
|
||||||
-- Bot names in Bot vs Bot mode
|
|
||||||
local BOT_NAME_1 = NS("Weak Computer 1")
|
|
||||||
local BOT_NAME_2 = NS("Weak Computer 2")
|
|
||||||
-- Delay in seconds for a bot moving a piece (excluding choosing a promotion)
|
|
||||||
local BOT_DELAY_MOVE = 1.0
|
|
||||||
-- Delay in seconds for a bot promoting a piece
|
|
||||||
local BOT_DELAY_PROMOTE = 1.0
|
|
||||||
|
|
||||||
-- Timeout in seconds to allow resetting the game or digging the chessboard.
|
|
||||||
-- If no move was made for this time, everyone can reset the game
|
|
||||||
-- and remove the chessboard.
|
|
||||||
local TIMEOUT = 300
|
|
||||||
|
|
||||||
screwdriver = screwdriver or {}
|
|
||||||
|
|
||||||
-- Chess games are disabled because they are currently too broken.
|
-- Chess games are disabled because they are currently too broken.
|
||||||
-- Set this to true to enable this again and try your luck.
|
-- Set this to true to enable this again and try your luck.
|
||||||
@ -29,6 +21,19 @@ local ENABLE_CHESS_GAMES = true
|
|||||||
-- and enables a "Bot vs Bot" gamemode for testing the bot
|
-- and enables a "Bot vs Bot" gamemode for testing the bot
|
||||||
local CHESS_DEBUG = false
|
local CHESS_DEBUG = false
|
||||||
|
|
||||||
|
-- Bot names
|
||||||
|
local BOT_NAME = NS("Weak Computer")
|
||||||
|
-- Bot names in Bot vs Bot mode
|
||||||
|
local BOT_NAME_1 = NS("Weak Computer 1")
|
||||||
|
local BOT_NAME_2 = NS("Weak Computer 2")
|
||||||
|
|
||||||
|
-- Timeout in seconds to allow resetting the game or digging the chessboard.
|
||||||
|
-- If no move was made for this time, everyone can reset the game
|
||||||
|
-- and remove the chessboard.
|
||||||
|
local TIMEOUT = 300
|
||||||
|
|
||||||
|
local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
|
||||||
|
|
||||||
-- Returns the player name for the given player color.
|
-- Returns the player name for the given player color.
|
||||||
-- In case of a bot player, will return a translated
|
-- In case of a bot player, will return a translated
|
||||||
-- bot name.
|
-- bot name.
|
||||||
@ -141,7 +146,7 @@ local function send_message_2(playerName1, playerName2, message, botColor, isDeb
|
|||||||
end
|
end
|
||||||
|
|
||||||
local notation_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)
|
function realchess.index_to_notation(idx)
|
||||||
local x, y = index_to_xy(idx)
|
local x, y = index_to_xy(idx)
|
||||||
if not x or not y then
|
if not x or not y then
|
||||||
return "??"
|
return "??"
|
||||||
@ -151,7 +156,7 @@ local function index_to_notation(idx)
|
|||||||
return xstr .. ystr
|
return xstr .. ystr
|
||||||
end
|
end
|
||||||
|
|
||||||
local function board_to_table(inv)
|
function realchess.board_to_table(inv)
|
||||||
local t = {}
|
local t = {}
|
||||||
for i = 1, 64 do
|
for i = 1, 64 do
|
||||||
t[#t + 1] = inv:get_stack("board", i):get_name()
|
t[#t + 1] = inv:get_stack("board", i):get_name()
|
||||||
@ -180,7 +185,7 @@ local rookThreats = {false, true, false, true, true, false, true, false}
|
|||||||
local queenThreats = {true, true, true, true, true, true, true, true}
|
local queenThreats = {true, true, true, true, true, true, true, true}
|
||||||
local kingThreats = {true, true, true, true, true, true, true, true}
|
local kingThreats = {true, true, true, true, true, true, true, true}
|
||||||
|
|
||||||
local function attacked(color, idx, board)
|
function realchess.attacked(color, idx, board)
|
||||||
local threatDetected = false
|
local threatDetected = false
|
||||||
local kill = color == "white"
|
local kill = color == "white"
|
||||||
local pawnThreats = {kill, false, kill, false, false, not kill, false, not kill}
|
local pawnThreats = {kill, false, kill, false, false, not kill, false, not kill}
|
||||||
@ -302,7 +307,7 @@ local function en_passant_to_string(double_step)
|
|||||||
else
|
else
|
||||||
dsy = dsy + 1
|
dsy = dsy + 1
|
||||||
end
|
end
|
||||||
s_en_passant = index_to_notation(xy_to_index(dsx, dsy))
|
s_en_passant = realchess.index_to_notation(xy_to_index(dsx, dsy))
|
||||||
end
|
end
|
||||||
return s_en_passant
|
return s_en_passant
|
||||||
end
|
end
|
||||||
@ -354,7 +359,7 @@ local function can_castle(meta, board, from_list, from_idx, to_idx)
|
|||||||
-- Check if square of king as well the squares that king must cross and reach
|
-- Check if square of king as well the squares that king must cross and reach
|
||||||
-- are NOT attacked
|
-- are NOT attacked
|
||||||
for i = from_idx, from_idx + 2 * pc.acheck_dir, pc.acheck_dir do
|
for i = from_idx, from_idx + 2 * pc.acheck_dir, pc.acheck_dir do
|
||||||
if attacked(kingColor, i, board) then
|
if realchess.attacked(kingColor, i, board) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -767,10 +772,10 @@ local function get_theoretical_moves_from(meta, board, from_idx)
|
|||||||
-- King can't move to any attacked square
|
-- King can't move to any attacked square
|
||||||
-- king_board simulates the board with the king moved already.
|
-- king_board simulates the board with the king moved already.
|
||||||
-- Required for the attacked() check to work
|
-- Required for the attacked() check to work
|
||||||
local king_board = board_to_table(inv)
|
local king_board = realchess.board_to_table(inv)
|
||||||
king_board[to_idx] = king_board[from_idx]
|
king_board[to_idx] = king_board[from_idx]
|
||||||
king_board[from_idx] = ""
|
king_board[from_idx] = ""
|
||||||
if attacked(color, to_idx, king_board) then
|
if realchess.attacked(color, to_idx, king_board) then
|
||||||
moves[to_idx] = nil
|
moves[to_idx] = nil
|
||||||
else
|
else
|
||||||
local dx = from_x - to_x
|
local dx = from_x - to_x
|
||||||
@ -798,6 +803,7 @@ local function get_theoretical_moves_from(meta, board, from_idx)
|
|||||||
return {}
|
return {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Rate the possible moves depending on its piece value
|
||||||
for i in pairs(moves) do
|
for i in pairs(moves) do
|
||||||
local stack_name = board[tonumber(i)]
|
local stack_name = board[tonumber(i)]
|
||||||
if stack_name ~= "" then
|
if stack_name ~= "" then
|
||||||
@ -825,7 +831,7 @@ end
|
|||||||
-- origin_index is the board index for the square to start the piece from (as string)
|
-- 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.
|
-- 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.
|
-- r1, r2, r3 ... are numeric values (normally 0) to "rate" this square for the bot.
|
||||||
local function get_theoretical_moves_for(meta, board, player)
|
function realchess.get_theoretical_moves_for(meta, board, player)
|
||||||
local moves = {}
|
local moves = {}
|
||||||
for i = 1, 64 do
|
for i = 1, 64 do
|
||||||
local possibleMoves = get_theoretical_moves_from(meta, board, i)
|
local possibleMoves = get_theoretical_moves_from(meta, board, i)
|
||||||
@ -839,36 +845,7 @@ local function get_theoretical_moves_for(meta, board, player)
|
|||||||
return moves
|
return moves
|
||||||
end
|
end
|
||||||
|
|
||||||
local function best_move(moves)
|
function realchess.locate_kings(board)
|
||||||
local value, choices = 0, {}
|
|
||||||
|
|
||||||
for from, _ in pairs(moves) do
|
|
||||||
for to, val in pairs(_) do
|
|
||||||
if val > value then
|
|
||||||
value = val
|
|
||||||
choices = {{
|
|
||||||
from = from,
|
|
||||||
to = to
|
|
||||||
}}
|
|
||||||
elseif val == value then
|
|
||||||
choices[#choices + 1] = {
|
|
||||||
from = from,
|
|
||||||
to = to
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if #choices == 0 then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local random = math.random(1, #choices)
|
|
||||||
local choice_from, choice_to = choices[random].from, choices[random].to
|
|
||||||
|
|
||||||
return tonumber(choice_from), choice_to
|
|
||||||
end
|
|
||||||
|
|
||||||
local function locate_kings(board)
|
|
||||||
local Bidx, Widx
|
local Bidx, Widx
|
||||||
for i = 1, 64 do
|
for i = 1, 64 do
|
||||||
local piece, color = board[i]:match(":(%w+)_(%w+)")
|
local piece, color = board[i]:match(":(%w+)_(%w+)")
|
||||||
@ -888,10 +865,10 @@ end
|
|||||||
-- 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 is 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 realchess.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)
|
function realchess.has_king_safe_move(theoretical_moves, board, player)
|
||||||
local safe_moves = {}
|
local safe_moves = {}
|
||||||
-- create a virtual board
|
-- create a virtual board
|
||||||
local v_board = table.copy(board)
|
local v_board = table.copy(board)
|
||||||
@ -907,7 +884,7 @@ local function has_king_safe_move(theoretical_moves, board, player)
|
|||||||
-- move the piece on the virtual board
|
-- move the piece on the virtual board
|
||||||
v_board[to_idx] = v_board[from_idx]
|
v_board[to_idx] = v_board[from_idx]
|
||||||
v_board[from_idx] = ""
|
v_board[from_idx] = ""
|
||||||
local black_king_idx, white_king_idx = locate_kings(v_board)
|
local black_king_idx, white_king_idx = realchess.locate_kings(v_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!")
|
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
|
||||||
return false
|
return false
|
||||||
@ -918,7 +895,7 @@ local function has_king_safe_move(theoretical_moves, board, player)
|
|||||||
else
|
else
|
||||||
king_idx = white_king_idx
|
king_idx = white_king_idx
|
||||||
end
|
end
|
||||||
local playerAttacked = attacked(player, king_idx, v_board)
|
local playerAttacked = realchess.attacked(player, king_idx, v_board)
|
||||||
if not playerAttacked then
|
if not playerAttacked then
|
||||||
safe_moves[from_idx] = safe_moves[from_idx] or {}
|
safe_moves[from_idx] = safe_moves[from_idx] or {}
|
||||||
safe_moves[from_idx][to_idx] = value
|
safe_moves[from_idx][to_idx] = value
|
||||||
@ -1421,8 +1398,8 @@ local function get_moves_formstring(meta)
|
|||||||
end
|
end
|
||||||
local pieceTo_si_id = pieceTo ~= "" and get_figurine_id(pieceTo)
|
local pieceTo_si_id = pieceTo ~= "" and get_figurine_id(pieceTo)
|
||||||
|
|
||||||
local coordFrom = index_to_notation(from_idx)
|
local coordFrom = realchess.index_to_notation(from_idx)
|
||||||
local coordTo = index_to_notation(to_idx)
|
local coordTo = realchess.index_to_notation(to_idx)
|
||||||
|
|
||||||
if curPlayerIsWhite then
|
if curPlayerIsWhite then
|
||||||
move_no = move_no + 1
|
move_no = move_no + 1
|
||||||
@ -1492,7 +1469,7 @@ local verify_eaten_list
|
|||||||
if CHESS_DEBUG then
|
if CHESS_DEBUG then
|
||||||
verify_eaten_list = function(meta)
|
verify_eaten_list = function(meta)
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
local board = board_to_table(inv)
|
local board = realchess.board_to_table(inv)
|
||||||
local whitePiecesLeft = 0
|
local whitePiecesLeft = 0
|
||||||
local whitePiecesEaten = 0
|
local whitePiecesEaten = 0
|
||||||
local blackPiecesLeft = 0
|
local blackPiecesLeft = 0
|
||||||
@ -1744,7 +1721,7 @@ end
|
|||||||
|
|
||||||
local function update_game_result(meta)
|
local function update_game_result(meta)
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
local board_t = board_to_table(inv)
|
local board_t = realchess.board_to_table(inv)
|
||||||
|
|
||||||
local playerWhite = meta:get_string("playerWhite")
|
local playerWhite = meta:get_string("playerWhite")
|
||||||
local playerBlack = meta:get_string("playerBlack")
|
local playerBlack = meta:get_string("playerBlack")
|
||||||
@ -1753,8 +1730,8 @@ local function update_game_result(meta)
|
|||||||
local blackCanMove = false
|
local blackCanMove = false
|
||||||
local whiteCanMove = false
|
local whiteCanMove = false
|
||||||
|
|
||||||
local blackMoves = get_theoretical_moves_for(meta, board_t, "black")
|
local blackMoves = realchess.get_theoretical_moves_for(meta, board_t, "black")
|
||||||
local whiteMoves = get_theoretical_moves_for(meta, board_t, "white")
|
local whiteMoves = realchess.get_theoretical_moves_for(meta, board_t, "white")
|
||||||
if next(blackMoves) then
|
if next(blackMoves) then
|
||||||
blackCanMove = true
|
blackCanMove = true
|
||||||
end
|
end
|
||||||
@ -1765,7 +1742,7 @@ local function update_game_result(meta)
|
|||||||
-- assume lastMove was updated *after* the player moved
|
-- assume lastMove was updated *after* the player moved
|
||||||
local lastMove = meta:get_string("lastMove")
|
local lastMove = meta:get_string("lastMove")
|
||||||
|
|
||||||
local black_king_idx, white_king_idx = locate_kings(board_t)
|
local black_king_idx, white_king_idx = realchess.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!")
|
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
|
||||||
return
|
return
|
||||||
@ -1784,10 +1761,10 @@ local function update_game_result(meta)
|
|||||||
|
|
||||||
-- King attacked? This reduces the list of available moves,
|
-- King attacked? This reduces the list of available moves,
|
||||||
-- so remove these, too and check if there are still any left.
|
-- so remove these, too and check if there are still any left.
|
||||||
local isKingAttacked = attacked(checkPlayer, king_idx, board_t)
|
local isKingAttacked = realchess.attacked(checkPlayer, king_idx, board_t)
|
||||||
if isKingAttacked then
|
if isKingAttacked then
|
||||||
meta:set_string(checkPlayer.."Attacked", "true")
|
meta:set_string(checkPlayer.."Attacked", "true")
|
||||||
local is_safe = has_king_safe_move(checkMoves, board_t, checkPlayer)
|
local is_safe = realchess.has_king_safe_move(checkMoves, board_t, checkPlayer)
|
||||||
-- If not safe moves left, player can't move
|
-- If not safe moves left, player can't move
|
||||||
if not is_safe then
|
if not is_safe then
|
||||||
if checkPlayer == "black" then
|
if checkPlayer == "black" then
|
||||||
@ -2451,7 +2428,7 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
|||||||
local dy = from_y - to_y
|
local dy = from_y - to_y
|
||||||
local check = true
|
local check = true
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
local board = board_to_table(inv)
|
local board = realchess.board_to_table(inv)
|
||||||
|
|
||||||
-- Castling
|
-- 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(meta, board, from_list, from_index, to_index)
|
||||||
@ -2479,17 +2456,17 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local board = board_to_table(inv)
|
local board = realchess.board_to_table(inv)
|
||||||
board[to_index] = board[from_index]
|
board[to_index] = board[from_index]
|
||||||
board[from_index] = ""
|
board[from_index] = ""
|
||||||
|
|
||||||
local black_king_idx, white_king_idx = locate_kings(board)
|
local black_king_idx, white_king_idx = realchess.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!")
|
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 = realchess.attacked("black", black_king_idx, board)
|
||||||
local whiteAttacked = attacked("white", white_king_idx, board)
|
local whiteAttacked = realchess.attacked("white", white_king_idx, board)
|
||||||
|
|
||||||
-- Refuse to move if it would put or leave the own king
|
-- Refuse to move if it would put or leave the own king
|
||||||
-- under attack
|
-- under attack
|
||||||
@ -2554,139 +2531,20 @@ function realchess.move(meta, from_list, from_index, to_list, to_index, playerNa
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bot_move(inv, meta)
|
-- Causes the player ("white" or "blue") to resign
|
||||||
local board_t = board_to_table(inv)
|
function realchess.resign(meta, playerColor)
|
||||||
local lastMove = meta:get_string("lastMove")
|
if playerColor == "black" then
|
||||||
local gameResult = meta:get_string("gameResult")
|
|
||||||
local botColor = meta:get_string("botColor")
|
|
||||||
if botColor == "" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local currentBotColor, opponentColor
|
|
||||||
local botName
|
|
||||||
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 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
|
|
||||||
update_formspec(meta)
|
|
||||||
|
|
||||||
local moves = get_theoretical_moves_for(meta, board_t, currentBotColor)
|
|
||||||
|
|
||||||
local choice_from, choice_to = best_move(moves)
|
|
||||||
if choice_from == nil then
|
|
||||||
-- No best move: stalemate or checkmate
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local pieceFrom = inv:get_stack("board", choice_from):get_name()
|
|
||||||
local pieceTo = inv:get_stack("board", choice_to):get_name()
|
|
||||||
|
|
||||||
local black_king_idx, white_king_idx = locate_kings(board_t)
|
|
||||||
local bot_king_idx
|
|
||||||
if currentBotColor == "black" then
|
|
||||||
bot_king_idx = black_king_idx
|
|
||||||
else
|
|
||||||
bot_king_idx = white_king_idx
|
|
||||||
end
|
|
||||||
local botAttacked = attacked(currentBotColor, bot_king_idx, board_t)
|
|
||||||
local kingSafe = true
|
|
||||||
local bestMoveSaveFrom, bestMoveSaveTo
|
|
||||||
|
|
||||||
if botAttacked then
|
|
||||||
kingSafe = false
|
|
||||||
meta:set_string(currentBotColor.."Attacked", "true")
|
|
||||||
local is_safe, safe_moves = has_king_safe_move(moves, board_t, currentBotColor)
|
|
||||||
if is_safe then
|
|
||||||
bestMoveSaveFrom, bestMoveSaveTo = best_move(safe_moves)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
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 moveOK = false
|
|
||||||
if not kingSafe then
|
|
||||||
-- Make a move to put the king out of check
|
|
||||||
if bestMoveSaveTo ~= nil then
|
|
||||||
moveOK = realchess.move(meta, "board", bestMoveSaveFrom, "board", bestMoveSaveTo, botName)
|
|
||||||
if not moveOK then
|
|
||||||
minetest.log("error", "[xdecor] Chess: Bot tried to make an invalid move (to protect the king) from "..
|
|
||||||
index_to_notation(bestMoveSaveFrom).." to "..index_to_notation(bestMoveSaveTo))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- No safe move left: checkmate or stalemate
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- Make a regular move
|
|
||||||
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 "..
|
|
||||||
index_to_notation(choice_from).." to "..index_to_notation(choice_to))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- Bot resigns if it made an incorrect move
|
|
||||||
if not moveOK then
|
|
||||||
meta:set_string("gameResultReason", "resign")
|
|
||||||
if currentBotColor == "black" then
|
|
||||||
meta:set_string("gameResult", "whiteWon")
|
meta:set_string("gameResult", "whiteWon")
|
||||||
|
meta:set_string("gameResultReason", "resign")
|
||||||
add_special_to_moves_list(meta, "whiteWon")
|
add_special_to_moves_list(meta, "whiteWon")
|
||||||
else
|
update_formspec(meta)
|
||||||
|
elseif playerColor == "white" then
|
||||||
meta:set_string("gameResult", "blackWon")
|
meta:set_string("gameResult", "blackWon")
|
||||||
|
meta:set_string("gameResultReason", "resign")
|
||||||
add_special_to_moves_list(meta, "blackWon")
|
add_special_to_moves_list(meta, "blackWon")
|
||||||
end
|
|
||||||
update_formspec(meta)
|
update_formspec(meta)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
|
||||||
else
|
|
||||||
update_formspec(meta)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function bot_promote(inv, meta, pawnIndex)
|
|
||||||
minetest.after(BOT_DELAY_PROMOTE, function()
|
|
||||||
local lastMove = meta:get_string("lastMove")
|
|
||||||
local color
|
|
||||||
if lastMove == "black" or lastMove == "" then
|
|
||||||
color = "white"
|
|
||||||
else
|
|
||||||
color = "black"
|
|
||||||
end
|
|
||||||
-- Always promote to queen
|
|
||||||
realchess.promote_pawn(meta, color, "queen")
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function timeout_format(timeout_limit)
|
local function timeout_format(timeout_limit)
|
||||||
local time_remaining = timeout_limit - minetest.get_gametime()
|
local time_remaining = timeout_limit - minetest.get_gametime()
|
||||||
@ -2725,7 +2583,7 @@ function realchess.fields(pos, _, fields, sender)
|
|||||||
meta:set_string("playerWhite", "*"..BOT_NAME_1.."*")
|
meta:set_string("playerWhite", "*"..BOT_NAME_1.."*")
|
||||||
meta:set_string("playerBlack", "*"..BOT_NAME_2.."*")
|
meta:set_string("playerBlack", "*"..BOT_NAME_2.."*")
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
bot_move(inv, meta)
|
chessbot.move(inv, meta)
|
||||||
elseif fields.single_w then
|
elseif fields.single_w then
|
||||||
meta:set_string("mode", "single")
|
meta:set_string("mode", "single")
|
||||||
meta:set_string("botColor", "black")
|
meta:set_string("botColor", "black")
|
||||||
@ -2735,7 +2593,7 @@ function realchess.fields(pos, _, fields, sender)
|
|||||||
meta:set_string("botColor", "white")
|
meta:set_string("botColor", "white")
|
||||||
meta:set_string("playerWhite", "*"..BOT_NAME.."*")
|
meta:set_string("playerWhite", "*"..BOT_NAME.."*")
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
bot_move(inv, meta)
|
chessbot.move(inv, meta)
|
||||||
elseif fields.multi then
|
elseif fields.multi then
|
||||||
meta:set_string("mode", "multi")
|
meta:set_string("mode", "multi")
|
||||||
end
|
end
|
||||||
@ -2792,13 +2650,10 @@ function realchess.fields(pos, _, fields, sender)
|
|||||||
whiteWon = true
|
whiteWon = true
|
||||||
end
|
end
|
||||||
if winner and loser then
|
if winner and loser then
|
||||||
meta:set_string("gameResultReason", "resign")
|
|
||||||
if whiteWon then
|
if whiteWon then
|
||||||
meta:set_string("gameResult", "whiteWon")
|
realchess.resign(meta, "black")
|
||||||
add_special_to_moves_list(meta, "whiteWon")
|
|
||||||
else
|
else
|
||||||
meta:set_string("gameResult", "blackWon")
|
realchess.resign(meta, "white")
|
||||||
add_special_to_moves_list(meta, "blackWon")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
send_message(loser, S("You have resigned."))
|
send_message(loser, S("You have resigned."))
|
||||||
@ -2912,16 +2767,16 @@ function realchess.move_piece(meta, pieceFrom, from_list, from_index, to_list, t
|
|||||||
-- Let the bot play when it its turn
|
-- Let the bot play when it its turn
|
||||||
if (mode == "bot_vs_bot" or (mode == "single" and lastMove ~= botColor)) and gameResult == "" then
|
if (mode == "bot_vs_bot" or (mode == "single" and lastMove ~= botColor)) and gameResult == "" then
|
||||||
if not promo then
|
if not promo then
|
||||||
bot_move(inv, meta)
|
chessbot.move(inv, meta)
|
||||||
else
|
else
|
||||||
bot_promote(inv, meta, to_index)
|
chessbot.promote(inv, meta, to_index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function realchess.update_state(meta, from_index, to_index, thisMove, promoteFrom, promoteTo)
|
function realchess.update_state(meta, from_index, to_index, thisMove, promoteFrom, promoteTo)
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
local board = board_to_table(inv)
|
local board = realchess.board_to_table(inv)
|
||||||
local pieceTo = board[to_index]
|
local pieceTo = board[to_index]
|
||||||
local pieceFrom = promoteFrom or board[from_index]
|
local pieceFrom = promoteFrom or board[from_index]
|
||||||
|
|
||||||
@ -2930,13 +2785,13 @@ function realchess.update_state(meta, from_index, to_index, thisMove, promoteFro
|
|||||||
board[from_index] = ""
|
board[from_index] = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
local black_king_idx, white_king_idx = locate_kings(board)
|
local black_king_idx, white_king_idx = realchess.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!")
|
minetest.log("error", "[xdecor] Chess: Insufficient kings on chessboard!")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local blackAttacked = attacked("black", black_king_idx, board)
|
local blackAttacked = realchess.attacked("black", black_king_idx, board)
|
||||||
local whiteAttacked = attacked("white", white_king_idx, board)
|
local whiteAttacked = realchess.attacked("white", white_king_idx, board)
|
||||||
|
|
||||||
if blackAttacked then
|
if blackAttacked then
|
||||||
meta:set_string("blackAttacked", "true")
|
meta:set_string("blackAttacked", "true")
|
||||||
@ -3004,7 +2859,7 @@ function realchess.promote_pawn(meta, color, promoteTo)
|
|||||||
local mode = meta:get_string("mode")
|
local mode = meta:get_string("mode")
|
||||||
local gameResult = meta:get_string("gameResult")
|
local gameResult = meta:get_string("gameResult")
|
||||||
if (mode == "bot_vs_bot" or (mode == "single" and lastMove ~= botColor)) and gameResult == "" then
|
if (mode == "bot_vs_bot" or (mode == "single" and lastMove ~= botColor)) and gameResult == "" then
|
||||||
bot_move(inv, meta)
|
chessbot.move(inv, meta)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
minetest.log("error", "[xdecor] Chess: Could not find pawn to promote!")
|
minetest.log("error", "[xdecor] Chess: Could not find pawn to promote!")
|
||||||
|
159
src/chessbot.lua
Normal file
159
src/chessbot.lua
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
local chessbot = {}
|
||||||
|
|
||||||
|
-- Delay in seconds for a bot moving a piece (excluding choosing a promotion)
|
||||||
|
local BOT_DELAY_MOVE = 1.0
|
||||||
|
-- Delay in seconds for a bot promoting a piece
|
||||||
|
local BOT_DELAY_PROMOTE = 1.0
|
||||||
|
|
||||||
|
local function best_move(moves)
|
||||||
|
local value, choices = 0, {}
|
||||||
|
|
||||||
|
for from, _ in pairs(moves) do
|
||||||
|
for to, val in pairs(_) do
|
||||||
|
if val > value then
|
||||||
|
value = val
|
||||||
|
choices = {{
|
||||||
|
from = from,
|
||||||
|
to = to
|
||||||
|
}}
|
||||||
|
elseif val == value then
|
||||||
|
choices[#choices + 1] = {
|
||||||
|
from = from,
|
||||||
|
to = to
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #choices == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local random = math.random(1, #choices)
|
||||||
|
local choice_from, choice_to = choices[random].from, choices[random].to
|
||||||
|
|
||||||
|
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")
|
||||||
|
if botColor == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local currentBotColor, opponentColor
|
||||||
|
local botName
|
||||||
|
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 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 choice_from, choice_to = best_move(moves)
|
||||||
|
if choice_from == nil then
|
||||||
|
-- No best move: stalemate or checkmate
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local pieceFrom = inv:get_stack("board", choice_from):get_name()
|
||||||
|
local pieceTo = inv:get_stack("board", choice_to):get_name()
|
||||||
|
|
||||||
|
local black_king_idx, white_king_idx = realchess.locate_kings(board_t)
|
||||||
|
local bot_king_idx
|
||||||
|
if currentBotColor == "black" then
|
||||||
|
bot_king_idx = black_king_idx
|
||||||
|
else
|
||||||
|
bot_king_idx = white_king_idx
|
||||||
|
end
|
||||||
|
local botAttacked = realchess.attacked(currentBotColor, bot_king_idx, board_t)
|
||||||
|
local kingSafe = true
|
||||||
|
local bestMoveSaveFrom, bestMoveSaveTo
|
||||||
|
|
||||||
|
if botAttacked then
|
||||||
|
kingSafe = false
|
||||||
|
meta:set_string(currentBotColor.."Attacked", "true")
|
||||||
|
local is_safe, safe_moves = realchess.has_king_safe_move(moves, board_t, currentBotColor)
|
||||||
|
if is_safe then
|
||||||
|
bestMoveSaveFrom, bestMoveSaveTo = best_move(safe_moves)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
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 moveOK = false
|
||||||
|
if not kingSafe then
|
||||||
|
-- Make a move to put the king out of check
|
||||||
|
if bestMoveSaveTo ~= nil then
|
||||||
|
moveOK = realchess.move(meta, "board", bestMoveSaveFrom, "board", bestMoveSaveTo, botName)
|
||||||
|
if not moveOK then
|
||||||
|
minetest.log("error", "[xdecor] Chess: Bot tried to make an invalid move (to protect the king) from "..
|
||||||
|
realchess.index_to_notation(bestMoveSaveFrom).." to "..realchess.index_to_notation(bestMoveSaveTo))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- No safe move left: checkmate or stalemate
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Make a regular move
|
||||||
|
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
|
||||||
|
end
|
||||||
|
-- Bot resigns if it made an incorrect move
|
||||||
|
if not moveOK then
|
||||||
|
realchess.resign(meta, currentBotColor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function chessbot.promote(inv, meta, pawnIndex)
|
||||||
|
minetest.after(BOT_DELAY_PROMOTE, function()
|
||||||
|
local lastMove = meta:get_string("lastMove")
|
||||||
|
local color
|
||||||
|
if lastMove == "black" or lastMove == "" then
|
||||||
|
color = "white"
|
||||||
|
else
|
||||||
|
color = "black"
|
||||||
|
end
|
||||||
|
-- Always promote to queen
|
||||||
|
realchess.promote_pawn(meta, color, "queen")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
return chessbot
|
Loading…
Reference in New Issue
Block a user