valanga 0.1.11__tar.gz → 0.1.14__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 (23) hide show
  1. {valanga-0.1.11 → valanga-0.1.14}/PKG-INFO +4 -4
  2. {valanga-0.1.11 → valanga-0.1.14}/pyproject.toml +15 -10
  3. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/evaluations.py +7 -11
  4. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/game.py +30 -21
  5. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/over_event.py +51 -30
  6. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/policy.py +20 -9
  7. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/progress_messsage.py +3 -5
  8. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/representation_factory.py +15 -12
  9. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/represention_for_evaluation.py +9 -7
  10. {valanga-0.1.11 → valanga-0.1.14}/src/valanga.egg-info/PKG-INFO +4 -4
  11. {valanga-0.1.11 → valanga-0.1.14}/src/valanga.egg-info/requires.txt +3 -3
  12. {valanga-0.1.11 → valanga-0.1.14}/tests/test_over_event.py +11 -0
  13. {valanga-0.1.11 → valanga-0.1.14}/tests/test_placeholder.py +2 -2
  14. {valanga-0.1.11 → valanga-0.1.14}/tests/test_representation_factory.py +9 -0
  15. {valanga-0.1.11 → valanga-0.1.14}/LICENSE +0 -0
  16. {valanga-0.1.11 → valanga-0.1.14}/README.md +0 -0
  17. {valanga-0.1.11 → valanga-0.1.14}/setup.cfg +0 -0
  18. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/__init__.py +12 -12
  19. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/evaluator_types.py +0 -0
  20. {valanga-0.1.11 → valanga-0.1.14}/src/valanga/py.typed +0 -0
  21. {valanga-0.1.11 → valanga-0.1.14}/src/valanga.egg-info/SOURCES.txt +0 -0
  22. {valanga-0.1.11 → valanga-0.1.14}/src/valanga.egg-info/dependency_links.txt +0 -0
  23. {valanga-0.1.11 → valanga-0.1.14}/src/valanga.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valanga
3
- Version: 0.1.11
3
+ Version: 0.1.14
4
4
  Summary: Shared types and utilities for evaluation
5
5
  Author-email: Victor Gabillon <victorgabillon@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -684,11 +684,11 @@ Requires-Python: >=3.13
684
684
  Description-Content-Type: text/markdown
685
685
  License-File: LICENSE
686
686
  Provides-Extra: test
687
- Requires-Dist: pytest>=8.4.1; extra == "test"
687
+ Requires-Dist: pytest>=9.0.2; extra == "test"
688
688
  Requires-Dist: coverage; extra == "test"
689
689
  Requires-Dist: pytest-cov>=6.0.0; extra == "test"
690
690
  Provides-Extra: lint
691
- Requires-Dist: ruff>=0.14.10; extra == "lint"
691
+ Requires-Dist: ruff>=0.15.0; extra == "lint"
692
692
  Requires-Dist: pylint>=4.0.4; extra == "lint"
693
693
  Provides-Extra: typecheck
694
694
  Requires-Dist: mypy>=1.19.1; extra == "typecheck"
@@ -696,7 +696,7 @@ Requires-Dist: pyright[nodejs]>=1.1.408; extra == "typecheck"
696
696
  Provides-Extra: dev
697
697
  Requires-Dist: tox>=4.32.0; extra == "dev"
698
698
  Requires-Dist: types-PyYAML>=6.0.12.12; extra == "dev"
699
- Requires-Dist: ruff>=0.14.10; extra == "dev"
699
+ Requires-Dist: ruff>=0.15.0; extra == "dev"
700
700
  Requires-Dist: pylint>=4.0.4; extra == "dev"
701
701
  Requires-Dist: black>=25.12.0; extra == "dev"
