match-3/main.lua
2023-03-29 15:19:08 +05:00

933 lines
19 KiB
Lua

local board={}
local bw,bh=10,10
local g=love.graphics
local font=g.newFont(9,"mono")
font:setFilter("nearest")
g.setFont(font)
local colors={
function(a,b)
g.setColor(1,0,0,b*a)
g.polygon("fill",0.5,0, 1,0.5, 0.5,1, 0,0.5)
g.setColor(1,0,0,a)
g.polygon("line",0.5,0, 1,0.5, 0.5,1, 0,0.5)
end,
function(a,b)
local w=0.5^0.5
local p=(1-w)/2
g.setColor(1,1,0,b*a)
g.rectangle("fill",p,p,w,w)
g.setColor(1,1,0,a)
g.rectangle("line",p,p,w,w)
end,
function(a,b)
g.setColor(0,1,0,b*a)
g.circle("fill",0.5,0.5,0.45,8)
g.setColor(0,1,0,a)
g.circle("line",0.5,0.5,0.45,8)
end,
function(a,b)
g.translate(0,0.15)
g.translate(0.5,0.5)
g.rotate(math.pi/2*3)
g.translate(-0.5,-0.5)
g.setColor(0,1,1,b*a)
g.circle("fill",0.5,0.5,0.5,3)
g.setColor(0,1,1,a)
g.circle("line",0.5,0.5,0.5,3)
end,
function(a,b)
g.translate(0.5,0.5)
g.rotate(math.pi/2*3)
g.translate(-0.5,-0.5)
g.setColor(1,0,1,b*a)
g.circle("fill",0.5,0.5,0.45,6)
g.setColor(1,0,1,a)
g.circle("line",0.5,0.5,0.45,6)
end,
function(a,b)
g.setColor(0,0,1,b*a)
g.setColor(0,0,1,a)
g.line(0.1,0.4, 0.5,0, 0.9,0.4)
g.line(0.1,1-0.4, 0.5,1-0, 0.9,1-0.4)
g.line(0.3,0.5, 0.7,0.5)
end,
}
local gobjs={}
local cgobjs={}
local lgobjs={}
local mgobjs={}
for k,v in ipairs(colors) do
gobjs[k]={c=k}
cgobjs[k]={c=k,super="charge"}
lgobjs[k]={c=k,super="lines"}
mgobjs[k]={c=k,super="color"}
end
local cc=3
local tt=0
local bat=1
local batd=bat
local score=0
local tochar=0
local scored=score
local function znoise(...)
return love.math.noise(...)*2-1
end
local function eudist(x,y)
return (x*x+y*y)^0.5
end
local function norm(x1,y1)
local d=eudist(x,y)
if d==0 then return 0,0 end
return x/d,y/d
end
local function dlbolt(x,y,x1,y1)
end
local function drawfig(n,a)
g.push("all")
g.scale(2-(a or 1))
g.translate(-0.5,-0.5)
g.translate(0.1,0.1)
g.scale(0.8)
colors[n.c](a or 1,n.super=="charge" and ((math.sin(tt*math.pi*2)*0.5+0.5)*0.4+0.1) or 0)
if n.super=="lines" then
local hps,vps={},{}
local det=10
for n=0,det do
local dmul=(det-math.abs(n-det/2))/det*4
hps[n*2+1],hps[n*2+2]=n/det,0.5+znoise(tt,583,-775,n)*0.1*dmul
vps[n*2+1],vps[n*2+2]=0.5+znoise(tt,-492,444,n)*0.1*dmul,n/det
end
g.line(hps)
g.line(vps)
elseif n.super=="color" then
local l={}
local det=100
for n=0,det do
l[n*2+1],l[n*2+2]=0.5+znoise(tt,55,51,n*math.pi/det*2),0.5+znoise(tt,44,25,n*math.pi/det*2)
end
g.setLineJoin("none")
g.polygon("line",l)
end
g.scale(0.02)
g.pop()
end
local function pti(x,y)
return y+(x-1)*bh
end
local trans=love.math.newTransform()
local anims={}
local falling={}
local booms={}
local function smtar(x,y,tx,ty)
local i=pti(x,y)
local anim
if not anims[i] then
if x==tx and y==ty then return end
anims[i]={
cx=x,cy=y,
tx=tx,ty=ty,
x=x,y=y
}
end
anim=anims[i]
anim.tx,anim.ty=tx,ty
end
local function setbsize(x,y,w,h)
trans:reset()
trans:translate(x,y)
trans:scale(w/bw,h/bh)
end
local gemrng
local function randgem()
tochar=tochar-1
local objl=gobjs
if tochar<=0 then
tochar=gemrng:random((bw*bh)*0.5,(bw*bh)*1.5)
objl=cgobjs
end
return objl[gemrng:random(math.floor(cc))]
end
local function nextgem(o)
local c=o.c%math.floor(cc)+1
return (o.super and cgobjs or gobjs)[c]
end
local function isgem(o)
return type(o)=="table" and colors[o.c]
end
local function eqgem(a,b)
return isgem(a) and isgem(b) and a.c==b.c
end
local function badboigemcheck(c,am,ac,rm)
c=isgem(c) and c.c
if not c then return end
if ac~=5 and ac~=4 then return end
local aa={}
for k,v in pairs(am) do
local cc=board[v[1]][v[2]]
if isgem(cc) and (cc.super and cc.super~="charge") then
return
end
aa[#aa+1]=k
end
local e=aa[gemrng:random(#aa)]
local rc=ac==5 and mgobjs[c] or lgobjs[c]
rm[e][3]=rc
end
local function megascan(x,y,c,rm)
local ogc=c
rm=rm or {}
if not isgem(ogc) then return next(rm) and rm end
if ogc.super~="color" then return next(rm) and rm end
for x=1,bw do
for y=1,bh do
local i=pti(x,y)
local c=board[x][y]
if eqgem(c,ogc) then
rm[i]=rm[i] or {x,y}
end
end end
return next(rm) and rm
end
local function check(x,y,rm,oam,obm)
local c=board[x][y]
if not c then return rm,oam,obm end
local ac,bc=1,1
local am,bm={},{}
oam,obm=oam or {}, obm or {}
do
local i=pti(x,y)
local v={x,y}
am[i],bm[i]=v,v
end
rm=rm or {}
for d,v in ipairs{{-1,0},{1,0},{0,-1},{0,1}} do
local ox,oy=unpack(v)
local x,y=x+ox,y+oy
while x>=1 and x<=bw and y>=1 and y<=bh do
local i=pti(x,y)
if eqgem(board[x][y],c) then
if d>2 and not oam[i] then
ac=ac+1
am[i]={x,y}
oam[i]=true
elseif d<=2 and not obm[i] then
bc=bc+1
bm[i]={x,y}
obm[i]=true
end
else
break
end
x,y=x+ox,y+oy
end
end
if ac>=3 then
for k,v in pairs(am) do rm[k]=v end
badboigemcheck(c,am,ac,rm)
end
if bc>=3 then
for k,v in pairs(bm) do rm[k]=v end
badboigemcheck(c,bm,bc,rm)
end
return next(rm) and rm,oam,obm
end
local falltest=false
local function drawboard(x,y,w,h)
g.push("all")
g.applyTransform(trans)
g.setLineWidth(0.1)
g.stencil(function()
g.rectangle("fill",0,0,bw,bh)
end)
g.push("all")
g.translate(0.1,-1-0.05)
local bat=math.min(batd,1)
g.setColor((1-bat^2),bat^0.5,math.max(0,batd-1)^0.5)
g.rectangle("line",0,0,bw/2-0.1,0.8)
g.rectangle("fill",0.2,0.2,(bw/2-0.5)*bat,0.4)
g.rectangle("line",bw/2-0.09,0.2,0.2,0.4)
g.pop()
g.push("all")
local sctext=("%.1i"):format(math.floor(scored+.5))
local xx,yy=trans:transformPoint(0,0)
local tty=0.8/font:getHeight()
local ttx=font:getWidth(sctext)*tty
g.translate(bw-ttx,-1.1)
g.scale(tty)
g.setColor(1,1,1)
g.print(sctext)
g.pop()
if not falltest then
g.setStencilTest("greater",0)
end
for x=1,bw do
for y=1,bh do
g.push()
local tx,ty=x,y
local anim=anims[pti(x,y)]
if anim then
tx,ty=anim.cx or x, anim.cy or y
end
g.translate(tx-0.5,ty-0.5)
if isgem(board[x][y]) then
drawfig(board[x][y])
end
g.pop()
end
end
for x,falling in ipairs(falling) do
for fall,_ in pairs(falling) do
for i,c in ipairs(fall.figs) do
g.push("all")
local tx,ty=fall.x,fall.y-i+1
g.translate(tx-0.5,ty-0.5)
if isgem(c) then
drawfig(c)
end
g.pop()
end
end
end
for v,_ in pairs(booms) do
g.push("all")
local tx,ty=v.x,v.y
g.translate(tx-0.5,ty-0.5)
drawfig(v.c,v.a)
g.pop()
end
g.setStencilTest()
g.setColor(0.2,0.2,0.2)
g.rectangle("line",0,0,bw,bh)
g.pop()
end
local function fall(l)
local cols={}
for k,v in pairs(l) do
local x,y=unpack(v)
cols[x]=cols[x] or {l={},m={}}
cols.l[#cols+1]=y
cols.m[y]=true
end
end
local killall
local function kill(x,y,rc)
if not board[x][y] then print("Whar") return end
if board[x][y].super=="charge" then
bat=math.min(2,bat+0.1)
end
do
local tx,ty=x,y
local anim=anims[pti(x,y)]
if anim then
tx,ty=anim.cx,anim.cy
end
booms[{
x=tx,y=ty,
a=1,c=board[x][y] or mgobjs[4]
}]=true
end
if rc then
board[x][y]=rc
return true
end
board[x][y]=nil
local fall={}
for y=y-1,gem and 1 or (-bh+1),-1 do
if board[x][y] then
if not fall.figs then
fall.x,fall.y=x,y
fall.figs={}
fall.vel=0
falling[x][fall]=true
end
fall.figs[#fall.figs+1]=board[x][y]
board[x][y]=nil
elseif fall.figs then
fall={}
end
end
local my,mfall=math.huge
local unfell={}
local yls={}
local cx=x
for x=1,bw do
local yl
for y=bh,0,-1 do
if not board[x][y] then
yl=y
break
end
end
unfell[x]={}
local tt={}
for k,v in pairs(falling[x]) do
tt[#tt+1]=k
end
table.sort(tt,function(a,b)
return a.y>b.y
end)
for _,fall in ipairs(tt) do
if cx==x and fall.y<my then
my=fall.y
mfall=fall
end
for k,v in ipairs(fall.figs) do
unfell[x][yl]={board[x][yl]}
board[x][yl]=v
yl=yl-1
end
end
yls[x]=yl
end
local yl=yls[x]
if not mfall then
mfall={x=x,y=0,vel=0,figs={}}
falling[x][mfall]=true
end
local ll=#mfall.figs+1
mfall.figs[ll]=randgem()
local aa={}
unfell[x][yls[x]]=unfell[x][yls[x]] or {board[x][yls[x]]}
board[x][yls[x]]=mfall.figs[ll]
local ccc=0
if love.keyboard.isDown("f6") then
for x,falling in pairs(falling) do
for fall,_ in pairs(falling) do
falling[fall]=nil
end
end
return
end
local occ=mfall.figs[ll]
while check(x,yls[x]) do
mfall.figs[ll]=nextgem(mfall.figs[ll])
board[x][yls[x]]=mfall.figs[ll]
if eqgem(mfall.figs[ll],occ) then break end
end
for x,uf in pairs(unfell) do
for y,v in pairs(uf) do
board[x][y]=v[1]
end
end
cc=math.min(#colors,cc+((0.1/(bw*bh))/((cc/3)^2)))
end
killall=function(kills,b,...)
if not kills then return end
if b then
for k,v in pairs(b) do
kills[k]=v
end
return killall(kills,...)
end
local tt={}
local seen={}
local news=true
while news do
news=false
local ko
kills,ko={},kills
for k,v in pairs(ko) do
kills[k]=v
if not seen[k] then
local x,y=unpack(v)
local c=board[v[1]][v[2]]
seen[k]=true
if isgem(c) and c.super=="lines" then
for x=1,bw do
local i=pti(x,y)
if board[x][y] and not kills[i] then
news=true
kills[i]={x,y}
end
end
for y=1,bh do
local i=pti(x,y)
if board[x][y] and not kills[i] then
news=true
kills[i]={x,y}
end
end
end
if isgem(c) then
local mks={}
megascan(x,y,c,mks)
for k,v in pairs(mks) do
if not kills[k] then
kills[k]=v
news=true
end
end
end
end
end
end
for k,v in pairs(kills) do
tt[#tt+1]=v
end
table.sort(tt,function(a,b)
return b[2]>a[2] or b[1]>b[1]
end)
local checks={}
for k,v in ipairs(tt) do
local a=kill(unpack(v))
if a then
checks[k]=v
end
end
local rm,oam,obm={},{},{}
for k,v in pairs(checks) do
local x,y=unpack(v)
check(x,y,rm,oam,obm)
end
if next(rm) then
killall(rm)
end
score=score+#tt
end
local function dropall(off,die)
for x=1,bw do
falling[x]=falling[x] or {}
local fall={
x=x,y=bh+(off or 0),
vel=0,
figs={}
}
falling[x][fall]=true
for y=bh,1,-1 do
fall.figs[#fall.figs+1]=board[x][y]
board[x][y]=nil
end
end
if die then
for x,falling in pairs(falling) do
for fall,_ in pairs(falling) do
fall.die=true
end
end
end
end
local function fillboard()
for x=1,bw do
board[x]={}
for y=1,bh do
local a=randgem()
board[x][y]=a
end
end
for n=1,math.huge do
local good=true
local rm={}
local kills
for x=1,bw do
for y=1,bh do
kills=check(x,y,rm) or kills
end
end
if not next(rm) then break end
for k,v in pairs(rm) do
local x,y=unpack(v)
board[x][y]=randgem()
end
end
dropall(-bh)
end
local function resboard()
cc=3
score=0
bat=1
gemrng=love.math.newRandomGenerator(991)
tochar=bw*bh+gemrng:random(bw)
fillboard()
end
local dbg=false
local playdbg=false
function love.load()
resboard()
end
local grabbed=nil
local dying=false
local running=false
function love.mousepressed(x,y,b)
x,y=trans:inverseTransformPoint(x,y)
x,y=x+1-0.5,y+1-0.5
local ix,iy=math.floor(x+.5),math.floor(y+.5)
if b==2 and dbg then
for ix=ix-50,iy+50 do
for iy=iy-50,iy+50 do
if board[ix] and board[ix][iy] and math.random()>0.1 then
kill(ix,iy)
end
end end
end
if b~=1 then return end
running=true
if ix<1 or ix>bw or
iy<1 or iy>bh
then
return
end
if grabbed then
return
end
grabbed={
ix,iy,
ix-x,iy-y,
}
end
function love.mousereleased(x,y,b)
if b~=1 then return end
if grabbed then
local ix,iy,ox,oy,px,py=unpack(grabbed)
if ix and px and (ix~=px or iy~=py) then
board[ix][iy],board[px][py]=
board[px][py],board[ix][iy]
local kills=check(ix,iy,check(px,py))
if kills and board[ix][iy] and board[px][py] then
megascan(ix,iy,board[ix][iy],megascan(px,py,board[px][py],kills))
local ii,pi=pti(ix,iy),pti(px,py)
local ania,anib=anims[ii],anims[pi]
anims[ii],anims[pi]=anib,ania
ania=ania or {x=ix,y=iy}
anib=anib or {x=px,y=py}
ania.x,anib.x=anib.x,ania.x
ania.y,anib.y=anib.y,ania.y
grabbed=nil
killall(kills)
return
else
board[ix][iy],board[px][py]=
board[px][py],board[ix][iy]
end
end
grabbed=nil
smtar(ix,iy,ix,iy)
if px then
smtar(px,py,px,py)
end
return
end
end
local gacc=50
-- i forgor how intersections work so here's a thingy to figure it out
-- A1-----A2
-- B1-----B2
--
-- B1-----B2
-- A1-----A2
local function inters(a1,a2,b1,b2)
return
(a1<=b1 and a2>=b1)
or (b1<=a1 and b2>=a1)
end
local function negdiff(a,b)
return math.abs(a-b)<0.01
end
local function sign(a)
return a>0 and 1 or (a<0 and -1 or 0)
end
local function interp(a,b,dt)
return a+sign(b-a)*math.min((b-a)*sign(b-a),math.max(dt,(b-a)*sign(b-a)*math.max(dt,0.01)*10))
end
local animc=0
local entc=0
local boomc=0
local slow=1
local accumdt=0
local tps=120
local speeds={1,0.05,10}
local plchint=1
local toplchi=plchint
function love.mousemoved()
if grabbed then
local x,y,ox,oy,px,py=unpack(grabbed)
local mx,my=love.mouse.getPosition()
mx,my=trans:inverseTransformPoint(mx,my)
mx,my=mx+1-0.5+ox,my+1-0.5+oy
mx,my=math.floor(mx+.5),math.floor(my+.5)
if math.abs(x-mx)>math.abs(y-my) then
mx,my=math.min(bw,math.max(1,x+sign(mx-x))),y
elseif math.abs(y-my)>0 then
my,mx=math.min(bh,math.max(1,y+sign(my-y))),x
end
if px then
smtar(px,py,px,py)
end
grabbed[5],grabbed[6]=mx,my
if not board[x][y] then
elseif board[mx][my] then
smtar(mx,my,x,y)
smtar(x,y,mx,my)
end
end
end
function love.update(dt)
dt=math.min(dt,0.2)*(speeds[slow])
accumdt=math.min(1,accumdt+dt)
local fcc=0
while accumdt>1/tps do
fcc=fcc+1
local dt=1/tps
accumdt=accumdt-dt
if fcc>10 then return end
if running and playdbg then
for _=1,100 do
local ix,iy=math.random(1,bw),math.random(1,bh)
local px,py=0,0
while px<1 or py<1 or px>bw or py>bh do
local ox,oy=unpack(({{1,0},{-1,0},{0,1},{0,-1}})[math.random(1,4)])
px,py=ix+ox,iy+oy
end
board[ix][iy],board[px][py]=
board[px][py],board[ix][iy]
local kills=check(ix,iy,check(px,py))
if kills and board[ix][iy] and board[px][py] then
megascan(ix,iy,board[ix][iy],megascan(px,py,board[px][py],kills))
killall(kills)
else
board[ix][iy],board[px][py]=
board[px][py],board[ix][iy]
end
end
end
if running then
bat=math.max(0,bat-dt*0.015*(math.max(bat,0.0625)^0.5))
batd=interp(batd,bat,dt)
scored=interp(scored,score,dt)
end
if bat==0 and not dying then
dying=true
dropall(0,true)
end
animc=0
entc=0
boomc=0
for k,v in pairs(anims) do
animc=animc+1
v.cx=interp(v.cx,v.tx,dt*2)
v.cy=interp(v.cy,v.ty,dt*2)
if negdiff(v.cx,v.x) and negdiff(v.cy,v.y) then
anims[k]=nil
end
end
local tk={}
if running then
for x,falling in ipairs(falling) do
for fall,_ in pairs(falling) do
entc=entc+1
fall.vel=fall.vel+dt*gacc
fall.y=fall.y+fall.vel*dt
local iy=math.floor(fall.y)
for fallb,_ in pairs(falling) do
if fall~=fallb and inters(
fall.y-#fall.figs+1-0.5,fall.y+0.5,
fallb.y-#fallb.figs+1-0.5,fallb.y+0.5
) and fall.die==fallb.die then
local wa,wb=#fall.figs,#fallb.figs
fall.vel=(fall.vel*wa+fallb.vel*wb)/(wa+wb)
if fallb.y<fall.y then
local ff=#fall.figs
for i,fig in ipairs(fallb.figs) do
fall.figs[ff+i]=fig
end
else
local tt={}
for i,fig in ipairs(fall.figs) do
tt[i]=fig
end
for i,fig in ipairs(fallb.figs) do
fall.figs[i]=fig
end
local ff=#fallb.figs
for i,fig in ipairs(tt) do
fall.figs[ff+i]=fig
end
fall.y=fallb.y
end
falling[fallb]=nil
end
end
if board[x][iy+1] or (not fall.die and {iy>=bh} or {iy>=bh+#fall.figs})[1] then
falling[fall]=nil
if not fall.die then
local off=math.min(iy,bh)
for i,fig in ipairs(fall.figs) do
while board[x][off] do
off=off-1
end
if off<=bh and off>=1 then
board[x][off]=fig
end
off=off-1
end
for i=off+1,off+#fall.figs do
tk[#tk+1]={x,i}
end
end
end
end
end
end
if entc==0 and dying then
dying=false
running=false
resboard()
end
if entc==0 and running then
toplchi=toplchi-dt
if toplchi<0 then
toplchi=toplchi+plchint
local good=false
for ix=1,bw do
for iy=1,bh do
for _,a in pairs{{1,0},{-1,0},{0,1},{0,-1}} do
local px,py=ix+a[1],iy+a[2]
if px>=1 and py>=1 and px<=bw and py<=bh then
board[ix][iy],board[px][py]=
board[px][py],board[ix][iy]
good=good or check(ix,iy,check(px,py))
board[ix][iy],board[px][py]=
board[px][py],board[ix][iy]
end
end end end
if not good then dropall(0,true)fillboard() end
end
else
toplchi=plchint
end
local oam,obm,rm={},{},{}
for k,v in pairs(tk) do
local x,y=unpack(v)
check(x,y,rm,oam,obm)
end
killall(rm)
for v,_ in pairs(booms) do
boomc=boomc+1
v.a=v.a-dt/math.abs(v.a)*4
if v.a<=0.01 then
booms[v]=nil
end
end
tt=tt+dt
end
end
local yes=true
function love.keypressed(k)
local x,y=love.mouse.getPosition()
x,y=trans:inverseTransformPoint(x,y)
x,y=math.floor(x)+1,math.floor(y)+1
x=math.max(1,math.min(bw,x))
y=math.max(1,math.min(bh,y))
if dbg and tonumber(k) then
print(gobjs[tonumber(k)],tonumber(k))
board[x][y]=gobjs[tonumber(k)] or board[x][y]
end
if dbg and isgem(board[x][y]) then
local c=board[x][y].c
if k=="h" then
board[x][y]=cgobjs[c]
elseif k=="j" then
board[x][y]=lgobjs[c]
elseif k=="k" then
board[x][y]=mgobjs[c]
end
end
if k=="f2" then
yes=not yes
elseif k=="f3" then
dbg=not dbg
elseif k=="f4" then
local unfell={}
local yls={}
local cx=x
for x=1,bw do
local yl
for y=bh,0,-1 do
if not board[x][y] then
yl=y
break
end
end
unfell[x]={}
local tt={}
for k,v in pairs(falling[x]) do
tt[#tt+1]=k
end
table.sort(tt,function(a,b)
return a.y>b.y
end)
for _,fall in ipairs(tt) do
falling[x][fall]=nil
for k,v in ipairs(fall.figs) do
unfell[x][yl]={board[x][yl]}
board[x][yl]=v
yl=yl-1
end
end
yls[x]=yl
end
elseif k=="f5" then
slow=slow%3+1
elseif k=="f7" then
falltest=not falltest
elseif k=="f8" then
bat=bat*0.25
elseif k=="f9" then
bat=bat*2
elseif k=="f10" then
playdbg=not playdbg
end
end
function love.draw()
if dbg then
g.push("all")
g.scale(2,2)
love.graphics.print(("FPS: %i; dt: %.2f; speed: %f; anims: %i; fallings: %i; booms: %i"):format(
love.timer.getFPS(),
math.floor(love.timer.getDelta()*100000)/100,
speeds[slow],
animc, entc, boomc
))
g.pop()
end
local sw,sh=g.getDimensions()
local ww=math.min(sw,sh)
local y
if falltest then
ww=math.min(sw,sh)*0.5
y=(sh)/2
end
local x,y=(sw-ww)/2,y or (sh-ww)/2
setbsize(x+ww*0.1,y+ww*0.1+ww/bh*0.2,ww*0.8,ww*0.8)
if yes then
drawboard()
end
end