Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
A jam submission

lcd-assaultView game page

A tiny lcd style shooter.
Submitted by PixelPaladin (@pixel_paladin) — 7 hours, 46 minutes before the deadline

Play game

lcd-assault's itch.io page

Results

CriteriaRankScore*Raw Score
Winner#23.1673.167
Gameplay#23.3333.333
Graphics#92.8332.833
Sound#121.6671.667

Ranked from 6 ratings. Score is adjusted from raw score by the median number of ratings per game in the jam.

Source Code
Strict

Import mojo

#MOJO_IMAGE_FILTERING_ENABLED=False
#GLFW_WINDOW_TITLE="lcd-assault"

Class LCD
Field _imageRects:Int[]
Field _image:Image
Field _pageWidth:Int = 640
Field _ptrMap := New StringMap<Int>
Field _figureCount:Int = 0

Function Load:LCD(path:String)
Local lcd := New LCD

Local lcdStr:String = LoadString(path)
Local lines := lcdStr.Split("~n")
Local tempStack := New IntStack
Local currentPage:Int = 0
Local currentPtr:Int = 0

For Local l := Eachin lines
l = l.Trim()
If l.StartsWith("#") Then
Continue
Elseif l.StartsWith("IMAGE:") Then
lcd._image = LoadImage(l[6..])
Elseif l.StartsWith("PAGE_WIDTH:") Then
lcd._pageWidth = Int(l[11..])
Elseif l.StartsWith("PAGE:") Then
currentPage = Int(l[5..])
Elseif l.StartsWith("PTR:") Then
lcd.SetPtr(l[4..], currentPtr)
Else l.Contains(",")
Local tempRect := l.Split(",")
If tempRect.Length >= 4 Then
tempStack.Push(Int(tempRect[0].Trim())+currentPage*lcd._pageWidth)
tempStack.Push(Int(tempRect[1].Trim()))
tempStack.Push(Int(tempRect[2].Trim()))
tempStack.Push(Int(tempRect[3].Trim()))
Else
tempStack.Push(0)
tempStack.Push(0)
tempStack.Push(1)
tempStack.Push(1)
End
currentPtr += 1
End
End

lcd._imageRects = tempStack.ToArray()
lcd._figureCount = lcd._imageRects.Length/4

Return lcd
End

Method SetPtr:Void(name:String, value:Int)
_ptrMap.Set(name, value)
End

Method GetPtr:Int(name:String)
Return _ptrMap.Get(name)
End

Method DrawFigure:Void(id:Int)
If id < 0 Or id >= _figureCount Then Return
DrawImageRect(_image, _imageRects[id*4] Mod _pageWidth, _imageRects[id*4+1], _imageRects[id*4], _imageRects[id*4+1], _imageRects[id*4+2], _imageRects[id*4+3])
End

Method FigureX:Int(id:Int)
Return _imageRects[id*4] Mod _pageWidth
End

Method FigureY:Int(id:Int)
Return _imageRects[id*4+1]
End

Method FigureWidth:Int(id:Int)
Return _imageRects[id*4+2]
End

Method FigureHeight:Int(id:Int)
Return _imageRects[id*4+3]
End

Method FigureCenterX:Int(id:Int)
Return (_imageRects[id*4] Mod _pageWidth)+(_imageRects[id*4+1]/2)
End

Method FigureCenterY:Int(id:Int)
Return _imageRects[id*4+1]+(_imageRects[id*4+2]/2)
End

Method FigureCount:Int() Property
Return _figureCount
End

Method RenderBackground:Void()
SetColor(0, 255, 0)
SetColor(255, 255, 255)
DrawImageRect(_image, 0, 0, 0, 0, _pageWidth, _image.Height())
End

Method RenderShadow:Void()
SetAlpha(0.05)
Local pages:Int = _image.Width() / _pageWidth
For Local i := 0 Until pages
DrawImageRect(_image, 0, 0, _pageWidth*i, 0, _pageWidth, _image.Height())
End
SetAlpha(1.0)
End
End

Class MainApp Extends App

Field fnt:Font
Field lcd:LCD

