Conjunto de Construção de Masmorras

19

Quando eu era criança, joguei o jogo Intellivision Advanced Dungeons and Dragons: Treasure of Tarmin . Os gráficos 3D colocam você em uma perspectiva em primeira pessoa com um realismo chocante:

Gráficos 3D altamente chocantes

Mas então eu tenho um C-64. E pude desenhar na grade de 40x25 caracteres, percorrendo a tela, definindo a cor com a tecla Ctrl e um dígito e colocando os símbolos em qualquer lugar que eu quisesse (por que não bashme permite fazer isso?) . O conjunto de caracteres tinha componentes triangulares e componentes de bloco sólido. Então, eu pude argumentar sobre como alguém pode gerar uma renderização de sua perspectiva em uma grade através desse meio.

Encontrei a especificação de quase três décadas de idade, em papel de caderno encadernado em espiral, sobre "Dungeon Construction Set" esta semana:

insira a descrição da imagem aqui

( ATUALIZAÇÃO : Leitores cuidadosos perceberão que isso não se sustenta nas partes inclinadas. Os números corrigidos são fornecidos abaixo.)

Embora Treasure of Tarmin tenha sido jogado em uma grade, as paredes existiam apenas nas bordas dos quadrados da grade. Tendo aprendido o que eram bytes, percebi que se eu fizesse o mapa com bytes ... cada quadrado no mapa poderia ter quatro estados possíveis para cada uma de suas bordas:

  1. Desobstruído
  2. parede
  3. Porta
  4. Algo mais?

Eu nunca escrevi (até a noite passada). Eu pensei que poderia ser divertido para os outros tentarem.

Portanto, sua tarefa é implementar um renderizador de labirinto baseado no modo de personagem que implemente meu (corrigido !!) especificações ... mas usando as tecnologias de 2013.

Entrada

Como a especificação não define a renderização para portas, apenas assumiremos que as únicas opções são parede e não parede. Por uma questão de simplicidade, sua entrada é um mapa composto por linhas de seqüências de caracteres que se parecem com isso:

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

Isso seria um mapa 5x5. O canto superior esquerdo (1,1) tem sua parede West e Nortogonal. O canto inferior direito (5,5) tem sua parede Sexterna e Eexterna.

Isso é consideravelmente menos divertido sem navegação no mapa. Então, no mínimo, coloque seu jogador em (1,1) voltado para o norte e ofereça a ele:

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

Em cada etapa, produza uma exibição de 16x15 da perspectiva em primeira pessoa, conforme definido pelas especificações do papel para notebook. Para evitar que você precise contar, o tamanho das paredes planas nas três distâncias são:

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

Os tamanhos delimitadores das paredes inclinadas são:

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

Esclarecimentos

  • As células adjacentes podem discordar sobre paredes compartilhadas. Portanto, a borda sul de um quadrado pode ser uma parede, enquanto a borda norte da praça ao sul dela seria desobstruída. No design original, considerei isso um recurso: permite idéias interessantes como portas de mão única ... ou paredes invisíveis que só aparecem depois que você passa por elas. Para essa simplificação, siga a mesma regra: para navegação e renderização, preste atenção apenas ao status da borda na célula mais próxima de você na direção em que está voltada .

  • A vista é muito melhor com "sombreamento". Portanto, para seus blocos completos, alterne o Unicode 2593 ▓ e 2591 ░ ou use Xe +se sua implementação for ASCII.

  • Caracteres de triângulo Unicode (25E2 ◢, 25E3 ◣, 25E4 ◤, 25E5 ◥) são um pouco ruins para desenhar isso. Além de não ter variantes sombreadas, elas geralmente aumentam apenas a largura do caractere e não a altura total ... mesmo em fontes de largura fixa. Você pode desenhar blocos completos ou caracteres de barra ou algo de sua escolha nos locais que eu queria diagonais. Soluções criativas interessantes que incorporam cores e usam esses caracteres em vez de sombreado são apreciadas.

  • Você pode assumir que as paredes mais externas estão definidas para limitar a área de jogo, para que você não precise se preocupar em renderizar nada fora do labirinto. Quaisquer paredes mais afastadas de você do que as especificações são ignoradas e apenas deixam espaço vazio.

  • O sombreamento da parede que você vê diretamente à sua frente se estiver voltado para o norte em (1,1) deve ser ESCURO. Sombreamento alternativo nas paredes adjacentes no mapa, de modo que, se todas as paredes estivessem presentes, uma parede clara nunca encostaria em uma parede escura.

  • Uma implementação C-64 que realmente faz o que eu originalmente pretendia ... com os caracteres diagonais e tudo ... superará qualquer outro critério de entrada. :-)

Exemplos

Para o mapa de amostra fornecido acima ...

Em (1,3), virado a sul:

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

Na (3,2) virada a sul:

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

Em (3,2), de frente para o leste:

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

Em (2,3), voltado para o norte:

               /
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
X++++++++++++++X
X++++++++++++++X
X++++++++++++++X
X++++++++++++++X
X++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
 ++++++++++++++X
               \
