見出し画像

【紙からデジタルへ!】自動採点&タイマー付き「百ます計算」で算数をつよつよに!!🌝

こんにちは!
3学期も残すところわずかとなりました。
クラスの雰囲気はとても良く、私はものすごく楽です笑
3年生ってここまでできるんだ、と感動しています。

さて、前回の「GASで算数の問題自動作成&自動採点フォームをつくろう」の記事をたくさん見ていただけたので、需要があるのかと思って新作を作ってみました。
前回の記事はこちら↓↓↓


その名も「デジタル百ます計算」です!!!
ずっと、あったらいいなって思っていたのですが、なかなか良いものが見当たらず、それなら作っちゃえということでノリと勢いで作りました。
なかなかうまくできたと思うので共有します。



はじめに:そもそも百ます計算って何がイイの?

「百ます計算」は、縦と横に並んだ数字を順番に足し算(掛け算などもあり)していく、いわゆる計算スピードと正確さを身につけるための練習メニューです。
先生方にとってはお馴染みのものだと思います。

子どもにとって何がいいかというと:

1.計算の定着をスピード重視で図れる
縦横の数字をどんどん足していくので、繰り返しの中で暗算力や計算スピードが着実に身につく。「この答えはもう見なくてもわかる!」みたいに、コツコツと計算回路を鍛えられるんですよね。「1+1=2」なんて私たちはいちいち計算していません。見た瞬間答えが浮かびます。要は暗記しているってことです。これが一桁同士の足し算全てでできるようになれば、今後一生重宝します。大人は当たり前にできることですが、そもそもここから躓いている子どもは多いです。

計算力が高まると、算数・数学の問題で「式を立てるのが難しい」場合でも、最後に待っている計算部分を高速で処理できます。
問題文が長くても、方程式が複雑でも、
計算力がある=最終的な処理がスムーズなので、結果的に全体を解くスピードと正確性がグッと上がるんです。

2.集中力・自信の向上
たくさんのマスを素早く埋める体験は、子どもたちにとってわかりやすい成功体験です。「今日はここまで速くできた!」という実感が積み重なると、計算に対しての自信だけでなく、算数全般へのポジティブな感情にもつながります。また、単純な計算は集中力も高めてくれます。朝イチにできれば良いスタートを切れます。

3.シンプルな仕組みで学年や特性に合わせやすい
これもでかいです。足し算でも掛け算でも、マス目の大きさを変えれば難易度の調整が自在。学習到達度や特性に合わせた“ちょうどいいレベル”で取り組ませやすいんです。

このあたりの「百ます計算のメリット」に関しては広く知られている部分だと思います。
では、こんなにたくさんのメリットがあるのにどうしてみんなやらないのか。

そこには大きな課題があるからですよね。
私はその課題をズバリ紙でしているから、という側面から考えました。


紙のままだとハードルが高い理由

  1. 問題を作るのが圧倒的に面倒
    毎回同じ問題では子どもたちが飽きるので、先生がわざわざ並び替えたり、ExcelやWordで作り直したり…。「ランダムな数字を生成したいけど、どうやって効率化したらいいの?」と頭を抱える場面も多いはず。

  2. 採点に時間がかかる
    とにかくマスの数が多いので、全部一人ひとり手作業でチェックすると結構な労力。問題数が増えれば増えるほど、先生の放課後や休日を蝕む恐れも…。かといって子どもたちに採点させるのも時間がかかるしミスも多い…。授業の時間が減りすぎるのも避けたいですよね。

  3. タイマーを使うときも手動管理
    「あと何秒だよ~」と声をかけるか、ストップウォッチを持って回るか…地味に授業の流れを止めてしまいがち。ほんの些細なことでも繰り返しになると馬鹿にできない手間です。また、早めに終わった人も、遅い人を待つ無駄な時間が流れます。

そんなこんなで、「百ます計算はいいけど、続けるのが大変…」というのが、割と多くの先生のリアルな本音ではないでしょうか。


そこで登場! デジタル化でラク&楽しく

