地下施設での強制労働をPythonにやらせる
プロローグ
ロシアの文豪ドストエフスキーの著作「死の家の記録」にはこんな話がある。
シベリアに流刑された男は、半日かけて穴を掘り、半日かけてその穴を埋めるという強制労働をひたすらやらされた。朝、自分で掘った穴を、午後、自分で埋めている。そんなふうになったら、誰だって生きる意味が見出せなくなる。何の意味もない単純作業を延々とやらされると、やがて人は精神に異常をきたし発狂する。
何の価値もない、むだな労働を延々と強制させられる。まるでカイジの地下施設。
これはえげつない...。人間なら長くは持たないだろう…。
そうだ。Pythonにやらせればいいじゃないか!
↑ 犠牲になるPythonくん(一応へび🐍のつもり。カエルではない)
そして以下のgifが地下労働施設で強制労働の様子だ...。
「穴を掘ってその穴を埋める」作業を延々に繰り返している。
健気にがんばっているPythonくんに心うたれる。がんばれー!
全体像
Pythonくんは優秀なので、ただまっすぐな穴を掘らせるのでは芸がない。迷路をつくらせよう。そして毎回同じ迷路では見てるほうも退屈なので、新しい迷路をつくらせ続けよう。
これから実装しなければならないことは2つだ。
- Pythonくんに毎回新しい迷路を掘らせる
- Pythonくんに迷路を埋めさせる
これを無限回、こちらの気が済むまでくり返してもらう。
1.Pythonくんに毎回新しい迷路を掘らせる
迷路生成のやり方はいくつかあるが、ここでは「穴掘り法」を使用する。
迷路の定義
以下の条件を満たすものを迷路とする。
- 到達できない閉じた空間が存在しない
- ループできる通路は存在しない
つまりこの図みたいにはしない。
追記(2020/05/13):@otagaisama-1さんの指摘の通り、グラフで言うと、孤立点のない一つの木をつくって迷路にする。
それでは迷路をどうやってつくるかを見ていこう。
穴掘り法のアルゴリズム
イメージとしては深さ優先探索の要領でドンドン掘り進めて、進めなくなったら掘れるところまで戻ってまた再開する。
- 迷路の幅と高さは奇数にする
- 迷路は初めすべて「かべ」とする
- 始点
S(x, y), x,yは奇数
を決める - 以下を再帰的に行う
- 現在の座標
(x, y)
を「通路」にする - 4方向からランダムに選ぶ。2マス先が「かべ」ならそこまで「通路」にし、4.1に戻る
- どこにも進めなくなったら戻る
- 現在の座標
深さ優先探索の実装には
- スタックを使う
- 再帰関数を使う
がある。ここでは読みやすさのために再帰関数でやった。
穴掘り法の実装
穴掘り法を実行するmake_maze()
。説明のため疑似コードっぽく書いている。
def make_maze(self, y, x):
# 2マス先まで掘りすすめる
dx = [(1, 2), (-1, -2), (0, 0), (0, 0)] # x方向
dy = [(0, 0), (0, 0), (1, 2), (-1, -2)] # y方向
# 4方向いずれに進むかランダムに優先順位をつける
array = list(range(4))
random.shuffle(array)
for i in array:
# 2マス先の座標(nx, ny)
nx = x + dx[i][1]
ny = y + dy[i][1]
# 迷路の外に出ていたらダメ
if not(0 <= ny < self.HEIGHT) or not(0 <= nx < self.WIDTH):
continue
# 2マス先がすでに通路になっている場合、掘るとループになってしまうのでダメ
if self.maze[ny][nx] == "通路":
continue
# 通路を掘る
for j in range(2):
ax = x + dx[i][j]
ay = y + dy[i][j]
if self.maze[ay][ax] == "カベ":
self.maze[ay][ax] = "通路"
# 掘った先で再帰的に呼び出す
self.make_maze(ny, nx)
make_maze()
を呼び出して、迷路を作成するgenerate_maze()
。(命名が雑..)
def generateMaze(self):
# 最初はすべてカベ
self.maze = [["カベ"]*self.WIDTH for _ in range(self.HEIGHT)]
# ランダムなスタート地点から迷路を作る
sx, sy = self.get_random_start()
self.maze[sy][sx] = "通路"
# 穴掘り法を実行
self.make_maze(sy, sx)
# プレイヤーをスタートに配置
self.maze[sy][sx] = "プレイヤー"
これでPythonくんが良い感じに迷路を掘ってくれるようになった。
2.Pythonくんに迷路を埋めさせる
埋めさせるアルゴリズム
特に思いつかなかったので、穴を掘った順番と逆順に埋めてもらうことにした。
実装
穴掘り法をするときに掘った順番をlog_of_dig
に保持しておく。
def make_maze(self, y, x):
# 省略
for i in array:
# 省略
# 通路を掘る
for j in range(2):
ax = x + dx[i][j]
ay = y + dy[i][j]
if self.maze[ay][ax] == "カベ":
self.maze[ay][ax] = "通路"
self.log_of_dig.append((ax, ay))
# 掘った先で再帰的に呼び出す
self.make_maze(ny, nx)
これを逆順にたどるだけ。
self.log_of_visit = self.log_of_dig[::-1]
これでよし。
強制労働プログラムの完成
GitHubにコードをあげた。dannyso16/Maze
ここまでで「穴を掘って埋める」順番は求められているので、あとはよしなに画面に描画してあげるだけだ。ゲームエンジンPyxelでつくった。
めんどくさい部分はMap
クラスに切り分けたりしたので、気になる人はGitHubを見てほしい。
最後に
健気に労働するPythonくんを見ながら飲むコーヒーはおいしいですね(最悪)
もし借金を背負って地下施設行きが決まりそうになったら、Pythonくんにやってもらいましょう!!
おまけ
「穴掘り法」をまじめに使うと下みたいな迷路ゲームができる。
ゴールはスタートから一番遠いところに設定してみた。ミニマップがあるとそれっぽくて良い感じ。
参考
dannyso16/Maze:書いたプログラム
Author And Source
この問題について(地下施設での強制労働をPythonにやらせる), 我々は、より多くの情報をここで見つけました https://qiita.com/odanny/items/9f7d4ea611763cb84355著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .