Programowanie bloków Tetris (dosłownie)

33

W grze Tetris istnieje 7 rodzajów klocków lub Tetr i minoes , które są matematycznie znane jako tetr o minoes, ponieważ wszystkie są wykonane z 4 kwadratowych segmentów:

Cegły Tetris

Posiadają nazwy I, J, L, O, S, T i Z, które odpowiadają ich przybliżonym kształtom. Przy obrotach 90 ° istnieje łącznie 19 unikalnych kształtów:

I
I
I
I

IIII

 J
 J
JJ

JJJ
  J

JJ
J
J

J
JJJ

L
L
LL

  L
LLL

LL
 L
 L

LLL
L

OO
OO

 SS
SS

S
SS
 S

TTT
 T

T
TT
T

 T
TTT

 T
TT
 T

ZZ
 ZZ

 Z
ZZ
Z

Wyzwanie

Napisz prostokątny blok kodu, który działa jak segment podstawowy, z którego wykonano 19 kształtów. Kiedy ten kod jest ułożony w jeden z kształtów, należy utworzyć program, który wyprowadza pojedynczą wielką literę związaną z tym kształtem. To musi działać dla wszystkich 19 kształtów.

Wiodące puste obszary obecne w niektórych z 19 kształtów są całkowicie wypełnione spacjami ( ). Końcowe puste obszary nie są niczym wypełnione (więc programy nie zawsze są dokładnie prostokątne).

Przykład

Załóżmy, że to był twój blok kodu:

ABC
123

Wówczas każde ustawienie bloku w kawałku S Tetris byłoby programem, który wypisuje S:

   ABCABC
   123123
ABCABC
123123

ABC
123
ABCABC
123123
   ABC
   123

(Zauważ, że wszystkie wiodące puste miejsca są wypełnione znakami spacji i że żadne wiersze nie mają spacji końcowych).

Ten sam pomysł dotyczy wszystkich 6 innych elementów i ich odpowiednich obrotów.

Notatki

  • Wszystkie 19 programów końcowych ma być uruchomionych w tym samym języku programowania.
  • W razie potrzeby możesz dodać jeden końcowy znak nowej linii do wszystkich programów (nie tylko niektóre, wszystkie lub żadne).
  • Blok kodu może zawierać dowolne znaki (w tym spacje), które nie są zakończeniami linii .
  • Wypisz literę na standardowe wyjście (lub najbliższą alternatywę twojego języka) z opcjonalnym końcowym znakiem nowej linii.

Punktacja

Zgłoszenie, którego blok kodu ma najmniejszy obszar (szerokość razy wysokość) wygrywa. Zasadniczo oznacza to, że wygrywa najkrótszy kod i dlatego jest oznaczony . Tiebreaker przechodzi do najlepiej głosowanej odpowiedzi.

ABC\n123Przykład ma powierzchnię 3 x 2 = 6.

Skrawek

Biorąc pod uwagę blok kodu, ten fragment kodu wygeneruje wszystkie 19 programów:

<script>function drawShape(X,n,v){for(var t="",e=0;e<v.length;e++)for(var l=0;l<n.length;l++){for(var r=0;r<v[e].length;r++)t+="X"===v[e][r]?n[l]:X[l];t+="\n"}return t}function go(){var X=document.getElementById("input").value;if(0!=X.length){var n=X.replace(/./g," ").split("\n");X=X.split("\n");for(var v="I (v1):|I (v2):|J (v1):|J (v2):|J (v3):|J (v4):|L (v1):|L (v2):|L (v3):|L (v4):|O:|S (v1):|S (v2):|T (v1):|T (v2):|T (v3):|T (v4):|Z (v1):|Z (v2):".split("|"),t="X\nX\nX\nX|XXXX| X\n X\nXX|XXX\n  X|XX\nX\nX|X\nXXX|X\nX\nXX|  X\nXXX|XX\n X\n X|XXX\nX|XX\nXX| XX\nXX|X\nXX\n X|XXX\n X|X\nXX\nX| X\nXXX| X\nXX\n X|XX\n XX| X\nXX\nX".split("|"),e="",l=0;l<v.length;l++)e+=v[l]+"\n\n"+drawShape(n,X,t[l].split("\n"))+"\n";e=e.substring(0,e.length-2),document.getElementById("output").value=e}}</script><style>html *{font-family: monospace;}</style>Code Block:<br><textarea id='input' rows='8' cols='64'>ABC&#010;123</textarea><br><button type='button' onclick='go()'>Go</button><br><br>All 19 Programs:<br><textarea id='output' rows='24' cols='64'></textarea>

