/*
 * Copyright (c) 2021 Ross Cunniff
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include "stdadv.oah" /* Include the standard package */

using namespace adv;
using namespace stdadv;

/* The following are Object properties */

public
    BROKEN,     /* Is the robot damaged? */
    TOLD,       /* Have I told the robot something? */
    BSTATE;     /* State of the button */

const B_OFF   =  0;   /* Button is off */
const B_FLASH =  1;   /* Button is flashing */
const B_LIT   =  2;   /* Button is lit */


/* Global variables */

var
    Score = 0;              /* Current score */


/* Utility routines */

proc
    NoGo,       Sayer,  Myself, Lifter,
    DoorCk,     TrapCk, RobMov, BlueCk,
    Header,     MyDie,  Skore,  RobEntr,
    HatchSD;


/* Locations in the dungeon */
LitRoom Redrm {...}
LitRoom Bluerm {...}
LitRoom Greenrm {...}
LitRoom Cellar {...}
LitRoom Endrm {...}


/* Immovable objects */
class Button(Thing) {public var BSTATE = B_OFF;}
Button button(Bluerm) {...}
class Door(Thing) {public var pOpens, pOpened;}
Door door(Cellar) {...}
class Hatch(Thing) {public var pNoTake, pOpens;}
Hatch hatch(Bluerm) {...}


/* Objects which may become actors */
StdActor me(Redrm);
class Robot(StdActor) {public var BROKEN, TOLD;}
Robot robot(Greenrm) {...}

/* Room descriptions */
... Redrm {
    ldesc = proc() {
        "You are in a large room which is illuminated by a bright ",
        "red glow.  Exits lie to the east and south.\n";
    }
    sdesc = proc() {return Header("Red room", oadl::nargs());}
    ...
}

... Greenrm {
    ldesc = proc() {
        "You are in a smallish room which is illuminated by a pleasant ",
        "green glow.  The only exit is to the west.\n";
    }
    sdesc = proc() {return Header("Green room", oadl::nargs());}
    ...
}
... Bluerm {
    ldesc = proc() {
        "You are in a tiny room which is barely illuminated by a ";
        "dim blue glow.  There is an exit to the north,";

        if (button.BSTATE == B_LIT) {
            " and most of the floor has tilted up to reveal a hatch leading ";
            "down into blackness.  A button on the wall is glowing brightly.";
        }
        else {
            " and you seem to make out something on the floor.";
            if (button.BSTATE)
                "  A button on the wall is flashing urgently.";
            else
                "  There is a button on the wall.";
        }

        "  Above the button is a sign that reads:\n\n",
        "           DANGER!\n\n",
        "        HIGH VOLTAGE!\n\n";
    }
    sdesc = proc() {return Header("Blue room", oadl::nargs());}
    ...
}

... Cellar {
    ldesc = proc() {
        "You are in the cellar.  Far above you can be seen a dim blue light.";
        if (door.pOpened) 
            "  An open door leads to the north.\n";
        else
            "  You can barely see the outline of a door to the north.\n";
    }
    sdesc = proc() { return Header("Cellar", oadl::nargs()); }
    ...
}

... Endrm {
    ldesc = proc() {
        "You exit from the dark cellar into a land filled with singing birds, ";
        "blooming flowers, flowing streams, and bright blue skies.  In other ";
        "words, you have finished this game!\n";

        Score = Score + 25;
        Skore();
        Quit();
    }
}


/* Verbs */
VerbClass score {...}
VerbClass push {...}
VerbClass shout {...}
VerbClass adv::TELLER {...}
Thing adv::STRING();

Synonym tell(adv::TELLER);
Synonym V_say(adv::TELLER);
Synonym press(push);
Synonym feel(touch);
Synonym yell(shout);


