  REM =================================================================
REM JD'S INVADERS - MODERNIZED & VECTORIZED (ADVANCED)
REM Refactored to use a Structure-of-Arrays (SoA) design with
REM advanced array-oriented functions for collision detection.
REM =================================================================

REM --- GAME CONFIGURATION ---
MAX_ALIENS = 40
MAX_BLOCKS = 4
ALIEN_COLS = 8
ALIEN_ROWS = 5

REM =================================================================
REM DATA STRUCTURES (SoA - Structure of Arrays)
REM =================================================================

TYPE T_Alien
  X             ' Array of X positions
  Y             ' Array of Y positions
  InitialX      ' Array of initial X
  InitialY      ' Array of initial Y
  Points        ' Array of point values
  Direction     ' Array of directions
  Species       ' Array of types
  Visible       ' Boolean array of visibility
ENDTYPE

TYPE T_Player
  X AS DOUBLE
  Y AS DOUBLE
  Direction AS DOUBLE
  Visible AS BOOLEAN
  RespawnTimer AS DOUBLE
ENDTYPE

TYPE T_Missile
  X AS DOUBLE
  Y AS DOUBLE
  Visible AS BOOLEAN
  IsFiring AS BOOLEAN
ENDTYPE

TYPE T_Block
  X           ' Array of X positions
  Y           ' Array of Y positions
  Hits        ' Array of hit counts
  Visible     ' Boolean array of visibility
ENDTYPE

REM --- DECLARE DATA VARIABLES ---
DIM Aliens AS T_Alien
DIM Blocks AS T_Block
DIM Player AS T_Player
DIM PlayerMissile AS T_Missile
DIM AlienMissile AS T_Missile

REM -- GLOBAL GAME STATE --
DIM SCORE AS INTEGER
DIM LIVES AS INTEGER
DIM SPEED AS INTEGER
DIM GAMEOVER AS INTEGER
DIM DIFFICULTY AS INTEGER
DIM FRAME AS INTEGER
DIM SFRAME AS INTEGER

REM --- SPRITE GRAPHICS ---
DIM PLAYER_T[2]
PLAYER_T[0] = " ▞▚ "
PLAYER_T[1] = "▛▀▀▜"

DIM BLOCK_A_T[2] : BLOCK_A_T[0] = "▗▇▇▖" : BLOCK_A_T[1] = "████"
DIM BLOCK_B_T[2] : BLOCK_B_T[0] = "    " : BLOCK_B_T[1] = "████"
DIM BLOCK_C_T[2] : BLOCK_C_T[0] = "    " : BLOCK_C_T[1] = "▚▞▚▞"

DIM ALIEN_T[12]
ALIEN_T[0] = "▗▇▇▖" : ALIEN_T[1] = "▞  ▚" : ALIEN_T[2] = "▗▇▇▖"
ALIEN_T[3] = "▚  ▞" : ALIEN_T[4] = "▗▇▇▖" : ALIEN_T[5] = " ▚▞ "
ALIEN_T[6] = "▗▇▇▖" : ALIEN_T[7] = " ▞▚"  : ALIEN_T[8] = "▛▀▀▜"
ALIEN_T[9] = "▚▅▅▞" : ALIEN_T[10] = "▛▀▀▜": ALIEN_T[11] = "▚  ▞"

P_MISSILE$ = "|"
A_MISSILE$ = "┇"

REM ======================================================================
REM INITIALIZATION
REM ======================================================================

