Invasion (ex Defender) in NTC BASIC

Invasion è stato convertito a ritroso (backporting) dalla mia versione per ZX Spectrum Next di Defender prima di cambiarne il titolo in Invasion. La versione iniziale di Defender era ok, pensavo: «Mmh, il CPC che fa girare BASIC compilato… è più o meno quello che ci si aspetta», ma… provando di nuovo Invasion su ZX Spectrum Next dopo aver sviluppato Cyborg Hellgate, ho notato uno strano comportamento: più nemici eliminavo, più il gioco diventava veloce… e il CPC ha ereditato lo stesso bug. Ora la versione per CPC (così come quella per ZX Spectrum Next) gira che è una meraviglia! È il miglior clone di Defender per CPC? Non saprei: ho ancora un debole per Defend or Die e, se non mi andasse in crash continuo a caso, per autenticità sceglierei proprio Defend or Die.

Ho rimosso l’assembly inline per rendere il codice più facile da seguire, ma in origine il programma era scritto al 100% in NTC BASIC e poi ottimizzato, il che si è ridotto per lo più al movimento degli oggetti, dato che il CPC non dispone di sprite hardware. Anche tutti gli sprite sono compilati.

COMMENTI in main.bas

' ============================================================================
' INVASION - Edizione Amstrad CPC (mode 0, sprite software)
' ============================================================================
' NTC BASIC. Il gioco della versione ZX Spectrum Next, tale e quale, su CPC: il suo mondo, i suoi lander,
' mutanti, umani e scanner, i suoi suoni e gameplay:
'   - i 16 pattern di sprite (due frame per la nave, lander, mutanti e umani, un colpo
'     nemico, un'esplosione, il baiter) disegnati dal programma (Cpc.cls) dalla grafica di INVASION.SPR
'     convertita in mode 0 (CpcData.cls, tramite tools/cpcgfx.py): 8 x 16 pixel in mode 0 ciascuno, la
'     stessa dimensione a schermo
'   - stelle sullo sfondo che scorrono più lentamente del terreno, un paesaggio di 1024 pixel, lo scanner
'     che mostra a che punto si trova la schermata (lo schermo mostra 320 pixel del mondo: un pixel in mode 0 corrisponde a
'     due del Next)
'   - suoni per ogni cosa sul chip AY (Sfx.cls): laser, esplosioni, rapimenti, salvataggi, la
'     smart bomb, l'iperspazio, la spinta (thrust)
'   - perdere una nave non fa ripartire l'ondata; un momento di invulnerabilità subito dopo; la nave vira
'     in modo fluido; gli umani camminano, cadono (da troppo in alto muoiono), vengono catturati e messi a terra (500 ciascuno);
'     il baiter arriva se l'ondata si protrae a lungo; una nave e una smart bomb extra ogni 10.000 punti;
'     gli umani e il pianeta ritornano ogni cinque ondate
' Due schermi (#C000 e #0000; il programma è posizionato a partire da #4000); a ogni frame Cpc.Draw inserisce
' gli sprite, il terreno e le stelle su quello non visualizzato, Cpc.Flip lo mostra.
' Un CPC elabora un frame in circa 25-40 ms, e Cpc.Flip ne mostra uno costante ogni 2/50 di   ' <--- MODIFICATO
' secondo (il Next lo faceva cinquanta volte al secondo), quindi ogni frame qui corrisponde a due del Next:   ' <--- MODIFICATO
' le velocità sono raddoppiate e i tempi in frame dimezzati (l'audio viene aggiornato due volte a frame): il   ' <--- MODIFICATO
' ritmo del Next.   ' <--- MODIFICATO


2 Mi Piace

main.bas

Option Explicit
Option Turbo
Option Org &H4000                 ' il secondo schermo è #0000-#3FFF (Cpc.cls); il programma parte da #4000

' Costanti del Mondo e dello Schermo (la visuale: 320 x 192 pixel del Next, 160 x 192 modo 0; la y cresce verso l'alto)
Const WORLD_W As Integer = 1024
Const WORLD_MASK As Integer = 1023
Const VIEW_W As Integer = 320
Const PLAY_TOP As Integer = 30
Const PLAY_BOT As Integer = 146

' Indici dei Pattern degli Sprite (CpcData.cls, da INVASION.SPR)
Const PAT_PLAYER As Integer = 0
Const PAT_LANDER As Integer = 1
Const PAT_MUTANT As Integer = 2
Const PAT_HUMAN  As Integer = 3
Const PAT_BOLT   As Integer = 4
Const PAT_FRAME2 As Integer = 5      ' + questo: il secondo frame di 0-3 (nave, lander, mutante, umano): pattern 5-8
Const PAT_SHOT   As Integer = 9
Const PAT_BOOM   As Integer = 10     ' 10-13
Const PAT_BAITER As Integer = 14     ' 14-15

' Assegnazione degli Slot Sprite (Cpc.Spr)
Const SLOT_PLAYER As Integer = 0
Const SLOT_LASER0 As Integer = 1
Const SLOT_HUMAN0 As Integer = 5
Const SLOT_ENEMY0 As Integer = 13
Const SLOT_SHOT0  As Integer = 23
Const SLOT_BOOM0  As Integer = 29

' Conteggio delle Entità
Const MAX_LASERS As Integer = 4
Const MAX_HUMANS As Integer = 8
Const MAX_ENEMIES As Integer = 10
Const MAX_SHOTS As Integer = 6
Const MAX_BOOMS As Integer = 4

' Stati dei Nemici
Const ST_DEAD As Integer = 0
Const ST_WANDER As Integer = 1
Const ST_DESCEND As Integer = 2
Const ST_ABDUCT As Integer = 3
Const ST_MUTANT As Integer = 4
Const ST_BAITER As Integer = 5

Const H_DEAD As Integer = 0
Const H_GROUND As Integer = 1
Const H_GRABBED As Integer = 2
Const H_FALLING As Integer = 3
Const H_CARRIED As Integer = 4

' Valori restituiti da EnemyStep e HumanStep (Z80)
Const EV_GRABBED As Integer = 1      ' il lander ha catturato un umano
Const EV_TAKEN As Integer = 2        ' ne ha portato uno in cima: ora è un mutante
Const EV_LASERED As Integer = 3      ' colpito da un laser: il suo numero nei bit 4-5
Const EV_RAMMED As Integer = 4       ' ha speronato la nave
Const EV_SHOOT As Integer = 64       ' e spara (es_x: posizione orizzontale nella visuale)
Const EV_SETDOWN As Integer = 1      ' un umano trasportato viene posato a terra
Const EV_CAUGHT As Integer = 2       ' un umano che cade viene preso al volo
Const EV_FELL As Integer = 3         ' uno è caduto da troppo in alto
Const EV_LANDED As Integer = 4       ' uno è caduto ed è atterrato
Dim es_x As Integer

' Pennelli (Cpc.cls: 0 nero, 1 bianco, 2 grigio, 3 ciano, 4 azzurro, 5 blu, 6 giallo, 7 arancione,
' 8 rosso, 9 rosso scuro, 10 verde, 11 verde scuro, 12 lime, 13 viola, 14 rosa, 15 giallo pastello)
Const PEN_DIVIDER As Integer = 13
Const PEN_SCANNER As Integer = 10

' Stato del Giocatore
Dim px As Integer, py As Integer
Dim pvx As Integer, pvy As Integer
Dim pdir As Integer, psx As Integer, cam_x As Integer
Dim thrust As Integer, safeT As Integer, kb As Integer

Dim score As Long, nextBonus As Long
Dim lives As Integer
Dim bombs As Integer
Dim wave As Integer
Dim game_over As Boolean
Dim planet_dead As Boolean
Dim fire_cd As Integer
Dim bomb_cd As Integer
Dim hyper_cd As Integer
Dim flash_bg As Integer
Dim frames As Integer, waveT As Integer, baiterT As Integer, hudDirty As Integer
' frame corrente: frames And 1, Mod 3, (\ 4) And 1, (\ 6) And 1 (una divisione costa 1.100 cicli)
Dim f2 As Integer, f3 As Integer, f4 As Integer, f12 As Integer

' Laser
Dim lx(MAX_LASERS) As Integer
Dim ly(MAX_LASERS) As Integer
Dim lvx(MAX_LASERS) As Integer
Dim lscr(MAX_LASERS) As Integer, lasersOn As Integer
Dim llife(MAX_LASERS) As Integer

' Umanoidi
Dim hx(MAX_HUMANS) As Integer
Dim hy(MAX_HUMANS) As Integer
Dim hstate(MAX_HUMANS) As Integer
Dim hcarrier(MAX_HUMANS) As Integer
Dim hdir(MAX_HUMANS) As Integer
Dim hfall(MAX_HUMANS) As Integer, hvy(MAX_HUMANS) As Integer
Dim humans_alive As Integer

' Nemici
Dim ex(MAX_ENEMIES) As Integer
Dim ey(MAX_ENEMIES) As Integer
Dim evx(MAX_ENEMIES) As Integer
Dim evy(MAX_ENEMIES) As Integer
Dim estate(MAX_ENEMIES) As Integer
Dim etarget(MAX_ENEMIES) As Integer
Dim enemies_alive As Integer
Dim spawn_timer As Integer
Dim enemies_to_spawn As Integer

' Proiettili Nemici
Dim sx(MAX_SHOTS) As Integer
Dim sy(MAX_SHOTS) As Integer
Dim svx(MAX_SHOTS) As Integer
Dim svy(MAX_SHOTS) As Integer
Dim slife(MAX_SHOTS) As Integer

' Esplosioni (x e y nel mondo, frame rimanenti)
Dim bx(MAX_BOOMS) As Integer, by(MAX_BOOMS) As Integer, bt(MAX_BOOMS) As Integer

' Il paesaggio: un'altezza per ciascuno dei 4 pixel del mondo (256 in totale)
Dim land(255) As Byte
Dim terDead As Boolean

' Le istruzioni in stile CPC utilizzate dall'originale (Pen, At...)
Dim txtPen As Integer, atx As Integer, aty As Integer
Dim i As Integer, j As Integer, k As Integer, rs As Integer

' ============================================================================
' FUNZIONI DI SUPPORTO SPRITE (gli slot sprite hardware del Next, disegnati da Cpc.Draw)
' ============================================================================

' Gli slot sprite sono Cpc.Spr slot, x (0..319 nella visuale), y (0..191, verso l'alto), pattern, specchiamento,
' visibile e Cpc.Hide slot - chiamati direttamente (una Sub intermedia costa 100 cicli a chiamata)

' ============================================================================
' FUNZIONI DI SUPPORTO COORDINATE E TERRENO
' ============================================================================

' il paesaggio, generato una sola volta: colline e montagne che salgono e scendono (non più di 4 pixel
' ogni 4 orizzontali, in modo che la linea non abbia interruzioni)
Sub MakeLand()
    Dim h As Integer, t As Integer
    rs = 12345 : h = 14 : t = 14
    For k = 0 To 255
        If k Mod 16 = 0 Then t = 8 + Rand(23)
        If h < t Then h = h + 1 + Rand(3)
        If h > t Then h = h - 1 - Rand(3)
        If h < 8 Then h = 8
        If h > 32 Then h = 32
        land(k) = h
    Next k
    ' gli ultimi punti si ricollegano al primo, così da chiudersi a ciclo continuo
    For k = 248 To 255
        land(k) = land(247) + (land(0) - land(247)) * (k - 247) \ 9
    Next k
End Sub

' Rand(n): da 0 a n - 1 da rs, identico ogni volta con lo stesso seme (il paesaggio e le
' stelle sono generati da questo). Una sequenza a 16 bit: quella a 32 bit dell'originale richiedeva 10.000 cicli a volta
Function Rand(ByVal n As Integer) As Integer
    rs = (rs * 2053 + 13849) And &H7FFF
    Rand = (rs \ 32) Mod n