702
702
  Requires-Dist: mypy>=1.19.1; extra == "dev"
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools>=80.9.0", "wheel"]
2
+ requires = ["setuptools>=80.10.2", "wheel"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "valanga"
7
- version = "0.1.11"
7
+ version = "0.1.14"
8
8
  description = "Shared types and utilities for evaluation"
9
9
  requires-python = ">=3.13"
10
10
  dependencies = []
@@ -20,13 +20,13 @@ classifiers = [
20
20
  [project.optional-dependencies]
21
21
  test = [
22
22
  # Testing dependencies
23
- "pytest>=8.4.1",
23
+ "pytest>=9.0.2",
24
24
  "coverage",
25
25
  "pytest-cov>=6.0.0"
26
26
  ]
27
27
 
28
28
  lint = [
29
- "ruff>=0.14.10",
29
+ "ruff>=0.15.0",
30
30
  "pylint>=4.0.4",
31
31
  ]
32
32
 
@@ -42,7 +42,7 @@ dev = [
42
42
  "types-PyYAML>=6.0.12.12",
43
43
 
44
44
  # Linting & formatting
45
- "ruff>=0.14.10",
45
+ "ruff>=0.15.0",
46
46
  "pylint>=4.0.4",
47
47
  "black>=25.12.0",
48
48
 
@@ -120,13 +120,18 @@ line-length = 88
120
120
  target-version = "py313"
121
121
 
122
122
  # Enable import sorting (alphabetical + group order)
123
- lint.select = ["I", "F", "E", "B", "TCH"] # I = imports, F = pyflakes, E = pycodestyle, TCH = type-checking, B = black
123
+ lint.select = ["I", "F", "E", "B", "TCH","UP","SIM",
124
+ "C4", "RUF", "PL","RET","ARG","ANN", "PERF", "TRY","LOG","RSE",
125
+ "D","ISC","PIE","ERA","TD"] # I = imports, F = pyflakes, E = pycodestyle, TCH = type-checking, B = black
124
126
 
125
127
  # Allow auto-fixing for imports and type-checking imports
126
- lint.fixable = ["I", "TCH", "F401"]
128
+ lint.fixable = ["I", "TCH", "F401", "RUF","PL","D"]
127
129
 
128
130
  # Ignore rules you don't care about
129
- lint.ignore = [ "E501"]
131
+ lint.ignore = [ "E501",
132
+ "D203", # incompatible with D211
133
+ "D213", # incompatible with D212
134
+ ]
130
135
 
131
136
  # Optional: add your first-party package to extend the known group
132
137
  [tool.ruff.lint.per-file-ignores]
@@ -186,10 +191,10 @@ commands = [["pytest", "{posargs}"]]
186
191
  [tool.tox.env.lint]
187
192
  description = "ruff + pylint (fast, no package install)"
188
193
  package = "skip"
189
- deps = ["ruff>=0.14.10", "pylint>=4.0.4"]
194
+ deps = ["ruff>=0.15.0", "pylint>=4.0.4"]
190
195
  set_env = { PYTHONPATH = "{toxinidir}/src" }
191
196
  commands = [
192
- ["python", "-m", "ruff", "check", "src", "tests"],
197
+ ["python", "-m", "ruff", "check", "src"],
193
198
  ["python", "-m", "ruff", "format", "--check", "src", "tests"],
194
199
  ["python", "-m", "pylint", "src/valanga"],
195
200
  ]
@@ -1,6 +1,4 @@
1
- """
2
- Evaluation-related classes and types.
3
- """
1
+ """Evaluation-related classes and types."""
4
2
 
5
3
  from dataclasses import dataclass
6
4
  from typing import Protocol
@@ -13,8 +11,8 @@ from .represention_for_evaluation import ContentRepresentation
13
11
 
14
12
 
15
13
  class EvalItem[StateT: State](Protocol):
16
- """
17
- The protocol for an evaluation item.
14
+ """Define the protocol for an evaluation item.
15
+
18
16
  An evaluation item is something that has a state and optionally a representation of that state.
19
17
  """
20
18
 
@@ -33,9 +31,7 @@ class EvalItem[StateT: State](Protocol):
33
31
 
34
32
  @dataclass
35
33
  class ForcedOutcome:
36
- """
37
- The class
38
- """
34
+ """The class."""
39
35
 
40
36
  # The forced outcome with optimal play by both sides.
41
37
  outcome: OverEvent
@@ -46,13 +42,13 @@ class ForcedOutcome:
46
42
 
47
43
  @dataclass
48
44
  class FloatyStateEvaluation:
49
- """
50
- The class to defines what is an evaluation of a board.
45
+ """Define what is an evaluation of a board.
46
+
51
47
  By convention is it always evaluated from the view point of the white side.
52
48
  """
53
49
 
54
50
  # The evaluation value for the white side when the outcome is not certain. Typically, a float.
55
- # todo can we remove the None option?
51
+ # TODO(victor): can we remove the None option? see issue #642
56
52
  value_white: float | None
57
53
 
58
54
 
@@ -1,11 +1,9 @@
1
- """
2
- Common types and utilities representing game objects shared by multiple libraries.
3
- """
1
+ """Common types and utilities representing game objects shared by multiple libraries."""
4
2
 
5
- from collections.abc import Hashable
3
+ from collections.abc import Hashable, Iterator, Sequence
6
4
  from dataclasses import dataclass, field
7
5
  from enum import Enum
8
- from typing import Annotated, Any, Iterator, Protocol, Self, Sequence, TypeVar
6
+ from typing import Annotated, Any, Protocol, Self, TypeVar
9
7
 
10
8
  type Seed = Annotated[int, "seed"]
11
9
 
@@ -33,34 +31,36 @@ class BranchKeyGeneratorP(Protocol[T_co]):
33
31
 
34
32
  @property
35
33
  def all_generated_keys(self) -> Sequence[T_co] | None:
36
- """Returns all generated branch keys if available, otherwise None."""
34
+ """Return all generated branch keys if available, otherwise None."""
37
35
  ...
38
36
 
39
37
  def __iter__(self) -> Iterator[T_co]:
40
- """Returns an iterator over the branch keys."""
38
+ """Return an iterator over the branch keys."""
41
39
  ...
42
40
 
43
41
  def __next__(self) -> T_co:
44
- """Returns the next branch key."""
42
+ """Return the next branch key."""
45
43
  ...
46
44
 
47
45
  def more_than_one(self) -> bool:
48
- """Checks if there is more than one branch available.
46
+ """Check if there is more than one branch available.
49
47
 
50
48
  Returns:
51
49
  bool: True if there is more than one branch, False otherwise.
50
+
52
51
  """
53
52
  ...
54
53
 
55
54
  def get_all(self) -> Sequence[T_co]:
56
- """Returns a list of all branch keys."""
55
+ """Return a list of all branch keys."""
57
56
  ...
58
57
 
59
58
  def copy_with_reset(self) -> Self:
60
- """Creates a copy of the legal move generator with an optional reset of generated moves.
59
+ """Create a copy of the legal move generator with an optional reset of generated moves.
61
60
 
62
61
  Returns:
63
62
  Self: A new instance of the legal move generator with the specified generated moves.
63
+
64
64
  """
65
65
  ...
66
66
 
@@ -70,54 +70,59 @@ class State(Protocol):
70
70
 
71
71
  @property
72
72
  def tag(self) -> StateTag:
73
- """Returns the tag of the content.
73
+ """Return the tag of the content.
74
74
 
75
75
  Returns:
76
76
  StateTag: The tag of the content.
77
+
77
78
  """
78
79
  ...
79
80
 
80
81
  @property
81
82
  def branch_keys(self) -> BranchKeyGeneratorP[BranchKey]:
82
- """Returns the branch keys associated with the content.
83
+ """Return the branch keys associated with the content.
83
84
 
84
85
  Returns:
85
86
  BranchKeyGeneratorP: The branch keys associated with the content.
87
+
86
88
  """
87
89
  ...
88
90
 
89
91
  def branch_name_from_key(self, key: BranchKey) -> str:
90
- """Returns the branch name corresponding to the given branch key.
92
+ """Return the branch name corresponding to the given branch key.
91
93
 
92
94
  Args:
93
95
  key (BranchKey): The branch key.
94
96
 
95
97
  Returns:
96
98
  str: The branch name corresponding to the given branch key.
99
+
97
100
  """
98
101
  ...
99
102
 
100
103
  def branch_key_from_name(self, name: str) -> BranchKey:
101
- """Returns the branch key corresponding to the given branch name.
104
+ """Return the branch key corresponding to the given branch name.
102
105
 
103
106
  Args:
104
107
  name (str): The branch name.
108
+
105
109
  Returns:
106
110
  BranchKey: The branch key corresponding to the given branch name.
111
+
107
112
  """
108
113
  ...
109
114
 
110
115
  def is_game_over(self) -> bool:
111
- """Checks if the game represented by the content is over.
116
+ """Check if the game represented by the content is over.
112
117
 
113
118
  Returns:
114
119
  bool: True if the game is over, False otherwise.
120
+
115
121
  """
116
122
  ...
117
123
 
118
124
  def copy(self, stack: bool, deep_copy_legal_moves: bool = True) -> Self:
119
- """
120
- Create a copy of the current board.
125
+ """Create a copy of the current board.
121
126
 
122
127
  Args:
123
128
  stack (bool): Whether to copy the previous action stack as well. Important in some games.
@@ -125,25 +130,28 @@ class State(Protocol):
125
130
 
126
131
  Returns:
127
132
  BoardChi: A new instance of the BoardChi class with the copied board.
133
+
128
134
  """
129
135
  ...
130
136
 
131
137
  def step(self, branch_key: BranchKey) -> StateModifications | None:
132
- """Advances the state by applying the action corresponding to the given branch key.
138
+ """Advance the state by applying the action corresponding to the given branch key.
133
139
 
134
140
  Args:
135
141
  branch_key (BranchKey): The branch key representing the action to be applied.
136
142
 
137
143
  Returns:
138
144
  StateModifications | None: The modifications to the state after applying the action, or None if the action is invalid.
145
+
139
146
  """
140
147
  ...
141
148
 
142
149
  def pprint(self) -> str:
143
- """Returns a pretty-printed string representation of the content.
150
+ """Return a pretty-printed string representation of the content.
144
151
 
145
152
  Returns:
146
153
  str: A pretty-printed string representation of the content.
154
+
147
155
  """
148
156
  ...
149
157
 
@@ -185,10 +193,11 @@ class HasTurn(Protocol):
185
193
 
186
194
  @property
187
195
  def turn(self) -> Color:
188
- """Returns the tag of the content.
196
+ """Return the tag of the content.
189
197
 
190
198
  Returns:
191
199
  ContentTag: The tag of the content.
200
+
192
201
  """
193
202
  ...
194
203
 
@@ -1,13 +1,23 @@
1
- """
2
- Module for handling game over events.
3
- """
1
+ """Module for handling game over events."""
4
2
 
5
3
  from dataclasses import dataclass
6
- from enum import Enum
4
+ from enum import Enum, StrEnum
7
5
 
8
6
  from .game import Color
9
7
 
10
8
 
9
+ class WinnerNotProperlyDefinedError(ValueError):
10
+ """Winner is not properly defined."""
11
+
12
+
13
+ class OverNotProperlyDefinedError(ValueError):
14
+ """Over is not properly defined."""
15
+
16
+
17
+ class OverEventInvariantError(ValueError):
18
+ """OverEvent reached an invalid state."""
19
+
20
+
11
21
  class HowOver(Enum):
12
22
  """Represents the possible outcomes of a game.
13
23
 
@@ -15,6 +25,7 @@ class HowOver(Enum):
15
25
  WIN (int): Indicates a win.
16
26
  DRAW (int): Indicates a draw.
17
27
  DO_NOT_KNOW_OVER (int): Indicates that the outcome is unknown.
28
+
18
29
  """
19
30
 
20
31
  WIN = 1
@@ -37,6 +48,7 @@ class Winner(Enum):
37
48
  is_none() -> bool: Checks if the winner is None.
38
49
  is_white() -> bool: Checks if the winner is white.
39
50
  is_black() -> bool: Checks if the winner is black.
51
+
40
52
  """
41
53
 
42
54
  WHITE = Color.WHITE
@@ -48,6 +60,7 @@ class Winner(Enum):
48
60
 
49
61
  Returns:
50
62
  bool: True if the winner is NO_KNOWN_WINNER, False otherwise.
63
+
51
64
  """
52
65
  return self is Winner.NO_KNOWN_WINNER
53
66
 
@@ -56,12 +69,10 @@ class Winner(Enum):
56
69
 
57
70
  Returns:
58
71
  bool: True if the winner is white, False otherwise.
72
+
59
73
  """
60
74
  is_white_bool: bool
61
- if not self.is_none():
62
- is_white_bool = self is Winner.WHITE
63
- else:
64
- is_white_bool = False
75
+ is_white_bool = self is Winner.WHITE if not self.is_none() else False
65
76
  return is_white_bool
66
77
 
67
78
  def is_black(self) -> bool:
@@ -69,17 +80,15 @@ class Winner(Enum):
69
80
 
70
81
  Returns:
71
82
  bool: True if the winner is black, False otherwise.
83
+
72
84
  """
73
85
  is_black_bool: bool
74
86
 
75
- if not self.is_none():
76
- is_black_bool = self is Winner.BLACK
77
- else:
78
- is_black_bool = False
87
+ is_black_bool = self is Winner.BLACK if not self.is_none() else False
79
88
  return is_black_bool
80
89
 
81
90
 
82
- class OverTags(str, Enum):
91
+ class OverTags(StrEnum):
83
92
  """Represents the possible tags for game over events.
84
93
 
85
94
  Attributes:
@@ -87,6 +96,7 @@ class OverTags(str, Enum):
87
96
  TAG_WIN_BLACK (str): Tag indicating a win for the black player.
88
97
  TAG_DRAW (str): Tag indicating a draw.
89
98
  TAG_DO_NOT_KNOW (str): Tag indicating an unknown outcome.
99
+
90
100
  """
91
101
 
92
102
  TAG_WIN_WHITE = "Win-Wh"
@@ -119,6 +129,7 @@ class OverEvent:
119
129
  is_winner: Checks if the specified player is the winner.
120
130
  print_info: Prints information about the `OverEvent` object.
121
131
  test: Performs tests on the `OverEvent` object.
132
+
122
133
  """
123
134
 
124
135
  how_over: HowOver = HowOver.DO_NOT_KNOW_OVER
@@ -126,6 +137,7 @@ class OverEvent:
126
137
  termination: Enum | None = None # Optional termination reason
127
138
 
128
139
  def __post_init__(self) -> None:
140
+ """Validate the initial over event state."""
129
141
  assert self.how_over in HowOver
130
142
  assert self.who_is_winner in Winner
131
143
 
@@ -142,24 +154,27 @@ class OverEvent:
142
154
  termination: Enum | None,
143
155
  who_is_winner: Winner = Winner.NO_KNOWN_WINNER,
144
156
  ) -> None:
145
- """Sets the `how_over` and `who_is_winner` attributes.
157
+ """Set the `how_over` and `who_is_winner` attributes.
146
158
 
147
159
  Args:
148
160
  how_over (HowOver): The way the game ended.
161
+ termination (Enum | None): Optional termination reason.
149
162
  who_is_winner (Winner, optional): The winner of the game. Defaults to `Winner.NO_KNOWN_WINNER`.
163
+
150
164
  """
151
165
  self.how_over = how_over
152
166
  self.who_is_winner = who_is_winner
153
167
  self.termination = termination
154
168
 
155
169
  def get_over_tag(self) -> OverTags:
156
- """Returns a tag string used in databases.
170
+ """Return a tag string used in databases.
157
171
 
158
172
  Returns:
159
173
  OverTags: The tag string representing the game outcome.
160
174
 
161
175
  Raises:
162
176
  Exception: If the winner is not properly defined.
177
+
163
178
  """
164
179
  over_tag: OverTags
165
180
  if self.how_over == HowOver.WIN:
@@ -168,49 +183,53 @@ class OverEvent:
168
183
  elif self.who_is_winner.is_black():
169
184
  over_tag = OverTags.TAG_WIN_BLACK
170
185
  else:
171
- raise ValueError("error: winner is not properly defined.")
186
+ raise WinnerNotProperlyDefinedError
172
187
  elif self.how_over == HowOver.DRAW:
173
188
  over_tag = OverTags.TAG_DRAW
174
189
  elif self.how_over == HowOver.DO_NOT_KNOW_OVER:
175
190
  over_tag = OverTags.TAG_DO_NOT_KNOW
176
191
  else:
177
- raise ValueError("error: over is not properly defined.")
192
+ raise OverNotProperlyDefinedError
178
193
  return over_tag
179
194
 
180
195
  def __bool__(self) -> None:
181
- """Raises an exception.
196
+ """Raise an exception.
182
197
 
183
198
  Raises:
184
199
  Exception: Always raises an exception.
200
+
185
201
  """
186
- raise ValueError("Nooooooooooo in over ebvent.py")
202
+ raise OverEventInvariantError
187
203
 
188
204
  def is_over(self) -> bool:
189
- """Checks if the game is over.
205
+ """Check if the game is over.
190
206
 
191
207
  Returns:
192
208
  bool: True if the game is over, False otherwise.
209
+
193
210
  """
194
211
  return self.how_over in {HowOver.WIN, HowOver.DRAW}
195
212
 
196
213
  def is_win(self) -> bool:
197
- """Checks if the game ended with a win.
214
+ """Check if the game ended with a win.
198
215
 
199
216
  Returns:
200
217
  bool: True if the game ended with a win, False otherwise.
218
+
201
219
  """
202
220
  return self.how_over == HowOver.WIN
203
221
 
204
222
  def is_draw(self) -> bool:
205
- """Checks if the game ended with a draw.
223
+ """Check if the game ended with a draw.
206
224
 
207
225
  Returns:
208
226
  bool: True if the game ended with a draw, False otherwise.
227
+
209
228
  """
210
229
  return self.how_over == HowOver.DRAW
211
230
 
212
231
  def is_winner(self, player: Color) -> bool:
213
- """Checks if the specified player is the winner.
232
+ """Check if the specified player is the winner.
214
233
 
215
234
  Args:
216
235
  player (chess.Color): The player to check.
@@ -220,23 +239,25 @@ class OverEvent:
220
239
 
221
240
  Raises:
222
241
  AssertionError: If the `player` argument is not a valid value from the `chess.Color` enum.
242
+
223
243
  """
224
244
  assert player in {Color.WHITE, Color.BLACK}
225
245
 
226
246
  is_winner: bool
227
247
  if self.how_over == HowOver.WIN:
228
- is_winner = bool(
229
- self.who_is_winner == Winner.WHITE
230
- and player == Color.WHITE
231
- or self.who_is_winner == Winner.BLACK
232
- and player == Color.BLACK
233
- )
248
+ winner_color = {
249
+ Winner.WHITE: Color.WHITE,
250
+ Winner.BLACK: Color.BLACK,
251
+ }.get(self.who_is_winner)
252
+
253
+ is_winner = player == winner_color
254
+
234
255
  else:
235
256
  is_winner = False
236
257
  return is_winner
237
258
 
238
259
  def print_info(self) -> None:
239
- """Prints information about the `OverEvent` object."""
260
+ """Print information about the `OverEvent` object."""
240
261
  print(
241
262
  "over_event:",
242
263
  "how_over:",
@@ -1,19 +1,18 @@
1
- """
2
- Policy-related classes and protocols for branch selection in game trees.
3
- """
1
+ """Policy-related classes and protocols for branch selection in game trees."""
4
2
 
3
+ from collections.abc import Callable, Mapping
5
4
  from dataclasses import dataclass
6
- from typing import Mapping, Protocol
5
+ from typing import Protocol, TypeVar
7
6
 
8
7
  from valanga.evaluations import StateEvaluation
9
8
  from valanga.game import BranchKey, BranchName, Seed, State
10
9
 
10
+ NotifyProgressCallable = Callable[[int], None] | None
11
+
11
12
 
12
13
  @dataclass(frozen=True, slots=True)
13
14
  class BranchPolicy:
14
- """
15
- Represents a probability distribution over branches.
16
- """
15
+ """Represents a probability distribution over branches."""
17
16
 
18
17
  probs: Mapping[BranchKey, float] # should sum to ~1.0
19
18
 
@@ -28,15 +27,27 @@ class Recommendation:
28
27
  branch_evals: Mapping[BranchName, StateEvaluation] | None = None
29
28
 
30
29
 
31
- class BranchSelector[StateT: State](Protocol):
30
+ StateT_contra = TypeVar("StateT_contra", bound=State, contravariant=True)
31
+
32
+
33
+ class BranchSelector(Protocol[StateT_contra]):
32
34
  """Protocol for a branch selector."""
33
35
 
34
- def recommend(self, state: StateT, seed: Seed) -> Recommendation:
36
+ def recommend(
37
+ self,
38
+ state: StateT_contra,
39
+ seed: Seed,
40
+ notify_progress: NotifyProgressCallable | None = None,
41
+ ) -> Recommendation:
35
42
  """Given a state and a seed, recommends a branch to take.
43
+
36
44
  Args:
37
45
  state (State): The current state of the game.
38
46
  seed (Seed): A seed for any randomness involved in the selection.
47
+ notify_progress (NotifyProgressCallable | None): Optional callback for progress updates.
48
+
39
49
  Returns:
40
50
  Recommendation: The recommended branch to take.
51
+
41
52
  """
42
53
  ...
@@ -1,6 +1,4 @@
1
- """
2
- Module for the ProgressMessage class.
3
- """
1
+ """Module for the ProgressMessage class."""
4
2
 
5
3
  from dataclasses import dataclass
6
4
 
@@ -9,11 +7,11 @@ from .game import Color
9
7
 
10
8
  @dataclass
11
9
  class PlayerProgressMessage:
12
- """
13
- Represents a message containing evaluation information.
10
+ """Represents a message containing evaluation information.
14
11
 
15
12
  Attributes:
16
13
  evaluation_stock (Any): The evaluation for the stock.
14
+
17
15
  """
18
16
 
19
17
  progress_percent: int | None
@@ -1,6 +1,4 @@
1
- """
2
- Factory for creating content representations from game states and state modifications.
3
- """
1
+ """Factory for creating content representations from game states and state modifications."""
4
2
 
5
3
  from dataclasses import dataclass
6
4
  from typing import Protocol
@@ -11,32 +9,34 @@ from .game import State
11
9
 
12
10
 
13
11
  class CreateFromState[StateT: State, EvalIn](Protocol):
14
- """
15
- Protocol for creating a state representation from a state.
16
- """
12
+ """Protocol for creating a state representation from a state."""
17
13
 
18
- def __call__(self, state: StateT) -> ContentRepresentation[StateT, EvalIn]: ...
14
+ def __call__(self, state: StateT) -> ContentRepresentation[StateT, EvalIn]:
15
+ """Create a content representation from a state."""
16
+ ...
19
17
 
20
18
 
21
19
  class CreateFromStateAndModifications[StateT: State, EvalIn, StateModT](Protocol):
22
- """
23
- Protocol for creating a state representation from a state and modifications.
24
- """
20
+ """Protocol for creating a state representation from a state and modifications."""
25
21
 
26
22
  def __call__(
27
23
  self,
28
24
  state: StateT,
29
25
  state_modifications: StateModT,
30
26
  previous_state_representation: ContentRepresentation[StateT, EvalIn],
31
- ) -> ContentRepresentation[StateT, EvalIn]: ...
27
+ ) -> ContentRepresentation[StateT, EvalIn]:
28
+ """Create a content representation from state modifications."""
29
+ ...
32
30
 
33
31
 
34
32
  @dataclass
35
33
  class RepresentationFactory[StateT: State, EvalIn, StateModT]:
36
34
  """Factory for creating content representations from states and state modifications.
35
+
37
36
  Attributes:
38
37
  create_from_state: Function to create a content representation from a state.
39
38
  create_from_state_and_modifications: Function to create a content representation from a state and state modifications.
39
+
40
40
  """
41
41
 
42
42
  create_from_state: CreateFromState[StateT, EvalIn]
@@ -50,13 +50,16 @@ class RepresentationFactory[StateT: State, EvalIn, StateModT]:
50
50
  previous_state_representation: ContentRepresentation[StateT, EvalIn] | None,
51
51
  modifications: StateModT | None,
52
52
  ) -> ContentRepresentation[StateT, EvalIn]:
