Should Dark Souls 2 Have Been Royal Souls The First?

In preparation for the imminent release of Dark Souls 3 I’ve been replaying the first two games and thinking about one of the more common criticism people had about Dark Souls 2: The fact that it somehow just didn’t fully capture the excellent feel of the first game. The combat was fun, the dungeons were interesting but at the end of the day it somehow didn’t have the same impact as the original. Still a great game, definitely worth playing but somehow lacking that special something.

Many people blame this on the fact that the game had a different director than the original. And that probably played a part.

But I think the bigger issue is that Dark Souls 2 was a sequel to a story that didn’t need a sequel. Even the best of directors is going to have trouble given that sort of prompt.

You see, a big part of what made the original so fascinating was the fact that the entire game was one big cohesive mystery revolving around the questions of “Why is civilization dying?” and “What happened to the gods?” The opening cutscene makes it clear that at one points the gods had built a pretty awesome civilization so why is the world you wind up exploring such an awful place?

From that starting point every location you visited, every item you picked up, and every scrap of lore hidden in a weapon description all worked together to weave the history of the ruined world the player was fighting their way through. Do even a half-decent job of finding and piecing together these story fragments and by the time you finished the game you had a solid idea of why the Dark Souls world was the way it was.

Sure, there were dozens of minor questions left unanswered and the ending was a little open-ended but the core mysteries all had satisfying conclusions. The game felt complete all on its own.

Now enter Dark Souls 2.

The developers’ goal was to build the same sort of mystery adventure that fans of the original loved so much. The problem is, you can’t just reuse a mystery. No point in asking “What happened to the gods?” when your veteran players already know the answers.

So Dark Souls 2 needed a brand new mystery to drive the adventure.

But even with a new core mystery Dark Souls 2 was still a Dark Souls game and had to have the same general look, feel and background lore as the first game.

And I think this is what ultimately dragged down Dark Souls 2. In the original every aspect of the game was carefully designed to tell a specific story, but in the sequel the new story was forced to share the stage with countless nostalgic references to the previous game.

The end result is a mildly muddled story that lacked the cohesive punch of the original.

So let’s play “What Could Have Been” and brainstorm what the game might have looked like if it had been a standalone title and allowed to focused 100% on it’s own themes and setting. This shouldn’t be too hard. I mean, Dark Souls 2 had a ton of really cool and original content. We just need to think of a way to move it to center stage.

For starters let’s identify what the core mystery theme of Dark Souls 2 actually was.

Well… half of the Dark Soul 2 main game and all three of the DLC areas focus on exploring the ruins of ancient castles and learning about the royalty that used to live there. You learn bits about how various kings and queens grew to power as well as how their various character flaws lead to their downfall. The “Scholar of the First Sin” edition even has a mysterious NPC who shows up on a regular basis and asks you “What does it mean to be a king?”

So there’s your central mystery. The player is stuck in the middle of a country filled with the ruins of multiple generations of failed kingdoms that leave him asking: Who built all this cool stuff? What went wrong to leave it a ruin?

Since the core theme is the rise and fall of kings, so let’s call our hypothetical standalone version “Royal Souls” and move on to how we might edit the existing game to fit our new mold. Strip out the undead and the old gods and what do we have left to work with?

As a reminder, the actual plot of Dark Souls 2 begins with the character realizing they have the undead curse from the first game and finding themselves drawn to the land of Drangleic. The player then encounters a mysterious lady who points them towards a distant castle and suggests that going there and talking with a certain king might help with the curse. But to get to the castle they need to gather four ancient souls (all with possible connections to the first game).

In our theoretical Royal Souls we could instead begin with the character (who isn’t a cursed undead) still finding themselves irresistibly drawn to the land of Drangleic. The player could then encounter the same mysterious lady who points them towards the same castle and tells them that the reason they have been drawn here is because they are destined to meet the King.

But wait! Without the undead curse how do we make the game work? Reviving when you die is a core part of the experience. And what about the bonfires? What do we use for check points if we don’t have the First Flame and bonfires from the Dark Souls series?

Well… what if we replaced the bonfires with ruined thrones that player can sit on? And when the player dies he returns to the last throne he sat upon?

You see, in Royal Souls the main driver of the plot wouldn’t be souls and humanity and curses and gods but instead the fact that the Royal Soul’s version of Drangleic is a mystic land that desperately needs a new king and naturally attracts people with the spark of royalty. The land itself then keeps them alive until they either go mad from their many deaths or finally achieve the goal of ascending to kinghood.

The four ancient souls the player needs to collect before opening the path to Drangleic Castle? They now become four royal souls, each belonging to a different king who at one time ruled Drangleic before falling into ruin and/or madness (which shouldn’t be a hard change since two of them fit that description anyways).

The player then eventually makes it to Drangleic Castle and discovers that the current king is mad or dying or cursed or maybe missing entirely. There’s some sort of final dramatic boss fight and the game ends with the player, having proven their worth, becoming the new king of Drangleic. The truth is finally revealed: The King they were questing to meet with was themselves all along.

And yet… after spending the entire game learning about the failures of a half dozen previous kings, can the player really say with any confidence that their reign will last? Maybe there is no such thing as a True Royal Soul.

So that basic plot sounds good enough. Gives the player a reason to explore a bunch of dungeons and has a nice but not too crazy twist ending. All that’s left to do now is flesh the world out with details about the various failed kingdoms. And since we’ve dropped all the Dark Souls references we have more than enough room to do so. Instead of “wasting” half of our equipment and most of our spells on references to Dark Souls we can make sure that every item description relates directly to one of the half-dozen kings of Drangleic.

For example, instead of pyromancy spells that constantly reference the old lore of Dark Souls we could have similar but new “Iron Works”, a unique brand of metal and fire themed magic invented during the reign Old Iron King. The player could then learn more about the Old Iron King by collecting Iron Works in the same way that Dark Souls 1 players learned more about the Witch of Izalith by learning pyromancies.

At this point you probably either think I’m crazy or are already half-way through your own redesign of the lore and setting of Dark Souls 2.

Either way it will certainly be interesting to see what happens with Dark Souls 3. They’ve brought back the original director and his recent work on Bloodborne shows he still knows how to tell an amazing standalone story. But can he pull off a sequel? Can he find a way to blend the lore he wrote in the original with the lore he wants to create for this third game?

I certainly hope so.

Let’s Program A Prisoner’s Dilemma Part 6: Eye For An Eye

It’s finally time to write a prisoner with some actual brain power. Let’s start by looking at our current prisoners and their unique issues:

Saints always cooperate, making them an easy target for defectors.

Devils always defect, which leads to bad scores for everyone when multiple devils get together.

Madmen act randomly, which means whether or not they make good choices is up to chance.

What we really want is a prisoner that cooperates when cooperating is a good idea and defects in self defense when it suspects its about to be betrayed. A prisoner that can judge whether or not its partner deserves to be trusted.

Trust No One?

But how will our new “judge” type prisoner decide who to trust? The only thing it will know about its partner is their ID. There’s no way to force the other prisoner to reveal their strategy*, or at least there shouldn’t be. After all, the prisoner’s dilemma is meant to model real life and most real humans aren’t mind readers.

So how will our judge decide how to make decisions if he can’t read minds or see the future? The same way we humans do. We trust people who act trustworthy and we are suspicious of people who betray us.

This leads to a strategy called “tit-for-tat”, where you treat your partner the same way they treat you. If they betray you then the next time you meet them you betray them. If they cooperate then the next time you see them you pay them back by cooperating.

To pull this off we’re going to have to give our judges a memory.

