Как сделать клон тетриса? - PullRequest
14 голосов
/ 19 февраля 2009

Я работаю над кодированием клона Тетрис в XNA C # и не уверен, как лучше подойти к стороне игры со структурой данных на высоком уровне.

Я полностью в порядке с обнаружением столкновений, вращением, анимацией и т. Д. Что мне нужно знать, как лучше всего хранить «упавшие блоки» - то есть блоки, которые больше не находятся под контролем игрока.

Я думаю, что каждый блок Tetromino должен храниться в своем собственном классе, который состоит из массива 4x4, чтобы блок мог легко вращаться. Тогда проблема заключается в том, как сохранить конечную позицию тетромино в игровой сетке, затем разрезать тетромино на отдельные блоки (для каждой ячейки), а затем установить соответствующие позиции основной игровой сетки, чтобы удерживать эти же блоки, а затем исчезнуть из тетромино он достиг своей окончательной позиции. Возможно, у моего метода есть какой-то недостаток.

Должен ли я создать матрицу 10x20 для основной игровой сетки, которую затем можно сохранить? или я должен использовать стеки или очереди, чтобы каким-то образом хранить отброшенные блоки. Или, может быть, есть какой-то лучший метод / структура данных для хранения вещей?

Я уверен, что мой путь сработает, но я пытаюсь выяснить, знает ли кто-нибудь лучший способ или достаточно хорош мой путь?

P.S. Не домашнее задание, это будет проект для моего портфолио. Спасибо.

Ответы [ 10 ]

20 голосов
/ 19 февраля 2009

Как только блок неподвижен, ничто не отличает его от любого другого блока, который теперь неподвижен. В связи с этим, я думаю, что имеет смысл хранить всю сетку в виде матрицы, где каждый квадрат либо заполнен, либо нет (вместе с цветом блока, если он есть).

Я чувствую, что матрица имеет много преимуществ. Это сделает обнаружение столкновений простым (не нужно сравнивать с несколькими объектами, только местоположениями в матрице). Хранение в виде матрицы также поможет определить, когда была создана полная строка. Кроме того, вам не нужно беспокоиться о сращивании неподвижного тетромино, когда линия исчезает. И когда это произойдет, вы можете просто сдвинуть всю матрицу одним махом.

4 голосов
/ 19 февраля 2009

Это пахнет как домашнее задание, но мой подход к объектно-ориентированному подходу к тетрису заключался бы в том, чтобы каждый отдельный квадрат был объектом, и оба «блока» (тетромино) и сама сетка были бы коллекциями одинаковых квадратных объектов .

Блочные объекты управляют вращением и положением падающих квадратов, а сетка управляет их отображением и разрушением завершенных рядов. Каждый блок будет иметь связанный с ним цвет или текстуру, которая будет предоставлена ​​исходным объектом блока, из которого он получен, но в противном случае квадраты у основания сетки не будут иметь другого указания, что они когда-либо были частью одного и того же исходного блока.

Для уточнения, когда вы создаете новый блочный объект, он создает набор из 4 квадратов одинакового цвета / текстуры на сетке. Сетка управляет их отображением. Поэтому, когда блок достигает дна, вы просто забываете о блоке, и квадраты остаются привязанными сеткой.

Вращения и отбрасывание - это операции, с которыми должен иметь дело только блок, и только один из его четырех квадратов (хотя он должен иметь возможность запрашивать сетку, чтобы убедиться, что вращение может соответствовать).

3 голосов
/ 19 февраля 2009

Не делать блоки на самом деле похожими на автономные блоки - это, на мой взгляд, большая неудача многих клонов тетриса. Я приложил особые усилия к тому, чтобы мой клон всегда выглядел правильно, независимо от того, находится ли блок в игре или упал. Это означало выход за рамки простой матричной структуры данных и создание чего-то, что поддерживало бы концепцию «связи» между частями блока.

У меня был класс с именем BlockGrid, который используется в качестве базового класса для Block и Board. BlockGrid имеет абстрактный (чисто виртуальный в C ++) метод, называемый AreBlockPartsSameBlock, который подклассы должны переопределять, чтобы определить, принадлежат ли две разные части блока одному и тому же блоку. Для реализации в Block он просто возвращает true, если в обоих местах есть части блока. Для реализации в Board возвращается true, если оба местоположения содержат одинаковые Block.

Класс BlockGrid использует эту информацию для "заполнения" деталей в визуализированных блоках, чтобы они фактически выглядели как блоки.

2 голосов
/ 24 марта 2009

Мое решение (дизайн) с примерами на Python в качестве хорошей замены псевдокоду.

Используйте сетку 20 х 10, чтобы тетромино упало.

Тетромино состоит из блоков, которые имеют атрибуты координат (x, y) и цвета.

