In the spirit of Terry's Tutorials - GUARDIAN Alien Space Ship Game
#11
So now the little alien dudes have sensors, and can keep from flying into one another. No collisions, so no sound effects needed for this demo. For fun, this demo runs 15 multi-color space ships. I guess it's Pride month on Alpha Centauri, but whatever...

The ships can now go off screen and return at some point near the exit. That's a nice escape feature for further development plans. Remember you can use left Ctlr to speed things up and left Alt to slow things down. Oh, and I made the bottom of the screen to be the surface of the planet, so the alien ships were taught to just bounce back up if they near the surface.

Stage 6)

Code: (Select All)
DIM SHARED top, bottom, left, right, s_delay
a = 120: b = 42
WIDTH a, b
_SCREENMOVE 0, 0
top = 3: bottom = _HEIGHT: left = 0: right = _WIDTH
msg$ = "Alien space ship movement demo."
LOCATE 1, (right - left) \ 2 - LEN(msg$) \ 2
PRINT msg$;
LOCATE 1, 2: PRINT STRING$(_WIDTH, "_");
s_delay = 100
DO
    _LIMIT 30
    k$ = INKEY$
    IF _KEYDOWN(100306) THEN IF s_delay - 5 > 0 THEN s_delay = s_delay - 5: LOCATE 1, 2: PRINT "Speed delay ="; s_delay / 100; "    ";
    IF _KEYDOWN(100308) THEN IF s_delay < 500 THEN s_delay = s_delay + 5: LOCATE 1, 2: PRINT "Speed delay ="; s_delay / 100; "    ";
    IF LEN(k$) THEN SOUND 1000, .1
    IF k$ = CHR$(27) THEN END
    alien_move
LOOP

offscreen:
IF ERR = 5 THEN er = -1: RESUME NEXT
PRINT "Opps, you have an error"; ERR
END

SUB alien_move:
    STATIC alien$, a_y(15), a_x(15), olda_y(15), olda_x(15), inertia(15), ran(15), ran_y(15), ran_x(15), oldran(15), z5, z6, itr, alien_ship_max
    IF alien$ = "" THEN
        alien$ = "-<>-": alien_ship_max = 15 ' Sets number of space ships and initiates attack.
        CALL explosion ' Loads sound files on first call.
    END IF
    IF ABS(z5 - TIMER) > s_delay / 1000 THEN ' Time delay.
        DO
            itr = itr + 1: IF itr > alien_ship_max THEN itr = 1 ' Needed to offset the EXIT DO hover event, which on exit does not affect the itr variable.
            y_restore = CSRLIN: x_restore = POS(0) ' Restore column and row upon exit.

            IF olda_y(itr) <> 0 AND olda_x(itr) <> 0 THEN ' If alien ship on screen then mask old image.
                LOCATE olda_y(itr), olda_x(itr): PRINT SPACE$(LEN(alien$));
            END IF

            IF inertia(itr) = 0 THEN ' Determine how many moves in one direction.
                inertia(itr) = INT(RND * (bottom - top) / 2) + 1 ' How many moves to go in any one direction.
                ran(itr) = INT(RND * 8) + 1 ' Choose 1 of 8 possible directions.

                IF ran(itr) = oldran(itr) OR olda_y(itr) = 0 AND olda_x(itr) = 0 AND ran = 1 OR olda_y(itr) = 0 AND olda_x(itr) = 0 AND ran = 5 THEN
                    EXIT DO ' Just hover if direction was not changed on existing alien space ship or if a new alien space ship is entering from the sides and up or down was generated.
                END IF

                SELECT CASE ran(itr) ' Get changes in column and row coordinates.
                    CASE 1: ran_y(itr) = -1: ran_x(itr) = 0 '  Up.
                    CASE 2: ran_y(itr) = -1: ran_x(itr) = 2 '  Up and right.
                    CASE 3: ran_y(itr) = 0: ran_x(itr) = 2 '  Right.
                    CASE 4: ran_y(itr) = 1: ran_x(itr) = 2 '  Down and right.
                    CASE 5: ran_y(itr) = 1: ran_x(itr) = 0 '  Down.
                    CASE 6: ran_y(itr) = 1: ran_x(itr) = -2 '  Down and left.
                    CASE 7: ran_y(itr) = 0: ran_x(itr) = -2 '  Left.
                    CASE 8: ran_y(itr) = -1: ran_x(itr) = -2 ' Up and left.
                END SELECT

                IF olda_y(itr) = 0 AND olda_x(itr) = 0 THEN ' New alien space ship enters the screen.
                    i = RND * (bottom - top) \ 4
                    a_y(itr) = (bottom - top) \ 4 + i
                    IF ran(itr) < 5 THEN ' Determine side of entry from initial direction.
                        IF SCREEN(a_y(itr), left + LEN(alien$)) = 32 THEN
                            a_x(itr) = left + 1 ' Enter from the left side and go right.
                        ELSE
                            GOSUB a_erase ' Cancel and wait because space is occupied.
                            EXIT DO
                        END IF
                    ELSE
                        IF SCREEN(a_y(itr), right - LEN(alien$) + 1) = 32 THEN
                            a_x(itr) = right - LEN(alien$) ' Enter from the right side and go left.
                        ELSE
                            GOSUB a_erase: ' Cancel and wait because space is occupied.
                            EXIT DO
                        END IF
                    END IF
                END IF
                oldran(itr) = ran(itr) ' Remember last direction. Another line uses this to disallow any RND that chooses the same direction twice.
            ELSE
                inertia(itr) = inertia(itr) - 1 ' Count down the number of moves in any one direction. When zero, switch direction.
            END IF

            FOR i = 1 TO alien_ship_max
                IF i <> itr AND a_y(i) <> 0 THEN
                    IF a_y(itr) + ran_y(itr) = a_y(i) THEN
                        IF a_x(itr) + ran_x(itr) + LEN(alien$) > a_x(i) AND a_x(itr) + ran_x(itr) < a_x(i) + LEN(alien$) THEN
                            collide = 1
                            EXIT FOR
                        END IF
                    END IF
                END IF
            NEXT
            IF collide = 1 THEN
                j = a_y(itr): k = a_x(itr)
                ran_y(itr) = 0: ran_x(itr) = 0: inertia(itr) = 0
                collide = 0 ' Collision detection off. Collision detected and avoided.
            ELSE
                j = a_y(itr) + ran_y(itr): k = a_x(itr) + ran_x(itr)
            END IF

            IF j <= top OR k <= left OR k + LEN(alien$) > right THEN ' Alien ship out of range.
                inertia(itr) = 0 ' These two lines keep the out of range ship(s) reasonably nearby.
                IF j > -3 AND k < right + 3 AND k > -3 THEN a_y(itr) = j: a_x(itr) = k
            ELSE
                ' Check for collisions and reverse course if detected.
                IF j >= bottom THEN collide = -1

                COLOR itr
                IF collide THEN
                    ran_y(itr) = -ran_y(itr): ran_x(itr) = -ran_x(itr)
                    collide = 0

                    LOCATE olda_y(itr), olda_x(itr): PRINT alien$;
                ELSE
                    a_y(itr) = j: a_x(itr) = k ' Next move coordinates.
                    LOCATE j, k: PRINT alien$; ' Move alien space ship.
                    olda_y(itr) = j: olda_x(itr) = k ' Remember these coordinates to erase alien space ship on next loop.
                END IF
                COLOR 7
            END IF

            IF itr = alien_ship_max THEN itr = 0: EXIT DO
            IF ABS(z6 - TIMER) > s_delay / 2 THEN skipz5 = -1: EXIT DO
        LOOP
        IF skipz5 = 0 THEN z5 = TIMER
        z6 = TIMER
        LOCATE y_restore, x_restore ' Restore entry column and row positions.
    END IF
    EXIT SUB

    a_erase:
    a_y(itr) = 0: a_x(itr) = 0: olda_y(itr) = 0: olda_x(itr) = 0: inertia(itr) = 0: ran(itr) = 0: ran_y(itr) = 0: ran_x(itr) = 0: oldran(itr) = 0
    RETURN
END SUB

SUB explosion
    STATIC sound_check, soundfile%, t1&, t6&, t7&, t8&
    IF sound_check = 0 THEN
        IF _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") THEN soundfile% = -1
        IF soundfile% THEN
            t1& = _SNDOPEN("Thunder1.ogg", "SYNC")
            t6& = _SNDOPEN("Thunder6.ogg", "SYNC")
            t7& = _SNDOPEN("Thunder7.ogg", "SYNC")
            t8& = _SNDOPEN("Thunder8.ogg", "SYNC")
        END IF
        sound_check = -1
        EXIT SUB
    END IF

    VIEW PRINT top TO bottom ' Needed to set print error parameters.
    b_y1 = CSRLIN: b_x1 = POS(0)
    h = 0
    ON ERROR GOTO offscreen
    DO
        IF h = 1 THEN burst$ = " " ELSE burst$ = CHR$(249)
        h = h + 1
        GOSUB flash
        FOR i = 1 TO 5
            SELECT CASE i
                CASE 1
                    COLOR 15
                    LOCATE b_y1, b_x1: PRINT burst$;
                    _DELAY .1
                CASE 2
                    IF burst$ = CHR$(249) THEN burst$ = CHR$(250)
                    COLOR 14, 0
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .1
                CASE 3
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                CASE 4
                    COLOR 4
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .3
                CASE 5
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
            END SELECT
        NEXT
        IF h = 1 THEN h = 0: EXIT DO
    LOOP
    VIEW PRINT
    ON ERROR GOTO 0
    COLOR 7
    LOCATE b_y1, b_x1
    EXIT SUB

    flash:
    IF eflag THEN
        IF ABS(z7 - TIMER) > .1 THEN
            eflag = 0
            PALETTE 0, 0
            z7 = TIMER
        ELSE
            j = -j * -1
            IF j = 0 THEN
                PALETTE 0, 63
                _DELAY .05
            ELSE
                PALETTE 0, 0
                _DELAY .05
            END IF
        END IF
    END IF
    IF eflag = 0 THEN
        PALETTE 0, 36
        IF soundfile% THEN
            _SNDPLAY t1&
            _DELAY .05
            PALETTE 0, 0
            _SNDPLAY t7&
        ELSE
            _DELAY .075
        END IF
        eflag = -1
        z7 = TIMER
    END IF
    PALETTE 0, 0
    RETURN
END SUB

So what's next? Well, at some point it becomes time to see what was needed to get this far, and consider if converting to TYPE variables would be adventitious to the project.

Pete
Reply
#12
Well, boring as it is, here is the code as TYPE variables with a certain amount of name changes to improve the naming conformity. Naming things is just not my strong suit. I used to say if I was the first man on earth, and asked to name all the lesser creations, you know, fish, foul, and Democrats, etc., I'd just name them all widgets. Hey, that widget just flew right into head. Ouch, that widget just swam up my fig leaf and bit me in the what-u-ma-call-it! So with that in mind...

Code: (Select All)
TYPE gen_var
    nol AS INTEGER ' Number of game levels.
    level AS INTEGER ' Current game level.
    level_up AS INTEGER ' Routes code to next game level.
    top AS INTEGER ' Top boundary. (Changeable).
    bottom AS INTEGER ' Bottom boundary. (Changeable).
    left AS INTEGER ' Left boundary. (Changeable).
    right AS INTEGER ' Right boundary. (Changeable).
    kb_router AS INTEGER ' Routes code to either Guardian/Alien or general keyboard routine. -1 Guardian/Alien, 0 keyboard.
    snd1 AS LONG ' Explosion sound effect.
    snd2 AS LONG ' Explosion sound effect.
END TYPE

DIM SHARED v AS gen_var

TYPE guardian
    num AS INTEGER ' Number of Guardians aka lives.
    diry AS INTEGER ' Guardian row move. +1, 0, -1.
    dirx AS INTEGER ' Guardian column move. +2, 0, -2. Equals vertical pixel movement. 16x8.
    y AS INTEGER ' Guardian coordinates row.
    x AS INTEGER ' Guardian coordinates column.
    speed AS INTEGER ' Guardian speed. (User Determined).
    m_max AS INTEGER ' Restricts # of missiles fired in one direction.
    m_status AS INTEGER ' Missile status. -1 when fired, 1 while moving.
    m_fired AS INTEGER ' The number of missile deployed and still active.
    m_n AS INTEGER ' Missile number. 1 to m_max. (Counter).
    m_d AS INTEGER ' Missile direction. (1-8).
    m_y AS INTEGER ' Missile row advancement increment: +1, 0, -1 Note: Missile row and column coordinates are defined in arrays.
    m_x AS INTEGER ' Missile column advancement increment: +2, 0, -2. Equals vertical pixel movement. 16x8.
    m_asc AS INTEGER ' ASCII charater representing a fired missile.
    icon AS STRING
END TYPE

DIM SHARED g AS guardian

TYPE alien
    max AS INTEGER ' Maximum # of alien ships on screen.
    count AS INTEGER ' Number of alien ships. (Counter).
    itr AS INTEGER ' Iteration array number to cycle through the active alien ships. (Counter).
    cycle_delay AS SINGLE ' Timer cycle controls how fast alien ships move.
    ship AS STRING ' Alien ship ASCII design.
END TYPE

DIM SHARED a AS alien

CALL set_up ' Loads sound files and some starting variables.

DO
    v.level = v.level + 1
    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left) \ 2 + v.left ' Set initial column and row for Guardian craft.
    LOCATE g.y, g.x: PRINT "*"; ' Show Guardian.
    i = 15 ' Default max setting for number of alien ships used here to intially dim arrays.
    REDIM SHARED a_y(i), a_x(i), a_mask_y(i), a_mask_x(i), a_inertia(i) ' Alien movement.
    REDIM SHARED a_ran(i), a_olda_ran(i), a_y_loc(i), a_x_loc(i) ' Alien motvement.
    REDIM SHARED m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8) ' Guardian missiles.

    ' Array descriptions and actions.
    ' a_y() , a_x() Alien ship postions rows and columns.
    ' a_mask_y(), a_mask_x() Alien ship last position. Masked on next move.
    ' a_inertia(i) Number of moves in one direction selected by random for an alien ship.
    ' a_ran(i), a_olda_ran(i) Determines the direction in which the inertia will travel and the prior direction is kept to disallow the same direction twice.
    ' a_y_loc(i), a_x_loc(i) The row and column cordinates of the aliens ships.
    ' m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8)  Missile number and Missile index 1 to g.m_max for position. 8 is the fixed number of 8 different directions.

    CALL game

    ' Zero variables.
    g.diry = 0: g.dirx = 0: g.m_status = 0: g.m_fired = 0: g.m_n = 0: g.m_d = 0: g.m_y = 0: g.m_x = 0
    g.m_asc = 0: a.count = 0: a.itr = 0: a.cycle_delay = 0: a.ship = "": v.level_up = 0

LOOP

What's interesting with TYPE variables is they are a bit safer to use, because they are usually more unique, which means it's harder to create a duplicate and unrelated variable in some other sub, when we are using DIM SHARED instead of passing variables by reference or value.

So next up will be combining a player move and fire routine to confront the alien ships. Stay tuned for the debut of: ASCII Guardian

Pete
Reply
#13
Here is GUARDIAN V1.0, the first version of a playable space shooter game...

Code: (Select All)
DEFINT H-K
$RESIZE:ON
_RESIZE OFF
RANDOMIZE TIMER
' Note: timer is not adjusted for stroke of midnight event, so don't stay up late playing this.
REM Main
f1 = 22 ' Sets font size to 22 and calculates the max screen height and width for your desktop.
h = (_DESKTOPHEIGHT - 60) \ f1
w = _DESKTOPWIDTH \ (f1 / 1.66)
WIDTH w, h
_SCREENMOVE 0, 0
fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\lucon.ttf" 'Find Windows Folder Path.
font& = _LOADFONT(fontpath$, f1, "monospace")
_FONT font&
_DELAY .25
_RESIZE ON , _SMOOTH ' Allows resizing. Note: this is for slight adjustments. As of this version there is no compensatory function to change the font size during screen size changes.
TYPE gen_var
    intro AS INTEGER ' Runs a protion of the alien subroutine as part of the intro.
    nol AS INTEGER ' Number of game levels.
    level AS INTEGER ' Current game level.
    level_up AS INTEGER ' Routes code to next game level.
    top AS INTEGER ' Top boundary. (Changeable).
    bottom AS INTEGER ' Bottom boundary. (Changeable).
    left AS INTEGER ' Left boundary. (Changeable).
    right AS INTEGER ' Right boundary. (Changeable).
    kb_access AS INTEGER ' Routes code to either Guardian/Alien or general keyboard routine. -1 Guardian/Alien, 0 keyboard.
    mouse_or_key_move AS INTEGER
    mouse_get_screen AS INTEGER
    play AS INTEGER
    snd1 AS LONG ' Explosion sound effect.
    snd2 AS LONG ' Explosion sound effect.
END TYPE

DIM SHARED v AS gen_var

TYPE guardian
    num AS INTEGER ' Number of Guardians aka lives.
    diry AS INTEGER ' Guardian row move. +1, 0, -1.
    dirx AS INTEGER ' Guardian column move. +2, 0, -2. Equals vertical pixel movement. 16x8.
    y AS INTEGER ' Guardian coordinates row.
    x AS INTEGER ' Guardian coordinates column.
    thrusters AS INTEGER ' Guardian speed. (User Determined).
    m_max AS INTEGER ' Restricts # of missiles fired in one direction.
    m_status AS INTEGER ' Missile status. -1 when fired, 1 while moving.
    m_fired AS INTEGER ' The number of missile deployed and still active.
    m_n AS INTEGER ' FOR/NEXT counter variable shared by other routines to index through the number of missiles fired in a specific direction.
    m_d AS INTEGER ' Missile direction. (1-8).
    m_y AS INTEGER ' Missile row advancement increment: +1, 0, -1 Note: Missile row and column coordinates are defined in arrays.
    m_x AS INTEGER ' Missile column advancement increment: +2, 0, -2. Equals vertical pixel movement. 16x8.
    m_asc AS INTEGER ' ASCII character representing a fired missile.
    m_launcher AS STRING
    icon AS STRING ' Guardian comm icon. For this edition, it is the same as the flagship: "*"
    flagship AS STRING ' Guardian ascii character.
END TYPE

DIM SHARED g AS guardian

TYPE alien
    max AS INTEGER ' Maximum # of alien ships on screen.
    count AS INTEGER ' Number of alien ships. (Counter).
    itr AS INTEGER ' Iteration array number to cycle through the active alien ships. (Counter).
    cycle_delay AS SINGLE ' Timer cycle controls how fast alien ships move.
    ship AS STRING ' Alien ship ASCII design.
END TYPE

DIM SHARED a AS alien

DO
    GOSUB set_arrays

    SELECT CASE v.play
        CASE 0
            CALL set_up
            CALL comm
            CALL intro
            v.intro = 0: a.max = 0
            GOSUB set_arrays
            v.level = -1: CALL game_level: v.level_up = 0 ' This variable is canceled here so game will play instead of go another level up.
        CASE 1
            CALL set_up
            CALL comm
    END SELECT

    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Set initial column and row for Guardian craft.

    CALL game

    GOSUB zero_variables
LOOP

set_arrays:
ii = 15 ' Default max setting for number of alien ships used here to initially dim arrays.
g.m_max = 8 ' * missiles max per direction.
REDIM SHARED a_y(ii), a_x(ii), a_mask_y(ii), a_mask_x(ii), a_inertia(ii) ' Alien movement.
REDIM SHARED a_ran(ii), a_olda_ran(ii), a_y_loc(ii), a_x_loc(ii), a_offscrn(ii) ' Alien movement.
REDIM SHARED m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8) ' Guardian missiles.

' Array descriptions and actions.
' a_y() , a_x() Alien ship positions rows and columns.
' a_mask_y(), a_mask_x() Alien ship last position. Masked on next move.
' a_inertia(ii) Number of moves in one direction selected by random for an alien ship.
' a_ran(ii), a_olda_ran(ii) Determines the direction in which the inertia will travel and the prior direction is kept to disallow the same direction twice.
' a_y_loc(ii), a_x_loc(ii) The row and column coordinates of the aliens ships.
' m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8)  Missile number and Missile index 1 to g.m_max for position. 8 is the fixed number of 8 different directions.
RETURN

zero_variables:
' Zero variables.
g.diry = 0: g.dirx = 0: g.m_status = 0: g.m_fired = 0: g.m_d = 0: g.m_y = 0: g.m_x = 0
a.count = 0: a.itr = 0: a.cycle_delay = 0: v.level_up = 0: v.mouse_or_key_move = 0: g.m_launcher = ""
RETURN

skipintro:
v.intro = 999
BEEP: BEEP
VIEW PRINT v.top TO v.bottom
CLS 2
VIEW PRINT
_DELAY .5
RETURN

' Error handler.
offscreen: ' Prevents error if blast particles row and column are off-screen. Effect is a partial blast on side of screen.
IF ERR = 5 THEN er = -1: RESUME NEXT
PRINT "Opps, unexpected error"; ERR
END

SUB intro
    v.mouse_get_screen = 1 ' Allows skip intro by mouse selection.
    LOCATE _HEIGHT, _WIDTH - 15
    PRINT "[S]kip Intro"; ' Option to skip the intro using the "S" key with ONKEY statement.
    KEY 15, CHR$(0) + CHR$(31) 'scancode for S
    ON KEY(15) GOSUB skipintro
    KEY(15) ON 'turn ON [S]kip intro event trapping.
    j = (v.bottom - v.top) \ 2 + v.top
    k = (v.right - v.left) \ 2 + v.left
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .33
    SOUND 1000, .3
    msg$ = " GUARDIAN "
    LOCATE j, k - LEN(msg$) \ 2: COLOR 14: PRINT msg$;: COLOR 7
    LOCATE , k
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .66
    msg$ = SPACE$(LEN(msg$))
    LOCATE j, k - LEN(msg$) \ 2: COLOR 7: PRINT msg$;
    LOCATE j, k
    IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB ELSE CALL explosion
    LOCATE j, k: COLOR 15 + 16: PRINT g.flagship;
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .75
    v.intro = -1: a.max = 10
    FOR i = 1 TO 90
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
        SOUND 900, .05
        CALL alien_move
        LOCATE j, k: COLOR 15: PRINT g.flagship;: COLOR 7
        _DELAY .07
    NEXT
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > 1
    LOCATE j, k: PRINT " ";
    CALL guardian_abduction
    a.max = 0
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .5
    KEY(15) OFF
    LOCATE v.bottom, _WIDTH - 20: PRINT SPACE$(20);

    ' Playing Instructions.
    REDIM msg$(13)
    msg$(1) = CHR$(249) + " " + "Look towards the top of your screen for Guardian ship status."
    msg$(2) = CHR$(249) + " " + "Hold arrow keys up/dn/lt/rt or in combination to move diagonally."
    msg$(3) = CHR$(249) + " " + "Missiles can be fired simultaneously in 8 different directions."
    msg$(4) = CHR$(249) + " " + "Press Tab to fire 1-8 missiles in the direction of movement."
    msg$(5) = CHR$(249) + " " + "Press Rt Ctrl to increase thrust, or Rt Alt to reduce thrust."
    msg$(6) = ""
    msg$(7) = CHR$(249) + " " + "Mouse alternative: "
    msg$(8) = CHR$(249) + " " + "Hold right mouse button to move toward mouse pointer."
    msg$(9) = CHR$(249) + " " + "Click left mouse button to fire 1-8 missiles in direction of mouse pointer."
    msg$(10) = CHR$(249) + " " + "Mouse wheel up for more thrust, wheel down for less."
    msg$(11) = CHR$(249) + " " + "Note: Tab and left mouse button cannot be used simultaneously."
    msg$(12) = ""
    msg$(13) = CHR$(249) + " " + "If you come in contact with an alien ship, your ship and crew get abducted."
    j = 0
    FOR i = 1 TO UBOUND(msg$)
        IF LEN(msg$(i)) > j THEN j = LEN(msg$(i))
    NEXT
    i = ((v.right - v.left) - j) \ 2
    t_mrgn = 6: b_mrgn = _HEIGHT - 4: l_mrgn = i + 1: r_mrgn = v.right - i
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    FOR i = 1 TO b_mrgn - t_mrgn + 3
        LOCATE t_mrgn - 2 + i, l_mrgn - 2
        PRINT CHR$(179);
        LOCATE , r_mrgn + 2: PRINT CHR$(179);
    NEXT
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT CHR$(218);
    LOCATE t_mrgn - 2, r_mrgn + 2: PRINT CHR$(191);
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT CHR$(192);
    LOCATE b_mrgn + 2, r_mrgn + 2: PRINT CHR$(217);
    msg$ = "<Intro>"
    LOCATE t_mrgn - 2, l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    msg$ = "You are the Captain of the Guardian, an elite battle cruiser commissioned to protect our planet against alien invaders. You have 3 lives. Every time you are abducted by an alien ship, you lose a life. If you survive, and wipe out all 3 flights of alien attacks, your mission is completed. You saved Earth!"
    j = r_mrgn - l_mrgn
    LOCATE t_mrgn, l_mrgn
    msg$ = RTRIM$(msg$) + " " ' Simple word parser routine.--------
    DO
        x$ = MID$(msg$, 1, j)
        x$ = MID$(x$, 1, _INSTRREV(x$, " ") - 1)
        msg$ = LTRIM$(MID$(msg$, LEN(x$) + 1))
        LOCATE , l_mrgn: PRINT x$
    LOOP UNTIL msg$ = "" '-----------------------------------------
    PRINT
    FOR i = 1 TO UBOUND(msg$)
        LOCATE , l_mrgn
        IF LEN(msg$(i)) THEN PRINT msg$(i) ELSE PRINT
    NEXT
    PRINT: IF CSRLIN < b_mrgn - 1 THEN PRINT
    msg$ = "Good luck, Captain! Press any key to begin."
    LOCATE , l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    DO
        _LIMIT 10
        WHILE _MOUSEINPUT: WEND
        IF _MOUSEBUTTON(1) THEN SOUND 1000, .1: EXIT DO
        ky$ = INKEY$
        IF ky$ = CHR$(27) THEN SYSTEM
        IF LEN(ky$) THEN EXIT DO
    LOOP
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
END SUB

SUB set_up
    v.top = 3: v.bottom = _HEIGHT: v.left = 1: v.right = _WIDTH ' Boundaries.
    g.flagship = CHR$(15)
    a.ship = "-<>-"
    g.num = 3 ' 3 Guardian (lives) to start.
    g.thrusters = 10 ' Shows 1/2 thrust at start up on comm.
    g.icon = g.flagship
    g.m_asc = 250: g.diry = -1: ' Initiate missile ASCII character. g.diry = -1 initiates fire upwards if unmoved.
    IF _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") THEN ' Sound effects provided by TheBOB, Bob Seguin, from The QBasic Forum.
        v.snd1 = _SNDOPEN("Thunder1.ogg", "SYNC")
        v.snd2 = _SNDOPEN("Thunder7.ogg", "SYNC")
    END IF
    v.play = -1 ' Skip into on replay.
    LOCATE 2, 1: PRINT STRING$(_WIDTH, CHR$(196));
    _KEYCLEAR ' Clear any previous key presses in buffer.
