Tuesday, April 2, 2019

Explore Simple Game Algorithms With Color Walk: Part 11

In this installment of exploring game algorithms using the simple game Color Walk, we're going to do something a little different. Last time we explored a number of variations and hybrids of Dijkstra's algorithm—the classic, efficient graph algorithm for finding shortest paths—and found that pairing it with a pre-run of Greedy Look-Ahead (GLA) performed better than any other algorithm we've seen so far. This time we're not going to explore any new algorithms. Instead, we're going to look into what makes Dijkstra's algorithm tick: the priority queue. Save for this variant of a standard queue, Dijkstra's algorithm is conceptually the same as Breadth-First Search (BFS), so we want to see what makes this priority queue so special and how we can implement one efficiently with a binary heap.

Priority Queues in Theory


The idea behind a priority queue is simple. It mostly acts like a standard queue, where items are put into the queue, and then items are taken out of the queue. The difference comes from which items are taken out of the queue first. In a standard queue, the first item put in will be the first item taken out. This type of queue is also referred to as a FIFO or First-In First-Out queue, as opposed to a LIFO, or Last-In First-Out queue, also known as a stack. In a priority queue, each item is assigned a key value, and the item with the lowest (or highest) key value is taken out of the queue first, regardless of the order that the items were put into the queue.

The key values can be anything useful for maintaining the priority queue. In our case it was advantageous for Dijkstra's algorithm to use a key value that was some heuristic of how close the game was to an empty board. That way the next closest move to the end-of-game condition could be taken from the queue and checked. If we can pull the next closest move out of the queue faster than we can search all of the moves up to that move, then we will easily win out over BFS.

The trick is to structure the priority queue in a way that makes it fast to extract the item with the minimum key value. We can't go searching through the entire queue for the minimum each time because we're letting the queue grow to 250,000 items. It would take way too long to search through such a big queue every time through the search loop, looking for the minimum key value. Similarly, it would take too long to keep the array sorted as new items were inserted because inserting items in the middle of the array would require shifting all of the items after the inserted item, taking linear time with the number of items in the queue.

Another option is to use a Binary Search Tree (BST), where each node has a left and a right child, and all children to the left of a node will have keys that are less than that node, and all children to the right of a node will have keys greater than that node. A BST can be searched and nodes can be inserted in O(lg n) time, but because we're continually removing the minimum node, the tree can easily become unbalanced. Operations on an unbalanced BST have worse running times. This issue could be alleviated by using a special BST, like a red-black tree, that maintains a balanced structure, but we would still need to deal with the added size of a more complex data structure. Red-black trees have a fair amount of overhead compared to an array, and that would mean less room in memory for the 250,000+ moves we want to keep in the priority queue.

Numerous other options exist for priority queue data structures, but one of the most elegant and simple structures that has everything we need is the binary heap. It can be implemented cleanly using an array, so it doesn't have much overhead, and the operations for inserting items and extracting the minimum key value have the desired O(lg n) running time. That means pulling out the next move from a queue of 250,000 moves will only take about 18 times longer than a queue of 1 move, instead of 250,000 times longer if extracting the minimum had a linear running time of O(n). The JavaScript priority queue that we used when implementing Dijkstra's algorithm implements the binary heap as one of its choices of data structures behind the priority queue. Let's take a deeper look at how this data structure works.

Binary Heap Insertion (Enqueue)


