001  (ns cc.journeyman.the-great-game.time
002    (:require [clojure.string :as s]))
003  
004  (def game-start-time
005    "The start time of this run."
006    (System/currentTimeMillis))
007  
008  (def ^:const game-day-length
009    "The Java clock advances in milliseconds, which is fine.
010    But we need game-days to be shorter than real world days.
011    A Witcher 3 game day is 1 hour 36 minutes, or 96 minutes, which is
012    presumably researched. Round it up to 100 minutes for easier
013    calculation."
014    (* 100          ;; minutes per game day
015       60           ;; seconds per minute
016       1000))       ;; milliseconds per second
017  
018  (defn now
019    "For now, we'll use Java timestamp for time; ultimately, we need a
020    concept of game-time which allows us to drive day/night cycle, seasons,
021    et cetera, but what matters about time is that it is a value which
022    increases."
023    []
024    (System/currentTimeMillis))
025  
026  (def ^:const canonical-ordering-of-houses
027    "The canonical ordering of religious houses."
028    [:eye
029     :foot
030     :nose
031     :hand
032     :ear
033     :mouth
034     :stomach
035     :furrow
036     :plough])
037  
038  (def ^:const days-of-week
039    "The eight-day week of the game world. This differs from the canonical
040    ordering of houses in that it omits the eye."
041    (rest canonical-ordering-of-houses))
042  
043  (def ^:const days-in-week
044    "This world has an eight day week."
045    (count days-of-week))
046  
047  (def ^:const seasons-of-year
048    "The ordering of seasons in the year is different from the canonical
049    ordering of the houses, for reasons of the agricultural cycle."
050    [:foot
051     :nose
052     :hand
053     :ear
054     :mouth
055     :stomach
056     :plough
057     :furrow
058     :eye])
059  
060  (def ^:const seasons-in-year
061    "Nine seasons in a year, one for each house (although the order is
062    different."
063    (count seasons-of-year))
064  
065  (def ^:const weeks-of-season
066    "To fit nine seasons of eight day weeks into 365 days, each must be of
067    five weeks."
068    [:first :second :third :fourth :fifth])
069  
070  (def ^:const weeks-in-season
071    "To fit nine seasons of eight day weeks into 365 days, each must be of
072    five weeks."
073    (count weeks-of-season))
074  
075  (def ^:const days-in-season
076    (* weeks-in-season days-in-week))
077  
078  (defn game-time
079    "With no arguments, the current game time. If a Java `timestamp` value is
080    passed (as a `long`), the game time represented by that value."
081    ([] (game-time (now)))
082    ([timestamp]
083     (- timestamp game-start-time)))
084  
085  (defmacro day-of-year
086    "The day of the year represented by this `game-time`, ignoring leap years."
087    [game-time]
088    `(mod (long (/ ~game-time game-day-length)) 365))
089  
090  (def waiting-day?
091    "Does this `game-time` represent a waiting day?"
092    (memoize
093      ;; we're likely to call this several times in quick succession on the
094      ;; same timestamp
095      (fn [game-time]
096          (>=
097            (day-of-year game-time)
098            (* seasons-in-year weeks-in-season days-in-week)))))
099  
100  (defn day
101    "Day of the eight-day week represented by this `game-time`."
102    [game-time]
103    (let [day-of-week (mod (day-of-year game-time) days-in-week)]
104      (if (waiting-day? game-time)
105        (nth weeks-of-season day-of-week)
106        (nth days-of-week day-of-week))))
107  
108  (defn week
109    "Week of season represented by this `game-time`."
110    [game-time]
111    (let [day-of-season (mod (day-of-year game-time) days-in-season)
112          week (/ day-of-season days-in-week)]
113      (if (waiting-day? game-time)
114        :waiting
115        (nth weeks-of-season week))))
116  
117  (defn season
118    [game-time]
119    (let [season (int (/ (day-of-year game-time) days-in-season))]
120      (if (waiting-day? game-time)
121        :waiting
122        (nth seasons-of-year season))))
123  
124  (defn date-string
125    "Return a correctly formatted date for this `game-time` in the calendar of
126    the Great Place."
127    [game-time]
128    (s/join
129      " "
130      (if
131        (waiting-day? game-time)
132        [(s/capitalize
133           (name
134             (nth
135               weeks-of-season
136               (mod (day-of-year game-time) days-in-week))))
137         "waiting day"]
138        [(s/capitalize (name (week game-time)))
139         (s/capitalize (name (day game-time)))
140         "of the"
141         (s/capitalize (name (season game-time)))])))
142  
143  
144