"""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