realchess_app.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. -- Based on https://github.com/minetest-mods/realchess
  2. -- WTFPL by kilbith
  3. local realchess = {}
  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. local function xy_to_index(x, y)
  11. return x + y * 8 + 1
  12. end
  13. function realchess.move(data, pos, from_list, from_index, to_list, to_index, _, player)
  14. if from_list ~= "board" and to_list ~= "board" then
  15. return 0
  16. end
  17. local playerName = player:get_player_name()
  18. local meta = minetest.get_meta(pos)
  19. if data.winner ~= "" then
  20. data.messageOther = "This game is over."
  21. return 0
  22. end
  23. local inv = meta:get_inventory()
  24. local pieceFrom = inv:get_stack(from_list, from_index):get_name():sub(8) --:sub(8) cuts "laptop:"
  25. local pieceTo = inv:get_stack(to_list, to_index):get_name():sub(8) --:sub(8) cuts "laptop:"
  26. local thisMove -- will replace lastMove when move is legal
  27. if pieceFrom:find("white") then
  28. if data.playerWhite ~= "" and data.playerWhite ~= playerName then
  29. data.messageOther = "Someone else plays white pieces!"
  30. return 0
  31. end
  32. if data.lastMove ~= "" and data.lastMove ~= "black" then
  33. data.messageWhite = "It's not your turn, wait for your opponent to play."
  34. return 0
  35. end
  36. if pieceTo:find("white") then
  37. -- Don't replace pieces of same color
  38. return 0
  39. end
  40. data.playerWhite = playerName
  41. thisMove = "white"
  42. elseif pieceFrom:find("black") then
  43. if data.playerBlack ~= "" and data.playerBlack ~= playerName then
  44. data.messageOther = "Someone else plays white pieces!"
  45. return 0
  46. end
  47. if data.lastMove ~= "" and data.lastMove ~= "white" then
  48. data.messageBlack = "It's not your turn, wait for your opponent to play."
  49. return 0
  50. end
  51. if pieceTo:find("black") then
  52. -- Don't replace pieces of same color
  53. return 0
  54. end
  55. data.playerBlack = playerName
  56. thisMove = "black"
  57. end
  58. -- DETERMINISTIC MOVING
  59. local from_x, from_y = index_to_xy(from_index)
  60. local to_x, to_y = index_to_xy(to_index)
  61. if pieceFrom:sub(11,14) == "pawn" then
  62. if thisMove == "white" then
  63. local pawnWhiteMove = inv:get_stack(from_list, xy_to_index(from_x, from_y - 1)):get_name()
  64. -- white pawns can go up only
  65. if from_y - 1 == to_y then
  66. if from_x == to_x then
  67. if pieceTo ~= "" then
  68. return 0
  69. elseif to_index >= 1 and to_index <= 8 then
  70. inv:set_stack(from_list, from_index, "laptop:realchess_queen_white")
  71. end
  72. elseif from_x - 1 == to_x or from_x + 1 == to_x then
  73. if not pieceTo:find("black") then
  74. return 0
  75. elseif to_index >= 1 and to_index <= 8 then
  76. inv:set_stack(from_list, from_index, "laptop:realchess_queen_white")
  77. end
  78. else
  79. return 0
  80. end
  81. elseif from_y - 2 == to_y then
  82. if pieceTo ~= "" or from_y < 6 or pawnWhiteMove ~= "" then
  83. return 0
  84. end
  85. else
  86. return 0
  87. end
  88. -- if x not changed,
  89. -- ensure that destination cell is empty
  90. -- elseif x changed one unit left or right
  91. -- ensure the pawn is killing opponent piece
  92. -- else
  93. -- move is not legal - abort
  94. if from_x == to_x then
  95. if pieceTo ~= "" then
  96. return 0
  97. end
  98. elseif from_x - 1 == to_x or from_x + 1 == to_x then
  99. if not pieceTo:find("black") then
  100. return 0
  101. end
  102. else
  103. return 0
  104. end
  105. elseif thisMove == "black" then
  106. local pawnBlackMove = inv:get_stack(from_list, xy_to_index(from_x, from_y + 1)):get_name()
  107. -- black pawns can go down only
  108. if from_y + 1 == to_y then
  109. if from_x == to_x then
  110. if pieceTo ~= "" then
  111. return 0
  112. elseif to_index >= 57 and to_index <= 64 then
  113. inv:set_stack(from_list, from_index, "laptop:realchess_queen_black")
  114. end
  115. elseif from_x - 1 == to_x or from_x + 1 == to_x then
  116. if not pieceTo:find("white") then
  117. return 0
  118. elseif to_index >= 57 and to_index <= 64 then
  119. inv:set_stack(from_list, from_index, "laptop:realchess_queen_black")
  120. end
  121. else
  122. return 0
  123. end
  124. elseif from_y + 2 == to_y then
  125. if pieceTo ~= "" or from_y > 1 or pawnBlackMove ~= "" then
  126. return 0
  127. end
  128. else
  129. return 0
  130. end
  131. -- if x not changed,
  132. -- ensure that destination cell is empty
  133. -- elseif x changed one unit left or right
  134. -- ensure the pawn is killing opponent piece
  135. -- else
  136. -- move is not legal - abort
  137. if from_x == to_x then
  138. if pieceTo ~= "" then
  139. return 0
  140. end
  141. elseif from_x - 1 == to_x or from_x + 1 == to_x then
  142. if not pieceTo:find("white") then
  143. return 0
  144. end
  145. else
  146. return 0
  147. end
  148. else
  149. return 0
  150. end
  151. elseif pieceFrom:sub(11,14) == "rook" then
  152. if from_x == to_x then
  153. -- moving vertically
  154. if from_y < to_y then
  155. -- moving down
  156. -- ensure that no piece disturbs the way
  157. for i = from_y + 1, to_y - 1 do
  158. if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
  159. return 0
  160. end
  161. end
  162. else
  163. -- mocing up
  164. -- ensure that no piece disturbs the way
  165. for i = to_y + 1, from_y - 1 do
  166. if inv:get_stack(from_list, xy_to_index(from_x, i)):get_name() ~= "" then
  167. return 0
  168. end
  169. end
  170. end
  171. elseif from_y == to_y then
  172. -- mocing horizontally
  173. if from_x < to_x then
  174. -- mocing right
  175. -- ensure that no piece disturbs the way
  176. for i = from_x + 1, to_x - 1 do
  177. if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
  178. return 0
  179. end
  180. end
  181. else
  182. -- mocing left
  183. -- ensure that no piece disturbs the way
  184. for i = to_x + 1, from_x - 1 do
  185. if inv:get_stack(from_list, xy_to_index(i, from_y)):get_name() ~= "" then
  186. return 0
  187. end
  188. end
  189. end
  190. else
  191. -- attempt to move arbitrarily -> abort
  192. return 0
  193. end
  194. if thisMove == "white" or thisMove == "black" then
  195. if pieceFrom:sub(-1) == "1" then
  196. data.castlingWhiteL = 0
  197. elseif pieceFrom:sub(-1) == "2" then
  198. data.castlingWhiteR = 0
  199. end
  200. end
  201. elseif pieceFrom:sub(11,16) == "knight" then
  202. -- get relative pos
  203. local dx = from_x - to_x
  204. local dy = from_y - to_y
  205. -- get absolute values
  206. if dx < 0 then dx = -dx end
  207. if dy < 0 then dy = -dy end
  208. -- sort x and y
  209. if dx > dy then dx, dy = dy, dx end
  210. -- ensure that dx == 1 and dy == 2
  211. if dx ~= 1 or dy ~= 2 then
  212. return 0
  213. end
  214. -- just ensure that destination cell does not contain friend piece
  215. -- ^ it was done already thus everything ok
  216. elseif pieceFrom:sub(11,16) == "bishop" then
  217. -- get relative pos
  218. local dx = from_x - to_x
  219. local dy = from_y - to_y
  220. -- get absolute values
  221. if dx < 0 then dx = -dx end
  222. if dy < 0 then dy = -dy end
  223. -- ensure dx and dy are equal
  224. if dx ~= dy then return 0 end
  225. if from_x < to_x then
  226. if from_y < to_y then
  227. -- moving right-down
  228. -- ensure that no piece disturbs the way
  229. for i = 1, dx - 1 do
  230. if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
  231. return 0
  232. end
  233. end
  234. else
  235. -- moving right-up
  236. -- ensure that no piece disturbs the way
  237. for i = 1, dx - 1 do
  238. if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
  239. return 0
  240. end
  241. end
  242. end
  243. else
  244. if from_y < to_y then
  245. -- moving left-down
  246. -- ensure that no piece disturbs the way
  247. for i = 1, dx - 1 do
  248. if inv:get_stack(from_list, xy_to_index(from_x - i, from_y + i)):get_name() ~= "" then
  249. return 0
  250. end
  251. end
  252. else
  253. -- moving left-up
  254. -- ensure that no piece disturbs the way
  255. for i = 1, dx - 1 do
  256. if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
  257. return 0
  258. end
  259. end
  260. end
  261. end
  262. elseif pieceFrom:sub(11,15) == "queen" then
  263. local dx = from_x - to_x
  264. local dy = from_y - to_y
  265. -- get absolute values
  266. if dx < 0 then dx = -dx end
  267. if dy < 0 then dy = -dy end
  268. -- ensure valid relative move
  269. if dx ~= 0 and dy ~= 0 and dx ~= dy then
  270. return 0
  271. end
  272. if from_x == to_x then
  273. if from_y < to_y then
  274. -- goes down
  275. -- ensure that no piece disturbs the way
  276. for i = 1, dx - 1 do
  277. if inv:get_stack(from_list, xy_to_index(from_x, from_y + i)):get_name() ~= "" then
  278. return 0
  279. end
  280. end
  281. else
  282. -- goes up
  283. -- ensure that no piece disturbs the way
  284. for i = 1, dx - 1 do
  285. if inv:get_stack(from_list, xy_to_index(from_x, from_y - i)):get_name() ~= "" then
  286. return 0
  287. end
  288. end
  289. end
  290. elseif from_x < to_x then
  291. if from_y == to_y then
  292. -- goes right
  293. -- ensure that no piece disturbs the way
  294. for i = 1, dx - 1 do
  295. if inv:get_stack(from_list, xy_to_index(from_x + i, from_y)):get_name() ~= "" then
  296. return 0
  297. end
  298. end
  299. elseif from_y < to_y then
  300. -- goes right-down
  301. -- ensure that no piece disturbs the way
  302. for i = 1, dx - 1 do
  303. if inv:get_stack(from_list, xy_to_index(from_x + i, from_y + i)):get_name() ~= "" then
  304. return 0
  305. end
  306. end
  307. else
  308. -- goes right-up
  309. -- ensure that no piece disturbs the way
  310. for i = 1, dx - 1 do
  311. if inv:get_stack(from_list, xy_to_index(from_x + i, from_y - i)):get_name() ~= "" then
  312. return 0
  313. end
  314. end
  315. end
  316. else
  317. if from_y == to_y then
  318. -- goes left
  319. -- ensure that no piece disturbs the way and destination cell does
  320. for i = 1, dx - 1 do
  321. if inv:get_stack(from_list, xy_to_index(from_x - i, from_y)):get_name() ~= "" then
  322. return 0
  323. end
  324. end
  325. elseif from_y < to_y then
  326. -- goes left-down
  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. else
  334. -- goes left-up
  335. -- ensure that no piece disturbs the way
  336. for i = 1, dx - 1 do
  337. if inv:get_stack(from_list, xy_to_index(from_x - i, from_y - i)):get_name() ~= "" then
  338. return 0
  339. end
  340. end
  341. end
  342. end
  343. elseif pieceFrom:sub(11,14) == "king" then
  344. local dx = from_x - to_x
  345. local dy = from_y - to_y
  346. local check = true
  347. if thisMove == "white" then
  348. if from_y == 7 and to_y == 7 then
  349. if to_x == 1 then
  350. local idx57 = inv:get_stack(from_list, 57):get_name()
  351. if data.castlingWhiteL == 1 and idx57 == "laptop:realchess_rook_white_1" then
  352. for i = 58, from_index - 1 do
  353. if inv:get_stack(from_list, i):get_name() ~= "" then
  354. return 0
  355. end
  356. end
  357. inv:set_stack(from_list, 57, "")
  358. inv:set_stack(from_list, 59, "laptop:realchess_rook_white_1")
  359. check = false
  360. end
  361. elseif to_x == 6 then
  362. local idx64 = inv:get_stack(from_list, 64):get_name()
  363. if data.castlingWhiteR == 1 and idx64 == "laptop:realchess_rook_white_2" then
  364. for i = from_index + 1, 63 do
  365. if inv:get_stack(from_list, i):get_name() ~= "" then
  366. return 0
  367. end
  368. end
  369. inv:set_stack(from_list, 62, "laptop:realchess_rook_white_2")
  370. inv:set_stack(from_list, 64, "")
  371. check = false
  372. end
  373. end
  374. end
  375. elseif thisMove == "black" then
  376. if from_y == 0 and to_y == 0 then
  377. if to_x == 1 then
  378. local idx1 = inv:get_stack(from_list, 1):get_name()
  379. if data.castlingBlackL == 1 and idx1 == "laptop:realchess_rook_black_1" then
  380. for i = 2, from_index - 1 do
  381. if inv:get_stack(from_list, i):get_name() ~= "" then
  382. return 0
  383. end
  384. end
  385. inv:set_stack(from_list, 1, "")
  386. inv:set_stack(from_list, 3, "laptop:realchess_rook_black_1")
  387. check = false
  388. end
  389. elseif to_x == 6 then
  390. local idx8 = inv:get_stack(from_list, 1):get_name()
  391. if data.castlingBlackR == 1 and idx8 == "laptop:realchess_rook_black_2" then
  392. for i = from_index + 1, 7 do
  393. if inv:get_stack(from_list, i):get_name() ~= "" then
  394. return 0
  395. end
  396. end
  397. inv:set_stack(from_list, 6, "laptop:realchess_rook_black_2")
  398. inv:set_stack(from_list, 8, "")
  399. check = false
  400. end
  401. end
  402. end
  403. end
  404. if check then
  405. if dx < 0 then dx = -dx end
  406. if dy < 0 then dy = -dy end
  407. if dx > 1 or dy > 1 then return 0 end
  408. end
  409. if thisMove == "white" then
  410. data.castlingWhiteL = 0
  411. data.castlingWhiteR = 0
  412. elseif thisMove == "black" then
  413. data.castlingBlackL = 0
  414. data.castlingBlackR = 0
  415. end
  416. end
  417. data.lastMove = thisMove
  418. data.lastMoveTime = minetest.get_gametime()
  419. if data.lastMove == "black" then
  420. data.messageWhite = "["..os.date("%H:%M:%S").."] "..
  421. playerName.." moved a "..pieceFrom:match("_(%a+)")..", it's now your turn."
  422. elseif data.lastMove == "white" then
  423. data.messageBlack = "["..os.date("%H:%M:%S").."] "..
  424. playerName.." moved a "..pieceFrom:match("_(%a+)")..", it's now your turn."
  425. end
  426. if pieceTo:sub(11,14) == "king" then
  427. data.messageWhite = playerName.." won the game."
  428. data.messageBlack = playerName.." won the game."
  429. data.winner = thisMove
  430. end
  431. return 1
  432. end
  433. local function timeout_format(timeout_limit)
  434. local time_remaining = timeout_limit - minetest.get_gametime()
  435. local minutes = math.floor(time_remaining / 60)
  436. local seconds = time_remaining % 60
  437. if minutes == 0 then return seconds.." sec." end
  438. return minutes.." min. "..seconds.." sec."
  439. end
  440. local function register_piece(name, count)
  441. for _, color in pairs({"black", "white"}) do
  442. if not count then
  443. minetest.register_craftitem("laptop:realchess_"..name.."_"..color, {
  444. description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
  445. inventory_image = "laptop_realchess_"..name.."_"..color..".png",
  446. stack_max = 1,
  447. groups = {not_in_creative_inventory=1}
  448. })
  449. else
  450. for i = 1, count do
  451. minetest.register_craftitem("laptop:realchess_"..name.."_"..color.."_"..i, {
  452. description = color:gsub("^%l", string.upper).." "..name:gsub("^%l", string.upper),
  453. inventory_image = "laptop_realchess_"..name.."_"..color..".png",
  454. stack_max = 1,
  455. groups = {not_in_creative_inventory=1}
  456. })
  457. end
  458. end
  459. end
  460. end
  461. register_piece("pawn", 8)
  462. register_piece("rook", 2)
  463. register_piece("knight", 2)
  464. register_piece("bishop", 2)
  465. register_piece("queen")
  466. register_piece("king")
  467. -- Laptop app registration
  468. laptop.register_app("realchess", {
  469. app_name = "Realchess",
  470. app_icon = "laptop_realchess_chessboard_icon.png",
  471. app_info = "A Chess game",
  472. os_min_version = "5.51",
  473. formspec_func = function(app, mtos)
  474. local data = mtos.bdev:get_app_storage('ram', 'realchess')
  475. if not data.init_done then
  476. data.init_done = true
  477. -- Initialize the game
  478. data.playerBlack = ""
  479. data.playerWhite = ""
  480. data.lastMove = ""
  481. data.winner = ""
  482. data.lastMoveTime = 0
  483. data.castlingBlackL = 1
  484. data.castlingBlackR = 1
  485. data.castlingWhiteL = 1
  486. data.castlingWhiteR = 1
  487. local meta = minetest.get_meta(mtos.pos)
  488. local inv = meta:get_inventory()
  489. inv:set_size("board", 64)
  490. inv:set_list("board", {
  491. "laptop:realchess_rook_black_1",
  492. "laptop:realchess_knight_black_1",
  493. "laptop:realchess_bishop_black_1",
  494. "laptop:realchess_queen_black",
  495. "laptop:realchess_king_black",
  496. "laptop:realchess_bishop_black_2",
  497. "laptop:realchess_knight_black_2",
  498. "laptop:realchess_rook_black_2",
  499. "laptop:realchess_pawn_black_1",
  500. "laptop:realchess_pawn_black_2",
  501. "laptop:realchess_pawn_black_3",
  502. "laptop:realchess_pawn_black_4",
  503. "laptop:realchess_pawn_black_5",
  504. "laptop:realchess_pawn_black_6",
  505. "laptop:realchess_pawn_black_7",
  506. "laptop:realchess_pawn_black_8",
  507. '','','','','','','','','','','','','','','','',
  508. '','','','','','','','','','','','','','','','',
  509. "laptop:realchess_pawn_white_1",
  510. "laptop:realchess_pawn_white_2",
  511. "laptop:realchess_pawn_white_3",
  512. "laptop:realchess_pawn_white_4",
  513. "laptop:realchess_pawn_white_5",
  514. "laptop:realchess_pawn_white_6",
  515. "laptop:realchess_pawn_white_7",
  516. "laptop:realchess_pawn_white_8",
  517. "laptop:realchess_rook_white_1",
  518. "laptop:realchess_knight_white_1",
  519. "laptop:realchess_bishop_white_1",
  520. "laptop:realchess_queen_white",
  521. "laptop:realchess_king_white",
  522. "laptop:realchess_bishop_white_2",
  523. "laptop:realchess_knight_white_2",
  524. "laptop:realchess_rook_white_2"
  525. })
  526. end
  527. local formspec =
  528. "bgcolor[#080808BB;true]background[3,1;8,8;laptop_realchess_chess_bg.png]"..
  529. mtos.theme:get_button('12,1;2,2', 'major', 'new', 'New Game', 'Start a new game')..
  530. "list[context;board;3,1;8,8;]"..
  531. "listcolors[#00000000;#00000000;#00000000;#30434C;#FFF]"
  532. if data.messageOther then
  533. formspec = formspec..mtos.theme:get_label('4,9.3', mtos.sysram.current_player.." "..data.messageOther)
  534. else
  535. formspec=formspec..
  536. mtos.theme:get_label('2,0.3', data.playerBlack.." "..(data.messageBlack or ""))..
  537. mtos.theme:get_label('4,9.3', data.playerWhite.." "..(data.messageWhite or ""))
  538. end
  539. return formspec
  540. end,
  541. receive_fields_func = function(app, mtos, sender, fields)
  542. local data = mtos.bdev:get_app_storage('ram', 'realchess')
  543. local playerName = sender:get_player_name()
  544. local timeout_limit = (data.lastMoveTime or 0) + 300
  545. if fields.quit then return end
  546. data.messageBlack = nil
  547. data.messageWhite = nil
  548. data.messageOther = nil
  549. -- timeout is 5 min. by default for resetting the game (non-players only)
  550. if fields.new and (data.playerWhite == playerName or data.playerBlack == playerName) then
  551. data.init_done = nil
  552. elseif fields.new and data.lastMoveTime ~= 0 and minetest.get_gametime() >= timeout_limit and
  553. (data.playerWhite ~= playerName or data.playerBlack ~= playerName) then
  554. data.init_done = nil
  555. else
  556. data.messageOther = "[!] You can't reset the chessboard, a game has been started.\n"..
  557. "If you are not a current player, try again in "..timeout_format(timeout_limit)
  558. end
  559. end,
  560. allow_metadata_inventory_move = function(app, mtos, player, from_list, from_index, to_list, to_index, count)
  561. local data = mtos.bdev:get_app_storage('ram', 'realchess')
  562. data.messageBlack = nil
  563. data.messageWhite = nil
  564. data.messageOther = nil
  565. local ret = realchess.move(data, mtos.pos, from_list, from_index, to_list, to_index, count, player), false --reshow = true
  566. minetest.after(0, mtos.set_app, mtos, mtos.sysram.current_app) -- refresh screen
  567. return ret
  568. end,
  569. on_metadata_inventory_move = function(app, mtos, player, from_list, from_index, to_list, to_index, count)
  570. local inv = minetest.get_meta(mtos.pos):get_inventory()
  571. inv:set_stack(from_list, from_index, '')
  572. return false
  573. end,
  574. allow_metadata_inventory_take = function()
  575. return 0
  576. end,
  577. })