2D Array drehen um jeweils 90°

Hallo zusammen,

Ich habe mir vorgenommen Tetris zu progammieren. Ich bin schon relativ weit gekommen, aber nun habe ich folgendes Problem:

Ich habe eine Matrix von 8x5 LEDs. Und nun möchte ich den Block drehen. Im Internet finde ich allerdings nur Anleitungen wie ich eine z.B. 4x4 oder 3x3, halt gleichmäßige Matrizen. Ich möchte nun aber meinen Block drehen. Allerding weis ich nicht wie, bzw. die Grunddreheung bekomme ich schon hin aber dann bewegt sich der Block an die falsche Stelle also dreht sich nicht um sich selbst sonder um den Matrixmittelpunkt.

Kann mir jemand helfen?

Vielen Dank im Vorraus

MfG bdorer

sonder um den Matrixmittelpunkt

Das ist doch schonmal ziemlich gut. :wink:

Ich nehme mal an, du meinst das Standard-Tetris mit den 7 verschiedenen 4-Klotz - Elementen ( I L J T O S Z ) .
wie speicherst du die denn ab? Und wie fügst du sie ein bzw. bewegst sie? Merk dir doch einfach zu jedem Element 4 mögliche Orientierungen. Den Element-Mittelpunkt musst du auch kennen. Und merken, wenn es am Rand ist. Und gelandet ! ...

8*5 ist für LEDS schonmal eine Fleiss-Arbeit (RGB oder einfarbig?) , für ein ganzes Tetris-Spiel aber recht klein ... :wink:

In der Tat ... 85 finde ich auch arg winzig. Wenn man sich die "normalen" Tetris-Spiele ansieht kommt man im Schnitt auf 2010 etwa. Vor 25 J. hatte ich Tetris unter DOS in Schwarz/Weiß nachprogrammiert und habe für jeden Stein die 4 Richtungen als Muster hinterlegt. Diese ließen sich recht einfach abrufen. Rechnet man 20*10 kommt man auf 200 Bytes fürs gesamte Spielfeld. Die Muster dann im PROGMEM hinterlegt - könnte die Sache gut funktionieren und auf 'nem Arduino unterzubringen sein.

Ich spinne den Gedanken gerade mal weiter - bei einer Einschränkung auf 168 könnte man 2 Stück 88 RGB-Displays übereinander anordnen. Dann hätte man etwas, mit dem sich schon akzetabel gut spielen läßt.

Hallo,

vielen Dank schon mal für die Schnellen Antworten. Ich weis es kommt sehr klein 8*5 aber das ist ein Shield für das Arduino Uno und ich möchte es einfach nur testen und danach vllt ein LED Tisch bauen oder etwas in diese Richtung.

Ich speicher die Blöcke in 3 verschiedenen Matrizen ab. Die erste Matrix speichert die festgesetzten Blöcke ab. Die zweite die 4 Blöcke die sich momentan bewegen, diese ist allerdings 12*5 groß da ich so besser die Blöcke erscheinen lassen kann. Diese müsste auch gedreht werden. Und die 3. Matrix speichert die aktuelle Situation, also die ersten beiden Matrizen zusammen.

michael_x wie soll ich mir den Mittelpunkt der Objekte berechnen oder Merken. Und ja es sind RGB LEDs und zwar die WS2812 die ich mit der NeoMatrix ansteuere.

MfG bdorer

Ich antworte mal schnell an Stelle von michael_x ... da ich so etwas schon mal gemacht habe. Damals hatte ich das selbe Problem. Als ich mir die Dinger genauer angesehen habe, erkannte ich dass da gar nix mit Mittelpunkt-Berechnung usw. notwendig ist. Am sinnvollsten erstellst du die Muster in Quadraten mit ungradzahliger Kantenlänge, dadurch hast du direkt deinen "Mittelpunkt", um den du deine Muster drehen kannst. Ich habe mal ein Bild gepinselt, welches das verdeutlichen soll. Nach dieser Methode hat das Progi damals sehr gut funktioniert und meine Kids waren begeistert.