/* Verb routines */
... adv::TELLER {
    preact = proc() {
        if (Iobj != robot) {
            /* The only logical thing to talk to is the robot */
            Sayer("Talking to yourself is said to be a sign of impending insanity");
        }
        else if (!(Dobj ?= String)) {
            /* You must say strings */
            Sayer("You must put what you want to say in quotes");
        }
        else if (robot.loc != me.loc) {
            /* The robot must be in the same place as the player */
            if (Myself()) "You don't see the robot here.\n";
        }
        else {
            /* Everything is OK.  Add 25 points to the score */
            if (!robot.TOLD) {
                Score = Score + 25;
                robot.TOLD = true;
            }
            ExitPhase(PHX_CURRENT);
        }
        ExitPhase(PHX_ACTOR);
    }
    action = proc() {
        /* Tell the player that we heard him */
        "\"Sure thing, Boss.\"\n";

        /* Delete the old action */
        robot.deactivate();

        /* Add the new action - a non-interactive actor */
        robot.activate(Dobj, false);
    }
}

... shout {
    preact = proc() {
        if (Iobj && (Iobj != robot))  {
            /* Shouting at things other than the robot */
            "AAARRRGGGHHH!\n";
        }
        else if (!(Dobj ?= String)) {
            /* Shouting things other than strings */
            "EEEYYYAAAHHH!\n";
        }
        else if (robot.BROKEN) {
            "There is no response.\n";
        }
        else {
            /* Shouting at the robot - same as telling the robot */
            if (!robot.TOLD) {
                Score = Score + 25;
                robot.TOLD = true;
            }
            ExitPhase(PHX_CURRENT);
        }
        ExitPhase(PHX_ACTOR);
    }
    action = proc() {
        /* Tell the player we heard them */
        if (robot.loc != me.loc) "In the distance you hear the words, ";
        "\"Sure thing, Boss\"\n";

        /* Delete the old robot action */
        robot.deactivate();

        /* Add the new robot action */
        robot.activate(Dobj, false);
    }
}

... push {
    preact = proc() {
        /* Expect a plain direct object */
        Expect(ONE_OBJ|PLAIN_OBJ, NO_OBJ);
        CheckAvail();
    }
    action = proc() {
        Sayer("That doesn't seem to do anything");
        ExitPhase(PHX_ACTOR);
    }
}

... score {
    preact = proc() {
        /* Score can accept no objects */
        Expect(NO_OBJ, NO_OBJ);
        Skore();
        ExitPhase(PHX_ACTOR);
    }
}

/* Object properties */

... button {
    sdesc = proc() {
        if (button.BSTATE == B_OFF)
            "a button";
        else if (button.BSTATE == B_FLASH)
            "an urgently flashing button";
        else
            "a brightly lit button";
    }
    action = proc() {
        if (Myself() && ((Verb == push)||(Verb == take)||(Verb == touch))) {
            /* The player tried to do something with the button */
            "As you reach for the button, a 10,000,000 volt bolt of lightning ";
            "arcs toward your finger, disintegrating you upon impact.\n";
            MyDie();
        }
        else if ((Verb == push) && (button.BSTATE == B_OFF)) {
            /* The robot pushed the button */
            button.BSTATE = B_FLASH;
            Score = Score + 50;
            me.setfuse(Lifter, 4);
            ExitPhase(PHX_ACTOR);
        }
        else if (Verb == take) {
            /* Can't take the button */
            Skip = true;
        }
    }
}

