Elegant array in subarray kopieren - 2D // Tetris Block Repräsentation



  • Hallo zusammen,

    ich habe das Gefühl, dass diese Frage blöd ist, aber nun gut:

    ich möchte folgendes machen (einmal in Bild, einmal in Code), ich finde es so leichter zu erklären, als in Worten:

    // c steht für "I want to copy this"
    array1 = std::array<std::array<T, 4>, 2>;:
    ------
    |cccc|
    |cccc|
    ------
    --> array2 = std::array<std::array<T, 10>, 22>:
    ------------
    |000cccc000|
    |000cccc000|
    |0000000000|
    // ....
    

    Wie ich es bisher mache:

    auto array1 = //...
    for (std::size_t row = 0; row != 2; ++row)
    {
        for (std::size_t cell = 3; cell != 7; ++cell)
        {
            array2[row][cell] = array1[row][cell - 3];
        }
    }
    

    Ich weiß nicht, ob da irgendwas mit copy oder copy_n geht. Ich komme auf jeden Fall auf nichts. Ich will also 'einfach' das komplette, kleinere 2D array1 an eine bestimmte Stelle in 2D array2 kopieren und frage mich, ob es da eine gute Methode gibt.

    EDIT: Worauf ich jetzt grad noch gekommen bin:

    for (std::size_t row = 0; row != 2; ++row)
    {
        std::copy_n(std::begin(array1[row]), 4, std::begin(array2[row]) + 3);
    }
    // bzw.
    for (std::size_t row = 0; row != 2; ++row)
    {
        std::copy(std::begin(array1[row]), std::end(array1[row]), std::begin(array2[row]) + 3);
    }
    

    Was ich mir irgendwie vorstelle:

    std::magical_2d_copy(array1.begin(), array1.end(), array2.begin() + 3);
    

    Falls jemand eine gute Idee hat, oder mir einfach nur getrost sagen kann, dass meine bisherige Variante garnicht so übel ist, bzw. die anderen nur noch übler wären, ich würde mich freuen.

    LG
    HarteWare

    P.S.: Wen es interessiert, es geht darum ein Tetris Spiel zu machen und das kleine Array sind die Tetrominos und das große das Spielfeld.



  • HarteWare schrieb:

    P.S.: Wen es interessiert, es geht darum ein Tetris Spiel zu machen und das kleine Array sind die Tetrominos und das große das Spielfeld.

    In dem Fall würde ich mich gar nicht erst mit der Frage des Kopierens von 2D Bereichen beschäftigen. Jedes Tetromino besteht aus exakt 4 Steinen...

    void putT(int x, int y, Board& board)
    {
      board(x, y) = board(x, y + 1) = board(x - 1, y) = board(x + 1, y) = BLOCK_T;
    }
    
    void putI(int x, int y, Board& board)
    {
      board(x, y) = board(x, y + 1) = board(x, y + 2) = board(x + 3, y + 3) = BLOCK_I;
    }
    
    void putJ(int x, int y, Board& board)
    {
      board(x, y) = board(x, y + 1) = board(x, y + 2) = board(x - 1, y + 2) = BLOCK_J;
    }
    
    ...
    


  • Interessant, es ist eben so, ich versuche es komplett alleine zu entwickeln, ohne irgendwelche online Tutorials zu lesen (abgesehen von der Spezifikation), und da kann es gut sein, dass so mancher Ansatz nicht so optimal ist. Man wird aber recht gut im Debugging 😃

    Wie sieht es dann aber aus beim Rotieren der Steine? Werden dann alle Richtungen "gehardcoded" und ebenfalls "geputtet"? Ich mache es bisher so, dass ich halt ein Subarray, welches das Tetromino enthält und auch die rotierte Form beinhalten kann, kopiere und dann rotiert wieder einsetze (also rotiert as in 90° im Uhrzeigersinn). Außerdem kann ich zusätzlich prüfen, ob evtl. irgendwelche Steine im Weg wären (da diese ja mitkopiert werden).

    Zum alleinigen erzeugen der Steine im Feld ist das aber vermutlich etwas leichter, als meine momentane Umsetzung. Ich werde mal sehen ob sich da nicht etwas verbessern lässst, danke.

    LG



  • Um das Problem mit dem Rotieren zu lösen, könnte man das Ganze folgendermaßen erweitern:

    template <typename Transform>
    void putT(Transform place, Board& board)
    {
      board(place(0, 0)) = board(place(0, 1)) = board(place(-1, 0)) = board(place(1, 0)) = BLOCK_T;
    }
    
    template <typename Transform>
    void putI(Transform transform, Board& board)
    {
      board(place(0, 0)) = board(place(0, 1)) = board(place(0, 2)) = board(place(0, 3)) = BLOCK_I;
    }
    
    template <typename Transform>
    void putJ(Transform transform, Board& board)
    {
      board(place(0, 0)) = board(place(0, 1)) = board(place(0, 2)) = board(place(-1, 2)) = BLOCK_J;
    }
    
    ...
    
    struct Pos
    {
      int x;
      int y;
    };
    
    class RotatedLeft
    {
      Pos p;
    public:
      RotatedLeft(Pos p) : p(p) {}
    
      Pos operator()(int x, int y) const
      {
        return { -y + p.x, x + p.y };
      }
    };
    
    class RotatedRight
    {
      Pos p;
    public:
      RotatedRight(Pos p) : p(p) {}
    
      Pos operator()(int x, int y) const
      {
        return { y + p.x, -x + p.y };
      }
    };
    
    putT(RotatedLeft({x, y}), board);
    


  • Hallo dot,

    das ich echt cool! Auf diese transform Geschichte mit Functionobjects wäre ich nie gekommen..

    Ich werde jetzt auf jeden Fall eine neue Version machen, welche dieses Prinzip anwendet!

    Noch ein paar Fragen:
    - Diese RotatedLeft/Right Transformationen sind ja ne einmalige Sache oder, d.h. ich müsste dann noch RotatedUp/Down implementieren, korrekt?

    - Die Transformationen funktionieren bei fast allen Formen blendend, außer bei I. Hier liegt nämlich der "Rotationspunkt" nicht innerhalb einer der Blöcke, sondern auf den Ecken der beiden mittleren Blöcke. Wäre es somit akzeptable extra Transformationen nur für den I Tetromino zu entwerfen? Oder wäre das wieder schlechtes Design.

    Hier noch ein Link, wo man sieht (hoffentlich) was ich meine:
    http://vignette1.wikia.nocookie.net/tetrisconcept/images/3/3d/SRS-pieces.png/revision/latest?cb=20060626173148

    LG
    HarteWare



  • Ich wuerde ueberhaupt nichts von Hand rotieren sondern fuer alle Steine in allen 1/2/4 Richtungen ein Array anlegen mit ihrem Aussehen. Das kannst du dann entweder aus einer Datei oder hardcoded fuellen in etwa der Art:
    [code]
    {
    0, 0, 1, 0,
    0, 0, 1, 0,
    0, 0, 1, 0,
    0, 0, 1, 0
    },
    {
    0, 0, 0, 0,
    1, 1, 1, 0,
    0, 0, 0, 0,
    0, 0, 0, 0
    }
    ...
    {
    0, 1, 0,
    0, 1, 1,
    0, 1, 0,
    }
    ...
    //[code]

    Das ist IMHO die simpelste und uebersichtlichste Methode damit man auch jede beliebige Tetrisregel korrekt implementieren kann. Dann brauchst du dots Ansatz nur noch fuer das Verschieben der Teile zu benutzen, wo dann die lokalen Koordinaten innerhalb des Steins in globale Koordinaten des Wells umgerechnet werden. Du wirst aber trotzdem noch wesentlich mehr Tabellen einbauen muessen, z.b. fuer die Entry Position/Rotation und die Kickregeln da sich diese ebenfalls schwer algorithmisch erfassen lassen.



  • Hallo TGGC,

    vielen Dank für Deine Antwort. Eigentlich müsstest Du es ja wissen, es scheint mir als wärst Du ein Tetris Liebhaber oder so 😃

    Habe ich richtig versanden, dass die "hardcode" Variante mit 4 shapes á 7 Blocks = 28 shapes (plus diverse weitere Situationen für jeden Tetromino) die günstigste ist, da sich unter anderem auch Dinge wie Wall kicks, T-Spins und "kann ich an dieser Stelle überhaupt nach rechts rotieren" am leichtesten umsetzten lassen?

    LG
    HarteWare



  • Ich wuerde nichtmal von genau 4 Rotationen pro Stein ausgehen denn nur T, L und J brauchen wirklich 4 Zustaende. Viele Tetrisspiele setzen nicht diese 4 Zustände der Steine um, denn man kann dann aus dem Aussehen des Steines nicht mehr ablesen, was bei einer Rotation passieren wird. Als Beispiel: http://n-blox.com/ Das hat eine Tetris Lizenz, aber ganz andere Regeln als dein Bild darstellt (dieses ist aus irgendeinem Tetris Guideline Spiel reverse engineered).

    Ich würde mich daher mit meinem Code nicht direkt auf eine bestimmte Regeln festlegen. Auch nicht auf 4 Tetrominos, evtl. will man irgendwann ja mal einen Sonderstein bauen oder einen Big-Mode. Ich wuerde einfach einen Stein als ein Array von nxn-Matrizen definieren, rotieren bedeutet vorwaerts/ rueckwaerts durch das Array zu laufen. So kriegt man über diesen Teil der Regeln eine relativ gute Übersicht und bleibt flexibel. Beim Rotieren in der Naehe von Hindernissen wird es dann aber wirklich interessant eine sinnvolle Regel zu designen. Diese laesst sich dann oft nur noch in Tabellen mit Eintraegen für jeden einzelnen Fall ausdruecken, was eben schnell unübersichtlich wird.



  • Die Definition als Array habe ich schon drin, der einzige Unterschied ist, dass ich die arrays in einer Funktion erzeuge und zurückgebe (vllt. ändere ich das noch). Rotation habe ich auch durch wörtlich Rotieren eines Subarrays im Feld gemacht. Also halt die Größe und Position passend bestimmen und einfach schwups mit einer bestimmten Schleife rotieren.

    So habe ich jetzt auch

    rotieren bedeutet vorwaerts/ rueckwaerts durch das Array zu laufen.

    interpretiert.

    Wobei ich an dieser Stelle auch dots Version mit den Transformationen sehr gut finde, nur dass es für diesen einen Fall (I Tetromino) nicht hinhaut (ausgehend von dem Rotationsschema, dass ich bisher verwende).

    Ich werde einfach mal mit den bisherigen Vorschlägen und Ansätzen weiter machen und versuchen das Beste daraus zu machen. Eventuell erstelle ich dann nochmal einen extra Thread für dieses Thema.

    Ich möchte mich auf jeden Fall nochmals bei euch bedanken dot und TGGC , dass ihr euch die Zeit genommen habt mir was beizubringen.

    LG
    HarteWare



  • Natuerlich kann man die Steine auch mit einem Algorithmus drehen. Der braucht aber ein paar mehr Eingabedaten als einfach nur die Richtung um die gedreht werden soll. Das auf dem Bild wuerde sich evtl. schon durch einen Mittelpunkt fuer alle Rotationen loesen lassen. Das fuehrt dann aber zu "Umhergewobbele" des Steins wenn man dreht. Fuer eine allgemeine Loesung braucht man aber auch noch vier Translationen fuer die 4 Rotationsschritte um genau zu definieren wie der Stein wobbeln soll. Dann hat man aber eigentlich schon wieder soviel Einstellungen, die man auch nicht direkt im Kopf visualisieren kann, das man auch gleich alle maximal 7x4 Zustaende expliziert definieren kann.


Anmelden zum Antworten