Zestaw do budowy lochów

19

Jako dziecko grałem w grę Intellivision Advanced Dungeons and Dragons: Treasure of Tarmin . Trójwymiarowa grafika pozwala spojrzeć z perspektywy pierwszej osoby z szokującym realizmem:

Szokująco realistyczna grafika 3D

Ale potem dostałem C-64. I mogłem rysować na siatce znaków 40x25, kursorami po ekranie, ustawiając kolor za pomocą klawisza Ctrl i cyfry oraz umieszczając symbole gdziekolwiek chciałem (dlaczego nie bashmogę tego zrobić?) . Zestaw znaków miał komponenty trójkątne i bryłowe. Byłem więc w stanie zrozumieć, w jaki sposób można wygenerować rendering własnej perspektywy w siatce za pośrednictwem tego medium.

Znalazłem prawie trzy dekady specyfikacji, w oprawionym spiralnie papierze do notatników, na temat „Zestawu konstrukcyjnego lochów” w tym tygodniu:

wprowadź opis zdjęcia tutaj

( AKTUALIZACJA : Uważni czytelnicy zauważą, że to nie do końca trzyma się pochyłych części. Poprawione liczby podano poniżej.)

Chociaż Treasure of Tarmin grano na siatce, ściany istniały tylko na krawędziach kwadratowych pól. Po zapoznaniu się z bajtami zdałem sobie sprawę, że jeśli zrobię mapę z bajtów ... to każdy kwadrat na mapie może mieć cztery możliwe stany dla każdej krawędzi:

  1. Bez przeszkód
  2. Ściana
  3. Drzwi
  4. Coś innego?

Nigdy nie zabrałem się do pisania tego (do ostatniej nocy). Pomyślałem, że fajnie byłoby spróbować innych.

Twoim zadaniem jest zaimplementowanie renderera labiryntu opartego na trybie znakowym, który implementuje moją (poprawioną !!) specyfikację ... ale przy użyciu technologii z 2013 roku.

Wejście

Ponieważ specyfikacja nie definiuje renderowania dla drzwi, zakładamy, że jedynymi opcjami są ściany i ściany. Dla uproszczenia dane wejściowe to mapa złożona z linii ciągów, które wyglądają tak:

WN.. .N.. .N.. .N.. .N.E
W... .... .... ..S. ...E
W... .N.E W... .N.. ...E
W... .... .... .... ...E
W.S. ..S. ..S. ..S. ..SE

To byłaby mapa 5 x 5. Lewy górny róg (1,1) ma ustawiony West i Nprawą ścianę. Prawy dolny róg (5,5) ma Sustawiony outh i East wall.

Jest to znacznie mniej przyjemne bez nawigacji na mapie. Więc przynajmniej ustaw swojego gracza w pozycji (1,1) twarzą na północ i zaoferuj mu:

[F]orward, [B]ackward, turn [L]eft, turn [R]ight or [Q]uit?

Na każdym kroku wydrukuj obraz 16x15 z perspektywy pierwszej osoby, zgodnie ze specyfikacją papieru do notebooków. Aby nie musieć liczyć, rozmiar płaskich ścian w trzech odległościach wynosi:

14x13  (directly in front of you; e.g. wall is in same cell)
8x7    (one step away)
6x5    (two steps away)

Graniczne rozmiary skośnych ścian to:

1x15   (your direct left or right; e.g. wall is in same cell)
3x13   (one step away)
1x7    (two steps away)

Wyjaśnienia

  • Sąsiednie komórki mogą nie zgadzać się co do wspólnych ścian. Zatem południowa krawędź kwadratu może być ścianą, podczas gdy północna krawędź kwadratu na południe od niego będzie niezakłócona. W oryginalnym projekcie uznałem to za cechę: pozwala na ciekawe pomysły, takie jak drzwi jednokierunkowe ... lub niewidoczne ściany, które pojawiają się dopiero po ich przejściu. W celu uproszczenia postępuj zgodnie z tą samą zasadą: podczas nawigacji i renderowania zwracaj uwagę tylko na stan krawędzi w komórce najbliżej Ciebie w kierunku, w którym patrzysz .

  • Widok jest znacznie lepszy dzięki „cieniowaniu”. Tak więc dla pełnych bloków naprzemiennie używaj Unicode 2593 ▓ i 2591 ░ lub użyj Xi +jeśli twoją implementacją jest ASCII.

  • Trójkątne znaki Unicode (25E2 ◢, 25E3 ◣, 25E4 ◤, 25E5 ◥) są nieco kiepskie przy rysowaniu tego. Oprócz braku cieniowanych wariantów, często rozciągają tylko szerokość znaku, a nie pełną wysokość ... nawet czcionkami o stałej szerokości. Możesz narysować pełne bloki lub ukośne postacie lub coś innego w miejscach, w których chciałem mieć przekątne. Doceniamy ciekawe kreatywne rozwiązania, które wykorzystują kolor i wykorzystują te postacie zamiast cieniowania.

  • Możesz założyć, że skrajne ściany są ustawione tak, aby ograniczały obszar gry, więc nie musisz się martwić o renderowanie czegokolwiek poza labiryntem. Wszelkie ściany bardziej oddalone od ciebie są ignorowane i po prostu zostawiają puste miejsce.

  • Cieniowanie ściany, którą widzisz bezpośrednio przed sobą, jeśli skierowane jest na północ w punkcie (1,1), powinno być CIEMNE. Naprzemienne cieniowanie sąsiednich ścian na mapie, tak że gdyby wszystkie ściany były obecne, jasna ściana nigdy nie przylegałaby do ciemnej ściany.

  • Implementacja C-64, która faktycznie robi to, co pierwotnie zamierzałem ... z ukośnymi znakami i wszystkim ... przebije każde inne kryterium wejścia. :-)

