'===========================================================================
' Subject: WORMHOLE V3.0                      Date: 11-01-96 (16:21)       
'  Author: Brent P. Newhall                   Code: QB, QBasic, PDS        
'  Origin: comp.lang.basic.misc             Packet: GAMES.ABC
'===========================================================================
' Wormhole
' Version 3.0.0
' by Brent P. Newhall
' This program is PUBLIC DOMAIN.  Anyone may use it however they
'   wish, provided that credit is given where it's due for coding.

' Version 1.0.0 -- Original release.
' Version 1.0.1 -- QBASIC compatibility assured, unneeded variables
'                  removed.
' Version 2.0.0 -- Longer missiles, multiple missiles, blue
'                  wormhole and discontinuities (long blue things),
'                  improved explosions, fading title, improved
'                  score handling, pause function, better graphics
'                  handling, blue good guys, history, QBASIC info.
' Version 3.0.0 -- Better stars, corrected input, energy, energy
'                  powerup, better graphics handling, more
'                  descriptive variables, bombers, fighters,
'                  kamikazes instead of original baddie, speed
'                  bonus, firepower bonus, smart bomb bonus, intro,
'                  levels

' My highest score: 11336 points and 183 aliens killed.  Beat that!

TYPE MonsterType
  x AS INTEGER
  y AS INTEGER
  degree AS INTEGER
  radius AS SINGLE
  speed AS SINGLE
  exist AS INTEGER
  dying AS INTEGER
  AlienType AS INTEGER
END TYPE
TYPE StarType
  x AS INTEGER ' World coordinates
  y AS INTEGER
  z AS INTEGER
  sx AS INTEGER ' Screen coordinates
  sy AS INTEGER
  spd AS INTEGER ' Speed
END TYPE
TYPE VertexType
  x AS SINGLE
  y AS SINGLE
END TYPE
TYPE PolygonType
  BorderColor AS INTEGER
  FilledColor AS INTEGER
  filled AS INTEGER
  lxo AS INTEGER
  lyo AS INTEGER
  NumVertices AS INTEGER
END TYPE
TYPE MissileType
  radius AS INTEGER
  x1 AS INTEGER
  y1 AS INTEGER
  x2 AS INTEGER
  y2 AS INTEGER
  angle AS INTEGER
END TYPE

CONST PI = 3.14159, DEG2RAD = PI / 180
CONST FALSE = 0, TRUE = NOT FALSE
CONST TOT.STARS = 25 ' Increase for a prettier starfield effect
CONST TOT.ALIENS = 20 ' Decrease for QBASIC
CONST NUM.ALIEN.GRAPHICS = 8
CONST TOT.ENERGY = 50

DECLARE SUB Intro ()
DECLARE SUB DrawPolygon (poly AS PolygonType, Verts() AS VertexType)
DECLARE SUB ErasePolygon (poly AS PolygonType, Verts() AS VertexType)
DECLARE SUB RotatePolygon (poly AS PolygonType, Verts() AS VertexType, angle AS INTEGER)
DECLARE SUB ScalePolygon (poly AS PolygonType, Verts() AS VertexType, factor AS SINGLE)
DECLARE SUB Add (amt, var)

DIM SHARED alien(0 TO TOT.ALIENS) AS MonsterType
DIM SHARED star(1 TO TOT.STARS) AS StarType
DIM ship AS PolygonType
DIM ShipVert(1 TO 11) AS VertexType
DIM Missile(1 TO 12) AS MissileType
DIM SHARED SinLook(0 TO 360) AS DOUBLE
DIM SHARED CosLook(0 TO 360) AS DOUBLE
DIM SHARED AlienGraph(1 TO 200, 1 TO NUM.ALIEN.GRAPHICS) AS INTEGER
DIM SHARED ExplodeGraph(1 TO 200, 1 TO 5) AS INTEGER
DIM SHARED AlienMask(1 TO 200, 1 TO NUM.ALIEN.GRAPHICS) AS INTEGER
DIM melt(3000) AS INTEGER
DIM score AS LONG ' Can go up to 2 trillion
DIM SHARED MonstSpeed AS SINGLE

Intro
GOSUB Init
DO
  GOSUB MainProgramLoop
LOOP UNTIL quit > 0
GOSUB EndOfGame
END

MainProgramLoop:
nothing$ = INKEY$ ' Mask out INKEY$
t! = TIMER: WHILE t! = TIMER: WEND ' Delay
nothing$ = INKEY$ ' Mask out INKEY$
GOSUB DoEveryone ' Take care of enemies, ship, missiles, etc.
nothing$ = INKEY$ ' Mask out INKEY$
GOSUB DoInput ' Take care of input
RETURN

DoEveryone:
GOSUB DoColors
GOSUB DoStars ' REM for QBASIC
GOSUB DoWormhole ' REM for QBASIC
GOSUB MoveShip
GOSUB DoAliens
GOSUB DoMissiles
GOSUB DoScore
GOSUB DoBonuses
RETURN

DoColors: ' Flash monster and score colors
IF Beginning THEN
  LOCATE 5, 17: PRINT "WORMHOLE";
  IF GoingUp THEN
    ScoreColor% = ScoreColor% + 1
    IF ScoreColor% = 60 THEN GoingUp = FALSE: Staying = 1
  ELSEIF Staying THEN
    Staying = Staying + 1
    IF Staying = 20 THEN Staying = 0
  ELSE
    ScoreColor% = ScoreColor% - 1
    IF ScoreColor% = 0 THEN ' Begin game
      Beginning = FALSE
      LOCATE 5, 17: PRINT "        ";
      GOSUB CreateMonster ' Create one monster to start
    END IF
  END IF
