continuation of assignment

#include #include "SimpleTetris.h" #include #include #include #include // Set a shape layout and its default settings void TetrisShape::SetShape(TetrisPieceType &piece) { // Copy shape information (layout, colour etc.) Piece = piece; // No rotation CurrentRotation = 0; // The X position of the shape will always be centerised in the bucket PosX = (TetrisBucket::BucketSizeX - 4) / 2; // The Y position of the shape must be such that the first solid block in the 4x4 grid appears on the top row of the bucket!

PosY = -firstUsedRow(); } // Bring a piece into play void TetrisShape::Activate() { LastMoveTime = GetTickCount(); } // Move the piece down one place if enough time has elapsed bool TetrisShape::Update(int level) { bool finished = false; // We will move the piece down at a rate of 1 row per second for level 1, decreasing by // 0.05 seconds on each additional level // TickCount measures time in ms unsigned int lifetime = 1000 - ((level - 1) * 50); if (GetTickCount() >= LastMoveTime + lifetime) { LastMoveTime = GetTickCount(); finished = MoveDown(); } return finished; } // Draw a tetris piece void TetrisShape::Draw(bool ignoreBucketHeight) { // Don't draw if no shape type set if (!HasShape()) return; for (int y = PosY; y < PosY + 4; y++) { // Don't plot outside the bucket if ((y < 0 || y >= TetrisBucket::BucketSizeY) && !ignoreBucketHeight) continue; for (int x = PosX; x < PosX + 4; x++) { // Don't plot outside the bucket if (x < 0 || x >= TetrisBucket::BucketSizeX) continue; // Plot a rectangle if this grid item in the shape contains a block if (Piece.Shape[CurrentRotation][y - PosY][x - PosX]) renderer->FillRectangle(TetrisBucket::LeftSide + x * SimpleTetris::BlockSize, TetrisBucket::TopSide + y * SimpleTetris::BlockSize, TetrisBucket::LeftSide + x * SimpleTetris::BlockSize + SimpleTetris::BlockSize, TetrisBucket::TopSide + y * SimpleTetris::BlockSize + SimpleTetris::BlockSize, Piece.ShapeColour); } } } // Draw a tetris piece in an arbitrary place on the screen, ignoring the bounding box constraints void TetrisShape::Draw(int sx, int sy) { int fx = firstUsedColumn(); int lx = lastUsedColumn(); int fy = firstUsedRow(); int ly = lastUsedRow(); int rows = lx - fx + 1; int cols = ly - fy + 1; for (int py = fy, y = 0; py <= ly; py++, y++) for (int px = fx, x = 0; px <= lx; px++, x++) if (Piece.Shape[CurrentRotation][py][px]) renderer->FillRectangle(sx + x * SimpleTetris::BlockSize, sy + y * SimpleTetris::BlockSize, sx + x * SimpleTetris::BlockSize + SimpleTetris::BlockSize, sy + y * SimpleTetris::BlockSize + SimpleTetris::BlockSize, Piece.ShapeColour); } // Collision detection against the edges of the bucket inline bool TetrisShape::isAtTop() { return (PosY + firstUsedRow() == 0); } inline bool TetrisShape::isAtBottom() { return (PosY + lastUsedRow() == TetrisBucket::BucketSizeY - 1); } inline bool TetrisShape::isAtLeft() { return (PosX + firstUsedColumn() == 0); } inline bool TetrisShape::isAtRight() { return (PosX + lastUsedColumn() == TetrisBucket::BucketSizeX - 1); } // Move piece if no collision occurs void TetrisShape::MoveLeft() { // Check for bucket edge collisions if (!isAtLeft()) { PosX--; // Check for piece collisions if (bucket->IsCollision(this)) PosX++; } } void TetrisShape::MoveRight() { // Check for bucket edge collisions if (!isAtRight()) { PosX++; // Check for piece collisions if (bucket->IsCollision(this)) PosX--; } } // Return true if the piece cannot move any further, triggering the launch of a new piece bool TetrisShape::MoveDown() { bool collision = isAtBottom(); if (!collision) { PosY++; collision = bucket->IsCollision(this); if (collision) PosY--; } return collision; } void TetrisShape::MoveToBottom() { // Repeatedly move the piece down until it can't move any further while (!MoveDown()); } void TetrisShape::Rotate() { // Remember whether the piece is currently at any edge of the bucket bool atTop = isAtTop(); bool atBottom = isAtBottom(); bool atLeft = isAtLeft(); bool atRight = isAtRight(); int firstRow = firstUsedRow(); int lastRow = lastUsedRow(); int firstCol = firstUsedColumn(); int lastCol = lastUsedColumn(); // Save the previous position of the piece in case we need to revert the changes int previousRotation = CurrentRotation; int previousX = PosX; int previousY = PosY; // First assume we can rotate the piece (clockwise) CurrentRotation = (CurrentRotation + 1) % 4; // If the piece was at the top or bottom, we need to re-align it // vertically so that the top (or bottom) row that the piece occupied // before it rotated is the same row after rotation (to prevent the // block moving down when at the top, or up when at the bottom, as well // as to stop it going outside the top or bottom of the bucket) // If you don't understand, just comment this out :-) if (atTop) PosY -= firstUsedRow() - firstRow; if (atBottom) PosY -= lastUsedRow() - lastRow; // Now repeat for the left and right if (atLeft) PosX -= firstUsedColumn() - firstCol; if (atRight) PosX -= lastUsedColumn() - lastCol; // Don't allow the rotation if it collides with an existing piece if (bucket->IsCollision(this)) { PosX = previousX; PosY = previousY; CurrentRotation = previousRotation; } } // Initial set up of the bucket void TetrisBucket::Init(SimpleTetris *r) { renderer = r; // Set the top-left co-ordinates of where the game board should be plotted LeftSide = (renderer->ResolutionX - SimpleTetris::BlockSize * BucketSizeX) / 2; TopSide = (renderer->ResolutionY - SimpleTetris::BlockSize * BucketSizeY) / 2; } // Empty the bucket void TetrisBucket::Reset() { for (int y = 0; y < BucketSizeY; y++) for (int x = 0; x < BucketSizeX; x++) board[y][x] = -1; } // Add a shape to the bucket void TetrisBucket::Add(TetrisShape &s, int &score, int &linesCleared, int &level) { // We already know the shape is in a valid position so there is no chance of out-of-bounds indexing here // Therefore we can just copy it straight into the bucket array for (int y = 0; y < 4; y++) for (int x = 0; x < 4; x++) board[s.PosY + y][s.PosX + x] = (s.Piece.Shape[s.CurrentRotation][y][x]? s.Piece.Index : board[s.PosY + y][s.PosX + x]); // Check for filled lines (rows) int newFills = 0; for (int y = 0, fills = 0; y < BucketSizeY; y++, fills = 0) { for (int x = 0; x < BucketSizeX; x++) if (board[y][x] != -1) fills++; if (fills == BucketSizeX) { newFills++; // Line is cleared, move the grid down // the line y will now become empty, and all y-i must become y+1-i for (int nx = 0; nx < BucketSizeX; nx++) board[y][nx] = -1; for (int ny = y; ny >= 1; ny--) for (int nx = 0; nx < BucketSizeX; nx++) board[ny][nx] = board[ny - 1][nx]; // Clear the top row for (int nx = 0; nx < BucketSizeX; nx++) board[0][nx] = -1; } } // New level?

