Let’s Illustrate Dark Souls 3 Part 7: The Quest For Pants

Defeating Vordt gives Fault access to the Undead Settlement, a rural farming community full of undead. Not really sure why though. Was it a normal farming town where everyone went hollow all at once or did a bunch of hollow decide to get together and take up gardening as a way of fighting off madness?

Anyways, Fault has more pressing issues than wondering about the history of undead agriculture. Her armor still sucks and she’s tired of scandalously flashing her ankles for the whole world to see. It’s time for a quest for pants.

Thanks to our high luck Fault manages to rob a pair of worker’s pants off the first undead villager she murders. Unfortunately it turns out to be completely worthless having even less stat bonuses than the “Deserter’s Trousers” we started the game with. Same problem with the cleric pants we find on a corpse later on in the area. So for now we’re stuck with our silly ragged shorts because as much as Fault hates running around with cold legs she hates getting stabbed to death even more.

But wait! Killing Vordt means we now have a bonfire right next to a group of three knights. Knights that dropped all sorts of awesome armor when I killed them in an earlier file.

Thus launches Operation Metal Pants which basically boils down to killing those three enemies, looting their corpses and then running back to the bonfire to start the whole cycle again in hopes of eventually getting a good armor set. The plan does have some slight kinks, mostly revolving around the fact that fighting heavily armored enemies while virtually naked yourself is kind of difficult, but eventually the drops start piling up.

And what drops they are! Fault’s high luck is already paying off as I’m inundated by cool drops I never saw on my earlier files: Free embers, powerful swords, cool shields and a ton of crossbows. But, frustratingly enough, no pants. The one piece of armor I always found on my non-luck files becomes the only piece of equipment I fail to find after killing several dozen of the things. Instead they just keep dropping these awesome weapons I don’t (and probably will never) have the stats to use.

Well, whatever. Function is more important than form so I dramatically boost my armor by throwing on a full steel helm and some gauntlets and warp back to the Undead Settlement with Fault still stuck in her breezy shorts.

Finally switched from Gimp to an actual illustration program, Manga Studio. It's nice but obviously not a miracle worker.

Finally switched from Gimp to an actual illustration program, Manga Studio. It’s nice but obviously not a miracle worker.

Let’s Illustrate Dark Souls 3 Part 6: Performance Enhancing Embers

Last time Fault the thief forged herself a +1 fire rapier and then died repeatedly to the game’s first non-tutorial boss: Vordt of the Boreal valley.

The basic problem is that Fault doesn’t have enough armor or HP to survive more than one attack in a row, which is unfortunate because Vordt’s fighting style involves a lot of combos where he uses one attack to knock you over and then hits you a second time while you’re standing up. All the estus in the world won’t do you any good if you go straight from full health to dead without a chance to drink it.

The obvious solution here is to just avoid getting hit in the first place but my fire rapier only does 40 points of damage per poke, dragging the fight out and giving Voldt lots and lots of time to pull off a two-hit combo. I manage to work myself up to something like a 90% successful dodge rate but that’s just not enough when the other 10% is lethal.

I finally burn one of my precious early game embers which gives me just enough bonus health to survive the occasional two-hit combo. Fault can now survive long enough to fire poke Voldt to death.

This victory gives Fault a ton of XP souls which, according to my build rules, I have to use to level up luck. Bummer. But at least my luck is finally high enough I can start boosting my vitality for some extra HP!

This was much funnier in my head.

Let’s Illustrate Dark Souls 3 Part 5: A Burning Question

Last time Fault found enough upgrade materials to give one of her weapons a +1 bonus.

Equally important is the fact that she still has the fire stone she selected as a starting gift. This can be used to imbue a weapon with fire, which removes normal stat bonuses in exchange for a set amount of fire damage. Ex: A normal axe does extra damage based on how strong you are but a fire axe does the same damage no matter what your stats are.

