@@ -47,10 +47,6 @@ def __init_subclass__(cls, **kwargs):
4747 if cls .__name__ == "CatalogGameFromContrib" or issubclass (cls , CatalogGameFromContrib ):
4848 return
4949
50- # Load game and extract metadata immediately when class is defined
51- cls .game = cls ._game ()
52- cls ._extract_description (cls .game )
53-
5450
5551class CatalogGameFromContrib (CatalogGame ):
5652 """
@@ -84,13 +80,9 @@ def _load_game(cls) -> Game:
8480 raise ValueError (f"Game file extension must be 'nfg' or 'efg', got '{ game_type } '" )
8581
8682 def __init_subclass__ (cls , ** kwargs ):
87- """Validate and extract metadata when subclass is defined."""
83+ """Validate metadata when subclass is defined."""
8884 super ().__init_subclass__ (** kwargs )
8985
90- # Load game and extract metadata immediately when class is defined
91- cls .game = cls ._load_game ()
92- cls ._extract_description (cls .game )
93-
9486
9587def games (
9688 num_actions : int | None = None ,
@@ -144,8 +136,8 @@ def games(
144136 >>> games(x=1) # Games with a custom metadata field 'x' equal to 1
145137 >>> games(is_tree=True, num_players=2) # 2-player extensive-form games
146138 """
147- # Import manually coded games to ensure they are registered in the catalog
148- _load_coded_games ()
139+ # Ensure all games are loaded
140+ _ensure_catalog_loaded ()
149141
150142 # Filter by extensive-form if filtering by tree-specific attributes
151143 if (
@@ -165,6 +157,19 @@ def get_all_subclasses(cls):
165157 all_subclasses .extend (get_all_subclasses (subclass ))
166158 continue
167159
160+ # Ensure game is instantiated before filtering
161+ if subclass .game is None :
162+ try :
163+ if issubclass (subclass , CatalogGameFromContrib ):
164+ subclass .game = subclass ._load_game ()
165+ else :
166+ subclass .game = subclass ._game ()
167+ subclass ._extract_description (subclass .game )
168+ except Exception :
169+ # Skip games that fail to load
170+ all_subclasses .extend (get_all_subclasses (subclass ))
171+ continue
172+
168173 if is_tree is not None and is_tree != subclass .game .is_tree :
169174 all_subclasses .extend (get_all_subclasses (subclass ))
170175 continue
@@ -250,6 +255,27 @@ def get_all_subclasses(cls):
250255
251256_CATALOG_YAML = Path (__file__ ).parent / "catalog.yml"
252257
258+ _catalog_initialized = False
259+ _catalog_initializing = False
260+
261+
262+ def _ensure_catalog_loaded ():
263+ """Ensure both YAML and coded games are loaded."""
264+ global _catalog_initialized , _catalog_initializing
265+ if _catalog_initialized :
266+ return
267+ if _catalog_initializing :
268+ return # Already loading, prevent re-entrance
269+
270+ _catalog_initializing = True
271+ try :
272+ _catalog_data = _load_catalog_from_yaml (_CATALOG_YAML )
273+ _generate_contrib_game_classes (_catalog_data )
274+ _load_coded_games ()
275+ _catalog_initialized = True
276+ finally :
277+ _catalog_initializing = False
278+
253279
254280def _load_catalog_from_yaml (path : Path ) -> dict [str , dict ]:
255281 if not path .exists ():
@@ -315,8 +341,3 @@ def _load_coded_games():
315341 globals ()[name ] = obj
316342
317343 _coded_games_loaded = True
318-
319-
320- # Generate classes at import time
321- _catalog_data = _load_catalog_from_yaml (_CATALOG_YAML )
322- _generate_contrib_game_classes (_catalog_data )
0 commit comments