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 mouseControl local scores local state state = { Reset = function() state.tick = 0 state.beat = {} state.currentBeat = 1 state.timeToSimulate = 0.0 state.longestStreak = 0 state.currentStreak = 0 state.lastCollisionTick = 0 state.lastCollisionX = 0 state.lastCollisionY = 0 end, timeToSimulate = 0.0, lastBeatScore = 0.0, currentBeat = 1, 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 state.lastCollisionTick = state.tick 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 scores.OnImpact( state.tick, pass ) local x, y = impact.r * math.cos(impact.th), impact.r * math.sin(impact.th) particles:setPosition( x, y ) particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true ) particles:emit( 50 * score * score ) state.lastCollisionX, state.lastCollisionY = transform:transformPoint( 1.2 * x, 1.2 * y ) audio.OnImpact( impact, score, pass, state.currentBeat ) marble.OnImpact( impact, state.currentBeat ) wave.OnImpact( impact, state.currentBeat ) end local _OnImpact = OnImpact local function NewGame( demoName ) love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink. OnImpact = _OnImpact ExtrapolateBeatScore = _ExtrapolateBeatScore love.draw = Draw love.update = Update do --particle shit particles:reset() particles:setSizes( 0.0007, 0.0001, 0.0003 ) --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 mouseControl.Reset() state.Reset() marble.Reset() wave.Reset() text.Reset() audio.Reset() recorder.Reset() scores.Reset() state.isDemo = demoName if demoName then recorder.Load( demoName ) end end function love.load() love.mouse.setRelativeMode( true ) UpdateWindowTransform( love.graphics.getDimensions() ) do--particle system setup particles = love.graphics.newParticleSystem( love.graphics.newImage( "prideflag.png" ), 2048) end loadGame = assert( require "loadgame" ) options = assert( require( "options" ) ) mouseControl = assert( require "mousecontrols" ) 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" ) scores = assert( require "scores" ) return NewGame() end ExtrapolateBeatScore = function( ) local t = state.tick / 120.0 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() if state.isDemo then state.isDemo = false else scores.Save() recorder.Save( scores.Get(), state.tick) end 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() ) Draw() love.graphics.setCanvas() local totalTime = state.tick / 120.0 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 ) scores.RenderHighScores() local score = state.longestStreak * state.longestStreak / totalTime 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 ) love.graphics.setColor( 0.3, 0.3, 0.3, 1.0 - score ) local s = "+"..state.currentStreak..(state.currentStreak > 50 and "!" or "") love.graphics.print( s, state.lastCollisionX, state.lastCollisionY, 0, 0.5 - 0.1 * score, 0.5 - 0.1 * score) end Update = function( dt ) audio.Update( ExtrapolateBeatScore(), state.currentBeat ) particles:update( dt ) dt = dt + state.timeToSimulate --Physics tick. while dt > step do state.tick = state.tick + 1 if mouseControl.isActive then mouseControl.update() marble.SetAcceleration( mouseControl.getAcceleration() ) end if state.isDemo then local ddx, ddy = recorder.NextTick() if ddx then marble.SetAcceleration( ddx, ddy ) else state.isDemo = false end else recorder.Update( marble.GetAcceleration() ) --For savegames. end 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 local function OptionsMenu() if not options then options = require( "options" ) end return options.EnterMenu() end local function LoadGame() -- Load game screen. local Click, Press = assert(loadGame.OnClick), assert(loadGame.KeyPress) loadGame.LoadGameMenu() love.mousepressed = function( x, y, button, istouch, presses) local demoName = Click( x, y, button, istouch, presses ) if demoName then return NewGame( demoName ) end end love.keypressed = function( key, code, isRepeat) local demoName = Press( key, code, isRepeat ) if demoName then return NewGame( demoName ) end end end function love.keypressed( key, code, isRepeat ) if code == "escape" then return love.event.quit() end if code == "space" then return NewGame() end if code == "return" then return LoadGame() end --Play demo. if code == "o" then return OptionsMenu() end if state.isDemo then return end mouseControl.isActive = false return marble.OnKey( love.keyboard.isScancodeDown( options.up.value ), love.keyboard.isScancodeDown( options.left.value ), love.keyboard.isScancodeDown( options.down.value ), love.keyboard.isScancodeDown( options.right.value ) ) end function love.keyreleased( key, code ) return marble.OnKey( love.keyboard.isScancodeDown( options.up.value), love.keyboard.isScancodeDown( options.left.value), love.keyboard.isScancodeDown( options.down.value), love.keyboard.isScancodeDown( options.right.value) ) end function love.resize( w, h ) UpdateWindowTransform( w, h ) if marble then marble.Resize() end end --TODO: make this feel better. function love.mousemoved( x, y, dx, dy, number, istouch ) mouseControl.isActive = true dx, dy = mouseControl.mousemoved( dx, dy ) if dx then return marble.SetAcceleration( dx, dy ) end end function love.mousepressed( x, y, button, istouch, presses ) return NewGame() end