i151-engine 0.2.3__tar.gz → 0.2.4__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.3 → i151_engine-0.2.4}/ARCHITECTURE.md +2 -2
  2. {i151_engine-0.2.3 → i151_engine-0.2.4}/PKG-INFO +1 -1
  3. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/scoring.py +4 -1
  4. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/game/recorder.py +1 -1
  5. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/runner/config.py +1 -1
  6. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine.egg-info/PKG-INFO +1 -1
  7. {i151_engine-0.2.3 → i151_engine-0.2.4}/pyproject.toml +1 -1
  8. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_scoring.py +53 -0
  9. {i151_engine-0.2.3 → i151_engine-0.2.4}/README.md +0 -0
  10. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/__init__.py +0 -0
  11. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/bots/__init__.py +0 -0
  12. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/bots/protocol.py +0 -0
  13. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/bots/random_bot.py +0 -0
  14. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/__init__.py +0 -0
  15. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/deck.py +0 -0
  16. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/errors.py +0 -0
  17. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/legal_actions.py +0 -0
  18. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/reducer.py +0 -0
  19. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/core/rules.py +0 -0
  20. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/game/__init__.py +0 -0
  21. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/game/round.py +0 -0
  22. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/game/round_state.py +0 -0
  23. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/models/__init__.py +0 -0
  24. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/models/action.py +0 -0
  25. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/models/card.py +0 -0
  26. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/models/enums.py +0 -0
  27. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/models/player.py +0 -0
  28. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/opponents/__init__.py +0 -0
  29. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/opponents/catalog.py +0 -0
  30. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/opponents/registry.py +0 -0
  31. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/opponents/schedules.py +0 -0
  32. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/opponents/types.py +0 -0
  33. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/runner/challenge_guards.py +0 -0
  34. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/runner/match_runner.py +0 -0
  35. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/view/builder.py +0 -0
  36. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine/view/player_view.py +0 -0
  37. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine.egg-info/SOURCES.txt +0 -0
  38. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine.egg-info/dependency_links.txt +0 -0
  39. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine.egg-info/requires.txt +0 -0
  40. {i151_engine-0.2.3 → i151_engine-0.2.4}/i151_engine.egg-info/top_level.txt +0 -0
  41. {i151_engine-0.2.3 → i151_engine-0.2.4}/setup.cfg +0 -0
  42. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_card.py +0 -0
  43. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_challenge_guards.py +0 -0
  44. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_deck.py +0 -0
  45. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_first_player.py +0 -0
  46. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_forfeit_scoring.py +0 -0
  47. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_match_runner.py +0 -0
  48. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_match_timeout.py +0 -0
  49. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_opponents.py +0 -0
  50. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_player_view_turn.py +0 -0
  51. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_recorder.py +0 -0
  52. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_reducer.py +0 -0
  53. {i151_engine-0.2.3 → i151_engine-0.2.4}/tests/test_rules.py +0 -0
@@ -606,7 +606,7 @@ def decide(view: PlayerView, legal_actions: list[Action], time_left_ms: int) ->
606
606
  | Taille ZIP | 50 Mo |
607
607
  | Fichier modèle unique | 30 Mo |
608
608
  | Temps `setup()` | 30 s |
609
- | Temps `decide()` | `decision_timeout_ms` (2 s par défaut) |
609
+ | Temps `decide()` | `decision_timeout_ms` (1000 ms par défaut) |
610
610
  | RAM processus | 512 Mo |
611
611
 
612
612
  Le runner charge le bot une fois par match (`setup` → boucle `decide` → `teardown`).
@@ -787,7 +787,7 @@ Les parties générées localement n'alimentent pas le classement ; seules les s
787
787
  class MatchConfig:
788
788
  seed: int
789
789
  target_score: int = 151
790
- decision_timeout_ms: int = 2000
790
+ decision_timeout_ms: int = 1000
791
791
  match_timeout_ms: int = 600_000
792
792
  max_steps_per_round: int = 500 # garde-fou anti-boucle
793
793
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: i151-engine
3
- Version: 0.2.3
3
+ Version: 0.2.4
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
@@ -108,7 +108,8 @@ def apply_round_scoring(match: MatchState, winner_id: str) -> MatchState:
108
108
  is_excluded=player.is_excluded or new_score >= match.target_score,
109
109
  has_drawn_this_turn=False,
110
110
  is_chaining=False,
111
- consecutive_round_wins=player.consecutive_round_wins,
111
+ # Aligné Flutter/TS : série cassée dès qu'un autre gagne la manche.
112
+ consecutive_round_wins=0,
112
113
  )
