Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Space Shooter with shapes
#1
Just playing around with N7 again and did this sample with shapes. 

Code:
' ============================================================================
' SPACE SHOOTER - NaaLaa / N7
' ----------------------------------------------------------------------------
'
' Controls:
'   Arrow keys / WASD  - Move ship
'   Space / Z          - Shoot
'   Space / Enter      - Start / continue
'   ESC                - Quit
' ============================================================================
#win32
set window "Space Shooter", 800, 600
set redraw off
set mouse false

' ── constants ────────────────────────────────────────────────────────────────
constant SCREEN_W = 800
constant SCREEN_H = 600

constant MAX_BULLETS       = 64
constant MAX_ENEMY_BULLETS = 128
constant MAX_ENEMIES       = 64
constant MAX_PARTICLES     = 256
constant MAX_STARS         = 120

constant PLAYER_SPEED      = 5
constant BULLET_SPEED      = 10
constant ENEMY_BULLET_SPD  = 2.2
constant SHOOT_COOLDOWN    = 10
constant INVINCIBLE_FRAMES = 120
constant SPAWN_INVINCIBLE  = 90

' Game states
constant ST_MENU     = 0
constant ST_PLAYING  = 1
constant ST_BANNER   = 2
constant ST_GAMEOVER = 3

' ── globals ──────────────────────────────────────────────────────────────────
visible bullets    = dim(MAX_BULLETS)
visible ebullets   = dim(MAX_ENEMY_BULLETS)
visible enemies    = dim(MAX_ENEMIES)
visible particles  = dim(MAX_PARTICLES)
visible stars      = dim(MAX_STARS)

visible enemy_count = 0
visible score    = 0
visible lives    = 3
visible wave     = 1
visible px       = SCREEN_W / 2
visible py       = SCREEN_H - 60
visible shoot_cd = 0
visible invincible   = 0
visible gstate       = ST_MENU
visible banner_timer = 0

' Track previous space state for single-press detection on menu/gameover
visible spacePrev = false

' ── helpers ──────────────────────────────────────────────────────────────────
randomize clock()

' Random float in [lo, hi]
function randf(lo, hi)
    return lo + rnd() * (hi - lo)
endfunc

function clamp(v, lo, hi)
    if v < lo then return lo
    if v > hi then return hi
    return v
endfunc

function absf(v)
    if v < 0 then return -v
    return v
endfunc

' ── object factories ─────────────────────────────────────────────────────────
function makeBullet(x, y)
    return [x: x, y: y, alive: true]
endfunc

function makeEBullet(x, y, vx, vy)
    return [x: x, y: y, vx: vx, vy: vy, alive: true]
endfunc

function makeParticle(x, y, vx, vy, r, g, b)
    return [x: x, y: y, vx: vx, vy: vy, life: 1.0, r: r, g: g, b: b]
endfunc

' ── particle spawning ────────────────────────────────────────────────────────
function spawn_particles(x, y, r, g, b, n)
    i = 0
    while i < n
        ' find a free slot
        j = 0
        placed = false
        while j < MAX_PARTICLES and not placed
            p = particles[j]
            if p = 0 or p.life <= 0 then
                angle = randf(0, 2 * PI)
                spd   = randf(1.0, 4.0)
                particles[j] = makeParticle(x, y, cos(angle) * spd, sin(angle) * spd, r, g, b)
                placed = true
            endif
            j = j + 1
        wend
        i = i + 1
    wend
endfunc

' ── stars (parallax background) ──────────────────────────────────────────────
function init_stars()
    for i = 0 to MAX_STARS - 1
        stars[i] = [
            x: randf(0, SCREEN_W),
            y: randf(0, SCREEN_H),
            speed: randf(0.3, 1.2),
            brightness: floor(randf(80, 220))
        ]
    next
endfunc

function scroll_stars()
    for i = 0 to MAX_STARS - 1
        s = stars[i]
        s.y = s.y + s.speed
        if s.y > SCREEN_H then
            s.y = 0
            s.x = randf(0, SCREEN_W)
        endif
    next
endfunc

' ── enemy type table (data + drawing strategy) ──────────────────────────────
' Each entry: { r, g, b, render: function(e, x, y, hw, hh) }
visible enemyTypes = [
    [r: 220, g: 80, b: 80, render: function(e, x, y, hw, hh)
        ' Inverted triangle
        for dy = -hh to hh
            half = floor(hw * (1 - (dy + hh) / (2 * hh)))
            draw line x - half, y + dy, x + half, y + dy
        next
    endfunc],
    [r: 240, g: 190, b: 60, render: function(e, x, y, hw, hh)
        draw rect x - hw, y - hh, e.w, e.h, true
        set color 255, 240, 80, 200
        draw rect x - 8, y - 4, 16, 8, true
    endfunc],
    [r: 160, g: 120, b: 240, render: function(e, x, y, hw, hh)
        ' Filled circle
        draw ellipse x, y, hw, hh, true
    endfunc]
]

