2 screwdriver = screwdriver or {}
4 local function index_to_xy(idx)
12 local y = (idx - x) / 8
17 local function xy_to_index(x, y)
21 local function get_square(a, b)
22 return (a * 8) - (8 - b)
25 local chat_prefix = minetest.colorize("#FFFF00", "[Chess] ")
26 local letters = {'A','B','C','D','E','F','G','H'}
28 local function board_to_table(inv)
31 t[#t + 1] = inv:get_stack("board", i):get_name()
37 local piece_values = {
46 local function get_possible_moves(board, from_idx)
47 local piece, color = board[from_idx]:match(":(%w+)_(%w+)")
48 if not piece then return end
50 local from_x, from_y = index_to_xy(from_idx)
53 local stack_name = board[i]
54 if stack_name:find((color == "black" and "white" or "black")) or
60 for to_idx in pairs(moves) do
61 local pieceTo = board[to_idx]
62 local to_x, to_y = index_to_xy(to_idx)
65 if piece == "pawn" then
66 if color == "white" then
67 local pawnWhiteMove = board[xy_to_index(from_x, from_y - 1)]
68 -- white pawns can go up only
69 if from_y - 1 == to_y then
70 if from_x == to_x then
74 elseif from_x - 1 == to_x or from_x + 1 == to_x then
75 if not pieceTo:find("black") then
81 elseif from_y - 2 == to_y then
82 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
91 ensure that destination cell is empty
92 elseif x changed one unit left or right
93 ensure the pawn is killing opponent piece
95 move is not legal - abort
98 if from_x == to_x then
102 elseif from_x - 1 == to_x or from_x + 1 == to_x then
103 if not pieceTo:find("black") then
110 elseif color == "black" then
111 local pawnBlackMove = board[xy_to_index(from_x, from_y + 1)]
112 -- black pawns can go down only
113 if from_y + 1 == to_y then
114 if from_x == to_x then
115 if pieceTo ~= "" then
118 elseif from_x - 1 == to_x or from_x + 1 == to_x then
119 if not pieceTo:find("white") then
125 elseif from_y + 2 == to_y then
126 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
135 ensure that destination cell is empty
136 elseif x changed one unit left or right
137 ensure the pawn is killing opponent piece
139 move is not legal - abort
142 if from_x == to_x then
143 if pieceTo ~= "" then
146 elseif from_x - 1 == to_x or from_x + 1 == to_x then
147 if not pieceTo:find("white") then
158 elseif piece == "rook" then
159 if from_x == to_x then
161 if from_y < to_y then
163 -- Ensure that no piece disturbs the way
164 for i = from_y + 1, to_y - 1 do
165 if board[xy_to_index(from_x, i)] ~= "" then
171 -- Ensure that no piece disturbs the way
172 for i = to_y + 1, from_y - 1 do
173 if board[xy_to_index(from_x, i)] ~= "" then
178 elseif from_y == to_y then
179 -- Mocing horizontally
180 if from_x < to_x then
182 -- ensure that no piece disturbs the way
183 for i = from_x + 1, to_x - 1 do
184 if board[xy_to_index(i, from_y)] ~= "" then
190 -- Ensure that no piece disturbs the way
191 for i = to_x + 1, from_x - 1 do
192 if board[xy_to_index(i, from_y)] ~= "" then
198 -- Attempt to move arbitrarily -> abort
203 elseif piece == "knight" then
205 local dx = from_x - to_x
206 local dy = from_y - to_y
208 -- Get absolute values
222 -- Ensure that dx == 1 and dy == 2
223 if dx ~= 1 or dy ~= 2 then
226 -- Just ensure that destination cell does not contain friend piece
227 -- ^ It was done already thus everything ok
230 elseif piece == "bishop" then
232 local dx = from_x - to_x
233 local dy = from_y - to_y
235 -- Get absolute values
244 -- Ensure dx and dy are equal
249 if from_x < to_x then
250 if from_y < to_y then
252 -- Ensure that no piece disturbs the way
254 if board[xy_to_index(from_x + i, from_y + i)] ~= "" then
260 -- Ensure that no piece disturbs the way
262 if board[xy_to_index(from_x + i, from_y - i)] ~= "" then
268 if from_y < to_y then
270 -- Ensure that no piece disturbs the way
272 if board[xy_to_index(from_x - i, from_y + i)] ~= "" then
278 -- ensure that no piece disturbs the way
280 if board[xy_to_index(from_x - i, from_y - i)] ~= "" then
288 elseif piece == "queen" then
289 local dx = from_x - to_x
290 local dy = from_y - to_y
292 -- Get absolute values
301 -- Ensure valid relative move
302 if dx ~= 0 and dy ~= 0 and dx ~= dy then
306 if from_x == to_x then
308 if from_y < to_y then
310 -- Ensure that no piece disturbs the way
311 for i = from_y + 1, to_y - 1 do
312 if board[xy_to_index(from_x, i)] ~= "" then
318 -- Ensure that no piece disturbs the way
319 for i = to_y + 1, from_y - 1 do
320 if board[xy_to_index(from_x, i)] ~= "" then
325 elseif from_x < to_x then
326 if from_y == to_y then
328 -- Ensure that no piece disturbs the way
330 if board[xy_to_index(from_x + i, from_y)] ~= "" then
334 elseif from_y < to_y then
336 -- Ensure that no piece disturbs the way
338 if board[xy_to_index(from_x + i, from_y + i)] ~= "" then
344 -- Ensure that no piece disturbs the way
346 if board[xy_to_index(from_x + i, from_y - i)] ~= "" then
352 if from_y == to_y then
353 -- Mocing horizontally
354 if from_x < to_x then
356 -- ensure that no piece disturbs the way
357 for i = from_x + 1, to_x - 1 do
358 if board[xy_to_index(i, from_y)] ~= "" then
364 -- Ensure that no piece disturbs the way
365 for i = to_x + 1, from_x - 1 do
366 if board[xy_to_index(i, from_y)] ~= "" then
371 elseif from_y < to_y then
373 -- Ensure that no piece disturbs the way
375 if board[xy_to_index(from_x - i, from_y + i)] ~= "" then
381 -- Ensure that no piece disturbs the way
383 if board[xy_to_index(from_x - i, from_y - i)] ~= "" then
391 elseif piece == "king" then
392 local dx = from_x - to_x
393 local dy = from_y - to_y
403 if dx > 1 or dy > 1 then
409 if not next(moves) then return end
411 for i in pairs(moves) do
412 local stack_name = board[tonumber(i)]
413 if stack_name ~= "" then
414 for p, value in pairs(piece_values) do
415 if stack_name:find(p) then
425 local function best_move(moves)
426 local value, choices = 0, {}
428 for from, _ in pairs(moves) do
429 for to, val in pairs(_) do
436 elseif val == value then
437 choices[#choices + 1] = {
445 local random = math.random(1, #choices)
446 local choice_from, choice_to = choices[random].from, choices[random].to
448 return tonumber(choice_from), choice_to
451 local rowDirs = {-1, -1, -1, 0, 0, 1, 1, 1}
452 local colDirs = {-1, 0, 1, -1, 1, -1, 0, 1}
454 local rowDirsKnight = { 2, 1, 2, 1, -2, -1, -2, -1}
455 local colDirsKnight = {-1, -2, 1, 2, 1, 2, -1, -2}
457 local bishopThreats = {true, false, true, false, false, true, false, true}
458 local rookThreats = {false, true, false, true, true, false, true, false}
459 local queenThreats = {true, true, true, true, true, true, true, true}
460 local kingThreats = {true, true, true, true, true, true, true, true}
462 local function attacked(color, idx, board)
463 local threatDetected = false
464 local kill = color == "white"
465 local pawnThreats = {kill, false, kill, false, false, not kill, false, not kill}
468 if not threatDetected then
469 local col, row = index_to_xy(idx)
470 col, row = col + 1, row + 1
473 row = row + rowDirs[dir]
474 col = col + colDirs[dir]
476 if row >= 1 and row <= 8 and col >= 1 and col <= 8 then
477 local square = get_square(row, col)
478 local square_name = board[square]
479 local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
482 if pieceColor ~= color then
483 if piece == "bishop" and bishopThreats[dir] then
484 threatDetected = true
485 elseif piece == "rook" and rookThreats[dir] then
486 threatDetected = true
487 elseif piece == "queen" and queenThreats[dir] then
488 threatDetected = true
491 if piece == "pawn" and pawnThreats[dir] then
492 threatDetected = true
494 if piece == "king" and kingThreats[dir] then
495 threatDetected = true
505 local colK, rowK = index_to_xy(idx)
506 colK, rowK = colK + 1, rowK + 1
507 rowK = rowK + rowDirsKnight[dir]
508 colK = colK + colDirsKnight[dir]
510 if rowK >= 1 and rowK <= 8 and colK >= 1 and colK <= 8 then
511 local square = get_square(rowK, colK)
512 local square_name = board[square]
513 local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
515 if piece and pieceColor ~= color and piece == "knight" then
516 threatDetected = true
522 return threatDetected
525 local function locate_kings(board)
528 local piece, color = board[i]:match(":(%w+)_(%w+)")
529 if piece == "king" then
530 if color == "black" then
542 "realchess:rook_black_1",
543 "realchess:knight_black_1",
544 "realchess:bishop_black_1",
545 "realchess:queen_black",
546 "realchess:king_black",
547 "realchess:bishop_black_2",
548 "realchess:knight_black_2",
549 "realchess:rook_black_2",
550 "realchess:pawn_black_1",
551 "realchess:pawn_black_2",
552 "realchess:pawn_black_3",
553 "realchess:pawn_black_4",
554 "realchess:pawn_black_5",
555 "realchess:pawn_black_6",
556 "realchess:pawn_black_7",
557 "realchess:pawn_black_8",
558 '','','','','','','','','','','','','','','','',
559 '','','','','','','','','','','','','','','','',
560 "realchess:pawn_white_1",
561 "realchess:pawn_white_2",
562 "realchess:pawn_white_3",
563 "realchess:pawn_white_4",
564 "realchess:pawn_white_5",
565 "realchess:pawn_white_6",
566 "realchess:pawn_white_7",
567 "realchess:pawn_white_8",
568 "realchess:rook_white_1",
569 "realchess:knight_white_1",
570 "realchess:bishop_white_1",
571 "realchess:queen_white",
572 "realchess:king_white",
573 "realchess:bishop_white_2",
574 "realchess:knight_white_2",
575 "realchess:rook_white_2"
578 local pieces_str, x = "", 0
579 for i = 1, #pieces do
580 local p = pieces[i]:match(":(%w+_%w+)")
581 if pieces[i]:find(":(%w+)_(%w+)") and not pieces_str:find(p) then
582 pieces_str = pieces_str .. x .. "=" .. p .. ".png,"
586 pieces_str = pieces_str .. "69=mailbox_blank16.png"
591 label[0,0;Select a mode:]
592 button[0,0.5;2,1;single;Singleplayer]
593 button[2,0.5;2,1;multi;Multiplayer]
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_init)
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", "")
705 meta:set_string("mode", "")
707 inv:set_list("board", pieces)
708 inv:set_size("board", 64)
711 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
712 if from_list ~= "board" and to_list ~= "board" then
716 local meta = minetest.get_meta(pos)
717 local playerName = player:get_player_name()
718 local inv = meta:get_inventory()
719 local pieceFrom = inv:get_stack(from_list, from_index):get_name()
720 local pieceTo = inv:get_stack(to_list, to_index):get_name()
721 local lastMove = meta:get_string("lastMove")
722 local playerWhite = meta:get_string("playerWhite")
723 local playerBlack = meta:get_string("playerBlack")
724 local thisMove -- Will replace lastMove when move is legal
726 if pieceFrom:find("white") then
727 if playerWhite ~= "" and playerWhite ~= playerName then
728 minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays white pieces!")
732 if lastMove ~= "" and lastMove ~= "black" then
736 if pieceTo:find("white") then
737 -- Don't replace pieces of same color
741 playerWhite = playerName
744 elseif pieceFrom:find("black") then
745 if playerBlack ~= "" and playerBlack ~= playerName then
746 minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays black pieces!")
750 if lastMove ~= "" and lastMove ~= "white" then
754 if pieceTo:find("black") then
755 -- Don't replace pieces of same color
759 playerBlack = playerName
765 local from_x, from_y = index_to_xy(from_index)
766 local to_x, to_y = index_to_xy(to_index)
769 if pieceFrom:sub(11,14) == "pawn" then
770 if thisMove == "white" then
771 local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
772 -- white pawns can go up only
773 if from_y - 1 == to_y then
774 if from_x == to_x then
775 if pieceTo ~= "" then
777 elseif to_index >= 1 and to_index <= 8 then
778 inv:set_stack(from_list, from_index, "realchess:queen_white")
780 elseif from_x - 1 == to_x or from_x + 1 == to_x then
781 if not pieceTo:find("black") then
783 elseif to_index >= 1 and to_index <= 8 then
784 inv:set_stack(from_list, from_index, "realchess:queen_white")
789 elseif from_y - 2 == to_y then
790 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
799 ensure that destination cell is empty
800 elseif x changed one unit left or right
801 ensure the pawn is killing opponent piece
803 move is not legal - abort
806 if from_x == to_x then
807 if pieceTo ~= "" then
810 elseif from_x - 1 == to_x or from_x + 1 == to_x then
811 if not pieceTo:find("black") then
818 elseif thisMove == "black" then
819 local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
820 -- black pawns can go down only
821 if from_y + 1 == to_y then
822 if from_x == to_x then
823 if pieceTo ~= "" then
825 elseif to_index >= 57 and to_index <= 64 then
826 inv:set_stack(from_list, from_index, "realchess:queen_black")
828 elseif from_x - 1 == to_x or from_x + 1 == to_x then
829 if not pieceTo:find("white") then
831 elseif to_index >= 57 and to_index <= 64 then
832 inv:set_stack(from_list, from_index, "realchess:queen_black")
837 elseif from_y + 2 == to_y then
838 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
847 ensure that destination cell is empty
848 elseif x changed one unit left or right
849 ensure the pawn is killing opponent piece
851 move is not legal - abort
854 if from_x == to_x then
855 if pieceTo ~= "" then
858 elseif from_x - 1 == to_x or from_x + 1 == to_x then
859 if not pieceTo:find("white") then
870 elseif pieceFrom:sub(11,14) == "rook" then
871 if from_x == to_x then
873 if from_y < to_y then
875 -- Ensure that no piece disturbs the way
876 for i = from_y + 1, to_y - 1 do
877 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
883 -- Ensure that no piece disturbs the way
884 for i = to_y + 1, from_y - 1 do
885 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
890 elseif from_y == to_y then
891 -- Mocing horizontally
892 if from_x < to_x then
894 -- ensure that no piece disturbs the way
895 for i = from_x + 1, to_x - 1 do
896 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
902 -- Ensure that no piece disturbs the way
903 for i = to_x + 1, from_x - 1 do
904 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
910 -- Attempt to move arbitrarily -> abort
914 if thisMove == "white" or thisMove == "black" then
915 if pieceFrom:sub(-1) == "1" then
916 meta:set_int("castlingWhiteL", 0)
917 elseif pieceFrom:sub(-1) == "2" then
918 meta:set_int("castlingWhiteR", 0)
923 elseif pieceFrom:sub(11,16) == "knight" then
925 local dx = from_x - to_x
926 local dy = from_y - to_y
928 -- Get absolute values
929 if dx < 0 then dx = -dx end
930 if dy < 0 then dy = -dy end
933 if dx > dy then dx, dy = dy, dx end
935 -- Ensure that dx == 1 and dy == 2
936 if dx ~= 1 or dy ~= 2 then
939 -- Just ensure that destination cell does not contain friend piece
940 -- ^ It was done already thus everything ok
943 elseif pieceFrom:sub(11,16) == "bishop" then
945 local dx = from_x - to_x
946 local dy = from_y - to_y
948 -- Get absolute values
949 if dx < 0 then dx = -dx end
950 if dy < 0 then dy = -dy end
952 -- Ensure dx and dy are equal
953 if dx ~= dy then return 0 end
955 if from_x < to_x then
956 if from_y < to_y then
958 -- Ensure that no piece disturbs the way
961 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
967 -- Ensure that no piece disturbs the way
970 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
976 if from_y < to_y then
978 -- Ensure that no piece disturbs the way
981 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
987 -- ensure that no piece disturbs the way
990 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
998 elseif pieceFrom:sub(11,15) == "queen" then
999 local dx = from_x - to_x
1000 local dy = from_y - to_y
1002 -- Get absolute values
1003 if dx < 0 then dx = -dx end
1004 if dy < 0 then dy = -dy end
1006 -- Ensure valid relative move
1007 if dx ~= 0 and dy ~= 0 and dx ~= dy then
1011 if from_x == to_x then
1012 if from_y < to_y then
1014 -- Ensure that no piece disturbs the way
1015 for i = 1, dx - 1 do
1017 from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
1023 -- Ensure that no piece disturbs the way
1024 for i = 1, dx - 1 do
1026 from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
1031 elseif from_x < to_x then
1032 if from_y == to_y then
1034 -- Ensure that no piece disturbs the way
1035 for i = 1, dx - 1 do
1037 from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
1041 elseif from_y < to_y then
1043 -- Ensure that no piece disturbs the way
1044 for i = 1, dx - 1 do
1046 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
1052 -- Ensure that no piece disturbs the way
1053 for i = 1, dx - 1 do
1055 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
1061 if from_y == to_y then
1063 -- Ensure that no piece disturbs the way and destination cell does
1064 for i = 1, dx - 1 do
1066 from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
1070 elseif from_y < to_y then
1072 -- Ensure that no piece disturbs the way
1073 for i = 1, dx - 1 do
1075 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
1081 -- Ensure that no piece disturbs the way
1082 for i = 1, dx - 1 do
1084 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
1092 elseif pieceFrom:sub(11,14) == "king" then
1093 local dx = from_x - to_x
1094 local dy = from_y - to_y
1097 if thisMove == "white" then
1098 if from_y == 7 and to_y == 7 then
1100 local castlingWhiteL = meta:get_int("castlingWhiteL")
1101 local idx57 = inv:get_stack(from_list, 57):get_name()
1103 if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
1104 for i = 58, from_index - 1 do
1105 if inv:get_stack(from_list, i):get_name() ~= "" then
1110 inv:set_stack(from_list, 57, "")
1111 inv:set_stack(from_list, 59, "realchess:rook_white_1")
1114 elseif to_x == 6 then
1115 local castlingWhiteR = meta:get_int("castlingWhiteR")
1116 local idx64 = inv:get_stack(from_list, 64):get_name()
1118 if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
1119 for i = from_index + 1, 63 do
1120 if inv:get_stack(from_list, i):get_name() ~= "" then
1125 inv:set_stack(from_list, 62, "realchess:rook_white_2")
1126 inv:set_stack(from_list, 64, "")
1131 elseif thisMove == "black" then
1132 if from_y == 0 and to_y == 0 then
1134 local castlingBlackL = meta:get_int("castlingBlackL")
1135 local idx1 = inv:get_stack(from_list, 1):get_name()
1137 if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
1138 for i = 2, from_index - 1 do
1139 if inv:get_stack(from_list, i):get_name() ~= "" then
1144 inv:set_stack(from_list, 1, "")
1145 inv:set_stack(from_list, 3, "realchess:rook_black_1")
1148 elseif to_x == 6 then
1149 local castlingBlackR = meta:get_int("castlingBlackR")
1150 local idx8 = inv:get_stack(from_list, 8):get_name()
1152 if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
1153 for i = from_index + 1, 7 do
1154 if inv:get_stack(from_list, i):get_name() ~= "" then
1159 inv:set_stack(from_list, 6, "realchess:rook_black_2")
1160 inv:set_stack(from_list, 8, "")
1176 if dx > 1 or dy > 1 then
1181 if thisMove == "white" then
1182 meta:set_int("castlingWhiteL", 0)
1183 meta:set_int("castlingWhiteR", 0)
1185 elseif thisMove == "black" then
1186 meta:set_int("castlingBlackL", 0)
1187 meta:set_int("castlingBlackR", 0)
1191 local board = board_to_table(inv)
1192 board[to_index] = board[from_index]
1193 board[from_index] = ""
1195 local black_king_idx, white_king_idx = locate_kings(board)
1196 if not black_king_idx or not white_king_idx then
1199 local blackAttacked = attacked("black", black_king_idx, board)
1200 local whiteAttacked = attacked("white", white_king_idx, board)
1202 if blackAttacked then
1203 if thisMove == "black" then
1204 --[(*)[ and meta:get_string("blackAttacked") == "true" ]] then
1207 meta:set_string("blackAttacked", "true")
1210 meta:set_string("blackAttacked", "")
1213 if whiteAttacked then
1214 if thisMove == "white" then
1215 --[(*)[ and meta:get_string("whiteAttacked") == "true" ]] then
1218 meta:set_string("whiteAttacked", "true")
1221 meta:set_string("whiteAttacked", "")
1224 --(*) Allow a piece to move and put its king in check. Maybe not in the chess rules though?
1227 meta:set_string("lastMove", lastMove)
1228 meta:set_int("lastMoveTime", minetest.get_gametime())
1230 if meta:get_string("playerWhite") == "" then
1231 meta:set_string("playerWhite", playerWhite)
1232 elseif meta:get_string("playerBlack") == "" then
1233 meta:set_string("playerBlack", playerBlack)
1236 local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
1237 get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_index, to_index)
1238 get_eaten_list(meta, pieceTo, pieceTo_s)
1243 local function ai_move(inv, meta)
1244 local board_t = board_to_table(inv)
1245 local lastMove = meta:get_string("lastMove")
1247 if lastMove == "white" then
1248 update_formspec(meta)
1252 local possibleMoves = get_possible_moves(board_t, i)
1253 local stack_name = inv:get_stack("board", i):get_name()
1255 if stack_name:find("black") then
1256 moves[tostring(i)] = possibleMoves
1260 local choice_from, choice_to = best_move(moves)
1262 local pieceFrom = inv:get_stack("board", choice_from):get_name()
1263 local pieceTo = inv:get_stack("board", choice_to):get_name()
1264 local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
1266 local board = board_to_table(inv)
1267 local black_king_idx = locate_kings(board)
1268 local blackAttacked = attacked("black", black_king_idx, board)
1269 local kingSafe = true
1270 local bestMoveSaveFrom, bestMoveSaveTo
1272 if blackAttacked then
1274 meta:set_string("blackAttacked", "true")
1275 local save_moves = {}
1277 for from_idx, _ in pairs(moves) do
1278 for to_idx, value in pairs(_) do
1279 from_idx = tonumber(from_idx)
1280 local from_idx_bak, to_idx_bak = board[from_idx], board[to_idx]
1281 board[to_idx] = board[from_idx]
1282 board[from_idx] = ""
1283 black_king_idx = locate_kings(board)
1285 if black_king_idx then
1286 blackAttacked = attacked("black", black_king_idx, board)
1287 if not blackAttacked then
1288 save_moves[from_idx] = save_moves[from_idx] or {}
1289 save_moves[from_idx][to_idx] = value
1293 board[from_idx], board[to_idx] = from_idx_bak, to_idx_bak
1297 if next(save_moves) then
1298 bestMoveSaveFrom, bestMoveSaveTo = best_move(save_moves)
1302 minetest.after(1.0, function()
1303 local lastMoveTime = meta:get_int("lastMoveTime")
1304 if lastMoveTime > 0 then
1305 if not kingSafe then
1306 if bestMoveSaveTo then
1307 inv:set_stack("board", bestMoveSaveTo, board[bestMoveSaveFrom])
1308 inv:set_stack("board", bestMoveSaveFrom, "")
1309 meta:set_string("blackAttacked", "")
1314 if pieceFrom:find("pawn") and choice_to >= 57 and choice_to <= 64 then
1315 inv:set_stack("board", choice_to, "realchess:queen_black")
1317 inv:set_stack("board", choice_to, pieceFrom)
1320 inv:set_stack("board", choice_from, "")
1323 board = board_to_table(inv)
1324 local _, white_king_idx = locate_kings(board)
1325 local whiteAttacked = attacked("white", white_king_idx, board)
1327 if whiteAttacked then
1328 meta:set_string("whiteAttacked", "true")
1331 if meta:get_string("playerBlack") == "" then
1332 meta:set_string("playerBlack", "Dumb AI")
1335 meta:set_string("lastMove", "black")
1336 meta:set_int("lastMoveTime", minetest.get_gametime())
1338 get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, choice_from, choice_to)
1339 get_eaten_list(meta, pieceTo, pieceTo_s)
1341 update_formspec(meta)
1345 update_formspec(meta)
1349 function realchess.on_move(pos, from_list, from_index)
1350 local meta = minetest.get_meta(pos)
1351 local inv = meta:get_inventory()
1352 inv:set_stack(from_list, from_index, "")
1354 if meta:get_string("mode") == "single" then
1361 local function timeout_format(timeout_limit)
1362 local time_remaining = timeout_limit - minetest.get_gametime()
1363 local minutes = math.floor(time_remaining / 60)
1364 local seconds = time_remaining % 60
1366 if minutes == 0 then
1367 return seconds .. " sec."
1370 return minutes .. " min. " .. seconds .. " sec."
1373 function realchess.fields(pos, _, fields, sender)
1374 local playerName = sender:get_player_name()
1375 local meta = minetest.get_meta(pos)
1376 local timeout_limit = meta:get_int("lastMoveTime") + 300
1377 local playerWhite = meta:get_string("playerWhite")
1378 local playerBlack = meta:get_string("playerBlack")
1379 local lastMoveTime = meta:get_int("lastMoveTime")
1380 if fields.quit then return end
1382 if fields.single or fields.multi then
1383 meta:set_string("mode", (fields.single and "single" or "multi"))
1384 update_formspec(meta)
1388 -- Timeout is 5 min. by default for resetting the game (non-players only)
1390 if (playerWhite == playerName or playerBlack == playerName) then
1393 elseif lastMoveTime > 0 then
1394 if minetest.get_gametime() >= timeout_limit and
1395 (playerWhite ~= playerName or playerBlack ~= playerName) then
1398 minetest.chat_send_player(playerName, chat_prefix ..
1399 "You can't reset the chessboard, a game has been started. " ..
1400 "If you aren't a current player, try again in " ..
1401 timeout_format(timeout_limit))
1407 function realchess.dig(pos, player)
1412 local meta = minetest.get_meta(pos)
1413 local playerName = player:get_player_name()
1414 local timeout_limit = meta:get_int("lastMoveTime") + 300
1415 local lastMoveTime = meta:get_int("lastMoveTime")
1417 -- Timeout is 5 min. by default for digging the chessboard (non-players only)
1418 return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
1419 minetest.chat_send_player(playerName, chat_prefix ..
1420 "You can't dig the chessboard, a game has been started. " ..
1421 "Reset it first if you're a current player, or dig it again in " ..
1422 timeout_format(timeout_limit))
1425 minetest.register_node(":realchess:chessboard", {
1426 description = "Chess Board",
1427 drawtype = "nodebox",
1428 paramtype = "light",
1429 paramtype2 = "facedir",
1430 inventory_image = "chessboard_top.png",
1431 wield_image = "chessboard_top.png",
1432 tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
1433 groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
1434 sounds = default.node_sound_wood_defaults(),
1435 node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
1436 sunlight_propagates = true,
1437 on_rotate = screwdriver.rotate_simple,
1438 can_dig = realchess.dig,
1439 on_construct = realchess.init,
1440 on_receive_fields = realchess.fields,
1441 allow_metadata_inventory_move = realchess.move,
1442 on_metadata_inventory_move = realchess.on_move,
1443 allow_metadata_inventory_take = function() return 0 end
1446 local function register_piece(name, count)
1447 for _, color in pairs({"black", "white"}) do
1449 minetest.register_craftitem(":realchess:" .. name .. "_" .. color, {
1450 description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
1451 inventory_image = name .. "_" .. color .. ".png",
1453 groups = {not_in_creative_inventory=1}
1457 minetest.register_craftitem(":realchess:" .. name .. "_" .. color .. "_" .. i, {
1458 description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
1459 inventory_image = name .. "_" .. color .. ".png",
1461 groups = {not_in_creative_inventory=1}
1468 register_piece("pawn", 8)
1469 register_piece("rook", 2)
1470 register_piece("knight", 2)
1471 register_piece("bishop", 2)
1472 register_piece("queen")
1473 register_piece("king")
1477 minetest.register_craft({
1478 output = "realchess:chessboard",
1480 {"dye:black", "dye:white", "dye:black"},
1481 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}