ELSE
  MonstColor% = MonstColor% + 2
  IF MonstColor% > 60 THEN MonstColor% = 0
  PALETTE 101, MonstColor%
  ScoreColor% = ScoreColor% + ScoreMoving%
  IF ScoreColor% > 50 THEN
    ScoreMoving% = -1
  ELSEIF ScoreColor% < 20 THEN
    ScoreMoving% = 1
  END IF
END IF
PALETTE 102, ScoreColor% + ScoreColor% * 256 + ScoreColor% * 65536
RETURN

DoStars:
FOR cnt% = 1 TO TOT.STARS
  PSET (star(cnt%).sx, star(cnt%).sy), 0 ' Erase star
  star(cnt%).z = star(cnt%).z - star(cnt%).spd ' Update position
  IF star(cnt%).z < 1 THEN ' Out of bounds ?
    star(cnt%).x = INT(RND * 1000) - 500 ' Create new star!
    star(cnt%).y = INT(RND * 1000) - 500
    star(cnt%).z = INT(RND * 1000) + 1
    star(cnt%).sx = 160
    star(cnt%).sy = 100
    star(cnt%).spd = INT(RND * 40) + 10
  END IF
  ' Project 3d coordinates to 2d coordinates
  star(cnt%).sx = 64 * (star(cnt%).x / star(cnt%).z) + 160
  star(cnt%).sy = 64 * (star(cnt%).y / star(cnt%).z) + 100
  ' Draw star
   PSET (star(cnt%).sx, star(cnt%).sy), (32 - star(cnt%).z / 64)
NEXT cnt%
RETURN

DoWormhole:
CIRCLE (159, 99), wormholeradius1, 0 ' Erase old circles
CIRCLE (159, 99), wormholeradius2, 0
wormholeradius1 = INT(RND * 6) ' Create new circle
wormholeradius2 = INT(RND * 6)
CIRCLE (159, 99), wormholeradius1, 9 ' Draw new circles
CIRCLE (159, 99), wormholeradius2, 1
RETURN

MoveShip:
IF moving THEN
  ErasePolygon ship, ShipVert()
  posit% = posit% + moving * ShipSpeed ' Move ship
  IF posit% < 0 THEN posit% = posit% + 360
  IF posit% > 360 THEN posit% = posit% - 360
  ship.lxo = INT(ShipRadius * CosLook(posit%)) + 159
  ship.lyo = INT(ShipRadius * SinLook(posit%)) + 99
  IF EndOfLevel THEN
    ScalePolygon ship, ShipVert(), .9
  ELSE
    IF POINT(ship.lxo, ship.lyo) >= 100 THEN ' Ran into baddie?
      cnt% = 0
      WHILE cnt% < NumAliens
        cnt% = cnt% + 1
        IF alien(cnt%).x <= ship.lxo THEN
          IF alien(cnt%).y <= ship.lyo THEN
            IF alien(cnt%).x + 10 >= ship.lxo THEN
              IF alien(cnt%).y + 10 >= ship.lyo THEN
                energy% = energy% - 10
                IF energy% < 1 THEN quit = 2
                alien(cnt%).dying = 1
              END IF
            END IF
          END IF
        END IF
      WEND
    END IF
    RotatePolygon ship, ShipVert(), moving * ShipSpeed
  END IF
  moving = 0
END IF
DrawPolygon ship, ShipVert()
RETURN

DoAliens:
ToBeKilled% = 0
FOR cnt% = 1 TO NumAliens
  IF alien(cnt%).dying THEN
    PUT (alien(cnt%).x, alien(cnt%).y), ExplodeGraph(1, alien(cnt%).dying)
  ELSE
    PUT (alien(cnt%).x, alien(cnt%).y), AlienGraph(1, alien(cnt%).AlienType)
  END IF
