Compare commits

...

10 Commits

Author SHA1 Message Date
yaw-man d5e0e1c5da Fixed typo in toki pona text 2023-01-28 10:30:45 -04:00
yaw-man 43bba05bc6 Silly bug fix: the demo folder should be created unconditionally. 2023-01-27 23:46:15 -04:00
yaw-man 5dfd415d3b Final commit before post-jam release, "1.0.0"?
New features since last release:
  - Display score breakdown, high score, and letter grades.
  - Options menu with volume control, keybindings, and true fullscreen.
  - Demos menu. View saved games!
  - Mouse controls, sorta. Mouse controlled menus!
  - High contrast mode.
  - Instructions displayed on startup.
  - Default demo ships with game.
  - Render sitelen pona quote symbols to and te
  - Beat combo indicator.

Outstanding defects:
  - Options menu not persistent.
  - Mouse controls feel like complete dogshit.
2023-01-27 23:17:46 -04:00
yaw-man e1e00dce4c Persistence of continuous accelerations. 2023-01-27 13:24:53 -04:00
yaw-man 7a1533a6a7 Fixed bug: options and demo menu not working in fused mode. 2023-01-27 12:15:13 -04:00
yaw-man 0a300b018c Fix: "load recording" menu wasn't scrolling correctly. 2023-01-24 18:30:38 -04:00
yaw-man 32a154cabe Adding options menu, local leaderboard. 2023-01-22 11:22:51 -04:00
yaw-man 337e0ce154 Added menu for browsing recordings. 2023-01-20 18:38:06 -04:00
yaw-man 67dc751cf0 Deterministic physics! 2023-01-20 09:53:24 -04:00
yaw-man 34f76c0b7c Preparing to add demos. 2023-01-18 18:51:03 -04:00
17 changed files with 811 additions and 98 deletions

View File

@ -179,11 +179,11 @@ local function Update( score, level )
if level > 2 then if level > 2 then
level = level / 120.0 level = level / 120.0
drones.bass:setVolume( 0.7 * (0.2 + math.pow( score, 0.25 )) * level ) drones.bass:setVolume( 0.7 * (0.0 + score) * level )
drones.alto:setVolume( 0.3 * (0.3 + math.pow( score, 0.25 )) * level * level) drones.alto:setVolume( 0.3 * (0.3 + score) * level * level)
drones.fuck:setVolume( 4.0 * math.max( 0, level - 0.75 )) drones.fuck:setVolume( 3.0 * (0.2 + score) * math.max( 0, level - 0.75 ))
drones.subs:setVolume( 0.5 * math.max( 0, level - 0.5 )) drones.subs:setVolume( 0.5 * (0.2 + score) * math.max( 0, level - 0.50 ))
drones.wail:setVolume( 2.0 * math.max( 0, level - 0.666 )) drones.wail:setVolume( 1.5 * (0.5 + score) * math.max( 0, level - 0.66 ))
end end
end end

View File

@ -1,5 +1,6 @@
function love.conf( t ) function love.conf( t )
t.version = "11.4" t.version = "11.4"
t.identity = "Your Own Drum"
t.modules.joystick = false t.modules.joystick = false
t.modules.physics = false t.modules.physics = false

BIN
demos/yam.yod Normal file

Binary file not shown.

187
loadgame.lua Normal file
View File

