2 screwdriver = screwdriver or {}
4 local function index_to_xy(idx)
7 local y = (idx - x) / 8
12 local function xy_to_index(x, y)
16 local function get_square(a, b)
17 return (a * 8) - (8 - b)
20 local chat_prefix = minetest.colorize("#FFFF00", "[Chess] ")
21 local letters = {'A','B','C','D','E','F','G','H'}
23 local function board_to_table(inv)
26 t[#t + 1] = inv:get_stack("board", i):get_name()
32 local piece_values = {
41 local function get_possible_moves(inv, from_idx)
42 local piece, color = inv:get_stack("board", from_idx):get_name():match(":(%w+)_(%w+)")
43 if not piece then return end
45 local from_x, from_y = index_to_xy(from_idx)
48 local stack = inv:get_stack("board", i)
49 local stack_name = stack:get_name()
51 if stack_name:find((color == "black" and "white" or "black")) or
57 for to_idx in pairs(moves) do
58 local pieceTo = inv:get_stack("board", to_idx):get_name()
59 local to_x, to_y = index_to_xy(to_idx)
62 if piece == "pawn" then
63 if color == "white" then
64 local pawnWhiteMove = inv:get_stack("board", xy_to_index(from_x, from_y - 1)):get_name()
65 -- white pawns can go up only
66 if from_y - 1 == to_y then
67 if from_x == to_x then
71 elseif from_x - 1 == to_x or from_x + 1 == to_x then
72 if not pieceTo:find("black") then
78 elseif from_y - 2 == to_y then
79 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
88 ensure that destination cell is empty
89 elseif x changed one unit left or right
90 ensure the pawn is killing opponent piece
92 move is not legal - abort
95 if from_x == to_x then
99 elseif from_x - 1 == to_x or from_x + 1 == to_x then
100 if not pieceTo:find("black") then
107 elseif color == "black" then
108 local pawnBlackMove = inv:get_stack("board", xy_to_index(from_x, from_y + 1)):get_name()
109 -- black pawns can go down only
110 if from_y + 1 == to_y then
111 if from_x == to_x then
112 if pieceTo ~= "" then
115 elseif from_x - 1 == to_x or from_x + 1 == to_x then
116 if not pieceTo:find("white") then
122 elseif from_y + 2 == to_y then
123 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
132 ensure that destination cell is empty
133 elseif x changed one unit left or right
134 ensure the pawn is killing opponent piece
136 move is not legal - abort
139 if from_x == to_x then
140 if pieceTo ~= "" then
143 elseif from_x - 1 == to_x or from_x + 1 == to_x then
144 if not pieceTo:find("white") then
155 elseif piece == "rook" then
156 if from_x == to_x then
158 if from_y < to_y then
160 -- Ensure that no piece disturbs the way
161 for i = from_y + 1, to_y - 1 do
162 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
168 -- Ensure that no piece disturbs the way
169 for i = to_y + 1, from_y - 1 do
170 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
175 elseif from_y == to_y then
176 -- Mocing horizontally
177 if from_x < to_x then
179 -- ensure that no piece disturbs the way
180 for i = from_x + 1, to_x - 1 do
181 if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
187 -- Ensure that no piece disturbs the way
188 for i = to_x + 1, from_x - 1 do
189 if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
195 -- Attempt to move arbitrarily -> abort
200 elseif piece == "knight" then
202 local dx = from_x - to_x
203 local dy = from_y - to_y
205 -- Get absolute values
219 -- Ensure that dx == 1 and dy == 2
220 if dx ~= 1 or dy ~= 2 then
223 -- Just ensure that destination cell does not contain friend piece
224 -- ^ It was done already thus everything ok
227 elseif piece == "bishop" then
229 local dx = from_x - to_x
230 local dy = from_y - to_y
232 -- Get absolute values
241 -- Ensure dx and dy are equal
246 if from_x < to_x then
247 if from_y < to_y then
249 -- Ensure that no piece disturbs the way
252 "board", xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
258 -- Ensure that no piece disturbs the way
261 "board", xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
267 if from_y < to_y then
269 -- Ensure that no piece disturbs the way
272 "board", xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
278 -- ensure that no piece disturbs the way
281 "board", xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
289 elseif piece == "queen" then
290 local dx = from_x - to_x
291 local dy = from_y - to_y
293 -- Get absolute values
302 -- Ensure valid relative move
303 if dx ~= 0 and dy ~= 0 and dx ~= dy then
307 if from_x == to_x then
309 if from_y < to_y then
311 -- Ensure that no piece disturbs the way
312 for i = from_y + 1, to_y - 1 do
313 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
319 -- Ensure that no piece disturbs the way
320 for i = to_y + 1, from_y - 1 do
321 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
326 elseif from_x < to_x then
327 if from_y == to_y then
329 -- Ensure that no piece disturbs the way
332 "board", xy_to_index(from_x + i, from_y)):get_name() ~= "" then
336 elseif from_y < to_y then
338 -- Ensure that no piece disturbs the way
341 "board", xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
347 -- Ensure that no piece disturbs the way
350 "board", xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
356 if from_y == to_y then
357 -- Mocing horizontally
358 if from_x < to_x then
360 -- ensure that no piece disturbs the way
361 for i = from_x + 1, to_x - 1 do
362 if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
368 -- Ensure that no piece disturbs the way
369 for i = to_x + 1, from_x - 1 do
370 if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
375 elseif from_y < to_y then
377 -- Ensure that no piece disturbs the way
380 "board", xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
386 -- Ensure that no piece disturbs the way
389 "board", xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
397 elseif piece == "king" then
398 local dx = from_x - to_x
399 local dy = from_y - to_y
409 if dx > 1 or dy > 1 then
415 if not next(moves) then return end
417 for i in pairs(moves) do
418 local stack = inv:get_stack("board", tonumber(i))
419 local stack_name = stack:get_name()
421 if stack_name ~= "" then
422 for p, value in pairs(piece_values) do
423 if stack_name:find(p) then
433 local function best_move(moves)
434 local value, choices = 0, {}
436 for from, _ in pairs(moves) do
437 for to, val in pairs(_) do
444 elseif val == value then
445 choices[#choices + 1] = {
453 local random = math.random(1, #choices)
454 local choice_from, choice_to = choices[random].from, choices[random].to
456 return tonumber(choice_from), choice_to
459 local rowDirs = {-1, -1, -1, 0, 0, 1, 1, 1}
460 local colDirs = {-1, 0, 1, -1, 1, -1, 0, 1}
462 local rowDirsKnight = { 2, 1, 2, 1, -2, -1, -2, -1}
463 local colDirsKnight = {-1, -2, 1, 2, 1, 2, -1, -2}
465 local bishopThreats = {true, false, true, false, false, true, false, true}
466 local rookThreats = {false, true, false, true, true, false, true, false}
467 local queenThreats = {true, true, true, true, true, true, true, true}
468 local kingThreats = {true, true, true, true, true, true, true, true}
470 local function attacked(color, idx, board)
471 local threatDetected = false
472 local kill = color == "white"
473 local pawnThreats = {kill, false, kill, false, false, not kill, false, not kill}
476 if not threatDetected then
477 local col, row = index_to_xy(idx)
478 col, row = col + 1, row + 1
481 row = row + rowDirs[dir]
482 col = col + colDirs[dir]
484 if row >= 1 and row <= 8 and col >= 1 and col <= 8 then
485 local square = get_square(row, col)
486 local square_name = board[square]
487 local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
490 if pieceColor ~= color then
491 if piece == "bishop" and bishopThreats[dir] then
492 threatDetected = true
493 elseif piece == "rook" and rookThreats[dir] then
494 threatDetected = true
495 elseif piece == "queen" and queenThreats[dir] then
496 threatDetected = true
499 if piece == "pawn" and pawnThreats[dir] then
500 threatDetected = true
502 if piece == "king" and kingThreats[dir] then
503 threatDetected = true
513 local colK, rowK = index_to_xy(idx)
514 colK, rowK = colK + 1, rowK + 1
515 rowK = rowK + rowDirsKnight[dir]
516 colK = colK + colDirsKnight[dir]
518 if rowK >= 1 and rowK <= 8 and colK >= 1 and colK <= 8 then
519 local square = get_square(rowK, colK)
520 local square_name = board[square]
521 local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
523 if piece and pieceColor ~= color and piece == "knight" then
524 threatDetected = true
530 return threatDetected
533 local function locate_kings(board)
536 local piece, color = board[i]:match(":(%w+)_(%w+)")
537 if piece == "king" then
538 if color == "black" then
550 "realchess:rook_black_1",
551 "realchess:knight_black_1",
552 "realchess:bishop_black_1",
553 "realchess:queen_black",
554 "realchess:king_black",
555 "realchess:bishop_black_2",
556 "realchess:knight_black_2",
557 "realchess:rook_black_2",
558 "realchess:pawn_black_1",
559 "realchess:pawn_black_2",
560 "realchess:pawn_black_3",
561 "realchess:pawn_black_4",
562 "realchess:pawn_black_5",
563 "realchess:pawn_black_6",
564 "realchess:pawn_black_7",
565 "realchess:pawn_black_8",
566 '','','','','','','','','','','','','','','','',
567 '','','','','','','','','','','','','','','','',
568 "realchess:pawn_white_1",
569 "realchess:pawn_white_2",
570 "realchess:pawn_white_3",
571 "realchess:pawn_white_4",
572 "realchess:pawn_white_5",
573 "realchess:pawn_white_6",
574 "realchess:pawn_white_7",
575 "realchess:pawn_white_8",
576 "realchess:rook_white_1",
577 "realchess:knight_white_1",
578 "realchess:bishop_white_1",
579 "realchess:queen_white",
580 "realchess:king_white",
581 "realchess:bishop_white_2",
582 "realchess:knight_white_2",
583 "realchess:rook_white_2"
586 local pieces_str, x = "", 0
587 for i = 1, #pieces do
588 local p = pieces[i]:match(":(%w+_%w+)")
589 if pieces[i]:find(":(%w+)_(%w+)") and not pieces_str:find(p) then
590 pieces_str = pieces_str .. x .. "=" .. p .. ".png,"
594 pieces_str = pieces_str .. "69=mailbox_blank16.png"
599 bgcolor[#080808BB;true]
600 background[0,0;14.7,10;chess_bg.png]
601 list[context;board;0.3,1;8,8;]
602 listcolors[#00000000;#00000000;#00000000;#30434C;#FFF]
603 tableoptions[background=#00000000;highlight=#00000000;border=false]
604 button[10.5,8.5;2,2;new;New game]
605 ]] .. "tablecolumns[image," .. pieces_str ..
606 ";text;color;text;color;text;image," .. pieces_str .. "]"
608 local function update_formspec(meta)
609 local black_king_attacked = meta:get_string("blackAttacked") == "true"
610 local white_king_attacked = meta:get_string("whiteAttacked") == "true"
612 local playerWhite = meta:get_string("playerWhite")
613 local playerBlack = meta:get_string("playerBlack")
615 local moves = meta:get_string("moves")
616 local eaten_img = meta:get_string("eaten_img")
617 local lastMove = meta:get_string("lastMove")
618 local turnBlack = minetest.colorize("#000001", (lastMove == "white" and playerBlack ~= "") and
619 playerBlack .. "..." or playerBlack)
620 local turnWhite = minetest.colorize("#000001", (lastMove == "black" and playerWhite ~= "") and
621 playerWhite .. "..." or playerWhite)
622 local check_s = minetest.colorize("#FF0000", "\\[check\\]")
624 local formspec = fs ..
625 "label[1.9,0.3;" .. turnBlack .. (black_king_attacked and " " .. check_s or "") .. "]" ..
626 "label[1.9,9.15;" .. turnWhite .. (white_king_attacked and " " .. check_s or "") .. "]" ..
627 "table[8.9,1.05;5.07,3.75;moves;" .. moves:sub(1,-2) .. ";1]" ..
630 meta:set_string("formspec", formspec)
633 local function get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_idx, to_idx)
634 local from_x, from_y = index_to_xy(from_idx)
635 local to_x, to_y = index_to_xy(to_idx)
636 local moves = meta:get_string("moves")
637 local pieceFrom_s = pieceFrom:match(":(%w+_%w+)")
638 local pieceFrom_si_id = pieces_str:match("(%d+)=" .. pieceFrom_s)
639 local pieceTo_si_id = pieceTo_s ~= "" and pieces_str:match("(%d+)=" .. pieceTo_s) or ""
641 local coordFrom = letters[from_x + 1] .. math.abs(from_y - 8)
642 local coordTo = letters[to_x + 1] .. math.abs(to_y - 8)
644 local new_moves = pieceFrom_si_id .. "," ..
646 (pieceTo ~= "" and "#33FF33" or "#FFFFFF") .. ", > ,#FFFFFF," ..
648 (pieceTo ~= "" and pieceTo_si_id or "69") .. "," ..
651 meta:set_string("moves", new_moves)
654 local function get_eaten_list(meta, pieceTo, pieceTo_s)
655 local eaten = meta:get_string("eaten")
656 if pieceTo ~= "" then
657 eaten = eaten .. pieceTo_s .. ","
660 meta:set_string("eaten", eaten)
662 local eaten_t = string.split(eaten, ",")
666 for i = 1, #eaten_t do
667 local is_white = eaten_t[i]:sub(-5,-1) == "white"
668 local X = (is_white and a or b) % 4
669 local Y = ((is_white and a or b) % 16 - X) / 4
677 eaten_img = eaten_img ..
678 "image[" .. ((X + (is_white and 11.67 or 8.8)) - (X * 0.45)) .. "," ..
679 ((Y + 5.56) - (Y * 0.2)) .. ";1,1;" .. eaten_t[i] .. ".png]"
682 meta:set_string("eaten_img", eaten_img)
685 function realchess.init(pos)
686 local meta = minetest.get_meta(pos)
687 local inv = meta:get_inventory()
689 meta:set_string("formspec", fs)
690 meta:set_string("infotext", "Chess Board")
691 meta:set_string("playerBlack", "")
692 meta:set_string("playerWhite", "")
693 meta:set_string("lastMove", "")
694 meta:set_string("blackAttacked", "")
695 meta:set_string("whiteAttacked", "")
697 meta:set_int("lastMoveTime", 0)
698 meta:set_int("castlingBlackL", 1)
699 meta:set_int("castlingBlackR", 1)
700 meta:set_int("castlingWhiteL", 1)
701 meta:set_int("castlingWhiteR", 1)
703 meta:set_string("moves", "")
704 meta:set_string("eaten", "")
706 inv:set_list("board", pieces)
707 inv:set_size("board", 64)
710 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
711 if from_list ~= "board" and to_list ~= "board" then
715 local meta = minetest.get_meta(pos)
716 local playerName = player:get_player_name()
717 local inv = meta:get_inventory()
718 local pieceFrom = inv:get_stack(from_list, from_index):get_name()
719 local pieceTo = inv:get_stack(to_list, to_index):get_name()
720 local lastMove = meta:get_string("lastMove")
721 local playerWhite = meta:get_string("playerWhite")
722 local playerBlack = meta:get_string("playerBlack")
723 local thisMove -- Will replace lastMove when move is legal
725 if pieceFrom:find("white") then
726 if playerWhite ~= "" and playerWhite ~= playerName then
727 minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays white pieces!")
731 if lastMove ~= "" and lastMove ~= "black" then
735 if pieceTo:find("white") then
736 -- Don't replace pieces of same color
740 playerWhite = playerName
743 elseif pieceFrom:find("black") then
744 if playerBlack ~= "" and playerBlack ~= playerName then
745 minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays black pieces!")
749 if lastMove ~= "" and lastMove ~= "white" then
753 if pieceTo:find("black") then
754 -- Don't replace pieces of same color
758 playerBlack = playerName
764 local from_x, from_y = index_to_xy(from_index)
765 local to_x, to_y = index_to_xy(to_index)
768 if pieceFrom:sub(11,14) == "pawn" then
769 if thisMove == "white" then
770 local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
771 -- white pawns can go up only
772 if from_y - 1 == to_y then
773 if from_x == to_x then
774 if pieceTo ~= "" then
776 elseif to_index >= 1 and to_index <= 8 then
777 inv:set_stack(from_list, from_index, "realchess:queen_white")
779 elseif from_x - 1 == to_x or from_x + 1 == to_x then
780 if not pieceTo:find("black") then
782 elseif to_index >= 1 and to_index <= 8 then
783 inv:set_stack(from_list, from_index, "realchess:queen_white")
788 elseif from_y - 2 == to_y then
789 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
798 ensure that destination cell is empty
799 elseif x changed one unit left or right
800 ensure the pawn is killing opponent piece
802 move is not legal - abort
805 if from_x == to_x then
806 if pieceTo ~= "" then
809 elseif from_x - 1 == to_x or from_x + 1 == to_x then
810 if not pieceTo:find("black") then
817 elseif thisMove == "black" then
818 local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
819 -- black pawns can go down only
820 if from_y + 1 == to_y then
821 if from_x == to_x then
822 if pieceTo ~= "" then
824 elseif to_index >= 57 and to_index <= 64 then
825 inv:set_stack(from_list, from_index, "realchess:queen_black")
827 elseif from_x - 1 == to_x or from_x + 1 == to_x then
828 if not pieceTo:find("white") then
830 elseif to_index >= 57 and to_index <= 64 then
831 inv:set_stack(from_list, from_index, "realchess:queen_black")
836 elseif from_y + 2 == to_y then
837 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
846 ensure that destination cell is empty
847 elseif x changed one unit left or right
848 ensure the pawn is killing opponent piece
850 move is not legal - abort
853 if from_x == to_x then
854 if pieceTo ~= "" then
857 elseif from_x - 1 == to_x or from_x + 1 == to_x then
858 if not pieceTo:find("white") then
869 elseif pieceFrom:sub(11,14) == "rook" then
870 if from_x == to_x then
872 if from_y < to_y then
874 -- Ensure that no piece disturbs the way
875 for i = from_y + 1, to_y - 1 do
876 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
882 -- Ensure that no piece disturbs the way
883 for i = to_y + 1, from_y - 1 do
884 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
889 elseif from_y == to_y then
890 -- Mocing horizontally
891 if from_x < to_x then
893 -- ensure that no piece disturbs the way
894 for i = from_x + 1, to_x - 1 do
895 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
901 -- Ensure that no piece disturbs the way
902 for i = to_x + 1, from_x - 1 do
903 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
909 -- Attempt to move arbitrarily -> abort
913 if thisMove == "white" or thisMove == "black" then
914 if pieceFrom:sub(-1) == "1" then
915 meta:set_int("castlingWhiteL", 0)
916 elseif pieceFrom:sub(-1) == "2" then
917 meta:set_int("castlingWhiteR", 0)
922 elseif pieceFrom:sub(11,16) == "knight" then
924 local dx = from_x - to_x
925 local dy = from_y - to_y
927 -- Get absolute values
928 if dx < 0 then dx = -dx end
929 if dy < 0 then dy = -dy end
932 if dx > dy then dx, dy = dy, dx end
934 -- Ensure that dx == 1 and dy == 2
935 if dx ~= 1 or dy ~= 2 then
938 -- Just ensure that destination cell does not contain friend piece
939 -- ^ It was done already thus everything ok
942 elseif pieceFrom:sub(11,16) == "bishop" then
944 local dx = from_x - to_x
945 local dy = from_y - to_y
947 -- Get absolute values
948 if dx < 0 then dx = -dx end
949 if dy < 0 then dy = -dy end
951 -- Ensure dx and dy are equal
952 if dx ~= dy then return 0 end
954 if from_x < to_x then
955 if from_y < to_y then
957 -- Ensure that no piece disturbs the way
960 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
966 -- Ensure that no piece disturbs the way
969 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
975 if from_y < to_y then
977 -- Ensure that no piece disturbs the way
980 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
986 -- ensure that no piece disturbs the way
989 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
997 elseif pieceFrom:sub(11,15) == "queen" then
998 local dx = from_x - to_x
999 local dy = from_y - to_y
1001 -- Get absolute values
1002 if dx < 0 then dx = -dx end
1003 if dy < 0 then dy = -dy end
1005 -- Ensure valid relative move
1006 if dx ~= 0 and dy ~= 0 and dx ~= dy then
1010 if from_x == to_x then
1011 if from_y < to_y then
1013 -- Ensure that no piece disturbs the way
1014 for i = 1, dx - 1 do
1016 from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
1022 -- Ensure that no piece disturbs the way
1023 for i = 1, dx - 1 do
1025 from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
1030 elseif from_x < to_x then
1031 if from_y == to_y then
1033 -- Ensure that no piece disturbs the way
1034 for i = 1, dx - 1 do
1036 from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
1040 elseif from_y < to_y then
1042 -- Ensure that no piece disturbs the way
1043 for i = 1, dx - 1 do
1045 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
1051 -- Ensure that no piece disturbs the way
1052 for i = 1, dx - 1 do
1054 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
1060 if from_y == to_y then
1062 -- Ensure that no piece disturbs the way and destination cell does
1063 for i = 1, dx - 1 do
1065 from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
1069 elseif from_y < to_y then
1071 -- Ensure that no piece disturbs the way
1072 for i = 1, dx - 1 do
1074 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
1080 -- Ensure that no piece disturbs the way
1081 for i = 1, dx - 1 do
1083 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
1091 elseif pieceFrom:sub(11,14) == "king" then
1092 local dx = from_x - to_x
1093 local dy = from_y - to_y
1096 if thisMove == "white" then
1097 if from_y == 7 and to_y == 7 then
1099 local castlingWhiteL = meta:get_int("castlingWhiteL")
1100 local idx57 = inv:get_stack(from_list, 57):get_name()
1102 if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
1103 for i = 58, from_index - 1 do
1104 if inv:get_stack(from_list, i):get_name() ~= "" then
1109 inv:set_stack(from_list, 57, "")
1110 inv:set_stack(from_list, 59, "realchess:rook_white_1")
1113 elseif to_x == 6 then
1114 local castlingWhiteR = meta:get_int("castlingWhiteR")
1115 local idx64 = inv:get_stack(from_list, 64):get_name()
1117 if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
1118 for i = from_index + 1, 63 do
1119 if inv:get_stack(from_list, i):get_name() ~= "" then
1124 inv:set_stack(from_list, 62, "realchess:rook_white_2")
1125 inv:set_stack(from_list, 64, "")
1130 elseif thisMove == "black" then
1131 if from_y == 0 and to_y == 0 then
1133 local castlingBlackL = meta:get_int("castlingBlackL")
1134 local idx1 = inv:get_stack(from_list, 1):get_name()
1136 if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
1137 for i = 2, from_index - 1 do
1138 if inv:get_stack(from_list, i):get_name() ~= "" then
1143 inv:set_stack(from_list, 1, "")
1144 inv:set_stack(from_list, 3, "realchess:rook_black_1")
1147 elseif to_x == 6 then
1148 local castlingBlackR = meta:get_int("castlingBlackR")
1149 local idx8 = inv:get_stack(from_list, 1):get_name()
1151 if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
1152 for i = from_index + 1, 7 do
1153 if inv:get_stack(from_list, i):get_name() ~= "" then
1158 inv:set_stack(from_list, 6, "realchess:rook_black_2")
1159 inv:set_stack(from_list, 8, "")
1175 if dx > 1 or dy > 1 then
1180 if thisMove == "white" then
1181 meta:set_int("castlingWhiteL", 0)
1182 meta:set_int("castlingWhiteR", 0)
1184 elseif thisMove == "black" then
1185 meta:set_int("castlingBlackL", 0)
1186 meta:set_int("castlingBlackR", 0)
1190 local board = board_to_table(inv)
1191 board[to_index] = board[from_index]
1192 board[from_index] = ""
1194 local black_king_idx, white_king_idx = locate_kings(board)
1195 local blackAttacked = attacked("black", black_king_idx, board)
1196 local whiteAttacked = attacked("white", white_king_idx, board)
1198 if blackAttacked then
1199 if thisMove == "black" then
1200 --[(*)[ and meta:get_string("blackAttacked") == "true" ]] then
1203 meta:set_string("blackAttacked", "true")
1206 meta:set_string("blackAttacked", "")
1209 if whiteAttacked then
1210 if thisMove == "white" then
1211 --[(*)[ and meta:get_string("whiteAttacked") == "true" ]] then
1214 meta:set_string("whiteAttacked", "true")
1217 meta:set_string("whiteAttacked", "")
1220 --(*) Allow a piece to move and put its king in check. Maybe not in the chess rules though?
1223 meta:set_string("lastMove", lastMove)
1224 meta:set_int("lastMoveTime", minetest.get_gametime())
1226 if meta:get_string("playerWhite") == "" then
1227 meta:set_string("playerWhite", playerWhite)
1228 elseif meta:get_string("playerBlack") == "" then
1229 meta:set_string("playerBlack", playerBlack)
1232 local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
1233 get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_index, to_index)
1234 get_eaten_list(meta, pieceTo, pieceTo_s)
1236 --print("from_index: " .. from_index)
1237 --print("to_index: " .. to_index)
1242 function realchess.on_move(pos, from_list, from_index)
1243 local meta = minetest.get_meta(pos)
1244 local inv = meta:get_inventory()
1245 inv:set_stack(from_list, from_index, "")
1247 local lastMove = meta:get_string("lastMove")
1248 if lastMove == "white" then
1249 update_formspec(meta)
1253 local possibleMoves = get_possible_moves(inv, i)
1254 local stack_name = inv:get_stack("board", i):get_name()
1256 if stack_name:find("black") then
1257 moves[tostring(i)] = possibleMoves
1261 --minetest.log("warning", "moves: " .. dump(moves))
1263 local choice_from, choice_to = best_move(moves)
1264 local pieceFrom = inv:get_stack("board", choice_from):get_name()
1265 local pieceTo = inv:get_stack("board", choice_to):get_name()
1266 local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
1268 local board = board_to_table(inv)
1269 local black_king_idx = locate_kings(board)
1270 local blackAttacked = attacked("black", black_king_idx, board)
1271 local kingSafe = true
1272 local bestMoveSaveFrom, bestMoveSaveTo
1274 if blackAttacked then
1276 meta:set_string("blackAttacked", "true")
1277 local save_moves = {}
1279 for from_idx, _ in pairs(moves) do
1280 for to_idx, value in pairs(_) do
1281 from_idx = tonumber(from_idx)
1282 local from_idx_bak, to_idx_bak = board[from_idx], board[to_idx]
1283 board[to_idx] = board[from_idx]
1284 board[from_idx] = ""
1285 black_king_idx = locate_kings(board)
1287 if black_king_idx then
1288 blackAttacked = attacked("black", black_king_idx, board)
1289 if not blackAttacked then
1290 save_moves[from_idx] = save_moves[from_idx] or {}
1291 save_moves[from_idx][to_idx] = value
1295 board[from_idx], board[to_idx] = from_idx_bak, to_idx_bak
1299 if next(save_moves) then
1300 bestMoveSaveFrom, bestMoveSaveTo = best_move(save_moves)
1304 minetest.after(1.0, function()
1305 local lastMoveTime = meta:get_int("lastMoveTime")
1306 if lastMoveTime > 0 then
1307 if not kingSafe then
1308 if bestMoveSaveTo then
1309 inv:set_stack("board", bestMoveSaveTo, board[bestMoveSaveFrom])
1310 inv:set_stack("board", bestMoveSaveFrom, "")
1311 meta:set_string("blackAttacked", "")
1316 inv:set_stack("board", choice_to, pieceFrom)
1317 inv:set_stack("board", choice_from, "")
1320 board = board_to_table(inv)
1321 local _, white_king_idx = locate_kings(board)
1322 local whiteAttacked = attacked("white", white_king_idx, board)
1324 if whiteAttacked then
1325 meta:set_string("whiteAttacked", "true")
1328 if meta:get_string("playerBlack") == "" then
1329 meta:set_string("playerBlack", "Dumb AI")
1332 meta:set_string("lastMove", "black")
1333 meta:set_int("lastMoveTime", minetest.get_gametime())
1335 get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, choice_from, choice_to)
1336 get_eaten_list(meta, pieceTo, pieceTo_s)
1338 update_formspec(meta)
1342 update_formspec(meta)
1348 local function timeout_format(timeout_limit)
1349 local time_remaining = timeout_limit - minetest.get_gametime()
1350 local minutes = math.floor(time_remaining / 60)
1351 local seconds = time_remaining % 60
1353 if minutes == 0 then
1354 return seconds .. " sec."
1357 return minutes .. " min. " .. seconds .. " sec."
1360 function realchess.fields(pos, _, fields, sender)
1361 local playerName = sender:get_player_name()
1362 local meta = minetest.get_meta(pos)
1363 local timeout_limit = meta:get_int("lastMoveTime") + 300
1364 local playerWhite = meta:get_string("playerWhite")
1365 local playerBlack = meta:get_string("playerBlack")
1366 local lastMoveTime = meta:get_int("lastMoveTime")
1367 if fields.quit then return end
1369 -- Timeout is 5 min. by default for resetting the game (non-players only)
1371 if (playerWhite == playerName or playerBlack == playerName) then
1374 elseif lastMoveTime > 0 then
1375 if minetest.get_gametime() >= timeout_limit and
1376 (playerWhite ~= playerName or playerBlack ~= playerName) then
1379 minetest.chat_send_player(playerName, chat_prefix ..
1380 "You can't reset the chessboard, a game has been started. " ..
1381 "If you aren't a current player, try again in " ..
1382 timeout_format(timeout_limit))
1388 function realchess.dig(pos, player)
1393 local meta = minetest.get_meta(pos)
1394 local playerName = player:get_player_name()
1395 local timeout_limit = meta:get_int("lastMoveTime") + 300
1396 local lastMoveTime = meta:get_int("lastMoveTime")
1398 -- Timeout is 5 min. by default for digging the chessboard (non-players only)
1399 return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
1400 minetest.chat_send_player(playerName, chat_prefix ..
1401 "You can't dig the chessboard, a game has been started. " ..
1402 "Reset it first if you're a current player, or dig it again in " ..
1403 timeout_format(timeout_limit))
1406 minetest.register_node(":realchess:chessboard", {
1407 description = "Chess Board",
1408 drawtype = "nodebox",
1409 paramtype = "light",
1410 paramtype2 = "facedir",
1411 inventory_image = "chessboard_top.png",
1412 wield_image = "chessboard_top.png",
1413 tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
1414 groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
1415 sounds = default.node_sound_wood_defaults(),
1416 node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
1417 sunlight_propagates = true,
1418 on_rotate = screwdriver.rotate_simple,
1419 can_dig = realchess.dig,
1420 on_construct = realchess.init,
1421 on_receive_fields = realchess.fields,
1422 allow_metadata_inventory_move = realchess.move,
1423 on_metadata_inventory_move = realchess.on_move,
1424 allow_metadata_inventory_take = function() return 0 end
1427 local function register_piece(name, count)
1428 for _, color in pairs({"black", "white"}) do
1430 minetest.register_craftitem(":realchess:" .. name .. "_" .. color, {
1431 description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
1432 inventory_image = name .. "_" .. color .. ".png",
1434 groups = {not_in_creative_inventory=1}
1438 minetest.register_craftitem(":realchess:" .. name .. "_" .. color .. "_" .. i, {
1439 description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
1440 inventory_image = name .. "_" .. color .. ".png",
1442 groups = {not_in_creative_inventory=1}
1449 register_piece("pawn", 8)
1450 register_piece("rook", 2)
1451 register_piece("knight", 2)
1452 register_piece("bishop", 2)
1453 register_piece("queen")
1454 register_piece("king")
1458 minetest.register_craft({
1459 output = "realchess:chessboard",
1461 {"dye:black", "dye:white", "dye:black"},
1462 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}