NEXT cnt%
m% = 0
WHILE m% < NumAliens
  m% = m% + 1 ' Loop through, 1..NumAliens
  IF alien(m%).dying < 5 THEN ' If not at end of dying
    alien(m%).radius = alien(m%).radius + alien(m%).speed
    IF alien(m%).AlienType = 1 THEN ' Kamikaze
      IF alien(m%).degree < posit% THEN ' Move towards player
        alien(m%).degree = alien(m%).degree + 1
      ELSEIF alien(m%).degree > posit% THEN
        alien(m%).degree = alien(m%).degree - 1
      END IF
    ELSEIF alien(m%).AlienType = 2 THEN ' Bomber
      IF RND > .99 THEN ' Occasionally create a new fighter
        GOSUB CreateMonster ' Create, then erase created alien
        PUT (alien(NumAliens).x, alien(NumAliens).y), AlienGraph(1, alien(NumAliens).AlienType), XOR
        alien(NumAliens).radius = alien(m%).radius + INT(RND * 11 - 5)
        alien(NumAliens).degree = alien(m%).degree + INT(RND * 7 - 3)
        IF alien(NumAliens).degree < 0 THEN
          alien(NumAliens).degree = alien(NumAliens).degree + 360
        ELSEIF alien(NumAliens).degree > 360 THEN
          alien(NumAliens).degree = alien(NumAliens).degree - 360
        END IF
        alien(NumAliens).x = INT(alien(NumAliens).radius * CosLook(alien(NumAliens).degree)) + 154
        alien(NumAliens).y = INT(alien(NumAliens).radius * SinLook(alien(NumAliens).degree)) + 94
        alien(NumAliens).AlienType = 3 ' Create fighter
        PUT (alien(NumAliens).x, alien(NumAliens).y), AlienGraph(1, alien(NumAliens).AlienType), PSET
      END IF
    END IF
    alien(m%).x = INT(alien(m%).radius * CosLook(alien(m%).degree)) + 154
    alien(m%).y = INT(alien(m%).radius * SinLook(alien(m%).degree)) + 94
    IF alien(m%).dying THEN
      alien(m%).dying = alien(m%).dying + 1
      PUT (alien(m%).x, alien(m%).y), ExplodeGraph(1, alien(m%).dying), PSET
    ELSE
      PUT (alien(m%).x, alien(m%).y), AlienMask(1, alien(m%).AlienType), AND
      PUT (alien(m%).x, alien(m%).y), AlienGraph(1, alien(m%).AlienType), OR
    END IF
    IF alien(m%).radius > 90 THEN ' Outside orbit
      IF alien(m%).AlienType = 4 THEN ' Good guy
        score = score + 250
        ToBeKilled% = m%
      ELSEIF alien(m%).AlienType > 4 THEN ' Bonus!
        MonstersKilled% = MonstersKilled% - 1
        ToBeKilled% = m%
      ELSE ' Baddie
        energy% = energy% - 5
        IF energy% < 1 THEN
          quit = 2
        ELSE
          ToBeKilled% = m%
        END IF
      END IF
    END IF
  ELSE ' At end of dying
    ToBeKilled% = m%
  END IF
WEND
IF ToBeKilled% > 0 THEN
  m% = ToBeKilled%
  ' Erase out last vestiges of monster graphic
  LINE (alien(m%).x - 4, alien(m%).y - 4)-(alien(m%).x + 14, alien(m%).y + 14), 0, BF
  GOSUB KillMonster
END IF
IF NOT Beginning THEN
  IF NumAliensThisLevel < TotAliensThisLevel THEN
    IF RND > MonstChance THEN ' Create new alien occasionally
      GOSUB CreateMonster
    END IF
  ELSEIF NumAliens = 0 THEN ' Full of monsters, all destroyed
    GOSUB EndLevel
  END IF
END IF
RETURN

EndLevel:
level = level + 1
IF level < 6 THEN
  FOR lcnt% = 1 TO 25
    GOSUB DoColors
    GOSUB DoStars
    GOSUB DoWormhole
    t! = TIMER: WHILE t! = TIMER: WEND
  NEXT lcnt%
  energy% = TOT.ENERGY ' Refill energy
  NumAliensThisLevel = 0
  Add 10, TotAliensThisLevel
  Add -.005, MonstChance
  Add .05, MonstSpeed
  EndOfLevel = TRUE
  temp = ShipRadius
  FOR ShipRadius = temp TO 0 STEP -5
    LOCATE 24, 1: PRINT ShipRadius;
    moving = 1
    GOSUB DoColors
    GOSUB DoStars
    GOSUB DoWormhole
    GOSUB MoveShip
    t! = TIMER: WHILE t! = TIMER: WEND
  NEXT ShipRadius
  ShipRadius = temp
  EndOfLevel = FALSE
  RESTORE
  FOR cnt% = 1 TO ship.NumVertices ' Reset vertices
    READ ShipVert(cnt%).x, ShipVert(cnt%).y
  NEXT cnt%
  posit% = 0
  moving = 1
  GOSUB MoveShip
ELSE ' End of game; you won!
  quit = 3
END IF
RETURN

DoMissiles:
miss% = 0
WHILE miss% < NumMissiles
  miss% = miss% + 1 ' Loop through, 1..NumMissiles
  ' Erase missile
  LINE (Missile(miss%).x1%, Missile(miss%).y1%)-(Missile(miss%).x2%, Missile(miss%).y2%), 0
  IF Missile(miss%).radius < 2 THEN ' If at center
    GOSUB KillMissile
  ELSE
    Missile(miss%).radius = Missile(miss%).radius - 3 ' Move its coordinates
    Missile(miss%).x1 = INT(Missile(miss%).radius * CosLook(Missile(miss%).angle)) + 159
    Missile(miss%).y1 = INT(Missile(miss%).radius * SinLook(Missile(miss%).angle)) + 99
    Missile(miss%).x2 = INT((Missile(miss%).radius + 3) * CosLook(Missile(miss%).angle)) + 159
    Missile(miss%).y2 = INT((Missile(miss%).radius + 3) * SinLook(Missile(miss%).angle)) + 99
    IF POINT(Missile(miss%).x1%, Missile(miss%).y1%) >= 100 THEN
      cnt% = 0 ' Hit a monster! (front end)
      WHILE cnt% < NumAliens AND alien(cnt%).dying = 0
        cnt% = cnt% + 1 ' Find hit monster
        IF Missile(miss%).x1% >= alien(cnt%).x THEN
          IF Missile(miss%).y1% >= alien(cnt%).y THEN
            IF Missile(miss%).x1% <= alien(cnt%).x + 10 THEN
              IF Missile(miss%).y1% <= alien(cnt%).y + 10 THEN
                GOSUB HitSomething
              END IF
            END IF
          END IF
        END IF
      WEND
      GOSUB KillMissile
    ELSEIF POINT(Missile(miss%).x2%, Missile(miss%).y2%) >= 100 THEN
      cnt% = 0 ' Hit a monster! (back end)
      WHILE cnt% < NumAliens AND alien(cnt%).dying = 0
        cnt% = cnt% + 1 ' Find hit monster
        IF Missile(miss%).x2% >= alien(cnt%).x THEN
          IF Missile(miss%).y2% >= alien(cnt%).y THEN
            IF Missile(miss%).x2% <= alien(cnt%).x + 10 THEN
              IF Missile(miss%).y2% <= alien(cnt%).y + 10 THEN
                GOSUB HitSomething
              END IF
            END IF
          END IF
        END IF
      WEND
      GOSUB KillMissile
    ELSE ' Draw Missile
      LINE (Missile(miss%).x1%, Missile(miss%).y1%)-(Missile(miss%).x2%, Missile(miss%).y2%), 14
    END IF
  END IF