SUB INIT_LEVEL()
    GAMEOVER = 0
    SPEED = 6
    FRAME = 0
    SFRAME = 0
    
    REM --- CREATE ALIENS (VECTORIZED) ---
    ColsX = (IOTA(ALIEN_COLS) - 1) * 8 + 8
    RowsY = (IOTA(ALIEN_ROWS) - 1) * 3 + 4
    TypeVec = [0, 1, 1, 2, 2]
    PointsVec = [500, 300, 300, 100, 100]

    AlienXMatrix = OUTER(IOTA(ALIEN_ROWS) * 0, ColsX, "+")
    AlienYMatrix = OUTER(RowsY, IOTA(ALIEN_COLS) * 0, "+")
    AlienTypeMatrix = OUTER(TypeVec, IOTA(ALIEN_COLS) * 0, "+")
    AlienPointsMatrix = OUTER(PointsVec, IOTA(ALIEN_COLS) * 0, "+")

    Aliens.X = RESHAPE(AlienXMatrix, [MAX_ALIENS])
    Aliens.Y = RESHAPE(AlienYMatrix, [MAX_ALIENS])
    Aliens.InitialX = Aliens.X
    Aliens.InitialY = Aliens.Y
    Aliens.Species = RESHAPE(AlienTypeMatrix, [MAX_ALIENS])
    Aliens.Points = RESHAPE(AlienPointsMatrix, [MAX_ALIENS])
    Aliens.Direction = RESHAPE([1], [MAX_ALIENS])
    Aliens.Visible = RESHAPE([TRUE], [MAX_ALIENS])
    
    REM --- CREATE BLOCKS (VECTORIZED) ---
    Blocks.X = IOTA(MAX_BLOCKS) * 17 - 5
    Blocks.Y = RESHAPE([30], [MAX_BLOCKS])
    Blocks.Hits = RESHAPE([0], [MAX_BLOCKS])
    Blocks.Visible = RESHAPE([TRUE], [MAX_BLOCKS])

    REM --- INIT PLAYER AND MISSILES ---
    Player.X = 40 
    Player.Y = 35 
    Player.Direction = 0
    Player.Visible = TRUE 
    Player.RespawnTimer = 0
    PlayerMissile.Visible = FALSE 
    PlayerMissile.IsFiring = FALSE
    AlienMissile.Visible = FALSE 
    AlienMissile.IsFiring = FALSE
ENDSUB

SUB INIT_GAME()
    OPTION "NOPAUSE"
    CLS
    CURSOR 0
    LIVES = 3
    SCORE = 0
    DIFFICULTY = 10
    INIT_LEVEL
ENDSUB

SUB FIRE_PLAYER_MISSILE()
    PlayerMissile.X = Player.X + 2
    PlayerMissile.Y = Player.Y - 4
    PlayerMissile.Visible = TRUE
    PlayerMissile.IsFiring = TRUE
ENDSUB

SUB FIRE_ALIEN_MISSILE(i)
    AlienMissile.X = Aliens.X[i] + 2
    AlienMissile.Y = Aliens.Y[i] + 2
    AlienMissile.Visible = TRUE
    AlienMissile.IsFiring = TRUE
ENDSUB

SUB PLAYER_DIE()
    Player.Visible = FALSE
    Player.RespawnTimer = 5
    LIVES = LIVES - 1
    IF LIVES < 0 THEN GAMEOVER = 2
ENDSUB

REM ======================================================================
REM GAME LOGIC AND UPDATES
REM ======================================================================

FUNC FIND_FIRST_HIT_INDEX(Projectile, Target)
    REM Performs a vectorized collision check.
    REM Returns the index of the first target hit, or -1 if no hit.
    
    HitMask = (Projectile.Y >= Target.Y) AND (Projectile.Y < Target.Y + 2) AND (Projectile.X >= Target.X) AND (Projectile.X < Target.X + 4) AND Target.Visible
    IF HitMask NOT FALSE THEN
        IF ANY(HitMask) THEN
            ' This is a vectorized method to find the index of the first TRUE value.
            ' 1. Create a score where non-hits are high (2) and hits are low (1).
            Scores = (HitMask * -1) + 2
            ' 2. Get the sort order; indices of hits will come first.
            GradeOrder = GRADE(Scores)
            ' 3. Take the very first index from the sorted list.
            RETURN TAKE(1, GradeOrder)[0]
        ENDIF
    ENDIF    
    RETURN -1
ENDFUNC

SUB CHECK_PLAYER_MISSILE_HITS()
    FOR i = 0 TO MAX_ALIENS - 1
        IF Aliens.Visible[i] THEN
            IF (PlayerMissile.X >= Aliens.X[i] AND PlayerMissile.X < Aliens.X[i] + 4) AND (PlayerMissile.Y >= Aliens.Y[i] AND PlayerMissile.Y < Aliens.Y[i] + 2) THEN
                Aliens.Visible[i] = FALSE
                SCORE = SCORE + Aliens.Points[i]
                PRINT Aliens.Points[i]
                PlayerMissile.Visible = 0
                PlayerMissile.IsFiring = 0
                EXITFOR                           
            ENDIF
        ENDIF
    NEXT i
ENDSUB