こうした紙方式の苦労をまとめて解決するために、Google Apps Script(以下GAS)を使って「百ます計算ツール」を作ってみました。
しかも、「ノーコード」レベルでもAIの力を借りながら作れちゃうんです。
私は何も分かっていませんが、なんかできました笑
完成形を見ると、次のようなメリットがあります。

  1. ワンクリックで毎回違う問題
    0~9の数字をランダムにシャッフル。子どもたちにとっては「今日もまた同じ並びか~」がなくなるし、先生にとってはわざわざ手動で組み合わせを変える苦労がゼロ!「新しい問題」ボタンを押すだけで、一瞬で新しい百ます計算が目の前に!印刷の手間もゼロにできます。

  2. 自動採点で先生の時間を節約
    終わったら「採点ボタン」をクリックするだけ。正答はそのまま、間違っていれば赤くハイライトされて正解も表示されるので、子ども本人が間違いをすぐ振り返れるのも大きなメリット。間違った問題も、子どもの誤答と正答を下に一覧でまとめてくれます。

  3. マス目(グリッド)の大きさも自由
    5×5~10×10まで選べるので、低学年や特性を考慮して小さめにしたり、慣れてきたら本物の10×10(百ます)でチャレンジさせたり。誰でも自分に合った難易度で取り組めます。

  4. タイマー機能で集中度UP
    画面に残り時間が表示されて、時間が来ると自動採点。わざわざ「あと◯秒!」と連呼する必要もなくなり、全員がサクサク進めやすい。また、自分でタイムを設定できるので、パーソナライズされた学習を提供できます。

  5. 演出付きで子どもたちも楽しく

    • 満点ならランダムメッセージ(「花丸!」「めっちゃええやん!!」「ヤルジャナイカ」など)がド派手に表示される。1%の超レアメッセージもあって、ガチャみたいな楽しさ!(ここは好きに変えてください笑)

    • 間違いがあると紙吹雪 が舞って盛り上がる。
      ちょっとしたゲーム感覚で、子どもが飽きずに取り組めるんです。意外とこういう仕掛けって、特に小学校だと大事ですよね。


子どもは抜け道を探す天才? テスターとして最強説

このツール、実は子どもたちに「ズルしてみて」とテストをお願いしたら、驚くほどバグを見つけてくれて完成度がグッと上がりました。

  • 「先生! このボタン押したら時間リセットできるやん。ゲヘヘ…」

  • 「タイマーがめっちゃ早く進んでしまう!!あわわ…」

  • 「時間設定をめちゃ増やしたら余裕やん!ふひひひ…」

  • 「あれ? 入力欄が動かない!ラララ…」

子どもってズルする方向の想像力がすごいので、テスターとして最強なんですよね。
先生が全然気づかなかった隙や、思わぬ動作ミスを教えてくれます。
結果、子どものやる気も上がるし、テストされる側も意外と楽しい。
ウィンウィン!
指摘してもらった点はそのままAIに丸投げ。
すぐにいい感じに直してくれます✨️


ざっくりコード紹介:「80点から100点に仕上げる」のが楽!

「でもプログラムとか無理」と思ったら、AIに部分的にやらせて構いません。

  1. 私が用意した下記のコード例をそのままコピペして動かしてみる

  2. 演出を変えたい、問題数を増やしたいなどの要望があれば、「ここをこう変えて」とAIに相談する

  3. 出てきたコードをマージ(組み合わせ)すれば、意外とすぐ理想に近づく

最初から完璧を目指さなくても、「おおむね動くサンプル(80点)を使って、足りない20点をAIや子どものテストで詰める」ほうが圧倒的に楽です。


具体的なコード例:Google Apps Scriptを使ってみる

百ます計算ツールのベースとなるコードを紹介します。「こんな風になっているんだ」とざっくり眺めてみてください。

