0

I'm trying to make a Tetris clone in C++ Builder. I started by making 2 classes. I have wrote them in a .h file and included it in my .cpp file:

class Stage
{
protected:
    int Width;
    int Height;
public:

    Stage()
    {
        Width=300;
        Height=733;
    }

    Stage(int Width0,int Height0)
    {
        Width=Width0;
        Height=Height0;
    }
};

This is my first class, Stage. I don't know for sure what this class should contain, so if you have any suggestions on what I should include in it, I'm glad to hear them.

class Tetromino : public Stage
{
protected:
    Tshape *Shape1;
public:
    Tetromino(Tshape *Shape1)
    {
        TetrisGame->Shape1->Width=33;
        TetrisGame->Shape1->Height=33;
        TetrisGame->Shape1->Brush->Color=clBlack;
        TetrisGame->Shape1->Left=10;
        TetrisGame->Shape1->Top=200;
    }

    void mLeft()
    {
        if(TetrisGame->Shape1->Left<=Widht)
            TetrisGame->Shape1->Left=TetrisGame->Shape1->Left+33;
    }

    void mRight()
    {
        if(TetrisGame->Shape1->Left>=Width)
            TetrisGame->Shape1->Left=TetrisGame->Shape1->Left-33;
    }

    void free_fall()
    {
        TetrisGame->PieceFallTime->Enabled=true;
    }
};

That's my second class, the class for pieces.

I know that that's not how it should work. I have to make a matrix for example let's say:

piece[4][4]=
    {(0,0,0,0),
     (0,0,0,0),
     (0,1,0,0),
     (1,1,1,0)}

but I still have no clue how I should make use of it. I think that I should put a shape where there is 1 and it should create my piece, but I don't know how to do that.

Right now I'm trying to figure out how to make a shape fall down and move it right or left with the keyboard. I did this before, but with no classes. It's a bit more complicated using classes and I don't understand.

I tried to initialize an object Tetromino x(Shape1) (the code is in form.cpp) having a Shape1 in my form. I'm thinking that this should force the Shape1 in my Form to use the methods that I wrote in my class, right?

But I'm getting this error:

