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