• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.

Archived

This topic is now archived and is closed to further replies.

Evil_Greven

Tetris clone in an hour with C++.

149 posts in this topic

I often see people asking "How to make tetris?" or "how should I start my first game?" Well, here's a short and sweet lesson on making a simple tetris clone. No fancy stuff this time, just the basics. This mini-tutorial is strictly for C++. I'll assume at least basic familiarization. You must create this project as a Win32 Application project. It includes only three files, main.cpp, bitmapobject.cpp, and bitmapobject.h. EDIT: MetaCipher converted the code to SDL format, which can be found on page 6. Go have a look! In main.cpp:
//FALLING BLOCK GAME!
//main.cpp
//tell compiler to not include many unneeded header files.
#define WIN32_LEAN_AND_MEAN
//need this for windows stuff.
#include <windows.h>
//need this for srand and rand
#include <stdlib.h>
//now let's include our bitmapobject definitions
#include "bitmapobject.h"
          
That's all you need to start off. Next, let's include some handy-dandy defines that makes editing your program a thousand times easier. Seriously.
//let's give our window a name
#define WINDOWCLASS "FallingBlockGame"
//let's give our window a title...er caption.
#define WINDOWTITLE "A Falling Block Game!"
          
That takes care of Windows. Now let's think ahead. What should we use if we're going to make a tetris clone? Hmm... well, seems to me map tiles would work perfectly! Let's make a size for each tile, then make a size for the map, PLUS an 8-block wide sidebar.
//since we're using square blocks, let's only use a single size.
const int TILESIZE=16;
//now for the map...
const int MAPWIDTH=10;
const int MAPHEIGHT=30;
const int GREY=8;
          
Easy! Now, let's have some (you can use enumerations if you prefer) variables for the future bitmap. We need 9 colors, and 1 Do Not Draw, so 10 in all.
const int TILENODRAW=-1;
const int TILEBLACK=0;
const int TILEGREY=1;
const int TILEBLUE=2;
const int TILERED=3;
const int TILEGREEN=4;
const int TILEYELLOW=5;
const int TILEWHITE=6;
const int TILESTEEL=7;
const int TILEPURPLE=8;
          
Alright, that's done. Let's do some function prototypes. We will need Game Init, Game Loop, Game Done, Draw Tile, Draw Map, New Block, Rotate Block, Collision Test, Move Block, Remove Row, and New Game.
bool GameInit(); // game initialization function
void GameLoop(); //where the game actually takes place
void GameDone(); //clean up! 
void DrawTile(int x, int y, int tile); //coordinates & tile type
void DrawMap(); //draw the whole map.. render function, basically
void NewBlock(); //create a new block!
void RotateBlock(); //rotate a block.. if you can!
void Move(int x, int y); //coordinates to move.
int CollisionTest(int nx, int ny); //test collision of blocks
void RemoveRow(int row); //remove a row.. that would be the 'x'.
void NewGame(); //make a new game!
          
Pretty simple so far, eh? Now, let's throw in some global variables, bwahahahah! Global variables are preferred, because they are FAST. Speed is essential to a game. Thus, when you can, use them. But, be careful with them. The first two we need are an application handle and a main window handle.
HINSTANCE hInstMain=NULL; //main app handle
HWND hWndMain=NULL; //main window handle
          
These are necessary for windows programming. Remember this, always. Next, let's create an array to hold the play area map's tile types. We will add one extra row for Map to make Y-movement collision detection easier.
int Map[MAPWIDTH][MAPHEIGHT+1]; //the game map!
          
Now, let's make a little structure for a game piece, then two variables of that structure; one for the actual piece, and one for the preview piece.
struct Piece {
	int size[4][4];
	int x;
	int y;
};

Piece sPrePiece; //preview piece.
Piece sPiece; //the 's' prefixes indicate this is a 'structure'
          
Simple! Now, something we *REALLY* need is for timing. Let's call it start_time, of the DWORD type. We also need a GAMESTARTED boolean.
DWORD start_time;  //used in timing
bool GAMESTARTED=false; //used by NewBlock()
          
NOW, let's leave main.cpp briefly, and visit bitmapobject.h:
//BitMapObject.h
#ifndef BITMAPOBJECT_H
#define BITMAPOBJECT_H
#pragma once
//we need this for windows stuff.
#include <windows.h>
          
Just the usual basics. We need to make a class for the bitmap object.. to store the bitmap, that is. We need a handle to a device context.. that is, an HDC, which refers to an output device, or multiple output devices. We also need a pair of HBITMAP; one old, and one new. We also need a width and height of the bitmap.
class BitMapObject
{
private:
	//memory dc
	HDC hdcMemory;
	//new bitmap!
	HBITMAP hbmNewBitMap;
	//old bitmap.
	HBITMAP hbmOldBitMap;
	//width & height as integers.
	int iWidth;
	int iHeight;
          
Now, let's add some functions. We need, of course, a constructor and destructor, a Load function, a Create function, a Destroy function, functions to return iHeight/iWidth, and a conversion to HDC (bwahah). Then we're done with the .h file.
public:
	//constructor
	BitMapObject();

