ByteBuster8k (8k code)
Community Forums/Showcase/ByteBuster8k (8k code)
| ||
![]() PLAYABLE BUILD: https://dl.dropboxusercontent.com/u/14221897/files/Experiments/ByteBuster8k.exe SOURCE CODE: https://dl.dropboxusercontent.com/u/14221897/files/Experiments/ByteBuster8k.bmx ![]() 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. |
| ||
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 |
| ||
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 |
| ||
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. |
| ||
Can anybody repost the screenshot (dropbox doesn't work for me)? |
| ||
Can anybody repost the screenshot (dropbox doesn't work for me)? No probs:![]() |
| ||
@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 |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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 |
| ||
Interesting concept and nice graphic style :) |
| ||
Love the screenshot, nice style. Thanks, big10p! |
| ||
this is really excellent pixel style and good learn from code you made too :) |
| ||
Love the look of this! |
| ||
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. |
| ||
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 |
| ||
TI-83 anyone?![]() |
| ||
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). |
| ||
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 |
| ||
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 |
| ||
cool |
| ||
@AdamStrange: I like it. I never really used timers much, but I suppose they're good for that sort of thing. |
| ||
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 :) |
| ||
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? |
| ||
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 |
| ||
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. |
| ||
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. |
| ||
@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 |
| ||
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. |
| ||
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 |
| ||
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 |
| ||
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. |
| ||
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 |
| ||
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: ![]() |
| ||
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 |