End Function

Function GetTerrainY(ByVal wx As Integer) As Integer
    If planet_dead Then GetTerrainY = 6 Else GetTerrainY = land(Cpc.Shr(wx, 2) And 255)
End Function

' Cpc.ScrX(wx, cx): posizione orizzontale di wx nel mondo all'interno della visuale da cx, da -512 a 511.
' Il radar: pixel del Next, 64 + ((wx - px + 512) And 1023) \ 8 all'interno del riquadro (la nave al
' centro), 153 + y \ 7 dal fondo: Cpc.RDot wx, px, y, pen disegna un punto lì
Function WorldToRadarX(ByVal wx As Integer) As Integer
    WorldToRadarX = 64 + Cpc.Shr((wx - px + 512) And WORLD_MASK, 3)
End Function

Dim hudScore As Long, hudLives As Integer, hudBombs As Integer, hudWave As Integer   ' <--- NUOVO (ciò che mostra l'HUD: viene stampato solo ciò che cambia)
Sub UpdateHUD()
    If score <> hudScore Then                                  ' <--- NUOVO
    Pen 15
    At 1, 1
    PrintS Right$("000000" & Mid$(Str$(score), 2), 7)
    hudScore = score                                           ' <--- NUOVO
    End If                                                     ' <--- NUOVO
    If lives <> hudLives Then                                  ' <--- NUOVO
    Pen 14
    At 10, 1
    PrintS "SHIPS" & Str$(lives) & " "
    hudLives = lives                                           ' <--- NUOVO
    End If                                                     ' <--- NUOVO
    If bombs <> hudBombs Then                                  ' <--- NUOVO
    Pen 12
    At 19, 1
    PrintS "BOMBS" & Str$(bombs) & " "
    hudBombs = bombs                                           ' <--- NUOVO
    End If                                                     ' <--- NUOVO
    If wave <> hudWave Then                                    ' <--- NUOVO
    Pen 11
    At 27, 1
    PrintS "WAVE" & Str$(wave) & " "
    hudWave = wave                                             ' <--- NUOVO
    End If                                                     ' <--- NUOVO
    hudDirty = 0
End Sub

Sub DrawStaticFrame()
    ClsAll
    hudScore = -1 : hudLives = -1 : hudBombs = -1 : hudWave = -1   ' <--- NUOVO (lo schermo è pulito: viene stampato l'intero HUD)
    ' Linea di divisione dello scanner (linea 43) e riquadro del radar (x 62..193 del Next, y 152..182 verso l'alto
    ' dal fondo: pixel CPC 31..96, linee 9..39)
    Cpc.HLine 0, 43, 160, PEN_DIVIDER
    Cpc.HLine 31, 9, 66, PEN_SCANNER
    Cpc.HLine 31, 39, 66, PEN_SCANNER
    For k = 10 To 38
        Cpc.HLine 31, k, 1, PEN_SCANNER : Cpc.HLine 96, k, 1, PEN_SCANNER
    Next k
    Stars
    Cpc.Ground 7
    UpdateHUD()
End Sub

' le stelle: sparse nel cielo (linee 46-155), disegnate da Cpc.Draw scorrendo a un quarto della
' velocità del terreno
Sub Stars()
    Dim x As Integer, y As Integer
    rs = 777
    For k = 0 To 31
        x = Rand(320) : y = 46 + Rand(110)
        Cpc.Star k, x, y, Val(Mid$("2211", (k Mod 4) + 1, 1))
    Next k
End Sub

' ============================================================================
' GESTIONE ONDATE ED ENTITÀ
' ============================================================================

Sub InitWave()
    Cpc.HideAll
    DrawStaticFrame()

    px = 512 : py = 96 : pvx = 0 : pvy = 0 : pdir = 1 : psx = 110
    safeT = 25

    For i = 0 To MAX_LASERS - 1
        llife(i) = 0
    Next i
    For i = 0 To MAX_SHOTS - 1
        slife(i) = 0
    Next i
    For i = 0 To MAX_BOOMS - 1
        bt(i) = 0
    Next i

    ' gli umani: tutti presenti all'inizio, e ripristinati ogni cinque ondate
    If wave = 1 Or (wave - 1) Mod 5 = 0 Then
        planet_dead = False
        humans_alive = MAX_HUMANS
        For i = 0 To MAX_HUMANS - 1
            hx(i) = (i * 128 + 48) And WORLD_MASK
            hstate(i) = H_GROUND
        Next i
    End If
    For i = 0 To MAX_HUMANS - 1
        hcarrier(i) = -1
        If hstate(i) <> H_DEAD Then
            hstate(i) = H_GROUND
            hy(i) = GetTerrainY(hx(i)) + 6
        End If
        If Rand(2) Then hdir(i) = 1 Else hdir(i) = -1
    Next i

    For i = 0 To MAX_ENEMIES - 1
        estate(i) = ST_DEAD
    Next i

    enemies_to_spawn = 10 + wave * 2
    If enemies_to_spawn > 24 Then enemies_to_spawn = 24
    enemies_alive = 0
    spawn_timer = 5
    waveT = 0 : baiterT = 0

    For i = 0 To 4
        SpawnEnemy()
    Next i
    UpdateHUD()
    Message "WAVE" & Str$(wave), 30
End Sub

Sub SpawnEnemy()
    If enemies_to_spawn <= 0 Then Exit Sub
    For i = 0 To MAX_ENEMIES - 1
        If estate(i) = ST_DEAD Then
            ex(i) = (px + 250 + Rnd(500)) And WORLD_MASK
            ey(i) = 110 + Rnd(30)
            If Rnd(2) = 0 Then evx(i) = 1 Else evx(i) = -1
            evy(i) = -1
            If planet_dead Then estate(i) = ST_MUTANT Else estate(i) = ST_WANDER
            etarget(i) = -1
            enemies_alive = enemies_alive + 1
            enemies_to_spawn = enemies_to_spawn - 1
            Exit Sub
        End If
    Next i
End Sub

' il baiter: un disco volante veloce che ti dà la caccia se un'ondata dura troppo a lungo
Sub SpawnBaiter()
    For i = 0 To MAX_ENEMIES - 1
        If estate(i) = ST_DEAD Then
            ex(i) = (px + 300) And WORLD_MASK
            ey(i) = py
            estate(i) = ST_BAITER
            evx(i) = 0 : evy(i) = 0
            etarget(i) = -1
            Sfx.Play 1, 40, 2, 13, 25, 0
            Exit Sub
        End If
    Next i
End Sub

Sub FireLaser()
    For i = 0 To MAX_LASERS - 1
        If llife(i) = 0 Then
            lx(i) = (px + pdir * 12) And WORLD_MASK
            ly(i) = py - 1
            lvx(i) = pdir * 32 + pvx
            llife(i) = 7
            Sfx.Play 0, 24, 5, 12, 9, 0
            Exit Sub
        End If
    Next i
End Sub

Sub Boom(ByVal wx As Integer, ByVal wy As Integer)
    For k = 0 To MAX_BOOMS - 1
        If bt(k) = 0 Then
            bx(k) = wx : by(k) = wy : bt(k) = 8
            Exit Sub
        End If
    Next k
End Sub

Sub KillEnemy(ByVal idx As Integer)
    Dim h As Integer
    Cpc.Hide SLOT_ENEMY0 + idx
    Boom ex(idx), ey(idx)

    h = etarget(idx)
    If h >= 0 And h < MAX_HUMANS Then
        If hstate(h) = H_GRABBED And hcarrier(h) = idx Then
            hstate(h) = H_FALLING
            hcarrier(h) = -1
            hfall(h) = hy(h) : hvy(h) = 0
        End If
    End If

    If estate(idx) = ST_BAITER Then
        AddScore 200
    Else
        AddScore 150
        enemies_alive = enemies_alive - 1
    End If
    estate(idx) = ST_DEAD
    Sfx.Play 1, 10, 1, 14, 20, 1
End Sub

Sub AddScore(ByVal n As Integer)
    score = score + n
    If score >= nextBonus Then
        nextBonus = nextBonus + 10000
        lives = lives + 1
        If bombs < 9 Then bombs = bombs + 1
        Sfx.Play 2, 60, -1, 14, 40, 0
    End If
    hudDirty = 1
End Sub

Sub TriggerSmartBomb(ByVal cx As Integer)
    Dim sx_pos As Integer
    If bombs <= 0 Then Exit Sub
    bombs = bombs - 1
    bomb_cd = 12
    Sfx.Play 2, 4, 1, 15, 45, 1
    Border 7
    Cpc.Ink 0, 26                  ' il cielo diventa bianco
    flash_bg = 2

    For i = 0 To MAX_ENEMIES - 1
        If estate(i) <> ST_DEAD Then
            sx_pos = Cpc.ScrX(ex(i), cx)
            If sx_pos >= -16 And sx_pos <= VIEW_W + 16 Then KillEnemy(i)
        End If
    Next i
    For i = 0 To MAX_SHOTS - 1
        slife(i) = 0
    Next i
    hudDirty = 1
End Sub

' la nave viene colpita: esplode, gli altri si allontanano un po', un istante di sicurezza e si riparte
Sub LoseLife()
    Dim f As Integer
    Sfx.Play 2, 6, 1, 15, 70, 1
    Sfx.Play 0, 150, 6, 14, 60, 0
    For i = 0 To MAX_HUMANS - 1
        If hstate(i) = H_CARRIED Then
            hstate(i) = H_FALLING : hfall(i) = hy(i) : hvy(i) = 0
        End If
    Next i
    For f = 0 To 29
        Cpc.Spr SLOT_PLAYER, psx, py, PAT_BOOM + (Cpc.Shr(f, 1) And 3), False, (f < 20)
        If (f And 4) = 0 Then Border 2 Else Border 0
        Vsync
    Next f
    Border 0
    lives = lives - 1
    If lives <= 0 Then
        Cpc.HideAll
        game_over = True
        Exit Sub
    End If
    ' i nemici vicini alla nave vengono allontanati, i proiettili eliminati
    For i = 0 To MAX_ENEMIES - 1
        If estate(i) <> ST_DEAD Then
            If Abs(Cpc.ScrX(ex(i), px)) < 200 Then ex(i) = (ex(i) + 400) And WORLD_MASK
            If estate(i) = ST_BAITER Then
                estate(i) = ST_DEAD : Cpc.Hide SLOT_ENEMY0 + i
            End If
        End If
    Next i
    For i = 0 To MAX_SHOTS - 1
        slife(i) = 0
    Next i
    py = 96 : pvx = 0 : safeT = 50 : baiterT = 0
    hudDirty = 1
End Sub

' un messaggio al centro dell'area di gioco per qualche istante
Dim msgT As Integer, msg As String
Sub Message(ByVal m As String, ByVal t As Integer)
    Pen 14
    At 17 - Cpc.Shr(Len(m), 1), 11                             ' <--- MODIFICATO (prima era Len(m) \ 2: una divisione)
    PrintS m
    msgT = t : msg = m
End Sub

' ============================================================================
' LE PARTI DI UN FRAME
' ============================================================================