	//destructor
	~BitMapObject();

	//loads bitmap from a file
	void Load(HDC hdcCompatible,LPCTSTR lpszFilename);

	//creates a blank bitmap
	void Create(HDC hdcCompatible, int width, int height);

	//destroys bitmap and dc
	void Destroy();

	//return width
	int GetWidth();

	//return height
	int GetHeight();

	//converts to HDC
	operator HDC();
};

#endif
          
Now, we're done with that. Let's go on and make the functions for the class in the bitmapobject.cpp file:
//BitMapObject.cpp
#include "bitmapobject.h"
          
So we need the header file. Let's make the constructor, first, then the destructor. We need to set the values of the variables to 0/NULL (I prefer NULL on non-basic variables, and 0 on int/char/long/etc).
BitMapObject::BitMapObject()
{
	hdcMemory=NULL;
	hbmNewBitMap=NULL;
	hbmOldBitMap=NULL;
	iWidth=0;
	iHeight=0;
}

BitMapObject::~BitMapObject()
{
	//if the hdcMemory hasn't been destroyed, do so
	if(hdcMemory)
		Destroy();
}
          
Simple, yes? Now, let's load a bitmap! Fun! It really is easy.
void BitMapObject::Load(HDC hdcCompatible, LPCTSTR lpszFilename)
{
	//if hdcMemory isn't null, make it so captain!
	if(hdcMemory)
		Destroy();

	//create memory dc.
	hdcMemory=CreateCompatibleDC(hdcCompatible);
	//load the bitmap
	hbmNewBitMap=(HBITMAP)LoadImage(NULL,lpszFilename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
	//shove the image into the dc
	hbmOldBitMap=(HBITMAP)SelectObject(hdcMemory,hbmNewBitMap);
	//grab the bitmap's properties
	BITMAP bmp;
	GetObject(hbmNewBitMap,sizeof(BITMAP),(LPVOID)&bmp);
	//grab the width & height
	iWidth=bmp.bmWidth;
	iHeight=bmp.bmHeight;
}
          
Not hard. You might look up on MSDN for more info on the above functions, as they explain it better than I could :). Now, let's make that Create function!
void BitMapObject::Create(HDC hdcCompatible, int width, int height)
{
	//if hdcMemory isn't null, blow it up!
	if(hdcMemory)
		Destroy();

	//create the memory dc.
	hdcMemory=CreateCompatibleDC(hdcCompatible);
	//create the bitmap
	hbmNewBitMap=CreateCompatibleBitmap(hdcCompatible, width, height);
	//shove the image into the dc
	hbmOldBitMap=(HBITMAP)SelectObject(hdcMemory, hbmNewBitMap);
	//change the width and height.
	iWidth=width;
	iHeight=height;
}
          
Not much new here. Now let's build that Destroy() function we've used so much!
void BitMapObject::Destroy()
{
	//restore old bitmap.
	SelectObject(hdcMemory, hbmOldBitMap);
	//delete new bitmap.
	DeleteObject(hbmNewBitMap);
	//delete device context.
	DeleteDC(hdcMemory);
	//set members to 0/NULL
	hdcMemory=NULL;
	hbmNewBitMap=NULL;
	hbmOldBitMap=NULL;
	iWidth=0;
	iHeight=0;
}
          
Pretty easy. Lastly to finish up the .cpp, let's do the HDC conversion, and the Height/Width functions.
BitMapObject::operator HDC()
{
	//return hdcMemory.
	return(hdcMemory);
}

int BitMapObject::GetWidth()
{
	//return width
	return(iWidth);
}

int BitMapObject::GetHeight()
{
	//return height
	return(iHeight);
}
          
Beautiful! We're finished with this file, now we can go back to main.cpp, where we left off.. in Global variables. Why did I leave off where I did? Because we need this class. Badly. We need to make a variable with this class to handle the Map, and one to handle the Blocks.
//map for the program
BitMapObject bmoMap;
//block images
BitMapObject bmoBlocks;
          