LG, Rudi

tetris.jpg

tetris.jpg

Heise hat im Hacks/Make mal ein Bastelbeispiel eines Bilderrahmen mit Ledmatrix vorgestellt und darauf Tetris programmiert.

https://shop.heise.de/katalog/tetris-klon-mit-led-streifen

Grüße Uwe

Hallo Rudi,

vielen Dank schonmal für deine Mühe.

RudiDL5:
Am sinnvollsten erstellst du die Muster in Quadraten mit ungradzahliger Kantenlänge, dadurch hast du direkt deinen "Mittelpunkt", um den du deine Muster drehen kannst.

also ich speichere die Blöcke und die dazugehörigen Farben mit zahlen ab. 1=rot, 2=grün, 3=blau, usw. Wenn ich nun Abfrage mit welcher Rotation der Block generiert wurde und heraus finde wo der "oberste" bzw. der erste Block ist den ich mit einer For-Schleife die alle Pixel überprüft, kann ich ja Theoretisch deine kleineren Arrays erstellen und diese dann drehen lassen und dann wieder zurück in die große Matrix bringen oder?

MfG bdorer

Hallöle,

joa, fast richtig. Ich weiß nicht mehr ganz genau wie ich das vor 25 J. gemacht hatte, aber auch damals schon habe ich die Muster einfach nur aus einem großen Muster-Array abgerufen und zur Ausgabe gebracht. Dadurch hatte ich mir die ganze Rechnerei und das Pixelsuchen erspart.

Schau dir mal das anhängende Bild an: Du müsstes lediglich ein großes Array mit den besagten Mustern anlegen, aus dem du die einzelnen per Index abrufst. In meinem Beispiel sind je Muster 12 Bytes, je Stein komplett dann 48 Bytes notwendig:

Byte 1: Die Stein-Nummer 1..6 (oder so)
Byte 2: Die Drehrichtung 0..3
Byte 3: Kennzeichnet welche Richtung gerade aktuell ist
Byte 4..12: Das eigentliche Muster das (je nach "aktuell") abgerufen werden soll

Bei jedem Tastendruck musst du dann lediglich aktualisieren, welche Drehrichtung du gerade gewählt hast und bringst das passende Muster zur Anzeige. Bei einer Spielfeld-Breite von 5 LEDs sind links und rechts 1..2 Spalten zusätzlich für die internen "Überstände" zu rechnen. Wenn ich dein 8*5-Feld zu Grunde lege komme ich inkl. Überstände, Muster-Array und vorbereitende Matrix bei 8 Steinen auf etwas über 500 Bytes SRAM für den reinen Spielbetrieb. Die notwendige Verwaltung drum herum braucht natürlich auch etwas, aber insgesamt dürfte das alles locker in einen UNO passen.

Ich habe allerdings keine Ahnung wie viel dein NeoMatrix verbraucht. Sollte des eng werden könnte man o.g Muster-Array auch auf Bit-Ebene verwalten, wäre aber dann etwas mehr Programmier-Aufwand. Ebenso lassen sich die Muster im PROGMEM hinterlegen, was auch gehen würde. Kommt halt nur darauf an, wie sich das in Richtung End-Ausbau entwickelt.

So 'n Tetris-Tisch habe ich schon mal gesehen, das ist echt spassig!

Viel Erfolg
Rudi

Hallo Rudi,

RudiDL5:
Byte 1: Die Stein-Nummer 1..6 (oder so)
Byte 2: Die Drehrichtung 0..3
Byte 3: Kennzeichnet welche Richtung gerade aktuell ist
Byte 4..12: Das eigentliche Muster das (je nach "aktuell") abgerufen werden soll

Meinst du ich kann statt deinen 12 Byte auch eine 4D Array erstellen?

