Renderize a vista de cima para baixo de um telhado de quadril em ASCII

23

Primeiro, alguma terminologia ( fonte ):

  • Um telhado de quadril é (citando a Wikipedia) "um tipo de telhado em que todos os lados se inclinam para baixo em direção às paredes, geralmente com uma inclinação bastante suave"
  • Uma inclinação é uma superfície plana que faz parte do telhado
  • Um cume é uma aresta onde duas encostas opostas se encontram
  • Um quadril é uma aresta convexa onde duas inclinações pertencentes a paredes perpendiculares se encontram
  • Um vale é uma borda côncava onde duas encostas pertencentes a paredes perpendiculares se encontram
  • Quadris e vales devem ser coletivamente referidos como bordas diagonais.

Entrada possível:

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

Saída correspondente:

    +-------+   +---+   +-----------+
    |\     /|   |\ /|   |\         /|
    | \   / |   | V |   | \   ^---< |
    |  \ /  |   | | |   |  \ / \   \|
+---+   V   +---+ | +---+   X   +---+
|\   \  |  /     \|/     \ / \  |
| >---> | <-------X-------V   > |
|/   /  |  \     /|\         /| |
+---+   ^   +---+ | +-------+ | +---+
    |  / \  |   | | |       | |/   /|
    | /   \ |   | ^ |       | /---< |
    |/     \|   |/ \|       |/     \|
    +-------+   +---+       +-------+

Mais alguns casos de teste:

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

Saídas correspondentes:

+-------+   +-----------+           +---+               +---+           +---+   +---+
|\     /|   |\         /|           |\ /|               |\ /|           |\ /|   |\ /|
| \---< |   | >-------< |           | V |               | V |           | V |   | X |
| |\   \|   |/         \|           | | |               | | |           | | |   |/ \|
| | +---+   +-----------+       +---+ | +---+           | | +-----------+ | |   +---+
| | |                           |\   \|/   /|           | |/             \| |
| ^ |                           | \   V   / |           | <               > |
|/ \|                           |  \     /  |           |  \             /  |
+---+           +-------+   +---+   \   /   +---+       |   \-----------/   |
                |\     /|   |\   \   \ /   /   /|       |   |\         /|   |
                | >---/ |   | >--->   X   <---< |       |   | \       / |   |
                |/   /| |   |/   /   / \   \   \|       |   |  \     /  |   |
+---+   +---+   +---+ | |   +---+   /   \   +---+   +---+   ^   +---+   ^   +---+
|\ /|   |\ /|       | | |       |  /     \  |       |\   \ / \  |   |  / \ /   /|
| V |   | V |       | | |       | /   ^   \ |       | >---V   > |   | <   V---< |
| | |   | | |       | | |       |/   /|\   \|       |/       /| |   | |\       \|
| | |   | | +-------+ | |       +---+ | +---+       +-------+ | |   | | +-------+
| | |   | |/         \| |           | | |                   | | |   | | |
| ^ |   | /-----------\ |           | ^ |                   | ^ |   | ^ |
|/ \|   |/             \|           |/ \|                   |/ \|   |/ \|
+---+   +---------------+           +---+                   +---+   +---+

Sua entrada será um bitmap - uma matriz 2D de pixels quadrados - da área que deve ser coberta pelo telhado. Você pode supor que o limite dessa área seja uma curva de Jordan - ou seja, contínua e sem interseção automática - ou seja, a área coberta será contínua, sem buracos e nunca haverá quatro paredes se encontrando em um único ponto. Os formatos de entrada válidos incluem uma única sequência com separadores de nova linha, uma lista de sequências e uma matriz 2D de caracteres ou booleanos.

