]> git.lizzy.rs Git - xdecor.git/blob - src/chess.lua
White pawn fix
[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 function realchess.init(pos)
16         local meta = minetest.get_meta(pos)
17         local inv = meta:get_inventory()
18
19         local formspec = [[ size[8,8.6;]
20                         bgcolor[#080808BB;true]
21                         background[0,0;8,8;chess_bg.png]
22                         button[3.1,7.8;2,2;new;New game]
23                         list[context;board;0,0;8,8;]
24                         listcolors[#00000000;#00000000;#00000000;#30434C;#FFF] ]]
25
26         meta:set_string("formspec", formspec)
27         meta:set_string("infotext", "Chess Board")
28         meta:set_string("playerBlack", "")
29         meta:set_string("playerWhite", "")
30         meta:set_string("lastMove", "")
31         meta:set_string("winner", "")
32
33         meta:set_int("lastMoveTime", 0)
34         meta:set_int("castlingBlackL", 1)
35         meta:set_int("castlingBlackR", 1)
36         meta:set_int("castlingWhiteL", 1)
37         meta:set_int("castlingWhiteR", 1)
38
39         inv:set_list("board", {
40                 "realchess:rook_black_1",
41                 "realchess:knight_black_1",
42                 "realchess:bishop_black_1",
43                 "realchess:queen_black",
44                 "realchess:king_black",
45                 "realchess:bishop_black_2",
46                 "realchess:knight_black_2",
47                 "realchess:rook_black_2",
48                 "realchess:pawn_black_1",
49                 "realchess:pawn_black_2",
50                 "realchess:pawn_black_3",
51                 "realchess:pawn_black_4",
52                 "realchess:pawn_black_5",
53                 "realchess:pawn_black_6",
54                 "realchess:pawn_black_7",
55                 "realchess:pawn_black_8",
56                 '','','','','','','','','','','','','','','','',
57                 '','','','','','','','','','','','','','','','',
58                 "realchess:pawn_white_1",
59                 "realchess:pawn_white_2",
60                 "realchess:pawn_white_3",
61                 "realchess:pawn_white_4",
62                 "realchess:pawn_white_5",
63                 "realchess:pawn_white_6",
64                 "realchess:pawn_white_7",
65                 "realchess:pawn_white_8",
66                 "realchess:rook_white_1",
67                 "realchess:knight_white_1",
68                 "realchess:bishop_white_1",
69                 "realchess:queen_white",
70                 "realchess:king_white",
71                 "realchess:bishop_white_2",
72                 "realchess:knight_white_2",
73                 "realchess:rook_white_2"
74         })
75
76         inv:set_size("board", 64)
77 end
78
79 function realchess.move(pos, from_list, from_index, to_list, to_index, _, player)
80         if from_list ~= "board" and to_list ~= "board" then
81                 return 0
82         end
83
84         local playerName = player:get_player_name()
85         local meta = minetest.get_meta(pos)
86
87         if meta:get_string("winner") ~= "" then
88                 minetest.chat_send_player(playerName, "This game is over.")
89                 return 0
90         end
91
92         local inv = meta:get_inventory()
93         local pieceFrom = inv:get_stack(from_list, from_index):get_name()
94         local pieceTo = inv:get_stack(to_list, to_index):get_name()
95         local lastMove = meta:get_string("lastMove")
96         local thisMove -- will replace lastMove when move is legal
97         local playerWhite = meta:get_string("playerWhite")
98         local playerBlack = meta:get_string("playerBlack")
99
100         if pieceFrom:find("white") then
101                 if playerWhite ~= "" and playerWhite ~= playerName then
102                         minetest.chat_send_player(playerName, "Someone else plays white pieces!")
103                         return 0
104                 end
105                 if lastMove ~= "" and lastMove ~= "black" then
106                         minetest.chat_send_player(playerName, "It's not your turn, wait for your opponent to play.")
107                         return 0
108                 end
109                 if pieceTo:find("white") then
110                         -- Don't replace pieces of same color
111                         return 0
112                 end
113                 playerWhite = playerName
114                 thisMove = "white"
115         elseif pieceFrom:find("black") then
116                 if playerBlack ~= "" and playerBlack ~= playerName then
117                         minetest.chat_send_player(playerName, "Someone else plays black pieces!")
118                         return 0
119                 end
120                 if lastMove ~= "" and lastMove ~= "white" then
121                         minetest.chat_send_player(playerName, "It's not your turn, wait for your opponent to play.")
122                         return 0
123                 end
124                 if pieceTo:find("black") then
125                         -- Don't replace pieces of same color
126                         return 0
127                 end
128                 playerBlack = playerName
129                 thisMove = "black"
130         end
131
132         -- DETERMINISTIC MOVING
133
134         local from_x, from_y = index_to_xy(from_index)
135         local to_x, to_y = index_to_xy(to_index)
136
137         if pieceFrom:sub(11,14) == "pawn" then
138                 if thisMove == "white" then
139                         local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
140                         -- white pawns can go up only
141                         if from_y - 1 == to_y then
142                                 if from_x == to_x then
143                                         if pieceTo ~= "" then
144                                                 return 0
145                                         elseif to_index >= 1 and to_index <= 8 then
146                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
147                                         end
148                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
149                                         if not pieceTo:find("black") then
150                                                 return 0
151                                         elseif to_index >= 1 and to_index <= 8 then
152                                                 inv:set_stack(from_list, from_index, "realchess:queen_white")
153                                         end
154                                 else
155                                         return 0
156                                 end
157                         elseif from_y - 2 == to_y then
158                                 if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
159                                         return 0
160                                 end
161                         else
162                                 return 0
163                         end
164
165                         -- if x not changed,
166                         --   ensure that destination cell is empty
167                         -- elseif x changed one unit left or right
168                         --   ensure the pawn is killing opponent piece
169                         -- else
170                         --   move is not legal - abort
171
172                         if from_x == to_x then
173                                 if pieceTo ~= "" then
174                                         return 0
175                                 end
176                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
177                                 if not pieceTo:find("black") then
178                                         return 0
179                                 end
180                         else
181                                 return 0
182                         end
183
184                 elseif thisMove == "black" then
185                         local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
186                         -- black pawns can go down only
187                         if from_y + 1 == to_y then
188                                 if from_x == to_x then
189                                         if pieceTo ~= "" then
190                                                 return 0
191                                         elseif to_index >= 57 and to_index <= 64 then
192                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
193                                         end
194                                 elseif from_x - 1 == to_x or from_x + 1 == to_x then
195                                         if not pieceTo:find("white") then
196                                                 return 0
197                                         elseif to_index >= 57 and to_index <= 64 then
198                                                 inv:set_stack(from_list, from_index, "realchess:queen_black")
199                                         end
200                                 else
201                                         return 0
202                                 end
203                         elseif from_y + 2 == to_y then
204                                 if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
205                                         return 0
206                                 end
207                         else
208                                 return 0
209                         end
210
211                         -- if x not changed,
212                         --   ensure that destination cell is empty
213                         -- elseif x changed one unit left or right
214                         --   ensure the pawn is killing opponent piece
215                         -- else
216                         --   move is not legal - abort
217
218                         if from_x == to_x then
219                                 if pieceTo ~= "" then
220                                         return 0
221                                 end
222                         elseif from_x - 1 == to_x or from_x + 1 == to_x then
223                                 if not pieceTo:find("white") then
224                                         return 0
225                                 end
226                         else
227                                 return 0
228                         end
229                 else
230                         return 0
231                 end
232
233         elseif pieceFrom:sub(11,14) == "rook" then
234                 if from_x == to_x then
235                         -- moving vertically
236                         if from_y < to_y then
237                                 -- moving down
238                                 -- ensure that no piece disturbs the way
239                                 for i = from_y + 1, to_y - 1 do
240                                         if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
241                                                 return 0
242                                         end
243                                 end
244                         else
245                                 -- mocing up
246                                 -- ensure that no piece disturbs the way
247                                 for i = to_y + 1, from_y - 1 do
248                                         if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
249                                                 return 0
250                                         end
251                                 end
252                         end
253                 elseif from_y == to_y then
254                         -- mocing horizontally
255                         if from_x < to_x then
256                                 -- mocing right
257                                 -- ensure that no piece disturbs the way
258                                 for i = from_x + 1, to_x - 1 do
259                                         if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
260                                                 return 0
261                                         end
262                                 end
263                         else
264                                 -- mocing left
265                                 -- ensure that no piece disturbs the way
266                                 for i = to_x + 1, from_x - 1 do
267                                         if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
268                                                 return 0
269                                         end
270                                 end
271                         end
272                 else
273                         -- attempt to move arbitrarily -> abort
274                         return 0
275                 end
276
277                 if thisMove == "white" or thisMove == "black" then
278                         if pieceFrom:sub(-1) == "1" then
279                                 meta:set_int("castlingWhiteL", 0)
280                         elseif pieceFrom:sub(-1) == "2" then
281                                 meta:set_int("castlingWhiteR", 0)
282                         end
283                 end
284
285         elseif pieceFrom:sub(11,16) == "knight" then
286                 -- get relative pos
287                 local dx = from_x - to_x
288                 local dy = from_y - to_y
289
290                 -- get absolute values
291                 if dx < 0 then dx = -dx end
292                 if dy < 0 then dy = -dy end
293
294                 -- sort x and y
295                 if dx > dy then dx, dy = dy, dx end
296
297                 -- ensure that dx == 1 and dy == 2
298                 if dx ~= 1 or dy ~= 2 then
299                         return 0
300                 end
301                 -- just ensure that destination cell does not contain friend piece
302                 -- ^ it was done already thus everything ok
303
304         elseif pieceFrom:sub(11,16) == "bishop" then
305                 -- get relative pos
306                 local dx = from_x - to_x
307                 local dy = from_y - to_y
308
309                 -- get absolute values
310                 if dx < 0 then dx = -dx end
311                 if dy < 0 then dy = -dy end
312
313                 -- ensure dx and dy are equal
314                 if dx ~= dy then return 0 end
315
316                 if from_x < to_x then
317                         if from_y < to_y then
318                                 -- moving right-down
319                                 -- ensure that no piece disturbs the way
320                                 for i = 1, dx - 1 do
321                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
322                                                 return 0
323                                         end
324                                 end
325                         else
326                                 -- moving right-up
327                                 -- ensure that no piece disturbs the way
328                                 for i = 1, dx - 1 do
329                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
330                                                 return 0
331                                         end
332                                 end
333                         end
334                 else
335                         if from_y < to_y then
336                                 -- moving left-down
337                                 -- ensure that no piece disturbs the way
338                                 for i = 1, dx - 1 do
339                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
340                                                 return 0
341                                         end
342                                 end
343                         else
344                                 -- moving left-up
345                                 -- ensure that no piece disturbs the way
346                                 for i = 1, dx - 1 do
347                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
348                                                 return 0
349                                         end
350                                 end
351                         end
352                 end
353
354         elseif pieceFrom:sub(11,15) == "queen" then
355                 local dx = from_x - to_x
356                 local dy = from_y - to_y
357
358                 -- get absolute values
359                 if dx < 0 then dx = -dx end
360                 if dy < 0 then dy = -dy end
361
362                 -- ensure valid relative move
363                 if dx ~= 0 and dy ~= 0 and dx ~= dy then
364                         return 0
365                 end
366
367                 if from_x == to_x then
368                         if from_y < to_y then
369                                 -- goes down
370                                 -- ensure that no piece disturbs the way
371                                 for i = 1, dx - 1 do
372                                         if inv:get_stack(from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
373                                                 return 0
374                                         end
375                                 end
376                         else
377                                 -- goes up
378                                 -- ensure that no piece disturbs the way
379                                 for i = 1, dx - 1 do
380                                         if inv:get_stack(from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
381                                                 return 0
382                                         end
383                                 end
384                         end
385                 elseif from_x < to_x then
386                         if from_y == to_y then
387                                 -- goes right
388                                 -- ensure that no piece disturbs the way
389                                 for i = 1, dx - 1 do
390                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
391                                                 return 0
392                                         end
393                                 end
394                         elseif from_y < to_y then
395                                 -- goes right-down
396                                 -- ensure that no piece disturbs the way
397                                 for i = 1, dx - 1 do
398                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
399                                                 return 0
400                                         end
401                                 end
402                         else
403                                 -- goes right-up
404                                 -- ensure that no piece disturbs the way
405                                 for i = 1, dx - 1 do
406                                         if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
407                                                 return 0
408                                         end
409                                 end
410                         end
411                 else
412                         if from_y == to_y then
413                                 -- goes left
414                                 -- ensure that no piece disturbs the way and destination cell does
415                                 for i = 1, dx - 1 do
416                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
417                                                 return 0
418                                         end
419                                 end
420                         elseif from_y < to_y then
421                                 -- goes left-down
422                                 -- ensure that no piece disturbs the way
423                                 for i = 1, dx - 1 do
424                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
425                                                 return 0
426                                         end
427                                 end
428                         else
429                                 -- goes left-up
430                                 -- ensure that no piece disturbs the way
431                                 for i = 1, dx - 1 do
432                                         if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
433                                                 return 0
434                                         end
435                                 end
436                         end
437                 end
438
439         elseif pieceFrom:sub(11,14) == "king" then
440                 local dx = from_x - to_x
441                 local dy = from_y - to_y
442                 local check = true
443
444                 if thisMove == "white" then
445                         if from_y == 7 and to_y == 7 then
446                                 if to_x == 1 then
447                                         local castlingWhiteL = meta:get_int("castlingWhiteL")
448                                         local idx57 = inv:get_stack(from_list, 57):get_name()
449
450                                         if castlingWhiteL == 1 and idx57 == "realchess:rook_white_1" then
451                                                 for i = 58, from_index - 1 do
452                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
453                                                                 return 0
454                                                         end
455                                                 end
456                                                 inv:set_stack(from_list, 57, "")
457                                                 inv:set_stack(from_list, 59, "realchess:rook_white_1")
458                                                 check = false
459                                         end
460                                 elseif to_x == 6 then
461                                         local castlingWhiteR = meta:get_int("castlingWhiteR")
462                                         local idx64 = inv:get_stack(from_list, 64):get_name()
463
464                                         if castlingWhiteR == 1 and idx64 == "realchess:rook_white_2" then
465                                                 for i = from_index + 1, 63 do
466                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
467                                                                 return 0
468                                                         end
469                                                 end
470                                                 inv:set_stack(from_list, 62, "realchess:rook_white_2")
471                                                 inv:set_stack(from_list, 64, "")
472                                                 check = false
473                                         end
474                                 end
475                         end
476                 elseif thisMove == "black" then
477                         if from_y == 0 and to_y == 0 then
478                                 if to_x == 1 then
479                                         local castlingBlackL = meta:get_int("castlingBlackL")
480                                         local idx1 = inv:get_stack(from_list, 1):get_name()
481
482                                         if castlingBlackL == 1 and idx1 == "realchess:rook_black_1" then
483                                                 for i = 2, from_index - 1 do
484                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
485                                                                 return 0
486                                                         end
487                                                 end
488                                                 inv:set_stack(from_list, 1, "")
489                                                 inv:set_stack(from_list, 3, "realchess:rook_black_1")
490                                                 check = false
491                                         end
492                                 elseif to_x == 6 then
493                                         local castlingBlackR = meta:get_int("castlingBlackR")
494                                         local idx8 = inv:get_stack(from_list, 1):get_name()
495
496                                         if castlingBlackR == 1 and idx8 == "realchess:rook_black_2" then
497                                                 for i = from_index + 1, 7 do
498                                                         if inv:get_stack(from_list, i):get_name() ~= "" then
499                                                                 return 0
500                                                         end
501                                                 end
502                                                 inv:set_stack(from_list, 6, "realchess:rook_black_2")
503                                                 inv:set_stack(from_list, 8, "")
504                                                 check = false
505                                         end
506                                 end
507                         end
508                 end
509
510                 if check then
511                         if dx < 0 then dx = -dx end
512                         if dy < 0 then dy = -dy end
513                         if dx > 1 or dy > 1 then return 0 end
514                 end
515
516                 if thisMove == "white" then
517                         meta:set_int("castlingWhiteL", 0)
518                         meta:set_int("castlingWhiteR", 0)
519                 elseif thisMove == "black" then
520                         meta:set_int("castlingBlackL", 0)
521                         meta:set_int("castlingBlackR", 0)
522                 end
523         end
524
525         meta:set_string("playerWhite", playerWhite)
526         meta:set_string("playerBlack", playerBlack)
527         lastMove = thisMove
528         meta:set_string("lastMove", lastMove)
529         meta:set_int("lastMoveTime", minetest.get_gametime())
530
531         if lastMove == "black" then
532                 minetest.chat_send_player(playerWhite, "["..os.date("%H:%M:%S").."] "..
533                                 playerName.." moved a "..pieceFrom:match(":(%a+)")..", it's now your turn.")
534         elseif lastMove == "white" then
535                 minetest.chat_send_player(playerBlack, "["..os.date("%H:%M:%S").."] "..
536                                 playerName.." moved a "..pieceFrom:match(":(%a+)")..", it's now your turn.")
537         end
538
539         if pieceTo:sub(11,14) == "king" then
540                 minetest.chat_send_player(playerBlack, playerName.." won the game.")
541                 minetest.chat_send_player(playerWhite, playerName.." won the game.")
542                 meta:set_string("winner", thisMove)
543         end
544
545         return 1
546 end
547
548 local function timeout_format(timeout_limit)
549         local time_remaining = timeout_limit - minetest.get_gametime()
550         local minutes = math.floor(time_remaining / 60)
551         local seconds = time_remaining % 60
552
553         if minutes == 0 then return seconds.." sec." end
554         return minutes.." min. "..seconds.." sec."
555 end
556
557 function realchess.fields(pos, _, fields, sender)
558         local playerName = sender:get_player_name()
559         local meta = minetest.get_meta(pos)
560         local timeout_limit = meta:get_int("lastMoveTime") + 300
561         local playerWhite = meta:get_string("playerWhite")
562         local playerBlack = meta:get_string("playerBlack")
563         local lastMoveTime = meta:get_int("lastMoveTime")
564         if fields.quit then return end
565
566         -- timeout is 5 min. by default for resetting the game (non-players only)
567         if fields.new and (playerWhite == playerName or playerBlack == playerName) then
568                 realchess.init(pos)
569         elseif fields.new and lastMoveTime ~= 0 and minetest.get_gametime() >= timeout_limit and
570                         (playerWhite ~= playerName or playerBlack ~= playerName) then
571                 realchess.init(pos)
572         else
573                 minetest.chat_send_player(playerName, "[!] You can't reset the chessboard, a game has been started.\n"..
574                                 "If you are not a current player, try again in "..timeout_format(timeout_limit))
575         end
576 end
577
578 function realchess.dig(pos, player)
579         if not player then
580                 return false
581         end
582         local meta = minetest.get_meta(pos)
583         local playerName = player:get_player_name()
584         local timeout_limit = meta:get_int("lastMoveTime") + 300
585         local lastMoveTime = meta:get_int("lastMoveTime")
586
587         -- timeout is 5 min. by default for digging the chessboard (non-players only)
588         return (lastMoveTime == 0 and minetest.get_gametime() > timeout_limit) or
589                 minetest.chat_send_player(playerName, "[!] You can't dig the chessboard, a game has been started.\n"..
590                                 "Reset it first if you're a current player, or dig again in "..timeout_format(timeout_limit))
591 end
592
593 function realchess.on_move(pos, from_list, from_index)
594         local inv = minetest.get_meta(pos):get_inventory()
595         inv:set_stack(from_list, from_index, '')
596         return false
597 end
598
599 minetest.register_node(":realchess:chessboard", {
600         description = "Chess Board",
601         drawtype = "nodebox",
602         paramtype = "light",
603         paramtype2 = "facedir",
604         inventory_image = "chessboard_top.png",
605         wield_image = "chessboard_top.png",
606         tiles = {"chessboard_top.png", "chessboard_top.png", "chessboard_sides.png"},
607         groups = {choppy=3, oddly_breakable_by_hand=2, flammable=3},
608         sounds = default.node_sound_wood_defaults(),
609         node_box = {type = "fixed", fixed = {-.375, -.5, -.375, .375, -.4375, .375}},
610         sunlight_propagates = true,
611         on_rotate = screwdriver.rotate_simple,
612         can_dig = realchess.dig,
613         on_construct = realchess.init,
614         on_receive_fields = realchess.fields,
615         allow_metadata_inventory_move = realchess.move,
616         on_metadata_inventory_move = realchess.on_move,
617         allow_metadata_inventory_take = function() return 0 end
618 })
619
620 local function register_piece(name, count)
621         for _, color in pairs({"black", "white"}) do
622         if not count then
623                 minetest.register_craftitem(":realchess:"..name.."_"..color, {
624                         description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
625                         inventory_image = name.."_"..color..".png",
626                         stack_max = 1,
627                         groups = {not_in_creative_inventory=1}
628                 })
629         else
630                 for i = 1, count do
631                         minetest.register_craftitem(":realchess:"..name.."_"..color.."_"..i, {
632                                 description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
633                                 inventory_image = name.."_"..color..".png",
634                                 stack_max = 1,
635                                 groups = {not_in_creative_inventory=1}
636                         })
637                 end
638         end
639         end
640 end
641
642 register_piece("pawn", 8)
643 register_piece("rook", 2)
644 register_piece("knight", 2)
645 register_piece("bishop", 2)
646 register_piece("queen")
647 register_piece("king")
648
649 -- Recipes
650
651 minetest.register_craft({
652         output = "realchess:chessboard",
653         recipe = {
654                 {"dye:black", "dye:white", "dye:black"},
655                 {"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"}
656         }
657 })