Field BULLETS:Int
Field ENEMY_PARTS_SMALL:Int
Field ENEMY_PARTS_LARGE:Int
Field BOSS:Int
Field PLAYER:Int
Field BAR_SHIELD:Int
Field BAR_HEAT:Int
Field TXT_SHIELD:Int
Field TXT_HEAT:Int
Field TXT_POINTS:Int
Field TXT_SHIPS:Int
Field TXT_GAMEOVER:Int
Field BOSS_LP_BAR:Int
Field BOSS_LP_BORDER:Int

Field mode:Int
Enumerate
MODE_LOADING,
MODE_START,
MODE_GAME,
MODE_GAMEOVER
Field counter:Float = 0
Field updateSpeed:Float = DEFAULT_SPEED
Field movementCounter:Float = 0
Field movementSpeed:Float = DEFAULT_SPEED/40.0

Field soundLaser:Sound
Field soundExplosion:Sound
Field soundHurt:Sound

Const DEFAULT_SPEED:Float = 1.5

Method OnCreate:Int()
' get random seed:
Local date:=GetDate()
Seed=date[5]+date[4]*60+date[3]*60*60+date[2]*60*60*24+12345

SetChannelVolume(0, 0.25)
SetChannelVolume(1, 0.25)
SetChannelVolume(2, 0.25)
fnt = Font.Load("fnt.png", 48, 10, False) '40, 65, 10)
SetFont(fnt)
lcd = LCD.Load("lcd.txt")

BULLETS = lcd.GetPtr("bullets")
ENEMY_PARTS_SMALL = lcd.GetPtr("small")
ENEMY_PARTS_LARGE = lcd.GetPtr("large")
BOSS = lcd.GetPtr("boss")
PLAYER = lcd.GetPtr("player")
BAR_SHIELD = lcd.GetPtr("bar_shield")
BAR_HEAT = lcd.GetPtr("bar_heat")
TXT_SHIELD = lcd.GetPtr("txt_shield")
TXT_HEAT = lcd.GetPtr("txt_heat")
TXT_POINTS = lcd.GetPtr("txt_points")
TXT_SHIPS = lcd.GetPtr("txt_ships")
TXT_GAMEOVER = lcd.GetPtr("txt_gameover")
BOSS_LP_BAR = lcd.GetPtr("bar_boss")
BOSS_LP_BORDER = lcd.GetPtr("boss_lp_border")

soundLaser = LoadSound("laser.wav")
soundExplosion = LoadSound("explosion.wav")
soundHurt = LoadSound("hurt.wav")

SetMode(MODE_LOADING)

SetUpdateRate(60)
Return 0
End

Field loadingAni:Float = 0
Method OnLoading:Int()
'Cls(11, 216, 163)
'loadingAni += 1
'Print("Loading ... ("+loading+")")
DrawLoadingScreen()
Return 0
End

Method DrawLoadingScreen:Void()
SetColor(255, 243, 229)
DrawRect(-2, -2, 644, 484)

loadingAni += 2.0

Local f:Float
SetColor(0,0,0)
f = 0.5+0.5*Sin(loadingAni)
For Local i:Int = 0 Until 360 Step 72
DrawCircle(320+Sin(Float(i)+loadingAni)*30.0*f, 240+Cos(Float(i)+loadingAni)*30.0*f, 11)
End

SetColor(255,186,114)
f = 0.5+0.5*Sin(loadingAni)
For Local i:Int = 0 Until 360 Step 72
DrawCircle(320+Sin(Float(i)+loadingAni)*30.0*f, 240+Cos(Float(i)+loadingAni)*30.0*f, 10)
End
SetColor(255,255,255)
End

Method OnUpdate:Int()
#if TARGET="html5"
If KeyHit(KEY_ESCAPE) Then playerLP = 0 ; gameover = True ; gameOverWave = waveNumber ; PlaySound(soundExplosion, 2)'SetMode(MODE_START)
#Else
If KeyHit(KEY_ESCAPE) Then EndApp()
#End
If gameover Then movementSpeed = 0.15

