Problem with symbol movement on 4x20 lcd

Hello everyone

Im trying to create a snake like game using arduino mega 2560 and a 4x20 I2C lcd for a school project, but now I have some major problems with moving between rows (up and down). Im using a keypad for control as that is the only controller I can get my hands on. I also need to be able to stop the symbols movement at the edges of the lcd (like a wall).

So basically how can I "roam freely" around the lcd display with my keypad using the up and down, right and left controls?

Keep track of the cursor position in your code and ignore keypresses that would take it off the edge of the screen.

Thank you for the info :slight_smile:

I have little to none programming experience so something like tracking the cursor movement seems way out of my reach. Is there anyway you could give me some coding hints on the tracking part?

Just thought I’d knock up a quick and dirty implementation of this. It works on my little 16 x 2 display but still needs tidying up a bit. Besides the logic of when to add a segment needs sorting out.

You’ll also need to define your own getKey() function (I’ve deleted the one I was using for testing purposes as it was VERY hardware specific).

#define KEY_RIGHT 1
#define KEY_UP 2
#define KEY_DOWN 3
#define KEY_LEFT 4
#define KEY_SET 5

#include <LiquidCrystal.h>
#define maxSnakeLength 20

//CHANGE THIS to suit the constraints of your display
#define maxColumn 15
#define maxRow  1
#define millisPerUpdate 1000

typedef struct snakeSegment;


//CHANGE THIS to suit your display
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

typedef struct snakeSegment
  {
   byte column;
   byte row; 
   char shape;
  };
  
snakeSegment segment[maxSnakeLength];

//direction can be 'n' (north) 'e' (east) 's' (south) or 'w' (west)
char direction='e';
  
void setup()
{
//CHANGE THIS to suit your display
lcd.begin(16,2);
  
//clear all segements
for (int n=0; n< maxSnakeLength; n++)
  segment[n].shape=0;

//Place the head and give it a shape
segment[0]=(snakeSegment){0,0,0x2e};
}


bool growing=false; //This gets set to true when adding a segment

unsigned long lastReport=0;

void loop()
{
unsigned long t=millis();

//CHANGE THIS to use your own keypad function
switch( getKey() )
  {
   case KEY_SET:
              growing=true;
              break;
   case KEY_RIGHT:
               direction='e';
               break;
   case KEY_LEFT:
               direction='w';
               break;
   case KEY_UP:
               direction='n';
               break;
   case KEY_DOWN:
               direction='s';
               break;
  }

if ( (t-lastReport) > millisPerUpdate)
  {//moveSnake updates position of snake and returns false if there's a crash
   if( !moveSnake(growing) )
     lcd.print("Squelch");
   else
     {
      growing=false;  
      drawSnake();  
     }
  lastReport = t;
  }  
}

void drawSnake()
{
//change head shape dependant on current direction
switch(direction)
  {case 'n':
           segment[0].shape='^';
           break;
   case 'e':
           segment[0].shape='>'; 
           break;
   case 's':
           segment[0].shape='v';
           break;
   case 'w':
           segment[0].shape='<';
           break;
  }
lcd.clear();

//copy every active segement to display
for (int n=0; segment[n].shape >0; n++)
  {
    lcd.setCursor(segment[n].column, segment[n].row);
    lcd.write(segment[n].shape);
  }
}

bool moveSnake(bool growing)
{
//moving the snake simply means putting every segment
//in the place currently occupied by the one in front.
//The head will move to a location determined by direction
int n;

//first find tail of snake
for (n=1; n < maxSnakeLength; n++)
  if (segment[n].shape == 0)
    break;

//n is now actually pointing to one segment past the end of the snake
//while we're here we can make it an active segment if snake is "growing"
if( growing )
  segment[n].shape='X';

//update the position of every segment to the position of the one in front of it
while(n >0)
  {
   segment[n].row = segment[n-1].row;
   segment[n].column = segment[n-1].column;
  n--;
  }

//now move the head
switch(direction)
  {case 'n':
           if( segment[0].row > 0 )
             segment[0].row--;
           else
            return false;
           break;
   case 'e':
           if( segment[0].column < maxColumn)
              segment[0].column++;
           else
              return false;
           break;
   case 's':
           if (segment[0].row < maxRow)
             segment[0].row++;
           else
             return false;
           break;
   case 'w':
           if (segment[0].column > 0)
              segment[0].column--;
           else
             return false;
           break;
  }


//return false if any segment occupies the same position as the head
for (int n=1; segment[n].shape !=0; n++)
  if( (segment[n].row == segment[0].row) && (segment[n].column == segment[0].column) )
    return false;
return true;
}


byte getKey()
{
//You'll have to write this bit yourself dependent on your own hardware  
}

