#include <stdio.h>
#include "genlib.h"


/* define the maximum dimensions of the map array */
#define MAP_SIZE_X 5
#define MAP_SIZE_Y 5
/* define the maximum number of objects we will have in the world */
#define MAX_OBJS 100


/*   Enumeration to keep track of the locations - this lets us refer to the
     locations symbolically, making code that refers to particular locations
     more readable  */
typedef enum {
   LOC_GRAVE,
   LOC_TOMB,
   LOC_DAVY,
   LOC_SINKHOLE,
   LOC_MICKY,

   LOC_BAR,
   LOC_SHOP,
   LOC_BRINE,
   LOC_CRAB,
   LOC_SUB,

   LOC_CORAL,
   LOC_ROCKS,
   LOC_OCTOPUS,
   LOC_EEL,
   LOC_SMELTS,

   LOC_SHELLS,
   LOC_SHIP,
   LOC_PUPPETS,
   LOC_BASE,
   LOC_PILE,

   LOC_PARTY,
   LOC_HORSES,
   LOC_COWBOYS,
   LOC_MONKEYS,
   LOC_CENTER,

   LOC_RINGO,
   LOC_SMASHED,
} Location;

/*   Enumeration for game objects - again, giving each object a symbolic
     name makes the code more readable */
typedef enum {
   OBJ_RINGO,
   OBJ_OCTOPUS,
   OBJ_EEL,
   OBJ_CRAB,
   OBJ_DRUM_START,
} GameObj;
   

/* input: a two-dimensional array of Locations map and array of short
          strings to be printed for each map location, locnames, that
          is indexed by Locations
   output: none
   side-effects: prints out a table of short map names based on where
                 each location is located in the map array
   notes: - assumes that the first map index encodes the north-south
	    direction and the second enoded east-west, and that map[0][0]
	    is the northwesternmost point
          - since this works by following the ordering of whatever is in the
	    map array, this will continue to work even if we scramble the
	    map around, or even enlarge it, provided we still organize the
	    new map as specified above */
void View(Location map[MAP_SIZE_Y][MAP_SIZE_X], string locnames[25])
{
   int x, y;

   printf("\n");

   /* Iterate over all map coordinates, starting in the upper-left-hand
      corner of the map, which has coordinates x=0, y=0 in this implementation.
      Note that if you made x=0, y=0 be the lower-left-hand corner, then
      you would need to run the outer for loop backwards, starting from 5
      and ending at zero */
   for(y = 0; y < MAP_SIZE_Y; y++)
   {
      for(x = 0; x < MAP_SIZE_X; x++)
      {
         /* the array locnames is indexed by locations, so we simply run
            over all locations in the map, and look up the equivalent
            string in the locnames array. We use a field width of 16
            in the printf formatting string since none of the strings
            are longer than 15 characters, so this should give us evenly-
            spaced columns */
         printf("%-16s", locnames[map[y][x]]);
      }
      printf("\n");
   }
}


/* input: a Location
   output: a string corresponding to that location, suitable for printing
           in movement messages "Ringo swims DIR, from X to Y" */
string LocationStr(Location loc)
{
   string locstr = "";
   /*   Find out the string corresponding to the location.   */

   switch(loc)
   {
   case LOC_GRAVE:    locstr = "watery grave";  break;
   case LOC_TOMB:     locstr = "undersea tomb";  break;
   case LOC_DAVY:     locstr = "Davy Jones locker";  break;
   case LOC_SINKHOLE: locstr = "spooky sinkhole";  break;
   case LOC_MICKY:    locstr = "Micky Dolens locker";  break;

   case LOC_BAR:      locstr = "sand bar & grill";  break;
   case LOC_SHOP:     locstr = "prawn shop";  break;
   case LOC_BRINE:    locstr = "brine & spirits";  break;
   case LOC_CRAB:     locstr = "crabs cottage";  break;
   case LOC_SUB:      locstr = "yellow submarine";  break;

   case LOC_CORAL:    locstr = "coral reef";  break;
   case LOC_ROCKS:    locstr = "pointy rocks";  break;
   case LOC_OCTOPUS:  locstr = "octopus garden";  break;
   case LOC_EEL:      locstr = "eels estates";  break;
   case LOC_SMELTS:   locstr = "mess of smelts";  break;

   case LOC_SHELLS:   locstr = "bunch of shells";  break;
   case LOC_SHIP:     locstr = "sunken pirate ship";  break;
   case LOC_PUPPETS:  locstr = "stolen puppets";  break;
   case LOC_BASE:     locstr = "marine refinery";  break;
   case LOC_PILE:     locstr = "barnacle pile";  break;

   case LOC_PARTY:    locstr = "manatee party";  break;
   case LOC_HORSES:   locstr = "sea horses";  break;
   case LOC_COWBOYS:  locstr = "sea cowboys";  break;
   case LOC_MONKEYS:  locstr = "sea monkeys";  break;
   case LOC_CENTER:   locstr = "visitors center";  break;

   default:
      printf("No location string for %d\n", loc);
   } /* end of switch */

   return locstr;
}

