ByteBuster8k (8k code)

Community Forums/Showcase/ByteBuster8k (8k code)

Mr. Goober(Posted 2014) [#1]


PLAYABLE BUILD:
https://dl.dropboxusercontent.com/u/14221897/files/Experiments/ByteBuster8k.exe

SOURCE CODE:
https://dl.dropboxusercontent.com/u/14221897/files/Experiments/ByteBuster8k.bmx

Licensed under Creative Commons. Feel free to play with the source.

EDIT: There's no way I'm gonna get this under 4096 bytes. After taking out the unfinished sound part, the source sits at 8043 bytes, under 8196, which beats the 8k challenge. There's still two more things I want to add though: enemies shooting back, and scaling difficulty.


Mr. Goober(Posted 2014) [#2]
Source code at the time I made this topic. No external images or media were used in creating this. You can plug this straight into BlitzMax and it plays:
SuperStrict
'-----------------------------
SetGraphicsDriver(GLMax2DDriver())
AppTitle = "ByteDestroyer"
Graphics(640,480)
SetVirtualResolution(160,120)


Global _colors		:Int[]			= [0,64,128,255]
Global _sprites		:t_sprite[]		= New t_sprite[10]
Global _spcounter	:Byte			= 0
Global _pship		:t_entity		= New t_entity
Global _sound		:TSound			= Null
Global _soundtime	:Byte			= 10
Global _sndchannel	:TChannel		= Null
Global _starscroll	:Float			= 0.00
Global _key_move	:Byte			= False
Global _key_space	:Byte			= False
Global _bullet		:t_entity		= New t_entity
Global _ebullets	:t_entity[]		= New t_entity[3]
Global _enemies		:t_entity[]		= New t_entity[10]
Global _score		:Float			= 0

_Init()
_MainLoop()

Type t_sprite
	Field data :Byte[,] = New Byte[10,10]
	Method Draw(X:Float=0,Y:Float=0)
		Local c:Byte=0
		For Local i:Byte=0 To 9
			For Local j:Byte=0 To 9
				c=_colors[data[i,j]]
				If (c>0)
					SetColor(c,c,c)
					DrawRect(i+X,j+Y,1,1)
				EndIf
			Next
		Next
	EndMethod
	Method SetData()
		For Local j:Byte=0 To 9
			For Local i:Byte=0 To 9
				ReadData data[i,j]
			Next
		Next
	EndMethod
EndType

Type t_entity
	Field pos_x 	:Float 	= 0.00
	Field pos_y 	:Float 	= 0.00
	Field sprite	:Byte	= 0
	Field life		:Byte	= 3
	Field bullets	:Byte	= 3
	Field flag_alive:Byte	= False
	Method MovePos(X:Float=0,Y:Float=0)
		pos_x :+ X
		pos_y :+ Y
	EndMethod
	Method Draw()
		If (flag_alive)
			_sprites[sprite].Draw(Floor(pos_x),Floor(pos_y))
		EndIf
	EndMethod
EndType

Function _DrawSprites()
	For Local i:Byte = 0 To _spcounter-1
		_sprites[i].Draw(0,0)
	Next
EndFunction

Function _CreateSprite()
	Local r:t_sprite = New t_sprite
	r.SetData()
	_sprites[_spcounter] = r
	_spcounter :+ 1
EndFunction

Function _DrawSprite(N:Byte=0,X:Float=0.00,Y:Float=0.00)
	_sprites[N].Draw(X,Y)
EndFunction

Function _Init()
	RestoreData G_Ship
	_CreateSprite()
	RestoreData G_Stars
	_CreateSprite()
	RestoreData G_Heart1
	_CreateSprite()
	RestoreData G_Enemy
	_CreateSprite()
	RestoreData G_Bullet
	_CreateSprite()
	_MakeSound()
	_pship.sprite = 0
	_pship.MovePos(75,100)
	_pship.life	= 3
	_pship.flag_alive = True
	_bullet.sprite = 4
	SetClsColor(16,16,16)
	SetVirtualResolution(160,120)
EndFunction

Function _MainLoop()
	While (KeyDown(KEY_ESCAPE) = False)
		Cls
		_UpdateShip()
		_UpdateBullet()
		_starscroll :+ 0.12
		If (_starscroll >= 10)
			_starscroll :- 10
		EndIf
		_DrawStars(_starscroll)
		_bullet.Draw()
		_pship.Draw()
		SetColor(0,0,0)
		DrawRect(0,110,160,10)
		SetColor(255,255,255)
		For Local i:Byte = 0 To _pship.life-1
			_DrawSprite(2,i*10,110)
		Next
		SetColor(255,255,255)
		_score :+ 0.03
		DrawText(RSet(String(Int(_score)),6),110,108)
		_DrawScanLines()
		Flip
	Wend
EndFunction

Function _DrawStars(X:Float)
	For Local i:Byte = 0 To 15
		For Local j:Byte = 0 To 11
			_DrawSprite(1,i*10,Floor(X+(j*10)-10))
		Next
	Next
EndFunction

Function _MakeSound()
	Local _s :TAudioSample = CreateAudioSample(64,16640,SF_MONO8)
	For Local i:Byte = 0 To 63
		If (i <= 31)
			_s.samples[i] = 160
		Else
			_s.samples[i] = 96
		EndIf
	Next
	_sound = LoadSound(_s,True)
EndFunction

Function _DrawScanLines()
	SetVirtualResolution(640,480)
	SetColor(0,0,0)
	For Local i:Byte = 0 To 159
		DrawLine(i*4,0,i*4,480)
		If (i <= 119)
			DrawLine(0,i*4,640,i*4)
		EndIf
	Next
	SetColor(255,255,255)
	SetVirtualResolution(160,120)
EndFunction

Function _CheckKeys()
	_key_move = False
	If (KeyDown(KEY_RIGHT))
		_key_move = 2
	Else If (KeyDown(KEY_LEFT))
		_key_move = 1
	EndIf
	_key_space = False
	If (KeyDown(KEY_SPACE))
		_key_space = True
	EndIf
EndFunction

Function _UpdateShip()
	_CheckKeys()
	If (_key_move > 0)
		If (_key_move = 2)
			If (_pship.pos_x < 150)
				_pship.MovePos(1,0)
			EndIf
		Else If (_key_move = 1)
			If (_pship.pos_x > 0)
				_pship.MovePos(-1,0)
			EndIf
		EndIf
	EndIf
	If (_key_space)
		If (_bullet.flag_alive = False)
			_bullet.flag_alive = True
			_bullet.pos_x = _pship.pos_x
			_bullet.pos_y = _pship.pos_y
		EndIf
	EndIf
EndFunction

Function _UpdateBullet()
	If (_bullet.flag_alive)
		_bullet.MovePos(0,-3)
		If (_bullet.pos_y < -10)
			_bullet.flag_alive = False
		EndIf
	EndIf
EndFunction

#G_Ship
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,2,3,0,0,3,2,0,0
DefData 0,0,2,3,1,1,3,2,0,0
DefData 2,2,2,2,3,3,2,2,2,2
DefData 2,0,2,2,2,2,2,2,0,2
DefData 2,1,2,0,2,2,0,2,1,2
DefData 0,1,0,0,0,0,0,0,1,0
#G_Stars
DefData 0,1,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,1,0,0
DefData 0,0,0,1,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,1,0
DefData 0,1,0,0,1,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,0,1,0,0,0,0,0,0
DefData 1,0,0,0,0,0,0,1,0,0
DefData 0,0,0,0,0,0,0,0,0,0
#G_Heart1
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,1,1,0,0,1,1,0,0
DefData 0,1,2,2,1,1,2,2,1,0
DefData 0,1,2,2,2,2,3,2,1,0
DefData 0,1,2,2,2,2,3,2,1,0
DefData 0,1,1,2,2,2,2,1,1,0
DefData 0,0,1,1,2,2,1,1,0,0
DefData 0,0,0,1,1,1,1,0,0,0
DefData 0,0,0,0,1,1,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
#G_Enemy
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,2,2,0,0,2,2,0,0
DefData 0,0,2,1,2,2,1,2,0,0
DefData 0,0,2,3,2,2,3,2,0,0
DefData 0,1,2,2,1,1,2,2,1,0
DefData 0,2,2,0,2,2,0,2,2,0
DefData 0,2,1,0,0,0,0,1,2,0
DefData 0,2,0,0,0,0,0,0,2,0
DefData 0,2,1,0,0,0,0,1,2,0
DefData 0,0,2,0,0,0,0,2,0,0
#G_Bullet
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,1,2,3,3,2,1,0,0
DefData 0,0,1,2,3,3,2,1,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,0,2,2,0,0,0,0



Derron(Posted 2014) [#3]
What you can do:

- shorten variable names (flag_alive -> alive, _bullet = b, ...)
- replace ":byte" ":int" ":string" with their shortcuts ($ etc.)
- remove "for local i:int =" with "for i =" and declare 2 array-walking-variables as global
- replace TRUE/FALSE with 1/0
- shorten function/method names
- replace const KEY_SOMETHING with their value
- replace multiple occurencies:

RestoreData VAR
_CreateSprite()
...
with a function _CreateSprite(somethingToAccess#Label), dunno if this is possible.


Hope you do not mind if you already knew this things. Just wanted to assist.
BTW: I like that "pixelated" look of the "sprites".


bye
Ron


big10p(Posted 2014) [#4]
I like the style. Good programming exercise, too. You've set yourself a pretty tough goal at only 4K, or is it for a compo?

There are a lot of repeated numbers in your data (especially zeros) so I guess you could grab a few bytes back by employing simple data compression, such as RLE?

[edit]
@Derron: None of those things will make a difference since I assume he's talking about a 4K exe, not source file.


GaryV(Posted 2014) [#5]
Can anybody repost the screenshot (dropbox doesn't work for me)?


big10p(Posted 2014) [#6]
Can anybody repost the screenshot (dropbox doesn't work for me)?
No probs:



Derron(Posted 2014) [#7]
@Derron: None of those things will make a difference since I assume he's talking about a 4K exe, not source file.


I'm trying to make a game in under 4096 bytes (chars) of code



BTW: I thought about compressing that defdata too - but I assume that further added "sprites" might be with less zeros. Coding the compress/uncompress might add more overhead than without.


	If (_key_move > 0)
		If (_key_move = 2)
			If (_pship.pos_x < 150)
				_pship.MovePos(1,0)
			EndIf
		Else If (_key_move = 1)
			If (_pship.pos_x > 0)
				_pship.MovePos(-1,0)
			EndIf
		EndIf
	EndIf


Maybe shorten this:
If (_key_move = 2 && _pship.pos_x < 150) then _pship.MovePos(1,0)

If (_key_move = 1) && _pship.pos_x > 0) then _pship.MovePos(-1,0)


or shorten it more with assigning the direction to _key_move (1 or -1)
and clamping the x-position within MovePos() using pos_x = Min(Max(0,pos_x),150)

EDIT:
Type t_entity
...
	Method MovePos(X:Float=0,Y:Float=0)
		pos_x :+ X
		pos_y :+ Y
		pos_x = Min(Max(0,pos_x),150)
	EndMethod
...
End Type

Function _CheckKeys()
	_key_move = KeyDown(KEY_RIGHT) '0 or 1
	_key_move :- KeyDown(KEY_LEFT)  '0 if both pressed, -1 if only left, 1 if only right

	_key_space = KeyDown(KEY_SPACE)
EndFunction

Function _UpdateShip()
	_CheckKeys()
	_pship.MovePos(_key_move)
...
End function



bye
Ron


big10p(Posted 2014) [#8]
Derron, exe's contain code! These exercises usually refer to compiled code as there is no benefit to just shortening variable names etc., making the source just less understandable.

BTW: I thought about compressing that defdata too - but I assume that further added "sprites" might be with less zeros. Coding the compress/uncompress might add more overhead than without.
Since the zeros represent transparent pixels, other sprites are likely to contain just as many. Also, there's no need for a compression routine as you'd just enter the ready compressed data into the data statements.


Derron(Posted 2014) [#9]
If the exercise is to build a binary of a specific size I assume writing in pure ASM is more efficient - or shortening the output of the "pre-compilate".

As the sourcecode is ~5.8kb and the author states a code size of 5844 I am quite sure that his own "task" is to code something with less than 4096 characters in source code (means he should use unix line endings :D).

Concerning compression: I did not get what you want to say - could you explain a bit more? Somewhere the decompression has to take place - coding this means adding code which adds bytes to the sourcecode/binaryfile.


Another "shortening" possibility:
		If (_bullet.pos_y < -10)
			_bullet.flag_alive = False
		EndIf


Like with KeyDown/KeyHit you can use the "boolean" not just for multiplying (1,0) but also as a simple return value:
		_bullet.flag_alive = (_bullet.pos_y < -10)


EDIT: you use "SetVirtualResolution" 2 times. Think there is a lot of space to free for the longthy awaited enemies.

bye
Ron


big10p(Posted 2014) [#10]
Concerning compression: I did not get what you want to say - could you explain a bit more? Somewhere the decompression has to take place - coding this means adding code which adds bytes to the sourcecode/binaryfile.
It needs a function to decompress the data, but not one to compress it - that can be done separately and the resulting data added by hand into the data statements.

RLE compression is very basic so the function to decompress it is short. Part of the exercise is to see if the overhead of a decompression function is outweighed by the bytes saved by using the data compression.


Derron(Posted 2014) [#11]
Ahh Okay... you got me :D
Just ignored that "compression" part, yeah you are absolutely right, this can be handmade/precalculated.

I never used that defdata-part so I do not know if you somehow could fill defdata with other functions (output of decompression).
Else you would end up having to: a) decode the datastream b) fill the useData-array.

Maybe it is even shorter to Draw directly from the compressed stream. If you assume a "fixed" width of a sprite you could "mod" the y-coordinate.

Edit: Using the "per sprite"-array includes the possibility for "explosion"-effects (dissolving aka "reset pixel to black") should be easily done as it does not include new properties (vectors, positions...).


There are plenty of optimizations possible: "DrawRect" -> "Plot" (-4 Bytes :D).


bye
Ron


Steve Elliott(Posted 2014) [#12]
Interesting concept and nice graphic style :)


GaryV(Posted 2014) [#13]
Love the screenshot, nice style.

Thanks, big10p!


Hotshot2005(Posted 2014) [#14]
this is really excellent pixel style and good learn from code you made too :)


BlitzSupport(Posted 2014) [#15]
Love the look of this!


Taron(Posted 2014) [#16]
You could do pseudo random for the stars and simple formulas for the look of some of the sprites to avoid the "images" in the code. Could save up size and offer addition to even more elements with calls to the same formulas with different parameters.

Love the look, too. Very convincing! 8D ...and I'm a total geek for keeping compiles small. But it's VERY tough with Blitzmax, because you kinda have to use mods without going crazy, I think, and they sure are "wasteful" beyond what you may need.


Mr. Goober(Posted 2014) [#17]
Wow, I didn't think that this would pick up as much attention as it did. Thanks for the positive feedback everyone.

@Derron:
Yeah, there's a lot of optimization I can do in regards to your post about shortening variable names, etc. I'll have to do that once I finish the game. Right now I want to make sure that the entire project is completed before I make source code a bit more compressed.

@big10p:
Nah, this isn't for a compo. It's just something that I wanted to do to challenge myself. The goal isn't about a smaller EXE. It's simply limiting myself to 4096 characters of source code. I've added some new stuff since the screenshot, and I'm already at a huge 7,544 bytes (8192 is the next limit). The last thing I need to program in is the sound, and collision from enemies so you lose lives, and eventually, a game over.

@Derron (post #7):
I'm probably going to keep the DefData the same. It would be too much of a mess for me to do it another way, unless it's more useful to use array pairs ([0,0,3] would put a bright dot on coordinate 0,0), but this would be debatable if the sprite happened to have a lot of nonzero pixels.

It would be beneficial to implement a Clamp() function in the source code to shorten up the rest of the code, indeed. I'll mark that as something to do. I'll also take out SuperStrict mode after it's all done and resort to using sigils for data types instead.

I also tried to use Plot over DrawRect, but apparently Plot is not affected by the VirtualResolution. Plot seems to always plot just one pixel and not scale either way.

@Hotshot2005/BlitzSupport:
I appreciate it. I'm glad to see that staff from Blitz Research are also intrigued by my progress. Hotshot, feel free to use the source however you like. There's some unfinished sound code which produces a square wave which I intend to use for most of the game's sound, but currently it isn't implemented.

@Taron:
Yeah, I'd need to tell BlitzMax not to include all of the modules by default. When the game is completed, I can focus more on keeping the EXE file small. I thought about Incbin'ing some external files, but that would sort of take away the beauty of just plugging in the code and playing it. Generally modules will take up about 2-3MB of space on an empty project depending on what sort of build you use.


PROGRESS REPORT:



SuperStrict
'-----------------------------
SetGraphicsDriver(GLMax2DDriver())
AppTitle = "ByteBuster 4k"
Graphics(640,480)
SetVirtualResolution(160,120)

Const  ENMOVE_AMT	:Byte			= 1

Global _colors		:Int[]			= [0,64,128,255]
Global _sprites		:t_sprite[]		= New t_sprite[10]
Global _spcounter	:Byte			= 0
Global _pship		:t_entity		= New t_entity
Global _sound		:TSound			= Null
Global _soundtime	:Byte			= 10
Global _sndchannel	:TChannel		= Null
Global _starscroll	:Float			= 0.00
Global _key_move	:Byte			= False
Global _key_space	:Byte			= False
Global _bullet		:t_entity		= New t_entity
Global _ebullets	:t_entity[]		= New t_entity[3]
Global _enemies		:t_entity[]		= New t_entity[10]
Global _encounter	:Byte			= 0
Global _score		:Float			= 0
Global _lastlife	:Float			= 10000


_Init()
_MainLoop()

Type t_sprite
	Field data :Byte[,] = New Byte[10,10]
	Method Draw(X:Float=0,Y:Float=0)
		Local c:Byte=0
		For Local i:Byte=0 To 9
			For Local j:Byte=0 To 9
				c=_colors[data[i,j]]
				If (c>0)
					SetColor(c,c,c)
					DrawRect(i+X,j+Y,1,1)
				EndIf
			Next
		Next
	EndMethod
	Method SetData()
		For Local j:Byte=0 To 9
			For Local i:Byte=0 To 9
				ReadData data[i,j]
			Next
		Next
	EndMethod
EndType

Type t_entity
	Field pos_x 	:Float 	= 0.00
	Field pos_y 	:Float 	= 0.00
	Field sprite	:Byte	= 0
	Field life		:Byte	= 3
	Field bullets	:Byte	= 3
	Field flag_alive:Byte	= False
	Field enmove	:Float	= 0
	Method MovePos(X:Float=0,Y:Float=0)
		pos_x :+ X
		pos_y :+ Y
	EndMethod
	Method Draw()
		If (flag_alive)
			_sprites[sprite].Draw(Floor(pos_x),Floor(pos_y))
		EndIf
	EndMethod
EndType

Function _DrawSprites()
	For Local i:Byte = 0 To _spcounter-1
		_sprites[i].Draw(0,0)
	Next
EndFunction

Function _CreateSprite()
	Local r:t_sprite = New t_sprite
	r.SetData()
	_sprites[_spcounter] = r
	_spcounter :+ 1
EndFunction

Function _DrawSprite(N:Byte=0,X:Float=0.00,Y:Float=0.00)
	_sprites[N].Draw(X,Y)
EndFunction

Function _Init()
	RestoreData G_Ship
	_CreateSprite()
	RestoreData G_Stars
	_CreateSprite()
	RestoreData G_Heart1
	_CreateSprite()
	RestoreData G_Enemy
	_CreateSprite()
	RestoreData G_Bullet
	_CreateSprite()
	_MakeSound()
	For Local i:Byte = 0 To 9
		_enemies[i] = New t_entity
	Next
	_pship.sprite = 0
	_pship.MovePos(75,100)
	_pship.life	= 3
	_pship.flag_alive = True
	_bullet.sprite = 4
	SetClsColor(16,16,16)
	SetVirtualResolution(160,120)
EndFunction

Function _MainLoop()
	While (KeyDown(KEY_ESCAPE) = False) And (AppTerminate() = False)
		Cls
		_UpdateShip()
		_UpdateEnemies()
		_UpdateBullet()
		_starscroll :+ 0.12
		If (_starscroll >= 10)
			_starscroll :- 10
		EndIf
		If (Rand(1,100) > 95)
			_CreateEnemy()
		EndIf
		_DrawStars(_starscroll)
		For Local i:Byte = 0 To 9
			_enemies[i].Draw()
		Next
		_bullet.Draw()
		_pship.Draw()
		SetColor(0,0,0)
		DrawRect(0,110,160,10)
		SetColor(255,255,255)
		For Local i:Byte = 0 To _pship.life-1
			_DrawSprite(2,i*10,110)
		Next
		SetColor(255,255,255)
		_score :+ 0.03
		If (_score >= _lastlife)
			_lastlife :+ 10000
			_pship.life :+ 1
		EndIf
		DrawText(RSet(String(Int(_score)),6),110,108)
		_DrawScanLines()
		Flip
	Wend
EndFunction

Function _DrawStars(X:Float)
	For Local i:Byte = 0 To 15
		For Local j:Byte = 0 To 11
			_DrawSprite(1,i*10,Floor(X+(j*10)-10))
		Next
	Next
EndFunction

Function _MakeSound()
	Local _s :TAudioSample = CreateAudioSample(64,16640,SF_MONO8)
	For Local i:Byte = 0 To 63
		If (i <= 31)
			_s.samples[i] = 160
		Else
			_s.samples[i] = 96
		EndIf
	Next
	_sound = LoadSound(_s,True)
EndFunction

Function _DrawScanLines()
	SetVirtualResolution(640,480)
	SetColor(0,0,0)
	For Local i:Byte = 0 To 159
		DrawLine(i*4,0,i*4,480)
		If (i <= 119)
			DrawLine(0,i*4,640,i*4)
		EndIf
	Next
	SetColor(255,255,255)
	SetVirtualResolution(160,120)
EndFunction

Function _CheckKeys()
	_key_move = False
	If (KeyDown(KEY_RIGHT))
		_key_move = 2
	Else If (KeyDown(KEY_LEFT))
		_key_move = 1
	EndIf
	_key_space = False
	If (KeyDown(KEY_SPACE))
		_key_space = True
	EndIf
EndFunction

Function _UpdateShip()
	_CheckKeys()
	If (_key_move > 0)
		If (_key_move = 2)
			If (_pship.pos_x < 150)
				_pship.MovePos(1,0)
			EndIf
		Else If (_key_move = 1)
			If (_pship.pos_x > 0)
				_pship.MovePos(-1,0)
			EndIf
		EndIf
	EndIf
	If (_key_space)
		If (_bullet.flag_alive = False)
			_bullet.flag_alive = True
			_bullet.pos_x = _pship.pos_x
			_bullet.pos_y = _pship.pos_y
		EndIf
	EndIf
EndFunction

Function _UpdateBullet()
	If (_bullet.flag_alive)
		_bullet.MovePos(0,-3)
		For Local i:Byte = 0 To 9
			If (_enemies[i].flag_alive)
				If (_PointInRect(_bullet.pos_x+5,_bullet.pos_y,_enemies[i].pos_x,_enemies[i].pos_y,_enemies[i].pos_x+10,_enemies[i].pos_y+10))
					_enemies[i].flag_alive = False
					_bullet.flag_alive = False
					_score :+ 100
				EndIf
			EndIf
		Next
		If (_bullet.pos_y < -10)
			_bullet.flag_alive = False
		EndIf
	EndIf
EndFunction

Function _UpdateEnemies()
	For Local i:Byte = 0 To 9
		If (_enemies[i].flag_alive)
			_enemies[i].MovePos(0,1)
			_enemies[i].enmove :+ 1
			If (_enemies[i].enmove >= 360)
				_enemies[i].enmove = 0
			EndIf
			_enemies[i].MovePos(ENMOVE_AMT*Sin(_enemies[i].enmove),0)
			If (_enemies[i].pos_y > 110)
				_enemies[i].flag_alive = False
			EndIf
		EndIf
	Next
EndFunction

Function _CreateEnemy()
	If (_enemies[_encounter].flag_alive = False)
		_enemies[_encounter].flag_alive = True
		_enemies[_encounter].pos_x		= Rand(0,150)
		_enemies[_encounter].pos_y		= -10
		_enemies[_encounter].sprite		= 3
		_enemies[_encounter].enmove		= Rand(0,360)
		_encounter :+ 1
		If (_encounter > 9)
			_encounter = 0
		EndIf
	EndIf
EndFunction

Function _PointInRect :Int(X:Float, Y:Float, X1:Float, Y1:Float, X2:Float, Y2:Float)
	If (X >= X1) And (X <= X2) And (Y >= Y1) And (Y <= Y2)
		Return True
	EndIf
	Return False
EndFunction

#G_Ship
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,2,3,0,0,3,2,0,0
DefData 0,0,2,3,1,1,3,2,0,0
DefData 2,2,2,2,3,3,2,2,2,2
DefData 2,0,2,2,2,2,2,2,0,2
DefData 2,1,2,0,2,2,0,2,1,2
DefData 0,1,0,0,0,0,0,0,1,0
#G_Stars
DefData 0,1,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,1,0,0
DefData 0,0,0,1,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,1,0
DefData 0,1,0,0,1,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,0,1,0,0,0,0,0,0
DefData 1,0,0,0,0,0,0,1,0,0
DefData 0,0,0,0,0,0,0,0,0,0
#G_Heart1
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,1,1,0,0,1,1,0,0
DefData 0,1,2,2,1,1,2,2,1,0
DefData 0,1,2,2,2,2,3,2,1,0
DefData 0,1,2,2,2,2,3,2,1,0
DefData 0,1,1,2,2,2,2,1,1,0
DefData 0,0,1,1,2,2,1,1,0,0
DefData 0,0,0,1,1,1,1,0,0,0
DefData 0,0,0,0,1,1,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
#G_Enemy
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,2,2,0,0,2,2,0,0
DefData 0,0,2,1,2,2,1,2,0,0
DefData 0,0,2,3,2,2,3,2,0,0
DefData 0,1,2,2,1,1,2,2,1,0
DefData 0,2,2,0,2,2,0,2,2,0
DefData 0,2,1,0,0,0,0,1,2,0
DefData 0,2,0,0,0,0,0,0,2,0
DefData 0,2,1,0,0,0,0,1,2,0
DefData 0,0,2,0,0,0,0,2,0,0
#G_Bullet
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,1,2,3,3,2,1,0,0
DefData 0,0,1,2,3,3,2,1,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,0,2,2,0,0,0,0



Mr. Goober(Posted 2014) [#18]
TI-83 anyone?



Mr. Goober(Posted 2014) [#19]
I added a completed build at the top post.
The source code is still not optimized, but the game is fully playable.
(extra lives per 10,000 points).


AdamStrange(Posted 2014) [#20]
Here's a new version, with added game play, colour and proper multitasking :)

SuperStrict
'-----------------------------
SetGraphicsDriver(GLMax2DDriver())
AppTitle = "intBuster 4k"
Graphics(640,480)
SetVirtualResolution(160,120)

Const  ENMOVE_AMT	:int			= 1

Global _red				:Int[]			= [0,64,128,255,255,192,0  ,0  ,0  ,0]
Global _green			:Int[]			= [0,64,128,255,0  ,0  ,255,192,0  ,0]
Global _blue			:Int[]			= [0,64,128,255,0  ,0  ,0  ,0  ,255,192]
Global _sprites		:t_sprite[]		= New t_sprite[10]
Global _spcounter	:int			= 0
Global _pship			:t_entity		= New t_entity
Global _sound			:TSound			= Null
Global _soundtime	:int			= 10
Global _sndchannel	:TChannel		= Null
Global _starscroll	:Float			= 0.00
Global _key_move	:int			= False
Global _key_space	:int			= False
Global _bullet		:t_entity		= New t_entity
Global _ebullets	:t_entity[]		= New t_entity[3]
Global _enemies		:t_entity[]		= New t_entity[10]
Global _encounter	:int			= 0
Global _score			:Float			= 0
Global _hiscore		:Float			= 0
Global _lastlife	:Float			= 10000


_Init()
_MainLoop()

Type t_sprite
	Field data :Int[,] = New int[10,10]
	Method Draw(X:Float=0,Y:Float=0)
		Local c:int=0
		For Local i:int=0 To 9
			For Local j:int=0 To 9
				c=data[i,j]
				If (c>0)
					SetColor(_red[c],_green[c],_blue[c])
					DrawRect(i+X,j+Y,1,1)
				EndIf
			Next
		Next
	EndMethod
	Method SetData()
		For Local j:int=0 To 9
			For Local i:int=0 To 9
				ReadData data[i,j]
			Next
		Next
	EndMethod
EndType

Type t_entity
	Field pos_x 	:Float 	= 0.00
	Field pos_y 	:Float 	= 0.00
	Field sprite	:int	= 0
	Field life		:int	= 3
	Field bullets	:int	= 3
	Field flag_alive:int	= False
	Field enmove	:Float	= 0
	Method SetPos(X:Float=0,Y:Float=0)
		pos_x = X
		pos_y = Y
	EndMethod
	Method MovePos(X:Float=0,Y:Float=0)
		pos_x :+ X
		pos_y :+ Y
	EndMethod
	Method Draw()
		If (flag_alive)
			_sprites[sprite].Draw(Floor(pos_x),Floor(pos_y))
		EndIf
	EndMethod
EndType

Function _DrawSprites()
	For Local i:int = 0 To _spcounter-1
		_sprites[i].Draw(0,0)
	Next
EndFunction

Function _CreateSprite()
	Local r:t_sprite = New t_sprite
	r.SetData()
	_sprites[_spcounter] = r
	_spcounter :+ 1
EndFunction

Function _DrawSprite(N:int=0,X:Float=0.00,Y:Float=0.00)
	_sprites[N].Draw(X,Y)
EndFunction

Function _Init()
	RestoreData G_Ship
	_CreateSprite()
	RestoreData G_Stars
	_CreateSprite()
	RestoreData G_Heart1
	_CreateSprite()
	RestoreData G_Enemy
	_CreateSprite()
	RestoreData G_Bullet
	_CreateSprite()
	_MakeSound()
	
	For Local i:int = 0 To 9
		_enemies[i] = New t_entity
	Next

	_pship.sprite = 0
	_bullet.sprite = 4

	SetClsColor(16,16,16)
	SetVirtualResolution(160,120)

	_pship.life	 = 0

	'create a timer to play nicely at 60 ticks a second
	CreateTimer(60)
End Function


Function _Restart()	
	For Local i:int = 0 To 9
		_enemies[i].flag_alive = False
	Next

	_pship.sprite = 0
	_pship.SetPos(75,110)
	_pship.flag_alive = True

	_bullet.flag_alive = False

	Cls
	For Local i:Int = 0 To _pship.life-1
		_DrawSprite(2,i*10,0)
	Next

	SetColor(255,255,255)
	DrawText("READY",60,50)
	SetColor(255,255,0)
	DrawText(RSet(String(Int(_score)),6),110,0)
	SetColor(0,128,255)
	DrawText("HI "+String(Int(_hiscore)),50,0)
	_DrawScanLines()
	Flip
	Delay(1500)

EndFunction


Function _MainLoop()
	EnablePolledInput()
		
	While (KeyDown(KEY_ESCAPE) = False) And (AppTerminate() = False)
		Local AppQueue:Int = PollEvent()
		If AppQueue = 0 Then
			Delay(2)
		Else
			Select EventID()
				Case EVENT_TIMERTICK
'					Print _pship.life+" "+_pship.pos_x+" "+_pship.pos_y
					Cls
					If _pship.life < 1 Then
						_score = 0
						SetColor(255,255,255)
						DrawText("INVADAR",50,5)
						DrawText("move......ARROWS",15,60)
						DrawText("fire......SPACE",15,75)
						DrawText("FIRE to play",30,100)
						_DrawScanLines()
						Flip
						If KeyDown(KEY_SPACE) Then
							_pship.life = 3
							_Restart()
						End if
					Else
						If(_UpdateEnemies()) Then
							_UpdateShip()
							_UpdateBullet()
							_starscroll :+ 0.12
							If (_starscroll >= 10)
								_starscroll :- 10
							EndIf
							If (Rand(1,100) > 95)
								_CreateEnemy()
							EndIf
							_DrawStars(_starscroll)
							For Local i:int = 0 To 9
								_enemies[i].Draw()
							Next
							_bullet.Draw()
							_pship.Draw()
						
							For Local i:Int = 0 To _pship.life-2
								_DrawSprite(2,i*10,0)
							Next
						
							If _score >= _lastlife Then
								_lastlife :+ 10000
								_pship.life :+ 1
							EndIf
							
							If _score > _hiscore Then _hiscore = _score
							SetColor(0,128,255)
							DrawText("HI "+String(Int(_hiscore)),50,0)

							SetColor(255,255,0)
							DrawText(RSet(String(Int(_score)),6),110,0)
							_DrawScanLines()
							Flip
						End If
					End if	
			End Select
		End If
	Wend 		

EndFunction

Function _DrawStars(X:Float)
	For Local i:int = 0 To 15
		For Local j:int = 0 To 12
			_DrawSprite(1,i*10,Floor(X+(j*10)-10))
		Next
	Next
EndFunction

Function _MakeSound()
	Local _s :TAudioSample = CreateAudioSample(64,16640,SF_MONO8)
	For Local i:int = 0 To 63
		If (i <= 31)
			_s.samples[i] = 160
		Else
			_s.samples[i] = 96
		EndIf
	Next
	_sound = LoadSound(_s,True)
EndFunction

Function _DrawScanLines()
	SetVirtualResolution(640,480)
	SetColor(0,0,0)
	For Local i:int = 0 To 159
		DrawLine(i*4,0,i*4,480)
		If (i <= 119)
			DrawLine(0,i*4,640,i*4)
		EndIf
	Next
	SetColor(255,255,255)
	SetVirtualResolution(160,120)
EndFunction

Function _CheckKeys()
	_key_move = False
	If (KeyDown(KEY_RIGHT))
		_key_move = 2
	Else If (KeyDown(KEY_LEFT))
		_key_move = 1
	EndIf
	_key_space = False
	If (KeyDown(KEY_SPACE))
		_key_space = True
	EndIf
EndFunction

Function _UpdateShip()
	_CheckKeys()
	If (_key_move > 0)
		If (_key_move = 2)
			If (_pship.pos_x < 150)
				_pship.MovePos(1,0)
			EndIf
		Else If (_key_move = 1)
			If (_pship.pos_x > 0)
				_pship.MovePos(-1,0)
			EndIf
		EndIf
	EndIf
	If (_key_space)
		If (_bullet.flag_alive = False)
			_bullet.flag_alive = True
			_bullet.pos_x = _pship.pos_x
			_bullet.pos_y = _pship.pos_y
		EndIf
	EndIf
EndFunction

Function _UpdateBullet()
	If (_bullet.flag_alive)
		_bullet.MovePos(0,-3)
		For Local i:int = 0 To 9
			If (_enemies[i].flag_alive)
				If (_PointInRect(_bullet.pos_x+5,_bullet.pos_y, _enemies[i].pos_x,_enemies[i].pos_y,_enemies[i].pos_x+10,_enemies[i].pos_y+10))
					_enemies[i].flag_alive = False
					_bullet.flag_alive = False
					_score :+ 100
				EndIf
			EndIf
		Next
		If (_bullet.pos_y < -10)
			_bullet.flag_alive = False
		EndIf
	EndIf
EndFunction

Function _UpdateEnemies:int()
	For Local i:int = 0 To 9
		If (_enemies[i].flag_alive)

			If _RectInRect(_pship.pos_x, _pship.pos_y, _pship.pos_x+10, _pship.pos_y+10, _enemies[i].pos_x, _enemies[i].pos_y, _enemies[i].pos_x+10, _enemies[i].pos_y+10)
				_pship.life :- 1
				If _pship.life > 0 Then
					_Restart()
				End if	
				Return false
			End If
			
			_enemies[i].MovePos(0,1)
			_enemies[i].enmove :+ 1
			If (_enemies[i].enmove >= 360)
				_enemies[i].enmove = 0
			EndIf
			_enemies[i].MovePos(ENMOVE_AMT*Sin(_enemies[i].enmove),0)
			If (_enemies[i].pos_y > 120)
				_enemies[i].flag_alive = False
			EndIf
		EndIf
	Next
	Return true
EndFunction

Function _CreateEnemy()
	If (_enemies[_encounter].flag_alive = False)
		_enemies[_encounter].flag_alive = True
		_enemies[_encounter].pos_x		= Rand(0,150)
		_enemies[_encounter].pos_y		= -10
		_enemies[_encounter].sprite		= 3
		_enemies[_encounter].enmove		= Rand(0,360)
		_encounter :+ 1
		If (_encounter > 9)
			_encounter = 0
		EndIf
	EndIf
EndFunction

Function _PointInRect :Int(X:Float, Y:Float, X1:Float, Y1:Float, X2:Float, Y2:Float)
	If (X >= X1) And (X <= X2) And (Y >= Y1) And (Y <= Y2)
		Return True
	EndIf
	Return False
EndFunction

Function _RectInRect :Int(X:Float, Y:Float, xx:Float, yy:float, X1:Float, Y1:Float, X2:Float, Y2:Float)
	If x < x2 And xx > x1 And y < y2 And yy > y1 Then
		Return True
	End If	
	Return False
EndFunction

#G_Ship
DefData 0,0,0,0,8,8,0,0,0,0
DefData 0,0,0,0,8,8,0,0,0,0
DefData 0,0,0,8,9,9,8,0,0,0
DefData 0,0,0,8,9,9,8,0,0,0
DefData 0,0,8,9,3,3,9,8,0,0
DefData 0,0,8,9,2,2,9,8,0,0
DefData 8,8,8,8,9,9,8,8,8,8
DefData 8,0,8,8,8,8,8,8,0,8
DefData 8,2,8,0,8,8,0,8,2,8
DefData 0,4,0,0,4,4,0,0,4,0
#G_Stars
DefData 0,1,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,1,0,0
DefData 0,0,0,1,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,1
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,1,0,0,1,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,0,0,0,0,1,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
#G_Heart1
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,4,4,0,0,4,4,0,0
DefData 0,4,3,5,4,4,3,5,4,0
DefData 0,4,5,5,5,5,5,5,4,0
DefData 0,4,5,5,5,5,5,5,4,0
DefData 0,4,5,5,5,5,5,5,4,0
DefData 0,0,4,5,5,5,5,4,0,0
DefData 0,0,0,4,5,5,4,0,0,0
DefData 0,0,0,0,4,4,0,0,0,0
DefData 0,0,0,0,0,0,0,0,0,0
#G_Enemy
DefData 0,0,0,0,0,0,0,0,0,0
DefData 0,0,6,6,0,0,6,6,0,0
DefData 0,0,6,7,6,6,7,6,0,0
DefData 0,0,6,3,6,6,3,6,0,0
DefData 0,7,6,6,7,7,6,6,7,0
DefData 0,6,6,0,6,6,0,6,6,0
DefData 0,6,7,0,0,0,0,7,6,0
DefData 0,6,0,0,0,0,0,0,6,0
DefData 0,6,7,0,0,0,0,7,6,0
DefData 0,0,6,0,0,0,0,6,0,0
#G_Bullet
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,1,2,3,3,2,1,0,0
DefData 0,0,1,2,3,3,2,1,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,0,2,2,0,0,0,0



Derron(Posted 2014) [#21]
Shaped off 1.8k of code.

There is more possible but I wanted to keep the "readability".

PS: the game loop isn't that proper (pressing ESC does not work "snappy").

Pay attention that certain variables changed type (move-key was byte is now int - so it can get negative), some things changed to be more dynamic ("for x = eachin array ; x.bla" VS "for x = 0 to 9 ; enemy[x].bla").

You could "compress" color definitons (instead of "0,64,92,.." you use "0,4,6" and multiply them with 16 - so your definitions stay between 0-9.

Most useable part will be my "init"-method as it does the same as the DATA-part (maintaining "readability"). But only as long as you do not allow more than 10 color definitions (you can use A-Z,a-Z,0-9 as "codes" too... without much hassle)




bye
Ron


Hardcoal(Posted 2014) [#22]
cool


Mr. Goober(Posted 2014) [#23]
@AdamStrange:
I like it. I never really used timers much, but I suppose they're good for that sort of thing.


AdamStrange(Posted 2014) [#24]
the other once thing is the game now runs at the same speed on any machine, any cpu.

You could have different timers, one for input one for the enemies, etc :)


Mr. Goober(Posted 2014) [#25]
Oh uh... I just relied on Flip so that it is timed on the refresh rate. That method works too. What does it do? Does it wait until x milliseconds have passed?


Derron(Posted 2014) [#26]
It depends on how you created your graphics object.

According to the blitzmax sources that "flip -1/0/1" work differently - and one of them uses already a kind of "timer".

So all in all there is no real need for an timer if you plan to update and draw simultaneously (at XX fps).

Else (200 updates vs 20 draw /second) you have to rely on timers or other methods of a game loop.


bye
Ron


Steve Elliott(Posted 2014) [#27]

I just relied on Flip so that it is timed on the refresh rate. That method works too.



So what happens if the vsync is turned off in drivers? It'll run at full speed - not at the refresh rate. Or what if the monitor refresh rate varies? Again it will vary in speed from computer to computer.


zzz(Posted 2014) [#28]
This is pretty neat. You should consider encoding your sprites as others have mentioned. You are using only 4 different values in your code, needing only 2 bits to be represented. Simply encoding this into a hexadecimal string (4 values per byte/2 hex characters) would give something like 70ish bytes per sprite instead of the 300ish you are using now. A decoding routine would add some, but it should be well worth it.


Derron(Posted 2014) [#29]
@Steve Elliot

brl.mod/graphics.mod/graphics.bmx:



Concerning sprite encoding:
If you use mine, you are already "char-ready" and so if using A-Za-z0-9+some others you could "mod" them with your "maxColor-const * index" and get back various information.


bye
Ron


Mr. Goober(Posted 2014) [#30]
I can indeed compress the image data into hex values and then decompress, which shouldn't be too much data to create. After trying one time to simply compress all of the source, I got it to around 5,500-ish bytes. I encountered some problems while trying to compress it further, and ended up making the program unusable. I'll try doing it again later.

@Steve Eliott
Flip in this sense is using vsync (sync is 0, and created with Graphics()). Though this does mean that the refresh rate of monitors will change how the game is played (we're depending on 60 hz monitors in this sense), but I didn't really care to change it because it wasn't too much of a problem.


Mr. Goober(Posted 2014) [#31]
So I made a little function to turn the data into hex. I'll probably convert the sprites to hex and then import those into the game.

an example
Global _hex		:String			= "0123456789ABCDEF"

RestoreData G_Ship
Global _sprite :String = _MakeSpriteCode()
Print _sprite

Function _MakeSpriteCode :String()
	Local u:Byte[] = New Byte[100]
	For Local i:Byte = 0 To 99
		ReadData u[i]
	Next
	Return _ToHex(u)
EndFunction

Function _ToHex :String(B:Byte[])
	Local u:Byte 	= 0
	Local r:String	= ""
	For Local i:Byte = 0 To Len(B)-1 Step 2
		u = 0
		u :| B[i]
		u = u Shl 2
		u :| B[i+1]
		r :+ Chr(_hex[u])
	Next
	Return r
EndFunction


#G_Ship
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,0,2,2,0,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,0,2,3,3,2,0,0,0
DefData 0,0,2,3,0,0,3,2,0,0
DefData 0,0,2,3,1,1,3,2,0,0
DefData 2,2,2,2,3,3,2,2,2,2
DefData 2,0,2,2,2,2,2,2,0,2
DefData 2,1,2,0,2,2,0,2,1,2
DefData 0,1,0,0,0,0,0,0,1,0



Derron(Posted 2014) [#32]
Again you can shorten your functions:

If you have a array and you are looping through ALL of them... you can replace "For local i:Byte = 0 to 99 ... u[i]" with "For local i@ = eachin ... i" ... this even gains benefits when "i" is a more complex variable and you call methods/properties of it.


@flip:
Don't know why I search for codes if my posts get ignored: use flip -1 and create the graphics with the "Graphics()"-command. Limit the refreshrate to the one you want (60) and BlitzMax will take care itself that the loop gets a delay if needed.

Only exception is: computer is to slow to handle XX refreshrate. But that problem arises with timers too.


bye
Ron


Mr. Goober(Posted 2014) [#33]
I didn't ignore it, but my focus changed. I am thinking on redoing the source and porting it to Monkey X and then sell the game on Android for like 99 cents. It would be my very first product in my game design career. Gotta start somewhere.


Derron(Posted 2014) [#34]
Just a tip: arcade games are much harder to play without proper input types (gamepads, keyboards).

So to avoid that virtual "dpad"-approach you might have a "left" and a "right" area (both sides of the display) and might think of auto-firing.

All in all this game might be a bit "to simple" for a >0ct game. So you end up improving the game - with powerups and varying enemies.

Welcome to a vertical scrolling Galaga game :D


bye
Ron


Mr. Goober(Posted 2014) [#35]
Yeah, I was thinking on making a bunch of improvements to the original idea, with perhaps powerups and other types of enemies that behave differently. I was also thinking of the way touchpads would use the left and right sides of the screen to move. I was also thinking on having the ship simply autofire to avoid needing to press a third button (maybe when the player is not moving it will fire).

We'll see how it goes. I changed the look of the game so that it actually looks like it was made on a gameboy or calculator. Take a look:




Derron(Posted 2014) [#36]
Pay attention to the smaller screensizes on smartphones.

So while we see those small lines between the "pixels" - they will be up to invisible on a small smartphone (high dpi vs lower dpi).


bye
Ron