Publicidade:

sexta-feira, 19 de agosto de 2016

Arduino - Tetris com display LCD 20x4 e Joystick

Em junho postei duas implementações do jogo Tetris (veja aqui), uma com display de leds e outra com display LCD 16x2. Em nenhuma o tamanho original do jogo foi utilizado (20 linhas e 10 colunas), por limitações dos componentes. Mas a ideia não era reproduzir o jogo em seu formato 100% original e sim, criar algo mais por diversão e pra servir de exemplo pra quem gosta de programar.

Mas essa semana chegou um display LCD 20x4 e um módulo I2C para o display que comprei há algum tempo e pensei que poderia criar uma versão do tetris pra ele também. Basicamente o que era preciso, era apenas substituir a biblioteca LCD pela biblioteca LCD I2C.

Através da própria IDE do Arduino (versão 1.6.10) procurei e instalei a biblioteca para display LCD I2C,  depois fiz uma cópia da sketch que tinha usado no jogo com o display 16x2 e fiz algumas pequenas alterações no código aumentando a quantidade de linhas e colunas do jogo. O Resultado foi que ficou extremamente lenta a atualização dos dados no display LCD. Fiquei um pouco frustrado, achei que não iria dar certo utilizando o display 20x4. Mas resolvi dar uma olhada na biblioteca I2C, pra ver como era a comunicação entre o Arduino e o display.

A biblioteca do display I2C utiliza a biblioteca Wire para o envio dos dados para o display e analisando melhor o código da biblioteca, de fato havia algumas coisas que poderiam ser melhoradas para que ela ficasse mais rápida. Pois a biblioteca, para cada caractere enviado para o display, inicia uma transmissão, envia o dado e finaliza a transmissão, isso significa que se 20 caracteres forem enviados para o display, serão iniciadas e finalizadas 20 transmissões, algo que não seria necessário, pois a biblioteca Wire conta com um buffer que pode armazenar vários dados, e depois de finalizado, os dados podem ser enviados todos dentro de uma mesma transmissão, economizando assim, chamadas desnecessárias. Para mais de 90% das aplicações que utilizam um display LCD com certeza isso não é nenhum problema, mas no caso do jogo, as atualizações da tela são executadas a uma taxa bem alta, faz muita diferença.

Então eu tinha duas opções, criar uma nova biblioteca, baseada na biblioteca I2C ou, na própria sketch criar uma classe que herdasse da classe original, e nela fizesse as alterações necessárias. Fiquei com a segunda opção, pois não queria criar e manter uma biblioteca a parte. Porém, foi necessário fazer algumas alterações na biblioteca original para que eu pudesse sobrescrever o método que fazia o envio de dados (tornar o método virtual) e também precisei alterar a visibilidade de alguns atributos da classe, para que as mesmas ao invés de serem private passassem a ser protected. Essas alterações foram necessárias apenas no Header (arquivo .h) da biblioteca. Com isso, na própria sketch criei uma nova classe herdada da classe original, e fiz os devidos tratamentos.

Nessa nova classe foram implementados dois métodos, startWrite() e sendAll(). Com isso, agora, antes de enviar os dados para o display eu chamo startWrite e começo a enviar os dados para o display, que na verdade são mantidos no buffer, e somente após eu chamar sendAll, os dados são de fatos enviados para o display. (Na verdade, se durante o envio o buffer ficar cheio, a classe envia os dados para o display).

Depois de tudo pronto, fiz os testes. Melhorou bastante, mas ainda não estava satisfatório o resultado, então resolvi analisar o que eu poderia alterar no código do próprio jogo, e vi que eu fiz, para que a cada caractere enviado para o display estava sendo posicionado onde deveria ser escrito (através de setCursor(...) ). Porém se os dados são enviados dentro de uma mesma linha, não há a necessidade de chamar setCursor para cada escrita, pois o próprio display, ao receber o comando de escrita de um caractere, já posiciona o cursor na próxima posição. Com isso eliminei, várias chamadas desnecessárias.

