Texto animado sinusoidal de arte ASCII

11

Sinto uma certa falta das antigas demos que mostram os recursos dos computadores quando foram chamados x86 em vez de i3, i5 e i7. Uma das primeiras que assisti no meu 386 foi a demo Unreal da Future Crew, que agora está comemorando seu 25º aniversário. No minuto 0:43, a primeira parte da demonstração é iniciada e vemos um texto de rolagem seguindo um caminho sinusoidal. Vamos tentar imitar esse efeito na arte ASCII!

O desafio

Dado este caminho:

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

e um texto de entrada, desenhe o texto seguindo esse caminho, assim:

Thi                                Golf! 
   s i                          de       Yay
      s                       Co            !
        P                     
         r                  d
         o                  n
          g                a
           ra            s 
             mmi      zle
                ng Puz

Observe que os espaços contam como caracteres no caminho e que o caminho se repete se o texto for maior que a amostra.

A parte da animação

Depois de desenhar o texto, aguarde 100 ms (para criar uma animação de cerca de 10 qps) e desenhe o texto novamente, mas partindo da próxima posição do caminho. Portanto, para o quadro #n, calcule n modulo 40e comece a desenhar na seguinte posição do caminho, com o texto sempre alinhado à esquerda da tela:

***                                ***
|  ***                          ***  |
|     **                      **     |
|       *                    *       |
|        *                  *        |
|        *                  *        |
|         *                *         |
|          **            **          |
|            ***      ***            |
|               ******               |
Position 0                 Position 39

Portanto, para o quadro 10, teremos:

                           and Co
                        es       de 
                      zl            Go
                     z                l
                    u                  f
T                   P                  !
 h                                       
  is             ng                       Ya
     is       mmi                           y!
        Progra

Notas

  • A entrada será uma única string(ou charmatriz, o que for) com o texto a ser animado e sempre terá pelo menos 1 caractere.
  • Caracteres válidos para animar são aqueles no conjunto ASCII imprimível .
  • O caminho a seguir será exatamente como indicado.
  • O texto sempre será alinhado à esquerda da tela; portanto, o efeito será o texto acenando como uma bandeira, sem deslocamento de texto. E por tela quero dizer tela ou o que você usar para mostrar o texto . ;-)
  • Os quadros devem estar livres de caracteres / pixels dos quadros anteriores, a menos que o caractere / pixel seja o mesmo nos dois quadros.
  • A velocidade da animação não importa desde que funcione sem problemas ou o mais rápido que o seu dispositivo puder (podemos definir um mínimo de 5 qps, mas isso não é um requisito). Basta ajustar a velocidade para torná-la fluida e não se preocupe se o tempo de espera não for exatamente o mesmo.
  • A animação será repetida sem parar.

Isso é , então o programa ou função mais curto capaz de animar o texto ganha!

Charlie
fonte
Relacionado . Sandbox .
Charlie
1
Posso contar 38 colunas em vez de 40.
Arnauld
1
@ Arnauld é porque o que conta são as posições no caminho, não as colunas.
Charlie
Ah eu vejo. Faz sentido.
Arnauld
Isso está bom como saída? Ele exibe a entrada na forma de uma onda sinusoidal e faz loop infinitamente. Obviamente, como o vídeo está no formato Graphics Interchange, é mais rápido na realidade.
R. Kap

Respostas:

9

HTML + ES6, 241 244 237 bytes

Demolir:

  • HTML: 16 bytes
  • Função JS: 221 bytes

let f =

s=>setInterval(_=>o.innerHTML=[...s].map((c,i)=>([j,y]=[...Array(40)].map((_,k)=>[j=k%20,y,y+=77732>>j&1?k<20||-1:0],y=0)[(i+p)%40],a[y]=a[y]||[...s].fill` `)[x]=(x+=j!=9,c),x=0,a=[],p++)&&a.map(r=>r.join``).join`
`,p=30)

f("This is Programming Puzzles and Code Golf! Yay!")
<pre id=o></pre>

Quão?

Construindo o caminho

O código a seguir cria o caminho:

[...Array(40)].map((_, k) =>
  [
    j = k % 20,
    y,
    y += 77732 >> j & 1 ? k < 20 || -1 : 0
  ],
  y = 0
)

Isso fornece uma matriz de matrizes [j, y, z]onde j é o módulo de posição 20, y é a coordenada y nessa posição e z não é usado mais tarde (apenas é calculado aqui para salvar alguns bytes).

Como o caminho é simétrico, precisamos codificar apenas as 20 primeiras posições. Fazemos isso usando um número binário em que cada 1bit significa que y deve ser atualizado (+1 na primeira metade, -1 na segunda metade).

001
   001
      01
        1
         1
         1
          1
           01
             001
                000

Com a primeira posição sendo mapeada para o bit menos significativo, isso fornece:

00010010111110100100 as binary = 77732 as decimal

Como esse número binário também é simétrico, podemos lê-lo na mesma ordem para a segunda metade.

Daí a fórmula:

y += (77732 >> j) & 1 ? (k < 20 ? 1 : -1) : 0

que também pode ser escrito como:

y += (77732 >> j) & 1 ? k < 20 || -1 : 0

onde k é a posição e j é o módulo de posição 20.

A atualização de x é muito mais fácil: temos apenas um caso especial a ser detectado comparando o módulo 20 da posição com 9.

Desenhando o texto

No código a seguir, o código de construção do caminho descrito acima foi substituído por uma pathvariável para facilitar a leitura.

s => setInterval(                       // set a periodic timer:
  _ =>                                  //   with an anonymous callback
    o.innerHTML =                       //   which updates the content of 'o'
      [...s].map((c, i) => (            //   for each character c of the string s
          [j, y] = path[(i + p) % 40],  //     retrieve j and y from the path
          a[y] = a[y] || [...s].fill` ` //     initialize a[y] if it's undefined
        )[x] = (x += j! = 9, c),        //     update a[y][x] / update x
        x = 0,                          //     initialize x
        a = [],                         //     initialize a[]
        p++                             //     increment the phase
      ) &&                              //   end of map()
      a.map(r => r.join``).join`\n`,    //   join everything together
  p = 30                                //   initial phase = interval period = 30
)                                       // end of timer definition
Arnauld
fonte
Isso é brilhante e bem próximo do caminho solicitado, mas o caminho não é exatamente o que eu desenhei. As duas colunas que contêm duas *na vertical não estão alinhadas (na mesma altura) e outro pequeno nitpick. Mas devo dizer que ainda não sei como seu código cria o efeito de onda (o que y+=155464faz?). Parabéns!
Charlie
@CarlosAlejo Acho que agora o caminho deve ser corrigido. Você poderia verificar duas vezes? Vou adicionar uma explicação do método usado.
Arnauld
1
Caminho verificado, e muito obrigado pela explicação!
Charlie