C# 如何制作俄罗斯方块克隆?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/566159/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 08:24:54  来源:igfitidea点击:

How To Make a Tetris Clone?

c#data-structuressoftware-design

提问by Brock Woolf

I am working on coding a Tetris clone in XNA C# and am unsure of the best way to approach the data structure side of the game on a high level.

我正在编写 XNA C# 中的俄罗斯方块克隆代码,但不确定在高层次上处理游戏数据结构方面的最佳方法。

I am totally fine with the collision detection, rotations, animation etc. What I need to know the best way to do the storing of "dropped blocks" - ie blocks that are no longer under tha player's control.

我对碰撞检测、旋转、动画等完全满意。我需要知道存储“掉落块”的最佳方法 - 即不再受玩家控制的块。

I think that each Tetromino block should be stored in its own class that consists of a 4x4 array so that the block can easily be rotated. The problem is then how to I store the tetromino's final position into the game grid by then cutting the tetromino up into individual blocks(for each cell) and then set the main game grid's corresponding positions to hold these same blocks, then disappearing the tetromino once it has reached its final position. Maybe there is some drawback to my method.

我认为每个 Tetromino 块都应该存储在由 4x4 数组组成的自己的类中,以便块可以轻松旋转。问题是如何通过将 tetromino 切割成单独的块(对于每个单元格),然后设置主游戏网格的相应位置以容纳这些相同的块,然后将 tetromino 消失一次,从而将 tetromino 的最终位置存储到游戏网格中它已经到了最后的位置。也许我的方法有一些缺点。

Should I create a 10x20 matrix for the main game grid which can then store? or should I use stacks or queues to somehow store the dropped blocks. Or maybe there is some better method/data structure to store things?

我应该为可以存储的主游戏网格创建一个 10x20 矩阵吗?或者我应该使用堆栈或队列以某种方式存储丢弃的块。或者也许有一些更好的方法/数据结构来存储东西?

I am sure my way would work, but I am reaching out to see if anybody knows a better way or if my way is good enough?

我确信我的方法会奏效,但我正在寻求帮助,看看是否有人知道更好的方法,或者我的方法是否足够好?

P.S. Not homework, this will be a project for my portfolio. Thanks.

PS 不是作业,这将是我的作品集的一个项目。谢谢。

采纳答案by Dan Lew

Once a block is immobile, there's nothing that distinguishes it from any other block that is now immobile. In that regard, I think it makes the most sense to store the entire grid as a matrix, where each square is either filled or not (along with the color of the block if it is).

一旦一个块不动了,就没有什么可以将它与现在不动的任何其他块区分开来了。在这方面,我认为将整个网格存储为矩阵是最有意义的,其中每个方块要么被填充,要么没有(如果有的话,还有块的颜色)。

I feel like the matrix has many advantages. It'll make collision detection simple (no having to compare with multiple objects, just locations on a matrix). Storing it as a matrix will also make it easier to determine when a full line has been created. On top of that, you don't have to worry about splicing an immobile Tetromino when a line disappears. And when one does, you can just shift the entire matrix down in one fell swoop.

我觉得矩阵有很多优点。它将使碰撞检测变得简单(无需与多个对象进行比较,只需在矩阵上的位置)。将其存储为矩阵还可以更轻松地确定何时创建了完整的行。最重要的是,当一条线消失时,您不必担心拼接固定的 Tetromino。当一个人这样做时,你可以一举将整个矩阵向下移动。

回答by Paul Tomblin

Keep in mind that a previous winner of the Obfuscated C Code Contest implemented a pretty good tetris game (for VT100 terminals on BSD unix) in fewer than 512 bytes of obfuscated C:

请记住,前一个混淆 C 代码竞赛的获胜者用少于 512 字节的混淆 C 实现了一个非常好的俄罗斯方块游戏(用于 BSD unix 上的 VT100 终端):

long h[4];t(){h[3]-=h[3]/3000;setitimer(0,h,0);}c,d,l,v[]={(int)t,0,2},w,s,I,K
=0,i=276,j,k,q[276],Q[276],*n=q,*m,x=17,f[]={7,-13,-12,1,8,-11,-12,-1,9,-1,1,
12,3,-13,-12,-1,12,-1,11,1,15,-1,13,1,18,-1,1,2,0,-12,-1,11,1,-12,1,13,10,-12,
1,12,11,-12,-1,1,2,-12,-1,12,13,-12,12,13,14,-11,-1,1,4,-13,-12,12,16,-11,-12,
12,17,-13,1,-1,5,-12,12,11,6,-12,12,24};u(){for(i=11;++i<264;)if((k=q[i])-Q[i]
){Q[i]=k;if(i-++I||i%12<1)printf("3[%d;%dH",(I=i)/12,i%12*2+28);printf(
"3[%dm  "+(K-k?0:5),k);K=k;}Q[263]=c=getchar();}G(b){for(i=4;i--;)if(q[i?b+
n[i]:b])return 0;return 1;}g(b){for(i=4;i--;q[i?x+n[i]:x]=b);}main(C,V,a)char*
*V,*a;{h[3]=1000000/(l=C>1?atoi(V[1]):2);for(a=C>2?V[2]:"jkl pq";i;i--)*n++=i<
25||i%12<2?7:0;srand(getpid());system("stty cbreak -echo stop u");sigvec(14,v,
0);t();puts("3[H3[J");for(n=f+rand()%7*4;;g(7),u(),g(0)){if(c<0){if(G(x+
12))x+=12;else{g(7);++w;for(j=0;j<252;j=12*(j/12+1))for(;q[++j];)if(j%12==10){
for(;j%12;q[j--]=0);u();for(;--j;q[j+12]=q[j]);u();}n=f+rand()%7*4;G(x=17)||(c
=a[5]);}}if(c==*a)G(--x)||++x;if(c==a[1])n=f+4**(m=n),G(x)||(n=m);if(c==a[2])G
(++x)||--x;if(c==a[3])for(;G(x+12);++w)x+=12;if(c==a[4]||c==a[5]){s=sigblock(
8192);printf("3[H3[J3[0m%d\n",w);if(c==a[5])break;for(j=264;j--;Q[j]=
0);while(getchar()-a[4]);puts("3[H3[J3[7m");sigsetmask(s);}}d=popen(
"stty -cbreak echo stop 3;cat - HI|sort -rn|head -20>/tmp/$$;mv /tmp/$$ HI\
;cat HI","w");fprintf(d,"%4d on level %1d by %s\n",w,l,getlogin());pclose(d);}

http://www.ioccc.org/1989/tromp.hint

http://www.ioccc.org/1989/tromp.hint

回答by Welbog

This smells like homework, but my take on an object-oriented approach to Tetris would be to have each individual square be an object, and both "blocks" (tetrominos) and the grid itself would be collections of the same square objects.

这闻起来像作业,但我对俄罗斯方块的面向对象方法的看法是让每个单独的方块都是一个对象,并且“块”(tetrominos)和网格本身都是相同方块对象的集合。

Block objects manage the rotation and position of the falling squares, and the grid handles displaying them and detroying completed rows. Each block would have a colour or texture associated with it that would be provided by the original block object it came from, but otherwise squares at the base of the grid would have no other indication that they were ever part of the same original block.

块对象管理下降方块的旋转和位置,网格手柄显示它们并破坏已完成的行。每个块都有一个与之相关的颜色或纹理,这些颜色或纹理将由它来自的原始块对象提供,否则网格底部的方块将没有其他迹象表明它们曾经是同一原始块的一部分。

To elaborate, when you create a new block object, it creates a set of 4 squares with the same colour/texture on the grid. The grid manages their display. So when the block hits the bottom, you just forget about the block, and the squares remain referenced by the grid.

详细地说,当您创建一个新的块对象时,它会在网格上创建一组 4 个具有相同颜色/纹理的方块。网格管理它们的显示。因此,当块碰到底部时,您只需忘记块,而正方形仍由网格引用。

Rotations and dropping are operations only a block need deal with, and only one its four squares (though it will need to be able to query the grid to make sure the rotation can fit).