also ich meine die 1. Dimension ist der Block an sich, also I,T,J,L oder wie auch immer.

Die 2. Dimension sind die Richtungen in die der Block gedreht werden kann.

Und die 3. und 4. Dimension ist für die Anordnung der Pixel zuständig.

int MatrixFiguren[7][4][4][4] = {
  //Figur1 I
  {
    { //Richtung 1
      {0, 0, 6, 0},
      {0, 0, 6, 0},
      {0, 0, 6, 0},
      {0, 0, 6, 0}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {6, 6, 6, 6},
      {0, 0, 0, 0}
    },

    { //Richtung 3
      {0, 0, 6, 0},
      {0, 0, 6, 0},
      {0, 0, 6, 0},
      {0, 0, 6, 0}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {6, 6, 6, 6},
      {0, 0, 0, 0}
    },
  },

  //Figur2 J
  {
    { //Richtung 1
      {0, 0, 0, 0},
      {0, 0, 3, 0},
      {0, 0, 3, 0},
      {0, 3, 3, 0}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 3, 0, 0},
      {0, 3, 3, 3},
      {0, 0, 0, 0}
    },

    { //Richtung 3
      {0, 0, 0, 0},
      {0, 0, 3, 3},
      {0, 0, 3, 0},
      {0, 0, 3, 0}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 3, 3, 3},
      {0, 0, 0, 3}
    },
  },

  //Figur3 L
  {
    { //Richtung 1
      {0, 0, 0, 0},
      {0, 0, 5, 0},
      {0, 0, 5, 0},
      {0, 0, 5, 5}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 5, 5, 5},
      {0, 5, 0, 0}
    },

    { //Richtung 3
      {0, 0, 0, 0},
      {0, 5, 5, 0},
      {0, 0, 5, 0},
      {0, 0, 5, 0}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 0, 0, 5},
      {0, 5, 5, 5},
      {0, 0, 0, 0}
    },
  },

  //Figur4 O
  {
    { //Richtung 1
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 0, 4, 4},
      {0, 0, 4, 4}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 0, 4, 4},
      {0, 0, 4, 4}
    },

    { //Richtung 3
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 0, 4, 4},
      {0, 0, 4, 4}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 0, 4, 4},
      {0, 0, 4, 4}
    },
  },

  //Figur5 S
  {
    { //Richtung 1
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 0, 2, 2},
      {0, 2, 2, 0}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 2, 0, 0},
      {0, 2, 2, 0},
      {0, 0, 2, 0}
    },

    { //Richtung 3
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 0, 2, 2},
      {0, 2, 2, 0}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 2, 0, 0},
      {0, 2, 2, 0},
      {0, 0, 2, 0}
    },
  },

  //Figur6 T
  {
    { //Richtung 1
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 7, 7, 7},
      {0, 0, 7, 0}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 0, 7, 0},
      {0, 0, 7, 7},
      {0, 0, 7, 0}
    },

    { //Richtung 3
      {0, 0, 0, 0},
      {0, 0, 7, 0},
      {0, 7, 7, 7},
      {0, 0, 0, 0}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 0, 7, 0},
      {0, 7, 7, 0},
      {0, 0, 7, 0}
    },
  },

  //Figur7 Z
  {
    { //Richtung 1
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 1, 1, 0},
      {0, 0, 1, 1}
    },

    { //Richtung 2
      {0, 0, 0, 0},
      {0, 0, 0, 1},
      {0, 0, 1, 1},
      {0, 0, 1, 0}
    },

    { //Richtung 3
      {0, 0, 0, 0},
      {0, 0, 0, 0},
      {0, 1, 1, 0},
      {0, 0, 1, 1}
    },

    { //Richtung 4
      {0, 0, 0, 0},
      {0, 0, 0, 1},
      {0, 0, 1, 1},
      {0, 0, 1, 0}
    },
  },
};

