chester's blog

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

Sorteio2600

| Comments

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.

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