Files
dotfiles/python/gems/domain/requirements.py
T
Richie add7a6a848
treefmt / nix fmt (pull_request) Successful in 6s
test ebook search / test-ebook-search (pull_request) Successful in 39s
pytest / pytest (pull_request) Successful in 40s
build_systems / build-brain (pull_request) Successful in 1m0s
build_systems / build-bob (pull_request) Successful in 1m2s
build_systems / build-rhapsody-in-green (pull_request) Successful in 1m33s
build_systems / build-jeeves (pull_request) Successful in 2m25s
test ebook search / test-ebook-search (push) Successful in 35s
build_systems / build-brain (push) Successful in 37s
build_systems / build-bob (push) Successful in 37s
build_systems / build-jeeves (push) Successful in 2m17s
treefmt / nix fmt (push) Successful in 5s
pytest / pytest (push) Successful in 29s
build_systems / build-rhapsody-in-green (push) Successful in 49s
feat(gems): add multiplayer gem game with custom content packs
- implement FastAPI and HTMX lobby and game interfaces
- support up to four human and AI-controlled players
- add configurable rules, victory conditions, and expansion modules
- support validated JSON cards, patrons, objectives, and outposts
2026-07-22 20:47:03 -04:00

59 lines
2.4 KiB
Python

"""Requirement evaluation shared by patrons, objectives, outposts, and AI."""
from __future__ import annotations
from .models import Requirement, RequirementKind
def requirements_met(requirements: list[Requirement], bonuses: dict[str, int]) -> bool:
"""Return whether one assignment satisfies all fixed and wildcard clauses."""
chosen: dict[str, str] = {}
def visit(index: int) -> bool:
if index == len(requirements):
return True
requirement = requirements[index]
if requirement.kind == RequirementKind.COLOR:
if bonuses.get(requirement.resource or "", 0) < requirement.count:
return False
chosen[requirement.id] = requirement.resource or ""
return visit(index + 1)
forbidden = set(requirement.exclude)
forbidden.update(chosen[item] for item in requirement.distinct_from if item in chosen)
for resource, amount in bonuses.items():
if resource in forbidden or amount < requirement.count:
continue
chosen[requirement.id] = resource
if visit(index + 1):
return True
chosen.pop(requirement.id, None)
return False
fixed = [item for item in requirements if item.kind == RequirementKind.COLOR]
flexible = [item for item in requirements if item.kind == RequirementKind.ANY_COLOR]
return visit_ordered([*fixed, *flexible], bonuses, chosen)
def visit_ordered(requirements: list[Requirement], bonuses: dict[str, int], chosen: dict[str, str]) -> bool:
"""Backtracking evaluator kept separate for straightforward unit testing."""
if not requirements:
return True
requirement, *rest = requirements
if requirement.kind == RequirementKind.COLOR:
resource = requirement.resource or ""
if bonuses.get(resource, 0) < requirement.count:
return False
chosen[requirement.id] = resource
return visit_ordered(rest, bonuses, chosen)
forbidden = set(requirement.exclude)
forbidden.update(chosen[item] for item in requirement.distinct_from if item in chosen)
for resource, amount in bonuses.items():
if resource in forbidden or amount < requirement.count:
continue
chosen[requirement.id] = resource
if visit_ordered(rest, bonuses, chosen):
return True
chosen.pop(requirement.id, None)
return False