Spaces:
Running
Running
| 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() | |