113
114
 
114
115
  winner, last_winner_id = _track_consecutive_wins(
@@ -130,6 +131,7 @@ def apply_round_scoring(match: MatchState, winner_id: str) -> MatchState:
130
131
  winner_id=winner_id_final,
131
132
  current_round=None,
132
133
  seed=match.seed,
134
+ cards_per_player=match.cards_per_player,
133
135
  )
134
136
 
135
137
  dealer_index, _ = _next_dealer_and_starter(players_list, match.dealer_index)
@@ -143,6 +145,7 @@ def apply_round_scoring(match: MatchState, winner_id: str) -> MatchState:
143
145
  winner_id=None,
144
146
  current_round=None,
145
147
  seed=match.seed,
148
+ cards_per_player=match.cards_per_player,
146
149
  )
147
150
 
148
151
 
@@ -66,7 +66,7 @@ class ReplayLog:
66
66
 
67
67
 
68
68
  class MatchRecorder:
69
- """Records public match state for web replay."""
69
+ """Enregistre l'état pour le replay web (après match, vue spectateur)."""
70
70
 
71
71
  def __init__(
72
72
  self,
@@ -9,7 +9,7 @@ class MatchConfig:
9
9
  seed: int
10
10
  target_score: int = 151
11
11
  cards_per_player: int = 8
12
- decision_timeout_ms: int = 2000
12
+ decision_timeout_ms: int = 1000
13
13
  match_timeout_ms: int = 600_000
14
14
  max_steps_per_round: int = 500
15
15
  max_rounds: int = 200
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: i151-engine
3
- Version: 0.2.3
3
+ Version: 0.2.4
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "i151-engine"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  description = "Headless game engine for the i151 student AI competition"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -81,3 +81,56 @@ def test_consecutive_win_bonus_deducts_ten_from_winner():
81
81
  result = apply_round_scoring(match, "p0")
82
82
  assert result.player_by_id("p0").total_score == 40
83
83
  assert result.player_by_id("p0").consecutive_round_wins == 0
84
+
85
+
86
+ def test_losing_resets_consecutive_round_wins():
87
+ """Régression : sans reset, la vue exposait une série fantôme au perdant."""
88
+ end_p0 = PlayerState(player_id="p0", name="NewWinner", hand=())
89
+ end_p1 = PlayerState(player_id="p1", name="PrevStreak", hand=(Card(Rank.SEVEN, Suit.CLUBS),))
90
+
91
+ round_state = _ended_round(players=(end_p0, end_p1), winner_id="p0")
92
+ match = MatchState(
93
+ status=MatchStatus.IN_PROGRESS,
94
+ players=(
95
+ PlayerState("p0", "NewWinner", hand=(), total_score=10, consecutive_round_wins=0),
96
+ PlayerState("p1", "PrevStreak", hand=(), total_score=20, consecutive_round_wins=2),
97
+ ),
98
+ round_number=3,
99
+ dealer_index=0,
100
+ target_score=151,
101
+ last_winner_id="p1",
102
+ winner_id=None,
103
+ current_round=round_state,
104
+ seed=1,
105
+ )
106
+
107
+ result = apply_round_scoring(match, "p0")
108
+ assert result.player_by_id("p0").consecutive_round_wins == 1
109
+ assert result.player_by_id("p1").consecutive_round_wins == 0
110
+ assert result.last_winner_id == "p0"
111
+
112
+
113
+ def test_apply_round_scoring_preserves_cards_per_player():
114
+ """Régression : sans ce champ, la manche 2 repasse à 8 cartes (banque=16)."""
115
+ end_p0 = PlayerState(player_id="p0", name="A", hand=())
116
+ end_p1 = PlayerState(player_id="p1", name="B", hand=(Card(Rank.SEVEN, Suit.CLUBS),))
117
+ round_state = _ended_round(players=(end_p0, end_p1), winner_id="p0")
118
+ match = MatchState(
119
+ status=MatchStatus.IN_PROGRESS,
120
+ players=(
121
+ PlayerState("p0", "A", hand=()),
122
+ PlayerState("p1", "B", hand=()),
123
+ ),
124
+ round_number=1,
125
+ dealer_index=0,
126
+ target_score=151,
127
+ last_winner_id=None,
128
+ winner_id=None,
129
+ current_round=round_state,
130
+ seed=1,
131
+ cards_per_player=16,
132
+ )
133
+ result = apply_round_scoring(match, "p0")
134
+ assert result.status == MatchStatus.IN_PROGRESS
135
+ assert result.cards_per_player == 16
136
+ assert result.round_number == 2
File without changes
File without changes