const SimpleRobot = "I am just a simple robot";
... robot {
    ldesc = proc() {"There is a robot here.\n";}
    sdesc = proc() {"a robot";}
    action = proc() {
        if (Myself()) {
            /* I'm doing something with the robot */
            if (Verb == adv::TELLER) {
                if (robot.BROKEN) {
                    "There is no response.\n";
                    ExitPhase(PHX_ACTOR);
                }
            }
            else if (Verb == take) {
                "The robot weighs at least 500 pounds!\n";
                ExitPhase(PHX_ACTOR);
            }
        }
        else if (Phase == PHASE_ACTOR) {
            /* This is being called as the Actor action */
            ActAction();
            switch( Verb ) {
            case push, go, wait, take, north, south, east, west,
                 up, down :
                "";
            default :
                /* The robot has a VERY simple vocabulary */
                Sayer(SimpleRobot);
                robot.deactivate();
                ExitPhase(PHX_ACTOR);
            }
        }
        else if (Verb == take) {
            /* The robot is trying to take itself */
            Sayer("Mmmph!  Akkk!!  GGGGRR!!  No can do.  Sorry");
            Skip = true;
        }
        else {
            /* The robot is doing something to itself */
            Sayer(SimpleRobot);
            robot.deactivate();
            ExitPhase(PHX_ACTOR);
        }
    }
}


/* We break me( action ) out into a named routine because
 * StdInit overwrites that property and we need to restore it
 */

proc MeAct()
{
    if (Phase == PHASE_ACTOR) {
        /* This is the Actor action - call standard's actor action */
        ActAction();
    }
    else if (Verb == take) {
        Sayer("I thought you would never ask");
        Skip = true;
    }
}


/* We break hatch( sdesc ) out into a named routine because
 * the hatch isn't visible until after Lifter has executed
 */

proc HatchSD() {"an open hatch";}
const HatchMSG = "The hatch doesn't budge";
... hatch {
    action = proc() {
        if (Verb == take) {
            /* Can't take the hatch */
            Sayer(HatchMSG);
            Skip = true;
        }
        else if ((Verb == open) || (Verb == push)) {
            /* Can't open or push it, either */
            Sayer(HatchMSG);
            ExitPhase(PHX_ACTOR);
        }
    }
    pOpens = true
    pNoTake = true
}

... door {
    sdesc = proc() {"a door";}
    action = proc() {
        if (Verb == take) {
            "You can't take a door!\n";
            Skip = true;
        }
    }
    pOpens = true
}

/* Transition routines.  Note that RobMov is used in miss.
 * This produces the 'The robot exits to the <direction>
 * messages.  The calls to RobEntr produce the messages like
 * 'The robot enters from the <direction>.
 */

... Bluerm {
    action = proc() {
        Miss(RobMov, NoGo, NoGo, NoGo, NoGo, TrapCk);
        Hit($ME, Redrm, 0, 0, 0, 0, Cellar);
        RobEntr();
    }
}

... Redrm {
    action = proc() {
        Miss(NoGo, BlueCk, RobMov, NoGo, NoGo, NoGo);
        Hit($ME, 0, Bluerm, Greenrm);
        RobEntr();
    }
}

... Greenrm {
    action = proc() {
        Miss(NoGo, NoGo, NoGo, RobMov, NoGo, NoGo);
        Hit($ME, 0, 0, 0, Redrm);
        RobEntr();
    }
}

... Cellar {
    action = proc() {
        Miss(DoorCk, NoGo, NoGo, NoGo, BlueCk, NoGo);
        Hit($ME, Endrm, 0, 0, 0, Bluerm);
        RobEntr();
    }
}

/* Routines */

/* Myself() - returns 1 if "me" is the current actor; 0 otherwise */
proc Myself()
{
    return ($ME == me);
}


/* Sayer(str) - Says a string with appropriate quoting, depending
 * on whether the robot or the player is doing the saying.
 */
proc Sayer(str)
{
    if (Myself()) {
        "", str, ".\n";
    }
    else if (robot.loc == me.loc) {
        "\"", str, ", Boss.\"\n";
    }
    else {
        "You hear a muffled voice in the distance.\n";
        "(", str, ")\n";
    }
}


/* NoGo() - "You can't go that way" */
proc NoGo()
{
    Sayer("You can't go that way");
    ExitPhase(PHX_ACTOR);
}


