BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
on:
2009-01-17 07:38:01 » |
|
Hi, I've been working on Elite4k, a trade and space sim, since December. The game is basically a clone of evergreens like Elite and Privateer: You fly your spaceship from planet to planet, trade goods, buy upgrades, hunt pirates, mine asteroids or turn pirate yourself ... I think you all get the picture  I've almost finished the game now, with a size of around 4.8k and enough optimization potential remaining (hopefuly  ) to reach 4k. However, some issues turned up during development which keep bothering me. I would be grateful for any input on the following questions: Text rendering I'm currently using drawBytes to draw text from a resource byte array to a graphics context without string overhead. Is there a smarter way to do this? Number rendering I'm currently using a method which fills a byte array from a given number by setting the current modulo-10-value of the numeric value to the current byte, then decimal shifting the number and advancing the output byte. Is there a smarter way to do this? Perhaps some light-weight number format method in the API I don't know about? Matrix inversion For the 3D space sim, the inverse camera matrix is required. Two ways to get it: Matrix inversion (my unrolled version is 12 lines with lots of multiplies and adds) or premultiplication with the negated matrix components. Which way to go? Premultiplication eliminates the inversion code but requires some control flow in the geometry pipeline. Any experiences? Loading and Saving For a game where you can easily spend dozens of hours, a load and save game function is a must. The amount of game data I have to load and save is around 20 bytes. One option is to write out a text representation of the values and let the user note it down and reenter it to load a game (old-skool console style). However, 20 bytes mapped to easily typable characters is a lot of text. So I consider using the clipboard: Copy to get the current game state as a string, paste the string (which you saved in some file) to resume. Does this make sense? I'm a bit in doubt Array Access When accessing array cells in approximately linear (depending on control flow) sequences, is it better to have something like "data[basepointer+1] (...) data[basepointer+2] (...) data[basepointer+27] (...)" or is it better to go unary where possible "data[pointer++] (...) data[pointer++] (...)"? Thanks for your input Wolfgang "I'm a ****ing starship, I'm allowed to cheat!" GCU Arbitrary, Culture Craft
|
|
|
|
|
moogie
JGO Strike Force    Posts: 775 Medals: 5
Java games rock!
|
 |
«
Reply #1 on:
2009-01-17 08:14:05 » |
|
For numbers perhaps this might be smaller?
g.drawBytes(Integer.toString(val).getBytes())
no idea tho...
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #2 on:
2009-01-17 08:53:39 » |
|
Loading and saving: using base 64, 20 bytes is 27 characters. That's not much longer than the passwords in some games I've played, and it's a bit more intuitive for a non-technical user than putting stuff in the system clipboard.
Array access: data[pointer++] repeated 27 times will compress a lot better than data[pointer + 1]...
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #3 on:
2009-01-17 09:01:42 » |
|
Sounds very impressive! Hope it lives up to expectations  1) I'm pretty sure drawBytes is the cheapest way of drawing text 2) I think you're right again, the overhead of the necessary api method would be prohibitively expensive 3) sorry can't help there. 4) I'd bite the bullet & require the user to write down the code - I think you are going to be extremely strapped for bytes, making the usability hit an acceptable loss. 5) It depends. It boils down to :- Vs 1 2 3
| iload x iconst_y / bipush y / sipush y iadd |
Given the iload instruction used will be the same in both instances, they can be compared solely on the cost of the other instructions, giving a comparison of 3 bytes Vs 2+ bytes. Ofcourse, this is all irrelevant, as 'pointer++' will undoubtably compress far better, so should always be used. :edit: it's worth noting that Requires exactly the same bytecode (though in a slightly different order) as :- 1 2
| a[counter] = 1; counter+=1; |
Which in turn is the same size as :- 1 2
| a[counter] = 1; counter+=2; |
This is worth knowing, as people might expect 'pointer++' to generates smaller bytecode than 'pointer+=127'
|
|
|
|
|
Ranger
Sr. Member   Posts: 340 Medals: 1
|
 |