Our luck build means that our stat bonuses are all horrible anyways so we should come out ahead in this deal. In fact, that’s why we picked this starting gift.

Most people can't draw hands, but I really have no excuse for also failing to draw a simple cube.

Most people can’t draw hands, but I really have no excuse for also failing to draw a simple cube.

But Fault only has enough materials to upgrade on of her weapons, meaning we have to decide between our rapier and our bandit knife. The rapier does more damage and has longer range but the bandit knife has a bleed effect that does bonus damage if you hit the same enemy enough times in a row.

In the end Fault goes with the rapier. It turns out that a fire bandit knife does slightly less bleed damage than a normal bandit knife and it seems a shame to mess with the weapon’s gimmick. Of course, neglecting to upgrade it means we won’t use it as often which means we won’t see the bleed effect anyways but, meh, decision making is hard.

With our shiny new +1 Fire Rapier in hand we go to tackle the first non-tutorial boss which, of course, absolutely destroys Fault over a dozen times in a row despite the fact that I’m doing a better job at dodging his attacks and exploiting his openings than ever before. Tune in next time to find out why.

Let’s Illustrate Dark Souls 3 Part 4: Second Hand Swords

Last time Fault made it through the tutorial area and reached Firelink Shrine. That means she can now activate the shrine’s main bonfire and use it to teleport to the first area of the game proper: The High Wall.

Once again, areas that my non-gimmick character effortlessly cleaved through require a ton of hard work as a thief. It’s not just that Fault’s armor is weak, her shield also only blocks 60% of incoming damage. That means that even if I block an attack I still take a beating that my low health can’t handle. As a result I have to rely almost entirely on dodging and the occasional parry. And while DS3 has really boosted the power of the dodge roll compared to the previous two games it’s still nerve racking and dangerous to not have a great shield to fall back on.

Now a normal thief build would solve this problem by dumping three points into strength so they could wield a decent shield. Especially since those strength points would also give them access to more and better weapons.

But Fault is a Luck Knight and is dozens of levels away from being allowed to touch her base stats. So no shields for us!

It’s not all bad knew though. Fault manages to corpse rob herself a rapier that she can actually wield using nothing other than her base stats! She also picks up a handful of titanite and still has her fire stone starting gift burning a hole in her pocket (pun intended) all of which means it’s time to go the blacksmith for some much needed upgrades.

04rapierfind

The smaller the drawing the less room there is for making mistakes, right?

Let’s Illustrate Dark Souls 3 Part 3: In Which The Plot Is Expositioned

Last time Fault the thief challenged the tutorial boss and died. Repeatedly.

Fortunately I eventually get good enough at dodging to kill the boss. As you might imagine this will become a sort of running theme for this playthrough.

More importantly, with the tutorial out of the way Fault is able to make her way to Firelink Shrine where a mysterious blindfolded woman offers to help her turn souls into levels because this a FromSoftware adventure game and that’s just what happens. The mysterious woman also mentions something about needing to find some Lords of Cinder so they can kindle some magic fire etc… etc… deja vu.

The thief’s crazy low starting level means I can level up multiple times with nothing but the souls from the tutorial boss. Normally this would be super exciting but my build restrictions mean I have to dump all those points straight into luck despite desperately wanting more health. Oh well, at least the game gives you an itty bitty defense boost just for leveling up.

Remember how back in the original you could level up all on your own at any bonfire? Is there a lore reason that doesn't work anymore?

Remember how back in the original you could level up all on your own at any bonfire? Is there a lore reason that doesn’t work anymore?

Let’s Illustrate Dark Souls 3 Part 2: Off To A Smashing Start

Last time I introduced the rules of doing a Luck Knight run of Dark Souls 3, rules that will basically require me to pour half of my levels directly into my luck score instead of something more obviously useful like strength.

That means it’s time to get things started with character creation. We choose the thief class, grab a fire stone for our starting gift and then it’s time to customize our appearance.