Sub Controls()
    kb = Cpc.Keys()
    ' Su: Q o Joy Su | Giù: A o Joy Giù
    If kb And 1 Then
        If pvy < 6 Then pvy = pvy + 2
    ElseIf kb And 2 Then
        If pvy > -6 Then pvy = pvy - 2
    Else
        pvy = 0
    End If

    ' Sinistra: O o Joy Sinistra | Destra: P o Joy Destra
    thrust = 0
    If kb And 4 Then
        pdir = -1 : thrust = 1
        If pvx > -20 Then pvx = pvx - 2
    ElseIf kb And 8 Then
        pdir = 1 : thrust = 1
        If pvx < 20 Then pvx = pvx + 2
    ElseIf f3 <> 0 Then
        If pvx > 0 Then pvx = pvx - 1
        If pvx < 0 Then pvx = pvx + 1
    End If
    If thrust And Sfx.Busy(2) = 0 Then Sfx.Play 2, 28, 0, 5, 3, 1

    px = (px + pvx) And WORLD_MASK
    py = py + pvy
    If py < PLAY_TOP Then py = PLAY_TOP
    If py > PLAY_BOT Then py = PLAY_BOT

    ' la nave sta da una parte, lasciando spazio davanti: voltandosi, scivola dall'altro lato
    If pdir > 0 And psx > 110 Then psx = psx - 8
    If pdir < 0 And psx < 210 Then psx = psx + 8
    cam_x = (px - psx) And WORLD_MASK

    ' Fuoco Laser: Spazio, Z o Joy Fuoco 1
    If fire_cd > 0 Then fire_cd = fire_cd - 1
    If (kb And 16) And fire_cd = 0 Then
        FireLaser()
        fire_cd = 3
    End If

    ' Bomba Intelligente: B, M o Joy Fuoco 2
    If bomb_cd > 0 Then bomb_cd = bomb_cd - 1
    If (kb And 32) And bomb_cd = 0 Then TriggerSmartBomb(cam_x)

    ' Iperspazio: H: altrove, a caso
    If hyper_cd > 0 Then hyper_cd = hyper_cd - 1
    If (kb And 64) And hyper_cd = 0 Then
        px = Rnd(WORLD_W)
        py = 50 + Rnd(85)
        pvx = 0
        hyper_cd = 25 : safeT = 12
        Sfx.Play 0, 500, -24, 14, 20, 0
    End If
End Sub

Sub DrawPlayer()
    Dim f As Integer
    f = PAT_PLAYER
    If thrust And f2 Then f = PAT_PLAYER + PAT_FRAME2
    Cpc.Spr SLOT_PLAYER, psx, py, f, (pdir < 0), (safeT = 0 Or (safeT And 2) = 0)
    If safeT > 0 Then safeT = safeT - 1
    Cpc.RDot px, px, py + 7, 1
End Sub

' Laser: i colpi viaggiano e vengono disegnati (Z80); lscr e lasersOn per il rilevamento collisioni dei nemici
Sub Lasers()
    ' CODICE ASSEMBLY INLINE RIMOSSO
End Sub

Sub Humans()
    ' ogni umano viene spostato e disegnato da HumanStep (Z80); ciò che restituisce è ciò che il gioco deve gestire
    Dim ev As Integer
    i = 0                                                      ' <--- NUOVO
    Do While i < MAX_HUMANS                                    ' <--- MODIFICATO (prima era For i = 0 To MAX_HUMANS - 1)
        ev = HumanStep(i)                                      ' <--- MODIFICATO (Z80: dall'umano i in poi fino al primo con azioni da compiere; i = quell'umano)
        If ev Then                                             ' <--- NUOVO (nulla da fare: nessun test)
        If ev = EV_SETDOWN Then
            AddScore 500
            Sfx.Play 0, 90, -3, 14, 20, 0
        ElseIf ev = EV_CAUGHT Then
            AddScore 500
            Sfx.Play 0, 140, -6, 14, 14, 0
        ElseIf ev = EV_FELL Then
            ' caduta troppo lunga
            Boom hx(i), hy(i)
            Sfx.Play 1, 100, 12, 13, 25, 0
            HumansGone
        ElseIf ev = EV_LANDED Then
            AddScore 250
        End If
        End If                                                 ' <--- NUOVO
        i = i + 1                                              ' <--- NUOVO
    Loop                                                       ' <--- MODIFICATO (prima era Next i)
End Sub

' nessun umano rimasto: il pianeta viene distrutto e ogni lander diventa un mutante
Sub HumansGone()
    If humans_alive <= 0 And Not planet_dead Then
        planet_dead = True
        Border 2
        flash_bg = 5
        Sfx.Play 2, 3, 1, 15, 60, 1
        For k = 0 To MAX_ENEMIES - 1
            If estate(k) = ST_WANDER Or estate(k) = ST_DESCEND Then estate(k) = ST_MUTANT
        Next k
        Message "THE PLANET IS GONE", 50
    End If
End Sub

Sub Enemies()
    Dim ev As Integer, l_idx As Integer

    If spawn_timer > 0 Then
        spawn_timer = spawn_timer - 1
    ElseIf enemies_to_spawn > 0 And enemies_alive < 7 Then
        SpawnEnemy()
        spawn_timer = 30
    End If
    ' il baiter: compare dopo 40 secondi, poi ogni 12
    waveT = waveT + 1
    If waveT > 1000 Then
        baiterT = baiterT + 1
        If baiterT >= 300 Or waveT = 1001 Then
            baiterT = 0
            SpawnBaiter()
        End If
    End If

    ' ogni nemico viene spostato, disegnato e controllato da EnemyStep (Z80: erano 90 parole chiave a nemico);
    ' ciò che restituisce è ciò che il gioco deve gestire
    i = 0                                                      ' <--- NUOVO
    Do While i < MAX_ENEMIES                                   ' <--- MODIFICATO (prima era For i = 0 To MAX_ENEMIES - 1)
        ev = EnemyStep(i)                                      ' <--- MODIFICATO (Z80: dal nemico i in poi fino al primo con azioni da compiere; i = quel nemico)
        If ev Then
            If (ev And 15) = EV_GRABBED Then
                Sfx.Play 1, 300, -16, 13, 30, 0
                Message "HUMANOID TAKEN!", 25
            ElseIf (ev And 15) = EV_TAKEN Then
                Sfx.Play 1, 60, 8, 15, 40, 0
                HumansGone
            ElseIf (ev And 15) = EV_LASERED Then
                l_idx = Cpc.Shr(ev, 4) And 3
                llife(l_idx) = 0
                Cpc.Hide SLOT_LASER0 + l_idx
                KillEnemy(i)
            ElseIf (ev And 15) = EV_RAMMED Then
                KillEnemy(i)
                LoseLife()
                Exit Sub
            End If
            If ev And EV_SHOOT Then EnemyShot i, es_x
        End If
        i = i + 1                                              ' <--- NUOVO
    Loop                                                       ' <--- MODIFICATO (prima era Next i)
End Sub

' EnemyStep(i): il frame del nemico i - come gestito in BASIC da Enemies (Main.bas dell'edizione
' per ZX Spectrum Next, dove poter leggere la logica): spostato in base al suo stato (vagare, scendere per
' catturare un umano, portarne uno su, mutante, baiter), mantenuto nell'area di gioco, disegnato con il punto radar
' (cp_showr), poi verificato contro i laser e la nave, e se spara. Restituisce
' EV_... (0 = nulla da fare), es_x = posizione orizzontale nella visuale
Function EnemyStep(ByVal n As Integer) As Integer   ' <--- MODIFICATO (n: {i} sotto corrisponde alla i del gioco)
    ' CODICE ASSEMBLY INLINE RIMOSSO
End Function

' HumanStep(i): il frame dell'umano i, come gestito in BASIC da Humans: cammina a terra, trasportato
' dalla nave (posato a terra quando tocca il suolo), cade (preso al volo dalla nave, o atterra - da
' troppo in alto, muore); disegnato con il punto radar. Restituisce EV_... (0 = nulla da fare)
Function HumanStep(ByVal n As Integer) As Integer   ' <--- MODIFICATO (n: {i} sotto corrisponde alla i del gioco)
    ' CODICE ASSEMBLY INLINE RIMOSSO
End Function


' un proiettile sparato dal nemico n alla posizione orizzontale esx sullo schermo, mirato alla nave
Sub EnemyShot(ByVal n As Integer, ByVal esx As Integer)
    Dim dx As Integer, dy As Integer, m As Integer
    For k = 0 To MAX_SHOTS - 1
        If slife(k) = 0 Then
            dx = psx - esx : dy = py - ey(n)
            m = Abs(dx)
            If Abs(dy) > m Then m = Abs(dy)
            If m < 1 Then m = 1
            sx(k) = ex(n) * 4 : sy(k) = ey(n) * 4
            svx(k) = dx * 24 \ m : svy(k) = dy * 24 \ m
            slife(k) = 30
            Sfx.Play 2, 70, -3, 8, 6, 0
            Exit Sub
        End If
    Next k
End Sub

' Shots: i proiettili nemici avanzano e vengono disegnati (Z80); se uno colpisce la nave la distrugge
Sub Shots()
    If ShotsStep() Then LoseLife()
End Sub

' ShotsStep(): il frame dei proiettili: True se uno colpisce la nave (viene distrutta)
Function ShotsStep() As Integer
    ' CODICE ASSEMBLY INLINE RIMOSSO
End Function

' Booms: i frame delle esplosioni (Z80)
Sub Booms()
    ' CODICE ASSEMBLY INLINE RIMOSSO
End Sub

' gli indicatori dello scanner che mostrano la porzione di mondo visibile sullo schermo (appena fuori dal
' riquadro, sopra e sotto): Cpc.Draw li posiziona su m e 40 pixel radar più avanti
Sub ViewMarks()
    Dim m As Integer
    m = WorldToRadarX(cam_x)
    If m >= 62 And m + 41 <= 194 Then Cpc.Mark m Else Cpc.Mark -1
End Sub

' ============================================================================
' PUNTO DI INGRESSO DEL PROGRAMMA PRINCIPALE
' ============================================================================

Mode 0
CpcData.Data                      ' (le tabelle: non viene mai eseguito, le mantiene nel programma)
Cpc.Init
Sfx.Init
Border 0
Pen 15
ClsAll
MakeLand
Heights

Do
    TitleScreen

    score = 0 : nextBonus = 10000
    lives = 3
    bombs = 3
    wave = 1
    game_over = False
    InitWave()

    Do While Not game_over
        Vsync
        frames = frames + 1
        f2 = frames And 1 : f3 = f3 + 1                       ' <--- MODIFICATO (prima era f3 = frames Mod 3: una divisione)
        If f3 = 3 Then f3 = 0                                  ' <--- NUOVO (frames Mod 3 usato come contatore)
        f4 = Cpc.Shr(frames, 2) And 1 : f12 = Cpc.Div(frames, 6) And 1
        If flash_bg > 0 Then
            flash_bg = flash_bg - 1
            If flash_bg = 0 Then
                Border 0
                Cpc.Ink 0, 0
            End If
        End If
        If msgT > 0 Then
            msgT = msgT - 1
            If msgT = 0 Then
                At 17 - Cpc.Shr(Len(msg), 1), 11 : PrintS Space$(Len(msg))   ' <--- MODIFICATO (solo dov'è posizionato il messaggio: prima era At 1, 11 : PrintS Space$(32), 64 lettere disegnate)
            ElseIf Cpc.Touched() Then
                Pen 14 : At 17 - Cpc.Shr(Len(msg), 1), 11 : PrintS msg   ' <--- MODIFICATO (uno sprite ne ha cancellato una parte: ristampa; prima era Message msg, msgT: due copie della stringa)
            End If
        End If

        Controls
        DrawPlayer
        Lasers
        Humans
        Enemies
        If Not game_over Then Shots
        Booms
        Terrain cam_x
        ViewMarks
        If hudDirty Then UpdateHUD()

        ' ondata completata: un bonus per ogni umano sopravvissuto
        If enemies_alive <= 0 And enemies_to_spawn <= 0 And Not game_over Then
            WaveDone
        End If
    Loop

    ' Schermata di Game Over
    Cpc.HideAll
    ClsAll
    Cpc.Ground 0
    Pen 10
    At 8, 9
    PrintS "G A M E   O V E R"
    Pen 15
    At 8, 12
    PrintS "FINAL SCORE: " & Mid$(Str$(score), 2)
    Pen 14
    At 8, 15
    PrintS "REACHED WAVE: " & Mid$(Str$(wave), 2)
    For i = 1 To 75
        Vsync
    Next i