Remember that reportResults function that we programed into the original prisoner class? We’ve been ignoring that so far because none of our prisoners needed it. Saints, devils and madmen always act the same way so they don’t really care how their partners act.

But our judge can use the info from reportResults to build a list of people he can and can’t trust. All we have to do is initialize a blank hash when the judge is created. Then every time reportResults is called we update that hash. We then use this hash in cooperate? by taking the provided partner ID, looking up in our hash whether or not they like to cooperate and then matching their behavior.

But wait! What do we do the first time we meet a new player? We can’t copy their behavior if we’ve never seen how they behave!

The answer is to remember the Golden Rule and “Treat others like you want to be treated”. That means the first time a judge meets a new player he will cooperate. Not only is this good manners, it helps make sure that when judges meet each other they will get locked into a loop of infinite cooperation instead of a loop of infinite betrayal.

Designing The Judicial Branch

The two things that make a judge a judge are:

1) It keeps track of how other prisoners treat it

2) If it meets a prisoner more than once it copies their last behavior

So let’s start with feature 1 by giving our prisoner a memory. To do that we’re going to have to create a hash during prisoner initialization and then use the learnResults function to fill it with useful data.

def initialize(id)
   super(id)
   @strategy = "Judge"
   @opponentBehavior = Hash.new()
end

def learnResults(opponentID, opponentCooperated)
   @opponentBehavior[opponentID] = opponentCooperated
end

Now that our opponentBehavior hash is filling up all that’s left is to use it as part of our decision making process. This is a pretty simple two step process. When our judge is asked whether or not it wants to cooperate with a given prisoner it starts by checking whether or not it has an opponentBehvaior entry for that ID. If it does it just mimics back whatever behavior it recorded for that ID, but if it’s empty it instead defaults to cooperation.

def cooperate?(opponentID)
   if( @opponentBehavior.key?(opponentID))
      return @opponentBehavior[opponentID]
   else
      return true
   end
end

Put it all together and what do you get?

class Judge < Prisoner

   def initialize(id)
      super(id)
      @strategy = "Judge"
      @opponentBehavior = Hash.new()
   end

   def cooperate?(opponentID)
      if( @opponentBehavior.key?(opponentID))
         return @opponentBehavior[opponentID]
      else
         return true
      end
   end

   def learnResults(opponentID, opponentCooperated)
      @opponentBehavior[opponentID] = opponentCooperated
   end
end

Featuring Four Different Character Classes!

Now let’s give our code a test by setting up a group with four judges, four saints, four devils and four madmen.

The Group’s Overall Score was -23546 in 1000 rounds with 16 prisoners

ID: 8 Score: -1126 Strategy: Devil

ID: 7 Score: -1170 Strategy: Devil

ID: 6 Score: -1198 Strategy: Devil

ID: 5 Score: -1254 Strategy: Devil

ID: 14 Score: -1391 Strategy: Judge

ID: 16 Score: -1394 Strategy: Judge

ID: 15 Score: -1398 Strategy: Judge

ID: 13 Score: -1425 Strategy: Judge

ID: 12 Score: -1479 Strategy: MadMan

ID: 9 Score: -1483 Strategy: MadMan

ID: 11 Score: -1498 Strategy: MadMan

ID: 10 Score: -1510 Strategy: MadMan

ID: 3 Score: -1766 Strategy: Saint

ID: 2 Score: -1790 Strategy: Saint

ID: 4 Score: -1802 Strategy: Saint

ID: 1 Score: -1862 Strategy: Saint

In our mixed group you can see that the judges outperform both the madmen and the saints and come pretty close to matching the devils.

But that’s not good enough. We didn’t want to just match the devils, we wanted to beat them! It’s absolutely infuriating that our genuinely intelligent judges are somehow being outscored by a bunch of dumb always-defectors.

Grudge Match

OK, time to get to the bottom of this. Why can’t our judges beat the devils? Let’s get rid of all the saints and madmen and isolate our problem.

The Group’s Overall Score was -17859 in 1000 rounds with 10 prisoners

ID: 10 Score: -1558 Strategy: Judge

ID: 7 Score: -1581 Strategy: Judge

ID: 6 Score: -1581 Strategy: Judge

ID: 9 Score: -1592 Strategy: Judge

ID: 8 Score: -1597 Strategy: Judge

ID: 2 Score: -1990 Strategy: Devil

ID: 3 Score: -1990 Strategy: Devil

ID: 4 Score: -1990 Strategy: Devil

ID: 5 Score: -1990 Strategy: Devil

ID: 1 Score: -1990 Strategy: Devil

Well would you look at that. When all you have are devils and judges the judges scream ahead into first place no problem.

It’s not that our judges weren’t smart enough to out-think the devils in the mixed game, it’s that the devils had a bunch of saints sitting around they could exploit for easy points. As soon as we took away the ever-forgiving all cooperators the devils sank like a rock while the judges pulled off a brilliant group victory by cooperating with each other and paying back the deceitful devils with defection after defection.

Impossible To Trust

Let’s finish up with a few more what-if scenarios, like “What if you stuck a bunch of judges with a bunch of saints?”

The Group’s Overall Score was -10000 in 1000 rounds with 10 prisoners

ID: 1 Score: -1000 Strategy: Saint

ID: 2 Score: -1000 Strategy: Saint

ID: 3 Score: -1000 Strategy: Saint

ID: 4 Score: -1000 Strategy: Saint

ID: 5 Score: -1000 Strategy: Saint

ID: 6 Score: -1000 Strategy: Judge

ID: 7 Score: -1000 Strategy: Judge

ID: 8 Score: -1000 Strategy: Judge

ID: 9 Score: -1000 Strategy: Judge

ID: 10 Score: -1000 Strategy: Judge

A ten way tie with a perfect group score, just like when we had a group of pure saints. As long as you’re nice to the judges they’ll be perfectly nice to you.

What if we get rid of the saints and just have judges?

The Group’s Overall Score was -10000 in 1000 rounds with 10 prisoners

ID: 1 Score: -1000 Strategy: Judge

ID: 2 Score: -1000 Strategy: Judge

ID: 3 Score: -1000 Strategy: Judge

ID: 4 Score: -1000 Strategy: Judge

ID: 5 Score: -1000 Strategy: Judge

ID: 6 Score: -1000 Strategy: Judge

ID: 7 Score: -1000 Strategy: Judge

ID: 8 Score: -1000 Strategy: Judge

ID: 9 Score: -1000 Strategy: Judge

ID: 10 Score: -1000 Strategy: Judge

Yet another perfect score! Since judges start off by cooperating they inevitably end up trusting each other and create just as nice a world as a group of pure saints would.

That’s enough happy thoughts for now. Why not try something more sinister? Like a group full of devils with only one judge?

The Group’s Overall Score was -19991 in 1000 rounds with 10 prisoners

ID: 1 Score: -1998 Strategy: Devil

ID: 2 Score: -1998 Strategy: Devil

ID: 3 Score: -1998 Strategy: Devil

ID: 4 Score: -1998 Strategy: Devil

ID: 5 Score: -1998 Strategy: Devil

ID: 9 Score: -1998 Strategy: Devil

ID: 7 Score: -1998 Strategy: Devil

ID: 8 Score: -1998 Strategy: Devil

ID: 6 Score: -1998 Strategy: Devil

ID: 10 Score: -2009 Strategy: Judge

As we’ve seen time and time again there is literally no way to win in a world full of devils. They will always betray you and so you can never get ahead.

But unlike the saint, which was absolutely destroyed by a gang of devils, the judge manages to at least keep his loses pretty low. As you can see from the numbers the judge cooperated exactly once with every single devil and then never trusted them again, thereby preventing them from leeching more points out of him.

