mirror of
https://github.com/ganelson/inform.git
synced 2024-07-09 02:24:21 +03:00
303 lines
8.7 KiB
Plaintext
303 lines
8.7 KiB
Plaintext
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 "<no time>"; 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: i<length: i++) {
|
|
switch (at->i) {
|
|
':': if (flag == false && i>0 && i<length-1) flag = true;
|
|
else illegal_char = true;
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9': ;
|
|
default: illegal_char = true;
|
|
}
|
|
}
|
|
if (length < 3 || length > 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++: i<length: i++, mn=mn*10) mn = mn + at->i - '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: sc<NUMBER_SCENES_CREATED: sc++)
|
|
if ((scene_status-->sc == 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);
|
|
}
|
|
];
|