Thursday, May 23, 2019

28Mm Italian Wars Pike, Gendarme And More


I've not been posting much on the Italian Wars Project as it grows and with a few units added to the ranks it was time to reveal them to the world.

TAG Pike Block 


Where as all of the foot figures I have used so far are from the Perry's these are from a Company called The Assault Group or TAG as they are more commonly known. Still 28mm and similar in proportion to the Perry figures they are slightly smaller and a lot "cleaner" (there is a comparison at the end of the post).



I have to be honest and say that while I was painting these I really wasn't very sure how they would come out, no criticism of the figures but the flatter less contoured sculpts of the TAG line don't really fit well with my painting style and I wasn't sure on the faces either. However when I came to photograph them I was more than pleasantly surprised by the outcome.


The pike on the rear ranks are quite upright and the figures have a bit more armour on than I would like for this far back in the Block but I'm nit picking. 8 figures per base on 60mm x 50mm mdf. Flags are Petes a Borgia Crest in white and the Papal Cross Keys in Red.


Middle ranks have a slightly lowered poke and we have another Borgia Papal flag this time in Red and then a Family Flag. The eagle eyed will notice I have placed the flags in the rear at the top of pike and then reduced the size of the flag pole in the middle ranks so that all four flags are visible from the front.


Front ranks on the extended bases I used on the other block although TAG don't do levelled pike figures these are around 45 degrees. I have been quite loose with the clothing in the Block using mostly Red, Blue, Green, Yellow and White as per the flags.


So that's 3 pike blocks of 48 figures so far, obvious a group shot required.


The Perry and TAG figures look fine side by side, I wouldn't mix them in the same unit but as part of the same Army no problem.



Gendarme


I've added a couple of 6 figure Gendarme units since the last YG blog post, both 28mm Foundry Figures with Petes Flags. The above photo includes my first Command Stand, two figures on an 80mm round base. The officer figure is metal Perrys whilst the Standard Bearer is from the plastic Light Cavalry box set. It was a bit of a light bulb moment realising that the plastic set included 12 perfect mounted flag carriers !


The second unit uses the same figures, this time with some Papal Flags, these Foundry Figures have been around for years but still cut the mustard. Good excuse to put all 6 units in the same shot.


Artillery 


Just the one Artillery piece to date, this one from Foundry.

Sword and Shield 


These figures are from the metal Perrys Range, described as Light Italian Infantry, they are armed with a sword and a oval shield. I have painted them up in a the red and white theme I have used earlier in the project.


I also kept up with the Command Stand push adding a Papal themed base with two figures from the metal Perrys Range and another plastic flag bearer.


Stradiots 


I also revisited my Stradiots adding another 6 figures to the 6 I already had making a 12 figure unit.


It was also a good excuse to get a Command Stand done for my light Cavalry, this time both figures are from the plastic Light Cavalry box set.


So there we have the latest Italian Wars Project updates, the first phase of building the army is nearly done, I have a couple of Infantry units left along with 1 more Gendarme and a couple of light Cavalry units to go before the initial metal pile is spent. Just waiting to get them on the table now.

ASOIAF: Deployment And Activations

Get ready for some hard lessons.

One of the best things about this game how in-depth alternate deployment and activations work.  All of this is outlined really well in the main rulebook, but I want to take a moment to stress the importance of how greatly this affects overall gameplay.  A lot of this harks all the way back to my WHFB days where "drops" and chaff really meant something when it comes to deployment.  Having more drops than the opponent allows you to see where their most crucial units get placed so you can better deploy against them.  Keep in mind that when it comes to "drops", this is strictly limited to combat units.  It doesn't matter how many NCUs you have when it comes to deployment, but this will matter when it comes to overall activations.  Activation order and the number of total activations matter because the more you have, the more you can "force" your opponents to activate their units so you can better counter them.

Alright, so let's dive into deployment first.  Going forward, I'm just going to call these drops because I'm an old-school WHFB player and you're just going to have to deal with it.  I'll start by using my Robb Stark list vs. the previously posted Ramsay list.

Faction: House Stark
Commander: Robb Stark – The Wolf Lord
Points: 40 (4 Neutral)

Combat Units:
• House Umber Greataxes (7)
  with Robb Stark – The Wolf Lord (0)
• Grey Wind (0)
• Stark Sworn Swords (5)
  with Bran and Hodor – Protector and Ward (3)
• Summer (0)
• House Umber Berserkers (7)
  with Sworn Sword Captain (1)
• Stark Outriders (7)
  with Brynden Tully – Vanguard Infiltrator (3)

Non-Combat Units:
• Sansa Stark – Little Bird (3)
• Lord Varys – The Spider (4)

Made with ASOIAFBuilder.com

vs.

Faction: House Lannister
Commander: Ramsay Snow – The Bastard of Bolton
Points: 40 (20 Neutral)

Combat Units:
• House Clegane Mountain Men (6)
  with Ramsay Snow – The Bastard of Bolton (0)
  and Theon Greyjoy – Reek (0)
• House Clegane Mountain Men (6)
  with Dreadfort Captain (1)
• Bolton Cutthroats (5)
  with Assault Veteran (1)
• The Flayed Men (10)
  with Gregor Clegane – Mounted Behemoth (3)

Non-Combat Units:
• Tywin Lannister – The Great Lion (4)
• Lord Varys – The Spider (4)

Made with ASOIAFBuilder.com

Before we talk about anything else, keep in mind that after the battlefield is set up, you roll a die with your opponent and you see who gets to pick their deployment zone (winner chooses or passes).  The player who does not choose their Deployment Zone will the First Player.  The player who chooses the deployment zone puts down their first combat unit.

