33This module contains the :class:`Battle` class, which speciefies how each type of battle is fought and scored,
44some basic battle types, and related classed.
55"""
6+
67from abc import abstractmethod
78from collections .abc import Iterable
89from dataclasses import dataclass , field
@@ -201,8 +202,7 @@ async def run(
201202 * ,
202203 with_results : Literal [False ] = False ,
203204 ** kwargs : Unpack [RunKwargs ],
204- ) -> Fight :
205- ...
205+ ) -> Fight : ...
206206
207207 @overload
208208 async def run (
@@ -211,8 +211,7 @@ async def run(
211211 * ,
212212 with_results : Literal [True ],
213213 ** kwargs : Unpack [RunKwargs ],
214- ) -> tuple [Fight , GeneratorResult , SolverResult | None ]:
215- ...
214+ ) -> tuple [Fight , GeneratorResult , SolverResult | None ]: ...
216215
217216 async def run (
218217 self ,
@@ -250,12 +249,7 @@ async def run(
250249 The resulting info about the executed fight, and the results if the flag has been set.
251250 """
252251 gen_result , sol_result = await self .run_raw (max_size = max_size , ** kwargs )
253- if gen_result .instance is None or gen_result .solution is None :
254- score = 1
255- elif sol_result is None or sol_result .solution is None :
256- score = 0
257- else :
258- score = self .calculate_score (gen_result , sol_result )
252+ score = self .calculate_score (gen_result , sol_result )
259253 fight = Fight .from_results (
260254 score = score ,
261255 max_size = max_size ,
@@ -321,7 +315,7 @@ async def run_raw(
321315 )
322316 return gen_result , sol_result
323317
324- def calculate_score (self , gen_result : GeneratorResult , sol_result : SolverResult ) -> float :
318+ def calculate_score (self , gen_result : GeneratorResult , sol_result : SolverResult | None ) -> float :
325319 """Calculates the score achieved by the solver in this fight.
326320
327321 Both results need to contain all instance and/or solution data required.
@@ -333,9 +327,25 @@ def calculate_score(self, gen_result: GeneratorResult, sol_result: SolverResult)
333327 Returns:
334328 A number in [0, 1] with higher numbers meaning the solver performed better.
335329 """
336- assert gen_result .instance is not None
337- assert sol_result .solution is not None
330+ # We first need to check whether the generator somehow failed. This can happen in three cases:
331+ # it doesn't produce an instance, it creates an invalid instance or it doesn't create a needed solution
332+ # note that gen_result.instance and gen_result.error will both contain data if the generator outputted an
333+ # invalid instance and/or solution
334+ if (
335+ gen_result .instance is None
336+ or gen_result .error is not None
337+ or (self .problem .with_solution and gen_result .solution is None )
338+ ):
339+ return 1
340+ # sol_result is None only if the generator failed, which needs to be caught by the above check
341+ assert sol_result is not None
342+ # The solver failed if it didn't output a solution or the solution contains some error
343+ if sol_result .solution is None or sol_result .error is not None :
344+ return 0
345+
338346 if self .problem .with_solution :
347+ # we need this assert since type checkers can't see the dependency between self.problem.with_solution
348+ # and gen_result.solution that is created by the last check in the first "if"
339349 assert gen_result .solution is not None
340350 score = self .problem .score (
341351 gen_result .instance , solver_solution = sol_result .solution , generator_solution = gen_result .solution
@@ -449,8 +459,7 @@ class FallbackConfig(Config):
449459
450460 if TYPE_CHECKING :
451461 # to hint that we're gonna fill this with arbitrary data belonging to some supposed battle type
452- def __getattr__ (self , attr : str , / ) -> Any :
453- ...
462+ def __getattr__ (self , attr : str , / ) -> Any : ...
454463
455464 class UiData (BaseModel ):
456465 """Object containing custom diplay data.
@@ -705,13 +714,13 @@ class Config(Battle.Config):
705714 """Number of fights that will be fought."""
706715 weighting : Annotated [float , Ge (0 )] = 1.1
707716 """How much each successive fight should be weighted more than the previous."""
708- scores : set [Role ] = {Role .generator , Role .solver } # noqa: RUF012
717+ scores : set [Role ] = {Role .generator , Role .solver } # noqa: RUF012
709718 """Who to show each fight's scores to."""
710- instances : set [Role ] = {Role .generator , Role .solver } # noqa: RUF012
719+ instances : set [Role ] = {Role .generator , Role .solver } # noqa: RUF012
711720 """Who to show the instances to."""
712- generator_solutions : set [Role ] = {Role .generator } # noqa: RUF012
721+ generator_solutions : set [Role ] = {Role .generator } # noqa: RUF012
713722 """Who to show the generator's solutions to, if the problem requires them."""
714- solver_solutions : set [Role ] = {Role .solver } # noqa: RUF012
723+ solver_solutions : set [Role ] = {Role .solver } # noqa: RUF012
715724 """Who to show the solver's solutions to."""
716725
717726 class UiData (Battle .UiData ):
0 commit comments