Przykłady

Dla przykładowej mapy podanej powyżej ...

W punkcie (1,3) skierowanym na południe:

               /
              /+
             /X+
            /XX+
           /XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
           \XXX+
            \XX+
             \X+
              \+
               \

W punkcie (3,2) skierowanym na południe:

                      /* blank line */        
X             /
X            /+
X           /++
X           +++
X           +++
X           +++
X           +++
X           +++
X           +++
X           +++
X           \++
X            \+
X             \
                      /* blank line */

W punkcie (3,2) skierowanym na wschód:

                      /* blank line */        
              / 
             /X 
            /XX 
            XXX 
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
            XXX 
            \XX 
             \X 
              \ 
                      /* blank line */        

W punkcie (2,3) skierowanym na północ:

               /
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
X++++++++++++++X
X++++++++++++++X
X++++++++++++++X
X++++++++++++++X
X++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
               \
Dr. Rebmu
źródło
1
Sugeruję, aby uczynić z tego wyzwanie kodowe - golf byłby zbyt nieczytelny i trudny: P
Klamka
1
@Doorknob Nie daj się zwieść ... to wcale nie jest takie trudne. Jest całkiem dobra wskazówka z listami 3 ograniczających rozmiarów. A czym jest golf, ale wyzwanie, które zostało rozwiązane, a następnie zredukowane? :-) Ale pozwolę ludziom wybrać, jak chcą to rozwiązać ... NP
Dr. Rebmu
Czy mógłbyś wyjaśnić dwie kolumny Xw swoim widoku 3, 2skierowanym na południe?
jazzpi
Zwłaszcza ten po prawej stronie. Rozumiem, dlaczego tam jest lewy. Ale właściwy wydaje się naruszać Wyjaśnienie nr 1.
jazzpi
@jazzpi Ups, masz rację, mapa, którą wystawiłem, musi być zgodna z wyjaśnieniem 1! Dobra robota. Naprawiony. (W pewnym momencie umieściłem brakującą ścianę południową na mojej własnej wersji ... ale dobrze, żeby mieć próbkę w próbce ... więc zostawmy ścianę południową na zewnątrz!)
Dr. Rebmu

Odpowiedzi:

10

Commodore 64 Basic

Człowieku, było fajnie. I twarde. C64 Basic jest prawie niemożliwy do debugowania, nie można nawet użyć printdebugowania, ponieważ ekran jest już zajęty do renderowania lochu. Wiesz, że dobrze się bawisz, pisząc kod 55250 goto 55110. Dijkstra mnie zabije.

Program wykorzystuje dwa kolory i znaki ukośne.

Nie trzeba dodawać, że nie grałem w golfa. W końcu mówi teraz wyzwanie kodowe . Jeśli jesteś zainteresowany, to 7183 bajtów.

Jest powolny - przy domyślnej prędkości renderowanie sceny zajmuje kilka sekund. Maksymalny rozmiar mapy to 10 na 10, ale można to zmienić edytując linię 120.

Opracowałem i przetestowałem to za pomocą emulatora VICE . Poniższy kod jest wyświetlany w ASCII, więc oznacza to przesunięty PETSCII. Jednak podczas wprowadzania mapy należy korzystać z nieprzesuniętego PETSCII .

Zrzut ekranu: Zrzut ekranu

Kod:

10 rem c64 dungeon construction set.
20 rem enter using lowercase mode
99 rem DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
100 rem initialisation
110 poke 53272,21
115 poke 53280,0
120 dim m%(10,10)
121 dim di$(3),wa$(1),ma%(2,2)
122 di$(0)="north"
123 di$(1)="east "
124 di$(2)="south"
125 di$(3)="west "
126 wa$(1)="-wall"
127 wa$(0)="     "

130 x=0:y=0:di=0:xs=0:ys=0:wa=0
134 rem read map
135 print "input map"
140 l$="":input l$
150 if len(l$)=0 goto 250
160 cz=0
170 for i=1 to len(l$)
180   c$=mid$(l$,i,1)
190   if c$="n" then cz=cz or 8
200   if c$="e" then cz=cz or 4
205   if c$="s" then cz=cz or 2
210   if c$="w" then cz=cz or 1
215   if c$=" " then m%(x,y)=cz:cz=0:x=x+1
220   if x>=xs then xs=x
225 next
230 m%(x,y)=cz:x=0:y=y+1
240 goto 140
250 rem come from 150
260 print chr$(147)
265 ys=y:xs=xs+1
270 x=0:y=0

500 rem loop
510 gosub 1000: rem status
515 gosub 2000: rem render
520 gosub 55000: rem input
530 goto 500

1000 rem display current (x,y) value
1010 sx=5
1020 sy=17
1030 sl$="    "
1035 sw=14
1040 gosub 63900
1050 cz=m%(x,y)
1060 sx=5:sl$=".":if cz and 8 then sl$="n"
1065 gosub 63900
1070 sx=6:sl$=".":if cz and 4 then sl$="e"
1075 gosub 63900
1080 sx=7:sl$=".":if cz and 2 then sl$="s"
1085 gosub 63900
1090 sx=8:sl$=".":if cz and 1 then sl$="w"
1095 gosub 63900
1100 return

