KcF
Published © CC BY-NC-SA

Another old-timer Snake game

Children found my oldest directional keyboard, like it, so we agreed that I will create something playable with it for them...

BeginnerWork in progress844
Another old-timer Snake game

Things used in this project

Hardware components

SparkFun Arduino Pro Micro
Miniature version of Arduino Leonardo
×1
1,3" OLED Display SH1106 128x64 I2C
×1
Keyboard
Up, Down, Left, Right, Ok, Cancel
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Schema

Code

Snake.ino

Arduino
//#define ShowXY

#include <EEPROM.h>

#define TASKER_MAX_TASKS 5
#include "Tasker.h"
Tasker Tasking;

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
#define OLED_RESET -1
Adafruit_SH1106 display(OLED_RESET);

#include <EasyButton.h>
EasyButton btnEnter(4);
EasyButton btnCancel(7);
EasyButton btnUp(8);
EasyButton btnDown(9);
EasyButton btnLeft(16);
EasyButton btnRight(5); 

02_MsgBox.ino

Arduino
//#include "progmem.h"

#define   BLACK           0
#define   WHITE           1

#define   MSGBOX_STRING_LEN_MAX   27
#define   MSGBOX_STRING_ROW_SIZE  9
#define   MSGBOX_STRING_ROWS      3


//  MsgBox(F("TbCdEfGhGjKlMnOpRsAuVxYz012"));
void MsgBox(const __FlashStringHelper* conditionalPrint){
//  display.clearDisplay();
  display.fillRoundRect( 20, 18, 65, 29, 3, BLACK);
  display.drawRoundRect( 20, 18, 65, 29, 3, WHITE);

  const char PROGMEM *p = (const char PROGMEM *)conditionalPrint;
  for(byte i = 0; i < MSGBOX_STRING_LEN_MAX; i++){
    display.setCursor( 22 + (i % MSGBOX_STRING_ROW_SIZE) * 7, 20 + (i / MSGBOX_STRING_ROW_SIZE) * 9);
    char j = pgm_read_byte(p++);
    if(j == 0){break;}
    display.print(j);
  }
  
  display.display();
}
void MsgBox_Clear(){
  //DshBrdWithoutMsgbox = true;
  //Screens[ScreenActual].Redraw();
}

void MsgBox(const __FlashStringHelper* conditionalPrint, byte TimeToShow){
  MsgBox(conditionalPrint);
  
  Tasking.setTimeout(MsgBox_Clear, TimeToShow * 1000);
}

void MsgBox_RedrawNum(byte Num){
    display.setCursor( 46, 29);
    display.setTextSize(2);
    display.print(Num);
    display.setTextSize(1);
    display.display();
}

03_Engine.ino

Arduino
#define PLAYGROUND_WIDTH    100
#define PLAYGROUND_HEIGH     64

#define SNAKE_LEN_MAX       100
#define GEMS_MAX            10

extern byte SnakeHeadPosition_X, SnakeHeadPosition_Y, SnakeLen;
extern byte GameLevel;
extern unsigned long GameScore;

extern byte    SnakeBodyPositions_X[];
extern byte    SnakeBodyPositions_Y[];
extern byte    SnakeBodyPositions_Pointer;

extern byte    GemsCount;
extern byte    GemPositions_X[];
extern byte    GemPositions_Y[];

void PaintCanvas(){
  display.clearDisplay();
  display.drawRect( 1, 1, PLAYGROUND_WIDTH - 1, PLAYGROUND_HEIGH - 1, WHITE);

  display.setCursor( 102,  1);
  display.print("Body");

  display.setCursor( 103,  38);
  display.print("C:");

  display.setCursor( 103,  47);
  display.print("U");

  display.setCursor( 103,  56);
  display.print("D");

  //display.display();
}

void PaintGameLevel(){
  display.setCursor( 109, 47);
  
  if(GameLevel < 100){display.print(":");}
  
  display.print(GameLevel);
}