Testei novamente e o resultado agora foi bem mais satisfatório, sendo os dados atualizados numa velocidade aceitável.

Outra alteração, foi que agora implementei o jogo com um joystick, onde antes eu utilizava um rotary encoder.

Vídeo



Código-fonte

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

/*************************************************************************************************************
*******************************CLASSE DERIVADA DE LiquidCrystal_I2C*******************************************
**************************************************************************************************************
*essa classe foi criada para ser mais rápida na hora de enviar os dados para o display através da biblioteca 
*wire, utilizando o buffer, requerendo assim, menos chamadas a biblioteca wire. a biblioteca original, a cada
*chamada para write estava iniciando uma transmissão, enviando o dado e em seguida finalizando a transmissão, 
*mesmo que várias informações estivessem sendo enviadas sequencialmente. sendo desnecessario o envio de 
*cada informação dentro uma transmissao separada. as alterações foram justamente pra utilizar o buffer 
*que a biblioteca wire disponibiliza.
*
*foram incluídos os métodos startWrite e sendAll. responsáveis pelo inicio e fim da transmissao. se durante o 
*envio dos dados o buffer ficar totalmente cheio, o fim da transmissao é automaticamente chamado e em seguida
*reiniciada outra transmissao. 
*
*caso esses métodos não forem chamados durante o uso da classe, os dados são enviados
*a cada escrita dentro de uma transmissao pra cada escrita, sem utilizar o buffer, como é exatamente o 
*comportamento da biblioteca original.
*
*foi necessário fazer algumas alterações na biblioteca original, no arquivo header (LiquidCrystal_I2C.h).
*os métodos e membros abaixo, passaram a ser protected ao invés de private:
*protected:
  uint8_t _Addr;                         //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
  uint8_t _backlightval;                 //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
  void pulseEnable(uint8_t);             //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas 

 a função abaixo passou a ser virtual para que pudesse ser reescrita

 virtual void expanderWrite(uint8_t);      //alterado fabiano. definido como método virtual, para poder ser redefinido
**************************************************************************************************************/
class LiquidCrystal_I2C_ : public LiquidCrystal_I2C {
  private:
    uint8_t _startWrite;
    uint8_t _countByte;
    void write4bits(uint8_t value) { pulseEnable(value); }
    void expanderWrite(uint8_t _data){
      if (_startWrite == 0) {   Wire.beginTransmission(_Addr);  } else { 
        _countByte += sizeof(int); 
        if (_countByte > 64 ) {                    //32 é o tamanho do buffer definido em BUFFER_LENGTH ............ fiz o teste com 64 bytes e funcionou, com valor maior ocorreu erros, caso der algum erro de envio, voltar para o valor BUFFER_LENGHT no lugar do 64
          sendAll();
          startWrite();
          _countByte += sizeof(int);
        }
      }
      Wire.write((int)(_data) | _backlightval);
      if (_startWrite == 0) {   Wire.endTransmission();   }
    }
  public:
    LiquidCrystal_I2C_(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows) : LiquidCrystal_I2C(lcd_Addr, lcd_cols, lcd_rows){
      _startWrite = 0;
      _countByte = 0;
    }
    inline size_t write(uint8_t value) {
      uint8_t highnib = value&0xf0;
      uint8_t lownib  = (value<<4)&0xf0;
      write4bits((highnib)|Rs);
      write4bits((lownib)|Rs); 
      return 1;
    }
    void startWrite() {
      _countByte = 0;
      _startWrite = 1;
      Wire.beginTransmission(_Addr); 
    }
    void sendAll(){
      _startWrite = 0;
      Wire.endTransmission();
    }
};
/*************************************************************************************************************
*******************************FIM CLASSE DERIVADA DE LiquidCrystal_I2C***************************************
**************************************************************************************************************/

