LED Tetris: Unterschied zwischen den Versionen
Zeile 174: | Zeile 174: | ||
== Programmcode == | == Programmcode == | ||
<div style="width:1100px; height: | <div style="width:1100px; height:800px; overflow:scroll; border: hidden"> | ||
<pre> | <pre> | ||
//---------------------------------------------// | //---------------------------------------------// | ||
Zeile 192: | Zeile 192: | ||
//---------------------------------------------// | //---------------------------------------------// | ||
//Defines | //Defines | ||
//Spielfeld | //Spielfeld | ||
Zeile 221: | Zeile 205: | ||
#define BTN_ROTATE 11 | #define BTN_ROTATE 11 | ||
// | // Tetriminos | ||
#define PIECE_W 4 | #define PIECE_W 4 | ||
#define PIECE_H 4 | #define PIECE_H 4 | ||
Zeile 236: | Zeile 220: | ||
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NR_LED, LED_DATA, NEO_GRB + NEO_KHZ800); | Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NR_LED, LED_DATA, NEO_GRB + NEO_KHZ800); | ||
LiquidCrystal_I2C lcd(0x27, 16, 2); | LiquidCrystal_I2C lcd(0x27, 16, 2); | ||
const byte empty[] = {0}; | const byte empty[] = {0}; | ||
Zeile 421: | Zeile 398: | ||
}; | }; | ||
//Scores | |||
unsigned int top_score = 0; | |||
unsigned int score = 0; | |||
//Zeiten | |||
unsigned long timeBefore = 0; | |||
unsigned long timeNow = 0; | |||
//Counter | |||
byte i = 0; | |||
//Letzte Werte der knöpfe, um mehrfachausführung zu verhindern | //Letzte Werte der knöpfe, um mehrfachausführung zu verhindern | ||
Zeile 455: | Zeile 442: | ||
void pixel(int x, int y, long color) { | void pixel(int x, int y, long color) { | ||
int a; | int a; | ||
if (x % 2) { | if (x % 2) { | ||
a = x * GRID_H + y; | a = x * GRID_H + y; | ||
Zeile 462: | Zeile 448: | ||
a = (x + 1) * GRID_H - (y + 1); | a = (x + 1) * GRID_H - (y + 1); | ||
} | } | ||
pixels.setPixelColor(a, color); | pixels.setPixelColor(a, color); | ||
} | } | ||
Zeile 470: | Zeile 453: | ||
//Spielfeld anzeigen | //Spielfeld anzeigen | ||
void draw_grid() { | void draw_grid() { | ||
int x, y; | int x, y; | ||
for (y = 0; y < GRID_H; ++y) { | for (y = 0; y < GRID_H; ++y) { | ||
Zeile 495: | Zeile 470: | ||
void choose_new_piece() { | void choose_new_piece() { | ||
if ( sequence_count >= DIFF_PIECES ) { | if ( sequence_count >= DIFF_PIECES ) { | ||
// Liste leer | // Liste leer | ||
Zeile 523: | Zeile 497: | ||
} | } | ||
void erase_piece_from_grid() { // | void erase_piece_from_grid() { //Tetrimino vom Spielfeld entfernen, um wo anders wieder einzufügen | ||
int x, y; | int x, y; | ||
//Ersten Pixel des | //Ersten Pixel des Tetriminos finden | ||
const byte *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | const byte *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | ||
Zeile 538: | Zeile 511: | ||
int nx = piece_x + x; //x-Koordinate des Pixels berechnen | int nx = piece_x + x; //x-Koordinate des Pixels berechnen | ||
if (nx < 0 || nx > GRID_W) continue; //Wenn außerhalb des Grids, dann Ignorieren | if (nx < 0 || nx > GRID_W) continue; //Wenn außerhalb des Grids, dann Ignorieren | ||
if (piece[y * PIECE_W + x] == 1) { //Wenn Pixel im Modell des | if (piece[y * PIECE_W + x] == 1) { //Wenn Pixel im Modell des Tetriminos =1... | ||
grid[ny * GRID_W + nx] = 0; //...Pixel aus dem Grid löschen | grid[ny * GRID_W + nx] = 0; //...Pixel aus dem Grid löschen | ||
} | } | ||
Zeile 546: | Zeile 519: | ||
void add_piece_to_grid() { | void add_piece_to_grid() { | ||
int x, y; | int x, y; | ||
//Ersten Pixel des | //Ersten Pixel des Tetriminos finden | ||
const byte *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | const byte *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | ||
Zeile 560: | Zeile 532: | ||
int nx = piece_x + x; //x-Koordinate des Pixels berechnen | int nx = piece_x + x; //x-Koordinate des Pixels berechnen | ||
if (nx < 0 || nx > GRID_W) continue; //Wenn außerhalb des Grids, dann Ignorieren | if (nx < 0 || nx > GRID_W) continue; //Wenn außerhalb des Grids, dann Ignorieren | ||
if (piece[y * PIECE_W + x] == 1) { //Wenn Pixel im Modell des | if (piece[y * PIECE_W + x] == 1) { //Wenn Pixel im Modell des Tetriminos =1... | ||
grid[ny * GRID_W + nx] = piece_id; //...Farbe an die Stellen des | grid[ny * GRID_W + nx] = piece_id; //...Farbe an die Stellen des Tetriminos schreiben | ||
} | } | ||
} | } | ||
Zeile 568: | Zeile 540: | ||
void delete_row(int y) { | void delete_row(int y) { | ||
//Score erhöhen | //Score erhöhen | ||
score = score + 10; | score = score + 10; | ||
Zeile 610: | Zeile 581: | ||
void remove_full_rows() { | void remove_full_rows() { | ||
int x, y, c; | int x, y, c; | ||
Zeile 627: | Zeile 597: | ||
void try_to_move_piece_sideways() { | void try_to_move_piece_sideways() { | ||
int new_px = 0; | int new_px = 0; | ||
Zeile 649: | Zeile 618: | ||
void try_to_rotate_piece() { | void try_to_rotate_piece() { | ||
int want_turn = 0; | int want_turn = 0; | ||
Zeile 664: | Zeile 632: | ||
int new_pr = ( piece_rotation + 1 ) % 4; //Rotation erhöhen | int new_pr = ( piece_rotation + 1 ) % 4; //Rotation erhöhen | ||
if (piece_can_fit(piece_x, piece_y, new_pr)) { | if (piece_can_fit(piece_x, piece_y, new_pr)) { | ||
piece_rotation = new_pr; //Wenn der | piece_rotation = new_pr; //Wenn der Tetrimino passt, drehen | ||
} else { | } else { | ||
if (piece_can_fit(piece_x - 1, piece_y, new_pr)) { //Wenn | if (piece_can_fit(piece_x - 1, piece_y, new_pr)) { //Wenn Tetrimino weiter links passt, 1 nach links und drehen | ||
piece_x = piece_x - 1; | piece_x = piece_x - 1; | ||
piece_rotation = new_pr; | piece_rotation = new_pr; | ||
} else if (piece_can_fit(piece_x + 1, piece_y, new_pr)) { //Wenn | } else if (piece_can_fit(piece_x + 1, piece_y, new_pr)) { //Wenn Tetrimino weiter rechts passt, 1 nach rechts und drehen | ||
piece_x = piece_x + 1; | piece_x = piece_x + 1; | ||
piece_rotation = new_pr; | piece_rotation = new_pr; | ||
Zeile 728: | Zeile 696: | ||
} | } | ||
void all_white() { | void all_white() { //Alle LED nacheinander mit kurzer Verzögerung Weiß machen | ||
for (int led_number = 0; led_number < NR_LED; led_number++) { | for (int led_number = 0; led_number < NR_LED; led_number++) { | ||
pixels.setPixelColor(led_number, pixels.Color(50, 50, 50)); | pixels.setPixelColor(led_number, pixels.Color(50, 50, 50)); | ||
Zeile 734: | Zeile 702: | ||
delay(5); | delay(5); | ||
} | } | ||
} | } | ||
void game_over() { | void game_over() { | ||
//Score zurücksetzen | |||
score = 0; | score = 0; | ||
//Zeit speichern | |||
long over_time = millis(); | long over_time = millis(); | ||
timeBefore = over_time; | |||
//Bei erster LED beginnen | |||
int led_number = 0; | int led_number = 0; | ||
while (HIGH) { | |||
timeNow = millis(); | while (HIGH) { //Endlosschleife | ||
if (timeNow - timeBefore >= 250) { | timeNow = millis(); //Zeit abfragen | ||
if (timeNow - timeBefore >= 250) { //Alle 250ms... | |||
pixels.setPixelColor(led_number, piece_colors[rand() % DIFF_PIECES]); | pixels.setPixelColor(led_number, piece_colors[rand() % DIFF_PIECES]); | ||
pixels.show(); | pixels.show(); //...Pixel zufüllige Farbe zuweisen und anzeigen | ||
led_number += 1; | led_number += 1; //Nächste LED | ||
led_number %= NR_LED; | led_number %= NR_LED; //Am Ende des Streifens von vorne beginnen | ||
timeBefore += 250; //Referenzzeit erhöhen | |||
} | } | ||
//restart? | |||
// | |||
if (!digitalRead(BTN_ROTATE) && timeNow - over_time >= 1000) { | if (!digitalRead(BTN_ROTATE) && timeNow - over_time >= 1000) { | ||
break; | break; | ||
} | } | ||
} | } | ||
setup(); | setup(); //Setup ausführen | ||
return; | return; //Mit Loop fortfahren | ||
} | } | ||
void try_to_drop_piece() { | void try_to_drop_piece() { | ||
//Tetrimino aus Grid entfernen | |||
erase_piece_from_grid(); | erase_piece_from_grid(); | ||
if (piece_can_fit(piece_x, piece_y + 1, piece_rotation)) { | if (piece_can_fit(piece_x, piece_y + 1, piece_rotation)) { //Tetrimino passt an neue Position | ||
piece_y++; //Teil runter bewegen | piece_y++; //Teil runter bewegen | ||
add_piece_to_grid(); | add_piece_to_grid(); | ||
} | } | ||
else { //Wenn Teil nicht passt | else { //Wenn Teil nicht passt | ||
add_piece_to_grid(); | add_piece_to_grid(); //Tetrimino wieder einfügen | ||
remove_full_rows(); //mögliche volle Reihen entfernen | remove_full_rows(); //mögliche volle Reihen entfernen | ||
if (game_is_over() == 1) { | if (game_is_over() == 1) { //Abfragen, ob Spiel verloren | ||
game_over(); | game_over(); | ||
} | } | ||
Zeile 784: | Zeile 752: | ||
void try_to_drop_faster() { | void try_to_drop_faster() { | ||
if (!digitalRead(BTN_DOWN)) //wird der Button Down gedrückt, versuchen | if (!digitalRead(BTN_DOWN)) //wird der Button Down gedrückt, versuchen Tetrimino runter zu bewegen | ||
{ | { | ||
try_to_drop_piece(); | try_to_drop_piece(); | ||
Zeile 791: | Zeile 759: | ||
void react_to_player() { | void react_to_player() { | ||
//Tetrimino aus Grid entfernen | |||
erase_piece_from_grid(); | erase_piece_from_grid(); | ||
//Seitwärts abfragen | |||
try_to_move_piece_sideways(); | try_to_move_piece_sideways(); | ||
//Rotation abfragen | |||
try_to_rotate_piece(); | try_to_rotate_piece(); | ||
//Tetrimino wieder einfügen | |||
add_piece_to_grid(); | add_piece_to_grid(); | ||
//Nach unten abfragen und durchführen | |||
try_to_drop_faster(); | try_to_drop_faster(); | ||
} | } | ||
int game_is_over() { | int game_is_over() { | ||
int x, y; | int x, y; | ||
const byte *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | const byte *piece = pieces[piece_id] + (piece_rotation * PIECE_H * PIECE_W); | ||
Zeile 809: | Zeile 783: | ||
for (x = 0; x < PIECE_W; ++x) { | for (x = 0; x < PIECE_W; ++x) { | ||
if (piece[y * PIECE_W + x] > 0) { | if (piece[y * PIECE_W + x] > 0) { | ||
if (ny < 0) return 1; // | if (ny < 0) return 1; // Tetrimino ragt über den Screen, game over | ||
} | } | ||
} | } | ||
Zeile 818: | Zeile 792: | ||
void setup() { | void setup() { | ||
//In- und Outputs setzen | |||
pinMode(BTN_LEFT, INPUT_PULLUP); | pinMode(BTN_LEFT, INPUT_PULLUP); | ||
pinMode(BTN_RIGHT, INPUT_PULLUP); | pinMode(BTN_RIGHT, INPUT_PULLUP); | ||
Zeile 826: | Zeile 799: | ||
pinMode(LED_DATA, OUTPUT); | pinMode(LED_DATA, OUTPUT); | ||
//Highscore auslesen | |||
top_score = EEPROM.read(1); | top_score = EEPROM.read(1); | ||
//LCD initialisieren | |||
lcd.init(); | lcd.init(); | ||
lcd.backlight(); | lcd.backlight(); | ||
//Startnachricht ausgeben | |||
lcd.setCursor(0, 0); | lcd.setCursor(0, 0); | ||
lcd.print("Rotate to start"); | lcd.print("Rotate to start"); | ||
//LED-Streifen initialisieren und löschen | |||
pixels.begin(); | pixels.begin(); | ||
pixels.clear(); | pixels.clear(); | ||
pixels.show(); | pixels.show(); | ||
//Sicherstellen, dass das Grid leer ist | |||
for (i = 0; i < GRID_W * GRID_H; ++i) { | for (i = 0; i < GRID_W * GRID_H; ++i) { | ||
grid[i] = 0; | grid[i] = 0; | ||
} | } | ||
//Zufallszahlen initialisieren | |||
randomSeed(millis()); | randomSeed(millis()); | ||
//Tetrimino-Sequenz füllen | |||
choose_new_piece(); | choose_new_piece(); | ||
//Timings festlegen | |||
move_delay = INI_MOVE_DELAY; | move_delay = INI_MOVE_DELAY; | ||
drop_delay = INI_DROP_DELAY; | drop_delay = INI_DROP_DELAY; | ||
draw_delay = INI_DRAW_DELAY; | draw_delay = INI_DRAW_DELAY; | ||
//Anfangszeit festlegen | |||
last_draw = last_drop = last_move = millis(); | last_draw = last_drop = last_move = millis(); | ||
//Auf Tastendruck warten | |||
while (digitalRead(BTN_ROTATE)) {} | while (digitalRead(BTN_ROTATE)) {} | ||
delay(500); | delay(500); | ||
//Scores auf Display schreiben | |||
lcd.clear(); | lcd.clear(); | ||
lcd.setCursor(0, 0); | lcd.setCursor(0, 0); | ||
Zeile 865: | Zeile 846: | ||
lcd.print("Highscore: "); | lcd.print("Highscore: "); | ||
lcd.print(top_score); | lcd.print(top_score); | ||
//Startanimation | |||
all_white(); | all_white(); | ||
} | } | ||
void loop() { | void loop() { | ||
//Aktuelle Zeit messen | |||
long t = millis(); | long t = millis(); | ||
//Wenn genug Zeit seit letztem Move vergangen, auf Eingabe reagieren | |||
if (t - last_move > move_delay ) { | if (t - last_move > move_delay ) { | ||
last_move = t; | last_move = t; | ||
react_to_player(); | react_to_player(); | ||
} | } | ||
//Wenn genug Zeit seit letztem Drop vergangen, Tetrimono nach unten bewegen | |||
if (t - last_drop > drop_delay ) { | if (t - last_drop > drop_delay ) { | ||
last_drop = t; | last_drop = t; | ||
try_to_drop_piece(); | try_to_drop_piece(); | ||
} | } | ||
//Wenn genug Zeit seit letzter Ausgabe vergangen, Spielfeld ausgeben | |||
if (t - last_draw > draw_delay ) { | if (t - last_draw > draw_delay ) { | ||
last_draw = t; | last_draw = t; |
Version vom 11. Januar 2022, 00:20 Uhr
Autoren: Yannick Schmidt & Nils Koch
Betreuer: Prof. Göbel & Prof. Schneider
→ zurück zur Übersicht: WS 20/21: Angewandte Elektrotechnik (BSE)
Einleitung
Das Ziel der Gruppe von Yannick Schmidt & Nils Koch ist es das Spiel "Tetris" auf einer selbstgebauten 10x20 LED Matrix im Rahmen des GET-Fachpraktikums zu realisieren. Der aktuelle Score und der Highscore sollen auf einem kleinen Display angezeigt werden. Die Eingabe erfolgt per selbstgebauten Gamepad.
Anforderungen
ID | Inhalt | Ersteller | Datum | Geprüft von | Datum |
---|---|---|---|---|---|
1 | Möglichst dicht gepackte LED Matrix | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
2 | Rechteckiger LED Look | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
3 | Start- und Endanimation | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
4 | Speicherbarer Highscore | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
5 | Eingabe per kabelgebundenes Gamepad (evtl. auch kabellos per Bluetooth) | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
6 | Stromversorgung mittels USB Powerbank | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
7 | Optional: Staufach für das Gamepad, welches sich per Sensor öffnen lässt | Yannick Schmidt | 5.10.2021 | Yannick Schmidt, Nils Koch | 26.10.2021 |
Funktionaler Systementwurf/Technischer Systementwurf
Komponentenspezifikation
Komponente | Beschreibung | Abbildung |
---|---|---|
Arduino UNO | Microcontroller 14 digitale I/O Pins 6 analoge Eingänge |
|
WS2812B ECO | 3 LEDs Rot, Grün, Blau 256 Helligkeitsstufen |
|
LCD-Display | I2C-Verbindung 16x2 Pixel Auflösung Hintergrundbeleuchtung |
|
Gitter | 3 Teile Rechteckiger Ausschnitt für LEDs Bohrlöcher und Stifte zum Verbauen |
|
Controller | Design orientiert am SNES Controller Controller-Board mittig befestigt Clip-Verbindung zum einfachen öffnen |
Umsetzung (HW/SW)
Hardware
Die Hardware besteht aus der LED-Einheit und dem Controller
LED-Einheit
Die LED-Einheit umfasst eine Bodenplatte, auf der 10 LED-Streifen des Typs WS2812b mit einer Länge von 20 LEDs in Form einer Matrix parallel aufgeklebt sind.
Auf der LED-Matrix wird ein Gitter montiert, welches als Abstandshalter zur Kunststoffscheibe fungiert und einen rechteckigen Leuchteffekt der LEDs erzeugt.
Unterhalb der Matrix ist das LCD-Display montiert.
Bodenplatte
Die in der Abbildung ... zu sehende Zeichnung zeigt die Bodenplatte.
Sie besteht aus einer 3mm Holzplatte, die auf 196mm Breite und 500mm Höhe zugeschnitten.
Es wurden die 6 Bohrlöcher des Gitters übertragen und der Ausschnitt des LCD-Displays ausgeschnitten.
LED-Matrix
Der in der Abbildung ... zu sehende Ausschnitt einer Zeichnung zeigt die LED-Matrix.
Sie besteht aus einem WS2812B Eco LED-Stripe, der auf 10 Abschnitte mit je 20 LEDs gekürzt wurde.
Hierbei war beim kleben zu beachten, dass die Streifen genau parallel mit 16,67mm Abstand liegen.
Gitter
Das auf Abbildung ... zu sehende Gitter ist als 3D Druck entworfen.
Jedes Loch sitzt über einer LED, wodurch sich ebenfalls ein Abstand von 16,67mm ergibt.
Das Gitter musst auf die Bodenplatte passen, wodurch sich eine Breite von 196mm ergibt.
Die Länge von 343,4mm kommt durch die Summe von 20 mal 16,67mm Abstand und 2 mal 5mm Wanddicke .
Zur Streuung des Lichts beträgt die Tiefe 16,67mm.
Durch das Bauvolumen, des Druckers Dremel 3D20, von 230x150mm musste das Gitter in 3 Teile geteilt werden.
Zur Verbindung der Gitterteile sind 4mm Stiftlöcher in die oberen und unteren Teile eingefügt worden.
Dem mittleren Teil wurden 3mm Stifte hinzugefügt.
Zur Befestigung der Gitterteile auf der Bodenplatte gibt es mittig auf jeder Seite 4mm Bohrungen.
Controller
Der in Abbildung ... zu sehende Controller ist optische dem SNES-Controller nachempfunden.
Anforderungen an den Controller waren, dass er zum einen das Controller-Board umfasst und ohne Schauben gefügt wird.
Das Controller-Board hat die Maße 70x30mm.
Es befinden sich mit dem Abstand von der Mitte aus 32,5mm und 17,5mm Löcher, um das Board zu befestigen.
Anhand dieser Maße gehen Stifte empor, die das Board in Position halten. Im unteren Teil
Elektronik
Software
Tetris ist wohl eines der am häufigsten programmierten Spiele der Welt und wurde deshalb nicht von Grund auf neu programmiert. Die Struktur des Programms wurde weitestgehend von ELECTRONOOBS übernommen. Der Code wurde optimiert und an unsere Hardware angepasst.
Zunächst wird
Programmcode
Komponententest
Ergebnis
Zusammenfassung
Lessons Learned
Projektunterlagen
Projektplan
Projektdurchführung
YouTube Video
Weblinks
Literatur
→ zurück zur Übersicht: WS 21/22: Angewandte Elektrotechnik (BSE)