When people co-operate, things usually turn out well. If no-one co-operates, then everyone usually suffers. So the game plan is obvious, right?
Wrong! If everyone else is co-operating, then some clever fellow will come along and take advantage. So now the game plan is obvious, refuse to co-operate and take advantage of everyone who is naively co-operating.
Stupid! This is so obvious that no-one will co-operate, will they? And, yes, everyone will suffer.
This is clever?
This is a famous dilemma, with all sorts of significant analogies in the real world from Mutually Assured Destruction to the Tragedy of the Commons to the evolution of ethics in a godless universe.
There is a very simple solution, though it only becomes perfectly obvious by using mathematical simulation.
The Nice Guy
Let’s build a player who always co-operates – returns true whenever asked for a response.
class cPlayerNiceGuy : cPlayerBase { public: cPlayerNiceGuy() : cPlayerBase( L"NiceGuy" ) {} bool Response( int , const cHistory& ) { return true; } cPlayerBase * Reproduce() { return new cPlayerNiceGuy; } };
Now build an ecology composed only of nice guys.
cEcology ecology; ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerNiceGuy() );
The result of this simulation is:
NiceGuy scored 1260 NiceGuy scored 1260 NiceGuy scored 1260 NiceGuy scored 1260 NiceGuy scored 1260 NiceGuy scored 1260 NiceGuy scored 1260
As you would expect, everyone does equally well.
The Black Hat
Now lets introduce someone who tries to take advantage of all these nice guys, someone who always return false when asked for a response
class cPlayerBlackHat : cPlayerBase { public: cPlayerBlackHat() : cPlayerBase( L"BlackHat" ) {} bool Response( int , const cHistory& ) { return false; } cPlayerBase * Reproduce() { return new cPlayerBlackHat; } }; cEcology ecology; ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add((cPlayerBase *)new cPlayerBlackHat() ); BlackHat scored 2100 NiceGuy scored 1080 NiceGuy scored 1080 NiceGuy scored 1080 NiceGuy scored 1080 NiceGuy scored 1080 NiceGuy scored 1080
Well now, the Black Hat has gained enormously at the expense of all the nice guys!
Darwin
So now we introduce a little ‘Darwinian Evolution’ – we kill off the lowest scoring player and allow the highest scorer to reproduce.
After a few generations, we have these results:
BlackHat scored 1140 BlackHat scored 1140 BlackHat scored 1140 BlackHat scored 1140 BlackHat scored 1140 NiceGuy scored 360 NiceGuy scored 360
The non co-operative players have multiplied like rabbits and the few remaining co-operators are having a perfectly miserable time of it.
But notice that there are so few co-operators left to take advantage of that the Black Hats are now scoring LESS than the co-operators did when they were the only players.
Tit For Tat
Someone who initially co-operates, and then returns whatever the other did last time, tit for tat
class cPlayerTitForTat : cPlayerBase { public: cPlayerTitForTat() : cPlayerBase( L"TitForTat" ) {} bool Response( int MyRole, const cHistory& history ) { if( ! history.size() ) return true; return history.GetPartnerLastRespone( MyRole ); } cPlayerBase * Reproduce() { return new cPlayerTitForTat; } }; <verbatim> Start with an even mixture of all the different players <verbatim> ecology.Add( (cPlayerBase *)new cPlayerRandom() ); ecology.Add((cPlayerBase *)new cPlayerBlackHat() ); ecology.Add((cPlayerBase *)new cPlayerTitForTat() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() ); ecology.Add( (cPlayerBase *)new cPlayerRandom() ); ecology.Add((cPlayerBase *)new cPlayerBlackHat() ); ecology.Add((cPlayerBase *)new cPlayerTitForTat() ); ecology.Add((cPlayerBase *)new cPlayerNiceGuy() );
The results after a few generations
TitForTat scored 650 TitForTat scored 650 TitForTat scored 650 BlackHat scored 444 BlackHat scored 444 BlackHat scored 444 BlackHat scored 444
The naive co-operators go quickly extinct, but the tit-for-tats are easily dominating the black hats. A few more generations and the Black hats will be exinct and the tit-for-tats will settle into a pure co-operative strategy, scoring as much as the naive co-operators ever did.
It is fun to try different combinations of starting populations and try to come up with a strategy that does better than tit-for-tat in a mixed population.
To do this, you will need a basic understanding of C++ programming and the code you can download from this repository.
A very interesting article, so true. However in a real world situations, it is hardly ever apparent who is a black hat, so task for TitForTat becomes difficult.
There is no need to recognize a black hat in the real world. TitForTat co-operates with everyone at first. If betrayed, TitForTat will then also betray. For this strategy to succeed, all that is needed is for the player to recognize previous opponents, and remember how they behaved before.