プログラミング問題をやってみた 神経衰弱 Ruby編

以前PaizaのCランクとDランクの問題を解いて投稿したので
今度はBランクの問題を解いてみましたので記事にしてみました。

##### 以前の記事
[初心者向けにCランク問題を解説してみた](https://qiita.com/koujiisan/items/4cbd6e9918fef72130ee)
[初心者向けPaizaの解き方を解説してみた](https://qiita.com/koujiisan/items/37cff5b575939fece292)

今回の記事の対象は
`CランクをクリアしてBランクに挑戦しているけどクリアできない方`
もしくは`Cランク挑戦中の方に向けて書いています。`

**Bランクの問題はCランクの問題の組み合わせで回答できます。**
基本的には行・列を扱う繰り返し処理と条件分岐、変数で状態保持する内容が多いです。
この記事を読めば**Cランクの問題でも活かせます**ので
ぜひ最後まで読んでいただけると嬉しいです。


**言語はRubyを選択していますが、どの言語でも共通の内容となっています。**

# 本題

## 問題 (神経衰弱 Ruby編)

今回はBランク問題の中から状況を整理して理解しやすい問題ということで
**トランプの神経衰弱を模した題材**を選びました。

[神経衰弱](https://paiza.jp/works/mondai/b_rank_skillcheck_archive/concentration)

問題の掲載は上記リンクにあります。

早速自分が書いたコードを確認していきます。
(問題のネタバレになりますがご了承ください)

``` ruby
H,W,N = gets.chomp.split.map(&:to_i)
#Cardの数字を取得
cards = H.times.map {gets.chomp.split.map(&:to_i)}
#playerのscoreを管理する配列を作り初期値として0を入れる
scores = []
player = 0
N.times {|i| scores[i] = 0}

#カードを引く回数を取得する
L = gets.chomp.to_i

#神経衰弱の再現
(1..L).each do |l|
    h_1, w_1, h_2, w_2 = gets.chomp.split.map(&:to_i)
    has_cards = []
    has_cards.push(cards[h_1 - 1][w_1 - 1])
    has_cards.push(cards[h_2 - 1][w_2 - 1])
    if has_cards[0] == has_cards[1]
        scores[player] += 2
    else
        player == N - 1 ? player = 0 : player += 1
    end
end 
puts scores
```

## 前準備　神経衰弱のルールを決める
### ①行,列,プレイヤーの数と入力が渡されるので取得します。
``` ruby
H,W,N = gets.chomp.split.map(&:to_i)
```
今回問題で行H,列W,プレイヤー数Nで問題が出されていましたので
そのまま定数に入力データを入れます。
Rubyで定数は名前を大文字で書くことで宣言できます。
ゲームのルールなど後に変更の必要がないものは定数で宣言しましょう。
（Rubyでは定数も注意分は出るものの書き換えができるので注意。書き換えを防ぐ場合にはFleezeを宣言するが今回は割愛）

### ②伏せているカードの状況を作る
``` ruby
#Cardの数字を取得
cards = H.times.map {gets.chomp.split.map(&:to_i)}
```
神経衰弱でいうとカードを伏せて並べている状態です。
本来はカードの中身は見えないですが、
内部的に配置されたカードに書かれた数字は決まるので
ここでcardsという配列に伏せているカードの状況を入れていきます。

Hは行の数、timesで行数だけ繰り返すのですが
その時にmap関数というものを使って
取得したデータをchompで末尾の改行を取り除き
splitで配列に分割して再度map関数で数値型(integer)に変換しています。

今回の入力例では
1 2 3
2 1 3
一行ずつにカードの中身が渡されるので行の数だけ繰り返すことで
伏せられたカード全体の状況を一度で配列に格納できます。

**Point**
**一つ一つデータを格納したタイミングで出力してみて
データが正しく入っているか確認しながら進めると後でミスが少なく
出力までのロジックに集中できるのでぜひ確認しながら進めましょう！**

配列を確認するときはpメソッドで出力して
行や列の関係が自分の想像した通りになっているか確認しましょう.
**Paizaでは提出前動作確認というところを選択すると出力ができます**

``` ruby
#例
p cards #=> [[1,2,3],[2,1,3]]
```

### ③各プレイヤーが取得したカード枚数を管理する配列を作る
``` ruby
#playerのscoreを管理する配列を作り初期値として0を入れる
scores = []
player = 0
N.times {|i| scores[i] = 0}
```
scoresという配列でプレイヤーが取得したカードの枚数を保存します。
一行目では空のデータの入れ子を作ります
二行目は直接関係ありませんが、
今ゲームに参加しているプレイヤーの状況をわかるようにするため
変数で状態を保存しています。
playerを0から始めることで配列との関係性を作れます。

つまりplayerが0のときは配列の0にデータを保存することができ
そのようにすることでプレイヤーごとに枚数を保存できます。

### ④カードを引く回数を取得して保存する
``` ruby
#カードを引く回数を取得する
L = gets.chomp.to_i
```
ここではゲームの中で何回カードを引くかを決めるための
数値を取得します。
Lに数値が入ってきますので、この数値を使って後に繰り返し文で
ゲームが行われている状況を再現します。

## 神経衰弱のゲーム進行を再現
ここからはまさに決められた条件下で神経衰弱が進んでいく様を再現していきます。
``` ruby
#神経衰弱の再現
(1..L).each do |l|
    h_1, w_1, h_2, w_2 = gets.chomp.split.map(&:to_i)
    has_cards = []
    has_cards.push(cards[h_1 - 1][w_1 - 1])
    has_cards.push(cards[h_2 - 1][w_2 - 1])
    if has_cards[0] == has_cards[1]
        scores[player] += 2
    else
        player == N - 1 ? player = 0 : player += 1
    end
end
```

### ①捲るカードの位置、中身を取得
``` ruby
h_1, w_1, h_2, w_2 = gets.chomp.split.map(&:to_i)
has_cards = []
has_cards.push(cards[h_1 - 1][w_1 - 1])
has_cards.push(cards[h_2 - 1][w_2 - 1])
```
h_1,w_1に1枚目の位置h_2,w_2に2枚目の位置を保存します。
これはプレイヤーがカードを捲る位置を表します。

そしてそのカードを捲ってみて覚える動作を再現するため
has_cardsつまり持っているカードを配列に格納して一度状況を格納します。
ここでcards[h_1 - 1][w_1 - 1]と書いて
pushメソッドでhas_cardsの末尾にデータを格納していますが
cardsの配列の添字は0から始まりますが、
**入力されるデータは１から行数や列数をカウントするため
取得位置を合わせるために-1を入れています。**

### ②引いたカードを確認して次のターンに移動する
``` ruby
if has_cards[0] == has_cards[1]
        scores[player] += 2
else
    player == N - 1 ? player = 0 : player += 1
end
```

先ほど格納したカードを比較して同じ数字であれば
各プレイヤーが取得するカードということで2枚ずつ加算していきます。

もし一致しなかったら次の人にターンが移るのでプレイヤーを入れ替える処理をします。
``` ruby
player == N - 1 ? player = 0 : player += 1
```
ここではプレイヤーの参加人数と比較して最後の番の人まで回ってきたら
また一番最初のプレイヤーに順番を回します。

ここでもNでプレイヤーの人数を表現していますが配列の状況と合わなくなるため
-1することで配列と状況を合わせるためにこのように設定します。

### ③各プレイヤーが取得した枚数を表示する
``` ruby
puts scores
```
最後に各プレイヤーが取得した枚数を表示します。
ここでputsを使っているのは
出力形式が以下のように設定されているためです。
6
0
このように各プレイヤーの枚数を
改行して表示されるように出力することを求められていますので
一行ごとに改行が入るputsメソッドを使用して出力しています。

## 最後に
どうでしょうか？
ここまで読んでいただき
一つ一つの区切りをつけて見ると
**繰り返し文、条件分岐(if文)、変数での条件の保存**
これができていれば大体の問題がクリアできそうです。

また神経衰弱というわかりやすい題材から取り組むと
理解しやすく状況も作りやすいでしょう。

ぜひみなさんも自分の取り組みやすい問題から順番に
まずはCランクで繰り返しや条件分岐などの
書き方を学び理解できたらそれを組み合わせるBランクに挑戦してみましょう。

わかりにくいところなどありましたらコメント欄やXなどぜひでシェアしてください。

これからもみなさんで共有しながら問題クリアしていきましょう。