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