/*************************************************************************************************************
************************************CLASSE JOYSTICK***********************************************************
*************************************************************************************************************/
class Joystick {
  private:
    boolean _isTop    : 1;
    boolean _isBottom : 1;
    boolean _isLeft   : 1;
    boolean _isRight  : 1;
    unsigned int _x   : 10;
    unsigned int _y   : 10;
    byte _pinX        : 8;
    byte _pinY        : 8;
    byte _pinZ        : 8;
  public:
    Joystick(int pinX, int pinY, int pinZ){
      _pinX = pinX;
      _pinY = pinY;
      _pinZ = pinZ;
      pinMode(pinX, INPUT);
      pinMode(pinY, INPUT);
      pinMode(pinZ, INPUT_PULLUP);
    }
    update() {
      _x = analogRead(_pinX);
      _y = analogRead(_pinY);
      _isTop    = (_x < 200);
      _isBottom = (_x > 800);
      _isRight  = (_y < 200);
      _isLeft   = (_y > 800);
    }
    boolean isTop()      { return _isTop; }
    boolean isBottom()   { return _isBottom; }
    boolean isLeft()     { return _isLeft; }
    boolean isRight()    { return _isRight; }
    unsigned int readX() { return _x; }
    unsigned int readY() { return _y; }
    byte buttonRead()    { return digitalRead(_pinZ); }
};
/*************************************************************************************************************
************************************FIM CLASSE CLASSE JOYSTICK************************************************
*************************************************************************************************************/
   
/*************************************************************************************************************
*******************************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**************************************************
*************************************************************************************************************/
     
