Wednesday, March 27, 2013

Code structure: Cleanliness and efficiency

There are many ways to help make your code more readable, less bug-prone, and even more efficient. These are ignored by so many people whose code I've seen that I've decided to post about it. To many of you this stuff may be considered common sense, and that's a good thing.

Early out with conditionals when appropriate. This prevents your conditionals from nesting.


Bad:
void processMapNodes()
{
    if ( !map.hasBeenProcessed() )
    {
        map.process();

        // Other stuff here

        map.setProcessed(true);
    }
}
Good:
void processMapNodes()
{
    if ( map.hasBeenProcessed() )
    {
        return;
    }

    map.process();

    // Other stuff here

    map.setProcessed(true);
}

Early out with for loops. If you have to loop to find something, break out as soon as you've found it.


Bad:
void removeMapNodeByName(const std::string& name)
{
    for ( int i = 0; i < map_nodes.size(); ++i )
    {
        if ( map_nodes[i].getName() == name )
        {
            // Possibly some stuff here

            map_nodes.erase( map_nodes.begin() + i );
        }
    }
}
Better:
void removeMapNodeByName(const std::string& name)
{
    for ( int i = 0; i < map_nodes.size(); ++i )
    {
        if ( map_nodes[i].getName() == name )
        {
            // Possibly some stuff here

            map_nodes.erase( map_nodes.begin() + i );
    
            // We've found what we're looking for, so we break and stop looping
            break;
        }
    }
}
Best: (Use a conditional to continue as soon as possible)
void removeMapNodeByName(const std::string& name)
{
    for ( int i = 0; i < map_nodes.size(); ++i )
    {
        if ( map_nodes[i].getName() != name )
        {
            continue;
        }

        // Possibly some stuff here

        map_nodes.erase( map_nodes.begin() + i );
        break;
    }
}

Some of these examples are just to give you an idea of how to break out of code as soon as possible, but sometimes you can get even more efficient. In the example above we're simply going through a vector of data and looking for a match and then removing it. If what you're doing is really that trivial then you can use the standard library remove_if algorithm to do that for you. There are good examples of that, here, and here.

Don't nest your conditionals unnecessarily


Bad: Here we have nested conditionals, it starts to get messy
void foo(Bar* pBar)
{
    if ( pBar )
    {
        if ( pBar->hasData() )
        {
            Data* pData = pBar->getData();
            
            for ( int i = 0; i < pData->size(); ++i )
            {
                if ( pData[i].needsProcessing() )
                {
                    pData[i].process();
                }
            }
        }
    }
}
Good: We unnest our if statements and leave the for loop as soon as possible.
void foo(Bar* pBar)
{
    if ( !pBar || !pBar->hasData() )
    {
        return;
    }
     
    Data* pData = pBar->getData();
            
    for ( int i = 0; i < pData->size(); ++i )
    {
        if ( !pData[i].needsProcessing() )
        {
            continue;
        }

        // Do a bunch of stuff here

        pData[i].process();
    }
}

Create data only once it's needed

Bad: We're creating 'node_pos' at a point before we might exit the function early. This is wasteful and unnecessary.
void moveToMapNode(const std::string& name)
{
    Vector3 node_pos;

    if ( map_nodes.empty() )
    {
        return;
    }

    for ( int i = 0; i < map_nodes.size(); ++i )
    {
        if ( map_nodes[i].getName() != name )
        {
            continue;
        }

        node_pos = map_nodes[i].getPosition();
        break;
    }

    player.setPosition( node_pos );
}
Better: We create node_pos after any point were we could leave the function early, so we're never creating it when it might not be needed.
void moveToMapNode(const std::string& name)
{
    if ( map_nodes.empty() )
    {
        return;
    }

    Vector3 node_pos;

    for ( int i = 0; i < map_nodes.size(); ++i )
    {
        if ( map_nodes[i].getName() != name )
        {
            continue;
        }

        node_pos = map_nodes[i].getPosition();
        break;
    }

    player.setPosition( node_pos );
}
Best: We don't need to create a named variable 'node_pos', and it doesn't need to be used in as large of a scope as it was, we can use the position we get directly within the for loop.
void moveToMapNode(const std::string& name)
{
    if ( map_nodes.empty() )
    {
        return;
    }

    for ( int i = 0; i < map_nodes.size(); ++i )
    {
        if ( map_nodes[i].getName() != name )
        {
            continue;
        }

        // This is still fairly clear, even though we have a function call within a function call.
        // I wouldn't recommend nesting function calls any deeper than this, or debugging them 
        // and their return values can get messy if you end up with any bugs.
        player.setPosition( map_nodes[i].getPosition() );
        break;
    }
}

