地震模拟器 · 极简稳定版
by FlashMaker22368 lines13.5 KB
<!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.