Essa é uma implementação para fins didáticos, voltada para programadores iniciantes (ou não).
Veja os demais vídeos de jogos para Arduino no canal:
Wall Defender - Versão display 8x8: https://youtu.be/LBVgnoU8u38
Wall Defender - Versão display LCD: https://youtu.be/7VWK050VXa4
Vídeo do jogo Snake: https://youtu.be/In1RD3-msJ8
Vídeo do jogo Race: https://youtu.be/lVmGilUGi8A
Inscreva-se no Canal: https://www.youtube.com/user/fabianoallex?sub_confirmation=1
Curta a página do facebook: https://www.facebook.com/dicasarduino
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*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSES TETRIS GAME************************************************************
**************************************************************************************************************/
const int TETRIS_TIME_INIT = 700; //tempo entre deslocamento dos inimigos
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:
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--; }
void generateBlock(byte col) {
_position.row = 0;
_position.column = col;
_blockType = (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES);
_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;
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;
_newBlock();
}
void _newBlock() {
_block->generateBlock( _display->columns() / 2 - 1 );
if (_testCollision()){ _gameOver(); }
}
void _updateDisplay() {
if (_tetrisGameStatus == TETRIS_GAME_ON) { _display->clear(); }
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 );
}
}
}
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();
_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********************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 16;
const int COLUNAS = 8;
Display displays_8x8[] = {
{1, {0,0}, BOTTOM},
{0, {8,0}, TOP}
};
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
TetrisGame tetris(&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.row;
int c = col - displays_8x8[i].position.column;
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() {
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;
if (re.getValue(0) < val_encoder && !change){ tetris.left(); change = true; } //sentido anti-horario move para esquerda
if (re.getValue(0) > val_encoder && !change){ tetris.right(); change = true; } //sentido horario move para direita
val_encoder = re.getValue(0);
if ( tetris.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
//clique curto --> rotate
//clique longo --> fast
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 = re.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( !b && millis()-m_pressed > 350){
tetris.fast();
} else {
tetris.noFast();
}
lastb = b;
}
Atualização 20/06/2016 - Versão em display LCD 16x2
Como já implementei alguns outros jogos em display lcd 16x2, não poderia deixar de fazer uma versão do tetris também.
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 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*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSES TETRIS GAME************************************************************
**************************************************************************************************************/
const int TETRIS_TIME_INIT = 700; //tempo entre deslocamento dos inimigos
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:
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--; }
void generateBlock(byte col) {
_position.row = 0;
_position.column = col;
_blockType = (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES);
_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;
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;
_newBlock();
}
void _newBlock() {
_block->generateBlock( _display->columns() / 2 - 1 );
if (_testCollision()){ _gameOver(); }
}
void _updateDisplay() {
if (_tetrisGameStatus == TETRIS_GAME_ON) { _display->clear(); }
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 );
}
}
}
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();
_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********************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************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 = 16;
const int COLUNAS = 6;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
GameLCD gameLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
TetrisGame tetris(&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
/*************************************************************************************************************
*******************************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 lin=ba.rows()-1; lin>=0; lin--) {
byte lin_lcd = 0;
byte cont = 0;
byte val = 0;
for (int col=0; col<ba.columns(); col++) {
if (cont == 0){ val = 0; }
val = val << 1;
val = val | ba.read(lin, col);
cont++;
if (cont == 3) {
gameLcd.write(LINHAS-lin-1, lin_lcd, val) ;
cont = 0;
lin_lcd++;
}
}
}
}
void setup() {
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;
if (re.getValue(0) < val_encoder && !change){ tetris.left(); change = true; } //sentido anti-horario move para esquerda
if (re.getValue(0) > val_encoder && !change){ tetris.right(); change = true; } //sentido horario move para direita
val_encoder = re.getValue(0);
if ( tetris.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
//clique curto --> rotate
//clique longo --> fast
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 = re.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( !b && millis()-m_pressed > 350){
tetris.fast();
} else {
tetris.noFast();
}
lastb = b;
}