Loop

Sub WaveDone()
    Dim b As Integer, f As Integer
    For i = 0 To MAX_ENEMIES - 1
        If estate(i) = ST_BAITER Then estate(i) = ST_DEAD
    Next i
    For i = 0 To MAX_BOOMS - 1
        bt(i) = 0
    Next i
    Cpc.HideAll                ' (le scritte prendono il posto degli sprite)
    b = humans_alive * 100 * wave
    If b > 5000 Then b = 5000
    Pen 14
    At 9, 10 : PrintS "ATTACK WAVE" & Str$(wave)
    At 11, 11 : PrintS "COMPLETED"
    Pen 15
    At 9, 13 : PrintS "BONUS" & Str$(b)
    For f = 0 To 5
        Sfx.Play f Mod 3, 120 - f * 12, 0, 13, 12, 0
        For i = 1 To 5
            Vsync
        Next i
    Next f
    AddScore b
    For i = 1 To 30
        Vsync
    Next i
    wave = wave + 1
    If bombs < 5 Then bombs = bombs + 1
    InitWave()
End Sub

Sub TitleScreen()
    Dim t As Integer, a As Integer
    Cpc.HideAll
    ClsAll
    Stars
    Cpc.Ground 2
    Cpc.Big 16, 6, "INVASION"   ' <--- MODIFICATO (prima era DEFENDER)
    Pen 11
    At 7, 6
    PrintS "AMSTRAD CPC EDITION"
    Pen 15
    At 9, 9 : PrintS "LANDER      150"
    At 9, 11 : PrintS "MUTANT      150"
    At 9, 13 : PrintS "BAITER      200"
    At 9, 15 : PrintS "HUMANOID    500"
    Pen 12
    At 3, 17 : PrintS "Q / A or UP/DN : ALTITUDE"
    At 3, 18 : PrintS "O / P or LT/RT : THRUST"
    At 3, 19 : PrintS "SPACE / FIRE 1 : LASER"
    At 3, 20 : PrintS "B / M / FIRE 2 : SMART BOMB"
    At 3, 21 : PrintS "H              : HYPERSPACE"
    Pen 11                                         ' <--- NUOVO
    At 1, 22 : PrintS "(C) 2026 CYBORG UNICORN PTY LTD"   ' <--- NUOVO
    t = 0
    Do
        Vsync
        t = t + 1
        a = Cpc.Shr(t, 2) And 1
        Cpc.Spr 1, 70, 195 - 9 * 8, PAT_LANDER + a * PAT_FRAME2, False, True
        Cpc.Spr 2, 70, 195 - 11 * 8, PAT_MUTANT + a * PAT_FRAME2, False, True
        Cpc.Spr 3, 70, 195 - 13 * 8, PAT_BAITER + a, False, True
        Cpc.Spr 4, 70, 195 - 15 * 8, PAT_HUMAN + a * PAT_FRAME2, False, True
        ' la nave attraversa lo schermo in basso
        Cpc.Spr 0, (t * 4) Mod 360 - 20, 6, PAT_PLAYER + a * PAT_FRAME2, False, True
        Pen 10
        At 7, 23
        If Cpc.Div(t, 12) And 1 Then PrintS "PRESS SPACE OR FIRE" Else PrintS "                   "
        k = Rnd(10)
    Loop Until Cpc.Keys() And 16
    Do
        Vsync
    Loop While Cpc.Keys() And 16
    Cpc.HideAll
End Sub

' ============================================================================
' ISTRUZIONI IN STILE CPC DELL'ORIGINALE
' ============================================================================

' Pen n: il colore per il testo seguente (i numeri di pennello originali, come colori Cpc.cls)
Sub Pen(ByVal n As Integer)
    Select Case n
        Case 14: txtPen = 6
        Case 11: txtPen = 3
        Case 10: txtPen = 8
        Case 12: txtPen = 10
        Case 13: txtPen = 14
        Case Else: txtPen = 1
    End Select
End Sub

' Border n: i colori del bordo dell'originale (0 nero, 2 rosso, 7 bianco)
Sub Border(ByVal n As Integer)
    Select Case n
        Case 2: Cpc.Ink 16, 6
        Case 7: Cpc.Ink 16, 26
        Case Else: Cpc.Ink 16, 0
    End Select
End Sub

' At x, y: posizione in cui andrà il testo (1, 1 = in alto a sinistra della grafica 32 x 24 del Next: colonna 4
' delle 40 del CPC, riga 0 delle sue 25)
Sub At(ByVal x As Integer, ByVal y As Integer)
    atx = x + 3 : aty = y - 1
End Sub

Sub PrintS(ByVal t As String)
    Cpc.Text atx, aty, t, txtPen
    atx = atx + Len(t)
End Sub

Sub ClsAll()
    Cpc.Cls
End Sub

' Vsync: il frame disegnato sullo schermo non visibile, mostrato al ritorno di traccia; l'audio viene mantenuto attivo
Sub Vsync()
    Cpc.Draw cam_x
    Cpc.Flip
    Sfx.Update
    Sfx.Update                     ' (due volte: un frame qui equivale a due del Next)
End Sub

' Il terreno: Cpc.Draw lo disegna a partire dalle altezze (Heights); solo la distruzione del pianeta le modifica
Sub Terrain(ByVal cx As Integer)
    If terDead <> planet_dead Then Heights : terDead = planet_dead
End Sub

' la linea del suolo per ogni x del mondo (dall'alto in basso), con pendenza tra i passi di 4 pixel di
' GetTerrainY
Sub Heights()
    Dim wx As Integer, h0 As Integer, h1 As Integer
    For wx = 0 To WORLD_MASK Step 2
        h0 = GetTerrainY(wx) : h1 = GetTerrainY((wx + 4) And WORLD_MASK)
        Cpc.Height wx, 191 - (h0 + (h1 - h0) * (wx And 3) \ 4)
    Next wx
    terDead = planet_dead
End Sub
1 Mi Piace

cpc.cls