Sort your conditionals such that the cheapest ones are evaluated first, or the one most likely to evaluate positively is evaluated first.

Bad: We're calling the more expensive function first. Both functions in the conditional must be true for us to continue, so we should be calling the cheap function first, and if it returns false there is no need to call the expensive function.
void someFcn()
{
    if ( isExpensiveFcnTrue() && isCheapFcnTrue() )
    {
        // Do stuff here
    }
}
Good: We're calling the least expensive function first.
void someFcn()
{
    if ( isCheapFcnTrue() && isExpensiveFcnTrue() )
    {
        // Do stuff here
    }
}
Bad: We're placing the cheaper methods first, however, if they're functions that are true most of the time then we're still likely to have to evaluate all of the functions, even the expensive one that is usually false.
void someFcn()
{
    if ( cheapFcnThatUsuallyReturnsTrue() 
      && someOtherCheapFcnThatsUsuallyTrue() 
      && moderatelyExpensiveButUsuallyFalse() )
    {
        // Do stuff here
    }
}
Good: We call the more expensive method first, because it usually returns false, allowing us to skip the calls to the less expensive functions that usually return true. Basically your goal is to pay the lowest overall cost in your conditional statement, sometimes that will be calling cheaper methods first to avoid having to call expensive ones, if all of the functions stand similar chances of returning positive values, and sometimes you'll want to call more expensive ones first if it means you can save yourself numerous calls to other methods afterward. You'll need to make a judgement call based on the expense of the methods and what allows you to bail out as early as possible.
void someFcn()
{
    if ( moderatelyExpensiveButUsuallyFalse() 
      && cheapFcnThatUsuallyReturnsTrue() 
      && someOtherCheapFcnThatsUsuallyTrue() )
    {
        // Do stuff here
    }
}
If you're using || in your conditional, then you want to evaluate to true as soon as you can in your conditional, if you have && then you want to evaluate to false as soon as possible. Whatever the logic you're using, you want to do the least amount of work as possible to terminate the conditional check early and/or cheaply. This isn't only the most efficient, but it can make it easier to debug as you're not having to step through as much code when you exit early.

Cache results from an expensive algorithm or method that you might be calling more than once.

The compiler cannot optimize out multiple calls to the same function, unless those functions are purely constant expressions, so it's up to you to do it.

Bad: Here we're calling numCompletedNodes() more than once.
void displayCompletedNodeCount()
{
    // Let's pretend that 'numCompletedNodes()' has a big O of N*log(n) each time it's called.

    // If the player has completed any nodes in the map
    if ( numCompletedNodes() > 0 )
    {
        // We update the UI to display the number of completed nodes
        ui_interface->updateNodeCount( numCompletedNodes() );
    }
}

Better: We cache the number of nodes so we don't have to call the function each time we use it.
void displayCompletedNodeCount()
{
    // Let's pretend that 'numCompletedNodes()' has a big O of N*log(n) each time it's called.

    const unsigned int num_completed_nodes = numCompletedNodes();

    // If the player has completed any nodes in the map
    if ( num_completed_nodes > 0 )
    {
        // We update the UI to display the number of completed nodes
        ui_interface->updateNodeCount( num_completed_nodes );
    }
}

