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