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.
velocidadeDoProjetilvencev2em 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 = 192uma 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
FORouWHILE. DIMdentro de procedure é global. Uma variável declarada comDIMdentro 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 15somam 1 e fazem a conta voltar a 0 depois de 15.LD ($F3EB), Aguarda a nova cor.CALL $0062chama a rotina CHGCLR da BIOS, que aplica as cores guardadas.CALL $00C0chama 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,DISSCReENASCR. Os outros você chama pelo endereço. - Dentro do bloco, escreva
@nomepara usar o endereço de uma variável do CSX:LD A, (@vidas)lê a variávelvidas, eLD HL, @mapaaponta para o início do arraymapa. A variável precisa ter umDIMantes 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
DBdentro 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 comEI. Se usar a pilha (PUSH), equilibre comPOPantes doEND 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:
- 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. - 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.
- 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.