この記事は、GAS(Google Apps Script)とDiscordを連携させた英単語テスト自動作成システムの開発記録・第5回です。
前回の記事では、 make_test 関数の実装②について説明し、実装完了まで行いました。
今回は、現状確認と新しい関数の実装をやっていきます。
現状
前回までで作成したmake_test関数を実行すると、Discordの画面は以下のようになる。

このリンクを押すと問題なくGoogleフォームが開くが、回答を送信しても以下のような画面になり正誤の確認ができない。

これはフォームの問題に正解を設定していないせいだが、GASでは記述式の問題に対して正解を設定することができない。
よって、自動で採点してくれる関数を自分で作成する必要がある。今回からはその関数の作成を始めていく。
saiten 関数の流れ
以下のような順序で処理を実行していく関数を作る。
- フォームの取得と回答の取り出し
- 回答と正答データの比較
- 採点
- Discordへの結果送信
- スプレッドシートへの記録
- 紐づいたGoogleフォームを削除
- トリガーを設定
saiten 関数作成①
make_test 関数の時と同様、まず関数を定義する。
function saiten() {
}
スプレッドシートなどの取得
saiten 関数内で使うスプレ類を取得しておく。
//スプレッドシート取得
const ss = SpreadsheetApp.getActiveSpreadsheet();
//スプレッドシートの全てのシートを取得
const allSheets = ss.getSheets();
//スプレッドシートのシート取得
const answerSheet = ss.getSheetByName("回答収集用");
const urlSheet = ss.getSheetByName("フォームのリンクと結果");
//「回答収集用」シート1列目の最後の行を取得
const answerLastRow = answerSheet.getRange(answerSheet.getMaxRows(), 1).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
//スクリプトプロパティから保存していたフォームIDを取得する
const formId = PropertiesService.getScriptProperties().getProperty('FORM_ID');
const form = FormApp.openById(formId); //フォームIDからフォームを取得
const responses = form.getResponses(); //フォームの回答を取得
const formDescription = form.getDescription(); //フォームの説明文を取得
//スクリプトプロパティに保存していた出題問題一覧を取得する
let questionOrder = JSON.parse(PropertiesService.getScriptProperties().getProperty('PROBLEM_NUMBER'));
make_test 関数の時とほとんど取得するものは変わらないが、今回はmake_test 関数で作成したフォームの説明文、出題した問題一覧、送信された回答を追加で取得している。
ここでフォームの説明文を取得している理由
「苦手問題全問テスト」かどうかを見分けるため。
現在はまだ紹介していないが、「苦手問題全問テスト」機能を後から追加した際に同じ関数をそのまま使用すると問題があるので、if文で分岐する用。
次に採点過程で必要な変数の宣言を行う。
let correctCount = 0; //正解の問題数を記録
let incorrectCount = 0; //不正解の問題数を記録
let index = 0; //出題した問題を追跡する用のカウンタ
let incorrectDetails = []; //不正解の問でどのような間違いをしたか記録
回答処理関数の定義
さっき取得した回答を一つづつ取り出して正誤判定をしていく。
この時、テストの形式が2種類ある(通常の英単語テストと苦手問題全問テスト)ので正誤判定部分を関数化し、同じ処理を繰り返し書く手間を省く。
※この時、テストの形式による差は大きくないため、関数を使用しないで正誤判定することはもちろん可能である。しかし、今後コードに手を加えたりする際に関数化しておいた方が汎用性が高いため、あえてこの方法で記述している
function processResponses(responses, totalQuestions) {
let remainingQuestions = totalQuestions;
responses.forEach(response => {
response.getItemResponses().forEach(itemResponse => {
remainingQuestions -= 1;
const questionText = itemResponse.getItem().getTitle();
const userAnswer = itemResponse.getResponse();
const correctAnswer = answerSheet.getRange(answerLastRow - remainingQuestions, 1).getValue();
answerSheet.getRange(answerLastRow - remainingQuestions, 3, 1, 2).setValues([[userAnswer, questionText]]);
//回答が正答と等しいかどうかを確認
//合ってるとき
if (userAnswer === correctAnswer) {
answerSheet.getRange(answerLastRow - remainingQuestions, 5, 1, 1).setValue("True");
//正解した問題数のカウントを増やす
correctCount++;
const statsRange = answerSheet.getRange(questionOrder[index] + 1, 12, 1, 2);
const stats = statsRange.getValues();
stats[0][0]++;
stats[0][1]++;
statsRange.setValues(stats);
//間違っているとき
} else {
answerSheet.getRange(answerLastRow - remainingQuestions, 5, 1, 1).setValue("False");
//間違えた問題数のカウントを増やす
incorrectCount++;
const statsRange = answerSheet.getRange(questionOrder[index] + 1, 12, 1, 1);
const stats = statsRange.getValues();
stats[0][0]++;
statsRange.setValues(stats);
//間違えた問題の問題文、誤答、正答を取得
incorrectDetails.push(`\n第${totalQuestions - remainingQuestions}問 問題:${questionText} 誤:${userAnswer} 正:${correctAnswer}`);
}
//カウンタ更新
index++;
});
});
}
通常の英単語テストと苦手問題全問テストの差は出題数だけなので、その部分だけ processResponses 関数の引数として取得する。
コード解説
上記のコードでは、 「フォームの解答を取得し、回答収集用シートに書き込み、正誤判定・統計更新・誤答リスト作成を行う」 という処理を行っている。
1.関数の引数
function processResponses(responses, totalQuestions) {
-
responses:フォームの回答 -
totalQuestions:全体の問題数
2.回答データを1つずつ取り出す
responses.forEach(response => {
response.getItemResponses().forEach(itemResponse => {
remainingQuestions -= 1;
const questionText = itemResponse.getItem().getTitle();
const userAnswer = itemResponse.getResponse();
const correctAnswer = answerSheet.getRange(answerLastRow - remainingQuestions, 1).getValue();
-
forEachでresponsesから、各設問・回答をitemResponseとして取り出す - 問題を処理したら
remainingQuestionsを1減らし、「回答収集用シートに書き込む行番号」を決める -
questionText:フォームに表示された問題文 -
userAnswer:ユーザーがフォームで入力した回答 -
correctAnswer:回答収集シートに入力していた正答
3.回答収集シートに書き込む
answerSheet.getRange(answerLastRow - remainingQuestions, 3, 1, 2).setValues([[userAnswer, questionText]]);
-
answerLastRow-remainingQuestionsで入力すべき行番号を計算
-
make_test関数実行後、「回答収集用シート」は上のようになっているが、取得した回答は1問目からなのでこの処理が必要 - 入力されるのは「回答」と「問題、課題」の列
4.正誤判定・正解時
if (userAnswer === correctAnswer) {
answerSheet.getRange(answerLastRow - remainingQuestions, 5, 1, 1).setValue("True");
//正解した問題数のカウントを増やす
correctCount++;
const statsRange = answerSheet.getRange(questionOrder[index] + 1, 12, 1, 2);
const stats = statsRange.getValues();
stats[0][0]++;
stats[0][1]++;
statsRange.setValues(stats);
- 「回答収集用」シートの「正誤」の列に
"True"を入力する - 「出題回数」「正解回数」などの情報を更新&シートに入力
5.正誤判定・不正解時
} else {
answerSheet.getRange(answerLastRow - remainingQuestions, 5, 1, 1).setValue("False");
//間違えた問題数のカウントを増やす
incorrectCount++;
const statsRange = answerSheet.getRange(questionOrder[index] + 1, 12, 1, 1);
const stats = statsRange.getValues();
stats[0][0]++;
statsRange.setValues(stats);
//間違えた問題の問題文、誤答、正答を取得
incorrectDetails.push(`\n第${totalQuestions - remainingQuestions}問 問題:${questionText} 誤:${userAnswer} 正:${correctAnswer}`);
}
- 正誤を入力・情報更新などは正解時とほぼ同じ
- 第何問目を間違えたのか、間違えた問題、自分の誤答、正答を取得(Discordで送信する用)
さいごに
今回はmake_test関数実装後の動作確認と、saiten関数の定義から正誤処理を行う部分まで実装しました。
次回はsaiten関数の実装完了までと、トリガーなどについて書いていきます。
