AI要約
プラレールの制御に向け、Pythonで簡易的なシミュレータを開発しました。レール配置や車両の挙動を仮想空間で再現し、動作テストの基盤を整えました。実機に触れずに動作検証ができ、開発の効率化に貢献します。
やること
前回、マイクロサーボでターンアウトレールのポイント切り替えを行いました。
これから車両の制御をやっていきたいのですが、何をやるにもまずはシミュレータが必要です。今回はプラレールの簡易的なシミュレータを作ってみます。
参考文献
こちらのサイトでプラレールの様々なレイアウトが公開されています。見ているだけで楽しいですね!

テストコース
このようなコースを考えます。

次のようにIDを割り当てました。

電車を遷移させていくため、有向グラフとしてパスを定義します。
#パスの定義
path = {0:1, 1:2, 2:3, 3:4, 4:5, 5:6, 6:7, 7:[8, 14], 8:[9, 20], 9:10, 10:11, 11:12, 12:13, 13:0,
14:15, 15:16, 16:17, 17:18, 18:19, 19:6, 20:21, 21:13}
#分岐の状態
switch = {7:0, 8:0}
#線路の座標、可視化のため
xy_set = {0:[30, 0], 1:[20, 0], 2:[10, 0], 3:[5, 7], 4:[5, 13], 5:[10, 20], 6:[20, 20], 7:[30, 20],
8:[40, 20], 9:[50, 20], 10:[55, 13], 11:[55, 7], 12:[50, 0], 13:[40, 0], 14:[35, 27],
15:[35, 33], 16:[30, 40], 17:[20, 40], 18:[15, 33], 19:[15, 27], 20:[45, 13], 21:[45, 7]}
分岐の状態は「0=直進」「1=曲がる」です。各レールの座標は後で可視化のために使うので本質的には不要です。
電車クラス
電車クラスを作っておき、3台走らせるなら3インスタンス立ち上げるようにすると良いと思います。train1.pos のように電車1の位置を取得できてシンプルです。
#電車のクラス
class TrainClass:
def __init__(self, name='a', speed=0.2, pos=0, color='yellow'):
self.name = name
self.speed = speed
self.pos = pos
self.color = color
#レール進行率
self.progress = 0.0
#1フレーム進める
def move(self):
#レール進行率を加算
self.progress += self.speed
#次のレールに移った場合
if self.progress >= 1.0:
self.progress = self.progress - 1.0
#分岐からの遷移なら
if self.pos in switch.keys():
s = switch[self.pos]
self.pos = path[self.pos][s]
#そうでないなら
else:
self.pos = path[self.pos]
#まだこのレールにいる場合
else:
pass
電車は「どのレールに乗っているか」「レールの中で何%進行したか」という2つの値で位置情報を持っています。そして、電車のスピードは「1フレームで何%進行するか」を意味しています。
例えば、スピード0.17の電車がレール0の0.9(90%)にいる場合、次のフレームではレール1の0.07(7%)に移ります。(伝われ)
カラーと名前は可視化のために用意しています。
試しに電車を作って10フレーム進めてみましょう。
train1 = TrainClass(name='a', speed=0.17, pos=0, color='yellow')
print(train1.pos, train1.progress)
for i in range(10):
train1.move()
print(train1.pos, train1.progress)
0 0.0
0 0.17
0 0.34
0 0.51
0 0.68
0 0.85
1 0.02
1 0.19
1 0.36
1 0.53
1 0.7
いい感じですね。
可視化関数
レールと電車の状態を可視化する関数を作ります。これはどこまでこだわるかによります。テクニック的なことで何か言うとすれば、ax.set_aspect(‘equal’) は座標軸を1:1のアスペクト比にしてくれるので便利です(マス目が正方形になるという意味、伝われ)。
import matplotlib.pyplot as plt
#可視化関数
def show(time=0):
fig = plt.figure(figsize=(8, 8))
ax = plt.axes()
#パス
for i, (key, value) in enumerate(path.items()):
if type(value) is list:
for v in value:
plt.plot([xy_set[key][0], xy_set[v][0]], [xy_set[key][1], xy_set[v][1]], '-', color='gray')
else:
plt.plot([xy_set[key][0], xy_set[value][0]], [xy_set[key][1], xy_set[value][1]], '-', color='gray')
#レール
for i, (key, value) in enumerate(xy_set.items()):
x, y = value
plt.plot(x, y, 'o', color='w', mec ='k', markersize=20)
plt.text(x + 1.4, y + 0.6, f'{key}')
#分岐の状態
for i, (key, value) in enumerate(switch.items()):
plt.text(xy_set[key][0] + 1.6, xy_set[key][1] - 1.8, f'to {path[key][value]}')
#電車
for train in trains:
x, y = xy_set[train.pos]
plt.plot(x, y, 'o', color=train.color, mec ='k', markersize=20)
plt.text(x - 0.4, y - 0.4, f'{train.name}')
plt.title(f'time = {time}')
ax.set_aspect('equal')
plt.xlim(0, 60)
plt.ylim(-5, 45)
plt.show()
plt.close()
複数の電車を配列に入れることを想定して作ってしまったので、1台でも配列に入れる必要があります。動作を確認してみましょう。
#電車の作成
trains = []
trains.append(TrainClass(name='a', speed=0.17, pos=0, color='yellow'))
#確認
show(time=0)
#シミュレーション開始
for i in range(1, 100):
#電車を進める
for train in trains:
train.move()
#確認
show(time=i)

良さそうですね。
3台の電車
3台で走らせてみましょう。
#電車の作成
trains = []
trains.append(TrainClass(name='a', speed=0.17, pos=0, color='yellow'))
trains.append(TrainClass(name='b', speed=0.23, pos=4, color='orange'))
trains.append(TrainClass(name='c', speed=0.41, pos=19, color='green'))

良さそうですが、電車ぶつかってすり抜けています。
衝突判定
衝突判定を追加しましょう。同じレールに複数の電車が乗ったら「Crash!!」と表示して終了させます。set() を使った重複判定が便利ですね。
#シミュレーション開始
for i in range(1, 100):
#電車を進める
for train in trains:
train.move()
#確認
show(time=i)
#衝突チェック
all_pos = [train.pos for train in trains]
#重複があれば衝突なので終了
if len(all_pos) != len(set(all_pos)):
print('Crash!!')
break

35フレーム目で衝突して終わりました。いい感じです!
おわりに
分岐はずっと直進でやりましたが、曲がることもできるので試してみてください。
次回に続きます。