game_page.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. --[[
  2. @module game_page
  3. @summary 俄罗斯方块游戏演示页面
  4. @version 1.0
  5. @date 2026.02.05
  6. @author 江访
  7. @usage
  8. 本文件是俄罗斯方块游戏的演示页面。
  9. ]]
  10. local game_page = {}
  11. -- 屏幕与棋盘参数(适配480*320竖屏)
  12. local SCREEN_W, SCREEN_H = 320, 480 -- 竖屏:320宽,480高
  13. local GRID_W, GRID_H = 10, 15 -- 调整为10列15行以适应竖屏
  14. local CELL_SIZE = 20 -- 每格像素大小(20px)
  15. local BOARD_W = GRID_W * CELL_SIZE
  16. local BOARD_H = GRID_H * CELL_SIZE
  17. local BOARD_X = (SCREEN_W - BOARD_W) // 2 -- 水平居中
  18. local BOARD_Y = 100 -- 棋盘左上角Y坐标
  19. -- 7 种俄罗斯方块颜色(RGB565格式)
  20. local COLORS = {
  21. 0x07FF, -- I (青色)
  22. 0x001F, -- J (蓝色)
  23. 0xFD20, -- L (橙色)
  24. 0xFFE0, -- O (黄色)
  25. 0x07E0, -- S (绿色)
  26. 0x8010, -- T (紫色)
  27. 0xF800, -- Z (红色)
  28. }
  29. -- 7 种形状(1 表示方块)
  30. local SHAPES = {
  31. { { 1, 1, 1, 1 } }, -- I
  32. { { 1, 0, 0 }, { 1, 1, 1 } }, -- J
  33. { { 0, 0, 1 }, { 1, 1, 1 } }, -- L
  34. { { 1, 1 }, { 1, 1 } }, -- O
  35. { { 0, 1, 1 }, { 1, 1, 0 } }, -- S
  36. { { 0, 1, 0 }, { 1, 1, 1 } }, -- T
  37. { { 1, 1, 0 }, { 0, 1, 1 } }, -- Z
  38. }
  39. -- 游戏状态
  40. local grid -- grid[y][x] = nil 或 颜色下标
  41. local curPiece -- {x, y, shape, color}
  42. local score = 0
  43. local gameOver = false
  44. local gameTimer = nil
  45. -- UI 控件
  46. local main_container
  47. local scoreLabel
  48. local statusLabel
  49. local leftBtn, rightBtn, rotateBtn, downBtn, restartBtn
  50. ----------------------------------------------------------------
  51. -- 工具函数:网格与方块
  52. ----------------------------------------------------------------
  53. local function initGrid()
  54. grid = {}
  55. for y = 1, GRID_H do
  56. grid[y] = {}
  57. for x = 1, GRID_W do
  58. grid[y][x] = nil
  59. end
  60. end
  61. end
  62. local function rotateShape(shape)
  63. local h, w = #shape, #shape[1]
  64. local res = {}
  65. for x = 1, w do
  66. res[x] = {}
  67. for y = 1, h do
  68. res[x][y] = shape[h - y + 1][x]
  69. end
  70. end
  71. return res
  72. end
  73. local function newPiece()
  74. local idx = math.random(1, #SHAPES)
  75. local shp = SHAPES[idx]
  76. local pw = #shp[1]
  77. local px = math.floor(GRID_W / 2) - math.floor(pw / 2) + 1
  78. curPiece = {
  79. x = px,
  80. y = 1,
  81. shape = shp,
  82. color = idx,
  83. }
  84. end
  85. local function eachBlock(piece, cb)
  86. local shape = piece.shape
  87. for j = 1, #shape do
  88. for i = 1, #shape[j] do
  89. if shape[j][i] == 1 then
  90. cb(piece.x + i - 1, piece.y + j - 1)
  91. end
  92. end
  93. end
  94. end
  95. local function validPosition(piece)
  96. local ok = true
  97. eachBlock(piece, function(x, y)
  98. if x < 1 or x > GRID_W or y < 1 or y > GRID_H then
  99. ok = false
  100. return
  101. end
  102. if grid[y][x] ~= nil then
  103. ok = false
  104. return
  105. end
  106. end)
  107. return ok
  108. end
  109. local function mergePiece()
  110. if not curPiece then return end
  111. eachBlock(curPiece, function(x, y)
  112. if y >= 1 and y <= GRID_H and x >= 1 and x <= GRID_W then
  113. grid[y][x] = curPiece.color
  114. end
  115. end)
  116. end
  117. -- 返回本次消掉的行数
  118. local function clearLines()
  119. local cleared = 0
  120. for y = GRID_H, 1, -1 do
  121. local full = true
  122. for x = 1, GRID_W do
  123. if grid[y][x] == nil then
  124. full = false
  125. break
  126. end
  127. end
  128. if full then
  129. cleared = cleared + 1
  130. -- 下移
  131. for yy = y, 2, -1 do
  132. grid[yy] = grid[yy - 1]
  133. end
  134. local row = {}
  135. for x = 1, GRID_W do row[x] = nil end
  136. grid[1] = row
  137. y = y + 1
  138. end
  139. end
  140. if cleared > 0 then
  141. score = score + cleared * 100
  142. end
  143. return cleared
  144. end
  145. ----------------------------------------------------------------
  146. -- 绘制函数:使用lcd.fill绘制棋盘
  147. ----------------------------------------------------------------
  148. local function drawCell(x, y, color)
  149. local cx = BOARD_X + (x - 1) * CELL_SIZE
  150. local cy = BOARD_Y + (y - 1) * CELL_SIZE
  151. lcd.fill(cx, cy, cx + CELL_SIZE - 2, cy + CELL_SIZE - 2, color)
  152. end
  153. local function drawBoard()
  154. -- 绘制棋盘背景
  155. lcd.fill(BOARD_X, BOARD_Y, BOARD_X + BOARD_W - 1, BOARD_Y + BOARD_H - 1, 0x1082) -- 深灰色背景
  156. -- 绘制固定的方块
  157. for y = 1, GRID_H do
  158. for x = 1, GRID_W do
  159. local fixed = grid[y][x]
  160. if fixed then
  161. drawCell(x, y, COLORS[fixed])
  162. else
  163. -- 绘制空单元格(带边框效果)
  164. local cx = BOARD_X + (x - 1) * CELL_SIZE
  165. local cy = BOARD_Y + (y - 1) * CELL_SIZE
  166. lcd.fill(cx, cy, cx + CELL_SIZE - 2, cy + CELL_SIZE - 2, 0x0841) -- 更深的灰色
  167. lcd.fill(cx + 1, cy + 1, cx + CELL_SIZE - 3, cy + CELL_SIZE - 3, 0x18C3) -- 浅灰色内框
  168. end
  169. end
  170. end
  171. -- 绘制当前方块
  172. if curPiece then
  173. eachBlock(curPiece, function(x, y)
  174. if y >= 1 and y <= GRID_H and x >= 1 and x <= GRID_W then
  175. drawCell(x, y, COLORS[curPiece.color])
  176. end
  177. end)
  178. end
  179. -- 绘制棋盘边框
  180. lcd.fill(BOARD_X - 2, BOARD_Y - 2, BOARD_X + BOARD_W + 1, BOARD_Y - 1, 0xFFFF) -- 上边框
  181. lcd.fill(BOARD_X - 2, BOARD_Y + BOARD_H, BOARD_X + BOARD_W + 1, BOARD_Y + BOARD_H + 1, 0xFFFF) -- 下边框
  182. lcd.fill(BOARD_X - 2, BOARD_Y - 2, BOARD_X - 1, BOARD_Y + BOARD_H + 1, 0xFFFF) -- 左边框
  183. lcd.fill(BOARD_X + BOARD_W, BOARD_Y - 2, BOARD_X + BOARD_W + 1, BOARD_Y + BOARD_H + 1, 0xFFFF) -- 右边框
  184. end
  185. ----------------------------------------------------------------
  186. -- UI更新函数
  187. ----------------------------------------------------------------
  188. local function updateScoreLabel()
  189. if scoreLabel then
  190. scoreLabel:set_text("分数: " .. tostring(score))
  191. end
  192. end
  193. local function updateStatusLabel(extra)
  194. if not statusLabel then return end
  195. if gameOver then
  196. statusLabel:set_text("游戏结束! 点击重新开始")
  197. else
  198. if extra and extra ~= "" then
  199. statusLabel:set_text("俄罗斯方块 | " .. extra)
  200. else
  201. statusLabel:set_text("俄罗斯方块 | 使用按钮游玩")
  202. end
  203. end
  204. end
  205. local function redrawAll(extraStatus)
  206. updateScoreLabel()
  207. updateStatusLabel(extraStatus)
  208. drawBoard()
  209. end
  210. ----------------------------------------------------------------
  211. -- 控制逻辑
  212. ----------------------------------------------------------------
  213. local function stepDown()
  214. if gameOver or not curPiece then return end
  215. local test = {
  216. x = curPiece.x,
  217. y = curPiece.y + 1,
  218. shape = curPiece.shape,
  219. color = curPiece.color,
  220. }
  221. if validPosition(test) then
  222. curPiece = test
  223. redrawAll()
  224. return
  225. end
  226. -- 碰到底或碰到已固定方块
  227. mergePiece()
  228. local cleared = clearLines()
  229. newPiece()
  230. if not validPosition(curPiece) then
  231. gameOver = true
  232. redrawAll("游戏结束!")
  233. if gameTimer then
  234. sys.timerStop(gameTimer)
  235. gameTimer = nil
  236. end
  237. else
  238. if cleared > 0 then
  239. redrawAll("消除了 " .. cleared .. " 行!")
  240. else
  241. redrawAll()
  242. end
  243. end
  244. end
  245. local function moveLeft()
  246. if gameOver or not curPiece then return end
  247. local test = {
  248. x = curPiece.x - 1,
  249. y = curPiece.y,
  250. shape = curPiece.shape,
  251. color = curPiece.color,
  252. }
  253. if validPosition(test) then
  254. curPiece = test
  255. redrawAll()
  256. end
  257. end
  258. local function moveRight()
  259. if gameOver or not curPiece then return end
  260. local test = {
  261. x = curPiece.x + 1,
  262. y = curPiece.y,
  263. shape = curPiece.shape,
  264. color = curPiece.color,
  265. }
  266. if validPosition(test) then
  267. curPiece = test
  268. redrawAll()
  269. end
  270. end
  271. local function softDrop()
  272. if gameOver or not curPiece then return end
  273. local test = {
  274. x = curPiece.x,
  275. y = curPiece.y + 1,
  276. shape = curPiece.shape,
  277. color = curPiece.color,
  278. }
  279. if validPosition(test) then
  280. curPiece = test
  281. redrawAll()
  282. end
  283. end
  284. local function rotatePiece()
  285. if gameOver or not curPiece then return end
  286. local newShape = rotateShape(curPiece.shape)
  287. local test = {
  288. x = curPiece.x,
  289. y = curPiece.y,
  290. shape = newShape,
  291. color = curPiece.color,
  292. }
  293. if validPosition(test) then
  294. curPiece = test
  295. redrawAll("旋转")
  296. end
  297. end
  298. local function restartGame()
  299. if gameTimer then
  300. sys.timerStop(gameTimer)
  301. end
  302. score = 0
  303. gameOver = false
  304. initGrid()
  305. newPiece()
  306. redrawAll("重新开始,分数: 0")
  307. -- 重新启动定时器
  308. gameTimer = sys.timerLoopStart(stepDown, 400)
  309. end
  310. ----------------------------------------------------------------
  311. -- 创建游戏UI
  312. ----------------------------------------------------------------
  313. function game_page.create_ui()
  314. -- 创建主容器
  315. main_container = airui.container({
  316. x = 0,
  317. y = 0,
  318. w = SCREEN_W,
  319. h = SCREEN_H,
  320. color = 0x0000, -- 黑色背景
  321. })
  322. -- 标题栏
  323. local title_bar = airui.container({
  324. parent = main_container,
  325. x = 0,
  326. y = 0,
  327. w = SCREEN_W,
  328. h = 50,
  329. color = 0x1E3A8A, -- 深蓝色
  330. })
  331. -- 返回按钮
  332. local back_btn = airui.button({
  333. parent = title_bar,
  334. x = 250,
  335. y = 10,
  336. w = 60,
  337. h = 30,
  338. text = "返回",
  339. on_click = function()
  340. game_page.cleanup()
  341. go_back()
  342. end
  343. })
  344. -- 游戏标题
  345. airui.label({
  346. parent = title_bar,
  347. text = "俄罗斯方块",
  348. x = 80,
  349. y = 15,
  350. w = 160,
  351. h = 20,
  352. })
  353. -- 分数标签
  354. scoreLabel = airui.label({
  355. parent = main_container,
  356. text = "分数: 0",
  357. x = 220,
  358. y = 70,
  359. w = 150,
  360. h = 20,
  361. })
  362. -- 状态标签
  363. statusLabel = airui.label({
  364. parent = main_container,
  365. text = "俄罗斯方块 | 使用按钮游玩",
  366. x = 10,
  367. y = 70,
  368. w = SCREEN_W - 20,
  369. h = 20,
  370. })
  371. -- 控制按钮区域
  372. local btnY = BOARD_Y + BOARD_H + 10
  373. local btnW, btnH, gap = 70, 30, 10
  374. -- 计算按钮起始X坐标(居中显示)
  375. local totalBtnWidth = btnW * 3 + gap * 2 -- 第一行3个按钮
  376. local btnStartX = (SCREEN_W - totalBtnWidth) // 2
  377. -- 第一行按钮
  378. leftBtn = airui.button({
  379. parent = main_container,
  380. x = btnStartX,
  381. y = btnY,
  382. w = btnW,
  383. h = btnH,
  384. text = "左移",
  385. on_click = function() moveLeft() end,
  386. })
  387. rightBtn = airui.button({
  388. parent = main_container,
  389. x = btnStartX + btnW + gap,
  390. y = btnY,
  391. w = btnW,
  392. h = btnH,
  393. text = "右移",
  394. on_click = function() moveRight() end,
  395. })
  396. rotateBtn = airui.button({
  397. parent = main_container,
  398. x = btnStartX + 2 * (btnW + gap),
  399. y = btnY,
  400. w = btnW,
  401. h = btnH,
  402. text = "旋转",
  403. on_click = function() rotatePiece() end,
  404. })
  405. -- 第二行按钮
  406. btnY = btnY + btnH + gap
  407. downBtn = airui.button({
  408. parent = main_container,
  409. x = btnStartX,
  410. y = btnY,
  411. w = btnW * 2 + gap, -- 稍宽一点
  412. h = btnH,
  413. text = "下落",
  414. on_click = function() softDrop() end,
  415. })
  416. restartBtn = airui.button({
  417. parent = main_container,
  418. x = btnStartX + btnW * 2 + gap * 2,
  419. y = btnY,
  420. w = btnW,
  421. h = btnH,
  422. text = "重新开始",
  423. on_click = function() restartGame() end,
  424. })
  425. end
  426. ----------------------------------------------------------------
  427. -- 页面生命周期函数
  428. ----------------------------------------------------------------
  429. function game_page.init(params)
  430. math.randomseed(os.time())
  431. -- 初始化游戏状态
  432. initGrid()
  433. newPiece()
  434. -- 创建UI
  435. game_page.create_ui()
  436. -- 初始绘制
  437. redrawAll("准备开始!")
  438. -- 启动定时器
  439. gameTimer = sys.timerLoopStart(stepDown, 400)
  440. end
  441. function game_page.cleanup()
  442. -- 停止定时器
  443. if gameTimer then
  444. sys.timerStop(gameTimer)
  445. gameTimer = nil
  446. end
  447. -- 清理UI
  448. if main_container then
  449. main_container:destroy()
  450. main_container = nil
  451. end
  452. -- 重置游戏状态
  453. grid = nil
  454. curPiece = nil
  455. score = 0
  456. gameOver = false
  457. leftBtn = nil
  458. rightBtn = nil
  459. rotateBtn = nil
  460. downBtn = nil
  461. restartBtn = nil
  462. scoreLabel = nil
  463. statusLabel = nil
  464. end
  465. return game_page