/* input: a single character representation of a compass direction (n,s,e
          or w)
   output: a string version of the corresponding direction */
string DirectionStr(char act)
{
   string dirstr = "";

   /*   Find out the string corresponding to the direction.   */
   switch(act)
   {
   case 'n': dirstr = "north"; break;
   case 's': dirstr = "south"; break;
   case 'e': dirstr = "east"; break;
   case 'w': dirstr = "west"; break;
   }
   return dirstr;
}


/* input: Ringo's current Location, a character representing the direction
          he is to move, and a two dimensional array representing a map
          of the world so that we can compute where the move will take
          him
   output: Ringo's new location
   side-effects: prints messages indicating the Ringo's movement has taken
                 place
*/
Location Move(Location current, char act, 
   Location map[MAP_SIZE_Y][MAP_SIZE_X])
{
   int x, y, vx, vy;
   bool found = FALSE; /* since break can only break out of one loop, we
                          use this variable to keep track of whether we
                          want to break out of the outer loop too. See
                          below */

   /*   find the map coordinates of Ringo's current location. Do this by
        running over the whole map array, and finding a Location it it
        matching Ringo's current location, which was passed as a parameter */
   for(x = 0; x < MAP_SIZE_X; x++)
   {
      for(y = 0; y < MAP_SIZE_Y; y++)
      {
         if(current == map[y][x])
         {
            found = TRUE; /* we've found the coordinates we want, so in order
                             to make it so that x and y will continue to have
                             the right values in them, we need to break out
                             of _both_ loops now. We do this by using a
                             simple break to get out of the inner loop, and
			     set this variable found to signal that we want
			     to break out of the outer loop too */
            break;
         }
      }
      if(found)
      {
         break; /* break out of the outer loop if we broke out of the inner
	           one */
      }
   } /* end of outer for */

   /* there's a bug in the program if we didn't find Ringo's location in
      the map array, so we might as well bail out */
   if(!found)
   {
      printf("Unknown location: %d\n", current);
      exit(1);
   }

   /*   find the location next to it and return the value.   */
   switch(act)
   {
   /* compute the change in coordinates we will need to make to make
      a move. vx is the change in x coordinates and vy is the change 
      in y coordinates. If we go east, that means that the x coordinate
      increases by 1, and y stays the same. If we go north, the
      y coordinate will decrease by 1, and x will stay the same */
   case 'e': vx = 1; vy = 0; break;
   case 'w': vx = -1; vy = 0; break;
   case 'n': vx = 0; vy = -1; break;
   case 's': vx = 0; vy = 1; break;
   }

   /* check to see whether the change we want to make will take us off
      the edge of the map (and therefore would be invalid array indices.
      If that is the case, just print a message and return the current
      map location.*/
   if(x + vx < 0 || x + vx >= MAP_SIZE_X ||
      y + vy < 0 || y + vy >= MAP_SIZE_Y)
   {
      printf("Nothing interesting in this direction.\n");
      printf("Let's stay here.\n");
      return map[y][x];
   }

   /* All messages we print will be of the form of the below
      formatting string. We can use DirectionStr() to compute the
      string form of a character direction, and by looking up
      the current and new Locations in the map array, we can
      use the LocationStr function to give us nice string
      versions of the abstract Locations. Since we have already
      calculated the change in coordinates we will need and
      stored the change to be made in vx and vy, then we can
      always compute the new coordinates as x+vx and y+vy */
   printf("You swim %s, out of the %s, into the %s.\n",
      DirectionStr(act), 
      LocationStr(map[y][x]), 
      LocationStr(map[y + vy][x + vx]));

   /* finally, return the new location so that the caller can update
      the Location array with Ringo's new location */
   return map[y + vy][x + vx];
}


/* input: an array of Locations, indexed by GameObjs, that keeps track of
	  the Location of all of the objects in the simulation
   output: none
   side-effects: changes the location of the grabbed object to Ringo's hand
		 by changing the array directly, and prints messages indicating
		 that this change has happened
   notes: - will not allow Ringo to grab more than one object
	  - Ringo can only grab an object at his current location and that
	    appears first in the locations array, if searched from the
	    beginning */