' Cpc - Schermo di INVASION sull'Amstrad CPC: modo 0 (160 x 200, 16 penne), due schermi (#C000
' e #0000-#3FFF: il programma è compilato a partire da #4000), sprite software che sostituiscono
' quelli hardware del Next e l'aritmetica veloce necessaria al game loop.
' (c) 2026 Cyborg Unicorn Pty Ltd
'
' A ogni frame il gioco imposta i suoi sprite (Spr: uno slot, la sua posizione, il suo pattern - come per gli
' slot del Next, rimangono invariati finché non vengono modificati; Hide), i punti del radar (RDot, Dot), i marcatori visivi (Mark) e
' qualsiasi testo (Text: direttamente su entrambi gli schermi); quindi Draw disegna il frame sullo schermo non
' visibile: il terreno (solo se la visuale si è mossa: i byte in cui si trovava la volta scorsa vengono cancellati in nero, poi un   ' <--- CHANGED
' pixel o due per colonna), le stelle (spegnimento di quelle vecchie, accensione di quelle nuove), le linee che uno sprite ha   ' <--- CHANGED
' cancellato in nero, e infine gli sprite (codice compilato). Flip lo mostra al flyback del secondo frame a partire   ' <--- CHANGED
' dall'ultimo flip - a 25 frame al secondo stabili, indipendentemente dal tempo impiegato da un frame sotto i 40 ms (cp_tick tiene   ' <--- CHANGED
' il tempo) - e predispone l'altro schermo: gli sprite di due frame fa cancellati in nero, il rispettivo   ' <--- CHANGED
' radar ripulito. Nulla viene conservato da sotto uno sprite: tutto ciò che uno sprite può coprire viene ridisegnato,   ' <--- CHANGED
' eccetto il testo (Touched segnala quando la riga dei messaggi è stata colpita e quali lettere).   ' <--- CHANGED
' Le coordinate degli sprite sono quelle del Next (x 0-319 lungo la visuale, y verso l'alto partendo dal basso come
' nel Plot originale); un pattern 16 x 16 diventa 8 x 16 pixel in modo 0 (CpcData.cls).
' Testo: 40 colonne da 4 pixel (il font stretto), 25 righe da 8 linee. Le chiamate relative ai pixel (Dot, HLine,
' Big) accettano pixel del modo 0, 0-159 in orizzontale, linee 0-199 verso il basso.
' Tutto è in Z80 (#asm); il blocco di Draw contiene le tabelle e le routine chiamate dagli altri elementi, per cui
' un programma deve necessariamente usare Draw (tutti lo fanno).
' Interrupt: IM 2 (una tabella da 257 byte: qualsiasi valore sul bus funziona), attivi solo mentre lo Z80 di Cpc, Sfx e del gioco gira sullo   ' <--- NEW
' stack reale; il thread BASIC (SP è il thread) viene eseguito con gli interrupt disattivati, come in precedenza.   ' <--- NEW
' Memoria oltre il programma (non nel file): le tabelle di Text, le tabelle delle linee, gli elenchi del terreno e delle   ' <--- NEW
' stelle (da ntc_free in su, fino a circa #AD00: la RAM del firmware non viene più usata una volta avviato).   ' <--- NEW
Option Explicit

Public Const Slots = 33

' Init: i 16 inchiostri e il bordo, le tabelle delle linee, lo schermo a #C000 mostrato (l'altro è
' #0000-#3FFF: il programma è compilato a partire da #4000), nulla ancora disegnato. Dopo Mode 0
Public Sub Init()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Ink pen, ink: inchiostro CPC (0-26) associato a una penna (0-15, 16 = il bordo)
Public Sub Ink(ByVal pen As Integer, ByVal ink As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Spr slot, x, y, pattern, mirror, shown: lo slot 0-32 mostra il pattern (0-15) con il centro in x
' (0-319 lungo la visuale), y (verso l'alto dal basso, 0-191), specchiato se mirror; oppure nascosto
Public Sub Spr(ByVal slot As Integer, ByVal x As Integer, ByVal y As Integer, ByVal pat As Integer, ByVal mirror As Integer, ByVal shown As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Hide slot: slot nascosto
Public Sub Hide(ByVal slot As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' HideAll: tutti gli slot nascosti
Public Sub HideAll()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Text col, row, s, pen: s a partire dalla colonna col (0-39), riga (0-24) con la penna pen (nero sullo sfondo), su entrambi
' gli schermi. Lettere 32-95 (le lettere minuscole sono visualizzate come maiuscole). Uno sprite sopra il testo lo
' cancella quando si sposta (Flip cancella in nero la posizione in cui si trovava): Touched segnala quando ciò è accaduto sulla
' riga dei messaggi
Public Sub Text(ByVal col As Integer, ByVal row As Integer, ByVal s As String, ByVal pen As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Touched(): True una volta quando uno sprite è stato cancellato in nero dalla riga dei messaggi (linee 80-87)
' su uno dei due schermi dall'ultima chiamata: il messaggio necessita di essere ridisegnato (e la Text che
' lo disegna traccia poi solo le lettere che erano state cancellate in nero, su ciascuno schermo)   ' <--- CHANGED
Public Function Touched() As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Dot x, y, pen: un punto radar (pixel x 0-159, linea y 0-199) sullo schermo su cui si sta disegnando (l'interno
' del radar viene ripulito a ogni Flip)
Public Sub Dot(ByVal x As Integer, ByVal y As Integer, ByVal pen As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' RDot wx, px, y, pen: il punto radar per la coordinata del mondo wx (la nave a px è al centro del
' riquadro), y (verso l'alto dal basso): al pixel 32 + ((wx - px + 512) And 1023) \ 16, linea 38 - y \ 7
Public Sub RDot(ByVal wx As Integer, ByVal px As Integer, ByVal y As Integer, ByVal pen As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Show(slot, wx, y, pattern, mirror, cx, px, pen): un'entità del mondo alle coordinate wx, y (verso l'alto dal
' basso) in un unico passaggio: il suo sprite nello slot (mostrato nella sua posizione lungo la visuale da cx quando questa è
' compresa tra -8 e 327, altrimenti nascosto), il suo punto radar con la penna pen (nessuno se pen è -1), e la sua posizione
' lungo la visuale restituita in output (-512 a 511). Una sola parola chiave al posto di dieci. (Il codice Z80 nel gioco
' popola cp_shb e chiama cp_showr: la stessa operazione, senza parola chiave)
Public Function Show(ByVal slot As Integer, ByVal wx As Integer, ByVal y As Integer, ByVal pat As Integer, ByVal mirror As Integer, ByVal cx As Integer, ByVal px As Integer, ByVal pen As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' ScrX(wx, cx): posizione della coordinata del mondo wx lungo una visuale da cx: da -512 a 511
Public Function ScrX(ByVal wx As Integer, ByVal cx As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Mark x: i marcatori visivi alla coordinata radar x (quelli del Next: 62-153), sullo schermo su cui si sta disegnando: un byte
' (due pixel bianchi) a x / 4 e 10 byte successivi, linee 7 e 41 (rimossi al Flip successivo, come
' accade per i punti). -1 = nessuno
Public Sub Mark(ByVal x As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Star k, x, y, pen: stella k (0-31) in x (0-319 nel cielo), linea y, con la penna pen. (Memorizzata come x,
' l'offset della linea nello schermo e i bit del pixel sinistro della penna: nessuna ricerca necessaria al momento del rendering)
Public Sub Star(ByVal k As Integer, ByVal x As Integer, ByVal y As Integer, ByVal pen As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Height wx, y: la linea del terreno (0-199) alle coordinate del mondo wx (0-1023, pari: i dispari non vengono salvati)
Public Sub Height(ByVal wx As Integer, ByVal y As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' GroundAt(wx): l'altezza del terreno (verso l'alto dal basso) alla coordinata del mondo wx, dalla tabella delle altezze
' (Height), in 100 cicli - GetTerrainY del gioco ne richiedeva 500
Public Function GroundAt(ByVal wx As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Ground on: cosa disegna Draw oltre agli sprite: 1 il terreno, 2 le stelle, 4 il radar (l'interno
' viene ripulito a ogni Flip, le sue linee ridisegnate). Valori sommati: 7 durante il gioco, 2 nella schermata del titolo, 0 nella
' schermata di game over
Public Sub Ground(ByVal on As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Cls: entrambi gli schermi neri, nulla memorizzato su nessuno dei due
Public Sub Cls()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' HLine x, y, w, pen: una linea orizzontale dal pixel x, linea y, per w pixel, su entrambi gli schermi
Public Sub HLine(ByVal x As Integer, ByVal y As Integer, ByVal w As Integer, ByVal pen As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Big x, y, s: s nei caratteri 8 x 8 nativi del CPC, due pixel in orizzontale e tre linee in verticale per ciascuno
' dei loro pixel (16 x 24), sfumato dal giallo pastello al rosso con un'ombra rosso scuro, dal pixel x (pari),
' linea y, su entrambi gli schermi
Public Sub Big(ByVal x As Integer, ByVal y As Integer, ByVal s As String)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Flip: attende il flyback del secondo frame dall'ultimo flip (il successivo se tale momento è già
' trascorso), mostra lo schermo su cui Draw ha disegnato, disegna sull'altro a partire da ora   ' <--- CHANGED
Public Sub Flip()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Shr(x, n): x (0-65535, come senza segno) spostato a destra di n bit: x \ 2, 4, 8 ... senza divisione
' (che richiede circa 1.100 cicli; questa ne richiede 100)
Public Function Shr(ByVal x As Integer, ByVal n As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Div(x, d): x \ d per x da 0 a 65535 (senza segno), d da 1 a 255: circa 400 cicli (la divisione standard ne impiega 1.100),
' 250 per x inferiore a 256
Public Function Div(ByVal x As Integer, ByVal d As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Rnd(n): un numero casuale da 0 a n - 1 (n da 1 a 255), in circa 350 cicli (il comando Rnd di NTC ne impiega 1.200)
Public Function Rnd(ByVal n As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Keys(): i tasti premuti: 1 su (Q, joystick su), 2 giù (A), 4 sinistra (O), 8 destra (P), 16 fuoco
' (spazio, Z, fuoco 1), 32 smart bomb (B, M, fuoco 2), 64 iperspazio (H)
Public Function Keys() As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Draw cx: il frame sullo schermo su cui si sta disegnando (quello non mostrato: Flip ha rimosso i suoi vecchi
' sprite e ripulito il radar), la visuale dalla coordinata del mondo cx: il terreno e le stelle
' (Ground), gli sprite. Poi Flip
Public Sub Draw(ByVal cx As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

sfx.cls

' Sfx - effetti sonori sul chip AY del CPC, tramite il PPI (la porta A #F4xx trasporta i dati,
' la porta C #F6xx i bit di controllo dell'AY). Tre canali, ciascuno dei quali riproduce un effetto alla volta:
' un tono o un rumore la cui intonazione varia (slide) e il cui volume sfuma a zero per tutta la sua durata.
' (c) 2026 Cyborg Unicorn Pty Ltd
' Chiamare Update una volta ogni cinquantesimo di secondo; qui Vsync lo chiama due volte per frame, e l'AY viene scritto
' sulla seconda delle due (le scritture della prima venivano sovrascritte immediatamente)   ' <--- MODIFICATO
'   Sfx.Play 0, 200, 8, 15, 12, 0     ' canale A: periodo di tono 200, intonazione crescente... 12 frame
Option Explicit

Private per(2) As Integer         ' il periodo attuale del tono (12 bit; rumore: 0-31)
Private slide(2) As Integer       ' aggiunto a esso a ogni frame
Private vol(2) As Integer         ' il volume di partenza (0-15)
Private total(2) As Integer       ' la sua durata in frame
Private left(2) As Integer        ' frame rimanenti (0 = silenzio)
Private nse(2) As Integer         ' 1 = rumore, 0 = tono

Private Sub W(ByVal r As Integer, ByVal v As Integer)
    ' LINGUAGGIO ASSEMBLY INLINE RIMOSSO
End Sub

' Init: silenzia tutti i canali
Public Sub Init()
  Dim i As Integer
  For i = 0 To 13
    W i, 0
  Next
  W 7, &H3F
  For i = 0 To 2
    left(i) = 0
  Next
End Sub

' Play ch, period, slidePerFrame, volume, frames, noise: avvia un effetto sul canale ch (0-2)
Public Sub Play(ByVal ch As Integer, ByVal period As Integer, ByVal sl As Integer, ByVal volume As Integer, ByVal frames As Integer, ByVal noise As Integer)
  If ch < 0 Or ch > 2 Then Exit Sub
  per(ch) = period : slide(ch) = sl : vol(ch) = volume : total(ch) = frames : left(ch) = frames : nse(ch) = noise
End Sub

' Busy(ch): True mentre il canale ch è in riproduzione
Public Function Busy(ByVal ch As Integer) As Integer
  Busy = (left(ch) > 0)
End Function

' Update: una volta per frame - le intonazioni variano, i volumi sfumano, l'AY viene scritto (Z80: viene chiamato
' due volte per frame)
Public Sub Update()
    ' LINGUAGGIO ASSEMBLY INLINE RIMOSSO
End Sub

' Quiet: tutti i canali interrotti
Public Sub Quiet()
  Dim i As Integer
  For i = 0 To 2
    left(i) = 0
    W 8 + i, 0
  Next
End Sub

e infine cpcdata.cls

' CpcData - sprite e font stretto di INVASION per Amstrad CPC, modalità 0 (generato da
' tools/cpcgfx.py a partire da INVASION.SPR: non modificare). Gli sprite sono compilati: ciascuno dei 16
' pattern, normale e specchiato (solo la nave, gli umani e il dardo ne hanno uno specchiato:
' le voci degli altri puntano a quello normale), come codice Z80 che lo disegna su HL (il suo byte
' in alto a sinistra) - un intero byte scritto, mezzo mascherato, uno vuoto saltato - da una riga alla
' successiva di #800 (cp_nextw oltre l'ottavo di riga: Cpc.cls). cd_spr è una tabella di 32 voci (pattern * 2 + specchio): xoff,
' yoff, w (byte), h, l'indirizzo del codice. Gli sprite vengono disegnati su pixel pari (un confine di byte).
' cd_font: 64 caratteri (32-95) di 8 righe, una riga di 4 pixel nei bit 7-4. cd_inks: i numeri di inchiostro CPC
' dei 16 pennelli.
' (c) 2026 Cyborg Unicorn Pty Ltd
Option Explicit

' Data: mai chiamata - contiene le tabelle e il codice degli sprite (etichette globali, utilizzate da Cpc.cls)
Public Sub Data()
' LINGUAGGIO ASSEMBLY INLINE RIMOSSO
End Sub

In cosa viene assemblato il codice qui sopra? Ecco uno snippet.

; ============================================================
; NTC program - generated by NTC BASIC (VB style BASIC to NTC)
; platform: cpc (Amstrad CPC - direct hardware, no firmware), starts in turbo mode
; ============================================================

			DEVICE AMSTRADCPC6128		; per SAVEAMSDOS alla fine
			org #4000

NTC_TERM	equ 0		; Opzione Terminal: 0 nessuno, 1 VT52, 2 ANSI (CP/M)
NTC_STRMAX	equ 40		; la stringa più lunga (buffer di libreria, temporanee)
NTC_MEMBANKS	equ 64		; Opzione Memory: il numero massimo di banchi da 64K cercati dalla classe Memory
NTC_ROMENABLE	equ 0		; Opzione RomEnable (cpcfw, cpcmin): il codice di avvio avvia le ROM
NTC_ROMLIB	equ 0		; 1 = la libreria è la ROM delle keyword (Library: ROM; ntcRomAsm la compila)
NTC_WINHOLE	equ 0		; 1 = il programma è compilato attorno a un intervallo Option Reserve sulla finestra #4000-#7FFF
NTC_BUFSIZE	equ 2048		; il buffer della libreria (ntc_buf: FileSystem, Memory.Copy): Option Buffer
NTC_CPMGFX	equ 0		; 1 = CP/M: la grafica disegna sullo schermo di un Amstrad CPC (2.2: #C000, Plus: blocco 1 dalla memoria comune)

ntc_start:
			di
			ld (ntc_csp), sp
			ld sp, main_thread
			ret

; ============================================================
; Main Thread
; ============================================================

main_thread:
			dw kw_bssinit, ntc_bss, ntc_bss_size		; gli array: ci stanno? azzerali

			; Main.bas 1975: Mode 0
			dw kw_model, 0

			; Main.bas 1976: CpcData.Data                      ' (le tabelle: non viene mai eseguito, le mantiene nel programma)
			dw fn_cpcdata_data

			; Main.bas 1977: Cpc.Init
			dw fn_cpc_init

			; Main.bas 1978: Sfx.Init
			dw p_sfx_init_c

			; Main.bas 1979: Border 0
			dw p_border_c, lit_0

			; Main.bas 1980: Pen 15
			dw p_pen_c, lit_15

			; Main.bas 1981: ClsAll
			dw p_clsall_c

			; Main.bas 1982: MakeLand
			dw p_makeland_c

			; Main.bas 1983: Heights
			dw p_heights_c

			; Main.bas 1985: Do
do1_t:

			; Main.bas 1986: TitleScreen
			dw p_titlescreen_c

			; Main.bas 1988: score = 0 : nextBonus = 10000
			dw kw_l_mov, llit_0, var_score
			dw kw_l_mov, llit_10000, var_nextbonus

			; Main.bas 1989: lives = 3
			dw kw_ltov, 3, var_lives

			; Main.bas 1990: bombs = 3
			dw kw_ltov, 3, var_bombs

			; Main.bas 1991: wave = 1
			dw kw_ltov, 1, var_wave

			; Main.bas 1992: game_over = False
			dw kw_ztov, var_game_over

			; Main.bas 1993: InitWave()
			dw p_initwave_c

			; Main.bas 1995: Do While Not game_over
do2_t:
			dw kw_notv, var_game_over, tmp0
			dw kw_goto_ifzero, tmp0, do2_e

			; Main.bas 1996: Vsync
			dw p_vsync_c

			; Main.bas 1997: frames = frames + 1
			dw kw_inc, var_frames

			; Main.bas 1998: f2 = frames And 1 : f3 = f3 + 1                       ' <--- MODIFICATO (prima era f3 = frames Mod 3: una divisione)
			dw kw_andvl, var_frames, 1, var_f2
			dw kw_inc, var_f3

			; Main.bas 1999: If f3 = 3 Then f3 = 0                                  ' <--- NUOVO (frames Mod 3 come contatore)
			dw kw_cpeqvl, var_f3, 3
			dw kw_goto_iffalse, if3_1
			dw kw_ztov, var_f3
if3_1:

			; Main.bas 2000: f4 = Cpc.Shr(frames, 2) And 1 : f12 = Cpc.Div(frames, 6) And 1
			dw fn_cpc_shr, var_frames, lit_2, var_cpc_shr_shr
			dw kw_vtov, var_cpc_shr_shr, tmp0
			dw kw_andvl, tmp0, 1, var_f4
			dw fn_cpc_div, var_frames, lit_6, var_cpc_div_div
			dw kw_vtov, var_cpc_div_div, tmp0
			dw kw_andvl, tmp0, 1, var_f12

			; Main.bas 2001: If flash_bg > 0 Then
			dw kw_scpgtvl, var_flash_bg, 0
			dw kw_goto_iffalse, if4_1

			; Main.bas 2002: flash_bg = flash_bg - 1
			dw kw_dec, var_flash_bg

			; Main.bas 2003: If flash_bg = 0 Then
			dw kw_cpeqvl, var_flash_bg, 0
			dw kw_goto_iffalse, if5_1

			; Main.bas 2004: Border 0
			dw p_border_c, lit_0

			; Main.bas 2005: Cpc.Ink 0, 0
			dw fn_cpc_ink, lit_0, lit_0

			; Main.bas 2006: End If
if5_1:

			; Main.bas 2007: End If
if4_1:

			; Main.bas 2008: If msgT > 0 Then
			dw kw_scpgtvl, var_msgt, 0
			dw kw_goto_iffalse, if6_1

			; Main.bas 2009: msgT = msgT - 1
			dw kw_dec, var_msgt

			; Main.bas 2010: If msgT = 0 Then
			dw kw_cpeqvl, var_msgt, 0
			dw kw_goto_iffalse, if7_1

			; Main.bas 2011: At 17 - Cpc.Shr(Len(msg), 1), 11 : PrintS Space$(Len(msg))   ' <--- MODIFICATO (solo dove si trova il messaggio: prima era At 1, 11 : PrintS Space$(32), 64 lettere disegnate)
			dw kw_strlen, var_msg, tmp1
			dw fn_cpc_shr, tmp1, lit_1, var_cpc_shr_shr
			dw kw_vtov, var_cpc_shr_shr, tmp1
			dw kw_sublv, 17, tmp1, tmp0
			dw p_at_c, tmp0, lit_11
			dw kw_strlen, var_msg, tmp0
			dw kw_strspace, stmp0, tmp0
			dw p_prints_c, stmp0

			; Main.bas 2012: ElseIf Cpc.Touched() Then
			dw if7_e_go
if7_1:
			dw fn_cpc_touched, var_cpc_touched_touched
			dw kw_goto_ifzero, var_cpc_touched_touched, if7_2

			; Main.bas 2013: Pen 14 : At 17 - Cpc.Shr(Len(msg), 1), 11 : PrintS msg   ' <--- MODIFICATO (uno sprite ne ha trascinata via una parte: di nuovo; prima era Message msg, msgT: due copie della stringa)
			dw p_pen_c, lit_14
			dw kw_strlen, var_msg, tmp1
			dw fn_cpc_shr, tmp1, lit_1, var_cpc_shr_shr
			dw kw_vtov, var_cpc_shr_shr, tmp1
			dw kw_sublv, 17, tmp1, tmp0
			dw p_at_c, tmp0, lit_11
			dw p_prints_c, var_msg

			; Main.bas 2014: End If
if7_2:
if7_e:

			; Main.bas 2015: End If
if6_1:

			; Main.bas 2017: Controls
			dw p_controls_c

			; Main.bas 2018: DrawPlayer
			dw p_drawplayer_c

			; Main.bas 2019: Lasers
			dw fn_lasers

			; Main.bas 2020: Humans
			dw p_humans_c

			; Main.bas 2021: Enemies
			dw p_enemies_c

			; Main.bas 2022: If Not game_over Then Shots
			dw kw_notv, var_game_over, tmp0
			dw kw_goto_ifzero, tmp0, if8_1
			dw p_shots_c
if8_1:

			; Main.bas 2023: Booms
			dw fn_booms

			; Main.bas 2024: Terrain cam_x
			dw p_terrain_c, var_cam_x

			; Main.bas 2025: ViewMarks
			dw p_viewmarks_c

			; Main.bas 2026: If hudDirty Then UpdateHUD()
			dw kw_goto_ifzero, var_huddirty, if9_1
			dw p_updatehud_c
if9_1:

			; Main.bas 2029: If enemies_alive <= 0 And enemies_to_spawn <= 0 And Not game_over Then
			dw kw_scplevl, var_enemies_alive, 0
			dw kw_ftov, tmp0
			dw kw_scplevl, var_enemies_to_spawn, 0
			dw kw_ftov, tmp1
			dw kw_andvv, tmp0, tmp1, tmp0
			dw kw_notv, var_game_over, tmp1
			dw kw_andvv, tmp0, tmp1, tmp0
			dw kw_goto_ifzero, tmp0, do2_t

			; Main.bas 2030: WaveDone
			dw p_wavedone_c

			; Main.bas 2031: End If
if10_1:

			; Main.bas 2032: Loop
			dw do2_t_go
do2_e:

			; Main.bas 2035: Cpc.HideAll
			dw fn_cpc_hideall

			; Main.bas 2036: ClsAll
			dw p_clsall_c

			; Main.bas 2037: Cpc.Ground 0
			dw fn_cpc_ground, lit_0

			; Main.bas 2038: Pen 10
			dw p_pen_c, lit_10

			; Main.bas 2039: At 8, 9
			dw p_at_c, lit_8, lit_9

			; Main.bas 2040: PrintS "G A M E   O V E R"
			dw p_prints_c, str0

			; Main.bas 2041: Pen 15
			dw p_pen_c, lit_15

			; Main.bas 2042: At 8, 12
			dw p_at_c, lit_8, lit_12

			; Main.bas 2043: PrintS "FINAL SCORE: " & Mid$(Str$(score), 2)
			dw kw_l_str, stmp1, var_score
			dw kw_strmid, stmp1, stmp1, lit_2, lit_255
			dw kw_strcpy, stmp0, str1
			dw kw_strcat, stmp0, stmp1
			dw p_prints_c, stmp0

			; Main.bas 2044: Pen 14
			dw p_pen_c, lit_14

			; Main.bas 2045: At 8, 15
			dw p_at_c, lit_8, lit_15

			; Main.bas 2046: PrintS "REACHED WAVE: " & Mid$(Str$(wave), 2)
			dw kw_strstr, stmp1, var_wave
			dw kw_strmid, stmp1, stmp1, lit_2, lit_255
			dw kw_strcpy, stmp0, str2
			dw kw_strcat, stmp0, stmp1
			dw p_prints_c, stmp0

			; Main.bas 2047: For i = 1 To 75
			dw kw_ltov, 1, var_i
for11_t:

			; Main.bas 2048: Vsync
			dw p_vsync_c

			; Main.bas 2049: Next i
			dw kw_next1l, var_i, 75, for11_t
for11_e:

			; Main.bas 2050: Loop
			dw do1_t_go
do1_e:
			dw kw_stop

; ============================================================
; sub makeland (modalità turbo)
; ============================================================

p_makeland:

			; Main.bas 172: Dim h As Integer, t As Integer

			; Main.bas 173: rs = 12345 : h = 14 : t = 14
			dw kw_ltov, 12345, var_rs
			dw kw_ltov, 14, var_makeland_h
			dw kw_ltov, 14, var_makeland_t

			; Main.bas 174: For k = 0 To 255
			dw kw_ztov, var_k
for12_t:

			; Main.bas 175: If k Mod 16 = 0 Then t = 8 + Rand(23)
			dw kw_modvl, var_k, 16, tmp0
			dw kw_cpeqvl, tmp0, 0
			dw kw_goto_iffalse, if13_1
			dw p_rand_c, lit_23
			dw kw_vtov, var_rand_rand, tmp1
			dw kw_addl, tmp1, 8, var_makeland_t
if13_1:

			; Main.bas 176: If h < t Then h = h + 1 + Rand(3)
			dw kw_scpltvv, var_makeland_h, var_makeland_t
			dw kw_goto_iffalse, if14_1
			dw kw_addl, var_makeland_h, 1, tmp0
			dw p_rand_c, lit_3
			dw kw_vtov, var_rand_rand, tmp1
			dw kw_add, tmp0, tmp1, var_makeland_h
if14_1:

			; Main.bas 177: If h > t Then h = h - 1 - Rand(3)
			dw kw_scpgtvv, var_makeland_h, var_makeland_t
			dw kw_goto_iffalse, if15_1
			dw kw_addl, var_makeland_h, -1, tmp0
			dw p_rand_c, lit_3
			dw kw_vtov, var_rand_rand, tmp1
			dw kw_subvv, tmp0, tmp1, var_makeland_h
if15_1:

			; Main.bas 178: If h < 8 Then h = 8
			dw kw_scpltvl, var_makeland_h, 8
			dw kw_goto_iffalse, if16_1
			dw kw_ltov, 8, var_makeland_h
if16_1:

			; Main.bas 179: If h > 32 Then h = 32
			dw kw_scpgtvl, var_makeland_h, 32
			dw kw_goto_iffalse, if17_1
			dw kw_ltov, 32, var_makeland_h
if17_1:

			; Main.bas 180: land(k) = h
			dw kw_aset8, arr_land, var_k, var_makeland_h

			; Main.bas 181: Next k
			dw kw_next1l, var_k, 255, for12_t
for12_e:

			; Main.bas 183: For k = 248 To 255
			dw kw_ltov, 248, var_k
for18_t:

			; Main.bas 184: land(k) = land(247) + (land(0) - land(247)) * (k - 247) \ 9
			dw kw_lptrtov8, arr_land+247, tmp1
			dw kw_lptrtov8, arr_land, tmp2
			dw kw_lptrtov8, arr_land+247, tmp3
			dw kw_subvv, tmp2, tmp3, tmp2
			dw kw_addl, var_k, -247, tmp3
			dw kw_mulvv, tmp2, tmp3, tmp2
			dw kw_divvl, tmp2, 9, tmp2
			dw kw_add, tmp1, tmp2, tmp1
			dw kw_aset8, arr_land, var_k, tmp1

			; Main.bas 185: Next k
			dw kw_next1l, var_k, 255, for18_t
for18_e:
			dw p_makeland_r

; ============================================================
; function rand (modalità turbo)
; ============================================================

p_rand:

			; Main.bas 191: rs = (rs * 2053 + 13849) And &H7FFF
			dw kw_mulvl, var_rs, 2053, tmp1
			dw kw_addl, tmp1, 13849, tmp1
			dw kw_andvl, tmp1, 32767, var_rs

			; Main.bas 192: Rand = (rs \ 32) Mod n
			dw kw_divvl, var_rs, 32, tmp1
			dw kw_modvv, tmp1, var_rand_n, var_rand_rand
			dw p_rand_r

; ============================================================
; function getterrainy (modalità turbo)
; ============================================================

p_getterrainy:

			; Main.bas 196: If planet_dead Then GetTerrainY = 6 Else GetTerrainY = land(Cpc.Shr(wx, 2) And 255)
			dw kw_goto_ifzero, var_planet_dead, if19_1
			dw kw_ltov, 6, var_getterrainy_getterrainy
			dw if19_e_go
if19_1:
			dw fn_cpc_shr, var_getterrainy_wx, lit_2, var_cpc_shr_shr
			dw kw_vtov, var_cpc_shr_shr, tmp1
			dw kw_andvl, tmp1, 255, tmp1
			dw kw_aget8, arr_land, tmp1, var_getterrainy_getterrainy
if19_e:
			dw p_getterrainy_r

; ============================================================
; function worldtoradarx (modalità turbo)
; ============================================================

p_worldtoradarx:

			; Main.bas 203: WorldToRadarX = 64 + Cpc.Shr((wx - px + 512) And WORLD_MASK, 3)
			dw kw_subvv, var_worldtoradarx_wx, var_px, tmp1
			dw kw_addl, tmp1, 512, tmp1
			dw kw_andvl, tmp1, 1023, tmp1
			dw fn_cpc_shr, tmp1, lit_3, var_cpc_shr_shr
			dw kw_vtov, var_cpc_shr_shr, tmp1
			dw kw_addl, tmp1, 64, var_worldtoradarx_worldtoradarx
			dw p_worldtoradarx_r

; ============================================================
; sub updatehud (modalità turbo)
; ============================================================

p_updatehud:

			; Main.bas 208: If score <> hudScore Then                                  ' <--- NEW
			dw kw_l_cpne, var_score, var_hudscore
			dw kw_goto_iffalse, if20_1

			; Main.bas 209: Pen 15
			dw p_pen_c, lit_15

			; Main.bas 210: At 1, 1
			dw p_at_c, lit_1, lit_1

			; Main.bas 211: PrintS Right$("000000" & Mid$(Str$(score), 2), 7)
			dw kw_l_str, stmp1, var_score
			dw kw_strmid, stmp1, stmp1, lit_2, lit_255
			dw kw_strcpy, stmp0, str3
			dw kw_strcat, stmp0, stmp1
			dw kw_strright, stmp0, stmp0, lit_7
			dw p_prints_c, stmp0

			; Main.bas 212: hudScore = score                                           ' <--- NEW
			dw kw_l_mov, var_score, var_hudscore

			; Main.bas 213: End If                                                     ' <--- NEW
if20_1:

			; Main.bas 214: If lives <> hudLives Then                                  ' <--- NEW
			dw kw_cpnevv, var_lives, var_hudlives
			dw kw_goto_iffalse, if21_1

			; Main.bas 215: Pen 14
			dw p_pen_c, lit_14

			; Main.bas 216: At 10, 1
			dw p_at_c, lit_10, lit_1

			; Main.bas 217: PrintS "SHIPS" & Str$(lives) & " "
			dw kw_strstr, stmp1, var_lives
			dw kw_strcpy, stmp0, str4
			dw kw_strcat, stmp0, stmp1
			dw kw_strcat, stmp0, str5
			dw p_prints_c, stmp0

			; Main.bas 218: hudLives = lives                                           ' <--- NEW
			dw kw_vtov, var_lives, var_hudlives

			; Main.bas 219: End If                                                     ' <--- NEW
if21_1:

			; Main.bas 220: If bombs <> hudBombs Then                                  ' <--- NEW
			dw kw_cpnevv, var_bombs, var_hudbombs
			dw kw_goto_iffalse, if22_1

			; Main.bas 221: Pen 12
			dw p_pen_c, lit_12

			; Main.bas 222: At 19, 1
			dw p_at_c, lit_19, lit_1

			; Main.bas 223: PrintS "BOMBS" & Str$(bombs) & " "
			dw kw_strstr, stmp1, var_bombs
			dw kw_strcpy, stmp0, str6
			dw kw_strcat, stmp0, stmp1
			dw kw_strcat, stmp0, str5
			dw p_prints_c, stmp0

			; Main.bas 224: hudBombs = bombs                                           ' <--- NEW
			dw kw_vtov, var_bombs, var_hudbombs

			; Main.bas 225: End If                                                     ' <--- NEW
if22_1:

			; Main.bas 226: If wave <> hudWave Then                                    ' <--- NEW
			dw kw_cpnevv, var_wave, var_hudwave
			dw kw_goto_iffalse, if23_1

			; Main.bas 227: Pen 11
			dw p_pen_c, lit_11

			; Main.bas 228: At 27, 1
			dw p_at_c, lit_27, lit_1

			; Main.bas 229: PrintS "WAVE" & Str$(wave) & " "
			dw kw_strstr, stmp1, var_wave
			dw kw_strcpy, stmp0, str7
			dw kw_strcat, stmp0, stmp1
			dw kw_strcat, stmp0, str5
			dw p_prints_c, stmp0

			; Main.bas 230: hudWave = wave                                             ' <--- NEW
			dw kw_vtov, var_wave, var_hudwave

			; Main.bas 231: End If                                                     ' <--- NEW
if23_1:

			; Main.bas 232: hudDirty = 0
			dw kw_ztov, var_huddirty
			dw p_updatehud_r

; ============================================================
; sub drawstaticframe (modalità turbo)
; ============================================================

p_drawstaticframe:

			; Main.bas 236: ClsAll
			dw p_clsall_c

			; Main.bas 237: hudScore = -1 : hudLives = -1 : hudBombs = -1 : hudWave = -1   ' <--- NEW (lo schermo è pulito: viene stampato l'intero HUD)
			dw kw_l_mov, llit_m1, var_hudscore
			dw kw_ltov, -1, var_hudlives
			dw kw_ltov, -1, var_hudbombs
			dw kw_ltov, -1, var_hudwave

			; Main.bas 240: Cpc.HLine 0, 43, 160, PEN_DIVIDER
			dw fn_cpc_hline, lit_0, lit_43, lit_160, lit_13

			; Main.bas 241: Cpc.HLine 31, 9, 66, PEN_SCANNER
			dw fn_cpc_hline, lit_31, lit_9, lit_66, lit_10

			; Main.bas 242: Cpc.HLine 31, 39, 66, PEN_SCANNER
			dw fn_cpc_hline, lit_31, lit_39, lit_66, lit_10

			; Main.bas 243: For k = 10 To 38
			dw kw_ltov, 10, var_k
for24_t:

			; Main.bas 244: Cpc.HLine 31, k, 1, PEN_SCANNER : Cpc.HLine 96, k, 1, PEN_SCANNER
			dw fn_cpc_hline, lit_31, var_k, lit_1, lit_10
			dw fn_cpc_hline, lit_96, var_k, lit_1, lit_10

			; Main.bas 245: Next k
			dw kw_next1l, var_k, 38, for24_t
for24_e:

			; Main.bas 246: Stars
			dw p_stars_c

			; Main.bas 247: Cpc.Ground 7
			dw fn_cpc_ground, lit_7

			; Main.bas 248: UpdateHUD()
			dw p_updatehud_c
			dw p_drawstaticframe_r

; ============================================================
; sub stars (modalità turbo)
; ============================================================

p_stars:

			; Main.bas 254: Dim x As Integer, y As Integer

			; Main.bas 255: rs = 777
			dw kw_ltov, 777, var_rs

			; Main.bas 256: For k = 0 To 31
			dw kw_ztov, var_k
for25_t:

			; Main.bas 257: x = Rand(320) : y = 46 + Rand(110)
			dw p_rand_c, lit_320
			dw kw_vtov, var_rand_rand, var_stars_x
			dw p_rand_c, lit_110
			dw kw_vtov, var_rand_rand, tmp1
			dw kw_addl, tmp1, 46, var_stars_y

			; Main.bas 258: Cpc.Star k, x, y, Val(Mid$("2211", (k Mod 4) + 1, 1))
			dw kw_modvl, var_k, 4, tmp4
			dw kw_inc, tmp4
			dw kw_strmid, stmp3, str8, tmp4, lit_1
			dw kw_strval, stmp3, tmp3
			dw fn_cpc_star, var_k, var_stars_x, var_stars_y, tmp3

			; Main.bas 259: Next k
			dw kw_next1l, var_k, 31, for25_t
for25_e:
			dw p_stars_r

; ============================================================
; sub initwave (modalità turbo)
; ============================================================

p_initwave:

			; Main.bas 267: Cpc.HideAll
			dw fn_cpc_hideall

			; Main.bas 268: DrawStaticFrame()
			dw p_drawstaticframe_c

			; Main.bas 270: px = 512 : py = 96 : pvx = 0 : pvy = 0 : pdir = 1 : psx = 110
			dw kw_ltov, 512, var_px
			dw kw_ltov, 96, var_py
			dw kw_ztov, var_pvx
			dw kw_ztov, var_pvy
			dw kw_ltov, 1, var_pdir
			dw kw_ltov, 110, var_psx

			; Main.bas 271: safeT = 25
			dw kw_ltov, 25, var_safet

			; Main.bas 273: For i = 0 To MAX_LASERS - 1
			dw kw_ztov, var_i
for26_t:

			; Main.bas 274: llife(i) = 0
			dw kw_asetl, arr_llife, var_i, 0

			; Main.bas 275: Next i
			dw kw_next1l, var_i, 3, for26_t
for26_e:

			; Main.bas 276: For i = 0 To MAX_SHOTS - 1
			dw kw_ztov, var_i
for27_t:

			; Main.bas 277: slife(i) = 0
			dw kw_asetl, arr_slife, var_i, 0

			; Main.bas 278: Next i
			dw kw_next1l, var_i, 5, for27_t
for27_e:

			; Main.bas 279: For i = 0 To MAX_BOOMS - 1
			dw kw_ztov, var_i
for28_t:

			; Main.bas 280: bt(i) = 0
			dw kw_asetl, arr_bt, var_i, 0

			; Main.bas 281: Next i
			dw kw_next1l, var_i, 3, for28_t
for28_e:

			; Main.bas 284: If wave = 1 Or (wave - 1) Mod 5 = 0 Then
			dw kw_cpeqvl, var_wave, 1
			dw kw_ftov, tmp0
			dw kw_addl, var_wave, -1, tmp1
			dw kw_modvl, tmp1, 5, tmp1
			dw kw_cpeqvl, tmp1, 0
			dw kw_ftov, tmp1
			dw kw_orvv, tmp0, tmp1, tmp0
			dw kw_goto_ifzero, tmp0, if29_1

			; Main.bas 285: planet_dead = False
			dw kw_ztov, var_planet_dead

			; Main.bas 286: humans_alive = MAX_HUMANS
			dw kw_ltov, 8, var_humans_alive

			; Main.bas 287: For i = 0 To MAX_HUMANS - 1
			dw kw_ztov, var_i
for30_t:

			; Main.bas 288: hx(i) = (i * 128 + 48) And WORLD_MASK
			dw kw_mulvl, var_i, 128, tmp1
			dw kw_addl, tmp1, 48, tmp1
			dw kw_andvl, tmp1, 1023, tmp1
			dw kw_aset, arr_hx, var_i, tmp1

			; Main.bas 289: hstate(i) = H_GROUND
			dw kw_asetl, arr_hstate, var_i, 1

			; Main.bas 290: Next i
			dw kw_next1l, var_i, 7, for30_t
for30_e:

			; Main.bas 291: End If
if29_1:

			; Main.bas 292: For i = 0 To MAX_HUMANS - 1
			dw kw_ztov, var_i
for31_t:

			; Main.bas 293: hcarrier(i) = -1
			dw kw_asetl, arr_hcarrier, var_i, -1

			; Main.bas 294: If hstate(i) <> H_DEAD Then
			dw kw_aget, arr_hstate, var_i, tmp0
			dw kw_cpnevl, tmp0, 0
			dw kw_goto_iffalse, if32_1

			; Main.bas 295: hstate(i) = H_GROUND
			dw kw_asetl, arr_hstate, var_i, 1

			; Main.bas 296: hy(i) = GetTerrainY(hx(i)) + 6
			dw kw_aget, arr_hx, var_i, tmp1
			dw p_getterrainy_c, tmp1
			dw kw_vtov, var_getterrainy_getterrainy, tmp1
			dw kw_addl, tmp1, 6, tmp1
			dw kw_aset, arr_hy, var_i, tmp1

			; Main.bas 297: End If
if32_1:

			; Main.bas 298: If Rand(2) Then hdir(i) = 1 Else hdir(i) = -1
			dw p_rand_c, lit_2
			dw kw_goto_ifzero, var_rand_rand, if33_1
			dw kw_asetl, arr_hdir, var_i, 1
			dw if33_e_go
if33_1:
			dw kw_asetl, arr_hdir, var_i, -1
if33_e:

			; Main.bas 299: Next i
			dw kw_next1l, var_i, 7, for31_t
for31_e:

			; Main.bas 301: For i = 0 To MAX_ENEMIES - 1
			dw kw_ztov, var_i
for34_t:

			; Main.bas 302: estate(i) = ST_DEAD
			dw kw_asetl, arr_estate, var_i, 0

			; Main.bas 303: Next i
			dw kw_next1l, var_i, 9, for34_t
for34_e:

			; Main.bas 305: enemies_to_spawn = 10 + wave * 2
			dw kw_2x, var_wave, tmp1
			dw kw_addl, tmp1, 10, var_enemies_to_spawn

			; Main.bas 306: If enemies_to_spawn > 24 Then enemies_to_spawn = 24
			dw kw_scpgtvl, var_enemies_to_spawn, 24
			dw kw_goto_iffalse, if35_1
			dw kw_ltov, 24, var_enemies_to_spawn
if35_1:

			; Main.bas 307: enemies_alive = 0
			dw kw_ztov, var_enemies_alive

			; Main.bas 308: spawn_timer = 5
			dw kw_ltov, 5, var_spawn_timer

			; Main.bas 309: waveT = 0 : baiterT = 0
			dw kw_ztov, var_wavet
			dw kw_ztov, var_baitert

			; Main.bas 311: For i = 0 To 4
			dw kw_ztov, var_i
for36_t:

			; Main.bas 312: SpawnEnemy()
			dw p_spawnenemy_c

			; Main.bas 313: Next i
			dw kw_next1l, var_i, 4, for36_t
for36_e:

			; Main.bas 314: UpdateHUD()
			dw p_updatehud_c

			; Main.bas 315: Message "WAVE" & Str$(wave), 30
			dw kw_strstr, stmp1, var_wave
			dw kw_strcpy, stmp0, str7
			dw kw_strcat, stmp0, stmp1
			dw p_message_c, stmp0, lit_30
			dw p_initwave_r

; ============================================================
; sub spawnenemy (modalità turbo)
; ============================================================

p_spawnenemy:

			; Main.bas 319: If enemies_to_spawn <= 0 Then Exit Sub
			dw kw_scplevl, var_enemies_to_spawn, 0
			dw kw_goto_iffalse, if37_1
			dw p_spawnenemy_r
if37_1:

			; Main.bas 320: For i = 0 To MAX_ENEMIES - 1
			dw kw_ztov, var_i
for38_t:

			; Main.bas 321: If estate(i) = ST_DEAD Then
			dw kw_aget, arr_estate, var_i, tmp0
			dw kw_cpeqvl, tmp0, 0
			dw kw_goto_iffalse, if39_1

			; Main.bas 322: ex(i) = (px + 250 + Rnd(500)) And WORLD_MASK
			dw kw_addl, var_px, 250, tmp1
			dw kw_rndl, 500, tmp2
			dw kw_add, tmp1, tmp2, tmp1
			dw kw_andvl, tmp1, 1023, tmp1
			dw kw_aset, arr_ex, var_i, tmp1

			; Main.bas 323: ey(i) = 110 + Rnd(30)
			dw kw_rndl, 30, tmp2
			dw kw_addl, tmp2, 110, tmp1
			dw kw_aset, arr_ey, var_i, tmp1

			; Main.bas 324: If Rnd(2) = 0 Then evx(i) = 1 Else evx(i) = -1
			dw kw_rndl, 2, tmp0
			dw kw_cpeqvl, tmp0, 0
			dw kw_goto_iffalse, if40_1
			dw kw_asetl, arr_evx, var_i, 1
			dw if40_e_go
if40_1:
			dw kw_asetl, arr_evx, var_i, -1
if40_e:

			; Main.bas 325: evy(i) = -1
			dw kw_asetl, arr_evy, var_i, -1

			; Main.bas 326: If planet_dead Then estate(i) = ST_MUTANT Else estate(i) = ST_WANDER
			dw kw_goto_ifzero, var_planet_dead, if41_1
			dw kw_asetl, arr_estate, var_i, 4
			dw if41_e_go
if41_1:
			dw kw_asetl, arr_estate, var_i, 1
if41_e:

			; Main.bas 327: etarget(i) = -1
			dw kw_asetl, arr_etarget, var_i, -1

			; Main.bas 328: enemies_alive = enemies_alive + 1
			dw kw_inc, var_enemies_alive

			; Main.bas 329: enemies_to_spawn = enemies_to_spawn - 1
			dw kw_dec, var_enemies_to_spawn

			; Main.bas 330: Exit Sub
			dw p_spawnenemy_r

			; Main.bas 331: End If
if39_1:

			; Main.bas 332: Next i
			dw kw_next1l, var_i, 9, for38_t
for38_e:
			dw p_spawnenemy_r

; ============================================================
; sub spawnbaiter (modalità turbo)
; ============================================================

p_spawnbaiter:

			; Main.bas 337: For i = 0 To MAX_ENEMIES - 1
			dw kw_ztov, var_i
for42_t:

			; Main.bas 338: If estate(i) = ST_DEAD Then
			dw kw_aget, arr_estate, var_i, tmp0
			dw kw_cpeqvl, tmp0, 0
			dw kw_goto_iffalse, if43_1

			; Main.bas 339: ex(i) = (px + 300) And WORLD_MASK
			dw kw_addl, var_px, 300, tmp1
			dw kw_andvl, tmp1, 1023, tmp1
			dw kw_aset, arr_ex, var_i, tmp1

			; Main.bas 340: ey(i) = py
			dw kw_aset, arr_ey, var_i, var_py

			; Main.bas 341: estate(i) = ST_BAITER
			dw kw_asetl, arr_estate, var_i, 5

			; Main.bas 342: evx(i) = 0 : evy(i) = 0
			dw kw_asetl, arr_evx, var_i, 0
			dw kw_asetl, arr_evy, var_i, 0

			; Main.bas 343: etarget(i) = -1
			dw kw_asetl, arr_etarget, var_i, -1

			; Main.bas 344: Sfx.Play 1, 40, 2, 13, 25, 0
			dw p_sfx_play_c, lit_1, lit_40, lit_2, lit_13, lit_25, lit_0

			; Main.bas 345: Exit Sub
			dw p_spawnbaiter_r

			; Main.bas 346: End If
if43_1:

			; Main.bas 347: Next i
			dw kw_next1l, var_i, 9, for42_t
for42_e:
			dw p_spawnbaiter_r

Come puoi vedere, il programma sembra un blocco di dati: lo è e non lo è. SP viene riutilizzato come PC: ciò significa che il programma può saltare direttamente da una parola chiave all’altra senza alcun overhead di chiamata e passaggio di parametri. Ecco perché sia sullo ZX Spectrum Next che sull’Amstrad CPC è possibile creare giochi così veloci e capaci di spingere al massimo l’hardware (soprattutto sullo ZX Spectrum Next) partendo dal BASIC, proprio perché il BASIC si adatta perfettamente a questo costrutto. Il C? Come zucchero sintattico sì, ma per come funziona tipicamente sotto il cofano, no. NTC BASIC ti permette di programmare in JavaScript (sottoinsieme ES5+) o PHP (sottoinsieme) se non ti piace lo stile di Visual BASIC 5/6. Il funzionamento si basa sulla traduzione da JavaScript o PHP a NTC BASIC tramite un preprocessore prima della compilazione: ciò garantisce un comportamento prevedibile e un unico insieme di bug di compilazione. Tutte le variabili sono statiche, senza eccezioni: perché è importante? Si perde la ricorsione, ma si guadagna stabilità: non c’è heap. Si può creare un heap? Certo che si può, ma è meglio creare funzioni per gestire la memoria come preferisci.

Anche CyborgOS (l’hypervisor per Z80) è scritto in NTC BASIC (con una buona dose di Assembly inline). Tutto il codice per le build CPC, ZX Spectrum Next e MSX si trova nella repo di CyborgOS.

In NTC BASIC esistono le classi sia come zucchero sintattico per racchiudere funzioni correlate, sia per garantire che rimangano confinate all’interno di un banco. Questo consente di controllare la mappa di memoria per programmi di grandi dimensioni fino a 4 megabyte! Oppure, come nel caso di CyborgOS, di avere 20 istanze di programmi CP/M in esecuzione contemporanea con ancora più di metà della RAM libera su macchine da 4 MB. Quella RAM extra potrebbe ospitare ulteriori istanze, ma… viene utilizzata per i driver, per «l’heap» contenuto all’interno di una classe Linked List, ramdisk, ecc. — puoi creare ed eliminare ramdisk al volo. Ciascuna istanza CP/M ignora la presenza delle altre, e non devono necessariamente essere istanze CP/M: potrebbero benissimo essere una shell POSIX, cosa che prenderò in considerazione. Le classi possono essere guidate da interrupt e attivate nei rispettivi banchi: quando un’applicazione richiede una grande quantità di dati, ne fa richiesta e il kernel, che può trovarsi a C0 o C2 (testato, ma per ora uso ancora C0), glieli fornisce in blocco come un DMA virtuale (è DMA solo dal punto di vista dell’applicazione).

Per i 464 non espansi? Puoi recuperare circa altri 12 KB utilizzando le ROM di NTC BASIC che contengono le parole chiave; ma seriamente? Comprati un’espansione di memoria: 1 MB fa miracoli per il tuo computer! 4 MB sono tutto ciò di cui avrai mai bisogno.

NTC BASIC è veloce quanto SDCC? No, e in realtà nemmeno SDCC è più veloce: dipende da cosa stai facendo. NTC BASIC non è un compilatore che ottimizza molto, esegue solo alcune ottimizzazioni di base. Ma offre così tanto che SDCC non dà, come il pieno supporto sia agli overlay che alle pagine (la sottile differenza è che gli overlay si sovrascrivono a vicenda per i computer con poca RAM, o per le istanze CP/M che non vogliono usare tecniche più moderne), mentre le pagine rimangono nei banchi di memoria.

1 Mi Piace