feat: add day menu

This commit is contained in:
2025-11-28 14:07:03 +01:00
parent 02c5542f2f
commit 75a2b404b1
4 changed files with 263 additions and 51 deletions

View File

@@ -1,4 +1,5 @@
SRC_PATH = "/aoc/src" SRC_PATH = "/aoc/src"
RES_PATH = "/aoc/res"
package.path = SRC_PATH .. "/lib/?.lua;" .. package.path package.path = SRC_PATH .. "/lib/?.lua;" .. package.path
@@ -6,6 +7,8 @@ START_DATE = {day=1, month=12, year=2025}
END_DATE = {day=12, month=12, year=2025} END_DATE = {day=12, month=12, year=2025}
local json = require("json") local json = require("json")
local dates = require("dates")
local days = require("days")
local today = os.date("*t") local today = os.date("*t")
local function loadStats(path) local function loadStats(path)
@@ -24,52 +27,12 @@ local function loadStats(path)
return data return data
end end
---Checks whether date2 is after date1
---@param date1 any
---@param date2 any
---@return boolean
local function isAfter(date1, date2)
if date2.year < date1.year then
return false
elseif date2.year > date1.year then
return true
end
if date2.month < date1.month then
return false
elseif date2.month > date1.month then
return true
end
return date2.day > date1.day
end
---Checks whether date2 is before date1
---@param date1 any
---@param date2 any
---@return boolean
local function isBefore(date1, date2)
if date2.year > date1.year then
return false
elseif date2.year < date1.year then
return true
end
if date2.month > date1.month then
return false
elseif date2.month < date1.month then
return true
end
return date2.day < date1.day
end
local function isInDateRange(startDate, targetDate, endDate)
return not (isBefore(startDate, targetDate) or isAfter(endDate, targetDate))
end
local function printDateInfo() local function printDateInfo()
if isBefore(START_DATE, today) then if dates.isBefore(START_DATE, today) then
print("AoC 2025 has not started yet") print("AoC 2025 has not started yet")
return return
end end
if isAfter(END_DATE, today) then if dates.isAfter(END_DATE, today) then
print("AoC 2025 has ended") print("AoC 2025 has ended")
return return
end end
@@ -77,8 +40,7 @@ local function printDateInfo()
print("Day " .. day .. "/" .. END_DATE.day) print("Day " .. day .. "/" .. END_DATE.day)
end end
local function printStats() local function printStats(stats, selected)
local stats = loadStats("aoc/res/stats.json")
local keys = {} local keys = {}
for k in pairs(stats) do for k in pairs(stats) do
table.insert(keys, k) table.insert(keys, k)
@@ -91,7 +53,13 @@ local function printStats()
local value = stats[key] local value = stats[key]
local day = tonumber(key:sub(4)) local day = tonumber(key:sub(4))
local date = {day=day, month=START_DATE.month, year=START_DATE.year} local date = {day=day, month=START_DATE.month, year=START_DATE.year}
if not isBefore(date, today) then term.setTextColor(colors.lightBlue)
if selected == day then
write("- ")
else
write(" ")
end
if not dates.isBefore(date, today) then
term.setTextColor(colors.white) term.setTextColor(colors.white)
else else
term.setTextColor(colors.gray) term.setTextColor(colors.gray)
@@ -123,15 +91,42 @@ local function printStats()
print() print()
end end
local function main() local function printBanner()
term.setTextColor(colors.green) term.setTextColor(colors.green)
textutils.slowPrint("+--------------------------------------+", 80) print("+--------------------------------------+")
textutils.slowPrint("| Welcome to the Advent of Code 2025 |", 80) print("| Welcome to the Advent of Code 2025 |")
textutils.slowPrint("+--------------------------------------+", 80) print("+--------------------------------------+")
term.setTextColor(colors.white) term.setTextColor(colors.white)
end
local function main()
local stats = loadStats("aoc/res/stats.json")
local selectedDay = math.max(1, math.min(END_DATE.day, today.day))
while true do
term.clear()
term.setCursorPos(1, 1)
printBanner()
printDateInfo() printDateInfo()
printStats() printStats(stats, selectedDay)
local event, key, is_held = os.pullEvent("key")
if key == keys.up then
selectedDay = math.max(1, selectedDay - 1)
elseif key == keys.down then
selectedDay = math.min(END_DATE.day, selectedDay + 1)
elseif key == keys["end"] then
break
elseif key == keys.enter then
local dayStats = stats[("day%02d"):format(selectedDay)]
local day = days.Day.new(
selectedDay,
dayStats.puzzle1,
dayStats.puzzle2
)
day:show()
end
end
end end
main() main()

46
src/lib/dates.lua Normal file
View File

