-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrpgcalendar.py
377 lines (317 loc) · 12.7 KB
/
rpgcalendar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
'''Simple RPG calendar, with 7 day week, 4 weeks per month, 3 months per season, 4 seasons per year.
The calendar starts at year zero by default. The lunar phase calculation reflects that. Year 0, 4, 8, ... are waxing moons. There are 743*4 = 336 days per year in this Calendar, ~92% of a year.
Seasons line up with the numbers of the months and as such it's more readable. The calendar starts on year zero, which may not be preferred by some. That said, I am tracking double lunar phases here (1/4 of a cycle, rather than 1/8).
The lunar phases are by default weekly, though for my own game I created the option of having a lunar phase that operated yearly instead.
There are a bunch of reporting options, including those easily machine- readable and reports that are more immersive. Some are below.
Day 2, Week 4, Month 1, Year 1. Lunar Phase: Full.
It is the 23rd of Springswax, of the year 1. It is Spring and the moon is full.
It is the 2nd day of the 4th week of the 1st month of Spring. The moon is full.
2,4,1,1,1,2
The choice of "back" for "backwards" is for brevity.'''
_lunar_phases = {1:'waxing', 2:'full', 3:'waning', 4:'new'}
_seasons = {1:'spring', 2:'summer', 3:'fall', 4:'winter'}
_months = {1:'springswax', 2:'springtide', 3:'springswane',
4:'summerswax', 5:'summertide', 6:'summerswane',
7:'fallswax', 8:'falltide', 9:'fallswane',
10:'winterswax', 11:'wintertide', 12:'winterswane'}
_days_per_week = 7
_weeks_per_month = 4
_months_per_year = len(_months)
_seasons_per_year = len(_seasons)
_lunar_phases_per_cycle = len(_lunar_phases)
def ordinal(num=0):
'Returns ordinal number, e.g. 21st, from integer.'
ord_end = num % 10
if ord_end == 1:
return str(num) + 'st'
elif ord_end == 2:
return str(num) + 'nd'
elif ord_end == 3:
return str(num) + 'rd'
else:
return str(num) + 'th'
class Calendar:
def __init__(self, day=1, week=1, month=1, year=0):
self.day = day
self.week = week
self.month = month
self.year = year
self.update_season()
self.update_lunar_phase()
def forward_year(self):
self.year += 1
# Do not remove: called here to apply to the YearlylunarPhase class.
self.update_lunar_phase()
def forward_years(self, years=1):
self.year += years
self.update_lunar_phase()
def back_year(self):
self.year -= 1
def back_years(self, years=1):
self.year -= years
def forward_lunar_phase(self):
'''Weekly lunar phases, puts you at start of next week.'''
self.day = 1
self.forward_week()
def back_lunar_phase(self):
'''Weekly lunar phases, puts you at start of previous week.'''
self.day = 1
self.back_week()
def update_lunar_phase(self):
# Because both week and lunar phase start at 1, modular
# division results in zero modulus in the 4th lunar phase.
self.lunar_phase = self.week % _lunar_phases_per_cycle
if self.lunar_phase == 0:
self.lunar_phase = 4
def day_of_month(self):
'''Returns the day of the month, as per normal calendar.'''
# Weeks starts at 1
return (self.week - 1) * _days_per_week + self.day
def back_week(self):
self.week -= 1
if self.week < 1:
self.week = _weeks_per_month
self.back_month()
self.update_lunar_phase()
def back_weeks(self, weeks=1):
for w in range(weeks):
self.back_week()
def forward_week(self):
self.week += 1
if self.week > _weeks_per_month:
self.week = 1
self.forward_month()
self.update_lunar_phase()
def forward_weeks(self, weeks=1):
for w in range(weeks):
self.forward_week()
def back_day(self):
self.day -= 1
if self.day < 1:
self.day = _days_per_week
self.back_week()
def back_days(self, days=1):
for d in range(days):
self.back_day()
def back_month(self):
self.month -= 1
if self.month < 1:
self.month = _months_per_year
self.back_year()
self.update_season()
def back_months(self, months=1):
for m in range(months):
self.back_month()
def forward_season(self):
'''This is akin to moving forward to the start of next season,
rather than moving forward three months.'''
self.day = 1
self.week = 1
self.lunar_phase = 1
if self.season == 1:
self.month = 4
elif self.season == 2:
self.month = 7
elif self.season == 3:
self.month = 10
else:
self.month = 1
self.forward_year()
self.update_season()
def back_season(self):
'''Go to the start of the previous season.'''
self.day = 1
self.week = 1
self.lunar_phase = 1
if self.season == 1:
self.month = 10
self.back_year()
elif self.season == 2:
self.month = 1
elif self.season == 3:
self.month = 4
else:
self.month = 7
self.update_season()
def forward_day(self):
self.day += 1
if self.day > _days_per_week:
self.day = 1
self.forward_week()
def forward_days(self, days=1):
for d in range(days):
self.forward_day()
def forward_month(self):
self.month += 1
if self.month > _months_per_year:
self.month = 1
self.forward_year()
self.update_season()
def forward_months(self, months=1):
for m in range(months):
self.forward_month()
def update_season(self):
if self.month in [1,2,3]:
self.season = 1
elif self.month in [4,5,6]:
self.season = 2
elif self.month in [7,8,9]:
self.season = 3
else:
self.season = 4
def report_long_and_tall(self):
rep = f'Day: {self.day}\nWeek: {self.week}\nMonth: {self.month}\n'
rep += f'Year: {self.year}\nSeason: {self.season}\n'
rep += f'Lunar Phase: {self.lunar_phase}\n\n'
return rep
def report_one_liner(self):
# Capitalizes the lunar phase.
rep = f'Day {self.day}, Week {self.week}, Month {self.month}, ' + \
f'Year {self.year}. ' + \
f'Lunar Phase: {_lunar_phases[self.lunar_phase].title()}.'
return rep
def report_immersive(self):
'''Provides descriptive one-liner report of date.'''
dayout = ordinal(self.day_of_month())
monthout = _months[self.month].title()
seasonout = _seasons[self.season].title()
moonout = _lunar_phases[self.lunar_phase]
rep = f'It is the {dayout} of {monthout}, of the year ' + \
f'{self.year}. It is {seasonout} and the moon is {moonout}.'
return rep
def month_of_season(self):
months_per_season = _months_per_year // _seasons_per_year
assert _months_per_year % _seasons_per_year == 0, \
'Strange number of months in a year, vs. seasons.'
return self.month % months_per_season
def report_farmers(self):
'''Provides descriptive one-liner report of date.'''
dayout = ordinal(self.day)
weekout = ordinal(self.week)
monthout = ordinal(self.month_of_season())
seasonout = _seasons[self.season].title()
moonout = _lunar_phases[self.lunar_phase]
rep = f'It is the {dayout} day of the {weekout} week of the ' + \
f'{monthout} month of {seasonout}. The moon is {moonout}.'
return rep
def report_machine(self):
'''Hopefully this is most convenient for use with other programs.'''
return f'{self.day},{self.week},{self.month},{self.year},' + \
f'{self.season},{self.lunar_phase}'
def report_machine_DOM(self):
'''This uses day of month.'''
return f'{self.day_of_month()},{self.week},{self.month},{self.year},' + \
f'{self.season},{self.lunar_phase}'
def report_machine_high_first(self):
'''Hopefully this will result in easier sorting when used with a
database. It is high to low, though I'm putting the season before the lunar
phase, and both of those are at the end.'''
return f'{self.year},{self.month},{self.week},{self.day},' + \
f'{self.season},{self.lunar_phase}'
def report(self):
return self.report_machine_DOM_high_first()
def report_machine_DOM_high_first(self):
'''This uses day of month.'''
return f'{self.year},{self.month},{self.day_of_month()},{self.week},' + \
f'{self.season},{self.lunar_phase}'
def generation_args(self):
'''Use the returned list to generate another calendar, if you want.'''
return [self.day, self.week, self.month, self.year]
def convert_to_gen_with_DOM_high(dom_high):
'''I assume this will be the preferred interaction with DB,
so I'm providing something that will return the args list required to generate
the calendar from this output.'''
# split on commas, take first three entries, convert them to ints.
year, month, day = map(int, dom_high.split(',')[0:3])
# The 7th day is still week 1.
if day % 7 == 0:
week = day // 7
day = 7
else:
week = day // 7 + 1
day %= 7
return [day, week, month, year]
def convert(dom_high):
'''Let's call this the default conversion method.'''
return Calendar.convert_to_gen_with_DOM_high(dom_high)
class YearlyLunarPhase(Calendar):
'''In this calendar, a lunar phase lasts an entire year, so there's a
year of waxing moon, a year of full moon, a year of waning moon, then
finally a year of new moon.'''
# There are some unnecessary methods called in the general class,
# e.g. update_lunar_phase() to fit this class, but I think that's fine.
def forward_lunar_phase(self):
'''This is akin to moving forward to the start of next year,
rather than moving forward one years.'''
self.day = 1
self.week = 1
self.month = 1
self.season = 1
self.forward_year()
def back_lunar_phase(self):
'''Go to the start of the previous lunar_phase, essentially
the last year.'''
self.day = 1
self.week = 1
self.month = 1
self.season = 1
self.back_year()
def update_lunar_phase(self):
'''This assumes that the year starts at year zero, currently.'''
# This works because the year starts at zero and lunar
# phase starts at 1.
self.lunar_phase = self.year % _lunar_phases_per_cycle + 1
def test_normal_calendar():
c = Calendar()
print(c.report_immersive())
c.forward_days(6)
print(c.report_farmers())
c.forward_day()
print(c.report_immersive())
c.forward_week()
print(c.report_immersive())
c.forward_week()
print(c.report_immersive())
c.forward_year()
print(c.report_immersive())
c.forward_lunar_phase()
print(c.report_immersive())
c.forward_lunar_phase()
print(c.report_immersive())
c.forward_season()
print(c.report_immersive())
c.back_weeks(2)
print(c.report_immersive())
print(c.report_machine())
print(c.report_machine_DOM())
print(c.report_machine_high_first())
print(c.report_machine_DOM_high_first())
t = c.report_machine_DOM_high_first()
t = Calendar.convert_to_gen_with_DOM_high(t)
print(t)
t2 = Calendar(*Calendar.convert(c.report()))
print(t2.report())
def test_odd_calendar():
c = YearlyLunarPhase()
print(c.report_long_and_tall())
c.forward_days(8)
print(c.report_long_and_tall())
c.forward_weeks(2)
print(c.report_long_and_tall())
print(c.day_of_month())
c.forward_months(4)
print(c.report_long_and_tall())
c.forward_months(8)
print(c.report_long_and_tall())
print(c.report_one_liner())
print(c.report_immersive())
print(c.report_farmers())
print(c.report_machine())
print(c.report_machine_DOM())
print(c.generation_args())
c.forward_years(2)
print(c.generation_args())
print(c.report_farmers())
if __name__ == '__main__':
## test_odd_calendar()
test_normal_calendar()