Animowana scena śniegowa ASCII

22

Napisz najkrótszy program, aby przekształcić dowolną sztukę ASCII w animowaną scenę śnieżną, która zaczyna się formować z opadającego śniegu ( przykład niezakończonego golfa JavaScript ostatnio zaktualizowany 2011-12-19).

Specyfikacja wejściowa : Twój program musi akceptować dowolne kombinacje spacji, gwiazdek i znaków nowej linii. Dane wejściowe będą zawierać maksymalnie 23 wiersze i 80 znaków w wierszu. Nie będzie pustych linii, ale linie mogą składać się tylko z białych znaków. Pojedyncza nowa linia zostanie dołączona i należy ją zignorować.

Dane wyjściowe : wyprowadzaj znaki ASCII (spacje, gwiazdki) i kody sterujące (znaki powrotu karetki, znaki linii, kody specjalne ANSI itp.) Dla konsoli tekstowej systemu operacyjnego lub emulatora terminala, dopóki użytkownik ręcznie nie zakończy programu. Możesz założyć, że okno terminala ma 80x24 znaków, jeśli twój system operacyjny pozwala na to ustawienie.

Zasady :

  • Animacja musi być płynna i szybka (preferowane 15 fps).
  • Gęstość śniegu musi wynosić od 5% do 15%.
  • Nie więcej niż jeden ekran śniegu może przewijać się na sekundę. (Oznacza to, że w ciągu jednej sekundy można dodać nie więcej niż 24 linie nowego śniegu).
  • Śnieg nie może wykazywać żadnego widocznego wzoru, gdy wchodzi do górnej części ekranu; musi wyglądać losowo.
  • Program musi jak najszybciej wypełnić wszystkie rzędy ekranu śniegiem; wstępne wypełnienie poszczególnych wierszy ekranu nie może być oczywiste dla widza.
  • Lewy dolny róg wejściowej grafiki ASCII musi znajdować się w lewym dolnym rogu ekranu (rysunek 1 dla dalszego wyjaśnienia).
  • Obszar wewnątrz lub pod grafiką ASCII nie może być na stałe wypełniony gwiazdkami. Jednak gwiazdki mogą (ale nie są wymagane) przewijać ten obszar.
  • Śnieg nie może gromadzić się na dole ekranu lub na istniejącym śniegu, z wyjątkiem przypadków pokazanych na wejściu.
  • Dolne spacje muszą być wypełnione przed górnymi, ponieważ wypełnianie spacji w odwrotnej kolejności powoduje, że animacja choinki wygląda zupełnie inaczej niż wynik mojego oryginalnego kodu. (dodano 2011-12-20)

Wesołych świąt!

Rysunek 1: oznaczone obszary ekranu 80x24

---------------------------New snow added on this line--------------------------
                                                                             |
                                                                             |
----------------------------------------------------------+                  |
                                                    ****  |                  |
    Snow MUST fall  Snow MAY fall ---------------->  **** |                  |
    through this    through these          ****      **** |  Snow MUST fall  |
    area.           areas of a              ****     **** |  through this    |
                    completed   \--------->  ****     ****|  area.           |
        ASCII art   scene.    \     ***        ****   ****|                  |
          area         \       \   *******      ****  ****|                  |
                        \       \    ********     ***  ***|  (ALL CAPS terms |
      (located in        \       \-->   *********  ***    |  have standard   |
       lower left         \     *******     ******  MAY   |     RFC 2119     |
       corner of           \    *************  **   fall  |    meanings.)    |
       screen)              \        ***********    here  |                  |
                         *** +--->          ****  ***     |                  |
                         *** | ****************   ***     |                  |
  | Snow MUST fall       *** | ****************   ***     |                  |
  | through this         *** +--->                ***     |                  |
  | area.                *** | ****************   ***     |                  |
--+---------------------+*** +--->                ***+----+------------------+--
  |   Snow MUST NOT     |****************************|      Snow MUST NOT    |
  V  accumulate here.   |****************************|     accumulate here.  V

Przykładowe dane wejściowe

Kod Banner Golf

 ******   *******  ********  ********     ******    *******  **       ******** 
**    ** **     ** **     ** **          **    **  **     ** **       **       
**       **     ** **     ** **          **        **     ** **       **       
**       **     ** **     ** ******      **   **** **     ** **       ******   
**       **     ** **     ** **          **    **  **     ** **       **       
**    ** **     ** **     ** **          **    **  **     ** **       **       
 ******   *******  ********  ********     ******    *******  ******** **       

