Time contents
Rounding.
The following rounds a numerical value t1 to the nearest unit of t2; for instance, if t2 is 5 then it rounds to the nearest 5. The name is an anachronism, as it's used for all kinds of value.
13[ RoundOffTime t1 t2;
14 if (t1 >= 0) return ((t1+t2/2)/t2)*t2;
15 return -((-t1+t2/2)/t2)*t2;
16];
21[ NUMBER_TY_to_TIME_TY n;
22 n = n%1440;
23 if (n < 0) return n + 1440;
24 return n;
25];
Square Root.
Although this routine performs integer square root, it does so using Glulx's floating-point operations if available (with code contributed by Andrew Plotkin): this is fast and remains accurate up to about 16 million.
The slower integer method is an old algorithm for extracting binary square roots, taking 2 bits at a time. We start out with one at the highest bit which isn't the sign bit; that used to be worked out as WORD_HIGHBIT/2, but this caused unexpected portability problems (exposing a minor bug in Inform and also glulxe) because of differences in how C compilers handle signed division of constants in the case where the dividend is -231, the unique number which cannot be negated in 32-bit twos complement arithmetic.
41[ SquareRoot num
42 op res one n x;
43 if (num < 0) { RunTimeProblem(RTP_NEGATIVEROOT); return 1; }
44
45
46 #ifdef TARGET_GLULX;
47 @gestalt 11 0 n;
48 if (n) {
49 @numtof num x;
50 @sqrt x x;
51 @ftonumz x num;
52 return num;
53 }
54 #endif;
55 op = num;
56 if (num < 0) { RunTimeProblem(RTP_NEGATIVEROOT); return 1; }
57
58 for (one = WORD_NEXTTOHIGHBIT: one > op: one = one/4) ;
59
60 while (one ~= 0) {
61
62 if (op >= res + one) {
63 op = op - res - one;
64 res = res/2 + one;
65 } else {
66 res = res/2;
67 }
68 one = one/4;
69 }
70
71 return res;
72];
Cube Root.
The following, again, uses floating-point arithmetic if it's available: this is fast and gives good accuracy for smallish numbers, but limited precision begins to tell at around 2000000.
The alternative is an iterative scheme for finding cube roots by Newton-Raphson approximation, not a great method but which, on the narrow ranges of integers we deal with, just about good enough. The square root is used only as a sighting shot.
85[ CubeRoot num neg x y n;
86
87 #ifdef TARGET_GLULX;
88 @gestalt 11 0 n;
89 if (n) {
90 if (num < 0) {
91 neg = true;
92 num = -num;
93 }
94 @numtof num x;
95 @pow x 1051372203 x;
96 @ftonumz x num;
97 if (neg)
98 return -num;
99 else
100 return num;
101 }
102 #endif;
103 if (num < 0) x = -SquareRoot(-num); else x = SquareRoot(num);
104 for (n=0: (y ~= x) && (n++ < 100): y = x, x = (2*x + num/x/x)/3) ;
105 return x;
106];
Digital Printing.
For instance, "2:06 am".
112[ PrintTimeOfDay t h aop;
113 if (t<0) { print "<no time>"; return; }
114 if (t >= TWELVE_HOURS) { aop = "pm"; t = t - TWELVE_HOURS; } else aop = "am";
115 h = t/ONE_HOUR; if (h==0) h=12;
116 print h, ":";
117 if (t%ONE_HOUR < 10) print "0"; print t%ONE_HOUR, " ", (string) aop;
118];
Analogue Printing.
For instance, "six minutes past two".
124[ PrintTimeOfDayEnglish t h m dir aop;
125 h = (t/ONE_HOUR) % 12; m = t%ONE_HOUR; if (h==0) h=12;
126 if (m==0) { print (number) h, " oclock"; return; }
127 dir = "past";
128 if (m > HALF_HOUR) { m = ONE_HOUR-m; h = (h+1)%12; if (h==0) h=12; dir = "to"; }
129 switch(m) {
130 QUARTER_HOUR: print "quarter"; HALF_HOUR: print "half";
131 default: print (number) m;
132 if (m%5 ~= 0) {
133 if (m == 1) print " minute"; else print " minutes";
134 }
135 }
136 print " ", (string) dir, " ", (number) h;
137];
Understanding.
This I6 grammar token converts words in the player's command to a valid I7 time, and is heavily based on the one presented as a solution to an exercise in the DM4.
145[ TIME_TOKEN first_word second_word at length flag
146 illegal_char offhour hr mn i original_wn;
147 original_wn = wn;
148{-call:PL::Parsing::Tokens::Values::time}
149 wn = original_wn;
150 first_word = NextWordStopped();
151 switch (first_word) {
152 'midnight': parsed_number = 0; return GPR_NUMBER;
153 'midday', 'noon': parsed_number = TWELVE_HOURS;
154 return GPR_NUMBER;
155 }
156
157 at = WordAddress(wn-1); length = WordLength(wn-1);
158 for (i=0: i<length: i++) {
159 switch (at->i) {
160 ':': if (flag == false && i>0 && i<length-1) flag = true;
161 else illegal_char = true;
162 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': ;
163 default: illegal_char = true;
164 }
165 }
166 if (length < 3 || length > 5 || illegal_char) flag = false;
167 if (flag) {
168 for (i=0: at->i~=':': i++, hr=hr*10) hr = hr + at->i - '0';
169 hr = hr/10;
170 for (i++: i<length: i++, mn=mn*10) mn = mn + at->i - '0';
171 mn = mn/10;
172 second_word = NextWordStopped();
173 parsed_number = HoursMinsWordToTime(hr, mn, second_word);
174 if (parsed_number == -1) return GPR_FAIL;
175 if (second_word ~= 'pm' or 'am') wn--;
176 return GPR_NUMBER;
177 }
178
179 offhour = -1;
180 if (first_word == 'half') offhour = HALF_HOUR;
181 if (first_word == 'quarter') offhour = QUARTER_HOUR;
182 if (offhour < 0) offhour = TryNumber(wn-1);
183 if (offhour < 0 || offhour >= ONE_HOUR) return GPR_FAIL;
184 second_word = NextWordStopped();
185 switch (second_word) {
186
187 'o^clock', 'am', 'pm', -1:
188 hr = offhour; if (hr > 12) return GPR_FAIL;
189
190 'to', 'past':
191 mn = offhour; hr = TryNumber(wn);
192 if (hr <= 0) {
193 switch (NextWordStopped()) {
194 'noon', 'midday': hr = 12;
195 'midnight': hr = 0;
196 default: return GPR_FAIL;
197 }
198 }
199 if (hr >= 13) return GPR_FAIL;
200 if (second_word == 'to') {
201 mn = ONE_HOUR-mn; hr--; if (hr<0) hr=23;
202 }
203 wn++; second_word = NextWordStopped();
204
205 default:
206 hr = offhour; mn = TryNumber(--wn);
207 if (mn < 0 || mn >= ONE_HOUR) return GPR_FAIL;
208 wn++; second_word = NextWordStopped();
209 }
210 parsed_number = HoursMinsWordToTime(hr, mn, second_word);
211 if (parsed_number < 0) return GPR_FAIL;
212 if (second_word ~= 'pm' or 'am' or 'o^clock') wn--;
213 return GPR_NUMBER;
214];
215
216[ HoursMinsWordToTime hour minute word x;
217 if (hour >= 24) return -1;
218 if (minute >= ONE_HOUR) return -1;
219 x = hour*ONE_HOUR + minute; if (hour >= 13) return x;
220 x = x % TWELVE_HOURS; if (word == 'pm') x = x + TWELVE_HOURS;
221 if (word ~= 'am' or 'pm' && hour == 12) x = x + TWELVE_HOURS;
222 return x;
223];
Relative Time Token.
"Time" is an interesting kind of value since it can hold two conceptually different ways of thinking about time: absolute times, such as "12:03 PM", and also relative times, like "ten minutes". For parsing purposes, these are completely different from each other, and the time token above handles only absolute times; we need the following for relative ones.
233[ RELATIVE_TIME_TOKEN first_word second_word offhour mult mn original_wn;
234 original_wn = wn;
235 wn = original_wn;
236
237 first_word = NextWordStopped(); wn--;
238 if (first_word == 'an' or 'a//') mn=1; else mn=TryNumber(wn);
239
240 if (mn == -1000) {
241 first_word = NextWordStopped();
242 if (first_word == 'half') offhour = HALF_HOUR;
243 if (first_word == 'quarter') offhour = QUARTER_HOUR;
244 if (offhour > 0) {
245 second_word = NextWordStopped();
246 if (second_word == 'of') second_word = NextWordStopped();
247 if (second_word == 'an') second_word = NextWordStopped();
248 if (second_word == 'hour') {
249 parsed_number = offhour;
250 return GPR_NUMBER;
251 }
252 }
253 return GPR_FAIL;
254 }
255 wn++;
256
257 first_word = NextWordStopped();
258 switch (first_word) {
259 'minutes', 'minute': mult = 1;
260 'hours', 'hour': mult = 60;
261 default: return GPR_FAIL;
262 }
263 parsed_number = mn*mult;
264 if (mult == 60) {
265 mn=TryNumber(wn);
266 if (mn ~= -1000) {
267 wn++;
268 first_word = NextWordStopped();
269 if (first_word == 'minutes' or 'minute')
270 parsed_number = parsed_number + mn;
271 else wn = wn - 2;
272 }
273 }
274 return GPR_NUMBER;
275];
280[ DuringSceneMatching prop sc;
281 for (sc=0: sc<NUMBER_SCENES_CREATED: sc++)
282 if ((scene_status-->sc == 1) && (prop(sc+1))) rtrue;
283 rfalse;
284];
289[ SceneUtility sc task;
290 if (sc <= 0) return 0;
291 if (task == 1 or 2) {
292 if (scene_endings-->(sc-1) == 0) return RunTimeProblem(RTP_SCENEHASNTSTARTED, sc);
293 } else {
294 if (scene_endings-->(sc-1) <= 1) return RunTimeProblem(RTP_SCENEHASNTENDED, sc);
295 }
296 switch (task) {
297 1: return (the_time - scene_started-->(sc-1))%(TWENTY_FOUR_HOURS);
298 2: return scene_started-->(sc-1);
299 3: return (the_time - scene_ended-->(sc-1))%(TWENTY_FOUR_HOURS);
300 4: return scene_ended-->(sc-1);
301 }
302];