@ -0,0 +1,187 @@
--UI for selecting a saved game.
local love = love
local loadGame = {}
local _Update
local _Draw
local _MousePressed
local _MouseMoved
local _Resize
local _KeyPressed
local function RestoreMainState()
love.update = _Update
love.draw = _Draw
love.mousepressed = _MousePressed
love.mousemoved = _MouseMoved
love.keypressed = _KeyPressed
love.resize = _Resize
love.mousemoved = nil
love.mouse.setRelativeMode( true )
end
local selectedGame = 1
local scrollOffset = 1
local deleteGame = false
local gameList = assert( love.filesystem.getDirectoryItems( "demos" ) )
local mouseX, mouseY = 0, 0
local w, h = love.graphics.getDimensions()
local canvas = love.graphics.newCanvas( w, h * (1 + #gameList) / 10.0 )
local font = love.graphics.newFont( 36 )
local deleteIcon = love.graphics.newImage( "sitelenpona/ala.png" )
local playIcon = love.graphics.newImage( "sitelenpona/li.png" )
local OnClick
local function DeleteLeft()
return love.graphics.getWidth() * 0.75 + 15 + 6
end
local function DeleteRight()
return DeleteLeft() + h / 10 - 12
end
local function DrawSelection( )
love.graphics.rectangle( "fill", 15, selectedGame * h / 10 )
end
local function PopulateGameList()
scrollOffset = 0
gameList = assert( love.filesystem.getDirectoryItems( "demos" ) )
local w, h = love.graphics.getDimensions()
canvas = love.graphics.newCanvas( w, h * (1 + #gameList) / 10.0 )
love.graphics.setCanvas( canvas )
love.graphics.clear()
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.setLineWidth( 3 )
for i, name in ipairs( gameList ) do
local x, y = 15, i * h / 10
love.graphics.printf( gameList[i], font, x + 10, y, 0.75 * w, "left" )
love.graphics.draw( deleteIcon, DeleteLeft() - 8, y - 8, 0, 0.25, 0.25 )
end
love.graphics.setCanvas()
end
local resizeLoadMenu = function( newWidth, newHeight )
_Resize(newWidth, newHeight )
w, h = love.graphics.getDimensions()
canvas = love.graphics.newCanvas( w, h * (1 + #gameList) / 10.0 )
return PopulateGameList()
end
local updateLoadMenu = function() end
local drawLoadGameMenu = function()
local w, h = love.graphics.getDimensions()
love.graphics.setColor( 1, 1, 1, 1 )
if options["high contrast"].value then
love.graphics.setColor( 0,0,0, 1 )
end
love.graphics.draw( canvas, 0, 15 - scrollOffset * h / 10 )
love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 )
love.graphics.rectangle( "fill", 0, 0, w, 10 + h / 10 - 6 )
love.graphics.setColor( 91 / 255, 206 / 255, 250 / 255 )--
love.graphics.print( "BACK")
if not selectedGame then return end
love.graphics.setColor( 1, 1, 1, 0.4 )
if deleteGame then
love.graphics.rectangle( "fill",
DeleteLeft(), 15 + ( selectedGame - scrollOffset ) * h / 10,
48, 48,
10, 10 )
else
love.graphics.rectangle( "fill", 15, 15 + ( selectedGame - scrollOffset ) * h / 10, w * 0.75,
--h / 10 - 12, 10, 10 )
48, 10, 10)
end
end
local mouseMoved = function( x, y, dx, dy, istouch)
deleteGame = ( x > DeleteLeft() )
selectedGame = math.max( math.min( #gameList, scrollOffset + math.floor( 10 * ( y - 15 ) / h ) ), 1)
if y < 4 + h / 10 then selectedGame = nil end
end
local KeyPress = function( key, code, isRepeat )
if code == "return" then
return OnClick()
end
if not selectedGame then selectedGame = 1 end
if code == "down" then
if selectedGame < #gameList then
if selectedGame > 8 then
scrollOffset = scrollOffset + 1
end
selectedGame = selectedGame + 1
end
return
end
if code == "up" then
if selectedGame > 9 then
scrollOffset = scrollOffset - 1
end
if selectedGame > 1 then
selectedGame = selectedGame - 1
end
return
end
if code == "left" then deleteGame = false end
if code == "right" then deleteGame = true end
if code == "escape" or code == "backspace" then return RestoreMainState() end
end
OnClick = function( x, y, button, istouch, presses )
if y and y < 4 + h / 10 then return RestoreMainState() end
if not selectedGame then return end
if #gameList < 1 then return end
if deleteGame then
love.filesystem.remove( "demos/"..gameList[selectedGame] )
return PopulateGameList()
end
--Restore main state.
RestoreMainState()
return "demos/"..gameList[selectedGame]
end
local wheelMoved = function(x, y)
--scrollOffset = math.max( 0, math.min( #gameList, scrollOffset - y * 0.1 ) )
end
local function LoadGameMenu()
_Update = love.update
_Draw = love.draw
_MousePressed = love.mousepressed
_MouseMoved = love.mousemoved
_Resize = love.resize
_KeyPressed = love.keypressed
love.mouse.setRelativeMode( false )
love.wheelmoved = wheelMoved
love.mousemoved = mouseMoved
love.draw = drawLoadGameMenu
love.update = updateLoadMenu
love.resize = resizeLoadMenu
PopulateGameList()
end
return { OnClick = OnClick, KeyPress = KeyPress, LoadGameMenu = LoadGameMenu }

183
main.lua
View File

@ -15,25 +15,27 @@ local Draw
local Update local Update
local ExtrapolateBeatScore local ExtrapolateBeatScore
local _ExtrapolateBeatScore local _ExtrapolateBeatScore
local mouseControl
local scores
local state local state
state = { state = {
Reset = function() Reset = function()
state.tick = 0
state.beat = {} state.beat = {}
state.startTime = love.timer.getTime()
state.currentBeat = 1 state.currentBeat = 1
state.timeToSimulate = 0.0 state.timeToSimulate = 0.0
state.longestStreak = 0 state.longestStreak = 0
state.currentStreak = 0 state.currentStreak = 0
state.lastCollisionTick = 0
state.lastCollisionX = 0
state.lastCollisionY = 0
end, end,
finishTime = 0.0,
timeToSimulate = 0.0, timeToSimulate = 0.0,
isGameStarted = false,
lastBeatScore = 0.0, lastBeatScore = 0.0,
currentBeat = 1, currentBeat = 1,
startTime = 0.0,
longestStreak = 0, longestStreak = 0,
currentStreak = 0, currentStreak = 0,
@ -60,6 +62,7 @@ OnImpact = function( impact )
local score = BeatScore( impact.t ) local score = BeatScore( impact.t )
--DEBUG --DEBUG
state.lastBeatScore = score state.lastBeatScore = score
state.lastCollisionTick = state.tick
local pass = false local pass = false
if score > 1.0 then if score > 1.0 then
@ -79,12 +82,16 @@ OnImpact = function( impact )
else else
state.currentStreak = 0 state.currentStreak = 0
end end
scores.OnImpact( state.tick, pass )
local x, y = math.cos(impact.th), math.sin(impact.th) local x, y = impact.r * math.cos(impact.th), impact.r * math.sin(impact.th)
particles:setPosition( impact.r * x, impact.r * y ) particles:setPosition( x, y )
particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true ) particles:setEmissionArea( "normal", 0.1, 0.2, impact.th, true )
particles:emit( 50 * score * score ) particles:emit( 50 * score * score )
state.lastCollisionX, state.lastCollisionY = transform:transformPoint( 1.2 * x, 1.2 * y )
audio.OnImpact( impact, score, pass, state.currentBeat ) audio.OnImpact( impact, score, pass, state.currentBeat )
marble.OnImpact( impact, state.currentBeat ) marble.OnImpact( impact, state.currentBeat )
@ -92,31 +99,17 @@ OnImpact = function( impact )
end end
local _OnImpact = OnImpact local _OnImpact = OnImpact
local function NewGame() local function NewGame( demoName )
love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink. love.graphics.setBackgroundColor( 245 / 255, 169 / 255, 184 / 255 ) --Trans pink.
OnImpact = _OnImpact OnImpact = _OnImpact
ExtrapolateBeatScore = _ExtrapolateBeatScore ExtrapolateBeatScore = _ExtrapolateBeatScore
love.draw = Draw love.draw = Draw
love.update = Update love.update = Update
do --particle shit
particles:reset() particles:reset()
particles:setSizes( 0.0007, 0.0001, 0.0003 ) particles:setSizes( 0.0007, 0.0001, 0.0003 )
state.Reset() --particles:setSizes( 0.0007, 0.0001, 0.0003 )
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:setSizeVariation( 1 )
particles:setRadialAcceleration( 0, 0.5 ) particles:setRadialAcceleration( 0, 0.5 )
particles:setSpeed( 0.2, 1 ) particles:setSpeed( 0.2, 1 )
@ -133,12 +126,43 @@ function love.load()
245 / 255, 169 / 255, 184 / 255, 1, 245 / 255, 169 / 255, 184 / 255, 1,
1,1,1,0, 1,1,1,0,
245 / 255, 169 / 255, 184 / 255, 1, 245 / 255, 169 / 255, 184 / 255, 1,
1,1,1,0 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 end
loadGame = assert( require "loadgame" )
options = assert( require( "options" ) )
mouseControl = assert( require "mousecontrols" )
sitelenpona = assert( require "sitelenpona" ) sitelenpona = assert( require "sitelenpona" )
text = assert( require "text" ) text = assert( require "text" )
marble = assert( require "marble" ) marble = assert( require "marble" )
@ -146,12 +170,13 @@ function love.load()
DetectCollision = assert ( require "collision" ) DetectCollision = assert ( require "collision" )
audio = assert( require "audio" ) audio = assert( require "audio" )
recorder = assert( require "recorder" ) recorder = assert( require "recorder" )
scores = assert( require "scores" )
return NewGame() return NewGame()
end end
ExtrapolateBeatScore = function( ) ExtrapolateBeatScore = function( )
local t = love.timer.getTime() local t = state.tick / 120.0
local beat = state.beat local beat = state.beat
if not beat.t then return 2.0 end if not beat.t then return 2.0 end
if not beat.mu then return 2.0 end if not beat.mu then return 2.0 end
@ -203,6 +228,13 @@ end
OnVictory = function() OnVictory = function()
if state.isDemo then
state.isDemo = false
else
scores.Save()
recorder.Save( scores.Get(), state.tick)
end
particles:setParticleLifetime( 0, 30 ) particles:setParticleLifetime( 0, 30 )
particles:setSizes( 0.001, 0.0001, 0.0005 ) particles:setSizes( 0.001, 0.0001, 0.0005 )
particles:setColors( particles:setColors(
@ -219,9 +251,10 @@ OnVictory = function()
particles:setEmissionArea( "normal", 0.01, 0.01, 0, true ) particles:setEmissionArea( "normal", 0.01, 0.01, 0, true )
love.graphics.setCanvas( marble.Canvas() ) love.graphics.setCanvas( marble.Canvas() )
Draw()
love.graphics.setCanvas() love.graphics.setCanvas()
local totalTime = love.timer.getTime() - state.startTime local totalTime = state.tick / 120.0
OnImpact = function() end OnImpact = function() end
love.update = function( dt ) love.update = function( dt )
while dt > step do while dt > step do
@ -230,6 +263,7 @@ OnVictory = function()
marble.Update() marble.Update()
wave.Update() wave.Update()
dt = dt - step dt = dt - step
end end
@ -245,19 +279,10 @@ OnVictory = function()
love.graphics.draw( particles ) love.graphics.draw( particles )
love.graphics.pop() love.graphics.pop()
text.Draw( 119 ) text.Draw( 119 )
scores.RenderHighScores()
local score = state.longestStreak * state.longestStreak / totalTime
love.graphics.setColor( 1, 1, 1, 1 ) local score = state.longestStreak * state.longestStreak / totalTime
love.graphics.printf(
string.format( "time:\t%.2f\nstreak:\t%d\nscore:%.2f", totalTime, state.longestStreak, score):gsub( "%.", "," ),
0, 0.5 * love.graphics.getHeight() - 2.0* love.graphics.getFont():getHeight(),
love.graphics.getWidth(),
"center"
)
marble.Draw() marble.Draw()
end end
@ -266,6 +291,8 @@ OnVictory = function()
end end
Draw = function() Draw = function()
local score = ExtrapolateBeatScore() local score = ExtrapolateBeatScore()
@ -276,6 +303,8 @@ Draw = function()
love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 ) love.graphics.setColor( 1.0, 1.0, 1.0, 1.0 )
love.graphics.draw(particles, 0, 0) love.graphics.draw(particles, 0, 0)
wave.Draw( score ) wave.Draw( score )
love.graphics.pop() love.graphics.pop()
@ -283,6 +312,12 @@ Draw = function()
sitelenpona.Draw( text.words[state.currentBeat] ) sitelenpona.Draw( text.words[state.currentBeat] )
marble.Draw() marble.Draw()
text.Draw( state.currentBeat ) 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 end
@ -296,7 +331,21 @@ Update = function( dt )
--Physics tick. --Physics tick.
while dt > step do while dt > step do
recorder.Update( marble.GetAcceleration() ) --For savegames.
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 ) marble.Integrate( step )
wave.Integrate( step ) wave.Integrate( step )
@ -315,14 +364,51 @@ Update = function( dt )
end end
_Update = Update _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 ) function love.keypressed( key, code, isRepeat )
if key == "escape" then return love.event.quit() end if code == "escape" then return love.event.quit() end
if key == "space" then return NewGame() end if code == "space" then return NewGame() end
return marble.OnKey() 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 end
function love.keyreleased( key, code ) function love.keyreleased( key, code )
return marble.OnKey() 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 end
function love.resize( w, h ) function love.resize( w, h )
@ -330,6 +416,13 @@ function love.resize( w, h )
if marble then marble.Resize() end if marble then marble.Resize() end
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 ) function love.mousepressed( x, y, button, istouch, presses )
return NewGame() return NewGame()
end end

View File

@ -19,8 +19,10 @@ function marble.Next() return newState end
function marble.GetAcceleration( ) return ddx, ddy end function marble.GetAcceleration( ) return ddx, ddy end
function marble.SetAcceleration( x, y ) ddx = x; ddy = y end
function marble.Integrate( step ) function marble.Integrate( step )
newState.t = love.timer.getTime() newState.t = curState.t + step
newState.dx = (1.0 - INERTIA) * curState.dx + INERTIA * ddx newState.dx = (1.0 - INERTIA) * curState.dx + INERTIA * ddx
newState.dy = (1.0 - INERTIA) * curState.dy + INERTIA * ddy newState.dy = (1.0 - INERTIA) * curState.dy + INERTIA * ddy
newState.x = curState.x + newState.dx * step * MAXSPEED newState.x = curState.x + newState.dx * step * MAXSPEED
@ -73,7 +75,7 @@ function marble.OnImpact( impact, level )
xn = 0.2 * unx + x, yn = 0.2 * uny + y, xn = 0.2 * unx + x, yn = 0.2 * uny + y,
vxout = x + vxout, vyout = y + vyout}]] vxout = x + vxout, vyout = y + vyout}]]
return marble.Integrate( math.max( impact.dt , 1 / 120 ) ) --Hmm! Maybe this should be a fixed step instead for stability's sake. return marble.Integrate( 1 / 120.0 ) --Hmm! Maybe this should be a fixed step instead for stability's sake.
end end
function marble.Update() function marble.Update()
@ -82,14 +84,18 @@ function marble.Update()
oldState[k] = curState[k] oldState[k] = curState[k]
curState[k] = newState[k] curState[k] = newState[k]
end end
--Inertia.
--ddx, ddy = ddx * 0.95, ddy * 0.95
end end
function marble.OnKey( w, a, s, d )
--print( w, a, s, d )
ddx = (d and 1.0 or 0.0) - (a and 1.0 or 0.0)
ddy = (w and 1.0 or 0.0) - (s and 1.0 or 0.0)
function marble.OnKey() --print( tick, ddx, ddy )
ddx = (love.keyboard.isScancodeDown( "d" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "a" ) and 1.0 or 0.0)
ddy = (love.keyboard.isScancodeDown( "w" ) and 1.0 or 0.0) - (love.keyboard.isScancodeDown( "s" ) and 1.0 or 0.0)
local n = math.sqrt( ddx * ddx + ddy * ddy ) local n = math.sqrt( ddx * ddx + ddy * ddy )
if n < 0.001 then return end if n < 0.001 then return end
ddx, ddy = ddx / n, ddy / n ddx, ddy = ddx / n, ddy / n
@ -123,7 +129,7 @@ function marble.Draw()
--Extrapolate forward for slightly smoother rendering. --Extrapolate forward for slightly smoother rendering.
local dt = love.timer.getTime() - curState.t local dt = love.timer.getTime() - curState.t
marble.Integrate( dt + 1 / 60.0 ) marble.Integrate( 2 / 120.0 )
local xp, yp = transform:transformPoint( oldState.x, oldState.y ) local xp, yp = transform:transformPoint( oldState.x, oldState.y )
local xc, yc = transform:transformPoint( curState.x, curState.y ) local xc, yc = transform:transformPoint( curState.x, curState.y )
@ -177,6 +183,7 @@ function marble.Reset()
INERTIA = 0.05 INERTIA = 0.05
MAXSPEED = 2.0 MAXSPEED = 2.0
ddx, ddy = 0, 0
oldState, curState, newState = State(), State(), State() oldState, curState, newState = State(), State(), State()
marble.Resize() marble.Resize()
marble.Draw = marble._Draw marble.Draw = marble._Draw

37
mousecontrols.lua Normal file
View File

@ -0,0 +1,37 @@
local mouseControl = { isActive = false }
local love = love
local x, y = 0, 0
local DECAY = 0.8
local SENSITIVITY = 0.8
function mouseControl.Reset()
x, y = 0, 0
end
function mouseControl.mousemoved( dx, dy )
dx, dy = SENSITIVITY * dx, SENSITIVITY * dy
dx, dy = dx * dx * dx , dy * dy * dy
x, y = dx + x, y - dy
return mouseControl.getAcceleration()
end
function mouseControl.update()
local norm = math.sqrt( x * x + y * y )
local decay = DECAY * norm / ( norm + 1 )
x, y = decay * x, decay * y
end
function mouseControl.getAcceleration()
local norm = math.sqrt( x * x + y * y )
if norm > 1 then return x / norm, y / norm end
if norm < 0.001 then return 0, 0 end
return x, y
end
return mouseControl

205
options.lua Normal file
View File

@ -0,0 +1,205 @@
local love = love
local draw = love.draw
local update = love.update
local mousepressed = love.mousepressed
local keypressed = love.keypressed
local mousemoved = love.mousemoved
local ExitMenu
options = options or {}
local options = options
local optionList = {}
local option
if not options.initialized then
options.initialized = true
local keyBindCallback = function(self, code)
self.value = code or self.value
end
options.optionValues = {
{ name = "options",
value = "",
callback = function(self)
end
},
{ name = "fullscreen",
value = false,
callback = function(self)
self.value = not(self.value)
love.window.setFullscreen( self.value )
love.event.push( "resize", love.graphics.getWidth(), love.graphics.getHeight() )
end
},
{ name = "high contrast",
value = false,
callback = function(self, code)
if code == "backspace" then return end
self.value = not( self.value )
return false
end
},
{ name = "volume",
value = 1.0,
callback = function(self, code)
local isIncreasing
if (code == "left" or code == "a") then isIncreasing = -1
elseif (code == "right" or code == "d") then isIncreasing = 1
else return false end
self.value = math.max( 0, math.min( 2,
self.value + (isIncreasing * 0.05 )))
love.audio.setVolume( self.value )
return true
end
},
{ name = "left",
value = "a",
callback = keyBindCallback
},
{ name = "right",
value = "d",
callback = keyBindCallback
},
{ name = "up",
value = "w",
callback = keyBindCallback
},
{ name = "down",
value = "s",
callback = keyBindCallback
}
}
do
for i, ov in ipairs( options.optionValues ) do
options[ ov.name ] = ov
end
end
end
local optionIdx = 1
local isAwaitingKey = false
local font = love.graphics.newFont( 32 )
local function Draw()
love.graphics.setColor( 1,1,1,1 )
if options["high contrast"].value then
love.graphics.setColor( 0, 0, 0, 1 )
end
for i, option in ipairs( options.optionValues ) do
love.graphics.printf( option.name, font, 100, i * 50, 1000, "left")
love.graphics.printf( tostring( option.value ), font, -100, i * 50, love.graphics.getWidth(), "right")
end
local w = love.graphics.getWidth()
love.graphics.setColor( 1,1,1,0.5 )
if isAwaitingKey then
love.graphics.rectangle( "fill", w - 200, optionIdx * 50, 100, 48, 10, 10 )
else
love.graphics.rectangle( "fill", 100, optionIdx * 50, 250, 48, 10, 10 )
end
end
local function SelectNextOption()
optionIdx = math.min( #options.optionValues, optionIdx + 1 )
option = options.optionValues[ optionIdx]
end
local function SelectPreviousOption()
optionIdx = math.max( 1, optionIdx - 1 )
option = options.optionValues[ optionIdx]
end
local function MouseMoved( x, y )
optionIdx =
math.min( #options.optionValues,
math.max( 1,
math.floor( y / 50 )
))
option = options.optionValues[ optionIdx]
if ( x > love.graphics.getWidth() - 200 ) then
isAwaitingKey = true
else
isAwaitingKey = false
end
end
local function KeyPress(key, code, isRepeat)
if not( isAwaitingKey ) then
if code == "backspace" then
return ExitMenu() end
if optionIdx and
code == "return" or
code == "right" then
isAwaitingKey = true
return
end
if code == "down" then return SelectNextOption() end
if code == "up" then return SelectPreviousOption() end
else
if code == "backspace" then isAwaitingKey = false end
if code == "escape" then return end
isAwaitingKey = (options.optionValues[optionIdx]):callback( code )
end
end
local function MousePressed( )
return KeyPress( )
end
local function EnterMenu()
draw = love.draw
update = love.update
mousepressed = love.mousepressed
keypressed = love.keypressed
mousemoved = love.mousemoved
love.mouse.setRelativeMode( false )
love.draw = Draw
love.keypressed = KeyPress
love.update = function() end
love.mousemoved = MouseMoved
love.mousepressed = MousePressed
end
function ExitMenu()
love.mouse.setRelativeMode( true )
love.mousemoved = mousemoved
love.keypressed = keypressed
love.mousepressed = mousepressed
love.update = update
love.draw = draw
end
options.EnterMenu = EnterMenu
options.ExitMenu = ExitMenu
return options

View File

@ -2,13 +2,20 @@
local love = love local love = love
local recorder = {} local recorder = {}
recorder.isLoaded = false recorder.isLoaded = false
local ddxs, ddys
local i = 0 local i = 0
local function Reset() function recorder.Reset()
i = 0 i = 1
for k, _ in ipairs( recorder ) do recorder[k] = nil end
end end
function recorder.Update( ddx, ddy ) function recorder.Update( ddx, ddy )
i = i + 1
recorder[i] = love.data.pack( "string", "!16<dd", ddx, ddy )
end
--[[function recorder.Update( ddx, ddy )
local byte = 0 local byte = 0
if ddx > 0.5 then byte = byte + 1 end if ddx > 0.5 then byte = byte + 1 end
if ddy > 0.5 then byte = byte + 2 end if ddy > 0.5 then byte = byte + 2 end
@ -16,17 +23,35 @@ function recorder.Update( ddx, ddy )
if ddy < -0.5 then byte = byte + 8 end if ddy < -0.5 then byte = byte + 8 end
i = i + 1 i = i + 1
recorder[i] = string.char( byte ) recorder[i] = string.char( byte + 48 )
end end]]
function recorder.Load( ) function recorder.Load( filename )
local s = love.filesystem.read( "demo" ) ddxs, ddys = {}, {}
local s = love.filesystem.read( filename )
if not s then return end
local j = 1
local score, ticks, k = love.data.unpack( "!16<dJ", s, 1)
local n = s:len()
while k < n do
ddxs[j], ddys[j], k = love.data.unpack( "!16<dd", s, k)
j = j + 1
end
recorder.isLoaded = true recorder.isLoaded = true
i = 0
return true
end end
function recorder.Save( ) function recorder.NextTick( )
assert( love.filesystem.write( "demo", table.concat( recorder ) ) ) i = i + 1
return ddxs[i], ddys[i]
end
function recorder.Save( score, ticks )
recorder[1] = love.data.pack( "string", "!16<dJ", score, ticks )
love.filesystem.createDirectory( "demos" )
return assert( love.filesystem.write( "demos/"..os.time()..".yod" , table.concat( recorder ) ) )
end end
return recorder return recorder

118
scores.lua Normal file
View File

@ -0,0 +1,118 @@
local love = love
local scores = {}
local highScores = {}
local letters = {
letters = { "D", "C", "B", "A", "S", "SS", "SSS" },
ticks = { 50000, 14400, 12000, 10000, 8000, 6000, 5000 },
streaks = { 1, 20, 40, 60, 80, 100, 120 },
score = { 0, 5, 10, 22, 56, 150, 300 },
}
letters.letters[0] = "!"
local function ScoreToLetter( ticks, streak, score )
local streakTier, ticksTier, scoreTier = 0, 0, 0
for i, threshold in ipairs( letters.ticks ) do
if ticks < threshold then ticksTier = i end
end
for i, threshold in ipairs( letters.streaks ) do
if streak > threshold then streakTier = i end
end
for i, threshold in ipairs( letters.score ) do
if score > threshold then scoreTier = i end
end
return letters.letters[ticksTier], letters.letters[streakTier], letters.letters[scoreTier]
end
function scores.OnImpact( tick, isHitSuccessful )
local score = 0
if isHitSuccessful then
scores.streak = scores.streak + 1
scores.longestStreak = math.max( scores.streak, scores.longestStreak )
else
table.insert( scores.streaks, scores.streak)
scores.streak = 0
end
for i, streak in ipairs( scores.streaks ) do
score = score + math.pow( streak, 1.7 )
end
scores.score = score / ( ( tick / 120.0 ) - 36.0 )
scores.t = tick
end
function scores.Reset()
scores.score = 0
scores.streak = 0
scores.longestStreak = 0
scores.highScore = scores.LoadHighScores()
scores.streaks = {}
end
function scores.Get()
return scores.score
end
function scores.Save()
local i = 1
for j = 1, #highScores do
i = j
if highScores[j] < scores.score then break end
end
table.insert( highScores, i )
scores.highScore = math.max( scores.score, scores.highScore )
end
function scores.LoadHighScores()
local gameList = assert( love.filesystem.getDirectoryItems( "demos" ) )
local highScore = 0
for i, name in ipairs( gameList ) do
local s = love.filesystem.read( "demos/"..name, 64 )
if ( s:len() > 32 ) then
local gamescore, gameticks = love.data.unpack( "!16<dJ", s, 1)
highScore = math.max( gamescore, highScore )
end
end
return highScore
end
function scores.RenderHighScores()
local wb = options["high contrast"].value and 0 or 1
love.graphics.setColor( wb, wb, wb, 1 )
local h = 1.0 * love.graphics.getFont():getHeight()
local y = 0.5 * love.graphics.getHeight()
local lTick, lStreak, lScore = ScoreToLetter( scores.t, scores.longestStreak, scores.score )
love.graphics.printf(
table.concat{
"time: ",
string.format("%.1f", scores.t / 120.0):gsub("%.", ","),
".",
lTick,
"\n",
"streak: ",
scores.longestStreak,
".",
lStreak,
"\n",
"score: ",
string.format("%.1f", scores.score ):gsub("%.", ","),
".",
lScore,
"\n",
"record: ",
string.format("%.1f", math.max( scores.highScore, scores.score )):gsub("%.", ","),
".!\n"
},
love.graphics.getWidth() * 0.5 - 250, y - 2 * h, 500, "justify" )
end
scores.highScore = scores.LoadHighScores()
return scores

View File

@ -10,12 +10,12 @@ end
sp.Draw = function( t ) sp.Draw = function( t )
local cx, cy = love.graphics.getDimensions() local cx, cy = love.graphics.getDimensions()
cx, cy = 0.5 * cx, 0.5 * cy cx, cy = 0.5 * cx, 0.5 * cy
love.graphics.setColor( 1.0, 1.0, 1.0, 0.5 ) love.graphics.setColor( 1.0, 1.0, 1.0, options["high contrast"].value and 0.2 or 0.5)
if #t == 1 then if #t == 1 then
love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 128 ) love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 128 )
elseif #t == 2 then elseif #t == 2 then
love.graphics.draw( sp[t[1]] or sp.pilin, cx - 144, cy - 96, 0, 0.75, 0.75 ) love.graphics.draw( sp[t[1]] or sp.pilin, cx - 160, cy - 96, 0, 0.75, 0.75 )
love.graphics.draw( sp[t[2]] or sp.pilin, cx - 16, cy - 96, 0, 0.75, 0.75 ) love.graphics.draw( sp[t[2]] or sp.pilin, cx - 16, cy - 96, 0, 0.75, 0.75 )
elseif #t == 3 then elseif #t == 3 then
love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 64, 0, 0.5, 0.5 ) love.graphics.draw( sp[t[1]] or sp.pilin, cx - 128, cy - 64, 0, 0.5, 0.5 )

