math.randomseed(os.time()*626) gc,kb,ms,tm,fs=love.graphics,love.keyboard,love.mouse,love.timer,love.filesystem toN,toS=tonumber,tostring int,ceil,abs,rnd,max,min,sin,cos,atan,pi=math.floor,math.ceil,math.abs,math.random,math.max,math.min,math.sin,math.cos,math.atan,math.pi sub,gsub,find,format,byte,char=string.sub,string.gsub,string.find,string.format,string.byte,string.char ins,rem,sort=table.insert,table.remove,table.sort function null()end function sortByTime(a,b) return a.time>b.time end ww,wh=gc.getWidth(),gc.getHeight() Timer=tm.getTime mx,my,mouseShow=-10,-10,true pause=0--pause countdown focus=true scene="" gamemode="" bgmPlaying=nil curBG="none" languages={"eng"} prevMenu={ load=love.event.quit, ready="mode", play="mode", mode="main", help="main", stat="main", setting="main", setting2="setting", intro="quit", main="quit", } swap={ none={2,1,d=function()end}, flash={8,1,d=function()gc.clear(1,1,1)end}, deck={60,20,d=function() local t=sceneSwaping.time t=(t>40 and 60-t or t>20 and 20 or t)/255 gc.setColor(.6,.6,.6,t*13) gc.rectangle("fill",0,0,1000,t*15) gc.rectangle("fill",0,600-t*15,1000,t*15) gc.setColor(.5,0,0,t*13) gc.line(0,t*15,1000,t*15) gc.line(0,600-t*15,1000,600-t*15) end }, } kb.setKeyRepeat(false) kb.setTextInput(false) Texts={ eng={ load={"Loading textures","Loading BGM","Loading SFX","Finished",}, stat={"Games played:","Game time:","Total block used:","Total rows cleared:","Total lines sent:",}, }, } numFonts={} function numFont(s) if numFonts[s]then gc.setFont(numFonts[s]) else local t=gc.setNewFont("cb.ttf",s) numFonts[s]=t gc.setFont(t) end currentFont=-1 end Fonts={}for i=1,#languages do Fonts[languages[i]]={}end fontLib={ eng=function(s) if s~=currentFont then if Fonts[s]then gc.setFont(Fonts[s]) else local t=gc.setNewFont("cb.ttf",s) Fonts[s]=t gc.setFont(t) end currentFont=s end end, chi=function(s) if s~=currentFont then if Fonts[setting.lang][s]then gc.setFont(Fonts[setting.lang][s]) else local t=gc.newFont("hei.ttf",s-5,"normal") Fonts[setting.lang][s]=t gc.setFont(t) end currentFont=s end end, } require("button") sfx={ "button", "ready","start", "move","rotate","rotatekick","hold", "prerotate","prehold", "drop","fall", "reach", "ren_1","ren_2","ren_3","ren_4","ren_5","ren_6","ren_7","ren_8","ren_9","ren_10","ren_11", "clear_1","clear_2","clear_3","clear_4", "spin_0","spin_1","spin_2","spin_3", "perfectclear", } bgm={ "blank", "way", "race", "push", "reason", } img={ title={ eng=gc.newImage("/image/title/eng.png"), chi=gc.newImage("/image/title/chi.png"), } } FX={ flash=0,--Black screen(frame) shake=0,--Screen shake(frame) beam={},--Attack beam appear=function(t) setFont(t.font) gc.setColor(1,1,1,min((30-abs(t.t-30))*.05,1)*(#field>9 and .7 or 1)) mStr(t.text,150,250-t.font*.5+t.dy) end, stretch=function(t) gc.push("transform") setFont(t.font) gc.translate(150,250) gc.setColor(1,1,1,min((30-abs(t.t-30))*.1,1)*(#field>9 and .7 or 1)) if t.t<20 then gc.scale((20-t.t)*.015+1,1)end mStr(t.text,0,-t.font*.5+t.dy) gc.pop() end, drive=function(t) gc.push("transform") setFont(t.font) gc.translate(150,290) gc.setColor(1,1,1,min((30-abs(t.t-30))*.1,1)*(#field>9 and .7 or 1)) if t.t<20 then gc.shear((20-t.t)*.03,0)end mStr(t.text,0,-t.font*.5+t.dy) gc.pop() end, spin=function(t) gc.push("transform") setFont(t.font) gc.translate(150,250) gc.setColor(1,1,1,min((30-abs(t.t-30))*.1,1)*(#field>9 and .7 or 1)) if t.t<20 then gc.scale((20-t.t)*.01+1,(20-t.t)*.015+1) end mStr(t.text,0,-t.font*.5+t.dy) gc.pop() end, flicker=function(t) setFont(t.font) gc.setColor(1,1,1,min((30-abs(t.t-30))*.05,1)*(#field>9 and .7 or 1)*(rnd()+.5)) mStr(t.text,150,250-t.font*.5+t.dy) end, } function stencil_field() gc.rectangle("fill",0,0,300,600) end --System data list={ clearname={"Single","Double","Triple"}, reason={[0]="Escape","Block out","Lock out","Finished","Top out"}, method={"Bag7","His4","Rnd"}, } actName={"moveLeft","moveRight","rotRight","rotLeft","rotFlip","hardDrop","softDrop","hold","toLeft","toRight"} actName_={"move left","move right","rotate right","rotate left","rotate flip","hard drop","soft drop","hold","toLeft","toRight"} name={"Z","S","L","J","T","O","I"} blockPos={4,4,4,4,4,5,4} renATK={[0]=0,0,0,1,1,1,2,2,2,3,3,3} require"SRS" gameEnv0={ das=6,arr=1, ghost=true,center=true, drop=30,lock=45, wait=0,fall=20, next=6,hold=true, sequence=1,visible=1, _20G=false,target=9e99, color={1,5,2,8,10,3,7,13}, key={"left","right","x","z","c","up","down","space","LEFT","RIGHT"}, reach=function()end } randomMethod={ function() P.bn,P.cb=rem(nxt,1),rem(nb,1) if #nxt<6 then local bag={1,2,3,4,5,6,7} for i=1,7 do ins(nxt,rem(bag,rnd(8-i))) end end for i=6,#nxt do nb[i]=blocks[nxt[i]][0] end end, function() P.bn,P.cb=rem(nxt,1),rem(nb,1) for j=1,4 do local i,f=rnd(7) for k=1,4 do if i==his[k]then f=true end end if not f then break end end P.nxt[6],P.nb[6]=i,blocks[i][0] rem(his,1)ins(his,i) end, function() P.bn,P.cb=rem(nxt,1),rem(nb,1) repeat i=rnd(7)until i~=nxt[5] P.nxt[6],P.nb[6]=i,blocks[i][0] end, } loadmode={ marathon=function() modeEnv={ drop=60, wait=1, fall=20, target=10, reach=Event.marathon_reach, } createPlayer(1,190,20,.8) curBG="game1" BGM("way") end, sprint=function() modeEnv={ wait=1, fall=1, target=40, reach=Event.gameover.win, } createPlayer(1,190,20,.8) curBG="game1" BGM("race") end, zen=function() modeEnv={ drop=1e99, lock=1e99, wait=1, fall=1, target=200, reach=Event.gameover.win, } createPlayer(1,190,20,.8) curBG="game1" BGM("reason") end, gm=function() modeEnv={ drop=60, wait=10, fall=5, target=100, reach=Event.gm_reach, } createPlayer(1,190,20,.8) curBG="game2" BGM("push") end, battle=function() modeEnv={ wait=1, fall=1, } createPlayer(1,240,30,.8)--Player -- createPlayer(2,580,25,.38,true) -- createPlayer(3,580,315,.38,true) --Triple -- createPlayer(2,580,140,.6,true) --Solo local n=2 for i=1,3 do for j=1,7 do createPlayer(n,75*i-65,80*j-55,.1,true) n=n+1 end end for i=11,13 do for j=1,7 do createPlayer(n,75*i-65,80*j-55,.1,true) n=n+1 end end curBG="game2" BGM("race") end, } Event={ task={ win=function() gameover=gameover+1 if gameover%3==0 then local j=gameover/3 if j<=#field then for i=1,10 do if field[j][i]>0 then field[j][i]=13 end end if j==#field then gameover=50 end end end if gameover>80 then return true end end, lose=function() gameover=gameover+1 if gameover%3==0 then local j=gameover/3 if j<=#field then for i=1,10 do if field[j][i]>0 then field[j][i]=13 end end end end if gameover>80 then return true end end, }, gameover={ win=function() P.control=false P.waiting=1e99 gameover=0 for i=1,#visTime do for j=1,10 do P.visTime[i][j]=1e99 end end--Make all visible P.control=false ins(task,Event.task.win) end, lose=function() P.control=false P.waiting=1e99 gameover=0 for i=1,#visTime do for j=1,10 do P.visTime[i][j]=1e99 end end--Make all visible P.control=false for i=1,#players.alive do if players.alive[i]==P.id then rem(players.alive,i) break end end for i=1,#P.atkBuffer do P.atkBuffer[i].sent=true P.atkBuffer[i].time=0 end ins(task,Event.task.lose) end, }, marathon_reach=function() local s=int(P.cstat.row*.1) if s>=20 then Event.gameover.win() else gameEnv.drop=Data.marathon_drop[s] gameEnv.target=s*10+10 end end, gm_reach=function() end } Data={ marathon_drop={[0]=60,50,40,30,25,20,18,16,14,12,10,8,7,6,5,4,3,2,1,1}, shirase_drop={[0]=0}, shirase_lock={[0]=0}, shirase_are={[0]=0}, shirase_lare={[0]=0}, } mesDisp={ marathon=function() gc.setColor(1,1,1) setFont(40) gc.print(format("%0.2f",time),-130,530) mStr(P.cstat.row.."/"..gameEnv.target,-80,250) end, sprint=function() gc.setColor(1,1,1) setFont(40) gc.print(format("%0.2f",time),-130,530) setFont(75) mStr(max(40-P.cstat.row,0),-80,280) end, zen=function() gc.setColor(1,1,1) setFont(40) gc.print(format("%0.2f",time),-130,530) setFont(75) mStr(max(200-P.cstat.row,0),-80,280) end, gm=function()end, battle=function()end, } --Game system Data setting={ sfx=true,bgm=true, fullscreen=false, lang="eng", das=5,arr=0, ghost=true,center=true, key={"left","right","x","z","c","up","down","space","LEFT","RIGHT"}, color={1,5,2,8,10,3,7,13}, } stat={ run=0, game=0, gametime=0, piece=0, row=0, atk=0, key=0, hold=0, rotate=0, spin=0, } --Userdata tables function string.splitS(s,sep) sep=sep or"/" local t={} repeat local i=find(s,sep) ins(t,sub(s,1,i-1)) s=sub(s,i+#sep) until #s==0 return t end function string.concat(t,sep) sep=sep or"/" local s="" for i=1,#t do s=s..t[i]..sep end return s end function sgn(i)return i>0 and 1 or i<0 and -1 or 0 end function stringPack(s,v)return s..toS(v).."\r\n"end function without(t,v) for i=1,#t do if t[i]==v then return nil end end return true end function nextLanguage() for i=1,#languages do if setting.lang==languages[i]then return languages[i+1]or"eng"end end end function mStr(s,x,y)gc.printf(s,x-500,y,1000,"center")end function mouseConvert(x,y) if wh/ww<=.6 then return 500+(x-ww*.5)*600/wh,y*600/wh else return x*1000/ww,300+(y-wh*.5)*1000/ww end end function drawButton() for i=1,#Buttons[scene]do local B=Buttons[scene][i] if not(B.hide and B.hide())then local t=B==Buttons.sel and .3 or 0 B.alpha=abs(B.alpha-t)>.02 and(B.alpha+(B.alphat then B.alpha=B.alpha-.02 elseif B.alpha#field+1 and #field+1 or P.cy while not ifoverlap(cb,cx,y_img-1)do P.y_img=P.y_img-1 end else while not ifoverlap(cb,cx,cy-1)do P.cy=P.cy-1 end P.y_img=P.cy end end function ifoverlap(bk,x,y) if x<1 or x+#bk[1]>11 or y<1 then return true end if y>#field then return nil end for i=1,#bk do for j=1,#bk[1]do if field[y+i-1]and bk[i][j]>0 and field[y+i-1][x+j-1]>0 then return true end end end end function resetblock() P.holded=false P.freshNext() P.sc={scs[bn][1],scs[bn][2]}P.dir=0 P.r,P.c=#cb,#cb[1] P.cx,P.cy=blockPos[bn],21+ceil(fieldBeneath/30) freshgho() P.dropDelay,P.lockDelay=gameEnv.drop,gameEnv.lock if keyPressing[8]then hold(true)end if keyPressing[3]then spin(1,true)end if keyPressing[4]then spin(-1,true)end if keyPressing[5]then spin(2,true)end if ifoverlap(cb,cx,cy)then Event.gameover.lose()end if keyPressing[6]then act.hardDrop()P.keyPressing[6]=false end end function pressKey(i,player) P=player or players[1] setmetatable(_G,P.index) if control then P.keyPressing[i]=true if waiting<=0 then act[actName[i]]() if i>2 and i<6 then keyPressing[i]=false end elseif i==1 then P.moving=-1 elseif i==2 then P.moving=1 end P.cstat.key=stat.key+1;ins(keyTime,1,frame)rem(keyTime,11) -- if playmode=="recording"then ins(rec,{i,frame})end stat.key=stat.key+1 end end function releaseKey(i,player) P=player or players[1] setmetatable(_G,P.index) P.keyPressing[i]=false -- if playmode=="recording"then ins(rec,{-i,frame})end end function spin(d,ifpre) if bn==6 then return nil end local icb=blocks[bn][(dir+d)%4] local isc=d==1 and{c-sc[2]+1,sc[1]}or d==-1 and{sc[2],r-sc[1]+1}or{r-sc[1]+1,c-sc[2]+1} local ir,ic=#icb,#icb[1] local ix,iy=cx+sc[2]-isc[2],cy+sc[1]-isc[1] local t=false local iki=SRS[bn][dir*10+(dir+d)%4] for i=1,#iki do if not ifoverlap(icb,ix+iki[i][1],iy+iki[i][2])then ix,iy=ix+iki[i][1],iy+iki[i][2] t=true break end end if t then P.cx,P.cy=ix,iy P.sc,P.cb=isc,icb P.r,P.c=ir,ic P.dir=(dir+d)%4 freshgho() P.lockDelay=gameEnv.lock SFX(ifpre and"prerotate"or ifoverlap(cb,cx,cy+1)and ifoverlap(cb,cx-1,cy)and ifoverlap(cb,cx+1,cy)and"rotatekick"or"rotate") stat.rotate=stat.rotate+1 end end function hold(ifpre) if not holded and waiting<=0 and gameEnv.hold then P.hn,P.bn=bn,hn P.hb,P.cb=blocks[hn][0],hb if bn==0 then freshNext()end P.sc={scs[bn][1],scs[bn][2]}P.dir=0 P.r,P.c=#cb,#cb[1] P.cx,P.cy=blockPos[bn],21 freshgho() P.dropDelay,P.lockDelay=gameEnv.drop,gameEnv.lock if ifoverlap(cb,cx,cy) then Event.gameover.lose()end P.holded=true SFX(ifpre and"prehold"or"hold") stat.hold=stat.hold+1 end end function drop() if cy==y_img then P.waiting=gameEnv.wait local dospin=ifoverlap(cb,cx,cy+1)and ifoverlap(cb,cx-1,cy)and ifoverlap(cb,cx+1,cy) ins(dropTime,1,frame)rem(dropTime,11) lock() local cc,csend=checkrow(cy,r),0--Currect clear&send P.combo=P.combo+1--combo=0 is under if cc==4 then if b2b then showText("Tetris B2B","drive",70) csend=5 else showText("Tetris","stretch",80) csend=4 P.b2b=true end elseif cc>0 then if dospin then if b2b then showText(name[bn].." spin "..list.clearname[cc].." B2B","spin",40) csend=2*cc+1 else showText(name[bn].." spin "..list.clearname[cc],"spin",50) csend=2*cc P.b2b=true end SFX("spin_"..cc) stat.spin=stat.spin+1 else P.b2b=false showText(list.clearname[cc],"appear",50) csend=cc-1 end else P.combo=0 if dospin then showText(name[bn].." spin","appear",50) SFX("spin_0") end end if cc>0 and #clearing==#field then showText("Perfect Clear","flicker",70,-60) csend=csend+5 SFX("perfectclear") end csend=csend+(renATK[combo]or 4) if cc>0 then SFX("clear_"..cc) SFX("ren_"..min(combo,11)) end if csend>0 then while csend>0 and P.atkBuffer[1]do csend=csend-1 P.atkBuffer[1].amount=P.atkBuffer[1].amount-1 if P.atkBuffer[1].amount==0 then rem(P.atkBuffer,1) end end if csend>0 and #players.alive>1 then garbageSend(P.id,csend,120)end elseif cc==0 then garbageRelease() end--Send attack stat.piece,stat.row,stat.atk=stat.piece+1,stat.row+cc,stat.atk+csend P.cstat.piece,P.cstat.row,P.cstat.atk=P.cstat.piece+1,P.cstat.row+cc,P.cstat.atk+csend if P.cstat.row>=gameEnv.target then gameEnv.reach() if control then SFX("reach")end end else if cy>y_img then P.cy=cy-1 end end end function lock() for i=1,r do local y=cy+i-1 if not P.field[y]then P.field[y],P.visTime[y]={0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0}end for j=1,c do if cb[i][j]~=0 then P.field[y][cx+j-1]=P.gameEnv.color[bn] P.visTime[y][cx+j-1]=P.showTime end end end end function ckfull(i) for j=1,10 do if field[i][j]==0 then return nil end end return true end function checkrow(s,num)--(cy,r) local c=0--rows cleared for i=s,s+num-1 do if ckfull(i)then ins(clearing,1,i) P.falling=gameEnv.fall c=c+1--row cleared+1 for k=1,1000 do PTC.dust[P.id]:setPosition(rnd(0,300),600-30*i+rnd(30)) PTC.dust[P.id]:emit(1) end end end return c end function garbageSend(sender,send,time) local pos,r=rnd(10) repeat r=players.alive[rnd(#players.alive)] until r~=P.id createBeam(sender,r) ins(players[r].atkBuffer,{pos,amount=send,countdown=time,cd0=time,time=0,sent=false}) sort(players[r].atkBuffer,sortByTime) end function garbageRelease() local t=P.showTime*2 for i=1,#P.atkBuffer do local atk=P.atkBuffer[i] if not atk.sent and atk.countdown==0 then for j=1,atk.amount do ins(P.field,1,{13,13,13,13,13,13,13,13,13,13}) ins(P.visTime,1,{t,t,t,t,t,t,t,t,t,t}) for k=1,#atk do P.field[1][atk[k]]=0 end end atk.sent=true atk.time=0 P.fieldBeneath=P.fieldBeneath+atk.amount*30 end end end function drawPixel(y,x,id,alpha) gc.setColor(1,1,1,alpha) gc.draw(blockSkin[id],30*x-30,600-30*y) end --------------------------------Warning!_G is __indexed to players[n]! require("user_actions")--Game control functions mouseDown={} keyDown={} function keyDown.play(key) local k=players[1].gameEnv.key for i=1,10 do if key==k[i]then pressKey(i,players[1]) break end end if key=="escape"then back()end end function keyDown.setting2(key) if key=="escape"then back() elseif keysetting then setting.key[keysetting]=key keysetting=nil end end keyUp={} function keyUp.play(key) local k=players[1].gameEnv.key for i=1,10 do if key==k[i]then releaseKey(i,players[1]) break end end end wheelmoved={} require("ai")--AI module require("timer")--Timer require("paint")--Paint require("game_scene")--Game scenes require("control")--User control function love.update(dt) if players then for i=1,#players do for k,v in pairs(players[i])do if rawget(_G,k)then print(i,k)end end end end if Buttons.pressing>0 then Buttons.pressing=Buttons.pressing+1 if Buttons.pressing>35 and Buttons.pressing%6==0 then love.mousepressed(ms.getX(),ms.getY(),1)end end if sceneSwaping then sceneSwaping.time=sceneSwaping.time-1 if sceneSwaping.time==sceneSwaping.mid then for i=1,#Buttons[scene]do Buttons[scene][i].alpha=0 end game[sceneSwaping.tar]() Buttons.sel=nil love.mousemoved(ms.getX(),ms.getY()) elseif sceneSwaping.time==0 then sceneSwaping=nil end elseif Tmr[scene]then Tmr[scene](dt) end end function love.draw() Pnt.BG[curBG]() if Pnt[scene]then Pnt[scene]()end setFont(35) drawButton() if mouseShow then gc.setColor(1,0,0,.6) gc.circle("fill",mx,my,4) end if sceneSwaping then sceneSwaping.draw()end gc.setColor(0,0,0) if wh/ww>=.6 then gc.rectangle("fill",0,0,1000,-(wh*1000/ww-600)*.5) gc.rectangle("fill",0,600,1000,(wh*1000/ww-600)*.5) else gc.rectangle("fill",0,0,-(ww*600/wh-1000)*.5,600) gc.rectangle("fill",1000,0,(ww*600/wh-1000)*.5,600) end--Draw black side --numFont(10)gc.setColor(1,1,1) --gc.print(tm.getFPS(),0,590) --if gcinfo()>500 then collectgarbage()end end function love.resize(x,y) ww,wh=x,y gc.origin() gc.translate(ww*.5,wh*.5) if wh/ww>=.6 then gc.scale(ww/1000) else gc.scale(wh/600) end gc.translate(-500,-300) end function love.focus(f) if f then focus=true ms.setVisible(false) if bgmPlaying then bgm[bgmPlaying]:play()end else if scene=="play"then pause=20 end focus=false ms.setVisible(true) if bgmPlaying then bgm[bgmPlaying]:pause()end end end function love.run() tm.step() love.resize(1000,600) game.load()--Launch return function() love.event.pump() for name,a,b,c,d,e,f in love.event.poll()do if name=="quit"then savedata() return 0 end love.handlers[name](a,b,c,d,e,f) end if focus or pause==20 then tm.step() love.update(tm.getDelta()) if gc.isActive()then gc.clear(1,1,1) love.draw()--Draw all things gc.present() end end end end --System callbacks do--Texture/Image local p=gc.newImage("/image/block.png") local l={} gc.setColor(1,1,1) for i=1,13 do l[i]=gc.newCanvas(30,30) gc.setCanvas(l[i]) gc.draw(p,30-30*i) end blockSkin=l l={} for i=1,1 do local p=gc.newImage("/image/BG/"..i..".png") l[i]=gc.newCanvas(1200,1200) gc.setCanvas(l[i]) gc.draw(p,nil,nil,nil,10,10) p:release() end background=l gc.setCanvas() end do--Particle PTC={dust={}}--Particle systems c=gc.newCanvas(6,6)gc.setCanvas(c) gc.clear(1,1,1) PTC.dust[0]=gc.newParticleSystem(c,10000) PTC.dust[0]:setParticleLifetime(.2,.3) PTC.dust[0]:setEmissionRate(0) PTC.dust[0]:setLinearAcceleration(-1500,-200,1500,200) PTC.dust[0]:setColors(1,1,1,.4,1,1,1,0) --Dust particles gc.setCanvas() end c=nil userdata=fs.newFile("userdata") if fs.getInfo("userdata")then loaddata() end stat.run=stat.run+1 setFont=fontLib[setting.lang] Text=Texts[setting.lang]