『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

ランダムウォーク(乱歩)

Pygame Zero の連載記事第2回です。今回はランダムウォークのアルゴリズムを使って、ボールをジグザグに動き回らせてみます。

Pythonでランダムウォーク

次の移動地点がランダム(確率的)に決定される運動をランダムウォークといいます。日本語では酔歩とか乱歩ということもあります。酔歩というのは言い得て妙ですね。すぐ後で実装して確認しますが、まさに酔っ払いのごとく、ふらふらとあちこちを動き回ります。実際のゲームでは、全くでららめに動くキャラクターというのは使い道がなさそうです。しかし、ランダムウォークの基本を知っておくと、予測不能な動きを交えながら全体としてある方向に動くようなキャラクターも作れます。

単純なランダムウォークのモデルを実装してみましょう。前回と同様に、最初に画像を用意しておきます。適当な場所に mygame フォルダを作成し、その中に imgages という名前の画像保管用フォルダを作成します。次に、KENNEY のサイトの Rolling Ball Assets のページに行って、rollingballassets_kenney.zip ファイルをダウンロードしてください。rollingballassets_kenney.zip を開いて、PNG フォルダ、Default フォルダの順に開き、ball_red_small.png を images フォルダにドラッグ & ドロップでコピーします。さらにファイルの名前を ball_red.png に変更しておきます。以下のサンプルコード (.py ファイル) はすべて、mygame フォルダに保存してください。
 
四方向に転がるボールを考え、どの方向も等しい確率で選択されるとします。つまり、上下左右どの方向に転がる確率も等しく 1/4 ずつです。以下のコードを実行すると、赤いボールが動き回ります。

# random_walk_01.py

import pgzrun
import random

WIDTH = 600
HEIGHT = 600

ball = Actor('ball_red', center=(300, 300))

# ボールの速度
v = 2

# 4方向の動き
motion_list = [(v, 0), (-v, 0), (0, v), (0, -v)]  # (1)

# 移動方向を格納するリスト
motion = 0

def update():
    global motion
    motion = random.choice(motion_list)  # (2)
    ball.x += motion[0]
    ball.y += motion[1]

def draw():
    screen.clear()
    ball.draw()

pgzrun.go()

Python ジグザグにあちこち動き回る赤いボール
(1) では、(x 方向の移動距離、y 方向の移動距離) を 4 種類リストに格納しています。たとえば、(-v, 0) はスクリーンの x 方向のマイナス方向に v だけ動く移動を表しています。
 
(2) は motion_list から動き方を等しい確率でランダムに選択して、変数 motion に格納するコードです。random.choice() という便利な関数があるおかげで、if で条件分枝させなくてすんでいます。
 
random_walk_01.py はボールが小刻みに動きすぎて、実際のゲームでは使えそうにありません。そこで、経過時間を格納する変数を用意して、ある程度一定方向に進んでから方向転換するようにしてみます。

# random_walk_02.py

import pgzrun
import random

WIDTH = 600
HEIGHT = 600

ball = Actor('ball_red', center=(300, 300))

v = 2
count = 0  # カウンター
motion_list = [(v, 0), (-v, 0), (0, v), (0, -v)]
motion = random.choice(motion_list)

def update():
    global count
    global motion
    count += 1
    if count > 30:  # 前の方向転換から0.5秒経過したら
        motion = random.choice(motion_list)
        count = 0  # カウンターを初期化
    ball.x += motion[0]
    ball.y += motion[1]

def draw():
    screen.clear()
    ball.draw()

pgzrun.go()

count には update() が呼び出された回数が記録されます。update() は 1 秒間に 60 回呼び出されるので、count が 60 になったとき、1 秒が経過していることになります。random_walk_02.py では、0.5 秒経過するごとに random.choice() を再実行して、進む方向を変えるようにしてあります。random_walk_02.py を実行すると、ボールが揺れることなく、時々向きを変えながら動き回ります。

次はボールが四方向だけでなく、360°自由な方向に移動できるようにしてみましょう。一定時間が経過するごとに、次に進む方角θを 0 ~ 360°の範囲でランダムに決めます。図のように、x 軸から時計回りに角度θを測り、速度が v ピクセルに設定されているとします。
Python 360度自由な方向に無作為に転がるボールのアルゴリズム
このとき、x 座標と y 座標の更新幅は、それぞれ三角関数を用いて
 
 ⊿x = v cosθ, ⊿y = v sinθ
 
