Repository: ASPP/pelita

Issue:
make the cumulative time counter available to clients
The `Bot` object should have an attribute `time` where the cumulative time for the team shown in the GUI is made available. This is useful for example when benchmarking different strategies. There's no way at the moment to programmatically access that value, as far as I could see.
Of course the client could time itself, but this has its own pitfalls. So it's better to just make that value available, even if we all know it's not super precise...

Discussion:
Debilski: wow. Fixing these tests took way longer than expected …


Patch:
diff --git a/pelita/game.py b/pelita/game.py
index 6cbacc56..efc51f3a 100644
--- a/pelita/game.py
+++ b/pelita/game.py
@@ -555,7 +555,8 @@ def prepare_bot_state(game_state, idx=None):
         'bot_was_killed': game_state['bot_was_killed'][own_team::2],
         'error_count': len(game_state['errors'][own_team]),
         'food': list(game_state['food'][own_team]),
-        'name': game_state['team_names'][own_team]
+        'name': game_state['team_names'][own_team],
+        'team_time': game_state['team_time'][own_team]
     }
 
     enemy_state = {
@@ -568,7 +569,8 @@ def prepare_bot_state(game_state, idx=None):
         'bot_was_killed': game_state['bot_was_killed'][enemy_team::2],
         'error_count': 0, # TODO. Could be left out for the enemy
         'food': list(game_state['food'][enemy_team]),
-        'name': game_state['team_names'][enemy_team]
+        'name': game_state['team_names'][enemy_team],
+        'team_time': game_state['team_time'][enemy_team]
     }
 
     bot_state = {
diff --git a/pelita/team.py b/pelita/team.py
index dab0930c..02c66bca 100644
--- a/pelita/team.py
+++ b/pelita/team.py
@@ -522,6 +522,7 @@ class Bot:
                           bot_char,
                           is_blue,
                           team_name,
+                          team_time,
                           error_count,
                           is_noisy,
                           bot_turn=None):
@@ -551,6 +552,7 @@ class Bot:
         self.char = bot_char
         self.is_blue = is_blue
         self.team_name = team_name
+        self.team_time = team_time
         self.error_count = error_count
         self.is_noisy = is_noisy
         self.graph = graph
@@ -745,7 +747,8 @@ def make_bots(*, walls, shape, initial_positions, homezone, team, enemy, round,
             initial_position=team_initial_positions[idx],
             is_blue=team_index % 2 == 0,
             homezone=homezone[team_index],
-            team_name=team['name'])
+            team_name=team['name'],
+            team_time=team['team_time'])
         b._bots = bots
         team_bots.append(b)
 
@@ -770,7 +773,8 @@ def make_bots(*, walls, shape, initial_positions, homezone, team, enemy, round,
             initial_position=enemy_initial_positions[idx],
             is_blue=enemy_index % 2 == 0,
             homezone=homezone[enemy_index],
-            team_name=enemy['name'])
+            team_name=enemy['name'],
+            team_time=enemy['team_time'])
         b._bots = bots
         enemy_bots.append(b)
 