Hobby Calvina
źródło
Więc stosunek długości do szerokości wynosi 2 do 3? Czy może być innego rozmiaru? Co jeszcze musi zrobić program? Zakładając, że puste programy się nie liczą, ale programy, które nic nie wypisują.
ASCIIThenANSI
@ASCIIThenANSI Każda szerokość i wysokość są w porządku. Wyobrażam sobie, że coś większego niż 2 * 3 będzie konieczne. Istnieje 19 programów, po jednym dla każdego układu bloku w jednym z 19 różnych kształtów tetromino. Kiedy jeden z tych programów jest uruchomiony, wyświetla odpowiednią literę kawałka tetris.
Calvin's Hobbies
Łał! Co za niesamowite wyzwanie! Czy ma znaczenie, jakiego języka używamy?
theonlygusti
@ thehelylygusti Prawie wszystkie pytania na tej stronie pozwalają na dowolny język. To nie jest wyjątek.
Calvin's Hobbies
@ Calvin'sHobbies Tak, wiem; Właśnie błędnie zinterpretowałem fragment kodu jako kontroler do uruchamiania odpowiedzi JavaScript. Najwyraźniej po prostu porządkuje bloki kodu.
theonlygusti

Odpowiedzi:

16

<> <(Ryby) - 12 * 32 = 384

Planowałem wybrać bardziej eleganckie rozwiązanie, ale jakoś skończyłem z tym, co jest dość brutalną siłą:

c  0  g84*%\
c2*0  g84*%\
0  84*g84*%\
c  84*g84*%\
c2*84*g84*%\
0  88*g84*%\
c  88*g84*%\
?v         \
;>?v~~?vv   
"L" o;  >   
"S" o; >~?v 
"T" o;    > 
;  >~?v"L"o 
;     >"J"o 
?v         \
 >~?v~~?vv  
"I"  o;  >  
"J"  o; >   
    \~~?vv  
"T"  o;  >  
"Z"  o; >   
?v         \
 >?v"J"o;   
   >?v"Z"o; 
"L"o;>?!v   
"J"o;   >?v 
"T"o;     > 
?v?v"I"o;  >
   >"L"o;   
 >?v"T"o;   
   >?v"O"o; 
     >"S"o; 

Jest to dość proste, sprawdza kod w kwadracie 3x3 pod kątem tekstu i wykorzystuje wyniki, aby zobaczyć, który tetrimino odpowiada kształtowi kodu. Jeszcze nie starałem się grać w golfa.

Wypróbuj kod tutaj (po użyciu fragmentu w celu ukształtowania go jak tetrimino)

Przykład kodu w kształcie Z (v1) tutaj

Thijs ter Haar
źródło
14

C (gcc) , 26x20 = 520 25x19 = 475 23x17 = 391

#ifndef M            //
#define M(a,b)a##b   //
#define W(z,x)M(z,x) //
char*s,*S[]={"!!!!c",//
"8M !7! M8 878","77",//
"7!!MO887","788OM!!7"//
,"N7!78","7N87!"},r[5//
],*p=r;i=7;main(){for//
(;i--;)strstr(S[i],r)//
&&putchar("ITOJLSZ"[i//
]);}                 //
#endif               //
__attribute__((      //
constructor(__LINE__)//
))W(f,__LINE__)(){s= //
"                     \
";*p++=strlen(s)+12;}//

Niedawno zostałem poinformowany o atrybutach funkcji GNU, a co najciekawsze, o constructoratrybucie, który pozwala na bardziej zwięzłą implementację tego, co robiłem w bardziej okrągły sposób w moim wcześniejszym podejściu do tego problemu.

Idea tego samego pomysłu jest taka sama jak poprzednio: Zbuduj ciąg i wyszukaj go na liście, aby określić, który blok tetris jest ułożony jako kod. Odbywa się to przez wywołanie funkcji, z których każda dodaje znak do łańcucha. Powikłaniem było i pozostaje, że liczba funkcji jest różna.

Zdefiniowanie funkcji za pomocą attribute((constructor(x)))powoduje, że funkcja jest uruchamiana przed main()wprowadzeniem, z opcjonalnym xpriorytetem (niższy oznacza, że ​​jest uruchamiany wcześniej). Eliminuje to potrzebę używania wskaźników funkcji, co pozwala nam upuszczać makro, niektóre deklaracje i łańcuch wywołań.

Użycie __LINE__priorytetu jest niepewne, ponieważ poziomy priorytetu 0–100 są zarezerwowane. Nie powoduje to jednak błędów, a jedynie ostrzeżenia, które są obfite podczas gry w golfa, więc co jeszcze?

Pomogłoby to ogolić kolejną kolumnę, aby w ogóle nie używać priorytetów, ale kolejność wykonywania nie wydaje się być określona. (W tym przypadku są one odwrócone, ale inne testy są niejednoznaczne).

Przykład L v2 tutaj

Starsze, bardziej przenośne podejście

#ifndef M              //
#define M(a,b) a##b    //
#define W(z,x)M(z,x)   //
#define F W(f,__LINE__)//
#define A W(a,__LINE__)//
char r[5],*S[]={"####k"//
,";<<US##;",";##SU<<;",//
";;",";T<;#","<S #;# S"//
"< <;<","T;#;<"},*s,*p=//
r;i;typedef(*T)();T a17//
,a36,a55,a74;main(){for//
(a17(),a36&&a36(),a55&&//
a55(),a74&&a74();i--;) //
strstr(S[i],r)&&putchar//
("ILJOZTS"[i]);}i=7;   //
#endif                 //
F();T A=F;F(){s=       //
"                       \
";*p++=strlen(s)+12;}  //

Jeden z moich ulubionych problemów, które rozwiązałem na tej stronie.

Zacząłem od stwierdzenia, że ​​każdy blok w jakiś sposób wyznaczy własne współrzędne. Rzędy są łatwe __LINE__, a liczbę sąsiadujących poziomo bloków można znaleźć, używając długości literału łańcuchowego, tak:

char*s=//char*s=//
"       ""       "
;        ;        

Weź długość wynikowego łańcucha i podziel przez odpowiednią liczbę, a uzyskasz szerokość. Niestety ta pusta przestrzeń przed blokiem jest niewidoczna. Wciąż podejrzewa ciągi byłoby rozwiązanie, ponieważ odstępy tylko ma sens poza strun bardzo rzadko, w rzeczy jak a+++bvs. a+ ++b. Przez chwilę zastanawiałem się nad czymś takim, ale nie mogłem znaleźć niczego przydatnego. Inną możliwością byłoby umożliwienie „sklejenia” identyfikatorów w miejscu, w którym spotykają się bloki:

A  BA  B

Nie zdziwiłbym się, gdyby to wciąż mogło stanowić ciekawe rozwiązanie.

Pomimo swojej prostoty zajęło mi sporo czasu znalezienie rozwiązania łańcuchowego, które opiera się na tym fragmencie bloku:

s=//
"  \
";//

Jeśli fragment nie ma poziomych sąsiadów, nowa linia w drugiej linii jest poprzedzana ukośnikiem odwrotnym, tworząc ciąg długości 2. Jeśli jednak ma sąsiad, odwrotny ukośnik zamiast tego unika znaku cudzysłowu na początku linii 2 następnego bloku:

s=//s=//
"  \"  \
";//";//

Spowoduje to utworzenie ciągu „\” „o długości 5.

Co ważniejsze, pozwala to również na wykrycie pustej przestrzeni przed blokiem:

    s=//
    "  \
    ";//

Znów znak nowej linii jest zastępowany, a spacja pustego bloku po lewej stronie jest zawarta w wynikowym ciągu „” o długości 6.

W sumie istnieje siedem różnych konfiguracji bloków w rzędzie, o które musimy się martwić, i wszystkie tworzą ciągi o unikalnej długości:

2 "  "
---
s=//
"  \
";//

5 "  \"  "
---
s=//s=//
"  \"  \
";//";//

6 "      "
---
    s=//
    "  \
    ";//

9 "  \"      "
----
    s=//s=//
    "  \"  \
    ";//";//

10 "          "
---
        s=//
        "  \
        ";//

8 "  \"  \"  "
---
s=//s=//s=//
"  \"  \"  \
";//";//";//

11 "  \"  \"  \"  "
----
s=//s=//s=//s=//
"  \"  \"  \"  \
";//";//";//";//

Ostateczne bloki oczywiście nie będą miały tak krótkiej długości, ale zasada jest taka sama niezależnie od wielkości bloku. Ma to również tę zaletę, że oddzielny mechanizm wykrywania szerokości jest niepotrzebny. Dodając znak odpowiadający długości tego ciągu do ciągu wyników, każda z 19 konfiguracji daje unikalny ciąg, który wystarczy porównać z odpowiednią listą po uruchomieniu wszystkich bloków.

