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.
- {i151_engine-0.2.2 → i151_engine-0.2.3}/PKG-INFO +1 -1
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/round.py +4 -1
- i151_engine-0.2.3/i151_engine/runner/challenge_guards.py +28 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/runner/config.py +2 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/runner/match_runner.py +4 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/PKG-INFO +1 -1
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/SOURCES.txt +3 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/pyproject.toml +1 -1
- i151_engine-0.2.3/tests/test_challenge_guards.py +77 -0
- i151_engine-0.2.3/tests/test_first_player.py +40 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/ARCHITECTURE.md +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/README.md +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/__init__.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/bots/__init__.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/bots/protocol.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/bots/random_bot.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/__init__.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/deck.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/errors.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/legal_actions.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/reducer.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/rules.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/core/scoring.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/__init__.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/recorder.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/game/round_state.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/__init__.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/action.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/card.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/enums.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/models/player.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/__init__.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/catalog.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/registry.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/schedules.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/opponents/types.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/view/builder.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine/view/player_view.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/dependency_links.txt +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/requires.txt +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/i151_engine.egg-info/top_level.txt +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/setup.cfg +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_card.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_deck.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_forfeit_scoring.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_match_runner.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_match_timeout.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_opponents.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_player_view_turn.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_recorder.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_reducer.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_rules.py +0 -0
- {i151_engine-0.2.2 → i151_engine-0.2.3}/tests/test_scoring.py +0 -0
|
@@ -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=
|
|
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
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|