Publicidade:

sexta-feira, 10 de junho de 2016

Arduino - Wall Defender Game com Display LCD (e com matriz de led) e Rotary Encoder

Mais um jogo no Arduino em display LCD. Agora foi uma versão do Jogo Wall Defender do Atari 2600. Lembra dele?



Claro que é uma versão bem mais simples. Mas a ideia é ser simples mesmo.


Veja outros jogos que já implementei:

Snake Game: http://fabianoallex.blogspot.com.br/2016/05/arduino-snake-game-com-display-lcd-e.html
Race Game: http://fabianoallex.blogspot.com.br/2016/06/arduino-race-game-com-display-lcd.html

Vídeo



Código-Fonte:

/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
     
#include <LiquidCrystal.h>
  
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
  private:
    unsigned int _rows;
    unsigned int _columns;
    unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado 
    byte**       _bits;
  public:
    BitArray2D(unsigned int rows, unsigned int columns){
      _rows       = rows;
      _columns    = columns;
      _cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
      _bits = (byte **)malloc(_rows * sizeof(byte *));
      for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc(  _cols_array  *  sizeof(byte)); } //cria varios arrays
      clear();
    }
    unsigned int rows(){ return _rows; }
    unsigned int columns(){ return _columns; }
    void clear() { 
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }       
      }   
    }
    void write(unsigned int row, unsigned int column, int value){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;    
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
      _bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
    }
    void write(byte value){
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) {      
          _bits[i][j] = value ? B11111111 : B00000000;     
        }       
      }  
    }
    int read(unsigned int row, unsigned int column){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;    
      return (b & (1 << bit)) != 0;
    }
    void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
    void toggle(){ for(int i=0;i<_rows;i++){      for(int j=0; j<_columns;j++) {      toggle(i,j);   }   }   }
};
   
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
  
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
  private:
    int _index;
    int _min;
    int _max;
    int _size;
    int* _list;
    void _init(int min, int max) {
      _list = 0; 
      if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
      _size = _max - _min; 
      _index = 0;
    }    
  public:
    UniqueRandom(int max)           { _init(0,   max); randomize(); } //construtor com 1 parametro
    UniqueRandom(int min, int max)  { _init(min, max); randomize(); } //construtor com 2 parametros
    void randomize() {
      _index = 0;
      if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }  
      for (int i=0; i<size(); i++) {   _list[i] = _min+i;  }   //preenche a lista do menor ao maior valor
      //embaralha a lista
      for (int i=0; i<size(); i++) {  
        int r = random(0, size());     //sorteia uma posição qualquer
        int aux = _list[i];               
        _list[i] = _list[r];
        _list[r] = aux;
      }
    }
    int next() {                                  //retorna o proximo numero da lista
      int n = _list[_index++];
      if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
      return n;
    }
    void first() { _index = 0; }
    boolean eof() { return size() == _index+1; }
    int size() { return _size; }
    int getIndex() {  return _index; }
    ~UniqueRandom(){ free ( _list ); }  //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
  
/*************************************************************************************************************
*******************************CLASSE WALL DEFENDER GAME******************************************************
**************************************************************************************************************/
struct Position {
  byte lin : 4;  //maximo 15
  byte col : 4;  //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};

const int WALL_DEFENDER_MAX_WALL_HIGH = 10;  //quantidade maxima de obstáculos na tela
const int WALL_DEFENDER_MAX_ENEMY = 10;  //quantidade maxima de obstáculos na tela
const int WALL_DEFENDER_TIME_INIT = 700; //tempo entre deslocamento dos obstáculos
const int WALL_DEFENDER_TIME_INC  = 10;  //incremento da velocidade
enum WallDefenderGameStatus { WALL_GAME_ON, WALL_GAME_OVER };
enum WallShootStatus {WALL_SHOOT_LEFT, WALL_SHOOT_RIGHT, WALL_SHOOT_STOP};
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};

struct Enemy{
  Position pos;
  Direction dir;
};

class WallDefenderGame {
  private:
    BitArray2D * _display;
    Position _position;
    Position _shoot;
    WallShootStatus _shootStatus;
    Position _wall[WALL_DEFENDER_MAX_WALL_HIGH];
    Enemy _enemys[WALL_DEFENDER_MAX_ENEMY];
    byte _lengthWall;
    byte _controlEnemys : 2;  //máximo = 3 B11
    byte _countUpdate : 3;       //maximo = 5 B101
    Direction _direction;
    unsigned long _last_millis;
    unsigned long _last_millis_player;
    unsigned long _last_millis_shoot;
    int _time;
    int _score;
    int _lengthEnemy;
    WallDefenderGameStatus _wallDefenderGameStatus;
    UniqueRandom * _ur;  //utilizado no game over
    void _gameOver(){ 
      _wallDefenderGameStatus = WALL_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
    void _inc_speed(){
      _score++;
      _time -= WALL_DEFENDER_TIME_INC;
    }
    void _generateWall() {
      _lengthWall = 0;
      for (int i=0;i<(_display->rows()-2)*2;i++) {
        _wall[i].lin = i<(_display->rows()-2) ? i+1 : i+1-_display->rows()+2;
        _wall[i].col = i<(_display->rows()-2) ?  _display->columns()/2-1 : _display->columns()/2;
        _lengthWall++;
      }
    }
    void _start(){
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){ 
        _enemys[i].dir == DIR_STOP; 
        _enemys[i].pos.lin=0;
        _enemys[i].pos.col=0;
      }
      _score  = 0;
      _time = WALL_DEFENDER_TIME_INIT;
      _last_millis  = 0;
      _last_millis_player = 0;
      _position.lin = 0; 
      _position.col = _display->columns() / 2 + 1;
      _direction = DIR_STOP;
      _wallDefenderGameStatus = WALL_GAME_ON;
      _shootStatus = WALL_SHOOT_STOP;
      _generateWall();
      
    }
    void _generateEnemys() {  
      Serial.println(_lengthEnemy);
      if (_controlEnemys == 0 && _lengthEnemy < WALL_DEFENDER_MAX_ENEMY){
        _lengthEnemy++;
        
        int r1 = random(0, _display->rows());
        int r2 = random(0, 2);
         
        for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
          if (_enemys[i].dir == DIR_STOP){
            _enemys[i].dir     = (r2 == 0) ? DIR_RIGHT : DIR_LEFT;
            _enemys[i].pos.col = (r2 == 0) ? 0 : _display->columns()-1;
            _enemys[i].pos.lin = r1;
            break;
          }
        }
      }
      _controlEnemys = (_controlEnemys < 2) ? _controlEnemys+1 : 0;  //a cada 4 atualizacoes gera novos obstáculos
    }
    void _runShoot(){
      if (_shootStatus == WALL_SHOOT_LEFT ) {  if (_shoot.col == 0)                     { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col--; } }
      if (_shootStatus == WALL_SHOOT_RIGHT) {  if (_shoot.col == _display->columns()-1) { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col++; }  }
    }
    void _runGameOver(){
      int r   = _ur->next();
      int lin = (r / _display->columns());
      int col = (r % _display->columns());
      _display->write(lin, col, HIGH );
      if ( _ur->getIndex() == (_ur->size()-1) ) {
        _ur->randomize();
        _ur->first();
        _start(); 
        Serial.println("teste");
      }
    }
    void _runPlayer(){
      if (_direction == DIR_TOP    && _position.lin > 0 )                     { _position.lin--;  }
      if (_direction == DIR_BOTTOM && _position.lin < _display->rows()-1 )    { _position.lin++;  }
      if (_direction == DIR_LEFT   && _position.col > 0 )                     { _position.col--;  }
      if (_direction == DIR_RIGHT  && _position.col < _display->columns()-1 ) { _position.col++;  }
       
      _direction = DIR_STOP; //depois de mover, mantem parado.
    }
    void _runEnemys() {
      /*move os inimigos*/
      byte contRemove = 0;
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++) {
        if (_enemys[i].dir == DIR_RIGHT && _enemys[i].pos.col == _display->columns()-1){ contRemove++; _enemys[i].dir = DIR_STOP; }
        if (_enemys[i].dir == DIR_LEFT  && _enemys[i].pos.col == 0)                    { contRemove++; _enemys[i].dir = DIR_STOP; }
        if (_enemys[i].dir != DIR_STOP ) {  _enemys[i].dir == DIR_LEFT ? _enemys[i].pos.col-- : _enemys[i].pos.col++; }
      }
      if (contRemove > 0){ _lengthEnemy -= contRemove;  }
      _generateEnemys(); 
      _countUpdate++;
      if (_countUpdate >= 5){
        _inc_speed();
        _countUpdate = 0;
      }
    }
    
    void _updateDisplay(){
      //verifica se o tiro colidiu com o inimigo
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
        if (_enemys[i].dir != DIR_STOP ) {  
          if (_enemys[i].pos.lin == _shoot.lin    && _enemys[i].pos.col == _shoot.col)    { _lengthEnemy--; _enemys[i].dir = DIR_STOP;  _shootStatus = WALL_SHOOT_STOP;}
          if (_enemys[i].pos.lin == _position.lin && _enemys[i].pos.col == _position.col) { _gameOver(); }
        }
      }
      //verifica se colidiu na parede
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
        for (int p=0; p<_lengthWall; p++) {
          if (_enemys[i].dir != DIR_STOP ) {
            if (_enemys[i].pos.lin == _wall[p].lin && _enemys[i].pos.col == _wall[p].col) { _gameOver(); }
          }
        }
      }
      if (_wallDefenderGameStatus == WALL_GAME_ON) { _display->clear();  }
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          for (int p=0; p<_lengthWall; p++){
            boolean val = _wall[p].col==col && _wall[p].lin==lin;
            if (val) { _display->write( lin, col,  val ); break;}
          }
          for (int p=0; p<WALL_DEFENDER_MAX_ENEMY; p++) {
            if (_enemys[p].dir != DIR_STOP){
              boolean val = _enemys[p].pos.col==col && _enemys[p].pos.lin==lin;
              if (val) { _display->write( lin, col,  val ); break;}
            }
          }
        }
      }
      _display->write(_position.lin, _position.col, HIGH);
      if (_shootStatus != WALL_SHOOT_STOP) { _display->write(_shoot.lin, _shoot.col, HIGH); }
    }
  public:
    WallDefenderGame(BitArray2D * display){ 
      _display = display;
      _ur = new UniqueRandom( _display->rows() * _display->columns() );
      _lengthWall = 0;
      _lengthEnemy = 0;
      _start();
    }
    void left()   { _direction = DIR_LEFT;   }
    void right()  { _direction = DIR_RIGHT;  }
    void top()    { _direction = DIR_TOP;    }
    void bottom() { _direction = DIR_BOTTOM; }
    int getScore(){ return _score; }
    Position getPositionPlayer() { return _position; }
    void shoot(){
      if (_shootStatus == WALL_SHOOT_STOP){
        _shoot.lin   = _position.lin;
        _shoot.col   = _position.col;
        _shootStatus = (_shoot.col <= _display->columns() / 2-1) ? WALL_SHOOT_LEFT : WALL_SHOOT_RIGHT;
      }
    }
    int update(){
      int r = false;
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();  
        if (_wallDefenderGameStatus == WALL_GAME_ON)   { _runEnemys(); } 
        if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
      }
      if (millis() - _last_millis_player > 70){
        r = true;
        _last_millis_player = millis();
        if (_wallDefenderGameStatus == WALL_GAME_ON)   { _runPlayer(); } 
        if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
      }
      if (millis() - _last_millis_shoot > 50){
        r = true;
        _last_millis_shoot = millis();
        if (_wallDefenderGameStatus == WALL_GAME_ON)   { _runShoot(); } 
        if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
      }
      if (r) { _updateDisplay(); }
      return r; //r-->indica se houve mudança no display
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE WALL DEFENDER GAME**************************************************
**************************************************************************************************************/
   
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON     255
      
