2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【農家はreplace()されました】迷路攻略(The Farmer Was Replaced : Maze)

Posted at

とりあえずコピペで動かしたい人用

clear()
maze_size = 16
N = 100  # N回宝箱を再配置する

# 初回のセットアップ
plant(Entities.Bush)
substance = maze_size * 2**(num_unlocked(Unlocks.Mazes) - 1)
use_item(Items.Weird_Substance, substance)

directions = [East, South, West, North]  # 時計回りの方角

chk_list = []  # ある場所を確認したかチェックするリスト
distance_list = []  # 宝箱までの距離をチェックするリスト
for _ in range(maze_size):
    tmp_list1 = []
    tmp_list2 = []
    for _ in range(maze_size):
        tmp_list1.append(False)
        tmp_list2.append(-1)
    chk_list.append(tmp_list1)
    distance_list.append(tmp_list2)


# 方角を壁の方向を示す数値に変換する関数(移動距離半分)
def direc2vec_half(x, y, direction):
    if direction==East:
        return x+0.5, y
    elif direction==South:
        return x, y-0.5
    elif direction==West:
        return x-0.5, y
    elif direction==North:
        return x, y+0.5
    
# 方角を壁の方向を示す数値に変換する関数
def direc2vec(x, y, direction):
    if direction==East:
        return x+1, y
    elif direction==South:
        return x, y-1
    elif direction==West:
        return x-1, y
    elif direction==North:
        return x, y+1

# 初回の迷路解析
can_move_simu = {}
index = 0
cnt = 0
while True:
    # もしまだ確認していない場所なら
    x, y = get_pos_x(), get_pos_y()
    if not(chk_list[x][y]):
        for direction in directions:
            can_move_simu[direc2vec_half(x, y, direction)] = can_move(direction)
        chk_list[x][y] = True
        cnt += 1
    # すべての場所を調べたら終了
    if cnt == maze_size * maze_size:
        break
    # 右壁に沿って進む
    next_index = (index + 1) % 4
    if can_move(directions[next_index]):
        index = next_index
    elif not(can_move(directions[index])):
        index = (index - 1) % 4
    move(directions[index])

for n in range(N):
	# 初期化
    for x in range(maze_size):
        for y in range(maze_size):
            chk_list[x][y] = False
            distance_list[x][y] = maze_size*maze_size*maze_size
	# 宝箱の位置の距離を0にする
    x_treasure, y_treasure = measure()
    distance_list[x_treasure][y_treasure] = 0
	# 宝箱までの道のりを価値で示す
    if get_entity_type() != Entities.Treasure:
        x_now, y_now = get_pos_x(), get_pos_y()
        queue = [[x_treasure, y_treasure, None]]  # 次に調べる場所のqueue(x, y, どの方向に進んできたか)
        complete_flg = False  # 宝箱までの道のりが完成したフラグ
        while True:
            next_queue = []
            for x, y, came_from in queue:
                for k in range(4):
                    # 移動してきた方向はスキップする
                    if directions[(k+2)%4] == came_from:
                        continue
                    # もし移動可能で未更新の場所があれば
                    direction = directions[k]
                    next_x, next_y = direc2vec(x, y, direction)
                    if can_move_simu[direc2vec_half(x, y, direction)]:
                        if not(chk_list[next_x][next_y]):
                            next_queue.append([next_x, next_y, direction])
                            distance_list[next_x][next_y] = distance_list[x][y] + 1
                            chk_list[next_x][next_y] = True
                            # もし現在位置までの道のりが完成すれば
                            if (x_now==next_x) and (y_now==next_y):
                                complete_flg = True
            queue = next_queue
            if complete_flg:
                break
            
    # 迷路を解く			
    while True:
        x, y = get_pos_x(), get_pos_y()
		# 価値が1高い方に進んでいく
        for direction in directions:
            next_x, next_y = direc2vec(x, y, direction)
            # もし移動可能で価値が1高いなら
            if can_move_simu[direc2vec_half(x, y, direction)]:
                if (distance_list[next_x][next_y]+1==distance_list[x][y]):
                    # 進める方向を更新しておく
                    for direction_ in directions:
                        can_move_simu[direc2vec_half(x, y, direction_)] = can_move(direction_)
                    move(direction)
                    break
        # 宝箱があれば
        if get_entity_type() == Entities.Treasure:
            x, y = get_pos_x(), get_pos_y()
            for direction in directions:
                can_move_simu[direc2vec_half(x, y, direction)] = can_move(direction)
            break
	# 再配置
    if n == N - 1:
        harvest()
    else:
        use_item(Items.Weird_Substance, substance)
        print(n, '/', N)

はじめに

先日ver1.0がリリースされたゲーム、農家はreplace()されました(The Farmer Was Replaced)の迷路の攻略例。(ほぼ)pythonのプログラムを組んで農業するゲームであり、迷路はその中のミッション(?)の1つ。見た目はこんな感じで、中央付近に見えるのは宝箱、左下の帽子をかぶったドローンが自分。

image.png

この迷路の特徴は以下の通り。

  • 初回は右手法(左手法)で解ける迷路が生成される
  • 宝箱の位置は取得可能
  • 自分の位置と、自分の位置から東西南北に進めるか(壁があるか)は取得可能
  • 宝箱を取得すると迷路のサイズに応じた報酬が得られる
  • 宝箱を取得する代わりに300回まで再配置が可能。再配置すると報酬が迷路1回分増え、壁がランダムに削除されることがある