As you can see in the example lists above, Robb's army has a total of 6 drops compared to Ramsay's 4 drops.   This is one of the best things about the Stark Dire Wolves and that's because it comes with good 0-point chaff that are combat units.

If you choose Deployment and therefore starts deploying first, it would look like:
  1. You put a wolf down
  2. They put down a combat unit
  3. You put another wolf down
  4. They have to put another combat unit
  5. You put down some Stark Swords
  6. They put down another combat unit
  7. You put down some Berserkers
  8. They have to put down their Flayed Men with Ser G
  9. Now you counter-deploy your Greataxes to meet his knights
  10. ...and you can put your Outriders somewhere that best suits your needs

Hell, if you count his drops ahead of time, you can even choose to Outflank with your Outriders because you know the drop advantage is yours.  Always count the number of drops your opponent has and take note of any units of significance.

Do you see the power of having more drops than the opponent?  This is actually one of Stark's most powerful tools and that's the free Dire Wolves with Robb and Brann.  Once Shaggydog gets up in here, it's going to be a hoot.  Even having one drop over the opponent can mean a big difference because it allows you to better set up your slower moving speed-4 Greataxes somewhere that's going to scare those Flayed Men.  If you end up tieing with your opponent when it comes to drops, consider letting them choose deployment so they drop first.

Activating in the most optimal order is key.

Next, let's talk a little bit about activations.  For the most part, I'll try and keep things as generic as possible.  Knowing the when and why you activate your units will mean the difference between victory and defeat.  This is where most of the complexity of the game comes from.  By understanding that your NCUs count towards total activations and directly interact with the game through the tactics board, this greatly enhances how you play the game.  While most units interact directly with tactics cards, there's also a ton of ways to cheat out free actions.  To explain all of this is going to be really complex because it's simply impossible to predict any and all events that are going to happen in a game.  No worries though, I'll try and give out some hints based on the games I've had so far.  Keep in mind that who is First Player also matters greatly.

Here are some helpful tips:
  • NCUs tend to activate first to either stifle the opponent's zones or take advantages for themselves.  For example, as First Player, taking the Tactics zone can be super useful, or robbing the Stark player of free maneuvers can also be strong, especially when there are plentiful objectives on the board.
  • Whenever you interact with the tactics board, you should first consider if you're planning to give a direct benefit to yourself or to disrupt the opponents' plans.  Every decision you make should be deliberate and has a significant impact on the game.
  • When units are already engaged,  claiming the Combat zone is very strong and should be claimed if you have First Player.  This is essentially a free combat action, which is just incredible.  Anything that gives free anything is highly-sought after.
  • Activating your Dire Wolves first before your main combat units allow you to better move units in response to what the opponent is going to do.  They have to activate their units and cannot choose to just pass.  They can, however, put an activation token on the unit and just not do anything.
  • Always look for low-risk activations first if you want to bait your opponent into doing something so you can counterplay it.  However, you need to prioritize high-value activations if you absolutely need something to go in your favor.  This is how tempo is set by the player:  If everything you're doing gives you an advantage some way or another, you will always be ahead.  An example of this could be deciding to put an NCU down first to disrupt the opponent or to activate a unit.  You want to choose the one that will put you ahead while leaving the opponent unable to respond.  The best type of activation is getting to do something that gives you advantage while the opponent gains nothing.
  • As the game progresses, this is where the true chess element of the game comes in:  Choosing the wrong activation order can literally mean victory or defeat.  Everything is a risk because both you and your opponent has a hidden hand of tactics cards, and with NCUs being different every game, there's always going to be calculated risk.  This is why Varys is one of the strongest NCUs in the game currently:  He has 4 tries to foil your opponents' plans with the tactics board or their NCUs.
  • Once combat is joined, the focus of the game shifts a bit from NCUs to actually fighting the battle.  Otherwise, you risk skipping pivotal combat and your opponent might just tactics your unit into the ground before you get a chance to swing.  If you see the opportunity to inflict damage, it's almost always worth it to take it unless you have the appropriate counter.  Look for unit activations that will give you the battlefield advantage.  NCUs are not the ones fighting over objectives.
  • Typically when you're a couple of turns in and the battle is joined, you should look for opportunities for free actions first, then combat, then NCUs in that order.  There are many factors that will change this order around, but that's completely up to you to analyze the opportunity cost.  Since there are a lot of things to keep track of once battle is joined so it's important for you to get comfortable with your units, your commander, your NCUs, and your tactics cards.  Else, you risk missing vital opportunities or triggers that can win the game for you.

Alright, that's pretty much all I have to say about activations.  There are just a billion examples and each one of them is unique.  However, I think mastering your activations is the most difficult, most complex, and most rewarding part of the game.  It's probably the biggest factor in identifying player skill and experience, so it's definitely worth practicing.  Good luck!

Bonus Track - Toys R Us

The closing of Toys R Us has been rolling around in my head for months, I finally had some time to sit down and record it. I guess it's appropriate since it's Christmas time. I hope you enjoy it, thank you for listening!

Toys R Us bankruptcy press release
Vintage Toys R Us on Retro Junk
Sunnyvale Toys R Us on Computer History Museum (thumbnail photo)
Doug McCoy's Nintendo Get With It Kit video
Jean Shepherd's Route 22

Explore Simple Game Algorithms With Color Walk: Part 9

Welcome back for more exploration of game algorithms using the simple game Color Walk. In the last post we covered the other fundamental graph search algorithm, depth-first search (DFS), the counterpart to the previously discussed breadth-first search (BFS). These graph algorithms do a full search of the graph of a color walk game, the full set of board positions resulting from each move at each point in the game. We found that running either of these algorithms to completion is extremely prohibitive due to the graph size being exponential in the number of moves. In order to deal with that exponential growth, we need to look at other graph algorithms, and we have quite a few to choose from. We'll explore some categories of graph algorithms and look at one in more detail, Dijkstra's algorithm.