Since my Dark Souls 2 hero was a man I figure I might as well go with a woman this time. I then spend roughly ten seconds on customizing her appearance because, let’s be honest, she’s going to spend her entire career wearing so many layers of Dark Souls style unisex armor that it won’t really matter whether she’s a nice looking brunette or some sort of radioactive green mutant. (For the curious she’s a blond with red eyes. Because why not?)

On the other hand her name deserves a little more thought. I eventually go with “Fault” since I figure I’m going to be spending a lot of time staring at the game over screen and saying to myself “Yup, that was my Fault”.

With that out of the way the game finally begins with Fault waking up in a graveyard filled with handy tutorial messages, weak enemies and a nifty tutorial boss.

At this point I get my first taste of how much “fun” this run is going to be when the tutorial boss effortlessly flattens me half a dozen times in a row.

Gimp is a great open source tool for photo editing. Admittedly not so great for freehand illustration.

Gimp is a great open source tool for photo editing. Admittedly not so great for freehand illustration.

For comparison I killed this same boss on my first try during my first non-gimmick run.

And my weird build restrictions haven’t kicked in yet! I’m not losing because I’m being silly. The thief class just has an innately rocky start due to their low starting level, weak equipment and a focus on range and backstabbing in a world where bosses usually can’t be backstabbed or fought at range.

Let’s Illustrate Dark Souls 3 Part 1: Let’s Do What Now?

I’ve always wished I could draw but I’ve never quite had the discipline to put in the daily practice required to learn the skill. This is a real shame because the ability to produce half-decent character art would be a really valuable skill for a hobbyist game developer like me.

So I’ve hit on a plan. Why don’t I illustrate my most recent Dark Souls 3 playthrough and post it on the Internet? Illustrating a game means I won’t run out of ideas of what to sketch and posting it online should help shame me into actually keeping up a regular schedule.

But why should you care? Because this isn’t just any Dark Souls playthrough, it’s a gimmick run! I’ve created a build I call the “Luck Knight” that focuses on pumping it’s luck super high instead of spending points on stats that actually help with killing monsters.

The rules of the Luck Knight build are as follows:

  • Starting class: Thief
  • Luck must always be the highest stat
  • Vitality, Vigor and Endurance can be raised no higher than half of luck
  • All other stats are capped at one third of luck

 

That means that if my luck is 30 then my vitality can be no higher than 15 and my strength is stuck at 10.

It also means that if I want to use something like a katana with an 16 dexterity stat requirement I will have to first boost my luck to 48.

So basically I’m going to be stuck with the thief class’s starting stats for a very long time.

At least I got the right number of limbs. It's a start, right?

At least I got the right number of limbs. It’s a start, right?

Let’s Program A Prisoner’s Dilemma Part 8: And The Moral Of The Story Is…

Last time we finished up our Prisoner’s Dilemma experiments by tossing all of our prisoners into a big arena and then having them compete in a multi-generational survival-of-the-fittest pit fight in order to see which strategies have the best long term survivability.

Now it’s time to see the final outcome, but nobody wants a blog post filled with nothing but thousands of lines of pure numbers. Instead I’ve dumped my test results into a graphing program program and built this nice little animation for you.

generations_of_prisoners_dilemmas

So what did we see happen here?

Things started out about like you’d expect. The first few generations saw the devils taking advantage of the naïve saints and quickly becoming the majority of our population. They then steamrolled their way through the madmen who just aren’t smart enough to purposely avoid being taken advantage of by the devils.

We’re now left with a population that’s 75% devil with a mere handful of judges. But judges ARE smart enough to notice when people keep cheating them so we see a sudden reversal. The judges stop feeding free points to the devils while simultaneously working together to keep their score high. A few more generations is all it takes for the devils to be completely wiped out by the superior teamwork of the judges.

Wasn’t This Supposed To Have Real World Applications?

Waaaaay back in part one I mentioned that the main goal of game theory was to use simple math games to analyze real world decisions making.