' ── enemies ──────────────────────────────────────────────────────────────────
function spawn_enemies(wv)
    enemy_count = 0
    nCols = 8
    nRows = 1 + wv
    if nRows > 4 then nRows = 4
    base_vx = 0.45 + wv * 0.12
    for r = 0 to nRows - 1
        for c = 0 to nCols - 1
            if enemy_count < MAX_ENEMIES then
                dir = -1
                if c % 2 = 0 then dir = 1
                enemies[enemy_count] = [
                    x: 70 + c * 85,
                    y: 50 + r * 50,
                    vx: dir * base_vx,
                    w: 28, h: 22,
                    hp: 1 + wv / 3,
                    typ: r % 3,
                    alive: true,
                    shoot_timer: 180 + floor(rnd() * 240)
                ]
                enemy_count = enemy_count + 1
            endif
        next
    next
endfunc

function reset_game()
    score = 0
    lives = 3
    wave = 1
    px = SCREEN_W / 2
    py = SCREEN_H - 60
    shoot_cd = 0
    invincible = SPAWN_INVINCIBLE
    for i = 0 to MAX_BULLETS - 1
        bullets[i] = 0
    next
    for i = 0 to MAX_ENEMY_BULLETS - 1
        ebullets[i] = 0
    next
    for i = 0 to MAX_PARTICLES - 1
        particles[i] = 0
    next
    for i = 0 to MAX_ENEMIES - 1
        enemies[i] = 0
    next
    spawn_enemies(wave)
endfunc

