脱初心者向けにpaizaランク B「神経衰弱」を解説

「paizaxQiita記事投稿キャンペーン」という楽しそうなキャンペーンを見つけ、参加することにしました。

今回はキャンペーン対象の中で、レベルB「神経衰弱」に挑戦しました。中の人は生粋のJavaScripterですので、言語はJavaScriptで解いています。

https://paiza.jp/works/mondai/b_rank_skillcheck_archive/concentration

## 解き方
問題文を読み、入力される値の○行目に何が出力されているかを把握します。当たり前のことですがここを間違えると全て台無しになってしまいます。頑張って理解しましょう。

そしていきなり書くのではなく、どういった手順を踏んでいけばいいか考えます。今回だとまずトランプの配置をどう表現するか、プレイ中の値をどのように処理すればいいだろうか、などです。

自分はまず整理してクリアにした状態から書きたいタイプですので、その方法で進めていました。

## 解答コード
まずは私が書いたコードの全体をお見せします。

:::note info
構成自体は手続き型ではなく、オブジェクト指向でクラスを使用しています。手続き型でも十分解けるのですが、可読性の面がありクラスを使用することになりました。
:::

```javascript
class Game {
    baseH = 0;
    playerCount = 0;
    totalTurnCount = 0;
    lines = [];
    turnActions = [];
    playerResults = [];
    tramps = [];
    
    constructor (lines) {
        this.lines = lines;
    }
    
    setup() {
        const [baseH, _, playerCount] = this.lines[0].split(' ').map(val => Number(val));
        const totalTurnCount = Number(lines[1 + baseH]);
        this.baseH = baseH;
        this.playerCount = playerCount;
        this.totalTurnCount = totalTurnCount;
        this.turnActions = this.lines.slice(-totalTurnCount);
        this.playerResults = Array(playerCount).fill(0);
        for (let i = 0; i < this.baseH; i++) {
            const trampLine = this.lines[i + 1].split(' ').map(val => Number(val));
            this.tramps.push(trampLine);
        }
    }
    
    play() {
        let currentPlayer = 1;
        for (let i = 0; i < this.totalTurnCount; i++) {
            const actions = this.turnActions[i].split(' ').map(val => Number(val));
            const y1 = actions[0] - 1;
            const x1 = actions[1] - 1;
            const y2 = actions[2] - 1;
            const x2 = actions[3] - 1;
            const result1 = this.tramps[y1][x1];
            const result2 = this.tramps[y2][x2];
            if (result1 === result2) {
                this.playerResults[currentPlayer - 1] += 2;
                continue;
            }
            if (currentPlayer === this.playerCount) {
                currentPlayer = 1;
                continue;
            }
            currentPlayer++;
        }
    }
    
    outputResult() {
        const outputResult = this.playerResults.join('\n');
        console.log(outputResult);
    }
}

const game = new Game(lines);
game.setup();
game.play();
game.outputResult();
```

## setup関数
ゲームを開始するのに必要な値を用意する関数です。

ここでのポイントは**トランプ配置を配列で表現する**ところです。

入力1行目から取得した行数をもとに、それぞれのトランプ行を取得し数値に直して配列に入れていきます。
今後プレイヤーが指定する場所と値を一致させるようにします。

```javascript
setup() {
    const [baseH, _, playerCount] = this.lines[0].split(' ').map(val => Number(val));
    const totalTurnCount = Number(lines[1 + baseH]);
    this.baseH = baseH;
    this.playerCount = playerCount;
    this.totalTurnCount = totalTurnCount;
    this.turnActions = this.lines.slice(-totalTurnCount);
    this.playerResults = Array(playerCount).fill(0);
    // トランプ配置を配列で表現する
    for (let i = 0; i < this.baseH; i++) {
        const trampLine = this.lines[i + 1].split(' ').map(val => Number(val));
        this.tramps.push(trampLine);
    }
}
```

## play関数
準備した値を使用してターンに応じたゲーム処理を進める関数です。

ここでのポイントは**プレイヤーの切り替えと点数の付け方**です。

前提として、playerResultsはsetup関数で設定されており、初期値では配列でプレイヤー数分の0が入っています。
- トランプの値が一緒ならば、現在のプレイヤー（`playerResults`配列に対応するので-1をしています）に2を加算します。プレイヤーは変わることがないので次のアクションに進みます。
- もしトランプ値が違っていて、かつ現在のプレイヤーが最後のプレイヤーだった場合は最初に戻ります。
- トランプ値が違っていて次のプレイヤーがいる場合は次のプレイヤーに移行します。

```javascript
let currentPlayer = 1;
for (let i = 0; i < this.totalTurnCount; i++) {
    const actions = this.turnActions[i].split(' ').map(val => Number(val));
    const y1 = actions[0] - 1;
    const x1 = actions[1] - 1;
    const y2 = actions[2] - 1;
    const x2 = actions[3] - 1;
    const result1 = this.tramps[y1][x1];
    const result2 = this.tramps[y2][x2];
    if (result1 === result2) {
        this.playerResults[currentPlayer - 1] += 2;
        continue;
    }
    if (currentPlayer === this.playerCount) {
        currentPlayer = 1;
        continue;
    }
    currentPlayer++;
}
```

## outputResult関数
ゲームを終えて得られたプレイヤー毎の結果を出力する関数です。

```javascript
outputResult() {
    const outputResult = this.playerResults.join('\n');
    console.log(outputResult);
}
```

## ちょっとリファクタリングしてみる
各処理の中で共通している処理があります。得られた入力値を数値配列にするための処理です。
```javascript
[].split(' ').map(val => Number(val));
```

こちらを別関数に切り出してみましょう。

```javascript
class Game {
    // ...
    convertLineToNumArray(line) {
        return line.split(' ').map(val => Number(val));
    }
}
```

以下のように入力値の値を引数に渡すことで数値配列が返ってくるようになります。

```javascript
setup() {
    const [baseH, _, playerCount] = this.convertLineToNumArray(lines[0]);
    // ...
}
```

## さいごに
いかがでしょうか。Bランク問題、CやDと比べ少し難しいなぁと感じる部分があるかと思います。

それぞれ役割毎に分解して考えると解きやすくなるのではないかと思います。