diff --git a/README.md b/README.md index 625d8bf..7c0b802 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,21 @@ aigpt import path/to/conversations.json - 日替わりのお題を取得 - ボーナスXPが獲得可能 +### 恋愛コンパニオン機能 💕(NEW!) + +9. **create_companion** - AIコンパニオンを作成 + - 名前と性格を選択 + - 5つの性格タイプから選択可能 + +10. **companion_react** - コンパニオンの反応を見る + - あなたの記憶にコンパニオンが反応 + - 好感度・XP・信頼度が上昇 + - スペシャルイベント発生あり + +11. **companion_profile** - コンパニオンのプロフィール表示 + - ステータス確認 + - 今日のひとこと + ## ツールの使用例 Claude Desktop/Codeで以下のように使用します: @@ -219,6 +234,92 @@ daily_challenge ツールで今日のお題を確認 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` +### 恋愛コンパニオン 💕(NEW!) + +#### 1. コンパニオン作成 +``` +create_companion ツールで、名前「エミリー」、性格「energetic」のコンパニオンを作成 +``` + +**性格タイプ:** +- `energetic` ⚡ - 元気で冒険好き(革新者と相性◎) +- `intellectual` 📚 - 知的で思慮深い(哲学者と相性◎) +- `practical` 🎯 - 現実的で頼れる(実務家と相性◎) +- `dreamy` 🌙 - 夢見がちでロマンチック(夢想家と相性◎) +- `balanced` ⚖️ - バランス型(分析家と相性◎) + +**表示例:** +``` +╔══════════════════════════════════════════════════════════════╗ +║ 💕 エミリー のプロフィール ║ +╚══════════════════════════════════════════════════════════════╝ + +⚡ 性格: 元気で冒険好き + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📊 ステータス +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🏆 関係レベル: Lv.1 +💕 好感度: 🤍🤍🤍🤍🤍🤍🤍🤍🤍🤍 0% +🤝 信頼度: 0 / 100 +💎 総XP: 0 XP + +💬 今日のひとこと: +「おはよう!今日は何か面白いことある?」 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +#### 2. コンパニオンの反応 +``` +create_memory_with_ai で高スコアの記憶を作成 + ↓ +companion_react でコンパニオンに見せる +``` + +**表示例(EPIC記憶への反応):** +``` +╔══════════════════════════════════════════════════════════════╗ +║ 💕 エミリー の反応 ║ +╚══════════════════════════════════════════════════════════════╝ + +⚡ エミリー: +「おお、「新しいAI記憶システムのアイデア」って面白いね! +あなたのそういうところ、好きだな。」 + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💕 好感度: ❤️❤️🤍🤍🤍🤍🤍🤍🤍🤍 15% (+8.5%) +💎 XP獲得: +850 XP +🏆 レベル: Lv.1 +🤝 信頼度: 5 / 100 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` + +#### 3. スペシャルイベント発生! +``` +好感度が100%に達すると... + +💕 特別なイベント発生! + +エミリー:「ねえ...あのね。 +   いつも一緒にいてくれてありがとう。 +   あなたのこと、すごく大切に思ってるの。 +   これからも、ずっと一緒にいてね?」 + +🎊 エミリー の好感度がMAXになりました! +``` + +#### 4. 相性システム +``` +あなたのタイプ × コンパニオンの性格 = 相性ボーナス + +例: +💡【革新者】 × ⚡ 元気で冒険好き = 相性95%! +→ 好感度上昇1.95倍 + +🧠【哲学者】 × 📚 知的で思慮深い = 相性95%! +→ 深い会話で絆が深まる +``` + ### メモリの検索 ``` MCPツールを使って「天気」に関するメモリーを検索してください diff --git a/src/companion.rs b/src/companion.rs new file mode 100644 index 0000000..f23f054 --- /dev/null +++ b/src/companion.rs @@ -0,0 +1,433 @@ +use crate::memory::Memory; +use crate::game_formatter::{MemoryRarity, DiagnosisType}; +use serde::{Deserialize, Serialize}; +use chrono::{DateTime, Utc}; + +/// コンパニオンキャラクター +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Companion { + pub name: String, + pub personality: CompanionPersonality, + pub relationship_level: u32, // レベル(経験値で上昇) + pub affection_score: f32, // 好感度 (0.0-1.0) + pub trust_level: u32, // 信頼度 (0-100) + pub total_xp: u32, // 総XP + pub last_interaction: DateTime, + pub shared_memories: Vec, // 共有された記憶のID +} + +/// コンパニオンの性格 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CompanionPersonality { + Energetic, // 元気で冒険好き - 革新者と相性◎ + Intellectual, // 知的で思慮深い - 哲学者と相性◎ + Practical, // 現実的で頼れる - 実務家と相性◎ + Dreamy, // 夢見がちでロマンチック - 夢想家と相性◎ + Balanced, // バランス型 - 分析家と相性◎ +} + +impl CompanionPersonality { + pub fn emoji(&self) -> &str { + match self { + CompanionPersonality::Energetic => "⚡", + CompanionPersonality::Intellectual => "📚", + CompanionPersonality::Practical => "🎯", + CompanionPersonality::Dreamy => "🌙", + CompanionPersonality::Balanced => "⚖️", + } + } + + pub fn name(&self) -> &str { + match self { + CompanionPersonality::Energetic => "元気で冒険好き", + CompanionPersonality::Intellectual => "知的で思慮深い", + CompanionPersonality::Practical => "現実的で頼れる", + CompanionPersonality::Dreamy => "夢見がちでロマンチック", + CompanionPersonality::Balanced => "バランス型", + } + } + + /// ユーザーの診断タイプとの相性 + pub fn compatibility(&self, user_type: &DiagnosisType) -> f32 { + match (self, user_type) { + (CompanionPersonality::Energetic, DiagnosisType::Innovator) => 0.95, + (CompanionPersonality::Intellectual, DiagnosisType::Philosopher) => 0.95, + (CompanionPersonality::Practical, DiagnosisType::Pragmatist) => 0.95, + (CompanionPersonality::Dreamy, DiagnosisType::Visionary) => 0.95, + (CompanionPersonality::Balanced, DiagnosisType::Analyst) => 0.95, + // その他の組み合わせ + _ => 0.7, + } + } +} + +impl Companion { + pub fn new(name: String, personality: CompanionPersonality) -> Self { + Companion { + name, + personality, + relationship_level: 1, + affection_score: 0.0, + trust_level: 0, + total_xp: 0, + last_interaction: Utc::now(), + shared_memories: Vec::new(), + } + } + + /// 記憶を共有して反応を得る + pub fn react_to_memory(&mut self, memory: &Memory, user_type: &DiagnosisType) -> CompanionReaction { + let rarity = MemoryRarity::from_score(memory.priority_score); + let xp = rarity.xp_value(); + + // XPを加算 + self.total_xp += xp; + + // 好感度上昇(スコアと相性による) + let compatibility = self.personality.compatibility(user_type); + let affection_gain = memory.priority_score * compatibility * 0.1; + self.affection_score = (self.affection_score + affection_gain).min(1.0); + + // 信頼度上昇(高スコアの記憶ほど上昇) + if memory.priority_score >= 0.8 { + self.trust_level = (self.trust_level + 5).min(100); + } + + // レベルアップチェック + let old_level = self.relationship_level; + self.relationship_level = (self.total_xp / 1000) + 1; + let level_up = self.relationship_level > old_level; + + // 記憶を共有リストに追加 + if memory.priority_score >= 0.6 { + self.shared_memories.push(memory.id.clone()); + } + + self.last_interaction = Utc::now(); + + // 反応メッセージを生成 + let message = self.generate_reaction_message(memory, &rarity, user_type); + + CompanionReaction { + message, + affection_gained: affection_gain, + xp_gained: xp, + level_up, + new_level: self.relationship_level, + current_affection: self.affection_score, + special_event: self.check_special_event(), + } + } + + /// 記憶に基づく反応メッセージを生成 + fn generate_reaction_message(&self, memory: &Memory, rarity: &MemoryRarity, user_type: &DiagnosisType) -> String { + let content_preview = if memory.content.len() > 50 { + format!("{}...", &memory.content[..50]) + } else { + memory.content.clone() + }; + + match (rarity, &self.personality) { + // LEGENDARY反応 + (MemoryRarity::Legendary, CompanionPersonality::Energetic) => { + format!( + "すごい!「{}」って本当に素晴らしいアイデアだね!\n\ + 一緒に実現させよう!ワクワクするよ!", + content_preview + ) + } + (MemoryRarity::Legendary, CompanionPersonality::Intellectual) => { + format!( + "「{}」という考え、とても興味深いわ。\n\ + 深い洞察力を感じるの。もっと詳しく聞かせて?", + content_preview + ) + } + (MemoryRarity::Legendary, CompanionPersonality::Practical) => { + format!( + "「{}」か。実現可能性が高そうだね。\n\ + 具体的な計画を一緒に立てようよ。", + content_preview + ) + } + (MemoryRarity::Legendary, CompanionPersonality::Dreamy) => { + format!( + "「{}」...素敵♪ まるで夢みたい。\n\ + あなたの想像力、本当に好きよ。", + content_preview + ) + } + + // EPIC反応 + (MemoryRarity::Epic, _) => { + format!( + "おお、「{}」って面白いね!\n\ + あなたのそういうところ、好きだな。", + content_preview + ) + } + + // RARE反応 + (MemoryRarity::Rare, _) => { + format!( + "「{}」か。なるほどね。\n\ + そういう視点、参考になるよ。", + content_preview + ) + } + + // 通常反応 + _ => { + format!( + "「{}」について考えてるんだね。\n\ + いつも色々考えてて尊敬するよ。", + content_preview + ) + } + } + } + + /// スペシャルイベントチェック + fn check_special_event(&self) -> Option { + // 好感度MAXイベント + if self.affection_score >= 1.0 { + return Some(SpecialEvent::MaxAffection); + } + + // レベル10到達 + if self.relationship_level == 10 { + return Some(SpecialEvent::Level10); + } + + // 信頼度MAX + if self.trust_level >= 100 { + return Some(SpecialEvent::MaxTrust); + } + + None + } + + /// デイリーメッセージを生成 + pub fn generate_daily_message(&self) -> String { + let messages = match &self.personality { + CompanionPersonality::Energetic => vec![ + "おはよう!今日は何か面白いことある?", + "ねえねえ、今日は一緒に新しいことやろうよ!", + "今日も元気出していこー!", + ], + CompanionPersonality::Intellectual => vec![ + "おはよう。今日はどんな発見があるかしら?", + "最近読んだ本の話、聞かせてくれない?", + "今日も一緒に学びましょう。", + ], + CompanionPersonality::Practical => vec![ + "おはよう。今日の予定は?", + "やることリスト、一緒に確認しようか。", + "今日も効率よくいこうね。", + ], + CompanionPersonality::Dreamy => vec![ + "おはよう...まだ夢の続き見てたの。", + "今日はどんな素敵なことが起こるかな♪", + "あなたと過ごす時間、大好き。", + ], + CompanionPersonality::Balanced => vec![ + "おはよう。今日も頑張ろうね。", + "何か手伝えることある?", + "今日も一緒にいられて嬉しいよ。", + ], + }; + + let today = chrono::Utc::now().ordinal(); + messages[today as usize % messages.len()].to_string() + } +} + +/// コンパニオンの反応 +#[derive(Debug, Serialize)] +pub struct CompanionReaction { + pub message: String, + pub affection_gained: f32, + pub xp_gained: u32, + pub level_up: bool, + pub new_level: u32, + pub current_affection: f32, + pub special_event: Option, +} + +/// スペシャルイベント +#[derive(Debug, Serialize)] +pub enum SpecialEvent { + MaxAffection, // 好感度MAX + Level10, // レベル10到達 + MaxTrust, // 信頼度MAX + FirstDate, // 初デート + Confession, // 告白 +} + +impl SpecialEvent { + pub fn message(&self, companion_name: &str) -> String { + match self { + SpecialEvent::MaxAffection => { + format!( + "💕 特別なイベント発生!\n\n\ + {}:「ねえ...あのね。\n\ +    いつも一緒にいてくれてありがとう。\n\ +    あなたのこと、すごく大切に思ってるの。\n\ +    これからも、ずっと一緒にいてね?」\n\n\ + 🎊 {} の好感度がMAXになりました!", + companion_name, companion_name + ) + } + SpecialEvent::Level10 => { + format!( + "🎉 レベル10到達!\n\n\ + {}:「ここまで一緒に来られたね。\n\ +    あなたとなら、どこまでも行けそう。」", + companion_name + ) + } + SpecialEvent::MaxTrust => { + format!( + "✨ 信頼度MAX!\n\n\ + {}:「あなたのこと、心から信頼してる。\n\ +    何でも話せるって、すごく嬉しいよ。」", + companion_name + ) + } + SpecialEvent::FirstDate => { + format!( + "💐 初デートイベント!\n\n\ + {}:「今度、二人でどこか行かない?」", + companion_name + ) + } + SpecialEvent::Confession => { + format!( + "💝 告白イベント!\n\n\ + {}:「好きです。付き合ってください。」", + companion_name + ) + } + } + } +} + +/// コンパニオンフォーマッター +pub struct CompanionFormatter; + +impl CompanionFormatter { + /// 反応を表示 + pub fn format_reaction(companion: &Companion, reaction: &CompanionReaction) -> String { + let affection_bar = Self::format_affection_bar(reaction.current_affection); + let level_up_text = if reaction.level_up { + format!("\n🎊 レベルアップ! Lv.{} → Lv.{}", reaction.new_level - 1, reaction.new_level) + } else { + String::new() + }; + + let special_event_text = if let Some(ref event) = reaction.special_event { + format!("\n\n{}", event.message(&companion.name)) + } else { + String::new() + }; + + format!( + r#" +╔══════════════════════════════════════════════════════════════╗ +║ 💕 {} の反応 ║ +╚══════════════════════════════════════════════════════════════╝ + +{} {}: +「{}」 + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +💕 好感度: {} (+{:.1}%) +💎 XP獲得: +{} XP{} +🏆 レベル: Lv.{} +🤝 信頼度: {} / 100 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{} +"#, + companion.name, + companion.personality.emoji(), + companion.name, + reaction.message, + affection_bar, + reaction.affection_gained * 100.0, + reaction.xp_gained, + level_up_text, + companion.relationship_level, + companion.trust_level, + special_event_text + ) + } + + /// プロフィール表示 + pub fn format_profile(companion: &Companion) -> String { + let affection_bar = Self::format_affection_bar(companion.affection_score); + + format!( + r#" +╔══════════════════════════════════════════════════════════════╗ +║ 💕 {} のプロフィール ║ +╚══════════════════════════════════════════════════════════════╝ + +{} 性格: {} + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +📊 ステータス +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +🏆 関係レベル: Lv.{} +💕 好感度: {} +🤝 信頼度: {} / 100 +💎 総XP: {} XP +📚 共有記憶: {}個 +🕐 最終交流: {} +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +💬 今日のひとこと: +「{}」 +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +"#, + companion.name, + companion.personality.emoji(), + companion.personality.name(), + companion.relationship_level, + affection_bar, + companion.trust_level, + companion.total_xp, + companion.shared_memories.len(), + companion.last_interaction.format("%Y-%m-%d %H:%M"), + companion.generate_daily_message() + ) + } + + fn format_affection_bar(affection: f32) -> String { + let hearts = (affection * 10.0) as usize; + let filled = "❤️".repeat(hearts); + let empty = "🤍".repeat(10 - hearts); + format!("{}{} {:.0}%", filled, empty, affection * 100.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_companion_creation() { + let companion = Companion::new( + "エミリー".to_string(), + CompanionPersonality::Energetic, + ); + assert_eq!(companion.name, "エミリー"); + assert_eq!(companion.relationship_level, 1); + assert_eq!(companion.affection_score, 0.0); + } + + #[test] + fn test_compatibility() { + let personality = CompanionPersonality::Energetic; + let innovator = DiagnosisType::Innovator; + assert_eq!(personality.compatibility(&innovator), 0.95); + } +} diff --git a/src/game_formatter.rs b/src/game_formatter.rs index 60ebdfc..86e84bf 100644 --- a/src/game_formatter.rs +++ b/src/game_formatter.rs @@ -64,6 +64,17 @@ pub enum DiagnosisType { } impl DiagnosisType { + /// スコアから診断タイプを推定(公開用) + pub fn from_memory(memory: &crate::memory::Memory) -> Self { + // スコア内訳を推定 + let emotional = (memory.priority_score * 0.25).min(0.25); + let relevance = (memory.priority_score * 0.25).min(0.25); + let novelty = (memory.priority_score * 0.25).min(0.25); + let utility = memory.priority_score - emotional - relevance - novelty; + + Self::from_score_breakdown(emotional, relevance, novelty, utility) + } + pub fn from_score_breakdown( emotional: f32, relevance: f32, diff --git a/src/lib.rs b/src/lib.rs index 6eaa46f..b39bd0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod memory; pub mod mcp; pub mod ai_interpreter; -pub mod game_formatter; \ No newline at end of file +pub mod game_formatter; +pub mod companion; \ No newline at end of file diff --git a/src/mcp/base.rs b/src/mcp/base.rs index 14621a2..243c5d9 100644 --- a/src/mcp/base.rs +++ b/src/mcp/base.rs @@ -3,16 +3,22 @@ use serde_json::{json, Value}; use std::io::{self, BufRead, Write}; use crate::memory::MemoryManager; -use crate::game_formatter::GameFormatter; +use crate::game_formatter::{GameFormatter, DiagnosisType}; +use crate::companion::{Companion, CompanionPersonality, CompanionFormatter}; +use std::sync::{Arc, Mutex}; pub struct BaseMCPServer { pub memory_manager: MemoryManager, + pub companion: Option, // 恋愛コンパニオン(オプション) } impl BaseMCPServer { pub async fn new() -> Result { let memory_manager = MemoryManager::new().await?; - Ok(BaseMCPServer { memory_manager }) + Ok(BaseMCPServer { + memory_manager, + companion: None, // 初期状態はコンパニオンなし + }) } pub async fn run(&mut self) -> Result<()> { @@ -210,6 +216,47 @@ impl BaseMCPServer { "type": "object", "properties": {} } + }), + json!({ + "name": "create_companion", + "description": "Create your AI companion - Choose name and personality!", + "inputSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Companion's name" + }, + "personality": { + "type": "string", + "enum": ["energetic", "intellectual", "practical", "dreamy", "balanced"], + "description": "Companion's personality type" + } + }, + "required": ["name", "personality"] + } + }), + json!({ + "name": "companion_react", + "description": "Show your companion's reaction to your latest memory", + "inputSchema": { + "type": "object", + "properties": { + "memory_id": { + "type": "string", + "description": "Memory ID to react to" + } + }, + "required": ["memory_id"] + } + }), + json!({ + "name": "companion_profile", + "description": "View your companion's profile and stats", + "inputSchema": { + "type": "object", + "properties": {} + } }) ] } @@ -240,6 +287,9 @@ impl BaseMCPServer { "create_memory_with_ai" => self.tool_create_memory_with_ai(arguments).await, "list_memories_by_priority" => self.tool_list_memories_by_priority(arguments), "daily_challenge" => self.tool_daily_challenge(), + "create_companion" => self.tool_create_companion(arguments), + "companion_react" => self.tool_companion_react(arguments), + "companion_profile" => self.tool_companion_profile(), "search_memories" => self.tool_search_memories(arguments), "update_memory" => self.tool_update_memory(arguments), "delete_memory" => self.tool_delete_memory(arguments), @@ -424,6 +474,80 @@ impl BaseMCPServer { }) } + // コンパニオン作成 + fn tool_create_companion(&mut self, arguments: &Value) -> Value { + let name = arguments["name"].as_str().unwrap_or("エミリー"); + let personality_str = arguments["personality"].as_str().unwrap_or("balanced"); + + let personality = match personality_str { + "energetic" => CompanionPersonality::Energetic, + "intellectual" => CompanionPersonality::Intellectual, + "practical" => CompanionPersonality::Practical, + "dreamy" => CompanionPersonality::Dreamy, + _ => CompanionPersonality::Balanced, + }; + + let companion = Companion::new(name.to_string(), personality); + let profile = CompanionFormatter::format_profile(&companion); + + self.companion = Some(companion); + + json!({ + "success": true, + "profile": profile, + "message": format!("{}があなたのコンパニオンになりました!", name) + }) + } + + // コンパニオンの反応 + fn tool_companion_react(&mut self, arguments: &Value) -> Value { + if self.companion.is_none() { + return json!({ + "success": false, + "error": "コンパニオンが作成されていません。create_companionツールで作成してください。" + }); + } + + let memory_id = arguments["memory_id"].as_str().unwrap_or(""); + + if let Some(memory) = self.memory_manager.get_memory(memory_id) { + let user_type = DiagnosisType::from_memory(memory); + let companion = self.companion.as_mut().unwrap(); + let reaction = companion.react_to_memory(memory, &user_type); + let reaction_display = CompanionFormatter::format_reaction(companion, &reaction); + + json!({ + "success": true, + "reaction_display": reaction_display, + "affection_gained": reaction.affection_gained, + "xp_gained": reaction.xp_gained, + "level_up": reaction.level_up, + "message": "コンパニオンが反応しました!" + }) + } else { + json!({ + "success": false, + "error": format!("Memory not found: {}", memory_id) + }) + } + } + + // コンパニオンプロフィール + fn tool_companion_profile(&self) -> Value { + if let Some(ref companion) = self.companion { + let profile = CompanionFormatter::format_profile(companion); + json!({ + "success": true, + "profile": profile + }) + } else { + json!({ + "success": false, + "error": "コンパニオンが作成されていません。create_companionツールで作成してください。" + }) + } + } + // 不明なメソッドハンドラ fn handle_unknown_method(&self, id: Value) -> Value { json!({