void Grab(Location locations[MAX_OBJS])
{
   int i;

   /*   Check if ringo has anything.   */
   for(i = 0; i < MAX_OBJS; i++)
   {
      /* if something is already in Ringo's hand (encoded by Location
         LOC_RINGO), then don't let Ringo grab anything else */
      if(locations[i] == LOC_RINGO)
      {
         printf("You already have something in your hand.\n");
         return;
      }
   }

   /*  Find an object where ringo is.   */
   for(i = 0; i < MAX_OBJS; i++)
   {
      /* if we find an object in Ringo's current location other than
	 Ringo himself ...*/
      if(locations[i] == locations[(int)OBJ_RINGO] && i != OBJ_RINGO)
      {
	 /* ... print a message that depends on the identity of the
	    object, and ... */
         switch(i)
         {
         case OBJ_RINGO:
            /*   just skip, but not actually possible anyway because of
		 the surrounding if statement */
            break;

         case OBJ_OCTOPUS:
            printf("You grab the octopus with your mighty European ");
            printf("arms.\n");
            break;

         case OBJ_EEL:
            printf("You grab an eel.\n");
            break;

         case OBJ_CRAB:
            printf("You grab a crab.\n");
            break;

         default:
            /*   should be a drum, since these are the only game objects
		 we haven't already dealt with above.   */
            printf("You grab a drum of liquid waste.\n");
            break;
         }
	 /* change the location of that object to Ringo's hand */
         locations[i] = LOC_RINGO;
         return;
      } /* end of if (locations[i] == ... */
   } /* end of for */

   /* if we didn't find anything at Ringo's current location ... */
   printf("Nothing here.\n");
}


/* input: an array of Locations, indexed by GameObjs, that keeps track of
	  the Location of all of the objects in the simulation
   output: none
   side-effect: changes the Location of the object in Ringo's hand to Ringo's
		current location by directly changing the locations array,
		and prints a message indicating that this has happened
   notes: - Ringo cannot throw anything if there's nothing in his hand
	  - The octopus is handled specially: instead of being placed at
	    Ringo's current location, it is put in the pseudo-location
            LOC_SMASHED to encode that the octopus is (temporarily) dazed */
void Throw(Location locations[MAX_OBJS])
{
   int i;
   bool found;

   /*   Find the one in ringo's hand.   */
   found = FALSE;
   for(i = 0; i < MAX_OBJS; i++)
   {
      /* find the object that is in Ringo's hand, if any */
      if(locations[i] == LOC_RINGO)
      {
	 /* print a message indicating that this object has been thrown */
         switch(i)
         {
         case OBJ_RINGO:
            /*   shouldn't happen.   */
            printf("Ringo holding Ringo???\n");
            exit(1);
            break;

         case OBJ_OCTOPUS:
            printf("You throw the octopus onto the ground.  It is ");
            printf("momentarily dazed.\n");
            break;

         case OBJ_CRAB:
            printf("You throw a crab onto the ground.\n");
            break;

         case OBJ_EEL:
            printf("You throw an eel onto the ground.\n");
            break;

         default:
            /*   should be one of the drums.   */
            printf("You throw a drum of liquid waste onto the ground.\n");
            break;
         } /* end of switch */

	 /* if it's the octopus, make it dazed; otherwise, just put the
	    thrown object into the same location as Ringo's current
	    location (locations[OBJ_RINGO]) */
         if (i == OBJ_OCTOPUS){
            /* handle the special case of the octopus */
            locations[(int)OBJ_OCTOPUS] = LOC_SMASHED;
         } else {
            locations[i] = locations[OBJ_RINGO];
         }

         found = TRUE;
         break;
      } /* end of if(locations[i] == LOC_RINGO) */
   } /* end of for */

   /* if we didn't actually find anything in Ringo's hand, then print an
      appropriate message */
   if(!found)
   {
      printf("You try to throw something, but you have nothing ");
      printf("in your hands.\n");
   }
}


/* the main program - consists, essentially, of a giant loop that takes
   single-character actions from the user, and calls functions to change
   the state of the game according to the current state and the action the
   user takes */
