【Phase 2】エラーと格闘:タイピングゲームに間違い判定機能を追加

タイピングゲームの2回目の機能追加。「間違えた文字を赤色で表示する」機能を実装しました。多数のエラーと格闘し、デバッグを重ねて完成させた記録です。

公開URL: https://dondokoboy.github.io/typing-game/


前回の機能追加(振り返り)

1回目:入力表示機能(午前)

実装内容:

  • 入力した文字を青色で表示
  • 未入力の文字は通常表示

結果: スムーズに実装完了


2回目の機能追加:間違い判定

目標

現状:

正解: apple
「apx」入力 → 青色(間違いでも青)

目指す姿:

正解: apple
「app」入力 → 青色(正解)
「apx」入力 → 赤色(不正解)

設計

やること:

  1. 入力文字が正解と一致しているか判定
  2. 正解なら青色、不正解なら赤色

実装方針:

  • startsWith()メソッドを使う
  • CSS に .wrong クラスを追加
  • className で切り替え

パターンの選択

2つのパターン:

  • パターンA: 間違えたら次に進めない
  • パターンB: 間違えても続けられる

選択: パターンB

理由: 次の機能追加(間違い文字リストアップ)を見据えて


実装開始

Step 1: バックアップ作成

cp index.html index.html.backup
cp style.css style.css.backup
cp script.js script.js.backup

失敗に備えて。


Step 2: CSS追加

.wrongクラスを追加:

.wrong {
  background-color: #e74c3c;  /* 赤色 */
  color: white;
  padding: 2px 4px;
  border-radius: 4px;
}

Step 3: JavaScript修正

updateWordDisplay関数を修正:

function updateWordDisplay(word, typedText) {
  const enteredElement = document.querySelector('.entered');
  const remainedElement = document.querySelector('.remained');

  if (!enteredElement || !remainedElement) {
    return;
  }

  const enteredPart = word.slice(0, typedText.length);
  const remainedPart = word.slice(typedText.length);

  enteredElement.textContent = enteredPart;

  // 正解判定
  if (word.startsWith(typedText)) {
    enteredElement.className = 'entered';  // 青色
  } else {
    enteredElement.className = 'wrong';    // 赤色
  }

  remainedElement.textContent = remainedPart;
}

startsWith()メソッド:

  • "apple".startsWith("app")true
  • "apple".startsWith("apx")false

エラーとの格闘

エラー1: Backspaceでエラー

症状: Backspaceを押すとエラー

エラー:

Uncaught TypeError: Cannot set properties of null (setting 'textContent')

原因: 要素が見つからない

解決: 安全策を追加

if (!enteredElement || !remainedElement) {
  return;
}

エラー2: Backspace後に処理が止まる

症状:

  • Backspaceでエラーは解消
  • でも、正解を入力しても次の単語が出ない

デバッグ:

console.log('入力:', typedWord);
console.log('正解:', currentWord);
console.log('一致?:', typedWord === currentWord);

結果:

  • 一致: true
  • 「正解処理実行」と表示される
  • でも次の単語が出ない

エラー3: showRandomWordでnull

エラー:

Uncaught TypeError: Cannot set properties of null (setting 'textContent')
showRandomWord @ script.js:33

原因: showRandomWord関数内で要素が見つからない

解決: 安全策を追加

const enteredElement = document.querySelector(".entered");
const remainedElement = document.querySelector(".remained");

if (enteredElement && remainedElement) {
  enteredElement.textContent = "";
  remainedElement.textContent = currentWord;
}

エラー4: enteredElement is not defined

エラー:

Uncaught ReferenceError: enteredElement is not defined

原因: 変数を定義せずに使っていた

間違い:

