AmongUsエージェントを動かしたらカフェテリアから出なかった話

# はじめに
最近、友人とAmongUsで遊ぶことが多い。

プレイヤーの証言を突き合わせ犯人を割り出す本ゲームでは、他のメンバーに見つからないような状況で殺人行為を行う必要があり、かつ、行為に関与していないというアリバイ作りも必要となる。

むずかしい。。どういう行動が最適なのか。。

ということで、本取り組みでは、インポスター（犯人）としてAmangUsで勝つための最適パターンを見つけるため、エージェントモデルの行動シミュレーションを実施してみる。
<img width=400 alt="ファイル名" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/a005c09c-8560-8bd5-a7c3-aaaa0ffad45b.png>

# AmongUsとは
アクションと人狼の要素を組み合わせたオンラインマルチプレイヤーゲーム

こういったプレイマップの中で、
<img width=400 alt="ファイル名" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/b082e16d-0a45-c2bf-ac20-fd4add6336b0.jpeg>

こういうキャラが、
<img width=400 alt="ファイル名" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/ffcdc1e1-f5bd-2522-8c6d-cf1ea877baf4.png>


殺したり、
<img width=400 alt="ファイル名" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/0b52106c-e6f4-3923-4bb8-2b7bf880197e.png>


犯人を割り出したりする
<img width=400 alt="ファイル名" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/b0aec10f-9b84-acaf-10a4-ad03b4be4dca.jpeg>

ちなみにタイトルの「カフェテリア」とは2枚目の画像のことで、同じ位置に集結した全プレイヤーがゲーム開始と同時に別の場所に移動する。

# アプローチ

AmongUsのシミュレーションを行うために以下のことを実施した。

- 画像解析よりプレイマップのエッジの画像を作成
- エッジだけとなったプレイマップの中にエージェントを投入
- 決まった規則のもとエージェントを行動させる
    - 行動規則
        - 壁にぶつかっていない状態では等速直線運動
        - 壁にぶつかったら、ランダムの方向に跳ね返る


プレイマップ

```python
img_org = cv2.imread('../data/the_skeld.PNG')
```
<img width=400 alt="ファイル名" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/d3e909d8-5f97-a28a-a092-c4dc36643496.png>
シミュレーション結果を画像として保存

```python
# 画像内にエージェント（〇）を描写する
def move_agent(x, y, img, agent_size=30):
    radius = int(agent_size/2)
    img_move_agent = cv2.circle(img, (x,y), radius, (255,255,255), thickness=-1)
    return img_move_agent

# 行動開始地点の選択
def choice_start_position(xrange=(1000, 1250), yrange=(500, 600)):
    x = np.random.choice(np.arange(xrange[0], xrange[1],1))
    y = np.random.choice(np.arange(yrange[0], yrange[1],1))
    return x, y

# 進行方向の選択
def choice_direction(directions=[1,2,3,4,6,7,8,9]):
    return np.random.choice(directions)

# エージェントの座標を動かす
def move(x, y, direction, step=1):
    if direction == 1:
        x += -step
        y += step
    elif direction == 2:
        x += 0
        y += step
    elif direction == 3:
        x += step
        y += step
    elif direction == 4:
        x += -step
        y += 0
    elif direction == 6:
        x += step
        y += 0
    elif direction == 7:
        x += -step
        y += -step
    elif direction == 8:
        x += 0
        y += -step
    elif direction == 9:
        x += step
        y += -step
    return x, y

# 壁にぶつかった際に進行できる方向を探る
def free_space_search(x, y, img):
    free_direction = list()
    xkernel = [x-1, x, x+1, x-1, x+1, x-1, x, x+1]
    ykernel = [y+1, y+1, y+1, y, y, y-1, y-1, y-1]
    kernel = [(xk, yk) for (xk,yk) in zip(xkernel, ykernel)]
    direction_code = dict(zip(kernel,[1,2,3,4,6,7,8,9]))
    for (xk, yk) in zip(xkernel, ykernel):
        pixel = img[yk,xk]
        if pixel == 0:
            free_direction.append(direction_code[(xk, yk)])
    return np.random.choice(free_direction)

img_path = '../data/the_skeld.PNG'
if __name__ == '__main__':
    loop_n = 5000
    write_path = '../data/move_agent1/'
    x, y = choice_start_position()
    direction = choice_direction()
    for i in range(loop_n):
        img_org = cv2.imread(img_path,0)
        threshold = 200
        ret, img_thresh = cv2.threshold(img_org, threshold, 255, cv2.THRESH_BINARY)
        kernel = np.ones((10, 10), np.uint8)
        closing = cv2.morphologyEx(img_thresh, cv2.MORPH_CLOSE, kernel)
        img = closing.copy()
        x_next, y_next = move(x, y, direction)
        x, y = x_next, y_next
        img_move_agent = move_agent(x, y, img)
        #if np.max(img_org[y-10:y+10, x-10:x+10]) == 255:
        if img_org[y, x] == 255:
            direction = free_space_search(x, y, img_org)
        i_str = '{0:04}'.format(i)        
        img_move = move_agent(x, y, img)
        cv2.imwrite(write_path + 'move%s.png'%i_str, img_move)
```

画像を連結しmp4として保存

```python
fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v')
video = cv2.VideoWriter('video.mp4',fourcc, 20.0,(320,240))
```
```python
filenames = glob.glob('../data/move_agent1/*')
for filename in filenames:
    img = cv2.imread(filename)
    img_text = img.copy()
    img_text = cv2.resize(img_text, (320,240))
    if img is None:
        print("can't read")
        break
    video.write(img_text)
video.release()
```

# 結果
![amangus.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/766086/7b3fd989-e540-fa4b-bb26-e88e68f50ad9.gif)

直進するだけでは、何の検証にもならない。。。ゲームがはじまらない（カフェテリアからでない）
次のステップは、エージェントにカフェテリアの出口を認識させて、そこに向かわせることですかね。

# まとめ
- AmongUsでのエージェントモデルの行動シミュレーションを試してみた。
- 直進運動をするエージェントは、カフェテリアから出ず、ゲームが開始しなかった。
- が、なんとかこのエージェント君に出口を認識させたいという、新たな目標ができた。


深層学習を用いたエージェント学習など、勉強の幅が広がりそうな予感！
以上です。