B/timet: Time Template. @Purpose: Support for parsing and printing times of day. @------------------------------------------------------------------------------- @p 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. @c [ RoundOffTime t1 t2; if (t1 >= 0) return ((t1+t2/2)/t2)*t2; return -((-t1+t2/2)/t2)*t2; ]; @p Conversion To Number. @c [ NUMBER_TY_to_TIME_TY n; n = n%1440; if (n < 0) return n + 1440; return n; ]; @p 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 $-2^{31}$, the unique number which cannot be negated in 32-bit twos complement arithmetic. @c [ SquareRoot num op res one n x; if (num < 0) { RunTimeProblem(RTP_NEGATIVEROOT); return 1; } ! Use floating-point ops if available. #ifdef TARGET_GLULX; @gestalt 11 0 n; if (n) { @numtof num x; @sqrt x x; @ftonumz x num; return num; } #endif; op = num; if (num < 0) { RunTimeProblem(RTP_NEGATIVEROOT); return 1; } ! "one" starts at the highest power of four <= the argument. for (one = WORD_NEXTTOHIGHBIT: one > op: one = one/4) ; while (one ~= 0) { ! print "Round: op = ", op, " res = ", res, ", res**2 = ", res*res, " one = ", one, " nthb = ", WORD_NEXTTOHIGHBIT, "^"; if (op >= res + one) { op = op - res - one; res = res/2 + one; } else { res = res/2; } one = one/4; } ! print "Res is ", res, "^"; return res; ]; @p 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. @c [ CubeRoot num neg x y n; ! Use floating-point ops if available. #ifdef TARGET_GLULX; @gestalt 11 0 n; if (n) { if (num < 0) { neg = true; num = -num; } @numtof num x; @pow x 1051372203 x; ! pow(x, 0.3333) @ftonumz x num; if (neg) return -num; else return num; } #endif; if (num < 0) x = -SquareRoot(-num); else x = SquareRoot(num); for (n=0: (y ~= x) && (n++ < 100): y = x, x = (2*x + num/x/x)/3) ; return x; ]; @p Digital Printing. For instance, "2:06 am". @c [ PrintTimeOfDay t h aop; if (t<0) { print ""; return; } if (t >= TWELVE_HOURS) { aop = "pm"; t = t - TWELVE_HOURS; } else aop = "am"; h = t/ONE_HOUR; if (h==0) h=12; print h, ":"; if (t%ONE_HOUR < 10) print "0"; print t%ONE_HOUR, " ", (string) aop; ]; @p Analogue Printing. For instance, "six minutes past two". @c [ PrintTimeOfDayEnglish t h m dir aop; h = (t/ONE_HOUR) % 12; m = t%ONE_HOUR; if (h==0) h=12; if (m==0) { print (number) h, " o'clock"; return; } dir = "past"; if (m > HALF_HOUR) { m = ONE_HOUR-m; h = (h+1)%12; if (h==0) h=12; dir = "to"; } switch(m) { QUARTER_HOUR: print "quarter"; HALF_HOUR: print "half"; default: print (number) m; if (m%5 ~= 0) { if (m == 1) print " minute"; else print " minutes"; } } print " ", (string) dir, " ", (number) h; ]; @p 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. @c [ TIME_TOKEN first_word second_word at length flag illegal_char offhour hr mn i original_wn; original_wn = wn; {-call:PL::Parsing::Tokens::Values::time} wn = original_wn; first_word = NextWordStopped(); switch (first_word) { 'midnight': parsed_number = 0; return GPR_NUMBER; 'midday', 'noon': parsed_number = TWELVE_HOURS; return GPR_NUMBER; } ! Next try the format 12:02 at = WordAddress(wn-1); length = WordLength(wn-1); for (i=0: ii) { ':': if (flag == false && i>0 && i 5 || illegal_char) flag = false; if (flag) { for (i=0: at->i~=':': i++, hr=hr*10) hr = hr + at->i - '0'; hr = hr/10; for (i++: ii - '0'; mn = mn/10; second_word = NextWordStopped(); parsed_number = HoursMinsWordToTime(hr, mn, second_word); if (parsed_number == -1) return GPR_FAIL; if (second_word ~= 'pm' or 'am') wn--; return GPR_NUMBER; } ! Lastly the wordy format offhour = -1; if (first_word == 'half') offhour = HALF_HOUR; if (first_word == 'quarter') offhour = QUARTER_HOUR; if (offhour < 0) offhour = TryNumber(wn-1); if (offhour < 0 || offhour >= ONE_HOUR) return GPR_FAIL; second_word = NextWordStopped(); switch (second_word) { ! "six o'clock", "six" 'o^clock', 'am', 'pm', -1: hr = offhour; if (hr > 12) return GPR_FAIL; ! "quarter to six", "twenty past midnight" 'to', 'past': mn = offhour; hr = TryNumber(wn); if (hr <= 0) { switch (NextWordStopped()) { 'noon', 'midday': hr = 12; 'midnight': hr = 0; default: return GPR_FAIL; } } if (hr >= 13) return GPR_FAIL; if (second_word == 'to') { mn = ONE_HOUR-mn; hr--; if (hr<0) hr=23; } wn++; second_word = NextWordStopped(); ! "six thirty" default: hr = offhour; mn = TryNumber(--wn); if (mn < 0 || mn >= ONE_HOUR) return GPR_FAIL; wn++; second_word = NextWordStopped(); } parsed_number = HoursMinsWordToTime(hr, mn, second_word); if (parsed_number < 0) return GPR_FAIL; if (second_word ~= 'pm' or 'am' or 'o^clock') wn--; return GPR_NUMBER; ]; [ HoursMinsWordToTime hour minute word x; if (hour >= 24) return -1; if (minute >= ONE_HOUR) return -1; x = hour*ONE_HOUR + minute; if (hour >= 13) return x; x = x % TWELVE_HOURS; if (word == 'pm') x = x + TWELVE_HOURS; if (word ~= 'am' or 'pm' && hour == 12) x = x + TWELVE_HOURS; return x; ]; @p 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. @c [ RELATIVE_TIME_TOKEN first_word second_word offhour mult mn original_wn; original_wn = wn; wn = original_wn; first_word = NextWordStopped(); wn--; if (first_word == 'an' or 'a//') mn=1; else mn=TryNumber(wn); if (mn == -1000) { first_word = NextWordStopped(); if (first_word == 'half') offhour = HALF_HOUR; if (first_word == 'quarter') offhour = QUARTER_HOUR; if (offhour > 0) { second_word = NextWordStopped(); if (second_word == 'of') second_word = NextWordStopped(); if (second_word == 'an') second_word = NextWordStopped(); if (second_word == 'hour') { parsed_number = offhour; return GPR_NUMBER; } } return GPR_FAIL; } wn++; first_word = NextWordStopped(); switch (first_word) { 'minutes', 'minute': mult = 1; 'hours', 'hour': mult = 60; default: return GPR_FAIL; } parsed_number = mn*mult; if (mult == 60) { mn=TryNumber(wn); if (mn ~= -1000) { wn++; first_word = NextWordStopped(); if (first_word == 'minutes' or 'minute') parsed_number = parsed_number + mn; else wn = wn - 2; } } return GPR_NUMBER; ]; @p During Scene Matching. @c [ DuringSceneMatching prop sc; for (sc=0: scsc == 1) && (prop(sc+1))) rtrue; rfalse; ]; @p Scene Questions. @c [ SceneUtility sc task; if (sc <= 0) return 0; if (task == 1 or 2) { if (scene_endings-->(sc-1) == 0) return RunTimeProblem(RTP_SCENEHASNTSTARTED, sc); } else { if (scene_endings-->(sc-1) <= 1) return RunTimeProblem(RTP_SCENEHASNTENDED, sc); } switch (task) { 1: return (the_time - scene_started-->(sc-1))%(TWENTY_FOUR_HOURS); 2: return scene_started-->(sc-1); 3: return (the_time - scene_ended-->(sc-1))%(TWENTY_FOUR_HOURS); 4: return scene_ended-->(sc-1); } ];