// ----------------------------------------------------------------------------
// Rotary Encoder Driver with Acceleration
// Supports Click, DoubleClick, Long Click
//
// (c) 2010 [email protected]
// (c) 2014 [email protected]
//
// Timer-based rotary encoder logic by Peter Dannegger
// http://www.mikrocontroller.net/articles/Drehgeber
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Rotary Encoder Driver with Acceleration
// Supports Click, DoubleClick, Long Click
//
// (c) 2010 [email protected]
// (c) 2014 [email protected]
//
// Timer-based rotary encoder logic by Peter Dannegger
// http://www.mikrocontroller.net/articles/Drehgeber
// ----------------------------------------------------------------------------
#ifndef __have__ClickEncoder_h__
#define __have__ClickEncoder_h__
// ----------------------------------------------------------------------------
#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include "Arduino.h"
// ----------------------------------------------------------------------------
#define ENC_NORMAL (1 << 1) // use Peter Danneger's decoder
#define ENC_FLAKY (1 << 2) // use Table-based decoder
// ----------------------------------------------------------------------------
#ifndef ENC_DECODER
# define ENC_DECODER ENC_NORMAL
#endif
#if ENC_DECODER == ENC_FLAKY
# ifndef ENC_HALFSTEP
# define ENC_HALFSTEP 1 // use table for half step per default
# endif
#endif
// ----------------------------------------------------------------------------
class ClickEncoder
{
public:
typedef enum Button_e {
Open = 0,
Closed,
Pressed,
Held,
Released,
Clicked,
DoubleClicked
} Button;
public:
ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN = -1,
uint8_t stepsPerNotch = 1, bool active = LOW);
void service(void);
int16_t getValue(void);
#ifndef WITHOUT_BUTTON
public:
Button getButton(void);
#endif
#ifndef WITHOUT_BUTTON
public:
void setDoubleClickEnabled(const bool &d)
{
doubleClickEnabled = d;
}
const bool getDoubleClickEnabled()
{
return doubleClickEnabled;
}
#endif
public:
void setAccelerationEnabled(const bool &a)
{
accelerationEnabled = a;
if (accelerationEnabled == false) {
acceleration = 0;
}
}
const bool getAccelerationEnabled()
{
return accelerationEnabled;
}
private:
const uint8_t pinA;
const uint8_t pinB;
const uint8_t pinBTN;
const bool pinsActive;
volatile int16_t delta;
volatile int16_t last;
uint8_t steps;
volatile uint16_t acceleration;
bool accelerationEnabled;
#if ENC_DECODER != ENC_NORMAL
static const int8_t table[16];
#endif
#ifndef WITHOUT_BUTTON
volatile Button button;
bool doubleClickEnabled;
uint16_t keyDownTicks = 0;
uint8_t doubleClickTicks = 0;
unsigned long lastButtonCheck = 0;
#endif
};
// ----------------------------------------------------------------------------
#endif // __have__ClickEncoder_h__
// ----------------------------------------------------------------------------
// Button configuration (values for 1ms timer service calls)
//
#define ENC_BUTTONINTERVAL 10 // check button every x milliseconds, also debouce time
#define ENC_DOUBLECLICKTIME 600 // second click within 600ms - bylo 600 / tetris 300
#define ENC_HOLDTIME 1200 // report held button after 1.2s - bylo 1200 / tetris 500
// ----------------------------------------------------------------------------
// Acceleration configuration (for 1000Hz calls to ::service())
//
#define ENC_ACCEL_TOP 3072 // max. acceleration: *12 (val >> 8)
#define ENC_ACCEL_INC 25
#define ENC_ACCEL_DEC 2
// ----------------------------------------------------------------------------
#if ENC_DECODER != ENC_NORMAL
# ifdef ENC_HALFSTEP
// decoding table for hardware with flaky notch (half resolution)
const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = {
0, 0, -1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, -1, 0, 0
};
# else
// decoding table for normal hardware
const int8_t ClickEncoder::table[16] __attribute__((__progmem__)) = {
0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
};
# endif
#endif
// ----------------------------------------------------------------------------
ClickEncoder::ClickEncoder(uint8_t A, uint8_t B, uint8_t BTN, uint8_t stepsPerNotch, bool active)
: doubleClickEnabled(true), accelerationEnabled(true),
delta(0), last(0), acceleration(0),
button(Open), steps(stepsPerNotch),
pinA(A), pinB(B), pinBTN(BTN), pinsActive(active)
{
uint8_t configType = (pinsActive == LOW) ? INPUT_PULLUP : INPUT;
pinMode(pinA, configType);
pinMode(pinB, configType);
pinMode(pinBTN, configType);
if (digitalRead(pinA) == pinsActive) {
last = 3;
}
if (digitalRead(pinB) == pinsActive) {
last ^=1;
}
}
// ----------------------------------------------------------------------------
// call this every 1 millisecond via timer ISR
//
void ClickEncoder::service(void)
{
bool moved = false;
unsigned long now = millis();
if (accelerationEnabled) { // decelerate every tick
acceleration -= ENC_ACCEL_DEC;
if (acceleration & 0x8000) { // handle overflow of MSB is set
acceleration = 0;
}
}
#if ENC_DECODER == ENC_FLAKY
last = (last << 2) & 0x0F;
if (digitalRead(pinA) == pinsActive) {
last |= 2;
}
if (digitalRead(pinB) == pinsActive) {
last |= 1;
}
uint8_t tbl = pgm_read_byte(&table[last]);
if (tbl) {
delta += tbl;
moved = true;
}
#elif ENC_DECODER == ENC_NORMAL
int8_t curr = 0;
if (digitalRead(pinA) == pinsActive) {
curr = 3;
}
if (digitalRead(pinB) == pinsActive) {
curr ^= 1;
}
int8_t diff = last - curr;
if (diff & 1) { // bit 0 = step
last = curr;
delta += (diff & 2) - 1; // bit 1 = direction (+/-)
moved = true;
}
#else
# error "Error: define ENC_DECODER to ENC_NORMAL or ENC_FLAKY"
#endif
if (accelerationEnabled && moved) {
// increment accelerator if encoder has been moved
if (acceleration <= (ENC_ACCEL_TOP - ENC_ACCEL_INC)) {
acceleration += ENC_ACCEL_INC;
}
}
// handle button
//
#ifndef WITHOUT_BUTTON
if (pinBTN > 0 // check button only, if a pin has been provided
&& (now - lastButtonCheck) >= ENC_BUTTONINTERVAL) // checking button is sufficient every 10-30ms
{
lastButtonCheck = now;
if (digitalRead(pinBTN) == pinsActive) { // key is down
keyDownTicks++;
if (keyDownTicks > (ENC_HOLDTIME / ENC_BUTTONINTERVAL)) {
button = Held;
}
}
if (digitalRead(pinBTN) == !pinsActive) { // key is now up
if (keyDownTicks /*> ENC_BUTTONINTERVAL*/) {
if (button == Held) {
button = Released;
doubleClickTicks = 0;
}
else {
#define ENC_SINGLECLICKONLY 1
if (doubleClickTicks > ENC_SINGLECLICKONLY) { // prevent trigger in single click mode
if (doubleClickTicks < (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL)) {
button = DoubleClicked;
doubleClickTicks = 0;
}
}
else {
doubleClickTicks = (doubleClickEnabled) ? (ENC_DOUBLECLICKTIME / ENC_BUTTONINTERVAL) : ENC_SINGLECLICKONLY;
}
}
}
keyDownTicks = 0;
}
if (doubleClickTicks > 0) {
doubleClickTicks--;
if (--doubleClickTicks == 0) {
button = Clicked;
}
}
}
#endif // WITHOUT_BUTTON
}
// ----------------------------------------------------------------------------
int16_t ClickEncoder::getValue(void)
{
int16_t val;
cli();
val = delta;
if (steps == 2) delta = val & 1;
else if (steps == 4) delta = val & 3;
else delta = 0; // default to 1 step per notch
sei();
if (steps == 4) val >>= 2;
if (steps == 2) val >>= 1;
int16_t r = 0;
int16_t accel = ((accelerationEnabled) ? (acceleration >> 8) : 0);
if (val < 0) {
r -= 1 + accel;
}
else if (val > 0) {
r += 1 + accel;
}
return r;
}
// ----------------------------------------------------------------------------
#ifndef WITHOUT_BUTTON
ClickEncoder::Button ClickEncoder::getButton(void)
{
ClickEncoder::Button ret = button;
if (button != ClickEncoder::Held) {
button = ClickEncoder::Open; // reset
}
return ret;
}
#endif
/*------ TETRIS ------*/
#include <TimerOne.h>
#include <U8glib.h>
U8GLIB_SSD1306_128X32 u8g(U8G_I2C_OPT_NONE);
#define bW 4
#define bH 4
#define ROW 23
#define COL 10
#define bOffX 1
#define bOffY 33
#define nextX 4
#define nextY 4
#define bP 3
#define DOWN_SCORE 1+1*lvl
int board[ROW][COL];
boolean isStarted = 0;
//long int lastTick = 0;
unsigned long int gameTime = 0;
unsigned long int score = 0;
unsigned int lines = 0;
int lvl = 0;
int gameSpeed = 1000;
boolean gameOver = 1;
//unsigned int IBlock[4] = {0x4444,0x0F00,0x2222,0x00F0};
unsigned int IBlock[4] = {0x4444,0x0F00,0x4444,0x0F00};
unsigned int JBlock[4] = {0x2260,0x4700,0x3220,0x0710};
unsigned int LBlock[4] = {0x4460,0x0E80,0xC440,0x2E00};
unsigned int OBlock[4] = {0x6600,0x6600,0x6600,0x6600};
unsigned int SBlock[4] = {0x6C00,0x4620,0x06C0,0x8C40};
unsigned int TBlock[4] = {0x0270,0x4640,0x7200,0x1310};
unsigned int ZBlock[4] = {0xC600,0x2640,0x0C60,0x4C80};
unsigned int nextBlock;
unsigned int activeBlock[4];
int nbR;
int nbID;
int abID;
int abX;
int abY;
int abR;
byte mID;
bool mActive;
ClickEncoder *encoder;
int16_t last, value;
void timerIsr() {
encoder->service();
}
void setup(void) {
u8g.setRot90();
encoder = new ClickEncoder(3, 4, 5, 4);
Timer1.initialize(1000);
Timer1.attachInterrupt(timerIsr);
encoder->setAccelerationEnabled(false);
last = 0;
abX = 0;
abY = 0;
abR = 0;
//lastTick = millis();
isStarted = 0;
gameOver = 1;
gameSpeed = 1200;
mID = 0;
mActive = 0;
randomSeed(analogRead(A0));
delay(500);
}
void loop(void) {
//if (millis()-lastTick > 15) {
value += encoder->getValue();
if (value != last) {
if(value > last) {
if(gameOver == 0){
moveRight();
}else{
if(!mActive){
mID = (mID + 1) % 3;
}else{
if(lvl < 9 && mID == 1){lvl++;}
}
}
}else{
if(gameOver == 0){
moveLeft();
}else{
if(!mActive){
if(mID == 0){mID = 2;}else{mID = (mID - 1) % 3;}
}else{
if(lvl > 0 && mID == 1){lvl--;}
}
}
}
last = value;
}
ClickEncoder::Button b = encoder->getButton();
if (b != ClickEncoder::Open) {
switch (b) {
//VERBOSECASE(ClickEncoder::Pressed);
//VERBOSECASE(ClickEncoder::Held)
//VERBOSECASE(ClickEncoder::Released)
//VERBOSECASE(ClickEncoder::Clicked)
case ClickEncoder::Clicked:
if(gameOver == 0){
moveRotate();
}else{
menuOpen();
//isStarted = 1;
//gameOver = 0;
}
break;
case ClickEncoder::Held:
if(gameOver == 0){
moveDown();
score += DOWN_SCORE;
moveDown();
score += DOWN_SCORE;
}
break;
case ClickEncoder::DoubleClicked:
lvl++;
break;
}
}
u8g.firstPage();
do {
draw();
} while( u8g.nextPage() );
//lastTick = millis();
//}
if(!gameOver){
if (millis()-gameTime > (gameSpeed-lvl*100)) {
moveDown();
gameTime = millis();
}
}
if(isStarted && gameOver == 0){
//gameOver = 0;
score = 0;
lines = 0;
isStarted = 0;
prepareNext();
createBlock();
}
}
void menuOpen(){
if(mID == 0){
isStarted = 1;
gameOver = 0;
}else{
mActive = !mActive;
}
}
void drawMenu(){
if(!mActive || mID == 1){
u8g.setFont(u8g_font_5x7);
u8g.setPrintPos(4, 42);
u8g.print(F("Start"));
u8g.setPrintPos(4, 52);
u8g.print(F("Level"));
u8g.setPrintPos(4, 62);
u8g.print(F("Score"));
u8g.drawLine(2,42+mID*10,28,42+mID*10);
if(mActive){
u8g.drawLine(2,42-8+mID*10,28,42-8+mID*10);
u8g.drawLine(2,42-7+mID*10,2,42+mID*10);
u8g.drawLine(28,42-7+mID*10,28,42+mID*10);
}
}
}
void drawGUI(void){
u8g.drawFrame(2, 2, 16, 16);
u8g.drawFrame(0, 32, COL*bP+2, ROW*bP+2);
//u8g.drawFrame(0, 0, 32, 100);
u8g.setFont(u8g_font_7x14);//lvl
u8g.setPrintPos(22, 15);
u8g.print(lvl);
drawScore();
}
void drawScore(){
u8g.setFont(u8g_font_5x7);//score
u8g.setPrintPos(1, 28);
u8g.print(score);
}
void drawBlock(int x, int y){
u8g.drawFrame(x ,y , bP, bP);
}
void drawNextBlock(void){
int b = 15;
for(int s=0;s < bW; s++){
for(int r=0;r < bH; r++){
if(!bitRead(nextBlock, b)){ b--;continue;}
drawBlock(r*bP+nextX, s*bP+nextY);
b--;
}
}
}
void drawBoard(){
for(int y=0;y < ROW; y++){
for(int x=0;x < COL; x++){
if(!board[y][x]){continue;}
drawBlock(x*bP+bOffX, y*bP+bOffY);
}
}
}
void drawActiveBlock(){
int b = 15;
for(int s=0;s < bW; s++){
for(int r=0;r < bH; r++){
if(!bitRead(activeBlock[abR], b)){ b--;continue;}
drawBlock(r*bP+bOffX+abX*bP, s*bP+bOffY+abY*bP);
b--;
}
}
}
void prepareNext(){
nbID = random(6);
nbR = random(3);
switch(nbID){
case 0:
nextBlock = IBlock[nbR];
break;
case 1:
nextBlock = JBlock[nbR];
break;
case 2:
nextBlock = LBlock[nbR];
break;
case 3:
nextBlock = OBlock[nbR];
break;
case 4:
nextBlock = SBlock[nbR];
break;
case 5:
nextBlock = TBlock[nbR];
break;
case 6:
nextBlock = ZBlock[nbR];
break;
}
}
void createBlock(){
abX = 3;
abY = 0;
abR = nbR;
abID = nbID;
switch(abID){
case 0:
for(int i=0; i < 4;i++){activeBlock[i] = IBlock[i];}
break;
case 1:
for(int i=0; i < 4;i++){activeBlock[i] = JBlock[i];}
break;
case 2:
for(int i=0; i < 4;i++){activeBlock[i] = LBlock[i];}
break;
case 3:
for(int i=0; i < 4;i++){activeBlock[i] = OBlock[i];}
break;
case 4:
for(int i=0; i < 4;i++){activeBlock[i] = SBlock[i];}
break;
case 5:
for(int i=0; i < 4;i++){activeBlock[i] = TBlock[i];}
break;
case 6:
for(int i=0; i < 4;i++){activeBlock[i] = ZBlock[i];}
break;
}
prepareNext();
}
void moveRight(){
if(notColision(abX+1,abY,abR)){
abX++;
}
}
void moveLeft(){
if(notColision(abX-1,abY,abR)){
abX--;
}
}
void moveDown(){
if(notColision(abX,abY+1,abR)){
abY++;
}else{
lockBlock();
}
}
void moveRotate(){
byte kOff = 1;
if(notColision(abX,abY,(abR + 1) % 4)){
abR = (abR + 1) % 4;
}else{
if(abX <= 0){
if(notColision(abX+1,abY,(abR + 1) % 4)){
abX++;
abR = (abR + 1) % 4;
}
}
if(abX >= 7){
if(abID == 0 && abX == 8){kOff = 2;}
if(notColision(abX-kOff,abY,(abR + 1) % 4)){
abX -= kOff;
abR = (abR + 1) % 4;
}
}
}
}
void lockBlock(){
int b = 15;
int fullRowCounter = 0;
for(int s=0;s < bW; s++){
for(int r=0;r < bH; r++){
if(!bitRead(activeBlock[abR], b)){ b--;continue;}
board[abY+s][abX+r] = 1;
b--;
if(abY == 0){
gameOver = 1;
}
}
}
if(!gameOver){
for(int y=0;y < ROW; y++){
boolean isRowFull = true;
for(int x=0;x < COL; x++){
isRowFull = isRowFull && board[y][x];
}
if(isRowFull){
for(int r = y;r > 1; r--){
for(int s = 0; s < COL; s++){
board[r][s] = board[r-1][s];
}
}
score += 10 + (10*fullRowCounter);
fullRowCounter++;
}
}
createBlock();
} else {
eraseGame();
}
}
boolean notColision(int newX, int newY, int newR){
boolean nocolide = true;
int b = 15;
for(int s=0;s < bW; s++){
for(int r=0;r < bH; r++){
if(!bitRead(activeBlock[newR], b)){b--; continue;}
if((newX+r >= COL)||(newX+r < 0)){nocolide=false;}
if(newY+s >= ROW){nocolide=false;}
if(board[newY+s][newX+r]){nocolide=false;}
b--;
}
}
return nocolide;
}
void eraseGame(){
for(int y=0;y < ROW; y++){
for(int x=0;x < COL; x++){
board[y][x] = 0;
}
}
gameOver = 1;
isStarted = 0;
}
void draw(void) {
/*if(!isStarted){
drawActiveBlock();
}*/
drawGUI();
drawScore();
if(gameOver) {
drawMenu();
}else{
drawActiveBlock();
drawNextBlock();
drawBoard();
}
}
Loading
ssd1306
ssd1306