struct RotaryEncoderLimits{
  int min;
  int max;
};
      
class RotaryEncoder {
  private:
    byte _pin_clk;
    byte _pin_dt;
    byte _pin_sw;
    volatile byte _num_results;
    volatile int _result;
    volatile int * _results;
    byte _index_result;
    RotaryEncoderLimits * _limits;
    byte _a : 1;
    byte _b : 1;
  public:
    RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){   //parametro do botao opcional
      _pin_clk = pin_clk;
      _pin_dt = pin_dt;
      _pin_sw = pin_sw;
      pinMode(_pin_clk, INPUT);
      pinMode(_pin_dt, INPUT);
      if (_pin_sw != ROTARY_NO_BUTTON){ 
        pinMode(_pin_sw, INPUT); 
        digitalWrite(_pin_sw, HIGH);
      }
      if (num_results == 0) { num_results = 1; }
      _num_results = num_results;
      _results = new int[_num_results];
      for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
      _index_result = 0;
      _limits = limits;
      _a = false;
      _b = false;
    }
    byte getIndex() { return _index_result; }
    void next()     { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } } 
    void update_a() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_clk) != _a ) { 
        _a = !_a;
        if ( _a && !_b ) { _result = -1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    void update_b() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_dt) != _b ) {  
        _b = !_b;
        if ( _b && !_a ) { _result = +1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    int read(){ return _result; }                                        //retorn -1, 0 ou 1.
    int getValue(int index=-1) {                                         //retorna o valor da variável corrente ou a passada como parametro
      if (index < 0 ){ return _results[_index_result]; }
      return _results[index];
    }
    void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
    
/*************************************************************************************************************
*******************************CLASSE GAME LCD****************************************************************
**************************************************************************************************************/
/*
  0        1        2        3        4        5        6
 ***                        ***      ***               ***                 
 ***                        ***      ***               ***          
                                                              
 ***      ***               ***               ***             
 ***      ***               ***               ***             
                                                                
 ***      ***      ***               ***                        
 ***      ***      ***               ***                            
*/
byte c0[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c1[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c2[8] = {B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c3[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c4[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c5[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c6[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B00000,  B00000 };
class GameLCD {
  private:
    LiquidCrystal * _lcd;  //ponteiro para um objeto lcd
  public:
    void createChars() { 
      _lcd->createChar(0, c0);  
      _lcd->createChar(1, c1);  
      _lcd->createChar(2, c2); 
      _lcd->createChar(3, c3);  
      _lcd->createChar(4, c4);
      _lcd->createChar(5, c5);
      _lcd->createChar(6, c6);
    }
    GameLCD(LiquidCrystal * lcd) {  _lcd = lcd;  }
    void write(byte col, byte row, byte val){
      _lcd->setCursor(col, row);
      if (val == B000) { _lcd->print(" ");  }
      if (val == B111) { _lcd->write((byte)0);  }
      if (val == B011) { _lcd->write((byte)1);  }
      if (val == B001) { _lcd->write((byte)2);  }
      if (val == B110) { _lcd->write((byte)3);  }
      if (val == B101) { _lcd->write((byte)4);  }
      if (val == B010) { _lcd->write((byte)5);  }
      if (val == B100) { _lcd->write((byte)6);  }
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE GAME LCD***********************************************************
**************************************************************************************************************/
   
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
 tamanho display real
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
*/
  
const int LINHAS  = 6;
const int COLUNAS = 16;
   
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
GameLCD gameLcd(&lcd);
   
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
WallDefenderGame wall(&ba);
  
RotaryEncoderLimits lim[] = { {-1000,1000} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 1, lim);  //pino clk, pino dt, pino sw, variaveis, limites
  
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
    
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
  volatile static byte lastVal_a0 = LOW;
  volatile static byte lastVal_a1 = LOW;
  byte val_a0 = digitalRead(A0);
  byte val_a1 = digitalRead(A1);
  if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
  if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
   
void setup_interrupts(){
  //-----PCI - Pin Change Interrupt ----
  pinMode(A0,INPUT);   // set Pin as Input (default)
  digitalWrite(A0,HIGH);  // enable pullup resistor
  pinMode(A1,INPUT);   // set Pin as Input (default)
  digitalWrite(A1,HIGH);  // enable pullup resistor
  cli();
  PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
  PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
  sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
     
void update_display() {
  for (int col=0; col<ba.columns(); col++) {
    byte lin_lcd = 0;
    byte cont = 0;
    byte val = 0;
    for (int lin=0; lin<ba.rows(); lin++) {
      if (cont == 0){ val = 0; }
      val = val << 1; 
      val = val | ba.read(lin, col);
      cont++;
      if (cont == 3) {
        gameLcd.write(col, lin_lcd, val) ;
        cont = 0;
        lin_lcd++;
      }
    }
  }
}
     
void setup() { 
  Serial.begin(9600);
  setup_interrupts();
  lcd.begin(16, 2);
  gameLcd.createChars();
  randomSeed(analogRead(A2));
  re.setValue(0, 0);  //inicializa o rotary em 0
}
     
void loop() {
  static int val_encoder = 0;
  static boolean change = false;
    
  //anti-horario
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin > 0  && wall.getPositionPlayer().col == 9){ wall.top();    change = true; }
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col >= 7){ wall.left();   change = true; }
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin < 5  && wall.getPositionPlayer().col == 6){ wall.bottom(); change = true; }  
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 5 && wall.getPositionPlayer().col <= 9){ wall.right();  change = true; }
  //horario
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin > 0  && wall.getPositionPlayer().col == 6){ wall.top();    change = true; }  
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 5 && wall.getPositionPlayer().col >= 7){ wall.left();   change = true; }
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin < 5  && wall.getPositionPlayer().col == 9){ wall.bottom(); change = true; }
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col <= 8){ wall.right();  change = true; }
  
  val_encoder = re.getValue(0);
  if ( wall.update() ) { 
    change = false;
    update_display(); 
  }  
    
  //controla o click do botao do enconder
  static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
  if( re.buttonRead() == LOW && b != re.buttonRead() ) {
    wall.shoot();        //dispara o tiro
    delay(50);           //debounce meia boca
  }
  b = re.buttonRead();
} 


Atualização 13/06/2016 - Versão com display de leds 8x8

Implementei uma versão em um display mais apropriado, duas matrizes de leds 8x8. Como a implementação do jogo foi feita utilizando conceitos de orientação a objetos, foi fácil fazer esta versão.

Alguns detalhes sobre a implementação: Para controlar a matriz de leds foi criada uma classe baseada na biblioteca LedControl (na verdade um ctrl+c ctrl+v), mas com algumas modificações, visando deixar a atualização do display mais rápida, mas o código todo está na própria sketch e não em uma biblioteca a parte. Outro detalhe, é que os dois displays, como poder ser visto no vídeo, não estão "alinhados", eles estão cada um posicionado de uma maneira, ou seja, isso significa que foi necessário criar uma rotina para converter linhas e colunas de maneira a mostrar os dados corretamente. Isso foi feito para facilitar o arranjo dos displays da forma mais conveniente.

Vídeo:



Código-fonte:

/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/

/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
  private:
    unsigned int _rows;
    unsigned int _columns;
    unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado 
    byte**       _bits;
  public:
    BitArray2D(unsigned int rows, unsigned int columns){
      _rows       = rows;
      _columns    = columns;
      _cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
      _bits = (byte **)malloc(_rows * sizeof(byte *));
      for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc(  _cols_array  *  sizeof(byte)); } //cria varios arrays
      clear();
    }
    unsigned int rows(){ return _rows; }
    unsigned int columns(){ return _columns; }
    void clear() { 
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }       
      }   
    }
    void write(unsigned int row, unsigned int column, int value){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;    
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
      _bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
    }
    void write(byte value){
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) {      
          _bits[i][j] = value ? B11111111 : B00000000;     
        }       
      }  
    }
    int read(unsigned int row, unsigned int column){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;    
      return (b & (1 << bit)) != 0;
    }
    void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
    void toggle(){ for(int i=0;i<_rows;i++){      for(int j=0; j<_columns;j++) {      toggle(i,j);   }   }   }
};
    
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
   
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
  private:
    int _index;
    int _min;
    int _max;
    int _size;
    int* _list;
    void _init(int min, int max) {
      _list = 0; 
      if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
      _size = _max - _min; 
      _index = 0;
    }    
  public:
    UniqueRandom(int max)           { _init(0,   max); randomize(); } //construtor com 1 parametro
    UniqueRandom(int min, int max)  { _init(min, max); randomize(); } //construtor com 2 parametros
    void randomize() {
      _index = 0;
      if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }  
      for (int i=0; i<size(); i++) {   _list[i] = _min+i;  }   //preenche a lista do menor ao maior valor
      //embaralha a lista
      for (int i=0; i<size(); i++) {  
        int r = random(0, size());     //sorteia uma posição qualquer
        int aux = _list[i];               
        _list[i] = _list[r];
        _list[r] = aux;
      }
    }
    int next() {                                  //retorna o proximo numero da lista
      int n = _list[_index++];
      if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
      return n;
    }
    void first() { _index = 0; }
    boolean eof() { return size() == _index+1; }
    int size() { return _size; }
    int getIndex() {  return _index; }
    ~UniqueRandom(){ free ( _list ); }  //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
   