That's that. Now, let's build a simple message handler. To make that more clear, Windows sends messages to the window all the bloody time. Some of those are useful to us, like a key being pressed or a mouse button being pressed. Most Windows applications have this function. DestroyWindow() beings the shutdown, and PostQuitMessage() tells the program it's exiting. The WM_PAINT might be confusing. It is called a LOT, and basically redraws the window. BitBlt() is the function we'll be using to draw our bitmaps. Yay Windows GDI! Don't have to bother with DirectX, and it still runs fairly well.
LRESULT CALLBACK TheWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
	//which message did we get?
	switch(uMsg)
	{
	case WM_KEYDOWN:
		{
			//check for escape key
			if(wParam==VK_ESCAPE)
			{
				DestroyWindow(hWndMain);
				return(0);//handled message
			}
			else if(wParam==VK_DOWN) //check for down arrow key
			{
				Move(0,1);
				return(0);//handled message
			}
			else if(wParam==VK_UP) //check for up arrow key
			{
				RotateBlock();
				return(0);//handled message
			}
			else if(wParam==VK_LEFT) //check for left arrow key
			{
				Move(-1,0);
				return(0);//handled message
			}
			else if(wParam==VK_RIGHT) //check for right arrow key
			{
				Move(1,0);
				return(0);//handled message
			}
		}break;
	case WM_DESTROY://the window is being destroyed
		{

			//tell the application we are quitting
			PostQuitMessage(0);

			//handled message, so return 0
			return(0);

		}break;
	case WM_PAINT://the window needs repainting
		{
			//a variable needed for painting information
			PAINTSTRUCT ps;
			
			//start painting
			HDC hdc=BeginPaint(hwnd,&ps);

			//redraw the map
			BitBlt(hdc,0,0,TILESIZE*MAPWIDTH+TILESIZE*GREY,TILESIZE*MAPHEIGHT,bmoMap,0,0,SRCCOPY);

			//end painting
			EndPaint(hwnd,&ps);
					
			//handled message, so return 0
			return(0);
		}break;
	}

	//pass along any other message to default message handler
	return(DefWindowProc(hwnd,uMsg,wParam,lParam));
}
          
Next, we come to the ever-formidable evil mountain that is known as WinMain(). This is where you make your window, and your pact with Bill Gates! Just smile and nod as you sign away your soul. But seriously, once you've made this window, you're going to use pretty much the same framework code for other games. Again, MSDN provides more uh, useful info on it. Yeah.
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
{
	//assign instance to global variable
	hInstMain=hInstance;

	//create window class
	WNDCLASSEX wcx;

	//set the size of the structure
	wcx.cbSize=sizeof(WNDCLASSEX);

	//class style
	wcx.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

	//window procedure
	wcx.lpfnWndProc=TheWindowProc;

	//class extra
	wcx.cbClsExtra=0;

	//window extra
	wcx.cbWndExtra=0;

	//application handle
	wcx.hInstance=hInstMain;

	//icon
	wcx.hIcon=LoadIcon(NULL,IDI_APPLICATION);

	//cursor
	wcx.hCursor=LoadCursor(NULL,IDC_ARROW);

	//background color
	wcx.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH);

	//menu
	wcx.lpszMenuName=NULL;

	//class name
	wcx.lpszClassName=WINDOWCLASS;

	//small icon
	wcx.hIconSm=NULL;

	//register the window class, return 0 if not successful
	if(!RegisterClassEx(&wcx)) return(0);

	//create main window
	hWndMain=CreateWindowEx(0,WINDOWCLASS,WINDOWTITLE, WS_BORDER | WS_SYSMENU | WS_CAPTION| WS_VISIBLE,0,0,320,240,NULL,NULL,hInstMain,NULL);

	//error check
	if(!hWndMain) return(0);

	//if program initialization failed, then return with 0
	if(!GameInit()) return(0);

	//message structure
	MSG msg;

	//message pump
	for( ; ; )	
	{
		//look for a message
		if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
		{
			//there is a message

			//check that we arent quitting
			if(msg.message==WM_QUIT) break;
			
			//translate message
			TranslateMessage(&msg);

			//dispatch message
			DispatchMessage(&msg);
		}

		//run main game loop
		GameLoop();
		
	}
	
	//clean up program data
	GameDone();

	//return the wparam from the WM_QUIT message
	return(msg.wParam);
}
          
Now we get to the beginnings of the actual game! WHEE! First, Initialization is the key to victory. We use a temporar rectangle (RECT rcTemp) to clear out the window. Then, we create the map image, and similiarly clear it out. Lastly, we load the blocks bitmap file (which you will have to create on your own, more on that at the bottom of this mini-tutorial). Let's also put in the GameDone().. it has no code in it, since there's no need for it currently. Hey, you can use the framework for your own games :)!
bool GameInit()
{
	//set the client area size
	RECT rcTemp;
	SetRect(&rcTemp,0,0,MAPWIDTH*TILESIZE+TILESIZE*GREY,MAPHEIGHT*TILESIZE);//160x480 client area
	AdjustWindowRect(&rcTemp,WS_BORDER | WS_SYSMENU | WS_CAPTION| WS_VISIBLE,FALSE);//adjust the window size based on desired client area
	SetWindowPos(hWndMain,NULL,0,0,rcTemp.right-rcTemp.left,rcTemp.bottom-rcTemp.top,SWP_NOMOVE);//set the window width and height

	//create map image
	HDC hdc=GetDC(hWndMain);
	bmoMap.Create(hdc,MAPWIDTH*TILESIZE+TILESIZE*GREY,MAPHEIGHT*TILESIZE);
	FillRect(bmoMap,&rcTemp,(HBRUSH)GetStockObject(BLACK_BRUSH));
	ReleaseDC(hWndMain,hdc);

	bmoBlocks.Load(NULL,"blocks.bmp");
	NewGame();

	return(true);//return success
}

