!--------------------------------------------------------------------------
!                            THE MAGIC TOYSHOP
!                    Copyright (c) 1995 by Gareth Rees
!                               July 1995
!--------------------------------------------------------------------------
! INTRODUCTION
! 
! This was an entry for the 1995 short interactive fiction contest, though
! not really a serious piece of fiction.  It is instead a gratuitous
! sequence of puzzles purely for puzzles' sake.  Some of the puzzles
! pastiche famous puzzles from other adventure games: the gnomon and
! sundial from Trinity; the robot mouse and the featureless mahogany rod
! from Curses; the egg from the Unnkulia series.  Others are well-known
! games and puzzles with silly twists (towers of Hanoi, noughts and
! crosses).  Still others are games that were fun to code up (mostly highly
! derivative).
! 
! As of July 1995 my electronic mail address was <gdr11@cl.cam.ac.uk>, but
! if you have trouble getting hold of me, try the Usenet newsgroups
! rec.arts.int-fiction and rec.games.int-fiction.
!--------------------------------------------------------------------------
! LICENCE
! 
! This program is free software; you can redistribute it and/or modify it
! under the terms of the GNU General Public License as published by the
! Free Software Foundation; either version 2 of the License, or (at your
! option) any later version.
! 
! This program is distributed in the hope that it will be useful, but
! without any warranty; without even the implied warranty of merchant-
! ability or fitness for a particular purpose.  See the GNU General Public
! License for more details.
!--------------------------------------------------------------------------
! CONTENTS
! 
! 1.1 About the game
! 1.2 Attributes, properties, actions and grammar
! 1.3 Asking questions
! 1.4 Entry points
! 1.5 Plot & scoring system
! 1.6 The toyshop
! 
! 2.1 Making the robot mouse
! 2.2 Noughts and crosses
! 2.3 The gnomon
! 2.4 The towers of Hanoi
! 2.5 Dots and boxes
! 2.6 The robot mouse
! 2.7 Tea-time
! 2.8 Dodgems
! 2.9 The infernal machine
!--------------------------------------------------------------------------

Constant Story "THE MAGIC TOYSHOP";
Constant Headline "^A fun game for all the family^by Gareth Rees^New \
    players should type ~help~^";
Replace FullScoreSub;
Constant MAX_SCORE 20;
Constant TASKS_PROVIDED;

Include "parser";
[ FullScoreSub; MyFullScore(); ];
Include "verblib";
Include "grammar";


!--------------------------------------------------------------------------
! 1.1 ABOUT THE GAME
!--------------------------------------------------------------------------

[ HelpSub;
    "~The Magic Toyshop~ is an entry in the 1995 interactive fiction \
    programming competition. It isn't a serious story by any means, but \
    you might have fun playing with some of the gadgets herein. Thanks to \
    Michael Kinyon for finding bugs and offering suggestions.^^~The Magic \
    Toyshop~ is copyright (c) 1995 by Gareth Rees, and may be freely \
    distributed and used under the terms of version 2 of the GNU General \
    Public Licence or, at your option, any later version.^^Some of the \
    ASCII graphics can be turned off using the command ~plain~ and on \
    again using ~pretty~. There are hints provided in the game.";
];

Verb meta "help" * -> Help;


!--------------------------------------------------------------------------
! 1.2 ATTRIBUTES, PROPERTIES, ACTIONS AND GRAMMAR
!--------------------------------------------------------------------------

Attribute is_disk;     ! identifies disks for the Towers of Hanoi game
Attribute is_peg;      ! identifies pegs on the Towers of Hanoi board
Attribute sticky;      ! if an object has had glue added to it

Property next;         ! next object in a linked list
Property stuck_to;     ! which other disk a disk is stuck to
Property state;        ! general state property
Property prev_num;     ! previous number property (for Hanoi board)
Property puzzle_state; ! 0 unseen; 1 seen; 2 attempted; 3 solved
Property puzzle_pre;   ! prerequisites for the puzzle to be available
Property puzzle_name;  ! name of a puzzle (for fullscore command)

Fake_Action Reset;     ! reset a puzzle to its initial state
Fake_Action Invoke;    ! start up a new puzzle
Fake_Action Display;   ! print a display

Global pretty = 1;     ! 1 iff ASCII graphics are used by preference
Global help = 0;       ! 1 iff game boards print help information

[ PlainSub; pretty = 0; "Special effects turned off."; ];
[ PrettySub; pretty = 1; "Special effects turned on."; ];
[ HelpOnSub; help = 1; "Help turned on."; ];
[ HelpOffSub; help = 0; "Help turned off."; ];

Verb meta "plain" * -> Plain;
Verb meta "pretty" * -> Pretty;
Extend "help"
    * "on" -> HelpOn
    * "off" -> HelpOff;


!--------------------------------------------------------------------------
! 1.3 ASKING QUESTIONS
!--------------------------------------------------------------------------
! Here are some replacements for the various conversational grammar entries
! that allow conversation topics to be parsed as objects within some scope
! (namely the AskQuestions object); thus "ask catharine about noughts and
! crosses" will work.  Topics representing puzzles are moved into this
! scope when they are seen for the first time, so that clues aren't
! available before the puzzle has been encountered.
! 
! This approach is somewhat tricky to make correct in general; here, it's
! ok for "ask catharine about and" to reply "Which do you mean, the noughts
! and crosses, or the dots and boxes?", but in a less frivolous game
! identification of conversational subjects in this way might be frowned
! upon.  There may be problems if there are several people to talk to; thus
! "say noughts" might produce "Who do you want to say the noughts and
! crosses to?" whereas "say aardvark" would produce "Who do you want to say
! that to?" (if there were no topic for aardvarks).  You could give the
! noughts and crosses topic the name "that", so that this error message
! wouldn't reveal the valid topics, but then you'd have to make sure that
! no word appeared in two topics, otherwise you'd get error messages of the
! form "Which do you mean, that or that?".
! 
! See my example game "Encyclopedia Frobozzica" (look in the /programming/
! inform/examples/ directory at the IF-archive) for more details about
! talking to characters in Inform games.
!--------------------------------------------------------------------------

[ AskScope;
    if (scope_stage == 1) rfalse;
    if (scope_stage == 2) {
        ScopeWithin(AskQuestions);
        rtrue;
    }
    "** Error: that input should have matched a later line in grammar **";
];

