Chess: Detect checkmate and stalemate (kinda)
This commit is contained in:
parent
fb081e5411
commit
db80dd58db
130
src/chess.lua
130
src/chess.lua
@ -126,7 +126,17 @@ local function attacked(color, idx, board)
|
|||||||
return threatDetected
|
return threatDetected
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get_possible_moves(board, from_idx)
|
-- Returns all theoretically possible moves from a given
|
||||||
|
-- square, according to the piece it occupies. Ignores restrictions like check, etc.
|
||||||
|
-- If the square is empty, no moves are returned.
|
||||||
|
-- Parameters:
|
||||||
|
-- * board: chessboard table
|
||||||
|
-- * from_idx:
|
||||||
|
-- returns: table with the keys used as destination indices
|
||||||
|
-- Any key with a numeric value is a possible destination.
|
||||||
|
-- The numeric value is a move rating for AI and is 0 by default.
|
||||||
|
-- Example: { [4] = 0, [9] = 0 } -- can move to squares 4 and 9
|
||||||
|
local function get_theoretical_moves_from(board, from_idx)
|
||||||
local piece, color = board[from_idx]:match(":(%w+)_(%w+)")
|
local piece, color = board[from_idx]:match(":(%w+)_(%w+)")
|
||||||
if not piece then
|
if not piece then
|
||||||
return {}
|
return {}
|
||||||
@ -513,6 +523,33 @@ local function get_possible_moves(board, from_idx)
|
|||||||
return moves
|
return moves
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- returns all theoretically possible moves on the board for a player
|
||||||
|
-- Parameters:
|
||||||
|
-- * board: chessboard table
|
||||||
|
-- * player: "black" or "white"
|
||||||
|
-- returns: table of this format:
|
||||||
|
-- {
|
||||||
|
-- [origin_index_1] = { [destination_index_1] = r1, [destination_index_2] = r2 },
|
||||||
|
-- [origin_index_2] = { [destination_index_3] = r3 },
|
||||||
|
-- ...
|
||||||
|
-- }
|
||||||
|
-- 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 AI.
|
||||||
|
local function get_theoretical_moves_for(board, player)
|
||||||
|
local moves = {}
|
||||||
|
for i = 1, 64 do
|
||||||
|
local possibleMoves = get_theoretical_moves_from(board, i)
|
||||||
|
if next(possibleMoves) then
|
||||||
|
local stack_name = board[i]
|
||||||
|
if stack_name:find(player) then
|
||||||
|
moves[tostring(i)] = possibleMoves
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return moves
|
||||||
|
end
|
||||||
|
|
||||||
local function best_move(moves)
|
local function best_move(moves)
|
||||||
local value, choices = 0, {}
|
local value, choices = 0, {}
|
||||||
|
|
||||||
@ -647,6 +684,10 @@ local function add_move_to_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_
|
|||||||
meta:set_string("moves_raw", moves_raw)
|
meta:set_string("moves_raw", moves_raw)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function add_special_to_moves_list(meta, special)
|
||||||
|
add_move_to_moves_list(meta, "", "", "", "", "", special)
|
||||||
|
end
|
||||||
|
|
||||||
-- Create the full formspec string for the sequence of moves.
|
-- Create the full formspec string for the sequence of moves.
|
||||||
-- Uses Figurine Algebraic Notation.
|
-- Uses Figurine Algebraic Notation.
|
||||||
local function get_moves_formstring(meta)
|
local function get_moves_formstring(meta)
|
||||||
@ -811,9 +852,9 @@ local function update_formspec(meta)
|
|||||||
-- player has resigned
|
-- player has resigned
|
||||||
local resign_s = minetest.colorize("#FF0000", "["..S("resigned").."]")
|
local resign_s = minetest.colorize("#FF0000", "["..S("resigned").."]")
|
||||||
-- player has won
|
-- player has won
|
||||||
local win_s = minetest.colorize("#00FF00", "["..S("winner").."]")
|
local win_s = minetest.colorize("#26AB2B", "["..S("winner").."]")
|
||||||
-- player has lost
|
-- player has lost
|
||||||
local lose_s = minetest.colorize("#00FF00", "["..S("loser").."]")
|
local lose_s = minetest.colorize("#FF0000", "["..S("loser").."]")
|
||||||
-- player has a draw
|
-- player has a draw
|
||||||
local draw_s = minetest.colorize("#FF00FF", "["..S("draw").."]")
|
local draw_s = minetest.colorize("#FF00FF", "["..S("draw").."]")
|
||||||
|
|
||||||
@ -901,61 +942,77 @@ 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 = board_to_table(inv)
|
||||||
|
|
||||||
|
local playerWhite = meta:get_string("playerWhite")
|
||||||
|
local playerBlack = meta:get_string("playerBlack")
|
||||||
|
|
||||||
update_formspec(meta)
|
update_formspec(meta)
|
||||||
local blackCanMove = false
|
local blackCanMove = false
|
||||||
local whiteCanMove = false
|
local whiteCanMove = false
|
||||||
|
|
||||||
for i = 1, 64 do
|
local blackMoves = get_theoretical_moves_for(board_t, "black")
|
||||||
local possibleMovesHere = get_possible_moves(board_t, i)
|
local whiteMoves = get_theoretical_moves_for(board_t, "white")
|
||||||
local stack_name = inv:get_stack("board", i):get_name()
|
local b = 0
|
||||||
if stack_name:find("black") then
|
for k,v in pairs(blackMoves) do
|
||||||
for k, v in pairs(possibleMovesHere) do
|
b = b + 1
|
||||||
blackCanMove = true
|
blackCanMove = true
|
||||||
break
|
|
||||||
end
|
|
||||||
elseif stack_name:find("white") then
|
|
||||||
for k, v in pairs(possibleMovesHere) do
|
|
||||||
whiteCanMove = true
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if blackCanMove and whiteCanMove then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
local currentTurn
|
b = 0
|
||||||
local lastTurn = meta:get_string("lastTurn")
|
for k,v in pairs(whiteMoves) do
|
||||||
if lastTurn == "black" or lastTurn == "" then
|
b = b + 1
|
||||||
currentTurn = "white"
|
whiteCanMove = true
|
||||||
else
|
|
||||||
currentTurn = "black"
|
|
||||||
end
|
end
|
||||||
if currentTurn == "black" and not blackCanMove then
|
|
||||||
|
-- assume lastMove was updated *after* the player moved
|
||||||
|
local lastMove = meta:get_string("lastMove")
|
||||||
|
if lastMove == "white" and not blackCanMove then
|
||||||
if meta:get_string("blackAttacked") == "true" then
|
if meta:get_string("blackAttacked") == "true" then
|
||||||
-- black was checkmated
|
-- black was checkmated
|
||||||
meta:set_string("gameResult", "whiteWon")
|
meta:set_string("gameResult", "whiteWon")
|
||||||
|
meta:set_string("gameResultReason", "checkmate")
|
||||||
|
add_special_to_moves_list(meta, "whiteWon")
|
||||||
|
minetest.chat_send_player(playerWhite, chat_prefix .. S("You have checkmated @1. You win!", playerBlack))
|
||||||
|
minetest.chat_send_player(playerBlack, chat_prefix .. S("You were checkmated by @1. You lose!", playerWhite))
|
||||||
|
minetest.log("action", "[xdecor] Chess: "..playerWhite.." won against "..playerBlack.." by checkmate")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
-- stalemate
|
-- stalemate
|
||||||
meta:set_string("gameResult", "draw")
|
meta:set_string("gameResult", "draw")
|
||||||
|
meta:set_string("gameResultReason", "stalemate")
|
||||||
|
add_special_to_moves_list(meta, "draw")
|
||||||
|
minetest.chat_send_player(playerWhite, chat_prefix .. S("The game ended up in a stalemate! It's a draw!"))
|
||||||
|
if playerWhite ~= playerBlack then
|
||||||
|
minetest.chat_send_player(playerBlack, chat_prefix .. S("The game ended up in a stalemate! It's a draw!"))
|
||||||
|
end
|
||||||
|
minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a stalemate")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if currentTurn == "white" and not whiteCanMove then
|
if lastMove == "black" and not whiteCanMove then
|
||||||
if meta:get_string("whiteAttacked") == "true" then
|
if meta:get_string("whiteAttacked") == "true" then
|
||||||
-- white was checkmated
|
-- white was checkmated
|
||||||
meta:set_string("gameResult", "blackWon")
|
meta:set_string("gameResult", "blackWon")
|
||||||
|
meta:set_string("gameResultReason", "checkmate")
|
||||||
|
add_special_to_moves_list(meta, "blackWon")
|
||||||
|
minetest.chat_send_player(playerBlack, chat_prefix .. S("You have checkmated @1. You win!", playerWhite))
|
||||||
|
minetest.chat_send_player(playerWhite, chat_prefix .. S("You were checkmated by @1. You lose!", playerBlack))
|
||||||
|
minetest.log("action", "[xdecor] Chess: "..playerBlack .." won against "..playerWhite.." by checkmate")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
-- stalemate
|
-- stalemate
|
||||||
meta:set_string("gameResult", "draw")
|
meta:set_string("gameResult", "draw")
|
||||||
|
meta:set_string("gameResultReason", "stalemate")
|
||||||
|
add_special_to_moves_list(meta, "draw")
|
||||||
|
minetest.chat_send_player(playerWhite, chat_prefix .. S("The game ended up in a stalemate! It's a draw!"))
|
||||||
|
if playerWhite ~= playerBlack then
|
||||||
|
minetest.chat_send_player(playerBlack, chat_prefix .. S("The game ended up in a stalemate! It's a draw!"))
|
||||||
|
end
|
||||||
|
minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a stalemate")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function realchess.init(pos)
|
function realchess.init(pos)
|
||||||
local meta = minetest.get_meta(pos)
|
local meta = minetest.get_meta(pos)
|
||||||
local inv = meta:get_inventory()
|
local inv = meta:get_inventory()
|
||||||
@ -1638,16 +1695,8 @@ local function ai_move(inv, meta)
|
|||||||
end
|
end
|
||||||
if (lastMove == opponentColor or (aiColor == "white" and lastMove == "")) and gameResult == "" then
|
if (lastMove == opponentColor or (aiColor == "white" and lastMove == "")) and gameResult == "" then
|
||||||
update_formspec(meta)
|
update_formspec(meta)
|
||||||
local moves = {}
|
|
||||||
|
|
||||||
for i = 1, 64 do
|
local moves = get_theoretical_moves_for(board_t, aiColor)
|
||||||
local possibleMoves = get_possible_moves(board_t, i)
|
|
||||||
local stack_name = inv:get_stack("board", i):get_name()
|
|
||||||
|
|
||||||
if stack_name:find(aiColor) then
|
|
||||||
moves[tostring(i)] = possibleMoves
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local choice_from, choice_to = best_move(moves)
|
local choice_from, choice_to = best_move(moves)
|
||||||
if choice_from == nil then
|
if choice_from == nil then
|
||||||
@ -1758,6 +1807,7 @@ local function ai_move(inv, meta)
|
|||||||
|
|
||||||
add_move_to_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, choice_from, choice_to)
|
add_move_to_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, choice_from, choice_to)
|
||||||
add_to_eaten_list(meta, pieceTo, pieceTo_s)
|
add_to_eaten_list(meta, pieceTo, pieceTo_s)
|
||||||
|
|
||||||
update_game_result(meta)
|
update_game_result(meta)
|
||||||
|
|
||||||
update_formspec(meta)
|
update_formspec(meta)
|
||||||
@ -1856,10 +1906,10 @@ function realchess.fields(pos, _, fields, sender)
|
|||||||
meta:set_string("gameResultReason", "resign")
|
meta:set_string("gameResultReason", "resign")
|
||||||
if whiteWon then
|
if whiteWon then
|
||||||
meta:set_string("gameResult", "whiteWon")
|
meta:set_string("gameResult", "whiteWon")
|
||||||
add_move_to_moves_list(meta, "", "", "", "", "", "whiteWon")
|
add_special_to_moves_list(meta, "whiteWon")
|
||||||
else
|
else
|
||||||
meta:set_string("gameResult", "blackWon")
|
meta:set_string("gameResult", "blackWon")
|
||||||
add_move_to_moves_list(meta, "", "", "", "", "", "blackWon")
|
add_special_to_moves_list(meta, "blackWon")
|
||||||
end
|
end
|
||||||
|
|
||||||
minetest.chat_send_player(loser, chat_prefix .. S("You have resigned."))
|
minetest.chat_send_player(loser, chat_prefix .. S("You have resigned."))
|
||||||
|
Loading…
Reference in New Issue
Block a user