Invasion (ehemals Defender) in NTC BASIC

Invasion wurde von meiner Version von Defender für den ZX Spectrum Next zurückportiert, bevor der Titel in Invasion geändert wurde. Die ursprüngliche Veröffentlichung von Defender war in Ordnung; ich dachte mir: hm, ein CPC, auf dem kompiliertes BASIC läuft – genau das, was man erwarten würde, aber… Als ich Invasion auf dem ZX Spectrum Next erneut testete, nachdem ich Cyborg Hellgate programmiert hatte, bemerkte ich ein seltsames Verhalten – je mehr Gegner ich erledigte, desto schneller wurde das Spiel – und… der CPC erbte genau denselben Fehler. Jetzt läuft die CPC-Version (und auch die für den ZX Spectrum Next) sooooo flüssig. Ist es der beste Defender-Klon auf dem CPC? Ich weiß es nicht – ich habe immer noch eine Schwäche für Defend or Die, und wenn es bei mir nicht so oft zufällig abstürzen würde, würde ich mich wegen der Authentizität für Defend or Die entscheiden.

Ich habe das Inline-Assembly entfernt, damit der Code leichter nachvollziehbar ist, aber das Programm wurde ursprünglich zu 100 % in NTC BASIC geschrieben und anschließend optimiert – was sich größtenteils auf die Objektbewegung belief, da der CPC keine Hardware-Sprites besitzt. Alle Sprites sind ebenfalls kompiliert.

main.bas KOMMENTARE

' ============================================================================
' INVASION - Amstrad CPC Edition (Mode 0, Software-Sprites)
' ============================================================================
' NTC BASIC. Das Spiel der ZX Spectrum Next Edition, genau so, auf einem CPC: seine Welt, seine Lander,
' Mutanten, Menschen und Scanner, seine Sounds und sein Gameplay:
'   - die 16 Sprite-Muster (zwei Frames für das Schiff, Lander, Mutanten und Menschen, ein gegnerischer
'     Schuss, eine Explosion, der Baiter), gezeichnet vom Programm (Cpc.cls) aus den Grafiken von INVASION.SPR,
'     umgewandelt in Mode 0 (CpcData.cls, mittels tools/cpcgfx.py): jeweils 8 x 16 Mode-0-Pixel, die
'     gleiche Größe auf dem Bildschirm
'   - Sterne im Hintergrund, die langsamer als der Boden scrollen, eine Landschaft mit 1024 Pixeln, der Scanner,
'     der anzeigt, wo sich der Bildschirm befindet (der Bildschirm zeigt 320 Pixel der Welt: ein Mode-0-Pixel entspricht
'     zwei Pixeln des Next)
'   - Sounds für alles über den AY (Sfx.cls): Laser, Explosionen, Entführungen, Rettungen, die
'     Smart Bomb, Hyperspace, der Schub
'   - der Verlust eines Schiffes startet die Welle nicht neu; ein kurzer Moment der Sicherheit danach; das Schiff dreht sich
'     flüssig um; Menschen laufen, fallen (aus großer Höhe sterben sie), werden gefangen und abgesetzt (jeweils 500);
'     der Baiter taucht auf, wenn sich eine Welle in die Länge zieht; ein Extra-Schiff und eine Smart Bomb alle 10.000 Punkte; die
'     Menschen und der Planet kehren jede fünfte Welle zurück
' Zwei Bildschirme (#C000 und #0000; das Programm ist ab #4000 aufgebaut); bei jedem Frame platziert Cpc.Draw die
' Sprites, den Boden und die Sterne auf dem nicht sichtbaren, Cpc.Flip blendet ihn ein.
' Ein CPC bewältigt einen Frame davon in 25–40 ms, und Cpc.Flip zeigt einen alle zwei 50stel einer   ' <--- GEÄNDERT
' Sekunde an, stabil (der Next schaffte fünfzig Mal pro Sekunde), jeder Frame hier entspricht also zwei des Next:   ' <--- GEÄNDERT
' Die Geschwindigkeiten sind verdoppelt und die Zeiten in Frames halbiert (der Sound wird zweimal pro Frame aktualisiert): das   ' <--- GEÄNDERT
' Tempo des Next.   ' <--- GEÄNDERT


2 „Gefällt mir“

main.bas

Option Explicit
Option Turbo
Option Org &H4000                 ' der zweite Bildschirm ist #0000-#3FFF (Cpc.cls); das Programm ab #4000

' Welt- & Bildschirmkonstanten (die Ansicht: 320 x 192 Next-Pixel, 160 x 192 Modus 0; y zählt aufwärts)
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

' Sprite-Muster-Indizes (CpcData.cls, aus 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      ' + dies: der zweite Frame von 0-3 (Schiff, Lander, Mutant, Mensch): Muster 5-8
Const PAT_SHOT   As Integer = 9
Const PAT_BOOM   As Integer = 10     ' 10-13
Const PAT_BAITER As Integer = 14     ' 14-15

' Sprite-Slot-Belegungen (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

' Entitäten-Anzahlen
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

' Feind-Zustände
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