手順の流れ

  1. Google Apps Script の新規プロジェクトを作成

    • ブラウザで script.google.com を開き、新しいプロジェクトを作成。

    • ドライブ→新規→その他→Google Apps Script でもいけます。

  2. ファイルを追加して貼り付け

    • 「ファイルを追加」から Code.gs、index.html を作成(2つのコードを別々に入力します。)

    • 以下のコードをそれぞれコピペして貼り付けます。

  3. デプロイ(ウェブアプリとして公開)

    • 右上の「デプロイ」ボタンを押し、「新しいデプロイ」を選択

    • タイプを「ウェブアプリ」にして、アクセス権を「全員」にしておく(学校のドメインなどで制限がある場合は管理者の先生に相談!)

    • デプロイするとURLがもらえるので、そのリンク先にアクセスすれば動作確認ができます。

    • リンクをClassroomなどにあげれば完成です。

  4. 好きにカスタマイズ

    • 「満点時のメッセージをもっと増やしたい」「掛け算バージョンにしたい」など、興味があればAIやネットで調べつつ変更してみましょう。


Code.gs

// Code.gs
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('index')
      .setTitle('百ます計算 - 一桁足し算');
}

function generateProblems() {
  // 0~9の配列を作成
  var digits = [];
  for (var i = 0; i < 10; i++) {
    digits.push(i);
  }
  // headerRowとheaderColに、それぞれシャッフル済みの数字をセット(重複なし)
  var headerRow = shuffleArray(digits.slice());
  var headerCol = shuffleArray(digits.slice());
  
  return { headerRow: headerRow, headerCol: headerCol };
}

// Fisher-Yatesシャッフルアルゴリズムで配列をランダムに並べ替える
function shuffleArray(array) {
  for (var i = array.length - 1; i > 0; i--) {
    var j = Math.floor(Math.random() * (i + 1));
    var temp = array[i];
    array[i] = array[j];
    array[j] = temp;
  }
  return array;
}

