簡単なゲームを作りながらPythonを学ぶ

## はじめに
[独学プログラマー](https://www.amazon.co.jp/%E7%8B%AC%E5%AD%A6%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC-Python%E8%A8%80%E8%AA%9E%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%8B%E3%82%89%E4%BB%95%E4%BA%8B%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9%E3%81%BE%E3%81%A7-%E3%82%B3%E3%83%BC%E3%83%AA%E3%83%BC%E3%83%BB%E3%82%A2%E3%83%AB%E3%82%BD%E3%83%95/dp/4822292274/ref=sr_1_1?adgrpid=57356695670&hvadid=338526277128&hvdev=c&hvlocphy=1009461&hvnetw=g&hvpos=1t1&hvqmt=e&hvrand=12669604124011409084&hvtargid=kwd-362748005277&jp-ad-ap=0&keywords=%E7%8B%AC%E5%AD%A6%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC&qid=1554538525&s=gateway&sr=8-1)という本を参考にしながらPythonについて少し学びました。

Rubyについては学習済みで、Pythonの文法に慣れるために本に記載されているゲームのコードを改良したりしました。
その際に学んだことを記事にしようと思います。

## ゲームルール
[この本](https://www.amazon.co.jp/%E7%8B%AC%E5%AD%A6%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC-Python%E8%A8%80%E8%AA%9E%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%8B%E3%82%89%E4%BB%95%E4%BA%8B%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9%E3%81%BE%E3%81%A7-%E3%82%B3%E3%83%BC%E3%83%AA%E3%83%BC%E3%83%BB%E3%82%A2%E3%83%AB%E3%82%BD%E3%83%95/dp/4822292274/ref=sr_1_1?adgrpid=57356695670&hvadid=338526277128&hvdev=c&hvlocphy=1009461&hvnetw=g&hvpos=1t1&hvqmt=e&hvrand=12669604124011409084&hvtargid=kwd-362748005277&jp-ad-ap=0&keywords=%E7%8B%AC%E5%AD%A6%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%83%BC&qid=1554538525&s=gateway&sr=8-1)にのっているハングマンというゲームのルールを説明します。

>1. プレイヤー１がプログラム上で単語を決めて、単語の文字の数だけ、アンダースコア(_)を引きます。
>2. プレイヤー２に単語を予想させて、１回に1文字ずつ回答させていきます。
>3. プレイヤー２が回答した文字が隠してある単語に含まれていたら、プレイヤー１が書いておいた下線のその文字があるべきところに、文字が表示されます。１つの単語に同じ文字が２つ以上含まれている場合は、回答1回につき1文字だけ表示されます。プレイヤー２の回答が間違っていた場合、用意してある絵の一部が現れます(上の方から)。
>4. 用意されている絵（吊られた人の絵）が完成する前に、プレイヤー２が隠された単語の文字を全て当てられたら、プレイヤー２の勝ちです。逆に、絵が完成した場合は負けです。

<br>
元々のソースコードはこちら
<br>

~~~hangman.py
def hangman(word):
    wrong = 0
    stages = ["",
              "_______      ",
              "|            ",
              "|      |     ",
              "|      0     ",
              "|     /|\    ",
              "|     / \    ",
              "|            "
              ]
    rletters = list(word)
    board = ["_"] * len(word)
    win = False
    print("ハングマンへようこそ!")
    while wrong < len(stages) - 1:
        print("\n")
        msg = "1文字を予想してね! "
        char = input(msg)
        if char in rletters:
            cind = rletters.index(char)
            board[cind] = char
            rletters[cind] = '$'
        else:
            wrong += 1
        print(" ".join(board))
        e = wrong + 1
        print("\n".join(stages[0:e]))
        if "_" not in board:
            print("あなたの勝ち!")
            print(" ".join(board))
            win = True
            break
    if not win:
        print("\n".join(stages[0:wrong+1]))
        print("あなたの負け!正解は {}.".format(word))

hangman("cat")
~~~
<br>
ターミナル上で、`python hangman.py`と入力し、実行すると
<br>
<img width="1178" alt="スクリーンショット 2019-04-06 18.01.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/350795/1637f132-2eda-0313-e2f3-17aea03f75ab.png">
<img width="1178" alt="スクリーンショット 2019-04-06 18.01.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/350795/c38d29d4-7a40-014f-0501-1d4d3bb5c144.png">

という流れになりますが、これだと毎回プログラム上で単語を決めてから実行となるので
ターミナル上でプレイヤー１が単語を決めることができるようにしてみます。
<br>

## コードを改良してみる

まず、プレイヤー１がターミナル上で単語を決める際に、ターミナル上で入力した内容をわからないようにするために`getpass`というモジュールを使ってみます。
`getpass`モジュールの`getpass()`関数を使うと、入力した内容が表示されなくなります。

ではコードを編集してみましょう。

~~~hangman.py
import getpass  #getpassモジュールを読み込むために記述
import re       #reモジュールを読み込むことで正規表現が使えるようになります

## 以下省略

    if not win:
        print("\n".join(stages[0:wrong+1]))
        print("あなたの負け!正解は {}.".format(word))

## ↓ここから追記↓

while True:
    player1 = "player1: 英単語を入力してね!(3文字以上6文字以内)"
    question = getpass.getpass(player1)
    if not re.match('^[a-z]+$', question):
        print("英字で入力してね")
    else:
        if len(question) >= 3 and len(question) <= 6:
            break
        else:
            print("3文字以上6文字以内だよ!")

hangman(question) #引数をプレイヤー１が入力した値にする。
~~~
<br>
プレイヤー１が決める英単語が長すぎたら当てるのが難しく、短すぎたらすぐ当たって面白くないので
とりあえず3文字以上6文字以内に制限してみました。この辺は個人のお好みで変えてください。

コードの説明としては、`while True:`で、if文の条件（プレイヤー１が3文字以上、6文字以内の文字を入力）を満たすまでループするようにしています。
そして、len関数を使用して文字列の長さを取得しています。
記述方法は、`len(オブジェクト)`となります。

また、英字かどうかの判定を`if not re.match('^[a-z]+$', question):`で行なっています。
詳しい正規表現の使い方に関しては[こちら](https://qiita.com/wanwanland/items/ce272419dde2f95cdabc)を参考にしてください。
英字かどうかの判定についての詳しい記事は[こちら](http://www.thothchildren.com/chapter/5b180aea52392816a2f569a5)

あとは、プレイヤー１が入力した内容（　`question`　）を引数として渡してゲーム開始となります。

次に、プレイヤー２が入力する部分を編集しましょう。

~~~hangman.py
##   ↓↓↓      ここの部分を編集        ↓↓↓

    while wrong < len(stages) - 1:
        print("\n")
        msg = "1文字を予想してね! "
        char = input(msg)
        if char in rletters:
            cind = rletters.index(char)
            board[cind] = char
            rletters[cind] = '$'
        else:
            wrong += 1


##   ↓↓↓      以下のように書き換える        ↓↓↓

    while wrong < len(stages) - 1:
        while True:
            print("\n")
            msg = "player2: 1文字(英字小文字)を予想してね! "
            char = input(msg)
            if not re.match('^[a-z]+$', char):
                print("英字(小文字)で入力してね")
            else:
                if len(char) == 1:
                    break
                else:
                    print("1文字ずつだよ!")
~~~

プレイヤー１の時と同じ様に、`while True:`で、if文の条件（プレイヤー2が1文字の英単語を入力）を満たすまでループするようにしています。

変わったこととしては、今までは1文字以上入力していても次の動作を行なってしまっていたために、文字数制限をかけたのと、プレイヤー２が入力する際の、表示を変えたぐらいです。

これでプログラムを実行すると、プレイヤー１の入力した内容がプレイヤー２にわからない様になっています。

<img width="813" alt="スクリーンショット 2019-04-08 14.16.07.png" src="https://qiita-image-store.s3.amazonaws.com/0/350795/2dea7e68-55e7-d98f-f613-52c75415e08c.png">

これで、対戦型ゲームとして遊びやすい内容となりました。
他にもPythonで簡単に作れるお手軽なゲームはたくさんあり、楽しみながら学習できるので是非調べてみてください。
個人的には対戦型のゲームで、ヌメロンというゲームが面白いと感じたので、ヌメロンを作ることをお勧めします。

Pythonには便利なモジュールがたくさん用意されているので、積極的に活用していきましょう。

筆者は、まだエンジニアとして働いたことのないプログラミング学習歴2ヶ月程度の未経験エンジニアなので
まだまだコードに改善の余地があると思いますので、気づいた方はご指摘していただけると嬉しいです。
よろしくお願いします。

### 完成したコード(対戦型)

~~~hangman.py
import getpass
import re

def hangman(word):
    wrong = 0
    stages = ["",
              "_______      ",
              "|            ",
              "|      |     ",
              "|      |     ",
              "|      |     ",
              "|      |     ",
              "|      |     ",
              "|      0     ",
              "|     /|\    ",
              "|     / \    ",
              "|            "
              ]
    rletters = list(word)
    board = ["_"] * len(word)
    win = False
    print("ハングマンへようこそ!")
    while wrong < len(stages) - 1:
        while True:
            print("\n")
            msg = "player2: 1文字(英字小文字)を予想してね! "
            char = input(msg)
            if not re.match('^[a-z]+$', char):
                print("英字(小文字)で入力してね")
            else:
                if len(char) == 1:
                    break
                else:
                    print("1文字ずつだよ!")
        if char in rletters:
            cind = rletters.index(char)
            board[cind] = char
            rletters[cind] = '$'
        else:
            wrong += 1
        print(" ".join(board))
        e = wrong + 1
        print("\n".join(stages[0:e]))
        if "_" not in board:
            print("あなたの勝ち!")
            print(" ".join(board))
            win = True
            break
    if not win:
        print("\n".join(stages[0:wrong+1]))
        print("あなたの負け!正解は {}.".format(word))

while True:
     player1 = "player1: 英単語を入力してね!(3文字以上6文字以内)"
     question = getpass.getpass(player1)
     if not re.match('^[a-z]+$', question):
         print("英字で入力してね")
     else:
         if len(question) >= 3 and len(question) <= 6:
             break
         else:
             print("3文字以上6文字以内だよ!")

hangman(question)
~~~


## 最後に

このゲームを対戦型ではなく、一人でも遊べる様にしたコードも作ったので、記載しておきます。
興味がある方は、見ていって下さい。

### 仕様

>一人で遊べる様に、自動で単語が選択される様にする
>まずは、辞書から3文字の単語を取り出す
>取り出した単語の中からランダムで選択
>自動で選択された単語をプレイヤーが当てる様にする

はじめに、hangmanのコードを書いているファイルにコードを追加すると、可読性が下がるので、
自動で単語を選択するコードは別ファイルに書きましょう。
`choice.py`を作成します。
<br>

~~~choice.py
import random  #Pythonでランダムな処理が必要な場合に活躍する標準モジュール

f = open ('/usr/share/dict/words','r')
lines = f.read().splitlines()
f.close()

input_words = []
for i in range(len(lines)):
    if len(lines[i]) == 3:
        input_words.append(lines[i].lower())

input_word = input_words[random.randint(0,len(input_words))]
~~~
<br>
[こちら](https://www.cyamax.com/entry/2017/02/04/111905)の記事を参考にして、辞書ファイルからランダムに単語を抽出する様にしました。

コードの説明としては、まず`random`モジュールを読み込み、`randint`関数が使える様にしています。

linuxには辞書ファイルがり、Macの場合、辞書ファイルは`/usr/share/dict/words`にある様です。
なので、`open()`を用いてファイルを開いています。`open()`と`f.close()`はセットで覚えてください。
`f.close()`を使わない方法として`with`を用いた書き方もあるみたいなので、興味がある方は調べてみてください。

そして、単語の最後に改行が含まれるので`.splitlines()`を使用して`改行文字（\n）`で分割してリストとして返す様にしています。
その他、`replace`で置換する方法もあります。

for文とif文を利用して、取り出した単語の中から、3文字の単語にだけ処理をする様にしています。
処理内容は、単語に大文字が含まれているものもあるので、文字列を大文字から小文字へ変換するメソッド`.lower()`を使用して、`input_words`に追加しています。

最後の行では、`randint`関数を用いて、0から`input_words`の要素数の間でランダムな整数を選ぶ様にしています。
そのランダムで選ばれた整数を用いて、3文字の単語のみが格納された`input_words`から単語を取り出す様にしています。

あとは、`hangman.py`を少し編集して完成です。
<br>

~~~hangman.py
# import getpass を削除
# import re も削除
import choice  # この記述でchoice.pyの変数を使える様にする

# while True:
#     player1 = "player1: 英単語を入力してね!(3文字以上6文字以内)"
#     question = getpass.getpass(player1)
#     if not re.match('^[a-z]+$', question):
#         print("英字で入力してね")
#     else:
#         if len(question) >= 3 and len(question) <= 6:
#             break
#         else:
#             print("3文字以上6文字以内だよ!")

#   ↑↑↑  　　　この部分を削除        ↑↑↑
#   ↓↓↓       ここから追記          ↓↓↓

question = choice.input_word
hangman(question)
~~~
<br>
`question`に、`choice.py`の`input_word`を代入しています。
これで、一人でも遊べる様になりました。

しかし、序盤に1文字当てれないとどんな単語か予想がしづらく、難易度が高めなので
何回か間違えたら1文字表示させてあげる様に親切設計にするといいかもしれません。

### 完成したコード(一人用)

~~~hangman.py
import choice

def hangman(word):
    wrong = 0
    stages = ["",
              "_______      ",
              "|            ",
              "|      |     ",
              "|      |     ",
              "|      |     ",
              "|      |     ",
              "|      |     ",
              "|      0     ",
              "|     /|\    ",
              "|     / \    ",
              "|            "
              ]
    rletters = list(word)
    board = ["_"] * len(word)
    win = False
    print("ハングマンへようこそ!")
    while wrong < len(stages) - 1:
        while True:
            print("\n")
            msg = "player2: 1文字(英字小文字)を予想してね! "
            char = input(msg)
            if not re.match('^[a-z]+$', char):
                print("英字(小文字)で入力してね")
            else:
                if len(char) == 1:
                    break
                else:
                    print("1文字ずつだよ!")
        if char in rletters:
            cind = rletters.index(char)
            board[cind] = char
            rletters[cind] = '$'
        else:
            wrong += 1
        print(" ".join(board))
        e = wrong + 1
        print("\n".join(stages[0:e]))
        if "_" not in board:
            print("あなたの勝ち!")
            print(" ".join(board))
            win = True
            break
    if not win:
        print("\n".join(stages[0:wrong+1]))
        print("あなたの負け!正解は {}.".format(word))

question = choice.input_word
hangman(question)
~~~