Skip to content

8BitConsole-tetris

2020-12-20 18:30:00

Tetris.ino

#include <Arduino.h>
#include "LEDMatrix.cpp"
#include "Button.cpp"
#include <LedControl.h>

#define LOAD_PIN 7
#define CLOCK_PIN 6
#define DATA_PIN 8

#define CLOCK_COUNTER_PIN 2
#define LOAD_COUNTER_PIN 3
#define DATA_COUNTER_PIN 4

#define RIGHT_BUTTON_PIN A0
#define LEFT_BUTTON_PIN A3
#define ROTATE_BUTTON_PIN A4
#define SLAM_BUTTON_PIN A2

// Game
void spawnNewShape();
void playDeathAnimation();
void setupNewGame();

// Matrix functions
void renderMatrix(byte *matrix);
void renderShapeInMatrix(byte *original, byte *destination, word shape, int x, int y);
bool collides(byte *matrix, word shape, int x, int y);

// Shape functions
int leftOffsetPositionForShape(word shape);
int rightOffsetPositionForShape(word shape);
int bottomOffsetPositionForShape(word shape);

// Actions
void moveLeftIfPossible();
void moveRightIfPossible();
int removeFullLines(byte *matrix);
void shiftLinesDown(byte *matrix, int toIndex);

byte currentMatrix[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

word shapes[7][4] = {{0xCC00, 0xCC00, 0xCC00, 0xCC00}, // O
                    {0x4444, 0x0F00, 0x2222, 0x0F00}, // I
                    {0x4E00, 0x4640, 0x0E40, 0x4C40}, // T
                    {0x4460, 0x0E80, 0xC440, 0x2E00}, // L
                    {0x44C0, 0x8E00, 0x6440, 0x0E20}, // J
                    {0x4C80, 0xC600, 0x4C80, 0xC600}, // Z
                    {0x8C40, 0x6C00, 0x8C40, 0x6C00}}; // S

word currentShape;
int currentShapeIndex = 0;
int currentX = 0;
int currentY = 0;
int currentRotation = 0;
int score = 0;

int previousRotateButtonState = LOW;
int previousSlamButtonState = LOW;

unsigned long previousMillis = 0;
long gameInterval = 250;

LEDMatrix ledMatrix(2, CLOCK_PIN, DATA_PIN, LOAD_PIN);


Button rightButton(RIGHT_BUTTON_PIN);
Button leftButton(LEFT_BUTTON_PIN);
Button rotateButton(ROTATE_BUTTON_PIN);
Button slamButton(SLAM_BUTTON_PIN);

LedControl display_counter = LedControl(DATA_COUNTER_PIN, CLOCK_COUNTER_PIN, LOAD_COUNTER_PIN,3);

void print_digit(byte d,byte n) {
  display_counter.setColumn(0,d,pgm_read_byte_near(charTable + n));
}

void print_number(int base) {
  int first;
  int second;
  int third;  

  first = base / 100 % 10;
  second =  base / 10 % 10;
  third = base % 10;

  print_digit(1, first);
  print_digit(2, second);
  print_digit(3, third);  
}

void setup() {
  randomSeed(analogRead(0));

  setupNewGame();
  playDeathAnimation();
  spawnNewShape();

  display_counter.shutdown(0,false);
  display_counter.setIntensity(0,2);
  display_counter.clearDisplay(0);

  // NOTE: For some reason, if I omit this line, the game will restart
  // every time a shape (except for "I") hits the floor.
  // If anyone could tell me why, that'd be great...
  Serial.begin(9600);
}

void loop() {
  byte matrix[16];
  renderShapeInMatrix(currentMatrix, matrix, currentShape, currentX, currentY);
  renderMatrix(matrix);

  if (leftButton.exlusivePressed()) {
    moveLeftIfPossible();
  }

  if (rightButton.exlusivePressed()) {
    moveRightIfPossible();
  }

  if (rotateButton.exlusivePressed()) {
    int nextRotation = currentRotation + 1;
    if (nextRotation > 3) {
      nextRotation = 0;
    }
    word nextShape = shapes[currentShapeIndex][nextRotation];
    if (!collides(currentMatrix, nextShape, currentX, currentY)) {
      currentRotation = nextRotation;
      currentShape = nextShape;
    }
  }

  if (slamButton.exlusivePressed()) {
    int bottomOffset = bottomOffsetPositionForShape(currentShape);
    while (!collides(currentMatrix, currentShape, currentX, currentY + 1) && currentY + 4 - bottomOffset <= 15) {
      currentY++;
    }
    currentY--;
  }

  // Increment currentY with given intervals
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= gameInterval) {
    previousMillis = currentMillis;

    int bottomOffset = bottomOffsetPositionForShape(currentShape);
    if (collides(currentMatrix, currentShape, currentX, currentY + 1) || currentY + 4 - bottomOffset > 15) {
      // Check for game over
      if (currentY < 0) {
        playDeathAnimation();
        setupNewGame();
        return;
      }

      renderShapeInMatrix(currentMatrix, currentMatrix, currentShape, currentX, currentY);
      spawnNewShape();
      int linesRemoved = removeFullLines(currentMatrix);
      score += linesRemoved;

      // Increase the game speed
      gameInterval -= linesRemoved * 3;

      print_number(score);
    } else {
      currentY++;
    }
  }
}