Logo przepełnienia stosu

                                                    ****
                                                     ****
                                           ****      ****
                                            ****     ****
                                             ****     ****
                                    ***        ****   ****
                                   *******      ****  ****
                                     ********     ***  ***
                                        *********  ***
                                *******     ******
                                *************  **
                                     ***********
                         ***                ****  ***
                         ***   ****************   ***
                         ***   ****************   ***
                         ***                      ***
                         ***   ****************   ***
                         ***                      ***
                         ****************************
                         ****************************

Choinki

                                        *
                                       ***                           *
                *                     *****                         ***
               ***                   *******           *           *****
              *****                 *********         ***            *
                *                  ***********       *****
                       *          *************     *******
        *             ***        ***************       *               *
       ***           *****      *****************                     ***
      *****         *******    *******************                   *****
     *******           *      *********************                 *******
    *********                           *                          *********
        *                                                              *
Proszę wstać
źródło
1
Trzecia choinka jest zepsuta.
Bobby,
Niezłe wyzwanie! Myślę, że reguły powinny być wyliczone dla łatwiejszego odniesienia i nie rozumiem trzeciej i szóstej zasady ...
hallvabo
@hallvabo Wyjaśniłem te dwie zasady, te ostatnie poprzez dodanie oznaczonej liczby.
PleaseStand
Prośba o wyjaśnienie: czy nowa linia jest zawarta w maksymalnej długości linii wynoszącej 80 znaków, czy może to maksymalnie 80 znaków plus nowa linia? (Przyjąłem to drugie, ale wydaje się, że niektóre wnioski przyjęły to pierwsze.)
Ilmari Karonen,
@IlmariKaronen Ten ostatni.
PleaseStand

Odpowiedzi:

5

Perl, 196/239 znaków

chomp(@p=(@f=($"x80)x24,<>)[-24..-1]);{@s=(join("",map rand>.1?$":"*",1..80),@s);if(@s>23){$t=$f[$_],print$_?$/:"\e[H",($f[$_]|=$s[$_]&$p[$_])|($s[$_]&=~$t^$f[$_])for 0..23;select"","","",.1}redo}

To rozwiązanie różni się od twojego przykładu JS tym, że wzorzec jest wypełniany od góry do dołu, a nie od dołu do góry, ale zakładam, że jest OK, ponieważ nic nie powiedziałeś o tym w regułach.

Trywialną redukcję o 1 znak można uzyskać zastępując \edosłowny znak ESC, ale znacznie utrudnia to czytanie i edycję kodu.


Aktualizacja: I nie udało się wymyślić wersji, która wypełnia wzorzec z dołu do góry, a nie pozwala śnieg spadnie przez wypełnionymi częściami wzór, jak w przykładzie realizacji JS, kosztem 43 dodatkowych znaków:

chomp(@p=(@q=@f=($"x80)x24,<>)[-24..-1]);{@s=(join("",map rand>.1?$":"*",1..80),@s);if(@s>23){my$q;$q[-1-$_]=($q|=$p[-$_]&~$f[-$_])for@a=0..23;print$_?$/:"\e[H",($f[$_]|=$s[$_]&$p[$_]&~$q[$_])|($s[$_]&=~$f[$_])for@a;select"","","",.1}redo}

Zastąpienie ($s[$_]&=~$f[$_])po prostu $s[$_]uratuje 11 znaków, pozwalając padającemu śniegowi przejść przez wypełnione części wzoru (co odpowiada specyfikacji, ale nie przykładowej implementacji).


OK, ponieważ nadal wydaje mi się, że prowadzę wyścig po tygodniu, chyba powinienem wyjaśnić, jak działa moje rozwiązanie, aby zachęcić do większej konkurencji. (Uwaga: to wyjaśnienie dotyczy odgórnej wersji wypełniania o wielkości 196 znaków. Mogę ją zmienić, aby uwzględnić inną wersję później).

Po pierwsze, jedną wielką sztuczką, na której opiera się moje rozwiązanie, jest to, że ze względu na sposób ułożenia kodów znaków ASCII, 1-bit w kodzie ASCII dla spacji okazuje się być podzbiorem tych w kodzie dla gwiazdka.

Zatem prawdziwe są następujące wyrażenia: " " & "*" eq " "i " " | "*" eq "*". To pozwala mi używać bitowych operacji na łańcuchach do łączenia statycznych i ruchomych części sceny bez konieczności zapętlania poszczególnych znaków.

