MSXInício
Capítulo 18Disponível

Pensando em programas maiores

Você já tem todas as ferramentas básicas. Vamos falar de como combiná-las com elegância.

O esqueleto típico de um jogo MSX

Quase todo jogo segue o mesmo padrão: inicialização, laço principal e tratamento de fim. Em CSX, fica assim:

USES SCREEN 2, SPRITES, KEYBOARD, MUSIC
SCREEN 2 : CLS

' === Inicialização (uma vez) ===
DIM jogador_x AS BYTE = 120
DIM jogador_y AS BYTE = 90
DIM vidas AS BYTE = 3
DIM pontos AS WORD = 0
DIM jogo_rodando AS BOOL = TRUE

' cada parte do jogo é um procedimento (capítulo 8);
' aqui elas estão vazias, para você preencher
PROCEDURE PrepararSprites()
BEGIN
END
PROCEDURE DesenharCenario()
BEGIN
END
PROCEDURE LerControles()
BEGIN
END
PROCEDURE AtualizarJogador()
BEGIN
END
PROCEDURE AtualizarInimigos()
BEGIN
END
PROCEDURE VerificarColisoes()
BEGIN
END
PROCEDURE DesenharFrame()
BEGIN
  jogo_rodando = STRIG(0) = 0
END
PROCEDURE MostrarGameOver()
BEGIN
  PRINT "FIM DE JOGO"
END

PrepararSprites()
DesenharCenario()

' === Laço principal ===
WHILE jogo_rodando
    LerControles()
    AtualizarJogador()
    AtualizarInimigos()
    VerificarColisoes()
    DesenharFrame()
    DELAY 1
WEND

' === Fim ===
STOPMUSIC
MostrarGameOver()

Cada nome em maiúscula camelo ali em cima é um procedimento que você ainda vai escrever. Veja como o programa principal ficou curto e legível mesmo sem saber o que cada procedimento faz por dentro. Essa é a magia da abstração.

Boas práticas que poupam horas de depuração

  • Nomes claros sempre. velocidadeDoProjetil vence v2 em qualquer dia da semana.
  • Procedimentos curtos: idealmente cabem em uma tela. Se não cabe, divida.
  • CONST para números mágicos: em vez de espalhar 192 pelo código, defina CONST ALTURA_TELA = 192 uma vez.
  • Comentários explicam o porquê, não o quê. O código já diz o quê.
  • Teste cedo, teste sempre. Compile e rode a cada poucas linhas.

Limites que você precisa conhecer

Procedimentos e funções em CrossScript são rápidos porque não montam uma pilha de variáveis a cada chamada, como fazem as linguagens de computadores maiores. Essa economia tem duas consequências práticas:

  • Sem recursão. Um procedimento não pode chamar a si mesmo, nem de forma indireta (A chama B, que chama A). As variáveis dele ocupam um lugar fixo na memória, e a segunda chamada apagaria os valores da primeira. Quando precisar repetir, use FOR ou WHILE.
  • DIM dentro de procedure é global. Uma variável declarada com DIM dentro de um procedimento vive na mesma área das variáveis do programa principal e continua existindo depois que ele termina. Use nomes que não se repitam, por exemplo com um prefixo: DIM pj_contador AS BYTE.

Parâmetros e o valor de RETURN funcionam normalmente; a limitação vale só para chamadas que se sobrepõem.

DICA

Recursão vira laço

Quase todo algoritmo recursivo dos livros (fatorial, preencher uma área, percorrer uma árvore) pode ser escrito com um laço e um array servindo de pilha. No Z80 essa versão, além de possível, costuma ser a mais rápida.

Onde tudo isso vai dar