diff --git a/pelita/utils.py b/pelita/utils.py
index 6db44810..10f15473 100644
--- a/pelita/utils.py
+++ b/pelita/utils.py
@@ -245,7 +245,8 @@ def setup_test_game(*, layout, is_blue=True, round=None, score=None, seed=None,
         'bot_was_killed' : [False]*2,
         'error_count': 0,
         'food': food[team_index],
-        'name': "blue" if is_blue else "red"
+        'name': "blue" if is_blue else "red",
+        'team_time': 0.0,
     }
     enemy = {
         'bot_positions': enemy_positions,
@@ -257,7 +258,8 @@ def setup_test_game(*, layout, is_blue=True, round=None, score=None, seed=None,
         'error_count': 0,
         'food': food[enemy_index],
         'is_noisy': is_noisy_enemy,
-        'name': "red" if is_blue else "blue"
+        'name': "red" if is_blue else "blue",
+        'team_time': 0.0,
     }
 
     bot = make_bots(walls=layout['walls'],


Test patch:
diff --git a/test/test_network.py b/test/test_network.py
index 701d5307..9fcaca7f 100644
--- a/test/test_network.py
+++ b/test/test_network.py
@@ -88,6 +88,7 @@ def test_simpleclient(zmq_context):
                     'error_count': 0,
                     'food': [(1, 1)],
                     'name': 'dummy',
+                    'team_time': 0,
                 },
                 'enemy': {
                     'team_index': 1,
@@ -98,6 +99,7 @@ def test_simpleclient(zmq_context):
                     'bot_was_killed': [False]*2,
                     'food': [(2, 2)],
                     'name': 'other dummy',
+                    'team_time': 0,
                     'is_noisy': [False, False],
                     'error_count': 0
                 },
diff --git a/test/test_team.py b/test/test_team.py
index aa2a7d62..6544ed7c 100644
--- a/test/test_team.py
+++ b/test/test_team.py
@@ -1,3 +1,5 @@
+import time
+
 import pytest
 import networkx
 
@@ -523,6 +525,103 @@ def test_team_names():
     assert state['fatal_errors'] == [[], []]
 
 
+def test_team_time():
+    test_layout = (
+    """ ##################
+        #a#.  .  # .     #
+        #b#####    #####x#
+        #     . #  .  .#y#
+        ################## """)
+
+    def team_pattern(fn):
+        # The pattern for a local team.
+        return f'local-team ({fn})'
+
+    # NB: time.monotonic() makes huge leaps on windows,
+    # we must therefore test with >= instead of just >
+    # although this is a little stupid
+
+    check = [None, None]
+
+    def team_1(bot, state):
+        if bot.round == 1 and bot.turn == 0:
+            assert bot.team_time == 0
+            assert bot.enemy[0].team_time == 0
+        else:
+            assert bot.team_time >= 0
+
+        # we fake the time in round 2!!
+        if bot.round == 2 and bot.turn == 0:
+            assert bot.team_time == 1.0
+            assert bot.enemy[0].team_time == 2.0
+
+        if bot.round == 2 and bot.turn == 1:
+            assert bot.team_time >= 1.0
+            assert bot.enemy[0].team_time >= 2.0
+
+        check[0] = bot.team_time
+
+        return bot.position
+
+    def team_2(bot, state):
+        if bot.round == 1 and bot.turn == 0:
+            assert bot.team_time == 0
+            assert bot.enemy[0].team_time >= 0
+        else:
+            assert bot.team_time >= 0
+
+        # we fake the time in round 2!!
+        if bot.round == 2 and bot.turn == 0:
+            assert bot.team_time == 2.0
+            assert bot.enemy[0].team_time >= 1.0
+
+        if bot.round == 2 and bot.turn == 1:
+            assert bot.team_time >= 2.0
+            assert bot.enemy[0].team_time >= 1.0
+
+        check[1] = bot.team_time
+
+        return bot.position
+
+    state = setup_game([team_1, team_2], layout_dict=parse_layout(test_layout), max_rounds=3, allow_exceptions=True)
+
+    state = play_turn(state)
+    assert state['team_time'][0] >= 0
+    assert state['team_time'][1] == 0
+
+    state = play_turn(state)
+    assert state['team_time'][0] >= 0
+    assert state['team_time'][1] >= 0
+
+    # time before bots get updated
+    old_time = list(state['team_time'])
+
+    state = play_turn(state)
+    state = play_turn(state)
+
+    assert check == old_time
+
+    # Round 2. We fake the time so we do not have to sleep!
+    state['team_time'] = [1.0, 2.0]
+
+    state = play_turn(state)
+    state = play_turn(state)
+
+    # time before bots get updated
+    old_time = list(state['team_time'])
+
+    state = play_turn(state)
+    state = play_turn(state)
+
+    assert check == old_time
+
+    # check that player did not fail
+    assert state['errors'] == [{}, {}]
+    assert state['fatal_errors'] == [[], []]
+    assert state['team_time'][0] >= 1.0
+    assert state['team_time'][1] >= 2.0
+
+
 def test_bot_str_repr():
     test_layout = """
         ##################