网址:AtomGit - 全球开发者的开源社区,开源代码托管平台

一、前言:AI赋能开源开发,新手也能玩转实战项目

        在AI赋能开发的当下,开源免费的AI编码工具正在彻底降低编程实战的门槛。AtomCode 作为一款开源、多模型适配、免费Token的全自动AI编码助手,无需复杂配置、无需高额付费,兼顾代码生成、问题调试、功能优化、样式美化等全流程开发能力,是开发者入门实战、落地小型项目、打磨编码能力的优质工具。

        本次参与AtomGit「码动四季·开源同行」夏季征稿活动,我将完整分享借助AtomCode从零开发网页版坦克大战游戏的全流程实战经验。从项目需求梳理、核心逻辑搭建、代码编写调试,到UI视觉美化、交互优化、bug修复,全程依托AtomCode辅助开发,完整记录开源AI工具在前端实战项目中的落地价值,同时拆解新手如何高效借助AtomCode快速落地完整可运行项目。

        本次开发的坦克大战基于原生 HTML+CSS+JavaScript 实现,无需依赖任何框架,轻量化、可直接运行、功能完整,包含坦克移动射击、敌人AI刷新、墙体碰撞、道具拾取、粒子特效、计分系统、暂停重启、游戏胜负判定等全套核心功能,同时搭配赛博风动态UI,视觉效果远超基础原生小游戏。

二、项目开发前置:为什么选择AtomCode赋能本次开发?

        在传统原生JS小游戏开发中,新手往往会面临诸多痛点:碰撞逻辑繁琐、动画特效编写复杂、边界判定容易出错、UI样式单调、bug排查耗时、功能迭代繁琐。而AtomCode完美解决了这些开发难题,相比传统手动编码、普通AI编码工具,具备多重核心优势,也是我本次选择它全程辅助开发的核心原因:

        1. 开源免费无门槛:依托AtomGit开源生态,提供免费Token额度,无需付费、无需复杂部署,个人开发者可直接使用,适配学生、新手开发者实战练手需求。

        2. 全场景编码适配:支持原生前端、后端、小游戏、工具类代码生成,针对本次原生JS坦克大战,能够精准生成规范、可运行、高兼容性的代码,无冗余垃圾代码。

        3. 调试优化一体化:不仅能生成基础代码,还能针对性优化代码逻辑、修复运行bug、优化性能、美化UI样式,实现「代码生成-问题修复-功能迭代-视觉升级」全流程赋能。

        4. 贴合实战场景:区别于通用AI的模板化代码,AtomCode生成的代码贴合真实开发规范,注释清晰、结构分层合理,便于二次修改和功能拓展,非常适合新手学习落地实战项目。

        本次项目我全程采用「需求拆解+AI辅助生成+人工微调优化」的开发模式,大幅缩短开发周期,原本需要3-4天的完整项目开发,依托AtomCode仅用半天即可完成,同时保证代码质量和项目完整性。

三、项目整体需求与功能架构拆解

        在正式开发前,我通过AtomCode辅助完成项目需求拆解和架构设计,明确本次坦克大战的核心功能模块,让开发流程条理清晰、避免逻辑混乱。

核心功能需求

1. 基础操作:方向键控制坦克上下左右移动、空格键开火、P键暂停/继续游戏;

2. 游戏场景:内置砖墙、钢铁墙双重障碍物,砖墙可被炮弹摧毁,钢铁墙永久不可破坏;

3. 敌人AI:自动刷新敌方坦克,敌方自主移动、自动瞄准玩家射击,智能规避墙体障碍;

4. 道具系统:随机刷新生命血包、加速道具,拾取后可恢复血量、临时提升移动速度;

5. 数据统计:实时展示关卡、得分、击杀数、剩余生命值,数据实时更新;

6. 特效交互:子弹光影、爆炸粒子特效、无敌闪烁、加速状态提示、动态背景动画;

7. 胜负机制:玩家血量耗尽游戏失败,累计击杀达标即可胜利,支持一键重启游戏;

8. 容错机制:完善的异常捕获、DOM校验、边界判定,避免游戏闪退、卡死、报错。

项目技术架构

项目采用分层开发思路,通过AtomCode辅助拆分四大核心模块,结构清晰易维护:

1. UI视觉层:HTML搭建页面结构,CSS实现动态渐变背景、霓虹边框、发光文字、悬浮动画;

2. 场景渲染层:Canvas画布实现游戏主体渲染,绘制坦克、墙体、子弹、道具、粒子特效;

3. 逻辑交互层:JS实现键盘监听、移动碰撞、射击逻辑、敌人AI、道具刷新、数据统计;

4. 状态管理层:统一管控游戏暂停、结束、胜利、无敌、加速等全局状态。

网页链接:https://ai.gitcode.com/ascend-model-ecosystem#mofix-agent

需要登录AtomCode

登录昇腾终端

昇腾显卡加持

登录使用

或者直接在web使用

四、AtomCode实战开发全流程:从0到1落地项目

        本次开发全程依托AtomCode完成核心代码生成、逻辑优化、bug修复、样式升级,我将拆解关键开发步骤,分享具体的AI使用技巧和实操心得。

4.1 初始化项目结构,生成基础模板代码

        新手开发小游戏最容易出现的问题是项目结构混乱、代码堆砌、无规范。我通过向AtomCode输入精准需求:「帮我生成一个原生HTML+CSS+JS坦克大战基础模板,包含Canvas画布、自适应布局、基础页面结构、全局样式重置、容错处理,代码结构分层清晰」

终端更加详细:

        AtomCode快速生成了标准化的项目基础框架,包含完整的HTML结构、全局样式重置、动画预设、DOM获取容错函数,从源头保证代码规范。同时自动封装了safeGetElementsafeSetText等工具函数,避免DOM获取失败导致的页面报错,大幅提升项目稳定性。

4.2 核心游戏逻辑开发,AI高效攻克难点

        坦克大战的核心难点集中在碰撞检测、敌人AI逻辑、子弹轨迹判定、实体移动限制,这也是原生JS开发最繁琐、最容易出错的部分。传统手动编写需要反复调试坐标、修正碰撞误差,耗时费力。

我借助AtomCode分模块生成核心逻辑代码:

        1. 碰撞检测逻辑:指令需求「编写精准的矩形碰撞检测函数,支持坦克与墙体、坦克与敌人、子弹与障碍物、子弹与坦克的碰撞判定,区分可摧毁砖墙和不可摧毁钢铁墙」。AtomCode生成的rectCollide函数判定精准,完美解决实体穿透、误碰撞问题。

        2. 玩家操控逻辑:实现方向键八向移动、空格开火、开火冷却、无敌保护机制,同时优化移动逻辑,支持单轴移动判定,避免坦克卡墙卡死。

        3. 敌人AI智能逻辑:通过AI生成随机移动机制、玩家瞄准逻辑、自动射击冷却,敌方坦克可自主规避障碍、动态调整移动方向,同时根据距离判定是否瞄准玩家射击,AI行为更加智能真实。

        4. 实体刷新机制:自动限制最大敌人数、定时刷新敌方坦克、随机刷新道具,保证游戏难度均衡,提升游玩体验。