Best: If you really want to be more efficient, in this example it would probably be best to make sure that the numCompletedNodes() function is cheaper to call. Just from the example we can probably assume that it has to go through the map of nodes and calculate for each one if the player has completed it. That is probably information that could be stored on each node, which would bring the big O down to O(n). And you could go further and have a variable that keeps track of the completed node count as it changes, and then it would bring the cost down to O(1) (constant).

Tuesday, March 26, 2013

Djikstra's Algorithm: Finding the Shortest Path

For those that are unfamiliar, Djikstra's Algorithm is used to find the guaranteed shortest path between two points in a graph (as opposed to A* which will take an educated guess, but is more efficient). For the second time in as many years I've found myself needing a shortest path algorithm in a game project. The last one that I wrote was before my blog existed and I never published it, and since then I've lost access to the code base it was published in. This time I'm a bit more pressed for time and didn't want to write my own from scratch so I decided to grab one from online and fix it up a bit.


It has several issues with it that I needed to solve:
  • The algorithm itself and several methods related to it were not encapsulated, which is messy. At the least they should be in their own file.
  • Any nodes and edges that were created had to be deleted by the user otherwise the program would leak memory.
  • The code assumes all edges were bi-directional, I need to handle the possibility of a directional edge.
  • Querying the node graph would remove the nodes from the graph, making it so that if you wanted to query again you had to recreate the graph.
  • The code didn't handle the case in which there wasn't a possible path between two nodes
  • There were several inefficiencies.
The changes I made were:
  • Encapsulated the algorithm, and the containers for the nodes and edges into a Graph class.
  • The Graph class destructor made sure to clean up the nodes and edges to prevent memory leaks.
  • Made edges directional while still allowing for bi-directional connections.
  • Allowing the Graph class to not remove nodes while calculating the shortest path, so the user can query the graph as often as they'd like without recreating it.
  • Made a user-friendly method to allow the user to get a path between any two nodes.
  • Corrected inefficiencies.
For the code containing all of my changes, (click here). I go over the changes in more detail at the end of this post.

In the example code, here is the graph:

Notice that the edges between A and B, and E and F, are uni-directional, you cannot move from B to A, or F to E. In a shortest path algorithm, each node connection (edge) has a weight rather than a distance, this is because we're really not looking for the path of least distance, but the path that can be traversed the fastest, so we may want to factor in things along the path that may slow down the traveller. For example, the connection between B and E, and B and D are of different distances, but both have a weight of 4. Maybe the path between B and E is a smooth concrete path, but from B to D is a swampy path that is hard to traverse.

Given the weights that we've given to each edge, denoted by the numbers near each edge, the shortest path from A to F is: A->B->E->F. Because some edges are uni-directional, the shortest path from F to A is: F->D->B->E->C->A (in fact, that's the only path from F to A). We can also start on nodes besides A and F. For example, the shortest path from C to D is: C->E->F->D.

Below here you'll find the code, organized into main.cpp and three classes. Graph is the class that contains the algorithm and stores Nodes and Edges. And a class for Node, and a class for Edge.

First let's look at main.cpp so we can see how the graph is created and utilized:

#include <iostream>

#include "graph.h"
#include "node.h"

// NOTE: Credit for the original code for this implementation of 
// Djikstra's Algorithm goes to:
// http://www.reviewmylife.co.uk/blog/2008/07/15/dijkstras-algorithm-code-in-c/
// I've modified it to suit my needs.

void outputPath(std::vector<node> path)
{
    for ( long i = (path.size() - 1); i >= 0; --i )
    {
        std::cout << path[i]->getID() << " ";
    }
    
    std::cout << std::endl;
}