2000 rem render dungeon
2010 rem DDDDDDDDDDDDDD
2020 rem clear area
2030 sw=14:sz=32
2040 for sy=0 to 15
2050   for sx=0 to 16
2060      gosub 63950
2070   next
2080 next
2090 rem find cells / reorient sw
2100 rem store in ma% - we're at (0,1)
2110 sx=x:sy=y
2113 co=di+x+y and 1
2115 for ty=0 to 2
2120    gosub 59800:rem left/right sx/sy
2125    ma%(1,ty)=0
2126    if sx>=0 and sy>=0 and sx<xs and sy<ys then ma%(1,ty)=m%(sx,sy)
2130    ma%(0,ty)=rl
2140    ma%(2,ty)=rr
2150    gosub 59900:rem advance
2160 next
2170 rem draw back walls
2180 sa=ma%(1,2):gosub 59700
2190 if rf=0 goto 2245
2195 sw=14-11*co:sz=160
2200 for sy=5 to 9
2210    for sx=5 to 10
2220       gosub 63950
2230    next
2240 next
2245 sw=3:if co=1 then sw=14
2250 for de=0 to 2 step 2 
2260    sa=ma%(de,2):gosub 59700
2270    if rf=0 goto 2350
2280    for sx=de*5.5 to 4+de*5.5
2290       for sy=5 to 9
2300          gosub 63950
2310       next
2340    next 
2350 next
2360 rem 1,2 left wall
2370 sa=ma%(1,2):gosub 59700
2380 if rl=0 goto 2430
2390 sx=4:sz=160
2400 for sy=5 to 9:gosub 63950:next
2410 sy=4:sz=223:gosub 63950
2420 sy=10:sz=105:gosub 63950
2430 rem 1,2 right wall
2440 if rr=0 goto 2490
2450 sx=11:sz=160
2460 for sy=5 to 9:gosub 63950:next
2470 sy=4:sz=233:gosub 63950
2480 sy=10:sz=95:gosub 63950
2490 rem 1,1 back wall
2500 sa=ma%(1,1):gosub 59700
2510 sz=160
2520 sw=14:if co=1 then sw=3
2520 if rf=0 goto 2580
2530 for sy=4 to 10
2540    for sx=4 to 11
2550       gosub 63950
2560    next
2570 next
2580 rem (0-2),1 back walls
2590 sw=14:if co=1 then sw=3
2600 for de=0 to 2 step 2
2610    sa=ma%(de,1):gosub 59700
2620    if rf=0 goto 2680
2630    for sx=de*6 to 3+de*6
2640       for sy=4 to 10
2650          gosub 63950
2660       next
2670    next
2680 next 
2690 rem 1,1 left side wall
2700 sw=14:if co=1 then sw=3
2710 sa=ma%(1,1):gosub 59700
2720 if rl=0 goto 2760
2730 for sx=1 to 3
2735   sy=sx:sz=223:gosub 63950
2736   sy=14-sx:sz=105:gosub 63950
2737   sz=160
2740   for sy=1+sx to 13-sx:gosub 63950:next
2750 next
2760 rem 1,1 right side wall
2770 if rr=0 goto 2850
2780 for qx=1 to 3
2790   sx=15-qx
2800   sy=qx:sz=233:gosub 63950
2810   sy=14-qx:sz=95:gosub 63950
2820   sz=160
2830   for sy=1+qx to 13-qx:gosub 63950:next
2840 next
2850 rem 0,1 back wall
2860 sa=ma%(1,0):gosub 59700
2870 if rf=0 goto 2930
2880 for sy=1 to 13
2890   for sx=1 to 14
2900     gosub 63950
2910   next
2920 next
2930 rem (0,2)-0 back walls
2940 sw=3:if co=1 then sw=14
2950 for de=0 to 2 step 2
2960   sa=ma%(de,0):gosub 59700
2970   if rf=0 goto 3000
2980   sx=de*7.5
2990   for sy=1 to 13:gosub 63950:next
3000 next
3010 rem (1,0) left side wall
3020 sa=ma%(1,0):gosub 59700
3030 if rl=0 goto 3080
3040 sx=0:sy=0:sz=223:gosub 63950
3050 sy=14:sz=105:gosub 63950
3060 sz=160
3070 for sy=1 to 13:gosub 63950:next
3080 rem (1,0) right side wall
3085 if rr=0 goto 3130
3090 sx=15:sy=0:sz=233:gosub 63950
3100 sy=14:sz=95:gosub 63950
3110 sz=160
3120 for sy=1 to 13:gosub 63950:next
3130 rem done
3140 return

55000 rem ask for prompt & handle input
55010 sx=0:sy=20:gosub 63850
55013 print "at";x+1;y+1;"going ";di$(di);" size";xs;ys;wa$(wa)
55020 print "{f}rwd {b}kwd {l}eft {r}ight {q}uit"
55030 input c$
55040 if c$="q" goto 63999
55050 if c$="f" then dm=1:goto 55100
55060 if c$="b" then dm=-1:goto 55100
55070 if c$="l" then di=(di-1)and 3
55080 if c$="r" then di=(di+1)and 3
55090 return
55100 goto 55200:rem check walls
55110 if di=0 then y=y-dm
55120 if di=1 then x=x+dm
55130 if di=2 then y=y+dm
55140 if di=3 then x=x-dm
55145 wa=0
55146 if y>=ys then y=0
55147 if y<0   then y=ys-1
55148 if x>=xs then x=0
55149 if x<0   then x=xs-1
55150 return
55200 rem check walls
55205 cz=m%(x,y)
55207 if dm=-1 goto 55280
55210 if (di=0) and (cz and 8) goto 55260
55220 if (di=1) and (cz and 4) goto 55260
55230 if (di=2) and (cz and 2) goto 55260
55240 if (di=3) and (cz and 1) goto 55260
55250 goto 55110
55260 wa=1
55270 return : rem wall in the way
55280 rem backward
55290 if (di=2) and (cz and 8) goto 55260
55300 if (di=3) and (cz and 4) goto 55260
55310 if (di=0) and (cz and 2) goto 55260
55320 if (di=1) and (cz and 1) goto 55260
55330 goto 55110