/*************************************************************************************************************
*******************************CLASSES TETRIS GAME************************************************************
**************************************************************************************************************/
const int TETRIS_TIME_INIT = 700; //tempo entre deslocamento das peças
const int TETRIS_TIME_INC  = 20;  //incremento da velocidade
enum Direction        { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum TetrisGameStatus { TETRIS_GAME_ON, TETRIS_GAME_OVER };
enum TetrisBlockTypes { TETRIS_I, TETRIS_J, TETRIS_L, TETRIS_O, TETRIS_S, TETRIS_Z, TETRIS_T, TETRIS_COUNT_BTYPES};
struct Position       { int row, column; };
struct Block          { byte row0:4; byte row1:4; byte row2:4; byte row3:4; };  //4 bit cada linha
//blocos e suas rotacoes
Block block_i[] = { {B1111, B0000, B0000, B0000}, {B0001, B0001, B0001, B0001} };
Block block_j[] = { {B0000, B0010, B0010, B0011}, {B0000, B0000, B0001, B0111}, {B0000, B0110, B0010, B0010}, {B0000, B0000, B0111, B0100} };
Block block_l[] = { {B0000, B0010, B0010, B0110}, {B0000, B0000, B0100, B0111}, {B0000, B0011, B0010, B0010}, {B0000, B0000, B0111, B0001} };
Block block_o[] = { {B0000, B0000, B0110, B0110} };
Block block_s[] = { {B0000, B0001, B0011, B0010}, {B0000, B0000, B0110, B0011} };
Block block_z[] = { {B0000, B0000, B0110, B1100}, {B0000, B1000, B1100, B0100} };
Block block_t[] = { {B0000, B0000, B0010, B0111}, {B0000, B0010, B0011, B0010}, {B0000, B0000, B0111, B0010}, {B0000, B0001, B0011, B0001} };
class TetrisBlock{
  private:
    Block _block;       //bloco que vai descendo. player
    Position _position; //posição do bloco na tela. pode assumir posição negativa
    TetrisBlockTypes _blockType;
    byte _blockTypeRotation;
  public:
    TetrisBlock(){
      _blockType =  (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES);
    }
    Position getPosition()  { return _position; }
    byte getRow(int row) { 
      if (row == 0 ) return _block.row0;
      if (row == 1 ) return _block.row1;
      if (row == 2 ) return _block.row2;
      if (row == 3 ) return _block.row3; 
      return 0;
    }
    byte getColumn(int col) {
      byte b = 0;
      for (int i=0;i<4;i++){ bitWrite(b, 3-i, bitRead(getRow(i), 3-col) );  }
      return b;
    }
    int read(int row, int column){
      byte b = getRow(row);
      unsigned int bit = column%8;    
      return (b & (1 << bit)) != 0;
    }
    void left()   { _position.column--;  }
    void right()  { _position.column++;  }
    void bottom() { _position.row++;     }
    void top()    { _position.row--;     }
    TetrisBlockTypes getBlockType() { return _blockType; }
    void generateBlock(byte col, TetrisBlockTypes blockType = TETRIS_COUNT_BTYPES) {
      _position.row      = 0; 
      _position.column   = col; 
      _blockType         = (blockType == TETRIS_COUNT_BTYPES) ? (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES) : blockType;
      _blockTypeRotation = 0;
      if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
      if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
      if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
      if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
      if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
      if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
      if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
      if (!getRow(0))   { _position.row--;
        if (!getRow(1))   { _position.row--;          
          if (!getRow(2))   { _position.row--; } } }
    }
    void rotate(int direction = 1){
      int maxPos = 0;
      if ( _blockType == TETRIS_I ){ maxPos = sizeof(block_i)/sizeof(Block); }
      if ( _blockType == TETRIS_J ){ maxPos = sizeof(block_j)/sizeof(Block); }
      if ( _blockType == TETRIS_L ){ maxPos = sizeof(block_l)/sizeof(Block); }
      if ( _blockType == TETRIS_O ){ maxPos = sizeof(block_o)/sizeof(Block); }
      if ( _blockType == TETRIS_S ){ maxPos = sizeof(block_s)/sizeof(Block); }
      if ( _blockType == TETRIS_Z ){ maxPos = sizeof(block_z)/sizeof(Block); }
      if ( _blockType == TETRIS_T ){ maxPos = sizeof(block_t)/sizeof(Block); }
      (direction==1) ? _blockTypeRotation++ : _blockTypeRotation--;
      if (_blockTypeRotation == 255)    { _blockTypeRotation = maxPos-1; }
      if (_blockTypeRotation >= maxPos) { _blockTypeRotation = 0; }
      if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
      if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
      if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
      if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
      if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
      if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
      if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
    }
};
    
class TetrisGame {
  private:
    BitArray2D * _display;   //display com todos os objetos da tela. recebido como parâmetro
    BitArray2D * _recipient; //recepiente onde os blocos vão se acumulando. criado internamente pela classe
    TetrisBlock * _block;    //bloco controlado
    TetrisBlock * _blockNext;    //bloco que fica na parte superior, indicando o próximo bloco a ser controlado
    Direction _direction;
    TetrisGameStatus _tetrisGameStatus;
    unsigned long _last_millis;
    unsigned long _last_millis_player;
    int _time;
    int _score;
    byte _fast : 1;          //acelerar a descida
    UniqueRandom * _ur;      //utilizado no game over
    void _gameOver(){ 
      _tetrisGameStatus = TETRIS_GAME_OVER; 
      _direction   = DIR_STOP;
      _time = 20;
    }
    void _inc_speed(){
      _score++;
      _time -= TETRIS_TIME_INC;
    }
    void _start(){
      _recipient->clear();
      _last_millis = 0;
      _score = 0;
      _time = TETRIS_TIME_INIT;
      _fast = 0;
      _direction = DIR_STOP;
      _tetrisGameStatus = TETRIS_GAME_ON; 
      _blockNext->generateBlock( _display->columns() / 2 - 1);
      _newBlock();
    }
    void _newBlock() {
      _block->generateBlock( _display->columns() / 2 - 1, _blockNext->getBlockType() );
      _blockNext->generateBlock( _display->columns() / 2 - 1 );
      if (_testCollision()){ _gameOver(); }
    }
    void _updateDisplay() {
      if (_tetrisGameStatus == TETRIS_GAME_ON) { _display->clear();  }
      boolean indPrintNext = false;
      for (int r=0; r<4; r++) {
        for (int c=0; c<4; c++) { 
          if ( _block->read(r, c) ) { 
            _display->write(_block->getPosition().row+r, _block->getPosition().column+c,  HIGH ); 
            if (_block->getPosition().row > 2 ) { indPrintNext = true; }
          }
        }
      }
      if (indPrintNext){              //só imprime o próximo bloco, quando o bloco controlado já tiver descido um pouco
        for (int r=0; r<4; r++) {
          for (int c=0; c<4; c++) { 
            if ( _blockNext->read(r, c) ) { 
              _display->write(_blockNext->getPosition().row+r, _blockNext->getPosition().column+c,  HIGH ); 
            }
          }
        }
      }
      for (int row=0; row<_display->rows(); row++) {
        for (int col=0; col<_display->columns(); col++) {
          boolean val = _recipient->read(row, col);
          if (val) { _display->write(row, col,  val ); }
        }
      }
    }
    boolean _testCollision(){
      int col = _block->getPosition().column;  //coluna do bloco em relacao ao recepiente. pode ser negativa
      int row = _block->getPosition().row;     //linha do bloco em relacao ao recepiente. pode ser negativa
      for (byte c=0; c<4; c++){
        for (byte r=0; r<4; r++){
          if (! _block->read(r, c) )            { continue;   }  
          if ( row+r < 0 )                      { return true;}  
          if ( row+r >= _display->rows() )      { return true;}
          if ( col+c < 0 )                      { return true;}
          if ( col+c >= _display->columns() )   { return true;}
          if ( _recipient->read(row+r, col+c) ) { return true;}
        }
      }
      return false;
    }
    boolean _canLeft(){
      _block->left();
      boolean r = ! _testCollision();
      _block->right();
      return r;
    }
    boolean _canRight(){
      _block->right();
      boolean r = ! _testCollision();
      _block->left();
      return r;
    }
    boolean _canBottom(){
      _block->bottom();
      boolean r = ! _testCollision();
      _block->top();
      return r;
    }
    boolean _canRotate(){
      _block->rotate();
      boolean r = ! _testCollision();
      _block->rotate(-1);
      return r;
    }
    void _blockToRecipient(){
      for (byte c=0; c<4; c++){
        for (byte r=0; r<4; r++){
          if ( _block->read(r, c) ){ _recipient->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH); }
        }
      }
      _verifyCompleteRow();
      _newBlock();
    }
    void _verifyCompleteRow(){
      for (int row=0; row<_recipient->rows(); row++) {
        boolean complete = true;
        for (int col=0; col<_recipient->columns(); col++) {
          if (! _recipient->read(row, col) ) { complete = false; break; }
        }
        if (complete){ 
          _incScore(); 
          //move as linhas pra baixo
          for (int row2 = row; row2>=0; row2--) {
            for (int col2=0; col2<_recipient->columns(); col2++){
              _recipient->write(row2, col2,  (row2>0) ? _recipient->read(row2-1, col2) : 0 );
            }
          }
        }
      }
    }
    void _incScore(){
      _score++;
      _time -= TETRIS_TIME_INC;
    }
    void _runGameOver(){
      int r   = _ur->next();
      int row = (r / _display->columns());
      int col = (r % _display->columns());
      _display->write(row, col, HIGH );
      if ( _ur->getIndex() == (_ur->size()-1) ) {
        _ur->randomize();
        _ur->first();
        _start(); 
      }
    }
    void _runPlayer() {
      if (_direction == DIR_LEFT  && _canLeft()  ) { _block->left();  }
      if (_direction == DIR_RIGHT && _canRight() ) { _block->right(); }
      _direction = DIR_STOP;
    }
    void _runBlock() {
      _canBottom() ? _block->bottom() : _blockToRecipient() ;
      _direction = DIR_STOP; 
    }
  public:
    TetrisGame(BitArray2D * display){ 
      _display   = display;
      _recipient = new BitArray2D( _display->rows(), _display->columns() );
      _ur        = new UniqueRandom( _display->rows() * _display->columns() );
      _block     = new TetrisBlock();
      _blockNext = new TetrisBlock();
      _start();
    }
    void fast()   { _fast = 1; }  //acelerar o deslocamento
    void noFast() { _fast = 0; }  //descida do bloco na velocidade normal
    void left()   { _direction = DIR_LEFT;   }
    void right()  { _direction = DIR_RIGHT;  }
    void bottom() { _direction = DIR_BOTTOM; }
    void rotate() { if (_canRotate()) { _block->rotate(); } }
    int getScore(){ return _score; }
    Position getPosition() { return _block->getPosition(); }
    int update(){
      int r = false;
      if (millis() - _last_millis > ( (_fast==1) ? 50 : _time ) ) { 
        r = true;
        _last_millis = millis();  
        if (_tetrisGameStatus == TETRIS_GAME_ON)   { _runBlock(); } 
        if (_tetrisGameStatus == TETRIS_GAME_OVER) { _runGameOver(); }
      }
      if (millis() - _last_millis_player > 70 ) {  //atualiza os movimentos do player a cada 70ms
        r = true;
        _last_millis_player = millis();  
        if (_tetrisGameStatus == TETRIS_GAME_ON)   { _runPlayer(); } 
      }
      if (r) { _updateDisplay(); }
      return r; //r-->indica se houve mudança no display
    }
};
/*************************************************************************************************************
*******************************FIM CLASSES TETRIS 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********************************************************************
**************************************************************************************************************/

