-- NAME: Tetris -- DESC: Stack falling blocks, clear lines -- Game states local STATE_MENU = 0 local STATE_PLAYING = 1 local STATE_GAME_OVER = 2 -- Game constants local GRID_WIDTH = 10 local GRID_HEIGHT = 20 local CELL_SIZE = 8 local SPAWN_RATE = 30 -- Frames before piece drops -- Tetromino shapes (4 orientations each) local TETROMINOS = { -- I piece { {{0, 0}, {1, 0}, {2, 0}, {3, 0}}, {{0, 0}, {0, 1}, {0, 2}, {0, 3}}, {{0, 0}, {1, 0}, {2, 0}, {3, 0}}, {{0, 0}, {0, 1}, {0, 2}, {0, 3}} }, -- O piece { {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}}, {{0, 0}, {1, 0}, {0, 1}, {1, 1}} }, -- T piece { {{1, 0}, {0, 1}, {1, 1}, {2, 1}}, {{1, 0}, {0, 1}, {1, 1}, {1, 2}}, {{0, 1}, {1, 1}, {2, 1}, {1, 2}}, {{1, 0}, {1, 1}, {2, 1}, {1, 2}} }, -- S piece { {{1, 0}, {2, 0}, {0, 1}, {1, 1}}, {{0, 0}, {0, 1}, {1, 1}, {1, 2}}, {{1, 0}, {2, 0}, {0, 1}, {1, 1}}, {{0, 0}, {0, 1}, {1, 1}, {1, 2}} }, -- Z piece { {{0, 0}, {1, 0}, {1, 1}, {2, 1}}, {{1, 0}, {0, 1}, {1, 1}, {0, 2}}, {{0, 0}, {1, 0}, {1, 1}, {2, 1}}, {{1, 0}, {0, 1}, {1, 1}, {0, 2}} }, -- J piece { {{0, 0}, {0, 1}, {1, 1}, {2, 1}}, {{1, 0}, {2, 0}, {1, 1}, {1, 2}}, {{0, 1}, {1, 1}, {2, 1}, {2, 0}}, {{1, 0}, {1, 1}, {0, 2}, {1, 2}} }, -- L piece { {{2, 0}, {0, 1}, {1, 1}, {2, 1}}, {{1, 0}, {1, 1}, {1, 2}, {2, 2}}, {{0, 1}, {1, 1}, {2, 1}, {0, 0}}, {{0, 0}, {1, 0}, {1, 1}, {1, 2}} } } function init() game.vars.state = STATE_MENU game.vars.score = 0 game.vars.level = 1 game.vars.lines = 0 -- Grid (0 = empty, 1 = filled) game.vars.grid = {} for y = 1, GRID_HEIGHT do game.vars.grid[y] = {} for x = 1, GRID_WIDTH do game.vars.grid[y][x] = 0 end end -- Current piece game.vars.piece = nil game.vars.piece_x = 0 game.vars.piece_y = 0 game.vars.piece_type = 0 game.vars.piece_rotation = 0 -- Animation game.vars.frame_count = 0 game.vars.clear_rows = {} game.vars.clearing = false -- Enable continuous updates game.set_frame_updates(true) print("Tetris initialized") end function update(event) local state = game.vars.state if state == STATE_MENU then if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then reset_game() game.vars.state = STATE_PLAYING return true end elseif state == STATE_PLAYING then -- Handle input if event.type == INPUT.TOUCH_DOWN then if event.x < game.width() / 2 then -- Left side: move left if can_move(game.vars.piece, game.vars.piece_x - 1, game.vars.piece_y, game.vars.piece_rotation) then game.vars.piece_x = game.vars.piece_x - 1 end else -- Right side: move right if can_move(game.vars.piece, game.vars.piece_x + 1, game.vars.piece_y, game.vars.piece_rotation) then game.vars.piece_x = game.vars.piece_x + 1 end end return true end -- Update physics on frame tick if event.type == INPUT.FRAME_TICK then game.vars.frame_count = game.vars.frame_count + 1 if game.vars.clearing then -- Clear animation if game.vars.frame_count % 10 == 0 then clear_lines() game.vars.clearing = false end else -- Drop piece if game.vars.frame_count >= SPAWN_RATE then game.vars.frame_count = 0 if can_move(game.vars.piece, game.vars.piece_x, game.vars.piece_y + 1, game.vars.piece_rotation) then game.vars.piece_y = game.vars.piece_y + 1 else -- Lock piece lock_piece() -- Check for complete lines local complete = check_complete_lines() if #complete > 0 then game.vars.clear_rows = complete game.vars.clearing = true else spawn_piece() end end end end return true end elseif state == STATE_GAME_OVER then if event.type == INPUT.TOUCH_DOWN or event.type == INPUT.BUTTON_0 or event.type == INPUT.BUTTON_1 then game.vars.state = STATE_MENU return true end end return false end function draw() renderer.clear(false) -- Black background local state = game.vars.state if state == STATE_MENU then renderer.text_scaled(game.width() / 2 - 20, game.height() / 2 - 30, "TETRIS", true, 2) renderer.text_scaled(game.width() / 2 - 50, game.height() / 2, "Tap to Start", true, 2) elseif state == STATE_PLAYING or state == STATE_GAME_OVER then -- Draw grid local start_x = 20 local start_y = 15 for y = 1, GRID_HEIGHT do for x = 1, GRID_WIDTH do if game.vars.grid[y][x] == 1 then renderer.rect(start_x + (x - 1) * CELL_SIZE, start_y + (y - 1) * CELL_SIZE, CELL_SIZE, CELL_SIZE, true, true) end end end -- Draw current piece if game.vars.piece then for _, block in ipairs(game.vars.piece) do local x = start_x + (game.vars.piece_x + block[1]) * CELL_SIZE local y = start_y + (game.vars.piece_y + block[2]) * CELL_SIZE renderer.rect(x, y, CELL_SIZE, CELL_SIZE, true, true) end end -- Draw score renderer.text_scaled(game.width() - 50, 10, "Score: " .. tostring(game.vars.score), true, 2) renderer.text_scaled(game.width() - 50, 20, "Lines: " .. tostring(game.vars.lines), true, 2) if state == STATE_GAME_OVER then renderer.text_scaled(game.width() / 2 - 40, game.height() / 2, "GAME OVER", true, 2) renderer.text_scaled(game.width() / 2 - 50, game.height() / 2 + 20, "Tap to Menu", true, 2) end end end function spawn_piece() game.vars.piece_type = math.random(1, #TETROMINOS) game.vars.piece_rotation = 1 game.vars.piece = TETROMINOS[game.vars.piece_type][game.vars.piece_rotation] game.vars.piece_x = 3 game.vars.piece_y = 0 -- Check if game over if not can_move(game.vars.piece, game.vars.piece_x, game.vars.piece_y, game.vars.piece_rotation) then game.vars.state = STATE_GAME_OVER end end function lock_piece() if not game.vars.piece then return end for _, block in ipairs(game.vars.piece) do local x = game.vars.piece_x + block[1] + 1 local y = game.vars.piece_y + block[2] + 1 if y >= 1 and y <= GRID_HEIGHT and x >= 1 and x <= GRID_WIDTH then game.vars.grid[y][x] = 1 end end end function can_move(piece, x, y, rotation) if not piece then return false end for _, block in ipairs(piece) do local grid_x = x + block[1] + 1 local grid_y = y + block[2] + 1 if grid_x < 1 or grid_x > GRID_WIDTH or grid_y < 1 or grid_y > GRID_HEIGHT then return false end if game.vars.grid[grid_y][grid_x] == 1 then return false end end return true end function check_complete_lines() local complete = {} for y = 1, GRID_HEIGHT do local full = true for x = 1, GRID_WIDTH do if game.vars.grid[y][x] == 0 then full = false break end end if full then table.insert(complete, y) end end return complete end function clear_lines() for _, y in ipairs(game.vars.clear_rows) do -- Remove line table.remove(game.vars.grid, y) -- Add empty line at top table.insert(game.vars.grid, 1, {}) for x = 1, GRID_WIDTH do game.vars.grid[1][x] = 0 end end game.vars.score = game.vars.score + (#game.vars.clear_rows * 100) game.vars.lines = game.vars.lines + #game.vars.clear_rows game.vars.clear_rows = {} spawn_piece() end function reset_game() game.vars.score = 0 game.vars.level = 1 game.vars.lines = 0 game.vars.frame_count = 0 game.vars.clearing = false -- Clear grid for y = 1, GRID_HEIGHT do for x = 1, GRID_WIDTH do game.vars.grid[y][x] = 0 end end spawn_piece() end