Dann könnte ich ja theoretisch die Array einfach in die Matrix einsetzten und später erneut einsetzten und drehen wie ich möchte.

Ist meine Überlegung richtig? Dann könnte ich alle möglichen Blöcke vorspeichern und sie nachher auch einfacher generieren.

MfG bdorer

4D-Array?? Nee, ich bin fast überzeugt dass du hier viel zu kompliziert denkst und dich verhaspeln wirst. Sorry. Aber m.E. brauchst du nur 2 "flache" 2D-Arrays. Das erste nimmt alle Muster auf wie oben im Bild, das zweite ist das Spielfeld.

Ich habe zwar keine Ahnung, wie NeoMatrix funktionieren soll, aber das spielt ja auch (fast) keine Rolle. Letztendlich hast du als Ausgabe-Ziel doch nur ein 2D-Array 8*5, welches zunächst leer ist. Dieses sicherst du temporär, bevor irgend ein Event auftritt. Nun setzt du per Zufall das erste Muster "oben" ein. Wenn jetzt irgend ein weiterer Event auftritt ("Fallen", "Verschieben", "Drehen") holst du kurz das gesicherte Array zurück und setzt das aktuelle Muster mit neuer Position / Drehrichtung wieder ein. Danach sicherst wieder diesen nun aktuellen Stand.

Nach diesem Verfahren gehst du einfach weiter vor und füllst im Laufe der Zeit das ganze 2D-Ziel (welches ja direkt mit den LEDs korrespondiert) und siehst, wie sich die einzelnen Elemente verhalten. Natürlich musst du ständig prüfen, ob irgend ein Zielfeld "besetzt" ist, dann darf natürlich nicht gedreht oder geschoben werden. Erst wenn der aktuelle Stein nicht mehr weiter nach unten fallen oder gedreht oder verschoben werden kann, ist der nächste Zufalls-Stein für "oben" abzurufen.

Nach jedem Tastendruck bzw. "Herunterfallen" musst du auch immer wieder nachsehen, ob "unten" irgend eine Reihe voll ist. In diesem Fall ist die "volle" Zeile zu löschen, der komplette Rest "darüber" muss nun eine Position nach unten rutschen und die nun leere Zeile besetzen.

Das wäre im Grunde eigentlich alles und m.E. recht simpel zu lösen.

Oh weh, gerade erst sehe ich folgendes:

int MatrixFiguren[7][4][4][4]

Mit INT-Arrays schießt du dir den Speicher des UNO für dieses Programm schneller zu als du ahnst. Du solltest unbedingt BYTE oder sogar CHAR nutzen. Denn 744*4 INTs belegen schon 896 Bytes nur für die Muster. Das haut vorne und hinten nicht hin!

Baue besser ein char-Array (hier für 3 Steine als Beispiel gemäß obigen Bildern):

char musterGesamt[3][38] = 
{
  "0010111000010011010000111010010110010",
  "0020020020000222000020020020000222000",
  "0300300330000333300330030030000003333"
};

char patternAktuell[9] = "         ";

Belegt für 8 Steine lediglich 312 Bytes. Das 1. Byte je Kette ist dann die aktuelle Drehrichtung. Die restlichen 36 Bytes sind die Pattern mit allen Drehrichtungen (und Farb-Indizes). Der Zugriff erfolgt dann mittels:

offset = musterGesamt[2][0] -48;
for( byte x = 0; x < 9; x++ )
   patternAktuell[x] = musterGesamt[2][offset *9 +1 +x] ;

Heißt: "2" ist der Index welches Muster genutzt werden soll. "Offset" (Byte 0 in der Kette) zeigt jetzt die aktuelle Drehrichtung an und kann bei Bedarf neu gesetzt werden. In "patternAktuell" stehen dann exakt diese 9 Zeichen aus "Richtung 0 von Muster 2", welches du dann in 3er-Blöchen in deine Zielmatrix übertragen kannst. Später für die echte Ausgabe müssen die reinen ASCII-Zeichen aus "patternAktuell" noch mit -48 auf tatsächliche Byte-Werte runtergerechnet werden.