[bcc32c Error] StartGame.cpp(110): unknown type name 'Shape1'

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    In `Tetromino`, what is `Tshape`? – NathanOliver Nov 26 '18 at 20:55
  • I think that "TShape *Shape1" is like saying "int x". So... a data type i'm thinking. –  Nov 26 '18 at 21:00
  • Where is the line of code that has this error? – drescherjm Nov 26 '18 at 21:18
  • 1
    Note that in `Tetromino(Tshape *Shape1)` `Shape1` is a variable local to the constructor. You also have a class member `Shape1` but that is not set and is shadowed by the parameter `Shape1`. And I have no idea what `TetrisGame->Shape1` is and why you make no use of the passed parameter. – drescherjm Nov 26 '18 at 21:21
  • @NathanOliver `Tshape` is likely mean to be [`TShape`](http://docwiki.embarcadero.com/Libraries/Rio/en/Vcl.ExtCtrls.TShape) instead - a VCL component provided by C++Builder. – Remy Lebeau Nov 26 '18 at 23:11
  • 2
    @g0dafk nothing in the code you have shown is trying to use `Shape1` as a type name. So, which line of code EXACTLY is line 110 of `StartGame.cpp`? Also note that `Tshape` should be `TShape` instead. Remember, C++ is a **case-sensitive** language. – Remy Lebeau Nov 26 '18 at 23:11
  • 1
    I would draw the tetromino directly on Canvas of your form ... see [Graphics rendering](https://stackoverflow.com/a/21699076/2521214) GDI and GDI Bitmap bullets ... – Spektre Nov 27 '18 at 04:34
  • I don't know if i'm doing it right, but for me it makes sense. I declared a Class named Tetromino with a Constructor that has parameters. Now i want to initialize an object of my class with this line "Tetromino x(Shape1);". I also have a Shape1 in my form so i'm thinking that if i have a Shape named Shape1 i can make a connection between my class and this Shape. I don't know exactly how i should draw my tetris pieces or what's the best way to do it. I honestly don't understand what's the difference between C++ and C++ Builder. All this forms confuses me. I used only the console till now. –  Nov 27 '18 at 19:13
  • 1
    @g0dafk What you describe to link the classes together is fine, so the issue has to be a mistake in your code that you have not shown yet. However, `TShape` only supports basic shapes (square, circle, etc), it is not suitable for Tetris pieces. I would use `TImage` instead, or like Spektre said, just draw images of Tetris pieces directly on the `Canvas` of your Form (or maybe a `TPaintBox` instead) in their `OnPaint` events, using `TBitmap` to hold the images in memory. – Remy Lebeau Nov 27 '18 at 19:18
  • 1
    @g0dafk As for the difference between C++ and C++Builder, the former is a coding language, the latter is just an IDE/compiler toolchain for the former. You can certainly use standard C++ coding practices in C++Builder, but it also has its own language extensions, too. Remember, C++ has no concept of UI development. You are simply utilizing one of C++Builder's provided UI frameworks. That doesn't change how you write your C++ code. – Remy Lebeau Nov 27 '18 at 19:22
  • Ok, so i decided on using a TBitmap and get my image like this ( hope it is the right way ). `Graphics::TBitmap *MyImage= new Graphics::TBitmap;` then `MyImage->LoadFromFile(MyImage.bmp);`so i'm storing the bmp file in MyImage, then `Canvas->Draw(10,10,MyImage);` to show my image on the form. But, if TBitmap is a class, how can i declare a TBitmap object in my class Tetromino? I want to use the same ideea but to implement it using a class created by me. –  Nov 27 '18 at 21:49
  • I tried to write something like: `class Tetromino { protected: TBitmap *MyPiece; public: Tetromino() { MyPiece->LoadFromFile("image.bmp"); Form1->Canvas->Draw(10,10,MyPiece); } ~Tetromino() { delete MyPiece; } };` But it says "Access violation at adress xxxxxxxx in module Project1.exe.Read at adress yyyyyyyy" when im trying to initialize an object with `Tetromino tetrispiece;` –  Nov 27 '18 at 22:08
  • @g0dafk you forgot create your bitmap object with `new` ... I added answer with simple Tetris example I coded quite some years ago... so you see what I had in mind with the bitmap ... – Spektre Nov 28 '18 at 09:58

1 Answers1

0

You forgot to use new for the bitmap in the constructor so you are loading bmp file into bitmap objects that is not yet created hence the access violation. You can use transparency for the tetrominos bitmaps ... you just need to activate it on target canvas (like your Form1 ..within its properties). However I would render my tetrominos directly into bitmap instead of using files... You can use bitmap as a screen buffer ...

Here simple BDS2006 C++/VCL Tetris example I coded with my students during one lecture years ago as they wanted to code a game and voted for tetris ... (its ugly but works ...)

preview

//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

// window
TForm1 *Form1;
// keyboard map
enum{
    _key_left =0,
    _key_right,
    _key_rot  ,
    _key_drop ,
    _keys
    };
int keys      [_keys];  // is key pressed ?
WORD keys_code[_keys];  // scan code

// back/double buffer
Graphics::TBitmap *bmp = new Graphics::TBitmap;
//  scoore,  position of falling tetromino,next shape
int score=0,px=0,py=0,novy_dielik=0;
// actual tetromino map
BYTE pm[4][4]=
    {
    0,0,1,0,
    0,1,1,0,
    0,0,1,0,
    0,0,1,0
    };

// game board size
const int map_xs=10;
const int map_ys=20;
BYTE map[map_xs][map_ys];
// tetrominos
BYTE shp[7*16]=
    {
    0,1,0,0,
    0,1,0,0,
    0,1,0,0,
    0,1,0,0,

    0,1,0,0,
    0,1,0,0,
    0,1,1,0,
    0,0,0,0,

    0,0,1,0,
    0,0,1,0,
    0,1,1,0,
    0,0,0,0,

    0,1,0,0,
    0,1,1,0,
    0,0,1,0,
    0,0,0,0,

    0,0,1,0,
    0,1,1,0,
    0,1,0,0,
    0,0,0,0,

    0,0,0,0,
    0,0,1,0,
    0,1,1,1,
    0,0,0,0,

    0,0,0,0,
    0,1,1,0,
    0,1,1,0,
    0,0,0,0
    };
//---------------------------------------------------------------------------
// tetromino colision test
//---------------------------------------------------------------------------
int step()
    {
    int i,j,x,y;
    for (i=0;i<4;i++)
     for (j=0;j<4;j++)
      if (pm[i][j]!=0)
        {
        x=px+j;
        y=py+i;
        if (x<0)          return 1;
        if (x>=map_xs)    return 1;
        if (y<0)          return 1;
        if (y>=map_ys)    return 1;
        if (map[x][y]!=0) return 1;
        }
    return 0;
    }
//---------------------------------------------------------------------------
// render game
//---------------------------------------------------------------------------
void draw()
    {
    // clear screen
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,bmp->Width,bmp->Height));

    // [game board]
    int x,y,a,i,j,adr;
    x=bmp->Width/(map_xs+4+3);
    y=bmp->Height/(map_ys+2);
    if (x<y) a=x; else a=y;

    // border
    x=a; y=a;
    bmp->Canvas->Pen->Color=clBlue;
    bmp->Canvas->Pen->Width=a>>1;
    bmp->Canvas->Rectangle(x,y,x+(map_xs*a),y+(map_ys*a));
    bmp->Canvas->Pen->Width=1;

    // game board
    for (j=0;j<map_ys;j++)
        {
        for (i=0;i<map_xs;i++)
            {
            // set color from map
            if (map[i][j]==0) bmp->Canvas->Brush->Color=clBlack;
            else              bmp->Canvas->Brush->Color=clAqua;
            // grid cell
            bmp->Canvas->FillRect(TRect(x,y,x+a,y+a));
            x+=a;       // posun sa o policko vpravo
            }
        x-=map_xs*a;    // vrat sa vlavo
        y+=a;           // posun sa o policko dole
        }
    y-=map_ys*a;        // vrat sa hore

    // falling tetromino
    x+=px*a;
    y+=py*a;
    for (j=0;j<4;j++)
        {
        for (i=0;i<4;i++)
            {
            // cell
            if (pm[j][i]!=0)
                {
                bmp->Canvas->Brush->Color=clGreen;
                bmp->Canvas->FillRect(TRect(x,y,x+a,y+a));
                }
            x+=a;       // posun sa o policko vpravo
            }
        x-=4*a;         // vrat sa vlavo
        y+=a;           // posun sa o policko dole
        }
    y-=4*a;             // vrat sa hore
    x-=px*a;
    y-=py*a;

    x+=(map_xs+1)*a;    // posun sa na pravy stlpec

    // score:
    bmp->Canvas->Font->Color=clWhite;
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->TextOutA(x,y,AnsiString().sprintf("Score: %i",score));
    y+=a+a;

    // border
    bmp->Canvas->Pen->Color=clBlue;
    bmp->Canvas->Pen->Width=a>>1;
    bmp->Canvas->Rectangle(x,y,x+(4*a),y+(4*a));
    bmp->Canvas->Pen->Width=1;

    adr=16*novy_dielik;
    for (j=0;j<4;j++)
        {
        for (i=0;i<4;i++)
            {
            // set color from map
            if (shp[adr]==0) bmp->Canvas->Brush->Color=clBlack;
            else             bmp->Canvas->Brush->Color=clAqua;
            // nakresli stvorcek
            bmp->Canvas->FillRect(TRect(x,y,x+a,y+a));
            x+=a;       // go right
            adr++;
            }
        x-=4*a;         // return left
        y+=a;           // go down
        }
    y-=4*a;             // return up

    Form1->Canvas->Draw(0,0,bmp);
    }
