// Download this in a .zip file.


/* Defuse for the Freetronics 16x2 LCD shield
 http://www.freetronics.com/pages/16x2-lcd-shield-quickstart-guide#.UBKMm7T9OK9
 http://www.atariarchives.org/morebasicgames/showpage.php?page=48
 
 You're in a building of 1 million rooms. There are 100 floor, and 100x100 rooms
 per floor. A bomb is about to go off in 200 seconds. Use your bomb detector to
 find the bomb. All it has is a single signal to read.
 
 by eturnerx@gmail.com 201211
 
 */
/*--------------------------------------------------------------------------------------
 Includes
 --------------------------------------------------------------------------------------*/
#include    // include LCD library
/*--------------------------------------------------------------------------------------
 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             05  // Waiting for a keypress
#define STATE_GAMESTART           20  // The game begins
#define STATE_TURNSTART           30
#define STATE_INPUTNUMBER         40

// Misc. Defines
#define MSGFLASHDELAY           1500  // How long to flash messages for (ms)?

/*--------------------------------------------------------------------------------------
 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 timer, inputpos, flash;
boolean update;
byte bombpos[6], pos[6], signal[6];
byte ipos[6] = { 
  4,5, 11,12, 14,15 };
int tmp;
unsigned long flashtime;

/*--------------------------------------------------------------------------------------
 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( "-_D E F U S E _-" );
  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( "= D E F U S E  =");
    lcd.setCursor( 0, 1 );
    lcd.print( "Press [any] key" );
    nextstate = STATE_GAMESTART;
    state = STATE_KEYWAIT;
    break;

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

  case STATE_GAMESTART:
    //Init signal order
    for(int i = 0; i < 6; i++) signal[i] = i;

    //Shuffle signal order
    for(int cnt = 0; cnt < 500; cnt++) {
      int pos1 = random(6);
      int pos2 = random(6);
      tmp = signal[pos1];
      signal[pos1] = signal[pos2];
      signal[pos2] = tmp;
    }
    //Pick a bomb position. It can't be 00,00,00
    do {
      tmp = 0;
      for(int i = 0; i < 6; i++) {
        pos[i] = 0;
        bombpos[i] = random(9);
        tmp += bombpos[i];
      }
    }
    while(tmp == 0);
    timer = 210;
    lcd.clear();
    //         1234567890123456
    lcd.print("Find the bomb in"); 
    lcd.setCursor(0,1);
    lcd.print("time! Here's the");
    delay(MSGFLASHDELAY); 
    lcd.setCursor(0,0);
    lcd.print("detector. Theres"); 
    lcd.setCursor(0,1);
    lcd.print("1 million rooms.");
    delay(MSGFLASHDELAY); 
    lcd.setCursor(0,0);
    lcd.print("Umm. So how does");
    lcd.setCursor(0,1);
    lcd.print("this thing work?");
    delay(MSGFLASHDELAY); 
    state = STATE_TURNSTART;
    //falls through

  case STATE_TURNSTART:
    //         1234567890123456
    //         SSSSSSsig  t:TTT
    //         Flr:YY  Rm:XX.ZZ
    if(timer == 0) {
      flashbacklight();
      lcd.setCursor(0,0);
      lcd.print("B O O O O O O "); 
      lcd.setCursor(0,1);
      lcd.print(" O O O O O OM!");
      delay(MSGFLASHDELAY); 
      lcd.clear();
      lcd.print("The bombs was"); 
      lcd.setCursor(0,1);
      lcd.print("Flr:"); 
      lcd.print(bombpos[0]); 
      lcd.print(bombpos[1]);
      lcd.print("  Rm:"); 
      lcd.print(bombpos[2]); 
      lcd.print(bombpos[3]);
      lcd.print("."); 
      lcd.print(bombpos[4]); 
      lcd.print(bombpos[5]);
      nextstate = STATE_TITLE;
      state = STATE_KEYWAIT;
      break;
    }
    timer -= 10;
    lcd.clear();
    for(int i = 0; i < 6; i++) {
      lcd.print(9 - abs((int)bombpos[signal[i]] - (int)pos[signal[i]]));
    }
    lcd.print("sig  t:");
    if(timer<100) lcd.print("0");
    lcd.print(timer);
    lcd.setCursor(0,1);
    lcd.print("Flr:YY  Rm:XX.ZZ");
    for(int i = 0; i < 6; i++) {
      lcd.setCursor(ipos[i],1);
      lcd.print(pos[i]);
    }
    inputpos = 0;
    flashtime = millis() + 750; 
    flash = 1;
    state = STATE_INPUTNUMBER;
    //falls through

  case STATE_INPUTNUMBER:
    update = false;
    if(millis() >= flashtime) {
      flashtime = millis() + (750 - (500 * flash));      
      flash = 1 - flash;
      update = true;
    }
    if(buttonJustPressed) {
      switch(button) {
      case BUTTON_LEFT:
        lcd.setCursor(ipos[inputpos],1);
        lcd.print(pos[inputpos]);
        if(inputpos == 0) inputpos = 5;
        else inputpos--;
        update = true;
        break;

      case BUTTON_RIGHT:
        lcd.setCursor(ipos[inputpos],1);
        lcd.print(pos[inputpos]);
        if(inputpos == 5) inputpos = 0;
        else inputpos++;
        update = true;
        break;

      case BUTTON_DOWN:
        if(pos[inputpos] == 0) pos[inputpos] = 9; 
        else pos[inputpos]--;
        update = true;
        break;

      case BUTTON_UP:
        if(pos[inputpos] == 9) pos[inputpos] = 0; 
        else pos[inputpos]++;
        update = true;
        break;

      case BUTTON_SELECT:
        lcd.clear();
        if(bombpos[0] == pos[0] && bombpos[1] == pos[1] && bombpos[2] == pos[2] &&
        bombpos[3] == pos[3] && bombpos[4] == pos[4] && bombpos[5] == pos[5]) {
          //         1234567890123456
          lcd.print("Bomb deactivated"); lcd.setCursor(0,1);
          lcd.print("You're a hero!");
          nextstate = STATE_TITLE;
          state = STATE_KEYWAIT;
        } else {
          lcd.print("Scanning");
          delay(MSGFLASHDELAY/2);
          lcd.print(".");
          delay(MSGFLASHDELAY/3);
          lcd.print(".");
          delay(MSGFLASHDELAY/4);
          lcd.print(".");
          delay(MSGFLASHDELAY/5);
          lcd.print(".");
          delay(MSGFLASHDELAY/6);
          state = STATE_TURNSTART;
        }
        update = false;
        break;
      }
    }
    if(update == true) {
      lcd.setCursor(ipos[inputpos],1);
      if(flash == 0) lcd.print("_");
      else lcd.print(pos[inputpos]);
    }
    break;

  default: 
    break;
  }
  //clear the buttonJustPressed & buttonJustReleased flags; they've already done their job
  if(buttonJustPressed)  buttonJustPressed  = false;
  if(buttonJustReleased) buttonJustReleased = false;
  delay(25); // Delay to stop buttons "jittering" false presses. Not as good as a real debounce.
}

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

/*--------------------------------------------------------------------------------------
 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 eve
    //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 );
}