Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented WmAdams strategy (k44r from Axelrod's second) #1142

Merged
merged 5 commits into from
Nov 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
UnnamedStrategy, SteinAndRapoport, TidemanAndChieruzzi)
from .axelrod_second import (
Champion, Eatherley, Tester, Gladstein, Tranquilizer, MoreGrofman,
Kluepfel, Borufsen, Cave)
Kluepfel, Borufsen, Cave, WmAdams)
from .backstabber import BackStabber, DoubleCrosser
from .better_and_better import BetterAndBetter
from .bush_mosteller import BushMosteller
Expand Down Expand Up @@ -274,6 +274,7 @@
Winner21,
WinShiftLoseStay,
WinStayLoseShift,
WmAdams,
WorseAndWorse,
WorseAndWorse2,
WorseAndWorse3,
Expand Down
44 changes: 41 additions & 3 deletions axelrod/strategies/axelrod_second.py
Original file line number Diff line number Diff line change
Expand Up @@ -755,9 +755,9 @@ def strategy(self, opponent: Player) -> Action:
turn = len(self.history) + 1
if turn == 1: return C

did_d = np.vectorize(lambda action: float(action == D))
number_defects = np.sum(did_d(opponent.history))
perc_defects = number_defects / turn # Size of numerator is smaller than denomator -- How it was in the Fortran.
number_defects = opponent.defections
# Size of numerator is smaller than denomator -- How it was in the Fortran.
perc_defects = number_defects / turn

# If overly defect or appears random
if turn > 39 and perc_defects > 0.39: return D
Expand All @@ -771,3 +771,41 @@ def strategy(self, opponent: Player) -> Action:
return random_choice(0.5)
else:
return C

class WmAdams(Player):
"""
Strategy submitted to Axelrod's second tournament by William Adams (K44R),
and came in fifth in that tournament.

Count the number of opponent defections after their first move, call
`c_defect`. Defect if c_defect equals 4, 7, or 9. If c_defect > 9,
then defect immediately after opponent defects with probability =
(0.5)^(c_defect-1). Otherwise cooperate.

Names:

- WmAdams: [Axelrod1980b]_
"""

name = "WmAdams"
classifier = {
'memory_depth': float('inf'),
'stochastic': True,
'makes_use_of': set(),
'long_run_time': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def strategy(self, opponent: Player) -> Action:
if len(self.history) <= 1:
return C
number_defects = opponent.defections
if opponent.history[0] == D:
number_defects -= 1

if number_defects in [4, 7, 9]: return D
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gaffney2010 can you confirm which strategy this is? Looking at /~https://github.com/Axelrod-Python/TourExec/blob/master/src/strategies/k44r.f I don't immediately follow how this implementation corresponds to k44r?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can confirm that it's k44r. I'll say more about the translation...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I confirm I get the same fingerprints :)

I think you've just done a good job refactoring and re writing the code to be much more readable, just got me scratching my head to follow along, so one or two pointers for my own benefit just to confirm would be awesome (even if it's not in the PR itself but just in a comment here).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ones I'm particularly stumped by are the comparisons of number_defects to 4, 7 and 9 and also where number_defects - 9 comes from...

Copy link
Member Author

@gaffney2010 gaffney2010 Nov 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically every time a defect comes up, it increments this MC variable. While AM is still a whole number, then MC is either less than AM or equal to AM. [While less than, line 16 returns C.] While MC=AM it defects; this happens for the first time at 4 defects [line 11]. As soon as MC exceeds AM, then AM is halved, and MC is reset to zero. [The 5th defect resets MC and sets AM to 2. So the 7th defect gets MC=AM. Then the 8th defect resets MC and sets AM to 1. So the 9th defect gets MC=AM]

After the 9th defect, the function changes because the AM is no longer whole, so MC never again equal AM (and line 17 is out of play). So as soon as MC is incremented (any defect), lines 19-22 are triggered. [Line 22 was 100% until now.] And MC is immediately reset, so there's no sustained defecting, like there was earlier. [For example, there could be exactly 4 defects for many turns, and the strategy would defect for all those turns. But after the 100th defect, the strategy will defect (with some probability) ONLY once.]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's awesome and make sense. Thanks for explaining.

Copy link
Member Author

@gaffney2010 gaffney2010 Nov 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

number_defects - 9 just makes the AM follow the right halving pattern. AM starts at 4.0 and halves after the 4th and 7th, for AM=1.0. After the 9th defect EACH defect will trigger a halving.

if number_defects > 9 and opponent.history[-1] == D:
return random_choice((0.5) ** (number_defects - 9))
return C
82 changes: 64 additions & 18 deletions axelrod/tests/strategies/test_axelrod_second.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,14 +442,15 @@ def test_strategy(self):

# Now we have to test the detect-random logic, which doesn't pick up
# until after 26 turns. So we need a big sample.
actions = [(C, D), (D, D), (D, D), (D, D), (D, D), (D, C), (C, C), (C, D),
(C, C), (D, D), (D, C), (C, C), (C, D), (D, D), (C, D), (D, D),
(D, C), (C, C), (D, C), (C, C), (C, D), (D, D), (D, C), (C, D),
(D, C), (C, C), (C, D),
actions = [(C, D), (D, D), (D, D), (D, D), (D, D), (D, C), (C, C),
(C, D), (C, C), (D, D), (D, C), (C, C), (C, D), (D, D),
(C, D), (D, D), (D, C), (C, C), (D, C), (C, C), (C, D),
(D, D), (D, C), (C, D), (D, C), (C, C), (C, D),
# Success detect random opponent for remaining turns.
(D, D),(D, D),(D, D),(D, C),(D, D),(D, C),(D, D),(D, C),(D, D),
(D, C),(D, C),(D, D),(D, D),(D, C),(D, C),(D, C),(D, C),(D, D),
(D, C),(D, C),(D, C),(D, C),(D, D)]
(D, D), (D, D), (D, D), (D, C), (D, D), (D, C), (D, D),
(D, C), (D, D), (D, C), (D, C), (D, D), (D, D), (D, C),
(D, C), (D, C), (D, C), (D, D), (D, C), (D, C), (D, C),
(D, C), (D, D)]
self.versus_test(axelrod.Random(0.5), expected_actions=actions, seed=10)

class TestBorufsen(TestPlayer):
Expand All @@ -475,16 +476,21 @@ def test_strategy(self):
self.versus_test(axelrod.Defector(), expected_actions=actions)

# Alternates with additional coop, every sixth turn
# Won't be labeled as random, since 2/3 of opponent's C follow player's C
# `flip_next_defect` will get set on the sixth turn, which changes the seventh action
# Won't be labeled as random, since 2/3 of opponent's C follow
# player's C
# `flip_next_defect` will get set on the sixth turn, which changes the
# seventh action
# Note that the first two turns of each period of six aren't
# marked as echoes, and the third isn't marked that way until the fourth turn.
# marked as echoes, and the third isn't marked that way until the
# fourth turn.
actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D)] * 20
self.versus_test(axelrod.Alternator(), expected_actions=actions)

# Basically does tit-for-tat against Win-Shift, Lose-Stay D
# After 26 turns, will detect random since half of opponent's C follow Cs
# Coming out of it, there will be new pattern. Then random is detected again.
# After 26 turns, will detect random since half of opponent's C follow
# Cs
# Coming out of it, there will be new pattern. Then random is detected
# again.
actions = [(C, D), (D, C), (C, C)] * 8 + \
[(C, D), (D, C)] + [(D, C)] * 25 + \
[(D, C)] + [(C, C), (C, D), (D, C)] * 8 + \
Expand All @@ -505,7 +511,7 @@ class TestCave(TestPlayer):
}

def test_strategy(self):
actions = [(C, C)] * 100
actions = [(C, C)] * 100
self.versus_test(axelrod.Cooperator(), expected_actions=actions)

# It will take until turn 18 to respond decide to repond D->D
Expand All @@ -531,11 +537,51 @@ def test_strategy(self):

