-
Notifications
You must be signed in to change notification settings - Fork 271
/
Copy pathtitfortat.py
986 lines (788 loc) · 27.7 KB
/
titfortat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
from axelrod.action import Action, actions_to_str
from axelrod.player import Player
import statistics
from axelrod.strategy_transformers import (
FinalTransformer,
TrackHistoryTransformer,
)
C, D = Action.C, Action.D
class TitForTat(Player):
"""
A player starts by cooperating and then mimics the previous action of the
opponent.
This strategy was referred to as the *'simplest'* strategy submitted to
Axelrod's first tournament. It came first.
Note that the code for this strategy is written in a fairly verbose
way. This is done so that it can serve as an example strategy for
those who might be new to Python.
Names:
- Rapoport's strategy: [Axelrod1980]_
- TitForTat: [Axelrod1980]_
"""
# These are various properties for the strategy
name = "Tit For Tat"
classifier = {
"memory_depth": 1, # Four-Vector = (1.,0.,1.,0.)
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent: Player) -> Action:
"""This is the actual strategy"""
# First move
if not self.history:
return C
# React to the opponent's last move
if opponent.history[-1] == D:
return D
return C
class TitFor2Tats(Player):
"""A player starts by cooperating and then defects only after two defects by
opponent.
Submitted to Axelrod's second tournament by John Maynard Smith; it came in
24th in that tournament.
Names:
- Tit for two Tats: [Axelrod1984]_
- Slow tit for two tats: Original name by Ranjini Das
- JMaynardSmith: [Axelrod1980b]_
"""
name = "Tit For 2 Tats"
classifier = {
"memory_depth": 2, # Long memory, memory-2
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
return D if opponent.history[-2:] == [D, D] else C
class TwoTitsForTat(Player):
"""A player starts by cooperating and replies to each defect by two
defections.
Names:
- Two Tits for Tats: [Axelrod1984]_
"""
name = "Two Tits For Tat"
classifier = {
"memory_depth": 2, # Long memory, memory-2
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
return D if D in opponent.history[-2:] else C
class DynamicTwoTitsForTat(Player):
"""
A player starts by cooperating and then punishes its opponent's
defections with defections, but with a dynamic bias towards cooperating
based on the opponent's ratio of cooperations to total moves
(so their current probability of cooperating regardless of the
opponent's move (aka: forgiveness)).
Names:
- Dynamic Two Tits For Tat: Original name by Grant Garrett-Grossman.
"""
name = "Dynamic Two Tits For Tat"
classifier = {
"memory_depth": float("inf"),
"stochastic": True,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent):
"""Actual strategy definition that determines player's action."""
# First move
if not opponent.history:
# Make sure we cooperate first turn
return C
if D in opponent.history[-2:]:
# Probability of cooperating regardless
return self._random.random_choice(
opponent.cooperations / len(opponent.history)
)
else:
return C
class Bully(Player):
"""A player that behaves opposite to Tit For Tat, including first move.
Starts by defecting and then does the opposite of opponent's previous move.
This is the complete opposite of Tit For Tat, also called Bully in the
literature.
Names:
- Reverse Tit For Tat: [Nachbar1992]_
"""
name = "Bully"
classifier = {
"memory_depth": 1, # Four-Vector = (0, 1, 0, 1)
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
return C if opponent.history[-1:] == [D] else D
class SneakyTitForTat(Player):
"""Tries defecting once and repents if punished.
Names:
- Sneaky Tit For Tat: Original name by Karol Langner
"""
name = "Sneaky Tit For Tat"
classifier = {
"memory_depth": float("inf"), # Long memory
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if len(self.history) < 2:
return C
if D not in opponent.history:
return D
if opponent.history[-1] == D and self.history[-2] == D:
return C
return opponent.history[-1]
class SuspiciousTitForTat(Player):
"""A variant of Tit For Tat that starts off with a defection.
Names:
- Suspicious Tit For Tat: [Hilbe2013]_
- Mistrust: [Beaufils1997]_
"""
name = "Suspicious Tit For Tat"
classifier = {
"memory_depth": 1, # Four-Vector = (1.,0.,1.,0.)
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
return C if opponent.history[-1:] == [C] else D
class AntiTitForTat(Player):
"""A strategy that plays the opposite of the opponents previous move.
This is similar to Bully, except that the first move is cooperation.
Names:
- Anti Tit For Tat: [Hilbe2013]_
- Psycho (PSYC): [Ashlock2009]_
"""
name = "Anti Tit For Tat"
classifier = {
"memory_depth": 1, # Four-Vector = (1.,0.,1.,0.)
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
return D if opponent.history[-1:] == [C] else C
class HardTitForTat(Player):
"""A variant of Tit For Tat that uses a longer history for retaliation.
Names:
- Hard Tit For Tat: [PD2017]_
"""
name = "Hard Tit For Tat"
classifier = {
"memory_depth": 3, # memory-three
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
# Cooperate on the first move
if not opponent.history:
return C
# Defects if D in the opponent's last three moves
if D in opponent.history[-3:]:
return D
# Otherwise cooperates
return C
class HardTitFor2Tats(Player):
"""A variant of Tit For Two Tats that uses a longer history for
retaliation.
Names:
- Hard Tit For Two Tats: [Stewart2012]_
"""
name = "Hard Tit For 2 Tats"
classifier = {
"memory_depth": 3, # memory-three
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
@staticmethod
def strategy(opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
# Cooperate on the first move
if not opponent.history:
return C
# Defects if two consecutive D in the opponent's last three moves
history_string = actions_to_str(opponent.history[-3:])
if "DD" in history_string:
return D
# Otherwise cooperates
return C
class OmegaTFT(Player):
"""OmegaTFT modifies Tit For Tat in two ways:
- checks for deadlock loops of alternating rounds of (C, D) and (D, C),
and attempting to break them
- uses a more sophisticated retaliation mechanism that is noise tolerant
Names:
- OmegaTFT: [Slany2007]_
"""
name = "Omega TFT"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(
self, deadlock_threshold: int = 3, randomness_threshold: int = 8
) -> None:
super().__init__()
self.deadlock_threshold = deadlock_threshold
self.randomness_threshold = randomness_threshold
self.randomness_counter = 0
self.deadlock_counter = 0
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
# Cooperate on the first move
if not self.history:
return C
# TFT on round 2
if len(self.history) == 1:
return opponent.history[-1]
# Are we deadlocked? (in a CD -> DC loop)
if self.deadlock_counter >= self.deadlock_threshold:
move = C
if self.deadlock_counter == self.deadlock_threshold:
self.deadlock_counter = self.deadlock_threshold + 1
else:
self.deadlock_counter = 0
else:
# Update counters
if opponent.history[-2:] == [C, C]:
self.randomness_counter -= 1
# If the opponent's move changed, increase the counter
if opponent.history[-2] != opponent.history[-1]:
self.randomness_counter += 1
# If the opponent's last move differed from mine,
# increase the counter
if self.history[-1] != opponent.history[-1]:
self.randomness_counter += 1
# Compare counts to thresholds
# If randomness_counter exceeds Y, Defect for the remainder
if self.randomness_counter >= self.randomness_threshold:
move = D
else:
# TFT
move = opponent.history[-1]
# Check for deadlock
if opponent.history[-2] != opponent.history[-1]:
self.deadlock_counter += 1
else:
self.deadlock_counter = 0
return move
class OriginalGradual(Player):
"""
A player that punishes defections with a growing number of defections
but after punishing for `punishment_limit` number of times enters a calming
state and cooperates no matter what the opponent does for two rounds.
The `punishment_limit` is incremented whenever the opponent defects and the
strategy is not in either calming or punishing state.
Note that `Gradual` appears in [CRISTAL-SMAC2018]_ however that version of
`Gradual` does not give the results reported in [Beaufils1997]_ which is the
paper that first introduced the strategy. For a longer discussion of this
see: https://github.com/Axelrod-Python/Axelrod/issues/1294. This is why this
strategy has been renamed to `OriginalGradual`.
Names:
- Gradual: [Beaufils1997]_
"""
name = "Original Gradual"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self) -> None:
super().__init__()
self.calming = False
self.punishing = False
self.punishment_count = 0
self.punishment_limit = 0
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if self.calming:
self.calming = False
return C
if self.punishing:
if self.punishment_count < self.punishment_limit:
self.punishment_count += 1
return D
else:
self.calming = True
self.punishing = False
self.punishment_count = 0
return C
if D in opponent.history[-1:]:
self.punishing = True
self.punishment_count += 1
self.punishment_limit += 1
return D
return C
class Gradual(Player):
"""
Similar to OriginalGradual, this is a player that punishes defections with a
growing number of defections but after punishing for `punishment_limit`
number of times enters a calming state and cooperates no matter what the
opponent does for two rounds.
This version of Gradual is an update of `OriginalGradual` and the difference
is that the `punishment_limit` is incremented whenever the opponent defects
(regardless of the state of the player).
Note that this version of `Gradual` appears in [CRISTAL-SMAC2018]_ however
this version of
`Gradual` does not give the results reported in [Beaufils1997]_ which is the
paper that first introduced the strategy. For a longer discussion of this
see: https://github.com/Axelrod-Python/Axelrod/issues/1294.
This version is based on https://github.com/cristal-smac/ipd/blob/master/src/strategies.py#L224
Names:
- Gradual: [CRISTAL-SMAC2018]_
"""
name = "Gradual"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self) -> None:
super().__init__()
self.calm_count = 0
self.punish_count = 0
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if len(self.history) == 0:
return C
if self.punish_count > 0:
self.punish_count -= 1
return D
if self.calm_count > 0:
self.calm_count -= 1
return C
if opponent.history[-1] == D:
self.punish_count = opponent.defections - 1
self.calm_count = 2
return D
return C
@TrackHistoryTransformer(name_prefix=None)
class ContriteTitForTat(Player):
"""
A player that corresponds to Tit For Tat if there is no noise. In the case
of a noisy match: if the opponent defects as a result of a noisy defection
then ContriteTitForTat will become 'contrite' until it successfully
cooperates.
Names:
- Contrite Tit For Tat: [Axelrod1995]_
"""
name = "Contrite Tit For Tat"
classifier = {
"memory_depth": 3,
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self):
super().__init__()
self.contrite = False
self._recorded_history = []
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if not opponent.history:
return C
# If contrite but managed to cooperate: apologise.
if self.contrite and self.history[-1] == C:
self.contrite = False
return C
# Check if noise provoked opponent
if self._recorded_history[-1] != self.history[-1]: # Check if noise
if self.history[-1] == D and opponent.history[-1] == C:
self.contrite = True
return opponent.history[-1]
class AdaptiveTitForTat(Player):
"""ATFT - Adaptive Tit For Tat (Basic Model)
Algorithm
if (opponent played C in the last cycle) then
world = world + r*(1-world)
else
world = world + r*(0-world)
If (world >= 0.5) play C, else play D
Attributes
world : float [0.0, 1.0], set to 0.5
continuous variable representing the world's image
1.0 - total cooperation
0.0 - total defection
other values - something in between of the above
updated every round, starting value shouldn't matter as long as
it's >= 0.5
Parameters
rate : float [0.0, 1.0], default=0.5
adaptation rate - r in Algorithm above
smaller value means more gradual and robust
to perturbations behaviour
Names:
- Adaptive Tit For Tat: [Tzafestas2000]_
"""
name = "Adaptive Tit For Tat"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
world = 0.5
def __init__(self, rate: float = 0.5) -> None:
super().__init__()
self.rate = rate
self.world = rate
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if len(opponent.history) == 0:
return C
if opponent.history[-1] == C:
self.world += self.rate * (1.0 - self.world)
else:
self.world -= self.rate * self.world
if self.world >= 0.5:
return C
return D
class SpitefulTitForTat(Player):
"""
A player starts by cooperating and then mimics the previous action of the
opponent until opponent defects twice in a row, at which point player
always defects
Names:
- Spiteful Tit For Tat: [Prison1998]_
"""
name = "Spiteful Tit For Tat"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self) -> None:
super().__init__()
self.retaliating = False
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
# First move
if not self.history:
return C
if opponent.history[-2:] == [D, D]:
self.retaliating = True
if self.retaliating:
return D
else:
# React to the opponent's last move
if opponent.history[-1] == D:
return D
return C
class SlowTitForTwoTats2(Player):
"""
A player plays C twice, then if the opponent plays the same move twice,
plays that move, otherwise plays previous move.
Names:
- Slow Tit For Tat: [Prison1998]_
"""
name = "Slow Tit For Two Tats 2"
classifier = {
"memory_depth": 2,
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
# Start with two cooperations
if len(self.history) < 2:
return C
# Mimic if opponent plays the same move twice
if opponent.history[-2] == opponent.history[-1]:
return opponent.history[-1]
# Otherwise play previous move
return self.history[-1]
@FinalTransformer((D,), name_prefix=None)
class Alexei(Player):
"""
Plays similar to Tit-for-Tat, but always defect on last turn.
Names:
- Alexei: [LessWrong2011]_
"""
name = "Alexei"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if not self.history:
return C
if opponent.history[-1] == D:
return D
return C
@FinalTransformer((D,), name_prefix=None)
class EugineNier(Player):
"""
Plays similar to Tit-for-Tat, but with two conditions:
1) Always Defect on Last Move
2) If other player defects five times, switch to all defects.
Names:
- Eugine Nier: [LessWrong2011]_
"""
name = "EugineNier"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self):
super().__init__()
self.is_defector = False
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if not self.history:
return C
if not (self.is_defector) and opponent.defections >= 5:
self.is_defector = True
if self.is_defector:
return D
return opponent.history[-1]
class NTitsForMTats(Player):
"""
A parameterizable Tit-for-Tat,
The arguments are:
1) M: the number of defection before retaliation
2) N: the number of retaliations
Names:
- N Tit(s) For M Tat(s): Original name by Marc Harper
"""
name = "N Tit(s) For M Tat(s)"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self, N: int = 3, M: int = 2) -> None:
"""
Parameters
----------
N: int
Number of retaliations
M: int
Number of defection before retaliation
Special Cases
-------------
NTitsForMTats(1,1) is equivalent to TitForTat
NTitsForMTats(1,2) is equivalent to TitFor2Tats
NTitsForMTats(2,1) is equivalent to TwoTitsForTat
NTitsForMTats(0,*) is equivalent to Cooperator
NTitsForMTats(*,0) is equivalent to Defector
"""
super().__init__()
self.N = N
self.M = M
self.classifier["memory_depth"] = max([M, N])
self.retaliate_count = 0
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
# if opponent defected consecutively M times, start the retaliation
if not self.M or opponent.history[-self.M :].count(D) == self.M:
self.retaliate_count = self.N
if self.retaliate_count:
self.retaliate_count -= 1
return D
return C
@FinalTransformer((D,), name_prefix=None)
class Michaelos(Player):
"""
Plays similar to Tit-for-Tat with two exceptions:
1) Defect on last turn.
2) After own defection and opponent's cooperation, 50 percent of the time,
cooperate. The other 50 percent of the time, always defect for the rest of
the game.
Names:
- Michaelos: [LessWrong2011]_
"""
name = "Michaelos"
classifier = {
"memory_depth": float("inf"),
"stochastic": True,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self):
super().__init__()
self.is_defector = False
def strategy(self, opponent: Player) -> Action:
"""Actual strategy definition that determines player's action."""
if not self.history:
return C
if self.is_defector:
return D
if self.history[-1] == D and opponent.history[-1] == C:
decision = self._random.random_choice()
if decision == C:
return C
else:
self.is_defector = True
return D
return opponent.history[-1]
class RandomTitForTat(Player):
"""
A player starts by cooperating and then follows by copying its
opponent (tit-for-tat style). From then on the player
will switch between copying its opponent and randomly
responding every other iteration.
Name:
- Random TitForTat: Original name by Zachary M. Taylor
"""
# These are various properties for the strategy
name = "Random Tit for Tat"
classifier = {
"memory_depth": 1,
"stochastic": True,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def __init__(self, p: float = 0.5) -> None:
"""
Parameters
----------
p, float
The probability to cooperate
"""
super().__init__()
self.p = p
self.act_random = False
if p in [0, 1]:
self.classifier["stochastic"] = False
def strategy(self, opponent: Player) -> Action:
"""This is the actual strategy"""
if not self.history:
return C
if self.act_random:
self.act_random = False
try:
return self._random.random_choice(self.p)
except AttributeError:
return D if self.p == 0 else C
self.act_random = True
return opponent.history[-1]
class BurnBothEnds(Player):
"""
Plays like TitForTat except it cooperates after opponent cooperation with probability 9/10.
Names:
- BurnBothEnds (BBE): Marinoff [Marinoff1992]_
"""
name = "Burn Both Ends"
classifier = {
"memory_depth": 1,
"stochastic": True,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent):
# First move
if not opponent.history:
# Make sure we cooperate first turn
return C
# BBE modification
if opponent.history[-1] == C:
# Cooperate with 0.9
return self._random.random_choice(0.9)
# Else TFT. Opponent played D, so play D in return.
return D
class ModalTFT(Player):
"""
A player starts by cooperating and then analyses the history of the opponent. If the opponent Cooperated in the
last round, they are returned with a Cooperation. If the opponent chose to Defect in the previous round,
then this strategy will return with the mode of the previous opponent responses.
"""
# These are various properties for the strategy
name = "Modal TFT"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}
def strategy(self, opponent: Player) -> Action:
"""This is the actual strategy"""
# First move
if not self.history:
return C
# React to the opponent's historical moves
if opponent.history[-1] == C:
return C
else:
# returns with the mode of the opponent's history.
return statistics.mode(opponent.history)