Part 32: Testing the Ai.
I wanted to write a successor of Dragontorc for the C64. As I did not want Hewson to have any claim over the game I decided it needed to be completely different. Also I thought C64 players were much more arcade orientated. The games that did well seemed to be fast action games. So I decided to design a game that was superficially like a beat-em-up but had the depth of a free format game here you could explore a huge scenario. I wanted to use the assets of the C64 to their full capability. That meant scrolling backdrops and multiplexed sprites. I started off with John Cumming's scroll routine from Soldier Of Fortune and tidied it up a bit to make it run a little faster. I had a look at the sprite multiplexor, that is using a raster interrupt to allow you to quickly reassign sprite data and reuse sprites. That meant you could have more than 8 sprites on the screen at once as long as they did not all overlap vertically. I wanted my characters to be two sprites high and I wanted them to walk in front of each other. Ultimate had a few games that did this using double height sprites but that made the resolution lumpy. Other games just joined two sprites together but embarrassing things happened like a mans trousers going in front pf another sprite while his top half went behind. Sprites are plotted in Z order so I needed to assign the sprites according to how near the front of the view that each character was. I decided on a revolutionary method. I used the same sprite for the top and bottom of the each character. I set a raster interrupt at the bottom of the sprite and quickly reassigned its Y position and graphic. It worked but the raster interrupt timing was not entirely accurate and sometimes took a couple of raster lines before it executed. This caused the middle of each character to flicker . My solution was to overlap the top and bottom sprites and to have the same graphic in the overlap. Thus if the bottom sprite came in a couple of lines late it did not matter.
John Cummings provided most of the graphics before he left Graftgold. The background was built in a character set so it could be controlled rapidly. The hardware allowed you to shift the view by 0 to 7 pixels. Then you pointed the hardware at the next character column to start with. You made a character map a bit bigger than the screen and had to build the next column and row to appear during the frames it took to scroll the new column into view. Change direction and you start building the column or row the other side of the screen. By building the new row and column over 8 cycles it spread the processer load so you did not use up too many raster lines. We timed everything in raster lines by setting the border to different colours. The idea was to try to get the whole processing in a frame of raster lines so the scrolling was nice and smooth. By timing routines from the start of development we ensured the game was running as fast as possible.
I was really pleased with the game and very disappointed with the lack lustre publicity and poor sales that followed. The publisher approached me a few weeks before publishing and asked me whether I could change the game to depict red beretted guardian angels that were in the news at the time. Originally patrolling the subways of New York a chapter was being established to counter crime on the London Underground. Having the game just about finished this would have been a huge extra cost so I declined. I said it was too late to make such big changes and may as well use the guardian angle idea for a new game using many of the parts of Bushido. The publisher wanted a game instantly so it never happened. It showed they underestimated the work that goes in a game and the effort that it would take to change a scenario. It would not have been a matter of dropping in new graphics, the whole design would have to change to get rid of magic. The idea came from the publicity department so it is no wonder that they put little effort in marketing the game.
Deepest BlueI often compare writing a game to running a marathon. You get to the point were you have done so much but still seem to have an endless amount of work to do. The last couple of months has been tough. I had to just keep going and try to break the back of the work. I started off by looking at my squad routines. I realised that when a squad does a mission stage some of the ships had the right abilities and some would just have to keep guard. Also I needed a mechanism for AI squads to form.
A mission recruited ships to undertake it so it was the natural place for squad formation. If a mission requirement was beyond a single ship a squad would be formed adding ships until the mission's required abilities were present. I decided it would be sensible to group ships by ability, so for example fighting ships were in one group and freighters another. Thus a fleet would be made up of one or more squads which could act together or independently.
I needed a squad leader routine to control the assembly of a fleet and to give orders to the fleet. When the leader was busy doing a mission stage such as docking one of the ships not involved in the mission stage needed to act as a temporary leader to protect the busy ships. I needed to work out when a squad was in formation or when all its ships had finished a mission. This involved adding some fields in the squad that the leader could monitor. I also had to cater for ships dying, no use waiting around for a dead ship. It was all getting a bit complicated. It was the combinations of various things that was causing me the biggest headache. For example a ship could be :
A player ship piloted by the player.
A player ship piloted but with auto pilot switched on as the player was looking at another screen.
A player ship piloted by one of his pilots ( a player can take over any of his ships replacing the pilot, the game reverts with the original pilot when the player leaves the ship)
An AI ship.
Any of the above can lead a squad or be a follower of a squad.
The squad or ship can have a mission or not.
Squads now could exist as several squads linked together.
So I had to think through all the main activities and made sure they worked for all the categories. I drew some structure charts for squads to organise their actions. The needed to form up, navigate to a mission stage, then these ships that could do a stage had to split off and do the mission while the rest stayed in formation and protected them. Then the squad had to reform and repeat the process. That's the beauty of structured design it can cater for just about any scenario and lets you break it down into its logical sub parts.
All the time I was contemplating and adding these new things I was running the game to test the AI. I found it very confusing to guess what ships were doing so added a temporary HUD element to display each ships action and sub action in real time. I had ships visible on the map screen so could just have that displayed and watch ships pop in and out of the sector. However if one did something unusual I had to chase it down with the player ship. It was difficult finding the ship so I enhanced the map screen to allow me to select any item as a navigation target. I could then return to the game view and use the HUD to track the ship. While I was amending that screen I also added a double click event to the warp paths. So I could follow ships on the map display by double clicking the warp path the took and the warp exit location would be displayed.
|Text labels on map organised so they do not overlap|
I noticed that ships generally congregated around bases and that meant the text displayed on the map often was on top of other text making it hard to read. It needed a system whereby the text position adjusted to a clear bit of the screen. This sounded easy but was a lot harder in practice. The trouble is its not a static screen. You can spin it or tip it in any direction so the best position of the text items changes all the time. Every now and then it has to rearrange the text. If it did this constantly it could be annoying although it does look computery, if that is a word. I tried a Y position algorithm that ignores X position and it sort of works. I will probably go back to this later if it annoys me.
In a moment of madness I thought it would be cool of the ship selection screen showed the actual game view of each ship as I flicked between them , rather than have to exit to see what each ship was up to. Thus I could send each of my ships on a different mission and keep an eye on them by just switching between them. I racked my brains to work out how and it turned out to be just a few lines of code. I had to hijack the scene and camera of the game view and link it to my ship selection display window, tell it to resize and that was it. I had to do the converse when I exited the ship selection screen. That was the easy bit I had more trouble with game things like the HUD showing in the ship select screen.
|Ship Select screen Now Showing game view.|
Somewhere whilst tweaking the squads I managed to put an error in the friend or foe routine. Now when the game started one of my ships started blowing up the rest of my ships including me. If I was in the ship select screen the screen was left showing data about a non existing ship and accessing memory that had been freed. I decided it would be best not to delete player ships when they are destroyed but to keep them in limbo in case a player wants to just reorder the same ship. To do, add new button the ship select screen. So I left the friend or foe bug for a bit allowing me to test the game death cycle. May as well use a situation instead of having to set it up later. When that was working I fixed the bug. The silly routine was deciding ships were friends but still returning them as enemy. Who wrote that!
So another 2 months gone, still debugging the AI but I seem to have done a lot of programming. I just wish I did not break as much but its like the final stages of doing a Rubix cube. The nearer it is to completion the longer the shuffles take to get each new cube into place and it looks a complete mess in between. Next is to follow the freight ships and miner ships to see why they are disappearing. My guess is they are crashing into things and blowing themselves up. They were all working at one stage so hopefully it is not much.
In a commercial setting refactoring code is one of the most controversial of a programmers activities. By refactoring I mean reorganising a piece of code that has become so difficult to change and understand it is a liability. The view of the product manager is that change introduces bugs and at the least needs through retesting. The programmer knows the risk of not refactoring almost certainly will lead to nastier bugs and that future changes will be more difficult and maybe impossible. I am working on my own so I can decide. Personally I take the long term view, a little refactoring as you go along is better than a major rework at some stage as no one can work out what the code does let alone fix a problem.
Here are some of the thing that I am on the constant look out for and the refactoring solution.
1. A routine starts getting too big too handle.
It started off a neat routine with one purpose but over time gets more functionality added transforming its original purpose. There comes a time when a routine needs splitting into two or more component parts. I do not like changing things unnecessarily. The big driver for me that makes this change necessary is wanting to execute some of what a routine does but not the rest. You could add a few conditions and weave in and out of the code but that just makes it more difficult to understand and more likely to not do what you really intend. So how do I split a routine into parts with the minimum of change and thus reduce the likelyhood of introducing errors? My method is to copy the original routine into two (or more) new routines and immediately change the names so I can tell them apart. I will leave the original as a reference until I have completed the new routines. Then it is a matter of deleting lines in each routine to leave the processing that you need. Sometimes there is no new typing, no possibility of new code errors. Care has to be taken in removing unused local variables and the input parameters may need to be reduced. That is a danger it means previous calls have to be changed, but then they are probably have to be changed anyway to call both the new routines. In a commercial situation I may decide to add a wrapper that calls both the routines so all previous calls are untouched.
I prefer to do a project search for all existing calls and change them. That keeps the whole project a little simpler.
2. I notice duplicated processing.It is all too easy to end up with code that is identical in more than one place, or worse code that should be identical but has drifted apart due to changes that should have been replicated. It happens when a simple bit of code (maybe 1 line) is not a subroutine. Then functionality is added and the single line becomes many. It needs extracting and becoming a subroutine in its own right, leaving replicated is dangerous. It is all too easy to miss changing a replicated piece of code. The method I use to refactor this is to choose the best version of the replicated code and to change that to a subroutine right where it is, so I can see it in context with the calling code. I then copy the first line that names the routine and its parameters and paste that just above the routine. I then change that into the call of the routine. I make a last read to check the processing has not been changed then cut and paste the new routine to the best place for it. That may be below the parent routine. Then I put the call in to replace any replication, fixing it up before deleting the replicated code.
The idea with both these is to minimise the change. If you are just carefully moving code from one place to another intact it should still work, you have not changed the functionality just the organisation.