#Here it will take until turn 40 to detect random and defect
actions = [(C, C)]
actions += [(C, D), (D, C), (C, D), (D, C), (C, D), (C, C), (C, D),
(C, C), (C, D), (D, C), (C, D), (D, C), (C, D), (D, C),
(C, D), (C, C), (C, D), (D, C), (C, D), (D, C), (C, D),
(D, C), (C, D), (C, C), (C, D), (C, C), (C, D), (C, C),
(C, D), (D, C), (C, D), (D, C), (C, D), (D, C), (C, D)] #Randomly choose
actions += [(C, D), (D, C), (C, D), (D, C), (C, D), (C, C), (C, D),
(C, C), (C, D), (D, C), (C, D), (D, C), (C, D), (D, C),
(C, D), (C, C), (C, D), (D, C), (C, D), (D, C), (C, D),
(D, C), (C, D), (C, C), (C, D), (C, C), (C, D), (C, C),
(C, D), (D, C), (C, D), (D, C), (C, D), (D, C), (C, D)] #Randomly choose
actions += [(D, C), (C, D), (D, C)] # 17 D have come, so tit for tat for a while
actions += [(D, D), (D, C)] * 100 # Random finally detected
self.versus_test(axelrod.Alternator(), expected_actions=actions, seed=2)

class TestWmAdams(TestPlayer):
name = "WmAdams"
player = axelrod.WmAdams
expected_classifier = {
'memory_depth': float('inf'),
'stochastic': True,
'makes_use_of': set(),
'long_run_time': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def test_strategy(self):
actions = [(C, C)] * 100 # Cooperate forever
self.versus_test(axelrod.Cooperator(), expected_actions=actions)

# Will ignore the first four defects
opponent_actions = [D] * 4 + [C] * 100
defect_four = axelrod.MockPlayer(actions=opponent_actions)
actions = [(C, D)] * 4 + [(C, C)] * 100
self.versus_test(defect_four, expected_actions=actions)

actions = [(C, D), (C, D), (C, D), (C, D), (C, D), (D, D), (C, D),
(C, D), (D, D), (C, D), (D, D), (C, D), (D, D), (D, D),
(D, D), (D, D)]
self.versus_test(axelrod.Defector(), expected_actions=actions, seed=1)
actions = [(C, D), (C, D), (C, D), (C, D), (C, D), (D, D), (C, D),
(C, D), (D, D), (C, D), (D, D), (D, D), (D, D), (C, D),
(D, D), (D, D)]
self.versus_test(axelrod.Defector(), expected_actions=actions, seed=2)

# After responding to the 11th D (counted as 10 D), just start cooperating
opponent_actions = [D] * 11 + [C] * 100
changed_man = axelrod.MockPlayer(actions=opponent_actions)
actions = [(C, D), (C, D), (C, D), (C, D), (C, D), (D, D), (C, D),
(C, D), (D, D), (C, D), (D, D), (C, C)]
actions += [(C, C)] * 99
self.versus_test(changed_man, expected_actions=actions, seed=1)
2 changes: 1 addition & 1 deletion docs/reference/overview_of_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ repository.
"K41R_", "Herb Weiner", "Not Implemented"
"K42R_", "Otto Borufsen", ":class:`Borufsen <axelrod.strategies.axelrod_second.Borufsen>`"
"K43R_", "R D Anderson", "Not Implemented"
"K44R_", "William Adams", "Not Implemented"
"K44R_", "William Adams", ":class:`WmAdams <axelrod.strategies.axelrod_second.WmAdams>`"
"K45R_", "Michael F McGurrin", "Not Implemented"
"K46R_", "Graham J Eatherley", ":class:`Eatherley <axelrod.strategies.axelrod_second.Eatherley>`"
"K47R_", "Richard Hufford", "Not Implemented"
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/advanced/classification_of_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ strategies::
... }
>>> strategies = axl.filtered_strategies(filterset)
>>> len(strategies)
77
78

Or, to find out how many strategies only use 1 turn worth of memory to
make a decision::
Expand Down