
本文详解如何在 flask 中通过 http 请求-响应周期与 session 持久化,替代传统 while 循环,实现面向听障儿童的法语 hangman 变体游戏的分步交互逻辑。
本文详解如何在 flask 中通过 http 请求-响应周期与 session 持久化,替代传统 while 循环,实现面向听障儿童的法语 hangman 变体游戏的分步交互逻辑。
在 Web 环境中,无法直接“暂停”或“逐步执行”一个阻塞式 while 循环并实时渲染每一步结果——因为 Flask 的每个视图函数必须在一次请求内完成执行并返回响应,而浏览器不会持续等待服务端循环结束。但我们可以巧妙地将“循环逻辑”拆解为多个独立、可中断、状态驱动的请求步骤,利用 session 存储游戏上下文(如当前单词、已猜字母、轮次计数等),让每一次用户点击(“是/否”)触发一次新请求,从而模拟出流畅的交互式游戏流程。
✅ 正确架构:以请求为“步”,以 session 为“状态机”
核心思想是:把原 while 循环的每次迭代,映射为一个独立的 Flask 路由处理函数(如 /play/step),并通过 session 在多次请求间共享和更新游戏状态。 用户每点击一次按钮(如“是”或“否”),即发起一次 POST 请求,服务端据此更新状态并重定向至下一轮页面。
以下是一个精简、可复用的模板,已适配你的 Hangman 游戏需求:
from flask import Flask, render_template, request, redirect, url_for, session
import random
import math
app = Flask(__name__)
app.secret_key = 'your_secret_key_here' # 生产环境请使用 secrets.token_hex()
# 加载词库(简化版,实际使用你原有的 liste_mots)
def load_word_dict(path="mots.txt"):
dico = {"Facile": [], "Moyen": [], "Difficile": []}
with open(path, "r", encoding="utf-8") as f:
current_level = None
for line in f:
line = line.strip()
if line in dico:
current_level = line
elif line and current_level:
dico[current_level].append(line)
return dico
# 初始化游戏状态(在选择单词后调用)
@app.route('/commencer_partie/<difficulty>/<word>')
def commencer_partie(difficulty, word):
word_dict = load_word_dict()
# 验证所选单词合法
if word not in word_dict.get(difficulty, []):
return "Invalid word", 400
# 初始化 session 状态
session.clear() # 清除旧状态
session.update({
'selected_word': word,
'guessed_letters': ['_'] * len(word),
'current_turn': 0,
'common_letters': list("eastirnulodmcpévhgfb"),
'uncommon_letters': list("qjàxèêzykôûwâîüùëœçï"),
'alphabet': list("eastirnulodmcpévhgfbqjàxèêzykôûwâîüùëœçï"),
'difficulty': difficulty,
'letter_limit': math.floor(0.6 * len(word))
})
return redirect(url_for('play_step'))
# 核心游戏步骤路由 —— 每次请求代表一轮猜测
@app.route('/play/step', methods=['GET', 'POST'])
def play_step():
# 强制初始化检查
if 'selected_word' not in session:
return redirect(url_for('menu'))
word = session['selected_word']
guessed = session['guessed_letters']
turn = session['current_turn']
letter_limit = session['letter_limit']
common = session['common_letters']
uncommon = session['uncommon_letters']
alphabet = session['alphabet']
# 判断是否进入「猜词阶段」
true_guessed = len(guessed) - guessed.count('_')
in_word_phase = (true_guessed >= letter_limit) or (turn >= 8)
# 生成本轮计算机猜测(字母 or 单词)
if not in_word_phase:
# 字母阶段逻辑(简化示例,保留你原有策略的核心分支)
if len(word) > 6 and true_guessed < 5 and any(c in common for c in word):
choice = random.choice(common)
common.remove(choice)
alphabet.remove(choice)
else:
choice = random.choice(alphabet)
alphabet.remove(choice)
session['current_choice'] = choice
session['choice_type'] = 'letter'
else:
# 单词阶段:筛选同长度、含所有已猜字母的候选词(忽略位置)
word_dict = load_word_dict()
all_words = word_dict[session['difficulty']]
possible = [
w for w in all_words
if len(w) == len(word)
and all(g == '_' or g in w for g in guessed)
]
choice = random.choice(possible) if possible else "??"
session['current_choice'] = choice
session['choice_type'] = 'word'
# 更新 session(关键!)
session.update({
'common_letters': common,
'uncommon_letters': uncommon,
'alphabet': alphabet,
'current_turn': turn + 1 # 注意:此 turn 是“即将进行的轮次”,非已执行次数
})
# 渲染当前步骤页面(含按钮)
return render_template('play_step.html',
current_choice=session['current_choice'],
choice_type=session['choice_type'],
guessed_letters=''.join(guessed),
turn=turn + 1,
max_turns=10
)
# 处理用户反馈(是/否),决定是否继续或结束
@app.route('/play/feedback', methods=['POST'])
def play_feedback():
feedback = request.form.get('answer') # 'yes' or 'no'
choice_type = session.get('choice_type', 'letter')
choice = session.get('current_choice', '')
word = session['selected_word']
guessed = session['guessed_letters'].copy()
if choice_type == 'letter' and feedback == 'yes' and choice in word:
# 更新已猜字母
for i, c in enumerate(word):
if c == choice:
guessed[i] = choice
session['guessed_letters'] = guessed
# 检查胜利/失败条件(简化示例)
true_guessed = len(guessed) - guessed.count('_')
if ''.join(guessed) == word:
return redirect(url_for('game_result', status='win'))
if session['current_turn'] >= 10:
return redirect(url_for('game_result', status='lose'))
# 继续下一轮
return redirect(url_for('play_step'))
@app.route('/result/<status>')
def game_result(status):
return render_template('result.html', status=status, word=session.get('selected_word', ''))? 对应 HTML 模板 (templates/play_step.html)
<!DOCTYPE html>
<html>
<head><title>Hangman pour enfants sourds</title></head>
<body>
<h2>Étape {{ turn }} / 10</h2>
<p>Mot partiel : <strong>{{ guessed_letters }}</strong></p>
{% if choice_type == 'letter' %}
<p>L'ordinateur propose la lettre : <strong>{{ current_choice }}</strong></p>
{% else %}
<p>L'ordinateur propose le mot : <strong>{{ current_choice }}</strong></p>
{% endif %}
<form method="post" action="{{ url_for('play_feedback') }}">
<button type="submit" name="answer" value="yes">✅ Oui</button>
<button type="submit" name="answer" value="no">❌ Non</button>
</form>
<a href="{{ url_for('menu') }}">← Retour au menu</a>
</body>
</html>⚠️ 关键注意事项
- Session 是唯一可靠的状态载体:不要尝试在全局变量或函数局部变量中保存游戏状态,它们在请求间不持久。
- 避免重复计算:词库加载(load_word_dict)建议缓存(如用 functools.lru_cache 或模块级变量),而非每次请求都读文件。
- 安全性增强:生产环境需对 word 和 difficulty 参数做严格校验(防止路径遍历或注入),推荐使用整数 ID 替代明文单词作为 URL 参数。
- 用户体验优化:可添加 AJAX 实现无刷新交互;为听障儿童设计时,务必确保按钮有高对比度图标+文字,并支持键盘导航(
-
调试技巧:在开发时临时添加
Debug: {{ session }}
查看当前 session 内容(上线前务必移除)。
通过这种请求驱动的状态机模式,你不仅解决了 while 循环无法在 Web 中分步渲染的问题,更构建了一个可扩展、易测试、符合 Web 本质的游戏架构。每一步都清晰可控,也为后续添加音效提示(如振动反馈)、手语动画集成等无障碍功能预留了标准接口。


