index.html

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>百ます計算 一桁足し算</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        padding: 10px;
      }
      h2 {
        margin-bottom: 10px;
      }
      .controls {
        margin-bottom: 10px;
      }
      .timer {
        font-size: 18px;
        font-weight: bold;
      }
      table {
        border-collapse: collapse;
        margin-bottom: 20px;
        width: 100%;
      }
      th, td {
        border: 1px solid #ddd;
        padding: 8px;
        text-align: center;
        vertical-align: middle;
      }
      th {
        background-color: #f0f0f0;
        font-weight: bold;
      }
      input.answer {
        width: 40px;  /* 狭くして数字入力に集中 */
        font-size: 16px;
        text-align: center;
        transition: background-color 0.5s ease;
      }
      .note {
        font-size: 12px;
        margin-top: 4px;
        color: #fff;
      }
      .halfWidthWarning {
        font-size: 10px;
        color: red;
        margin-top: 2px;
      }
      #result {
        margin-top: 20px;
        font-size: 28px;
        font-weight: bold;
      }
      #errorSummary {
        margin-top: 20px;
        font-size: 16px;
      }
      button {
        font-size: 16px;
        margin-right: 10px;
      }
      /* タイマー設定用の入力欄スタイル */
      input.timerInput {
        width: 40px;
        font-size: 16px;
        text-align: center;
        margin: 0 5px;
      }
    </style>
  </head>
  <body>
    <h2>百ます計算 一桁足し算</h2>
    <div class="controls">
      <button onclick="confirmNewProblems()">新しい問題</button>
      <!-- タイマー設定用の入力欄 -->
      <label>
        タイマー設定: 
        <input id="minutesInput" class="timerInput" type="number" value="5" min="0">分
        <input id="secondsInput" class="timerInput" type="number" value="0" min="0" max="59">秒
      </label>
      <!-- タイマー開始ボタンに id を追加 -->
      <button id="startTimerButton" onclick="startTimer()">タイマー開始</button>
      <span class="timer" id="timerDisplay">残り時間: --:--</span>
    </div>
    <table id="problemTable"></table>
    <button onclick="gradeProblems()">採点</button>
    <div id="result"></div>
    <div id="errorSummary"></div>
    
    <script>
      let headerRow = [];
      let headerCol = [];
      let timerTimeout;  // setTimeoutの識別子
      let timeRemaining = 0;  // ユーザー設定の残り秒数
      let timerRunning = false; // タイマーが動作中かどうかのフラグ
      
      // 生成されたヘッダー数字を元に、10x10の問題表(入力欄付き)を作成
      function populateTable(data) {
        headerRow = data.headerRow;
        headerCol = data.headerCol;
        
        let table = document.getElementById("problemTable");
        table.innerHTML = "";
        
        // 上側のヘッダー行生成(左上は空白)
        let headerRowElem = document.createElement("tr");
        let emptyTh = document.createElement("th");
        emptyTh.textContent = "";
        headerRowElem.appendChild(emptyTh);
        for (let c = 0; c < headerRow.length; c++) {
          let th = document.createElement("th");
          th.textContent = headerRow[c];
          headerRowElem.appendChild(th);
        }
        table.appendChild(headerRowElem);
        
        // 各行の生成:行の先頭に左側の数字、その後に入力欄
        let inputIndex = 0;
        for (let r = 0; r < headerCol.length; r++) {
          let row = document.createElement("tr");
          let th = document.createElement("th");
          th.textContent = headerCol[r];
          row.appendChild(th);
          for (let c = 0; c < headerRow.length; c++) {
            let cell = document.createElement("td");
            let input = document.createElement("input");
            input.type = "text";
            input.className = "answer";
            input.setAttribute("data-index", inputIndex);
            // 正解は「左の数字 + 上の数字」
            let correctAnswer = headerCol[r] + headerRow[c];
            input.setAttribute("data-answer", correctAnswer.toString());
            input.setAttribute("aria-label", "問題 " + (inputIndex+1) + ": " + headerCol[r] + " + " + headerRow[c] + " = ?");
            
            // キーボード操作でセル間移動(Enter, 矢印キー対応)
            input.addEventListener("keydown", function(e) {
              const currentIndex = parseInt(this.getAttribute("data-index"));
              if (e.key === "Enter" || e.key === "ArrowRight") {
                e.preventDefault();
                moveFocus(currentIndex + 1);
              } else if (e.key === "ArrowLeft") {
                e.preventDefault();
                moveFocus(currentIndex - 1);
              } else if (e.key === "ArrowDown") {
                e.preventDefault();
                moveFocus(currentIndex + 10);
              } else if (e.key === "ArrowUp") {
                e.preventDefault();
                moveFocus(currentIndex - 10);
              }
            });
            
            // 入力イベントで半角チェック
            input.addEventListener("input", function(e) {
              // 全角数字(0~9)のUnicode範囲: \uFF10 - \uFF19
              if (/[\uFF10-\uFF19]/.test(this.value)) {
                if (!this.parentElement.querySelector(".halfWidthWarning")) {
                  let warning = document.createElement("div");
                  warning.className = "halfWidthWarning";
                  warning.textContent = "半角で入力してください";
                  this.parentElement.appendChild(warning);
                }
              } else {
                let warning = this.parentElement.querySelector(".halfWidthWarning");
                if (warning) {
                  warning.remove();
                }
              }
            });
            
            cell.appendChild(input);
            row.appendChild(cell);
            inputIndex++;
          }
          table.appendChild(row);
        }
      }
      
      // 指定されたインデックスの入力欄にフォーカス移動
      function moveFocus(index) {
        var nextInput = document.querySelector('input[data-index="' + index + '"]');
        if (nextInput) {
          nextInput.focus();
        }
      }
      
      // 「新しい問題」ボタン押下時に確認ダイアログ表示
      function confirmNewProblems() {
        if (confirm("新しい問題を生成します。よろしいですか?")) {
          newProblems();
        }
      }
      
      // GASのgenerateProblems()を呼び出して、新しいヘッダー数字を取得・テーブル再描画、タイマーリセット
      function newProblems() {
        google.script.run.withSuccessHandler(function(data) {
          populateTable(data);
          clearResult();
          resetTimer();
        }).generateProblems();
      }
      
      // 採点処理(タイマー停止も)
      function gradeProblems() {
        stopTimer();
        var inputs = document.querySelectorAll(".answer");
        let correctCount = 0;
        let errors = [];
        inputs.forEach(function(input, index) {
          let userAnswer = input.value.trim();
          let correctAnswer = input.getAttribute("data-answer");
          let parent = input.parentElement;
          let existingNote = parent.querySelector(".note");
          if (existingNote) {
            existingNote.remove();
          }
          if (userAnswer === correctAnswer) {
            correctCount++;
            input.style.backgroundColor = "";
          } else {
            input.style.backgroundColor = "red";
            let note = document.createElement("div");
            note.className = "note";
            note.textContent = "誤答: " + userAnswer + " 正答: " + correctAnswer;
            parent.appendChild(note);
            errors.push("問題 " + (index+1) + ": " + headerCol[Math.floor(index/10)] + " + " + headerRow[index % 10] + " → 誤答: " + userAnswer + " / 正答: " + correctAnswer);
          }
        });
        document.getElementById("result").textContent = "正解数: " + correctCount + " / 100";
        displayErrorSummary(errors);
      }
      
      // 誤答一覧を表示
      function displayErrorSummary(errors) {
        var summaryDiv = document.getElementById("errorSummary");
        summaryDiv.innerHTML = "";
        if (errors.length > 0) {
          let heading = document.createElement("h3");
          heading.textContent = "誤答一覧";
          summaryDiv.appendChild(heading);
          let list = document.createElement("ul");
          errors.forEach(function(err) {
            let listItem = document.createElement("li");
            listItem.textContent = err;
            list.appendChild(listItem);
          });
          summaryDiv.appendChild(list);
        }
      }
      
      // 採点結果と誤答一覧をクリア
      function clearResult() {
        document.getElementById("result").textContent = "";
        document.getElementById("errorSummary").innerHTML = "";
      }
      
      // タイマー関連の処理
      // startTimer() では setTimeout を利用して1秒ごとに正確にカウントダウンし、タイマー開始後は1問目にフォーカス移動
      function startTimer() {
        if (timerRunning) {
          // 既にタイマーが動作中の場合は何もしない
          return;
        }
        timerRunning = true;
        // 入力欄からタイマー設定を取得
        let minutes = parseInt(document.getElementById("minutesInput").value, 10);
        let seconds = parseInt(document.getElementById("secondsInput").value, 10);
        if (isNaN(minutes)) minutes = 0;
        if (isNaN(seconds)) seconds = 0;
        timeRemaining = minutes * 60 + seconds;
        updateTimerDisplay();
        countDown();
        // タイマー開始後、1問目の入力欄にフォーカスを移動
        let firstInput = document.querySelector('input[data-index="0"]');
        if (firstInput) {
          firstInput.focus();
        }
      }
      
      function countDown() {
        if (timeRemaining <= 0) {
          gradeProblems();
        } else {
          timerTimeout = setTimeout(function() {
            timeRemaining--;
            updateTimerDisplay();
            countDown();
          }, 1000);
        }
      }
      
      function stopTimer() {
        if (timerTimeout) {
          clearTimeout(timerTimeout);
          timerTimeout = null;
        }
        timerRunning = false;
      }
      
      // タイマーリセット:新しい問題生成時にタイマー設定欄の値を反映
      function resetTimer() {
        stopTimer();
        let minutes = parseInt(document.getElementById("minutesInput").value, 10);
        let seconds = parseInt(document.getElementById("secondsInput").value, 10);
        if (isNaN(minutes)) minutes = 0;
        if (isNaN(seconds)) seconds = 0;
        timeRemaining = minutes * 60 + seconds;
        updateTimerDisplay();
      }
      
      function updateTimerDisplay() {
        let minutes = Math.floor(timeRemaining / 60);
        let seconds = timeRemaining % 60;
        let display = ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2);
        document.getElementById("timerDisplay").textContent = "残り時間: " + display;
      }
      
      // タイマー設定入力欄にもキーボード操作で移動できるように設定
      window.onload = function() {
        newProblems();
        
        // 分の入力欄:Enterまたは右矢印で秒の入力欄、下矢印でタイマー開始ボタンへ
        document.getElementById("minutesInput").addEventListener("keydown", function(e) {
          if (e.key === "ArrowRight" || e.key === "Enter") {
            e.preventDefault();
            document.getElementById("secondsInput").focus();
          } else if (e.key === "ArrowDown") {
            e.preventDefault();
            document.getElementById("startTimerButton").focus();
          }
        });
        // 秒の入力欄:左矢印で分の入力欄、Enterまたは下矢印でタイマー開始ボタンへ
        document.getElementById("secondsInput").addEventListener("keydown", function(e) {
          if (e.key === "ArrowLeft") {
            e.preventDefault();
            document.getElementById("minutesInput").focus();
          } else if (e.key === "Enter" || e.key === "ArrowDown") {
            e.preventDefault();
            document.getElementById("startTimerButton").focus();
          }
        });
        // 秒の入力欄:入力値が59を超えたら59に補正する
        document.getElementById("secondsInput").addEventListener("input", function(e) {
          let val = parseInt(this.value, 10);
          if (!isNaN(val) && val > 59) {
            this.value = 59;
          }
        });
      };
    </script>
  </body>