@@ -0,0 +1,46 @@
local dates = {}
---Checks whether date2 is after date1
---@param date1 any
---@param date2 any
---@return boolean
function dates.isAfter(date1, date2)
if date2.year < date1.year then
return false
elseif date2.year > date1.year then
return true
end
if date2.month < date1.month then
return false
elseif date2.month > date1.month then
return true
end
return date2.day > date1.day
end
---Checks whether date2 is before date1
---@param date1 any
---@param date2 any
---@return boolean
function dates.isBefore(date1, date2)
if date2.year > date1.year then
return false
elseif date2.year < date1.year then
return true
end
if date2.month > date1.month then
return false
elseif date2.month < date1.month then
return true
end
return date2.day < date1.day
end
function dates.isInDateRange(startDate, targetDate, endDate)
return not (
dates.isBefore(startDate, targetDate) or
dates.isAfter(endDate, targetDate)
)
end
return dates

143
src/lib/days.lua Normal file
View File

@@ -0,0 +1,143 @@
local utils = require("utils")
local days = {}
local DAY_CACHE_PATH = "/.cache/days"
local CHOICES = {
create = "Create files",
example = "Run examples",
real = "Run with real input",
main = "Back to main menu"
}
---@class Day
---@field day integer
---@field title string?
---@field puzzle1 boolean
---@field puzzle2 boolean
local Day = {day = 0, title = nil, puzzle1 = false, puzzle2 = false}
Day.__index = Day
---Creates a new Day object
---@param dayI integer
---@param puzzle1 boolean
---@param puzzle2 boolean
---@return Day
function Day.new(dayI, puzzle1, puzzle2)
local day = {}
setmetatable(day, Day)
day.day = dayI
day.puzzle1 = puzzle1
day.puzzle2 = puzzle2
return day
end
---Returns the title of this day.
---
---This function looks in the following places, in order:
---1. self.title
---2. Cache directory (DAY_CACHE_PATH)
---3. HTTP request to adventofcode.com
---@return string
function Day:getTitle()
if self.title then
return self.title
end
local cachePath = DAY_CACHE_PATH .. ("/%02d.txt"):format(self.day)
if fs.exists(cachePath) then
local cache = fs.open(cachePath, "r")
if cache then
local title = cache.readLine()
cache.close()
if title then
self.title = title
return title
end
end
end
fs.makeDir(DAY_CACHE_PATH)
local res = http.get("https://adventofcode.com/2024/day/" .. self.day)
local title = "Day " .. self.day
if res then
local body = res.readAll() or ""
local htmlTitle = body:match("%-%-%- (Day %d+: .-) %-%-%-")
if htmlTitle then
title = htmlTitle
self.title = title
local cache = fs.open(cachePath, "w")
if cache then
cache.writeLine(title)
cache.close()
end
end
end
return title
end
function Day:srcDir()
return SRC_PATH .. ("/day%02d"):format(self.day)
end
function Day:examplePath(suffix)
local filename = ("day%02d"):format(self.day)
if suffix then
filename = filename .. "_" .. suffix
end
return RES_PATH .. "/examples/" .. filename .. ".txt"
end
function Day:inputPath()
local filename = ("day%02d"):format(self.day)
return RES_PATH .. "/inputs/" .. filename .. ".txt"
end
---Creates the files for this day.
---
---This method creates the following files:
---- Script for puzzle 1
---- Script for puzzle 2
---- Example input file
---- Real input file
function Day:createFiles()
local srcDir = self:srcDir()
fs.makeDir(srcDir)
local files = {
srcDir .. "/puzzle1.lua",
srcDir .. "/puzzle2.lua",
self:examplePath(),
self:inputPath()
}
for _, path in ipairs(files) do
local f = fs.open(path, "a")
if f then
f.close()
else
printError("Could not create file " .. path)
end
end
end
---Displays this day and prompts the user with possible actions
function Day:show()
term.clear()
term.setCursorPos(1, 1)
term.setTextColor(colors.green)
print(" -*- [ " .. self:getTitle() .. " ] -*-")
if fs.exists(self:srcDir()) then
local c = utils.promptChoices({CHOICES.example, CHOICES.real, CHOICES.main})
if c == CHOICES.example then
end
else
local c = utils.promptChoices({CHOICES.create, CHOICES.main})
if c == CHOICES.create then
self:createFiles()
end
end
print("Puzzle 1")
end
days.Day = Day
return days

28
src/lib/utils.lua Normal file
View File

@@ -0,0 +1,28 @@
local utils = {}
function utils.promptChoices(choices)
local c = 1
local ox, oy = term.getCursorPos()
while true do
term.setCursorPos(ox, oy)
for i, choice in ipairs(choices) do
if i == c then
term.setTextColor(colors.white)
else
term.setTextColor(colors.lightGray)
end
print(choice)
end
local event, key, is_held = os.pullEvent("key")
if key == keys.up then
c = math.max(1, c - 1)
elseif key == keys.down then
c = math.min(#choices, c + 1)
elseif key == keys.enter then
break
end
end
return choices[c]
end
return utils