«
Reply #4 on:
2009-01-17 11:04:18 » |
|
Loading and Saving For a game where you can easily spend dozens of hours, a load and save game function is a must. The amount of game data I have to load and save is around 20 bytes. One option is to write out a text representation of the values and let the user note it down and reenter it to load a game (old-skool console style). However, 20 bytes mapped to easily typable characters is a lot of text. So I consider using the clipboard: Copy to get the current game state as a string, paste the string (which you saved in some file) to resume. Does this make sense? I'm a bit in doubt
This is what I found: Clipboard: Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(""), null); ==> adds 150 bytes Dialog: JOptionPane.showInputDialog(this, null, ""); ==> adds 61 bytes
|
|
|
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #5 on:
2009-01-17 11:13:46 » |
|
This is what I found: Clipboard: Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(""), null); ==> adds 150 bytes Dialog: JOptionPane.showInputDialog(this, null, ""); ==> adds 61 bytes
It'd be smaller still if you used the variant without the 'parentComponent' parameter.
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #6 on:
2009-01-17 12:17:17 » |
|
It'd be smaller still if you used the variant without the 'parentComponent' parameter.
Or even smaller if you just draw it to the screen, since OP is already using methods to draw text to the screen.
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #7 on:
2009-01-17 12:27:57 » |
|
This is worth knowing, as people might expect 'pointer++' to generates smaller bytecode than 'pointer+=127'
Invaluable! Most of the rendering and game logic codes does little more than add and mul based on pointers. This will help a lot! Thanks Wolfgang
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #8 on:
2009-01-17 12:31:07 » |
|
Or even smaller if you just draw it to the screen, since OP is already using methods to draw text to the screen.
Thanks for the input on the issue! Fortunately, neither the input nor the rendering of the save-game-string is the problem, I'll probably end up well below the mentioned byte counts by integrating it into the existing event logic. From the responses so far, I conclude that a save-game-string of, say, 30, alphanumeric chars wouldnt be a problem (correct if wrong). Thanks Wolfgang
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #9 on:
2009-01-17 12:33:59 » |
|
For numbers perhaps this might be smaller?
g.drawBytes(Integer.toString(val).getBytes())
no idea tho...
I tested this but (a) it compresses a lot worse than the solution I use now and (b) it does not support some required features. I need to draw tabular displays, with right-aligned numbers and a unit sign at the end of each number. Here is what I use now, input welcome: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private byte[] f(int numVal, int chrUni) { byte[] resVal=new byte[8]; resVal[7]=(byte)chrUni; for(int digPos=6;digPos>=0;digPos--) { if(numVal>0) resVal[digPos]=(byte)(48+numVal%10); else resVal[digPos]=(byte)32; numVal/=10; } if(resVal[6]==32) resVal[6]=48; return resVal; } |
Thanks Wolfgang
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #10 on:
2009-01-17 13:25:21 » |
|
Without spending any time actually testing things, one probable-optimisation which jumps at me is moving the "Write zero" up before the "Write values" and skipping the test. I.e. replace 1 2 3 4 5 6 7 8 9 10 11
| for(int digPos=6;digPos>=0;digPos--) { if(numVal>0) resVal[digPos]=(byte)(48+numVal%10); else resVal[digPos]=(byte)32; numVal/=10; } if(resVal[6]==32) resVal[6]=48; |
with 1 2 3 4 5 6 7 8 9 10 11
| resVal[6]=48; for(int digPos=6;digPos>=0;digPos--) { if(numVal>0) resVal[digPos]=(byte)(48+numVal%10); else resVal[digPos]=(byte)32; numVal/=10; } |
That saves a few bytes in the uncompressed version.
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #11 on:
2009-01-17 13:30:37 » |
|
Without spending any time actually testing things, one probable-optimisation which jumps at me is moving the "Write zero" up before the "Write values"
You are completely right. Will work and save a byte or two. Thanks Wolfgang
|
|
|
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #12 on:
2009-01-17 13:31:56 » |
|
note - pjt33 replied while I was typing, so there maybe some duplication  Only 2 optimisations I can see missing are to use the ternary operator & include the final if statement inside the loop. Both of these eliminate the duplicate array access instructions, which are rather large. Together. i'd guestimate around 10 byte saving? Forgive the tampering with your literals - embedding non-trivial literals is one of my pet hates  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| private static byte[] f(int numVal, int chrUni) { final int MAX_DIGITS = 7; byte[] resVal=new byte[MAX_DIGITS+1]; resVal[MAX_DIGITS]=(byte)chrUni; for(int digPos=MAX_DIGITS-1;digPos>=0;digPos--) { resVal[digPos] = (byte)(numVal>0||digPos==MAX_DIGITS-1?'0'+numVal%10:' '); numVal/=10; } return resVal; } |
:edit: Further optimised by combining the 2 ternary expressions inside the loop into a single or'ed one.
|
|
|
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #13 on:
2009-01-17 13:34:37 » |
|
You are completely right. Will work and save a byte or two.
Thanks
Wolfgang
erm, are you sure? if numVal is passed as zero, it'll set resVal[6] to '0', and then immediately set it to ' '. Breaking what you intended it to be doing.
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #14 on:
2009-01-17 13:47:02 » |
|
if numVal is passed as zero, it'll set resVal[6] to '0', and then immediately set it to ' '. Breaking what you intended it to be doing.
Correct, and exiting early reintroduces the if. Stupid me. Thats what you get for debugging a geometry pipeline while playing solitaire with your wife and posting in boards simultaneously Anyhow, your optimizations have put an end to that discussion, very very nice! Thanks Wolfgang PS: "Whoever finds embedded literals may keep them" 
|
|
|
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #15 on:
2009-01-17 13:52:21 » |
|
I'm looking forward to seeing this game!  Faster! Faster! <cracks whip>
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #16 on:
2009-01-17 16:51:02 » |
|
erm, are you sure?
Doh! There's an else clause. Of course, if we can change the requirements we could have it pad with 0s rather than spaces for simpler, shorter code with a retro feel...
|
|
|
|
|
moogie
JGO Strike Force    Posts: 775 Medals: 5
Java games rock!
|
 |