' Rückgabewerte von EnemyStep und HumanStep (Z80)
Const EV_GRABBED As Integer = 1      ' der Lander hat einen Menschen ergriffen
Const EV_TAKEN As Integer = 2        ' er hat einen oben entführt: jetzt ein Mutant
Const EV_LASERED As Integer = 3      ' von Laser getroffen: seine Nummer in Bits 4-5
Const EV_RAMMED As Integer = 4       ' er hat das Schiff gerammt
Const EV_SHOOT As Integer = 64       ' und er feuert (es_x: seine horizontale Position im Sichtfeld)
Const EV_SETDOWN As Integer = 1      ' ein getragener Mensch wird abgesetzt
Const EV_CAUGHT As Integer = 2       ' ein fallender Mensch wird aufgefangen
Const EV_FELL As Integer = 3         ' einer ist zu tief gefallen
Const EV_LANDED As Integer = 4       ' einer ist gefallen und gelandet
Dim es_x As Integer

' Zeichenstifte (Cpc.cls: 0 Schwarz, 1 Weiß, 2 Grau, 3 Cyan, 4 Himmelblau, 5 Blau, 6 Gelb, 7 Orange,
' 8 Rot, 9 Dunkelrot, 10 Grün, 11 Dunkelgrün, 12 Limette, 13 Lila, 14 Rosa, 15 Pastellgelb)
Const PEN_DIVIDER As Integer = 13
Const PEN_SCANNER As Integer = 10

' Spieler-Status
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
' Werte dieses Frames: frames And 1, Mod 3, (\ 4) And 1, (\ 6) And 1 (eine Division kostet 1.100 Zyklen)
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

' Humanoide
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

' Feinde
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

' Feindliche Geschosse
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

' Explosionen (Welt x, y, verbleibende Frames)
Dim bx(MAX_BOOMS) As Integer, by(MAX_BOOMS) As Integer, bt(MAX_BOOMS) As Integer

' Die Landschaft: eine Höhe für jeweils 4 Pixel der Welt (256 davon)
Dim land(255) As Byte
Dim terDead As Boolean

' Die Anweisungen im CPC-Stil, die das Original nutzte (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

' ============================================================================
' SPRITE-HILFSFUNKTIONEN (Hardware-Sprite-Slots des Next, gezeichnet von Cpc.Draw)
' ============================================================================

' Die Sprite-Slots sind Cpc.Spr slot, x (0..319 über das Sichtfeld), y (0..191, aufwärts), pattern, mirror,
' shown und Cpc.Hide slot - direkt aufgerufen (ein dazwischenliegendes Sub kostet 100 Zyklen pro Aufruf)

' ============================================================================
' KOORDINATEN- & TERRAIN-HILFSFUNKTIONEN
' ============================================================================

' Die Landschaft, einmal erzeugt: Hügel und Berge wandern auf und ab (nicht mehr als 4 Pixel
' pro 4 Schritte waagerecht, damit die Linie lückenlos bleibt)
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
    ' die letzten wenigen führen zurück zum Anfang, sodass sie rundherum nahtlos abschließen
    For k = 248 To 255
        land(k) = land(247) + (land(0) - land(247)) * (k - 247) \ 9
    Next k
End Sub

' Rand(n): 0 bis n - 1 aus rs, jedes Mal gleich bei demselben Seed (die Landschaft und die
' Sterne werden daraus generiert). Eine 16-Bit-Sequenz: die ursprüngliche 32-Bit-Variante brauchte 10.000 Zyklen pro Durchlauf
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): wo Welt-x wx im Sichtfeld von cx liegt, -512 bis 511.
' Das Radar: Pixel des Next, 64 + ((wx - px + 512) And 1023) \ 8 waagerecht im Kasten (das Schiff in der
' Mitte), 153 + y \ 7 vom unteren Rand nach oben: Cpc.RDot wx, px, y, pen zeichnet dort einen Punkt
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   ' <--- NEU (was das HUD anzeigt: nur Änderungen werden gedruckt)
Sub UpdateHUD()
    If score <> hudScore Then                                  ' <--- NEU
    Pen 15
    At 1, 1
    PrintS Right$("000000" & Mid$(Str$(score), 2), 7)
    hudScore = score                                           ' <--- NEU
    End If                                                     ' <--- NEU
    If lives <> hudLives Then                                  ' <--- NEU
    Pen 14
    At 10, 1
    PrintS "SHIPS" & Str$(lives) & " "
    hudLives = lives                                           ' <--- NEU
    End If                                                     ' <--- NEU
    If bombs <> hudBombs Then                                  ' <--- NEU
    Pen 12
    At 19, 1
    PrintS "BOMBS" & Str$(bombs) & " "
    hudBombs = bombs                                           ' <--- NEU
    End If                                                     ' <--- NEU
    If wave <> hudWave Then                                    ' <--- NEU
    Pen 11
    At 27, 1
    PrintS "WAVE" & Str$(wave) & " "
    hudWave = wave                                             ' <--- NEU
    End If                                                     ' <--- NEU
    hudDirty = 0
End Sub

Sub DrawStaticFrame()
    ClsAll
    hudScore = -1 : hudLives = -1 : hudBombs = -1 : hudWave = -1   ' <--- NEU (der Bildschirm ist geleert: das gesamte HUD wird neu ausgegeben)
    ' Scanner-Trennlinie (Zeile 43) und Radarkasten (Next-Koordinaten x 62..193, y 152..182 vom
    ' unteren Rand: CPC-Pixel 31..96, Zeilen 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

' Die Sterne: über den Himmel verteilt (Zeilen 46-155), gezeichnet von Cpc.Draw, scrollen mit einem
' Viertel der Bodengeschwindigkeit
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

' ============================================================================
' WELLEN- & ENTITÄTEN-VERWALTUNG
' ============================================================================

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

    ' die Menschen: alle zu Beginn, und jede fünfte Welle wieder vollzählig
    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

' Der Baiter: eine schnelle Untertasse, die Jagd auf dich macht, wenn eine Welle zu lange dauert
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                  ' der Himmel weiß
    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

' Das Schiff wurde getroffen: Es explodiert, die anderen weichen etwas zurück, ein kurzer Moment Schutz, und weiter
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
    ' Feinde in der Nähe des Schiffes werden wegbewegt, Schüsse entfernt
    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

' Eine Nachricht mitten im Spielbereich für eine bestimmte Zeit
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                             ' <--- GEÄNDERT (vorher Len(m) \ 2: eine Division)
    PrintS m
    msgT = t : msg = m
End Sub

' ============================================================================
' DIE BESTANDTEILE EINES FRAMES
' ============================================================================

Sub Controls()
    kb = Cpc.Keys()
    ' Hoch: Q oder Joy Hoch | Runter: A oder Joy Runter
    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

    ' Links: O oder Joy Links | Rechts: P oder Joy Rechts
    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

    ' das Schiff sitzt seitlich versetzt, um Raum nach vorn zu lassen: beim Umdrehen gleitet es herüber
    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

    ' Laser abfeuern: Leertaste, Z oder Joy Feuer 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

    ' Smart-Bombe: B, M oder Joy Feuer 2
    If bomb_cd > 0 Then bomb_cd = bomb_cd - 1
    If (kb And 32) And bomb_cd = 0 Then TriggerSmartBomb(cam_x)

    ' Hyperraum: H: irgendwohin, rein zufällig
    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: Die Schüsse fliegen weiter und werden gezeichnet (Z80); lscr und lasersOn für Treffererkennung der Feinde
Sub Lasers()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

Sub Humans()
    ' Jeder Mensch wird von HumanStep (Z80) bewegt und gezeichnet; was zurückkommt, ist vom Spiel zu verarbeiten
    Dim ev As Integer
    i = 0                                                      ' <--- NEU
    Do While i < MAX_HUMANS                                    ' <--- GEÄNDERT (vorher For i = 0 To MAX_HUMANS - 1)
        ev = HumanStep(i)                                      ' <--- GEÄNDERT (Z80: ab Mensch i weiter bis zum ersten mit einer Aktion; i = dieser Mensch)
        If ev Then                                             ' <--- NEU (nichts zu tun: keine Tests)
        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
            ' zu tiefer Sturz
            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                                                 ' <--- NEU
        i = i + 1                                              ' <--- NEU
    Loop                                                       ' <--- GEÄNDERT (vorher Next i)
End Sub

' Keine Menschen mehr übrig: Der Planet wird vernichtet, und jeder Lander mutiert
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
    ' der Baiter: nach 40 Sekunden, danach alle 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

    ' Jeder Feind wird von EnemyStep (Z80: es waren 90 Befehlswörter pro Feind) bewegt, gezeichnet und geprüft;
    ' was zurückkommt, ist vom Spiel zu verarbeiten
    i = 0                                                      ' <--- NEU
    Do While i < MAX_ENEMIES                                   ' <--- GEÄNDERT (vorher For i = 0 To MAX_ENEMIES - 1)
        ev = EnemyStep(i)                                      ' <--- GEÄNDERT (Z80: ab Feind i weiter bis zum ersten mit einer Aktion; i = dieser Feind)
        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                                              ' <--- NEU
    Loop                                                       ' <--- GEÄNDERT (vorher Next i)
End Sub

' EnemyStep(i): Frame von Feind i - wie Enemies es in BASIC hatte (die ZX Spectrum Next-Version
' Main.bas enthält die gesamte Logik): gesteuert durch seinen Zustand (umherwandern, auf einen Menschen
' herabsinken, einen entführen, Mutant, Baiter), im Spielfeld gehalten, gezeichnet mit seinem Radar-Punkt
' (cp_showr), dann gegen Laser und Schiff geprüft, und ob er feuert. Gibt EV_... zurück
' (0 = nichts zu tun), es_x = horizontale Position im Sichtfeld
Function EnemyStep(ByVal n As Integer) As Integer   ' <--- GEÄNDERT (n: {i} unten ist das i des Spiels)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' HumanStep(i): Frame von Mensch i, wie Humans es in BASIC hatte: läuft am Boden herum, wird vom
' Schiff transportiert (beim Erreichen des Bodens abgesetzt), fällt (vom Schiff gefangen oder gelandet - aus
' zu großer Höhe tot); mit Radar-Punkt gezeichnet. Gibt EV_... zurück (0 = nichts zu tun)
Function HumanStep(ByVal n As Integer) As Integer   ' <--- GEÄNDERT (n: {i} unten ist das i des Spiels)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function


