]> git.lizzy.rs Git - xdecor.git/blob - src/chess.lua
be7fc96ad9d4e8f25c0152185972d02c3c954ecc
[xdecor.git] / src / chess.lua
1 local realchess = {}
2 screwdriver = screwdriver or {}
3
4 local function index_to_xy(idx)
5         if not idx then
6                 return nil
7         end
8
9         idx = idx - 1
10
11         local x = idx % 8
12         local y = (idx - x) / 8
13
14         return x, y
15 end
16
17 local function xy_to_index(x, y)
18         return x + y * 8 + 1
19 end
20
21 local function get_square(a, b)
22         return (a * 8) - (8 - b)
23 end
24
25 local chat_prefix = minetest.colorize("#FFFF00", "[Chess] ")
26 local letters = {'A','B','C','D','E','F','G','H'}
27
28 local function board_to_table(inv)
29         local t = {}
30         for i = 1, 64 do
31                 t[#t + 1] = inv:get_stack("board", i):get_name()
32         end
33
34         return t
35 end
36
37 local piece_values = {
38         pawn   = 10,
39         knight = 30,
40         bishop = 30,
41         rook   = 50,
42         queen  = 90,
43         king   = 900
44 }
45
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
49         local moves = {}
50         local from_x, from_y = index_to_xy(from_idx)
51
52         for i = 1, 64 do
53                 local stack_name = board[i]
54                 if stack_name:find((color == "black" and "white" or "black")) or
55                                 stack_name == "" then
56                         moves[i] = 0
57                 end
58         end
59
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)
63
64                 -- PAWN
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
71                                                 if pieceTo ~= "" then
72                                                         moves[to_idx] = nil
73                                                 end
74                                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
75                                                 if not pieceTo:find("black") then
76                                                         moves[to_idx] = nil
77                                                 end
78                                         else
79                                                 moves[to_idx] = nil
80                                         end
81                                 elseif from_y - 2 == to_y then
82                                         if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
83                                                 moves[to_idx] = nil
84                                         end
85                                 else
86                                         moves[to_idx] = nil
87                                 end
88
89                                 --[[
90                                      if x not changed
91                                           ensure that destination cell is empty
92                                      elseif x changed one unit left or right
93                                           ensure the pawn is killing opponent piece
94                                      else
95                                           move is not legal - abort
96                                 ]]
97
98                                 if from_x == to_x then
99                                         if pieceTo ~= "" then
100                                                 moves[to_idx] = nil
101                                         end
102                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
103                                         if not pieceTo:find("black") then
104                                                 moves[to_idx] = nil
105                                         end
106                                 else
107                                         moves[to_idx] = nil
108                                 end
109
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
116                                                         moves[to_idx] = nil
117                                                 end
118                                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
119                                                 if not pieceTo:find("white") then
120                                                         moves[to_idx] = nil
121                                                 end
122                                         else
123                                                 moves[to_idx] = nil
124                                         end
125                                 elseif from_y + 2 == to_y then
126                                         if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
127                                                 moves[to_idx] = nil
128                                         end
129                                 else
130                                         moves[to_idx] = nil
131                                 end
132
133                                 --[[
134                                      if x not changed
135                                           ensure that destination cell is empty
136                                      elseif x changed one unit left or right
137                                           ensure the pawn is killing opponent piece
138                                      else
139                                           move is not legal - abort
140                                 ]]
141
142                                 if from_x == to_x then
143                                         if pieceTo ~= "" then
144                                                 moves[to_idx] = nil
145                                         end
146                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
147                                         if not pieceTo:find("white") then
148                                                 moves[to_idx] = nil
149                                         end
150                                 else
151                                         moves[to_idx] = nil
152                                 end
153                         else
154                                 moves[to_idx] = nil
155                         end
156
157                 -- ROOK
158                 elseif piece == "rook" then
159                         if from_x == to_x then
160                                 -- Moving vertically
161                                 if from_y < to_y then
162                                         -- Moving down
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
166                                                         moves[to_idx] = nil
167                                                 end
168                                         end
169                                 else
170                                         -- Mocing up
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
174                                                         moves[to_idx] = nil
175                                                 end
176                                         end
177                                 end
178                         elseif from_y == to_y then
179                                 -- Mocing horizontally
180                                 if from_x < to_x then
181                                         -- mocing right
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
185                                                         moves[to_idx] = nil
186                                                 end
187                                         end
188                                 else
189                                         -- Mocing left
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
193                                                         moves[to_idx] = nil
194                                                 end
195                                         end
196                                 end
197                         else
198                                 -- Attempt to move arbitrarily -> abort
199                                 moves[to_idx] = nil
200                         end
201
202                 -- KNIGHT
203                 elseif piece == "knight" then
204                         -- Get relative pos
205                         local dx = from_x - to_x
206                         local dy = from_y - to_y
207
208                         -- Get absolute values
209                         if dx < 0 then
210                                 dx = -dx
211                         end
212
213                         if dy < 0 then
214                                 dy = -dy
215                         end
216
217                         -- Sort x and y
218                         if dx > dy then
219                                 dx, dy = dy, dx
220                         end
221
222                         -- Ensure that dx == 1 and dy == 2
223                         if dx ~= 1 or dy ~= 2 then
224                                 moves[to_idx] = nil
225                         end
226                         -- Just ensure that destination cell does not contain friend piece
227                         -- ^ It was done already thus everything ok
228
229                 -- BISHOP
230                 elseif piece == "bishop" then
231                         -- Get relative pos
232                         local dx = from_x - to_x
233                         local dy = from_y - to_y
234
235                         -- Get absolute values
236                         if dx < 0 then
237                                 dx = -dx
238                         end
239
240                         if dy < 0 then
241                                 dy = -dy
242                         end
243
244                         -- Ensure dx and dy are equal
245                         if dx ~= dy then
246                                 moves[to_idx] = nil
247                         end
248
249                         if from_x < to_x then
250                                 if from_y < to_y then
251                                         -- Moving right-down
252                                         -- Ensure that no piece disturbs the way
253                                         for i = 1, dx - 1 do
254                                                 if board[xy_to_index(from_x + i, from_y + i)] ~= "" then
255                                                         moves[to_idx] = nil
256                                                 end
257                                         end
258                                 else
259                                         -- Moving right-up
260                                         -- Ensure that no piece disturbs the way
261                                         for i = 1, dx - 1 do
262                                                 if board[xy_to_index(from_x + i, from_y - i)] ~= "" then
263                                                         moves[to_idx] = nil
264                                                 end
265                                         end
266                                 end
267                         else
268                                 if from_y < to_y then
269                                         -- Moving left-down
270                                         -- Ensure that no piece disturbs the way
271                                         for i = 1, dx - 1 do
272                                                 if board[xy_to_index(from_x - i, from_y + i)] ~= "" then
273                                                         moves[to_idx] = nil
274                                                 end
275                                         end
276                                 else
277                                         -- Moving left-up
278                                         -- ensure that no piece disturbs the way
279                                         for i = 1, dx - 1 do
280                                                 if board[xy_to_index(from_x - i, from_y - i)] ~= "" then
281                                                         moves[to_idx] = nil
282                                                 end
283                                         end
284                                 end
285                         end
286
287                 -- QUEEN
288                 elseif piece == "queen" then
289                         local dx = from_x - to_x
290                         local dy = from_y - to_y
291
292                         -- Get absolute values
293                         if dx < 0 then
294                                 dx = -dx
295                         end
296
297                         if dy < 0 then
298                                 dy = -dy
299                         end
300
301                         -- Ensure valid relative move
302                         if dx ~= 0 and dy ~= 0 and dx ~= dy then
303                                 moves[to_idx] = nil
304                         end
305
306                         if from_x == to_x then
307                                 -- Moving vertically
308                                 if from_y < to_y then
309                                         -- Moving down
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
313                                                         moves[to_idx] = nil
314                                                 end
315                                         end
316                                 else
317                                         -- Mocing up
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
321                                                         moves[to_idx] = nil
322                                                 end
323                                         end
324                                 end
325                         elseif from_x < to_x then
326                                 if from_y == to_y then
327                                         -- Goes right
328                                         -- Ensure that no piece disturbs the way
329                                         for i = 1, dx - 1 do
330                                                 if board[xy_to_index(from_x + i, from_y)] ~= "" then
331                                                         moves[to_idx] = nil
332                                                 end
333                                         end
334                                 elseif from_y < to_y then
335                                         -- Goes right-down
336                                         -- Ensure that no piece disturbs the way
337                                         for i = 1, dx - 1 do
338                                                 if board[xy_to_index(from_x + i, from_y + i)] ~= "" then
339                                                         moves[to_idx] = nil
340                                                 end
341                                         end
342                                 else
343                                         -- Goes right-up
344                                         -- Ensure that no piece disturbs the way
345                                         for i = 1, dx - 1 do
346                                                 if board[xy_to_index(from_x + i, from_y - i)] ~= "" then
347                                                         moves[to_idx] = nil
348                                                 end
349                                         end
350                                 end
351                         else
352                                 if from_y == to_y then
353                                         -- Mocing horizontally
354                                         if from_x < to_x then
355                                                 -- mocing right
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
359                                                                 moves[to_idx] = nil
360                                                         end
361                                                 end
362                                         else
363                                                 -- Mocing left
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
367                                                                 moves[to_idx] = nil
368                                                         end
369                                                 end
370                                         end
371                                 elseif from_y < to_y then
372                                         -- Goes left-down
373                                         -- Ensure that no piece disturbs the way
374                                         for i = 1, dx - 1 do
375                                                 if board[xy_to_index(from_x - i, from_y + i)] ~= "" then
376                                                         moves[to_idx] = nil
377                                                 end
378                                         end
379                                 else
380                                         -- Goes left-up
381                                         -- Ensure that no piece disturbs the way
382                                         for i = 1, dx - 1 do
383                                                 if board[xy_to_index(from_x - i, from_y - i)] ~= "" then
384                                                         moves[to_idx] = nil
385                                                 end
386                                         end
387                                 end
388                         end
389
390                 -- KING
391                 elseif piece == "king" then
392                         local dx = from_x - to_x
393                         local dy = from_y - to_y
394
395                         if dx < 0 then
396                                 dx = -dx
397                         end
398
399                         if dy < 0 then
400                                 dy = -dy
401                         end
402
403                         if dx > 1 or dy > 1 then
404                                 moves[to_idx] = nil
405                         end
406                 end
407         end
408
409         if not next(moves) then return end
410
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
416                                         moves[i] = value
417                                 end
418                         end
419                 end
420         end
421
422         return moves
423 end
424
425 local function best_move(moves)
426         local value, choices = 0, {}
427
428         for from, _ in pairs(moves) do
429         for to, val in pairs(_) do
430                 if val > value then
431                         value = val
432                         choices = {{
433                                 from = from,
434                                 to = to
435                         }}
436                 elseif val == value then
437                         choices[#choices + 1] = {
438                                 from = from,
439                                 to = to
440                         }
441                 end
442         end
443         end
444
445         local random = math.random(1, #choices)
446         local choice_from, choice_to = choices[random].from, choices[random].to
447
448         return tonumber(choice_from), choice_to
449 end
450
451 local rowDirs = {-1, -1, -1, 0, 0, 1, 1, 1}
452 local colDirs = {-1, 0, 1, -1, 1, -1, 0, 1}
453
454 local rowDirsKnight = { 2,  1, 2, 1, -2, -1, -2, -1}
455 local colDirsKnight = {-1, -2, 1, 2,  1,  2, -1, -2}
456
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}
461
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}
466
467         for dir = 1, 8 do
468                 if not threatDetected then
469                         local col, row = index_to_xy(idx)
470                         col, row = col + 1, row + 1
471
472                         for step = 1, 8 do
473                                 row = row + rowDirs[dir]
474                                 col = col + colDirs[dir]
475
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+)")
480
481                                         if piece then
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
489                                                         else
490                                                                 if step == 1 then
491                                                                         if piece == "pawn" and pawnThreats[dir] then
492                                                                                 threatDetected = true
493                                                                         end
494                                                                         if piece == "king" and kingThreats[dir] then
495                                                                                 threatDetected = true
496                                                                         end
497                                                                 end
498                                                         end
499                                                 end
500                                                 break
501                                         end
502                                 end
503                         end
504
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]
509
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+)")
514
515                                 if piece and pieceColor ~= color and piece == "knight" then
516                                         threatDetected = true
517                                 end
518                         end
519                 end
520         end
521
522         return threatDetected
523 end
524
525 local function locate_kings(board)
526         local Bidx, Widx
527         for i = 1, 64 do
528                 local piece, color = board[i]:match(":(%w+)_(%w+)")
529                 if piece == "king" then
530                         if color == "black" then
531                                 Bidx = i
532                         else
533                                 Widx = i
534                         end
535                 end
536         end
537
538         return Bidx, Widx
539 end
540
541 local pieces = {
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"
576 }
577
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,"
583                 x = x + 1
584         end
585 end
586 pieces_str = pieces_str .. "69=mailbox_blank16.png"
587
588 local fs_init = [[
589         size[4,1.2;]
590         no_prepend[]
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]
594 ]]
595
596 local fs = [[
597         size[14.7,10;]
598         no_prepend[]
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 .. "]"
607
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"
611
612         local playerWhite = meta:get_string("playerWhite")
613         local playerBlack = meta:get_string("playerBlack")
614
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\\]")
623
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]" ..
628                 eaten_img
629
630         meta:set_string("formspec", formspec)
631 end
632
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 ""
640
641         local coordFrom = letters[from_x + 1] .. math.abs(from_y - 8)
642         local coordTo   = letters[to_x   + 1] .. math.abs(to_y   - 8)
643
644         local new_moves = pieceFrom_si_id .. "," ..
645                 coordFrom .. "," ..
646                         (pieceTo ~= "" and "#33FF33" or "#FFFFFF") .. ", > ,#FFFFFF," ..
647                 coordTo .. "," ..
648                 (pieceTo ~= "" and pieceTo_si_id or "69") .. "," ..
649                 moves
650
651         meta:set_string("moves", new_moves)
652 end
653
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 .. ","
658         end
659
660         meta:set_string("eaten", eaten)
661
662         local eaten_t   = string.split(eaten, ",")
663         local eaten_img = ""
664
665         local a, b = 0, 0
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
670
671                 if is_white then
672                         a = a + 1
673                 else
674                         b = b + 1
675                 end
676
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]"
680         end
681
682         meta:set_string("eaten_img", eaten_img)
683 end
684
685 function realchess.init(pos)
686         local meta = minetest.get_meta(pos)
687         local inv  = meta:get_inventory()
688
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", "")
696
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)
702
703         meta:set_string("moves", "")
704         meta:set_string("eaten", "")
705         meta:set_string("mode", "")
706
707         inv:set_list("board", pieces)
708         inv:set_size("board", 64)
709 end
710
711 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
712         if from_list ~= "board" and to_list ~= "board" then
713                 return 0
714         end
715
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
725
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!")
729                         return 0
730                 end
731
732                 if lastMove ~= "" and lastMove ~= "black" then
733                         return 0
734                 end
735
736                 if pieceTo:find("white") then
737                         -- Don't replace pieces of same color
738                         return 0
739                 end
740
741                 playerWhite = playerName
742                 thisMove = "white"
743
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!")
747                         return 0
748                 end
749
750                 if lastMove ~= "" and lastMove ~= "white" then
751                         return 0
752                 end
753
754                 if pieceTo:find("black") then
755                         -- Don't replace pieces of same color
756                         return 0
757                 end
758
759                 playerBlack = playerName
760                 thisMove = "black"
761         end
762
763         -- MOVE LOGIC
764
765         local from_x, from_y = index_to_xy(from_index)
766         local to_x, to_y     = index_to_xy(to_index)
767
768         -- PAWN
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
776                                                 return 0
777                                         elseif to_index >= 1 and to_index <= 8 then
778                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
779                                         end
780                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
781                                         if not pieceTo:find("black") then
782                                                 return 0
783                                         elseif to_index >= 1 and to_index <= 8 then
784                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
785                                         end
786                                 else
787                                         return 0
788                                 end
789                         elseif from_y - 2 == to_y then
790                                 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
791                                         return 0
792                                 end
793                         else
794                                 return 0
795                         end
796
797                         --[[
798                              if x not changed
799                                   ensure that destination cell is empty
800                              elseif x changed one unit left or right
801                                   ensure the pawn is killing opponent piece
802                              else
803                                   move is not legal - abort
804                         ]]
805
806                         if from_x == to_x then
807                                 if pieceTo ~= "" then
808                                         return 0
809                                 end
810                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
811                                 if not pieceTo:find("black") then
812                                         return 0
813                                 end
814                         else
815                                 return 0
816                         end
817
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
824                                                 return 0
825                                         elseif to_index >= 57 and to_index <= 64 then
826                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
827                                         end
828                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
829                                         if not pieceTo:find("white") then
830                                                 return 0
831                                         elseif to_index >= 57 and to_index <= 64 then
832                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
833                                         end
834                                 else
835                                         return 0
836                                 end
837                         elseif from_y + 2 == to_y then
838                                 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
839                                         return 0
840                                 end
841                         else
842                                 return 0
843                         end
844
845                         --[[
846                              if x not changed
847                                   ensure that destination cell is empty
848                              elseif x changed one unit left or right
849                                   ensure the pawn is killing opponent piece
850                              else
851                                   move is not legal - abort
852                         ]]
853
854                         if from_x == to_x then
855                                 if pieceTo ~= "" then
856                                         return 0
857                                 end
858                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
859                                 if not pieceTo:find("white") then
860                                         return 0
861                                 end
862                         else
863                                 return 0
864                         end
865                 else
866                         return 0
867                 end
868
869         -- ROOK
870         elseif pieceFrom:sub(11,14) == "rook" then
871                 if from_x == to_x then
872                         -- Moving vertically
873                         if from_y < to_y then
874                                 -- Moving down
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
878                                                 return 0
879                                         end
880                                 end
881                         else
882                                 -- Mocing up
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
886                                                 return 0
887                                         end
888                                 end
889                         end
890                 elseif from_y == to_y then
891                         -- Mocing horizontally
892                         if from_x < to_x then
893                                 -- mocing right
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
897                                                 return 0
898                                         end
899                                 end
900                         else
901                                 -- Mocing left
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
905                                                 return 0
906                                         end
907                                 end
908                         end
909                 else
910                         -- Attempt to move arbitrarily -> abort
911                         return 0
912                 end
913
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)
919                         end
920                 end
921
922         -- KNIGHT
923         elseif pieceFrom:sub(11,16) == "knight" then
924                 -- Get relative pos
925                 local dx = from_x - to_x
926                 local dy = from_y - to_y
927
928                 -- Get absolute values
929                 if dx < 0 then dx = -dx end
930                 if dy < 0 then dy = -dy end
931
932                 -- Sort x and y
933                 if dx > dy then dx, dy = dy, dx end
934
935                 -- Ensure that dx == 1 and dy == 2
936                 if dx ~= 1 or dy ~= 2 then
937                         return 0
938                 end
939                 -- Just ensure that destination cell does not contain friend piece
940                 -- ^ It was done already thus everything ok
941
942         -- BISHOP
943         elseif pieceFrom:sub(11,16) == "bishop" then
944                 -- Get relative pos
945                 local dx = from_x - to_x
946                 local dy = from_y - to_y
947
948                 -- Get absolute values
949                 if dx < 0 then dx = -dx end
950                 if dy < 0 then dy = -dy end
951
952                 -- Ensure dx and dy are equal
953                 if dx ~= dy then return 0 end
954
955                 if from_x < to_x then
956                         if from_y < to_y then
957                                 -- Moving right-down
958                                 -- Ensure that no piece disturbs the way
959                                 for i = 1, dx - 1 do
960                                         if inv:get_stack(
961                                                 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
962                                                 return 0
963                                         end
964                                 end
965                         else
966                                 -- Moving right-up
967                                 -- Ensure that no piece disturbs the way
968                                 for i = 1, dx - 1 do
969                                         if inv:get_stack(
970                                                 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
971                                                 return 0
972                                         end
973                                 end
974                         end
975                 else
976                         if from_y < to_y then
977                                 -- Moving left-down
978                                 -- Ensure that no piece disturbs the way
979                                 for i = 1, dx - 1 do
980                                         if inv:get_stack(
981                                                 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
982                                                 return 0
983                                         end
984                                 end
985                         else
986                                 -- Moving left-up
987                                 -- ensure that no piece disturbs the way
988                                 for i = 1, dx - 1 do
989                                         if inv:get_stack(
990                                                 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
991                                                 return 0
992                                         end
993                                 end
994                         end
995                 end
996
997         -- QUEEN
998         elseif pieceFrom:sub(11,15) == "queen" then
999                 local dx = from_x - to_x
1000                 local dy = from_y - to_y
1001
1002                 -- Get absolute values
1003                 if dx < 0 then dx = -dx end
1004                 if dy < 0 then dy = -dy end
1005
1006                 -- Ensure valid relative move
1007                 if dx ~= 0 and dy ~= 0 and dx ~= dy then
1008                         return 0
1009                 end
1010
1011                 if from_x == to_x then
1012                         if from_y < to_y then
1013                                 -- Goes down
1014                                 -- Ensure that no piece disturbs the way
1015                                 for i = 1, dx - 1 do
1016                                         if inv:get_stack(
1017                                                 from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
1018                                                 return 0
1019                                         end
1020                                 end
1021                         else
1022                                 -- Goes up
1023                                 -- Ensure that no piece disturbs the way
1024                                 for i = 1, dx - 1 do
1025                                         if inv:get_stack(
1026                                                 from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
1027                                                 return 0
1028                                         end
1029                                 end
1030                         end
1031                 elseif from_x < to_x then
1032                         if from_y == to_y then
1033                                 -- Goes right
1034                                 -- Ensure that no piece disturbs the way
1035                                 for i = 1, dx - 1 do
1036                                         if inv:get_stack(
1037                                                 from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
1038                                                 return 0
1039                                         end
1040                                 end
1041                         elseif from_y < to_y then
1042                                 -- Goes right-down
1043                                 -- Ensure that no piece disturbs the way
1044                                 for i = 1, dx - 1 do
1045                                         if inv:get_stack(
1046                                                 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
1047                                                 return 0
1048                                         end
1049                                 end
1050                         else
1051                                 -- Goes right-up
1052                                 -- Ensure that no piece disturbs the way
1053                                 for i = 1, dx - 1 do
1054                                         if inv:get_stack(
1055                                                 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
1056                                                 return 0
1057                                         end
1058                                 end
1059                         end
1060                 else
1061                         if from_y == to_y then
1062                                 -- Goes left
1063                                 -- Ensure that no piece disturbs the way and destination cell does
1064                                 for i = 1, dx - 1 do
1065                                         if inv:get_stack(
1066                                                 from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
1067                                                 return 0
1068                                         end
1069                                 end
1070                         elseif from_y < to_y then
1071                                 -- Goes left-down
1072                                 -- Ensure that no piece disturbs the way
1073                                 for i = 1, dx - 1 do
1074                                         if inv:get_stack(
1075                                                 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
1076                                                 return 0
1077                                         end
1078                                 end
1079                         else
1080                                 -- Goes left-up
1081                                 -- Ensure that no piece disturbs the way
1082                                 for i = 1, dx - 1 do
1083                                         if inv:get_stack(
1084                                                 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
1085                                                 return 0
1086                                         end
1087                                 end
1088                         end
1089                 end
1090
1091         -- KING
1092         elseif pieceFrom:sub(11,14) == "king" then
1093                 local dx = from_x - to_x
1094                 local dy = from_y - to_y
1095                 local check = true
1096
1097                 if thisMove == "white" then
1098                         if from_y == 7 and to_y == 7 then
1099                                 if to_x == 1 then
1100                                         local castlingWhiteL = meta:get_int("castlingWhiteL")
1101                                         local idx57 = inv:get_stack(from_list, 57):get_name()
1102
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
1106                                                                 return 0
1107                                                         end
1108                                                 end
1109
1110                                                 inv:set_stack(from_list, 57, "")
1111                                                 inv:set_stack(from_list, 59, "realchess:rook_white_1")
1112                                                 check = false
1113                                         end
1114                                 elseif to_x == 6 then
1115                                         local castlingWhiteR = meta:get_int("castlingWhiteR")
1116                                         local idx64 = inv:get_stack(from_list, 64):get_name()
1117
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
1121                                                                 return 0
1122                                                         end
1123                                                 end
1124
1125                                                 inv:set_stack(from_list, 62, "realchess:rook_white_2")
1126                                                 inv:set_stack(from_list, 64, "")
1127                                                 check = false
1128                                         end
1129                                 end
1130                         end
1131                 elseif thisMove == "black" then
1132                         if from_y == 0 and to_y == 0 then
1133                                 if to_x == 1 then
1134                                         local castlingBlackL = meta:get_int("castlingBlackL")
1135                                         local idx1 = inv:get_stack(from_list, 1):get_name()
1136
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
1140                                                                 return 0
1141                                                         end
1142                                                 end
1143
1144                                                 inv:set_stack(from_list, 1, "")
1145                                                 inv:set_stack(from_list, 3, "realchess:rook_black_1")
1146                                                 check = false
1147                                         end
1148                                 elseif to_x == 6 then
1149                                         local castlingBlackR = meta:get_int("castlingBlackR")
1150                                         local idx8 = inv:get_stack(from_list, 8):get_name()
1151
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
1155                                                                 return 0
1156                                                         end
1157                                                 end
1158
1159                                                 inv:set_stack(from_list, 6, "realchess:rook_black_2")
1160                                                 inv:set_stack(from_list, 8, "")
1161                                                 check = false
1162                                         end
1163                                 end
1164                         end
1165                 end
1166
1167                 if check then
1168                         if dx < 0 then
1169                                 dx = -dx
1170                         end
1171
1172                         if dy < 0 then
1173                                 dy = -dy
1174                         end
1175
1176                         if dx > 1 or dy > 1 then
1177                                 return 0
1178                         end
1179                 end
1180
1181                 if thisMove == "white" then
1182                         meta:set_int("castlingWhiteL", 0)
1183                         meta:set_int("castlingWhiteR", 0)
1184
1185                 elseif thisMove == "black" then
1186                         meta:set_int("castlingBlackL", 0)
1187                         meta:set_int("castlingBlackR", 0)
1188                 end
1189         end
1190
1191         local board       = board_to_table(inv)
1192         board[to_index]   = board[from_index]
1193         board[from_index] = ""
1194
1195         local black_king_idx, white_king_idx = locate_kings(board)
1196         if not black_king_idx or not white_king_idx then
1197                 return 0
1198         end
1199         local blackAttacked = attacked("black", black_king_idx, board)
1200         local whiteAttacked = attacked("white", white_king_idx, board)
1201
1202         if blackAttacked then
1203                 if thisMove == "black" then
1204                         --[(*)[ and meta:get_string("blackAttacked") == "true" ]] then
1205                         return 0
1206                 else
1207                         meta:set_string("blackAttacked", "true")
1208                 end
1209         else
1210                 meta:set_string("blackAttacked", "")
1211         end
1212
1213         if whiteAttacked then
1214                 if thisMove == "white" then
1215                         --[(*)[ and meta:get_string("whiteAttacked") == "true" ]] then
1216                         return 0
1217                 else
1218                         meta:set_string("whiteAttacked", "true")
1219                 end
1220         else
1221                 meta:set_string("whiteAttacked", "")
1222         end
1223
1224         --(*) Allow a piece to move and put its king in check. Maybe not in the chess rules though?
1225
1226         lastMove = thisMove
1227         meta:set_string("lastMove", lastMove)
1228         meta:set_int("lastMoveTime", minetest.get_gametime())
1229
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)
1234         end
1235
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)
1239
1240         return 1
1241 end
1242
1243 local function ai_move(inv, meta)
1244         local board_t = board_to_table(inv)
1245         local lastMove = meta:get_string("lastMove")
1246
1247         if lastMove == "white" then
1248                 update_formspec(meta)
1249                 local moves = {}
1250
1251                 for i = 1, 64 do
1252                         local possibleMoves = get_possible_moves(board_t, i)
1253                         local stack_name    = inv:get_stack("board", i):get_name()
1254
1255                         if stack_name:find("black") then
1256                                 moves[tostring(i)] = possibleMoves
1257                         end
1258                 end
1259
1260                 local choice_from, choice_to = best_move(moves)
1261
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 ""
1265
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
1271
1272                 if blackAttacked then
1273                         kingSafe = false
1274                         meta:set_string("blackAttacked", "true")
1275                         local save_moves = {}
1276
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)
1284
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
1290                                         end
1291                                 end
1292
1293                                 board[from_idx], board[to_idx] = from_idx_bak, to_idx_bak
1294                         end
1295                         end
1296
1297                         if next(save_moves) then
1298                                 bestMoveSaveFrom, bestMoveSaveTo = best_move(save_moves)
1299                         end
1300                 end
1301
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", "")
1310                                         else
1311                                                 return
1312                                         end
1313                                 else
1314                                         if pieceFrom:find("pawn") and choice_to >= 57 and choice_to <= 64 then
1315                                                 inv:set_stack("board", choice_to, "realchess:queen_black")
1316                                         else
1317                                                 inv:set_stack("board", choice_to, pieceFrom)
1318                                         end
1319
1320                                         inv:set_stack("board", choice_from, "")
1321                                 end
1322
1323                                 board = board_to_table(inv)
1324                                 local _, white_king_idx = locate_kings(board)
1325                                 local whiteAttacked = attacked("white", white_king_idx, board)
1326
1327                                 if whiteAttacked then
1328                                         meta:set_string("whiteAttacked", "true")
1329                                 end
1330
1331                                 if meta:get_string("playerBlack") == "" then
1332                                         meta:set_string("playerBlack", "Dumb AI")
1333                                 end
1334
1335                                 meta:set_string("lastMove", "black")
1336                                 meta:set_int("lastMoveTime", minetest.get_gametime())
1337
1338                                 get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, choice_from, choice_to)
1339                                 get_eaten_list(meta, pieceTo, pieceTo_s)
1340
1341                                 update_formspec(meta)
1342                         end
1343                 end)
1344         else
1345                 update_formspec(meta)
1346         end
1347 end
1348
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, "")
1353
1354         if meta:get_string("mode") == "single" then
1355                 ai_move(inv, meta)
1356         end
1357
1358         return false
1359 end
1360
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
1365
1366         if minutes == 0 then
1367                 return seconds .. " sec."
1368         end
1369
1370         return minutes .. " min. " .. seconds .. " sec."
1371 end
1372
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
1381
1382         if fields.single or fields.multi then
1383                 meta:set_string("mode", (fields.single and "single" or "multi"))
1384                 update_formspec(meta)
1385                 return
1386         end
1387
1388         -- Timeout is 5 min. by default for resetting the game (non-players only)
1389         if fields.new then
1390                 if (playerWhite == playerName or playerBlack == playerName) then
1391                         realchess.init(pos)
1392
1393                 elseif lastMoveTime > 0 then
1394                         if minetest.get_gametime() >= timeout_limit and
1395                                         (playerWhite ~= playerName or playerBlack ~= playerName) then
1396                                 realchess.init(pos)
1397                         else
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))
1402                         end
1403                 end
1404         end
1405 end
1406
1407 function realchess.dig(pos, player)
1408         if not player then
1409                 return false
1410         end
1411
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")
1416
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))
1423 end
1424
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
1444 })
1445
1446 local function register_piece(name, count)
1447         for _, color in pairs({"black", "white"}) do
1448         if not count then
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",
1452                         stack_max = 1,
1453                         groups = {not_in_creative_inventory=1}
1454                 })
1455         else
1456                 for i = 1, count do
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",
1460                                 stack_max = 1,
1461                                 groups = {not_in_creative_inventory=1}
1462                         })
1463                 end
1464         end
1465         end
1466 end
1467
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")
1474
1475 -- Recipes
1476
1477 minetest.register_craft({
1478         output = "realchess:chessboard",
1479         recipe = {
1480                 {"dye:black", "dye:white", "dye:black"},
1481                 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}
1482         }
1483 })