Code

//Snake source: https://www.youtube.com/watch?v=IkTK6WMlMs4 & http://pastebin.com/yiDBzjvZ

#include <LiquidCrystal.h>
#include <Time.h>

// LCD variables
LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
int adc_key_val[5] = {50, 200, 400, 600, 800 };
int NUM_KEYS = 5;
int adc_key_in;
int key = -1;
int oldkey = -1;
int pressedKey = -1;

// project variables
int pinIn = 3;
int pinOut = 2;
int reading;
int previous = LOW;
long lastTime;
long checkTime = 100;
bool snakeMode = false;
bool clockMode = false;

// clock variables
bool settingClock, settingTime, settingDate;
bool settingHour, settingMinute;
bool settingDay, settingMonth, settingYear;
int h, m, s, d, n, y, dMax;

//snake variables
unsigned long time, timeNow;
int gameSpeed;
boolean skip, gameOver, gameStarted;
int olddir;
int selectedLevel, levels;

boolean x[16][80];
byte myChar[8];
byte nullChar[8] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
boolean special;

struct partdef
{
  int row, column, dir; //0 - up, 1 - down, 2 - right, 3 - left
  struct partdef *next;
};
typedef partdef part;

part *head, *tail;
int i, j, collected;
long pc, pr;

void startF()
{
  gameOver = false;
  gameStarted = false;
  selectedLevel = 1;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Select to Start");
  collected = 0;
  gameSpeed = 8;
  createSnake(3);
  time = 0;
}

void setup()
{
  pinMode(pinIn, INPUT_PULLUP);
  pinMode(pinOut, OUTPUT);
  Serial.begin(9600);
  lcd.begin(16, 2);
  setTime(12, 00, 00, 01, 01, 2016);
  levels = 5; //number of lvls
  checkMode();
}

void loop()
{
  while (snakeMode)
  {
    if (!gameOver && !gameStarted)
    {
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);  // convert into key press
      if (key != oldkey)   // if keypress is detected
      {
        delay(50);  // wait for debounce time
        adc_key_in = analogRead(0);    // read the value from the sensor
        key = get_key(adc_key_in);    // convert into key press
        if (key != oldkey)
        {
          oldkey = key;
          if (key >= 0)
          {
            olddir = head->dir;
            if (key == 1 && selectedLevel < levels) selectedLevel++;
            if (key == 2 && selectedLevel > 1) selectedLevel--;
            if (key == 4)
            {
              lcd.clear();
              selectedLevel--;
              newPoint();
              gameStarted = true;
            }
            else
            {
              lcd.setCursor(14, 0);
              lcd.print(selectedLevel);
            }
          }
        }
      }
    }
    if (!gameOver && gameStarted)
    {
      skip = false; //skip the second moveAll() function call if the first was made
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);  // convert into key press
      if (key != oldkey)   // if keypress is detected
      {
        delay(50);  // wait for debounce time
        adc_key_in = analogRead(0);    // read the value from the sensor
        key = get_key(adc_key_in);    // convert into key press
        if (key != oldkey)
        {
          oldkey = key;
          if (key >= 0)
          {
            olddir = head->dir;
            if (key == 0 && head->dir != 3) head->dir = 2;
            if (key == 1 && head->dir != 1) head->dir = 0;
            if (key == 2 && head->dir != 0) head->dir = 1;
            if (key == 3 && head->dir != 2) head->dir = 3;

            if (olddir != head->dir)
            {
              skip = true;
              delay(1000 / gameSpeed);
              moveAll();
              drawMatrix();
            }
          }
        }
      }
      if (!skip)
      {
        timeNow = millis();
        if (timeNow - time > 1000 / gameSpeed)
        {
          moveAll();
          drawMatrix();
          time = millis();
        }
      }
    }
    if (gameOver)
    {
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);  // convert into key press
      if (key != oldkey)   // if keypress is detected
      {
        delay(50);  // wait for debounce time
        adc_key_in = analogRead(0);    // read the value from the sensor
        key = get_key(adc_key_in);    // convert into key press
        if (key != oldkey)
        {
          oldkey = key;
          if (key >= 0)
          {
            startF();
          }
        }
      }
    }
    checkMode();
  }
  while (clockMode)
  {
    if (settingTime)
      setTime();
    else if (settingDate)
      setDate();
    else
      printTimeDate();

    readingInput();

    lcd.noBlink();
    checkMode();
  }
}