END SUB

SUB game
    STATIC mouse_event1, mouse_event2
    REM Set mouse_event2 = 0 here to produce constant motion with directional changes.
    'Press single or arrow key combo to move. Rt Ctrl = faster / Rt Alt = slower. Tab to fire missles.
    g.thrusters = 10 ' Guardian movement delay. (1 to 20). Rt Ctrl = faster / Rt Alt = slower.

    SELECT CASE v.level
        CASE 1: a.max = 5 ' Sets number of space ships and initiates attack.
        CASE 2: a.max = 10
        CASE 3: a.max = 15
    END SELECT
    a.count = a.max
    IF a.max < 6 THEN
        a.cycle_delay = .005
    ELSEIF a.max < 11 THEN a.cycle_delay = .003
    ELSE a.cycle_delay = .001
    END IF

    CALL comm

    DO
        _LIMIT 60 ' Display at 60 frames per second.

        CALL comm

        IF g.m_status THEN CALL Guardian_missiles ' g.m_status determines Guardian or alien turn. 1 = Guardian, -1 = alien.

        IF a.count = 0 THEN EXIT SUB ' Moving on to next level.

        CALL alien_move

        ' To change thrusters only when guardian moves, remove the timer and move this routine to: IF v.kb_access = -1 THEN
        IF ABS(z3 - TIMER) > .1 THEN ' z3 is a keypress delay timer for Guardian thrusters. Check every .1 seconds for a speed change.
            IF _KEYDOWN(100307) AND g.thrusters < 20 THEN g.thrusters = g.thrusters + 1 ' Rt Ctrl key. Slows down Guardian ship down.
            IF _KEYDOWN(100305) AND g.thrusters > 0 THEN g.thrusters = g.thrusters - 1 '  Rt Alt key. Speeds up Guardian ship.
            z3 = TIMER
        END IF

        IF ABS(z2 - TIMER) > g.thrusters / 100 THEN ' z2 is delay for Guardian movement cycle. Note: Division needed because computer math can't add decimal numbers correctly.
            IF v.kb_access = 0 THEN
                IF _KEYDOWN(18432) OR _KEYDOWN(19200) OR _KEYDOWN(19712) OR _KEYDOWN(20480) OR mouse_event2 = -1 THEN ' Arrow keys.
                    v.kb_access = -1 ' Routes code to guardian move. When zero, Guardian move gets bypassed.
                    z1 = TIMER ' Delay timer for key lag effect in Guardian move routine.
                END IF
            END IF
        END IF

        IF v.kb_access = -1 THEN ' Guardian move routine.-----------------------------------------------------------------------------------------------------------------------------
            IF ABS(z1 - TIMER) > .05 THEN ' z1 is a key lag time delay to allow guardian to press two keys together within a reasonable amount of time.
                DO ' Faux loop added to throw out illegal key combos like up + down.
                    IF mouse_event2 = 0 THEN ' Bypass this keyboard routine if the right mouse button is in use.
                        IF mouse_event1 = 0 THEN g.m_x = 0: g.m_y = 0 ' Variables to control length and direction of Guardian movement. Must be reset to zero each cycle so diagonal moves are possible.

                        IF g.m_x = 0 AND g.m_y = 0 THEN
                            ' Eliminate illegal combos.
                            IF _KEYDOWN(18432) AND _KEYDOWN(20480) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Up + down
                            IF _KEYDOWN(19712) AND _KEYDOWN(19200) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Left + right.

                            ' IF female THEN STOP AND GET #1, directions. Keys that control movement.
                            IF _KEYDOWN(18432) THEN ' Up-arrow.
                                g.m_y = -1 ' To move 1-row up.
                            END IF
                            IF _KEYDOWN(19712) THEN ' Right-arrow.
                                g.m_x = 1 ' To move 1-column right.
                            END IF
                            IF _KEYDOWN(20480) THEN ' Down-arrow.
                                g.m_y = 1 ' To move 1-row down.
                            END IF
                            IF _KEYDOWN(19200) THEN ' Left-arrow.
                                g.m_x = -1 ' To move 1-column left.
                            END IF

                            IF g.m_x AND g.m_y THEN ' Double key hold. Routine to cancel keys when double hold is lifted. Compensates for both keys not being released at exactly the same time.
                                combo = -1 ' Double key hold in progress.
                            ELSE
                                IF combo THEN combo = 0: v.kb_access = 0: EXIT DO ' Double key hold was just removed, so skip Guardian move and exit.
                            END IF
                        END IF
                    END IF

                    ' Move Guardian. *****************************************************************************************************
                    IF g.y + g.m_y > v.top AND g.y + g.m_y <= v.bottom AND g.x + 2 * g.m_x > v.left AND g.x + 2 * g.m_x < v.right THEN
                        LOCATE g.y, g.x
                        PRINT " ";
                        g.y = g.y + g.m_y: g.x = g.x + 2 * g.m_x
                        IF SCREEN(g.y, g.x) <> 32 AND SCREEN(g.y, g.x) <> g.m_asc THEN ' Guardian abducted by bad move. Ignore if you run into your own missile.
                            CALL guardian_abduction: EXIT SUB
                        ELSE
                            LOCATE g.y, g.x
                            PRINT g.flagship;
                        END IF
                    ELSE
                        BEEP ' Hit boundary.
                    END IF
                    v.kb_access = 0 ' Guardian move completed. Returns control to general keyboard next cycle.
                    z2 = TIMER ' Timer for moving. Lag regulated by "g.thrusters" variable.
                    EXIT DO ' ************************************************************************************************************
                LOOP
            END IF '
        ELSE ' If you want additional key routines, put them here...------------------------------------------------------------------------------------------------------------------
            CALL mouse(mouse_event1, mouse_event2)
            ky$ = INKEY$
            IF mouse_event1 = -1 THEN ky$ = CHR$(9)
            IF LEN(ky$) = 1 THEN ' For demo, exclude keys that start with chr$(0). Note without this arrow keys would still register here.
                SELECT CASE ky$
                    CASE CHR$(9) ' Tab key. Bug note: Space bar will not register with arrow up + arrow v.left.
                        IF ABS(z8 - TIMER) > .25 THEN
                            SELECT CASE g.m_launcher
                                CASE ""
                                    IF mouse_event1 = -1 THEN g.m_launcher = "mouse" ELSE g.m_launcher = "keyboard" ' Initiate.
                                CASE "keyboard"
                                    IF mouse_event1 = -1 THEN
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "mouse" ' Switch.
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                                CASE "mouse"
                                    IF mouse_event1 = 0 THEN ' Tab key was used.
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "keyboard" ' Switch
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                            END SELECT
                            IF LEN(ky$) THEN
                                g.m_status = -1 ' -1 indicates missile just fired.
                                IF mouse_event1 = 0 THEN v.mouse_or_key_move = 0: ' Key pressed missile fire clears any previous mouse button missile fire.
                            END IF
                            mouse_event1 = 0 ' Completes left mouse missile firing cycle.
                            z8 = TIMER ' Missile firing delay reset.
                        END IF
                    CASE CHR$(27) ' Esc
                        _DELAY 1: SYSTEM
                    CASE ELSE
                        REM PRINT "You pressed key: "; ky$;
                END SELECT
            END IF
        END IF '----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    LOOP
END SUB

SUB comm
    STATIC middle% ' Local variable.
    IF middle% = 0 THEN middle% = v.left + (v.right - v.left) \ 2
    msg$ = "  Thrusters = " + LTRIM$(STR$(5 * (20 - g.thrusters))) + "%  Fired = " + LTRIM$(STR$(g.m_fired)) + " Alien ships = " + LTRIM$(STR$(a.count)) + " Level = " + LTRIM$(STR$(v.level)) + " Guardians: "
    LOCATE 1, v.left + middle% - ((LEN(msg$) + 8) \ 2): PRINT msg$;
    SELECT CASE g.num
        CASE 3
            PRINT g.icon; " "; g.icon; " "; g.icon;
        CASE 2
            PRINT g.icon; " "; g.icon; " ";: COLOR 8, 0: PRINT g.icon;: COLOR 7, 0
        CASE 1
            PRINT g.icon; " ";: COLOR 8, 0: PRINT g.icon; " "; g.icon;: COLOR 7, 0
        CASE 0
            COLOR 8, 0: PRINT g.icon; " "; g.icon; " "; g.icon; " ";: COLOR 7, 0
    END SELECT
    PRINT "   "; ' Cut off any former printing caused by length changes in comm report like when numbers change from double to single digits.
END SUB

SUB Guardian_missiles
    STATIC z4

    DIM direction AS INTEGER ' 8 possible directions. local variable.

    IF ABS(z4 - TIMER) > .03 THEN
        z4 = TIMER
        IF v.mouse_or_key_move = 0 THEN ' GAME OPTION: Remove this first IF/THEN to make it necessary to move  Guardian in a direction to fire in that direction.
            IF g.m_y <> 0 OR g.m_x <> 0 THEN g.diry = g.m_y: g.dirx = g.m_x ' Initiate by setting row and column missile direction to last column and row movement direction of Guardian location.
        END IF
        IF g.m_status = -1 THEN
            IF g.diry = -1 AND g.dirx = 0 THEN
                direction = 1
            ELSEIF g.diry = -1 AND g.dirx = 1 THEN
                direction = 2
            ELSEIF g.diry = 0 AND g.dirx = 1 THEN
                direction = 3
            ELSEIF g.diry = 1 AND g.dirx = 1 THEN
                direction = 4
            ELSEIF g.diry = 1 AND g.dirx = 0 THEN
                direction = 5
            ELSEIF g.diry = 1 AND g.dirx = -1 THEN
                direction = 6
            ELSEIF g.diry = 0 AND g.dirx = -1 THEN
                direction = 7
            ELSEIF g.diry = -1 AND g.dirx = -1 THEN
                direction = 8
            END IF

            IF m_n(direction) + 1 <= g.m_max THEN ' Don't fire if out of missiles.
                IF g.y > v.top + 1 AND g.y < v.bottom AND g.x > v.left + 1 AND g.x < v.right - 1 THEN ' Don't fire if at a border.
                    m_n(direction) = m_n(direction) + 1
                    m_y(m_n(direction), direction) = g.y
                    m_x(m_n(direction), direction) = g.x
                    g.m_fired = g.m_fired + 1
                    SOUND 900, .1
                END IF
            END IF
            g.m_status = 1 ' Code will now execute missile launch.
        END IF

        IF g.m_status = 1 THEN
            FOR g.m_d = 1 TO 8 ' Check all directions.
                IF m_n(g.m_d) > 0 THEN
                    j = m_n(g.m_d)
                    FOR g.m_n = 1 TO j
                        IF m_y(g.m_n, g.m_d) = g.y AND m_x(g.m_n, g.m_d) = g.x THEN
                        ELSE
                            LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d): PRINT " ";
                        END IF
                        SELECT CASE g.m_d ' Missile direction.
                            CASE 1
                                IF m_y(g.m_n, g.m_d) - 1 > v.top THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR ' Missile off screen.
                                END IF
                            CASE 2
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 3
                                IF m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 4
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 5
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 6
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 7
                                IF m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 8
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                        END SELECT
                    NEXT
                    IF a.count = 0 THEN EXIT SUB
                END IF
            NEXT
            IF g.m_fired <= 0 THEN g.m_fired = 0: g.m_status = 0 ' All missiles cleared.
        END IF
    END IF
END SUB

SUB missile_check (k)
    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d) ' Place cursor at current missile position.
    k = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)) ' Read the screen.
    id_by_color = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d), 1)
    IF k <> 32 AND k <> g.m_asc AND k <> ASC(g.flagship) THEN ' If screen space is occupied by alien ship then explosion.
        CALL remove_missile
        CALL explosion
        CALL remove_ship(id_by_color)
        k = -1 ' A flag to exit the FOR/NEXT loop upon return.
    ELSE
        COLOR 14: PRINT CHR$(g.m_asc);: COLOR 7 ' Print missile on the screen. Missile advances here. Only place a missile is printed to the screen.
    END IF
END SUB

SUB remove_missile
    m_n(g.m_d) = m_n(g.m_d) - 1 ' Counter. Reduce the number of missiles fired, in a specific direction, by 1.
    FOR k = g.m_n TO m_n(g.m_d) ' Re-stack arrays.
        m_y(k, g.m_d) = m_y(k + 1, g.m_d)
        m_x(k, g.m_d) = m_x(k + 1, g.m_d)
    NEXT
    m_y(k, g.m_d) = 0: m_x(k, g.m_d) = 0 'Zero out location variables of the missile removed. A zero removes unnecessary loop checking for other routines.
    g.m_fired = g.m_fired - 1 ' Count of number of missiles fired is reduce by 1.
END SUB

SUB mask_missiles
    FOR i = 1 TO g.m_max
        FOR j = 1 TO 8
            IF m_y(i, j) <> 0 THEN LOCATE m_y(i, j), m_x(i, j): PRINT " "; ' Mask missile.
    NEXT j, i
    m_status = 0: m_fired = 0: m_n = 0: m_d = 0: m_y = 0: m_x = 0
    REDIM m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8)
END SUB

SUB explosion
    soundfile% = 1 ' Local variable turns sound on.
    b_y1 = CSRLIN: b_x1 = POS(0)
    VIEW PRINT v.top TO v.bottom ' Needed to set print error parameters.
    h = 0
    ON ERROR GOTO offscreen
    DO
        IF h = 1 THEN burst$ = " " ELSE burst$ = CHR$(249)
        IF v.intro THEN burst$ = "" ' No fireworks, just flash and sound.
        h = h + 1
        ' Flash
        IF eflag THEN
            IF ABS(z7 - TIMER) > .1 THEN
                eflag = 0
                PALETTE 0, 0
                z7 = TIMER
            ELSE
                j = -j * -1
                IF j = 0 THEN
                    PALETTE 0, 63
                    _DELAY .05
                ELSE
                    PALETTE 0, 0
                    _DELAY .05
                END IF
            END IF
        END IF
        IF eflag = 0 THEN
            PALETTE 0, 36
            IF soundfile% THEN
                _SNDPLAY v.snd1
                _DELAY .05
                PALETTE 0, 0
                _SNDPLAY v.snd2
            ELSE
                _DELAY .075
            END IF
            eflag = -1
            z7 = TIMER
        END IF
        PALETTE 0, 0 ' End flash.

        FOR i = 1 TO 5
            SELECT CASE i
                CASE 1
                    COLOR 15
                    LOCATE b_y1, b_x1: PRINT burst$;
                    _DELAY .1
                CASE 2
                    IF burst$ = CHR$(249) THEN burst$ = CHR$(250)
                    COLOR 14, 0
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .1
                CASE 3
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                CASE 4
                    COLOR 4
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .3
                CASE 5
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
            END SELECT
        NEXT
        IF h = 1 THEN h = 0: EXIT DO
    LOOP
    VIEW PRINT
    ON ERROR GOTO 0
    COLOR 7
    IF a.count = 1 THEN CALL mask_missiles ' Clear unexploded missiles off the screen when changing levels. Game Option: Remove variable to mask missiles after every explosion.
    LOCATE b_y1, b_x1
END SUB

SUB remove_ship (id_by_color) ' Removes ship by color identification.
    i = a.itr
    ON ERROR GOTO offscreen
    LOCATE a_mask_y(id_by_color), a_mask_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    LOCATE a_y(id_by_color), a_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    ON ERROR GOTO 0
    a.itr = id_by_color: CALL a_erase
    a_ran(id_by_color) = -1 ' Denotes alien ship was destroyed and removed from battle.
    a.itr = i
    a.count = a.count - 1 ' a.count = 0  will cause program to exit any unneeded subs/loops after the game level sub is completed.
    CALL comm
    ' Restore any alien ships in blast zone.
    FOR i = 1 TO a.max
        IF a_ran(i) > 0 THEN
            IF a_y(i) > v.top AND a_x(i) > v.left AND a_x(i) + LEN(a.ship) < v.right THEN LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;
        END IF
    NEXT
    COLOR 7
    LOCATE g.y, g.x: PRINT g.flagship; ' Re-display Guardian.
    IF a.count = 0 THEN ' All alien ships destroyed.
        IF v.level < 3 THEN
            CALL game_level ' Advance to next level.
        ELSE
            CALL game_over ' Guardian wins! IMPORTANT: Must now exit sub without other conditions.
        END IF
    END IF
END SUB

SUB alien_move
    STATIC z5
    y_restore = CSRLIN: x_restore = POS(0) ' Restore column and row upon exit.
    IF ABS(z5 - TIMER) > a.cycle_delay OR v.intro THEN ' z5 is a time delay for alien space ship maneuvers. It can be altered in the "game" subroutine.
        IF v.intro = 0 THEN h_alien_nom = INT(RND * a.max) + 1 ELSE h_alien_nom = 15
        FOR h = 1 TO h_alien_nom ' Local counting variable for alien number of moves in this cycle.
            a.itr = a.itr + 1: IF a.itr > a.max THEN a.itr = 1 ' Needed to offset the EXIT DO hover event, which on exit does not affect the a.itr variable.
            IF a_ran(a.itr) <> -1 THEN ' This is how a destroyed ship is bypassed. -1 is a destroyed alien ship. Code moves to end of DO:LOOP.
                IF a_inertia(a.itr) = 0 THEN ' Determine how many moves in one direction.
                    a_inertia(a.itr) = INT(RND * (v.bottom - v.top) / 2) + 1 ' How many moves to go in any one direction.
                    a_ran(a.itr) = INT(RND * 8) + 1 ' Choose 1 of 8 possible directions.

                    IF a_ran(a.itr) = a_olda_ran(a.itr) OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 1 OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 5 THEN
                        EXIT FOR ' Just hover if direction was not changed on existing alien space ship or if a new alien space ship is entering from the sides and up or down was generated.
                    END IF

                    SELECT CASE a_ran(a.itr) ' Get changes in column and row coordinates.
                        CASE 1: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0 '  Up.
                        CASE 2: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 2 '  Up and right.
                        CASE 3: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 2 '   Right.
                        CASE 4: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 2 '   Down and right.
                        CASE 5: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0 '   Down.
                        CASE 6: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -2 '  Down and left.
                        CASE 7: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -2 '  Left.
                        CASE 8: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -2 ' Up and left.
                    END SELECT

                    IF a_y(a.itr) = 0 AND a_x(a.itr) = 0 AND a_ran(a.itr) <> -1 THEN ' New alien space ship enters the screen.
                        i = RND * (v.bottom - v.top) \ 4
                        a_y(a.itr) = (v.bottom - v.top) \ 4 + i + v.top
                        IF a_ran(a.itr) < 5 THEN ' Determine side of entry from initial direction.
                            IF SCREEN(a_y(a.itr), v.left + LEN(a.ship)) = 32 THEN
                                a_x(a.itr) = v.left + 1 ' Enter from the left side and go right.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        ELSE
                            IF SCREEN(a_y(a.itr), v.right - LEN(a.ship) + 1) = 32 THEN
                                a_x(a.itr) = v.right - LEN(a.ship) ' Enter from the right side and go left.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                    a_olda_ran(a.itr) = a_ran(a.itr) ' Remember last direction. Another line uses this to disallow any RND that chooses the same direction twice.
                ELSE
                    a_inertia(a.itr) = a_inertia(a.itr) - 1 ' Count down the number of moves in any one direction. When zero, switch direction.
                END IF

                FOR i = 1 TO a.max
                    IF i <> a.itr AND a_y(i) <> 0 THEN
                        IF a_y(a.itr) + a_y_loc(a.itr) = a_y(i) THEN
                            IF a_x(a.itr) + a_x_loc(a.itr) + LEN(a.ship) > a_x(i) AND a_x(a.itr) + a_x_loc(a.itr) < a_x(i) + LEN(a.ship) THEN
                                collide = 1
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                NEXT
                IF collide = 1 THEN
                    j = a_y(a.itr): k = a_x(a.itr)
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_inertia(a.itr) = 0
                    collide = 0 ' Collision detection off. Collision was detected and avoided.
                ELSE
                    j = a_y(a.itr) + a_y_loc(a.itr): k = a_x(a.itr) + a_x_loc(a.itr)
                END IF

                IF j <= v.top OR k <= v.left OR k + LEN(a.ship) > v.right THEN ' Alien ship out of range, off screen.
                    a_inertia(a.itr) = 0 ' These two lines keep the out of range ship(s) reasonably nearby.
                    IF j > v.top - 4 AND k < v.right + 3 AND k > v.left - 4 THEN a_y(a.itr) = j: a_x(a.itr) = k
                    IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN
                        LOCATE a_mask_y(a.itr), a_mask_x(a.itr)
                        PRINT SPACE$(LEN(a.ship)); ' Mask old position here because the show part of the mask-and-show routine cannot be used when out of range.
                    END IF
                    IF a_offscrn(a.itr) > 25 THEN a_y(a.itr) = 0: a_x(a.itr) = 0: a_inertia(a.itr) = 0: a_offscrn(a.itr) = 0
                    a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_offscrn(a.itr) = a_offscrn(a.itr) + 1
                ELSE
                    ' Check for v.bottom collision and reverse course if detected.
                    COLOR a.itr
                    IF j >= v.bottom THEN
                        a_y_loc(a.itr) = -a_y_loc(a.itr): a_x_loc(a.itr) = -a_x_loc(a.itr)
                    ELSE
                        a_y(a.itr) = j: a_x(a.itr) = k ' Next move coordinates.
                        ii = 0
                        kk = ASC(g.flagship)
                        FOR i = 0 TO LEN(a.ship) - 1 ' Check area through width of ship. Remember all or parts of ship are still present on screen.
                            SELECT CASE SCREEN(j, k + i)
                                CASE kk
                                    ii = 1 ' Indicates contact with flagship and evokes call abduction routine a few lines down.
                                    EXIT FOR
                                CASE g.m_asc
                                    ii = 2 ' Indicates ship into missile collision.
                                    EXIT FOR 'Okay to exit as a missile and Guardian craft cannot be present in the same location.
                            END SELECT
                        NEXT
                        IF ii <> 2 THEN ' This will make a move unless a ship into missile event would occur.
                            '--------------------------------------------Move alien ship-------------------------------------------------
                            IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN LOCATE a_mask_y(a.itr), a_mask_x(a.itr): PRINT SPACE$(LEN(a.ship));
                            LOCATE j, k: PRINT a.ship;
                            a_mask_y(a.itr) = j: a_mask_x(a.itr) = k ' Remember these coordinates to erase alien space ship on next loop.
                            '------------------------------------------------------------------------------------------------------------
                        END IF
                        IF ii = 1 THEN CALL guardian_abduction: EXIT FOR ' Exit loop.
                    END IF
                    COLOR 7
                END IF
            END IF ' a_ran(a.itr) > -1 exit point.
            IF a.itr = a.max THEN a.itr = 0: EXIT FOR ' Finished loop. Keep this outside the IF/THEN statement.
        NEXT h
        z5 = TIMER
        LOCATE y_restore, x_restore ' Restore entry column and row positions.
    END IF ' End time event.
END SUB

SUB guardian_abduction

    IF v.intro = 0 THEN
        CALL mask_missiles ' Clear unexploded missiles off the screen after alien abduction.

        msg$ = "[GUARDIAN ABDUCTED]"
        SOUND 500, .4: SOUND 1000, .3: SOUND 1500, .2
        PCOPY 0, 1
        SCREEN 0, 0, 1, 1
        LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
        COLOR 14 + 16: PRINT msg$;
        _DELAY 2
        SCREEN 0, 0, 0, 0
    END IF

    DO
        j = 0
        FOR i = 1 TO a.max
            IF a_y(i) >= v.top AND a_x(i) >= v.left AND a_x(i) <= v.right - LEN(a.ship) THEN
                SOUND 500, .04
                LOCATE a_y(i), a_x(i): PRINT SPACE$(LEN(a.ship));
                IF a_y(i) <> v.top THEN a_y(i) = a_y(i) - 1: LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;: _DELAY .02: j = 1
            END IF
        NEXT
    LOOP WHILE j
    COLOR 7
    IF v.intro THEN EXIT SUB
    g.num = g.num - 1
    CALL comm
    IF g.num = 0 THEN ' Game Over.
        CALL comm
        CALL game_over
    END IF
    v.level = -v.level ' Satisfies two conditions. 1) Puts game back to beginning level. 2) Bypasses the part of the game_level routine that reprints the guardian craft.
    CALL game_level
    _DELAY .75
    a.count = 0 ' Needed to exit subs back to main game loop.
END SUB

SUB game_level ' Evaluates both alien defeated on a level and Guardian abduction.
    IF v.level > -1 THEN ' Bypass if abducted (v.level = -1) Make a new variable for this if the game is modified to not go back to the starting level.
        LOCATE g.y, g.x
        PRINT g.flagship;
        _DELAY .5
        LOCATE g.y, g.x
        PRINT " "; ' Mask Guardian craft.
    END IF
    _DELAY 1
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    IF v.level < 0 THEN v.level = ABS(v.level) ELSE v.level = v.level + 1 ' Less than zero if abducted.
    msg$ = "[LEVEL " + LTRIM$(STR$(v.level)) + "]"
    LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16: PRINT msg$;
    j = 0: k = 0
    DO
        IF ABS(z0 - TIMER) > .15 THEN
            IF j = 0 THEN j = 1: SOUND 750, .3: k = k + 1 ELSE j = 0: _DELAY .1
            z0 = TIMER
        END IF
    LOOP UNTIL k = 6
    COLOR 7
    LOCATE , v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    PRINT msg$;
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    _DELAY .75
    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Reset column and row for Guardian craft.
    LOCATE g.y, g.x
    COLOR 15 + 16: PRINT g.flagship;
    _DELAY 1.5
    COLOR 7: LOCATE g.y, g.x: PRINT g.flagship;
    v.level_up = -1
    CALL mouse(0, 0) ' Clears the mouse_events.
END SUB

SUB a_erase
    a_y(a.itr) = 0: a_x(a.itr) = 0: a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_inertia(a.itr) = 0: a_ran(a.itr) = 0: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_olda_ran(a.itr) = 0
END SUB