/*************************************************************************************************************
*******************************LEDCONTROL ALTERADA************************************************************
mais informações: http://fabianoallex.blogspot.com.br/2015/09/arduino-alteracoes-na-biblioteca.html
**************************************************************************************************************/
//the opcodes for the MAX7221 and MAX7219
#define OP_DECODEMODE  9
#define OP_INTENSITY   10
#define OP_SCANLIMIT   11
#define OP_SHUTDOWN    12
#define OP_DISPLAYTEST 15
class LedControl {
  private :
    byte spidata[16];
    byte * status;
    int SPI_MOSI;
    int SPI_CLK;
    int SPI_CS;
    int maxDevices;
    int _auto_send;
    void spiTransfer(int addr, volatile byte opcode, volatile byte data) {
      int offset   = addr*2;
      int maxbytes = maxDevices*2;
      for(int i=0;i<maxbytes;i++)  { spidata[i]=(byte)0; }
      spidata[offset+1] = opcode;
      spidata[offset]   = data;
      digitalWrite(SPI_CS,LOW);
      for(int i=maxbytes;i>0;i--) { shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); }
      digitalWrite(SPI_CS,HIGH);
    }
  public:
    LedControl(int dataPin, int clkPin, int csPin, int numDevices) {
      _auto_send  = true;
      SPI_MOSI    = dataPin;
      SPI_CLK     = clkPin;
      SPI_CS      = csPin;
      maxDevices  = numDevices;
      pinMode(SPI_MOSI, OUTPUT);
      pinMode(SPI_CLK,  OUTPUT);
      pinMode(SPI_CS,   OUTPUT);
      digitalWrite(SPI_CS, HIGH);
      status      = new byte[maxDevices * 8]; //instancia o array de acordo com a quantia de displays usados
      for(int i=0;i<maxDevices * 8 ;i++) { status[i]=0x00; }
      for(int i=0;i<maxDevices;i++) {
        spiTransfer(i, OP_DISPLAYTEST,0);
        setScanLimit(i, 7);               //scanlimit is set to max on startup
        spiTransfer(i, OP_DECODEMODE,0);  //decode is done in source
        clearDisplay(i);
        shutdown(i,true);                 //we go into shutdown-mode on startup
      }
    }
    void startWrite() {  _auto_send = false;  };
    void send() {
      for (int j=0; j<maxDevices; j++) {
        int offset = j*8;
        for(int i=0;i<8;i++) { spiTransfer(j, i+1, status[offset+i]); }
      }
      _auto_send = true;
    }
    int getDeviceCount(){ return maxDevices; }
    void shutdown(int addr, bool b){
      if(addr<0 || addr>=maxDevices) return;
      spiTransfer(addr, OP_SHUTDOWN, b ? 0 : 1);
    }
    void setScanLimit(int addr, int limit){
      if(addr<0 || addr>=maxDevices) return;
      if(limit>=0 && limit<8) spiTransfer(addr, OP_SCANLIMIT,limit);
    }
    void setIntensity(int addr, int intensity) {
      if(addr<0 || addr>=maxDevices)   {  return;                                    }
      if(intensity>=0 && intensity<16) {  spiTransfer(addr, OP_INTENSITY, intensity); }
    }
    void clearDisplay(int addr){
      if(addr<0 || addr>=maxDevices) return;
      int offset = addr*8;
      for(int i=0;i<8;i++) {
        status[offset+i] = 0;
        if (_auto_send) { spiTransfer(addr, i+1, status[offset+i]); }
      }
    }
    void setLed(int addr, int row, int column, boolean state) {
      if(addr<0 || addr>=maxDevices)             { return; }
      if(row<0 || row>7 || column<0 || column>7) { return; }
      int offset = addr*8;
      byte val = B10000000 >> column;
      if(state) { status[offset+row] = status[offset+row] | val; } else { val=~val; status[offset+row] = status[offset+row]&val; }
      if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
    }
    void setRow(int addr, int row, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(row<0 || row>7) return;
      int offset = addr*8;
      status[offset+row] = value;
      if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
    }
    void setColumn(int addr, int col, byte value) {
      if(addr<0 || addr>=maxDevices) return;
      if(col<0 || col>7)             return;
      byte val;
      for(int row=0; row<8; row++) {
        val=value >> (7-row);
        val=val & 0x01;
        setLed(addr,row,col,val);
      }
    }
};
/*************************************************************************************************************
*******************************FIM LEDCONTROL ALTERADA********************************************************
**************************************************************************************************************/
   
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON     255
       
struct RotaryEncoderLimits{
  int min;
  int max;
};
       
