Skip to content

Commit 5bf2d69

Browse files
committed
Edited and added comments/docstrings.
1 parent a8db7da commit 5bf2d69

File tree

1 file changed

+76
-45
lines changed

1 file changed

+76
-45
lines changed

axelrod/strategies/axelrod_second.py

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -988,18 +988,16 @@ class Harrington(Player):
988988
In Normal and Fair-weather modes, the strategy begins by:
989989
990990
- Update history
991-
- Detects random if turn is multiple of 15 and >=30.
991+
- Try to detect random opponent if turn is multiple of 15 and >=30.
992992
- Check if `burned` flag should be raised.
993993
- Check for Fair-weather opponent if turn is 38.
994994
995995
Updating history means to increment the correct cell of the `move_history`.
996996
`move_history` is a matrix where the columns are the opponent's previous
997-
move and rows are indexed by the combo of this player and the opponent's
998-
moves two turns ago*. [The upper-left cell must be all cooperations, but
999-
otherwise order doesn't matter.] * If the player is exiting Defect mode,
1000-
then the history to determine the row is taken from before the turn that
1001-
the player entered Defect mode. (That is, the turn that started in Normal
1002-
mode, but ended in Defect mode.)
997+
move and the rows are indexed by the combo of this player's and the
998+
opponent's moves two turns ago. [The upper-left cell must be all
999+
Cooperations, but otherwise order doesn't matter.] After we enter Defect
1000+
mode, `move_history` won't be used again.
10031001
10041002
If the turn is a multiple of 15 and >=30, then attempt to detect random.
10051003
If random is detected, enter Defect mode and defect immediately. If the
@@ -1046,10 +1044,10 @@ class Harrington(Player):
10461044
parity streak that we're pointing to. If the parity streak that we're
10471045
pointing to is then greater than `parity_limit` then reset the streak and
10481046
cooperate immediately. `parity_limit` is initially set to five, but after
1049-
its been hit eight times, it decreases to three. The parity streak that
1050-
we're pointing to also gets incremented if in normal mode and WE defect but
1051-
not on turn 38, unless the result of a defect streak. Note that the parity
1052-
streaks reset but the defect streak doesn't.
1047+
it has been hit eight times, it decreases to three. The parity streak that
1048+
we're pointing to also gets incremented if in normal mode and we defect but
1049+
not on turn 38, unless we are defecting as the result of a defect streak.
1050+
Note that the parity streaks resets but the defect streak doesn't.
10531051
10541052
If `more_coop` >= 1, then we cooperate and lower that flag here, in Normal
10551053
mode after checking streaks. Still lower this flag if cooperating as the
@@ -1086,33 +1084,46 @@ class Harrington(Player):
10861084
def __init__(self):
10871085
super().__init__()
10881086
self.mode = "Normal"
1089-
self.recorded_defects = 0
1090-
self.exit_defect_meter = 0
1091-
self.coops_in_first_36 = None
1092-
self.was_defective = False
1087+
self.recorded_defects = 0 # Count opponent defects after turn 1
1088+
self.exit_defect_meter = 0 # When >= 11, then exit defect mode.
1089+
self.coops_in_first_36 = None # On turn 37, count cooperations in first 36
1090+
self.was_defective = False # Previously in Defect mode
10931091

1094-
self.prob = 0.25
1092+
self.prob = 0.25 # After turn 37, probability that we'll defect
10951093

10961094
self.move_history = np.zeros([4, 2])
1095+
# Will cache value only for testing purposes, not used otherwise
10971096
self.chi_squared = None
10981097
self.history_row = 0
10991098

1100-
self.more_coop = 0
1101-
self.generous_n_turns_ago = 3
1099+
self.more_coop = 0 # This schedules cooperation for future turns
1100+
# Initial last_generous_n_turns_ago to 3 because this counts up and
1101+
# triggers a strategy change at 2.
1102+
self.last_generous_n_turns_ago = 3 # How many tuns ago was a "generous" move
11021103
self.burned = False
11031104

11041105
self.defect_streak = 0
1105-
self.parity_streak = [0, 0]
1106-
self.parity_bit = 0
1107-
self.parity_limit = 5
1108-
self.parity_hits = 0
1106+
self.parity_streak = [0, 0] # Counters that get (almost) alternatively incremented.
1107+
self.parity_bit = 0 # Which parity_streak to increment
1108+
self.parity_limit = 5 # When a parity streak hits this limit, alter strategy.
1109+
self.parity_hits = 0 # Counts how many times a parity_limit was hit.
1110+
# After hitting parity_hits 8 times, lower parity_limit to 3.
11091111

11101112
def try_return(self, to_return, lower_flags=True, inc_parity=False):
1113+
"""
1114+
This will return to_return, with some end-of-turn logic.
1115+
"""
1116+
11111117
if lower_flags and to_return == C:
1118+
# In most cases when Cooperating, we want to reduce the number that
1119+
# are scheduled.
11121120
self.more_coop -= 1
1113-
self.generous_n_turns_ago += 1
1121+
self.last_generous_n_turns_ago += 1
11141122

11151123
if inc_parity and to_return == D:
1124+
# In some cases we increment the `parity_streak` that we're on when
1125+
# we return a Defection. In detect_parity_streak, `parity_streak`
1126+
# counts opponent's Defections.
11161127
self.parity_streak[self.parity_bit] += 1
11171128

11181129
return to_return
@@ -1131,9 +1142,6 @@ def detect_random(self, turn):
11311142
We say this is modified because it differs from a usual Chi-Squared
11321143
test in that:
11331144
1134-
- It divides by turns minus 2 to get expected, whereas usually we'd
1135-
divide by matrix total. Total equals turns minus 1, unless Defect
1136-
mode has been entered at any point.
11371145
- Terms where expected counts are less than 1 get excluded.
11381146
- There's a check at the beginning on the first cell of the matrix.
11391147
- There's a check at the beginning for the recorded number of defects.
@@ -1146,25 +1154,29 @@ def detect_random(self, turn):
11461154
if self.recorded_defects / denom < 0.25 or self.recorded_defects / denom > 0.75:
11471155
return False
11481156

1149-
expected_matrix = np.outer(self.move_history.sum(axis=1), \
1150-
self.move_history.sum(axis=0))
1157+
# In each cell, we expect (for an independent distribution) the total
1158+
# number of recorded turns times the portion in that row times the
1159+
# portion in that column
1160+
expected_matrix = np.outer(self.move_history.sum(axis=1),
1161+
self.move_history.sum(axis=0)) / denom
11511162

11521163
chi_squared = 0.0
11531164
for i in range(4):
11541165
for j in range(2):
1155-
expct = expected_matrix[i, j] / denom
1156-
if expct > 1.0:
1157-
chi_squared += (expct - self.move_history[i, j]) ** 2 / expct
1166+
expect = expected_matrix[i, j]
1167+
if expect > 1.0:
1168+
chi_squared += (expect - self.move_history[i, j]) ** 2 / expect
11581169

1159-
self.chi_squared = round(chi_squared, 3) # For testing
1170+
# Caching value only for testing purposes, not used otherwise
1171+
self.chi_squared = round(chi_squared, 3)
11601172

11611173
if chi_squared > 3:
11621174
return False
11631175
return True
11641176

11651177
def detect_streak(self, last_move):
11661178
"""
1167-
Return if and only if the opponent's last twenty moves are defects.
1179+
Return true if and only if the opponent's last twenty moves are defects.
11681180
"""
11691181

11701182
if last_move == D:
@@ -1176,7 +1188,17 @@ def detect_streak(self, last_move):
11761188
return False
11771189

11781190
def detect_parity_streak(self, last_move):
1179-
self.parity_bit = 1 - self.parity_bit # Flip bit
1191+
"""
1192+
Switch which `parity_streak` we're pointing to and incerement if the
1193+
opponent's last move was a Defection. Otherwise reset the flag. Then
1194+
return true if and only if the `parity_streak` is at least
1195+
`parity_limit`.
1196+
1197+
This is similar to detect_streak with alternating streaks, except that
1198+
these streaks get incremented elsewhere as well.
1199+
"""
1200+
1201+
self.parity_bit = 1 - self.parity_bit # Flip bit
11801202
if last_move == D:
11811203
self.parity_streak[self.parity_bit] += 1
11821204
else:
@@ -1191,10 +1213,12 @@ def strategy(self, opponent: Player) -> Action:
11911213
return C
11921214

11931215
if self.mode == "Defect":
1216+
# There's a chance to exit Defect mode.
11941217
if opponent.history[-1] == D:
11951218
self.exit_defect_meter += 1
11961219
else:
11971220
self.exit_defect_meter -= 3
1221+
# If opponent has been mostly defecting.
11981222
if self.exit_defect_meter >= 11:
11991223
self.mode = "Normal"
12001224
self.was_defective = True
@@ -1226,19 +1250,21 @@ def strategy(self, opponent: Player) -> Action:
12261250
if self.history[-1] == D:
12271251
self.history_row += 2
12281252

1229-
# If generous 2 turn ago and opponent defected last turn
1230-
if self.generous_n_turns_ago == 2 and opponent.history[-1] == D:
1253+
# If generous 2 turns ago and opponent defected last turn
1254+
if self.last_generous_n_turns_ago == 2 and opponent.history[-1] == D:
12311255
self.burned = True
12321256

1257+
# Only enter Fair-weather mode if the opponent Cooperated the first 37
1258+
# turns then Defected on the 38th.
12331259
if turn == 38 and opponent.history[-1] == D and opponent.cooperations == 36:
12341260
self.mode = "Fair-weather"
12351261
return self.try_return(to_return=C, lower_flags=False)
12361262

12371263

12381264
if self.mode == "Fair-weather":
12391265
if opponent.history[-1] == D:
1240-
self.mode = "Normal" # Post-Defect is not possible
1241-
#Continue below
1266+
self.mode = "Normal" # Post-Defect is not possible
1267+
# Proceed with Normal mode this turn.
12421268
else:
12431269
# Never defect against a fair-weather opponent
12441270
return self.try_return(C)
@@ -1249,23 +1275,28 @@ def strategy(self, opponent: Player) -> Action:
12491275
if self.detect_streak(opponent.history[-1]):
12501276
return self.try_return(D, inc_parity=True)
12511277
if self.detect_parity_streak(opponent.history[-1]):
1252-
self.parity_streak[self.parity_bit] = 0
1253-
self.parity_hits += 1
1254-
if self.parity_hits >= 8:
1278+
self.parity_streak[self.parity_bit] = 0 # Reset `parity_streak` when we hit the limit.
1279+
self.parity_hits += 1 # Keep track of how many times we hit the limit.
1280+
if self.parity_hits >= 8: # After 8 times, lower the limit.
12551281
self.parity_limit = 3
1256-
return self.try_return(C, inc_parity=True) # Inc parity won't get used here.
1282+
return self.try_return(C, inc_parity=True) # Inc parity won't get used here.
12571283

1284+
# If we have Cooperations scheduled, then Cooperate here.
12581285
if self.more_coop >= 1:
1259-
return self.try_return(C, inc_parity=True)
1286+
return self.try_return(C, lower_flags=True, inc_parity=True)
12601287

12611288
if turn < 37:
1289+
# Tit-for-Tat
12621290
return self.try_return(opponent.history[-1], inc_parity=True)
12631291
if turn == 37:
1264-
self.more_coop, self.generous_n_turns_ago = 2, 1
1292+
# Defect once on turn 37 (if no streaks)
1293+
self.more_coop, self.last_generous_n_turns_ago = 2, 1
12651294
return self.try_return(D, lower_flags=False)
12661295
if self.burned or random.random() > self.prob:
1296+
# Tit-for-Tat with probability 1-`prob`
12671297
return self.try_return(opponent.history[-1], inc_parity=True)
12681298
else:
1299+
# Otherwise Defect, Cooperate, Cooperate, and increase `prob`
12691300
self.prob += 0.05
1270-
self.more_coop, self.generous_n_turns_ago = 2, 1
1301+
self.more_coop, self.last_generous_n_turns_ago = 2, 1
12711302
return self.try_return(D, lower_flags=False)

0 commit comments

Comments
 (0)