SUB mouse (mouse_event1, mouse_event2)
    STATIC z9, z10, lb, lb_status ' lb_status keeps track of press/release.
    WHILE _MOUSEINPUT
        IF ABS(z10 - TIMER) > .1 THEN
            z10 = TIMER
            IF _MOUSEWHEEL > 0 THEN ' Down
                IF g.thrusters < 20 THEN g.thrusters = g.thrusters + 1
            ELSEIF _MOUSEWHEEL < 0 THEN ' Up
                IF g.thrusters > 0 THEN g.thrusters = g.thrusters - 1
            END IF
        END IF
    WEND
    mx = _MOUSEX
    my = _MOUSEY
    lb = _MOUSEBUTTON(1)
    rb = _MOUSEBUTTON(2)

    IF lb THEN
        IF lb_status = 0 THEN
            IF ABS(z9 - TIMER) > .33 THEN
                z9 = TIMER
                mouse_event1 = -1: lb_status = -1: ' Left button down
            END IF
        END IF
    ELSE
        IF lb_status THEN lb_status = 0 ' Left button was released.
    END IF

    IF v.mouse_get_screen THEN
        IF lb_status = -1 THEN
            x$ = CHR$(SCREEN(my, mx))
            SELECT CASE v.mouse_get_screen
                CASE 1 ' Skip intro.
                    IF x$ = "S" THEN v.intro = 999: SOUND 1000, .3: EXIT SUB
                CASE 2 ' Replay
                    IF x$ = "Y" THEN v.play = 999: SOUND 1000, .1: EXIT SUB
                    IF x$ = "N" THEN v.play = -999: SOUND 1000, .1: EXIT SUB
            END SELECT
        END IF
    END IF
    cx = g.x: cy = g.y ' Angular calculations provided by bplus from the QB64 Phoenix Forum.
    stepX = ABS(cx - mx): stepY = ABS(cy - my)

    dAng = INT(_R2D(_ATAN2(my - cy, mx - cx)) + .5)
    IF dAng < 0 THEN dAng = dAng + 360

    IF dAng <= 90 THEN
        startA = 0: endA = dAng: ra = dAng
    ELSEIF dAng <= 180 THEN
        startA = dAng: endA = 180: ra = 90 - (dAng - 90)
    ELSEIF dAng <= 270 THEN
        startA = 180: endA = dAng: ra = dAng - 180
    ELSEIF dAng <= 360 THEN
        startA = dAng: endA = 360: ra = 90 - (dAng - 270)
    END IF

    m_y = 0: m_x = 0
    IF ra <= 90 AND ra >= 50 THEN
        IF my > g.y THEN
            x$ = "down": m_y = 1: m_x = 0 ' Down.
        ELSE
            x$ = "up": m_y = -1: m_x = 0 ' Up.
        END IF
    ELSEIF ra < 50 AND ra >= 15 THEN
        IF mx > g.x AND my > g.y THEN
            x$ = "down right": m_y = 1: m_x = 2 ' Down and right.
        ELSEIF mx < g.x AND my > g.y THEN
            x$ = "down left": m_y = 1: m_x = -2 ' Down an left.
        ELSEIF mx > g.x AND my < g.y THEN
            x$ = "up right": m_y = -1: m_x = 2 ' Up and right.
        ELSEIF mx < g.x AND my < g.y THEN
            x$ = "up left": m_y = -1: m_x = -2 ' Up and left.
        END IF
    ELSEIF ra < 15 AND ra >= 0 THEN
        IF mx > g.x THEN
            x$ = "right": m_y = 0: m_x = 2 ' Right
        ELSE
            x$ = "left": m_y = 0: m_x = -2 ' Left
        END IF
    END IF

    IF rb AND mouse_event2 = 0 THEN
        mouse_event2 = -1
        g.m_y = m_y: g.m_x = m_x / 2
        v.mouse_or_key_move = 1 ' Right mouse button to move.
    ELSE
        IF mouse_event2 THEN mouse_event2 = 0
    END IF

    IF mouse_event1 THEN
        g.diry = m_y: g.dirx = m_x / 2
        v.mouse_or_key_move = -1
    END IF
END SUB

SUB game_over
    v.mouse_get_screen = 2 ' Allows mouse to select replay options.
    _DELAY .5
    CALL comm
    IF a.count = 0 THEN SOUND 1000, .75: SOUND 500, .75: SOUND 1000, 1.5 ELSE SOUND 1000, .75: SOUND 700, .75: SOUND 500, 1.5
    _DELAY .5
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    IF a.count = 0 THEN msg$ = "[GUARDIAN WINS]" ELSE msg$ = "[Game Over]"
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16
    PRINT msg$;
    _DELAY 2
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14: PRINT msg$;
    msg$ = "Replay? Y/N"
    _DELAY 1
    LOCATE (v.bottom - v.top) \ 2 + v.top + 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 8
    PRINT msg$;
    COLOR 7
    DO
        _LIMIT 30
        CALL mouse(mouse_event1, mouse_event2)
        ky$ = INKEY$
        IF LEN(ky$) OR v.play = 999 OR v.play = -999 THEN
            IF v.play = 999 OR UCASE$(ky$) = "Y" OR ky$ = CHR$(13) THEN v.play = 1: v.level = 0: VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT: EXIT DO
            IF v.play = -999 OR UCASE$(ky$) = "N" OR ky$ = CHR$(27) THEN
                msg$ = "   [Bye Bye]   "
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14 + 16: PRINT msg$;
                _DELAY 1
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14: PRINT msg$;
                _DELAY .5
                SYSTEM
            END IF
        END IF
    LOOP
END SUB


Note 1: I coded it to adjust to your desktop size. It works well on mine, but let me know if it doesn't seem to be a good fit on yours. You can always press Alt + Enter, to run it full screen, too.

Note 2: Alien's can run off the screen, and come back. What I need to work on now is a method so the last one doesn't run off the screen for too long. Normally it's just a few seconds, but I have had a handful of time it has taken a few minutes, and that's way too long. But that, and a little more A.I. is for another day... after my egg facial wears off from another discussion thread.

Pete

Don't forget, download the soud effects file below, and save it in the same folder.
.7z   ASCII Invaders Sound.7z (Size: 1.45 MB / Downloads: 85)
Reply
#14
I just got done playing a few rounds of Guardians. It's amazing how much can be done on a giant ASCII screen. I see no reason why this game couldn't be converted to graphics easily.

The game play is difficult. It took me a few times to beat level 3 "Guardian Wins". It's difficult to aim the bullets at the enemy since you really have no indication of where the enemy ships are heading. There's no "front end" of the ship so to speak. The game play reminds me of a cross between Asteroids and Phoenix.

https://en.wikipedia.org/wiki/Phoenix_(video_game)

Converting this to graphics would allow:

- Rotating the player ship in more than 8 positions allowing for finer missile aiming
- The player and enemy ships could have front ends showing which direction they are heading
- The enemy ships could be made to turn around in tight circles showing they are changing direction
- Explosion effects (although your ASCII explosions are really cool)
- Allow the player ship to be thrusted around like the ship in Asteroids. That gets intuitive pretty quickly and allows "sliding" around enemies.

When converting I would suggest:

- use sprites that have the same odd dimensions: 129x129, 65x65, 31x31, etc..
    This gives the sprite a definitive center point for _MAPTRIANGLE rotation. Over the years I've found this to work best with _MAPTRIANGLE.
- Use a combination of rectangular collision and line intersect collision. Rectangular for proximity and then line intersection to see if a bullet crosses the rotated bounding box surrounding the enemy ship. Basically a box rotating inside of another box. The inner box will always maintain the same dimension while the outer box changes in size.
    (As you rotate a sprite it changes size. The overall rectangle grows and shrinks. This is where rectangular collision comes into play for proximity. The four points of rotation are used to create a bounding box of four lines that line detection can then use to see if the actual sprite was hit. Line detection eliminates the effect of "bullet pass through" where an enemy is heading straight for a bullet and the bullet passes through because of the speed of the bullet and enemy combined causes the bullet to skip over. I had to do this in Widescreen Asteroids because of small fast moving asteroids. An imaginary line is drawn from the previous bullet location to the next and that imaginary line is used for line intersection with the four bounding box lines. This also gives you a precise coordinate of collision allowing for accurate explosion placement.)

Some suggestions to add:

- A limited number of "smart" missiles per level that lock on to an enemy and chase it down.
- As the enemy ships get closer to the player they get "smarter" and home in on the player.
    As the player gets "braver" they'll get closer to the enemy ships for better aiming knowing the risks of doing that.
- Every once in a while the enemy ships could group into small attack formations and swoop toward the player.
- As the levels progress the enemy ships gain skills: shooting, better attack formations, the "smart" home in distance gets greater, etc..

This is a concept for a game that graphics would really enhance. all of the above suggestions could easily be put into the ASCII version as well.

Terry
Reply
#15
Question, did you use the keyboard or the mouse to control Guardian? I like the mouse. I would have programmed a joystick, if I had one, but the mouse is a close second, as it can cause Guardian to move and fire in any direction (based on where the pointer is in relation to the ship (relationship) and the mouse wheel controls the thrust speed.

Yeah, with just randomization, it's hard to win for a bit,and I'm actually continuing work on an AI "hunting" routine as we speak. I started that routine last night. The next effect I want to explore is a reduction of power so if you explode an enemy ship too close (within the animated blast zone.) it results in slower speed, fewer missiles, etc. Of course these two modifications benefit the aliens, and at some point, it might get too difficult to play to win, which could be balanced with something like your suggestion of using smart missiles.

One game aspect I' torn between is there are three ways this game could be coded to play. 1) No motion. 2) Motion with no stopping. 3) The combo of 1 and 2 that it currently is. Each has a different playing strategy.

I've got a list of other ideas for the game. Surprise factors, like that swarming idea you posted (hadn't thought of that one, thanks!) a mother ship, etc. all make games more unpredictable and exciting.

I feel the trick is to never over-complicate things. For instance 10 keys to control all sorts of player choices is over-kill! Oh, and remember Defender? It granted the player smart bombs for good play. That was cool, but what the developers failed to address was a player could camp out safely at the bottom of the screen, build up smart bombs, and then just keep using them. Took forever playing tit that way, but once I rolled the counter over on the Atari version using that method.

What I haven't had a good feel for is a good way to make a "high score" in this game. It's a bit like Star Master. You win or lose. The way that game determined score was by how fast you were able to win, minus the number of times you were hit. That's the best I can come up with for this game, as well.

Thanks a ton for all the graphics mechanics "how to's.". Bob mentioned he had something worked up for a rotating Guardian sprite. I don't know how much difference there would be in a SCREEN 12 version vs using _NEWMAGE. I don't think Bob has ever worked using _NEWIMAGE, but I guess a sprite is a sprite in either case.

Thanks for giving it a spin,

Pete
Reply
#16
Okay, if you ever get really good at my first version, I made the aliens even tougher to beat...

GUARDIAN V 1.1 (Don't forget to download the sound files in the other post if this is new to you.)
Code: (Select All)
DEFINT H-K
$RESIZE:ON
_RESIZE OFF
RANDOMIZE TIMER
' Note: timer is not adjusted for stroke of midnight event, so don't stay up late playing this.
REM Main
f1 = 22 ' Sets font size to 22 and calculates the max screen height and width for your desktop.
h = (_DESKTOPHEIGHT - 60) \ f1
w = _DESKTOPWIDTH \ (f1 / 1.66)
WIDTH w, h
_SCREENMOVE 0, 0
fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\lucon.ttf" 'Find Windows Folder Path.
font& = _LOADFONT(fontpath$, f1, "monospace")
_FONT font&
_DELAY .25
_RESIZE ON , _SMOOTH ' Allows resizing. Note: this is for slight adjustments. As of this version there is no compensatory function to change the font size during screen size changes.
TYPE gen_var
    intro AS INTEGER ' Runs a protion of the alien subroutine as part of the intro.
    nol AS INTEGER ' Number of game levels.
    redo_level AS INTEGER ' Alien abdution results in redo level.
    level AS INTEGER ' Current game level.
    level_up AS INTEGER ' Routes code to next game level.
    top AS INTEGER ' Top boundary. (Changeable).
    bottom AS INTEGER ' Bottom boundary. (Changeable).
    left AS INTEGER ' Left boundary. (Changeable).
    right AS INTEGER ' Right boundary. (Changeable).
    kb_access AS INTEGER ' Routes code to either Guardian/Alien or general keyboard routine. -1 Guardian/Alien, 0 keyboard.
    mouse_or_key_move AS INTEGER
    mouse_get_screen AS INTEGER
    play AS INTEGER
    snd1 AS LONG ' Explosion sound effect.
    snd2 AS LONG ' Explosion sound effect.
END TYPE

DIM SHARED v AS gen_var

TYPE guardian
    num AS INTEGER ' Number of Guardians aka lives.
    diry AS INTEGER ' Guardian row move. +1, 0, -1.
    dirx AS INTEGER ' Guardian column move. +2, 0, -2. Equals vertical pixel movement. 16x8.
    y AS INTEGER ' Guardian coordinates row.
    x AS INTEGER ' Guardian coordinates column.
    thrusters AS INTEGER ' Guardian speed. (User Determined).
    m_max AS INTEGER ' Restricts # of missiles fired in one direction.
    m_status AS INTEGER ' Missile status. -1 when fired, 1 while moving.
    m_fired AS INTEGER ' The number of missile deployed and still active.
    m_n AS INTEGER ' FOR/NEXT counter variable shared by other routines to index through the number of missiles fired in a specific direction.
    m_d AS INTEGER ' Missile direction. (1-8).
    m_y AS INTEGER ' Missile row advancement increment: +1, 0, -1 Note: Missile row and column coordinates are defined in arrays.
    m_x AS INTEGER ' Missile column advancement increment: +2, 0, -2. Equals vertical pixel movement. 16x8.
    m_asc AS INTEGER ' ASCII character representing a fired missile.
    m_launcher AS STRING
    icon AS STRING ' Guardian comm icon. For this edition, it is the same as the flagship: "*"
    flagship AS STRING ' Guardian ascii character.
END TYPE

DIM SHARED g AS guardian

TYPE alien
    max AS INTEGER ' Maximum # of alien ships on screen.
    count AS INTEGER ' Number of alien ships. (Counter).
    itr AS INTEGER ' Iteration array number to cycle through the active alien ships. (Counter).
    cycle_delay AS SINGLE ' Timer cycle controls how fast alien ships move.
    ship AS STRING ' Alien ship ASCII design.
END TYPE

DIM SHARED a AS alien

DO
    GOSUB set_arrays

    SELECT CASE v.play
        CASE 0
            CALL set_up
            CALL comm
            CALL intro
            GOSUB set_arrays: GOSUB zero_variables ' Reset arrays and zero variables after intro.
            CALL level_up
        CASE 1
            CALL set_up
            CALL comm
    END SELECT

    CALL game_level ' Display level.

    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Set initial column and row for Guardian craft.

    CALL game


    IF v.level < v.nol AND g.num > 0 THEN
        IF v.redo_level = 0 THEN CALL level_up
    ELSE
        CALL game_over
        v.level = 1 ' Reinitiate if player chooses to replay.
    END IF

    GOSUB zero_variables
LOOP

set_arrays:
ii = 15 ' Default max setting for number of alien ships used here to initially dim arrays.
g.m_max = 8 ' * missiles max per direction.
REDIM SHARED a_y(ii), a_x(ii), a_mask_y(ii), a_mask_x(ii), a_inertia(ii) ' Alien movement.
REDIM SHARED a_ran(ii), a_olda_ran(ii), a_y_loc(ii), a_x_loc(ii), a_offscrn(ii) ' Alien movement.
REDIM SHARED m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8) ' Guardian missiles.

' Array descriptions and actions.
' a_y() , a_x() Alien ship positions rows and columns.
' a_mask_y(), a_mask_x() Alien ship last position. Masked on next move.
' a_inertia(ii) Number of moves in one direction selected by random for an alien ship.
' a_ran(ii), a_olda_ran(ii) Determines the direction in which the inertia will travel and the prior direction is kept to disallow the same direction twice.
' a_y_loc(ii), a_x_loc(ii) The row and column coordinates of the aliens ships.
' m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8)  Missile number and Missile index 1 to g.m_max for position. 8 is the fixed number of 8 different directions.
RETURN

zero_variables:
' Zero variables.
g.diry = 0: g.dirx = 0: g.m_status = 0: g.m_fired = 0: g.m_d = 0: g.m_y = 0: g.m_x = 0: v.redo_level = 0: a.max = 0: v.intro = 0
a.count = 0: a.itr = 0: a.cycle_delay = 0: v.level_up = 0: v.mouse_or_key_move = 0: g.m_launcher = ""
RETURN

skipintro:
v.intro = 999
BEEP: BEEP
VIEW PRINT v.top TO v.bottom
CLS 2
VIEW PRINT
_DELAY .5
RETURN

' Error handler.
offscreen: ' Prevents error if blast particles row and column are off-screen. Effect is a partial blast on side of screen.
IF ERR = 5 THEN er = -1: RESUME NEXT
PRINT "Opps, unexpected error"; ERR
END

SUB intro
    v.mouse_get_screen = 1 ' Allows skip intro by mouse selection.
    LOCATE _HEIGHT, _WIDTH - 15
    PRINT "[S]kip Intro"; ' Option to skip the intro using the "S" key with ONKEY statement.
    KEY 15, CHR$(0) + CHR$(31) 'scancode for S
    ON KEY(15) GOSUB skipintro
    KEY(15) ON 'turn ON [S]kip intro event trapping.
    j = (v.bottom - v.top) \ 2 + v.top
    k = (v.right - v.left) \ 2 + v.left
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .33
    SOUND 1000, .3
    msg$ = " GUARDIAN "
    LOCATE j, k - LEN(msg$) \ 2: COLOR 14: PRINT msg$;: COLOR 7
    LOCATE , k
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .66
    msg$ = SPACE$(LEN(msg$))
    LOCATE j, k - LEN(msg$) \ 2: COLOR 7: PRINT msg$;
    LOCATE j, k
    IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB ELSE CALL explosion
    LOCATE j, k: COLOR 15 + 16: PRINT g.flagship;
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .75
    v.intro = -1: a.max = 10
    FOR i = 1 TO 90
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
        SOUND 900, .05
        CALL alien_move
        LOCATE j, k: COLOR 15: PRINT g.flagship;: COLOR 7
        _DELAY .07
    NEXT
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > 1
    LOCATE j, k: PRINT " ";
    CALL guardian_abduction
    a.max = 0
    z1 = TIMER
    DO
        CALL mouse(mouse_event1, mouse_event2)
        IF v.intro = 999 THEN KEY(15) OFF: EXIT SUB
    LOOP UNTIL ABS(z1 - TIMER) > .5
    KEY(15) OFF
    LOCATE v.bottom, _WIDTH - 20: PRINT SPACE$(20);

    ' Playing Instructions.
    REDIM msg$(13)
    msg$(1) = CHR$(249) + " " + "Look towards the top of your screen for Guardian ship status."
    msg$(2) = CHR$(249) + " " + "Hold arrow keys up/dn/lt/rt or in combination to move diagonally."
    msg$(3) = CHR$(249) + " " + "Missiles can be fired simultaneously in 8 different directions."
    msg$(4) = CHR$(249) + " " + "Press Tab to fire 1-8 missiles in the direction of movement."
    msg$(5) = CHR$(249) + " " + "Press Rt Ctrl to increase thrust, or Rt Alt to reduce thrust."
    msg$(6) = ""
    msg$(7) = CHR$(249) + " " + "Mouse alternative: "
    msg$(8) = CHR$(249) + " " + "Hold right mouse button to move toward mouse pointer."
    msg$(9) = CHR$(249) + " " + "Click left mouse button to fire 1-8 missiles in direction of mouse pointer."
    msg$(10) = CHR$(249) + " " + "Mouse wheel up for more thrust, wheel down for less."
    msg$(11) = CHR$(249) + " " + "Note: Tab and left mouse button cannot be used simultaneously."
    msg$(12) = ""
    msg$(13) = CHR$(249) + " " + "If you come in contact with an alien ship, your ship and crew get abducted."
    j = 0
    FOR i = 1 TO UBOUND(msg$)
        IF LEN(msg$(i)) > j THEN j = LEN(msg$(i))
    NEXT
    i = ((v.right - v.left) - j) \ 2
    t_mrgn = 6: b_mrgn = _HEIGHT - 4: l_mrgn = i + 1: r_mrgn = v.right - i
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    FOR i = 1 TO b_mrgn - t_mrgn + 3
        LOCATE t_mrgn - 2 + i, l_mrgn - 2
        PRINT CHR$(179);
        LOCATE , r_mrgn + 2: PRINT CHR$(179);
    NEXT
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT CHR$(218);
    LOCATE t_mrgn - 2, r_mrgn + 2: PRINT CHR$(191);
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT CHR$(192);
    LOCATE b_mrgn + 2, r_mrgn + 2: PRINT CHR$(217);
    msg$ = "<Intro>"
    LOCATE t_mrgn - 2, l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    msg$ = "You are the Captain of the Guardian, an elite battle cruiser commissioned to protect our planet against alien invaders. You have 3 lives. Every time you are abducted by an alien ship, you lose a life. If you survive, and wipe out all 3 flights of alien attacks, your mission is completed. You saved Earth!"
    j = r_mrgn - l_mrgn
    LOCATE t_mrgn, l_mrgn
    msg$ = RTRIM$(msg$) + " " ' Simple word parser routine.--------
    DO
        x$ = MID$(msg$, 1, j)
        x$ = MID$(x$, 1, _INSTRREV(x$, " ") - 1)
        msg$ = LTRIM$(MID$(msg$, LEN(x$) + 1))
        LOCATE , l_mrgn: PRINT x$
    LOOP UNTIL msg$ = "" '-----------------------------------------
    PRINT
    FOR i = 1 TO UBOUND(msg$)
        LOCATE , l_mrgn
        IF LEN(msg$(i)) THEN PRINT msg$(i) ELSE PRINT
    NEXT
    PRINT: IF CSRLIN < b_mrgn - 1 THEN PRINT
    msg$ = "Good luck, Captain! Press any key to begin."
    LOCATE , l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    DO
        _LIMIT 10
        WHILE _MOUSEINPUT: WEND
        IF _MOUSEBUTTON(1) THEN SOUND 1000, .1: EXIT DO
        ky$ = INKEY$
        IF ky$ = CHR$(27) THEN SYSTEM
        IF LEN(ky$) THEN EXIT DO
    LOOP
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
END SUB

SUB set_up
    v.top = 3: v.bottom = _HEIGHT: v.left = 1: v.right = _WIDTH ' Boundaries.
    LOCATE v.top - 1, 1: PRINT STRING$(_WIDTH, CHR$(196));
    v.nol = 3
    g.flagship = CHR$(15)
    a.ship = "-<>-"
    g.num = 3 ' 3 Guardian (lives) to start.
    g.thrusters = 10 ' Shows 1/2 thrust at start up on comm.
    g.icon = g.flagship
    g.m_asc = 250: g.diry = -1: ' Initiate missile ASCII character. g.diry = -1 initiates fire upwards if unmoved.
    IF _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") THEN ' Sound effects provided by TheBOB, Bob Seguin, from The QBasic Forum.
        v.snd1 = _SNDOPEN("Thunder1.ogg", "SYNC")
        v.snd2 = _SNDOPEN("Thunder7.ogg", "SYNC")
    END IF
    v.play = -1 ' Skip into on replay.
END SUB

SUB game
    STATIC mouse_event1, mouse_event2
    REM Set mouse_event2 = 0 here to produce constant motion with directional changes.
    'Press single or arrow key combo to move. Rt Ctrl = faster / Rt Alt = slower. Tab to fire missles.
    g.thrusters = 10 ' Guardian movement delay. (1 to 20). Rt Ctrl = faster / Rt Alt = slower.

    SELECT CASE v.level
        CASE 1: a.max = 1 ' Sets number of space ships and initiates attack.
        CASE 2: a.max = 1
        CASE 3: a.max = 1
    END SELECT
    a.count = a.max
    IF a.max < 6 THEN
        a.cycle_delay = 1.005
    ELSEIF a.max < 11 THEN a.cycle_delay = .003
    ELSE a.cycle_delay = .001
    END IF

    _KEYCLEAR ' Clear any previous key presses in buffer.
    CALL mouse(0, 0) ' Clear mouse.

    CALL comm

    DO
        _LIMIT 60 ' Display at 60 frames per second.

        CALL comm

        IF g.m_status THEN CALL Guardian_missiles ' g.m_status determines Guardian or alien turn. 1 = Guardian, -1 = alien.

        IF a.count = 0 THEN EXIT SUB ' Moving on to next level.

        CALL alien_move

        ' To change thrusters only when guardian moves, remove the timer and move this routine to: IF v.kb_access = -1 THEN
        IF ABS(z3 - TIMER) > .1 THEN ' z3 is a keypress delay timer for Guardian thrusters. Check every .1 seconds for a speed change.
            IF _KEYDOWN(100307) AND g.thrusters < 20 THEN g.thrusters = g.thrusters + 1 ' Rt Ctrl key. Slows down Guardian ship down.
            IF _KEYDOWN(100305) AND g.thrusters > 0 THEN g.thrusters = g.thrusters - 1 '  Rt Alt key. Speeds up Guardian ship.
            z3 = TIMER
        END IF

        IF ABS(z2 - TIMER) > g.thrusters / 100 THEN ' z2 is delay for Guardian movement cycle. Note: Division needed because computer math can't add decimal numbers correctly.
            IF v.kb_access = 0 THEN
                IF _KEYDOWN(18432) OR _KEYDOWN(19200) OR _KEYDOWN(19712) OR _KEYDOWN(20480) OR mouse_event2 = -1 THEN ' Arrow keys.
                    v.kb_access = -1 ' Routes code to guardian move. When zero, Guardian move gets bypassed.
                    z1 = TIMER ' Delay timer for key lag effect in Guardian move routine.
                END IF
            END IF
        END IF

        IF v.kb_access = -1 THEN ' Guardian move routine.-----------------------------------------------------------------------------------------------------------------------------
            IF ABS(z1 - TIMER) > .05 THEN ' z1 is a key lag time delay to allow guardian to press two keys together within a reasonable amount of time.
                DO ' Faux loop added to throw out illegal key combos like up + down.
                    IF mouse_event2 = 0 THEN ' Bypass this keyboard routine if the right mouse button is in use.
                        IF mouse_event1 = 0 THEN g.m_x = 0: g.m_y = 0 ' Variables to control length and direction of Guardian movement. Must be reset to zero each cycle so diagonal moves are possible.

                        IF g.m_x = 0 AND g.m_y = 0 THEN
                            ' Eliminate illegal combos.
                            IF _KEYDOWN(18432) AND _KEYDOWN(20480) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Up + down
                            IF _KEYDOWN(19712) AND _KEYDOWN(19200) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Left + right.

                            ' IF female THEN STOP AND GET #1, directions. Keys that control movement.
                            IF _KEYDOWN(18432) THEN ' Up-arrow.
                                g.m_y = -1 ' To move 1-row up.
                            END IF
                            IF _KEYDOWN(19712) THEN ' Right-arrow.
                                g.m_x = 1 ' To move 1-column right.
                            END IF
                            IF _KEYDOWN(20480) THEN ' Down-arrow.
                                g.m_y = 1 ' To move 1-row down.
                            END IF
                            IF _KEYDOWN(19200) THEN ' Left-arrow.
                                g.m_x = -1 ' To move 1-column left.
                            END IF

                            IF g.m_x AND g.m_y THEN ' Double key hold. Routine to cancel keys when double hold is lifted. Compensates for both keys not being released at exactly the same time.
                                combo = -1 ' Double key hold in progress.
                            ELSE
                                IF combo THEN combo = 0: v.kb_access = 0: EXIT DO ' Double key hold was just removed, so skip Guardian move and exit.
                            END IF
                        END IF
                    END IF

                    ' Move Guardian. *****************************************************************************************************
                    IF g.y + g.m_y > v.top AND g.y + g.m_y <= v.bottom AND g.x + 2 * g.m_x > v.left AND g.x + 2 * g.m_x < v.right THEN
                        LOCATE g.y, g.x
                        PRINT " ";
                        g.y = g.y + g.m_y: g.x = g.x + 2 * g.m_x
                        IF SCREEN(g.y, g.x) <> 32 AND SCREEN(g.y, g.x) <> g.m_asc THEN ' Guardian abducted by bad move. Ignore if you run into your own missile.
                            CALL guardian_abduction: EXIT SUB
                        ELSE
                            LOCATE g.y, g.x
                            PRINT g.flagship;
                        END IF
                    ELSE
                        BEEP ' Hit boundary.
                    END IF
                    v.kb_access = 0 ' Guardian move completed. Returns control to general keyboard next cycle.
                    z2 = TIMER ' Timer for moving. Lag regulated by "g.thrusters" variable.
                    EXIT DO ' ************************************************************************************************************
                LOOP
            END IF '
        ELSE ' If you want additional key routines, put them here...------------------------------------------------------------------------------------------------------------------
            CALL mouse(mouse_event1, mouse_event2)
            ky$ = INKEY$
            IF mouse_event1 = -1 THEN ky$ = CHR$(9)
            IF LEN(ky$) = 1 THEN ' For demo, exclude keys that start with chr$(0). Note without this arrow keys would still register here.
                SELECT CASE ky$
                    CASE CHR$(9) ' Tab key. Bug note: Space bar will not register with arrow up + arrow v.left.
                        IF ABS(z8 - TIMER) > .25 THEN
                            SELECT CASE g.m_launcher
                                CASE ""
                                    IF mouse_event1 = -1 THEN g.m_launcher = "mouse" ELSE g.m_launcher = "keyboard" ' Initiate.
                                CASE "keyboard"
                                    IF mouse_event1 = -1 THEN
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "mouse" ' Switch.
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                                CASE "mouse"
                                    IF mouse_event1 = 0 THEN ' Tab key was used.
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "keyboard" ' Switch
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                            END SELECT
                            IF LEN(ky$) THEN
                                g.m_status = -1 ' -1 indicates missile just fired.
                                IF mouse_event1 = 0 THEN v.mouse_or_key_move = 0: ' Key pressed missile fire clears any previous mouse button missile fire.
                            END IF
                            mouse_event1 = 0 ' Completes left mouse missile firing cycle.
                            z8 = TIMER ' Missile firing delay reset.
                        END IF
                    CASE CHR$(27) ' Esc
                        _DELAY 1: SYSTEM
                    CASE ELSE
                        REM PRINT "You pressed key: "; ky$;
                END SELECT
            END IF
        END IF '----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    LOOP