Po posortowaniu, kolejnym dużym problemem było to, jak „odwiedzić” każdy rząd bloków. W C jesteśmy bardzo ograniczeni do tego, co można zrobić poza funkcjami. Musimy się także main()pojawić, ale tylko raz. To drugie jest łatwo osiągalne przez niektóre #defines, ale jeśli chcemy, aby kod kolejnych bloków był w środku main(), problem, jak się dowiedzieć, kiedy umieścić końcowy nawias zamykający. W końcu nie wiemy, ile wierszy bloków faktycznie zostanie wykorzystanych. Musimy więc mieć main()statyczne, a reszta być dynamiczna.

Jeśli pozostałe wiersze bloków mają być samowystarczalne, muszą to być funkcje, ale musimy upewnić się, że każda funkcja ma unikalną nazwę, a jednocześnie jest wystarczająco przewidywalna, aby można ją było wywoływać main(). Potrzebujemy również mechanizmu rozpoznawania, które funkcje są rzeczywiście wywoływane. Generowanie unikalnych nazw rozwiązują makra pomocnicze:

#define M(a,b) a##b     //
#define W(z,x)M(z,x)    //
#define F W(f,__LINE__) //
#define A W(a,__LINE__) //

Wywołanie Fspowoduje utworzenie identyfikatora, którego nazwa zaczyna się na literę f, a kończy na numerze linii. Arobi to samo, ale z prefiksem as, który jest używany dla drugiej części rozwiązania, czyli wskaźników funkcji. Deklarujemy cztery takie wskaźniki:

typedef(*T)();T a17,a36,a55,a74;

Ponieważ są one zadeklarowane jako zmienne globalne, wygodnie są ustawione na NULL. Później każdy wiersz bloku będzie zawierał następujący fragment kodu:

F();T A=F;F()

To najpierw zadeklaruje funkcję, zdefiniuje odpowiedni wskaźnik funkcji wskazujący na tę funkcję (możemy zdefiniować globały tylko raz, ale wcześniejsza deklaracja nie liczyła się jako definicja, nawet jeśli zainicjowała wartość NULL), a następnie zdefiniowała rzeczywistą funkcjonować. Pozwala main()to wywołać dowolny wskaźnik funkcji, który nie jest NULL (a17 nigdy nie będzie NULL):

a17(),a36&&a36(),a55&&a55(),a74&&a74()

Spowoduje to zbudowanie ciągu znaków r, który jest następnie szukany w tabeli ciągów i jeśli zostanie znaleziony, wyświetlana jest odpowiednia litera.

Jedyną sztuczką jest to, że lista pasujących ciągów znaków została skrócona, ilekroć można było uniknąć dwuznaczności, lub nakładających się łańcuchów można połączyć.

Przykład L v2 tutaj

gastropner
źródło
6

x86 opcode (.com), 86 82 bajtów

Próbnik:

org 100h
macro e {
db $F6,$04,$DF,$78,$13,$75,$08,$00,$C0,$40,$83,$C6,$52,$EB,$F1,$88
db $C2,$00,$D0,$00,$D0,$46,$EB,$E8,$05,$02,$40,$73,$ED,$E8,$26,$00
db $50,$08,$43,$4D,$2C,$0C,$1C,$15,$A5,$14,$10,$13,$3F,$27,$20,$0F
db $51,$1D,$29,$49,$49,$4A,$4A,$4A,$4A,$4C,$4C,$4C,$4C,$4F,$53,$53
db $54,$54,$54,$54,$5A,$5A,$5F,$AE,$75,$FD,$8A,$55,$12,$B4,$02,$CD
db $21,$C3
}

macro n { db 82 dup $20 }

macro s { db 10 }

n
e
s
n
e
s
e
e  

Źródło:

BOF:    ;mov bx, 100h
p:      test [si], byte $DF
        js _a ; exist
        jnz _b ; newline
_z:     add al, al
        inc ax
q:      add si, EOF-BOF
        jmp p
_b:     mov dl, al
        add al, dl
        add al, dl
        inc si
        jmp p
_a:     add ax, 4002h
        jnc q
        call y
        db 80,8,67,77,44,12,28,21,165,20,16,19,63,39,32,15,81,29,41
        db 'IIJJJJLLLLOSSTTTTZZ'
y:      pop di
        scasb
        jnz y+1
        mov dl,[di+18]
        mov ah,2
        int $21
        ret
EOF:

Uruchom w win7dos, gdzie init AX = 0, SI = 100, BX = 0 Referencje

l4m2
źródło
Jeśli nie masz nic przeciwko zmniejszeniu liczby obsługiwanych środowisk, możesz założyć SI = 100h i użyć tego rejestru zamiast BX do indeksowania, aby zaoszczędzić 3 bajty przez upuszczenie mov bx, 100hna początku.
gastropner
@gastropner Wykonano i naprawiono punkt, w którym nie zauważyłem
l4m2