So what real world lessons can we learn from all our experiences with the prisoner’s dilemma?

First, that a group full of trustworthy people is a good thing for everybody.

Second, that a cheater can easily exploit a group full of trustworthy people for massive gain.

Third, that having too many cheaters isn’t just bad for the group, it eventually leaves the cheaters worse off too.

Fourth, that a reliable way to prevent cheaters from messing up a group is by identifying and then punishing them until they either reform or leave the group.

These four observations together explain the rise and fall of a lot of real world organizations. You start off with a group of trustworthy people who work together to accomplish something. Eventually a cheater infiltrates the group and realizes that he can exploit the good will of the group for personal gain. Other cheaters eventually notice the scam and decide they also want to join in on exploiting the organizations.

At this point one of two things can happen: Either the group notices the cheaters and manages to remove them before irreparable harm can be done or the group fails to stop the cheaters and collapses under their corrupting weight (possibly after limping along for years or decades).

But why do so many groups fail to stop the cheaters?

Sometimes it’s because the group is just too nice for their own good. They feel bad about having to punish or expel anyone, so instead they just put up with the cheaters until it’s too late.

But more often than not the problem comes from the fact that identifying cheaters is really hard.

This is, in fact, one of the big limitations of the Prisoner’s Dilemma: It assumes you can tell when people are cooperating with or betraying you. But relatively few real world situations fit that pattern. Usually it’s really really hard to tell between a good natured cooperator and a really sly betrayer. A competent cheater can get away with dozens of mini-betrayals before anybody figures out what’s really going on.

Consider a politician who helps pass a popular law, but only after adding in a few sneaky sections that will help slowly funnel money and power to his buddies.

Consider a CEO who generates record profits in the short term by knowingly sabotaging the long term health of the company that hired him.

Consider a forum member who claims to be interested in giving people constructive criticism but in reality is a troll who just likes frustrating people by picking apart everything they say.

Knowing that the tit-for-tat Judge strategy works doesn’t actually do us much good unless we happen to have a talent for judging the behavior of others. And that is sadly a feat simple game theory can’t help us with.

In Summary

Don’t be a cheater. In the short run it might seem like a good idea but in the long run the odds are pretty high you’ll get stuck in a downward spiral with other cheaters and wind up worse off than if you had played it straight all along.

Don’t be naive. If there are no consequences to taking advantage of you cheaters will eventually drain you dry.

Use your judgment. Helping helpful people and avoiding cheaters seems to be the most reliable path to both individual and group success.

Acknowledge that figuring out who is helpful and who is a cheater is much harder than our simple simulations made it seem. Real life is messy.

Homework Assignment

Crack open a history book and find your favorite failed civilization, company, club or whatever. Do some research into the specifics of it’s rise and fall. Did it start out as a bunch of people cooperating towards a common goal? Can you spot a point at which cheaters started infiltrating? Was there a final tipping point where too many people were scamming the group and it could no longer hold up?

Now look up a group that seems to have survived the test of time. What did it do differently? Did it have some sort of rule or tradition that helped prevent cheaters from taking advantage of the group?

Remember, real life is messy so it’s entirely possible that whatever real life examples you find won’t quite fit into the standard pattern of an iterated prisoner dilemma. In which case you now have a great excuse to dig deeper into game theory and see if any of the more advanced scenarios do fit your historical example.

Dark Souls 3: That Which Has Been Illuminated Can No Longer Be Hid

A couple weeks ago we looked at Dark Souls 2 and talked about how FromSoftware is really good at making mystery adventure games but that their standard mystery formula doesn’t work so good when trying to make a sequel instead of standalone game.

Now that Dark Souls 3 is out it’s time to continue that discussion by asking ourselves: How did DS3 approach the mystery sequel problem? Did they do a better job than DS2?

Surprisingly enough the answer turns out to be: Dark Souls 3 avoided the mystery sequel problem entirely by not being a mystery game. It focused entirely on just being the best sequel it could.

