chester's blog

technology, travel, comics, books, math, web, software and random thoughts

sorteio2600

07 Nov 2011

O sorteio2600 é um programa que eu fiz para o Dev In Vale, por sugestão do Willian Fernandes, um dos organizadores do evento. Já que eu já ia demonstrar o Hello World em um Atari de verdade (graças ao Harmony), e a minha palestra era a última, a idéia era aproveitar o setup e fazer o sorteio dos brindes no próprio Atari.

Pensei que seria uma tarefa fácil, mesmo numa plataforma tão limitada, mas levei umas vinte horas ou mais quebrando a cabeça para que o programa saísse! Este post comenta o processo de desenvolvimento e no final mostra um vídeo do software rodando no Atari. É recomendável ver os slides ou assistir à palestra para entender melhor, mas talvez dê pra acompanhar sem isso.

Dígitos na tela ⇒ Python ao resgate

O ponto de partida foi o Hello World da palestra (que escreve uma frase na tela usando o playfield, isto é, os 20 “pixels largos” que ocupam a parte esquerda da tela do Atari, repetidos no lado direito). Usando o score mode, é possível esconder um dos lados (colocando nele a mesma cor do fundo), e usando 6 pixels por dígito seria possível representar os três dígitos necessários para escrever um número entre 000 e 999.

O problema com essa idéia é que dígitos não se alinham com os registradores do playfield (PF0, PF1 e PF2). Veja na ilustração como eles precisam ser posicionados:

Dígitos nos registradores do playfield do Atari

Seria necessário fazer vários shifts para combinar os dígitos nos registradores, e, além disso, temos o fato de que o PF0 e o PF2 são lidos “ao contrário” (com o bit menos significativo à esquerda). Era muita manipulação pra fazer on-the-fly, então resolvi que, ao invés de uma única tabela de bitmaps dos dígitos, eu teria duas tabelas para o dígito das centenas (uma para o PF0 e outra para o PF1), duas para o das dezenas (uma para o PF1 e outra para o PF2) e uma para a unidade (que fica inteira no PF2).

As tabelas já teriam as inversões e shifts pré-executados, tornando a operação de escrever o PF1 e o PF2 tão “simples” quanto ler um valor, combiná-lo com outro (via operação OR ou soma) e armazenar. O PF0 teria só uma leitura e armazenamento, e isso tudo poderia ser feito na scanline enquanto o canhão está do lado esquerdo da tela (que não seria usado de qualquer forma, já que eu queria os dígitos no lado direito).

Calcular essas tabelas de bits manualmente seria um porre, mas eu não precisaria fazer isso, já que tenho um computador: fiz um script Python que lê os bimaps dos caracteres “0″ a “9″ e gera o .asm com as tabelas descritas acima (recorrendo novamente à fonte do ZX Spectrum, que já tinha transcrita em hexadecimal no tilewriter).

Score Mode

Teria sido um plano perfeito, não fosse por um detalhe: o score mode foi feito para desenhar placares, não para dividir perfeitamente a tela. No emulador tudo funcionava bem, mas no Atari de verdade o TIA muda a cor um pouco antes do esperado, fazendo com que um pedaço do playfield esquerdo apareça onde não deveria.

Se eu soubesse disso antes, teria usado menos bits, mas com o programa já escrevendo os caracteres na tela, apelei: estiquei o missile 0 (que já tinha a mesma cor que o fundo da tela) e posicionei de forma a cobrir a parte da tela onde aparece o lixo. Não é a solução mais limpinha do mundo, mas funcionou bem.

Etapas do sorteio

Com tudo isso eu já tinha um programa que escrevia dígitos arbitrariamente na tela. A partir daí, se eu incrementasse o número a cada scanline não utilizada, eu teria eles mudando na tela em alta velocidade, dando um efeito de sorteio. Incrementando a cada frame (ou a cada n frames) eu podia reduzir a velocidade, o que me levou a dividr o programa em quatro “modos”:

  • Modo select: Mostra os dígitos correspondentes ao limite máximo do sorteio. Pressionar GAME SELECT em qualquer outro modo muda para este, e pressionar novamente GAME SELECT incrementa o limite máximo (de 100 em 100).
  • Modo rodando: Acionado pelo GAME RESET, este modo incrementa o número a cada scanline, conforme descrito acima. Aqui surgiu um dos bugs mais difíceis de encontrar: eventualmente eu usava duas scanlines (por conta do “vai um”) sem contabilizar, gerando uma tela com scanlines a mais. Isso fazia o vídeo tremer, por perder a sincronia.
  • Modo parando: Passsa a valer se o jogo estiver no modo rodando e o usuário apertar o botão do joystick. Ele incrementa o número a cada n frames, começando em 2 e aumentando aos poucos. Com isso, os números vão avançando cada vez mais devagar por uns poucos segundos, simulando o efeito de uma roleta parando (confesso: a minha inspiração foi a memória do Roletrando nos anos 80!)
  • Modo parado: Quando o atraso de frames do modo parando chega a um limite, é hora de parar e declarar um número vencedor. Para isso o programa muda para este modo, permanecendo nele até um novo GAME RESET.</ul>

Números (pseudo-)aleatórios