* There is actually a variation of the prisoner dilemma where each prisoner is given a complete copy of their partner’s code as an input. We’re not doing that.

Let’s Program A Prisoners Dilemma 5: What Is A Decision Making Process?

The saint and devil prisoners were easy to write but, as we’ve seen, they’re not actually very good at playing the game. The always cooperating saints are wide open to exploitation and the devils will always defect even when cooperating would score them more points in the long run.

We clearly need a prisoner who can actually make choices instead of just doing the same thing again and again.

1d6 Points of SAN Loss

As any game designer can tell you, the easiest way to simulate decision making is with random numbers. Just give the computer a list of decisions and let it pick one at random.

So on that note I give you: The madman.

class MadMan < Prisoner
   def initialize(id)
      super(id)
      @strategy = "MadMan"
   end

   def cooperate?(opponentID)
      choice = rand(2)
      if( choice == 1)
         return true
      else
         return false
      end
   end
end

Ruby weirdness warning: In a lot of languages “0” is the same as false, so you might be tempted to have cooperate? just return the number generated by rand. Don’t do that. In Ruby only “false” and “null” are considered false. Everything else, including the number 0, are considered true. This is useful because it means the number 0 always acts like just a number. On the other hand it messes up a lot of other useful programming shortcuts so all in all it sort of break even.

Anyways, don’t forget to tell our create Prisoners method that there’s a new type of prisoner object for it to work with.

def createPrisoners(saintCount, devilCount, madmanCount)
   prisoners = Array.new
   playerCounter = 0
   saintCount.times{ prisoners.push(Saint.new(playerCounter += 1)) }
   devilCount.times{ prisoners.push(Devil.new(playerCounter += 1)) }
   madmanCount.times{ prisoners.push(MadMan.new(playerCounter += 1)) }
   return prisoners
end

Inmates Are Running The Asylum

Let’s be honest here: Making decisions at random is almost never a good idea. So how does our new class of insane prisoners perform in an actual game?

prisoners = createPrisoners(4, 4, 4)
playPrisonersDilemma(prisoners, 1000)

Ten doesn’t divide evenly into thirds so this time we’ll have four of each type of prisoner. Please not that this changes the perfect score to -12,000.

The Group’s Overall Score was -17905 in 1000 rounds with 12 prisoners

ID: 6 Score: -876 Strategy: Devil

ID: 7 Score: -882 Strategy: Devil

ID: 8 Score: -892 Strategy: Devil

ID: 5 Score: -906 Strategy: Devil

ID: 12 Score: -1499 Strategy: MadMan

ID: 9 Score: -1504 Strategy: MadMan

ID: 10 Score: -1538 Strategy: MadMan

ID: 11 Score: -1564 Strategy: MadMan

ID: 2 Score: -2026 Strategy: Saint

ID: 1 Score: -2060 Strategy: Saint

ID: 4 Score: -2074 Strategy: Saint

ID: 3 Score: -2084 Strategy: Saint

Madmen randomly flip between acting like saints and acting like devils so it makes sense they would wind up scoring squarely in between the two. They don’t just let the devils betray them; sometimes they betray right back. And they alternate between cooperating with saints for small gains and betraying them for big gains.

So all in all it seems like mild insanity is actually a pretty well rounded fit for the cutthroat world of the prisoner’s dilemma.

Also, as promised, the fact that madmen can make actual decisions means that our overall group score now has some variation to it even when running multiple games with the same group.

The Group’s Overall Score was -17995 in 1000 rounds with 12 prisoners

The Group’s Overall Score was -18046 in 1000 rounds with 12 prisoners

The Group’s Overall Score was -17938 in 1000 rounds with 12 prisoners

The Lost And The Damned

So madmen seem to do pretty well in a mixed group… but maybe that’s just because they had some saints to act as backup. What happens when we pair up only devils and madmen?

The Group’s Overall Score was -17523 in 1000 rounds with 10 prisoners

ID: 4 Score: -1440 Strategy: Devil

ID: 1 Score: -1450 Strategy: Devil

ID: 2 Score: -1456 Strategy: Devil

ID: 3 Score: -1456 Strategy: Devil

ID: 5 Score: -1472 Strategy: Devil

ID: 9 Score: -2013 Strategy: MadMan

ID: 7 Score: -2014 Strategy: MadMan

ID: 8 Score: -2068 Strategy: MadMan

ID: 6 Score: -2072 Strategy: MadMan

ID: 10 Score: -2082 Strategy: MadMan

About the same thing as when there were saints, it turns out. The madmen’s habit of cooperating roughly half the time means they still can’t directly compete with the vicious defecting devils, but at least randomly defecting half of the time allows them to sort of defend themselves.

In fact, if you compare this to the time we evenly paired up devils and saints you’ll see that the madmen scored about the same as the saints. But the big difference is that the madmen did much more damage to the devils in the process.

Although it’s up in the air as to whether this is a good thing or not. Taking the devils down a notch is certainly a satisfying feeling but the madmen still lost and the group score is much much worse then when the saints had their match with the devil.

The More Randomness You Have The Less Random It Is

For our final experiment I just want to point out that while I call them “Madmen” the random decision prisoners actually managed to achieve some pretty reliable results, consistently scoring halfway between a saint and a devil.

This is of course because random numbers tend to average out over time. So “cooperates at random” eventually transforms into “consistently cooperates 50% of the time”.

To show this off I’m going to have a bunch of madmen play increasingly long games against each other.

prisoners = createPrisoners(0, 0, 10)
playPrisonersDilemma(prisoners, 10)
playPrisonersDilemma(prisoners, 100)
playPrisonersDilemma(prisoners, 1000)
playPrisonersDilemma(prisoners, 1000000)

The Group’s Overall Score was -141 in 10 rounds with 10 prisoners

ID: 10 Score: -7 Strategy: MadMan

ID: 1 Score: -10 Strategy: MadMan

ID: 6 Score: -12 Strategy: MadMan

ID: 8 Score: -13 Strategy: MadMan

ID: 9 Score: -14 Strategy: MadMan

ID: 5 Score: -15 Strategy: MadMan

ID: 2 Score: -16 Strategy: MadMan

ID: 3 Score: -17 Strategy: MadMan

ID: 7 Score: -18 Strategy: MadMan

ID: 4 Score: -19 Strategy: MadMan

The Group’s Overall Score was -1493 in 100 rounds with 10 prisoners

ID: 8 Score: -132 Strategy: MadMan

ID: 3 Score: -135 Strategy: MadMan

ID: 6 Score: -139 Strategy: MadMan

ID: 5 Score: -144 Strategy: MadMan

ID: 1 Score: -145 Strategy: MadMan

ID: 9 Score: -154 Strategy: MadMan

ID: 7 Score: -156 Strategy: MadMan

ID: 4 Score: -159 Strategy: MadMan

ID: 2 Score: -163 Strategy: MadMan

ID: 10 Score: -166 Strategy: MadMan

The Group’s Overall Score was -14976 in 1000 rounds with 10 prisoners

ID: 3 Score: -1437 Strategy: MadMan

ID: 8 Score: -1457 Strategy: MadMan

ID: 1 Score: -1482 Strategy: MadMan

ID: 4 Score: -1487 Strategy: MadMan

ID: 7 Score: -1491 Strategy: MadMan

ID: 10 Score: -1492 Strategy: MadMan

ID: 5 Score: -1503 Strategy: MadMan

ID: 6 Score: -1514 Strategy: MadMan

ID: 9 Score: -1537 Strategy: MadMan

ID: 2 Score: -1576 Strategy: MadMan

The Group’s Overall Score was -15001829 in 1000000 rounds with 10 prisoners