Thank you KenF! :smiley:

Hell would have frozen over first before I could have figured something like that out.

I got to try this one next week when we have our next lesson. I will post new questions here if I have any major problems with the code that I cant figure out.

Hello again!

I need some help in finishing my snake like game now titled the “Snako”. I was wondering how to create a loading screen for my project and is it possible to make randomly generated symbols appear at my screen? I know I can use random function to create numbers and send those numbers to my setcursor or something alike, but how is it done I have no clue.

Collision detection will be out of the question as we dont have enough time if its too hard to make.

#include <Wire.h>
#include <Keypad.h>
#include <LiquidCrystal_I2C.h>

#define maxSnakeLength 20
#define millisPerUpdate 500
boolean alive = true;
boolean dead = false;
const byte rows = 4;
const byte cols = 4;
#define maxColumn 19
#define maxRow  3

typedef struct snakeSegment;

char keys[rows][cols] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte rowPins[rows] = {11,10,9,8};
byte colPins[cols] = {7,6,5,4};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, rows, cols);

LiquidCrystal_I2C lcd(0x27,20, 4);  // set the LCD address

typedef struct snakeSegment
  {
   byte column;
   byte row; 
   char shape;
  };
  
snakeSegment segment[maxSnakeLength];

//direction can be 'n' (north) 'e' (east) 's' (south) or 'w' (west)
char direction='e';
  
void setup()
{
lcd.init();                      // initialize the lcd 
  lcd.backlight();
  lcd.clear();
lcd.begin(20,4);
  
//clear all segements
for (int n=0; n< maxSnakeLength; n++)
  segment[n].shape=0;

//Place the head and give it a shape
segment[0]=(snakeSegment){0,0,0x2e};
}


bool growing=false; //This gets set to true when adding a segment

unsigned long lastReport=0;

void loop()
{
unsigned long t=millis();

char key = keypad.getKey();
if(key)  // Check for a valid key.
switch (key)
  {
   case '0':
              growing=true;
              break;
   case '6':
               direction='e';
               break;
   case '4':
               direction='w';
               break;
   case '2':
               direction='n';
               break;
   case '8':
               direction='s';
               break;
               
              
  }

if ( ((t-lastReport) > millisPerUpdate) && alive)
  {//moveSnake updates position of snake and returns false if there's a crash
   if( !moveSnake(growing) ) {
     lcd.clear();
     lcd.setCursor(4,1);
     lcd.print("You are dead");
     alive = false;
   }
   else
     {
      growing=false;  
      drawSnake();  
     }
  lastReport = t;
  }  
}

void drawSnake()
{
//change head shape dependant on current direction
switch(direction)
  {case 'n':
           segment[0].shape='^';
           break;
   case 'e':
           segment[0].shape='>'; 
           break;
   case 's':
           segment[0].shape='v';
           break;
   case 'w':
           segment[0].shape='<';
           break;
  }
lcd.clear();

//copy every active segment to display
for (int n=0; segment[n].shape >0; n++)
  {
    lcd.setCursor(segment[n].column, segment[n].row);
    lcd.write(segment[n].shape);
  }
}

bool moveSnake(bool growing)
{
//moving the snake simply means putting every segment
//in the place currently occupied by the one in front.
//The head will move to a location determined by direction
int n;

//first find tail of snake
for (n=1; n < maxSnakeLength; n++)
  if (segment[n].shape == 0)
    break;

//n is now actually pointing to one segment past the end of the snake
//while we're here we can make it an active segment if snake is "growing"
if( growing )
  segment[n].shape='o';

//update the position of every segment to the position of the one in front of it
while(n >0)
  {
   segment[n].row = segment[n-1].row;
   segment[n].column = segment[n-1].column;
  n--;
  }

//now move the head
switch(direction)
  {case 'n':
           if( segment[0].row > 0 )
             segment[0].row--;
           else
            return false;
           break;
   case 'e':
           if( segment[0].column < maxColumn)
              segment[0].column++;
           else
              return false;
           break;
   case 's':
           if (segment[0].row < maxRow)
             segment[0].row++;
           else
             return false;
           break;
   case 'w':
           if (segment[0].column > 0)
              segment[0].column--;
           else
             return false;
           break;
  }


//return false if any segment occupies the same position as the head
for (int n=1; segment[n].shape !=0; n++)
  if( (segment[n].row == segment[0].row) && (segment[n].column == segment[0].column) )
    return false;
return true;
}

Loading screens are not the Arduino way. Does your car have a loading screen before it starts the engine? Microcontrollers are usually configured to start 'instantly'.

Make this a feature in the advertising material for your game. (You do have a team member making a poster and a box design?) Instant-on! Ready to play a game in 4.3 microseconds!