valanga 0.1.1__tar.gz → 0.1.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 (24) hide show
  1. {valanga-0.1.1/src/valanga.egg-info → valanga-0.1.3}/PKG-INFO +8 -2
  2. valanga-0.1.3/README.md +43 -0
  3. {valanga-0.1.1 → valanga-0.1.3}/pyproject.toml +53 -3
  4. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/__init__.py +2 -2
  5. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/evaluations.py +1 -1
  6. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/game.py +3 -1
  7. valanga-0.1.3/src/valanga/policy.py +42 -0
  8. {valanga-0.1.1 → valanga-0.1.3/src/valanga.egg-info}/PKG-INFO +8 -2
  9. {valanga-0.1.1 → valanga-0.1.3}/src/valanga.egg-info/SOURCES.txt +4 -1
  10. {valanga-0.1.1 → valanga-0.1.3}/src/valanga.egg-info/requires.txt +9 -1
  11. valanga-0.1.3/tests/test_over_event.py +106 -0
  12. valanga-0.1.3/tests/test_representation_factory.py +70 -0
  13. valanga-0.1.1/README.md +0 -2
  14. {valanga-0.1.1 → valanga-0.1.3}/LICENSE +0 -0
  15. {valanga-0.1.1 → valanga-0.1.3}/setup.cfg +0 -0
  16. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/evaluator_types.py +0 -0
  17. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/over_event.py +0 -0
  18. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/progress_messsage.py +0 -0
  19. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/py.typed +0 -0
  20. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/representation_factory.py +0 -0
  21. {valanga-0.1.1 → valanga-0.1.3}/src/valanga/represention_for_evaluation.py +0 -0
  22. {valanga-0.1.1 → valanga-0.1.3}/src/valanga.egg-info/dependency_links.txt +0 -0
  23. {valanga-0.1.1 → valanga-0.1.3}/src/valanga.egg-info/top_level.txt +0 -0
  24. {valanga-0.1.1 → valanga-0.1.3}/tests/test_placeholder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valanga
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Shared types and utilities for evalaution
5
5
  Requires-Python: >=3.13
6
6
  License-File: LICENSE
@@ -8,6 +8,12 @@ Provides-Extra: test
8
8
  Requires-Dist: pytest>=8.4.1; extra == "test"
9
9
  Requires-Dist: coverage; extra == "test"
10
10
  Requires-Dist: pytest-cov>=6.0.0; extra == "test"
11
+ Provides-Extra: lint
12
+ Requires-Dist: ruff>=0.14.10; extra == "lint"
13
+ Requires-Dist: pylint>=4.0.4; extra == "lint"
14
+ Provides-Extra: typecheck
15
+ Requires-Dist: mypy>=1.19.1; extra == "typecheck"
16
+ Requires-Dist: pyright[nodejs]>=1.1.408; extra == "typecheck"
11
17
  Provides-Extra: dev
12
18
  Requires-Dist: tox>=4.32.0; extra == "dev"
13
19
  Requires-Dist: types-PyYAML>=6.0.12.12; extra == "dev"
@@ -15,7 +21,7 @@ Requires-Dist: ruff>=0.14.10; extra == "dev"
15
21
  Requires-Dist: pylint>=4.0.4; extra == "dev"
16
22
  Requires-Dist: black>=25.12.0; extra == "dev"
17
23
  Requires-Dist: mypy>=1.19.1; extra == "dev"
18
- Requires-Dist: pyright>=1.1.407; extra == "dev"
24
+ Requires-Dist: pyright[nodejs]>=1.1.408; extra == "dev"
19
25
  Requires-Dist: build; extra == "dev"
20
26
  Requires-Dist: twine; extra == "dev"
21
27
  Requires-Dist: sphinx; extra == "dev"
