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
164 elseif thisMove == "black" then
165 local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
166 -- black pawns can go down only
167 if from_y + 1 == to_y then
168 if from_x == to_x then
169 if pieceTo ~= "" then
171 elseif to_index >= 57 and to_index <= 64 then
172 inv:set_stack(from_list, from_index, "realchess:queen_black")
174 elseif from_x - 1 == to_x or from_x + 1 == to_x then
175 if not pieceTo:find("white") then
177 elseif to_index >= 57 and to_index <= 64 then
178 inv:set_stack(from_list, from_index, "realchess:queen_black")
183 elseif from_y + 2 == to_y then
184 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
192 -- ensure that destination cell is empty
193 -- elseif x changed one unit left or right
194 -- ensure the pawn is killing opponent piece
196 -- move is not legal - abort
198 if from_x == to_x then
199 if pieceTo ~= "" then
202 elseif from_x - 1 == to_x or from_x + 1 == to_x then
203 if not pieceTo:find("white") then
213 elseif pieceFrom:sub(11,14) == "rook" then
214 if from_x == to_x then
216 if from_y < to_y then
218 -- ensure that no piece disturbs the way
219 for i = from_y + 1, to_y - 1 do
220 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
226 -- ensure that no piece disturbs the way
227 for i = to_y + 1, from_y - 1 do
228 if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
233 elseif from_y == to_y then
234 -- mocing horizontally
235 if from_x < to_x then
237 -- ensure that no piece disturbs the way
238 for i = from_x + 1, to_x - 1 do
239 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
245 -- ensure that no piece disturbs the way
246 for i = to_x + 1, from_x - 1 do
247 if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
253 -- attempt to move arbitrarily -> abort
257 if thisMove == "white" or thisMove == "black" then
258 if pieceFrom:sub(-1) == "1" then
259 meta:set_int("castlingWhiteL", 0)
260 elseif pieceFrom:sub(-1) == "2" then
261 meta:set_int("castlingWhiteR", 0)
265 elseif pieceFrom:sub(11,16) == "knight" then
267 local dx = from_x - to_x
268 local dy = from_y - to_y
270 -- get absolute values
271 if dx < 0 then dx = -dx end
272 if dy < 0 then dy = -dy end
275 if dx > dy then dx, dy = dy, dx end
277 -- ensure that dx == 1 and dy == 2
278 if dx ~= 1 or dy ~= 2 then
281 -- just ensure that destination cell does not contain friend piece
282 -- ^ it was done already thus everything ok
284 elseif pieceFrom:sub(11,16) == "bishop" then
286 local dx = from_x - to_x
287 local dy = from_y - to_y
289 -- get absolute values
290 if dx < 0 then dx = -dx end
291 if dy < 0 then dy = -dy end
293 -- ensure dx and dy are equal
294 if dx ~= dy then return 0 end
296 if from_x < to_x then
297 if from_y < to_y then
299 -- ensure that no piece disturbs the way
301 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
307 -- ensure that no piece disturbs the way
309 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
315 if from_y < to_y then
317 -- ensure that no piece disturbs the way
319 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
325 -- ensure that no piece disturbs the way
327 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
334 elseif pieceFrom:sub(11,15) == "queen" then
335 local dx = from_x - to_x
336 local dy = from_y - to_y
338 -- get absolute values
339 if dx < 0 then dx = -dx end
340 if dy < 0 then dy = -dy end
342 -- ensure valid relative move
343 if dx ~= 0 and dy ~= 0 and dx ~= dy then
347 if from_x == to_x then
348 if from_y < to_y then
350 -- ensure that no piece disturbs the way
352 if inv:get_stack(from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
358 -- ensure that no piece disturbs the way
360 if inv:get_stack(from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
365 elseif from_x < to_x then
366 if from_y == to_y then
368 -- ensure that no piece disturbs the way
370 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
374 elseif from_y < to_y then
376 -- ensure that no piece disturbs the way
378 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
384 -- ensure that no piece disturbs the way
386 if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
392 if from_y == to_y then
394 -- ensure that no piece disturbs the way and destination cell does
396 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
400 elseif from_y < to_y then
402 -- ensure that no piece disturbs the way
404 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
410 -- ensure that no piece disturbs the way
412 if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
419 elseif pieceFrom:sub(11,14) == "king" then
420 local dx = from_x - to_x
421 local dy = from_y - to_y
424 if thisMove == "white" then
425 if from_y == 7 and to_y == 7 then
427 local castlingWhiteL = meta:get_int("castlingWhiteL")
428 local idx57 = inv:get_stack(from_list, 57):get_name()
430 if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
431 for i = 58, from_index - 1 do
432 if inv:get_stack(from_list, i):get_name() ~= "" then
436 inv:set_stack(from_list, 57, "")
437 inv:set_stack(from_list, 59, "realchess:rook_white_1")
440 elseif to_x == 6 then
441 local castlingWhiteR = meta:get_int("castlingWhiteR")
442 local idx64 = inv:get_stack(from_list, 64):get_name()
444 if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
445 for i = from_index + 1, 63 do
446 if inv:get_stack(from_list, i):get_name() ~= "" then
450 inv:set_stack(from_list, 62, "realchess:rook_white_2")
451 inv:set_stack(from_list, 64, "")
456 elseif thisMove == "black" then
457 if from_y == 0 and to_y == 0 then
459 local castlingBlackL = meta:get_int("castlingBlackL")
460 local idx1 = inv:get_stack(from_list, 1):get_name()
462 if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
463 for i = 2, from_index - 1 do
464 if inv:get_stack(from_list, i):get_name() ~= "" then
468 inv:set_stack(from_list, 1, "")
469 inv:set_stack(from_list, 3, "realchess:rook_black_1")
472 elseif to_x == 6 then
473 local castlingBlackR = meta:get_int("castlingBlackR")
474 local idx8 = inv:get_stack(from_list, 1):get_name()
476 if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
477 for i = from_index + 1, 7 do
478 if inv:get_stack(from_list, i):get_name() ~= "" then
482 inv:set_stack(from_list, 6, "realchess:rook_black_2")
483 inv:set_stack(from_list, 8, "")
491 if dx < 0 then dx = -dx end
492 if dy < 0 then dy = -dy end
493 if dx > 1 or dy > 1 then return 0 end
496 if thisMove == "white" then
497 meta:set_int("castlingWhiteL", 0)
498 meta:set_int("castlingWhiteR", 0)
499 elseif thisMove == "black" then
500 meta:set_int("castlingBlackL", 0)
501 meta:set_int("castlingBlackR", 0)
505 meta:set_string("playerWhite", playerWhite)
506 meta:set_string("playerBlack", playerBlack)
507 meta:set_string("lastMove", thisMove)
508 meta:set_int("lastMoveTime", minetest.get_gametime())
509 local lastMove = meta:get_string("lastMove")
511 if lastMove == "black" then
512 minetest.chat_send_player(playerWhite, "["..os.date("%H:%M:%S").."] "..
513 playerName.." moved a "..pieceFrom:match(":(%a+)")..", it's now your turn.")
514 elseif lastMove == "white" then
515 minetest.chat_send_player(playerBlack, "["..os.date("%H:%M:%S").."] "..
516 playerName.." moved a "..pieceFrom:match(":(%a+)")..", it's now your turn.")
519 if pieceTo:sub(11,14) == "king" then
520 minetest.chat_send_player(playerBlack, playerName.." won the game.")
521 minetest.chat_send_player(playerWhite, playerName.." won the game.")
522 meta:set_string("winner", thisMove)
528 local function timeout_format(timeout_limit)
529 local time_remaining = timeout_limit - minetest.get_gametime()
530 local minutes = math.floor(time_remaining / 60)
531 local seconds = time_remaining % 60
533 if minutes == 0 then return seconds.." sec." end
534 return minutes.." min. "..seconds.." sec."
537 function realchess.fields(pos, _, fields, sender)
538 local playerName = sender:get_player_name()
539 local meta = minetest.get_meta(pos)
540 local timeout_limit = meta:get_int("lastMoveTime") + 300
541 local playerWhite = meta:get_string("playerWhite")
542 local playerBlack = meta:get_string("playerBlack")
543 local lastMoveTime = meta:get_int("lastMoveTime")
544 if fields.quit then return end
546 -- timeout is 5 min. by default for resetting the game (non-players only)
547 if fields.new and (playerWhite == playerName or playerBlack == playerName) then
549 elseif fields.new and lastMoveTime ~= 0 and minetest.get_gametime() >= timeout_limit and
550 (playerWhite ~= playerName or playerBlack ~= playerName) then
553 minetest.chat_send_player(playerName, "[!] You can't reset the chessboard, a game has been started.\n"..
554 "If you are not a current player, try again in "..timeout_format(timeout_limit))
558 function realchess.dig(pos, player)
559 local meta = minetest.get_meta(pos)
560 local playerName = player:get_player_name()
561 local timeout_limit = meta:get_int("lastMoveTime") + 300
562 local lastMoveTime = meta:get_int("lastMoveTime")
564 -- timeout is 5 min. by default for digging the chessboard (non-players only)
565 return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
566 minetest.chat_send_player(playerName, "[!] You can't dig the chessboard, a game has been started.\n"..
567 "Reset it first if you're a current player, or dig again in "..timeout_format(timeout_limit))
570 function realchess.on_move(pos, from_list, from_index)
571 local inv = minetest.get_meta(pos):get_inventory()
572 inv:set_stack(from_list, from_index, '')
576 minetest.register_node(":realchess:chessboard", {
577 description = "Chess Board",
578 drawtype = "nodebox",
580 paramtype2 = "facedir",
581 inventory_image = "chessboard_top.png",
582 wield_image = "chessboard_top.png",
583 tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
584 groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
585 sounds = default.node_sound_wood_defaults(),
586 node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
587 sunlight_propagates = true,
588 on_rotate = screwdriver.rotate_simple,
589 can_dig = realchess.dig,
590 on_construct = realchess.init,
591 on_receive_fields = realchess.fields,
592 allow_metadata_inventory_move = realchess.move,
593 on_metadata_inventory_move = realchess.on_move,
594 allow_metadata_inventory_take = function() return 0 end
597 local function register_piece(name, count)
598 for _, color in pairs({"black", "white"}) do
600 minetest.register_craftitem(":realchess:"..name.."_"..color, {
601 description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
602 inventory_image = name.."_"..color..".png",
604 groups = {not_in_creative_inventory=1}
608 minetest.register_craftitem(":realchess:"..name.."_"..color.."_"..i, {
609 description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
610 inventory_image = name.."_"..color..".png",
612 groups = {not_in_creative_inventory=1}
619 register_piece("pawn", 8)
620 register_piece("rook", 2)
621 register_piece("knight", 2)
622 register_piece("bishop", 2)
623 register_piece("queen")
624 register_piece("king")