We fought side-by-side on the Plains of Blood in the last war against Doomdark

Since the start of this project there has been an interesting issue with Doomdark’s armies reaching Blood too soon. I’d noticed it while playing, and the testers have brought it up more than a few times. I have checked the code, doubled checked the code, triple checked the code, and the fact is, there is absolutely no reason why Doomdark’s armies cannot reach Blood the first night.

There are two regiments that start the game at the keeps at the Gap of Valethor, lets call them regiments 100 and 101. These are both riders set to wander.

A wandering regiment has 6 turns in the night. Firstly it checks up to 3 locations away, if there is anything interesting between it and 3 locations, it will make 1 turn step toward it. If not, it will randomly pick a direction, and as long as it isn’t frozen wastes, make 1 turn step in that direction.

Movement costs depending on terrain and direction. eg: Cost is *2 turns for a warrior, and a mountain costs 4 turns. So a warrior walking out of a mountain will take 8 turns. You take the terrain cost from the terrain you are leaving not the one you are stepping into. So a warrior walking from plains into mountains will take 2 turns, leaving 4 turns. The next move will cost 8 turns, but the fact they only have 4 turns left, is not important, they take the move, but then have -ve turns left, and thus their processing ends.

The process will be repeated until the 6 turns have been exhausted.

Now regiment 100 starts at the keep directly north of Blood. If on its first move, it decides to move south, then that places it within 3 locations of Blood, which means the next turn will focus it on the stronghold, and therefore it will guaranty hitting Blood in the first night. A movement of southeast, or southwest, will reduce the chance that the regiment will hit Blood in the first night, but it is still a possibility.

So, the original AI for Lords of Midnight, makes it possible that Blood could be hit on the first night. But it hardly ever happened. On my version, it happened almost every time. And every time makes it impossible for you to recruit Lord Blood, let alone try and mount the Blood Defence.

I figured that the random number generator mustn’t be functioning, so tested that, double tested it, and then tested it again. It seemed to be giving a reasonable stream of numbers. Didn’t change the fact that the results I was getting were not desirable. So I considered placing a delay command on those regiments, just to slow them down.

Last night I was talking through the issue again with Mike and we decided to throw out the random routine, and use another one instead. He gave me the C code version of the one used in Midwinter. The results were the same! But then I noticed something. The random number functions generate a number between 0.0 and 1.0 And thus picking number between say 0 and 8 just involves multiplying 8 by the random number, and there was the problem. This number needed to be converted to an integer, a whole number, and it was being done with a cast to int. And this truncates, which means the number is ALWAYS rounded down. In theory it just means that in this instance, 8 would almost never be picked. And in the case of these wandering regiments, that just means losing Northwest. However, changing that one line of code in the random number generator, has put those wandering regiments, back on the right track.

Blood can still be hit, but it is hit much less often than before.

4 thoughts on “We fought side-by-side on the Plains of Blood in the last war against Doomdark

  1. I was worried that you’d discovered the Spectrum RAND function used in the original version generated a unique set of numbers that drove the AI in the specific way we all know and love.

    Thought you might have had to use a precalculated array of random numbers generated on a real Speccy from the original code.

    • I considered it. The version that LOM used was based on the ‘r register and the ROM. Basically the rom became a lookup… I thought hard about dropping a copy of the ROM into the codebase… 🙂

  2. Ah the good old R register. That takes me back. I remember coding a custom loader system for one of my Quilled adventures where I used the R register to encrypt the game to tape by xoring each byte against it (and of course doing the same on load to decrypt).

    Quite a brilliant system if you ask me, it’s only flaws being that:

    A. no-one cared about trying to load my game into the Quill
    B. anyone wanting to hack the game would have loaded it into the Speccy memory anyway then saved out the decrypted version from after the screen&system variables and stopping below ~60k where the Quill editor lived.

  3. I’m sorry for answering to this post almost two months late, but I think I missed it when you first posted it and now I find it intriguing, being a fellow programmer.

    The random functions usually return a value in the [0, 1) range. That is, a decimal number between 0, included, and 1, not included.
    By multiplying that by 8 you get results in the [0, 8) range. That is, a decimal number between 0, included, and 8, not included.
    By later truncating it into an int, you get integer values in [0, 7] range. That is, a whole number between 0, included, and 7, included.
    That is 8 different values, that seem to match the 8 different directions. Assuming that those are assigned clockwise, they would be N=0, NE=1, E=2, SE=3, S=4, SW=5, W=6, and NW=7.

    So unless I’m mistaken at some point NW was never excluded at all. :S

Comments are closed.