if (enteredElement && remainedElement) {  // 未定義

修正:

const enteredElement = document.querySelector(".entered");
const remainedElement = document.querySelector(".remained");

if (enteredElement && remainedElement) {

エラー5: updateWordDisplay is not defined

エラー:

Uncaught ReferenceError: updateWordDisplay is not defined

原因:

  • showRandomWord関数が重複していた
  • updateWordDisplay関数が消えていた

解決: 関数を正しく配置し直す


エラー6: 最大の問題 – クラス名の消失

症状:

  1. 正しく入力 → 青色(OK)
  2. 間違えて入力 → 赤色(OK)
  3. Backspace → エラーなし(OK)
  4. 正しく入力 → 青色にならない
  5. 正解しても次の単語が出ない

デバッグ:

console.log('enteredElement:', enteredElement);
console.log('remainedElement:', remainedElement);

結果:

enteredElement: null
remainedElement: <span class="remained"></span>

.entered要素が見つからない!


原因の特定

updateWordDisplay関数で:

enteredElement.className = 'wrong';  // .enteredクラスを削除

.enteredクラスが消えて.wrongだけになる

→ 次にquerySelector('.entered')で探しても見つからない


解決策:複数のクラスを使う

HTML構造を変更:

変更前:

<span class="entered"></span>
<span class="remained">apple</span>

変更後:

<span class="typed-text entered"></span>
<span class="untyped-text remained">apple</span>

ポイント:

  • .typed-text: 固定のクラス(常に存在)
  • .entered または .wrong: 状態に応じて切り替え

JavaScript修正:

querySelector:

document.querySelector('.typed-text')    // これで確実に見つかる
document.querySelector('.untyped-text')

className設定:

// 正解
enteredElement.className = "typed-text entered";

// 不正解
enteredElement.className = "typed-text wrong";

これで.typed-textは残り続ける!


最終的なコード

updateWordDisplay関数

function updateWordDisplay(word, typedText) {
  const enteredElement = document.querySelector(".typed-text");
  const remainedElement = document.querySelector(".untyped-text");

  if (!enteredElement || !remainedElement) {
    return;
  }

  const enteredPart = word.slice(0, typedText.length);
  const remainedPart = word.slice(typedText.length);

  enteredElement.textContent = enteredPart;

  // 正解判定
  if (word.startsWith(typedText)) {
    enteredElement.className = "typed-text entered";  // 正解:青色
  } else {
    enteredElement.className = "typed-text wrong";    // 不正解:赤色
  }

  remainedElement.textContent = remainedPart;
}

showRandomWord関数

function showRandomWord() {
  const randomIndex = Math.floor(Math.random() * words.length);
  currentWord = words[randomIndex];

  const enteredElement = document.querySelector(".typed-text");
  const remainedElement = document.querySelector(".untyped-text");

  if (enteredElement && remainedElement) {
    enteredElement.textContent = "";
    remainedElement.textContent = currentWord;
    // クラスをリセット
    enteredElement.className = "typed-text entered";
  }
}

完成した機能

動作

正しく入力 → 青色表示
間違えて入力 → 赤色表示
Backspaceで修正 → 青色に戻る
正解したら次の単語へ

パターンB(間違えても続けられる)完成!


エラーとの格闘から学んだこと

技術面

複数のクラスを持つ要素:

<span class="class1 class2"></span>
  • 複数のクラスをスペース区切りで指定
  • 一部のクラスが変わっても、他は残る

querySelector の仕組み:

  • クラスが1つでも一致すれば見つかる
  • でも、完全一致で探すと見つからないことがある

className での上書き:

element.className = 'new-class';  // 全部置き換わる
  • すべてのクラスが上書きされる
  • 注意が必要

classList の使い方:

element.classList.add('class');     // 追加
element.classList.remove('class');  // 削除
  • 個別に操作できる
  • より安全

デバッグ技術

console.logの重要性:

  • 何が起きているか可視化
  • 予想と実際の違いを確認
  • エラーの原因を特定

段階的なデバッグ:

  1. エラーメッセージを読む
  2. 行番号を確認
  3. console.logで値を確認
  4. 仮説を立てる
  5. 修正して確認

エラーは学びのチャンス:

  • エラーが出ないと、問題に気づかない
  • エラーを解決することで、深く理解できる

開発プロセス

バックアップの重要性:

  • 失敗しても戻せる安心感
  • 思い切って実装できる

段階的な実装:

  • 一度に全部やらない
  • 小さく確認しながら進める

あきらめない粘り強さ:

  • 6つのエラーと格闘
  • すべて解決できた
  • 最後までやり遂げる力

1日の振り返り

午前:1回目の機能追加

入力表示機能(青色):

  • スムーズに実装完了
  • 自分で設計・実装

午後:2回目の機能追加

間違い判定機能(赤色):

  • 多数のエラーと格闘
  • デバッグを重ねて解決
  • より深い理解を獲得

感想

「エラーは怖くない」

以前:

  • エラーが出ると不安
  • どうしていいか分からない

:

  • エラーメッセージを読む
  • console.logで確認
  • 段階的に解決
  • エラーは学びのチャンス

「複雑な問題も解決できる」

今回のクラス名の問題:

  • 原因の特定が難しい
  • 解決方法が分かりにくい

でも:

  • デバッグを重ねて原因を特定
  • HTMLとJavaScript両方を修正
  • 複雑な問題も解決できた

自信がついた。


「次はもっとスムーズに」

今回学んだこと:

  • 複数のクラスの扱い方
  • querySelector の仕組み
  • デバッグ技術

次の機能追加では、もっとスムーズにできるはず。


次の機能追加(予定)

間違えた文字のリストアップ:

  • 間違えた文字を記録
  • リストとして表示
  • リセット機能

今回パターンBを選んだ理由:

  • 間違いを記録できる
  • ゲームを続けながら蓄積できる

準備は整った。


Phase 2、着実に成長しています。

エラーと格闘しながら、深く理解する。

これが本物の学習です。


コメント