void GameDone()
{
	//clean up code goes here
}
          
Piece of cake. Actually, I'm having a cookie, but anyway, let's make the Game Loop. I bet you think this part will be hard? HAH! Well, we need to do timing, but that's about it. GetTickCount() runs in milliseconds, thus this locks the game to 1fps, and 1-second movement. You can change this in various ways, such as 1000 to 33 (for 30fps hard lock) and changing the automatic Move(0,1) on a timer (ex: if(timer==30) {timer=0; Move(0,1); } timer++;)
void GameLoop()
{
	if( (GetTickCount() - start_time) > 1000)
	{
		Move(0,1);
		start_time=GetTickCount();
	}

}
          
See? SEE? I bet you didn't believe me, did you? Bah! Anyway, now to build the NewGame() function. We set up the start_time, and we initialize the Map array, then tell it to put in a new block & new
void NewGame()
{
	start_time=GetTickCount();
	GAMESTARTED=false;

	//start out the map
	for(int x=0;x< MAPWIDTH;x++)
	{
		for(int y=0;y< MAPHEIGHT+1;y++)
		{
			if(y==MAPHEIGHT) //makes Y-collision easier.
				Map[x][y]=TILEGREY;
			else
				Map[x][y]=TILEBLACK;
		}
	}
	NewBlock();
	
	DrawMap();
}
          
Easy. Now, let's build the DrawTile() function. Simple enough, take a pair of coordinates and place the tile (block) of the specified type.. err color... er you get the picture. edit: I want to explain BitBlt() more. The first variable it needs is an a DESTINATION handle to the device context (remember the operator HDC() function? bmoMap returns that). The second two are starting X and Y coordinates, respectively, and the upper left corner of a window is 0,0, and the bottom right just depends on the size you have; in this case, 160,480. The next two determine how MUCH of the bitmap is sent to bmoMap, that is, they are the width and height respectively (since we have a square, we're using one size: TILESIZE). After that, it's another HDC, this time bmoBlocks, which is the SOURCE. Then, we have another pair of coordinates; these two determine where on the bitmap you are starting at... that is, if you wanted to be able to draw the whole bitmap, you'd use 0,0, but if you wanted to draw only the bottom right of the bitmap, you would use (for 16x16) 8,8. Lastly, you can use either SRCAND, SRCPAINT, or SRCCOPY, and these determine HOW the hmoBlocks are copied to hmoMap: SRCCOPY is a straight copy of the source that overwrites whatever's in the destination in that area, SRCAND copies the source to the destination using the AND operation (and we use it for bitmasking), while SRCPAINT copies the source to the destination using the XOR operation.
void DrawTile(int x,int y,int tile)//put a tile
{
	//mask first
	BitBlt(bmoMap,x*TILESIZE,y*TILESIZE,TILESIZE,TILESIZE,bmoBlocks,tile*TILESIZE,TILESIZE,SRCAND);
	//then image
	BitBlt(bmoMap,x*TILESIZE,y*TILESIZE,TILESIZE,TILESIZE,bmoBlocks,tile*TILESIZE,0,SRCPAINT);
}
          