int main(int argc, const char * argv[])
{
    Graph graph;
    
    // Create and name our nodes
    Node* a = graph.createNode('a');
    Node* b = graph.createNode('b');
    Node* c = graph.createNode('c');
    Node* d = graph.createNode('d');
    Node* e = graph.createNode('e');
    Node* f = graph.createNode('f');
    
    // Create some edges
    graph.createEdge(a, b, 5);
    graph.createEdge(a, c, 4);
    graph.createEdge(b, d, 4);
    graph.createEdge(b, e, 4);
    graph.createEdge(c, e, 6);
    graph.createEdge(d, f, 3);
    graph.createEdge(e, f, 2);
    
    // Create some edges so we can get from 'f' to 'a'
    graph.createEdge(f, d, 3);
    graph.createEdge(d, b, 4);
    graph.createEdge(e, c, 6);
    graph.createEdge(c, a, 4);
 
    std::vector<node> path;

    // Go from 'c' to 'f'
    graph.getPathFromStartNodeToFinishNode(c, f, path);
    outputPath(path);
    
    // Go from 'a' to 'f'
    graph.getPathFromStartNodeToFinishNode(a, f, path);
    outputPath(path);
    
    // Go from 'f' to 'a'
    graph.getPathFromStartNodeToFinishNode(f, a, path);
    outputPath(path);
    
    return 0;
}

And below you'll find the three class .h/.cpp files.

graph.h
#pragma once

#include <vector>

class Node;
class Edge;

class Graph
{
public:
    Graph() {}
    ~Graph();
    
    Node* createNode(char id);
    Edge* createEdge(Node* node1, Node* node2, const int weight);
    void removeEdge(Edge* edge);
    
    void getPathFromStartNodeToFinishNode(Node* node1, Node* node2, std::vector<node>& path);

private:
    void processGraph();
    Node* extractSmallest(std::vector<node>& nodes);
    
private:
    std::vector<node> m_nodes;
    std::vector<edge> m_edges;
};

graph.cpp
#include "graph.h"
#include "node.h"
#include "edge.h"

#include <iostream>

Graph::~Graph()
{
    for ( int i = 0; i < m_nodes.size(); ++i )
    {
        delete m_nodes[i];
    }
    
    for ( int i = 0; i < m_edges.size(); ++i )
    {
        delete m_edges[i];
    }
}

Node* Graph::createNode(char id)
{
    Node* new_node = new Node(id);
    
    m_nodes.push_back(new_node);
    
    return new_node;
}

Edge* Graph::createEdge(Node* node1, Node* node2, const int weight)
{
    // Yes, we intentionally reverse the node order here
    Edge* new_edge = new Edge(node2, node1, weight);
    
    m_edges.push_back(new_edge);
    
    return new_edge;
}

void Graph::removeEdge(Edge* edge)
{
    std::vector<edge>::iterator it = m_edges.begin();
    for ( ; it < m_edges.end(); ++it)
    {
        if (*it == edge)
        {
     m_edges.erase(it);
     return;
        }
    }
}

void Graph::getPathFromStartNodeToFinishNode(Node* start, Node* finish, std::vector<node>& path)
{
    path.clear();
    
    // Clear any existing node information
    for ( int i = 0; i < m_nodes.size(); ++i )
    {
        m_nodes[i]->setDistanceFromStart(INT_MAX);
        m_nodes[i]->setPreviousNode(NULL);
    }
    
    start->setDistanceFromStart(0);
    
    processGraph();
    
    Node* previous = finish;

    while (previous)
    {
        path.push_back(previous);
        
        previous = previous->getPreviousNode();
    }
    
    if ( path.size() == 1 )
    {
        path.clear();
    }
}

void Graph::processGraph()
{
    // Create a copy of the nodes.
    std::vector<node> nodes = m_nodes;
    
    while (nodes.size() > 0)
    {
 Node* smallest = extractSmallest(nodes);
        
        std::vector<node> out_nodes;
        smallest->adjacentRemainingNodes(nodes, m_edges, out_nodes);
        
 const size_t size = out_nodes.size();
 for (int i = 0; i < size; ++i)
 {
     Node* adjacent = out_nodes[i];
            
     const int distance_to_node = adjacent->distanceToNode(m_edges, smallest);
            if ( distance_to_node == -1 )
            {
                continue;
            }
            
            const int total_distance = distance_to_node + smallest->getDistanceFromStart();   
     if ( total_distance < adjacent->getDistanceFromStart() )
     {
         adjacent->setDistanceFromStart(total_distance);
         adjacent->setPreviousNode(smallest);
     }
 }
    }
}

