I'm writing code for what should be a tetris game. It's early on, and right now it only displays a single piece (the piece that would be "falling" at that point), which is all it should do. The up arrow allows you to cycle forward (specifically only) through a randomly generated sequence of pieces (using the "bag" method). And, using the left and right arrows, you can rotate the pieces. Anyways, it's coded on a win32 platform, and I've found that after a certain number of frames (running WM_PAINT) the main HDC turns null and everything stops. It takes roughly twice as many frames of holding down the right or left arrow keys to achieve this as it does to hold down the up arrow key. Something odd is that after about 1000 frames the area of the console that I'm actively painting over (a 600x600 px frame) will go black (the hdc has not been nullified yet), and it's only when the up arrow is pressed that the hdc is nullified. What I suspect to be the problem is that the methods called when the up arrow key is pressed pass the hdc as a parameter to the in-class methods (tell me if this is poor practice or there's something I should be doing instead). I think the hdc might be corrupting or something (honestly I have no idea). The left and right arrow keys don't directly call methods with HDC's as a parameter. Because of case labels and how the win32 template is designed, I have to store an HDC who's scope falls out of any case-label, so that I can access the window's handle outside of the paint case (I feel like this is poor practice, but I'd like to understand why before I go out of my way to find a new way). I'll post the code, the stored HDC is called mainHDC, and it's defined just before the main case statement. To clue you in, I'll give an overview of the code structure:
The main .cpp file, which contains the basic win32 program, calls the Tetris class constructor in WM_PAINT and stores it universally like the mainHDC is and, when prompted, the "next" method (which brings the next piece), the "turnPiece" method (which rotates the piece clockwise or counter-clockwise based on the paramter), and the "paint" method which updates the screen, repainting the current piece. In the tetris class (which is in it's own header file), there is a sub-class called "pieces" which contain information about its objects, that are defined by another level of sub-classes named with a single character depicted by the pieces shape. The shape, color, and size of the pieces are stored in a 2d array of pointers (using COLORREF). "Pieces" contains its own "DrawObject" method which draws the object that calls it (which like all of the drawing/painting methods have an HDC as an argument). There's also a method that rotates the shape called "turnTet" (the "turnPiece" method relays the call from the main .cpp file to "pieces"). The only other applicable methods are those found in the "tetris" class which are "paint" and "next" (they ultimately paint the object). The WM_KEY cases, excluding the VK_UP case, don't use the saved hdc but rather use InvalidateRect().
Here's the applicable part of the .cpp file
int tempt = 2;
int tempvar = 0;
tetris *mainOBJ;
HDC mainHDC;
HDC testHDC;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN:
switch (wParam) {
case VK_LEFT:
mainOBJ->turnPiece(false);
InvalidateRect(hWnd, 0, FALSE);
break;
case VK_RIGHT:
mainOBJ->turnPiece(true);
InvalidateRect(hWnd, 0, FALSE);
break;
case VK_UP:
mainOBJ->next(mainHDC);
InvalidateRect(hWnd, 0, FALSE);
break;
}
break;
case WM_COMMAND:
//Non-applicable & has not been changed
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
mainHDC = hdc;
HPEN oldP;
HPEN newP;
COLORREF qLC;
qLC = RGB(0, 0, 0);
newP = CreatePen(PS_SOLID, 1, qLC);
oldP = (HPEN) SelectObject(hdc, newP);
tempt++;
//USED FOR COUNTING FRAMES WITH DEBUGGER
if (tempt % 1 == 0) {
if (tempvar == 0) {
//CONSTRUCTOR CALL
mainOBJ = new tetris(hdc);
tempvar++;
}
//PAINT METHOD CALL
mainOBJ->paint(hdc);
}
if (hdc == NULL) {
int x = 3;
int y = x + 3; //SET DEBUG_BREAK POINT HERE
}
testHDC = hdc;
SelectObject(hdc, oldP);
DeleteObject(newP);
EndPaint(hWnd, &ps);
}
break;
}
}
The header file, which contains the Tetris and piece classes
class tetris {
public:
class pieces {
private:
COLORREF **test;
int size;
public:
//Abbreviated for space
class O {};
class I {};
class S {};
class Z {};
class T {};
class L {};
class J {};
void setSize(int a) {
//Initializing the piece-array
test = new COLORREF*[a];
size = a;
int i = 0;
while (i < a) {
test[i] = new COLORREF[a];
i++;
}
}
void setShape(COLORREF **shape) {
test = shape;
}
void setColor(HDC hdc, COLORREF rgb) {
HPEN Penn = CreatePen(PS_SOLID, 1, rgb);
HPEN Peno = (HPEN)SelectObject(hdc, Penn);
}
static pieces getObject(char type) {
pieces Gen;
switch (type) {
case 'O':
{
//Identical (almost) to the other cases
O pcs = O();
Gen = *pcs.getObject();
return Gen;
}
break;
case 'I':
case 'S':
case 'Z':
case 'T':
case 'L':
case 'J':
return pieces();
}
void turnTet(bool clockwise) {
int i = 0;
int s;
COLORREF **shape;
int ter = size - 1;
shape = new COLORREF*[2];
while (i < size) {
shape[i] = new COLORREF[2];
s = 0;
while (s < size) {
shape[i][s] = def;
s++;
}
i++;
}
i = 0;
while (i < size) {
s = 0;
while (s < size) {
if (clockwise) {
shape[s][ter - i] = test[i][s];
}
else {
shape[ter - s][i] = test[i][s];
}
s++;
}
i++;
}
test = shape;
}
void drawObject(HDC hWnd) {
int i = 0;
int s;
while (i < size) {
s = 0;
while (s < size) {
setColor(hWnd, test[i][s]);
int scaleOfBox = 90;
DrawBox((s + 1) * scaleOfBox, (i + 1) * scaleOfBox, scaleOfBox - 1, scaleOfBox - 1, hWnd);
s++;
}
i++;
}
}
void DrawBox(int x, int y, int w, int h, HDC hdc) {
if (h < 0) {
h *= -1;
}
if (w < 0) {
w *= -1;
}
int i = 0;
while (i < h) {
MoveToEx(hdc, x, y + i, NULL);
LineTo(hdc, x + w, y + i);
i++;
}
}
};
tetris(HDC hdc) {
refresh();
bagp[cur].drawObject(hdc);
}
void next(HDC hdc) {
bagp[cur].DrawBox(0, 0, 600, 600, hdc);
bagp[cur].drawObject(hdc);
cur++;
if (cur > 6) {
refresh();
}
}
void turnPiece(bool clockwise) {
bagp[cur].turnTet(clockwise);
}
void refresh() {
srand(time(NULL));
bag[0] = rand() % 7;
int i = 1;
while (i < 7) {
bool open = false;
cur = i;
while (!open) {
bag[i] = rand() % 7;
int s = 1;
open = true;
while (s <= i) {
if (bag[i] == bag[i - s]) {
open = false;
}
s++;
}
}
i++;
}
cur = 0;
while (cur < 7) {
switch (bag[cur]) {
case 0:
bagp[cur] = pieces::getObject('O');
break;
case 2:
bagp[cur] = pieces::getObject('T');
break;
case 1:
bagp[cur] = pieces::getObject('I');
break;
case 3:
bagp[cur] = pieces::getObject('S');
break;
case 4:
bagp[cur] = pieces::getObject('Z');
break;
case 5:
bagp[cur] = pieces::getObject('L');
break;
case 6:
bagp[cur] = pieces::getObject('J');
break;
}
cur++;
}
cur = 0;
}
void paint(HDC hdc) {
COLORREF temp = def;
bagp[cur].setColor(hdc, temp);
bagp[cur].DrawBox(0, 0, 600, 600, hdc);
bagp[cur].drawObject(hdc);
}
private:
int bag[7];
int cur;
pieces bagp[7];
};
I don't understand why the HDC nullifies like it does. Once again I suspect it's related to how the hdc is passed as a parameter or maybe how I'm saving the hdc. Help...please (and thank you).
Ooh, old school windows programming...
This may not be the entirety of the problem, but in your setColor
function you're creating a Pen with CreatePen
but never calling DeleteObject
on it. This will leak the resource and eventually cause problems when Windows runs out of resource handles for objects.
Also, the Device Context returned by BeginPaint is (generally) only valid until EndPaint is called (unless you specify otherwise in the CreateWindow call, if I remember rightly). This can be another source of handle leakage.
In the Process tab of Task Manager, you can add the "GDI objects" column which will have an increasing number as your program runs if you're leaking handles. I saw this once with something I wrote way back when.