経路探索APIとゲームAI

経路探索アルゴリズムA*をWeb APIとして公開し、さらにゲームAIの敵追跡に応用する方法を学びます。

🔥 Vibe プロンプト

「FastAPI経路探索サービスを作成:POST /pathfindでグリッド地図を受け取り、最短経路と探索ノード数を返す。」

構築するもの

A*アルゴリズムをFastAPIサービスとして公開し、フロントエンドアプリが経路探索できるようにします。

リクエスト形式

| フィールド | 型 | 説明 | |-----------|------|------| | grid | List[List[int]] | 0=通路, 1=障害物の二次元配列 | | start | Tuple[int, int] | スタート地点 (row, col) | | goal | Tuple[int, int] | ゴール地点 (row, col) |

レスポンス形式

| フィールド | 型 | 説明 | |-----------|------|------| | path | Optional[List[Tuple[int, int]]] | 最短経路の座標リスト(見つからない場合はnull) | | found | bool | 経路が見つかったかどうか | | nodes_explored | int | 探索中に調べたノード数 |

実装

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Tuple, Optional

app = FastAPI(title="経路探索API", version="1.0.0")

class PathRequest(BaseModel):
    grid: List[List[int]]  # 0=通路, 1=障害物
    start: Tuple[int, int]
    goal: Tuple[int, int]

class PathResponse(BaseModel):
    path: Optional[List[Tuple[int, int]]]
    found: bool
    nodes_explored: int

@app.post("/pathfind", response_model=PathResponse)
def pathfind(req: PathRequest):
    """A*で最短経路を探索"""
    path, explored = astar_with_stats(req.grid, req.start, req.goal)
    return PathResponse(
        path=path,
        found=path is not None,
        nodes_explored=explored
    )

@app.get("/health")
def health():
    return {"status": "ok", "algorithm": "A*"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

A*アルゴリズム本体

import heapq
from typing import List, Tuple, Optional

def astar_with_stats(grid: List[List[int]], start: Tuple[int, int], goal: Tuple[int, int]):
    """A*探索を行い、経路と探索ノード数を返す"""
    rows, cols = len(grid), len(grid[0])
    open_set = [(0, start)]
    came_from = {}
    g_score = {start: 0}
    explored = 0

    while open_set:
        _, current = heapq.heappop(open_set)
        explored += 1

        if current == goal:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            path.append(start)
            return path[::-1], explored

        r, c = current
        for dr, dc in [(1,0), (-1,0), (0,1), (0,-1)]:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 0:
                neighbor = (nr, nc)
                tentative = g_score[current] + 1
                if neighbor not in g_score or tentative < g_score[neighbor]:
                    g_score[neighbor] = tentative
                    f_score = tentative + abs(nr - goal[0]) + abs(nc - goal[1])  # マンハッタン距離
                    heapq.heappush(open_set, (f_score, neighbor))
                    came_from[neighbor] = current

    return None, explored

テスト

curl -X POST http://localhost:8000/pathfind \
  -H "Content-Type: application/json" \
  -d '{"grid": [[0,0,0],[1,0,1],[0,0,0]], "start": [0,0], "goal": [2,2]}'

# 応答:
# {"path": [[0,0],[0,1],[0,2],[1,2],[2,2]], "found": true, "nodes_explored": 7}

ゲームAI:敵追跡

A*をゲームの敵キャラクターの移動に応用します。プレイヤーの位置を目標として毎フレーム経路を再計算し、敵がプレイヤーを追跡します。

class Enemy:
    def __init__(self, x, y, speed=1):
        self.x, self.y = x, y
        self.speed = speed

    def update(self, grid, player_pos):
        """A*を使ってプレイヤーに向かって移動"""
        path = astar(grid, (self.x, self.y), player_pos)
        if path and len(path) > 1:
            next_pos = path[1]
            self.x, self.y = next_pos
            return True
        return False

enemies = [Enemy(5, 5), Enemy(3, 8), Enemy(9, 2)]
# ゲームループ内で毎フレーム:
# for enemy in enemies:
#     if enemy.update(grid, player_pos):
#         if (enemy.x, enemy.y) == (player.x, player.y):
#             game_over()

ゲームループ例(Pygame)

import pygame
import sys

# Pygameの初期化
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# グリッド設定
CELL = 40
grid = [[0]*20 for _ in range(15)]
# 障害物を配置
for i in range(5, 15):
    grid[7][i] = 1

player_pos = [0, 0]
enemy = Enemy(12, 7)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            r, c = player_pos
            if event.key == pygame.K_UP and r > 0 and grid[r-1][c] == 0:
                r -= 1
            elif event.key == pygame.K_DOWN and r < 14 and grid[r+1][c] == 0:
                r += 1
            elif event.key == pygame.K_LEFT and c > 0 and grid[r][c-1] == 0:
                c -= 1
            elif event.key == pygame.K_RIGHT and c < 19 and grid[r][c+1] == 0:
                c += 1
            player_pos = [r, c]

    # 敵の移動
    enemy.update(grid, tuple(player_pos))

    # 描画
    screen.fill((255, 255, 255))
    for r in range(15):
        for c in range(20):
            color = (0, 0, 0) if grid[r][c] == 1 else (255, 255, 255)
            pygame.draw.rect(screen, color, (c*CELL, r*CELL, CELL, CELL), 1)
    # プレイヤー
    pygame.draw.circle(screen, (0, 0, 255),
                       (player_pos[1]*CELL+CELL//2, player_pos[0]*CELL+CELL//2), 15)
    # 敵
    pygame.draw.circle(screen, (255, 0, 0),
                       (enemy.y*CELL+CELL//2, enemy.x*CELL+CELL//2), 15)

    pygame.display.flip()
    clock.tick(10)

pygame.quit()
sys.exit()

🔥 Vibe プロンプト

「Pygameで敵がA*を使ってプレイヤーを追跡するゲームを作成。矢印キーで移動。探索過程を可視化。」

まとめ

| 項目 | 詳細 | |------|------| | API | FastAPIによるA*経路探索サービス | | ゲームAI | 毎フレームの敵移動制御 | | 性能 | 大規模マップではJPS+で高速化 | | 拡張 | 経路平滑化、群行動、予測追跡 |

完全なチュートリアルをロック解除

このチャプターは有料コンテンツです。プロジェクトに参加して、10以上の神レベルのPromptや実際のソースコード例を含む、5000字以上の深い分析をロック解除してください!