RAP 1.0 Reactive Agent Planner for TADS ------------------------------- by Nate Cull (culln@xtra.co.nz, www.geocities.com/Athens/Forum/6748) Provisional One Point Nought Docuumentation Under Construction Handle with Care Danger Infohazard Etc, ... Archive Contents ---------------- This document should have reached you inside a ZIP archive. That archive should have contained: RAP10DOC.TXT - RAP 1.0 Documentation (this file) RAP10K.T - RAP 1.0 Kernel (TADS source) RAP10P.T - RAP 1.0 Planbase (TADS source) RAPTEST.T - RAP 1.0 Test Game File (TADS source) FAQ --- 1) What is RAP? --------------- RAP 1.0 is a goal-planning library for TADS, the Text Adventure Development System. It's intended for competent TADS authors, so I assume that you're familiar with the TADS language concepts and syntax, and the general art of designing text adventures. If I'm confusing you already, check out my URL above or rec.arts.int-fiction on Usenet for information on the text adventure underground. 2) So what does RAP let me do? ------------------------------ It will let you build smarter NPCs. Okay, so in IF circles that's a little like claiming to own the Brooklyn Bridge. And you're right to be cynical. RAP isn't that smart. But it is a simple backward-chaining goal search algorithm that is adaptable to any situation. That means, right out of the box it: * gives NPCs (or the player) the ability to traverse a map without specifying individual movements (now you can implement a GO TO command like in _Suspended_ and _Suspect_). A built-in map scanning function generates NPC planbases from existing code with no extra effort. * can be scaled up to arbitary situations, not just map-crawling. Your NPC faces a locked door? No problem, he can hunt down the key. She's hungry? Let her find food. Implement social interactions! Solve massive computational AI dilemmas on your PC! (And when you've done it, let me know how, so I can take the credit. Heh heh.) * is fully object-based and overridable, so you can combine the best of procedural TADS methods with list-based goal directed behaviour. A particular RAP plan is too dumb for your NPC? Then create a new one on the fly, or implement a complete algorithm in an action step. Be as simple or creative as you want. * uses a shared planbase, so multiple actors in a game can use the same knowledge pool, unless you want them to act differently, in which case (with a bit more effort) you can. 3) So what _doesn't_ RAP do? ----------------------------- Nothing important. :) Okay, there are a couple of _tiny_ things. Firstly, it has no built-in learning ability. An actor's knowledge must be precoded in the form of plans. That's not a major problem, since most IF doesn't even need goal-seeking, but it's certainly room for expansion. But I have no intention of even trying to think about how that could be done at the moment. Secondly, and a bit more annoying at present, there's no "fog-of-war" implemented in version 1.0. RAP assumes total omniscience of the state of all objects. That's believable with regards to map-crawling, for an actor who knows the area well, but an actor who instantly knows the location of all objects and people probably needs to be dumbed down a bit. Building a generic FOW function is next on my implementation list. In the meantime, you'll just have to hardcode knowledge checks into each condition's rTrue method. Thirdly, RAP has no time sense, memory or look-ahead ability. It will follow plans in a rigid, one-step-at-a-time mentality, without considering consequences or making future predictions. I have no idea how you would do that, or even if it's feasible on a PC. I suspect it's overkill myself, but it's entirely possible that RAP could find itself walking in circles in some situations. If it does, let me know. 3) How fast is it? I don't know. That's something I want to find out by posting this 1.0 release. RAPTEST.T computes fast enough, though not instantly, and that's just dealing with ten rooms and one door. Exactly how fast it is in a full-size game could be interesting to learn. I probably wouldn't want to have a dozen RAP NPCs in a WorldClass library, though. 4) What libraries does it work with? Hopefully anyone's. Definitely adv.t. Probably WorldClass. Are there any others? I've tested it with adv.t. I haven't tested it with WorldClass, but the basic kernel at least should work unchanged. The 1.0 planbase may need changing if isCarrying, isIn, isReachable and the methods of lockable and doorway classes are different. I haven't used WorldClass at all so I don't know. This is one reason why I've separated the kernel and planbase files, though. Anyone want to test it with WorldClass? 5) Enough with the advertising. How do I use RAP in my game? -------------------------------------------------------------- Fairly simply. You include the two RAP 1.0 source files, RAP10K (the 1.0 kernel) and RAP10P (the planbase), at the start of your game file. Then, to make your NPC RAP-enabled, define it as a "rapper" class as well as whatever actor class you're using. If you want to use the automatic map-crawling functions, make a call to rBuildMap() in your preinit function. It's best run at compile time. Define whatever special conditions or actions you need to make up your game's planbase. Then, whenever you want your NPC to make a decision, call its rapAct method with a top-level goal (condition and parameter). (Or if you want to be really clever, muck around with the lower-level rFind method yourself. But I'm not going to bother documenting that here.) 6) Huh? Can you explain that again? ------------------------------------- No. 7) So how do I make sense of this thing? ----------------------------------------- Five ways. Compile RAPTEST.T and run it. Play around with it, and don't forget to YOMIN RAP. Then read the code. That will show you how to implement a trivial map-crawler in RAP. Read the source code for RAP10K.T and RAP10P.T. I make no guarantees about the readability or style of the code - it's 1.0 - but I have commented the major API functions. Read _Alice's New Planner_ in this document, which is a brief overview of the RAP way of thinking about the universe. There may be major errors in my logic, but that's expected. Let me know if it's completely incomprehensible, though. The Dormouse tends to drink a lot of mushroom tea, and so is not a very reliable editor. I'm shooting for partially incomprehensible here. Post on rec.arts.int-fiction. I _might_ read it. TADS gurus much smarter than I certainly will. Email me at the address at the top of this document with any questions, comments, suggestions or allegations of errors. I'll do my best to respond intelligently. Alice's New Planner =================== A Confused Sort of Introduction to RAP Planbase Programming for Looking-Glass Girls and other Ordinary People ----------------------------------------------------------- RAP thinks backwards. Which is by and large a good thing, because we all generally think backwards, even when we think we're thinking forwards. Confused? Alice was. "Let me explain," said Rap, a rather large brown dog who happened to be sitting between the Dormouse and the Mock Turtle. "When I say I think backwards, what I really mean is, when I get up in the morning I don't start out by thinking of the first thing I am going to do. When you get up in the morning, do you first think about putting on your dress and tying your shoelaces, or do you think about the bright sunny day and how much fun it will be to play in the garden?" Alice wrinkled her eyebrows. "I don't know. I generally do both." "Ah," said the Dormouse sleepily, "but that's because you're a Looking-Glass girl, and so you can do things the wrong way round and all at once. Here in TADS-land, we generally only do one thing at a time and so we have to make it count." "Exactly," said Rap. "So whenever I think, I do it simply and logically and start from the top. I start with a GOAL, which is what I want to accomplish, and then work out how to make that happen. Sometimes I can see immediately what to do -" "That would be an ACTION," sighed the Dormouse. " - but otherwise, I have to look for other things to do " - "SUBGOALS" " - and which themselves have things to do to make them happen " "CONDITIONS, PARAMETERS" "- and so on, reasoning backwards, all the way - " "BACKWARDS CHAINING" snores the Dormouse, who seems entirely asleep by now. " - until eventually I come up with one simple ACTION, which I do. Like this!" With which Rap threw a lump of sugar at the sleeping Dormouse, who lazily opened his eyes and continued speaking as if he had never fallen asleep. "- and there you have it," said the Dormouse. "Perfectly logical. Backwards is forwards. The proper way to do things." "But," said Alice, "I don't understand. How does this help me write adventure games?" The Mock Turtle sighed sadly and unfolded a blackboard from his shell. "Because it gives," he muttered as he scribbled. "Your NPCs. A mind of their own. This," he murmured, almost out of breath, "Is your brain. This. Is your brain. On RAP. Or rather, Rap's brain. On himself. Any questions?" And this is what the Mock Turtle wrote: rHappy: rCond sdesc = "rHappy" rTrue(a,p) = { return (a.isCarrying(ball) and a.isIn(startroom)); } rPlans(a,p) = [ [rBe rCarrying ball rBe rIn startroom] ] ; Alice shook her head. "That doesn't look like TADS code to me! All those square brackets! I must be dreaming about LISP or something equally horrid, and I won't have it! I shall pinch myself and wake up right now!" "No," growled Rap, "You're still in TADS-land. We're just using the list structure. You have read your TADS manual, haven't you?" Alice blushed at this, because she _had_ read her TADS manual, but had forgotten all the bits that came after "#include ." So she nodded primly and said nothing. "I will be using TADS list structures a great deal," said Rap firmly, "in fact almost everything that is in my head is expressed as a list, so please pay attention." The Mock Turtle scowled and rapped on his blackboard-shell. "Shall we begin?" he said. "This, harumph, young lady, is the top level of your friend Rap's brain, vacant as it is right now. Translated into Looking-Glass English it says this: 'You are happy if you are in the room "startroom" and carrying the ball. Otherwise, you are unhappy. (And you don't want to be unhappy, though that is something you will be told elsewhere.) 'You want to be happy? Here's how. First be carrying the ball. Second, be in the room "startroom'. And don't bother doing anything else, because that's all you need to be happy.' "As you can see from his rather simple value system, Rap is a refugee from the '60s. Pity you missed that decade, eh what?" Hearing this, the Dormouse wriggled in his sleep and began snoring, "All you need is the ball, yay yay yay, all you need is the baaaall..." before falling into the teapot with a sploshy gurgle. Alice ignored him. "I can understand the rTrue method", she said somewhat uncertainly, "at least I believe I can. That tells me whether or not I am currently happy. I'm quite familiar with the isIn and isCarrying methods because Mr Dodgson (after teaching me mathematics) has been showing me how to use the adv.t class library. And I can assume that Rap wants to be happy because there is a method call somewhere telling him so. "But what is a plan? And why is it laid out like that with all those brackets? What is an rBe? What does it all mean?" At this the Dormouse (who had been gurgling quietly in the bottom of the teapot) poked his head above water and sang softly, "Ah-bees make ah-honey, they ah-live in ah-hive, to rBe or not 2rBe, ah, that's why we're rLive" until Rap pushed his whiskers back into the pot and and leaned both forepaws on the lid. "rBe," said the Mock Turtle seriously, "is a RAP opcode. All RAP classes and methods begin with an r. And I know you are going to ask me what an opcode is - " "I wasn't," said Alice, who was feeling contrariwise, but the Turtle ignored her. " - so perhaps it is time I introduced you to the RAP plan syntax, which goes like this: Plan group :- [ [ <-- one parallel plan opcode condition parameter {opcode condition parameter} <--- one step {opcode...} ] [opcode condition parameter..] <-- an alternative parallel plan [...] <-- etcetera ] " "I once met an etcetera," said the Dormouse from the teapot. "He was dating an elipsis, but they broke up. She ran off with a much older opcode." Everyone ignored him, especially the Turtle. "AS YOU CAN SEE," he frowned, "each Plan Group may actually be a set of PARALLEL PLANS. And each Parallel Plan may actually be a sequence of STEPS. (Please ignore the curly brackets - they're just there to indicate repetition. If this were a proper syntax diagram they would be square brackets, but I'm using those already to show TADS list structures, so I had to be creative.)" "Curly brackets, squiggly wiggly, repetition, ad infinity," muttered the Dormouse and then shut up hurriedly before anyone could throw something at him. "AS I WAS SAYING," harumphed the Turtle, "our friend Rap's rHappy plan is very simple because he has only ONE parallel plan - to be in Startroom while holding the ball - and only TWO steps in that plan." "So the first step would be rBe rCarrying ball and the second is rBe rIn startroom ?" asked Alice, curiously. "I must say, you have very nice whitespace formatting," remarked Rap, "for a Looking-Glass girl." "I've been practicing with the Caterpillar," said Alice. "But that's right, is it? I've read Rap's top-level Plan properly?" "Yes, indeed," nodded the Turtle. "You've read it right, all right." "Read, write, read, write," sang the Dormouse, blowing tea bubbles absently in his sleep, "sequential planning's dead, I don't know my name so I'll backwards chain, until I get out of bed." "Some of us," growled Rap, "have better things to do than make bad nursery rhymes. I know I do. I have a plan for my next action right here." "Ah," said Alice, who was starting to catch on, but slowly because her head still hurt from all this backwards talk, "so Rap takes each step in sequence, right? First he picks up the ball, then he goes to the Startroom?" "Not precisely," whined Rap, fishing in the teapot with one paw, while the Dormouse dodged dreamily. "STEPS are not exactly ACTIONS. They're not things I DO, and they're not exactly in SEQUENCE." "But the Turtle said - " "They're CONDITIONS, and they're arranged in order of PRIORITY," finished Rap, withdrawing his paw from the Dormouse's teapot and licking it. "What's the difference?" "If they were STEPS, I would take first one, and then the other, right? I would always _pick up the ball_, and then always follow that with _going to the startroom_. Right?" "Yes, of course," said Alice, who was a very logical girl and couldn't understand why everyone else was being so strange. "But then," said the Dormouse, emerging from the teapot and shaking his whiskers furiously, "we should all be back writing sequential, procedural, algorithmic code, and right back where we started. Doing things forwards. When as I said, backwards is the only proper way to do anything." Saying which, he washed his whiskers from the outside in, brushed his coat from the tail up, and promptly curled up inside the large bubbling Klein Bottle marked -EM KNIRD-. "Exactly," said Rap. "If I did everything forwards, I should be very dumb indeed. Supposing I was already holding the ball. Why should I want to pick it up again? Or supposing I was halfway to the Startroom when a frumious Bandersnatch came galumphing out of nowhere and snatched it away? They do, you know. They're a menace. If I were only following a sequence of STEPS, I would keep going as if nothing had happened. Which would be quite silly indeed, don't you agree? And certainly something that the standard TADS library could implement quite easily without introducing _me_ into the picture." "I see," said Alice doubtfully, less and less sure all the time that she did, but not willing to admit it. "So every CONDITION in one Parallel Plan has to be satisfied in order, and if it is it acts like a sequence of steps, but if one condition fails, you go back and repeat it?" "More or less," agreed the Turtle, "more or less. Each CONDITION in a STEP specifies something which must be true (such as the ball being carried by Rap, or him being in the Startroom). If that Condition is true, then Rap keeps going, checking out the next condition, and so on. In that way it is a little like a sequence of actions. But if it's false, then it becomes a GOAL for Rap to _make_ that condition become true." "Loag, noitidnoc, loag, noitidnoc," sang the Dormouse from within the Klein Bottle, but nobody could make any sense at all of him this time so they didn't even bother to reply. "Hmm," said Alice, even more doubtfully, but getting a little smarter each time she thought about this. "And of course, Rap then looks up THAT goal as a Condition in his - what do you call your, er - " "My PLANBASE," barked Rap. "- your Planbase -" "Which, as its name indicates, is a database of Plans," put in the Turtle. "(Implemented as the rPlans methods of the entire set of rCond and rAction objects in the game file. But I digress.)" ".od ouy ,seY" sang the Dormouse. ".od ouy ,seY" " - you look up that goal in your Planbase," continued Alice determinedly, "and find all the Parallel Plans for THAT Condition. And if that Condition isn't true -" "I scan each plan in parallel. Which is why they're called Parallel Plans," finished Rap. "Could you explain that part, please, Mr Turtle," asked Alice. "I don't think we've covered that." "He scans each plan in parallel And each plan he scans, it's clear as a bell And he scans each step in each plan as well Until a condition fails, then it all goes to h - " "AHEM!" shouted Rap and the Turtle together at the Dormouse, who had crawled out of the Klein Bottle and was now blinking at them from a few weeks south of Last Tuesday. "I only meant - " said the Dormouse meekly - "That's precisely your problem," snapped the Turtle. "A Dormouse shouldn't _mean_. It should _be_." "That's demeaning," murmured the Dormouse from Next Week. "Oh, do stop messing up the continuum like that. You know it's already far too wrinkly. Anyway - " "What the Turtle is trying to say," said the Dormouse, now fully awake and dropping back into the table's gravity well with a faint splatter of Hawking radiation, "is that Rap evaluates each Parallel Plan in a Plan Group IN PARALLEL. That is, for each Parallel Plan (for a particular goal that he's trying to make true, remember) he looks at each Condition in sequence. Continuing to the next Condition if it's true, or creating a new GOAL for that condition if it's not." "So he creates a kind of Tree of Goals, then," said Alice, whose forehead was getting almost as wrinkly as the continuum, though still a lot cleaner. "A Goal Tree? Or a Stack? That sounds horribly AI-ish. Does it take a lot of computational resources to store this tree? And won't it expand to infinity? And break the universe or something?" "Heavens no, child," said the Turtle, glancing at the Dormouse, who nodded. "It's perfectly safe. We've taken steps to stop that sort of thing. We remove duplicate goals from the stack, so it won't explode. And then there's the rIf opcode -" " - which you still haven't talked about - " "- but I'll get to that in time. Anyway, Rap recalculates the Goal Stack (it's really a stack, not a tree, though the list of active goals tends to grow a bit like a tree) - " "An upside down tree," murmured the Dormouse, falling asleep again, "with its root at the top. But of course you knew that already." "Yes, we did. So anyway, let's just say that Rap is quite good enough at keeping track of his goals and things, and that he DOES evaluate each Plan in Parallel. And within each Plan, he goes through it step by step, looking at each Condition -" "But why does he look at multiple plans at once?" said Alice, whose head was starting to spin, just like Linda Blair. "He can only do one thing at a time, surely?" "Ah, but he can THINK about lots of things at once. Let me demonstrate," said the Turtle. "With another Plan from Rap's planbase. This one is for moving between rooms. A very typical IF thing to want to do, and in fact what Rap's author first wrote him for." "Hey," said the Author, "leave me out of this, okay?" The Turtle ignored him and scribbled on his blackboard-shell again: rIn(actor, param) { if (param = startroom) return ( [ [rBe rIn room2 rDo rGo startroom] ] ); else if (param = room2) return ( [ [rBe rIn startroom rDo rGo room2] [rBe rIn room3 rDo rGo room2] [rBe rIn room4 rDo rGo room2] ] ); } Alice looked dubiously at the board, which looked like nonsense, but being a well-brought-up young Looking-Glass girl she felt it was her duty to make _some_ sense of it all. "I suppose," she said slowly, "I can work out some of this. This seems to be two Plan Groups for Being In two rooms. The first one is simple enough. It tells Rap how to get to Startroom -" "Not GET TO," growled Rap. "How to BE IN. There's a difference. It's a Condition, not an Action." "As I'm a Dormouse," yawned the Dormouse, "not a Do-mouse. I practice Being, not Doing. It's much healthier." " - how to Be In Startroom," Alice corrected herself. "By... let me see. First, Being In the room 'Room2'. Which I suppose must be next to Startroom on the map in RAPTEST.T." "It is," growled Rap. "Very good, Looking-Glass girl." "And then it does an.. oh, my. rDo rGo startroom? I don't believe I've met the rDo opcode yet." "We will, in just a minute - " said the Turtle. "Though they're not very nice," said the Dormouse. "Opcodes, I mean. Just ask the etcetera." " - after you finish working this out. You can, can't you?" "Well, I _think_ so. I should imagine it means that Rap will Do an Action. Which would be Going to the room Startroom. Right?" "Very good," said the Turtle, pulling a second blackboard from his shell and writing on it: RAP Opcodes: ------------ rBe condition param <---- make this condition true (create a goal) rIf condition param <---- check this condition, but don't make it true (used for optimisation) rDo action param <---- do an action (this ends goal scanning) "I see," said Alice, for about the umpteenth time. "So rBe is the main Condition opcode. rIf is sort of like rBe, only it doesn't make a goal. Where would I want to use that?" "The author hasn't really thought about that," said the Dormouse, blowing more tea-bubbles. "Have you?" "Er, no. But it's there if you want to use it. For optimisation and stuff. It prunes the goal tree. But you don't really need to use it at all, generally. Um, will you let me out of that tea-bubble?" "Sorry." The bubble popped, and the Author disappeared. " - And rDo forces an Action to be made. What do you mean by ending goal scanning?" "Just what it sounds like, my dear." "It sounds painful." "Well, it's not really. It just means that as soon as Rap finds an Action that he can do, he does it. And then his move is over, and the Player gets a move, and all sorts of game state gets updated, and such, and then Rap can take another move and so on and so on. This means that when Rap is evaluating multiple parallel plans, he does the first Action that he can find - " "That sounds sensible." "- which means that generally, he chooses the shortest path between two rooms, or the shortest sequence of actions that leads to the goal he wants. Which, as you say, is generally sensible." Alice wrinkled her nose. "Are there any cases when it wouldn't be?" "I don't know that, either," said the Author. "So let's just say that it's sensible and leave it at that." "Then I think I understand. Now, suppose I take a look at the second Plan Group there. The one for Room2. (What silly names these rooms all have!)" "They're very sensible names," growled Rap, batting at the Author's tea bubble with one paw. The Author winced. "Everything's numbers, when you take it apart." "Like you did with the White Rabbit's wristwatch," said the Dormouse sleepily. "Only you couldn't get the numbers back in again right. That's why we keep repeating this conversation." "ANYWAY," continued Alice, "I can see there really ARE three Parallel Plans here. We're trying to Be In Room2, but there are three different ways to do it: Either: [rBe rIn startroom rDo rGo room2] Or: [rBe rIn room3 rDo rGo room2] Or even: [rBe rIn room4 rDo rGo room2] "Yes indeed," said the Turtle approvingly. "And Rap tries all of these at once. Each one creates either an Action or a goal. And so on. And the first goal that produces an Action wins." "I really do think I'm getting it," said Alice excitedly. "So there are three ways to get to Room2 -" "As you'd expect in a room with three entrances," put in Rap. " - and the first one is to be in Startroom and then go to Room2, and the second is to be in Room3 and go to Room2, and so on. Yes, it all makes sense." "Oh dear," the Dormouse whispered, "It's starting to make sense to her. That means she must be starting to wake up. And you know what that means..." "Wait, please!" cried Alice, jumping to her feet, who was a bright little Looking-Glass girl who had been around the dream clock a few times and knew just how these things worked. "If you're all going to vanish away _just_ when it's all making sense I shall be _very_ annoyed. Because all this will go out of my head and straight back into Looking-Glass land when I wake up. And I shall cry and bang my head on my pillow and I shall give myself a splitting headache faster than you can type YOMIN ME WITH A FROTZED GRUE. So please answer my last few questions right now!" "Very well," said the Mock Turtle, who was looking at his watch and hurriedly packing up his blackboard-shells without trying to look as if he was hurrying. "What else do you need to know?" "Conditions. Actions. Parameters. What are they and how do I use them? I know how do do a Plan Group, I think. I use the rPlans method. But what about all the rest of it, tying it into my TADS code?" The Turtle picked up the Klein Bottle, shook it a few times, and wrote some glowing letters in the continuum: Conditions ---> rCond objects truth method ---> rTrue(actor, param) plan method ---> rPlans(actor, param) Actions ---> rAct objects action method ---> rAction(actor, param) "These are the basics. Actor is a parameter to all the methods because, well, you know how all TADS verbs pass the actor, it's just a TADSy kind of thing to do. RAP is almost always associated with an actor, so we pass that in case it's useful. Param is a parameter to the action or condition, and yes, that's the one we haven't yet talked about. The reason why is it's very simple. "A Parameter can be anything at all. Any Object class, primitive type, or even a List (so you can have multiple parameters in a Condition or Action if you really want)." Alice shuddered. For some reason, she still didn't want to think about any more Lists than she had to. "I'll try to forget that," she said. "But I understand now. A Condition and an Action both need an Opcode and a Parameter. Like, for example: rDo rGo startroom is an rDo Opcode, an rGo Action, and a Parameter which is an Object Reference to the room 'startroom'. And this is all returned somewhere inside the rIn Condition's rPlans method. Right?" "Right," said Rap, carefully preening his coat and fastening his bowtie without trying to look like he was doing either. "And it's all a TADS list. So, I can use any kind of TADS tricks I like to create that list, right? Like this: rDo rGo x where x is a local variable in my rPlans method. Or any kind of object property. Or anything. Right?" "Right," said the Dormouse, carefully folding up the teapot and packing it away into a passing tea-bubble, taking very special care not to pop it. "And... I could even generate new Plans at runtime, by modifying the rPlans method to do all kinds of calculations... or I could maybe create plans at compile time, maybe by scanning the map with some kind of scanning routine..." "Hrrmmph," said the Mock Turtle, looking at his watch. "Oh, my goodness, is that the time? We really must be - " "No!" cried Alice. "I remember now. You still haven't told me about that automatic map building routine! You simply _can't_ vanish away and not have told me about that!" "Sorry," said the Dormouse, folding up the last of the continuum into his tea-bubble. "Can't. Rules, you know. Sheer physics. Space. Time. Out of. Etcetera. Elipsis. But if you really want to know more - " "Yes?" shouted Alice, into the by now very black and empty darkness. "Read the source," said a hollow voice. "And who made _you_ an authority?" said Alice, turning angrily towards the voice. There was an awkward pause. "Ah. Because I'm the, you know, thing, author." "And that's the last straw." said Alice. "I refuse to be talked to by a talking tea-bubble. This whole conversation has been one piece of nonsense piled upon another! I should pop you right now!" "Er. No, you really don't want to - " POP. "Oops," said Alice.