文字がランダムに表示してマッチした文字を当てるアプリで、コードを参考にして頂いてお役に立てば幸いです。

1.表示したいローマ字、ひらがな、カタカナの各選択

2.ランダムで止める対象の文字を表示

3.文字表示の領域枠

4.スタート・ストップ・クリアの各ボタン

5.正解・不正解・正解率の表示

6.表示スピード調整(プログレスバー)

7.メッセージ表示

ページの最後にデモサンプルのリンクを設けています

尚、デモサイト(このページの一番下付近にリンク)でPLAYできますが、MOMO企画は本アプリをフリーゲームサイト等などに公式に一般公開していません。文字ストップの動作確認とソースコードの公開が本来の目的なので、ユーザー様がコピペして一般公開する事は許可致しませんのでご了承ください。ただし、JavaScriptのプログラム全体のコピペと画面を全面変更して公開する事はこの限りではありません。

文字ストップ起動画面

JavaScriptのプログラム部分は詳細なコメント表示しています

統合されたHTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文字ストップ</title>
<style>
* {box-sizing: border-box;}
html, body { width: 100%; height: 100%; margin: 0; }
body { margin: 0; padding: 24px 16px; font-family: "Meiryo", "MS Gothic", sans-serif; background: #7b7c7c; color: #1f2937; text-align: center; }
.game { width: min(100%, 520px); margin: 0 auto; padding: 24px; background: #e3f7d7; border: 1px solid #d1d5db; border-radius: 16px; box-shadow: 0 8px 24px #0a0a0ad2; }
h1 { color: #fff;text-shadow: 0 0 5px #ff007f,0 0 10px #ff007f,0 0 20px #ff007f; }
.label { margin: 14px 0 8px; font-weight: 700; }
#target { display: inline-flex; align-items: center; justify-content: center; min-width: 88px; min-height: 76px; padding: 4px 18px; border: 2px solid #f59e0b; border-radius: 12px; color: #b45309; font-size: 2.6rem; font-weight: 800; }
#letterBox { display: flex; align-items: center; justify-content: center; width: 100%; height: 230px; margin: 10px 0 16px; border: 3px solid #64748b; border-radius: 12px; background: #f8fafc; }
#letter { font-family: Consolas, "Courier New", monospace; font-size: clamp(5rem, 25vw, 11rem); line-height: 1; font-weight: 800; color: #2563eb; }
.buttons { display: flex; justify-content: center; gap: 12px; }
button { min-width: 130px; padding: 13px 22px; border: 0; border-radius: 9px; color: white; font-size: 1rem; font-weight: 700; cursor: pointer; }
button:disabled { opacity: .45; cursor: not-allowed; }
#startButton { background: #16a34a; }
#stopButton { background: #dc2626; }
.stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 22px; }
.storage-buttons { grid-column: span 3; margin-top: 4px; }
#clearAllButton { width: 100%; background: #dc2626; }
.stat { padding: 12px 6px; border: 1px solid #d1d5db; border-radius: 10px; background: #f9fafb; }
label { color: #0a2adf; font-size: 22px; font-weight: bold; }
.change { color: #b45309; font-size: 1rem; font-weight: 800; }
#characterType { appearance: none; -webkit-appearance: none; width: 100%; max-width: 220px; padding: 12px 42px 12px 16px; font-family: "Meiryo", "MS Gothic", sans-serif; font-size: 15px; font-weight: 600; color: #243b53; background-color: #f5f9ff; border: 1px solid #b8cce5; border-radius: 10px; cursor: pointer; outline: none; transition: border-color 0.2s, box-shadow 0.2s, background-color 0.2s; }
#letter.latin { font-size: clamp(6rem, 28vw, 12rem) !important; }
#characterType:hover { background-color: #eaf3ff; border-color: #729bd0; }
#characterType:focus { border-color: #4285d4; box-shadow: 0 0 0 3px rgba(66, 133, 212, 0.18); }
#characterType option { padding: 10px; font-family: "Meiryo", "MS Gothic", sans-serif; font-size: 15px; color: #243b53; background-color: #ffffff; }
.stat-label { font-size: .85rem; color: #4b5563; }
.stat-value { margin-top: 5px; font-size: 1.45rem; font-weight: 800; }
.speed-area { margin-top: 24px; text-align: left; }
.speed-label { display: flex; justify-content: space-between; gap: 12px; }
input[type="range"] { width: 100%; margin: 14px 0 4px; accent-color: #2563eb; }
.speed-hints { display: flex; justify-content: space-between; color: #6b7280; font-size: .85rem; }
#result { min-height: 1.8em; margin: 2px 0 0; font-size: 1.2rem; font-weight: 800; }
.correct { color: #15803d; }
.incorrect { color: #b91c1c; }
@media (max-width: 420px) {.game {padding: 18px 14px;}#letterBox {height: 190px;}.buttons button {min-width: 0;flex: 1;}}
</style>
</head>
<body>
<main class="game">
  <h1>文字ストップ【サンプル用】</h1>

  <label for="characterType">文字の選択:</label>
  <select id="characterType">
    <option value="romaji">ローマ字(A~Z)</option>
    <option value="hiragana">ひらがな(あ~ん)</option>
    <option value="katakana">カタカナ(ア~ン)</option>
  </select>

  <div class="label">止める対象の文字</div>
  <div id="target" aria-live="polite">A</div>

  <div id="letterBox" aria-label="切り替わる文字">
    <span id="letter">A</span>
  </div>

  <div class="buttons">
    <button id="startButton" type="button">スタート</button>
    <button id="stopButton" type="button" disabled>ストップ</button>
  </div>

  <section class="stats" aria-label="ゲーム成績">
    <div class="stat">
      <div class="stat-label">正解回数</div>
      <div class="stat-value" id="correctCount">0回</div>
    </div>
    <div class="stat">
      <div class="stat-label">不正解回数</div>
      <div class="stat-value" id="incorrectCount">0回</div>
    </div>
    <div class="stat">
      <div class="stat-label">正解率</div>
      <div class="stat-value" id="accuracy">0%</div>
    </div> 
    <div class="storage-buttons">
      <button id="clearAllButton" type="button">
       正解回数・ 不正解回数・正解率のクリア
      </button>
    </div>
  </section>

  <div class="speed-area">
    <div class="speed-label">
      <div class="change">切り替えスピード</div>
      <strong id="speedText">高速</strong>
    </div>
    <input id="speed" type="range" min="1" max="100" value="70">
    <div class="speed-hints"><span>低速</span><span>高速</span></div>
  </div>

  <p id="result" role="status" aria-live="polite">スタートを押してください</p>
</main>

<script>
"use strict";

/*
==================================================
文字ストップゲーム
JavaScriptのプログラム
==================================================

【主な機能】
・ローマ字、ひらがな、カタカナの切り替え
・文字のランダム表示
・ゲームの開始と停止
・正解、不正解の判定
・正解率の計算
・成績の自動保存と読み込み
・成績の一括クリア

JavaScriptのコメントは、プログラムの説明を書くためのもの。
コメント自体はプログラムの動作に影響しない。

// 1行のコメント:行末までがコメント
/* 複数行のコメント:この形式で囲んだ範囲がコメント */

// --------------------------------------------------
// 1. 使用する文字を定義する
// --------------------------------------------------

/*
const は、変数を宣言するためのキーワード。
ここでは、文字の種類と文字列をまとめたオブジェクトを定義する。

CHARACTERS.romaji   → ローマ字
CHARACTERS.hiragana → ひらがな
CHARACTERS.katakana → カタカナ

オブジェクトは、関連するデータを名前付きでまとめる仕組み。

なお、const で宣言したオブジェクトでも、
その中のデータを書き換えること自体は可能。
このプログラムでは、定義した文字列を参照して使用する。
*/
const CHARACTERS = {
  romaji: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",

  hiragana: "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",

  katakana: "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン"
};

// --------------------------------------------------
// 2. HTML要素を取得する
// --------------------------------------------------

/*
document.getElementById("id名") は、
HTML内で指定した id を持つ要素を取得する命令。

取得した要素を変数に保存しておくと、
後から文字やボタンの状態などを操作できる。

const は、変数そのものへの再代入を禁止する宣言。
取得したHTML要素の内容まで変更できなくなるわけではない。
*/

// 文字の種類を選択するプルダウン
const characterType = document.getElementById("characterType");

// 正解となる対象文字を表示する要素
const targetElement = document.getElementById("target");

// 切り替わる文字を表示する要素
const letterElement = document.getElementById("letter");

// スタートボタン
const startButton = document.getElementById("startButton");

// ストップボタン
const stopButton = document.getElementById("stopButton");

// スピード調整用のスライダー
const speedInput = document.getElementById("speed");

// スピードを文字で表示する要素
const speedText = document.getElementById("speedText");

// ゲーム結果を表示する要素
const resultElement = document.getElementById("result");

// --------------------------------------------------
// 3. ゲームの状態を管理する変数
// --------------------------------------------------

/*
let は、後から値を変更できる変数を宣言するキーワード。

const と let の使い分け:
const → 変数への再代入をしない場合
let   → 変数への再代入が必要な場合
*/

// タイマーIDを保存する変数。
// タイマーがない状態を null で表す。
let timerId = null;

// 正解となる対象文字を保存する。
// ゲーム開始時にランダムな文字が入る。
let targetLetter = "";

// ゲームが動作中かどうかを表す。
// true  = 動作中
// false = 停止中
let isRunning = false;

// 正解した回数
let correctCount = 0;

// 不正解だった回数
let incorrectCount = 0;

// --------------------------------------------------
// 4. 成績を表示するHTML要素を取得する
// --------------------------------------------------

// 正解回数の表示欄
const correctCountElement = document.getElementById("correctCount");

// 不正解回数の表示欄
const incorrectCountElement = document.getElementById("incorrectCount");

// 正解率の表示欄
const accuracyElement = document.getElementById("accuracy");

// --------------------------------------------------
// 5. 成績を計算して画面に表示する
// --------------------------------------------------

function updateStats() {

  /*
  正解回数と不正解回数を合計する。

  例:
  正解3回、不正解2回なら、合計は5回。
  */
  const total = correctCount + incorrectCount;

  /*
  正解率を計算する。

  計算式:
  正解回数 ÷ 合計回数 × 100

  total === 0 は、まだプレイしていない状態。
  0で割ることを避けるため、合計が0なら正解率を0にする。

  ? : は条件演算子。
  条件 ? 条件がtrueの場合 : falseの場合
  */
  const accuracy = total === 0
    ? 0
    : (correctCount / total) * 100;

  /*
  textContent は、HTML要素内のテキストを設定するプロパティ。

  例えば correctCount が3なら「3回」と表示する。
  */
  correctCountElement.textContent = correctCount + "回";

  incorrectCountElement.textContent = incorrectCount + "回";

  /*
  toFixed(1) は、小数点以下1桁に整えるメソッド。

  例:
  75 → "75.0"
  66.666... → "66.7"

  最後に % を付けて正解率を表示する。
  */
  accuracyElement.textContent = accuracy.toFixed(1) + "%";

}

// --------------------------------------------------
// 6. ランダムな文字を1つ選ぶ
// --------------------------------------------------

function randomLetter() {

  /*
  プルダウンで選択されている文字の種類を取得する。

  characterType.value が "romaji" なら、
  CHARACTERS["romaji"] に相当する文字列を取得する。

  このように、オブジェクトの名前を使って
  必要なデータを取り出すことができる。
  */
  const letters = CHARACTERS[characterType.value];

  /*
  Math.random() は、0以上1未満の乱数を返す。

  Math.random() * letters.length
  → 文字数に応じた範囲の数値になる。

  Math.floor() は、小数点以下を切り捨てる。

  例えば文字数が26なら、0~25の整数が得られる。

  文字列[番号] で、その位置にある文字を取り出せる。

  return は、選んだ文字を呼び出し元に返す命令。
  */
  return letters[Math.floor(Math.random() * letters.length)];

}

// --------------------------------------------------
// 7. スライダーの値から文字の切り替え間隔を計算する
// --------------------------------------------------

function getInterval() {

  /*
  setInterval() に指定する時間の単位はミリ秒。

  スライダーの値:
  1   → 500ミリ秒
  100 → 25ミリ秒

  Number() は、値を数値に変換する。

  スライダーの値が大きいほど、
  切り替え間隔が短くなり、文字の表示が速くなる。

  Math.round() は、計算結果を整数に丸める。
  */
  return Math.round(
    500 - (Number(speedInput.value) - 1) * (475 / 99)
  );

}

// --------------------------------------------------
// 8. スピード表示を更新する
// --------------------------------------------------

function updateSpeedLabel() {

  // スライダーの現在値を数値として取得する。
  const speed = Number(speedInput.value);

  /*
  条件演算子を使って、速度を3段階で表示する。

  35未満       → 低速
  35以上70未満 → 中速
  70以上       → 高速

  条件演算子は、条件によって値を選ぶ書き方。
  */
  speedText.textContent =
    speed < 35 ? "低速" : speed < 70 ? "中速" : "高速";

  /*
  ゲーム動作中に速度を変更した場合は、
  新しい速度をすぐに反映する。

  if (isRunning) は、
  isRunning が true の場合だけ処理を実行する。
  */
  if (isRunning) {

    // 現在のタイマーを停止する。
    clearInterval(timerId);

    // 新しい切り替え間隔でタイマーを作り直す。
    timerId = setInterval(changeLetter, getInterval());

  }

}

// --------------------------------------------------
// 9. 表示する文字を切り替える
// --------------------------------------------------

function changeLetter() {

  // 選択中の文字の種類に対応する文字列を取得する。
  const letters = CHARACTERS[characterType.value];

  // 次に表示する文字をランダムに選ぶ。
  let nextLetter = randomLetter();

  /*
  同じ文字が連続して表示されないようにする。

  letters.length > 1
  → 使用できる文字が2文字以上あることを確認する。

  nextLetter === letterElement.textContent
  → 新しい文字が現在の表示文字と同じか確認する。

  && は「かつ」を意味する論理演算子。
  両方の条件がtrueの場合だけ、ifの中を実行する。
  */
  if (
    letters.length > 1 &&
    nextLetter === letterElement.textContent
  ) {

    // 選ばれた文字が文字列の何番目にあるか調べる。
    const index = letters.indexOf(nextLetter);

    /*
    現在の文字とは異なる位置から文字を選び直す。

    indexOf() は、指定した文字の位置を返す。
    位置は0から数える。

    Math.random() と Math.floor() で整数を作り、
    現在の位置から1以上ずらしている。

    % は剰余演算子。
    文字列の末尾を超えた場合、位置を先頭側へ戻す。

    この処理によって、現在の文字と同じ文字が
    連続して表示されるのを防ぐ。
    */
    nextLetter = letters[
      (index + 1 + Math.floor(Math.random() * (letters.length - 1)))
      % letters.length
    ];

  }

  // 選んだ文字を画面に表示する。
  letterElement.textContent = nextLetter;

  /*
  ローマ字が選択されている場合だけ、
  表示文字の要素に latin クラスを付ける。

  classList.toggle("latin", 条件) の動作:
  条件がtrue  → latin クラスを付ける
  条件がfalse → latin クラスを外す

  CSS側で #letter.latin などを指定すると、
  ローマ字だけ文字サイズを変更できる。
  */
  letterElement.classList.toggle(
    "latin",
    characterType.value === "romaji"
  );

}

// --------------------------------------------------
// 10. ゲームを開始する
// --------------------------------------------------

function startGame() {

  /*
  すでにゲームが動作中なら、何もせず終了する。

  return は、関数の処理を終了させる命令。
  タイマーが重複して作られるのを防ぐ。
  */
  if (isRunning) return;


  // 正解となる対象文字をランダムに決める。
  targetLetter = randomLetter();


  // 対象文字を画面に表示する。
  targetElement.textContent = targetLetter;


  // ゲームの案内メッセージを表示する。
  resultElement.textContent = "対象文字と同じ文字で止めてください";

  /*
  className は、HTML要素のclass属性を設定する。

  空文字列にすると、以前の結果に付いていた
  correct や incorrect のクラスを外せる。
  */
  resultElement.className = "";


  // ゲーム中に文字の種類を変更できないようにする。
  characterType.disabled = true;


  // ゲームが動作中であることを記録する。
  isRunning = true;


  // スタートボタンを無効にする。
  startButton.disabled = true;


  // ストップボタンを有効にする。
  stopButton.disabled = false;


  // 開始直後に文字を1回切り替える。
  changeLetter();

  /*
  setInterval() は、指定した時間ごとに
  関数を繰り返し実行する。

  第1引数:繰り返し実行する関数
  第2引数:実行間隔(ミリ秒)

  戻り値のタイマーIDを timerId に保存する。
  後で clearInterval(timerId) を使うと停止できる。
  */
  timerId = setInterval(changeLetter, getInterval());

}

// --------------------------------------------------
// 11. ゲームを停止して正解・不正解を判定する
// --------------------------------------------------

function stopGame() {

  // ゲームが動作していなければ、何もせず終了する。
  if (!isRunning) return;


  // 文字を切り替えているタイマーを停止する。
  clearInterval(timerId);


  // タイマーIDを「タイマーなし」の状態に戻す。
  timerId = null;


  // ゲームを停止状態にする。
  isRunning = false;


  // 停止後は文字の種類を変更できるようにする。
  characterType.disabled = false;


  // スタートボタンを有効に戻す。
  startButton.disabled = false;


  // ストップボタンを無効にする。
  stopButton.disabled = true;


  /*
  停止時の表示文字と、対象文字を比較する。

  === は厳密等価演算子。
  値と型が一致している場合にtrueとなる。

  今回はどちらも文字列なので、
  同じ文字なら正解、違う文字なら不正解になる。
  */
  if (letterElement.textContent === targetLetter) {

    // 正解回数を1増やす。
    correctCount++;


    // 正解メッセージを表示する。
    resultElement.textContent = "正解! 対象文字と一致しました。";


    // CSSの correct クラスを付け、正解表示の色を変える。
    resultElement.className = "correct";

  } else {

    // 不正解回数を1増やす。
    incorrectCount++;

    /*
    停止時の文字と対象文字を表示する。

    + 演算子で文字列をつなげている。
    これを文字列の連結という。
    */
    resultElement.textContent =
      "残念! 表示:" + letterElement.textContent + " / 対象:" + targetLetter;


    // CSSの incorrect クラスを付け、不正解表示の色を変える。
    resultElement.className = "incorrect";

  }

  // 成績を再計算し、画面の表示を更新する。
  updateStats();


  // 最新の成績をブラウザーに保存する。
  saveStats();

}

// --------------------------------------------------
// 12. 成績をブラウザーに自動保存する
// --------------------------------------------------

/*
localStorage は、ブラウザーにデータを保存する機能。

ページを再読み込みしても、通常は保存したデータが残る。

try ~ catch は、処理中にエラーが起きた場合に
そのエラーを受け取り、別の処理を行うための構文。
*/
function saveStats() {

  try {

    /*
    localStorage.setItem(キー, 値)
    → 指定したキーにデータを保存する。

    JSON.stringify() は、
    JavaScriptのオブジェクトをJSON形式の文字列に変換する。

    ここでは正解回数と不正解回数をまとめて保存する。

    { correctCount, incorrectCount } は、
    次の書き方を短くしたもの。

    {
      correctCount: correctCount,
      incorrectCount: incorrectCount
    }
    */
    localStorage.setItem("letterStopStats", JSON.stringify({
      correctCount,
      incorrectCount
    }));

  } catch (error) {

    /*
    保存に失敗した場合に実行する。

    console.warn() は、開発者向けのコンソールに
    警告メッセージを表示する。
    */
    console.warn("成績を保存できませんでした。");

  }

}

// --------------------------------------------------
// 13. 保存してある成績を読み込む
// --------------------------------------------------

/*
ページを開いたときに、以前の成績を復元するための関数。
*/
function loadStats() {

  try {

    // 保存キー "letterStopStats" のデータを取得する。
    const saved = localStorage.getItem("letterStopStats");

    /*
    保存データが存在しない場合は関数を終了する。

    !saved は、saved が空文字列やnullなどの場合に
    trueになる。
    */
    if (!saved) return;

    /*
    localStorageから取得したデータは文字列。

    JSON.parse() で解析し、
    JavaScriptのオブジェクトに戻す。
    */
    const data = JSON.parse(saved);

    /*
    読み込んだ成績が正しい形式か確認する。

    Number.isSafeInteger()
    → 安全に扱える整数かを確認する。

    >= 0
    → 0以上であることを確認する。

    && は「かつ」を意味する。
    すべての条件がtrueの場合だけ、
    保存データを成績として採用する。
    */
    if (
      Number.isSafeInteger(data.correctCount) &&
      data.correctCount >= 0 &&
      Number.isSafeInteger(data.incorrectCount) &&
      data.incorrectCount >= 0
    ) {

      // 保存されていた正解回数を復元する。
      correctCount = data.correctCount;


      // 保存されていた不正解回数を復元する。
      incorrectCount = data.incorrectCount;

    }

  } catch (error) {

    // 読み込みやJSONの解析に失敗した場合の警告。
    console.warn("保存データを読み込めませんでした。");

  }

  // 復元した成績を画面に表示する。
  updateStats();


  // 現在の成績を保存し直す。
  saveStats();

}

// --------------------------------------------------
// 14. 成績を一括クリアする
// --------------------------------------------------

function clearAllStats() {

  // 正解回数を0に戻す。
  correctCount = 0;


  // 不正解回数を0に戻す。
  incorrectCount = 0;


  // 0回・0%になった成績を画面に表示する。
  updateStats();


  // クリア後の成績を保存する。
  saveStats();

}

// --------------------------------------------------
// 15. ボタンやスライダーのイベントを登録する
// --------------------------------------------------

/*
addEventListener() は、指定したイベントが発生したときに
実行する関数を登録する命令。

基本形:
要素.addEventListener("イベント名", 関数名);
*/


// スタートボタンがクリックされたら、ゲームを開始する。
startButton.addEventListener("click", startGame);


// ストップボタンがクリックされたら、ゲームを停止する。
stopButton.addEventListener("click", stopGame);


// スライダーの値が変更されたら、速度表示などを更新する。
// input イベントは、操作中にも発生する。
speedInput.addEventListener("input", updateSpeedLabel);


// 一括クリアボタンがクリックされたら、成績を0に戻す。
document.getElementById("clearAllButton")
  .addEventListener("click", clearAllStats);

// --------------------------------------------------
// 16. ページを開いたときの初期処理
// --------------------------------------------------

/*
関数は、定義しただけでは実行されない。
関数名() と書くことで実行する。

以下は、JavaScriptが読み込まれたときに
上から順番に実行される。
*/


// スライダーの現在値に合わせて速度表示を整える。
updateSpeedLabel();


// 現在の成績を画面に表示する。
updateStats();


// ブラウザーに保存されている成績を読み込む。
// 保存データがあれば、成績を復元して表示する。
loadStats();

</script>
</body>
</html>

≪≪文字ストップ デモサイトはこちら≫≫