void checkMode()
{
  lcd.clear();
  reading = digitalRead(pinIn);
  Serial.println(reading);
  if (reading == 1)
  {
    snakeMode = true;
    clockMode = false;
    startF();
  }
  else if (reading == 0)
  {
    snakeMode = false;
    clockMode = true;
  }
}

bool isKeyPressed()
{
  if (clockMode)
  {
    adc_key_in = analogRead(0);    // read the value from the sensor
    key = get_key(adc_key_in);  // convert into key press
    if (key != oldkey)   // if keypress is detected
    {
      delay(50);  // wait for debounce time
      adc_key_in = analogRead(0);    // read the value from the sensor
      key = get_key(adc_key_in);    // convert into key press
      if (key != oldkey)
      {
        oldkey = key;
        return true;
      }
    }
    delay(100);
    return false;
  }
}

void printTimeDate()
{
  lcd.setCursor(4, 0);
  if (hour() < 10)
    lcd.print(0);
  lcd.print(hour());
  lcd.print(":");
  if (minute() < 10)
    lcd.print(0);
  lcd.print(minute());
  lcd.print(":");
  if (second() < 10)
    lcd.print(0);
  lcd.print(second());
  lcd.setCursor(1, 1);
  lcd.print(dayShortStr(weekday()));
  lcd.setCursor(5, 1);
  if (day() < 10)
    lcd.print(0);
  lcd.print(day());
  lcd.print("/");
  if (month() < 10)
    lcd.print(0);
  lcd.print(month());
  lcd.print("/");
  lcd.print(year());
}

void setTime()
{
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Set time:");
  lcd.setCursor(4, 1);
  if (h < 10)
    lcd.print(0);
  lcd.print(h);
  lcd.print(":");
  if (m < 10)
    lcd.print(0);
  lcd.print(m);
  lcd.print(":00");
  if (settingHour)
    lcd.setCursor(5, 1);
  else if (settingMinute)
    lcd.setCursor(8, 1);
  lcd.blink();
}

void setDate()
{
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Set date:");
  lcd.setCursor(3, 1);
  if (d < 10)
    lcd.print(0);
  lcd.print(d);
  lcd.print("/");
  if (n < 10)
    lcd.print(0);
  lcd.print(n);
  lcd.print("/");
  lcd.print(y);
  if (settingDay)
    lcd.setCursor(4, 1);
  else if (settingMonth)
    lcd.setCursor(7, 1);
  else if (settingYear)
    lcd.setCursor(12, 1);
  lcd.blink();
}

