// Download this in a .zip file.


/* High Noon Converted/Reimagined for the Freetronics 16x2 LCD shield
by eturnerx@gmail.com 201209

Based info found here: http://www.mybitbox.com/highnoon-1970/
Original game by Chris Gaylo, 1970

http://www.freetronics.com/pages/16x2-lcd-shield-quickstart-guide#.UBKMm7T9OK9

*/
/*--------------------------------------------------------------------------------------
  Includes
--------------------------------------------------------------------------------------*/
#include    // include LCD library
#include     // put the strings into flash RAM - SRAM is filling up!
/*--------------------------------------------------------------------------------------
  Defines
--------------------------------------------------------------------------------------*/
// Pins in use
#define BUTTON_ADC_PIN           A0  // A0 is the button ADC input
#define LCD_BACKLIGHT_PIN         3  // D3 controls LCD backlight
// ADC readings expected for the 5 buttons on the ADC input
#define RIGHT_10BIT_ADC           0  // right
#define UP_10BIT_ADC            145  // up
#define DOWN_10BIT_ADC          329  // down
#define LEFT_10BIT_ADC          505  // left
#define SELECT_10BIT_ADC        741  // right
#define BUTTONHYSTERESIS         10  // hysteresis for valid button sensing window
//return values for ReadButtons()
#define BUTTON_NONE               0  // 
#define BUTTON_RIGHT              1  // 
#define BUTTON_UP                 2  // 
#define BUTTON_DOWN               3  // 
#define BUTTON_LEFT               4  // 
#define BUTTON_SELECT             5  // 

// Define our states
#define STATE_TITLE               00    // A title screen
#define STATE_KEYWAIT             10    // Waiting for a keypress
#define STATE_TITLE               20
#define STATE_NEWGAME             30    // Flavour intro
#define STATE_NEWTURN             40
#define STATE_PLAYERMOVE          50
#define STATE_ADVANCING           60
#define STATE_ADVANCINGINPUT      65    // How many paces?
#define STATE_STANDING            70
#define STATE_FIRING              80
#define STATE_DUCK                90
#define STATE_NOTROUGHS           100
#define STATE_GIVEUP              110
#define STATE_RUN                 120
#define STATE_RUNINPUT            125
#define STATE_NOAMMO              130
#define STATE_BARTMOVES           200
#define STATE_BARTNOAMMO          210
#define STATE_RELOAD              190   // Not in original... but
#define STATE_GAMEOVERWON         250
#define STATE_GAMEOVERLOST        251