While the binary heap is implemented on top of an array, adding a specific way of managing the array elements and a couple new operations to the normal array operations, it is easier to think of it as a binary tree (but not a binary search tree, that's different). The tree is filled from left to right on each level of the tree before advancing to the next level and filling from left to right again. This method of filling the tree allows for easy storage in an array because new items can be added to the array by simply incrementing the index. The following picture shows the correspondence between the array behind the heap and the tree with nodes labeled by their index in the array.

Example binary heap with array representation

The tree is a heap when it maintains a specific property among all of its nodes. The key value for each node is the minimum key value among all nodes in its sub-tree. The above picture also satisfies this heap property if we assume that the numbers in the nodes are key values. We'll see shortly how this property can be maintained during insertion operations, but look at what we get with it. By definition, the minimum key value is at the root of the tree, which means it is the first element in the array, so we can always find it immediately.

In order to implement insertion, we'll need to be able to quickly find the parent of a node. Due to how the heap is stored in the array, the parent of a node is simply the node's index divided by two. But arrays are zero-indexed, so we need to adjust slightly for that, giving us the following formula for finding a node's parent:

parent = (node - 1) / 2

Now, when inserting a node, we can stick it at the end of the array, and keep comparing it with its parent. If the node is less than its parent, swap them and compare the node with its new parent. We are guaranteed to maintain the heap property for every parent that's swapped because that parent was less than its other child before the swap, and we're replacing it with a node that's less than the parent after the swap. Therefore, the new parent is less than both of its children. The code is straightforward:
function BinaryHeap() {
this.data = [];
}

BinaryHeap.prototype.enqueue(value) {
this.data.push(value);
var i = this.data.length - 1;
while (i > 0) {
var parent = (i - 1) >>> 1;
if (this.data[i].key < this.data[parent].key) {
var x = this.data[parent];
this.data[parent] = this.data[i];
this.data[i] = x;
i = parent;
} else {
break;
}
}
}
We initialize an empty heap in the constructor, and we assume that each element added will have a key property in addition to whatever object information comes along for the ride. A real implementation would handle things slightly differently so that the heap structure could use a comparator function defined by the caller, and the value passed into enqueue() would be a generic object. I simplified things here slightly to make the code more clear. Also notice that the divide is implemented as the unsigned shift right operator >>> to divide by two and truncate the result to an integer.

Notice that a node inserted into the binary heap will start at a leaf node and at most swap with all of its parents on its way up to the root node, if it happens to have the smallest key in the heap. Since the heap is a full binary tree except for the last, possibly partial, level and the height of a full binary tree with n nodes is lg n, the running time of this function is O(lg n).

That's basically all there is to inserting into a binary heap. This gif shows what happens when building up a heap by inserting somewhat random elements:

Example binary heap insertion

Binary Heap Extraction (Dequeue)


Extracting the minimum key value from the heap is a two step process. First, we need to find the minimum key, which is easy because it's the root of the heap and the root is the first item in the array. Second, we need to fill in the root with the new minimum key value in the heap. This step is a little trickier, but still not too bad.

To fill in the root, the easiest item to use is the one at the end of the array. We can take that item and put it in for the root, reducing the size of the array by one in the process. Now we most likely don't have the minimum key at the root, so we have to fix that. We can fix the heap by comparing the root with both of its children and swapping it with the child with the smaller key. This swap will restore the heap property between the root and its children because it now has the smallest key of the three nodes.

The entire sub-tree of the child that was not swapped also still satisfies the heap property because it hasn't changed from before the operation, except possibly by removing one of its leaves if the last item in the array came from its sub-tree. The sub-tree with the swapped child will need to be fixed, so we can repeat the same comparison and swap with it and its children as was done with the root. We can continue moving the leaf node that came from the end of the array down the heap until it's key is less than both of its children. At that point the heap property has been fully restored, and the extraction is complete. The code reflects this description quite closely:
BinaryHeap.prototype.dequeue() {
if (this.data.length === 0) {
throw 'Empty queue';
}

var min = this.data[0];
var last = this.data.pop();
if (this.data.length > 0) {
this.data[0] = last;

var pos = 0;
var last_idx = this.data.length - 1;
while (true) {
var left = (pos << 1) + 1;
var right = left + 1;
var min_idx = pos;

if (left <= last_idx &&
this.data[left].key < this.data[min_idx].key) {
min_idx = left;
}

if (right <= last_idx &&
this.data[right].key < this.data[min_idx].key) {
min_idx = right;
}

if (min_idx !== pos) {
var x = this.data[min_idx];
this.data[min_idx] = this.data[pos];
this.data[pos] = x;
pos = min_idx;
} else {
break;
}
}
}
return min;
}
We needed some error checking at the beginning to make sure we don't try to extract an item from an empty queue. Then we save off the root, pop the last item off the array, and make it the new root. Then we start comparing the root node with its children. To find the children's indexes in the array is pretty straightforward. For whichever node we're at, the children will simply be the indexes that are double the current index and the index immediately following that one.

left_child = node*2
right_child = node*2 + 1

You can check the picture of the binary heap and its corresponding array to convince yourself that this is true:

Example binary heap with array representation

Since the real code has the array index starting at zero, we need to add one to the left child index to get the right index. After that, we assume the current node has the minimum key, compare it to both of its children, and if the minimum key has changed after the comparisons, we swap the current node with the node with the minimum key. Otherwise, we're done and we can return the original minimum node that we had saved off before fixing the heap.

It is easy to see that this function runs in O(lg n) time for the same reason that insertion did, except the node is moving down instead of up the tree. The new root will at most travel down from the root to a leaf node, and the height of the tree is lg n for n nodes. Take a look at what it looks like for items to be extracted from the binary heap:

Example binary heap extract minimum

That's about all there is to a binary heap. It's a neat little data structure that elegantly solves the problem of implementing a priority queue. It has the nice property of using an array for efficient storage of items in the queue, and the heap property can easily be maintained with efficient insert and extract operations. It's a great data structure to have in your tool set.

We've just about wrapped up our exploration of simple game algorithms with Color Walk. We've covered a lot of ground through all of these posts, so next time I'll do a summary of all of the strategies we've looked at and how far we've come in solving this simple game.


Article Index
Part 1: Introduction & Setup
Part 2: Tooling & Round-Robin
Part 3: Random & Skipping
Part 4: The Greedy Algorithm
Part 5: Greedy Look Ahead
Part 6: Heuristics & Hybrids
Part 7: Breadth-First Search
Part 8: Depth-First Search
Part 9: Dijkstra's Algorithm
Part 10: Dijkstra's Hybrids
Part 11: Priority Queues
Part 12: Summary

Op Compass - Game 5 Same Day, Different Fort


We are back in the Desert this week for the 5th Game in our ongoing Campaign at the start of the war. Today sees the plucky Brits led astray by a bunch of Kiwi Truck Drivers whilst the brave Italian Forces try to hold out.

If you want more information on the Campaign I have set up a separate page which is updated regularly with updates on rules along with links to all the previous games,

https://yarkshiregamer.blogspot.co.uk/2018/04/opcompass-1940-resource-page.html

The games are based on an excellent book by Robert Avery which is available from The Toofatlardies, there is a direct link to purchase the book on the Resource Page.

We use 28mm figures with this scenario taking place on an 8 x 6 table using a home brew set of rules, based on Iron Ivans Disposable Hero's.

Desert Rats advancing into the Fort
Historical Background

Our last game was set on the morning of the 9th December 1940 in the Fort of Nibeiwa, this game is set on the afternoon of the same day just down the road (ok track) at the Fort of Tummar West.

This was to be a simultaneous attack with a similar adventure down at Tummar East, Infantry would lead the attack, supported by the Matildas of 7 RTR who were rushing (If a Matilda can rush) over from Nibeiwa.

The attack was lead by the New Zealanders of the No4 Reserve Motor Transport Company, instead of dropping off their Infantry cargo at the standard 500 yards from the target they raced forwards to within 150 yards and having stolen bayonets from the Infantry Bren Gun Teams they yelled "Come on you Pommie Bastards" abandoned their trucks and raced headlong into the Italian Defences.

Kiwi Truckers to the Fore
Resistance was fierce within the camp and the Italians held out for quite some time before the fighting ended, it was only with the arrival of additional reinforcements in the shape of a Punjab unit that the Fort was taken.


Table Set Up and Terrain 

Above is a picture of the table used for the game, as in the previous match up I have only used 8 x 6 of our avaliable space. Everything is as you see it, the road is just a track and gives no movement bonus.

The crates are Ammo Dumps

The following Special Rules are in force,

Kiwi Truckers 

The 2 sections will not have an activation card in the deck but will always activate first, after the Ammo Dumps have been rolled for and before the first card is turned. They are not subject to morale tests.

Ammo Dumps

These rules also apply to buildings however they can only be set alight during the initial Artillery Barrage (see British Briefing below).

When stuck by Artillery or Mortar Fire an Ammo Dump will set on fire and have a chance of further explosions.

As soon as fire is set and once at the start of every turn follow the following procedure,

1. Roll 2d6 one plus and one minus, any minus and the wind direction changes (on the first turn of the fire ignore this), then roll 1d12 and 1d6 to indicate smoke direction from the fire the d12 indicates the clock direction and the d6 the number of 60 x 50mm smoke markers. So a roll of 7 and 4 would mean 4 markers at 7 o'clock from the dump. The dumps will burn all game once lit.

2. Roll 2d6 one plus and one minus, on any minus an additional explosion has occurred, roll 1 d 12 and a random direction indicator (the Spin for It App is ideal), the d12 is the distance in inches. At that location place a standard Mortar round and calculate any casualties as normal or if you are really good, set another Ammo Dump off ! Any double rolled on the initial pair of dice mean the explosions have stopped (the fire and smoke continues).

Ammo Dumps burn and beltch fire !
British Briefing 

As in your previous game your brief is simple, clear all the active resistance from the Fort whilst minimising your losses.

Your Forces are as follows, all arrive anywhere on the far edge of the table as shown in the photo,

Kiwi Truckers  - 2 x 8 man Sections no LMG  (the Kiwis are immune to all morale checks and are removed when they reach one remaining figure)

2 Companies of Infantry each with 
1 HQ of 3 Figs, Officer, Radio Op and Sgt
3 Units of 10 men each with 1 Sgt with SMG, 2 man Bren Team and 7 Rifles

After turn 5 place the British Reinforcements card into the deck and roll 1d4 on its first appearance, that is the number of turns until the following arrive,

1 x Troop 7RTR 3 x Matilda Tanks (I used a rogue Valentine for a change)

Bren Carriers Charge !
After the Tanks have been on table for 3 turns add in the British Reinforcements card again, treat as above, this time reinforcements are,

2 x HMG and crew each in a Bren Carrier

4 x Bren Carrier each with 1 x 8 man Punjabi Infantry Squad of 1 Sgt with SMG and 7 Rifles. I know 8 men is a squeeze in a Bren but I haven't got any more !

Italian Briefing 

Once again you are faced with an onslaught of seemingly unstoppable Infantry and Tanks (although you still can't quite believe how slow those tanks are !), smoke and flame is all around, hang on as long as you can.

The Italians have a secret set up, I marked each building with a letter and put 3 letters along each of the trenches, each one can hold either 2 support weapons or 1 support weapon and a section of Infantry. Normal spotting rules apply.

Italian Mortar lays down fire

Italian Forces are

2 x HQ with Officer and Radio Operator
3 x Rifle Sections with 1 Sgt with SMG and 10 Rifles
3 LMG Sections with 1 Sgt, 2 3 man LMG teams and 2 Rifles

2 x HMG with 3 crew
1 x Medium Mortar with 3 crew
1 x Light Mortar with 2 crew
2 x 75mm Field Gun with 4 crew
2 x 47mm Anti Tank Gun with 3 crew
1 x Anti Tank Rifle with 2 crew

Italian LMG Section defend a building
Pre Game Barrage

Once the Italians have set up randomly select 4 buildings and or Ammo Dumps and these are declared as being destroyed by the strike, any troops in those buildings are lost. The areas hit will now be subject to the Ammo Dump rules above (note this is the only time in this game that buildings will set ablaze).

The initial Artillery Barrage causes chaos
How did we get on 

Those of you who are following the campaign will be aware of the streak of poor luck the Italians are currently suffering. This would prove to be the case once again. Table Set Up ready to go, in comes the Artillery Barrage..... and takes out the Italian HQ in turn 1 !

The British decided to push all their troops along the left side (as viewed in the set up photo) using the small walled area and then the tented area for a bit of cover.

Charge  !
The initial advance was helped with the draw of the Charge event card, perfect for the Kiwis, as the Italians had already fired,  they ran into the nearest building capturing it at bayonet point. The situation was going well as the Italians had set up an artillery piece in the forward trench which was quickly over run (top picture).

Fat Badger is so fat he gets stuck.
The British tanks turned up on Turn 7 and true to it's name Fat Badger got stuck and spend the next 7 turns trying to get free, not the best thing when you are in one of the slowest tanks of the war.

Allies advance, Kiwis to the fore
Using the trench captured in the charge against the Artillery, the Allies pushed down the table, Kiwis to the fore. But it wasn't all one way traffic, casualties were racking up for the British, something not seen before in this campaign.

British Infantry working hard to push through the centre of the fort
The Ammo Dump and Smoke rules worked really well (considering I made them up the night before !) and visibility issues got worse when a Sandstorm kicked up in the middle of the table. Infantry advanced under the cover of smoke or defenders lined up a perfect shot only to find the wind swirl and the smoke position change.


As in the real battle, the arrival of the Bren mounted Infantry swung the balance of the game in favour of the Allies, the commander taking full advantage of the high speed of the Brens to get the reinforcements to the right place quickly.


The last of the Italian Forces fell back on one of the trenches and stood ready for a final stand, things went well initially as a British section charged the southern end of the trench and was seen off with a bloody nose. However with concentrated fire the last of the ranged Italian Weapons was silenced leaving them with only Rifle armed troops. As the British had the ability to stand off with their HMGs and Lt Mortars we declared that the remaining Italians surrendered ending the game.


Allied Casualties Top, Italian bottom

Losses Allied 36 Infantry
Losses Italian 39 Infanty plus 9 Support Weapons and crews

Result wise I have declared this as a draw, 2pts each , yes the British took the Fort but let's be honest there isn't any other possible outcome, the 36 Infantry losses for the Allies was by far the highest of the Campaign so far (they lost two in one game !)

So 5 games in the British lead 13 points to 7. Game 6, due early 2019, will be a bit of a change as we see a clash of forces in the open as they manoeuvre around the forts.

Monday, April 1, 2019

The Seventh Link: Summary And Rating

The game manual featured some fairly modest hand-drawn art.
           
The Seventh Link
Canada
Oblique Triad (developer and publisher)
Released 1989 for Tandy Color Computer 3
Date Started: 16 December 2018
Date Ended: 16 March 2019
Total Hours: 22
Difficulty: Medium-Hard (3.5/5)
Final Rating: (to come later)
Ranking at Time of Posting: (to come later)
      
Summary:

Inspired graphically and thematically by the Ultima series, The Seventh Link is probably the most extensive and full-featured RPG for the TRS-80 Color Computer. A single starting character ultimately enlists a group of allies of different races and classes on a quest to save their planet from a black hole at its core, about to break its containment. Solving the quest will take the party through dozens of towns across multiple planets and through multiple large, multi-leveled dungeons. Although the game gets off to a slow, grindy start, character development is rewarding and the tactical combat system (drawn from Ultima III) is the most advanced seen on this platform. The problem is that the game's content is not up to its size, and not enough interesting stuff happens while exploring the enormous world.
          
****
       
I never like giving up on games, and I particularly don't like when I know the author is reading (I'm frankly not sure it's ever happened before). But in several months of trying, I simply haven't been able to make any decent progress in The Seventh Link. That doesn't necessarily mean I don't like it. If I was a Tandy Color Computer 3 owner, I'm sure I'd prize the game and play to the very end. The problem is that as a blogger, I have to be able to justify my playing time with material. If I spend four hours in a dungeon and all I can say is I killed a bunch of enemies (showing the same combat screens I've shown before) and gathered some gold, it's hard to countenance that time.

In some ways, The Seventh Link is the quintessential 1980s RPG. It offers a framing story with more detail than appears in the game itself, sticks the player in a large world that the player has to map if he's to make any progress, and features a lot of combat. In mechanics, it's as good as any of the early Wizardries or Ultimas.

Unfortunately, Link was the last game I encountered before leaving the 1980s, and I'd just spent a decade mapping featureless dungeon corridors. It's not its fault that it's last; that's just the way it happened. And by the time I got to Link, I just couldn't do it anymore. I couldn't--I can't--play a game that's just a few dozen 20 x 20 dungeon levels full of combats. The Bard's Tale and its derivatives drained that battery.
          
I never figured out anything to do with the pillars.
        
This is the 90s, and gamers are demanding more interesting content in their game worlds. We want NPCs, special encounters, puzzles, and other features in those dungeons, at regular intervals. We've decimated forests in our consumption of graph paper; we're ready for automaps. Ones that don't require us to find a spell first. 

Despite investing a fair number of hours into the game, I really didn't accomplish much. I explored the surface of Elira, visited each of its towns to assemble a party, and mapped 4 of 13 levels of one dungeon. There were at least 9 more dungeon entrances on Elira alone, some of which would have taken me to teleporters to three other planets and their own towns and dungeons. I would have found a final party member, a female ranger named Starwind, on the planet Dulfin. Others dungeons would have led me to power packs and the places where I needed to install them to save the planet. I still don't know where I was to find the other spells. From hints in an old disk magazine, I learned that the maximum character level is 25 (my main character reached 8) and that one of the planets has a store where you can buy potions that increase attributes, serving in the role of Ambrosia from Ultima III.
           
One of the few lines from an NPC. Alas, I will probably never explore Selenia.
       
My GIMLET is naturally based on an incomplete picture of the game:
         
  • 4 points for the game world. The sci-fi origin story is fairly original, and well-told in epistolatory fashion, although it fails to explain a number of aspects of the world (e.g., why are there settlements on other planets). While the player's role is somewhat clear, it's less clear where he came from, how he got started on this path, and whether he understands his role.
  • 3 points for character creation and development. The selection of races and classes is familiar but not entirely derivative. There's nothing special about character creation or the development and leveling process, but they're reasonably rewarding. I don't know if the level cap would have caused any issues or if you finish the game well before reaching it.
  • 3 points for NPC interaction. The game has a better system than it uses. You learn a few things from NPCs, but there are hardly any NPCs that say anything to you. Expanding that number would have resulted in a richer, more engaging world. I do like the Ultima IV approach to assembling your party by finding members in the towns.
  • 2 points for encounters and foes. The monsters are mostly derivative of other games (though I like the explanations for their names here: the ship that populated the planet had Tolkien fans on it), and I didn't really experience other types of encounters.
  • 4 points for magic and combat. The tactical combat screen is about as good as Ultima III, but with fewer spells.
          
On Level 3 of the dungeon, I met an enemy called "Floating Stars."
       
  • 3 points for equipment. You can get melee weapons, missile weapons, armor, and adventuring equipment like torches and keys. Various sites hint at more advanced items like rods and gems of seeing. The selection of stuff is a little paltry in the traditional Ultima style.
  • 5 points for the economy. It lacks a certain complexity, but money is certainly valuable. You almost never have enough keys, for one thing. Healing, torches, equipment, and leveling up consume gold fast, and it sounds like the shop on Dulfan would have served as an endless money sink for any extra you could accumulate.
  • 2 points for a main quest with no side-quests or quest options.
  • 4 points for graphics, sound, and interface. Almost all of that is for the interface. It adopts the Ultima standard of one key per action, which ought to have been mandatory as far as I'm concerned. Graphics are functional but sound sparse.
          
I never quite got used to the perspective. That lava square is only one square in front of me.
         
  • 2 points for gameplay. It gets a bit for nonlinearity and a bit more for the moderate-to-challenging difficulty. But it's not very replayable and it's way, way, way, way too big and too long.
            
That gives is a final score of 32, which is hardly awful for the era. It's actually the highest score that I've given to the platform. The only things that stop me from finishing it are the number of hours it will take and the number of other games on my list.

The Georgetown, Ontario-based Oblique Triad was a mail-order developer and publisher, co-founded by Jeff Noyle and Dave Triggerson. The name referred to the decorative bars on the top of a Color Computer. Mr. Noyle used to host a page (available now only on the Internet Archive) with links to their games, which included a pair of graphical adventures called Caladuril: Flame of Light (1987) and Caladuril 2: Weatherstone's End (1988); a strategy game called Overlord (1990); an arcade game called Those Darn Marbles! (1990); and a sound recording and editing package called Studio Works.
          
Caladuril, the company's first game, is a decent-looking graphical adventure.
         
With the Color Computer in serious decline by 1990, Oblique Triad shifted its focus to specializing in sound programming, and both Noyle and Triggerson have associated credits on Wizardry VI: Bane of the Cosmic Forge (1990) and Wizardry: Crusaders of the Dark Savant (1992). I haven't been able to trace Triggerson from there, but Noyle got a job at Microsoft in 1995 working on Direct3D, DirectX, and DirectDraw and remains (at least according to his LinkedIn profile) there today. He also has a voice credit for a Skyrim mod called Enderal: The Shards of Order (2016).

Mr. Noyle was kind enough to not only comment on one of my entries, but to take the time to create overworld maps to speed things along. I'm sorry that it wasn't quite enough, but every game that I abandon stands a chance of coming back when circumstances are different, and I'll consider trying this one again when I feel like I'm making better progress through the 1990s.