Node* Graph::extractSmallest(std::vector&ltnode&gt& nodes)
{
    if ( nodes.empty() )
    {
        return NULL;
    }
    
    int smallest_position = 0;
    
    Node* smallest = nodes[0];
    for ( int i = 1; i < nodes.size(); ++i )
    {
        Node* current = nodes[i];
        
        if ( current->getDistanceFromStart() < smallest->getDistanceFromStart() )
        {
            smallest = current;
            smallest_position = i;
        }
    }
    
    nodes.erase(nodes.begin() + smallest_position);
    
    return smallest;
}

node.h
#pragma once

#include <vector>

class Edge;

class Node
{
public:
    Node(char id) :
        id(id)
        , m_previous(NULL)
        , m_distance_from_start(INT_MAX)
    {
    }
    
    char getID() const                          { return id; }
    
    void setPreviousNode(Node* node)            { m_previous = node; }
    Node* getPreviousNode() const               { return m_previous; }
    
    void setDistanceFromStart(int distance)     { m_distance_from_start = distance; }
    int getDistanceFromStart() const            { return m_distance_from_start; }
    
    Node* extractSmallest(std::vector<node>& nodes);
    
    void adjacentRemainingNodes(const std::vector<node>& nodes, const std::vector<edge>& edges,
                                std::vector<node>& out_nodes) const;
    
    int distanceToNode(const std::vector<edge>& edges, const Node* other_node) const;
    
    bool contains(const std::vector<node>& nodes, const Node* node) const;
    
private:
    char    id;
    Node*   m_previous;
    int     m_distance_from_start;
};



node.cpp
#include "node.h"
#include "edge.h"

void Node::adjacentRemainingNodes(const std::vector<node>& nodes, const std::vector<edge>& edges,
                                  std::vector<node>& out_nodes) const
{
    const size_t size = edges.size();
    
    for(int i = 0; i < size; ++i)
    {
        const Edge* edge = edges[i];
        Node* adjacent = NULL;
        if (edge->first() == this)
        {
            adjacent = edge->second();
        }
        else if (edge->second() == this)
        {
            adjacent = edge->first();
        }
        
        if (adjacent && contains(nodes, adjacent))
        {
            out_nodes.push_back(adjacent);
        }
    }
}

int Node::distanceToNode(const std::vector<edge>& edges, const Node* other_node) const
{
    const size_t size = edges.size();
    for(int i = 0; i < size; ++i)
    {
        const Edge* edge = edges[i];
        if ( edge->connects(this, other_node) )
        {
            return edge->getDistance();
        }
    }

    // Happens if two nodes do not connect or the connection is directed in the opposite direction
    return -1;
}

bool Node::contains(const std::vector<node>& nodes, const Node* node) const
{
    for(int i = 0; i < nodes.size(); ++i)
    {
        if (node == nodes[i])
        {
            return true;
        }
    }
    
    return false;
}

edge.h
#pragma once

#include <vector>

class Node;

class Edge
{
public:
    Edge(Node* node1, Node* node2, int distance) :
        m_node1(node1)
      , m_node2(node2)
      , m_distance(distance)
    {
        // Assert that node1 and node2 aren't null and that distance is a positive value
    }
    
    Node* first() const           { return m_node1; }
    Node* second() const          { return m_node2; }
    int getDistance() const       { return m_distance; }
    
    bool connects(const Node* node1, const Node* node2) const;
    
private:
    Node* m_node1;
    Node* m_node2;
    int m_distance;
};

edge.cpp
#include "edge.h"
#include "node.h"

bool Edge::connects(const Node* node1, const Node* node2) const
{
    return ( (node1 == m_node1) && (node2 == m_node2) );
}

The Changes

The Graph class destructor made sure to clean up the nodes and edges to prevent memory leaks.
The original required you to keep track of all of your own pointers to nodes and edges and delete them.