That’s right, DS3 is a straight up adventure game with no big core mystery to solve. Sure, there are still a ton of mysterious environments to explore with their own hidden lore and history but the major points of the plot along with your overall endgame goal are all spelled out within the first thirty minutes of the game. This is obviously a big departure from the pacing of the original, where it was very possible to be two dozen hours into the game and still have only the vaguest idea of what was going on or why you were doing what you were doing.

On the one hand this is disappointing because my favorite thing about FromSoftware is how good they are at designing satisfying mysteries.

But on the other hand I have to admit that going for a more straightforward adventure story was probably a smart move. As I’ve said before, the first game really didn’t need a sequel. The plot was self-contained and all the big questions were answered. Forcing major new plot elements into the setting would just make a mess of both games.

So the developers gave up on mystery and instead tried (and succeeded) at showing a natural evolution of the world of Dark Souls. Almost every faction, enemy and location you discover has some sort of historical connection to an event from the original game. It leaves you with a satisfying feeling of “I can see why Thing A happening in Dark Souls has lead to Thing B existing in Dark Souls 3”.

In summary: Dark Souls 3 gave up on large scale mysteries in exchange for honoring and preserving the legacy of the original game. This was probably a smart move and the game was pretty fun but I personally hope that in the future FromSoftware goes back to making standalone mystery adventures and more or less gives up on direct sequels.

Let’s Program A Prisoner’s Dilemma Part 7: Survival of the Fittest

So we’ve built a bunch of different prisoner models and thrown them together in all sorts of different combinations to see what happens. And we could keep doing that.

But isn’t running all these experiments by hand kind of boring? I mean, who really has the time to sit around all day adjusting prisoner ratios and recording results?

Fortunately, as programmers we know that the answer to boredom is automation, so let’s build us a system that can test different prisoner populations for us.

What we’re going to be doing specifically is a simple survival of the fittest algorithm. We’ll still give our program a starting mix of prisoners, but after their first game is done the worst 10% of the prisoners will be removed while the best 10% will be duplicated. This new group will then play the game again. We’ll repeat this pattern a few dozen or hundred times producing all sorts of interesting numbers on what sorts of groups seem to work best.

It Runs In The Family

So what should our new multi-generational code look like?

For starters the main game playing algorithm should stay the same. We’ll still have a group of prisoners that need to be randomly paired up for a certain number of rounds and we’ll still want to keep track of everyone’s score.

The only difference is that at the end of the round instead of just reporting those scores and exiting we’re going to want to generate a new group of prisoners and then run the entire experiment again. We’ll do this for however many generations we think the experiment should run.

So there’s our first hint on what this should look like. Instead of just asking for a group of prisoners and how many rounds they should play we are going to ask for a group of prisoners, how many rounds they should play and how many times (or generations) we should repeat the experiment.

def playMultiGenerationalPrisonersDilemma(prisoners, generations, roundsPerGeneration)

Now inside of that we’re going to need a loop that runs once for every generation we asked for. And inside of that loop we’re going to want to copy our existing prisoner dilemma code. Standard Disclaimer: Copying code is sloppy and should be avoided on serious projects. If you were building a professional prisoner dilemma product you would want to figure out some way that playPrisonersDilemma and playMultiGenerationalPrisonersDilemma could both reference the same piece of code instead of copying each other.

Anyways…

