oma-command/game/highscores.lua
nosignal 64ce7f0fcb Initial public release
OMA-COMMAND — Missile Command arcade clone in Love2D with Omarchy theme integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 18:32:07 +01:00

255 lines
7.7 KiB
Lua

local HighScores = {}
local MAX_SCORES = 10
local SAVE_FILE = "high_scores.dat"
local scores = {}
-- Entry state
local entry = {
active = false,
score = 0,
letters = {"A", "A", "A"},
position = 1, -- 1, 2, or 3
blink = 0,
}
-- ── Data Management ──
function HighScores.init()
HighScores.load()
end
function HighScores.load()
scores = {}
if love.filesystem.getInfo(SAVE_FILE) then
local data = love.filesystem.read(SAVE_FILE)
if data then
for line in data:gmatch("[^\n]+") do
local initials, score = line:match("^(%a%a%a)%s+(%d+)$")
if initials and score then
table.insert(scores, {initials = initials:upper(), score = tonumber(score)})
end
end
end
end
table.sort(scores, function(a, b) return a.score > b.score end)
-- Trim to max
while #scores > MAX_SCORES do
table.remove(scores)
end
end
function HighScores.save()
local lines = {}
for _, entry in ipairs(scores) do
table.insert(lines, string.format("%s %d", entry.initials, entry.score))
end
love.filesystem.write(SAVE_FILE, table.concat(lines, "\n") .. "\n")
end
function HighScores.isHighScore(score)
if score <= 0 then return false end
if #scores < MAX_SCORES then return true end
return score > scores[#scores].score
end
function HighScores.addScore(initials, score)
table.insert(scores, {initials = initials:upper(), score = score})
table.sort(scores, function(a, b) return a.score > b.score end)
while #scores > MAX_SCORES do
table.remove(scores)
end
HighScores.save()
end
function HighScores.getScores()
local result = {}
for _, s in ipairs(scores) do
table.insert(result, {initials = s.initials, score = s.score})
end
return result
end
function HighScores.getHighest()
if #scores > 0 then
return scores[1].score
end
return 0
end
-- ── Entry Screen ──
function HighScores.startEntry(score)
entry.active = true
entry.score = score
entry.letters = {"A", "A", "A"}
entry.position = 1
entry.blink = 0
end
function HighScores.isEntryActive()
return entry.active
end
function HighScores.updateEntry(dt)
entry.blink = entry.blink + dt
end
function HighScores.keypressedEntry(key)
if not entry.active then return nil end
if key == "left" then
entry.position = math.max(1, entry.position - 1)
elseif key == "right" then
entry.position = math.min(3, entry.position + 1)
elseif key == "up" then
local b = entry.letters[entry.position]:byte()
b = b + 1
if b > 90 then b = 65 end -- wrap Z -> A
entry.letters[entry.position] = string.char(b)
elseif key == "down" then
local b = entry.letters[entry.position]:byte()
b = b - 1
if b < 65 then b = 90 end -- wrap A -> Z
entry.letters[entry.position] = string.char(b)
elseif key == "return" or key == "kpenter" then
local initials = table.concat(entry.letters)
local score = entry.score
entry.active = false
HighScores.addScore(initials, score)
return "done", {initials = initials, score = score}
elseif key:match("^%a$") and #key == 1 then
-- Direct letter input
entry.letters[entry.position] = key:upper()
if entry.position < 3 then
entry.position = entry.position + 1
end
end
return nil
end
function HighScores.drawEntry(screenW, screenH, palette, fonts)
local midX = screenW / 2
local midY = screenH * 0.25
-- "NEW HIGH SCORE" header
love.graphics.setFont(fonts.large)
love.graphics.setColor(palette.bright)
local t = love.timer.getTime()
local pulse = 0.7 + math.sin(t * 4) * 0.3
love.graphics.setColor(palette.bright[1], palette.bright[2], palette.bright[3], pulse)
love.graphics.printf("NEW HIGH SCORE", 0, midY, screenW, "center")
-- Score display
midY = midY + fonts.large:getHeight() + 16
love.graphics.setFont(fonts.medium)
love.graphics.setColor(palette.fg)
love.graphics.printf(string.format("%d", entry.score), 0, midY, screenW, "center")
-- Letter entry boxes
midY = midY + fonts.medium:getHeight() + 32
local boxW = math.floor(screenW * 0.06)
local boxH = math.floor(boxW * 1.3)
local gap = math.floor(boxW * 0.4)
local totalW = boxW * 3 + gap * 2
local startX = midX - totalW / 2
love.graphics.setFont(fonts.large)
local letterH = fonts.large:getHeight()
for i = 1, 3 do
local bx = startX + (i - 1) * (boxW + gap)
local by = midY
-- Box outline
local isSelected = (i == entry.position)
if isSelected then
local blinkOn = math.floor(entry.blink * 3) % 2 == 0
if blinkOn then
love.graphics.setColor(palette.bright)
else
love.graphics.setColor(palette.dim)
end
love.graphics.setLineWidth(3)
else
love.graphics.setColor(palette.dim)
love.graphics.setLineWidth(2)
end
love.graphics.rectangle("line", bx, by, boxW, boxH)
-- Up/down arrows for selected position
if isSelected then
love.graphics.setColor(palette.bright[1], palette.bright[2], palette.bright[3], 0.5)
local arrowX = bx + boxW / 2
-- Up arrow
local arrowTop = by - 12
love.graphics.polygon("fill", arrowX - 5, arrowTop + 8, arrowX + 5, arrowTop + 8, arrowX, arrowTop)
-- Down arrow
local arrowBot = by + boxH + 4
love.graphics.polygon("fill", arrowX - 5, arrowBot, arrowX + 5, arrowBot, arrowX, arrowBot + 8)
end
-- Letter
love.graphics.setColor(palette.fg)
local lw = fonts.large:getWidth(entry.letters[i])
local lx = bx + (boxW - lw) / 2
local ly = by + (boxH - letterH) / 2
love.graphics.print(entry.letters[i], lx, ly)
end
-- Instructions
local instrY = midY + boxH + 32
love.graphics.setFont(fonts.small)
love.graphics.setColor(palette.dim)
love.graphics.printf("TYPE LETTERS / ARROWS TO SELECT / ENTER TO CONFIRM", 0, instrY, screenW, "center")
end
function HighScores.drawTable(screenW, screenH, palette, fonts)
local topY = screenH * 0.68
local lineH = fonts.medium:getHeight() + 4
love.graphics.setFont(fonts.medium)
love.graphics.setColor(palette.bright)
love.graphics.printf("HIGH SCORES", 0, topY, screenW, "center")
topY = topY + lineH + 8
love.graphics.setFont(fonts.small)
local entryH = fonts.small:getHeight() + 3
if #scores == 0 then
love.graphics.setColor(palette.dim)
love.graphics.printf("NO SCORES YET", 0, topY, screenW, "center")
return
end
local colW = math.floor(screenW * 0.4)
local startX = (screenW - colW) / 2
for i, s in ipairs(scores) do
local y = topY + (i - 1) * entryH
local rankStr = string.format("%2d.", i)
local scoreStr = string.format("%d", s.score)
-- Rank and initials
if i == 1 then
love.graphics.setColor(palette.bright)
else
love.graphics.setColor(palette.fg[1], palette.fg[2], palette.fg[3], 0.8)
end
love.graphics.print(rankStr .. " " .. s.initials, startX, y)
-- Score right-aligned
local sw = fonts.small:getWidth(scoreStr)
love.graphics.print(scoreStr, startX + colW - sw, y)
-- Subtle separator line
love.graphics.setColor(palette.dim[1], palette.dim[2], palette.dim[3], 0.2)
love.graphics.setLineWidth(1)
love.graphics.line(startX, y + entryH - 1, startX + colW, y + entryH - 1)
end
end
return HighScores