Local tick := False
If Int(counter+updateSpeed) <> Int(counter) Then tick = True
Local movementTick := False
If Int(movementCounter+movementSpeed) <> Int(movementCounter) Then movementTick = True
counter += updateSpeed
movementCounter += movementSpeed

Select mode
Case MODE_START
If counter >= 50 Then
SetMode(MODE_GAME)
InitPlayer()
End
Case MODE_GAME
UpdatePlayer(tick)
UpdateEnemies(movementTick)
End

Return 0
End

Field waiting:Bool = True

Method OnRender:Int()
Cls()
SetColor(255, 255, 255)
DrawRect(0, 0, 640, 480)

' draw background image:
lcd.RenderBackground()

' draw shadows:
lcd.RenderShadow()

' draw shadows (points, ships):
SetAlpha(0.05)
DrawText(888, 24, 132)
DrawText(88, 524, 132)
SetAlpha(1.0)

Select mode
Case MODE_LOADING
SetMode(MODE_START)
Case MODE_START
updateSpeed = 1.5
movementSpeed = DEFAULT_SPEED/40.0
If counter < 24 Then
For Local f := 0 Until lcd.FigureCount
If lcd.FigureCenterY(f) < counter*16 Then lcd.DrawFigure(f)
End
Else
For Local f := 0 Until lcd.FigureCount
If lcd.FigureCenterY(f) > (counter-24)*16 Then lcd.DrawFigure(f)
End
End
Case MODE_GAME
If waiting Then gameover = True ; playerLP = 0
RenderPlayer()
RenderPlayerBullets()
RenderEnemies()
End

Return 0
End

Method SetMode:Void(m:Int)
mode = m
counter = 0.0
updateSpeed = DEFAULT_SPEED
End

Function Flicker:Bool()
If Millisecs() Mod 128 > 48 Then Return True
Return False
End

Function Blink:Bool()
If Millisecs() Mod 500 > 200 Then Return True
Return False
End

'### PLAYER ######################################################

Field playerPos:Int
Field playerLP:Int
Field playerHeat:Int
Field playerBullets := New Int[5*10]
Field points:Int
'Field ships:Int = 3
Field waveNumber:Int
Field gameover:Bool = False
Field gameoverCounter:Int
Field damage:Int
Field gameoverAni:Int
Field gameOverTime:Int
Field gameOverWave:Int

Method InitPlayer:Void()
playerPos = 2
playerLP = 16
playerHeat = 0
points = 0
waveNumber = 1
gameover = False
gameoverCounter = 0
damage = 0
updateSpeed = 1.5
movementSpeed = DEFAULT_SPEED/40.0

FirstWave()

' clear player bullets:
For Local i := 0 Until playerBullets.Length
playerBullets[i] = 0
End
End

Method RenderPlayer:Void()
DrawPoints()

If gameover Then
If Blink() And Not waiting Then lcd.DrawFigure(TXT_GAMEOVER)
gameoverAni += 1
For Local i:Int = 0 Until 16
If Abs(i-(Int(Float(gameoverAni)/4.0) Mod 20-2)) <= 1 Then
lcd.DrawFigure(BAR_HEAT+15-i)
lcd.DrawFigure(BAR_SHIELD+15-i)
End
End
Return
End

If Not damage Or Flicker() Then lcd.DrawFigure(PLAYER+playerPos)

' hear bar:
lcd.DrawFigure(TXT_HEAT)
For Local i := 0 Until playerHeat
'For Local i := playerHeat Until 16
lcd.DrawFigure(BAR_HEAT+i)
End

' lp bar
lcd.DrawFigure(TXT_SHIELD)
'For Local i := playerLP Until 16
For Local i := 0 Until playerLP
lcd.DrawFigure(BAR_SHIELD+i)
End
End

Method DrawPoints:Void()
' points and ships:
lcd.DrawFigure(TXT_POINTS)
lcd.DrawFigure(TXT_SHIPS)

Local visiblePoints := points
Local visibleWaveNumber := waveNumber
If gameover Then visibleWaveNumber = gameOverWave
If visiblePoints < 0 Then visiblePoints = 0
If visiblePoints > 999 Then visiblePoints = 999
If visibleWaveNumber < 0 Then visibleWaveNumber = 0
If visibleWaveNumber > 99 Then visibleWaveNumber = 99
If visiblePoints < 10 Then
DrawText(visiblePoints, 24+80, 132)
Elseif visiblePoints < 100 Then
DrawText(visiblePoints, 24+40, 132)
Else
DrawText(visiblePoints, 24, 132)
End
If visibleWaveNumber < 10 Then
DrawText(visibleWaveNumber, 524+40, 132)
Else
DrawText(visibleWaveNumber, 524, 132)
End
End

Method UpdatePlayer:Void(tick:Bool = False)
If gameover Then
If (Millisecs()-gameOverTime > 3000 Or waiting) And (KeyHit(KEY_SPACE) Or KeyHit(KEY_X) Or KeyHit(KEY_Y) Or KeyHit(KEY_Z) Or (waiting And (KeyHit(KEY_LEFT) Or KeyHit(KEY_RIGHT)))) Then SetMode(MODE_START) ; bossEnergy = 0 ; InitPlayer() ; waiting = False
UpdatePlayerBullets()
Return
End

If KeyHit(KEY_LEFT) Then
playerPos -= 1
If playerPos < 0 Then playerPos = 0
End
If KeyHit(KEY_RIGHT) Then
playerPos += 1
If playerPos > 4 Then playerPos = 4
End
If KeyHit(KEY_SPACE) Or KeyHit(KEY_X) Or KeyHit(KEY_Y) Or KeyHit(KEY_Z) Then
If playerBullets[playerPos+9*5] = 0 And playerHeat < 16-5 Then
playerBullets[playerPos+9*5] = 1
playerHeat += 5
PlaySound(soundLaser, 0)
End
End

If tick Then
If damage > 0 Then damage -= 1
UpdatePlayerBullets()

If playerHeat > 0 Then playerHeat -= 1 Else playerHeat = 0
End
End

Method PlayerBulletHitCheck:Void()
' player bullets <=> enemy ship:
For Local i:Int = 0 Until 5*10
If playerBullets[i] And enemyShips[i] Then
enemyShips[i] -= 1
If Not enemyShips[i] Then points += 1 ; PlaySound(soundExplosion, 1)
playerBullets[i] = 0
Elseif playerBullets[i] And asteroids[i] Then
asteroids[i] -= 1
If Not asteroids[i] Then points += 3 ; PlaySound(soundExplosion, 1)
playerBullets[i] = 0
End
End
End

Method UpdatePlayerBullets:Void()
PlayerBulletHitCheck()

' move bullets:
For Local y:Int = 0 Until 9
For Local x:Int = 0 Until 5
If y = 0
If playerBullets[x+y*5] And bossEnergy > 0 And bossDamageCounter <= 0 And bossPosition = x Then
bossEnergy -= 1
If bossEnergy <= 0 Then points += 10 ; PlaySound(soundExplosion, 1)
bossDamageCounter = 3
End
playerBullets[x+y*5] = 0
End
If playerBullets[x+(y+1)*5] <> 0 Then
playerBullets[x+y*5] = playerBullets[x+(y+1)*5]
playerBullets[x+(y+1)*5] = 0
End
End
End

PlayerBulletHitCheck()
End

Method RenderPlayerBullets:Void()
For Local i:Int = 0 Until 50
If playerBullets[i] Then lcd.DrawFigure(BULLETS+i)
End
End

Method DamagePlayer:Void()
If damage > 0 Or playerLP <= 0 Then Return
damage = 10
playerLP -= 1
If playerLP < 1 Then
gameover = True
gameOverWave = waveNumber
PlaySound(soundExplosion, 2)
'gameoverCounter = 10
gameOverTime = Millisecs()
Else
PlaySound(soundHurt, 2)
End
End

'### ENEMY #######################################################

Field wave:Int = WAVE_START
Enumerate
WAVE_START,
WAVE_RANDOM_SHIPS,
WAVE_FORMATION_1,
WAVE_BOSS,
WAVE_ASTEROIDS,
WAVE_FRONT_1,
NUMBER_OF_WAVE_TYPES
Field waveCounter:Int

