]> git.lizzy.rs Git - xdecor.git/blob - src/chess.lua
Implement a chess AI
[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
9         return x, y
10 end
11
12 local function xy_to_index(x, y)
13         return x + y * 8 + 1
14 end
15
16 local function get_square(a, b)
17         return (a * 8) - (8 - b)
18 end
19
20 local chat_prefix = minetest.colorize("#FFFF00", "[Chess] ")
21 local letters = {'A','B','C','D','E','F','G','H'}
22
23 local function board_to_table(inv)
24         local t = {}
25         for i = 1, 64 do
26                 t[#t + 1] = inv:get_stack("board", i):get_name()
27         end
28
29         return t
30 end
31
32 local piece_values = {
33         pawn   = 10,
34         knight = 30,
35         bishop = 30,
36         rook   = 50,
37         queen  = 90,
38         king   = 900
39 }
40
41 local function get_possible_moves(inv, from_idx)
42         local piece, color = inv:get_stack("board", from_idx):get_name():match(":(%w+)_(%w+)")
43         if not piece then return end
44         local moves = {}
45         local from_x, from_y = index_to_xy(from_idx)
46
47         for i = 1, 64 do
48                 local stack = inv:get_stack("board", i)
49                 local stack_name = stack:get_name()
50
51                 if stack_name:find((color == "black" and "white" or "black")) or
52                                 stack:is_empty() then
53                         moves[i] = 0
54                 end
55         end
56
57         for to_idx in pairs(moves) do
58                 local pieceTo    = inv:get_stack("board", to_idx):get_name()
59                 local to_x, to_y = index_to_xy(to_idx)
60
61                 -- PAWN
62                 if piece == "pawn" then
63                         if color == "white" then
64                                 local pawnWhiteMove = inv:get_stack("board", xy_to_index(from_x, from_y - 1)):get_name()
65                                 -- white pawns can go up only
66                                 if from_y - 1 == to_y then
67                                         if from_x == to_x then
68                                                 if pieceTo ~= "" then
69                                                         moves[to_idx] = nil
70                                                 end
71                                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
72                                                 if not pieceTo:find("black") then
73                                                         moves[to_idx] = nil
74                                                 end
75                                         else
76                                                 moves[to_idx] = nil
77                                         end
78                                 elseif from_y - 2 == to_y then
79                                         if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
80                                                 moves[to_idx] = nil
81                                         end
82                                 else
83                                         moves[to_idx] = nil
84                                 end
85
86                                 --[[
87                                      if x not changed
88                                           ensure that destination cell is empty
89                                      elseif x changed one unit left or right
90                                           ensure the pawn is killing opponent piece
91                                      else
92                                           move is not legal - abort
93                                 ]]
94
95                                 if from_x == to_x then
96                                         if pieceTo ~= "" then
97                                                 moves[to_idx] = nil
98                                         end
99                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
100                                         if not pieceTo:find("black") then
101                                                 moves[to_idx] = nil
102                                         end
103                                 else
104                                         moves[to_idx] = nil
105                                 end
106
107                         elseif color == "black" then
108                                 local pawnBlackMove = inv:get_stack("board", xy_to_index(from_x, from_y + 1)):get_name()
109                                 -- black pawns can go down only
110                                 if from_y + 1 == to_y then
111                                         if from_x == to_x then
112                                                 if pieceTo ~= "" then
113                                                         moves[to_idx] = nil
114                                                 end
115                                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
116                                                 if not pieceTo:find("white") then
117                                                         moves[to_idx] = nil
118                                                 end
119                                         else
120                                                 moves[to_idx] = nil
121                                         end
122                                 elseif from_y + 2 == to_y then
123                                         if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
124                                                 moves[to_idx] = nil
125                                         end
126                                 else
127                                         moves[to_idx] = nil
128                                 end
129
130                                 --[[
131                                      if x not changed
132                                           ensure that destination cell is empty
133                                      elseif x changed one unit left or right
134                                           ensure the pawn is killing opponent piece
135                                      else
136                                           move is not legal - abort
137                                 ]]
138
139                                 if from_x == to_x then
140                                         if pieceTo ~= "" then
141                                                 moves[to_idx] = nil
142                                         end
143                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
144                                         if not pieceTo:find("white") then
145                                                 moves[to_idx] = nil
146                                         end
147                                 else
148                                         moves[to_idx] = nil
149                                 end
150                         else
151                                 moves[to_idx] = nil
152                         end
153
154                 -- ROOK
155                 elseif piece == "rook" then
156                         if from_x == to_x then
157                                 -- Moving vertically
158                                 if from_y < to_y then
159                                         -- Moving down
160                                         -- Ensure that no piece disturbs the way
161                                         for i = from_y + 1, to_y - 1 do
162                                                 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
163                                                         moves[to_idx] = nil
164                                                 end
165                                         end
166                                 else
167                                         -- Mocing up
168                                         -- Ensure that no piece disturbs the way
169                                         for i = to_y + 1, from_y - 1 do
170                                                 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
171                                                         moves[to_idx] = nil
172                                                 end
173                                         end
174                                 end
175                         elseif from_y == to_y then
176                                 -- Mocing horizontally
177                                 if from_x < to_x then
178                                         -- mocing right
179                                         -- ensure that no piece disturbs the way
180                                         for i = from_x + 1, to_x - 1 do
181                                                 if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
182                                                         moves[to_idx] = nil
183                                                 end
184                                         end
185                                 else
186                                         -- Mocing left
187                                         -- Ensure that no piece disturbs the way
188                                         for i = to_x + 1, from_x - 1 do
189                                                 if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
190                                                         moves[to_idx] = nil
191                                                 end
192                                         end
193                                 end
194                         else
195                                 -- Attempt to move arbitrarily -> abort
196                                 moves[to_idx] = nil
197                         end
198
199                 -- KNIGHT
200                 elseif piece == "knight" then
201                         -- Get relative pos
202                         local dx = from_x - to_x
203                         local dy = from_y - to_y
204
205                         -- Get absolute values
206                         if dx < 0 then
207                                 dx = -dx
208                         end
209
210                         if dy < 0 then
211                                 dy = -dy
212                         end
213
214                         -- Sort x and y
215                         if dx > dy then
216                                 dx, dy = dy, dx
217                         end
218
219                         -- Ensure that dx == 1 and dy == 2
220                         if dx ~= 1 or dy ~= 2 then
221                                 moves[to_idx] = nil
222                         end
223                         -- Just ensure that destination cell does not contain friend piece
224                         -- ^ It was done already thus everything ok
225
226                 -- BISHOP
227                 elseif piece == "bishop" then
228                         -- Get relative pos
229                         local dx = from_x - to_x
230                         local dy = from_y - to_y
231
232                         -- Get absolute values
233                         if dx < 0 then
234                                 dx = -dx
235                         end
236
237                         if dy < 0 then
238                                 dy = -dy
239                         end
240
241                         -- Ensure dx and dy are equal
242                         if dx ~= dy then
243                                 moves[to_idx] = nil
244                         end
245
246                         if from_x < to_x then
247                                 if from_y < to_y then
248                                         -- Moving right-down
249                                         -- Ensure that no piece disturbs the way
250                                         for i = 1, dx - 1 do
251                                                 if inv:get_stack(
252                                                         "board", xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
253                                                         moves[to_idx] = nil
254                                                 end
255                                         end
256                                 else
257                                         -- Moving right-up
258                                         -- Ensure that no piece disturbs the way
259                                         for i = 1, dx - 1 do
260                                                 if inv:get_stack(
261                                                         "board", xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
262                                                         moves[to_idx] = nil
263                                                 end
264                                         end
265                                 end
266                         else
267                                 if from_y < to_y then
268                                         -- Moving left-down
269                                         -- Ensure that no piece disturbs the way
270                                         for i = 1, dx - 1 do
271                                                 if inv:get_stack(
272                                                         "board", xy_to_index(from_x - i, from_y + i)):get_name() ~= "" 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 inv:get_stack(
281                                                         "board", xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
282                                                         moves[to_idx] = nil
283                                                 end
284                                         end
285                                 end
286                         end
287
288                 -- QUEEN
289                 elseif piece == "queen" then
290                         local dx = from_x - to_x
291                         local dy = from_y - to_y
292
293                         -- Get absolute values
294                         if dx < 0 then
295                                 dx = -dx
296                         end
297
298                         if dy < 0 then
299                                 dy = -dy
300                         end
301
302                         -- Ensure valid relative move
303                         if dx ~= 0 and dy ~= 0 and dx ~= dy then
304                                 moves[to_idx] = nil
305                         end
306
307                         if from_x == to_x then
308                                 -- Moving vertically
309                                 if from_y < to_y then
310                                         -- Moving down
311                                         -- Ensure that no piece disturbs the way
312                                         for i = from_y + 1, to_y - 1 do
313                                                 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
314                                                         moves[to_idx] = nil
315                                                 end
316                                         end
317                                 else
318                                         -- Mocing up
319                                         -- Ensure that no piece disturbs the way
320                                         for i = to_y + 1, from_y - 1 do
321                                                 if inv:get_stack("board", xy_to_index(from_x, i)):get_name() ~= "" then
322                                                         moves[to_idx] = nil
323                                                 end
324                                         end
325                                 end
326                         elseif from_x < to_x then
327                                 if from_y == to_y then
328                                         -- Goes right
329                                         -- Ensure that no piece disturbs the way
330                                         for i = 1, dx - 1 do
331                                                 if inv:get_stack(
332                                                         "board", xy_to_index(from_x + i, from_y)):get_name() ~= "" then
333                                                         moves[to_idx] = nil
334                                                 end
335                                         end
336                                 elseif from_y < to_y then
337                                         -- Goes right-down
338                                         -- Ensure that no piece disturbs the way
339                                         for i = 1, dx - 1 do
340                                                 if inv:get_stack(
341                                                         "board", xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
342                                                         moves[to_idx] = nil
343                                                 end
344                                         end
345                                 else
346                                         -- Goes right-up
347                                         -- Ensure that no piece disturbs the way
348                                         for i = 1, dx - 1 do
349                                                 if inv:get_stack(
350                                                         "board", xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
351                                                         moves[to_idx] = nil
352                                                 end
353                                         end
354                                 end
355                         else
356                                 if from_y == to_y then
357                                         -- Mocing horizontally
358                                         if from_x < to_x then
359                                                 -- mocing right
360                                                 -- ensure that no piece disturbs the way
361                                                 for i = from_x + 1, to_x - 1 do
362                                                         if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
363                                                                 moves[to_idx] = nil
364                                                         end
365                                                 end
366                                         else
367                                                 -- Mocing left
368                                                 -- Ensure that no piece disturbs the way
369                                                 for i = to_x + 1, from_x - 1 do
370                                                         if inv:get_stack("board", xy_to_index(i, from_y)):get_name() ~= "" then
371                                                                 moves[to_idx] = nil
372                                                         end
373                                                 end
374                                         end
375                                 elseif from_y < to_y then
376                                         -- Goes left-down
377                                         -- Ensure that no piece disturbs the way
378                                         for i = 1, dx - 1 do
379                                                 if inv:get_stack(
380                                                         "board", xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
381                                                         moves[to_idx] = nil
382                                                 end
383                                         end
384                                 else
385                                         -- Goes left-up
386                                         -- Ensure that no piece disturbs the way
387                                         for i = 1, dx - 1 do
388                                                 if inv:get_stack(
389                                                         "board", xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
390                                                         moves[to_idx] = nil
391                                                 end
392                                         end
393                                 end
394                         end
395
396                 -- KING
397                 elseif piece == "king" then
398                         local dx = from_x - to_x
399                         local dy = from_y - to_y
400
401                         if dx < 0 then
402                                 dx = -dx
403                         end
404
405                         if dy < 0 then
406                                 dy = -dy
407                         end
408
409                         if dx > 1 or dy > 1 then
410                                 moves[to_idx] = nil
411                         end
412                 end
413         end
414
415         if not next(moves) then return end
416
417         for i in pairs(moves) do
418                 local stack = inv:get_stack("board", tonumber(i))
419                 local stack_name = stack:get_name()
420
421                 if stack_name ~= "" then
422                         for p, value in pairs(piece_values) do
423                                 if stack_name:find(p) then
424                                         moves[i] = value
425                                 end
426                         end
427                 end
428         end
429
430         return moves
431 end
432
433 local function best_move(moves)
434         local value, choices = 0, {}
435
436         for from, _ in pairs(moves) do
437         for to, val in pairs(_) do
438                 if val > value then
439                         value = val
440                         choices = {{
441                                 from = from,
442                                 to = to
443                         }}
444                 elseif val == value then
445                         choices[#choices + 1] = {
446                                 from = from,
447                                 to = to
448                         }
449                 end
450         end
451         end
452
453         local random = math.random(1, #choices)
454         local choice_from, choice_to = choices[random].from, choices[random].to
455
456         return tonumber(choice_from), choice_to
457 end
458
459 local rowDirs = {-1, -1, -1, 0, 0, 1, 1, 1}
460 local colDirs = {-1, 0, 1, -1, 1, -1, 0, 1}
461
462 local rowDirsKnight = { 2,  1, 2, 1, -2, -1, -2, -1}
463 local colDirsKnight = {-1, -2, 1, 2,  1,  2, -1, -2}
464
465 local bishopThreats = {true,  false, true,  false, false, true,  false, true}
466 local rookThreats   = {false, true,  false, true,  true,  false, true,  false}
467 local queenThreats  = {true,  true,  true,  true,  true,  true,  true,  true}
468 local kingThreats   = {true,  true,  true,  true,  true,  true,  true,  true}
469
470 local function attacked(color, idx, board)
471         local threatDetected = false
472         local kill           = color == "white"
473         local pawnThreats    = {kill, false, kill, false, false, not kill, false, not kill}
474
475         for dir = 1, 8 do
476                 if not threatDetected then
477                         local col, row = index_to_xy(idx)
478                         col, row = col + 1, row + 1
479
480                         for step = 1, 8 do
481                                 row = row + rowDirs[dir]
482                                 col = col + colDirs[dir]
483
484                                 if row >= 1 and row <= 8 and col >= 1 and col <= 8 then
485                                         local square            = get_square(row, col)
486                                         local square_name       = board[square]
487                                         local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
488
489                                         if piece then
490                                                 if pieceColor ~= color then
491                                                         if piece == "bishop" and bishopThreats[dir] then
492                                                                 threatDetected = true
493                                                         elseif piece == "rook" and rookThreats[dir] then
494                                                                 threatDetected = true
495                                                         elseif piece == "queen" and queenThreats[dir] then
496                                                                 threatDetected = true
497                                                         else
498                                                                 if step == 1 then
499                                                                         if piece == "pawn" and pawnThreats[dir] then
500                                                                                 threatDetected = true
501                                                                         end
502                                                                         if piece == "king" and kingThreats[dir] then
503                                                                                 threatDetected = true
504                                                                         end
505                                                                 end
506                                                         end
507                                                 end
508                                                 break
509                                         end
510                                 end
511                         end
512
513                         local colK, rowK = index_to_xy(idx)
514                         colK, rowK = colK + 1, rowK + 1
515                         rowK = rowK + rowDirsKnight[dir]
516                         colK = colK + colDirsKnight[dir]
517
518                         if rowK >= 1 and rowK <= 8 and colK >= 1 and colK <= 8 then
519                                 local square            = get_square(rowK, colK)
520                                 local square_name       = board[square]
521                                 local piece, pieceColor = square_name:match(":(%w+)_(%w+)")
522
523                                 if piece and pieceColor ~= color and piece == "knight" then
524                                         threatDetected = true
525                                 end
526                         end
527                 end
528         end
529
530         return threatDetected
531 end
532
533 local function locate_kings(board)
534         local Bidx, Widx
535         for i = 1, 64 do
536                 local piece, color = board[i]:match(":(%w+)_(%w+)")
537                 if piece == "king" then
538                         if color == "black" then
539                                 Bidx = i
540                         else
541                                 Widx = i
542                         end
543                 end
544         end
545
546         return Bidx, Widx
547 end
548
549 local pieces = {
550         "realchess:rook_black_1",
551         "realchess:knight_black_1",
552         "realchess:bishop_black_1",
553         "realchess:queen_black",
554         "realchess:king_black",
555         "realchess:bishop_black_2",
556         "realchess:knight_black_2",
557         "realchess:rook_black_2",
558         "realchess:pawn_black_1",
559         "realchess:pawn_black_2",
560         "realchess:pawn_black_3",
561         "realchess:pawn_black_4",
562         "realchess:pawn_black_5",
563         "realchess:pawn_black_6",
564         "realchess:pawn_black_7",
565         "realchess:pawn_black_8",
566         '','','','','','','','','','','','','','','','',
567         '','','','','','','','','','','','','','','','',
568         "realchess:pawn_white_1",
569         "realchess:pawn_white_2",
570         "realchess:pawn_white_3",
571         "realchess:pawn_white_4",
572         "realchess:pawn_white_5",
573         "realchess:pawn_white_6",
574         "realchess:pawn_white_7",
575         "realchess:pawn_white_8",
576         "realchess:rook_white_1",
577         "realchess:knight_white_1",
578         "realchess:bishop_white_1",
579         "realchess:queen_white",
580         "realchess:king_white",
581         "realchess:bishop_white_2",
582         "realchess:knight_white_2",
583         "realchess:rook_white_2"
584 }
585
586 local pieces_str, x = "", 0
587 for i = 1, #pieces do
588         local p = pieces[i]:match(":(%w+_%w+)")
589         if pieces[i]:find(":(%w+)_(%w+)") and not pieces_str:find(p) then
590                 pieces_str = pieces_str .. x .. "=" .. p .. ".png,"
591                 x = x + 1
592         end
593 end
594 pieces_str = pieces_str .. "69=mailbox_blank16.png"
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)
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
706         inv:set_list("board", pieces)
707         inv:set_size("board", 64)
708 end
709
710 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
711         if from_list ~= "board" and to_list ~= "board" then
712                 return 0
713         end
714
715         local meta        = minetest.get_meta(pos)
716         local playerName  = player:get_player_name()
717         local inv         = meta:get_inventory()
718         local pieceFrom   = inv:get_stack(from_list, from_index):get_name()
719         local pieceTo     = inv:get_stack(to_list, to_index):get_name()
720         local lastMove    = meta:get_string("lastMove")
721         local playerWhite = meta:get_string("playerWhite")
722         local playerBlack = meta:get_string("playerBlack")
723         local thisMove    -- Will replace lastMove when move is legal
724
725         if pieceFrom:find("white") then
726                 if playerWhite ~= "" and playerWhite ~= playerName then
727                         minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays white pieces!")
728                         return 0
729                 end
730
731                 if lastMove ~= "" and lastMove ~= "black" then
732                         return 0
733                 end
734
735                 if pieceTo:find("white") then
736                         -- Don't replace pieces of same color
737                         return 0
738                 end
739
740                 playerWhite = playerName
741                 thisMove = "white"
742
743         elseif pieceFrom:find("black") then
744                 if playerBlack ~= "" and playerBlack ~= playerName then
745                         minetest.chat_send_player(playerName, chat_prefix .. "Someone else plays black pieces!")
746                         return 0
747                 end
748
749                 if lastMove ~= "" and lastMove ~= "white" then
750                         return 0
751                 end
752
753                 if pieceTo:find("black") then
754                         -- Don't replace pieces of same color
755                         return 0
756                 end
757
758                 playerBlack = playerName
759                 thisMove = "black"
760         end
761
762         -- MOVE LOGIC
763
764         local from_x, from_y = index_to_xy(from_index)
765         local to_x, to_y     = index_to_xy(to_index)
766
767         -- PAWN
768         if pieceFrom:sub(11,14) == "pawn" then
769                 if thisMove == "white" then
770                         local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
771                         -- white pawns can go up only
772                         if from_y - 1 == to_y then
773                                 if from_x == to_x then
774                                         if pieceTo ~= "" then
775                                                 return 0
776                                         elseif to_index >= 1 and to_index <= 8 then
777                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
778                                         end
779                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
780                                         if not pieceTo:find("black") then
781                                                 return 0
782                                         elseif to_index >= 1 and to_index <= 8 then
783                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
784                                         end
785                                 else
786                                         return 0
787                                 end
788                         elseif from_y - 2 == to_y then
789                                 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
790                                         return 0
791                                 end
792                         else
793                                 return 0
794                         end
795
796                         --[[
797                              if x not changed
798                                   ensure that destination cell is empty
799                              elseif x changed one unit left or right
800                                   ensure the pawn is killing opponent piece
801                              else
802                                   move is not legal - abort
803                         ]]
804
805                         if from_x == to_x then
806                                 if pieceTo ~= "" then
807                                         return 0
808                                 end
809                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
810                                 if not pieceTo:find("black") then
811                                         return 0
812                                 end
813                         else
814                                 return 0
815                         end
816
817                 elseif thisMove == "black" then
818                         local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
819                         -- black pawns can go down only
820                         if from_y + 1 == to_y then
821                                 if from_x == to_x then
822                                         if pieceTo ~= "" then
823                                                 return 0
824                                         elseif to_index >= 57 and to_index <= 64 then
825                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
826                                         end
827                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
828                                         if not pieceTo:find("white") then
829                                                 return 0
830                                         elseif to_index >= 57 and to_index <= 64 then
831                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
832                                         end
833                                 else
834                                         return 0
835                                 end
836                         elseif from_y + 2 == to_y then
837                                 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
838                                         return 0
839                                 end
840                         else
841                                 return 0
842                         end
843
844                         --[[
845                              if x not changed
846                                   ensure that destination cell is empty
847                              elseif x changed one unit left or right
848                                   ensure the pawn is killing opponent piece
849                              else
850                                   move is not legal - abort
851                         ]]
852
853                         if from_x == to_x then
854                                 if pieceTo ~= "" then
855                                         return 0
856                                 end
857                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
858                                 if not pieceTo:find("white") then
859                                         return 0
860                                 end
861                         else
862                                 return 0
863                         end
864                 else
865                         return 0
866                 end
867
868         -- ROOK
869         elseif pieceFrom:sub(11,14) == "rook" then
870                 if from_x == to_x then
871                         -- Moving vertically
872                         if from_y < to_y then
873                                 -- Moving down
874                                 -- Ensure that no piece disturbs the way
875                                 for i = from_y + 1, to_y - 1 do
876                                         if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
877                                                 return 0
878                                         end
879                                 end
880                         else
881                                 -- Mocing up
882                                 -- Ensure that no piece disturbs the way
883                                 for i = to_y + 1, from_y - 1 do
884                                         if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
885                                                 return 0
886                                         end
887                                 end
888                         end
889                 elseif from_y == to_y then
890                         -- Mocing horizontally
891                         if from_x < to_x then
892                                 -- mocing right
893                                 -- ensure that no piece disturbs the way
894                                 for i = from_x + 1, to_x - 1 do
895                                         if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
896                                                 return 0
897                                         end
898                                 end
899                         else
900                                 -- Mocing left
901                                 -- Ensure that no piece disturbs the way
902                                 for i = to_x + 1, from_x - 1 do
903                                         if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
904                                                 return 0
905                                         end
906                                 end
907                         end
908                 else
909                         -- Attempt to move arbitrarily -> abort
910                         return 0
911                 end
912
913                 if thisMove == "white" or thisMove == "black" then
914                         if pieceFrom:sub(-1) == "1" then
915                                 meta:set_int("castlingWhiteL", 0)
916                         elseif pieceFrom:sub(-1) == "2" then
917                                 meta:set_int("castlingWhiteR", 0)
918                         end
919                 end
920
921         -- KNIGHT
922         elseif pieceFrom:sub(11,16) == "knight" then
923                 -- Get relative pos
924                 local dx = from_x - to_x
925                 local dy = from_y - to_y
926
927                 -- Get absolute values
928                 if dx < 0 then dx = -dx end
929                 if dy < 0 then dy = -dy end
930
931                 -- Sort x and y
932                 if dx > dy then dx, dy = dy, dx end
933
934                 -- Ensure that dx == 1 and dy == 2
935                 if dx ~= 1 or dy ~= 2 then
936                         return 0
937                 end
938                 -- Just ensure that destination cell does not contain friend piece
939                 -- ^ It was done already thus everything ok
940
941         -- BISHOP
942         elseif pieceFrom:sub(11,16) == "bishop" then
943                 -- Get relative pos
944                 local dx = from_x - to_x
945                 local dy = from_y - to_y
946
947                 -- Get absolute values
948                 if dx < 0 then dx = -dx end
949                 if dy < 0 then dy = -dy end
950
951                 -- Ensure dx and dy are equal
952                 if dx ~= dy then return 0 end
953
954                 if from_x < to_x then
955                         if from_y < to_y then
956                                 -- Moving right-down
957                                 -- Ensure that no piece disturbs the way
958                                 for i = 1, dx - 1 do
959                                         if inv:get_stack(
960                                                 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
961                                                 return 0
962                                         end
963                                 end
964                         else
965                                 -- Moving right-up
966                                 -- Ensure that no piece disturbs the way
967                                 for i = 1, dx - 1 do
968                                         if inv:get_stack(
969                                                 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
970                                                 return 0
971                                         end
972                                 end
973                         end
974                 else
975                         if from_y < to_y then
976                                 -- Moving left-down
977                                 -- Ensure that no piece disturbs the way
978                                 for i = 1, dx - 1 do
979                                         if inv:get_stack(
980                                                 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
981                                                 return 0
982                                         end
983                                 end
984                         else
985                                 -- Moving left-up
986                                 -- ensure that no piece disturbs the way
987                                 for i = 1, dx - 1 do
988                                         if inv:get_stack(
989                                                 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
990                                                 return 0
991                                         end
992                                 end
993                         end
994                 end
995
996         -- QUEEN
997         elseif pieceFrom:sub(11,15) == "queen" then
998                 local dx = from_x - to_x
999                 local dy = from_y - to_y
1000
1001                 -- Get absolute values
1002                 if dx < 0 then dx = -dx end
1003                 if dy < 0 then dy = -dy end
1004
1005                 -- Ensure valid relative move
1006                 if dx ~= 0 and dy ~= 0 and dx ~= dy then
1007                         return 0
1008                 end
1009
1010                 if from_x == to_x then
1011                         if from_y < to_y then
1012                                 -- Goes down
1013                                 -- Ensure that no piece disturbs the way
1014                                 for i = 1, dx - 1 do
1015                                         if inv:get_stack(
1016                                                 from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
1017                                                 return 0
1018                                         end
1019                                 end
1020                         else
1021                                 -- Goes up
1022                                 -- Ensure that no piece disturbs the way
1023                                 for i = 1, dx - 1 do
1024                                         if inv:get_stack(
1025                                                 from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
1026                                                 return 0
1027                                         end
1028                                 end
1029                         end
1030                 elseif from_x < to_x then
1031                         if from_y == to_y then
1032                                 -- Goes right
1033                                 -- Ensure that no piece disturbs the way
1034                                 for i = 1, dx - 1 do
1035                                         if inv:get_stack(
1036                                                 from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
1037                                                 return 0
1038                                         end
1039                                 end
1040                         elseif from_y < to_y then
1041                                 -- Goes right-down
1042                                 -- Ensure that no piece disturbs the way
1043                                 for i = 1, dx - 1 do
1044                                         if inv:get_stack(
1045                                                 from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
1046                                                 return 0
1047                                         end
1048                                 end
1049                         else
1050                                 -- Goes right-up
1051                                 -- Ensure that no piece disturbs the way
1052                                 for i = 1, dx - 1 do
1053                                         if inv:get_stack(
1054                                                 from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
1055                                                 return 0
1056                                         end
1057                                 end
1058                         end
1059                 else
1060                         if from_y == to_y then
1061                                 -- Goes left
1062                                 -- Ensure that no piece disturbs the way and destination cell does
1063                                 for i = 1, dx - 1 do
1064                                         if inv:get_stack(
1065                                                 from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
1066                                                 return 0
1067                                         end
1068                                 end
1069                         elseif from_y < to_y then
1070                                 -- Goes left-down
1071                                 -- Ensure that no piece disturbs the way
1072                                 for i = 1, dx - 1 do
1073                                         if inv:get_stack(
1074                                                 from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
1075                                                 return 0
1076                                         end
1077                                 end
1078                         else
1079                                 -- Goes left-up
1080                                 -- Ensure that no piece disturbs the way
1081                                 for i = 1, dx - 1 do
1082                                         if inv:get_stack(
1083                                                 from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
1084                                                 return 0
1085                                         end
1086                                 end
1087                         end
1088                 end
1089
1090         -- KING
1091         elseif pieceFrom:sub(11,14) == "king" then
1092                 local dx = from_x - to_x
1093                 local dy = from_y - to_y
1094                 local check = true
1095
1096                 if thisMove == "white" then
1097                         if from_y == 7 and to_y == 7 then
1098                                 if to_x == 1 then
1099                                         local castlingWhiteL = meta:get_int("castlingWhiteL")
1100                                         local idx57 = inv:get_stack(from_list, 57):get_name()
1101
1102                                         if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
1103                                                 for i = 58, from_index - 1 do
1104                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
1105                                                                 return 0
1106                                                         end
1107                                                 end
1108
1109                                                 inv:set_stack(from_list, 57, "")
1110                                                 inv:set_stack(from_list, 59, "realchess:rook_white_1")
1111                                                 check = false
1112                                         end
1113                                 elseif to_x == 6 then
1114                                         local castlingWhiteR = meta:get_int("castlingWhiteR")
1115                                         local idx64 = inv:get_stack(from_list, 64):get_name()
1116
1117                                         if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
1118                                                 for i = from_index + 1, 63 do
1119                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
1120                                                                 return 0
1121                                                         end
1122                                                 end
1123
1124                                                 inv:set_stack(from_list, 62, "realchess:rook_white_2")
1125                                                 inv:set_stack(from_list, 64, "")
1126                                                 check = false
1127                                         end
1128                                 end
1129                         end
1130                 elseif thisMove == "black" then
1131                         if from_y == 0 and to_y == 0 then
1132                                 if to_x == 1 then
1133                                         local castlingBlackL = meta:get_int("castlingBlackL")
1134                                         local idx1 = inv:get_stack(from_list, 1):get_name()
1135
1136                                         if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
1137                                                 for i = 2, from_index - 1 do
1138                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
1139                                                                 return 0
1140                                                         end
1141                                                 end
1142
1143                                                 inv:set_stack(from_list, 1, "")
1144                                                 inv:set_stack(from_list, 3, "realchess:rook_black_1")
1145                                                 check = false
1146                                         end
1147                                 elseif to_x == 6 then
1148                                         local castlingBlackR = meta:get_int("castlingBlackR")
1149                                         local idx8 = inv:get_stack(from_list, 1):get_name()
1150
1151                                         if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
1152                                                 for i = from_index + 1, 7 do
1153                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
1154                                                                 return 0
1155                                                         end
1156                                                 end
1157
1158                                                 inv:set_stack(from_list, 6, "realchess:rook_black_2")
1159                                                 inv:set_stack(from_list, 8, "")
1160                                                 check = false
1161                                         end
1162                                 end
1163                         end
1164                 end
1165
1166                 if check then
1167                         if dx < 0 then
1168                                 dx = -dx
1169                         end
1170
1171                         if dy < 0 then
1172                                 dy = -dy
1173                         end
1174
1175                         if dx > 1 or dy > 1 then
1176                                 return 0
1177                         end
1178                 end
1179
1180                 if thisMove == "white" then
1181                         meta:set_int("castlingWhiteL", 0)
1182                         meta:set_int("castlingWhiteR", 0)
1183
1184                 elseif thisMove == "black" then
1185                         meta:set_int("castlingBlackL", 0)
1186                         meta:set_int("castlingBlackR", 0)
1187                 end
1188         end
1189
1190         local board       = board_to_table(inv)
1191         board[to_index]   = board[from_index]
1192         board[from_index] = ""
1193
1194         local black_king_idx, white_king_idx = locate_kings(board)
1195         local blackAttacked = attacked("black", black_king_idx, board)
1196         local whiteAttacked = attacked("white", white_king_idx, board)
1197
1198         if blackAttacked then
1199                 if thisMove == "black" then
1200                         --[(*)[ and meta:get_string("blackAttacked") == "true" ]] then
1201                         return 0
1202                 else
1203                         meta:set_string("blackAttacked", "true")
1204                 end
1205         else
1206                 meta:set_string("blackAttacked", "")
1207         end
1208
1209         if whiteAttacked then
1210                 if thisMove == "white" then
1211                         --[(*)[ and meta:get_string("whiteAttacked") == "true" ]] then
1212                         return 0
1213                 else
1214                         meta:set_string("whiteAttacked", "true")
1215                 end
1216         else
1217                 meta:set_string("whiteAttacked", "")
1218         end
1219
1220         --(*) Allow a piece to move and put its king in check. Maybe not in the chess rules though?
1221
1222         lastMove = thisMove
1223         meta:set_string("lastMove", lastMove)
1224         meta:set_int("lastMoveTime", minetest.get_gametime())
1225
1226         if meta:get_string("playerWhite") == "" then
1227                 meta:set_string("playerWhite", playerWhite)
1228         elseif meta:get_string("playerBlack") == "" then
1229                 meta:set_string("playerBlack", playerBlack)
1230         end
1231
1232         local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
1233         get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, from_index, to_index)
1234         get_eaten_list(meta, pieceTo, pieceTo_s)
1235
1236         --print("from_index: " .. from_index)
1237         --print("to_index: " .. to_index)
1238
1239         return 1
1240 end
1241
1242 function realchess.on_move(pos, from_list, from_index)
1243         local meta = minetest.get_meta(pos)
1244         local inv  = meta:get_inventory()
1245         inv:set_stack(from_list, from_index, "")
1246
1247         local lastMove = meta:get_string("lastMove")
1248         if lastMove == "white" then
1249                 update_formspec(meta)
1250                 local moves = {}
1251
1252                 for i = 1, 64 do
1253                         local possibleMoves = get_possible_moves(inv, i)
1254                         local stack_name    = inv:get_stack("board", i):get_name()
1255
1256                         if stack_name:find("black") then
1257                                 moves[tostring(i)] = possibleMoves
1258                         end
1259                 end
1260
1261                 --minetest.log("warning", "moves: " .. dump(moves))
1262
1263                 local choice_from, choice_to = best_move(moves)
1264                 local pieceFrom = inv:get_stack("board", choice_from):get_name()
1265                 local pieceTo   = inv:get_stack("board", choice_to):get_name()
1266                 local pieceTo_s = pieceTo ~= "" and pieceTo:match(":(%w+_%w+)") or ""
1267
1268                 local board          = board_to_table(inv)
1269                 local black_king_idx = locate_kings(board)
1270                 local blackAttacked  = attacked("black", black_king_idx, board)
1271                 local kingSafe       = true
1272                 local bestMoveSaveFrom, bestMoveSaveTo
1273
1274                 if blackAttacked then
1275                         kingSafe = false
1276                         meta:set_string("blackAttacked", "true")
1277                         local save_moves = {}
1278
1279                         for from_idx, _ in pairs(moves) do
1280                         for to_idx, value in pairs(_) do
1281                                 from_idx = tonumber(from_idx)
1282                                 local from_idx_bak, to_idx_bak = board[from_idx], board[to_idx]
1283                                 board[to_idx]   = board[from_idx]
1284                                 board[from_idx] = ""
1285                                 black_king_idx  = locate_kings(board)
1286
1287                                 if black_king_idx then
1288                                         blackAttacked = attacked("black", black_king_idx, board)
1289                                         if not blackAttacked then
1290                                                 save_moves[from_idx] = save_moves[from_idx] or {}
1291                                                 save_moves[from_idx][to_idx] = value
1292                                         end
1293                                 end
1294
1295                                 board[from_idx], board[to_idx] = from_idx_bak, to_idx_bak
1296                         end
1297                         end
1298
1299                         if next(save_moves) then
1300                                 bestMoveSaveFrom, bestMoveSaveTo = best_move(save_moves)
1301                         end
1302                 end
1303
1304                 minetest.after(1.0, function()
1305                         local lastMoveTime = meta:get_int("lastMoveTime")
1306                         if lastMoveTime > 0 then
1307                                 if not kingSafe then
1308                                         if bestMoveSaveTo then
1309                                                 inv:set_stack("board", bestMoveSaveTo, board[bestMoveSaveFrom])
1310                                                 inv:set_stack("board", bestMoveSaveFrom, "")
1311                                                 meta:set_string("blackAttacked", "")
1312                                         else
1313                                                 return
1314                                         end
1315                                 else
1316                                         inv:set_stack("board", choice_to, pieceFrom)
1317                                         inv:set_stack("board", choice_from, "")
1318                                 end
1319
1320                                 board = board_to_table(inv)
1321                                 local _, white_king_idx = locate_kings(board)
1322                                 local whiteAttacked = attacked("white", white_king_idx, board)
1323
1324                                 if whiteAttacked then
1325                                         meta:set_string("whiteAttacked", "true")
1326                                 end
1327
1328                                 if meta:get_string("playerBlack") == "" then
1329                                         meta:set_string("playerBlack", "Dumb AI")
1330                                 end
1331
1332                                 meta:set_string("lastMove", "black")
1333                                 meta:set_int("lastMoveTime", minetest.get_gametime())
1334
1335                                 get_moves_list(meta, pieceFrom, pieceTo, pieceTo_s, choice_from, choice_to)
1336                                 get_eaten_list(meta, pieceTo, pieceTo_s)
1337
1338                                 update_formspec(meta)
1339                         end
1340                 end)
1341         else
1342                 update_formspec(meta)
1343         end
1344
1345         return false
1346 end
1347
1348 local function timeout_format(timeout_limit)
1349         local time_remaining = timeout_limit - minetest.get_gametime()
1350         local minutes        = math.floor(time_remaining / 60)
1351         local seconds        = time_remaining % 60
1352
1353         if minutes == 0 then
1354                 return seconds .. " sec."
1355         end
1356
1357         return minutes .. " min. " .. seconds .. " sec."
1358 end
1359
1360 function realchess.fields(pos, _, fields, sender)
1361         local playerName    = sender:get_player_name()
1362         local meta          = minetest.get_meta(pos)
1363         local timeout_limit = meta:get_int("lastMoveTime") + 300
1364         local playerWhite   = meta:get_string("playerWhite")
1365         local playerBlack   = meta:get_string("playerBlack")
1366         local lastMoveTime  = meta:get_int("lastMoveTime")
1367         if fields.quit then return end
1368
1369         -- Timeout is 5 min. by default for resetting the game (non-players only)
1370         if fields.new then
1371                 if (playerWhite == playerName or playerBlack == playerName) then
1372                         realchess.init(pos)
1373
1374                 elseif lastMoveTime > 0 then
1375                         if minetest.get_gametime() >= timeout_limit and
1376                                         (playerWhite ~= playerName or playerBlack ~= playerName) then
1377                                 realchess.init(pos)
1378                         else
1379                                 minetest.chat_send_player(playerName, chat_prefix ..
1380                                         "You can't reset the chessboard, a game has been started. " ..
1381                                         "If you aren't a current player, try again in " ..
1382                                         timeout_format(timeout_limit))
1383                         end
1384                 end
1385         end
1386 end
1387
1388 function realchess.dig(pos, player)
1389         if not player then
1390                 return false
1391         end
1392
1393         local meta          = minetest.get_meta(pos)
1394         local playerName    = player:get_player_name()
1395         local timeout_limit = meta:get_int("lastMoveTime") + 300
1396         local lastMoveTime  = meta:get_int("lastMoveTime")
1397
1398         -- Timeout is 5 min. by default for digging the chessboard (non-players only)
1399         return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
1400                 minetest.chat_send_player(playerName, chat_prefix ..
1401                                 "You can't dig the chessboard, a game has been started. " ..
1402                                 "Reset it first if you're a current player, or dig it again in " ..
1403                                 timeout_format(timeout_limit))
1404 end
1405
1406 minetest.register_node(":realchess:chessboard", {
1407         description = "Chess Board",
1408         drawtype = "nodebox",
1409         paramtype = "light",
1410         paramtype2 = "facedir",
1411         inventory_image = "chessboard_top.png",
1412         wield_image = "chessboard_top.png",
1413         tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
1414         groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
1415         sounds = default.node_sound_wood_defaults(),
1416         node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
1417         sunlight_propagates = true,
1418         on_rotate = screwdriver.rotate_simple,
1419         can_dig = realchess.dig,
1420         on_construct = realchess.init,
1421         on_receive_fields = realchess.fields,
1422         allow_metadata_inventory_move = realchess.move,
1423         on_metadata_inventory_move = realchess.on_move,
1424         allow_metadata_inventory_take = function() return 0 end
1425 })
1426
1427 local function register_piece(name, count)
1428         for _, color in pairs({"black", "white"}) do
1429         if not count then
1430                 minetest.register_craftitem(":realchess:" .. name .. "_" .. color, {
1431                         description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
1432                         inventory_image = name .. "_" .. color .. ".png",
1433                         stack_max = 1,
1434                         groups = {not_in_creative_inventory=1}
1435                 })
1436         else
1437                 for i = 1, count do
1438                         minetest.register_craftitem(":realchess:" .. name .. "_" .. color .. "_" .. i, {
1439                                 description = color:gsub("^%l", string.upper) .. " " .. name:gsub("^%l", string.upper),
1440                                 inventory_image = name .. "_" .. color .. ".png",
1441                                 stack_max = 1,
1442                                 groups = {not_in_creative_inventory=1}
1443                         })
1444                 end
1445         end
1446         end
1447 end
1448
1449 register_piece("pawn", 8)
1450 register_piece("rook", 2)
1451 register_piece("knight", 2)
1452 register_piece("bishop", 2)
1453 register_piece("queen")
1454 register_piece("king")
1455
1456 -- Recipes
1457
1458 minetest.register_craft({
1459         output = "realchess:chessboard",
1460         recipe = {
1461                 {"dye:black", "dye:white", "dye:black"},
1462                 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}
1463         }
1464 })