cardinal_kernel/engine/
scripting.rs1use rhai::{Engine, AST, Scope, Dynamic};
2use std::collections::HashMap;
3use crate::error::CardinalError;
4
5pub struct RhaiEngine {
8 engine: Engine,
9 scripts: HashMap<String, AST>,
11}
12
13impl RhaiEngine {
14 pub fn new() -> Self {
16 let mut engine = Engine::new();
17
18 engine.set_max_operations(10_000); engine.set_max_expr_depths(32, 32); Self::register_helpers(&mut engine);
24
25 RhaiEngine {
26 engine,
27 scripts: HashMap::new(),
28 }
29 }
30
31 fn register_helpers(engine: &mut Engine) {
33 engine.register_fn("deal_damage", |target: i32, amount: i32| {
35 let mut map = rhai::Map::new();
36 map.insert("type".into(), Dynamic::from("damage"));
37 map.insert("target".into(), Dynamic::from(target));
38 map.insert("amount".into(), Dynamic::from(amount));
39 Dynamic::from(map)
40 });
41
42 engine.register_fn("draw_cards", |player: i32, count: i32| {
44 let mut map = rhai::Map::new();
45 map.insert("type".into(), Dynamic::from("draw"));
46 map.insert("player".into(), Dynamic::from(player));
47 map.insert("count".into(), Dynamic::from(count));
48 Dynamic::from(map)
49 });
50
51 engine.register_fn("gain_life", |player: i32, amount: i32| {
53 let mut map = rhai::Map::new();
54 map.insert("type".into(), Dynamic::from("gain_life"));
55 map.insert("player".into(), Dynamic::from(player));
56 map.insert("amount".into(), Dynamic::from(amount));
57 Dynamic::from(map)
58 });
59
60 engine.register_fn("pump_creature", |card: i32, power: i32, toughness: i32| {
62 let mut map = rhai::Map::new();
63 map.insert("type".into(), Dynamic::from("pump"));
64 map.insert("card".into(), Dynamic::from(card));
65 map.insert("power".into(), Dynamic::from(power));
66 map.insert("toughness".into(), Dynamic::from(toughness));
67 Dynamic::from(map)
68 });
69 }
70
71 pub fn register_script(&mut self, card_id: String, script: &str) -> Result<(), CardinalError> {
73 match self.engine.compile(script) {
74 Ok(ast) => {
75 self.scripts.insert(card_id, ast);
76 Ok(())
77 }
78 Err(err) => {
79 Err(CardinalError(format!("Failed to compile script for card {}: {}", card_id, err)))
80 }
81 }
82 }
83
84 pub fn execute_ability(&self, card_id: &str, context: ScriptContext) -> Result<Vec<Dynamic>, CardinalError> {
87 let ast = self.scripts.get(card_id)
88 .ok_or_else(|| CardinalError(format!("No script registered for card {}", card_id)))?;
89
90 let mut scope = Scope::new();
91
92 scope.push("controller", context.controller as i32);
94 scope.push("source_card", context.source_card as i32);
95
96 match self.engine.call_fn::<Dynamic>(&mut scope, ast, "execute_ability", ()) {
98 Ok(result) => {
99 if let Some(arr) = result.clone().try_cast::<rhai::Array>() {
102 Ok(arr)
103 } else {
104 Ok(vec![result])
106 }
107 }
108 Err(err) => {
109 Err(CardinalError(format!("Script execution failed for card {}: {}", card_id, err)))
110 }
111 }
112 }
113}
114
115#[derive(Debug, Clone)]
117pub struct ScriptContext {
118 pub controller: u8,
119 pub source_card: u32,
120}
121
122impl Default for RhaiEngine {
123 fn default() -> Self {
124 Self::new()
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_rhai_engine_creation() {
134 let engine = RhaiEngine::new();
135 assert_eq!(engine.scripts.len(), 0);
136 }
137
138 #[test]
139 fn test_register_simple_script() {
140 let mut engine = RhaiEngine::new();
141 let script = r#"
142 fn execute_ability() {
143 deal_damage(0, 2)
144 }
145 "#;
146
147 let result = engine.register_script("test_card".to_string(), script);
148 assert!(result.is_ok());
149 assert_eq!(engine.scripts.len(), 1);
150 }
151
152 #[test]
153 fn test_execute_simple_script() {
154 let mut engine = RhaiEngine::new();
155 let script = r#"
156 fn execute_ability() {
157 deal_damage(0, 2)
158 }
159 "#;
160
161 engine.register_script("test_card".to_string(), script).unwrap();
162
163 let context = ScriptContext {
164 controller: 0,
165 source_card: 1,
166 };
167
168 let result = engine.execute_ability("test_card", context);
169 assert!(result.is_ok());
170
171 let commands = result.unwrap();
172 assert_eq!(commands.len(), 1);
173 }
174
175 #[test]
176 fn test_execute_multi_command_script() {
177 let mut engine = RhaiEngine::new();
178 let script = r#"
179 fn execute_ability() {
180 [
181 deal_damage(0, 2),
182 draw_cards(0, 1)
183 ]
184 }
185 "#;
186
187 engine.register_script("test_card".to_string(), script).unwrap();
188
189 let context = ScriptContext {
190 controller: 0,
191 source_card: 1,
192 };
193
194 let result = engine.execute_ability("test_card", context);
195 assert!(result.is_ok());
196
197 let commands = result.unwrap();
198 assert_eq!(commands.len(), 2);
199 }
200}