WEND
RETURN

HitSomething:
IF alien(cnt%).AlienType > 4 THEN ' Powerup
  m% = cnt%
  SELECT CASE alien(cnt%).AlienType
    CASE 5 ' Energy
      energy% = energy% + 10
      IF energy% > TOT.ENERGY THEN energy% = TOT.ENERGY
    CASE 6 ' Firepower
      LOCATE 25, 10: PRINT CHR$(15); ' Print firepower thingy
      RateOfFire = .4 ' Quicker rate of fire
      FirepowerCnt% = 250 ' Lasts 250 ticks
      bonus = TRUE
    CASE 7 ' Speed
      LOCATE 25, 12: PRINT CHR$(19); ' Print speed thingy
      ShipSpeed = 5 ' Quicker speed around orbit
      ExtraSpeedCnt% = 250 ' Lasts 250 ticks
      bonus = TRUE
    CASE 8
      LOCATE 25, 14: PRINT CHR$(236);
      SmartBomb = TRUE
  END SELECT
  ' Erase out last vestiges of alien graphic
  LINE (alien(m%).x - 3, alien(m%).y - 3)-(alien(m%).x + 13, alien(m%).y + 13), 0, BF
  GOSUB KillMonster
ELSE
  alien(cnt%).dying = 1
END IF
RETURN

Fire:
IF TIMER > LastMissile! + RateOfFire OR NumMissiles = 0 THEN
  ' If time has passed or there are no active missiles
  IF NumMissiles < 10 THEN ' If enough missiles left
    NumMissiles = NumMissiles + 1
    Missile(NumMissiles).radius = 78 ' Put near ship
    Missile(NumMissiles).angle = posit% ' Missile Angle = Ship Angle
    score = score - 5
    IF score < 0 THEN score = 0
    LastMissile! = TIMER
  END IF
END IF
RETURN

CreateMonster:
IF NumAliens < TOT.ALIENS - 1 THEN ' If there's space
  Add 1, NumAliens ' Another monster
  alien(NumAliens).x = 154 ' First coordinates
  alien(NumAliens).y = 94
  alien(NumAliens).degree = INT(RND * 361)
  alien(NumAliens).radius = 0 ' In the middle
  alien(NumAliens).exist = TRUE
  IF RND > .95 THEN ' 5% of the time
    IF RND > .5 THEN ' Create good guy half the time
      alien(NumAliens).AlienType = 4
    ELSE ' Create bonus
      IF RND > .75 THEN ' energy 25% of the time
        alien(NumAliens).AlienType = 5
      ELSEIF RND > .75 THEN ' firepower 25% of the time
        alien(NumAliens).AlienType = 6
      ELSEIF RND > .75 THEN ' speed 25% of the time
        alien(NumAliens).AlienType = 7
      ELSE ' smart bomb rest of the time
        alien(NumAliens).AlienType = 8
      END IF
    END IF
  ELSE ' Regular bad guy rest of the time
    Add 1, NumAliensThisLevel ' Another (real, bad) alien
    IF RND > .66 THEN ' fighter 33% of the time
      alien(NumAliens).AlienType = 3
      alien(NumAliens).speed = MonstSpeed / 3
    ELSEIF RND > .8 THEN ' bomber 20% of the time
      alien(NumAliens).AlienType = 2
      alien(NumAliens).speed = MonstSpeed
    ELSE ' kamikaze rest of the time
      alien(NumAliens).AlienType = 1
      alien(NumAliens).speed = MonstSpeed
    END IF
  END IF
  PUT (alien(NumAliens).x, alien(NumAliens).y), AlienGraph(1, alien(NumAliens).AlienType), PSET
END IF
RETURN

KillMonster:
IF alien(m%).AlienType <= 3 THEN
  ' Further in, higher score
  score = score + (100 - alien(m%).radius)
  MonstersKilled% = MonstersKilled% + 1
ELSE
  score = score - 100
  IF score < 0 THEN score = 0
END IF
FOR cnt1% = m% TO NumAliens ' Copy next over current
  alien(cnt1%).x = alien(cnt1% + 1).x
  alien(cnt1%).y = alien(cnt1% + 1).y
  alien(cnt1%).degree = alien(cnt1% + 1).degree
  alien(cnt1%).radius = alien(cnt1% + 1).radius
  alien(cnt1%).dying = alien(cnt1% + 1).dying
  alien(cnt1%).exist = alien(cnt1% + 1).exist
  alien(cnt1%).AlienType = alien(cnt1% + 1).AlienType
NEXT cnt1%
NumAliens = NumAliens - 1 ' One less monster!
RETURN

KillMissile:
FOR cnt1% = miss% TO NumMissiles ' Copy next over current
  Missile(cnt1%).x1 = Missile(cnt1% + 1).x1
  Missile(cnt1%).y1 = Missile(cnt1% + 1).y1
  Missile(cnt1%).x2 = Missile(cnt1% + 1).x2
  Missile(cnt1%).y2 = Missile(cnt1% + 1).y2
  Missile(cnt1%).angle = Missile(cnt1% + 1).angle
  Missile(cnt1%).radius = Missile(cnt1% + 1).radius
NEXT cnt1%
NumMissiles = NumMissiles - 1 ' One less missile
RETURN

DoScore:
IF NOT Beginning THEN
  LOCATE 25, 1: PRINT score;
  LOCATE 25, 26: PRINT "-"; level; "-";
  LOCATE 25, 36: PRINT MonstersKilled%;
  LINE (0, 24)-(0, 174), 0
  LINE (0, 174 - energy% * 3)-(0, 174), 138
END IF
RETURN

DoInput:
kp% = INP(&H60) ' Get keypress in kp%
IF kp% < 100 THEN ' &H60 is > 100 if no keypresses
  IF kp% = 77 THEN ' [RIGHT]
    moving = -1
  ELSEIF kp% = 75 THEN ' [LEFT]
    moving = 1
  ELSEIF kp% = 57 THEN ' [SPACE]
    GOSUB Fire
  ELSEIF kp% = 25 THEN ' [P]
    GOSUB Pause
  ELSEIF kp% = 28 THEN ' [ENTER]
    IF Beginning THEN
      GoingUp = FALSE
      Staying = 0
      ScoreColor% = 1
    ELSEIF SmartBomb THEN ' If you have a smart bomb
      LOCATE 25, 14: PRINT " ";
      SmartBomb = FALSE
      FOR cnt% = 1 TO NumAliens
        IF alien(cnt%).dying < 1 THEN ' If not dying
          alien(cnt%).dying = 1 ' Kill 'em
        END IF
      NEXT cnt%
    END IF
  ELSEIF kp% = 1 THEN ' [ESC]
    quit = 1
  END IF
END IF
RETURN

DoBonuses:
IF bonus THEN
  IF FirepowerCnt% > 0 THEN
    FirepowerCnt% = FirepowerCnt% - 1
    IF FirepowerCnt% = 0 THEN
      RateOfFire = .7
      IF ExtraSpeedCnt% = 0 THEN bonus = FALSE
      LOCATE 25, 10: PRINT " ";
    END IF
  ELSEIF ExtraSpeedCnt% > 0 THEN
    ExtraSpeedCnt% = ExtraSpeedCnt% - 1
    IF ExtraSpeedCnt% = 0 THEN
      ShipSpeed = 3
      IF FirepowerCnt% = 0 THEN bonus = FALSE
      LOCATE 25, 12: PRINT " ";
    END IF
  END IF
END IF
RETURN

Pause:
IF NOT Beginning THEN
  LOCATE 5, 18: PRINT "PAUSED";
  DO: LOOP UNTIL INKEY$ <> "" ' Wait for keypress
  LOCATE 5, 18: PRINT "      ";
END IF
RETURN

Init:
SCREEN 13
RANDOMIZE TIMER

CIRCLE (5, 5), 5, 100: PAINT (5, 5), 100 ' Draw kamikaze
LINE (0, 0)-(10, 10), 100
LINE (10, 0)-(0, 10), 100
LINE (4, 5)-(6, 5), 101
LINE (5, 4)-(5, 6), 101
PSET (0, 0), 101: PSET (10, 0), 101
PSET (0, 10), 101: PSET (10, 10), 101
GET (0, 0)-(10, 10), AlienGraph(1, 1)
LINE (0, 0)-(10, 10), 255, BF ' Draw mask
CIRCLE (5, 5), 5, 100: PAINT (5, 5), 100
LINE (0, 0)-(10, 10), 100
LINE (10, 0)-(0, 10), 100
LINE (4, 5)-(6, 5), 101
LINE (5, 4)-(5, 6), 101
PSET (0, 0), 101: PSET (10, 0), 101
PSET (0, 10), 101: PSET (10, 10), 101
GET (0, 0)-(10, 10), AlienMask(1, 1)
PUT (0, 0), AlienMask(1, 1)

PUT (10, 10), AlienGraph(1, 1)
LINE (10, 15)-(20, 15), 0 ' Explosion 1
LINE (15, 10)-(16, 20), 0
LINE (14, 14)-(17, 16), 0, B
CIRCLE (15, 15), 3, 12, , , 1 / 3
PAINT (15, 15), 12
PSET (14, 13), 7
PSET (17, 17), 7
GET (8, 8)-(22, 22), ExplodeGraph(1, 1)

CIRCLE (16, 15), 4, 0 ' Explosion 2
CIRCLE (15, 15), 4, 12, , , 1 / 2
CIRCLE (15, 15), 2, 14, , , 1 / 2
PAINT (15, 15), 14
CIRCLE (13, 12), 1, 7: PSET (13, 12), 7
CIRCLE (18, 18), 1, 7: PSET (18, 18), 7
GET (8, 8)-(22, 22), ExplodeGraph(1, 2)

PUT (10, 10), AlienGraph(1, 1), PSET ' Explosion 3
CIRCLE (15, 15), 6, 0: PAINT (15, 15), 0
CIRCLE (15, 15), 6, 4, , , 1 / 2
PAINT (15, 15), 4
CIRCLE (15, 15), 4, 14, , , 1 / 2
PAINT (15, 15), 14
PSET (10, 10), 0
CIRCLE (12, 11), 1, 8
CIRCLE (19, 19), 1, 8
GET (8, 8)-(22, 22), ExplodeGraph(1, 3)
PUT (8, 8), ExplodeGraph(1, 3)

CIRCLE (15, 15), 7, 116, , , 1 / 2 ' Explosion 4
PAINT (15, 15), 116
CIRCLE (15, 15), 4, 0, , , 1 / 2
PAINT (15, 15), 0
GET (8, 8)-(22, 22), ExplodeGraph(1, 4)
PUT (8, 8), ExplodeGraph(1, 4)

CIRCLE (15, 15), 7, 43, , , 1 / 2 ' Explosion 5
GET (8, 8)-(22, 22), ExplodeGraph(1, 5)
PUT (8, 8), ExplodeGraph(1, 5)

CIRCLE (15, 15), 5, 100, , , 1 ' Draw bomber
PAINT (15, 15), 100
CIRCLE (15, 15), 5, 101, , , 1
GET (10, 10)-(20, 20), AlienGraph(1, 2)
LINE (10, 10)-(20, 20), 255, BF ' Draw mask
CIRCLE (15, 15), 5, 100, , , 1
PAINT (15, 15), 100
CIRCLE (15, 15), 5, 101, , , 1
GET (10, 10)-(20, 20), AlienMask(1, 2)
PUT (10, 10), AlienMask(1, 2)

LINE (15, 11)-(15, 19), 101 ' Draw fighter
LINE (11, 15)-(19, 15), 101
LINE (12, 15)-(15, 12), 100
LINE (15, 12)-(18, 15), 100
LINE (12, 15)-(15, 18), 100
LINE (15, 18)-(18, 15), 100
PAINT (15, 15), 100
GET (10, 10)-(20, 20), AlienGraph(1, 3)
LINE (10, 10)-(20, 20), 255, BF
LINE (15, 11)-(15, 19), 101
LINE (11, 15)-(19, 15), 101
LINE (12, 15)-(15, 12), 100
LINE (15, 12)-(18, 15), 100
LINE (12, 15)-(15, 18), 100
LINE (15, 18)-(18, 15), 100
PAINT (15, 15), 100
GET (10, 10)-(20, 20), AlienMask(1, 3)
PUT (10, 10), AlienMask(1, 3)

CIRCLE (15, 15), 5, 104, , , 1 / 3 ' Draw good guy
PAINT (15, 15), 104
CIRCLE (15, 15), 5, 104, , , 3
LINE (15, 10)-(15, 20), 104
LINE (10, 10)-(20, 20), 104
LINE (20, 10)-(10, 20), 104
GET (10, 10)-(20, 20), AlienGraph(1, 4)
LINE (10, 10)-(20, 20), 255, BF ' Draw mask
CIRCLE (15, 15), 5, 104, , , 1 / 3
PAINT (15, 15), 104
CIRCLE (15, 15), 5, 104, , , 3
LINE (15, 10)-(15, 20), 104
LINE (10, 10)-(20, 20), 104
LINE (20, 10)-(10, 20), 104
GET (10, 10)-(20, 20), AlienMask(1, 4)
PUT (10, 10), AlienMask(1, 4)

CIRCLE (15, 15), 1, 105 ' Draw energy powerup
PAINT (15, 15), 105
CIRCLE (15, 15), 3, 105
GET (10, 10)-(20, 20), AlienGraph(1, 5)
LINE (10, 10)-(20, 20), 255, BF ' Draw mask
CIRCLE (15, 15), 1, 105
PAINT (15, 15), 105
CIRCLE (15, 15), 3, 105
GET (10, 10)-(20, 20), AlienMask(1, 5)
PUT (10, 10), AlienMask(1, 5)

LINE (12, 12)-(18, 18), 105, B ' Draw firepower powerup
LINE (12, 12)-(18, 18), 105
LINE (12, 18)-(18, 12), 105
GET (10, 10)-(20, 20), AlienGraph(1, 6)
LINE (10, 10)-(20, 20), 255, BF
LINE (12, 12)-(18, 18), 105, B
LINE (12, 12)-(18, 18), 105
LINE (12, 18)-(18, 12), 105
GET (10, 10)-(20, 20), AlienMask(1, 6)
PUT (10, 10), AlienMask(1, 6)

LINE (15, 10)-(15, 20), 105 ' Draw speed powerup
LINE (10, 15)-(20, 15), 105
LINE (14, 11)-(16, 19), 105, B
LINE (11, 14)-(19, 16), 105, B
LINE (14, 15)-(16, 15), 0
LINE (15, 14)-(15, 16), 0
GET (10, 10)-(20, 20), AlienGraph(1, 7)
LINE (10, 10)-(20, 20), 255, BF
LINE (15, 10)-(15, 20), 105 ' Draw speed powerup
LINE (10, 15)-(20, 15), 105
LINE (14, 11)-(16, 19), 105, B
LINE (11, 14)-(19, 16), 105, B
LINE (14, 15)-(16, 15), 255
LINE (15, 14)-(15, 16), 255
GET (10, 10)-(20, 20), AlienMask(1, 7)
PUT (10, 10), AlienMask(1, 7)

