Sign in to follow this  

Feedback wanted on my tetris source

This topic is 4375 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Working on a tetris clone in C# and Managed DirectX. So far I got: blocks dropping, rotation, row removal and game restart when board is full. No menus, no scoring and nothing fancy yet -- and the source is VERY VERY unpolished. However, I'm posting it here, looking for some feedback to learn from, what I'm mostly looking for is - feedback on my use of directx - feedback on the way I limit input speed Feel free to post any feedback though, but don't flame me for the many very obvious points of polishing ;) (for instance the 4-5 instances of the exact same code copied and pasted etc) Anyways, you can grab the source ziped with a VS2005 solution here: zip - homepage And for those who just want to read the source...here goes: ...will reply with source in a sec... thanks!

Share this post


Link to post
Share on other sites

/*
* T-Clone
* Another tetris clone
* Author: Jens B (jens@the-bengtssons.com)
*
* -- Search for 'QQQ' for TODO's --
*
*/


using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;
using System.Drawing; // for Color.Blue....QQQ

using D3D = Microsoft.DirectX.Direct3D;
using DInput = Microsoft.DirectX.DirectInput;

namespace tclone
{
/*
* tcloneWindow
* Window / Form class for the main window
*/

class tcloneWindow : Form
{
// d3d device
private D3D.Device device = null;

// dinput device
private DInput.Device inputDevice = null;

// textures
private Texture backgroundTexture = null; // playfield / background image / texture
private Texture blocksTexture = null; // blocks image

// sprites
private Sprite background = null; // playfield / background sprite
private Sprite blocks = null; // blocks

// "pile" array
private int[,] pileArray = new int[10, 20];

// the block arrays, with rotations, that is x,y coordinate for each piece of the block,
// for all 4 rotations
#region Block arrays with rotations
/*
* # | # | ### | #
* ### | ## | # | ##
* | # | | #
*/

private int[, ,] block1 = new int[4, 4, 2]
{
{ {1,0},{0,1},{1,1},{2,1} },
{ {1,0},{0,1},{1,1},{1,2} },
{ {0,0},{1,0},{2,0},{1,1} },
{ {0,0},{0,1},{1,1},{0,2} }
};

/*
* ####
*/

private int[, ,] block2 = new int[4, 4, 2]
{
{ {0,0},{1,0},{2,0},{3,0} },
{ {0,0},{0,1},{0,2},{0,3} },
{ {0,0},{1,0},{2,0},{3,0} },
{ {0,0},{0,1},{0,2},{0,3} }
};

/*
* ##
* ##
*/

private int[, ,] block3 = new int[4, 4, 2]
{
{ {0,0},{1,0},{0,1},{1,1} },
{ {0,0},{1,0},{0,1},{1,1} },
{ {0,0},{1,0},{0,1},{1,1} },
{ {0,0},{1,0},{0,1},{1,1} }
};

/*
* ## | #
* ## | ##
* | #
*/

private int[, ,] block4 = new int[4, 4, 2]
{
{ {0,0},{1,0},{1,1},{2,1} },
{ {1,0},{1,1},{0,1},{0,2} },
{ {0,0},{1,0},{1,1},{2,1} },
{ {1,0},{1,1},{0,1},{0,2} }
};

/*
* ## | #
* ## | ##
* | #
*/

private int[, ,] block5 = new int[4, 4, 2]
{
{ {1,0},{2,0},{0,1},{1,1} },
{ {0,0},{0,1},{1,1},{1,2} },
{ {1,0},{2,0},{0,1},{1,1} },
{ {0,0},{0,1},{1,1},{1,2} }
};
#endregion

// current & next piece
private int currentPiece = -1;
private int nextPiece = -1;
private int currentColor = -1; // indicates wich color to use from the texture
private int nextColor = -1;
private int currentRotation = 0;

// top left corner of playing piece
private int currentX = 4;
private int currentY = 0;

// speed value / level / difficulty
private int speed = 4;
private int tick = 0;

// funkey variable to control fps....QQQ
private int lastFrame = 0;

// another funkey variable to control input rate....QQQ
private int lastInput = 0;

/*
* Constructor to set up properties for the main window
* Put here instead of in a form designer window to make it more readable
*/

public tcloneWindow()
{
Text = "T-Clone by Jens B";
Width = 800; // QQQ support fullscreen
Height = 600;
FormBorderStyle = FormBorderStyle.Fixed3D;
MaximizeBox = false;

// adjust size to have a 800x600 client area (QQQ probably there is a smarter way to do this :P)
Width = 800 + (800 - ClientSize.Width);
Height = 600 + (600 - ClientSize.Height);
}


/*
* Initialize the playfield etc.
*/

private void InitializePlayfield()
{
// zero out playfied
for (int i = 0; i < 10; ++i)
{
for (int j = 0; j < 20; ++j)
{
pileArray[i, j] = 0;
}
}

// set up current and next piece
Random rnd = new Random();
currentPiece = rnd.Next(5);
nextPiece = rnd.Next(5);
currentColor = rnd.Next(5);
nextColor = rnd.Next(5);
currentX = 4;
currentY = 0;
}


/*
* Moves the piece down
*/

private void MovePiece()
{
tick += speed;
if (tick >= 100)
{
tick = 0;
++currentY;
}

if (CheckHit())
{
if (--currentY < 0) // reverse move
{
// we lost!!
InitializePlayfield();
return;
}

// add piece to pile
AddPiece();

// set up a new piece
currentY = 0;
currentX = 4;
currentPiece = nextPiece;
currentColor = nextColor;
Random rnd = new Random();
nextPiece = rnd.Next(5);
nextColor = rnd.Next(5);
}
}


/*
* Adds the current piece at the current position to the pile
*/

private void AddPiece()
{
/*
* This block of code is reproduced waaaay to many times, need to change the logic a bit
* Most likely by making the currentblock an array describing it....
*/

int[, ,] block = new int[4, 4, 2];

switch (currentPiece)
{
case 0:
block = block1;
break;
case 1:
block = block2;
break;
case 2:
block = block3;
break;
case 3:
block = block4;
break;
case 4:
block = block5;
break;
}

for (int i = 0; i < 4; i++)
{
pileArray[currentX + block[currentRotation, i, 0], currentY + block[currentRotation, i, 1]] = currentColor+1; // +1 just to confuse :P
}

// QQQ no overflow check :P
}


/*
* Checks if the piece hit the stack
*/

public bool CheckHit()
{
int[, ,] block = new int[4, 4, 2];

switch (currentPiece)
{
case 0:
block = block1;
break;
case 1:
block = block2;
break;
case 2:
block = block3;
break;
case 3:
block = block4;
break;
case 4:
block = block5;
break;
}

for (int i = 0; i < 4; ++i)
{
if (currentX + block[currentRotation, i, 0] > 9 || currentY + block[currentRotation, i, 1] > 19)
{
// we got a hit!
return true;
}

if (pileArray[currentX + block[currentRotation, i, 0], currentY + block[currentRotation, i, 1]] != 0)
{
// we got a hit!
return true;
}
}

return false;
}


/*
* Check input
*/

public void CheckInput()
{
// doing a move takes about 1/5 of a second, so make sure we dont process to quickly
// QQQ is this a bright idea, and is 1/5 fast enough?
int now = System.Environment.TickCount;
if (now - lastInput < 200)
return;

KeyboardState state = inputDevice.GetCurrentKeyboardState();

if (state[Key.Left])
{
if (--currentX < 0)
currentX = 0;

lastInput = System.Environment.TickCount;
}

if (state[Key.Right])
{
++currentX;
if (CheckHit())
--currentX;

lastInput = System.Environment.TickCount;
}

if (state[Key.Up])
{
if (++currentRotation > 3)
currentRotation = 0;

if (CheckHit())
{
if (--currentRotation < 0)
currentRotation = 3;
}

lastInput = System.Environment.TickCount;
}
}


/*
* D3D initialization function
* Also initialzes DInput
*/

private void InitializeGraphics()
{
try
{
PresentParameters presentParams = new PresentParameters();
presentParams.Windowed = true; // QQQ support fullscreen
presentParams.SwapEffect = SwapEffect.Discard;

device = new D3D.Device(0, D3D.DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParams);
}
catch (DirectXException e)
{
MessageBox.Show(null, "Error intializing graphics: " + e.Message, "Error");
Close();
}

// QQQ set up callbacks for loosing devices etc

// set up input device
inputDevice = new DInput.Device(DInput.SystemGuid.Keyboard);
inputDevice.SetCooperativeLevel(this, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);
inputDevice.Acquire();
}


/*
* Load textures and create background sprite
*/

private void LoadTextures()
{
// load the background texture -- QQQ load from resource to make the exe as self contained as possible
string file = "C:\\Documents and Settings\\Jens\\My Documents\\Visual Studio 2005\\Projects\\tclone\\playfield.bmp";
backgroundTexture = TextureLoader.FromFile(device, file, 800, 600, 24, 0, Microsoft.DirectX.Direct3D.Format.Unknown, Microsoft.DirectX.Direct3D.Pool.Default, Microsoft.DirectX.Direct3D.Filter.None, Microsoft.DirectX.Direct3D.Filter.None, 0);

// load the blocks texture -- QQQ load from resource to make the exe as self contained as possible
file = "C:\\Documents and Settings\\Jens\\My Documents\\Visual Studio 2005\\Projects\\tclone\\blocks.bmp";
blocksTexture = TextureLoader.FromFile(device, file, 800, 28, 24, 0, Microsoft.DirectX.Direct3D.Format.Unknown, Microsoft.DirectX.Direct3D.Pool.Default, Microsoft.DirectX.Direct3D.Filter.None, Microsoft.DirectX.Direct3D.Filter.None, 0);

// create the sprites
background = new Sprite(device);
blocks = new Sprite(device);


// QQQ this dont seem to read the dimensions from the file
//backgroundTexture = TextureLoader.FromFile(device, file);

// QQQ for some reason this comented out code dont work...
//Bitmap background = new Bitmap( file);

//backgroundTexture = new Texture(device, background,Microsoft.DirectX.Direct3D.Usage.None, Microsoft.DirectX.Direct3D.Pool.Default);
}


/*
* Draws the current piece
*/

private void DrawCurrentPiece()
{
int textureStartX = currentColor * 28;
int[, ,] block = new int[4, 4, 2];

switch (currentPiece)
{
case 0:
block = block1;
break;
case 1:
block = block2;
break;
case 2:
block = block3;
break;
case 3:
block = block4;
break;
case 4:
block = block5;
break;
}

int startx = 80 + (28 * currentX);
int starty = 10 + (28 * currentY);

blocks.Begin(SpriteFlags.None);

for (int i = 0; i < 4; ++i)
{
blocks.Draw2D(blocksTexture, new Rectangle(textureStartX, 0, 27, 27), new SizeF(28f, 28f),
new Point(startx + (block[currentRotation, i, 0] * 28), starty + (block[currentRotation, i, 1] * 28)), Color.White);

}

blocks.End();
}


/*
* Draws the "next" piece
*/

private void DrawNextPiece()
{
int textureStartX = nextColor * 28;
int[, ,] block = new int[4, 4, 2];

switch (nextPiece)
{
case 0:
block = block1;
break;
case 1:
block = block2;
break;
case 2:
block = block3;
break;
case 3:
block = block4;
break;
case 4:
block = block5;
break;
}

int startx = 515; // QQQ Write some clever code to center this
int starty = 245;

blocks.Begin(SpriteFlags.None);

for (int i = 0; i < 4; ++i)
{
blocks.Draw2D(blocksTexture, new Rectangle(textureStartX, 0, 27, 27), new SizeF(28f, 28f),
new Point(startx + (block[0, i, 0] * 28), starty + (block[0, i, 1] * 28)), Color.White);

}

blocks.End();
}


/*
* Rendering function that draws the contens of the window
*
*/

private void Render()
{
if (device == null)
return; // no device = do nothing

// limit to 70 fps -- QQQ is this really a good idea?
int now = System.Environment.TickCount;
while (now - lastFrame < 15) // aproxes to 70 fps....QQQ
{
now = System.Environment.TickCount;
Application.DoEvents(); // QQQ do i need to acctually sleep?
}


// begin the scene
device.BeginScene();


// draw the playfield as a sprite
background.Begin(SpriteFlags.None);
background.Draw2D(backgroundTexture, new Rectangle(0, 0, 800, 600), new SizeF(800f, 600f),
new Point(0, 0), Color.White);
background.End();

// draw current piece
DrawCurrentPiece();

// draw "pile"
DrawPile();

// draw "next" piece, if applicable
DrawNextPiece();

// end the scene
device.EndScene();
device.Present();
}


/*
* Draws the pile!
*/

private void DrawPile()
{
int textureStartX = 0;
int startx = 80;
int starty = 10;

blocks.Begin(SpriteFlags.None);

for (int i = 0; i < 10; ++i)
{
for (int j = 0; j < 20; ++j)
{
if (pileArray[i, j] > 0)
{
textureStartX = (pileArray[i, j] - 1) * 28;

blocks.Draw2D(blocksTexture, new Rectangle(textureStartX, 0, 27, 27), new SizeF(28f, 28f),
new Point(startx + (i * 28), starty + (j * 28)), Color.White);
}

}
}

blocks.End();
}


/*
* Check for row removal or loss
* returns false for loss, true for "ok"
*/

private bool CheckWin()
{
for (int i = 19; i >= 0; --i)
{
bool rowFull = false;

for (int j = 0; j < 10; ++j)
{
if (pileArray[j, i] > 0)
rowFull = true;
else
{
rowFull = false;
break;
}
}

if (rowFull)
{
for (int y = i; y > 0; --y)
{
for (int x = 0; x < 10; ++x)
{
pileArray[x, y] = pileArray[x, y - 1];
}
}

for (int x = 0; x < 10; ++x)
{
pileArray[x, 0] = 0;
}

}
}

return true;
}


/*
* mog, the main function!
*/

static void Main()
{
// create our window and show it
tcloneWindow win = new tcloneWindow();

win.InitializeGraphics();
win.LoadTextures();

win.InitializePlayfield();

win.Show();

// game loop
while (win.Created)
{
// check inputs
win.CheckInput();

// move piece (will check for collision)
win.MovePiece();

// check "win" (remove line(s)) / loss
win.CheckWin();

// render d3d win
win.Render();

// let windows do message processing
Application.DoEvents();
}

}
}
}

Share this post


Link to post
Share on other sites
This is not a comment, I'm sorry. I think people won't have enough time to surf through all your code. If you want some advices, please post more specific questions about problems you encountered (design issues for example). You've already had a running game, then the best critic in your design will be your future games, through projects you'll fine tune your design to suit your own needs.

You may also take a look at good game programming books, the GDNet Book section maybe a good place.

Share this post


Link to post
Share on other sites
Quote:
Original post by Rob Loach
I got: "Error intializing graphics: Error in the application."

Other then that, looking through the source is quite nice. It's bound to be my non-working video card that's the problem.


Either that, or you are not using the latest DirectX drivers.

Share this post


Link to post
Share on other sites
Quote:
Original post by Rob Loach
I got: "Error intializing graphics: Error in the application."

Other then that, looking through the source is quite nice. It's bound to be my non-working video card that's the problem.

Or... the hardcoded path to the game's graphical resources [wink]

Share this post


Link to post
Share on other sites

This topic is 4375 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this