local love = love local step = 1.0 / 120 local sitelenpona local text local marble local wave local particles local audio local recorder local DetectCollision local OnImpact local OnVictory local BeatScore local Draw local Update local ExtrapolateBeatScore local _ExtrapolateBeatScore local state state = { Reset = function() state.beat = {} state.startTime = love.timer.getTime() state.currentBeat = 1 state.timeToSimulate = 0.0 state.longestStreak = 0 state.currentStreak = 0 end, finishTime = 0.0, timeToSimulate = 0.0, isGameStarted = false, lastBeatScore = 0.0, currentBeat = 1, startTime = 0.0, longestStreak = 0, currentStreak = 0, beat = { t = nil, mu = nil, }, } local function UpdateWindowTransform( w, h ) local d = math.min( w, h ) local r = love.graphics.get local tf = love.math.newTransform() local size = 3 tf:translate( w / 2, h / 2) tf:scale( d / size, -d / size ) transform = tf end OnImpact = function( impact ) if not impact then return end local score = BeatScore( impact.t ) --DEBUG state.lastBeatScore = score local pass = false if score > 1.0 then pass = true state.currentStreak = state.currentStreak + 1 if state.currentStreak > state.longestStreak then state.longestStreak = state.currentStreak end state.currentBeat = state.currentBeat + 1 if state.currentBeat >= 119 then return OnVictory() end else state.currentStreak = 0 end local x, y = math.cos(impact.th), math.sin(impact.th) particles:setPosition( impact.r * x, impact.r * y ) particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true ) particles:emit( 50 * score * score ) audio.OnImpact( impact, score, pass, state.currentBeat ) marble.OnImpact( impact, state.currentBeat ) wave.OnImpact( impact, state.currentBeat ) end local _OnImpact = OnImpact local function NewGame() love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink. OnImpact = _OnImpact ExtrapolateBeatScore = _ExtrapolateBeatScore love.draw = Draw love.update = Update particles:reset() particles:setSizes( 0.0007, 0.0001, 0.0003 ) state.Reset() marble.Reset() wave.Reset() text.Reset() audio.Reset() end function love.load() UpdateWindowTransform( love.graphics.getDimensions() ) do--particle system setup particles = love.graphics.newParticleSystem( love.graphics.newImage( "prideflag.png" ), 1024) --particles:setSizes( 0.0007, 0.0001, 0.0003 ) particles:setSizeVariation( 1 ) particles:setRadialAcceleration( 0, 0.5 ) particles:setSpeed( 0.2, 1 ) particles:setLinearDamping( 1, 10 ) particles:setRelativeRotation( true ) particles:setEmitterLifetime( -1 ) particles:setParticleLifetime( 0, 5 ) particles:setSpread( 0.05 ) particles:setColors( 1, 1, 1, 1, 91 / 255, 206 / 255, 250 / 255, 1, 245 / 255, 169 / 255, 184 / 255, 1, 1,1,1,0, 245 / 255, 169 / 255, 184 / 255, 1, 1,1,1,0, 245 / 255, 169 / 255, 184 / 255, 1, 1,1,1,0 ) end sitelenpona = assert( require "sitelenpona" ) text = assert( require "text" ) marble = assert( require "marble" ) wave = assert( require "wave" ) DetectCollision = assert ( require "collision" ) audio = assert( require "audio" ) recorder = assert( require "recorder" ) return NewGame() end ExtrapolateBeatScore = function( ) local t = love.timer.getTime() local beat = state.beat if not beat.t then return 2.0 end if not beat.mu then return 2.0 end if beat.mu < 0.001 then return 2.0 end local pow = 1 / math.max( beat.mu, 0.2 ) t = 1.05 * math.sin( 0.5 * math.pi * ( t - beat.t ) / beat.mu ) return math.pow( t * t, pow ) end _ExtrapolateBeatScore = ExtrapolateBeatScore BeatScore = function( t ) local beat = state.beat --Base case 1: first tap. if not beat.t then beat.t = t return 2.0 end local dt = t - beat.t beat.t = t --Base case 2: second tap. if not beat.mu then beat.mu = dt return 2.0 end local pow = 1 / math.max( beat.mu, 0.1 ) local score = 1.1 * math.sin( 0.5 * math.pi * dt / beat.mu ) score = math.pow( score * score, pow ) --General case: update average beat length. --Debounce: max BPM 200 if dt > 60.0 / 200.0 then local WEIGHT = 0.75 --High number makes the last beat more significant. local mu = ( 1.0 - WEIGHT ) * beat.mu + WEIGHT * dt beat.mu = mu else score = 0 end return score end OnVictory = function() particles:setParticleLifetime( 0, 30 ) particles:setSizes( 0.001, 0.0001, 0.0005 ) particles:setColors( 245 / 255, 169 / 255, 184 / 255, 1, 1, 1, 1, 1, 245 / 255, 169 / 255, 184 / 255, 1, 1, 1, 1, 0, 245 / 255, 169 / 255, 184 / 255, 1, 1, 1, 1, 0 ) particles:setPosition( 0, 0 ) particles:setEmissionArea( "normal", 0.5, 0.5, 0, true ) particles:emit( 500 ) particles:setEmissionArea( "normal", 0.01, 0.01, 0, true ) love.graphics.setCanvas( marble.Canvas() ) love.graphics.setCanvas() local totalTime = love.timer.getTime() - state.startTime OnImpact = function() end love.update = function( dt ) while dt > step do marble.Integrate( step ) wave.Integrate( step ) marble.Update() wave.Update() dt = dt - step end local marblePos = marble.Current() particles:emit( 1 ) particles:update( dt ) particles:moveTo( marblePos.x, marblePos.y ) end love.draw = function() love.graphics.push( "transform" ) love.graphics.applyTransform( transform ) love.graphics.setColor( 1, 1, 1, 1 ) love.graphics.draw( particles ) love.graphics.pop() text.Draw( 119 ) love.graphics.setColor( 1, 1, 1, 1 ) love.graphics.printf( string.format( "time:\t%.3f\nstreak:\t%d", totalTime, state.longestStreak ):gsub( "%.", "," ), 0, 0.5 * love.graphics.getHeight() - love.graphics.getFont():getHeight(), love.graphics.getWidth(), "center" ) marble.Draw() end marble.OnVictory() love.graphics.setBackgroundColor( 91 / 255, 206 / 255, 250 / 255 ) --Trans blue. end Draw = function() local score = ExtrapolateBeatScore() love.graphics.push( "transform" ) love.graphics.applyTransform( transform ) love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 ) love.graphics.draw(particles, 0, 0) wave.Draw( score ) love.graphics.pop() sitelenpona.Draw( text.words[state.currentBeat] ) marble.Draw() text.Draw( state.currentBeat ) end Update = function( dt ) audio.Update( ExtrapolateBeatScore(), state.currentBeat ) particles:update( dt ) dt = dt + state.timeToSimulate --Physics tick. while dt > step do recorder.Update( marble.GetAcceleration() ) --For savegames. marble.Integrate( step ) wave.Integrate( step ) OnImpact( DetectCollision( marble.Current(), marble.Next(), wave.Current(), wave.Next() )) marble.Update() wave.Update() dt = dt - step end state.timeToSimulate = dt end _Update = Update function love.keypressed( key, code, isRepeat ) if key == "escape" then return love.event.quit() end if key == "space" then return NewGame() end return marble.OnKey() end function love.keyreleased( key, code ) return marble.OnKey() end function love.resize( w, h ) UpdateWindowTransform( w, h ) if marble then marble.Resize() end end function love.mousepressed( x, y, button, istouch, presses ) return NewGame() end