diff --git a/src/chess.lua b/src/chess.lua index 511bce4..1887d7d 100644 --- a/src/chess.lua +++ b/src/chess.lua @@ -126,7 +126,17 @@ local function attacked(color, idx, board) return threatDetected 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+)") if not piece then return {} @@ -513,6 +523,33 @@ local function get_possible_moves(board, from_idx) return moves 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 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) 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. -- Uses Figurine Algebraic Notation. local function get_moves_formstring(meta) @@ -811,9 +852,9 @@ local function update_formspec(meta) -- player has resigned local resign_s = minetest.colorize("#FF0000", "["..S("resigned").."]") -- player has won - local win_s = minetest.colorize("#00FF00", "["..S("winner").."]") + local win_s = minetest.colorize("#26AB2B", "["..S("winner").."]") -- player has lost - local lose_s = minetest.colorize("#00FF00", "["..S("loser").."]") + local lose_s = minetest.colorize("#FF0000", "["..S("loser").."]") -- player has a 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 board_t = board_to_table(inv) + local playerWhite = meta:get_string("playerWhite") + local playerBlack = meta:get_string("playerBlack") + update_formspec(meta) local blackCanMove = false local whiteCanMove = false - for i = 1, 64 do - local possibleMovesHere = get_possible_moves(board_t, i) - local stack_name = inv:get_stack("board", i):get_name() - if stack_name:find("black") then - for k, v in pairs(possibleMovesHere) do - 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 + local blackMoves = get_theoretical_moves_for(board_t, "black") + local whiteMoves = get_theoretical_moves_for(board_t, "white") + local b = 0 + for k,v in pairs(blackMoves) do + b = b + 1 + blackCanMove = true end - local currentTurn - local lastTurn = meta:get_string("lastTurn") - if lastTurn == "black" or lastTurn == "" then - currentTurn = "white" - else - currentTurn = "black" + b = 0 + for k,v in pairs(whiteMoves) do + b = b + 1 + whiteCanMove = true 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 -- black was checkmated 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 else -- stalemate 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 end end - if currentTurn == "white" and not whiteCanMove then + if lastMove == "black" and not whiteCanMove then if meta:get_string("whiteAttacked") == "true" then -- white was checkmated 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 else -- stalemate 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 end end end - function realchess.init(pos) local meta = minetest.get_meta(pos) local inv = meta:get_inventory() @@ -1638,16 +1695,8 @@ local function ai_move(inv, meta) end if (lastMove == opponentColor or (aiColor == "white" and lastMove == "")) and gameResult == "" then update_formspec(meta) - local moves = {} - for i = 1, 64 do - 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 moves = get_theoretical_moves_for(board_t, aiColor) local choice_from, choice_to = best_move(moves) 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_to_eaten_list(meta, pieceTo, pieceTo_s) + update_game_result(meta) update_formspec(meta) @@ -1856,10 +1906,10 @@ function realchess.fields(pos, _, fields, sender) meta:set_string("gameResultReason", "resign") if whiteWon then meta:set_string("gameResult", "whiteWon") - add_move_to_moves_list(meta, "", "", "", "", "", "whiteWon") + add_special_to_moves_list(meta, "whiteWon") else meta:set_string("gameResult", "blackWon") - add_move_to_moves_list(meta, "", "", "", "", "", "blackWon") + add_special_to_moves_list(meta, "blackWon") end minetest.chat_send_player(loser, chat_prefix .. S("You have resigned."))