' Schuss von Feind n bei Bildschirm-x esx, gezielt auf das Schiff
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

' Schüsse: Feindliche Schüsse bewegen sich weiter und werden gezeichnet (Z80); ein Treffer am Schiff beendet es
Sub Shots()
    If ShotsStep() Then LoseLife()
End Sub

' ShotsStep(): Frame der Schüsse: True, wenn einer das Schiff getroffen hat (es ist zerstört)
Function ShotsStep() As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Explosionen: Die Frames der Explosionen (Z80)
Sub Booms()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Markierungen des Scanners für den sichtbaren Weltausschnitt (knapp außerhalb des Kastens, darüber
' und darunter): Cpc.Draw positioniert sie bei m und 40 Radarpixel weiter
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

' ============================================================================
' HAUPTPROGRAMM-EINSTIEG
' ============================================================================

Mode 0
CpcData.Data                      ' (die Tabellen: wird nie ausgeführt, hält sie im Programm)
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                       ' <--- GEÄNDERT (vorher f3 = frames Mod 3: eine Division)
        If f3 = 3 Then f3 = 0                                  ' <--- NEU (frames Mod 3 als Zähler)
        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))   ' <--- GEÄNDERT (nur dort, wo der Text steht: vorher At 1, 11 : PrintS Space$(32), 64 Zeichen gezeichnet)
            ElseIf Cpc.Touched() Then
                Pen 14 : At 17 - Cpc.Shr(Len(msg), 1), 11 : PrintS msg   ' <--- GEÄNDERT (ein Sprite hat Teile überdeckt: neu gezeichnet; vorher Message msg, msgT: zwei Kopien des Strings)
            End If
        End If

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

        ' Welle abgeschlossen: Ein Bonus für jeden verbliebenen Menschen
        If enemies_alive <= 0 And enemies_to_spawn <= 0 And Not game_over Then
            WaveDone
        End If
    Loop

    ' Game-Over-Bildschirm
    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                ' (der Text erscheint dort, wo die Sprites waren)
    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"   ' <--- GEÄNDERT (vorher 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                                         ' <--- NEU
    At 1, 22 : PrintS "(C) 2026 CYBORG UNICORN PTY LTD"   ' <--- NEU
    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
        ' das Schiff fliegt am unteren Rand vorbei
        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

' ============================================================================
' CPC-BEFEHLE DES ORIGINALS
' ============================================================================