Now we have a simple tile drawing function in place! Works great. First, it masks the spot with a background image (so you can do like, see-through numbers or whatever. It prints an image from bmoBlocks to bmoMap (which basically functions as a buffer). Now let's do our main rendering function; DrawMap()! Real easy, just draw the toolbar, then the preview block on top of that, then draw the background blocks and the moving piece that the player controls on that!
void DrawMap()//draw screen
{
	int xmy, ymx;

	//place the toolbar
	for(xmy=MAPWIDTH; xmy< MAPWIDTH+GREY; xmy++)
		for(ymx=0; ymx< MAPHEIGHT; ymx++)
			DrawTile(xmy, ymx, TILEGREY);

	//draw preview block
	for(xmy=0; xmy<4; xmy++)
		for(ymx=0; ymx<4; ymx++)
			if(sPrePiece.size[xmy][ymx] != TILENODRAW)
				DrawTile(sPrePiece.x+xmy, sPrePiece.y+ymx, sPrePiece.size[xmy][ymx]);
	
	//draw the map
	//loop through the positions
	for(xmy=0;xmy< MAPWIDTH;xmy++)
		for(ymx=0;ymx< MAPHEIGHT;ymx++)
				DrawTile(xmy,ymx,Map[xmy][ymx]);

	//draw moving block
	for(xmy=0; xmy<4; xmy++)
		for(ymx=0; ymx<4; ymx++)
			if(sPiece.size[xmy][ymx] != TILENODRAW)
				DrawTile(sPiece.x+xmy, sPiece.y+ymx, sPiece.size[xmy][ymx]);

	//invalidate the window rect
	InvalidateRect(hWndMain,NULL,FALSE);
}
          
We're doing good! Now, let's tackle the NewBlock() function. This is where we generate our little blocks. Let's assume the default shapes that Tetris(TM) fans are familiar with. We'll need to generate a preview piece, then replace the piece the player uses when s/he's finished with the current one. We'll also need to do a special case generation for the initial startup of the game.
void NewBlock()
{
	int newblock;
	int i,j;
	//  0   1   2   3   4    5   6    
	//   X                             These
	//   X   XX   X  XX   XX  XX   XX  are
	//   X   XX  XXX  XX XX    X   X   block
	//   X                     X   X   types

	//begin game! make generate a block and then one in preview.

	srand(GetTickCount());


	//initialize the piece to all blank.
	for(i=0; i<4; i++)
		for(j=0; j<4; j++)
			sPiece.size[ i ][j]=TILENODRAW;

	sPiece.x=MAPWIDTH/2-2;
	sPiece.y=-1;

	//let's see if the game's started yet
	if(GAMESTARTED == false)
	{
		//guess not..  
		//Generate a piece right off.
		//From now on, use previous preview block.
		GAMESTARTED=true;

		newblock=rand()%7;

		switch (newblock)
		{
		case 0: //Tower!
			{
				sPiece.size[1][0]=TILERED;
				sPiece.size[1][1]=TILERED;
				sPiece.size[1][2]=TILERED;
				sPiece.size[1][3]=TILERED;
				sPiece.y=0;
			}break;
		case 1: //Box!
			{
				sPiece.size[1][1]=TILEBLUE;
				sPiece.size[1][2]=TILEBLUE;
				sPiece.size[2][1]=TILEBLUE;
				sPiece.size[2][2]=TILEBLUE;
			}break;
		case 2: //Pyramid!
			{
				sPiece.size[1][1]=TILESTEEL;
				sPiece.size[0][2]=TILESTEEL;
				sPiece.size[1][2]=TILESTEEL;
				sPiece.size[2][2]=TILESTEEL;
			}break;
		case 3://Left Leaner
			{
				sPiece.size[0][1]=TILEYELLOW;
				sPiece.size[1][1]=TILEYELLOW;
				sPiece.size[1][2]=TILEYELLOW;
				sPiece.size[2][2]=TILEYELLOW;
			}break;
		case 4://Right Leaner
			{
				sPiece.size[2][1]=TILEGREEN;
				sPiece.size[1][1]=TILEGREEN;
				sPiece.size[1][2]=TILEGREEN;
				sPiece.size[0][2]=TILEGREEN;
			}break;
		case 5://Left Knight
			{
				sPiece.size[1][1]=TILEWHITE;
				sPiece.size[2][1]=TILEWHITE;
				sPiece.size[2][2]=TILEWHITE;
				sPiece.size[2][3]=TILEWHITE;
			}break;
		case 6://Right Knight
			{
				sPiece.size[2][1]=TILEPURPLE;
				sPiece.size[1][1]=TILEPURPLE;
				sPiece.size[1][2]=TILEPURPLE;
				sPiece.size[1][3]=TILEPURPLE;
			}break;
		}
	}
	else
	{
		for(i=0; i<4; i++)
			for(j=0; j<4; j++)
				sPiece.size[ i ][j]=sPrePiece.size[ i ][j];

	}

	newblock=rand()%7;

	for(i=0; i<4; i++)
		for(j=0; j<4; j++)
			sPrePiece.size[ i ][j]=TILENODRAW;

	sPrePiece.x=MAPWIDTH+GREY/4;
	sPrePiece.y=GREY/4;

	switch (newblock)
	{
		case 0: //Tower!
			{
				sPrePiece.size[1][0]=TILERED;
				sPrePiece.size[1][1]=TILERED;
				sPrePiece.size[1][2]=TILERED;
				sPrePiece.size[1][3]=TILERED;
			}break;
		case 1: //Box!
			{
				sPrePiece.size[1][1]=TILEBLUE;
				sPrePiece.size[1][2]=TILEBLUE;
				sPrePiece.size[2][1]=TILEBLUE;
				sPrePiece.size[2][2]=TILEBLUE;
			}break;
		case 2: //Pyramid!
			{
				sPrePiece.size[1][1]=TILESTEEL;
				sPrePiece.size[0][2]=TILESTEEL;
				sPrePiece.size[1][2]=TILESTEEL;
				sPrePiece.size[2][2]=TILESTEEL;
			}break;
		case 3://Left Leaner
			{
				sPrePiece.size[0][1]=TILEYELLOW;
				sPrePiece.size[1][1]=TILEYELLOW;
				sPrePiece.size[1][2]=TILEYELLOW;
				sPrePiece.size[2][2]=TILEYELLOW;
			}break;
		case 4://Right Leaner
			{
				sPrePiece.size[2][1]=TILEGREEN;
				sPrePiece.size[1][1]=TILEGREEN;
				sPrePiece.size[1][2]=TILEGREEN;
				sPrePiece.size[0][2]=TILEGREEN;
			}break;
		case 5://Left Knight
			{
				sPrePiece.size[1][1]=TILEWHITE;
				sPrePiece.size[2][1]=TILEWHITE;
				sPrePiece.size[2][2]=TILEWHITE;
				sPrePiece.size[2][3]=TILEWHITE;
			}break;
		case 6://Right Knight
			{
				sPrePiece.size[2][1]=TILEPURPLE;
				sPrePiece.size[1][1]=TILEPURPLE;
				sPrePiece.size[1][2]=TILEPURPLE;
				sPrePiece.size[1][3]=TILEPURPLE;
			}break;
	}

	DrawMap();
}
          
Ah, randomization. You could adjust these values to a more weighted approach, but these are straight, equal chance randomizations. Now, let's build our rotation function, RotateBlock(). Simple and easy: copy & rotate to a temporary, then check collisions, then copy it back to the original. Like switching two variables, sorta.
void RotateBlock()
{
	int i, j, temp[4][4];

	//copy &rotate the piece to the temporary array
	for(i=0; i<4; i++)
		for(j=0; j<4; j++)
			temp[3-j][ i ]=sPiece.size[ i ][j];

	//check collision of the temporary array with map borders
	for(i=0; i<4; i++)
		for(j=0; j<4; j++)
			if(temp[ i ][j] != TILENODRAW)
				if(sPiece.x + i < 0 || sPiece.x + i > MAPWIDTH - 1 ||
					sPiece.y + j < 0 || sPiece.y + j > MAPHEIGHT - 1)
					return;

	//check collision of the temporary array with the blocks on the map
	for(int x=0; x< MAPWIDTH; x++)
		for(int y=0; y< MAPHEIGHT; y++)
			if(x >= sPiece.x && x < sPiece.x + 4)
				if(y >= sPiece.y && y < sPiece.y +4)
					if(Map[x][y] != TILEBLACK)
						if(temp[x - sPiece.x][y - sPiece.y] != TILENODRAW)
							return;

	//end collision check

	//successful!  copy the rotated temporary array to the original piece
	for(i=0; i<4; i++)
		for(j=0; j<4; j++)
			sPiece.size[ i ][j]=temp[ i ][j];
	
	DrawMap();

	return;
}
          
And you thought game programming was hard. Well, it can be. Just not Tetris-clones. Now, let's build our Move() function. Check for a collision, then move, or put piece to map / remove row / start a new game if you're too high.
void Move(int x, int y)
{
	if(CollisionTest(x, y))
	{
		if(y == 1)
		{
			if(sPiece.y<1)
			{
				//you lose!  new game.
				NewGame();
			}
			else
			{
				bool killblock=false;
				int i,j;
				//new block time! add this one to the list!
				for(i=0; i<4; i++)
					for(j=0; j<4; j++)
						if(sPiece.size[ i ][j] != TILENODRAW)
							Map[sPiece.x+i][sPiece.y+j] = sPiece.size[ i ][j];

				//check for cleared row!
				for(j=0; j< MAPHEIGHT; j++)
				{
					bool filled=true;
					for(i=0; i< MAPWIDTH; i++)
						if(Map[ i ][j] == TILEBLACK)
							filled=false;

					if(filled)
					{
						RemoveRow(j);
						killblock=true;
					}
				}

				if(killblock)
				{
					for(i=0; i<4; i++)
						for(j=0; j<4; j++)
							sPiece.size[ i ][j]=TILENODRAW;
				}
				NewBlock();
			}
		}

	}
	else
	{
		sPiece.x+=x;
		sPiece.y+=y;
	}

	DrawMap();
}
          
Now, let's put in the CollisionTest() function. Really easy, just testing bounds and if it its another block.
int CollisionTest(int nx, int ny)
{
	int newx=sPiece.x+nx;
	int newy=sPiece.y+ny;

	int i,j,x,y;

	for(i=0; i< 4; i++)
		for(j=0; j< 4; j++)
			if(sPiece.size[ i ][j] != TILENODRAW)
				if(newx + i < 0 || newx + i > MAPWIDTH - 1 ||
					newy + j < 0 || newy + j > MAPHEIGHT - 1)
					return 1;

	for(x=0; x< MAPWIDTH; x++)
		for(y=0; y< MAPHEIGHT; y++)
			if(x >= newx && x < newx + 4)
				if(y >= newy && y < newy +4)
					if(Map[x][y] != TILEBLACK)
						if(sPiece.size[x - newx][y - newy] != TILENODRAW)
							return 1;
	return 0;
}
    
Simple, yes? Now, the RemoveRow() function. Easy stuff here, just deleting a row and moving the rest down.
void RemoveRow(int row)
{
	int x,y;
	int counter=0;

	for(x=0; x< MAPWIDTH; x++)
		for(y=row; y>0; y--)
			Map[x][y]=Map[x][y-1];

}
          
And that's it! You've got the code, now you just need the graphics. To make the graphics, create a new bitmap of a size TILESIZE*9 width, and TILESIZE*2 height. Now, divide this into 16x16 squares, or whatever TILESIZE you have, and you'll have two rows of 9 columns. Make the bottom row totally black (0 r,0 g,0 b). Make each column of the top row a different color. In order, make the columns Black, Grey, Blue, Red, Green, Yellow, White, Steel, and Purple. Black and Grey should be totally 1 color, while (for a good look!) the other colors should be in this format:
x x x x x x x x x x x x x x x x
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
x o o o o o o o o o o o o o o m
m m m m m m m m m m m m m m m m
    
Where o = normal shade of a color, x = lighter shade of the color, and m = darker shade of the color. Enjoy. -Greven edit: annoying smilies. and the italic code. Gr. and the weird vanishing code. Double Gr. edit2: Re-added CollisionTest() function. Sorry about that. I had had it before, but somehow I had deleted it when trying to fix the vanishing code / italic code problems. My appologies. edit3: another annoying forum/code problem. added more detailed description of BitBlt() and timing edit4: changed the description of #define WIN32_LEAN_AND_MEAN, per correction from James Gregory edit5: Thanks for the SDL version, MetaCipher! [Edited by - Evil_Greven on July 13, 2004 12:13:46 AM]
1

Share this post


Link to post
Share on other sites
i think that is quite possibly the longest post i''ve seen on the gamedev forums... why not html-ize it and send it in as an article?
0

Share this post


Link to post
Share on other sites
Hi,

Have you left out the CollisionTest(int, int) function?
I can see the prototype but not the actual function(and neither can my compiler ).

Thanks for an excellent tutorial.

Tronn.
0

Share this post


Link to post
Share on other sites
Evil_Greven, perhaps you should put this in a HTML file, rather than on a topic at GameDev.net. Or, you could go here: http://gamedev.net/info/writers.asp



Quick Clickys: [ WiseElben.com | My Journal | nMagic | My Profile ]
"Give a man a fish and he will eat for a day. Teach a man how to fish and he will eat for a life time."
-Chinese Proverb
0

Share this post


Link to post
Share on other sites
Excellent. It now compiles and runs.:D

I did have to change this line:

wcx.nonononononononostyle=CS_OWNDC | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

To this:

wcx.nostyle=CS_OWNDC | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

After that it compiled fine apart from a couple of warnings.

Now I just need to go through it with fine toothed comb and work out how it all works.:D

Tronn
0

Share this post


Link to post
Share on other sites
Oops. I changed the line to:

wcx.nostyle=CS_OWNDC | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;

Tronn.
0

Share this post


Link to post
Share on other sites
Thats strange. The line I changed says ''wcx.style'' but every time I post it it changes to ''wcx.nostyle''.

Tronn
0

Share this post


Link to post
Share on other sites
rite, sorry amaeur - love the post, but do you create the bitmap with the class? im sorry this may sound really really bad, or in paint for example. im just trying to learn off the tutorial and study the code. thnxs again for the topic.
0

Share this post


Link to post
Share on other sites
Anonymous Poster,

Argh! Another blasted forum problem! Sigh. I think it says nononono several times because I had to edit it a few times, and every time, then, it changed it. Got it fixed, had to put a space between 'style' and '=' and that seems to work. Thanks for the heads up.

Daz_mk, you need to build the bitmap in like, MS Paint or something. Using a tile size of 16 pixels, make a bitmap with a height of 32 pixels and a width of 144. Make the entire bottom row of 16 pixels black. Then color the rest of it in 16x16 squares, and follow the format I have above.

-Greven


[edited by - Evil_Greven on November 23, 2003 11:18:44 AM]
0

Share this post


Link to post
Share on other sites
Hey nice tut man, havent gone thru it yet, But this should give me some great knowlage & experiance into makeing a game.
0

Share this post


Link to post
Share on other sites
In my humble opinion

pouya's tetris article was better


~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Any programmer can make snake.
The difference is that real game programmers would never think about using a linked list


[edited by - THe Alchemist on November 24, 2003 10:30:37 PM]
0

Share this post


Link to post
Share on other sites
Weird. It compiles for me, no errors or notin but when I run it I have the windows frame. The stuff I have in my background shows up in the space where the game is saposta appear. And when I exit I get a illegal operation. Wtf? O_o

I did change this
//create window class
WNDCLASSEX wcx; //set the size of the structure
wcx.cbSize=sizeof(WNDCLASSEX); //class style
wcx.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW|CS_DBLCLKS;//window procedure
wcx.lpfnWndProc=TheWindowProc; //class extra
wcx.cbClsExtra=0; //window extra
wcx.cbWndExtra=0; //application handlewcx.hInstance=hInstMain; //icon
wcx.hIcon=LoadIcon(NULL,IDI_APPLICATION); //cursor
wcx.hCursor=LoadCursor(NULL,IDC_ARROW); //background color
wcx.hbrBackground=(HBRUSH)GetStockObject(BLACK_BRUSH); //menu
wcx.lpszMenuName=NULL; //class name
wcx.lpszClassName=WINDOWCLASS; //small icon
wcx.hIconSm=NULL;


To this

WNDCLASSEX wcx = { sizeof(WNDCLASSEX), CS_OWNDC | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
TheWindowProc, 0L, 0L, hInstMain,
LoadIcon(NULL,IDI_APPLICATION),
LoadCursor(NULL,IDC_ARROW),
(HBRUSH)GetStockObject(BLACK_BRUSH),
NULL,
WINDOWCLASS, NULL};

Basically the samething or should be
Edit: I changed my code again, I relized that it wasn't the same thing. Well I have replaced the old with the new so no I get a black background now (yay!) but still no game and still error on exit (nay ) oh and my whole screen flickers too.





[edited by - Dko on November 25, 2003 12:41:00 PM]
0

Share this post


Link to post
Share on other sites
quote:
Original post by Rob Loach
quote:
Original post by The Alchemist
pouya''s tetris article was better

hehe. Does it compile? I''m too lazy to try right now.




Yes, it compiles and works.

0

Share this post


Link to post
Share on other sites
quote:
Original post by Evil_Greven
Pretty simple so far, eh? Now, let's throw in some global variables, bwahahahah! Global variables are preferred, because they are FAST. Speed is essential to a game. Thus, when you can, use them. But, be careful with them.

It's easy to make a case that any speed gains from global variables are far outweighed by their risks, in even the simplest applications. You seem to imply that there will be significant speed gains from using global variables, which is almost never the case (and is certainly not here). Since this tutorial is geared towards new programmers, you should probably emphasize good programming practice over micro-optimizing.

If you don't rewrite the code without globals, perhaps you could write accessor functions for the globals, at least offer a better warning about them.

Edit: One more thing, if you absolutely must use Hungarian notation, you should probably use a prefix of "n" for numbers, instead of "i" for ints "s" for shorts "dw" for DWORDs "l" for longs, etc. It just makes things a whole lot less confusing if you have to change a variable's type later on. The prefix in Hungarian notation should describe the purpose of the variable, not the type of the variable (which can easily be checked by hovering the mouse over the var name in most IDEs).

And finally, you should use an enum or constants to refer to piece types, not index numbers.

[edited by - glassJAw on November 25, 2003 4:43:01 PM]
0

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
http://totalnoobism.cjb.net/ can use you


aren''t you that "maxd gaming" guy?

If so, then why don''t you post in your real name.
0

Share this post


Link to post
Share on other sites
Dko,
You need to make the bitmap that it uses for yourself. Read the bottom part of the tutorial, it''s easy Name it "blocks.bmp", also. You don''t have the graphics, you''re not going to see anything. I suspect that there may be a problem in your MessageHandler() or further down in your WinMain() code when it''s exiting, you might want to doublecheck those two.

glassJAw,
In a simple program such as this, risks are quite minimal. What would you have done for something such as a ''score'' variable? That''d basically have to be global. You''d be passing it constantly. Sure, it''s good programming to pass stuff, but newbies aren''t always familiar with the different forms of passing, so I try to keep their use minimal.

Also, I don''t use exact Hungarian notation (bleh, n for ints, I prefer i), I use my own variant.

Lastly, I would have to change the code significantly in order to set piece types as enum/constants. The program doesn''t really care what *kind* of piece it is, it just moves it around how it needs to. Like I said, this is a very simple program.

To all beginning game programmers reading this, I wish you luck on your journey; either professional or hobbyist. You''ll need it, with all the frustrations that come up. I won''t say this is the best tutorial in the world, just one of the shorter ones .

-Greven
0

Share this post


Link to post
Share on other sites
quote:
Original post by The Alchemist
In my humble opinion

pouya''s tetris article was better


~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Any programmer can make snake.
The difference is that real game programmers would never think about using a linked list


[edited by - THe Alchemist on November 24, 2003 10:30:37 PM]
I concur.


[My Image Gallery (WIP)][Greatest Tetris clone evar!][Return your stolen MP3s]
0

Share this post


Link to post
Share on other sites
I sat thruogh the entire tutorial and finished the code then at the end i get this(BTW i named the project SimpleTris):

SimpleTris error LNK2019: unresolved external symbol _main referenced in function _mainCRTStartup
SimpleTris fatal error LNK1120: 1 unresolved externals

i really dont know whats wrong is it my fault or the code.
0

Share this post


Link to post
Share on other sites
Guest
This topic is now closed to further replies.