はじめに
安定した回答を保存し、そのラウンドの間違いだけを独立した練習にします。前のクイズの概念と Python の関数、リスト、辞書、条件分岐、ループが必要です。この単元は前の3つのルールを独立して用意するので、前の単元のファイルやプロセスは不要です。
WebIDE Terminal → New Terminal を使います(狭い画面では More 内)。logic.py だけを編集し、engine.py、quiz.py、bank.json は保ってください。読解と練習の推定時間は35分で、初心者の実測時間ではありません。
送信済みの回答を JSON に保存する
このステップでは、画面位置に結び付けずに実際のラウンドを保存します。用意されたエンジンは、送信済みの question_id/option_id の組だけを含むリストを返します。フィードバックと点数はエンジンが担当し、記録には自己申告の正誤や画面番号を入れません。
WebIDE ターミナルから始めます。
cd /home/labex/project
json.dump は Python のリストや辞書を JSON として書きます。with open(..., "w", encoding="utf-8") はテキストファイルを書き込み用に開き、その後閉じます。UTF-8 はテキストの可搬性を保ち、ensure_ascii=False は Unicode を読みやすく残し、indent=2 は字下げで見やすくします。stream.write("\n") は末尾に改行を入れます。外側の {"answers": answers} がリストを識別します。
以下の here-document 全体は準備済みの3ルールを保ち、新しい保存関数を追加します。> は logic.py を置き換え、引用符付き <<'PY' は最後の PY までをそのまま書き込みます。練習用の関数は次に追加します。
cat > logic.py <<'PY'
import json
def choose_questions(questions, count, rng):
return rng.sample(questions, min(count, len(questions)))
def move_options(question, rng):
options = question["options"].copy()
rng.shuffle(options)
return options
def read_answer(options, ask, tell):
while True:
text = ask("Answer (number or q): ").strip()
if text.lower() == "q":
return None
for position, option in enumerate(options, start=1):
if text == str(position):
return option["option_id"]
tell("Enter one displayed number, or q to stop.")
def save_answers(path, answers):
with open(path, "w", encoding="utf-8") as stream:
json.dump({"answers": answers}, stream, ensure_ascii=False, indent=2)
stream.write("\n")
PY
--ordered は既知の問題・選択肢の順序を保ち、--output は新しい記録名を指定します。既存の出力先はプレイ前に拒否され、前の記録が守られます。2問に答えます。sky で 1(正解)、続いて tide でも 1(不正解)を入力します。
python3 quiz.py bank.json --ordered --count 2 --output round.json
Score: 1/2 answered
Saved round.json
cat で本当の記録を表示します。
cat round.json
sky/sun と tide/sun が入っています。同じ選択肢 ID でも問題が違えば意味は異なります。この元のラウンドを残してください。誤って再実行した場合、既存出力のエラーは想定内です。上書きせず、追加の実験には新しい名前を使います。入力元のファイル名を間違えたら ls で確認して正しいパスで再実行してください。Quiz error: ... は保存成功を意味しません。
このラウンドの間違いだけを練習する
このステップでは各問題の本当の正解 ID から間違いを計算し直します。辞書で回答済みの問題 ID を選んだ選択肢 ID に対応付けます。回答が存在し、かつその問題自身の correct_option_id と異なる場合だけ含めます。正解した問題も未回答の問題も除外します。リストには問題オブジェクト全体を保ちます。{answer["question_id"]: answer["option_id"] for answer in answers} では for の後ろが各回答を巡回し、前のキーと値の部分が辞書の要素を作ります。これが辞書内包表記です。[question for question in questions if ...] では for が各問題を巡回し、if が条件に合うものだけを残します。これがリスト内包表記です。and は「回答がある」と「間違っている」の両方を要求するため、未回答を間違いにしません。>> は既存ルールを置き換えずに追記します。
cat >> logic.py <<'PY'
def wrong_questions(questions, answers):
selected = {answer["question_id"]: answer["option_id"] for answer in answers}
return [question for question in questions
if question["question_id"] in selected
and selected[question["question_id"]] != question["correct_option_id"]]
PY
--practice は元のラウンドを読み、--output は別の新しい記録を書きます。そのラウンドの全間違いだけを練習し、履歴全体を蓄積するものではありません。実行します。
python3 quiz.py bank.json --practice round.json --output retry.json
tide の問題だけが出ます。移動する可能性があるので、Moon の現在の表示番号を選びます。結果は Score: 1/1 answered です。独立した2つの記録を確認します。
cat round.json retry.json
元は tide/sun のまま、練習は tide/moon です。問題集は変わりません。次は全問正解のラウンドを作ります。題数は正の数で、実際の抽出は min(count, len(questions)) が上限となります。100 は3問を1回ずつ尋ねます。
python3 quiz.py bank.json --ordered --count 100 --output perfect.json
sky、tide、roots に 1、2、2 と答え、そのラウンドを練習します。
python3 quiz.py bank.json --practice perfect.json --output empty.json
No mistakes to practice.
Score: 0/0 answered
Saved empty.json
空の間違い集合も正当です。最後に新しいラウンドを始め、回答する前に q を入力します。
python3 quiz.py bank.json --output stopped.json
点数は0/0で、stopped.json の answers リストは空です。途中で離れた問題を不正解として保存してはいけません。5つの記録をすべて残してください。編集で構文エラーが生じたら、そのステップのブロック全体を再実行します。ステップ1はファイルを置き換えるので、その後ステップ2の追記ブロックも再実行します。既存の記録は保ち、追加のプレイには別名を使ってください。
まとめ
実際に送信された ID の組を保存し、元のラウンドを保ったまま独立した間違い練習を作りました。正当な空のラウンドや途中終了も扱えます。



