import os import sys # Add project root to path sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))) try: import engine_rust except ImportError: # Try importing from backend if not in root try: from backend import engine_rust except ImportError: print("Could not import engine_rust. Make sure it is built.") sys.exit(1) print(f"DEBUG engine_rust file: {getattr(engine_rust, '__file__', 'No __file__')}") def test_order_deck_logic_rust(): # Load RUST_DB compiled_path = "data/cards_compiled.json" if not os.path.exists(compiled_path): print(f"Compiled data not found: {compiled_path}") return with open(compiled_path, "r", encoding="utf-8") as f: json_data = f.read() db = engine_rust.PyCardDatabase(json_data) gs = engine_rust.PyGameState(db) # Setup Logic relying on PyGameState bindings # We need to construct deck with known IDs. # Card 467 is PL!N-bp1-002-P target_id = 467 # We can't directly set fields on PyGameState easily unless exposed. # But init_game takes decks! # p0_deck, p1_deck, p0_energy, p1_energy, p0_lives, p1_lives # We want target_id in HAND. # init_game draws 6 cards. # So if we put target_id at end of deck (top), it will be drawn? # No, deck is popped from end. # Let's create a deck where target_id is at index 0..5 (drawn) # And known cards at 6..N (remaining in deck). # IDs for dummy cards? 1001..1005 might not exist in RUST_DB. # We should use valid IDs. # Let's just use 467 for everything to be safe, or some other valid ID. # Let's use 467 for hand, and maybe 40000 (Action/Energy? No) # Let's find another valid member ID. # ID 1 is usually valid? other_id = 1 # Deck construction: # Drawn (6): [467, other, other, other, other, other] # Remaining (Top->Bottom for drawing purposes? Or strictly array?) # Rust `initialize_game`: # p.main_deck = deck; (cloned) # draw 6: p.hand.push(p.main_deck.pop()) # So if we want 467 in hand, we put it at END of the list we pass. # We also want specific cards in DECK to check ordering. # Let's use 1, 2, 3 (assuming they exist). # If not sure, use 467 with different instance IDs? # Rust engine handles instance IDs internally if we pass base IDs? # Actually PyGameState::initialize_game takes List[u32]. # It converts to creating unique IDs internally? # Let's check logic.rs/py_bindings.rs. # Assuming it takes base IDs and makes them unique or takes UIDs. # Usually it takes UIDs if passed directly to `deck`. # server.py does `convert_deck_strings_to_ids` which calls `create_uid`. # So we should pass UIDs. BASE_ID_MASK = 0xFFFFF def mk_uid(base, idx): return base | (idx << 20) p0_main = [] # 3 known cards for deck (will be at bottom of our list, so deep in deck? No) # We want them at TOP of deck (end of list). # 1. Hand cards (will be popped LAST, so push them FIRST?) # Wait, `pop` takes from END. # So if deck is [A, B, C], pop gives C. # We want 467 to be drawn. So 467 should be at END of `p0_main`. # Remaining Deck: [Card C, Card B, Card A] <- Top (End) # Drawn Hand: [..., Target] # So list structure: [Card C, Card B, Card A, ..., Target] # Let's use IDs 10, 20, 30 for cards (assuming valid). # If they are invalid, they might work as dummy cards if DB lookup returns None (or crash). # Safest is to use valid ID 467 and just use UID distinctions. c_A = mk_uid(target_id, 10) c_B = mk_uid(target_id, 11) c_C = mk_uid(target_id, 12) c_D = mk_uid(target_id, 13) # Extra card for Draw Phase p0_main = [c_D, c_C, c_B, c_A] # A on top, then B, C, D. # Draw phase takes A. # LookDeck takes B, C, D (3 cards). # No, we draw 6 cards. # So we need at least 6 cards ON TOP of these. hand_uids = [mk_uid(target_id, i) for i in range(6)] # 0..5 p0_main.extend(hand_uids) # [C, B, A, H0, H1, ... H5] # Last is H5. # Pop 6 times -> Hand gets H5..H0. # Remaining deck: [C, B, A]. Top is A. p1_main = [mk_uid(target_id, 100 + i) for i in range(40)] # Energy p0_energy = [mk_uid(40000, i) for i in range(10)] p1_energy = [mk_uid(40000, 10 + i) for i in range(10)] # Lives p0_lives = [mk_uid(target_id, 200 + i) for i in range(3)] p1_lives = [mk_uid(target_id, 210 + i) for i in range(3)] gs.initialize_game(p0_main, p1_main, p0_energy, p1_energy, p0_lives, p1_lives) # Force turn to 1, Phase Main. # After init, it's MULLIGAN_P1. # We need to skip mulligan. # gs.do_action(0) # Pass/End mulligan? # Action 0 in Mulligan is "End Mulligan"? Or "Check/Pass"? # `game_state.py` says `a==0`: "【確認】マリガンを実行" (Execute Mulligan). gs.step(0) # P1 Mulligan gs.step(0) # P2 Mulligan # Now Phase.ACTIVE -> Energy -> Draw -> Main? # We need to reach Main Phase. # Skip phases. # In Rust `get_legal_actions` handles phase transitions often? # No, we must perform actions. # Active -> Energy (if no start effects). # Energy: Charge or Pass(0). # Auto-advance to Main Phase max_steps = 10 steps = 0 while gs.phase != 4 and steps < max_steps: # 4 = MAIN print(f"Current Phase: {gs.phase}, stepping 0...") gs.step(0) steps += 1 assert gs.phase == 4, f"Failed to reach Main phase. Current: {gs.phase}" print(f"Reached Main Phase: {gs.phase}") # Check if we have 467 in hand. # Hand should be populated. # Play card from Hand Index 0 (latest drawn) to Center (Area 1). # Action: 1 + 0*3 + 1 = 2. print("Playing card...") gs.step(2) print(f"Phase after play: {gs.phase}") print(f"Engine Rust File: {engine_rust.__file__}") print(f"GS Attributes: {dir(gs)}") print(f"Rule Log: {gs.rule_log}") # print(f"Looked Cards: {gs.looked_cards}") # print(f"Deck Count: {gs.deck_count}") # CHECK 1: Phase should be RESPONSE (10) assert gs.phase == 10, f"Expected Phase 10 (Response), got {gs.phase}" # CHECK 2: pending_choice_type # PyGameState exposes pending_choice_type (added in my fix? No, was there? check bindings) # The snippet of logic.rs showed self.pending_choice_type setting. # Does PyGameState binding expose it? # If not, we check get_legal_actions. # CHECK 3: Legal Actions mask = gs.get_legal_actions() # list of bools legal_indices = [i for i, x in enumerate(mask) if x] print(f"Legal Actions: {legal_indices}") # We expect 3 choices (for 3 cards looked) PLUS Done option. # 550 + 100 + 0 + 0 = 650 # 650, 651, 652 (Cards) # 653 (Done) expected = [650, 651, 652, 653] for e in expected: assert e in legal_indices, f"Expected action {e} not found" print("O_ORDER_DECK logic validated!") if __name__ == "__main__": test_order_deck_logic_rust()