53
- """Creates a content representation from a state transition.
53
+ """Create a content representation from a state transition.
54
+
54
55
  Args:
55
56
  state: The current state of the game.
56
57
  previous_state_representation: The content representation of the previous state, or None if not available.
57
58
  modifications: The modifications applied to the previous state to reach the current state, or None if not available.
59
+
58
60
  Returns:
59
61
  ContentRepresentation[StateT, EvalIn]: The content representation of the current state.
62
+
60
63
  """
61
64
  if previous_state_representation is None or modifications is None:
62
65
  return self.create_from_state(state)
@@ -1,6 +1,4 @@
1
- """
2
- Contains the definition of the ContentRepresentation protocol for content representations used in evaluations.
3
- """
1
+ """Contains the definition of the ContentRepresentation protocol for content representations used in evaluations."""
4
2
 
5
3
  from typing import Protocol, TypeVar
6
4
 
@@ -16,19 +14,23 @@ EvalIn_co = TypeVar(
16
14
 
17
15
 
18
16
  class ContentRepresentation[StateT_contra, EvalIn_co](Protocol):
19
- """
20
- Protocol defining the interface for a content representation.
17
+ """Define the interface for a content representation.
18
+
21
19
  It is a function returning the proper input for evaluation by the content evaluator.
22
20
  """