Graph::~Graph()
{
    for ( int i = 0; i < m_nodes.size(); ++i )
    {
        delete m_nodes[i];
    }
    
    for ( int i = 0; i < m_edges.size(); ++i )
    {
        delete m_edges[i];
    }
}

Made edges directional while still allowing for bi-directional connections.
The original 'connects' method checked if the edge went in either direction, which made it so you could not have uni-directional edges.

#include "edge.h"
#include "node.h"

bool Edge::connects(const Node* node1, const Node* node2) const
{
    return ( (node1 == m_node1) && (node2 == m_node2) );
}

And here we handle the case where we don't get a valid distance from a distance check, which can happen if there is no possible path.
void Graph::processGraph()
{
    // Create a copy of the nodes.
    std::vector<Node*> nodes = m_nodes;
    
    while (nodes.size() > 0)
    {
        Node* smallest = extractSmallest(nodes);
        
        std::vector out_nodes;
        smallest->adjacentRemainingNodes(nodes, m_edges, out_nodes);
        
        const size_t size = out_nodes.size();
        for (int i = 0; i < size; ++i)
        {
     Node* adjacent = out_nodes[i];
            
            // We get the distance, if it's -1 then we know there is no possible path between the two nodes
     const int distance_to_node = adjacent->distanceToNode(m_edges, smallest);
            if ( distance_to_node == -1 )
            {
                continue;
            }
            
            const int total_distance = distance_to_node + smallest->getDistanceFromStart();   
     if ( total_distance < adjacent->getDistanceFromStart() )
     {
         adjacent->setDistanceFromStart(total_distance);
         adjacent->setPreviousNode(smallest);
     }
        }
    }
}

Allowing the Graph class to not remove nodes while calculating the shortest path, so the user can query the graph as often as they'd like without recreating it.
The key change from the original is that we make a copy of the list of nodes. The copies list is then whittled away by this function and then goes out of scope, the original would alter the 'm_nodes' list, thus requiring the user to recreate it after each time they queried for a new path.

void Graph::processGraph()
{
    // Create a copy of the nodes.
    std::vector<node> nodes = m_nodes;
    
    // ... Rest of function ...
}

Made a user-friendly method to allow the user to get a path between any two nodes.
The original required you to recreate the entire graph, and then manually set the distance on the start node to 0, and then run the algorithm. I don't have the graph nodes deleted after they process so here I clear out changes the nodes might be storing, then set the distance on the start node, then iterate through the nodes and save out the path. If the path only ends up with the last node in it then we know that there is no path to the start node.

void Graph::getPathFromStartNodeToFinishNode(Node* start, Node* finish, std::vector<node>& path)
{
    path.clear();
    
    // Clear any existing node information
    for ( int i = 0; i < m_nodes.size(); ++i )
    {
        m_nodes[i]->setDistanceFromStart(INT_MAX);
        m_nodes[i]->setPreviousNode(NULL);
    }
    
    start->setDistanceFromStart(0);
    
    processGraph();
    
    Node* previous = finish;

    while (previous)
    {
        path.push_back(previous);
        
        previous = previous->getPreviousNode();
    }
    
    if ( path.size() == 1 )
    {
        path.clear();
    }
}

Corrected inefficiencies
There were several places in the original code sample that included the use of std::vector's 'at' method. This method is around 5-10x slower than using the [] operator. 'at' performs a range check, which is unnecessary if your code is setup to prevent indices from going out of range. Additionally there were places where entire containers were being created on the heap (using 'new'), and then deleted within the same scope. These have been changed to use the stack instead, which is generally much more efficient.

As an example, here was the original method:
void Dijkstras()
{
    while (nodes.size() > 0)
    {
        Node* smallest = ExtractSmallest(nodes);
        vector<node>* adjacentNodes = 
            AdjacentRemainingNodes(smallest);

        const int size = adjacentNodes->size();
        for (int i=0; i<size; ++i)
        {
            Node* adjacent = adjacentNodes->at(i);
            int distance = Distance(smallest, adjacent) + smallest->distanceFromStart;
   
            if (distance < adjacent->distanceFromStart)
            {
                adjacent->distanceFromStart = distance;
                adjacent->previous = smallest;
            }    
        }
        delete adjacentNodes;
    }
}

