From 75a2b404b1c582073ce2de9bf292f0e89bbc8b17 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Fri, 28 Nov 2025 14:07:03 +0100 Subject: [PATCH] feat: add day menu --- src/lib/aoc.lua | 97 +++++++++++++++---------------- src/lib/dates.lua | 46 +++++++++++++++ src/lib/days.lua | 143 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib/utils.lua | 28 +++++++++ 4 files changed, 263 insertions(+), 51 deletions(-) create mode 100644 src/lib/dates.lua create mode 100644 src/lib/days.lua create mode 100644 src/lib/utils.lua diff --git a/src/lib/aoc.lua b/src/lib/aoc.lua index 436b192..f0b5dd7 100644 --- a/src/lib/aoc.lua +++ b/src/lib/aoc.lua @@ -1,4 +1,5 @@ SRC_PATH = "/aoc/src" +RES_PATH = "/aoc/res" 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} local json = require("json") +local dates = require("dates") +local days = require("days") local today = os.date("*t") local function loadStats(path) @@ -24,52 +27,12 @@ local function loadStats(path) return data 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() - if isBefore(START_DATE, today) then + if dates.isBefore(START_DATE, today) then print("AoC 2025 has not started yet") return end - if isAfter(END_DATE, today) then + if dates.isAfter(END_DATE, today) then print("AoC 2025 has ended") return end @@ -77,8 +40,7 @@ local function printDateInfo() print("Day " .. day .. "/" .. END_DATE.day) end -local function printStats() - local stats = loadStats("aoc/res/stats.json") +local function printStats(stats, selected) local keys = {} for k in pairs(stats) do table.insert(keys, k) @@ -91,7 +53,13 @@ local function printStats() local value = stats[key] local day = tonumber(key:sub(4)) 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) else term.setTextColor(colors.gray) @@ -123,15 +91,42 @@ local function printStats() print() end -local function main() +local function printBanner() term.setTextColor(colors.green) - textutils.slowPrint("+--------------------------------------+", 80) - textutils.slowPrint("| Welcome to the Advent of Code 2025 |", 80) - textutils.slowPrint("+--------------------------------------+", 80) + print("+--------------------------------------+") + print("| Welcome to the Advent of Code 2025 |") + print("+--------------------------------------+") term.setTextColor(colors.white) +end - printDateInfo() - printStats() +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() + 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 main() \ No newline at end of file diff --git a/src/lib/dates.lua b/src/lib/dates.lua new file mode 100644 index 0000000..03754d8 --- /dev/null +++ b/src/lib/dates.lua @@ -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 \ No newline at end of file diff --git a/src/lib/days.lua b/src/lib/days.lua new file mode 100644 index 0000000..58f6172 --- /dev/null +++ b/src/lib/days.lua @@ -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 \ No newline at end of file diff --git a/src/lib/utils.lua b/src/lib/utils.lua new file mode 100644 index 0000000..7a5050d --- /dev/null +++ b/src/lib/utils.lua @@ -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