Ich hoffe du kommst damit erst mal klar
Rudi

RudiDL5:

char musterGesamt[3][38] = 

{
  "0010111000010011010000111010010110010",
  "0020020020000222000020020020000222000",
  "0300300330000333300330030030000003333"
};

char patternAktuell[9] = "        ";



Belegt für 8 Steine lediglich 312 Bytes. Das 1. Byte je Kette ist dann die aktuelle Drehrichtung. Die restlichen 36 Bytes sind die Pattern mit allen Drehrichtungen (und Farb-Indizes). Der Zugriff erfolgt dann mittels:



offset = musterGesamt[2][0] -48;
for( byte x = 0; x < 9; x++ )
  patternAktuell[x] = musterGesamt[2][offset *9 +1 +x] ;

Du hast quasi alle 4 möglichen Richtungen in eins der Mustergesamt eingespeichert und holst dir immer nur die 9 raus die du brauchst. Und dann willst du auch nur das bewegen in der Matrix wo die Anzeige darin ist. Und du bewegst dann quasi den Block und nicht die ganze Matrix. Verstehe ich das Richtig?

Ich frage da Ich wie gesagt 3 Matrizen habe. Eine für die gespeicherten Blöcke, eine für die Beweglichen und eine für die Anzeige, das heißt ich bräuchte die Matrix für die Beweglichen nicht mehr da diese durch deine ersetzt wird?

Wenn ich das so richtig verstehe kommt das vom Prinzip auf das gleiche raus wie ich das mit der 4D Array gemacht habe. Nur das ich mehr Speicherplatz brauche?

Du hast quasi alle 4 möglichen Richtungen in eins der Mustergesamt eingespeichert und holst dir immer nur die 9 raus die du brauchst.

Bis hier hin korrekt

Und dann willst du auch nur das bewegen in der Matrix wo die Anzeige darin ist. Und du bewegst dann quasi den Block und nicht die ganze Matrix.

Ebenso richtig. Denn egal ob ich drehe oder bewege... ich lese je nach aktuellem Stand immer wieder genau die 9 Bytes aus die ich brauche und lege sie dort ab, wo sie hin sollen. Beim "Bewegen" kann ich auf das zuletzt gelesene zurückgreifen, beim "Drehen" muss ich das nächste Muster gemäß Offset aus der Reihe lesen. Daher der Index im 1. Byte der Kette. Dieser muss nach einem Dreh-Vorgang natürlich neu gesetzt werden. Erst wenn ein Muster endgültig "abgesetzt" wird, ist der Dreh-Index wieder auf 0 zu setzen - oder halt auf einen anderen zufälligen Wert um beim nächsten Start "oben" in neuer Stellung zu erscheinen.

Man darf natürlich nicht vergessen, vor jedem Bewegungs-/Dreh-Vorgang das zuletzt in die Ziel-Matrix eingesetzte Muster dort wieder zu löschen, denn sonst bleiben beim Verschieben/Fallen irgendwelche Reste stehen.

Ich frage da Ich wie gesagt 3 Matrizen habe. Eine für die gespeicherten Blöcke, eine für die Beweglichen und eine für die Anzeige, das heißt ich bräuchte die Matrix für die Beweglichen nicht mehr da diese durch deine ersetzt wird?

Ich denke mal "ja, wahrscheinlich". Sorry, ich verstehe leider nicht warum hier 3 Arrays eingesetzt werden sollen/wollen. Wie oben beschrieben würde ich nur 2 "Haupt"-Arrays dafür einsetzen müssen. Muster und Ausgabe. Das 3. wäre nur temporär innerhalb einer Funktion, die für die neue Darstellung inkl. Prüfung auf freie Wege usw. notwendig ist. Aber jeder denkt da halt etwas anders.