ID: 3 Score: -1498113 Strategy: MadMan

ID: 10 Score: -1499339 Strategy: MadMan

ID: 1 Score: -1499525 Strategy: MadMan

ID: 9 Score: -1500065 Strategy: MadMan

ID: 7 Score: -1500128 Strategy: MadMan

ID: 2 Score: -1500445 Strategy: MadMan

ID: 4 Score: -1500662 Strategy: MadMan

ID: 5 Score: -1500894 Strategy: MadMan

ID: 8 Score: -1501085 Strategy: MadMan

ID: 6 Score: -1501573 Strategy: MadMan

As you can see, the longer the game lasts the less difference there is in the scores.

After ten rounds the worst score (-19) was almost three times as bad as the best score (-7).

After one hundred rounds the worst score (-166) was only about 25% worse than the best score (-132).

After a thousand rounds there was only a 10% difference between best (-1437) and worst (-1576).

And after a million rounds there was less than a 1% difference between best and worst.

So in the long run cooperating at random is a viable way to take a middle path cooperation and defection.

We Can Be Smarter Than This

Random decision making lead to some interesting outcomes but it failed to come even close to beating the devils. Plus it’s an embarrassingly simple algorithm for AI enthusiasts like ourselves. Surely we can come up with a smarter prisoner. One that actually thinks instead of guessing.

But that’s going to have to wait for next time.

Let’s Program A Prisoners Dilemma Part 4: Heaven or Hell, Let’s Rock!

Last time we wrote enough code to actually play the prisoner’s dilemma. Combined with the Saint and Devil prisoners we wrote we now have enough code to run some interesting experiments.

Heaven

Let’s start by putting together a group of nothing but saints and seeing what happens to them after a thousand rounds.

#A group of all saints
prisoners = createPrisoners(10, 0)
playPrisonersDilemma(prisoners, 1000)

Which results in this:

The Group’s Overall Score was -10000 in 1000 rounds with 10 prisoners

ID: 1 Score: -1000 Strategy: Saint

ID: 2 Score: -1000 Strategy: Saint

ID: 3 Score: -1000 Strategy: Saint

ID: 4 Score: -1000 Strategy: Saint

ID: 5 Score: -1000 Strategy: Saint

ID: 6 Score: -1000 Strategy: Saint

ID: 7 Score: -1000 Strategy: Saint

ID: 8 Score: -1000 Strategy: Saint

ID: 9 Score: -1000 Strategy: Saint

ID: 10 Score: -1000 Strategy: Saint

Because the saints always cooperate they all ended up with the same score. What is much more interesting is their group score, which is actually perfect.

Why is -10,000 perfect? In every round every pair can do one of three things: if both prisoners cooperate the pair loses only 2 points, if one prisoner cooperates but the other defects the pair loses 3 points and if both defect the pair loses 4 points. Ten prisoners means five pairs. Five pairs multiplied by the best case scenario of -2 points gives us -10 points per round. Multiply that by the 1000 rounds we played the game and you find out that the best possible score for a group of this size playing a game this long is in fact -10,000 points.

So, unsurprisingly, when you have a group made up of nothing but saints you get a really really good outcome.

Hell

Now let’s try the inverse and put 10 devil’s together.

#A group of all devils
prisoners = createPrisoners(0, 10)
playPrisonersDilemma(prisoners, 1000)

Which turns out like this:

The Group’s Overall Score was -20000 in 1000 rounds with 10 prisoners

ID: 1 Score: -2000 Strategy: Devil

ID: 2 Score: -2000 Strategy: Devil

ID: 3 Score: -2000 Strategy: Devil

ID: 4 Score: -2000 Strategy: Devil

ID: 5 Score: -2000 Strategy: Devil

ID: 6 Score: -2000 Strategy: Devil

ID: 7 Score: -2000 Strategy: Devil

ID: 8 Score: -2000 Strategy: Devil

ID: 9 Score: -2000 Strategy: Devil

ID: 10 Score: -2000 Strategy: Devil

The devils also wound up with a ten-way tie, but the real story is in that group score: -20,000 points is the absolute worst score a group this size can manage! (-4 points for a double defection * 5 pairs * 1000 rounds)

You probably saw that coming. Of course a big group of devils is going to make an absolute mess of things. I didn’t just draw that name out of a hat.

Wolf Among Sheep

So if you have a group of all saints they will all tie for first place while a group of all devils will tie for last place. That’s good to know but doesn’t do much good for those of us living in a world full of both good and bad people.

So let’s mix things up!

#Saints with one devil among them
prisoners = createPrisoners(9, 1)
playPrisonersDilemma(prisoners, 1000)

The Group’s Overall Score was -11000 in 1000 rounds with 10 prisoners

ID: 10 Score: 0 Strategy: Devil

ID: 5 Score: -1188 Strategy: Saint

ID: 6 Score: -1206 Strategy: Saint

ID: 4 Score: -1210 Strategy: Saint

ID: 2 Score: -1212 Strategy: Saint

ID: 1 Score: -1216 Strategy: Saint

ID: 8 Score: -1220 Strategy: Saint

ID: 9 Score: -1232 Strategy: Saint

ID: 3 Score: -1236 Strategy: Saint

ID: 7 Score: -1280 Strategy: Saint

This test is actually a little interesting because it’s the first one we’ve run where the score will be different every time you play the game. Run the program a few times in a row and see for yourself: The scores will move move up and down a bit. The devil always gets a perfect score but the individual saint scores will go up and down based on how often they had the bad luck of getting stuck with the all-betraying devil.

The fact that the devil always get a perfect score is also pretty interesting, although it isn’t hard to figure out how he pulled it off. The devil never accepts the -1 penalty for cooperating and the saints never penalize their partner with the -2 points of betrayal, so there is literally no way for the devil to lose.

Also, look at the group score. The devil may have messed up the saints ten way tie but at least the group is still within 10% of being perfect.

Diabolic Duo

It doesn’t seem fair to have a single devil running around taking advantage of everything, so let’s give him a taste of his own medicine by introducing a second devil to the mix.

#A group of mostly saints
prisoners = createPrisoners(8, 2)
playPrisonersDilemma(prisoners, 1000)

The Group’s Overall Score was -12000 in 1000 rounds with 10 prisoners

ID: 10 Score: -232 Strategy: Devil

ID: 9 Score: -232 Strategy: Devil

ID: 8 Score: -1408 Strategy: Saint

ID: 7 Score: -1438 Strategy: Saint

ID: 1 Score: -1444 Strategy: Saint

ID: 6 Score: -1446 Strategy: Saint

ID: 2 Score: -1448 Strategy: Saint

ID: 3 Score: -1448 Strategy: Saint

ID: 4 Score: -1452 Strategy: Saint

ID: 5 Score: -1452 Strategy: Saint

Once again the scores will move up and down a bit depending on how often the devils get paired up and betray each other versus how often they get paired up with a defenseless saint, but the overwhelming trend here is for the devils to come out ahead. After all, each devil has an 8 out of 9 chance of getting paired with an easy to exploit saint.

(It is technically possible for the devils to lose if they get paired up a lot… but the odds of that happening a thousand times in a row is really really low. Try running shorter games if you want to see what happens when lightning strikes and devils always get stuck together).

Other interesting things to note include the fact that the devils will always have the same score (since they only lose points when they get paired together and then they always both lose two points). It’s also worth noting that our group score is now 20% worse than perfect. If this was a neighborhood it would still be a nice place to live even if a few of your neighbors smell like sulfur and brimstone and tend to park their cars in front of other people’s driveways.

Wheat and Tears

Let’s level the playing field and mix together an even number of saints and devils.