CIRCLE (15, 15), 3, 105, , , 1 ' Draw smart bomb
PAINT (15, 15), 105
LINE (12, 15)-(18, 15), 106
LINE (15, 12)-(15, 18), 106
GET (10, 10)-(20, 20), AlienGraph(1, 8)
LINE (10, 10)-(20, 20), 255, BF
CIRCLE (15, 15), 3, 105, , , 1
PAINT (15, 15), 105
LINE (12, 15)-(18, 15), 106
LINE (15, 12)-(15, 18), 106
GET (10, 10)-(20, 20), AlienMask(1, 8)
PUT (10, 10), AlienMask(1, 8)

PALETTE 100, 30 ' Change alien color to red
PALETTE 101, 60 ' Change alien other color to red
PALETTE 104, 3942440 ' Change good guy color to bright blue
PALETTE 105, 50 + 50 * 256 ' Change bonus color to yellow
PALETTE 106, 10 + 2560 + 655360 ' Extra color, gray

FOR cnt% = 0 TO 360 ' Initialise look-up tables
  SinLook(cnt%) = SIN(cnt% * DEG2RAD)
  CosLook(cnt%) = COS(cnt% * DEG2RAD)
NEXT cnt%

posit% = 0 ' Player's initial position = 0
ship.lxo = INT(80 * CosLook(posit%)) + 159
ship.lyo = INT(80 * SinLook(posit%)) + 99
energy% = TOT.ENERGY
score = 0
MonstersKilled% = 0
ScoreColor% = 0
ScoreMoving% = 1

FOR cnt% = 1 TO TOT.STARS ' Initialise stars
  star(cnt%).x = INT(RND * 1000) - 500
  star(cnt%).y = INT(RND * 1000) - 500
  star(cnt%).z = INT(RND * 1000) + 1
  star(cnt%).sx = 160
  star(cnt%).sy = 100
  star(cnt%).spd = INT(RND * 40) + 10
NEXT cnt%

ship.BorderColor = 53 ' Original ship color is light blue
ship.filled = FALSE ' Ship is not filled
ship.NumVertices = 11 ' 11 vertices (3 for QBASIC)
FOR cnt% = 1 TO ship.NumVertices ' Define vertices
  READ ShipVert(cnt%).x, ShipVert(cnt%).y
NEXT cnt%
ShipRadius = 80
RateOfFire = .7
ShipSpeed = 3

level = 1
NumAliensThisLevel = 0
TotAliensThisLevel = 30

MonstSpeed = .1
MonstChance = .97

PALETTE 102, 0
COLOR 102

Beginning = TRUE
GoingUp = TRUE

DrawPolygon ship, ShipVert()
RETURN

EndOfGame:
IF quit = 1 THEN ' Manual end
  SCREEN 0: WIDTH 80
  PRINT "Quitter!"
ELSEIF quit = 2 THEN ' Death
  LOCATE 12, 16: PRINT "YOU LOSE";
  SLEEP 2 ' Pause for two seconds
  done = FALSE
  cnt% = 0
  WHILE cnt% < 2000 AND NOT done
    cnt% = cnt% + 1 ' Loop 3000 times
    XX% = INT(RND * 271)
    YX% = INT(RND * 150)
    GET (XX%, YX%)-(XX% + 48, YX% + 48), melt
    PUT (XX%, YX% + 1), melt, PSET
    ' End on keyhit only after melting awhile
    IF INKEY$ <> "" THEN IF cnt% > 500 THEN done = TRUE
    ' Do an occasional delay
    IF cnt% MOD 100 = 0 THEN t! = TIMER: WHILE t! = TIMER: WEND
  WEND
  SCREEN 0: WIDTH 80 ' Normal text screen
  PRINT "You lose."
ELSEIF quit = 3 THEN ' You won!
  REM Neat animation to be added here
  ErasePolygon ship, ShipVert()
  PALETTE 14, 0 ' Erase missiles
  PALETTE 102, 0
  PALETTE 138, 0
  FOR ecnt% = 1 TO 30
    GOSUB DoStars
    r = r + 10
    CIRCLE (159, 99), wormholeradius1, 0 ' Erase old circles
    CIRCLE (159, 99), wormholeradius2, 0
    wormholeradius1 = INT(RND * 6) + r
    wormholeradius2 = INT(RND * 6) + r
    CIRCLE (159, 99), wormholeradius1, 9 ' Draw new circles
    CIRCLE (159, 99), wormholeradius2, 1
    t! = TIMER: WHILE t! = TIMER: WEND
  NEXT ecnt%
  FOR ecnt% = 1 TO 25
    t! = TIMER: WHILE t! = TIMER: WEND
  NEXT ecnt%
  SCREEN 0: WIDTH 80
  PRINT "You won!"
END IF
PRINT "You ended up with a score of"; score; "and"; MonstersKilled%; "aliens killed."
RETURN

' REM out the following 11 DATA statements for QBASIC

