ÎÌÌÌ̈ˆˆ¬ªªª¬ˆˆˆ¬ÌÌ̬ÌÀ̬ÌÀ̬ÌÀÌÌÌìÌÀÀî̬ÀÌÀ¬ÀÀÀ¬ÀÀÀ¬ÀÀÎÌÌÌ̈ˆˆ¬ªªª¬ˆˆˆ¬ÌÌ̬ÌÌ̬ÌÀ̬ÌÀÌÌÌìÌÀÀî̬ÀÌ̬ÀÀÀ¬ÀÀÀ¬ÀÀ¬ÌÌ̬ªªª¬ª¬ª¬ªÊ̬ªªªŒˆˆˆÌÀÌÎÌÌÎ̬̪ªÀ쬪ÀÀÀÌîÀìîÌÌîî¬ÌÌ̬ªªª¬ª¬ª¬ªÊ̬ªªªŒˆˆˆÌÀÌÎÌÌÎ̬̪ªÀ쬪ÀÀÀÌîÀìîÌÌîî >i<™™ ™™ ™™™™™™™™™ ™™™™™™ ÌÌÌÌ ÀÌÌÌÀÌ ÀÌÌ ÀÌ ÌÌ ÌÌ ™™™™™™ Ì ÌÌ Ì ÌÌ Ì ÌÌÌ™™™™™™ Ì ÌÌÌ Ì ÌÀ Ì ÌÌ ÌÀ ÌÌÐÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ™™™™™™ ÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ ÌÌÌÌ ÌÌÌ ÌÌ ÌÀ ÌÌÌЙ™™™™™ Ì ÌÌ ÀÌÌ ÀÌ Ì Ì ÀÌ ÌÌ ÌÌ Ð™™™ Й™ Й™ Й™ Й™ Й™ Й™ Й™ ÀÌ ÀÌÀÌÌÌÌÌ ÀÌÐ Ì Ì Ì Ì ÌÌÀ Ð ÀÌÌÌ Ì ÌÌÌ Ð Ì Ì Ì ÌÌÌÌ Ð ÌÌÀÌÀÌÌ ÌÌ ÀÌÐ Ð Ð Ð ÀÌ ÀÌ ÌÀ ÌÌ ÌÀÌÐ ÌÌÌÌ ÌÀÌÌ ÀÌ Ð ÌÌÌÌ ÌÌÌÀÀÌ Ì Ð ÌÌ ÌÌÌ Ì Ð ÌÀÌ ÌÌ ÀÀÌÌÌРРРЈˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ Ð Ð Ð Ð Ð ™™™™ Ð ™™™™ Ð ™™™™ Ð ðÿÿÿðÿð™™™™ Ð ÿÿÿð™™™™ Ð ÿðÿÿÿ™™™™ Ð ÿÿÿð™™™™ Ð ðÿÿÿÿð™™™™ Ð ™™™™ Ð ™™™™ Ð ÐÌÌÌÌÌÌÌÌÌÌÌÌÿÿÿ ÐLDDDDDDDDDDÄ ÐLDDDDDDDDDDÄ ÐLDDDDDDDDDDÄ ÐLDDDDDDDDDDÄ ÐLDDDDDDDDDDÄ ÐLDDDDDDDDDDÄÿÿÿ ÐLDDDÌÌÌÌÌÌÌÌ ÐLDDÄÿÿÿÿÿÿÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ ÐLDDÄ™™™™™™ œ™™™™™™™™™™™™™™É ÐLDDÄ™™™™™™ œ™™™™™™™™™™™™™™É ÐLDDÄ™™™™™™ œ™™™™™™™™™™™™™™É ÐLDDÄ™™™™™™ œ™™™™™™™™™™™™™™É ÐLDDÄ™™™™™™ œ™™™™™™™™™™™™™™É ÐLDDÄ™™™™™™ ÿÿÿÿÿÿœ™™™™™™™™™™™™™™É ÐÌÌÌÌ™™™™™™ ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ Ð™™™™™™ ÌÌÌÌÌÌÌÌÿÿÿ Ð`ffffff™™™™™™ ,""""""Â`ffffff Ð`ffffff™™™™™™ ,""""""Â`ffffff Ð`ffffff™™™™™™ ,""""""Â`ffffff @Ð`ffffff™™™™™™ ,""""""Â`ffffff îîîîàîà@Ð`ffffff™™™™™™ ,""""""Â`ffffff îîîîà@Ð`ffffff™™™™™™ ,""""""Âÿÿÿ`ffffff îîîîàîî@Ð`ffffff,""""""Âfff îàîîîà@Ð`ffffff,""""""Â`ff îîîîîà@Ð`ffffff™™™™™™ ,""""""Â`ffffff`ff @Ð`ffffff™™™™™™ ,""""""Â`ffffff`ff @DÐ`ffffff™™™™™™ ,""""""Â`ffffff`ff @DDÐ`ffffff™™™™™™ ,""""""Â`ffffff`ff @DDÐ`ffffff™™™™™™ ,""""""Â`ffffff`ff Ð`ffffff™™™™™™ ,""""""Â`ffffff`ff Й™™™™™ ÌÌÌÌÌÌÌÌfff`ff Й™™™™™ `ff`ff Й™™™™™ ™™™™™™ ™™™™™™ `ff`ff """""" Й™™™™™ ™™™™™™ ™™™™™™ `ff`ff """""" Й™™™™™ ™™™™™™ ™™™™™™ `ff`ff """""" Й™™™™™ ™™™™™™ ™™™™™™ `ff`ff """""" Й™™™™™ ™™™™™™ ™™™™™™ `ff`ff """""" Й™™™™™ ™™™™™™ ™™™™™™ `ff`ff """""" Й™™™™™ ™™™™™™ `ff """""" Й™™™™™ ™™™™™™ `ff """""" Й™™™™™ @DD™™™™™™ `ff """""" Й™™™™™ @DD™™™™™™ `ff """""" ÐÝÝÝÝÝÐÝ ÐÝ ÝÐ ÐÝÝ ÝÝ Ð™™™™™™ @DD™™™™™™ `ff """""" Ý Ý Ý Ý Ý ÝÝ Ý Ý ÝЙ™™™™™ @DD™™™™™™ `ff """""" Ý Ý Ý Ý ÝÝ ÐÝÝÝ Ý Ý ÝÝЙ™™™™™ @DD™™™™™™ `ff """""" ÝÝ ÝÝÝÝÝ ÝÝ Ý Ý ÐÝÝЙ™™™™™ @DD™™™™™™ `ff """""" Ý Ý Ý ÐÝÝÐ ÝÝ ÐÝ ÝÝ Ð@DD Ð@DD Ð@DDDDDD@DD™™™™™™™™™™ `ff Ð@DDDDDD@DD™™™™™™™™™™ `ff ÝÝ ÝÝÐÝ ÐÝÝ ÝÝÝÝÐÝÝÝÐ@DDDDDD@DD™™™™™™™™™™ `ff ÝÝ ÝÝ ÝÐÐÝ Ý Ý Ý Ð@DDDDDD@DD™™™™™™™™™™ `ff Ð Ý ÝÝÝ Ý Ý Ý Ý Ð@DDDDDD@DD™™™™™™™™™™ `ff ÝÝÝÝÝ ÝÐÐÝ ÝÝÝ ÝÝÐ@DDDDDD@DD™™™™™™™™™™ `ff ÝÝ ÝÐÝ ÐÝÝ ÝÝÝ ÐÝÝÐDDD@DD™™™`ff Ð@DD@DDD™™ `ff Ð@DD@DDDDDD™™ `ff Ð@DD@DDDDDD™™ `ff Ð@DD@DDDDDD™™ `ff Ð@DD@DDDDDD™™ `ff ÐÐÝÝ Ð ÝÝÐÝÐÝ ÐÝÝ Ð@DD@DDDDDD™™ `ff Ý Ý Ð Ý Ý ÝÝ ÝÐÐ@DD@DDDDDD™™ `ff Ý ÝÝ ÐÝ Ý Ý ÝÝ Ý ÐÝÐ@DD`ff ÝÝ ÝÐ ÝÝÝÝ ÝÝ ÝÐÐ@DD`ff ÐÝ Ý ÝÝ ÐÝÝÐÝÝ Ð@DD@DDDDDDDDDD@DD`ff Ð@DD@DDDDDDDDDD@DD`ff Ð@DD@DDDDDDDDDD@DD`ff Ð@DD@DDDDDDDDDD@DD`ff ÝÐ ÐÝÐÝ ÐÝÝ ÐÝ Ý ÐÝÝÝÝÝ ÐÝÐÝÝÐÝÝÝÐÐ@DD@DDDDDDDDDD@DD`ff ÝÝ Ý ÝÝ Ý Ý Ý Ý Ý Ý Ý Ý ÝÝ Ý Ð@DD@DDDDDDDDDD@DD`ff ÝÝ Ý ÝÝÝÝ ÐÝÝÝ Ý Ý ÝÝÐÝÝÝ ÝÝ Ý ÐDDDD@DD`ff ÝÝ ÝÝ Ý Ý Ý ÝÝ ÝÝÝ Ý Ý ÝÝ ÝÝ Ð@DDDDDD`ff Ý ÝÐÝÝ ÝÝÝ Ý ÝÝÝ ÐÝÐÝÝÝ ÐÝÝ ÐЙ™™™™™™™™™™™™™ @DD@DDDDDDDDDD`ff Й™™™™™™™™™™™™™ @DD@DDDDDDDDDD`ff Й™™™™™™™™™™™™™ @DD@DDDDDDDDDD`ff Й™™™™™™™™™™™™™ @DD@DDDDDDDDDD`ff Ý Ð ÝÝÐÝÐÝ ÐÝÐÝ ÝÝ ÐÝÐ ÝÝÐÝÐÝÝ Ð™™™™™™™™™™™™™™ @DD@DDDDDDDDDD`ff Ý Ð Ý Ý ÝÝ ÝÝÝ Ð Ý Ý Ý ÝÐЙ™™™™™™™™™™™™™ @DD@DDDDDDDDDD`ff ÐÝÝ Ý ÝÝ ÝÝÝÝ ÝÝÝ ÝÝ Ð Ý Ð ÝÝÝ ÝÝÝ ÝÝÝÝ Ð Ý ÝÝ Ý ÝÐÐÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝ Ý Ð Ý ÐÝÝÝ ÝÝÝ Ý Ð ÝÝÝ ÐÝÝ H-- title: Reorganize -- author: frantisek -- desc: Dig through a colorful heap and free the requested blocks, one piece at a time. -- script: lua -- ============================================================ -- REORGANIZE -- A Tetris-flavoured puzzle about digging, not dropping. -- -- The board starts filled with a heap of tetromino pieces. Each -- of the 4 shapes (I, O, T, L) can appear in any of the 4 -- colors, so color and shape are independent - the game asks -- you for a specific SHAPE + COLOR combo (shown top right as a -- little preview), in ANY rotation. The requested combo is -- always guaranteed to actually exist somewhere in the heap. -- -- Pieces that are truly free right now (nothing at all above -- them) are outlined in a bright highlight so you can see your -- options at a glance. Move the cursor over the heap and press -- Z on a highlighted piece: -- - if its shape and color match the request (whatever -- rotation it happens to be sitting in), it vanishes, you -- score a point, AND a brand new random piece appears that -- you are forced to place somewhere on the heap (it's -- deliberately never the same combo as the new request, so -- you can't just feed it straight back for a free point). -- - otherwise you pick it up. Move it left/right, rotate it -- with X (handy for fitting it into a gap), and drop it -- back with Z somewhere else on the heap to dig deeper. -- Press A to cancel and put it back exactly where it came -- from. -- You may NOT rest a piece above the dashed limit line, so you -- have to plan where you shuffle things instead of just piling -- everything on the left or right. -- -- You're racing the clock: 60 seconds on the clock, +5 seconds -- for every block you correctly fetch. A rising beep plays each -- of the last 10 seconds before time runs out. Miss the clock -- and it's GAME OVER. -- ============================================================ local COLS, ROWS, CELL = 12, 15, 8 local BX, BY = 8, 8 local LIMIT_ROW = 3 -- palette indices for the 4 piece colors (sweetie-16 default palette) local COL = {2, 4, 6, 9} -- red, yellow, green, blue -- the 4 tetromino shapes, independent of color - any shape can be -- generated in any color local shapesBase = { [1] = {{0,0},{1,0},{2,0},{3,0}}, -- I [2] = {{0,0},{1,0},{0,1},{1,1}}, -- O [3] = {{0,0},{1,0},{2,0},{1,1}}, -- T [4] = {{0,0},{0,1},{0,2},{1,2}}, -- L } local function deepcopyCells(cells) local t = {} for i, c in ipairs(cells) do t[i] = {c[1], c[2]} end return t end local function rotateCW(cells) local nc = {} for i, c in ipairs(cells) do nc[i] = {c[2], -c[1]} end local minx, miny = math.huge, math.huge for _, c in ipairs(nc) do if c[1] < minx then minx = c[1] end if c[2] < miny then miny = c[2] end end for _, c in ipairs(nc) do c[1] = c[1] - minx c[2] = c[2] - miny end return nc end local function shapeWidth(cells) local maxdx = 0 for _, c in ipairs(cells) do if c[1] > maxdx then maxdx = c[1] end end return maxdx + 1 end -- precompute the 4 rotation states for each shape type local shapeRotations = {} for shapeType = 1, 4 do local states = {} local cur = deepcopyCells(shapesBase[shapeType]) for r = 1, 4 do states[r] = cur cur = rotateCW(cur) end shapeRotations[shapeType] = states end -- board state: boardColor/boardPiece are ROWS x COLS grids, 0-indexed local boardColor, boardPiece = {}, {} for y = 0, ROWS - 1 do boardColor[y] = {} boardPiece[y] = {} for x = 0, COLS - 1 do boardColor[y][x] = 0 boardPiece[y][x] = 0 end end local pieces = {} -- id -> {shapeType=, color=, cells={ {x,y}, ... }, rot=} local nextId = 1 local function boardTop(x) for y = 0, ROWS - 1 do if boardPiece[y][x] ~= 0 then return y end end return ROWS end -- given relative shape offsets and an anchor column, find the row the -- shape would land on if dropped straight down onto the current heap local function dropLanding(cellsRel, ax) local colMax = {} for _, c in ipairs(cellsRel) do local col = ax + c[1] if col < 0 or col >= COLS then return nil end if colMax[col] == nil or c[2] > colMax[col] then colMax[col] = c[2] end end local landingY = nil for col, maxdy in pairs(colMax) do local bt = boardTop(col) local candidate = bt - 1 - maxdy if landingY == nil or candidate < landingY then landingY = candidate end end return landingY end local function placeAt(shapeType, colorId, rot, ax, ay, cellsRel) local id = nextId nextId = nextId + 1 local abscells = {} for i, c in ipairs(cellsRel) do local X, Y = ax + c[1], ay + c[2] abscells[i] = {X, Y} boardColor[Y][X] = colorId boardPiece[Y][X] = id end pieces[id] = {shapeType = shapeType, color = colorId, cells = abscells, rot = rot} return id end local function restoreCells(shapeType, color, rot, cells) local id = nextId nextId = nextId + 1 pieces[id] = {shapeType = shapeType, color = color, cells = cells, rot = rot} for _, c in ipairs(cells) do boardColor[c[2]][c[1]] = color boardPiece[c[2]][c[1]] = id end return id end local function removePiece(id) local p = pieces[id] for _, c in ipairs(p.cells) do boardColor[c[2]][c[1]] = 0 boardPiece[c[2]][c[1]] = 0 end pieces[id] = nil end -- a piece is only "free" if there is truly nothing above it - check -- every row above its topmost cell in each column it occupies, not -- just the one cell directly above local function isAccessible(id) local p = pieces[id] if not p then return false end local colTop = {} for _, c in ipairs(p.cells) do local x, y = c[1], c[2] if colTop[x] == nil or y < colTop[x] then colTop[x] = y end end for x, topY in pairs(colTop) do for yy = 0, topY - 1 do if boardPiece[yy][x] ~= 0 then return false end end end return true end -- build the initial heap out of real placed pieces (so every cell on -- the board genuinely belongs to some tetromino you can pick up) local function buildInitialHeap() for i = 1, 60 do local shapeType = math.random(1, 4) local colorId = math.random(1, 4) local rot = math.random(1, 4) local cellsRel = shapeRotations[shapeType][rot] local w = shapeWidth(cellsRel) if w <= COLS then local ax = math.random(0, COLS - w) local landingY = dropLanding(cellsRel, ax) if landingY then local ok = true for _, c in ipairs(cellsRel) do local ry = landingY + c[2] if ry < LIMIT_ROW + 2 or ry >= ROWS then ok = false break end end if ok then placeAt(shapeType, colorId, rot, ax, landingY, cellsRel) end end end end end buildInitialHeap() -- always pick a target/next combo from a piece that ACTUALLY exists on -- the board right now, so the request is always obtainable. Optionally -- excludes one combo (used so "next" doesn't just repeat "target" - -- if it did, and that combo only had ONE piece left, fetching target -- would make "next" unobtainable the moment it got promoted). local function pickExistingCombo(excludeShape, excludeColor) local allIds, filteredIds = {}, {} for id, p in pairs(pieces) do allIds[#allIds + 1] = id if not (p.shapeType == excludeShape and p.color == excludeColor) then filteredIds[#filteredIds + 1] = id end end local pool = (#filteredIds > 0) and filteredIds or allIds if #pool == 0 then return nil, nil end local p = pieces[pool[math.random(1, #pool)]] return p.shapeType, p.color end local score = 0 local gameWon = false local gameOver = false local targetShape, targetColor = pickExistingCombo() local nextShape, nextColor = pickExistingCombo(targetShape, targetColor) -- pick the (shape,color) combo that is currently LEAST common on the -- board, so newly spawned pieces keep the heap varied instead of -- letting one shape/color pile up. excludeShape/excludeColor is kept -- out of consideration entirely (used to avoid matching the target). local function pickRareSpawnCombo(excludeShape, excludeColor) local shapeCount, colorCount = {0, 0, 0, 0}, {0, 0, 0, 0} for _, p in pairs(pieces) do shapeCount[p.shapeType] = shapeCount[p.shapeType] + 1 colorCount[p.color] = colorCount[p.color] + 1 end local bestScore = math.huge local candidates = {} for s = 1, 4 do for c = 1, 4 do if not (s == excludeShape and c == excludeColor) then local score2 = shapeCount[s] + colorCount[c] if score2 < bestScore then bestScore = score2 candidates = {{s, c}} elseif score2 == bestScore then candidates[#candidates + 1] = {s, c} end end end end local pick = candidates[math.random(1, #candidates)] return pick[1], pick[2] end local function computeAccessibleSet() local acc = {} for id, _ in pairs(pieces) do if isAccessible(id) then acc[id] = true end end return acc end local state = "select" -- "select", "hold" (repositioning a board piece), or "spawn" (must place a new piece) local cx, cy = math.floor(COLS / 2), LIMIT_ROW + 1 local holdShape, holdColor, holdRot, holdAnchorX local holdOrigShape, holdOrigColor, holdOrigRot, holdOrigCells local msg, msgTimer = "", 0 local t = 0 -- timer: 60s to start, +15s for every correctly fetched block local START_SECONDS, BONUS_SECONDS = 60, 5 local timeFrames = START_SECONDS * 60 local prevSecondsLeft = START_SECONDS local function setMsg(text, ttl) msg = text msgTimer = ttl end -- draw a set of shape-relative cells (dx,dy) as ONE solid connected -- shape: filled cells with an outline only around the true perimeter, -- so touching cells that belong to the same piece read as one block. local function drawShapeCells(cellsRel, px0, py0, colorId, scale) local set = {} for _, c in ipairs(cellsRel) do set[c[1] .. "," .. c[2]] = true end for _, c in ipairs(cellsRel) do local cx0 = px0 + c[1] * scale local cy0 = py0 + c[2] * scale rect(cx0, cy0, scale, scale, COL[colorId]) if not set[c[1] .. "," .. (c[2] - 1)] then line(cx0, cy0, cx0 + scale - 1, cy0, 0) end if not set[c[1] .. "," .. (c[2] + 1)] then line(cx0, cy0 + scale - 1, cx0 + scale - 1, cy0 + scale - 1, 0) end if not set[(c[1] - 1) .. "," .. c[2]] then line(cx0, cy0, cx0, cy0 + scale - 1, 0) end if not set[(c[1] + 1) .. "," .. c[2]] then line(cx0 + scale - 1, cy0, cx0 + scale - 1, cy0 + scale - 1, 0) end end end local function drawBoard(accessibleSet) rectb(BX - 1, BY - 1, COLS * CELL + 2, ROWS * CELL + 2, 13) local blink = math.floor(t / 15) % 2 == 0 local highlightCol = blink and 12 or 14 for y = 0, ROWS - 1 do for x = 0, COLS - 1 do local c = boardColor[y][x] if c ~= 0 then local pid = boardPiece[y][x] local px, py = BX + x * CELL, BY + y * CELL rect(px, py, CELL, CELL, COL[c]) -- outline only on edges that border a DIFFERENT piece (or empty), -- so cells belonging to the same piece merge into one shape and -- two touching pieces of the same color stay visually separate local function borders(nx, ny) if nx < 0 or nx >= COLS or ny < 0 or ny >= ROWS then return true end return boardPiece[ny][nx] ~= pid end local edgeCol = accessibleSet[pid] and highlightCol or 0 if borders(x, y - 1) then line(px, py, px + CELL - 1, py, edgeCol) end if borders(x, y + 1) then line(px, py + CELL - 1, px + CELL - 1, py + CELL - 1, edgeCol) end if borders(x - 1, y) then line(px, py, px, py + CELL - 1, edgeCol) end if borders(x + 1, y) then line(px + CELL - 1, py, px + CELL - 1, py + CELL - 1, edgeCol) end end end end for x = 0, COLS - 1, 2 do line(BX + x * CELL, BY + LIMIT_ROW * CELL, BX + x * CELL + CELL - 2, BY + LIMIT_ROW * CELL, 8) end end local function drawCursor() local blink = math.floor(t / 15) % 2 == 0 local col = blink and 12 or 15 rectb(BX + cx * CELL - 1, BY + cy * CELL - 1, CELL + 2, CELL + 2, col) end local function drawHold() local cellsRel = shapeRotations[holdShape][holdRot] local hoverY = BY - 14 local landingY = dropLanding(cellsRel, holdAnchorX) if landingY then for _, c in ipairs(cellsRel) do local gx = BX + (holdAnchorX + c[1]) * CELL local gy = BY + (landingY + c[2]) * CELL rectb(gx, gy, CELL - 1, CELL - 1, 15) end end drawShapeCells(cellsRel, BX + holdAnchorX * CELL, hoverY, holdColor, CELL) end local function drawUI() print("REORGANIZE", 120, 4, 12, true) local secondsLeft = math.ceil(timeFrames / 60) local timeCol = (secondsLeft <= 10) and 2 or 12 print("SCORE " .. score, 120, 16, 12, true) print("TIME " .. secondsLeft .. "s", 120, 24, timeCol, true) if gameOver then print("GAME OVER", 120, 40, 2, true) print("FINAL SCORE " .. score, 120, 50, 12, true) return end if gameWon then print("ALL CLEARED!", 120, 40, 12, true) print("YOU WIN!", 120, 50, 12, true) else print("GET:", 120, 40, 15, true) drawShapeCells(shapeRotations[targetShape][1], 152, 36, targetColor, 6) print("NEXT:", 120, 68, 14, true) drawShapeCells(shapeRotations[nextShape][1], 152, 66, nextColor, 4) end if state == "spawn" then print("NEW PIECE!", 120, 90, 11, true) print("PLACE IT (Z)", 120, 98, 11, true) else print("ARROWS MOVE", 120, 90, 13, true) print("Z PICK/DROP", 120, 98, 13, true) end print("(ANY ROT OK -", 120, 108, 13, true) print(" MATCH SHAPE+COLOR)", 120, 116, 13, true) print("X:ROTATE A:BACK", 120, 124, 13, true) if msgTimer > 0 then print(msg, 8, 130, 12, true) end end function TIC() t = t + 1 cls(0) if not gameOver and not gameWon then timeFrames = timeFrames - 1 if timeFrames < 0 then timeFrames = 0 end local secondsLeft = math.ceil(timeFrames / 60) if secondsLeft < prevSecondsLeft then if secondsLeft > 0 and secondsLeft <= 10 then sfx(0, "A-3", 8, 1, 10) -- countdown tick end prevSecondsLeft = secondsLeft end if timeFrames <= 0 then gameOver = true sfx(0, "C-2", 45, 2, 15) -- game over sound end end if not gameOver and not gameWon then if state == "select" then if btnp(0, 10, 4) then cy = math.max(0, cy - 1) end if btnp(1, 10, 4) then cy = math.min(ROWS - 1, cy + 1) end if btnp(2, 10, 4) then cx = math.max(0, cx - 1) end if btnp(3, 10, 4) then cx = math.min(COLS - 1, cx + 1) end if btnp(4) then local pid = boardPiece[cy][cx] if pid == 0 then setMsg("EMPTY CELL", 40) elseif not isAccessible(pid) then setMsg("BURIED - CLEAR AROUND IT", 40) else local p = pieces[pid] local origCells = deepcopyCells(p.cells) local origShape = p.shapeType local origColor = p.color local origRot = p.rot removePiece(pid) if origShape == targetShape and origColor == targetColor then score = score + 10 timeFrames = timeFrames + BONUS_SECONDS * 60 prevSecondsLeft = math.ceil(timeFrames / 60) sfx(0, "C-5", 15, 0, 15) -- found it! targetShape, targetColor = nextShape, nextColor nextShape, nextColor = pickExistingCombo(targetShape, targetColor) if targetShape == nil then -- board is now completely empty gameWon = true else -- force a fresh piece into play so the heap keeps evolving - -- deliberately the rarest shape+color combo currently on the -- board (to keep things varied), and never the new target -- combo (so it can't just be fed straight back for a point) local spawnShape, spawnColor = pickRareSpawnCombo(targetShape, targetColor) holdShape = spawnShape holdColor = spawnColor holdRot = 1 local w = shapeWidth(shapeRotations[holdShape][holdRot]) holdAnchorX = math.min(math.floor(COLS / 2) - 1, COLS - w) state = "spawn" end setMsg("CLEARED! +10 +15s", 30) else holdShape = origShape holdColor = origColor holdRot = origRot local minx = math.huge for _, c in ipairs(origCells) do if c[1] < minx then minx = c[1] end end holdAnchorX = minx holdOrigShape = origShape holdOrigColor = origColor holdOrigRot = origRot holdOrigCells = origCells state = "hold" end end end elseif state == "hold" or state == "spawn" then local cellsRel = shapeRotations[holdShape][holdRot] local w = shapeWidth(cellsRel) if btnp(2, 10, 4) then holdAnchorX = math.max(0, holdAnchorX - 1) end if btnp(3, 10, 4) then holdAnchorX = math.min(COLS - w, holdAnchorX + 1) end if btnp(5) then holdRot = holdRot % 4 + 1 cellsRel = shapeRotations[holdShape][holdRot] w = shapeWidth(cellsRel) if holdAnchorX > COLS - w then holdAnchorX = COLS - w end end if state == "hold" and btnp(6) then restoreCells(holdOrigShape, holdOrigColor, holdOrigRot, holdOrigCells) state = "select" elseif btnp(4) then local landingY = dropLanding(cellsRel, holdAnchorX) if landingY == nil then setMsg("CAN'T PLACE THERE", 30) else local tooHigh = false for _, c in ipairs(cellsRel) do if landingY + c[2] < LIMIT_ROW then tooHigh = true break end end if tooHigh then setMsg("TOO HIGH! TRY ANOTHER SPOT", 40) else placeAt(holdShape, holdColor, holdRot, holdAnchorX, landingY, cellsRel) state = "select" end end end end end local accessibleSet = computeAccessibleSet() drawBoard(accessibleSet) if state == "select" then drawCursor() end if state == "hold" or state == "spawn" then drawHold() end drawUI() if msgTimer > 0 then msgTimer = msgTimer - 1 end end -- -- 000:00000000ffffffff00000000ffffffff -- 001:0123456789abcdeffedcba9876543210 -- -- -- 000:50003000300020002000200030003000300030004000400050005000600060007000800090009000a000b000b000c000c000d000e000e000f000f000304000000000 --