class RotaryEncoder {
  private:
    byte _pin_clk;
    byte _pin_dt;
    byte _pin_sw;
    volatile byte _num_results;
    volatile int _result;
    volatile int * _results;
    byte _index_result;
    RotaryEncoderLimits * _limits;
    byte _a : 1;
    byte _b : 1;
  public:
    RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){   //parametro do botao opcional
      _pin_clk = pin_clk;
      _pin_dt = pin_dt;
      _pin_sw = pin_sw;
      pinMode(_pin_clk, INPUT);
      pinMode(_pin_dt, INPUT);
      if (_pin_sw != ROTARY_NO_BUTTON){ 
        pinMode(_pin_sw, INPUT); 
        digitalWrite(_pin_sw, HIGH);
      }
      if (num_results == 0) { num_results = 1; }
      _num_results = num_results;
      _results = new int[_num_results];
      for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
      _index_result = 0;
      _limits = limits;
      _a = false;
      _b = false;
    }
    byte getIndex() { return _index_result; }
    void next()     { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } } 
    void update_a() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_clk) != _a ) { 
        _a = !_a;
        if ( _a && !_b ) { _result = -1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    void update_b() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_dt) != _b ) {  
        _b = !_b;
        if ( _b && !_a ) { _result = +1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    int read(){ return _result; }                                        //retorn -1, 0 ou 1.
    int getValue(int index=-1) {                                         //retorna o valor da variável corrente ou a passada como parametro
      if (index < 0 ){ return _results[_index_result]; }
      return _results[index];
    }
    void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
   
/*************************************************************************************************************
*******************************CLASSE WALL DEFENDER GAME******************************************************
**************************************************************************************************************/
struct Position {
  int lin ;  //maximo 15
  int col ;  //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};
 
const int WALL_DEFENDER_MAX_WALL_HIGH = 15;  //quantidade maxima de inimigos na tela
const int WALL_DEFENDER_MAX_ENEMY = 10;  //quantidade maxima de obstáculos na tela
const int WALL_DEFENDER_TIME_INIT = 700; //tempo entre deslocamento dos inimigos
const int WALL_DEFENDER_TIME_INC  = 10;  //incremento da velocidade
enum WallDefenderGameStatus { WALL_GAME_ON, WALL_GAME_OVER };
enum WallShootStatus {WALL_SHOOT_LEFT, WALL_SHOOT_RIGHT, WALL_SHOOT_STOP};
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
 
struct Enemy{
  Position pos;
  Direction dir;
};
 
class WallDefenderGame {
  private:
    BitArray2D * _display;
    Position _position;
    Position _shoot;
    WallShootStatus _shootStatus;
    Position _wall[WALL_DEFENDER_MAX_WALL_HIGH];
    Enemy _enemys[WALL_DEFENDER_MAX_ENEMY];
    byte _lengthWall;
    byte _controlEnemys : 2;  //máximo = 3 B11
    byte _countUpdate : 3;       //maximo = 5 B101
    Direction _direction;
    unsigned long _last_millis;
    unsigned long _last_millis_player;
    unsigned long _last_millis_shoot;
    int _time;
    int _score;
    int _lengthEnemy;
    WallDefenderGameStatus _wallDefenderGameStatus;
    UniqueRandom * _ur;  //utilizado no game over
    void _gameOver(){ 
      _wallDefenderGameStatus = WALL_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
    void _inc_speed(){
      _score++;
      _time -= WALL_DEFENDER_TIME_INC;
    }
    void _generateWall() {
      _lengthWall = 0;
      for (int i=0;i<(_display->rows()-2)*2;i++) {
        _wall[i].lin = i<(_display->rows()-2) ? i+1 : i+1-_display->rows()+2;
        _wall[i].col = i<(_display->rows()-2) ?  _display->columns()/2-1 : _display->columns()/2;
        _lengthWall++;
      }
    }
    void _start(){
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){ 
        _enemys[i].dir == DIR_STOP; 
        _enemys[i].pos.lin=0;
        _enemys[i].pos.col=0;
      }
      _score  = 0;
      _time = WALL_DEFENDER_TIME_INIT;
      _last_millis  = 0;
      _last_millis_player = 0;
      _position.lin = 0; 
      _position.col = _display->columns() / 2 + 1;
      _direction = DIR_STOP;
      _wallDefenderGameStatus = WALL_GAME_ON;
      _shootStatus = WALL_SHOOT_STOP;
      _generateWall();
      
      Serial.println("a");
    }
    void _generateEnemys() {  
      Serial.println(_lengthEnemy);
      if (_controlEnemys == 0 && _lengthEnemy < WALL_DEFENDER_MAX_ENEMY){
        _lengthEnemy++;
         
        int r1 = random(0, _display->rows());
        int r2 = random(0, 2);
          
        for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
          if (_enemys[i].dir == DIR_STOP){
            _enemys[i].dir     = (r2 == 0) ? DIR_RIGHT : DIR_LEFT;
            _enemys[i].pos.col = (r2 == 0) ? 0 : _display->columns()-1;
            _enemys[i].pos.lin = r1;
            break;
          }
        }
      }
      _controlEnemys = (_controlEnemys < 2) ? _controlEnemys+1 : 0;  //a cada 4 atualizacoes gera novos obstáculos
    }
    void _runShoot(){
      if (_shootStatus == WALL_SHOOT_LEFT ) {  if (_shoot.col == 0)                     { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col--; } }
      if (_shootStatus == WALL_SHOOT_RIGHT) {  if (_shoot.col == _display->columns()-1) { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col++; }  }
    }
    void _runGameOver(){
      int r   = _ur->next();
      int lin = (r / _display->columns());
      int col = (r % _display->columns());
      _display->write(lin, col, HIGH );
      if ( _ur->getIndex() == (_ur->size()-1) ) {
        _ur->randomize();
        _ur->first();
        _start(); 
        Serial.println("teste");
      }
    }
    void _runPlayer(){
      if (_direction == DIR_TOP    && _position.lin > 0 )                     { _position.lin--;  }
      if (_direction == DIR_BOTTOM && _position.lin < _display->rows()-1 )    { _position.lin++;  }
      if (_direction == DIR_LEFT   && _position.col > 0 )                     { _position.col--;  }
      if (_direction == DIR_RIGHT  && _position.col < _display->columns()-1 ) { _position.col++;  }
        
      _direction = DIR_STOP; //depois de mover, mantem parado.
    }
    void _runEnemys() {
      /*move os inimigos*/
      byte contRemove = 0;
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++) {
        if (_enemys[i].dir == DIR_RIGHT && _enemys[i].pos.col == _display->columns()-1){ contRemove++; _enemys[i].dir = DIR_STOP; }
        if (_enemys[i].dir == DIR_LEFT  && _enemys[i].pos.col == 0)                    { contRemove++; _enemys[i].dir = DIR_STOP; }
        if (_enemys[i].dir != DIR_STOP ) {  _enemys[i].dir == DIR_LEFT ? _enemys[i].pos.col-- : _enemys[i].pos.col++; }
      }
      if (contRemove > 0){ _lengthEnemy -= contRemove;  }
      _generateEnemys(); 
      _countUpdate++;
      if (_countUpdate >= 5){
        _inc_speed();
        _countUpdate = 0;
      }
    }
     
    void _updateDisplay(){
      //verifica se o tiro colidiu com o inimigo
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
        if (_enemys[i].dir != DIR_STOP ) {  
          if (_enemys[i].pos.lin == _shoot.lin    && _enemys[i].pos.col == _shoot.col)    { _lengthEnemy--; _enemys[i].dir = DIR_STOP;  _shootStatus = WALL_SHOOT_STOP;}
          if (_enemys[i].pos.lin == _position.lin && _enemys[i].pos.col == _position.col) { _gameOver(); }
        }
      }
      //verifica se colidiu na parede
      for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
        for (int p=0; p<_lengthWall; p++) {
          if (_enemys[i].dir != DIR_STOP ) {
            if (_enemys[i].pos.lin == _wall[p].lin && _enemys[i].pos.col == _wall[p].col) { _gameOver(); }
          }
        }
      }
      if (_wallDefenderGameStatus == WALL_GAME_ON) { _display->clear();  }
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          for (int p=0; p<_lengthWall; p++){
            boolean val = _wall[p].col==col && _wall[p].lin==lin;
            if (val) { _display->write( lin, col,  val ); break;}
          }
          for (int p=0; p<WALL_DEFENDER_MAX_ENEMY; p++) {
            if (_enemys[p].dir != DIR_STOP){
              boolean val = _enemys[p].pos.col==col && _enemys[p].pos.lin==lin;
              if (val) { _display->write( lin, col,  val ); break;}
            }
          }
        }
      }
      _display->write(_position.lin, _position.col, HIGH);
      if (_shootStatus != WALL_SHOOT_STOP) { _display->write(_shoot.lin, _shoot.col, HIGH); }
    }
  public:
    WallDefenderGame(BitArray2D * display){ 
      _display = display;
      _ur = new UniqueRandom( _display->rows() * _display->columns() );
      _lengthWall = 0;
      _lengthEnemy = 0;
      _start();
    }
    void left()   { _direction = DIR_LEFT;   }
    void right()  { _direction = DIR_RIGHT;  }
    void top()    { _direction = DIR_TOP;    }
    void bottom() { _direction = DIR_BOTTOM; }
    int getScore(){ return _score; }
    Position getPositionPlayer() { return _position; }
    void shoot(){
      if (_shootStatus == WALL_SHOOT_STOP){
        _shoot.lin   = _position.lin;
        _shoot.col   = _position.col;
        _shootStatus = (_shoot.col <= _display->columns() / 2-1) ? WALL_SHOOT_LEFT : WALL_SHOOT_RIGHT;
      }
    }
    int update(){
      int r = false;
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();  
        if (_wallDefenderGameStatus == WALL_GAME_ON)   { _runEnemys(); } 
        if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
      }
      if (millis() - _last_millis_player > 50){
        r = true;
        _last_millis_player = millis();
        if (_wallDefenderGameStatus == WALL_GAME_ON)   { _runPlayer(); } 
        if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
      }
      if (millis() - _last_millis_shoot > 50){
        r = true;
        _last_millis_shoot = millis();
        if (_wallDefenderGameStatus == WALL_GAME_ON)   { _runShoot(); } 
        if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
      }
      if (r) { _updateDisplay(); }
      return r; //r-->indica se houve mudança no display
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE WALL DEFENDER GAME**************************************************
**************************************************************************************************************/
 
/*************************************************************************************************************
*******************************DISPLAY************************************************************************
**************************************************************************************************************/
 
/*
rotation indica qual parte do display estará para cima
 
         TOP
L  . . . . . . . . R
E  . . . . . . . . I
F  . . . . . . . . G
T  . . . . . . . . H
   . . . . . . . . T
   . . . . . . . .
   . . . . . . . .
   . . . . . . . .   
       BOTTOM
     
*/
enum Rotation {TOP, LEFT, BOTTOM, RIGHT};
 
struct Display {
  int index;
  Position position; 
  Rotation rotation;
};
 
/*************************************************************************************************************
*******************************FIM DISPLAY********************************************************************
**************************************************************************************************************/
 
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
 tamanho display real
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * 
*/
   
const int LINHAS  = 8;
const int COLUNAS = 16;

Display displays_8x8[] = {
  {0, {0,0}, LEFT},
  {1, {0,8}, RIGHT}
};
    
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
WallDefenderGame wall(&ba);
   
RotaryEncoderLimits lim[] = { {-1000,1000} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 1, lim);  //pino clk, pino dt, pino sw, qtd variaveis, limites

LedControl lc=LedControl(10,12,11,2);  //pin 10: DataIn ; pin 12: CLK ;  pin 11: LOAD   --- 2 display 8x8

   
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
     
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
  volatile static byte lastVal_a0 = LOW;
  volatile static byte lastVal_a1 = LOW;
  byte val_a0 = digitalRead(A0);
  byte val_a1 = digitalRead(A1);
  if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
  if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
    
void setup_interrupts(){
  //-----PCI - Pin Change Interrupt ----
  pinMode(A0,INPUT);   // set Pin as Input (default)
  digitalWrite(A0,HIGH);  // enable pullup resistor
  pinMode(A1,INPUT);   // set Pin as Input (default)
  digitalWrite(A1,HIGH);  // enable pullup resistor
  cli();
  PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
  PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
  sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
void update_display() {
  lc.startWrite();
   
  for (int lin=0; lin<ba.rows(); lin++) {
    for (int col=0; col<ba.columns(); col++) {
      for (int i=0; i<sizeof(displays_8x8)/sizeof(Display); i++) {
        int l = lin - displays_8x8[i].position.lin;
        int c = col - displays_8x8[i].position.col;
        if (l>=0 && l<=7 && c>=0 && c<=7) {
          if (displays_8x8[i].rotation == BOTTOM) {            c=7-c; l=7-l;   }
          if (displays_8x8[i].rotation == LEFT)   { int aux=c; c=l;   l=7-aux; }
          if (displays_8x8[i].rotation == RIGHT)  { int aux=l; l=c;   c=7-aux; }
          lc.setLed(displays_8x8[i].index, l, c, ba.read(lin, col) );
        }
      }
    }
  }
   
  lc.send();
}
 
 
void setup() { 
  Serial.begin(9600);
  
  lc.shutdown(0,false);
  lc.setIntensity(0,1);
  lc.clearDisplay(0);  
  lc.shutdown(1,false);
  lc.setIntensity(1,1);
  lc.clearDisplay(1);  
  
  setup_interrupts();
  randomSeed(analogRead(A2));
  re.setValue(0, 0);  //inicializa o rotary em 0
}
      
void loop() {
  static int val_encoder = 0;
  static boolean change = false;
     
  //anti-horario
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin > 0  && wall.getPositionPlayer().col == 9){ wall.top();    change = true; }
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col >= 7){ wall.left();   change = true; }
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin < 7  && wall.getPositionPlayer().col == 6){ wall.bottom(); change = true; }  
  if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 7 && wall.getPositionPlayer().col <= 9){ wall.right();  change = true; }
  //horario
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin > 0  && wall.getPositionPlayer().col == 6){ wall.top();    change = true; }  
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 7 && wall.getPositionPlayer().col >= 7){ wall.left();   change = true; }
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin < 7  && wall.getPositionPlayer().col == 9){ wall.bottom(); change = true; }
  if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col <= 8){ wall.right();  change = true; }
   
  val_encoder = re.getValue(0);
  if ( wall.update() ) { 
    change = false;
    update_display(); 
  }  
     
  //controla o click do botao do enconder
  static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
  if( re.buttonRead() == LOW && b != re.buttonRead() ) {
    wall.shoot();        //dispara o tiro
    delay(50);           //debounce meia boca
  }
  b = re.buttonRead();
} 


segunda-feira, 6 de junho de 2016

Arduino - Race Game com display LCD

Semana passada postei uma implementação minha do clássico Snake Game em um display LCD 16x2. Baseado naquele exemplo, implementei um jogo de corrida. Apesar de simples ficou legalzinho.

Vídeo:


