Skip to content

Commit e6c9662

Browse files
adds test for tree search functions. Adds doctstring for get_siblings and is_stochastic functions. Corrects syntax error when initializing history_by_cond in constructor
1 parent 2a60d7d commit e6c9662

File tree

2 files changed

+221
-16
lines changed

2 files changed

+221
-16
lines changed

axelrod/strategies/dbs.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ def __init__(self, discount_factor=.75, promotion_threshold=3,
109109
# easy and efficient
110110
# initial hypothesized policy is TitForTat
111111
self.history_by_cond = {
112-
[(C, C)] = ([1], [1])
113-
[(C, D)] = ([1], [1])
114-
[(D, C)] = ([0], [1])
115-
[(D, D)] = ([0], [1])
112+
(C, C): ([1], [1]),
113+
(C, D): ([1], [1]),
114+
(D, C): ([0], [1]),
115+
(D, D): ([0], [1])
116116
}
117117

118118
def reset(self):
@@ -123,11 +123,13 @@ def reset(self):
123123
self.Pi = self.Rd # policy used by MoveGen
124124
self.violation_counts = {}
125125
self.v = 0
126-
self.history_by_cond = {}
127-
self.history_by_cond[(C, C)] = ([1], [1])
128-
self.history_by_cond[(C, D)] = ([1], [1])
129-
self.history_by_cond[(D, C)] = ([0], [1])
130-
self.history_by_cond[(D, D)] = ([0], [1])
126+
self.history_by_cond = {
127+
(C, C): ([1], [1]),
128+
(C, D): ([1], [1]),
129+
(D, C): ([0], [1]),
130+
(D, D): ([0], [1])
131+
}
132+
131133

132134
def should_promote(self, r_plus, promotion_threshold=3):
133135
"""
@@ -351,12 +353,20 @@ def __init__(self, own_action, pC, depth):
351353
self.own_action = own_action
352354

353355
def get_siblings(self):
354-
# siblings of a stochastic node get depth += 1
356+
"""
357+
Returns the siblings node of the current StochasticNode
358+
There are two sibling which are DeterministicNodes, their depth
359+
is equal to current node depth's + 1
360+
This function allows to build the tree
361+
"""
355362
opponent_c_choice = DeterministicNode(self.own_action, C, self.depth+1)
356363
opponent_d_choice = DeterministicNode(self.own_action, D, self.depth+1)
357364
return (opponent_c_choice, opponent_d_choice)
358365

359366
def is_stochastic(self):
367+
"""
368+
Returns True if self is a StochasticNode
369+
"""
360370
return True
361371

362372

@@ -373,9 +383,10 @@ def __init__(self, action1, action2, depth):
373383

374384
def get_siblings(self, policy):
375385
"""
376-
build 2 siblings (C, X) and (D, X)
377-
siblings of a DeterministicNode are Stochastic, and are of the
378-
same depth
386+
Returns the siblings node of the current DeterministicNode
387+
Builds 2 siblings (C, X) and (D, X) that are StochasticNodes
388+
Those siblings are of the same depth as the current node
389+
Their probability pC are defined by the policy argument
379390
"""
380391
c_choice = StochasticNode(
381392
C, policy[(self.action1, self.action2)], self.depth
@@ -386,6 +397,9 @@ def get_siblings(self, policy):
386397
return (c_choice, d_choice)
387398

388399
def is_stochastic(self):
400+
"""
401+
Returns True if self is a StochasticNode
402+
"""
389403
return False
390404

391405
def get_value(self):
@@ -404,6 +418,11 @@ def create_policy(pCC, pCD, pDC, pDD):
404418
As defined in the reference, a Policy is a set of (prev_move, p)
405419
where p is the probability to cooperate after prev_move,
406420
where prev_move can be (C, C), (C, D), (D, C) or (D, D)
421+
422+
Parameters
423+
424+
pCC, pCD, pDC, pDD : float
425+
Must be between 0 and 1
407426
"""
408427
return {(C, C): pCC, (C, D): pCD, (D, C): pDC, (D, D): pDD}
409428

@@ -416,9 +435,11 @@ def action_to_int(action):
416435

417436
def minimax_tree_search(begin_node, policy, max_depth):
418437
"""
419-
tree search function (minimax search procedure)
438+
Tree search function (minimax search procedure)
420439
build by recursion the tree corresponding to a game against
421440
opponent's policy, and solve it
441+
Returns a tuple of two float, that are the utility of playing C,
442+
and the utility of playing D
422443
"""
423444
if begin_node.is_stochastic():
424445
# a stochastic node cannot has the same depth than its parent
@@ -459,7 +480,7 @@ def minimax_tree_search(begin_node, policy, max_depth):
459480

460481
def MoveGen(outcome, policy, depth_search_tree=5):
461482
"""
462-
returns the best move considering opponent's policy and last move,
483+
Returns the best move considering opponent's policy and last move,
463484
using tree-search procedure
464485
"""
465486
current_node = DeterministicNode(outcome[0], outcome[1], depth=0)

axelrod/tests/strategies/test_dbs.py

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import axelrod
44
import unittest
55
from .test_player import TestPlayer
6+
from axelrod import dbs
67

78
C, D = axelrod.Actions.C, axelrod.Actions.D
89

@@ -11,7 +12,7 @@ class TestNode(unittest.TestCase):
1112
"""
1213
Test for the base class
1314
"""
14-
node = axelrod.dbs.Node()
15+
node = dbs.Node()
1516

1617
def test_get_siblings(self):
1718
with self.assertRaises(NotImplementedError) as context:
@@ -22,6 +23,189 @@ def test_is_stochastic(self):
2223
self.node.is_stochastic()
2324

2425

26+
class TestTreeSearch(unittest.TestCase):
27+
"""
28+
A set of tests for the tree-search functions.
29+
We test the answers of both minimax_tree_search and MoveGen
30+
functions, against a set of classic policies (the answer being the
31+
best move to play for the next turn, considering an income
32+
position (C, C), (C, D), (D, C) or (D, D))
33+
For each policy, we test the answer for all income position
34+
"""
35+
def setUp(self):
36+
"""
37+
Initialization for tests.
38+
"""
39+
# For each test, we check the answer againt each possible
40+
# inputs, that are in self.input_pos
41+
self.input_pos = [(C, C), (C, D), (D, C), (D, D)]
42+
# We define the policies against which we are going to test
43+
self.cooperator_policy = dbs.create_policy(1, 1, 1, 1)
44+
self.defector_policy = dbs.create_policy(0, 0, 0, 0)
45+
self.titForTat_policy = dbs.create_policy(1, 1, 0, 0)
46+
self.alternator_policy = dbs.create_policy(0, 1, 0, 1)
47+
self.grudger_policy = dbs.create_policy(1, 0, 0, 0)
48+
self.random_policy = dbs.create_policy(.5, .5, .5, .5)
49+
50+
def test_minimaxTreeSearch_cooperator(self):
51+
"""
52+
Tests the minimax_tree_search function when playing against a
53+
Cooperator player.
54+
Output == 0 means Cooperate, 1 means Defect.
55+
The best (hence expected) answer to Cooperator is to defect
56+
whatever the input position is.
57+
"""
58+
expected_output = [1, 1, 1, 1]
59+
for inp, out in zip(self.input_pos, expected_output):
60+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
61+
values = dbs.minimax_tree_search(begin_node,
62+
self.cooperator_policy, max_depth=5)
63+
self.assertEqual(values.index(max(values)),out)
64+
65+
def test_MoveGen_cooperator(self):
66+
"""
67+
Tests the MoveGen function when playing against a
68+
Cooperator player.
69+
"""
70+
expected_output = [D, D, D, D]
71+
for inp, out in zip(self.input_pos, expected_output):
72+
out_move = dbs.MoveGen(inp, self.cooperator_policy,
73+
depth_search_tree=5)
74+
self.assertEqual(out_move, out)
75+
76+
def test_minimaxTreeSearch_defector(self):
77+
"""
78+
Tests the minimax_tree_search function when playing against a
79+
Defector player.
80+
The best answer to Defector is to always defect
81+
"""
82+
expected_output = [1, 1, 1, 1]
83+
for inp, out in zip(self.input_pos, expected_output):
84+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
85+
values = dbs.minimax_tree_search(begin_node,
86+
self.defector_policy, max_depth=5)
87+
self.assertEqual(values.index(max(values)),out)
88+
89+
def test_MoveGen_defector(self):
90+
"""
91+
Tests the MoveGen function when playing against a
92+
Defector player.
93+
"""
94+
expected_output = [D, D, D, D]
95+
for inp, out in zip(self.input_pos, expected_output):
96+
out_move = dbs.MoveGen(inp, self.defector_policy,
97+
depth_search_tree=5)
98+
self.assertEqual(out_move, out)
99+
100+
def test_minimaxTreeSearch_titForTat(self):
101+
"""
102+
Tests the minimax_tree_search function when playing against a
103+
TitForTat player.
104+
The best (hence expected) answer to TitFOrTat is to cooperate
105+
whatever the input position is.
106+
"""
107+
expected_output = [0, 0, 0, 0]
108+
for inp, out in zip(self.input_pos, expected_output):
109+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
110+
values = dbs.minimax_tree_search(begin_node,
111+
self.titForTat_policy, max_depth=5)
112+
self.assertEqual(values.index(max(values)),out)
113+
114+
def test_last_node_titForTat(self):
115+
"""
116+
Test that against TitForTat, for the last move, i.e. if tree
117+
depth is 1, the algorithms defects for all input
118+
"""
119+
expected_output = [1, 1, 1, 1]
120+
for inp, out in zip(self.input_pos, expected_output):
121+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
122+
values = dbs.minimax_tree_search(begin_node,
123+
self.titForTat_policy, max_depth=1)
124+
self.assertEqual(values.index(max(values)),out)
125+
126+
def test_MoveGen_titForTat(self):
127+
"""
128+
Tests the MoveGen function when playing against a
129+
TitForTat player.
130+
"""
131+
expected_output = [C, C, C, C]
132+
for inp, out in zip(self.input_pos, expected_output):
133+
out_move = dbs.MoveGen(inp, self.titForTat_policy,
134+
depth_search_tree=5)
135+
self.assertEqual(out_move, out)
136+
137+
def test_minimaxTreeSearch_alternator(self):
138+
"""
139+
Tests the minimax_tree_search function when playing against an
140+
Alternator player.
141+
The best answer to Alternator is to always defect
142+
"""
143+
expected_output = [1, 1, 1, 1]
144+
for inp, out in zip(self.input_pos, expected_output):
145+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
146+
values = dbs.minimax_tree_search(begin_node,
147+
self.alternator_policy, max_depth=5)
148+
self.assertEqual(values.index(max(values)),out)
149+
150+
def test_MoveGen_alternator(self):
151+
"""
152+
Tests the MoveGen function when playing against an
153+
Alternator player.
154+
"""
155+
expected_output = [D, D, D, D]
156+
for inp, out in zip(self.input_pos, expected_output):
157+
out_move = dbs.MoveGen(inp, self.random_policy, depth_search_tree=5)
158+
self.assertEqual(out_move, out)
159+
160+
def test_minimaxTreeSearch_random(self):
161+
"""
162+
Tests the minimax_tree_search function when playing against a
163+
Random player.
164+
The best answer to Random is to always defect
165+
"""
166+
expected_output = [1, 1, 1, 1]
167+
for inp, out in zip(self.input_pos, expected_output):
168+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
169+
values = dbs.minimax_tree_search(begin_node,
170+
self.random_policy, max_depth=5)
171+
self.assertEqual(values.index(max(values)),out)
172+
173+
def test_MoveGen_random(self):
174+
"""
175+
Tests the MoveGen function when playing against a
176+
Random player.
177+
"""
178+
expected_output = [D, D, D, D]
179+
for inp, out in zip(self.input_pos, expected_output):
180+
out_move = dbs.MoveGen(inp, self.random_policy, depth_search_tree=5)
181+
self.assertEqual(out_move, out)
182+
183+
def test_minimaxTreeSearch_grudger(self):
184+
"""
185+
Tests the minimax_tree_search function when playing against a
186+
Grudger player.
187+
The best answer to Grudger is to cooperate if both cooperated
188+
at last round, else it's to defect
189+
"""
190+
expected_output = [0, 1, 1, 1]
191+
for inp, out in zip(self.input_pos, expected_output):
192+
begin_node = dbs.DeterministicNode(inp[0], inp[1], depth=0)
193+
values = dbs.minimax_tree_search(begin_node,
194+
self.grudger_policy, max_depth=5)
195+
self.assertEqual(values.index(max(values)),out)
196+
197+
def test_MoveGen_grudger(self):
198+
"""
199+
Tests the MoveGen function when playing against a
200+
Grudger player.
201+
"""
202+
expected_output = [C, D, D, D]
203+
for inp, out in zip(self.input_pos, expected_output):
204+
out_move = dbs.MoveGen(inp,
205+
self.grudger_policy, depth_search_tree=5)
206+
self.assertEqual(out_move, out)
207+
208+
25209
class TestDBS(TestPlayer):
26210
name = "DBS: 0.75, 3, 4, 3, 5"
27211
player = axelrod.DBS

0 commit comments

Comments
 (0)