#An even mix of saints and devils
prisoners = createPrisoners(5, 5)
playPrisonersDilemma(prisoners, 1000)

The Group’s Overall Score was -15000 in 1000 rounds with 10 prisoners

ID: 6 Score: -900 Strategy: Devil

ID: 9 Score: -900 Strategy: Devil

ID: 7 Score: -910 Strategy: Devil

ID: 10 Score: -924 Strategy: Devil

ID: 8 Score: -934 Strategy: Devil

ID: 4 Score: -2030 Strategy: Saint

ID: 2 Score: -2080 Strategy: Saint

ID: 1 Score: -2086 Strategy: Saint

ID: 5 Score: -2116 Strategy: Saint

ID: 3 Score: -2120 Strategy: Saint

About what you would expect. The devils still manage to absolutely destroy the saints. Even worse, our group score is starting to get pretty bad and even after exploiting the saints mercilessly the devils are only a little better off than they would have been if they had decided to cooperate.

I guess the lesson here is that crime only pays when you have a small number of criminals exploiting a large population of good productive citizens. If you have too many criminals there just isn’t enough loot to go around.

A Light in the Darkness

For our last experiment let’s see what happens when you have a group made up mostly of devils and throw in a single saint. Doesn’t take a genius to predict this is going to be pretty bad…

#A single saint in a group of devils
prisoners = createPrisoners(1, 9)
playPrisonersDilemma(prisoners, 1000)

The Group’s Overall Score was -19000 in 1000 rounds with 10 prisoners

ID: 6 Score: -1756 Strategy: Devil

ID: 3 Score: -1766 Strategy: Devil

ID: 10 Score: -1766 Strategy: Devil

ID: 7 Score: -1774 Strategy: Devil

ID: 8 Score: -1774 Strategy: Devil

ID: 4 Score: -1780 Strategy: Devil

ID: 9 Score: -1792 Strategy: Devil

ID: 5 Score: -1794 Strategy: Devil

ID: 2 Score: -1798 Strategy: Devil

ID: 1 Score: -3000 Strategy: Saint

Ouch. Our poor saint wound up with the absolute worst possible individual score this time around. In every single round the saint hit himself with a -1 penalty for cooperating and then got with a -2 penalty from his defecting partner. Doesn’t get any worse. How perfectly terrible.

The group score is also pretty horrible and the devils are now significantly worse off than if they had just cooperated. (Fun experiment, mess with the ratio of saints to devils and find the exact point at which being a devil no longer pays).

One Last Observation

Let’s finish this post off with one last interesting trend you may have noticed: Running an experiment multiple times in a row caused individual scores to bounce around a lot but the group score never changed by even a single point. Why is that?

I’ll give you a few moments to think about it.

The answer is simple enough. Saints always cooperate, which causes the group to lose one point per round. Devils always defect, which causes the group to lose two points per round. Doesn’t matter how you mix up the pairs; the group as a whole will always lose one point per saint and two points per devil.

To get a group score that occasionally changes we would have to have a prisoner that sometimes cooperates (losing the group only one point) and sometimes defects (losing the group two points). Then the group score could go up or down based on how that individual prisoner decided to play.

Which is exactly what we’re going to be doing next time!

Let’s Program A Prisoner’s Dilemma Part 3: Fight In Cell Block D

Last time we programmed a couple prisoners and ran them through their paces. This time we’re going to write the code for the actual prisoner’s dilemma game.

We start by pseudo-coding our algorithm:

  • First: generate a bunch of prisoners objects.
  • Second: decide how many rounds the game should last.
  • During each round:
    •    Randomly pair up prisoners.
    •    Each prisoner is asked whether or not they want to cooperate with their partner
    •    Prisoners lose points based on the decision they and their partner made
    •    Move on to the next round by randomly sorting prisoners into new pairs
  • When all the rounds are done show the stats of every prisoners so we know who won (by losing the least points).
  • Also show the total sum of all the prisoners’ scores so we can compare different mixes of prisoners.

Breaking News: Prisons Filling Up At An Alarming Rate

Let’s start by figuring out how to set up a group of prisoners. We could just hard code an array of prisoner objects but that would leave us in the bad situation of having to rewrite actual code every time we wanted to try a new mix of prisoners. That sounds both boring and error prone so instead let’s come up with a function we can use to create a mix of prisoners on the fly.

def createPrisoners(saintCount, devilCount)
   prisoners = Array.new
   playerCounter = 0
   saintCount.times{ prisoners.push(Saint.new(playerCounter += 1)) }
   devilCount.times{ prisoners.push(Devil.new(playerCounter += 1)) }
   return prisoners
end

We just tell this function how many saints and devils we want and it gives us exactly what we asked for packaged inside a convenient array. It also keeps count of how many prisoners it has created so far and uses that count to make sure every prisoner has a unique ID.

This function also shows off a rather handy Ruby shortcut for writing a classic “for loop”. If you want a loop that runs a certain number of times just take that number, or a variable holding that number, and add .times{ your code here } to the end. Ex: 4.times{ puts “This will print four times” }

Exciting Next-gen Gameplay!

To actually play the prisoners dilemma we need a group of prisoners and an idea of how many rounds we want the game to last, which means our function definition probably needs to look a little like this:

def playPrisonersDilemma(prisoners, rounds)

Then inside the function itself we want to set up a loop that runs once for every round in the game. During those rounds we want to randomly shuffle our prisoners, divide them up into pairs and then ask every prisoner whether they want to cooperate with their temporary partner or not.

At that point we subtract one point from any player who chose to cooperate and subtract two points from any player who was betrayed by their partner (Yes, a player can be hit by both penalties in the same round).

rounds.times{
   pairs = prisoners.shuffle.each_slice(2)
   pairs.each{ |pair|
      firstPlayerCooperates = pair[0].cooperate?(pair[1].id)
      secondPlayerCooperates = pair[1].cooperate?(pair[0].id)

      if(firstPlayerCooperates)
         pair[0].score -= 1
      else
         pair[1].score -= 2
      end

      if(secondPlayerCooperates)
         pair[1].score -= 1
      else
         pair[0].score -= 2
      end

      pair[0].learnResults(pair[1].id, secondPlayerCooperates)
      pair[1].learnResults(pair[0].id, firstPlayerCooperates)
   }
}

Once again we’re using handy Ruby shortcuts to save a bit of typing. We use rounds.times to set up a loop that will play the game the proper number of times. We then use prionser.shuffle to randomly mix up the prisoners and then chain it to each_slice(2) to divide the random mix into random pairs.

Important note: shuffle and slice don’t change the original array. They instead return a transformed copy that has to be assigned to a variable before you can use it. But for us this is actually a good thing because it means we can just shuffle and slice the same prisoners array at the start of each loop without having to worry about it somehow getting messed up between iterations.

Once we’ve got our random pair array we can use pairs.each to write some quick code we want to run once for each piece of data in our array. The each loop starts out by grabbing an item from the array and storing it inside whatever variable we’ve named with the | variable | syntax.

In our case the pairs array is full of tiny two item arrays, so we call our variable pair. From there it’s pretty simple to each half ot he pair whether or not it wants to cooperate with the other half and then we can assign points. Remember our scoring rules: A player who cooperates loses one point. A player who refuses to cooperate forces the other player to lose two points. We also call the learnResults function to let each prisoner know how the round played out.

After the rounds.times loop has finished playing the game all that’s left is to print out the final score.

#Show the stats for the group as a whole as well as for each individual prisoner
groupScore = 0
prisoners.each{ |prisoner| groupScore += prisoner.score }
puts "The Group's Overall Score was #{groupScore} in #{rounds} rounds with #{prisoners.length} prisoners"
prisoners.sort{ |x, y| y.score <=> x.score}.each{ |prisoner| prisoner.report }