void PaintGameScore(){
  if(GameScore >= 1000){
    unsigned short ToDisplay;
    
    ToDisplay = GameScore / 1000;
    display.setCursor( 106, 10);
    if(ToDisplay < 100){display.print(" ");}
    if(ToDisplay <  10){display.print(" ");}
    display.print(ToDisplay);
  
    ToDisplay = GameScore % 1000;
    display.setCursor( 106, 18);
    if(ToDisplay < 100){display.print("0");}
    if(ToDisplay <  10){display.print("0");}
    display.print(ToDisplay);
  }else{
    display.setCursor( 106, 11);
    if(GameScore < 100){display.print(" ");}
    if(GameScore <  10){display.print(" ");}
    display.print(GameScore);
  }
}

void PaintSnakeLen(){
  display.setCursor( 109, 56);
  
  if(SnakeLen < 100){display.print(":");}
  
  display.print(SnakeLen);
}

void PaintRemainTime(){
  display.setCursor( 114, 38);
  
  unsigned long RemainTime = Tasking.scheduledIn(LevelEnd) / 1000;
  display.print(RemainTime);
  if(RemainTime <  10){display.print(" ");}
}

void PaintXY(){
  display.setCursor( 3, 3);
  display.print("X=");
  display.print(SnakeHeadPosition_X);
  if(SnakeHeadPosition_X < 10){display.print(" ");}
  
  display.setCursor( 3, 11);
  display.print("Y=");
  display.print(SnakeHeadPosition_Y);
  if(SnakeHeadPosition_Y < 10){display.print(" ");}

  for(byte i = 0; i < GemsCount; i++){
    display.setCursor( PLAYGROUND_WIDTH - 25, 3 + i * 16);
    display.print("X=");
    display.print(GemPositions_X[i]);
    if(GemPositions_X[i] < 10){display.print(" ");}
    
    display.setCursor( PLAYGROUND_WIDTH - 25, 11 + i * 16);
    display.print("Y=");
    display.print(GemPositions_Y[i]);
    if(GemPositions_Y[i] < 10){display.print(" ");}
  }
}

void PaintHead(){
  display.fillRect(SnakeBodyPositions_X[SnakeBodyPositions_Pointer] - 1, SnakeBodyPositions_Y[SnakeBodyPositions_Pointer] - 1, 3, 3, BLACK);
  
  display.fillRect(SnakeHeadPosition_X - 1, SnakeHeadPosition_Y - 1, 3, 3, WHITE);
}

void PaintBody(){
  if(SnakeLen > 0){
    display.fillCircle(SnakeBodyPositions_X[SnakeBodyPositions_Pointer], SnakeBodyPositions_Y[SnakeBodyPositions_Pointer], 1, WHITE);

    byte LastBodyPointer;
    if(SnakeLen > SnakeBodyPositions_Pointer){
      LastBodyPointer = SNAKE_LEN_MAX + SnakeBodyPositions_Pointer - SnakeLen;
    }else{
      LastBodyPointer = SnakeBodyPositions_Pointer - SnakeLen;
    }
    display.fillCircle(SnakeBodyPositions_X[LastBodyPointer], SnakeBodyPositions_Y[LastBodyPointer], 1, BLACK);
  }
}

void PaintGems(){
  if(GemsCount == 1){
    display.drawCircle(GemPositions_X[0], GemPositions_Y[0], 1, WHITE);
  }else{
    bool IsDoubled;
    for(byte i = 0; i < GemsCount; i++){
      IsDoubled = false;
      for(byte j = i + 1; j < GemsCount; j++){
        if((GemPositions_X[i] == GemPositions_X[j]) &&(GemPositions_Y[i] == GemPositions_Y[j])){
          IsDoubled = true;
        }
      }

      if(IsDoubled){
        display.drawRect(GemPositions_X[i] - 1, GemPositions_Y[i] - 1, 3, 3, WHITE);
      }else{
        display.drawCircle(GemPositions_X[i], GemPositions_Y[i], 1, WHITE);
      }
    }
  }
}

04_Game.ino

