| | 1 | /* |
| | 2 | * Copyright 2003, 2006 Michael J. Roberts |
| | 3 | * |
| | 4 | * "Subjective Time" module. This implements a form of in-game |
| | 5 | * time-keeping that attempts to mimic the player's subjective |
| | 6 | * experience of time passing in the scenario while still allowing for |
| | 7 | * occasional, reasonably precise time readings, such as from a |
| | 8 | * wristwatch in the game world. |
| | 9 | * |
| | 10 | * It's always been difficult to handle the passage of time in |
| | 11 | * interactive fiction. It's such a vexing problem that most IF avoids |
| | 12 | * the problem entirely by setting the game action in a world that |
| | 13 | * forgot time, where there are no working clocks and where night never |
| | 14 | * falls. Like so many of the hard parts of interactive fiction, the |
| | 15 | * difficulty here comes from the uneasy union of simulation and |
| | 16 | * narrative that's at the heart of all IF. |
| | 17 | * |
| | 18 | * In ordinary static narratives, the passage of time can swing between |
| | 19 | * extremes: a scene that only takes moments of real-time within the |
| | 20 | * story, such as an intense action scene or an extended inner |
| | 21 | * monologue, can go on for many pages; and elsewhere in the same story, |
| | 22 | * days and years can pass in the space of a sentence or two. Narrative |
| | 23 | * time has no relationship to the length of a passage of text; |
| | 24 | * narrative time is related only to the density of story-significant |
| | 25 | * action. |
| | 26 | * |
| | 27 | * It might seem at first glance that this break between in-story time |
| | 28 | * and chapter length is a special privilege of the staticness of a |
| | 29 | * book: the text just sits there on a page, unchanging in time, so of |
| | 30 | * course there's no real-time anchor for the story's internal clock. |
| | 31 | * But that can't be it, because we see exactly the same narrative |
| | 32 | * manipulation of time in film, which is a fundamentally time-based |
| | 33 | * medium. Films don't present their action in real-time any more than |
| | 34 | * books do. What's more, film follows the same rules as text-based |
| | 35 | * fiction in compressing and expanding story-time: many minutes of film |
| | 36 | * can spool by as the last seven seconds tick off a bomb's count-down |
| | 37 | * timer, while the seasons can change in the course of a five-second |
| | 38 | * dissolve. Even Fox's "24," a television series whose entire conceit |
| | 39 | * is that the action is presented in real-time, can be seen on closer |
| | 40 | * inspection to hew to its supposed real-time anchors only every ten |
| | 41 | * minutes or so, where the ads are inserted; in between the ads, the |
| | 42 | * normal bag of tricks is liberally employed to compress and expand |
| | 43 | * time. |
| | 44 | * |
| | 45 | * In simulations, we have a different situation, because the pacing of |
| | 46 | * the story is under the indirect control of the player. (I call the |
| | 47 | * control indirect because it's not presented to the player as a simple |
| | 48 | * control knob with settings for "slower" and "faster." The player can |
| | 49 | * directly control how quickly she enters commands, but this has only a |
| | 50 | * very minor effect on the pace of the story; the major component of |
| | 51 | * the story's pacing is the player's rate of absorbing information from |
| | 52 | * the story and synthesizing solutions to its obstacles. In most |
| | 53 | * cases, this is the gating factor: the player is motivated, by a |
| | 54 | * desire to win the game, to go as fast as possible, but is unable to |
| | 55 | * go any faster because of the need to peform time-consuming mental |
| | 56 | * computation. Thus the player's control is indirect; there's the |
| | 57 | * illusion of a control knob for "faster" and "slower," but in practice |
| | 58 | * the knob's setting is forced to a fixed value, determined by the |
| | 59 | * player's ability to solve puzzles: no player ever wants to choose a |
| | 60 | * "slower" setting than they have to, and the "faster" settings are |
| | 61 | * unavailable because of the gating mental computation factor.) |
| | 62 | * |
| | 63 | * But even so, the player in a simulation does have complete control |
| | 64 | * over the specific actions that the player character performs. The |
| | 65 | * player makes the character walk around, examine things, try a few |
| | 66 | * random verbs on an object, walk around some more, and so on. All of |
| | 67 | * these things clearly ought to take time in the simulation. The |
| | 68 | * question is: how much time? |
| | 69 | * |
| | 70 | * The most obvious approach is to tie the passage of game-time to the |
| | 71 | * actions the player character performs, by making each turn take some |
| | 72 | * amount of time. At the simplest, each turn could take a fixed amount |
| | 73 | * of time, say 1 minute. A more sophisticated approach might be for |
| | 74 | * some kinds of actions to take more time than others; walking to a new |
| | 75 | * room might take 2 minutes, say, while examining a wall might take 30 |
| | 76 | * seconds. Some early IF games took this approach. Infocom's |
| | 77 | * Planetfall, for example, has a cycle of day and night that's tied to |
| | 78 | * the number of turns taken by the player. |
| | 79 | * |
| | 80 | * Unfortunately, such a direct mapping of turns to game-time hasn't |
| | 81 | * proved to be very satisfactory in practice. This approach doesn't |
| | 82 | * seem to make the treatment of time more realistic -- in fact, it's |
| | 83 | * just the opposite. For example, in Planetfall, as objective |
| | 84 | * game-time passes, it's necessary to eat and sleep; the game has a |
| | 85 | * limited supply of food, so if you spend too much time exploring, you |
| | 86 | * can lock yourself out of victory simply by exhausting all available |
| | 87 | * food and starving to death. The workaround, of course, is to restart |
| | 88 | * the game after you've explored enough to know your way around, at |
| | 89 | * which point you can cruise through the parts you've been through |
| | 90 | * before. The end result is that we get two opposite but equally |
| | 91 | * unrealistic treatments of time: on the first pass, far too much |
| | 92 | * objective time passes as we wander around exploring, given how little |
| | 93 | * we accomplish; and on the second pass, far too little objective time |
| | 94 | * elapses, given how rapidly we progress through the story. |
| | 95 | * |
| | 96 | * One problem would seem to be that verbs are far too coarse a means to |
| | 97 | * assign times to actions: picking up five small items off a table |
| | 98 | * might take almost no time at all, while picking up the table itself |
| | 99 | * could require a minute or two; walking to the next room might take |
| | 100 | * ten seconds, while walking across an airport terminal takes ten |
| | 101 | * minutes. An author could in principle assign timings to actions at |
| | 102 | * the level of individual verb-object combinations, but this would |
| | 103 | * obviously be unmanageable in anything but the tiniest game. |
| | 104 | * |
| | 105 | * A second problem is that it might be better to see the player's |
| | 106 | * actual sequence of command input as somewhat distinct from the player |
| | 107 | * character's subjective storyline. In the turns-equals-time approach, |
| | 108 | * we're essentially asserting a literal equivalence between the |
| | 109 | * player's commands and the player character's story. In typical IF |
| | 110 | * game play, the player spends quite a lot of time exploring, |
| | 111 | * examining, and experimenting. If we were to imagine ourselves as an |
| | 112 | * observer inside the game world, what we'd see would be highly |
| | 113 | * unnatural and unrealistic: no one actually behaves like an IF player |
| | 114 | * character. If we wanted to achieve more realism, we might consider |
| | 115 | * that the medium imposes a certain amount of this exploration activity |
| | 116 | * artificially, as a means for the player to gather information that |
| | 117 | * would, in reality, be immediately and passively accessible to the |
| | 118 | * player character - just by being there, the player character would |
| | 119 | * instantly absorb a lot of information that the player can only learn |
| | 120 | * by examination and experimentation. Therefore, we could say that, |
| | 121 | * realistically, a lot of the exploration activity that a player |
| | 122 | * performs isn't really part of the story, but is just an artifact of |
| | 123 | * the medium, and thus shouldn't count against the story world's |
| | 124 | * internal clock. |
| | 125 | * |
| | 126 | * One ray of hope in all of this is that the human sense of time is |
| | 127 | * neither precise nor objective. People do have an innate sense of the |
| | 128 | * passage of time, but it doesn't work anything like the |
| | 129 | * turns-equals-time approach would have it. People don't internally |
| | 130 | * track time by counting up the actions they perform. In fact, people |
| | 131 | * generally don't even have a very precise intuitive sense for how long |
| | 132 | * a particular action takes; in most cases, people are actually pretty |
| | 133 | * bad at estimating the time it will take to perform ordinary tasks. |
| | 134 | * (Unless you're asking about areas where they have a lot of hands-on |
| | 135 | * experience actually timing things on a clock, such as you might find |
| | 136 | * in certain professions. And a lot of people aren't even good at that |
| | 137 | * - just try getting a decent schedule estimate out of a programmer, |
| | 138 | * for example.) |
| | 139 | * |
| | 140 | * The human sense of time is not, then, an objective accounting of |
| | 141 | * recent activity. Rather, it's a highly subjective sense, influenced |
| | 142 | * by things like emotion and focus of attention. A couple of familiar |
| | 143 | * examples illustrate how a person's subjective experience of time can |
| | 144 | * wildly depart from time's objective passage. First, think about how |
| | 145 | * time seems to stretch out when you have nothing to do but wait for |
| | 146 | * something, like when you're stuck in an airport waiting for a late |
| | 147 | * flight. Second, think about how time flies by when you're doing |
| | 148 | * something that intensely focuses your attention, like when you're |
| | 149 | * playing a really good computer game or you're deeply engaged in a |
| | 150 | * favorite hobby: hours can seem to disappear into a black hole. Being |
| | 151 | * bored tends to expand time, while being intensely occupied tends to |
| | 152 | * compress it. There are other factors that affect the subjective time |
| | 153 | * sense, but occupation of attention seems to be one of the most |
| | 154 | * important. |
| | 155 | * |
| | 156 | * So where does this lead us? If it's not clear which "turns" are part |
| | 157 | * of the story and which are merely artifacts of the medium, the turn |
| | 158 | * counter can't be a reliable source of game-time. But if the human |
| | 159 | * sense of time is inherently subjective, then maybe we're better off |
| | 160 | * tying the passage of time to something other than the raw turn |
| | 161 | * counter anyway. |
| | 162 | * |
| | 163 | * The key to our solution is to treat the turn counter as correlated to |
| | 164 | * the player's subjective time, rather than the story's objective time. |
| | 165 | * In other words, rather than saying that a LOOK command actually takes |
| | 166 | * 30 seconds in the game world, we say that a LOOK command might *feel* |
| | 167 | * like it takes some amount of time to the player, but that the actual |
| | 168 | * time it takes depends on what it accomplishes in the story. How much |
| | 169 | * time really passes for a single LOOK command? It all depends on how |
| | 170 | * many LOOK commands it takes to accomplish something. |
| | 171 | * |
| | 172 | * So, if the player spends long periods of time exploring and frobbing |
| | 173 | * around with objects, but not making any progress, we've had a lot of |
| | 174 | * subjective time pass (many turns), but very little story time (few |
| | 175 | * story-significant events). This is exactly what people are used to |
| | 176 | * in real life: when you're bored and not accomplishing anything, time |
| | 177 | * seems to drag. If the player starts knocking down puzzles left and |
| | 178 | * right, making great progress through the story, we suddenly have a |
| | 179 | * feeling of little subjective time passing (few turns) while story |
| | 180 | * time is flying past (many story-significant events). Again, just |
| | 181 | * what we're used to in real life when deeply engaged in some activity. |
| | 182 | * |
| | 183 | * This basic approach isn't new. Gareth Rees's Christminster includes |
| | 184 | * plot elements that occur at particular clock-times within the story, |
| | 185 | * and a working clock-tower to let the player character tell the time. |
| | 186 | * Time advances in the game strictly according to the player's progress |
| | 187 | * through the plot (i.e., solving puzzles). At key plot events, the |
| | 188 | * clock advances; at all other times, it just stays fixed in place. |
| | 189 | * The game works around the "precision problem" (which we'll come to |
| | 190 | * shortly) with the somewhat obvious contrivance of omitting the |
| | 191 | * minute-hand from the clock, so the time can only be read to the |
| | 192 | * nearest hour. Michael Gentry's Anchorhead also ties events to the |
| | 193 | * story's clock-time, and provides a story clock of sorts that advances |
| | 194 | * by the player's progress in solving puzzles. Anchorhead handles the |
| | 195 | * precision problem by using an even coarser clock than Christminster, |
| | 196 | * specifically the position of the sun in the sky, which I think can |
| | 197 | * only be read to a precision of "day" or "night." The |
| | 198 | * precision-avoiding contrivance here is much less obvious than in |
| | 199 | * Christminster, and it especially helps that the setting and |
| | 200 | * atmosphere of the game practically demand a continuous dark overcast. |
| | 201 | * |
| | 202 | * But what if we wanted a normal clock in the game - one with a minute |
| | 203 | * hand? This is where the precision problem comes in. The problem |
| | 204 | * with subjective time is that, in the real world, a tool-using human |
| | 205 | * in possession of a wristwatch can observe the objective time whenever |
| | 206 | * she wants. In games like Anchorhead and Christminster, the clock |
| | 207 | * only advances at key plot points, so if we permitted the player |
| | 208 | * character a timepiece with a minute hand, we'd get unrealistic |
| | 209 | * exchanges like this: |
| | 210 | * |
| | 211 | * >x watch |
| | 212 | *. It's 3:05pm. |
| | 213 | * |
| | 214 | * >east. get bucket. west. fill bucket with water. drink water. east. |
| | 215 | *. ... done ... |
| | 216 | * |
| | 217 | * >etc, etc, etc... |
| | 218 | *. ... done ... |
| | 219 | * |
| | 220 | * >x watch |
| | 221 | *. It's 3:05pm. |
| | 222 | * |
| | 223 | * That's why Christminster doesn't have a minute hand, and why |
| | 224 | * Anchorhead doesn't have clocks at all. When the time is only |
| | 225 | * reported vaguely, the player won't notice that the clock is frozen |
| | 226 | * between plot events: it's not a problem when we're told that it's |
| | 227 | * still "some time after three" for dozens of turns in a row. |
| | 228 | * |
| | 229 | * This is where this implementation tries to add something new. We try |
| | 230 | * to achieve a compromise between an exclusively narrative advancement |
| | 231 | * of time, and an exclusively turn-based clock. The goal is to have |
| | 232 | * the story drive the actual advancement of the clock, while still |
| | 233 | * giving plausible time readings whenever the player wants them. |
| | 234 | * |
| | 235 | * The compromise hinges on the way people use clocks in the real world: |
| | 236 | * a person will typically glance at the clock occasionally, but not |
| | 237 | * watch it continuously. Our approach also depends upon the |
| | 238 | * observation that people are generally bad at estimating how long a |
| | 239 | * particular activity takes. If people are bad at judging the time for |
| | 240 | * one activity, then they're even worse at judging it for a series of |
| | 241 | * activities. This is an advantage for us, because it means that the |
| | 242 | * "error bars" around a player's estimate of how long something ought |
| | 243 | * to be taking are inherently quite large, which in turn means that we |
| | 244 | * have a large range of plausible results - as long as we don't say |
| | 245 | * it's 3:05pm every time we're asked. |
| | 246 | * |
| | 247 | * Here's the scheme. We start by having the game assign the times of |
| | 248 | * important events. The game doesn't have to tell us in advance about |
| | 249 | * everything; it only has to tell us the starting time, and the game |
| | 250 | * clock time of the next important event in the plot. For example, the |
| | 251 | * game could tell us that it's now noon, and the next important plot |
| | 252 | * point will be when the player character manages to get into the |
| | 253 | * castle, which happens at 6pm, just as the sun is starting to set. |
| | 254 | * Note that the story asserts that this event happens at 6pm; it |
| | 255 | * doesn't matter how many turns it takes the player to solve the |
| | 256 | * puzzles and get into the castle, since *in the story*, we always get |
| | 257 | * in at 6pm. |
| | 258 | * |
| | 259 | * If the player looks at a clock at the start of the game, they'll see |
| | 260 | * that it's noon. The clock module then starts keeping track of the |
| | 261 | * number of turns the player takes. The next time the player looks at |
| | 262 | * a clock, we'll check to see how many turns it's been since the last |
| | 263 | * look at the clock. (We'll also consider plot points where the story |
| | 264 | * actually sets the clock to be the same as the player looking at the |
| | 265 | * clock, since these have the same effect of making us commit to a |
| | 266 | * particular time.) We'll scale this linearly to a number of minutes |
| | 267 | * passing. Then, we'll crank down this linear scaling factor according |
| | 268 | * to how close we're getting to the next event time - this ensures that |
| | 269 | * we'll leave ourselves room to keep the clock advancing without |
| | 270 | * bumping up against the next event time. So, we have a sort of Zeno's |
| | 271 | * paradox factor: the closer we get to the next event time, the slower |
| | 272 | * we'll approach it. |
| | 273 | * |
| | 274 | * The point is to create a plausible illusion of precision. A player |
| | 275 | * who checks infrequently, as should be typical, should see a plausible |
| | 276 | * series of intermediate times between major plot points. |
| | 277 | * |
| | 278 | * One final note: if you want to display a clock in the status line, or |
| | 279 | * show the current time with every prompt, this is the wrong module to |
| | 280 | * use. A key assumption of the scheme implemented here is that the |
| | 281 | * time will be checked only occasionally, when it occurs to the player |
| | 282 | * to look. If the game is constantly checking the time |
| | 283 | * programmatically, it'll defeat the whole purpose, since we won't be |
| | 284 | * able to exploit the player's presumed uncertainty about exactly how |
| | 285 | * much time should have elapsed between checks. |
| | 286 | */ |
| | 287 | |
| | 288 | #include <tads.h> |
| | 289 | |
| | 290 | |
| | 291 | /* ------------------------------------------------------------------------ */ |
| | 292 | /* |
| | 293 | * The Clock Manager is the object that keeps track of the game-world |
| | 294 | * wall-clock time. Timepieces in the game can consult this for the |
| | 295 | * current official time. |
| | 296 | * |
| | 297 | * To use the clock manager's services, you should decide on a set of |
| | 298 | * plot events in your game that occur at particular times. This |
| | 299 | * requires you to "linearize" your game's plot to the extent that these |
| | 300 | * events must occur in a particular order and at particular times within |
| | 301 | * the game world. This might sound at odds with the idea that the |
| | 302 | * player is in control, but most IF stories are actually structured this |
| | 303 | * way anyway, with at least a few plot points that always happen in a |
| | 304 | * particular order, no matter what the player does. Puzzles tend to |
| | 305 | * introduce a locally linear structure by their nature, in that a player |
| | 306 | * can't reach a plot point that depends on the solution to a puzzle |
| | 307 | * until solving that puzzle. |
| | 308 | * |
| | 309 | * Once you've decided on your important plot points, create a ClockEvent |
| | 310 | * object to represent each one. Each ClockEvent object specifies the |
| | 311 | * game-clock time at which the event occurs. Once you've defined these |
| | 312 | * objects, add code to your game to call the eventReached() method of |
| | 313 | * the appropriate ClockEvent object when each plot event occurs. This |
| | 314 | * anchors the game-world clock to the player's progress through the |
| | 315 | * special set of plot points. |
| | 316 | * |
| | 317 | * Note that you must always create a ClockEvent to represent the very |
| | 318 | * beginning of the game - this establishes the time at the moment the |
| | 319 | * game opens. At start-up, we'll initialize the game clock to the time |
| | 320 | * of the earliest event we find, so it's never necessary to call |
| | 321 | * eventReached() on this special first event - its function is to tell |
| | 322 | * the clock manager the game's initial wall-clock time. |
| | 323 | * |
| | 324 | * We keep track of the "day" in game time, but we don't have a calendar |
| | 325 | * feature. We simply keep track of the day relative to the moment the |
| | 326 | * game begins; it's up to the game to make this into a calendar date, if |
| | 327 | * desired. For example, if the game is designed to begin at noon on |
| | 328 | * March 21, 1882, the game could figure out the current calendar day by |
| | 329 | * adding the current day to March 20 (since the first day is day 1). |
| | 330 | * The game would have to figure out when month and year boundaries are |
| | 331 | * crossed, of course, to show the resulting calendar date. If you don't |
| | 332 | * care about tying your story to a particular calendar date, but you do |
| | 333 | * want to nail it down to particular days of the week, this is a lot |
| | 334 | * easier, since you can use "mod-7" arithmetic to compute the weekday - |
| | 335 | * just use "(day % 7) + 1" as an index into a seven-element list of |
| | 336 | * weekday names. |
| | 337 | * |
| | 338 | * A recommendation on usage: if you're using the clock for scheduling |
| | 339 | * appointments that the player character is responsible for keeping, |
| | 340 | * it's a good idea to structure the game so that there's a plot event a |
| | 341 | * little before an appointment. For example, if you've told the player |
| | 342 | * that they have a noon lunch planned with a friend, you should set up |
| | 343 | * the plot sequence so that there's some important event that happens at |
| | 344 | * 11:45am, or thereabouts. The event doesn't need to be related to the |
| | 345 | * lunch in any visible way, as far as the player is concerned - the |
| | 346 | * point of the event is to gate the lunch appointment. The event should |
| | 347 | * usually be something of the nature of solving a puzzle. This way, the |
| | 348 | * player works on the puzzle, since it's not yet time for the lunch |
| | 349 | * appointment; suddenly, when they solve the puzzle, the game can let |
| | 350 | * them know that it's almost lunch time, and that they should go meet |
| | 351 | * their friend. This plays well into the whole subjective-time design |
| | 352 | * of the clock manager, because the player will presumably have her |
| | 353 | * attention occupied solving the puzzle, and so it will seem perfectly |
| | 354 | * natural for the game clock to have changed substantially when she |
| | 355 | * comes up for air after having been working on the puzzle. You can |
| | 356 | * then set up another plot event to represent the player character's |
| | 357 | * arrival at the restaurant, which might occur in the story at 11:55am |
| | 358 | * (or maybe 12:20pm, if the PC is someone who tends to annoy his friends |
| | 359 | * by always running late). |
| | 360 | * |
| | 361 | * When the player character isn't supposed to be waiting for a |
| | 362 | * particular plot event, there's no need to gate it like this. If you |
| | 363 | * have a plot event that coincides with night falling, |
| | 364 | * |
| | 365 | * Note that you can use the clock manager even if your game has no |
| | 366 | * pre-defined plot points. Just create one event to represent the start |
| | 367 | * of the game, to give the clock its initial setting. The clock manager |
| | 368 | * will use the same subjective time scheme it would if there were more |
| | 369 | * plot points, but with no ending boundary to worry about. The clock |
| | 370 | * manager probably isn't as interesting in a game without significant |
| | 371 | * time-anchored plot events, since the passage of time doesn't have any |
| | 372 | * real relation to the plot in such a game;s but it might be desirable |
| | 373 | * to have a working clock anyway, if only for the added sense of detail. |
| | 374 | */ |
| | 375 | clockManager: PreinitObject |
| | 376 | /* |
| | 377 | * Get the current game-clock time. This returns a list in the same |
| | 378 | * format as ClockEvent.eventTime: [day,hour,minute]. |
| | 379 | * |
| | 380 | * Remember that our time-keeping scheme is a sort of "Schrodinger's |
| | 381 | * clock" [see footnote 1]. Between time checks, the game time |
| | 382 | * clock is in a vague, fuzzy state, drifting along at an |
| | 383 | * indeterminate pace from the most recent check. When this method |
| | 384 | * is called, though, the clock manager is forced to commit to a |
| | 385 | * particular time, because we have to give a specific answer to the |
| | 386 | * question we're being asked ("what time is it?"). As in quantum |
| | 387 | * mechanics, then, the act of observation affects the quantity |
| | 388 | * being observed. Therefore, you should avoid calling this routine |
| | 389 | * unnecessarily; call it only when you actually have to tell the |
| | 390 | * player what time it is - and don't tell the player what time it |
| | 391 | * is unless they ask, or there's some other good reason. |
| | 392 | * |
| | 393 | * If you want a string-formatted version of the time (as in |
| | 394 | * '9:05pm'), you can call checkTimeFmt(). |
| | 395 | */ |
| | 396 | checkTime() |
| | 397 | { |
| | 398 | local turns; |
| | 399 | local mm; |
| | 400 | |
| | 401 | /* |
| | 402 | * Determine how many turns it's been since we last committed to |
| | 403 | * a specific wall-clock time. This will give us the |
| | 404 | * psychological "scale" of the amount of elapsed wall-clock the |
| | 405 | * user might expect. |
| | 406 | */ |
| | 407 | turns = Schedulable.gameClockTime - turnLastCommitted; |
| | 408 | |
| | 409 | /* |
| | 410 | * start with the base scaling factor - this is the number of |
| | 411 | * minutes of game time we impute to a hundred turns, in the |
| | 412 | * absence of the constraint of running up against the next event |
| | 413 | */ |
| | 414 | mm = (turns * baseScaleFactor) / 100; |
| | 415 | |
| | 416 | /* |
| | 417 | * If the base scaled time would take us within two hours of the |
| | 418 | * next event time, slow the clock down from our base scaling |
| | 419 | * factor so that we always leave ourselves room to advance the |
| | 420 | * clock further on the next check. Reduce the passage of time |
| | 421 | * in proportion to our reduced window - so if we have only 60 |
| | 422 | * minutes left, advance time at half the normal pace. |
| | 423 | */ |
| | 424 | if (nextTime != nil) |
| | 425 | { |
| | 426 | /* get the minutes between now and the next scheduled event */ |
| | 427 | local delta = diffMinutes(nextTime, curTime); |
| | 428 | |
| | 429 | /* check to see if the raw increment would leave under 2 hours */ |
| | 430 | if (delta - mm < 120) |
| | 431 | { |
| | 432 | /* |
| | 433 | * The raw time increment would leave us under two hours |
| | 434 | * away. If we have under two hours to go before the |
| | 435 | * next event, scale down the rate of time in proportion |
| | 436 | * to our share under two hours. (Note that we might |
| | 437 | * have more than two hours to go and still be here, |
| | 438 | * because the raw adjusted time leaves under two |
| | 439 | * hours.) |
| | 440 | */ |
| | 441 | if (delta < 120) |
| | 442 | mm = (mm * delta) / 120; |
| | 443 | |
| | 444 | /* |
| | 445 | * In any case, cap it at half the remaining time, to |
| | 446 | * ensure that we won't ever make it to the next event |
| | 447 | * time until the next event occurs. |
| | 448 | */ |
| | 449 | if (mm > delta / 2) |
| | 450 | mm = delta / 2; |
| | 451 | } |
| | 452 | } |
| | 453 | |
| | 454 | /* |
| | 455 | * If our calculation has left us with no passage of time, simply |
| | 456 | * return the current time unchanged, and do not treat this as a |
| | 457 | * commit point. We don't consider this a commit point because |
| | 458 | * we treat it as not even checking again - it's effectively just |
| | 459 | * a repeat of the last check, since it's still the same time. |
| | 460 | * This ensures that we won't freeze the clock for good due to |
| | 461 | * rounding - enough additional turns will eventually accumulate |
| | 462 | * to nudge the clock forward. |
| | 463 | */ |
| | 464 | if (mm == 0) |
| | 465 | return curTime; |
| | 466 | |
| | 467 | /* add the minutes to the current time */ |
| | 468 | curTime = addMinutes(curTime, mm); |
| | 469 | |
| | 470 | /* the current turn is now the last commit point */ |
| | 471 | turnLastCommitted = Schedulable.gameClockTime; |
| | 472 | |
| | 473 | /* return the new time */ |
| | 474 | return curTime; |
| | 475 | } |
| | 476 | |
| | 477 | /* |
| | 478 | * The base scaling factor: this is the number of minutes per hundred |
| | 479 | * turns when we have unlimited time until the next event. This |
| | 480 | * number is pretty arbitrary, since we're depending so much on the |
| | 481 | * player's uncertainty about just how long things take, and also |
| | 482 | * because we'll adjust it anyway when we're running out of time |
| | 483 | * before the next event. Even so, you might want to adjust this |
| | 484 | * value up or down according to your sense of the pacing of your |
| | 485 | * game. |
| | 486 | */ |
| | 487 | baseScaleFactor = 60 |
| | 488 | |
| | 489 | /* |
| | 490 | * Get the current game-clock time, formatted into a string with the |
| | 491 | * given format mask - see formatTime() for details on how to write a |
| | 492 | * mask string. |
| | 493 | * |
| | 494 | * Note that the same cautions for checkTime() apply here - calling |
| | 495 | * this routine commits us to a particular time, so you should call |
| | 496 | * this routine only when you're actually ready to display a time to |
| | 497 | * the player. |
| | 498 | */ |
| | 499 | checkTimeFmt(fmt) { return formatTime(checkTime(), fmt); } |
| | 500 | |
| | 501 | /* |
| | 502 | * Get a formatted version of the given wall-clock time. The time is |
| | 503 | * expressed as a list, in the same format as ClockEvent.eventTime: |
| | 504 | * [day,hour,minute], where 'day' is 1 for the first day of the game, |
| | 505 | * 2 for the second, and so on. |
| | 506 | * |
| | 507 | * The format string consists of one or more prefixes, followed by a |
| | 508 | * format mask. The prefixes are flags that control the formatting, |
| | 509 | * but don't directly insert any text into the result string: |
| | 510 | * |
| | 511 | * 24 -> use 24-hour time; if this isn't specified, a 12-hour clock |
| | 512 | * is used instead. On the 24-hour clock, midnight is hour zero, so |
| | 513 | * 12:10 AM is represented as 00:10. |
| | 514 | * |
| | 515 | * [am][pm] -> use 'am' as the AM string, and 'pm' as the PM string, |
| | 516 | * for the 'a' format mask character. This lets you specify an |
| | 517 | * arbitrary formatting for the am/pm marker, overriding the default |
| | 518 | * of 'am' or 'pm'. For example, if you want to use 'A.M.' and |
| | 519 | * 'P.M.' as the markers, you'd write a prefix of [A.M.][P.M.]. If |
| | 520 | * you want to use ']' within the marker string itself, quote it with |
| | 521 | * a '%': '[[AM%]][PM%]]' indicates markers of '[AM]' and '[PM]'. |
| | 522 | * |
| | 523 | * Following the prefix flags, you specify the format mask. This is |
| | 524 | * a set of special characters that specify parts of the time to |
| | 525 | * insert. Each special character is replaced with the corresponding |
| | 526 | * formatted time information in the result string. Any character |
| | 527 | * that isn't special is just copied to the result string as is. The |
| | 528 | * special character are: |
| | 529 | * |
| | 530 | * h -> hour, no leading zero for single digits (hence 9:05, for |
| | 531 | * example) |
| | 532 | * |
| | 533 | * hh -> hour, leading zero (09:05) |
| | 534 | * |
| | 535 | * m -> minutes, no leading zero (9:5) |
| | 536 | * |
| | 537 | * mm -> minutes with a leading zero (9:05) |
| | 538 | * |
| | 539 | * a -> AM/PM marker. If an [am][pm] prefix was specified, the 'am' |
| | 540 | * or 'pm' string from the prefix is used. Otherwise, 'am' or 'pm' |
| | 541 | * is literally inserted. |
| | 542 | * |
| | 543 | * % -> quote next character (so %% -> a single %) |
| | 544 | * |
| | 545 | * other -> literal |
| | 546 | * |
| | 547 | * Examples: |
| | 548 | * |
| | 549 | * 'hh:mma' produces '09:05am' |
| | 550 | *. '[A.M][P.M]h:mma' produces '9:05 P.M.' |
| | 551 | *. '24hhmm' produces '2105'. |
| | 552 | */ |
| | 553 | formatTime(t, fmt) |
| | 554 | { |
| | 555 | local hh = t[2]; |
| | 556 | local mm = t[3]; |
| | 557 | local pm = (hh >= 12); |
| | 558 | local use24 = nil; |
| | 559 | local amStr = nil; |
| | 560 | local pmStr = nil; |
| | 561 | local ret; |
| | 562 | local match; |
| | 563 | |
| | 564 | /* check flags */ |
| | 565 | for (;;) |
| | 566 | { |
| | 567 | local fl; |
| | 568 | |
| | 569 | /* check for a flag string */ |
| | 570 | match = rexMatch( |
| | 571 | '24|<lsquare>(<^rsquare>|%%<rsquare>)+<rsquare>', fmt, 1); |
| | 572 | |
| | 573 | /* if we didn't find another flag, we're done */ |
| | 574 | if (match == nil) |
| | 575 | break; |
| | 576 | |
| | 577 | /* pull out the flag text */ |
| | 578 | fl = fmt.substr(1, match); |
| | 579 | fmt = fmt.substr(match + 1); |
| | 580 | |
| | 581 | /* check the match */ |
| | 582 | if (fl == '24') |
| | 583 | { |
| | 584 | /* note 24-hour time */ |
| | 585 | use24 = true; |
| | 586 | } |
| | 587 | else |
| | 588 | { |
| | 589 | /* it's an am/pm marker - strip the brackets */ |
| | 590 | fl = fl.substr(2, fl.length() - 2); |
| | 591 | |
| | 592 | /* change any '%]' sequences into just ']' */ |
| | 593 | fl = fl.findReplace('%]', ']', ReplaceAll, 1); |
| | 594 | |
| | 595 | /* set AM if we haven't set it already, else set PM */ |
| | 596 | if (amStr == nil) |
| | 597 | amStr = fl; |
| | 598 | else |
| | 599 | pmStr = fl; |
| | 600 | } |
| | 601 | } |
| | 602 | |
| | 603 | /* if we didn't select an AM/PM, use the default */ |
| | 604 | amStr = (amStr == nil ? 'am' : amStr); |
| | 605 | pmStr = (pmStr == nil ? 'pm' : pmStr); |
| | 606 | |
| | 607 | /* adjust for a 12-hour clock if we're using one */ |
| | 608 | if (!use24) |
| | 609 | { |
| | 610 | /* subtract 12 from PM times */ |
| | 611 | if (pm) |
| | 612 | hh -= 12; |
| | 613 | |
| | 614 | /* hour 0 on a 12-hour clock is written as 12 */ |
| | 615 | if (hh == 0) |
| | 616 | hh = 12; |
| | 617 | } |
| | 618 | |
| | 619 | /* run through the format and build the result string */ |
| | 620 | for (ret = '', local i = 1, local len = fmt.length() ; i <= len ; ++i) |
| | 621 | { |
| | 622 | /* check what we have */ |
| | 623 | match = rexMatch( |
| | 624 | '<case>h|hh|m|mm|a|A|am|AM|a%.m%.|A.%M%.|24|%%', fmt, i); |
| | 625 | if (match == nil) |
| | 626 | { |
| | 627 | /* no match - copy this character literally */ |
| | 628 | ret += fmt.substr(i, 1); |
| | 629 | } |
| | 630 | else |
| | 631 | { |
| | 632 | /* we have a match - check what we have */ |
| | 633 | switch (fmt.substr(i, match)) |
| | 634 | { |
| | 635 | case 'h': |
| | 636 | /* add the hour, with no leading zero */ |
| | 637 | ret += toString(hh); |
| | 638 | break; |
| | 639 | |
| | 640 | case 'hh': |
| | 641 | /* add the hour, with a leading zero if needed */ |
| | 642 | if (hh < 10) |
| | 643 | ret += '0'; |
| | 644 | ret += toString(hh); |
| | 645 | break; |
| | 646 | |
| | 647 | case 'm': |
| | 648 | /* add the minute, with no leading zero */ |
| | 649 | ret += toString(mm); |
| | 650 | break; |
| | 651 | |
| | 652 | case 'mm': |
| | 653 | /* add the minute, with a leading zero if needed */ |
| | 654 | if (mm < 10) |
| | 655 | ret += '0'; |
| | 656 | ret += toString(mm); |
| | 657 | break; |
| | 658 | |
| | 659 | case 'a': |
| | 660 | /* add the am/pm indicator */ |
| | 661 | ret += (pm ? pmStr : amStr); |
| | 662 | break; |
| | 663 | |
| | 664 | case '%': |
| | 665 | /* add the next character literally */ |
| | 666 | ++i; |
| | 667 | ret += fmt.substr(i, 1); |
| | 668 | break; |
| | 669 | } |
| | 670 | |
| | 671 | /* skip any extra characters in the field */ |
| | 672 | i += match - 1; |
| | 673 | } |
| | 674 | } |
| | 675 | |
| | 676 | /* return the result string */ |
| | 677 | return ret; |
| | 678 | } |
| | 679 | |
| | 680 | /* pre-initialize */ |
| | 681 | execute() |
| | 682 | { |
| | 683 | local vec; |
| | 684 | |
| | 685 | /* build a list of all of the ClockEvent objects in the game */ |
| | 686 | vec = new Vector(10); |
| | 687 | forEachInstance(ClockEvent, {x: vec.append(x)}); |
| | 688 | |
| | 689 | /* sort the list by time */ |
| | 690 | vec.sort(SortAsc, {a, b: a.compareTime(b)}); |
| | 691 | |
| | 692 | /* store it */ |
| | 693 | eventList = vec.toList(); |
| | 694 | |
| | 695 | /* |
| | 696 | * The earliest event is always the marker for the beginning of |
| | 697 | * the game. Since it's now the start of the game, mark the |
| | 698 | * first event in our list as reached. (The first event is |
| | 699 | * always the earliest we find, by virtue of the sort we just |
| | 700 | * did.) |
| | 701 | */ |
| | 702 | vec[1].eventReached(); |
| | 703 | } |
| | 704 | |
| | 705 | /* |
| | 706 | * Receive notification from a clock event that an event has just |
| | 707 | * occurred. (This isn't normally called directly from game code; |
| | 708 | * instead, game code should usually call the ClockEvent object's |
| | 709 | * eventReached() method.) |
| | 710 | */ |
| | 711 | eventReached(evt) |
| | 712 | { |
| | 713 | local idx; |
| | 714 | |
| | 715 | /* find the event in our list */ |
| | 716 | idx = eventList.indexOf(evt); |
| | 717 | |
| | 718 | /* |
| | 719 | * Never go backwards - if events fire out of order, keep only |
| | 720 | * the later event. (Games should generally be constructed in |
| | 721 | * such a way that events can only fire in order to start with, |
| | 722 | * but in case a weird case slips through, we make this extra |
| | 723 | * test to ensure that the player doesn't see any strange |
| | 724 | * retrograde motion on the clock.) |
| | 725 | */ |
| | 726 | if (lastEvent != nil && lastEvent.compareTime(evt) > 0) |
| | 727 | return; |
| | 728 | |
| | 729 | /* note the current time */ |
| | 730 | curTime = evt.eventTime; |
| | 731 | |
| | 732 | /* if there's another event following, note the next time */ |
| | 733 | if (idx < eventList.length()) |
| | 734 | nextTime = eventList[idx + 1].eventTime; |
| | 735 | else |
| | 736 | nextTime = nil; |
| | 737 | |
| | 738 | /* |
| | 739 | * we're committing to an exact wall-clock time, so remember the |
| | 740 | * current turn counter as the last commit point |
| | 741 | */ |
| | 742 | turnLastCommitted = Schedulable.gameClockTime; |
| | 743 | } |
| | 744 | |
| | 745 | /* add minutes to a [dd,hh,mm] value, returning a new [dd,hh,mm] value */ |
| | 746 | addMinutes(t, mm) |
| | 747 | { |
| | 748 | /* add the minutes; if that takes us over 60, carry to hours */ |
| | 749 | if ((t[3] += mm) >= 60) |
| | 750 | { |
| | 751 | local hh; |
| | 752 | |
| | 753 | /* we've passed 60 minutes - figure how many hours that is */ |
| | 754 | hh = t[3] / 60; |
| | 755 | |
| | 756 | /* keep only the excess-60 minutes in the minutes slot */ |
| | 757 | t[3] %= 60; |
| | 758 | |
| | 759 | /* add the hours; if that takes us over 24, carry to days */ |
| | 760 | if ((t[2] += hh) >= 24) |
| | 761 | { |
| | 762 | local dd; |
| | 763 | |
| | 764 | /* we've passed 24 hours - figure how many days that is */ |
| | 765 | dd = t[2] / 24; |
| | 766 | |
| | 767 | /* keep only the excess-24 hours in the hours slot */ |
| | 768 | t[2] %= 24; |
| | 769 | |
| | 770 | /* add the days */ |
| | 771 | t[1] += dd; |
| | 772 | } |
| | 773 | } |
| | 774 | |
| | 775 | /* return the adjusted time */ |
| | 776 | return t; |
| | 777 | } |
| | 778 | |
| | 779 | /* get the difference in minutes between two [dd,hh,mm] values */ |
| | 780 | diffMinutes(t1, t2) |
| | 781 | { |
| | 782 | local mm; |
| | 783 | local hh; |
| | 784 | local dd; |
| | 785 | local bhh = 0; |
| | 786 | local bdd = 0; |
| | 787 | |
| | 788 | /* get the difference in minutes; if negative, note the borrow */ |
| | 789 | mm = t1[3] - t2[3]; |
| | 790 | if (mm < 0) |
| | 791 | { |
| | 792 | mm += 60; |
| | 793 | bhh = 1; |
| | 794 | } |
| | 795 | |
| | 796 | /* get the difference in hours; if negative, note the borrow */ |
| | 797 | hh = t1[2] - t2[2] - bhh; |
| | 798 | if (hh < 0) |
| | 799 | { |
| | 800 | hh += 24; |
| | 801 | bdd = 1; |
| | 802 | } |
| | 803 | |
| | 804 | /* get the difference in days */ |
| | 805 | dd = t1[1] - t2[1] - bdd; |
| | 806 | |
| | 807 | /* add them all together to get the total minutes */ |
| | 808 | return mm + 60*hh + 60*24*dd; |
| | 809 | } |
| | 810 | |
| | 811 | /* |
| | 812 | * our list of clock events (we build this automatically during |
| | 813 | * pre-initialization) |
| | 814 | */ |
| | 815 | eventList = nil |
| | 816 | |
| | 817 | /* the current game clock time */ |
| | 818 | curTime = nil |
| | 819 | |
| | 820 | /* the most recent event that we reached */ |
| | 821 | lastEvent = nil |
| | 822 | |
| | 823 | /* the next event's game clock time */ |
| | 824 | nextTime = nil |
| | 825 | |
| | 826 | /* |
| | 827 | * The turn counter (Schedulable.gameClockTime) on the last turn |
| | 828 | * where committed to a specific time. Each time we check the time, |
| | 829 | * we look here to see how many turns have elapsed since the last |
| | 830 | * time check, and we use this to choose a plausible scale for the |
| | 831 | * wall-clock time change. |
| | 832 | */ |
| | 833 | turnLastCommitted = 0 |
| | 834 | ; |
| | 835 | |
| | 836 | /* |
| | 837 | * Clock-setting plot event. This object represents a plot point that |
| | 838 | * occurs at a particular time in the story world. Create one of these |
| | 839 | * for each of your plot events. The Clock Manager automatically builds |
| | 840 | * a list of all of these objects during pre-initialization, so you don't |
| | 841 | * have to explicitly tell the clock manager about these. |
| | 842 | * |
| | 843 | * Whenever the story reaches one of these events, you should call the |
| | 844 | * eventReached() method of the event object. This will set the clock |
| | 845 | * time to the event's current time, and take note of how long we have |
| | 846 | * until the next plot event. |
| | 847 | */ |
| | 848 | class ClockEvent: object |
| | 849 | /* |
| | 850 | * The time at which this event occurs. This is expressed as a list |
| | 851 | * with three elements: the day number, the hour (on a 24-hour |
| | 852 | * clock), and the minute. The day number is relative to the start |
| | 853 | * of the game - day 1 is the first day of the game. So, for |
| | 854 | * example, to express 2:40pm on the second day of the game, you'd |
| | 855 | * write [2,14,40]. Note that 12 AM is written as 0 (zero) on a |
| | 856 | * 24-hour clock, so 12:05am on day 1 would be [1,0,5]. |
| | 857 | */ |
| | 858 | eventTime = [1,0,0] |
| | 859 | |
| | 860 | /* get a formatted version of the event time */ |
| | 861 | formatTime(fmt) { return clockManager.formatTime(eventTime, fmt); } |
| | 862 | |
| | 863 | /* |
| | 864 | * Compare our event time to another event's time. Returns -1 if our |
| | 865 | * time is earlier than other's, 0 if we're equal, and 1 if we're |
| | 866 | * after other. |
| | 867 | */ |
| | 868 | compareTime(other) |
| | 869 | { |
| | 870 | local a = eventTime; |
| | 871 | local b = other.eventTime; |
| | 872 | |
| | 873 | /* compare based on the most significant element that differs */ |
| | 874 | if (a[1] != b[1]) |
| | 875 | return a[1] - b[1]; |
| | 876 | else if (a[2] != b[2]) |
| | 877 | return a[2] - b[2]; |
| | 878 | else |
| | 879 | return a[3] - b[3]; |
| | 880 | } |
| | 881 | |
| | 882 | /* |
| | 883 | * Notify the clock manager that this event has just occurred. This |
| | 884 | * sets the game clock to the event's time. The game code must call |
| | 885 | * this method when our point in the plot is reached. |
| | 886 | */ |
| | 887 | eventReached() |
| | 888 | { |
| | 889 | /* notify the clock manager */ |
| | 890 | clockManager.eventReached(self); |
| | 891 | } |
| | 892 | ; |
| | 893 | |
| | 894 | /* ------------------------------------------------------------------------ */ |
| | 895 | /* |
| | 896 | * [Footnote 1] |
| | 897 | * |
| | 898 | * "Schrodinger's cat" is a famous thought experiment in quantum |
| | 899 | * physics, concerning how a quantum mechanical system exists in |
| | 900 | * multiple, mutually exclusive quantum states simultaneously until an |
| | 901 | * observer forces the system to assume only one of the states by the |
| | 902 | * act of observation. The thought experiment has been popularized as |
| | 903 | * an illustration of how weird and wacky QM is, but it's interesting to |
| | 904 | * note that Schrodinger actually devised it to expose what he saw as an |
| | 905 | * unacceptable paradox in quantum theory. |
| | 906 | * |
| | 907 | * The thought experiment goes like this: a cat is locked inside a |
| | 908 | * special box that's impervious to light, X-rays, etc., so that no one |
| | 909 | * on the outside can see what's going on inside. The box contains, |
| | 910 | * apart from the cat, a little radiation source and a radiation |
| | 911 | * counter. When the counter detects a certain radioactive emission, it |
| | 912 | * releases some poison gas, killing the cat. The radioactive emission |
| | 913 | * is an inherently quantum mechanical, unpredictable process, and as |
| | 914 | * such can (and must) be in a superposition of "emitted" and "not |
| | 915 | * emitted" states until observed. Because the whole system is |
| | 916 | * unobservable from the outside, the supposition is that everything |
| | 917 | * inside is "entangled" with the quantum state of the radioactive |
| | 918 | * emission, hence the cat is simultaneously living and dead until |
| | 919 | * someone opens the box and checks. It's not just that no one knows; |
| | 920 | * rather, the cat is actually and literally alive and dead at the same |
| | 921 | * time. |
| | 922 | * |
| | 923 | * Schrodinger's point was that this superposition of the cat's states |
| | 924 | * is a necessary consequence of the way QM was interpreted at the time |
| | 925 | * he devised the experiment, but that it's manifestly untrue, since we |
| | 926 | * know that cats are macroscopic objects that behave according to |
| | 927 | * classical, deterministic physics. Hence a paradox, hence the |
| | 928 | * interpretation of the theory must be wrong. The predominant |
| | 929 | * interpretation of QM has since shifted a bit so that the cat would |
| | 930 | * now count as an observer - not because it's alive or conscious or |
| | 931 | * anything metaphysical, but simply because it's macroscopic - so the |
| | 932 | * cat's fate is never actually entangled with the radioactive source's |
| | 933 | * quantum state. Popular science writers have continued to talk about |
| | 934 | * Schrodinger's cat as though it's for real, maybe to make QM seem more |
| | 935 | * exotic to laypersons, but most physicists today wouldn't consider the |
| | 936 | * experiment to be possible as literally stated. Physicists today |
| | 937 | * might think of it as a valid metaphor to decribe systems where all of |
| | 938 | * the components are on an atomic or subatomic scale, but no one today |
| | 939 | * seriously thinks you can create an actual cat that's simultaneously |
| | 940 | * alive and dead. |
| | 941 | */ |
| | 942 | |