Game components-Tetris

Source: Internet
Author: User
Tetris

All the helper classes and game components have been discussed enough. It's time to write the next cool game. Thanks to the many categories that can use the mini-game engine, it is now very easy to write text, draw genie, and play sounds on the screen.

It is helpful to consider positioning all game elements in the same way you did in the previous games before entering the logic of the RIS game. You only need to show the background box to understand what will be displayed, rather than drawing all game components on the screen. For the background, you use the cosmic background again (I promise this will be the last time ). The background box is a new texture and exists in two modes (see Figure 4-7 ). Normally, game components are separated to make each component more adaptable to the screen. You may also reuse the same box for the two parts of the game. However, because the appearance ratio between them is different, it may not seem suitable for either the background box or other game components, these components are too small, but they also need a background box image, which is just a small number.


Figure 4-7

Rendering background

To render the boxes on these screens, you use the spritehelper class again and use the unit test below to test everything:

Public static void testbackgroundboxes () {testgame. start ("testbackgroundboxes", delegate {// render background testgame. game. background. render (); // draw background boxes for all the components testgame. game. backgroundbigbox. render (New rectangle (512-200)-15, 40-12,400 + 23, (768-40) + 16); testgame. game. backgroundsmallbox. render (New rectangle (512-480)-15, 40-10,290-30,300); testgame. game. backgroundsmallbox. render (New rectangle (512 + 240)-15, 40-10,290-30,190) ;}// testbackgroundboxes ()

This unit test will generate the output shown in 4-8.

Figure 4-8

You may ask the box on the right is a small one. Where can I get all these values? Okay, I just start with any value, and then improve it until everything fits into the final game. First, draw the background in unit test, because if you are in unit test, you will not call the draw method of the tetrisgame class (otherwise, when the game is completely completed, unit Tests can no longer run ).

The three boxes are then drawn. The box in the upper-left corner shows the next brick. The box in the middle shows the current Tetris grid. Finally, the box on the right is used to display the scoreboard. You should have seen their unit tests before.

Processing Grid

It is time to fill in all the box content. Starting from the main component: tetrisgrid. This class is responsible for displaying the entire Tetris grid. The bricks that process input and move the falling data also display all current data. When discussing game components, you have seen methods used in the tetrisgrid class. Before rendering the grid, you should view the constants defined in the tetrisgrid class:

 
# Region constantspublic const int gridwidth = 12; Public const int gridheight = 20 ;..

There are a series of more interesting constants, but now you only need the grid size. So there are 12 columns and 20 rows in your RIS field. With the help of block.png texture, it is just a simple square brick. Now you can use the draw method to easily draw the entire grid:

 
// Calc sizes for block, etc.int blockwidth = gridrect. width/gridwidth; int blockheight = gridrect. height/gridheight; For (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) {game. blocksprite. render (New rectangle (gridrect. X + x * blockwidth, gridrect. Y + y * blockheight, blockWidth-1, blockHeight-1), new color (60, 60, 60,128); // empty color} //

The gridrect variable is passed to the draw method as a parameter from the main class to specify the area where you want to draw the grid. It is the same as the rectangle you use in the background box, but it is slightly smaller to adapt. The first thing you need to do here is to calculate the width and height of each brick to be drawn. Then traverse the entire array and use the spritehelper. Render method to draw each brick with a translucent dark color to display an empty grid site. 4-9 shows what it looks like. Because you actually use game components, and you don't have to do anything in unit testing.Code. The unit test only draws the background box, and then calls the tetrisgrid. Draw method to display the result (see testemptygrid unit test)

Figure 4-9

Block Type

Before you do something useful for rendering a new grid, you should consider the brick type in the game. The standard RIS game has 7 types of bricks; they all have 4 small bricks (4-10) connected to each other ). The most popular brick type is a straight line because it can destroy up to four lines if you have a deep essence.

Figure 4-10

The brick types must be defined in the tetrisgrid class. One way to do this is to use an enumerative number type that saves all possible brick types. This enumeration also contains an empty brick type to allow you to use this data structure for the entire grid, because each grid brick can contain any portion of the predefined brick type, or an empty part. Let's take a look at the remaining constants in the tetrisgrid class:

/// <Summary> // block types we can have for each new block that falls down. /// </Summary> Public Enum blocktypes {empty, block, triangle, line, Rightt, leftt, rightshape, leftshape ,} // Enum blocktypes /// <summary> // Number of block types we can use for each grid block. /// </Summary> Public static readonly int numofblocktypes = enumhelper. getsize (typeof (blocktypes); // <summary> // block colors for each block type. /// </Summary> Public static readonly color [] blockcolor = new color [] {new color (60, 60, 60,128), // empty, color unused new color (50, 50,255,255), // line, blue new color (160,160,160,255), // block, gray new color (255, 50, 50,255), // Rightt, red new color (255,255, 50,255), // leftt, yellow new color (50,255,255,255), // rightshape, teal new color (255, 50,255,255), // leftshape, purple new color (50,255, 50,255), // triangle, green }; // color [] blockcolor /// <summary> // unrotated shapes /// </Summary> Public static readonly int [] [,] blocktypeshapesnormal = new int [] [,] {// empty New int [,] {0}, // line new int [,] {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, // block new int [,] {1, 1 },{ 1, 1 }}, // Rightt new int [,] {1, 1}, {1, 0}, {1, 0 }}, // leftt new int [,] {1, 1}, {0, 1}, {0, 1}, // rightshape new int [,] {0, 1, 1}, {1, 1, 0 }}, // leftshape new int [,] {1, 1, 0}, {0, 1, 1 }}, // triangle new int [,] {0, 1, 0}, {1, 1, 1}, {0, 0, 0 }},}; // blocktypeshapesnormal

Blocktypes is the enum Enumeration type we have mentioned. It contains all possible brick types and is also used in nextblock game components to randomly generate new bricks. All the original grid areas are filled with empty bricks. Grid is defined:

 
/// <Summary> // The actual grid, contains all blocks, // including the currently falling block. /// </Summary> blocktypes [,] grid = new blocktypes [gridwidth, gridheight];

By the way, numofblocktypes shows the usage of enumeration classes. You can easily determine the number of entries in blocktypes enumeration.

Next, the color of each brick type is defined. You can use nextblock to preview these colors and render the entire grid. Each grid has a block type. By converting emun to an int type, you can also easily use the brick color. The code is written in the draw method:

 
Blockcolor [(INT) grid [x, y]

Finally, the shape of the bricks is defined and looks a bit more complicated, especially if you consider that the parts of the bricks must be rotated. By using blocktypeshapes, all possible bricks in a large array are rotated in the risgrid constructor.

To add a new brick to the RIS grid, you only need to add each part of the brick to your grid, and the addrandomblock method will do this. Every time the update method is called, The floatinggrid method is called to maintain an independent list to remember the parts in the grid that must be moved down (see the next section, "gravity "; you don't just let everything fall ):

// Randomize block type and rotationcurrentblocktype = (INT) nextblock. setnewrandomblock (); currentblockrot = randomhelper. getrandomint (4); // get precalculated shapeint [,] shape = blocktypeshapes [currentblocktype, currentblockrot]; int xpos = gridwidth/2-shape.getlength (0)/2; // center block at top most position of our gridcurrentblockpos = new point (xpos, 0); // Add new blockfor (INT x = 0; x <shape. getle Ngth (0); X ++) for (INT y = 0; y <shape. getlength (1); y ++) if (shape [x, y]> 0) {// check if there is already something If (grid [x + xpos, y]! = Blocktypes. Empty) {// then game is over dude! Gameover = true; sound. play (sound. sounds. lose);} // If else {grid [x + xpos, y] = (blocktypes) currentblocktype; floatinggrid [x + xpos, y] = true ;} // else} // For if

First, you need to determine which Brick type will be added here. To help you do this, you need an auxiliary method of the nextblock class, which randomizes the next brick type and returns the previous brick type displayed in the nextblock window. The rotation is also randomized; say "hi" to the randomhelper class ".

With this data, you can now get a pre-calculated shape and place it in the center of the top of the grid. Two for loop iterations run through the entire shape. It adds a valid shape part. Until you encounter any data that already exists in the grid. In case of game over, you will hear the sound of failure. If the bricks heap to the top of the grid, you will not add any new bricks.

Now you have a new brick in the grid, but it is annoying to see it on the top, and it should often fall.

Gravity

To test the gravity of the current brick, the testfallingblockandlinekill unit test is used. Every time you call the update method of the tetrisgrid class, the active bricks are updated, which are not called very frequently. At the first level, the update method is called every 1000 ms (every second. If the current brick can be moved down, check here:

 
// Try to move floating stuff downif (moveblock (movetypes. Down) = false | movingdownwasblocked) {// failed? Then fix floating stuff, not longer moveable! For (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) floatinggrid [x, y] = false; sound. play (sound. sounds. blockfalldown);} // ifmovingdownwasblocked = false;

Most of the RIS logic is processed in the moveblock auxiliary method. This method checks whether the movement to the specified direction actually occurs. If the brick can no longer be moved, it will be fixed. You can clear the floatinggrid array and play the sound of the brick landing.

After clearing the floatinggrid array, no active bricks can be moved down. The following code is used to check whether a row is destroyed:

// Check if we got any moveable stuff, // if not add new random block at top! Bool canmove = false; For (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) if (floatinggrid [x, y]) canmove = true; If (canmove = false) {int lineskilled = 0; // check if we got a full line for (INT y = 0; y <gridheight; y ++) {bool fullline = true; For (INT x = 0; x <gridwidth; X ++) if (grid [x, y] = blocktypes. empty) {fullline = false; break;} // For if // we got a full line? If (fullline) {// move everything down for (INT ydown = Y-1; ydown> 0; ydown-) for (INT x = 0; x <gridwidth; X ++) grid [x, ydown + 1] = grid [x, ydown]; // clear top line for (INT x = 0; x <gridwidth; X ++) grid [0, x] = blocktypes. empty; // Add 10 points and count line score + = 10; lines ++; lineskilled ++; sound. play (sound. sounds. linekill);} // If} // For // if we killed 2 or more lines, add extra score if (lineskilled> = 2) score + = 5; if (lineskilled> = 3) score + = 10; If (lineskilled> = 4) score + = 25; // Add new block at top addrandomblock ();} // If

The first thing we do here is to check whether there is a brick for positive activity. If you do not enter the "If statement block", check whether a complete row is full and can be destroyed. To determine whether a row is filled, assume that it is filled, and then check whether any bricks in this row are empty. You know this line is not completely filled up. Check the next line. If this row is filled up, delete it by copying all the rows above it and moving them down. There may be a pretty explosion in this location. In short, players get 10 points for each line of destruction, and you can hear the sound of row destruction.

If a player destroys more than one row, the player returns more scores. Finally, you have read the addrandomblock method, which is used to create a new brick on the top.

Process Input

Thanks to the input helper class, processing user input is no longer a big task. You can easily check whether an arrow or a handle button is pressed or not ,. The escape and back keys are processed in the basegame class, allowing you to exit the game. In addition, you only need four keys in the RIS game. To move left and right, use the corresponding arrow buttons. The up arrow is used to rotate the current brick, and the down arrow, space, or a key is used to make the brick fall faster.

Similar to a gravity check, you can see if you can move bricks down, and you can also check whether you can move the current bricks left or right. Only when the check result is true can you actually move the bricks; because you want to check the player input for each frame, not only when you update tetrisgrid, As you learned earlier, updating tetrisgrid may occur every MS, so the code is put in the update method of the tetrisgame class. The Code mentioned above in the update method of the tetrisgrid class is just to improve the user's experience when moving, and by hitting the arrow multiple times, you can greatly improve the Brick's left and right movement speed.

well, you have learned a lot about all the support code, and you almost need to run the Tetris game in the first place. But you should take a look at the moveblock auxiliary method, because it is the most complete and the most important part of the RIS game. Another important method is the rotateblock method, which tests in a similar way whether a brick can be rotated. You can view the Source Code of the RIS game. Use unit tests in the tetrisgame class to see how these methods work:

# Region move blockpublic Enum movetypes {left, right, down,} // Enum movetypes // <summary> // remember if moving down was blocked, this increases // The Game speed because we can force the next block! /// </Summary> Public bool movingdownwasblocked = false; // <summary> // move current floating block to left, right or down. /// if anything is blocking, moving is not possible and // nothing gets changed! /// </Summary> /// <returns> returns true if moving was successful, otherwise false </returns> Public bool moveblock (movetypes movetype) {// clear old POS for (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) if (floatinggrid [x, y]) grid [x, y] = blocktypes. empty; // move stuff to new positionbool anythingblocking = false; point [] newpos = new point [4]; int newposnum = 0; If (movetype = mov Etypes. left) {for (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) if (floatinggrid [x, y]) {If (x-1 <0 | grid [X-1, y]! = Blocktypes. empty) anythingblocking = true; else if (newposnum <4) {newpos [newposnum] = new point (x-1, Y); newposnum ++ ;} // else if} // For if} // If (left) else if (movetype = movetypes. right) {for (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) if (floatinggrid [x, y]) {If (x + 1> = gridwidth | grid [x + 1, y]! = Blocktypes. empty) anythingblocking = true; else if (newposnum <4) {newpos [newposnum] = new point (x + 1, Y); newposnum ++ ;} // else if} // For if} // If (right) else if (movetype = movetypes. down) {for (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) if (floatinggrid [x, y]) {If (Y + 1> = gridheight | grid [x, y + 1]! = Blocktypes. empty) anythingblocking = true; else if (newposnum <4) {newpos [newposnum] = new point (X, Y + 1); newposnum ++ ;} // else if} // For if (anythingblocking = true) movingdownwasblocked = true;} // If (down) // if anything is blocking restore old state if (anythingblocking | // or we didn't get all 4 new positions? Newposnum! = 4) {for (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) if (floatinggrid [x, y]) grid [x, y] = (blocktypes) currentblocktype; return false;} // If else {If (movetype = movetypes. left) currentblockpos = new point (currentblockpos. x-1, currentblockpos. y); else if (movetype = movetypes. right) currentblockpos = new point (currentblockpos. X + 1, currentblockpos. y); else if (Mo Vetype = movetypes. Down) currentblockpos = new point (currentblockpos. X, currentblockpos. Y + 1); // else we can move to the new position, lets do it! For (INT x = 0; x <gridwidth; X ++) for (INT y = 0; y <gridheight; y ++) floatinggrid [x, y] = false; for (INT I = 0; I <4; I ++) {grid [newpos [I]. x, newpos [I]. y] = (blocktypes) currentblocktype; floatinggrid [newpos [I]. x, newpos [I]. y] = true;} // for sound. play (sound. sounds. blockmove); Return true;} // else} // moveblock (movetype) # endregion

You can make three types of movement: left, right, and bottom. Each of these moves is processed in an independent code block to see if the left, right, and bottom data can be obtained, and whether the data can be moved there. Before going into the details of this method, two things should be mentioned. First, there is an auxiliary variable called movingdownwasblocked, which is defined on the method. The reason for this variable is to speed up the process of checking whether the current brick has reached the ground, in addition, this variable is stored at the class level so that the update method can be picked up later (probably after several frames), and when the user does not want to drop bricks, this greatly accelerates the refreshing of the gravity code you saw earlier. This is a very important part of the game, because if each brick is fixed immediately when it reaches the ground, the game will become very difficult and the fun will be lost, as the game becomes faster, grid becomes more full.

Then you can use another technique to simplify the check process by temporarily deleting the current brick from the grid. In this way, you can simply check whether a new location is feasible because your current location is no longer blocked. The Code also uses several auxiliary variables to store new locations, and the code is simplified because only four brick parts are computed. If you change the brick type and number of parts, you should also modify this method.

After everything is set, check whether a new virtual brick position is feasible in three code blocks. Generally, it is feasible and ends with four new values in the newposnum array. If you get less than three values, you will know that there is blocking. In any case, the anythingblocking variable is set to true. In this case, the location of the old bricks is restored, and the grid and floatinggrid arrays remain unchanged.

However, if the attempt to move the block is successful, the block position will be updated. You can clear the floatinggrid and add the block to the new position through the grid and floatinggrid arrays again. The user also heard a very quiet brick movement sound, which you did in the method.

Test

All the new code in the tetrisgrid class can now be unit tested in the tetrisgame class. Additionally, you need to test the two most important unit tests for game logic that you have previously seen:

    • Testrotatingblock, which tests the rotateblock method of the tetrisgrid class.

    • Testfallingblockandkillline, which is used to test gravity and user input you just learned.

You need to make the latest changes based on the game's needs, so you should track back the old unit tests to update them. For example, the testbackgroundboxes unit test you saw earlier is very simple, but when you execute and test the game components, the hierarchy planning and location of the background box are greatly changed, and its update must be reflected according to the modification. In this example, the scoreboard is surrounded by a background box. Before you know the size of the scoreboard, you must know what it is and how much space it consumes. For example, after compiling the testscoreboard method, it is obvious that the scoreboard must be much smaller than the nextblock background box.

Another part of the game test is to constantly check bugs and improve the game code. The first few games are quite simple. After the first run, you only need to make minor changes, but Tetris is much more complicated. It takes several hours to correct and improve it.

Last thing, you may test the running of the game on Xbox 360-just select the Xbox 360 console in the project and try running on Xbox 360. All the steps to be done are described in Chapter 1. In case of exceptions on the Xbox 360, it also has a useful fault repair piece. If you write new code, make sure that it is also compiled on xbox360 from time to time. You are not allowed to write any InterOP code that calls unmanaged accessories. Some. NET 2.0 Framework classes and methods are missing on Xbox 360.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.