mirror of
https://github.com/ganelson/inform.git
synced 2024-07-05 16:44:21 +03:00
266 lines
7.7 KiB
Plaintext
266 lines
7.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.
|
|
This 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;
|
|
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 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, is good enough. The square root is used
|
|
only as a sighting shot.
|
|
|
|
@c
|
|
[ CubeRoot num x y n;
|
|
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:Plugins::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);
|
|
}
|
|
];
|