hello121311

BlockBlast Retro by DinnerUnlucky4661

Sep 6th, 2025
1,382
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 53.84 KB | Source Code | 0 0
  1. GRID_SIZE=8
  2. CELL_SIZE=8
  3. GRID_X=40
  4. GRID_Y=56
  5. PREVIEW_BOX=32
  6. PREVIEW_GAP=6
  7. RIGHT_MARGIN=8
  8. RIGHT_PANEL_X=240-RIGHT_MARGIN-PREVIEW_BOX
  9. RIGHT_PANEL_TOP=(136-(3*PREVIEW_BOX+2*PREVIEW_GAP))//2+5
  10. SCORE_Y=18
  11. SCORE_WIDTH=120
  12. SCORE_HEIGHT=26
  13. SCORE_X=8
  14. PREVIEW_CELL_CAP=CELL_SIZE
  15. PREVIEW_MIN_CELL=4
  16. SHAKE_MULT=0.15
  17. SHAKE_MAX=0.8
  18. FLASH_FRAMES=6
  19. WAVE_TIME=10
  20. LINE_WIDTH_MAX=6
  21. ENABLE_SCREEN_FLASH=false
  22. BG_COLORS={{r=48,g=0,b=73},{r=74,g=0,b=114},{r=213,g=132,b=231},{r=0,g=131,b=191},{r=0,g=105,b=92}}
  23. grace_max=3
  24. grace_remaining=0
  25. warning_active=false
  26. warning_beep_timer=0
  27. flash_color=12
  28. heart_beat_timer=0
  29. turns_since_line_clear=0
  30. line_guarantee_active=false
  31. grid={}
  32. score=0
  33. best_score=0
  34. combo=0
  35. next_shapes={}
  36. dragging=nil
  37. mouse_x=0
  38. mouse_y=0
  39. game_over=false
  40. clearing_cells={}
  41. particles={}
  42. shake=0
  43. flash=0
  44. score_popups={}
  45. wave_effect=0
  46. rainbow_offset=0
  47. line_clear_effects={}
  48. bg_time=0
  49. sound_events={}
  50. music_time=0
  51. music_playing=true
  52. last_beat=0
  53. beat_duration=240
  54. title_glow=0
  55. title_float=0
  56. shapes={{{1}},{{1,1}},{{1},{1}},{{1,1,1}},{{1},{1},{1}},{{1,0},{1,0},{1,1}},{{0,1},{0,1},{1,1}},{{1,1,1},{0,1,0}},{{1,1},{1,1}},{{0,1,0},{1,1,1}},{{1,1,0},{0,1,1}},{{0,1,1},{1,1,0}},{{1,1,1,1}},{{1},{1},{1},{1}},{{1,0,0},{1,1,1}},{{0,0,1},{1,1,1}},{{1,0},{1,0},{1,0},{1,1}},{{0,1},{0,1},{0,1},{1,1}},{{1,0,1},{1,1,1}},{{1,0,0},{1,1,0},{0,1,1}},{{1,1,1},{1,1,1}},{{1,1,1},{1,1,1},{1,1,1}},{{1,1},{1,1},{1,0}},{{0,1,1},{1,1,0}},{{1,1,0},{0,1,1}},{{1,1,1},{0,1,0}}}
  57. colors={2,3,4,5,6,7,8,9,10,11}
  58. USE_WEIGHTED_BAG=true
  59. WB_R_TRIES=3
  60. WB_P_FAIL=0.15
  61. WB_PITY_CAP=2
  62. WB_WINDOW=20
  63. WB_MAX_MICRO_IN_WIN=4
  64. WB_MAX_SMALL_IN_WIN=10
  65. WB_MAX_MEDIUM_IN_WIN=9
  66. WB_MAX_LARGE_IN_WIN=6
  67. WB_MAX_AWK_IN_WIN=4
  68. WB_W_MICRO=1.00
  69. WB_W_SMALL=0.85
  70. WB_W_MEDIUM=0.60
  71. WB_W_LARGE=0.35
  72. WB_W_AWK=0.20
  73. WB_CRAMPED_LARGEST_PATCH=5
  74. WB_CRAMPED_HOLES=3
  75. wb_recent_classes={}
  76. wb_consec_dead=0
  77. function sfx_pick()
  78.  sfx(0, 28, 6, 0, 8, 0)
  79. end
  80. function sfx_place()
  81.  playBlockPlace()
  82. end
  83. function sfx_invalid()
  84.  sfx(0, 18, 10, 0, 8, 0)
  85. end
  86. function sfx_clear(cellcount)
  87.  if cellcount > 8 then
  88.   playCombo() -- Big combo sound
  89.  else
  90.   playLineClear() -- Regular line clear
  91.  end
  92. end
  93. function sfx_streak_up(level)
  94.  if level <= 2 then
  95.   sfx(0, 24, 6, 0, 8, 0)   -- Low C
  96.   sfx(1, 28, 6, 1, 7, 0)   -- Low E
  97.  elseif level <= 4 then
  98.   sfx(0, 24, 6, 0, 9, 0)   -- Low C
  99.   sfx(1, 28, 6, 1, 8, 0)   -- Low E
  100.   sfx(2, 31, 6, 2, 7, 0)   -- Low G
  101.  else
  102.   sfx(0, 24, 6, 0, 10, 0)  -- Low C
  103.   sfx(1, 28, 6, 1, 9, 0)   -- Low E
  104.   sfx(2, 31, 6, 2, 8, 0)   -- Low G
  105.   sfx(3, 36, 8, 3, 10, 0)  -- Mid C (still reasonable)
  106.  end
  107. end
  108. function sfx_warning_tick()
  109.  sfx(0, 30, 4, 0, 6, 0) -- Low gentle beep
  110. end
  111. function sfx_streak_break()
  112.  sfx(0, 32, 8, 0, 10, 0)  -- Low notes only
  113.  sfx(1, 28, 8, 1, 9, 0)
  114.  sfx(2, 24, 10, 2, 8, 0)
  115.  sfx(3, 20, 12, 3, 10, 0) -- Very low final note
  116. end
  117. function sfx_game_over()
  118.  sfx(0, 31, 15, 0, 10, 0)  -- Low G
  119.  sfx(1, 28, 15, 1, 9, 0)   -- Low E
  120.  sfx(2, 24, 15, 2, 8, 0)   -- Low C
  121.  sfx(3, 18, 20, 3, 10, 0)  -- Very low ending
  122. end
  123. function sfx_restart()
  124.  sfx(0, 24, 8, 0, 8, 0)   -- Low C
  125.  sfx(1, 28, 8, 1, 7, 0)   -- Low E
  126.  sfx(2, 31, 8, 2, 7, 0)   -- Low G
  127.  sfx(3, 36, 10, 3, 8, 0)  -- Mid C (gentle)
  128. end
  129. function setupMusic()
  130. end
  131. function update_music()
  132.  if not music_playing then return end
  133. end
  134. function playBlockPlace()
  135.  sfx(0, 24, 8, 0, 10, 0) -- Deep woody sound
  136. end
  137. function playLineClear()
  138.  sfx(1, 32, 12, 1, 8, 0) -- Gentle celebration
  139. end
  140. function playCombo()
  141.  sfx(2, 40, 15, 2, 6, 0) -- Happy but reasonable pitch
  142. end
  143. local function update_warning_audio()
  144.  if warning_active then
  145.   warning_beep_timer = warning_beep_timer - 1
  146.   local period = math.max(20, 60 - combo*8)  -- Less frequent beeping
  147.   if warning_beep_timer<=0 then
  148.     sfx_warning_tick()
  149.     warning_beep_timer = period
  150.   end
  151.  else
  152.   warning_beep_timer = 0
  153.  end
  154. end
  155. function lerp_color(c1, c2, t)
  156.  return {
  157.   r=math.floor(c1.r + (c2.r - c1.r) * t),
  158.   g=math.floor(c1.g + (c2.g - c1.g) * t),
  159.   b=math.floor(c1.b + (c2.b - c1.b) * t)
  160.  }
  161. end
  162. function rgb_to_tic_color(color)
  163.  local r, g, b = color.r, color.g, color.b
  164.  if r < 50 and g < 50 and b > 50 then return 1 end
  165.  if r < 100 and g < 50 and b > 80 then return 2 end
  166.  if r > 150 and g > 100 and b > 200 then return 14 end
  167.  if r < 50 and g > 100 and b > 150 then return 9 end
  168.  if r < 50 and g > 80 and b > 80 then return 8 end
  169.  return 1
  170. end
  171. function draw_animated_background()
  172.  bg_time=bg_time+0.02
  173.  for y=0,135 do
  174.   local w1=math.sin(y*0.05+bg_time)*0.5+0.5 local w2=math.sin(y*0.03+bg_time*1.3)*0.5+0.5 local w3=math.sin(y*0.08+bg_time*0.7)*0.5+0.5
  175.   local t1=(w1*3)%1 local t2=(w2*2)%1 local t3=(w3*4)%1
  176.   local i1=math.floor(w1*(#BG_COLORS-1))+1 local i2=(i1%#BG_COLORS)+1 local i3=math.floor(w3*(#BG_COLORS-1))+1
  177.   local c1=lerp_color(BG_COLORS[i1],BG_COLORS[i2],t1) local c2=lerp_color(BG_COLORS[i3],BG_COLORS[1],t2)
  178.   line(0,y,239,y,rgb_to_tic_color(lerp_color(c1,c2,t3*0.3)))
  179.  end
  180.  for i=1,12 do
  181.   local x=(math.sin(bg_time*0.4+i*0.7)*100+120)%240
  182.   local y=(math.cos(bg_time*0.3+i*1.2)*50+68)%136
  183.   local size=math.sin(bg_time*0.8+i)*1.5+2
  184.   local pulse=math.sin(bg_time*2+i)*0.5+0.5
  185.   local color=pulse>0.7 and 12 or 14
  186.   circb(x,y,size,color)
  187.  end
  188. end
  189. local function streak_multiplier()
  190.  return 1 + 0.5 * combo
  191. end
  192. local function streak_ui_color(level)
  193.  if level<=0 then return 12 end
  194.  if level<=2 then return 11 end
  195.  if level<=4 then return 2 end
  196.  return get_rainbow_color(rainbow_offset)
  197. end
  198. local function enter_warning()
  199.  if warning_active then return end
  200.  warning_active=true
  201.  warning_beep_timer=1
  202.  for i=1,20 do
  203.   local gx=GRID_X-6+math.random()*((GRID_SIZE*9)+12)
  204.   local gy=GRID_Y-6+math.random()*((GRID_SIZE*9)+12)
  205.   table.insert(particles,{x=gx,y=gy,vx=(math.random()-0.5)*0.5,vy=(math.random()-0.5)*0.5,life=20,color=6,warning=true})
  206.  end
  207. end
  208. local function exit_warning()
  209.  warning_active=false
  210. end
  211. local function streak_break()
  212.  combo=0
  213.  grace_remaining=0
  214.  exit_warning()
  215.  flash=FLASH_FRAMES
  216.  flash_color=6 -- red flash
  217.  sfx_streak_break()
  218. end
  219. function draw_heart(x, y, size, filled, color, outline_color, beating)
  220.  local s = size
  221.  local beat_offset = 0
  222.  if beating then
  223.   beat_offset = math.sin(time() * 0.15) * 0.5 + 0.5
  224.   s = s + beat_offset
  225.  end
  226.  local function draw_shape(px, py, psize, pfilled, pcolor)
  227.   if pfilled then
  228.    for dy = -psize, psize do
  229.     for dx = -psize, psize do
  230.      local dist = math.sqrt(dx*dx + dy*dy)
  231.      local angle = math.atan2(dy, dx)
  232.      local heart_dist = psize * (1 - math.sin(angle + math.pi/2) * 0.3)
  233.      if dy < 0 then
  234.       if (dx < 0 and (dx+psize/2)*(dx+psize/2) + dy*dy < (psize/2)*(psize/2)) or
  235.          (dx > 0 and (dx-psize/2)*(dx-psize/2) + dy*dy < (psize/2)*(psize/2)) then
  236.        pix(px + dx, py + dy, pcolor)
  237.       end
  238.      else
  239.       if math.abs(dx) < psize - dy then
  240.        pix(px + dx, py + dy, pcolor)
  241.       end
  242.      end
  243.     end
  244.    end
  245.   else
  246.    circb(px - psize//2, py - psize//2, psize//2, pcolor)
  247.    circb(px + psize//2, py - psize//2, psize//2, pcolor)
  248.    line(px - psize, py, px, py + psize, pcolor)
  249.    line(px + psize, py, px, py + psize, pcolor)
  250.   end
  251.  end
  252.  if outline_color then
  253.   draw_shape(x-1, y, s, true, outline_color)
  254.   draw_shape(x+1, y, s, true, outline_color)
  255.   draw_shape(x, y-1, s, true, outline_color)
  256.   draw_shape(x, y+1, s, true, outline_color)
  257.  end
  258.  draw_shape(x, y, s, filled, color)
  259. end
  260. function _init()
  261.  for r=1,GRID_SIZE do
  262.   grid[r]={}
  263.   for c=1,GRID_SIZE do
  264.    grid[r][c]=0
  265.   end
  266.  end
  267.  generate_shapes()
  268.  clearing_cells,particles,score_popups,line_clear_effects={},{},{},{}
  269.  bg_time=0
  270.  music_time=0
  271.  title_glow=0
  272.  title_float=0
  273.  score=0
  274.  best_score=best_score or 0
  275.  combo=0
  276.  grace_remaining=0
  277.  warning_active=false
  278.  flash_color=12
  279.  heart_beat_timer=0
  280.  turns_since_line_clear=0
  281.  line_guarantee_active=false
  282.  setupMusic()
  283.  if music_playing then
  284.   music(0, -1, -1, true) -- Start background music
  285.  end
  286. end
  287. function can_place_shape(shape,row,col)
  288.  for r=1,#shape do
  289.   for c=1,#shape[r] do
  290.    if shape[r][c]==1 then
  291.     local nr=row+r-1
  292.     local nc=col+c-1
  293.     if nr<1 or nr>GRID_SIZE or nc<1 or nc>GRID_SIZE or grid[nr][nc]~=0 then
  294.      return false
  295.     end
  296.    end
  297.   end
  298.  end
  299.  return true
  300. end
  301. function place_shape(shape,color,row,col)
  302.  for r=1,#shape do
  303.   for c=1,#shape[r] do
  304.    if shape[r][c]==1 then
  305.     grid[row+r-1][col+c-1]=color
  306.     spawn_place_particles(row+r-1,col+c-1,color)
  307.    end
  308.   end
  309.  end
  310. end
  311. function check_clears()
  312.  local cells={}
  313.  for r=1,GRID_SIZE do
  314.   local full=true
  315.   for c=1,GRID_SIZE do
  316.    if grid[r][c]==0 then full=false break end
  317.   end
  318.   if full then
  319.    for c=1,GRID_SIZE do table.insert(cells,{r,c,grid[r][c]}) end
  320.    table.insert(line_clear_effects,{type="row",pos=r,life=18,max_life=18})
  321.   end
  322.  end
  323.  for c=1,GRID_SIZE do
  324.   local full=true
  325.   for r=1,GRID_SIZE do
  326.    if grid[r][c]==0 then full=false break end
  327.   end
  328.   if full then
  329.    for r=1,GRID_SIZE do
  330.     local dup=false
  331.     for _,cell in ipairs(cells) do
  332.      if cell[1]==r and cell[2]==c then dup=true break end
  333.     end
  334.     if not dup then table.insert(cells,{r,c,grid[r][c]}) end
  335.    end
  336.    table.insert(line_clear_effects,{type="col",pos=c,life=18,max_life=18})
  337.   end
  338.  end
  339.  return cells
  340. end
  341. function clear_cells(c)
  342.  if #c==0 then return end
  343.  clearing_cells=c
  344.  flash=FLASH_FRAMES
  345.  flash_color=streak_ui_color(combo+1)
  346.  local cells_cleared=#c
  347.  local line_multiplier=1+math.min(cells_cleared/8*0.5,1.5)
  348.  local combo_bonus=1+math.min(combo*0.1,0.8)
  349.  shake=math.min(cells_cleared*SHAKE_MULT*line_multiplier*combo_bonus,SHAKE_MAX*2)
  350.  wave_effect=WAVE_TIME
  351.  for _,cell in ipairs(c) do
  352.   spawn_explosion_particles(cell[1],cell[2],cell[3])
  353.  end
  354.  combo=combo+1
  355.  grace_remaining=grace_max
  356.  exit_warning()
  357.  spawn_streak_particles(combo)
  358.  sfx_clear(#c)
  359.  sfx_streak_up(combo)
  360.  add_score_popup("x"..string.format("%.1f",streak_multiplier()),120,90)
  361.  turns_since_line_clear = 0
  362.  line_guarantee_active = false
  363. end
  364. function spawn_explosion_particles(row,col,color)
  365.  local x=GRID_X+(col-1)*(CELL_SIZE+1)+CELL_SIZE/2
  366.  local y=GRID_Y+(row-1)*(CELL_SIZE+1)+CELL_SIZE/2
  367.  for i=1,24 do
  368.   local a=math.pi*2*i/24
  369.   local speed = 1.5 + math.random() * 2.5
  370.   table.insert(particles,{
  371.    x=x,y=y,
  372.    vx=math.cos(a)*speed,
  373.    vy=math.sin(a)*speed,
  374.    life=300,color=color,size=2,trail=true
  375.   })
  376.  end
  377.  for i=1,12 do
  378.   table.insert(particles,{
  379.    x=x+math.random(-4,4),y=y+math.random(-4,4),
  380.    vx=(math.random()-0.5)*0.5,vy=math.random()*-1.5,
  381.    life=200,color=12,size=1,sparkle=true
  382.   })
  383.  end
  384.  for i=1,16 do
  385.   local a=math.pi*2*i/16 + math.random()*0.5
  386.   local speed = 0.8 + math.random() * 1.2
  387.   table.insert(particles,{
  388.    x=x+math.random(-2,2),y=y+math.random(-2,2),
  389.    vx=math.cos(a)*speed,
  390.    vy=math.sin(a)*speed,
  391.    life=250,color=14,size=1
  392.   })
  393.  end
  394. end
  395. function spawn_place_particles(row,col,color)
  396.  local x=GRID_X+(col-1)*(CELL_SIZE+1)+CELL_SIZE/2
  397.  local y=GRID_Y+(row-1)*(CELL_SIZE+1)+CELL_SIZE/2
  398.  for i=1,5 do
  399.   table.insert(particles,{
  400.    x=x+math.random(-2,2),y=y+math.random(-2,2),
  401.    vx=(math.random()-0.5)*0.8,vy=math.random()*-1.5,
  402.    life=18,color=color,size=1
  403.   })
  404.  end
  405. end
  406. function add_score_popup(points_or_text,x,y)
  407.  local text=type(points_or_text)=="number" and ("+"..points_or_text) or tostring(points_or_text)
  408.  table.insert(score_popups,{text=text,x=x,y=y,life=60,scale=2})
  409. end
  410. function spawn_streak_particles(level)
  411.   local cx = GRID_X + (GRID_SIZE*(CELL_SIZE+1))//2
  412.   local cy = GRID_Y - 10
  413.   local count = 6 + math.min(level,10)*2
  414.   if level>=5 then count=count+6 end
  415.   for i=1,count do
  416.     local a   = (i/count) * 6.28318
  417.     local spd = 1.4 + 0.12*level + (math.random()*0.5)
  418.     table.insert(particles,{
  419.       x=cx, y=cy,
  420.       vx=math.cos(a)*spd,
  421.       vy=math.sin(a)*spd*0.6 - 0.3,
  422.       life=28 + math.min(level,10)*3,
  423.       color= (level>=5) and get_rainbow_color((i*36+rainbow_offset)%360) or (11 - (i%3)),
  424.       size=2 + math.floor(level/3),
  425.       streak_particle=true
  426.     })
  427.   end
  428. end
  429. function update_particles()
  430.  for i=#particles,1,-1 do
  431.   local p=particles[i]
  432.   if not (p and p.x and p.y and p.life) then
  433.    table.remove(particles,i)
  434.   else
  435.    p.x=p.x+p.vx; p.y=p.y+p.vy
  436.    p.vy=p.vy+0.08; p.vx=p.vx*0.985
  437.    p.life=p.life-1
  438.    if p.sparkle and p.life%4==0 then
  439.     p.color = (p.color % 15) + 1
  440.    end
  441.    if p.trail and p.life%3==0 then
  442.     table.insert(particles,{x=p.x,y=p.y,vx=0,vy=0,life=10,color=14,size=1})
  443.    end
  444.    if p.x<-20 or p.x>260 or p.y<-20 or p.y>160 or p.life<=0 then table.remove(particles,i) end
  445.   end
  446.  end
  447. end
  448. function update_score_popups()
  449.  for i=#score_popups,1,-1 do
  450.   local p=score_popups[i]
  451.   p.y=p.y-0.8; p.life=p.life-1; p.scale=p.scale+(2.5-p.scale)*0.12
  452.   if p.life<=0 then table.remove(score_popups,i) end
  453.  end
  454. end
  455. function update_line_effects()
  456.  for i=#line_clear_effects,1,-1 do
  457.   local e=line_clear_effects[i]; e.life=e.life-1
  458.   if e.life<=0 then table.remove(line_clear_effects,i) end
  459.  end
  460. end
  461. function get_grid_pos(mx,my)
  462.  local rx=mx-GRID_X; local ry=my-GRID_Y
  463.  if rx<0 or ry<0 then return nil end
  464.  local col=math.floor(rx/(CELL_SIZE+1))+1
  465.  local row=math.floor(ry/(CELL_SIZE+1))+1
  466.  if row>=1 and row<=GRID_SIZE and col>=1 and col<=GRID_SIZE then
  467.   return row,col
  468.  end
  469. end
  470. function check_game_over()
  471.  if #clearing_cells>0 or flash>0 then return false end
  472.  if not next_shapes or #next_shapes == 0 then return true end
  473.  for _,sd in ipairs(next_shapes) do
  474.   if sd and sd.shape then
  475.    local s=sd.shape
  476.    for r=1,GRID_SIZE do
  477.     for c=1,GRID_SIZE do
  478.      if can_place_shape(s,r,c) then
  479.       return false -- Found a valid placement, not game over
  480.      end
  481.     end
  482.    end
  483.   end
  484.  end
  485.  return true -- No valid placements found, game over
  486. end
  487. local function fair_clone(src)
  488.   local g={}
  489.   for r=1,GRID_SIZE do g[r]={}; for c=1,GRID_SIZE do g[r][c]=src[r][c] end end
  490.   return g
  491. end
  492. local function fair_can(g,s,r,c)
  493.   for i=1,#s do for j=1,#s[i] do
  494.     if s[i][j]==1 then
  495.       local rr=r+i-1; local cc=c+j-1
  496.       if rr<1 or rr>GRID_SIZE or cc<1 or cc>GRID_SIZE or g[rr][cc]~=0 then return false end
  497.     end
  498.   end end
  499.   return true
  500. end
  501. local function fair_place(g,s,r,c)
  502.   for i=1,#s do for j=1,#s[i] do if s[i][j]==1 then g[r+i-1][c+j-1]=1 end end end
  503. end
  504. local function fair_clear_once(g)
  505.   local cleared=false
  506.   for r=1,GRID_SIZE do
  507.     local full=true
  508.     for c=1,GRID_SIZE do if g[r][c]==0 then full=false break end end
  509.     if full then for c=1,GRID_SIZE do g[r][c]=0 end cleared=true end
  510.   end
  511.   for c=1,GRID_SIZE do
  512.     local full=true
  513.     for r=1,GRID_SIZE do if g[r][c]==0 then full=false break end end
  514.     if full then for r=1,GRID_SIZE do g[r][c]=0 end cleared=true end
  515.   end
  516.   return cleared
  517. end
  518. local function fair_clear_all(g) while fair_clear_once(g) do end end
  519. local function fair_list(g,s)
  520.   local t={}
  521.   for r=1,GRID_SIZE do
  522.     for c=1,GRID_SIZE do
  523.       if fair_can(g,s,r,c) then t[#t+1]={r=r,c=c} end
  524.     end
  525.   end
  526.   return t
  527. end
  528. local function get_difficulty_level()
  529.   if score < 500 then return 1 end
  530.   if score < 1500 then return 2 end
  531.   if score < 4000 then return 3 end
  532.   if score < 8000 then return 4 end
  533.   if score < 12000 then return 5 end
  534.   if score < 20000 then return 6 end
  535.   if score < 35000 then return 7 end
  536.   if score < 60000 then return 8 end
  537.   if score < 100000 then return 9 end
  538.   return 10
  539. end
  540. local function get_difficulty_params(level)
  541.   if level == 1 then return {min_solutions = 8, max_complexity = 3.5, line_guarantee = false} end
  542.   if level == 2 then return {min_solutions = 6, max_complexity = 5.0, line_guarantee = false} end
  543.   if level == 3 then return {min_solutions = 4, max_complexity = 6.5, line_guarantee = false} end
  544.   if level == 4 then return {min_solutions = 2, max_complexity = 8.0, line_guarantee = false} end
  545.   if level == 5 then return {min_solutions = 2, max_complexity = 9.5, line_guarantee = false} end
  546.   if level == 6 then return {min_solutions = 1, max_complexity = 10.5, line_guarantee = false} end
  547.   if level == 7 then return {min_solutions = 1, max_complexity = 12.0, line_guarantee = false} end
  548.   if level == 8 then return {min_solutions = 1, max_complexity = 13.5, line_guarantee = false} end
  549.   if level == 9 then return {min_solutions = 2, max_complexity = 14.0, line_guarantee = true} end
  550.   if level == 10 then return {min_solutions = 3, max_complexity = 15.0, line_guarantee = true} end
  551.   return {min_solutions = 3, max_complexity = 15.0, line_guarantee = true}
  552. end
  553. local function get_shape_complexity(shape)
  554.   local cells = 0
  555.   local max_width = 0
  556.   local max_height = #shape
  557.   for r = 1, #shape do
  558.     local row_width = 0
  559.     for c = 1, #shape[r] do
  560.       if shape[r][c] == 1 then
  561.         cells = cells + 1
  562.         row_width = row_width + 1
  563.       end
  564.     end
  565.     max_width = math.max(max_width, row_width)
  566.   end
  567.   local awkwardness = 0
  568.   if max_width == 1 and max_height > 3 then awkwardness = 2 end
  569.   if max_height == 1 and max_width > 4 then awkwardness = 2 end
  570.   if cells >= 6 then awkwardness = awkwardness + 1 end
  571.   local has_corners = false
  572.   for r = 1, #shape do
  573.     for c = 1, #shape[r] do
  574.       if shape[r][c] == 1 then
  575.         local neighbors = 0
  576.         if r > 1 and shape[r-1][c] == 1 then neighbors = neighbors + 1 end
  577.         if r < #shape and shape[r+1] and shape[r+1][c] == 1 then neighbors = neighbors + 1 end
  578.         if c > 1 and shape[r][c-1] == 1 then neighbors = neighbors + 1 end
  579.         if c < #shape[r] and shape[r][c+1] == 1 then neighbors = neighbors + 1 end
  580.         if neighbors == 2 then has_corners = true end
  581.       end
  582.     end
  583.   end
  584.   if has_corners then awkwardness = awkwardness + 1 end
  585.   return cells + awkwardness
  586. end
  587. local function wb_is_awkward(shape)
  588.   local corners = false
  589.   for r=1,#shape do
  590.     for c=1,#shape[r] do
  591.       if shape[r][c]==1 then
  592.         local n=0
  593.         if r>1 and shape[r-1][c]==1 then n=n+1 end
  594.         if r<#shape and shape[r+1] and shape[r+1][c]==1 then n=n+1 end
  595.         if c>1 and shape[r][c-1]==1 then n=n+1 end
  596.         if c<#shape[r] and shape[r][c+1]==1 then n=n+1 end
  597.         if n==2 then corners=true end
  598.       end
  599.     end
  600.   end
  601.   return corners
  602. end
  603. local function wb_cell_count(shape)
  604.   local n=0
  605.   for r=1,#shape do
  606.     for c=1,#shape[r] do
  607.       if shape[r][c]==1 then n=n+1 end
  608.     end
  609.   end
  610.   return n
  611. end
  612. local function wb_classify_shape(shape)
  613.   local cells = wb_cell_count(shape)
  614.   if wb_is_awkward(shape) and cells>=5 then return "awk" end
  615.   if cells<=2 then return "micro"
  616.   elseif cells<=3 then return "small"
  617.   elseif cells<=5 then return "medium"
  618.   elseif cells>=7 then return "large"
  619.   else return "medium" end
  620. end
  621. local function wb_any_fit(shape)
  622.   for r=1,GRID_SIZE do
  623.     for c=1,GRID_SIZE do
  624.       if can_place_shape(shape, r, c) then return true end
  625.     end
  626.   end
  627.   return false
  628. end
  629. local function wb_board_metrics()
  630.   local seen = {}
  631.   for r=1,GRID_SIZE do seen[r]={} end
  632.   local function bfs(sr,sc)
  633.     local q={{sr,sc}}; seen[sr][sc]=true
  634.     local size=0
  635.     local dirs={{1,0},{-1,0},{0,1},{0,-1}}
  636.     while #q>0 do
  637.       local cur=table.remove(q)
  638.       local r,c = cur[1],cur[2]
  639.       size = size + 1
  640.       for _,d in ipairs(dirs) do
  641.         local nr, nc = r+d[1], c+d[2]
  642.         if nr>=1 and nr<=GRID_SIZE and nc>=1 and nc<=GRID_SIZE then
  643.           if not seen[nr][nc] and grid[nr][nc]==0 then
  644.             seen[nr][nc]=true
  645.             q[#q+1]={nr,nc}
  646.           end
  647.         end
  648.       end
  649.     end
  650.     return size
  651.   end
  652.   local free_cells=0
  653.   for r=1,GRID_SIZE do for c=1,GRID_SIZE do if grid[r][c]==0 then free_cells=free_cells+1 end end end
  654.   local largest=0
  655.   local holes=0
  656.   for r=1,GRID_SIZE do
  657.     for c=1,GRID_SIZE do
  658.       if grid[r][c]==0 and not seen[r][c] then
  659.         local comp=bfs(r,c)
  660.         if comp==1 then holes=holes+1 end
  661.         if comp>largest then largest=comp end
  662.       end
  663.     end
  664.   end
  665.   return {free=free_cells, largest=largest, holes=holes}
  666. end
  667. local function wb_base_weights()
  668.   local w = {}
  669.   for i,sh in ipairs(shapes) do
  670.     local cls = wb_classify_shape(sh)
  671.     local v = (cls=="micro" and WB_W_MICRO)
  672.           or (cls=="small"  and WB_W_SMALL)
  673.           or (cls=="medium" and WB_W_MEDIUM)
  674.           or (cls=="large"  and WB_W_LARGE)
  675.           or WB_W_AWK
  676.     w[i]={w=v, class=cls}
  677.   end
  678.   return w
  679. end
  680. local function wb_apply_board_tweaks(weights)
  681.   local m = wb_board_metrics()
  682.   local cramped = (m.largest < WB_CRAMPED_LARGEST_PATCH) or (m.holes >= WB_CRAMPED_HOLES)
  683.   local function tweak(cls, delta)
  684.     for i,rec in ipairs(weights) do
  685.       if rec.class==cls then rec.w = math.max(0.001, rec.w * (1+delta)) end
  686.     end
  687.   end
  688.   if cramped then
  689.     tweak("small",  0.25)
  690.     tweak("micro",  0.15)
  691.     tweak("large", -0.10)
  692.     tweak("awk",  -0.10)
  693.   else
  694.     if m.free>=40 then
  695.       tweak("medium", 0.10)
  696.       tweak("large",  0.10)
  697.     end
  698.   end
  699. end
  700. local function wb_apply_quotas(weights)
  701.   local count={micro=0, small=0, medium=0, large=0, awk=0}
  702.   for _,cls in ipairs(wb_recent_classes) do count[cls]=(count[cls] or 0)+1 end
  703.   local function cap(cls, maxn)
  704.     if count[cls] and count[cls] >= maxn then
  705.       for _,rec in ipairs(weights) do
  706.         if rec.class==cls then rec.w = 0.001 end
  707.       end
  708.     end
  709.   end
  710.   cap("micro", WB_MAX_MICRO_IN_WIN)
  711.   cap("small", WB_MAX_SMALL_IN_WIN)
  712.   cap("medium",WB_MAX_MEDIUM_IN_WIN)
  713.   cap("large", WB_MAX_LARGE_IN_WIN)
  714.   cap("awk",   WB_MAX_AWK_IN_WIN)
  715. end
  716. local function wb_sample_k(weights, k)
  717.   local pool = {}
  718.   for i,rec in ipairs(weights) do if rec.w>0 then pool[#pool+1]={i=i,w=rec.w,class=rec.class} end end
  719.   local chosen={}
  720.   for _=1,k do
  721.     local total=0
  722.     for _,p in ipairs(pool) do total=total+p.w end
  723.     if total<=0 or #pool==0 then break end
  724.     local r = math.random()*total
  725.     local acc=0
  726.     local pick_idx=1
  727.     for j,p in ipairs(pool) do
  728.       acc = acc + p.w
  729.       if r<=acc then pick_idx=j; break end
  730.     end
  731.     local pick = pool[pick_idx]
  732.     table.insert(chosen, {idx=pick.i, class=pick.class})
  733.     table.remove(pool, pick_idx)
  734.   end
  735.   return chosen
  736. end
  737. local function wb_push_history(classes)
  738.   for _,cls in ipairs(classes) do
  739.     table.insert(wb_recent_classes, cls)
  740.   end
  741.   while #wb_recent_classes > WB_WINDOW do
  742.     table.remove(wb_recent_classes, 1)
  743.   end
  744. end
  745. local function wb_triplet_has_fit(triplet)
  746.   for _,pick in ipairs(triplet) do
  747.     if wb_any_fit(shapes[pick.idx]) then return true end
  748.   end
  749.   return false
  750. end
  751. local function wb_lerp(a,b,t) return a+(b-a)*t end
  752. local function wb_health()
  753.   local m = wb_board_metrics()           -- you already added this in my patch
  754.   local free_norm   = math.min(1, m.free / 40)
  755.   local patch_norm  = math.min(1, m.largest / 12)
  756.   local hole_penalty= math.min(1, m.holes / 5)
  757.   local raw = (free_norm*0.6 + patch_norm*0.6) - hole_penalty*0.5
  758.   return math.max(0, math.min(1, raw))
  759. end
  760. local function wb_plug_score(shape)
  761.   local cells = wb_cell_count(shape)
  762.   if cells==1 then return 1.0 end
  763.   if cells==2 then return 0.7 end
  764.   if cells<=3 then return 0.4 end
  765.   return 0
  766. end
  767. local function count_solution_paths(shape_indices)
  768.   local orderings = {
  769.     {1,2,3}, {1,3,2}, {2,1,3}, {2,3,1}, {3,1,2}, {3,2,1}
  770.   }
  771.   local solution_count = 0
  772.   local max_solutions_to_check = 20
  773.   for _, order in ipairs(orderings) do
  774.     if solution_count >= max_solutions_to_check then break end
  775.     local shape1_idx = shape_indices[order[1]]
  776.     local shape2_idx = shape_indices[order[2]]
  777.     local shape3_idx = shape_indices[order[3]]
  778.     local spots1 = fair_list(grid, shapes[shape1_idx])
  779.     for _, pos1 in ipairs(spots1) do
  780.       if solution_count >= max_solutions_to_check then break end
  781.       local test_grid1 = fair_clone(grid)
  782.       fair_place(test_grid1, shapes[shape1_idx], pos1.r, pos1.c)
  783.       local spots2 = fair_list(test_grid1, shapes[shape2_idx])
  784.       for _, pos2 in ipairs(spots2) do
  785.         if solution_count >= max_solutions_to_check then break end
  786.         local test_grid2 = fair_clone(test_grid1)
  787.         fair_place(test_grid2, shapes[shape2_idx], pos2.r, pos2.c)
  788.         local spots3 = fair_list(test_grid2, shapes[shape3_idx])
  789.         if #spots3 > 0 then solution_count = solution_count + 1 end
  790.       end
  791.     end
  792.   end
  793.   return solution_count
  794. end
  795. local function check_for_line_completion_in_trio(trio_indices)
  796.   local orderings = {
  797.     {1,2,3}, {1,3,2}, {2,1,3}, {2,3,1}, {3,1,2}, {3,2,1}
  798.   }
  799.   for _, order in ipairs(orderings) do
  800.     local shape1_idx = trio_indices[order[1]]
  801.     local shape2_idx = trio_indices[order[2]]
  802.     local shape3_idx = trio_indices[order[3]]
  803.     local spots1 = fair_list(grid, shapes[shape1_idx])
  804.     for _, pos1 in ipairs(spots1) do
  805.       local test_grid1 = fair_clone(grid)
  806.       fair_place(test_grid1, shapes[shape1_idx], pos1.r, pos1.c)
  807.       fair_clear_all(test_grid1)
  808.       local line_cleared = false
  809.       for r=1,GRID_SIZE do
  810.         local full=true
  811.         for c=1,GRID_SIZE do if grid[r][c]==0 then full=false break end end
  812.         if full then
  813.           for c=1,GRID_SIZE do if test_grid1[r][c]==0 then line_cleared=true break end end
  814.         end
  815.         if line_cleared then break end
  816.       end
  817.       if not line_cleared then
  818.         for c=1,GRID_SIZE do
  819.           local full=true
  820.           for r=1,GRID_SIZE do if grid[r][c]==0 then full=false break end end
  821.           if full then
  822.             for r=1,GRID_SIZE do if test_grid1[r][c]==0 then line_cleared=true break end end
  823.           end
  824.           if line_cleared then break end
  825.         end
  826.       end
  827.       if line_cleared then return true end
  828.       local spots2 = fair_list(test_grid1, shapes[shape2_idx])
  829.       for _, pos2 in ipairs(spots2) do
  830.         local test_grid2 = fair_clone(test_grid1)
  831.         fair_place(test_grid2, shapes[shape2_idx], pos2.r, pos2.c)
  832.         fair_clear_all(test_grid2)
  833.         local line_cleared2 = false
  834.         for r=1,GRID_SIZE do
  835.           local full=true
  836.           for c=1,GRID_SIZE do if test_grid1[r][c]==0 then full=false break end end
  837.           if full then
  838.             for c=1,GRID_SIZE do if test_grid2[r][c]==0 then line_cleared2=true break end end
  839.           end
  840.           if line_cleared2 then break end
  841.         end
  842.         if not line_cleared2 then
  843.           for c=1,GRID_SIZE do
  844.             local full=true
  845.             for r=1,GRID_SIZE do if test_grid1[r][c]==0 then full=false break end end
  846.             if full then
  847.               for r=1,GRID_SIZE do if test_grid2[r][c]==0 then line_cleared2=true break end end
  848.             end
  849.             if line_cleared2 then break end
  850.           end
  851.         end
  852.         if line_cleared2 then return true end
  853.         local spots3 = fair_list(test_grid2, shapes[shape3_idx])
  854.         if #spots3 > 0 then
  855.           local test_grid3 = fair_clone(test_grid2)
  856.           fair_place(test_grid3, shapes[shape3_idx], spots3[1].r, spots3[1].c)
  857.           fair_clear_all(test_grid3)
  858.           local line_cleared3 = false
  859.           for r=1,GRID_SIZE do
  860.             local full=true
  861.             for c=1,GRID_SIZE do if test_grid2[r][c]==0 then full=false break end end
  862.             if full then
  863.               for c=1,GRID_SIZE do if test_grid3[r][c]==0 then line_cleared3=true break end end
  864.             end
  865.             if line_cleared3 then break end
  866.           end
  867.           if not line_cleared3 then
  868.             for c=1,GRID_SIZE do
  869.               local full=true
  870.               for r=1,GRID_SIZE do if test_grid2[r][c]==0 then full=false break end end
  871.               if full then
  872.                 for r=1,GRID_SIZE do if test_grid3[r][c]==0 then line_cleared3=true break end end
  873.               end
  874.               if line_cleared3 then break end
  875.             end
  876.           end
  877.           if line_cleared3 then return true end
  878.         end
  879.       end
  880.     end
  881.   end
  882.   return false
  883. end
  884. function wb_generate_shapes()
  885.   local pity = (wb_consec_dead >= WB_PITY_CAP)
  886.   local H = wb_health()
  887.   local R_eff      = math.max(1, math.floor(wb_lerp(WB_R_TRIES+1, WB_R_TRIES-1, H)))
  888.   local p_fail_eff = wb_lerp(0.05, 0.25, H)  -- cramped => ~5%, open => ~25%
  889.   local weights = wb_base_weights()
  890.   wb_apply_board_tweaks(weights)
  891.   wb_apply_quotas(weights)
  892.   local m = wb_board_metrics()
  893.   if m.holes >= WB_CRAMPED_HOLES then
  894.     for i,rec in ipairs(weights) do
  895.       local s = wb_plug_score(shapes[i])
  896.       if s>0 then rec.w = rec.w * (1 + 0.3*s) end -- up to +30%
  897.     end
  898.   end
  899.   if combo >= 3 then
  900.     for _,rec in ipairs(weights) do
  901.       if rec.class=="micro" then rec.w = rec.w * 0.90 end
  902.       if rec.class=="large" or rec.class=="awk" then rec.w = rec.w * 1.10 end
  903.     end
  904.   elseif combo==0 and turns_since_line_clear>0 then
  905.     for _,rec in ipairs(weights) do
  906.       if rec.class=="small" then rec.w = rec.w * 1.10 end
  907.     end
  908.   end
  909.   local last_triplet = nil
  910.   local had_fit = false
  911.   for t=1,R_eff do
  912.     local sample = wb_sample_k(weights, 3)
  913.     if #sample==3 then
  914.       last_triplet = sample
  915.       if wb_triplet_has_fit(sample) then
  916.         had_fit = true
  917.         break
  918.       end
  919.     end
  920.   end
  921.   if not last_triplet or #last_triplet<3 then
  922.     local picked={}
  923.     while #picked<3 do
  924.       local i=math.random(#shapes)
  925.       local ok=true
  926.       for _,p in ipairs(picked) do if p.idx==i then ok=false break end end
  927.       if ok then picked[#picked+1]={idx=i, class=wb_classify_shape(shapes[i])} end
  928.     end
  929.     last_triplet = picked
  930.     had_fit = wb_triplet_has_fit(last_triplet)
  931.   end
  932.   if not last_triplet or #last_triplet < 3 then
  933.     last_triplet = {
  934.       {idx=1, class="micro"},  -- single dot
  935.       {idx=2, class="micro"},  -- double dot
  936.       {idx=3, class="micro"}   -- vertical double
  937.     }
  938.   end
  939.   if not had_fit and not pity then
  940.     if math.random() < p_fail_eff then
  941.       wb_consec_dead = wb_consec_dead + 1
  942.     else
  943.       for _=1,5 do
  944.         local sample = wb_sample_k(weights, 3)
  945.         if #sample==3 then
  946.           last_triplet = sample
  947.           if wb_triplet_has_fit(sample) then
  948.             had_fit = true
  949.             break
  950.           end
  951.         end
  952.       end
  953.       wb_consec_dead = 0
  954.     end
  955.   else
  956.     wb_consec_dead = 0
  957.   end
  958.   if turns_since_line_clear >= 3 then
  959.     local has_saver=false
  960.     for _,p in ipairs(last_triplet) do
  961.       if p.class=="micro" or p.class=="small" then has_saver=true break end
  962.     end
  963.     if not has_saver then
  964.       local worst_i=1
  965.       local priority = {awk=5, large=4, medium=3, small=2, micro=1}
  966.       for i=2,3 do
  967.         if priority[last_triplet[i].class] > priority[last_triplet[worst_i].class] then
  968.           worst_i=i
  969.         end
  970.       end
  971.       local candidates={}
  972.       for i,rec in ipairs(wb_base_weights()) do
  973.         if rec.class=="micro" or rec.class=="small" then
  974.           table.insert(candidates, {idx=i,class=rec.class,w=rec.w})
  975.         end
  976.       end
  977.       if #candidates>0 then
  978.         local best = candidates[ math.random(#candidates) ]
  979.         last_triplet[worst_i] = {idx=best.idx, class=best.class}
  980.         had_fit = had_fit or wb_any_fit(shapes[best.idx])
  981.       end
  982.     end
  983.   end
  984.   local m = wb_board_metrics()
  985.   local open = (m.free >= 40 and m.largest >= 10)
  986.   local function count_big(tri)
  987.     local n=0; for _,p in ipairs(tri) do if p.class=="large" or p.class=="awk" then n=n+1 end end
  988.     return n
  989.   end
  990.   if not open and count_big(last_triplet) > 1 then
  991.     local sample2 = wb_sample_k(weights,3)
  992.     if #sample2==3 and count_big(sample2)<=1 then last_triplet=sample2 end
  993.   end
  994.   if wb_consec_dead >= 2 then
  995.     local best_i, best_count = 1, -1
  996.     for i,p in ipairs(last_triplet) do
  997.       local count = 0
  998.       for r=1,GRID_SIZE do
  999.         for c=1,GRID_SIZE do
  1000.           if can_place_shape(shapes[p.idx], r, c) then count = count + 1 end
  1001.         end
  1002.       end
  1003.       if count > best_count then best_count = count; best_i = i end
  1004.     end
  1005.     if best_count < 4 then
  1006.       local candidates={}
  1007.       for i,rec in ipairs(wb_base_weights()) do
  1008.         if rec.class=="micro" or rec.class=="small" then
  1009.           local cnt = 0
  1010.           for r=1,GRID_SIZE do
  1011.             for c=1,GRID_SIZE do
  1012.               if can_place_shape(shapes[i], r, c) then cnt = cnt + 1 end
  1013.             end
  1014.           end
  1015.           if cnt>=4 then table.insert(candidates,{idx=i,class=rec.class}) end
  1016.         end
  1017.       end
  1018.       if #candidates>0 then
  1019.         local pick = candidates[math.random(#candidates)]
  1020.         last_triplet[1] = {idx=pick.idx, class=pick.class}
  1021.         had_fit = had_fit or wb_any_fit(shapes[pick.idx])
  1022.       end
  1023.     end
  1024.   end
  1025.   local order = {1,2,3}
  1026.   for i=#order,2,-1 do local j=math.random(i) order[i],order[j]=order[j],order[i] end
  1027.   next_shapes = {}
  1028.   for k=1,3 do
  1029.     local pick = last_triplet[order[k]]
  1030.     if not pick or not pick.idx or not shapes[pick.idx] then
  1031.       pick = {idx = 1, class = "micro"}
  1032.     end
  1033.     next_shapes[k] = {
  1034.       shape = shapes[pick.idx],
  1035.       color = colors[math.random(#colors)],
  1036.       id = k,
  1037.       spawn_time = time()
  1038.     }
  1039.   end
  1040.   local classes = {}
  1041.   for i=1,3 do
  1042.     if last_triplet[i] and last_triplet[i].class then
  1043.       table.insert(classes, last_triplet[i].class)
  1044.     else
  1045.       table.insert(classes, "micro") -- fallback class
  1046.     end
  1047.   end
  1048.   wb_push_history(classes)
  1049. end
  1050. function enhanced_generate_shapes()
  1051.   local difficulty = get_difficulty_level()
  1052.   local params = get_difficulty_params(difficulty)
  1053.   local min_solutions = params.min_solutions
  1054.   local max_avg_complexity = params.max_complexity
  1055.   local line_guarantee = params.line_guarantee
  1056.   local max_attempts = 300
  1057.   for attempt = 1, max_attempts do
  1058.     local trio_indices = {}
  1059.     local used_shapes = {}
  1060.     local total_complexity = 0
  1061.     for i = 1, 3 do
  1062.       local shape_idx
  1063.       local attempts_for_shape = 0
  1064.       repeat
  1065.         local base_roll = math.random()
  1066.         if base_roll < 0.15 then
  1067.           repeat
  1068.             shape_idx = math.random(#shapes)
  1069.           until get_shape_complexity(shapes[shape_idx]) >= 3
  1070.         elseif base_roll < 0.35 then
  1071.           repeat
  1072.             shape_idx = math.random(#shapes)
  1073.           until get_shape_complexity(shapes[shape_idx]) >= 4
  1074.         else
  1075.           shape_idx = math.random(#shapes)
  1076.         end
  1077.         attempts_for_shape = attempts_for_shape + 1
  1078.       until (not used_shapes[shape_idx] and attempts_for_shape < 50)
  1079.       if attempts_for_shape >= 50 then goto continue end
  1080.       used_shapes[shape_idx] = true
  1081.       table.insert(trio_indices, shape_idx)
  1082.       total_complexity = total_complexity + get_shape_complexity(shapes[shape_idx])
  1083.     end
  1084.     if #trio_indices ~= 3 then goto continue end
  1085.     local avg_complexity = total_complexity / 3
  1086.     if avg_complexity > max_avg_complexity then goto continue end
  1087.     if difficulty <= 2 and total_complexity > 12 then goto continue end
  1088.     local solution_count = count_solution_paths(trio_indices)
  1089.     local line_requirement_met = true
  1090.     if line_guarantee and turns_since_line_clear >= 2 then
  1091.       line_guarantee_active = true
  1092.       line_requirement_met = check_for_line_completion_in_trio(trio_indices)
  1093.       if line_requirement_met then
  1094.         local complexity_variance = 0
  1095.         for i = 1, 3 do
  1096.           local shape_complexity = get_shape_complexity(shapes[trio_indices[i]])
  1097.           complexity_variance = complexity_variance + math.abs(shape_complexity - (total_complexity / 3))
  1098.         end
  1099.         if complexity_variance < 1.5 then
  1100.           line_requirement_met = math.random() < 0.7 -- 70% chance to accept low variance
  1101.         end
  1102.         if line_requirement_met then
  1103.           local total_placement_options = 0
  1104.           for i = 1, 3 do
  1105.             local placement_count = #fair_list(grid, shapes[trio_indices[i]])
  1106.             total_placement_options = total_placement_options + placement_count
  1107.           end
  1108.           if total_placement_options < 8 then
  1109.             line_requirement_met = math.random() < 0.6 -- Reduce acceptance for obvious placements
  1110.           end
  1111.         end
  1112.       end
  1113.     elseif line_guarantee and turns_since_line_clear >= 1 and math.random() < 0.15 then
  1114.       line_requirement_met = check_for_line_completion_in_trio(trio_indices)
  1115.     end
  1116.     if solution_count >= min_solutions and line_requirement_met then
  1117.       next_shapes = {}
  1118.       local order = {1, 2, 3}
  1119.       for i = #order, 2, -1 do
  1120.         local j = math.random(i)
  1121.         order[i], order[j] = order[j], order[i]
  1122.       end
  1123.       for k = 1, 3 do
  1124.         local idx = trio_indices[order[k]]
  1125.         next_shapes[k] = {
  1126.           shape = shapes[idx],
  1127.           color = colors[math.random(#colors)],
  1128.           id = k,
  1129.           spawn_time = time()
  1130.         }
  1131.       end
  1132.       return
  1133.     end
  1134.     ::continue::
  1135.   end
  1136.   emergency_generate_shapes()
  1137. end
  1138. function emergency_generate_shapes()
  1139.   local simple_shapes = {}
  1140.   for i, shape in ipairs(shapes) do
  1141.     local complexity = get_shape_complexity(shape)
  1142.     table.insert(simple_shapes, {idx = i, complexity = complexity})
  1143.   end
  1144.   table.sort(simple_shapes, function(a, b) return a.complexity < b.complexity end)
  1145.   for i = 1, math.min(10, #simple_shapes) do
  1146.     for j = i + 1, math.min(15, #simple_shapes) do
  1147.       for k = j + 1, math.min(20, #simple_shapes) do
  1148.         local trio = {simple_shapes[i].idx, simple_shapes[j].idx, simple_shapes[k].idx}
  1149.         if count_solution_paths(trio) >= 1 then
  1150.           next_shapes = {}
  1151.           for l = 1, 3 do
  1152.             next_shapes[l] = {
  1153.               shape = shapes[trio[l]],
  1154.               color = colors[math.random(#colors)],
  1155.               id = l,
  1156.               spawn_time = time()
  1157.             }
  1158.           end
  1159.           return
  1160.         end
  1161.       end
  1162.     end
  1163.   end
  1164.   next_shapes = {}
  1165.   for i = 1, 3 do
  1166.     next_shapes[i] = { shape = {{1}}, color = colors[math.random(#colors)], id = i, spawn_time = time() }
  1167.   end
  1168. end
  1169. function generate_shapes()
  1170.   if USE_WEIGHTED_BAG then
  1171.     wb_generate_shapes()
  1172.   else
  1173.     enhanced_generate_shapes()  -- your original "always solvable" generator
  1174.   end
  1175. end
  1176. local function clone_grid()
  1177.  local g={}
  1178.  for r=1,GRID_SIZE do
  1179.   g[r]={}
  1180.   for c=1,GRID_SIZE do g[r][c]=grid[r][c] end
  1181.  end
  1182.  return g
  1183. end
  1184. local function calc_clears_for_grid(g)
  1185.  local cells={}
  1186.  local seen={}
  1187.  for r=1,GRID_SIZE do seen[r]={} end
  1188.  for r=1,GRID_SIZE do
  1189.   local full=true
  1190.   for c=1,GRID_SIZE do
  1191.    if g[r][c]==0 then full=false break end
  1192.   end
  1193.   if full then
  1194.    for c=1,GRID_SIZE do
  1195.     if not seen[r][c] then
  1196.      table.insert(cells,{r,c,g[r][c]}); seen[r][c]=true
  1197.     end
  1198.    end
  1199.   end
  1200.  end
  1201.  for c=1,GRID_SIZE do
  1202.   local full=true
  1203.   for r=1,GRID_SIZE do
  1204.    if g[r][c]==0 then full=false break end
  1205.   end
  1206.   if full then
  1207.    for r=1,GRID_SIZE do
  1208.     if not seen[r][c] then
  1209.      table.insert(cells,{r,c,g[r][c]}); seen[r][c]=true
  1210.     end
  1211.    end
  1212.   end
  1213.  end
  1214.  return cells
  1215. end
  1216. local function simulate_clears_if_placed(shape, base_row, base_col)
  1217.  local g=clone_grid()
  1218.  for r=1,#shape do
  1219.   for c=1,#shape[r] do
  1220.    if shape[r][c]==1 then
  1221.     local rr=base_row + r - 1
  1222.     local cc=base_col + c - 1
  1223.     if rr<1 or rr>GRID_SIZE or cc<1 or cc>GRID_SIZE then
  1224.      return {}
  1225.     end
  1226.     if g[rr][cc]~=0 then
  1227.      return {}
  1228.     end
  1229.     g[rr][cc]=1
  1230.    end
  1231.   end
  1232.  end
  1233.  return calc_clears_for_grid(g)
  1234. end
  1235. function preview_cell_size(shape)
  1236.  local h=#shape; local w=#shape[1]
  1237.  local fit=math.floor((PREVIEW_BOX-4)/math.max(w,h))
  1238.  if fit<1 then fit=1 end
  1239.  local bs=math.min(PREVIEW_CELL_CAP, fit)
  1240.  if bs<PREVIEW_MIN_CELL then bs=PREVIEW_MIN_CELL end
  1241.  return bs
  1242. end
  1243. function TIC()
  1244.  local mx,my,left=mouse(); mouse_x=mx; mouse_y=my
  1245.  rainbow_offset=(rainbow_offset+2)%360
  1246.  title_glow = (title_glow + 0.15) % (math.pi * 2)
  1247.  title_float = (title_float + 0.08) % (math.pi * 2)
  1248.  heart_beat_timer = heart_beat_timer + 1
  1249.  if wave_effect>0 then wave_effect=wave_effect-1 end
  1250.  if left and not dragging then
  1251.   for i,_ in ipairs(next_shapes) do
  1252.    local cx=RIGHT_PANEL_X
  1253.    local cy=RIGHT_PANEL_TOP+(i-1)*(PREVIEW_BOX+PREVIEW_GAP)
  1254.    if mx>=cx and mx<=cx+PREVIEW_BOX and my>=cy and my<=cy+PREVIEW_BOX then
  1255.     local sd=next_shapes[i]
  1256.     local s=sd.shape
  1257.     local h=#s; local w=#s[1]
  1258.     local bs=preview_cell_size(s)
  1259.     local sx0=cx+PREVIEW_BOX//2 - (w*bs)//2
  1260.     local sy0=cy+PREVIEW_BOX//2 - (h*bs)//2
  1261.     local grab_r,grab_c=nil,nil
  1262.     for r=1,h do
  1263.      for c=1,w do
  1264.       if s[r][c]==1 then
  1265.        local bx=sx0+(c-1)*bs
  1266.        local by=sy0+(r-1)*bs
  1267.        if mx>=bx and mx<=bx+bs-1 and my>=by and my<=by+bs-1 then
  1268.         grab_r=r; grab_c=c
  1269.        end
  1270.       end
  1271.      end
  1272.     end
  1273.     if not grab_r then
  1274.      for r=1,h do
  1275.       for c=1,w do
  1276.        if s[r][c]==1 then grab_r=r; grab_c=c; break end
  1277.       end
  1278.       if grab_r then break end
  1279.      end
  1280.     end
  1281.     dragging={shape=sd.shape,color=sd.color,index=i,grab_r=grab_r,grab_c=grab_c}
  1282.     sfx_pick()
  1283.     break
  1284.    end
  1285.   end
  1286.  elseif not left and dragging then
  1287.   local row,col=get_grid_pos(mx,my)
  1288.   if row and col then
  1289.    local base_row = row - (dragging.grab_r-1)
  1290.    local base_col = col - (dragging.grab_c-1)
  1291.    if can_place_shape(dragging.shape, base_row, base_col) then
  1292.     place_shape(dragging.shape, dragging.color, base_row, base_col)
  1293.     table.remove(next_shapes,dragging.index)
  1294.     turns_since_line_clear = turns_since_line_clear + 1
  1295.     local cells=check_clears()
  1296.     if #cells>0 then
  1297.      clear_cells(cells)
  1298.      local raw=10*#cells
  1299.      local pts=math.floor(raw*streak_multiplier()+0.5)
  1300.      add_score_popup(pts,120,100)
  1301.     else
  1302.      if combo>0 then
  1303.        grace_remaining=math.max(0,grace_remaining-1)
  1304.        if grace_remaining==1 then
  1305.          flash=FLASH_FRAMES
  1306.          flash_color=6
  1307.          enter_warning()
  1308.        elseif grace_remaining==0 then
  1309.          streak_break()
  1310.        end
  1311.      end
  1312.     end
  1313.     if #next_shapes==0 then generate_shapes() end
  1314.     if #clearing_cells==0 and flash==0 and check_game_over() then
  1315.      game_over=true
  1316.      music_playing=false
  1317.      sfx_game_over()
  1318.     end
  1319.     sfx_place()
  1320.    else
  1321.     sfx_invalid()
  1322.    end
  1323.   else
  1324.    sfx_invalid()
  1325.   end
  1326.   dragging=nil
  1327.  end
  1328.  if #clearing_cells>0 then
  1329.   if flash>0 then flash=flash-1 end
  1330.   if shake>0 then shake=shake-0.4 end
  1331.   if flash==0 then
  1332.    local pts=0
  1333.    for _,cell in ipairs(clearing_cells) do
  1334.     grid[cell[1]][cell[2]]=0; pts=pts+10
  1335.    end
  1336.    local final_score = math.floor(pts*streak_multiplier()+0.5)
  1337.    score=score+final_score
  1338.    if score>best_score then best_score=score end
  1339.    clearing_cells={}
  1340.    local again=check_clears()
  1341.    if #again>0 then
  1342.     clear_cells(again)
  1343.    else
  1344.     if #next_shapes==0 then generate_shapes() end
  1345.     if check_game_over() then
  1346.       game_over=true
  1347.       music_playing=false
  1348.       sfx_game_over()
  1349.     end
  1350.    end
  1351.   end
  1352.  end
  1353.  update_particles(); update_score_popups(); update_line_effects()
  1354.  update_music(); update_warning_audio()
  1355.  if not game_over and #clearing_cells==0 and flash==0 then
  1356.    if check_game_over() then
  1357.      game_over=true
  1358.      music_playing=false
  1359.      sfx_game_over()
  1360.    end
  1361.  end
  1362.  if keyp(7) then -- G key for testing
  1363.    game_over = true
  1364.    music_playing = false
  1365.    sfx_game_over()
  1366.  end
  1367.  if keyp(18) and game_over then
  1368.   _init(); game_over=false; score=0; combo=0; music_playing=true
  1369.   grace_remaining=0; warning_active=false; flash_color=12
  1370.   turns_since_line_clear=0; line_guarantee_active=false
  1371.   sfx_restart()
  1372.  end
  1373.  if keyp(12) then music_playing = not music_playing end
  1374.  draw()
  1375. end
  1376. local function text_w(s, sc) return #s*6*(sc or 1) end
  1377. local function outlined_text(s, x, y, fill, outline, sc)
  1378.   sc = sc or 1
  1379.   for dx=-1,1 do
  1380.     for dy=-1,1 do
  1381.       if dx~=0 or dy~=0 then print(s, x+dx, y+dy, outline, false, sc) end
  1382.     end
  1383.   end
  1384.   print(s, x, y, fill, false, sc)
  1385. end
  1386. local function outlined_center(s, y, fill, outline, sc)
  1387.   sc = sc or 1
  1388.   local x = (240 - text_w(s, sc))//2
  1389.   outlined_text(s, x, y, fill, outline, sc)
  1390. end
  1391. local function print_center(s, y, col, sc)
  1392.   sc = sc or 1
  1393.   local x = (240 - text_w(s, sc))//2
  1394.   print(s, x, y, col, false, sc)
  1395. end
  1396. local function get_rainbow_table()
  1397.   return {6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
  1398. end
  1399. function get_rainbow_color(offset)
  1400.   local colors = get_rainbow_table()
  1401.   local idx = math.floor(offset / 36) % #colors + 1
  1402.   return colors[idx]
  1403. end
  1404. local function draw_scoreboard(sx, sy)
  1405.   local score_x = sx + SCORE_X
  1406.   local score_y = sy + SCORE_Y
  1407.   rect(score_x-2, score_y-2, SCORE_WIDTH+4, SCORE_HEIGHT+4, 0)
  1408.   rect(score_x-1, score_y-1, SCORE_WIDTH+2, SCORE_HEIGHT+2, 14)
  1409.   rect(score_x, score_y, SCORE_WIDTH, SCORE_HEIGHT, 2)
  1410.   local y1 = score_y + 3
  1411.   local y2 = score_y + 14
  1412.   local bx = score_x + 4
  1413.   local best_s = string.format("%08d", best_score)
  1414.   local score_s = string.format("%08d", score)
  1415.   print("BEST", bx, y1, 12, false, 1)
  1416.   print(best_s, bx + text_w("BEST "), y1, 14, false, 1)
  1417.   print("SCORE", bx, y2, 12, false, 1)
  1418.   print(score_s, bx + text_w("SCORE "), y2, 11, false, 1)
  1419.   if combo > 0 then
  1420.     local mult = "x"..string.format("%.1f", streak_multiplier())
  1421.     local combo_x = score_x + SCORE_WIDTH + 6
  1422.     local combo_w, combo_h = 40, 16
  1423.     local combo_y = score_y + (SCORE_HEIGHT - combo_h)//2
  1424.     local col = streak_ui_color(combo)
  1425.     rect(combo_x-1, combo_y-1, combo_w+2, combo_h+2, 0)
  1426.     rect(combo_x, combo_y, combo_w, combo_h, col)
  1427.     local cx = combo_x + (combo_w - text_w(mult))//2
  1428.     local cy = combo_y + (combo_h - 6)//2
  1429.     print(mult, cx, cy, 14, false, 1)
  1430.     local heart_y = score_y + SCORE_HEIGHT + 6
  1431.     local heart_x = score_x + 8
  1432.     for i=1,grace_max do
  1433.       local filled = (i <= grace_remaining)
  1434.       local hc = filled and 2 or 1
  1435.       local shake_x, shake_y = 0, 0
  1436.       local beating = false
  1437.       if grace_remaining == 1 and i == 1 and filled then
  1438.         shake_x = math.sin(time() * 0.15) * 0.3
  1439.         shake_y = math.sin(time() * 0.12) * 0.2
  1440.         beating = true
  1441.       end
  1442.       draw_heart(heart_x + (i-1)*12 + shake_x, heart_y + shake_y, 3, filled, hc, 0, beating)
  1443.     end
  1444.   end
  1445. end
  1446. function draw()
  1447.  cls(1)
  1448.  rect(0,0,240,136,1)
  1449.  local sx,sy=0,0
  1450.  if shake>0 then
  1451.   sx=math.sin(time()*0.5)*shake; sy=math.cos(time()*0.7)*shake
  1452.  end
  1453.  draw_animated_background()
  1454.  if ENABLE_SCREEN_FLASH and flash>0 and flash%2==0 then
  1455.   rect(0,0,240,136,flash_color)
  1456.  end
  1457.  local title_y = 3 + math.sin(title_float) * 2
  1458.  local title = "BLOCK BLAST"
  1459.  local x = (240 - text_w(title, 1))//2
  1460.  for i=1,#title do
  1461.   local char = title:sub(i,i)
  1462.   local char_color = get_rainbow_color(rainbow_offset + i*30)
  1463.   local char_y = title_y + math.sin(title_float + i*0.3) * 1
  1464.   outlined_text(char, x + (i-1)*6, char_y, char_color, 0, 1)
  1465.  end
  1466.  for i=1,6 do
  1467.   local spark_x = x + math.sin(time()*0.003 + i) * 60 + 60
  1468.   local spark_y = title_y + math.cos(time()*0.004 + i*2) * 8 + 4
  1469.   local spark_color = get_rainbow_color(rainbow_offset + i*60)
  1470.   pix(spark_x, spark_y, spark_color)
  1471.  end
  1472.  local gx=sx+GRID_X; local gy=sy+GRID_Y
  1473.  rect(gx-4,gy-4,GRID_SIZE*9+7,GRID_SIZE*9+7,0)
  1474.  rect(gx-3,gy-3,GRID_SIZE*9+5,GRID_SIZE*9+5,14)
  1475.  rect(gx-2,gy-2,GRID_SIZE*9+3,GRID_SIZE*9+3,2)
  1476.  rect(gx-1,gy-1,GRID_SIZE*9+1,GRID_SIZE*9+1,1)
  1477.  for r=1,GRID_SIZE do
  1478.   for c=1,GRID_SIZE do
  1479.    local x=gx+(c-1)*9; local y=gy+(r-1)*9
  1480.    local color=grid[r][c]
  1481.    local woff=0
  1482.    if wave_effect>0 and color==0 then
  1483.     woff=math.sin((c+r)*0.5+time()*0.01)*wave_effect*0.03
  1484.    end
  1485.    if color==0 then
  1486.     rect(x,y+woff,CELL_SIZE,CELL_SIZE,1)
  1487.     rectb(x,y+woff,CELL_SIZE,CELL_SIZE,2)
  1488.    else
  1489.     rect(x,y,CELL_SIZE,CELL_SIZE,color)
  1490.     line(x,y,x+CELL_SIZE-2,y,14)
  1491.     line(x,y,x,y+CELL_SIZE-2,14)
  1492.     line(x+CELL_SIZE-1,y+1,x+CELL_SIZE-1,y+CELL_SIZE-1,0)
  1493.     line(x+1,y+CELL_SIZE-1,x+CELL_SIZE-1,y+CELL_SIZE-1,0)
  1494.     rectb(x,y,CELL_SIZE,CELL_SIZE,0)
  1495.    end
  1496.    for _,cell in ipairs(clearing_cells) do
  1497.     if cell[1]==r and cell[2]==c then
  1498.      rectb(x-1,y-1,CELL_SIZE+2,CELL_SIZE+2,14)
  1499.      rectb(x-2,y-2,CELL_SIZE+4,CELL_SIZE+4,12)
  1500.     end
  1501.    end
  1502.   end
  1503.  end
  1504.  for _,e in ipairs(line_clear_effects) do
  1505.   local a=e.life/e.max_life
  1506.   local w=2+(1-a)*LINE_WIDTH_MAX
  1507.   local brightness = math.sin((1-a)*math.pi)
  1508.   if e.type=="row" then
  1509.    local y=gy+(e.pos-1)*9+4
  1510.    rect(gx-w,y-2,GRID_SIZE*9+w*2,5,14)
  1511.    rect(gx-w+1,y-1,GRID_SIZE*9+w*2-2,3,12)
  1512.    if brightness > 0.5 then rect(gx-w+2,y,GRID_SIZE*9+w*2-4,1,15) end
  1513.   else
  1514.    local x=gx+(e.pos-1)*9+4
  1515.    rect(x-2,gy-w,5,GRID_SIZE*9+w*2,14)
  1516.    rect(x-1,gy-w+1,3,GRID_SIZE*9+w*2-2,12)
  1517.    if brightness > 0.5 then rect(x,gy-w+2,1,GRID_SIZE*9+w*2-4,15) end
  1518.   end
  1519.  end
  1520.  if dragging then
  1521.   local row,col=get_grid_pos(mouse_x,mouse_y)
  1522.   if row and col then
  1523.    local base_row = row - (dragging.grab_r-1)
  1524.    local base_col = col - (dragging.grab_c-1)
  1525.    if can_place_shape(dragging.shape, base_row, base_col) then
  1526.     local would_clear = simulate_clears_if_placed(dragging.shape, base_row, base_col)
  1527.     if #would_clear>0 then
  1528.      for _,cell in ipairs(would_clear) do
  1529.       local r,c = cell[1], cell[2]
  1530.       local x = gx + (c-1)*9
  1531.       local y = gy + (r-1)*9
  1532.       rect(x,y,CELL_SIZE,CELL_SIZE,dragging.color)
  1533.       line(x,y,x+CELL_SIZE-2,y,12)
  1534.       line(x,y,x,y+CELL_SIZE-2,12)
  1535.       rectb(x,y,CELL_SIZE,CELL_SIZE,0)
  1536.       rectb(x-1,y-1,CELL_SIZE+2,CELL_SIZE+2,14)
  1537.       rectb(x-2,y-2,CELL_SIZE+4,CELL_SIZE+4,12)
  1538.       if time()%20<10 then rectb(x-3,y-3,CELL_SIZE+6,CELL_SIZE+6,15) end
  1539.      end
  1540.     end
  1541.    end
  1542.   end
  1543.  end
  1544.  if dragging then
  1545.   local row,col=get_grid_pos(mouse_x,mouse_y)
  1546.   if row and col then
  1547.    local base_row = row - (dragging.grab_r-1)
  1548.    local base_col = col - (dragging.grab_c-1)
  1549.    if can_place_shape(dragging.shape, base_row, base_col) then
  1550.     local s=dragging.shape
  1551.     for r=1,#s do
  1552.      for c=1,#s[r] do
  1553.       if s[r][c]==1 then
  1554.        local x=gx+(base_col + c -2)*9
  1555.        local y=gy+(base_row + r -2)*9
  1556.        rect(x,y,CELL_SIZE,CELL_SIZE,12)
  1557.        rectb(x,y,CELL_SIZE,CELL_SIZE,14)
  1558.        if time()%30<15 then rectb(x-1,y-1,CELL_SIZE+2,CELL_SIZE+2,15) end
  1559.       end
  1560.      end
  1561.     end
  1562.    else
  1563.     local s=dragging.shape
  1564.     for r=1,#s do
  1565.      for c=1,#s[r] do
  1566.       if s[r][c]==1 then
  1567.        local x=gx+(base_col + c -2)*9
  1568.        local y=gy+(base_row + r -2)*9
  1569.        if time()%20<10 then
  1570.         rect(x,y,CELL_SIZE,CELL_SIZE,6)
  1571.         rectb(x,y,CELL_SIZE,CELL_SIZE,7)
  1572.        end
  1573.       end
  1574.      end
  1575.     end
  1576.    end
  1577.   end
  1578.  end
  1579.  local streak_level = combo
  1580.  for _,p in ipairs(particles) do
  1581.   if p and p.life and p.x and p.y then
  1582.     if p.life>0 then
  1583.       local alpha = p.life / 45
  1584.       if p.sparkle then
  1585.         if time()%6<3 then
  1586.           rect(p.x+sx-1,p.y+sy-1,2,2,p.color)
  1587.         else
  1588.           pix(p.x+sx,p.y+sy,15)
  1589.         end
  1590.       elseif p.streak_particle then
  1591.         local size = p.size + math.sin(time()*0.1 + p.x*0.1)*0.5
  1592.         rect(p.x+sx-size//2,p.y+sy-size//2,size,size,p.color)
  1593.         if streak_level >= 5 then circb(p.x+sx,p.y+sy,size+1,15) end
  1594.       elseif p.warning then
  1595.         local pulse_size = 1 + math.sin(time()*0.2)*0.5
  1596.         rect(p.x+sx-pulse_size//2,p.y+sy-pulse_size//2,pulse_size,pulse_size,p.color)
  1597.       elseif p.size==2 then
  1598.         rect(p.x+sx-1,p.y+sy-1,2,2,p.color)
  1599.         if alpha > 0.7 then pix(p.x+sx,p.y+sy,15) end
  1600.       else
  1601.         pix(p.x+sx,p.y+sy,p.color)
  1602.       end
  1603.     end
  1604.   end
  1605.  end
  1606.  draw_scoreboard(sx, sy)
  1607.  print("NEXT", RIGHT_PANEL_X-2, RIGHT_PANEL_TOP-12, 12, false, 1)
  1608.  for i,sd in ipairs(next_shapes) do
  1609.   if not dragging or dragging.index~=i then
  1610.    local cx=sx+RIGHT_PANEL_X
  1611.    local cy=sy+RIGHT_PANEL_TOP+(i-1)*(PREVIEW_BOX+PREVIEW_GAP)
  1612.    rect(cx-2, cy-2, PREVIEW_BOX+4, PREVIEW_BOX+4, 0)
  1613.    rect(cx-1, cy-1, PREVIEW_BOX+2, PREVIEW_BOX+2, 14)
  1614.    rect(cx, cy, PREVIEW_BOX, PREVIEW_BOX, 2)
  1615.    local s=sd.shape; local h=#s; local w=#s[1]
  1616.    local bs=preview_cell_size(s)
  1617.    local sx0=cx+PREVIEW_BOX//2 - (w*bs)//2
  1618.    local sy0=cy+PREVIEW_BOX//2 - (h*bs)//2
  1619.    for r=1,h do
  1620.     for c=1,w do
  1621.      if s[r][c]==1 then
  1622.       local bx=sx0+(c-1)*bs; local by=sy0+(r-1)*bs
  1623.       rect(bx,by,bs-1,bs-1,sd.color)
  1624.       line(bx,by,bx+bs-2,by,12)
  1625.       line(bx,by,bx,by+bs-2,12)
  1626.       line(bx+bs-1,by+1,bx+bs-1,by+bs-1,0)
  1627.       line(bx+1,by+bs-1,bx+bs-1,by+bs-1,0)
  1628.       rectb(bx,by,bs-1,bs-1,0)
  1629.      end
  1630.     end
  1631.    end
  1632.    if mouse_x>=cx and mouse_x<=cx+PREVIEW_BOX and mouse_y>=cy and mouse_y<=cy+PREVIEW_BOX then
  1633.     rectb(cx-3,cy-3,PREVIEW_BOX+6,PREVIEW_BOX+6,14)
  1634.     if time()%40<20 then rectb(cx-4,cy-4,PREVIEW_BOX+8,PREVIEW_BOX+8,15) end
  1635.    end
  1636.   end
  1637.  end
  1638.  if dragging then
  1639.   local s=dragging.shape
  1640.   local x0 = mouse_x - (dragging.grab_c-1)*8 - 4
  1641.   local y0 = mouse_y - (dragging.grab_r-1)*8 - 4
  1642.   for r=1,#s do
  1643.    for c=1,#s[r] do
  1644.     if s[r][c]==1 then
  1645.      local x=x0 + (c-1)*8 + 2
  1646.      local y=y0 + (r-1)*8 + 2
  1647.      rect(x,y,7,7,0)
  1648.     end
  1649.    end
  1650.   end
  1651.   for r=1,#s do
  1652.    for c=1,#s[r] do
  1653.     if s[r][c]==1 then
  1654.      local x=x0 + (c-1)*8
  1655.      local y=y0 + (r-1)*8
  1656.      rect(x,y,7,7,dragging.color)
  1657.      line(x,y,x+6,y,12); line(x,y,x,y+6,12)
  1658.      line(x+6,y+1,x+6,y+6,0); line(x+1,y+6,x+6,y+6,0)
  1659.      rectb(x,y,7,7,0)
  1660.     end
  1661.    end
  1662.   end
  1663.  end
  1664.  for _,p in ipairs(score_popups) do
  1665.   if p and p.life and p.x and p.y and p.text then
  1666.     if p.life>0 then
  1667.       local s=p.scale or 1
  1668.       local alpha = p.life / 60
  1669.       local color = alpha > 0.5 and 14 or 12
  1670.       if tonumber((p.text or ""):match("%d+")) and tonumber((p.text or ""):match("%d+")) > 100 then
  1671.         outlined_text(p.text, (240 - text_w(p.text, s))//2, p.y+sy, color, 6, s)
  1672.       else
  1673.         outlined_center(p.text, p.y+sy, color, 0, s)
  1674.       end
  1675.     end
  1676.   end
  1677.  end
  1678.  if music_playing then
  1679.   local note_x = 8
  1680.   local note_y = 50 + math.sin(time()*0.005)*2
  1681.   local beat_pulse = math.sin(music_time*0.02)*0.3 + 0.7
  1682.   pix(note_x, note_y, beat_pulse > 0.8 and 12 or 14)
  1683.   pix(note_x+1, note_y, beat_pulse > 0.8 and 12 or 14)
  1684.   print("♪", note_x+3, note_y-2, beat_pulse > 0.8 and 11 or 12, false, 1)
  1685.  end
  1686.  if game_over then
  1687.   for y=0,136,3 do for x=0,240,3 do pix(x,y,0) pix(x+1,y+1,0) end end
  1688.   rect(45,35,150,80,0)
  1689.   rect(46,36,148,78,14)
  1690.   rect(47,37,146,76,1)
  1691.   rect(48,38,144,74,2)
  1692.   local go_y = 45 + math.sin(time()*0.005)*1
  1693.   outlined_center("GAME OVER", go_y, 6, 0, 2)
  1694.   print_center("FINAL SCORE", 70, 14, 1)
  1695.   local s = string.format("%08d", score)
  1696.   local bw = text_w(s,1) + 16
  1697.   local bx = (240 - bw)//2
  1698.   rect(bx-1, 81, bw+2, 14, 0)
  1699.   rect(bx, 82, bw, 12, 1)
  1700.   rect(bx+1, 83, bw-2, 10, 2)
  1701.   outlined_text(s, bx+8, 85, 11, 0, 1)
  1702.   if (time()%1200)<600 then
  1703.     outlined_center("Press R to RESTART", 102, 12, 0, 1)
  1704.   end
  1705.   print_center("Press M to toggle music", 112, 8, 1)
  1706.  end
  1707. end
  1708. _init()
  1709.  
  1710.  
  1711.  
Add Comment
Please, Sign In to add comment