全自動ババ抜き　作ってみた

# はじめに
大学のテスト勉強中に突如としてババ抜きを作りたいと思い立ち、どうせなら全自動ババ抜きを作ろうということで作りました。
しかし、ただ作りたかったというだけでなく、importの仕方という初歩中の初歩を記事として投稿したあげく、間違いを指摘されるほどの初心者だった自分が、どれほど成長できたのかという記録としての意味もあります。

この記事は、主に全自動ババ抜きプログラムの解説や自分の思ったことなどを書いています。
しばらくお付き合いください。

コードは[Gist](https://gist.github.com/k-Acer/0dd0de8d4161be703b57b25b46da67a0)に貼ってあります。

[2021年4月１日追記]
改めてコードを見返してみると
コメントで @shiracamus さんが暗示しているように
コードの可読性が低いな。。。

# プログラムの流れ
1. 山札をシャッフル
2. 山札を各プレイヤーに分配
3. 各プレイヤーの手札から重複したカードを削除
4. 各プレイヤーに順番でカードを引かせる
5. 手札がなくなったプレイヤーは上がり
6. プレイヤーが残り一人になったらゲーム終了

#各プレイヤーに手札を配るまでの処理
```
stock = [("Spade_A",1),("Spade_2",2), *** ,("Heart_K",13),("Joker",0)]

# 山札のシャッフル
random.shuffle(stock)

# playerNum人分の手札を用意
playerHand = [["Player{}".format(i)] for i in range(playerNum)]

# 各プレイヤーにカードを分配
while len(stock) != 0:
  for i in range(playerNum):
      if len(stock) != 0:
         playerHand[i].append(stock[0])
         stock.pop(0)  
```

stockは山札で、("Spade_A",1)はスペードのAを表しています。***で省略されていますが、実際にはこれが("Joker",0)までの５３個あります。
文字列と数値に分けられている理由は後々説明します。

`random.shuffle(stock)`で山札をシャッフルし
`[["Player{}".format(i)] for i in range(playerNum)]`ではplayerNum人分の手札を用意しています。
仮にplayerNum=3とすると、`[["Player0"]["Player1"]["Player2"]]` のようなリストが出来上がります。
`while len(stock) != 0:` 以下は、山札が0になるまで各プレイヤーに手札を配り、配ったものから削除しています。
 
# 重複したカードを削除する処理
```
playerHand = [["Player0",("Spade_A",1), *** ,("Heart_5",5)],
              ["Player1",("Spade_5",5), *** ,("Heart_5",5)],
              ["Player2",("Diamond_5",5), *** ,("Heart_5",5)]]

# playerHand = [[Player0の手札],[Player1の手札],[Player2の手札]]
```
これまでの処理の結果、playerHand変数は上のようになっていると思います。
つぎは、各プレイヤーの手札から重複したカードを削除していきます。

```
# iはプレイヤーの数(プレイヤーごとに手札を削除していく)
for i in range(playerNum):
   #ｊは各プレイヤーの手札の枚数        
   for j in range(len(playerHand[i])):
       val = playerHand[i][j][0]
       num = playerHand[i][j][1]
            
       for k in range(len(playerHand[i])):
           if val != playerHand[i][k][0] and num == playerHand[i][k][1]:
              playerHand[i][j] = "Delete"
              playerHand[i][k] = "Delete"                
              break 
   #filterにより重複していたものを削除
   playerHand[i] = list(filter(lambda x:x != "Delete", playerHand[i]))

```
###削除プログラムの流れ
1. "プレーヤー0"の0番目の手札の文字列をval、数値をnumとして所持
2. "プレイヤー0"自身の持つほかの手札と比較していき、文字列は異なるが、数値は同じカードがあるかどうかをチェックしていく
3. あった場合はそのカードと0番目の手札に"Delete"を代入する
4. 0番目の手札の処理が終わったら1,2番目とみていく
5. 最終的に削除すべき手札のカードはすべて"Delete"になる
6. filterを用いて"Delete"を除く
5. "プレイヤー0"の手札が完成したら"プレイヤー1"をみていく

###数値と文字列を分けている理由
前半で、("Spade_A",1)のように手札を文字列と数値に分けている理由を説明するといいましたが、その理由は、そうしないと0番目の手札が0番目自身をチェックする際に、数値が同じである（当然ですが）ため"Delete"が代入されてしますからです。
正直、分けなくてもできるのであればその方がいいと思うので、アイデアがある方は教えてくださるととてもうれしいです。

#各プレイヤーに順番でカードを引かせる処理

```
for i in range(playerNum):

    if i == playerNum - 1:
        i = -1

    if len(playerHand[i]) != 1 and len(playerHand[i + 1]) != 1:
        player_choice_card = playerHand[i + 1][random.randrange(1, len(playerHand[i + 1]))]
        playerHand[i].insert(1, player_choice_card)
        playerHand[i + 1].remove(player_choice_card)
```
上のプログラムは、プレイヤーiがプレイヤーi+1からランダムにカードを選択する処理です。選ばれたカードはプレイヤーiの手札に加えられ、プレイヤーi+1の手札からは削除されます。

```
if i == playerNum - 1:
        i = -1
```
この処理は、たとえば３人(player0, player1, player2)でババ抜きをている場合、プレイヤー2はプレイヤー0からカードを引かなければならない。という問題に対応しています。
上の処理がないとplayer2はplayer3という存在しないプレイヤーから手札を引こうとしてエラーを起こします。
この問題をどうしようかと結構悩みましたが、最後のプレイヤーの場合はi=-1とすることで解決することができました。

つぎに、引いた手札と自分の手札を見比べて、数値が同じものがあった場合は削除しなければなりませんが、この処理は重複したカードを削除する処理とほとんど同じなので割愛します。

# まとめ
この後に、あがりの処理やランキングをつける処理を実装すれば、全自動ババ抜きの完成です。
ここまで解説しておいてなんですが、初心者にとって自分の頭で考え、実装することは多くの学びがあると思います。なのでぜひ、自分で実装してみてください。
また、このプログラムにはまだまだ改善点がたくさんあると思います。なので、ここが悪いという点がありましたらコメントしていただけるととてもありがたいです。

# おわりに
最後までお付き合いいただき、ありがとうございます。記事を書くといった経験が皆無だったため、読みづらく、かつ説明不十分なところが多々あると思います。
そういった点がありましたら、ぜひ指摘していただけるとありがたいです。
ありがとうございました。