</html>

ポイント:

  • gridSizeSelect で5×5から10×10まで選択可能

  • タイマーは「分」と「秒」を入力して、startTimer 関数でカウントダウン&時間切れと同時に採点

  • gradeProblems 関数で採点し、不正解は赤く塗って正解表示、全問正解ならメッセージ&間違いありなら花火をドーン!

  • 開発者用ショートカットとして、「ctrl+o」ですべての欄に正答が表示されるようにしています。試すときに使ってみてください。

これで百ます計算の紙の手間や採点時間が劇的に減り、子どもたちのモチベーションも上がるという、いいことづくめの仕組みが完成します。


コードのカスタマイズ:AIに聞いちゃうのが手っ取り早い

「自分でコードを見ると頭が痛い…」と思っても、今はChatGPTなどのAIが質問に答えてくれる時代です。

  • 「ここに掛け算の機能を追加したい」と相談

  • 「演出をもうちょっと派手にできる?」と聞いてみる

  • 「配列の範囲を変えたいだけなんだけど…」とリクエスト
    すると、それらしいコードが提案されます。
    あとはコピペして、動かしてみながら微調整すればOK。

  • エラーが出ても、子どもテスターをはじめ先生仲間とワイワイ「どこがダメ?」と探っているうちに、けっこう簡単に克服できますよ。


