はじめに
選択肢の位置が変わっても正しく採点できる、実際に遊べるローカルクイズを作ります。Python の関数、リスト、辞書、条件分岐、ループが前提です。JSON、ランダムな選択、入力検証はこのラボで説明します。用意されたターミナルエンジンが表示とフィードバックを担当し、あなたは logic.py に3つのルールを実装します。
WebIDE の Terminal メニューから New Terminal を開きます(狭い画面では More 内)。すべてのコマンドをそのターミナルで実行します。engine.py、quiz.py、demo.py、bank.json は変更せず、logic.py だけを編集してください。読解と練習の所要時間は推定40分で、初心者による実測値ではありません。
問題を選び、選択肢をオブジェクトごと移動する
このステップでは、順序の分かる2問を遊んでから、識別情報を失わずにランダム抽出を追加します。WebIDE ターミナルでは、cd が作業ディレクトリを変更し、python3 が Python ファイルを実行します。
cd /home/labex/project
python3 demo.py
Sun には 1、続いて Moon には 2 を入力します。どちらも Correct! と表示されます。この固定順のデモは準備済みです。これから作る再利用可能なクイズでは選択肢も移動します。
cat はテキストファイルを表示します。
cat bank.json
JSON ではリストを []、辞書を {} で表し、文字列を二重引用符で囲みます。各問題に question_id、prompt、options、correct_option_id があり、各選択肢は option_id と text の組です。ID は安定した識別子ですが、画面の番号は位置です。sky と tide の両方に Sun/Moon があっても、Sun が正解なのは sky だけです。選択肢 ID が一意なのは同じ問題の中だけなので、識別には問題と選択肢の組を使います。提供する問題集では問題 ID、および各問題内の選択肢の文字列は一意です。
rng は渡される乱数生成器オブジェクトです。sample(items, count) は要素を重複して選ばない新しいリストを返しますが、重複する問題 ID を修正する機能ではありません。shuffle(items) はリストを直接変更するため、先に選択肢をコピーします。辞書全体を移動し、ID と文字列の組を保ちましょう。要求数は正の数とし、min(count, len(questions)) で実際の抽出数を問題集の大きさまでに制限します。乱数は遊びの変化のためで、安全性を保証するものではありません。同等の抽出・並べ替え方法も使え、固定シードや決まった順序は要求しません。
次の here-document で Python のテキストを書きます。> は logic.py を置き換え、引用符付きの <<'PY' は末尾の PY までを文字どおり書き込みます。最後の行も含むブロック全体を実行してください。初期ファイルをこの2関数で置き換え、入力関数は次のステップで追加します。
cat > logic.py <<'PY'
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
PY
これで choose_questions は上限のある抽出結果を返し、move_options は問題集を変更せずに並べ替えた選択肢を返します。ターミナルが継続入力を待っていたら、最後の PY を単独の行に入力してください。
有効な番号を選択肢 ID に戻す
このステップでは、画面の選択を安定した選択肢 ID に変換します。用意されたエンジンが ask(入力)と tell(出力)の関数を渡します。ask はテキストを返し、strip() は前後の空白を取り除きます。enumerate(..., start=1) は各オブジェクトに表示番号を対応付けます。テキストを str(position) と比較すれば、oops、-1、0、範囲外の番号では回答を送信できません。表示どおりの表記のみ有効です。1 は使えますが 01 は使えません。q は送信前に終了します。有効な回答を一度返し、それ以外は while ループで再度尋ねます。
同じターミナルで、>> を使い、前の2関数を置き換えずにこの関数を追記します。
cat >> logic.py <<'PY'
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.")
PY
用意された全問題を遊びます。--count は正の最大数を指定します。100 を指定しても、問題集の3問だけを選びます。--ordered を付けなければ、問題と選択肢の位置は変わります。
python3 quiz.py bank.json --count 100
最初の入力で oops、-1、0、99 を試します。毎回 Enter one displayed number, or q to stop. が表示され、同じ問題が再び尋ねられます。その後は文字列を読んで全問に答えてください。恒星は Sun、潮の満ち引きは Moon、土の水を吸う部分は Root です。毎回、表示された番号を読みます。選択に合うフィードバックが出て、最後は次の結果になります。
Score: 3/3 answered
短いラウンドを開始し、すぐに q を入力します。
python3 quiz.py bank.json --count 2
Score: 0/0 answered
未回答の問題は含まれず、不正解とはなりません。エンジンが一度だけ送信し、現在の問題と選択肢の関係からフィードバックと点数を決めます。画面番号は保存しません。ファイル名を間違えたときの Quiz error: ... と非ゼロ終了は、ゲームが正常に完了していないことを示します。ls で名前を確認し、上の正確なコマンドを実行し直してください。Python の構文エラーなら、該当する here-document 全体をやり直し、環境はリセットしないでください。
まとめ
有限のランダム抽出と安定した回答の対応付けを備えた、再利用して遊べるクイズを作りました。この単元では記録を保存しません。