/*--------------------------------------------------------------------------------------
  String Table
--------------------------------------------------------------------------------------*/
#define STR_HIGHNOON         6
#define STR_GAMEOVER         7
#define STR_GAMEOVERWIN      8
#define STR_GAMEOVERLOST     9
#define STR_BARTNOAMMO      10
#define STR_YOUNOAMMO       11
#define STR_HOWFARTOWALK    12
#define STR_YOUGITRIGHTIN   13
#define STR_BARTSUGLYFACE   14
#define STR_YOUADVANCEHERO  15
#define STR_WAIT1           16
#define STR_WAIT2           17
#define STR_YOUFIREAND      18
#define STR_NICKHIMDRAT     19
#define STR_KILLHIMYEEHAW   20
#define STR_MISSBYAMILE     21
#define STR_CLICKNOAMMO     22
#define STR_NOMORETROUGHS   23
#define STR_YOULOOKSILLY    24
#define STR_YOUDUCKBEHIND   25
#define STR_AWATERTROUGH    26
#define STR_THEREAINTMORE   27
#define STR_COWARDLYTROUGHS 28
#define STR_BARTACCEPTSAND  29
#define STR_YOULEAVEALIVE   30
#define STR_HOWFARTORUN     31
#define STR_YOURLEGSCARRY   32
#define STR_YOUOUTTATOWN    33
#define STR_YOUTURNANDRUN   34
#define STR_BARTGETSRIGHT   35
#define STR_UPINYOURFACE    36
#define STR_BARTCOMESONUP   37
#define STR_BARTSHOOTSBUT   38
#define STR_TROUGHHIDESYOU  39
#define STR_BARTSHOOTSAND   40
#define STR_NICKSYOUOUCH    41
#define STR_KILLSYOURIP     42
#define STR_MISSESBYAMILE   43
#define STR_BARTHIGHTAILS   44
#define STR_FOLKSWANTBLOOD  45
#define STR_SOGIVENEWAMMO   46
//                               1234567890123456
prog_char string_0[]  PROGMEM = "run  _WALK_ wait";
prog_char string_1[]  PROGMEM = "walk _WAIT_ fire";
prog_char string_2[]  PROGMEM = "wait _FIRE_ duck";
prog_char string_3[]  PROGMEM = "fire _DUCK_ quit";
prog_char string_4[]  PROGMEM = "duck _QUIT_ run ";
prog_char string_5[]  PROGMEM = "quit _RUN _ walk";
prog_char string_6[]  PROGMEM = "H I G H  N O O N";
prog_char string_7[]  PROGMEM = "-- GAME  OVER --";
prog_char string_8[]  PROGMEM = "YOU WIN $20,000!";
prog_char string_9[]  PROGMEM = "--  YOU LOSE  --";
prog_char string_10[] PROGMEM = "Bart has no ammo";
prog_char string_11[] PROGMEM = "You have no ammo";
prog_char string_12[] PROGMEM = "How far to walk?";
prog_char string_13[] PROGMEM = "You git right in";
prog_char string_14[] PROGMEM = "Bart's ugly face";
prog_char string_15[] PROGMEM = "You advance hero";
prog_char string_16[] PROGMEM = "Wait=Easy Target";
prog_char string_17[] PROGMEM = "Brave & stupid";
prog_char string_18[] PROGMEM = "You fire and ...";
prog_char string_19[] PROGMEM = "nick him. Drat!";
prog_char string_20[] PROGMEM = "kill him! Yeehaw";
prog_char string_21[] PROGMEM = "miss by a mile";
prog_char string_22[] PROGMEM = "Click! No ammo";
prog_char string_23[] PROGMEM = "No more troughs";
prog_char string_24[] PROGMEM = "You look silly";
prog_char string_25[] PROGMEM = "You duck behind";
prog_char string_26[] PROGMEM = "a water trough";
prog_char string_27[] PROGMEM = "There ain't more";
prog_char string_28[] PROGMEM = "cowardly troughs";
prog_char string_29[] PROGMEM = "Bart accepts and";
prog_char string_30[] PROGMEM = "you leave alive";
prog_char string_31[] PROGMEM = "How far to run?";
prog_char string_32[] PROGMEM = "Your legs carry";
prog_char string_33[] PROGMEM = "you outta town";
prog_char string_34[] PROGMEM = "You turn and run";
prog_char string_35[] PROGMEM = "Bart gets right";
prog_char string_36[] PROGMEM = "up in your face";
prog_char string_37[] PROGMEM = "Bart comes on up";
prog_char string_38[] PROGMEM = "Bart shoots but";
prog_char string_39[] PROGMEM = "trough hides you";
prog_char string_40[] PROGMEM = "Bart shoots and";
prog_char string_41[] PROGMEM = "nicks you. Ouch!";
prog_char string_42[] PROGMEM = "kills you. R.I.P";
prog_char string_43[] PROGMEM = "misses by a mile";
prog_char string_44[] PROGMEM = "Bart high tails";
prog_char string_45[] PROGMEM = "Folks want blood";
prog_char string_46[] PROGMEM = "so give new ammo";
PROGMEM const char *string_table[] = {
  string_0,  string_1,  string_2,  string_3,  string_4,  string_5,
  string_6,  string_7,  string_8,  string_9,  string_10, string_11,
  string_12, string_13, string_14, string_15, string_16, string_17,
  string_18, string_19, string_20, string_21, string_22, string_23,
  string_24, string_25, string_26, string_27, string_28, string_29,
  string_30, string_31, string_32, string_33, string_34, string_35,
  string_36, string_37, string_38, string_39, string_40, string_41,
  string_42, string_43, string_44, string_45, string_46
};


/*--------------------------------------------------------------------------------------
  Variables
--------------------------------------------------------------------------------------*/
byte buttonJustPressed  = false;         //this will be true after a ReadButtons() call if triggered
byte buttonJustReleased = false;         //this will be true after a ReadButtons() call if triggered
byte buttonWas          = BUTTON_NONE;   //used by ReadButtons() for detection of button events

// Setup the variables that will be used
byte state, nextstate;
byte bulletsyou, bulletsbart;
byte choice, inputnumber;
int turns, distance, troughs, hitchance;
byte bartmoves;
char stringbuffer[17];
int choicetostate[6] = { 
  STATE_ADVANCING, STATE_STANDING, STATE_FIRING,
  STATE_DUCK, STATE_GIVEUP, STATE_RUN
};

/*--------------------------------------------------------------------------------------
  Init the LCD library with the LCD pins to be used
--------------------------------------------------------------------------------------*/
LiquidCrystal lcd( 8, 9, 4, 5, 6, 7 );   //Pins for the freetronics 16x2 LCD shield. LCD: ( RS, E, LCD-D4, LCD-D5, LCD-D6, LCD-D7 )