END SUB

SUB comm
    STATIC middle% ' Local variable.
    IF middle% = 0 THEN middle% = v.left + (v.right - v.left) \ 2
    msg$ = "  Thrusters = " + LTRIM$(STR$(5 * (20 - g.thrusters))) + "%  Fired = " + LTRIM$(STR$(g.m_fired)) + " Alien ships = " + LTRIM$(STR$(a.count)) + " Level = " + LTRIM$(STR$(v.level)) + " Guardians: "
    LOCATE 1, v.left + middle% - ((LEN(msg$) + 8) \ 2): PRINT msg$;
    SELECT CASE g.num
        CASE 3
            PRINT g.icon; " "; g.icon; " "; g.icon;
        CASE 2
            PRINT g.icon; " "; g.icon; " ";: COLOR 8, 0: PRINT g.icon;: COLOR 7, 0
        CASE 1
            PRINT g.icon; " ";: COLOR 8, 0: PRINT g.icon; " "; g.icon;: COLOR 7, 0
        CASE 0
            COLOR 8, 0: PRINT g.icon; " "; g.icon; " "; g.icon; " ";: COLOR 7, 0
    END SELECT
    PRINT "   "; ' Cut off any former printing caused by length changes in comm report like when numbers change from double to single digits.
END SUB

SUB Guardian_missiles
    STATIC z4

    DIM direction AS INTEGER ' 8 possible directions. local variable.

    IF ABS(z4 - TIMER) > .03 THEN
        z4 = TIMER
        IF v.mouse_or_key_move = 0 THEN ' GAME OPTION: Remove this first IF/THEN to make it necessary to move  Guardian in a direction to fire in that direction.
            IF g.m_y <> 0 OR g.m_x <> 0 THEN g.diry = g.m_y: g.dirx = g.m_x ' Initiate by setting row and column missile direction to last column and row movement direction of Guardian location.
        END IF
        IF g.m_status = -1 THEN
            IF g.diry = -1 AND g.dirx = 0 THEN
                direction = 1
            ELSEIF g.diry = -1 AND g.dirx = 1 THEN
                direction = 2
            ELSEIF g.diry = 0 AND g.dirx = 1 THEN
                direction = 3
            ELSEIF g.diry = 1 AND g.dirx = 1 THEN
                direction = 4
            ELSEIF g.diry = 1 AND g.dirx = 0 THEN
                direction = 5
            ELSEIF g.diry = 1 AND g.dirx = -1 THEN
                direction = 6
            ELSEIF g.diry = 0 AND g.dirx = -1 THEN
                direction = 7
            ELSEIF g.diry = -1 AND g.dirx = -1 THEN
                direction = 8
            END IF

            IF m_n(direction) + 1 <= g.m_max THEN ' Don't fire if out of missiles.
                IF g.y > v.top + 1 AND g.y < v.bottom AND g.x > v.left + 1 AND g.x < v.right - 1 THEN ' Don't fire if at a border.
                    m_n(direction) = m_n(direction) + 1
                    m_y(m_n(direction), direction) = g.y
                    m_x(m_n(direction), direction) = g.x
                    g.m_fired = g.m_fired + 1
                    SOUND 900, .1
                END IF
            END IF
            g.m_status = 1 ' Code will now execute missile launch.
        END IF

        IF g.m_status = 1 THEN
            FOR g.m_d = 1 TO 8 ' Check all directions.
                IF m_n(g.m_d) > 0 THEN
                    j = m_n(g.m_d)
                    FOR g.m_n = 1 TO j
                        IF m_y(g.m_n, g.m_d) = g.y AND m_x(g.m_n, g.m_d) = g.x THEN
                        ELSE
                            LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d): PRINT " ";
                        END IF
                        SELECT CASE g.m_d ' Missile direction.
                            CASE 1
                                IF m_y(g.m_n, g.m_d) - 1 > v.top THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR ' Missile off screen.
                                END IF
                            CASE 2
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 3
                                IF m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 4
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 5
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 6
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 7
                                IF m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 8
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                        END SELECT
                    NEXT
                    IF a.count = 0 THEN EXIT SUB
                END IF
            NEXT
            IF g.m_fired <= 0 THEN g.m_fired = 0: g.m_status = 0 ' All missiles cleared.
        END IF
    END IF
END SUB

SUB missile_check (k)
    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d) ' Place cursor at current missile position.
    k = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)) ' Read the screen.
    id_by_color = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d), 1)
    IF k <> 32 AND k <> g.m_asc AND k <> ASC(g.flagship) THEN ' If screen space is occupied by alien ship then explosion.
        CALL remove_missile
        CALL explosion
        CALL remove_ship(id_by_color)
        k = -1 ' A flag to exit the FOR/NEXT loop upon return.
    ELSE
        COLOR 14: PRINT CHR$(g.m_asc);: COLOR 7 ' Print missile on the screen. Missile advances here. Only place a missile is printed to the screen.
    END IF
END SUB

SUB remove_missile
    m_n(g.m_d) = m_n(g.m_d) - 1 ' Counter. Reduce the number of missiles fired, in a specific direction, by 1.
    FOR k = g.m_n TO m_n(g.m_d) ' Re-stack arrays.
        m_y(k, g.m_d) = m_y(k + 1, g.m_d)
        m_x(k, g.m_d) = m_x(k + 1, g.m_d)
    NEXT
    m_y(k, g.m_d) = 0: m_x(k, g.m_d) = 0 'Zero out location variables of the missile removed. A zero removes unnecessary loop checking for other routines.
    g.m_fired = g.m_fired - 1 ' Count of number of missiles fired is reduce by 1.
END SUB

SUB mask_missiles
    FOR i = 1 TO g.m_max
        FOR j = 1 TO 8
            IF m_y(i, j) <> 0 THEN LOCATE m_y(i, j), m_x(i, j): PRINT " "; ' Mask missile.
    NEXT j, i
    m_status = 0: m_fired = 0: m_n = 0: m_d = 0: m_y = 0: m_x = 0
    REDIM m_n(g.m_max), m_x(g.m_max, 8), m_y(g.m_max, 8)
END SUB

SUB explosion
    soundfile% = 1 ' Local variable turns sound on.
    b_y1 = CSRLIN: b_x1 = POS(0)
    VIEW PRINT v.top TO v.bottom ' Needed to set print error parameters.
    h = 0
    ON ERROR GOTO offscreen
    DO
        IF h = 1 THEN burst$ = " " ELSE burst$ = CHR$(249)
        IF v.intro THEN burst$ = "" ' No fireworks, just flash and sound.
        h = h + 1
        ' Flash
        IF eflag THEN
            IF ABS(z7 - TIMER) > .1 THEN
                eflag = 0
                PALETTE 0, 0
                z7 = TIMER
            ELSE
                j = -j * -1
                IF j = 0 THEN
                    PALETTE 0, 63
                    _DELAY .05
                ELSE
                    PALETTE 0, 0
                    _DELAY .05
                END IF
            END IF
        END IF
        IF eflag = 0 THEN
            PALETTE 0, 36
            IF soundfile% THEN
                _SNDPLAY v.snd1
                _DELAY .05
                PALETTE 0, 0
                _SNDPLAY v.snd2
            ELSE
                _DELAY .075
            END IF
            eflag = -1
            z7 = TIMER
        END IF
        PALETTE 0, 0 ' End flash.

        FOR i = 1 TO 5
            SELECT CASE i
                CASE 1
                    COLOR 15
                    LOCATE b_y1, b_x1: PRINT burst$;
                    _DELAY .1
                CASE 2
                    IF burst$ = CHR$(249) THEN burst$ = CHR$(250)
                    COLOR 14, 0
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .1
                CASE 3
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                CASE 4
                    COLOR 4
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .3
                CASE 5
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
            END SELECT
        NEXT
        IF h = 1 THEN h = 0: EXIT DO
    LOOP
    VIEW PRINT
    ON ERROR GOTO 0
    COLOR 7
    IF a.count = 1 THEN CALL mask_missiles ' Clear unexploded missiles off the screen when changing levels. Game Option: Remove variable to mask missiles after every explosion.
    LOCATE b_y1, b_x1
END SUB

SUB remove_ship (id_by_color) ' Removes ship by color identification.
    i = a.itr
    ON ERROR GOTO offscreen
    LOCATE a_mask_y(id_by_color), a_mask_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    LOCATE a_y(id_by_color), a_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    ON ERROR GOTO 0
    a.itr = id_by_color: CALL a_erase
    a_ran(id_by_color) = -1 ' Denotes alien ship was destroyed and removed from battle.
    a.itr = i
    a.count = a.count - 1 ' a.count = 0  will cause program to exit any unneeded subs/loops after the game level sub is completed.
    LOCATE g.y, g.x: PRINT g.flagship; ' Re-display Guardian.
    ' Restore any alien ships in blast zone.
    FOR i = 1 TO a.max
        IF a_ran(i) > 0 THEN
            IF a_y(i) > v.top AND a_x(i) > v.left AND a_x(i) + LEN(a.ship) < v.right THEN LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;
        END IF
    NEXT
    COLOR 7
    CALL comm
END SUB

SUB alien_move
    STATIC z5
    y_restore = CSRLIN: x_restore = POS(0) ' Restore column and row upon exit.
    IF ABS(z5 - TIMER) > a.cycle_delay OR v.intro THEN ' z5 is a time delay for alien space ship maneuvers. It can be altered in the "game" subroutine.
        IF v.intro = 0 THEN h_alien_nom = INT(RND * a.max) + 1 ELSE h_alien_nom = 15
        FOR h = 1 TO h_alien_nom ' Local counting variable for alien number of moves in this cycle.
            a.itr = a.itr + 1: IF a.itr > a.max THEN a.itr = 1 ' Needed to offset the EXIT DO hover event, which on exit does not affect the a.itr variable.
            IF a_ran(a.itr) <> -1 THEN ' This is how a destroyed ship is bypassed. -1 is a destroyed alien ship. Code moves to end of DO:LOOP.
                IF a_inertia(a.itr) = 0 THEN ' Determine how many moves in one direction.
                    a_inertia(a.itr) = INT(RND * (v.bottom - v.top) / 2) + 1 ' How many moves to go in any one direction.
                    a_ran(a.itr) = INT(RND * 8) + 1 ' Choose 1 of 8 possible directions.

                    IF a_ran(a.itr) = a_olda_ran(a.itr) OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 1 OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 5 THEN
                        EXIT FOR ' Just hover if direction was not changed on existing alien space ship or if a new alien space ship is entering from the sides and up or down was generated.
                    END IF

                    SELECT CASE a_ran(a.itr) ' Get changes in column and row coordinates.
                        CASE 1: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0 '  Up.
                        CASE 2: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 2 '  Up and right.
                        CASE 3: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 2 '   Right.
                        CASE 4: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 2 '   Down and right.
                        CASE 5: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0 '   Down.
                        CASE 6: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -2 '  Down and left.
                        CASE 7: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -2 '  Left.
                        CASE 8: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -2 ' Up and left.
                    END SELECT

                    IF a_y(a.itr) = 0 AND a_x(a.itr) = 0 AND a_ran(a.itr) <> -1 THEN ' New alien space ship enters the screen.
                        i = RND * (v.bottom - v.top) \ 4
                        a_y(a.itr) = (v.bottom - v.top) \ 4 + i + v.top
                        IF a_ran(a.itr) < 5 THEN ' Determine side of entry from initial direction.
                            IF SCREEN(a_y(a.itr), v.left + LEN(a.ship)) = 32 THEN
                                a_x(a.itr) = v.left + 1 ' Enter from the left side and go right.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        ELSE
                            IF SCREEN(a_y(a.itr), v.right - LEN(a.ship) + 1) = 32 THEN
                                a_x(a.itr) = v.right - LEN(a.ship) ' Enter from the right side and go left.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                    a_olda_ran(a.itr) = a_ran(a.itr) ' Remember last direction. Another line uses this to disallow any RND that chooses the same direction twice.
                ELSE
                    a_inertia(a.itr) = a_inertia(a.itr) - 1 ' Count down the number of moves in any one direction. When zero, switch direction.
                END IF

                FOR i = 1 TO a.max
                    IF i <> a.itr AND a_y(i) <> 0 THEN
                        IF a_y(a.itr) + a_y_loc(a.itr) = a_y(i) THEN
                            IF a_x(a.itr) + a_x_loc(a.itr) + LEN(a.ship) > a_x(i) AND a_x(a.itr) + a_x_loc(a.itr) < a_x(i) + LEN(a.ship) THEN
                                collide = 1
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                NEXT
                IF collide = 1 THEN
                    j = a_y(a.itr): k = a_x(a.itr)
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_inertia(a.itr) = 0
                    collide = 0 ' Collision detection off. Collision was detected and avoided.
                ELSE
                    j = a_y(a.itr) + a_y_loc(a.itr): k = a_x(a.itr) + a_x_loc(a.itr)
                END IF

                IF j <= v.top OR k <= v.left OR k + LEN(a.ship) > v.right THEN ' Alien ship out of range, off screen.
                    a_inertia(a.itr) = 0 ' These two lines keep the out of range ship(s) reasonably nearby.
                    IF j > v.top - 4 AND k < v.right + 3 AND k > v.left - 4 THEN a_y(a.itr) = j: a_x(a.itr) = k
                    IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN
                        LOCATE a_mask_y(a.itr), a_mask_x(a.itr)
                        PRINT SPACE$(LEN(a.ship)); ' Mask old position here because the show part of the mask-and-show routine cannot be used when out of range.
                    END IF
                    IF a_offscrn(a.itr) > 25 THEN a_y(a.itr) = 0: a_x(a.itr) = 0: a_inertia(a.itr) = 0: a_offscrn(a.itr) = 0
                    a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_offscrn(a.itr) = a_offscrn(a.itr) + 1
                ELSE
                    ' Check for v.bottom collision and reverse course if detected.
                    COLOR a.itr
                    IF j >= v.bottom THEN
                        a_y_loc(a.itr) = -a_y_loc(a.itr): a_x_loc(a.itr) = -a_x_loc(a.itr)
                    ELSE
                        a_y(a.itr) = j: a_x(a.itr) = k ' Next move coordinates.
                        ii = 0
                        kk = ASC(g.flagship)
                        FOR i = 0 TO LEN(a.ship) - 1 ' Check area through width of ship. Remember all or parts of ship are still present on screen.
                            SELECT CASE SCREEN(j, k + i)
                                CASE kk
                                    ii = 1 ' Indicates contact with flagship and evokes call abduction routine a few lines down.
                                    EXIT FOR
                                CASE g.m_asc
                                    ii = 2 ' Indicates ship into missile collision.
                                    EXIT FOR 'Okay to exit as a missile and Guardian craft cannot be present in the same location.
                            END SELECT
                        NEXT
                        IF ii <> 2 THEN ' This will make a move unless a ship into missile event would occur.
                            '--------------------------------------------Move alien ship-------------------------------------------------
                            IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN LOCATE a_mask_y(a.itr), a_mask_x(a.itr): PRINT SPACE$(LEN(a.ship));
                            LOCATE j, k: PRINT a.ship;
                            a_mask_y(a.itr) = j: a_mask_x(a.itr) = k ' Remember these coordinates to erase alien space ship on next loop.
                            '------------------------------------------------------------------------------------------------------------
                        END IF
                        IF ii = 1 THEN CALL guardian_abduction: EXIT FOR ' Exit loop.
                        j = j - g.y: k = k - g.x + LEN(a.ship) / 2
                        IF ABS(j) < 3 AND ABS(k) < 8 THEN CALL a_hunt(j, k)
                    END IF
                    COLOR 7
                END IF
            END IF ' a_ran(a.itr) > -1 exit point.
            IF a.itr = a.max THEN a.itr = 0: EXIT FOR ' Finished loop. Keep this outside the IF/THEN statement.
        NEXT h
        z5 = TIMER
        LOCATE y_restore, x_restore ' Restore entry column and row positions.
    END IF ' End time event.
END SUB

SUB guardian_abduction

    IF v.intro = 0 THEN
        CALL mask_missiles ' Clear unexploded missiles off the screen after alien abduction.

        msg$ = "[GUARDIAN ABDUCTED]"
        SOUND 500, .4: SOUND 1000, .3: SOUND 1500, .2
        PCOPY 0, 1
        SCREEN 0, 0, 1, 1
        LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
        COLOR 14 + 16: PRINT msg$;
        _DELAY 2
        SCREEN 0, 0, 0, 0
    END IF

    DO
        j = 0
        FOR i = 1 TO a.max
            IF a_y(i) >= v.top AND a_x(i) >= v.left AND a_x(i) <= v.right - LEN(a.ship) THEN
                SOUND 500, .04
                LOCATE a_y(i), a_x(i): PRINT SPACE$(LEN(a.ship));
                IF a_y(i) <> v.top THEN a_y(i) = a_y(i) - 1: LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;: _DELAY .02: j = 1
            END IF
        NEXT
    LOOP WHILE j
    COLOR 7
    IF v.intro THEN EXIT SUB
    g.num = g.num - 1
    CALL comm
    _DELAY .75
    v.redo_level = -1
    a.count = 0 ' Zero to exit subs back to main game loop.
END SUB

SUB game_level ' Evaluates both alien defeated on a level and Guardian abduction.
    _DELAY 1: VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT ' Slight delay then clear playng screen.
    msg$ = "[LEVEL " + LTRIM$(STR$(v.level)) + "]"
    LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16: PRINT msg$;
    j = 0: k = 0
    DO
        IF ABS(z0 - TIMER) > .15 THEN
            IF j = 0 THEN j = 1: SOUND 750, .3: k = k + 1 ELSE j = 0: _DELAY .1
            z0 = TIMER
        END IF
    LOOP UNTIL k = 6
    COLOR 7
    LOCATE , v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    PRINT msg$;
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    _DELAY .75
    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Reset column and row for Guardian craft.
    LOCATE g.y, g.x
    COLOR 15 + 16: PRINT g.flagship;
    _DELAY 1.5
    COLOR 7: LOCATE g.y, g.x: PRINT g.flagship;
    v.level_up = -1
END SUB

SUB level_up
    v.level = v.level + 1
END SUB

SUB a_erase
    a_y(a.itr) = 0: a_x(a.itr) = 0: a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_inertia(a.itr) = 0: a_ran(a.itr) = 0: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_olda_ran(a.itr) = 0
END SUB

SUB mouse (mouse_event1, mouse_event2)
    STATIC z9, z10, lb, lb_status ' lb_status keeps track of press/release.
    WHILE _MOUSEINPUT
        IF ABS(z10 - TIMER) > .1 THEN
            z10 = TIMER
            IF _MOUSEWHEEL > 0 THEN ' Down
                IF g.thrusters < 20 THEN g.thrusters = g.thrusters + 1
            ELSEIF _MOUSEWHEEL < 0 THEN ' Up
                IF g.thrusters > 0 THEN g.thrusters = g.thrusters - 1
            END IF
        END IF
    WEND
    mx = _MOUSEX
    my = _MOUSEY
    lb = _MOUSEBUTTON(1)
    rb = _MOUSEBUTTON(2)

    IF lb THEN
        IF lb_status = 0 THEN
            IF ABS(z9 - TIMER) > .33 THEN
                z9 = TIMER
                mouse_event1 = -1: lb_status = -1: ' Left button down
            END IF
        END IF
    ELSE
        IF lb_status THEN lb_status = 0 ' Left button was released.
    END IF

    IF v.mouse_get_screen THEN
        IF lb_status = -1 THEN
            x$ = CHR$(SCREEN(my, mx))
            SELECT CASE v.mouse_get_screen
                CASE 1 ' Skip intro.
                    IF x$ = "S" THEN v.intro = 999: SOUND 1000, .3: EXIT SUB
                CASE 2 ' Replay
                    IF x$ = "Y" THEN v.play = 999: SOUND 1000, .1: EXIT SUB
                    IF x$ = "N" THEN v.play = -999: SOUND 1000, .1: EXIT SUB
            END SELECT
        END IF
    END IF
    cx = g.x: cy = g.y ' Angular calculations provided by bplus from the QB64 Phoenix Forum.
    stepX = ABS(cx - mx): stepY = ABS(cy - my)

    dAng = INT(_R2D(_ATAN2(my - cy, mx - cx)) + .5)
    IF dAng < 0 THEN dAng = dAng + 360

    IF dAng <= 90 THEN
        startA = 0: endA = dAng: ra = dAng
    ELSEIF dAng <= 180 THEN
        startA = dAng: endA = 180: ra = 90 - (dAng - 90)
    ELSEIF dAng <= 270 THEN
        startA = 180: endA = dAng: ra = dAng - 180
    ELSEIF dAng <= 360 THEN
        startA = dAng: endA = 360: ra = 90 - (dAng - 270)
    END IF

    m_y = 0: m_x = 0
    IF ra <= 90 AND ra >= 50 THEN
        IF my > g.y THEN
            x$ = "down": m_y = 1: m_x = 0 ' Down.
        ELSE
            x$ = "up": m_y = -1: m_x = 0 ' Up.
        END IF
    ELSEIF ra < 50 AND ra >= 15 THEN
        IF mx > g.x AND my > g.y THEN
            x$ = "down right": m_y = 1: m_x = 2 ' Down and right.
        ELSEIF mx < g.x AND my > g.y THEN
            x$ = "down left": m_y = 1: m_x = -2 ' Down an left.
        ELSEIF mx > g.x AND my < g.y THEN
            x$ = "up right": m_y = -1: m_x = 2 ' Up and right.
        ELSEIF mx < g.x AND my < g.y THEN
            x$ = "up left": m_y = -1: m_x = -2 ' Up and left.
        END IF
    ELSEIF ra < 15 AND ra >= 0 THEN
        IF mx > g.x THEN
            x$ = "right": m_y = 0: m_x = 2 ' Right
        ELSE
            x$ = "left": m_y = 0: m_x = -2 ' Left
        END IF
    END IF

    IF rb AND mouse_event2 = 0 THEN
        mouse_event2 = -1
        g.m_y = m_y: g.m_x = m_x / 2
        v.mouse_or_key_move = 1 ' Right mouse button to move.
    ELSE
        IF mouse_event2 THEN mouse_event2 = 0
    END IF

    IF mouse_event1 THEN
        g.diry = m_y: g.dirx = m_x / 2
        v.mouse_or_key_move = -1
    END IF
END SUB

SUB game_over
    v.mouse_get_screen = 2 ' Allows mouse to select replay options.
    _DELAY .5
    CALL comm
    IF g.num THEN SOUND 1000, .75: SOUND 500, .75: SOUND 1000, 1.5 ELSE SOUND 1000, .75: SOUND 700, .75: SOUND 500, 1.5
    _DELAY .5
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    IF g.num THEN msg$ = "[GUARDIAN WINS]" ELSE msg$ = "[Game Over]"
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16
    PRINT msg$;
    _DELAY 2
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14: PRINT msg$;
    msg$ = "Replay? Y/N"
    _DELAY 1
    LOCATE (v.bottom - v.top) \ 2 + v.top + 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 8
    PRINT msg$;
    COLOR 7
    DO
        _LIMIT 30
        CALL mouse(mouse_event1, mouse_event2)
        ky$ = INKEY$
        IF LEN(ky$) OR ABS(v.play) = 999 THEN
            IF v.play = 999 OR UCASE$(ky$) = "Y" OR ky$ = CHR$(13) THEN v.play = 1: VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT: EXIT DO
            IF v.play = -999 OR UCASE$(ky$) = "N" OR ky$ = CHR$(27) THEN
                msg$ = "   [Bye Bye]   "
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14 + 16: PRINT msg$;
                _DELAY 1
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14: PRINT msg$;
                _DELAY .5
                SYSTEM
            END IF
        END IF
    LOOP
END SUB

SUB a_hunt (j, k)
    LOCATE 3, 1
    a_inertia(a.itr) = 4
    h = j / ABS(j): i = k / ABS(k)
    SELECT CASE h
        CASE IS < 0 ' Ship above Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 1
                CASE 0 ' Ship same column as Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -1
            END SELECT
        CASE 0 ' Ship same row as Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 1
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -1
            END SELECT
        CASE IS > 0 ' Ship below Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 1
                CASE 0 ' Ship same column as Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -1
            END SELECT
    END SELECT
END SUB

Pete
Reply
#17
Added 3 "Difficulty Levels" to the last version.