const int LINHAS  = 20;
const int COLUNAS = 10;

/*************************************************************************************************************
*******************************CLASSE GAME LCD****************************************************************
*classe auxiliar para imprimir o jogo no display lcd e criar os caracteres customizáveis
**************************************************************************************************************/
/*
  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:
    byte _col;
    byte _row;
    LiquidCrystal_I2C_ * _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_I2C_ * lcd) {  _lcd = lcd;  }
    void write(byte col, byte row, byte val){
      if ( ! (_row == row && _col == (col-1) && col < (LINHAS-1) ) ) {  _lcd->setCursor(col, row); }  //só seta o cursor, caso a posicao não seja a que está na sequencia
      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);  }
      _col = col;
      _row = row;
    }
    void startWrite(){ _lcd->startWrite(); }
    void sendAll()   { _lcd->sendAll();    }
};
/*************************************************************************************************************
*******************************FIM CLASSE GAME LCD***********************************************************
**************************************************************************************************************/
    
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
LiquidCrystal_I2C_ lcd(0x3F,20,4);  // set the LCD address to 0x3F for a 20 chars and 4 line display
GameLCD gameLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
TetrisGame tetris(&ba);
Joystick joystick(A0, A1, 4);
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
 
void update_display() {
  gameLcd.startWrite();
  int cols_lcd = (ba.columns() / 3 + (ba.columns()%3 ? 1 : 0));
  for(int col_lcd=0; col_lcd<cols_lcd; col_lcd++ ) {
    for (int lin=ba.rows()-1; lin>=0; lin--) {
      byte val = 0;
      for(byte i=0;i<3;i++) {
        byte leitura = (col_lcd*3+i >= ba.columns()) ? HIGH : ba.read(lin, col_lcd*3+i);  //se for a coluna maior que o tamnho do display, retorna como "parede"
        if (i == 0){ val = 0; }
        val = val << 1; 
        val = val | leitura;
        if (i == 2) { gameLcd.write(LINHAS-lin-1, col_lcd, val) ; }
      }
    }
  }
  gameLcd.sendAll();
}
 
