Simple Car Game




/* Simple Car game for a 16x2 LCD display
   You can use any Hitachi HD44780 compatible LCD.
   Wiring explained at http://www.arduino.cc/en/Tutorial/LiquidCrystal
   (I used theLCD Electronic Brick on bus 1:
     rs on pin 2, rw on pin 3, enable on pin 4,
     data on pins 5,6,7,8)
   There's also a "steering wheel" potentiometer on analog input 1,
   and a Piezo speaker on pin 9 (PWM).

   Enjoy,
   @TheRealDod, Nov 25, 2010
*/
#include <LiquidCrystal.h>
// LiquidCrystal display
// You can use any Hitachi HD44780 compatible. Wiring explained at
// http://www.arduino.cc/en/Tutorial/LiquidCrystal
// LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define btnRight     0
#define btnUp        1
#define btnDown      2
#define btnLeft      3
#define btnSelect    4
#define btnNone      5

// Steering wheel potentiometer
// const int POTPIN = 1;
// const int MAXPOT = 800; // no need to turn the wheel all the way to 1023 :)


// Piezo speaker
const int SPEAKERPIN = 3;

const int RANDSEEDPIN = 0; // an analog pin that isn't connected to anything

const int MAXSTEPDURATION = 300; // Start slowly, each step is 1 millisec shorter.
const int MINSTEPDURATION = 150; // This is as fast as it gets

const int NGLYPHS = 6;
// the glyphs will be defined starting from 1 (not 0),
// to enable lcd.print() of null-terminated strings
byte glyphs[NGLYPHS][8] = {
  // 1: car up
  { B00000,
    B01110,
    B11111,
    B01010,
    B00000,
    B00000,
    B00000,
    B00000
  }
  // 2: car down
  , {
    B00000,
    B00000,
    B00000,
    B00000,
    B01110,
    B11111,
    B01010,
    B00000
  }
  // 3: truck up
  , {
    B00000,
    B11110,
    B11111,
    B01010,
    B00000,
    B00000,
    B00000,
    B00000
  }
  // 4: truck down
  , {
    B00000,
    B00000,
    B00000,
    B00000,
    B11110,
    B11111,
    B01010,
    B00000
  }
  // 5: crash up
  , {
    B10101,
    B01110,
    B01110,
    B10101,
    B00000,
    B00000,
    B00000,
    B00000
  }
  // 6: crash down
  , {
    B00000,
    B00000,
    B00000,
    B10101,
    B01110,
    B01110,
    B10101,
    B00000
  }
};

const int NCARPOSITIONS = 4;

// Each position is mapped to a column of 2 glyphs
// Used to make sense when I had a 5th position
// where car or crash was drawn as 2 glyphs
// (can't do that since 0 terminates strings),
// so it's kinda silly now, but it ain't broke :)
const char BLANK = 32;
char car2glyphs[NCARPOSITIONS][2] = {
  {1, BLANK}, {2, BLANK}, {BLANK, 1}, {BLANK, 2}
};
char truck2glyphs[NCARPOSITIONS][2] = {
  {3, BLANK}, {4, BLANK}, {BLANK, 3}, {BLANK, 4}
};
char crash2glyphs[NCARPOSITIONS][2] = {
  {5, BLANK}, {6, BLANK}, {BLANK, 5}, {BLANK, 6}
};

const int ROADLEN = 15; // LCD width (not counting our car)
int road[ROADLEN]; // positions of other cars
char line_buff[2 + ROADLEN]; // aux string for drawRoad()
int road_index;
int car_pos;
int old_key = btnNone;
// Off-the-grid position means empty column, so MAXROADPOS
// determines the probability of a car in a column
// e.g. 3*NCARPOSITIONS gives p=1/3
const int MAXROADPOS = 3 * NCARPOSITIONS;
int step_duration;

int crash; // true if crashed
unsigned int crashtime; // millis() when crashed
const int CRASHSOUNDDURATION = 250;