@@ -0,0 +1,43 @@
1
+ # valanga
2
+
3
+ ![Python 3.13](https://img.shields.io/badge/python-3.13-blue?logo=python)
4
+ [![Tests](https://github.com/victorgabillon/valanga/actions/workflows/ci.yaml/badge.svg?label=tests)](https://github.com/victorgabillon/valanga/actions/workflows/ci.yaml)
5
+ [![License: GPL v3](https://img.shields.io/github/license/victorgabillon/valanga)](LICENSE)
6
+
7
+ Shared Python types and lightweight utilities for describing turn-based games and their evaluations. The package exposes protocols for representing states, outcomes, and state representations that other libraries can build on.
8
+
9
+ ## Key concepts
10
+ - **Game primitives**: `Color`, `BranchKey`, `State`, and related protocols describe whose turn it is, how to enumerate legal branches, and how to copy or advance a state. 【F:src/valanga/game.py†L14-L95】
11
+ - **Evaluations**: `FloatyStateEvaluation` captures heuristic scores while `ForcedOutcome` records a definitive result plus the line of play that forces it. Both are grouped under `BoardEvaluation`. 【F:src/valanga/evaluations.py†L13-L43】
12
+ - **Game termination**: `OverEvent` combines how a game ended with who won, backed by `HowOver` and `Winner` enums. Utility helpers like `is_over()` and `get_over_tag()` simplify downstream checks. 【F:src/valanga/over_event.py†L8-L115】
13
+ - **State representations**: `ContentRepresentation` defines how to turn a `State` into evaluator input, and `RepresentationFactory` builds or updates those representations from states and modifications. 【F:src/valanga/represention_for_evaluation.py†L9-L22】【F:src/valanga/representation_factory.py†L7-L55】
14
+ - **Progress reporting**: `PlayerProgressMessage` communicates per-player progress percentages during long-running work. 【F:src/valanga/progress_messsage.py†L9-L18】
15
+
16
+ ## Installation
17
+ ```bash
18
+ pip install .
19
+ ```
20
+ The project targets Python 3.13 and has no required runtime dependencies.
21
+
22
+ ## Quick start
23
+ Below is a minimal example that marks a finished game and obtains an evaluation structure:
24
+ ```python
25
+ from valanga import ForcedOutcome, OverEvent
26
+ from valanga.over_event import HowOver, Winner
27
+
28
+ # A checkmate where white wins.
29
+ over_event = OverEvent(how_over=HowOver.WIN, who_is_winner=Winner.WHITE)
30
+
31
+ # Record the forced outcome and the line of optimal moves that lead to it.
32
+ forced = ForcedOutcome(outcome=over_event, line=["e2e4", "e7e5"])
33
+ print(forced.outcome.get_over_tag()) # -> OverTags.TAG_WIN_WHITE
34
+ ```
35
+
36
+ To wire in a custom state representation, supply callables to `RepresentationFactory` that know how to build representations from a `State` and its modifications.
37
+
38
+ ## Development
39
+ Install the development dependencies and run the test suite:
40
+ ```bash
41
+ pip install -e '.[dev]'
42
+ pytest
43
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "valanga"
7
- version = "0.1.1"
7
+ version = "0.1.3"
8
8
  description = "Shared types and utilities for evalaution"
9
9
  requires-python = ">=3.13"
10
10
  dependencies = []
@@ -20,6 +20,16 @@ test = [
20
20
  "pytest-cov>=6.0.0"
21
21
  ]
22
22
 
23
+ lint = [
24
+ "ruff>=0.14.10",
25
+ "pylint>=4.0.4",
26
+ ]
27
+
28
+ typecheck = [
29
+ "mypy>=1.19.1",
30
+ "pyright[nodejs]>=1.1.408",
31
+ ]
32
+
23
33
 
24
34
  dev = [
25
35
  # Development dependencies (includes test)
@@ -33,7 +43,7 @@ dev = [
33
43
 
34
44
  # Type checking
35
45
  "mypy>=1.19.1",
36
- "pyright>=1.1.407 ",
46
+ "pyright[nodejs]>=1.1.408 ",
37
47
 
38
48
  # Building & publishing
39
49
  "build",
@@ -150,4 +160,44 @@ reportMissingImports = true
150
160
  reportUnusedImport = true
151
161
  reportMissingTypeStubs = false
152
162
  reportUnknownMemberType = false
153
- reportPrivateImportUsage = false
163
+ reportPrivateImportUsage = false
164
+
165
+
166
+ #################### TOX ####################
167
+
168
+ [tool.tox]
169
+ min_version = "4.32.0"
170
+ env_list = ["py313", "lint", "typecheck"]
171
+ isolated_build = true
172
+
173
+ [tool.tox.env_run_base]
174
+ base_python = ["python3.13"]
175
+
176
+ [tool.tox.env.py313]
177
+ description = "Run tests"
178
+ extras = ["test"]
179
+ commands = [["pytest", "{posargs}"]]
180
+
181
+ [tool.tox.env.lint]
182
+ description = "ruff + pylint (fast, no package install)"
183
+ package = "skip"
184
+ deps = ["ruff>=0.14.10", "pylint>=4.0.4"]
185
+ set_env = { PYTHONPATH = "{toxinidir}/src" }
186
+ commands = [
187
+ ["python", "-m", "ruff", "check", "src", "tests"],
188
+ ["python", "-m", "ruff", "format", "--check", "src", "tests"],
189
+ ["python", "-m", "pylint", "src/valanga"],
190
+ ]
191
+
192
+ [tool.tox.env.typecheck]
193
+ description = "mypy + pyright (fast, no package install)"
194
+ package = "skip"
195
+ deps = ["mypy>=1.19.1", "pyright[nodejs]>=1.1.408"]
196
+ set_env = { MYPYPATH = "{toxinidir}/src" }
197
+ commands = [
198
+ ["python", "-m", "mypy", "--config-file", "pyproject.toml", "src/valanga"],
199
+ ["python", "-m", "pyright", "src/valanga"],
200
+ ]
201
+
202
+
203
+ #################### END TOX ####################
@@ -1,6 +1,6 @@
1
1
  """Common types and utilities shared by multiple libraries."""
2
2
 
3
- from .evaluations import BoardEvaluation, EvalItem, FloatyStateEvaluation, ForcedOutcome
3
+ from .evaluations import EvalItem, FloatyStateEvaluation, ForcedOutcome, StateEvaluation
4
4
  from .game import (
5
5
  BLACK,
6
6
  WHITE,
@@ -22,7 +22,7 @@ from .represention_for_evaluation import ContentRepresentation
22
22
  __all__ = [
23
23
  "ForcedOutcome",
24
24
  "FloatyStateEvaluation",
25
- "BoardEvaluation",
25
+ "StateEvaluation",
26
26
  "EvalItem",
27
27
  "OverEvent",
28
28
  "Color",
@@ -55,4 +55,4 @@ class FloatyStateEvaluation:
55
55
  value_white: float | None
56
56
 
57
57
 
58
- BoardEvaluation = FloatyStateEvaluation | ForcedOutcome
58
+ StateEvaluation = FloatyStateEvaluation | ForcedOutcome
@@ -6,6 +6,8 @@ from collections.abc import Hashable
6
6
  from enum import Enum
7
7
  from typing import Annotated, Iterator, Protocol, Self, Sequence, TypeVar
8
8
 
9
+ type Seed = Annotated[int, "seed"]
10
+
9
11
  type StateTag = Annotated[Hashable, "A label or identifier for a state in a game"]
10
12
 
11
13
  type StateModifications = Annotated[
@@ -16,7 +18,7 @@ type StateModifications = Annotated[
16
18
  type BranchKey = Annotated[Hashable, "A label or identifier for a branch in a tree"]
17
19
 
18
20
 
19
- T_co = TypeVar("T_co", bound=BranchKey, covariant=True)
21
+ T_co = TypeVar("T_co", bound=BranchKey, covariant=True, default=BranchKey)
20
22
 
21
23
 
22
24
  class BranchKeyGeneratorP(Protocol[T_co]):
@@ -0,0 +1,42 @@
1
+ """
2
+ Policy-related classes and protocols for branch selection in game trees.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Mapping, Protocol
7
+
8
+ from valanga.evaluations import StateEvaluation
9
+ from valanga.game import BranchKey, Seed, State
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class BranchPolicy:
14
+ """
15
+ Represents a probability distribution over branches.
16
+ """
17
+
18
+ probs: Mapping[BranchKey, float] # should sum to ~1.0
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class Recommendation:
23
+ """A recommendation for a specific branch in a tree node."""
24
+
25
+ recommended_key: BranchKey
26
+ evaluation: StateEvaluation | None = None
27
+ policy: BranchPolicy | None = None
28
+ branch_evals: Mapping[BranchKey, StateEvaluation] | None = None
29
+
30
+
31
+ class BranchSelector(Protocol):
32
+ """Protocol for a branch selector."""
33
+
34
+ def recommend(self, state: State, seed: Seed) -> Recommendation:
35
+ """Given a state and a seed, recommends a branch to take.
36
+ Args:
37
+ state (State): The current state of the game.
38
+ seed (Seed): A seed for any randomness involved in the selection.
39
+ Returns:
40
+ Recommendation: The recommended branch to take.
41
+ """
42
+ ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: valanga
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Shared types and utilities for evalaution
5
5
  Requires-Python: >=3.13
6
6
  License-File: LICENSE
@@ -8,6 +8,12 @@ Provides-Extra: test
8
8
  Requires-Dist: pytest>=8.4.1; extra == "test"
9
9
  Requires-Dist: coverage; extra == "test"
10
10
  Requires-Dist: pytest-cov>=6.0.0; extra == "test"
11
+ Provides-Extra: lint
12
+ Requires-Dist: ruff>=0.14.10; extra == "lint"
13
+ Requires-Dist: pylint>=4.0.4; extra == "lint"
14
+ Provides-Extra: typecheck
15
+ Requires-Dist: mypy>=1.19.1; extra == "typecheck"
16
+ Requires-Dist: pyright[nodejs]>=1.1.408; extra == "typecheck"
11
17
  Provides-Extra: dev
12
18
  Requires-Dist: tox>=4.32.0; extra == "dev"
13
19
  Requires-Dist: types-PyYAML>=6.0.12.12; extra == "dev"
@@ -15,7 +21,7 @@ Requires-Dist: ruff>=0.14.10; extra == "dev"
15
21
  Requires-Dist: pylint>=4.0.4; extra == "dev"
16
22
  Requires-Dist: black>=25.12.0; extra == "dev"
17
23
  Requires-Dist: mypy>=1.19.1; extra == "dev"
18
- Requires-Dist: pyright>=1.1.407; extra == "dev"
24
+ Requires-Dist: pyright[nodejs]>=1.1.408; extra == "dev"
19
25
  Requires-Dist: build; extra == "dev"
20
26
  Requires-Dist: twine; extra == "dev"
21
27
  Requires-Dist: sphinx; extra == "dev"
@@ -6,6 +6,7 @@ src/valanga/evaluations.py
6
6
  src/valanga/evaluator_types.py
7
7
  src/valanga/game.py
8
8
  src/valanga/over_event.py
9
+ src/valanga/policy.py
9
10
  src/valanga/progress_messsage.py
10
11
  src/valanga/py.typed
11
12
  src/valanga/representation_factory.py
@@ -15,4 +16,6 @@ src/valanga.egg-info/SOURCES.txt
15
16
  src/valanga.egg-info/dependency_links.txt
16
17
  src/valanga.egg-info/requires.txt
17
18
  src/valanga.egg-info/top_level.txt
18
- tests/test_placeholder.py
19
+ tests/test_over_event.py
20
+ tests/test_placeholder.py
21
+ tests/test_representation_factory.py
@@ -6,7 +6,7 @@ ruff>=0.14.10
6
6
  pylint>=4.0.4
7
7
  black>=25.12.0
8
8
  mypy>=1.19.1
9
- pyright>=1.1.407
9
+ pyright[nodejs]>=1.1.408
10
10
  build
11
11
  twine
12
12
  sphinx
@@ -14,7 +14,15 @@ sphinx-rtd-theme
14
14
  sphinx-autodoc-typehints
15
15
  pre-commit
16
16
 
17
+ [lint]
18
+ ruff>=0.14.10
19
+ pylint>=4.0.4
20
+
17
21
  [test]
18
22
  pytest>=8.4.1
19
23
  coverage
20
24
  pytest-cov>=6.0.0
25
+
26
+ [typecheck]
27
+ mypy>=1.19.1
28
+ pyright[nodejs]>=1.1.408
@@ -0,0 +1,106 @@
1
+ import pytest
2
+
3
+ from valanga.game import Color
4
+ from valanga.over_event import HowOver, OverEvent, OverTags, Winner
5
+
6
+
7
+ @pytest.mark.parametrize(
8
+ "who, expected",
9
+ [
10
+ (Winner.WHITE, OverTags.TAG_WIN_WHITE),
11
+ (Winner.BLACK, OverTags.TAG_WIN_BLACK),
12
+ ],
13
+ )
14
+ def test_get_over_tag_win_outcomes(who, expected):
15
+ event = OverEvent(how_over=HowOver.WIN, who_is_winner=who)
16
+
17
+ assert event.get_over_tag() == expected
18
+ assert event.is_over() is True
19
+ assert event.is_win() is True
20
+ assert event.is_draw() is False
21
+
22
+
23
+ @pytest.mark.parametrize("winner", [Winner.WHITE, Winner.BLACK])
24
+ def test_is_winner_matches_color(winner):
25
+ event = OverEvent(how_over=HowOver.WIN, who_is_winner=winner)
26
+
27
+ assert event.is_winner(Color.WHITE) is (winner == Winner.WHITE)
28
+ assert event.is_winner(Color.BLACK) is (winner == Winner.BLACK)
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ "how_over, winner, expected",
33
+ [
34
+ (HowOver.DRAW, Winner.NO_KNOWN_WINNER, OverTags.TAG_DRAW),
35
+ (HowOver.DO_NOT_KNOW_OVER, Winner.NO_KNOWN_WINNER, OverTags.TAG_DO_NOT_KNOW),
36
+ ],
37
+ )
38
+ def test_get_over_tag_draw_and_unknown(how_over, winner, expected):
39
+ event = OverEvent(how_over=how_over, who_is_winner=winner)
40
+
41
+ assert event.get_over_tag() == expected
42
+ assert event.is_over() is (how_over == HowOver.DRAW)
43
+ assert event.is_win() is False
44
+ assert event.is_draw() is (how_over == HowOver.DRAW)
45
+
46
+
47
+ @pytest.mark.parametrize(
48
+ "how_over, who_is_winner",
49
+ [
50
+ (HowOver.WIN, Winner.NO_KNOWN_WINNER),
51
+ (HowOver.DRAW, Winner.WHITE),
52
+ ],
53
+ )
54
+ def test_post_init_validates_winner_configuration(how_over, who_is_winner):
55
+ with pytest.raises(AssertionError):
56
+ OverEvent(how_over=how_over, who_is_winner=who_is_winner)
57
+
58
+
59
+ @pytest.mark.parametrize("player", [Color.WHITE, Color.BLACK])
60
+ def test_is_winner_requires_win(player):
61
+ event = OverEvent(how_over=HowOver.DRAW, who_is_winner=Winner.NO_KNOWN_WINNER)
62
+
63
+ assert event.is_winner(player) is False
64
+
65
+
66
+ @pytest.mark.parametrize(
67
+ "how_over, expected_exception",
68
+ [
69
+ (HowOver.WIN, ValueError),
70
+ (None, ValueError),
71
+ ],
72
+ )
73
+ def test_get_over_tag_invalid_configurations(how_over, expected_exception):
74
+ event = OverEvent(how_over=HowOver.WIN, who_is_winner=Winner.WHITE)
75
+ event.how_over = how_over # mutate to bypass post-init validation
76
+ event.who_is_winner = Winner.NO_KNOWN_WINNER
77
+
78
+ with pytest.raises(expected_exception):
79
+ event.get_over_tag()
80
+
81
+
82
+ @pytest.mark.parametrize(
83
+ "how_over, who_is_winner",
84
+ [
85
+ (HowOver.WIN, Winner.WHITE),
86
+ (HowOver.WIN, Winner.BLACK),
87
+ ],
88
+ )
89
+ def test_test_method_for_wins(how_over, who_is_winner):
90
+ event = OverEvent(how_over=how_over, who_is_winner=who_is_winner)
91
+
92
+ event.test()
93
+
94
+
95
+ def test_test_method_invalid_draw_configuration():
96
+ event = OverEvent(how_over=HowOver.DRAW, who_is_winner=Winner.NO_KNOWN_WINNER)
97
+
98
+ with pytest.raises(AssertionError):
99
+ event.test()
100
+
101
+
102
+ def test_bool_raises():
103
+ event = OverEvent()
104
+
105
+ with pytest.raises(ValueError):
106
+ bool(event)
@@ -0,0 +1,70 @@
1
+ from collections import Counter
2
+
3
+ from valanga.representation_factory import RepresentationFactory
4
+
5
+
6
+ class DummyRepresentation:
7
+ pass
8
+
9
+
10
+ def test_create_from_transition_root_uses_full_creator():
11
+ calls = Counter()
12
+
13
+ def create_from_state(state):
14
+ calls["create_from_state"] += 1
15
+ return ("state", state)
16
+
17
+ factory = RepresentationFactory(
18
+ create_from_state=create_from_state, create_from_state_and_modifications=None
19
+ )
20
+
21
+ result = factory.create_from_transition(
22
+ state="root", previous_state_representation=None, modifications=None
23
+ )
24
+
25
+ assert result == ("state", "root")
26
+ assert calls["create_from_state"] == 1
27
+
28
+
29
+ def test_create_from_transition_no_modifications_falls_back_to_state():
30
+ calls = Counter()
31
+
32
+ def create_from_state(state):
33
+ calls["create_from_state"] += 1
34
+ return ("state", state)
35
+
36
+ factory = RepresentationFactory(
37
+ create_from_state=create_from_state, create_from_state_and_modifications=None
38
+ )
39
+
40
+ result = factory.create_from_transition(
41
+ state="child",
42
+ previous_state_representation=DummyRepresentation(),
43
+ modifications=None,
44
+ )
45
+
46
+ assert result == ("state", "child")
47
+ assert calls["create_from_state"] == 1
48
+
49
+
50
+ def test_create_from_transition_with_modifications():
51
+ calls = Counter()
52
+
53
+ def create_from_state_and_modifications(
54
+ state, state_modifications, previous_state_representation
55
+ ):
56
+ calls["create_from_state_and_modifications"] += 1
57
+ return (state, state_modifications, previous_state_representation)
58
+
59
+ factory = RepresentationFactory(
60
+ create_from_state=lambda state: ("unused", state),
61
+ create_from_state_and_modifications=create_from_state_and_modifications,
62
+ )
63
+
64
+ previous = DummyRepresentation()
65
+ result = factory.create_from_transition(
66
+ state="child", previous_state_representation=previous, modifications="delta"
67
+ )
68
+
69
+ assert result == ("child", "delta", previous)
70
+ assert calls["create_from_state_and_modifications"] == 1
valanga-0.1.1/README.md DELETED
@@ -1,2 +0,0 @@
1
- # valanga
2
- Common objects for evaluations
File without changes
File without changes
File without changes