SUB UPDATE_GAME()
    REM --- MOVE PLAYER ---
    IF Player.Visible THEN
        Player.X = Player.X + Player.Direction
        IF Player.X < 2 THEN Player.X = 2
        IF Player.X > 75 THEN Player.X = 75
        Player.Direction = 0
    ELSE
        Player.RespawnTimer = Player.RespawnTimer - 1
        IF Player.RespawnTimer = 0 THEN Player.Visible = TRUE
    ENDIF

   IF PlayerMissile.Visible THEN
       PlayerMissile.Y = PlayerMissile.Y - 1
       IF PlayerMissile.Y < 3 THEN
           PlayerMissile.Visible = FALSE
           PlayerMissile.IsFiring = FALSE
       ELSE
           CHECK_PLAYER_MISSILE_HITS 
       ENDIF
   ENDIF

    REM --- MOVE ALIENS (VECTORIZED) ---
    NextX = Aliens.X + Aliens.Direction
    WallHits = (NextX < 2) OR (NextX > 75)
    MoveDown = ANY(WallHits AND Aliens.Visible)

    IF MoveDown THEN
        Aliens.Y = Aliens.Y + Aliens.Visible
        Aliens.Direction = Aliens.Direction * -1
        IF SPEED > DIFFICULTY THEN SPEED = SPEED - 1
    ELSE
        Aliens.X = Aliens.X + (Aliens.Direction * Aliens.Visible)
    ENDIF

    REM --- ALIEN LOGIC (FIRE, GAME OVER CHECK) ---
    AliveCount = SUM(Aliens.Visible)
    
    IF AlienMissile.IsFiring = FALSE AND AliveCount > 0 THEN
        IF RND(1) * 100 < 5 THEN
            VisibleAlienIndices = []
            FOR i = 0 TO MAX_ALIENS - 1
                IF Aliens.Visible[i] THEN VisibleAlienIndices = APPEND(VisibleAlienIndices, i)
            NEXT i
            RandomIndex = INT(RND(1) * AliveCount)
            AlienToFire = VisibleAlienIndices[RandomIndex]
            FIRE_ALIEN_MISSILE(AlienToFire)
        ENDIF
    ENDIF
    
    IF ANY(Aliens.Y > 32 AND Aliens.Visible) THEN GAMEOVER = 2
    IF AliveCount = 0 AND GAMEOVER = 0 THEN GAMEOVER = 1

    REM --- MOVE ALIEN MISSILE AND CHECK HITS (VECTORIZED) ---
   IF AlienMissile.Visible THEN
       AlienMissile.Y = AlienMissile.Y + 1
       IF AlienMissile.Y > 36 THEN
           AlienMissile.Visible = FALSE
           AlienMissile.IsFiring = FALSE
       ELSE
           ' Direct collision check for the player
           IF Player.Visible AND (AlienMissile.X >= Player.X AND AlienMissile.X < Player.X + 4 AND AlienMissile.Y >= Player.Y AND AlienMissile.Y < Player.Y + 2) THEN
               PLAYER_DIE()
               AlienMissile.Visible = FALSE
               AlienMissile.IsFiring = FALSE
           ELSE
               ' Vectorized collision check for the blocks
               BlockHitIndex = FIND_FIRST_HIT_INDEX(AlienMissile, Blocks)
               IF BlockHitIndex > -1 THEN
                   Blocks.Hits[BlockHitIndex] = Blocks.Hits[BlockHitIndex] + 1
                   IF Blocks.Hits[BlockHitIndex] >= 3 THEN Blocks.Visible[BlockHitIndex] = FALSE
                   AlienMissile.Visible = FALSE
                   AlienMissile.IsFiring = FALSE
               ENDIF
           ENDIF
       ENDIF
   ENDIF
ENDSUB

REM ======================================================================
REM RENDERING
REM ======================================================================

sub print_alien(i, f, t)
    LOCATE Aliens.Y[I], Aliens.X[I]
    print ALIEN_T[t*4+f*2]
    LOCATE Aliens.Y[I]+1, Aliens.X[I]
    print ALIEN_T[t*4+1+f*2]
endsub

SUB PRINT_PLAYER()
    LOCATE Player.Y, Player.X
    PRINT PLAYER_T[0]
    LOCATE Player.Y+1, Player.X
    PRINT PLAYER_T[1]
ENDSUB