const char *INTRO1 = "Trucks ahead,";
const char *INTRO2 = "Drive carefully";
const int INTRODELAY = 2000;

void setup()
{
  crash = crashtime = road_index = 0;
  step_duration = MAXSTEPDURATION;
  line_buff[1 + ROADLEN] = '\0'; // null terminate it
  randomSeed(analogRead(RANDSEEDPIN));
  for (int i = 0; i < NGLYPHS; i++) {
    lcd.createChar(i + 1, glyphs[i]);
  }
  for (int i = 0; i < ROADLEN; i++) {
    road[i] = -1;
  }
  pinMode(SPEAKERPIN, OUTPUT);
  analogWrite(SPEAKERPIN, 0); // to be on the safe side
  lcd.begin(16, 2);
  getSteeringWheel();
  drawRoad();
  lcd.setCursor(1, 0);
  lcd.print(INTRO1);
  lcd.setCursor(1, 1);
  lcd.print(INTRO2);
  delay(INTRODELAY);
}

void loop() {
  unsigned long now = millis() - INTRODELAY;
  if (!crash) {
    //getSteeringWheel();
    crash = (car_pos == road[road_index]);
  }
  if (crash) {
    if (!crashtime) {
      crashtime = now;
      drawRoad();
      // Game over text
      // (keep first 2 "crash" columns intact)
      lcd.setCursor(2, 0);
      lcd.print("Crashed after");
      lcd.setCursor(2, 1);
      lcd.print(now / 1000);
      lcd.print(" seconds.");
    }
    if ((now - crashtime) < CRASHSOUNDDURATION) {
      analogWrite(SPEAKERPIN, random(255)); // white noise
    }
    else {
      analogWrite(SPEAKERPIN, 0); // dramatic post-crush silence :)
    }
    delay(10); // Wait a bit between writes
  }
  else {

    int prev_pos = road[(road_index - 1) % ROADLEN];
    int this_pos = random(MAXROADPOS);
    while (abs(this_pos - prev_pos) < 2) { // don't jam the road
      this_pos = random(MAXROADPOS);
    }
    road[road_index] = this_pos;
    road_index = (road_index + 1) % ROADLEN;
    drawRoad();
    //delay(step_duration);
    unsigned long wait = millis() + step_duration;
    while (millis() < wait) getSteeringWheel();
    if (step_duration > MINSTEPDURATION) {
      step_duration--; // go faster
    }
  }
}
void getSteeringWheel() {
  //car_pos = map(analogRead(POTPIN),0,1024,0,NCARPOSITIONS);
  int k = getKey();
  if (k != old_key) {
    switch (k) {
      case btnDown:
        if (car_pos < NCARPOSITIONS - 1) car_pos++;
        break;
      case btnUp:
        if (car_pos > 0) car_pos--;
        break;
    }
    old_key = k;
  }
}

void drawRoad() {
  for (int i = 0; i < 2; i++) {
    if (crash) {
      line_buff[0] = crash2glyphs[car_pos][i];
    }
    else {
      line_buff[0] = car2glyphs[car_pos][i];
    }
    for (int j = 0; j < ROADLEN; j++) {
      int pos = road[(j + road_index) % ROADLEN];
      line_buff[j + 1] = pos >= 0 && pos < NCARPOSITIONS ? truck2glyphs[pos][i] : BLANK;
    }
    lcd.setCursor(0, i);
    lcd.print(line_buff);
  }
}

int getKey() {
  int b = analogRead(A0);
  if (b > 1000) return btnNone;
  delay(8);
  if (b < 50) return btnRight;
  if (b < 180) return btnUp;
  if (b < 330) return btnDown;
  if (b < 520) return btnLeft;
  if (b < 700) return btnSelect;
}


Intuitive Machines™ Biotronics™ Zoikrons Autognorics™  ELFS™ IM™ 
are original trademarks and logos
solely distributed by 
L.A.W.S.I.N. 

Comments