' ── update ───────────────────────────────────────────────────────────────────
function update_game()
    ' Movement
    if keydown(KEY_LEFT)  or keydown(KEY_A) then px = px - PLAYER_SPEED
    if keydown(KEY_RIGHT) or keydown(KEY_D) then px = px + PLAYER_SPEED
    if keydown(KEY_UP)    or keydown(KEY_W) then py = py - PLAYER_SPEED
    if keydown(KEY_DOWN)  or keydown(KEY_S) then py = py + PLAYER_SPEED
    px = clamp(px, 16, SCREEN_W - 16)
    py = clamp(py, 16, SCREEN_H - 16)

    ' Shoot
    if shoot_cd > 0 then shoot_cd = shoot_cd - 1
    if (keydown(KEY_SPACE) or keydown(KEY_Z)) and shoot_cd = 0 then
        for i = 0 to MAX_BULLETS - 1
            if bullets[i] = 0 or not bullets[i].alive then
                bullets[i] = makeBullet(px, py - 16)
                ' Double shot at higher waves
                if wave >= 3 and i + 1 < MAX_BULLETS then
                    if bullets[i + 1] = 0 or not bullets[i + 1].alive then
                        bullets[i + 1] = makeBullet(px + 10, py - 10)
                    endif
                endif
                break
            endif
        next
        shoot_cd = SHOOT_COOLDOWN
    endif

    ' Move player bullets
    for i = 0 to MAX_BULLETS - 1
        b = bullets[i]
        if b <> 0 and b.alive then
            b.y = b.y - BULLET_SPEED
            if b.y < -10 then b.alive = false
        endif
    next

    ' Move enemy bullets
    for i = 0 to MAX_ENEMY_BULLETS - 1
        b = ebullets[i]
        if b <> 0 and b.alive then
            b.x = b.x + b.vx
            b.y = b.y + b.vy
            if b.y > SCREEN_H + 10 or b.x < -10 or b.x > SCREEN_W + 10 then b.alive = false
        endif
    next

    ' Move enemies, wall bounce
    hit_wall = false
    for i = 0 to enemy_count - 1
        e = enemies[i]
        if e <> 0 and e.alive then
            e.x = e.x + e.vx
            if (e.x + e.w / 2 > SCREEN_W - 10 and e.vx > 0) or (e.x - e.w / 2 < 10 and e.vx < 0) then
                hit_wall = true
            endif
        endif
    next
    if hit_wall then
        for i = 0 to enemy_count - 1
            e = enemies[i]
            if e <> 0 and e.alive then
                e.vx = -e.vx
                e.y  = e.y + 14
            endif
        next
    endif

    ' Enemy shooting
    for i = 0 to enemy_count - 1
        e = enemies[i]
        if e <> 0 and e.alive then
            e.shoot_timer = e.shoot_timer - 1
            if e.shoot_timer <= 0 then
                e.shoot_timer = 150 + floor(rnd() * 200) - wave * 8
                if e.shoot_timer < 60 then e.shoot_timer = 60
                dx = px - e.x
                dy = py - e.y
                dist = sqr(dx * dx + dy * dy) + 0.001
                spd = ENEMY_BULLET_SPD + wave * 0.1
                for j = 0 to MAX_ENEMY_BULLETS - 1
                    if ebullets[j] = 0 or not ebullets[j].alive then
                        ebullets[j] = makeEBullet(e.x, e.y, dx / dist * spd, dy / dist * spd)
                        break
                    endif
                next
            endif
        endif
    next

    ' Bullet-enemy collisions
    for bi = 0 to MAX_BULLETS - 1
        b = bullets[bi]
        if b <> 0 and b.alive then
            for ei = 0 to enemy_count - 1
                e = enemies[ei]
                if e <> 0 and e.alive then
                    if absf(b.x - e.x) < e.w / 2 + 3 and absf(b.y - e.y) < e.h / 2 + 3 then
                        b.alive = false
                        e.hp = e.hp - 1
                        t = enemyTypes[e.typ]
                        if e.hp <= 0 then
                            e.alive = false
                            score = score + 10 + wave * 5
                            spawn_particles(e.x, e.y, t.r, t.g, t.b, 12)
                        else
                            spawn_particles(e.x, e.y, 255, 255, 255, 3)
                        endif
                        break
                    endif
                endif
            next
        endif
    next

    ' Enemy bullet-player & enemy-player collisions
    if invincible > 0 then
        invincible = invincible - 1
    else
        for i = 0 to MAX_ENEMY_BULLETS - 1
            b = ebullets[i]
            if b <> 0 and b.alive then
                if absf(b.x - px) < 14 and absf(b.y - py) < 12 then
                    b.alive = false
                    lives = lives - 1
                    invincible = INVINCIBLE_FRAMES
                    spawn_particles(px, py, 80, 160, 255, 15)
                    if lives <= 0 then
                        gstate = ST_GAMEOVER
                        return
                    endif
                endif
            endif
        next
        for i = 0 to enemy_count - 1
            e = enemies[i]
            if e <> 0 and e.alive then
                if absf(e.x - px) < e.w / 2 + 10 and absf(e.y - py) < e.h / 2 + 10 then
                    lives = lives - 1
                    invincible = INVINCIBLE_FRAMES
                    spawn_particles(px, py, 80, 160, 255, 15)
                    if lives <= 0 then
                        gstate = ST_GAMEOVER
                        return
                    endif
                endif
            endif
        next
    endif

    ' Particles
    for i = 0 to MAX_PARTICLES - 1
        p = particles[i]
        if p <> 0 and p.life > 0 then
            p.x  = p.x + p.vx
            p.y  = p.y + p.vy
            p.vy = p.vy + 0.06
            p.life = p.life - 0.025
        endif
    next

    ' Stars scroll
    scroll_stars()

    ' Wave clear?
    aliveCount = 0
    for i = 0 to enemy_count - 1
        if enemies[i] <> 0 and enemies[i].alive then aliveCount = aliveCount + 1
    next
    if aliveCount = 0 then
        wave = wave + 1
        score = score + wave * 50
        spawn_enemies(wave)
        gstate = ST_BANNER
        banner_timer = 120
        return
    endif

    ' Enemy reached bottom -> game over
    for i = 0 to enemy_count - 1
        e = enemies[i]
        if e <> 0 and e.alive then
            if e.y + e.h / 2 > SCREEN_H - 40 then
                gstate = ST_GAMEOVER
                return
            endif
        endif
    next
endfunc

' ── drawing ──────────────────────────────────────────────────────────────────
function draw_stars()
    for i = 0 to MAX_STARS - 1
        s = stars[i]
        if s <> 0 then
            b = s.brightness
            set color b, b, b, 255
            draw pixel s.x, s.y
        endif
    next
endfunc

function draw_player(blink)
    if blink then return
    ' Thruster
    set color 80, 150, 255, 120
    draw rect px - 5, py + 10, 10, 8, true
    ' Body (triangle approximated by horizontal lines)
    set color 60, 140, 255, 255
    for dy = -12 to 12
        half = floor(14 * (1 - (dy + 12) / 24))
        draw line px - half, py + dy, px + half, py + dy
    next
    ' Cockpit
    set color 180, 220, 255, 200
    draw rect px - 4, py - 7, 8, 10, true
endfunc

function draw_enemy(e)
    if e = 0 or not e.alive then return
    t = enemyTypes[e.typ]
    set color t.r, t.g, t.b, 255
    t.render(e, e.x, e.y, e.w / 2, e.h / 2)
endfunc

function draw_bullets()
    set color 100, 200, 255, 255
    for i = 0 to MAX_BULLETS - 1
        b = bullets[i]
        if b <> 0 and b.alive then
            draw rect b.x - 2, b.y - 8, 4, 14, true
        endif
    next
    set color 255, 140, 60, 255
    for i = 0 to MAX_ENEMY_BULLETS - 1
        b = ebullets[i]
        if b <> 0 and b.alive then
            draw rect b.x - 3, b.y - 3, 6, 6, true
        endif
    next