/*--------------------------------------------------------------------------------------
  setup()
  Called by the Arduino framework once, before the main loop begins
--------------------------------------------------------------------------------------*/
void setup() {
  // Setup pins. 
  pinMode(BUTTON_ADC_PIN, INPUT );         // Analogue input for the LCD's buttons
  digitalWrite(BUTTON_ADC_PIN, LOW );
  pinMode(LCD_BACKLIGHT_PIN, OUTPUT );     // Backlight control (off/on)
  digitalWrite(LCD_BACKLIGHT_PIN, HIGH );  // Turn the backlight on
  randomSeed(analogRead(1));               // Send the random number generator

  lcd.begin( 16, 2 );                      // Tell LCD library this is 16x2 display
  lcd.setCursor( 0, 0 );
  //          1234567890123456
  lcd.print(getString(STR_HIGHNOON));
  lcd.setCursor( 0, 1 );
  lcd.print( '.' );
  lcd.print(  '.' );   flashbacklight();
  lcd.print(   '.' );  flashbacklight();
  lcd.print(    '.' ); flashbacklight();
  state = STATE_TITLE;  
}


/*--------------------------------------------------------------------------------------
  loop()
  Arduino main loop
--------------------------------------------------------------------------------------*/
void loop() {
  byte button;
  
  button = ReadButtons();
  switch(state) {                  //    Do different things depending on "state"

    case STATE_TITLE:
      lcd.setCursor( 0, 0 );
      //          1234567890123456
      lcd.print(getString(STR_HIGHNOON));
      pressanykey(STATE_NEWGAME);
      break;

    case STATE_KEYWAIT:
      if(buttonJustPressed) state = nextstate;
      break;

    case STATE_NEWGAME:
      bulletsyou = bulletsbart = 4;
      troughs = random(2,5);
      distance = 90;
      turns = 0;
      state = STATE_NEWTURN;
      // falls through

    case STATE_NEWTURN:
      turns++;
      lcd.clear();
      lcd.print('B');
      for(byte i=0;i 0) {
        bulletsyou--;
        //         1234567890123456
        lcd.print(getString(STR_YOUFIREAND)); lcd.setCursor(0,1);
        if(random(1,100) >= distance) {
          if(random(1,4) == 1) {
            //         1234567890123456
            lcd.print(getString(STR_NICKHIMDRAT));
            nextstate = STATE_BARTMOVES;
          } else {
            lcd.print(getString(STR_KILLHIMYEEHAW));
            nextstate = STATE_GAMEOVERWON;
          }
        } else {
          //         1234567890123456
          lcd.print(getString(STR_MISSBYAMILE));
          nextstate = STATE_BARTMOVES;
        }
        //check bullets etc if we're still live
        if(nextstate == STATE_BARTMOVES && bulletsyou == 0) {
          if(bulletsbart == 0) nextstate = STATE_RELOAD;
          else nextstate = STATE_NOAMMO;
        }
      } else {
        //         1234567890123456
        lcd.print(getString(STR_CLICKNOAMMO));
        choice = 1;
        pressanykey(STATE_BARTMOVES);
      }
      state = STATE_KEYWAIT;
      break;

    case STATE_DUCK:
      lcd.clear();
      troughs = troughs - 1;
      if(troughs < 0) {
        //         1234567890123456
        lcd.print(getString(STR_NOMORETROUGHS)); lcd.setCursor(0,1);
        lcd.print(getString(STR_YOULOOKSILLY));
        choice = 1;
      } else {
        lcd.print(getString(STR_YOUDUCKBEHIND)); lcd.setCursor(0,1);
        lcd.print(getString(STR_AWATERTROUGH));
      }
      if(troughs != 0) nextstate = STATE_BARTMOVES;
      else nextstate = STATE_NOTROUGHS;
      state = STATE_KEYWAIT;
      break;
      
    case STATE_NOTROUGHS:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_THEREAINTMORE)); lcd.setCursor(0,1);
      lcd.print(getString(STR_COWARDLYTROUGHS));
      nextstate = STATE_BARTMOVES;
      state = STATE_KEYWAIT;
      break;

    case STATE_GIVEUP:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_BARTACCEPTSAND)); lcd.setCursor(0,1);
      lcd.print(getString(STR_YOULEAVEALIVE));
      nextstate = STATE_GAMEOVERLOST;
      state = STATE_KEYWAIT;
      break;

    case STATE_RUN:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_HOWFARTORUN));
      inputnumber = 1;
      printinputnumber();  
      state = STATE_RUNINPUT;
      break;
      
    case STATE_RUNINPUT:
      if(doinputnumber(0,10,button)) {
        if(inputnumber == 0) {
          state = STATE_STANDING;
        } else if(distance + inputnumber >= 120) {
          lcd.clear();
          //         1234567890123456
          lcd.print(getString(STR_YOURLEGSCARRY)); lcd.setCursor(0,1);
          lcd.print(getString(STR_YOUOUTTATOWN));
          nextstate = STATE_GAMEOVERLOST;
          state = STATE_KEYWAIT;
        } else {
          lcd.clear();
          distance = distance + inputnumber;
          //         1234567890123456
          lcd.print(getString(STR_YOUTURNANDRUN));
          pressanykey(STATE_BARTMOVES);
        }
      }
      break;

    case STATE_NOAMMO:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_YOUNOAMMO));
      pressanykey(STATE_BARTMOVES);
      break;

    case STATE_BARTMOVES:
      lcd.clear();
      
      //Simple AI - a little different to the original, maybe smarter a tad?
      hitchance = distance;
      if(choice == 1) hitchance -= 20;       // Easier to hit you when you stand or miss a duck or fire with no ammo
      else if(choice == 4) hitchance += 10;  // Cannot hit behind a trough, but Bart doesn't fully get this
      else if(choice == 5) hitchance -= 30;  // Easier to hit you when you run

      if(bulletsbart == 0) bartmoves = 2;      // If he's outta ammo he runs a bit
      else if(distance < 20) bartmoves = 1;    // If we're close Bart shoots
      else if(bulletsyou == 0) bartmoves = 0;  // Otherwise if we've no more bullets, Bart moves closer for the shot
      else if(distance > 80) bartmoves = 0;    // Otherwise if we're too far apart he moves up
      else {
        if(random(1,100) >= hitchance)     // Otherwise, his move depends on the chance of hitting you
          bartmoves = 1;                       // Shoots!
        else
          bartmoves = 0;                       // Moves
      }
      
      switch(bartmoves) {
        case 0:
          bartmoves = random(1,11);
          distance = distance - bartmoves;
          if(distance < 0) {
            distance = 0;
            //         1234567890123456
            lcd.print(getString(STR_BARTGETSRIGHT)); lcd.setCursor(0,1);
            lcd.print(getString(STR_UPINYOURFACE));
          } else {
            //         1234567890123456
            lcd.print(getString(STR_BARTCOMESONUP));
            lcd.setCursor(0,1);
            lcd.print("about ");
            lcd.print(bartmoves);
            lcd.print(" paces.");
          }
          nextstate = STATE_NEWTURN;
          break;
          
        case 1:
          bulletsbart--;
          if(choice == 3) {
            //         1234567890123456
            lcd.print(getString(STR_BARTSHOOTSBUT)); lcd.setCursor(0,1);
            lcd.print(getString(STR_TROUGHHIDESYOU));
            nextstate = STATE_NEWTURN;
          } else {
            lcd.print(getString(STR_BARTSHOOTSAND));
            lcd.setCursor(0,1);
            if(random(1,100) >= hitchance) {
              if(random(1,4) == 1) {
                //         1234567890123456
                lcd.print(getString(STR_NICKSYOUOUCH));
                nextstate = STATE_NEWTURN;
              } else {
                lcd.print(getString(STR_KILLSYOURIP));
                nextstate = STATE_GAMEOVERLOST;
              }
            } else {
              //         1234567890123456
              lcd.print(getString(STR_MISSESBYAMILE));
              nextstate = STATE_NEWTURN;
            }
          }
          //check bullets etc if we're still live
          if(nextstate == STATE_NEWTURN && bulletsbart == 0) {
           if(bulletsyou == 0) nextstate = STATE_RELOAD;
           else nextstate = STATE_BARTNOAMMO;
          }
          break;
          
        case 2:
          bartmoves = random(1,6);
          distance = distance + bartmoves;
          //         1234567890123456
          lcd.print(getString(STR_BARTHIGHTAILS));
          lcd.setCursor(0,1);
          lcd.print("it back ");
          lcd.print(bartmoves);
          lcd.print(" paces");
          nextstate = STATE_NEWTURN;
          break;
      }
      state = STATE_KEYWAIT;
      break;
      
    case STATE_BARTNOAMMO:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_BARTNOAMMO));
      pressanykey(STATE_NEWTURN);
      break;

    case STATE_RELOAD:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_FOLKSWANTBLOOD)); lcd.setCursor(0,1);
      lcd.print(getString(STR_SOGIVENEWAMMO));
      bulletsyou = bulletsbart = random(1,4);
      if(distance < 30) distance = 30;
      if(distance > 50) distance = 50;
      nextstate = STATE_NEWTURN;
      state = STATE_KEYWAIT;
      break;

    case STATE_GAMEOVERWON:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_GAMEOVER)); lcd.setCursor(0,1);
      lcd.print(getString(STR_GAMEOVERWIN));
      nextstate = STATE_TITLE;
      state = STATE_KEYWAIT;
      break;

    case STATE_GAMEOVERLOST:
      lcd.clear();
      //         1234567890123456
      lcd.print(getString(STR_GAMEOVER)); lcd.setCursor(0,1);
      lcd.print(getString(STR_GAMEOVERLOST));
      nextstate = STATE_TITLE;
      state = STATE_KEYWAIT;
      break;

    default: break;
  }
  //clear the buttonJustPressed & buttonJustReleased flags; they've already done their job
  if(buttonJustPressed)  buttonJustPressed  = false;
  if(buttonJustReleased) buttonJustReleased = false;
  delay(50);  // Delay to stop buttons "jittering" false presses
}