void readingInput()
{
  if (isKeyPressed())
  {
    if (key == 0) // right key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          settingMinute = true;
          settingHour = false;
        }
        else if (settingMinute)
        {
          settingHour = true;
          settingMinute = false;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          settingMonth = true;
          settingDay = false;
        }
        else if (settingMonth)
        {
          settingYear = true;
          settingMonth = false;
        }
        else if (settingYear)
        {
          settingDay = true;
          settingYear = false;
        }
      }
    }
    if (key == 1) // up key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          h++;
          if (h > 23)
            h = 0;
        }
        else if (settingMinute)
        {
          m++;
          if (m > 59)
            m = 0;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          d++;
          if (d > dMax)
            d = 1;
        }
        else if (settingMonth)
        {
          n++;
          if (n > 12)
            n = 1;
          updateMaxDay();
        }
        else if (settingYear)
        {
          y++;
          updateMaxDay();
        }
      }
    }
    if (key == 2) // down key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          h--;
          if (h < 0)
            h = 23;
        }
        else if (settingMinute)
        {
          m--;
          if (m < 0)
            m = 59;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          d--;
          if (d < 1)
            d = dMax;
        }
        else if (settingMonth)
        {
          n--;
          if (n < 1)
            n = 12;
          updateMaxDay();
        }
        else if (settingYear)
        {
          y--;
          updateMaxDay();
        }
      }
    }
    if (key == 3) // left key
    {
      if (settingTime)
      {
        if (settingHour)
        {
          settingMinute = true;
          settingHour = false;
        }
        else if (settingMinute)
        {
          settingHour = true;
          settingMinute = false;
        }
      }
      else if (settingDate)
      {
        if (settingDay)
        {
          settingYear = true;
          settingDay = false;
        }
        else if (settingYear)
        {
          settingMonth = true;
          settingYear = false;
        }
        else if (settingMonth)
        {
          settingDay = true;
          settingMonth = false;
        }
      }
    }
    if (key == 4) // select key
    {
      updateMaxDay();
      lcd.clear();
      if (!settingClock)
      {
        settingTime = true;
        settingClock = true;
        settingHour = true;
        h = hour();
        m = minute();
        s = 0;
        d = day();
        n = month();
        y = year();
      }
      else if (settingTime)
      {
        settingHour = false;
        settingDay = true;
        settingTime = false;
        settingDate = true;
      }
      else if (settingDate)
      {
        settingDay = false;
        settingDate = false;
        settingClock = false;
        setTime(h, m, s, d, n, y);
      }
    }
    key = -1;
  }
}

void updateMaxDay()
{
  if (n == 2)
  {
    if (y % 4 == 0)
      dMax = 29;
    else
      dMax = 28;
  }
  else if (n == 4 || n == 6 || n == 9 || n == 11)
    dMax = 30;
  else
    dMax = 31;
}

// Convert ADC value to key number
int get_key(unsigned int input)
{
  int k;
  for (k = 0; k < NUM_KEYS; k++)
  {
    if (input < adc_key_val[k])
    {
      return k;
    }
  }
  if (k >= NUM_KEYS)k = -1;  // No valid key pressed
  return k;
}

void drawMatrix()
{
  int cc = 0;
  if (!gameOver)
  {
    x[pr][pc] = true;
    for (int r = 0; r < 2; r++)
    {
      for (int c = 0; c < 16; c++)
      {
        special = false;
        for (int i = 0; i < 8; i++)
        {
          byte b = B00000;
          if (x[r * 8 + i][c * 5 + 0]) {
            b += B10000;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 1]) {
            b += B01000;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 2]) {
            b += B00100;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 3]) {
            b += B00010;
            special = true;
          }
          if (x[r * 8 + i][c * 5 + 4]) {
            b += B00001;
            special = true;
          }
          myChar[i] = b;
        }
        if (special)
        {
          lcd.createChar(cc, myChar);
          lcd.setCursor(c, r);
          lcd.write(byte(cc));
          cc++;
        }
        else
        {
          lcd.setCursor(c, r);
          lcd.write(254);
        }
      }
    }
  }
}

void freeList()
{
  part *p, *q;
  p = tail;
  while (p != NULL)
  {
    q = p;
    p = p->next;
    free(q);
  }
  head = tail = NULL;
}

void gameOverFunction()
{
  delay(1000);
  lcd.clear();
  freeList();
  lcd.setCursor(3, 0);
  lcd.print("Game Over!");
  lcd.setCursor(4, 1);
  lcd.print("Score: ");
  lcd.print(collected);
  delay(1000);
}

void growSnake()
{
  part *p;
  p = (part*)malloc(sizeof(part));
  p->row = tail->row;
  p->column = tail->column;
  p->dir = tail->dir;
  p->next = tail;
  tail = p;
}

void newPoint()
{
  part *p;
  p = tail;
  boolean newp = true;
  while (newp)
  {
    pr = random(16);
    pc = random(80);
    newp = false;
    while (p->next != NULL && !newp)
    {
      if (p->row == pr && p->column == pc) newp = true;
      p = p->next;
    }
  }
  if (collected < 13 && gameStarted) growSnake();
}