BIN
sitelenpona/te.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
sitelenpona/to.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -10,9 +10,10 @@ local poemLines = {[0] = 0}
local mt = { __index = function() return "linja" end } local mt = { __index = function() return "linja" end }
local s = love.filesystem.read( "text/tok.txt" ) local s = love.filesystem.read( "text/tok.txt" )
local enFont = love.graphics.setNewFont( "text/linja-sike.ttf", 18 ) local instrFont = love.graphics.newFont( 24 )
local smallFont = love.graphics.setNewFont( "text/linja-sike.ttf", 24 ) local enFont = love.graphics.setNewFont( "text/yod-linja-sike.ttf", 18 )
local largeFont = love.graphics.setNewFont( "text/linja-sike.ttf", 64 ) local smallFont = love.graphics.setNewFont( "text/yod-linja-sike.ttf", 24 )
local largeFont = love.graphics.setNewFont( "text/yod-linja-sike.ttf", 64 )
local i = 1 local i = 1
@ -39,11 +40,11 @@ for foot in s:gmatch( ".-%-" ) do
local j = 1 local j = 1
for w in foot:gmatch( [["]] ) do for w in foot:gmatch( [["]] ) do
word[j] = isTe and "te" or "to" word[j] = isTe and "te" or "to"
isTe = not( isTe ) isTe = not( isTe )
j = j + 1 j = j + 1
end end
for w in foot:gmatch( "%a+" ) do for w in foot:gmatch( "%a+" ) do
@ -57,25 +58,54 @@ end
local function Draw( beat ) local function Draw( beat )
local foot = feet[ beat ] local foot = feet[ beat ]
if beat == 1 then if beat == 1 then
love.graphics.setColor(0.0, 0.0, 0.0, 1.0)
love.graphics.setColor(0, 0, 0, 0.5)
if options["high contrast"].value then
love.graphics.setColor( 1, 1, 1, 1 )
end
love.graphics.rectangle( "fill",
0.5 * love.graphics.getWidth() - 250, 10,
500, 1.1 * largeFont:getHeight(), 10, 10 )
love.graphics.rectangle( "fill",
0.5 * love.graphics.getWidth() - 200, love.graphics.getHeight() - 5.25 * instrFont:getHeight(),
400, 4.5 * instrFont:getHeight(), 50, 50 )
love.graphics.setColor(1.0, 1.0, 1.0, 1.0 )
if options["high contrast"].value then
love.graphics.setColor( 0, 0, 0, 1 )
end
love.graphics.printf( "your.own.drum", love.graphics.printf( "your.own.drum",
0, 0, largeFont,
love.graphics.getWidth(), 0, 0,
"center" love.graphics.getWidth(),
"center"
)
love.graphics.printf(
[[
WASD MOVE
SPACE RESTART
RETURN DEMOS
O OPTIONS]],
instrFont,
0.5 * love.graphics.getWidth() - 150, love.graphics.getHeight() - 5 * instrFont:getHeight(),
300,
"justify"
) )
love.graphics.printf( "wasd.space", return
0, 0.87 * love.graphics.getHeight(),
love.graphics.getWidth(),
"center"
)
return
end end
love.graphics.setColor(1.0, 1.0, 1.0, 0.5) love.graphics.setColor(1.0, 1.0, 1.0, 0.5)
if options["high contrast"].value then
love.graphics.setColor( 0, 0, 0, 1 )
end
local lineNumber local lineNumber
if poemLines[beat] < 2 then lineNumber = 0 if poemLines[beat] < 2 then lineNumber = 0
else lineNumber = 2 - poemLines[beat] else lineNumber = 2 - poemLines[beat]
@ -88,6 +118,9 @@ local function Draw( beat )
) )
love.graphics.setColor(1.0, 1.0, 1.0, 1.0) love.graphics.setColor(1.0, 1.0, 1.0, 1.0)
if options["high contrast"].value then
love.graphics.setColor( 0, 0, 0, 1 )
end
love.graphics.printf( foot, love.graphics.printf( foot,
largeFont, largeFont,
0, 0.87 * love.graphics.getHeight(), 0, 0.87 * love.graphics.getHeight(),
@ -96,6 +129,9 @@ local function Draw( beat )
) )
love.graphics.setColor(1.0, 1.0, 1.0, 0.5) love.graphics.setColor(1.0, 1.0, 1.0, 0.5)
if options["high contrast"].value then
love.graphics.setColor( 0, 0, 0, 1 )
end
love.graphics.printf( poemLang[beat], love.graphics.printf( poemLang[beat],
enFont, enFont,
-8, 0,--(2 - poemLines[beat]) * smallFont:getHeight(), -8, 0,--(2 - poemLines[beat]) * smallFont:getHeight(),
@ -107,7 +143,7 @@ local function Draw( beat )
end end
s = love.filesystem.read( "text/en.txt" ) s = love.filesystem.read( "text/en.txt" )
i = 1 i = 0
--Split string into lines. --Split string into lines.
for line in s:gmatch( ".-\n") do for line in s:gmatch( ".-\n") do

View File

@ -1,7 +1,7 @@
open -la mi -jan pi -toki- open -la mi -jan pi -toki-
ala -li mu -taso -li ken- ala -li mu -taso -li ken-
ala -pana -toki -pilin- ala -pana -toki -pilin-
toki -mu li -wawa -wawa toki -mu li -wawa -wawa-
nasa -la jan -ante -li ken- nasa -la jan -ante -li ken-
awen -pona -tawa -mi li- awen -pona -tawa -mi li-
awen -ala-sa e -pona.- awen -ala-sa e -pona.-

View File

@ -46,10 +46,10 @@ local shader = love.graphics.newShader([[
vec2 p = (3.0 * screen_coords - 1.5 * love_ScreenSize.xy ) / love_ScreenSize.y; vec2 p = (3.0 * screen_coords - 1.5 * love_ScreenSize.xy ) / love_ScreenSize.y;
p.y = -p.y; p.y = -p.y;
float r = r( atan(p.y, p.x) ) - length( p ); float r = r( atan(p.y, p.x) ) - length( p );
float q = float( r < 0.05 ) * clamp( 0.5 - score, 0.0, 1.0 ) ; float q = float( r < 0.05 ) * clamp( 1.0 - score, 0.0, 1.0 ) ;
return vec4( q + (1.0 + clamp( score, 0.0, 1.0 ) * r * r * 0.2) * color.xyz, float(r > 0.0) ) ; return vec4( q + (1.0 + clamp( score, 0.0, 1.0 ) * r * r * 0.3) * color.xyz, float(r > 0.0) ) ;
} }
]]) ]])
@ -149,7 +149,7 @@ end
--Apply bandlimited impulse to wave, adjust free parameters according to game state. --Apply bandlimited impulse to wave, adjust free parameters according to game state.
local function OnImpact( impact, level ) local function OnImpact( impact, level )
IMPULSESIZE = 10.0 * ( level - 2.0) / 120.0 IMPULSESIZE = 10.0 * ( level - 2.0) / 120.0
SOUNDSPEED = 25 - 10 * level / 120 SOUNDSPEED = 25 - 10 * level / 120
DAMPING = 0.01 * ( 1.0 - 0.4 * level / 120 ) DAMPING = 0.01 * ( 1.0 - 0.4 * level / 120 )
@ -159,7 +159,7 @@ local function OnImpact( impact, level )
local r = cur.radii local r = cur.radii
local theta = impact.th local theta = impact.th
local magnitude = IMPULSESIZE * impact.speed local magnitude = IMPULSESIZE * impact.speed
local dt = math.max( impact.dt, 1 / 120.0 ) local dt = 1.0 / 120.0
for i = 0, N - 1 do for i = 0, N - 1 do
r[ i + 1 ] = r[ i + 1 ] + dt * magnitude * AliasedSinc( theta, 2.0 * math.pi * i / N ) r[ i + 1 ] = r[ i + 1 ] + dt * magnitude * AliasedSinc( theta, 2.0 * math.pi * i / N )
end end
@ -201,7 +201,11 @@ local function Draw( score )
-- Blue circle. -- Blue circle.
love.graphics.setColor( 91 / 255, 206 / 255, 250 / 255 ) love.graphics.setColor( 91 / 255, 206 / 255, 250 / 255 )
--Black circle.
if options["high contrast"].value then
love.graphics.setColor( 0, 0, 0 )
end
shader:send( "re", unpack( cur.dftre ) ) shader:send( "re", unpack( cur.dftre ) )
shader:send( "im", unpack( cur.dftim ) ) shader:send( "im", unpack( cur.dftim ) )
shader:send( "score", score ) shader:send( "score", score )
@ -255,7 +259,7 @@ local function Draw( score )
local r = cur:Interpolate( t ) local r = cur:Interpolate( t )
local x, y = r * math.cos( t ), r * math.sin( t ) local x, y = r * math.cos( t ), r * math.sin( t )
love.graphics.circle( "fill", x, y, 0.02 )]] love.graphics.circle( "fill", x, y, 0.02 )]]
end end
@ -279,12 +283,12 @@ end
Integrate = function( step ) Integrate = function( step )
for i = 1, N do for i = 1, N do
local rxx = cur:SecondDerivative( math.pi * 2.0 * ( i - 1 ) / N ) local rxx = cur:SecondDerivative( math.pi * 2.0 * ( i - 1 ) / N )
local r = ( 1.0 - DAMPING ) * ( 2.0 * cur.radii[i] - old.radii[i] + step * step * SOUNDSPEED * rxx ) --Verlet local r = ( 1.0 - DAMPING ) * ( 2.0 * cur.radii[i] - old.radii[i] + step * step * SOUNDSPEED * rxx ) --Verlet
+ DAMPING --Damping: oscillate toward 1. + DAMPING --Damping: oscillate toward 1.
--Avoid explosions. --Avoid explosions.
r = math.max( 0.5, math.min( r, 4.0 ) ) r = math.max( 0.5, math.min( r, 4.0 ) )
new.radii[i] = r new.radii[i] = r
@ -299,11 +303,11 @@ local function DetectCollision( px, py, vpx, vpy )
end end
local function Reset() local function Reset()
IMPULSESIZE = 1 / 10.0 IMPULSESIZE = 1 / 10.0
SOUNDSPEED = 5.0 SOUNDSPEED = 5.0
DAMPING = 0.1 / 1 DAMPING = 0.1 / 1
old = Wave() old = Wave()
cur = Wave() cur = Wave()
new = Wave() new = Wave()