void flashbacklight() {
  digitalWrite(LCD_BACKLIGHT_PIN, LOW);  delay(150);
  digitalWrite(LCD_BACKLIGHT_PIN, HIGH); delay(150);
}

void pressanykey(byte _nextstate) {
  lcd.setCursor( 0, 1 );
  //          1234567890123456
  lcd.print( "Press [any] key " );
  nextstate = _nextstate;
  state = STATE_KEYWAIT;
}

boolean doinputnumber(int minimum, int maximum, byte button) {
  boolean retval = false;
  if(buttonJustPressed) {
    switch(button) {
      case BUTTON_LEFT:
        if(inputnumber <= minimum) inputnumber = maximum; else inputnumber = inputnumber - 1;
        printinputnumber();  
        break;
      case BUTTON_RIGHT:
        if(inputnumber >= maximum) inputnumber = minimum; else inputnumber = inputnumber + 1;
        printinputnumber();  
        break;
      case BUTTON_SELECT:
        retval = true;
        break;
    }
  }
  return retval;
}

void printinputnumber() {
  lcd.setCursor(4,1);
  if(inputnumber <= 0) lcd.print("   "); else lcd.print("<  ");
  if(inputnumber <= 9) lcd.print(' ');
  lcd.print(inputnumber);
  if(inputnumber <= 99) lcd.print("  >");
}