generations.times{ |generation|
   #Copy paste our code from the normal playPrisonersDilemma function here
end

That’s enough to make our code play the prisoners dilemma again and again as many times as we want, but it will use the same group every time. What we want instead is to create a new group based off of the last group but with the bottom 10% of prisoners removed and the top 10% of prisoners doubled. We can do this by adding a little bit of code after the copy pasted dilemma logic, right after the part where it prints out the score.

#Generate a new group of prisoners based off of the last group.
#Prisoners that scored well get duplicated. Prisoners that scored poorly get dropped

newPrisoners = Array.new
newPrisonerCount = 0
newSaintCount = 0
newDevilCount = 0
newMadmenCount = 0
newJudgeCount = 0

prisoners.sort{ |x, y| y.score <=> x.score}.each{ |prisoner|
   #Top ten percent get an extra prisoner in the next generation
   if newPrisonerCount < (prisoners.length / 10).floor
      case prisoner.strategy
      when "Saint"
         newSaintCount = newSaintCount + 1
      when "Devil"
         newDevilCount = newDevilCount + 1
      when "MadMan"
         newMadmenCount = newMadmenCount + 1
      when "Judge"
         newJudgeCount = newJudgeCount + 1
      end
   end

   #Bottom ten percent don't get added to the next generation at all
   if newPrisonerCount >= prisoners.length - (prisoners.length / 10).floor
      break
   end

   #If it's not the bottom ten percent add one to the next generation
   case prisoner.strategy
   when "Saint"
      newSaintCount = newSaintCount + 1
   when "Devil"
      newDevilCount = newDevilCount + 1
   when "MadMan"
      newMadmenCount = newMadmenCount + 1
   when "Judge"
      newJudgeCount = newJudgeCount + 1
   end

   newPrisonerCount = newPrisonerCount + 1
}

#Create new generation and load it into the prisoners variable for the next game
prisoners = createPrisoners(newSaintCount, newDevilCount, newMadmenCount, newJudgeCount)

Nothing fancy here. We just sort the prisoners and then iterate through them while keeping track of how many prisoners of each type we’ve seen. For the first ten percent of prisoners we count double and we skip the last ten percent entirely.

We then use our good old createPrisoners function to build a new generation of prisoners. Starting with fresh prisoners like this is actually better than trying to modify our old prisoner group because it ensures no lingering data gets left behind to mess things up. For instance, it would be a bad thing if Judge style prisoners kept their memory between matches (especially since IDs might wind up changing between games).

With the new prisoners created the generation loop then repeats and our new group of prisoners play the game again. All that’s left to do is add a nice little message to our score output letting us know which generation we’re on and we have a complete little program.

def playMultiGenerationalPrisonersDilemma(prisoners, generations, roundsPerGeneration)
        if !prisoners.length.even?
            throw "Prisoners Dilemma requires an even number of participants"
        end
        
        generations.times{ |generation|
        
            # Make sure each prisoner starts out with a clean slate
            prisoners.each{ |prisoner| prisoner.score = 0}
        
            roundsPerGeneration.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)
                }
            }
        
            #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 "In generation #{generation+1} the group's overall score was #{groupScore} in #{roundsPerGeneration} rounds with #{prisoners.length} prisoners"
            prisoners.sort{ |x, y| y.score <=> x.score}.each{ |prisoner| prisoner.report }
            
            #Generate a new group of prisoners based off of the last group.
            #Prisoners that scored well get duplicated. Prisoners that scored poorly get dropped
            newPrisoners = Array.new
            newPrisonerCount = 0
            newSaintCount = 0
            newDevilCount = 0
            newMadmenCount = 0
            newJudgeCount = 0
            prisoners.sort{ |x, y| y.score <=> x.score}.each{ |prisoner|
                #Top ten percent get an extra prisoner in the next generation
                if newPrisonerCount < (prisoners.length / 10).floor
                    case prisoner.strategy
                    when "Saint"
                        newSaintCount = newSaintCount + 1
                    when "Devil"
                        newDevilCount = newDevilCount + 1
                    when "MadMan"
                        newMadmenCount = newMadmenCount + 1
                    when "Judge"
                        newJudgeCount = newJudgeCount + 1
                    end
                end
            
                #Bottom ten percent don't get added to the next generation at all
                if newPrisonerCount >= prisoners.length - (prisoners.length / 10).floor
                    break
                end
                
                #If it's not the bottom ten percent add one to the next generation
                case prisoner.strategy
                when "Saint"
                    newSaintCount = newSaintCount + 1
                when "Devil"
                    newDevilCount = newDevilCount + 1
                when "MadMan"
                    newMadmenCount = newMadmenCount + 1
                when "Judge"
                    newJudgeCount = newJudgeCount + 1
                end
                
                newPrisonerCount = newPrisonerCount + 1
            }        
                    
            #Create new generation and load it into the prisoners variable for the next game
            prisoners = createPrisoners(newSaintCount, newDevilCount, newMadmenCount, newJudgeCount)
        }
