// Hammurabi.java 
// Version 0.9

import com.sun.kjava.*;

/**
 *  The classic game of Hammurabi for the JavaOne 1999 KVM.
 *<P>
 *  Play the part of the ruler of ancient Babylon with this
 *  great-great-great-granddaddy of empire building games.   
 *<P> 
 *  The foundation of this Spotlet is Sun's Rumor game that was 
 *  distributed at the 1999 JavaOne developer conference; all the event
 *  and text handling comes directly from that code.  Everything else
 *  was converted from the C version of the game (see below).  I won't
 *  claim that the coding is incredibly clean or well-implemented, but this
 *  is more a demonstration that an ordinary Java engineer can write a 
 *  functional KVM application than anything else (although there's no
 *  data access of any kind).  
 *<P>
 *  Based on C code from the Open License Software Supplement (Skunkware)
 *  that I found at ftp.sco.com/skunkware/ and seemed to me to be close enough
 *  to the original version (in BASIC?) to use as the basis for this code.  : )
 *<P>
 *  TODO:
 *<LI> Add saving of high scores and perhaps game state in a .pdb file.
 *<P>
 *
 *  @author Jim Lesko  --  Jim@MartianWind.com
 *  @version 0.9
 */
public class Hammurabi extends Spotlet 
{
   // UI objects:
   static Graphics g = Graphics.getGraphics();
   
   static Button newButton;
   static Button exitButton;
   static Button errorButton;
   static Button doneButton;
   
   // UI state:
   static int lineChar;
   static int lineHeight = 11;
   static int displayX   = 0;
   static int displayY   = 0;
   static int line       = 0;
   
   static int    inputStringLength = 8;
   static int    inputLength       = 7;
   static byte[] inputString       = new byte[inputStringLength];
   static int    value             = 0;
   
   // Game variables:
   static boolean playing;
   static String prompt;
   
   static int plagueCount = 0;    // How many died of plague
   static int year        = 1;    // Turn number
   static int acres       = 1000; // Acres owned
   static int bushels     = 2800; // Bushels in storage
   static int pop         = 100;  // Population of city
   static int immigrants  = 5;    // How many folks immigrated
   static int starved     = 0;    // How many died of starvation
   static int rats        = 200;  // How many bushels rats destroyed 
   static int harvest     = 3000; // Size of last year's harvest
   static int acreCost    = 0;    // Cost of an acre in bushels
   static int ratPop      = 0;    // Number of rats in warehouses 
   static int stage       = 0;    // Keeps track of what we're asking for
   
   static boolean error  = false; // If true, we're showing an error.
   static boolean plague = false; // Plagues struck this turn.
   
   /**
    *  Create a Hammurabi Spotlet, show the instructions, and begin
    *  the game.
    */
   public static void main(String[] args)
   {
      
      String helpText = "Hammurabi\n\nGreetings, Hammurabi.  You have inherited"+
         " control of the city of Babylon and are charged with growing it into"+
         " a great nation.  You will do this through careful land management,"+
         " distributing food to your subjects, and permitting those subjects to"+
         " tend to your fields.  Rats, starvation, and plague are the enemies"+
         " which you must fight against every step of the way.\n\n"+
            
         "Each year you must decide whether to buy or sell land, how much grain"+
         " to feed the people, and how many acres to plant.  Once you've allocated"+
         " your resources by entering the appropriate amounts and tapping the 'Enter'"+
         " button after each, you will be told of the results of your decisions."+
         "  The game ends only when you sell all your land or tap the 'exit' button."+
            
         "\n\n Go now -- Babylon awaits your enlightened rule.\n\n"+
         "JavaOne 1999 KVM version by Jim Lesko";
         
      (new HelpDisplay(helpText, "Hammurabi",
                      NO_EVENT_OPTIONS)).register(NO_EVENT_OPTIONS);
   }


   /** 
    *  Game starts here.  Create UI structures, display beginning
    *  info, and begin allowing UI events.
    */ 
   public Hammurabi ()
   {
      // create the main buttons
      exitButton  = new Button ("Exit",130,146);
      errorButton = new Button ("OK",130,128);
      doneButton  = new Button ("Enter",10,146);
      
      // deactivate events
      playing = false;
      
      inputString[0] = (byte) 0;
      
      // show first turn 
      showStatus();
      
      // activate events
      playing = true;
   }


   /**
    *  Refresh screen with this turn's info.  
    */
   private void showStatus()
   {
      line = 16;
      error = false;
      
      g.clearScreen();
      
      g.drawString("Hammurabi", 5, 1, g.PLAIN);
      g.drawString("Year "+year, 110, 1, g.PLAIN);
      
      // draw the border between the top text and the input area
      g.drawLine(0,14,159,14,g.PLAIN);
      
      // Tell user how this turn went
      if (plague)
      {
         plagueCount = pop;       
         pop /= 2;
         plagueCount -= pop;
      
         line = drawText ("Plague! "+plagueCount+ ((plagueCount == 1) ?
            " person" : " people") +" died, "+starved+" starved,", line);
      } 
      else
         line = drawText ("This year "+starved+((starved == 1) ?
            " person" : " people") + " starved", line);
      
      line = drawText ("and "+immigrants+((immigrants == 1) ?
         " person" : " people") + " entered the city.", line);
      
      line = drawText ("The population is now "+pop+".", line);
      
      line = drawText ("We harvested "+harvest+" bushels at "+
         acreCost+" BPA.", line);
      
      line = drawText ("Rats ate "+rats+" bushels, leaving "+
         bushels+".",line);
      
      line = drawText ("The city owns "+acres+" acres of land.", line);
      
      acreCost = Runtime.random (Runtime.timeOfDay ()) % 6 + 22;
      
      line = drawText ("Land is worth "+acreCost+" an acre.", line);
      
      // seperate status from input area
      g.drawLine (0,line-1,159,line-1, g.PLAIN);
      
      // start first stage and prompt for input
      stage = 1;
      line += 12; // offset to put current status under line
      getInput ();
      
      // draw the bottom GUI buttons
      doneButton.paint();
      exitButton.paint();
      
      // draw the border between the bottom buttons and the text
      g.drawLine(0,142,159,142,g.PLAIN);
   }


   /**
    *  Displays text asking for the info we need at this stage.
    *  Just does display; doesn't actually get input.
    */
   private void getInput ()
   {
      inputString [0] = (byte) 0;
      lineChar = 0;

      // Set prompt:
      switch (stage)
      {
         case 1:
            prompt = "Buy how many acres?";
            break;
         case 2: 
            prompt = "Sell how many acres?";
            break;
         case 3:
            prompt = "How many bushels as food?";
            break;
         case 4:
            prompt = "Plant how many acres?";
            break;
         default:
            prompt = "Sorry, there was an error.";
            break;
      }

      // Display prompt w/ all input trappings:
      showCurrentStatus ();
      clearLine (0, line);
      displayInput (120, line, true);
      g.drawLine   (120, line+12, 150, line+12, g.GRAY);
      drawText (prompt, line);
   }


   /**
    *  Shows a line w/ current info.  It's easier for the player
    *  with this line; without it the player would need to keep track
    *  of results such as feeding, buy/sell land, etc., in her head.
    */    
   private void showCurrentStatus ()
   {
      line -= (lineHeight + 1);
      clearLine (0, line);
      line = drawText ("B: "+bushels+"  P: "+pop+"  A: "+acres, line);
   }

           
    /**
     *  Draws <code>text</code> at vertical location <code>line</code>.
     *  
     *  @returns int The location of the next line of text.
     */  
   public int drawText (String text, int line)
   {
      g.drawString (text, 5, line, g.PLAIN);
      return (line + lineHeight + 1);
   } 

 
   /**
    *  Shows input at the current coordinates.
    *  <code>cursor</code> should be true if a cursor should be drawn
    *  after the text.
    */ 
   private void displayInput (boolean cursor)
   {
      displayInput (displayX, displayY, cursor);
   }


   /**
    *  Shows input at x & y and adds a caret if <code>cursor</code>
    *  is true.  Also stores x & y for future use.
    */
   private void displayInput (int x, int y, boolean cursor)
   {
       displayX = x;
       displayY = y;

       // move the input to a string buffer
       StringBuffer text = new StringBuffer();
       for (int i = 0; i < inputStringLength; i++)
       {
          if (inputString[i] == 0)
             break;
          text.append((char) inputString[i]);
       }

       // if desired add a cursor
       if (cursor)
          text.append("|");

       // add a final null
       text.append((char) 0);

       // draw the text
       g.drawString(text.toString(), x, y, g.PLAIN);
   }


   /**  
    *  Checks for button events.  Responsible for updating state of the
    *  game, exiting, acknowledging errors, etc.
    */
   public void penDown(int x, int y)
   {
      // exit
      if (exitButton.pressed(x,y))
      {
         endGame ();
      }
  
      // error acknowledged 
      if (error && errorButton.pressed (x, y)) 
      {
         clearError ();
         error = false;
         getInput ();
         return;
      }

      // process this stage of the game 
      if (playing && doneButton.pressed (x, y))
      {
         inputString[lineChar] = (byte) 0;
         value = inputToInteger (inputString);
         processTurn ();
      }
   }