As regras de construção do telhado são:

  • Cada segmento reto da área coberta (doravante denominada parede) deve ter exatamente uma inclinação adjacente. A inclinação deve se afastar da parede. Cada declive deve ter pelo menos uma parede adjacente e todas as paredes adjacentes a uma inclinação devem ser colineares.
  • Todas as inclinações devem ter o mesmo ângulo (diferente de zero) contra a superfície horizontal. Ou seja, eles devem ter o mesmo tom.
  • As encostas devem formar uma superfície cujo limite é o limite da área coberta. Ou seja, nenhuma superfície além das pistas pode ser usada.
  • Qualquer cenário em que mais de uma solução (até a escala vertical) seja permitida por esta especificação é considerada um bug na especificação. Quaisquer correções se aplicam retroativamente.

De maneira equivalente, o telhado pode ser definido pela regra de que cada ponto do telhado é colocado o mais alto possível, sem exceder a inclinação máxima para esse telhado, medida usando a distância de Chebyshev na vista de cima para baixo.

Sua saída deve ser uma representação de arte ASCII do telhado - uma única sequência contendo caracteres de nova linha ou uma matriz de sequências, cada uma indicando uma única linha da saída. O telhado deve ser renderizado na vista de cima para baixo em uma escala de 4x - ou seja, cada quadrado da planta deve afetar uma área 5x5 da saída, de modo que os cantos dessa área 5x5 sejam compartilhados com os quadrados vizinhos (de modo que cada caractere de canto é afetado por quatro quadrados de entrada diferentes), conforme indicado no exemplo de saída. Espaço em branco extra é permitido desde que a forma de saída seja preservada. Os caracteres na saída devem ser:

  • um marcador de nova linha definido pelo ambiente deve ser usado (geralmente U + 000A, U + 000D ou um par de ambos) se a saída estiver na forma de uma única sequência
  • (Espaço U + 0020) representa um ponto fora de uma área coberta ou um ponto interno a uma inclinação
  • + (U + 002B sinal de mais) representa um ponto com duas paredes perpendiculares adjacentes a ele
  • - (U + 002D hífen-menos) representa uma parede ou uma crista orientada horizontalmente (leste-oeste)
  • / (U + 002F solidus) representa um quadril ou um vale orientado de nordeste para sudeste, ou um ponto adjacente a dois deles
  • < (Sinal menor que U + 003C) representa um ponto com duas arestas diagonais adjacentes a ele no leste
  • > (U + 003E sinal maior que) representa um ponto com duas arestas diagonais adjacentes a ele no oeste
  • \ (Solidus reverso U + 005C) representa um quadril ou um vale orientado de noroeste a sudeste, ou um ponto adjacente a dois deles
  • ^ (Acento circunflexo U + 005E) representa um ponto com duas arestas diagonais adjacentes a ele no sul
  • V (U + 0056 letra maiúscula latina v) representa um ponto com duas arestas diagonais adjacentes a ele no norte
  • X (U + 0058 letra maiúscula latina x) representa um ponto com arestas diagonais adjacentes a ele nos quatro lados
  • | (Barra vertical U + 007C) representa uma parede ou uma crista orientada verticalmente (norte-sul)

Observe que não é possível que um número ímpar de arestas diagonais termine no mesmo ponto (exceto nas paredes). Podemos visualizar isso dividindo a vizinhança de cada ponto em declive norte + declive sul e em declive leste + declive oeste. O limite entre as duas partições deve ser composto de arestas diagonais.

Se o seu ambiente usa uma codificação de caracteres incompatível com ASCII, você pode usar os caracteres equivalentes (o mesmo glifo ou o mais próximo disponível) no caractere que codifica o ambiente.

A implementação de referência (feia) a seguir no Ruby é normativa em relação à saída que não é de espaço em branco. Observe particularmente o rendermétodo:

def pad ary
  row = ary.first.map{-1}
  ([row] + ary + [row]).map{|r| [-1] + r + [-1]}
end

def parse str
  str.split("\n").map{|r| r.chars.map(&{" " => -1, "*" => Float::INFINITY})}
end

def squares ary, size
  ary.each_cons(size).map do |rows|
    rows.map{|row| row.each_cons(size).to_a}.transpose
  end
end

def consmap2d ary, size
  squares(ary, size).map{|sqrow| sqrow.map{|sq| yield sq}}
end

