ゼロからのPython入門講座演習 適切なデータ型を使う

スイカ割りゲームを作ってみようリファクタリング - マジックナンバーを避ける適切なデータ型を使う関数を活用する

元のソースコードでは、スイカとプレイヤーの位置を、x座標とy座標それぞれ別々の変数として保持しています。

suika_x = random.randrange(0, BOARD_SIZE)  # スイカのx座標
suika_y = random.randrange(0, BOARD_SIZE)  # スイカのy座標

player_x = random.randrange(0, BOARD_SIZE) # プレイヤーのx座標
player_y = random.randrange(0, BOARD_SIZE) # プレイヤーのy座標

しかし、スイカやプレイヤーの座標は本来一つの情報ですので、x座標とy座標で2つの変数に分ける意味はありません。こういった場合、

(x座標, y座標)

という形式の、2つの整数からなる タプル にするように、リファクタリング するのが良いでしょう。

ここでは、タプルを利用する練習も兼ねて、位置情報をタプルに置き換えていきましょう。

座標の生成方法を変更

現在、スイカとプレイヤーの座標は、x座標とy座標に分けて、四つの変数に代入されています。これを、suika_posplayer_pos という名前の、タプルを参照する変数に変更しましょう。

In [2]:
import random

# スイカの位置のタプル
suika_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE))

# プレイヤーの位置のタプル
player_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE)) 

変数名 suika_posplayer_pospos は、英語の 位置(position) から取っています。

生成した値を表示してみましょう。

In [3]:
print("スイカの位置は", suika_pos)
print("プレイヤーの位置は", player_pos)
スイカの位置は (2, 3)
プレイヤーの位置は (1, 0)

データの形式が決まりましたので、プログラム全体で、スイカとプレイヤーの座標データとして、このタプルを使うように書き換えていきます。

距離計算関数を修正

二点間の距離を計算する関数 calc_distance() は、引数 として2つの位置のx座標とy座標を受け取り、距離を計算して返します。

In [ ]:
def calc_distance(x1, y1, x2, y2):
    # 2点間の距離を求める
    diff_x = x1 - x2
    diff_y = y1 - y2
    
    return math.sqrt(diff_x**2 + diff_y**2)

この関数を呼び出すときは、四つの数字を指定します。

In [8]:
x1 = 10
y1 = 10
x2 = 20
y2 = 20

calc_distance(x1, y1, x2, y2)  # 座標 (10, 10) から 座標 (20, 20) までの距離を計算する
Out[8]:
14.142135623730951

この関数の 引数 を、別々にx座標とy座標を指定するのではなく、座標をタプルで指定するように変更しましょう。

In [10]:
def calc_distance(pos1, pos2): # <- この行を修正
    # 2点間の距離を求める
    diff_x = pos1[0] - pos2[0] # <- この行を修正
    diff_y = pos1[1] - pos2[1] # <- この行を修正
    
    return math.sqrt(diff_x**2 + diff_y**2)

呼び出すときは、次のようにタプルを指定します。

In [11]:
pos1 = (10, 10)
pos2 = (20, 20)

calc_distance(pos1, pos2)  # 座標 (10, 10) から 座標 (20, 20) までの距離を計算する
Out[11]:
14.142135623730951

データを2つのタプルにまとめることで、2つの位置のx座標とy座標の数値のまとまりが明確になり、シンプルになっているのがわかると思います。

while文を修正

ゲームの中心となるwhile 文の、ループ継続条件は次のようになっています。

# スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_x != player_x) or (suika_y != player_y):
    ...

修正前の書き方では、スイカとプレイヤーの位置を、x座標とy座標で別々に比較しています。

修正後は、座標をタプルに変更していますから、次のように、2つの値を一度に比較できます。

# 修正後
while suika_pos != player_pos:
    ...

x座標とy座標を別々に比較するのに比べて、すっきりとしていますね。2つのタプルを == を比較する方法については、タプルの比較演算子 を復習しておいてください。

プレイヤー移動処理を変更

元の処理では、キー入力に応じてプレイヤーを移動する処理は次のようになっています。

# キー入力に応じて、プレイヤーを移動する
c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
if c == "n":
    player_y = player_y - 1
elif c == "s":
    player_y = player_y + 1
elif c == "w":
    player_x = player_x - 1
elif c == "e":
    player_x = player_x + 1

タプルオブジェクトの操作 で説明したように、タプルは、リスト と違って要素を別々に変更できません。

プレイヤーの位置を player_pos から取り出して、座標を更新してから新しいタプルを作るように修正します。

# 修正後

# キー入力に応じて、プレイヤーを移動する
c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")

current_x, current_y = player_pos

if c == "n":
    current_y = current_y - 1
elif c == "s":
    current_y = current_y + 1
elif c == "w":
    current_x = current_x - 1
elif c == "e":
    current_x = current_x + 1

player_pos = (current_x, current_y)

タプルのアンパック

この処理では、次のように変数 player_pos からx座標とy座標の値を取り出しています。

current_x, current_y = player_pos

これは、コレクションのアンパック で紹介した、タプルをアンパックした代入文です。player_pos の最初の要素と次の要素を、それぞれ変数 current_xcurrent_y に代入しています。

まとめると

以上で、スイカとプレイヤーの座標をタプルとして保持するための修正が終わりました。最終的なプログラムは次のようになります。

In [13]:
import random
import math

BOARD_SIZE = 5  # ボードの初期サイズ

def calc_distance(pos1, pos2):
    # 2点間の距離を求める
    diff_x = pos1[0] - pos2[0]
    diff_y = pos1[1] - pos2[1]
    
    return math.sqrt(diff_x**2 + diff_y**2)


suika_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE))  # スイカの位置
player_pos = (random.randrange(0, BOARD_SIZE), random.randrange(0, BOARD_SIZE)) # プレイヤーの位置

# スイカとプレイヤーの位置が異なる間、処理を繰り返す
while (suika_pos != player_pos):

    # スイカとプレイヤーの距離を表示する
    distance = calc_distance(suika_pos, player_pos)
    print("スイカへの距離:", distance)
    
    # キー入力に応じて、プレイヤーを移動する
    c = input("n:北に移動 s:南に移動 e:東に移動 w:西に移動")
    current_x, current_y = player_pos

    if c == "n":
        current_y = current_y - 1
    elif c == "s":
        current_y = current_y + 1
    elif c == "w":
        current_x = current_x - 1
    elif c == "e":
        current_x = current_x + 1

    player_pos = (current_x, current_y)

print("スイカを割りました!")
スイカへの距離: 1.0
スイカへの距離: 1.4142135623730951
スイカへの距離: 1.0
スイカを割りました!
Copyright © 2001-2023 python.jp Privacy Policy python_japan
Amazon.co.jpアソシエイト
Amazonで他のPython書籍を検索