GUARDIAN V 1.2
Code: (Select All)
DEFINT H-K
$RESIZE:ON
_RESIZE OFF
RANDOMIZE TIMER
' Note: timer is not adjusted for stroke of midnight event, so don't stay up late playing this.
REM Main
f1 = 22 ' Sets font size to 22 and calculates the max screen height and width for your desktop.
h = (_DESKTOPHEIGHT - 60) \ f1
w = _DESKTOPWIDTH \ (f1 / 1.66)
WIDTH w, h
_SCREENMOVE 0, 0
fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\lucon.ttf" 'Find Windows Folder Path.
font& = _LOADFONT(fontpath$, f1, "monospace")
_FONT font&
_DELAY .25
_RESIZE ON , _SMOOTH ' Allows resizing. Note: this is for slight adjustments. As of this version there is no compensatory function to change the font size during screen size changes.

TYPE gen_var
    intro AS INTEGER ' Runs a protion of the alien subroutine as part of the intro.
    nol AS INTEGER ' Number of game levels.
    redo_level AS INTEGER ' Alien abdution results in redo level.
    level AS INTEGER ' Current game level.
    level_up AS INTEGER ' Routes code to next game level.
    top AS INTEGER ' Top boundary. (Changeable).
    bottom AS INTEGER ' Bottom boundary. (Changeable).
    left AS INTEGER ' Left boundary. (Changeable).
    right AS INTEGER ' Right boundary. (Changeable).
    kb_access AS INTEGER ' Routes code to either Guardian/Alien or general keyboard routine. -1 Guardian/Alien, 0 keyboard.
    mouse_or_key_move AS INTEGER
    mouse_get_screen AS INTEGER
    play AS INTEGER
    snd1 AS LONG ' Explosion sound effect.
    snd2 AS LONG ' Explosion sound effect.
END TYPE

DIM SHARED v AS gen_var

TYPE guardian
    difficulty AS INTEGER
    num AS INTEGER ' Number of Guardians aka lives.
    diry AS INTEGER ' Guardian row move. +1, 0, -1.
    dirx AS INTEGER ' Guardian column move. +2, 0, -2. Equals vertical pixel movement. 16x8.
    y AS INTEGER ' Guardian coordinates row.
    x AS INTEGER ' Guardian coordinates column.
    thrusters AS INTEGER ' Guardian speed. (User Determined).
    m_max AS INTEGER ' Restricts # of missiles fired in one direction.
    m_status AS INTEGER ' Missile status. -1 when fired, 1 while moving.
    m_fired AS INTEGER ' The number of missile deployed and still active.
    m_n AS INTEGER ' FOR/NEXT counter variable shared by other routines to index through the number of missiles fired in a specific direction.
    m_d AS INTEGER ' Missile direction. (1-8).
    m_y AS INTEGER ' Missile row advancement increment: +1, 0, -1 Note: Missile row and column coordinates are defined in arrays.
    m_x AS INTEGER ' Missile column advancement increment: +2, 0, -2. Equals vertical pixel movement. 16x8.
    m_asc AS INTEGER ' ASCII character representing a fired missile.
    m_launcher AS STRING
    icon AS STRING ' Guardian comm icon. For this edition, it is the same as the flagship: "*"
    flagship AS STRING ' Guardian ascii character.
END TYPE

DIM SHARED g AS guardian

TYPE alien
    max AS INTEGER ' Maximum # of alien ships on screen.
    count AS INTEGER ' Number of alien ships. (Counter).
    itr AS INTEGER ' Iteration array number to cycle through the active alien ships. (Counter).
    cycle_delay AS SINGLE ' Timer cycle controls how fast alien ships move.
    ai AS INTEGER ' Degree of artificial intelligence. (0-2)
    ship AS STRING ' Alien ship ASCII design.
END TYPE

DIM SHARED a AS alien

DO
    GOSUB set_arrays

    SELECT CASE v.play
        CASE 0
            CALL set_up
            CALL comm
            CALL intro
            GOSUB set_arrays: GOSUB zero_variables ' Reset arrays and zero variables after intro.
            CALL level_up
        CASE 1
            CALL set_up
            CALL comm
    END SELECT

    CALL game_level ' Display level.

    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Set initial column and row for Guardian craft.

    CALL game

    IF g.num = 0 OR v.level = v.nol AND v.redo_level = 0 THEN
        CALL game_over ' Determines win or lose.
        v.level = 1 ' Reinitiate if player chooses to replay.
    ELSE
        IF v.redo_level = 0 THEN CALL level_up
    END IF

    GOSUB zero_variables
LOOP

set_arrays:
ii = 15 ' Default max setting for number of alien ships used here to initially dim arrays.
g.m_max = 8 ' * missiles max per direction.
REDIM SHARED a_y(ii), a_x(ii), a_mask_y(ii), a_mask_x(ii), a_inertia(ii) ' Alien movement.
REDIM SHARED a_ran(ii), a_olda_ran(ii), a_y_loc(ii), a_x_loc(ii), a_offscrn(ii) ' Alien movement.
REDIM SHARED m_n(8), m_x(g.m_max, 8), m_y(g.m_max, 8) ' Guardian missiles. 8 represents 8 possible diretions of movement and missile fire.

' Array descriptions and actions.
' a_y() , a_x() Alien ship positions rows and columns.
' a_mask_y(), a_mask_x() Alien ship last position. Masked on next move.
' a_inertia(ii) Number of moves in one direction selected by random for an alien ship.
' a_ran(ii), a_olda_ran(ii) Determines the direction in which the inertia will travel and the prior direction is kept to disallow the same direction twice.
' a_y_loc(ii), a_x_loc(ii) The row and column coordinates of the aliens ships.
' m_n(8), m_x(g.m_max, 8), m_y(g.m_max, 8)  Missile number and Missile index 1 to g.m_max for position. 8 is the fixed number of 8 different directions.
RETURN

zero_variables:
' Zero variables.
g.diry = 0: g.dirx = 0: g.m_status = 0: g.m_fired = 0: g.m_d = 0: g.m_y = 0: g.m_x = 0: v.redo_level = 0: a.max = 0: v.intro = 0
a.count = 0: a.itr = 0: a.cycle_delay = 0: v.level_up = 0: v.mouse_or_key_move = 0: g.m_launcher = ""
RETURN

skipintro: ' ONKEY 15.
v.intro = 999
KEY(15) OFF
RETURN

' Error handler.
offscreen: ' Prevents error if blast particles row and column are off-screen. Effect is a partial blast on side of screen.
IF ERR = 5 THEN er = -1: RESUME NEXT
PRINT "Opps, unexpected error"; ERR
END

SUB intro
    WHILE -1 ' Faux loop.
        v.mouse_get_screen = 1 ' Allows skip intro by mouse selection.
        LOCATE _HEIGHT, _WIDTH - 15
        PRINT "[S]kip Intro"; ' Option to skip the intro using the "S" key with ONKEY statement.
        KEY 15, CHR$(0) + CHR$(31) 'scancode for S
        ON KEY(15) GOSUB skipintro
        KEY(15) ON 'turn ON [S]kip intro event trapping.
        j = (v.bottom - v.top) \ 2 + v.top
        k = (v.right - v.left) \ 2 + v.left
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .33
        SOUND 1000, .3
        msg$ = " GUARDIAN "
        LOCATE j, k - LEN(msg$) \ 2: COLOR 14: PRINT msg$;: COLOR 7
        LOCATE , k
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .66
        msg$ = SPACE$(LEN(msg$))
        LOCATE j, k - LEN(msg$) \ 2: COLOR 7: PRINT msg$;
        LOCATE j, k
        IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE ELSE CALL explosion
        LOCATE j, k: COLOR 15 + 16: PRINT g.flagship;
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .75
        v.intro = -1: a.max = 10
        FOR i = 1 TO 90
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
            SOUND 900, .05
            CALL alien_move
            LOCATE j, k: COLOR 15: PRINT g.flagship;: COLOR 7
            _DELAY .07
        NEXT
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > 1
        LOCATE j, k: PRINT " ";
        CALL guardian_abduction
        a.max = 0
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .5
        EXIT WHILE
    WEND
    LOCATE v.bottom, _WIDTH - 20: PRINT SPACE$(20);

    ' Playing Instructions.
    REDIM msg$(13)
    msg$(1) = CHR$(249) + " " + "Look towards the top of your screen for Guardian ship status."
    msg$(2) = CHR$(249) + " " + "Hold arrow keys up/dn/lt/rt or in combination to move diagonally."
    msg$(3) = CHR$(249) + " " + "Missiles can be fired simultaneously in 8 different directions."
    msg$(4) = CHR$(249) + " " + "Press Tab to fire 1-8 missiles in the direction of movement."
    msg$(5) = CHR$(249) + " " + "Press Rt Ctrl to increase thrust, or Rt Alt to reduce thrust."
    msg$(6) = ""
    msg$(7) = CHR$(249) + " " + "Mouse alternative: "
    msg$(8) = CHR$(249) + " " + "Hold right mouse button to move toward mouse pointer."
    msg$(9) = CHR$(249) + " " + "Click left mouse button to fire 1-8 missiles in direction of mouse pointer."
    msg$(10) = CHR$(249) + " " + "Mouse wheel up for more thrust, wheel down for less."
    msg$(11) = CHR$(249) + " " + "Note: Tab and left mouse button cannot be used simultaneously."
    msg$(12) = ""
    msg$(13) = CHR$(249) + " " + "If you come in contact with an alien ship, your ship and crew get abducted."
    j = 0
    FOR i = 1 TO UBOUND(msg$)
        IF LEN(msg$(i)) > j THEN j = LEN(msg$(i))
    NEXT
    i = ((v.right - v.left) - j) \ 2
    t_mrgn = 6: b_mrgn = _HEIGHT - 4: l_mrgn = i + 1: r_mrgn = v.right - i
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    FOR i = 1 TO b_mrgn - t_mrgn + 3
        LOCATE t_mrgn - 2 + i, l_mrgn - 2
        PRINT CHR$(179);
        LOCATE , r_mrgn + 2: PRINT CHR$(179);
    NEXT
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT CHR$(218);
    LOCATE t_mrgn - 2, r_mrgn + 2: PRINT CHR$(191);
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT CHR$(192);
    LOCATE b_mrgn + 2, r_mrgn + 2: PRINT CHR$(217);
    msg$ = "<Intro>"
    LOCATE t_mrgn - 2, l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    msg$ = "You are the Captain of the Guardian, an elite battle cruiser commissioned to protect our planet against alien invaders. You have 3 lives. Every time you are abducted by an alien ship, you lose a life. If you survive, and wipe out all 3 flights of alien attacks, your mission is completed. You saved Earth!"
    j = r_mrgn - l_mrgn
    LOCATE t_mrgn, l_mrgn
    msg$ = RTRIM$(msg$) + " " ' Simple word parser routine.--------
    DO
        x$ = MID$(msg$, 1, j)
        x$ = MID$(x$, 1, _INSTRREV(x$, " ") - 1)
        msg$ = LTRIM$(MID$(msg$, LEN(x$) + 1))
        LOCATE , l_mrgn: PRINT x$
    LOOP UNTIL msg$ = "" '-----------------------------------------
    PRINT
    FOR i = 1 TO UBOUND(msg$)
        LOCATE , l_mrgn
        IF LEN(msg$(i)) THEN PRINT msg$(i) ELSE PRINT
    NEXT
    PRINT: IF CSRLIN < b_mrgn - 1 THEN PRINT
    msg$ = "Select Player Level: [1] Beginner  [2] Intermediate  [3] Advanced"
    LOCATE , l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    DO
        _LIMIT 10
        CALL mouse(0, 0)
        ky$ = INKEY$
        IF LEN(ky$) OR g.difficulty THEN
            IF ky$ = CHR$(27) THEN SYSTEM
            SELECT CASE ky$
                CASE "1": g.difficulty = 1
                CASE "2": g.difficulty = 2
                CASE "3": g.difficulty = 3
            END SELECT
            IF g.difficulty THEN EXIT DO
        END IF
    LOOP
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
END SUB

SUB skip_intro
    KEY(15) OFF
    v.intro = 0
    BEEP: _DELAY .3: BEEP
    VIEW PRINT v.top TO v.bottom
    COLOR 7
    CLS 2
    VIEW PRINT
    _DELAY .5
END SUB

SUB set_up
    v.top = 3: v.bottom = _HEIGHT: v.left = 1: v.right = _WIDTH ' Boundaries.
    LOCATE v.top - 1, 1: PRINT STRING$(_WIDTH, CHR$(196));
    v.nol = 3
    g.flagship = CHR$(15)
    a.ship = "-<>-"
    g.num = 3 ' 3 Guardian (lives) to start.
    g.thrusters = 10 ' Shows 1/2 thrust at start up on comm.
    g.icon = g.flagship
    g.m_asc = 250: g.diry = -1: ' Initiate missile ASCII character. g.diry = -1 initiates fire upwards if unmoved.
    IF _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") THEN ' Sound effects provided by TheBOB, Bob Seguin, from The QBasic Forum.
        v.snd1 = _SNDOPEN("Thunder1.ogg", "SYNC")
        v.snd2 = _SNDOPEN("Thunder7.ogg", "SYNC")
    END IF
    v.play = -1 ' Skip into on replay.
END SUB

SUB game
    STATIC mouse_event1, mouse_event2
    REM Set mouse_event2 = 0 here to produce constant motion with directional changes.
    'Press single or arrow key combo to move. Rt Ctrl = faster / Rt Alt = slower. Tab to fire missles.
    g.thrusters = 10 ' Guardian movement delay. (1 to 20). Rt Ctrl = faster / Rt Alt = slower.

    SELECT CASE v.level
        CASE 1: a.max = 5 ' Sets number of space ships and initiates attack.
        CASE 2: a.max = 10
        CASE 3: a.max = 15
    END SELECT

    a.count = a.max
    IF a.max < 6 THEN
        SELECT CASE g.difficulty
            CASE 1: a.cycle_delay = .15: a.ai = 0
            CASE 2: a.cycle_delay = .05: a.ai = 1
            CASE 3: a.cycle_delay = 0: a.ai = 2
        END SELECT
    ELSEIF a.max < 11 THEN
        SELECT CASE g.difficulty
            CASE 1: a.cycle_delay = .125: a.ai = 0
            CASE 2: a.cycle_delay = .05: a.ai = 1
            CASE 3: a.cycle_delay = .003: a.ai = 2
        END SELECT
    ELSE
        SELECT CASE g.difficulty
            CASE 1: a.cycle_delay = .1: a.ai = 0
            CASE 2: a.cycle_delay = .03: a.ai = 1
            CASE 3: a.cycle_delay = .001: a.ai = 2
        END SELECT
    END IF

    _KEYCLEAR ' Clear any previous key presses in buffer.
    CALL mouse(0, 0) ' Clear mouse.

    CALL comm

    DO
        _LIMIT 60 ' Display at 60 frames per second.

        CALL comm

        IF g.m_status THEN CALL Guardian_missiles ' g.m_status determines Guardian or alien turn. 1 = Guardian, -1 = alien.

        IF a.count = 0 THEN EXIT SUB ' Moving on to next level.

        CALL alien_move

        ' To change thrusters only when guardian moves, remove the timer and move this routine to: IF v.kb_access = -1 THEN
        IF ABS(z3 - TIMER) > .1 THEN ' z3 is a keypress delay timer for Guardian thrusters. Check every .1 seconds for a speed change.
            IF _KEYDOWN(100307) AND g.thrusters < 20 THEN g.thrusters = g.thrusters + 1 ' Rt Ctrl key. Slows down Guardian ship down.
            IF _KEYDOWN(100305) AND g.thrusters > 0 THEN g.thrusters = g.thrusters - 1 '  Rt Alt key. Speeds up Guardian ship.
            z3 = TIMER
        END IF

        IF ABS(z2 - TIMER) > g.thrusters / 100 THEN ' z2 is delay for Guardian movement cycle. Note: Division needed because computer math can't add decimal numbers correctly.
            IF v.kb_access = 0 THEN
                IF _KEYDOWN(18432) OR _KEYDOWN(19200) OR _KEYDOWN(19712) OR _KEYDOWN(20480) OR mouse_event2 = -1 THEN ' Arrow keys.
                    v.kb_access = -1 ' Routes code to guardian move. When zero, Guardian move gets bypassed.
                    z1 = TIMER ' Delay timer for key lag effect in Guardian move routine.
                END IF
            END IF
        END IF

        IF v.kb_access = -1 THEN ' Guardian move routine.-----------------------------------------------------------------------------------------------------------------------------
            IF ABS(z1 - TIMER) > .05 THEN ' z1 is a key lag time delay to allow guardian to press two keys together within a reasonable amount of time.
                DO ' Faux loop added to throw out illegal key combos like up + down.
                    IF mouse_event2 = 0 THEN ' Bypass this keyboard routine if the right mouse button is in use.
                        IF mouse_event1 = 0 THEN g.m_x = 0: g.m_y = 0 ' Variables to control length and direction of Guardian movement. Must be reset to zero each cycle so diagonal moves are possible.

                        IF g.m_x = 0 AND g.m_y = 0 THEN
                            ' Eliminate illegal combos.
                            IF _KEYDOWN(18432) AND _KEYDOWN(20480) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Up + down
                            IF _KEYDOWN(19712) AND _KEYDOWN(19200) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Left + right.

                            ' IF female THEN STOP AND GET #1, directions. Keys that control movement.
                            IF _KEYDOWN(18432) THEN ' Up-arrow.
                                g.m_y = -1 ' To move 1-row up.
                            END IF
                            IF _KEYDOWN(19712) THEN ' Right-arrow.
                                g.m_x = 1 ' To move 1-column right.
                            END IF
                            IF _KEYDOWN(20480) THEN ' Down-arrow.
                                g.m_y = 1 ' To move 1-row down.
                            END IF
                            IF _KEYDOWN(19200) THEN ' Left-arrow.
                                g.m_x = -1 ' To move 1-column left.
                            END IF

                            IF g.m_x AND g.m_y THEN ' Double key hold. Routine to cancel keys when double hold is lifted. Compensates for both keys not being released at exactly the same time.
                                combo = -1 ' Double key hold in progress.
                            ELSE
                                IF combo THEN combo = 0: v.kb_access = 0: EXIT DO ' Double key hold was just removed, so skip Guardian move and exit.
                            END IF
                        END IF
                    END IF

                    ' Move Guardian. *****************************************************************************************************
                    IF g.y + g.m_y > v.top AND g.y + g.m_y <= v.bottom AND g.x + 2 * g.m_x > v.left AND g.x + 2 * g.m_x < v.right THEN
                        LOCATE g.y, g.x
                        PRINT " ";
                        g.y = g.y + g.m_y: g.x = g.x + 2 * g.m_x
                        IF SCREEN(g.y, g.x) <> 32 AND SCREEN(g.y, g.x) <> g.m_asc THEN ' Guardian abducted by bad move. Ignore if you run into your own missile.
                            CALL guardian_abduction: EXIT SUB
                        ELSE
                            LOCATE g.y, g.x: PRINT g.flagship;
                        END IF
                    ELSE
                        ' Hit boundary.
                    END IF
                    v.kb_access = 0 ' Guardian move completed. Returns control to general keyboard next cycle.
                    z2 = TIMER ' Timer for moving. Lag regulated by "g.thrusters" variable.
                    EXIT DO ' ************************************************************************************************************
                LOOP
            END IF '
        ELSE ' If you want additional key routines, put them here...------------------------------------------------------------------------------------------------------------------
            CALL mouse(mouse_event1, mouse_event2)
            ky$ = INKEY$
            IF mouse_event1 = -1 THEN ky$ = CHR$(9)
            IF LEN(ky$) = 1 THEN ' For demo, exclude keys that start with chr$(0). Note without this arrow keys would still register here.
                SELECT CASE ky$
                    CASE CHR$(9) ' Tab key. Bug note: Space bar will not register with arrow up + arrow v.left.
                        IF ABS(z8 - TIMER) > .25 THEN
                            SELECT CASE g.m_launcher
                                CASE ""
                                    IF mouse_event1 = -1 THEN g.m_launcher = "mouse" ELSE g.m_launcher = "keyboard" ' Initiate.
                                CASE "keyboard"
                                    IF mouse_event1 = -1 THEN
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "mouse" ' Switch.
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                                CASE "mouse"
                                    IF mouse_event1 = 0 THEN ' Tab key was used.
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "keyboard" ' Switch
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                            END SELECT
                            IF LEN(ky$) THEN
                                g.m_status = -1 ' -1 indicates missile just fired.
                                IF mouse_event1 = 0 THEN v.mouse_or_key_move = 0: ' Key pressed missile fire clears any previous mouse button missile fire.
                            END IF
                            mouse_event1 = 0 ' Completes left mouse missile firing cycle.
                            z8 = TIMER ' Missile firing delay reset.
                        END IF
                    CASE CHR$(27) ' Esc
                        _DELAY 1: SYSTEM
                    CASE ELSE
                        REM PRINT "You pressed key: "; ky$;
                END SELECT
            END IF
        END IF '----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    LOOP
END SUB

SUB comm
    STATIC middle% ' Local variable.
    IF middle% = 0 THEN middle% = v.left + (v.right - v.left) \ 2
    msg$ = "  Thrusters = " + LTRIM$(STR$(5 * (20 - g.thrusters))) + "%  Fired = " + LTRIM$(STR$(g.m_fired)) + " Alien ships = " + LTRIM$(STR$(a.count)) + " Level = " + LTRIM$(STR$(v.level)) + "-" + LTRIM$(STR$(g.difficulty)) + " Guardians: "
    LOCATE 1, v.left + middle% - ((LEN(msg$) + 8) \ 2): PRINT msg$;
    SELECT CASE g.num
        CASE 3
            PRINT g.icon; " "; g.icon; " "; g.icon;
        CASE 2
            PRINT g.icon; " "; g.icon; " ";: COLOR 8, 0: PRINT g.icon;: COLOR 7, 0
        CASE 1
            PRINT g.icon; " ";: COLOR 8, 0: PRINT g.icon; " "; g.icon;: COLOR 7, 0
        CASE 0
            COLOR 8, 0: PRINT g.icon; " "; g.icon; " "; g.icon; " ";: COLOR 7, 0
    END SELECT
    PRINT "   "; ' Cut off any former printing caused by length changes in comm report like when numbers change from double to single digits.
END SUB

SUB Guardian_missiles
    STATIC z4

    DIM direction AS INTEGER ' 8 possible directions. local variable.

    IF ABS(z4 - TIMER) > .03 THEN
        z4 = TIMER
        IF v.mouse_or_key_move = 0 THEN ' GAME OPTION: Remove this first IF/THEN to make it necessary to move  Guardian in a direction to fire in that direction.
            IF g.m_y <> 0 OR g.m_x <> 0 THEN g.diry = g.m_y: g.dirx = g.m_x ' Initiate by setting row and column missile direction to last column and row movement direction of Guardian location.
        END IF
        IF g.m_status = -1 THEN
            IF g.diry = -1 AND g.dirx = 0 THEN
                direction = 1
            ELSEIF g.diry = -1 AND g.dirx = 1 THEN
                direction = 2
            ELSEIF g.diry = 0 AND g.dirx = 1 THEN
                direction = 3
            ELSEIF g.diry = 1 AND g.dirx = 1 THEN
                direction = 4
            ELSEIF g.diry = 1 AND g.dirx = 0 THEN
                direction = 5
            ELSEIF g.diry = 1 AND g.dirx = -1 THEN
                direction = 6
            ELSEIF g.diry = 0 AND g.dirx = -1 THEN
                direction = 7
            ELSEIF g.diry = -1 AND g.dirx = -1 THEN
                direction = 8
            END IF

            IF m_n(direction) + 1 <= g.m_max THEN ' Don't fire if out of missiles.
                IF g.y > v.top + 1 AND g.y < v.bottom AND g.x > v.left + 1 AND g.x < v.right - 1 THEN ' Don't fire if at a border.
                    m_n(direction) = m_n(direction) + 1
                    m_y(m_n(direction), direction) = g.y
                    m_x(m_n(direction), direction) = g.x
                    g.m_fired = g.m_fired + 1
                    SOUND 900, .1
                END IF
            END IF
            g.m_status = 1 ' Code will now execute missile launch.
        END IF

        IF g.m_status = 1 THEN
            FOR g.m_d = 1 TO 8 ' Check all directions.
                IF m_n(g.m_d) > 0 THEN
                    j = m_n(g.m_d)
                    FOR g.m_n = 1 TO j
                        IF m_y(g.m_n, g.m_d) = g.y AND m_x(g.m_n, g.m_d) = g.x THEN
                        ELSE
                            LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d): PRINT " ";
                        END IF
                        SELECT CASE g.m_d ' Missile direction.
                            CASE 1
                                IF m_y(g.m_n, g.m_d) - 1 > v.top THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR ' Missile off screen.
                                END IF
                            CASE 2
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 3
                                IF m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 4
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 5
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 6
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 7
                                IF m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 8
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                        END SELECT
                    NEXT
                    IF a.count = 0 THEN EXIT SUB
                END IF
            NEXT
            IF g.m_fired <= 0 THEN g.m_fired = 0: g.m_status = 0 ' All missiles cleared.
        END IF
    END IF
END SUB

SUB missile_check (k)
    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d) ' Place cursor at current missile position.
    k = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)) ' Read the screen.
    id_by_color = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d), 1)
    IF k <> 32 AND k <> g.m_asc AND k <> ASC(g.flagship) THEN ' If screen space is occupied by alien ship then explosion.
        CALL remove_missile
        CALL explosion
        REM Game Option - IF SCREEN(g.y, g.x) <> ASC(g.flagship) THEN BEEP: BEEP: BEEP
        CALL remove_ship(id_by_color)
        k = -1 ' A flag to exit the FOR/NEXT loop upon return.
    ELSE
        COLOR 14: PRINT CHR$(g.m_asc);: COLOR 7 ' Print missile on the screen. Missile advances here. Only place a missile is printed to the screen.
    END IF
END SUB

SUB remove_missile
    m_n(g.m_d) = m_n(g.m_d) - 1 ' Counter. Reduce the number of missiles fired, in a specific direction, by 1.
    FOR k = g.m_n TO m_n(g.m_d) ' Re-stack arrays.
        m_y(k, g.m_d) = m_y(k + 1, g.m_d)
        m_x(k, g.m_d) = m_x(k + 1, g.m_d)
    NEXT
    m_y(k, g.m_d) = 0: m_x(k, g.m_d) = 0 'Zero out location variables of the missile removed. A zero removes unnecessary loop checking for other routines.
    g.m_fired = g.m_fired - 1 ' Count of number of missiles fired is reduce by 1.
END SUB

SUB mask_missiles
    FOR i = 1 TO g.m_max
        FOR j = 1 TO 8
            IF m_y(i, j) <> 0 THEN LOCATE m_y(i, j), m_x(i, j): PRINT " "; ' Mask missile.
    NEXT j, i
    m_status = 0: m_fired = 0: m_n = 0: m_d = 0: m_y = 0: m_x = 0
    REDIM m_n(8), m_x(g.m_max, 8), m_y(g.m_max, 8)