旋转和放置是只有一个块需要处理的操作,并且只有一个它的四个正方形(尽管它需要能够查询网格以确保旋转可以适合)。

回答by KingNestor

I'm by no means a Tetris expert, but as you described a 10x20 matrix seems like a natural choice to me.

我绝不是俄罗斯方块专家,但正如你所描述的,10x20 矩阵对我来说似乎是一个自然的选择。

It will make it very easy when the time comes to check if you have completed a line or not, and dealing with it. Simply iterating over the 2d-array looking at boolean values of each position to see if they add up to 10 block positions.

当需要检查您是否已完成一行并处理它时,这将变得非常容易。简单地遍历二维数组,查看每个位置的布尔值,看看它们是否加起来等于 10 个块位置。

However, you'll have some manual clean up to do if there is a completed line. Having to shift everything down. All though it isn't that big of a deal when it comes down to it.

但是,如果有一条完整的线路,您将需要进行一些手动清理。不得不把一切都往下移。尽管归根结底这没什么大不了的。

回答by HyperCas

Using arrays would be the easiest way to handle tetris. There is a direct correlation between what you see on screen and the structures used in memory. Using stack/queues would be an overkill and unnecessarily complicated.

使用数组将是处理俄罗斯方块的最简单方法。您在屏幕上看到的内容与内存中使用的结构之间存在直接关联。使用堆栈/队列将是一种矫枉过正和不必要的复杂。

You can have 2 copies of a falling block. One will be for display (Alpha) and the other one will be movement (Beta).

你可以有 2 个下落方块的副本。一个用于显示(Alpha),另一个用于移动(Beta)。

You will need a structure like

您将需要一个类似的结构


class FallingBlock
{
  int pos_grid_x;
  int pos_grid_y;
  int blocks_alpha[4][4];
  int blocks_beta[4][4];

  function movedDown();
  function rotate(int direction();
  function checkCollision();
  function revertToAlpha();
  function copyToBeta()
};

The _beta array would be move or rotated and checked against the board for collisions. If there is a collision, revert it to _alpha, if not, copy _beta onto _alpha.

_beta 数组将被移动或旋转,并检查板是否发生碰撞。如果发生碰撞,则将其还原为 _alpha,否则,将 _beta 复制到 _alpha。

And if there is a collision on movedDown(), the block's life is over and the _alpha grid would have to copied onto the game board and the FallingBlock object deleted.

如果在movedDown() 上发生碰撞,块的生命就结束了,_alpha 网格必须复制到游戏板上,并删除FallingBlock 对象。

The board would of course have to be another structure like:

董事会当然必须是另一种结构,例如:


class Board
{
  int gameBoard[10][20];

  //some functions go here
}

I used int to represent a block, each value (like 1,2,3) representing a different texture or color (0 would mean an empty spot).

我用 int 表示一个块,每个值(如 1,2,3)代表不同的纹理或颜色(0 表示一个空点)。

Once the block is part of the gameboard, it would only need a texture/color identifier to be displayed.

一旦块成为游戏板的一部分,它只需要显示纹理/颜色标识符。

回答by Davy8

I actually just did this a few days ago except in WPF rather than XNA. Here's what I did:

实际上,我几天前才这样做,除了在 WPF 而不是 XNA 中。这是我所做的:

Edit: Seems like I define "Block" differently than other people. What I define as a Block is one of 4 cells that make up a Tetromino, and an actual Tetromino itself as a Piece.

编辑:似乎我对“阻止”的定义与其他人不同。我定义为块的是组成 Tetromino 的 4 个单元格之一,以及作为 Piece 的实际 Tetromino 本身。

Have a Block as a struct that had X, Y coordinates and Color. (I later added a bool IsSet to indicate whether it was in a floating piece or on the actual board, but that was just because I wanted to distinguish them visually)

将 Block 作为具有 X、Y 坐标和颜色的结构。(我后来添加了一个 bool IsSet 来表明它是在浮动块中还是在实际板上,但这只是因为我想在视觉上区分它们)

As methods on Block, I had Left, Right, Down, and Rotate(Block center) which returned a new shifted Block. This allowed me to rotate or move any piece without knowing the shape or orientation of the piece.

作为 Block 上的方法,我有 Left、Right、Down 和 Rotate(Block center),它们返回一个新的移位块。这使我可以在不知道工件的形状或方向的情况下旋转或移动任何工件。

I had a generic Piece object that had a List of all the blocks it contained and the index of the Block that was the center, which is used as the center of rotation.

我有一个通用的 Piece 对象,它有一个包含它包含的所有块的列表以及作为中心的块的索引,用作旋转中心。

I then made a PieceFactory that could produce all the different pieces, and with a Piece not needing to know what kind of piece it was, I could (and did) easily add variation of Pieces consisting of more or less than 4 Blocks without needing to create any new classes

然后我制作了一个 PieceFactory 可以生产所有不同的零件,并且不需要知道它是什么类型的零件,我可以(并且确实)轻松地添加由多于或少于 4 个块组成的零件的变化,而无需创建任何新类

The Board consisted of a Dictionary which was all the blocks that were currently on the board, as well as the dimensions of the board that was configurable. You can use a Matrix just as well probably, but with a Dictionary I only needed to iterate through Blocks without white spaces.

Board 由一个 Dictionary 组成,其中包含当前在 Board 上的所有块,以及可配置的 Board 尺寸。您可能也可以使用 Matrix,但是使用 Dictionary 我只需要遍历 Blocks 而没有空格。

回答by Kent Boogaart

Not making blocks actually look like autonomous blocks is - in my opinion - a big failing of many a Tetris clone. I put special effort into ensuring my clonealways looked right, whether the block is still "in play" or dropped. This meant going slightly beyond the simple matrix data structure and coming up with something that supported the concept of "connection" between block parts.

在我看来,不让方块看起来像自治方块是许多俄罗斯方块克隆的一大失败。我特别努力确保我的克隆始终看起来正确,无论该块是否仍在“播放中”或已丢弃。这意味着稍微超越简单的矩阵数据结构,并提出一些支持块部件之间“连接”概念的东西。

I had a class called BlockGridthat is used as a base class for both a Blockand the Board. BlockGridhas an abstract (pure virtual in C++) method called AreBlockPartsSameBlockthat subclasses must override to determine whether two different block parts belong to the same block. For the implementation in Block, it simply returns trueif there are block parts at both locations. For the implementation in Board, it returns trueif both locations contain the same Block.

我有一个名为的类BlockGrid,用作 aBlockBoard. BlockGrid有一个抽象(C++ 中的纯虚拟)方法AreBlockPartsSameBlock,子类必须重写该方法来确定两个不同的块部分是否属于同一个块。对于 中的实现Blocktrue如果两个位置都有块部分,它就会简单地返回。对于 中的实现Boardtrue如果两个位置都包含相同的,则返回Block

The BlockGridclass uses this information to "fill in" the details in the rendered blocks, so that they actually look like blocks.

BlockGrid班采用“填满”的渲染块的细节这一信息,所以他们实际上看起来像块。

回答by Simon Peverett

My Solution (design), with examples in Python as a good substitute for pseudo code.

我的解决方案(设计),以 Python 中的示例作为伪代码的良好替代品。

Use a grid 20 x 10, that the tetrominoes fall down.

使用 20 x 10 的网格,使四元骨掉下来。

Tetrominoes are made up of blocks, which have attributes of coordinate (x,y) and colour.

Tetrominoes 由块组成,块具有坐标 (x,y) 和颜色属性。

So, for example, the T-shape tetrominoe looks like this...

因此,例如,T 形四联牌看起来像这样......

     . 4 5 6 7 8 .
  .
  19     # # #
  20       #
  .   

Thus, the T-shape is a collection of blocks with the coords (5,19), (6,19), (7,19), (6,20).

因此,T 形是具有坐标 (5,19)、(6,19)、(7,19)、(6,20) 的块的集合。

Moving the shape is a matter of applying a simple transformation to all the coords in the group. e.g. to move the shape down add (0,1), left (-1,0) or right (1,0) to all coords in the collection that make the shape.

移动形状是对组中的所有坐标应用简单变换的问题。例如,将形状向下移动,将 (0,1)、向左 (-1,0) 或向右 (1,0) 添加到构成该形状的集合中的所有坐标。

This also lets you use some simple trig to rotate the shape by 90-degrees. The rule is that when rotating 90-degrees relative to an origin, then (x,y) becomes equal to (-y,x).

这还允许您使用一些简单的触发器将形状旋转 90 度。规则是,当相对于原点旋转 90 度时,(x,y) 变得等于 (-y,x)。

Here is an example to explain it. Taking the T-shape from above, use the (6,19) as the centre block to rotate around. For simplicity, make this the first coordinate in the collection, so...

这里有一个例子来解释它。从上面取T形,使用(6,19)作为中心块旋转。为简单起见,将其设为集合中的第一个坐标,因此...

 t_shape = [ [6,19], [5,19], [7,19], [6,20] ]

Then, here is a simple function to rotate that collection of coordinates by 90-degrees

然后,这是一个将坐标集合旋转 90 度的简单函数

def rotate( shape ):
    X=0      # for selecting the X and Y coords
    Y=1

    # get the middle block
    middle = shape[0]   

    # work out the coordinates of the other blocks relative to the
    # middle block
    rel = []
    for coords in shape:
        rel.append( [ coords[X]-middle[X], coords[Y]-middle[Y] ] )

    # now rotate 90-degrees; x,y = -y, x
    new_shape = []
    for coords in rel:
        new_shape.append( [ middle[X]-coords[Y], middle[Y]+coords[X] ] )

    return new_shape

Now, if you apply this function to our collection of coordinate for the T-shape...

现在,如果您将此函数应用于我们的 T 形坐标集合...

    new_t_shape = rotate( t_shape )

    new_t_shape
    [[6, 19], [6, 18], [6, 20], [5, 19]]

Plot this out in the coordinate system and it looks like this...

在坐标系中绘制它,它看起来像这样......

     . 4 5 6 7 8 .
  .
  18       #
  19     # #
  20       #
  .   

That was the hardest bit for me, hope this helps someone.

这对我来说是最难的一点,希望这对某人有所帮助。

回答by Francis.Beauchamp

Using Simon Peverett logic, here is what I ended up with in c#

使用 Simon Peverett 逻辑,这是我在 c# 中的结果

public class Tetromino 
{
    // Block is composed of a Point called Position and the color
    public Block[] Blocks { get; protected internal set; }

    // Constructors, etc.

    // Rotate the tetromino by 90 degrees, clock-wise
    public void Rotate() 
    {
        Point middle = Blocks[0].Position;
        List<Point> rel = new List<Point>();
        foreach (Block b in Blocks)
            rel.Add(new Point(b.Position.x - middle.x, b.Position.y - middle.y));

        List<Block> shape = new List<Block>();
        foreach (Point p in rel)
            shape.Add(new Block(middle.x - p.y, middle.y + p.x));

        Blocks = shape.ToArray();
    }

    public void Translate(Point p)
    {
        // Block Translation: Position+= p; 
        foreach (Block b in Blocks)
            b.Translate(p);
    }
}

Note:Using XNA, Pointstructure could be swapped for Vector2D

注意:使用 XNA,Point结构可以交换为Vector2D

回答by Ion

in my example (Java) - all figures have lists of blocks - which can be removed whenever needed. Also in my Board class I have a list of figures and a field variable figure - which is controlled by the user. When the figure is "landed" - it goes into the list of other figures, and a new figure is made controllable by the user. A better explanation here: http://bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/

在我的示例(Java)中 - 所有图形都有块列表 - 可以在需要时删除。同样在我的 Board 类中,我有一个数字列表和一个字段变量数字 - 由用户控制。当图形“着陆”时 - 它进入其他图形列表,并且用户可以控制新图形。这里有更好的解释:http: //bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/