' Pen n: Schriftfarbe für nachfolgenden Text (Stiftnummern des Originals, als Cpc.cls-Stifte)
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: Rahmenfarben des Originals (0 Schwarz, 2 Rot, 7 Weiß)
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: Textposition (1, 1 = oben links des 32 x 24 Bildausschnitts des Next: Spalte 4
' der 40 des CPC, Zeile 0 seiner 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: Bild wird unsichtbar gerendert und beim Bildrücklauf angezeigt; Sound läuft weiter
Sub Vsync()
    Cpc.Draw cam_x
    Cpc.Flip
    Sfx.Update
    Sfx.Update                     ' (zweimal: ein Frame hier entspricht zwei Frames des Next)
End Sub

' Das Terrain: Cpc.Draw zeichnet es anhand der Höhendaten (Heights); nur die Zerstörung des Planeten ändert sie
Sub Terrain(ByVal cx As Integer)
    If terDead <> planet_dead Then Heights : terDead = planet_dead
End Sub

' Die Bodenlinie für jeden x-Wert der Welt (von oben nach unten), interpoliert zwischen den 4-Pixel-Schritten
' von 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 „Gefällt mir“

cpc.cls

' Cpc - Bildschirm von INVASION auf dem Amstrad CPC: Modus 0 (160 x 200, 16 Pens), zwei Bildschirme (#C000
' und #0000-#3FFF: das Programm baut ab #4000 auf), Software-Sprites als Ersatz für die
' Hardware-Sprites des Next und die schnelle Arithmetik, die die Spielschleife benötigt.
' (c) 2026 Cyborg Unicorn Pty Ltd
'
' In jedem Frame setzt das Spiel seine Sprites (Spr: ein Slot, seine Position, sein Muster – wie die
' Slots des Next bleiben sie bis zur Änderung erhalten; Hide), die Radarpunkte (RDot, Dot) und Sichtmarkierungen (Mark) sowie
' etwaigen Text (Text: direkt auf beide Bildschirme). Anschließend zeichnet Draw den Frame auf den nicht
' sichtbaren Bildschirm: den Boden (nur bei Bewegung des Sichtfelds: die Bytes der vorherigen Position geschwärzt, dann ein   ' <--- CHANGED
' oder zwei Pixel pro Spalte), die Sterne (die alten aus, die neuen an), die von einem Sprite   ' <--- CHANGED
' geschwärzten Linien, dann die Sprites (kompilierter Code). Flip zeigt ihn beim zweiten Frame-Flyback seit   ' <--- CHANGED
' dem letzten Flip – 25 Frames pro Sekunde, stabil, unabhängig davon, wie lange ein Frame unter 40 ms brauchte (cp_tick zählt   ' <--- CHANGED
' die Zeit) – und bereitet den anderen Bildschirm vor: die Sprites von vor zwei Frames geschwärzt, sein   ' <--- CHANGED
' Radar gelöscht. Unter einem Sprite wird nichts beibehalten: Alles, was überdeckt werden kann, wird neu gezeichnet,   ' <--- CHANGED
' außer Text (Touched meldet, wann die Nachrichtenzeile getroffen wurde und welche Buchstaben).   ' <--- CHANGED
' Die Koordinaten der Sprites entsprechen denen des Next (x 0-319 über das Sichtfeld, y von unten nach oben wie beim
' Plot des Originals); ein 16 x 16-Muster wird zu 8 x 16 Modus-0-Pixeln (CpcData.cls).
' Text: 40 Spalten zu 4 Pixeln (die schmale Schriftart), 25 Zeilen zu 8 Zeilen. Pixel-Aufrufe (Dot, HLine,
' Big) verwenden Modus-0-Pixel, 0-159 horizontal, Zeilen 0-199 vertikal.
' Alles davon ist Z80 (#asm); der Block von Draw enthält die Tabellen und Routinen, die von den anderen aufgerufen werden,
' daher muss ein Programm Draw verwenden (was alle tun).
' Interrupts: IM 2 (eine 257-Byte-Tabelle: jeder Bus-Wert funktioniert), nur aktiv, während der Z80 von Cpc, Sfx und dem Spiel auf dem   ' <--- NEW
' echten Stack läuft; der BASIC-Thread (SP ist der Thread) läuft wie zuvor bei deaktivierten Interrupts.   ' <--- NEW
' Speicher hinter dem Programm (nicht in der Datei): Tabellen von Text, die Linientabellen, die Listen von Boden und   ' <--- NEW
' Sternen (ntc_free aufwärts bis etwa #AD00: das Firmware-RAM wird nach dem Start nicht mehr genutzt).   ' <--- NEW
Option Explicit

Public Const Slots = 33

' Init: die 16 Inks und der Rand, die Linientabellen, der Bildschirm bei #C000 angezeigt (der andere ist
' #0000-#3FFF: das Programm baut ab #4000 auf), noch nichts gezeichnet. Nach Mode 0
Public Sub Init()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Ink pen, ink: CPC-Ink (0-26) eines Pens (0-15, 16 = der Rand)
Public Sub Ink(ByVal pen As Integer, ByVal ink As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Spr slot, x, y, pattern, mirror, shown: Slot 0-32 zeigt Muster (0-15) mit Mittelpunkt bei x
' (0-319 über das Sichtfeld), y (von unten nach oben, 0-191), gespiegelt, falls mirror; oder ausgeblendet
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 ausgeblendet
Public Sub Hide(ByVal slot As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' HideAll: alle Slots ausgeblendet
Public Sub HideAll()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Text col, row, s, pen: s ab Spalte col (0-39), Zeile (0-24) in pen (schwarz dahinter), auf beiden
' Bildschirmen. Buchstaben 32-95 (Kleinbuchstaben als Großbuchstaben dargestellt). Ein Sprite über dem Text nimmt diesen
' beim Verlassen mit (Flip schwärzt den vorherigen Bereich): Touched meldet, wenn dies auf
' der Nachrichtenzeile geschehen ist
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(): Einmalig True, wenn seit dem letzten Aufruf ein Sprite auf der Nachrichtenzeile (Zeilen 80-87)
' auf einem der Bildschirme geschwärzt wurde: Die Nachricht muss erneut ausgegeben werden (und das Text,
' das sie ausgibt, zeichnet dann auf jedem Bildschirm nur die geschwärzten Buchstaben)   ' <--- CHANGED
Public Function Touched() As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Dot x, y, pen: Ein Radarpunkt (Pixel x 0-159, Zeile y 0-199) auf dem aktuell gezeichneten Bildschirm (das
' Innere des Radars wird bei jedem Flip gelöscht)
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: Der Radarpunkt für Welt-x wx (das Schiff bei px befindet sich in der Mitte des
' Kastens), y (von unten nach oben): bei Pixel 32 + ((wx - px + 512) And 1023) \ 16, Zeile 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): Eine Entität der Welt bei wx, y (von unten nach
' oben) in einem Durchgang: ihr Sprite in slot (angezeigt an der Position im Sichtfeld ab cx, wenn dieser
' -8 bis 327 ist, andernfalls ausgeblendet), ihr Radarpunkt in pen (keiner, wenn pen -1 ist), und Rückgabe
' der Position im Sichtfeld (-512 bis 511). Ein Schlüsselwort für das, was vorher zehn benötigte. (Z80 im Spiel
' füllt cp_shb und ruft cp_showr auf: dieselbe Funktion, kein Schlüsselwort)
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): Wo Welt-x wx im Sichtfeld ab cx liegt: -512 bis 511
Public Function ScrX(ByVal wx As Integer, ByVal cx As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Mark x: Die Sichtmarkierungen bei Radar-x (die des Next: 62-153), auf dem gezeichneten Bildschirm: ein Byte
' (zwei weiße Pixel) bei x / 4 und 10 Bytes weiter, Zeilen 7 und 41 (beim nächsten Flip entfernt, wie
' auch die Punkte). -1 = keine
Public Sub Mark(ByVal x As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Star k, x, y, pen: Stern k (0-31) bei x (0-319 am Himmel), Zeile y, in pen. (Gespeichert als x,
' Zeilenoffset im Bildschirm und linke Pixel-Bits des Pens: kein Nachschlagen beim Zeichnen nötig)
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: Die Bodenlinie (0-199) bei Welt-x wx (0-1023, gerade: die ungeraden werden nicht gespeichert)
Public Sub Height(ByVal wx As Integer, ByVal y As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' GroundAt(wx): Die Höhe des Bodens (von unten nach oben) bei Welt-x wx aus der Höhentabelle
' (Height), in 100 Zyklen – GetTerrainY des Spiels benötigte 500
Public Function GroundAt(ByVal wx As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Ground on: Was Draw neben den Sprites zeichnet: 1 den Boden, 2 die Sterne, 4 das Radar (dessen
' Inneres bei jedem Flip gelöscht und dessen Linien neu gezeichnet werden). Zusammengerechnet: 7 im Spiel, 2 beim Titel, 0 beim
' Game-Over-Bildschirm
Public Sub Ground(ByVal on As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Cls: Beide Bildschirme schwarz, auf keinem etwas beibehalten
Public Sub Cls()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' HLine x, y, w, pen: Eine horizontale Linie ab Pixel x, Zeile y, w Pixel breit, auf beiden Bildschirmen
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 in den CPC-eigenen 8 x 8-Zeichen, zwei Pixel breit und drei Zeilen hoch für jedes
' Pixel (16 x 24), schattiert von Pastellgelb nach Rot mit dunkelrotem Schatten, ab Pixel x (gerade),
' Zeile y, auf beiden Bildschirmen
Public Sub Big(ByVal x As Integer, ByVal y As Integer, ByVal s As String)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Flip: Auf den zweiten Frame-Flyback seit dem letzten Flip warten (den nächsten, falls dieser bereits
' vorbei ist), den Bildschirm anzeigen, auf den Draw gezeichnet hat, ab jetzt auf den anderen zeichnen   ' <--- CHANGED
Public Sub Flip()
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

' Shr(x, n): x (0-65535, als unsigned) um n Bits nach rechts verschoben: x \ 2, 4, 8 ... ohne Division
' (welche etwa 1.100 Zyklen benötigt; dies benötigt 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 für x 0-65535 (unsigned), d 1-255: etwa 400 Zyklen (die Division braucht 1.100),
' 250 für x unter 256
Public Function Div(ByVal x As Integer, ByVal d As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Rnd(n): Eine Zufallszahl von 0 bis n - 1 (n 1-255), in etwa 350 Zyklen (Rnd von NTC benötigt 1.200)
Public Function Rnd(ByVal n As Integer) As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Keys(): Die gehaltenen Tasten: 1 oben (Q, Joystick oben), 2 unten (A), 4 links (O), 8 rechts (P), 16 Feuer
' (Leertaste, Z, Feuer 1), 32 Smart Bomb (B, M, Feuer 2), 64 Hyperspace (H)
Public Function Keys() As Integer
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Function

' Draw cx: Der Frame auf dem gezeichneten Bildschirm (der nicht sichtbare: Flip hat seine alten
' Sprites entfernt und sein Radar gelöscht), das Sichtfeld ab Welt-x cx: der Boden und die Sterne
' (Ground), die Sprites. Anschließend Flip
Public Sub Draw(ByVal cx As Integer)
    ' INLINE ASSEMBLY LANGUAGE REMOVED
End Sub

sfx.cls

' Sfx - Soundeffekte auf dem AY-Chip des CPC über die PPI (Port A #F4xx überträgt die Daten,
' Port C #F6xx die Steuerbits des AY). Drei Kanäle, auf denen jeweils ein Effekt gleichzeitig abgespielt wird:
' ein Ton oder Rauschen, dessen Tonhöhe gleitet und dessen Lautstärke über die Gesamtdauer auf null abfällt.
' (c) 2026 Cyborg Unicorn Pty Ltd
' Update einmal alle 1/50 Sekunde aufrufen; hier ruft Vsync es zweimal pro Frame auf, und der AY wird beim
' zweiten der beiden Male beschrieben (die Schreibvorgänge des ersten wurden direkt überschrieben)   ' <--- GEÄNDERT
'   Sfx.Play 0, 200, 8, 15, 12, 0     ' Kanal A: Ton-Periode 200, steigende Tonhöhe... 12 Frames
Option Explicit

Private per(2) As Integer         ' die aktuelle Ton-Periode (12 Bits; Rauschen: 0-31)
Private slide(2) As Integer       ' wird jedem Frame hinzuaddiert
Private vol(2) As Integer         ' die Anfangslautstärke (0-15)
Private total(2) As Integer       ' Gesamtlänge in Frames
Private left(2) As Integer        ' verbleibende Frames (0 = stumm)
Private nse(2) As Integer         ' 1 = Rauschen, 0 = Ton

Private Sub W(ByVal r As Integer, ByVal v As Integer)
    ' INLINE-ASSEMBLER-CODE ENTFERNT
End Sub

' Init: alle Kanäle stummschalten
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: startet einen Effekt auf Kanal 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, solange Kanal ch abgespielt wird
Public Function Busy(ByVal ch As Integer) As Integer
  Busy = (left(ch) > 0)
End Function

' Update: einmal pro Frame - die Tonhöhen gleiten, die Lautstärken faden, der AY wird beschrieben (Z80: wird
' zweimal pro Frame aufgerufen)
Public Sub Update()
    ' INLINE-ASSEMBLER-CODE ENTFERNT
End Sub

' Quiet: alle Kanäle angehalten
Public Sub Quiet()
  Dim i As Integer
  For i = 0 To 2
    left(i) = 0
    W 8 + i, 0
  Next
End Sub

und schließlich cpcdata.cls

' CpcData - Sprites und schmale Schriftart von INVASION für den Amstrad CPC, Modus 0 (erstellt mit
' tools/cpcgfx.py aus INVASION.SPR: nicht bearbeiten). Die Sprites sind kompiliert: jedes der 16
' Muster, normal und gespiegelt (nur das Schiff, die Menschen und der Blitz besitzen eine gespiegelte Version:
' die Einträge der anderen verweisen auf das normale), als Z80-Code, der es an HL zeichnet (sein oberstes linkes
' Byte) – ein ganzes Byte geschrieben, ein halbes maskiert eingefügt, ein leeres übersprungen – eine Zeile zur
' nächsten über #800 (cp_nextw nach dem achten einer Zeile: Cpc.cls). cd_spr ist eine Tabelle mit 32 Einträgen (Muster * 2 + Spiegelung): xoff,
' yoff, w (Bytes), h, die Adresse des Codes. Sprites werden an geraden Pixeln gezeichnet (einer Bytegrenze).
' cd_font: 64 Zeichen (32-95) aus 8 Zeilen, eine 4-Pixel-Zeile in den Bits 7-4. cd_inks: die CPC-Farbtinten-Nummern
' der 16 Stifte.
' (c) 2026 Cyborg Unicorn Pty Ltd
Option Explicit

' Data: wird nie aufgerufen – enthält die Tabellen und den Sprite-Code (globale Labels, verwendet von Cpc.cls)
Public Sub Data()
' INLINE-ASSEMBLER-CODE ENTFERNT
End Sub

Zu was wird der obige Code assembliert? Hier ist ein Ausschnitt.

; ============================================================
; NTC-Programm – generiert von NTC BASIC (BASIC im VB-Stil nach NTC)
; Plattform: cpc (Amstrad CPC – direkte Hardware, keine Firmware), startet im Turbomodus
; ============================================================

			DEVICE AMSTRADCPC6128		; für SAVEAMSDOS am Ende
			org #4000

NTC_TERM	equ 0		; Option Terminal: 0 keine, 1 VT52, 2 ANSI (CP/M)
NTC_STRMAX	equ 40		; die längste Zeichenkette (Bibliothekspuffer, temporäre Daten)
NTC_MEMBANKS	equ 64		; Option Memory: die maximale Anzahl an 64K-Bänken, nach denen die Memory-Klasse sucht
NTC_ROMENABLE	equ 0		; Option RomEnable (cpcfw, cpcmin): Der Startcode initialisiert die ROMs
NTC_ROMLIB	equ 0		; 1 = die Bibliothek ist das Keyword-ROM (Library: ROM; ntcRomAsm erstellt es)
NTC_WINHOLE	equ 0		; 1 = das Programm ist um einen Option Reserve-Bereich über dem Fenster #4000-#7FFF herum aufgebaut
NTC_BUFSIZE	equ 2048		; der Puffer der Bibliothek (ntc_buf: FileSystem, Memory.Copy): Option Buffer
NTC_CPMGFX	equ 0		; 1 = CP/M: Grafik zeichnet auf dem Bildschirm eines Amstrad CPC (2.2: #C000, Plus: Block 1 aus dem gemeinsamen Speicher)

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

; ============================================================
; Haupt-Thread
; ============================================================

main_thread:
			dw kw_bssinit, ntc_bss, ntc_bss_size		; die Arrays: Passen sie, lösche sie

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

			; Main.bas 1976: CpcData.Data                      ' (die Tabellen: wird nie ausgeführt, behält sie im Programm)
			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                       ' <--- GEÄNDERT (war f3 = frames Mod 3: eine Division)
			dw kw_andvl, var_frames, 1, var_f2
			dw kw_inc, var_f3

			; Main.bas 1999: If f3 = 3 Then f3 = 0                                  ' <--- NEU (frames Mod 3 als Zähler)
			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))   ' <--- GEÄNDERT (nur dort, wo sich die Nachricht befindet: war At 1, 11 : PrintS Space$(32), 64 gezeichnete Buchstaben)
			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   ' <--- GEÄNDERT (ein Sprite hat einen Teil davon mitgenommen: nochmals; war Message msg, msgT: zwei Kopien der Zeichenkette)
			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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

p_updatehud:

			; Main.bas 208: If score <> hudScore Then                                  ' <--- NEU
			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                                           ' <--- NEU
			dw kw_l_mov, var_score, var_hudscore

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

			; Main.bas 214: If lives <> hudLives Then                                  ' <--- NEU
			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                                           ' <--- NEU
			dw kw_vtov, var_lives, var_hudlives

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

			; Main.bas 220: If bombs <> hudBombs Then                                  ' <--- NEU
			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                                           ' <--- NEU
			dw kw_vtov, var_bombs, var_hudbombs

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

			; Main.bas 226: If wave <> hudWave Then                                    ' <--- NEU
			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                                             ' <--- NEU
			dw kw_vtov, var_wave, var_hudwave

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

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

; ============================================================
; sub drawstaticframe (Turbomodus)
; ============================================================

p_drawstaticframe:

			; Main.bas 236: ClsAll
			dw p_clsall_c

			; Main.bas 237: hudScore = -1 : hudLives = -1 : hudBombs = -1 : hudWave = -1   ' <--- NEU (der Bildschirm ist gelöscht: das gesamte HUD wird ausgegeben)
			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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

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 (Turbomodus)
; ============================================================

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

Wie du siehst, sieht das Programm wie Daten aus – das ist es und ist es auch wieder nicht. SP wird als PC umfunktioniert – das bedeutet, dass das Programm direkt von einem Schlüsselwort zum nächsten springen kann, ganz ohne den Overhead von Funktionsaufrufen und Parameterübergaben. Aus diesem Grund können sowohl auf dem ZX Spectrum Next als auch auf dem Amstrad CPC so schnelle, die Hardware voll ausreizende Spiele (besonders auf dem ZX Spectrum Next) in BASIC geschrieben werden, da BASIC perfekt zu diesem Konstrukt passt. C? Als syntaktischer Zucker ja, aber so, wie es typischerweise unter der Haube funktioniert – nein. NTC BASIC lässt dich in JavaScript (ES5±Subset) oder PHP (Subset) programmieren, falls dir der Stil von Visual BASIC 5/6 nicht gefällt. Das funktioniert, indem JavaScript oder PHP per Präprozessor vor dem Kompilieren in NTC BASIC übersetzt werden – was vorhersehbares Verhalten und nur eine einzige Quelle für Compiler-Bugs bedeutet. Alle Variablen sind ausnahmslos statisch – warum das wichtig ist? Man verliert die Rekursion – gewinnt dafür aber an Stabilität – es gibt keinen Heap. Kann man sich einen Heap basteln? Natürlich, aber es ist besser, eigene Funktionen zu schreiben, um den Speicher so zu verwalten, wie man möchte.

CyborgOS (der Z80-Hypervisor) ist ebenfalls in NTC BASIC geschrieben (mit einer ganzen Menge Inline-Assembler). Der gesamte Code befindet sich im Repo für CyborgOS für die CPC-, ZX Spectrum Next- und MSX-Builds.

Klassen gibt es in NTC BASIC, um zusammengehörige Funktionen als syntaktischen Zucker zu bündeln, aber auch um sicherzustellen, dass sie innerhalb einer Speicherbank (Bank) bleiben. Das ermöglicht die Kontrolle über das Memory Mapping für große Programme mit bis zu 4 Megabyte! Oder wie bei CyborgOS: 20 gleichzeitig laufende Instanzen von CP/M-Programmen, wobei auf 4-MB-Rechnern immer noch mehr als die Hälfte des RAMs frei ist. Dieser zusätzliche RAM könnte für noch mehr Instanzen genutzt werden, aber … er wird für Treiber verwendet, für „den Heap“, der in einer Linked-List-Klasse gekapselt ist, für RAM-Disks usw. – man kann RAM-Disks im laufenden Betrieb erstellen und verwerfen. Jede CP/M-Instanz weiß nichts von den anderen Instanzen – und es müssen auch keine CP/M-Instanzen sein – es könnte genauso gut eine POSIX-Shell sein, was ich mir noch überlege. Klassen können interruptgesteuert sein und in ihren eigenen Bänken ausgelöst werden – wenn eine Anwendung viele Daten anfordert, fragt sie diese an, und der Kernel, der bei C0 oder C2 liegen kann (getestet, aber aktuell wird noch C0 verwendet), liefert sie blockweise wie ein virtueller DMA (aus Sicht der Anwendung ist es DMA).

Und für nicht erweiterte 464er? Da kann man noch etwa 12 KB herausholen, indem man die NTC-BASIC-ROMs nutzt, die die Schlüsselwörter enthalten – aber mal ehrlich? Kauf dir lieber eine Speichererweiterung – 1 MB wirkt bei deinem Rechner wahre Wunder! 4 MB ist alles, was du jemals brauchen wirst.

Ist NTC BASIC so schnell wie SDCC? Nein – und in Wirklichkeit ist SDCC auch nicht schneller – es kommt darauf an, was man macht. NTC BASIC ist kein besonders stark optimierender Compiler – es werden nur einige grundlegende Optimierungen vorgenommen. Aber es bietet so vieles, was SDCC nicht hat – wie die volle Nutzung von Overlays und Pages (der feine Unterschied ist: Overlays ersetzen sich gegenseitig bei Rechnern mit wenig RAM – oder bei CP/M-Instanzen, die keine neueren Techniken nutzen wollen), während Pages in den Speicherbänken resident bleiben.

1 „Gefällt mir“