Tak więc, nie wspominając o tym, przejdźmy do kodu. Oto jego wersja bez golfa:

chomp(@p = (@f = ($" x 80) x 24, <ARGV>)[-24..-1]);
{
    @s = (join('', map((rand > 0.1 ? $" : '*'), 1..80)), @s);
    if (@s > 23) {
        foreach (0 .. 23) {
            $t = $f[$_];
            print( $_ ? $/ : "\e[H" );
            print( ($f[$_] |= $s[$_] & $p[$_]) | ($s[$_] &= ~$t ^ $f[$_]) );
        }
        select '', '', '', 0.1;
    }
    redo;
}

Pierwszy wiersz ustawia tablice @f(dla „ustalonego”) i @p(dla „wzoru”). @futworzy stałą część wyświetlacza i zacznie zawierać wyłącznie spacje, podczas gdy @p, który nie jest pokazany bezpośrednio, zawiera wzorzec wejściowy; w miarę postępu animacji dodamy do niej coraz więcej gwiazdek, @faż w końcu będzie wyglądać tak samo @p.

W szczególności @f = ($" x 80) x 23ustawia @fna 24 ciągi po 80 spacji. ( $"jest specjalną zmienną Perla, której domyślną wartością jest spacja.) Następnie bierzemy tę listę, dołączamy do niej wiersze wejściowe za pomocą operatora readline <>, bierzemy ostatnie 24 wiersze tej połączonej listy i przypisujemy ją @p: to jest kompaktowy sposób wypełniania @ppustymi liniami, aby wzór pojawiał się tam, gdzie powinien. Na koniec chompwprowadzamy linie wejściowe, @paby usunąć końcowe znaki nowej linii, aby nie powodowały problemów później.

Teraz spójrzmy na główną pętlę. Okazuje się, że {...;redo}jest to krótszy sposób na napisanie nieskończonej pętli niż, while(1){...}a nawet for(;;){...}, zwłaszcza jeśli wcześniej pominiemy średnik, redoponieważ natychmiast następuje on po ifbloku.

Pierwszy wiersz głównej pętli wprowadza tablicę @s(oczywiście dla „śniegu”), do której przygotowuje losowy ciąg 80 znaków zawierający 90% spacji i 10% gwiazdek przy każdej iteracji. (Aby zaoszczędzić kilka znaków, tak naprawdę nigdy nie usuwam dodatkowych wierszy poza końcem @stablicy, więc robi się to coraz dłuższe. W końcu zatrzyma to program, ponieważ tablica staje się zbyt długa, aby zmieścić się w pamięci, ale to zajmie to znacznie więcej czasu niż większość osób ogląda tę animację. Dodanie pop@s;instrukcji przed nią selectnaprawiłoby to kosztem siedmiu znaków.)

Reszta głównej pętli jest owinięta w ifblok, dzięki czemu działa tylko wtedy, gdy @stablica zawiera co najmniej 24 linie. Jest to prosty sposób na dostosowanie się do specyfikacji, która od samego początku wymaga wypełnienia całego ekranu spadającym śniegiem, a także nieco upraszcza operacje bitowe.

Następnie pojawia się foreachpętla, która w wersji golfowej jest tak naprawdę pojedynczą instrukcją z for 0..23modyfikatorem. Ponieważ zawartość pętli prawdopodobnie wymaga wyjaśnienia, rozpakuję ją nieco bardziej poniżej:

foreach (0 .. 23) {
    print $_ ? $/ : "\e[H";     # move cursor top left before first line, else print newline
    $t = $f[$_];                # save the previous fixed snowflakes
    $f[$_] |= $s[$_] & $p[$_];  # snowflakes that hit the pattern become fixed 
    $s[$_] &= ~$t ^ $f[$_];     # ...and are removed from the moving part
    print $f[$_] | $s[$_];      # print both moving and fixed snowflakes ORed together
}

Przede wszystkim $_jest domyślną zmienną licznika pętli w Perlu, chyba że zostanie określona inna zmienna. Tutaj biegnie od 0 do 23, tj. Ponad 24 liniami w ramce wyświetlacza. $foo[$_]oznacza element indeksowany $_w tablicy @foo.

W pierwszym wierszu pętli do gry w golfa wypisujemy albo nową linię (dogodnie uzyskaną ze $/specjalnej zmiennej), albo, gdy $_jest równa 0, ciąg znaków "\e[H", gdzie \eoznacza znak ESC. Jest to kod sterujący terminala ANSI, który przesuwa kursor do lewego górnego rogu ekranu. Technicznie rzecz biorąc, moglibyśmy pominąć ten fakt, gdybyśmy przyjęli określony rozmiar ekranu, ale zachowałem go w tej wersji, ponieważ oznacza to, że nie muszę zmieniać rozmiaru terminala, aby uruchomić animację.

W $t = $f[$_]linii po prostu zapisujemy bieżącą wartość $f[$_]zmiennej „tymczasowej” (stąd $t) przed potencjalną zmianą w następnej linii, gdzie $s[$_] & $p[$_]podaje przecięcie (bitowe AND) opadającego śniegu i wzorca wejściowego oraz |=operator OR to do stałej linii wyjściowej $f[$_].

W poniższym wierszu $t ^ $f[$_]podaje bitowy XOR poprzednich i bieżących wartości $f[$_], tj. Listę bitów, które zmieniliśmy w poprzednim wierszu, jeśli występują, i negując jeden z ciągów wejściowych z ~neguje wynik. Tak więc otrzymujemy maskę bitów ze wszystkimi bitami ustawionymi na 1 oprócz tych, które właśnie dodaliśmy do $f[$_]poprzedniej linii. ORAZ ta maska $s[$_]bitowa usuwa z niej te bity; w efekcie oznacza to, że gdy spadający płatek śniegu wypełnia otwór w ustalonym wzorze, jest usuwany z tablicy spadającego śniegu.

Na koniec print $f[$_] | $s[$_](który w wersji golfowej jest implementowany poprzez ORing dwóch poprzednich linii razem) po prostu drukuje połączenie (bitowe OR) stałych i ruchomych płatków śniegu na bieżącej linii.

Jeszcze jedna rzecz do wyjaśnienia to select '', '', '', 0.1poniżej wewnętrznej pętli. Jest to po prostu świetny sposób na spanie 0,1 sekundy w Perlu; z jakiegoś głupiego historycznego powodu standardowe sleeppolecenie Perla ma jednosekundową rozdzielczość, a importowanie lepszego sleepz Time::HiResmodułu zajmuje więcej znaków niż nadużywanie 4-argumentówselect .

Ilmari Karonen
źródło
Wydaje się, że występuje niewielki błąd: linia 24 nie jest używana, a dolna linia grafiki ASCII to linia 23. I naprawdę powinien wypełnić dolne przestrzenie, zanim wypełni górne, chociaż pierwotnie tego nie określałem.
PleaseStand
@PleaseStand: Zmieniłem kod, aby używać wszystkich 24 wierszy (a przy okazji pozbyłem się say), kosztem 2 dodatkowych znaków. Nie sądzę jednak, że mogę łatwo zmienić kolejność napełniania; moja implementacja jest z tym zasadniczo związana.
Ilmari Karonen,
Świetne wyjaśnienie! Chciałbym móc ponownie głosować za tym wpisem.
Dillon Cower
@PleaseStand: I rzeczywiście nie udało się zrobić wersję napełniania oddolnego, który teraz wygląda niemal tak samo jak np JS; jest nieco dłuższy niż z góry, ale nadal krótszy niż inne dotychczasowe wpisy.
Ilmari Karonen,
3

HTML i JavaScript, 436 znaków

Dodaj go do wejścia:

<body onload="for(a=[],b=[],c=document.body.firstChild,e=c[H='innerHTML'].split(N='\n'),f=e.length-1,g=24,h=g-f;f--;)for(X=80;X--;)b[80*(h+f)+X]='*'==e[f][X];for(setInterval(F='for(y=24;y--;)for(x=80;x--;)if(a[w=80*y+x]){d=1;if(b[w])for(d=0,z=y+1;24>z;++z)b[s=80*z+x]&&!a[s]&&(d=1);d&&(a[w]=0,a[w+80]=1)}for(x=80;x--;).1>Math.random(i=0)&&(a[x]=1);for(t=\'\';1920>i;++i)t+=\'* \'[+!a[i]],79==i%80&&(t+=N);c[H]=t',67);g--;)eval(F)"><pre>

Zobacz, jak działa dla każdego przykładu: kod golfa , logo Stack Overflow , choinki . Użytkownicy przeglądarki Internet Explorer muszą uruchomić wersję 9 i ustawić „Tryb dokumentu” na „standardy IE9” (przy użyciu narzędzi programistycznych F12), aby to przesłanie działało poprawnie.

Proszę wstać
źródło
1
Wszystkie 3 wydają się być zepsute? pasteall.org/pic/show.php?id=66297
CoDEmanX
1

Python, 299 znaków

Powinno to być zgodne z zasadami, zakładając, że nowa linia jest uwzględniona w limicie 80 znaków.

import random,sys,time
C=1920
v=_,x=' *'
a=['']*C
f=lambda n:[random.choice(e*9+x)for e in _*n]
for e in sys.stdin:a+="%-80s"%e
a=a[-C:]
s=f(C)
while 1:
 z=0;t=''
 for e in s:
    t+=v[x<a[z]or e>_]
    if(e>_<a[z])>(x in a[z+80::80]):a[z]='+'
    t+=z%80/79*'\n';z+=1
 print t;s=f(80)+s[:-80];time.sleep(.1)
hallvabo
źródło
Twój wynik jest zniekształcony, jeśli na wejściu znajdują się wiersze o długości dokładnie 80 znaków (plus nowa linia). Poprosiłem PleaseStand o wyjaśnienie, czy to w porządku.
Ilmari Karonen,
To dlatego, że dołączam nowy wiersz na końcu każdej linii o szerokości 80 znaków. Opis jest tu niejednoznaczny, ponieważ określa, że ​​należy wstawić znaki nowej linii, ale można również założyć, że terminal ma szerokość 80 znaków (w takim przypadku można pominąć znaki nowej linii i polegać na automatycznym zawijaniu).
hallvabo,
Myślę, że faktyczny problem polega na tym, że nie usuwasz znaków nowej linii przed uzupełnieniem linii wejściowych do 80 znaków, tak że wejście 80 znaków plus nowa linia kończy się na dodaniu 81 znaków, aa tym samym zakłóca indeksowanie. Właśnie próbowałem, i wygląda na to, zastępując %ez %e.rstrip()na linii 6 rozwiązuje problem. (Oczywiście może być krótsza poprawka; nie jestem dobry w golfie w Python.)
Ilmari Karonen
Jeśli chcesz obsługiwać 81 linii znaków, po prostu zmień liczby i wszystko będzie dobrze. Dopóki pozostanie poniżej 100 znaków w wierszu, nie zmieni to liczby
znaków
Jeśli zmienię twój kod, aby używał linii 81-znakowych, to nie będzie działał poprawnie na 80-kolumnowym terminalu, prawda? Wytwarzasz wynik 80-kolumnowy, po prostu nie akceptujesz poprawnie wprowadzania 80-kolumnowego . Wypróbuj: utwórz plik wejściowy z kilkoma liniami z 80 gwiazdkami na każdym i zobacz, co się stanie. To nie powinno być takie trudne: moje rozwiązanie radzi sobie z tym dobrze.
Ilmari Karonen,
0

Java, 625 znaków

import java.io.*;import java.util.*;class s extends TimerTask {int _c,_k;char _i[],_o[];boolean _b[];public s(String f) throws IOException {_i=new char[23*80];_o=new char[80];_b=new boolean [23*80];BufferedReader br = new BufferedReader(new FileReader(f));while (br.read(_i,_c++*80,80)!=-1);} public void run(){_k=--_k<0?_c:_k;for(int i=0;i<80;_b[_k*80+i]=Math.random()>0.9?true:false,i++);for(int m=0;m<_c;m++){for(int n=0;n<80;_o[n]=_b[(_k+m)%_c*80+n]?'*':_i[m*80+n],n++);System.out.println(_o);}}public static void main(String[] a) throws IOException{Timer timer=new Timer();timer.scheduleAtFixedRate(new s(a[0]),0,500);}}

Proste rozwiązanie w Javie.

SiZ
źródło
Fajnie, ale nie sądzę, że przestrzegasz specyfikacji. W szczególności od samego początku wyświetlasz cały wzór - nie „tworzy się on z padającego śniegu”, jak w przykładowej wersji demonstracyjnej. (Trzeba to wyjaśnić lepiej w pytaniu. Naprawdę musisz obejrzeć wersję demonstracyjną, aby zrozumieć, co powinno zrobić.) Ponadto szybkość klatek jest zbyt wolna, nie zaczynasz od wypełnionego śniegiem ekranu, wydaje się, że przyjmujesz format wejściowy inny niż podane przykłady i naprawdę powinieneś zresetować pozycję kursora między ramkami (drukowanie "\033[H"powinno to zrobić).
Ilmari Karonen,