日期2026 年 5 月 4 日—— 5 月 17 日项目绘画 AI 博弈小游戏 —— 人机对抗绘画猜词与心理解读系统本周核心任务是为画风建模系统搭建完整的后端支撑使得 AI 在每一局对抗中都能更精准地理解玩家的绘画特征形成越玩越懂的学习闭环。一、数据库设计player_style_profiles 表1.1 表结构设计SQLCREATE TABLE IF NOT EXISTS player_style_profiles ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, room_id TEXT, version INTEGER DEFAULT 1, rounds_analyzed INTEGER DEFAULT 0, features_json TEXT NOT NULL, profile_text TEXT NOT NULL, model_used TEXT DEFAULT , generated_at REAL NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (room_id) REFERENCES rooms(id) );设计要点分析version 字段的意义每次刷新档案时 1形成版本链不覆盖历史记录便于追踪玩家画风演变前端可展示 v1 → v2 → v3 的画风进化曲线features_json 存储方案Python# 聚合的量化特征喂给 prompt 用 { rounds_analyzed: 5, avg_stroke_speed: 145.67, # px/ms stroke_speed_variance: 32.18, # 速度方差高方差节奏多变 avg_canvas_coverage: 0.48, # 画面占比 avg_symmetry: 0.72, # 对称性得分 avg_undo_eraser: 2.4, # 平均修改次数 avg_stroke_count: 18.5, # 平均笔画数 avg_duration_ms: 32500, # 平均绘画时长 avg_turn_density: 0.35, # 线条拐点密度 avg_color_count: 2.8, # 平均用色数 tags: [快笔型, 大胆构图, 细节丰富] }profile_text 的 LLM 生成字段存储由 DeepSeek-V3.2 生成的自然语言档案长度 100-200 字便于 AI prompt 理解包含构图偏好、线条特征、修改习惯等易于识别的描述二、数据流设计从 behaviors 到 profile2.1 完整流程图解Code┌─────────────────────────────────────────────────────────────┐ │ 玩家完成绘画 │ │ Canvas.getBehaviorData() → drawing_behaviors 表 │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 后台任务_refresh_style_profile_async() │ │ 检查局数 STYLE_PROFILE_MIN_ROUNDS (3) │ └────────────────────┬────────────────────────────────────────┘ │ YES ▼ ┌─────────────────────────────────────────────────────────────┐ │ models.get_user_behaviors(user_id, limit10) │ │ 取最近 N 局的 drawing_behaviors 原始数据 │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ style_profiler.aggregate_features(behaviors) │ │ │ │ • 计算平均值速度、覆盖率、对称性等 │ │ • 计算方差识别节奏多变特征 │ │ • 派生标签快笔型、细节丰富、一气呵成等 │ │ │ │ 输出features_dict含量化指标 派生标签 │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ style_profiler.generate_profile_text(features) │ │ │ │ • 构建 prompt包含派生标签 │ │ • 调用 DeepSeek-V3.2 文本模型 │ │ • 生成自然语言档案100-200字 │ │ │ │ 返回profile_text用于 AI prompt 注入 │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ models.save_style_profile(user_id, features, profile) │ │ │ │ • 取当前最高版本号 v_max │ │ • 插入新行version v_max 1 │ │ • 存入 features_json 和 profile_text │ │ • 记录生成时间和使用的模型 │ └────────────────────┬────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ player_style_profiles 表新增一行版本链 │ │ 下次 AI 猜词时调用 get_active_profile() 获取最新档案 │ └─────────────────────────────────────────────────────────────┘关键设计决策为什么不覆盖历史记录保留版本链便于前端展示画风演变曲线研究端可以分析单个玩家的成长轨迹数据库存储成本低只是文本字段为什么用异步后台任务Python# app.py 中的调用方式 socketio.start_background_task( _refresh_style_profile_async, drawer_id, room_id )不阻塞游戏流程回合结果立即返回避免 LLM 调用延迟影响用户体验失败自动降级用模板描述何时触发生成PythonSTYLE_PROFILE_MIN_ROUNDS 3 # 至少3局才生成 STYLE_PROFILE_BEHAVIOR_LIMIT 10 # 每次取最近10局聚合前 3 局积累基础特征第 4 局后每局都更新一次只聚合最近 10 局权重自然递减三、特征聚合算法详解3.1 从原始行为数据到派生标签在style_profiler.py中的aggregate_features()和_derive_tags()函数核心逻辑Pythondef aggregate_features(behaviors): 输入多局 drawing_behaviors 行list of dict 输出画风特征字典量化指标 派生标签 n len(behaviors) # 1. 计算基础统计量 speed_avg statistics.mean([b.get(stroke_speed_avg, 0) for b in behaviors]) speed_var statistics.stdev([b.get(stroke_speed_avg, 0) for b in behaviors]) coverage_avg statistics.mean([b.get(canvas_coverage, 0) for b in behaviors]) symmetry_avg statistics.mean([b.get(symmetry_score, 0) for b in behaviors]) # 2. 派生标签的阈值设定基于心理学研究 tags [] # 速度标签笔迹学依据Pulver, 1931 if speed_avg 200: tags.append(快笔型运笔急促) # 高速 情绪激活 elif speed_avg 50: tags.append(慢笔型运笔缓慢) # 低速 心理退缩 else: tags.append(中速运笔) # 节奏稳定性速度方差大 节奏多变 思维跳跃 if speed_var 80: tags.append(节奏多变) # 构图类型HTP 理论Buck, 1948 if coverage_avg 0.55: tags.append(大胆构图占满画面) # 占比大 外向、自信 elif coverage_avg 0.2: tags.append(保守构图大量留白) # 占比小 内向、谨慎 # 对称性偏好格式塔心理学Arnheim, 1974 if symmetry_avg 0.65: tags.append(对称偏好) # 追求秩序感 elif symmetry_avg 0.3: tags.append(自由构图) # 不拘一格、创意强 return { rounds_analyzed: n, avg_stroke_speed: round(speed_avg, 2), stroke_speed_variance: round(speed_var, 2), avg_canvas_coverage: round(coverage_avg, 2), avg_symmetry: round(symmetry_avg, 2), tags: tags, # ... 更多指标 }派生标签的心理学意义标签心理学解释阈值依据快笔型情绪激活、兴奋状态速度 200 px/ms慢笔型情绪低沉、心理退缩速度 50 px/ms节奏多变思维跳跃、注意力分散速度方差 80大胆构图自信、外向、不拘束覆盖率 55%保守构图谨慎、内向、自我保护覆盖率 20%对称偏好追求秩序感、完美主义对称性 0.65自由构图创意丰富、随性对称性 0.3一气呵成自信、果断、不修改撤销次数 1反复推敲谨慎、完美主义、压力大撤销次数 6细节丰富观察力强、耐心笔画数 25简笔风格高效、追求简洁笔画数 83.2 为什么采用多层级特征提取Code原始行为数据drawing_behaviors 表 ↓ ├─ stroke_speed_avg ├─ canvas_coverage ├─ turn_point_density └─ undo_count ... (15 个字段) ▼ 中间层量化指标聚合 ├─ avg_stroke_speed10局均值 ├─ stroke_speed_variance速度方差 ├─ avg_canvas_coverage └─ ... ▼ 高层派生标签转化为人话 ├─ 快笔型 ├─ 大胆构图 ├─ 节奏多变 └─ ... ▼ 最终LLM prompt 注入 该玩家快笔型、大胆构图、节奏多变... 平均每局用时32秒、笔触18笔、覆盖率48%... AI应注意其习惯性起笔位置和色彩搭配分层好处量化指标用于统计分析后端研究派生标签便于自然语言理解LLM 理解原始数据保留支持未来算法升级四、API 设计与实现4.1 获取画风档案 APIPythonapp.route(/api/style_profile/user_id) def api_get_style_profile(user_id): 获取用户当前最新的画风档案 历史版本列表 latest models.get_latest_style_profile(user_id) if not latest: return jsonify({ has_profile: False, message: 暂无画风档案至少完成 3 局对战才会生成。, history: [], }) # 最新版档案 history models.get_style_profile_history(user_id, limit10) return jsonify({ has_profile: True, latest: { profile_id: latest[id], version: latest[version], rounds_analyzed: latest[rounds_analyzed], profile_text: latest[profile_text], features: latest.get(features, {}), # 量化指标供前端画图 model_used: latest[model_used], # 标识 LLM 版本 generated_at: latest[generated_at], }, history: history, # 用于展示版本演变 })返回数据示例JSON{ has_profile: true, latest: { profile_id: abc12345, version: 3, rounds_analyzed: 8, profile_text: 该玩家是典型的快笔型、大胆构图者。笔触速度均值145px/ms..., features: { avg_stroke_speed: 145.67, avg_canvas_coverage: 0.52, avg_symmetry: 0.38, tags: [快笔型, 大胆构图, 自由构图] }, model_used: deepseek-chat, generated_at: 1718625600 }, history: [ {version: 3, rounds_analyzed: 8, generated_at: 1718625600}, {version: 2, rounds_analyzed: 5, generated_at: 1718539200}, {version: 1, rounds_analyzed: 3, generated_at: 1718452800} ] }4.2 内部接口AI 识别调用Python# 在 ai_recognizer.py 中 def recognize_drawing(image_base64, difficulty, category, word_listNone, user_idNone): 调用 AI 猜词可选注入玩家画风档案 # 关键取玩家的最新画风档案 style_profile if user_id: try: from utils import style_profiler style_profile style_profiler.get_active_profile(user_id) # 若有档案会返回 100-200 字的描述否则返回空串 except Exception as e: print(f[AI] 画风档案加载失败: {e}) # 构建 prompt难度相关 prompt _build_prompt(difficulty, category, word_list, style_profilestyle_profile) # 调用多模态 AI使用档案增强识别精度 result _call_zhipu(image_base64, prompt) return resultprompt 中的档案注入示例Python# 当 style_profile 有值时 prompt ( f\n\n【该玩家的画风档案基于历史绘画行为】\n f{style_profile}\n 请结合该玩家的画风习惯进行识别但最终判断仍以画面内容为准。 )4.3 后台任务档案刷新Python# app.py 中的回合结束处理 def _do_end_guessing(room_id): 回合结束异步刷新画家的画风档案 # ... 游戏逻辑 ... # 触发档案刷新不阻塞主流程 try: drawer_id round_info.get(drawer_id) if drawer_id: socketio.start_background_task( _refresh_style_profile_async, drawer_id, room_id ) except Exception as e: print(f[StyleProfiler] 触发失败: {e}) def _refresh_style_profile_async(user_id, room_id): 后台异步任务刷新画风档案 try: from utils import style_profiler min_rounds config.STYLE_PROFILE_MIN_ROUNDS # 3 limit config.STYLE_PROFILE_BEHAVIOR_LIMIT # 10 # 取该玩家最近 10 局的行为数据 behaviors models.get_user_behaviors(user_id, limitlimit) if len(behaviors) min_rounds: print(f[StyleProfiler] 玩家 {user_id} 仅 {len(behaviors)} 局暂不生成) return # 转 dictsqlite Row 需要转换 behavior_dicts [dict(b) if not isinstance(b, dict) else b for b in behaviors] user models.get_user(user_id) nickname user.get(nickname, 该玩家) # 核心构建并保存档案 result style_profiler.build_and_save_profile( user_iduser_id, behaviorsbehavior_dicts, nicknamenickname, room_idroom_id, save_to_dbTrue, ) print(f[StyleProfiler] 玩家 {user_id} 档案已刷新: frounds{result[rounds_analyzed]}, fmodel{result[model_used]}) except Exception as e: print(f[StyleProfiler] 后台刷新异常: {e}) traceback.print_exc()五、DeepSeek-V3.2 后端接入初始化代码style_profiler.pyPythondef _call_deepseek(features, nickname): 调用 DeepSeek-V3.2 API 生成画风档案 流程 1. 初始化客户端使用 API Key 2. 构建 system/user 消息 3. 调用 chat.completions.create() 4. 解析响应并返回 try: from openai import OpenAI except ImportError: print([StyleProfiler] openai SDK 未安装请 pip install openai) return _template_fallback(features, nickname) # ★ 关键初始化 DeepSeek 客户端 # DeepSeek 提供 OpenAI 兼容接口URL 和 key 配置即可 client OpenAI( api_keyconfig.DEEPSEEK_API_KEY, base_urlgetattr(config, DEEPSEEK_BASE_URL, https://api.deepseek.com/v1), ) # ★ 构建 prompt prompt _build_profile_prompt(features, nickname) # ★ 调用 API resp client.chat.completions.create( modelgetattr(config, DEEPSEEK_MODEL, deepseek-chat), messages[ { role: system, content: ( 你是一个细致的绘画行为分析师。 根据玩家的绘画行为数据写一段精炼、客观、便于AI识别使用的画风描述。 不要出现可能、也许这类模糊词给出明确的判断。 ) }, { role: user, content: prompt } ], temperature0.4, # ★ 控制创意度0.4 较稳定避免过度创意 max_tokens400, # ★ 限制输出长度100-200 字节省 token top_p0.9, # ★ nucleus sampling增加多样性 ) # ★ 提取结果 text resp.choices[0].message.content.strip() print(f[StyleProfiler] DeepSeek 生成档案 {len(text)} 字) print(f[StyleProfiler] 本次调用耗费 token: finput{resp.usage.prompt_tokens}, foutput{resp.usage.completion_tokens}) return text5.1 Prompt 工程档案生成策略Pythondef _build_profile_prompt(features, nickname): 构建用于 DeepSeek 的 profile 生成 prompt tags_str 、.join(features.get(tags, [])) or 暂无明显倾向 return ( f玩家昵称{nickname}\n f已分析局数{features[rounds_analyzed]}\n\n 【量化指标多局平均】\n f- 平均笔速{features[avg_stroke_speed]} px/s\n f (方差 {features[stroke_speed_variance]}) # 方差大节奏不稳定\n f- 平均画面覆盖率{features[avg_canvas_coverage]}\n f- 平均对称性得分{features[avg_symmetry]}\n f- 平均撤销橡皮擦次数{features[avg_undo_eraser]}\n f- 平均笔画数{features[avg_stroke_count]}\n f- 平均绘画时长{features[avg_duration_ms]} ms\n f- 平均拐点密度{features[avg_turn_density]}\n f- 平均用色数量{features[avg_color_count]}\n\n f【派生标签】{tags_str}\n\n 请输出一段100-180字的画风档案格式为\n 1. 总体画风定位一句话\n 2. 关键识别特征2-3条便于AI在猜词时参考\n 3. 推测画法偏好例如喜欢先画轮廓再填细节或反之\n 不要使用Markdown标题直接写连贯的中文段落。 )生成的档案示例Code该玩家是典型的快笔型、大胆构图者运笔速度平均145px/s 显示较强的情绪激活和行动力。画面覆盖率达52%明显倾向占满整个 画布反映出自信和不拘束的性格。虽然对称性评分仅0.38但这正 体现了其自由、创意驱动的绘画风格——不遵循规则更注重表达力。 识别建议该玩家通常先画大轮廓再快速补充细节线条曲折度高 拐点密度0.42说明思维跳跃。建议AI优先识别其起笔特征和色彩 组合偏好而非精确轮廓。5.2 容错机制与降级Pythondef generate_profile_text(features, nickname该玩家): 调用 LLM 生成档案失败时自动降级 try: if (getattr(config, DEEPSEEK_API_KEY, ) and getattr(config, STYLE_MODEL_PROVIDER, ) deepseek): # ★ 方案一LLM 调用 return _call_deepseek(features, nickname) else: # ★ 方案二模板拼接 return _template_fallback(features, nickname) except Exception as e: print(f[StyleProfiler] LLM 调用失败: {e}) # ★ 方案三最终降级完全失败 return _template_fallback(features, nickname) def _template_fallback(features, nickname): 无 API Key 或 LLM 失败时的模板拼接方案 保证系统可用性 tags features.get(tags, []) if not tags: return f{nickname}尚未积累足够画风数据暂以默认策略识别。 lead 、.join(tags[:3]) detail 、.join(tags[3:]) if len(tags) 3 else parts [ f{nickname}的画风总体呈现「{lead}」的特征。, ] if detail: parts.append(f此外还表现出{detail}的倾向。) parts.append( f平均每局画 {features[avg_stroke_count]:.0f} 笔、 f用时 {features[avg_duration_ms]/1000:.1f} 秒、 f画面覆盖率约 {features[avg_canvas_coverage]:.0%}。 ) return .join(parts)三层容错设计Code┌─────────────────────────────────┐ │ 尝试方案一调用 DeepSeek-V3.2 │ │ 成功 ✅ → 返回高质量档案 │ └────────────┬────────────────────┘ │ 失败API Error, Timeout ▼ ┌─────────────────────────────────┐ │ 尝试方案二模板拼接 │ │ (无网络也能用) │ │ 成功 ✅ → 返回可用档案 │ └────────────┬────────────────────┘ │ 不适用无派生标签 ▼ ┌─────────────────────────────────┐ │ 方案三最小化降级 │ │ 返回通用提示 基础指标 │ │ ✅ 系统保证可用 │ └─────────────────────────────────┘六、性能优化与查询设计6.1 数据库查询优化Pythondef get_user_behaviors(user_id, limit50): 获取用户历史行为数据带索引优化 with get_db_connection() as conn: # ★ 利用 INDEX 加速查询 rows conn.execute( SELECT * FROM drawing_behaviors WHERE user_id ? ORDER BY created_at DESC LIMIT ?, (user_id, limit) ).fetchall() return [dict(r) for r in rows] def get_latest_style_profile(user_id): 获取最新档案单行查询O(1) with get_db_connection() as conn: row conn.execute( SELECT * FROM player_style_profiles WHERE user_id ? ORDER BY version DESC LIMIT 1, (user_id,) ).fetchone() if not row: return None result dict(row) # ★ 解析 JSON 字段 try: result[features] json.loads(result[features_json]) except (TypeError, json.JSONDecodeError): result[features] {} return result数据库索引设计SQL-- 既有索引从 models.py 初始化 CREATE INDEX idx_drawing_behaviors_round ON drawing_behaviors(round_id); -- 新增索引针对画风系统 CREATE INDEX idx_drawing_behaviors_user_time ON drawing_behaviors( user_id, created_at DESC ); -- 加速查询取该玩家最近 N 局 CREATE INDEX idx_style_profiles_user_version ON player_style_profiles( user_id, version DESC ); -- 加速查询该玩家最新版档案6.2 聚合性能分析Pythondef aggregate_features(behaviors): 性能分析N 局数据聚合耗时 N10 局时 - 遍历求平均值O(N*M) O(10*15) ≈ 150 ops毫秒级 - 计算方差O(N) ≈ 10 ops毫秒级 - 派生标签O(1)比对阈值 - 总耗时 5ms可忽略 # 1. 计算统计量Python 的 statistics 模块优化过 speeds [b.get(stroke_speed_avg, 0) for b in behaviors] avg statistics.mean(speeds) # O(N) var statistics.stdev(speeds) # O(N) # 2. 派生标签条件判断O(1) tags [] if avg 200: # 阈值比对 tags.append(快笔型) return {...}性能保证单次聚合 10msLLM 调用 (1-3s) 在后台异步运行不影响游戏主流程的响应时间七、AI辅助开发记录Prompt1我需要设计一个表来存储玩家的画风档案要求1. 支持版本控制追踪画风演变每次更新1版本号2. 存储量化特征JSON格式包含笔速、覆盖率、对称性等15指标3. 存储AI生成的自然语言档案100-200字的文本描述4. 记录生成时间和使用的模型信息请给出- 完整的 SQL CREATE TABLE 语句SQLite- 每个字段的设计理由- 建议的索引策略- 字段大小和存储成本估算Prompt2我需要在 models.py 中实现以下函数用于存取 player_style_profiles 表1. save_style_profile(user_id, features_dict, profile_text, room_id,rounds_analyzed, model_used)- 保存一份新的画风档案- 自动计算版本号取当前最高版本1- 不覆盖历史版本形成版本链- 返回 profile_id2. get_latest_style_profile(user_id)- 获取该用户的最新档案最高版本号- 返回包含解析后 features 字典的完整记录3. get_style_profile_history(user_id, limit10)- 获取该用户的历史版本列表新到旧- 用于前端展示画风演变请生成这三个函数的完整实现包括- SQL 语句- 错误处理- 参数校验- 返回格式统一