59700 rem return front/back/left/right
59710 rem given sa and d
59720 sn=0:if sa and 8 then sn=1
59725 se=0:if sa and 4 then se=1
59730 ss=0:if sa and 2 then ss=1
59735 zw=0:if sa and 1 then zw=1
59740 if di=0 then rf=sn:rr=se:rb=ss:rl=zw
59745 if di=1 then rf=se:rr=ss:rb=zw:rl=sn
59750 if di=2 then rf=ss:rr=zw:rb=sn:rl=se
59755 if di=3 then rf=zw:rr=sn:rb=se:rl=ss
59760 return

59800 rem return left/right from sx/sy/d
59810 if di=0 then ly=sy:ry=sy:lx=sx-1:rx=sx+1
59820 if di=1 then lx=sx:rx=sx:ly=sy-1:ry=sy+1
59830 if di=2 then ly=sy:ry=sy:lx=sx+1:rx=sx-1
59840 if di=3 then lx=sx:rx=sx:ly=sy+1:ry=sy-1
59850 rl=0:rr=0
59860 if lx<0 or lx>=xs or ly<0 or ly>=ys goto 59880
59870 rl=m%(lx,ly)
59880 if rx<0 or rx>=xs or ry<0 or ry>=ys goto 59895
59890 rr=m%(rx,ry)
59895 return

59900 rem step forward
59910 if di=0 then sy=sy-1:rem N
59920 if di=1 then sx=sx+1:rem E
59930 if di=2 then sy=sy+1:rem S
59940 if di=3 then sx=sx-1:rem W
59950 return

63850 rem set cursor position
63851 rem sx=x sy=y
63860 poke 781,sy
63870 poke 782,sx
63880 poke 783,0
63890 sys 65520
63895 return

63900 rem write str to screen
63901 rem sl$ = string
63910 gosub 63850
63920 print sl$;
63930 return

63950 rem write chr to screen
63951 rem sx = x coordinate
63952 rem sy = y coordinate
63953 rem sz = character code
63954 rem sw = color
63950 sv=sx+sy*40
63960 poke 1024+sv,sz
63970 poke 55296+sv,sw
63980 return

63998 rem quit program
63999 print chr$(147):end

Obraz na taśmie: pobierz tutaj .

Przykłady:

przykłady

marinus
źródło
1
O MÓJ BOŻE. Jeśli inni chcą to rozwiązać, do cholery, świetnie ... ale wygrałeś nagrodę z definicji karty atutowej w wyzwaniu. Kusiło mnie, aby wyciągnąć emulator i zrobić to z powodów nostalgii, ale pomyślałem, że bardziej produktywne jest napisanie go w kolorze czerwonym, aby zobaczyć, jak dobrze kompilator krzyżowy kompilatora może wytrzymać. źródło tego . Będę Rebmu-IFY go i umieścić go w pewnym momencie ... ale laska jest Twój! Wielkie brawa.
Dr. Rebmu,
1
Ponadto, RE: Dijkstra, ma zabawny cytat o nieśmiertelności : „Mam na myśli, że jeśli za 10 lat robisz coś szybko i brudno, nagle wyobrażasz sobie, że patrzę ponad twoimi ramionami i mówisz sobie:„ Dijkstra nie podobało mi się to, no cóż, to by mi wystarczyło nieśmiertelności ”. Więc chyba spełniło się jego życzenie! :-)
Dr. Rebmu
@ Dr.Rebmu: dzięki za nagrodę! Pisanie zajęło mi dosłownie cały dzień :)
marinus
10

(dlaczego mi na to nie bashpozwala?)

Właśnie musiałem teraz.

Bash, 12743 znaków

