I wanted to make a corona-virus shooter, but this may be inappropriate. So a Meteorite shooter it is.
Code: Select all
#Include Once "fbgfx.bi"
'===============================================================================
Type int2d
As Integer x, y
Declare Constructor
Declare Constructor(x As Integer, y As Integer)
Declare Operator Cast () As String
End Type
Constructor int2d
End Constructor
Constructor int2d(x As Integer, y As Integer)
This.x = x : This.y = y
End Constructor
' "x, y"
Operator int2d.cast () As String
Return Str(x) & "," & Str(y)
End Operator
Operator = (a As int2d, b As int2d) As boolean
If a.x <> b.x Then Return false
If a.y <> b.y Then Return false
Return true
End Operator
Operator <> (a As int2d, b As int2d) As boolean
If a.x = b.x And a.y = b.y Then Return false
Return true
End Operator
' a + b
Operator + (a As int2d, b As int2d) As int2d
Return Type(a.x + b.x, a.y + b.y)
End Operator
' a - b
Operator - (a As int2d, b As int2d) As int2d
Return Type(a.x - b.x, a.y - b.y)
End Operator
' -a
Operator - (a As int2d) As int2d
Return Type(-a.x, -a.y)
End Operator
' a * b
Operator * (a As int2d, b As int2d) As int2d
Return Type(a.x * b.x, a.y * b.y)
End Operator
' a * mul
Operator * (a As int2d, mul As Integer) As int2d
Return Type(a.x * mul, a.y * mul)
End Operator
' a \ b
Operator \ (a As int2d, b As int2d) As int2d
Return Type(a.x \ b.x, a.y \ b.y)
End Operator
' a \ div
Operator \ (a As int2d, divider As Integer) As int2d
Return Type(a.x \ divider, a.y \ divider)
End Operator
'===============================================================================
Type screen_type ' pretty dumb graphics class
Private:
'dim as fb.Image ptr pFbImg
Dim As Integer wPage, vPage 'work page, visible page
Public:
'dim as long w, h 'size
Dim As int2d size
Dim As int2d cntr 'center
Dim As int2d edge
Dim As Long gfxFlags
Declare Constructor(w As Long, h As Long, flags As Long)
Declare Sub activate()
Declare Sub flipPage()
Declare Sub clearScreen(colour As ULong)
End Type
Constructor screen_type(w As Long, h As Long, flags As Long)
size = int2d(w, h)
cntr = int2d(w \ 2, h \ 2)
edge = int2d(w - 1, h - 1)
gfxFlags = flags
End Constructor
Sub screen_type.activate()
ScreenRes size.x, size.y, 32, 2, gfxFlags
Width size.x \ 8, size.y \ 16 'bigger font
'pFbImg = ImageCreate(w, h)
wPage = 0
vPage = 0
ScreenSet wPage, vPage
wPage = 1
End Sub
Sub screen_type.flipPage()
ScreenSet wPage, vPage
wPage Xor= 1
vPage Xor= 1
End Sub
Sub screen_type.clearScreen(colour As ULong)
Line(0, 0)-(size.x - 1, size.y - 1), colour, bf
End Sub
'===============================================================================
'TODO:
' use bit-field
' union of pos & x,y
' add clip
' make real class
#Define MOUSE_IDLE 0
#Define MOUSE_POS_CHANGED 1
#Define MOUSE_LB_PRESSED 2
#Define MOUSE_LB_RELEASED 3
#Define MOUSE_RB_PRESSED 4
#Define MOUSE_RB_RELEASED 5
#Define MOUSE_MB_PRESSED 6
#Define MOUSE_MB_RELEASED 7
#Define MOUSE_WHEEL_UP 8
#Define MOUSE_WHEEL_DOWN 9
Type mouseType
Pos As int2d
posChange As int2d
wheel As Integer
buttons As Integer
lb As Integer 'left button
rb As Integer 'right button
mb As Integer 'middle button
End Type
Function handleMouse(ByRef mouse As mouseType) As Integer
Static previous As mouseType
Dim As Integer change = MOUSE_IDLE
GetMouse mouse.pos.x, mouse.pos.y, mouse.wheel, mouse.buttons
If (mouse.buttons = -1) Then
mouse.lb = 0
mouse.rb = 0
mouse.mb = 0
mouse.posChange.x = 0
mouse.posChange.y = 0
Else
mouse.lb = (mouse.buttons And 1)
mouse.rb = (mouse.buttons ShR 1) And 1
mouse.mb = (mouse.buttons ShR 2) And 1
'if (previous.pos.x <> mouse.pos.x or previous.pos.y <> mouse.pos.y) then
If previous.pos <> mouse.pos Then
change = MOUSE_POS_CHANGED
End If
'mouse.posChange.x = mouse.pos.x - previous.pos.x
'mouse.posChange.y = mouse.pos.y - previous.pos.y
mouse.posChange = mouse.pos - previous.pos
If (previous.buttons <> mouse.buttons) Then
If (previous.lb = 0 And mouse.lb = 1) Then change = MOUSE_LB_PRESSED
If (previous.lb = 1 And mouse.lb = 0) Then change = MOUSE_LB_RELEASED
If (previous.rb = 0 And mouse.rb = 1) Then change = MOUSE_RB_PRESSED
If (previous.rb = 1 And mouse.rb = 0) Then change = MOUSE_RB_RELEASED
If (previous.mb = 0 And mouse.mb = 1) Then change = MOUSE_MB_PRESSED
If (previous.mb = 1 And mouse.mb = 0) Then change = MOUSE_MB_RELEASED
End If
If (mouse.wheel > previous.wheel) Then change = MOUSE_WHEEl_UP
If (mouse.wheel < previous.wheel) Then change = MOUSE_WHEEl_DOWN
previous = mouse
End If
Return change
End Function
'===============================================================================
Type sgl2d
As Single x, y
Declare Constructor
Declare Constructor(x As Single, y As Single)
Declare Operator Cast() As String
Declare Function cross(b As sgl2d) As Single
Declare Function lengthSqrd() As Single
Declare Function dist(b As sgl2d) As Single
Declare Function distSqrd(b As sgl2d) As Single
Declare Function normalise() As sgl2d
End Type
Constructor sgl2d
End Constructor
Constructor sgl2d(x As Single, y As Single)
This.x = x : This.y = y
End Constructor
Function sgl2d.cross(b As sgl2d) As Single
Return This.x * b.y - This.y * b.x
End Function
Function sgl2d.lengthSqrd() As Single
Return (This.x * This.x) + (This.y * This.y)
End Function
Function sgl2d.dist(b As sgl2d) As Single
Dim As Single dx = This.x - b.x
Dim As Single dy = This.y - b.y
Return Sqr((dx * dx) + (dy * dy))
End Function
Function sgl2d.distSqrd(b As sgl2d) As Single
Dim As Single dx = This.x - b.x
Dim As Single dy = This.y - b.y
Return (dx * dx) + (dy * dy)
End Function
Function sgl2d.normalise() As sgl2d
Dim As Single length = Sqr((This.x * This.x) + (This.y * This.y))
Return sgl2d(This.x / length, This.y / length)
End Function
' "x, y"
Operator sgl2d.cast() As String
Return Str(x) & "," & Str(y)
End Operator
'---- operators ---
' distance / lenth
Operator Len (a As sgl2d) As Single
Return Sqr(a.x * a.x + a.y * a.y)
End Operator
' a = b ?
Operator = (a As sgl2d, b As sgl2d) As boolean
If a.x <> b.x Then Return false
If a.y <> b.y Then Return false
Return true
End Operator
' a != b ?
Operator <> (a As sgl2d, b As sgl2d) As boolean
If a.x = b.x And a.y = b.y Then Return false
Return true
End Operator
' a + b
Operator + (a As sgl2d, b As sgl2d) As sgl2d
Return Type(a.x + b.x, a.y + b.y)
End Operator
' a - b
Operator - (a As sgl2d, b As sgl2d) As sgl2d
Return Type(a.x - b.x, a.y - b.y)
End Operator
' -a
Operator - (a As sgl2d) As sgl2d
Return Type(-a.x, -a.y)
End Operator
' a * b
Operator * (a As sgl2d, b As sgl2d) As sgl2d
Return Type(a.x * b.x, a.y * b.y)
End Operator
' a * mul
Operator * (a As sgl2d, mul As Single) As sgl2d
Return Type(a.x * mul, a.y * mul)
End Operator
' a / div
Operator / (a As sgl2d, div As Single) As sgl2d
Return Type(a.x / div, a.y / div)
End Operator
'===============================================================================
Declare Function toInt2d Overload (v As sgl2d) As int2d
Function toInt2d(v As sgl2d) As int2d
Return int2d(Int(v.x), Int(v.y))
End Function
Declare Function toCint2d Overload (v As sgl2d) As int2d
Function toCint2d(v As sgl2d) As int2d
Return int2d(CInt(v.x), CInt(v.y))
End Function
Function toSgl2d(v As int2d) As sgl2d
Return sgl2d(v.x, v.y)
End Function
'===============================================================================
'simple event timer
Type timer_type
Private:
Dim As Double tEnd
Dim As Double tStart
Dim As Double tSpan
Dim As boolean active = false
Public:
Declare Sub start(duration As Double)
Declare Sub stop_()
Declare Function isActive() As boolean
Declare Function ended() As boolean
Declare Function timeLeft() As Double
'~ declare sub restart()
End Type
Sub timer_type.start(duration As Double)
tStart = Timer()
tSpan = duration
tEnd = tStart + tSpan
active = true
End Sub
Sub timer_type.stop_()
active = false
End Sub
'does NOT update the timer status
Function timer_type.isActive() As boolean
Return active
End Function
'check only once in loop! Inactive after ended.
Function timer_type.ended() As boolean
If active = false Then Return false
If Timer() >= tEnd Then
active = false
Return true
Else
Return false
End If
End Function
Function timer_type.timeLeft() As Double
Dim As Double tLeft = tEnd - Timer
Return IIf(tLeft < 0, 0, tLeft)
End Function
'===============================================================================
Type loop_timer_type
Private:
Dim As Double tStart
Dim As Double tNow
Dim As Double tPrev
Dim As Double dt
'dim as double dtAvg
Dim As Integer pauseFlag = 0
Public:
Declare Sub init()
Declare Sub update()
Declare Sub togglePause()
Declare Function isPaused() As Integer
Declare Function getDt() As Double
Declare Function getRunTime() As Double
End Type
Sub loop_timer_type.init()
tStart = Timer
tNow = tStart
tPrev = tNow
dt = 0.0
pauseFlag = 0
'dtAvg = 0.0
End Sub
Sub loop_timer_type.update()
tPrev = tNow
tNow = Timer
dt = tNow - tPrev
If pauseFlag = 1 Then dt = 0
'dtAvg = 0.95 * dtAvg + 0.05 * dt
End Sub
Sub loop_timer_type.togglePause()
pauseFlag Xor= 1
End Sub
Function loop_timer_type.isPaused() As Integer
Return pauseFlag
End Function
Function loop_timer_type.getDt() As Double
Return dt
End Function
Function loop_timer_type.getRunTime() As Double
Return Timer - tStart
End Function
'===============================================================================
Const As Single V_BULLET = 400 'pixels/s
Type bullet_type
Dim As sgl2d pos_
Dim As sgl2d vel
Dim As Single dist 'travel distance
End Type
'list can grow, but never shrink, for performance, non-sorted
Type bullet_list
Private:
Dim As Integer numItems
Dim As bullet_type item(Any)
Public:
Dim As Single radius = 5
Declare Constructor(startSize As Integer)
Declare Constructor()
Declare Destructor()
Declare Sub Add(pos_ As sgl2d, dir_ As sgl2d)
Declare Sub del(index As Integer)
'not essential methods
Declare Function numAlloc() As Integer
Declare Function numInUse() As Integer
Declare Function getPos(index As Integer) As sgl2d
Declare Sub show()
'non-list methods
Declare Sub draw_()
Declare Sub update(dt As Double, maxDist As Single)
End Type
Constructor bullet_list(startSize As Integer)
If startSize > 0 Then
ReDim item(startSize - 1)
End If
End Constructor
Constructor bullet_list()
This.constructor(0)
End Constructor
Destructor bullet_list()
Erase(item)
End Destructor
Sub bullet_list.add(pos_ As sgl2d, dir_ As sgl2d)
Dim As Integer ub = UBound(item)
'if list is full, increase list size by 1
If numItems = ub + 1 Then
ReDim Preserve item(numItems)
End If
item(numItems).pos_ = pos_
item(numItems).vel = dir_ * V_BULLET
item(numItems).dist = 0
numItems += 1
End Sub
Sub bullet_list.del(index As Integer)
'check valid index
'if index >= 0 andalso index < numItems then
'move last to del pos
item(index) = item(numItems - 1)
numItems -= 1
'end if
End Sub
Function bullet_list.numAlloc() As Integer
Return UBound(item) + 1
End Function
Function bullet_list.numInUse() As Integer
Return numItems
End Function
Function bullet_list.getPos(index As Integer) As sgl2d
Return item(index).pos_
End Function
'for debugging
Sub bullet_list.show()
Print "--- " & numInUse() & " / " & numAlloc() & " ---"
For i As Integer = 0 To numItems - 1
Print i, item(i).pos_.x, item(i).pos_.y
Next
End Sub
Sub bullet_list.draw_()
For i As Integer = 0 To numItems - 1
With item(i)
Circle(.pos_.x, .pos_.y), radius, RGB(200,200,0),,,,f
End With
Next
End Sub
'update positions, remove to too far
Sub bullet_list.update(dt As Double, maxDist As Single)
For i As Integer = 0 To numItems - 1
With item(i)
.dist += V_BULLET * dt
If .dist > maxDist Then
del(i)
Else
.pos_ += .vel * dt
End If
End With
Next
End Sub
'===============================================================================
Const As Single V_ENEMY_MAX = 200 'pixels/s
Const As Single V_ENEMY_MIN = 50 'pixels/s
Type enemy_type
Dim As sgl2d pos_
Dim As sgl2d vel 'velocity vector
End Type
'list can grow, but never shrink, for performance, non-sorted
Type enemy_list
Private:
Dim As Integer numItems
Dim As enemy_type item(Any)
Public:
Dim As Single radius = 10
Declare Constructor(startSize As Integer)
Declare Constructor()
Declare Destructor()
Declare Sub Add(pos_ As sgl2d, dir_ As sgl2d)
Declare Sub del(index As Integer)
'not essential methods
Declare Function numAlloc() As Integer
Declare Function numInUse() As Integer
Declare Function getPos(index As Integer) As sgl2d
Declare Sub show()
'non-list methods
Declare Sub draw_()
Declare Function update(dt As Double, targetPos As sgl2d, targetRadius As Single) As Integer
End Type
Constructor enemy_list(startSize As Integer)
If startSize > 0 Then
ReDim item(startSize - 1)
End If
End Constructor
Constructor enemy_list()
This.constructor(0)
End Constructor
Destructor enemy_list()
Erase(item)
End Destructor
Sub enemy_list.add(pos_ As sgl2d, dir_ As sgl2d)
Dim As Integer ub = UBound(item)
'if list is full, increase list size by 1
If numItems = ub + 1 Then
ReDim Preserve item(numItems)
End If
Dim As Single speed = V_ENEMY_MIN + Rnd * (V_ENEMY_MAX - V_ENEMY_MIN)
item(numItems).pos_ = pos_
item(numItems).vel = dir_ * speed
numItems += 1
End Sub
Sub enemy_list.del(index As Integer)
'check valid index
'if index >= 0 andalso index < numItems then
'move last to del pos
item(index) = item(numItems - 1)
numItems -= 1
'end if
End Sub
Function enemy_list.numAlloc() As Integer
Return UBound(item) + 1
End Function
Function enemy_list.numInUse() As Integer
Return numItems
End Function
Function enemy_list.getPos(index As Integer) As sgl2d
Return item(index).pos_
End Function
'for debugging
Sub enemy_list.show()
Print "--- " & numInUse() & " / " & numAlloc() & " ---"
For i As Integer = 0 To numItems - 1
Print i, item(i).pos_.x, item(i).pos_.y
Next
End Sub
Sub enemy_list.draw_()
For i As Integer = 0 To numItems - 1
With item(i)
Circle(.pos_.x, .pos_.y), radius, RGB(200,100,0),,,,f
End With
Next
End Sub
'update positions, remove to too far
Function enemy_list.update(dt As Double, targetPos As sgl2d, targetRadius As Single) As Integer
Dim As Integer hitCount = 0
For i As Integer = 0 To numItems - 1
With item(i)
.pos_ += .vel * dt
Dim As Single dist = .pos_.dist(targetPos)
If dist < (radius + targetRadius) Then
del(i)
hitCount += 1
End If
End With
Next
Return hitCount
End Function
'===============================================================================
Const As String KEY_ESC = Chr(27)
Const SW = 800, SH = 600
Const MAX_DIST = Sqr((SW / 2) ^ 2 + (SH / 2) ^ 2)
Const SPAWN_DIST = MAX_DIST * 1.2
Const PI = Atn(1) * 4
Dim Shared As screen_type scr = screen_type(SW, SH, fb.GFX_ALPHA_PRIMITIVES) '1024, 768
Declare Function main() As String
scr.activate() 'set screen
Randomize Timer
Dim As String quitStr
SetMouse( , , 0) 'hide cursor
quitStr = main()
SetMouse( , , 1) 'restore cursor
Print "End: " & quitStr
Sleep 5000
End
Function main() As String
Dim As mouseType mouse
Dim As Integer mouseEvent
Dim As sgl2d aimPos, deltaPosNorm, barrelPos
Dim As sgl2d gunPos = toSgl2d(scr.size) / 2 'centre
Dim As Single gunRadius = 15
Dim As timer_type trigTmr
Dim As bullet_list bulletList
Dim As timer_type spawnTmr
Dim As enemy_list enemyList
Dim As timer_type hitTmr
Dim As loop_timer_type loopTimer
Dim As Integer bulletCount, enemyCount, hitCount, damage, health = 10
Dim As ULong gunColor = RGB(80, 80, 80)
Dim As ULong barrelColor = RGB(160, 160, 160)
Dim As ULong aimColor = RGB(80, 160, 80)
Dim As Double spawnTime = 2.0
Dim As String quitStr = ""
'main loop
spawnTmr.start(spawnTime)
loopTimer.init()
While quitStr = ""
If InKey = KEY_ESC Then quitStr = "Quit, user abort request"
mouseEvent = handleMouse(mouse)
If mouse.buttons <> -1 Then aimPos = toSgl2d(mouse.pos)
deltaPosNorm = (aimPos - gunPos).normalise()
barrelPos = gunPos + deltaPosNorm * 24
If mouse.lb = 1 Then
If trigTmr.isActive = false Then
trigTmr.start(0.2)
bulletList.add(barrelPos, deltaPosNorm)
bulletCount += 1
End If
End If
If spawnTmr.ended() Then
spawnTmr.start(spawnTime)
enemyCount += 1
If enemyCount Mod 10 = 0 Then spawnTime *= 0.9
Dim As Single angle = Rnd * 2 * PI
Dim As sgl2d relPos = sgl2d(Cos(angle) * SPAWN_DIST, Sin(angle) * SPAWN_DIST)
enemyList.add(relPos + gunPos, -relPos.normalise())
End If
If hitTmr.ended() Then
gunColor = RGB(80, 80, 80)
barrelColor = RGB(160, 160, 160)
End If
trigTmr.ended() 'update timer
bulletList.update(loopTimer.getdt(), MAX_DIST)
damage = enemyList.update(loopTimer.getdt(), gunPos, gunRadius)
health -= damage
If damage > 0 Then
gunColor = RGB(160, 80, 80)
barrelColor = RGB(240, 160, 160)
hitTmr.start(0.2)
End If
'check bullet <-> enemy collisions
Dim As Integer iBullet = 0
While iBullet < bulletList.numInUse()
Dim As Integer iEnemy = 0
While iEnemy < enemyList.numInUse()
Dim As sgl2d bulletPos = bulletList.getPos(iBullet)
Dim As Single dist = bulletPos.dist(enemyList.getPos(iEnemy))
If dist < (bulletList.radius + enemyList.radius) Then
bulletList.del(iBullet)
enemyList.del(iEnemy)
'list changed start over
'iBullet = 0
'iEnemy = 0
'list changed recheck these indices, is this right?
If iBullet > 0 Then iBullet -= 1
If iEnemy > 0 Then iEnemy -= 1
hitCount += 1
End If
iEnemy += 1
Wend
iBullet += 1
Wend
ScreenLock
scr.clearScreen(RGBA(0, 0, 0, 255))
'draw gun
Circle(gunPos.x, gunPos.y), gunRadius, gunColor,,,,f
'draw target aim
Circle(aimPos.x, aimPos.y), 10, aimColor,,,0.1,f
Circle(aimPos.x, aimPos.y), 10, aimColor,,,10.0,f
'draw gun barrel
barrelPos = gunPos
For i As Integer = 0 To 4
Circle(barrelPos.x, barrelPos.y), 5, barrelColor,,,,f
barrelPos += (deltaPosNorm * 6)
Next
'draw all existing bullets & enemies
bulletList.draw_()
enemyList.draw_()
Locate 1,1: Print "Press left mouse button to fire"
Locate 2,1: Print "Bullet list: " & bulletList.numInUse() & " / " & bulletList.numAlloc()
Locate 3,1: Print "Enemy list: " & enemyList.numInUse() & " / " & enemyList.numAlloc()
Locate 4,1: Print "Health: " & health
Locate 5,1: Print "Hits: " & hitCount
ScreenUnLock
Sleep 1
loopTimer.update()
If health <= 0 Then quitStr = "Game over!"
Wend
Return quitStr
End Function
It gets more difficult after time. My best score so far: 123