Arduino
enum    GameStates{StartUp, Running, LevelFinished, GameOverS};
enum    GameModes{Levels, Continual};
GameStates  GameState         = StartUp;
GameModes   GameMode          = Levels;

byte    GameLevel;
unsigned long GameScore;

byte    SnakeLen;
byte    SnakeBodyPositions_X[SNAKE_LEN_MAX];
byte    SnakeBodyPositions_Y[SNAKE_LEN_MAX];
byte    SnakeBodyPositions_Pointer;

byte    GemsCount;
byte    GemPositions_X[GEMS_MAX];
byte    GemPositions_Y[GEMS_MAX];

byte    SnakeHeadPosition_X, SnakeHeadPosition_Y;

enum    Direction{None, Left, Right, Up, Down};
Direction   SnakeDirection;

void GameNew(){
  SnakeHeadPosition_X = PLAYGROUND_WIDTH / 2;
  SnakeHeadPosition_Y = PLAYGROUND_HEIGH  / 2;
  SnakeBodyPositions_X[0] = SnakeHeadPosition_X;
  SnakeBodyPositions_Y[0] = SnakeHeadPosition_Y;

  SnakeLen            = 0;
  SnakeBodyPositions_Pointer  = 0;
  
  SnakeDirection      = None;
  GameState           = Running;
  
  switch(GameMode){
    case Levels:    GameNew_ModeL(); break;
    case Continual: GameNew_ModeC(); break;
  }
}
void GameNew_ModeL(){
  GameLevel++;
  GemsCount             = GameLevel / 10 + 1;
  if(GemsCount > GEMS_MAX){GemsCount == GEMS_MAX;}
  for(byte i = 0; i < GemsCount; i++){
    GemsRandomize2(i);
  }

  
  Tasking.setInterval(DoMove, 500 - (GameLevel - 1) * 5);
  Tasking.setTimeout(LevelEnd, 60000);
}
void GameNew_ModeC(){
  GemsCount = 1;
  GemsRandomize2(0);

  
  Tasking.setInterval(DoMove, 400);
  Tasking.setInterval(LevelEnd, 30000);
}

void GameOver(){
  Tasking.cancel(DoMove);
  Tasking.cancel(LevelEnd);
  GameState    = GameOverS;

  MsgBox(F("Game over"));
  display.setCursor( 22, 21 + 8);
  display.setTextSize(2);
  if(GameScore < 1000){display.print(" ");}
  if(GameScore <  100){display.print(" ");}
  if(GameScore <   10){display.print(" ");}
  display.print(GameScore);

  display.setCursor( 4, 47);
  display.print("Top:");
  switch(GameMode){
    case Levels:
      display.print(LoadTopScore_ModeL());
      SaveTopScore_ModeL();
    break;
    case Continual:
      display.print(LoadTopScore_ModeC());
      SaveTopScore_ModeC();
  }
  
  display.setTextSize(1);
  display.display();
}

void LevelEnd(){
  if(GameMode == Levels){
    Tasking.cancel(DoMove);
    Tasking.cancel(LevelEnd);
    GameState    = LevelFinished;
  
    GameScore   += GameLevel * 10;
    PaintGameScore();
    
    MsgBox(F(" Uroven  zvladnutaStlac OK"));
  }else if(GameMode == Continual){
    GameLevel++;
    PaintGameLevel();
    
    Tasking.setInterval(DoMove, 400);
  }
}

void StartNewLevel(){
  PaintCanvas();
  
  GameNew();

  #ifdef ShowXY
    PaintXY();
  #endif
  PaintGameScore();
  PaintRemainTime();
  PaintGameLevel();
  PaintSnakeLen();
  PaintGems();
  
  PaintHead();
  display.display();
}

