diff --git a/conf.lua b/conf.lua index 3b8953b..a28298f 100644 --- a/conf.lua +++ b/conf.lua @@ -1,6 +1,4 @@ function love.conf(t) ---[[ t.window.width=420 - t.window.height=700]] - t.window.fullscreen=true - t.window.resizable=true + t.identity="net.kimapr.m3" + t.window=nil end diff --git a/main.lua b/main.lua index c70a912..758196c 100644 --- a/main.lua +++ b/main.lua @@ -1,18 +1,52 @@ -local board={} -local bw,bh=10,10 +local ostype=love.system.getOS() +local mobile=ostype=="Android" or ostype=="iOS" +love.window.setMode(mobile and 400 or 800,mobile and 720 or 600,{resizable=true,fullscreen=mobile}) +love.window.setTitle("m3") +love.window.maximize() +local fs=love.filesystem +fs.setIdentity(fs.getIdentity(),true) local g=love.graphics -local font=g.newFont(9,"mono") +local bw,bh=8,8 +local board={} +local cc=3 +local tt=0 +local bat=1 +local batd=bat +local score=0 +local taint=0 +local taintd=taint +local tochar=0 +local scored=score +local falltest=false +local playdbg=false +local dbg=false +local grabbed=nil +local dying=false +local running=false +local animc=0 +local entc=0 +local boomc=0 +local sfxc=0 +local slow=1 +local accumdt=0 +local tps=120 +local rtps=0 +local tpsl={} +local speeds={1,0.05,10} +local plchint=1 +local toplchi=plchint +local yes=true +local font=g.newFont(9,"mono",1) font:setFilter("nearest") g.setFont(font) -local ltest=os.time() local ltest=0 -local hookdbg=false +local hookdbg=true if hookdbg then debug.sethook(function() ltest=ltest+1 if ltest>=1000 then - print("!! we are in a freezer !!",debug.traceback()) ltest=0 + print("!! we are in a freezer !!",debug.traceback()) end end,"",1000) end @@ -59,10 +93,15 @@ local colors={ end, function(a,b) g.setColor(0,0,1,b*a) + g.polygon("fill", + 0.1,0.4, 0.5,0, 0.9,0.4, + 0.9,1-0.4, 0.5,1-0, 0.1,1-0.4 + ) + g.setColor(1,0,0,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) + g.line(0.4,0.5, 0.6,0.5) end, } local gobjs={} @@ -75,18 +114,59 @@ for k,v in ipairs(colors) do 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 taint=0 -local taintd=taint -local tochar=0 -local scored=score local function znoise(...) return love.math.noise(...)*2-1 end +local soundgen=require"soundgen" +local sfx={} +do + local rate=44100 + local len=1 + local bfreq=700 + local lacu,pers=1.5,0.5 + local octas=4 + local rta=0 + local oofs={} + math.randomseed(os.time()) + do + local pe=1 + for n=1,octas do + oofs[n]=math.random()*2-1 + rta=rta+(pe) + pe=pe*pers + end + end + local sd=love.sound.newSoundData(rate*len,rate,16,1) + local function gsample(t) + local arg=(bfreq*(t^((1+math.sin(t*3*math.pi)*0.1)*love.math.noise(t*0.1-0.05,5244,love.math.noise(t*1000,2949)*0.01-0.005)))) + local nn=0 + local pe=1/rta + local ao=1 + for n=1,octas do + local nnn=znoise(arg*ao,n/5+oofs[n])*pe + --nn=math.max(-1,math.min(1,nn+nnn)) + nn=nn+nnn + --print(n,nn,nnn,pe,ao) + pe=pe*pers + ao=ao*lacu + end + return (nn*(1-t)^5)*(math.min(1,math.max(t*100))^0.5) + end + for n=0,len*rate-1 do + local a=gsample(n/(rate)) + sd:setSample(n,a) + end + sfx.boom=love.audio.newSource(sd) +end +do + local sg=soundgen + local wave=sg:noise():phase(math.random()*1000):freq(1000):amp(0.25) + :pm(sg:triangle():freq(50):am(sg:silence(1):fade(0,0.04),-1,1),-0.01,0.01) + :add(sg:sine():amp(0.55):freq(50):pm(sg:noise():freq(500):phase(0.834715384),-0.01,0.01)) + wave=wave:fade(0,0.1) + wave=wave:fade(0.001,0) + sfx.fall=love.audio.newSource(wave:compile()) +end local function eudist(x,y) return (x*x+y*y)^0.5 end @@ -105,6 +185,7 @@ local function drawfig(n,a) 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) + g.setLineWidth(g.getLineWidth()*0.5) if n.super=="lines" then local hps,vps={},{} local det=10 @@ -254,33 +335,40 @@ local function check(x,y,rm,oam,obm) 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.setLineJoin("bevel") + g.setLineWidth(0.15) 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-taintd,1-taintd) - g.print(sctext) - g.pop() + do + g.push("all") + g.scale(bw/10,bh/10) + local bw,bh=10,10 + 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() + end + do + g.push("all") + g.scale(bw/10,bh/10) + local bw,bh=10,10 + 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-taintd,1-taintd) + g.print(sctext) + g.pop() + end if not falltest then g.setStencilTest("greater",0) end @@ -321,6 +409,7 @@ local function drawboard(x,y,w,h) end g.setStencilTest() g.setColor(0.2,0.2,0.2) + g.setLineWidth(g.getLineWidth()*0.5) g.rectangle("line",0,0,bw,bh) g.pop() end @@ -436,6 +525,7 @@ local function kill(x,y,rc) end cc=math.min(#colors,cc+((0.1/(bw*bh))/((cc/3)^2))) end +local sfxs={} killall=function(kills,b,...) if not kills then return end if b then @@ -504,6 +594,13 @@ killall=function(kills,b,...) local x,y=unpack(v) check(x,y,rm,oam,obm) end + if #tt>0 then + local boom=sfx.boom:clone() + boom:setVolume(math.min(0.8,0.5+(#tt/10)*0.5)) + boom:setPitch(1.2-math.min(1.2-0.5,#tt/30)) + boom:play() + sfxs[boom]=true + end if next(rm) then killall(rm) end @@ -558,7 +655,6 @@ local function fillboard() end dropall(-bh) end -local dbg=false local function resboard() cc=3 score=0 @@ -570,16 +666,11 @@ local function resboard() tochar=bw*bh+gemrng:random(bw) fillboard() end -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 @@ -669,16 +760,6 @@ 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) @@ -703,17 +784,24 @@ function love.mousemoved() end end +local ttt=0 + function love.update(dt) if hookdbg then ltest=0 end + ttt=ttt+dt dt=math.min(dt,0.2)*(speeds[slow]) accumdt=math.min(0.6,accumdt+dt) - local fcc=0 - while accumdt>1/tps do - fcc=fcc+1 - local dt=1/tps + local fdt=1/tps + while accumdt>=fdt do + local dt=fdt accumdt=accumdt-dt + tpsl[#tpsl+1]=ttt + while ttt-tpsl[1]>1 do + table.remove(tpsl,1) + end + rtps=#tpsl if running and playdbg then local gc=0 local tc=0 @@ -725,7 +813,7 @@ function love.update(dt) if gc/tc>0.4 then local goods={} for _=1,1000 do - local ix,iy=math.random(1,bw),math.random(1,bh) + local ix,iy=gemrng:random(1,bw),gemrng: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)]) @@ -782,8 +870,17 @@ function love.update(dt) anims[k]=nil end end + sfxc=0 + for k,_ in next,sfxs do + if k:isPlaying() then + sfxc=sfxc+1 + else + sfxs[k]=nil + end + end local tk={} if running then + local unfallc=0 for x,falling in ipairs(falling) do for fall,_ in next,falling do entc=entc+1 @@ -822,6 +919,7 @@ function love.update(dt) 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 + unfallc=unfallc+#fall.figs*(fall.vel/30) local off=math.min(iy,bh) for i,fig in ipairs(fall.figs) do while board[x][off] do @@ -839,6 +937,11 @@ function love.update(dt) end end end + if unfallc>0 then + local fall=sfx.fall:clone() + fall:setVolume(math.min(1,unfallc/10)) + fall:play() + end end if entc==0 and dying then dying=false @@ -868,7 +971,12 @@ function love.update(dt) toplchi=plchint end local oam,obm,rm={},{},{} + local loopc=0 for k,v in next,tk do + --[[loopc=loopc+1 + if loopc>1000 then + error("bai"..loopc) + end]] local x,y=unpack(v) check(x,y,rm,oam,obm) end @@ -884,14 +992,18 @@ function love.update(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)) + local cheat=dbg or taint==1 + if k=="escape" then + if not mobile then + love.event.quit() + end + end if dbg and tonumber(k) then print(gobjs[tonumber(k)],tonumber(k)) board[x][y]=gobjs[tonumber(k)] or board[x][y] @@ -910,11 +1022,11 @@ function love.keypressed(k) taint=1 end end - if k=="f2" and dbg then + if k=="f2" and cheat then yes=not yes elseif k=="f3" then dbg=not dbg - elseif k=="f4" and dbg then + elseif k=="f4" and cheat then local unfell={} local yls={} local cx=x @@ -945,19 +1057,19 @@ function love.keypressed(k) yls[x]=yl end taint=1 - elseif k=="f5" and dbg then + elseif k=="f5" and cheat then slow=slow%3+1 taint=1 - elseif k=="f7" and dbg then + elseif k=="f7" and cheat then falltest=not falltest taint=1 - elseif k=="f8" and dbg then + elseif k=="f8" and cheat then bat=bat*0.25 taint=1 - elseif k=="f9" and dbg then + elseif k=="f9" and cheat then bat=bat*2 taint=1 - elseif k=="f10" and dbg then + elseif k=="f10" and cheat then playdbg=not playdbg taint=1 elseif k=="f11" then @@ -966,17 +1078,6 @@ function love.keypressed(k) 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 @@ -985,8 +1086,21 @@ function love.draw() 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) + setbsize(x+ww*0.1,y+ww*0.1+ww/10*0.3,ww*0.8,ww*0.8) if yes then drawboard() end + if dbg then + g.push("all") + g.scale(2,2) + love.graphics.print(("FPS: %i; TPS: %i; dt: %.2f; speed: %f; anims: %i; fallings: %i; booms: %i; sounds counted: %i; sounds: %i"):format( + love.timer.getFPS(), + rtps, + math.floor(love.timer.getDelta()*100000)/100, + speeds[slow], + animc, entc, boomc, sfxc, + love.audio.getActiveSourceCount() + )) + g.pop() + end end diff --git a/soundgen.lua b/soundgen.lua new file mode 100644 index 0000000..e172d5d --- /dev/null +++ b/soundgen.lua @@ -0,0 +1,204 @@ +local smt={} +smt.__index=smt +smt.proto=smt + +local function round(x) + return math.floor(x+.5) +end + +function smt:bounds_and(a,b,c,...) + if not a or not b then return false end + local mi,ma + mi=math.max(a[1],b[1]) + ma=math.min(a[2],b[2]) + if ma-mi<=0 then return false end + if c then + return self:bounds_and({mi,ma},c,...) + end + return {mi,ma} +end + +function smt:bounds_or(a,b,c,...) + local mi,ma=math.min(a[1],b[1]),math.max(a[2],b[2]) + if not a then return b end + if not b then return a end + if c then + return self:bounds_or({mi,ma},c,...) + end + return {mi,ma} +end + +function smt:new(fn,bounds) + local obj={} + obj.bounds=bounds + if obj.bounds==nil then + obj.bounds={-math.huge,math.huge} + end + obj.sample=fn + return setmetatable(obj,self.proto) +end + +function smt:sine() + return self:new(function(self,t) + return math.sin(t*math.pi*2) + end) +end + +function smt:square() + return self:new(function(self,t) + return (t%1)<=0.5 and -1 or 1 + end) +end + +function smt:sawtooth() + return self:new(function(self,t) + return (t%1)*2-1 + end) +end + +function smt:triangle() + return self:new(function(self,t) + --return ((t%1)<=0.5 and (t*2)%1 or 1-(t*2)%1)*2-1 -- sounds cool and glitchy but wrong + local a=t%1 + if a>0.5 then + return (1-(a*2-1))*2-1 + end + return (a*2)*2-1 + end) +end + +function smt:noise(y,z,x) + return self:new(function(self,t) + return love.math.noise(t,y,z,x)*2-1 + end) +end + +function smt:silence(a) + a=a or 0 + return smt:new(function()return a end) +end + +local function rated_bounds(bounds,rate) + local mi,ma=bounds[1],bounds[2] + mi,ma=mi*rate,ma*rate + mi,ma=round(mi),round(ma)-1 + return mi,ma +end + +local function prefi(y) + return math.min(1,math.max(-1,y)) +end + +function smt:compile(rate,bits,alt) + rate=rate or 44100 + bits=bits or 16 + local bounds=self.bounds + local mi,ma=rated_bounds(bounds,rate) + if alt then + bounds=self:bounds_and(bounds,alt.bounds) + local mi,ma=rated_bounds(bounds,rate) + local sdata=love.sound.newSoundData(ma-mi+1,rate,bits,2) + for x=mi,ma do + local x=x+mi + sdata:setSample(x*2,prefi(self:sample(x/rate+0.5/rate))) + sdata:setSample(x*2+1,prefi(alt:sample(x/rate+0.5/rate))) + end + return sdata + end + local sdata=love.sound.newSoundData(ma-mi+1,rate,bits,1) + for x=mi,ma do + local xx=x-mi + sdata:setSample(xx,prefi(self:sample(x/rate+0.5/rate))) + end + return sdata +end + +function smt:phase(p) + local bb=self.bounds + if bb then + bb={bb[1]-p,bb[2]-p} + end + return self:new(function(obj,t) + return self:sample(t+p) + end,bb) +end + +function smt:amp(a) + return self:new(function(obj,t) + return self:sample(t)*a + end,self.bounds) +end + +function smt:freq(f) + local bb=self.bounds + if bb then + bb={bb[1]/f,bb[2]/f} + if bb[2]ma then return 0 end + return self.sample(self,t) + end,self:bounds_and(self.bounds,{mi,ma})) +end + +function smt.am(car,mod,mi,ma) + return smt:new(function(self,t) + return car:sample(t)*((mod:sample(t)*0.5+0.5)*(ma-mi)+mi) + end,car:bounds_and(car.bounds,mod.bounds)) +end + +function smt.pm(car,mod,mi,ma) + assert(car.bounds + and car.bounds[1]==-math.huge + and car.bounds[2]==math.huge, + "infinite carrier expected") + mod=mod:add(mod:silence()) + return smt:new(function(self,t) + return car:sample(t+((mod:sample(t)*0.5+0.5)*(ma-mi)+mi)) + end,car.bounds) +end + +function smt.add(a,b,...) + if not b then + if not a then return smt:silence() end + return a + end + local ls={a,b,...} + local bbs={} + for k,v in pairs(ls) do + bbs[k]=v.bounds + end + return smt:new(function(self,t) + local r=0 + for k,v in pairs(ls) do + r=r+v:sample(t) + end + return r + end,a:bounds_or(unpack(bbs))) +end + +do + local fwav=smt:sine():phase(0.75):freq(0.25) + fwav=fwav:clamp(0,1) + fwav=fwav:add(smt:silence(-1):clamp(-math.huge,0)) + function smt.fade(wave,ma,mi) + local ff=fwav:freq(1/(mi-ma)):phase(-ma) + return wave:am(ff,1,-1) + end +end + +function smt.fm(car,mod,rate,mi,ma) -- TODO + local cframe,cache=0,{} + return smt.new(function(self,t) + end,mod.bounds) +end + +return smt diff --git a/sounds/fall.lua b/sounds/fall.lua new file mode 100644 index 0000000..e69de29