//--- konstruktor okna ------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
    {
    keys_code[_key_left ]='O';
    keys_code[_key_right]='P';
    keys_code[_key_rot  ]='Q';
    keys_code[_key_drop ]='A';

    // new game
    novy_dielik=rand()%7;       // new tetromino
    px=(map_xs>>1)-2;           // position top/middle
    py=0;
    int adr=16*novy_dielik;
    for (int j=0;j<4;j++)       // copy it to pm
     for (int i=0;i<4;i++)
        {
        pm[j][i]=shp[adr];
        adr++;
        }
    for (int j=0;j<map_ys;j++)  // clear map
     for (int i=0;i<map_xs;i++)
      map[i][j]=0;
    novy_dielik=rand()%7;
    }
//--- resize event ----------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    bmp->Width=ClientWidth;
    bmp->Height=ClientHeight;
    draw(); // force redraw
    }
//--- redraw event ----------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    {
    draw();
    }
//--- falling timer ---------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
    py++;
    if (step()) // tetromino hit the ground?
        {
        py--;
        int i,j,x,y,adr,e;      // copy it to map
        for (j=0;j<4;j++)
         for (i=0;i<4;i++)
          if (pm[j][i])
            {
            x=px+i;
            y=py+j;
            map[x][y]=1;
            }
        px=(map_xs>>1)-2;       // new tetromino top/middle position
        py=0;
        adr=16*novy_dielik;
        for (j=0;j<4;j++)       // copy it to pm
         for (i=0;i<4;i++)
            {
            pm[j][i]=shp[adr];
            adr++;
            }

         // map full line test
        int dscore=0;
        for (j=map_ys-1;j>=0;j--)
            {
            e=1;
            for (i=0;i<map_xs;i++)
             if (map[i][j]==0)
                {
                e=0;
                break;
                }
            if (e)
                {
                dscore<<=1;
                dscore+=100;
                e=j;
                for (j=e-1;j>=0;j--)
                 for (i=0;i<map_xs;i++)
                  map[i][j+1]=map[i][j];
                j=e+1;
                draw();
                Sleep(200);
                }
            }
        score+=dscore;

        if (step()) // end game? (no new tetromino)
            {
            int q;
            q=bmp->Canvas->Font->Height;
            bmp->Canvas->Font->Height=40;
            bmp->Canvas->Font->Color=clRed;
            bmp->Canvas->Brush->Style=bsClear;
            AnsiString s="Game over";
            bmp->Canvas->TextOutA(
                (bmp->Width-bmp->Canvas->TextWidth(s))>>1,
                (bmp->Height-bmp->Canvas->TextHeight(s))>>1,s);
            bmp->Canvas->Brush->Style=bsSolid;
            bmp->Canvas->Font->Height=q;
            Form1->Canvas->Draw(0,0,bmp);
            Sleep(1000);

            // new game
            novy_dielik=rand()%7;       // new tetromino
            px=(map_xs>>1)-2;           // top/middle
            py=0;
            int adr=16*novy_dielik;
            for (int j=0;j<4;j++)       // copy it to pm
             for (int i=0;i<4;i++)
                {
                pm[j][i]=shp[adr];
                adr++;
                }
            for (int j=0;j<map_ys;j++)  // clear board
             for (int i=0;i<map_xs;i++)
              map[i][j]=0;
            novy_dielik=rand()%7;
            }
        novy_dielik=rand()%7;           // random next tetromino 0-6
        }
    draw();
    }