void DoMove(){
  if(GameState != Running){
    Tasking.cancel(DoMove);
    Tasking.cancel(LevelEnd);

    MsgBox(F("Chyba 1, restartujte zariadenie"));
    
    return;
  }
  
  PaintRemainTime();
  
  if(SnakeDirection == None){return;}
  
  SnakeBodyPositions_Pointer++;
  if(SnakeBodyPositions_Pointer == SNAKE_LEN_MAX){SnakeBodyPositions_Pointer = 0;}
  
  SnakeBodyPositions_X[SnakeBodyPositions_Pointer] = SnakeHeadPosition_X;
  SnakeBodyPositions_Y[SnakeBodyPositions_Pointer] = SnakeHeadPosition_Y;
  
  switch(SnakeDirection){
    case Up:    SnakeHeadPosition_Y -= 3;  break;
    case Down:  SnakeHeadPosition_Y += 3;  break;
    case Left:  SnakeHeadPosition_X -= 3;  break;
    case Right: SnakeHeadPosition_X += 3;
  }

  for(byte i = 0; i < GemsCount; i++){
    if(SnakeHeadPosition_X == GemPositions_X[i] && SnakeHeadPosition_Y == GemPositions_Y[i]){
      if(SnakeLen < SNAKE_LEN_MAX){SnakeLen++;}
      GameScore += 1 + (SnakeLen >> 1);
      PaintGameScore();
      PaintSnakeLen();

      GemsRandomize2(i);
      PaintGems();
    }
  }

  #ifdef ShowXY
    PaintXY();
  #endif
  PaintHead();
  PaintBody();
  display.display();

  if(SnakeHeadPosition_X < 3 || SnakeHeadPosition_X > (PLAYGROUND_WIDTH - 3)){
    GameOver();
    return;
  }
  if(SnakeHeadPosition_Y < 3 || SnakeHeadPosition_Y > (PLAYGROUND_HEIGH - 3)){
    GameOver();
    return;
  }

  for(byte i = 0; i < SnakeLen; i++){
    if(SnakeHeadPosition_X == SnakeBodyPositions_X[SnakeBodyPositions_Pointer - i] && SnakeHeadPosition_Y == SnakeBodyPositions_Y[SnakeBodyPositions_Pointer - i]){
      GameOver();
      return;
    }
  }
}

void GemsRandomize2(byte Index){
  bool IsOk;
  randomSeed(analogRead(0));
    
  do{
    IsOk = true;
    GemPositions_X[Index] = random(1, 32) * 3 + 2;
    GemPositions_Y[Index] = random(1, 20) * 3 + 2;

    char Pointer = SnakeBodyPositions_Pointer;
    for(byte i = 0; i <= SnakeLen; i++){
      if(Pointer + i >= SNAKE_LEN_MAX){Pointer -= SNAKE_LEN_MAX;}
      if(i == 0){
        if((GemPositions_X[Index] == SnakeHeadPosition_X) && (GemPositions_Y[Index] == SnakeHeadPosition_Y)){
          IsOk = false;
          break;
        }
      }else{
        if((GemPositions_X[Index] == SnakeBodyPositions_X[SnakeBodyPositions_Pointer - (i - 1)]) && (GemPositions_Y[Index] == SnakeBodyPositions_Y[SnakeBodyPositions_Pointer - (i - 1)])){
          IsOk = false;
          break;
        }
      }
    }
  }while(!IsOk);
}
/*
void GemsRandomize(byte Index){
  byte MaxY;
  randomSeed(analogRead(0));
  do{
    GemPositions_X[Index] = random(1, 32) * 3 + 2;
  
    MaxY = 19;
    for(byte i = 0; i < SnakeLen; i++){
      if(i == 0){
        if(GemPositions_X[Index] == SnakeHeadPosition_X){MaxY--;}
      }else{
        if(GemPositions_X[Index] == SnakeBodyPositions_X[SnakeBodyPositions_Pointer - (i - 1)]){MaxY--;}
      }
    }
  }while(MaxY == 0);
  
  randomSeed(analogRead(1));
  //GemPositions_Y[Index] = random(1, 20) * 3 + 2;
  GemPositions_Y[Index] = random(1, (MaxY + 1)) * 3 + 2;
  for(byte i = 0; i < SnakeLen; i++){
    if(i == 0){
      if(GemPositions_Y[Index] == SnakeHeadPosition_Y){GemPositions_Y[Index] += 3;}
    }else{
      if(GemPositions_Y[Index] == SnakeBodyPositions_Y[SnakeBodyPositions_Pointer - (i - 1)]){GemPositions_Y[Index] += 3;}
    }
  }
}
*/

