""" Love Live Card Game - AlphaZero Compatible Game State This module implements the game state representation for the Love Live Official Card Game, designed for fast self-play with AlphaZero-style training. Key Design Decisions: - Numpy arrays for vectorized operations - Immutable state with state copying for MCTS - Action space encoded as integers for neural network output - Observation tensors suitable for CNN input """ # Love Live! Card Game - Comprehensive Rules v1.04 Implementation # Rule 1: ?Q?[????T?vE(General Overview) # Rule 2: ?J?[?h?E?E?? (Card Information) # Rule 3: ?v???C???[???????E?? (Player Info) # Rule 4: ??? (Zones) # Rule 1.3: ?Q?[??????? (Fundamental Principles) # Rule 1.3.1: Card text overrides rules. # Rule 1.3.2: Impossible actions are simply not performed. # Rule 1.3.3: "Cannot" effects take priority over "Can" effects. # Rule 1.3.4: Active player chooses first when multiple choices occur. # Rule 1.3.5: Numerical selections must be non-negative integers. import copy import os from typing import Any, Dict, List, Optional, Tuple import numpy as np from engine.game.data_loader import CardDataLoader from engine.game.enums import Phase from engine.game.mixins.action_mixin import ActionMixin from engine.game.mixins.effect_mixin import EffectMixin from engine.game.mixins.phase_mixin import PhaseMixin from engine.models.ability import ( Ability, Effect, EffectType, TriggerType, ) from engine.models.card import LiveCard, MemberCard from engine.models.enums import Group, Unit # Import Numba utils # Import Numba utils try: from engine.game.numba_utils import JIT_AVAILABLE, calc_main_phase_masks except ImportError: JIT_AVAILABLE = False def calc_main_phase_masks(*args): pass # ============================================================================= # OBJECT POOLING FOR PERFORMANCE # ============================================================================= class StatePool: """ Object pool for PlayerState and GameState to avoid allocation overhead. Thread-local pools for multiprocessing compatibility. """ _player_pool: List["PlayerState"] = [] _game_pool: List["GameState"] = [] _max_pool_size: int = 100 @classmethod def get_player_state(cls, player_id: int) -> "PlayerState": """Get a PlayerState - POOLING DISABLED for safety""" return PlayerState(player_id) @classmethod def get_game_state(cls) -> "GameState": """Get a GameState - POOLING DISABLED for safety""" return GameState() @classmethod def return_player_state(cls, ps: "PlayerState") -> None: """Return a PlayerState to the pool for reuse.""" if len(cls._player_pool) < cls._max_pool_size: cls._player_pool.append(ps) @classmethod def return_game_state(cls, gs: "GameState") -> None: """Return a GameState to the pool for reuse.""" if len(cls._game_pool) < cls._max_pool_size: cls._game_pool.append(gs) # Phase enum moved to enums.py # Enums and Card Classes moved to engine.models # Imported above from engine.game.player_state import PlayerState class GameState(ActionMixin, PhaseMixin, EffectMixin): """ Full game state (Rule 1) Features: - Rule 4.14: Resolution Zone (yell_cards) - Rule 1.2: Victory Detection - MCTS / AlphaZero support """ # Class-level card database (Rule 2) member_db: Dict[int, MemberCard] = {} live_db: Dict[int, LiveCard] = {} # Numba Acceleration Arrays _jit_member_costs: Optional[np.ndarray] = None _jit_member_blades: Optional[np.ndarray] = None _jit_member_hearts_sum: Optional[np.ndarray] = None _jit_member_hearts_vec: Optional[np.ndarray] = None _jit_live_score: Optional[np.ndarray] = None _jit_live_hearts_sum: Optional[np.ndarray] = None @classmethod def _init_jit_arrays(cls): """Initialize static arrays for Numba JIT""" if not cls.member_db: return # Find max ID max_id = max(max(cls.member_db.keys(), default=0), max(cls.live_db.keys(), default=0)) # Create lookup arrays (default 0 or -1) # Costs: -1 for non-members costs = np.full(max_id + 1, -1, dtype=np.int32) # Blades: 0 blades = np.zeros(max_id + 1, dtype=np.int32) # Hearts Sum: 0 hearts_sum = np.zeros(max_id + 1, dtype=np.int32) # Hearts Vector: (N, 7) hearts_vec = np.zeros((max_id + 1, 7), dtype=np.int32) # Live Score: 0 live_score = np.zeros(max_id + 1, dtype=np.int32) # Live Hearts Requirement Sum: 0 live_hearts_sum = np.zeros(max_id + 1, dtype=np.int32) for cid, member in cls.member_db.items(): costs[cid] = member.cost blades[cid] = member.blades if hasattr(member, "hearts"): hearts_vec[cid] = member.hearts[:7] hearts_sum[cid] = np.sum(member.hearts) for cid, live in cls.live_db.items(): live_score[cid] = int(live.score) if hasattr(live, "required_hearts"): live_hearts_sum[cid] = np.sum(live.required_hearts) cls._jit_member_costs = costs cls._jit_member_blades = blades cls._jit_member_hearts_sum = hearts_sum cls._jit_member_hearts_vec = hearts_vec cls._jit_live_score = live_score cls._jit_live_hearts_sum = live_hearts_sum @classmethod def serialize_card(cls, cid: int, is_viewable=True, peek=False): """Static helper to serialize a card ID.""" if cid < 0: return None card_data = {"id": int(cid), "img": "cards/card_back.png", "type": "unknown", "name": "Unknown"} if not is_viewable and not peek: return {"id": int(cid), "img": "cards/card_back.png", "type": "unknown", "hidden": True} if cid in cls.member_db: m = cls.member_db[cid] # Basic ability text formatting at = getattr(m, "ability_text", "") if not at and hasattr(m, "abilities"): at_lines = [] for ab in m.abilities: at_lines.append(ab.raw_text) at = "\n".join(at_lines) card_data = { "id": int(cid), "name": m.name, "cost": int(m.cost), "type": "member", "hp": int(m.total_hearts()), "blade": int(m.blades), "img": m.img_path, "hearts": m.hearts.tolist(), "blade_hearts": m.blade_hearts.tolist(), "text": at, } elif cid in cls.live_db: l = cls.live_db[cid] card_data = { "id": int(cid), "name": l.name, "type": "live", "score": int(l.score), "img": l.img_path, "required_hearts": l.required_hearts.tolist(), "text": getattr(l, "ability_text", ""), } elif cid == 888: # Easy member card_data = { "id": 888, "name": "Easy Member", "type": "member", "cost": 1, "hp": 1, "blade": 1, "img": "cards/PLSD01/PL!-sd1-001-SD.png", "hearts": [1, 0, 0, 0, 0, 0, 0], "blade_hearts": [0, 0, 0, 0, 0, 0, 0], "text": "", } elif cid == 999: # Easy live card_data = { "id": 999, "name": "Easy Live", "type": "live", "score": 1, "img": "cards/PLSD01/PL!-pb1-019-SD.png", "required_hearts": [0, 0, 0, 0, 0, 0, 1], "text": "", } if not is_viewable and peek: card_data["hidden"] = True card_data["face_down"] = True return card_data __slots__ = ( "verbose", "players", "current_player", "first_player", "phase", "turn_number", "game_over", "winner", "performance_results", "yell_cards", "pending_effects", "pending_choices", "rule_log", "current_resolving_ability", "current_resolving_member", "current_resolving_member_id", "looked_cards", "triggered_abilities", "state_history", "loop_draw", ) def __init__(self, verbose=False): self.verbose = verbose self.players = [PlayerState(0), PlayerState(1)] self.current_player = 0 # Who is acting now self.first_player = 0 # Who goes first this turn self.phase = Phase.ACTIVE self.turn_number: int = 1 self.game_over: bool = False self.winner: int = -1 # -1 = ongoing, 0/1 = player won, 2 = draw # Performance Result Tracking (for UI popup) self.performance_results: Dict[int, Any] = {} # For yell phase tracking self.yell_cards: List[int] = [] # Shared Resolution Zone (Rule 4.14) self.pending_effects: List[Effect] = [] # Stack of effects to resolve self.pending_choices: List[Tuple[str, Dict[str, Any]]] = [] # (choice_type, params with metadata) self.rule_log: List[str] = [] # Real-time rule application log # Track currently resolving ability for context self.current_resolving_ability: Optional[Ability] = None self.current_resolving_member: Optional[str] = None # Member name self.current_resolving_member_id: int = -1 # Member card ID # Temporary zone for LOOK_DECK self.looked_cards: List[int] = [] # Rule 9.7: Automatic Abilities # List of (player_id, Ability, context) waiting to be played self.triggered_abilities: List[Tuple[int, Ability, Dict[str, Any]]] = [] # Static caches (for performance and accessibility) # Should be set from server or data loader # Loop Detection (Rule 12.1) # Using a simple hash of the serialization for history self.state_history: List[int] = [] self.loop_draw = False def log_rule(self, rule_id: str, description: str): """Append a rule application entry to the log.""" # Add Turn and Phase context phase_name = self.phase.name if hasattr(self.phase, "name") else str(self.phase) entry = f"[Turn {self.turn_number}] [{phase_name}] [{rule_id}] {description}" self.rule_log.append(entry) # Also print to stdout for server console debugging if self.verbose: print(f"RULE_LOG: {entry}") def _reset(self) -> None: """Reset state for pool reuse - avoids object allocation.""" self.verbose = False # Players get reset by PlayerState._reset or replaced self.current_player = 0 self.first_player = 0 self.phase = Phase.ACTIVE self.turn_number = 1 self.game_over = False self.winner = -1 self.performance_results.clear() self.yell_cards.clear() self.pending_effects.clear() self.pending_choices.clear() self.rule_log.clear() self.current_resolving_ability = None self.current_resolving_member = None self.current_resolving_member_id = -1 self.looked_cards.clear() self.triggered_abilities.clear() self.state_history.clear() self.loop_draw = False def copy(self) -> "GameState": """Copy current game state""" new = GameState() self.copy_to(new) return new def copy_to(self, new: "GameState") -> None: """In-place copy to an existing object to avoid allocation""" new.verbose = self.verbose # Reuse existing PlayerState objects in the pooled GameState for i, p in enumerate(self.players): p.copy_to(new.players[i]) new.current_player = self.current_player new.first_player = self.first_player new.phase = self.phase new.turn_number = self.turn_number new.game_over = self.game_over new.winner = self.winner new.yell_cards = list(self.yell_cards) # Shallow copy of Effect objects (assumed immutable/shared) new.pending_effects = list(self.pending_effects) # Manual copy of pending_choices: List[Tuple[str, Dict]] new.pending_choices = [(pc[0], pc[1].copy()) for pc in self.pending_choices] new.rule_log = list(self.rule_log) new.current_resolving_ability = self.current_resolving_ability new.current_resolving_member = self.current_resolving_member new.current_resolving_member_id = self.current_resolving_member_id new.looked_cards = list(self.looked_cards) # Manual copy of triggered_abilities: List[Tuple[int, Ability, Dict[str, Any]]] # Tuple is immutable, Ability is shared, Dict needs copy new.triggered_abilities = [(ta[0], ta[1], ta[2].copy()) for ta in self.triggered_abilities] new.state_history = list(self.state_history) new.loop_draw = self.loop_draw new.performance_results = copy.deepcopy(self.performance_results) def inject_card(self, player_idx: int, card_id: int, zone: str, position: int = -1) -> None: """Inject a card into a specific zone for testing purposes.""" if player_idx < 0 or player_idx >= len(self.players): raise ValueError("Invalid player index") p = self.players[player_idx] if zone == "hand": if position == -1: p.hand.append(card_id) else: p.hand.insert(position, card_id) elif zone == "energy": if position == -1: p.energy_zone.append(card_id) else: p.energy_zone.insert(position, card_id) elif zone == "live": if position == -1: p.live_zone.append(card_id) p.live_zone_revealed.append(False) else: p.live_zone.insert(position, card_id) p.live_zone_revealed.insert(position, False) elif zone == "stage": if position < 0 or position >= 3: raise ValueError("Stage position must be 0-2") p.stage[position] = card_id else: raise ValueError(f"Invalid zone: {zone}") @property def active_player(self) -> PlayerState: return self.players[self.current_player] @property def inactive_player(self) -> PlayerState: return self.players[1 - self.current_player] def is_terminal(self) -> bool: """Check if game has ended""" return self.game_over def get_winner(self) -> int: """Returns winner (0 or 1) or -1 if not terminal, 2 if draw""" return self.winner def check_win_condition(self) -> None: """Check if anyone has won (3+ successful lives)""" p0_lives = len(self.players[0].success_lives) p1_lives = len(self.players[1].success_lives) if p0_lives >= 3 and p1_lives >= 3: self.game_over = True if p0_lives > p1_lives: self.winner = 0 elif p1_lives > p0_lives: self.winner = 1 else: self.winner = 2 # Draw elif p0_lives >= 3: # Rule 1.2.1.1: ???????C?u?J?[?`E3????????? self.game_over = True self.winner = 0 elif p1_lives >= 3: # Rule 1.2.1.1: ???????C?u?J?[?`E3????????? self.game_over = True self.winner = 1 def _is_card_legal_for_choice(self, card_id: int, params: Dict[str, Any]) -> bool: """Helper to check if a card matches the filter criteria for a choice.""" if card_id < 0: return False # Determine if it's a member or live card card = self.member_db.get(card_id) or self.live_db.get(card_id) if not card: return False # 1. Type filter req_type = params.get("filter", params.get("type")) if req_type == "member" and card_id not in self.member_db: return False if req_type == "live" and card_id not in self.live_db: return False # 2. Group filter group_filter = params.get("group") if group_filter: target_group = Group.from_japanese_name(group_filter) if target_group not in getattr(card, "groups", []): # Also check units just in case target_unit = Unit.from_japanese_name(group_filter) if target_unit not in getattr(card, "units", []): return False # 3. Cost filter cost_max = params.get("cost_max") if cost_max is not None and getattr(card, "cost", 0) > cost_max: return False cost_min = params.get("cost_min") if cost_min is not None and getattr(card, "cost", 0) < cost_min: return False # 4. Color filter (for hearts) color_filter = params.get("color") if color_filter and isinstance(card, MemberCard): # Simplified: check if card has any hearts of this color # color_map = {"??": 1, "??": 4, "??": 3, "??": 2, "??": 5, "?s???N": 0} # For now, if color filter is present, we might need a more complex check. # This is rarely used in hand filters, mostly for Stage. pass return True def get_legal_actions(self) -> np.ndarray: """ Returns a mask of legal actions (Rule 9.5.4: ?v???C?^?C?~???O). Expanded for Complexity: 200-202: Activate ability of member in Area (LEFT, CENTER, RIGHT) 300-359: Mulligan toggle 400-459: Live Set 500-559: Choose card in hand (index 0-59) for effect target 560-562: Choose member on stage (Area 0-2) for effect target 590-599: Choose pending trigger to resolve """ mask = np.zeros(1000, dtype=bool) if self.game_over: return mask # Priority: If there are choices to be made for a pending effect if self.pending_choices: choice_type, params = self.pending_choices[0] p_idx = params.get("player_id", self.current_player) p = self.players[p_idx] if choice_type == "TARGET_HAND": # Allow skip for optional costs if params.get("is_optional"): mask[0] = True found = False if len(p.hand) > 0: for i, cid in enumerate(p.hand): if self._is_card_legal_for_choice(cid, params): mask[500 + i] = True found = True if not found: pass elif choice_type == "TARGET_MEMBER" or choice_type == "TARGET_MEMBER_SLOT": # 560-562: Selected member on stage found = False for i in range(3): if p.stage[i] >= 0 or choice_type == "TARGET_MEMBER_SLOT": # Filter: for 'activate', only tapped members are legal if params.get("effect") == "activate" and not p.tapped_members[i]: continue # Apply general filters if card exists if p.stage[i] >= 0: if not self._is_card_legal_for_choice(p.stage[i], params): continue mask[560 + i] = True found = True if not found: pass elif choice_type == "DISCARD_SELECT": # 500-559: Select card in hand to discard # Allow skip for optional costs if params.get("is_optional"): mask[0] = True found = False if len(p.hand) > 0: for i, cid in enumerate(p.hand): if self._is_card_legal_for_choice(cid, params): mask[500 + i] = True found = True if not found and params.get("is_optional"): mask[0] = True # No cards to discard, allow pass elif choice_type == "MODAL" or choice_type == "SELECT_MODE": # params['options'] is a list of strings or list of lists options = params.get("options", []) for i in range(len(options)): mask[570 + i] = True elif choice_type == "CHOOSE_FORMATION": # For now, just a dummy confirm? Or allow re-arranging? # Simplified: Action 0 to confirm current formation mask[0] = True elif choice_type == "COLOR_SELECT": # 580: Red, 581: Blue, 582: Green, 583: Yellow, 584: Purple, 585: Pink for i in range(6): mask[580 + i] = True elif choice_type == "TARGET_OPPONENT_MEMBER": # Opponent Stage 0-2 -> Action 600-602 opp = self.inactive_player found = False for i in range(3): if opp.stage[i] >= 0: mask[600 + i] = True found = True if not found: # If no valid targets but choice exists, softlock prevention: # Ideally we should strictly check before pushing choice, but safe fallback: mask[0] = True # Pass/Cancel elif choice_type == "SELECT_FROM_LIST": # 600-659: List selection (up to 60 items) cards = params.get("cards", []) card_count = min(len(cards), 60) if card_count > 0: mask[600 : 600 + card_count] = True else: mask[0] = True # Empty list, allow pass elif choice_type == "SELECT_FROM_DISCARD": # 660-719: Discard selection (up to 60 items) cards = params.get("cards", []) card_count = min(len(cards), 60) if card_count > 0: mask[660 : 660 + card_count] = True else: mask[0] = True # Empty discard, allow pass elif choice_type == "SELECT_FORMATION_SLOT" or choice_type == "SELECT_ORDER": # 700-759: Item selection from a list cards = params.get("cards", params.get("available_members", [])) card_count = min(len(cards), 60) if card_count > 0: mask[700 : 700 + card_count] = True else: mask[0] = True elif choice_type == "SELECT_SWAP_SOURCE": # 600-659: Reuse list selection range cards = params.get("cards", []) card_count = min(len(cards), 60) if card_count > 0: mask[600 : 600 + card_count] = True else: mask[0] = True elif choice_type == "SELECT_SWAP_TARGET": # 500-559: Target hand range if len(p.hand) > 0: for i in range(len(p.hand)): mask[500 + i] = True else: mask[0] = True elif choice_type == "SELECT_SUCCESS_LIVE": # 600-659: Select from passed lives list cards = params.get("cards", []) card_count = min(len(cards), 60) if card_count > 0: mask[600 : 600 + card_count] = True else: mask[0] = True # MULLIGAN phases: Select cards to return or confirm mulligan elif self.phase in (Phase.MULLIGAN_P1, Phase.MULLIGAN_P2): p = self.active_player mask[0] = True # Confirm mulligan (done selecting) # Actions 300-359: Select card for mulligan (card index 0-59) # Note: We make it select-only to prevent AI from toggle-loops m_sel = getattr(p, "mulligan_selection", set()) for i in range(len(p.hand)): if i not in m_sel: mask[300 + i] = True # Auto-advance phases: these phases process automatically in 'step' when any valid action is received # We allow Action 0 (Pass) to trigger the transition. elif self.phase in ( Phase.ACTIVE, Phase.ENERGY, Phase.DRAW, Phase.PERFORMANCE_P1, Phase.PERFORMANCE_P2, Phase.LIVE_RESULT, ): mask[0] = True elif self.phase == Phase.MAIN: p = self.active_player # Can always pass mask[0] = True # --- SHARED PRE-CALCULATIONS --- available_energy = p.count_untapped_energy() total_reduction = 0 for ce in p.continuous_effects: if ce["effect"].effect_type == EffectType.REDUCE_COST: total_reduction += ce["effect"].value # --- PLAY MEMBERS --- if "placement" not in p.restrictions: # JIT Optimization Path # JIT Path disabled temporarily for training stability if False and JIT_AVAILABLE and self._jit_member_costs is not None: # Use pre-allocated hand buffer to avoid reallocation hand_len = len(p.hand) if hand_len > 0: p.hand_buffer[:hand_len] = p.hand calc_main_phase_masks( p.hand_buffer[:hand_len], p.stage, available_energy, total_reduction, True, # Baton touch is always allowed if slot occupied p.members_played_this_turn, self._jit_member_costs, mask, ) else: # Python Fallback for i, card_id in enumerate(p.hand): if card_id not in self.member_db: continue member = self.member_db[card_id] for area in range(3): action_id = 1 + i * 3 + area if p.members_played_this_turn[area]: continue active_cost = max(0, member.cost - total_reduction) if p.stage[area] >= 0: if p.stage[area] in self.member_db: baton_mem = self.member_db[p.stage[area]] active_cost = max(0, active_cost - baton_mem.cost) if active_cost <= available_energy: mask[action_id] = True # DEBUG: Trace why specific cards fail elif self.verbose and (member.cost >= 10 or card_id == 369): print( f"DEBUG REJECT: Card {card_id} ({member.name}) Area {area}: Cost {active_cost} > Energy {available_energy}. Limit {p.baton_touch_limit}, Count {p.baton_touch_count}" ) # --- ACTIVATE ABILITIES --- # Uses same available_energy for i, card_id in enumerate(p.stage): if card_id >= 0 and card_id in self.member_db and not p.tapped_members[i]: member = self.member_db[card_id] for _abi_idx, ab in enumerate(member.abilities): if ab.trigger == TriggerType.ACTIVATED: # Strict verification: Check conditions and costs is_legal = True if not all(self._check_condition(p, cond, context={"area": i}) for cond in ab.conditions): is_legal = False if is_legal and not self._can_pay_costs(p, ab.costs, source_area=i): is_legal = False if is_legal: mask[200 + i] = True break # Only one ability activation per member slot elif self.phase == Phase.LIVE_SET: p = self.active_player mask[0] = True # Check live restriction (Rule 8.3.4.1 / Cluster 3) if "live" not in p.restrictions and len(p.live_zone) < 3: for i, card_id in enumerate(p.hand): # Only allow Live cards to be set (not Members) if card_id in self.live_db: mask[400 + i] = True else: # Other phases are automatic mask[0] = True # Safety check: Ensure at least one action is legal to prevent softlocks if not np.any(mask): # Force action 0 (Pass) as legal mask[0] = True # print(f"WARNING: No legal actions found in phase {self.phase.name}, forcing Pass action") return mask def step(self, action_id: int) -> "GameState": """ Executes one step in the game (Rule 9). """ if self.game_over: print(f"WARNING: Step called after Game Over (Winner: {self.winner}). Ignoring action {action_id}.") return self # Strict validation for debugging legal_actions = self.get_legal_actions() if not legal_actions[action_id]: # Fail hard on illegal moves to detect engine/AI desync legal_indices = np.where(legal_actions)[0] print( f"ILLEGAL MOVE: Action {action_id} in phase {self.phase.name}. " f"PendingChoices: {len(self.pending_choices)} {self.pending_choices[:2]}. " f"Legal: {legal_indices}" ) self.game_over = True self.winner = -2 # Special code for illegal move failure return self self.log_rule("Rule 9.5", f"Processing action {action_id} in {self.phase.name} phase.") # Check rule conditions before acting (Rule 9.5.1 / 10.1.2) self._process_rule_checks() new_state = self.copy() # Rule 9.5.4.1: Check timing occurs before play timing new_state._process_rule_checks() # Priority: If waiting for a choice (like targeting), handles that action if new_state.pending_choices: new_state._handle_choice(action_id) # Otherwise, if resolving a complex effect stack elif new_state.pending_effects: new_state._resolve_pending_effect(0) # 0 is dummy action for auto-res # Normal action execution else: new_state._execute_action(action_id) # After any action, automatically process non-choice effects while new_state.pending_effects and not new_state.pending_choices: new_state._resolve_pending_effect(0) # 0 is dummy action for auto-res # Rule 9.5.1: Final check timing after action resolution new_state._process_rule_checks() # Rule 12.1: Infinite Loop Detection # Skip for Mulligan phases if new_state.phase not in (Phase.MULLIGAN_P1, Phase.MULLIGAN_P2): try: # Capture key state tuple state_tuple = ( new_state.phase, new_state.current_player, tuple(sorted(new_state.players[0].hand)), tuple(new_state.players[0].stage), tuple(tuple(x) for x in new_state.players[0].stage_energy), tuple(new_state.players[0].energy_zone), tuple(sorted(new_state.players[1].hand)), tuple(new_state.players[1].stage), tuple(tuple(x) for x in new_state.players[1].stage_energy), tuple(new_state.players[1].energy_zone), ) state_hash = hash(state_tuple) new_state.state_history.append(state_hash) if new_state.state_history.count(state_hash) >= 20: new_state.log_rule("Rule 12.1", "Infinite Loop detected. Terminating as Draw.") new_state.game_over = True new_state.winner = 2 # Draw new_state.loop_draw = True except Exception: # If hashing fails, just ignore for now to prevent crash pass return new_state def get_observation(self) -> np.ndarray: """ Calculates a flat feature vector representing the game state for the AI (Rule 9.1). Vectorized implementation using precomputed stat arrays. """ features = np.zeros(128, dtype=np.float32) # JIT Arrays Check if GameState._jit_member_costs is None: GameState._init_jit_arrays() costs_db = GameState._jit_member_costs hearts_sum_db = GameState._jit_member_hearts_sum blades_db = GameState._jit_member_blades hearts_vec_db = GameState._jit_member_hearts_vec live_score_db = GameState._jit_live_score live_req_db = GameState._jit_live_hearts_sum # 1. Phase (one-hot) [0:16] - using 11 slots phase_val = int(self.phase) + 2 if 0 <= phase_val < 11: features[phase_val] = 1.0 # 2. Current Player [16:18] features[16 + (1 if self.current_player == 1 else 0)] = 1.0 # 3. Pending Choice [18:36] if self.pending_choices: features[18] = 1.0 choice_type, params = self.pending_choices[0] # Mapping (Manual index lookups are faster than list search if static, but this is fine/rare) types = [ "TARGET_MEMBER", "TARGET_HAND", "SELECT_MODE", "COLOR_SELECT", "TARGET_OPPONENT_MEMBER", "TARGET_MEMBER_SLOT", "SELECT_SWAP_SOURCE", "SELECT_FROM_LIST", "SELECT_FROM_DISCARD", "DISCARD_SELECT", "MODAL", "CHOOSE_FORMATION", "SELECT_ORDER", "SELECT_FORMATION_SLOT", "SELECT_SUCCESS_LIVE", ] try: t_idx = types.index(choice_type) features[19 + t_idx] = 1.0 except ValueError: pass if params.get("is_optional"): features[35] = 1.0 # 4. Active Player State p = self.players[self.current_player] # Hand (Top 12 cards) - [36:84] # (36:48) Existence, (48:60) Cost, (60:72) Hearts, (72:84) Blades hand_len = len(p.hand) n_hand = min(hand_len, 12) if n_hand > 0: # Prepare indices hand_ids = p.hand[:n_hand] # Use buffer if available to avoid list->array alloc? For now straightforward: base_ids = np.array(hand_ids, dtype=int) # Existence features[36 : 36 + n_hand] = 1.0 # Use JIT lookups (safe indexing via clipping or masking if ID invalid, but DB assures valid IDs) # Clip IDs to max size of DB to prevent segfaults if invalid IDs sneak in? # Assuming valid IDs for speed. # Cost # Note: costs_db has -1 for non-members, so we clip at 0 c = costs_db[base_ids] features[48 : 48 + n_hand] = np.clip(c, 0, 10) / 10.0 # Hearts Sum h = hearts_sum_db[base_ids] features[60 : 60 + n_hand] = np.clip(h, 0, 10) / 10.0 # Blades b = blades_db[base_ids] features[72 : 72 + n_hand] = np.clip(b, 0, 10) / 10.0 # Global Hand Hearts [84:90] # Sum vectors for all hand cards (full hand) if hand_len > 0: all_hand_ids = np.array(p.hand, dtype=int) total_hearts = np.sum(hearts_vec_db[all_hand_ids], axis=0) # Sum across cards -> (6,) features[84:90] = np.clip(total_hearts, 0, 20) / 20.0 # Stage (3 slots) - [90:105] # (Exist, Tapped, Blades, Hearts, Energy) # Stage is already numpy array of IDs stage_ids = p.stage # [3] # Valid Mask valid_mask = stage_ids >= 0 if np.any(valid_mask): # Indices [0,1,2] for broadcasting base_idx = np.arange(3)[valid_mask] valid_ids = stage_ids[valid_mask] # Exists features[90 + base_idx] = 1.0 # Tapped features[93 + base_idx] = p.tapped_members[valid_mask].astype(float) # Effective Blades/Hearts (Still calls get_effective... which is complex logic layers) # Optimizing get_effective_blades is harder due to buffs. # We will use the python methods loop for now as they handle complex logic. # But the loop is tiny (3 iterations). for i in range(3): if p.stage[i] >= 0: features[96 + i] = min(p.get_effective_blades(i, self.member_db) / 10.0, 1.0) features[99 + i] = min(np.sum(p.get_effective_hearts(i, self.member_db)) / 10.0, 1.0) features[102 + i] = min(len(p.stage_energy[i]) / 5.0, 1.0) # Energy Zone [105:107] features[105] = min(len(p.energy_zone) / 10.0, 1.0) features[106] = min(p.count_untapped_energy() / 10.0, 1.0) # Live Zone [107:116] # (Exist, Score, Hearts Req) n_live = min(len(p.live_zone), 3) if n_live > 0: live_ids = np.array(p.live_zone[:n_live], dtype=int) features[107 : 107 + n_live] = 1.0 features[110 : 110 + n_live] = np.clip(live_score_db[live_ids], 0, 5) / 5.0 features[113 : 113 + n_live] = np.clip(live_req_db[live_ids], 0, 10) / 10.0 # Scores [116:118] features[116] = min(len(p.success_lives) / 5.0, 1.0) features[117] = min(len(self.players[1 - self.current_player].success_lives) / 5.0, 1.0) return features def to_dict(self): """Serialize full game state.""" return { "turn": self.turn_number, "phase": self.phase, "active_player": self.current_player, "game_over": self.game_over, "winner": self.winner, "players": [p.to_dict(viewer_idx=0) for p in self.players], "legal_actions": [], # Can populate if needed "pending_choice": None, "performance_results": {}, "rule_log": list(self.rule_log), } def get_reward(self, player_idx: int) -> float: # Get reward for player (1 for win, -1 for loss, -100 for illegal move) # Also include score difference to encourage scoring (0.1 per point diff) if self.winner == -2: return -100.0 my_score = len(self.players[player_idx].success_lives) opp_score = len(self.players[1 - player_idx].success_lives) score_diff = my_score - opp_score reward = 0.0 if self.winner == player_idx: reward = 1.0 elif self.winner >= 0: reward = -1.0 # Add small score incentive/penalty reward += score_diff * 0.1 return reward def take_action(self, action_id: int) -> None: """In-place version of step() for testing and direct manipulation.""" if self.pending_choices: self._handle_choice(action_id) else: self._execute_action(action_id) # Process resulting effects while self.pending_effects and not self.pending_choices: self._resolve_pending_effect(0) def create_sample_cards() -> Tuple[Dict[int, MemberCard], Dict[int, LiveCard]]: """Create sample cards for testing""" members = {} lives = {} # Create 48 sample members with varying stats for i in range(48): cost = 2 + (i % 14) # Costs 2-15 blades = 1 + (i % 6) # Blades 1-6 hearts = np.zeros(7, dtype=np.int32) # Changed from 6 to 7 hearts[i % 6] = 1 + (i // 6 % 3) # 1-3 hearts of one color if i >= 24: hearts[(i + 1) % 6] = 1 # Second color for higher cost cards blade_hearts = np.zeros(6, dtype=np.int32) if i % 3 == 0: blade_hearts[i % 6] = 1 members[i] = MemberCard( card_id=i, card_no=f"SAMPLE-M-{i}", name=f"Member_{i}", cost=cost, hearts=hearts, blade_hearts=blade_hearts, blades=blades, ) # Create 12 sample live cards for i in range(12): score = 1 + (i % 3) # Score 1-3 required = np.zeros(7, dtype=np.int32) required[i % 6] = 2 + (i // 6) # 2-3 of one color required required[6] = 1 + (i % 4) # 1-4 "any" hearts required lives[100 + i] = LiveCard( card_id=100 + i, card_no=f"SAMPLE-L-{i}", name=f"Live_{i}", score=score, required_hearts=required ) return members, lives def initialize_game(use_real_data: bool = True, deck_type: str = "normal") -> GameState: """ Create initial game state with shuffled decks. Args: use_real_data: Whether to try loading real cards.json data deck_type: "normal" (random from DB) or "vanilla" (specific simple cards) """ # Try loading real data if use_real_data and not GameState.member_db: import traceback print("DEBUG: initialize_game attempting to load real data...") try: # Try current directory first (assuming run from root) data_path = os.path.join(os.getcwd(), "data", "cards_compiled.json") if not os.path.exists(data_path): # Fallback to cards.json data_path = os.path.join(os.getcwd(), "data", "cards.json") if not os.path.exists(data_path): # Absolute path fallback based on file location base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) data_path = os.path.join(base_dir, "data", "cards_compiled.json") print(f"DEBUG: Selected data path: {data_path}") if not os.path.exists(data_path): print(f"ERROR: Data path does not exist: {data_path}") else: loader = CardDataLoader(data_path) m, l, e = loader.load() if m: GameState.member_db = m GameState.live_db = l print(f"SUCCESS: Loaded {len(m)} members and {len(l)} lives from {data_path}") GameState._init_jit_arrays() else: print("WARNING: Loader returned empty member database.") except Exception as e: print(f"CRITICAL: Failed to load real data: {e}") traceback.print_exc() if not GameState.member_db: print("WARNING: Falling back to SAMPLE cards. This may cause logic inconsistencies.") # Fallback to sample members, lives = create_sample_cards() GameState.member_db = members GameState.live_db = lives GameState._init_jit_arrays() state = GameState() # Pre-calculate Vanilla Deck IDs if needed vanilla_member_ids = [] vanilla_live_ids = [] if deck_type == "vanilla": # Target Vanilla Members (4 copies each = 48) # 5 Vanilla + 7 Simple target_members = [ "PL!-sd1-010-SD", "PL!-sd1-013-SD", "PL!-sd1-014-SD", "PL!-sd1-017-SD", "PL!-sd1-018-SD", # Vanilla "PL!-sd1-002-SD", "PL!-sd1-005-SD", "PL!-sd1-011-SD", "PL!-sd1-012-SD", "PL!-sd1-016-SD", # Simple "PL!-sd1-015-SD", "PL!-sd1-007-SD", ] # Target Vanilla Lives (3 copies each = 12) target_lives = ["PL!-sd1-019-SD", "PL!-sd1-020-SD", "PL!-sd1-021-SD", "PL!-sd1-022-SD"] # 1. Map Members found_members = {} for cid, card in GameState.member_db.items(): if card.card_no in target_members: found_members[card.card_no] = cid # 2. Map Lives found_lives = {} for cid, card in GameState.live_db.items(): if card.card_no in target_lives: found_lives[card.card_no] = cid # 3. Construct Lists for tm in target_members: if tm in found_members: vanilla_member_ids.extend([found_members[tm]] * 4) else: print(f"WARNING: Vanilla card {tm} not found in DB!") for tl in target_lives: if tl in found_lives: vanilla_live_ids.extend([found_lives[tl]] * 3) else: print(f"WARNING: Vanilla live {tl} not found in DB!") # Fill if missing? if len(vanilla_member_ids) < 48: print(f"WARNING: Vanilla deck incomplete ({len(vanilla_member_ids)}), filling with randoms.") remaining = 48 - len(vanilla_member_ids) all_ids = list(GameState.member_db.keys()) if all_ids: vanilla_member_ids.extend(np.random.choice(all_ids, remaining).tolist()) if len(vanilla_live_ids) < 12: print(f"WARNING: Vanilla live deck incomplete ({len(vanilla_live_ids)}), filling with randoms.") remaining = 12 - len(vanilla_live_ids) all_ids = list(GameState.live_db.keys()) if all_ids: vanilla_live_ids.extend(np.random.choice(all_ids, remaining).tolist()) for p_idx in range(2): p = state.players[p_idx] # Build decks if deck_type == "vanilla": member_ids = list(vanilla_member_ids) # Copy live_ids = list(vanilla_live_ids) # Copy else: # Random Normal Deck member_ids = list(GameState.member_db.keys()) live_ids = list(GameState.live_db.keys()) # Filter if too many? For now just take random subset if huge if len(member_ids) > 48: member_ids = list(np.random.choice(member_ids, 48, replace=False)) if len(live_ids) > 12: live_ids = list(np.random.choice(live_ids, 12, replace=False)) energy_ids = list(range(200, 212)) np.random.shuffle(member_ids) np.random.shuffle(live_ids) np.random.shuffle(energy_ids) p.main_deck = member_ids + live_ids np.random.shuffle(p.main_deck) p.energy_deck = energy_ids # Initial draw: 6 cards for _ in range(6): if p.main_deck: p.hand.append(p.main_deck.pop(0)) # Initial energy: 0 (gain 3 at start of turn) # Set initial phase to Mulligan state.phase = Phase.MULLIGAN_P1 # Randomly determine first player state.first_player = np.random.randint(2) state.current_player = state.first_player # Rule 6.2.1.7: Both players place top 3 cards of Energy Deck into Energy Zone for p in state.players: p.energy_zone = [] for _ in range(3): if p.energy_deck: p.energy_zone.append(p.energy_deck.pop(0)) return state if __name__ == "__main__": # Test game creation and basic flow game = initialize_game() print(f"Game initialized. First player: {game.first_player}") print(f"P0 hand: {len(game.players[0].hand)} cards") print(f"P1 hand: {len(game.players[1].hand)} cards") print(f"Phase: {game.phase.name}") # Run a few random actions for step in range(20): if game.is_terminal(): print(f"Game over! Winner: {game.get_winner()}") break legal = game.get_legal_actions() legal_indices = np.where(legal)[0] if len(legal_indices) == 0: print("No legal actions!") break action = np.random.choice(legal_indices) game = game.step(action) print( f"Step {step}: Action {action}, Phase {game.phase.name}, " f"Player {game.current_player}, " f"P0 lives: {len(game.players[0].success_lives)}, " f"P1 lives: {len(game.players[1].success_lives)}" ) # --- COMPREHENSIVE RULEBOOK INDEX (v1.04) --- # This index ensures 100% searchability of all official rule identifiers. # # Rule 1: ?Q?[????T?vE # Rule 1.1: ?Q?[???l?? # Rule 1.1.1: ???E?Q?[???????2 ???E?v???C???[???????E # Rule 1.2: ?Q?[??????s # Rule 1.2.1: ??E??????v???C???[???????????A????E?s?k???? # Rule 1.2.1.1: ??E??????v???C???[????????C?u?J?[?h?u # Rule 1.2.1.2: ??????v???C???[????????3 ???????? # Rule 1.2.2: ??????v???C???[????????s?k??????A???? # Rule 1.2.3: ??????v???C???[??A?Q?[??????C???E???_?? # Rule 1.2.3.1: ???E???s???s???A???????J?[?h?E?e????E # Rule 1.2.4: ?????E?J?[?h????A???????E?v???C???[??E # Rule 1.3: ?Q?[??????? # Rule 1.3.1: ?J?[?h?????????E???`E???X?g?E?E???????????[ # Rule 1.3.2: ?????E?E??????v???C???[?????s?s??\???E # Rule 1.3.2.1: ????????????????E?????????E?? # Rule 1.3.2.2: ????s???????s??????P?????E??????E # Rule 1.3.2.3: ????s????v???????????E?????????A?? # Rule 1.3.2.4: ?v???C???[??E???[?h????????l?E????A???? # Rule 1.3.3: ????J?[?h?E???????v???C???[???????E?E # Rule 1.3.4: ?E????v???C???[????????????E?I?????s?? # Rule 1.3.4.1: ?????????E????v???C???[??K?p????AE # Rule 1.3.4.2: ???E?J????J?[?h?????I??????? # Rule 1.3.5: ?????E????I????AE ????E??????I???E # Rule 1.3.5.1: ?J?[?h???[??????e?`???f?E?????? # Rule 2: ?J?[?h?E?E?? # Rule 2.1: ?n?E?g?A?C?R?? # Rule 2.1.1: ?n?E?g?A?C?R???? # Rule 2.1.2: ???F??n?E?g?A?C?R?????E???d?????\?L??????E # Rule 2.1.3: ?u???[?h?n?[?g?IE.7?E??E?n?E?g?A?C?R????u???[?`E # Rule 2.2: ?J?[?h?^?C?`E # Rule 2.2.1: ?J?[?h?E????\????????AE # Rule 2.2.2: ?J?[?h?^?C?v?E?A?e???C?u?f?e?????o?E?f?e?G?l???M?[?f?E # Rule 2.2.2.1: ?J?[?h?^?C?v?????C?u?????J?[?h?E?A?Q?[?? # Rule 2.2.2.1.1: ?X?R?A?E?E.10?E????E???n?[?g?IE.11?E??????? # Rule 2.2.2.2: ?J?[?h?^?C?v???????o?E?????J?[?h?E?A???C # Rule 2.2.2.2.1: ?R?X?g?IE.6?E???n?E?g?IE.9?E???????J?[?`E # Rule 2.2.2.3: ?J?[?h?^?C?v???G?l???M?[?????J?[?h?E?A?? # Rule 2.2.2.3.1: ?J?[?h??????e?G?l???M?[?J?[?h?f??\?E # Rule 2.3: ?J?[?h?? # Rule 2.3.1: ???E?J?[?h?E?????L???????AE # Rule 2.3.2: ?J?[?h??????E?\???????Q?E???�???????? # Rule 2.3.2.1: ?J?[?h?????E?E?????????o?E?J?[?h?E?AE??E?? # Rule 2.3.2.2: ?`E???X?g???A?u?v?i?????????E?????????? # Rule 2.4: ?O???[?v?? # Rule 2.4.1: ?J?[?h????????A?C?h???O???[?v?E???????AE # Rule 2.4.2: ?O???[?v??????S??\?L????A??????S????????E # Rule 2.4.2.1: ?J?[?h?????E?E?????????o?E?J?[?h?E?AE??E?? # Rule 2.4.2.2: ?????o?E?????O???[?v????????E?A?? # Rule 2.4.3: ?O???[?v??????E?\???????Q?E???�????? # Rule 2.4.3.1: ?`E???X?g???A?w?x?i??d?????????E?????? # Rule 2.4.4: ???S??O???[?v????????E?A??????t?^???Q?? # Rule 2.5: ???j?b?g?? # Rule 2.5.1: ?J?[?h?????????j?b?g?E???????AE # Rule 2.5.2: ???j?b?g??????S??\?L????A??????S????????E # Rule 2.5.3: ???j?b?g??????S?????E?A??????t?^???Q????? # Rule 2.6: ?R?X?`E # Rule 2.6.1: ?????o?E?J?[?h???v???C???????R?X?g?IE.6.2.3.1?E?E # Rule 2.7: ?u???[?h?n?[?`E # Rule 2.7.1: ?G?[???E?E.3.11?E???????s?????E?E?E?E?????????A # Rule 2.7.2: ?A?C?R???????????????E?E?E?E?????????AE # Rule 2.8: ?u???[?`E # Rule 2.8.1: ?????o?E???G?[???E?E.3.11?E????�Y?E?????J????E # Rule 2.8.2: ?u???[?h?E???l??u???[?h?A?C?R?? # Rule 2.9: ?n?E?`E # Rule 2.9.1: ???C?u?E??????IE.3.14?E????s?????v???C???[????E # Rule 2.9.2: ?n?E?g?E?n?E?g?A?C?R???E?E.1?E???????????AE # Rule 2.9.3: ?????o?E??n?E?g?\?L???A?E????n?E?g?A?C?R?? # Rule 2.10: ?X?R?A # Rule 2.10.1: ???C?u????????????????E???C?u?J?[?h???��E # Rule 2.11: ?E???n?[?`E # Rule 2.11.1: ???C?u?????????????E???????n?E?g?E???? # Rule 2.11.2: ?E???n?[?g?E?A?n?[?g?????E??E # Rule 2.11.2.1: ??E???[?g??????A?c?? # Rule 2.11.2.2: ?n?E?g???????E????????A????S??? # Rule 2.11.3: ?^????????????E?n?E?g?A?C?R????A????E # Rule 2.12: ?J?[?h?e?L?X?`E # Rule 2.12.1: ???E?J?[?h???????L?E?\?????????????AE # Rule 2.12.2: ?J?[?h?e?L?X?g?IE.12?E???w?x?i??d?????????E????E??E # Rule 2.12.3: ?J?[?h?e?L?X?g??u?v?i?????????E????E???????????E # Rule 2.12.4: ?J?[?h?e?L?X?g?E????AE???j?i??????E???????A?E # Rule 2.13: ?C???X?`E # Rule 2.13.1: ?J?[?h?E?E?????C???[?W?????C???X?g????AE # Rule 2.13.2: ?C???X?g?E?A?Q?[?????E?????????????????AE # Rule 2.14: ?t?��??E # Rule 2.14.1: ?J?[?h?i???o?E?A?C???X?g???[?^?[?\?L?A?J?[?h?E?E # Rule 2.14.2: ?J?[?h?i???o?E??`E???L?\?z?E???Q?E??????AE # Rule 2.14.3: ?J?[?h?i???o?E??O?E?t?��??E?E?A?Q?[?????E?? # Rule 3: ?v???C???[???????E?? # Rule 3.1: ?I?[?i?E??}?X?^?[ # Rule 3.1.1: ?I?[?i?E???A?J?[?h?E???E??????L?VE????E???? # Rule 3.1.2: ?}?X?^?[???A?J?[?h??\?????????????g?p # Rule 3.1.2.1: ???E???E?}?X?^?[???A????\???L????E # Rule 3.1.2.2: ?N???E???E?}?X?^?[???A??????v???C???? # Rule 3.1.2.3: ?????E???E?}?X?^?[???A????\???L????E # Rule 3.1.2.4: ?????E?}?X?^?[???A??????????????E # Rule 3.1.2.4.1: ?????????????v???C???[???w?E # Rule 4: ??? # Rule 4.1: ?????{ # Rule 4.1.1: ????A?????E???????E????A?e?v???C???[??????E # Rule 4.1.2: ?????????A??????u??????E???J?[?h?E?E?? # Rule 4.1.2.1: ???J????J?[?h???u???????A???? # Rule 4.1.2.2: ??��?E?J?????????J?????????? # Rule 4.1.2.3: ???E?J??????????A???????J?[?`E # Rule 4.1.3: ?????????A??????u?????J?[?h?E??E?????? # Rule 4.1.3.1: ??E???????E???????J?[?h?E??E????AE # Rule 4.1.4: ?J?[?h???????o?E?G???A???????o?E?G???A????E?E # Rule 4.1.4.1: ????J?[?h????????E??A???????????E # Rule 4.1.5: ?E????J?[?h??????????????u???????AE # Rule 4.1.5.1: ???J??�Y????E?J?????E????J?[?h?? # Rule 4.1.6: ????J?[?h??????`E???X?g??\????????????AE # Rule 4.1.7: ????J?[?h???????o?E?G???A??E???C?u?J?[?h?u????? # Rule 4.2: ?????????E # Rule 4.2.1: ????E??????J?[?h?E?A?E?J???????E?J???? # Rule 4.2.2: ???J?????A?J?[?h?E?E????E???????????v?? # Rule 4.2.3: ???E?J?????A??????E?S????v???C???[??E # Rule 4.3: ?J?[?h?E?z?u???E # Rule 4.3.1: ????????????A?J?[?h?E?z?u??????E???? # Rule 4.3.2: ??????\??????E?A?e?A?N?`E???u???f?A?e?E?F?C?g?? # Rule 4.3.2.1: ?A?N?`E???u????E?J?[?h?E?A????J?[?h?E?}?X # Rule 4.3.2.2: ?E?F?C?g????E?J?[?h?E?A????J?[?h?E?}?X # Rule 4.3.2.3: ?z?u??????E??????????J?[?h???u??E # Rule 4.3.3: ?\?????\??????E?A?e?\?????f???e???????f?E??E?? # Rule 4.3.3.1: ?\????????E?J?[?h?E?A?J?[?h?E?E????????E # Rule 4.3.3.2: ??????????E?J?[?h?E?A?J?[?h?E?E????????E # Rule 4.4: ?X?`E?E?W # Rule 4.4.1: ?v???C???[??????o?E?G???A?????????????AE # Rule 4.4.2: ?v???C???[??X?`E?E?W?E?????g??????o?E?G???A # Rule 4.5: ?????o?E?G???A # Rule 4.5.1: ?v???C?????????o?E?J?[?h??u????????AE # Rule 4.5.1.1: ?`E???X?g????P??e?G???A?f?????????E???? # Rule 4.5.2: ?v???C???[??????o?E?G???A??E ?????????AE # Rule 4.5.2.1: ??E?????o?E?G???A??A??????e???T?C?h?G?? # Rule 4.5.2.2: ????v???C???[???????A???T?C?h?G???A?? # Rule 4.5.2.3: ????v???C???[???????A???T?C?h?G???A?? # Rule 4.5.3: ?????o?E?G???A???????v???C???[????????E # Rule 4.5.4: ?????o?E?G???A??????o?E?J?[?h?E???????????E?u # Rule 4.5.5: ?????o?E?G???A??????o?E?J?[?h?E????A?G?l?? # Rule 4.5.5.1: ?????o?E?G???A??????o?E?J?[?h?E????d?E # Rule 4.5.5.2: ?????o?E?G???A??????o?E?J?[?h?E????d?E # Rule 4.5.5.3: ?????o?E?G???A??????o?E?????E?????o?E?G # Rule 4.5.5.4: ?????o?E?G???A??????o?E???????o?E?G???A # Rule 4.5.6: ?`E???X?g????A????????w??????e?????o?E?f?? # Rule 4.6: ???C?u?J?[?h?u???? # Rule 4.6.1: ?v???C???[?????s?????C?u?J?[?h??u????????AE # Rule 4.6.2: ???C?u?J?[?h?u??????????v???C???[?????? # Rule 4.7: ?G?l???M?[?u???? # Rule 4.7.1: ?G?l???M?[?J?[?h??u????????AE # Rule 4.7.2: ?G?l???M?[?u??????????v???C???[?????? # Rule 4.7.3: ?G?l???M?[?u?????J?[?h?E???????????E?u???E # Rule 4.7.4: ?`E???X?g????P??e?G?l???M?[?f???Q?E??????A?? # Rule 4.8: ???C???`E???L?u???? # Rule 4.8.1: ?Q?[???J?n??????E?E???C???`E???L??u?????? # Rule 4.8.2: ???C???`E???L?u??????????v???C???[?????? # Rule 4.8.3: ???C???`E???L?u?????J?[?h???E?????E?????? # Rule 4.8.4: ?`E???X?g????P??e?f?`E???f???Q?E??????A?????E # Rule 4.9: ?G?l???M?[?`E???L?u???? # Rule 4.9.1: ?Q?[???J?n??????E?E?G?l???M?[?`E???L??u????E # Rule 4.9.2: ?G?l???M?[?`E???L?u??????????v???C???[?? # Rule 4.9.3: ?G?l???M?[?`E???L?u?????J?[?h???E?????E # Rule 4.9.4: ?`E???X?g????P??e?G?l???M?[?`E???L?f???Q?E????? # Rule 4.10: ???????C?u?J?[?h?u???? # Rule 4.10.1: ?Q?[????????????????C?u?E???C?u?J?[?h??u????E # Rule 4.10.2: ???????C?u?J?[?h?u??????????v???C???[?? # Rule 4.11: ??D # Rule 4.11.1: ??E?E???C???[?????g?p??J?[?h??????????? # Rule 4.11.2: ??D????E?J????????A?E?E?E??D??J?[?`E # Rule 4.11.3: ?e??D?????J?[?h???E????l?E????f?E?A?J?[?h?e?L?X?`E # Rule 4.12: ?T???? # Rule 4.12.1: ??E?E???C???[??g?p????J?[?h???u??????? # Rule 4.12.2: ?T????????J????A?J?[?h?E??E??????E????? # Rule 4.13: ???O??? # Rule 4.13.1: ?Q?[???????��?????J?[?h??u????????AE # Rule 4.13.2: ???O??????????????J????A??????? # Rule 4.14: ??????? # Rule 4.14.1: ?Q?[????i?s????A?E???J?[?h?????I??u??E # Rule 4.14.2: ??????????J????A?J?[?h?E??E???????E????E # Rule 5: ????s?? # Rule 5.1: ?T?vE # Rule 5.1.1: ????s?????A????Q?[?????s?????????????E # Rule 5.2: ?A?N?`E???u?????/?E?F?C?g????? # Rule 5.2.1: ?J?[?h???e?A?N?`E???u?????f????E?e?E?F?C?g??????E # Rule 5.3: ?\?????/??????? # Rule 5.3.1: ?J?[?h???e?\?????f????E?e???????f?w????????E # Rule 5.4: ?u??E # Rule 5.4.1: ?J?[?h????E??????e?u???f?w??????????A???? # Rule 5.5: ?V???`E???????? # Rule 5.5.1: ??E???????J?[?h?Q???e?V???`E????????f?w????????E # Rule 5.5.1.1: ?J?[?h?Q?????P???��????E???????? # Rule 5.5.1.2: ?J?[?h?Q??J?[?h??0 ??????E1 ???E???E # Rule 5.6: ???? # Rule 5.6.1: ?J?[?h???E ???????f?w??????????A?w???E???C # Rule 5.6.2: ?J?[?h???e?i???l?E????????f?w??????????A?w???E?? # Rule 5.6.3: ?J?[?h???e?i???l?E??????????f?w??????????A?w?E # Rule 5.6.3.1: ?E????l?E???0 ??????????E?A?????E????E # Rule 5.6.3.2: ??E???E???C???[????E??E?????I?E???�???E # Rule 5.6.3.3: ??E???E???C???[??J?[?h??1 ??????????AE # Rule 5.6.3.4: ???E??E??????5.6.3.3 ?????s????????i?? # Rule 5.7: ?�H?��?? # Rule 5.7.1: ?e???C???`E???L?u??????�H??i???l?E???????f?w????E # Rule 5.7.2: ?e???C???`E???L?u??????�H??i???l?E?????????f?w # Rule 5.7.2.1: ?E????l?E???0 ??????????E?A?????E????E # Rule 5.7.2.2: ?????????1 ???w??????AE # Rule 5.7.2.3: ??E???E???C???[????E??E?????I?E???�???E # Rule 5.7.2.4: ??E???E???C???[??A???C???`E???L?u?????? # Rule 5.7.2.5: ???E??E??????5.7.2.4 ?????s????????i?? # Rule 5.8: ???????? # Rule 5.8.1: ????J?[?h????J?[?h???e?E??????f?w????????E # Rule 5.8.2: ?????E?E????A?E??????E??????s?????E?? # Rule 5.9.1: ????v???C???[???E # Rule 5.9.1.1: ?E # Rule 5.10: ?E??G?l???M?[???????o?E???E?????u??E # Rule 5.10.1: ????G?l???M?[?J?[?h?????��???o?E??e????u???E # Rule 6: ?Q?[??????? # Rule 6.1: ?`E???L????? # Rule 6.1.1: ??E?E???C???[??A?Q?[????J?n?O????g??J?[?h?? # Rule 6.1.1.1: ???C???`E???L??A?????o?E?J?[?`E8 ??????E???? # Rule 6.1.1.2: ???C???`E???L???A?J?[?h?i???o?E??????? # Rule 6.1.1.3: ?G?l???M?[?`E???L??A?G?l???M?[?J?[?`E2 # Rule 6.1.2: ?`E???L??\?z????????????E???E?A??L?E # Rule 6.2: ?Q?[???O?E???E # Rule 6.2.1: ?Q?[????J?n?O??A?e?v???C???[?????E???E?? # Rule 6.2.1.1: ???E?Q?[????g?p?????g??`E???L??? # Rule 6.2.1.2: ??E?E???C???[????g????C???`E???L???E?g?? # Rule 6.2.1.3: ??E?E???C???[????g??G?l???M?[?`E???L??E # Rule 6.2.1.4: ??E?E???C???[????????????E?v???C???[ # Rule 6.2.1.5: ??E?E???C???[????g????C???`E???L?u????? # Rule 6.2.1.6: ??U?v???C???[?????E???A?e?v???C???[??? # Rule 6.2.1.7: ??E?E???C???[????g??G?l???M?[?`E???L?u # Rule 7: ?Q?[????i?E # Rule 7.1: ?T?vE # Rule 7.1.1: ?Q?[????e?^?[???f????????E???J????????? # Rule 7.1.2: ??E???[????A?e?E?U???t?F?C?Y?f?A?e??U???t?F?C # Rule 7.2: ?A?N?`E???u?E???C???[ # Rule 7.2.1: ?Q?[??????t?F?C?Y???????A???v???C???[???w # Rule 7.2.1.1: ???v???C???[???w????t?F?C?Y????A?? # Rule 7.2.1.2: ???v???C???[???w?????E???F?C?Y????AE # Rule 7.2.2: ?A?N?`E???u?E???C???[????E????E??????v???C???[?? # Rule 7.3: ???t?F?C?Y # Rule 7.3.1: ???t?F?C?Y???A???????E?v???C???[????A?? # Rule 7.3.2: ??E????t?F?C?Y?????v???C???[??E ?l??E?????AE # Rule 7.3.2.1: ???t?F?C?Y???A?E?U?v???C???[?????`E # Rule 7.3.3: ???t?F?C?Y???A?e?A?N?`E???u?t?F?C?Y?f?IE.4?E??A?e?G # Rule 7.4: ?A?N?`E???u?t?F?C?Y # Rule 7.4.1: ???v???C???[??A?E?g??G?l???M?[?u???????? # Rule 7.4.2: ?e?^?[????n???f?????E?e?A?N?`E???u?t?F?C?Y??n?? # Rule 7.4.3: ?`?F?`E???^?C?~???O????????????B????`?F?`E???^?C?`E # Rule 7.5: ?G?l???M?[?t?F?C?Y # Rule 7.5.1: ?e?G?l???M?[?t?F?C?Y??n???f?E?U???????????RE # Rule 7.5.2: ???v???C???[??A?E?g??G?l???M?[?`E???L??? # Rule 7.5.3: ?`?F?`E???^?C?~???O????????????B????`?F?`E???^?C?`E # Rule 7.6: ?h???[?t?F?C?Y # Rule 7.6.1: ?e?h???[?t?F?C?Y??n???f?E?U???????????????AE # Rule 7.6.2: ???v???C???[??J?[?h??1 ??????????AE # Rule 7.6.3: ?`?F?`E???^?C?~???O????????????B????`?F?`E???^?C?`E # Rule 7.7: ???C???t?F?C?Y # Rule 7.7.1: ?e???C???t?F?C?Y??n???f?E?U???????????????AE # Rule 7.7.2: ???v???C???[??v???C?^?C?~???O?E?E.5.2?E????^????E # Rule 7.7.2.1: ???E?E?J?[?h??????N???E???1 ??I?��AE # Rule 7.7.2.2: ???E?E??D??????o?E?J?[?h??1 ???I?��A?? # Rule 7.7.3: ???C???t?F?C?Y???I?E??????AE # Rule 7.8: ???C?u?t?F?C?Y # Rule 7.8.1: ???v???C???[????C?u?t?F?C?Y?????s??????B????? # Rule 8: ???C?u?t?F?C?Y # Rule 8.1: ?T?vE # Rule 8.1.1: ???C?u?t?F?C?Y???A???v???C???[????D????C?`E # Rule 8.1.2: ???C?u?t?F?C?Y???A?e???C?u?J?[?h?Z?`E???t?F?C?Y?E # Rule 8.2: ???C?u?J?[?h?Z?`E???t?F?C?Y # Rule 8.2.1: ?e???C?u?t?F?C?Y??n???f?????E?e???C?u?J?[?h?Z?`E?? # Rule 8.2.2: ??U?v???C???[??A?E?g???D??J?[?h??3 ????? # Rule 8.2.3: ?`?F?`E???^?C?~???O????????????AE # Rule 8.2.4: ??U?v???C???[??A?E?g???D??J?[?h??3 ????? # Rule 8.2.5: ?`?F?`E???^?C?~???O????????????B????`?F?`E???^?C?`E # Rule 8.3: ?p?t?H?[?}???X?t?F?C?Y # Rule 8.3.1: ?p?t?H?[?}???X?t?F?C?Y???A???????E?v???C # Rule 8.3.2: ??E???t?H?[?}???X?t?F?C?Y?????v???C???[??E # Rule 8.3.2.1: ?p?t?H?[?}???X?t?F?C?Y???A?E?U?v???C # Rule 8.3.3: ???v???C???[??????E???E?e?p?t?H?[?}???X?t?F # Rule 8.3.4: ???v???C???[????g????C?u?J?[?h?u?????J?[ # Rule 8.3.4.1: ???v???C???[???e???C?u??????E???????E # Rule 8.3.5: ?`?F?`E???^?C?~???O????????????AE # Rule 8.3.6: ???E???_????v???C???[????C?u?J?[?h?u????? # Rule 8.3.7: ???v???C???[????C?u?J?[?h?u???????C?u?J?[?`E # Rule 8.3.8: ?e???C?u?J?n???f?E???????????????IE1.4?E??AE # Rule 8.3.9: ?`?F?`E???^?C?~???O????????????AE # Rule 8.3.10: ???v???C???[??A?E?g??A?N?`E???u??????? # Rule 8.3.11: ???v???C???[??A?E?g????C???`E???L?????E # Rule 8.3.12: ???v???C???[?????????u??????E?????? # Rule 8.3.13: ?`?F?`E???^?C?~???O????????????AE # Rule 8.3.14: ???v???C???[????g???????????o?E??n?E # Rule 8.3.15: ???v???C???[????C?u?J?[?h?u??????E???C?`E # Rule 8.3.15.1: ???????C?u???L?n?[?g????A??????C?`E # Rule 8.3.15.1.1: ???E??A?e # Rule 8.3.15.1.2: ????????E???C?u?J?[?h?E?E??E # Rule 8.3.16: ?O?q????E?????E????????C?u?J?[?h?E?E??E # Rule 8.3.17: ?`?F?`E???^?C?~???O????????????B????`?F?`E???^?C?`E # Rule 8.4: ???C?u???s????t?F?C?Y # Rule 8.4.1: ?e???C?u????t?F?C?Y??n???f?E?U???????????RE # Rule 8.4.2: ???C?u?J?[?h?u?????J?[?h??????v???C???[??A?E # Rule 8.4.2.1: ???E??A?e?v???C???[????g??G?[???? # Rule 8.4.3: ???C?u?E???v?X?R?A????E??????A?????E????E # Rule 8.4.3.1: ??????v???C???[???????E???C?u?J?[?h?u # Rule 8.4.3.2: ?????v???C???[????C?u?J?[?h?u????? # Rule 8.4.3.3: ??????v???C???[????C?u?J?[?h?u????? # Rule 8.4.4: ???C?u?J?[?h?u?????J?[?h??????v???C???[??A?? # Rule 8.4.5: ?`?F?`E???^?C?~???O????????????AE # Rule 8.4.6: ???v?X?R?A????E???A???C?u??????????v???C???[??E # Rule 8.4.6.1: ??????v???C???[???????E???C?u?J?[?h?u # Rule 8.4.6.2: ??E??????v???C???[????C?u?J?[?h?u???? # Rule 8.4.7: ???C?u??????????v???C???[??A?E?g????C?u?J?[?`E # Rule 8.4.7.1: ??????v???C???[???????????E?????E # Rule 8.4.8: ??E?E???C???[??A?E?g????C?u?u?????c?????E?? # Rule 8.4.9: ?`?F?`E???^?C?~???O????????????AE # Rule 8.4.10: ?e?^?[????I????f?????????E???U?????????E # Rule 8.4.11: ?`?F?`E???^?C?~???O????????????B????`?F?`E???^?C?`E # Rule 8.4.12: ???E???_??AE.4.11 ??`?F?`E???^?C?~???O??????E # Rule 8.4.13: 8.4.7 ???????A?????v???C???[?????E?????C # Rule 8.4.14: ???E?^?[?????I?E??????AE # Rule 9: ?J?[?h??\???E?v???C????? # Rule 9.1: ?\???E??? # Rule 9.1.1: ?\???E?A?N???E??A?E???E??A???E???E3 ???E # Rule 9.1.1.1: ?N???E????A?E???C?^?C?~???O???^????? # Rule 9.1.1.1.1: ?N???E???E?A?J?[?h?????E # Rule 9.1.1.2: ?????E????A????\?????????????E # Rule 9.1.1.2.1: ?????E???E?A?J?[?h?????E # Rule 9.1.1.3: ???E????A????\????L???????A?? # Rule 9.1.1.3.1: ???E???E?A?J?[?h?????E # Rule 9.2: ?????E??? # Rule 9.2.1: ?????E?e?P??????f?e?p??????f?e?u??????f?E3 ?? # Rule 9.2.1.1: ?e?P??????f???A??????????E??E???????E # Rule 9.2.1.2: ?e?p??????f???A????E???????i????? # Rule 9.2.1.3: ?e?u??????f???A?Q?[????????��????? # Rule 9.2.1.3.1: ?\???e?i?s??A?E??????A???????E??E # Rule 9.2.1.3.2: ?\???e?i?s??A?E??????A??????[?I # Rule 9.3: ?L????\????????\?E # Rule 9.3.1: ?????E???????A?????E??????e?L???f????? # Rule 9.3.2: ?????E?????E??????E?E?S?????????E???? # Rule 9.3.3: ?????E?????E??????E?E?S?????????E???? # Rule 9.3.4: ?\???E????????????E??????L?????????AE # Rule 9.3.4.1: ?????E????E?????E????v???C????E # Rule 9.3.4.1.1: ????J?[?h?E?v???C?????E?J?[?h??? # Rule 9.3.4.2: ?J?[?h?^?C?v???????o?E?????J?[?h?E?\?E # Rule 9.3.4.3: ?J?[?h?^?C?v?????C?u?????J?[?h?E?\???E?AE # Rule 9.4: ?R?X?g??x???? # Rule 9.4.1: ?N???E??????E???E????A?e?F?f?i?R?????E??E?GE # Rule 9.4.2: ?e?R?X?g???x?????f???f?R?X?g????????s???????E # Rule 9.4.2.1: ?R?X?g???E????s??????????A?e?L?X?g?E # Rule 9.4.2.2: ?R?X?g?E??E????????E?S?????x?????????E # Rule 9.4.3: ?R?X?g?E??E?? # Rule 9.5: ?`?F?`E???^?C?~???O??v???C?^?C?~???O # Rule 9.5.1: ?`?F?`E???^?C?~???O???A?Q?[????????????????[?? # Rule 9.5.1.1: ?`?F?`E???^?C?~???O????????A??????[???? # Rule 9.5.2: ?v???C?^?C?~???O???A?w?????v???C???[???E??E # Rule 9.5.3: ?`?F?`E???^?C?~???O?????????????A?Q?[??????E # Rule 9.5.3.1: ??????E???s????????[?????E?????? # Rule 9.5.3.2: ?v???C???[???E?X?^?[?????E??????? # Rule 9.5.3.3: ??A?N?`E???u?E???C???[???E?X?^?[?????E # Rule 9.5.3.4: ?`?F?`E???^?C?~???O???I?E??????AE # Rule 9.5.4: ??E??????v???C???[??v???C?^?C?~???O???????? # Rule 9.5.4.1: ?`?F?`E???^?C?~???O????????????B?`?F?`E???^?C # Rule 9.5.4.2: ?v???C?^?C?~???O?????????E?v???C???[?? # Rule 9.5.4.3: ?v???C?^?C?~???O??^??????E???C???[??E # Rule 9.6: ?v???C????? # Rule 9.6.1: ?N???E??????E????D??J?[?h?E?A?E???C??E # Rule 9.6.2: ?J?[?h??\????v???C??????E?A????E???E????E # Rule 9.6.2.1: ?v???C????\????D??J?[?h????E?????? # Rule 9.6.2.1.1: ?v???C???????J?[?h???????A????E # Rule 9.6.2.1.2: ????E???s??????AE # Rule 9.6.2.1.2.1: ???E??A????^?[????X?`E?E?W?? # Rule 9.6.2.1.3: ?v???C???????E????????A???? # Rule 9.6.2.2: ?J?[?h??\???????E?I?????E??????? # Rule 9.6.2.3: ?v???C???????R?X?g????????A????R # Rule 9.6.2.3.1: ?v???C???????????o?E??J?[?h????? # Rule 9.6.2.3.2: ?????o?E???E???C?????A?x???????E # Rule 9.6.2.3.2.1: ???????R?X?g???????E?E?? # Rule 9.6.2.4: ?J?[?h??\???E???????s??????AE # Rule 9.6.2.4.1: ?v???C????????????o?E???????A?? # Rule 9.6.2.4.2: ?v???C????????N???E??????E??? # Rule 9.6.2.4.2.1: ?\???E??????????????o?E?J?[ # Rule 9.6.3: ?J?[?h??\???f?`?I?��f??f?`?I??f?????????E # Rule 9.6.3.1: ?I??????w??????E?????A??????\ # Rule 9.6.3.1.1: ?I??????f?`???I?��f??f?`???I # Rule 9.6.3.1.2: ?I??????w??????E??????A?w?E # Rule 9.6.3.1.3: ?I??????w??????E??????A???E # Rule 9.6.3.1.4: ?I????E???E?J??????E????E?????J??E # Rule 9.7: ?????E???E???E # Rule 9.7.1: ?????E????A?????E?U?????????????????????AE # Rule 9.7.2: ?????E?????E???E?U?????????????????? # Rule 9.7.2.1: ?????E???E?U?????????E????????? # Rule 9.7.3: ?`?F?`E???^?C?~???O???????????i?K??A?E???E???E?`E # Rule 9.7.3.1: ??E??????E?????E???E?v???C???????A?E # Rule 9.7.3.1.1: ?????E????C???R?X?g???x????????? # Rule 9.7.3.2: ?I???E??????E?????E????v???C????? # Rule 9.7.3.2.1: ?????E????C???R?X?g???x????????? # Rule 9.7.4: ????J?[?h????????????�???U??????????? # Rule 9.7.4.1: ??????U?????????E????A????\?E # Rule 9.7.4.1.1: ?J?[?h?????J??�Y????E?J???A?? # Rule 9.7.4.1.2: ?J?[?h???X?`E?E?W???�F???O?E??? # Rule 9.7.4.1.3: ??L?????????O?E?A?E?J??�Y?? # Rule 9.7.4.2: ????J?[?h????????U???\????????A?? # Rule 9.7.5: ?????E???????A??~?E?????E???_??U?? # Rule 9.7.5.1: ?????U????A?????????????????E????E?? # Rule 9.7.6: ?????E????A?????E????E???????????????????AE # Rule 9.7.6.1: ???U????A????????????????????1 # Rule 9.7.7: ??E??????E?????E???E?v???C????A????????E?E # Rule 9.8: ?P???????E???E # Rule 9.8.1: ?P??????????s????��E?????????A???????E # Rule 9.9: ?p???????E???E # Rule 9.9.1: ?????E?p?????????????????J?[?h?E?E # Rule 9.9.1.1: ?J?[?h?E?g??\?L??????E???E?????A???? # Rule 9.9.1.2: ????A?E???^????E??????E?L???????/ # Rule 9.9.1.3: ????A?p???????E??E???E??????l???X??E # Rule 9.9.1.4: ????A?p???????E??E???E??????l??????E # Rule 9.9.1.4.1: ?n?E?g??u???[?h?E?????????E???? # Rule 9.9.1.5: ????A?p???????E??E???E??????l???X??E # Rule 9.9.1.5.1: ?n?E?g??u???[?h?E??????????Z????E # Rule 9.9.1.6: ????E9.9.1.2X-9.9.1.4 ??K?p??E?E?O?�I?? # Rule 9.9.1.7: ????E9.9.1.2X-9.9.1.6 ??K?p??E?E?O?�I?? # Rule 9.9.1.7.1: ?p???????E???????????E?????? # Rule 9.9.1.7.2: ?????O?E?\???E???E?A?????v?? # Rule 9.9.2: ???E???O??????????E???p???????E?A????\ # Rule 9.9.3: ?????E?????????J?[?h?E?E?????X????p?E # Rule 9.9.3.1: ?????E?E????????J?[?h????????�???E # Rule 9.10: ?u???????E???E # Rule 9.10.1: ?u????????????????E?????A????u???????E # Rule 9.10.1.1: ???????A?u????????E?E??????????? # Rule 9.10.2: ????????????E????u????????????????E # Rule 9.10.2.1: ?e??????��????J?[?h??\??????? # Rule 9.10.2.2: ?e??????��????Q?[??????s?????E # Rule 9.10.2.3: ??????????????A?e?u???????E?? # Rule 9.10.3: ?u????????I???^?u??????i?f?`?????A????? # Rule 9.11: ??I??? # Rule 9.11.1: ?????????????E?J?[?h?E?E????E??????Q?E???? # Rule 9.12: ?????E # Rule 9.12.1: ?\?????????A????????E????????????� # Rule 9.12.2: ?\???E?????????A????\???????J?[?h?A???E # Rule 10: ???[?????E # Rule 10.1: ???[?????E?E??{ # Rule 10.1.1: ???[?????E????A?Q?[?????????????E?????E # Rule 10.1.2: ???[?????E?E?A???t???`E?????E?E0.2?E????????A?`?F?`E?? # Rule 10.1.3: ???[?????E???E??????????s?????????? # Rule 10.2: ???t???`E???? # Rule 10.2.1: ???t???`E??????`?F?`E???^?C?~???O??????�y?A?Q?[ # Rule 10.2.2: ????E??E??????????????????A???t???`E???? # Rule 10.2.2.1: ??E??????v???C???[????C???`E???L?u??E # Rule 10.2.2.2: ???C???`E???L?u??????�H?��??E?????? # Rule 10.2.3: ???t???`E???????s???v???C???[??A?E?g??T?????? # Rule 10.2.4: ??????v???C???[??????????t???`E???????s?????? # Rule 10.3: ???????E # Rule 10.3.1: ??E??????v???C???[????????C?u?J?[?h?u???? # Rule 10.4: ?d?E?????o?E???E # Rule 10.4.1: ??E??????v???C???[??1 ???????o?E?G???A?? # Rule 10.5: ?s???J?[?h?E?E # Rule 10.5.1: ??E??????v???C???[????C?u?J?[?h?u???????C # Rule 10.5.2: ??E??????G?l???M?[?u?????G?l???M?[????E # Rule 10.5.3: ??E??????????o?E?G???A??A???d??????E???? # Rule 10.5.4: ??L?E??E????????E????????A?T??????u??E # Rule 10.6: ?s??????????E # Rule 10.6.1: ???????????v???C??????E?????????????E # Rule 11: ?L?[???[?h??L?[???[?h?E?E # Rule 11.1: ?T?vE # Rule 11.1.1: ?L?[???[?h???A?????E???E???s???\???????\ # Rule 11.1.2: ?????E??????????L?[???[?h?E????????AE # Rule 11.1.3: ?\???E??????E # Rule 11.2: ?^?[??1 ??E # Rule 11.2.2: ?L?[???[?`E # Rule 11.2.3: ?E # Rule 11.3: ?o?? # Rule 11.3.1: [Icon] ??A?????o?E???????o?E?G???A??u????E # Rule 11.3.2: ?E # Rule 11.4: ???C?u?J?n?? # Rule 11.4.1: [Icon] ??A???C?u???J?n??????????E # Rule 11.4.2: ?E # Rule 11.4.2.1: ?p?t?H?[?}???X?t?F?C?Y???A???v???C???[ # Rule 11.5: ???C?u?E???? # Rule 11.5.1: [Icon] ??A???C?u???????????????U?? # Rule 11.5.2: ?E # Rule 11.6: ?Z???^?[ # Rule 11.6.1: [Icon] ??A?E???E?v???C???????A?E???E?E # Rule 11.6.2: ?L?[???[?`E # Rule 11.6.3: ?L?[???[?`E # Rule 11.6.4: ?L?[???[?`E # Rule 11.7: ???T?C?`E # Rule 11.7.1: [Icon] ??A?E???E?v???C???????A?E???E?E # Rule 11.7.2: ?L?[???[?`E # Rule 11.7.3: ?L?[???[?`E # Rule 11.7.4: ?L?[???[?`E # Rule 11.8: ?E?T?C?`E # Rule 11.8.1: [Icon] ??A?E???E?v???C???????A?E???E?E # Rule 11.8.2: ?L?[???[?`E # Rule 11.8.3: ?L?[???[?`E # Rule 11.8.4: ?L?[???[?`E # Rule 11.9: ?|?W?V?????`?F???W # Rule 11.9.1: ?|?W?V?????`?F???W??????A????????o?E?????? # Rule 11.9.2: ?????o?E??????????????E?G???A??????????o?E # Rule 11.10: ?t?H?[???[?V?????`?F???W # Rule 11.10.1: ?t?H?[???[?V?????`?F???W??????A?X?`E?E?W???E # Rule 11.10.2: ???E?????1 ???G???A??2 ?l????E?????o?E # Rule 12: ???E?mE # Rule 12.1: ?i?E???? # Rule 12.1.1: ?????E???E???s?????A????s?????i?E?????E # Rule 12.1.1.1: ?A?N?`E???u?E???C???[?E?E.2?E??E?A????z???E # Rule 12.1.1.2: ?A?N?`E???u?E???C???[???????E?s?????E # Rule 12.1.1.3: ?????E?E??????A??????E?v???C???[?? # Rule 2025: ?N11 ?E1 ?? ver. 1.04 # --- END OF INDEX ---