Newer
Older
quiz2023 / sakasaka.js
document.addEventListener('DOMContentLoaded', () => {
    // Leafletマップ初期化 (東京中心)
    const map = L.map('map').setView([35.6762, 139.6503], 13);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map);

    const gridSize = 10;
    let elevationGrid = [];
    let start = null;
    let goal = null;
    let pathLayer = null;
    let startMarker = null;
    let goalMarker = null;

    // 初期バウンディングボックス基準化
    let initialBounds;
    function setInitialBounds() {
        initialBounds = map.getBounds();
    }

    // ランダム標高生成
    function generateElevation() {
        elevationGrid = [];
        for (let y = 0; y < gridSize; y++) {
            elevationGrid[y] = [];
            for (let x = 0; x < gridSize; x++) {
                elevationGrid[y][x] = Math.floor(Math.random() * 100);
            }
        }
    }

    // 緯度経度→グリッド
    function latLngToGrid(latLng) {
        const bounds = initialBounds;
        const north = bounds.getNorth();
        const south = bounds.getSouth();
        const east = bounds.getEast();
        const west = bounds.getWest();

        const x = Math.floor(((latLng.lng - west) / (east - west)) * gridSize);
        const y = Math.floor(((north - latLng.lat) / (north - south)) * gridSize);

        return { x: Math.max(0, Math.min(gridSize - 1, x)), y: Math.max(0, Math.min(gridSize - 1, y)) };
    }

    // グリッド→緯度経度
    function gridToLatLng(grid) {
        const bounds = initialBounds;
        const north = bounds.getNorth();
        const south = bounds.getSouth();
        const east = bounds.getEast();
        const west = bounds.getWest();

        const lat = north - (grid.y / gridSize) * (north - south);
        const lng = west + (grid.x / gridSize) * (east - west);

        return L.latLng(lat, lng);
    }

    // 坂が少ない道を選ぶA*アルゴリズム
    function findPath(startGrid, goalGrid) {
        const openSet = [];
        const openSetKeys = new Set();
        const cameFrom = {};
        const gScore = {};
        const fScore = {};

        const getKey = (node) => `${node.x},${node.y}`;
        const heuristic = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);

        openSet.push(startGrid);
        openSetKeys.add(getKey(startGrid));
        gScore[getKey(startGrid)] = 0;
        fScore[getKey(startGrid)] = heuristic(startGrid, goalGrid);

        while (openSet.length > 0) {
            openSet.sort((a, b) => fScore[getKey(a)] - fScore[getKey(b)]);
            const current = openSet.shift();
            openSetKeys.delete(getKey(current));

            if (current.x === goalGrid.x && current.y === goalGrid.y) {
                const path = [];
                let temp = current;
                while (temp) {
                    path.push(temp);
                    temp = cameFrom[getKey(temp)];
                    if (path.length > gridSize * gridSize) break; // 無限ループ対抗
                }
                return path.reverse();
            }

            const neighbors = [
                { x: current.x + 1, y: current.y },
                { x: current.x - 1, y: current.y },
                { x: current.x, y: current.y + 1 },
                { x: current.x, y: current.y - 1 }
            ];

            for (let neighbor of neighbors) {
                if (neighbor.x < 0 || neighbor.x >= gridSize || neighbor.y < 0 || neighbor.y >= gridSize) continue;

                const elevDiff = Math.abs(elevationGrid[current.y][current.x] - elevationGrid[neighbor.y][neighbor.x]);
                // ★ 坂(標高差)を優先(10倍。必要なら値を大きく・小さくして調整してください)
                const tentativeG = gScore[getKey(current)] + 1 + elevDiff * 10;

                if (typeof gScore[getKey(neighbor)] === 'undefined' || tentativeG < gScore[getKey(neighbor)]) {
                    cameFrom[getKey(neighbor)] = current;
                    gScore[getKey(neighbor)] = tentativeG;
                    fScore[getKey(neighbor)] = tentativeG + heuristic(neighbor, goalGrid);
                    if (!openSetKeys.has(getKey(neighbor))) {
                        openSet.push(neighbor);
                        openSetKeys.add(getKey(neighbor));
                    }
                }
            }
        }
        return [];
    }

    // パス描画
    function drawPath(path) {
        if (pathLayer) map.removeLayer(pathLayer);
        if (path.length === 0) return;

        const latLngs = path.map(grid => gridToLatLng(grid));
        pathLayer = L.polyline(latLngs, { color: 'blue', weight: 3 }).addTo(map);
    }

    // クリックで start/goal 選択
    map.on('click', (event) => {
        const grid = latLngToGrid(event.latlng);

        if (!start) {
            start = grid;
            if (startMarker) map.removeLayer(startMarker);
            startMarker = L.marker(event.latlng).addTo(map);
        } else if (!goal) {
            goal = grid;
            if (goalMarker) map.removeLayer(goalMarker);
            goalMarker = L.marker(event.latlng).addTo(map);

            const path = findPath(start, goal);
            drawPath(path);
        }
    });

    document.getElementById('resetButton').addEventListener('click', resetMap);

    function resetMap() {
        start = null;
        goal = null;
        if (startMarker) { map.removeLayer(startMarker); startMarker = null; }
        if (goalMarker) { map.removeLayer(goalMarker); goalMarker = null; }
        if (pathLayer) map.removeLayer(pathLayer);
        generateElevation();
        // 初期bounds取り直す場合は↓
        // setInitialBounds();
    }

    generateElevation();
    setInitialBounds();
});