void setup() { 
  lcd.init();
  lcd.backlight();
  gameLcd.createChars();
  randomSeed(analogRead(A2));
//  Serial.begin(9600);
}
         
void loop() {
  joystick.update();

  static byte lastLeft = 0;
  static unsigned long m_left = 0;
  byte left = joystick.isLeft();
  if( left && !lastLeft ) {    //borda de descida
    tetris.left();
    delay(70);
    m_left = millis();
  } else if (left) {
    if (millis() - m_left > 100) {
      tetris.left();
      m_left = millis();
    }
  }
  lastLeft = left;

  static byte lastRight = 0;
  static unsigned long m_right = 0;
  byte right = joystick.isRight();
  if( right && !lastRight ) {    //borda de descida
    tetris.right();
    delay(70);
    m_left = millis();
  } else if (right) {
    if (millis() - m_right > 100) {
      tetris.right();
      m_right = millis();
    }
  }
  lastRight = right;

  if ( tetris.update() ) {  update_display();  }  
        
  static byte lastb = HIGH; //leitura anterior do botão
  static unsigned long m_pressed = 0;  //millis na borda de subida do botão
    
  int b = joystick.buttonRead();
  if( !b && lastb ) {    //borda de subida
    m_pressed = millis();
    delay(70);
  }
  if( b && !lastb ) {    //borda de descida
    if (millis() - m_pressed < 350) {
      tetris.rotate();
      m_pressed = millis();
    }
  } 
  if( joystick.isBottom() ){
    tetris.fast(); 
  } else {
    tetris.noFast(); 
  }
  lastb = b;
} 