Dr. Rebmu
fonte
11
Sugiro fazer disso um desafio de código - um golfe seria muito ilegível e difícil: P
Maçaneta da porta
11
@ Doorknob Não deixe que isso te engane ... na verdade não é tão difícil assim. Há uma dica muito boa com as listas de três tamanhos delimitadores. E o que é um golfe senão um desafio que é resolvido e depois reduzido? :-) Mas vou deixar as pessoas escolherem como querem resolvê-lo ... NP
Dr. Rebmu
Você poderia, por favor, explicar as duas colunas de Xs na sua opinião, 3, 2voltadas para o sul?
jazzpi
Especialmente aquele do lado direito. Eu vejo por que o esquerdo está lá. Mas o caminho certo parece violar o esclarecimento nº 1.
jazzpi
@jazzpi Opa, você está certo, o mapa que coloquei precisa obedecer ao esclarecimento 1! Bem feito. Fixo. (Eu colocaria a parede sul que faltava em minha própria versão em algum momento aparentemente ... mas é bom ter um caso de teste na amostra ... então vamos deixar a parede sul de fora!)
Dr. Rebmu

Respostas:

10

Commodore 64 Basic

Cara, isso foi divertido. E difícil. O C64 Basic é quase indeformável, você nem pode usar a printdepuração porque a tela já foi usada para renderizar a masmorra. Você sabe que está se divertindo quando escreve código como55250 goto 55110 . Dijkstra vai me matar.

O programa usa duas cores e caracteres diagonais.

Escusado será dizer que não joguei golfe. Afirma agora o desafio do código , afinal. É 7183 bytes, se você estiver interessado.

É lento - na velocidade padrão, leva alguns segundos para renderizar a cena. O tamanho máximo do mapa é 10 por 10, mas pode ser alterado editando a linha 120.

Eu desenvolvi e testei isso usando o emulador VICE . O código abaixo é exibido em ASCII, o que significa PETSCII deslocado . No entanto, ao inserir o mapa, você deve usar PETSCII sem deslocamento .

Captura de tela: Captura de tela

Código:

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

Imagem da fita: faça o download aqui .

Os exemplos:

exemplos

marinus
fonte
11
AMD. Se outros quiserem resolver isso, ótimo ... mas você ganhou a recompensa por definição do trunfo no desafio. Fiquei tentado a puxar um emulador e fazê-lo por motivos de nostalgia, mas achei mais produtivo escrevê-lo em vermelho para ver como o compilador cruzado do compilador aguentava. fonte para isso . Vou reformulá- lo e publicá-lo em algum momento ... mas a recompensa é sua! Grande aplauso.
Dr. Rebmu
11
Além disso, RE: Dijkstra, ele tem uma citação engraçada sobre imortalidade : "Quero dizer, se daqui a dez anos, quando você estiver fazendo algo rápido e sujo, de repente você visualiza que estou olhando por cima dos ombros e diz para si mesmo 'Dijkstra faria não gostei disso ', bem, isso seria imortalidade suficiente para mim ". Então eu acho que ele conseguiu o que queria! :-)
Dr. Rebmu
@ Dr.Rebmu: obrigado pela recompensa! Isso me levou literalmente todos os dias para escrever :)
marinus
10

(por que não bashme deixa fazer isso?)

Eu só tinha que agora.

Bash, 12743 caracteres

#!/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

Lembre-se de que essa é praticamente a primeira coisa que fiz com bash mais do que apenas juntar alguns comandos. Provavelmente seria bastante redutível se eu não codificasse todas as paredes, mas parecia mais fácil. Não tem consistência alguma. O formato de bytes para cada quadrado é escolhido de maneira horrível. Mas funciona.

Eu até adicionei suporte ao movimento pelas teclas de seta :)

Estas são algumas capturas de tela para a entrada de amostra (observe que meu mapa começa em (0 | 0)):

0 | 0, voltado para o norte 0 | 2, virado a sul 2 | 1, de frente para o leste 2 | 1, virado a sul 1 | 2, virado a norte

Além do quarto, todos também se parecem com os exemplos (veja meu comentário sobre o OP).

Essas capturas de tela foram tiradas no urxvt v9.15 com suporte a 256 cores, provavelmente pareceriam uma porcaria em um terminal de 88 cores e os terminais sem suporte a unicode não funcionam. A fonte que eu usei foi o Source Code Pro da Adobe.

jazzpi
fonte
11
Haha, na festança, e na cor também! Agradável. Você estava totalmente certo sobre esse muro, aparentemente eu já havia "corrigido" em meu programa. Então, eu o soltei. :-) Obrigado pela captura!
Dr. Rebmu
3

Aqui está minha versão, no Python 3. É algo como caracteres 3k e pode ficar um pouco menor com um pouco de esforço (há muito espaço em branco que pode ser removido, para começar).