def relax ary
  loop do
    new = consmap2d(pad(ary), 3){|sq| sq[1][1] == -1 ? -1 : sq.flatten.min + 1}
    return new if new == ary
    ary = new
  end
end

def semidouble ary, op
  ary.zip(ary.each_cons(2).map{|r1,r2|r1.zip(r2).map(&op)}).flatten(1).compact.transpose
end

def heightmap str
  relax(semidouble(semidouble(semidouble(semidouble(pad(parse str),:max),:max),:min),:min))
end

def render heightmap
  puts consmap2d(heightmap, 3){|sq|
    next " " if sq[1][1] == -1
    hwall = sq[0][1] == -1 || sq[2][1] == -1
    vwall = sq[1][0] == -1 || sq[1][2] == -1
    next "+" if hwall && vwall
    next "-" if hwall
    next "|" if vwall
    next "+" if sq.flatten.min == -1

    nws = sq[0][1] == sq[1][0]
    nes = sq[0][1] == sq[1][2]
    sws = sq[2][1] == sq[1][0]
    ses = sq[2][1] == sq[1][2]

    next "X"  if nws && nes && sws && ses
    next "V"  if nws && nes
    next "^"  if sws && ses
    next ">"  if nws && sws
    next "<"  if nes && ses
    next "/"  if nes && sws
    next "\\" if nws && ses
    next " "  if sq[0][1] != sq[2][1] || sq[1][0] != sq[1][2]
    next "|"  if sq[0][1] == sq[1][1]
    next "-"  if sq[1][0] == sq[1][1]
    ??
  }.map(&:join)
end

render heightmap $<.read if __FILE__ == $0 
John Dvorak
fonte
1
Você deve adicionar mais casos de teste.
Mbomb007
@ mbomb007 Adicionado. Dado o espaço que ocupam - devo acrescentar mais?
John Dvorak
@JanDvorak Talvez adicione o caso de teste *. Caso contrário, isso provavelmente é suficiente.
mbomb007
É [[0,1,1],[1,0,1],[1,1,1]]uma entrada válida? (A entrada não tem “buracos”, mas há um canto traquinas quase auto-intersecção.)
Lynn
@ Lynn Você não precisa se preocupar com esse caso, não é uma entrada válida. O canto que você menciona conta como um limite de interseção automática (ou melhor, um limite que não é uma curva).
John Dvorak

Respostas:

11

Python 2, 500 bytes

z=input()
W=4*len(z[0])+1
H=4*len(z)+1
R=range
s=[-~W*[0]for _ in R(-~H)]
for y in R(H/4):
 for x in R(W/4):
        for h in R(25):s[y*4+h%5][x*4+h/5]|=z[y][x]
F=[(x/3-1,x%3-1)for x in[1,7,3,5,0,6,8,2]]
exec'for y in R(H):\n for x in R(W):s[y][x]+=0<s[y][x]<=min(s[y+d][x+e]for(e,d)in F)\n'*H
for y in R(H):
 l=''
 for x in R(W):h=s[y][x];a=[s[y+d][x+e]for(e,d)in F[:4]];l+=r' XabcVde^f ||g>h\\+//+<<jk<l//+\\+>>m --^^oVVqrX'[h and int(''.join(`int(n==h)`for n in a),2)*3+((h==1)*2or max(a)==h)+1]
 print l

Cansado de jogar golfe e cheguei a uma boa rodada, então aqui está.

O recuo de oito espaços é uma guia.

Passe uma matriz binária sobre STDIN, da seguinte maneira:

python2.7 roof.py <<<"[[1,1,0,1,1,1,0,0,0,1,0,0,0,0,1,0,0,0,1,0], [1,0,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,1,0], [0,0,0,0,1,1,0,1,1,1,1,1,0,0,1,1,1,1,1,0], [1,0,1,0,0,1,0,0,1,1,1,0,0,1,1,1,0,1,1,1], [1,0,1,1,1,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0]]"
Lynn
fonte
Totalmente jogado ou não, isso é incrível. Bem feito. 1
R. Kap 4/16