--[[ @module game_page @summary 俄罗斯方块游戏演示页面 @version 1.0 @date 2026.02.05 @author 江访 @usage 本文件是俄罗斯方块游戏的演示页面。 ]] local game_page = {} -- 屏幕与棋盘参数 local SCREEN_W, SCREEN_H = 480, 320 -- 横屏:480宽,320高 local common_ui = require("tsb_common_page") local GRID_W, GRID_H = 15, 10 local CELL_SIZE = 20 -- 每格像素大小(20px) local BOARD_W = GRID_W * CELL_SIZE local BOARD_H = GRID_H * CELL_SIZE local BOARD_X = (SCREEN_W - BOARD_W) // 2 -- 水平居中 local BOARD_Y = 100 -- 棋盘左上角Y坐标 -- 7 种俄罗斯方块颜色(RGB565格式) local COLORS = { 0x07FF, -- I (青色) 0x001F, -- J (蓝色) 0xFD20, -- L (橙色) 0xFFE0, -- O (黄色) 0x07E0, -- S (绿色) 0x8010, -- T (紫色) 0xF800, -- Z (红色) } -- 7 种形状(1 表示方块) local SHAPES = { { { 1, 1, 1, 1 } }, -- I { { 1, 0, 0 }, { 1, 1, 1 } }, -- J { { 0, 0, 1 }, { 1, 1, 1 } }, -- L { { 1, 1 }, { 1, 1 } }, -- O { { 0, 1, 1 }, { 1, 1, 0 } }, -- S { { 0, 1, 0 }, { 1, 1, 1 } }, -- T { { 1, 1, 0 }, { 0, 1, 1 } }, -- Z } -- 游戏状态 local grid -- grid[y][x] = nil 或 颜色下标 local curPiece -- {x, y, shape, color} local score = 0 local gameOver = false local gameTimer = nil -- UI 控件 local main_container local scoreLabel local statusLabel local leftBtn, rightBtn, rotateBtn, downBtn, restartBtn ---------------------------------------------------------------- -- 工具函数:网格与方块 ---------------------------------------------------------------- local function initGrid() grid = {} for y = 1, GRID_H do grid[y] = {} for x = 1, GRID_W do grid[y][x] = nil end end end local function rotateShape(shape) local h, w = #shape, #shape[1] local res = {} for x = 1, w do res[x] = {} for y = 1, h do res[x][y] = shape[h - y + 1][x] end end return res end local function newPiece() local idx = math.random(1, #SHAPES) local shp = SHAPES[idx] local pw = #shp[1] local px = math.floor(GRID_W / 2) - math.floor(pw / 2) + 1 curPiece = { x = px, y = 1, shape = shp, color = idx, } end local function eachBlock(piece, cb) local shape = piece.shape for j = 1, #shape do for i = 1, #shape[j] do if shape[j][i] == 1 then cb(piece.x + i - 1, piece.y + j - 1) end end end end local function validPosition(piece) local ok = true eachBlock(piece, function(x, y) if x < 1 or x > GRID_W or y < 1 or y > GRID_H then ok = false return end if grid[y][x] ~= nil then ok = false return end end) return ok end local function mergePiece() if not curPiece then return end eachBlock(curPiece, function(x, y) if y >= 1 and y <= GRID_H and x >= 1 and x <= GRID_W then grid[y][x] = curPiece.color end end) end -- 返回本次消掉的行数 local function clearLines() local cleared = 0 for y = GRID_H, 1, -1 do local full = true for x = 1, GRID_W do if grid[y][x] == nil then full = false break end end if full then cleared = cleared + 1 -- 下移 for yy = y, 2, -1 do grid[yy] = grid[yy - 1] end local row = {} for x = 1, GRID_W do row[x] = nil end grid[1] = row y = y + 1 end end if cleared > 0 then score = score + cleared * 100 end return cleared end ---------------------------------------------------------------- -- 绘制函数:使用lcd.fill绘制棋盘 ---------------------------------------------------------------- local function drawCell(x, y, color) local cx = BOARD_X + (x - 1) * CELL_SIZE local cy = BOARD_Y + (y - 1) * CELL_SIZE lcd.fill(cx, cy, cx + CELL_SIZE - 2, cy + CELL_SIZE - 2, color) end local function drawBoard() -- 绘制棋盘背景 lcd.fill(BOARD_X, BOARD_Y, BOARD_X + BOARD_W - 1, BOARD_Y + BOARD_H - 1, 0x1082) -- 深灰色背景 -- 绘制固定的方块 for y = 1, GRID_H do for x = 1, GRID_W do local fixed = grid[y][x] if fixed then drawCell(x, y, COLORS[fixed]) else -- 绘制空单元格(带边框效果) local cx = BOARD_X + (x - 1) * CELL_SIZE local cy = BOARD_Y + (y - 1) * CELL_SIZE lcd.fill(cx, cy, cx + CELL_SIZE - 2, cy + CELL_SIZE - 2, 0x0841) -- 更深的灰色 lcd.fill(cx + 1, cy + 1, cx + CELL_SIZE - 3, cy + CELL_SIZE - 3, 0x18C3) -- 浅灰色内框 end end end -- 绘制当前方块 if curPiece then eachBlock(curPiece, function(x, y) if y >= 1 and y <= GRID_H and x >= 1 and x <= GRID_W then drawCell(x, y, COLORS[curPiece.color]) end end) end -- 绘制棋盘边框 lcd.fill(BOARD_X - 2, BOARD_Y - 2, BOARD_X + BOARD_W + 1, BOARD_Y - 1, 0xFFFF) -- 上边框 lcd.fill(BOARD_X - 2, BOARD_Y + BOARD_H, BOARD_X + BOARD_W + 1, BOARD_Y + BOARD_H + 1, 0xFFFF) -- 下边框 lcd.fill(BOARD_X - 2, BOARD_Y - 2, BOARD_X - 1, BOARD_Y + BOARD_H + 1, 0xFFFF) -- 左边框 lcd.fill(BOARD_X + BOARD_W, BOARD_Y - 2, BOARD_X + BOARD_W + 1, BOARD_Y + BOARD_H + 1, 0xFFFF) -- 右边框 end ---------------------------------------------------------------- -- UI更新函数 ---------------------------------------------------------------- local function updateScoreLabel() if scoreLabel then scoreLabel:set_text("分数: " .. tostring(score)) end end local function updateStatusLabel(extra) if not statusLabel then return end if gameOver then statusLabel:set_text("游戏结束! 点击重新开始") else if extra and extra ~= "" then statusLabel:set_text("俄罗斯方块 | " .. extra) else statusLabel:set_text("俄罗斯方块 | 使用按钮游玩") end end end local function redrawAll(extraStatus) updateScoreLabel() updateStatusLabel(extraStatus) drawBoard() end ---------------------------------------------------------------- -- 控制逻辑 ---------------------------------------------------------------- local function stepDown() if gameOver or not curPiece then return end local test = { x = curPiece.x, y = curPiece.y + 1, shape = curPiece.shape, color = curPiece.color, } if validPosition(test) then curPiece = test redrawAll() return end -- 碰到底或碰到已固定方块 mergePiece() local cleared = clearLines() newPiece() if not validPosition(curPiece) then gameOver = true redrawAll("游戏结束!") if gameTimer then sys.timerStop(gameTimer) gameTimer = nil end else if cleared > 0 then redrawAll("消除了 " .. cleared .. " 行!") else redrawAll() end end end local function moveLeft() if gameOver or not curPiece then return end local test = { x = curPiece.x - 1, y = curPiece.y, shape = curPiece.shape, color = curPiece.color, } if validPosition(test) then curPiece = test redrawAll() end end local function moveRight() if gameOver or not curPiece then return end local test = { x = curPiece.x + 1, y = curPiece.y, shape = curPiece.shape, color = curPiece.color, } if validPosition(test) then curPiece = test redrawAll() end end local function softDrop() if gameOver or not curPiece then return end local test = { x = curPiece.x, y = curPiece.y + 1, shape = curPiece.shape, color = curPiece.color, } if validPosition(test) then curPiece = test redrawAll() end end local function rotatePiece() if gameOver or not curPiece then return end local newShape = rotateShape(curPiece.shape) local test = { x = curPiece.x, y = curPiece.y, shape = newShape, color = curPiece.color, } if validPosition(test) then curPiece = test redrawAll("旋转") end end local function restartGame() if gameTimer then sys.timerStop(gameTimer) end score = 0 gameOver = false initGrid() newPiece() redrawAll("重新开始,分数: 0") -- 重新启动定时器 gameTimer = sys.timerLoopStart(stepDown, 400) end ---------------------------------------------------------------- -- 创建游戏UI ---------------------------------------------------------------- function game_page.create_ui() -- 创建主容器 main_container = airui.container({ x = 0, y = 0, w = SCREEN_W, h = SCREEN_H, color = 0x0000, -- 黑色背景 }) -- 标题栏 local title_bar = airui.container({ parent = main_container, x = 0, y = 0, w = SCREEN_W, h = 50, color = 0x1E3A8A, -- 深蓝色 }) -- 游戏标题 airui.label({ parent = title_bar, text = "俄罗斯方块", x = 80, y = 15, w = 160, h = 20, }) local battery_label = common_ui.add_battery_display(title_bar) -- 返回按钮 common_ui.create_back_button(title_bar, game_page.cleanup) -- 分数标签 scoreLabel = airui.label({ parent = main_container, text = "分数: 0", x = 220, y = 70, w = 150, h = 20, }) -- 状态标签 statusLabel = airui.label({ parent = main_container, text = "俄罗斯方块 | 使用按钮游玩", x = 10, y = 70, w = SCREEN_W - 20, h = 20, }) -- 控制按钮区域 local btnY = BOARD_Y + BOARD_H + 10 local btnW, btnH, gap = 70, 30, 10 -- 计算按钮起始X坐标(居中显示) local totalBtnWidth = btnW * 3 + gap * 2 -- 第一行3个按钮 local btnStartX = (SCREEN_W - totalBtnWidth) // 2 -- 第一行按钮 leftBtn = airui.button({ parent = main_container, x = btnStartX, y = btnY, w = btnW, h = btnH, text = "左移", on_click = function() moveLeft() end, }) rightBtn = airui.button({ parent = main_container, x = btnStartX + btnW + gap, y = btnY, w = btnW, h = btnH, text = "右移", on_click = function() moveRight() end, }) rotateBtn = airui.button({ parent = main_container, x = btnStartX + 2 * (btnW + gap), y = btnY, w = btnW, h = btnH, text = "旋转", on_click = function() rotatePiece() end, }) -- 第二行按钮 btnY = btnY + btnH + gap downBtn = airui.button({ parent = main_container, x = btnStartX, y = btnY, w = btnW * 2 + gap, -- 稍宽一点 h = btnH, text = "下落", on_click = function() softDrop() end, }) restartBtn = airui.button({ parent = main_container, x = btnStartX + btnW * 2 + gap * 2, y = btnY, w = btnW, h = btnH, text = "重新开始", on_click = function() restartGame() end, }) end ---------------------------------------------------------------- -- 页面生命周期函数 ---------------------------------------------------------------- function game_page.init(params) math.randomseed(os.time()) -- 初始化游戏状态 initGrid() newPiece() -- 创建UI game_page.create_ui() -- 初始绘制 redrawAll("准备开始!") -- 启动定时器 gameTimer = sys.timerLoopStart(stepDown, 400) end function game_page.cleanup() -- 停止定时器 if gameTimer then sys.timerStop(gameTimer) gameTimer = nil end -- 清理UI if main_container then main_container:destroy() main_container = nil end -- 重置游戏状态 grid = nil curPiece = nil score = 0 gameOver = false leftBtn = nil rightBtn = nil rotateBtn = nil downBtn = nil restartBtn = nil scoreLabel = nil statusLabel = nil end return game_page