Wednesday, 15 April 2015

The Cult of Health

A few days ago, someone in my Facebook feed shared an article about how psychiatrists want to classify an obsession with health as a mental illness. The impropriety! How could it possibly be that wanting to be healthy be a disorder?!

I don't know if the article was correct, and I certainly don't know the details of psychiatric diagnoses, or what should be a disorder or not. I'm not a psychiatrist. But the article did get me thinking about a more general concern I have - that the fixation on health isn't so much about actually being healthy.

Before I go into this, I need to take a few steps back.

A number of years back, I was browsing the health section in a book store. There were plenty of books to choose from, but almost all of them were complete crap. I left the the book store with two thoughts: people want to be healthy, and there's plenty of misinformation out there for anyone wanting to make that choice. For a society that demonises being fat, glorifies being fit and thin, what we've got is an industry dedicated to making money from it.

Fast forward a little. A couple of years ago, I joined a gym in a somewhat futile effort to get in shape. One day, as I was trying to work my triceps, I looked up at the TV. There was an ad for some equipment that promised all the results without any of the effort. Here I am in agony making barely any inroads, yet there was a machine promising me everything I wanted without needing to engage in this masochistic endeavour. Naturally I'm dubious, so I kept my gym membership and did not get suckered in by the infomercial, but again there was that parasitism on the desire for being fit. (Ignoring, for a moment, the false promise that is a gym membership.)

About 18 months into the gym membership, and it was going reasonably well. I was losing weight, though not as much as I liked, and I was getting fitter. Then two things happened: I injured myself, meaning I had to cut back, and I took on some extra responsibility at work, which meant I was working longer hours and eating a lot of pizza (provided by the company). In a month, I had regained over half of the 11kg I had lost over the previous 18 months. There had to be a better way.

First, I read The Healthy Programmer - a book I had bought during a sale, but it sat on my virtual shelf for months. I read it in 4 days, and was eager to take my new-found knowledge and put it into action. Second, my mother told me about a website called MyFitnessPal, which I could use for tracking my food and exercise - something I took to quite obsessively. And between the diet and exercise, I've had really good results. I'm now normal weight for the first time in my adult life, and I recently beat the 10km mark in a run.

