Outside minor bugs, my sole OW focus for the past month has been refactoring the code for clarity and responsibility delineation, but especially strictly proper javascript. This involves not taking advantage of the allowable slop the interpreter creates, such as going back and forth between creatures[‘fish’] and the alternative creatures.fish syntax.
The main reason for these rather time-consuming changes is the symbol resolution performed by closure. Since javascript is not compiled and loosely typed, it doesn’t have to make up its mind about several things until it is being run. However, going through the preprocessing step of closure necessitates a structure closer to what a compiler would demand on the first pass.
Actually, I’ve been running closure since the release last Christmas. However, I am now running with the ADVANCED_COMPILATION option enabled.
Here’s an example of fresh, unadulterated code from my own fingers.
Map.prototype.useThread = function() { var threadBlock = this.threadBlock(); if (threadBlock == 1) { log.soulMovement(player, player.pos, player.pos, move_shake); return 1; } else if (threadBlock) { g.setCutscene("quest", "Something blocks the path!", "local", "hero", "north_still", threadBlock.getImage(), null, 'upstairs'); return 1; } var oSoul = this.getTile(origin).getSoul(); if (oSoul) { g.setCutscene("quest", "You try to escape, but end up where you started!", wall, null, null, 'hero', 'south_still', 'upstairs'); return 1; } player.teleportTarget = { 'map': this, 'pos': origin, }; player.incMoves(); player.deed("escape"); player.interrupt(); return 0; };
Now, here is the partially obfuscated code: locally scoped variables can be renamed at the non-advanced level, and of course whitespace and comments are easy to filter.
Map.prototype.useThread=function(){var a=this.threadBlock();if(1==a)return log.soulMovement(player,player.pos,player.pos,move_shake),1;if(a)return g.setCutscene(“quest”,”Something blocks the path!”,”local”,”hero”,”north_still”,a.getImage(),null,”upstairs”),1;if(this.getTile(origin).getSoul())return g.setCutscene(“quest”,”You try to escape, but end up where you started!”,wall,null,null,”hero”,”south_still”,”upstairs”),1;player.teleportTarget={map:this,pos:origin};player.incMoves();player.deed(“escape”);
Finally, here is the latest code I am running. Symbols from within the Overworld engine can now also be changed, and more aggressive code restructuring can be applied.
Map.prototype.Ic=function(){var a=this.wa();if(1==a)return z(B,B.b,B.b,-7),1;if(a)return I.C(“quest”,”Something blocks the path!”,”local”,”hero”,”north_still”,a.j(),null,”upstairs”),1;if(this.g(v).D())return I.C(“quest”,”You try to escape, but end up where you started!”,”wall”,null,null,”hero”,”south_still”,”upstairs”),1;B.Ya={map:this,pos:v};B.qc();fb(B,”escape”);B.ea();return 0};
Another tricky part of the procedure stemmed from OW not operating in a vacuum. It requires access to the pixi.js engine, and because that is a 3rd party self-contained library, I don’t want to (and indeed, cannot) run it through the compilation. Pixi.js has no onus to be strict, and I want to continue to update their library. So that presents a problem: An integrated system cannot be put through variable and function renaming when it needs static references to a portion of its system.
The solution was to first segregate all the code related to rendering, anything which referred to pixi.js. These files I’d given a pixi* suffix and their own folder, so that was not difficult. The hard part was sanitizing references between rendering code and the game engine.
First, it had to be ensured that the renderer was only consuming engine code, never the reverse (ie. the engine could be run in a vacuum). This was not the case in all places, especially IO. Keypresses had to stop invoking engine methods directly, and an event system was put in place. Next, tools that were common to both renderer and engine had to be exported, this meant telling closure to not change certain variable names in the target code, where they were also used by the renderer. This was mostly bare-bones stuff like a “clone” function, and some constants. Finally, references the compiled code needed to make to external code (the renderer) had to be externed, a subtle but important distinction, also resulting in symbol intransience but under different auspices and methods.
Why do this?
- Code efficiency: The compiled code achieves double-digit gains in overall size and runtime efficiency.
- Obfuscation: While no actual barrier to reading and understanding the javascript (which, of course, must be accessible to the browser in plaintext form, and travel over poorly encrypted mediums), obfuscation affords a first line of defense. The code can be prettified (whitespace readded) right in the chrome browser, but as evinced this is at best a partial aid to comprehension. It would probably take an intelligent person at least a few days to renovate it into something readable again, and that person hopefully has other things to do with their time.
- Engine isolation: This work will make it easy for me to completely replace the renderer at a later date, which if I plan to make any money on this deal is likely to happen at some point.
This is just a first step towards serious production readiness. In its final state the game will run in a cell or PC app, and no doubt incorporate additional hurdles to any kind of dissection. At that point, after a commercial transition, the mechanics of the game will be laid bare regardless and the security focus will shift to the back end.
Permalink
Permalink