void main()
{
   char act;                /* the action the user wishes Ringo to take */
   int i;                   /* a loop control variable */
   Location lastOctopusLoc; /* this keeps track of where the octopus was last
			       so that we can tell whether the octopus should
			       escape or not - see the last 3 if statement
			       at the bottom of the main loop. */
   string locNames[25];     /* an array of short strings apropriate for
                               printing a compact map of the game world */

   /*   These keep track of locations of all game objects */
   Location objs[MAX_OBJS];

   /*   Initialize the map using 2-dimensional array initialization syntax.
	This makes the the upper left corner (LOC_GRAVE) be at coordinates
	(x,y) = (0,0), LOC_TOMB be at (1,0), ... the octopuses garden
	at (2,2), ... LOC_MONKEYS be at (3,4) and the LOC_CENTER be at (4,4).
	This could also be done in the style of locNames, below: one array
	location at a time */
   Location map [MAP_SIZE_Y] [MAP_SIZE_X] =
   {
     { LOC_GRAVE,  LOC_TOMB,   LOC_DAVY,    LOC_SINKHOLE, LOC_MICKY },
     { LOC_BAR,    LOC_SHOP,   LOC_BRINE,   LOC_CRAB,     LOC_SUB },
     { LOC_CORAL,  LOC_ROCKS,  LOC_OCTOPUS, LOC_EEL,      LOC_SMELTS },
     { LOC_SHELLS, LOC_SHIP,   LOC_PUPPETS, LOC_BASE,     LOC_PILE },
     { LOC_PARTY,  LOC_HORSES, LOC_COWBOYS, LOC_MONKEYS,  LOC_CENTER }
   };

   /* initialize locNames, indexing by Location, with the short names that
      will be printed out by the View function */
   locNames[(int)LOC_GRAVE]    = "watery grave";
   locNames[(int)LOC_TOMB]     = "undersea tomb";
   locNames[(int)LOC_DAVY]     = "DJ's locker";
   locNames[(int)LOC_SINKHOLE] = "sinkhole";
   locNames[(int)LOC_MICKY]    = "MD's locker";

   locNames[(int)LOC_BAR]      = "sand bar/grill";
   locNames[(int)LOC_SHOP]     = "prawn shop";
   locNames[(int)LOC_BRINE]    = "brine/spirits";
   locNames[(int)LOC_CRAB]     = "crab";
   locNames[(int)LOC_SUB]      = "yellow sub";

   locNames[(int)LOC_CORAL]    = "coral reef";
   locNames[(int)LOC_ROCKS]    = "pointy rocks";
   locNames[(int)LOC_OCTOPUS]  = "octopus";
   locNames[(int)LOC_EEL]      = "eels";
   locNames[(int)LOC_SMELTS]   = "smelts";

   locNames[(int)LOC_SHELLS]   = "shells";
   locNames[(int)LOC_SHIP]     = "pirate ship";
   locNames[(int)LOC_PUPPETS]  = "puppets";
   locNames[(int)LOC_BASE]     = "refinery";
   locNames[(int)LOC_PILE]     = "barnacles";

   locNames[(int)LOC_PARTY]    = "manatee party";
   locNames[(int)LOC_HORSES]   = "sea horses";
   locNames[(int)LOC_COWBOYS]  = "sea cowboys";
   locNames[(int)LOC_MONKEYS]  = "sea monkeys";
   locNames[(int)LOC_CENTER]   = "visitor ctr";

   /*   Initialize the location of game objects. This is what is referred to
	in the newsgroup and in function definitions as the "locations"
	array. This array encodes the locations of all game objects by
	simply assigning an arbitrary array slot to each object, and
	storing the current location of that object in the slot assigned
	to it. So we just index the array with GameObjs and give them all
	their initial Locations.*/
   objs[(int)OBJ_RINGO] = LOC_OCTOPUS;
   objs[(int)OBJ_OCTOPUS] = LOC_OCTOPUS;
   objs[(int)OBJ_EEL] = LOC_EEL;
   objs[(int)OBJ_CRAB] = LOC_CRAB;

   /* we make a lot more than twelve barrels here: we just let every slot
      in the array after OBJ_DRUM_START be a barrel, and we use a for
      loop to place all of those barrels at the submarine base. We
      could also just create exactly 12 barrels in a very similar
      way by running the loop until (and including) OBJ_DRUM_START+11 */
   for(i = OBJ_DRUM_START; i < MAX_OBJS; i++)
   {
      objs[i] = LOC_BASE;
   }

   /*   Introductory text.   */
   printf("You're in an octopus' garden, just like in your ");
   printf("song, except the octopus is\nthrowing things at ");
   printf("you and frightening you.\n");

   /* keep getting actions from the user until the action is 'q' */
   while(1)
   {
      /*   Receive input from the user.   */
      printf("Your action: ");
      act = getchar();
      getchar();

      /*   Check for the quit command as a special case.   */
      if(act == 'q')
      {
         printf("You quit, bored of undersea life already.\n");
         break;
      }


      /* store the octopus's current location since it's movement has
	 to be handled specially */
      lastOctopusLoc = objs[(int)OBJ_OCTOPUS];

      /*   Branch out depending on the user's input:  note that one switch
	   statement suffices - we don't need a special one for each
	   location. Most actions have the same effect at any location.
	   For actions that _do_ have a location dependence, we can put
	   an if statement to handle this just in those cases. See below */
      switch(act)
      {

      /*   Let's take care of all the cases for swiming in one shot: we
	   always call Move, except if Ringo hasn't escaped yet */
      case 'n':
      case 's':
      case 'e':
      case 'w':
	 /* if the octopus isn't stunned and Ringo is trying to escape the
	    octopus's garden, then the octopus should prevent him */
         if(objs[(int)OBJ_RINGO] == LOC_OCTOPUS && 
            objs[(int)OBJ_OCTOPUS] != LOC_SMASHED)
         {
           printf("You try to swim %s, ", DirectionStr(act));
           printf("but the octopus gets in your way.\n");
         }
	 /* otherwise, call Move to compute the new Location, passing
	    Ringo's current Location, the direction character, and
	    the entire map array (note the lack of square brackets).
	    Be sure to store the new location of Ringo back in the
	    locations array (named "objs" here) */
         else
         {
            objs[(int)OBJ_RINGO] = Move(objs[(int)OBJ_RINGO], act, map);
         }
         break;

      /* to throw something, we simply pass the whole array objs to Throw,
	 which takes care of _all_ of the details */
      case 't':
         /*   Throw.   */
         Throw(objs);
         break;

      /* similarly, we pass objs to Grab, and it totally does all the work
	 involved in grabbing an object */
      case 'g':
         /*   Grab.   */
         Grab(objs);
         break;

      /* The yell action just prints a message. This version prints a
	 special message when in the octopus's garden, and another
	 elsewhere. It's also okay to always print the same message,
	 or customize the messages in any way you wish */
      case 'y':
         /*   Yell.   */
         if(objs[(int)OBJ_RINGO] == LOC_OCTOPUS)
         {
             printf("You yell at the octopus, \"go watch TV!\"\n");
         }
         else
         {
             printf("You yell, \"I hate the ocean!\"\n");
             printf("Nothing happens.\n");
         }
         break;

      /* At the moment, there's never anything to hide behind, so we print
	 the same message every time */
      case 'h':
         /*   Hide.   */
         printf("You try to hide, but there is nothing to hide ");
         printf("behind.\n");
         break;

      /* The View function totally takes care of printing the map. We need
	 only pass it the map array and an array, indexed by Locations,
	 of strings that should be printed for each Location */
      case 'v':
         /*   View map.   */
         View(map,locNames);
         break;

      default:
         /*   Error case.  Just let the user know.   */
         printf("Unknown action, type n, s, e, w, y, h, g, t, v, or q.\n");
         break;
      } /* end of action-decoding switch statement */


      /*   Set the situation variables to new values.   */

      /* if Ringo was holding the octopus, and his next action was _not_
	 to throw it, then the octopus should escape, so we reset its
	 location to Ringo's location */
      if(lastOctopusLoc == LOC_RINGO &&
         objs[(int)OBJ_OCTOPUS] != LOC_SMASHED)
      {
         printf("The octopus wriggles free of your grasp.\n");
         objs[(int)OBJ_OCTOPUS] = objs[(int)OBJ_RINGO];
      }
      /* if the octopus was dazed on the last turn (indicated by the special
	 location LOC_SMASHED), and Ringo is still in the octopus's garden,
	 then the octopus should recover, leaving the LOC_SMASHED state
	 so Ringo has to start the cycle all over again */
      if(lastOctopusLoc == LOC_SMASHED &&
         objs[(int)OBJ_RINGO] == LOC_OCTOPUS)
      {
         printf("The octopus recovers from its fall.\n");
         objs[(int)OBJ_OCTOPUS] = objs[(int)OBJ_RINGO];
      }

      /*   Check if Ringo is still being harrased: he should be if he's still
	   in the octopus's garden and he hasn't grabbed or thrown the
	   octopus on this turn */
      if(objs[(int)OBJ_RINGO] == LOC_OCTOPUS &&
         objs[(int)OBJ_OCTOPUS] == LOC_OCTOPUS)
      {
         printf("The octopus keeps harassing you.\n");
      }
   } /* end of main while-loop */
}


