質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

2回答

4821閲覧

addEventListenerが多重起動してしまうのを防ぎたい

tomiswinner

総合スコア3

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

文字コード

文字コードとは、文字や記号をコンピュータ上で使用するために用いられるバイト表現を指します。

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

0グッド

0クリップ

投稿2020/07/21 23:32

タイピングゲームでのmissの数の表示がおかしくなる

javascriptでタイピングのゲームを作成しています。
画面に表示された内容の文字を打つと、正解したタイプ数と間違えたタイプ数が表示されるだけのタイマー付きのシンプルなものです。
Enterキーを押すとゲームが始まり、終了時にはスコアがalertで表示されます。
終了時の状態でEnterを押すとリプレイできるようにしています。

ゲームの開始、進行には問題はないのですが、リプレイをする際に入力するEnterキーがmissとしてカウントされてしまいます。またリプレイ後は、リプレイの回数だけミスカウントの増加がおかしくなります(2,4,6と増えたり、3,6,9と増えたり)

addEventListenerが二重三重と起動してしまっているのではないか、とは思うのですが、どうにも解決ができませんでした。

該当のソースコード

javascript

1{ 2 3 4 5const getWord = document.getElementById(`word`); 6 7const getScore = document.getElementById(`score`); 8 9const getMiss = document.getElementById(`miss`); 10 11const randomWord = [ 12 `banana`, 13 `train`, 14 `apple`, 15 `rabbit`, 16 `elephant` 17] 18const getTimer = document.getElementById("timer"); 19 20let isPlaying = false;//ゲーム進行中かどうか 21let loc = 0;//添え字の役割 22let scoreCount = 0; 23let missCount =0; 24 25 26window.addEventListener(`keydown`,(e)=>{ 27 if(e.key === "Enter"){ 28 if(isPlaying ===true){ 29 return; 30 }//ゲームが始まっていたら以下の処理を読み込まない、つまり、一回スタートした後はisplayingがtrueになるので(ゲーム進行中)二重起動しない 31 32 isPlaying = true; 33 34 35 loc = 0; 36 scoreCount = 0; 37 missCount = 0; 38 39 getWord.textContent = wordMaker(); // 入力しなければならない値(この値は、一つの単語の入力を終えて次のwordMakerを起動するまで変化しません) 40 let target = getWord.textContent; // 表記を変えるための値(表記のための値なので変化します。) 41 42 let startTime = Date.now(); 43 44 Timer(); 45 46 //入力したキーをコンソールで確認 47 window.addEventListener(`keydown`, (c) =>{ 48 49 if(isPlaying !== true){ 50 return; 51 }//ゲーム進行中でなければ処理をしない 52 53 console.log(missCount) ; 54 55 //loc番目の文字が、取得したkeyと一緒か確認して条件分岐 56 if(target[loc] === c.key){ 57 scoreCount++; 58 loc++;//入力位置を示す 59 updateTarget(); 60 61 getScore.textContent = scoreCount; 62 console.log(`score =`,scoreCount); 63 }else{ 64 missCount++; 65 getMiss.textContent = missCount; 66 console.log(`miss =`,missCount); 67 } 68 if(loc === target.length){ 69 getWord.textContent = wordMaker(); 70 target = getWord.textContent; 71 72 loc = 0; 73 } 74 console.log(`target.length = `,getWord.textContent.length); 75 }) 76 77 function Timer(){ 78 79 let timeLimit = 3 * 1000; 80 let passedTime = Date.now() - startTime; 81 let timeLeft = timeLimit - passedTime; 82 getTimer.textContent = (timeLeft /1000).toFixed(2); 83 // console.log(startTime,Date.now()); 84 const timeoutId = setTimeout(() => { 85 Timer(); 86 }, 10);//10mm秒ごとに呼び出す 87 88 if(timeLeft < 0){ 89 isPlaying = false; 90 clearTimeout(timeoutId); 91 getTimer.textContent = `00:00`; 92 //なんで00:00とかじゃなくて、00:01とかなっちゃうんだ? 93 setTimeout(() => { 94 showResult(); 95 getWord.textContent = `Press "Enter" to replay`; 96 97 }, 100); 98 //alertはブラウザを完全ストップさせるのでタイマー表記に多少の誤差がでてしまう、それを防ぐためにalertを少し遅らせて表示させる 99 } 100 } 101 102 function updateTarget(){ 103 let underscore = `_`; 104 for(let i = 0; i < loc; i++){ 105 underscore += `_`; 106 } 107 getWord.textContent = underscore + target.substring(loc); 108 //ここ変数じゃだめっぽい(targetにgetWord以下代入してるので行けると思ったのに、、、なんで?? 109 console.log(`loc = `,loc); 110 }// f updateTarget end 111 112 113 function wordMaker(){ 114 return randomWord[Math.floor(Math.random() * randomWord.length)]; 115 } // f wordMaker end 116 117 118 function showResult(){ 119 let accuracy = scoreCount + missCount === 0 ? 0 : ((missCount / scoreCount) * 100); 120 //条件演算子っていうのらしい、要google 121 122 alert(`${scoreCount} letters typed correctly, ${missCount} lettrs typed wrongly, acurracy is ${accuracy}`); 123 124 } 125 }// if e.key === enter end 126 127 })//addeve enter end 128 129 130 131}//use strict end

html

1<!DOCTYPE html> 2<html lang = "ja"> 3 <head> 4 <title>typing game</title> 5 <meta charset = "utf-8"> 6 <link rel ="stylesheet" href = "main.css"> 7 8 </head> 9 <body> 10 <p id = "word">Press "Enter" to start</p> 11 <p>score:<span id = "score">0</span> miss:<span id = "miss">0</span></p> 12 <p id = "timer">timer:00:00</p> 13 14 <script src = "typing.js"> 15 </script> 16 </body> 17</html>

試したこと

preventDafaultやstopPropagation を調べ使ってみたましたが、解決しませんでした。
いまいち使い方を把握できていないことも原因かもしれません。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2020/07/21 23:40

なぜkeydownの中にkeydownを入れる必要があるんでしょう
tomiswinner

2020/07/22 11:49

迅速な追記助かります。 もともとEnterキースタートではなく、clickスタートで設定していたものをそのまま書き換えたため、 このような事態になっております。調整してみます!
guest

回答2

0

2つ目のwindow.addEventListener('keydown', ...)、以下の処理

javascript

1 //入力したキーをコンソールで確認 2 window.addEventListener(`keydown`, (c) =>{

がプレイ開始時とリプレイ開始時に呼ばれ、イベントリスナーが多重登録されていることが原因のような気がします。
ブラウザのDevToolのconsoleで、getEventListeners(window).keydownとすると登録されたイベントリスナーの数が確認できるので見てみてください。

この場合であれば解決策は

  • 1つ目のwindow.addEventListener('keydown', ...)の外に上記の処理を移動する。
  • プレイ終了時、リプレイ終了時にwindow.removeEventListnerでイベントリスナーを削除する。

のどちらかになると思います。

投稿2020/07/22 00:38

編集2020/07/22 02:43
mcho71

総合スコア67

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tomiswinner

2020/07/22 11:47

わかりやすく要点の絞られた回答ありがとうございます!
guest

0

ベストアンサー

なにやら混乱されていますね……
処理がぐちゃぐちゃになっているので、

  • タイマーを表示する処理
  • キー入力の処理
    • Enterならゲーム開始または再開
    • Enter以外なら正誤判定

というのを分けて考えたほうがよいですね。

js

1// TODO: シングルトンにしたほうがよい 2class TimerManager { 3 constructor() { 4 this.intervalId = NaN; 5 this.startedAt = new Date(0); 6 this.timeLimit = 0; 7 this.timerElm = document.getElementById('timer'); 8 } 9 10 /** 11 * タイマーを開始する 12 * @param Function timeoverCallback タイムオーバーになったときに実行する関数 13 */ 14 start(timeLimit, timeoverCallback) { 15 if(!Number.isNaN(this.intervalId)) { 16 throw new Error('Already started!'); 17 } 18 this.timeLimit = timeLimit; 19 this.startedAt = Date.now(); 20 this.intervalId = setInterval(() => { 21 const passed = Date.now() - this.startedAt; 22 const displayDate = new Date(this.timeLimit - passed); 23 const mm = ('0' + displayDate.getMinutes()).slice(-2); 24 const ss = ('0' + displayDate.getSeconds()).slice(-2); 25 this.timerElm.textContent = `${mm}:${ss}.${('' + displayDate.getMilliseconds()).slice(-2)}`; 26 27 if(passed >= this.timeLimit) { 28 this.stop(); 29 this.timerElm.textContent = '00:00'; 30 timeoverCallback(); 31 } 32 }); 33 } 34 35 /// タイマーを停止する 36 stop() { 37 if(Number.isNaN(this.intervalId)) { 38 throw new Error('Timer has not started!'); 39 } 40 clearInterval(this.intervalId); 41 this.intervalId = NaN; 42 } 43} 44 45class GameManager { 46 constructor() { 47 this.questionWords = [ 48 `banana`, 49 `train`, 50 `apple`, 51 `rabbit`, 52 `elephant` 53 ]; 54 this.currentQuestion = ''; 55 this.typingLocation = 0; 56 this.scoreCount = 0; 57 this.missCount = 0; 58 this.isPlaying = false; 59 this.wordElm = document.getElementById('word'); 60 window.addEventListener('keydown', e => { 61 this.onKeyDown(e.key); 62 }); 63 this.timerManager = new TimerManager(); 64 } 65 66 /// ゲームを開始する 67 launchGame() { 68 this.isPlaying = true; 69 70 // 問題を生成 71 this.generateQuestion(); 72 73 // 各値を初期化 74 this.typingLocation = 0; 75 this.scoreCount = 0; 76 this.missCount = 0; 77 78 // タイマー起動、タイムリミットを迎えたらゲーム終了 79 this.timerManager.start(3 * 1000, () => { 80 this.finishGame(); 81 }); 82 } 83 84 /// ゲームを終了して結果表示する 85 finishGame() { 86 this.isPlaying = false; 87 // クリアレートの算出はゼロ除算にならないように配慮する 88 let accuracy = 0; 89 if(this.scoreCount !== 0) { 90 accuracy = (this.missCount / this.scoreCount) * 100; 91 } 92 alert(`${this.scoreCount} letters typed correctly, ${this.missCount} lettrs typed wrongly, acurracy is ${accuracy}`); 93 this.wordElm.textContent = 'Press "Enter" to replay'; 94 } 95 96 /// 問題文をランダム生成する 97 generateQuestion() { 98 this.currentQuestion = this.questionWords[Math.floor(Math.random() * this.questionWords.length)]; 99 this.wordElm.textContent = this.currentQuestion; 100 } 101 102 /** 103 * キーボードの何かしらのキーが押されたときのイベントハンドラ 104 * @param String 入力されたキーの名前 105 */ 106 onKeyDown(pressedKey) { 107 console.log(this.scoreCount, this.missCount); 108 109 // 押されたのがゲーム再開キー (Enter) の場合 110 if(pressedKey === 'Enter') { 111 // ゲーム進行中 (= 終了画面ではない) ときは、何もしない 112 if(this.isPlaying) { 113 return; 114 } 115 116 this.launchGame(); 117 return; 118 } 119 120 // 押されたのが Enter 以外の場合 121 // インゲームでないときは、何もしない 122 if(!this.isPlaying) { 123 return; 124 } 125 126 if(this.currentQuestion[this.typingLocation] === pressedKey) { 127 this.correctTyped(); 128 } else { 129 this.missTyped(); 130 } 131 } 132 133 /// 正しいキーが押されたときの処理 134 correctTyped() { 135 this.scoreCount++; 136 this.typingLocation++; 137 if(this.typingLocation >= this.currentQuestion.length) { 138 this.generateQuestion(); 139 this.typingLocation = 0; 140 } 141 this.wordElm.textContent = '_'.repeat(this.typingLocation) + this.currentQuestion.substring(this.typingLocation); 142 } 143 144 /// 正しくないキーが押されたときの処理 145 missTyped() { 146 this.missCount++; 147 } 148} 149const gameManager = new GameManager();

投稿2020/07/22 01:40

thyda.eiqau

総合スコア2982

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tomiswinner

2020/07/22 11:46

1から書き換えいただきありがとうございます。 いろいろと勉強になりそうなことが多く助かります!調べてみます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問