Nothing complicated here. We use an each loop to tally up the scores from every player so we can print a group score. Then we sort the prisoners by score and use another each loop to print out the individual stats for every prisoner.

All Together Now

Gluing together all the bits of code we just wrote leaves us with this nice function:

def playPrisonersDilemma(prisoners, rounds)
   if !prisoners.length.even?
      throw "Prisoners Dilemma requires an even number of participants"
   end

   # Make sure each prisoner starts out with a clean slate
   prisoners.each{ |prisoner| prisoner.score = 0}
   
   rounds.times{
      pairs = prisoners.shuffle.each_slice(2)
      pairs.each{ |pair|
         firstPlayerCooperates = pair[0].cooperate?(pair[1].id)
         secondPlayerCooperates = pair[1].cooperate?(pair[0].id)

         if(firstPlayerCooperates)
            pair[0].score -= 1
         else
            pair[1].score -= 2
         end

         if(secondPlayerCooperates)
            pair[1].score -= 1
         else
            pair[0].score -= 2
         end
      }
   }

   #Show the stats for the group as a whole as well as for each individual prisoner
   groupScore = 0
   prisoners.each{ |prisoner| groupScore += prisoner.score }
   puts "The Group's Overall Score was #{groupScore} in #{rounds} rounds with #{prisoners.length} prisoners"
   prisoners.sort{ |x, y| y.score <=> x.score}.each{ |prisoner| prisoner.report }
end

And now we can test it by having a group of prisoners play the game. Let’s try it out with a group of two saints and two devils playing for a thousand rounds:

prisoners = createPrisoners(2, 2)
playPrisonersDilemma(prisoners, 1000)

This should give you output kind of like this:

The Group’s Overall Score was -6000 in 1000 rounds with 4 prisoners

ID: 4 Score: -650 Strategy: Devil

ID: 3 Score: -650 Strategy: Devil

ID: 2 Score: -2350 Strategy: Saint

ID: 1 Score: -2350 Strategy: Saint

Hmm… looks like the Devils had no trouble at all completely crushing the Saints. We’ll look at that match up in more detail next time.

Is Your Server A Survivor?

Heavy ice storms have knocked out power throughout most of Oklahoma and left me thinking about emergency preparedness. Not for humans, mind you, but for machines. After all, in the web business uptime is everything and a customer living in California isn’t going to care or even realize that the reason your online store is down is because of heavy flooding in a data center halfway across the country.

So… Quiz Time! How many natural disasters could your server survive before your customers could no longer access your website and/or services?

Most professional hosting services have backup generators and battery arrays that keep things humming along without so much as a hiccup during even the worst of blackouts. And in 99% of the case that’s all you need.

But what about the other 1% of the time?

For instance, generators need fuel. What happens if that fuel runs out and deep snows or heavy flooding prevents you from bringing in more fuel to start them back up? How many hours or days could you server go without supplies before finally falling silent? Do you even know? (I don’t. Such shame.)

And what about your server’s physical safety? A backup generator won’t do you any good if an earthquake drops a steel girder right on top of your hardware or if a tornado blows your whole data center off to Oz.

Now obviously there comes a point where it’s no longer worth spending a bunch of money just to make your server 0.0001% more robust. There’s nothing wrong with shrugging your shoulders and saying, “If a meteor crashes into my server I’ll build a new one from backup and my customers will just have to deal with the downtime. If anybody complains I’ll send them a picture of the crater.”

And that’s perfectly fine. Nobody has ever died because they had to wait an extra day to order a funny t-shirt they found online.

Of course… people HAVE died because they had to wait an extra day for military communication. And a thirty second outage of financial data can make all sorts of people wish they were dead. So there are some industries where downtime is never OK.

Which means there are people working in those industries who get paid to sit around talking about how they plan to recover from simultaneous lightning strikes in five different states during a zombie apocalypse.

I’m… feeling a little career envy here.

Let’s Program A Prisoner’s Dilemma Part 2: Crime And Punishment

Last time we learned about a game theory thought experiment called the “Iterated Group Prisoner’s Dilemma”. Basically players get randomly paired up and have to decide to either cooperate by accepting a small penalty for themselves or defect by shoving a big penalty onto the other player. The players then get shuffled around and repeat the game with a new partner. After a few hundred rounds (or more) the game comes to a stop and the winner is whoever has lost the fewest points.

This time we’re going to start programing a simulator for that game so we can watch how different strategies play out.

I will be writing this project in Ruby. It’s not really “better” for this program than any of the various other high level languages we could choose from, but I’ve been using it a lot as part of my game programming hobby and figured I might as well stick with it while it was in my head.

So if you want to follow along with my exact code open up a file named prisonerdilemma.rb in your favorite text editor and get ready for some Ruby. Of course, rewriting the project in your favorite language is always an option.

Introducing Prisoner 24601

The most important part of the prisoner’s dilemma is the prisoners. So let’s make a list of what our little digital prisoners need to be able to do in order for this simulation to work.

1) They need to have a unique ID so that other prisoners (and us humans) can recognize them.

2) They need to be able to decide whether to cooperate or defect with other prisoners based on ID.

3) They need to be able to see whether their opponent decided to cooperate or defect.

4) They need to be able to report what strategy they are using so we can keep track of how different strategies perform.

5) They need to be able to keep track of their score so we can calculate who is winning and losing.

Based on all this I’ve thrown together a very simple prisoner class for us to start working with. It can’t really do much except book keeping though. It has variables to keep track of it’s own ID, score and strategy and even exposes the score to outside code so we can update it during the game. It also has a convenient “report” function that will print out all that information in human friendly format.

As for actual game playing code… it does technically have a “cooperate?” function for deciding how to play the game along with a “learnResults” function for keeping track of its opponents. But at the moment both of these functions are empty because this is just skeleton code; it isn’t meant to actually do anything. In fact, if you try to call the “cooperate?” function it will throw an error!

class Prisoner
   attr_reader :id, :strategy, :score
   attr_writer :score

   def initialize(id)
      @id = id;
      @score = 0;
      @strategy = "Prisoner Parent Class"
   end

   def cooperate?(opponentID)
      throw "Prisoner child class must implement a cooperate? method"
   end

   def learnResults(opponentID,oponentCooperated)
   end

   def report
      puts "ID: #{@id} Score: #{@score} Strategy: #{@strategy}"
   end
end

Still, even with two incomplete functions we can still give this a test run. If you were to add some code like this to your project:

testprisoner = Prisoner.new(6)
testprisoner.score = -10
testprisoner.report

You would get some output like this:

ID: 6 Score: -10 Strategy: Prisoner Parent Class

Ye Not Guilty – The “Saint” Strategy

Now that we’ve got a nice generic class up and running we can move on to writing a prisoner with actual dilemma solving code!

For our first attempt we’re going to be writing the simplest possible of all prisoner algorithms: A player who always always chooses to cooperate. We’ll be calling this the “Saint” strategy.

Now the actual code for this will be virtually identical to the generic prisoner. It will just have a different name inside of the “strategy” variable and its “cooperate” function will return true instead of throwing an error.

But we all know that copy pasting code is horrible and hard to maintain, so we’ll be using the miracle of inheritance to avoid any copy pasting.

class Saint < Prisoner
   def initialize(id)
      super(id)
      @strategy = "Saint"
   end

   def cooperate?(opponentID)
      return true
   end
end

That “Saint < Prisoner” tells the compiler that Saint is a type of Prisoner and should get a free copy of all of its code. Then instead of writing an entire new class from scratch we only have to write the few bits that are different.