Const WAVE_DURATION:Int = 80

Field enemyShips:Int[5*10]
Field asteroids:Int[5*10]

Method FirstWave:Void()
updateSpeed = 0.15
' clear normal enemy ships:
For Local i := 0 Until 5*9
enemyShips[i] = 0
End

wave = WAVE_START
waveCounter = WAVE_DURATION

' clear enemies:
For Local i:Int = 0 Until enemyShips.Length()
enemyShips[i] = 0
End
For Local i:Int = 0 Until asteroids.Length()
asteroids[i] = 0
End
bossEnergy = 0
End

Method StartNewWave:Void()
updateSpeed += 0.005
movementSpeed += 0.005

waveNumber += 1
Local lastWave := wave
Local type:Int = WAVE_RANDOM_SHIPS
If wave <> WAVE_START Then type = Int(Rnd(1000)) Mod NUMBER_OF_WAVE_TYPES
If type = lastWave Then type = (type+1) Mod NUMBER_OF_WAVE_TYPES
wave = type
waveCounter = WAVE_DURATION
End

Field bossEnergy:Int
Field bossPosition:Int
Field bossDirection:Int = -1
Field bossDamageCounter:Int

Method UpdateEnemies:Void(tick:Bool)
If tick Then
waveCounter -= 1
If waveCounter <= 0 Then StartNewWave()

Select wave
Case WAVE_START
If waveCounter Mod 10 = 5 Or waveCounter Mod 20 = 12 Then
CreateEnemyShip(Rnd(5))
Else
If Rnd(100) < 10 Then CreateAsteroid(Rnd(5))
End
Case WAVE_RANDOM_SHIPS
If waveCounter Mod 5 = 2 Or waveCounter Mod 20 = 10 Then
CreateEnemyShip(Rnd(5))
Else
If Rnd(100) < 10 Then CreateAsteroid(Rnd(5))
End
Case WAVE_FORMATION_1
Select waveCounter
Case 70, 60, 50, 40, 30, 20, 10
CreateEnemyShip(Rnd(5))
Case 77, 46, 26
CreateEnemyShip(2)
Case 76, 45, 25
CreateEnemyShip(1)
CreateEnemyShip(3)
Case 75, 44, 24
CreateEnemyShip(0)
CreateEnemyShip(4)
Default
If Rnd(100) < 10 Then CreateAsteroid(Rnd(5))
End
Case WAVE_ASTEROIDS
If Rnd(100) < 40 Then CreateAsteroid(Rnd(5))
Case WAVE_BOSS
Select waveCounter
Case 78
bossEnergy = 10
bossDamageCounter = 3
bossPosition = Rnd(1000) Mod 5
Case 1
If Not gameover Then
If bossEnergy > 0 Then waveCounter = 10
Else
bossEnergy = 0
End
End
If waveCounter < 78 And bossEnergy <= 0 Then waveCounter = 0
Case WAVE_FRONT_1
Select waveCounter
Case 70, 60, 50, 35, 30, 20, 5, 1
CreateEnemyShip(Rnd(5))
Case 77,74, 46,43, 26,13
CreateEnemyShip(2)
Case 76,73, 45,42, 25,12
CreateEnemyShip(1)
CreateEnemyShip(3)
Case 75,72, 44,41, 24,11
CreateEnemyShip(0)
CreateEnemyShip(4)
End
Default '(WAVE_FORMATION_1)
Select waveCounter
Case 90, 80, 70, 60, 50, 40, 30, 20, 10
CreateEnemyShip(Rnd(5))
Case 97, 56, 26
CreateEnemyShip(2)
Case 96, 55, 25
CreateEnemyShip(1)
CreateEnemyShip(3)
Case 95, 54, 24
CreateEnemyShip(0)
CreateEnemyShip(4)
End
End

UpdateEnemyShips()
UpdateAsteroids()
UpdateBoss(tick)
End
End

Method RenderEnemies:Void()
RenderEnemyShips()
RenderAsteroids()
RenderBoss()
End