void StartSnake(){
  PaintCanvas();
  
  GameLevel           = 0;
  GameScore           = 0;
  
  GameNew();
  
  #ifdef ShowXY
    PaintXY();
  #endif

  PaintGameScore();
  PaintRemainTime();
  PaintGameLevel();
  PaintSnakeLen();
  PaintGems();
  
  PaintHead();
  display.display();
}

05_Storage.ino

Arduino
#define EEPROM_INITED_VALUE       36

#define EEPROM_ADR_TOP_SCORE      10
#define EEPROM_ADR_TOP_SCORE_C    4

byte          Stored_GameLevel  = 0;
unsigned long Stored_GameScore  = 0;
#define EEPROM_STORAGE_SIZE       9
#define EEPROM_ADR_STORAGE        20
#define EEPROM_STORAGE_CELL_SIZE  5

void StoreActualGameToEEPROM(byte Position){
  EEPROM.write(     EEPROM_ADR_STORAGE + Position * EEPROM_STORAGE_CELL_SIZE,     GameLevel);
  EEPROM_writelong( EEPROM_ADR_STORAGE + Position * EEPROM_STORAGE_CELL_SIZE + 1, GameScore);
}
void LoadGameFromEEPROM(byte Position){
  GameLevel = EEPROM.read(    EEPROM_ADR_STORAGE + Position * EEPROM_STORAGE_CELL_SIZE);
  GameScore = EEPROM_readlong(EEPROM_ADR_STORAGE + Position * EEPROM_STORAGE_CELL_SIZE + 1);
}

void SaveActualGame(){
  Stored_GameLevel  = GameLevel;
  Stored_GameScore  = GameScore;
}
void StartStoredGame(){
  GameLevel         = Stored_GameLevel;
  GameScore         = Stored_GameScore;
  
  StartNewLevel();
}

void SaveTopScore_ModeL(){
  unsigned long ActualStored = EEPROM_readlong(EEPROM_ADR_TOP_SCORE);

  if(GameScore > ActualStored){
    EEPROM_writelong(EEPROM_ADR_TOP_SCORE, GameScore);
  }
}
unsigned long LoadTopScore_ModeL(){
  return EEPROM_readlong(EEPROM_ADR_TOP_SCORE);
}

void SaveTopScore_ModeC(){
  unsigned long ActualStored = EEPROM_readlong(EEPROM_ADR_TOP_SCORE_C);

  if(GameScore > ActualStored){
    EEPROM_writelong(EEPROM_ADR_TOP_SCORE_C, GameScore);
  }
}
unsigned long LoadTopScore_ModeC(){
  return EEPROM_readlong(EEPROM_ADR_TOP_SCORE_C);
}


void EEPROM_writelong(byte Address, unsigned long Value){
  EEPROM.write(Address + 2,highByte(word(Value)));
  EEPROM.write(Address + 3,lowByte( word(Value)));
  Value = Value >> 16;
  EEPROM.write(Address + 0,highByte(word(Value)));
  EEPROM.write(Address + 1,lowByte( word(Value)));
}

unsigned long EEPROM_readlong(byte Address){
  unsigned long Ret = word(EEPROM.read(Address), EEPROM.read(Address + 1));
  Ret = Ret << 16;
  Ret = Ret | word(EEPROM.read(Address + 2), EEPROM.read(Address + 3));
  return Ret;
}

07_IO.ino

Arduino
bool  SavingFlag = false;
byte  SaveNum  = 1;

void LeftPressed()  {SnakeDirection = Left;}
void RightPressed() {SnakeDirection = Right;}

void UpPressed(){
  if(!SavingFlag){
    SnakeDirection = Up;
  }else{
    if(SaveNum < EEPROM_STORAGE_SIZE){SaveNum++;}
    MsgBox_RedrawNum(SaveNum);
  }
}