«
Reply #17 on:
2009-01-17 16:57:45 » |
|
I tested this but (a) it compresses a lot worse than the solution I use now and (b) it does not support some required features. I need to draw tabular displays, with right-aligned numbers and a unit sign at the end of each number.
Yeah, i wrote that at 2am... my brain was not really firing well.. it makes sense that it should compress worse!
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #18 on:
2009-01-18 03:26:57 » |
|
Yeah, i wrote that at 2am... my brain was not really firing well.. it makes sense that it should compress worse!
Anyhow, your solution raises an issue I've been thinking about a lot: API calls versus do-it-yourself. As I see it, there are three basic cases. Two are no-brainers: Never ever use the API, as in max/min or event handling, where the API calls compress much worse than your own constructs. Always use the API, as in drawing ovals or filling polys, where your own constructs get much too long. But the third is tricky: To use System.arrayCopy() or Arrays.fill(), or not? My guess is that for using such methods once, your have to pay the full overhead, but by using them frequently, the overhead should decrease ... where is the breakeven, when your own constructs start to get more expensive than the call overhead? I'm aware that this is almost a philosophical question and depends on the type of method we are talking about.
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #19 on:
2009-01-18 07:50:47 » |
|
Always use the API, as in drawing ovals or filling polys, where your own constructs get much too long.
I'm drawing my circles myself. Admittedly this is in large part because I'm doing complicated things with them (e.g. drawing an anti-aliased circle to the alpha channel).
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #20 on:
2009-01-18 09:19:15 » |
|
I'm drawing my circles myself. Admittedly this is in large part because I'm doing complicated things with them (e.g. drawing an anti-aliased circle to the alpha channel).
Of course, my general statement does not hold if the drawing functionality is a central part of your app and involves complex stuff hard to do using the API. In Ares, I referenced the backbuffer as a byte array and implemented the fake-phong rasterizer based on that. On the other hand: setRenderingHints, setComposite and drawOval is worse than a hand-rolled antialiasing bresenham? Obviously, I don't know any details ...
|
|
|
|
|
Abuse
JGO Kernel      Posts: 1866 Medals: 5
falling into the abyss of reality
|
 |
