デモページ 卓球得点表 ロジックのリファクタ[JavaScript]


デモページとして、今まで学習してきた内容をもとに、
卓球の得点表のようなものを作成してみました。

卓球得点表：ロジック

https://qiita.com/shisshi_engineer/items/5e2d57a093db0323ca96

卓球得点表：フロント

https://qiita.com/shisshi_engineer/items/387321c29fbbfd6fd5ef

これを、より可読性を高めるためにリファクタリングしてみようと思います。

また、上記のコードに新たな機能を実装して修正していきます。

## 目次：

- [コードのリファクタリング](#コードのリファクタリング)
- [機能追加　ゲームの結果を記録する「HTML」](#機能追加-ゲームの結果を記録するhtml)
- [機能追加 ゲームの結果を記録する「JavaScript」](#機能追加-ゲームの結果を記録するjavascript)
- [機能追加 デュース機能を追加する「JavaScript」](#機能追加-デュース機能を追加するjavascript)

## コードのリファクタリング

現状のコードで、プレイヤーの数だけ同じコードが書かれている部分があります。

この部分に関して、追加で処理を加えるたびに、同じ処理を複数書く必要があるので、共通してコードが書けるように修正していこうと思います。

```js
p1Button.addEventListener('click',function() {
    if(!isGameOver) {
        p1Score += 1;
        p1Display.textContent = p1Score;
        if(p1Score === winningScore) {
            isGameOver  = true;
            p1Display.classList.add('has-text-success');
            p2Display.classList.add('has-text-danger');
            p1Button.disabled = true;
            p2Button.disabled = true;
        }    
    }
    
});

p2Button.addEventListener('click',function() {
    if(!isGameOver) {
        p2Score += 1;
        p2Display.textContent = p2Score;
        if(p2Score === winningScore) {
            isGameOver  = true;
            p2Display.classList.add('has-text-success');
            p1Display.classList.add('has-text-danger');
            p1Button.disabled = true;
            p2Button.disabled = true;
        }    
    }
    
});
```

#### データ構造の形をオブジェクトに変更して対処する

p1に関連するものを保持するオブジェクトとp2に関連するものを保持するオブジェクトをそれぞれ作成して、対応していきたいと思います。

p1のscore、button、displayに対応する値を取得するものをそれぞれ保持する
p2のscore、button、displayに対応する値を取得するものをそれぞれ保持する

```js
const p1 = {
    score: 0,
    button: document.querySelector('#p1Button'),
    display: document.querySelector('#p1Display')
}

const p2 = {
    score: 0,
    button:document.querySelector('#p2Button'),
    display:document.querySelector('#p2Display')
}
```

#### 特点に関連するロジックを違う関数として出力する

関数をupdateScoresとして、
パラメータにplayer「主となるプレイヤー」とopponent「相手側」として汎用的なロジックとして作成します。

```js
function updateScores(player, opponent) {
    if(!isGameOver) {
        player.score += 1;
        player.display.textContent = player.score;
        if(player.score === winningScore) {
            isGameOver  = true;
            player.display.classList.add('has-text-success');
            opponent.display.classList.add('has-text-danger');
            player.button.disabled = true;
            opponent.button.disabled = true;
        }    
    }
}
```
上記で作成した汎用的な関数を使い、p1とp2のbuttonそれぞれにクリックイベントを追加します。

```js
p1.button.addEventListener('click',function() {
    updateScores(p1,p2);
});

p2.button.addEventListener('click',function() {
    updateScores(p2,p1);
});
```
resetボタンもp1とp2に対応させる。
```js
resetButton.addEventListener('click', reset )

function reset() {
    isGameOver = false;
    p1.score = 0;
    p2.score = 0;
    p1.display.textContent = 0;
    p2.display.textContent = 0;
    p1.display.classList.remove('has-text-success','has-text-danger');
    p2.display.classList.remove('has-text-success','has-text-danger');
    p1.button.disabled = false;
    p2.button.disabled = false;
};
```

また、resetボタンに関しても共通の処理内容が多いので、
リファクタをしていきます。

```js
function reset() {
    isGameOver = false;
    for (let p of [p1, p2]) {
        p.score = 0;
        p.display.textContent = 0;
        p.display.classList.remove('has-text-success','has-text-danger');
        p.button.disabled = false;
    }
}
```
1. p1、p2を配列に格納し、for ofループを回します。
2. それぞれのpに対応した処理を１つにします。

上記のコードを記載することにより、コードの機能をそのまま保ちつつ、
コードをスッキリとした見た目に保つことができました。

## 機能追加 ゲームの結果を記録する「HTML」

新たな機能の追加として、２つ機能を追加していきたいと思います。
まず、１つ目として

:::note info
ゲームの記録機能
:::
を追加していきたいと思います。

```html
<div class="content">
    <h2 class="title is-2">ゲームの結果</h2>
    <ul id="results"></ul>
</div>
```
1. 全体をdivで囲む、classとしてcontentを適用
2. h2を作成。出力はゲーム結果、classとしてtitle is-2を適用
3. ulを作成。idをresultsとして保存しておきます。


## 機能追加 ゲームの結果を記録する「JavaScript」

endGame関数を作成して、ゲームが終了したら関数が呼び出され、
ゲーム結果を記録します。


```js
function endGame(winner, loser) {
    isGameOver = true;
    winner.display.classList.add('has-text-success');
    loser.display.classList.add('has-text-danger');
    winner.button.disabled = true;
    loser.button.disabled = true;

    const newResult = document.createElement('li');
    newResult.textContent = `${winner.display.id === 'p1Display' ? 'プレイヤー1' : 'プレイヤー2'}の勝利！ スコア: ${p1.score} 対 ${p2.score}`;
    results.appendChild(newResult);
}
```

endGame関数　ロジック内容：
1. endGame関数を呼び出します。パラメータとして、winnerとloserを受け取るようにします。
2. endGame関数が実行されるときに、ゲームが終了した時なので、isGameOver変数をtrueに変更します。
3. 勝者と敗者のスコア表示に特定のクラスを追加して、色を変更します。  winner.display.classList.add('has-text-success');
loser.display.classList.add('has-text-danger');
4. 勝者と敗者のボタンを無効にして、これ以上得点が増えないようにします。
winner.button.disabled = true;
loser.button.disabled = true;

得点を出力する：
1. newResult変数を作成し、liを新たに作ります。
2. 作成したnewResultにテキストを追加します。
テキストの追加内容：
2-1. テンプレートリテラルを使用します。
2-2. winner.display.idがp1Displayだったら、プレイヤー１をそうでなかったらプレイヤー2を出力します。
2-3. p1.score 対 p2.scoreでそれぞれの点数も同時に出力させます。
3. results(ul)に対して、作成したnewResult(li)を追加します。

## 機能追加 デュース機能を追加する「JavaScript」

ゲームの得点管理にデュースの機能を追加したいと思います。

:::note info 
デュースとは？

デュースとは、テニスや卓球、バレーボールなどの試合で、マッチポイントの直前のポイントに両者ともがなった場合に発動します。

次のポイントを２点先取しないと、今行なっているゲームを奪取することができないという仕組みです。
:::

まず、isGameOverのようにisDeuce変数をfalseとして保持しておきます。
```js
let isDeuce = false;
```

このisDeuceがtrueの場合の処理として条件分岐で記載していこうと思います。

```js
function updateScores(player, opponent) {
    if (!isGameOver) {
        player.score += 1;
        player.display.textContent = player.score;

        if (isDeuce) {
            if (Math.abs(player.score - opponent.score) >= 2) {
                endGame(player, opponent);
            }
        } else {
            if (player.score === winningScore - 1 && opponent.score === winningScore - 1) {
                isDeuce = true;
            } else if (player.score === winningScore) {
                endGame(player, opponent);
            }
        }
    }
}
```

1.updateScores関数は[特点に関連するロジックを違う関数として出力する](https://qiita.com/drafts/3a16e570c0451443e9cf/edit#%E7%89%B9%E7%82%B9%E3%81%AB%E9%96%A2%E9%80%A3%E3%81%99%E3%82%8B%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF%E3%82%92%E9%81%95%E3%81%86%E9%96%A2%E6%95%B0%E3%81%A8%E3%81%97%E3%81%A6%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B)こちらで修正した関数をそのまま使い、if文をネストしていきます。
また、この関数は、プレイヤーの得点を更新し、ゲーム終了やデュース（ジュース）状況のチェックを行います。
2. デュースの状態を確認
2-1. playerの点数とopponentの点数の比較した値の絶対値が2より大きかったらゲームを終了します。
2-2. また、playerの点数とopponentの点数が勝利のための点数よりも1少ない状態だとデュースがtrueになります。
2-3. この状態が維持しなくなったらゲームを終了します。