LiquidCristal_I2C.h - Alterações da biblioteca original


//YWROBOT
#ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h

#include <inttypes.h>
#include "Print.h" 
#include <Wire.h>

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00

#define En B00000100  // Enable bit
#define Rw B00000010  // Read/Write bit
#define Rs B00000001  // Register select bit

class LiquidCrystal_I2C : public Print {
public:
  LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
  void clear();
  void home();
  void noDisplay();
  void display();
  void noBlink();
  void blink();
  void noCursor();
  void cursor();
  void scrollDisplayLeft();
  void scrollDisplayRight();
  void printLeft();
  void printRight();
  void leftToRight();
  void rightToLeft();
  void shiftIncrement();
  void shiftDecrement();
  void noBacklight();
  void backlight();
  void autoscroll();
  void noAutoscroll(); 
  void createChar(uint8_t, uint8_t[]);
  void setCursor(uint8_t, uint8_t); 
#if defined(ARDUINO) && ARDUINO >= 100
  virtual size_t write(uint8_t);
#else
  virtual void write(uint8_t);
#endif
  void command(uint8_t);
  void init();

////compatibility API function aliases
void blink_on();      // alias for blink()
void blink_off();            // alias for noBlink()
void cursor_on();             // alias for cursor()
void cursor_off();           // alias for noCursor()
void setBacklight(uint8_t new_val);    // alias for backlight() and nobacklight()
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
void printstr(const char[]);

////Unsupported API functions (not implemented in this library)
uint8_t status();
void setContrast(uint8_t new_val);
uint8_t keypad();
void setDelay(int,int);
void on();
void off();
uint8_t init_bargraph(uint8_t graphtype);
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end);
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end);
  

private:
  void init_priv();
  void send(uint8_t, uint8_t);
  void write4bits(uint8_t);
  virtual void expanderWrite(uint8_t);      //alterado fabiano. definido como método virtual, para poder ser redefinido
  uint8_t _displayfunction;
  uint8_t _displaycontrol;
  uint8_t _displaymode;
  uint8_t _numlines;
  uint8_t _cols;
  uint8_t _rows;
protected:
  uint8_t _Addr;                         //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
  uint8_t _backlightval;                 //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
  void pulseEnable(uint8_t);             //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas 
};

#endif





Um comentário: