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

时间:2009-02-19 16:43:15

标签: c# data-structures software-design

我正致力于在XNA C#中编写一个俄罗斯方块克隆,并且不确定在高级别上接近游戏数据结构方面的最佳方法。

我对碰撞检测,旋转,动画等完全没问题。我需要知道最好的方法来存储“掉落的块” - 即不再受玩家控制的块。

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

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

我确信我的方式会奏效,但我正在伸出手去看看是否有人知道更好的方式,或者我的方式是否足够好?

P.S。不是作业,这将是我的投资组合的项目。感谢。

10 个答案:

答案 0 :(得分:20)

一旦一个区块不能移动,就没有任何东西可以区别于现在不动的任何其他区块。在这方面,我认为将整个网格存储为矩阵是最有意义的,其中每个正方形是否填充(如果是,则与块的颜色一起)。

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

答案 1 :(得分:4)

这就像家庭作业一样,但我对俄罗斯方块采用面向对象的方法是将每个方块都作为一个对象,两个“块”(tetrominos)和网格本身都是相同方形对象的集合

块对象管理下降方块的旋转和位置,网格处理显示它们并摧毁已完成的行。每个块将具有与其相关联的颜色或纹理,该颜色或纹理将由它来自的原始块对象提供,但是否则网格底部的正方形将没有其他指示它们曾经是同一原始块的一部分。 / p>

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

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

答案 2 :(得分:3)

在我看来,没有让实际看起来像自治块的块是许多俄罗斯方块克隆的重大失败。我特别努力确保my clone总是看起来正确,无论该块是“在场”还是掉线。这意味着略微超越简单矩阵数据结构,并提出支持块部件之间“连接”概念的东西。

我有一个名为BlockGrid的类,它被用作BlockBoard的基类。 BlockGrid有一个名为AreBlockPartsSameBlock的抽象(C ++中的纯虚拟)方法,子类必须重写以确定两个不同的块部分是否属于同一个块。对于Block中的实现,如果两个位置都有块部分,则只返回true。对于Board中的实施,如果两个位置包含相同的true,则返回Block

BlockGrid类使用此信息“填充”渲染块中的​​细节,以使它们看起来像块。

答案 3 :(得分:2)

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

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

您需要一个像

这样的结构

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()
};

_beta数组将被移动或旋转,并在棋盘上检查是否有碰撞。如果发生碰撞,请将其恢复为_alpha,否则,将_beta复制到_alpha。

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

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


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

  //some functions go here
}

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

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

答案 4 :(得分:2)

我实际上只是在几天前做过这个,除了WPF而不是XNA。这是我做的:

编辑: 似乎我定义“阻止”与其他人不同。我定义为Block是4个组成Tetromino的单元之一,而Tetromino本身就是一个单元。

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

作为Block上的方法,我有左,右,下和旋转(块中心),它返回一个新的移位块。这使我可以旋转或移动任何一块而不知道该块的形状或方向。

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

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

董事会由一个字典组成,该字典是当前在电路板上的所有块,以及可配置的电路板尺寸。您也可以使用Matrix,但是使用Dictionary我只需要迭代没有空格的块。

答案 5 :(得分:2)

我的解决方案(设计),Python中的示例可以替代伪代码。

使用20 x 10的格子,使四联体落下。

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

因此,例如,T形tetrominoe看起来像这样......

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

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

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

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

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

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

然后,这是一个简单的函数,用于将坐标集合旋转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

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

    new_t_shape = rotate( t_shape )

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

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

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

这对我来说是最难的,希望这有助于某人。

答案 6 :(得分:1)

请记住,之前的混淆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("\033[%d;%dH",(I=i)/12,i%12*2+28);printf(
"\033[%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("\033[H\033[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("\033[H\033[J\033[0m%d\n",w);if(c==a[5])break;for(j=264;j--;Q[j]=
0);while(getchar()-a[4]);puts("\033[H\033[J\033[7m");sigsetmask(s);}}d=popen(
"stty -cbreak echo stop \023;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

答案 7 :(得分:1)

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

当检查你是否完成了一条线路并处理它时,它将变得非常容易。只需迭代2d数组,查看每个位置的布尔值,看它们是否加起来最多10个块位置。

但是,如果有完整的行,您将需要进行一些手动清理。不得不把一切都降下来。虽然它归结为它并不是一件大事。

答案 8 :(得分:0)

使用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);
    }
}

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

答案 9 :(得分:0)

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