char *getString(int stringindex) {
  strcpy_P(stringbuffer, (char*)pgm_read_word(&(string_table[stringindex])));
  return stringbuffer;
}

/*--------------------------------------------------------------------------------------
  ReadButtons()
  Detect the button pressed and return the value
  Uses global values buttonWas, buttonJustPressed, buttonJustReleased.
--------------------------------------------------------------------------------------*/
byte ReadButtons() {
   unsigned int buttonVoltage;
   byte button = BUTTON_NONE;   // return no button pressed if the below checks don't write to btn
   
   //read the button ADC pin voltage
   buttonVoltage = analogRead( BUTTON_ADC_PIN );
   //sense if the voltage falls within valid voltage windows
   if( buttonVoltage < ( RIGHT_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_RIGHT;
   }
   else if(   buttonVoltage >= ( UP_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( UP_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_UP;
   }
   else if(   buttonVoltage >= ( DOWN_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( DOWN_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_DOWN;
   }
   else if(   buttonVoltage >= ( LEFT_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( LEFT_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_LEFT;
   }
   else if(   buttonVoltage >= ( SELECT_10BIT_ADC - BUTTONHYSTERESIS )
           && buttonVoltage <= ( SELECT_10BIT_ADC + BUTTONHYSTERESIS ) )
   {
      button = BUTTON_SELECT;
   }
   //handle button flags for just pressed and just released events
   if( ( buttonWas == BUTTON_NONE ) && ( button != BUTTON_NONE ) )
   {
      //the button was just pressed, set buttonJustPressed, this can optionally be used to trigger a once-off action for a button press event
      //it's the duty of the receiver to clear these flags if it wants to detect a new button change event
      buttonJustPressed  = true;
      buttonJustReleased = false;
   }
   if( ( buttonWas != BUTTON_NONE ) && ( button == BUTTON_NONE ) )
   {
      buttonJustPressed  = false;
      buttonJustReleased = true;
   }
   
   //save the latest button value, for change event detection next time round
   buttonWas = button;
   
   return( button );
}