/* Header(str, arg0) - To accomplish the printing of header lines,
 * each location sdesc needs to return a string if a parameter is
 * passed to it.  By doing return Header(<sdesc>, arg(0)), we can
 * centralize the saying/returning decision.
 */
proc Header(str, arg0)
{
    if (!arg0) "", str, ".\n";
    return str;
}


proc RobMov()
{
    if (!Myself() && (robot.loc == me.loc)) {
        "The robot exits to the ";
        if (Verb == east)
            "east";
        else if (Verb == west)
            "west";
        else if (Verb == south)
            "south";
        /* The robot can't be seen leaving to the north */
        ".\n";
    }
}


proc RobEntr()
{
    if (!Myself() && (robot.loc == me.loc)) {
        if (Verb == north)
            "The robot enters from the south.\n";
        else if (Verb == east)
            "The robot enters from the west.\n";
        else if (Verb == west)
            "The robot enters from the east.\n";
         /* The robot can't enter from the north in this scenario */
    }
}


proc DoorCk()
{
    if (!door.pOpened) {
        "The door seems to be closed.\n";
        ExitPhase(PHX_ACTOR);
    }
}


proc TrapCk() { if (button.BSTATE != B_LIT) NoGo(); }

/* BlueCk() - make sure that only one actor is in the blue room
 * at one time.
 */
proc BlueCk()
{
    if ((me.loc == Bluerm) || (robot.loc == Bluerm)) {
        if (Myself())
            "The room is too small for both you and the robot to fit.\n";
        ExitPhase(PHX_ACTOR);
    }
    else if (!Myself() && (button.BSTATE == B_LIT)) {
        RobMov();
        "You hear a loud CRASH! in the distance.\n";
        Score = Score - 10;
        robot.BROKEN = true;
        robot.move(Bluerm);
        robot.deactivate();
        ExitPhase(PHX_ACTOR);
    }
    RobMov();
}


/* MyDie() - kill off the player */
proc MyDie()
{
    Score = Score - 50;
    Skore();
    Die();
}


/* Lifter() - Lift the hatch, possibly killing the robot or the player */
proc Lifter()
{
    if (me.loc == Bluerm) {
        "All of a sudden, the floor lifts up, and you are crushed between it ",
        "and the wall!  ";
        MyDie();
    }
    else {
        "In the distance, you hear a loud CRASH!\n";
        if (robot.loc == Bluerm) {
            Score = Score - 10;
            robot.BROKEN = true;
            robot.deactivate();
        }
    }
    hatch.sdesc = HatchSD;
    button.BSTATE = B_LIT;
    Bluerm.pSeen = false;
}


/* Prompt - print the status line and a prompt */
proc PROMPT()
{
    Status($ME.loc.sdesc(1), Score, Turns);
    "> ";
}


/* Increment - increment the turn counter */
proc INCREMENT()
{
    if (Myself()) {
        /* We only want to increment once per turn */
        Incturn();
    }
    else {
        /* We don't want Looker executing for the robot */
        ExitPhase(PHX_CURRENT);
    }
}


/* Skore() - print out the current score. */
proc Skore()
{
    "You have scored ", Score,
        " out of a possible 100 in ", Turns, " moves.\n";
}


/* Dwimming routines */
proc adv::DWIMI(prs,obj) {return Dwimmer(prs,obj);}
proc adv::DWIMD(prs,obj) {return Dwimmer(prs,obj);}

proc adv::START()
{
    //spec(MARGIN, 69);  /* Set the screen to 69 wide */

    Setdaemon(INCREMENT);   /* Turn counter increment */
    StdInit(me);        /* Initialize standard */
    me.action = MeAct;    /* Restore me( action ) */
    dirVec = {north, south, east, west, up, down}; /* Our own dirvec */
    Prompt = PROMPT;    /* and our own prompter */
    Indent = true; /* Indent the object descriptions */
}

/**** EOF actdemo.adl ****/