Different Graph Algorithms for Different Questions


Before getting into the algorithms, we should review what makes up a graph and how it relates to the move graph for Color Walk. Most graph algorithms, beyond the basic BFS and DFS, are designed to work on graphs with weighted edges. A weight on an edge means the edge has a value associated with it that can be thought of as a cost of traversing that edge from one vertex to another. A vertex is the same thing as what we've been calling, so far, a node, because we've been talking about the game's move graphs as trees. Internal nodes in a tree are connected by edges and end in leaf nodes. The root node of the move tree is just another vertex, and in the case of a generic graph, any vertex in the graph could potentially be the root node of a corresponding tree. We simply have to pick which vertex we want to use as the root, and the rest of the tree branches out from there. In the case of our move graph, we pick the vertex with the starting board position as the root of the tree.

We can further compare trees and graphs by noticing that a tree is a special type of graph, namely a directed, acyclic graph (DAG). Directed means that edges are traversed in one direction, and in the move graph that direction is from vertices corresponding to earlier moves to vertices corresponding to later moves. More precisely, the vertices are the board positions that are arrived at for each move, and the edges represent the moves that change one board position into another board position by removing blocks.

Acyclic means that a vertex cannot be revisited by following some finite number of edges without backtracking (since the DAG has directed edges, we can't backtrack anyway). The move graph isn't quite acyclic because each vertex has an edge to itself for the same move as the move that results in that board position. Some vertexes also have edges that come back to themselves because the corresponding move doesn't remove any blocks, resulting in the same board position. These cycles can easily be detected and eliminated, as we saw in the BFS and DFS algorithms, so the move tree can be thought of as a DAG, even if it isn't in the strictest sense, by removing these cyclic edges.

Returning to the idea of edge weights, most graph algorithms work by optimizing the cost of traversing edges by their weights. A path consisting of the lowest sum of weights is considered the best path, and graph algorithms will work to find those paths. We have already been assigning weights to the edges in our greedy algorithms. We called them heuristics instead of weights, but they amount to the same thing. The heuristic values associated with each move can be considered the weights assigned to each corresponding edge in the graph. Different heuristics are different weight functions. For example, in the basic greedy algorithm the heuristic was the number of blocks removed on a given move. The number of blocks removed would be the weight of the edge corresponding to that move. So far all of the weights we've used have been positive, but it is feasible to have negative weights on graph edges as well. Some graph algorithms will work with negative weights, while others require all positive (or all negative) weights.

Graph algorithms can be classified into four different types, based on what they solve for a given graph. Minimum spanning tree algorithms will find the minimum set of edges, taking weights into account, that connects all of the nodes in the graph. Basically, these algorithms remove the most costly and unnecessary edges from the graph to create a tree still containing all vertices from the original graph. Since our move graph is already a tree and we're interested in finding the minimum path from one vertex to another, minimum spanning tree algorithms won't be too useful for us, but they do solve a large class of problems, including automatic routers for circuit board or integrated circuit layouts, for example.

The shortest paths algorithms are the ones we're most interested in. These algorithms find the lowest-cost path from a source vertex to all other vertices in the graph. It is interesting that solving this problem is asymptotically as fast as finding the shortest path between two specific nodes, but we are working with an enormous graph, so we'll have to make some modifications to Dijkstra's algorithm, the shortest path algorithm we'll use, to make it tractable for the move graphs. Part of that modification will be stopping early when we find the shortest path to the end vertex we're looking for.

Another type of algorithm that's similar to the shortest paths algorithms is the all-pairs shortest paths algorithm. These algorithms will efficiently find the shortest paths between all pairs of vertices in a graph. This problem seems much larger than the single-source shortest path problem, but it can actually be solved faster than iterating through every vertex and running Dijkstra's algorithm. We don't need to do this for our problem, so we won't be looking into this type of algorithm here.

The last type is the maximum flow algorithms. These algorithms take a pair of vertices and find the maximum rate that something can move through the graph from one vertex to the other without overflowing any of the weights in the graph. The weights can be thought of as capacities for each edge between vertices, and this type of problem has all kinds of applications such as electricity in wires, liquids in pipes, traffic flow, assembly lines, and much more. Yet, it's not useful for our immediate problem, although it's good to know that these algorithms exist.

Single-Source Shortest Path Algorithms


As described just a moment ago, single-source shortest path algorithms will find the shortest path from a source vertex to all other vertices in the graph. Let's ignore for the moment that this is a colossal problem for our move graphs, with well over 440 vertices for most boards even after trimming out all cyclical edges. Instead, think about what the graph looks like. It's a tree with a single root node, our source vertex, that spreads out at each level by a factor of four.

At some point the branches in the exponentially expanding tree will start reaching leaf nodes, but these leaf nodes are, in fact, all the same vertex—an empty board. That means at some point every branching path in the tree will end up at the same vertex with an enormous number of edges coming into it, and we want to find the one that comes from the path through the least number of vertices. This is a potentially simplifying insight that we'll have to keep in mind. Take the example sub-graph pictured below as an example, where there are three colors to choose from for moves, and each vertex is labeled with its move number. Eventually, every path of moves ends in the same place, at the end vertex.

Example move graph with three colors and an end node

The most straightforward way to find this path is to use the Bellman-Ford algorithm. This algorithm will sweep through every vertex in the graph, and for each vertex it will take each edge in the graph and relax it. You see, each edge has a source vertex, where the edge comes from, and a sink vertex, where the edge goes to. Each vertex will also have a distance associated with it that starts at infinity, unless it is the overall source vertex where the path starts, in which case the distance is zero. Relaxing an edge means that we compare the distance of the sink vertex with the distance of the source vertex plus the edge weight. If the sink distance is more, then the sink distance will be updated to the source distance plus the edge weight. The pseudo-JavaScript code for the Bellman-Ford algorithm looks like this:
function BellmanFord(graph) {
_.each(graph.vertices, function(vertex) {
_.each(graph.edges, function(edge) {
relax(edge);
});
});
}

function relax(edge) {
if (edge.sink.distance > edge.source.distance + edge.weight) {
edge.sink.distance = edge.source.distance + edge.weight;
edge.sink.previous = edge.source;
}
}
This code makes some assumptions about the graph's data structure, namely that it has a list of vertices and edges, and each edge has properties for its source and sink vertices and weight. Vertices have properties for their distance and a link to the previous vertex that is on the currently found shortest path. A proof of the correctness of this algorithm is a bit too involved to get into here, but essentially, it relaxes every edge in the graph for every vertex in the graph so that the calculated distances are guaranteed to fully propagate through the graph. The result is that each vertex will have its shortest distance to the source vertex, and a link to the previous vertex in its shortest path.

This algorithm is dead simple, but also completely unworkable for our problem for two reasons. First, it requires us to generate and store the entire move graph before running the algorithm. This task has already been shown to be quite beyond the ability of any computer for the foreseeable future. Second, we have to run relax(edge) for every edge in the graph, and do that full sweep of edges for every vertex in the graph. In other words, this algorithm is O(V2) where V is the number of vertices. Such a processing feat is never going to happen. We need to find a better way.

One thing we should notice is that the Bellman-Ford algorithm is doing a ton of extra work to propagate distances through the graph. Most vertex distances will not change on any particular sweep of the edges because the distances around them haven't changed. The algorithm was designed to work with any type of graph and tolerate cycles, but the move graph has the added restriction of being a DAG. Using that restriction, we can improve the algorithm by traversing the graph in breadth-first order while relaxing edges.
function dagShortestPaths(graph) {
var vertices = new Queue();
vertices.enqueue(graph.root);
while (vertices.getLength() > 0) {
var vertex = vertices.dequeue();
_.each(vertex.edges, function(edge) {
relax(edge);
vertices.enqueue(edge.sink);
});
}
}
The relax() function is the same as with Bellman-Ford. With this improvement, we're doing much less work, while propagating vertex distances in a way that ensures that vertices are updated quickly and without a lot of extra waste. In the case of our move graph, where vertices nearly always have one incoming edge, the running time will be O(V).

This algorithm is a great improvement over Bellman-Ford, but we've run into a somewhat silly issue. This algorithm is basically BFS with edge weights added into the mix. Because edge weights are just an additional metric in the move graph and not a strict cost of traversing the edges, they become meaningless, and the algorithm will perform no better than BFS. It should be obvious that it would take at least as long to run, and if we allow it to run over the entire graph as defined, much longer. Remember, BFS stopped as soon as it found the empty board node.

This DAG shortest paths algorithm is great for smaller DAGs than what we're working with, and with DAGs that have true edge weights instead of weights that we're just using as heuristics. What we need is an algorithm that uses the edge weights in a way that dramatically reduces the number of vertices visited before the destination node is visited, and for that we need Dijkstra's algorithm.

Dijkstra's Algorithm


To implement Dijkstra's algorithm for the Color Walk move graph, we're going to need to revisit the the heuristics we would use for edge weights. Simply using the number of blocks removed for each edge (move) probably won't be good enough, but to see why, let's first look at an outline of the algorithm.
function Dijkstra(graph)
var vertices = new Queue();
vertices.enqueue(graph.root);
while(vertices.getLength() > 0) {
var vertex = vertices.extractMin();
_.each(vertex.edges, function(edge) {
relax(edge);
vertices.enqueue(edge.sink);
});
}
}
Waaait a minute! This algorithm looks almost exactly like the DAG shortest paths algorithm, which looks almost exactly like BFS. What gives? In truth, it is very similar to both of those algorithms. The key difference here is that we're not pulling the vertices out of the queue in the same order that we're adding them in. We're pulling out the vertex with the minimum distance to the source vertex of all the vertices in the queue with vertices.extractMin(). That means the queue is actually a priority queue of some kind. That means that we're efficiently prioritizing which path we're looking at each time we look at a new vertex. That means we need to make sure we're prioritizing the vertices with the right weight function.

