Merge pull request #904 from 26F-Studio/ci-web-test

Ci web test
This commit is contained in:
Particle_G
2023-06-15 10:56:52 +08:00
committed by GitHub
18 changed files with 274 additions and 213 deletions

View File

@@ -143,7 +143,8 @@ jobs:
build-android:
runs-on: ubuntu-latest
needs: [get-info, build-core, auto-test]
if: github.event_name != 'pull_request'
# if: github.event_name != 'pull_request'
if: ${{ !always() }}
env:
OUTPUT_FOLDER: ./build
RELEASE_FOLDER: ./release
@@ -216,7 +217,8 @@ jobs:
build-ios:
runs-on: macos-latest
needs: [get-info, build-core, auto-test]
if: github.event_name != 'pull_request'
# if: github.event_name != 'pull_request'
if: ${{ !always() }}
env:
OUTPUT_FOLDER: ./build
RELEASE_FOLDER: ./release
@@ -297,6 +299,7 @@ jobs:
build-linux:
runs-on: ubuntu-latest
needs: [get-info, build-core, auto-test]
if: ${{ !always() }}
env:
OUTPUT_FOLDER: ./build
RELEASE_FOLDER: ./release
@@ -380,7 +383,8 @@ jobs:
build-macos-appstore:
runs-on: macos-latest
needs: [get-info, build-core, auto-test]
if: github.event_name != 'pull_request'
# if: github.event_name != 'pull_request'
if: ${{ !always() }}
env:
OUTPUT_FOLDER: ./build
RELEASE_FOLDER: ./release
@@ -464,7 +468,8 @@ jobs:
build-macos-portable:
runs-on: macos-latest
needs: [get-info, build-core, auto-test]
if: github.event_name != 'pull_request'
# if: github.event_name != 'pull_request'
if: ${{ !always() }}
env:
OUTPUT_FOLDER: ./build
RELEASE_FOLDER: ./release
@@ -560,6 +565,7 @@ jobs:
build-windows:
runs-on: windows-latest
needs: [get-info, build-core, auto-test]
if: ${{ !always() }}
env:
OUTPUT_FOLDER: ./build
RELEASE_FOLDER: ./release

View File

@@ -2,6 +2,15 @@ SYSTEM=love._os if SYSTEM=='OS X' then SYSTEM='macOS' end
MOBILE=SYSTEM=='Android' or SYSTEM=='iOS'
FNNS=SYSTEM:find'\79\83'-- What does FNSF stand for? IDK so don't ask me lol
if SYSTEM=='Web' then
local oldRead=love.filesystem.read
function love.filesystem.read(name,size)
if love.filesystem.getInfo(name) then
return oldRead(name,size)
end
end
end
function love.conf(t)
local identity='Techmino'
local msaa=0

View File

@@ -10,8 +10,7 @@
Instructions:
1. I made a framework called Zframework, *most* code in Zframework are not directly relevant to game;
2. "xxx" are texts for reading by player, 'xxx' are string values just used in program;
3. Some goto statement are used for better performance. All goto-labes have detailed names so don't be afraid;
4. Except "gcinfo" function of lua itself, other "gc" are short for "graphics";
3. Except "gcinfo" function of lua itself, other "gc" are short for "graphics";
]]--
@@ -560,16 +559,15 @@ applySettings()
-- Load replays
for _,fileName in next,fs.getDirectoryItems('replay') do
if fileName:sub(12,12):match("[a-zA-Z]") then
if fileName:sub(12,12):match("[a-zA-Z]") then repeat
local date,mode,version,player,seed,setting,mod
local fileData=fs.read('replay/'..fileName)
local success,fileData=true,fs.read('replay/'..fileName)
date, fileData=STRING.readLine(fileData)date=date:gsub("[a-zA-Z]","")
mode, fileData=STRING.readLine(fileData)mode=MODE_UPDATE_MAP[mode] or mode
version,fileData=STRING.readLine(fileData)
player, fileData=STRING.readLine(fileData) if player=="Local Player" then player="Stacker" end
local success
success,fileData=pcall(love.data.decompress,'string','zlib',fileData)
if not success then goto BREAK_cannotParse end
if not success then break end
seed, fileData=STRING.readLine(fileData)
setting,fileData=STRING.readLine(fileData)setting=JSON.decode(setting)
mod, fileData=STRING.readLine(fileData)mod=JSON.decode(mod)
@@ -578,7 +576,7 @@ for _,fileName in next,fs.getDirectoryItems('replay') do
not mod or
not mode or
#mode==0
then goto BREAK_cannotParse end
then break end
fs.remove('replay/'..fileName)
local newName=fileName:sub(1,10)..fileName:sub(15)
@@ -597,8 +595,7 @@ for _,fileName in next,fs.getDirectoryItems('replay') do
)
)
fileName=newName
end
::BREAK_cannotParse::
until true end
local rep=DATA.parseReplay('replay/'..fileName)
table.insert(REPLAY,rep)
end

