(() => {
'use strict';
document.addEventListener("DOMContentLoaded", () => {
const START_POS = [38.891, 139.824];
const REVEAL_RADIUS = 40;
const MAX_EXPLORE_POINTS = 300;
const BASE_CAPTURE_RADIUS = 30;
const titleEl = document.getElementById("title");
const startBtn = document.getElementById("start");
const stopBtn = document.getElementById("stop");
const rateEl = document.getElementById("rate");
const baseStatusEl = document.getElementById("baseStatus");
let map;
let playerMarker;
let watchId = null;
const revealedPoints = [];
const bases = [
{ latlng: L.latLng(38.892, 139.825), captured: false, marker: null },
{ latlng: L.latLng(38.889, 139.823), captured: false, marker: null },
{ latlng: L.latLng(38.893, 139.827), captured: false, marker: null }
];
const FogLayer = L.GridLayer.extend({
createTile: function (coords) {
const tile = document.createElement("canvas");
const size = this.getTileSize();
tile.width = size.x;
tile.height = size.y;
const ctx = tile.getContext("2d");
ctx.fillStyle = "rgba(0,0,0,0.85)";
ctx.fillRect(0, 0, size.x, size.y);
const bounds = this._tileCoordsToBounds(coords);
ctx.globalCompositeOperation = "destination-out";
revealedPoints.forEach(latlng => {
if (!bounds.contains(latlng)) return;
const point = map.latLngToContainerPoint(latlng);
const tilePoint = map.latLngToContainerPoint(bounds.getNorthWest());
ctx.beginPath();
ctx.arc(
point.x - tilePoint.x,
point.y - tilePoint.y,
metersToPixels(REVEAL_RADIUS, latlng.lat),
0,
Math.PI * 2
);
ctx.fill();
});
return tile;
}
});
let fogLayer;
function metersToPixels(m, lat) {
return (
m / 40075017 *
256 *
Math.pow(2, map.getZoom()) /
Math.cos(lat * Math.PI / 180)
);
}
function isNearExisting(latlng, threshold = 10) {
return revealedPoints.some(p => p.distanceTo(latlng) < threshold);
}
function updateExploreRate() {
const rate = Math.min(
100,
Math.floor((revealedPoints.length / MAX_EXPLORE_POINTS) * 100)
);
rateEl.textContent = `制圧率: ${rate}%`;
}
function updateBaseStatus() {
const captured = bases.filter(b => b.captured).length;
baseStatusEl.textContent = `拠点: ${captured} / ${bases.length}`;
if (captured === bases.length) {
titleEl.textContent = "全拠点制圧!";
}
}
function initMap() {
map = L.map("locationmap").setView(START_POS, 16);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "© OpenStreetMap contributors"
}).addTo(map);
fogLayer = new FogLayer();
fogLayer.addTo(map);
playerMarker = L.marker(map.getCenter()).addTo(map);
playerMarker.bindPopup("STARTで探索開始").openPopup();
bases.forEach(b => {
b.marker = L.circleMarker(b.latlng, {
radius: 8,
color: "red",
fillColor: "red",
fillOpacity: 1
}).addTo(map).bindPopup("未制圧拠点");
});
map.on("click", e => updateLocation(e.latlng));
updateBaseStatus();
}
function checkBases(latlng) {
bases.forEach(b => {
if (b.captured) return;
if (latlng.distanceTo(b.latlng) <= BASE_CAPTURE_RADIUS) {
b.captured = true;
b.marker.setStyle({
color: "blue",
fillColor: "blue"
});
b.marker.setPopupContent("制圧済み拠点");
updateBaseStatus();
}
});
}
function reveal(latlng) {
if (isNearExisting(latlng)) return;
revealedPoints.push(latlng);
fogLayer.redraw();
updateExploreRate();
}
function updateLocation(latlng) {
map.panTo(latlng, { animate: false });
playerMarker.setLatLng(latlng);
reveal(latlng);
checkBases(latlng);
}
function onSuccess(pos) {
updateLocation(
L.latLng(pos.coords.latitude, pos.coords.longitude)
);
}
function startWatch() {
stopWatch();
watchId = navigator.geolocation.watchPosition(
onSuccess,
null,
{ enableHighAccuracy: true }
);
titleEl.textContent = "探索中";
}
function stopWatch() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
}
titleEl.textContent = "霧解除探索ゲーム";
}
startBtn.addEventListener("click", startWatch);
stopBtn.addEventListener("click", stopWatch);
initMap();
});
})();