🎮ArcadeLab

地震模拟器 · 极简稳定版

by FlashMaker22
368 lines13.5 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>地震模拟器 · 极简稳定版</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body { width: 100%; height: 100%; overflow: hidden; background: #050a10; color: #dbe9f5; font-family: sans-serif; }
  #map { display: block; position: absolute; top: 0; left: 0; }
  .panel { position: absolute; background: rgba(9,18,29,0.88); border: 1px solid rgba(94,200,240,0.2); border-radius: 10px; padding: 10px; z-index: 10; font-size: 12px; }
  #left { top: 10px; left: 10px; width: 170px; }
  #right { top: 10px; right: 10px; width: 190px; }
  #deaths { font-size: 22px; font-weight: 900; font-family: monospace; color: #ff4d4d; }
  #hint { position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%); font-size: 11px; color: #5b8aa8; background: rgba(9,18,29,0.8); padding: 6px 14px; border-radius: 14px; border: 1px solid rgba(94,200,240,0.15); z-index: 10; }
  label { display: block; margin: 8px 0 4px; color: #8bb4d4; font-size: 11px; }
  input[type=range] { width: 100%; }
  .row { display: flex; align-items: center; gap: 4px; padding: 3px; font-size: 11px; }
  .dot { width: 7px; height: 7px; border-radius: 50%; }
  button { margin-top: 6px; width: 100%; padding: 6px; border-radius: 6px; border: 1px solid rgba(94,200,240,0.3); background: rgba(94,200,240,0.1); color: #a8d8f0; cursor: pointer; }
</style>
</head>
<body>

<canvas id="map"></canvas>

<div class="panel" id="left">
  <div style="font-size:13px;font-weight:700;color:#5ec8f0;">🌍 地震模拟器</div>
  <label>震级 <span id="magVal">7.0</span></label>
  <input type="range" id="mag" min="3" max="9.5" step="0.1" value="7">
  <label>深度 <span id="depVal">10 km</span></label>
  <input type="range" id="dep" min="5" max="700" step="5" value="10">
  <button id="randomBtn">🎲 随机地震</button>
  <button id="worstBtn">💀 世纪大震</button>
</div>

<div class="panel" id="right">
  <div style="font-size:10px;color:#4a7fa5;">预估死亡人数</div>
  <div id="deaths">0</div>
  <div id="meta" style="font-size:10px;color:#6f9cbd;margin-top:4px;">震中:--</div>
  <div style="font-size:10px;color:#4a7fa5;margin-top:8px;">受影响地区</div>
  <div id="list" style="max-height:300px;overflow-y:auto;margin-top:4px;"></div>
</div>

<div id="hint">点击地图设置震中</div>

<script>
(function(){
  'use strict';

  // ===== 极简大陆轮廓(只有几个大洲,保证数据完整) =====
  var LAND = [
    // 北美
    [[-168,65],[-160,70],[-140,70],[-120,69],[-100,68],[-80,70],[-62,66],[-57,53],[-66,48],
     [-74,40],[-77,35],[-81,31],[-80,27],[-83,28],[-89,29],[-97,26],[-98,19],[-105,20],
     [-110,24],[-117,32],[-124,41],[-127,50],[-135,57],[-148,60],[-160,56],[-166,62]],
    // 格陵兰
    [[-45,60],[-30,68],[-19,76],[-36,82],[-62,79],[-58,71],[-50,65]],
    // 南美
    [[-81,7],[-72,11],[-60,9],[-50,1],[-41,-3],[-35,-6],[-39,-18],[-49,-27],[-59,-39],
     [-69,-52],[-74,-51],[-70,-30],[-73,-15],[-80,-5],[-79,3]],
    // 非洲
    [[-17,15],[-13,25],[-6,35],[6,37],[19,33],[31,31],[37,24],[43,15],[48,9],[51,11],
     [45,3],[40,-6],[36,-18],[30,-30],[19,-35],[13,-25],[9,-13],[5,-5],[0,5],[-10,6],[-17,14]],
    // 欧亚
    [[-10,36],[-8,43],[0,46],[6,53],[10,58],[10,64],[24,70],[40,68],[56,69],[72,73],
     [90,76],[110,77],[130,73],[150,70],[170,68],[180,62],[172,61],[156,58],[140,50],
     [128,42],[120,33],[114,24],[104,11],[99,6],[91,18],[81,20],[71,20],[60,25],[48,17],
     [42,19],[34,31],[26,38],[18,39],[12,44],[4,43],[-4,38]],
    // 澳洲
    [[114,-22],[115,-28],[117,-32],[124,-33],[132,-32],[139,-36],[147,-38],[153,-30],
     [148,-20],[141,-12],[130,-12],[122,-17],[118,-20]],
    // 南极
    [[-180,-72],[-140,-73],[-100,-74],[-60,-63],[-30,-72],[0,-70],[30,-69],[60,-67],
     [90,-66],[120,-66],[150,-68],[180,-72],[180,-90],[-180,-90]],
    // 日本
    [[131,34],[137,36],[141,40],[144,44],[143,42],[138,37],[133,34]],
    // 英国
    [[-5,50],[-3,52],[-1,53],[-2,56],[-4,58],[-6,56],[-5,52]],
    // 马达加斯加
    [[43,-12],[48,-13],[50,-17],[48,-22],[45,-25],[43,-22]],
    // 新西兰
    [[173,-35],[176,-37],[178,-39],[176,-41],[174,-41],[172,-38]],
    // 台湾
    [[120,22],[122,24],[121,25],[120,23]],
    // 苏门答腊
    [[95,5.5],[103,-2],[106,-5.5],[101,-2.5],[97,1]],
    // 婆罗洲
    [[109,2],[114,-1],[117,-3.5],[111,-3]],
    // 新几内亚
    [[131,-1],[144,-4],[150,-6],[147,-8.5],[139,-8],[132,-3]]
  ];

  var CITIES = [
    ['东京',35.68,139.69,37,0.07],['大阪',34.69,135.50,19,0.07],
    ['首尔',37.57,126.98,25,0.08],['北京',39.90,116.41,21,0.13],
    ['上海',31.23,121.47,25,0.12],['广州',23.13,113.26,18,0.14],
    ['台北',25.03,121.57,7,0.10],['重庆',29.56,106.55,16,0.18],
    ['成都',30.57,104.07,16,0.18],['西安',34.34,108.94,12,0.17],
    ['昆明',25.04,102.71,6,0.25],['马尼拉',14.60,120.98,14,0.42],
    ['雅加达',-6.21,106.85,11,0.50],['曼谷',13.76,100.50,10,0.26],
    ['河内',21.03,105.85,8,0.34],['胡志明市',10.82,106.63,9,0.33],
    ['新加坡',1.35,103.82,6,0.06],['仰光',16.87,96.20,5.5,0.52],
    ['达卡',23.81,90.41,21,0.62],['加尔各答',22.57,88.36,15,0.44],
    ['德里',28.61,77.21,29,0.40],['孟买',19.08,72.88,20,0.36],
    ['卡拉奇',24.86,67.01,16,0.50],['加德满都',27.72,85.32,1.5,0.68],
    ['德黑兰',35.69,51.39,9,0.42],['伊斯坦布尔',41.01,28.98,15,0.30],
    ['巴格达',33.31,44.36,7,0.50],['利雅得',24.71,46.68,7,0.12],
    ['开罗',30.04,31.24,21,0.42],['拉各斯',6.52,3.38,15,0.44],
    ['约翰内斯堡',-26.20,28.05,6,0.22],['莫斯科',55.76,37.62,12,0.14],
    ['伦敦',51.51,-0.13,9,0.08],['巴黎',48.86,2.35,11,0.08],
    ['纽约',40.71,-74.01,19,0.06],['洛杉矶',34.05,-118.24,13,0.07],
    ['旧金山',37.77,-122.42,4.7,0.06],['墨西哥城',19.43,-99.13,21,0.34],
    ['利马',-12.05,-77.04,10,0.42],['圣地亚哥',-33.45,-70.67,7,0.22],
    ['圣保罗',-23.55,-46.63,22,0.19],['悉尼',-33.87,151.21,5.3,0.06]
  ];

  // ===== 画布初始化(多重兜底) =====
  var canvas = document.getElementById('map');
  var ctx = canvas.getContext('2d');
  var W = 800, H = 600;

  function resize() {
    var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 800;
    var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight || 600;
    if (w < 100) w = 800;
    if (h < 100) h = 600;
    W = w; H = h;
    canvas.width = W;
    canvas.height = H;
    canvas.style.width = W + 'px';
    canvas.style.height = H + 'px';
  }

  // ===== 坐标转换 =====
  function px(lon) { return (lon + 180) / 360 * W; }
  function py(lat) { return (90 - lat) / 180 * H; }

  // ===== 模拟状态 =====
  var sim = { lon: 139.69, lat: 35.68, mag: 7.0, depth: 10, results: [], total: 0, displayed: 0 };

  // ===== 计算 =====
  function haversine(lat1, lon1, lat2, lon2) {
    var R = 6371, p = Math.PI / 180;
    var dLat = (lat2 - lat1) * p, dLon = (lon2 - lon1) * p;
    var a = Math.sin(dLat/2)*Math.sin(dLat/2) + Math.cos(lat1*p)*Math.cos(lat2*p)*Math.sin(dLon/2)*Math.sin(dLon/2);
    return 2 * R * Math.asin(Math.min(1, Math.sqrt(a)));
  }

  function computeSim() {
    var res = [], total = 0;
    for (var i = 0; i < CITIES.length; i++) {
      var c = CITIES[i];
      var dist = haversine(sim.lat, sim.lon, c[1], c[2]);
      var cityR = 6 * Math.sqrt(c[3]);
      var hyp = Math.sqrt(dist*dist + cityR*cityR + sim.depth*sim.depth);
      var I = 1.5*sim.mag - 3.3*Math.log(Math.max(hyp,5))/Math.LN10 + 2.5;
      if (I < 0) I = 0; if (I > 12) I = 12;
      if (I < 3.5) continue;
      var frac = Math.min(0.7, 1 - Math.exp(-(I-3.5)/2.5));
      var mortality = Math.min(0.5, c[4]*Math.pow(10,(I-11)/1.5));
      var deaths = c[3]*1e6 * frac * mortality * (0.72 + Math.random()*0.56);
      total += deaths;
      res.push({ name: c[0], lat: c[1], lon: c[2], I: I, deaths: deaths });
    }
    res.sort(function(a,b){ return b.deaths - a.deaths; });
    sim.results = res;
    sim.total = total;
    renderList();
    updateMeta();
  }

  // ===== 颜色 =====
  function mmiColor(i) {
    if (i < 4) return '#4a90d9';
    if (i < 5) return '#5ec8f0';
    if (i < 6) return '#4ee0a0';
    if (i < 7) return '#f5e663';
    if (i < 8) return '#ffb74d';
    if (i < 9) return '#ff7043';
    if (i < 10) return '#ff3d3d';
    if (i < 11) return '#e11d74';
    if (i < 12) return '#a855f7';
    return '#7c3aed';
  }

  function fmtNum(n) {
    if (n >= 1e8) return (n/1e8).toFixed(2) + '亿';
    if (n >= 1e4) return (n/1e4).toFixed(1) + '万';
    return Math.round(n);
  }

  // ===== 绘制 =====
  function draw() {
    // 海洋背景
    var g = ctx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, '#0a1826');
    g.addColorStop(1, '#04090f');
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, H);

    // 网格
    ctx.strokeStyle = 'rgba(94,200,240,0.06)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    for (var lon = -180; lon <= 180; lon += 30) {
      ctx.moveTo(px(lon), 0);
      ctx.lineTo(px(lon), H);
    }
    for (var lat = -60; lat <= 60; lat += 30) {
      ctx.moveTo(0, py(lat));
      ctx.lineTo(W, py(lat));
    }
    ctx.stroke();

    // 赤道
    ctx.strokeStyle = 'rgba(94,200,240,0.2)';
    ctx.beginPath();
    ctx.moveTo(0, H/2);
    ctx.lineTo(W, H/2);
    ctx.stroke();

    // 陆地
    ctx.fillStyle = '#1e4a68';
    ctx.strokeStyle = 'rgba(150,220,255,0.6)';
    ctx.lineWidth = 1;
    for (var k = 0; k < LAND.length; k++) {
      var poly = LAND[k];
      if (!poly || poly.length < 3) continue;
      ctx.beginPath();
      ctx.moveTo(px(poly[0][0]), py(poly[0][1]));
      for (var j = 1; j < poly.length; j++) {
        ctx.lineTo(px(poly[j][0]), py(poly[j][1]));
      }
      ctx.closePath();
      ctx.fill();
      ctx.stroke();
    }

    // 震中
    var ex = px(sim.lon), ey = py(sim.lat);
    ctx.beginPath();
    ctx.arc(ex, ey, 8, 0, Math.PI*2);
    ctx.fillStyle = '#ff3b30';
    ctx.fill();
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 2;
    ctx.stroke();

    // 城市标记
    for (var m = 0; m < sim.results.length && m < 10; m++) {
      var r = sim.results[m];
      var x = px(r.lon), y = py(r.lat);
      var color = mmiColor(r.I);
      ctx.beginPath();
      ctx.arc(x, y, 4, 0, Math.PI*2);
      ctx.fillStyle = color;
      ctx.fill();
      ctx.strokeStyle = '#fff';
      ctx.lineWidth = 1;
      ctx.stroke();

      ctx.font = '11px sans-serif';
      ctx.fillStyle = color;
      ctx.fillText(r.name, x + 7, y + 4);
    }
  }

  // ===== UI =====
  function updateMeta() {
    var ns = sim.lat >= 0 ? 'N' : 'S';
    var ew = sim.lon >= 0 ? 'E' : 'W';
    document.getElementById('meta').innerHTML =
      '震中:' + Math.abs(sim.lat).toFixed(1) + '°' + ns + ', ' +
      Math.abs(sim.lon).toFixed(1) + '°' + ew;
  }

  function renderList() {
    var html = '';
    for (var i = 0; i < sim.results.length && i < 12; i++) {
      var r = sim.results[i];
      var c = mmiColor(r.I);
      html += '<div class="row">' +
        '<span class="dot" style="background:' + c + '"></span>' +
        '<span style="flex:1;color:#c8dcec;">' + r.name + '</span>' +
        '<span style="color:' + c + ';font-size:9px;">M' + r.I.toFixed(1) + '</span>' +
        '<span style="color:#ff7a7a;font-size:9px;min-width:35px;text-align:right;">' + (r.deaths < 1 ? '—' : fmtNum(r.deaths)) + '</span>' +
        '</div>';
    }
    if (!html) html = '<div class="empty">附近无大型城市</div>';
    document.getElementById('list').innerHTML = html;
  }

  function updateDeaths() {
    var diff = sim.total - sim.displayed;
    if (Math.abs(diff) > 0.5) {
      sim.displayed += diff * 0.15;
      if (Math.abs(sim.total - sim.displayed) < 1) sim.displayed = sim.total;
    }
    document.getElementById('deaths').textContent = fmtNum(Math.round(sim.displayed));
  }

  // ===== 主循环 =====
  function loop() {
    try {
      draw();
      updateDeaths();
    } catch(e) {
      document.getElementById('hint').textContent = '错误: ' + e.message;
      document.getElementById('hint').style.color = '#ff6b6b';
    }
    requestAnimationFrame(loop);
  }

  // ===== 事件 =====
  document.getElementById('mag').addEventListener('input', function() {
    sim.mag = parseFloat(this.value);
    document.getElementById('magVal').textContent = sim.mag.toFixed(1);
    computeSim();
  });

  document.getElementById('dep').addEventListener('input', function() {
    sim.depth = parseInt(this.value, 10);
    document.getElementById('depVal').textContent = sim.depth + ' km';
    computeSim();
  });

  canvas.addEventListener('pointerdown', function(e) {
    var rect = canvas.getBoundingClientRect();
    var x = e.clientX - rect.left;
    var y = e.clientY - rect.top;
    sim.lon = x / W * 360 - 180;
    sim.lat = 90 - y / H * 180;
    computeSim();
  });

  document.getElementById('randomBtn').addEventListener('click', function() {
    sim.lon = Math.random() * 360 - 180;
    sim.lat = Math.random() * 100 - 50;
    computeSim();
  });

  document.getElementById('worstBtn').addEventListener('click', function() {
    sim.mag = 9.5;
    sim.depth = 10;
    document.getElementById('mag').value = 9.5;
    document.getElementById('dep').value = 10;
    document.getElementById('magVal').textContent = '9.5';
    document.getElementById('depVal').textContent = '10 km';
    sim.lon = 140.29;
    sim.lat = 35.38;
    computeSim();
  });

  window.addEventListener('resize', function() {
    resize();
    computeSim();
  });

  // ===== 启动 =====
  resize();
  computeSim();
  loop();

})();
</script>
</body>
</html>

Game Source: 地震模拟器 · 极简稳定版

Creator: FlashMaker22

Libraries: none

Complexity: complex (368 lines, 13.5 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-flashmaker22" to link back to the original. Then publish at arcadelab.ai/publish.