Mesmo implementando todos esses modos, ainda havia um problema: a pseudo-aleatoriedade seria garantida pelo fato de um tempo indeterminado se passar entre o GAME RESET e o botão do joystick ser pressionado, e neste tempo incrementarmos o número sorteado a cada scanline fora do desenho. Isso tem um problema: se temos k scanlines livres na tela, o sorteio avança de k em k números, e dependendo dos divisores comuns entre este número e o limite superior do sorteio, o mesmo ficaria limitado a um subconjunto de números.

Como o limite superior varia, o melhor jeito de evitar o problema era inicializar o contador com um número pseudo-aleatório “de verdade”. Para isso, a solução foi introduzir um contador de um byte na RAM, incrementado a cada frame enquanto o console estiver ligado, e usá-lo para inicializar a dezena e unidade do número sorteado. Um truque bacana foi fazer este incremento usando o modo decimal, ou modo BCD do 6502. Com isso, cada nibble do contador contém um dígito de 0 a 9, e pode ser usado diretamente para inicializar a dezena ou a unidade.

Som

Com isso, faltava adicionar um efeito sonoro que complementasse o sentimento de “roleta”. Não falei sobre som na palestra, mas a teoria é simples: o TIA tem dois circuitos de som independentes, e pra cada um você pode escolher o volume, a frequência e o tipo de som, escrevendo nos registradores apropriados.

É preciso usar alguns dos minguados ciclos para gerenciar isso, e a produção de músicas é dificultada pelo fato de as frequências não baterem com uma oitava musical “tradicional”. Mas para esse programa não foi um problema: fiz um tom curto e grave ser acionado a cada frame em que ocorre uma mudança de dígitos. O resultado é um ronco de motor meio Endurozento no modo rodando e um tuc-tuc-tuc no modo parando, o que serviu bem ao propósito.

Cores e o resultado final

O toque final foi acrescentar cores: deixei uma diferenciada para o modo select, e no modo parado eu reutilizei o contador de frames para mudar as cores constantemente (se não fosse o BCD, creio que ficaria parecido com o cálice do Adventure). Isso deixou o programa pronto para usar, e o pessoal do evento curtiu o sorteio feito de forma tão inusitada. Veja ele em ação:

Normalmente eu programo com um pouco mais de planejamento, mas no Atari as limitações do hardware fazem com que qualquer coisa além de uma idéia inicial viável tenha que ser decidida conforme os recursos vão permitindo. Foi assim que David Crane criou o Pitfall! – ele tinha um esquema pra fazer um homenzinho correndo, e queria bolar um jogo usando isso:

Eu sentei com uma folha de papel em branco e desenhei um homenzinho no meio. E disse, “Ok, eu tenho um homenzinho que corre, vamos colocá-lo em um caminho [mais duas linhas desenhadas no papel]. Onde fica esse caminho? Vamos colocá-lo em uma selva [desenho umas árvores]. Por que ele está correndo [desenho tesouros a coletar, inimigos a evitar, etc.]?” E o Pitfall! nasceu. Todo esse processo levou uns dez minutos. Cerca de 1000 horas de programação depois, o jogo estava pronto.

David Crane, em entrevista de 2003

Comments


Leonardo Santagada

Leonardo Santagada

Cara não sei nem o q dizer, que genial. Eu coloquei o livro do ian bogost na minha lista de leituras futuras, mas tenho certeza q nunca vou ter tempo de aprender a programar pro atari.

chester

Olha, eu também achava que nunca ia ter tempo. O lance é atacar aos poucos: tirando o Assembly 6502 (que eu vi nos tempos de Apple II) eu só comecei a fuçar em 2000, e não escrevi nada prático antes de, sei lá, 2002 ou algo assim (quando eu comecei a fazer o primeiro programa que desenhava algo na tela).

De tempos em tempos eu esquecia e retomava o assunto, e a cada vez eu ia aprendendo mais. Uma hora o pessoal do Dev In Sampa, ao ouvir no café a história que eu contava sobre o artigo no Fliperama (http://bit.ly/rExuGs), me desafiou a fazer a palestra, e isso deu um novo gás na pesquisa.

Não foi naquela edição (2010) que eu apresentei, mas na seguinte. E com o Dev In Vale veio o novo desafio, que levou ao programa mais consistente que aparece aí em cima.

Moral da história: vai aos poucos. Eu levei em 10 anos, e sequer completei um jogo propriamente dito! :-)


Edinei

Fala Chester, kra primeiramente parabenizar pelo trabalho.

Estava lá no Dev in Vale e confesso que a palestra sobre o Atari era a mais ansiosa, realmente deve ter sido um desafio e tanto visto a complexidade de se programar para o Atari.

Mas muito legal poder conhecer os truques e genialidade dos programadores da época, podemos afirmar que o programador do jogo "HERO" foi muito ninja, pois o gráfico e o jogo são muito bons.

Abs

chester

Obrigado pela força e pela presença! O H.E.R.O. é de uma geração de jogos mais nova, pois, até onde sei, usa bank-switching para permitir que o jogo disponha de "espaçosos" 8KB no lugar dos 4KB tradicionais.

Isso introduz uma nova dimensão de problemas, mas permite que o jogo tenha a infinidade de telas que possui, com variações bem mais refinadas (as paredes, lâmpadas, morcegos e outros elementos podem estar em praticamente qualquer lugar de cada tela).

O resultado é um jogo refinado e divertidíssimo, um dos meus top 5 tanto da infância quanto de agora.