void DownPressed(){
  if(!SavingFlag){
    SnakeDirection = Down;
  }else{
    if(SaveNum > 1){SaveNum--;}
    MsgBox_RedrawNum(SaveNum);
  }
}

void EnterPressed() {
  if(SavingFlag){return;}
  
  switch(GameState){
    case StartUp:
    case GameOverS:     StartSnake();     break;
    
    case LevelFinished: StartNewLevel();//  break;
  }
}

void CancelPressed(){
  if(SavingFlag){return;}
  if(GameMode != Levels){return;}
  
  switch(GameState){
    case LevelFinished: SaveActualGame();  break;
    case GameOverS:     StartStoredGame();//  break;
  }
}


void EnterHeld(){
  if(SavingFlag){  
    switch(GameState){
      case LevelFinished:
        StoreActualGameToEEPROM(SaveNum);
        SavingFlag = false;
        MsgBox(F("Ulozene  Stlac OK"));
      break;
      case StartUp:
      case GameOverS:
        LoadGameFromEEPROM(SaveNum);
        SavingFlag = false;
        GameState = LevelFinished;
        MsgBox(F("Nahrane  Stlac OK"));
    }
  }else if(GameState == StartUp || GameState == GameOverS){
    if(GameMode == Levels){
      GameMode = Continual;
      MsgBox(F("Mod  bez urovni"));
    }else{
      GameMode = Levels;
      MsgBox(F("Mod   s  urovnami"));
    }
  }
}

void CancelHeld(){
  if(GameMode != Levels){return;}
  if(GameState == LevelFinished){
    if(!SavingFlag){
      MsgBox(F("Uloz ako:"));
      MsgBox_RedrawNum(SaveNum);
  
      SavingFlag = true;
    }else{
      SavingFlag = false;
      MsgBox(F(" Uroven  zvladnutaStlac OK"));
    }
  }else if((GameState == GameOverS) || (GameState == StartUp)){
    if(!SavingFlag){
      MsgBox(F("Nahrat :"));
      MsgBox_RedrawNum(SaveNum);
  
      SavingFlag = true;
    }else{
      SavingFlag = false;
      MsgBox(F("Game over"));
    }
  }
}

x_Initializations.ino

Arduino
void InitDisplay(){
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  //display.setRotation(1);
  display.setTextWrap(false);
  display.setTextColor(WHITE, BLACK);
  display.setTextSize(1);
  display.setFont();

  MsgBox(F("Hra Snake  Ready  Stlac OK"));
    
  display.display();
}

void InitButtons(){
  btnEnter.begin();
  btnEnter.onPressed(EnterPressed);
  btnEnter.onPressedFor(1000, EnterHeld);
  
  btnCancel.begin();
  btnCancel.onPressed(CancelPressed);
  btnCancel.onPressedFor(1000, CancelHeld);
  
  btnUp.begin();
  btnUp.onPressed(UpPressed);
  
  btnDown.begin();
  btnDown.onPressed(DownPressed);
  
  btnLeft.begin();
  btnLeft.onPressed(LeftPressed);
  
  btnRight.begin();
  btnRight.onPressed(RightPressed);
}

void InitEEPROM(){
  if(EEPROM.read(1) != EEPROM_INITED_VALUE){
    EEPROM_writelong(EEPROM_ADR_TOP_SCORE, 1);
    EEPROM_writelong(EEPROM_ADR_TOP_SCORE_C, 1);
    EEPROM.write(1, EEPROM_INITED_VALUE);
  }
}

y_Setup.ino

Arduino
void setup() {
  InitDisplay();
  InitEEPROM();
  InitButtons();
}

z_Loop.ino

Arduino
void loop(){
  Tasking.loop();

  btnEnter.read();
  btnCancel.read();
  btnUp.read();
  btnDown.read();
  btnLeft.read();
  btnRight.read();
}

Credits

KcF
1 project • 0 followers

Comments