END SUB

SUB explosion
    soundfile% = 1 ' Local variable turns sound on.
    b_y1 = CSRLIN: b_x1 = POS(0)
    VIEW PRINT v.top TO v.bottom ' Needed to set print error parameters.
    h = 0
    ON ERROR GOTO offscreen
    DO
        IF h = 1 THEN burst$ = " " ELSE burst$ = CHR$(249)
        IF v.intro THEN burst$ = "" ' No fireworks, just flash and sound.
        h = h + 1
        ' Flash
        IF eflag THEN
            IF ABS(z7 - TIMER) > .1 THEN
                eflag = 0
                PALETTE 0, 0
                z7 = TIMER
            ELSE
                j = -j * -1
                IF j = 0 THEN
                    PALETTE 0, 63
                    _DELAY .05
                ELSE
                    PALETTE 0, 0
                    _DELAY .05
                END IF
            END IF
        END IF
        IF eflag = 0 THEN
            PALETTE 0, 36
            IF soundfile% THEN
                _SNDPLAY v.snd1
                _DELAY .05
                PALETTE 0, 0
                _SNDPLAY v.snd2
            ELSE
                _DELAY .075
            END IF
            eflag = -1
            z7 = TIMER
        END IF
        PALETTE 0, 0 ' End flash.

        FOR i = 1 TO 5
            SELECT CASE i
                CASE 1
                    COLOR 15
                    LOCATE b_y1, b_x1: PRINT burst$;
                    _DELAY .1
                CASE 2
                    IF burst$ = CHR$(249) THEN burst$ = CHR$(250)
                    COLOR 14, 0
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .1
                CASE 3
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                CASE 4
                    COLOR 4
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .3
                CASE 5
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
            END SELECT
        NEXT
        IF h = 1 THEN h = 0: EXIT DO
    LOOP
    VIEW PRINT
    ON ERROR GOTO 0
    COLOR 7
    IF a.count = 1 THEN CALL mask_missiles ' Clear unexploded missiles off the screen when changing levels. Game Option: Remove variable to mask missiles after every explosion.
    LOCATE b_y1, b_x1
END SUB

SUB remove_ship (id_by_color) ' Removes ship by color identification.
    i = a.itr
    ON ERROR GOTO offscreen
    LOCATE a_mask_y(id_by_color), a_mask_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    LOCATE a_y(id_by_color), a_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    ON ERROR GOTO 0
    a.itr = id_by_color: CALL a_erase
    a_ran(id_by_color) = -1 ' Denotes alien ship was destroyed and removed from battle.
    a.itr = i
    a.count = a.count - 1 ' a.count = 0  will cause program to exit any unneeded subs/loops after the game level sub is completed.
    LOCATE g.y, g.x: PRINT g.flagship; ' Re-display Guardian.
    ' Restore any alien ships in blast zone.
    FOR i = 1 TO a.max
        IF a_ran(i) > 0 THEN
            IF a_y(i) > v.top AND a_x(i) > v.left AND a_x(i) + LEN(a.ship) < v.right THEN LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;
        END IF
    NEXT
    COLOR 7
    CALL comm
END SUB

SUB alien_move
    STATIC z5
    y_restore = CSRLIN: x_restore = POS(0) ' Restore column and row upon exit.
    IF ABS(z5 - TIMER) > a.cycle_delay OR v.intro THEN ' z5 is a time delay for alien space ship maneuvers. It can be altered in the "game" subroutine.
        IF v.intro = 0 THEN h_alien_nom = INT(RND * a.max) + 1 ELSE h_alien_nom = 15
        FOR h = 1 TO h_alien_nom ' Local counting variable for alien number of moves in this cycle.
            a.itr = a.itr + 1: IF a.itr > a.max THEN a.itr = 1 ' Needed to offset the EXIT DO hover event, which on exit does not affect the a.itr variable.
            IF a_ran(a.itr) <> -1 THEN ' This is how a destroyed ship is bypassed. -1 is a destroyed alien ship. Code moves to end of DO:LOOP.
                IF a_inertia(a.itr) = 0 THEN ' Determine how many moves in one direction.
                    a_inertia(a.itr) = INT(RND * (v.bottom - v.top) / 2) + 1 ' How many moves to go in any one direction.
                    a_ran(a.itr) = INT(RND * 8) + 1 ' Choose 1 of 8 possible directions.

                    IF a_ran(a.itr) = a_olda_ran(a.itr) OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 1 OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 5 THEN
                        EXIT FOR ' Just hover if direction was not changed on existing alien space ship or if a new alien space ship is entering from the sides and up or down was generated.
                    END IF

                    SELECT CASE a_ran(a.itr) ' Get changes in column and row coordinates.
                        CASE 1: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0 '  Up.
                        CASE 2: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 2 '  Up and right.
                        CASE 3: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 2 '   Right.
                        CASE 4: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 2 '   Down and right.
                        CASE 5: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0 '   Down.
                        CASE 6: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -2 '  Down and left.
                        CASE 7: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -2 '  Left.
                        CASE 8: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -2 ' Up and left.
                    END SELECT

                    IF a_y(a.itr) = 0 AND a_x(a.itr) = 0 AND a_ran(a.itr) <> -1 THEN ' New alien space ship enters the screen.
                        i = RND * (v.bottom - v.top) \ 4
                        a_y(a.itr) = (v.bottom - v.top) \ 4 + i + v.top
                        IF a_ran(a.itr) < 5 THEN ' Determine side of entry from initial direction.
                            IF SCREEN(a_y(a.itr), v.left + LEN(a.ship)) = 32 THEN
                                a_x(a.itr) = v.left + 1 ' Enter from the left side and go right.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        ELSE
                            IF SCREEN(a_y(a.itr), v.right - LEN(a.ship) + 1) = 32 THEN
                                a_x(a.itr) = v.right - LEN(a.ship) ' Enter from the right side and go left.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                    a_olda_ran(a.itr) = a_ran(a.itr) ' Remember last direction. Another line uses this to disallow any RND that chooses the same direction twice.
                ELSE
                    a_inertia(a.itr) = a_inertia(a.itr) - 1 ' Count down the number of moves in any one direction. When zero, switch direction.
                END IF

                FOR i = 1 TO a.max
                    IF i <> a.itr AND a_y(i) <> 0 THEN
                        IF a_y(a.itr) + a_y_loc(a.itr) = a_y(i) THEN
                            IF a_x(a.itr) + a_x_loc(a.itr) + LEN(a.ship) > a_x(i) AND a_x(a.itr) + a_x_loc(a.itr) < a_x(i) + LEN(a.ship) THEN
                                collide = 1
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                NEXT
                IF collide = 1 THEN
                    j = a_y(a.itr): k = a_x(a.itr)
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_inertia(a.itr) = 0
                    collide = 0 ' Collision detection off. Collision was detected and avoided.
                ELSE
                    j = a_y(a.itr) + a_y_loc(a.itr): k = a_x(a.itr) + a_x_loc(a.itr)
                END IF

                IF j <= v.top OR k <= v.left OR k + LEN(a.ship) > v.right THEN ' Alien ship out of range, off screen.
                    a_inertia(a.itr) = 0 ' These two lines keep the out of range ship(s) reasonably nearby.
                    IF j > v.top - 4 AND k < v.right + 3 AND k > v.left - 4 THEN a_y(a.itr) = j: a_x(a.itr) = k
                    IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN
                        LOCATE a_mask_y(a.itr), a_mask_x(a.itr)
                        PRINT SPACE$(LEN(a.ship)); ' Mask old position here because the show part of the mask-and-show routine cannot be used when out of range.
                    END IF
                    IF a_offscrn(a.itr) > 25 THEN a_y(a.itr) = 0: a_x(a.itr) = 0: a_inertia(a.itr) = 0: a_offscrn(a.itr) = 0
                    a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_offscrn(a.itr) = a_offscrn(a.itr) + 1
                ELSE
                    ' Check for v.bottom collision and reverse course if detected.
                    COLOR a.itr
                    IF j >= v.bottom THEN
                        a_y_loc(a.itr) = -a_y_loc(a.itr): a_x_loc(a.itr) = -a_x_loc(a.itr)
                    ELSE
                        a_y(a.itr) = j: a_x(a.itr) = k ' Next move coordinates.
                        ii = 0
                        kk = ASC(g.flagship)
                        FOR i = 0 TO LEN(a.ship) - 1 ' Check area through width of ship. Remember all or parts of ship are still present on screen.
                            SELECT CASE SCREEN(j, k + i)
                                CASE kk
                                    ii = 1 ' Indicates contact with flagship and evokes call abduction routine a few lines down.
                                    EXIT FOR
                                CASE g.m_asc
                                    ii = 2 ' Indicates ship into missile collision.
                                    EXIT FOR 'Okay to exit as a missile and Guardian craft cannot be present in the same location.
                            END SELECT
                        NEXT
                        IF ii <> 2 THEN ' This will make a move unless a ship into missile event would occur.
                            '--------------------------------------------Move alien ship-------------------------------------------------
                            IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN LOCATE a_mask_y(a.itr), a_mask_x(a.itr): PRINT SPACE$(LEN(a.ship));
                            LOCATE j, k: PRINT a.ship;
                            a_mask_y(a.itr) = j: a_mask_x(a.itr) = k ' Remember these coordinates to erase alien space ship on next loop.
                            '------------------------------------------------------------------------------------------------------------
                        END IF
                        IF ii = 1 THEN CALL guardian_abduction: EXIT FOR ' Exit loop.
                        j = j - g.y: k = k - g.x + LEN(a.ship) / 2
                        IF ABS(j) < 3 AND ABS(k) < 8 THEN CALL a_hunt(j, k)
                    END IF
                    COLOR 7
                END IF
            END IF ' a_ran(a.itr) > -1 exit point.
            IF a.itr = a.max THEN a.itr = 0: EXIT FOR ' Finished loop. Keep this outside the IF/THEN statement.
        NEXT h
        z5 = TIMER
        LOCATE y_restore, x_restore ' Restore entry column and row positions.
    END IF ' End time event.
END SUB

SUB guardian_abduction

    IF v.intro = 0 THEN
        CALL mask_missiles ' Clear unexploded missiles off the screen after alien abduction.

        msg$ = "[GUARDIAN ABDUCTED]"
        SOUND 500, .4: SOUND 1000, .3: SOUND 1500, .2
        PCOPY 0, 1
        SCREEN 0, 0, 1, 1
        LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
        COLOR 14 + 16: PRINT msg$;
        _DELAY 2
        SCREEN 0, 0, 0, 0
    END IF

    DO
        j = 0
        FOR i = 1 TO a.max
            IF a_y(i) >= v.top AND a_x(i) >= v.left AND a_x(i) <= v.right - LEN(a.ship) THEN
                SOUND 500, .04
                LOCATE a_y(i), a_x(i): PRINT SPACE$(LEN(a.ship));
                IF a_y(i) <> v.top THEN a_y(i) = a_y(i) - 1: LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;: _DELAY .02: j = 1
            END IF
        NEXT
    LOOP WHILE j
    COLOR 7
    IF v.intro THEN EXIT SUB
    g.num = g.num - 1
    CALL comm
    _DELAY .75
    v.redo_level = -1
    a.count = 0 ' Zero to exit subs back to main game loop.
END SUB

SUB game_level ' Evaluates both alien defeated on a level and Guardian abduction.
    _DELAY .5
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    _DELAY .5
    msg$ = "[LEVEL " + LTRIM$(STR$(v.level)) + "]"
    LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16: PRINT msg$;
    j = 0: k = 0
    DO
        IF ABS(z0 - TIMER) > .15 THEN
            IF j = 0 THEN j = 1: SOUND 750, .3: k = k + 1 ELSE j = 0: _DELAY .1
            z0 = TIMER
        END IF
    LOOP UNTIL k = 6
    COLOR 7
    LOCATE , v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    PRINT msg$;
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    _DELAY .75
    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Reset column and row for Guardian craft.
    LOCATE g.y, g.x
    COLOR 15 + 16: PRINT g.flagship;
    _DELAY 1.5
    COLOR 7: LOCATE g.y, g.x: PRINT g.flagship;
    v.level_up = -1
END SUB

SUB level_up
    v.level = v.level + 1
    CALL comm
END SUB

SUB a_erase
    a_y(a.itr) = 0: a_x(a.itr) = 0: a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_inertia(a.itr) = 0: a_ran(a.itr) = 0: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_olda_ran(a.itr) = 0
END SUB

SUB mouse (mouse_event1, mouse_event2)
    STATIC z9, z10, lb, lb_status ' lb_status keeps track of press/release.
    WHILE _MOUSEINPUT
        IF ABS(z10 - TIMER) > .1 THEN
            z10 = TIMER
            IF _MOUSEWHEEL > 0 THEN ' Down
                IF g.thrusters < 20 THEN g.thrusters = g.thrusters + 1
            ELSEIF _MOUSEWHEEL < 0 THEN ' Up
                IF g.thrusters > 0 THEN g.thrusters = g.thrusters - 1
            END IF
        END IF
    WEND
    mx = _MOUSEX
    my = _MOUSEY
    lb = _MOUSEBUTTON(1)
    rb = _MOUSEBUTTON(2)

    IF lb THEN
        IF lb_status = 0 THEN
            IF ABS(z9 - TIMER) > .33 THEN
                z9 = TIMER
                mouse_event1 = -1: lb_status = -1: ' Left button down
            END IF
        END IF
    ELSE
        IF lb_status THEN lb_status = 0 ' Left button was released.
    END IF

    IF v.mouse_get_screen THEN
        IF lb_status = -1 THEN
            x$ = CHR$(SCREEN(my, mx))
            SELECT CASE v.mouse_get_screen
                CASE 1 ' Select difficulty.
                    SELECT CASE x$
                        CASE "1"
                            g.difficulty = 1
                        CASE "2"
                            g.difficulty = 2
                        CASE "3"
                            g.difficulty = 3
                        CASE "S" ' Skip intro.
                            IF x$ = "S" THEN v.intro = 999: SOUND 1000, .3: EXIT SUB
                    END SELECT
                CASE 2 ' Replay
                    IF x$ = "Y" THEN v.play = 999: SOUND 1000, .1: EXIT SUB
                    IF x$ = "N" THEN v.play = -999: SOUND 1000, .1: EXIT SUB
            END SELECT
        END IF
    END IF
    cx = g.x: cy = g.y ' Angular calculations provided by bplus from the QB64 Phoenix Forum.
    stepX = ABS(cx - mx): stepY = ABS(cy - my)

    dAng = INT(_R2D(_ATAN2(my - cy, mx - cx)) + .5)
    IF dAng < 0 THEN dAng = dAng + 360

    IF dAng <= 90 THEN
        startA = 0: endA = dAng: ra = dAng
    ELSEIF dAng <= 180 THEN
        startA = dAng: endA = 180: ra = 90 - (dAng - 90)
    ELSEIF dAng <= 270 THEN
        startA = 180: endA = dAng: ra = dAng - 180
    ELSEIF dAng <= 360 THEN
        startA = dAng: endA = 360: ra = 90 - (dAng - 270)
    END IF

    m_y = 0: m_x = 0
    IF ra <= 90 AND ra >= 50 THEN
        IF my > g.y THEN
            x$ = "down": m_y = 1: m_x = 0 ' Down.
        ELSE
            x$ = "up": m_y = -1: m_x = 0 ' Up.
        END IF
    ELSEIF ra < 50 AND ra >= 15 THEN
        IF mx > g.x AND my > g.y THEN
            x$ = "down right": m_y = 1: m_x = 2 ' Down and right.
        ELSEIF mx < g.x AND my > g.y THEN
            x$ = "down left": m_y = 1: m_x = -2 ' Down an left.
        ELSEIF mx > g.x AND my < g.y THEN
            x$ = "up right": m_y = -1: m_x = 2 ' Up and right.
        ELSEIF mx < g.x AND my < g.y THEN
            x$ = "up left": m_y = -1: m_x = -2 ' Up and left.
        END IF
    ELSEIF ra < 15 AND ra >= 0 THEN
        IF mx > g.x THEN
            x$ = "right": m_y = 0: m_x = 2 ' Right
        ELSE
            x$ = "left": m_y = 0: m_x = -2 ' Left
        END IF
    END IF

    IF rb AND mouse_event2 = 0 THEN
        mouse_event2 = -1
        g.m_y = m_y: g.m_x = m_x / 2
        v.mouse_or_key_move = 1 ' Right mouse button to move.
    ELSE
        IF mouse_event2 THEN mouse_event2 = 0
    END IF

    IF mouse_event1 THEN
        g.diry = m_y: g.dirx = m_x / 2
        v.mouse_or_key_move = -1
    END IF
END SUB

SUB game_over
    v.mouse_get_screen = 2 ' Allows mouse to select replay options.
    _DELAY .5
    CALL comm
    IF g.num THEN SOUND 1000, .75: SOUND 500, .75: SOUND 1000, 1.5 ELSE SOUND 1000, .75: SOUND 700, .75: SOUND 500, 1.5
    _DELAY .5
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    IF g.num THEN msg$ = "[GUARDIAN WINS]" ELSE msg$ = "[Game Over]"
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16
    PRINT msg$;
    _DELAY 2
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14: PRINT msg$;
    msg$ = "Replay? Y/N"
    _DELAY 1
    LOCATE (v.bottom - v.top) \ 2 + v.top + 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 8
    PRINT msg$;
    COLOR 7
    DO
        _LIMIT 30
        CALL mouse(mouse_event1, mouse_event2)
        ky$ = INKEY$
        IF LEN(ky$) OR ABS(v.play) = 999 THEN
            IF v.play = 999 OR UCASE$(ky$) = "Y" OR ky$ = CHR$(13) THEN v.play = 1: VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT: EXIT DO
            IF v.play = -999 OR UCASE$(ky$) = "N" OR ky$ = CHR$(27) THEN
                msg$ = "   [Bye Bye]   "
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14 + 16: PRINT msg$;
                _DELAY 1
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14: PRINT msg$;
                _DELAY .5
                SYSTEM
            END IF
        END IF
    LOOP
END SUB

SUB a_hunt (j, k)
    SELECT CASE a.ai
        CASE 0: EXIT SUB ' No hunting for level 1.
        CASE 1: IF RND * 99 < 33 THEN EXIT SUB ' Reduces chances of abduction for level 2.
    END SELECT
    LOCATE 3, 1
    a_inertia(a.itr) = 4
    h = j / ABS(j): i = k / ABS(k)
    SELECT CASE h
        CASE IS < 0 ' Ship above Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 1
                CASE 0 ' Ship same column as Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -1
            END SELECT
        CASE 0 ' Ship same row as Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 1
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -1
            END SELECT
        CASE IS > 0 ' Ship below Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 1
                CASE 0 ' Ship same column as Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -1
            END SELECT
    END SELECT
    REM Game Option - Shields as a_y_loc(a.itr) = -a_y_loc(a.itr) : a_x_loc(a.itr) = -a_x_loc(a.itr)
END SUB


Again, if you don't have the sounds effects, get them from post #13.

Pete
Reply
#18
Added ASCII city below where alien abductions are -500 points per incident and a "High Score" board if you win and have a top 5 score.

GUARDIAN V 1.3
Code: (Select All)
DEFINT H-K
$RESIZE:ON
$COLOR:32
_RESIZE OFF
RANDOMIZE TIMER
' Note: timer is not adjusted for stroke of midnight event, so don't stay up late playing this.
REM Main
f1 = 22 ' Sets font size to 22 and calculates the max screen height and width for your desktop.
h = (_DESKTOPHEIGHT - 60) \ f1
w = _DESKTOPWIDTH \ (f1 / 1.66)
WIDTH w, h
_SCREENMOVE 0, 0
fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\lucon.ttf" 'Find Windows Folder Path.
font& = _LOADFONT(fontpath$, f1, "monospace")
_FONT font&
_DELAY .25
_RESIZE ON , _SMOOTH ' Allows resizing. Note: this is for slight adjustments. As of this version there is no compensatory function to change the font size during screen size changes.

TYPE gen_var
    intro AS INTEGER ' Runs a protion of the alien subroutine as part of the intro.
    nol AS INTEGER ' Number of game levels.
    redo_level AS INTEGER ' Alien abdution results in redo level.
    level AS INTEGER ' Current game level.
    level_up AS INTEGER ' Routes code to next game level.
    top AS INTEGER ' Top boundary. (Changeable).
    bottom AS INTEGER ' Bottom boundary. (Changeable).
    left AS INTEGER ' Left boundary. (Changeable).
    right AS INTEGER ' Right boundary. (Changeable).
    kb_access AS INTEGER ' Routes code to either Guardian/Alien or general keyboard routine. -1 Guardian/Alien, 0 keyboard.
    mouse_or_key_move AS INTEGER
    mouse_get_screen AS INTEGER
    play AS INTEGER
    snd1 AS LONG ' Explosion sound effect.
    snd2 AS LONG ' Explosion sound effect.
END TYPE

DIM SHARED v AS gen_var

TYPE guardian
    difficulty AS INTEGER
    population AS _INTEGER64
    num AS INTEGER ' Number of Guardians aka lives.
    diry AS INTEGER ' Guardian row move. +1, 0, -1.
    dirx AS INTEGER ' Guardian column move. +2, 0, -2. Equals vertical pixel movement. 16x8.
    y AS INTEGER ' Guardian coordinates row.
    x AS INTEGER ' Guardian coordinates column.
    thrusters AS INTEGER ' Guardian speed. (User Determined).
    m_max AS INTEGER ' Restricts # of missiles fired in one direction.
    m_status AS INTEGER ' Missile status. -1 when fired, 1 while moving.
    m_fired AS INTEGER ' The number of missile deployed and still active.
    m_n AS INTEGER ' FOR/NEXT counter variable shared by other routines to index through the number of missiles fired in a specific direction.
    m_d AS INTEGER ' Missile direction. (1-8).
    m_y AS INTEGER ' Missile row advancement increment: +1, 0, -1 Note: Missile row and column coordinates are defined in arrays.
    m_x AS INTEGER ' Missile column advancement increment: +2, 0, -2. Equals vertical pixel movement. 16x8.
    m_asc AS INTEGER ' ASCII character representing a fired missile.
    m_launcher AS STRING
    icon AS STRING ' Guardian comm icon. For this edition, it is the same as the flagship: "*"
    flagship AS STRING ' Guardian ascii character.
END TYPE

DIM SHARED g AS guardian

TYPE alien
    max AS INTEGER ' Maximum # of alien ships on screen.
    count AS INTEGER ' Number of alien ships. (Counter).
    itr AS INTEGER ' Iteration array number to cycle through the active alien ships. (Counter).
    cycle_delay AS SINGLE ' Timer cycle controls how fast alien ships move.
    ai AS INTEGER ' Degree of artificial intelligence. (0-2)
    ship AS STRING ' Alien ship ASCII design.
END TYPE

DIM SHARED a AS alien
DIM SHARED Overlay

DO
    GOSUB set_arrays

    SELECT CASE v.play
        CASE 0
            CALL set_up
            CALL city
            v.level = 1
            CALL comm
            CALL intro
            v.level = 0: g.population = 100000 ' Reset from intro demo.
            GOSUB set_arrays: GOSUB zero_variables ' Reset arrays and zero variables after intro.
            CALL level_up
            CALL comm
        CASE 1
            CALL set_up
            CALL city
            CALL comm
    END SELECT

    CALL game_level ' Display level.

    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Set initial column and row for Guardian craft.

    CALL game

    IF g.num = 0 OR v.level = v.nol AND v.redo_level = 0 THEN
        CALL game_over ' Determines win or lose.
        v.level = 1 ' Reinitiate if player chooses to replay.
    ELSE
        IF v.redo_level = 0 THEN CALL level_up
    END IF

    GOSUB zero_variables
LOOP

set_arrays:
ii = 15 ' Default max setting for number of alien ships used here to initially dim arrays.
g.m_max = 8 ' * missiles max per direction.
REDIM SHARED a_y(ii), a_x(ii), a_mask_y(ii), a_mask_x(ii), a_inertia(ii) ' Alien movement.
REDIM SHARED a_ran(ii), a_olda_ran(ii), a_y_loc(ii), a_x_loc(ii), a_offscrn(ii) ' Alien movement.
REDIM SHARED m_n(8), m_x(g.m_max, 8), m_y(g.m_max, 8) ' Guardian missiles. 8 represents 8 possible diretions of movement and missile fire.

' Array descriptions and actions.
' a_y() , a_x() Alien ship positions rows and columns.
' a_mask_y(), a_mask_x() Alien ship last position. Masked on next move.
' a_inertia(ii) Number of moves in one direction selected by random for an alien ship.
' a_ran(ii), a_olda_ran(ii) Determines the direction in which the inertia will travel and the prior direction is kept to disallow the same direction twice.
' a_y_loc(ii), a_x_loc(ii) The row and column coordinates of the aliens ships.
' m_n(8), m_x(g.m_max, 8), m_y(g.m_max, 8)  Missile number and Missile index 1 to g.m_max for position. 8 is the fixed number of 8 different directions.
RETURN

zero_variables:
' Zero variables.
g.diry = 0: g.dirx = 0: g.m_status = 0: g.m_fired = 0: g.m_d = 0: g.m_y = 0: g.m_x = 0: v.redo_level = 0: a.max = 0: v.intro = 0
a.count = 0: a.itr = 0: a.cycle_delay = 0: v.level_up = 0: v.mouse_or_key_move = 0: g.m_launcher = ""
RETURN

skipintro: ' ONKEY 15.
v.intro = 999
KEY(15) OFF
RETURN

' Error handler.
offscreen: ' Prevents error if blast particles row and column are off-screen. Effect is a partial blast on side of screen.
IF ERR = 5 THEN er = -1: RESUME NEXT
PRINT "Opps, unexpected error"; ERR
END