void moveHead()
{
  switch (head->dir) // 1 step in direction
  {
    case 0: head->row--; break;
    case 1: head->row++; break;
    case 2: head->column++; break;
    case 3: head->column--; break;
    default : break;
  }
  if (head->column >= 80) head->column = 0;
  if (head->column < 0) head->column = 79;
  if (head->row >= 16) head->row = 0;
  if (head->row < 0) head->row = 15;

  part *p;
  p = tail;
  while (p != head && !gameOver) // self collision
  {
    if (p->row == head->row && p->column == head->column) gameOver = true;
    p = p->next;
  }
  if (gameOver)
    gameOverFunction();
  else
  {
    x[head->row][head->column] = true;

    if (head->row == pr && head->column == pc) // point pickup check
    {
      collected++;
      if (gameSpeed < 25) gameSpeed += 3;
      newPoint();
    }
  }
}

void moveAll()
{
  part *p;
  p = tail;
  x[p->row][p->column] = false;
  while (p->next != NULL)
  {
    p->row = p->next->row;
    p->column = p->next->column;
    p->dir = p->next->dir;
    p = p->next;
  }
  moveHead();
}

void createSnake(int n) // n = size of snake
{
  for (i = 0; i < 16; i++)
    for (j = 0; j < 80; j++)
      x[i][j] = false;

  part *p, *q;
  tail = (part*)malloc(sizeof(part));
  tail->row = 7;
  tail->column = 39 + n / 2;
  tail->dir = 3;
  q = tail;
  x[tail->row][tail->column] = true;
  for (i = 0; i < n - 1; i++) // build snake from tail to head
  {
    p = (part*)malloc(sizeof(part));
    p->row = q->row;
    p->column = q->column - 1; //initial snake id placed horizoltally
    x[p->row][p->column] = true;
    p->dir = q->dir;
    q->next = p;
    q = p;
  }
  if (n > 1)
  {
    p->next = NULL;
    head  = p;
  }
  else
  {
    tail->next = NULL;
    head = tail;
  }
}

Conclusion

I think it’s safe to say that I have badly managed this project and haven’t fully completed everything that I needed to have done in order to pass this course. I can make no excuses, I simply didn’t put enough time into this project at the beginning and it has left me scrambling to get at least something I can present at the last minute. I don’t know if my initial idea was too ambitious, because I managed to complete one small part of my idea pretty well but that was mostly a coding exercise after I failed to build a separate display unit and instead relying on a pre-built shield for the Arduino. The clock does work well but unfortunately I didn’t get around to the alarm system for it.

I could fill this page with things I didn’t do, but I’ll just focus on what I really should have done. First, I didn’t even attempt to make a proper enclosure for the device mainly because I wasn’t sure how and I ran out of time anyway. And really when it comes down to it, I only made a small part of what I said I would make. If (or more likely when) I have to do this again I would make far better use of my time and plan to do a bit of work on the project every week. I don’t know if I’d ever feel comfortable using solder and PCBs but I could at least try, as I also could try at making some sort of enclosure,  be it 3D printed or otherwise hand made.

The final post for this project will have a full code listing.

Tilt

This update will cover the attempts I had to get the device working with a tilt sensor. Even before I got started I could tell that this could prove almost fruitless especially now that I’m using the LCD Shield which fits directly onto the Arduino and even though it leaves some pins for other uses, it uses a lot of power and makes harder to build stuff away from the microcontroller. I tried anyway, but taking power from the device to a breadboard just meant that there wasn’t any to power the display. I guess that is how the shield operates – if you use the outer pins then they override the use of what ever is below them.

So I tried to directly put a tilt switch onto the shield/Arduino though pins 2 & 3, as they aren’t in use by the LCD screen. I actually tried a couple of analog pins first but the power levels were too inconstant to get a clear reading. But with the digital pins I was at least to get a reading of either 1 or 0. With that I tried a small test program to switch displaying one of two messages onto the LCD display but the end results of the tests weren’t promising as there was a lot of flickering and neither message was clearly readable.