Código-Fonte:

 /*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
    
#include <LiquidCrystal.h>
 
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray{
  private:
    int _num_bits;   //quantidade de bits a serem gerenciados
    int _num_bytes;  //quantidade de bytes utilizados para armazenar os bits a serem gerenciados
    byte * _bytes;   //array de bytes onde estarão armazenados os bits
  public:
    BitArray(int num_bits){
      _num_bits  = num_bits;
      _num_bytes = _num_bits/8 + (_num_bits%8 ? 1 : 0) + 1;
      _bytes = (byte *)(malloc( _num_bytes * sizeof(byte) ) );
    }
    void write(int index, byte value) {
      byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
      unsigned int bit = index%8;
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
      _bytes[ index/8 + (index%8 ? 1 : 0) ] = b;
    }
    void write(byte value) { for(int j=0; j<_num_bytes;j++) { _bytes[j] = value ? B11111111 : B00000000;  }  }
    int read(int index) {
      byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
      unsigned int bit = index%8;
      return (b & (1 << bit)) != 0;
    }
    ~BitArray(){ free ( _bytes ); }
};
 
class BitArray2D {
  private:
    unsigned int _rows;
    unsigned int _columns;
    unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado 
    byte**       _bits;
  public:
    BitArray2D(unsigned int rows, unsigned int columns){
      _rows       = rows;
      _columns    = columns;
      _cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
      _bits = (byte **)malloc(_rows * sizeof(byte *));
      for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc(  _cols_array  *  sizeof(byte)); } //cria varios arrays
      clear();
    }
    unsigned int rows(){ return _rows; }
    unsigned int columns(){ return _columns; }
      
    void clear() { 
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }       
      }   
    }
    
    void write(unsigned int row, unsigned int column, int value){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;
        
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
        
      _bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
    }
      
    void write(byte value){
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) {      
          _bits[i][j] = value ? B11111111 : B00000000;     
        }       
      }  
    }
      
    int read(unsigned int row, unsigned int column){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;
        
      return (b & (1 << bit)) != 0;
    }
      
    void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
    void toggle(){ for(int i=0;i<_rows;i++){      for(int j=0; j<_columns;j++) {      toggle(i,j);   }   }   }
};
  
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
 
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
   
class UniqueRandom{
  private:
    int _index;
    int _min;
    int _max;
    int _size;
    int* _list;
    void _init(int min, int max) {
      _list = 0; 
      if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
      _size = _max - _min; 
      _index = 0;
    }    
  public:
    UniqueRandom(int max)           { _init(0,   max); randomize(); } //construtor com 1 parametro
    UniqueRandom(int min, int max)  { _init(min, max); randomize(); } //construtor com 2 parametros
       
    void randomize() {
      _index = 0;
        
      if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }  
      for (int i=0; i<size(); i++) {   _list[i] = _min+i;  }   //preenche a lista do menor ao maior valor
         
      //embaralha a lista
      for (int i=0; i<size(); i++) {  
        int r = random(0, size());     //sorteia uma posição qualquer
        int aux = _list[i];               
        _list[i] = _list[r];
        _list[r] = aux;
      }
    }
       
    int next() {                                  //retorna o proximo numero da lista
      int n = _list[_index++];
      if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
      return n;
    }
       
    int size() { return _size; }
       
    ~UniqueRandom(){ free ( _list ); }  //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
 
/*************************************************************************************************************
*******************************CLASSE RACE GAME***************************************************************
**************************************************************************************************************/
struct Position {
  byte lin : 4;  //maximo 15
  byte col : 4;  //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};

const int RACE_MAX_OBSTACLES = 30;  //quantidade maxima de obstáculos na tela
const int RACE_TIME_INIT = 500; //tempo entre deslocamento dos obstáculos
const int RACE_TIME_INC  = 15;  //incremento da velocidade

enum RaceGameStatus { RACE_GAME_ON, RACE_GAME_OVER };
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};

class RaceGame{
  private:
    BitArray2D * _display;
    Position _position;
    Position _obstacles_positions[RACE_MAX_OBSTACLES];
    Direction _direction;
    byte _lengthObstacles;
    byte _controlObstacle : 2;  //máximo = 3 B11
    byte _isCountdown : 1;      //apenas 1 ou 0   -------- contagem regressiva
    byte _countUpdate : 3;       //maximo = 5 B101
    byte _countdown : 3;       //ma´ximo = 5 B101
    unsigned long _last_millis;
    unsigned long _last_millis_car;
    unsigned long _millisCountdown;
    int _time;
    int _score;
    RaceGameStatus _raceGameStatus;
    UniqueRandom * _ur;  //utilizado no game over
    UniqueRandom * _urObstacles;  //utilizado no game over
    
    void _gameOver(){ 
      _raceGameStatus = RACE_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
      
    void _inc_speed(){
      //_length++; 
      _score++;
      _time -= RACE_TIME_INC;
    }
      
    void _runGameOver(){
      int r   = _ur->next();
      int lin = (r / _display->columns());
      int col = (r % _display->columns());
        
      _display->write(lin, col, HIGH );
        
      //if ( r>=(_ur->size()-1) || _direction != DIR_STOP ) {  
      if ( r>=(_ur->size()-1) ) {  
        _ur->randomize();
        start(); 
      }
    }
    
    void _generateObstacles() {  
      if (_controlObstacle == 0){
        _urObstacles->randomize();
        
        //quantidade de obstaculos estara entre 1 e quantidade de linhas -1
        int numObstacles = random(0, _display->rows()-4) + 4;   
        
        if (_lengthObstacles + numObstacles > RACE_MAX_OBSTACLES) {
          numObstacles = RACE_MAX_OBSTACLES - _lengthObstacles;
        }
        
        for (int i=0; i<numObstacles; i++){
          _lengthObstacles++;
          
          _obstacles_positions[_lengthObstacles-1].lin = _urObstacles->next();
          _obstacles_positions[_lengthObstacles-1].col = _display->columns()-1;
        }
      }
      _controlObstacle = (_controlObstacle < 3) ? _controlObstacle+1 : 0;  //a cada 4 atualizacoes gera novos obstáculos
    }
    
    void _runCountdown(){
      
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          _display->write( lin, col,  0 );
        }
      }
      
      unsigned long dif = millis() - _millisCountdown;
      
      if ( dif < 1000){
        _display->write( 1, 3,  1 );  //5
        _display->write( 1, 4,  1 );
        _display->write( 1, 5,  1 );
        _display->write( 2, 3,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
        _display->write( 5, 4,  1 );
        _display->write( 5, 3,  1 );
      } else if (dif < 2000) {
        _display->write( 1, 3,  1 );  //4
        _display->write( 1, 5,  1 );
        _display->write( 2, 3,  1 );
        _display->write( 2, 5,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
      } else if (dif < 3000) {
        _display->write( 1, 3,  1 );  //3
        _display->write( 1, 4,  1 );
        _display->write( 1, 5,  1 );
        _display->write( 2, 5,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
        _display->write( 5, 4,  1 );
        _display->write( 5, 3,  1 );
      } else if (dif < 4000) {
        _display->write( 1, 3,  1 );  //2
        _display->write( 1, 4,  1 );
        _display->write( 1, 5,  1 );
        _display->write( 2, 5,  1 );
        _display->write( 3, 3,  1 );
        _display->write( 3, 4,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 3,  1 );
        _display->write( 5, 5,  1 );
        _display->write( 5, 4,  1 );
        _display->write( 5, 3,  1 );
      } else if (dif < 5000) {
        _display->write( 1, 5,  1 );  //1
        _display->write( 2, 5,  1 );
        _display->write( 3, 5,  1 );
        _display->write( 4, 5,  1 );
        _display->write( 5, 5,  1 );
      } else {
        _isCountdown  = 0;
      }
      
    }
    
    void _runObastacles(){
      /*move os obstáculos*/
      byte contRemove = 0;
      for (int i=0; i<_lengthObstacles; i++) {
        if (_obstacles_positions[i].col == 0 ) { contRemove++;}  //B1111 --> linha inexistente no display. significa que será removido do array
        _obstacles_positions[i].col--;
      }
      
      if (contRemove > 0){
        for (int i=0; i<_lengthObstacles-contRemove; i++) {
          _obstacles_positions[i] = _obstacles_positions[i+contRemove];
        }
        _lengthObstacles -= contRemove; 
      }
        
      _generateObstacles();
      
      _countUpdate++;
      
      if (_countUpdate >= 5){
        _inc_speed();
        _countUpdate = 0;
      }
    }
    
    void _runCar() {
      if (_direction == DIR_TOP )    { _position.lin--;  }
      if (_direction == DIR_BOTTOM ) { _position.lin++;  }
      if (_direction == DIR_LEFT )   { _position.col--;  }
      if (_direction == DIR_RIGHT )  { _position.col++;  }
      
      _direction = DIR_STOP; //depois de mover, mantem parado.
      
      //verifica se ultrapassou o limite do display
      if (_position.lin < 0)                     { _gameOver(); }
      if (_position.lin >= _display->rows() )    { _gameOver(); }
      if (_position.col < 0)                     { _gameOver(); }
      if (_position.col >= _display->columns() ) { _gameOver(); }
        
      //verifica se colidiu nos obstáculos
      for (int i=0; i<_lengthObstacles; i++){
        if (_obstacles_positions[i].lin == _position.lin && _obstacles_positions[i].col == _position.col) {
          _gameOver();
        }  
      }
        
      //update display
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          for (int p=0; p<_lengthObstacles; p++){
            boolean val = _obstacles_positions[p].col==col && _obstacles_positions[p].lin==lin;
            _display->write( lin, col,  val );
            if (val) {break;}
          }
        }
      }
      _display->write(_position.lin, _position.col, HIGH);
    }
  public:
    RaceGame(BitArray2D * display){ 
      _display = display;
      _ur = new UniqueRandom( _display->rows() * _display->columns() );
      _urObstacles = new UniqueRandom( _display->rows() );
      start();
    }
      
    void start(){
      _score  = 0;
      _time = RACE_TIME_INIT;
      _last_millis = 0;
      _position.lin = _display->rows() / 2;
      _position.col = 0;
      _direction = DIR_STOP;
      _lengthObstacles = 0;
      _raceGameStatus = RACE_GAME_ON;
      _countUpdate = 0;
      _generateObstacles();
      _isCountdown = 1;
      _countdown = 5;
      _millisCountdown = millis();
      _last_millis_car = millis();
    }
      
    void left()   { _direction = DIR_LEFT;   }
    void right()  { _direction = DIR_RIGHT;  }
    void top()    { _direction = DIR_TOP;    }
    void bottom() { _direction = DIR_BOTTOM; }
      
