未命名 0nSrB4edit icon

创建者:
邓朝元
Fork(复制)
下载
嵌入
BUG反馈
index.html
index.html
            
            <!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>
        
编辑器加载中
预览
控制台