First up we write a new initialize function so we can set the strategy variable to “Saint” instead of the default from the Prisoner class. But we still want the Prisoner class to handle things like setting our ID and initializing our score so we start out by calling the generic Prisoner initialize function with a call to “super”.

The super call checks whether the parent has a function with the same name as the current function and then calls it. It’s really useful for when you want a child function to do everything the parent did and then a little extra. Just start with super to get the parent’s behavior and then add in the unique child logic afterwards.

Next we write a new cooperate? function, which for our super trusting saint just involves returning “true”. In this case we want to completely override the parent’s version of the function so we leave out the call to super and just write new code.

Father of Lies

That was pretty easy, so let’s keep going by programming a second type of prisoner that perfectly mirrors our first. This time we’ll create a player that always always chooses to defect. Let’s call this the “Devil” strategy.

class Devil < Prisoner
   def initialize(id)
      super(id)
      @strategy = "Devil"
   end

   def cooperate?(opponentID)
      return false
   end
end

Just like with the “Saint” we use inheritance to grab our basic Prionser code and then just add in our new strategy name and cooperate? Algorithm. Couldn’t be easier.

A Not So Epic Clash Between Good And Evil

And now here’s a little code snippet you can use for testing that both of our new classes work:

testSaint = Saint.new(1)
testSaint.report
testDevil = Devil.new(2)
testDevil.report
puts "Saint Cooperates?"
puts testSaint.cooperate?(testDevil.id)
puts "Devil Cooperates?"
puts testDevil.cooperate?(testSaint.id)

Which results in this output:

ID: 1 Score: 0 Strategy: Saint

ID: 2 Score: 0 Strategy: Devil

Saint Cooperates?

true

Devil Cooperates?

False

Let’s Get Ready To Rumble

Now that we have some players assembled our next task will be setting up an actual game for them to play in. But that will have to wait until the next post.

Could Your Web App Survive A Transplant?

Sometimes you need to move your code off of the computer you wrote it for and onto some other machine. A buddy wants a copy of your random D&D dungeon generator. A co-worker needs a copy of the billing software you’ve been working on. A client wants you to move their inventory software to a new system.

And when you get a job like this it quickly becomes apparent that some programs are easier to move than others. A simple Java application can be tossed onto just about any machine and stand a good chance of automagically working while a complex piece of legacy code might be almost impossible to move due to dependency on obsolete libraries or bizarre hardware requirements*.

Now unfortunately for us web developers, online applications tend to be some of the harder pieces of software to move from one computer to another. In general it’s not enough to just have a copy of the code moved to the new machine; you also need a copy of the right database and server along with the proper database table definitions and server config instructions.

And that’s just the starting point. The older and more complex your web app is the higher the odds are that it has developed dependencies of certain URLs, IP addresses, third party data feeds, server libraries and so on. At that point you’re no longer trying to move an application, you’re trying to relocate an entire digital ecosystem.

But I’m Never Ever Going To Move My Web App To A Different Computer

Now at this point you might be thinking to yourself “But Scott, web apps don’t need to move. You just install them on your server and let other people visit them through the Internet. That’s the whole point.”

And for the most part that’s true. If your web app runs properly on its current server it doesn’t really matter how hard or easy it would be to get a second copy of your web app running on a different server with a different URL.

Until one day you find out your website is so popular that it’s starting to slow down and you need to create a second copy and load balance between the two.

Or maybe someone offers to pay you a bunch of money to build a copy of your web app for their business.

Or maybe you find out you need a bunch of risky new features and you want to create a test server to run your changes on before you update the main server.

Or maybe you finally get enough funding to hire a second developer and now he needs to know how to set up a local copy of your online web app so he can start experimenting without taking down half your live database.

Take your pick. There are plenty of reasons that a complex website that you thought would always run on just one machine with just one URL might suddenly need to be split across multiple computers with various different access methods.

Oh No! How Do I Prevent This From Ruining My Web App?

Fortunately, there are ways to make your online applications easier to move and copy.

Probably the most important tip is: Avoid hard coding resource locations.

Hard coding the IP address of your database straight into your code will result in all sorts of headaches when some new developer comes along and wants to connect to localhost or a test database instead. Similarly hard coding the location of libraries (ex: /home/bob/businessapp/library/coolstuff.php) can lead to extreme frustration if your app ever ends up installed in a different location or even on a different operating system. You’ll wind up being forced to hunt down and replace dozens or hundreds of file references with new locations that match the new machine.

The fix for this is to store environment specific data like code paths and database connection credentials in some sort of variable instead.

//BAD! This will make it really hard to run this code on a different machine or OS
include('/home/bob/businessapp/library/coolstuff.php');
include('/home/bob/businessapp/library/importantstuff.php');

//BETTER! Now we can adapt this code to new machines by changing just one variable
$libraryPath = '/home/bob/businessapp/library/';
include($libraryPath.'coolstuff.php');
include($libraryPath.'importantstuff.php');

But where should you declare these environment specific variables?

Your first instinct might be to just store them in the same file where they are used. Put the database variables at the top of your database connection script or your library path at the top of the file that loads up all the support code for your framework.

But mixing up environment variables and code can be dangerous.

For instance, imagine you send your buddy a copy of your web app and teach him how to customize the connection information in the database file “database.php”. Then a few months later you find a bug in that code and have to send him an updated copy. He deletes his broken “database.php”, loads the new one and suddenly nothing works because deleting the old “database.php” file also deleted the customi connection information that pointed to his database.

But what if you instead had two files: a “database_connect.php” file that has the actual database logic and a “database_config.php” file that just holds a few variables for database location and authentication. You give your friend a copy of both and show him how to update “database_config.php”. A few months later you find an error in the logic of “database_connect.php” and send him an update. This time everything works perfectly because replacing the broken “database_connect.php” file had no impact on the customized variables your friend had entered into “database_config.php”.

This is the basic idea behind config files. Sometimes they’re text files holding values for the code to read. Other times they are actual code files filled with static variables. But no matter how they are written the goal is the same: to create a file that will never be overwritten by bug fixes or feature updates; a safe place for keeping track of machine and user specific information.

Keep that in mind when designing your next major web app and you should hopefully find yourself in a situation where copying or duplicating your program is as easy as copying some code and updating a few config files. And doesn’t that sound like a lot more fun than trying to manually find and replace dozens of hard coded references that keep trying to overwrite each other every time you install an update?

 

*  Example: Sometimes software, especially in embedded systems, keeps track of time by counting processor cycles. As you can imagine a program that thinks one second is equal to one million processor cycles will start acting really weird if you give it a processor that runs several times faster than that.

Let’s Program A Prisoner’s Dilemma Part 1: Game Theory Is Less Fun Than It Sounds

A Different Kind Of Gaming

Game Theory, despite the name, is unfortunately not “The study of videogames”. Instead it is the science of mathematically analyzing how decisions are made.

The reason it’s called game theory is because one popular way to analyze real world decision making is to come up with a simplistic “game” that mimics the core dynamic of the real world problem. The game can then be mathematically analyzed and the results applied back to the more complex real world situation.

For instance, a game theorist studying crime might come up with a “game” where people who follow the law get 1 point and people who break the law have a chance of gaining two points but also a chance of losing three points. He can then mathematically analyze how different risk levels influence whether or not crime is a winning strategy.

The Prisoner’s Dilemma

The most famous example of a game theory game is the “Prisoner’s Dilemma”. Two criminal are caught red-handed committing a small crime that will land them in jail for one year. The police also suspect the two criminals are guilty of a larger crime worth two years of jail time, but they don’t have enough evidence to prove it.