When we were searching for the empty board node with BFS, we were searching in move order, meaning we searched all first moves before searching all second moves, before searching all third moves, and so on. Now with Dijkstra's algorithm, we could be searching moves out-of-order. We could be headed down a promising path on the twelfth move when the next vertex pulled out of the priority queue is actually for the tenth move because the previous 12-move path has gotten too long, and there are shorter paths with less moves at the top of the queue. We want to capture the property that the shortest path is the one with the least number of moves, but balance that with the idea that moves that remove more blocks are likely to be on the shortest path.

The problem with simply combining the move number for a vertex and the number of blocks removed for that vertex is that we want to minimize the former and maximize the latter. We can't mix the two goals. We need to pick either minimization or maximization for the priority queue to work. It doesn't much matter which way we choose, so we'll stick with minimization and make the values for number of blocks removed negative. Thus, the weight for any given edge is

weight = 1 - blocks_removed_on_move

This equation is true because each move will increase the number of moves by 1 and remove a certain number of blocks. To calculate the distance associated with a new vertex, v, we add the weight to the distance of the previous vertex, u:

distancev = distanceu + weightu,v

We can simplify this equation by noticing that the distance to any vertex is simply the accumulation of moves minus the accumulation of blocks removed, or:

distancev = move_number - blocks_removed

