I found a few spare minutes tonight to work on fixing the AI; managed to find the graphical glitch (took about 20s) and another 4 sections of code that had bugs. Much to my dismay, the guards still run in the wrong direction on the first demo level.
For those interested, the debugging process atm consists of opening both the Apple II and Coco 3 versions in the MESS emulator side-by-side, and running them (manually) in lock-step, comparing key results throughout the AI routines. Painful but a no-brainer! And it's actually fortunate in this instance that the very first guard starts off in the wrong direction - it should mean that the first pass through the AI logic will reveal most of the bugs! I mean, I've found 4 bugs already and I've only stepped through about a quarter of the AI logic!
It might seem to some that there are an awful lots of bugs in the AI code (I'm looking at you James) but you need to remember that I've added over 700 lines of 6809 without much visible feedback along the way. When porting the mechanics of movement, I was able to code small chunks of routines and - usually - immediately see whether or not it actually worked. And then fix the bugs before blogging about them!
I've also been thinking a bit about the colour version. I'm pretty sure that with clever use of the palette I'll be able to render both colour and monochrome versions of the Apple II display in the Coco3's 4-colour mode using the same graphics data. This of course means there's no reason to retain the 2-colour version of the game... though being the pack-rat that I am, it'll probably remain in the source only .ifdef'd out.
And that also goes for the Neo Geo version, of course. On that note, I've decided to tackle an automated static translation of the original 6502 code - it should be an interesting exercise in itself! I guess once I've got a 68K source base, I could also look at porting to other platforms such as Amiga, Atari ST and some other more obscure platforms.
But for now, back to debugging...
This blog chronicles my progress porting various retro games to other retro platforms. The goal in each project - at least when targeting a new CPU - is to effectively replicate the original graphics and the original code line-by-line, to produce a 100% accurate port of the original game.
Pages
▼
Saturday, 31 May 2014
Friday, 30 May 2014
Mostly Playable
Tonight I finished porting the guard AI. I also did a quick audit of the ported code and discovered a few minor routines that I hadn't done and also a few lines here-and-there related to, amongst other things, the speed of the guards that I've subsequently completed.
However, there are clearly one or more bugs in the AI code (one of which causes a graphical glitch - they're generally easier to find) but nothing too major. The guards do wander around and occasionally act intelligently but more often than not get stuck or stand still. Regardless, it's a major milestone and within a session or two I'm hoping to have the demo screens running correctly and a 100% accurate and fully playable game!
It's also very good to note that without the delay loop (hack) in the main game loop it still runs too fast! Once I've ironed out the above-mentioned bugs in the AI code I'll port the original game speed throttling code, and then release a demo for beta testing whilst I attend to the rest of the missing features I detailed in my last post.
Lode Runner is approaching 4,200 lines of 6809 and it's interesting to note that my Neo Geo port of Donkey Kong is roughly the same size (68K code of course) but is only 50% complete!
I've been thinking a bit about a Neo Geo port of Lode Runner; hardware platform issues aside, it probably wouldn't be too difficult to write a 6502-68000 translator, especially given my now-intimate knowledge of the code base - even if it was in part Lode Runner-specific. That plus the fact that the rendering routines would be replaced with much, much simpler sprites, it may not be a lot of work to get something running! Food for thought...
However, there are clearly one or more bugs in the AI code (one of which causes a graphical glitch - they're generally easier to find) but nothing too major. The guards do wander around and occasionally act intelligently but more often than not get stuck or stand still. Regardless, it's a major milestone and within a session or two I'm hoping to have the demo screens running correctly and a 100% accurate and fully playable game!
It's also very good to note that without the delay loop (hack) in the main game loop it still runs too fast! Once I've ironed out the above-mentioned bugs in the AI code I'll port the original game speed throttling code, and then release a demo for beta testing whilst I attend to the rest of the missing features I detailed in my last post.
Lode Runner is approaching 4,200 lines of 6809 and it's interesting to note that my Neo Geo port of Donkey Kong is roughly the same size (68K code of course) but is only 50% complete!
I've been thinking a bit about a Neo Geo port of Lode Runner; hardware platform issues aside, it probably wouldn't be too difficult to write a 6502-68000 translator, especially given my now-intimate knowledge of the code base - even if it was in part Lode Runner-specific. That plus the fact that the rendering routines would be replaced with much, much simpler sprites, it may not be a lot of work to get something running! Food for thought...
Wednesday, 28 May 2014
More on the AI and overall progress
Last night I finished commenting the guard AI code. FTR there's approximately 700 lines of 6502 assembler to produce almost exactly 1KB of object code for the AI. It's straight-forward enough to decipher on a line-by-line basis; it's when I try to wrap my head around the algorithm as a whole that it takes a few more brain cells. I'm most of the way there I think.
It also doesn't help that one of the first sections of the algorithm that I tackled has a bug. I'm yet to confirm this, but I believe that this bug is (at least partly) responsible for the instances where the guards actually run away from the player. You may be tempted to suggest that it's a feature rather than a bug, but the very nature of it suggests to me that it is indeed a bug, even if the behaviour was an unintended side-effect that Doug Smith decided to retain.
Roughly speaking, as far as I've deduced the guards attempt to get themselves - first and foremost - onto the same level (row) as the player, and the search algorithm for a path is quite exhaustive, which means a lot of code gets executed. When scanning down to see if the guard can get to the same level, it (also) checks each side of that column to see if there is a route to the left or right. Checking the route to the left is OK, but when checking to the right, under some circumstances the aforementioned bug causes it to check the wrong row. So it sometimes sees an obstruction when there isn't one, and will run away when in fact the guard is only a few (empty) tiles away from the player. Other times the guard will get stuck trying to follow a non-existent route (e.g. when you see them looping on ladders).
Anyway, I'm yet to port the code and when I've done so, I'll experiment more to document the exact behaviour caused by the bug. I can also 'fix' it and see if the game is any harder.
I also did a quick 'audit' of the original code and I'm satisfied that I've identified most of the code and that those sections that I haven't commented can be attributed to the level editor or other sections that I know about but haven't ported yet. On this note, a list of what is yet to be ported includes:
Aside from the above, there's only the matter of saving/loading high scores (if I opt to implement that) and of course the level editor, which I have no plans to port at this stage.
And for the case of the Coco3, it'll be tight but I still believe it will be possible to have a 4-colour mode version of the game. The cartridge version is looking a little less likely at this stage, at least without some fancy bank-switching hardware on the cartridge itself.
I've also come across a few small sections of dead code. This includes a look-up table that determines the time that a guard will remain in a hole before attempting to escape, whose index is determined not only by the number of guards on the level, but also by an unknown variable whose value never appears to be set to anything other than zero - unless there is a problem loading/displaying the level!?! Weird...
Right now I need to finish porting the AI and then ensure that the demo levels run correctly. Once I'm satisfied that the game-play is intact and 100% faithful to the original, I'll release a playable demo of the first 5 (or so) levels whilst I finish off the bells and whistles.
It also doesn't help that one of the first sections of the algorithm that I tackled has a bug. I'm yet to confirm this, but I believe that this bug is (at least partly) responsible for the instances where the guards actually run away from the player. You may be tempted to suggest that it's a feature rather than a bug, but the very nature of it suggests to me that it is indeed a bug, even if the behaviour was an unintended side-effect that Doug Smith decided to retain.
Roughly speaking, as far as I've deduced the guards attempt to get themselves - first and foremost - onto the same level (row) as the player, and the search algorithm for a path is quite exhaustive, which means a lot of code gets executed. When scanning down to see if the guard can get to the same level, it (also) checks each side of that column to see if there is a route to the left or right. Checking the route to the left is OK, but when checking to the right, under some circumstances the aforementioned bug causes it to check the wrong row. So it sometimes sees an obstruction when there isn't one, and will run away when in fact the guard is only a few (empty) tiles away from the player. Other times the guard will get stuck trying to follow a non-existent route (e.g. when you see them looping on ladders).
Anyway, I'm yet to port the code and when I've done so, I'll experiment more to document the exact behaviour caused by the bug. I can also 'fix' it and see if the game is any harder.
I also did a quick 'audit' of the original code and I'm satisfied that I've identified most of the code and that those sections that I haven't commented can be attributed to the level editor or other sections that I know about but haven't ported yet. On this note, a list of what is yet to be ported includes:
Accurate game speed throttling- Circular wipe at the start and end of each level/life
Spinning 'GAME OVER' text animationRestoration of game screen after High Score screen displayed mid-gameHigh score table (itself)High score determination andname entry- Storage and loading of the entire 150 levels
- And last but certainly not least - sound!
Aside from the above, there's only the matter of saving/loading high scores (if I opt to implement that) and of course the level editor, which I have no plans to port at this stage.
And for the case of the Coco3, it'll be tight but I still believe it will be possible to have a 4-colour mode version of the game. The cartridge version is looking a little less likely at this stage, at least without some fancy bank-switching hardware on the cartridge itself.
I've also come across a few small sections of dead code. This includes a look-up table that determines the time that a guard will remain in a hole before attempting to escape, whose index is determined not only by the number of guards on the level, but also by an unknown variable whose value never appears to be set to anything other than zero - unless there is a problem loading/displaying the level!?! Weird...
Right now I need to finish porting the AI and then ensure that the demo levels run correctly. Once I'm satisfied that the game-play is intact and 100% faithful to the original, I'll release a playable demo of the first 5 (or so) levels whilst I finish off the bells and whistles.
Monday, 26 May 2014
The guards are getting smarter!
As I mentioned, the AI for when the guard is on the same row as the player is quite simple; if there's a direct path to the player, then move towards the player! Pretty intuitive stuff.
So this much I've implemented, and I can confirm that it's enough to get the guards escaping from the holes if the player remains on the same level. Yah! I did discover another minor glitch though - when the guard interrupts the digging it isn't cleaned up properly. I'll get to that in due course...
This morning I hit memory problems again (the code is about 3,500 lines and assembles to about 8KB now), so it was time to move the title screen data out of the source code and load it into high memory. Here's the breakdown of the Coco's 64KB memory space so far:
Each colour tile will require twice the data, but since I'll only need two (2) sets instead of four, it should occupy the same space. Likewise since the title screen data is stored in RLE format, I'm not expecting it to require too much more memory either.
Anyway, onwards with the AI for now...
So this much I've implemented, and I can confirm that it's enough to get the guards escaping from the holes if the player remains on the same level. Yah! I did discover another minor glitch though - when the guard interrupts the digging it isn't cleaned up properly. I'll get to that in due course...
This morning I hit memory problems again (the code is about 3,500 lines and assembles to about 8KB now), so it was time to move the title screen data out of the source code and load it into high memory. Here's the breakdown of the Coco's 64KB memory space so far:
- monochrome screen #1 $0000-$1DFF (8KB)
- monochrome screen #2 $2000-$3DFF (8KB)
- 6502 zero-page memory variables $3F00-$3FFF
- program code & data $4000-$6DFF (assembler output)
- monochrome tile data (4 sets) $8000-$A3C0 (pre-loaded from a disk file)
- monochrome title data $A800-$B993 (pre-loaded from a disk file)
Each colour tile will require twice the data, but since I'll only need two (2) sets instead of four, it should occupy the same space. Likewise since the title screen data is stored in RLE format, I'm not expecting it to require too much more memory either.
Anyway, onwards with the AI for now...
Saturday, 24 May 2014
Monkey See, Monkey Do
I've finished - but not completely debugged - the mechanics of the guard movement.
There's no AI to speak of yet; I've implemented some test code whereby the guards simply mirror the player or, more correctly, the keys held down by the player. Here's a short video to show the guards all moving, falling into holes and re-spawning.
You'll notice they'll actually start to climb - and then subsequently fall - whenever the player tries to move up, regardless of whether there's a ladder there or not. I started to debug this behaviour and then discovered that there was nothing at all in the guard movement code preventing this from happening! Turns out that the code relies on the AI routines to not even attempt to move up without a ladder.
To this end I've started to comment the guard AI. I was pleasantly surprised to find that the logic for when the guard is on the same row as the player is quite straight-forward. That got my hopes up that the rest of the AI would be just as simple - alas that's not the case at all. It gets significantly more involved when the guards are on a different row...
I'm still not sure whether the guards escaping from the holes is driven by the movement mechanics or the AI - they don't yet do that correctly as you'll notice in the video. From here-on in it's a matter of reverse-engineering the AI, adding it in sections, and continuing to test the movement until I've ported every line and the demo plays correctly. On that note, the re-spawn points are deterministic, and I suspect there's no random element at all to the AI either - I can't see how the demo would work if there was.
Getting down to the business end of the port now!
EDIT: After implementing a small part of the AI, it seems that the guards' escaping from holes is actually driven by the AI, not the movement mechanics. It's possible then that the mechanics are complete and working. Onwards with the AI now...
There's no AI to speak of yet; I've implemented some test code whereby the guards simply mirror the player or, more correctly, the keys held down by the player. Here's a short video to show the guards all moving, falling into holes and re-spawning.
You'll notice they'll actually start to climb - and then subsequently fall - whenever the player tries to move up, regardless of whether there's a ladder there or not. I started to debug this behaviour and then discovered that there was nothing at all in the guard movement code preventing this from happening! Turns out that the code relies on the AI routines to not even attempt to move up without a ladder.
To this end I've started to comment the guard AI. I was pleasantly surprised to find that the logic for when the guard is on the same row as the player is quite straight-forward. That got my hopes up that the rest of the AI would be just as simple - alas that's not the case at all. It gets significantly more involved when the guards are on a different row...
I'm still not sure whether the guards escaping from the holes is driven by the movement mechanics or the AI - they don't yet do that correctly as you'll notice in the video. From here-on in it's a matter of reverse-engineering the AI, adding it in sections, and continuing to test the movement until I've ported every line and the demo plays correctly. On that note, the re-spawn points are deterministic, and I suspect there's no random element at all to the AI either - I can't see how the demo would work if there was.
Getting down to the business end of the port now!
EDIT: After implementing a small part of the AI, it seems that the guards' escaping from holes is actually driven by the AI, not the movement mechanics. It's possible then that the mechanics are complete and working. Onwards with the AI now...
Friday, 23 May 2014
Left, right and up!
Fixed a few more glitches - including the 1st frame glitch - and added right and up to the guard's repertoire. As for movement, only down to go, although there are still a few more minor issues with the guard movement as a whole. The guards also die and re-spawn.
I'll press on with adding down and then look at solving the remaining issues.
Aside from the AI, I really am starting to round out the code. I'm missing the high score entry routine, and the circular wipe, but there's not a lot else aside from the editor, which I'm not planning on porting. I'll need to write my own level data access routines, which will likely be bank-switched into memory to negate the need for any DOS (and also facilitate a cartridge version).
But, I'm getting ahead of myself here. The immediate task is to complete guard movement.
I'll press on with adding down and then look at solving the remaining issues.
Aside from the AI, I really am starting to round out the code. I'm missing the high score entry routine, and the circular wipe, but there's not a lot else aside from the editor, which I'm not planning on porting. I'll need to write my own level data access routines, which will likely be bank-switched into memory to negate the need for any DOS (and also facilitate a cartridge version).
But, I'm getting ahead of myself here. The immediate task is to complete guard movement.
Thursday, 22 May 2014
Still cranking and squashing
Squashed another handful of bugs in the guard code, but still not done!
Discovered in the process that the player movement code also had a bug; the rendering looked fine on the screen but it didn't update one of the 'logical playfields' correctly - effectively leaving a clone of the player on top of every ladder they've climbed. Made it somewhat easier for the guards to kill the player!
I've deduced that the unknown guard state variable is, in a nutshell, the state of the guard. I'm yet to work out the meaning of all the different values, but 7-12 mean that the guard is wriggling to get out of a hole and - I think - negative means he's carrying gold!?! There's a bit more to it, too.
The guard state is also influenced by other variables, including an initialiser in the main game loop that takes into account the total number of guards on the level, amongst other things. A look-up table is then used to calculate the actual initial value. This is unexpectedly complicating matters somewhat, as the 'wrong' initialiser value causes guards to behave improperly.
Anyway, the guards all run left (and fall) properly now (except for a graphics glitch after the very first frame of gameplay), but are yet to be killed by the holes filling in. That I'll be implementing next session, and will hopefully shed further light on the guard status byte.
Still plenty to do, I'm afraid.
Discovered in the process that the player movement code also had a bug; the rendering looked fine on the screen but it didn't update one of the 'logical playfields' correctly - effectively leaving a clone of the player on top of every ladder they've climbed. Made it somewhat easier for the guards to kill the player!
I've deduced that the unknown guard state variable is, in a nutshell, the state of the guard. I'm yet to work out the meaning of all the different values, but 7-12 mean that the guard is wriggling to get out of a hole and - I think - negative means he's carrying gold!?! There's a bit more to it, too.
The guard state is also influenced by other variables, including an initialiser in the main game loop that takes into account the total number of guards on the level, amongst other things. A look-up table is then used to calculate the actual initial value. This is unexpectedly complicating matters somewhat, as the 'wrong' initialiser value causes guards to behave improperly.
Anyway, the guards all run left (and fall) properly now (except for a graphics glitch after the very first frame of gameplay), but are yet to be killed by the holes filling in. That I'll be implementing next session, and will hopefully shed further light on the guard status byte.
Still plenty to do, I'm afraid.
Wednesday, 21 May 2014
3,000 lines
Wasn't going to post but I had a little bit of a win tonight. And I should mention that the 6809 code has reached approximately 3,000 lines (sans data).
I've got the guards running/swinging left (and falling) with the player, albeit with a handful of glitches on the screen and in the logic (eg. they tend to leave 'holes' in the bricks occasionally). But after porting all the 'scaffolding' code for the guards (look-up tables, calculation routines, etc) the mechanics of moving are in place and after fixing the bugs - which I suspect won't be too difficult - I'll just need to fill out the routines for the other 3 directions. That'll be a case of simply cranking the handle for a few sessions.
After that there's a bit more logic to fill out to do with guard status, picking up and dropping gold etc but that's all fairly straightforward - although I must admit there's a couple of memory variables related to the guards whose function I'm not 100% clear on.
I also took a quick peek at the guard AI routine just to gauge how much code there was, but stopped looking after scrolling down a few screens... mine's a bit simpler:
guard_ai: ; $70D8
; A=col, B=row
; ret: B=0..4 (direction)
lda *key_1
ldb #1
cmpa #0xca
beq 9$
ldb #0
9$: rts
It effectively says, "move left if the player tries to move left, else don't move".
I've got the guards running/swinging left (and falling) with the player, albeit with a handful of glitches on the screen and in the logic (eg. they tend to leave 'holes' in the bricks occasionally). But after porting all the 'scaffolding' code for the guards (look-up tables, calculation routines, etc) the mechanics of moving are in place and after fixing the bugs - which I suspect won't be too difficult - I'll just need to fill out the routines for the other 3 directions. That'll be a case of simply cranking the handle for a few sessions.
After that there's a bit more logic to fill out to do with guard status, picking up and dropping gold etc but that's all fairly straightforward - although I must admit there's a couple of memory variables related to the guards whose function I'm not 100% clear on.
I also took a quick peek at the guard AI routine just to gauge how much code there was, but stopped looking after scrolling down a few screens... mine's a bit simpler:
guard_ai: ; $70D8
; A=col, B=row
; ret: B=0..4 (direction)
lda *key_1
ldb #1
cmpa #0xca
beq 9$
ldb #0
9$: rts
It effectively says, "move left if the player tries to move left, else don't move".
Sunday, 18 May 2014
Guarded optimism
I've spent the last few sessions - several hours - reverse-engineering the code for the guards.
I've long suspected (and by long, I mean a good quarter of a century) that the design of the guards - in the player's image - was solely to enable the use of the same movement logic & code. As it turns out, I'm only half right; the code is very, very similar but it is duplicated in its entirety for the guards.
The good news is that it's very simple to reverse-engineer, for the most part, as a lot of it really is a carbon copy of the player movement code. The bad news is - there's an awful lot of it! More than the player movement code because it needs to handle 5 guards, and it needs to handle other logic like re-spawning.
There are three main areas of guard code that I need to implement; the death of the guards when they fall into a hole and get filled-in (fairly trivial), the movement (both AI and mechanics) of the guards, and the re-spawning of the guards when they are resurrected.
Of course the guard code only deals with the current guard; a table of state data gets copied to-and-fro for each of the guards during the main loop iteration. The guards also need a bit more state data than the player, such as dead/alive and whether or not they're carrying gold.
As it is, I've probably done enough to get a start on the porting, and the rest should come out in the wash while I'm doing so. I've located the AI routine but haven't yet peeked inside it - that will come last. I've already decided that for testing the guard mechanics, I'll simply have them mirror the player's movements, which will of course allow me to manoeuvre them manually. Furthermore, I've already worked out exactly how to do that!
Given the amount of code that needs to be ported, I'd expect it'll be a week or so before I have the mechanics in place, but I'm optimistic that it will be quite straightforward.
The AI is another issue altogether.
I've long suspected (and by long, I mean a good quarter of a century) that the design of the guards - in the player's image - was solely to enable the use of the same movement logic & code. As it turns out, I'm only half right; the code is very, very similar but it is duplicated in its entirety for the guards.
The good news is that it's very simple to reverse-engineer, for the most part, as a lot of it really is a carbon copy of the player movement code. The bad news is - there's an awful lot of it! More than the player movement code because it needs to handle 5 guards, and it needs to handle other logic like re-spawning.
There are three main areas of guard code that I need to implement; the death of the guards when they fall into a hole and get filled-in (fairly trivial), the movement (both AI and mechanics) of the guards, and the re-spawning of the guards when they are resurrected.
Of course the guard code only deals with the current guard; a table of state data gets copied to-and-fro for each of the guards during the main loop iteration. The guards also need a bit more state data than the player, such as dead/alive and whether or not they're carrying gold.
As it is, I've probably done enough to get a start on the porting, and the rest should come out in the wash while I'm doing so. I've located the AI routine but haven't yet peeked inside it - that will come last. I've already decided that for testing the guard mechanics, I'll simply have them mirror the player's movements, which will of course allow me to manoeuvre them manually. Furthermore, I've already worked out exactly how to do that!
Given the amount of code that needs to be ported, I'd expect it'll be a week or so before I have the mechanics in place, but I'm optimistic that it will be quite straightforward.
The AI is another issue altogether.
Friday, 16 May 2014
Freeing up memory
As I mentioned in my previous post, I've hit the (free) RAM limit on the Coco. With 16KB reserved for screen memory, and 32KB reversed for BASIC and DISK ROM, that only leaves 16KB for all the code and data for Lode Runner, before I need to start loading in sections.
I went back to my old Tutankham port to see how I handled loading of the numerous bank-switched ROM images, expecting to find a loader that I had written. What I (re)discovered was a much neater solution; a multi-segment .BIN file that updated the MMU registers directly during the load, and restored them on completion. So it was simply a matter of updating my tool to produce a .BIN file for the tile data (in addition to ASM .db statements) that would load itself into banked memory.
And to make my life a little easier, I wrote a small BASIC loader to streamline the now-multi file loading operation - something I've already done hundreds of times during the course of this port.
With the tile data loaded externally, I've been able to restore the title screen and a handful of levels, and I still have 4KB of code/data space remaining. Eventually I'll move the title and level data into external files as well, possibly loading the levels from disk, and implementing high score load/save.
And there's always the cartridge version...
So, now onto demo mode!
UPDATE: I've now implemented the demo mode - it was even easier than I dared hope! A simple table look-up that pokes keys into the key input registers, with a counter for each entry that determines how many game loop iterations the 'key' is held for.
BUT: it didn't take long to discover that the demo mode wasn't going to work properly without the guards - only a few seconds into the demo and the player digs a hole, the guard falls in, and the player runs over the guard. But without the guards implemented...
No worries, it's all implemented and now it's time to tackle the guards!!!
I went back to my old Tutankham port to see how I handled loading of the numerous bank-switched ROM images, expecting to find a loader that I had written. What I (re)discovered was a much neater solution; a multi-segment .BIN file that updated the MMU registers directly during the load, and restored them on completion. So it was simply a matter of updating my tool to produce a .BIN file for the tile data (in addition to ASM .db statements) that would load itself into banked memory.
And to make my life a little easier, I wrote a small BASIC loader to streamline the now-multi file loading operation - something I've already done hundreds of times during the course of this port.
With the tile data loaded externally, I've been able to restore the title screen and a handful of levels, and I still have 4KB of code/data space remaining. Eventually I'll move the title and level data into external files as well, possibly loading the levels from disk, and implementing high score load/save.
And there's always the cartridge version...
So, now onto demo mode!
UPDATE: I've now implemented the demo mode - it was even easier than I dared hope! A simple table look-up that pokes keys into the key input registers, with a counter for each entry that determines how many game loop iterations the 'key' is held for.
BUT: it didn't take long to discover that the demo mode wasn't going to work properly without the guards - only a few seconds into the demo and the player digs a hole, the guard falls in, and the player runs over the guard. But without the guards implemented...
No worries, it's all implemented and now it's time to tackle the guards!!!
A 'hole' lot more...
In a nutshell, the player movement and interaction with the environment is complete. Running, climbing, swinging, falling, digging and collecting gold is all done, and the holes now fill themselves in, killing the player if they happen to be in the hole.
Here's an updated video, showing the first 2 levels of the game. I've had to remove the title screen due to memory restrictions on the Coco3. I've also removed the guards from the game entirely because they affect the ability to dig. In the video I purposefully fall into a hole to show the player losing a life, and then use CTRL-R to end the game.
All that remains now, as far as I believe, is the demo mode logic (just a large look-up table) and the logic related to the guards. I'm sure there's also some housekeeping stuff I'm missing as well (like high score entry), but that will fall out in the wash.
The code, without the large data tables, is heading towards 2,500 lines now - roughly 4.5KB of 6809 object code. With the graphics data and a handful of levels the executable object is ~16KB.
To be honest, I've been quite pleasantly surprised by how smoothly the port has gone, and how easy the reverse-engineering has proven to be thus far - especially given my lack of experience with either the 6502 or the Apple II. It has certainly been easier than (arcade) Donkey Kong, for example! I can only hope that the remainder doesn't throw up too many challenges, though I suspect the guard AI will be the real thorn-in-the-side!
The next task is to get the demo mode running before I move onto the guard logic. I'm not expecting too many issues there, as it is little more than a large table look-up, and it should run correctly even in the absence of the enemy AI.
Here's an updated video, showing the first 2 levels of the game. I've had to remove the title screen due to memory restrictions on the Coco3. I've also removed the guards from the game entirely because they affect the ability to dig. In the video I purposefully fall into a hole to show the player losing a life, and then use CTRL-R to end the game.
All that remains now, as far as I believe, is the demo mode logic (just a large look-up table) and the logic related to the guards. I'm sure there's also some housekeeping stuff I'm missing as well (like high score entry), but that will fall out in the wash.
The code, without the large data tables, is heading towards 2,500 lines now - roughly 4.5KB of 6809 object code. With the graphics data and a handful of levels the executable object is ~16KB.
To be honest, I've been quite pleasantly surprised by how smoothly the port has gone, and how easy the reverse-engineering has proven to be thus far - especially given my lack of experience with either the 6502 or the Apple II. It has certainly been easier than (arcade) Donkey Kong, for example! I can only hope that the remainder doesn't throw up too many challenges, though I suspect the guard AI will be the real thorn-in-the-side!
The next task is to get the demo mode running before I move onto the guard logic. I'm not expecting too many issues there, as it is little more than a large table look-up, and it should run correctly even in the absence of the enemy AI.
Thursday, 15 May 2014
Scaffolding
A surprisingly productive day today!
Managed to complete the left and right digging, though holes are yet to be filled-in.
The rest of the progress was scaffolding around the main loop and in-game keys. There's a proper distinction between attract mode and game play mode now, and you can exit from the former. The high score display screen (with dummy data) is also in the outer loop now, and it all transitions properly, and the correct game levels are displayed in both demo and play modes.
There's a bunch of in-game CTRL key functions including killing your player (for when you get stuck in holes that enemies can't get to), adding an extra life, skipping to the next level, freezing the game, terminating the game, and displaying the high scores. Most of these have been implemented now, as they are very handy for testing!
There are now 5 game levels - in addition to the 3 demo levels - and I'll probably leave it at that when I release a playable demo due to space restrictions. In fact, I'm almost at the limit for the Coco3 now (16KB program space from $4000-$7FFF) without having to resort to writing loaders to shift data into high memory.
The two functions I want to complete next are filling in the holes, and running the attract mode logic. (once these are complete, all that remains is the code for rendering and the enemy and the AI). I've identified both but am yet to decide which to implement first. The latter will be easier, but won't work properly until the former is complete. Decisions, decisions...
Managed to complete the left and right digging, though holes are yet to be filled-in.
The rest of the progress was scaffolding around the main loop and in-game keys. There's a proper distinction between attract mode and game play mode now, and you can exit from the former. The high score display screen (with dummy data) is also in the outer loop now, and it all transitions properly, and the correct game levels are displayed in both demo and play modes.
There's a bunch of in-game CTRL key functions including killing your player (for when you get stuck in holes that enemies can't get to), adding an extra life, skipping to the next level, freezing the game, terminating the game, and displaying the high scores. Most of these have been implemented now, as they are very handy for testing!
There are now 5 game levels - in addition to the 3 demo levels - and I'll probably leave it at that when I release a playable demo due to space restrictions. In fact, I'm almost at the limit for the Coco3 now (16KB program space from $4000-$7FFF) without having to resort to writing loaders to shift data into high memory.
The two functions I want to complete next are filling in the holes, and running the attract mode logic. (once these are complete, all that remains is the code for rendering and the enemy and the AI). I've identified both but am yet to decide which to implement first. The latter will be easier, but won't work properly until the former is complete. Decisions, decisions...
Wednesday, 14 May 2014
Diggin' it!
Some more good progress today!
When you collect all the gold, the end-of-screen ladder appears and climbing to the top awards the end-of-level bonus (1,500 pts), taking you to the next level. To this end I have included the three demo levels to complement the 1st in-game level.
I've also implemented digging (to the left); you can dig the hole but it will never fill in. The (same) digging routine is actually re-entered each iteration of the in-game loop to sequence through the digging animation. There are still some issues with it, but it's looking good... I'll do a video once digging is complete!
Whilst the porting continues, I'm also chipping away at the reverse-engineering as I work. The in-game loop is starting to take shape and I can see where the holes are filled in, and the enemies are re-spawned once they die - although I'm yet to comment these in any detail.
Once I've completed the digging routines, I'll do the fill-in and then I'll investigate what is required to implement the demo. I should then be able to round out the outer loop with title screen, attract-mode demo, and high score table.
A very quick scroll through the listing suggests that I have commented a good portion of the code thus far. Once I've done the above-mentioned, it really just comes down to the enemy rendering and AI. The remainder of the code must then be the level editor. Still a lot of work to do, but I am just beginning to see the light at the end of the tunnel!
When you collect all the gold, the end-of-screen ladder appears and climbing to the top awards the end-of-level bonus (1,500 pts), taking you to the next level. To this end I have included the three demo levels to complement the 1st in-game level.
I've also implemented digging (to the left); you can dig the hole but it will never fill in. The (same) digging routine is actually re-entered each iteration of the in-game loop to sequence through the digging animation. There are still some issues with it, but it's looking good... I'll do a video once digging is complete!
Whilst the porting continues, I'm also chipping away at the reverse-engineering as I work. The in-game loop is starting to take shape and I can see where the holes are filled in, and the enemies are re-spawned once they die - although I'm yet to comment these in any detail.
Once I've completed the digging routines, I'll do the fill-in and then I'll investigate what is required to implement the demo. I should then be able to round out the outer loop with title screen, attract-mode demo, and high score table.
A very quick scroll through the listing suggests that I have commented a good portion of the code thus far. Once I've done the above-mentioned, it really just comes down to the enemy rendering and AI. The remainder of the code must then be the level editor. Still a lot of work to do, but I am just beginning to see the light at the end of the tunnel!
Monday, 12 May 2014
A video is worth 1500 lines of assembler
Today at lunchtime, and earlier this evening, I managed to get the player movement done, including gravity, and even had time to add the collecting of the titular gold 'lode'.
So without further ado, here's a video from MESS of Lode Runner in action on the Coco3:
My dilemma now is whether to continue the momentum with the Coco port - and put the Z80 version on ice - or take a step back and bring the TRS-80 version up-to-date. I'm concerned that the bandwidth for the Grafyx Solution hires board is simply not up to the task, without some optimisations, and quite frankly the prospect of porting the indexed-indirect code to the Z80 isn't exactly appealing.
At this stage I'm leaning towards continuing the momentum with the 6809 port, and getting the end-of-screen ladders appearing, then moving on to the player moving around in the attract mode, adding high scores, and getting the main loop completed.
So without further ado, here's a video from MESS of Lode Runner in action on the Coco3:
My dilemma now is whether to continue the momentum with the Coco port - and put the Z80 version on ice - or take a step back and bring the TRS-80 version up-to-date. I'm concerned that the bandwidth for the Grafyx Solution hires board is simply not up to the task, without some optimisations, and quite frankly the prospect of porting the indexed-indirect code to the Z80 isn't exactly appealing.
At this stage I'm leaning towards continuing the momentum with the 6809 port, and getting the end-of-screen ladders appearing, then moving on to the player moving around in the attract mode, adding high scores, and getting the main loop completed.
Late Sunday Night Update
I've been making very good progress over the last week. I've managed to find time for a few sessions of coding but I soon discovered that there was more code to write than I'd reckoned on.
I'd taken a few shortcuts previously on both the Z80 and 6809 ports. But now I'd reached the stage where I had to fill in the gaps.
First, I needed to implement the second hires page. I'd reserved the memory, but hadn't coded the routines to read the current page from the zero-page variable(s), so I implemented that. Relatively trivial, but I should note that the Coco now requires 2*8KB=16KB just for the mono screens (as does the Apple II, incidentally) . With the Coco ROM's that doesn't leave a lot of memory, and in fact for now I've removed the title page so I don't need to worry about loaders and switching out the ROM's just yet...
When the level data is first read from disk, a full sector (256 bytes) is read into a disk buffer in low memory. Since each level is 28*16=448 bytes, the level data is packed into nibbles. Thus the sector is subsequently unpacked - not into one but two (2) different buffers - static & live. I had only coded the one copy in order to display the level. Now it does both of course.
And when the level is rendered, it is actually rendered to the 2nd hires page, and then copied to the 1st during the famous circular wipe transition. The program then goes through and wipes the player and enemies from the 2nd page, which is used as the background tile-map during the game to make the sprite emulation more efficient.
So I had to render to the 2nd page, and then I do a straight copy to the 1st (as I'm not attempting the circular wipe at this point). I also added some debug code to toggle the displayed hires page with the <ENTER> key.
Then it was on to the player movement routines, which comprises reading the keyboard, checking the aforementioned level data to ascertain if the player can move, and then emulating sprites using the two hires pages. Here there was a reasonable amount of code to implement, including two basic rendering routines that weren't used for the level rendering, and a handful of support routines that calculate address offsets, pixel shifts, sprite-to-character mappings, etc. It was tedious and error-prone more than anything.
I've discovered that in some cases, the 6502 is actually more efficient than the 6809 - or at least my limited knowledge of the 6809 is preventing me from a line-by-line translation. On the 6502 it's possible to specify an offset (in a register) from a 16-bit address stored in zero-page memory; on the 6809 I can't see how to do this, so for now I've been using 2 instructions to compensate. Otherwise, the main source of confusion is mapping the A,X & Y registers to A,B & X/Y on the 6809. For the most part I've been able to get away with just A&B, since most routines only return one or two values (though sometimes in X and sometimes in Y) - but there's one routine that returns three values in the three 6502 registers, so it gets a little messy with the 6809's 16-bit X register.
As for actual progress, the player can now both run and swing left & right and climb up ladders. In truth, there's a few ladders on the 1st level that you can't (yet) climb, so I need to find that bug. But otherwise, it's working perfectly. Climbing down is just a matter of cranking the handle, and I've simply run out of time.
The good news is that most of the hard grind - the mechanics of rendering - has been done and the movement routines are relatively straight-forward. I also need to add gravity, but I recall seeing that in the Apple II disassembly so that shouldn't be too difficult either.
Once I've got the player moving around everywhere, and falling, I'll post a video of the Coco3 version on this blog. And then I guess I'll roll up my sleeves and bring the Z80 version up-to-date. To be honest, I'm not looking forward to that as there's a lot of index-indirect addressing mode code and the Z80 is going to be horrible (ADD HL,DE).
I'd taken a few shortcuts previously on both the Z80 and 6809 ports. But now I'd reached the stage where I had to fill in the gaps.
First, I needed to implement the second hires page. I'd reserved the memory, but hadn't coded the routines to read the current page from the zero-page variable(s), so I implemented that. Relatively trivial, but I should note that the Coco now requires 2*8KB=16KB just for the mono screens (as does the Apple II, incidentally) . With the Coco ROM's that doesn't leave a lot of memory, and in fact for now I've removed the title page so I don't need to worry about loaders and switching out the ROM's just yet...
When the level data is first read from disk, a full sector (256 bytes) is read into a disk buffer in low memory. Since each level is 28*16=448 bytes, the level data is packed into nibbles. Thus the sector is subsequently unpacked - not into one but two (2) different buffers - static & live. I had only coded the one copy in order to display the level. Now it does both of course.
And when the level is rendered, it is actually rendered to the 2nd hires page, and then copied to the 1st during the famous circular wipe transition. The program then goes through and wipes the player and enemies from the 2nd page, which is used as the background tile-map during the game to make the sprite emulation more efficient.
So I had to render to the 2nd page, and then I do a straight copy to the 1st (as I'm not attempting the circular wipe at this point). I also added some debug code to toggle the displayed hires page with the <ENTER> key.
Then it was on to the player movement routines, which comprises reading the keyboard, checking the aforementioned level data to ascertain if the player can move, and then emulating sprites using the two hires pages. Here there was a reasonable amount of code to implement, including two basic rendering routines that weren't used for the level rendering, and a handful of support routines that calculate address offsets, pixel shifts, sprite-to-character mappings, etc. It was tedious and error-prone more than anything.
I've discovered that in some cases, the 6502 is actually more efficient than the 6809 - or at least my limited knowledge of the 6809 is preventing me from a line-by-line translation. On the 6502 it's possible to specify an offset (in a register) from a 16-bit address stored in zero-page memory; on the 6809 I can't see how to do this, so for now I've been using 2 instructions to compensate. Otherwise, the main source of confusion is mapping the A,X & Y registers to A,B & X/Y on the 6809. For the most part I've been able to get away with just A&B, since most routines only return one or two values (though sometimes in X and sometimes in Y) - but there's one routine that returns three values in the three 6502 registers, so it gets a little messy with the 6809's 16-bit X register.
As for actual progress, the player can now both run and swing left & right and climb up ladders. In truth, there's a few ladders on the 1st level that you can't (yet) climb, so I need to find that bug. But otherwise, it's working perfectly. Climbing down is just a matter of cranking the handle, and I've simply run out of time.
The good news is that most of the hard grind - the mechanics of rendering - has been done and the movement routines are relatively straight-forward. I also need to add gravity, but I recall seeing that in the Apple II disassembly so that shouldn't be too difficult either.
Once I've got the player moving around everywhere, and falling, I'll post a video of the Coco3 version on this blog. And then I guess I'll roll up my sleeves and bring the Z80 version up-to-date. To be honest, I'm not looking forward to that as there's a lot of index-indirect addressing mode code and the Z80 is going to be horrible (ADD HL,DE).
Tuesday, 6 May 2014
Brief update
I've identified the main game loop, the keyboard read routines, and commented most (95%) of the movement routines - definitely enough to start porting and see the results.
There's a decent amount of code that needs to be written before I'll see the player moving around on the screen. It's probably not something that can be written in a single session. I also need to properly implement the dual screen logic as it is used extensively in the movement code, for example.
For this reason, I've decided to update the Coco3 (6809) port first. It's a bit simpler than the TRS-80 port since for the latter I'll need a shadow copy in RAM of the port-mapped hires screen, and hence I'm unable to make use of dual-purpose routines. The latter is also slower and more painful than the 6809 port - so much so that I'm actually tempted to put it on ice for the moment.
Next time I sit down to work on this I'll be able to start on the Coco3 port!
There's a decent amount of code that needs to be written before I'll see the player moving around on the screen. It's probably not something that can be written in a single session. I also need to properly implement the dual screen logic as it is used extensively in the movement code, for example.
For this reason, I've decided to update the Coco3 (6809) port first. It's a bit simpler than the TRS-80 port since for the latter I'll need a shadow copy in RAM of the port-mapped hires screen, and hence I'm unable to make use of dual-purpose routines. The latter is also slower and more painful than the 6809 port - so much so that I'm actually tempted to put it on ice for the moment.
Next time I sit down to work on this I'll be able to start on the Coco3 port!
Saturday, 3 May 2014
Spaghetti?
No more porting but I have spent some time on the reverse-engineering.
I've located the player movement (and dig) routines, which I eventually tracked down via the keyboard read call. I'm working through those now and have unearthed a few more crucial variables related to the player tracking throughout the play-field.
It's relatively early days yet, and I reserve the right to retract my statements, but I find some comfort in what I've found so far. Way back some 20+ years ago when I was attempting to write my TRS-80 version, I naively coded the player movement routines by handling each case explicitly in what equated to a rather lengthy case statement. It was classic spaghetti Z80 assembler, and whilst I knew it wasn't elegant, I was too eager to see results to attempt to formulate a more efficient algorithm.
On the surface, that appears to be pretty much how the original code was written. There's a ton of duplicated, cut 'n' paste code related to movement from what I've seen so far; perhaps not as bad as the code I first churned out but certainly nowhere near as elegant as the code that I finally crafted - albeit in its 3rd incarnation - on the PC. At least, it seems to be the case as far as I've gotten at this point.
It's also somewhat reassuring to note that the variables I've managed to identify thus far are heavily used throughout most of the codebase, in fact a surprisingly large percentage of the code seems to be concerned with manipulating these very variables, mostly related to movement (presumably of enemies and the player). If I'm right in assuming the same logic is used for both, then I guess there's little more to the actual game mechanics, other than digging (which should be relatively simple) and enemy AI.
What this suggest is, in stark contrast to a few other projects I've undertaken, that the actual reverse-engineering effort may be relatively straightforward, and somewhat devoid of instances of large sections of code whose functions defy explanation without some serious time & effort and even some guesswork and trial-and-error. That would be nice for a change!
I'll continue with the reverse-engineering for the time being as there are a few aspects of the movement that I'm yet to decipher that would prevent a functional port at this point. But I'm still on a roll and would expect that it won't be too much longer before I can draw a line in the sand and start the implementation of the movement routines.
I'm also sorely tempted to break with tradition and start with the 6809 this time 'round. We'll see...
I've located the player movement (and dig) routines, which I eventually tracked down via the keyboard read call. I'm working through those now and have unearthed a few more crucial variables related to the player tracking throughout the play-field.
It's relatively early days yet, and I reserve the right to retract my statements, but I find some comfort in what I've found so far. Way back some 20+ years ago when I was attempting to write my TRS-80 version, I naively coded the player movement routines by handling each case explicitly in what equated to a rather lengthy case statement. It was classic spaghetti Z80 assembler, and whilst I knew it wasn't elegant, I was too eager to see results to attempt to formulate a more efficient algorithm.
On the surface, that appears to be pretty much how the original code was written. There's a ton of duplicated, cut 'n' paste code related to movement from what I've seen so far; perhaps not as bad as the code I first churned out but certainly nowhere near as elegant as the code that I finally crafted - albeit in its 3rd incarnation - on the PC. At least, it seems to be the case as far as I've gotten at this point.
It's also somewhat reassuring to note that the variables I've managed to identify thus far are heavily used throughout most of the codebase, in fact a surprisingly large percentage of the code seems to be concerned with manipulating these very variables, mostly related to movement (presumably of enemies and the player). If I'm right in assuming the same logic is used for both, then I guess there's little more to the actual game mechanics, other than digging (which should be relatively simple) and enemy AI.
What this suggest is, in stark contrast to a few other projects I've undertaken, that the actual reverse-engineering effort may be relatively straightforward, and somewhat devoid of instances of large sections of code whose functions defy explanation without some serious time & effort and even some guesswork and trial-and-error. That would be nice for a change!
I'll continue with the reverse-engineering for the time being as there are a few aspects of the movement that I'm yet to decipher that would prevent a functional port at this point. But I'm still on a roll and would expect that it won't be too much longer before I can draw a line in the sand and start the implementation of the movement routines.
I'm also sorely tempted to break with tradition and start with the 6809 this time 'round. We'll see...