rabukasim / engine_rust_src /src /opcode_missing_tests.rs
trioskosmos's picture
Upload folder using huggingface_hub
463f868 verified
use crate::core::hearts::HeartBoard;
use crate::core::logic::*;
use crate::test_helpers::{create_test_db, create_test_state};
// use std::collections::HashMap;
#[test]
fn test_opcode_select_member() {
let db = create_test_db();
let mut state = create_test_state();
// Member in slot 0
state.players[0].stage[0] = 10;
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// O_SELECT_MEMBER 1 (Count 1)
let bc = vec![O_SELECT_MEMBER, 1, 0, 0, 0, O_RETURN, 0, 0, 0, 0];
state.resolve_bytecode_cref(&db, &bc, &ctx);
// Updated behavior: Should enter Response phase and set pending choice
assert_eq!(
state.phase,
Phase::Response,
"O_SELECT_MEMBER should enter Phase::Response"
);
assert_eq!(
state
.interaction_stack
.last()
.map(|i| i.choice_type)
.unwrap_or(ChoiceType::None),
ChoiceType::SelectMember,
"Pending choice type mismatch"
);
}
#[test]
fn test_opcode_select_live() {
let db = create_test_db();
let mut state = create_test_state();
// Live in slot 0
state.players[0].live_zone[0] = 1001;
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// O_SELECT_LIVE 1
let bc = vec![O_SELECT_LIVE, 1, 0, 0, 0, O_RETURN, 0, 0, 0, 0];
state.resolve_bytecode_cref(&db, &bc, &ctx);
// Updated behavior
assert_eq!(
state.phase,
Phase::Response,
"O_SELECT_LIVE should enter Phase::Response"
);
assert_eq!(
state
.interaction_stack
.last()
.map(|i| i.choice_type)
.unwrap_or(ChoiceType::None),
ChoiceType::SelectLive,
"Pending choice type mismatch"
);
}
#[test]
fn test_opcode_opponent_choose() {
let db = create_test_db();
let mut state = create_test_state();
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// O_OPPONENT_CHOOSE
let bc = vec![O_OPPONENT_CHOOSE, 1, 0, 0, 0, O_RETURN, 0, 0, 0, 0];
state.resolve_bytecode_cref(&db, &bc, &ctx);
// Updated behavior
assert_eq!(
state.phase,
Phase::Response,
"O_OPPONENT_CHOOSE should enter Phase::Response"
);
assert_eq!(
state
.interaction_stack
.last()
.map(|i| i.choice_type)
.unwrap_or(ChoiceType::None),
ChoiceType::OpponentChoose,
"Pending choice type mismatch"
);
// After my fix, ctx.player_id correctly remains the activator (0),
// but the engine correctly suspends with the opponent (1) as the current_player.
assert_eq!(
state.current_player, 1,
"Engine should flip current_player to opponent (player 1) during suspension"
);
}
#[test]
fn test_opcode_prevent_activate() {
let mut db = create_test_db();
let mut state = create_test_state();
// Add a dummy member to DB
let mut m = MemberCard::default();
m.card_id = 100;
m.abilities.push(Ability {
trigger: TriggerType::Activated,
costs: vec![Cost {
cost_type: AbilityCostType::None,
value: 0,
..Default::default()
}],
..Default::default()
});
db.members.insert(12343, m.clone());
// Place member on stage
state.players[0].stage[0] = 12343;
// 1. Initial check: Activation possible (mock check, logic.rs handles this)
// We can't fully mock activate_ability without a complex DB setup,
// but we can check the flag and the specific error condition if possible.
// For now, let's verify the flag setting and the error from activate_ability.
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// 2. Apply Restriction
// O_PREVENT_ACTIVATE, val=0, attr=0, target=0 (Self)
let bc = vec![O_PREVENT_ACTIVATE, 0, 0, 0, 0, O_RETURN, 0, 0, 0, 0];
state.resolve_bytecode_cref(&db, &bc, &ctx);
assert_eq!(
state.players[0].prevent_activate, 1,
"Flag should be set"
);
// 3. Try to activate
// activate_ability uses current_player
state.current_player = 0;
// activate_ability(db, slot_idx, ab_idx)
let res = state.activate_ability(&db, 0, 0);
assert!(res.is_err(), "Activation should fail");
// Depending on logic.rs implementation, error string might vary slightly
// logic.rs: "Cannot activate abilities due to restriction"
if let Err(e) = res {
assert!(
e.contains("restriction"),
"Error should mention restriction: {}",
e
);
}
}
#[test]
fn test_opcode_prevent_baton_touch() {
let mut db = create_test_db();
let mut state = create_test_state();
// Dummy member
let mut m = MemberCard::default();
m.card_id = 10;
m.cost = 1;
// Need abilities list initialized
m.abilities = vec![];
db.members.insert(19, m.clone());
// Setup: Slot 0 has a card (ID 10)
state.players[0].stage[0] = 10;
state.players[0].baton_touch_limit = 1;
state.players[0].hand.push(19); // Card to play
state.players[0].hand_added_turn.push(0);
// Give energy
state.players[0].tapped_energy_mask = 0; // 2 energy
// 1. Apply Restriction (Global prevent baton touch on player)
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// O_PREVENT_BATON_TOUCH
let bc = vec![O_PREVENT_BATON_TOUCH, 0, 0, 0, 0, O_RETURN, 0, 0, 0, 0];
state.resolve_bytecode_cref(&db, &bc, &ctx);
assert_eq!(
state.players[0].prevent_baton_touch, 1,
"Flag should be set"
);
// 2. Try to Baton Touch (Play to slot 0)
state.current_player = 0;
let res = state.play_member(&db, 0, 0); // hand_idx=0, slot_idx=0
assert!(res.is_err(), "Baton touch should fail");
if let Err(e) = res {
assert!(
e.contains("restricted"),
"Error should mention restricted: {}",
e
);
}
}
#[test]
fn test_opcode_prevent_play_to_slot() {
let mut db = create_test_db();
let mut state = create_test_state();
// Dummy
let mut m = MemberCard::default();
m.card_id = 10;
m.cost = 0;
m.abilities = vec![];
db.members.insert(10, m.clone());
state.players[0].hand.push(10); // idx 0
state.players[0].hand.push(10); // idx 1 (if needed)
state.players[0].hand_added_turn.push(0);
state.players[0].hand_added_turn.push(0);
// 1. Apply Restriction to Slot 1 (Target=1)
let ctx = AbilityContext {
player_id: 0,
..Default::default()
};
// O_PREVENT_PLAY_TO_SLOT, val=0, attr=0, target_slot=1 (s parameter)
// interpreter.rs: if target_slot >= 0 && target_slot < 3 ...
// bc[3] is s/target_slot.
let bc = vec![O_PREVENT_PLAY_TO_SLOT, 0, 0, 0, 1, O_RETURN, 0, 0, 0, 0];
state.resolve_bytecode_cref(&db, &bc, &ctx);
assert_ne!(
state.players[0].prevent_play_to_slot_mask & (1 << 1),
0,
"Mask should be set for slot 1"
);
// 2. Try to play to Slot 1
state.current_player = 0;
// According to Q181, if the slot is EMPTY, play is ALLOWED even if restricted.
// To test the "failure" (blocking), we must have a member there already.
state.players[0].stage[1] = 10;
let res = state.play_member(&db, 0, 1); // hand_idx 0 to slot 1 (occupied + restricted)
assert!(res.is_err(), "Play to slot 1 should fail when occupied and restricted");
if let Err(e) = res {
assert!(
e.contains("restriction"),
"Error should mention restriction: {}",
e
);
}
}
#[test]
fn test_opcode_heart_modifiers() {
let mut db = create_test_db();
let mut state = create_test_state();
// Create Live Card
let mut l = LiveCard::default();
l.card_id = 10001;
l.name = "Test Live".to_string();
// Requirements: 1 Pink (idx 0), 1 Red (idx 1)
l.required_hearts = [10, 1, 0, 0, 0, 0, 0];
l.hearts_board = HeartBoard::from_array(&l.required_hearts);
// Ability 1: Increase Pink Cost by 1 (O_INCREASE_HEART_COST 1, 1 (Pink))
// Ability 2: Transform Red to Blue (O_TRANSFORM_HEART 2(Red), 5(Blue))
l.abilities.push(Ability {
trigger: TriggerType::Constant,
bytecode: vec![
O_INCREASE_HEART_COST,
1,
1,
0,
0,
O_TRANSFORM_HEART,
2,
5,
0,
0,
O_RETURN,
0,
0,
0,
0,
],
..Default::default()
});
db.lives.insert(10001, l.clone());
// Set up state
state.players[0].live_zone[0] = 10001;
state.players[0].live_zone[1] = -1;
state.players[0].live_zone[2] = -1;
// Verify Logic via check_live_success directly?
// Or simulate total_hearts and see if it passes.
// Requirement expectation:
// Base: 1 Pink, 1 Red
// Increase Pink by 1 -> 2 Pink
// Transform Red (10) to Blue -> 0 Red, 1 Blue
// Final: 2 Pink, 0 Red, 1 Blue.
let pass_hearts = [11, 0, 0, 0, 1, 0, 0]; // Exact match
let fail_hearts = [10, 1, 0, 0, 0, 0, 0]; // Original requirements (should fail)
assert!(
state.check_live_success(&db, 0, &l, &pass_hearts),
"Should pass with modified requirements"
);
assert!(
!state.check_live_success(&db, 0, &l, &fail_hearts),
"Should fail with original requirements"
);
}