use crate::agent::AgentContext;
use crate::oxyde_game::behavior::{Behavior, BehaviorResult};
use crate::oxyde_game::emotion::EmotionalState;
use crate::oxyde_game::intent::Intent;
use crate::Result;
use std::sync::Arc;
#[async_trait::async_trait]
pub trait SelectionStrategy: Send + Sync {
async fn select_behavior(
&self,
behaviors: &[Arc<dyn Behavior>],
intent: &Intent,
emotional_state: &EmotionalState,
context: &AgentContext,
) -> Result<(String, BehaviorResult)>;
fn name(&self) -> &str;
}
#[derive(Debug, Clone)]
pub struct EmotionModulatedStrategy;
impl EmotionModulatedStrategy {
pub fn new() -> Self {
Self
}
}
impl Default for EmotionModulatedStrategy {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl SelectionStrategy for EmotionModulatedStrategy {
async fn select_behavior(
&self,
behaviors: &[Arc<dyn Behavior>],
intent: &Intent,
emotional_state: &EmotionalState,
context: &AgentContext,
) -> Result<(String, BehaviorResult)> {
let mut candidates = Vec::new();
for behavior in behaviors {
if let Some(trigger) = behavior.emotion_trigger() {
if !trigger.matches(emotional_state) {
continue; }
}
if behavior.matches_intent(intent).await {
candidates.push(behavior);
}
}
if candidates.is_empty() {
return Ok(("none".to_string(), BehaviorResult::None));
}
candidates.sort_by(|a, b| {
let a_priority = a.priority() as i32 + a.emotional_priority_modifier(emotional_state);
let b_priority = b.priority() as i32 + b.emotional_priority_modifier(emotional_state);
b_priority.cmp(&a_priority)
});
let selected = candidates[0];
let result = selected.execute(intent, context).await?;
let name = format!("{:?}", selected)
.split('(')
.next()
.unwrap_or("unknown")
.to_string();
Ok((name, result))
}
fn name(&self) -> &str {
"emotion_modulated"
}
}
#[derive(Debug, Clone)]
pub struct FixedPriorityStrategy;
impl FixedPriorityStrategy {
pub fn new() -> Self {
Self
}
}
impl Default for FixedPriorityStrategy {
fn default() -> Self {
Self::new()
}
}
#[async_trait::async_trait]
impl SelectionStrategy for FixedPriorityStrategy {
async fn select_behavior(
&self,
behaviors: &[Arc<dyn Behavior>],
intent: &Intent,
_emotional_state: &EmotionalState, context: &AgentContext,
) -> Result<(String, BehaviorResult)> {
let mut candidates = Vec::new();
for behavior in behaviors {
if behavior.matches_intent(intent).await {
candidates.push(behavior);
}
}
if candidates.is_empty() {
return Ok(("none".to_string(), BehaviorResult::None));
}
candidates.sort_by(|a, b| b.priority().cmp(&a.priority()));
let selected = candidates[0];
let result = selected.execute(intent, context).await?;
let name = format!("{:?}", selected)
.split('(')
.next()
.unwrap_or("unknown")
.to_string();
Ok((name, result))
}
fn name(&self) -> &str {
"fixed_priority"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oxyde_game::behavior::{FleeBehavior, NeutralGreetingBehavior};
use crate::oxyde_game::intent::IntentType;
#[tokio::test]
async fn test_emotion_modulated_strategy() {
use crate::oxyde_game::behavior::{DefaultAcknowledgeBehavior, AggressiveBehavior};
let mut emotional_state = EmotionalState::new();
emotional_state.update_emotion("anger", 0.8);
let behaviors: Vec<Arc<dyn Behavior>> = vec![
Arc::new(AggressiveBehavior::new(0.6)), Arc::new(DefaultAcknowledgeBehavior::new()), ];
let intent = Intent {
intent_type: IntentType::Hostile, confidence: 0.9,
raw_input: "I'm going to attack you!".to_string(),
keywords: vec!["attack".to_string()],
};
let strategy = EmotionModulatedStrategy::new();
let context = std::collections::HashMap::new();
let (name, _result) = strategy
.select_behavior(&behaviors, &intent, &emotional_state, &context)
.await
.unwrap();
assert!(name.contains("AggressiveBehavior"), "Expected AggressiveBehavior, got: {}", name);
}
#[tokio::test]
async fn test_fixed_priority_strategy() {
use crate::oxyde_game::behavior::DefaultAcknowledgeBehavior;
let emotional_state = EmotionalState::new();
let behaviors: Vec<Arc<dyn Behavior>> = vec![
Arc::new(FleeBehavior::new(0.7)),
Arc::new(DefaultAcknowledgeBehavior::new()), ];
let intent = Intent {
intent_type: IntentType::Threat,
confidence: 0.9,
raw_input: "Threatening message".to_string(),
keywords: vec!["threat".to_string()],
};
let strategy = FixedPriorityStrategy::new();
let context = std::collections::HashMap::new();
let (name, _result) = strategy
.select_behavior(&behaviors, &intent, &emotional_state, &context)
.await
.unwrap();
assert!(name.contains("FleeBehavior"), "Expected FleeBehavior, got: {}", name);
}
#[tokio::test]
async fn test_neutral_fallback_when_no_emotional_match() {
let emotional_state = EmotionalState::new();
let behaviors: Vec<Arc<dyn Behavior>> = vec![
Arc::new(FleeBehavior::new(0.7)), Arc::new(NeutralGreetingBehavior::new()), ];
let intent = Intent {
intent_type: IntentType::Greeting,
confidence: 0.9,
raw_input: "Hello".to_string(),
keywords: vec!["hello".to_string()],
};
let strategy = EmotionModulatedStrategy::new();
let context = std::collections::HashMap::new();
let (name, _result) = strategy
.select_behavior(&behaviors, &intent, &emotional_state, &context)
.await
.unwrap();
assert!(name.contains("NeutralGreetingBehavior"), "Expected NeutralGreetingBehavior, got: {}", name);
}
}