The police eventually come up with a plan to interview each criminal separately and offer them this deal: Testify about the other guy’s involvement in the larger crime and we’ll drop the current small charge against you.

And now here’s the “game”: As one of the prisoners do you protect your buddy and keep quiet even though it means going to jail for a year? Or do you tattle in hopes of going free? And how do you know whether or not you can trust the other guy? If he confesses you go to jail for an extra two years.

Other Guy Keeps Quiet Other Guy Confesses
You Keep Quiet You both go to jail for one year You go to jail for three years

Other guy goes free

You Confess You go free

Other guy goes to jail for three years

You both go to jail for two years

Now as mathematicians we want to be able to analyze this choice with actual numbers, so let’s do a little game theorizing. Instead of two prisoners let’s imagine a two player game where each player is allowed to choose to either play nice and cooperate or to betray their partner and defect. If you cooperate you lose 1 point. If you defect the OTHER player loses 2 points.

Player B Cooperates Player B Defects
Player A Cooperates Player A: -1 point

Player B: -1 point

Player A: -3 points

Player B: -0 points

Player A Defects Player A: -0 points

Player B: -3 points

Player A: -2 points

Player B: -2 points

What makes this such an interesting game to study is the fact that the best group strategy and the best individual strategy are complete opposites.

As a group the obvious solution is for both players to cooperate so that between them they only lose 2 points. If one player defects the total loss jumps up to 3 points and if both players defect the total loss is 4 points, as bad as it gets.

But as an individual the equally obvious solution is to defect. After all, if you cooperate you are guaranteed to lose 1 point and might even lose 3 points if the other player defects. But if you yourself defect you might not lose any points and even if the other player defects you only lose 2. There is just no good reason to purposely give up one point.

So the smartest thing to do as an individual is to defect but the smartest thing to do as a group is to cooperate and there doesn’t seem to be any logical way to bridge the gap between these two strategies. Or is there?

Iterated Prisoner’s Dilemma

The core problem in the prisoners dilemma is the fact that it’s a one-time choice. You either cooperate or defect and a few seconds later you find out who won and who lost, end of story. This is why there is no logical reason not to betray the other player. If you defect you’re guaranteed to at least tie so why risk anything else?

But what if you had to play the game multiple times?

This could change things up. Now your decision to cooperate or defect might impact how future games are played. There is now a possibility to build or lose trust.

But sadly you can show that even with multiple games the best logical strategy is still to always defect. The logic goes like this:

1) During the last round you might as well defect since there are no more future rounds in which the other player could punish you.

2) Since a logical opponent will defect during the last round you might as well defect during the second to last round. It’s not like your opponent was going to cooperate in that last round anyways.

3) Since a logical opponent will defect during the second to last round you might as well defect during the third to last round…

4) Continue the pattern and you can see that always defecting is the best strategy.

There are only two ways to break out of this cycle.:

One is to have an infinite number of rounds so that players are always at risk of being punished in the future for bad behavior in the present. We’re not going to do that though for the simple reason that there is no way to run an infinite number of Prisoner’s Dilemmas on a finite computer.

The other way to break the cycle is to introduce more players.

Iterated Group Prisoner’s Dilemma

The iterated group prisoners dilemma works the same as the normal iterated prisoner dilemma except that you have several different players who get randomly paired up before every round.

This drastically changes the dynamics of the game.

In a two-player prisoner’s dilemma double defection is a “stable” strategy because it means neither player will get ahead of the other. You might be losing two points every round but the other guy is too so you aren’t actually losing.

But when there’s a group? Now a players who is constantly losing two points from double defection can end up falling behind other pairs of players who cooperate and thus only lose one point per round. Suddenly it’s not enough to try and figure out whether your current partner is going to cooperate or defect; you also have to worry about whether other players in other matches are cooperating or defecting.

Interesting… But Is It Useful?

The Iterated Group Prisoners Dilemma is not only mathematically interesting, it is highly relevant to real life. Ever day people are faced with scenarios where they can choose to either work together or try to cheat each other. In the long run it’s best for society when people work together but on an individual level exploiting others can provide huge short term benefits.

So by analyzing the prisoners dilemma we can get some interesting insights into why different societies work the way they do along with some ideas on what sort of ethical systems are and aren’t stable.

Of course, real life has all sorts of issues that aren’t fully reflected in a simple tool like the prisoners dilemma so don’t think it somehow has all the answers to all our problems. But it’s still better than nothing and at the very least provides a nice topic for small talk with other math geeks.

What Does Any Of This Have To Do With Programming?

As AI enthusiasts we are obviously very interested in writing programs that can make logical decisions, and the prisoners dilemma provides a great sandbox for practicing that skill. It’s simple enough of a scenario to be implemented in less than a page of code while still being complex enough to produce interesting results.

So here’s the plan for this Let’s Program: We’re going to write a Prisoner’s Dilemma simulation along with a few different types of players each with their own strategy for deciding when to cooperate and when to defect. We’ll then run a bunch of simulations and see if we can find any interesting trends.

Like usual this isn’t exactly an original idea. Game theorists have been writing and testing prisoners dilemma programs for years and if you’re familiar with their research you probably already know everything we’re going to cover in this Let’s Program.

But who cares? The goal here is to practice our programming, not push the cutting edge of computer science. And there’s bound to be at least one reader in my audience who isn’t already a game theory expert.

Actually, quick show of hands: Is there anyone here who has never studied game theory before reading this blog?

There, see? That guy over there is the reason we’re doing this. He’s about to learn something cool.

Open Worlds With Closed Plots

Open world games have become an incredibly popular genre and the industry has settled on a predictable formula for building them. You start by creating a decent sized world for the player to explore, usually a big city or a small country. You then fill that world with secrets to find, activities to do and optional mission to complete. You then finish up by introducing a series of connected, mandatory missions that work together to tell some sort of story.

Unfortunately, half the time the main story is the least entertaining part of the game.

Part of this is because open world games are hard to write for. Writers are used to linear stories where they can control what happens and when. Need tension? Put a bunch of tough challenges one right after another. Need urgency? Introduce a time limit. Want some relief? Tone down the enemies for a level or two.

But in an open world game? The pacing is all in the hands of the player. He might decide to follow along and do your “urgent” mission right away, or he might decide to spend five hours fishing and blow away whatever sense of dramatic tension you were trying to build. And while it is possible to write a story that makes sense regardless of whether it is done quickly or at a leisurely pace the truth is that most writers have yet to master the knack of producing such a flexible script.

But it’s not just story. There’s a real gameplay conflict between the freedom of the open world genre and the limits of mandatory plot missions. This leads to frustrated players who have gotten used to being able to pick and choose what they want to do and why only to suddenly find themselves forced into a specific type of gameplay or given a specific motivation they may or may not actually care about.

For instance, I spent most of my time in Watchdogs playing as the world’s sneakiest pacifist hacker and was rather annoyed when the game suddenly mandated that I murder several dozen mobsters in a literally explosive action scene. Sure, it was cool but that wasn’t how I wanted to play the game.

All of which makes me wonder… what if we built an open world game with no main plot? Just drop the player into the world, teach him what a sidequest marker looks like and then leave it completely up to him to pick and choose how to play the game. Don’t force a specific high-level goal on him or try to tell him what his character’s ultimate motivation is. Instead let every player blaze their own narrative into the world.

The Elder Scroll series and their Fallout cousins are actually pretty close to doing this. The main quest is already mechanically treated just like an extra big side quest and the manual is often eager to reassure you that nothing bad will happen if you decide to ignore the whole thing and go off and do your own thing instead. So why not go just one step further and drop that main quest entirely? Take the development time that would have gone into it to instead spice up as many of the side quests as possible.

Personally I think it’d be a blast.