Method CreateEnemyShip:Void(pos:Int)
If pos < 0 Or pos >= 5 Then Return
enemyShips[pos] = 2
End

Method CreateAsteroid:Void(pos:Int)
If pos < 0 Or pos >= 5 Then Return
asteroids[pos] = 4
End

Method UpdateEnemyShips:Void()
' update enemy ships
For Local y:Int = 10 To 0 Step -1
For Local x:Int = 0 Until 5
If y = 0 Then
' do nothing?
Elseif y < 10
If enemyShips[x+(y-1)*5] <> 0 Then
enemyShips[x+y*5] = enemyShips[x+(y-1)*5]
enemyShips[x+(y-1)*5] = 0
End
'Elseif y = 9 Then
Else
If enemyShips[x+(y-1)*5] And playerPos = x Then DamagePlayer()
enemyShips[x+(y-1)*5] = 0
End
End
End
End

Method RenderEnemyShips:Void()
For Local i:Int = 0 Until 5*10
If enemyShips[i] = 0 Then Continue
lcd.DrawFigure(ENEMY_PARTS_LARGE+i-5)
lcd.DrawFigure(BULLETS+i-5)
lcd.DrawFigure(ENEMY_PARTS_SMALL+i-5)
lcd.DrawFigure(ENEMY_PARTS_SMALL+i)
End
End

Method UpdateAsteroids:Void()
' update enemy ships
For Local y:Int = 10 To 0 Step -1
For Local x:Int = 0 Until 5
If y = 0 Then
' do nothing?
Elseif y < 10
If asteroids[x+(y-1)*5] <> 0 Then
asteroids[x+y*5] = asteroids[x+(y-1)*5]
asteroids[x+(y-1)*5] = 0
End
Else
If asteroids[x+(y-1)*5] And playerPos = x Then DamagePlayer()
asteroids[x+(y-1)*5] = 0
End
End
End
End

Method RenderAsteroids:Void()
For Local i:Int = 0 Until 5*10
If asteroids[i] = 0 Then Continue
lcd.DrawFigure(BULLETS+i)
lcd.DrawFigure(ENEMY_PARTS_SMALL+i)
End
End

Method UpdateBoss:Void(tick:Bool)
If bossEnergy <= 0 Then Return
If tick Then
If Rnd(1000) < 200 Then bossDirection *= -1
bossDamageCounter -= 1
bossPosition += bossDirection
If bossPosition < 0 Then bossPosition = 0 ; bossDirection = 1
If bossPosition > 4 Then bossPosition = 4 ; bossDirection = -1
If Rnd(1000) < 200 Then CreateAsteroid(bossPosition)
End
End

Method RenderBoss:Void()
If bossEnergy <= 0 Then Return
If bossDamageCounter <= 0 Or Flicker() Then lcd.DrawFigure(BOSS+bossPosition)
If Not gameover Then
lcd.DrawFigure(BOSS_LP_BORDER)
For Local i := 0 Until bossEnergy
lcd.DrawFigure(BOSS_LP_BAR+i)
End
End
End

'#################################################################

End

Function Main:Int()
New MainApp
Return 0
End

Leave a comment

Log in with itch.io to leave a comment.

Comments

Submitted

I love the fact you limited the amount the player can shoot with the overheating system, rather than something like ammo, that's been done and would be more boring. I also quite enjoyed how the ship bends a little bit depending on your position.

Developer

Thank you! Overheating was part of some games that I played as a child and somehow I really like this concept ;)

HostSubmitted

Very well put together LCD game! Flawless style, and the concept allows for much variation. And you really topped it with the boss fight! Shame that the balancing is not yet there, if you fix it I'd suggest that you also add more different flight formations for the fighters, to make it even better!

Developer

Thx! New formations can be added easily and the balancing will also be adjusted no time – however I will do this after the voting.

Brilliant - blood brilliant. I keep playing it.....

Developer

Thank you! After the voting I will fix some minor issues (balancing …) and eventually add some new enemies and behaviors so that there is a little bit more variety.

Submitted

Very nice and extremely well playable. I want to play a physical version of it :)

Developer

Tank you so much!