単純に解くだけなら壁に沿って動いていけばいつかは宝箱に辿り着くが、ここでは再配置を繰り返して効率的に報酬を稼ぐことを考える。迷路の特徴を踏まえて、以下のステップで迷路を攻略する。

  1. 初めに右手法で全マスの移動可能な方向を調べておく
  2. 宝箱からの距離のマップをつくる
  3. マップを元に現在位置から宝箱までの距離が1少なくなるマスに進んでいく
  4. 宝箱を見つけたら再配置し2.に戻る

以下コードの該当部分について解説していく。

初めに右手法で全マスの移動可能な方向を調べておく

# 初回の迷路解析
can_move_simu = {}
index = 0
cnt = 0
while True:
    # もしまだ確認していない場所なら
    x, y = get_pos_x(), get_pos_y()
    if not(chk_list[x][y]):
        for direction in directions:
            can_move_simu[direc2vec_half(x, y, direction)] = can_move(direction)
        chk_list[x][y] = True
        cnt += 1
    # すべての場所を調べたら終了
    if cnt == maze_size * maze_size:
        break
    # 右壁に沿って進む
    next_index = (index + 1) % 4
    if can_move(directions[next_index]):
        index = next_index
    elif not(can_move(directions[index])):
        index = (index - 1) % 4
    move(directions[index])

宝箱の位置や自分の位置は以下の座標で取得できる。基本的には自分のいる位置の東西南北しか壁の有無を確かめられないが、初回は右手法解ける(右手法で全マスを通ることができる)迷路が生成されるので、初回の内に調べておく。例えば以下のような6×6の迷路が生成される。

image.png

壁の位置はcan_move_simuという辞書の中に保存しておく。マスは0~Nの整数で表されるので、keyは(0.5, 0)のようなxかyが0.5ずれた座標にする。こうすることで、(0, 0)から(0, 1)に移動するときも、(1, 0)から(0, 0)に移動するときも同じ値を参照できる。(後に移動しながら壁の有無を更新するとき楽になる。)全マス確認したら終わり。

宝箱からの距離のマップをつくる

# 宝箱の位置の距離を0にする
x_treasure, y_treasure = measure()
distance_list[x_treasure][y_treasure] = 0
# 宝箱までの道のりを価値で示す
if get_entity_type() != Entities.Treasure:
    x_now, y_now = get_pos_x(), get_pos_y()
    queue = [[x_treasure, y_treasure, None]]  # 次に調べる場所のqueue(x, y, どの方向に進んできたか)
    complete_flg = False  # 宝箱までの道のりが完成したフラグ
    while True:
        next_queue = []
        for x, y, came_from in queue:
            for k in range(4):
                # 移動してきた方向はスキップする
                if directions[(k+2)%4] == came_from:
                    continue
                # もし移動可能で未更新の場所があれば
                direction = directions[k]
                next_x, next_y = direc2vec(x, y, direction)
                if can_move_simu[direc2vec_half(x, y, direction)]:
                    if not(chk_list[next_x][next_y]):
                        next_queue.append([next_x, next_y, direction])
                        distance_list[next_x][next_y] = distance_list[x][y] + 1
                        chk_list[next_x][next_y] = True
                        # もし現在位置までの道のりが完成すれば
                        if (x_now==next_x) and (y_now==next_y):
                            complete_flg = True
        queue = next_queue
        if complete_flg:
            break

欲しいマップのイメージは以下の通り。

image.png

0が宝箱の位置で、そこから進める方向の1ずつ数字が増えていく。これをdistance_list[x][y]に記録していく。同じ場所を上書きしないようchk_listで管理しておく。(宝箱の再配置をしないなら不要。)また、全マップをつくる必要はなく、自分のいる位置までできたところでマップの作成をやめる。そうすれば近くに宝箱があるときにすぐに移動できる。

マップを元に現在位置から宝箱までの距離が1少なくなるマスに進んでいく

# 迷路を解く			
while True:
    x, y = get_pos_x(), get_pos_y()
    # 価値が1高い方に進んでいく
    for direction in directions:
        next_x, next_y = direc2vec(x, y, direction)
        # もし移動可能で価値が1高いなら
        if can_move_simu[direc2vec_half(x, y, direction)]:
            if (distance_list[next_x][next_y]+1==distance_list[x][y]):
                # 進める方向を更新しておく
                for direction_ in directions:
                    can_move_simu[direc2vec_half(x, y, direction_)] = can_move(direction_)
                move(direction)
                break
    # 宝箱があれば
    if get_entity_type() == Entities.Treasure:
        x, y = get_pos_x(), get_pos_y()
        for direction in directions:
            can_move_simu[direc2vec_half(x, y, direction)] = can_move(direction)
        break

後は宝箱の距離が1縮み、かつ移動可能な方向へ進んでいくだけ。ここでは宝箱の再配置を前提にしているため、ついでに通った場所の壁の有無を更新しておく。進む方向を決める前に更新すると事故るので、進む方向が決まった後に更新して進む。こうすれば繰り返す内に壁が少なくなり、宝箱までの距離も縮んでいく。

おわりに

しばらく動かしてうまくいっているので問題ないはず。改善案あれば教えてください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?