Pythonでスイカ割りゲームを作ってみよう

# はじめに
昨年から[「ゼロからのPython入門講座」](https://www.python.jp/train/index.html)（[「Python japan」サイト](https://www.python.jp/index.html)）を使ってPythonの勉強を行っています。
基本的な学習は終了して **「演習」** に進みましたので、独自に課題に取り組みたいと思います。

# スイカ割りゲームを作ってみよう
**演習** の最初の課題である[「スイカ割りゲームを作ってみよう」](https://www.python.jp/train/exercise/index.html)に取り組みます。
課題の詳しい内容は、[「スイカ割りゲームを作ってみよう」](https://www.python.jp/train/exercise/index.html)を参照してください。
ゲームを簡単に説明すると、下図の様なマス目のフィールドがあり、その何処かのマスにスイカがあるので、プレイヤー（自分）の位置を移動してスイカと同じマス目に入る（スイカを割った状態）と言うものです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/cf759119-d591-ebdd-22e1-b42e840e91c0.png)
スイカの位置を推理するヒントとしては、スイカとプレイヤー間の距離を表示します。

# 実際に作ったプログラム
※Google Colaboratory (Colab)の使い方については、[「Python の学習を始めました」](https://qiita.com/pump33/items/81561e68bbfcba561b95)を御参照ください。
```py
# スイカ割りゲーム

# 算術用モジュール
import math
# 乱数を使用する為のモジュール
import random

# 距離の計算関数
def calc_distance(suika, player):
    diff_x = suika[0] - player[0]
    diff_y = suika[1] - player[1]
    return math.sqrt(diff_x**2 + diff_y**2)

# メイン処理

# スイカとプレイヤーの位置を初期化
suika = (random.randrange(0, 10), random.randrange(0, 10))
player = (random.randrange(0, 10), random.randrange(0, 10))

# スイカが割れるまで、キー入力を繰り返す。
while suika != player:
    print("スイカは割れていません。")
    print("自分の位置：", player)
    print("スイカまでの距離：", calc_distance(suika, player))
    key = input("←：a／↑：e or i／↓：c or m／→：l　")
    if (key == "a") and (player[0] > 0):
        player = (player[0] - 1, player[1])
    elif (key == "e" or key == "i") and (player[1] > 0):
        player = (player[0], player[1] - 1)
    elif (key == "c" or key == "m") and (player[1] < 9):
        player = (player[0], player[1] + 1)
    elif (key == "l") and (player[0] < 9):
        player = (player[0] + 1, player[1])

# ゲーム終了時の表示
print("###########################################")
print("おめでとう御座います。スイカは割れました。")
print("スイカの位置は", suika, "でした。")
print("###########################################")
```
正解については、[「スイカ割りゲームを作ってみよう」](https://www.python.jp/train/exercise/index.html)を参照して頂くとして、上記は、私がほぼ独自（勝手）に作成したものです。[「ゼロからのPython入門講座」](https://www.python.jp/train/index.html)の[「Python初体験」](https://www.python.jp/train/experience/index.html)～[「タプルとコレクション」](https://www.python.jp/train/tuple/unpack.html)で学んだ知識で作る事ができました。
一部、ゲーム開始時にスイカとプレイヤーの位置をランダムに決める方法と、スイカとプレイヤーの距離を計測する方法は、[「スイカ割りゲームを作ってみよう」](https://www.python.jp/train/exercise/index.html)の各項目を参考にさせて頂きました。
- [「スイカとプレイヤーの初期位置」](https://www.python.jp/train/exercise/index.html#スイカとプレイヤーの初期位置)
- [「スイカとプレイヤーの位置を設定する」](https://www.python.jp/train/exercise/index.html#スイカとプレイヤーの位置を設定する)

スイカとプレイヤーの位置（情報）を保持する変数には、[タプル](https://www.python.jp/train/tuple/index.html#%E3%82%BF%E3%83%97%E3%83%AB)を使用しました。
　 **ex.** `suika = (x軸の位置, y軸の位置)`
スイカとプレイヤーの位置をランダムに決めるには、`randrange()`関数を使用します。この関数を使用するには`random`モジュールを読み込む必要があります。
```py
# 乱数を使用する為のモジュール
import random
    ：
# スイカとプレイヤーの位置を初期化
suika = (random.randrange(0, 10), random.randrange(0, 10))
player = (random.randrange(0, 10), random.randrange(0, 10))
```
スイカとプレイヤーの距離を計測するには、平方根の計算が必要なので`sqrt()`関数を使用します。この関数を使用するには`math`モジュールを読み込む必要があります。
```py
# 算術用モジュール
import math
    ：
# 距離の計算関数
def calc_distance(suika, player):
    diff_x = suika[0] - player[0]
    diff_y = suika[1] - player[1]
    return math.sqrt(diff_x**2 + diff_y**2)
```
プレイヤーを動かすキーは、←（左）：`〔a〕`、↑（上）：`〔e〕`or`〔i〕`、↓（下）：`〔c〕`or`〔m〕`、→（右）：`〔l〕`です。（キーは個人の趣味です :sweat:）

# プログラムを動かしてみる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/510221/a105b4cf-9279-21c9-0ea1-da75d3b5ff6a.png)
```
スイカは割れていません。
自分の位置： (8, 3)
スイカまでの距離： 5.0990195135927845
←：a／↑：e or i／↓：c or m／→：l　a
スイカは割れていません。
自分の位置： (7, 3)
スイカまでの距離： 4.123105625617661
←：a／↑：e or i／↓：c or m／→：l　a
スイカは割れていません。
自分の位置： (6, 3)
スイカまでの距離： 3.1622776601683795
←：a／↑：e or i／↓：c or m／→：l　a
スイカは割れていません。
自分の位置： (5, 3)
スイカまでの距離： 2.23606797749979
←：a／↑：e or i／↓：c or m／→：l　e
スイカは割れていません。
自分の位置： (5, 2)
スイカまでの距離： 2.0
←：a／↑：e or i／↓：c or m／→：l　a
スイカは割れていません。
自分の位置： (4, 2)
スイカまでの距離： 1.0
←：a／↑：e or i／↓：c or m／→：l　a
###########################################
おめでとう御座います。スイカは割れました。
スイカの位置は (3, 2) でした。
###########################################
```

# 実際に作ったプログラム（改良版）
一応、上記の様にプログラムは実行できたのですが、文字が表示されるだけで色気がないので少し改良してみました。
10×10のフィールドを、記号（`+`,`-`,`|`）を使ってマトリクスで表示して、プレイヤーの位置を`*`で示す様にします。
```py
# スイカ割りゲーム

# 算術用モジュール
import math
# 乱数を使用する為のモジュール
import random
# 色々便利なモジュール
import IPython

# 距離の計算関数
def calc_distance(suika, player):
    diff_x = suika[0] - player[0]
    diff_y = suika[1] - player[1]
    return math.sqrt(diff_x**2 + diff_y**2)
# マトリクスの表示関数
def create_matrix(position):
    IPython.display.clear_output(True)
    y = 0
    print("   0 1 2 3 4 5 6 7 8 9")
    print("  +-+-+-+-+-+-+-+-+-+-+")
    while y < 10:
        x = 0
        row = str(y) + " |"
        while x < 10:
            if x == position[0] and y == position[1]:
                row = row + "*|"
            else:
                row = row + " |"
            x = x + 1
        print(row)
        y = y + 1
    print("  +-+-+-+-+-+-+-+-+-+-+")

# メイン処理

# スイカとプレイヤーの位置を初期化
suika = (random.randrange(0, 10), random.randrange(0, 10))
player = (random.randrange(0, 10), random.randrange(0, 10))

create_matrix(player)

# スイカが割れるまで、キー入力を繰り返す。
while suika != player:
    create_matrix(player)
    print("スイカは割れていません。")
    print("自分の位置：", player)
    print("スイカまでの距離：", calc_distance(suika, player))
    key = input("←：a／↑：e or i／↓：c or m／→：l　")
    if (key == "a") and (player[0] > 0):
        player = (player[0] - 1, player[1])
    elif (key == "e" or key == "i") and (player[1] > 0):
        player = (player[0], player[1] - 1)
    elif (key == "c" or key == "m") and (player[1] < 9):
        player = (player[0], player[1] + 1)
    elif (key == "l") and (player[0] < 9):
        player = (player[0] + 1, player[1])

# ゲーム終了時の表示
create_matrix(player)
print("###########################################")
print("おめでとう御座います。スイカは割れました。")
print("スイカの位置は", suika, "でした。")
print("###########################################")
```
下記がマトリクス（プレイヤー）を表示する関数、`create_matrix(position)`です。
引数の`position`には、プレイヤーの位置を示す変数をタプル（`(x軸の位置, y軸の位置)`）で指定します。
また、`print()`関数だけで表示を繰り返すと、前に表示されていた文字がそのままとなってしまうので、`clear_output()`関数を使用して前に表示されていた画面をクリアする様にします。この関数を使用するには`IPython`モジュールを読み込む必要があります。
`clear_output()`関数については、カックさんが公開している[「kakakakakku blog」](https://kakakakakku.hatenablog.com/)の[「Jupyter Notebook で clear_output() を使って定期的に表示を更新する」](https://kakakakakku.hatenablog.com/entry/2020/03/18/121440)を参考にさせて頂きました。
```py
# 色々便利なモジュール
import IPython
    ：
# マトリクスの表示関数
def create_matrix(position):
    IPython.display.clear_output(True)
    y = 0
    print("   0 1 2 3 4 5 6 7 8 9")
    print("  +-+-+-+-+-+-+-+-+-+-+")
    while y < 10:
        x = 0
        row = str(y) + " |"
        while x < 10:
            if x == position[0] and y == position[1]:
                row = row + "*|"
            else:
                row = row + " |"
            x = x + 1
        print(row)
        y = y + 1
    print("  +-+-+-+-+-+-+-+-+-+-+")

```

# プログラムを動かしてみる（改良版）
実行方法は前回と同じです。
```
   0 1 2 3 4 5 6 7 8 9
  +-+-+-+-+-+-+-+-+-+-+
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | |*| | | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
  +-+-+-+-+-+-+-+-+-+-+
スイカは割れていません。
自分の位置： (3, 4)
スイカまでの距離： 2.8284271247461903
←：a／↑：e or i／↓：c or m／→：l　
```
```
   0 1 2 3 4 5 6 7 8 9
  +-+-+-+-+-+-+-+-+-+-+
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | |*| | | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
  +-+-+-+-+-+-+-+-+-+-+
スイカは割れていません。
自分の位置： (4, 4)
スイカまでの距離： 2.23606797749979
←：a／↑：e or i／↓：c or m／→：l　l
```
```
   0 1 2 3 4 5 6 7 8 9
  +-+-+-+-+-+-+-+-+-+-+
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | |*| | | | |
5 | | | | | | | | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
  +-+-+-+-+-+-+-+-+-+-+
スイカは割れていません。
自分の位置： (5, 4)
スイカまでの距離： 2.0
←：a／↑：e or i／↓：c or m／→：l　c
```
```
   0 1 2 3 4 5 6 7 8 9
  +-+-+-+-+-+-+-+-+-+-+
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | |*| | | | |
6 | | | | | | | | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
  +-+-+-+-+-+-+-+-+-+-+
スイカは割れていません。
自分の位置： (5, 5)
スイカまでの距離： 1.0
←：a／↑：e or i／↓：c or m／→：l　c
```
```
   0 1 2 3 4 5 6 7 8 9
  +-+-+-+-+-+-+-+-+-+-+
0 | | | | | | | | | | |
1 | | | | | | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
5 | | | | | | | | | | |
6 | | | | | |*| | | | |
7 | | | | | | | | | | |
8 | | | | | | | | | | |
9 | | | | | | | | | | |
  +-+-+-+-+-+-+-+-+-+-+
###########################################
おめでとう御座います。スイカは割れました。
スイカの位置は (5, 6) でした。
###########################################
```
少し、ゲームっぽくなったかな:question: :sweat: 

---
#### 2023年3月8日 追記
# 実際に作ったプログラム（改良版の改良）
上記の投稿の後、[「ゼロからのPython入門講座」](https://www.python.jp/train/index.html)の[「リファクタリング - マジックナンバーを避ける」](https://www.python.jp/train/exercise/refactor.html)[「適切なデータ型を使う」](https://www.python.jp/train/exercise/refactor-proper-type.html)[「関数を活用する」](https://www.python.jp/train/exercise/refactor-function.html)まで学習を進めました（「入門講座」を終了しました）ので、その結果を反映させます。
なお、「適切なデータ型を使う」については、独自のプログラミングで殆ど対応していました。:v::sweat_smile:  
改良の要点としては、全体的な関数化とフィールドを動的に変更できる様にした事です。
```py
# スイカ割りゲーム

# 算術用モジュール
import math
# 乱数を使用する為のモジュール
import random
# 色々便利なモジュール
import IPython

# 距離の計算関数
def calc_distance(suika, player):
    diff_x = suika[0] - player[0]
    diff_y = suika[1] - player[1]
    return math.sqrt(diff_x**2 + diff_y**2)
# マトリクスの表示関数
def create_matrix(position, keisen, field):
    IPython.display.clear_output(True)
    y = 0
    print(keisen[0])
    print(keisen[1])
    while y < field[1]:
        x = 0
        row = str(y) + " |"
        while x < field[0]:
            if x == position[0] and y == position[1]:
                row = row + "*|"
            else:
                row = row + " |"
            x = x + 1
        print(row)
        y = y + 1
    print(keisen[1])
# X軸罫線の作成関数
def create_keisen_x(size):
    x = 0
    retsu_bango = "   "
    keisen_x = "  +"
    while x < size:
        retsu_bango = retsu_bango + str(x) + " "
        keisen_x = keisen_x + "-+"
        x = x + 1
    return (retsu_bango, keisen_x)
# 位置の初期化関数
def generate_position(size_x, size_y):
    x = random.randrange(0, size_x)
    y = random.randrange(0, size_y)
    return (x, y)
# プレイヤーの移動関数
def move_position(key, position, field):
    x, y = position    # タプルのアンパック
    if (key == "a") and (x > 0):
        x = x - 1
    elif (key == "e" or key == "i") and (y > 0):
        y = y - 1
    elif (key == "c" or key == "m") and (y < (field[1] - 1)):
        y = y + 1
    elif (key == "l") and (x < (field[0] - 1)):
        x = x + 1
    return (x, y)

# メイン処理
def suika_wari(field):
    # スイカとプレイヤーの位置を初期化
    suika = generate_position(field[0], field[1])
    player = generate_position(field[0], field[1])

    keisen = create_keisen_x(field[0])
    create_matrix(player, keisen, field)

    # スイカが割れるまで、キー入力を繰り返す。
    while suika != player:
        create_matrix(player, keisen, field)
        print("スイカは割れていません。")
        print("自分の位置：", player)
        print("スイカまでの距離：", calc_distance(suika, player))
        key = input("←：a／↑：e or i／↓：c or m／→：l　")
        player = move_position(key, player, field)

    # ゲーム終了時の表示
    create_matrix(player, keisen, field)
    print("###########################################")
    print("おめでとう御座います。スイカは割れました。")
    print("スイカの位置は", suika, "でした。")
    print("###########################################")

# フィールドのサイズ (x: 1～10, y: 1～10)で指定する。
FIELD = (10, 10)

# メイン処理の実行
suika_wari(FIELD)
```
`create_keisen_x(size)`関数で、フィールドのX軸の列番号ならびに罫線を作成（タプル）します。
作成する列数は、引数`size`で指定します。
```py
# X軸罫線の作成関数
def create_keisen_x(size):
    x = 0
    retsu_bango = "   "
    keisen_x = "  +"
    while x < size:
        retsu_bango = retsu_bango + str(x) + " "
        keisen_x = keisen_x + "-+"
        x = x + 1
    return (retsu_bango, keisen_x)
```
`create_keisen_x(size)`関数の処理結果を次のプログラムで確認します。（`size`に1～10を指定して作成された罫線を表示します）
```py
# create_keisen_x()関数の動作確認用処理
i = 1
while i <= 10:
    keisen = create_keisen_x(i) 
    print("i:", i)
    print(keisen[0])
    print(keisen[1])
    i = i + 1
```
処理結果は次の様になります。
```
i: 1
   0 
  +-+
i: 2
   0 1 
  +-+-+
i: 3
   0 1 2 
  +-+-+-+
i: 4
   0 1 2 3 
  +-+-+-+-+
i: 5
   0 1 2 3 4 
  +-+-+-+-+-+
i: 6
   0 1 2 3 4 5 
  +-+-+-+-+-+-+
i: 7
   0 1 2 3 4 5 6 
  +-+-+-+-+-+-+-+
i: 8
   0 1 2 3 4 5 6 7 
  +-+-+-+-+-+-+-+-+
i: 9
   0 1 2 3 4 5 6 7 8 
  +-+-+-+-+-+-+-+-+-+
i: 10
   0 1 2 3 4 5 6 7 8 9 
  +-+-+-+-+-+-+-+-+-+-+
```
列番号も罫線も正しく作成されています。

ゲーム（プログラム）を実行する前に`FIELD`の値をを変更しておくと、フィールドの大きさが自動的に変更されます。
```py
FIELD = (10, 5)
```
```
   0 1 2 3 4 5 6 7 8 9 
  +-+-+-+-+-+-+-+-+-+-+
0 | | | | | | | | | | |
1 | | | |*| | | | | | |
2 | | | | | | | | | | |
3 | | | | | | | | | | |
4 | | | | | | | | | | |
  +-+-+-+-+-+-+-+-+-+-+
スイカは割れていません。
自分の位置： (3, 1)
スイカまでの距離： 2.23606797749979
←：a／↑：e or i／↓：c or m／→：l
```
```py
FIELD = (3, 8)
```
```
   0 1 2 
  +-+-+-+
0 | |*| |
1 | | | |
2 | | | |
3 | | | |
4 | | | |
5 | | | |
6 | | | |
7 | | | |
  +-+-+-+
スイカは割れていません。
自分の位置： (1, 0)
スイカまでの距離： 4.0
←：a／↑：e or i／↓：c or m／→：l
```
勿論、`FIELD`を変更する事により、`suika`と`player`の初期化の位置、`player`の移動範囲も自動的に変更されています。

以上です。:wink: