Chapter 4(2):Tetris 俄罗斯方块

Tetris, Tetris, Tetris!

Enough with all the helper classes and game components discussions. It is time to write another cool game. Thanks to the many classes available in the little game engine it is now easy to write text on the screen, draw sprites, handle input, and play sounds.

辅助类和游戏组件的讨论已经足够多了,是时候来写一个酷酷的游戏了。多亏了这个小型的游戏引擎,现在往屏幕上写文本、绘制精灵、操作输入和播放声音都容易多了.

Before going into the details of the Tetris game logic, it would be useful to think about the placement of all game elements in a similar way you did in the previous games. Instead of drawing all game components on the screen, you just show the background boxes to see what is going to be displayed. For the background you use the space background once 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). It is used to separate the game components and make everything fit much nicer on the screen. You could also just reuse the same box for both parts of the game, but because the aspect ratio is so different for them it would either look bad for the background box or for the extra game components, which are smaller, but also need the background box graphic, just a smaller version of it.

在进入详细的Tetris游戏逻辑之前,像以前一样考虑一下游戏元素的布局是有必要的。不必把所有的游戏组件都绘制到屏幕上,你只需要画出背景框看看什么东西要被展示出来。对于背景你要再次用到空间背景图(我保证这会是最后一次)。背景框是一个新的纹理,并且有两种模式。经常要分离游戏的组件,并在屏幕上找到最适合它们的地方。你可以为游戏的两个部分重用同一个框,但是因为大小比例过于不同这样要么是背景框看起来不好要么是额外的组件看起来太小,但是还是需要背景框图像,只是一个小一些的版本。


Figure 4-7

Rendering the Background

To render these boxes on the screen you use the SpriteHelper class again and test everything with help of the following unit test:

向屏幕上渲染这些边框你要再一次用到SpriteHelper并且依靠下面的单元测试来测试所有的东西:

  

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- 1540 - 12400 + 23, (768 - 40+ 16));
      TestGame.game.backgroundSmallBox.Render(
new Rectangle(
        (
512 - 480- 1540 - 10290 - 30300));
      TestGame.game.backgroundSmallBox.Render(
new Rectangle(
        (
512 + 240- 1540 - 10290 - 30190));
    }
);
}
  //  TestBackgroundBoxes()

 

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

运行这个单元测试你会看到下面的图:


Figure 4-8

You might ask why the right box is a little bit smaller and where I got all these values from. Well, I just started with some arbitrary values and then improved the values until everything in the final game fit. First, the background is drawn in the unit test because you will not call the Draw method of TetrisGame if you are in the unit test (otherwise the unit tests won’t work anymore later when the game is fully implemented).

你可能要问为什么右边的框会比较小,还有这些值我要从哪里获得。好,我仅仅是随便使用了一些值来开始的,然后还要不断的修正这些数值。首先背景已经在单元测试中被绘制了,因为如果你在单元测试中你将不会调用TetrisGameDraw方法(不然的话当游戏完全执行时单元测试就会起不到任何作用)。

Then three boxes are drawn. The upper-left box is used to show the next block. The center box shows the current Tetris grid. And finally, the upper-right box is used to display the scoreboard. You already saw the unit test for that earlier.

这三个方框已经画好了。左上角的方框用来显示下一个方块。中间的方框显示当前的Tetris格子。最后,右上角的方框用来显示分数板。你已经在之前看到了这些单元测试。

Handling the Grid

It is time to fill the content of these boxes. Start with the main component: the TetrisGrid. This class is responsible for displaying the whole Tetris grid. It handles the input and moves the falling block and it shows all the existing data as well. You already saw which methods are used in the TetrisGrid class in the discussion about the game components. Before rendering the grid you should check out the first constants defined in the TetrisGrid class:

现在可以开始填充内容到这些方框中去了。从主要的部分开始:TetrisGrid。这个类负责显示所有的Tetris格子。它控制输入和移动落下的方块还有显示所有存在的数据。你已经在讨论游戏组件的时候看到了哪一个方法在TetrisGrid类中使用。在开始渲染格子之前你应该检查在TetrisGrid类中定义的常量:

  

Constants

 

There are a couple more interesting constants, but for now you only need the grid dimensions. So you have 12 columns and 20 lines for your Tetris field. With help of the Block.png texture, which is just a simple quadratic block, you can now easily draw the full grid in the Draw method:

有几个有趣的常量,但是现在你只需要格子的尺寸。因此你有1220行的Tetris区域。依靠Block.png纹理(只是一个简单的方形块)现在你可以很用以的在Draw方法中绘制全部的格子了:

  

//  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(606060128)); // Empty color
  }
  //  for for

 

The gridRect variable is passed as a parameter to the Draw method from the main class to specify the area where you want the grid to be drawn to. It is the same rectangle as you used for the background box, just a little bit smaller to fit in. The first thing you are doing here is calculating the block width and height for each block you are going to draw. Then you go through the whole array and draw each block with the help of the SpriteHelper.Render method using a half transparent dark color to show an empty background grid. See Figure 4-9 to see how this looks. Because of the fact that you use game components you also don’t have to do all this code in your unit test. The unit test just draws the background box and then calls the TetrisGrid.Draw method to show the results (see the TestEmptyGrid unit test).

gridRect变量作为一个参数被从main类传递到Draw方法中来指定你要绘制各自的区域。这个是和你用来绘制背景框一样的矩形,只是更小一些。这里你要做的第一件事是为每一个要绘制的方块计算的宽和高。然后你要仔细检查整个数组并使用SpriteHelper.Render绘制每一个方块,使用半透明的暗色来呈现一个空的背景格。你会得到图4-9的效果。因为你使用了组件所以你不需要在单元测试中写完这些代码。单元测试只是绘制背景框然后调用TetrisGrid.Draw方法来显示结果(去看TestEmptyGrid单元测试)。


Figure 4-9

Block Types

Before you can render anything useful on your new grid you should think about the block types you can have in your game. The standard Tetris game has seven block types; all of them consist of four small blocks connected to each other (see Figure 4-10). The most favorite block type is of course the line type because you can kill up to four lines with that giving you the most points.

在你能在你的格子里渲染东西之前你应该考虑你游戏中应有的方块类型。标准的Tetris游戏有七种方块类型,这所有类型都包含有四个更小的方块。最喜欢的类型当然是直线形的那一个,因为这个可以消掉四行一次性获得最高的分数。


Figure 4-10

These block types have to be defined in the TetrisGrid class. One way of doing that is to use an enum holding all the possible block types. This enum can also hold an empty block type allowing you to use this data structure for the whole grid too because each grid block can contain either any part of the predefined block types or it is empty. Take a look at the rest of the constants in the TetrisGrid class:

这些砖块类型要在TetrisGrid类中定义。其中一种方法就是使用一个枚举来保存这些类型。枚举值还可以保存一个空的砖块类型来允许你也为整个格子使用这个数据结构,因为每一个格子块都包含预先设定的方块的任何部分或者是空的。来看看TetrisGrid类还有些什么常量:

  

/// <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( 606060128 ), // Empty, color unused
    new Color( 5050255255 ), // Line, blue
    new Color( 160160160255 ), // Block, gray
    new Color( 2555050255 ), // RightT, red
    new Color( 25525550255 ), // LeftT, yellow
    new Color( 50255255255 ), // RightShape, teal
    new Color( 25550255255 ), // LeftShape, purple
    new Color( 5025550255 ), // Triangle, green
  }
//  Color[] BlockColor
/// <summary>
/// Unrotated shapes
/// 未旋转的形状
/// </summary>

public   static   readonly   int [][,] BlockTypeShapesNormal  =   new   int [][,]
  
{
    
// Empty
    new int[,] 0 } },
    
// Line
    new int[,] 010 }010 }010 }010 } },
    
// Block
    new int[,] 11 }11 } },
    
// RightT
    new int[,] 11 }10 }10 } },
    
// LeftT
    new int[,] 11 }01 }01 } },
    
// RightShape
    new int[,] 011 }110 } },
    
// LeftShape
    new int[,] 110 }011 } },
    
// Triangle
    new int[,] 010 }111 }000 } },
  }
//  BlockTypeShapesNormal

 

BlockTypes is the enum we talked about; it contains all the possible block types and also is used to randomly generate new blocks in the NextBlock game component. Initially all of the grid fields are filled with the empty block type. The grid is defined as:

BlockTypes就是我们索道的枚举;它包含所有可能的砖块类型,也用于在NextBlock组件中随机生成新的砖块。一开始所有的格子区域都被空块类型填充满了。格子是这样定义的:

  

/// <summary>
/// The actual grid, contains all blocks,
/// including the currently falling block.
/// 实际的格子包含所有的砖块,也包括现在正在下落的砖块
/// </summary>

BlockTypes[,] grid  =   new  BlockTypes[GridWidth, GridHeight];

 

By the way, NumOfBlockTypes shows you the usefulness of the enum class. You can easily determine how many entries are in the BlockTypes enum.

顺便说一下,NumOfBlockTypes显示给你枚举类的用处。你可以轻易决定在BlockTypes枚举中有多少【入口】。

Next the colors for each block type are defined. These colors are used for the NextBlock preview, but also for rendering the whole grid. Each grid has a block type and you can easily use the BlockColors by converting the enum to an int number, which is used in the Draw method:

下一个是定义每一种砖块的颜色。这些颜色是用于前面的NextBlock的,但是也是用于渲染整个栅栏格子的。每一个格子都有一个砖块类型,并且你可以通过将枚举值转换成int行来方便地使用BlockColors,这些在Draw方法中使用:


  
  
   
   
  
  
BlockColor[(int)grid[x,y]]

And finally the block shapes are defined, which looks a little bit more complicated, especially if you take into consideration that you have to allow these block parts to be rotated. This is done with help of the BlockTypeShapes, which is a big array of all possible blocks and rotations calculated in the constructor of TetrisGrid.

最后,定义了砖块的形状,这看起来有点儿复杂,特别是如果你仔细考虑砖块的旋转的话。这些是由BlockTypeShapes来帮助完成的,这是一个储存所有可能砖块形状的大数组并且在TetrisGrid构造器中计算旋转。

To add a new block to the Tetris grid you can just add each of the block parts to your grid, which is done in the AddRandomBlock method. You keep a separate list called floatingGrid to remember which parts of the grid have to be moved down (see the following section, “Gravity”; you can’t just let everything fall down) each time Update is called:

要添加一个新的砖块到Tetris网格中你只需添加砖块的每一部分到你的网格中就行了,这个由AddRandomBlock方法来完成。你使用一个独立的叫floatingGrid的列表,在每一次Update被调用的时候记住格子的哪一部分已经向下移动了(看下一部分“Gravity”,你就不能使每一样东西都往下落)

  

//  Randomize block type and rotation
//  随机化砖块类型和旋转
currentBlockType  =  ( int )nextBlock.SetNewRandomBlock();
currentBlockRot 
=  RandomHelper.GetRandomInt( 4 );

//  Get precalculated shape
//  获得预先计算好的形状
int [,] shape  =  BlockTypeShapes[currentBlockType,currentBlockRot];
int  xPos  =  GridWidth / 2 - shape.GetLength( 0 ) / 2 ;
//  Center block at top most position of our grid
currentBlockPos  =   new  Point(xPos,  0 );

//  Add new block
for  (  int  x = 0 ; x < shape.GetLength( 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 for if

 

First you determine which block type you are going to add here. To help you do that you have a helper method in the NextBlock class, which randomizes the next block type and returns the last block type that was displayed in the NextBlock window. The rotation is also randomized; say “Hi” to the RandomHelper class.

首先你要确定你要添加哪一种砖块。为了完成这个问题,你需要使用NextBlock类中的一个辅助方法,用来随机生成下一种砖块并返回在NextBlock窗口中显示的上一种砖块。砖块的旋转也是随机化的;向RandomHelper类说“你好”。

With that data you can now get the precalculated shape and put it centered on the top of your grid. The two for loops iterate through the whole shape. It adds each valid part of the shape until you hit any existing data in the grid. In case that happens the game is over and you hear the lose sound. This will happen if the pile of blocks reaches the top of the grid and you cannot add any new blocks.

根据数据你现在可以预先计算砖块的形状然后把它放到网格顶部的中间。用两个for循环迭代所有的形状。它添加形状的正确部分直到你碰到格子中已经存在的数据。万一发生这种情况游戏就结束,你会听到输掉的声音。这种情况将会发生在大量的砖块已经堆积到了网格的顶部而且你已经不能添加任何新的砖块的时候。

You now have the new block on your grid, but it is boring to just see it on the top there; it should fall down sometimes.

现在你的网格中有了新的砖块,但是现在只能无聊的看着它在顶部不动;它应该不断的下落。

Gravity

To test the gravity of the current block the TestFallingBlockAndLineKill unit test is used. The active block is updated each time you call the Update method of TetrisGrid, which is not very often. In the first level the Update method is called only every 1000ms (every second). There you check if the current block can be moved down:

TestFallingBlockAndLineKill单元测试用来测试当前砖块的下落引力。每一次当你调用TetrisGridUpdate方法是当前活动的砖块就会被更新。在第一等级每1000毫秒调用一次Update方法。这里你要检查砖块是否正常向下移动:

  

//  Try to move floating stuff down
if  (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);
}
  //  if
movingDownWasBlocked  =   false ;

 

Most of the Tetris logic is done in the MoveBlock helper method, which checks if moving in a specific direction is possible at all. If the block can’t be moved anymore it gets fixed and you clear the floatingGrid array and play the sound for landing a block on the ground.

Tetris的逻辑部分大多在MoveBlock辅助方法中完成,这个方法检查在指定方向上的移动是否可能。如果砖块不再能移动它将被固定然后你要清空floatingGrid数组然后播放砖块触底的声音。

After clearing the floatingGrid array there is no active block you can move down and the following code is used to check if a line was destroyed:

清空floatingGrid数组后你就没有可以向下移动的砖块了,下面的代码用来检查是否有可以消掉的行:

  

//  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
     
// 增加10分和消除的行数
     score += 10;
     lines
++;
     linesKilled
++;
     Sound.Play(Sound.Sounds.LineKill);
   }
 // if
  }
 // for
  
// If we killed 2 or more lines, add extra score
// 如果一次清楚超过2行,有额外加分
  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 that is done here is to check if there is an active moving block. If not you go into the “if block,” checking if a full line is filled and can be destroyed. To determine if a line is filled you assume it is filled and then check if any block of the line is empty. Then you know that this line is not fully filled and continue checking the next line. If the line is filled, however, you remove it by copying all the lines above it down. This is the place where a nice explosion could occur. Anyway, the player gets 10 points for this line kill, and you hear the line kill sound.

现在要做的第一件事是是否有正在移动的砖块。如果没有,你会进到if block,”检查是否有已被填满的行,然后将其销毁。要确定一行是否被填满,你首先要假设他是被填满的然后检查这一行中是否有空的格子。然后你就可以知道这一行还没有被填满并继续检查下一行。如果这是一个满行,你就需要把从这一行开始的每一行的上一行往下复制,这样这一行就被消除了。可以显示一个爆炸的场景。玩家每消掉一行就会得到10分,你还会听到消行的声音。

If the player was able to kill more than one line he gets awarded more points. And finally the AddRandomBlock method you saw before is used to create a new block at the top.

如果玩家消掉了多于一行他会得到奖励分数。最后AddRandomBlock方法会在顶部创建一个新的砖块。

Handling Input

Handling the user input itself is no big task anymore thanks to the Input helper class. You can easily check if a cursor or gamepad key was just pressed or is being held down. Escape and Back are handled in the BaseGame class and allow you to quit the game. Other than that you only need four keys in your Tetris game. To move to the left and right the cursor keys are used. The up cursor key is used to rotate the current block and the down cursor key or alternatively the space or A keys can be used to let the block fall down faster.