And here you can see that we're creating a vector on the stack and passing it by reference to the 'adjacentRemainingNodes' method. Because it's not being created on the heap we also don't have to worry about deleting a pointer that was created by another method:

void Graph::processGraph()
{
    // Create a copy of the nodes.
    std::vector<Node*> nodes = m_nodes;
    
    while (nodes.size() > 0)
    {
        Node* smallest = extractSmallest(nodes);
        
        std::vector<node> out_nodes;
        smallest->adjacentRemainingNodes(nodes, m_edges, out_nodes);
        
        const size_t size = out_nodes.size();
        for (int i = 0; i < size; ++i)
        {
            Node* adjacent = out_nodes[i];
            
            const int distance_to_node = adjacent->distanceToNode(m_edges, smallest);
            if ( distance_to_node == -1 )
            {
                continue;
            }
            
            const int total_distance = distance_to_node + smallest->getDistanceFromStart();   
            if ( total_distance < adjacent->getDistanceFromStart() )
            {
                adjacent->setDistanceFromStart(total_distance);
                adjacent->setPreviousNode(smallest);
            }
        }
    }
}

Thursday, March 7, 2013

Why always-on DRM is bad, and companies using it should feel bad

Always-on DRM means that a video game requires a constant Internet connection to be able to play. The reason companies use it is that it is currently the best means of preventing piracy. By placing a large amount of the game's logic on the servers, they can insure that pirates cannot simply crack the game overnight, anyone that wanted to crack the game would have to write their own version of the server's logic and build it into the client, and on top of that they would have to strip the existing client of all of the calls to the server for that logic.

Ok, but why is this bad?

- The company can patch your single player game at any time. You just wanted to sit down and play for 15-20 minutes? Sorry, you'll need to download this patch real quick. And "real quick" completely depends on how bogged-down their patching servers are. Besides that fact, they're patching your single player game, let that sink in for a minute. What if you already liked the game exactly how it is? Too bad, you have to accept the patch to play. Anytime there are patches there is also a potential for new bugs, I've seen games get much worse after a patch.

- You have to have an Internet connection. For some people this truly isn't an issue, but others may not have a decent Internet connection, or they may have bandwidth caps. What if you want to play the game while on a trip, or in the airplane? Tough cookies.

- You cannot play when their servers are down. Even if your Internet connection is reliable, the EA servers aren't, and when they're not working properly you may not be able to play at all. You will have a game that you paid for, and you cannot play a single-player experience because of some server issues 800 miles away. Or maybe you can play, but your city that is saved on their servers becomes corrupted, and you have to roll it back. Someday the servers will be shut down, and when that day comes you'll no longer be able to play Sim City 5, you'll never get to play it again. You can go back and play Sim City 1 still, but not 5. Some people play old games and aren't comfortable with their $60 being for a temporary experience with a game.

- You cannot play if your account gets banned. EA is notorious for banning players accounts due to stuff like a forum post that they don't approve of. I understand EA potentially banning them from the forum, but they also ban the player's Origin account entirely, which blocks them from playing any games on Origin. I'm not sure how this is even legal, but I'm betting EA covered their asses in the EULA for stuff like this.

- Servers cannot handle the same computational load per player. There's a lot of logic going on at any given time in a city, and the more logic the server wants to handle the more CPU power EA's servers are going to need. EA will only have a finite amount of CPU power to give to each player, so they'll either need more servers, which will cost them money, or they'll need to scale down how much CPU power the game needs. Why is this bad for you? This is the reason why SimCity 5 does not allow large cities, the computations that the server would have to do would become exponentially larger as the cities grew, and so rather than cut down on the simulation level to allow for a larger city, they chose to cut down on the city size itself. Had the game been running on your CPU instead of theirs this likely would not have been as much of an issue.