と表されます。次のコードを実行して、ボールが色々な方向へ動き回る様子を確認してください。

# random_walk_3.py

import pgzrun
import random
import math

WIDTH = 600
HEIGHT = 600

ball = Actor('ball_red', center=(300, 300))

# ボールの速度
v = 2

# ボールの移動角度
angle = 0

# タイムカウンター
count = 0

def update(delta_x=0, delta_y=0):
    global angle
    global count
    count += 1
    if count == 30:  # 前回の方向転換から0.5秒経っていたら
        angle = 2 * math.pi * random.random()
        count = 0
    delta_x = v * math.cos(angle)  # x方向の移動距離
    delta_y = v * math.sin(angle)  # y方向の移動距離
    if ball.x < 10:  # ボールが左端に来たら
        delta_x += abs(delta_x)  # (3)
    elif ball.x > WIDTH - 10:  # ボールが右端に来たら
        delta_x -= abs(delta_x)  # (4)
    if ball.y < 10:  # ボールが上端に来たら
        delta_y += abs(delta_y)
    elif ball.y > HEIGHT - 10:  # ボールが下端に来たら
        delta_y -= abs(delta_y)
    ball.x += delta_x
    ball.y += delta_y

def draw():
    screen.clear()
    ball.draw()

pgzrun.go()

今回のコードでは境界条件も設定してあるので、ボールがスクリーンの外へ行かないようになっています。たとえば、(3) ではボールが左端にきたとき、abs() 関数で delta_x の絶対値をとって、ボールが必ず x 軸のプラス方向へ動いて端から離れるようにしてあります。逆に (4) では、ボールが右端に来たとき、必ず x 軸のマイナス方向へ動くようにしています。

コメント

  1. あとりえこばと より:

    【AI連載小説】科学とコードの交差点(58)
    「Pythonでランダムウォークの軌跡を描こう」
     
    Pythonサークルのメンバーはランダムウォークの軌跡を描くプログラムを作成するために一堂に会しています。ノートパソコンと資料が机に広がり、三人はコードを書きながらアイディアを出し合っています。
    開誠(ノートパソコンの画面を指差しながら):「今日はランダムウォークの軌跡を描くプログラムを作りたいな」
    美純(興奮気味で):「面白そう! どうやってやるの?」
    明信(キーボードを叩きながら):「まず、ランダムな方向に進む一歩をどう表現するかだな。ランダムな方向ってことは、角度をランダムに決めて、その方向に進むってことか」
    開誠(コードを書きながら):「そうだ。それに、一歩進む距離もランダムに決めよう」

    import matplotlib.pyplot as plt
    import numpy as np
    
    def random_walk(num_steps):
        x, y = [0], [0]
    
        for _ in range(num_steps):
            angle = np.random.uniform(0, 2 * np.pi)
            distance = np.random.uniform(0, 1)
            
            x.append(x[-1] + distance * np.cos(angle))
            y.append(y[-1] + distance * np.sin(angle))
    
        return x, y
    
    # ランダムウォークの軌跡を生成
    num_steps = 1000
    x_trajectory, y_trajectory = random_walk(num_steps)
    
    # グラフの描画
    plt.plot(x_trajectory, y_trajectory, label="Random Walk")
    plt.scatter(x_trajectory[0], y_trajectory[0], color='green', marker='o', label="Start")
    plt.scatter(x_trajectory[-1], y_trajectory[-1], color='red', marker='x', label="End")
    plt.title("Random Walk Trajectory")
    plt.xlabel("X-axis")
    plt.ylabel("Y-axis")
    plt.legend()
    plt.grid(True)
    plt.show()

    美純(グラフを見ながら):「すごい! これでランダムに歩いた軌跡が分かるわね」
    明信(にやりと笑いながら):「面白いだろ? numpyのおかげで数学的な操作も楽ちんだよ」
    開誠(続けて):「パラメータを変えたり、複数のランダムウォーカーを描いたりするのも面白いかもしれないね」