Files
kiss-vscode/projects/habit-puzzle-game/source/HabitModel.kiss

163 lines
8.0 KiB
Plaintext

(prop :Array<Entry> dailyEntries [])
(prop :Array<Entry> intervalEntries [])
(prop :Array<Entry> bonusEntries [])
(prop :Array<Entry> todoEntries [])
(prop :Array<RewardFile> rewardFiles [])
(defNew [&prop :String textFile]
(let [s (Stream.fromFile textFile)
&mut lastHeader ""]
// TODO could be whileLet
(loop
(case (s.takeLine)
((Some "DAILY")
(set lastHeader "DAILY"))
((Some "INTERVAL")
(set lastHeader "INTERVAL"))
((Some "BONUS")
(set lastHeader "BONUS"))
((Some "TODO")
(set lastHeader "TODO"))
((Some "FILES")
(set lastHeader "FILES"))
((when (apply = (concat ["-"] (line.split ""))) (Some line))
(continue))
((Some "") (continue))
// Types won't unify with the next case, so this is its own:
((when (= lastHeader "FILES") (Some line))
(rewardFiles.push
(let [parts (line.split " ")
startingPoints (Std.parseInt (parts.pop))
path (parts.join " ")]
(objectWith path startingPoints))))
((Some line)
(.push
(case lastHeader
("DAILY" dailyEntries)
("INTERVAL" intervalEntries)
("BONUS" bonusEntries)
("TODO" todoEntries)
(otherwise (throw "bad header")))
(object
type
(case lastHeader
("BONUS" Bonus)
("TODO" Todo)
("DAILY"
(case (line.split ":")
([noColon]
(Daily
// all days of week
(collect (range 7))
// never done before
""))
([::&mut preColon ...afterColon]
(set line (afterColon.join ":"))
(Daily
// Days of week specified by abbreviation:
(sort (filter
[
// disambiguate Th from T and Su from S:
(when (contains preColon "Th") {(set preColon (StringTools.replace preColon "Th" "")) 4})
(when (contains preColon "Su") {(set preColon (StringTools.replace preColon "Su" "")) 0})
(when (contains preColon "M") 1)
(when (contains preColon "T") 2)
(when (contains preColon "W") 3)
(when (contains preColon "F") 5)
(when (contains preColon "S") 6)
]))
// Last date completed after that:
(ifLet [[days date] (preColon.split " ")]
date
"")))
(otherwise (throw "bad line"))))
("INTERVAL"
(case (line.split ":")
([::&mut preColon ...afterColon]
(set line (afterColon.join ":"))
(case (preColon.split " ")
([days]
(Interval (Std.parseInt days) ""))
([days lastDayDone]
(Interval (Std.parseInt days) lastDayDone))
(otherwise (throw "bad line"))))
(otherwise (throw "bad line"))))
(otherwise (throw "bad header")))
labels
(for l (line.split "/")
(object
label (StringTools.trim (StringTools.replace l "|" ""))
points (count (l.split "") ->c (= c "|")))))))
(otherwise (break))))))
(method :Int totalPoints []
(apply + (for l (flatten (for e (the Array<Entry> (concat dailyEntries intervalEntries bonusEntries todoEntries)) e.labels)) l.points)))
(function :String stringify [:Entry e]
"$(ifLet [(Daily days lastDayDone) e.type]
(+
(.join (for day days
(case day
(0 "Su")
(1 "M")
(2 "T")
(3 "W")
(4 "Th")
(5 "F")
(6 "S")
(otherwise (throw "bad day")))) "")
" "
lastDayDone
": ")
(ifLet [(Interval days lastDayDone) e.type]
"$days ${lastDayDone}: "
""))$(.join (for label e.labels
"${label.label} $(* "|" label.points)") "/")")
(function :String stringifyRewardFile [:RewardFile rewardFile]
"${rewardFile.path} ${rewardFile.startingPoints}")
(method :Void save []
(localVar &mut content "DAILY\n-----\n")
(+= content (.join (map dailyEntries stringify) "\n") "\n")
(+= content "\nINTERVAL\n--------\n")
(+= content (.join (map intervalEntries stringify) "\n") "\n")
(+= content "\nBONUS\n-----\n")
(+= content (.join (map bonusEntries stringify) "\n") "\n")
(+= content "\nTODO\n----\n")
(+= content (.join (map todoEntries stringify) "\n") "\n")
(+= content "\nFILES\n-----\n")
(+= content (.join (map rewardFiles stringifyRewardFile) "\n") "\n")
(File.saveContent textFile
content))
// With rotating entries, the active one is the first one with the lowest score:
(function :EntryLabel activeLabel [:Entry e]
(let [lowScore (apply min (for label e.labels label.points))]
(doFor label e.labels (when (= lowScore label.points) (return label)))
(throw "no active?!")))
(function todayString []
(let [d (Date.now)] "$(d.getDate)-$(+ 1 (d.getMonth))-$(d.getFullYear)"))
(function isActive [:Entry e]
(case e.type
((Daily days lastDayDone)
(and !(= lastDayDone (todayString)) (contains days (.getDay (Date.now)))))
((Interval days lastDayDone)
(or !lastDayDone (<= days #|(DateTime.fromDate(Date.now()) - DateTime.fromString(lastDayDone)).getTotalDays()|#)))
(Todo (= 0 .points (activeLabel e)))
(otherwise true)))
(method :Array<Entry> activeDailyEntries []
(filter dailyEntries isActive))
(method :Array<Entry> activeIntervalEntries []
(filter intervalEntries isActive))
(method :Array<Entry> activeBonusEntries []
(filter bonusEntries isActive))
(method :Array<Entry> activeTodoEntries []
(filter todoEntries isActive))