「自分だけのカスタマイズ」を楽しもう

  • 演出のメッセージを変える(「Good!」「アメイジング!」などを増やす)

  • 背景色や文字フォントを変更してポップなデザインに

  • 足し算以外にも拡張(かけ算・わり算など)

こういうアイデアを思いついたら、コードの該当箇所をちょこっといじればOK。
もし分からなければ、コードをAIにコピペして「こんな風に変えて」と頼めば、大体のヒントを出してくれます。
最初の80点はこのサンプルのままでも十分使えるので、そこから自分好みの100点
に仕上げるほうが断然楽ですよ。


まとめ:高速計算力で「算数嫌いを減らす」&「先生がラクする」一石二鳥!

  • 百ます計算は計算速度と正確性を鍛えるのに最適で、算数全般の底力を上げる武器になる。

  • 紙ベースでやると先生が大変だけど、デジタル化すれば問題作成~採点~タイマー管理までワンクリック。

  • 子どもにもわかりやすい演出を入れると「ゲーム感覚」で積極的に取り組むようになるし、間違いの振り返りもしやすい。

  • 子どもがズルを発見してくれる→ それを修正して良いツールになる→ さらに子どもの計算力が伸びる…の好循環。

  • 「コードは難しい」と思っても、AIがかなり手助けしてくれるし、一度“形になる”とアレンジも楽しい。

  • 先生の時間を減らしつつ、子どもの学習効果は落とさないどころかむしろ上げられるので、試さない手はない!

「やってみる価値は絶対ある!」 そう感じたら、まずはこの記事に載せたコードをコピペして、Google Apps Scriptで走らせてみてください。
動くものが目の前にあるだけでも、かなり面白いですよ。
そして慣れてきたら、ぜひ子どもたちやAIを巻き込んでカスタマイズ!
先生も子どもたちも「計算やるのがちょっと楽しい!」と思える日がきっと来るはずです。


これを機に、ぜひスピード計算力の武器を子どもたちに授けつつ、先生自身もデジタル化による手間削減でラクをして、空いた時間をほかのクリエイティブな授業に回していきましょう!

ここまで読んでくれてありがとうございました!!
充実した年度末をお迎えしましょうね笑

いいなと思ったら応援しよう!