LovecaSim / engine /tests /logic /test_order_deck_rust.py
trioskosmos's picture
Upload folder using huggingface_hub
bb3fbf9 verified
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()