23
21
 
24
22
  def get_evaluator_input(self, state: StateT_contra) -> EvalIn_co:
25
- """
26
- Returns the evaluator input tensor for the content. Content representations have generally a compressed view and complemetary view of state info so to avoid redundancy and have all the necessary info we also give the state as input.
23
+ """Return the evaluator input tensor for the content.
24
+
25
+ Content representations have generally a compressed view and complementary view
26
+ of state info so to avoid redundancy and have all the necessary info we also
27
+ give the state as input.
27
28
 
28
29
  Args:
29
30
  state: The current state of the game.
30
31
 
31
32
  Returns:
32
33
  The evaluator input tensor.
34
+
33
35
  """
34
36
  ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valanga
3
- Version: 0.1.11
3
+ Version: 0.1.14
4
4
  Summary: Shared types and utilities for evaluation
5
5
  Author-email: Victor Gabillon <victorgabillon@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -684,11 +684,11 @@ Requires-Python: >=3.13
684
684
  Description-Content-Type: text/markdown
685
685
  License-File: LICENSE
686
686
  Provides-Extra: test
687
- Requires-Dist: pytest>=8.4.1; extra == "test"
687
+ Requires-Dist: pytest>=9.0.2; extra == "test"
688
688
  Requires-Dist: coverage; extra == "test"
689
689
  Requires-Dist: pytest-cov>=6.0.0; extra == "test"
690
690
  Provides-Extra: lint
691
- Requires-Dist: ruff>=0.14.10; extra == "lint"
691
+ Requires-Dist: ruff>=0.15.0; extra == "lint"
692
692
  Requires-Dist: pylint>=4.0.4; extra == "lint"
693
693
  Provides-Extra: typecheck
694
694
  Requires-Dist: mypy>=1.19.1; extra == "typecheck"
@@ -696,7 +696,7 @@ Requires-Dist: pyright[nodejs]>=1.1.408; extra == "typecheck"
696
696
  Provides-Extra: dev
697
697
  Requires-Dist: tox>=4.32.0; extra == "dev"
698
698
  Requires-Dist: types-PyYAML>=6.0.12.12; extra == "dev"
699
- Requires-Dist: ruff>=0.14.10; extra == "dev"
699
+ Requires-Dist: ruff>=0.15.0; extra == "dev"
700
700
  Requires-Dist: pylint>=4.0.4; extra == "dev"
701
701
  Requires-Dist: black>=25.12.0; extra == "dev"
702
702
  Requires-Dist: mypy>=1.19.1; extra == "dev"
@@ -2,7 +2,7 @@
2
2
  [dev]
3
3
  tox>=4.32.0
4
4
  types-PyYAML>=6.0.12.12
5
- ruff>=0.14.10
5
+ ruff>=0.15.0
6
6
  pylint>=4.0.4
7
7
  black>=25.12.0
8
8
  mypy>=1.19.1
@@ -15,11 +15,11 @@ sphinx-autodoc-typehints
15
15
  pre-commit
16
16
 
17
17
  [lint]
18
- ruff>=0.14.10
18
+ ruff>=0.15.0
19
19
  pylint>=4.0.4
20
20
 
21
21
  [test]
22
- pytest>=8.4.1
22
+ pytest>=9.0.2
23
23
  coverage
24
24
  pytest-cov>=6.0.0
25
25
 
@@ -1,3 +1,5 @@
1
+ """Tests for valanga.over_event module."""
2
+
1
3
  import pytest
2
4
 
3
5
  from valanga.game import Color
@@ -12,6 +14,7 @@ from valanga.over_event import HowOver, OverEvent, OverTags, Winner
12
14
  ],
13
15
  )
14
16
  def test_get_over_tag_win_outcomes(who, expected):
17
+ """Test get_over_tag for win outcomes."""
15
18
  event = OverEvent(how_over=HowOver.WIN, who_is_winner=who)
16
19
 
17
20
  assert event.get_over_tag() == expected
@@ -22,6 +25,7 @@ def test_get_over_tag_win_outcomes(who, expected):
22
25
 
23
26
  @pytest.mark.parametrize("winner", [Winner.WHITE, Winner.BLACK])
24
27
  def test_is_winner_matches_color(winner):
28
+ """Test is_winner method for different winners."""
25
29
  event = OverEvent(how_over=HowOver.WIN, who_is_winner=winner)
26
30
 
27
31
  assert event.is_winner(Color.WHITE) is (winner == Winner.WHITE)
@@ -36,6 +40,7 @@ def test_is_winner_matches_color(winner):
36
40
  ],
37
41
  )
38
42
  def test_get_over_tag_draw_and_unknown(how_over, winner, expected):
43
+ """Test get_over_tag for draw and unknown outcomes."""
39
44
  event = OverEvent(how_over=how_over, who_is_winner=winner)
40
45
 
41
46
  assert event.get_over_tag() == expected
@@ -52,12 +57,14 @@ def test_get_over_tag_draw_and_unknown(how_over, winner, expected):
52
57
  ],
53
58
  )
54
59
  def test_post_init_validates_winner_configuration(how_over, who_is_winner):
60
+ """Test that invalid configurations raise AssertionError."""
55
61
  with pytest.raises(AssertionError):
56
62
  OverEvent(how_over=how_over, who_is_winner=who_is_winner)
57
63
 
58
64
 
59
65
  @pytest.mark.parametrize("player", [Color.WHITE, Color.BLACK])
60
66
  def test_is_winner_requires_win(player):
67
+ """Test that is_winner returns False when the game is a draw."""
61
68
  event = OverEvent(how_over=HowOver.DRAW, who_is_winner=Winner.NO_KNOWN_WINNER)
62
69
 
63
70
  assert event.is_winner(player) is False
@@ -71,6 +78,7 @@ def test_is_winner_requires_win(player):
71
78
  ],
72
79
  )
73
80
  def test_get_over_tag_invalid_configurations(how_over, expected_exception):
81
+ """Test that get_over_tag raises ValueError for invalid configurations."""
74
82
  event = OverEvent(how_over=HowOver.WIN, who_is_winner=Winner.WHITE)
75
83
  event.how_over = how_over # mutate to bypass post-init validation
76
84
  event.who_is_winner = Winner.NO_KNOWN_WINNER
@@ -87,12 +95,14 @@ def test_get_over_tag_invalid_configurations(how_over, expected_exception):
87
95
  ],
88
96
  )
89
97
  def test_test_method_for_wins(how_over, who_is_winner):
98
+ """Test the test method for win outcomes."""
90
99
  event = OverEvent(how_over=how_over, who_is_winner=who_is_winner)
91
100
 
92
101
  event.test()
93
102
 
94
103
 
95
104
  def test_test_method_invalid_draw_configuration():
105
+ """Test that the test method raises AssertionError for invalid draw configuration."""
96
106
  event = OverEvent(how_over=HowOver.DRAW, who_is_winner=Winner.NO_KNOWN_WINNER)
97
107
 
98
108
  with pytest.raises(AssertionError):
@@ -100,6 +110,7 @@ def test_test_method_invalid_draw_configuration():
100
110
 
101
111
 
102
112
  def test_bool_raises():
113
+ """Test that __bool__ raises ValueError."""
103
114
  event = OverEvent()
104
115
 
105
116
  with pytest.raises(ValueError):
@@ -1,10 +1,10 @@
1
1
  """Placeholder test file."""
2
2
 
3
+ import valanga
4
+
3
5
 
4
6
  def test_package_imports():
5
7
  """Verify the package can be imported."""
6
- import valanga
7
-
8
8
  assert valanga is not None
9
9
 
10
10
 
@@ -1,16 +1,22 @@
1
+ """Docstring for tests.test_representation_factory."""
2
+
1
3
  from collections import Counter
2
4
 
3
5
  from valanga.representation_factory import RepresentationFactory
4
6
 
5
7
 
6
8
  class DummyRepresentation:
9
+ """A dummy representation class for testing purposes."""
10
+
7
11
  pass
8
12
 
9
13
 
10
14
  def test_create_from_transition_root_uses_full_creator():
15
+ """At the root, create_from_state should be used even if modifications are None."""
11
16
  calls = Counter()
12
17
 
13
18
  def create_from_state(state):
19
+ """Mock create_from_state function."""
14
20
  calls["create_from_state"] += 1
15
21
  return ("state", state)
16
22
 
@@ -27,6 +33,7 @@ def test_create_from_transition_root_uses_full_creator():
27
33
 
28
34
 
29
35
  def test_create_from_transition_no_modifications_falls_back_to_state():
36
+ """If no modifications are provided, create_from_state should be used."""
30
37
  calls = Counter()
31
38
 
32
39
  def create_from_state(state):
@@ -48,11 +55,13 @@ def test_create_from_transition_no_modifications_falls_back_to_state():
48
55
 
49
56
 
50
57
  def test_create_from_transition_with_modifications():
58
+ """If modifications are provided, create_from_state_and_modifications should be used."""
51
59
  calls = Counter()
52
60
 
53
61
  def create_from_state_and_modifications(
54
62
  state, state_modifications, previous_state_representation
55
63
  ):
64
+ """Mock create_from_state_and_modifications function."""
56
65
  calls["create_from_state_and_modifications"] += 1
57
66
  return (state, state_modifications, previous_state_representation)
58
67
 
File without changes
File without changes
File without changes
@@ -20,23 +20,23 @@ from .representation_factory import RepresentationFactory
20
20
  from .represention_for_evaluation import ContentRepresentation
21
21
 
22
22
  __all__ = [
23
- "ForcedOutcome",
24
- "FloatyStateEvaluation",
25
- "StateEvaluation",
26
- "EvalItem",
27
- "OverEvent",
28
- "Color",
29
- "WHITE",
30
23
  "BLACK",
24
+ "WHITE",
25
+ "BranchKey",
26
+ "BranchKeyGeneratorP",
27
+ "Color",
31
28
  "ColorIndex",
32
- "HasTurn",
33
- "TurnState",
34
29
  "ContentRepresentation",
30
+ "EvalItem",
31
+ "FloatyStateEvaluation",
32
+ "ForcedOutcome",
33
+ "HasTurn",
34
+ "OverEvent",
35
+ "PlayerProgressMessage",
35
36
  "RepresentationFactory",
36
37
  "State",
38
+ "StateEvaluation",
37
39
  "StateModifications",
38
- "BranchKeyGeneratorP",
39
- "BranchKey",
40
- "PlayerProgressMessage",
41
40
  "StateTag",
41
+ "TurnState",
42
42
  ]
File without changes