4.3 视觉特效全面升级,告别简陋原生样式

        基础原生小游戏普遍存在UI单调、无动画、无特效的问题,我通过AtomCode针对性优化视觉效果,打造赛博朋克动态游戏界面:

        1. 动态渐变背景:实现四色渐变无限滚动动画,搭配悬浮粒子效果,页面层次感拉满;

        2. 霓虹发光特效:标题流光、画布脉冲边框、数值发光高亮,适配游戏科技风格;

        3. 粒子爆炸特效:坦克被击毁、子弹撞墙、道具拾取时触发动态粒子扩散效果;

        4. 状态可视化:无敌闪烁、加速状态提示、游戏暂停遮罩、结束弹窗动画,交互反馈清晰直观;

        5. 数据面板美化:将分数、击杀数、生命值、关卡数据模块化展示,配色区分状态,视觉整洁高级。

        整个美化过程无需手动查阅CSS文档,通过AtomCode「需求描述+迭代优化」即可快速实现,大幅降低前端样式开发成本。

4.4 bug调试与性能优化,AI精准排错

        项目开发中我遇到了多个典型问题:坦克卡墙卡死、子弹穿透障碍物、敌人无限刷新、道具不消失、游戏结束不弹窗等。以往需要逐行排查代码,耗时很久,而AtomCode的调试能力极大提升了效率。

我直接粘贴报错代码和问题现象,AtomCode快速定位bug根源并给出修复方案:

1. 修复坦克双向碰撞冲突,区分玩家与敌人碰撞逻辑,避免实体重叠卡死;

2. 优化子弹生命周期管理,过滤失效子弹,避免内存占用过高;

3. 增加道具过期销毁机制,防止场景道具堆积;

4. 完善游戏状态锁,暂停、结束状态下禁止多余操作;

5. 全局增加异常捕获,避免单个模块报错导致整个游戏崩溃。

        经过AtomCode的迭代优化,项目实现了零报错、零卡死、运行流畅稳定,完全达到可上线体验的标准。

五、AtomCode使用核心心得与技巧总结

        本次完整开发坦克大战项目,让我深度体验到AtomCode作为开源AI编码助手的核心价值,相比传统开发和其他AI工具,优势十分突出,同时也总结出新手高效使用的技巧:

5.1 核心使用优势

        1. 开源免费,轻量化无负担:依托AtomGit开源生态,免费Token足够日常开发、项目实战、代码学习,无需付费订阅,对学生和入门开发者极度友好。

        2. 代码实用性极强:生成的代码无套路、无冗余,可直接运行,逻辑严谨、注释规范,完全贴合真实开发标准,不是“玩具代码”。

        3. 全流程赋能开发:从项目架构、代码生成、功能迭代、UI美化、bug调试到性能优化,一站式解决开发全流程问题,无需切换多个工具。

        4. 适配新手成长:不仅能快速落地项目,还能通过生成的规范代码,学习分层开发、容错处理、逻辑封装的编程思维,实现“做项目+学技术”双向提升。

5.2 新手高效使用技巧

        1. 需求精准拆分:不要一次性输入复杂需求,将项目拆分为结构、逻辑、样式、特效、调试等小模块,逐一生成,代码更精准、BUG更少;

        2. 先复用再优化:先用AtomCode生成基础可运行代码,再通过AI迭代优化细节、美化样式、拓展功能,大幅提升开发效率;

        3. 借助AI学逻辑:对于碰撞检测、AI逻辑等难点代码,可让AtomCode逐行讲解原理,在实战中吃透原生JS核心知识点;

        4. 善用调试能力:遇到运行报错直接粘贴问题,无需自行盲目排查,AI可快速定位并修复底层逻辑问题。

六、开源视角:AtomCode带给开发者的价值思考

        本次借助AtomCode完成实战项目,让我对开源AI工具有了全新的认知。在开源生态快速发展的今天,编程开发早已不是“纯手动敲代码”的时代,AtomCode以开源、免费、专业、轻量化的定位,打破了传统AI编码工具付费贵、代码质量差、适配性弱的痛点。

        对于入门开发者而言,AtomCode降低了实战门槛,让每个人都能低成本落地完整项目,积累开源作品、夯实编程基础;对于进阶开发者而言,可借助AtomCode快速实现功能原型、优化代码性能、提升开发效率,专注于核心逻辑创新。

        同时,AtomCode扎根AtomGit开源社区,依托社区生态持续迭代优化,汇聚海量开发者实战经验,形成了「工具赋能开发+社区共建成长」的良性开源生态,这也是本次积极参与「码动四季·开源同行」征稿活动的核心原因——希望通过真实实战案例,让更多开发者了解并使用这款优质开源AI编码工具。

七、项目最终效果与源码说明

        本次开发的坦克大战项目已实现完整可运行、功能全覆盖、视觉高颜值的效果,支持PC端浏览器直接打开游玩,操作流畅、特效炫酷、难度均衡,兼具趣味性与技术性。

        项目源码全程由AtomCode辅助生成、人工优化微调,代码结构清晰、注释完整、可二次拓展,新手可直接下载运行学习,也可基于此迭代双人模式、关卡升级、更多道具皮肤等拓展功能。

八、总结

        本次基于AtomCode开发网页坦克大战的实战经历,让我真切感受到开源AI工具的强大赋能能力。从零基础搭建项目架构、编写核心游戏逻辑,到美化UI特效、调试修复bug、优化项目性能,AtomCode全程高效助力,极大缩短了开发周期,同时保证了项目质量与代码规范性。

        「码动四季·开源同行」活动让更多开发者有机会接触、体验、深耕AtomCode开源生态。对于想要快速落地实战项目、提升编程能力、积累开源作品的开发者来说,AtomCode无疑是性价比最高、最适配新手的免费开源AI编码助手。未来我也会继续依托AtomCode,深耕前端实战、开源项目开发,持续输出更多优质实战内容,与开源社区共同成长。