Так, например, Т-образное тетромино выглядит следующим образом ...

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

Таким образом, Т-образная форма представляет собой набор блоков с координатами (5,19), (6,19), (7,19), (6,20).

Перемещение фигуры заключается в применении простого преобразования ко всем координатам в группе. например чтобы переместить форму вниз, добавьте (0,1), влево (-1,0) или вправо (1,0) ко всем координатам в коллекции, которые составляют фигуру.

Это также позволяет использовать простой триггер для поворота фигуры на 90 градусов. Правило заключается в том, что при повороте на 90 градусов относительно начала координат (x, y) становится равным (-y, x).

Вот пример, чтобы объяснить это. Принимая Т-образную форму сверху, используйте (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

Теперь, если вы примените эту функцию к нашей коллекции координат для Т-формы ...

    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       #
  .   

Это было самое трудное для меня, надеюсь, это кому-нибудь поможет.

2 голосов
/ 19 февраля 2009

Я просто сделал это несколько дней назад, за исключением WPF, а не XNA. Вот что я сделал:

Edit: Кажется, я определяю «Блок» иначе, чем другие люди. То, что я определяю как Блок, - это одна из 4 ячеек, составляющих Тетромино, и сам Тетромино как Часть.

Имейте Блок как структуру, у которой были координаты X, Y и Цвет. (Позже я добавил BoSl IsSet, чтобы указать, был ли он в плавающей части или на самой плате, но это было только потому, что я хотел различить их визуально)

В качестве методов для Block у меня были Left, Right, Down и Rotate (центр блока), которые возвращали новый сдвинутый блок. Это позволило мне вращать или перемещать любую фигуру, не зная формы или ориентации фигуры.

У меня был общий объект Piece, который имел список всех блоков, которые он содержал, и индекс блока, который был центром, который используется в качестве центра вращения.

Затем я создал PieceFactory, который мог бы производить все разные фигуры, и с помощью фигуры, не нуждающейся в том, чтобы знать, что это за штука, я мог (и делал) легко добавлять вариации штук, состоящие из более или менее 4 блоков без необходимости создавать какие-либо новые классы

Доска состояла из словаря, в котором были все блоки, которые в настоящее время были на плате, а также размеры платы, которая была настраиваемой. Вы можете использовать Матрицу, вероятно, также хорошо, но со Словарём мне нужно было только перебирать блоки без пробелов.

2 голосов
/ 19 февраля 2009

Использование массивов было бы самым простым способом обработки тетриса. Существует прямая связь между тем, что вы видите на экране, и структурами, используемыми в памяти. Использование стека / очередей было бы излишним и излишне сложным.

Вы можете иметь 2 копии падающего блока. Один будет для отображения (альфа), а другой будет движение (бета).

Вам понадобится такая структура, как


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.

И если на MoveDown () произошло столкновение, срок жизни блока закончился, и сетку _alpha пришлось бы скопировать на игровую доску, а объект FallingBlock удалили.

Конечно, доска должна быть другой структуры, такой как:


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

  //some functions go here
}

Я использовал int для представления блока, каждое значение (например, 1,2,3) представляет различную текстуру или цвет (0 означает пустое место).

Как только блок станет частью игрового поля, для его отображения потребуется только идентификатор текстуры / цвета.

1 голос
/ 19 февраля 2009

Я ни в коем случае не эксперт по тетрису, но, как вы описали, матрица 10x20 кажется мне естественным выбором.

Это будет очень легко, когда придет время проверить, закончили ли вы линию или нет, и справиться с ней. Просто перебирая 2d-массив, просматривая логические значения каждой позиции, чтобы увидеть, добавляют ли они до 10 позиций блока.

Тем не менее, вам придется выполнить некоторую ручную очистку, если завершена линия. Нужно сдвинуть все вниз. Хотя это и не так уж важно, когда дело доходит до этого.

1 голос
/ 19 февраля 2009

Имейте в виду, что предыдущий победитель конкурса кодов с запутанным кодом реализовал довольно хорошую игру в тетрис (для терминалов VT100 на BSD Unix) в менее чем 512 байтах запутанного C:

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

0 голосов
/ 21 октября 2014

в моем примере (Java) - все фигуры имеют списки блоков - которые могут быть удалены при необходимости. Также в моем классе Board у меня есть список фигур и переменная поля, которая контролируется пользователем. Когда фигура «приземлилась» - она ​​попадает в список других фигур, и новая фигура становится контролируемой пользователем. Лучшее объяснение здесь: http://bordiani.wordpress.com/2014/10/20/tetris-in-java-part-i-overview/

0 голосов
/ 12 августа 2013

Используя логику Саймона Певеретта, вот что я закончил в 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

...