]> git.lizzy.rs Git - xdecor.git/blob - src/chess.lua
53d05f1157880ba044dcaad292a4dc7e297be84e
[xdecor.git] / src / chess.lua
1 local realchess = {}
2 screwdriver = screwdriver or {}
3
4 local function index_to_xy(idx)
5         idx = idx - 1
6         local x = idx % 8
7         local y = (idx - x) / 8
8         return x, y
9 end
10
11 local function xy_to_index(x, y)
12         return x + y * 8 + 1
13 end
14
15 local function get_square(a, b)
16         return (a * 8) - (8 - b)
17 end
18
19 local chat_prefix = minetest.colorize("#FFFF00", "[Chess] ")
20 local letters = {'A','B','C','D','E','F','G','H'}
21
22 local rowDirs = {-1, -1, -1, 0, 0, 1, 1, 1}
23 local colDirs = {-1, 0, 1, -1, 1, -1, 0, 1}
24
25 local bishopThreats = {true,  false, true,  false, false, true,  false, true}
26 local rookThreats   = {false, true,  false, true,  true,  false, true,  false}
27 local queenThreats  = {true,  true,  true,  true,  true,  true,  true,  true}
28 local kingThreats   = {true,  true,  true,  true,  true,  true,  true,  true}
29
30 local function board_to_table(inv)
31         local t = {}
32         for i = 1, 64 do
33                 t[#t + 1] = inv:get_stack("board", i):get_name()
34         end
35
36         return t
37 end
38
39 local function attacked(color, idx, board)
40         local threatDetected = false
41         local kill           = color == "white"
42         local pawnThreats    = {kill, false, kill, false, false, not kill, false, not kill}
43
44         for dir = 1, 8 do
45                 if not threatDetected then
46                         local col, row = index_to_xy(idx)
47                         col, row = col + 1, row + 1
48
49                         for step = 1, 8 do
50                                 row = row + rowDirs[dir]
51                                 col = col + colDirs[dir]
52
53                                 if row >= 1 and row <= 8 and col >= 1 and col <= 8 then
54                                         local square            = get_square(row, col)
55                                         local square_name       = board[square]
56                                         local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
57
58                                         if piece then
59                                                 if pieceColor ~= color then
60                                                         if piece == "bishop" and bishopThreats[dir] then
61                                                                 threatDetected = true
62                                                         elseif piece == "rook" and rookThreats[dir] then
63                                                                 threatDetected = true
64                                                         elseif piece == "queen" and queenThreats[dir] then
65                                                                 threatDetected = true
66                                                         else
67                                                                 if step == 1 then
68                                                                         if piece == "pawn" and pawnThreats[dir] then
69                                                                                 threatDetected = true
70                                                                         end
71                                                                         if piece == "king" and kingThreats[dir] then
72                                                                                 threatDetected = true
73                                                                         end
74                                                                 end
75                                                         end
76                                                 end
77                                                 break
78                                         end
79                                 end
80                         end
81                 end
82         end
83
84         return threatDetected
85 end
86
87 local function locate_kings(board)
88         local Bidx, Widx
89         for i = 1, 64 do
90                 local piece, color = board[i]:match(":(%w+)_(%w+)")
91                 if piece == "king" then
92                         if color == "black" then
93                                 Bidx = i
94                         else
95                                 Widx = i
96                         end
97                 end
98         end
99
100         return Bidx, Widx
101 end
102
103 local pieces = {
104         "realchess:rook_black_1",
105         "realchess:knight_black_1",
106         "realchess:bishop_black_1",
107         "realchess:queen_black",
108         "realchess:king_black",
109         "realchess:bishop_black_2",
110         "realchess:knight_black_2",
111         "realchess:rook_black_2",
112         "realchess:pawn_black_1",
113         "realchess:pawn_black_2",
114         "realchess:pawn_black_3",
115         "realchess:pawn_black_4",
116         "realchess:pawn_black_5",
117         "realchess:pawn_black_6",
118         "realchess:pawn_black_7",
119         "realchess:pawn_black_8",
120         '','','','','','','','','','','','','','','','',
121         '','','','','','','','','','','','','','','','',
122         "realchess:pawn_white_1",
123         "realchess:pawn_white_2",
124         "realchess:pawn_white_3",
125         "realchess:pawn_white_4",
126         "realchess:pawn_white_5",
127         "realchess:pawn_white_6",
128         "realchess:pawn_white_7",
129         "realchess:pawn_white_8",
130         "realchess:rook_white_1",
131         "realchess:knight_white_1",
132         "realchess:bishop_white_1",
133         "realchess:queen_white",
134         "realchess:king_white",
135         "realchess:bishop_white_2",
136         "realchess:knight_white_2",
137         "realchess:rook_white_2"
138 }
139
140 local pieces_str, x = "", 0
141 for i = 1, #pieces do
142         local p = pieces[i]:match(":(%w+_%w+)")
143         if pieces[i]:find(":(%w+)_(%w+)") and not pieces_str:find(p) then
144                 pieces_str = pieces_str .. x .. "=" .. p .. ".png,"
145                 x = x + 1
146         end
147 end
148 pieces_str = pieces_str .. "69=mailbox_blank16.png"
149
150 local fs = [[
151         size[14.7,10;]
152         no_prepend[]
153         bgcolor[#080808BB;true]
154         background[0,0;14.7,10;chess_bg.png]
155         list[context;board;0.3,1;8,8;]
156         listcolors[#00000000;#00000000;#00000000;#30434C;#FFF]
157         tableoptions[background=#00000000;highlight=#00000000;border=false]
158         button[10.5,8.5;2,2;new;New game]
159 ]] ..  "tablecolumns[image," .. pieces_str ..
160                 ";text;color;text;color;text;image," .. pieces_str .. "]"
161
162 local function get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_x, to_x, from_y, to_y)
163         local moves           = meta:get_string("moves")
164         local pieceFrom_s     = pieceFrom:match(":(%w+_%w+)")
165         local pieceFrom_si_id = pieces_str:match("(%d+)=" .. pieceFrom_s)
166         local pieceTo_si_id   = pieceTo_s ~= "" and pieces_str:match("(%d+)=" .. pieceTo_s) or ""
167
168         local coordFrom = letters[from_x + 1] .. math.abs(from_y - 8)
169         local coordTo   = letters[to_x   + 1] .. math.abs(to_y   - 8)
170
171         local new_moves = pieceFrom_si_id .. "," ..
172                 coordFrom .. "," ..
173                         (pieceTo ~= "" and "#33FF33" or "#FFFFFF") .. ", > ,#FFFFFF," ..
174                 coordTo .. "," ..
175                 (pieceTo ~= "" and pieceTo_si_id or "69") .. "," ..
176                 moves
177
178         meta:set_string("moves", new_moves)
179 end
180
181 local function get_eaten_list(meta, pieceTo, pieceTo_s)
182         local eaten = meta:get_string("eaten")
183         if pieceTo ~= "" then
184                 eaten = eaten .. pieceTo_s .. ","
185         end
186
187         meta:set_string("eaten", eaten)
188
189         local eaten_t   = string.split(eaten, ",")
190         local eaten_img = ""
191
192         local a, b = 0, 0
193         for i = 1, #eaten_t do
194                 local is_white = eaten_t[i]:sub(-5,-1) == "white"
195                 local X = (is_white and a or b) % 4
196                 local Y = ((is_white and a or b) % 16 - X) / 4
197
198                 if is_white then
199                         a = a + 1
200                 else
201                         b = b + 1
202                 end
203
204                 eaten_img = eaten_img ..
205                         "image[" .. ((X + (is_white and 11.7 or 8.8)) - (X * 0.45)) .. "," ..
206                                     ((Y + 5.56) - (Y * 0.2)) .. ";1,1;" .. eaten_t[i] .. ".png]"
207         end
208
209         meta:set_string("eaten_img", eaten_img)
210 end
211
212 function realchess.init(pos)
213         local meta = minetest.get_meta(pos)
214         local inv  = meta:get_inventory()
215
216         meta:set_string("formspec", fs)
217         meta:set_string("infotext", "Chess Board")
218         meta:set_string("playerBlack", "")
219         meta:set_string("playerWhite", "")
220         meta:set_string("lastMove",    "")
221
222         meta:set_int("lastMoveTime",   0)
223         meta:set_int("castlingBlackL", 1)
224         meta:set_int("castlingBlackR", 1)
225         meta:set_int("castlingWhiteL", 1)
226         meta:set_int("castlingWhiteR", 1)
227
228         meta:set_string("moves", "")
229         meta:set_string("eaten", "")
230
231         inv:set_list("board", pieces)
232         inv:set_size("board", 64)
233 end
234
235 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
236         if from_list ~= "board" and to_list ~= "board" then
237                 return 0
238         end
239
240         local meta        = minetest.get_meta(pos)
241         local playerName  = player:get_player_name()
242         local inv         = meta:get_inventory()
243         local pieceFrom   = inv:get_stack(from_list, from_index):get_name()
244         local pieceTo     = inv:get_stack(to_list, to_index):get_name()
245         local lastMove    = meta:get_string("lastMove")
246         local playerWhite = meta:get_string("playerWhite")
247         local playerBlack = meta:get_string("playerBlack")
248         local thisMove    -- Will replace lastMove when move is legal
249
250         if pieceFrom:find("white") then
251                 if playerWhite ~= "" and playerWhite ~= playerName then
252                         minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays white pieces!")
253                         return 0
254                 end
255
256                 if lastMove ~= "" and lastMove ~= "black" then
257                         return 0
258                 end
259
260                 if pieceTo:find("white") then
261                         -- Don't replace pieces of same color
262                         return 0
263                 end
264
265                 playerWhite = playerName
266                 thisMove = "white"
267
268         elseif pieceFrom:find("black") then
269                 if playerBlack ~= "" and playerBlack ~= playerName then
270                         minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays black pieces!")
271                         return 0
272                 end
273
274                 if lastMove ~= "" and lastMove ~= "white" then
275                         return 0
276                 end
277
278                 if pieceTo:find("black") then
279                         -- Don't replace pieces of same color
280                         return 0
281                 end
282
283                 playerBlack = playerName
284                 thisMove = "black"
285         end
286
287         -- MOVE LOGIC
288
289         local from_x, from_y = index_to_xy(from_index)
290         local to_x, to_y     = index_to_xy(to_index)
291
292         -- PAWN
293         if pieceFrom:sub(11,14) == "pawn" then
294                 if thisMove == "white" then
295                         local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
296                         -- white pawns can go up only
297                         if from_y - 1 == to_y then
298                                 if from_x == to_x then
299                                         if pieceTo ~= "" then
300                                                 return 0
301                                         elseif to_index >= 1 and to_index <= 8 then
302                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
303                                         end
304                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
305                                         if not pieceTo:find("black") then
306                                                 return 0
307                                         elseif to_index >= 1 and to_index <= 8 then
308                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
309                                         end
310                                 else
311                                         return 0
312                                 end
313                         elseif from_y - 2 == to_y then
314                                 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
315                                         return 0
316                                 end
317                         else
318                                 return 0
319                         end
320
321                         --[[
322                              if x not changed
323                                   ensure that destination cell is empty
324                              elseif x changed one unit left or right
325                                   ensure the pawn is killing opponent piece
326                              else
327                                   move is not legal - abort
328                         ]]
329
330                         if from_x == to_x then
331                                 if pieceTo ~= "" then
332                                         return 0
333                                 end
334                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
335                                 if not pieceTo:find("black") then
336                                         return 0
337                                 end
338                         else
339                                 return 0
340                         end
341
342                 elseif thisMove == "black" then
343                         local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
344                         -- black pawns can go down only
345                         if from_y + 1 == to_y then
346                                 if from_x == to_x then
347                                         if pieceTo ~= "" then
348                                                 return 0
349                                         elseif to_index >= 57 and to_index <= 64 then
350                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
351                                         end
352                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
353                                         if not pieceTo:find("white") then
354                                                 return 0
355                                         elseif to_index >= 57 and to_index <= 64 then
356                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
357                                         end
358                                 else
359                                         return 0
360                                 end
361                         elseif from_y + 2 == to_y then
362                                 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
363                                         return 0
364                                 end
365                         else
366                                 return 0
367                         end
368
369                         --[[
370                              if x not changed
371                                   ensure that destination cell is empty
372                              elseif x changed one unit left or right
373                                   ensure the pawn is killing opponent piece
374                              else
375                                   move is not legal - abort
376                         ]]
377
378                         if from_x == to_x then
379                                 if pieceTo ~= "" then
380                                         return 0
381                                 end
382                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
383                                 if not pieceTo:find("white") then
384                                         return 0
385                                 end
386                         else
387                                 return 0
388                         end
389                 else
390                         return 0
391                 end
392
393         -- ROOK
394         elseif pieceFrom:sub(11,14) == "rook" then
395                 if from_x == to_x then
396                         -- Moving vertically
397                         if from_y < to_y then
398                                 -- Moving down
399                                 -- Ensure that no piece disturbs the way
400                                 for i = from_y + 1, to_y - 1 do
401                                         if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
402                                                 return 0
403                                         end
404                                 end
405                         else
406                                 -- Mocing up
407                                 -- Ensure that no piece disturbs the way
408                                 for i = to_y + 1, from_y - 1 do
409                                         if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
410                                                 return 0
411                                         end
412                                 end
413                         end
414                 elseif from_y == to_y then
415                         -- Mocing horizontally
416                         if from_x < to_x then
417                                 -- mocing right
418                                 -- ensure that no piece disturbs the way
419                                 for i = from_x + 1, to_x - 1 do
420                                         if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
421                                                 return 0
422                                         end
423                                 end
424                         else
425                                 -- Mocing left
426                                 -- Ensure that no piece disturbs the way
427                                 for i = to_x + 1, from_x - 1 do
428                                         if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
429                                                 return 0
430                                         end
431                                 end
432                         end
433                 else
434                         -- Attempt to move arbitrarily -> abort
435                         return 0
436                 end
437
438                 if thisMove == "white" or thisMove == "black" then
439                         if pieceFrom:sub(-1) == "1" then
440                                 meta:set_int("castlingWhiteL", 0)
441                         elseif pieceFrom:sub(-1) == "2" then
442                                 meta:set_int("castlingWhiteR", 0)
443                         end
444                 end
445
446         -- KNIGHT
447         elseif pieceFrom:sub(11,16) == "knight" then
448                 -- Get relative pos
449                 local dx = from_x - to_x
450                 local dy = from_y - to_y
451
452                 -- Get absolute values
453                 if dx < 0 then dx = -dx end
454                 if dy < 0 then dy = -dy end
455
456                 -- Sort x and y
457                 if dx > dy then dx, dy = dy, dx end
458
459                 -- Ensure that dx == 1 and dy == 2
460                 if dx ~= 1 or dy ~= 2 then
461                         return 0
462                 end
463                 -- Just ensure that destination cell does not contain friend piece
464                 -- ^ It was done already thus everything ok
465
466         -- BISHOP
467         elseif pieceFrom:sub(11,16) == "bishop" then
468                 -- Get relative pos
469                 local dx = from_x - to_x
470                 local dy = from_y - to_y
471
472                 -- Get absolute values
473                 if dx < 0 then dx = -dx end
474                 if dy < 0 then dy = -dy end
475
476                 -- Ensure dx and dy are equal
477                 if dx ~= dy then return 0 end
478
479                 if from_x < to_x then
480                         if from_y < to_y then
481                                 -- Moving right-down
482                                 -- Ensure that no piece disturbs the way
483                                 for i = 1, dx - 1 do
484                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
485                                                 return 0
486                                         end
487                                 end
488                         else
489                                 -- Moving right-up
490                                 -- Ensure that no piece disturbs the way
491                                 for i = 1, dx - 1 do
492                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
493                                                 return 0
494                                         end
495                                 end
496                         end
497                 else
498                         if from_y < to_y then
499                                 -- Moving left-down
500                                 -- Ensure that no piece disturbs the way
501                                 for i = 1, dx - 1 do
502                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
503                                                 return 0
504                                         end
505                                 end
506                         else
507                                 -- Moving left-up
508                                 -- ensure that no piece disturbs the way
509                                 for i = 1, dx - 1 do
510                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
511                                                 return 0
512                                         end
513                                 end
514                         end
515                 end
516
517         -- QUEEN
518         elseif pieceFrom:sub(11,15) == "queen" then
519                 local dx = from_x - to_x
520                 local dy = from_y - to_y
521
522                 -- Get absolute values
523                 if dx < 0 then dx = -dx end
524                 if dy < 0 then dy = -dy end
525
526                 -- Ensure valid relative move
527                 if dx ~= 0 and dy ~= 0 and dx ~= dy then
528                         return 0
529                 end
530
531                 if from_x == to_x then
532                         if from_y < to_y then
533                                 -- Goes down
534                                 -- Ensure that no piece disturbs the way
535                                 for i = 1, dx - 1 do
536                                         if inv:get_stack(from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
537                                                 return 0
538                                         end
539                                 end
540                         else
541                                 -- Goes up
542                                 -- Ensure that no piece disturbs the way
543                                 for i = 1, dx - 1 do
544                                         if inv:get_stack(from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
545                                                 return 0
546                                         end
547                                 end
548                         end
549                 elseif from_x < to_x then
550                         if from_y == to_y then
551                                 -- Goes right
552                                 -- Ensure that no piece disturbs the way
553                                 for i = 1, dx - 1 do
554                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
555                                                 return 0
556                                         end
557                                 end
558                         elseif from_y < to_y then
559                                 -- Goes right-down
560                                 -- Ensure that no piece disturbs the way
561                                 for i = 1, dx - 1 do
562                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
563                                                 return 0
564                                         end
565                                 end
566                         else
567                                 -- Goes right-up
568                                 -- Ensure that no piece disturbs the way
569                                 for i = 1, dx - 1 do
570                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
571                                                 return 0
572                                         end
573                                 end
574                         end
575                 else
576                         if from_y == to_y then
577                                 -- Goes left
578                                 -- Ensure that no piece disturbs the way and destination cell does
579                                 for i = 1, dx - 1 do
580                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
581                                                 return 0
582                                         end
583                                 end
584                         elseif from_y < to_y then
585                                 -- Goes left-down
586                                 -- Ensure that no piece disturbs the way
587                                 for i = 1, dx - 1 do
588                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
589                                                 return 0
590                                         end
591                                 end
592                         else
593                                 -- Goes left-up
594                                 -- Ensure that no piece disturbs the way
595                                 for i = 1, dx - 1 do
596                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
597                                                 return 0
598                                         end
599                                 end
600                         end
601                 end
602
603         -- KING
604         elseif pieceFrom:sub(11,14) == "king" then
605                 local dx = from_x - to_x
606                 local dy = from_y - to_y
607                 local check = true
608
609                 if thisMove == "white" then
610                         if from_y == 7 and to_y == 7 then
611                                 if to_x == 1 then
612                                         local castlingWhiteL = meta:get_int("castlingWhiteL")
613                                         local idx57 = inv:get_stack(from_list, 57):get_name()
614
615                                         if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
616                                                 for i = 58, from_index - 1 do
617                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
618                                                                 return 0
619                                                         end
620                                                 end
621                                                 inv:set_stack(from_list, 57, "")
622                                                 inv:set_stack(from_list, 59, "realchess:rook_white_1")
623                                                 check = false
624                                         end
625                                 elseif to_x == 6 then
626                                         local castlingWhiteR = meta:get_int("castlingWhiteR")
627                                         local idx64 = inv:get_stack(from_list, 64):get_name()
628
629                                         if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
630                                                 for i = from_index + 1, 63 do
631                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
632                                                                 return 0
633                                                         end
634                                                 end
635                                                 inv:set_stack(from_list, 62, "realchess:rook_white_2")
636                                                 inv:set_stack(from_list, 64, "")
637                                                 check = false
638                                         end
639                                 end
640                         end
641                 elseif thisMove == "black" then
642                         if from_y == 0 and to_y == 0 then
643                                 if to_x == 1 then
644                                         local castlingBlackL = meta:get_int("castlingBlackL")
645                                         local idx1 = inv:get_stack(from_list, 1):get_name()
646
647                                         if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
648                                                 for i = 2, from_index - 1 do
649                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
650                                                                 return 0
651                                                         end
652                                                 end
653
654                                                 inv:set_stack(from_list, 1, "")
655                                                 inv:set_stack(from_list, 3, "realchess:rook_black_1")
656                                                 check = false
657                                         end
658                                 elseif to_x == 6 then
659                                         local castlingBlackR = meta:get_int("castlingBlackR")
660                                         local idx8 = inv:get_stack(from_list, 1):get_name()
661
662                                         if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
663                                                 for i = from_index + 1, 7 do
664                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
665                                                                 return 0
666                                                         end
667                                                 end
668
669                                                 inv:set_stack(from_list, 6, "realchess:rook_black_2")
670                                                 inv:set_stack(from_list, 8, "")
671                                                 check = false
672                                         end
673                                 end
674                         end
675                 end
676
677                 if check then
678                         if dx < 0 then
679                                 dx = -dx
680                         end
681
682                         if dy < 0 then
683                                 dy = -dy
684                         end
685
686                         if dx > 1 or dy > 1 then
687                                 return 0
688                         end
689                 end
690
691                 if thisMove == "white" then
692                         meta:set_int("castlingWhiteL", 0)
693                         meta:set_int("castlingWhiteR", 0)
694
695                 elseif thisMove == "black" then
696                         meta:set_int("castlingBlackL", 0)
697                         meta:set_int("castlingBlackR", 0)
698                 end
699         end
700
701         local board       = board_to_table(inv)
702         board[to_index]   = board[from_index]
703         board[from_index] = ""
704
705         local black_king_idx, white_king_idx = locate_kings(board)
706         local blackAttacked = attacked("black", black_king_idx, board)
707         local whiteAttacked = attacked("white", white_king_idx, board)
708
709         if (thisMove == "black" and blackAttacked) or
710            (thisMove == "white" and whiteAttacked) then
711                 return 0
712         end
713
714         lastMove = thisMove
715
716         meta:set_string("lastMove", lastMove)
717         meta:set_int("lastMoveTime", minetest.get_gametime())
718         meta:set_string("playerWhite", playerWhite)
719         meta:set_string("playerBlack", playerBlack)
720
721         local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
722         get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_x, to_x, from_y, to_y)
723         get_eaten_list(meta, pieceTo, pieceTo_s)
724
725         return 1
726 end
727
728 function realchess.on_move(pos, from_list, from_index)
729         local meta = minetest.get_meta(pos)
730         local inv  = meta:get_inventory()
731         inv:set_stack(from_list, from_index, '')
732
733         local board = board_to_table(inv)
734         local black_king_idx, white_king_idx = locate_kings(board)
735         local black_king_attacked = attacked("black", black_king_idx, board)
736         local white_king_attacked = attacked("white", white_king_idx, board)
737
738         local playerWhite = meta:get_string("playerWhite")
739         local playerBlack = meta:get_string("playerBlack")
740
741         local moves       = meta:get_string("moves")
742         local eaten_img   = meta:get_string("eaten_img")
743         local lastMove    = meta:get_string("lastMove")
744         local turnBlack   = minetest.colorize("#000001", (lastMove == "white" and playerBlack ~= "") and
745                             playerBlack .. "..." or playerBlack)
746         local turnWhite   = minetest.colorize("#000001", (lastMove == "black" and playerWhite ~= "") and
747                             playerWhite .. "..." or playerWhite)
748         local check_s     = minetest.colorize("#FF0000", "\\[check\\]")
749
750         local formspec = fs ..
751                 "label[1.9,0.3;"  .. turnBlack .. (black_king_attacked and " " .. check_s or "") .. "]" ..
752                 "label[1.9,9.15;" .. turnWhite .. (white_king_attacked and " " .. check_s or "") .. "]" ..
753                 "table[8.9,1.05;5.07,3.75;moves;" .. moves:sub(1,-2) .. ";1]" ..
754                 eaten_img
755
756         meta:set_string("formspec", formspec)
757
758         return false
759 end
760
761 local function timeout_format(timeout_limit)
762         local time_remaining = timeout_limit - minetest.get_gametime()
763         local minutes        = math.floor(time_remaining / 60)
764         local seconds        = time_remaining % 60
765
766         if minutes == 0 then
767                 return seconds .. " sec."
768         end
769
770         return minutes .. " min. " .. seconds .. " sec."
771 end
772
773 function realchess.fields(pos, _, fields, sender)
774         local playerName    = sender:get_player_name()
775         local meta          = minetest.get_meta(pos)
776         local timeout_limit = meta:get_int("lastMoveTime") + 300
777         local playerWhite   = meta:get_string("playerWhite")
778         local playerBlack   = meta:get_string("playerBlack")
779         local lastMoveTime  = meta:get_int("lastMoveTime")
780         if fields.quit then return end
781
782         -- Timeout is 5 min. by default for resetting the game (non-players only)
783         if fields.new then
784                 if (playerWhite == playerName or playerBlack == playerName) then
785                         realchess.init(pos)
786
787                 elseif lastMoveTime ~= 0 then
788                         if minetest.get_gametime() >= timeout_limit and
789                                         (playerWhite ~= playerName or playerBlack ~= playerName) then
790                                 realchess.init(pos)
791                         else
792                                 minetest.chat_send_player(playerName, chat_prefix ..
793                                         "You can't reset the chessboard, a game has been started. " ..
794                                         "If you aren't a current player, try again in " ..
795                                         timeout_format(timeout_limit))
796                         end
797                 end
798         end
799 end
800
801 function realchess.dig(pos, player)
802         if not player then
803                 return false
804         end
805
806         local meta          = minetest.get_meta(pos)
807         local playerName    = player:get_player_name()
808         local timeout_limit = meta:get_int("lastMoveTime") + 300
809         local lastMoveTime  = meta:get_int("lastMoveTime")
810
811         -- Timeout is 5 min. by default for digging the chessboard (non-players only)
812         return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
813                 minetest.chat_send_player(playerName, chat_prefix ..
814                                 "You can't dig the chessboard, a game has been started. " ..
815                                 "Reset it first if you're a current player, or dig it again in " ..
816                                 timeout_format(timeout_limit))
817 end
818
819 minetest.register_node(":realchess:chessboard", {
820         description = "Chess Board",
821         drawtype = "nodebox",
822         paramtype = "light",
823         paramtype2 = "facedir",
824         inventory_image = "chessboard_top.png",
825         wield_image = "chessboard_top.png",
826         tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
827         groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
828         sounds = default.node_sound_wood_defaults(),
829         node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
830         sunlight_propagates = true,
831         on_rotate = screwdriver.rotate_simple,
832         can_dig = realchess.dig,
833         on_construct = realchess.init,
834         on_receive_fields = realchess.fields,
835         allow_metadata_inventory_move = realchess.move,
836         on_metadata_inventory_move = realchess.on_move,
837         allow_metadata_inventory_take = function() return 0 end
838 })
839
840 local function register_piece(name, count)
841         for _, color in pairs({"black", "white"}) do
842         if not count then
843                 minetest.register_craftitem(":realchess:" .. name .. "_" .. color, {
844                         description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
845                         inventory_image = name .. "_" .. color .. ".png",
846                         stack_max = 1,
847                         groups = {not_in_creative_inventory=1}
848                 })
849         else
850                 for i = 1, count do
851                         minetest.register_craftitem(":realchess:" .. name .. "_" .. color .. "_" .. i, {
852                                 description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
853                                 inventory_image = name .. "_" .. color .. ".png",
854                                 stack_max = 1,
855                                 groups = {not_in_creative_inventory=1}
856                         })
857                 end
858         end
859         end
860 end
861
862 register_piece("pawn", 8)
863 register_piece("rook", 2)
864 register_piece("knight", 2)
865 register_piece("bishop", 2)
866 register_piece("queen")
867 register_piece("king")
868
869 -- Recipes
870
871 minetest.register_craft({
872         output = "realchess:chessboard",
873         recipe = {
874                 {"dye:black", "dye:white", "dye:black"},
875                 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}
876         }
877 })