View File

@@ -65,14 +65,19 @@ local function _getScore(field,cb,cy)
local hole=0
for i=cy+#cb-1,cy,-1 do
local full=true
for j=1,10 do
if field[i][j]==0 then
goto CONTINUE_notFull
-- goto CONTINUE_notFull
full=false
break
end
end
discardRow(rem(field,i))
clear=clear+1
::CONTINUE_notFull::
if full then
-- ::CONTINUE_notFull::
discardRow(rem(field,i))
clear=clear+1
end
end
if #field==0 then-- PC
return 1e99

View File

@@ -376,22 +376,26 @@ function DATA.parseReplay(fileName,ifFull)
return DATA.parseReplayData(fileName,fileData,ifFull)
end
function DATA.parseReplayData(fileName,fileData,ifFull)
local success,metaData,rep
local success,metaData
local rep={-- unavailable replay object
fileName=fileName,
available=false,
}
if not (fileData and #fileData>0) then goto BREAK_cannotParse end
if not (fileData and #fileData>0) then return rep end-- goto BREAK_cannotParse
-- Decompress file
success,fileData=pcall(love.data.decompress,'string','zlib',fileData)
if not success then goto BREAK_cannotParse end
if not success then return rep end-- goto BREAK_cannotParse
-- Load metadata
metaData,fileData=STRING.readLine(fileData)
metaData=JSON.decode(metaData)
if not metaData then goto BREAK_cannotParse end
if not metaData then return rep end-- goto BREAK_cannotParse
-- Convert ancient replays
metaData.mode=MODE_UPDATE_MAP[metaData.mode] or metaData.mode
if not MODES[metaData.mode] then goto BREAK_cannotParse end
if not MODES[metaData.mode] then return rep end-- goto BREAK_cannotParse
-- Create replay object
rep={
@@ -409,13 +413,6 @@ function DATA.parseReplayData(fileName,fileData,ifFull)
tasUsed=metaData.tasUsed,
}
if ifFull then rep.data=fileData end
do return rep end
-- Create unavailable replay object
::BREAK_cannotParse::
return {
fileName=fileName,
available=false,
}
return rep
end
return DATA

View File

@@ -76,15 +76,17 @@ return {
D.rankPts=1
for i=1,#P.field do
local h=getOpenHole(i)
local flag
for j=1,10 do
if P.field[i][j]>0 and h==j then goto post_pts_calc end
if P.field[i][j]==0 and h~=j then goto post_pts_calc end
if P.field[i][j]>0 and h==j then flag=true break end-- goto post_pts_calc
if P.field[i][j]==0 and h~=j then flag=true break end-- goto post_pts_calc
end
if i==#P.field then goto post_pts_calc end
if P.field[i+1][h]==0 then goto post_pts_calc end
if flag then break end
if i==#P.field then break end-- goto post_pts_calc
if P.field[i+1][h]==0 then break end-- goto post_pts_calc
D.rankPts=D.rankPts+1
end
::post_pts_calc::
-- ::post_pts_calc::
generateGuide(D.rankPts+20)
end
}

View File

@@ -981,7 +981,7 @@ do-- function resetGameData(args)
GAME.replaying=true
else
GAME.frameStart=args:find'n' and 0 or 180-SETTING.reTime*60
GAME.seed=seed or math.random(1046101471,2662622626)
GAME.seed=seed or math.random(1046101471)
GAME.pauseTime=0
GAME.pauseCount=0
GAME.saved=false

View File

@@ -652,7 +652,7 @@ do-- Userdata tables
showSpike=true,
highCam=true,
nextPos=true,
fullscreen=true,
fullscreen=SYSTEM~='Web',
portrait=false,
msaa=0,
bg='on',

View File

@@ -77,16 +77,19 @@ function Player:createLockFX()
for i=1,#CB do
local y=self.curY+i-1
local L=self.clearedRow
local skip
for j=1,#L do
if L[j]==y then goto CONTINUE_skip end
if L[j]==y then skip=true break end-- goto CONTINUE_skip
end
y=-30*y
for j=1,#CB[1] do
if CB[i][j] then
ins(self.lockFX,{30*(self.curX+j-2),y,0,t})
if not skip then
y=-30*y
for j=1,#CB[1] do
if CB[i][j] then
ins(self.lockFX,{30*(self.curX+j-2),y,0,t})
end
end
end
::CONTINUE_skip::
-- ::CONTINUE_skip::
end
end
end
@@ -610,6 +613,7 @@ do-- function Player:dropPosition(x,y,size)
vy=vy+.0626
self:setPosition(x,y,size)
if y>2600 then
table.remove(PLAYERS,TABLE.find(PLAYERS,self))
return true
end
end
@@ -1041,16 +1045,20 @@ function Player:_checkClear(field,start,height,CB,CX)
h=h+1
-- Row filled
local full=true
for x=1,10 do
if field[h][x]<=0 then
goto CONTINUE_notFull
full=false
break-- goto CONTINUE_notFull
end
end
cc=cc+1
if field[h].garbage then gbcc=gbcc+1 end
ins(self.clearingRow,h-cc+1)
ins(self.clearedRow,h)
::CONTINUE_notFull::
if full then
cc=cc+1
if field[h].garbage then gbcc=gbcc+1 end
ins(self.clearingRow,h-cc+1)
ins(self.clearedRow,h)
end
-- ::CONTINUE_notFull::
end
return cc,gbcc
end
@@ -1265,20 +1273,22 @@ function Player:hold_norm(ifpre)
y=y+(#C.bk-#H.bk)*.5
local iki=phyHoldKickX[x==int(x)]
local success
for Y=int(y),ceil(y+.5) do
for i=1,#iki do
local X=x+iki[i]
if not self:ifoverlap(H.bk,X,Y) then
x,y=X,Y
goto BREAK_success
success=true
break
end
end
if success then break end
end
-- <for-else> All test failed, interrupt with sound
if not success then -- All test failed, interrupt with sound
SFX.play('drop_cancel')
do return end
-- <for-end>
::BREAK_success::
return
end
self.spinLast=false
@@ -1332,20 +1342,22 @@ function Player:hold_swap(ifpre)
y=y+(#C.bk-#H.bk)*.5
local iki=phyHoldKickX[x==int(x)]
for Y=int(y),ceil(y+.5) do
for i=1,#iki do
local X=x+iki[i]
if not self:ifoverlap(H.bk,X,Y) then
x,y=X,Y
goto BREAK_success
local success
for Y=int(y),ceil(y+.5) do
for i=1,#iki do
local X=x+iki[i]
if not self:ifoverlap(H.bk,X,Y) then
x,y=X,Y
success=true
break
end
end
if success then break end
end
end
-- <for-else> All test failed, interrupt with sound
if not success then -- All test failed, interrupt with sound
SFX.play('finesseError')
do return end
-- <for-end>
::BREAK_success::
return
end
self.spinLast=false
@@ -2508,90 +2520,92 @@ local function update_alive(P,dt)
local stopAtFalling
-- Falling animation
if P.falling>0 then
stopAtFalling=true
P:_updateFalling(P.falling-1)
repeat
if P.falling>0 then
goto THROW_stop
end
end
-- Update block state
if P.control then
-- Try spawn new block
if not P.cur then
if not stopAtFalling and P.waiting>0 then
P.waiting=P.waiting-1
stopAtFalling=true
P:_updateFalling(P.falling-1)
if P.falling>0 then
break-- goto THROW_stop
end
if P.waiting<=0 then
P:popNext()
end
goto THROW_stop
end
-- Natural block falling
if P.cur then
if P.curY>P.ghoY then
local D=P.dropDelay
local dist-- Drop distance
if D>1 then
D=D-1
if P.keyPressing[7] and P.downing>=ENV.sddas then
D=D-ceil(ENV.drop/ENV.sdarr)
end
if D<=0 then
dist=1
P.dropDelay=(D-1)%ENV.drop+1
else
P.dropDelay=D
goto THROW_stop
end
elseif D==1 then-- We don't know why dropDelay is 1, so checking ENV.drop>1 is neccessary
if ENV.drop>1 and P.downing>=ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then
dist=2
else
dist=1
end
-- Reset drop delay
P.dropDelay=ENV.drop
else-- High gravity case (>1G)
-- Add extra 1 if time to auto softdrop
if P.downing>ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then
dist=1/D+1
else
dist=1/D
end
-- Update block state
if P.control then
-- Try spawn new block
if not P.cur then
if not stopAtFalling and P.waiting>0 then
P.waiting=P.waiting-1
end
if P.waiting<=0 then
P:popNext()
end
break-- goto THROW_stop
end
-- Limit dropping to ghost at max
dist=min(dist,P.curY-P.ghoY)
-- Drop and create FXs
if ENV.moveFX and ENV.block and dist>1 then
for _=1,dist do
P:createMoveFX('down')
P.curY=P.curY-1
-- Natural block falling
if P.cur then
if P.curY>P.ghoY then
local D=P.dropDelay
local dist-- Drop distance
if D>1 then
D=D-1
if P.keyPressing[7] and P.downing>=ENV.sddas then
D=D-ceil(ENV.drop/ENV.sdarr)
end
if D<=0 then
dist=1
P.dropDelay=(D-1)%ENV.drop+1
else
P.dropDelay=D
break-- goto THROW_stop
end
elseif D==1 then-- We don't know why dropDelay is 1, so checking ENV.drop>1 is neccessary
if ENV.drop>1 and P.downing>=ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then
dist=2
else
dist=1
end
-- Reset drop delay
P.dropDelay=ENV.drop
else-- High gravity case (>1G)
-- Add extra 1 if time to auto softdrop
if P.downing>ENV.sddas and (P.downing-ENV.sddas)%ENV.sdarr==0 then
dist=1/D+1
else
dist=1/D
end
end
-- Limit dropping to ghost at max
dist=min(dist,P.curY-P.ghoY)
-- Drop and create FXs
if ENV.moveFX and ENV.block and dist>1 then
for _=1,dist do
P:createMoveFX('down')
P.curY=P.curY-1
end
else
P.curY=P.curY-dist
end
P.spinLast=false
P:freshBlock('fresh')
P:checkTouchSound()
else
P.curY=P.curY-dist
end
P.spinLast=false
P:freshBlock('fresh')
P:checkTouchSound()
else
P.lockDelay=P.lockDelay-1
if P.lockDelay>=0 then
goto THROW_stop
end
P:drop(true)
if P.bot then
P.bot:lockWrongPlace()
P.lockDelay=P.lockDelay-1
if P.lockDelay>=0 then
break-- goto THROW_stop
end
P:drop(true)
if P.bot then
P.bot:lockWrongPlace()
end
end
end
end
end
::THROW_stop::
until true
-- ::THROW_stop::
-- B2B bar animation
if P.b2b1~=P.b2b then
@@ -2911,16 +2925,20 @@ function Player:lose(force)
self:dropPosition()
freshPlayerPosition('update')
local finished=true
for i=1,#PLY_ALIVE-1 do
if PLY_ALIVE[i].group==0 or PLY_ALIVE[i].group~=PLY_ALIVE[i+1].group then
goto BREAK_notFinished
finished=false
break-- goto BREAK_notFinished
end
end
-- Only 1 people or only 1 team survived, they win
for i=1,#PLY_ALIVE do
PLY_ALIVE[i]:win()
if finished then
for i=1,#PLY_ALIVE do
PLY_ALIVE[i]:win()
end
end
::BREAK_notFinished::
-- ::BREAK_notFinished::
end
end
--------------------------<\Event>--------------------------

View File

@@ -65,13 +65,15 @@ local seqGenerators={
local r
for _=1,hisLen do-- Reroll up to [hisLen] times
r=rndGen:random(len)
local rollAgain
for i=1,hisLen do
if r==history[i] then
goto CONTINUE_rollAgain
rollAgain=true
break-- goto CONTINUE_rollAgain
end
end
do break end
::CONTINUE_rollAgain::
if not rollAgain then break end
-- ::CONTINUE_rollAgain::
end
if history[1]~=0 then
P:getNext(seq0[r])
@@ -128,16 +130,21 @@ local seqGenerators={
-- print"======================"
-- Pick a mino from pool
local tryTime=0
::REPEAT_pickAgain::
local r=_poolPick()-- Random mino-index in pool
for i=1,len do
if r==history[i] then
tryTime=tryTime+1
if tryTime<hisLen then
goto REPEAT_pickAgain
local r
repeat-- ::REPEAT_pickAgain::
local pickAgain
r=_poolPick()-- Random mino-index in pool
for i=1,len do
if r==history[i] then
tryTime=tryTime+1
if tryTime<hisLen then
pickAgain=true
break-- goto REPEAT_pickAgain
end
end
end
end
if not pickAgain then break end
until true
-- Give mino to player & update history
if history[1]~=0 then

View File

@@ -38,13 +38,15 @@ local function restart()
end
local function checkBoard(b,p)
for i=1,8 do
local testNextLine
for j=1,3 do
if b[lines[i][j]]~=p then
goto CONTINUE_testNextLine
testNextLine=true
break-- goto CONTINUE_testNextLine
end
end
do return true end
::CONTINUE_testNextLine::
if not testNextLine then return true end
-- ::CONTINUE_testNextLine::
end
end
local function full(L)

View File

@@ -127,8 +127,9 @@ local function checkLink(x1,y1,x2,y2)
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
for x=x1+1,x2-1 do if field[y][x] then goto CONTINUE_nextRow end end
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
@@ -138,7 +139,7 @@ local function checkLink(x1,y1,x2,y2)
addPoint(bestLine,x2,y2)
end
end
::CONTINUE_nextRow::
-- ::CONTINUE_nextRow::
end
end
-- X-Y-X Check
@@ -150,8 +151,9 @@ local function checkLink(x1,y1,x2,y2)
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
for y=y1+1,y2-1 do if field[y][x] then goto CONTINUE_nextCol end end
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
@@ -161,7 +163,7 @@ local function checkLink(x1,y1,x2,y2)
addPoint(bestLine,x2,y2)
end
end
::CONTINUE_nextCol::
-- ::CONTINUE_nextCol::
end
end
return bestLine

View File

@@ -153,32 +153,34 @@ function player:click(y,x)
SFX.play('touch')
local merged
::REPEAT_merge::
local cur=self.board[y][x]
local b1=TABLE.shift(self.board)
self.mergedTiles={}
local count=self:merge(b1,cur,y,x)
if count>2 then
merged=true
self.board=b1
b1[y][x]=cur+1
repeat-- ::REPEAT_merge::
local repeating
local cur=self.board[y][x]
local b1=TABLE.shift(self.board)
self.mergedTiles={}
local count=self:merge(b1,cur,y,x)
if count>2 then
merged=true
self.board=b1
b1[y][x]=cur+1
if cur+1>self.maxTile then
self.maxTile=cur+1
if self.maxTile>=6 then
ins(self.progress,("%s - %.3fs"):format(self.maxTile,TIME()-player.startTime))
if cur+1>self.maxTile then
self.maxTile=cur+1
if self.maxTile>=6 then
ins(self.progress,("%s - %.3fs"):format(self.maxTile,TIME()-player.startTime))
end
SFX.play('reach')
end
SFX.play('reach')
end
local getScore=4^cur*count
self.score=self.score+getScore
TEXT.show(getScore,player.x+self.selectX*100-50,player.y+self.selectY*100-50,40,'score',1.626/math.log(getScore,3))
for i=1,#self.mergedTiles do
newMergeFX(self.mergedTiles[i][1],self.mergedTiles[i][2],cur+1)
local getScore=4^cur*count
self.score=self.score+getScore
TEXT.show(getScore,player.x+self.selectX*100-50,player.y+self.selectY*100-50,40,'score',1.626/math.log(getScore,3))
for i=1,#self.mergedTiles do
newMergeFX(self.mergedTiles[i][1],self.mergedTiles[i][2],cur+1)
end
repeating=true-- goto REPEAT_merge
end
goto REPEAT_merge
end
until not repeating
ins(self.nexts,self:newTile())

View File

@@ -104,20 +104,23 @@ function scene.keyDown(key,isRep)
elseif key=='v' and kb.isDown('lctrl','rctrl') or key=='cV' then
local str=sys.getClipboardText()
local args=str:sub((str:find(":") or 0)+1):split("!")
if #args<4 then goto THROW_fail end
if not (
DATA.pasteQuestArgs(args[1]) and
DATA.pasteSequence(args[2]) and
DATA.pasteMission(args[3])
) then goto THROW_fail end
TABLE.cut(FIELD)
FIELD[1]=DATA.newBoard()
for i=4,#args do
if args[i]:find("%S") and not DATA.pasteBoard(args[i],i-3) and i<#args then goto THROW_fail end
end
MES.new('check',text.importSuccess)
do return end
::THROW_fail::MES.new('error',text.dataCorrupted)
repeat
if #args<4 then break end-- goto THROW_fail
if not (
DATA.pasteQuestArgs(args[1]) and
DATA.pasteSequence(args[2]) and
DATA.pasteMission(args[3])
) then break end-- goto THROW_fail
TABLE.cut(FIELD)
FIELD[1]=DATA.newBoard()
for i=4,#args do
if args[i]:find("%S") and not DATA.pasteBoard(args[i],i-3) and i<#args then break end-- goto THROW_fail
end
MES.new('check',text.importSuccess)
return
until true
-- ::THROW_fail::
MES.new('error',text.dataCorrupted)
else
return true
end

View File

@@ -195,14 +195,17 @@ function scene.keyDown(key)
local F=FIELD[page]
local cleared=false
for i=#F,1,-1 do
local full
for j=1,10 do
if F[i][j]<=0 then goto CONTINUE_notFull end
if F[i][j]<=0 then full=false break end-- goto CONTINUE_notFull
end
cleared=true
SYSFX.newShade(3,200,660-30*i,300,30)
SYSFX.newRectRipple(3,200,660-30*i,300,30)
rem(F,i)
::CONTINUE_notFull::
if full then
cleared=true
SYSFX.newShade(3,200,660-30*i,300,30)
SYSFX.newRectRipple(3,200,660-30*i,300,30)
rem(F,i)
end
-- ::CONTINUE_notFull::
end
if cleared then
SFX.play('clear_3',.8)

View File

@@ -195,15 +195,19 @@ function scene.touchMove()
for n=1,#keys do
local B=keys[n]
if B.ava then
local nextKey
for i=1,#L,2 do
if (L[i]-B.x)^2+(L[i+1]-B.y)^2<=B.r^2 then
goto CONTINUE_nextKey
nextKey=true
break-- goto CONTINUE_nextKey
end
end
PLAYERS[1]:releaseKey(n)
VK.release(n)
if not nextKey then
PLAYERS[1]:releaseKey(n)
VK.release(n)
end
-- ::CONTINUE_nextKey::
end
::CONTINUE_nextKey::
end
end
function scene.keyDown(key,isRep)

View File

@@ -109,15 +109,19 @@ function scene.touchMove()
for n=1,#keys do
local B=keys[n]
if B.ava then
local nextKey
for i=1,#L,2 do
if (L[i]-B.x)^2+(L[i+1]-B.y)^2<=B.r^2 then
goto CONTINUE_nextKey
nextKey=true
break-- goto CONTINUE_nextKey
end
end
PLAYERS[1]:releaseKey(n)
VK.release(n)
if not nextKey then
PLAYERS[1]:releaseKey(n)
VK.release(n)
end
-- ::CONTINUE_nextKey::
end
::CONTINUE_nextKey::
end
end
function scene.keyDown(key,isRep)