Files
Techmino/parts/scenes/app_link.lua
2024-09-26 02:29:24 +08:00

410 lines
12 KiB
Lua

local ms=love.mouse
local msIsDown,kbIsDown=ms.isDown,love.keyboard.isDown
local gc=love.graphics
local gc_setColor,gc_rectangle,gc_draw=gc.setColor,gc.rectangle,gc.draw
local setFont,mStr=FONT.set,GC.mStr
local floor,rnd,abs=math.floor,math.random,math.abs
local max,min=math.max,math.min
local ins,rem=table.insert,table.remove
local levels={
{c=6,r=3,color=3},
{c=6,r=4,color=3},
{c=6,r=4,color=4},
{c=8,r=5,color=4},
{c=8,r=6,color=5},
{c=9,r=6,color=5},
{c=10,r=6,color=6},
{c=12,r=7,color=6},
{c=14,r=8,color=7},
{c=14,r=9,color=7},
{c=15,r=10,color=7},
{c=17,r=10,color=8},
{c=18,r=11,color=8},
{c=20,r=12,color=9},
{c=22,r=13,color=9},
{c=24,r=14,color=9},
}
local colorList={
COLOR.lR,
COLOR.lB,
COLOR.lY,
COLOR.lG,
COLOR.lM,
COLOR.lC,
COLOR.H,
COLOR.Z,
COLOR.lF,
}
gc.setDefaultFilter('nearest','nearest')
local iconList={
GC.DO{10,10,{'fRect',2,2,6,6}},
GC.DO{10,10,{'dRect',2.5,2.5,5,5}},
GC.DO{10,10,{'fCirc',5,5,2}},
GC.DO{10,10,{'fRect',2,2,2,6},{'fRect',6.5,2,2,6}},
GC.DO{10,10,{'fRect',2,2,1,1},{'fRect',3,3,1,1},{'fRect',4,4,1,1},{'fRect',5,5,1,1},{'fRect',6,6,1,1},{'fRect',7,7,1,1}},
GC.DO{10,10,{'fRect',2,2,2,2},{'fRect',2,6,2,2},{'fRect',6,2,2,2},{'fRect',6,6,2,2}},
GC.DO{1,1},
GC.DO{10,10,{'fRect',2,2,1,6},{'fRect',3,2,1,5},{'fRect',4,2,1,4},{'fRect',5,2,1,3},{'fRect',6,2,1,2},{'fRect',7,2,1,1}},
GC.DO{10,10,{'fRect',2,5,3,3},{'fRect',5,2,3,3}},
}
gc.setDefaultFilter('linear','linear')
local invis
local state
local startTime,time
local progress,level
local score,score1
local combo,comboTime,maxCombo,noComboBreak
local field={
x=160,y=40,
w=960,h=640,
c=16,r=10,
remain=0,
}
local lines={}
local selX,selY
local function resetBoard()
selX,selY=false,false
local colors=levels[level].color
field.c,field.r=levels[level].c,levels[level].r
local total=field.r*field.c/2-- Total cell count
local pool=TABLE.new(floor(total/colors),colors)
for i=1,total%colors do pool[i]=pool[i]+1 end
for i=1,#pool do pool[i]=pool[i]*2 end
field.remain=total
field.full=true
total=total*2
TABLE.cut(field)
for y=1,field.r do
field[y]={}
for x=1,field.c do
local ri=0
local rn=rnd(total)
while rn>0 do
ri=ri+1
rn=rn-pool[ri]
end
total=total-1
pool[ri]=pool[ri]-1
field[y][x]=ri
end
end
noComboBreak=true
comboTime=comboTime+2
SYSFX.newShade(2,field.x,field.y,field.w,field.h,1,1,1)
end
local function newGame()
state=0
level=1
progress={}
time=0
startTime=TIME()
score,score1,combo,comboTime,maxCombo=0,0,0,0,0
resetBoard()
end
local function addPoint(list,x,y)
local l=#list
if x~=list[l-1] or y~=list[l] then
list[l+1]=x
list[l+2]=y
end
end
local function checkLink(x1,y1,x2,y2)
-- Y-X-Y Check
local bestLen,bestLine=1e99,false
do
if x1>x2 then x1,y1,x2,y2=x2,y2,x1,y1 end
local luy,ldy,ruy,rdy=y1,y1,y2,y2
while luy>1 and not field[luy-1][x1] do luy=luy-1 end
while ldy<field.r and not field[ldy+1][x1] do ldy=ldy+1 end
while ruy>1 and not field[ruy-1][x2] do ruy=ruy-1 end
while rdy<field.r and not field[rdy+1][x2] do rdy=rdy+1 end
for y=max(luy,ruy),min(ldy,rdy) do
local nextLine
for x=x1+1,x2-1 do if field[y][x] then nextLine=true break end end-- goto CONTINUE_nextRow
if not nextLine then
local len=abs(x1-x2)+abs(y-y1)+abs(y-y2)
if len<bestLen then
bestLen=len
bestLine={x1,y1}
addPoint(bestLine,x1,y)
addPoint(bestLine,x2,y)
addPoint(bestLine,x2,y2)
end
end
-- ::CONTINUE_nextRow::
end
end
-- X-Y-X Check
do
if y1>y2 then x1,y1,x2,y2=x2,y2,x1,y1 end
local ulx,urx,dlx,drx=x1,x1,x2,x2
while ulx>1 and not field[y1][ulx-1] do ulx=ulx-1 end
while urx<field.c and not field[y1][urx+1] do urx=urx+1 end
while dlx>1 and not field[y2][dlx-1] do dlx=dlx-1 end
while drx<field.c and not field[y2][drx+1] do drx=drx+1 end
for x=max(ulx,dlx),min(urx,drx) do
local nextLine
for y=y1+1,y2-1 do if field[y][x] then nextLine=true break end end-- goto CONTINUE_nextCol
if not nextLine then
local len=abs(y1-y2)+abs(x-x1)+abs(x-x2)
if len<bestLen then
bestLen=len
bestLine={x1,y1}
addPoint(bestLine,x,y1)
addPoint(bestLine,x,y2)
addPoint(bestLine,x2,y2)
end
end
-- ::CONTINUE_nextCol::
end
end
return bestLine
end
local function tap(x,y)
if x>=1 and x<=field.c and y>=1 and y<=field.r then
if state==0 then
state=1
startTime=TIME()
elseif state==1 then
if selX and (x~=selX or y~=selY) and field[y][x]==field[selY][selX] then
local line=checkLink(x,y,selX,selY)
if line then
ins(lines,{time=0,line=line})
-- Clear
field[y][x]=false
field[selY][selX]=false
field.remain=field.remain-1
field.full=false
-- Score
local s=1000+floor(combo^.9)
score=score+s
TEXT.show("+"..s,1205,600,20,'score')
-- Combo
if comboTime==0 then
combo=0
noComboBreak=false
end
comboTime=comboTime*max(1-combo*.001,.95)+max(1-combo*.01,.8)
combo=combo+1
if combo>maxCombo then maxCombo=combo end
-- Check win
if field.remain==0 then
if noComboBreak then
SFX.play('emit')
SFX.play('clear_4')
TEXT.show("FULL COMBO",640,360,100,'beat',.626)
comboTime=comboTime+3
score=floor(score*1.1)
end
ins(progress,
noComboBreak and
("%s [FC] %.2fs"):format(level,TIME()-startTime) or
("%s - %.2fs"):format(level,TIME()-startTime)
)
level=level+1
if levels[level] then
resetBoard()
SFX.play('reach')
else
state=2
SFX.play('win')
end
else
SFX.play(
combo<50 and 'clear_1' or
combo<100 and 'clear_2' or
'clear_3',.8
)
end
selX,selY=false,false
else
selX,selY=x,y
SFX.play('lock',.9)
end
else
if field[y][x] and (x~=selX or y~=selY) then
selX,selY=x,y
SFX.play('lock',.8)
end
end
end
end
end
local scene={}
function scene.enter()
invis=false
newGame()
BGM.play('truth')
DiscordRPC.update("Playing Link")
end
function scene.keyDown(key,isRep)
if isRep then return end
if key=='r' then
if state~=1 or tryReset() then
newGame()
end
elseif key=='z' or key=='x' then
love.mousepressed(ms.getPosition())
elseif key=='escape' then
if state~=1 or tryBack() then
SCN.back()
end
elseif state==0 then
if key=='q' then
invis=not invis
end
end
end
local function touch(x,y)
x=floor((x-field.x)/field.w*field.c+1)
y=floor((y-field.y)/field.h*field.r+1)
tap(x,y)
end
function scene.mouseDown(x,y,k) if k==1 or k==2 or not k then touch(x,y) end end
function scene.mouseMove(x,y) if (msIsDown(1) or kbIsDown('z','x')) then touch(x,y) end end
function scene.touchDown(x,y) touch(x,y) end
function scene.touchMove(x,y) touch(x,y) end
function scene.update(dt)
if state==1 then
time=TIME()-startTime
comboTime=max(comboTime-dt,0)
score1=score1+MATH.sign(score-score1)+floor((score-score1)*.1+.5)
end
for i=#lines,1,-1 do
local L=lines[i]
L.time=L.time+dt
if L.time>=1 then
rem(lines,i)
end
end
end
function scene.draw()
gc.push('transform')
-- Camera
gc.translate(field.x,field.y)
-- Background
gc.setColor(COLOR.dX)
gc.rectangle('fill',0,0,field.w,field.h)
gc.scale(field.w/field.c,field.h/field.r)
-- Matrix
local mono=state==0 or invis and not field.full
if mono then
gc_setColor(COLOR.dH)
for y=1,field.r do
for x=1,field.c do
if field[y][x] then
gc_rectangle('fill',x-1,y-1,1,1)
end
end
end
else
for y=1,field.r do
for x=1,field.c do
local t=field[y][x]
if t then
gc_setColor(colorList[t])
gc_rectangle('fill',x-1,y-1,1,1)
gc_setColor(0,0,0,.26)
gc_draw(iconList[t],x-1,y-1,nil,.1,.1)
end
end
end
end
-- Selecting box
gc.setLineWidth(.1)
if selX then
gc_setColor(1,1,1)
gc_rectangle('line',selX-1+.05,selY-1+.05,.9,.9)
end
-- Clearing lines
gc.translate(-.5,-.5)
for i=1,#lines do
gc_setColor(1,1,1,1-lines[i].time)
gc.line(lines[i].line)
end
gc.pop()
-- Frame
if state==2 then
gc.setColor(.9,.9,0)-- win
elseif state==1 then
gc.setColor(.9,.9,.9)-- game
elseif state==0 then
gc.setColor(.2,.8,.2)-- ready
end
gc.setLineWidth(6)
gc.rectangle('line',field.x-5,field.y-5,field.w+10,field.h+10)
-- Draw no-setting area
if state==2 then
gc.setColor(1,0,0,.3)
gc.rectangle('fill',0,100,155,80)
end
-- Maxcombo
setFont(20)gc.setColor(COLOR.dF)
gc.print(maxCombo,1142,1)
-- Time
setFont(30)gc.setColor(COLOR.Z)
gc.print(("%.3f"):format(time),1140,20)
-- Progress time list
setFont(15)gc.setColor(.6,.6,.6)
for i=1,#progress do gc.print(progress[i],1140,40+20*i) end
-- Combo Rectangle
if comboTime>0 then
local r=32*comboTime^.3
gc.setColor(1,1,1,min(.6+comboTime,1)*.25)
gc.rectangle('fill',1205-r,440-r,2*r,2*r,2)
gc.setColor(1,1,1,min(.6+comboTime,1))
gc.setLineWidth(2)
gc.rectangle('line',1205-r,440-r,2*r,2*r,4)
end
-- Combo Text
setFont(60)
if combo>50 then
gc.setColor(1,.2,.2,min(.3+comboTime*.5,1)*min(comboTime,1))
mStr(combo,1205+(rnd()-.5)*combo^.5,398+(rnd()-.5)*combo^.5)
end
gc.setColor(1,1,max(1-combo*.001,.5),min(.4+comboTime,1))
mStr(combo,1205,398)
-- Score
setFont(25)gc.setColor(COLOR.Z)
mStr(score1,1205,560)
end
scene.widgetList={
WIDGET.newButton{name='reset',x=80,y=60,w=110,h=60,color='lG',fText=CHAR.icon.retry_spin,code=pressKey'r',hideF=function() return state==0 end},
WIDGET.newSwitch{name='invis',x=100,y=140,lim=80,disp=function() return invis end,code=pressKey'q',hideF=function() return state==1 end},
WIDGET.newButton{name='back',x=1200,y=660,w=110,font=45,sound='back',fText=CHAR.icon.back,code=pressKey'escape'},
}
return scene