    int getScore(){ return _score; }
     
     
    int update(){
      int r = false;
      
      if (_isCountdown){
        if (millis() - _last_millis_car > 20){
          _last_millis_car = millis();
          
          _runCountdown();
          return true;
        }
        
        
        return false;
        
      }
        
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();
        
        if (_raceGameStatus == RACE_GAME_ON)   { _runObastacles(); } 
        if (_raceGameStatus == RACE_GAME_OVER) { _runGameOver(); }
      }
      
      if (millis() - _last_millis_car > 20){
        r = true;
        _last_millis_car = millis();
        
        if (_raceGameStatus == RACE_GAME_ON)   { _runCar();         } 
        if (_raceGameStatus == RACE_GAME_OVER) { _runGameOver(); }
      }
        
      return r; //r-->indica se houve mudança no display
    }
};
  
/*************************************************************************************************************
*******************************FIM CLASSE RACE GAME***********************************************************
**************************************************************************************************************/
  
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON     255
     
struct RotaryEncoderLimits{
  int min;
  int max;
};
     
class RotaryEncoder {
  private:
    byte _pin_clk;
    byte _pin_dt;
    byte _pin_sw;
    volatile byte _num_results;
    volatile int _result;
    volatile int * _results;
    byte _index_result;
    RotaryEncoderLimits * _limits;
         