«
Reply #21 on:
2009-01-18 11:58:53 » |
|
Of course, my general statement does not hold if the drawing functionality is a central part of your app and involves complex stuff hard to do using the API. In Ares, I referenced the backbuffer as a byte array and implemented the fake-phong rasterizer based on that.
On the other hand: setRenderingHints, setComposite and drawOval is worse than a hand-rolled antialiasing bresenham? Obviously, I don't know any details ...
When writing the algorithm yourself you can ofcourse sacrifice speed for code simplicity/size, so bresenham's algo. might not be the best choice.
|
|
|
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #22 on:
2009-01-18 12:11:30 » |
|
Of course, my general statement does not hold if the drawing functionality is a central part of your app and involves complex stuff hard to do using the API. In Ares, I referenced the backbuffer as a byte array and implemented the fake-phong rasterizer based on that.
On the other hand: setRenderingHints, setComposite and drawOval is worse than a hand-rolled antialiasing bresenham? Obviously, I don't know any details ...
I hadn't come across setComposite before, so thanks for that. It doesn't do everything I need for Grav4k, but I may well find it useful in the future. I'm doing fillOval , which is simpler than drawOval. I'm also, as Abuse suggested, sacrificing some speed for simplicity.
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #23 on:
2009-01-18 12:52:20 » |
|
When writing the algorithm yourself you can ofcourse sacrifice speed for code simplicity/size
Comprehension dawns ... still thinking too much from a performance point of view. Of course you could get away with evaluating a distance function per pixel, especially if you use a lut, and for filling antialiasing would be cheap. Nice one, pjt33, and thanks for the clarification, abuse!
|
|
|
|
|
rdcarvallo
Sr. Member   Posts: 300
2D Java games forever!
|
 |
«
Reply #24 on:
2009-02-01 00:25:48 » |
|
A bit Off-topic,
I found today Elite Plus for DOS boxed for $6.00. It's on two 5 1/4 floppies!! But 150 pages manual its pure game design gold.
|
|
|
|
|
BitDragon
Jr. Member   Posts: 66
Sunset? Nice gradient paint!
|
 |
«
Reply #25 on:
2009-02-02 03:29:21 » |
|
A bit Off-topic,
I found today Elite Plus for DOS boxed for $6.00. It's on two 5 1/4 floppies!! But 150 pages manual its pure game design gold.
5,25" floppies?!?! What did you do with those? Got a toaster with a USB port?  Anyhow, the novel included in the manual alone is worth the price! Still playing it on DOS-Box. By the way, the original Elite was about 22k in size ... so quite a challenge to wrestle it down to 4k, despite all the help the api provides. Brief status update: Finished the second iteration last week and ended up at 5.5k compressed. Now coding the third iteration, with many new ideas ... there is hope  It looks like the space sim part (including models and all string resources for the trade sim) will end up at 3.2k, so at least a streamlined version of the trade sim should be possible. Stuff I definitely had to drop: Station and docking (you just fly straight at the planet until you automatically land), Thargoids, multi-galaxy, tribbles  . Stuff that exceeds the original elite: Flat-Shaded graphics  and the option to purchase a new ship (as a long-term motivation) Galaxy size is 1024 systems. Boa, Mamba, Kraith, Viper and Cobra are included. So is asteroid mining, cargo pickup, refuelling at sun. Nuff said, now I better go and live up to the promises
|
|
|
|
|
|