end

Me, Papa and Granpappy

Now let’s give our code a whirl. For testing purposes I’m going to create a small 10 prisoner group and have it run for a mere three generations.

prisoners = createPrisoners(2, 3, 3, 2)
#playPrisonersDilemma(prisoners, 1000)
playMultiGenerationalPrisonersDilemma(prisoners, 3, 1000)

In generation 1 the group’s overall score was -15444 in 1000 rounds with 10 prisoners

ID: 5 Score: -1176 Strategy: Devil

ID: 3 Score: -1190 Strategy: Devil

ID: 4 Score: -1192 Strategy: Devil

ID: 9 Score: -1495 Strategy: Judge

ID: 10 Score: -1505 Strategy: Judge

ID: 6 Score: -1589 Strategy: MadMan

ID: 8 Score: -1616 Strategy: MadMan

ID: 7 Score: -1639 Strategy: MadMan

ID: 1 Score: -1994 Strategy: Saint

ID: 2 Score: -2048 Strategy: Saint

In generation 2 the group’s overall score was -16716 in 1000 rounds with 10 prisoners

ID: 4 Score: -1440 Strategy: Devil

ID: 3 Score: -1444 Strategy: Devil

ID: 5 Score: -1448 Strategy: Devil

ID: 2 Score: -1498 Strategy: Devil

ID: 10 Score: -1602 Strategy: Judge

ID: 9 Score: -1629 Strategy: Judge

ID: 7 Score: -1793 Strategy: MadMan

ID: 6 Score: -1815 Strategy: MadMan

ID: 8 Score: -1839 Strategy: MadMan

ID: 1 Score: -2208 Strategy: Saint

In generation 3 the group’s overall score was -17991 in 1000 rounds with 10 prisoners

ID: 2 Score: -1642 Strategy: Devil

ID: 5 Score: -1656 Strategy: Devil

ID: 3 Score: -1680 Strategy: Devil

ID: 1 Score: -1688 Strategy: Devil

ID: 4 Score: -1690 Strategy: Devil

ID: 10 Score: -1738 Strategy: Judge

ID: 9 Score: -1749 Strategy: Judge

ID: 6 Score: -2039 Strategy: MadMan

ID: 7 Score: -2045 Strategy: MadMan

ID: 8 Score: -2064 Strategy: MadMan

Looking at the first generation you’ll notice that the devils picked on the saints (like usual) which put the devils in the lead and sent the saints to last place. That means that the top 10% of our group was a single devil and the bottom 10% was a single saint. Because of this the next generation has four devils (up from the starting three) and only one saint (down from the starting two).

Another generation then passes with results very much like the first, causing us to lose another saint and gain another devil. Which is honestly a little ominous…

Next Time: Turning Boring Data Overflow Into Interesting Visuals

So that was just ten prisoners playing three generations of fairly short games. For a real experiment we’re going to want hundreds of prisoners playing significantly longer games over at least a dozen generations. Things like:

prisoners = createPrisoners(250, 250, 250, 250)
playMultiGenerationalPrisonersDilemma(prisoners, 25, 10000)

Only problem is that a big experiment like that is going to rapidly fill up your terminal with junk. Sending the results to a text file helps but even that produces a lot more numbers than the human brain can conveniently work with.

But you know what the human brain can conveniently work with? Animated bar graphs!