#!/bin/bash
IFS=
declare -a term
typeset -i term[0] term[1]
IFS=' ' read -a term <<< `stty size`
front[0]='\e[2;2H██████████████
\e[3;2H██████████████
\e[4;2H██████████████
\e[5;2H██████████████
\e[6;2H██████████████
\e[7;2H██████████████
\e[8;2H██████████████
\e[9;2H██████████████
\e[10;2H██████████████
\e[11;2H██████████████
\e[12;2H██████████████
\e[13;2H██████████████
\e[14;2H██████████████'
front[1]='\e[5;5H████████
\e[6;5H████████
\e[7;5H████████
\e[8;5H████████
\e[9;5H████████
\e[10;5H████████
\e[11;5H████████'
front[2]='\e[6;6H██████
\e[7;6H██████
\e[8;6H██████
\e[9;6H██████
\e[10;6H██████'
lfront[0]='\e[2;1H█
\e[3;1H█
\e[4;1H█
\e[5;1H█
\e[6;1H█
\e[7;1H█
\e[8;1H█
\e[9;1H█
\e[10;1H█
\e[11;1H█
\e[12;1H█
\e[13;1H█
\e[14;1H█'
lfront[1]='\e[5;1H████
\e[6;1H████
\e[7;1H████
\e[8;1H████
\e[9;1H████
\e[10;1H████
\e[11;1H████'
lfront[2]='\e[6;1H█████
\e[7;1H█████
\e[8;1H█████
\e[9;1H█████
\e[10;1H█████'
rfront[0]='\e[2;16H█
\e[3;16H█
\e[4;16H█
\e[5;16H█
\e[6;16H█
\e[7;16H█
\e[8;16H█
\e[9;16H█
\e[10;16H█
\e[11;16H█
\e[12;16H█
\e[13;16H█
\e[14;16H█'
rfront[1]='\e[5;13H████
\e[6;13H████
\e[7;13H████
\e[8;13H████
\e[9;13H████
\e[10;13H████
\e[11;13H████'
rfront[2]='\e[6;12H█████
\e[7;12H█████
\e[8;12H█████
\e[9;12H█████
\e[10;12H█████'
left[0]='\e[1;1H▙
\e[2;1H█
\e[3;1H█
\e[4;1H█
\e[5;1H█
\e[6;1H█
\e[7;1H█
\e[8;1H█
\e[9;1H█
\e[10;1H█
\e[11;1H█
\e[12;1H█
\e[13;1H█
\e[14;1H█
\e[15;1H▛'
left[1]='\e[2;2H▙
\e[3;2H█▙
\e[4;2H██▙
\e[5;2H███
\e[6;2H███
\e[7;2H███
\e[8;2H███
\e[9;2H███
\e[10;2H███
\e[11;2H███
\e[12;2H██▛
\e[13;2H█▛
\e[14;2H▛'
left[2]='\e[5;5H▙
\e[6;5H█
\e[7;5H█
\e[8;5H█
\e[9;5H█
\e[10;5H█
\e[11;5H▛'
right[0]='\e[1;16H▟
\e[2;16H█
\e[3;16H█
\e[4;16H█
\e[5;16H█
\e[6;16H█
\e[7;16H█
\e[8;16H█
\e[9;16H█
\e[10;16H█
\e[11;16H█
\e[12;16H█
\e[13;16H█
\e[14;16H█
\e[15;16H▜'
right[1]='\e[2;13H  ▟
\e[3;13H ▟█
\e[4;13H▟██
\e[5;13H███
\e[6;13H███
\e[7;13H███
\e[8;13H███
\e[9;13H███
\e[10;13H███
\e[11;13H███
\e[12;13H▜██
\e[13;13H ▜█
\e[14;13H  ▜'
right[2]='\e[5;12H▟
\e[6;12H█
\e[7;12H█
\e[8;12H█
\e[9;12H█
\e[10;12H█
\e[11;12H▜'

echo -e "\e[2J"

# Read map
typeset -i cout
cout=0
echo "Please input your map!"
echo "Please input the next row (or leave it blank if you're finished!)"
read input

declare -A map

typeset -i xlen ylen
ylen=0

until [ -z $input ]
do
    IFS=' ' read -a inputmap <<< "$input"
    xlen=${#inputmap[*]}
    let ylen++
    for index in "${!inputmap[@]}"
    do
        typeset -i map[$index,$cout]
        map[$index,$cout]=0
        el=${inputmap[index]}
        if [[ $el == W??? ]]
        then
            let "map[$index,$cout]|=1"
        fi
        if [[ $el == ?N?? ]]
        then
            let "map[$index,$cout]|=2"
        fi
        if [[ $el == ??S? ]]
        then
            let "map[$index,$cout]|=4"
        fi
        if [[ $el == ???E ]]
        then
            let "map[$index,$cout]|=8"
        fi
    done
    echo "Please input the next row (or leave it blank if you're finished!)"
    read input
    cout+=1
done

echo -ne "\e[2J"

typeset -i dir x y
dir=0
x=0
y=0

move() {
    if ((dir == 0)) && ( ((${map[$x,$y]} & 2)) || ((y == 0)) )
    then
        return 1
    elif ((dir == 1)) && ( ((${map[$x,$y]} & 8)) || (($x == $xlen)) )
    then
        return 1
    elif ((dir == 2)) && ( ((${map[$x,$y]} & 4)) || ((y == $ylen)) )
    then
        return 1
    elif ((dir == 3)) && ( ((${map[$x,$y]} & 1)) || ((x == 0)) )
    then
        return 1
    fi
    x=$1
    y=$2
}

input=

until [[ $input == [qQ] ]]
do
    if [[ $input == [DlL] ]]
    then
        let dir-=1
        if (( dir == -1 ))
        then
            dir=3
        fi
    elif [[ $input == [CrR] ]]
    then
        let dir+=1
        if (( dir == 4 ))
        then
            dir=0
        fi
    elif [[ $input == [AfF] ]]
    then
        if (( dir == 0 ))
        then
            move $x $(( y-1 ))
        elif (( dir == 1 ))
        then
            move $(( x+1 )) $y
        elif (( dir == 2 ))
        then
            move $x $(( y+1 ))
        elif (( dir == 3 ))
        then
            move $(( x-1 )) $y
        fi
    elif [[ $input == [bB] ]]
    then
        if (( dir == 0 ))
        then
            dir=2
            move $x $(( y+1 ))
            dir=0
        elif (( dir == 1 ))
        then
            dir=3
            move $(( x-1 )) $y
            dir=1
        elif (( dir == 2 ))
        then
            dir=0
            move $x $(( y-1 ))
            dir=2
        elif (( dir == 3 ))
        then
            dir=1
            move $(( x+1 )) $y
            dir=3
        fi
    fi
    echo -ne "\e[2J"
    echo -ne "\e[16;1Hd=$dir; x=$x; y=$y\e[48;5;29m"
    for (( y2=1; y2 <= 15; y2++ ))
    do
        echo -ne "\e[$y2;16H\e[1K"
    done
    if (( dir == 0 ))
    then
        for (( y2=(y-2); y2 <= y; y2++ ))
        do
            if (( y2 < 0 )); then continue; fi
            let i=y-y2
            if (( x > 0 )) && (( ${map[$((x-1)),$y2]} & 2 ))
            then
                if (( ((x-1) + y2) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${lfront[$i]}
            fi
            if (( (x+1) < xlen )) && (( ${map[$((x+1)),$y2]} & 2 ))
            then
                if (( ((x-1) + y2) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${rfront[$i]}
            fi
            if (( ${map[$x,$y2]} & 1 ))
            then
                if (( (x + y2) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${left[$i]}
            fi
            if (( ${map[$x,$y2]} & 8 ))
            then
                if (( (x + y2) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${right[$i]}
            fi
            if (( ${map[$x,$y2]} & 2 ))
            then
                if (( (x + y2) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${front[$i]}
            fi
        done
    elif (( dir == 1 ))
    then
        for (( x2=x+2; x2 >= x; x2-- ))
        do
            if (( x2 > 16 )) || (( x2 >= xlen )); then continue; fi
            let i=x2-x
            if (( y > 0 )) && (( ${map[$x2,$((y-1))]} & 8 ))
            then
                if (( (x2 + (y-1)) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${lfront[$i]}
            fi
            if (( (y+1) < ylen )) && (( ${map[$x2,$((y+1))]} & 8 ))
            then
                if (( (x2 + (y-1)) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${rfront[$i]}
            fi
            if (( ${map[$x2,$y]} & 2 ))
            then
                if (( (x2 + y) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${left[$i]}
            fi
            if (( ${map[$x2,$y]} & 4 ))
            then
                if (( (x2 + y) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${right[$i]}
            fi
            if (( ${map[$x2,$y]} & 8 ))
            then
                if (( (x2 + y) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${front[$i]}
            fi
        done
    elif (( dir == 2 ))
    then
        for (( y2=(y+2); y2 >= y; y2-- ))
        do
            if (( y2 > 15 )) || (( y2 >= ylen )); then continue; fi
            let i=y2-y
            if (( x > 0 )) && (( ${map[$((x-1)),$y2]} & 4 ))
            then
                if (( ((x-1) + y2) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${rfront[$i]}
            fi
            if (( (x+1) < xlen )) && (( ${map[$((x+1)),$y2]} & 4 ))
            then
                if (( ((x+1) + y2) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${lfront[$i]}
            fi
            if (( ${map[$x,$y2]} & 8 ))
            then
                if (( (x + y2) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${left[$i]}
            fi
            if (( ${map[$x,$y2]} & 1 ))
            then
                if (( (x + y2) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${right[$i]}
            fi
            if (( ${map[$x,$y2]} & 4 ))
            then
                if (( (x + y2) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${front[$i]}
            fi
        done
    elif (( dir == 3 ))
    then
        for (( x2=(x-2); x2 <= x; x2++ ))
        do
            if (( x2 < 0 )); then continue; fi
            let i=x-x2
            if (( y > 0 )) && (( ${map[$x2,$((y-1))]} & 1 ))
            then
                if (( (x2 + (y-1)) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${rfront[$i]}
            fi
            if (( (y+1) < ylen )) && (( ${map[$x2,$((y+1))]} & 1 ))
            then
                if (( (x2 + (y+1)) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${lfront[$i]}
            fi
            if (( ${map[$x2,$y]} & 4 ))
            then
                if (( (x2 + y) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${left[$i]}
            fi
            if (( ${map[$x2,$y]} & 2 ))
            then
                if (( (x2 + y) & 1 ))
                then
                    echo -ne "\e[38;5;40m"
                else
                    echo -ne "\e[38;5;28m"
                fi
                echo -ne ${right[$i]}
            fi
            if (( ${map[$x2,$y]} & 1 ))
            then
                if (( (x2 + y) & 1 ))
                then
                    echo -ne "\e[38;5;28m"
                else
                    echo -ne "\e[38;5;40m"
                fi
                echo -ne ${front[$i]}
            fi
        done
    fi
    echo -ne "\e[0m"
    echo -ne "\e[${term[0]};0H[F]orward, [B]ackward, turn [L]eft, turn [R]ight or [Q]uit?"
    read -n 1 input
done

echo

Proszę pamiętać, że to pierwsza rzecz, którą zrobiłem bash, to więcej niż połączenie kilku poleceń razem. Prawdopodobnie można by go znacznie zmniejszyć, gdybym nie kodował wszystkich ścian, ale wydawało się to łatwiejsze. Nie ma żadnej konsekwencji. Format bajtu dla każdego kwadratu jest wybierany w straszny sposób. Ale to działa.

Dodałem nawet obsługę ruchu za pomocą klawiszy strzałek :)

Oto kilka zrzutów ekranu dla przykładowego wejścia (Uwaga: moja mapa zaczyna się od (0 | 0)):

0 | 0, skierowany na północ 0 | 2, skierowany na południe 2 | 1, skierowany na wschód 2 | 1, skierowany na południe 1 | 2, skierowany na północ

Oprócz czwartej wszystkie one również wyglądają jak próbki (patrz mój komentarz do PO).

Te zrzuty ekranu zostały zrobione na urxvt v9.15 z obsługą 256 kolorów, prawdopodobnie wyglądałoby to dość bzdury na terminalu w 88 kolorach, a terminale bez obsługi Unicode w ogóle nie działają. Czcionka, której użyłem, to Source Code Pro firmy Adobe.

jazzpi
źródło
1
Haha, w bash i również w kolorze! Ładny. Miałeś całkowitą rację co do tej ściany, najwyraźniej w pewnym momencie „naprawiłem” ją w moim programie. Więc to naprawiłem. :-) Dzięki za haczyk!
Dr. Rebmu,
3

Oto moja wersja w Pythonie 3. To coś w rodzaju postaci 3k i przy odrobinie wysiłku może być nieco mniejsze (na początek można usunąć wiele białych znaków).

Obecnie używa +X/\jako znaków do rysowania, ale jest skonfigurowany do rysowania ze znakami Unicode, jeśli masz czcionkę o stałej szerokości, która będzie renderować je poprawnie. Obsługuje stosowanie oddzielnych kafelków dla kątowych części różnych ścian, chociaż nie używam tej funkcji. Pozwala także na umieszczenie sufitu, podłogi i „odległych” kafelków, a także możesz użyć różnych, gdy gracz jest skierowany na wschód lub zachód kontra północ lub południe. Niestety, to nigdy nie wyglądało zbyt dobrze, więc prawdopodobnie wszystkie powinny być puste (lub coś solidnego, jak ).

Niestety, na moim systemie Windows 7 miałem okropny czas, próbując znaleźć czcionkę o stałej szerokości z pełnym zestawem znaków blokowych (np. I ). Większość z tych, które znalazłem, nie mogła zostać udostępniona w cmdkonsoli z jakiegoś powodu (być może dlatego, że nie są idealnie rozmieszczone w jednej przestrzeni ?). Jeśli uważasz, że twoja konsola jest bardziej funkcjonalna, spróbuj użyć alternatywnego zestawu znaków, który skomentowałem w górnej części pliku, który nie wygląda źle nawet przy dwóch kolorach. Ma wypełnione sufity i podłogi, a przeważnie przezroczyste ściany.

Kod:

from itertools import product as p
r=range
cs=r"+X//\\//\\      " #" ░▛▛▜▜▟▟▙▙██████"
shapes=[(1,[(x,y,0)for x,y in p(r(5),r(5,10))]),
        (0,[(x,y,0)for x,y in p(r(5,11),r(5,10))]),
        (1,[(x,y,0)for x,y in p(r(11,16),r(5,10))]),
        (1,[(4,4,4),(4,10,6)]+[(4,y,0)for y in r(5,10)]),
        (1,[(11,4,2),(11,10,8)]+[(11,y,0)for y in r(5,10)]),
        (0,[(x,y,0)for x,y in p(r(4),r(4,11))]),
        (1,[(x,y,0)for x,y in p(r(4,12),r(4,11))]),
        (0,[(x,y,0)for x,y in p(r(12,16),r(4,11))]),
        (0,[(1,1,4),(2,2,4),(3,3,4),(1,13,6),(2,12,6),(3,11,6)]+
           [(x,y,0)for x,y in p(r(1,4),r(2,14)) if x<y<14-x]),
        (0,[(14,1,2),(13,2,2),(12,3,2),(14,13,8),(13,12,8),(12,11,8)]+
           [(x,y,0)for x,y in p(r(12,15),r(2,14)) if 15-x<y<x-1]),
        (1,[(0,y,0) for y in r(1,14)]),
        (0,[(x,y,0) for x,y in p(r(1,15),r(1,14))]),
        (1,[(15,y,0) for y in r(1,14)]),
        (1,[(0,0,4),(0,14,6)]+[(0,y,0)for y in r(1,14)]),
        (1,[(15,0,2),(15,14,8)]+[(15,y,0) for y in r(1,14)])]
def rr(s):
    for r in s:print("".join(r))
def dw(s,a,p,d):
    for i,r in enumerate(s):r[:]=cs[10+i//5*2+d%2]*16
    for w,(pl,sh) in zip(a,shapes):
        if w:
            for x,y,c in sh:
                s[y][x]=cs[c+(p+d+pl)%2]
dx=[1,0,-1,0]
def ga(x,y,d,m):
    fx=dx[d];fy=lx=dx[d-1];ly=dx[d-2]
    return [m[y+2*fy+ly][x+2*fx+lx][d],m[y+2*fy][x+2*fx][d],
            m[y+2*fy-ly][x+2*fx-lx][d],m[y+2*fy][x+2*fx][d-1],
            m[y+2*fy][x+2*fx][d-3],m[y+fy+ly][x+fx+lx][d],
            m[y+fy][x+fx][d],m[y+fy-ly][x+fx-lx][d],
            m[y+fy][x+fx][d-1],m[y+fy][x+fx][d-3],
            m[y+ly][x+lx][d],m[y][x][d],
            m[y-ly][x-lx][d],m[y][x][d-1],m[y][x][d-3]]
def rd():
    l=input();
    while l!="":
        if "\n" in l:yield from l.split("\n")
        else:yield l
        l=input()
def rm():
    m=[[[d in s for d in"ESWN"]for s in r.strip().split()]+[[1]*4]*2
       for r in rd()]
    return m+[[[1]*4 for _ in m[0]]]*2
def cl():print("\n"*30)
def gl():
    print("Enter map, followed by a blank line.")
    x=y=0;d=3;m=rm();mv="";s=[[""]*16 for _ in r(15)]
    while True:
        cl();dw(s,ga(x,y,d,m),x+y,d);rr(s)
        print("X:",x+1,"Y:",y+1,"Facing:","ESWN"[d])
        if mv:print("Last move:",mv)
        mv=input("[FBLRQ]? ").upper()
        if mv=="F":
            if not m[y][x][d]:x+=dx[d];y+=dx[d-1]
            else:mv+=" (Blocked)"
        elif mv=="B":
            if not m[y][x][d-2]:x+=dx[d-2];y+=dx[d-3]
            else:mv+=" (Blocked)"
        elif mv=="L":d=(d-1)%4
        elif mv=="R":d=(d+1)%4
        elif mv=="Q":break
        else:mv="I didn't understand %r."%mv
gl()

Zestaw znaków jest określony u góry pliku. Kolejność znaków to:

  1. nawet ściana parzystości
  2. dziwna ściana parzystości
  3. równy górny kąt ściany parzystości (np. /ze ścianą poniżej)
  4. nieparzysty parytet górny prawy kąt ściany
  5. równy górny lewy kąt ściany
  6. nieparzysty parytet górny lewy kąt ściany
  7. równy parzysty dolny kąt ściany
  8. nieparzysty parytet prawy dolny kąt ściany
  9. równy lewy dolny kąt ściany
  10. nieparzysty parytet dolny lewy kąt ściany
  11. okładzina sufitowa E / W
  12. okładzina sufitowa N / S
  13. horyzont skierowany w stronę E / W (środek ekranu, jeśli nie ma ścian)
  14. horyzont skierowany w stronę N / S
  15. okładzina podłogowa E / W
  16. okładzina podłogowa N / S

Istnieje 15 ścian, które mogą wymagać renderowania przez grę, w podobny sposób (ze Vwskazaniem pozycji gracza i łuku widzenia):

_ _ _
_|_|_ 
_|_|_
 |V|

Kafelki używane przez 15 ścian są zdefiniowane na shapesliście. To lista 2-krotek. Pierwsza wartość krotki wskazuje na „parzystość” ściany, ze 0wskazaniem, że powinna być narysowana tymi samymi znakami co ściana bezpośrednio przed znakiem i 1wskazując, że powinien to być alternatywny wzór (np. +Vs X). Druga wartość to lista x,y,tkrotek wskazująca współrzędne ekranu i indeks jednego piksela (ściany renderowane z nieparzystą parzystością zostaną 1dodane do każdego z tych indeksów). Kształty są uporządkowane według odległości, więc pierwsze trzy reprezentują prostopadłe ściany dwie płytki przed postacią, a następnie dwie równoległe ściany dwie płytki przed sobą i tak dalej.

Funkcje to:

  • rr: „render” ekran (drukując kafelki w buforze ekranu).
  • dw: „rysuj ściany” do dostarczonego bufora ekranu. Korzysta z algorytmu malarzy, więc najbardziej odległe ściany są rysowane jako pierwsze i mogą zostać zasłonięte przez bliższe.
  • ga: „get area” zwraca listę wartości boolowskich wskazujących, które ściany są nieprzezroczyste dla danej pozycji mapy i które są zwrócone.
  • rd: „read”, generator, który czyta mapę, uzyskując linie. Jest to potrzebne tylko dlatego, że konsola IDLE robi dziwne rzeczy, gdy wklejasz wieloliniowe dane wejściowe zamiast wpisywać jedną linię na raz.
  • rm: „czytaj mapę”, analizuje mapę w zagnieżdżoną listę wartości logicznych, indeksowaną według m[y][x][d](z d=0zachowaniem Wschodu i d=1Południa). Dodaje również dwa wiersze i dwie kolumny kwadratów wypełnienia, aby uniknąć błędów indeksu w drugim kodzie.
  • cl: „wyczyść” wynik (pisząc wystarczającą liczbę nowych wierszy, aby przewinąć stary widok z góry większości konsol).
  • gl: „pętla gry”, w której gromadzone są dane wejściowe i wywoływane są powyższe elementy.

Kilka „zrzutów ekranu”:

Pozycja wyjściowa:

\               
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
+XXXXXXXXXXXXXX+
/               
X: 1 Y: 1 Facing: N
[FBLRQ]? 

Patrząc wzdłuż północnej ściany:

\               
X\              
X+\             
X++\            
X+++\           
X+++X           
X+++X           
X+++X           
X+++X           
X+++X           
X+++/           
X++/            
X+/             
X/              
/               
X: 1 Y: 1 Facing: E
Last move: R
[FBLRQ]? 

Kilka ujęć pasujących do twoich przykładów (uwaga, puste pierwsze linie są odcinane przez przepełnienie stosu, są one w wyniku programu):

X             / 
X            /+ 
X           /++ 
X           +++ 
X           +++ 
X           +++ 
X           +++ 
X           +++ 
X           +++ 
X           +++ 
X           \++ 
X            \+ 
X             \ 

X: 3 Y: 2 Facing: S
Last move: F
[FBLRQ]? 

I:

              / 
             /X 
            /XX 
            XXX 
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
+++++XXXXXX+XXX+
            XXX 
            \XX 
             \X 
              \ 

X: 3 Y: 2 Facing: E
Last move: L
[FBLRQ]? 

Oto jeden z dziwniejszych widoków na dostarczonej mapie, ponieważ ściana równoległa do naszego widoku jest tego samego koloru, co wystająca za nią prostopadła ściana:

 \              
 +\             
 ++\            
++++        ++++
++++        ++++
++++        ++++
++++        ++++
++++        ++++
++++        ++++
++++        ++++
 ++/            
 +/             
 /              

X: 3 Y: 4 Facing: N
Last move: R
[FBLRQ]? 

Oto jak wyglądałby obszar ostatniego strzału z góry:

_   _
 |
  V
Blckknght
źródło
Ładnie dodane analizy i diagramy! Hm, te ściany kończą się tym samym kolorem na tym ostatnim w mojej realizacji. Dobra uwaga na temat obudowy krawędzi. Nie sądziłem, że tak się stanie, ale w pewnym sensie musi. Zgadnij, to jak kolorowanie mapy, a dwa kolory to w rzeczywistości za mało ...: - /
Dr. Rebmu