Wenn ich das so richtig verstehe kommt das vom Prinzip auf das gleiche raus wie ich das mit der 4D Array gemacht habe. Nur das ich mehr Speicherplatz brauche?

Gut möglich. "Flache" 2D-Arrays sind m.E. wesentlich leichter zu überschauen und zu behandeln als wenn man in "Länge * Breite * Höhe * Irgendwas" denken müsste... behaupte ich mal. Und halt eben CHAR statt INT, spart Platz und Rechen-Zeit.

Nachsatz

Interessant wäre, wie Kollege bdorer mit meinen Tipps klar kommt und ob sein Progi vielleicht schon funktioniert. Ich habe nach o.g. Vorgaben dieses 8*5 Tetris auf die Schnelle noch einmal erstellt - und es funktioniert! Einziger Unterschied zu meinen Ausführungen oben: Dieses erwähnte temporäre 3. Array ist vollkommen überflüssig. Es reichen wirklich nur "Muster" und "Ausgabe" jeweils als 2D-Arrays ... und alles ist in Butter.

tetris.jpg

Falls Interesse besteht: Ich habe den Source hier eingefügt, ist aber kein C++ sondern Object-Pascal (DELPHI). Ohne Punktezähler und ohne Zufallsgenerator - aber das könnte man m.E. recht einfach nachrüsten. Nur für eine Übersetzung in C++ fehlt mir im Moment die Zeit und die Hardware.

LG Rudi

tetris.jpg

tetris.txt (11.6 KB)

Hallo,

bin immer wieder freudig erstaunt über solche gelöste Knoten im Hirn ... :slight_smile:

Daumenhoch :wink:

Hallo Rudi,

Vielen Vielen Dank für deine Hilfe und Mühen. Es funktioniert, allerdings habe ich es mit einer 4D-Array gemacht das war für mich übersichtlicher und einfacher einzubinden.

Vielen Dank natürlich auch noch an den Rest der mir geholfen hat. Und an das Forum allgemein ich habe noch nie in einem Forum so freundliche und schnelle Antworten erhalten.

MfG bdorer

allerdings habe ich es mit einer 4D-Array gemacht

Wow !
Du hast also für 6 Tetris-Elemente je 4 Orientierungen einer 2D Form? Oder?

Mir sind in der Regel schon 2D-Arrays zu kompliziert und ich nehme lieber ein eindimensionales Reihen-Array aus eindimensionalen Arrays aller Spalten-Werte einer Reihe.
Oft zeigt sich, dass Reihen und Spalten eigentlich sowieso ganz verschiedene Sachen sind und das ganze also ein Design-Fehler ist.
Für zweidimensionale Objekte würde ich mal eine Ausnahme machen :wink:

Es funktioniert

Daumenhoch :wink:

nehme lieber ein eindimensionales Reihen-Array

Mache ich normalerweise auch, zumal die Adressierung sich dabei auf reines Addieren / Subtrahieren der Indizes reduziert. Selbst bei meinem bekannten SudokuSolver UNO habe ich für das Feld nur eine eindimensionale Kette genommen. Lediglich die "Abstreichliste" darin ist in die 2. Dimension gegangen :wink: Für Tetris hätte eine eindimensionale Kette ebenfalls ausgereicht.

Egal, hauptsache es läuft.

michael_x:
Wow !
Du hast also für 6 Tetris-Elemente je 4 Orientierungen einer 2D Form? Oder?

So wie ich es davor gepostet habe ist die 4D-Matrix definiert dann gibt man nur in die Variabel den Wert der Figur dann der Richtung und dann ließt man quasi eine 2D Matrix aus und kann diese nacheinander einfügen.

//Zugriff auf einzelne Pixel

MatrixFigur[Figur][Richtung][Pixel_horizontal][Pixel_vertikal]