Nos capítulos seguintes deste volume, você aprofunda tudo o que já foi visto e entra no MSX 2:

  • Trabalhar com strings (capítulo 9) — ler, guardar e mostrar texto dinâmico.
  • Sair do MSX 1 e entrar no SCREEN 5 / 7 / 8 do MSX 2 (capítulo 19).
  • Animar personagens grandes e coloridos sem flicker com BOBs (capítulo 20).
  • Carregar cenários prontos do PC com PAGELOAD + LZB (capítulo 21).
  • Deixar o Gerenciador de Memória cuidar dos 16 KB de cada página (capítulo 26).

CURIOSIDADE

O que você já sabe agora

Com o conteúdo deste volume você consegue escrever programas que: fazem cálculos numéricos, tomam decisões, repetem ações, organizam dados em arrays e separam responsabilidades em procedimentos. Isso é mais do que muitos programadores dos anos 80 sabiam quando começaram a escrever os clássicos do MSX. Bem-vindo ao clube.

Assembly Z80 dentro do seu programa

Você não está preso aos comandos do CrossScript. Em qualquer ponto do programa dá para abrir um bloco ASM ... END ASM e escrever instruções do Z80 diretamente. O compilador copia esse trecho, sem mexer, para o meio do código que ele mesmo gera — por isso ele roda na velocidade máxima da máquina.

Use quando precisar de algo que a linguagem ainda não oferece (uma rotina da BIOS, um registrador do VDP, um laço que precisa ser muito rápido).

DIGITE E VEJA

A borda que muda de cor

USES SCREEN 1
DIM i AS BYTE
SCREEN 1
PRINT "ASSEMBLY DENTRO DO CSX"
FOR i = 1 TO 15
  ASM
        LD      A, ($F3EB)   ; BDRCLR: cor da borda
        INC     A
        AND     15           ; mantém entre 0 e 15
        LD      ($F3EB), A
        CALL    $0062        ; CHGCLR: aplica as cores
        CALL    $00C0        ; BEEP
  END ASM
  PRINT PEEK($F3EB)
  WAIT
NEXT i

TELA ESPERADA

O que acontece

A cada volta a borda troca de cor, o MSX dá um bipe e a tela mostra o número da cor atual (1, 2, 3 ... 15).

Linha por linha

  • LD A, ($F3EB) lê a variável de sistema BDRCLR, onde o MSX guarda a cor da borda.
  • INC A / AND 15 somam 1 e fazem a conta voltar a 0 depois de 15.
  • LD ($F3EB), A guarda a nova cor.
  • CALL $0062 chama a rotina CHGCLR da BIOS, que aplica as cores guardadas.
  • CALL $00C0 chama BEEP.
  • De volta ao CSX, PEEK($F3EB) lê o mesmo endereço. É assim que o Assembly e o CSX trocam informação: pelos endereços de memória.

Regras importantes

  • As linhas de instrução precisam de recuo; um rótulo (MEU_LACO:) começa na primeira coluna.
  • O compilador já conhece alguns nomes da BIOS: WRTVDP, CHGMOD, LDIRVM, FILVRM, RDVDP, SNSMAT, DISSCR e ENASCR. Os outros você chama pelo endereço.
  • Dentro do bloco, escreva @nome para usar o endereço de uma variável do CSX: LD A, (@vidas) lê a variável vidas, e LD HL, @mapa aponta para o início do array mapa. A variável precisa ter um DIM antes do bloco. O exemplo Comboio Espacial (MSX 1) desenha a tela inteira assim, num bloco ASM, e roda a 60 quadros por segundo.
  • O seu programa fica no cartucho, que é só leitura. Um DB dentro do bloco serve para tabelas fixas; se você gravar nele, nada acontece. Para guardar valores, use RAM (variáveis de sistema ou o que o CSX reservou).
  • Se você usar DI, devolva com EI. Se usar a pilha (PUSH), equilibre com POP antes do END ASM.
  • Um bloco com apenas dados (DB, DW, DS) é pulado automaticamente: o processador nunca tenta executar seus bytes.

DICA

Veja o resultado

Depois de compilar, abra a aba Assembly (Z80): o seu bloco aparece exatamente onde você o escreveu, no meio do código gerado. O capítulo 32 usa um bloco ASM de verdade para ligar o vídeo invertido do modo 80 colunas.

