Znajdź najlepszy ruch w grze Tetris

10

Bardzo lubię Tetris, ale nie jestem w tym zbyt dobry. Raz chciałbym zobaczyć, jak ten statek kosmiczny startuje na własne oczy! A ponieważ komputery są naprawdę świetne we wszystkim, jedynym możliwym rozwiązaniem jest stworzenie dla mnie programu do grania ... z wyjątkiem tego, że zrobisz to dla mnie!

Biorąc pod uwagę tetromino (kształt złożony z czterech kwadratów) i mapę pola gry, należy umieścić tetromino tak, aby uzyskało największą liczbę linii (sprawia, że ​​najwięcej wierszy jest całkowicie wypełnionych blokami) i tworzy najmniejszą liczbę z nowych otworów (pustej przestrzeni, która nie może „widzieć” szczyt boisku 1 ).

Wejście

Dane wejściowe będą zawierać znak w jednym wierszu reprezentującym upuszczające tetromino, a następnie 10 * 18 siatki 2 spacji ( ) i znaków plus ( +).

Postać reprezentuje dowolną z siedmiu podstawowych tetrominoes znalezionych w Tetris. Wszystkie elementy można obracać o 90 stopni, ale nie można ich odwrócić. Wszystkie tetromino i ich rotacje są następujące:

         #
S =  ##  ##
    ##    #

          #
Z = ##   ##
     ##  #

    #   ###  ##
L = #   #     #    #
    ##        #  ###

     #  ###  ##
J =  #    #  #     #
    ##       #   ###

          #   #   #
T = ###  ##  ###  ##
     #    #       #

O = ##
    ##

    #
I = #  ####
    #
    #

Siatka reprezentuje pole gry Tetris, z +uprzednio umieszczonymi blokami. Tak więc przykładowe dane wejściowe mogą wyglądać następująco:

I











+       ++
+    +++++
++ +++++++
++ +++++++
++ +++++++
++ +++++++
++++++ +++

Wynik

Twój wynik będzie identyczny z wejściem, ale z tetromino w idealnej pozycji. Tetromino powinno być reprezentowane przez, #aby odróżnić je od wstępnie umieszczonych bloków. Oprócz tego masz również wyprowadzić, ile linii / otworów utworzy Twoje umiejscowienie w formularzu xL yHw nowej linii.

Dane wyjściowe dla powyższego przykładu byłyby następujące 3 :

I











+       ++
+    +++++
++#+++++++
++#+++++++
++#+++++++
++#+++++++
++++++ +++
4L 0H

Masz generować tylko najlepsze wyniki; w przypadku dwóch lub więcej przypadków dających ten sam wynik, musisz wypisać wszystkie (oddzielone pustą linią). Najlepsze wyniki należy ustalić, sortując najpierw według liczby linii (malejąco), a następnie liczby utworzonych nowych otworów (rosnąco). Tak, 1L 1Hto lepszy wynik niż 0L 0H.

Będę pracować nad stworzeniem listy różnych danych wejściowych i oczekiwanych danych wyjściowych, na podstawie których możesz przetestować swój program. Patrz na przestrzeń.

Zasady i ujednoznacznienie

  • To jest , więc wygrywa najkrótsza poprawna implementacja.
  • Dane wejściowe / wyjściowe mogą być na dowolnym nośniku, który pasuje do twojego języka docelowego (np. Plik, stdin / stdout, pole tekstowe).
  • Jeśli Twój język docelowy nie obsługuje wprowadzania wielu wierszy (lub jest to niewygodne), możesz zamiast tego ograniczyć każdy wiersz wpisu przecinkami ( ,).
  • Możesz pominąć wyświetlanie jakichkolwiek pustych linii w siatce.
  • Pamiętaj, że tetromino spada z góry - nie możesz umieszczać elementu „pod ziemią”. Możesz zatem założyć, że wszystkie możliwe położenia elementu będą na „poziomie powierzchni” (tzn. Nie będzie żadnych bloków między kawałkiem a górną częścią planszy).
  • Załóżmy, że nigdy nie wystąpi sytuacja, w której zostaniesz zmuszony do zakończenia gry (umieszczone tetromino dotyka górnej środkowej części pola).
  • Rozwiązania, które mają identyczne wyjście, należy pominąć (np. Istnieją 3 wyjścia, jeśli naiwnie obracasz Oelement).