//--- keyboard timer --------------------------------------------------------
void __fastcall TForm1::Timer2Timer(TObject *Sender)
    {
    int e=0;
    if (keys[_key_left ]!=0)
        {
        px--; if (step()) px++; else e=1;
        }
    if (keys[_key_right]!=0)
        {
        px++; if (step()) px--; else e=1;
        }
    if (keys[_key_rot  ]!=0)
        {
        int i,j;
        BYTE ori[4][4];
        for (j=0;j<4;j++) for (i=0;i<4;i++) ori[i][j]=pm[i][j];
        for (j=0;j<4;j++) for (i=0;i<4;i++) pm[3-j][i]=ori[i][j];
        if (step()) for (j=0;j<4;j++) for (i=0;i<4;i++) pm[i][j]=ori[i][j];
        else e=1;
        }
    if (keys[_key_drop ]!=0)
        {
        py++; if (step()) py--; else e=1;
        }
    if (e) draw();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,
      TShiftState Shift)
    {
    Caption=Key;
    for (int i=0;i<_keys;i++)   // test all keys
     if (Key==keys_code[i])     // if match
      keys[i]=1;                // set it as pressed
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    for (int i=0;i<_keys;i++)   // test all keys
     if (Key==keys_code[i])     // if match
      keys[i]=0;                // set it as released
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
    {
    for (int i=0;i<_keys;i++)   // set all keys
      keys[i]=0;                // as released
    }
//---------------------------------------------------------------------------
---------------------------------------

Its a single Form App with 2 timers on it

Form


Timer1 is 250ms
Timer2 is 80ms

If you want to use mine you have to:

  1. create empty form App
  2. place 2 timers on it and set the intervals
  3. create all events my code is using
  4. copy the event and game codes to correct places

But you should use this more like as hint how the game architecture could look like. I know its ugly and without classes (those students where not yet OOP capable and we coded the game together so some consensus on all parts where made with the aim to keep this simple...)

So the bmp is like screen back buffer (to avoid flickering) controlling is done by O P Q A keys. One timer control the falling animation/simulation and the other handles keyboard. Rendering is done by using VCL encapsulated GDI and the bmp ...

for more info see:

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Wow, thank you for the example ! It's really awesome to see the game written like this. I also tried the code myself and it works flawless. Now i will try to figure out how can i create two classes based on your version of the game. I will come back if i encounter any problems or if i have questions. –  Nov 28 '18 at 22:29
  • Hi again, can you please tell me how can i implement your ideea with BYTE map[map_xs][map_ys] in a class? It says : " Constant expression required " even if i'm using const int. –  Dec 01 '18 at 14:39
  • @g0dafk well some weird compilers like (VC++/GCC) handles `const int` as a variable ... in such case use #define instead like `#define map_xs 10` ... – Spektre Dec 01 '18 at 20:10
  • I need some help again. As i said i'm trying to make a project that has 2 classes, and i want to transform your version. I have a problem with the two matrix pm and shp. I want to make a class Stage that has the shp matrix and i want to make another class that inherits this class and is able to use shp matrix to. I tried something like `BYTE shp[7][16];' then i created a constructor `STAGE():shp{{values},{values},...,{values}}{}` but it won't work... Can you suggest me a way ? How should i do this? –  Dec 04 '18 at 21:37
  • @g0dafk add the code as an edit into your question and comment me ... so I can see what exactly you got ... reading code from comments is very hard and it usually got truncated anyway... – Spektre Dec 05 '18 at 08:13
  • I solved the problem. It was because of the IDE. The 32 bit version is kinda old and i can't do this with it. I changed it to 64 bit and works. Another question that i have is : Can i make your code to work with classes? I'm not entirely sure but i think it is possible, right? I just need to make the right classes constructors and functions and it should be fine, right? –  Dec 05 '18 at 14:04
  • @g0dafk of coarse you can ... most of my games and demos are class based ... as I mentioned this one was not because I coded it with students during lecture and their knowledge was very limited at that time... so using classes would just make it incomprehendable for them. Take a look at this: [How to implement a constraint solver for 2-D geometry?](https://stackoverflow.com/a/40827518/2521214) its small C++/VCL based simulation that uses clases (does not differ much from a game) – Spektre Dec 05 '18 at 14:31
  • @g0dafk I usually got some base class for the whole game that contains all the stuff like map, players lists, key/mouse stuff, window api like draw/resize/onmouse/onkey to handle events and a bitmap holding the game screen ... sub classes are usually not inherited but directly instanced as members instead ... – Spektre Dec 05 '18 at 14:40
  • Hi again sir! Can i ask you how should i access Key ( from Key Up/Down/Active ) and Caption inside a class? I'm not able to access them out of their events. Is it possible? I followed your advice and i created 2 classes: One that contains all the variables and matrices and things like that and the other one which contains the functions. I hope it is correct and it counts as inheritance. –  Dec 07 '18 at 19:28
  • @g0dafk create a member functions that handles key/mouse in your master game class and call it from `Form` events ... that way you do not need access to form stuff... Also why access Caption? you can print stuff directly on bitmap ... – Spektre Dec 07 '18 at 19:31
  • I'm back with another question. I managed to put everything into classes and it works ! But i have only 1 problem. After 2 rounds, the pieces won't "fall" anymore. It's just the black stage. The code is exactly the same as yours. What could be the problem? It works flawless until i lose 2 times and after the second "Game Over" the stage stays black and nothing happens. –  Dec 20 '18 at 13:57
  • @g0dafk hard/impossible to say without seeing the code... try to debug using breakpoints and trace ... You most likely forget something to port or call in specific order – Spektre Dec 20 '18 at 14:27