<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>网格帝国:领土战争模拟器</title>
<style>
:root {
--bg-color: #f0f2f5;
--grid-line: #e1e4e8;
--text-color: #333;
--panel-bg: rgba(255, 255, 255, 0.95);
--shadow: 0 4px 12px rgba(0,0,0,0.1);
--font-family: 'Segoe UI', sans-serif;
}
/* 主题定义 */
body.theme-modern { --bg-color: #f0f2f5; --grid-line: #d1d5db; --text-color: #1f2937; --panel-bg: rgba(255,255,255,0.9); }
body.theme-cyber {
--bg-color: #050505;
--grid-line: #1a1a1a;
--text-color: #00ff9d;
--panel-bg: rgba(10, 15, 20, 0.9);
--shadow: 0 0 15px rgba(0, 255, 157, 0.1);
}
body.theme-retro {
--bg-color: #e8e0c5;
--grid-line: #cbbfa0;
--text-color: #5d4037;
--panel-bg: rgba(232, 224, 197, 0.95);
font-family: 'Courier New', monospace;
}
body.theme-deep-space {
--bg-color: #0b0d17;
--grid-line: #1f2345;
--text-color: #a2aaff;
--panel-bg: rgba(11, 13, 23, 0.9);
}
* { margin: 0; padding: 0; box-sizing: border-box; user-select: none; }
body {
background-color: var(--bg-color);
color: var(--text-color);
overflow: hidden;
font-family: var(--font-family);
height: 100vh;
width: 100vw;
transition: background 0.5s;
}
canvas { display: block; }
/* UI 层 */
#ui-container {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 15px;
padding: 12px 20px;
background: var(--panel-bg);
border-radius: 8px;
box-shadow: var(--shadow);
backdrop-filter: blur(5px);
z-index: 10;
border: 1px solid rgba(128,128,128,0.1);
}
.control-item { display: flex; flex-direction: column; align-items: center; gap: 4px; }
label { font-size: 12px; opacity: 0.8; font-weight: bold; }
select, button {
padding: 6px 12px;
border-radius: 4px;
border: 1px solid rgba(128,128,128,0.3);
background: rgba(255,255,255,0.1);
color: var(--text-color);
cursor: pointer;
font-size: 13px;
outline: none;
}
button.primary { background: var(--text-color); color: var(--bg-color); border: none; font-weight: bold; }
button:hover { opacity: 0.9; }
/* 状态栏 */
#status-bar {
position: absolute;
bottom: 20px;
left: 20px;
background: var(--panel-bg);
padding: 8px 16px;
border-radius: 6px;
font-size: 13px;
box-shadow: var(--shadow);
pointer-events: none;
}
/* 移动端适配 */
@media (max-width: 768px) {
#ui-container {
top: auto;
bottom: 20px;
width: 90%;
justify-content: space-around;
padding: 10px;
}
.control-item { flex-direction: row; gap: 8px; }
#status-bar { bottom: 100px; left: 50%; transform: translateX(-50%); width: 90%; text-align: center; }
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="ui-container">
<div class="control-item">
<label>风格</label>
<select id="themeSelect">
<option value="modern">现代简约</option>
<option value="cyber">赛博朋克</option>
<option value="retro">复古战棋</option>
<option value="deep-space">深空战略</option>
</select>
</div>
<div class="control-item">
<label>速度</label>
<select id="speedSelect">
<option value="1">慢速</option>
<option value="3" selected>正常</option>
<option value="10">极速</option>
</select>
</div>
<div class="control-item">
<button id="restartBtn" class="primary">重置世界</button>
</div>
</div>
<div id="status-bar">
存活国家:<span id="nationCount">0</span> | 总格子数:<span id="totalCells">0</span>
</div>
<script>
/**
* 网格帝国模拟器
* 核心逻辑:基于元胞自动机的领土扩张
*/
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d', { alpha: false }); // 优化性能
// 配置
const CONFIG = {
minNations: 6,
cellSize: 12, // 每个小方格的像素大小
baseGrowthRate: 0.05, // 基础扩张概率
colors: [
'#FF4757', '#2ED573', '#1E90FF', '#FFA502', '#8E44AD',
'#00CEC9', '#FF7675', '#74B9FF', '#55E6C1', '#FDCB6E'
]
};
// 全局状态
let grid = []; // 一维数组存储网格数据
let cols = 0;
let rows = 0;
let nations = [];
let animationId;
let lastTime = 0;
let simulationSpeed = 3;
let currentTheme = 'modern';
// 国家类
class Nation {
constructor(id, color, startX, startY, power) {
this.id = id;
this.color = color;
this.power = power; // 当前总兵力/影响力
this.maxPower = power * 2;
this.cells = []; // 拥有的格子索引列表
this.alive = true;
this.name = `帝国 ${id}`;
// 初始占领
this.claimCell(startX, startY);
}
claimCell(x, y) {
const index = y * cols + x;
if (grid[index] !== null && grid[index] !== this) return;
// 如果格子原本属于别人,先移除那个主人的记录
if (grid[index] !== null) {
const oldOwner = grid[index];
const cellIdx = oldOwner.cells.indexOf(index);
if (cellIdx > -1) oldOwner.cells.splice(cellIdx, 1);
if (oldOwner.cells.length === 0) oldOwner.die();
}
grid[index] = this;
this.cells.push(index);
this.power += 10; // 占领格子增加国力
}
die() {
this.alive = false;
this.cells = [];
}
update() {
if (!this.alive) return;
// 国力自然恢复
if (this.power < this.maxPower) {
this.power += 0.5;
}
// 扩张逻辑:尝试从现有领土边缘向外扩张
// 为了性能,我们随机选取几个拥有的格子作为“前线”尝试扩张
const attempts = Math.min(this.cells.length, Math.ceil(this.power / 50));
for (let i = 0; i < attempts; i++) {
if (Math.random() > CONFIG.baseGrowthRate * simulationSpeed) continue;
const sourceIndex = this.cells[Math.floor(Math.random() * this.cells.length)];
const sy = Math.floor(sourceIndex / cols);
const sx = sourceIndex % cols;
// 随机选择一个方向 (上右下左)
const dir = Math.floor(Math.random() * 4);
let nx = sx, ny = sy;
if (dir === 0) ny--; // 上
else if (dir === 1) nx++; // 右
else if (dir === 2) ny++; // 下
else if (dir === 3) nx--; // 左
// 边界检查
if (nx >= 0 && nx < cols && ny >= 0 && ny < rows) {
const targetIndex = ny * cols + nx;
const targetOwner = grid[targetIndex];
if (targetOwner === null) {
// 空地:直接占领
this.claimCell(nx, ny);
} else if (targetOwner !== this) {
// 敌对领土:发生战斗
// 战斗公式:攻击方国力 vs 防守方在该格子的稳固度 (简化为随机 + 国力差)
const attackRoll = this.power * Math.random();
const defenseRoll = targetOwner.power * Math.random() * 0.8; // 防守方略有劣势鼓励进攻
if (attackRoll > defenseRoll) {
this.claimCell(nx, ny);
targetOwner.power -= 20; // 防守方损失兵力
} else {
this.power -= 5; // 进攻失败损失少量兵力
}
}
}
}
}
}
// 初始化世界
function initWorld() {
resizeCanvas();
grid = new Array(cols * rows).fill(null);
nations = [];
// 生成国家
for (let i = 0; i < CONFIG.minNations; i++) {
// 随机位置,确保不重叠
let placed = false;
let attempts = 0;
while (!placed && attempts < 100) {
const x = Math.floor(Math.random() * (cols - 4)) + 2;
const y = Math.floor(Math.random() * (rows - 4)) + 2;
// 检查周围是否有其他国家
const idx = y * cols + x;
if (grid[idx] === null) {
const color = CONFIG.colors[i % CONFIG.colors.length];
// 随机初始国力差异
const startPower = 100 + Math.random() * 100;
nations.push(new Nation(i, color, x, y, startPower));
placed = true;
}
attempts++;
}
}
updateStats();
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// 重新计算行列,保持格子大小一致
cols = Math.floor(canvas.width / CONFIG.cellSize);
rows = Math.floor(canvas.height / CONFIG.cellSize);
// 如果调整窗口导致网格变化,需要重置或迁移数据(这里简化为重置)
if (grid.length !== cols * rows) {
initWorld();
}
}
// 渲染循环
function draw() {
const time = performance.now();
const dt = time - lastTime;
// 控制帧率逻辑在 update 中处理,这里只负责绘制
// 1. 绘制背景
ctx.fillStyle = getComputedStyle(document.body).getPropertyValue('--bg-color').trim();
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. 绘制网格和领土
// 优化:只遍历可见区域或直接遍历 grid 数组
// 为了展示网格线,我们需要逐个绘制或批量绘制
// 这里采用批量填充颜色,然后叠加网格线
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const cell = grid[y * cols + x];
if (cell) {
ctx.fillStyle = cell.color;
ctx.fillRect(x * CONFIG.cellSize, y * CONFIG.cellSize, CONFIG.cellSize, CONFIG.cellSize);
}
}
}
// 3. 绘制网格线 (可选,根据主题决定是否显示)
if (currentTheme !== 'cyber') {
ctx.strokeStyle = getComputedStyle(document.body).getPropertyValue('--grid-line').trim();
ctx.lineWidth = 1;
ctx.beginPath();
// 垂直线
for (let x = 0; x <= cols; x++) {
ctx.moveTo(x * CONFIG.cellSize, 0);
ctx.lineTo(x * CONFIG.cellSize, rows * CONFIG.cellSize);
}
// 水平线
for (let y = 0; y <= rows; y++) {
ctx.moveTo(0, y * CONFIG.cellSize);
ctx.lineTo(cols * CONFIG.cellSize, y * CONFIG.cellSize);
}
ctx.stroke();
}
// 4. 绘制国家标识 (中心点)
nations.forEach(n => {
if (!n.alive || n.cells.length === 0) return;
// 计算中心
let sumX = 0, sumY = 0;
n.cells.forEach(idx => {
sumX += idx % cols;
sumY += Math.floor(idx / cols);
});
const cx = (sumX / n.cells.length) * CONFIG.cellSize + CONFIG.cellSize/2;
const cy = (sumY / n.cells.length) * CONFIG.cellSize + CONFIG.cellSize/2;
// 绘制文字
ctx.fillStyle = '#fff';
ctx.font = 'bold 10px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 添加阴影让文字更清晰
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 4;
ctx.fillText(n.id, cx, cy);
ctx.shadowBlur = 0;
});
requestAnimationFrame(draw);
}
// 逻辑更新循环 (独立于渲染,控制速度)
function updateLoop() {
if (!isPaused) {
nations.forEach(n => n.update());
// 清理死亡国家
const aliveCount = nations.filter(n => n.alive).length;
if (aliveCount !== lastAliveCount) {
updateStats();
lastAliveCount = aliveCount;
}
// 胜利判定
if (aliveCount <= 1 && nations.length > 1) {
// 可以在这里添加游戏结束逻辑
}
}
setTimeout(() => {
requestAnimationFrame(updateLoop);
}, 1000 / 60); // 固定逻辑更新频率
}
let isPaused = false;
let lastAliveCount = 0;
function updateStats() {
const alive = nations.filter(n => n.alive).length;
document.getElementById('nationCount').innerText = alive;
document.getElementById('totalCells').innerText = (cols * rows).toLocaleString();
}
// 事件监听
window.addEventListener('resize', () => {
// 防抖处理
clearTimeout(window.resizeTimer);
window.resizeTimer = setTimeout(initWorld, 200);
});
document.getElementById('restartBtn').addEventListener('click', initWorld);
document.getElementById('themeSelect').addEventListener('change', (e) => {
currentTheme = e.target.value;
document.body.className = `theme-${currentTheme}`;
// 强制重绘以应用新样式
draw();
});
document.getElementById('speedSelect').addEventListener('change', (e) => {
simulationSpeed = parseInt(e.target.value);
});
// 启动
initWorld();
lastAliveCount = nations.length;
requestAnimationFrame(draw);
updateLoop();
</script>
</body>
</html>
index.html
index.html