SUB intro
    WHILE -1 ' Faux loop.
        v.mouse_get_screen = 1 ' Allows skip intro by mouse selection.
        LOCATE _HEIGHT - 1, _WIDTH - 15
        COLOR 8: PRINT "[S]kip Intro";: COLOR 7 ' Option to skip the intro using the "S" key with ONKEY statement.
        KEY 15, CHR$(0) + CHR$(31) 'scancode for S
        ON KEY(15) GOSUB skipintro
        KEY(15) ON 'turn ON [S]kip intro event trapping.
        j = (v.bottom - v.top) \ 2 + v.top
        k = (v.right - v.left) \ 2 + v.left
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .33
        SOUND 1000, .3
        msg$ = " GUARDIAN "
        LOCATE j, k - LEN(msg$) \ 2: COLOR 14: PRINT msg$;: COLOR 7
        LOCATE , k
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .66
        msg$ = SPACE$(LEN(msg$))
        LOCATE j, k - LEN(msg$) \ 2: COLOR 7: PRINT msg$;
        LOCATE j, k
        IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE ELSE CALL explosion
        LOCATE j, k: COLOR 15 + 16: PRINT g.flagship;
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .75
        v.intro = -1: a.max = 10
        FOR i = 1 TO 90
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
            SOUND 900, .05
            CALL alien_move
            LOCATE j, k: COLOR 15: PRINT g.flagship;: COLOR 7
            _DELAY .07
        NEXT
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > 1
        LOCATE j, k: PRINT " ";
        CALL guardian_abduction
        a.max = 0
        z1 = TIMER
        DO
            CALL mouse(0, 0)
            IF v.intro = 999 THEN CALL skip_intro: EXIT WHILE
        LOOP UNTIL ABS(z1 - TIMER) > .5
        EXIT WHILE
    WEND
    LOCATE v.bottom, _WIDTH - 20: PRINT SPACE$(20);

    ' Playing Instructions.
    REDIM msg$(13)
    msg$(1) = CHR$(249) + " " + "Look towards the top of your screen for Guardian ship status."
    msg$(2) = CHR$(249) + " " + "Hold arrow keys up/dn/lt/rt or in combination to move diagonally."
    msg$(3) = CHR$(249) + " " + "Missiles can be fired simultaneously in 8 different directions."
    msg$(4) = CHR$(249) + " " + "Press Tab to fire 1-8 missiles in the direction of movement."
    msg$(5) = CHR$(249) + " " + "Press Rt Ctrl to increase thrust, or Rt Alt to reduce thrust."
    msg$(6) = ""
    msg$(7) = CHR$(249) + " " + "Mouse alternative: "
    msg$(8) = CHR$(249) + " " + "Hold right mouse button to move toward mouse pointer."
    msg$(9) = CHR$(249) + " " + "Click left mouse button to fire 1-8 missiles in direction of mouse pointer."
    msg$(10) = CHR$(249) + " " + "Mouse wheel up for more thrust, wheel down for less."
    msg$(11) = CHR$(249) + " " + "Note: Tab and left mouse button cannot be used simultaneously."
    msg$(12) = ""
    msg$(13) = CHR$(249) + " " + "If you come in contact with an alien ship, your ship and crew get abducted."
    j = 0
    FOR i = 1 TO UBOUND(msg$)
        IF LEN(msg$(i)) > j THEN j = LEN(msg$(i))
    NEXT
    i = ((v.right - v.left) - j) \ 2
    t_mrgn = 6: b_mrgn = _HEIGHT - 4: l_mrgn = i + 1: r_mrgn = v.right - i
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT STRING$(r_mrgn - l_mrgn + 4, CHR$(196));
    FOR i = 1 TO b_mrgn - t_mrgn + 3
        LOCATE t_mrgn - 2 + i, l_mrgn - 2
        PRINT CHR$(179);
        LOCATE , r_mrgn + 2: PRINT CHR$(179);
    NEXT
    LOCATE t_mrgn - 2, l_mrgn - 2: PRINT CHR$(218);
    LOCATE t_mrgn - 2, r_mrgn + 2: PRINT CHR$(191);
    LOCATE b_mrgn + 2, l_mrgn - 2: PRINT CHR$(192);
    LOCATE b_mrgn + 2, r_mrgn + 2: PRINT CHR$(217);
    msg$ = "<Intro>"
    LOCATE t_mrgn - 2, l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    msg$ = "You are the Captain of the Guardian, an elite battle cruiser commissioned to protect our planet against alien invaders. You have 3 lives. Every time you are abducted by an alien ship, you lose a life. If you survive, and wipe out all 3 flights of alien attacks, your mission is completed. You saved Earth!"
    j = r_mrgn - l_mrgn
    LOCATE t_mrgn, l_mrgn
    msg$ = RTRIM$(msg$) + " " ' Simple word parser routine.--------
    DO
        x$ = MID$(msg$, 1, j)
        x$ = MID$(x$, 1, _INSTRREV(x$, " ") - 1)
        msg$ = LTRIM$(MID$(msg$, LEN(x$) + 1))
        LOCATE , l_mrgn: PRINT x$
    LOOP UNTIL msg$ = "" '-----------------------------------------
    PRINT
    FOR i = 1 TO UBOUND(msg$)
        LOCATE , l_mrgn
        IF LEN(msg$(i)) THEN PRINT msg$(i) ELSE PRINT
    NEXT
    PRINT: IF CSRLIN < b_mrgn - 1 THEN PRINT
    msg$ = "Select Player Level: [1] Beginner  [2] Intermediate  [3] Advanced"
    LOCATE , l_mrgn + (r_mrgn - l_mrgn) \ 2 - LEN(msg$) \ 2
    PRINT msg$;
    DO
        _LIMIT 10
        CALL mouse(0, 0)
        ky$ = INKEY$
        IF LEN(ky$) OR g.difficulty THEN
            IF ky$ = CHR$(27) THEN SYSTEM
            SELECT CASE ky$
                CASE "1": g.difficulty = 1
                CASE "2": g.difficulty = 2
                CASE "3": g.difficulty = 3
            END SELECT
            IF g.difficulty THEN EXIT DO
        END IF
    LOOP
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
END SUB

SUB skip_intro
    KEY(15) OFF
    v.intro = 0
    BEEP: _DELAY .3: BEEP
    VIEW PRINT v.top TO v.bottom
    COLOR 7
    CLS 2
    VIEW PRINT
    _DELAY .5
END SUB

SUB set_up
    v.top = 3: v.bottom = _HEIGHT - 1: v.left = 1: v.right = _WIDTH ' Boundaries.
    LOCATE v.top - 1, 1: PRINT STRING$(_WIDTH, CHR$(196));
    v.nol = 3
    g.population = 100000
    g.flagship = CHR$(15)
    a.ship = "-<>-"
    g.num = 3 ' 3 Guardian (lives) to start.
    g.thrusters = 10 ' Shows 1/2 thrust at start up on comm.
    g.icon = g.flagship
    g.m_asc = 250: g.diry = -1: ' Initiate missile ASCII character. g.diry = -1 initiates fire upwards if unmoved.
    IF _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") AND _FILEEXISTS("Thunder1.ogg") THEN ' Sound effects provided by TheBOB, Bob Seguin, from The QBasic Forum.
        v.snd1 = _SNDOPEN("Thunder1.ogg", "SYNC")
        v.snd2 = _SNDOPEN("Thunder7.ogg", "SYNC")
    END IF
    v.play = -1 ' Skip into on replay.
END SUB

SUB game
    STATIC mouse_event1, mouse_event2
    REM Set mouse_event2 = 0 here to produce constant motion with directional changes.
    'Press single or arrow key combo to move. Rt Ctrl = faster / Rt Alt = slower. Tab to fire missles.
    g.thrusters = 10 ' Guardian movement delay. (1 to 20). Rt Ctrl = faster / Rt Alt = slower.

    SELECT CASE v.level
        CASE 1: a.max = 5 ' Sets number of space ships and initiates attack.
        CASE 2: a.max = 10
        CASE 3: a.max = 15
    END SELECT

    a.count = a.max
    IF a.max < 6 THEN
        SELECT CASE g.difficulty
            CASE 1: a.cycle_delay = .15: a.ai = 0
            CASE 2: a.cycle_delay = .05: a.ai = 1
            CASE 3: a.cycle_delay = 0: a.ai = 2
        END SELECT
    ELSEIF a.max < 11 THEN
        SELECT CASE g.difficulty
            CASE 1: a.cycle_delay = .125: a.ai = 0
            CASE 2: a.cycle_delay = .05: a.ai = 1
            CASE 3: a.cycle_delay = .003: a.ai = 2
        END SELECT
    ELSE
        SELECT CASE g.difficulty
            CASE 1: a.cycle_delay = .1: a.ai = 0
            CASE 2: a.cycle_delay = .03: a.ai = 1
            CASE 3: a.cycle_delay = .001: a.ai = 2
        END SELECT
    END IF

    _KEYCLEAR ' Clear any previous key presses in buffer.
    CALL mouse(0, 0) ' Clear mouse.

    DO
        _LIMIT 60 ' Display at 60 frames per second.

        CALL comm

        IF g.m_status THEN CALL Guardian_missiles ' g.m_status determines Guardian or alien turn. 1 = Guardian, -1 = alien.

        IF a.count = 0 THEN EXIT SUB ' Moving on to next level.

        CALL alien_move

        ' To change thrusters only when guardian moves, remove the timer and move this routine to: IF v.kb_access = -1 THEN
        IF ABS(z3 - TIMER) > .1 THEN ' z3 is a keypress delay timer for Guardian thrusters. Check every .1 seconds for a speed change.
            IF _KEYDOWN(100307) AND g.thrusters < 20 THEN g.thrusters = g.thrusters + 1 ' Rt Ctrl key. Slows down Guardian ship down.
            IF _KEYDOWN(100305) AND g.thrusters > 0 THEN g.thrusters = g.thrusters - 1 '  Rt Alt key. Speeds up Guardian ship.
            z3 = TIMER
        END IF

        IF ABS(z2 - TIMER) > g.thrusters / 100 THEN ' z2 is delay for Guardian movement cycle. Note: Division needed because computer math can't add decimal numbers correctly.
            IF v.kb_access = 0 THEN
                IF _KEYDOWN(18432) OR _KEYDOWN(19200) OR _KEYDOWN(19712) OR _KEYDOWN(20480) OR mouse_event2 = -1 THEN ' Arrow keys.
                    v.kb_access = -1 ' Routes code to guardian move. When zero, Guardian move gets bypassed.
                    z1 = TIMER ' Delay timer for key lag effect in Guardian move routine.
                END IF
            END IF
        END IF

        IF v.kb_access = -1 THEN ' Guardian move routine.-----------------------------------------------------------------------------------------------------------------------------
            IF ABS(z1 - TIMER) > .05 THEN ' z1 is a key lag time delay to allow guardian to press two keys together within a reasonable amount of time.
                DO ' Faux loop added to throw out illegal key combos like up + down.
                    IF mouse_event2 = 0 THEN ' Bypass this keyboard routine if the right mouse button is in use.
                        IF mouse_event1 = 0 THEN g.m_x = 0: g.m_y = 0 ' Variables to control length and direction of Guardian movement. Must be reset to zero each cycle so diagonal moves are possible.

                        IF g.m_x = 0 AND g.m_y = 0 THEN
                            ' Eliminate illegal combos.
                            IF _KEYDOWN(18432) AND _KEYDOWN(20480) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Up + down
                            IF _KEYDOWN(19712) AND _KEYDOWN(19200) THEN v.kb_access = 0: z2 = TIMER: EXIT DO ' Left + right.

                            ' IF female THEN STOP AND GET #1, directions. Keys that control movement.
                            IF _KEYDOWN(18432) THEN ' Up-arrow.
                                g.m_y = -1 ' To move 1-row up.
                            END IF
                            IF _KEYDOWN(19712) THEN ' Right-arrow.
                                g.m_x = 1 ' To move 1-column right.
                            END IF
                            IF _KEYDOWN(20480) THEN ' Down-arrow.
                                g.m_y = 1 ' To move 1-row down.
                            END IF
                            IF _KEYDOWN(19200) THEN ' Left-arrow.
                                g.m_x = -1 ' To move 1-column left.
                            END IF

                            IF g.m_x AND g.m_y THEN ' Double key hold. Routine to cancel keys when double hold is lifted. Compensates for both keys not being released at exactly the same time.
                                combo = -1 ' Double key hold in progress.
                            ELSE
                                IF combo THEN combo = 0: v.kb_access = 0: EXIT DO ' Double key hold was just removed, so skip Guardian move and exit.
                            END IF
                        END IF
                    END IF

                    ' Move Guardian. *****************************************************************************************************
                    IF g.y + g.m_y > v.top AND g.y + g.m_y <= v.bottom AND g.x + 2 * g.m_x > v.left AND g.x + 2 * g.m_x < v.right THEN
                        LOCATE g.y, g.x
                        PRINT " ";
                        g.y = g.y + g.m_y: g.x = g.x + 2 * g.m_x
                        IF SCREEN(g.y, g.x) <> 32 AND SCREEN(g.y, g.x) <> g.m_asc THEN ' Guardian abducted by bad move. Ignore if you run into your own missile.
                            CALL guardian_abduction: EXIT SUB
                        ELSE
                            LOCATE g.y, g.x: PRINT g.flagship;
                        END IF
                    ELSE
                        ' Hit boundary.
                    END IF
                    v.kb_access = 0 ' Guardian move completed. Returns control to general keyboard next cycle.
                    z2 = TIMER ' Timer for moving. Lag regulated by "g.thrusters" variable.
                    EXIT DO ' ************************************************************************************************************
                LOOP
            END IF '
        ELSE ' If you want additional key routines, put them here...------------------------------------------------------------------------------------------------------------------
            CALL mouse(mouse_event1, mouse_event2)
            ky$ = INKEY$
            IF mouse_event1 = -1 THEN ky$ = CHR$(9)
            IF LEN(ky$) = 1 THEN ' For demo, exclude keys that start with chr$(0). Note without this arrow keys would still register here.
                SELECT CASE ky$
                    CASE CHR$(9) ' Tab key. Bug note: Space bar will not register with arrow up + arrow v.left.
                        IF ABS(z8 - TIMER) > .25 THEN
                            SELECT CASE g.m_launcher
                                CASE ""
                                    IF mouse_event1 = -1 THEN g.m_launcher = "mouse" ELSE g.m_launcher = "keyboard" ' Initiate.
                                CASE "keyboard"
                                    IF mouse_event1 = -1 THEN
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "mouse" ' Switch.
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                                CASE "mouse"
                                    IF mouse_event1 = 0 THEN ' Tab key was used.
                                        IF g.m_fired = 0 THEN
                                            g.m_launcher = "keyboard" ' Switch
                                        ELSE
                                            ky$ = ""
                                        END IF
                                    END IF
                            END SELECT
                            IF LEN(ky$) THEN
                                g.m_status = -1 ' -1 indicates missile just fired.
                                IF mouse_event1 = 0 THEN v.mouse_or_key_move = 0: ' Key pressed missile fire clears any previous mouse button missile fire.
                            END IF
                            mouse_event1 = 0 ' Completes left mouse missile firing cycle.
                            z8 = TIMER ' Missile firing delay reset.
                        END IF
                    CASE CHR$(27) ' Esc
                        _DELAY 1: SYSTEM
                    CASE ELSE
                        REM PRINT "You pressed key: "; ky$;
                END SELECT
            END IF
        END IF '----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    LOOP
END SUB

SUB comm
    STATIC middle% ' Local variable.
    IF middle% = 0 THEN middle% = v.left + (v.right - v.left) \ 2
    REM msg$ = "  Thrusters = " + LTRIM$(STR$(5 * (20 - g.thrusters))) + "%  Fired = " + LTRIM$(STR$(g.m_fired)) + " Alien ships = " + LTRIM$(STR$(a.count)) + " Level = " + LTRIM$(STR$(v.level)) + "-" + LTRIM$(STR$(g.difficulty)) + " Guardians: "
    msg$ = "  Population = " + LTRIM$(STR$(g.population)) + " Thrusters = " + LTRIM$(STR$(5 * (20 - g.thrusters))) + "%  Fired = " + LTRIM$(STR$(g.m_fired)) + " Alien ships = " + LTRIM$(STR$(a.count)) + " Level = " + LTRIM$(STR$(v.level)) + "-" + LTRIM$(STR$(g.difficulty)) + " Guardians: "
    LOCATE 1, v.left + middle% - ((LEN(msg$) + 8) \ 2): PRINT msg$;
    SELECT CASE g.num
        CASE 3
            PRINT g.icon; " "; g.icon; " "; g.icon;
        CASE 2
            PRINT g.icon; " "; g.icon; " ";: COLOR 8, 0: PRINT g.icon;: COLOR 7, 0
        CASE 1
            PRINT g.icon; " ";: COLOR 8, 0: PRINT g.icon; " "; g.icon;: COLOR 7, 0
        CASE 0
            COLOR 8, 0: PRINT g.icon; " "; g.icon; " "; g.icon; " ";: COLOR 7, 0
    END SELECT
    PRINT "   "; ' Cut off any former printing caused by length changes in comm report like when numbers change from double to single digits.
END SUB

SUB Guardian_missiles
    STATIC z4

    DIM direction AS INTEGER ' 8 possible directions. local variable.

    IF ABS(z4 - TIMER) > .03 THEN
        z4 = TIMER
        IF v.mouse_or_key_move = 0 THEN ' GAME OPTION: Remove this first IF/THEN to make it necessary to move  Guardian in a direction to fire in that direction.
            IF g.m_y <> 0 OR g.m_x <> 0 THEN g.diry = g.m_y: g.dirx = g.m_x ' Initiate by setting row and column missile direction to last column and row movement direction of Guardian location.
        END IF
        IF g.m_status = -1 THEN
            IF g.diry = -1 AND g.dirx = 0 THEN
                direction = 1
            ELSEIF g.diry = -1 AND g.dirx = 1 THEN
                direction = 2
            ELSEIF g.diry = 0 AND g.dirx = 1 THEN
                direction = 3
            ELSEIF g.diry = 1 AND g.dirx = 1 THEN
                direction = 4
            ELSEIF g.diry = 1 AND g.dirx = 0 THEN
                direction = 5
            ELSEIF g.diry = 1 AND g.dirx = -1 THEN
                direction = 6
            ELSEIF g.diry = 0 AND g.dirx = -1 THEN
                direction = 7
            ELSEIF g.diry = -1 AND g.dirx = -1 THEN
                direction = 8
            END IF

            IF m_n(direction) + 1 <= g.m_max THEN ' Don't fire if out of missiles.
                IF g.y > v.top + 1 AND g.y < v.bottom AND g.x > v.left + 1 AND g.x < v.right - 1 THEN ' Don't fire if at a border.
                    m_n(direction) = m_n(direction) + 1
                    m_y(m_n(direction), direction) = g.y
                    m_x(m_n(direction), direction) = g.x
                    g.m_fired = g.m_fired + 1
                    SOUND 900, .1
                END IF
            END IF
            g.m_status = 1 ' Code will now execute missile launch.
        END IF

        IF g.m_status = 1 THEN
            FOR g.m_d = 1 TO 8 ' Check all directions.
                IF m_n(g.m_d) > 0 THEN
                    j = m_n(g.m_d)
                    FOR g.m_n = 1 TO j
                        IF m_y(g.m_n, g.m_d) = g.y AND m_x(g.m_n, g.m_d) = g.x THEN
                        ELSE
                            LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d): PRINT " ";
                        END IF
                        SELECT CASE g.m_d ' Missile direction.
                            CASE 1
                                IF m_y(g.m_n, g.m_d) - 1 > v.top THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR ' Missile off screen.
                                END IF
                            CASE 2
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 3
                                IF m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 4
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) + 2 < v.right THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) + 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 5
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 6
                                IF m_y(g.m_n, g.m_d) + 1 < v.bottom AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) + 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 7
                                IF m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                            CASE 8
                                IF m_y(g.m_n, g.m_d) - 1 > v.top AND m_x(g.m_n, g.m_d) - 2 > v.left THEN
                                    m_y(g.m_n, g.m_d) = m_y(g.m_n, g.m_d) - 1
                                    m_x(g.m_n, g.m_d) = m_x(g.m_n, g.m_d) - 2
                                    CALL missile_check(k): IF k = -1 THEN EXIT FOR
                                ELSE
                                    CALL remove_missile: EXIT FOR
                                END IF
                        END SELECT
                    NEXT
                    IF a.count = 0 THEN EXIT SUB
                END IF
            NEXT
            IF g.m_fired <= 0 THEN g.m_fired = 0: g.m_status = 0 ' All missiles cleared.
        END IF
    END IF
END SUB

SUB missile_check (k)
    LOCATE m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d) ' Place cursor at current missile position.
    k = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d)) ' Read the screen.
    id_by_color = SCREEN(m_y(g.m_n, g.m_d), m_x(g.m_n, g.m_d), 1)
    IF k <> 32 AND k <> g.m_asc AND k <> ASC(g.flagship) THEN ' If screen space is occupied by alien ship then explosion.
        CALL remove_missile
        CALL explosion
        REM Game Option - IF SCREEN(g.y, g.x) <> ASC(g.flagship) THEN BEEP: BEEP: BEEP
        CALL remove_ship(id_by_color)
        k = -1 ' A flag to exit the FOR/NEXT loop upon return.
    ELSE
        COLOR 14: PRINT CHR$(g.m_asc);: COLOR 7 ' Print missile on the screen. Missile advances here. Only place a missile is printed to the screen.
    END IF
END SUB

SUB remove_missile
    m_n(g.m_d) = m_n(g.m_d) - 1 ' Counter. Reduce the number of missiles fired, in a specific direction, by 1.
    FOR k = g.m_n TO m_n(g.m_d) ' Re-stack arrays.
        m_y(k, g.m_d) = m_y(k + 1, g.m_d)
        m_x(k, g.m_d) = m_x(k + 1, g.m_d)
    NEXT
    m_y(k, g.m_d) = 0: m_x(k, g.m_d) = 0 'Zero out location variables of the missile removed. A zero removes unnecessary loop checking for other routines.
    g.m_fired = g.m_fired - 1 ' Count of number of missiles fired is reduce by 1.
END SUB

SUB mask_missiles
    FOR i = 1 TO g.m_max
        FOR j = 1 TO 8
            IF m_y(i, j) <> 0 THEN LOCATE m_y(i, j), m_x(i, j): PRINT " "; ' Mask missile.
    NEXT j, i
    m_status = 0: m_fired = 0: m_n = 0: m_d = 0: m_y = 0: m_x = 0
    REDIM m_n(8), m_x(g.m_max, 8), m_y(g.m_max, 8)
END SUB

SUB explosion
    soundfile% = 1 ' Local variable turns sound on.
    b_y1 = CSRLIN: b_x1 = POS(0)
    VIEW PRINT v.top TO v.bottom ' Needed to set print error parameters.
    h = 0
    ON ERROR GOTO offscreen
    DO
        IF h = 1 THEN burst$ = " " ELSE burst$ = CHR$(249)
        IF v.intro THEN burst$ = "" ' No fireworks, just flash and sound.
        h = h + 1
        ' Flash
        IF eflag THEN
            IF ABS(z7 - TIMER) > .1 THEN
                eflag = 0
                PALETTE 0, 0
                z7 = TIMER
            ELSE
                j = -j * -1
                IF j = 0 THEN
                    PALETTE 0, 63
                    _DELAY .05
                ELSE
                    PALETTE 0, 0
                    _DELAY .05
                END IF
            END IF
        END IF
        IF eflag = 0 THEN
            PALETTE 0, 36
            IF soundfile% THEN
                _SNDPLAY v.snd1
                _DELAY .05
                PALETTE 0, 0
                _SNDPLAY v.snd2
            ELSE
                _DELAY .075
            END IF
            eflag = -1
            z7 = TIMER
        END IF
        PALETTE 0, 0 ' End flash.

        FOR i = 1 TO 5
            SELECT CASE i
                CASE 1
                    COLOR 15
                    LOCATE b_y1, b_x1: PRINT burst$;
                    _DELAY .1
                CASE 2
                    IF burst$ = CHR$(249) THEN burst$ = CHR$(250)
                    COLOR 14, 0
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .1
                CASE 3
                    LOCATE b_y1 - 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                CASE 4
                    COLOR 4
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT burst$; ELSE er = 0
                    _DELAY .3
                CASE 5
                    LOCATE b_y1 - 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 - 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1, b_x1 + 2
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 - 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
                    LOCATE b_y1 + 1, b_x1 + 1
                    IF er = 0 THEN PRINT " "; ELSE er = 0
            END SELECT
        NEXT
        IF h = 1 THEN h = 0: EXIT DO
    LOOP
    VIEW PRINT
    ON ERROR GOTO 0
    COLOR 7
    IF a.count = 1 THEN CALL mask_missiles ' Clear unexploded missiles off the screen when changing levels. Game Option: Remove variable to mask missiles after every explosion.
    LOCATE b_y1, b_x1
END SUB

SUB remove_ship (id_by_color) ' Removes ship by color identification.
    i = a.itr
    ON ERROR GOTO offscreen
    LOCATE a_mask_y(id_by_color), a_mask_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    LOCATE a_y(id_by_color), a_x(id_by_color): PRINT SPACE$(LEN(a.ship));
    ON ERROR GOTO 0
    a.itr = id_by_color: CALL a_erase
    a_ran(id_by_color) = -1 ' Denotes alien ship was destroyed and removed from battle.
    a.itr = i
    a.count = a.count - 1 ' a.count = 0  will cause program to exit any unneeded subs/loops after the game level sub is completed.
    LOCATE g.y, g.x: PRINT g.flagship; ' Re-display Guardian.
    ' Restore any alien ships in blast zone.
    FOR i = 1 TO a.max
        IF a_ran(i) > 0 THEN
            IF a_y(i) > v.top AND a_x(i) > v.left AND a_x(i) + LEN(a.ship) < v.right THEN LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;
        END IF
    NEXT
    COLOR 7
    CALL comm
END SUB