endfunc

function draw_particles()
    set additive true
    for i = 0 to MAX_PARTICLES - 1
        p = particles[i]
        if p <> 0 and p.life > 0 then
            a = floor(p.life * 255)
            if a < 0 then a = 0
            if a > 255 then a = 255
            set color p.r, p.g, p.b, a
            draw rect p.x - 2, p.y - 2, 4, 4, true
        endif
    next
    set additive false
endfunc

function draw_hud()
    set color 220, 220, 255, 255
    set caret 10, 10
    write "SCORE " + score
    set caret SCREEN_W / 2 - 40, 10
    write "WAVE " + wave
    ' Lives as small ship rectangles
    for i = 0 to lives - 1
        set color 80, 160, 255, 255
        draw rect SCREEN_W - 20 - i * 18, 8, 12, 18, true
    next
endfunc

function draw_overlay(line1, line2)
    set color 5, 5, 20, 200
    draw rect SCREEN_W / 2 - 180, SCREEN_H / 2 - 80, 360, 160, true
    set color 80, 80, 180, 255
    draw rect SCREEN_W / 2 - 180, SCREEN_H / 2 - 80, 360, 160, false
    set color 160, 200, 255, 255
    set caret SCREEN_W / 2, SCREEN_H / 2 - 40
    center line1
    set color 180, 180, 220, 255
    set caret SCREEN_W / 2, SCREEN_H / 2 - 10
    center line2
    set color 120, 220, 160, 255
    set caret SCREEN_W / 2, SCREEN_H / 2 + 30
    center "PRESS SPACE"
endfunc

' ── init ─────────────────────────────────────────────────────────────────────
init_stars()
reset_game()
gstate = ST_MENU

' ── main loop ────────────────────────────────────────────────────────────────
do
    ' Handle Space/Enter on non-playing states (single-press)
    spaceNow = keydown(KEY_SPACE) or keydown(KEY_RETURN)
    if spaceNow and not spacePrev then
        if gstate = ST_MENU or gstate = ST_GAMEOVER then
            reset_game()
            gstate = ST_PLAYING
        elseif gstate = ST_BANNER then
            gstate = ST_PLAYING
        endif
    endif
    spacePrev = spaceNow

    if gstate = ST_PLAYING then
        update_game()
    elseif gstate = ST_BANNER then
        ' Drift particles only (stars always scroll below)
        for i = 0 to MAX_PARTICLES - 1
            p = particles[i]
            if p <> 0 and p.life > 0 then
                p.x = p.x + p.vx
                p.y = p.y + p.vy
                p.life = p.life - 0.025
            endif
        next
        banner_timer = banner_timer - 1
        if banner_timer <= 0 then gstate = ST_PLAYING
    endif

    ' Stars always scroll, even on menu / game-over
    if gstate <> ST_PLAYING then scroll_stars()

    ' ── render ──
    set color 5, 5, 16, 255
    cls

    draw_stars()
    draw_bullets()
    for i = 0 to enemy_count - 1
        draw_enemy(enemies[i])
    next

    blink = false
    if invincible > 0 then
        if floor(invincible / 8) % 2 = 0 then blink = true
    endif
    draw_player(blink)

    draw_particles()
    draw_hud()

    if gstate = ST_MENU then
        draw_overlay("SPACE SHOOTER", "ARROW KEYS + SPACE")
    elseif gstate = ST_GAMEOVER then
        draw_overlay("GAME OVER", "SCORE  " + score)
    elseif gstate = ST_BANNER then
        draw_overlay("WAVE  " + wave, "GET READY")
    endif

    redraw
    fwait 60
until keydown(KEY_ESCAPE)
   
Reply
#2
What a great shooter - many thanks for sharing.....I'm of an age where this is the type of game that I grew up with back in the 1980's, and I can't get enough of them. I appreciate the effort that you have gone to to comment your code too, as I find that helps so much when trying to read through it.

Now to see if I can get past the third level this time.......
Reply
#3
NO!!! I died in level 4!! But, I did find the perfect strategy! I discovered that, if I remained as still as possible, I would be destroyed in seconds, thus reducing my pain and anguish....

"Impressive. Most impressive. Obi-Wan has taught you well. You have controlled your fear." Darth Vader. The Empire Strikes Back. 1980
Logic is the beginning of wisdom.

Live long and prosper.
Reply
#4
This is super cool and fun! Mind if I put it in the examples folder? The code is quite interesting.
Reply
#5
(05-19-2026, 06:42 AM)Marcus Wrote: This is super cool and fun! Mind if I put it in the examples folder? The code is quite interesting.

Sure go ahead and improve it it should have some mess around there :-)
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)