So hopefully I've established that I do have an interest in health, in being healthy, and in leading a healthy lifestyle. I'm not at all opposed to the idea of people wanting to be healthy, and I don't care in the slightest if that interest in health for others is something related to wanting to look sexually desirable or simply wanting to avoid the prejudices our society has against the overweight. People spend far too much effort impugning and maligning the motives of others. (Again, the impropriety!)
  1. Health foods - There's an aisle in my local supermarket dedicated to what it calls "health" foods. It's good that there's an aisle for foods that don't contain foods with allergens, it's hard to grasp how the foods in that section are any healthier than the equivalents in different aisles. Are the organic almonds really better for me nutritionally than the regular almonds sitting with the other nuts? Are bars made of sesame seeds, condensed milk and honey? They taste good, don't get me wrong, but it's hard to know where the "health" comes into it. And of course, the "health" food is opposite the wall of vitamin pills and meal replacement powders. So we have an entire section of the supermarket dedicated to foods pretending to be better for us next to pills and powder that try to mimic food. Whatever happened to a balanced diet?
  2. Health food claims - Two aisles over from the wall of pills is the wall of sugar, which incidentally is my favourite wall because it has dark chocolate. But it's also the aisle that shows how health claims permeate all our subconscious desires. Because labels like "all natural" and "99% fat free" sit proudly on packets harbouring highly process sugar melded into fun shapes. No marketer is really trying to convince us that Starburst is actually healthy, but highlights the absurdity of the practice taken to an extreme. It's comical on that packet of Starburst, but pernicious on TV dinners with the brand "Healthy Choice".
  3. Superfoods - There's no such thing. There are basic micro and macronutrients we need to survive, and the best way to do that seems to be to eat a balanced diet. But beyond that, the idea that there are particular foods that are nutritionally-overcharged and really ought to be on our plates is nothing more than marketing hype. Our species survived and thrived long before the vitamin pill aisle, and we similarly did without kale salad dusted with quinoa and chia seeds and a side of acai berries.
  4. Calories - On the flip-side to superfoods is the dreaded notion of energy. So while most of us need ~2000 of these a day to survive, foods that are high calories are demonised despite having a good nutritional profile. One article talking about the nutritional content of almonds, for example, talked about all the nutrients it had in it, then warned people off them because they are very high in calories. Again, it seems to be the superfood thinking - that there's this one magic ingredient that we should consume copious quantities of, and it would be almonds if not for its caloric content.
  5. Ethical eaters - I can appreciate vegans being so for ethical reasons, or people not wanting to eat certain foods because of industrial practices. But it amazes me how these ethical considerations just happen to line up with what they think is healthy. Just a vegan's luck that not only is eating animals cruel to the animal, it's unhealthy for us. Just as organic has extra nutritional benefits, genetically modified food is a health crisis in waiting, and those "unnatural" artificial sweeteners are already a health crisis. It's great that there's a direct correlation between ethical eating and healthy eating...
  6. Diets - Inevitably I'm asked what kind of diet I'm on. I answer "none". After being looked at sceptically, I elaborate "I'm on the not overeating diet". After growing up in a world where fat was the enemy, now it's carbohydrates that are the enemy. We now should quit sugar, live like our paleo ancestors (with our modern foods), take two days a week for radical reduction in calories, or even kick-start our metabolism with coffee filled with butter and coconut oil! And when those techniques don't do anything to help with the obesity crisis, you can be sure there's going to be some new techniques complete with success testimonials just around the corner. Again, whatever happened to a balanced diet?
  7. Willpower and choice - Everywhere in the West seems to be in an obesity crisis right now. So while more adults all around the world are becoming overweight, the issue keeps coming down to choice. That is, if you are fat, then you chose to be fat. If you try to lose weight and fail, it's because you didn't have the willpower for it. In other words, you are the disgusting blob you are because you make bad choices and don't apply willpower properly. It strikes me as a post hoc justification for victim-blaming, and that's highlighted by how much this argument is used by junk food companies to oppose regulation, but it's prevalent nonetheless.
  8. Motivation-in-a-box - The internalisation of the willpower argument is the idea that a particular product is going to be the thing that finally motivates you into becoming healthy. Right now, it's activity trackers. A few years ago, it was Wii Fit and Dance Dance Revolution. For as long as I can remember, there's always been some product that plays to our recognition of our inactivity. Even at the gym, I see infomercials on the TV for products that will tone our body without the slightest strain.
  9. The shortcut - Since the dawn of our species, we've taken the effort to make us look better than we actually are. We want to give the impression of health, youth, and vitality. The beauty industry is the logical, albeit absurd, consequence of this fundamental drive. And, thankfully, for those who recognise that there's no such thing as a product that will get them the look they desire, there's a product that will give the illusion. Body-shaping clothes sitting atop of skin full of fake tan and anti-wrinkle cream, you can appear healthy to the ouside world.
  10. Purity - Perhaps the worst aspect of health as a commodity is the expectation that health should be the aspect of our being our actions revolve around. Why should it be that drinking a glass of red wine for its supposed health benefits is acceptable, while drinking a glass of red wine for the sheer pleasure not? This idea that we have to be obsessed with our health (and by extension, everyone else's) is something that's just assumed by the "my body is my temple" crowd. The flip-side of this language is the impurity of failing to live up to the same aspirations, thereby condemning the failure as having morally failed.
What's questionable to me is whether any of this is doing us any good. Do any of the products in the store that have health claims make for part of being healthy? Does the consumption of superfoods and avoidance of calories? Do any diets, ethical or otherwise, work? Does making it a matter of willpower? Or is the focus on the appearance of health creating the market for products that mimic the look? And are we just becoming self-righteous in the condemnation of others for the crime of not living to our ideals?

We can frame obesity in economic terms, as everything is these days, and deduce that it's a moral imperative from the sheer weight exerted on our medical systems - similar to how we've deduced the moral imperative to rid tobacco from the world. But while smoking has declined, decades of focus on the "obesity epidemic" has seen obesity soar, as if somehow obese people haven't gotten the message that it's really really really really bad to be obese.

It seems to me that the focus on health as this sole marker of societal (and therefore, human) worth needs to be vastly toned down, or at the very least addressed in the way other health crises have been addressed - by trying to root out the underlying factors. Because what we have right now is a self-aware society getting more obese doomed to failure by the memetic equivalent of snake oil. The focus on health, at least in the current environment should be considered a complete failure, because it has not led to healthy outcomes. It hasn't even led to a blueprint for health that people can at least try to follow, beyond eat less skittles and exercise more - with the purchasing of skittles thereby being a moral failing. Clearly something has gone horribly wrong!

Saturday, 28 March 2015

Game Review: Frozen Cortex

The rogue-like FTL: Faster Than Light was an uncompromisingly difficult game, where playing up to two hours at a time could be undone by one moment of chaos. So many times I ploughed through that game only to declare the universe as being intrinsically unfair as a simple mistake cost me a good hour of careful planning, only to have to start over again. The illusion is that next time I'd get it right. I'd shield the doors earlier, or make sure to stock up on extra missiles, or avoid saving the crew of a ship on fire.

The futility of the game combined with the (somewhat illusory) sense that I was in control kept me coming back. It was only when I could get through to the end of the game regularly that I finally lost interest.

Which brings me to Frozen Cortex.

Frozen Cortex is what you get when you cross American Football with Frozen Synapse - which is in turn what you get when you cross chess with guns. I really enjoyed the idea of Frozen Synapse, but I never could fully immerse myself in it (Hell is Internet multiplayer...) I haven't had that trouble with Frozen Cortex, already rocking up close to 30 hours of game time.

The funny thing is that there's really not a lot to Frozen Cortex. The entire game consists of short matches, with each player controlling 5 robots on rectangular arenas populated with various obstacles. It's simple enough to grasp, simple enough to control, and a tiny mistake can undo hours of meticulous planning. Any given match can be thrown away from incorrectly guessing what an opponent would do, and that makes knock-out mode all the more difficult.

Because the games are so short, minor mistakes have those major consequences. Screw up on your own offense, and it's virtually guaranteed to be game over. Misread your opponents and it can be similarly difficult to come back during your own. The game's annoying habit to "pause" the turn halfway through a touchdown pass can often be just rubbing it in that you didn't realise the opponent had that option open, and there's nothing to do but slam the PRIME button and hope there's still enough time to recover.

And there are many ways it can cost you. A slightly mistimed run means a key player is knocked out. Throwing to a player too close to an opponent gives them no time to act before they are tackled. Passes that get intercepted. Having your player slightly mistime an interception. Moving to the wrong side of an obstacle. And of course, all these are compounded with scoring pads littered throughout the arena. They are only worth 2 points (compared to a 7 point "touchdown"), but a good run can cross several pads at a time. One particularly bad example was a robot that could just sneak around an obstacle for an unguarded heavily-padded run to the end-zone.

It's that futility that like in FTL: Faster Than Light keeps me coming back. I didn't see that play. I'll be mindful of that next time. It's with that attitude I've been able to beat the game on season mode (and opened up interesting variations), but haven't quite gotten through a knockout comp yet. The game is cruel, uncompromising, punishing, and that's what makes it such a rewarding experience.

Monday, 15 December 2014

Slipping Back into Old Habits

I have a lot more time to think about coding issues than I have time to code. In some ways, this is quite handy as it allows me to plan through certain problems and foresee potential pitfalls of a particular solution. I think there are two key problems with this, however. First, even if I come up with a solution it's still only half the job done. Second, it gets be bogged down in coding problems that often are beyond my coding abilities. This all became apparent in the last month when I became bogged down in a particular design problem. A month later, I'm still no closer to a solution, and worse still I'm not making any progress in any other area. And instead of just coding a workaround for the time being, I became fixated on solving the problem.

(I needed compile-time polymorphism where I previously had run-time polymorphism. I read through books and websites on templating, tried rudimentary attempts at adjusting my code, but always I was trying to code up a perfect solution in my head, weighing up potential considerations and pitfalls. And at the end of it, all I have is the lament I just didn't code around it.)

What I find so disappointing is I know all this. There's nothing particularly revelatory in that I dwell on problems instead of working around them, but it is disappointing that I still haven't moved on from the problems I had when I was at university learning to code. I get too hung up on solutions that I haven't learnt how to code around it.

This game coding happens in the limited spare time I have. It's a hobby, and comes after other aspects of work and life. So maintaining motivation is always a struggle, especially in the face of seeming blockers. And from what I've come to know about the role pressure plays in inhibiting the creative process, the limited time often translates into fixating on particular solutions instead of just getting on with something else.

Given that I'm still adjusting back into C++, it's easy for me to think of failures as a lack of knowledge of the language itself. Sometimes that's apparent, and I know as I'm reading through various C++ material that I'm skimming for a particular solution context rather than internalising the language feature as a potential design tool in the future. But I think it would be a cop-out to blame my current understanding of C++ when the language is so flexible in the kinds of solutions that are permitted.

It's always, I think, going to be a question of motivation. That is, am I able to code something that's good enough for the task at hand? One thing that's problematic is that my initial scope is far too ambitious – that is, I'm trying to create a framework that could be used to make any number of 2D games. So in that respect I'm stuck trying to figure out generic solutions.

And therein lies the problem. How do I know I'm going to get to use any of the design work I've put in so far? For example, all the time I spent writing wrapper objects for SDL code, as far as I can tell, has had very little benefit so far. Indeed, the main reason I did that was to have a sort of platform agnosticism – the theory was that I could refactor out the core features of the engine to a different library without having to permeate those decisions down through game code. Yet if I'm never going to do that, then as happy as I am with some of the wrapper code I wrote, then a lot of that effort was wasted.

(Funnily enough, as I was trying to learn SDL, one thing I got frustrated with was that the tutorials were so direct in their use of the libraries. I took it as an act of clarity on account of the tutorial writers that they didn't try to muddle things up by enclosing their code in some simple patterns. But as I was coding up my input system, I wondered just what benefit I was getting out of mapping SDL structures with my own.)

This too, I think, highlights the perils of solo development. As I'm working on this project, I'm accountable to one person: myself. What I design, what I build, what my desired end-product is – all that is my decision. And since I'm starting from scratch and working completely unguided, it’s inevitable that I'm going to not only make bad decisions, but those bad decisions will lead t o a lot of wasted time. You live, you (hopefully) learn.

What I think I've learnt is something about the limitations of myself. If I have a certain disposition to coding a certain way, then it would be madness to think I can just overcome it by saying "don't". What I hoped with prototype-driven design was that I could better focus on what needs to be done, rather than what I would think might need to be done. That, now, seems like only addressing half of the equation, and what's missing are techniques for build-and-refactor so I don’t get bogged down where an elegant solution is not immediately apparent.

I know at times I need to get out of my head – I have notebooks that are filling up with ideas faster than I'm adding lines of code to the codebase. What will remain to be seen is if I can work with my own personal practices and habits. Like losing weight, if you try to do it on willpower, then you’re going to fail miserably.

Sunday, 23 November 2014

The How of A*

(For the Why of A*, go here)

Here is a basic implementation of A*. Note that this isn’t necessarily an optimal solution, but it is a working one.

For my solution, I have two classes: Node and PathfindingAStar. The two classes are tightly coupled, with Pathfinding depending on Node to have certain properties in order to work. Node itself is the bridge between the pathfinding subsystem and the wider system.

Here is Node.h:

typedef struct
{
 unsigned int x;
 unsigned int y;
}NodePos;

class Node
{
public:
 Node(unsigned int p_x, unsigned int p_y);
 ~Node();
 NodePos getPosition();
 bool isSameNode(Node* p_node);
 void setNeighbourAt(Direction p_dir, Node* p_neighbour, double p_cost = 1.0);
 Node* getNeighbourAt(Direction p_dir);
 unsigned int getMovementCost(Direction p_dir);
 std::vector getAllAvailableDirections();
 bool isOccupied();
 void setOccupied(bool p_occupied);
 void setParent(Node* p_parent);
 Node* getParent();
 double getG();
 void setG(double p_distanceTravelled);
 double getH();
 void setH(double p_heuristic);
 double getF();
 unsigned int getX();
 unsigned int getY();
private:
 std::map m_movementCost;
 NodePos m_pos;
 std::map m_neighbours;
 Node *m_parent;
 bool m_occupied;
 double m_h;
 double m_g;
};
The Node caters for two purposes. Firstly, it holds information about the world itself that are vital for Pathfinding to operate. The position of the world, the neighbours of the node, the costs, and whether the node is occupied are all created and maintained by the game code. Neighbours cannot be populated upon creation because neighbours won’t have necessarily have been created themselves. Whether the node is currently occupied will be need to be maintained by the game code.

Secondly, the node holds information relevant to the cycle of the path. m_g and m_h are used by Pathfinding for the node’s distance from the start node and to the goal node. Similarly, m_parent is used to keep track of where the navigation came from.

As far as methods in Node.cpp go, there are two methods that are worth exploring, both to do with the available directions.

void Node::setNeighbourAt(Direction p_dir, Node* p_neighbour, double p_cost)
{
 m_neighbours[p_dir] = p_neighbour;
 m_movementCost[p_dir] = p_cost;
}
std::vector Node::getAllAvailableDirections()
{
 std::vector directions;
 directions.clear();
 std::map::iterator itr;
 std::pair pair;
 for(itr = m_movementCost.begin(); itr != m_movementCost.end(); itr++)
 {
  pair = *itr;
  directions.push_back(pair.first);
 }
 return directions;
}
The Pathfinding algorithm will need to know what directions are available to move in, and how much it will “cost” to move in that direction. An alternative would be to simply have a cost associated with a node, but this way gives more flexibility to the game code. So there are two lists, one for the cost of moving in a direction, and one for the neighbour Node in that direction.

Since directions are set when neighbours are set, there’s never any need to worry about the two maps going out of sync. So either map could have been iterated over to build a list for Pathfinding to use. This returned list could just as easily have been a private member of Node, generated as part of the setting of neighbours, and perhaps could be a point of efficiency to do so in the future.
Here is the source code for PathfindingAStar.h:

#include "Node.h"

class PathfindingAStar
{
public:
    static PathfindingAStar* getInstance();
    ~PathfindingAStar();
 std::list findPath(Node* p_startNode, Node* p_goalNode);
private:
 std::list finalisePath(Node* p_currentNode); 
 PathfindingAStar();
 static PathfindingAStar *singleton;
};
Pathfinding has one public method – to find the path. The rest is just for housekeeping. There only ever needs to be one instance of the class, so the Singleton pattern is implemented. There’s nothing particularly special about the class from an OO point of view, as all of the work is done in the single method search.

std::list PathfindingAStar::findPath(Node* p_startNode, Node* p_goalNode) 
{
 std::vector m_openList; //Nodes that are active
 std::vector m_closedList; //Nodes that have been used
 //Clear the list
 m_openList.clear();
 m_closedList.clear();
 //Reset the node to parent for the current cycle
 p_startNode->setParent(0);
 //and reset all values of f, g & h
 p_startNode->setG(0.0);
 double h = (double) abs(p_goalNode->getX() - p_startNode->getX());
 h += (double) abs(p_goalNode->getY() - p_startNode->getY());
 p_startNode->setH(h);
 m_openList.push_back(p_startNode);

 while (!m_openList.empty()) //If there are still nodes to explore
 {
  //Get the node with the smallest f value
  Node* currentNode = m_openList.front();
  //Remove node from the open list
  std::vector::iterator itr = m_openList.begin();
  m_openList.erase(itr);


  if (currentNode->isSameNode(p_goalNode)) 
  {
   std::list goalList = finalisePath(currentNode);
   //return out of the program
   return goalList;
  }

  std::vector neighbours = currentNode->getAllAvailableDirections();

  bool isFound;

  for (unsigned int i = 0; i < neighbours.size(); i++) {
   isFound = false;
   Node *n = currentNode->getNeighbourAt(neighbours[i]);
   if (n->isOccupied()) {
    continue;
   }
   //Make sure node isn't in open list
   for (unsigned int j = 0; j < m_openList.size(); j++) {
    Node *n2 = m_openList.at(j);
    if (n2 == n) 
    {
     double g = currentNode->getG() + currentNode->getMovementCost(neighbours[i]);
     if (g < n2->getG()) 
     {
      std::vector::iterator itr2 = m_openList.begin();
      m_openList.erase(itr2 + j);
      for (; itr2 != m_openList.end(); itr2++) 
      {
       Node* n2 = *itr2;
       if (n->getF() <= n2->getF()) 
       {
        break;
       }
      }
      m_openList.insert(itr2, n);

      n2->setG(g);
      n2->setParent(currentNode);
     }
     isFound = true;
     break;
    }
   }
   if (isFound)
    continue;
   for (unsigned int j = 0; j < m_closedList.size(); j++) 
   {
    Node *n2 = m_closedList.at(j);
    if (n2 == n) 
    {
     isFound = true;
     break;
    }
   }
   if (isFound)
    continue;

   n->setParent(currentNode);
   //work out g
   n->setG(currentNode->getG() + currentNode->getMovementCost(neighbours[i]));
   //work out h
   h = (double) abs(p_goalNode->getX() - n->getX());
   h += (double) abs(p_goalNode->getY() - n->getY());
   n->setH(h);

   //add to open list in order
   std::vector::iterator itr2 = m_openList.begin();
   for (; itr2 != m_openList.end(); itr2++) 
   {
    Node* n2 = *itr2;
    if (n->getF() <= n2->getF()) 
    {
     break;
    }
   }
   m_openList.insert(itr2, n);
  }

  m_closedList.push_back(currentNode);
 }
 std::list dummylist;
 return dummylist;
}
The first few lines of code are setting up the world. Since the start node has no parent, it’s set as 0. The start node is then pushed onto the frontier list. The reason there are two lists is so that once a node is fully explored, it never needs to be explored again. It subsequently shifts from the frontier list to the closed list.

The main algorithm is a while loop, which will run until either the goal Node is found, or the frontier list is exhausted. The Node at the front of the frontier list (sorted by f) is checked for whether it is in-fact the goal Node, in which case the algorithm constructs the list of Nodes and returns it. Otherwise, the Node goes through each available direction on the current Node, removes it from the frontier list, and adds it to the closed list.

For each neighbour Node, it needs to be checked whether it’s already a) on the frontier, or b) on the closed list. In either of these cases, the Node needs no further exploration except in one special case of a Node on the frontier. If the distance travelled to the Node in this iteration is less than the distance travelled to the Node, then the Node and frontier list need to be updated. i.e. we have found a shorter path to the same position. If the neighbour Node is not yet explored, its g and h are calculated (in this case, h is calculated using the Manhattan heuristic), its parent is set as the current Node, then it is put onto the frontier list sorted by f. Finally, if the frontier list is exhausted, this means there was no path from the start Node to the goal Node, so an empty list is created and returned. Thus the empty() function on list acts as the indicator of whether there was path successfully generated.

The code for building the path list is as follows:

std::list PathfindingAStar::finalisePath(Node* p_currentNode) 
{
 std::list goalList;
 while (p_currentNode != NULL) 
 {
  goalList.push_front(p_currentNode);
  p_currentNode = p_currentNode->getParent();
 }
 //don't need the first node
 goalList.pop_front();
 return goalList;
}
The code is fairly straight forward. Since each Node knows its parent, it’s simply a matter of traversing back to the first Node where the parent was set as NULL.
And there it is – a basic implementation of the A* algorithm. It’s by no means the most efficient possible version of the algorithm, nor does it do any of the C++ things one really ought to do when using the language. The important thing is that it works, and it’s reasonably clear how.

Saturday, 22 November 2014

The Why of A*

For pathfinding, breadth-first search (BFS) is almost always preferable to depth-first search (DFS). The general problem of a breadth-first search is that it is resource-intensive. Depth-first follows a single path that grows as the path is explored. Breadth-first explores multiple paths at once, with the amount of paths growing exponentially.

Both DFS and BFS are brute force attempts at finding a path, but they express themselves in different ways. A depth-first will in all likelihood find a path that won’t directly go from A to B, but follow the rules which govern how to choose the next position to explore. A breadth-first search might find a more direct path from A to B, but the result will take much more time to compute – something that’s unforgivable in a game which requires immediate feedback.

There are ways of directing a BFS so that it doesn’t take as much computational power. A* is the best of these, but it’s worth at least glancing over why this is the case. A* works on the following equation:
f = g + h
Where g is the total distance travelled, h is the estimated distance left to travel, and the smallest f is chosen next. An undirected BFS would be the equivalent of ignoring h altogether, so it’s worth exploring the role of h.

One strategy with a BFS is to just take into account the shortest distance to the goal. So whatever node is “closest” (and there are various strategies for working this out) should be the next node to search. This strategy works well if there are no obstacles directly between the start node and the goal node, as the closest node will always bring the path closer to the goal. The strategy fails, however, if there are obstacles, because the path will inevitably explore those dead ends like a DFS would.

A* gets around this problem by also taking into account just how far the path has travelled. In other words, what makes a path the most fruitful to explore is the estimated total of the journey. A path that goes down a dead end will have an overall larger journey than a path that took a deviation before the dead end path.

With the A* algorithm, the final output would be a path that always has the least number of moves between A and B calculated with exploring as little of the search tree as necessary. The algorithm, like with any algorithm, has its limitations, but ought to be used where the start and end goals are known and need to be computed.