因为有了Input辅助类,操作用户输入就不是什么大问题了。你可以很方便的检查方向键或手柄按键仅仅是被按了一下还是一直按着。Esc和退格键在BaseGame类中控制,允许你退出游戏。不同于你只在Tetris使用四个键。左右方向键用来控制砖块的左右移动。上方向键用来使砖块旋转,下方向键或空格或手柄的A键用来加速砖块的下落速度。

Similar to the gravity check to see if you can move the block down, the same check is done to see if you can move the current block left or right. Only if that is true do you actually move the block; this code is done in the TetrisGame Update method because you want to check for the player input every frame and not just when updating the TetrisGrid, which can only happen every 1000ms as you learned before. The code was in the TetrisGrid Update method before, but to improve the user experience it was moved and improved quite a lot also allowing you to move the block quickly left and right by hitting the cursor buttons multiple times.

像在重力【gravity】下落中那样检查是否还可以向下移动,也同样检查你是否还能左右移动当前砖块。如果为真,那就可以移动。代码在TetrisGame Update中完成,因为每一帧你都要检查玩家的输入,只有在更新TetrisGrid的时候不用检查,这个只是每1000毫秒发生一次。代码之前是在TetrisGrid Update方法中的,但是为了来两用户体验它被移动了,并且还做了改进允许你通过多次敲击方向键来快速左右移动。

Well, you have learned a lot about all the supporting code and you are almost doneto run the Tetris game for the first time. But you should take a look at the MoveBlock helper method because it is the most integral and important part of your Tetris game. Another important method is the RotateBlock method, which works in a similar way testing if a block can be rotated. You can check it out yourself in the source code for the Tetris game. Please use the unit tests in the TetrisGame class to see how all these methods work:

好了,你已经学到了起重要作用的代码了,你几乎要开始第一次运行Tetris游戏了。但是还要看看MoveBlock辅助类,因为它是Tetris游戏最主要和重要的部分。另外一个重要的方法是RotateBlock,它使用相似的方法检查一个砖块是否能旋转。你可以自己去看Tetris的源代码。请在TetrisGame类中使用单元测试看看所有这些方法是怎样工作的:

  

Move block

 

There are three kinds of moves you can do: Left, Right, and Down. Each of these moves is handled in a separate code block to see if the left, right, or down data is available and if it is possible to move there. Before going into the details of this method there are two things that should be mentioned. First of all there is a helper variable called movingDownWasBlocked defined above the method. The reason you have this variable is to speed up the process of checking if the current block reached the ground, and it is stored at the class level to let the Update method pick it up later (which can be several frames later) and make the gravity code you saw earlier update much faster than in the case when the user doesn’t want to drop the block down right here. This is a very important part of the game because if each block were immediately fixed when reaching the ground the game would become very hard, and all the fun is lost when it gets faster and the grid gets more filled.

你可以完成三种类型的移动:向左,向右和向下。这么一种操作都由独立的代码块来完成,它们检查左右下是否可移动。在详细讨论这个方法之前还有两点要提。第一是有一个在前面定义的叫movingDownWasBlocked的方法可以利用。这个用来加速检测砖块是否触底的过程,它还保存在类的等级中,让Update方法使用它,并且当用户不想等着砖块慢慢下落时还可以让砖块加速下落。这是游戏非常重要的部分,因为如果当砖块一到达地面就被固定,这样游戏难度会变大,当速度变快、网格被迅速填满,游戏就会失去所有的乐趣。

Then you use another trick to simplify the checking process by temporarily removing the current block from the grid. This way you can easily check if a new position is possible because your current position does not block you anymore. The code also uses several helper variables to store the new position and that code is simplified a bit to account for only four block parts. If you change the block types and the number of block parts, you should also change this method.

接着你可以使用另一个诡计来简化检查的过程,这个方法就是临时将当前的砖块从网格中移除。用这个方法你可以容易的检查一个新的位置是否可能使用,因为当前的位置不会对你造成阻碍。代码依然要使用几个辅助变量来储存新的位置,计算方块四个部分的代码也会被简化。如果你要改变砖块的类型和小方块的数目,你也需要修改这个方法。