// ---- Game functions ----

void setupNewGame() {
  currentShapeIndex = 0;
  currentX = 0;
  currentY = 0;
  currentRotation = 0;
  score = 0;
  gameInterval = 300;
  for (int i = 0; i < 16; i++) {
    currentMatrix[i] = 0x0;
  }
  print_number(score);
  spawnNewShape();
}

void spawnNewShape() {
  currentShapeIndex = random(7);
  currentShape = shapes[currentShapeIndex][currentRotation];
  currentY = -4;
  currentX = 3;
}

void playDeathAnimation() {
  for (int i = 0; i < 16; i++) {
    if (i < 8) {
      ledMatrix.WriteOne(1, i + 1, 0xff);
    } else {
      ledMatrix.WriteOne(2, i + 1 - 8, 0xff);
    }
    delay(75);
  }
  for (int i = 0; i < 16; i++) {
    if (i < 8) {
      ledMatrix.WriteOne(1, i + 1, 0x00);
    } else {
      ledMatrix.WriteOne(2, i + 1 - 8, 0x00);
    }
    delay(75);
  }
}

// ---- Matrix functions ----

// Returns the numer of lines removed
int removeFullLines(byte *matrix) {
  int res = 0;
  for (int i = 0; i < 16; i++) {
    if (matrix[i] == 0xff) { // Full line
      matrix[i] = 0x0;
      shiftLinesDown(matrix, i);
      res++;
    }
  }
  return res;
}

void shiftLinesDown(byte *matrix, int toIndex) {
  matrix[0] = 0x0;
  for (int i = toIndex; i > 0; i--) {
    matrix[i] = matrix[i - 1];
  }
}

bool collides(byte *matrix, word shape, int x, int y) {
  for (int i = 0; i < 4; i++) {
    if (y + i < 0) { 
      continue; 
    }
    int shift = (3 - i) * 4;
    byte rowsValue = (shape & (0xf << shift)) >> shift;

    // Move to left hand side
    int lOffset = leftOffsetPositionForShape(shape);
    rowsValue = rowsValue << (4 + lOffset);

    // Move to right according to x and offset
    rowsValue = rowsValue >> (x + lOffset);

    if ((matrix[y + i] & rowsValue) > 0) {
      return true;
    }
  }
  return false;
}

void renderMatrix(byte *matrix) {
  for (int i = 0; i < 16; i++) {
    if (i < 8) {
      ledMatrix.WriteOne(1, i + 1, matrix[i]);
    } else {
      ledMatrix.WriteOne(2, i + 1 - 8, matrix[i]);
    }
  }
}

// Render a shape in a Matrix
void renderShapeInMatrix(byte *original, byte *destination, word shape, int x, int y) {
  for (int i = 0; i < 16; i++) {
    destination[i] = original[i];
  }

  for (int i = 0; i < 4; i++) {
    if (y + i < 0) { // Allow for negative Y (to render half a shape)
      continue;
    }

    int shift = (3 - i) * 4;
    byte rowsValue = (shape & (0xf << shift)) >> shift;

    // Move to left hand side
    int lOffset = leftOffsetPositionForShape(shape);
    rowsValue = rowsValue << (4 + lOffset);

    // Move to right according to x and offset
    rowsValue = rowsValue >> (x + lOffset);

    destination[y + i] = original[y + i] | rowsValue;
  }
}

// --- Actions ----

void moveLeftIfPossible() {
  // Check that currentX + bitIndex - 1 >= 0
  int offset = leftOffsetPositionForShape(currentShape);
  bool wouldCollide = collides(currentMatrix, currentShape, currentX - 1, currentY);
  if (currentX + offset - 1 >= 0 && !wouldCollide) {
    currentX--;
  }
}

void moveRightIfPossible() {
  // Check that currentX + bitIndex + 1 < board_width (8)
  int offset = rightOffsetPositionForShape(currentShape);
  bool wouldCollide = collides(currentMatrix, currentShape, currentX + 1, currentY);
  if (currentX + (4 - offset) < 8 && !wouldCollide) {
    currentX++;
  }
}

// --- Shape functions ----

// TODO: Would be great if I could refactor these functions into something
// a little more generic.

// Find leftmost active bit in shape
int leftOffsetPositionForShape(word shape) {
  int offset = 4;
  for (int i = 0; i < 4; i++) {
    int shift = (3 - i) * 4;
    byte rowsValue = (shape & (0xf << shift)) >> shift;
    byte test = 0b1000;
    for (int m = 0; m < 3; m++) {
      int l = rowsValue & test;
      if (l > 0 && m < offset) {
        offset = m;
      }
      test >>= 1;
    }
  }
  return offset;
}