if (linesCleared / SimpleTetris::LinesPerLevel < (linesCleared + newFills) / SimpleTetris::LinesPerLevel) level++; // Update score linesCleared += newFills; if (newFills == 1) score += 10; if (newFills == 2) score += 50; if (newFills == 3) score += 200; if (newFills == 4) score += 1000; } // Check if a piece would create a collision with the bucket contents bool TetrisBucket::IsCollision(TetrisShape *s) { bool collision = false; // The weird equation in the assignment of y allows us to collision check for // when a piece is partly off the top of the grid in the game over state for (int y = (s->PosY >= 0? 0 : -s->PosY); y < 4 && !collision; y++) for (int x = 0; x < 4 && !collision; x++) if (board[s->PosY + y][s->PosX + x] != -1 && s->Piece.Shape[s->CurrentRotation][y][x]) collision = true; return collision; } // Set up rendering resources used by the bucket void TetrisBucket::SetupResources() { // Grid is 20 x 10 // Each block will be 18x18 // Therefore right-hand edge of left side of bucket is (ResolutionX - 15*10) / 2 // Top edge of bucket is (ResolutionY - 15*20) / 2 // Create a brush for the bucket bucketBrush = renderer->MakeBrush(Colour::CornflowerBlue); } // Draw the bucket outline and contents void TetrisBucket::Draw() { renderer->SetBrush(bucketBrush); // Left side of bucket renderer->FillRectangle(LeftSide - BucketWidth, TopSide, LeftSide, TopSide + BucketSizeY * SimpleTetris::BlockSize + BucketWidth); // Right side of bucket renderer->FillRectangle(LeftSide + BucketSizeX * SimpleTetris::BlockSize, TopSide, LeftSide + BucketSizeX * SimpleTetris::BlockSize + BucketWidth, TopSide + BucketSizeY * SimpleTetris::BlockSize + BucketWidth); // Bottom of bucket renderer->FillRectangle(LeftSide, TopSide + BucketSizeY * SimpleTetris::BlockSize, LeftSide + BucketSizeX * SimpleTetris::BlockSize, TopSide + BucketSizeY * SimpleTetris::BlockSize + BucketWidth); // Contents of bucket for (int y = 0; y < BucketSizeY; y++) for (int x = 0; x < BucketSizeX; x++) if (board[y][x] != -1) { renderer->FillRectangle( LeftSide + x * SimpleTetris::BlockSize, TopSide + y * SimpleTetris::BlockSize, LeftSide + x * SimpleTetris::BlockSize + SimpleTetris::BlockSize, TopSide + y * SimpleTetris::BlockSize + SimpleTetris::BlockSize, renderer->Pieces[board[y][x]].ShapeColour); } } // Constructor for main application SimpleTetris::SimpleTetris() { // Seed random number generator srand(static_cast(time(NULL))); // Create shapes // First dimension: which shape // Second dimension: which rotation // Third & fourth dimensions: rows and columns of shape bool shapetypes[7][4][4][4] = { { // Line of four { { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } }, { { 0, 0, 0, 0 }, { 1, 1, 1, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 } } }, { // S-shape (orientation 1) { { 0, 0, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 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 }, { 1, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 1, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } } }, { // S-shape (orientation 2) { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 1, 1, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 0 } } }, { // T-piece { { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 0, 0 } }, { { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 0, 0 }, { 1, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } } }, { // L-piece (orientation 1) { { 0, 0, 0, 0 }, { 0, 0, 1, 0 }, { 1, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 1, 0, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 1, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 0 } } }, { // L-piece (orientation 2) { { 0, 0, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 1, 1 }, { 0, 0, 0, 0 } }, { { 0, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 1, 1, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 0 } }, { { 0, 0, 1, 0 }, { 0, 0, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } } }, { // Cube { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, { { 0, 0, 0, 0 }, { 0, 1, 1, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } } } }; // Store the layouts for (int i = 0; i < numShapeTypes; i++) { Pieces[i].Index = i; for (int u = 0; u < 4; u++) for (int v = 0; v < 4; v++) for (int w = 0; w < 4; w++) Pieces[i].Shape[u][v][w] = shapetypes[i][u][v][w]; } // Set pixel size of each tetris block BlockSize = 18; // Bucket must be set up *after* block pixel size!