SUB PRINT_BLOCK(I, HITS)
    LOCATE Blocks.Y[I], Blocks.X[I]
    IF HITS = 0 THEN
        PRINT BLOCK_A_T[0]
        LOCATE Blocks.Y[I]+1, Blocks.X[I]
        PRINT BLOCK_A_T[1]
    ENDIF
    IF HITS = 1 THEN
        PRINT BLOCK_B_T[0]
        LOCATE Blocks.Y[I]+1, Blocks.X[I]
        PRINT BLOCK_B_T[1]
    ENDIF
    IF HITS = 2 THEN
        PRINT BLOCK_C_T[0]
        LOCATE Blocks.Y[I]+1, Blocks.X[I]
        PRINT BLOCK_C_T[1]
    ENDIF
ENDSUB

SUB RENDER_SCREEN()
    CLS
    COLOR 7, 0
    LOCATE 1, 1
    PRINT "LIVES: " + STR$(LIVES);
    LOCATE 1, 35
    PRINT "JD INVADERS";
    LOCATE 1, 68
    PRINT "SCORE: " + STR$(SCORE);
    LOCATE 2, 1
    PRINT "--------------------------------------------------------------------------------";

    REM -- DRAW ALIENS --
    FOR I = 0 TO MAX_ALIENS - 1
        IF Aliens.Visible[I] = 1 THEN
            Spec = Aliens.Species[I]
            IF Spec = 0 THEN 
               COLOR 1, 0 ' Red
	        ENDIF
            IF Spec = 1 THEN 
               COLOR 2, 0 ' Green
	        ENDIF
            IF Spec = 2 THEN 
               COLOR 6, 0 ' Cyan
            ENDIF
	    PRINT_ALIEN I, FRAME, Spec
        ENDIF
    NEXT I
    
    REM -- DRAW BLOCKS --
    COLOR 1, 0 ' Gray
    FOR I = 0 TO MAX_BLOCKS - 1 
        IF Blocks.Visible[I] = 1 THEN 
            HITS = Blocks.Hits[I] 
            PRINT_BLOCK I, HITS 
        ENDIF
    NEXT I 

    REM -- DRAW PLAYER --
    IF Player.Visible = 1 THEN
        COLOR 4, 0 ' Blue
	    PRINT_PLAYER 
    ENDIF
    
    REM -- DRAW MISSILES --
    IF PlayerMissile.Visible = 1 THEN
        COLOR 7, 0 ' White
        LOCATE PlayerMissile.Y, PlayerMissile.X
	    PRINT P_MISSILE$;
    ENDIF
    IF AlienMissile.Visible = 1 THEN
        COLOR 1, 0 ' Red
        LOCATE AlienMissile.Y, AlienMissile.X
	    PRINT A_MISSILE$;
    ENDIF

    REM -- DRAW GAME OVER MESSAGES --
    IF GAMEOVER > 0 THEN
        COLOR 7, 0
        LOCATE 20, 35
	PRINT "GAME OVER"
        IF GAMEOVER = 1 THEN
            LOCATE 22, 28
	    PRINT "PRESS 'N' FOR NEXT LEVEL"
        ELSE
            LOCATE 22, 36
	    PRINT "YOU LOST"
            LOCATE 23, 29
	    PRINT "PRESS 'N' TO RESTART"
        ENDIF
    ENDIF
ENDSUB


REM ======================================================================
REM MAIN GAME LOOP
REM ======================================================================
SUB GAME_LOOP()
MAIN_LOOP:
    
    K$ = INKEY$()
    IF K$ = "z" or asc(k$) = 27 THEN 
	GOTO END_GAME
    ENDIF
    IF GAMEOVER > 0 THEN
        IF K$ = "n" THEN
            IF GAMEOVER = 1 THEN
                LIVES = LIVES + 1
                INIT_LEVEL
            ELSE
                INIT_GAME
            ENDIF
        ENDIF
    ENDIF

    IF Player.Visible = 1 THEN
        IF K$ = "a" THEN 
    	    Player.Direction = -1
    	ENDIF
        IF K$ = "d" THEN 
            Player.Direction = 1
        ENDIF
        IF K$ = " " AND PlayerMissile.IsFiring = 0 THEN 
            FIRE_PLAYER_MISSILE
        ENDIF
    ENDIF
    
    IF GAMEOVER = 0 THEN
        SFRAME = SFRAME + 1
        IF SFRAME > SPEED THEN
            UPDATE_GAME
	        RENDER_SCREEN
            SFRAME = 0
            FRAME = FRAME + 1
            IF FRAME > 1 then
            FRAME = 0
            ENDIF
        ENDIF
    ENDIF

    SLEEP 20
    GOTO MAIN_LOOP

END_GAME:
    CURSOR 1
    CLS
ENDSUB

INIT_GAME
GAME_LOOP