// Find rightmost active bit in shape
int rightOffsetPositionForShape(word shape) {
  int offset = 4;
  for (int i = 0; i < 4; i++) {
    int shift = (3 - i) * 4;
    byte rowsValue = (shape & (0xf << shift)) >> shift;
    byte test = 1;
    for (int m = 0; m < 3; m++) {
      int l = rowsValue & test;
      if (l > 0 && m < offset) {
        offset = m;
      }
      test <<= 1;
    }
  }
  return offset;
}

// Find offset for bottom active bit in shape
int bottomOffsetPositionForShape(word shape) {
  for (int i = 3; i >= 0; i--) {
    int shift = (3 - i) * 4;
    byte rowsValue = (shape & (0xf << shift)) >> shift;
    if (rowsValue > 0) {
      return 3 - i;
    }
  }
}

Button.cpp

#include <Arduino.h>

class Button {
    public: 
        Button(int pin) {
            _pin = pin;
            _lastState = LOW;
            pinMode(_pin, INPUT);
        }

        bool exlusivePressed() {
            int state = digitalRead(_pin);
            if (state != _lastState) {
                _lastState = state;
                return state == HIGH;
            }
            return false;
        }
  private:
        int _pin;
        int _lastState;
};

LEDMatrix.cpp

#include <Arduino.h>

class LEDMatrix {
    public:
        LEDMatrix(int numberOfMatrices, int clockPin, int dataPin, int loadPin) {
            _numberOfMatrices = numberOfMatrices;
            _clockPin = clockPin;
            _dataPin = dataPin;
            _loadPin = loadPin;

            pinMode(_dataPin, OUTPUT);
            pinMode(_clockPin, OUTPUT);
            pinMode(_loadPin, OUTPUT);

                                                            //initiation of the max 7219
            WriteAll(max7219_reg_scanLimit, 0x07);
            WriteAll(max7219_reg_decodeMode, 0x00);         // using an led matrix (not digits)
            WriteAll(max7219_reg_shutdown, 0x01);           // not in shutdown mode
            WriteAll(max7219_reg_displayTest, 0x00);        // no display test
            for (int e = 1; e <= 8; e++) {                  // empty registers, turn all LEDs off
                WriteAll(e, 0);
            }
                                                            // the first 0x0f is the value you can set
                                                            // range: 0x00 to 0x0f
            WriteAll(max7219_reg_intensity, 0x01 & 0x0f);
        }
        WriteAll(byte reg, byte col) {
            digitalWrite(_loadPin, LOW);
            for (int c = 1; c <= _numberOfMatrices; c++) {
                putByte(reg);
                putByte(col);                           //((data & 0x01) * 256) + data >> 1); // put data
            }
            digitalWrite(_loadPin, LOW);
            digitalWrite(_loadPin, HIGH);
        }
        WriteOne(byte matrixIndex, byte reg, byte col) {
            digitalWrite(_loadPin, LOW);  // begin

            for (int c = 2; c > matrixIndex; c--) {
                putByte(0);                             // means no operation
                putByte(0);                             // means no operation
            }

            putByte(reg);                               // specify register
            putByte(col);                               //((data & 0x01) * 256) + data >> 1); // put data

            for (int c = matrixIndex - 1; c >= 1; c--) {
                putByte(0);                             // means no operation
                putByte(0);                             // means no operation
            }

            digitalWrite(_loadPin, LOW);                // and load the stuff
            digitalWrite(_loadPin, HIGH);
        }

    private:
        // define max7219 registers
        byte max7219_reg_noop        = 0x00;
        byte max7219_reg_digit0      = 0x01;
        byte max7219_reg_digit1      = 0x02;
        byte max7219_reg_digit2      = 0x03;
        byte max7219_reg_digit3      = 0x04;
        byte max7219_reg_digit4      = 0x05;
        byte max7219_reg_digit5      = 0x06;
        byte max7219_reg_digit6      = 0x07;
        byte max7219_reg_digit7      = 0x08;
        byte max7219_reg_decodeMode  = 0x09;
        byte max7219_reg_intensity   = 0x0a;
        byte max7219_reg_scanLimit   = 0x0b;
        byte max7219_reg_shutdown    = 0x0c;
        byte max7219_reg_displayTest = 0x0f;

        int _numberOfMatrices;
        int _clockPin;
        int _dataPin;
        int _loadPin;

        void putByte(byte data) {
            byte i = 8;
            byte mask;
            while (i > 0) {
            mask = 0x01 << (i - 1);                 // get bitmask
            digitalWrite(_clockPin, LOW);           // tick
            if (data & mask) {                      // choose bit
                digitalWrite(_dataPin, HIGH);       // send 1
            } else {
                digitalWrite(_dataPin, LOW);        // send 0
            }
                digitalWrite(_clockPin, HIGH);      // tock
                --i;                                // move to lesser bit
            }
        }
};

Lib