
本文讲解如何在 flask 中通过 http 请求-响应周期与 session 协作,替代传统 while 循环,实现面向听障儿童的法语 hangman 变体游戏的分步交互逻辑。
本文讲解如何在 flask 中通过 http 请求-响应周期与 session 协作,替代传统 while 循环,实现面向听障儿童的法语 hangman 变体游戏的分步交互逻辑。
在 Web 应用中,无法直接“暂停”或“逐帧渲染”一个阻塞式 while 循环——因为 Flask(及其他同步 Web 框架)的每个请求必须在有限时间内完成并返回响应,服务器不会为单个用户维持长期运行的 Python 线程。你遇到的 session.get('current_letter') 返回 None 或默认值,正是由于循环从未真正“运行在服务端上下文中”,而只是被写在了路由函数内部却未被调用或未与请求生命周期对齐。
正确的思路是:将“每一轮猜测”建模为一次独立的 HTTP 请求,利用 session 持久化游戏状态(如已选单词、已猜字母、当前轮次、剩余可能词库等),并通过表单提交(Yes/No)触发下一轮逻辑。这种方式天然契合“点击→反馈→再点击”的交互范式,也完全适配听障儿童所需的清晰、节奏可控的界面设计。
✅ 核心实现模式:状态驱动的请求链
以下是一个精简但完整的可运行示例,展示如何将你的猜词逻辑拆解为可中断、可恢复的步骤:
from flask import Flask, render_template_string, request, session, redirect, url_for
import random
import math
app = Flask(__name__)
app.secret_key = 'dev-key-for-demo'
# 模拟词库加载(实际中调用 liste_mots("mots.txt"))
def load_word_dict():
return {
"Facile": ["chat", "chien", "soleil"],
"Moyen": ["ordinateur", "bibliothèque"],
"Difficile": ["anticonstitutionnellement"]
}
@app.route('/')
def menu():
return render_template_string('''
<h2>Bienvenue au Jeu de Devinettes !</h2>
<p>Choisissez un niveau :</p>
<ul>
<li><a href="{{ url_for('choose_diff', difficulty='Facile') }}">Facile</a></li>
<li><a href="{{ url_for('choose_diff', difficulty='Moyen') }}">Moyen</a></li>
<li><a href="{{ url_for('choose_diff', difficulty='Difficile') }}">Difficile</a></li>
</ul>
''')
@app.route('/choisir/<difficulty>')
def choose_diff(difficulty):
word_dict = load_word_dict()
words = word_dict.get(difficulty, [])
return render_template_string('''
<h3>Niveau : {{ difficulty }}</h3>
<p>Sélectionnez un mot :</p>
<ul>
{% for word in words %}
<li><a href="{{ url_for('start_game', word=word) }}">{{ word }}</a></li>
{% endfor %}
</ul>
<a href="{{ url_for('menu') }}">← Retour au menu</a>
''', difficulty=difficulty, words=words)
@app.route('/jeu/<word>', methods=['GET', 'POST'])
def start_game(word):
if request.method == 'GET':
# 初始化游戏状态
letters = list(word)
word_length = len(letters)
letter_limit = max(1, int(math.floor(0.6 * word_length))) # 至少猜1个字母
session.update({
'selected_word': word,
'guessed_letters': ['_'] * word_length,
'current_turn': 0,
'letter_limit': letter_limit,
'common_letters': list("eastirnulodmcpévhgfb"),
'uncommon_letters': list("qjàxèêzykôûwâîüùëœçï"),
'alphabet': list("eastirnulodmcpévhgfbqjàxèêzykôûwâîüùëœçï"),
'possible_words': [] # 后续填充
})
return redirect(url_for('display_step'))
return redirect(url_for('menu'))
@app.route('/etape', methods=['GET', 'POST'])
def display_step():
# 强制校验会话状态
required_keys = ['selected_word', 'guessed_letters', 'current_turn', 'letter_limit']
if not all(k in session for k in required_keys):
return redirect(url_for('menu'))
word = session['selected_word']
guessed = session['guessed_letters']
turn = session['current_turn']
limit = session['letter_limit']
letters = list(word)
# ✅ 判断是否进入“猜词阶段”
true_guessed = len(guessed) - guessed.count('_')
in_word_guessing_phase = (turn >= 8) or (true_guessed >= limit)
# ✅ 执行本轮 AI 决策(仅当是新回合且未结束时)
if request.method == 'GET' and not in_word_guessing_phase:
# 选择下一个字母(简化版逻辑,保留你原策略核心)
common = session.get('common_letters', [])
uncommon = session.get('uncommon_letters', [])
alphabet = session.get('alphabet', [])
if len(letters) > 6 and true_guessed < 5 and any(c in common for c in letters):
choice = random.choice(common)
session['current_letter'] = choice
# 从可用列表中移除(真实场景需深拷贝或更健壮管理)
if choice in common:
common.remove(choice)
if choice in alphabet:
alphabet.remove(choice)
session['common_letters'] = common
session['alphabet'] = alphabet
else:
choice = random.choice(alphabet) if alphabet else 'a'
session['current_letter'] = choice
if choice in alphabet:
alphabet.remove(choice)
session['alphabet'] = alphabet
elif request.method == 'POST':
# 处理用户反馈(Yes/No)
user_response = request.form.get('answer') # 'yes' or 'no'
current_letter = session.get('current_letter', '')
if user_response == 'yes' and current_letter in letters:
# 更新已猜中字母
for i, c in enumerate(letters):
if c == current_letter:
guessed[i] = current_letter
elif user_response == 'no':
session['current_turn'] = turn + 1 # 错误计数+1
session['guessed_letters'] = guessed
# ✅ 检查是否满足进入猜词阶段或游戏结束条件
true_guessed = len(guessed) - guessed.count('_')
if (turn + 1 >= 8) or (true_guessed >= limit):
# 构建 possible_words(简化:同长度 + 包含所有已知字母)
word_dict = load_word_dict()
all_words = word_dict['Facile'] + word_dict['Moyen'] + word_dict['Difficile']
possible = [
w for w in all_words
if len(w) == len(letters)
and all(g == '_' or g in w for g in guessed)
]
session['possible_words'] = possible[:20] # 限制数量防卡顿
session['guessing_words'] = True
else:
session['current_turn'] = turn + 1
return redirect(url_for('display_step'))
# ✅ 渲染当前步骤页面
current_letter = session.get('current_letter', '?')
guessing_words = session.get('guessing_words', False)
possible_words = session.get('possible_words', [])
if guessing_words and possible_words:
current_word = random.choice(possible_words)
session['current_word'] = current_word
template = '''
<h3>Phase : Deviner le mot !</h3>
<p>Mot proposé par l'ordinateur : <strong>{{ current_word }}</strong></p>
<form method="post">
<button type="submit" name="answer" value="yes">✅ C'est ça !</button>
<button type="submit" name="answer" value="no">❌ Non</button>
</form>
<p><small>(Tour {{ turn }}, mot cible : "{{ word }}")</small></p>
<a href="{{ url_for('menu') }}">⏹ Arrêter la partie</a>
'''
else:
template = '''
<h3>Phase : Deviner les lettres</h3>
<p>Mot partiel : <strong>{{ ' '.join(guessed) }}</strong></p>
<p>Lettre proposée : <strong>{{ current_letter }}</strong></p>
<form method="post">
<button type="submit" name="answer" value="yes">✅ Oui, elle est dedans</button>
<button type="submit" name="answer" value="no">❌ Non, elle n'y est pas</button>
</form>
<p><small>(Tour {{ turn }}, {{ true_guessed }}/{{ limit }} lettres trouvées)</small></p>
<a href="{{ url_for('menu') }}">⏹ Arrêter la partie</a>
'''
return render_template_string(template,
word=word,
guessed=guessed,
current_letter=current_letter,
turn=turn,
true_guessed=true_guessed,
limit=limit,
current_word=session.get('current_word', '')
)
@app.route('/fin')
def game_over():
result = "Victoire de l'ordinateur !" if session.get('won') else "Vous avez gagné !"
return render_template_string('<h2>{{ result }}</h2><a href="{{ url_for("menu") }}">Nouvelle partie</a>', result=result)⚠️ 关键注意事项
- Session 不是万能存储:避免存放大对象(如完整词典、大列表)。优先只存关键状态(索引、标志位、少量字符串),必要时结合数据库或 Redis。
- 并发安全:本例假设单用户单会话。若支持多玩家,需确保 session 隔离性(Flask 默认满足);多人实时协作则需 WebSocket 或长轮询。
-
用户体验优化:
- 为听障儿童添加图标(✅/❌)、高对比色、大按钮、手语提示图;
- 在 commencer_partie.html 中嵌入 <audio> 播放法语发音(若资源允许);
- 使用 session.pop() 清理已完成游戏的状态,防止脏数据。
- 调试技巧:在关键路由开头加入 print(f"[DEBUG] Session keys: {list(session.keys())}") 快速定位丢失字段。
✅ 总结
你无需“把 while 循环搬到网页上”,而应将循环逻辑转化为状态机 + 请求驱动流。每一次 redirect(url_for('display_step')) 就是一次“循环迭代”,session 是它的内存寄存器,表单提交是它的时钟信号。这种设计不仅解决了技术限制,更天然符合教育类应用所需的渐进式、低认知负荷交互原则——而这,正是为听障儿童设计数字学习工具的核心要义。


















