打造AI智能”成语接龙“游戏
本文介绍了一个基于Flask+CozeAI开发的智能成语接龙游戏系统。系统采用三层架构设计:后端服务层处理游戏逻辑和API接口,AI交互层负责成语生成与验证,前端交互层优化用户体验。关键技术包括AI异步响应处理、跨域请求解决方案、敏感配置管理和多层容错机制。系统实现了成语接龙核心功能,并支持游戏状态管理、历史记录展示等功能。文章还探讨了性能优化、功能扩展方向,总结了轻量化AI集成和容错设计的重要性
目录
(1)初始成语生成(generate_initial_idiom)
成语接龙作为中国传统文化的经典游戏,既考验词汇量,又锻炼思维敏捷度。当传统游戏遇上 AI 技术,会碰撞出怎样的火花?本文将详细拆解一个基于 Flask+Coze AI 打造的智能成语接龙游戏的实现过程,从技术架构、核心逻辑到用户体验优化,带你深入了解如何将 AI 能力落地到实际应用中。
一、项目背景与技术选型
1. 需求分析
我们希望打造一个兼具趣味性和智能化的成语接龙游戏,核心需求包括:
- 自动生成初始接龙成语,支持游戏重置
- 验证用户输入的成语有效性,实现 AI 自动接龙
- 记录游戏历史,提供友好的交互界面
- 具备容错机制,在 AI 服务异常时保证游戏可用
2. 技术栈选择
| 技术 / 工具 | 用途 | 选型理由 |
|---|---|---|
| Flask | 后端 Web 框架 | 轻量级、易上手,适合快速开发小型 API 服务 |
| Coze AI | 智能成语生成 | 字节跳动旗下 AI 平台,支持多轮对话,响应速度快,中文处理能力优秀 |
| Flask-CORS | 跨域支持 | 解决前端页面与后端 API 的跨域请求问题 |
| HTML/CSS/JS | 前端界面 | 原生技术栈,无需额外依赖,适配性强 |
| dotenv | 环境变量管理 | 安全管理敏感配置(如 API Token),便于环境切换 |
二、系统架构与核心模块设计
整个系统分为后端服务层、AI 交互层和前端交互层三个核心部分,架构如下:
前端页面(HTML/JS) → 后端API(Flask) → Coze AI SDK → 成语生成/验证
↑ ↓ ↓
游戏状态展示 ← 游戏逻辑处理 ← AI响应解析与容错 ← 成语结果返回
1. 后端核心模块:游戏逻辑类(IdiomGame)
游戏的核心逻辑封装在IdiomGame类中,主要承担三个核心职责:
(1)初始成语生成(generate_initial_idiom)
- AI 调用流程:构造用户指令消息,调用 Coze Chat API 生成四字成语
- 容错机制:设置 30 秒超时时间,若 AI 响应超时 / 返回无效结果,自动切换到默认成语列表
- 数据清洗:过滤非中文字符,确保返回结果为标准四字成语
def generate_initial_idiom(self):
try:
# 构造AI请求消息
messages = [Message(role="user", content="生成一个四字成语作为开头,仅返回成语本身")]
chat = self.coze.chat.create(bot_id=self.bot_id, user_id=self.user_id, additional_messages=messages)
# 等待AI响应(超时控制)
timeout = 0
while chat.status == ChatStatus.IN_PROGRESS and timeout < 30:
chat = self.coze.chat.retrieve(conversation_id=chat.conversation_id, chat_id=chat.id)
timeout += 1
time.sleep(1)
# 解析并清洗AI响应
initial_idiom = msg.content.strip()
initial_idiom = "".join(filter(lambda x: '\u4e00' <= x <= '\u9fff', initial_idiom))
if initial_idiom and len(initial_idiom) == 4:
return initial_idiom
raise Exception("AI生成的初始成语无效")
except Exception as e:
# 降级策略:使用默认成语列表
return random.choice(COMMON_IDIOMS)
(2)游戏状态管理
reset_game:重置游戏,生成新初始成语并清空历史记录add_to_history:记录用户与 AI 的接龙记录,限制最多保存 20 条get_sdk_response:处理用户输入,调用 AI 完成接龙,返回标准化响应
(3)API 接口封装
后端暴露三个核心 API 接口,实现前后端交互:
| 接口路径 | 请求方法 | 功能 |
|---|---|---|
| /api/init | GET | 初始化游戏,返回当前成语和历史记录 |
| /api/play | POST | 提交用户成语,返回 AI 接龙结果 |
| /api/restart | POST | 重置游戏,生成新初始成语 |
2. AI 交互层:容错与响应处理
AI 交互是整个系统的关键环节,我们做了多层容错设计:
- 超时保护:所有 AI 请求设置 30 秒超时,避免服务挂起
- 响应验证:检查返回结果是否为 4 个中文字符,无效则触发降级
- 异常捕获:捕获网络错误、API 调用错误等,统一返回错误信息
- 历史兜底:即使 AI 服务完全不可用,默认成语列表仍能保证游戏基础功能
3. 前端交互层:用户体验优化
前端页面聚焦于流畅的交互体验和清晰的状态反馈,核心设计点包括:
(1)状态可视化
- 加载状态:初始成语获取时显示 “加载中...”
- 操作反馈:提交按钮显示 “提交中...”,禁用重复点击
- 结果提示:不同类型的消息使用不同样式(成功 - 绿色、错误 - 红色、信息 - 蓝色)
(2)输入校验
前端提前做输入验证,减少无效请求:
if (!/^[\u4e00-\u9fa5]+$/.test(userInput)) {
showMessage('请输入中文成语', 'error');
return;
}
if (userInput.length !== 4) {
showMessage('请输入四字成语', 'error');
return;
}
(3)历史记录展示
按时间倒序展示用户与 AI 的接龙记录,清晰呈现游戏进程:
<li>
<span class="user">你: 一心一意</span>
<span class="ai">AI: 意气风发</span>
<span class="time">14:25:30</span>
</li>
(4)完整代码
后端:
# 导入必要的库和模块
import os # 用于操作系统环境变量
from flask import Flask, request, jsonify, send_file # Flask web框架相关功能
from cozepy import Coze, TokenAuth, ChatStatus, COZE_CN_BASE_URL, Message # Coze AI服务SDK
from dotenv import load_dotenv # 用于加载环境变量文件
import uuid # 用于生成唯一的用户ID
import time # 用于时间相关操作
# 加载.env文件中的环境变量
load_dotenv()
# 创建Flask应用实例
app = Flask(__name__)
# 用于存储会话状态的字典,key为客户端IP或自定义标识,value为会话信息
user_sessions = {}
# 定义Coze服务类,用于封装与Coze AI服务的交互
class CozeService:
def __init__(self):
# 从环境变量获取Coze API令牌,如果没有则返回None
self.api_token = os.getenv("COZE_API_TOKEN")
# 从环境变量获取机器人ID,如果没有则使用默认值
self.bot_id = os.getenv("BOT_ID", "7552823978826694671")
# 初始化Coze客户端,使用令牌认证和中国区基础URL
self.coze = Coze(
auth=TokenAuth(token=self.api_token),
base_url=COZE_CN_BASE_URL
)
def get_sdk_response(self, user_message, user_identifier):
"""获取智能体响应,基于用户标识保持会话"""
try:
# 检查是否已有该用户的会话
if user_identifier in user_sessions:
session_data = user_sessions[user_identifier]
conversation_id = session_data["conversation_id"]
user_id = session_data["user_id"]
# 构建用户消息
messages = [
Message(
role="user", # 消息角色为用户
content=user_message, # 消息内容
content_type="text", # 内容类型为文本
type="question" # 消息类型为问题
)
]
# 在现有会话中继续聊天
chat = self.coze.chat.create(
bot_id=self.bot_id, # 指定机器人ID
user_id=user_id, # 使用相同的用户ID
conversation_id=conversation_id, # 使用相同的会话ID
additional_messages=messages, # 添加用户消息
auto_save_history=True # 自动保存聊天历史
)
else:
# 创建新的会话
# 生成唯一的用户ID
user_id = str(uuid.uuid4())
# 构建用户消息
messages = [
Message(
role="user", # 消息角色为用户
content=user_message, # 消息内容
content_type="text", # 内容类型为文本
type="question" # 消息类型为问题
)
]
# 创建新的聊天会话
chat = self.coze.chat.create(
bot_id=self.bot_id, # 指定机器人ID
user_id=user_id, # 指定用户ID
additional_messages=messages, # 添加用户消息
auto_save_history=True # 自动保存聊天历史
)
# 保存会话信息到user_sessions字典中
user_sessions[user_identifier] = {
"conversation_id": chat.conversation_id, # Coze的会话ID
"user_id": user_id, # 用户ID
"chat_id": chat.id, # 聊天ID
"created_at": time.time(), # 会话创建时间戳
"last_activity": time.time() # 最后活动时间
}
# 更新最后活动时间
user_sessions[user_identifier]["last_activity"] = time.time()
# 更新chat_id(每次新的聊天都会生成新的chat_id)
user_sessions[user_identifier]["chat_id"] = chat.id
# 轮询等待聊天完成,当聊天状态为"进行中"时继续查询
while chat.status == ChatStatus.IN_PROGRESS:
chat = self.coze.chat.retrieve(
conversation_id=chat.conversation_id, # 会话ID
chat_id=chat.id # 聊天ID
)
# 如果聊天状态为"已完成",获取聊天消息
if chat.status == ChatStatus.COMPLETED:
messages = self.coze.chat.messages.list(
conversation_id=chat.conversation_id, # 会话ID
chat_id=chat.id # 聊天ID
)
# 遍历消息,找到助手的回复
for msg in messages:
if msg.role == "assistant":
return {
"status": "success",
"content": msg.content
}
# 如果对话未完成,返回失败状态
return {"status": "failed", "content": "对话未完成"}
except Exception as e:
# 如果发生异常,返回错误状态和异常信息
return {"status": "error", "content": str(e)}
# 创建CozeService实例
coze_service = CozeService()
@app.route('/')
def index():
# 在返回页面之前清理过期会话
cleanup_expired_sessions()
# 返回index.html文件
return send_file('index.html')
# 定义聊天路由,处理POST请求
@app.route('/chat', methods=['POST'])
def chat():
# 从请求的JSON数据中获取用户消息
data = request.json
user_message = data.get('message')
# 如果消息为空,返回错误
if not user_message:
return jsonify({"status": "error", "content": "消息不能为空"})
# 使用客户端IP作为用户标识(也可以使用cookie或其他方式)
user_identifier = request.remote_addr
# 获取AI响应,基于用户标识保持会话
response = coze_service.get_sdk_response(user_message, user_identifier)
# 将响应转换为JSON格式返回
return jsonify(response)
# 清理过期会话的辅助函数
def cleanup_expired_sessions():
expired_sessions = []
for user_identifier, session_data in user_sessions.items():
expired_sessions.append(user_identifier)
# 删除过期会话
for user_identifier in expired_sessions:
del user_sessions[user_identifier]
# 如果是直接运行此脚本,启动Flask应用
if __name__ == '__main__':
# 启动应用,开启调试模式,端口为5000
app.run(debug=True, port=5000)
前端:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的人生我做主</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
color: #333;
line-height: 1.6;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background-color: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
header {
background: linear-gradient(90deg, #3498db, #2c3e50);
color: white;
padding: 30px 20px;
text-align: center;
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
}
header p {
font-size: 1.2rem;
opacity: 0.9;
}
.chat-container {
display: flex;
flex-direction: column;
height: 500px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 20px;
background-color: #f9f9f9;
}
.message {
margin-bottom: 15px;
padding: 12px 18px;
border-radius: 18px;
max-width: 80%;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.user-message {
background-color: #3498db;
color: white;
margin-left: auto;
border-bottom-right-radius: 5px;
}
.bot-message {
background-color: #ecf0f1;
color: #333;
margin-right: auto;
border-bottom-left-radius: 5px;
}
.input-area {
display: flex;
padding: 15px;
border-top: 1px solid #eee;
background-color: white;
}
#user-input {
flex: 1;
padding: 12px 18px;
border: 1px solid #ddd;
border-radius: 25px;
outline: none;
font-size: 1rem;
transition: border-color 0.3s;
}
#user-input:focus {
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
}
#send-button {
margin-left: 10px;
padding: 12px 25px;
background-color: #3498db;
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s, transform 0.2s;
}
#send-button:hover {
background-color: #2980b9;
transform: scale(1.05);
}
#send-button:active {
transform: scale(0.98);
}
.typing-indicator {
display: none;
padding: 10px 15px;
background-color: #ecf0f1;
border-radius: 18px;
margin-bottom: 15px;
width: fit-content;
border-bottom-left-radius: 5px;
}
.typing-dots {
display: flex;
align-items: center;
height: 20px;
}
.typing-dot {
width: 8px;
height: 8px;
background-color: #7f8c8d;
border-radius: 50%;
margin: 0 3px;
animation: typingAnimation 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(1) { animation-delay: 0s; }
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes typingAnimation {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-5px); }
}
@media (max-width: 600px) {
header h1 {
font-size: 2rem;
}
header p {
font-size: 1rem;
}
.message {
max-width: 90%;
}
#send-button {
padding: 12px 20px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>我的未来我做主</h1>
<p>与AI助手一起规划您的未来</p>
</header>
<div class="chat-container">
<div id="chat-messages" class="chat-messages">
<div class="message bot-message">
<p>您好!我是您的未来规划助手。请问您想了解什么关于未来规划的问题?</p>
</div>
</div>
<div class="typing-indicator" id="typing-indicator">
<div class="typing-dots">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
<div class="input-area">
<input type="text" id="user-input" placeholder="输入您的问题...">
<button id="send-button">发送</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const chatMessages = document.getElementById('chat-messages');
const userInput = document.getElementById('user-input');
const sendButton = document.getElementById('send-button');
const typingIndicator = document.getElementById('typing-indicator');
// 添加用户消息到聊天界面
function addUserMessage(message) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', 'user-message');
messageElement.innerHTML = `<p>${message}</p>`;
chatMessages.appendChild(messageElement);
scrollToBottom();
}
// 添加AI消息到聊天界面
function addBotMessage(message) {
const messageElement = document.createElement('div');
messageElement.classList.add('message', 'bot-message');
messageElement.innerHTML = `<p>${message}</p>`;
chatMessages.appendChild(messageElement);
scrollToBottom();
}
// 显示正在输入指示器
function showTypingIndicator() {
typingIndicator.style.display = 'block';
scrollToBottom();
}
// 隐藏正在输入指示器
function hideTypingIndicator() {
typingIndicator.style.display = 'none';
}
// 滚动到底部
function scrollToBottom() {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// 发送消息到后端
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
// 添加用户消息到界面
addUserMessage(message);
userInput.value = '';
// 显示正在输入指示器
showTypingIndicator();
try {
// 发送请求到后端
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message: message })
});
const data = await response.json();
// 隐藏正在输入指示器
hideTypingIndicator();
if (data.status === 'success') {
addBotMessage(data.content);
} else {
addBotMessage('抱歉,我遇到了一些问题,请稍后再试。');
console.error('API Error:', data.content);
}
} catch (error) {
// 隐藏正在输入指示器
hideTypingIndicator();
addBotMessage('网络错误,请检查您的连接。');
console.error('Fetch Error:', error);
}
}
// 发送按钮点击事件
sendButton.addEventListener('click', sendMessage);
// 输入框回车事件
userInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
// 页面加载后自动聚焦到输入框
userInput.focus();
});
</script>
</body>
</html>
三、关键技术难点与解决方案
1. AI 响应异步处理问题
Coze Chat API 采用异步响应模式,直接调用后无法立即获取结果。解决方案:
- 轮询机制:调用
chat.create后,循环调用chat.retrieve获取最新状态 - 超时控制:设置最大轮询次数,避免无限等待
- 状态判断:通过
ChatStatus枚举值判断 AI 处理状态(IN_PROGRESS/COMPLETED)
2. 跨域请求问题
前端页面与后端 API 部署在同一域名但不同端口,导致跨域请求被浏览器拦截。解决方案:
- 引入 Flask-CORS 扩展,全局启用 CORS:
CORS(app) - 生产环境可配置指定域名白名单,提升安全性
3. 敏感配置管理
API Token、Bot ID 等敏感信息直接写死代码存在安全风险。解决方案:
- 使用
.env文件存储环境变量:COZE_API_TOKEN=xxx - 通过
dotenv加载配置:load_dotenv() - 代码中通过
os.environ.get()获取,避免硬编码
4. 用户体验与容错平衡
AI 响应存在延迟,若直接等待会导致用户体验下降。解决方案:
- 前端禁用操作按钮并显示加载状态,明确告知用户处理中
- 后端设置合理的超时时间(30 秒),兼顾响应速度和成功率
- AI 服务异常时自动切换到本地成语列表,保证游戏不中断
四、系统优化与扩展方向
1. 现有优化点
- 性能优化:历史记录限制最多 20 条,减少数据传输量
- 资源复用:全局唯一的
IdiomGame实例,避免重复初始化 AI 客户端 - 错误处理:全局异常处理器,统一返回 JSON 格式错误信息
2. 扩展方向
(1)功能扩展
- 成语验证:增加成语合法性校验(接入成语词典 API)
- 难度分级:根据用户水平调整 AI 接龙难度(如生僻成语 / 常用成语)
- 计分系统:记录接龙成功次数,增加游戏竞技性
- 多语言支持:适配繁体中文,面向海外用户
(2)技术优化
- 缓存机制:缓存 AI 生成的成语列表,减少 API 调用次数
- 异步处理:使用 Flask-Async 扩展,将 AI 请求改为异步任务,提升并发能力
- 前端框架重构:使用 Vue/React 重构前端,提升代码可维护性
- 部署优化:使用 Docker 容器化部署,支持多环境一键部署
五、项目总结与思考
1. 技术价值
这个小项目展示了 AI 技术与传统应用结合的可能性:
- 轻量化 AI 集成:无需复杂的模型部署,通过 API 即可快速接入 AI 能力
- 容错设计的重要性:任何依赖外部服务的应用,都需要做好降级策略
- 前后端分离的简化实现:原生技术栈也能打造流畅的交互体验
2. 产品思考
从产品角度,这个游戏的核心价值在于 “轻量化” 和 “趣味性”:
- 无需下载安装,网页端直接体验
- AI 接龙替代人工,随时随地可玩
- 传统文化与现代技术结合,兼具教育意义和娱乐性
3. 开发感悟
- 小项目也需要注重架构设计:模块化封装让代码更易维护
- 容错机制是生产级应用的必备:用户不会关心技术细节,只在意是否可用
- 快速迭代与验证:先实现核心功能,再逐步优化体验和扩展功能
六、快速上手指南
1. 环境准备
# 安装依赖
pip install flask flask-cors cozepy python-dotenv
# 创建.env文件
COZE_API_TOKEN=你的Coze API Token
BOT_ID=你的Coze Bot ID
USER_ID=自定义用户ID
2. 启动服务
python app.py
3. 访问游戏
打开浏览器,访问http://localhost:5000即可开始游戏。
结语
这个智能成语接龙游戏看似简单,却涵盖了前后端交互、AI 集成、容错设计、用户体验优化等多个开发维度。在实际开发中,我们不需要一开始就追求完美,而是先实现核心功能,再通过持续优化提升系统的稳定性和用户体验。
AI 技术的普及让小型应用也能具备智能能力,关键在于找到合适的应用场景,并用简洁的技术方案解决核心问题。希望本文的拆解能为你带来启发,也欢迎你基于这个项目继续扩展,打造更有趣的 AI 应用。
更多推荐



所有评论(0)