With that worked out, we can start implementing our version of Dijkstra's algorithm for Color Walk. First, we add the algorithm to the list of choices in the HTML input element and to the switch statement:
  function Solver() {
// ...

this.init = function() {
// ...

$('#solver_type').change(function () {
switch (this.value) {
// ...
case 'dijkstra':
that.solverType = that.dijkstra;
that.metric = areaCount;
break;
default:
that.solverType = that.roundRobin;
break;
}

// ...
});

// ...
};
Then we need to fill in the algorithm function, which is similar to the BFS algorithm, but with a priority queue instead of a regular queue and some changes to the limit checking on the size of the queue:
    this.dijkstra = function() {
var vertices = new PriorityQueue({ comparator: function(a, b) { return a.cost - b.cost } });
vertices = addVertices(vertices, 1, null, blocks[0].cluster.blocks.length);

while (vertices.length > 0) {
var vertex = vertices.dequeue();
markers = vertex.markers;

if (vertices.length > 250000) {
doMarkedMoves();
vertices.clear();
} else {
vertices = addVertices(vertices, vertex.depth + 1, vertex.control, vertex.cleared);
}

vertex.markers = null;
}
}
This code follows the outline of the pseudocode presented above fairly closely. It creates a priority queue, adds the first vertex, and then loops through vertices, pulling the vertex with the minimum cost (or distance, they're interchangeable) out of the queue each time and adding its child vertices to the queue inside addVertices(). I looked around for a JavaScript priority queue to use for this task, and found a nice one with a clean API. When creating the queue, you need to provide a comparator function so that it knows how to compare the value of nodes in the queue and maintain its priority requirement. The cost properties are an additional property on the vertices that's calculated inside addVertices().

Because the vertices don't stay in inserted order, and we need to know the cost of vertices before adding them to the queue, we need to run checkGameBoard() on each vertex in addVertices(). (Check out this previous post on BFS for details on how it was implemented before.) This change simplifies the above function a bit, and other than what's already been described, we only need to make sure the queue doesn't get too big. If it reaches too big of a size, we're going to forget adding more vertices, do the moves corresponding to the current minimum vertex, clear the queue, and bail out. We'll come around again at the new move number and partially cleared board to try again to find the end-of-game vertex. Some boards will have so many similarly weighted paths that this tactic is necessary, otherwise the queue will get too large and cause a crash. Surprisingly, this doesn't happen too often, and the algorithm will be able to complete the search on most boards in a single sweep.

Now let's look at what's going on in addVertices(). The idea is simple. This function needs to run through all of the controls, checking to see how many blocks each control removes, calculating the cost of each of these new vertices, and adding the vertices to the queue. If it runs into the end-of-game vertex, it should stop. Here's what it looks like:
    function addVertices(vertices, depth, prev_control, prev_cleared) {
var stop = false;
_.each(controls, function (control) {
if (control !== prev_control && !stop) {
var removed_blocks = control.checkGameBoard(depth, areaCount);

if (endOfGame()) {
doMarkedMoves();
vertices.clear();
stop = true;
} else if (removed_blocks - prev_cleared > 0) {
var markers_dup = markers.slice();
var cost = max_moves*depth - removed_blocks;
if (removed_blocks > 590) cost -= (max_moves - 5)*depth;
vertices.queue({markers: markers_dup,
depth: depth,
control: control,
cost: cost,
cleared: removed_blocks});
}
}
});
While the concept of the function is simple, the implementation has a number of tricky details. I had a pretty rough time getting this code right. It tripped me up more times than I care to admit, but let's go through and look at each detail anyway. Starting at the top, we need a stop flag to know when we've found the end-of-game and need to stop searching. If we only did the moves and cleared out the queue, we may have found the end while having more controls to loop through, and the each() function would go on its merry way, adding more vertices to the recently emptied queue. The algorithm wouldn't actually stop when it was supposed to, so we need the flag to make sure we ignore any leftover controls after finding the end-of-game vertex.

Next, the check to make sure at least one block was removed on the current move before adding the new vertex to the queue needed to be correct. The correct calculation is the difference between the removed blocks after the move and those blocks that were cleared before that move, because areaCount() counts all of the marked blocks in markers, not just the ones marked on the current move. Therefore, I needed to pass prev_cleared, the number of previously cleared blocks, into addVertices() from the parent vertex, which means I needed to store the number of blocks removed on a move in that move's vertex for this whole calculation to work.

Another subtle point is that with vertices being pulled out of the queue in a different order than they were inserted, each set of markers needed to be copied instead of sharing the same copy among all of the child vertices from any given parent vertex. That means the markers.slice() line needed to get moved inside the each() loop.

Then, the basic cost function of move_number - blocks_removed didn't work too well. Why that is has to do with the scale of those two values and how they work together. Imagine getting rid of either one of them. If the cost function was only move_number, then every vertex with a smaller move number would be pulled out of the priority queue before any vertex with a larger move number. Dijkstra's algorithm would reduce to BFS in that case. If, on the other hand, the cost function was only -blocks_removed, then the vertex with the most blocks removed would always be pulled out of the queue first, reducing the algorithm to the greedy algorithm.

These three possible cost functions all lie on a continuum, with BFS on one end, the greedy algorithm on the other end, and move_number - blocks_removed somewhere in the middle. But move_number - blocks_removed is not the only intermediate option. Consider that move_number is on the order of 30-40 moves, and blocks_removed will always end in 600 for a 30x20 block board. The move_number has much less weight than blocks_removed, but we can add a scale factor to it. This scale factor is actually necessary to get good performance because otherwise it's possible, and indeed likely, that the minimum vertex pulled out of the queue at some point during the search will clear out a dozen or more blocks and end the game, but be several moves more than the minimum number of moves. We want to add weight to move_number, so we can use max_moves from the UI input as the scale factor for move_number (represented in the code as depth).

With the correct scale factor—found to be around 25—the performance of the algorithm is much better, but it then hits another snag. Near the end of the search, the algorithm hits a wall and starts churning on all of the other possibilities in the queue because the move_number gets larger than blocks_removed on the vertices closest to the end-of-game vertex. The algorithm kind of devolves into BFS again, and it would take forever backtracking to explore older paths in its history unless we can find a way of forcing it to choose the vertices close to the finish line. To force this behavior, if we're within 10 blocks of finishing, we'll reduce the scale factor to bump the priority of those vertices. Since we're within 10 blocks of finishing, there are likely only a few colors of blocks left, and we can move more towards the greedy algorithm on the spectrum to finish quickly.

After getting all of these little, important details right, we can finally run a version of Dijkstra's algorithm that will run to completion without crashing and, hopefully, find some good solutions to the game boards. Let's see what happens when we run 100 iterations of this algorithm.

Dijkstra's Algorithm Results

We should keep in mind when looking at these results that the version of Dijkstra's algorithm that we're using is not the complete algorithm. We're intentionally stopping early when we find the first end-of-game vertex because if we let it run to completion, it would take an eternity to run, and in its current form would always run out of memory and crash. Taking that into account, here are the results with a move scale factor of 25:

100 iterations of Color Walk with Dijkstra's Algorithm

That result seems pretty good. How does it stack up to the other algorithms?

AlgorithmMinMeanMaxStdev
RR with Skipping 37 46.9 59 4.1
Random with Skipping 43 53.1 64 4.5
Greedy 31 39.8 48 3.5
Greedy Look-Ahead-2 28 37.0 45 3.1
Greedy Look-Ahead-5 25 33.1 41 2.8
Max Perimeter 29 37.4 44 3.2
Max Perimeter Look-Ahead-2 27 35.0 44 2.8
Perimeter-Area Hybrid 31 39.0 49 3.8
Deep-Path 51 74.8 104 9.4
Path-Area Hybrid 35 44.2 54 3.5
Path-Area Hybrid Look-Ahead-4 32 38.7 45 2.7
BFS with Greedy Look-Ahead-5 26 32.7 40 2.8
DFS with Greedy Look-Ahead-5 25 34.8 43 3.9
Dijkstra's Algorithm 29 33.1 40 1.9

Well, the minimum number of moves for Dijkstra's algorithm was bested by GLA, BFS, and DFS, but the mean was only barely bested by BFS and the maximum was equal to the previous best, BFS. Look at the standard deviation, though. Dijkstra's algorithm gives consistently good solutions by a significant margin. The next best algorithm for giving consistent results is the deep path-area hybrid algorithm with look-ahead by 4, and that one performs 5.6 moves worse on average.

Dijkstra's algorithm definitely has some nice characteristics, but why doesn't it perform the best in all cases? The main reason is because of what was mentioned earlier: we're not running the algorithm to completion. Cutting it off early means that there are still potentially shorter paths in the priority queue that could be explored, but we stop searching the first time we find the end-of-game vertex. The weight function also plays a role here because we're trying to play a balancing act between optimizing the primary goal—the number of moves—and the secondary goal that helps focus the search—the number of blocks removed. If we increased the scale factor to add more weight to the number of moves, the search will take much longer, and it will run out of memory for the growing priority queue more often, requiring the algorithm to commit to some number of moves for the last minimum vertex it pulls out of the queue and start again from that point.

Part of what makes Dijkstra's algorithm so consistently effective is that it is another form of a greedy algorithm, but it keeps track of other promising paths while it pursues what looks like the best option at every moment. Each time through the while loop, it's looking at the current best possible next vertex in the path, and when the child vertices of that vertex are added to the queue, the next best vertex may not be one of those just added, but a vertex from a different promising path stored in the queue. The algorithm will remember all of the other paths it could take, and once the current path gets too expensive, it will switch to a cheaper one until that one also gets too expensive. This constant development of numerous good options for the shortest path turns out to be a very efficient way to find one that's quite short, if not the shortest.

Given the potential of Dijkstra's algorithm, we're not quite done exploring it. While implementing it, some options became apparent for making it perform better. We could look at another hybrid approach with the greedy algorithm, either running the greedy algorithm before or after Dijkstra's algorithm, or we could try running Dijkstra's algorithm twice—once to half the blocks removed and again to complete the game. Much experimentation is possible here, combined with varying the scale factor, to see if we can push Dijkstra's algorithm to beat GLA-5. We'll explore those options next time.


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

Tech Book Face Off: Rails AntiPatterns Vs. The Rails 5 Way

It's been a while since I've cracked open a Ruby on Rails book, and there were still a couple of these books that I've been meaning to read. So far the Rails books I've read have been beginner's books and tutorials. With this latest pair of books, I wanted to go deeper into Rails and learn how to program fluently in the framework. The first book, Rails AntiPatterns: Best Practice Ruby on Rails Refactoring by Chad Pytel and Tammer Saleh, was published way back in 2010 when Rails 2.3 was cutting edge. I took a chance that the book would focus more on timeless advice than version-specific tips and tricks. The opposing book, The Rails 5 Way by Obie Fernandez, was published much more recently at the end of 2017 and should be at less risk of being out-of-date. Although, it looks like Rails 6 will be out soon. One thing's for sure: technology doesn't stand still, but that shouldn't matter too much if the books take that into account. Let's see how they fare.

Rails AntiPatterns front coverVS.The Rails 5 Way front cover

Rails AntiPatterns


Most programming books will tell you the right way to do things and give examples of how to implement that advice. This book takes a different tack and instead shows you what not to do, with examples of how to refactor those mistakes into clean Rails code. This change in approach is refreshing for the intermediate to advanced programmer because over time we develop certain habits as we try to discover how to do things in a language or a framework, and they're not always the best (or even very good) ways of doing those things. Having a book to help identify where our code has gone astray and how to fix it is a helpful tool indeed.

I was a bit worried about reading a book specifically about Rails that was written so many versions ago, being that Rails 2.3 was out at the time and we're now heading into Rails 6.0. I normally don't mind reading older books about programming in general, since those books have been filtered by time and tend to contain plenty of wisdom that has survived current fads. However, this is a book about a specific web framework, so if it is over eight years old, how could it possibly still be relevant? I needn't have worried. Most of the book focuses on general good programming practices within the context of the Rails environment. Such advice is still as valid in Rails 6.0 as it was in Rails 2.3, even if some of the implementation details have changed.

The structure of the book works well, with ten chapters each focusing on a different topic of the Rails framework. These topics range from the obvious models, views, and controllers on which the Rails architecture is based, to the related services, testing, and databases that are all a part of web application programming. Each topic covers a few antipatterns with descriptions of a common programming mistake and why it's wrong, followed by one or more solutions that fix up the antipattern into beautiful, DRY code. The obvious solutions—like that views should contain strictly display logic, controllers should be thin, and models should contain all of the business logic, but broken out into helpers and plain Ruby objects—are covered along with a number of less obvious antipatterns. Some of the more extended solutions are quite satisfying to follow as the refactorings morph an ugly hack into a pretty Rails implementation.

Throughout these refactored antipatterns the authors relate clear reasons for why things should be done a certain way and sound advice on how to choose between competing options. For example, when discussing whether or not to use scopes to create chainable filters on models, they say:
There are downsides to this approach, including problems with readability and simplicity, as well as abuse of the Law of Demeter. Whether you use this approach is a judgment call on your part. Will you use the added flexibility? Is it better than defining a handful of separate finders? Like many advanced refactorings, this one has no easy answer and depends greatly on your tastes.
I appreciate the honesty of this advice. In programming, as in most things in life, not all rules are hard and fast. Multiple good options can and do exist, and in these cases it comes down to a judgement call from the programmer, given the context of the specific situation. Experience and taste will develop into a programmer's style, and as long as they're not making a poor choice for the wrong reason, it's okay to prefer one viable option over another.

The authors aren't always so amenable to choice in correcting antipatterns. They do discourage attempts at divining the future, as in this example:
Developers new to the template pattern tend to want to err on the side of flexibility. This is a form of future-proofing, and it's a mistake—especially when working in a language as geared toward agile development and the refactoring cycle as Ruby.
It's almost always better to spend time fixing real problems in the here and now instead of trying to predict and head off future problems that may never happen.

The whole book was full of insights like these, and it was all packaged in easy-to-read prose and concise, relevant examples of how not to write Rails code, followed by how to fix it. I found it to be a quite enjoyable way to learn better Rails programming practices, and it reminded me of an old chess book that had followed a similar format: How Not to Play Chess. Seeing the mistakes that can be made and their consequences clearly laid out can prove quite helpful in identifying our own mistakes and seeing the path to correcting them. This teaching method was expertly executed in Rails AntiPatterns, and I highly recommend giving it a read if you do any programming in Rails.

The Rails 5 Way


This was definitely the longest book on a programming language or framework that I've read in a while. Weighing in at over a thousand pages, it definitely achieves tome status, and strives to be a comprehensive map of the Rails 5 framework. The forwards and introduction recommend reading it straight through at least once to get the lay of the land and see everything that's available in Rails before using it as a reference.

I would agree that knowing what tools are available in any given language or framework is half the battle when attempting to develop solutions as efficiently as possible. If you know that something you want to do is possible and you know where to look to research how to do it, then you're already ahead of the game in reaching an optimal solution. However, I was not going to read all the way through this book, considering the last third of it is a pure reference listing of the Active Model and Active Support APIs. That's skim-worthy material at best.

At least another third of the non-appendix part of the book is also reference listings of various APIs in the Rails framework. The trouble with this reference material is that it is interspersed with actual guidance on how to use the different parts of Rails. That makes it much harder to skim through the boring, tedious method descriptions without missing tidbits of more useful information, so most of the book was kind of a slog.

Obie Fernandez took a slightly unconventional approach to covering Rails, and instead of starting with Active Record, he started at a high level by covering the Rails configuration and environments, then followed the flow of a request by going into routes, REST, resources, and controllers before getting to Active Record. After five chapters of the various aspects of Active Record, he continued with Action View, helpers, and Haml, followed up by an assortment of short chapters on session management, security, Action Mailer, caching, background processing, Ajax, Turbolinks, Action Cable, and testing with RSpec. Whew. Like I said, comprehensive, and even if some things couldn't be included in depth, they were at least mentioned so that the reader could be aware of them and do more research on their own.

Some chapters were quite useful and informative, like the main chapters on the MVC architecture of Rails. These chapters contained a fair amount of good advice and recommendations that can save developers a lot of trouble, such as:
Rails' schema definition provides the authoritative record of truth for the latest version of your database schema. If you need to recreate your database on another server, you should be using db:schema:load, not running all the migrations from scratch.
Anyone who's tried to run a long chain of migrations while setting up an application on a new machine can attest to the futile debugging situation you end up in when one of the migrations inevitably fails halfway through. Using the schema is definitely the way to go. I also found the chapters on security, caching, and Turbolinks to be quite helpful, and I learned a bunch of great things that will help me build better Rails applications.

On the other hand, there were large swaths of the book that were pure drudgery. Most of the helpers chapter was like this with page after page of very similar method descriptions:
11.3.1 asset_path(source, options = {})
Computes the path to asset in public directory. If :type options is set, a file extension will be appended and scoped to the corresponding public directory. All other asset *_path helpers delegate through this method.

<...code example...>

11.3.2 asset_url(source, options = {})
Computes the full URL to an asset in the public directory. This will use asset_path internally, so they behave the same way. If the :host option is set, it overrides the global config.action_controller.asset_host setting normally set in config/environments/production.rb.

asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js

Also aliased as url_to_asset.
Ugh. This kind of stuff is much easier to search for in the online Rails documentation when you need to look it up, not by paging through a giant book. I was also surprised at how much of these method descriptions ended with comments about how useless they were. Maybe 20% of the methods would have a description of what they did with a code example, and then Fernandez would end it with saying he had never found a use for this method or you would never want to do things this way because some other method was much better. That got more than a little frustrating as the book dragged on.

It may sound a bit strange to criticize this book for including ways not to do things in Rails, having just praised a book whose entire structure was showing how not to do things and then showing the right way, but this is different. In Rails AntiPatterns, the author was showing common mistakes when programming in Rails and how to fix those mistakes by using Rails the right way. My complaint with The Rails 5 Way is that time is spent describing irrelevant methods that may have been included in the framework for completeness or for a specific, uncommon use case, but are hardly ever used in practice. When those things are included in a book whose title asserts that this is how things should be done, it muddies the waters.

Between the appendices, the long lists of method descriptions, and the inclusion of dozens of methods that are rarely used, this book is probably three to four times longer than it needs to be, all for the sake of being comprehensive. It was light enough on actual recommendations for how to use Rails, focusing more on every single thing that's available, that a more appropriate title would simple be The Rails 5 Documentation.


I much prefer reading programming books that directly address the 'how' and 'why' of programming, like these books I previously reviewed on how to use Rails. The Ruby on Rails Tutorial is especially good and the latest version is also up-to-date with Rails 5.  Even Rails AntiPatterns is still relevant nearly a decade later because it addresses the fundamentals of developing in Rails. I would rather leave the 'what' of programming to the online documentation where it is easily searchable and stays updated to the latest version of the tools. In that respect The Rails 5 Way falls flat. It would have been a much more compelling book if it had stripped out all of the drab documentation and focused on showing the right way to program in Rails and explaining why that was the best way to do it. As it is, reading through Rails AntiPatterns and referring to the online documentation is the more productive path.

The Untold Sad Story Of Erangel

Erangel, one of the maps of PUBG, is 8×8 km wide containing grass, woodland, urban and sea terrains. According to PUBG team, this map was inspired by a real-life abandoned island located in the black sea near Russia. The story behind this island is full of sadness and sorrowful that might change the way you play this map.








Everyone knows that without struggle there is no success. But the story of Erangel is related to the society who never got the fruit of their struggles.
         
                  Erangel is a fictional island located in the black sea abandoned near Russia. Once this was a happy island but today there is nothing except silence. What happened to this island that it became an isolated land where not even a person exists?

    The story starts in 1950 when the island was under Soviet forces. They used to test their biological/chemical experiments on the local people. The people became aware of it and they decided to protest against it. A large mass gathering of people protested against this illegal experiments. The military also tried to confiscate the protest and bring it under control. They established electrical fences all over the area to restrict the protesters. In the game, the same thing happens where the players are restricted by the blue zone and they are pressurized to come into a small area. 
   
                  The protesters didn't give up and gave their best but in the end, they failed. They didn't get the fruit of their struggles and then they decided to leave the island. One after one they left the island leaving the island abandoned.

          Now the same island inspired to Tencent games and they decided to pick this Island as one of the maps of PUBG and they named the island Erangel. The name Erangel was actually inspired by PLAYERUNKNOWN'S daughter Eryn. 

          So guys you have to think now what is the role of PUBG players on this island? Are they the millitary campers who is trying to confiscate the protest or the civilians who are fighting for their own rights? Tell us in the comments section below!

Brainstorming With Factoring

In the last post I described how I sometimes describe a problem with a matrix, and then look at the matrix transpose to see if it gives me new ideas. Another technique I use is to look for a factoring.

In algebra, factoring transforms a polynomial like 5x² + 8x - 21 into (x + 3)·(5x - 7). To solve 5x² + 8x - 21 = 0, we can first factor into (x + 3)·(5x - 7) = 0. Then we say that x + 3 = 0 or 5x - 7 = 0. Factoring turns a problem into several easier problems.

x 3
5x 5x² 15x
-7 -7x -21

Let's look at an example: I have six classes, File, EncryptedFile, GzipFile, EncryptedGzipFile, BzipFile, EncryptedBzipFile. I can factor these into a matrix:

Uncompressed Gzip Bzip
Unencrypted File Gzip(File) Bzip(File)
Encrypted Encrypt(File) Encrypt(Gzip(File)) Encrypt(Bzip(File))

Using the Decorator pattern (or mixins), I've turned six different types of files into four components: plain, gzip, bzip, encrypt. This doesn't seem like much savings, but if I add more variations, the savings will add up. Factoring turns O(M*N) components into O(M+N) components.

Another example comes up when people ask me things like "how do you write linear interpolation in C#?" There are a lot of potential tutorials I could write:

C++ Python Java C# Javascript Rust Idris
Interpolation
Neighbors
Pathfinding
Distances
River maps
Isometric
Voronoi
Transforms

If there are M topics and N languages, I could write M*N tutorials. However, that's a lot of work. Instead, I write a tutorial about interpolation, someone else writes a tutorial about C#, and then the reader combines knowledge of C# with knowledge about interpolation to write the C# version of interpolation.

Like transpose, factoring only helps sometimes, but when it applies, it can be quite useful.