GROUP123-LYT/turtle_soup_html_v2.html
DROUP123 7144087596 feat: 添加HTML版本海龟汤游戏并优化Python版本
refactor: 重构Python版本代码,增加API错误处理和默认题目功能

docs: 更新README文档,添加开发心得和运行指南

chore: 添加项目依赖到requirements.txt
2026-01-07 17:08:47 +08:00

860 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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-color: #f5f5f5;
color: #333;
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: white;
min-height: 100vh;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #ff6b35;
margin-bottom: 30px;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
}
.game-info {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border-left: 4px solid #ff6b35;
}
.story-section {
background-color: #fff3cd;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
border: 2px solid #ffeeba;
}
.story-title {
font-weight: bold;
color: #856404;
margin-bottom: 10px;
font-size: 1.2em;
}
.story-content {
font-size: 1.1em;
line-height: 1.8;
}
.chat-section {
margin-bottom: 20px;
}
.chat-history {
height: 300px;
overflow-y: auto;
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
border: 1px solid #dee2e6;
}
.message {
margin-bottom: 15px;
display: flex;
flex-direction: column;
}
.user-message {
align-items: flex-end;
}
.bot-message {
align-items: flex-start;
}
.message-content {
padding: 10px 15px;
border-radius: 18px;
max-width: 80%;
}
.user-message .message-content {
background-color: #007bff;
color: white;
}
.bot-message .message-content {
background-color: #e9ecef;
color: #333;
}
.message-time {
font-size: 0.8em;
color: #6c757d;
margin-top: 5px;
margin-right: 10px;
margin-left: 10px;
}
.input-section {
display: flex;
gap: 10px;
}
#question-input {
flex: 1;
padding: 12px;
border: 2px solid #dee2e6;
border-radius: 25px;
font-size: 1em;
outline: none;
transition: border-color 0.3s;
}
#question-input:focus {
border-color: #007bff;
}
#send-btn {
padding: 12px 24px;
background-color: #ff6b35;
color: white;
border: none;
border-radius: 25px;
font-size: 1em;
cursor: pointer;
transition: background-color 0.3s;
}
#send-btn:hover {
background-color: #ee5a24;
}
.command-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 15px;
}
.cmd-btn {
padding: 8px 16px;
background-color: #28a745;
color: white;
border: none;
border-radius: 15px;
font-size: 0.9em;
cursor: pointer;
transition: background-color 0.3s;
}
.cmd-btn:hover {
background-color: #218838;
}
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
.modal-content {
background-color: white;
margin: 15% auto;
padding: 30px;
border: 1px solid #888;
border-radius: 10px;
width: 80%;
max-width: 500px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
}
input[type="text"], textarea {
width: 100%;
padding: 10px;
border: 1px solid #dee2e6;
border-radius: 5px;
font-size: 1em;
}
textarea {
height: 100px;
resize: vertical;
}
.btn-primary {
background-color: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
}
.btn-primary:hover {
background-color: #0056b3;
}
.game-over {
background-color: #d4edda;
padding: 20px;
border-radius: 8px;
border: 2px solid #c3e6cb;
margin-bottom: 20px;
text-align: center;
color: #155724;
}
.stats-section {
background-color: #d1ecf1;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #17a2b8;
margin-bottom: 20px;
}
/* 滚动条样式 */
.chat-history::-webkit-scrollbar {
width: 8px;
}
.chat-history::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.chat-history::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.chat-history::-webkit-scrollbar-thumb:hover {
background: #555;
}
@media (max-width: 600px) {
.container {
padding: 10px;
}
.story-content {
font-size: 1em;
}
.chat-history {
height: 200px;
}
.command-buttons {
flex-direction: column;
}
.cmd-btn {
width: 100%;
}
.message-content {
max-width: 90%;
}
}
</style>
</head>
<body>
<div class="container">
<h1>🐢 海龟汤游戏 🐢</h1>
<div class="game-info">
<p>🔍 海龟汤是一种情境猜谜游戏,我会给出一个奇怪的情境,你可以通过提问来猜测故事的真相。</p>
<p>💬 我只会回答'是'、'否'或'无关'。</p>
<p>📋 输入'答案'查看完整故事,输入'新题'生成新的题目,输入'自定义'创建自己的题目,</p>
<p>📋 输入'提示'获取线索,输入'猜测 内容'尝试猜测答案,输入'结束'退出游戏。</p>
</div>
<div class="stats-section">
<div id="stats"></div>
</div>
<div class="story-section">
<div class="story-title">🥣 汤面</div>
<div class="story-content" id="story-content">请选择游戏模式开始游戏</div>
</div>
<div id="game-over" class="game-over" style="display: none;">
<h3 id="game-over-message"></h3>
<p id="solution-display"></p>
<button class="cmd-btn" onclick="startNewGame()">开始新游戏</button>
</div>
<div class="command-buttons">
<button class="cmd-btn" onclick="selectMode(1)">AI生成题目</button>
<button class="cmd-btn" onclick="selectMode(2)">自定义题目</button>
<button class="cmd-btn" onclick="showHint()">获取提示</button>
<button class="cmd-btn" onclick="showSolution()">查看答案</button>
<button class="cmd-btn" onclick="startNewGame()">新题</button>
<button class="cmd-btn" onclick="endGame()">结束游戏</button>
</div>
<div class="chat-section">
<div class="chat-history" id="chat-history"></div>
<div class="input-section">
<input type="text" id="question-input" placeholder="请输入问题或命令...">
<button id="send-btn" onclick="sendMessage()">发送</button>
</div>
</div>
<!-- 自定义题目模态框 -->
<div id="custom-modal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeModal()">&times;</span>
<h2>创建自定义题目</h2>
<div class="form-group">
<label for="custom-story">汤面(奇怪的情境)</label>
<textarea id="custom-story" placeholder="请输入汤面..."></textarea>
</div>
<div class="form-group">
<label for="custom-solution">汤底(完整解释)</label>
<textarea id="custom-solution" placeholder="请输入汤底..."></textarea>
</div>
<button class="btn-primary" onclick="saveCustomStory()">保存题目</button>
</div>
</div>
</div>
<script>
// 游戏状态
let gameState = {
story: null,
solution: null,
gameOver: false,
hints: [],
questions: [],
guesses: [],
startTime: null,
endTime: null,
result: "未完成",
isCustom: false
};
// 预设题目列表
const presetStories = [
{
story: "一个男人走进一家餐厅,点了一份海龟汤。喝了一口后,他放下汤匙,默默地付了钱离开了餐厅。",
solution: "这个男人曾经遭遇海难,和同伴被困在荒岛上。为了生存,同伴们煮了海龟汤给他喝,并告诉他这是他妻子的肉。获救后,他在餐厅喝到真正的海龟汤,才知道自己被骗了,同伴们牺牲了自己来救他。",
keywords: ["海难", "荒岛", "同伴", "牺牲", "被骗"]
},
{
story: "一个女人独居在公寓里,一天晚上她熄灭了灯准备睡觉。第二天早上,她打开报纸,看到一条消息后跳楼自杀了。",
solution: "这个女人是灯塔管理员,她熄灭的不是自己家的灯,而是灯塔的灯。结果导致一艘船在夜间触礁沉没,船上所有人都遇难了。看到报纸上的这个消息后,她因为内疚而自杀了。",
keywords: ["灯塔管理员", "灯塔", "船", "触礁", "遇难"]
},
{
story: "一个人在雨中行走,没有带任何雨具,头发却一点也没湿。",
solution: "这个人是光头,没有头发,所以即使在雨中行走,头发也不会湿。",
keywords: ["光头", "没有头发"]
}
];
// 初始化游戏
function initGame() {
loadGameStats();
updateStats();
addMessage("系统", "欢迎来到海龟汤游戏!请选择游戏模式开始游戏。");
}
// 选择游戏模式
function selectMode(mode) {
if (mode === 1) {
// 生成AI题目使用预设题目
generateStory();
} else if (mode === 2) {
// 显示自定义题目模态框
openModal();
}
}
// 生成题目
function generateStory() {
// 随机选择一个预设题目
const randomIndex = Math.floor(Math.random() * presetStories.length);
const selectedStory = presetStories[randomIndex];
gameState.story = selectedStory.story;
gameState.solution = selectedStory.solution;
gameState.isCustom = false;
gameState.gameOver = false;
gameState.startTime = new Date().toISOString();
gameState.endTime = null;
gameState.result = "未完成";
gameState.hints = [];
gameState.questions = [];
gameState.guesses = [];
// 更新界面
document.getElementById('story-content').textContent = gameState.story;
document.getElementById('game-over').style.display = 'none';
addMessage("系统", `汤面:${gameState.story}`);
addMessage("系统", "你可以通过提问来猜测故事的真相,游戏中我只会回答'是'、'否'或'无关'。");
}
// 打开自定义题目模态框
function openModal() {
document.getElementById('custom-modal').style.display = 'block';
}
// 关闭自定义题目模态框
function closeModal() {
document.getElementById('custom-modal').style.display = 'none';
// 清空表单
document.getElementById('custom-story').value = '';
document.getElementById('custom-solution').value = '';
}
// 保存自定义题目
function saveCustomStory() {
const customStory = document.getElementById('custom-story').value.trim();
const customSolution = document.getElementById('custom-solution').value.trim();
if (!customStory || !customSolution) {
alert("汤面和汤底不能为空!");
return;
}
gameState.story = customStory;
gameState.solution = customSolution;
gameState.isCustom = true;
gameState.gameOver = false;
gameState.startTime = new Date().toISOString();
gameState.endTime = null;
gameState.result = "未完成";
gameState.hints = [];
gameState.questions = [];
gameState.guesses = [];
// 更新界面
document.getElementById('story-content').textContent = gameState.story;
document.getElementById('game-over').style.display = 'none';
closeModal();
addMessage("系统", `汤面:${gameState.story}`);
addMessage("系统", "自定义题目创建成功!你可以通过提问来猜测故事的真相。");
}
// 发送消息
function sendMessage() {
const input = document.getElementById('question-input');
const message = input.value.trim();
if (!message) return;
// 清空输入框
input.value = '';
if (!gameState.story) {
addMessage("系统", "请先选择游戏模式开始游戏!");
return;
}
if (gameState.gameOver) {
addMessage("系统", "游戏已结束,请开始新游戏!");
return;
}
addMessage("我", message);
// 处理命令
const lowerMessage = message.toLowerCase();
if (lowerMessage.includes("答案") || lowerMessage.includes("查看答案")) {
showSolution();
return;
}
if (lowerMessage.includes("结束") || lowerMessage.includes("退出")) {
endGame();
return;
}
if (lowerMessage.includes("新题") || lowerMessage.includes("重新开始") || lowerMessage.includes("换一个")) {
startNewGame();
return;
}
if (lowerMessage.includes("自定义") || lowerMessage.includes("创建题目")) {
openModal();
return;
}
if (lowerMessage.includes("提示") || lowerMessage.includes("线索")) {
showHint();
return;
}
if (lowerMessage.startsWith("猜测") || lowerMessage.startsWith("我猜")) {
const guess = message.substring(2).trim();
handleGuess(guess);
return;
}
// 处理普通问题
handleQuestion(message);
}
// 处理问题
function handleQuestion(question) {
// 记录问题
gameState.questions.push({
question: question,
timestamp: new Date().toISOString()
});
// 简单的回答逻辑
let answer = "无关";
// 如果问题包含汤底中的关键词,回答"是"或"否"
// 这里使用简单的规则,实际应用中可以更复杂
if (gameState.solution.toLowerCase().includes(question.toLowerCase())) {
answer = "是";
} else if (gameState.solution.toLowerCase().includes(question.toLowerCase().replace("不", "")) ||
gameState.solution.toLowerCase().includes(question.toLowerCase().replace("不是", "是"))) {
answer = "否";
}
// 记录回答
gameState.questions[gameState.questions.length - 1].answer = answer;
addMessage("AI", answer);
}
// 处理猜测
function handleGuess(guess) {
// 记录猜测
gameState.guesses.push({
guess: guess,
timestamp: new Date().toISOString()
});
// 简单的猜测评估逻辑
const lowerGuess = guess.toLowerCase();
const lowerSolution = gameState.solution.toLowerCase();
// 检查是否包含所有关键词
const keywords = getKeywords(gameState.solution);
let matchCount = 0;
keywords.forEach(keyword => {
if (lowerGuess.includes(keyword)) {
matchCount++;
}
});
// 评估结果
if (matchCount >= keywords.length * 0.7) {
// 完全正确
addMessage("系统", "🎉 恭喜你!你的猜测完全正确!");
showSolution();
} else if (matchCount >= keywords.length * 0.3) {
// 部分正确
addMessage("系统", "👍 你的猜测部分正确,但还有一些关键信息没有猜出来。");
addMessage("系统", "🔍 继续提问,获取更多线索吧!");
} else {
// 错误
addMessage("系统", "👎 你的猜测不正确,再试一次吧!");
addMessage("系统", "🔍 继续提问,获取更多线索吧!");
}
}
// 提取关键词
function getKeywords(text) {
// 简单的关键词提取(移除常见停用词,取名词)
const stopWords = ['的', '了', '和', '是', '在', '有', '我', '你', '他', '她', '它', '们', '这', '那', '个', '件', '条', '只', '对', '于', '就', '也', '都', '但', '而', '所以', '因为', '如果', '虽然', '但是', '然而', '不过'];
// 简单分词(这里使用空格分词,实际应用中可以使用更复杂的分词方法)
let words = text.split(/[\s,.;?!]+/).filter(word => word.length > 1);
// 移除停用词
words = words.filter(word => !stopWords.includes(word));
// 返回前10个关键词
return words.slice(0, 10);
}
// 显示提示
function showHint() {
if (!gameState.solution) {
addMessage("系统", "请先选择游戏模式开始游戏!");
return;
}
if (gameState.gameOver) {
addMessage("系统", "游戏已结束,无法获取提示!");
return;
}
// 生成提示(基于汤底的部分内容)
let hint = "";
const solutionWords = gameState.solution.split(/\s+/);
if (gameState.hints.length === 0) {
// 第一个提示:提供人物信息
hint = "提示1故事中涉及到的主要人物是" + getMainCharacter(gameState.solution) + "。";
} else if (gameState.hints.length === 1) {
// 第二个提示:提供事件发生的地点
hint = "提示2故事发生在" + getLocation(gameState.solution) + "。";
} else if (gameState.hints.length === 2) {
// 第三个提示:提供关键物品
hint = "提示3故事中的关键物品是" + getKeyItem(gameState.solution) + "。";
} else {
// 没有更多提示
hint = "没有更多提示了,请尝试自己思考!";
}
if (!gameState.hints.includes(hint)) {
gameState.hints.push(hint);
}
addMessage("系统", hint);
}
// 辅助函数:获取主要人物
function getMainCharacter(solution) {
// 简单的人物提取,实际应用中可以更复杂
const possibleCharacters = ["男人", "女人", "男孩", "女孩", "老人", "孩子", "他", "她", "他们", "她们"];
for (let character of possibleCharacters) {
if (solution.includes(character)) {
return character;
}
}
return "一个人";
}
// 辅助函数:获取地点
function getLocation(solution) {
// 简单的地点提取,实际应用中可以更复杂
const possibleLocations = ["餐厅", "家里", "医院", "海滩", "森林", "办公室", "学校", "公寓", "灯塔"];
for (let location of possibleLocations) {
if (solution.includes(location)) {
return location;
}
}
return "某个地方";
}
// 辅助函数:获取关键物品
function getKeyItem(solution) {
// 简单的物品提取,实际应用中可以更复杂
const possibleItems = ["海龟汤", "灯", "雨伞", "报纸", "手机", "钥匙", "汤勺", "笔", "纸"];
for (let item of possibleItems) {
if (solution.includes(item)) {
return item;
}
}
return "某个物品";
}
// 显示答案
function showSolution() {
if (!gameState.solution) {
addMessage("系统", "请先选择游戏模式开始游戏!");
return;
}
gameState.gameOver = true;
gameState.endTime = new Date().toISOString();
gameState.result = "查看答案";
// 显示答案
const gameOverMessage = document.getElementById('game-over-message');
const solutionDisplay = document.getElementById('solution-display');
gameOverMessage.textContent = "游戏结束!汤底如下:";
solutionDisplay.innerHTML = `<strong>汤底:</strong>${gameState.solution}`;
document.getElementById('game-over').style.display = 'block';
addMessage("系统", `汤底:${gameState.solution}`);
// 保存游戏数据
saveGameSession();
}
// 结束游戏
function endGame() {
gameState.gameOver = true;
gameState.endTime = new Date().toISOString();
gameState.result = "中途退出";
addMessage("系统", "游戏已结束,感谢参与!");
// 保存游戏数据
saveGameSession();
}
// 开始新游戏
function startNewGame() {
// 清空聊天记录
document.getElementById('chat-history').innerHTML = '';
addMessage("系统", "正在准备新游戏...");
// 生成新题目
generateStory();
}
// 保存游戏会话
function saveGameSession() {
// 获取现有游戏数据
let gameData = JSON.parse(localStorage.getItem('turtleSoupData')) || {
games: [],
statistics: {
totalGames: 0,
totalQuestions: 0,
winRate: 0
}
};
// 添加当前游戏数据
gameData.games.push({
...gameState,
sessionId: `game_${Date.now()}`
});
// 更新统计信息
const totalGames = gameData.games.length;
const wonGames = gameData.games.filter(game => game.result === "胜利" || game.result === "查看答案").length;
const totalQuestions = gameData.games.reduce((sum, game) => sum + game.questions.length, 0);
gameData.statistics = {
totalGames: totalGames,
totalQuestions: totalQuestions,
winRate: wonGames / totalGames
};
// 保存数据到本地存储
localStorage.setItem('turtleSoupData', JSON.stringify(gameData));
// 更新统计信息显示
updateStats();
}
// 加载游戏统计信息
function loadGameStats() {
const gameData = JSON.parse(localStorage.getItem('turtleSoupData')) || {
games: [],
statistics: {
totalGames: 0,
totalQuestions: 0,
winRate: 0
}
};
return gameData.statistics;
}
// 更新统计信息显示
function updateStats() {
const stats = loadGameStats();
const statsElement = document.getElementById('stats');
statsElement.innerHTML = `
<p>📊 游戏统计:</p>
<p>总游戏数:${stats.totalGames}</p>
<p>总提问数:${stats.totalQuestions}</p>
<p>完成率:${(stats.winRate * 100).toFixed(1)}%</p>
`;
}
// 添加消息到聊天记录
function addMessage(sender, content) {
const chatHistory = document.getElementById('chat-history');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender === "我" ? "user-message" : "bot-message"}`;
const messageContentDiv = document.createElement('div');
messageContentDiv.className = 'message-content';
messageContentDiv.textContent = content;
const messageTimeDiv = document.createElement('div');
messageTimeDiv.className = 'message-time';
messageTimeDiv.textContent = new Date().toLocaleTimeString();
messageDiv.appendChild(messageContentDiv);
messageDiv.appendChild(messageTimeDiv);
chatHistory.appendChild(messageDiv);
// 滚动到底部
chatHistory.scrollTop = chatHistory.scrollHeight;
}
// 键盘事件监听
document.getElementById('question-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
// 关闭模态框(点击外部区域)
window.onclick = function(event) {
const modal = document.getElementById('custom-modal');
if (event.target === modal) {
closeModal();
}
}
// 初始化游戏
document.addEventListener('DOMContentLoaded', initGame);
</script>
</body>
</html>