' Regular ship polygon
DATA 0, 0
DATA 5, -5
DATA 10, -5
DATA 5, -10
DATA 10, -10
DATA 15, -5
DATA 15, 5
DATA 10, 10
DATA 5, 10
DATA 10, 5
DATA 5, 5

' QBASIC triangular ship polygon
DATA 0, 0
DATA 10, -5
DATA 10, 5

SUB Add (amt, var)

' Adds _amt_ to var
var = var + amt

END SUB

SUB DrawPolygon (poly AS PolygonType, Verts() AS VertexType)

xo = poly.lxo
yo = poly.lyo

IF poly.filled THEN
  FOR index = 1 TO poly.NumVertices - 1
    LINE (xo + Verts(index).x, yo + Verts(index).y)-(xo + Verts(index + 1).x, yo + Verts(index + 1).y), poly.FilledColor
  NEXT index
  LINE (xo + Verts(poly.NumVertices).x, yo + Verts(poly.NumVertices).y)-(xo + Verts(1).x, yo + Verts(1).y), poly.FilledColor
  PAINT (xo, yo), poly.FilledColor
ELSE
  FOR index = 1 TO poly.NumVertices - 1
    LINE (xo + Verts(index).x, yo + Verts(index).y)-(xo + Verts(index + 1).x, yo + Verts(index + 1).y), poly.BorderColor
  NEXT index
  LINE (xo + Verts(poly.NumVertices).x, yo + Verts(poly.NumVertices).y)-(xo + Verts(1).x, yo + Verts(1).y), poly.BorderColor
END IF

END SUB

SUB ErasePolygon (poly AS PolygonType, Verts() AS VertexType)

xo = poly.lxo
yo = poly.lyo

IF poly.filled THEN
  FOR index = 1 TO poly.NumVertices - 1
    LINE (xo + Verts(index).x, yo + Verts(index).y)-(xo + Verts(index + 1).x, yo + Verts(index + 1).y), 0
  NEXT index
  LINE (xo + Verts(poly.NumVertices).x, yo + Verts(poly.NumVertices).y)-(xo + Verts(1).x, yo + Verts(1).y), 0
  PAINT (xo, yo), FilledColor
ELSE
  FOR index = 1 TO poly.NumVertices - 1
    LINE (xo + Verts(index).x, yo + Verts(index).y)-(xo + Verts(index + 1).x, yo + Verts(index + 1).y), 0
  NEXT index
  LINE (xo + Verts(poly.NumVertices).x, yo + Verts(poly.NumVertices).y)-(xo + Verts(1).x, yo + Verts(1).y), 0
END IF

END SUB

SUB Intro ()

CLS
PRINT "     One day, without warning, the heavens next to our planet opened"
PRINT "   like a flower.  Out of this wormhole came an alien delegation,"
PRINT "   their only wish to sign a treaty with us.  We sent our entire"
PRINT "   starfleet to the wormhole's mouth in this treaty's honor."
PRINT "     Then, out of that wormhole poured ships the likes of which we had"
PRINT "   never dreamed.  Our fleet was decimated; most ships were"
PRINT "   destroyed, others pulled into the wormhole to fight on the"
PRINT "   aliens' ground.  But we had made our mark: the half of their"
PRINT "   fleet remaining withdrew into the wormhole."
PRINT "     Now, you man the only spaceworthy ship left.  Your mission:"
PRINT "   destroy the aliens as they come out of the wormhole, as their"
PRINT "   weapons are useless for a small critical distance from the"
PRINT "   wormhole's mouth.  They will be defenseless unless they get"
PRINT "   past you.  Good luck!"
PRINT "     Your ship can only move in a circular orbit around the wormhole"
PRINT "   out of which the red aliens are coming.  Use the left and right"
PRINT "   arrow keys to move around, [SPACE] to fire a missile at the"
PRINT "   aliens, and [ESC] to quit.  Hit [ENTER] to bypass the"
PRINT "   beginning, and [P] to pause.  Your score is displayed in the"
PRINT "   lower left, and number of aliens destroyed in the lower right."
PRINT "   Blue ships are our ships returning; let them pass.  The brown"
PRINT "   bar on the left is your energy.  Each alien that gets by will"
PRINT "   drain your energy; let enough by and Game Over!"
LOCATE 25, 50: PRINT "Hit any key to continue....";
DO: LOOP UNTIL INKEY$ <> ""


END SUB

SUB RotatePolygon (poly AS PolygonType, Verts() AS VertexType, angle AS INTEGER)

IF angle >= 0 THEN
  si = SIN(angle * DEG2RAD)
  cs = COS(angle * DEG2RAD)
ELSE
  si = SIN((angle + 360) * DEG2RAD)
  cs = COS((angle + 360) * DEG2RAD)
END IF

FOR index = 1 TO poly.NumVertices
  rx = Verts(index).x * cs - Verts(index).y * si
  ry = Verts(index).y * cs + Verts(index).x * si
  Verts(index).x = rx
  Verts(index).y = ry
NEXT index

END SUB

SUB ScalePolygon (poly AS PolygonType, Verts() AS VertexType, factor AS SINGLE)

' Scales the polygon in _poly_ and _verts()_ by the _factor_

FOR index% = 1 TO poly.NumVertices
  Verts(index%).x = Verts(index%).x * factor
  Verts(index%).y = Verts(index%).y * factor
NEXT index%

END SUB
