File size: 7,256 Bytes
bb3fbf9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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()