1 Wiem, że spowoduje to powstanie fałszywych trafień, ale jest to uproszczenie.

2 To jest rozmiar siatki używany w wersji Game Boy.

3 Tak, 0Hjest poprawne. Sprawdź jeszcze raz, powiedziałem nowe dziury; ^)

Sean Latham
źródło
Co jeśli kawałek spowoduje 1 nowy dołek, ale uzyska 2 linie, a tylko 1 linię?
Nathan Merrill
Najpierw posortuj według liczby wierszy. 2 linie> 1 linia, więc wygrywa bez względu na liczbę otworów.
Sean Latham
2
Jeśli uwolnisz dziurę, czy to da ci -1H?
overactor
Hm, nie myślałem o tym - może to wynikać z naiwnej definicji dziury. Tak, chyba tak.
Sean Latham
W moim rozwiązaniu nie rozważałem uwolnienia dziur. Zrozumiałem definicję problemu w taki sposób, że istniejące bloki nie powinny być modyfikowane. Dlatego nie należy usuwać kompletnych linii i nie zwalniać otworów.
mikail sheikh

Odpowiedzi:

3

C 1009 bajtów

#include <stdio.h>
#define L for(n=0;n<18;++n)
int T[19]={54,561,99,306,785,23,547,116,802,71,275,116,39,562,114,305,51,4369,15},W[19]={3,2,3,2,2,3,2,3,2,3,2,3,3,2,3,2,2,1,4},O[7]={0,2,4,8,12,16,17},R[7]={2,2,4,4,4,1,2},G[18],F[24],t=0,I,x,y,S[99],X[99],Y[99],N[99],s,M=0,i,j,k,l,m,n,h,c;char B[99],*C="SZLJTOI";void A(){for(m=0;m<24;++m)F[m]=0;for(m=0;m<4;++m)F[y+m]=(T[I]>>(m*4)&15)<<x;}void D(){S[s]=0;L S[s]+=(G[n]|F[n])==1023;S[s]=200*(S[s])+199;for(m=0;m<10;++m){l=0;L{h=(G[n]|F[n])&1<<m;l|=h;S[s]-=l&&!h;}}}int E(){A();c=0;L c|=G[n]&F[n];return !c;}int main(){S[0]=0;gets(B);while(C[t]!=B[0])++t;I=O[t];L{G[n]=0;gets(B);for(m=0;m<10;++m)G[n]|=(B[m]=='+')?(1<<m):0;}s=0;D();for(i=0;i<R[t];++i){for(x=0;x<10-W[I];x++){y=0;while(E())y++;--y;++s;A();D();if(S[s]>M)M=S[s];X[s]=x;Y[s]=y;N[s]=I;}I++;}for(i=1;i<s;++i)if(S[i]==M){j=i;x=X[i];y=Y[i];I=N[i];A();for(n=1;n<18;++n){for(m=0;m<10;++m)printf((G[n]&1<<m)!=0?"+":((F[n]&1<<m)!=0?"#":" "));printf("\n");}}printf("%dL %dH\n",S[j]/200,S[0]%200-S[j]%200);}

Oto wersja bez golfa

#include <stdio.h>

int tiles[19] = {54,561,99,306,785,23,547,116,802,71,275,116,39,562,114,305,51,4369,15};
int widths[19] = {3,2,3,2,2,3,2,3,2,3,2,3,3,2,3,2,2,1,4};
char *codes = "SZLJTOI";
int offsets[7] = {0,2,4,8,12,16,17};
int transformations[7] = {2,2,4,4,4,1,2};
int gameField[18], tileField[24];