   /**
    *  Uses most recent <code>value</code> and does the right thing
    *  with it, depending on the stage of the game.
    */
   public void processTurn ()
   {
      switch (stage)
      {
         case 1:                             // buy
            if (value == 0)
            {
               stage++;
            } 
            else
            {
               if ((value * acreCost) <= bushels)
               {
                  bushels -= value * acreCost;
                  acres += value;
                   stage += 2;
               }
               else
               {
                  error = true;
               }
            }
            break;
         case 2:                            // sell
            if (value != 0)
            {
               if (value < acres)
               {
                  bushels += value * acreCost;
                  acres -= value;
                  stage++;
               }
               else if (value > acres)
               {
                  error = true;
               }
               else 
               {
                  endGame ();
               }
            }
            else
               stage++;
            break;
         case 3:                            // feed
            if (value > bushels)
            {
               error = true;
            }
            else 
            {
               bushels -= value;
               starved = pop - value / 20;
               immigrants = 0;
             
               if (starved < 0)
               {
                  immigrants = -1 * (starved / 2);
                  starved = 0;
               }

               stage++;
            }
            break;
         case 4:                            // plant
            if (value     > acres   || 
                value / 2 > bushels ||
                value     > pop * 10)
            {
               error = true;
            }
            else
            {
               // Essentially, the turn's over; update state &
               // figure out random events (plague): 
               bushels -= value / 2;
               acreCost = getRandom (5) + 1;
               harvest = acreCost * value;
               ratPop = getRandom (600); 
               rats = (ratPop * (bushels + harvest)) / 1000; 
               bushels = bushels - rats + harvest;
               
               plague = (getRandom (10) == 0);
               immigrants += (5 - acreCost) * bushels / 600 + 1;
               immigrants = (immigrants > 50) ? 50 : immigrants;
               immigrants = (immigrants < 0)  ? 0  : immigrants;
               pop += immigrants - starved;
      
               year ++;
               stage ++;
            }
            break;
         default:                                 // runtime problem! 
            endGame();
            break;
      }
        
      if (error)
      {
         clearLine ();
         showError ();
      }
      else if (stage == 5)
         showStatus ();
      else
         getInput ();
   }
       

   /**
    *  Finish the game.  Good place to add final score, saving stats,
    *  that sort of thing.
    */
   public void endGame ()
   {
      playing = false;
      Runtime.exit ();
   }


   /** 
    *  Returns random number from 0 to <code>max</code>, using the
    *  time of day as the seed.
    */
   static public int getRandom (int max)
   {
      return (Runtime.random (Runtime.timeOfDay ()) % max);
   }


   /**
    *  Returns the value of <code>input</code> as a String.
    */
   public String inputToString (byte[] input)
   {
      StringBuffer text = new StringBuffer();
      for (int i = 0; i < inputStringLength; i++)
      {
         if (inputString[i] == 0)
            break;
         text.append((char) inputString[i]);
      }
      return (text.toString());
   }


   /**
    *  Returns the value of <code>input</code> as an int.
    */
   public int inputToInteger (byte[] input)
   {
      return (inputToString (input).toInteger ());
   }


   /**
    *  Process character input & update display accordingly.
    */
   public void keyDown(int keyCode)
   {
      // process only if playing game 
      if (playing)
      {
         // handle backspace
         if (keyCode == 010)
         {
             if (lineChar > 0)
                 lineChar = lineChar - 1;

             // put a null at the end of the name
             inputString[lineChar] = (byte) 0;

             // clear the old text
             clearLine();

             // display the record
             displayInput (true);

             return;
          }

          // nl (012) could be used as a done character

          // ignore if not a printable character
          if (keyCode < 040)
             return;

          // handle new character
          if (lineChar < inputLength) // * else beep
          {
             // if the count is zero then clear the text
             //   (clear the prompt the first time)
             if (lineChar == 0)
                clearLine();

             // save the new character
             inputString[lineChar] = (byte) keyCode;

             // increment text count
             lineChar = lineChar + 1;

             // put a null at the end of the name
             inputString[lineChar] = (byte) 0;

             // display the record
             displayInput (true);

             return;
         }
      }
   }

     
   /**
    *  Let the player know that something isn't right.  Effectively
    *  blocks until user taps <code>errorButton</code>.
    */
   private void showError ()
   {
      drawText ("Hammurabi: Think Again.", 128);
      errorButton.paint();
   }
       
   
   /**
    *  Clears current line.
    */ 
   private void clearLine ()
   {
      clearLine (displayX, displayY);
   } 


   /**
    *  Clears line starting at x,y.  Caches x,y as the current 
    *  position.
    */
   private void clearLine (int x, int y)
   {
      displayX = x;
      displayY = y;

      inputString [0] = (byte) 0;
      inputString [1] = (byte) 0;
      lineChar = 0;
   
      g.drawRectangle (x, y, 159, lineHeight, g.ERASE, 0);
   }

     
   /**
    *  Clears the text/button shown by showError().
    */ 
   private void clearError ()
   {
      g.drawRectangle (0, 127, 159, 15, g.ERASE, 0);
   }
}