2 screwdriver = screwdriver or {}
4 local function index_to_xy(idx)
7 local y = (idx - x) / 8
11 local function xy_to_index(x, y)
15 function realchess.init(pos)
16 local meta = minetest.get_meta(pos)
17 local inv = meta:get_inventory()
19 local formspec = [[ size[8,8.6;]
20 bgcolor[#080808BB;true]
21 background[0,0;8,8;chess_bg.png]
22 button[3.1,7.8;2,2;new;New game]
23 list[context;board;0,0;8,8;]
24 listcolors[#00000000;#00000000;#00000000;#30434C;#FFF] ]]
26 meta:set_string("formspec", formspec)
27 meta:set_string("infotext", "Chess Board")
28 meta:set_string("playerBlack", "")
29 meta:set_string("playerWhite", "")
30 meta:set_string("lastMove", "")
31 meta:set_string("winner", "")
33 meta:set_int("lastMoveTime", 0)
34 meta:set_int("castlingBlackL", 1)
35 meta:set_int("castlingBlackR", 1)
36 meta:set_int("castlingWhiteL", 1)
37 meta:set_int("castlingWhiteR", 1)
39 inv:set_list("board", {
40 "realchess:rook_black_1",
41 "realchess:knight_black_1",
42 "realchess:bishop_black_1",
43 "realchess:queen_black",
44 "realchess:king_black",
45 "realchess:bishop_black_2",
46 "realchess:knight_black_2",
47 "realchess:rook_black_2",
48 "realchess:pawn_black_1",
49 "realchess:pawn_black_2",
50 "realchess:pawn_black_3",
51 "realchess:pawn_black_4",
52 "realchess:pawn_black_5",
53 "realchess:pawn_black_6",
54 "realchess:pawn_black_7",
55 "realchess:pawn_black_8",
56 '','','','','','','','','','','','','','','','',
57 '','','','','','','','','','','','','','','','',
58 "realchess:pawn_white_1",
59 "realchess:pawn_white_2",
60 "realchess:pawn_white_3",
61 "realchess:pawn_white_4",
62 "realchess:pawn_white_5",
63 "realchess:pawn_white_6",
64 "realchess:pawn_white_7",
65 "realchess:pawn_white_8",
66 "realchess:rook_white_1",
67 "realchess:knight_white_1",
68 "realchess:bishop_white_1",
69 "realchess:queen_white",
70 "realchess:king_white",
71 "realchess:bishop_white_2",
72 "realchess:knight_white_2",
73 "realchess:rook_white_2"
76 inv:set_size("board", 64)
79 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
80 if from_list ~= "board" and to_list ~= "board" then
84 local playerName = player:get_player_name()
85 local meta = minetest.get_meta(pos)
87 if meta:get_string("winner") ~= "" then
88 minetest.chat_send_player(playerName, "This game is over.")
92 local inv = meta:get_inventory()
93 local pieceFrom = inv:get_stack(from_list, from_index):get_name()
94 local pieceTo = inv:get_stack(to_list, to_index):get_name()
95 local lastMove = meta:get_string("lastMove")
96 local thisMove -- will replace lastMove when move is legal
97 local playerWhite = meta:get_string("playerWhite")
98 local playerBlack = meta:get_string("playerBlack")
100 if pieceFrom:find("white") then
101 if playerWhite ~= "" and playerWhite ~= playerName then
102 minetest.chat_send_player(playerName, "Someone else plays white pieces!")
105 if lastMove ~= "" and lastMove ~= "black" then
106 minetest.chat_send_player(playerName, "It's not your turn, wait for your opponent to play.")
109 if pieceTo:find("white") then
110 -- Don't replace pieces of same color
113 playerWhite = playerName
115 elseif pieceFrom:find("black") then
116 if playerBlack ~= "" and playerBlack ~= playerName then
117 minetest.chat_send_player(playerName, "Someone else plays black pieces!")
120 if lastMove ~= "" and lastMove ~= "white" then
121 minetest.chat_send_player(playerName, "It's not your turn, wait for your opponent to play.")
124 if pieceTo:find("black") then
125 -- Don't replace pieces of same color
128 playerBlack = playerName
132 -- DETERMINISTIC MOVING
134 local from_x, from_y = index_to_xy(from_index)
135 local to_x, to_y = index_to_xy(to_index)
137 if pieceFrom:sub(11,14) == "pawn" then
138 if thisMove == "white" then
139 local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
140 -- white pawns can go up only
141 if from_y - 1 == to_y then
142 if from_x == to_x then
143 if pieceTo ~= "" then
145 elseif to_index >= 1 and to_index <= 8 then
146 inv:set_stack(from_list, from_index, "realchess:queen_white")
148 elseif from_x - 1 == to_x or from_x + 1 == to_x then
149 if not pieceTo:find("black") then
151 elseif to_index >= 1 and to_index <= 8 then
152 inv:set_stack(from_list, from_index, "realchess:queen_white")
157 elseif from_y - 2 == to_y then
158 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
166 -- ensure that destination cell is empty
167 -- elseif x changed one unit left or right
168 -- ensure the pawn is killing opponent piece
170 -- move is not legal - abort
172 if from_x == to_x then
173 if pieceTo ~= "" then
176 elseif from_x - 1 == to_x or from_x + 1 == to_x then
177 if not pieceTo:find("black") then
184 elseif thisMove == "black" then
185 local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
186 -- black pawns can go down only
187 if from_y + 1 == to_y then
188 if from_x == to_x then
189 if pieceTo ~= "" then
191 elseif to_index >= 57 and to_index <= 64 then
192 inv:set_stack(from_list, from_index, "realchess:queen_black")
194 elseif from_x - 1 == to_x or from_x + 1 == to_x then
195 if not pieceTo:find("white") then
197 elseif to_index >= 57 and to_index <= 64 then
198 inv:set_stack(from_list, from_index, "realchess:queen_black")
203 elseif from_y + 2 == to_y then
204 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
212 -- ensure that destination cell is empty
213 -- elseif x changed one unit left or right
214 -- ensure the pawn is killing opponent piece
216 -- move is not legal - abort
218 if from_x == to_x then
219 if pieceTo ~= "" then
222 elseif from_x - 1 == to_x or from_x + 1 == to_x then
223 if not pieceTo:find("white") then
233 elseif pieceFrom:sub(11,14) == "rook" then
234 if from_x == to_x then
236 if from_y < to_y then
238 -- ensure that no piece disturbs the way
239 for i = from_y + 1, to_y - 1 do
240 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
246 -- ensure that no piece disturbs the way
247 for i = to_y + 1, from_y - 1 do
248 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
253 elseif from_y == to_y then
254 -- mocing horizontally
255 if from_x < to_x then
257 -- ensure that no piece disturbs the way
258 for i = from_x + 1, to_x - 1 do
259 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
265 -- ensure that no piece disturbs the way
266 for i = to_x + 1, from_x - 1 do
267 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
273 -- attempt to move arbitrarily -> abort
277 if thisMove == "white" or thisMove == "black" then
278 if pieceFrom:sub(-1) == "1" then
279 meta:set_int("castlingWhiteL", 0)
280 elseif pieceFrom:sub(-1) == "2" then
281 meta:set_int("castlingWhiteR", 0)
285 elseif pieceFrom:sub(11,16) == "knight" then
287 local dx = from_x - to_x
288 local dy = from_y - to_y
290 -- get absolute values
291 if dx < 0 then dx = -dx end
292 if dy < 0 then dy = -dy end
295 if dx > dy then dx, dy = dy, dx end
297 -- ensure that dx == 1 and dy == 2
298 if dx ~= 1 or dy ~= 2 then
301 -- just ensure that destination cell does not contain friend piece
302 -- ^ it was done already thus everything ok
304 elseif pieceFrom:sub(11,16) == "bishop" then
306 local dx = from_x - to_x
307 local dy = from_y - to_y
309 -- get absolute values
310 if dx < 0 then dx = -dx end
311 if dy < 0 then dy = -dy end
313 -- ensure dx and dy are equal
314 if dx ~= dy then return 0 end
316 if from_x < to_x then
317 if from_y < to_y then
319 -- ensure that no piece disturbs the way
321 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
327 -- ensure that no piece disturbs the way
329 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
335 if from_y < to_y then
337 -- ensure that no piece disturbs the way
339 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
345 -- ensure that no piece disturbs the way
347 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
354 elseif pieceFrom:sub(11,15) == "queen" then
355 local dx = from_x - to_x
356 local dy = from_y - to_y
358 -- get absolute values
359 if dx < 0 then dx = -dx end
360 if dy < 0 then dy = -dy end
362 -- ensure valid relative move
363 if dx ~= 0 and dy ~= 0 and dx ~= dy then
367 if from_x == to_x then
368 if from_y < to_y then
370 -- ensure that no piece disturbs the way
372 if inv:get_stack(from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
378 -- ensure that no piece disturbs the way
380 if inv:get_stack(from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
385 elseif from_x < to_x then
386 if from_y == to_y then
388 -- ensure that no piece disturbs the way
390 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
394 elseif from_y < to_y then
396 -- ensure that no piece disturbs the way
398 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
404 -- ensure that no piece disturbs the way
406 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
412 if from_y == to_y then
414 -- ensure that no piece disturbs the way and destination cell does
416 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
420 elseif from_y < to_y then
422 -- ensure that no piece disturbs the way
424 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
430 -- ensure that no piece disturbs the way
432 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
439 elseif pieceFrom:sub(11,14) == "king" then
440 local dx = from_x - to_x
441 local dy = from_y - to_y
444 if thisMove == "white" then
445 if from_y == 7 and to_y == 7 then
447 local castlingWhiteL = meta:get_int("castlingWhiteL")
448 local idx57 = inv:get_stack(from_list, 57):get_name()
450 if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
451 for i = 58, from_index - 1 do
452 if inv:get_stack(from_list, i):get_name() ~= "" then
456 inv:set_stack(from_list, 57, "")
457 inv:set_stack(from_list, 59, "realchess:rook_white_1")
460 elseif to_x == 6 then
461 local castlingWhiteR = meta:get_int("castlingWhiteR")
462 local idx64 = inv:get_stack(from_list, 64):get_name()
464 if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
465 for i = from_index + 1, 63 do
466 if inv:get_stack(from_list, i):get_name() ~= "" then
470 inv:set_stack(from_list, 62, "realchess:rook_white_2")
471 inv:set_stack(from_list, 64, "")
476 elseif thisMove == "black" then
477 if from_y == 0 and to_y == 0 then
479 local castlingBlackL = meta:get_int("castlingBlackL")
480 local idx1 = inv:get_stack(from_list, 1):get_name()
482 if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
483 for i = 2, from_index - 1 do
484 if inv:get_stack(from_list, i):get_name() ~= "" then
488 inv:set_stack(from_list, 1, "")
489 inv:set_stack(from_list, 3, "realchess:rook_black_1")
492 elseif to_x == 6 then
493 local castlingBlackR = meta:get_int("castlingBlackR")
494 local idx8 = inv:get_stack(from_list, 1):get_name()
496 if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
497 for i = from_index + 1, 7 do
498 if inv:get_stack(from_list, i):get_name() ~= "" then
502 inv:set_stack(from_list, 6, "realchess:rook_black_2")
503 inv:set_stack(from_list, 8, "")
511 if dx < 0 then dx = -dx end
512 if dy < 0 then dy = -dy end
513 if dx > 1 or dy > 1 then return 0 end
516 if thisMove == "white" then
517 meta:set_int("castlingWhiteL", 0)
518 meta:set_int("castlingWhiteR", 0)
519 elseif thisMove == "black" then
520 meta:set_int("castlingBlackL", 0)
521 meta:set_int("castlingBlackR", 0)
525 meta:set_string("playerWhite", playerWhite)
526 meta:set_string("playerBlack", playerBlack)
528 meta:set_string("lastMove", lastMove)
529 meta:set_int("lastMoveTime", minetest.get_gametime())
531 if lastMove == "black" then
532 minetest.chat_send_player(playerWhite, "["..os.date("%H:%M:%S").."] "..
533 playerName.." moved a "..pieceFrom:match(":(%a+)")..", it's now your turn.")
534 elseif lastMove == "white" then
535 minetest.chat_send_player(playerBlack, "["..os.date("%H:%M:%S").."] "..
536 playerName.." moved a "..pieceFrom:match(":(%a+)")..", it's now your turn.")
539 if pieceTo:sub(11,14) == "king" then
540 minetest.chat_send_player(playerBlack, playerName.." won the game.")
541 minetest.chat_send_player(playerWhite, playerName.." won the game.")
542 meta:set_string("winner", thisMove)
548 local function timeout_format(timeout_limit)
549 local time_remaining = timeout_limit - minetest.get_gametime()
550 local minutes = math.floor(time_remaining / 60)
551 local seconds = time_remaining % 60
553 if minutes == 0 then return seconds.." sec." end
554 return minutes.." min. "..seconds.." sec."
557 function realchess.fields(pos, _, fields, sender)
558 local playerName = sender:get_player_name()
559 local meta = minetest.get_meta(pos)
560 local timeout_limit = meta:get_int("lastMoveTime") + 300
561 local playerWhite = meta:get_string("playerWhite")
562 local playerBlack = meta:get_string("playerBlack")
563 local lastMoveTime = meta:get_int("lastMoveTime")
564 if fields.quit then return end
566 -- timeout is 5 min. by default for resetting the game (non-players only)
567 if fields.new and (playerWhite == playerName or playerBlack == playerName) then
569 elseif fields.new and lastMoveTime ~= 0 and minetest.get_gametime() >= timeout_limit and
570 (playerWhite ~= playerName or playerBlack ~= playerName) then
573 minetest.chat_send_player(playerName, "[!] You can't reset the chessboard, a game has been started.\n"..
574 "If you are not a current player, try again in "..timeout_format(timeout_limit))
578 function realchess.dig(pos, player)
582 local meta = minetest.get_meta(pos)
583 local playerName = player:get_player_name()
584 local timeout_limit = meta:get_int("lastMoveTime") + 300
585 local lastMoveTime = meta:get_int("lastMoveTime")
587 -- timeout is 5 min. by default for digging the chessboard (non-players only)
588 return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
589 minetest.chat_send_player(playerName, "[!] You can't dig the chessboard, a game has been started.\n"..
590 "Reset it first if you're a current player, or dig again in "..timeout_format(timeout_limit))
593 function realchess.on_move(pos, from_list, from_index)
594 local inv = minetest.get_meta(pos):get_inventory()
595 inv:set_stack(from_list, from_index, '')
599 minetest.register_node(":realchess:chessboard", {
600 description = "Chess Board",
601 drawtype = "nodebox",
603 paramtype2 = "facedir",
604 inventory_image = "chessboard_top.png",
605 wield_image = "chessboard_top.png",
606 tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
607 groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
608 sounds = default.node_sound_wood_defaults(),
609 node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
610 sunlight_propagates = true,
611 on_rotate = screwdriver.rotate_simple,
612 can_dig = realchess.dig,
613 on_construct = realchess.init,
614 on_receive_fields = realchess.fields,
615 allow_metadata_inventory_move = realchess.move,
616 on_metadata_inventory_move = realchess.on_move,
617 allow_metadata_inventory_take = function() return 0 end
620 local function register_piece(name, count)
621 for _, color in pairs({"black", "white"}) do
623 minetest.register_craftitem(":realchess:"..name.."_"..color, {
624 description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
625 inventory_image = name.."_"..color..".png",
627 groups = {not_in_creative_inventory=1}
631 minetest.register_craftitem(":realchess:"..name.."_"..color.."_"..i, {
632 description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
633 inventory_image = name.."_"..color..".png",
635 groups = {not_in_creative_inventory=1}
642 register_piece("pawn", 8)
643 register_piece("rook", 2)
644 register_piece("knight", 2)
645 register_piece("bishop", 2)
646 register_piece("queen")
647 register_piece("king")
651 minetest.register_craft({
652 output = "realchess:chessboard",
654 {"dye:black", "dye:white", "dye:black"},
655 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}