坦克大战完整代码如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>坦克大战 - Tank Battle</title>
    <style>
        /* ========== 全局重置与主题 ========== */
        * { margin: 0; padding: 0; box-sizing: border-box; }

        @keyframes bgShift {
            0%   { background-position: 0% 50%; }
            50%  { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
        @keyframes titleGlow {
            0%, 100% { text-shadow: 0 0 10px #4fc3f7, 0 0 20px #4fc3f7, 0 0 40px #0288d1; }
            50%      { text-shadow: 0 0 15px #81c784, 0 0 30px #81c784, 0 0 60px #388e3c; }
        }
        @keyframes borderPulse {
            0%, 100% { border-color: #4fc3f7; box-shadow: 0 0 12px rgba(79,195,247,0.3); }
            50%      { border-color: #81c784; box-shadow: 0 0 20px rgba(129,199,132,0.5); }
        }
        @keyframes fadeInUp {
            from { opacity: 0; transform: translateY(30px) scale(0.9); }
            to   { opacity: 1; transform: translateY(0) scale(1); }
        }
        @keyframes overlayIn {
            0%   { opacity: 0; backdrop-filter: blur(0px); }
            100% { opacity: 1; backdrop-filter: blur(4px); }
        }
        @keyframes floatDot {
            0%, 100% { transform: translateY(0); opacity: 0.3; }
            50%      { transform: translateY(-20px); opacity: 0.7; }
        }
        @keyframes neonPulse {
            0%, 100% { box-shadow: 0 0 5px #4fc3f7, 0 0 10px #4fc3f7; }
            50%      { box-shadow: 0 0 15px #81c784, 0 0 30px #81c784; }
        }

        body {
            background: linear-gradient(-45deg, #0a0a1a, #1a1a3e, #0d2137, #16213e);
            background-size: 400% 400%;
            animation: bgShift 12s ease infinite;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
            overflow: hidden;
        }

        /* 浮动背景粒子 */
        body::before, body::after {
            content: '';
            position: fixed;
            width: 300px; height: 300px;
            border-radius: 50%;
            background: radial-gradient(circle, rgba(79,195,247,0.06), transparent 70%);
            pointer-events: none;
            z-index: 0;
        }
        body::before { top: -80px; left: -80px; animation: floatDot 6s ease-in-out infinite; }
        body::after { bottom: -80px; right: -80px; animation: floatDot 8s ease-in-out infinite reverse; }

        /* ========== 页面标题 ========== */
        .page-title {
            text-align: center;
            margin-bottom: 18px;
            font-size: 28px;
            font-weight: 900;
            letter-spacing: 4px;
            color: #e0e0e0;
            animation: titleGlow 3s ease-in-out infinite;
            position: relative;
            z-index: 1;
        }
        .page-title span { color: #4fc3f7; }

        /* ========== 游戏容器 ========== */
        .game-container {
            background: rgba(13, 18, 30, 0.85);
            backdrop-filter: blur(12px);
            border-radius: 20px;
            padding: 28px;
            box-shadow: 0 8px 48px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.05);
            border: 1px solid rgba(79,195,247,0.15);
            position: relative;
            z-index: 1;
        }

        /* ========== 游戏状态栏 ========== */
        .game-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 16px;
            color: #c8c8d8;
            font-size: 16px;
            font-weight: 600;
            padding: 0 4px;
        }
        .game-header .stat-item {
            display: flex;
            align-items: center;
            gap: 6px;
            background: rgba(255,255,255,0.04);
            padding: 6px 14px;
            border-radius: 10px;
            border: 1px solid rgba(255,255,255,0.06);
        }
        .game-header .stat-icon { font-size: 18px; }
        .game-header .stat-label { opacity: 0.6; font-size: 13px; font-weight: 400; }
        .game-header .stat-value {
            font-size: 20px;
            font-weight: 800;
            min-width: 24px;
            text-align: center;
        }
        .game-header .score .stat-value { color: #4fc3f7; text-shadow: 0 0 8px rgba(79,195,247,0.4); }
        .game-header .lives .stat-value { color: #ef5350; text-shadow: 0 0 8px rgba(239,83,80,0.4); }
        .game-header .level .stat-value { color: #81c784; text-shadow: 0 0 8px rgba(129,199,132,0.4); }
        .game-header .kills .stat-value { color: #ffb74d; text-shadow: 0 0 8px rgba(255,183,77,0.4); }

        /* ========== 画布 ========== */
        canvas {
            display: block;
            border: 2px solid rgba(79,195,247,0.3);
            border-radius: 12px;
            background: #141428;
            animation: borderPulse 4s ease-in-out infinite, neonPulse 3s ease-in-out infinite;
            position: relative;
            z-index: 1;
        }

        .canvas-wrapper {
            position: relative;
            display: inline-block;
        }
        .canvas-wrapper::before {
            content: '';
            position: absolute;
            inset: -3px;
            border-radius: 14px;
            background: linear-gradient(45deg, #4fc3f7, #81c784, #4fc3f7);
            opacity: 0.15;
            z-index: 0;
            animation: neonPulse 4s ease-in-out infinite;
        }

        /* ========== 游戏结束遮罩 ========== */
        .game-over-overlay {
            display: none;
            position: absolute;
            top: 0; left: 0; right: 0; bottom: 0;
            background: rgba(0,0,0,0.7);
            backdrop-filter: blur(4px);
            border-radius: 12px;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            color: white;
            z-index: 10;
            animation: overlayIn 0.6s ease-out;
        }
        .game-over-overlay.show {
            display: flex;
        }
        .game-over-overlay h1 {
            font-size: 52px;
            margin-bottom: 10px;
            animation: fadeInUp 0.5s ease-out 0.1s both;
            text-shadow: 0 0 20px rgba(255,255,255,0.3);
        }
        .game-over-overlay .final-score {
            font-size: 22px;
            margin-bottom: 8px;
            color: #4fc3f7;
            animation: fadeInUp 0.5s ease-out 0.2s both;
        }
        .game-over-overlay .sub-text {
            font-size: 14px;
            color: #999;
            margin-bottom: 24px;
            animation: fadeInUp 0.5s ease-out 0.3s both;
        }
        .game-over-overlay .restart-btn {
            padding: 14px 48px;
            font-size: 20px;
            background: linear-gradient(135deg, #4fc3f7, #0288d1);
            color: #fff;
            border: none;
            border-radius: 12px;
            cursor: pointer;
            font-weight: 700;
            letter-spacing: 1px;
            transition: all 0.3s;
            animation: fadeInUp 0.5s ease-out 0.35s both;
            box-shadow: 0 4px 20px rgba(79,195,247,0.3);
        }
        .game-over-overlay .restart-btn:hover {
            transform: translateY(-3px) scale(1.03);
            box-shadow: 0 8px 30px rgba(79,195,247,0.5);
            background: linear-gradient(135deg, #81c784, #4fc3f7);
        }
        .game-over-overlay .restart-btn:active {
            transform: scale(0.97);
        }

        /* ========== 操作提示 ========== */
        .controls {
            margin-top: 16px;
            color: #8888aa;
            font-size: 13px;
            text-align: center;
            padding: 10px 16px;
            background: rgba(255,255,255,0.03);
            border-radius: 12px;
            border: 1px solid rgba(255,255,255,0.05);
            letter-spacing: 0.5px;
        }
        .controls kbd {
            background: rgba(79,195,247,0.1);
            color: #4fc3f7;
            padding: 3px 10px;
            border-radius: 6px;
            font-size: 12px;
            border: 1px solid rgba(79,195,247,0.2);
            font-family: inherit;
            font-weight: 600;
        }
        .controls .divider {
            color: rgba(255,255,255,0.1);
            margin: 0 6px;
        }

        /* ========== 错误横幅 ========== */
        .error-banner {
            display: none;
            background: linear-gradient(135deg, #c62828, #b71c1c);
            color: white;
            padding: 10px 20px;
            border-radius: 10px;
            margin-bottom: 10px;
            font-size: 14px;
            text-align: center;
            font-weight: 500;
            box-shadow: 0 4px 16px rgba(198,40,40,0.3);
        }
        .error-banner.show { display: block; }
    </style>
</head>
<body>
<div class="game-container">
    <div class="page-title">⚡ 坦克<span>大战</span> ⚡</div>
    <div class="game-header">
        <div class="stat-item level">
            <span class="stat-icon">🏰</span>
            <span class="stat-label">关卡</span>
            <span class="stat-value" id="levelDisplay">1</span>
        </div>
        <div class="stat-item score">
            <span class="stat-icon">🏆</span>
            <span class="stat-label">分数</span>
            <span class="stat-value" id="scoreDisplay">0</span>
        </div>
        <div class="stat-item kills">
            <span class="stat-icon">💀</span>
            <span class="stat-label">击杀</span>
            <span class="stat-value" id="killsDisplay">0</span>
        </div>
        <div class="stat-item lives">
            <span class="stat-icon">❤️</span>
            <span class="stat-label">生命</span>
            <span class="stat-value" id="livesDisplay">3</span>
        </div>
    </div>
    <div class="error-banner" id="errorBanner"></div>
    <div class="canvas-wrapper">
        <canvas id="gameCanvas" width="800" height="600"></canvas>
        <div class="game-over-overlay" id="gameOverOverlay">
            <h1 id="resultTitle">🎮 游戏结束</h1>
            <div class="final-score">最终得分: <span id="finalScore">0</span></div>
            <div class="sub-text" id="subText"></div>
            <button class="restart-btn" id="restartBtn">🔄 重新开始</button>
        </div>
    </div>
    <div class="controls">
        <kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd> 移动 <span class="divider">|</span> <kbd>空格</kbd> 开火 <span class="divider">|</span> <kbd>P</kbd> 暂停
    </div>
</div>

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

    // ---------- 安全工具函数 ----------
    function safeGetElement(id) {
        try {
            const el = document.getElementById(id);
            if (!el) throw new Error(`Element #${id} not found`);
            return el;
        } catch (e) {
            console.error(`[safeGetElement] ${e.message}`);
            return null;
        }
    }

    function safeSetText(id, value) {
        try {
            const el = safeGetElement(id);
            if (el) el.textContent = value;
        } catch (e) {
            console.error(`[safeSetText] ${e.message}`);
        }
    }

    // ---------- DOM 引用 ----------
    const canvas = safeGetElement('gameCanvas');
    const ctx = canvas ? canvas.getContext('2d') : null;
    const restartBtn = safeGetElement('restartBtn');
    const gameOverOverlay = safeGetElement('gameOverOverlay');
    const errorBanner = safeGetElement('errorBanner');

    if (!canvas || !ctx) {
        if (errorBanner) {
            errorBanner.textContent = '❌ 画布初始化失败,请检查浏览器支持';
            errorBanner.classList.add('show');
        }
        return;
    }

    // ---------- 游戏常量 ----------
    const TANK_SIZE = 36;
    const BULLET_SIZE = 6;
    const TANK_SPEED = 2.5;
    const BULLET_SPEED = 5;
    const ENEMY_SPEED = 1.2;
    const ENEMY_SHOOT_INTERVAL = 1200; // ms
    const MAX_ENEMIES = 4;
    const SPAWN_INTERVAL = 4000; // ms
    const INVINCIBLE_DURATION = 2000; // ms

    // ---------- 游戏状态 ----------
    let game = null;

    // ---------- 工具函数 ----------
    function rand(min, max) {
        try {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        } catch (e) {
            return min;
        }
    }

    function rectCollide(a, b) {
        try {
            return a.x < b.x + b.w && a.x + a.w > b.x &&
                   a.y < b.y + b.h && a.y + a.h > b.y;
        } catch (e) {
            return false;
        }
    }

    function distance(x1, y1, x2, y2) {
        try {
            return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
        } catch (e) {
            return Infinity;
        }
    }

    // ---------- 墙壁生成 ----------
    function generateWalls(canvasW, canvasH) {
        try {
            const walls = [];
            const w = 40, h = 20;
            // 外围墙壁 (除了顶部留出入口)
            for (let x = 0; x < canvasW; x += w) {
                walls.push({ x, y: 0, w, h, hp: 3, type: 'brick' });
                walls.push({ x, y: canvasH - h, w, h, hp: 3, type: 'brick' });
            }
            for (let y = 0; y < canvasH; y += h) {
                walls.push({ x: 0, y, w, h, hp: 3, type: 'brick' });
                walls.push({ x: canvasW - w, y, w, h, hp: 3, type: 'brick' });
            }
            // 内部障碍
            const innerPositions = [
                [120, 120], [320, 120], [520, 120],
                [200, 240], [480, 240],
                [120, 360], [320, 360], [520, 360],
                [200, 480], [480, 480],
                [360, 180], [360, 380],
            ];
            for (const [px, py] of innerPositions) {
                walls.push({ x: px, y: py, w: w * 1.5, h: h * 1.5, hp: 3, type: 'brick' });
            }
            // 钢铁墙壁 (不可摧毁)
            const steelPositions = [[360, 80], [720, 280], [40, 280]];
            for (const [px, py] of steelPositions) {
                walls.push({ x: px, y: py, w: w, h: h, hp: Infinity, type: 'steel' });
            }
            return walls;
        } catch (e) {
            console.error('[generateWalls]', e);
            return [];
        }
    }

    // ---------- 创建玩家 ----------
    function createPlayer(canvasW, canvasH) {
        try {
            return {
                x: canvasW / 2 - TANK_SIZE / 2,
                y: canvasH - TANK_SIZE - 20,
                w: TANK_SIZE,
                h: TANK_SIZE,
                dir: 'up',
                speed: TANK_SPEED,
                hp: 3,
                maxHp: 3,
                invincibleUntil: 0,
                cooldown: 0,
                cooldownMax: 15, // frames
                alive: true,
                speedBoostUntil: 0,
            };
        } catch (e) {
            console.error('[createPlayer]', e);
            return null;
        }
    }

    // ---------- 创建敌人 ----------
    function createEnemy(canvasW, canvasH) {
        try {
            const spawns = [
                { x: 40, y: 40 },
                { x: canvasW / 2 - TANK_SIZE / 2, y: 40 },
                { x: canvasW - TANK_SIZE - 40, y: 40 },
            ];
            const pos = spawns[rand(0, spawns.length - 1)];
            return {
                x: pos.x,
                y: pos.y,
                w: TANK_SIZE,
                h: TANK_SIZE,
                dir: 'down',
                speed: ENEMY_SPEED + (Math.random() * 0.4),
                hp: 1,
                alive: true,
                isEnemy: true,
                cooldown: 0,
                cooldownMax: rand(30, 80),
                moveTimer: 0,
                moveDir: 'down',
                score: 100,
            };
        } catch (e) {
            console.error('[createEnemy]', e);
            return null;
        }
    }

    // ---------- 初始化游戏 ----------
    function initGame() {
        try {
            const cw = canvas.width;
            const ch = canvas.height;

            const player = createPlayer(cw, ch);
            if (!player) throw new Error('Failed to create player');

            game = {
                player,
                enemies: [],
                bullets: [],
                walls: generateWalls(cw, ch),
                particles: [],
                healthPacks: [],
                powerups: [],
                score: 0,
                kills: 0,
                level: 1,
                paused: false,
                gameOver: false,
                win: false,
                frame: 0,
                spawnTimer: 0,
                powerupSpawnTimer: 600,
                keys: {},
                canvasW: cw,
                canvasH: ch,
            };

            // 初始生成 2 个敌人(适度挑战)
            for (let i = 0; i < 2; i++) {
                try {
                    const enemy = createEnemy(cw, ch);
                    if (enemy) game.enemies.push(enemy);
                } catch (e) {
                    console.error('[initGame] spawn enemy', e);
                }
            }

            updateUI();
            if (gameOverOverlay) gameOverOverlay.classList.remove('show');
            if (errorBanner) errorBanner.classList.remove('show');

            return true;
        } catch (e) {
            console.error('[initGame]', e);
            if (errorBanner) {
                errorBanner.textContent = '❌ 游戏初始化失败: ' + e.message;
                errorBanner.classList.add('show');
            }
            return false;
        }
    }

    // ---------- 碰撞检测 ----------
    function canMove(entity, nx, ny, walls, enemies, player) {
        try {
            const rect = { x: nx, y: ny, w: entity.w, h: entity.h };

            // 边界
            if (nx < 0 || nx + entity.w > canvas.width ||
                ny < 0 || ny + entity.h > canvas.height) {
                return false;
            }

            // 墙壁 (跳过已摧毁的墙)
            for (const wall of walls) {
                if (wall.hp <= 0) continue;
                if (rectCollide(rect, wall)) return false;
            }

            // 玩家与敌人双向碰撞(敌人之间不互相碰撞)
            if (entity.isEnemy) {
                // 敌人与玩家
                if (player && player.alive && rectCollide(rect, player)) return false;
            } else {
                // 玩家与敌人
                for (const other of enemies) {
                    if (!other || !other.alive) continue;
                    if (rectCollide(rect, other)) return false;
                }
            }

            return true;
        } catch (e) {
            console.error('[canMove]', e);
            return false;
        }
    }

    // ---------- 射击 ----------
    function shoot(entity, isEnemy) {
        try {
            if (!game || !entity || !entity.alive) return;

            const cx = entity.x + entity.w / 2 - BULLET_SIZE / 2;
            const cy = entity.y + entity.h / 2 - BULLET_SIZE / 2;
            let vx = 0, vy = 0;

            switch (entity.dir) {
                case 'up':    vy = -BULLET_SPEED; break;
                case 'down':  vy = BULLET_SPEED;  break;
                case 'left':  vx = -BULLET_SPEED; break;
                case 'right': vx = BULLET_SPEED;  break;
                default: vy = -BULLET_SPEED;
            }

            game.bullets.push({
                x: cx, y: cy,
                w: BULLET_SIZE, h: BULLET_SIZE,
                vx, vy,
                isEnemy,
                alive: true,
            });
        } catch (e) {
            console.error('[shoot]', e);
        }
    }

    // ---------- 添加粒子效果 ----------
    function addExplosion(x, y, count, color) {
        try {
            if (!game) return;
            for (let i = 0; i < count; i++) {
                const angle = Math.random() * Math.PI * 2;
                const speed = 1 + Math.random() * 3;
                game.particles.push({
                    x, y,
                    vx: Math.cos(angle) * speed,
                    vy: Math.sin(angle) * speed,
                    life: 20 + rand(0, 20),
                    maxLife: 40,
                    size: 2 + rand(0, 4),
                    color: color || '#ff6f00',
                });
            }
        } catch (e) {
            console.error('[addExplosion]', e);
        }
    }

    // ---------- 生成血包 ----------
    function spawnHealthPack() {
        try {
            if (!game) return;
            const size = 20;
            for (let attempt = 0; attempt < 20; attempt++) {
                const x = rand(40, game.canvasW - 40 - size);
                const y = rand(40, game.canvasH - 40 - size);
                const rect = { x, y, w: size, h: size };
                let blocked = false;
                for (const wall of game.walls) {
                    if (wall.hp <= 0) continue;
                    if (rectCollide(rect, wall)) { blocked = true; break; }
                }
                if (!blocked) {
                    game.healthPacks.push({ x, y, w: size, h: size, alive: true, createdAt: Date.now() });
                    return;
                }
            }
        } catch (e) {
            console.error('[spawnHealthPack]', e);
        }
    }

    // ---------- 生成速度道具 ----------
    function spawnSpeedPowerup() {
        try {
            if (!game) return;
            const size = 20;
            for (let attempt = 0; attempt < 20; attempt++) {
                const x = rand(40, game.canvasW - 40 - size);
                const y = rand(40, game.canvasH - 40 - size);
                const rect = { x, y, w: size, h: size };
                let blocked = false;
                for (const wall of game.walls) {
                    if (wall.hp <= 0) continue;
                    if (rectCollide(rect, wall)) { blocked = true; break; }
                }
                if (!blocked) {
                    game.powerups.push({ x, y, w: size, h: size, alive: true, createdAt: Date.now() });
                    return;
                }
            }
        } catch (e) {
            console.error('[spawnSpeedPowerup]', e);
        }
    }

    // ---------- 玩家输入 ----------
    function setupInput() {
        try {
            window.addEventListener('keydown', (e) => {
                try {
                    if (!game) return;
                    game.keys[e.key] = true;

                    if (e.key === 'p' || e.key === 'P') {
                        game.paused = !game.paused;
                    }
                    if (e.key === ' ' && !game.gameOver) {
                        e.preventDefault();
                        if (game.player && game.player.alive && game.player.cooldown <= 0) {
                            shoot(game.player, false);
                            game.player.cooldown = game.player.cooldownMax;
                        }
                    }
                } catch (err) {
                    console.error('[keydown handler]', err);
                }
            });

            window.addEventListener('keyup', (e) => {
                try {
                    if (game) game.keys[e.key] = false;
                } catch (err) {
                    console.error('[keyup handler]', err);
                }
            });

            // 重启按钮
            if (restartBtn) {
                restartBtn.addEventListener('click', () => {
                    try {
                        if (initGame()) {
                            requestAnimationFrame(gameLoop);
                        }
                    } catch (err) {
                        console.error('[restart]', err);
                    }
                });
            }
        } catch (e) {
            console.error('[setupInput]', e);
        }
    }

    // ---------- 更新 UI ----------
    function updateUI() {
        try {
            if (!game) return;
            safeSetText('scoreDisplay', game.score);
            safeSetText('killsDisplay', game.kills);
            safeSetText('livesDisplay', game.player ? game.player.hp : 0);
            safeSetText('levelDisplay', game.level);
        } catch (e) {
            console.error('[updateUI]', e);
        }
    }

    // ---------- 更新逻辑 ----------
    function update() {
        try {
            if (!game || game.paused || game.gameOver) return;

            const { player, enemies, bullets, walls, canvasW, canvasH } = game;
            game.frame++;

            // ----- 玩家移动 -----
            if (player && player.alive) {
                try {
                    let dx = 0, dy = 0;
                    if (game.keys['ArrowUp'] || game.keys['w'] || game.keys['W']) { dy = -1; player.dir = 'up'; }
                    else if (game.keys['ArrowDown'] || game.keys['s'] || game.keys['S']) { dy = 1; player.dir = 'down'; }
                    else if (game.keys['ArrowLeft'] || game.keys['a'] || game.keys['A']) { dx = -1; player.dir = 'left'; }
                    else if (game.keys['ArrowRight'] || game.keys['d'] || game.keys['D']) { dx = 1; player.dir = 'right'; }

                    if (dx !== 0 || dy !== 0) {
                        const nx = player.x + dx * player.speed;
                        const ny = player.y + dy * player.speed;
                        if (canMove(player, nx, ny, walls, enemies, player)) {
                            player.x = nx;
                            player.y = ny;
                        } else if (dx !== 0) {
                            // 尝试仅 x 移动
                            if (canMove(player, nx, player.y, walls, enemies, player)) {
                                player.x = nx;
                            }
                        } else if (dy !== 0) {
                            if (canMove(player, player.x, ny, walls, enemies, player)) {
                                player.y = ny;
                            }
                        }
                    }

                    if (player.cooldown > 0) player.cooldown--;

                    // 键盘射击 (空格已在 keydown 中处理)
                } catch (e) {
                    console.error('[player update]', e);
                }
            }

            // ----- 敌人 AI -----
            for (const enemy of enemies) {
                try {
                    if (!enemy.alive) continue;

                    enemy.moveTimer--;

                    if (enemy.moveTimer <= 0) {
                        const dirs = ['up', 'down', 'left', 'right'];
                        enemy.moveDir = dirs[rand(0, 3)];
                        enemy.moveTimer = rand(40, 120);
                    }

                    let dx = 0, dy = 0;
                    switch (enemy.moveDir) {
                        case 'up':    dy = -1; enemy.dir = 'up';    break;
                        case 'down':  dy = 1;  enemy.dir = 'down';  break;
                        case 'left':  dx = -1; enemy.dir = 'left';  break;
                        case 'right': dx = 1;  enemy.dir = 'right'; break;
                    }

                    const nx = enemy.x + dx * enemy.speed;
                    const ny = enemy.y + dy * enemy.speed;
                    if (canMove(enemy, nx, ny, walls, enemies, player)) {
                        enemy.x = nx;
                        enemy.y = ny;
                    } else {
                        enemy.moveDir = dirs[rand(0, 3)];
                        enemy.moveTimer = rand(20, 60);
                    }

                    // 敌人射击
                    if (enemy.cooldown <= 0) {
                        if (player && player.alive && distance(enemy.x, enemy.y, player.x, player.y) < 500) {
                            // 瞄准玩家方向
                            const dx2 = player.x - enemy.x;
                            const dy2 = player.y - enemy.y;
                            if (Math.abs(dx2) > Math.abs(dy2)) {
                                enemy.dir = dx2 > 0 ? 'right' : 'left';
                            } else {
                                enemy.dir = dy2 > 0 ? 'down' : 'up';
                            }
                            shoot(enemy, true);
                            enemy.cooldown = rand(20, 60);
                        } else if (Math.random() < 0.02) {
                            shoot(enemy, true);
                            enemy.cooldown = enemy.cooldownMax;
                        }
                    } else {
                        enemy.cooldown--;
                    }
                } catch (e) {
                    console.error('[enemy update]', e);
                }
            }

            // ----- 子弹更新 -----
            for (const bullet of bullets) {
                try {
                    if (!bullet.alive) continue;
                    bullet.x += bullet.vx;
                    bullet.y += bullet.vy;

                    // 边界
                    if (bullet.x < 0 || bullet.x > canvasW ||
                        bullet.y < 0 || bullet.y > canvasH) {
                        bullet.alive = false;
                        continue;
                    }

                    // 墙壁碰撞 (跳过已摧毁的墙)
                    for (const wall of walls) {
                        if (wall.hp <= 0) continue;
                        if (rectCollide(bullet, wall)) {
                            bullet.alive = false;
                            if (wall.hp !== Infinity) {
                                wall.hp--;
                            }
                            addExplosion(bullet.x, bullet.y, 6, '#ffab00');
                            break;
                        }
                    }
                    if (!bullet.alive) continue;

                    // 玩家子弹击中敌人
                    if (!bullet.isEnemy) {
                        for (const enemy of enemies) {
                            if (!enemy.alive) continue;
                            if (rectCollide(bullet, enemy)) {
                                bullet.alive = false;
                                enemy.alive = false;
                                game.score += enemy.score;
                                game.kills++;
                                addExplosion(enemy.x + enemy.w / 2, enemy.y + enemy.h / 2, 20, '#ff6f00');
                                spawnHealthPack();
                                updateUI();
                                break;
                            }
                        }
                    }

                    // 敌人子弹击中玩家
                    if (bullet.isEnemy && player && player.alive) {
                        if (rectCollide(bullet, player)) {
                            bullet.alive = false;
                            if (game.frame > player.invincibleUntil) {
                                player.hp--;
                                player.invincibleUntil = game.frame + INVINCIBLE_DURATION / (1000 / 60);
                                addExplosion(player.x + player.w / 2, player.y + player.h / 2, 15, '#4fc3f7');
                                updateUI();
                                if (player.hp <= 0) {
                                    player.alive = false;
                                    addExplosion(player.x + player.w / 2, player.y + player.h / 2, 30, '#ef5350');
                                }
                            }
                        }
                    }
                } catch (e) {
                    console.error('[bullet update]', e);
                }
            }

            // ----- 生成敌人 -----
            try {
                game.spawnTimer--;
                if (game.spawnTimer <= 0 && enemies.filter(e => e.alive).length < MAX_ENEMIES) {
                    const enemy = createEnemy(canvasW, canvasH);
                    if (enemy) enemies.push(enemy);
                    game.spawnTimer = SPAWN_INTERVAL / (1000 / 60);
                }
            } catch (e) {
                console.error('[spawn enemy]', e);
            }

            // ----- 生成速度道具(每 10 秒)-----
            try {
                game.powerupSpawnTimer--;
                if (game.powerupSpawnTimer <= 0) {
                    spawnSpeedPowerup();
                    game.powerupSpawnTimer = 600; // 10秒 × 60帧
                }
            } catch (e) {
                console.error('[spawn powerup]', e);
            }

            // ----- 粒子更新 -----
            for (const p of game.particles) {
                try {
                    p.x += p.vx;
                    p.y += p.vy;
                    p.vx *= 0.95;
                    p.vy *= 0.95;
                    p.life--;
                } catch (e) { /* skip bad particle */ }
            }
            game.particles = game.particles.filter(p => p.life > 0);

            // ----- 血包拾取 -----
            if (player && player.alive) {
                for (const hp of game.healthPacks) {
                    if (!hp.alive) continue;
                    if (rectCollide(player, hp)) {
                        hp.alive = false;
                        if (player.hp < player.maxHp) {
                            player.hp++;
                        }
                        addExplosion(hp.x + hp.w / 2, hp.y + hp.h / 2, 10, '#81c784');
                        updateUI();
                    }
                }
            }
            game.healthPacks = game.healthPacks.filter(hp => {
                if (!hp.alive) return false;
                if (Date.now() - hp.createdAt > 30000) return false;
                return true;
            });

            // ----- 速度道具拾取 -----
            if (player && player.alive) {
                for (const pu of game.powerups) {
                    if (!pu.alive) continue;
                    if (rectCollide(player, pu)) {
                        pu.alive = false;
                        player.speed = TANK_SPEED * 2;
                        player.speedBoostUntil = Date.now() + 5000;
                        addExplosion(pu.x + pu.w / 2, pu.y + pu.h / 2, 12, '#4fc3f7');
                    }
                }
            }
            // 速度道具过期清理(15秒)+ 加速到期恢复
            game.powerups = game.powerups.filter(pu => {
                if (!pu.alive) return false;
                if (Date.now() - pu.createdAt > 15000) return false;
                return true;
            });
            if (player && player.alive && player.speedBoostUntil > 0 && Date.now() > player.speedBoostUntil) {
                player.speed = TANK_SPEED;
                player.speedBoostUntil = 0;
            }

            // ----- 清理 -----
            game.bullets = bullets.filter(b => b.alive);
            game.enemies = enemies.filter(e => e.alive);

            // ----- 游戏结束 -----
            if (!player || !player.alive) {
                game.gameOver = true;
                game.win = false;
                showGameOver(false);
            }

            // ----- 胜利条件 (消灭 20 个敌人) -----
            if (game.score >= 2000 && player && player.alive) {
                game.gameOver = true;
                game.win = true;
                showGameOver(true);
            }

        } catch (e) {
            console.error('[update]', e);
        }
    }

    // ---------- 显示游戏结束 ----------
    function showGameOver(win) {
        try {
            if (!gameOverOverlay) return;
            const titleEl = safeGetElement('resultTitle');
            const scoreEl = safeGetElement('finalScore');
            const subTextEl = safeGetElement('subText');
            if (titleEl) titleEl.textContent = win ? '🏆 胜利!' : '💀 游戏结束';
            if (scoreEl) scoreEl.textContent = game ? game.score : 0;
            if (subTextEl && game) {
                subTextEl.textContent = win
                    ? `🎯 消灭 ${game.kills} 个敌人,你是坦克之王!`
                    : `💪 消灭 ${game.kills} 个敌人,再接再厉!`;
            }
            gameOverOverlay.classList.add('show');
        } catch (e) {
            console.error('[showGameOver]', e);
        }
    }

    // ---------- 渲染 ----------
    function draw() {
        try {
            if (!game || !ctx) return;
            const { player, enemies, bullets, walls, particles, healthPacks, powerups } = game;

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // 绘制网格背景
            try {
                ctx.strokeStyle = '#3a3a5c';
                ctx.lineWidth = 0.5;
                for (let x = 0; x < canvas.width; x += 40) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, canvas.height);
                    ctx.stroke();
                }
                for (let y = 0; y < canvas.height; y += 40) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(canvas.width, y);
                    ctx.stroke();
                }
            } catch (e) { /* skip grid */ }

            // 绘制墙壁
            for (const wall of walls) {
                try {
                    if (wall.hp <= 0) continue;
                    if (wall.type === 'steel') {
                        ctx.fillStyle = '#78909c';
                        ctx.fillRect(wall.x, wall.y, wall.w, wall.h);
                        ctx.strokeStyle = '#546e7a';
                        ctx.lineWidth = 1;
                        ctx.strokeRect(wall.x, wall.y, wall.w, wall.h);
                        // 钢纹
                        ctx.fillStyle = '#90a4ae';
                        ctx.fillRect(wall.x + 4, wall.y + 4, wall.w - 8, 4);
                        ctx.fillRect(wall.x + 4, wall.y + wall.h - 8, wall.w - 8, 4);
                    } else {
                        // 砖墙 - 根据血量显示破损程度
                        const alpha = wall.hp / 3;
                        ctx.fillStyle = `rgba(188, 108, 37, ${alpha})`;
                        ctx.fillRect(wall.x, wall.y, wall.w, wall.h);
                        ctx.strokeStyle = '#6d4c21';
                        ctx.lineWidth = 1;
                        // 砖纹
                        for (let bx = wall.x; bx < wall.x + wall.w; bx += 20) {
                            for (let by = wall.y; by < wall.y + wall.h; by += 10) {
                                ctx.strokeRect(bx, by, 20, 10);
                            }
                        }
                    }
                } catch (e) { /* skip wall */ }
            }

            // 绘制坦克
            function drawTank(tank, color, isPlayer) {
                try {
                    if (!tank || !tank.alive) return;
                    const cx = tank.x + tank.w / 2;
                    const cy = tank.y + tank.h / 2;

                    ctx.save();
                    ctx.translate(cx, cy);

                    // 闪烁 (无敌时)
                    if (isPlayer && game.frame < tank.invincibleUntil &&
                        Math.floor(game.frame / 6) % 2 === 0) {
                        ctx.globalAlpha = 0.5;
                    }

                    // 坦克主体
                    ctx.fillStyle = color;
                    ctx.fillRect(-tank.w / 2, -tank.h / 2, tank.w, tank.h);

                    // 履带
                    ctx.fillStyle = isPlayer ? '#2e7d32' : '#b71c1c';
                    ctx.fillRect(-tank.w / 2, -tank.h / 2, 5, tank.h);
                    ctx.fillRect(tank.w / 2 - 5, -tank.h / 2, 5, tank.h);

                    // 炮塔
                    ctx.fillStyle = isPlayer ? '#66bb6a' : '#ef5350';
                    const turretSize = tank.w * 0.55;
                    ctx.fillRect(-turretSize / 2, -turretSize / 2, turretSize, turretSize);

                    // 炮管
                    ctx.fillStyle = isPlayer ? '#388e3c' : '#c62828';
                    const barrelLen = tank.w * 0.6;
                    const barrelW = 5;
                    switch (tank.dir) {
                        case 'up':    ctx.fillRect(-barrelW / 2, -tank.h / 2 - barrelLen / 2, barrelW, barrelLen); break;
                        case 'down':  ctx.fillRect(-barrelW / 2, tank.h / 2 - barrelLen / 2, barrelW, barrelLen); break;
                        case 'left':  ctx.fillRect(-tank.w / 2 - barrelLen / 2, -barrelW / 2, barrelLen, barrelW); break;
                        case 'right': ctx.fillRect(tank.w / 2 - barrelLen / 2, -barrelW / 2, barrelLen, barrelW); break;
                    }

                    ctx.restore();
                } catch (e) {
                    console.error('[drawTank]', e);
                }
            }

            drawTank(player, '#43a047', true);
            for (const enemy of enemies) {
                drawTank(enemy, '#e53935', false);
            }

            // 绘制子弹
            for (const bullet of bullets) {
                try {
                    if (!bullet.alive) continue;
                    ctx.fillStyle = bullet.isEnemy ? '#ff7043' : '#4fc3f7';
                    ctx.shadowColor = bullet.isEnemy ? '#ff7043' : '#4fc3f7';
                    ctx.shadowBlur = 8;
                    ctx.beginPath();
                    ctx.arc(bullet.x + bullet.w / 2, bullet.y + bullet.h / 2, bullet.w / 2 + 1, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.shadowBlur = 0;
                } catch (e) { /* skip bullet */ }
            }

            // 绘制粒子
            for (const p of particles) {
                try {
                    const alpha = p.life / p.maxLife;
                    ctx.globalAlpha = alpha;
                    ctx.fillStyle = p.color;
                    ctx.fillRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);
                } catch (e) { /* skip particle */ }
            }
            ctx.globalAlpha = 1;

            // 绘制血包
            const now = Date.now();
            for (const hp of healthPacks) {
                try {
                    if (!hp.alive) continue;
                    const age = now - hp.createdAt;
                    // 最后 5 秒闪烁
                    if (age > 25000 && Math.floor(age / 200) % 2 === 0) continue;
                    // 绿色底板
                    ctx.fillStyle = '#2e7d32';
                    ctx.shadowColor = '#4caf50';
                    ctx.shadowBlur = 10;
                    ctx.fillRect(hp.x, hp.y, hp.w, hp.h);
                    // 白色十字
                    ctx.shadowBlur = 0;
                    ctx.fillStyle = '#ffffff';
                    ctx.fillRect(hp.x + 8, hp.y + 2, 4, hp.h - 4);
                    ctx.fillRect(hp.x + 2, hp.y + 8, hp.w - 4, 4);
                } catch (e) { /* skip health pack */ }
            }
            ctx.shadowBlur = 0;

            // 绘制速度道具(蓝色闪电)
            for (const pu of powerups) {
                try {
                    if (!pu.alive) continue;
                    // 蓝色底板
                    ctx.fillStyle = '#1565c0';
                    ctx.shadowColor = '#42a5f5';
                    ctx.shadowBlur = 12;
                    ctx.fillRect(pu.x, pu.y, pu.w, pu.h);
                    // 白色闪电 ⚡
                    ctx.shadowBlur = 0;
                    ctx.fillStyle = '#ffffff';
                    ctx.font = 'bold 16px sans-serif';
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    ctx.fillText('⚡', pu.x + pu.w / 2, pu.y + pu.h / 2 + 1);
                } catch (e) { /* skip powerup */ }
            }
            ctx.shadowBlur = 0;

            // 加速状态指示
            if (player && player.alive && player.speedBoostUntil > 0) {
                ctx.fillStyle = '#42a5f5';
                ctx.font = 'bold 14px sans-serif';
                ctx.textAlign = 'left';
                ctx.textBaseline = 'top';
                ctx.fillText('⚡ 加速中', 10, 10);
            }

            // 暂停提示
            if (game.paused && !game.gameOver) {
                try {
                    ctx.fillStyle = 'rgba(0,0,0,0.5)';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.fillStyle = 'white';
                    ctx.font = 'bold 48px sans-serif';
                    ctx.textAlign = 'center';
                    ctx.fillText('⏸ 暂停中', canvas.width / 2, canvas.height / 2);
                    ctx.font = '20px sans-serif';
                    ctx.fillText('按 P 继续', canvas.width / 2, canvas.height / 2 + 50);
                } catch (e) { /* skip pause overlay */ }
            }

        } catch (e) {
            console.error('[draw]', e);
        }
    }

    // ---------- 游戏主循环 ----------
    function gameLoop() {
        try {
            if (!game) return;
            update();
            draw();
            if (!game.gameOver) {
                requestAnimationFrame(gameLoop);
            }
        } catch (e) {
            console.error('[gameLoop]', e);
            // 尝试恢复:继续循环
            try { requestAnimationFrame(gameLoop); } catch (err) {
                console.error('[gameLoop recovery failed]', err);
            }
        }
    }

    // ---------- 启动 ----------
    function start() {
        try {
            if (initGame()) {
                setupInput();
                requestAnimationFrame(gameLoop);
                console.log('🎮 坦克大战启动成功!');
            } else {
                console.error('❌ 游戏初始化失败');
            }
        } catch (e) {
            console.error('[start]', e);
            if (errorBanner) {
                errorBanner.textContent = '❌ 游戏启动失败: ' + e.message;
                errorBanner.classList.add('show');
            }
        }
    }

    // 页面加载完成后启动
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', start);
    } else {
        start();
    }

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

更多推荐