After setting everything up you check if the new virtual block position is possible in the three code blocks. Usually it is possible and you end up with four new values in the newPosNum array. If there are less than three values available you know that something was blocking you and the anythingBlocking variable is set to true anyway. In that case the old block position is restored and both the grid and the floatingGrid arrays stay the same way.

设置好一切之后,你还要检查新的虚拟砖块的位置在三个代码块中是否是可能的。通常这是没问题的,你可以在newPosNum数组中使用四个新的值来完成。这样旧的砖块位置也被储存,gridfloatingGrid数组都还保持原样。

But in case the move attempt was successful the block position is updated and you clear the floatingGrid and finally add the block again to the new position by adding it both to the grid and the floatingGrid. The user also hears a very silent block move sound and you are done with this method.

【但是万一移动的尝试成功了,砖块的位置被更新了倪还清空了floatingGrid数组,最后又一次在gridfloatingGrid中添加了砖块到新的位置。用户同样会听到一声轻微的移动的声效,你使用这个方法来完成。】

Testing

With all that new code in the TetrisGrid class you can now test the unit tests in the TetrisGame class. In addition to the tests you saw before the two most important unit tests for the game logic are:

所有的新代码都在TetrisGrid类中,你现在可以测试TetrisGame类中的单元测试了。在测试中你会看到之前游戏中的最重要的两个单元测试:

§  TestRotatingBlock, which tests the RotateBlock method of the TetrisGrid class.

TestRotatingBlock,用来测试TetrisGrid类中的RotateBlock方法。

§  TestFallingBlockAndKillLine, which is used to test the gravity and user input you just learned about.

TestFallingBlockAndKillLine用来测试引力下落和刚刚学到的用户输入。

It should be obvious that you often go back to older unit tests to update them according to the newest changes you require for your game. For example, the TestBackgroundBoxes unit test you saw earlier is very simple, but the layout and position of the background boxes changed quite a lot while implementing and testing the game components and it had to be updated accordingly to reflect the changes. One example for that would be the scoreboard, which is surrounded by the background box, but before you can know how big the scoreboard is going to be you have to know what the contents are and how much space they are going to consume. After writing the TestScoreboard method it became very obvious that the scoreboard has to be much smaller than the NextBlock background box, for example.

很明显地,你会经常使用以前的单元测试,并根据游戏的新需要改变它们。例如,TestBackgroundBoxes是一个在以前使用的非常简单的单元测试,但是在改进游戏时背景框的布局和位置经常改变,你要根据改变来作出更新。一个例子就是分数板,它被背景框包围着,但是在你知道分数板有多大之前,你必须知道容量还有要消耗多少空间。在写了TestScoreboard方法之后,分数板要比NextBlock背景框小得多是显而易见的。

Another part of testing the game is constantly checking for bugs and improving the game code. The previous games were pretty simple and you only had to make minor improvements after the first initial runs, but the Tetris game is much more complex and you can spend many hours fixing and improving it.

另一个测试游戏的部分是检查bug和改进游戏代码。前面的游戏都非常简单,在你第一次运行后只需要慢慢地每次做一点点小的改进,但是Tetris就复杂多了,你可以花上很多个小时的时间来修正和改进它。

One last thing you could test is running the game on the Xbox 360 - just select the Xbox 360 console platform in the project and try to run it on the Xbox 360. All the steps to do that were explained in Chapter 1, which also has a useful troubleshooting section in case something does not work on the Xbox 360. If you write new code you should make sure from time to time that it compiles on the Xbox 360 too. You are not allowed to write any interop code calling unmanaged assemblies, and some of the .NET 2.0 Framework classes and methods are missing on the Xbox 360.

最有一个测试是在Xbox360上进行测试,只需要选择Xbox360平台然后试着在其上运行。所有的步骤在第一章已经解释过了。在Xbox360上你不能写和非托管代码交互的代码,也不能使用那些在Xbox360上不支持的.NET 2.0的类和方法(Xbox360使用的是精简版的.NET2.0.

  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值