ATENÇÃO

Com cuidado

O compilador não confere o que você escreve no bloco. Uma instrução errada pode travar o MSX — se isso acontecer, desligue e ligue o emulador e revise o trecho.

Partida demonstração: um piloto automático (IA) em CSX

Muitos jogos de fliperama e de MSX tocam uma partida DEMO quando ninguém aperta nada na tela de título. O exemplo Sample › Jogo: Comboio Espacial (MSX 1) faz isso: se o jogador ficar uns 8 segundos parado na abertura, o jogo começa sozinho, com a palavra DEMO no canto, e um "piloto" controla a mira. Apertar espaço encerra a demo e volta à abertura.

A ideia que torna isso fácil: o jogo nunca lê o joystick direto. Ele lê duas variáveis — d (direção, no mesmo formato de STICK, 0 a 8) e fg (tiro). Quem preenche essas variáveis é o jogador ou o piloto:

IF demo = 1 THEN GOSUB Piloto ELSE d = STICK(0)
...
IF demo = 1 THEN fg = af ELSE fg = STRIG(0)

O resto do jogo (rolagem, tiro, explosão, captura, fases) é exatamente o mesmo nos dois casos.

O que o piloto pensa

O piloto é uma sub-rotina comum, com três passos — o mesmo raciocínio de quem joga:

  1. Escolher o alvo. Entre os raptors ativos, pega o que está mais perto da nave que ele persegue (o mais perigoso). A distância é |dx| + |dy| em casas — conta barata, sem multiplicação.
  2. Mirar. Compara a posição do raptor na tela com o centro (onde fica a mira). Se ele está à direita, "empurra" o controle para a direita; acima, para cima. Uma folga de 4 pontos evita que a mira fique tremendo.
  3. Atirar. Quando o raptor entra na janela da mira, liga af = 1.
Piloto:
  d = 0 : af = 0 : c = 255 : l = 255
  FOR j = 0 TO 2
    IF RS(j) = 1 THEN
      alvo = RT(j)
      tx = EX(j) / 8 : ty = EY(j) / 8
      IF tx > CX(alvo) THEN k = tx - CX(alvo) ELSE k = CX(alvo) - tx
      IF ty > CY(alvo) THEN v = ty - CY(alvo) ELSE v = CY(alvo) - ty
      IF k + v < c THEN c = k + v : l = j
    ENDIF
  NEXT j
  IF l = 255 THEN RETURN
  j = l : GOSUB PosR            ' sx, sy = raptor na tela
  mx = 0 : my = 0
  IF sx > 132 THEN mx = 1
  IF sx < 124 THEN mx = 2
  IF sy > 91 THEN my = 1
  IF sy < 83 THEN my = 2
  ' ... converte (mx, my) em d = 1..8, como o STICK
  IF sx >= 120 AND sx <= 136 AND sy >= 79 AND sy <= 95 THEN af = 1
RETURN

DICA

Uma IA não precisa ser perfeita

O piloto erra: o anel leva 12 quadros para fechar e o raptor anda nesse meio-tempo. Isso é bom — uma demo que nunca perde não parece humana. Para deixá-lo melhor, tente "adiantar" a mira para onde o raptor vai estar; para deixá-lo pior, faça-o reagir só a cada 2 ou 3 quadros.

CURIOSIDADE

A demo não mostra o jogo inteiro

A demo termina logo antes do fim do primeiro estágio (IF demo = 1 AND dist = 5001 THEN GOTO Titulo) e volta para a abertura. Assim ela mostra como se joga sem revelar as fases seguintes nem a chegada a Marte.

Comunidade

Discussão · Pensando em programas maiores

Curta, marque como lido e comente o que achou deste capítulo.

0 comentários
Entre para participar
Crie uma conta ou faça login para curtir, marcar como lido e comentar.

Carregando comentários…