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();
}





