i151-engine 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {i151_engine-0.2.2 → i151_engine-0.2.3}/PKG-INFO +1 -1
  2. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/round.py +4 -1
  3. i151_engine-0.2.3/i151_engine/runner/challenge_guards.py +28 -0
  4. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/runner/config.py +2 -0
  5. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/runner/match_runner.py +4 -0
  6. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/PKG-INFO +1 -1
  7. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/SOURCES.txt +3 -0
  8. {i151_engine-0.2.2 → i151_engine-0.2.3}/pyproject.toml +1 -1
  9. i151_engine-0.2.3/tests/test_challenge_guards.py +77 -0
  10. i151_engine-0.2.3/tests/test_first_player.py +40 -0
  11. {i151_engine-0.2.2 → i151_engine-0.2.3}/ARCHITECTURE.md +0 -0
  12. {i151_engine-0.2.2 → i151_engine-0.2.3}/README.md +0 -0
  13. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/__init__.py +0 -0
  14. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/bots/__init__.py +0 -0
  15. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/bots/protocol.py +0 -0
  16. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/bots/random_bot.py +0 -0
  17. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/__init__.py +0 -0
  18. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/deck.py +0 -0
  19. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/errors.py +0 -0
  20. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/legal_actions.py +0 -0
  21. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/reducer.py +0 -0
  22. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/rules.py +0 -0
  23. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/scoring.py +0 -0
  24. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/__init__.py +0 -0
  25. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/recorder.py +0 -0
  26. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/round_state.py +0 -0
  27. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/__init__.py +0 -0
  28. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/action.py +0 -0
  29. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/card.py +0 -0
  30. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/enums.py +0 -0
  31. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/player.py +0 -0
  32. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/__init__.py +0 -0
  33. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/catalog.py +0 -0
  34. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/registry.py +0 -0
  35. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/schedules.py +0 -0
  36. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/types.py +0 -0
  37. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/view/builder.py +0 -0
  38. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/view/player_view.py +0 -0
  39. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/dependency_links.txt +0 -0
  40. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/requires.txt +0 -0
  41. {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/top_level.txt +0 -0
  42. {i151_engine-0.2.2 → i151_engine-0.2.3}/setup.cfg +0 -0
  43. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_card.py +0 -0
  44. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_deck.py +0 -0
  45. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_forfeit_scoring.py +0 -0
  46. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_match_runner.py +0 -0
  47. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_match_timeout.py +0 -0
  48. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_opponents.py +0 -0
  49. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_player_view_turn.py +0 -0
  50. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_recorder.py +0 -0
  51. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_reducer.py +0 -0
  52. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_rules.py +0 -0
  53. {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_scoring.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: i151-engine
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Headless game engine for the i151 student AI competition
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -15,8 +15,11 @@ def create_match(
15
15
  seed: int,
16
16
  target_score: int = 151,
17
17
  cards_per_player: int = 8,
18
+ initial_dealer_index: int = 0,
18
19
  ) -> MatchState:
19
20
  validate_cards_per_player(cards_per_player, player_count=len(player_names))
21
+ n = len(player_names)
22
+ dealer = int(initial_dealer_index) % n
20
23
  players = tuple(
21
24
  PlayerState(player_id=f"p{i}", name=name, hand=())
22
25
  for i, name in enumerate(player_names)
@@ -25,7 +28,7 @@ def create_match(
25
28
  status=MatchStatus.IN_PROGRESS,
26
29
  players=players,
27
30
  round_number=1,
28
- dealer_index=0,
31
+ dealer_index=dealer,
29
32
  target_score=target_score,
30
33
  last_winner_id=None,
31
34
  winner_id=None,
@@ -0,0 +1,28 @@
1
+ """Gardes compétition : aucun oracle / ground-truth pendant un match arène.
2
+
3
+ Le chemin d'entraînement MCTS (``collect_context``) peut exposer l'état moteur
4
+ complet via un ``ContextVar`` ancré dans ``sys.modules``. Les matchs officiels
5
+ n'utilisent jamais ce chemin, mais on efface tout de même le contexte avant
6
+ chaque ``decide`` pour qu'une soumission ne puisse pas lire un état résiduel.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+
13
+ # Doit rester aligné avec ``mcts_bot/collect_context.py`` (_STORE_NAME).
14
+ _GT_STORE_NAME = "_i151_mcts_collect_gt_store"
15
+
16
+
17
+ def clear_training_oracle_context() -> None:
18
+ """Efface tout ContextVar ground-truth éventuellement présent."""
19
+ store = sys.modules.get(_GT_STORE_NAME)
20
+ if store is None:
21
+ return
22
+ var = getattr(store, "var", None)
23
+ if var is None:
24
+ return
25
+ try:
26
+ var.set(None)
27
+ except Exception:
28
+ pass
@@ -13,3 +13,5 @@ class MatchConfig:
13
13
  match_timeout_ms: int = 600_000
14
14
  max_steps_per_round: int = 500
15
15
  max_rounds: int = 200
16
+ # 0 (défaut) → p1 commence la 1ʳᵉ manche ; 1 → p0 commence.
17
+ initial_dealer_index: int = 0
@@ -16,6 +16,7 @@ from i151_engine.game.round import create_match, start_round
16
16
  from i151_engine.models.enums import MatchStatus, RoundPhase
17
17
  from i151_engine.opponents.registry import OpponentRegistry, default_registry
18
18
  from i151_engine.opponents.types import OpponentSpec
19
+ from i151_engine.runner.challenge_guards import clear_training_oracle_context
19
20
  from i151_engine.runner.config import MatchConfig
20
21
  from i151_engine.view.builder import build_player_view
21
22
 
@@ -154,6 +155,7 @@ def _run_match(
154
155
  seed=config.seed,
155
156
  target_score=config.target_score,
156
157
  cards_per_player=config.cards_per_player,
158
+ initial_dealer_index=getattr(config, "initial_dealer_index", 0),
157
159
  )
158
160
  recorder.start_round(match.round_number)
159
161
  steps = 0
@@ -199,6 +201,8 @@ def _run_match(
199
201
 
200
202
  remaining_ms = config.decision_timeout_ms
201
203
  try:
204
+ # Compétition : jamais d'état moteur complet pour le bot.
205
+ clear_training_oracle_context()
202
206
  action = _decide_with_timeout(bot, view, legal, remaining_ms)
203
207
  except TimeoutError:
204
208
  return _forfeit_result(match, opponent_id, pid, steps, "timeout", recorder)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: i151-engine
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Headless game engine for the i151 student AI competition
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -31,12 +31,15 @@ i151_engine/opponents/catalog.py
31
31
  i151_engine/opponents/registry.py
32
32
  i151_engine/opponents/schedules.py
33
33
  i151_engine/opponents/types.py
34
+ i151_engine/runner/challenge_guards.py
34
35
  i151_engine/runner/config.py
35
36
  i151_engine/runner/match_runner.py
36
37
  i151_engine/view/builder.py
37
38
  i151_engine/view/player_view.py
38
39
  tests/test_card.py
40
+ tests/test_challenge_guards.py
39
41
  tests/test_deck.py
42
+ tests/test_first_player.py
40
43
  tests/test_forfeit_scoring.py
41
44
  tests/test_match_runner.py
42
45
  tests/test_match_timeout.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "i151-engine"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Headless game engine for the i151 student AI competition"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -0,0 +1,77 @@
1
+ """Gardes compétition : oracle ground-truth effacé avant decide."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ import types
7
+ from contextvars import ContextVar
8
+
9
+ from i151_engine.models.action import Action
10
+ from i151_engine.runner.challenge_guards import _GT_STORE_NAME, clear_training_oracle_context
11
+ from i151_engine.runner.config import MatchConfig
12
+ from i151_engine.runner.match_runner import play_bots_match
13
+ from i151_engine.view.player_view import PlayerView
14
+
15
+
16
+ class _OracleProbeBot:
17
+ """Laisse un résidu GT après chaque decide ; le suivant doit le trouver effacé."""
18
+
19
+ def __init__(self) -> None:
20
+ self.saw_oracle = False
21
+ self.calls = 0
22
+
23
+ def setup(self, submission_dir) -> None: # noqa: ANN001
24
+ return None
25
+
26
+ def decide(
27
+ self,
28
+ view: PlayerView,
29
+ legal_actions: list[Action],
30
+ time_left_ms: int,
31
+ ) -> Action:
32
+ self.calls += 1
33
+ store = sys.modules.get(_GT_STORE_NAME)
34
+ var = getattr(store, "var", None) if store is not None else None
35
+ if var is not None and var.get() is not None:
36
+ self.saw_oracle = True
37
+ # Résidu volontaire : le runner doit l'effacer avant le prochain decide.
38
+ if var is not None:
39
+ var.set({"leak": self.calls})
40
+ return legal_actions[0]
41
+
42
+ def teardown(self) -> None:
43
+ return None
44
+
45
+
46
+ def test_clear_training_oracle_context_resets_store():
47
+ var: ContextVar[object | None] = ContextVar("test_gt", default=None)
48
+ sys.modules[_GT_STORE_NAME] = types.SimpleNamespace(var=var)
49
+ try:
50
+ var.set(object())
51
+ assert var.get() is not None
52
+ clear_training_oracle_context()
53
+ assert var.get() is None
54
+ finally:
55
+ del sys.modules[_GT_STORE_NAME]
56
+
57
+
58
+ def test_play_bots_match_clears_oracle_before_decide():
59
+ var: ContextVar[object | None] = ContextVar("test_gt_match", default=None)
60
+ sys.modules[_GT_STORE_NAME] = types.SimpleNamespace(var=var)
61
+ try:
62
+ var.set({"pre_match_leak": True})
63
+ probe = _OracleProbeBot()
64
+ opponent = _OracleProbeBot()
65
+ result = play_bots_match(
66
+ probe,
67
+ opponent,
68
+ MatchConfig(seed=7, target_score=1, max_rounds=3, max_steps_per_round=40),
69
+ challenger_name="probe",
70
+ opponent_name="opp",
71
+ )
72
+ assert result.steps_played > 0
73
+ assert probe.calls + opponent.calls >= 2
74
+ assert not probe.saw_oracle
75
+ assert not opponent.saw_oracle
76
+ finally:
77
+ del sys.modules[_GT_STORE_NAME]
@@ -0,0 +1,40 @@
1
+ """Tests first_player_stats + initial_dealer_index."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from i151_engine.game.round import create_match
6
+ from i151_engine.bots.random_bot import RandomBot
7
+ from i151_engine.runner.config import MatchConfig
8
+ from i151_engine.runner.match_runner import play_bots_match
9
+
10
+
11
+ def test_create_match_dealer_1_makes_p0_start():
12
+ match = create_match(("A", "B"), seed=7, initial_dealer_index=1)
13
+ assert match.dealer_index == 1
14
+ assert match.current_round is not None
15
+ assert match.current_round.current_player.player_id == "p0"
16
+
17
+
18
+ def test_create_match_dealer_0_makes_p1_start():
19
+ match = create_match(("A", "B"), seed=7, initial_dealer_index=0)
20
+ assert match.dealer_index == 0
21
+ assert match.current_round is not None
22
+ assert match.current_round.current_player.player_id == "p1"
23
+
24
+
25
+ def test_match_config_initial_dealer_index():
26
+ a = play_bots_match(
27
+ RandomBot(),
28
+ RandomBot(),
29
+ MatchConfig(seed=11, target_score=1, initial_dealer_index=1),
30
+ challenger_name="c",
31
+ opponent_name="o",
32
+ )
33
+ b = play_bots_match(
34
+ RandomBot(),
35
+ RandomBot(),
36
+ MatchConfig(seed=11, target_score=1, initial_dealer_index=0),
37
+ challenger_name="c",
38
+ opponent_name="o",
39
+ )
40
+ assert a.steps_played > 0 and b.steps_played > 0
File without changes
File without changes
File without changes