Newer
Older
2025-shino / hokaku.js
(() => {
  'use strict';

  document.addEventListener("DOMContentLoaded", () => {

    const START_POS = [38.891, 139.824];
    const CAPTURE_DISTANCE = 30;
    const NEAR_DISTANCE = 100;
    const CAPTURE_RATE = 0.7;

    const titleEl = document.getElementById("title");
    const startBtn = document.getElementById("start");
    const stopBtn = document.getElementById("stop");
    const captureBtn = document.getElementById("capture");

    let map;
    let playerMarker;
    let monsterMarker = null;
    let monsterLatLng = null;

    let watchId = null;
    let nTrial = 100;
    let canCapture = false;

    function initMap() {
      map = L.map("locationmap", { tap: true })
        .setView(START_POS, 16);

      L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png", {
        attribution:
          '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      }).addTo(map);

      playerMarker = L.marker(map.getCenter()).addTo(map);
      playerMarker.bindPopup("STARTを押して探索開始").openPopup();

      map.on("click", (e) => {
        updateLocation(e.latlng);
      });
    }

    function spawnMonster(center) {
      const offsetLat = (Math.random() - 0.5) * 0.002;
      const offsetLng = (Math.random() - 0.5) * 0.002;

      monsterLatLng = L.latLng(
        center.lat + offsetLat,
        center.lng + offsetLng
      );

      if (monsterMarker) {
        map.removeLayer(monsterMarker);
      }

      monsterMarker = L.marker(monsterLatLng, {
        title: "なにかの気配"
      }).addTo(map);
    }

    function updateLocation(latlng) {
      map.panTo(latlng);
      playerMarker.setLatLng(latlng);

      if (!monsterLatLng) {
        spawnMonster(latlng);
      }

      const distance = map.distance(latlng, monsterLatLng);
      let message = "探索中…";

      if (distance < CAPTURE_DISTANCE) {
        message = "🎯 モンスターが近い!捕獲できる!";
        canCapture = true;
        captureBtn.disabled = false;
      } else if (distance < NEAR_DISTANCE) {
        message = "👀 気配が近い…";
        canCapture = false;
        captureBtn.disabled = true;
      } else {
        canCapture = false;
        captureBtn.disabled = true;
      }

      playerMarker.setPopupContent(message).openPopup();
    }

    function captureMonster() {
      if (!canCapture || !monsterLatLng) return;

      const success = Math.random() < CAPTURE_RATE;

      playerMarker.setPopupContent(
        success ? "🎉 捕獲成功!" : "💨 逃げられた…"
      ).openPopup();

      if (monsterMarker) {
        map.removeLayer(monsterMarker);
      }

      monsterMarker = null;
      monsterLatLng = null;
      canCapture = false;
      captureBtn.disabled = true;
    }

    function onSuccess(pos) {
      updateLocation(
        L.latLng(pos.coords.latitude, pos.coords.longitude)
      );
    }

    function onError() {
      nTrial--;
      playerMarker
        .setPopupContent(`捕捉失敗… 残り ${nTrial} 回`)
        .openPopup();

      if (nTrial <= 0 && watchId !== null) {
        navigator.geolocation.clearWatch(watchId);
        watchId = null;
        titleEl.textContent = "ERROR";
      }
    }

    function startWatch() {
      stopWatch();
      watchId = navigator.geolocation.watchPosition(
        onSuccess,
        onError,
        { maximumAge: 0, timeout: 3000, enableHighAccuracy: true }
      );
      titleEl.textContent = "探索中";
    }

    function stopWatch() {
      if (watchId !== null) {
        navigator.geolocation.clearWatch(watchId);
        watchId = null;
        titleEl.textContent = "STOP";
        playerMarker.setPopupContent("探索を停止しました").openPopup();
      }
    }

    startBtn.addEventListener("click", startWatch);
    stopBtn.addEventListener("click", stopWatch);
    captureBtn.addEventListener("click", captureMonster);


    initMap();

  });
})();