bucket.Init(this); // Setup the pointers and references in the shapes - must be done after the bucket is set up currentShape.Init(this, &bucket); nextShape.Init(this, &bucket); // Items that must be initialized one time, but after the renderer has been setup scoreFormat = NULL; // Start on menu screen gameState = 0; } bool SimpleTetris::SetupResources() { if (scoreFormat == NULL) { // Make text layout formats textFormat = MakeTextFormat(L"Courier New", 18.0f, DWRITE_FONT_WEIGHT_BOLD); scoreFormat = MakeTextFormat(L"Verdana", 24.0f); gameOverFormat = MakeTextFormat(L"Courier New", 64.0f, DWRITE_FONT_WEIGHT_BOLD); } // Text colours textBrush = MakeBrush(Colour::Snow); scoreBrush = MakeBrush(Colour::CornflowerBlue); gameOverBrush = MakeBrush(Colour::DarkRed); // Create a brush for each piece type Pieces[0].ShapeColour = MakeBrush(Colour::Blue); Pieces[1].ShapeColour = MakeBrush(Colour::LightBlue); Pieces[2].ShapeColour = MakeBrush(Colour::LightSeaGreen); Pieces[3].ShapeColour = MakeBrush(Colour::Yellow); Pieces[4].ShapeColour = MakeBrush(Colour::Red); Pieces[5].ShapeColour = MakeBrush(Colour::Azure); Pieces[6].ShapeColour = MakeBrush(Colour::Violet); // Create a brush for the next piece box nextBoxBrush = MakeBrush(Colour::White); bucket.SetupResources(); return true; } void SimpleTetris::ReleaseResources() { for (int i = 0; i < numShapeTypes; i++) SafeRelease(&Pieces[i].ShapeColour); } void SimpleTetris::UpdateObjects() { // Do nothing if the game is over if (gameState == 2) return; // Skip menu (we haven't implemented it yet) and start a new game if we aren't playing yet if (gameState == 0) { gameState = 1; newGame(); } if (currentShape.Update(level)) { newPiece(); } } void SimpleTetris::OnKeyPress(WPARAM key) { if (gameState != 1) return; if (currentShape.HasShape()) { if (key == VK_LEFT) currentShape.MoveLeft(); if (key == VK_RIGHT) currentShape.MoveRight(); if (key == VK_DOWN) currentShape.MoveDown(); if (key == VK_UP) { currentShape.MoveToBottom(); newPiece(); } if (key == VK_CONTROL) currentShape.Rotate(); } } void SimpleTetris::DrawScene() { // Draw this in playing or game over states if (gameState != 0) { // Draw bucket bucket.Draw(); // Draw current shape (only allow it to appear over the top of the bucket edge in game over state) currentShape.Draw((gameState == 2)); // Draw the bounding box around the next shape DrawRectangle( ResolutionX - 50 - BlockSize * 6, 50, ResolutionX - 50, 50 + BlockSize * 4, nextBoxBrush); // Draw the next shape centered in the next shape box int nw = nextShape.GetWidth(); int nh = nextShape.GetHeight(); nextShape.Draw(ResolutionX - 50 - BlockSize * 6 + (BlockSize * 6 - nw * BlockSize) / 2, 50 + (BlockSize * 4 - nh * BlockSize) / 2); // Print score Text(50, 60, "Score", textFormat, textBrush); Text(50, 140, "Lines Cleared", textFormat, textBrush); Text(50, 220, "Level", textFormat, textBrush); std::stringstream s, l, lv; s << std::setw(6) << std::setfill('0') << score; l << linesCleared; lv << level; Text(50, 75, s.str(), scoreFormat, scoreBrush); Text(50, 155, l.str(), scoreFormat, scoreBrush); Text(50, 235, lv.str(), scoreFormat, scoreBrush); } // Print game over message if (gameState == 2) { gameOverFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER); Text(0, 200, "EPIC FAIL", gameOverFormat, gameOverBrush); } } // Set up a new game // Note this has to be called AFTER the brush resources are made void SimpleTetris::newGame() { int shapeNum; // Assign the first and second shapes (the screen shows what the next shape to drop will be) shapeNum = rand() % numShapeTypes; currentShape.SetShape(Pieces[shapeNum]); currentShape.Activate(); shapeNum = rand() % numShapeTypes; nextShape.SetShape(Pieces[shapeNum]); // You are on level 1 level = 1; // No score yet score = 0; // No lines cleared linesCleared = 0; // Empty the bucket bucket.Reset(); } // Set up a new piece void SimpleTetris::newPiece() { // Put the current shape in the bucket bucket.Add(currentShape, score, linesCleared, level); // Bring the next shape into play currentShape = nextShape; currentShape.Activate(); // Create a new 'next shape' int shapeNum = rand() % numShapeTypes; nextShape.SetShape(Pieces[shapeNum]); // If the new shape is already on another shape, it's game over!

if (bucket.IsCollision(&currentShape)) { // Set game over state gameState = 2; // We still want to show this shape, to show that the grid is full, // so we have to move it gradually upwards til there are no collisions while (bucket.IsCollision(&currentShape)) currentShape.PosY--; } } void Simple2DStart() { SimpleTetris tetris; tetris.SetWindowName(L"SimpleTetris by Katy Coe (c) 2012"); tetris.SetResolution(640, 480); tetris.SetBackgroundColour(Colour::Black); tetris.SetResizableWindow(false); tetris.Run(); }