AiSenario / game_system.py
Song
claud
4831a5d
"""
遊戲化系統模組
包含成就、等級、積分系統
"""
import json
import time
import threading
import hashlib
from datetime import datetime
from typing import Dict, List, Optional, Tuple
from enum import Enum
# ============================================================================
# 每日挑戰定義
# ============================================================================
DAILY_CHALLENGES = [
{
"id": "low_risk_conversations",
"name": "低風險對話",
"description": "完成 {count} 次風險分數 < {threshold} 的對話",
"params": {"count": 3, "threshold": 30},
"reward": {"points": 100, "badge": "🛡️ 風險控制者"}
},
{
"id": "different_personas",
"name": "人格探索",
"description": "嘗試 {count} 種不同人格",
"params": {"count": 2},
"reward": {"points": 75, "badge": "👥 人格大師"}
},
{
"id": "low_risk_streak",
"name": "連續低風險",
"description": "達成 {count} 次連續低風險回應",
"params": {"count": 5},
"reward": {"points": 150, "badge": "🔥 連擊王"}
},
{
"id": "quick_responses",
"name": "快速回應",
"description": "完成 {count} 次在 {time} 秒內的對話",
"params": {"count": 2, "time": 30},
"reward": {"points": 100, "badge": "⚡ 閃電俠"}
},
{
"id": "scenario_explorer",
"name": "情境探索者",
"description": "嘗試 {count} 種不同情境",
"params": {"count": 3},
"reward": {"points": 75, "badge": "🗺️ 探索家"}
},
{
"id": "high_score_achiever",
"name": "高分達人",
"description": "獲得 {count} 次得分 > {score} 的對話",
"params": {"count": 2, "score": 80},
"reward": {"points": 125, "badge": "🏆 分數王"}
}
]
# ============================================================================
# 成就定義
# ============================================================================
ACHIEVEMENTS = {
# 首次成就
"first_success": {
"name": "🎯 首次成功",
"description": "完成第一次對話",
"points": 50,
"icon": "🎯",
"condition": lambda stats: stats["total_conversations"] >= 1,
"hidden": False
},
# 連續成就
"streak_3": {
"name": "🌟 連續3次低風險",
"description": "連續3次風險分數 < 30",
"points": 100,
"icon": "🌟",
"condition": lambda stats: stats.get("current_streak", 0) >= 3,
"hidden": False
},
"streak_5": {
"name": "🛡️ 防禦專家",
"description": "連續5次風險 < 20",
"points": 250,
"icon": "🛡️",
"condition": lambda stats: stats.get("current_streak", 0) >= 5,
"hidden": False
},
# 完美應對
"perfect_response": {
"name": "🏆 完美應對",
"description": "單次風險分數 < 10",
"points": 150,
"icon": "🏆",
"condition": lambda stats: stats.get("best_risk_score", 100) < 10,
"hidden": False
},
# 情境探索
"scenario_explorer": {
"name": "📚 情境探索者",
"description": "完成5種不同情境",
"points": 200,
"icon": "📚",
"condition": lambda stats: len(stats.get("completed_scenarios", [])) >= 5,
"hidden": False
},
"persona_master": {
"name": "👤 人格大師",
"description": "完成所有7種人格",
"points": 300,
"icon": "👤",
"condition": lambda stats: len(stats.get("completed_personas", [])) >= 7,
"hidden": False
},
"full_clear": {
"name": "🗺️ 全境通關",
"description": "完成所有49種組合",
"points": 500,
"icon": "🗺️",
"condition": lambda stats: stats.get("total_combinations", 0) >= 49,
"hidden": False
},
# 快速應對
"quick_response": {
"name": "⚡ 快速應對",
"description": "在30秒內完成對話",
"points": 100,
"icon": "⚡",
"condition": lambda stats: stats.get("fastest_time", 999) < 30,
"hidden": False
},
# 大師成就
"communication_master": {
"name": "🎖️ 溝通大師",
"description": "總分數 > 5000",
"points": 1000,
"icon": "🎖️",
"condition": lambda stats: stats.get("total_score", 0) > 5000,
"hidden": False
},
# 多樣化
"diversity": {
"name": "🌈 多樣化",
"description": "完成10種以上組合",
"points": 150,
"icon": "🌈",
"condition": lambda stats: stats.get("total_combinations", 0) >= 10,
"hidden": False
},
# 連續高分
"high_score_streak": {
"name": "🔥 高分連發",
"description": "連續3次得分 > 80",
"points": 200,
"icon": "🔥",
"condition": lambda stats: stats.get("high_score_streak", 0) >= 3,
"hidden": False
},
# 風險控制大師
"risk_master": {
"name": "🎯 風險控制大師",
"description": "平均風險分數 < 20",
"points": 300,
"icon": "🎯",
"condition": lambda stats: stats.get("average_risk", 100) < 20,
"hidden": False
},
# 情境全通
"scenario_master": {
"name": "📋 情境全通",
"description": "完成所有7種情境",
"points": 250,
"icon": "📋",
"condition": lambda stats: len(stats.get("completed_scenarios", [])) >= 7,
"hidden": False
},
# 挑戰模式
"challenge_complete": {
"name": "⚔️ 挑戰者",
"description": "完成一次挑戰模式",
"points": 100,
"icon": "⚔️",
"condition": lambda stats: stats.get("challenge_completed", 0) >= 1,
"hidden": False
},
# 每日挑戰
"daily_challenge": {
"name": "📅 每日挑戰",
"description": "完成今日挑戰",
"points": 75,
"icon": "📅",
"condition": lambda stats: stats.get("daily_challenge_completed", False),
"hidden": False
},
}
# ============================================================================
# 等級定義
# ============================================================================
LEVELS = {
1: {"name": "新手教師", "min_score": 0, "color": "#3B82F6", "icon": "🌱"},
2: {"name": "初級教師", "min_score": 500, "color": "#10B981", "icon": "🌿"},
3: {"name": "中級教師", "min_score": 1500, "color": "#F59E0B", "icon": "🌼"},
4: {"name": "資深教師", "min_score": 3000, "color": "#F97316", "icon": "🌳"},
5: {"name": "專家教師", "min_score": 5000, "color": "#8B5CF6", "icon": "🌟"},
6: {"name": "溝通大師", "min_score": 8000, "color": "#EF4444", "icon": "👑"},
7: {"name": "傳奇大師", "min_score": 12000, "color": "#FFD700", "icon": "🏆"},
}
# ============================================================================
# 情境難度係數
# ============================================================================
SCENARIO_DIFFICULTY = {
"📉 成績退步質疑": 1.0,
"📚 作業量過多抱怨": 1.2,
"😢 孩子情緒問題歸咎老師": 1.5,
"😞 孩子被同學霸凌卻未即時處理": 2.0,
"✏️ 孩子不寫作業、家長被指責教養問題": 1.5,
"🧩 特殊生輔導方式爭議(例如ADHD)": 2.0,
"💻 線上學習或遠距教學設備問題投訴": 1.2,
}
# ============================================================================
# 人格複雜度係數
# ============================================================================
PERSONA_COMPLEXITY = {
"😰 焦慮型家長": 1.2,
"🤔 質疑型家長": 1.5,
"😠 高衝突型家長": 2.0,
"🛡️ 過度保護型家長": 2.0,
"⚖️ 理性協商型家長": 1.0,
"😴 疲憊工作型家長": 1.2,
"🌏 文化差異型家長": 1.5,
}
# ============================================================================
# 遊戲狀態管理
# ============================================================================
class GameState:
def __init__(self, storage_path: str = "player_data.json"):
self.storage_path = storage_path
self.data = self.load_data()
self.session_start_time = time.time()
self.last_conversation_time = None
self.current_streak = 0
self.high_score_streak = 0
self.current_challenge_mode = None
self.current_persona = None
self.current_scenario = None
self.current_round = 0
self.challenge_start_time = None
def load_data(self) -> Dict:
"""載入玩家數據"""
try:
with open(self.storage_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return self.get_default_data()
def save_data(self):
"""儲存玩家數據"""
try:
with open(self.storage_path, 'w', encoding='utf-8') as f:
json.dump(self.data, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"儲存數據失敗: {e}")
def get_default_data(self) -> Dict:
"""取得預設數據"""
return {
"total_score": 0,
"level": 1,
"achievements": [],
"statistics": {
"total_conversations": 0,
"average_risk": 0,
"best_risk_score": 100,
"best_score": 0,
"completed_scenarios": [],
"completed_personas": [],
"total_combinations": 0,
"fastest_time": 999,
"challenge_completed": 0,
"daily_challenge_completed": False,
"last_daily_challenge_date": None,
"current_daily_challenge": None,
"daily_challenge_progress": 0,
"daily_challenge_badges": [],
},
"last_updated": datetime.now().isoformat()
}
def calculate_score(self, risk_score: int, persona: str, scenario: str,
conversation_time: float) -> int:
"""計算本次對話得分"""
# 基礎分數
base_score = 100 - risk_score
# 連續加成
streak_bonus = self.current_streak * 10
# 情境加成
scenario_bonus = int(SCENARIO_DIFFICULTY.get(scenario, 1.0) * 20)
# 人格加成
persona_bonus = int(PERSONA_COMPLEXITY.get(persona, 1.0) * 15)
# 時間加成(快速完成)
time_bonus = 0
if conversation_time < 30:
time_bonus = 20
total_score = base_score + streak_bonus + scenario_bonus + persona_bonus + time_bonus
# 確保分數不為負
return max(0, total_score)
def update_stats(self, risk_score: int, score: int, scenario: str,
persona: str, conversation_time: float):
"""更新統計數據"""
stats = self.data["statistics"]
# 更新總對話次數
stats["total_conversations"] += 1
# 更新平均風險
total_risk = stats["average_risk"] * (stats["total_conversations"] - 1) + risk_score
stats["average_risk"] = total_risk / stats["total_conversations"]
# 更新最佳風險
if risk_score < stats["best_risk_score"]:
stats["best_risk_score"] = risk_score
# 更新最佳分數
if score > stats["best_score"]:
stats["best_score"] = score
# 更新完成情境
if scenario not in stats["completed_scenarios"]:
stats["completed_scenarios"].append(scenario)
# 更新完成人格
if persona not in stats["completed_personas"]:
stats["completed_personas"].append(persona)
# 更新總組合數
stats["total_combinations"] = len(stats["completed_scenarios"]) * len(stats["completed_personas"])
# 更新最快時間
if conversation_time < stats["fastest_time"]:
stats["fastest_time"] = conversation_time
# 更新總分數
self.data["total_score"] += score
# 更新連續 streak
if risk_score < 30:
self.current_streak += 1
else:
self.current_streak = 0
# 更新高分 streak
if score > 80:
self.high_score_streak += 1
else:
self.high_score_streak = 0
# 更新每日挑戰
self.check_daily_challenge()
self.update_daily_challenge_progress(risk_score, score, scenario, persona, conversation_time)
# 更新等級
self.update_level()
# 更新最後時間
self.data["last_updated"] = datetime.now().isoformat()
# 儲存數據
self.save_data()
def update_level(self):
"""更新等級"""
total_score = self.data["total_score"]
for level_num, level_data in LEVELS.items():
if total_score >= level_data["min_score"]:
self.data["level"] = level_num
def generate_daily_challenge(self, date_str: str) -> Dict:
"""根據日期生成每日挑戰"""
# 使用日期的 hash 來選擇挑戰,確保同一天總是相同挑戰
hash_obj = hashlib.md5(date_str.encode())
hash_int = int(hash_obj.hexdigest(), 16)
challenge_index = hash_int % len(DAILY_CHALLENGES)
challenge = DAILY_CHALLENGES[challenge_index].copy()
# 格式化描述
description = challenge["description"].format(**challenge["params"])
challenge["description"] = description
return challenge
def check_daily_challenge(self):
"""檢查每日挑戰"""
today = datetime.now().date().isoformat()
stats = self.data["statistics"]
if stats["last_daily_challenge_date"] != today:
# 重置挑戰
stats["daily_challenge_completed"] = False
stats["last_daily_challenge_date"] = today
stats["daily_challenge_progress"] = 0
# 生成新挑戰
challenge = self.generate_daily_challenge(today)
stats["current_daily_challenge"] = challenge
def update_daily_challenge_progress(self, risk_score: int, score: int, scenario: str, persona: str, conversation_time: float):
"""更新每日挑戰進度"""
stats = self.data["statistics"]
if not stats.get("current_daily_challenge") or stats.get("daily_challenge_completed"):
return
challenge = stats["current_daily_challenge"]
challenge_id = challenge["id"]
params = challenge["params"]
if challenge_id == "low_risk_conversations":
if risk_score < params["threshold"]:
stats["daily_challenge_progress"] += 1
elif challenge_id == "different_personas":
if persona not in stats.get("daily_personas_used", []):
stats.setdefault("daily_personas_used", []).append(persona)
stats["daily_challenge_progress"] = len(stats["daily_personas_used"])
elif challenge_id == "low_risk_streak":
if self.current_streak >= params["count"]:
stats["daily_challenge_progress"] = params["count"]
elif challenge_id == "quick_responses":
if conversation_time < params["time"]:
stats["daily_challenge_progress"] += 1
elif challenge_id == "scenario_explorer":
if scenario not in stats.get("daily_scenarios_used", []):
stats.setdefault("daily_scenarios_used", []).append(scenario)
stats["daily_challenge_progress"] = len(stats["daily_scenarios_used"])
elif challenge_id == "high_score_achiever":
if score > params["score"]:
stats["daily_challenge_progress"] += 1
# 檢查是否完成
if stats["daily_challenge_progress"] >= params["count"]:
self.complete_daily_challenge()
def complete_daily_challenge(self):
"""完成每日挑戰"""
stats = self.data["statistics"]
if stats.get("daily_challenge_completed"):
return
stats["daily_challenge_completed"] = True
challenge = stats["current_daily_challenge"]
reward = challenge["reward"]
# 獎勵積分
self.data["total_score"] += reward["points"]
# 獎勵徽章
if reward["badge"] not in stats.get("daily_challenge_badges", []):
stats.setdefault("daily_challenge_badges", []).append(reward["badge"])
self.save_data()
def check_achievements(self) -> List[Dict]:
"""檢查並返回新解鎖的成就"""
new_achievements = []
stats = self.data["statistics"]
# 添加 session-specific stats
session_stats = {
"current_streak": self.current_streak,
"high_score_streak": self.high_score_streak,
"total_score": self.data["total_score"],
}
all_stats = {**stats, **session_stats}
for achievement_id, achievement in ACHIEVEMENTS.items():
if achievement_id not in self.data["achievements"]:
try:
if achievement["condition"](all_stats):
self.data["achievements"].append(achievement_id)
new_achievements.append({
"id": achievement_id,
"name": achievement["name"],
"points": achievement["points"],
"icon": achievement["icon"]
})
except Exception as e:
print(f"檢查成就 {achievement_id} 時發生錯誤: {e}")
if new_achievements:
self.save_data()
return new_achievements
def get_level_info(self) -> Dict:
"""取得當前等級資訊"""
level_num = self.data["level"]
level_data = LEVELS[level_num]
# 計算距離下一等級的進度
if level_num < len(LEVELS):
next_level = LEVELS[level_num + 1]
current_score = self.data["total_score"]
min_score = level_data["min_score"]
next_min_score = next_level["min_score"]
progress = (current_score - min_score) / (next_min_score - min_score) * 100
progress = min(100, max(0, progress))
else:
progress = 100
return {
"level": level_num,
"name": level_data["name"],
"color": level_data["color"],
"icon": level_data["icon"],
"score": self.data["total_score"],
"progress": progress,
"next_level": level_num + 1 if level_num < len(LEVELS) else None
}
def get_achievements_summary(self) -> Dict:
"""取得成就摘要"""
unlocked = len(self.data["achievements"])
total = len(ACHIEVEMENTS)
return {
"unlocked": unlocked,
"total": total,
"progress": (unlocked / total) * 100 if total > 0 else 0,
"recent": self.data["achievements"][-5:] if len(self.data["achievements"]) > 0 else []
}
def get_daily_challenge_info(self) -> Dict:
"""取得每日挑戰資訊"""
stats = self.data["statistics"]
challenge = stats.get("current_daily_challenge")
if not challenge:
return {"active": False}
params = challenge["params"]
progress = stats.get("daily_challenge_progress", 0)
completed = stats.get("daily_challenge_completed", False)
return {
"active": True,
"name": challenge["name"],
"description": challenge["description"],
"progress": progress,
"target": params["count"],
"completed": completed,
"reward": challenge["reward"]
}
def get_statistics_summary(self) -> Dict:
"""取得統計摘要"""
stats = self.data["statistics"]
return {
"total_conversations": stats["total_conversations"],
"average_risk": round(stats["average_risk"], 1),
"best_risk_score": stats["best_risk_score"],
"best_score": stats["best_score"],
"completed_scenarios": len(stats["completed_scenarios"]),
"completed_personas": len(stats["completed_personas"]),
"total_combinations": stats["total_combinations"],
"fastest_time": stats["fastest_time"] if stats["fastest_time"] < 999 else None,
}
def get_adaptive_selection(self) -> Dict[str, str]:
"""根據玩家表現自適應選擇情境和人格"""
import random
stats = self.data["statistics"]
avg_risk = stats["average_risk"]
completed_scenarios = stats["completed_scenarios"]
completed_personas = stats["completed_personas"]
# 基礎權重
scenario_weights = {s: 1.0 for s in SCENARIO_DIFFICULTY}
persona_weights = {p: 1.0 for p in PERSONA_COMPLEXITY}
# 根據表現調整因子
performance_factor = 1.0
if avg_risk < 20:
performance_factor = 1.5 # 表現好,增加難度
elif avg_risk > 50:
performance_factor = 0.7 # 表現差,降低難度
# 應用權重調整
for scenario, difficulty in SCENARIO_DIFFICULTY.items():
if scenario in completed_scenarios:
scenario_weights[scenario] *= 0.5 # 偏好新情境
scenario_weights[scenario] *= (difficulty ** performance_factor)
for persona, complexity in PERSONA_COMPLEXITY.items():
if persona in completed_personas:
persona_weights[persona] *= 0.5 # 偏好新人格
persona_weights[persona] *= (complexity ** performance_factor)
# 正規化為機率
total_scenario = sum(scenario_weights.values())
scenario_probs = {s: w / total_scenario for s, w in scenario_weights.items()}
total_persona = sum(persona_weights.values())
persona_probs = {p: w / total_persona for p, w in persona_weights.items()}
# 隨機選擇
selected_scenario = random.choices(
list(scenario_probs.keys()),
weights=list(scenario_probs.values())
)[0]
selected_persona = random.choices(
list(persona_probs.keys()),
weights=list(persona_probs.values())
)[0]
return {"scenario": selected_scenario, "persona": selected_persona}
def reset_session(self):
"""重置 session 狀態"""
self.current_streak = 0
self.high_score_streak = 0
self.last_conversation_time = None
# 重置每日挑戰追蹤
stats = self.data["statistics"]
stats.pop("daily_personas_used", None)
stats.pop("daily_scenarios_used", None)
def check_challenge_complete(self) -> bool:
"""檢查挑戰是否完成"""
if not self.current_challenge_mode:
return False
if self.current_challenge_mode == "時間挑戰 (30秒)":
if self.challenge_start_time:
elapsed = time.time() - self.challenge_start_time
return elapsed >= 30
return False
elif self.current_challenge_mode == "連續挑戰 (3回合)":
return self.current_round >= 3
else:
return False
def add_conversation(self, score: int, risk_score: int, persona: str, scenario: str, time_taken: float):
"""添加對話並更新統計"""
self.update_stats(risk_score, score, scenario, persona, time_taken)
if self.current_challenge_mode == "連續挑戰 (3回合)":
self.current_round += 1
# ============================================================================
# 遊戲狀態實例
# ============================================================================
game_state = GameState()