[ QuestionSub; if (RunLife(noun,##Ask)~=0) rfalse; "No reply."; ];
[ RQuestionSub; <<Question second noun>>; ];
[ NoQuestionSub; <<Question noun 0>>; ];

[ ConTopicPrep prep w; consult_from = wn;
  do w=NextWordStopped(); until (w==prep or -1); if (w==-1) return -1;
  wn--; consult_words = wn-consult_from;
  if (consult_words==0) return -1; return 0; ];
[ ConTopicTo; return ConTopicPrep('to'); ];
[ ConTopicAt; return ConTopicPrep('at'); ];

Extend "ask" replace
    * creature "about" scope=AskScope   -> Question
    * creature "for" scope=AskScope     -> Question
    * creature scope=AskScope           -> Question
    * creature ConTopic                 -> NoQuestion;

Extend "say" replace
    * scope=AskScope "to" creature      -> RQuestion
    * scope=AskScope "at" creature      -> RQuestion
    * ConTopicTo "to" creature          -> NoQuestion
    * ConTopicAt "at" creature          -> NoQuestion
    * ConTopic "to" creature            -> NoQuestion;

Extend "tell" replace
    * creature "about" scope=AskScope   -> Question
    * creature "about" ConTopic         -> NoQuestion
    * creature scope=AskScope           -> Question
    * creature ConTopic                 -> NoQuestion;

Object  AskQuestions "questions";

Nearby  QHello "that" has proper
 with   name "hello" "good" "morning" "afternoon" "day" "hi";
Nearby  QToyshop "that" has proper
 with   name "toy" "shop" "toyshop" "store";
Nearby  QExit "that" has proper
 with   name "exit" "way" "out" "door" "help";
Nearby  QNiece "that" has proper
 with   name "niece" "isabelle" "present" "gift" "birthday";


!--------------------------------------------------------------------------
! 1.4 ENTRY POINTS
!--------------------------------------------------------------------------

[ Initialise;
    location = Toyshop;
    StartDaemon(Toyshop);

    "^^You were looking for a birthday present for your niece Isabelle, \
    that was it. The toy stores along Regent Street were blaring out pop \
    music and the window displays were garish scenes of animated computer \
    violence. But down a dim Victorian arcade you came across a different \
    kind of toyshop, with a peeling rocking-horse behind a grimy \
    window. Perhaps in here, you thought...^^";
];


!--------------------------------------------------------------------------
! 1.5 PLOT & SCORING SYSTEM
!--------------------------------------------------------------------------
! Each puzzle is associated with an object by the `Puzzles' array.  The
! state of the puzzle can be
! 
!   0 puzzle not yet seen
!   1 puzzle has been seen but not really attempted yet
!   2 player attempted the puzzle and encountered the difficulty
!   3 puzzle has been solved
! 
! The distinction between 1 and 2 is so that Catharine doesn't give away
! hints until the player has at least had a go.
! 
! Each puzzle can have prerequisites, that is, puzzles which need to be
! solved before this one can be seen: for example, the dots and boxes
! puzzle can't be seen until the noughts and crosses puzzle has been solved
! (to prevent there being confusion over the two pieces of paper).
! 
! The `NewPuzzle' function picks a new puzzle, but also prevents there
! being too many puzzles available at once.  The puzzles appear in the
! order they are given in the `Puzzles' array, subject to preconditions
! being satisfied.
! 
! The graph of puzzles follows (a puzzle depends for its solution on the
! puzles above it).  Some of the connections are enforced by the
! proconditions to ensure that there isn't too much happening at once; some
! other connections are enforced by objects needing to be present that
! result from the solution of previous puzzles.  The dotted connections
! show that a puzzle only needs to be present, not solved, in order to
! solve another.
! 
!                             Start
!                 ._____________|___________.
!                 |                         |
!             Assembling                 Noughts
!             the mouse                 & crosses
!         ._______|_____________________.   |
!         |       |          |          |   |
!         |       |        Towers       |   |
!         |       |       of Hanoi      |   |
!         |       |  . . . . |          |   |
!         |       |  |                  |   |
!         |     Gnomon &                Dots &
!         |     sundial                 boxes
!         |       |__________________.    |
!         |       |                  |    |
!         |      Egg                 Dodgems
!         |   . . |
!         |   |
!         Robot
!         mouse
!           |
!           |
!         Chest
! 
! It will be seen that it isn't necessary to solve the Egg, the Towers of
! Hanoi, Dodgems or Dots and Boxes to win.  It is necessary to solve
! Noughts and Crosses, because of constraints on which puzzles are present.
!--------------------------------------------------------------------------

Constant NPUZZLES 9;
Array Puzzles --> CardboardBox OXPaper Gnomon RobotMouse DBPaper HanoiBoard
    DodgemsPaper Hamper PuzzleChest;

[ NewPuzzle
    reply  ! 1 iff Catharine should comment (INPUT)
    a      ! count of puzzles in progress
    b      ! count of available puzzles
    c      ! first available puzzle to be found
    i j;   ! loop counters

    ! count the puzzle in progress and available; pick first available
    for (: i < NPUZZLES: i++) {
        if ((Puzzles-->i).puzzle_state == 1 or 2) a++;
        if ((Puzzles-->i).puzzle_state == 0) {
            for (j = 0: j < ((Puzzles-->i).#puzzle_pre)/2: j++)
                if ((((Puzzles-->i).&puzzle_pre)-->j).puzzle_state ~= 3)
                    jump NextPuzzle;
            b ++;
            if (b == 1) c = i;
            .NextPuzzle;
        }
    }
    if (reply == 1) {
        if (a >= 3)
            print_ret "~I think that ", (EnglishNumber) a, " puzzles on \
                the go at once is plenty,~ says Catharine.";
        if (b == 0)
            "~I'm right out of puzzles at the moment,~ says Catharine.";
    }
    if (a >= 3 || b == 0) rtrue;
    <<Invoke Puzzles-->c>>;
];

[ MyFullScore
    a   ! count of solved puzzles
    b   ! count of puzzle in progress
    i;  ! loop counter

    for (i = 0: i < NPUZZLES: i++) {
        switch ((Puzzles-->i).puzzle_state) {
         1,2: b ++;
         3: a ++;
        }
    }

    ScoreSub();
    if (score == 0) rfalse;

    if (a > 0) {
        print "^You ";
        if (deadflag == 0) print "have ";
        print "solved ", (EnglishNumber) a, " puzzle";
        if (a > 1) print "s";
        print ":^^";
        for (i = 0: i < NPUZZLES: i++)
            if ((Puzzles-->i).puzzle_state == 3)
                print "    ", (string) (Puzzles-->i).puzzle_name, "^";
    }
    if (b > 0) {
        print "^You ";
        if (deadflag == 0) print "are";
        else print "were";
        print " still working on ", (EnglishNumber) b, " puzzle";
        if (b > 1) print "s";
        print ":^^";
        for (i = 0: i < NPUZZLES: i++)
            if ((Puzzles-->i).puzzle_state == 1 or 2)
                print "    ", (string) (Puzzles-->i).puzzle_name, "^";
    }
];


!--------------------------------------------------------------------------
! 1.6 THE TOYSHOP
!--------------------------------------------------------------------------

Object  Toyshop "Toyshop"
 has    light
 with   name "toyshop" "emporium" "shop" "store",
        description "Who knows what might be hidden in the dark rafters \
            and shadowy corners of this emporium?",
        state 0,
        daemon [;
            self.state = self.state + 1;
            switch (self.state) {
             1: give Catharine proper;
                "^~Welcome,~ says the young woman, ~My father usually \
                runs the store, but I'm afraid he isn't here today and \
                I'm looking after the store. My name is Catharine.~";
             2: NewPuzzle();
             9: self.state = 1;
            }
        ],
        before [;
         Exit: <<Go out_obj>>;
        ],
        cant_go "You turn to leave, but alarmingly you are unable to find \
            a way out.";

Nearby  ToyChest "oak chest"
 has    container openable lockable locked static
 with   name "oak" "chest" "oaken",
        article "an",
        describe [;
            if (self has open) print "^An oak chest stands open";
            else print "^There's a closed oak chest";
            print_ret " in the ", (DirectionName) self.state, " corner.";
        ],
        before [;
         Unlock:
            if (second ~= BrassKey) rfalse;
            <<Invoke PuzzleChest>>;
        ],
        state ne_to,
        each_turn [;
            if (random(5) ~= 1) rtrue;
            switch(random(4)) {
             1: self.state = ne_to;
             2: self.state = nw_to;
             3: self.state = se_to;
             4: self.state = sw_to;
            }
        ];

Nearby  Catharine "Catharine"
 has    animate female transparent
 with   name "catharine" "girl" "woman" "hair" "pigtail" "young" "catherine",
        describe [;
            if (self has proper) print "^Catharine ";
            else print "^A young woman ";
            switch (self.state) {
             0: "stands attentively nearby.";
             1: "sits on the floor nearby.";
            }
        ],
        short_name [;
            if (self has proper) print "Catharine";
            else print "young woman";
            rtrue;
        ],
        description "She is a young woman, in her late teens perhaps. She \
            is wearing a white crinoline dress with a hoop skirt, and her \
            long black hair is tied into a pigtail.",
        state 0,
        life [;
         Kiss: "~You are a sweet thing,~ she says.";
         Attack:
            deadflag = 1;
            "You punch her in the face. She screams and falls to the \
            floor. One hand shielding her cheek, she looks up with \
            frightened eyes at you towering over her. ~I thought -~ she \
            starts, and then you want no more of this, and turn to \
            flee. Your foot catches on the open chest, which wasn't at \
            all where you were expecting it to be, and you tumble into \
            it. The lid shuts with a reassuringly final thud.";
         Ask:
            if (second == 0 || self hasnt proper)
                print_ret (The) self, " doesn't reply.";
            if (second.next ~= 0) {
                if (second.next.puzzle_state == 1)
                    "~But you haven't even tried to solve that puzzle on \
                    your own!~ exclaims Catharine. ~I think you ought to \
                    have a go first, and then if you get stuck, maybe I \
                    can help you.~";
                if (second.next.puzzle_state == 3)
                    "~You've already solved that,~ says Catharine. ~I \
                    don't think you need any help from me.~";
                if (second.state >= (second.#description)/2)
                    "~I don't think I should give you any more help with \
                    that,~ says Catharine. ~If I gave the game away, it \
                    would only spoil your fun!~";
                print (string) ((second.&description)-->(second.state));
                second.state = second.state + 1;
                new_line;
                rtrue;
            }
            switch (second) {
             QHello: "~Good afternoon,~ says Catharine.";
             QExit: "~Looking to leave, already?~ says Catharine. ~But \
                you only just arrived!~";
             QNiece: "~I'm sure that we have just the thing for your \
                niece,~ says Catharine.";
             QToyshop: "~A lovely shop, isn't it?~ says Catharine. ~My \
                father and I, we do feel under pressure to modernise, but \
                we're resisting as much as we can. We feel that the best \
                puzzles and games are the ones you have to work at; ones \
                which don't give instant gratification. Yes, I know \
                that's a bit patronising, but there you are.~";
            }
        ],
        before [;
         Shake: "~Pleased to meet you,~ says Catharine, shaking your \
             hand.";
        ];

Object  Dress "crinoline dress" Catharine
 has    concealed worn
 with   name "crinoline" "dress" "hoop" "skirt",
        before [;
         Take,Remove: print_ret "That seems to belong to ", (the)
            Catharine, ".";
         ! Added after testing. Poor Catharine, to suffer this ignominy:
         LookUnder: "You get down on hands and knees to attempt this \
            task, when Catharine stamps on your hand and you yelp in \
            pain. ~I'm sorry,~ she says. ~What are you doing down there? \
            Have you lost your cuff-link?~ You stand up, shame-faced.";
        ];


!--------------------------------------------------------------------------
! 2.1 MAKING THE ROBOT MOUSE
!--------------------------------------------------------------------------
! This is just here to provide a decent excuse for the glue to be present
! (because it is needed for the Towers of Hanoi puzzle).
!--------------------------------------------------------------------------

Object  BoxQuestion "Airfix model"
 with   name "airfix" "model" "card" "cardboard" "box" "glue" "gadget" "wheel",
        next CardboardBox,
        state 0,
        description "~It's a little trifle, I admit,~ says Catharine, \
            ~but you might like it, and so might your niece. I don't want \
            to tell you what it is until you've made it, as that would \
            spoil the surprise~."
            "~I did say it would need some assembly,~ says \
            Catharine. ~Doesn't glue usually come with these models? I \
            wonder where it's got to?~";

Object  CardboardBox "cardboard box"
 has    container openable
        ! general if found the glue
 with   name "card" "cardboard" "box",
        puzzle_name "The Airfix model",
        puzzle_state 0,
        capacity 3,
        description "The box is very faded, perhaps as a result of lying \
            unpurchased on a shelf for too many years. You can make out \
            the words ~Airfix~ and ~voice activated~, and a picture of \
            something that might be a model car.",
        invent [;
            if (inventory_stage == 2) {
                if (self has open) print " (which is open)";
                else print " (which is closed)";
                rtrue;
            }
        ],
        before [;
         Examine: PrintOrRun(self,description); rtrue;
         Empty:
            if (self notin player)
                "You should try picking it up first.";
         Search:
            if (self has open)
                "You can't make out much inside the box. Maybe you could \
                just try to empty it.";
         Shake:
            if (children(self) > 0)
                "Something rattles inside the box.";
            if (self hasnt general) {
                give self general;
                move Glue to self;
                "You give the box a vigourous shake, and you hear a \
                satisfactory rattle.";
            }
         Invoke:
            move self to player;
            move BoxQuestion to AskQuestions;
            self.puzzle_state = 1;
            score ++;
            "^Catharine opens the chest and roots around inside it. ~I \
            wonder if your niece would like something like this?~ she \
            says. ~It's quite old, but I think it still works.~ She \
            closes the chest and shows you a small cardboard box. She \
            holds it up to her ear and shakes it. ~Sounds like it's all \
            there. It does require some assembly, though,~ she adds, and \
            hands the box to you.";
        ],
        after [;
         Open: print_ret "You open the cardboard box.";
        ];

Class   MousePartClass
 with   parse_name [ w n ok i len;
            do {
                ok = 0;
                w = NextWord();
                if (w == 'gadgets') {
                    ok = 1;
                    parser_action=##PluralFound;
                } else {
                    len = (self.#name)/2;
                    for (i = 0: ok == 0 && i < len: i++)
                        if ((self.&name)-->i == w)
                            ok = 1;
                }
                if (ok == 1) n++;
            } until (ok == 0);
            return n;
        ],
        before [;
         PutOn,Tie,Insert:
            if (second ~= self.next) rfalse;
            if (self hasnt sticky && second hasnt sticky)
                "You bring the two gadgets together, and after a bit of \
                fiddling you make them fit, but without you to hold them \
                together they immediately fall apart again.";
            remove MousePartA;
            remove MousePartB;
            CardboardBox.puzzle_state = 3;
            score ++;
            print "You bring the two gadgets together, and they \
                adhere. The resulting object looks not at all like a car, \
                and quite a lot like a mouse.^";
            <<Invoke RobotMouse>>;
        ],
        each_turn [;
            if (self in CardboardBox) rfalse;
            self.each_turn = NULL;
            CardboardBox.puzzle_state = 2;
        ];

Nearby  MousePartA "grey gadget"
 class  MousePartClass
 with   name "grey" "gray" "gadget",
        description "It's a piece of metal, covered on one side with what \
            looks like a piece of grey carpet. Who knows what it could be \
            for?",
        next MousePartB;

Nearby  MousePartB "wheeled gadget"
 class  MousePartClass
 with   name "wheel" "wheeled" "gadget" "wheels" "rubber",
        description "It's a piece of metal with four rubber wheels. Who \
            knows what it could be for?",
        next MousePartA;

Object  Glue "tube of glue"
        ! general if seen the instructions
 with   name "tube" "of" "glue",
        description "A little tube with a nozzle on one end. Writing \
            along the side reads ~Super Safe Wonder Glue! Sticks only \
            metal. Completely non-toxic and safe for all other \
            materials.~",
        before [;
         Squeeze,PutOn:
            if (second == 0) 
                "[To apply the glue to an object, use ~squeeze glue onto \
                thing~.]";
            if (second == Catharine)
                "~Keep that stuff off me!~ says Catharine. ~I know it's \
                supposed to be non-toxic, but you never know with these \
                things, and I'd rather be safe than sorry.~";
            if (second hasnt is_disk && second ~= MousePartA or MousePartB
                or Gnomon)
                print_ret "You squeeze some glue onto ", (the) second, " \
                    and in seconds it evaporates. It looks like the \
                    description of the amazing properties of the glue was \
                    accurate.";
            if (second has is_disk && children(second) > 0)
                print_ret "There would be little point in doing that, \
                    because ", (the) child(second), " is already on ",
                        (the) second, ".";
            give second sticky;
            print_ret "You squeeze some glue onto ", (the) second, " and \
                it spreads out to form a sticky film.";
        ],
        after [;
         Examine:
            if (self hasnt general) {
                give self general;
                "^[To apply the glue to an object, use ~squeeze glue onto \
                thing~.]";
            }
        ];

[ ShakeSub; "Nothing happens."; ];

Verb "shake"
    * noun -> Shake
    * "hands" "with" noun -> Shake;

Extend "squeeze"
    * noun "onto" noun -> Squeeze
    * noun "at" noun -> Squeeze
    * noun "on" noun -> Squeeze;

Verb "stick"
    * noun "to" noun -> Tie;


!--------------------------------------------------------------------------
! 2.2 NOUGHTS AND CROSSES
!--------------------------------------------------------------------------
! The noughts & crosses code uses a standard trick to simplify    1 8 3
! the programming, which is to use a magic square to find the     6 4 2
! rows of three (see diagram at right).                           5 0 7
! 
! Given two board positions, i and j say, the position that makes up a row
! of three is given by looking up i and j in the magic square and
! subracting the values there from 12.  (The result must be checked for
! bounds and that it doesn't equal i or j).
! 
! Catharine's strategy is the simplest unbeatable strategy I could devise,
! which is (1) play in the centre on the first move; (2) win if possible;
! (3) block an opponent's win if necessary; (4) play a random corner if
! possible; (5) play a random edge.
! 
! It is left as an exercise for the reader to show that the simpler
! strategy which deletes steps (4) and (5) and substitutes "(5') play a
! random vacant square" can be beaten in 2/35 games.
!--------------------------------------------------------------------------

Array MagicSquare   -> 1 8 3 6 4 2 5 0 7;  ! see above
Array OXBoard       -> 0 0 0 0 0 0 0 0 0;  ! the entries in the board
Array OXCorners     -> 0 2 6 8;            ! the four corners
Array OXEdges       -> 1 3 5 7;            ! the four edges
Array OXDisplay -> " OX";                  ! characters to display

Object  OXQuestion "noughts and crosses"
 with   name "noughts" "and" "crosses" "tic" "tac" "toe" "tic-tac-toe",
        next OXPaper,
        state 0,
        description "~I told you I was unbeatable at noughts and crosses,~
            says Catharine."
            "~It's such a simple game to master,~ says Catharine, ~that \
            you have no chance of beating me without cheating.~";

Object  OXPaper "piece of paper"
        ! general if seen instructions
 with   name "paper" "piece" "of",
        puzzle_state 0,
        puzzle_name "Noughts and crosses",
        description [ i j k;
            font off;
            new_line;
            for (i = 2: i >= 0: i--) {
                for (j = 0: j < 3: j++) {
                    k = OXBoard->(i*3+j);
                    spaces 1;
                    if (k ~= 0)
                        print char (OXDisplay->k);
                    else {
                        if (help == 0)
                            spaces 1;
                        else
                            print i*3+j+1;
                    }
                    spaces 1;
                    if (j ~= 2) print "|";
                }
                new_line;
                if (i ~= 0) print "---+---+---^";
            }
            font on;
            if (self hasnt general) {
                give self general;
                "^[The squares are numbered from 1 to 9, with 1 at the \
                bottom left and 9 at the top right. To play, type ~play o \
                at 1~, or just ~play 1~ for short. To see the numbers, \
                type ~help on~; to turn them off, type ~help off~.]";
            }
            rtrue;
        ],
        before [ i;
         Take,Remove:
            "~But we're in the middle of a game!~ Catharine exclaims.";
         Invoke:
            move self to Toyshop;
            move OXQuestion to AskQuestions;
            <Reset self>;
            OXBoard->4 = 2;
            self.puzzle_state = 1;
            Catharine.state = 1;
            score ++;
            "^Catharine extracts a piece of paper from the chest, places \
            it on the floor, and sits by it. She draws a three by three \
            grid of squares, and carefully writes an ~X~ into the centre \
            square. ~Try playing me at noughts and crosses,~ she says to \
            you. ~But I warn you, I'm unbeatable!~";
         Reset:
            for (i = 0: i < 9: i++)
                OXBoard->i = 0;
        ];

! returns 1 if play didn't happen.
! returns 2 if play happened and the game continues.
! returns 3 if play happened and the player won.
! returns 4 if play happened and game over (a draw).

[ OXPlay
    what  ! the value being played (1 = O, 2 = X) (INPUT)
    where ! the board position to be played (INPUT)
    who   ! who is playing it (Catharine or player) (INPUT)
    i j;  ! loop counters

    if (who ~= player or Catharine ||
        where < 0 || where > 8 ||
        what ~= 1 or 2)
        "** Error: call to `OXPlay' with bad arguments **";
    if (OXBoard->where ~= 0) {
        if (who == Catharine)
            "** Error: Catharine tried to play in non-empty location **";
        "~You can't play there,~ Catharine gently admonishes you. ~That \
        square's already occupied.~";
    }
    OXBoard->where = what;
    for (i = 0: i < 9: i++) {
        if (i ~= where && OXBoard->i == what) {
            j = 12 - MagicSquare->where - MagicSquare->i;
            if (j >= 0 && j < 9) {
                j = MagicSquare->(8-j);
                if (j ~= where && j ~= i && OXBoard->j == what)
                    return 3;
            }
        }
    }
    for (i = 0: i < 9: i++)
        if (OXBoard->i == 0)
            return 2;
    return 4;
];

[ OXPlayPlayer
    what ! The value being played (1 = O, 2 = X) (INPUT)
    move ! The move decided upon for the player and for Catharine
    i j k l a;

    if (DBPaper in Toyshop)
        "Use a letter to indicate which edge to draw: ~play a~, for \
        example.";
    if (OXPaper notin Toyshop)
        "You're not playing a game at the moment.";
    if (special_number < 1 || special_number > 9)
        "Try using a number from one to nine.";
    switch (OXPlay(what, special_number - 1, player)) {
     1: rtrue;
     2: ; ! fall through to Catharine's move
     3: <Examine OXPaper>;
        remove OXPaper;
        OXPaper.puzzle_state = 3;
        Catharine.state = 0;
        score ++;
        "^~Well done,~ says Catharine. ~You appear to have won. That's \
        definitely three in a row you have there. And to think that I \
        considered myself unbeatable! It just goes to show that you can \
        always learn something new about a game.~ She tidies the paper \
        away.";
     4: <Examine OXPaper>;
        jump Drawn;
    }

    ! Catharine's strategy
    ! --------------------

    ! (1) Check that the centre has an X in it.
    if (OXBoard->4 ~= 2)
        "** Error: centre square not played in **";

    ! (2) See if there's a win for X; (3) then a blockable win for O
    for (i = 2: i >= 1: i--) {
        for (j = 0: j < 9: j++) {
            for (k = 0: k < 9: k++) {
                if (j ~= k && OXBoard->j == i && OXBoard->k == i) {
                    l = 12 - MagicSquare->j - MagicSquare->k;
                    if (l >= 0 && l < 9) {
                        l = MagicSquare->(8-l);
                        if (l ~= j && l ~= k && OXBoard->l == 0) {
                            move = l;
                            jump FoundOXMove;
                        }
                    }
                }
            }
        }
    }

    ! (4) Select a corner if possible, (5) otherwise an edge
    for (i = 0: i < 2: i++) {
        if (i == 0) a = OXCorners;
        else a = OXEdges;
        for (j = 0, k = 0: j < 4: j++)
            if (OXBoard->(a->j) == 0)
                k++;
        if (k > 0) {
            l = random(k);
            for (j = 0, k = 0: j < 4: j++) {
                if (OXBoard->(a->j) == 0) {
                    k++;
                    if (l == k) {
                        move = a->j;
                        jump FoundOXMove;
                    }
                }
            }
        }
    }

    ! No move was found (this shouldn't happen)
    "** Error: Catharine couldn't find a move **";

    .FoundOXMove;
    i = OXPlay(2, move, Catharine);
    if (i < 2) rtrue;
    print "Catharine plays square number ", (EnglishNumber) (move+1), ".^";
    <Examine OXPaper>;
    if (i == 2) "^~Your move,~ she says.";
    if (i == 3)
        print "^~I won!~ exclaims Catharine. ~You see, I'm unbeatable at \
            noughts and crosses! But ";
    if (i == 4) {
        .Drawn;
        print "^~It's a draw,~ says Catharine. ~You don't play so \
            badly. But you'll need to play better than that to beat me! ";
    }
    print "I'll give you another chance.~ She clears away the old piece \
        of paper, brings out a new one, and marks her X in the centre \
        square.^";
    OXPaper.puzzle_state = 2;
    <Reset OXPaper>;
    OXBoard->4 = 2;
    <Examine OXPaper>;
    "^~Your move,~ she says.";
];

[ XMoveSub; OXPlayPlayer(2); ];
[ OMoveSub; OXPlayPlayer(1); ];

Verb "play"
    * "at" number     -> OMove
    * number          -> OMove
    * "o" "at" number -> OMove
    * "x" "at" number -> XMove;


!--------------------------------------------------------------------------
! 2.3 THE GNOMON
!--------------------------------------------------------------------------
! The gnomon pastiches a puzzle in Brian Moriarty's game "Trinity",
! although Moriarty never used the word "chirality".
!--------------------------------------------------------------------------

Object  GnomonQuestion "gnomon and the sundial"
 with   name "gnomon" "sundial",
        next Gnomon,
        state 0,
        description "~The word `chirality' means handedness,~ says \
            Catharine. ~It refers to the sense in which a spiral coils, \
            clockwise or anticlockwise.~"
            "~The only thing stopping you is the screw thread on the \
            gnomon,~ says Catharine. ~If it weren't for that, it would be \
            easy.~"
            "~Can you find a way to reflect the gnomon?~ asks \
            Catharine. ~Remember, a reflection in three dimensions is a \
            rotation in four dimensions.~ She giggles."
            "~If you can't reflect the screw thread,~ says Catharaine, \
            ~maybe you could find a way to file down the gnomon so it \
            fits?~";

Object  Sundial "sundial"
 has    static supporter
        ! general if you've seen it
 with   name "sundial" "sun" "dial",
        capacity 4,
        description [ i;
            if (self hasnt general)
                print "You're not quite sure why you never noticed the \
                    sundial before. ";
            print "It's a square pedestal of stone, with markings for the \
                hours. The gnomon ";
            if (Gnomon in self)
                print "casts a shadow over the flat surface";
            else
                print "is missing, and there is a small hole in the \
                    centre of the flat surface";
            if (children(self)>1 || (Gnomon notin self && children(self)>0)) {
                objectloop(i in self) give i workflag;
                give Gnomon ~workflag;
                print ". On the sundial";
                WriteListFrom(child(self), ISARE_BIT + ENGLISH_BIT +
                    WORKFLAG_BIT);
            }
            ".";
        ],
        before [;
         Receive,Tie:
            if (noun ~= Gnomon) rfalse;
            if (Gnomon hasnt general) {
                Gnomon.puzzle_state = 2;
                "You attempt to screw the gnomon into the sundial, but it \
                won't fit. After some examination, you realise that this \
                is because the screw threads of the gnomon and the \
                sundial have opposite chirality.";
            }
            if (Gnomon hasnt sticky)
                "The filed-down gnomon fits into the hole, but the fit \
                isn't good, and it wobbles. You take it out, dissatisfied \
                with your efforts.";
            give Gnomon static ~sticky;
            move Gnomon to self;
            move Shadow to Toyshop;
            Gnomon.puzzle_state = 3;
            score ++;
            "The filed-down gnomon fits into the hole and adheres.";
        ];

Object  Gnomon "gnomon"
        ! general if filed down
        ! static if glued to sundial
 with   name "gnomon",
        puzzle_state 0,
        puzzle_name "The gnomon and the sundial",
        puzzle_pre CardboardBox,
        description [;
            print "It's the part of a sundial that casts the shadow. Made \
                of metal, there is a long screw on one end";
            if (self has general)
                print ". The screw thread has been filed off";
            ".";
        ],
        before [;
         Tie:
            if (second == Sundial) <<PutOn self Sundial>>;
         Invoke:
            move self to player;
            move GnomonQuestion to AskQuestions;
            move Sundial to Toyshop;
            self.puzzle_state = 1;
            score ++;
            "^~You might like to have a go at this problem,~ says \
            Catharine. She reaches into a pocket and extracts a strange \
            metal object. ~It's a gnomon,~ she says, handing it to \
            you. ~It's the missing part of that sundial over there. Can \
            you repair it?~";
        ];

Object  Shadow "shadow"
 has    scenery
 with   name "shade" "shadow",
        description "The gnomon casts a shadow across the sundial. The \
            tip of the shadow just reaches the marking for tea-time.",
        before [;
         Examine: ;
         Receive:
            if (noun ~= Mushroom || Mushroom has general)
                <<PutOn noun Sundial>>;
            give Mushroom general;
            move Mushroom to Sundial;
            move Rod to Sundial;
            "You place the mushroom on the sundial. As the tip of the \
            shadow falls across the mushroom, a white door appears in the \
            mushroom's stalk. An inch-high man in shorts and t-shirt \
            walks through the door and stares up at you, aghast, before \
            turning on his heels and exiting through the door, which \
            vanishes as soon as it closes. He left something behind, \
            though.";
         default: "You can't do that to a shadow.";
        ];

[ FileWithSub;
    if (second hasnt is_disk)
        print_ret (The) second, " won't make a good file.";
    if (noun has animate)
        "That seems rather a dangerous thing to attempt.";
    if (noun ~= Gnomon)
        "Futile.";
    if (Gnomon has general)
        "You've already filed down the gnomon.";
    give Gnomon general;
    "The disk from the Towers of Hanoi set isn't a particularly good \
    file, but you make do, and with a lot of effort you manage to file \
    down the gnomon's screw thread.";
];

Verb "file"
    * noun "with" held -> FileWith
    * "down" noun "with" held -> FileWith;

Extend "screw"
    * held "into" noun -> Insert;


!--------------------------------------------------------------------------
! 2.4 THE TOWERS OF HANOI
!--------------------------------------------------------------------------

Object  HanoiQuestion "towers of Hanoi"
 with   name "hanoi" "towers" "of" "peg" "disk" "disc" "disks" "discs"
            "pegs",
        next HanoiBoard,
        state 0,
        description "~The usual solution is recusive,~ says Catharine. \
            ~For example, to move a stack of three disks from the left \
            peg to the right peg, first move a stack of two disks to the \
            middle peg, then move the big disk, then move the stack of \
            two again."
            "~I said the usual solution is recursive,~ says Catharine, \
            ~but you'll see if you take the trouble to work it out that \
            three disks take seven moves to transport. So you'll have to \
            be cleverer than that to move them in six."
            "~The usual recursive solution is in fact minimal,~ says \
            Catharine. ~So you'll have to cheat to do better than that.~"
            "~If you could move two disks at a time,~ says Catharine, ~it \
            would be easy, but the rules only allow you to pick up the \
            top disk from a stack.~";

Object  HanoiBoard "Towers of Hanoi board"
 has    supporter
        ! general if explained the names of the components
 with   name "tower" "towers" "of" "hanoi" "board" "wooden" "wood",
        number 0,     ! counts the number of moves made
        state 0,      ! counts number of times play tried to cheat
        prev_num 0,   ! previous value of number, so C can announce change
        puzzle_state 0,
        puzzle_name "The towers of Hanoi",
        puzzle_pre CardboardBox,
        description [ i j k l;
            ! We can provide a text-only description
            if (pretty == 0) {
                print "A wooden board, bearing three pegs.^";
                <Examine LeftPeg>;
                <Examine MiddlePeg>;
                <Examine RightPeg>;
                give self general;
                rtrue;
            }

            ! Alternatively, draw a picture
            new_line;
            font off;
            for (i = 0: i < 3: i++) {
                for (j = LeftPeg: j ~= 0: j = j.next) {
                    for (k = 0, l = j: l ~= 0 && k < 3 - i: k++) {
                        if (l ~= 0 && children(l) ~= 0)
                            l = child(l);
                        else l = 0;
                    }
                    if (l ~= 0) l = l.number;
                    spaces(6-l*2);
                    for (k = 0: k < l: k++) print "==";
                    print "|";
                    for (k = 0: k < l: k++) print "==";
                    spaces(8-l*2);
                }
                new_line;
            }
            print "------+--------------+--------------+------^";
            font on;
            if (self hasnt general) {
                give self general;
                "^[The disks are called ~small~, ~medium~ and ~big~; the \
                pegs are ~left~, ~middle~ and ~right~.]";
            }
            rtrue;
        ],
        capacity 3,
        before [;
         Take,Remove:
            "It's easier to play with the Towers of Hanoi when they're on \
            the floor.";
         Receive:
            if (noun has is_disk)
                "You should say which peg you want to put it on.";
            "There's only room for the three pegs.";
         Invoke:
            move self to Toyshop;
            move HanoiQuestion to AskQuestions;
            <Reset self>;
            self.puzzle_state = 1;
            score ++;
            "^Catharine rummages around in the chest and extracts a large \
            wooden board on which three pegs are set. Three metal disks, \
            each with a hole in the middle, are threaded onto the left \
            peg. ~This puzzle is called the Towers of Hanoi,~ she says. \
            ~The idea is to move the three disks from the left peg to the \
            right peg in only six moves, without putting a disk on top of \
            a smaller one, and only picking up the top disk from a \
            peg. Here, have a go.~";
         Reset:
            remove BigDisk;
            remove MediumDisk;
            remove SmallDisk;
            move BigDisk to LeftPeg;
            move MediumDisk to BigDisk;
            move SmallDisk to MediumDisk;
            self.state = 0;
            self.number = 0;
            self.prev_num = 0;
        ],
        each_turn [;
            if (BigDisk in RightPeg && MediumDisk in BigDisk &&
                SmallDisk in MediumDisk) {
                self.puzzle_state = 3;
                self.each_turn = NULL;
                score ++;
                "^~You did it!~ says Catharine excitedly. ~And there was \
                me thinking that it took two to the power of n moves, \
                less one, to move a stack of n disks from one peg to \
                another!~";
            }
            if (self.number >= 6) {
                <Reset self>;
                self.puzzle_state = 2;
                "^~You've had your six moves,~ says Catharine, ~and it \
                doesn't look as though you've managed to move the stack \
                successfully. It's back to the beginning for you.~ She \
                collects up the disks and puts them back on the left \
                peg.";
            }
            if (self.state >= 3) {
                self.state = 0;
                switch (random(3)) {
                 1: "^~You don't seem to be getting the hang of this,~ \
                    she says. ~Perhaps you should try a different game.~";
                 2: "^~The rules are really very simple,~ she says, ~One \
                    disk at a time, and never put a larger disk on a \
                    smaller one.~";
                 3: "^~There's nothing to be gained from cheating like
                    that,~ she says.";
                }
            }
            if (self.number ~= self.prev_num) {
                self.prev_num = self.number;
                print "^~That's ", (EnglishNumber) self.number, " move";
                if (self.number > 1) print "s";
                ",~ says Catharine.";
            }
        ];

Class   PegClass
 has    concealed static supporter is_peg
 with   name "peg",
        capacity 1,
        description [ i;
            if (children(self) == 0) print_ret (The) self, " is empty.";
            print "On ", (the) self;
            if (children(child(self)) == 0) print " is ";
            else print " are ";
            for (i = self: children(i) ~= 0:) {
                i = child(i);
                print (a) i;
                if (children(i) ~= 0) {
                    if (children(child(i)) == 0)
                        print " and ";
                    else print ", ";
                }
            }
            ".";
        ],
        before [;
         Take,Remove,Push,Pull,Turn:
            print_ret (The) self, " is fixed to the board.";
         Receive:
            if (noun hasnt is_disk)
                "You can only put disks on the peg.";
            if (children(self) ~= 0)
                <<PutOn noun child(self)>>;
        ];

Object  LeftPeg "left peg" HanoiBoard
 class  PegClass
 with   name "left",
        next MiddlePeg;

Object  MiddlePeg "middle peg" HanoiBoard
 class  PegClass
 with   name "middle",
        next RightPeg;

Object  RightPeg "right peg" HanoiBoard
 class  PegClass
 with   name "right",
        next 0;

Global MovingStuckDisk = 0;

Class   DiskClass
 has    supporter is_disk
 with   name "disk" "disc" "metal",
        capacity 1,
        stuck_to 0,
        description [ i;
            for (i = self: i ~= 0 && i hasnt is_peg: i = parent(i));
            print (The) self, " is made of metal, with a very rough \
                surface: it is painful to run your finger across \
                it. There is a hole in the middle";
            if (i hasnt is_peg) ".";
            print ". It is on ", (the) i;
            if (parent(self) == i && children(self) == 0) ".";
            print ". It ";
            if (parent(self) ~= i) {
                print "rests on ", (the) parent(self);
                if (children(self) ~= 0) print " and ";
            }
            if (children(self) ~= 0)
                print "supports ", (the) child(self);
            ".";
        ],
        before [ i;
         Take,Remove:
            if (self.stuck_to ~= 0) {
                i = parent(self.stuck_to);
                if (i hasnt is_disk && i hasnt is_peg)
                    print_ret (The) self, " appears to be firmly stuck to ",
                        (the) self.stuck_to, ".";
            }
            if (HanoiBoard.puzzle_state < 3 && children(self) ~= 0 && 
              MovingStuckDisk == 0) {
                HanoiBoard.state = HanoiBoard.state + 1;
                "~You can only take the top disk from a peg,~ says \
                Catharine.";
            }
            if (parent(self) hasnt is_disk && parent(self) hasnt is_peg)
                rfalse;
            if (HanoiBoard.puzzle_state < 3) {
                for (i = BigDisk: i ~= 0: i = i.next)
                    if (parent(i) hasnt is_disk && parent(i) hasnt is_peg) {
                        HanoiBoard.state = HanoiBoard.state + 1;
                        "~You can only have one disk at a time,~ says \
                        Catharine.";
                }
            }
            if (self.stuck_to ~= 0) {
                i = parent(self.stuck_to);
                if (i has is_disk || i has is_peg) {
                    MovingStuckDisk = 1;
                    <(action) self.stuck_to second>;
                    ResetVagueWords(self.stuck_to);
                    MovingStuckDisk = 0;
                    rtrue;
                }
                "** Error: disk came unstuck **";
            }
         Receive:
            if (noun hasnt is_disk)
                print_ret "You can't put ",(the)noun," on ",(the)self,".";
            if (parent(self) hasnt is_peg && parent(self) hasnt is_disk)
                print_ret "It would be better to put ", (the) self, " on \
                    a peg first.";
            if (HanoiBoard.puzzle_state < 3 && noun.number > self.number) {
                HanoiBoard.state = HanoiBoard.state + 1;
                print_ret "~You could put ", (the) self, " on ", (the) noun,
                    ",~ says Catharine, ~but putting ", (the) noun, " on \
                    ", (the) self, " is not allowed.~";
            }
            if (children(self) > 0)
                <<PutOn noun child(self)>>;
        ],
        after [;
         Take,Remove:
            if (MovingStuckDisk == 1)
                print_ret "Taken. ", (The) self, " comes with it.";
         PutOn:
            if (second hasnt is_disk && second hasnt is_peg) rfalse;
            HanoiBoard.number = HanoiBoard.number + 1;
            if (noun has sticky || second has sticky) {
                give noun ~sticky;
                give second ~sticky;
                noun.stuck_to = second;
                print (The) noun, " adheres to ", (the) second, ".^";
            }
            if (pretty == 1)
                <<Examine HanoiBoard>>;
        ];

Object  BigDisk "big disk" LeftPeg
 class  DiskClass
 with   name "big" "large",
        number 3,
        next MediumDisk;

Object  MediumDisk "medium disk" BigDisk
 class  DiskClass
 with   name "medium",
        number 2,
        next SmallDisk;

Object  SmallDisk "small disk" MediumDisk
 class  DiskClass
 with   name "small",
        number 1,
        next 0;


!--------------------------------------------------------------------------
! 2.5 DOTS AND BOXES
!--------------------------------------------------------------------------
! Catharine's strategy is remarkably simple, and is adapted from the
! discussion in "Winning Ways" volume 2 (Conway, Berlekamp and Guy).  It
! is, (1) capture any available boxes; (2) if the position is in the
! dictionary, play the dictionary move; (3) if the play that is
! rotationally symmetrical to the player's last move is available, play
! that; (4) play randomly.  Only 2 essentially different dictionary
! positions are needed.
! 
! Dots and boxes on a 2x2 board is a win for the first player, but I
! believe that this second-player strategy is `optimal' in the sense that
! it wins if the first player makes a single mistake.
! 
! `DBBoxes' is really a 4x4 array; it contains the numbers of the four
! edges for each of the four boxes (to make it easy to check which boxes
! have been closed by a move).
! 
! `DBInput' maps edge number to the word specifying that edge.  The
! slightly strange orders of lettering of the edges on the board is to
! prevent the sequence of plays `abcd' from being a win (I think that would
! be too obvious).
! 
! `DBDictionary' contains 16 positions (really 2 positions in each of 8
! orientations - it seemed simpler to extend the dictionary than add code
! to do the rotations and reflections); the board is encoded by a 12-bit
! binary number and is followed by the correct move in that position.
! 
! We need the global variable `NoBoxes' because the `DBPlay' function has
! to return two pieces of information: the game status, and the number of
! boxes captured.
!--------------------------------------------------------------------------

Global NoBoxes; ! number of boxes captured in the most recent move
Array DBBoard -> 0 0 0 0 0 0 0 0 0 0 0 0; ! the edges of the board
Array DBOwner -> 0 0 0 0;                 ! box owner (0=none, 1=P, 2=C)
Array DBDisplay --> " " "Z" "C";          ! components of display
Array DBBoxes -> 0 2 3 5 1 3 4 6 5 7 8 10 6 8 9 11;
Array DBInput --> n$a n$b n$g n$h n$i n$c n$d n$j n$k n$l n$e n$f;
Array DBDictionary -->
    $$111000000101  4    $$111000010111  4
    $$101010000101  1    $$101010010111  1
    $$101000010101 10    $$111010010101 10
    $$101000000111  7    $$111010000111  7
    $$110010010010  2    $$110010010111  2
    $$011010010010  0    $$011010010111  0
    $$010010010110 11    $$111010010110 11
    $$010010010011  9    $$111010010011  9;

Object  DBQuestion "dots and boxes"
 with   name "dots" "and" "boxes",
        next DBPaper,
        state 0,
        description "~Did you never play dots and boxes when you were a \
            child?~ asks Catharine. ~Many children play it, but few \
            continue to have any interest in it when they grow up, which \
            I think is a shame, because it's full of interesting tricks: \
            struggles for control, sacrifices and lots of game theory.~";

Object  DBPaper "piece of paper"
        ! general if seen instructions
 with   name "piece" "of" "paper",
        puzzle_state 0,
        puzzle_pre OXPaper CardboardBox,
        puzzle_name "Dots and boxes",
        description [ i j k;
            if (self hasnt general) {
                give self general;
                font off;
                print "^+ a + b +^g   h   i^+ c + d +^j   k   l^+ e + f +^";
                font on;
                "^[The edges are labelled with letters from A to L. To \
                fill in an edge, type for example ~play A~. Type ~help \
                on~ to see the letters; type ~help off~ to turn them \
                off.]";
            }
            new_line;
            font off;
            for (i = 0: i < 3: i++) {
                for (j = 0: j < 2: j++) {
                    k = DBBoard->(i*5+j);
                    print "+";
                    if (k == 1)
                        print "---";
                    else {
                        if (help == 1)
                            print " ", (address) DBInput-->(i*5+j), " ";
                        else
                            spaces 3;
                    }
                }
                print "+^";
                if (i ~= 2) {
                    for (j = 0: j < 3: j++) {
                        k = DBBoard->(i*5+j+2);
                        if (k == 1)
                            print "|";
                        else {
                            if (help == 1)
                                print (address) DBInput-->(i*5+j+2);
                            else
                                spaces 1;
                        }
                        if (j ~= 2) {
                            print " ", (string)
                                (DBDisplay-->(DBOwner->(i*2+j))), " ";
                        }
                    }
                    new_line;
                }
            }
            font on;
            rtrue;
        ],
        before [ i;
         Take,Remove:
            "~But we're in the middle of a game!~ Catharine exclaims.";
         Invoke:
            move self to Toyshop;
            move DBQuestion to AskQuestions;
            <Reset self>;
            self.puzzle_state = 1;
            Catharine.state = 1;
            score ++;
            "^Catharine extracts a piece of paper from the chest, sits \
            down by it, and draws nine dots in three rows of three. ~Do \
            you know how to play dots and boxes?~ she asks. ~We take \
            turns to draw an edge between two dots. Whoever draws the \
            last edge around one of the four boxes captures that box and \
            writes their initial in it - that's `Z' for you and `C' for \
            me. I'll let you start, but since it's an advantage to start, \
            if we get two boxes apiece then I win. And I warn you, I'm \
            unbeatable.~";
         Reset:
            for (i = 0: i < 12: i++)
                DBBoard->i = 0;
            for (i = 0: i < 4: i++)
                DBOwner->i = 0;
        ];

! returns 1 if play didn't happen
! returns 2 if move as normal
! returns 3 if game over
! returns 4 if complimenting move

[ DBPlay
    where ! edge to be drawn (0 to 11) (INPUT)
    who   ! who is drawing the edge (Catharine or player) (INPUT)
    i j;  ! loop counters

    if (who ~= player or Catharine ||
        where < 0 || where >= 12)
        "** Error: call to `DBPlay' with bad arguments **";
    if (DBBoard->where ~= 0) {
        if (who == Catharine)
            "** Error: Catharine tried to play in non-empty location **";
        "~You can't play there,~ Catharine gently admonishes you. ~That \
        edge has already been drawn.~";
    }
    DBBoard->where = 1;

    ! search for filled-in boxes
    for (NoBoxes = 0, i = 0: i < 4: i++) {
        if (DBOwner->i == 0) {
            for (j = 0: j < 4: j++)
                if (DBBoard->(DBBoxes->(i*4+j)) == 0)
                    jump NextBox;
            if (who == player)
                DBOwner->i = 1;
            else
                DBOwner->i = 2;
            NoBoxes ++;
            .NextBox;
        }
    }

    ! check for game end
    for (i = 0: i < 12: i++)
        if (DBBoard->i == 0) {
            if (NoBoxes > 0)
                return 4;
            return 2;
        }
    return 3;
];

[ DBMoveSub
    pmove ! move selected by the player
    cmove ! move selected by Catharine
    i j k ! loop counters
    sq    ! counts the number of boxes captured by Catharine in her moves
    c     ! counts number of boxes owned by Catharine at end of game
    end;  ! set to 1 iff game is over

    if (OXPaper in Toyshop)
        "The squares are numbered from 1 to 9, with 1 at the bottom left \
        and 9 at the top right. To play, type ~play o at 1~, or just \
        ~play 1~ for short.";
    if (DBPaper notin Toyshop)
        "You're not playing a game at the moment.";
    for (pmove = 0: pmove < 12: pmove++)
        if (special_word == DBInput-->pmove)
            jump DecodedMove;
    "Try using a letter from A to L.";

    .DecodedMove;
    give DBPaper general;
    switch (DBPlay(pmove, player)) {
     1: rtrue;
     2: ;
     3: end = 1; jump DisplayBoard;
     4: <Examine DBPaper>; "^~You get another move,~ says Catharine.";
    }

    ! Catharine's strategy
    ! --------------------

    ! (1) Complete any boxes
    do {
        k = 0; ! set to 1 if any boxes were captured
        for (i = 0: i < 4: i++) {
            for (c = 0, j = 0: j < 4: j++) {
                if (DBBoard->(DBBoxes->(i*4+j)) == 1)
                    c ++;
                else
                    cmove = DBBoxes->(i*4+j);
            }
            if (c == 3) {
                switch (DBPlay(cmove, Catharine)) {
                 1: rtrue;
                 2: "** Error: Catharine closed a box but got no \
                     complimenting move **";
                 3: sq = sq + NoBoxes; cmove = -1; end = 1; jump DoneMove;
                 4: sq = sq + NoBoxes; k = 1;
                }
            }
        }
    } until (k == 0); ! loop until no more boxes can be captured

    ! (2) See if the position is in the dictionary
    for (k = 0, i = 0: i < 12: i++)
        k = k * 2 + DBBoard->i;
    for (i = 0: i < 16: i++)
        if (k == DBDictionary-->(i*2)) {
            cmove = DBDictionary-->(i*2+1);
            jump FoundDBMove;
        }

    ! (3) See if the symmetry move is available
    if (DBBoard->(11 - pmove) == 0) {
        cmove = 11 - pmove;
        jump FoundDBMove;
    }

    ! (4) Play randomly
    for (j = 0, i = 0: i < 12: i++)
        if (DBBoard->i == 0)
            j++;
    if (j > 0) {
        k = random(j);
        for (j = 0, i = 0: i < 12: i++) {
            if (DBBoard->i == 0) {
                j ++;
                if (k == j) {
                    cmove = i;
                    jump FoundDBMove;
                }
            }
        }
    }

    ! No move was found (shouldn't happen)
    "** Error: unable to find move for Catharine **";

    ! Catharine can't end the game or capture a box here, because if either
    ! were possible, it would have happened at stage (1).
    .FoundDBMove;
    switch (DBPlay(cmove, Catharine)) {
     1: rtrue;
     2: ;
     3: "** Error: Catharine ended the game with symmetry/random move **";
     4: "** Error: Catharine closed a box with the symmetry/random move **";
    }

    .DoneMove;
    print "Catharine ";
    if (sq > 0) {
        print "closes ", (EnglishNumber) sq, " box";
        if (sq > 1) print "es";
        if (cmove >= 0) print " and ";
    }
    if (cmove >= 0)
        print "draws the line ~", (address) DBInput-->cmove, "~";
    print ".^";

    .DisplayBoard;
    <Examine DBPaper>;
    if (end == 1) {
        for (c = 0, i = 0: i < 4: i++)
            if (DBOwner->i == 2)
                c++;
        print "^~The game is over,~ says Catharine. ~You scored ",
            (EnglishNumber) (4-c), " box";
        if (c ~= 3) print "es";
        ! Catharine scores at least one box always, so no problem here:
        print " to my ", (EnglishNumber) c, ".";
        if (c >= 2) {
            <Reset DBPaper>;
            DBPaper.puzzle_state = 2;
            if (c == 2)
                print " Two boxes apiece, which means that, as second \
                    player,";
            " I won! I told you I was unbeatable! But I'll give you \
            another chance.~ She tidies away the finished game and brings \
            out a new piece of paper, and draws a grid of nine dots on \
            it. ~I'll let you start again.~";
        }
        remove DBPaper;
        DBPaper.puzzle_state = 3;
        Catharine.state = 0;
        score ++;
        " It seems that you won. Well, my. I was sure that Mister Conway \
        said that my symmetry strategy would never fail. Still, we live \
        and learn.~ She tidies away the finished game.";
    }
    "^~Your move,~ she says.";
];

Extend "play"
    * special -> DBMove;


!--------------------------------------------------------------------------
! 2.6 THE ROBOT MOUSE
!--------------------------------------------------------------------------
! This puzzle pastiches one in "Curses" by Graham Nelson.  My version is
! easier in some ways, such as "put mouse in hole" being recognised, and
! harder in others...
!--------------------------------------------------------------------------

Object  MouseQuestion "robot mouse"
 with   name "robot" "mouse" "hole" "cat" "maze" "key" "mousehole",
        next RobotMouse,
        state 0,
        description "~I can offer you a few hints about how the cat \
            works,~ says Catharine. ~First, the cat only moves after \
            the mouse has moved successfully.~"
            "~The cat's strategy,~ says Catharine, ~is always to move so \
            as to minimise the distance between it and the mouse.~"
            "~If the cat's strategy doesn't provide a unique move,~ says \
            Catharine, ~then it moves to the intersection occupied by the \
            mouse at the end of the last turn, if possible. Otherwise, it \
            chooses randomly among the moves that bring it closest to the \
            mouse.~"
            "~If you think about what I've said,~ says Catharine, ~and \
            play with some counters on paper, you'll realise that it's \
            just a parity problem. The cat is always an even number of \
            moves away from the mouse at the end of its turn, and the cat \
            can always reduce this number if the mouse doesn't \
            retreat.~"
            "~Now, if the mouse didn't start an even number of moves away \
            from the cat,~ says Catharine, ~the game would be a very \
            different thing altogether.~";

Object  Maze "maze";
        ! general if mouse has the key

Global mouse_x;
Global mouse_y;
Global cat_x;
Global cat_y;
Global last_mouse_x;
Global last_mouse_y;
Global key_x = 1;
Global key_y = 1;

Object  Mousehole "mousehole"
 has    concealed talkable container open static
 with   name "hole" "mousehole",
        description "A little hole in the wainscot of the west wall, such \
            as might have been made by mice.",
        life [;
         Order:
            if (RobotMouse notin Maze)
                "You feel silly talking to the hole.";
            RunLife(RobotMouse,##Order);
            rtrue;
        ],
        before [;
         Examine: PrintOrRun(self,description); rtrue;
         Search: "The hole is dark inside.";
         Receive:
            switch(noun) {
             Baguette:
                print "You push the baguette into the mousehole";
                if (RobotMouse in Maze && mouse_x == 4 && mouse_y == 4) {
                    mouse_x = 3;
                    ". The loaf encounters an obstacle, which you push as \
                    far into the hole as you can.";
                }
                if (RobotMouse in Maze && cat_x == 4 && cat_y == 4)
                    ". The loaf encounters an obstacle, and there is an \
                    angry hiss. Alarmed, you retrieve your bread.";
                ", but you encounter no obstruction, and retrieve your \
                bread.";
             RobotMouse:
                <Reset RobotMouse>;
                "You put the robot mouse into the hole.";
             default: "Nothing is to be gained by this.";
            }
        ];

Object  RobotMouse "robot mouse"
 has    talkable
        ! general if Catharine's explained about cat
 with   name "robot" "mouse" "grey" "fur" "wheel" "wheels" "rubber" "gray",
        description "A little mouse clad in fake grey fur, with four \
            rubber wheels.",
        puzzle_state 0,
        puzzle_pre CardboardBox,
        puzzle_name "The robot mouse",
        life [ dx dy sx sy;
         Order:
            if (action ~= ##Enter or ##Go || noun notin compass)
                "The mouse emits a querulous, unhappy bleep.";
            if (noun ~= n_obj or s_obj or e_obj && noun ~= w_obj)
                "The mouse bleeps with annoyance.";
            if (parent(self) ~= Toyshop or Maze)
                "The mouse wheels spin, but can't get a good enough grip \
                to move.";
            if (self in Toyshop && noun ~= w_obj)
                "The mouse emits a low-pitched bleep.";
            if (self in Toyshop) {
                <Reset self>;
                "The mouse just squeezes through the hole, and disappears \
                from view. Unfortunately you can't very easily talk to it \
                now...";
            }
            if (mouse_x == 4 && mouse_y == 4 && noun == e_obj) {
                move self to Toyshop;
                print "The mouse runs back out of the hole, looking \
                    dustier but none the worse for its adventure";
                if (Maze has general) {
                    give Maze ~general;
                    move BrassKey to Toyshop;
                    RobotMouse.puzzle_state = 3;
                    score ++;
                    ", and then almost looks proud as a magnet cuts out \
                    and something metal drops off the underside of the \
                    mouse onto the floor. The lost key is found!"
                }
                ".";
            }
            if ((mouse_x == 1 && noun == w_obj) ||
                (mouse_x == 4 && noun == e_obj) ||
                (mouse_y == 1 && noun == n_obj) ||
                (mouse_y == 4 && noun == s_obj))
                "From somewhere inside the hole, you hear a disconsolate \
                bleep.";
            print "From somewhere inside the hole, you hear tiny wheels \
                spin";

            last_mouse_x = mouse_x;
            last_mouse_y = mouse_y;
            switch (noun) {
             n_obj: mouse_y = mouse_y - 1;
             s_obj: mouse_y = mouse_y + 1;
             e_obj: mouse_x = mouse_x + 1;
             w_obj: mouse_x = mouse_x - 1;
            }
            ! See if the mouse has picked up the key.
            if (mouse_x == key_x && mouse_y == key_y) {
                key_x = 0;
                key_y = 0;
                give Maze general;
                print ", followed by an excitable high-pitched beep and a \
                    dull clanging noise";
            }
            print ".^";
            if (mouse_x == cat_x && mouse_y == cat_y)
                jump MouseDead;

            ! Now the cat. First see if it can chase to the last posn
            ! occupied by the mouse. If it can do this, it can't catch the
            ! mouse (by parity), so no need to check that first.
            dx = last_mouse_x - cat_x;
            dy = last_mouse_y - cat_y;
            if (dx * dx + dy * dy == 1) {
                cat_x = last_mouse_x;
                cat_y = last_mouse_y;
                rtrue;
            }

            ! Now see if it can catch the mouse
            dx = mouse_x - cat_x;
            dy = mouse_y - cat_y;
            if (dx * dx + dy * dy == 1)
                jump MouseDead;

            ! Find out which way it needs to go.
            sx = Sign(dx);
            sy = Sign(dy);
            if (sx ~= 0 && sy ~= 0) {
                if (Abs(dx) > Abs(dy)) sy = 0;
                else {
                    if (Abs(dy) > Abs(dx)) sx = 0;
                    else {
                        switch (random(2)) {
                         1: sx = 0;
                         2: sy = 0;
                        }
                    }
                }
            }
            cat_x = cat_x + sx;
            cat_y = cat_y + sy;
            rtrue;

            .MouseDead;
            move self to Toyshop;
            if (Maze has general) {
                key_x = mouse_x;
                key_y = mouse_y;
                give Maze ~general;
            }
            print "^From somewhere inside the hole, you hear a sharp hiss, \
                and a long drawn-out high-pitched squeal, and then the \
                robot mouse comes flying out of the hole at high speed, \
                looking somewhat the worse for wear.^";
            if (self hasnt general) {
                give self general;
                if (self.puzzle_state == 1)
                    self.puzzle_state = 2;
                print "^~Ah, I should have explained some things,~ says \
                    Catharine. ~There's a maze inside the wainscot in the \
                    form of a four-by-four grid of intersections, and my \
                    key is at the northwest corner. There's a robot cat \
                    in the maze too. When the robot mouse starts at the \
                    southeast corner, the robot cat goes to the northwest \
                    corner. Every time you give an instruction to the \
                    mouse and it moves from one intersection to the next, \
                    the robot cat moves too. I hope this helps.~^";
            }
            rtrue;
        ],
        before [;
         Reset:
            move self to Maze;
            mouse_x = 4;
            mouse_y = 4;
            last_mouse_x = 4;
            last_mouse_y = 4;
            cat_x = 1;
            cat_y = 1;
         Invoke:
            move Mousehole to Toyshop;
            move MouseQuestion to AskQuestions;
            move self to Toyshop;
            self.puzzle_state = 1;
            score ++;
            "^~That's a pretty robot mouse you have there,~ says \
            Catharine. ~Looks like just what I need. You see, I dropped \
            my key through the floorboards from the attic, and it's \
            fallen into the wainscot somewhere. Perhaps if you could send \
            the mouse into that mousehole over there, then it could find \
            my key.~";
        ];

[ Sign x;
    if (x < 0) return -1;
    if (x > 0) return 1;
    return 0;
];

[ Abs x;
    if (x < 0) return -x;
    return x;
];

Object  BrassKey "small brass key"
 with   name "small" "brass" "key",
        initial "Lying where the mouse dropped it is a small \
            brass-coloured key.",
        description "Brass is of course a non-magnetic copper-zinc alloy, \
            so presumably this key also contains iron.";


!--------------------------------------------------------------------------
! 2.7 TEA-TIME
!--------------------------------------------------------------------------
! The egg puzzle is a reference to Adventions' Unnkulia series, in which
! every game has a puzzle that involves cooking or otherwise manipulating
! an egg, usually for the benefit of Duhdist monks.  The inclusion of food
! is a useful excuse to have a mushroom (another Trinity reference) and a
! baguette with which to solve the robot mouse puzzle.
!--------------------------------------------------------------------------

Object  EggQuestion "egg"
 with   name "egg" "boiled" "fried" "raw" "hamper",
        article "an",
        next Hamper,
        state 0,
        description "~This isn't really a puzzle,~ says Catharine. ~It's \
            just tea-time. If you want to make a puzzle out of it, try \
            eating the egg.~"
            "Catharine says, ~If you haven't played Infocom's wonderful \
            game `Trinity' by Brian Moriarty, you're going to have \
            problems with this puzzle.~"
            "~In the game `Trinity',~ says Catharine, ~part of the action \
            takes place on the surface of a giant sundial covered with \
            mushrooms, each mushroom representing a mushroom cloud from \
            the explosion of an atomic bomb.~"
            "~The shadow from the gnomon was important too,~ says \
            Catharine. ~When it touched a mushroom, magical things would \
            happen.~"
            "Catharine says, ~You need to have played Graham Nelson's \
            marvellous game `Curses' too, or else you'll struggle.~"
            "~In the game `Curses',~ says Catharine, ~There are a number \
            of magic wands that can be charged by striking and discharged \
            by pointing them at things.~";

Object  Hamper "hamper"
 has    container open
 with   name "hamper" "picnic" "basket" "wicker" "wickerwork" "wicker-work",
        capacity 5,
        puzzle_state 0,
        puzzle_name "The egg",
        puzzle_pre Gnomon,
        description "A wicker picnic hamper.",
        before [;
         Invoke:
            move self to Toyshop;
            self.puzzle_state = 1;
            move EggQuestion to AskQuestions;
            score ++;
            "^Catharine looks at the shadow on the sundial. ~It looks \
            like it's tea-time,~ she says. ~Perhaps you would care to \
            join me in some food?~ She opens the chest, picks up a wicker \
            hamper, and closes the chest again.";
        ];

Nearby  Baguette "baguette"
 has    edible
 with   name "bread" "stick" "french" "baguette" "loaf" "breadstick",
        description "A long, thin loaf of French bread.";

Nearby  RawEgg "egg"
        ! general if you know it's raw
 with   name "egg" "shell" "eggshell",
        article "an",
        before [;
         Eat,Attack,Cut:
            if (self has general)
                "You've never really been fond of raw egg.";
            "You're not certain if the egg is raw or cooked, and it would \
            be nice to find out before you broke it.";
         Shake,Turn:
            give self general;
            Hamper.puzzle_state = 2;
            "The egg wobbles distinctly in your hand; it's clearly raw.";
        ],
        after [;
         Drop: "You put the egg down gently.";
        ];

Nearby  Mushroom "mushroom"
 has    edible
 with   name "mushroom" "fungus";

Object  Rod "featureless mahogany rod"
        ! general if struck
 with   name "featureless" "mahogany" "rod",
        description "A featureless mahogany rod, whose purpose is \
            oblique. It is about the size of a matchstick.",
        before [;
         PointAt:
            if (self hasnt general) rfalse;
            give self ~general;
            print "A tiny gout of fire leaps out from the tip of the rod, ";
            switch (second) {
             Baguette: "lightly toasting the baguette.";
             RawEgg:
                move CookedEgg to parent(RawEgg);
                StartTimer(CookedEgg,3);
                remove RawEgg;
                print "neatly cooking the egg";
                if (CookedEgg in player) {
                    move CookedEgg to Toyshop;
                    print ", which is now too hot to hold, and you drop \
                        it";
                }
                ".";
             Catharine: "but Catharine leaps backwards to avoid it. ~Mind \
                where you point that thing!~ she says.";
             Mushroom: "gently frying the mushroom.";
             selfobj: "making you uncomfortably hot.";
             default: print_ret "but ", (the) second, " seems unaffected.";
            }
         Strike:
            if (self has general) {
                remove self;
                "The already-charged rod explodes! Your hand is somewhat \
                singed.";
            }
            give self general;
            "The rod charges with etherial power, drawn up from the earth \
            through ley lines...";
        ];

Object  CookedEgg "egg"
 has    edible general
        ! general if too hot
 with   name "egg" "shell" "eggshell",
        article "an",
        before [;
         Take,Remove,Touch:
            if (self has general) "The egg is still too hot too to hold.";
         Shake,Turn: "The egg doesn't wobble at all: it's clearly \
            hard-boiled.";
        ],
        after [;
         Eat: Hamper.puzzle_state = 3; score++;
        ],
        time_left 0,
        time_out [;
            give self ~general;
        ];

[ PointAtSub; "Nothing happens."; ];

Verb "point"
    * held "at" noun -> PointAt;

[ StrikeSub; <<Attack noun>>; ];

Verb "strike"
    * noun -> Strike;

Verb "spin"
    * noun -> Turn;


!--------------------------------------------------------------------------
! 2.8 DODGEMS
!--------------------------------------------------------------------------
! This implementation of "Dodgems" uses a brute-force strategy: it encodes
! the result for every position (the table is copied from "Winning Ways",
! page 686).  The table takes up about half a kilobyte, so this approach
! doesn't seem too wasteful.
! 
! The array `DodgemsPositions' is a 45 by 45 array of 2-bit outcomes:
! 
!     0 = not possible or win for player
!     1 = win for first player
!     2 = win for second player
!     3 = win for Catharine
! 
! Each row is padded out to 48 so that it will fit in 6 words.  Catharine's
! position is looked up in the columns, the players position in the rows.
! 
! The `CLookup' and `PLookup' arrays translate from the position of one
! player's two pieces (encoded as a number from 0 to 99) to rows and
! columns, respectively, in the `DodgemsPositions' table.
! 
! Catharine's strategy when she can't win is to choose the move that
! maximises the chance that the player will make a fatal blunder on his or
! her next move.  I'm not altogether happy with the way this works: for one
! thing, it makes her strategy too deterministic, and it misses flaws that
! are beyond this one-move horizon.  An alternative would be to move
! randomly, but with a bias towards the move selected by this strategy.
! 
! If you compile with `Constant DEBUG' at the top of the source, and turn
! tracing on with `trace 1', then some information about Catharine's
! strategy is printed.
!--------------------------------------------------------------------------

Array DodgemsPositions --> [;
    $0000 $0000 $0000 $0000 $0000 $0500;
    $0000 $0000 $0000 $0000 $0000 $0540;
    $0000 $0000 $0000 $0000 $0000 $0540;
    $0000 $0000 $0000 $0000 $0051 $0f00;
    $0000 $0000 $0000 $0000 $0051 $0f00;
    $0000 $0000 $0000 $0000 $0051 $5fc0;
    $0000 $0000 $0001 $0010 $1644 $ccc0;
    $0000 $0000 $0000 $0410 $414d $5fc0;
    $0000 $0000 $1041 $0800 $0055 $5fc0;
    $0000 $0000 $0000 $4510 $00d8 $0c00;
    $0000 $0000 $0000 $4104 $00c7 $0f00;
    $0000 $0000 $0000 $4145 $00f7 $0f00;
    $0000 $0000 $0014 $4100 $3cd0 $ccc0;
    $0000 $0000 $0000 $4104 $10c3 $ffc0;
    $0000 $0000 $0000 $4545 $14f3 $ffc0;
    $0000 $0014 $0014 $4110 $3dd4 $ccc0;
    $0000 $0010 $0000 $4134 $d3cf $ffc0;
    $0000 $0000 $0000 $4155 $55ff $ffc0;
    $0001 $4114 $07d7 $4407 $fd9c $33c0;
    $0407 $c504 $5145 $1cf3 $cf3f $ffc0;
    $0c13 $4d00 $f3cf $3555 $55ff $ffc0;
    $0001 $0044 $0740 $100f $c074 $0300;
    $0000 $0000 $4100 $14d3 $403f $0f00;
    $0041 $0040 $d340 $37df $40ff $0f00;
    $0001 $0040 $075d $100f $3c70 $33c0;
    $0000 $0000 $4104 $10c3 $0c33 $ffc0;
    $0041 $0040 $d34d $37cf $3cf3 $ffc0;
    $0001 $147c $071c $100f $ff7c $33c0;
    $0000 $040c $4104 $10f3 $cf3f $ffc0;
    $0041 $0c54 $c30c $33ff $ffff $ffc0;
    $0000 $1411 $003d $d710 $33cc $ccc0;
    $0000 $1115 $0033 $c750 $3ffc $ccc0;
    $0010 $1231 $1451 $cf3c $f3cf $ffc0;
    $0044 $559c $003f $5400 $3ffc $00c0;
    $0000 $1504 $503f $1cf0 $0f3c $ccc0;
    $00d1 $0554 $d00f $3ff0 $3ffc $ccc0;
    $1410 $3d30 $0fff $440c $f3cc $33c0;
    $0400 $1f01 $d75d $0430 $c30f $ffc0;
    $04d0 $0d30 $f3cf $1f3c $f3cf $ffc0;
    $0014 $537c $0cf3 $4c0f $fffc $33c0;
    $0000 $c10d $34d3 $ccf3 $cf3f $ffc0;
    $0030 $c3fc $30c3 $0fff $ffff $ffc0;
    $1405 $7f0c $0fff $1c03 $cf3c $33c0;
    $04f3 $4ffc $03cf $3c0f $fffc $33c0;
    $0c03 $cf0c $f3cf $3cf3 $cf3f $ffc0;
];

Array CLookup ->
    99 26 38 44 25 37 43 24 36 42 26 99 11 23  2  8 20  1  5 17
    38 11 99 35 10 14 32  9 13 29 44 23 35 99 22 34 41 21 33 40
    25  2 10 22 99  7 19  0  4 16 37  8 14 34  7 99 31  6 12 28
    43 20 32 41 19 31 99 18 30 39 24  1  9 21  0  6 18 99  3 15
    36  5 13 33  4 12 30  3 99 27 42 17 29 40 16 28 39 15 27 99;

Array PLookup ->
    99  2  1  0  8  7  6 20 19 18  2 99  5  4 17 16 15 29 28 27
     1  5 99  3 14 13 12 26 25 24  0  4  3 99 11 10  9 23 22 21
     8 17 14 11 99 32 31 41 40 39  7 16 13 10 32 99 30 38 37 36
     6 15 12  9 31 30 99 35 34 33 20 29 26 23 41 38 35 99 44 43
    19 28 25 22 40 37 34 44 99 42 18 27 24 21 39 36 33 43 42 99;

Array Dodgems -> 12;                 ! three deep stack of board posns
Array DodgemsDisplay -> "12AB";      ! characters to print
Array Directions --> -1 -3 1 3;      ! directions to move pieces
Array DirectionNames --> "left" "up" "right" "down";
Array Mask --> $c000 $3000 $0c00 $0300 $00c0 $0030 $000c $0003;
Array Shift --> $4000 $1000 $0400 $0100 $0040 $0010 $0004 $0001;
Array MoveScore -> 6;

! Find the value of the position in the `DodgemsCopy' array.
[ Value
    a  ! row address, encoded from Catharine's pieces
    b  ! column address, encoded from player's pieces
    c; ! byte from table containing result

    a = CLookup->((Dodgems->0) * 10 + (Dodgems->1));
    b = PLookup->((Dodgems->2) * 10 + (Dodgems->3));
    c = DodgemsPositions-->(b * 6 + (a / 8));
    return ((c & (Mask-->(a % 8))) / (Shift-->(a % 8)));
];

! Manipulate the stack of positions
[ PushPosition i; for (i = 11: i > 3: i--) Dodgems->i = Dodgems->(i - 4); ];
[ PopPosition i; for (i = 0: i < 8: i++) Dodgems->i = Dodgems->(i + 4); ];

Object  DodgemsQuestion "game of dodgems"
 with   name "game" "dodgems" "dodgem",
        next DodgemsPaper,
        state 0,
        description "~Dodgems was invented by mathematician Colin Vout,~ \
            says Catharine. ~It's one of the more interesting games that \
            can be played on a noughts and crosses board.~"
            "~I can't offer you much in the way of strategy,~ says \
            Catharine, ~except that it isn't always wise to move a piece \
            off the board when it can be blocking your opponent's pieces \
            instead.~"
            "~A final hint,~ says Catharine. ~The northeast corner of the \
            board is a good square to aim for.~";

Object  DodgemsPaper "piece of paper"
        ! general if seen instructions
 with   name "piece" "of" "paper" "pieces",
        puzzle_state 0,
        puzzle_name "Dodgems",
        puzzle_pre DBPaper Gnomon,
        description [ i j k l;
            new_line;
            font off;
            for (i = 0: i < 3: i++) {
                for (j = 0: j < 3: j++) {
                    k = i * 3 + j + 1;
                    spaces 1;
                    for (l = 0: l < 4: l++) {
                        if (k == Dodgems->l) {
                            print char DodgemsDisplay->l;
                            jump FoundPiece;
                        }
                    }
                    spaces 1;
                    .FoundPiece;
                    spaces 1;
                    if (j ~= 2) print "|";
                }
                new_line;
                if (i ~= 2) print "---+---+---^";
            }
            font on;
            if (self hasnt general) {
                give self general;
                "^[To move piece A to the left, type ~move a left~. The \
                allowable directions are ~left~, ~right~ and ~up~.]";
            }
        ],
        before [;
         Take,Remove:
            "~But we're in the middle of a game!~ Catharine exclaims.";
         Invoke:
            move self to Toyshop;
            move DodgemsQuestion to AskQuestions;
            <Reset self>;
            self.puzzle_state = 1;
            Catharine.state = 1;
            score ++;
            "^Catharine open the chest and extracts another piece of \
            paper and four small pieces. The paper has a three-by-three \
            grid of squares on it, like a noughts and crosses \
            game. Catharine places two pieces at the left and two pieces \
            at the bottom.^^~This is a game called Dodgems,~ she \
            says. ~You have the pieces at the bottom, and I have the \
            pieces at the left. You can move your pieces up, left or \
            right, and I can move mine up, down, or right. Your aim is to \
            get both your pieces off the top of the board before I get \
            mine off the right hand side.~";
         Reset:
            Dodgems->0 = 1;
            Dodgems->1 = 4;
            Dodgems->2 = 8;
            Dodgems->3 = 9;
        ];

! returns 1 if play was illegal
! returns 2 if play happened and game continues
! returns 3 if play happened and player won

[ DodgemsPlay
    what   ! the piece being moved (0 to 3) (INPUT)
    dir    ! direction of piece (0 to 3) (INPUT)
    who    ! who's moving the piece (Catharine, player or 0) (INPUT)
    new    ! new position of piece
    i;     ! loop counter

    ! Basic sanity check on input
    if (who ~= player or Catharine or 0 ||
        what < 0 || what > 3 ||
        dir < 0 || dir > 3 ||
        (who == player or 0 && dir == 3) ||
        (who == Catharine && dir == 0))
        "** Error: call to DodgemsPlay with bad arguments **";

    ! Illegal moves:
    switch (who) {
     Catharine:
        if ((dir == 3 && (Dodgems->what) > 6) ||
            (dir == 1 && (Dodgems->what) < 4) || Dodgems->what == 0)
            rtrue;
     0:
        if ((dir == 0 && (Dodgems->what) % 3 == 1) ||
            (dir == 2 && (Dodgems->what) % 3 == 0) || Dodgems->what == 0)
            rtrue;
     player:
        if (dir == 0 && (Dodgems->what) % 3 == 1)
            "~You can't move your pieces left off the edge of the board,~ \
            says Catharine.";
        if (dir == 2 && (Dodgems->what) % 3 == 0)
            "~You can't move your pieces right off the edge of the \
            board,~ says Catharine.";
        if (Dodgems->what == 0)
            "~You can't move a piece any more once it's left the board,~ \
            says Catharine.";
    }

    ! Check for pieces moving off board (legally, that is)
    new = Dodgems->what + Directions-->dir;
    if (dir == 2 && new % 3 == 1) new = 0;
    if (dir == 1 && new < 1) new = 0;

    ! Check for a piece running into another piece
    if (new ~= 0) {
        for (i = 0: i < 4: i++) {
            if (Dodgems->i == new) {
                if (who == Catharine or 0) rtrue;
                "~You're not allowed to move a piece onto another piece,~ \
                says Catharine. ~There's no capturing in dodgems.~";
            }
        }
    }
    Dodgems->what = new;

    ! Check for a win
    if ((Dodgems->0 == 0 && Dodgems->1 == 0) ||
        (Dodgems->2 == 0 && Dodgems->3 == 0))
        return 3;
    return 2;
];

[ DodgemsPlayer
    what     ! which piece (2 or 3) (INPUT)
    dir      ! which direction to move it in (INPUT)
    a        ! count of available moves for Catharine
    b        ! count of available moves which win for her
    c        ! another count of moves which win
    move     ! the move selected
    s        ! number of opportunities for player to make a mistake
    max      ! best chance for player to make a mistake
    n        ! number of moves with the maximum score
    r        ! result of playing a move
    i j k l; ! loop counters

    switch (DodgemsPlay(what,dir,player)) {
     1: rtrue;
     2: ;
     3: .DodgemsPlayerWins;
        DodgemsPaper.puzzle_state = 3;
        Catharine.state = 0;
        score ++;
        remove DodgemsPaper;
        "^~You win!~ says Catharine. ~A splendid performance.~ She clears \
        away the paper and the pieces.";
    }

    ! Count available moves and available winnning moves; work out scores
    ! for moves that don't win.
    .DodgemStart;
    for (i = 0: i < 6: i++)
        MoveScore->i = 0;
    for (n = 0, max = 0, a = 0, b = 0, i = 0: i < 2: i++) {
        for (j = 1: j < 4: j++) {
            if (parser_trace >= 1)
                print "Catharine considering moving ", i+1, " ",
                    (string)DirectionNames-->j,"wards.^";
            PushPosition();
            r = DodgemsPlay(i, j, Catharine);
            if (r > 1) {
                a++;
                if (r == 3 || Value() >= 2) {
                    ! The move was winning; put 99 in the MoveScore array.
                    if (parser_trace >= 1)
                        print "  Move is winning^";
                    b++;
                    MoveScore->(i*3+j-1) = 99;
                }
                else {
                    ! The move was not winning, so count up how many of the
                    ! followups are losing.
                    if (parser_trace >= 1)
                        print "  Move is losing, considering responses:^";
                    for (s = 1, k = 2: k < 4: k++) {
                        for (l = 0: l < 3: l++) {
                            PushPosition();
                            r = DodgemsPlay(k, l, 0);
                            if (r == 2 && Value() == 1 or 3) {
                                s ++;
                                if (parser_trace >= 1)
                                    print "    losing response: ", s, ": ",
                                        char DodgemsDisplay->k, " ",
                                        (string) DirectionNames-->l,
                                        "wards^";
                            }
                            PopPosition();
                        }
                    }
                    MoveScore->(i*3+j-1) = s;
                    if (s == max) n ++;
                    if (s > max) {
                        n = 1;
                        max = s;
                    }
                }
            }
            PopPosition();
        }
    }

    if (parser_trace >= 1) {
        print "Result: MoveScores looks like: ";
        for (i = 0: i < 6: i++)
            print MoveScore->i, ", ";
        new_line;
    }

    ! It's possible that Catharine has no moves available
    if (a == 0) {
        print "~I'm stuck,~ says Catharine. ~Which means that you \
            lose.~^";
        r = 3;
        jump DodgemsDisplayBoard;
    }

    ! If there were any winning moves, pick one at random.  Otherwise,
    ! choose randomly among the moves that maximised the player's chance of
    ! making a mistake
    if (b > 0) {
        move = random(b);
        max = 99;
    } else {
        if (n >= 1)
            move = random(n);
        else
            move = -1; ! should never happen
    }
    for (c = 0, i = 0: i < 2: i++) {
        for (j = 1: j < 4: j++) {
            if (MoveScore->(i*3+j-1) == max)
                c ++;
            if (c == move) {
                if (parser_trace >= 1)
                    print "[Picked i = ", i, "; j = ", j, "]^";
                r = DodgemsPlay(i, j, Catharine);
                jump DodgemsDoneMove;
            }
        }
    }

    ! This should never happen:
    "** Error: unable to find a move for Catharine **";

    .DodgemsDoneMove;
    print "Catharine moves piece ", i + 1, " ", (string) DirectionNames-->j,
        "wards";
    if (Dodgems->i == 0)
        print " off the board";
    print ".^";

    .DodgemsDisplayBoard;
    <Examine DodgemsPaper>;
    if (r == 3) {
        <Reset DodgemsPaper>;
        if (DodgemsPaper.puzzle_state == 1)
            DodgemsPaper.puzzle_state = 2;
        print "^~I win!~ she says. ~However, I am feeling generous today \
            and I shall give you another chance.~ She rearranges the \
            pieces to their starting positions.^";
        <Examine DodgemsPaper>;
    }

    ! Check that player has a move
    for (i = 2: i < 4: i++) {
        for (j = 0: j < 3: j++) {
            PushPosition();
            r = DodgemsPlay(i,j,0);
            PopPosition();
            if (r > 1) jump DodgemEnd;
        }
    }

    ! If player has no move, then they win (this shouldn't happen with
    ! Catharine's strategy, but better safe than sorry).
    print "^~Oops. I appear to have boxed you in,~ she says.^";
    jump DodgemsPlayerWins;

    .DodgemEnd;
    "^~Your move,~ she says.";
];

[ DALeftSub; DodgemsPlayer(2,0); ];
[ DBLeftSub; DodgemsPlayer(3,0); ];
[ DARightSub; DodgemsPlayer(2,2); ];
[ DBRightSub; DodgemsPlayer(3,2); ];
[ DAUpSub; DodgemsPlayer(2,1); ];
[ DBUpSub; DodgemsPlayer(3,1); ];
[ DDownSub;
    "~You're not allowed to move your pieces downwards,~ says Catharine.";
];

Extend "move" first
    * "a" "left" -> DALeft
    * "b" "left" -> DBLeft
    * "a" "right" -> DARight
    * "b" "right" -> DBRight
    * "a" "up" -> DAUp
    * "b" "up" -> DBUp
    * "a" "down" -> DDown
    * "b" "down" -> DDown;


!--------------------------------------------------------------------------
! 2.9 THE INFERNAL MACHINE
!--------------------------------------------------------------------------
! The idea for this puzzle is shamelessly stolen from Raymond Smullyan's
! "Monte Carlo Lock" puzzle in his book "The Lady or the Tiger?"  That book
! has a machine which operated on strings of digits by the following rules:
! 
!     2X2 -> X
!     if X -> Y then 3X -> 2Y
!     if X -> Y then 4X -> YY
!     if X -> Y then 5X -> the reverse of Y
! 
! The puzzle was to find a string of digits that produced itself.  Here, we
! go for a more complicated system, based on Smullyan's.  People who have
! read the Smullyan book will find it an interesting, but not difficult,
! challenge.  People who haven't will have to resort to asking Catharine
! for advice.  The rules are:
! 
!     1X -> X + 1
!     if X -> Y then 2X -> Y with first character chopped off
!     if X -> Y then 3X -> 1Y
!     if X -> Y then 4X -> YY
!     if X -> Y then 5X -> the reverse of Y
! 
! I believe that the shortest self-replicating string is 543251543251, but
! I would be glad to be proved wrong.  (I decided that the simpler machine
! in which 1X generates X was too easy, since 4141 was self-replicating).
!--------------------------------------------------------------------------

Constant MAX_OUTPUT 200;             ! max number characters in output
Global input_n = 0;                  ! number of characters in input.
Array input_string -> 20;            ! stores strings of digits from 0 to 9.
Global output_n = 0;                 ! number of characters in output
Array output_string -> MAX_OUTPUT;   ! what comes out of the machine.

Object  ChestQuestion "oak chest"
 with   name "oak" "oaken" "panel" "screen" "keypad" "chest" "code"
            "combination" "lock",
        article "an",
        next PuzzleChest,
        state 0,
        description "~The first rule,~ says Catharine, ~is that the \
            number 1X - by which I mean the number consisting of 1 \
            followed by the string of digits denoted by the letter X, not \
            1 multiplied by X - generates the number X plus 1. For \
            example, the number 12345 generates the number 2346.~"
            "~The second rule,~ says Catharine, ~is that if the number X \
            generates the number Y, then the number 2X generates the \
            number Y, but with its first digit removed. For example, \
            since we know that 123 generates 24, then 2123 generates 4.~"
            "~The third rule,~ says Catharine, ~is that if X generates Y, \
            then 3X generates 1Y, for example, since 123 generates 24, \
            then 3123 generates 124.~"
            "~The fourth rule,~ says Catharine, ~is that if X generates \
            Y, then 4X generates YY, that is, Y repeated. For example, \
            since 123 generates 24, then 4123 generates 2424.~"
            "~The fifth and final rule,~ says Catharine, ~is that if X \
            generates Y, the 5X generates the reversal of Y. For example, \
            since 1234 generates 235, then 51234 generates 532.~";

Object  PuzzleChest "oak chest"
 has    static openable lockable locked
        ! general if read instructions
 with   name "chest" "screen" "keypad" "oak" "oaken",
        article "an",
        puzzle_name "The infernal machine",
        puzzle_state 0,
        puzzle_pre PuzzleChest, ! make sure it doesn't happen too soon
        state 0, ! 0 = in input mode
                 ! 1 = displaying output
        capacity 100,
        name "oak" "chest" "oaken",
        article "an",
        describe [;
            if (self has open) print "^An oak chest stands open";
            else print "^There's a closed oak chest";
            print_ret " in the ", (DirectionName) ToyChest.state, " corner.";
        ],
        description [;
            print "There's a little screen and keypad on the side of the \
                chest. ";
            <Display self>;
            if (self hasnt general) {
                give self general;
                "^[Type for example ~press 1~ to press the button marked \
                ~1~. Type ~press send~ to press the ~send~ button. Use \
                for example ~type 123~ to type a sequence of digits and \
                then press ~send~.]";
            }
            rtrue;
        ],
        before [ i;
         Display:
            print "The screen reads ~";
            switch (self.state) {
             0: print "Input: ";
                if (input_n == 0) print "none";
                for (i = 0: i < input_n: i++)
                    print input_string->i;
             1: print "Output: ";
                if (output_n == 0) print "none";
                for (i = 0: i < output_n: i++)
                    print output_string->i;
            }
            print "~.^";
         Enter:
            if (self hasnt open) rfalse;
            if (score >= 18)
                score = 20;
            deadflag = 2;
            "You climb into the chest, which turns out to be full of \
            drapery: large engulfing folds of cloth. You struggle to be \
            free of them, and pulling the last one aside, you find \
            yourself walking down a Victorian arcade. Looking around, you \
            see no sign of the toyshop, but clutched in your hands, you \
            find a wrapped parcel with a tag saying ~To Isabelle, on her \
            birthday.~ You are unsure of how you acquired it, but you are \
            somehow sure that Isabelle will enjoy it...";
         Lock, Unlock:
            if (second ~= BrassKey) rfalse;
            "Nothing happens.";
         Invoke:
            remove ToyChest;
            move self to Toyshop;
            move ChestQuestion to AskQuestions;
            self.puzzle_state = 1;
            score ++;
            "You turn the brass key in the lock, and with a click, a \
            panel in the side of the chest slides away, revealing a \
            little LCD screen and a keypad with six keys: ~1~ to ~5~ and \
            ~send~.^^~Aha!~ says Catharine, ~You've found the combination \
            lock to the chest. You probably won't get very far with it \
            unless I give you some hints, so here goes.^^~Some \
            combinations are said to generate other combinations \
            according to certain rules. When you enter a combination, the \
            chest either beeps, or displays the generated combination on \
            the screen. If a combination generates itself, then the chest \
            opens.~";
        ];

! returns 1 if unsuccessful
! returns 0 if successful

[ InputNumberSub
    nodisp;   ! 1 if the screen shouldn't be redisplayed (INPUT)

    if (PuzzleChest notin Toyshop)
        "You can't see any such thing.";
    if (special_number < 1 || special_number > 5)
        "The keys are ~1~ to ~5~ and ~send~.";
    if (input_n >= 20)
        "The chest beeps, but your digit doesn't appear on \
        screen. Perhaps you've reached the limit on the size of input.";
    input_string->input_n = special_number;
    input_n ++;
    PuzzleChest.state = 0;
    if (nodisp == 0)
        <Display PuzzleChest>;
    rfalse;
];

[ InputSendSub i;
    if (PuzzleChest notin Toyshop)
        "You can't see any such thing.";
    PuzzleChest.state = 1;
    if (PuzzleChest.puzzle_state == 1)
        PuzzleChest.puzzle_state = 2;
    switch(Decode(input_string,input_n)) {
     0: output_n = 0;
        input_n = 0;
        print "The chest beeps at you. ";
        <Display PuzzleChest>;
        rtrue;
     1: <Display PuzzleChest>;
        i = Compare();
        if (i == 0) {
            if (PuzzleChest hasnt open)
                print "^There's a series of clicks from somewhere inside \
                    the chest, and the lid pops open.^";
            if (PuzzleChest.puzzle_state < 3) {
                PuzzleChest.puzzle_state = 3;
                score ++;
            }
            give PuzzleChest open ~locked enterable container;
        }
        input_n = 0;
        rtrue;
    }
];

! Returns -1 if input less than output
! Returns 0 if input equals output
! Returns 1 if input greater than output
[ Compare i;
    if (output_n < input_n) return 1;
    if (input_n < output_n) return -1;
    for (i = 0: i < input_n: i++) {
        if (output_string->i < input_string->i) return 1;
        if (input_string->i < output_string->i) return -1;
    }
    return 0;
];

! Decodes from the array p to the output_string
! returns 1 if decoding was successful.
! returns 0 if it failed.
[ Decode
    p    ! pointer to a string to decode (INPUT)
    n    ! number of characters in the string (INPUT)
    i j; ! loop counters & miscellaneous

    if (n < 2) rfalse;
    switch (p->0) {
     1: ! copy the remainder of the input string, and add 1 to final digit
        ! (we know there will be no carry, since the maximum digit is 5).
        for (i = 1: i < n - 1: i++)
            output_string->(i-1) = p->i;
        output_string->(n-2) = p->(n-1) + 1;
        output_n = n - 1;
        rtrue;

     2: ! decode the remainder, chop the first character
        if (Decode(p+1,n-1) == 0) rfalse;
        if (output_n <= 0) rfalse;
        for (i = 1: i < output_n: i++)
            output_string->(i-1) = output_string->i;
        output_n --;
        rtrue;

     3: ! decode the remainder; put a 1 in front
        if (Decode(p+1,n-1) == 0) rfalse;
        if (output_n >= MAX_OUTPUT) rfalse;
        for (i = output_n: i > 0: i--)
            output_string->i = output_string->(i - 1);
        output_string->0 = 1;
        output_n ++;
        rtrue;

     4: ! decode the remainder, duplicate it
        if (Decode(p+1,n-1) == 0) rfalse;
        if (output_n > MAX_OUTPUT / 2) rfalse;
        for (i = 0: i < output_n: i++)
            output_string->(i+output_n) = output_string->i;
        output_n = output_n * 2;
        rtrue;

     5: ! decode the remainder, reverse it
        if (Decode(p+1,n-1) == 0) rfalse;
        for (i = 0: i < output_n / 2: i++) {
            j = output_string->i;
            output_string->i = output_string->(output_n-i-1);
            output_string->(output_n-i-1) = j;
        }
        rtrue;

     default: rfalse;
    }
];

[ TypeInputSub
    loc     ! location of word in parse table
    point   ! location of word in text buffer
    length  ! length of the word
    l       ! letter in word
    i;      ! loop counter

    if (PuzzleChest notin Toyshop)
        "You can't see anything to type on.";
    if (consult_words > 1)
        "Use for example ~type 123~ to type a string of digits and press \
        ~send~.";

    ! Check the input consists only of numbers from 1 to 5.
    loc = consult_from * 4 + 1;
    point = buffer+(parse->loc);
    length = parse->(loc-1);

    for (i = 0: i < length: i++) {
        l = point->i;
        if (l < '1' || l > '5')
            "The only numbers on the keypad are ~1~ to ~5~.";
    }

    for (i = 0: i < length: i++) {
        special_number = point->i - '0';
        if (InputNumberSub(1) == 1)
            jump PressSend;
    }

    .PressSend;
    InputSendSub();
];

Extend "press"
    * number -> InputNumber
    * "send" -> InputSend;

Verb "type"
    * ConTopic -> TypeInput;

End;