    boolean _a;
    boolean _b;
  public:
    RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){   //parametro do botao opcional
      _pin_clk = pin_clk;
      _pin_dt = pin_dt;
      _pin_sw = pin_sw;
      pinMode(_pin_clk, INPUT);
      pinMode(_pin_dt, INPUT);
      if (_pin_sw != ROTARY_NO_BUTTON){ 
        pinMode(_pin_sw, INPUT); 
        digitalWrite(_pin_sw, HIGH);
      }
      if (num_results == 0) { num_results = 1; }
      _num_results = num_results;
      _results = new int[_num_results];
      for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
      _index_result = 0;
      _limits = limits;
      _a = false;
      _b = false;
    }
    byte getIndex() { return _index_result; }
    void next()     { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } } 
    void update_a() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_clk) != _a ) { 
        _a = !_a;
        if ( _a && !_b ) { _result = -1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    void update_b() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_dt) != _b ) {  
        _b = !_b;
        if ( _b && !_a ) { _result = +1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    int read(){ return _result; }                                        //retorn -1, 0 ou 1.
    int getValue(int index=-1) {                                         //retorna o valor da variável corrente ou a passada como parametro
      if (index < 0 ){ return _results[_index_result]; }
      return _results[index];
    }
    void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
   
   
/*************************************************************************************************************
*******************************CLASSE RACE GAME LCD***********************************************************
**************************************************************************************************************/
/*
  0        1        2        3        4        5        6
 ***                        ***      ***               ***                 
 ***                        ***      ***               ***          
                                                             
 ***      ***               ***               ***             
 ***      ***               ***               ***             
                                                               
 ***      ***      ***               ***                        
 ***      ***      ***               ***                            
*/
byte c0[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c1[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c2[8] = {B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c3[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c4[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c5[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c6[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B00000,  B00000 };
 
class RaceGameLCD {
  private:
    LiquidCrystal * _lcd;  //ponteiro para um objeto lcd
  public:
    void createChars() { 
      _lcd->createChar(0, c0);  
      _lcd->createChar(1, c1);  
      _lcd->createChar(2, c2); 
      _lcd->createChar(3, c3);  
      _lcd->createChar(4, c4);
      _lcd->createChar(5, c5);
      _lcd->createChar(6, c6);
    }
    RaceGameLCD(LiquidCrystal * lcd) {  _lcd = lcd;  }
     
    void write(byte col, byte row, byte val){
      _lcd->setCursor(col, row);
      if (val == B000) { _lcd->print(" ");  }
      if (val == B111) { _lcd->write((byte)0);  }
      if (val == B011) { _lcd->write((byte)1);  }
      if (val == B001) { _lcd->write((byte)2);  }
      if (val == B110) { _lcd->write((byte)3);  }
      if (val == B101) { _lcd->write((byte)4);  }
      if (val == B010) { _lcd->write((byte)5);  }
      if (val == B100) { _lcd->write((byte)6);  }
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE RACE LCD***********************************************************
**************************************************************************************************************/
 
  
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
 
/*
 
  tamanho display real
 
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
*/
 
const int LINHAS  = 6;
const int COLUNAS = 16;
  
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
RaceGameLCD raceGameLcd(&lcd);
  
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
RaceGame race(&ba);
 
RotaryEncoderLimits lim[] = { {-1000,1000} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 1, lim);  //pino clk, pino dt, pino sw, variaveis, limites
 
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
   
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
  volatile static byte lastVal_a0 = LOW;
  volatile static byte lastVal_a1 = LOW;
  byte val_a0 = digitalRead(A0);
  byte val_a1 = digitalRead(A1);
  if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
  if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
  
void setup_interrupts(){
  //-----PCI - Pin Change Interrupt ----
  pinMode(A0,INPUT);   // set Pin as Input (default)
  digitalWrite(A0,HIGH);  // enable pullup resistor
  pinMode(A1,INPUT);   // set Pin as Input (default)
  digitalWrite(A1,HIGH);  // enable pullup resistor
  cli();
  PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
  PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
  sei();
}
  
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
    
void update_display() {
  for (int col=0; col<ba.columns(); col++) {
    byte lin_lcd = 0;
    byte cont = 0;
    byte val = 0;
     
    for (int lin=0; lin<ba.rows(); lin++) {
      if (cont == 0){ val = 0; }
      val = val << 1; 
      val = val | ba.read(lin, col);
      cont++;
      if (cont == 3) {
        raceGameLcd.write(col, lin_lcd, val) ;
        cont = 0;
        lin_lcd++;
      }
    }
  }
}
    
void setup() { 
  setup_interrupts();
  lcd.begin(16, 2);
  raceGameLcd.createChars();
  randomSeed(analogRead(A2));
  re.setValue(0, 0);  //inicializa o rotary em 0
}
    
void loop() {
  static int val_encoder = 0;
  static boolean change = false;
   
  if (re.getValue(0) < val_encoder && !change){ 
    race.bottom();
     
    change = true;
  }
   
  if (re.getValue(0) > val_encoder && !change ){
    race.top();
     
    change = true;
  }  
   
  val_encoder = re.getValue(0);
  if ( race.update() ) { 
    change = false;
    update_display(); 
  }  
   
  //controla o click do botao do enconder
  static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
  if( re.buttonRead() == LOW && b != re.buttonRead() ) {
    re.next();           //passa para a próxima variável (index)
    delay(200);          //debounce meia boca
  }
  b = re.buttonRead();
} 

terça-feira, 31 de maio de 2016

Arduino - Snake Game com display LCD e Rotary Encoder

Minha terceira versão do jogo Snake, agora com um display lcd controlado por um rotary encoder. As versões anteriores foram uma em matriz de led e outra em um display 5110. Como o programa foi desenvolvido Orientado a Objeto, foi fácil reaproveitar o código, já que a classe principal do jogo não se alterou.

Versão com matriz de led: http://fabianoallex.blogspot.com.br/2015/10/arduino-snake-game-jogo-da-cobrinha.html

versão com display 5110: http://fabianoallex.blogspot.com.br/2016/01/arduino-display-lcd-nokia-5110.html


Vídeo:


Código-fonte:

/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
   
#include <LiquidCrystal.h>

/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray{
  private:
    int _num_bits;   //quantidade de bits a serem gerenciados
    int _num_bytes;  //quantidade de bytes utilizados para armazenar os bits a serem gerenciados
    byte * _bytes;   //array de bytes onde estarão armazenados os bits
  public:
    BitArray(int num_bits){
      _num_bits  = num_bits;
      _num_bytes = _num_bits/8 + (_num_bits%8 ? 1 : 0) + 1;
      _bytes = (byte *)(malloc( _num_bytes * sizeof(byte) ) );
    }
    void write(int index, byte value) {
      byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
      unsigned int bit = index%8;
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
      _bytes[ index/8 + (index%8 ? 1 : 0) ] = b;
    }
    void write(byte value) { for(int j=0; j<_num_bytes;j++) { _bytes[j] = value ? B11111111 : B00000000;  }  }
    int read(int index) {
      byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
      unsigned int bit = index%8;
      return (b & (1 << bit)) != 0;
    }
    ~BitArray(){ free ( _bytes ); }
};

class BitArray2D {
  private:
    unsigned int _rows;
    unsigned int _columns;
    unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado 
    byte**       _bits;
  public:
    BitArray2D(unsigned int rows, unsigned int columns){
      _rows       = rows;
      _columns    = columns;
      _cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
      _bits = (byte **)malloc(_rows * sizeof(byte *));
      for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc(  _cols_array  *  sizeof(byte)); } //cria varios arrays
      clear();
    }
    unsigned int rows(){ return _rows; }
    unsigned int columns(){ return _columns; }
     
    void clear() { 
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }       
      }   
    }
   
    void write(unsigned int row, unsigned int column, int value){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;
       
      if (value) { b |= (1 << bit); } else { b &= ~(1 << bit);  }
       
      _bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
    }
     
    void write(byte value){
      for(int i=0;i<_rows;i++){      
        for(int j=0; j<_cols_array;j++) {      
          _bits[i][j] = value ? B11111111 : B00000000;     
        }       
      }  
    }
     
    int read(unsigned int row, unsigned int column){
      byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
      unsigned int bit = column%8;
       
      return (b & (1 << bit)) != 0;
    }
     
    void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
    void toggle(){ for(int i=0;i<_rows;i++){      for(int j=0; j<_columns;j++) {      toggle(i,j);   }   }   }
};
 
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/

/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
  
class UniqueRandom{
  private:
    int _index;
    int _min;
    int _max;
    int _size;
    int* _list;
    void _init(int min, int max) {
      _list = 0; 
      if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
      _size = _max - _min; 
      _index = 0;
    }    
  public:
    UniqueRandom(int max)           { _init(0,   max); randomize(); } //construtor com 1 parametro
    UniqueRandom(int min, int max)  { _init(min, max); randomize(); } //construtor com 2 parametros
      
    void randomize() {
      _index = 0;
       
      if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }  
      for (int i=0; i<size(); i++) {   _list[i] = _min+i;  }   //preenche a lista do menor ao maior valor
        
      //embaralha a lista
      for (int i=0; i<size(); i++) {  
        int r = random(0, size());     //sorteia uma posição qualquer
        int aux = _list[i];               
        _list[i] = _list[r];
        _list[r] = aux;
      }
    }
      
    int next() {                                  //retorna o proximo numero da lista
      int n = _list[_index++];
      if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
      return n;
    }
      
    int size() { return _size; }
      
    ~UniqueRandom(){ free ( _list ); }  //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/

/*************************************************************************************************************
*******************************CLASSE SNAKE GAME**************************************************************
**************************************************************************************************************/
struct Position {
  int lin;
  int col;
};

const int SNAKE_MAX_LEN   = 30;  //tamanho maximo da cobra
const int SNAKE_TIME_INIT = 500; //tempo entre deslocamento da cobra (velocidade)
const int SNAKE_TIME_INC  = 15;  //incremento da velocidade
 
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum SnakeStatus { SNAKE_GAME_ON, SNAKE_GAME_OVER };
 
class SnakeGame{
  private:
    BitArray2D * _display;
    Position _snake_positions[SNAKE_MAX_LEN];
    Position _apple;
    int _length;
    Direction _direction;
    unsigned long _last_millis;
    int _time;
    int _score;
    SnakeStatus _snakeStatus;
     
    UniqueRandom * _ur;  //utilizado no game over
     
    void _generateApple() {
      int lin, col;
      boolean random_ok = false;
       
      while (!random_ok) {
        random_ok = true;
        lin = random(0, _display->rows()-1);
        col = random(0, _display->columns()-1);
         
        for (int p=0; p<_length; p++){
          if (_snake_positions[p].col==col && _snake_positions[p].lin==lin){ //verifica se gerou em um local que não seja a cobra
            random_ok = false;
            break;
          }
        }
      }
      _apple.lin = lin;
      _apple.col = col;
    }
     
    void _gameOver(){ 
      _snakeStatus = SNAKE_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
     
    void _inc_length(){
      _length++; _score++;
      _time -= SNAKE_TIME_INC;
    }
     
    void _runGameOver(){
      int r = _ur->next();
      int lin = (r / _display->columns());
      int col = (r % _display->columns());
       
      _display->write(lin, col, HIGH );
       
      //if ( r>=(_ur->size()-1) || _direction != DIR_STOP ) {  
      if ( r>=(_ur->size()-1) ) {  
        _ur->randomize();
        start(); 
      }
    }
     
    void _run(){
      for (int i=_length-1; i>0; i--){
        _snake_positions[i].lin = _snake_positions[i-1].lin;
        _snake_positions[i].col = _snake_positions[i-1].col;
      }
       
      if (_direction == DIR_TOP )    { _snake_positions[0].lin--;  }
      if (_direction == DIR_BOTTOM ) { _snake_positions[0].lin++;  }
      if (_direction == DIR_LEFT )   { _snake_positions[0].col--;  }
      if (_direction == DIR_RIGHT )  { _snake_positions[0].col++;  }
       
      //verifica se ultrapassou o limite do display
      if (_snake_positions[0].lin < 0)                     { _gameOver(); }
      if (_snake_positions[0].lin >= _display->rows() )    { _gameOver(); }
      if (_snake_positions[0].col < 0)                     { _gameOver(); }
      if (_snake_positions[0].col >= _display->columns() ) { _gameOver(); }
       
      //verifica se colidiu na cobra
      for (int i=_length-1; i>0; i--){
        if (_snake_positions[i].lin == _snake_positions[0].lin && _snake_positions[i].col == _snake_positions[0].col) {
          _gameOver();
        }  
      }
       
      //verifica se comeu a maça
      if (_snake_positions[0].col == _apple.col && _snake_positions[0].lin == _apple.lin){
        _inc_length();
         
        if (_length > SNAKE_MAX_LEN) { _length = SNAKE_MAX_LEN; } else {
          _snake_positions[_length-1].lin = _snake_positions[_length-2].lin;
          _snake_positions[_length-1].col = _snake_positions[_length-2].col;
        }
        _generateApple();
      }
       
      //update display
      for (int lin=0; lin<_display->rows(); lin++) {
        for (int col=0; col<_display->columns(); col++) {
          for (int p=0; p<_length; p++){
            boolean val = _snake_positions[p].col==col && _snake_positions[p].lin==lin;
            _display->write( lin, col,  val );
            if (val) {break;}
          }
        }
      }
      _display->write(_apple.lin, _apple.col, HIGH);
      //--
    }
     
  public:
    SnakeGame(BitArray2D * display){ 
      _display = display;
      _ur = new UniqueRandom( _display->rows() * _display->columns() );
      start();
    }
     
    void start(){
      _length = 1;
      _score  = 0;
      _time = SNAKE_TIME_INIT;
      _last_millis = 0;
      _snake_positions[0].lin = _display->rows() / 2;
      _snake_positions[0].col = _display->columns() / 2;
      _direction = DIR_STOP;
       
      _snakeStatus = SNAKE_GAME_ON;
       
      _generateApple();
    }
     
    void left()   { if (_direction == DIR_RIGHT)  return; _direction = DIR_LEFT;   }
    void right()  { if (_direction == DIR_LEFT)   return; _direction = DIR_RIGHT;  }
    void top()    { if (_direction == DIR_BOTTOM) return; _direction = DIR_TOP;    }
    void bottom() { if (_direction == DIR_TOP)    return; _direction = DIR_BOTTOM; }
     
    int getScore(){ return _score; }
    
    Direction getDirection(){ return _direction; }
     
    int update(){
      int r = false;
       
      if (millis() - _last_millis > _time) {
        r = true;
        _last_millis = millis();
       
        if (_snakeStatus == SNAKE_GAME_ON)   { _run();         } 
        if (_snakeStatus == SNAKE_GAME_OVER) { _runGameOver(); }
      }
       
      return r; //r-->indica se houve mudança no display
    }
};
 
/*************************************************************************************************************
*******************************FIM CLASSE SNAKE GAME**********************************************************
**************************************************************************************************************/
 
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON     255
    
struct RotaryEncoderLimits{
  int min;
  int max;
};
    
class RotaryEncoder {
  private:
    byte _pin_clk;
    byte _pin_dt;
    byte _pin_sw;
    volatile byte _num_results;
    volatile int _result;
    volatile int * _results;
    byte _index_result;
    RotaryEncoderLimits * _limits;
        
    boolean _a;
    boolean _b;
  public:
    RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){   //parametro do botao opcional
      _pin_clk = pin_clk;
      _pin_dt = pin_dt;
      _pin_sw = pin_sw;
      pinMode(_pin_clk, INPUT);
      pinMode(_pin_dt, INPUT);
      if (_pin_sw != ROTARY_NO_BUTTON){ 
        pinMode(_pin_sw, INPUT); 
        digitalWrite(_pin_sw, HIGH);
      }
      if (num_results == 0) { num_results = 1; }
      _num_results = num_results;
      _results = new int[_num_results];
      for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
      _index_result = 0;
      _limits = limits;
      _a = false;
      _b = false;
    }
    byte getIndex() { return _index_result; }
    void next()     { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } } 
    void update_a() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_clk) != _a ) { 
        _a = !_a;
        if ( _a && !_b ) { _result = -1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    void update_b() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_dt) != _b ) {  
        _b = !_b;
        if ( _b && !_a ) { _result = +1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    int read(){ return _result; }                                        //retorn -1, 0 ou 1.
    int getValue(int index=-1) {                                         //retorna o valor da variável corrente ou a passada como parametro
      if (index < 0 ){ return _results[_index_result]; }
      return _results[index];
    }
    void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
  
  
/*************************************************************************************************************
*******************************CLASSE SNAKE LCD***************************************************************
**************************************************************************************************************/
/*
  0        1        2        3        4        5        6
 ***                        ***      ***               ***                 
 ***                        ***      ***               ***          
                                                            
 ***      ***               ***               ***             
 ***      ***               ***               ***             
                                                              
 ***      ***      ***               ***                        
 ***      ***      ***               ***                            
*/
byte c0[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c1[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B11111,  B11111 };
byte c2[8] = {B00000,  B00000,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c3[8] = {B11111,  B11111,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c4[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B11111,  B11111 };
byte c5[8] = {B00000,  B00000,  B00000,  B11111,  B11111,  B00000,  B00000,  B00000 };
byte c6[8] = {B11111,  B11111,  B00000,  B00000,  B00000,  B00000,  B00000,  B00000 };

class SnakeLCD {
  private:
    LiquidCrystal * _lcd;  //ponteiro para um objeto lcd
  public:
    void createChars() { 
      _lcd->createChar(0, c0);  
      _lcd->createChar(1, c1);  
      _lcd->createChar(2, c2); 
      _lcd->createChar(3, c3);  
      _lcd->createChar(4, c4);
      _lcd->createChar(5, c5);
      _lcd->createChar(6, c6);
    }
    SnakeLCD(LiquidCrystal * lcd) {  _lcd = lcd;  }
    
    void write(byte col, byte row, byte val){
      _lcd->setCursor(col, row);
      if (val == B000) { _lcd->print(" ");  }
      if (val == B111) { _lcd->write((byte)0);  }
      if (val == B011) { _lcd->write((byte)1);  }
      if (val == B001) { _lcd->write((byte)2);  }
      if (val == B110) { _lcd->write((byte)3);  }
      if (val == B101) { _lcd->write((byte)4);  }
      if (val == B010) { _lcd->write((byte)5);  }
      if (val == B100) { _lcd->write((byte)6);  }
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE SNAKE LCD***********************************************************
**************************************************************************************************************/

 
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/

/*

  tamanho display real

 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * *
*/

const int LINHAS  = 6;
const int COLUNAS = 16;
 
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
SnakeLCD snakeLcd(&lcd);
 
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
SnakeGame snake(&ba);

RotaryEncoderLimits lim[] = { {-1000,1000} };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 1, lim);  //pino clk, pino dt, pino sw, variaveis, limites

/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
  
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
  volatile static byte lastVal_a0 = LOW;
  volatile static byte lastVal_a1 = LOW;
  byte val_a0 = digitalRead(A0);
  byte val_a1 = digitalRead(A1);
  if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
  if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
 
void setup_interrupts(){
  //-----PCI - Pin Change Interrupt ----
  pinMode(A0,INPUT);   // set Pin as Input (default)
  digitalWrite(A0,HIGH);  // enable pullup resistor
  pinMode(A1,INPUT);   // set Pin as Input (default)
  digitalWrite(A1,HIGH);  // enable pullup resistor
  cli();
  PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
  PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
  sei();
}
 
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
   
void update_display() {
  for (int col=0; col<ba.columns(); col++) {
    byte lin_lcd = 0;
    byte cont = 0;
    byte val = 0;
    
    for (int lin=0; lin<ba.rows(); lin++) {
      if (cont == 0){ val = 0; }
      val = val << 1; 
      val = val | ba.read(lin, col);
      cont++;
      if (cont == 3) {
        snakeLcd.write(col, lin_lcd, val) ;
        cont = 0;
        lin_lcd++;
      }
    }
  }
}
   
void setup() { 
  Serial.begin(9600);
  setup_interrupts();
  lcd.begin(16, 2);
  snakeLcd.createChars();
  randomSeed(analogRead(A2));
  re.setValue(0, 0);  //inicializa o rotary em 0
}
   
void loop() {
  static int val_encoder = 0;
  static boolean change = false;
  
  if (re.getValue(0) < val_encoder && !change){ 
    if (snake.getDirection() == DIR_STOP)  {snake.right(); } else
    if (snake.getDirection() == DIR_TOP) { snake.left();   } else
    if (snake.getDirection() == DIR_RIGHT) { snake.top();  } else
    if (snake.getDirection() == DIR_LEFT) { snake.bottom();  } else
    if (snake.getDirection() == DIR_BOTTOM) { snake.right();   } 
    
    change = true;
  }
  
  if (re.getValue(0) > val_encoder && !change ){
    if (snake.getDirection() == DIR_STOP)  {snake.right();   }  else
    if (snake.getDirection() == DIR_TOP) { snake.right();    } else
    if (snake.getDirection() == DIR_RIGHT) { snake.bottom(); } else
    if (snake.getDirection() == DIR_LEFT) { snake.top();     } else
    if (snake.getDirection() == DIR_BOTTOM) { snake.left();  } 
    
    change = true;
  }  
  
  val_encoder = re.getValue(0);
  if ( snake.update() ) { 
    change = false;
    update_display(); 
    Serial.println(val_encoder);
  }  
  
  //controla o click do botao do enconder
  static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
  if( re.buttonRead() == LOW && b != re.buttonRead() ) {
    re.next();           //passa para a próxima variável (index)
    delay(200);          //debounce meia boca
  }
  b = re.buttonRead();
}


quarta-feira, 25 de maio de 2016

Arduino - Entrada de Texto com Rotary Encoder

Em algumas aplicações, temos eventualmente a necessidade de que o usuário digite algum texto. A maneira mais comum de se fazer isso é utilizando um teclado. Mas nem sempre essa é uma alternativa viável. Algum tempo atrás mostrei como digitar texto com teclado matricial numérico, como pode ser visto nesse link:  http://fabianoallex.blogspot.com.br/2015/05/arduino-teclado-4x3-texto-lcd.html e nesse outro, uma versão parecida, mas utilizando um controle remoto: http://fabianoallex.blogspot.com.br/2015/05/arduino-digitar-texto-com-controle.html. Porém, as vezes, nem uma dessas soluções é a mais viável de se utilizar, caso a entrada de dados seja algo muito eventual, ou ainda quando queremos algo que não ocupe tanto espaço.

Pensando nisso, aproveitei que estava com a mão na massa com o meu último artigo sobre Rotary Encoders, e desenvolvi uma classe pra escrever texto em um display LCD através de um Rotary Encoder.

Para maiores detalhes sobre o Rotary Encoder veja o artigo completo aqui: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html

Vídeo:



Código-Fonte:

/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
  
#include <LiquidCrystal.h>

/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
*************************************************************************************************************/
#define ROTARY_NO_BUTTON     255
   
struct RotaryEncoderLimits{
  int min;
  int max;
};
   
class RotaryEncoder {
  private:
    byte _pin_clk;
    byte _pin_dt;
    byte _pin_sw;
    volatile byte _num_results;
    volatile int _result;
    volatile int * _results;
    byte _index_result;
    RotaryEncoderLimits * _limits;
       
    boolean _a;
    boolean _b;
  public:
    RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){   //parametro do botao opcional
      _pin_clk = pin_clk;
      _pin_dt = pin_dt;
      _pin_sw = pin_sw;
      pinMode(_pin_clk, INPUT);
      pinMode(_pin_dt, INPUT);
      if (_pin_sw != ROTARY_NO_BUTTON){ 
        pinMode(_pin_sw, INPUT); 
        digitalWrite(_pin_sw, HIGH);
      }
      if (num_results == 0) { num_results = 1; }
      _num_results = num_results;
      _results = new int[_num_results];
      for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
      _index_result = 0;
      _limits = limits;
      _a = false;
      _b = false;
    }
    byte getIndex() { return _index_result; }
    void next()     { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } } 
    void update_a() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_clk) != _a ) { 
        _a = !_a;
        if ( _a && !_b ) { _result = -1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    void update_b() {
      _result = 0;
      delay (1);
      if( digitalRead(_pin_dt) != _b ) {  
        _b = !_b;
        if ( _b && !_a ) { _result = +1; }
      }
      if (_results[_index_result]+_result >= _limits[_index_result].min && 
          _results[_index_result]+_result <= _limits[_index_result].max ) {
        _results[_index_result] += _result;
      }
    }
    int read(){ return _result; }                                        //retorn -1, 0 ou 1.
    int getValue(int index=-1) {                                         //retorna o valor da variável corrente ou a passada como parametro
      if (index < 0 ){ return _results[_index_result]; }
      return _results[index];
    }
    void setValue(int value)           { _results[_index_result] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    void setValue(int index, int value){ _results[index] = value; }         //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
    int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/

/*************************************************************************************************************
************************************CLASSE ROTARY KEYBOARD****************************************************
*************************************************************************************************************/
const char teclas[] = {"abcdefghijklmnopqrstuvxwyz 0123456789"};  //caracteres a serem escolhidos
const unsigned long time_char = 1200;  //1200 milissegundos pra desconsiderar a ultima tecla
 
class RotaryKeyBoard {
  private:
    unsigned long   _millis_last_char;
    char            _last_char;
    String          _palavra;
    RotaryEncoder * _re;
    
    void _set_last_char(char c, int ind_palavra){
      if ( ind_palavra == 1 && _last_char != '\0' ) { _palavra += _last_char; }
      _last_char = c;
      _millis_last_char = millis();
    }    
    void _add(char c) { 
      if ( is_timing() ) {
        _set_last_char(  (teclas[_re->getValue(0)] == '\0') ? _last_char = teclas[0] : _last_char = teclas[_re->getValue(0)] , 0 );
        return ;
      }
      _set_last_char (c, 1); 
    }
  public:
    RotaryKeyBoard(RotaryEncoder * re){
      _re = re;
      _millis_last_char = millis();
      _last_char = '\0';
      _palavra = "";
      update();
      backspace();
      _re->setValue(0, 0);
    }
    
    char get_last_char() { return _last_char; }
    int is_timing()      { return ( (millis() - time_char) < _millis_last_char );  }
    String get_palavra() { return (_last_char) ? _palavra + _last_char : _palavra; }
    
    void backspace(){
      if (_palavra.length() >= 1){
        _last_char = _palavra[_palavra.length()-1];
        _palavra   = _palavra.substring(0, _palavra.length()-1);
      } else {
        _last_char = '\0';
      }
    }
    void update() {
      static char tecla_anterior = '\0';
      char tecla = teclas[_re->getValue(0)];
      if (tecla != tecla_anterior){
        if (tecla) { _add(tecla); }
      }
      tecla_anterior = tecla;
    }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY KEYBOARD************************************************
*************************************************************************************************************/

/*************************************************************************************************************
************************************DECLARACOES DOS OBJETOS***************************************************
*************************************************************************************************************/
LiquidCrystal       lcd(12, 11, 10, 9, 8, 7);
RotaryEncoderLimits lim[] = { {0, sizeof(teclas)-1 }  };  //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder       re(A0, A1, 4, 1, lim);  //pino clk, pino dt, pino sw, variaveis
RotaryKeyBoard      teclado(&re);
/*************************************************************************************************************
************************************FIM DECLARACOES DOS OBJETOS***********************************************
*************************************************************************************************************/
 
/*************************************************************************************************************
************************************TRATAMENTO DAS INTERRUPÇÕES***********************************************
*************************************************************************************************************/
ISR(PCINT1_vect) {
  volatile static byte lastVal_a0 = LOW;
  volatile static byte lastVal_a1 = LOW;
  byte val_a0 = digitalRead(A0);
  byte val_a1 = digitalRead(A1);
  if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
  if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}

void setup_interrupts(){
  pinMode(A0,INPUT);   // set Pin as Input (default)
  digitalWrite(A0,HIGH);  // enable pullup resistor
  pinMode(A1,INPUT);   // set Pin as Input (default)
  digitalWrite(A1,HIGH);  // enable pullup resistor
  cli();
  PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
  PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
  sei();
}
/*************************************************************************************************************
************************************FIM TRATAMENTO DAS INTERRUPÇÕES*******************************************
*************************************************************************************************************/

/*************************************************************************************************************
************************************SETUP / LOOP**************************************************************
*************************************************************************************************************/
void setup() {
  setup_interrupts();
  lcd.begin(16, 2);
  Serial.begin(9600);
}
 
void loop() {
  teclado.update();
  
  lcd.clear();
  lcd.setCursor(0, 0); 
  lcd.print(teclado.get_palavra());
  lcd.cursor();
  lcd.setCursor(teclado.get_palavra().length() - (teclado.is_timing() ? 1 : 0 ), 0);
  
  //controla o click do botao do enconder
  static byte b = HIGH;                                           //pra ler apenas uma vez o botao ao pressionar
  if( re.buttonRead() == LOW && b != re.buttonRead() ) {
    teclado.backspace();  
    delay(200);
  }
  b = re.buttonRead();
  
  delay(100);
}
/*************************************************************************************************************
************************************FIM SETUP / LOOP**************************************************************
*************************************************************************************************************/