Still I had to try with the full bits of code that I wanted to switch between and after a lot of tweaking of how and when to check the pin input I manage to get it to display the clock portion of the program very well but it’s not able to fully display and run the snake part. That could be because the Snake program I am using requires a fair about of memory to run or it could be clashes between the keypad reading functions. Whatever the reason is, I won’t be able to solve it now nor will I be able to add more to the project as I am out of time. There will be one more post, concluding this project.

Game

For the game portion of the project I look online for examples that people have previously created and I found a really good Snake game for a 16×2 LED display that made use of single pixels in the display but I really struggled to understand most of the code so I set about creating a very simple version of Snake that would just use the 32 available character spaces. The first task was to get was to draw and animate the snake which proved very difficult. I could get a few characters to go along the screen in a straight line but I couldn’t get them to change directions correctly or even at all.

As I was very quickly running out of time for the project I decided to use the Snake game code that I found online so that I can at least show some of the functionality I am aiming for. Here’s a link for the video source of the game: https://www.youtube.com/watch?v=IkTK6WMlMs4 and a link to the source code source: http://pastebin.com/yiDBzjvZ.

Now in the little time I have remaining I will try and get the tilt working so that the two functions of the project can be shown.

Clock

This is how I got a working clock. First I need to see if there was an Arduino library that could help me and I found one at http://playground.arduino.cc/Code/Time, which can be used to store and return time data. As I didn’t have any sort of external device to return the exact current time, like a Real-Time Clock (RTC) Chip, I would need a way for the time and date to be changed manually. But first I needed it to display a current time which was done by using the library mentioned earlier as it had a function to set a time with passed variables, so I set a default time of 12:00:00 and a date of 01/01/2016. The library also has very useful functions that could return the hour, minute, second, day, month and year values of the set date so using them I made a function that would display whatever time was set:

void printTimeDate() 
{ 
  lcd.setCursor(4, 0); 
  if (hour() < 10)
    lcd.print(0);
  lcd.print(hour());
  lcd.print(":");
  if (minute() < 10)
    lcd.print(0);
  lcd.print(minute()); 
  lcd.print(":"); 
  if (second() < 10) 
    lcd.print(0); 
  lcd.print(second()); 
  lcd.setCursor(1, 1); 
  lcd.print(dayShortStr(weekday())); 
  lcd.setCursor(5, 1); 
  if (day() < 10) 
    lcd.print(0); 
  lcd.print(day()); 
  lcd.print("/"); 
  if (month() < 10) 
    lcd.print(0); 
  lcd.print(month()); 
  lcd.print("/"); 
  lcd.print(year()); 
}

That provided a date and time  readout that looked exactly how I wanted it:

clock1

The next stage was to make the set date and time changeable. This required using the keypad buttons on the LED shield, which are readable through a single analogue data pin. So by checking if a button has been pressed and then determining which button was pressed allowed me to use the select button the draw another screen on the display, this one for setting the time. Another press of the button would take you to the set date screen. For each screen a press of the left or right buttons would change what value is being modified, indicated by a blinking cursor. The up and down buttons change that value within appropriate constraints. Pressing the select button for a third time returns to the time/date screen but after updating the set time/date with the manually entered values.

Now I have a functioning and modifiable clock I started work on the game part of the project.

Log Update 3

Met with supervisor for the first time in a few weeks due to personal problems. Supervisor advised me to inform my personal tutor about my problems and I have done so. Was also informed that I missed the submission deadline for my preliminary report and so was given the task of completing my preliminary report using previously collected research data and submitting it with the hope that it can still be assessed.

I also told my supervisor that I intend to change the focus of the project from VR display to motion control, and that the bulk of research for the reports will be focused on that area. With time permitting I would still like to be able to incorporate support for the Oculus Rift. Working on the preliminary report will also give me a chance to firmly set down what my ideas for this project are and what I hope to achieve.

 

