tetris_app.lua 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. -- Based on https://github.com/minetest-mods/homedecor_modpack/blob/master/computer/tetris.lua
  2. local shapes = {
  3. { { x = {0, 1, 0, 1}, y = {0, 0, 1, 1} } },
  4. { { x = {1, 1, 1, 1}, y = {0, 1, 2, 3} },
  5. { x = {0, 1, 2, 3}, y = {1, 1, 1, 1} } },
  6. { { x = {0, 0, 1, 1}, y = {0, 1, 1, 2} },
  7. { x = {1, 2, 0, 1}, y = {0, 0, 1, 1} } },
  8. { { x = {1, 0, 1, 0}, y = {0, 1, 1, 2} },
  9. { x = {0, 1, 1, 2}, y = {0, 0, 1, 1} } },
  10. { { x = {1, 2, 1, 1}, y = {0, 0, 1, 2} },
  11. { x = {0, 1, 2, 2}, y = {1, 1, 1, 2} },
  12. { x = {1, 1, 0, 1}, y = {0, 1, 2, 2} },
  13. { x = {0, 0, 1, 2}, y = {0, 1, 1, 1} } },
  14. { { x = {1, 1, 1, 2}, y = {0, 1, 2, 2} },
  15. { x = {0, 1, 2, 0}, y = {1, 1, 1, 2} },
  16. { x = {0, 1, 1, 1}, y = {0, 0, 1, 2} },
  17. { x = {0, 1, 2, 2}, y = {1, 1, 1, 0} } },
  18. { { x = {1, 0, 1, 2}, y = {0, 1, 1, 1} },
  19. { x = {1, 1, 1, 2}, y = {0, 1, 2, 1} },
  20. { x = {0, 1, 2, 1}, y = {1, 1, 1, 2} },
  21. { x = {0, 1, 1, 1}, y = {1, 0, 1, 2} } } }
  22. local base_color_texture = "laptop_tetris_block.png"
  23. local base_color_alpha = '128'
  24. local colors = { base_color_texture.."^[colorize:#00FFFF:"..base_color_alpha, base_color_texture.."^[colorize:#FF00FF:"..base_color_alpha, base_color_texture.."^[colorize:#FF0000:"..base_color_alpha,
  25. base_color_texture.."^[colorize:#0000FF:"..base_color_alpha, base_color_texture.."^[colorize:#00FF00:"..base_color_alpha, base_color_texture.."^[colorize:#FF4500:"..base_color_alpha,
  26. base_color_texture.."^[colorize:#FFFF00:"..base_color_alpha }
  27. local boardx, boardy = 0, 0
  28. local sizex, sizey, size = 0.42, 0.44, 0.47
  29. local comma = ","
  30. local semi = ";"
  31. local close = "]"
  32. local concat = table.concat
  33. local insert = table.insert
  34. local tetris_class = {}
  35. tetris_class.__index = tetris_class
  36. local function get_tetris(app, data)
  37. local self = setmetatable({}, tetris_class)
  38. self.data = data
  39. self.app = app
  40. return self
  41. end
  42. function tetris_class:new_game()
  43. local nex = math.random(7)
  44. self.data.t = {
  45. board = {},
  46. boardstring = "",
  47. previewstring = self:draw_shape(nex, 0, 0, 1, 6.5, 0.8),
  48. score = 0,
  49. cur = math.random(7),
  50. nex = nex,
  51. x=4, y=0, rot=1
  52. }
  53. self.app:get_timer():start(0.3)
  54. end
  55. function tetris_class:update_boardstring()
  56. local scr = {}
  57. local ins = #scr
  58. local fixed_color
  59. if self.app.os.theme.monochrome_textcolor then
  60. fixed_color = base_color_texture.."^[colorize:"..
  61. self.app.os.theme.monochrome_textcolor..
  62. ":"..base_color_alpha
  63. end
  64. for i, line in pairs(self.data.t.board) do
  65. for _, tile in pairs(line) do
  66. local tmp = { "image[",
  67. tile[1]*sizex+boardx, comma,
  68. i*sizey+boardy, semi,
  69. size, comma, size, semi,
  70. fixed_color or colors[tile[2]], close }
  71. ins = ins + 1
  72. scr[ins] = concat(tmp)
  73. end
  74. end
  75. self.data.t.boardstring = concat(scr)
  76. end
  77. function tetris_class:add()
  78. local t = self.data.t
  79. local d = shapes[t.cur][t.rot]
  80. for i=1,4 do
  81. local l = d.y[i] + t.y
  82. if not t.board[l] then t.board[l] = {} end
  83. insert(t.board[l], {d.x[i] + t.x, t.cur})
  84. end
  85. end
  86. function tetris_class:scroll(l)
  87. for i=l, 1, -1 do
  88. self.data.t.board[i] = self.data.t.board[i-1] or {}
  89. end
  90. end
  91. function tetris_class:check_lines()
  92. for i, line in pairs(self.data.t.board) do
  93. if #line >= 10 then
  94. self:scroll(i)
  95. self.data.t.score = self.data.t.score + 20
  96. end
  97. end
  98. end
  99. function tetris_class:check_position(x, y, rot)
  100. local d = shapes[self.data.t.cur][rot]
  101. for i=1,4 do
  102. local cx, cy = d.x[i]+x, d.y[i]+y
  103. if cx < 0 or cx > 9 or cy < 0 or cy > 19 then
  104. return false
  105. end
  106. for _, tile in pairs(self.data.t.board[ cy ] or {}) do
  107. if tile[1] == cx then return false end
  108. end
  109. end
  110. return true
  111. end
  112. function tetris_class:stuck()
  113. local t = self.data.t
  114. if self:check_position(t.x, t.y+1, t.rot) then
  115. return false
  116. else
  117. return true
  118. end
  119. end
  120. function tetris_class:tick()
  121. local t = self.data.t
  122. if self:stuck() then
  123. if t.y <= 0 then
  124. return false
  125. end
  126. self:add()
  127. self:check_lines()
  128. self:update_boardstring()
  129. t.cur, t.nex = t.nex, math.random(7)
  130. t.x, t.y, t.rot = 4, 0, 1
  131. t.previewstring = self:draw_shape(t.nex, 0, 0, 1, 6.5, 0.8)
  132. else
  133. t.y = t.y + 1
  134. end
  135. return true
  136. end
  137. function tetris_class:move(dx, dy)
  138. local t = self.data.t
  139. local newx, newy = t.x+dx, t.y+dy
  140. if not self:check_position(newx, newy, t.rot) then
  141. return
  142. end
  143. t.x, t.y = newx, newy
  144. end
  145. function tetris_class:rotate(dr)
  146. local t = self.data.t
  147. local no = #(shapes[t.cur])
  148. local newrot = (t.rot+dr) % no
  149. if newrot<1 then newrot = newrot+no end
  150. if not self:check_position(t.x, t.y, newrot) then
  151. return
  152. end
  153. t.rot = newrot
  154. end
  155. function tetris_class:key(fields)
  156. local t = self.data.t
  157. if fields.left then
  158. self:move(-1, 0)
  159. end
  160. if fields.rotateleft then
  161. self:rotate(-1)
  162. end
  163. if fields.down then
  164. t.score = t.score + 1
  165. self:move(0, 1)
  166. end
  167. if fields.drop then
  168. while not self:stuck() do
  169. t.score = t.score + 2
  170. self:move(0, 1)
  171. end
  172. end
  173. if fields.rotateright then
  174. self:rotate(1)
  175. end
  176. if fields.right then
  177. self:move(1, 0)
  178. end
  179. end
  180. function tetris_class:draw_shape(id, x, y, rot, posx, posy)
  181. local d = shapes[id][rot]
  182. local scr = {}
  183. local ins = #scr
  184. local color = colors[id]
  185. if self.app.os.theme.monochrome_textcolor then
  186. color = base_color_texture.."^[colorize:"..
  187. self.app.os.theme.monochrome_textcolor..
  188. ":"..base_color_alpha
  189. end
  190. for i=1,4 do
  191. local tmp = { "image[",
  192. (d.x[i]+x)*sizex+posx, comma,
  193. (d.y[i]+y)*sizey+posy, semi,
  194. size, comma, size, semi,
  195. color, close }
  196. ins = ins + 1
  197. scr[ins] = concat(tmp)
  198. end
  199. return concat(scr)
  200. end
  201. laptop.register_app("tetris", {
  202. app_name = "Tetris",
  203. app_icon = "laptop_tetris_icon.png",
  204. app_info = "Falling Tile Game",
  205. formspec_func = function(app, mtos)
  206. local data = mtos.bdev:get_app_storage('ram', 'tetris')
  207. local tetris = get_tetris(app, data)
  208. if not data.t then
  209. return mtos.theme:get_button('2,4;2,2', 'major', 'new', 'New Game', 'Start a new game')
  210. end
  211. local buttons = mtos.theme:get_button('6,6;1,1', 'minor', 'left', '<')..
  212. mtos.theme:get_button('6,5;1,1', 'minor', 'rotateleft', 'L')..
  213. mtos.theme:get_button('7,5;1,1', 'minor', 'down', 'v')..
  214. mtos.theme:get_button('7,6;1,1', 'minor', 'drop', 'V')..
  215. mtos.theme:get_button('8,5;1,1', 'minor', 'rotateright', 'R')..
  216. mtos.theme:get_button('8,6;1,1', 'minor', 'right', '>')..
  217. mtos.theme:get_button('6,3.5;3,1', 'major', 'new', 'New Game', 'Start a new game')
  218. local t = tetris.data.t
  219. return 'container[3,1]background[0,-0.05;4.35,9;'.. mtos.theme.contrast_background .. ']' ..
  220. t.boardstring .. t.previewstring ..
  221. tetris:draw_shape(t.cur, t.x, t.y, t.rot, boardx, boardy) ..
  222. mtos.theme:get_label('6.5,0.1', 'Next...') ..
  223. mtos.theme:get_label('6.5,2.7', 'Score:...'..t.score) ..
  224. buttons .. 'container_end[]'
  225. end,
  226. receive_fields_func = function(app, mtos, sender, fields)
  227. local data = mtos.bdev:get_app_storage('ram', 'tetris')
  228. local tetris = get_tetris(app, data)
  229. if fields.new then
  230. tetris:new_game()
  231. elseif fields.continue then
  232. app:get_timer():start(0.3)
  233. else
  234. tetris:key(fields)
  235. end
  236. end,
  237. on_timer = function(app, mtos)
  238. local data = mtos.bdev:get_app_storage('ram', 'tetris')
  239. if not data.t then
  240. return false
  241. else
  242. return get_tetris(app, data):tick()
  243. end
  244. end,
  245. })