Quão bonito é este mundoconsole.log () é um bom método para gerar informações de depuração no console da web. Você pode exibir números, seqüências de caracteres, matrizes, objetos, funções, texto sem formatação e, além disso, você pode adicionar formatação, cor, plano de fundo e muitos outros estilos a tudo isso ... É tudo? É só isso que todo esse método pode fazer? Bem ... E a implementação no console de um simples jogo de plataformas, algoritmo de fundição de Ray ou física de tecidos?
Para quem veio aqui apenas para ver, deixarei um link para a demo logo no início:
Github: githubExemplo ao vivo: demonstraçãoAbra a página, pressione F12, amplie o console e vá para a demonstração que lhe interessa. E é claro que você precisa se concentrar na página para poder controlar a imagem no console.
É aconselhável executar no chrome, mas pode haver um caso em que os caracteres usados para exibir a imagem não sejam suportados e exibidos como quadrados. Como opção, faça o download do código e altere os caracteres de saída para outros.
E agora um pouco mais sobre isso
Console como uma tela
Vejamos o método console.log () e o console como um todo, não como uma ferramenta de depuração, mas como uma tela. Sim, como um lugar onde podemos desenhar um pouco e até fazê-lo se mover. Além disso, ninguém cancelou caracteres Unicode.
Eu implementei métodos para "desenhar" no console, como métodos para trabalhar com canvas. Mas comparado a uma tela real, a saída para o console e, além disso, seu redesenho impõe grandes limitações, que infelizmente não podem ser contornadas (pelo menos eu acho que sim). Sobre eles em ordem.
Tamanho do pixel
Ao desenhar na tela, estamos lidando com pixels, que têm tamanho, atenção, pixel no monitor! Quando exibido no console, o "pixel" assume um conceito ligeiramente diferente. Sim, no que diz respeito ao console, esse é seu pixel peculiar, mas no que diz respeito ao pixel real, é apenas um caractere especial, por exemplo? Mas há uma pequena restrição nos caracteres, ou melhor, uma recomendação: ele deve ter uma altura igual à altura da quebra de linha no console. Mas isso é apenas se queremos obter uma imagem bonita (o máximo possível).
Redesenhar
Esse é o principal problema, pois O console não foi projetado para atualizar dados com freqüência. Nós os trazemos para lá, deduzimos e deduzimos. Existe console.clear () que limpa, mas acho que é usado muito raramente. Ah, sim, mas não no meu caso, onde tudo se baseia no fato de que você precisa limpá-lo constantemente e exibir o texto novamente. Aqui está apenas um console.clear () causa sua sobrecarga completa, que é acompanhada por um milissegundo piscando. E se você precisar redesenhar constantemente com alguma frequência, é melhor que as pessoas com hipersensibilidade não olhem para ela. Mas, infelizmente, nada pode ser feito sobre isso.
Cor
Como escrevi no começo, a formatação pode ser aplicada à saída, mas no meu caso, decidi não me contentar com uma imagem em preto e branco, que pode ser alcançada graças a uma ampla seleção de caracteres Unicode.
Limitações mais detalhadas podem ser vistas em exemplos ao vivo, nos links que deixei no final do artigo. Você já pode se familiarizar com eles, mas por enquanto vou descrever o próprio processo de desenho. Tudo isso eu criei em uma pequena biblioteca com a qual no final eu implemento jogos simples e o algoritmo Raycasting
Desenhamos no console
Adotei os nomes dos métodos, bem como o processo de desenho da tela (mais adiante descreverei o porquê) e foi isso que acabou
engine.jsconst canvas = { width: 70, height: 40, getContext(type) { if (type != '2d') { return console.log('Only 2d'); } return new Context2D(type); } } class Context2D { constructor(type) { this.fillStyle = '?'; this.emptyStyle = '?'; this.map = []; for (let i = 0; i < canvas.height; i++) { this.map[i] = []; for (let j = 0; j < canvas.width; j++) { this.map[i][j] = this.emptyStyle; } } this.path = []; this.clear(); } fillRect(x, y, width, height) { for (let i = y; i < y + height; i++) { for (let j = x; j < x + width; j++) { if (!this.map[i]) break; this.map[i][j] = this.fillStyle; } } this.draw(); } strokeRect(x, y, width, height) { for (let j = x; j < x + width; j++) { this.map[y][j] = this.fillStyle; this.map[y + height - 1][j] = this.fillStyle; } for (let i = y + 1; i < y + height - 1; i++) { this.map[i][x] = this.fillStyle; this.map[i][x + width - 1] = this.fillStyle; } this.draw(); } clearRect(x, y, width, height) { for (let i = y; i < y + height; i++) { for (let j = x; j < x + width; j++) { this.map[i][j] = this.emptyStyle; } } this.draw(); } beginPath() { this.path = []; } moveTo(x, y) { this.path.push([Math.round(x), Math.round(y), true]); } lineTo(x, y) { this.path.push([Math.round(x), Math.round(y)]); } closePath() { if (!this.path.length) return false this.path.push([this.path[0][0], this.path[0][1]]); } stroke() { const path = this.path; for (let i = 0; i < path.length - 1; i++) { const x0 = path[i][0]; const y0 = path[i][1]; const x1 = path[i+1][0]; const y1 = path[i+1][1]; this.fillPixel(x1, y1); if (path[i+1][2]) continue; const deltaX = Math.abs(x1 - x0); const deltaY = Math.abs(y1 - y0); const signX = x0 < x1 ? 1 : -1; const signY = y0 < y1 ? 1 : -1; let error = deltaX - deltaY; let x = x0; let y = y0; while(x !== x1 || y !== y1) { this.fillPixel(x, y) const error2 = error * 2; if (error2 > -deltaY) { error -= deltaY; x += signX; } if (error2 < deltaX) { error += deltaX; y += signY; } } } this.draw(); } fillPixel(x, y) { if (!this.map[y]) return false; this.map[y][x] = this.fillStyle; } arc(x1, y1, r) { let x = 0; let y = r; let delta = 1 - 2 * r; let error = 0; while (y >= 0) { this.moveTo(x1 + x, y1 + y); this.moveTo(x1 + x, y1 - y); this.moveTo(x1 - x, y1 + y); this.moveTo(x1 - x, y1 - y); error = 2 * (delta + y) - 1; if (delta < 0 && error <= 0) { delta += 2 * ++x + 1; continue; } if (delta > 0 && error > 0) { delta -= 2 * --y + 1; continue; } delta += 2 * (++x - y--); } this.draw() } draw() { this.clear();
Agora, incluímos esse arquivo no arquivo html, abrimos o console e podemos tentar vários métodos
canvas.width = 70 canvas.height = 30 const ctx = canvas.getContext('2d') ctx.beginPath() ctx.moveTo(30, 5) ctx.lineTo(30, 25) ctx.moveTo(30, 15) ctx.lineTo(35, 13) ctx.lineTo(38, 13) ctx.lineTo(40, 16) ctx.lineTo(40, 25) ctx.stroke()
Aqui está o resultado

A saída é uma imagem que eu tinha concebido, tudo é desenhado em coordenadas e por analogia com a tela.
Exemplos legais
Meus planos eram possibilitar a transferência de um jogo de tela regular para um jogo no console o mais simples possível. Para isso, implementei os mesmos métodos com alterações mínimas. O que isso significa? E o fato de que, para a implementação de qualquer jogo, eu apenas uso o canvas pronto, corrijo algumas linhas de código e ele começa no console!
Na verdade, foi isso que eu fiz. E a primeira coisa que me ocorreu (exceto o pequeno quadrado que pode ser movido pelo console) foi implementar o algoritmo Raycasting
Eu não escrevi o algoritmo propriamente dito, mas simplesmente o emprestei
aqui e, depois de alterar várias linhas, o iniciei no console.

Parece impressionante, não é?
Aqui estão mais algumas capturas de tela do que eu transferi para o console.
Cobra
Física do tecido que pode ser torcido e até rasgadoNovamente, a implementação dessa física de cobras e tecidos não é minha, apenas a adaptei para o console. Nos arquivos de origem, deixei links para as fontes originais.