Display

Choosing the right display unit for my device was problematic right up until the end of the end of the  project. The first unit I chose was a 20×4 LED display module with a blue backlight but I had a lot of problems just trying to work out how to connect it especially as I was hesitant about the use of soldering irons and the like. I did try to connect wires to it where appropriate but ended up damaging the unit completely.

So I looked for another display that would be easier to use and found a 16×2 LCD display shield with a built in keypad. Now I wasn’t sure if using something like this was relying on too much pre-built technology but I feel that my strengths lie in programming and not the actual building on the device and I decided to focus on my strengths. If that costs me marks that is fine by me.

Testing of the new display went  very well and also allowed me a chance to test the use of the 9V battery adapter, as I will need the device to be powered by a battery and not my computer via a USB lead. The next will involve working out what can be displayed on the screen and determining if I can use this to display a simple game.

16x2-led-test1

Components

This post is for talking about the components I need for this project. The staple components are required (wires, resistors and such like) but I’ll need something to output an alarm sound. I’ve previously used a small speaker during one of the lab assignments but I was unhappy with the sound it outputted so this time I’ll be using a piezo element type speaker which will allow for a cleaner sound and allow for a more melodious alarm for the clock.

A big element of this project is that the clock has two function states, dictated by the orientation of the device. To achieve this I need to use a tilt switch, which basically is a tube that has a metal ball inside of it and when the ball is at the bottom of the tube the ‘switch’ is connected and a circuit can be formed and so when the device is tilted the ball is no longer completing the circuit. For this project I will need to have a circuit running to a pin on the Arduino that is checking for the amount of current arriving at it so that one loop of code is running when the current is at a certain level and when that changes, another loop of code gets to run instead, which switches between the two functions. More than one tilt switch may be needed in order to have more exacting control over the change in functions.

Here’s a video of my testing of a tilt switch:

Next time I’ll talk about what I intend to use for the device’s display.

Log Update 2

Missed my meeting with my supervisor due to illness and scheduling conflicts but used email to pass on my weekly update. This past week I concentrated on doing more research into VR and motion control technologies, getting information sources for use in my report. I also decided that I will be using the Leap Motion device instead of the Microsoft Kinect as my main control method for the game. I chose the Leap Motion because it offers exactly the sort of thing I am after in a small and convenient package without unnecessary extras.

My plan for this week is to obtain a Leap Motion device and practice using it within Unity. I will also be starting to look for suitable assets, with appropriate licensing, that I can use.

Log Update 1

This is the first of a series of weekly log posts that cover my meetings with my supervisor, detailing what was discussed and creating plans for the following week.

In this first week we discussed my proposal and clarified some points with it. Specifically I gave details on gameplay and setting aspects of my project. The gameplay will mostly be walking around the game world and interacting objects that may lead to physics or gesture based puzzles. I plan to use a science fiction based setting, specifically a space station style environment. We also discussed the technology I want to use. I had already decided that I would be using Unity as the basis for building the project and was thinking about using the Kinect for controlling the game. The Leap Motion device was mentioned as a possible hands free control option which I will look into.

Virtual Reality is a large focus of my project and part of my report will cover my research into the history of VR and how it applies to games over the years. The primary VR technology I will probably be using is the Oculus Rift though I will be researching other VR technologies and looking into people involved with the history of VR.

I want to be able to demonstrate my programming skills with this project and as such the AI I plan to design and implement for the game will be very important. I haven’t worked out the specifics everything I want the AI to do but ideas I have currently include agents that follow the player as they navigate the world but keeping while keeping a certain distance away and at least one room which is guarded by agents that based their patrol patterns on the last place you were detected.

For this next week I plan to do more reading on VR (the technologies that can be used and how and the people involved), motion control (specifically the Kinect and Leap Motion, though if more come up in my research they will also be looked into), inspirational games and also look into the appropriate assets I could use. By the end of the week I want to have a firm idea what hardware I will be using so I can start practising using them.