int i,j,k,l,m,n,h;
char buffer[99];
int tile=0, tileIndex;
int xpos, ypos;
int scores[99], xplacements[99], yplacements[99], tindex[99];
int scoreIndex, maxScore=0;

void readGame()
{
  gets(buffer);
  while (codes[tile]!=buffer[0]) ++tile;
  tileIndex = offsets[tile];
  for (n=0;n<18;++n)
  {
    gameField[n] = 0;
    gets(buffer);
    for (m=0; m<10;++m)
      gameField[n] |= (buffer[m]=='+')?(1<<m):0;
  }
}

void writeGame()
{
  for (n=1;n<18;++n)
  {
    for (m=0; m<10;++m)
      printf( (tileField[n] & 1<<m) != 0 ? "#" :((gameField[n] & 1<<m) != 0 ? "+" :" "));
    printf("\n");
  }
}

void placeTile()
{
  for (m=0;m<24;++m) tileField[m] = 0;
  for (m=0;m<4;++m) 
    tileField[ypos+m] = (tiles[tileIndex]>>(m*4) & 15) << xpos;
}

void score()
{
  scores[scoreIndex] = 0;
  for (n=0;n<18;++n) 
    if ((gameField[n] | tileField[n])==1023) scores[scoreIndex]++;

  scores[scoreIndex] = 200*(scores[scoreIndex]) + 199;

  for (m=0;m<10;++m)
  {
    l=0;
    for (n=0;n<18;++n)
    {
      h = (gameField[n] | tileField[n]) & 1<<m;
      l |= h;
      scores[scoreIndex] -= l && !h;
    }
  }
}

int noCollision()
{
  placeTile();
  int coll = 0;
  for (n=0;n<18;++n) coll |= gameField[n] & tileField[n];
  return !coll;
}

int main()
{ scores[0] = 0;
  readGame();
  scoreIndex = 0;
  score();
  for (i=0; i<transformations[tile]; ++i)
  {
    for (xpos=0; xpos<10-widths[tileIndex]; xpos++)
    {
      ypos = 0;
      while (noCollision()) ypos++;
      --ypos;
      ++scoreIndex;
      placeTile();
      score();
      if (scores[scoreIndex]>maxScore) maxScore=scores[scoreIndex];
      xplacements[scoreIndex] = xpos;
      yplacements[scoreIndex] = ypos;
      tindex[scoreIndex] = tileIndex;
    }
    tileIndex++;
  }

  for (i=1;i<scoreIndex; ++i) 
    if (scores[i]==maxScore)
    {
      j=i;
      xpos = xplacements[i];
      ypos = yplacements[i];
      tileIndex = tindex[i];
      placeTile();
      writeGame();
    }

  printf("%dL %dH\n", scores[j]/200, scores[0]%200-scores[j]%200);
}

Widziałem, że głównym źródłem długiego kodu prawdopodobnie będzie definicja kafelków. Postanowiłem więc przedstawić je jako wzory bitowe w tablicy 4x4. Powoduje to 16 bitów, które łatwo pasują do jednego int. tilesTablica posiada wszystkie wzory na 19 możliwych obrotów 7 płytek.

Podczas kompilacji zignoruj getsprzestarzałe ostrzeżenie . Wiem, że tak, ale jest to najkrótszy sposób odczytu linii z wejścia.

Mikail Sheikh
źródło
W skali globalnej możesz upuścić intzgodnie z założeniami. Kilku z twoich printfsjedynych produkuje pojedynczy znak. Być może będziesz w stanie zastąpić je ekwiwalentem, putcharaby zapisać kilka znaków. Na przykład zmiana printf("\n")na putchar(10):)
Josh
puts („”) jest jeszcze krótszy, jeśli chcesz tylko nowej linii. Możesz także użyć return! C (bez spacji). Przy pierwszym użyciu indeksu pętli for można pominąć inicjalizację na 0, ponieważ zmienne są deklarowane globalnie. Myślę też, że używasz funkcji E tylko raz, więc powinno być możliwe, aby po prostu ją wstawić.
Alchymist