- Finite content due to server space. Because the plots of land are server-side, they take up space on the servers. If they allowed every player to deform their own terrain, they would have unique plots of land on a per-player basis, which would take up more space and could potentially cause issues with the simulation on their end. So because the game runs on their servers, you cannot have deformable terrain. SimCity 2000, which ran on DOS 5.x before Windows 95 was even released, almost 20 years ago, will allow you to deform terrain, but SimCity 5, which runs on a computer in your home that is several hundred times more powerful, does not.

- You cannot save your city locally. Some people like to save their city, make a copy and then burn it to the ground, or watch Godzilla tear it down. Then they have a copy where they can reload the city and all is well again, and good fun was had. But you cannot save versions of your cities on the cloud in the new SimCity. Also, the servers are having all kinds of issues with losing progress on people's cities, if those people could have save copies locally this wouldn't really be an issue.

I understand why companies use DRM, but there is a point when they take it too far and it negatively affects the experience for the customer. In this case EA screwed up large parts of a great game franchise in order to save a few bucks. I'm curious how much it will even save them in the long run as they have to maintain and run servers 24/7 during the life of the game.

Here's how I would've implemented the DRM. Single-player mode would be entirely offline, so you have no required connection to play the game. Single-player cities can never communicate with cities your friends have made, if you want that you have to play online. If you play online you get the exact experience that EA created with the current game, all the connectivity with your friends' cities, or the cities of complete strangers. This means the customer gets the best of both worlds. We're talking about a better game experience, and the publisher has denied the customer this experience because they want to make an extra buck.


My experience with SimCity 5 so far:
  • Didn't purchase the game on day 1 because I knew it would be a disaster. And it was indeed a disaster, most people couldn't play.
  • Purchased the game on day 2. Installed the game. After the game installed it ran an update and said the game was ready.
  • I run the game's launcher and it then downloads another update. After this update completes it says to relaunch the game.
  • I click the relaunch button and the launcher crashes. I run the game's launcher again, and it downloads another update. Again it says to relaunch the launcher.
  • I once again click the relaunch button, and the launcher crashes again.
  • I run the launcher again and the game finally says that I can play my single-player game that I have a physical copy of.
  • All of the servers for North America are full, so I connect to a European server.
  • I wanted to join with a friend's private region, however SimCity cannot see any of my friends from Origin, and they couldn't see me either, so I cannot play the game with my friends.
  • I decided to try making my own region. When attempting this I got an error saying I could not create my own region right now. Tried this every 5 minutes for the next two hours.
  • Finally it let me create a region, and now I have to pick a plot of land within the region to create a city. I chose my plot of land and now I get an error saying I cannot create a city right now, and that I should try again later. Tried this every 5 minutes for another two hours.
  • I finally gave up after a total of 6 hours with the game. I spent those 6 hours entirely within the game's menus.
  • I wake up the next morning and try again. This time when the game starts up it takes me straight to a tutorial mode, I cannot skip this tutorial.
  • The tutorial loads up a small city, and then the city sits there doing nothing, I cannot click on any of the UI for the city and I have no prompts telling me what I should do. Hitting escape does nothing to get me out of the tutorial. I do have some UI at the top of my screen that I can use to go back to the main menu or to quit. I go back to the main menu, which then sends me back into the tutorial, which still does not work. So I quit again.
  • 2 hours later I try again. I start up the game, and this time the tutorial is gone. I check my friend's list, and that is still empty. I try creating my own city again, and I'm back to not being able to create my own region.
In summary, I can't play multi-player, I can't play single player, and it's day 3 since the game was released. I'd consider this to be a failure of a game launch. If I cannot play the game on day 5 I will be demanding a refund from EA. And I'm not likely to purchase any of their games again anytime soon, I cannot support a company that puts a little bit of money above the importance of their customer. There are plenty of companies out there doing just fine without always-online DRM, so until it is problem-free it really shouldn't be used.