SUB alien_move
    STATIC z5
    y_restore = CSRLIN: x_restore = POS(0) ' Restore column and row upon exit.
    IF ABS(z5 - TIMER) > a.cycle_delay OR v.intro THEN ' z5 is a time delay for alien space ship maneuvers. It can be altered in the "game" subroutine.
        IF v.intro = 0 THEN h_alien_nom = INT(RND * a.max) + 1 ELSE h_alien_nom = 15
        FOR h = 1 TO h_alien_nom ' Local counting variable for alien number of moves in this cycle.
            a.itr = a.itr + 1: IF a.itr > a.max THEN a.itr = 1 ' Needed to offset the EXIT DO hover event, which on exit does not affect the a.itr variable.
            IF a_ran(a.itr) <> -1 THEN ' This is how a destroyed ship is bypassed. -1 is a destroyed alien ship. Code moves to end of DO:LOOP.
                IF a_inertia(a.itr) = 0 THEN ' Determine how many moves in one direction.
                    a_inertia(a.itr) = INT(RND * (v.bottom - v.top) / 2) + 1 ' How many moves to go in any one direction.
                    a_ran(a.itr) = INT(RND * 8) + 1 ' Choose 1 of 8 possible directions.

                    IF a_ran(a.itr) = a_olda_ran(a.itr) OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 1 OR a_mask_y(a.itr) = 0 AND a_mask_x(a.itr) = 0 AND ran = 5 THEN
                        EXIT FOR ' Just hover if direction was not changed on existing alien space ship or if a new alien space ship is entering from the sides and up or down was generated.
                    END IF

                    SELECT CASE a_ran(a.itr) ' Get changes in column and row coordinates.
                        CASE 1: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0 '  Up.
                        CASE 2: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 2 '  Up and right.
                        CASE 3: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 2 '   Right.
                        CASE 4: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 2 '   Down and right.
                        CASE 5: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0 '   Down.
                        CASE 6: a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -2 '  Down and left.
                        CASE 7: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -2 '  Left.
                        CASE 8: a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -2 ' Up and left.
                    END SELECT

                    IF a_y(a.itr) = 0 AND a_x(a.itr) = 0 AND a_ran(a.itr) <> -1 THEN ' New alien space ship enters the screen.
                        i = RND * (v.bottom - v.top) \ 4
                        a_y(a.itr) = (v.bottom - v.top) \ 4 + i + v.top
                        IF a_ran(a.itr) < 5 THEN ' Determine side of entry from initial direction.
                            IF SCREEN(a_y(a.itr), v.left + LEN(a.ship)) = 32 THEN
                                a_x(a.itr) = v.left + 1 ' Enter from the left side and go right.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        ELSE
                            IF SCREEN(a_y(a.itr), v.right - LEN(a.ship) + 1) = 32 THEN
                                a_x(a.itr) = v.right - LEN(a.ship) ' Enter from the right side and go left.
                            ELSE
                                CALL a_erase
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                    a_olda_ran(a.itr) = a_ran(a.itr) ' Remember last direction. Another line uses this to disallow any RND that chooses the same direction twice.
                ELSE
                    a_inertia(a.itr) = a_inertia(a.itr) - 1 ' Count down the number of moves in any one direction. When zero, switch direction.
                END IF

                FOR i = 1 TO a.max
                    IF i <> a.itr AND a_y(i) <> 0 THEN
                        IF a_y(a.itr) + a_y_loc(a.itr) = a_y(i) THEN
                            IF a_x(a.itr) + a_x_loc(a.itr) + LEN(a.ship) > a_x(i) AND a_x(a.itr) + a_x_loc(a.itr) < a_x(i) + LEN(a.ship) THEN
                                collide = 1
                                EXIT FOR
                            END IF
                        END IF
                    END IF
                NEXT
                IF collide = 1 THEN
                    j = a_y(a.itr): k = a_x(a.itr)
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_inertia(a.itr) = 0
                    collide = 0 ' Collision detection off. Collision was detected and avoided.
                ELSE
                    j = a_y(a.itr) + a_y_loc(a.itr): k = a_x(a.itr) + a_x_loc(a.itr)
                END IF

                IF j <= v.top OR k <= v.left OR k + LEN(a.ship) > v.right THEN ' Alien ship out of range, off screen.
                    a_inertia(a.itr) = 0 ' These two lines keep the out of range ship(s) reasonably nearby.
                    IF j > v.top - 4 AND k < v.right + 3 AND k > v.left - 4 THEN a_y(a.itr) = j: a_x(a.itr) = k
                    IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN
                        LOCATE a_mask_y(a.itr), a_mask_x(a.itr)
                        PRINT SPACE$(LEN(a.ship)); ' Mask old position here because the show part of the mask-and-show routine cannot be used when out of range.
                    END IF
                    IF a_offscrn(a.itr) > 25 THEN a_y(a.itr) = 0: a_x(a.itr) = 0: a_inertia(a.itr) = 0: a_offscrn(a.itr) = 0
                    a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_offscrn(a.itr) = a_offscrn(a.itr) + 1
                ELSE
                    ' Check for v.bottom collision and reverse course if detected.
                    COLOR a.itr
                    IF j >= v.bottom THEN
                        IF j = v.bottom AND k > v.left + 10 AND k < v.right - 10 THEN
                            LOCATE a_y(a.itr) + 1, a_x(a.itr)
                            COLOR 15 + 16: PRINT "-500";
                            g.population = g.population - 500
                            SOUND 300, .2: _DELAY .07: SOUND 600, .2: _DELAY .07: SOUND 400, .2
                            COLOR 7
                            CALL comm
                            LOCATE a_y(a.itr) + 1, a_x(a.itr)
                            PRINT SPACE$(5);
                            a_inertia(a.itr) = v.bottom - v.top
                            a_y_loc(a.itr) = -a_y_loc(a.itr): a_x_loc(a.itr) = -a_x_loc(a.itr)
                            a_y_loc(a.itr) = -1
                        ELSE
                            a_y_loc(a.itr) = -a_y_loc(a.itr): a_x_loc(a.itr) = -a_x_loc(a.itr)
                        END IF
                    ELSE
                        a_y(a.itr) = j: a_x(a.itr) = k ' Next move coordinates.
                        ii = 0
                        kk = ASC(g.flagship)
                        FOR i = 0 TO LEN(a.ship) - 1 ' Check area through width of ship. Remember all or parts of ship are still present on screen.
                            SELECT CASE SCREEN(j, k + i)
                                CASE kk
                                    ii = 1 ' Indicates contact with flagship and evokes call abduction routine a few lines down.
                                    EXIT FOR
                                CASE g.m_asc
                                    ii = 2 ' Indicates ship into missile collision.
                                    EXIT FOR 'Okay to exit as a missile and Guardian craft cannot be present in the same location.
                            END SELECT
                        NEXT
                        IF ii <> 2 THEN ' This will make a move unless a ship into missile event would occur.
                            '--------------------------------------------Move alien ship-------------------------------------------------
                            IF a_mask_y(a.itr) <> 0 AND a_mask_x(a.itr) <> 0 THEN LOCATE a_mask_y(a.itr), a_mask_x(a.itr): PRINT SPACE$(LEN(a.ship));
                            LOCATE j, k: PRINT a.ship;
                            a_mask_y(a.itr) = j: a_mask_x(a.itr) = k ' Remember these coordinates to erase alien space ship on next loop.
                            '------------------------------------------------------------------------------------------------------------
                        END IF
                        IF ii = 1 THEN CALL guardian_abduction: EXIT FOR ' Exit loop.
                        j = j - g.y: k = k - g.x + LEN(a.ship) / 2
                        IF ABS(j) < 3 AND ABS(k) < 8 THEN CALL a_hunt(j, k)
                    END IF
                    COLOR 7
                END IF
            END IF ' a_ran(a.itr) > -1 exit point.
            IF a.itr = a.max THEN a.itr = 0: EXIT FOR ' Finished loop. Keep this outside the IF/THEN statement.
        NEXT h
        z5 = TIMER
        LOCATE y_restore, x_restore ' Restore entry column and row positions.
    END IF ' End time event.
END SUB

SUB guardian_abduction

    IF v.intro = 0 THEN
        CALL mask_missiles ' Clear unexploded missiles off the screen after alien abduction.

        msg$ = "[GUARDIAN ABDUCTED]"
        SOUND 500, .4: SOUND 1000, .3: SOUND 1500, .2
        PCOPY 0, 1
        SCREEN 0, 0, 1, 1
        LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
        COLOR 14 + 16: PRINT msg$;
        _DELAY 2
        SCREEN 0, 0, 0, 0
    END IF

    DO
        j = 0
        FOR i = 1 TO a.max
            IF a_y(i) >= v.top AND a_x(i) >= v.left AND a_x(i) <= v.right - LEN(a.ship) THEN
                SOUND 500, .04
                LOCATE a_y(i), a_x(i): PRINT SPACE$(LEN(a.ship));
                IF a_y(i) <> v.top THEN a_y(i) = a_y(i) - 1: LOCATE a_y(i), a_x(i): COLOR i: PRINT a.ship;: _DELAY .02: j = 1
            END IF
        NEXT
    LOOP WHILE j
    COLOR 7
    IF v.intro THEN EXIT SUB
    g.num = g.num - 1
    CALL comm
    _DELAY .75
    v.redo_level = -1
    a.count = 0 ' Zero to exit subs back to main game loop.
END SUB

SUB game_level ' Evaluates both alien defeated on a level and Guardian abduction.
    _DELAY .5
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    _DELAY .5
    msg$ = "[LEVEL " + LTRIM$(STR$(v.level)) + "]"
    LOCATE (v.bottom - v.top) \ 2 + v.top, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16: PRINT msg$;
    j = 0: k = 0
    DO
        IF ABS(z0 - TIMER) > .15 THEN
            IF j = 0 THEN j = 1: SOUND 750, .3: k = k + 1 ELSE j = 0: _DELAY .1
            z0 = TIMER
        END IF
    LOOP UNTIL k = 6
    COLOR 7
    LOCATE , v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    PRINT msg$;
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    _DELAY .75
    CALL comm
    g.y = (v.bottom - v.top) \ 2 + v.top: g.x = (v.right - v.left + 1) \ 2 + v.left ' Reset column and row for Guardian craft.
    LOCATE g.y, g.x
    COLOR 15 + 16: PRINT g.flagship;
    _DELAY 1.5
    COLOR 7: LOCATE g.y, g.x: PRINT g.flagship;
    v.level_up = -1
END SUB

SUB level_up
    v.level = v.level + 1
END SUB

SUB a_erase
    a_y(a.itr) = 0: a_x(a.itr) = 0: a_mask_y(a.itr) = 0: a_mask_x(a.itr) = 0: a_inertia(a.itr) = 0: a_ran(a.itr) = 0: a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 0: a_olda_ran(a.itr) = 0
END SUB

SUB mouse (mouse_event1, mouse_event2)
    STATIC z9, z10, lb, lb_status ' lb_status keeps track of press/release.
    WHILE _MOUSEINPUT
        IF ABS(z10 - TIMER) > .1 THEN
            z10 = TIMER
            IF _MOUSEWHEEL > 0 THEN ' Down
                IF g.thrusters < 20 THEN g.thrusters = g.thrusters + 1
            ELSEIF _MOUSEWHEEL < 0 THEN ' Up
                IF g.thrusters > 0 THEN g.thrusters = g.thrusters - 1
            END IF
        END IF
    WEND
    mx = _MOUSEX
    my = _MOUSEY
    lb = _MOUSEBUTTON(1)
    rb = _MOUSEBUTTON(2)

    IF lb THEN
        IF lb_status = 0 THEN
            IF ABS(z9 - TIMER) > .33 THEN
                z9 = TIMER
                mouse_event1 = -1: lb_status = -1: ' Left button down
            END IF
        END IF
    ELSE
        IF lb_status THEN lb_status = 0 ' Left button was released.
    END IF

    IF v.mouse_get_screen THEN
        IF lb_status = -1 THEN
            x$ = CHR$(SCREEN(my, mx))
            SELECT CASE v.mouse_get_screen
                CASE 1 ' Select difficulty.
                    SELECT CASE x$
                        CASE "1"
                            g.difficulty = 1
                        CASE "2"
                            g.difficulty = 2
                        CASE "3"
                            g.difficulty = 3
                        CASE "S" ' Skip intro.
                            IF x$ = "S" THEN v.intro = 999: SOUND 1000, .3: EXIT SUB
                    END SELECT
                CASE 2 ' Replay
                    IF x$ = "Y" THEN v.play = 999: SOUND 1000, .1: EXIT SUB
                    IF x$ = "N" THEN v.play = -999: SOUND 1000, .1: EXIT SUB
            END SELECT
        END IF
    END IF
    cx = g.x: cy = g.y ' Angular calculations provided by bplus from the QB64 Phoenix Forum.
    stepX = ABS(cx - mx): stepY = ABS(cy - my)

    dAng = INT(_R2D(_ATAN2(my - cy, mx - cx)) + .5)
    IF dAng < 0 THEN dAng = dAng + 360

    IF dAng <= 90 THEN
        startA = 0: endA = dAng: ra = dAng
    ELSEIF dAng <= 180 THEN
        startA = dAng: endA = 180: ra = 90 - (dAng - 90)
    ELSEIF dAng <= 270 THEN
        startA = 180: endA = dAng: ra = dAng - 180
    ELSEIF dAng <= 360 THEN
        startA = dAng: endA = 360: ra = 90 - (dAng - 270)
    END IF

    m_y = 0: m_x = 0
    IF ra <= 90 AND ra >= 50 THEN
        IF my > g.y THEN
            x$ = "down": m_y = 1: m_x = 0 ' Down.
        ELSE
            x$ = "up": m_y = -1: m_x = 0 ' Up.
        END IF
    ELSEIF ra < 50 AND ra >= 15 THEN
        IF mx > g.x AND my > g.y THEN
            x$ = "down right": m_y = 1: m_x = 2 ' Down and right.
        ELSEIF mx < g.x AND my > g.y THEN
            x$ = "down left": m_y = 1: m_x = -2 ' Down an left.
        ELSEIF mx > g.x AND my < g.y THEN
            x$ = "up right": m_y = -1: m_x = 2 ' Up and right.
        ELSEIF mx < g.x AND my < g.y THEN
            x$ = "up left": m_y = -1: m_x = -2 ' Up and left.
        END IF
    ELSEIF ra < 15 AND ra >= 0 THEN
        IF mx > g.x THEN
            x$ = "right": m_y = 0: m_x = 2 ' Right
        ELSE
            x$ = "left": m_y = 0: m_x = -2 ' Left
        END IF
    END IF

    IF rb AND mouse_event2 = 0 THEN
        mouse_event2 = -1
        g.m_y = m_y: g.m_x = m_x / 2
        v.mouse_or_key_move = 1 ' Right mouse button to move.
    ELSE
        IF mouse_event2 THEN mouse_event2 = 0
    END IF

    IF mouse_event1 THEN
        g.diry = m_y: g.dirx = m_x / 2
        v.mouse_or_key_move = -1
    END IF
END SUB

SUB game_over
    v.mouse_get_screen = 2 ' Allows mouse to select replay options.
    _DELAY .5
    CALL comm
    IF g.num THEN SOUND 1000, .75: SOUND 500, .75: SOUND 1000, 1.5 ELSE SOUND 1000, .75: SOUND 700, .75: SOUND 500, 1.5
    _DELAY .5
    VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT
    IF g.num THEN msg$ = "[GUARDIAN WINS]" ELSE msg$ = "[Game Over]"
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14 + 16
    PRINT msg$;
    _DELAY 2
    IF g.num > 0 THEN
        LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
        COLOR 7
        PRINT SPACE$(LEN(msg$));
        CALL displayhighscores
    ELSE
        FOR i = 1 TO 2
            LOCATE v.bottom, v.left + ((v.right - v.left + 1) \ 2)
            CALL explosion
        NEXT
        LOCATE v.bottom + 1, v.left
        COLOR 8
        PRINT STRING$(v.right - v.left + 1, "÷");
        COLOR 7
        _DELAY .3
        g.population = 0
        CALL comm
        _DELAY .5
    END IF
    LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 14: PRINT msg$;
    msg$ = "Replay? Y/N"
    _DELAY 1
    LOCATE (v.bottom - v.top) \ 2 + v.top + 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
    COLOR 8
    PRINT msg$;
    COLOR 7
    DO
        _LIMIT 30
        CALL mouse(mouse_event1, mouse_event2)
        ky$ = INKEY$
        IF LEN(ky$) OR ABS(v.play) = 999 THEN
            IF v.play = 999 OR UCASE$(ky$) = "Y" OR ky$ = CHR$(13) THEN v.play = 1: VIEW PRINT v.top TO v.bottom: CLS 2: VIEW PRINT: EXIT DO
            IF v.play = -999 OR UCASE$(ky$) = "N" OR ky$ = CHR$(27) THEN
                msg$ = "   [Bye Bye]   "
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14 + 16: PRINT msg$;
                _DELAY 1
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                COLOR 14: PRINT msg$;
                _DELAY .5
                LOCATE (v.bottom - v.top) \ 2 + v.top + 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                PRINT SPACE$(LEN(msg$));
                COLOR 14
                msg$ = "   [Bye Bye]   "
                LOCATE (v.bottom - v.top) \ 2 + v.top - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                DO
                    LOCATE , v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                    SOUND 500, .04
                    PRINT SPACE$(LEN(msg$));
                    LOCATE CSRLIN - 1, v.left + ((v.right - v.left + 1) \ 2) - LEN(msg$) \ 2
                    PRINT msg$;
                    _DELAY .05
                LOOP UNTIL CSRLIN = v.top
                COLOR 7
                VIEW PRINT v.top TO v.bottom: CLS 2
                _DELAY .6
                SYSTEM
            END IF
        END IF
    LOOP
END SUB

SUB a_hunt (j, k)
    SELECT CASE a.ai
        CASE 0: EXIT SUB ' No hunting for level 1.
        CASE 1: IF RND * 99 < 33 THEN EXIT SUB ' Reduces chances of abduction for level 2.
    END SELECT
    LOCATE 3, 1
    a_inertia(a.itr) = 4
    h = j / ABS(j): i = k / ABS(k)
    SELECT CASE h
        CASE IS < 0 ' Ship above Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 1
                CASE 0 ' Ship same column as Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = 0
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = 1: a_x_loc(a.itr) = -1
            END SELECT
        CASE 0 ' Ship same row as Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = 1
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = 0: a_x_loc(a.itr) = -1
            END SELECT
        CASE IS > 0 ' Ship below Guardian.
            SELECT CASE i
                CASE IS < 0 ' Ship to left of Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 1
                CASE 0 ' Ship same column as Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = 0
                CASE IS > 0 ' Ship to right of Guardian.
                    a_y_loc(a.itr) = -1: a_x_loc(a.itr) = -1
            END SELECT
    END SELECT
    REM Game Option - Shields as a_y_loc(a.itr) = -a_y_loc(a.itr) : a_x_loc(a.itr) = -a_x_loc(a.itr)
END SUB

SUB city
    _CONTROLCHR OFF
    a$ = "_ê_ê_ê__²_²_²_²___ï_ï_ï___ê_ê___ããã______²_²___ê_ê_ê_"

    DO
        cityscape$ = cityscape$ + a$
    LOOP UNTIL LEN(cityscape$) > _WIDTH
    cityscape$ = MID$(cityscape$, 1, _WIDTH)
    LOCATE _HEIGHT, 1
    PRINT cityscape$;
END SUB

SUB displayhighscores
    hardware_top = v.top + 4
    hardware_left = v.left + 34
    score$ = LTRIM$(STR$(g.population))

    DIM hs AS STRING * 25
    REDIM highscore$(6), hsdata$(6)
    DO
        IF _FILEEXISTS("guardian-high-score.dat") THEN
            OPEN "guardian-high-score.dat" FOR RANDOM AS #1 LEN = 25
            FOR i = 1 TO 5
                GET #1, i, hs
                highscore$(i) = MID$(hs, 10, 6): hsdata$(i) = hs
            NEXT
            CLOSE #1
        ELSE
            FOR i = 1 TO 5
                hsdata$(i) = SPACE$(25)
            NEXT
        END IF

        IF VAL(score$) > VAL(highscore$(5)) THEN

            IF VAL(score$) > VAL(highscore$(1)) THEN
                topscore$ = "HIGH SCORE! Enter Initials!"
            ELSE
                topscore$ = "Top 5 Score Enter Initials!"
            END IF

            GOSUB hiscore

            i = 14
            OUT &H3C8, 0
            OUT &H3C9, 20 - i
            OUT &H3C9, 20 - i
            OUT &H3C9, 20 - i

            OUT &H3C8, 8
            OUT &H3C9, 30 - i
            OUT &H3C9, 30 - i
            OUT &H3C9, 30 - i

            OUT &H3C8, 7
            OUT &H3C9, 30 - i
            OUT &H3C9, 30 - i
            OUT &H3C9, 30 - i

            OUT &H3C8, 3
            OUT &H3C9, 30 - i
            OUT &H3C9, 30 - i
            OUT &H3C9, 30 - i

            GOSUB hardware_overlay

            COLOR White, 0
            t$ = msg$
            PSLC 4.6, 41 - LEN(msg$) \ 2, t$

            lscr = hardware_left + 6
            z3 = TIMER
            WHILE -1
                initials$ = "": i = 0: nxt = 0
                COLOR , _RGB(24, 24, 24): t$ = "   " ' Blank initials for redo. Okay to blank at start.
                PSL hardware_top + 2 + rank * 2, lscr, t$
                _DISPLAY
                DO
                    _LIMIT 30
                    IF ABS(z3 - TIMER) > .3 THEN ' Flashing cursor
                        underline hardware_top + 2 + rank * 2, lscr + nxt, 0
                        _DISPLAY
                        z3 = TIMER
                    END IF

                    ky$ = UCASE$(INKEY$)
                    IF LEN(ky$) THEN
                        IF ky$ = CHR$(13) THEN
                            kflag = 3
                        ELSEIF ky$ = CHR$(8) AND LEN(initials$) THEN
                            kflag = 2
                        ELSEIF ky$ = CHR$(27) THEN
                            kflag = 4
                        ELSEIF ky$ >= "A" AND ky$ <= "Z" AND LEN(initials$) < 3 THEN
                            initials$ = initials$ + ky$
                            kflag = 1
                        ELSE
                            ky$ = "": kflag = 0
                        END IF
                    END IF

                    SELECT CASE kflag
                        CASE 1
                            COLOR , _RGB(24, 24, 24)
                            PSL hardware_top + 2 + rank * 2, lscr + nxt, " "
                            COLOR Yellow
                            SOUND 1000, .1
                            PSL hardware_top + 2 + rank * 2, lscr + nxt, ky$
                            underline hardware_top + 2 + rank * 2, lscr + nxt, -1
                            nxt = nxt + 1
                            underline hardware_top + 2 + rank * 2, lscr + nxt, 0
                            _DISPLAY
                            kflag = 0
                        CASE 2
                            COLOR , _RGB(24, 24, 24)
                            underline hardware_top + 2 + rank * 2, lscr + nxt, -1
                            initials$ = MID$(initials$, 1, LEN(initials$) - 1)
                            nxt = nxt - 1
                            PSL hardware_top + 2 + rank * 2, lscr + nxt, " "
                            COLOR Yellow
                            SOUND 1000, .1
                            underline hardware_top + 2 + rank * 2, lscr + nxt, 0
                            _DISPLAY
                            kflag = 0
                        CASE 3
                            _DELAY 1
                            l$ = "12"
                            n$ = "n24": PLAY "L" + l$ + n$
                            n$ = "n28": PLAY "L" + l$ + n$
                            n$ = "n28": PLAY "L" + l$ + n$
                            l$ = "10"
                            n$ = "n31": PLAY "L" + l$ + n$
                            l$ = "12"
                            n$ = "n28": PLAY "L" + l$ + n$
                            l$ = "5"
                            n$ = "n31": PLAY "L" + l$ + n$
                            kflag = 1
                            _DELAY 1: EXIT DO
                        CASE 4
                            EXIT WHILE
                    END SELECT
                LOOP
                hsname$ = initials$

                MID$(hsdata$(rank), 5, 3) = hsname$ + SPACE$(3 - LEN(hsname$))
                OPEN "guardian-high-score.dat" FOR RANDOM AS #1 LEN = 25
                FOR i = 1 TO 5
                    hs = hsdata$(i)
                    IF LEFT$(hs, 1) = "" THEN MID$(hs, 1, 2) = "0" + LTRIM$(STR$(i))
                    PUT #1, i, hs
                NEXT
                CLOSE #1
                EXIT WHILE
            WEND

            _DISPLAY ' Remove scoreboard.
            _DELAY .5
            _FREEIMAGE Overlay

            _DEST 0 'Reset dest back to the normal screen 0.

            _AUTODISPLAY
            PALETTE
            _DELAY .5

            EXIT DO
        ELSE
            EXIT DO ' Not in the top 5 highest scores so exit sub.
        END IF
    LOOP
    EXIT SUB

    hardware_overlay:
    Overlay = _NEWIMAGE(_WIDTH * _FONTWIDTH, _HEIGHT * _FONTHEIGHT, 32)

    _DEST Overlay
    _DISPLAY ' Turn autodisplay off.

    font = _LOADFONT("lucon.ttf", 24, "monospace")
    IF font <= 0 THEN font = 16
    _FONT font

    bxy% = hardware_top
    bxx% = hardware_left

    COLOR White, 0
    PSL bxy% + .8, bxx% + 1, topscore$

    COLOR Yellow, 0
    t$ = " " + CHR$(218) + STRING$(27, CHR$(196)) + CHR$(191) + " "
    PSL bxy%, bxx% - 1, t$
    FOR i = 1 TO 12
        t$ = " " + CHR$(179) + STRING$(27, CHR$(32)) + CHR$(179) + " "
        PSL bxy% + i, bxx% - 1, t$
    NEXT
    t$ = " " + CHR$(192) + STRING$(27, CHR$(196)) + CHR$(217) + " "
    PSL bxy% + i, bxx% - 1, t$

    bxy% = hardware_top + 1
    COLOR Black, Yellow
    t$ = "    NAME   SCORE    DATE   "
    PSL bxy% + 1, bxx% + 1, t$

    COLOR Yellow, 0
    FOR i = 1 TO 5
        t$ = hsdata$(i)
        PSL bxy% + 1 + i * 2, bxx% + 2, t$
    NEXT
    _DISPLAY
    RETURN

    hiscore:
    FOR i = 1 TO 5
        IF VAL(score$) > VAL(highscore$(i)) THEN rank = i: EXIT FOR
    NEXT

    hsdata$(6) = SPACE$(25)
    MID$(hsdata$(6), 10, 6) = score$
    MID$(hsdata$(6), 18, 8) = MID$(DATE$, 1, 6) + MID$(DATE$, 9, 2)
    highscore$(6) = score$
    FOR i = 1 TO 6
        FOR j = 1 TO 6
            IF i <> j THEN
                IF VAL(highscore$(i)) > VAL(highscore$(j)) THEN
                    SWAP highscore$(i), highscore$(j)
                    SWAP hsdata$(i), hsdata$(j)
                END IF
            END IF
        NEXT
    NEXT
    FOR i = 1 TO 5
        MID$(hsdata$(i), 1, 2) = "0" + LTRIM$(STR$(i))
    NEXT
    RETURN
END SUB

SUB PSLC (y!, x, t$)
    _PRINTSTRING ((x - 1) * 8, (y! - 1) * 16), t$
END SUB

SUB PSL (y!, x, t$)
    _PRINTSTRING ((x - 1) * _FONTWIDTH, (y! - 1) * _FONTHEIGHT), t$
    Overlay_Hardware = _COPYIMAGE(Overlay, 33)
    _PUTIMAGE (0, 0), Overlay_Hardware
    _FREEIMAGE Overlay_Hardware
END SUB

SUB underline (y, x, uflag)
    STATIC ucnt
    ucnt = -ucnt - 1
    IF ucnt OR uflag THEN
        LINE ((x - 1) * _FONTWIDTH, y * _FONTHEIGHT)-((x - 1) * _FONTWIDTH + 12, y * _FONTHEIGHT), _RGB(24, 24, 24), BF
    ELSE
        LINE ((x - 1) * _FONTWIDTH, y * _FONTHEIGHT)-((x - 1) * _FONTWIDTH + 12, y * _FONTHEIGHT), Yellow, BF
    END IF

    Overlay_Hardware = _COPYIMAGE(Overlay, 33)
    _PUTIMAGE (0, 0), Overlay_Hardware
    _FREEIMAGE Overlay_Hardware
END SUB


Download sound effects files attachment in Post #13.

Pete
Reply
#19
OK I tried it. It looked and worked fine until I got to around the last couple of ships in level 3 all of a sudden I am getting Virus Threats from MS Defender and it refused to store .exe as far as I can tell (No it must of erased the one I was playing on). Very strange response, never seen it before. It refused to allow me to play it again???

Here is screen shot from Windows Defender
   
b = b + ...
Reply
#20
That was weird! I downloaded my Blackjack game in Terrys Game Programing Board just to check downloading on Windows. Everything went fine with that. Well let's see if anyone else runs into problems???

BTW I do like mouse action games, so much better than trying to find keys on my keyboard.
b = b + ...
Reply




Users browsing this thread: 6 Guest(s)