From 7c27bb75ed61a6aa41ff2b1ba0f99e10224fd5d8 Mon Sep 17 00:00:00 2001 From: Wuzzy Date: Mon, 17 Jul 2023 19:34:45 +0200 Subject: [PATCH] Partially implement 50-move rule & 'samepos' rule Using textures from PixelBOX --- src/chess.lua | 199 +++++++++++++++++++++++---- textures/chess_draw_50move.png | Bin 0 -> 472 bytes textures/chess_draw_50move_next.png | Bin 0 -> 466 bytes textures/chess_draw_repeat3.png | Bin 0 -> 340 bytes textures/chess_draw_repeat3_next.png | Bin 0 -> 441 bytes textures/chess_resign.png | Bin 0 -> 380 bytes 6 files changed, 173 insertions(+), 26 deletions(-) create mode 100644 textures/chess_draw_50move.png create mode 100644 textures/chess_draw_50move_next.png create mode 100644 textures/chess_draw_repeat3.png create mode 100644 textures/chess_draw_repeat3_next.png create mode 100644 textures/chess_resign.png diff --git a/src/chess.lua b/src/chess.lua index 4b59e4f..86bf75b 100644 --- a/src/chess.lua +++ b/src/chess.lua @@ -1102,7 +1102,7 @@ local fs_init = [[ ]] .."bgcolor[#080808BB;true]" .."background[0,0;16,10.7563;chess_bg.png;true]" - .."style_type[button,item_image_button;bgcolor=#8f3000]" + .."style_type[button,image_button,item_image_button;bgcolor=#8f3000]" .."label[2.2,0.652;"..minetest.colorize("#404040", FS("Select a game mode")).."]" .."label[2.2,10.21;"..minetest.colorize("#404040", FS("Select a game mode")).."]" .."label["..fs_gamemode_x..",1.8;"..FS("Select a mode:").."]" @@ -1119,7 +1119,7 @@ local fs = [[ no_prepend[] bgcolor[#080808BB;true] background[0,0;16,10.7563;chess_bg.png;true] - style_type[button,item_image_button;bgcolor=#8f3000] + style_type[button,image_button,item_image_button;bgcolor=#8f3000] style_type[list;spacing=0.1;size=0.975] listcolors[#00000000;#00000000;#00000000;#30434C;#FFF] list[context;board;0.47,1.155;8,8;] @@ -1359,6 +1359,33 @@ local function get_positions_history(meta) return positions_list end +-- Returns the highest number of positions that are repeated +-- in the given positions history list. +-- Arguments: +-- * positions: positions history list returned by get_position_history() +-- * stop_counting_at: stop counting when this many repetitons have been found (optional) +local function count_repeated_positions(positions, stop_counting_at) + -- Count how often each position occurred + local positions_counter = {} + local maxRepeatedPositions = 0 + for p = 1, #positions do + local position = positions[p] + if positions_counter[position] == nil then + positions_counter[position] = 1 + else + positions_counter[position] = positions_counter[position] + 1 + end + + if positions_counter[position] > maxRepeatedPositions then + maxRepeatedPositions = positions_counter[position] + end + if stop_counting_at and maxRepeatedPositions >= stop_counting_at then + break + end + end + return maxRepeatedPositions +end + -- Create the full formspec string for the sequence of moves. -- Uses Figurine Algebraic Notation. local function get_moves_formstring(meta) @@ -1673,13 +1700,54 @@ local function update_formspec(meta) end end - local game_buttons - if mode ~= "bot_vs_bot" and (gameResult == "" and (playerWhite ~= "" and playerBlack ~= "")) then - game_buttons = "button[13.36,0.26;2,0.8;resign;"..FS("Resign").."]" - else - game_buttons = "button[13.36,0.26;2,0.8;new;"..FS("New game").."]" + -- Resign / Start new game + local game_buttons = "" + game_buttons = game_buttons .. "button[13.36,0.26;2,0.8;new;"..FS("New game").."]" + + local playerActionsAvailable = mode ~= "bot_vs_bot" and gameResult == "" + + if playerActionsAvailable and (playerWhite ~= "" and playerBlack ~= "") then + game_buttons = game_buttons .. "image_button[14.56,9.7;0.8,0.8;chess_resign.png;resign;]" .. + "tooltip[resign;"..FS("Resign").."]" end + if playerActionsAvailable then + -- 50-move rule + local halfmoveClock = meta:get_int("halfmoveClock") + if halfmoveClock == 99 then + -- when the 50 moves without capture / pawn move is about to occur + game_buttons = game_buttons .. "image_button[13.36,9.7;0.8,0.8;chess_draw_50move_next.png;draw_50_moves;]".. + "tooltip[draw_50_moves;".. + FS("Invoke the 50-move rule and try to draw the game in your next move.").."\n".. + FS("If your next move is the 50th consecutive move of both players where no pawn moved and no piece was captured, the game will be drawn.").."]" + elseif halfmoveClock >= 100 then + -- when the 50 moves without capture / pawn move have occured occur + game_buttons = game_buttons .. "image_button[13.36,9.7;0.8,0.8;chess_draw_50move.png;draw_50_moves;]".. + "tooltip[draw_50_moves;".. + FS("Invoke the 50-move rule and draw the game.").."\n".. + FS("(In the last 50 moves of both players, no pawn moved and no piece was captured.)").."]" + end + + -- "same position has occured 3 times" rule + -- Count how often each position occurred + local positions = get_positions_history(meta) + local maxRepeatedPositions = count_repeated_positions(positions, 3) + if maxRepeatedPositions == 2 then + -- If the same position is about to occur 3 times + game_buttons = game_buttons .. "image_button[12.36,9.7;0.8,0.8;chess_draw_repeat3_next.png;draw_repeat_3;]".. + "tooltip[draw_repeat_3;".. + FS("Invoke the 'same position' rule and try to draw the game in your next move.").."\n".. + S("If your next move causes the same position to occur a 3rd time, the game will be drawn.").."]" + elseif maxRepeatedPositions >= 3 then + -- If the same position has already occured 3 times + game_buttons = game_buttons .. "image_button[12.36,9.7;0.8,0.8;chess_draw_repeat3.png;draw_repeat_3;]".. + "tooltip[draw_repeat_3;".. + FS("Invoke the 'same position' rule and draw the game.").."\n".. + S("(The same position has occured 3 times.)").."]" + end + end + + local debug_formstring = "" if CHESS_DEBUG then -- Write a debug string in the formspec based on FEN @@ -1711,7 +1779,7 @@ local function update_formspec(meta) local d_fullmove = tostring(get_current_fullmove(meta) + 1) local debug_str = d_turn .. " " .. d_castling .. " " .. d_en_passant .. " " .. d_halfmove_clock .. " " .. d_fullmove - debug_formstring = "label[9.9,10.2;DEBUG: "..debug_str.."]" + debug_formstring = "label[6.9,10.2;DEBUG: "..debug_str.."]" end local formspec = fs .. @@ -1854,28 +1922,15 @@ local function update_game_result(meta) -- First, generate the position history local forceRepetitionDraw = false local positions = get_positions_history(meta) - local positions_counter = {} - -- Count how often each position occurred - for p = 1, #positions do - local position = positions[p] - if positions_counter[position] == nil then - positions_counter[position] = 1 - else - positions_counter[position] = positions_counter[position] + 1 - end - - if positions_counter[position] == 5 then - forceRepetitionDraw = true - break - end + -- Then count the repeated positions + local maxRepeatedPositions = count_repeated_positions(positions, 5) + if maxRepeatedPositions >= 5 then + forceRepetitionDraw = true end if CHESS_DEBUG then - -- Show message if last position occurred at least 2 times + -- Show last position local last_position = positions[#positions] local msg = "Current position: \"" .. last_position .. "\"" - if positions_counter[last_position] >= 2 then - msg = msg .. " (occurred "..positions_counter[last_position].." times)" - end send_message_2(playerWhite, playerBlack, msg, botColor) -- Compare the last position with the actual chessboard @@ -2677,6 +2732,98 @@ function realchess.fields(pos, _, fields, sender) return end + -- Claim or declare draw via the 50-move rule + if fields.draw_50_moves then + local botColor = meta:get_string("botColor") + local lastMove = meta:get_string("lastMove") + if playerWhite == "" and playerBlack == "" or lastMove == "" then + return + end + local currentPlayer + if lastMove == "black" or lastMove == "" then + currentPlayer = "white" + else + currentPlayer = "black" + end + + local claimer, other + if (currentPlayer == "white" and playerWhite == playerName) then + claimer = playerWhite + other = playerBlack + elseif (currentPlayer == "black" and playerBlack == playerName) then + claimer = playerBlack + other = playerWhite + else + send_message(playerName, S("You can't claim a draw, it's not your turn!")) + return + end + + local halfmoveClock = meta:get_int("halfmoveClock") + if halfmoveClock == 99 then + send_message(claimer, S(""), botColor) + elseif halfmoveClock >= 100 then + meta:set_string("gameResult", "draw") + meta:set_string("gameResultReason", "50_move_rule") + add_special_to_moves_list(meta, "draw") + update_formspec(meta) + send_message(claimer, S("You have drawn the game by applying the 50-move rule."), botColor) + if claimer ~= other then + send_message(other, S("@1 has drawn the game by applying the 50-move rule.", claimer), botColor) + end + minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because "..claimer.." has applied the 50-move rule") + else + send_message(claimer, S("Your draw claim is invalid!"), botColor) + end + + return + end + + -- Claim or declare draw via the same position rule (same position occured >= 3 times) + if fields.draw_repeat_3 then + local botColor = meta:get_string("botColor") + local lastMove = meta:get_string("lastMove") + if playerWhite == "" and playerBlack == "" or lastMove == "" then + return + end + local currentPlayer + if lastMove == "black" or lastMove == "" then + currentPlayer = "white" + else + currentPlayer = "black" + end + + local claimer, other + if (currentPlayer == "white" and playerWhite == playerName) then + claimer = playerWhite + other = playerBlack + elseif (currentPlayer == "black" and playerBlack == playerName) then + claimer = playerBlack + other = playerWhite + else + send_message(playerName, S("You can't claim a draw, it's not your turn!")) + return + end + + local positions = get_positions_history(meta) + local maxRepeatedPositions = count_repeated_positions(positions, 3) + if maxRepeatedPositions == 2 then + send_message(claimer, S(""), botColor) + elseif maxRepeatedPositions >= 3 then + meta:set_string("gameResult", "draw") + meta:set_string("gameResultReason", "same_position_3") + add_special_to_moves_list(meta, "draw") + update_formspec(meta) + send_message(claimer, S("You have drawn the game by applying the same position rule."), botColor) + if claimer ~= other then + send_message(other, S("@1 has drawn the game by applying the same position rule.", claimer), botColor) + end + minetest.log("action", "[xdecor] Chess: A game between "..playerWhite.." and "..playerBlack.." ended in a draw because "..claimer.." has applied the same position rule") + else + send_message(claimer, S("Your draw claim is invalid!"), botColor) + end + return + end + local promotions = { "queen_white", "rook_white", "bishop_white", "knight_white", "queen_black", "rook_black", "bishop_black", "knight_black", diff --git a/textures/chess_draw_50move.png b/textures/chess_draw_50move.png new file mode 100644 index 0000000000000000000000000000000000000000..5ae1c4114d6635ad163268320981ceed235753a9 GIT binary patch literal 472 zcmV;}0Vn>6P)Nkl5Ht7{`B4p*WIuEm$dJXl){4WF(GWHoWN zQ!pS|4pk}(Jv<&`t0BgO~ViEv-vv;AQs=RvC)y(1t{2$f7?0;U8;Y?_%8WMsyYjpEK94CrNa(=K}@y@K9=`yeB_6Vy$R2^AOh zrv(ROoW&?9hAci8c81B+1flQhEYAD)edj0<;eVi-%_eQ941nuJ$$1=HFOs%XR)Dx( z^enKVg)G^#oc($88dI?f15SE3EZMUv2T(E%I!D)7g+Y?!C1(JuFragEP02J=QURdt zlzF(goRlPSE|~_d7x8s@>N!E&JDVhpjI;VX2Xfhq)nXp4QW+7k3Iow96{^MjKf<|8 zPfDi2&h9AQ8J>htP*snrcSd&hj*_@G|%o4)V=WP5X&+h3PF0rtMTWVyTf=>Px#07*qo IM6N<$f+P{$0ssI2 literal 0 HcmV?d00001 diff --git a/textures/chess_draw_repeat3.png b/textures/chess_draw_repeat3.png new file mode 100644 index 0000000000000000000000000000000000000000..1cbcb75863787be956a891921328c5be23138e90 GIT binary patch literal 340 zcmV-a0jvIrP)*4Q_h3_F+C)<0MA#H!T^cC~y`$&Q zw5eJemB}0L?)&n8mz;22mt9sN@6a0c&3M=|d3QwC1lFi;0L~hx7`^81{tAgi#!}sjn27U4*6{@qI^L_XGoqMkshQWVI>(HUqbn#GIxsQ@PZ6R291+q<*^81Az~M8oING!w*6b_s19 zx$Jn8*j5!~+xS*?062}T%KX~?k0mNsIJmG5!1Kic%C;C?-Uc9h7HQ4`oe^jljfA!U z$Xy-+U}PQ<(I}Rkw$~`BOkM#@_4L+_8p6Uu5~ub>$^>v2={w2)MDlyYM1-gq`%fTYU6vltIG=U@zZ7`9x>XKp+eE>_v&9Re(#-E)Leiob>sf(mO!wMee7E_BiNJSC;XG)<(GOw$zK^N8YD z0Gyp)E&~8`dwm`sZdI^%q-*c_l+7#wAc|uJh?NQeCNKAD|L|l%qV3cG$g_lY`+!** zEr3+Yvdhe0L7WY@P_fzrDJ8?Z8vuI8r#dh0wo@a|5`52F0MH6d)1=jGU>Ftv^}35; zShSiA?ZvMOtSap6wmtz|7u(A9!+dV!x8DfhZ*5~cH71j1f}o>Wi*xSP>K3MHD&Ah7 z00@GPHZRW-%^rqhJqt?!<^IadVK`=%Mug#*Zm%zXYyl9X;Xuu&PS=WjG#o7N!@n=n a+VKH}Y=nN{QF{vj0000