Atualmente, ele usa +X/\como caracteres de desenho, mas é configurado para desenhar com caracteres Unicode se você tiver uma fonte de largura fixa que os renderize corretamente. Ele suporta o uso de ladrilhos separados para as partes angulares das paredes de formas diferentes, embora eu não esteja usando esse recurso. Ele também permite que você forneça teto, piso e telhas "distantes", e você pode usar diferentes para quando o jogador estiver de frente para leste ou oeste versus norte ou sul. Infelizmente, isso nunca pareceu muito bom, então provavelmente todos esses itens devem estar em branco (ou algo sólido, como ).

Infelizmente, no meu sistema Windows 7, passei um tempo horrível tentando encontrar uma fonte monoespaçada com o conjunto completo de caracteres de bloco (por exemplo, e ). A maioria dos que encontrei não pôde ser disponibilizada nocmd console por algum motivo (talvez porque não sejam perfeitamente espaçados?). Se você acha que seu console é mais funcional, tente usar o conjunto alternativo de caracteres que comentei na parte superior do arquivo, o que não parece muito ruim, mesmo com apenas duas cores. Ele preencheu tetos e pisos, e principalmente paredes transparentes.

O código:

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()

O conjunto de caracteres é especificado na parte superior do arquivo. A ordem dos caracteres é:

  1. paridade paritária
  2. paridade ímpar
  3. ângulo de parede superior direito com paridade uniforme (por exemplo, /com uma parede abaixo dela)
  4. ângulo de parede superior direito de paridade ímpar
  5. ângulo de parede superior esquerdo com paridade uniforme
  6. ângulo de parede esquerdo superior de paridade ímpar
  7. ângulo de parede inferior direito com paridade uniforme
  8. ângulo de parede inferior direito de paridade ímpar
  9. ângulo de parede inferior esquerdo com paridade uniforme
  10. ângulo de parede esquerdo inferior de paridade ímpar
  11. para teto E / W
  12. revestimento de teto N / S
  13. horizonte voltado para E / W (no meio da tela, se não houver paredes)
  14. horizonte voltado para N / S
  15. de frente para o piso E / W
  16. revestimento de piso N / S

Existem 15 paredes que podem precisar ser renderizadas pelo jogo, em um padrão como este (com Vindicação da posição e do arco de visão do jogador):

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

Os ladrilhos usados ​​pelas 15 paredes são definidos na shapeslista. É uma lista de duas tuplas. O primeiro valor da tupla indica a "paridade" da parede, 0indicando que ela deve ser desenhada com os mesmos caracteres de uma parede diretamente na frente do caractere e 1indicando que esse deve ser o padrão alternativo (por exemplo, +vs X). O segundo valor é uma lista de x,y,ttuplas indicando as coordenadas da tela e o índice de blocos de um pixel (as paredes renderizadas com paridade ímpar serão 1adicionadas a cada um desses índices). As formas são ordenadas por distância, de modo que as três primeiras representam as paredes perpendiculares duas peças à frente do personagem, seguidas pelas duas paredes paralelas duas peças à frente e assim por diante.

As funções são:

  • rr: "renderiza" a tela (imprimindo os blocos no buffer da tela).
  • dw: "desenhar paredes" para um buffer de tela fornecido. Isso usa o algoritmo dos pintores, de modo que as paredes mais distantes são desenhadas primeiro e podem ser cobertas por outras mais próximas.
  • ga: "get area" retorna uma lista de valores booleanos indicando quais paredes são opacas para uma determinada posição e face do mapa.
  • rd: "read", um gerador que lê o mapa, produzindo as linhas. Isso é necessário apenas porque o console do IDLE faz coisas estranhas quando você cola entradas com várias linhas em vez de inserir uma linha por vez.
  • rm: "read map", analisa o mapa em uma lista aninhada de booleanos, indexados por m[y][x][d](com d=0leste e d=1sul). Ele também adiciona duas linhas e duas colunas de quadrados de preenchimento, para evitar erros de índice no outro código.
  • cl: "limpe" a saída (escrevendo novas linhas suficientes para rolar a exibição antiga da parte superior da maioria dos consoles).
  • gl: "game loop", onde a entrada é coletada e o material acima é chamado.

Algumas "capturas de tela":

A posição inicial:

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

Olhando ao longo da parede norte:

\               
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]? 

Algumas fotos correspondentes aos seus exemplos (observe, as primeiras linhas em branco estão sendo cortadas pelo Stack Overflow, elas estão na saída do programa):

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

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

E:

              / 
             /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]? 

Aqui está uma das vistas mais estranhas no mapa fornecido, pois a parede paralela à nossa vista é da mesma cor da parede perpendicular que se destaca atrás dela:

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

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

Aqui está como seria a área da última foto de cima:

_   _
 |
  V
Blckknght
fonte
Análise e diagramas adicionados agradáveis! Hum, essas paredes acabam sendo da mesma cor da última na minha implementação. Bom ponto sobre o caso de borda. Eu não pensei que isso iria acontecer, mas meio que tem que acontecer. Acho que é como colorir mapas, e duas cores não são suficientes ...: - /
Dr. Rebmu