吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 11404|回复: 187
收起左侧

[其他原创] 坦克大战-优化版,支持手机端和电脑端操作!

    [复制链接]
Summer000 发表于 2026-7-3 15:06
本帖最后由 Summer000 于 2026-7-4 21:59 编辑

坦克大战-优化版已发布,新增手机端和各类浏览器兼容;
方向键 / WASD 控制坦克移动
空格键 射击
P 暂停 / 继续
游戏内容
3 个关卡,每关 20 个敌人,难度递增
3 种敌人类型:普通(红色)、快速(粉色)、装甲(蓝色,需 3 发子弹击毁)
可破坏的砖墙、不可破坏的钢墙、水域障碍、树林掩护
保护底部基地(金色方块),被击毁则游戏结束
4 种道具掉落:护盾 (S)、炸弹 (B)、生命 (+)、加速星 (*)
3 条生命,出生自带短暂无敌
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>坦克大战</title>
<style>
/* Polyfill for CSS custom properties (IE fallback) */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
  width: 100%; height: 100%;
  overflow: hidden;
  background: #000;
  font-family: 'Courier New', monospace;
  color: #fff;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;
}
body {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
}
#gameWrapper {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  gap: 16px;
}
#gameContainer {
  position: relative;
  border: 3px solid #666;
  background: #000;
  line-height: 0;
}
canvas {
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  -ms-interpolation-mode: nearest-neighbor;
  max-width: 100%;
  height: auto;
}
/* Mobile responsive */
[url=home.php?mod=space&uid=945662]@media[/url] screen and (max-width: 600px), screen and (max-height: 600px) {
  body {
    -webkit-box-orient: vertical;
    -ms-flex-direction: column;
    flex-direction: column;
    -webkit-box-pack: start;
    -ms-flex-pack: start;
    justify-content: flex-start;
    padding: 4px;
    overflow-y: auto;
  }
  #gameWrapper {
    -webkit-box-orient: vertical;
    -ms-flex-direction: column;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    max-width: 100vw;
  }
  #gameContainer {
    width: 100%;
    max-width: calc(100vw - 8px);
    border-width: 2px;
  }
  canvas {
    width: 100% !important;
    height: auto !important;
    max-height: 50vh;
  }
  #sidebar {
    -webkit-box-orient: horizontal;
    -ms-flex-direction: row;
    flex-direction: row;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    -webkit-box-pack: center;
    -ms-flex-pack: center;
    justify-content: center;
    gap: 6px;
    min-width: 0;
    width: 100%;
  }
  .info-panel {
    padding: 6px 10px;
    min-width: 80px;
  }
  .info-panel h3 { font-size: 10px; margin-bottom: 4px; }
  .info-row { font-size: 11px; }
}
#sidebar {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -ms-flex-direction: column;
  flex-direction: column;
  gap: 12px;
  min-width: 120px;
}
.info-panel {
  background: #1a1a1a;
  border: 2px solid #444;
  padding: 10px;
}
.info-panel h3 {
  color: #ff0;
  font-size: 12px;
  margin-bottom: 8px;
  text-align: center;
  letter-spacing: 2px;
}
.info-row {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
  -ms-flex-pack: justify;
  justify-content: space-between;
  font-size: 13px;
  margin: 4px 0;
}
.info-row .label { color: #aaa; }
.info-row .value { color: #0f0; font-weight: bold; }
#enemyIcons {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-wrap: wrap;
  flex-wrap: wrap;
  gap: 3px;
  margin-top: 6px;
}
.enemy-icon {
  width: 12px; height: 12px;
  background: #e44;
}
.overlay {
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -ms-flex-direction: column;
  flex-direction: column;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  background: rgba(0,0,0,0.88);
  z-index: 10;
}
.overlay.hidden { display: none; }
.overlay h1 {
  font-size: 32px;
  color: #ff0;
  margin-bottom: 16px;
  text-shadow: 2px 2px #a00;
  letter-spacing: 4px;
}
.overlay h2 {
  font-size: 20px;
  color: #f44;
  margin-bottom: 12px;
}
.overlay p {
  font-size: 13px;
  color: #ccc;
  margin: 4px 0;
  text-align: center;
}
.overlay .highlight { color: #0f0; }
.btn {
  margin-top: 18px;
  padding: 10px 28px;
  font-family: 'Courier New', monospace;
  font-size: 15px;
  font-weight: bold;
  color: #000;
  background: #ff0;
  border: none;
  cursor: pointer;
  letter-spacing: 2px;
}
.btn:hover { background: #fc0; }
.btn:active { background: #fa0; }
/* Mobile Controls */
#mobileControls {
  display: none;
  position: fixed;
  bottom: 0; left: 0; right: 0;
  padding: 10px 16px 20px;
  z-index: 100;
  background: rgba(0,0,0,0.7);
}
#mobileControls.active { display: -webkit-box; display: -ms-flexbox; display: flex; }
#mobileControls {
  -webkit-box-pack: justify;
  -ms-flex-pack: justify;
  justify-content: space-between;
  -webkit-box-align: end;
  -ms-flex-align: end;
  align-items: flex-end;
}
.dpad {
  position: relative;
  width: 140px; height: 140px;
}
.dpad-btn {
  position: absolute;
  width: 46px; height: 46px;
  background: rgba(255,255,255,0.15);
  border: 2px solid rgba(255,255,255,0.3);
  border-radius: 8px;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  color: rgba(255,255,255,0.7);
  font-size: 20px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.dpad-btn:active, .dpad-btn.pressed {
  background: rgba(255,255,0,0.4);
  border-color: #ff0;
}
.dpad-up { top: 0; left: 47px; }
.dpad-down { bottom: 0; left: 47px; }
.dpad-left { top: 47px; left: 0; }
.dpad-right { top: 47px; right: 0; }
.fire-btn {
  width: 72px; height: 72px;
  border-radius: 50%;
  background: rgba(255,50,50,0.3);
  border: 3px solid rgba(255,50,50,0.6);
  color: #f66;
  font-size: 13px;
  font-weight: bold;
  font-family: 'Courier New', monospace;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
  -ms-flex-align: center;
  align-items: center;
  -webkit-box-pack: center;
  -ms-flex-pack: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fire-btn:active, .fire-btn.pressed {
  background: rgba(255,50,50,0.6);
  border-color: #f00;
}
#pauseBtn {
  position: absolute;
  top: 8px; right: 8px;
  width: 36px; height: 36px;
  background: rgba(255,255,255,0.1);
  border: 2px solid rgba(255,255,255,0.3);
  border-radius: 6px;
  color: #fff;
  font-size: 16px;
  font-family: 'Courier New', monospace;
  cursor: pointer;
  z-index: 20;
  display: none;
  -webkit-tap-highlight-color: transparent;
}
#pauseBtn.active { display: block; }
@media (max-width: 700px) {
  #gameWrapper { -webkit-box-orient: vertical; -ms-flex-direction: column; flex-direction: column; }
  #sidebar { -webkit-box-orient: horizontal; -ms-flex-direction: row; flex-direction: row; gap: 8px; }
  .info-panel { padding: 6px 10px; }
}
@media (max-height: 600px) {
  #gameWrapper { -webkit-box-orient: vertical; -ms-flex-direction: column; flex-direction: column; }
}
.mobile-hint { display: none;line-height: 2.2 }
@media (pointer: coarse) {
  .mobile-hint { display: block;margin: 6px 0 }
  .desktop-hint { display: none;line-height: 2.2 }
}
</style>
</head>
<body tabindex="0">
<div id="gameWrapper">
  <div id="gameContainer">
    <canvas id="gameCanvas" width="520" height="520"></canvas>
    <div id="startScreen" class="overlay">
      <h1>坦 克 大 战</h1>
      <p class="desktop-hint">方向键 / WASD 移动</p>
      <p class="desktop-hint">空格键 射击  |  P 暂停</p>
      <p class="mobile-hint">左侧方向键移动,右侧按钮射击</p>
      <p>保护基地,消灭所有敌方坦克!</p>
      <button class="btn" id="startBtn">开 始 游 戏</button>
    </div>
    <div id="gameOverScreen" class="overlay hidden">
      <h2>游 戏 结 束</h2>
      <p>最终得分: <span class="highlight" id="finalScore">0</span></p>
      <p>击杀数: <span class="highlight" id="finalKills">0</span></p>
      <p>到达关卡: <span class="highlight" id="finalLevel">1</span></p>
      <button class="btn" id="restartBtn">再 来 一 局</button>
    </div>
    <div id="winScreen" class="overlay hidden">
      <h1>胜 利 !</h1>
      <p>恭喜通关!</p>
      <p>总得分: <span class="highlight" id="winScore">0</span></p>
      <p>总击杀: <span class="highlight" id="winKills">0</span></p>
      <button class="btn" id="winRestartBtn">再 来 一 局</button>
    </div>
    <div id="levelScreen" class="overlay hidden">
      <h1 id="levelTitle">第 1 关</h1>
      <p>准备战斗!</p>
    </div>
    <button id="pauseBtn">II</button>
  </div>
  <div id="sidebar">
    <div class="info-panel">
      <h3>信 息</h3>
      <div class="info-row"><span class="label">关卡</span><span class="value" id="levelNum">1</span></div>
      <div class="info-row"><span class="label">得分</span><span class="value" id="scoreNum">0</span></div>
      <div class="info-row"><span class="label">生命</span><span class="value" id="livesNum">3</span></div>
      <div class="info-row"><span class="label">击杀</span><span class="value" id="killsNum">0</span></div>
    </div>
    <div class="info-panel">
      <h3>敌 军</h3>
      <div id="enemyIcons"></div>
    </div>
  </div>
</div>
<div id="mobileControls">
  <div class="dpad">
    <div class="dpad-btn dpad-up" data-dir="up">▲</div>
    <div class="dpad-btn dpad-down" data-dir="down">▼</div>
    <div class="dpad-btn dpad-left" data-dir="left">&#9664;</div>
    <div class="dpad-btn dpad-right" data-dir="right">&#9654;</div>
  </div>
  <div class="fire-btn" id="fireBtn">FIRE</div>
</div>
<script>
// ES5 Polyfills
(function() {
  // requestAnimationFrame polyfill
  var lastTime = 0;
  var vendors = ['ms', 'moz', 'webkit', 'o'];
  for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||
                                   window[vendors[x] + 'CancelRequestAnimationFrame'];
  }
  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function(callback) {
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16 - (currTime - lastTime));
      var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };
  }
  if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function(id) { clearTimeout(id); };
  }
  // Array.prototype.indexOf polyfill
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(item, start) {
      start = start || 0;
      for (var i = start; i < this.length; i++) {
        if (this[i] === item) return i;
      }
      return -1;
    };
  }
  // Array.prototype.forEach polyfill for NodeList
  if (typeof NodeList !== 'undefined' && !NodeList.prototype.forEach) {
    NodeList.prototype.forEach = Array.prototype.forEach;
  }
})();

// ===== CONSTANTS =====
var TILE = 20;
var COLS = 26;
var ROWS = 26;
var CANVAS_W = COLS * TILE;
var CANVAS_H = ROWS * TILE;
var TANK_SIZE = TILE * 2 - 2;
var BULLET_SIZE = 6;
var DIR_UP = 0;
var DIR_RIGHT = 1;
var DIR_DOWN = 2;
var DIR_LEFT = 3;
var DX = [0, 1, 0, -1];
var DY = [-1, 0, 1, 0];
var TILE_EMPTY = 0;
var TILE_BRICK = 1;
var TILE_STEEL = 2;
var TILE_WATER = 3;
var TILE_TREE = 4;
var TILE_BASE = 5;

// ===== LEVELS =====
var LEVELS = [
  [
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011002200110011001100",
    "00110011000000110011001100",
    "00110011000000110011001100",
    "00110011001100110011001100",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "11001100000000001100110000",
    "11001100000000001100110000",
    "00001100110011001100000000",
    "00001100110011001100000000",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011000000000011001100",
    "00110011000000000011001100",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "00000000001551000000000000",
    "00000000001551000000000000"
  ],
  [
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00110022001100110022001100",
    "00110022001100110022001100",
    "00110000001100110000001100",
    "00110000001100110000001100",
    "00000011220000221100000000",
    "00000011220000221100000000",
    "00220000001100110000002200",
    "00220000001100110000002200",
    "00001100110000110011000000",
    "00001100110000110011000000",
    "11000022000000002200001100",
    "11000022000000002200001100",
    "00001100001100110011000000",
    "00001100001100110011000000",
    "00220011000000001100220000",
    "00220011000000001100220000",
    "00000000001100110000000000",
    "00000000001100110000000000",
    "00110011000000001100110000",
    "00110011000000001100110000",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "00000000001551000000000000",
    "00000000001551000000000000"
  ],
  [
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00220011002200110022001100",
    "00220011002200110022001100",
    "00000011000000110000001100",
    "00000011000000110000001100",
    "11001100110000110011001100",
    "11001100110000110011001100",
    "00000000220000220000000000",
    "00000000220000220000000000",
    "00110022001100110022001100",
    "00110022001100110022001100",
    "00110000000000000000001100",
    "00110000000000000000001100",
    "22000011220000221100002200",
    "22000011220000221100002200",
    "00001100001100110011000000",
    "00001100001100110011000000",
    "00110011000000001100110000",
    "00110011000000001100110000",
    "00000000001100110000000000",
    "00000000001100110000000000",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "00000000001551000000000000",
    "00000000001551000000000000"
  ]
];

// ===== GAME STATE =====
var canvas = document.getElementById('gameCanvas');
var ctx = canvas.getContext('2d');
var map = [];
var player = null;
var enemies = [];
var bullets = [];
var explosions = [];
var powerups = [];
var gameState = 'start';
var currentLevel = 0;
var score = 0;
var lives = 3;
var kills = 0;
var enemiesLeft = 0;
var enemySpawnTimer = 0;
var enemySpawnCount = 0;
var maxEnemiesOnField = 4;
var totalEnemiesPerLevel = 20;
var keys = {};
var lastTime = 0;
var baseAlive = true;
var playerShieldTimer = 0;
var playerSpeedBoost = 0;
var levelTimer = 0;
var isPaused = false;
var isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0);

// ===== TANK CONSTRUCTOR =====
function Tank(x, y, dir, type) {
  this.x = x;
  this.y = y;
  this.dir = dir;
  this.type = type || 'player';
  this.speed = type === 'fast' ? 80 : (type === 'player' ? 90 : 50);
  this.hp = type === 'armor' ? 3 : 1;
  this.maxHp = this.hp;
  this.cooldown = 0;
  this.fireRate = type === 'player' ? 0.35 : 0.8;
  this.alive = true;
  this.moveTimer = 0;
  this.aiChangeDir = 0;
  this.animFrame = 0;
  this.flashTimer = 0;
}

// ===== BULLET CONSTRUCTOR =====
function Bullet(x, y, dir, owner) {
  this.x = x;
  this.y = y;
  this.dir = dir;
  this.owner = owner;
  this.speed = 220;
  this.alive = true;
}

// ===== EXPLOSION CONSTRUCTOR =====
function Explosion(x, y, big) {
  this.x = x;
  this.y = y;
  this.big = big || false;
  this.timer = 0;
  this.duration = big ? 0.5 : 0.3;
  this.alive = true;
}

// ===== POWERUP CONSTRUCTOR =====
function Powerup(x, y, type) {
  this.x = x;
  this.y = y;
  this.type = type;
  this.timer = 0;
  this.duration = 15;
  this.alive = true;
}

// ===== INIT LEVEL =====
function initLevel(levelIdx) {
  map = [];
  var levelData = LEVELS[levelIdx % LEVELS.length];
  for (var r = 0; r < ROWS; r++) {
    var row = [];
    for (var c = 0; c < COLS; c++) {
      row.push(parseInt(levelData[r][c], 10));
    }
    map.push(row);
  }
  enemies = [];
  bullets = [];
  explosions = [];
  powerups = [];
  baseAlive = true;
  enemySpawnTimer = 0;
  enemySpawnCount = 0;
  enemiesLeft = totalEnemiesPerLevel;
  playerShieldTimer = 3;
  playerSpeedBoost = 0;
  player = new Tank(4 * TILE, 24 * TILE, DIR_UP, 'player');
  updateUI();
  updateEnemyIcons();
}

// ===== UI UPDATES =====
function updateUI() {
  document.getElementById('levelNum').innerHTML = (currentLevel + 1);
  document.getElementById('scoreNum').innerHTML = score;
  document.getElementById('livesNum').innerHTML = lives;
  document.getElementById('killsNum').innerHTML = kills;
}

function updateEnemyIcons() {
  var container = document.getElementById('enemyIcons');
  var html = '';
  var remaining = enemiesLeft + enemies.length;
  for (var i = 0; i < remaining; i++) {
    html += '<div class="enemy-icon"></div>';
  }
  container.innerHTML = html;
}

// ===== COLLISION HELPERS =====
function rectOverlap(ax, ay, aw, ah, bx, by, bw, bh) {
  return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;
}

function getTilesInRect(x, y, w, h) {
  var tiles = [];
  var c1 = Math.floor(x / TILE);
  var r1 = Math.floor(y / TILE);
  var c2 = Math.floor((x + w - 1) / TILE);
  var r2 = Math.floor((y + h - 1) / TILE);
  for (var r = r1; r <= r2; r++) {
    for (var c = c1; c <= c2; c++) {
      if (r >= 0 && r < ROWS && c >= 0 && c < COLS) {
        tiles.push({r: r, c: c, tile: map[r][c]});
      }
    }
  }
  return tiles;
}

function canMoveTo(tank, nx, ny) {
  if (nx < 0 || ny < 0 || nx + TANK_SIZE > CANVAS_W || ny + TANK_SIZE > CANVAS_H) return false;
  var tiles = getTilesInRect(nx, ny, TANK_SIZE, TANK_SIZE);
  for (var i = 0; i < tiles.length; i++) {
    var t = tiles[i].tile;
    if (t === TILE_BRICK || t === TILE_STEEL || t === TILE_WATER || t === TILE_BASE) return false;
  }
  // Check collision with other tanks
  var allTanks = [];
  if (tank.type === 'player') {
    for (var j = 0; j < enemies.length; j++) {
      if (enemies[j].alive) allTanks.push(enemies[j]);
    }
  } else {
    if (player && player.alive) allTanks.push(player);
    for (var k = 0; k < enemies.length; k++) {
      if (enemies[k] !== tank && enemies[k].alive) allTanks.push(enemies[k]);
    }
  }
  for (var m = 0; m < allTanks.length; m++) {
    if (rectOverlap(nx, ny, TANK_SIZE, TANK_SIZE, allTanks[m].x, allTanks[m].y, TANK_SIZE, TANK_SIZE)) return false;
  }
  return true;
}

// ===== TANK MOVEMENT =====
function moveTank(tank, dt) {
  var spd = tank.speed;
  if (tank.type === 'player' && playerSpeedBoost > 0) spd *= 1.5;
  var dist = spd * dt;
  var nx = tank.x + DX[tank.dir] * dist;
  var ny = tank.y + DY[tank.dir] * dist;
  // Snap to grid on perpendicular axis
  if (tank.dir === DIR_UP || tank.dir === DIR_DOWN) {
    var snapX = Math.round(tank.x / (TILE / 2)) * (TILE / 2);
    if (Math.abs(snapX - tank.x) < 2) nx = snapX;
    else nx = tank.x;
    ny = tank.y + DY[tank.dir] * dist;
  } else {
    var snapY = Math.round(tank.y / (TILE / 2)) * (TILE / 2);
    if (Math.abs(snapY - tank.y) < 2) ny = snapY;
    else ny = tank.y;
    nx = tank.x + DX[tank.dir] * dist;
  }
  if (canMoveTo(tank, nx, ny)) {
    tank.x = nx;
    tank.y = ny;
    tank.animFrame += dt * 8;
    return true;
  }
  return false;
}

// ===== FIRE BULLET =====
function fireBullet(tank) {
  if (tank.cooldown > 0) return;
  // Limit active bullets
  var count = 0;
  for (var i = 0; i < bullets.length; i++) {
    if (bullets[i].alive && bullets[i].owner === tank) count++;
  }
  var maxBullets = tank.type === 'player' ? 2 : 1;
  if (count >= maxBullets) return;
  var bx = tank.x + TANK_SIZE / 2 - BULLET_SIZE / 2 + DX[tank.dir] * (TANK_SIZE / 2);
  var by = tank.y + TANK_SIZE / 2 - BULLET_SIZE / 2 + DY[tank.dir] * (TANK_SIZE / 2);
  bullets.push(new Bullet(bx, by, tank.dir, tank));
  tank.cooldown = tank.fireRate;
}

// ===== UPDATE PLAYER =====
function updatePlayer(dt) {
  if (!player || !player.alive) return;
  player.cooldown = Math.max(0, player.cooldown - dt);
  if (playerShieldTimer > 0) playerShieldTimer -= dt;
  if (playerSpeedBoost > 0) playerSpeedBoost -= dt;
  var moved = false;
  if (keys['ArrowUp'] || keys['w'] || keys['W'] || keys['KeyW']) {
    player.dir = DIR_UP; moved = moveTank(player, dt);
  } else if (keys['ArrowDown'] || keys['s'] || keys['S'] || keys['KeyS']) {
    player.dir = DIR_DOWN; moved = moveTank(player, dt);
  } else if (keys['ArrowLeft'] || keys['a'] || keys['A'] || keys['KeyA']) {
    player.dir = DIR_LEFT; moved = moveTank(player, dt);
  } else if (keys['ArrowRight'] || keys['d'] || keys['D'] || keys['KeyD']) {
    player.dir = DIR_RIGHT; moved = moveTank(player, dt);
  }
  if (keys[' '] || keys['Space']) fireBullet(player);
}

// ===== UPDATE ENEMIES AI =====
function updateEnemies(dt) {
  // Remove dead enemies from array
  var aliveEnemies = [];
  for (var i = 0; i < enemies.length; i++) {
    if (enemies[i].alive) aliveEnemies.push(enemies[i]);
  }
  enemies = aliveEnemies;

  for (var i = 0; i < enemies.length; i++) {
    var e = enemies[i];
    if (!e.alive) continue;
    e.cooldown = Math.max(0, e.cooldown - dt);
    e.aiChangeDir -= dt;
    e.flashTimer = Math.max(0, e.flashTimer - dt);
    var moved = moveTank(e, dt);
    if (!moved || e.aiChangeDir <= 0) {
      // Change direction
      var dirs = [DIR_UP, DIR_DOWN, DIR_LEFT, DIR_RIGHT];
      // Bias toward player/base
      if (player && player.alive && Math.random() < 0.3) {
        var dx = player.x - e.x;
        var dy = player.y - e.y;
        if (Math.abs(dx) > Math.abs(dy)) {
          e.dir = dx > 0 ? DIR_RIGHT : DIR_LEFT;
        } else {
          e.dir = dy > 0 ? DIR_DOWN : DIR_UP;
        }
      } else if (Math.random() < 0.2) {
        e.dir = DIR_DOWN;
      } else {
        e.dir = dirs[Math.floor(Math.random() * 4)];
      }
      e.aiChangeDir = 1 + Math.random() * 2;
    }
    // Fire
    if (Math.random() < dt * 1.5) fireBullet(e);
  }
}

// ===== SPAWN ENEMY =====
function spawnEnemy() {
  if (enemySpawnCount >= totalEnemiesPerLevel) return;
  if (enemies.length >= maxEnemiesOnField) return;
  enemySpawnTimer -= 1 / 60;
  if (enemySpawnTimer > 0) return;
  var spawnPoints = [[0, 0], [12 * TILE, 0], [24 * TILE, 0]];
  var sp = null;
  for (var i = 0; i < spawnPoints.length; i++) {
    var sx = spawnPoints[i][0];
    var sy = spawnPoints[i][1];
    var blocked = false;
    if (player && player.alive && rectOverlap(sx, sy, TANK_SIZE, TANK_SIZE, player.x, player.y, TANK_SIZE, TANK_SIZE)) {
      blocked = true;
    }
    for (var j = 0; j < enemies.length; j++) {
      if (enemies[j].alive && rectOverlap(sx, sy, TANK_SIZE, TANK_SIZE, enemies[j].x, enemies[j].y, TANK_SIZE, TANK_SIZE)) {
        blocked = true;
        break;
      }
    }
    if (!blocked) { sp = spawnPoints[i]; break; }
  }
  if (!sp) { enemySpawnTimer = 1; return; }
  var types = ['normal', 'normal', 'fast', 'normal', 'armor'];
  var type = types[Math.floor(Math.random() * types.length)];
  if (currentLevel >= 1 && Math.random() < 0.3) type = 'armor';
  if (currentLevel >= 2 && Math.random() < 0.3) type = 'fast';
  var enemy = new Tank(sp[0], sp[1], DIR_DOWN, type);
  enemies.push(enemy);
  enemySpawnCount++;
  enemySpawnTimer = 2 + Math.random();
  updateEnemyIcons();
}

// ===== UPDATE BULLETS =====
function updateBullets(dt) {
  for (var i = 0; i < bullets.length; i++) {
    var b = bullets[i];
    if (!b.alive) continue;
    b.x += DX[b.dir] * b.speed * dt;
    b.y += DY[b.dir] * b.speed * dt;
    // Out of bounds
    if (b.x < 0 || b.y < 0 || b.x > CANVAS_W || b.y > CANVAS_H) {
      b.alive = false;
      explosions.push(new Explosion(b.x - 8, b.y - 8, false));
      continue;
    }
    // Tile collision
    var tiles = getTilesInRect(b.x, b.y, BULLET_SIZE, BULLET_SIZE);
    var hitTile = false;
    for (var t = 0; t < tiles.length; t++) {
      var tile = tiles[t];
      if (tile.tile === TILE_BRICK) {
        map[tile.r][tile.c] = TILE_EMPTY;
        hitTile = true;
      } else if (tile.tile === TILE_STEEL) {
        hitTile = true;
      } else if (tile.tile === TILE_BASE) {
        map[tile.r][tile.c] = TILE_EMPTY;
        baseAlive = false;
        hitTile = true;
        explosions.push(new Explosion(tile.c * TILE - 10, tile.r * TILE - 10, true));
      }
    }
    if (hitTile) {
      b.alive = false;
      explosions.push(new Explosion(b.x - 4, b.y - 4, false));
      continue;
    }
    // Tank collision
    if (b.owner.type === 'player' || b.owner === player) {
      // Hit enemy
      for (var j = 0; j < enemies.length; j++) {
        var e = enemies[j];
        if (!e.alive) continue;
        if (rectOverlap(b.x, b.y, BULLET_SIZE, BULLET_SIZE, e.x, e.y, TANK_SIZE, TANK_SIZE)) {
          b.alive = false;
          e.hp--;
          e.flashTimer = 0.1;
          if (e.hp <= 0) {
            e.alive = false;
            explosions.push(new Explosion(e.x, e.y, true));
            score += e.type === 'armor' ? 300 : (e.type === 'fast' ? 200 : 100);
            kills++;
            updateUI();
            updateEnemyIcons();
            // Drop powerup
            if (Math.random() < 0.2) spawnPowerup(e.x, e.y);
          }
          break;
        }
      }
    } else {
      // Hit player
      if (player && player.alive) {
        if (rectOverlap(b.x, b.y, BULLET_SIZE, BULLET_SIZE, player.x, player.y, TANK_SIZE, TANK_SIZE)) {
          b.alive = false;
          if (playerShieldTimer <= 0) {
            player.alive = false;
            explosions.push(new Explosion(player.x, player.y, true));
            lives--;
            updateUI();
            if (lives > 0) {
              setTimeout(function() { respawnPlayer(); }, 1500);
            }
          }
        }
      }
    }
    // Bullet vs bullet
    for (var k = 0; k < bullets.length; k++) {
      var b2 = bullets[k];
      if (b2 === b || !b2.alive || !b.alive) continue;
      if (rectOverlap(b.x, b.y, BULLET_SIZE, BULLET_SIZE, b2.x, b2.y, BULLET_SIZE, BULLET_SIZE)) {
        b.alive = false;
        b2.alive = false;
        break;
      }
    }
  }
  // Cleanup
  var newBullets = [];
  for (var n = 0; n < bullets.length; n++) {
    if (bullets[n].alive) newBullets.push(bullets[n]);
  }
  bullets = newBullets;
}

// ===== RESPAWN PLAYER =====
function respawnPlayer() {
  player = new Tank(4 * TILE, 24 * TILE, DIR_UP, 'player');
  playerShieldTimer = 3;
  playerSpeedBoost = 0;
}

// ===== POWERUPS =====
function spawnPowerup(x, y) {
  var types = ['shield', 'bomb', 'life', 'star'];
  var type = types[Math.floor(Math.random() * types.length)];
  var px = Math.max(0, Math.min(x, CANVAS_W - TILE * 2));
  var py = Math.max(0, Math.min(y, CANVAS_H - TILE * 2));
  powerups.push(new Powerup(px, py, type));
}

function updatePowerups(dt) {
  for (var i = powerups.length - 1; i >= 0; i--) {
    var p = powerups[i];
    p.timer += dt;
    if (p.timer > p.duration) {
      powerups.splice(i, 1);
      continue;
    }
    if (player && player.alive) {
      if (rectOverlap(player.x, player.y, TANK_SIZE, TANK_SIZE, p.x, p.y, TILE * 2, TILE * 2)) {
        applyPowerup(p.type);
        powerups.splice(i, 1);
      }
    }
  }
}

function applyPowerup(type) {
  if (type === 'shield') {
    playerShieldTimer = 10;
  } else if (type === 'bomb') {
    for (var i = 0; i < enemies.length; i++) {
      if (enemies[i].alive) {
        enemies[i].alive = false;
        explosions.push(new Explosion(enemies[i].x, enemies[i].y, true));
        score += 50;
        kills++;
      }
    }
    updateUI();
    updateEnemyIcons();
  } else if (type === 'life') {
    lives++;
    updateUI();
  } else if (type === 'star') {
    playerSpeedBoost = 15;
  }
}

// ===== EXPLOSIONS =====
function updateExplosions(dt) {
  for (var i = explosions.length - 1; i >= 0; i--) {
    explosions[i].timer += dt;
    if (explosions[i].timer >= explosions[i].duration) {
      explosions.splice(i, 1);
    }
  }
}

// ===== CHECK WIN/LOSE =====
function checkWinLose() {
  if (!baseAlive) {
    gameState = 'gameover';
    showGameOver();
    return;
  }
  if (lives <= 0 && (!player || !player.alive)) {
    gameState = 'gameover';
    showGameOver();
    return;
  }
  if (enemySpawnCount >= totalEnemiesPerLevel && enemies.length === 0) {
    // Level complete
    currentLevel++;
    if (currentLevel >= LEVELS.length) {
      gameState = 'win';
      showWin();
    } else {
      gameState = 'levelIntro';
      levelTimer = 2;
      showLevelScreen();
    }
  }
}

// ===== DRAWING =====
function drawTile(c, r) {
  var x = c * TILE;
  var y = r * TILE;
  var tile = map[r][c];
  if (tile === TILE_BRICK) {
    ctx.fillStyle = '#b55a30';
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = '#8b4020';
    for (var by = 0; by < TILE; by += 5) {
      ctx.fillRect(x, y + by, TILE, 1);
    }
    ctx.fillRect(x + TILE / 2, y, 1, TILE);
  } else if (tile === TILE_STEEL) {
    ctx.fillStyle = '#ccc';
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = '#999';
    ctx.fillRect(x + 1, y + 1, TILE - 2, TILE - 2);
    ctx.fillStyle = '#fff';
    ctx.fillRect(x + 2, y + 2, 4, 4);
  } else if (tile === TILE_WATER) {
    ctx.fillStyle = '#2244aa';
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = '#4466cc';
    var waveOff = (Date.now() / 300) % TILE;
    for (var wy = 0; wy < TILE; wy += 6) {
      ctx.fillRect(x + ((wy + waveOff) % TILE), y + wy, 8, 2);
    }
  } else if (tile === TILE_BASE) {
    ctx.fillStyle = '#ffcc00';
    ctx.fillRect(x + 2, y + 2, TILE - 4, TILE - 4);
    ctx.fillStyle = '#cc9900';
    ctx.fillRect(x + 5, y + 5, TILE - 10, TILE - 10);
  }
}

function drawTrees() {
  for (var r = 0; r < ROWS; r++) {
    for (var c = 0; c < COLS; c++) {
      if (map[r][c] === TILE_TREE) {
        var x = c * TILE;
        var y = r * TILE;
        ctx.fillStyle = '#228822';
        ctx.fillRect(x, y, TILE, TILE);
        ctx.fillStyle = '#33aa33';
        ctx.fillRect(x + 3, y + 3, 6, 6);
        ctx.fillRect(x + 11, y + 11, 6, 6);
      }
    }
  }
}

function drawTank(tank) {
  if (!tank.alive) return;
  var x = tank.x;
  var y = tank.y;
  var s = TANK_SIZE;
  ctx.save();
  ctx.translate(x + s / 2, y + s / 2);
  ctx.rotate(tank.dir * Math.PI / 2);
  ctx.translate(-s / 2, -s / 2);
  var bodyColor, trackColor, turretColor;
  if (tank.type === 'player') {
    bodyColor = '#ffcc00'; trackColor = '#aa8800'; turretColor = '#ddaa00';
  } else if (tank.type === 'fast') {
    bodyColor = '#ee66aa'; trackColor = '#aa3377'; turretColor = '#cc4488';
  } else if (tank.type === 'armor') {
    bodyColor = '#66aaee'; trackColor = '#3377aa'; turretColor = '#4488cc';
    if (tank.hp === 2) bodyColor = '#4488cc';
    if (tank.hp === 1) bodyColor = '#2266aa';
  } else {
    bodyColor = '#ee4444'; trackColor = '#aa2222'; turretColor = '#cc3333';
  }
  if (tank.flashTimer > 0 && Math.floor(tank.flashTimer * 20) % 2 === 0) {
    bodyColor = '#fff';
  }
  // Tracks
  var trackOffset = Math.floor(tank.animFrame) % 2 === 0 ? 0 : 2;
  ctx.fillStyle = trackColor;
  ctx.fillRect(0, 0, 7, s);
  ctx.fillRect(s - 7, 0, 7, s);
  ctx.fillStyle = '#333';
  for (var ty = trackOffset; ty < s; ty += 6) {
    ctx.fillRect(1, ty, 5, 2);
    ctx.fillRect(s - 6, ty, 5, 2);
  }
  // Body
  ctx.fillStyle = bodyColor;
  ctx.fillRect(7, 3, s - 14, s - 6);
  // Turret
  ctx.fillStyle = turretColor;
  ctx.fillRect(12, 10, s - 24, s - 20);
  // Barrel
  ctx.fillStyle = turretColor;
  ctx.fillRect(s / 2 - 2, 0, 4, s / 2 - 2);
  ctx.restore();
  // Shield effect
  if (tank.type === 'player' && playerShieldTimer > 0) {
    ctx.save();
    ctx.strokeStyle = playerShieldTimer < 3 && Math.floor(playerShieldTimer * 4) % 2 === 0 ? 'transparent' : '#0ff';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(x + s / 2, y + s / 2, s / 2 + 3, 0, Math.PI * 2);
    ctx.stroke();
    ctx.restore();
  }
}

function drawBullet(b) {
  ctx.fillStyle = '#fff';
  ctx.fillRect(b.x, b.y, BULLET_SIZE, BULLET_SIZE);
  ctx.fillStyle = '#ff0';
  ctx.fillRect(b.x + 1, b.y + 1, BULLET_SIZE - 2, BULLET_SIZE - 2);
}

function drawExplosion(ex) {
  var progress = ex.timer / ex.duration;
  var maxR = ex.big ? 30 : 14;
  var r = maxR * (progress < 0.5 ? progress * 2 : 2 - progress * 2);
  ctx.save();
  ctx.globalAlpha = 1 - progress;
  ctx.fillStyle = '#ff6600';
  ctx.beginPath();
  ctx.arc(ex.x + (ex.big ? 20 : 8), ex.y + (ex.big ? 20 : 8), r, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = '#ffcc00';
  ctx.beginPath();
  ctx.arc(ex.x + (ex.big ? 20 : 8), ex.y + (ex.big ? 20 : 8), r * 0.6, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();
}

function drawPowerup(p) {
  if (Math.floor(p.timer * 3) % 2 === 0 && p.timer > p.duration - 3) return;
  var x = p.x;
  var y = p.y;
  var s = TILE * 2;
  ctx.fillStyle = 'rgba(255,0,0,0.3)';
  ctx.fillRect(x, y, s, s);
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 14px Courier New';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  var labels = {shield: 'S', bomb: 'B', life: '+', star: '*'};
  var colors = {shield: '#0ff', bomb: '#f00', life: '#0f0', star: '#ff0'};
  ctx.fillStyle = colors[p.type];
  ctx.fillText(labels[p.type], x + s / 2, y + s / 2);
}

function drawDebugKeys() {
  var pressed = [];
  for (var k in keys) {
    if (keys[k]) pressed.push(k);
  }
  ctx.save();
  ctx.fillStyle = '#0f0';
  ctx.font = '10px Courier New';
  ctx.textAlign = 'left';
  ctx.textBaseline = 'bottom';
  ctx.fillText('Keys: ' + pressed.join(','), 4, CANVAS_H - 4);
  ctx.restore();
}

// ===== MAIN DRAW =====
function draw() {
  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
  // Draw map (except trees)
  if (map.length > 0) {
    for (var r = 0; r < ROWS; r++) {
      for (var c = 0; c < COLS; c++) {
        if (map[r][c] !== TILE_EMPTY && map[r][c] !== TILE_TREE) {
          drawTile(c, r);
        }
      }
    }
  }
  // Draw powerups
  for (var pi = 0; pi < powerups.length; pi++) drawPowerup(powerups[pi]);
  // Draw tanks
  if (player) drawTank(player);
  for (var ei = 0; ei < enemies.length; ei++) drawTank(enemies[ei]);
  // Draw bullets
  for (var bi = 0; bi < bullets.length; bi++) drawBullet(bullets[bi]);
  // Draw trees (on top)
  if (map.length > 0) drawTrees();
  // Draw explosions
  for (var xi = 0; xi < explosions.length; xi++) drawExplosion(explosions[xi]);
  // Draw base destroyed indicator
  if (!baseAlive) {
    ctx.fillStyle = '#f00';
    ctx.font = 'bold 20px Courier New';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('BASE DESTROYED', CANVAS_W / 2, CANVAS_H / 2);
  }
  // Debug keys
  drawDebugKeys();
}

// ===== MAIN UPDATE =====
function update(dt) {
  if (gameState !== 'playing') return;
  if (isPaused) return;
  updatePlayer(dt);
  updateEnemies(dt);
  spawnEnemy();
  updateBullets(dt);
  updatePowerups(dt);
  updateExplosions(dt);
  checkWinLose();
}

// ===== GAME LOOP =====
function gameLoop(timestamp) {
  if (!lastTime) lastTime = timestamp;
  var dt = Math.min((timestamp - lastTime) / 1000, 0.05);
  lastTime = timestamp;
  if (gameState === 'levelIntro') {
    levelTimer -= dt;
    if (levelTimer <= 0) {
      hideLevelScreen();
      initLevel(currentLevel);
      gameState = 'playing';
    }
  }
  update(dt);
  draw();
  requestAnimationFrame(gameLoop);
}

// ===== SCREENS =====
function showGameOver() {
  document.getElementById('finalScore').innerHTML = score;
  document.getElementById('finalKills').innerHTML = kills;
  document.getElementById('finalLevel').innerHTML = currentLevel + 1;
  document.getElementById('gameOverScreen').className = 'overlay';
}

function showWin() {
  document.getElementById('winScore').innerHTML = score;
  document.getElementById('winKills').innerHTML = kills;
  document.getElementById('winScreen').className = 'overlay';
}

function showLevelScreen() {
  document.getElementById('levelTitle').innerHTML = '第 ' + (currentLevel + 1) + ' 关';
  document.getElementById('levelScreen').className = 'overlay';
}

function hideLevelScreen() {
  document.getElementById('levelScreen').className = 'overlay hidden';
}

function startGame() {
  currentLevel = 0;
  score = 0;
  lives = 3;
  kills = 0;
  gameState = 'levelIntro';
  levelTimer = 2;
  document.getElementById('startScreen').className = 'overlay hidden';
  document.getElementById('gameOverScreen').className = 'overlay hidden';
  document.getElementById('winScreen').className = 'overlay hidden';
  showLevelScreen();
  initLevel(0);
  // Focus for keyboard
  document.body.focus();
  if (isTouchDevice) {
    document.getElementById('mobileControls').className = 'active';
    document.getElementById('pauseBtn').className = 'active';
  }
}

// ===== INPUT HANDLING =====
function handleKeyDown(e) {
  var key = e.key || '';
  var code = e.code || '';
  // Edge/IE uses 'Spacebar' instead of ' ' for space key
  if (key === 'Spacebar') key = ' ';
  if (key === 'ArrowUp' || key === 'ArrowDown' || key === 'ArrowLeft' || key === 'ArrowRight' ||
      key === ' ' || code === 'KeyW' || code === 'KeyA' || code === 'KeyS' || code === 'KeyD' ||
      key === 'w' || key === 'a' || key === 's' || key === 'd' ||
      key === 'W' || key === 'A' || key === 'S' || key === 'D') {
    e.preventDefault();
  }
  if (key) keys[key] = true;
  if (code) keys[code] = true;
  if (key === 'p' || key === 'P') {
    if (gameState === 'playing') isPaused = !isPaused;
  }
}

function handleKeyUp(e) {
  var key = e.key || '';
  var code = e.code || '';
  // Edge/IE uses 'Spacebar' instead of ' ' for space key
  if (key === 'Spacebar') key = ' ';
  if (key) keys[key] = false;
  if (code) keys[code] = false;
}

document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
// Also listen on window for iframe compatibility
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);

// ===== MOBILE CONTROLS =====
function addTouchControl(elem, keyName) {
  elem.addEventListener('touchstart', function(e) {
    e.preventDefault();
    keys[keyName] = true;
    elem.className = elem.className.replace(' pressed', '') + ' pressed';
  }, false);
  elem.addEventListener('touchend', function(e) {
    e.preventDefault();
    keys[keyName] = false;
    elem.className = elem.className.replace(' pressed', '');
  }, false);
  elem.addEventListener('touchcancel', function(e) {
    keys[keyName] = false;
    elem.className = elem.className.replace(' pressed', '');
  }, false);
  // Mouse fallback
  elem.addEventListener('mousedown', function(e) {
    e.preventDefault();
    keys[keyName] = true;
    elem.className = elem.className.replace(' pressed', '') + ' pressed';
  });
  elem.addEventListener('mouseup', function(e) {
    keys[keyName] = false;
    elem.className = elem.className.replace(' pressed', '');
  });
  elem.addEventListener('mouseleave', function(e) {
    keys[keyName] = false;
    elem.className = elem.className.replace(' pressed', '');
  });
}

var dpadBtns = document.querySelectorAll('.dpad-btn');
for (var di = 0; di < dpadBtns.length; di++) {
  var dir = dpadBtns[di].getAttribute('data-dir');
  var keyMap = {up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight'};
  addTouchControl(dpadBtns[di], keyMap[dir]);
}
addTouchControl(document.getElementById('fireBtn'), ' ');

// Pause button
document.getElementById('pauseBtn').addEventListener('click', function() {
  if (gameState === 'playing') isPaused = !isPaused;
});
document.getElementById('pauseBtn').addEventListener('touchstart', function(e) {
  e.preventDefault();
  if (gameState === 'playing') isPaused = !isPaused;
});

// ===== BUTTON HANDLERS =====
document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('restartBtn').addEventListener('click', startGame);
document.getElementById('winRestartBtn').addEventListener('click', startGame);

// ===== AUTO FOCUS =====
document.body.focus();
document.body.addEventListener('click', function() { document.body.focus(); });

// Show mobile controls on touch devices
if (isTouchDevice) {
  document.getElementById('mobileControls').className = 'active';
  document.getElementById('pauseBtn').className = 'active';
}

// ===== START =====
requestAnimationFrame(gameLoop);
</script>
</body>
</html>

手机端

手机端

操作

操作

免费评分

参与人数 35吾爱币 +34 热心值 +31 收起 理由
dapaijun + 1 + 1 谢谢@Thanks!
liuyuchen1 + 1 + 1 我很赞同!
无名无尽 + 1 + 1 热心回复!
汉城路一哥 + 1 + 1 热心回复!
zp999 + 1 用心讨论,共获提升!
realocean + 1 谢谢@Thanks!
Tiniaual + 1 + 1 我很赞同!
Myunlove + 1 + 1 谢谢@Thanks!
liaohexlxw + 1 + 1 谢谢@Thanks!
wang82530 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
feng0321 + 1 谢谢@Thanks!
qingne0130 + 1 + 1 热心回复!
dkw7546 + 1 + 1 热心回复!
haisha + 1 + 1 热心回复!
635217113 + 1 + 1 谢谢@Thanks!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Rabbit1124 + 1 我很赞同!
18696358796 + 1 谢谢@Thanks!
cccc13775 + 1 + 1 谢谢@Thanks!
JIESEN123 + 1 热心回复!
yzqgeorge + 1 + 1 谢谢@Thanks!
HUANGHAIFANG + 1 + 1 热心回复!
shishi0655 + 1 + 1 谢谢@Thanks!
klmytwb + 1 + 1 谢谢@Thanks!
joody + 1 用心讨论,共获提升!
rjlly + 1 + 1 用心讨论,共获提升!
无尘浪子 + 1 谢谢@Thanks!
985csust + 1 我很赞同!
webmarksman + 1 热心回复!
Wyljj + 1 + 1 谢谢@Thanks!
ParllelShifterX + 1 + 1 谢谢@Thanks!
抱薪风雪雾 + 1 + 1 谢谢@Thanks!
Lcp1027 + 1 + 1 谢谢@Thanks!
hjsm + 1 + 1 能不能更新一下,把大家测试完的BUG修复以后,再发一个新版本出来
zxq1010117 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

l12345678qaz 发表于 2026-7-3 17:13
不能玩,根据原代码,修改后可以玩。

[HTML] 纯文本查看 复制代码
<!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; }
  body {
    background: #000;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    font-family: 'Press Start 2P', 'Courier New', monospace;
    overflow: hidden;
    color: #fff;
  }
  #gameWrapper {
    display: flex;
    gap: 20px;
    align-items: flex-start;
  }
  #gameCanvas {
    border: 3px solid #555;
    image-rendering: pixelated;
    image-rendering: crisp-edges;
  }
  #sidebar {
    width: 160px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding-top: 10px;
  }
  .info-block { text-align: center; }
  .info-block .label {
    font-size: 10px;
    color: #888;
    margin-bottom: 6px;
    letter-spacing: 2px;
  }
  .info-block .value {
    font-size: 22px;
    color: #ff0;
    text-shadow: 0 0 8px rgba(255,255,0,0.5);
  }
  .enemy-icons {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    justify-content: center;
  }
  .enemy-icon {
    width: 16px; height: 16px;
    background: #e44;
    border-radius: 2px;
  }
  .enemy-icon.dead { background: #333; }
  #startScreen, #gameOverScreen, #winScreen {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,0.92);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 100;
    gap: 30px;
  }
  #startScreen h1 {
    font-size: 36px;
    color: #ff0;
    text-shadow: 0 0 20px rgba(255,255,0,0.6);
    letter-spacing: 6px;
  }
  #startScreen .subtitle {
    font-size: 12px;
    color: #aaa;
    letter-spacing: 3px;
  }
  .btn {
    padding: 14px 40px;
    font-size: 14px;
    font-family: inherit;
    background: transparent;
    color: #ff0;
    border: 2px solid #ff0;
    cursor: pointer;
    letter-spacing: 3px;
    transition: all 0.2s;
  }
  .btn:hover {
    background: #ff0;
    color: #000;
  }
  #gameOverScreen h1 { font-size: 32px; color: #e44; letter-spacing: 4px; }
  #winScreen h1 { font-size: 28px; color: #4f4; letter-spacing: 4px; }
  .score-display { font-size: 16px; color: #ff0; }
  .controls-hint {
    font-size: 10px;
    color: #666;
    line-height: 2;
    text-align: center;
  }
  .hidden { display: none !important; }
  #pauseOverlay {
    position: fixed;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    font-size: 28px;
    color: #ff0;
    letter-spacing: 6px;
    z-index: 90;
    text-shadow: 0 0 20px rgba(255,255,0,0.6);
  }
   
  /* Mobile Controls */
  #mobileControls {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 20px;
    z-index: 80;
    pointer-events: none;
  }
   
  #mobileControls.active {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
  }
   
  .dpad {
    position: relative;
    width: 140px;
    height: 140px;
    pointer-events: auto;
  }
   
  .dpad-btn {
    position: absolute;
    width: 46px;
    height: 46px;
    background: rgba(255, 255, 0, 0.3);
    border: 2px solid rgba(255, 255, 0, 0.6);
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    color: #ff0;
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
  }
   
  .dpad-btn:active, .dpad-btn.pressed {
    background: rgba(255, 255, 0, 0.6);
  }
   
  .dpad-up { top: 0; left: 50%; transform: translateX(-50%); }
  .dpad-down { bottom: 0; left: 50%; transform: translateX(-50%); }
  .dpad-left { left: 0; top: 50%; transform: translateY(-50%); }
  .dpad-right { right: 0; top: 50%; transform: translateY(-50%); }
   
  .fire-btn {
    width: 80px;
    height: 80px;
    background: rgba(255, 68, 68, 0.4);
    border: 3px solid rgba(255, 68, 68, 0.8);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    color: #fff;
    font-weight: bold;
    pointer-events: auto;
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
  }
   
  .fire-btn:active, .fire-btn.pressed {
    background: rgba(255, 68, 68, 0.7);
  }
   
  .pause-btn {
    width: 44px;
    height: 44px;
    background: rgba(255, 255, 0, 0.3);
    border: 2px solid rgba(255, 255, 0, 0.6);
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    color: #ff0;
    font-weight: bold;
    pointer-events: auto;
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
  }
   
  .pause-btn:active, .pause-btn.pressed {
    background: rgba(255, 255, 0, 0.6);
  }
   
  /* Responsive layout */
  [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 768px), (max-height: 600px) {
    #gameWrapper {
      flex-direction: column;
      align-items: center;
      gap: 10px;
    }
     
    #sidebar {
      flex-direction: row;
      width: auto;
      gap: 20px;
      padding: 10px;
      flex-wrap: wrap;
      justify-content: center;
    }
     
    #gameCanvas {
      max-width: 90vw;
      max-height: 50vh;
      width: auto;
      height: auto;
    }
     
    #mobileControls {
      padding: 10px 20px 30px;
    }
     
    .dpad {
      width: 120px;
      height: 120px;
    }
     
    .dpad-btn {
      width: 40px;
      height: 40px;
      font-size: 16px;
    }
     
    .fire-btn {
      width: 70px;
      height: 70px;
      font-size: 12px;
    }
     
    .controls-hint {
      display: none;
    }
  }
   
  @media (max-width: 400px) {
    .dpad {
      width: 100px;
      height: 100px;
    }
     
    .dpad-btn {
      width: 34px;
      height: 34px;
      font-size: 14px;
    }
     
    .fire-btn {
      width: 60px;
      height: 60px;
    }
  }
</style>
</head>
<body tabindex="0">
 
<div id="startScreen">
  <h1>坦克大战</h1>
  <div class="subtitle">BATTLE CITY</div>
  <button class="btn">开始游戏</button>
  <div class="controls-hint" id="desktopHint">
    方向键 / WASD 移动<br>
    空格键 射击<br>
    P 暂停
  </div>
  <div class="controls-hint" id="mobileHint" style="display:none;">
    左侧方向键移动<br>
    右侧 FIRE 射击<br>
    II 按钮暂停
  </div>
</div>
 
<div id="gameOverScreen" class="hidden">
  <h1>游戏结束</h1>
  <div class="score-display">得分: <span id="finalScore">0</span></div>
  <button class="btn">再来一局</button>
</div>
 
<div id="winScreen" class="hidden">
  <h1>胜利!</h1>
  <div class="score-display">得分: <span id="winScore">0</span></div>
  <button class="btn">下一关</button>
</div>
 
<div id="pauseOverlay" class="hidden">暂停</div>
 
<div id="gameWrapper">
  <canvas id="gameCanvas" width="520" height="520"></canvas>
  <div id="sidebar">
    <div class="info-block">
      <div class="label">关卡</div>
      <div class="value" id="levelDisplay">1</div>
    </div>
    <div class="info-block">
      <div class="label">得分</div>
      <div class="value" id="scoreDisplay">0</div>
    </div>
    <div class="info-block">
      <div class="label">生命</div>
      <div class="value" id="livesDisplay">3</div>
    </div>
    <div class="info-block">
      <div class="label">剩余敌人</div>
      <div class="enemy-icons" id="enemyIcons"></div>
    </div>
    <div class="controls-hint">
      方向键/WASD 移动<br>
      空格 射击<br>
      P 暂停
    </div>
  </div>
</div>
 
<!-- Mobile Controls -->
<div id="mobileControls">
  <div class="dpad">
    <div class="dpad-btn dpad-up" data-dir="up">▲</div>
    <div class="dpad-btn dpad-down" data-dir="down">▼</div>
    <div class="dpad-btn dpad-left" data-dir="left">&#9664;</div>
    <div class="dpad-btn dpad-right" data-dir="right">&#9654;</div>
  </div>
  <div style="display:flex;flex-direction:column;gap:12px;align-items:center;pointer-events:auto;">
    <div class="pause-btn" id="pauseBtn">II</div>
    <div class="fire-btn" id="fireBtn">FIRE</div>
  </div>
</div>
 
<script>
// ==================== CONSTANTS ====================
const TILE = 20;
const COLS = 26;
const ROWS = 26;
const W = COLS * TILE;
const H = ROWS * TILE;
const HALF = TILE / 2;
 
const DIR = { UP: 0, RIGHT: 1, DOWN: 2, LEFT: 3 };
const DX = [0, 1, 0, -1];
const DY = [-1, 0, 1, 0];
 
const TANK_SIZE = TILE * 2 - 2;
const BULLET_SIZE = 4;
const PLAYER_SPEED = 1.8;
const ENEMY_SPEED = 1.0;
const BULLET_SPEED = 3.5;
const ENEMY_BULLET_SPEED = 2.8;
const ENEMY_SHOOT_INTERVAL = 90;
const SPAWN_INTERVAL = 180;
const MAX_ENEMIES_ON_FIELD = 4;
 
const TILE_EMPTY = 0;
const TILE_BRICK = 1;
const TILE_STEEL = 2;
const TILE_WATER = 3;
const TILE_TREE = 4;
const TILE_BASE = 5;
const TILE_BASE_DEAD = 6;
 
const COLORS = {
  bg: '#000',
  brick: '#b35900',
  brickLight: '#cc7a33',
  steel: '#aaa',
  steelLight: '#ccc',
  water: '#2244aa',
  waterLight: '#3366cc',
  tree: '#226622',
  treeLight: '#33aa33',
  base: '#ff0',
  baseDead: '#666',
  playerBody: '#ff0',
  playerTurret: '#cc0',
  playerTrack: '#aa0',
  enemyBody: '#e44',
  enemyTurret: '#c22',
  enemyTrack: '#a22',
  enemyFastBody: '#e8e',
  enemyFastTurret: '#c6c',
  enemyArmorBody: '#4ae',
  enemyArmorTurret: '#28c',
  bullet: '#fff',
  explosion: ['#ff0', '#fa0', '#f60', '#f00', '#800']
};
 
// ==================== LEVELS ====================
const LEVELS = [
  // Level 1
  [
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011002200110011001100",
    "00110011000000110011001100",
    "00110011000000110011001100",
    "00110011001100110011001100",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "11001100000000001100110000",
    "22001100000000001100002200",
    "00000000001100000000000000",
    "00000000001100000000000000",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011000000110011001100",
    "00110011000000110011001100",
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00000000001111000000000000",
    "00000000001551000000000000",
    "00000000001551000000000000",
  ],
  // Level 2
  [
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00110022001100110022001100",
    "00110022001100110022001100",
    "00110000001100110000001100",
    "00110000001100110000001100",
    "00000011220000221100000000",
    "00000011220000221100000000",
    "00220000001100000000220000",
    "00220000001100000000220000",
    "00001100110000110011000000",
    "00001100110000110011000000",
    "11000022000000002200001100",
    "11000022000000002200001100",
    "00001100110000110011000000",
    "00001100110000110011000000",
    "00220000001100000000220000",
    "00220000001100000000220000",
    "00110011001100110011001100",
    "00110011001100110011001100",
    "00110011000000110011001100",
    "00110011000000110011001100",
    "00000000000000000000000000",
    "00000000001111000000000000",
    "00000000001551000000000000",
    "00000000001551000000000000",
  ],
  // Level 3
  [
    "00000000000000000000000000",
    "00000000000000000000000000",
    "00110011003300110011001100",
    "00110011003300110011001100",
    "00110011000000110011001100",
    "00110011000000110011001100",
    "00000022001100220000001100",
    "00000022001100220000001100",
    "22110000002200000011002200",
    "22110000002200000011002200",
    "00001133000000331100000000",
    "00001133000000331100000000",
    "00110000221100220000110000",
    "00110000221100220000110000",
    "00001133000000331100000000",
    "00001133000000331100000000",
    "22110000002200000011002200",
    "22110000002200000011002200",
    "00000022001100220000001100",
    "00000022001100220000001100",
    "00110011000000110011001100",
    "00110011000000110011001100",
    "00000000000000000000000000",
    "00000000001111000000000000",
    "00000000001551000000000000",
    "00000000001551000000000000",
  ],
];
 
// ==================== GAME STATE ====================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
 
let gameState = 'start'; // start, playing, paused, gameover, win
let level = 0;
let score = 0;
let lives = 3;
let map = [];
let player = null;
let enemies = [];
let bullets = [];
let explosions = [];
let powerUps = [];
let enemiesRemaining = 0;
let enemiesSpawned = 0;
let totalEnemies = 20;
let spawnTimer = 0;
let frameCount = 0;
let baseAlive = true;
let spawnPoints = [];
let keys = {};
let playerInvincible = 0;
let freezeTimer = 0;
 
// ==================== INPUT ====================
function handleKeyDown(e) {
  keys[e.code] = true;
  // Also map to key for compatibility
  if (e.key) keys[e.key] = true;
   
  if (e.key === 'p' || e.key === 'P') {
    if (gameState === 'playing') {
      gameState = 'paused';
      document.getElementById('pauseOverlay').classList.remove('hidden');
    } else if (gameState === 'paused') {
      gameState = 'playing';
      document.getElementById('pauseOverlay').classList.add('hidden');
    }
  }
  if (['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key)) {
    e.preventDefault();
  }
}
 
function handleKeyUp(e) {
  keys[e.code] = false;
  if (e.key) keys[e.key] = false;
}
 
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
window.addEventListener('keydown', handleKeyDown);
window.addEventListener('keyup', handleKeyUp);
 
// Ensure focus for keyboard input in iframe environments
window.addEventListener('load', () => { document.body.focus(); });
document.addEventListener('click', () => { document.body.focus(); });
document.addEventListener('touchstart', () => { document.body.focus(); });
 
// ==================== MOBILE CONTROLS ====================
const mobileControls = document.getElementById('mobileControls');
const dpadBtns = document.querySelectorAll('.dpad-btn');
const fireBtn = document.getElementById('fireBtn');
 
// Detect touch device and show controls
function checkTouchDevice() {
  if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
    mobileControls.classList.add('active');
    document.getElementById('desktopHint').style.display = 'none';
    document.getElementById('mobileHint').style.display = 'block';
  }
}
checkTouchDevice();
 
// D-pad touch handling
const dirMap = { up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight' };
const codeMap = { up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight' };
 
dpadBtns.forEach(btn => {
  const dir = btn.dataset.dir;
   
  btn.addEventListener('touchstart', (e) => {
    e.preventDefault();
    btn.classList.add('pressed');
    keys[dirMap[dir]] = true;
    keys[codeMap[dir]] = true;
  });
   
  btn.addEventListener('touchend', (e) => {
    e.preventDefault();
    btn.classList.remove('pressed');
    keys[dirMap[dir]] = false;
    keys[codeMap[dir]] = false;
  });
   
  btn.addEventListener('touchcancel', (e) => {
    e.preventDefault();
    btn.classList.remove('pressed');
    keys[dirMap[dir]] = false;
    keys[codeMap[dir]] = false;
  });
   
  // Mouse fallback for testing
  btn.addEventListener('mousedown', (e) => {
    e.preventDefault();
    btn.classList.add('pressed');
    keys[dirMap[dir]] = true;
    keys[codeMap[dir]] = true;
  });
   
  btn.addEventListener('mouseup', (e) => {
    e.preventDefault();
    btn.classList.remove('pressed');
    keys[dirMap[dir]] = false;
    keys[codeMap[dir]] = false;
  });
   
  btn.addEventListener('mouseleave', (e) => {
    btn.classList.remove('pressed');
    keys[dirMap[dir]] = false;
    keys[codeMap[dir]] = false;
  });
});
 
// Fire button touch handling
fireBtn.addEventListener('touchstart', (e) => {
  e.preventDefault();
  fireBtn.classList.add('pressed');
  keys[' '] = true;
  keys['Space'] = true;
});
 
fireBtn.addEventListener('touchend', (e) => {
  e.preventDefault();
  fireBtn.classList.remove('pressed');
  keys[' '] = false;
  keys['Space'] = false;
});
 
fireBtn.addEventListener('touchcancel', (e) => {
  e.preventDefault();
  fireBtn.classList.remove('pressed');
  keys[' '] = false;
  keys['Space'] = false;
});
 
// Mouse fallback for fire button
fireBtn.addEventListener('mousedown', (e) => {
  e.preventDefault();
  fireBtn.classList.add('pressed');
  keys[' '] = true;
  keys['Space'] = true;
});
 
fireBtn.addEventListener('mouseup', (e) => {
  e.preventDefault();
  fireBtn.classList.remove('pressed');
  keys[' '] = false;
  keys['Space'] = false;
});
 
fireBtn.addEventListener('mouseleave', (e) => {
  fireBtn.classList.remove('pressed');
  keys[' '] = false;
  keys['Space'] = false;
});
 
// Pause button
const pauseBtn = document.getElementById('pauseBtn');
function togglePause() {
  if (gameState === 'playing') {
    gameState = 'paused';
    document.getElementById('pauseOverlay').classList.remove('hidden');
  } else if (gameState === 'paused') {
    gameState = 'playing';
    document.getElementById('pauseOverlay').classList.add('hidden');
  }
}
 
pauseBtn.addEventListener('touchstart', (e) => {
  e.preventDefault();
  pauseBtn.classList.add('pressed');
  togglePause();
});
pauseBtn.addEventListener('touchend', (e) => {
  e.preventDefault();
  pauseBtn.classList.remove('pressed');
});
pauseBtn.addEventListener('click', (e) => {
  e.preventDefault();
  togglePause();
});
 
// ==================== MAP ====================
function loadMap(lvl) {
  const data = LEVELS[lvl % LEVELS.length];
  map = [];
  for (let r = 0; r < ROWS; r++) {
    map[r] = [];
    for (let c = 0; c < COLS; c++) {
      map[r][c] = parseInt(data[r][c]);
    }
  }
}
 
function drawTile(r, c) {
  const x = c * TILE;
  const y = r * TILE;
  const t = map[r][c];
 
  if (t === TILE_BRICK) {
    ctx.fillStyle = COLORS.brick;
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = COLORS.brickLight;
    ctx.fillRect(x, y, TILE/2, TILE/2);
    ctx.fillRect(x + TILE/2, y + TILE/2, TILE/2, TILE/2);
    ctx.strokeStyle = '#000';
    ctx.lineWidth = 0.5;
    ctx.strokeRect(x, y, TILE, TILE);
  } else if (t === TILE_STEEL) {
    ctx.fillStyle = COLORS.steel;
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = COLORS.steelLight;
    ctx.fillRect(x + 2, y + 2, TILE - 4, TILE - 4);
    ctx.fillStyle = COLORS.steel;
    ctx.fillRect(x + 5, y + 5, TILE - 10, TILE - 10);
  } else if (t === TILE_WATER) {
    const wave = Math.sin(frameCount * 0.05 + c + r) * 0.3 + 0.5;
    ctx.fillStyle = wave > 0.5 ? COLORS.water : COLORS.waterLight;
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = wave > 0.5 ? COLORS.waterLight : COLORS.water;
    for (let i = 0; i < 3; i++) {
      const wy = y + 4 + i * 6 + Math.sin(frameCount * 0.08 + c * 0.5) * 2;
      ctx.fillRect(x, wy, TILE, 2);
    }
  } else if (t === TILE_BASE) {
    ctx.fillStyle = COLORS.base;
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = '#000';
    ctx.fillRect(x + 4, y + 4, TILE - 8, TILE - 8);
    ctx.fillStyle = COLORS.base;
    ctx.fillRect(x + 7, y + 7, TILE - 14, TILE - 14);
  } else if (t === TILE_BASE_DEAD) {
    ctx.fillStyle = COLORS.baseDead;
    ctx.fillRect(x, y, TILE, TILE);
    ctx.fillStyle = '#444';
    ctx.fillRect(x + 3, y + 3, TILE - 6, TILE - 6);
  }
}
 
function drawTree(r, c) {
  if (map[r][c] !== TILE_TREE) return;
  const x = c * TILE;
  const y = r * TILE;
  ctx.fillStyle = COLORS.tree;
  ctx.fillRect(x, y, TILE, TILE);
  ctx.fillStyle = COLORS.treeLight;
  ctx.fillRect(x + 2, y + 2, 6, 6);
  ctx.fillRect(x + 10, y + 8, 6, 6);
  ctx.fillRect(x + 4, y + 12, 6, 6);
}
 
// ==================== TANK ====================
class Tank {
  constructor(x, y, dir, isPlayer, type) {
    this.x = x;
    this.y = y;
    this.dir = dir;
    this.isPlayer = isPlayer;
    this.type = type || 'normal'; // normal, fast, armor
    this.speed = isPlayer ? PLAYER_SPEED : (type === 'fast' ? ENEMY_SPEED * 1.6 : ENEMY_SPEED);
    this.hp = isPlayer ? 1 : (type === 'armor' ? 3 : 1);
    this.maxHp = this.hp;
    this.shootCooldown = 0;
    this.alive = true;
    this.moveTimer = 0;
    this.aiDir = dir;
    this.aiDirTimer = 0;
    this.flashTimer = 0;
    this.spawnAnim = 30;
    this.size = TANK_SIZE;
  }
 
  get cx() { return this.x + this.size / 2; }
  get cy() { return this.y + this.size / 2; }
 
  update() {
    if (this.spawnAnim > 0) { this.spawnAnim--; return; }
    if (this.shootCooldown > 0) this.shootCooldown--;
    if (this.flashTimer > 0) this.flashTimer--;
 
    if (this.isPlayer) {
      this.updatePlayer();
    } else {
      if (freezeTimer <= 0) this.updateAI();
    }
  }
 
  updatePlayer() {
    let moving = false;
    let newDir = this.dir;
 
    if (keys['ArrowUp'] || keys['KeyW'] || keys['w'] || keys['W']) { newDir = DIR.UP; moving = true; }
    else if (keys['ArrowDown'] || keys['KeyS'] || keys['s'] || keys['S']) { newDir = DIR.DOWN; moving = true; }
    else if (keys['ArrowLeft'] || keys['KeyA'] || keys['a'] || keys['A']) { newDir = DIR.LEFT; moving = true; }
    else if (keys['ArrowRight'] || keys['KeyD'] || keys['d'] || keys['D']) { newDir = DIR.RIGHT; moving = true; }
 
    this.dir = newDir;
    if (moving) this.move(this.dir);
 
    if (keys[' '] || keys['Space']) this.shoot();
  }
 
  updateAI() {
    this.aiDirTimer--;
    if (this.aiDirTimer <= 0) {
      // Change direction periodically or when blocked
      const dirs = [DIR.UP, DIR.RIGHT, DIR.DOWN, DIR.LEFT];
      // Bias towards player direction
      if (player && player.alive && Math.random() < 0.3) {
        const dx = player.cx - this.cx;
        const dy = player.cy - this.cy;
        if (Math.abs(dx) > Math.abs(dy)) {
          this.aiDir = dx > 0 ? DIR.RIGHT : DIR.LEFT;
        } else {
          this.aiDir = dy > 0 ? DIR.DOWN : DIR.UP;
        }
      } else {
        this.aiDir = dirs[Math.floor(Math.random() * 4)];
      }
      this.aiDirTimer = 60 + Math.floor(Math.random() * 120);
    }
 
    this.dir = this.aiDir;
    const moved = this.move(this.dir);
    if (!moved) {
      this.aiDirTimer = 0; // change direction next frame
    }
 
    // Shooting
    if (Math.random() < 0.02 || (this.shootCooldown <= 0 && Math.random() < 0.05)) {
      this.shoot();
    }
  }
 
  move(dir) {
    const nx = this.x + DX[dir] * this.speed;
    const ny = this.y + DY[dir] * this.speed;
 
    // Boundary check
    if (nx < 0 || ny < 0 || nx + this.size > W || ny + this.size > H) return false;
 
    // Tile collision
    if (this.checkTileCollision(nx, ny)) return false;
 
    // Tank-tank collision
    if (this.checkTankCollision(nx, ny)) return false;
 
    this.x = nx;
    this.y = ny;
    return true;
  }
 
  checkTileCollision(nx, ny) {
    const margin = 1;
    const left = Math.floor((nx + margin) / TILE);
    const top = Math.floor((ny + margin) / TILE);
    const right = Math.floor((nx + this.size - 1 - margin) / TILE);
    const bottom = Math.floor((ny + this.size - 1 - margin) / TILE);
 
    for (let r = top; r <= bottom; r++) {
      for (let c = left; c <= right; c++) {
        if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return true;
        const t = map[r][c];
        if (t === TILE_BRICK || t === TILE_STEEL || t === TILE_WATER || t === TILE_BASE || t === TILE_BASE_DEAD) {
          return true;
        }
      }
    }
    return false;
  }
 
  checkTankCollision(nx, ny) {
    const allTanks = [player, ...enemies].filter(t => t && t.alive && t !== this && t.spawnAnim <= 0);
    for (const other of allTanks) {
      if (nx < other.x + other.size && nx + this.size > other.x &&
          ny < other.y + other.size && ny + this.size > other.y) {
        return true;
      }
    }
    return false;
  }
 
  shoot() {
    if (this.shootCooldown > 0) return;
    // Limit bullets per tank
    const myBullets = bullets.filter(b => b.owner === this);
    if (this.isPlayer && myBullets.length >= 2) return;
    if (!this.isPlayer && myBullets.length >= 1) return;
 
    const bx = this.cx - BULLET_SIZE / 2 + DX[this.dir] * (this.size / 2);
    const by = this.cy - BULLET_SIZE / 2 + DY[this.dir] * (this.size / 2);
    const speed = this.isPlayer ? BULLET_SPEED : ENEMY_BULLET_SPEED;
 
    bullets.push(new Bullet(bx, by, this.dir, this.isPlayer, this, speed));
    this.shootCooldown = this.isPlayer ? 15 : ENEMY_SHOOT_INTERVAL;
  }
 
  draw() {
    if (!this.alive) return;
 
    // Spawn animation
    if (this.spawnAnim > 0) {
      const flash = this.spawnAnim % 8 < 4;
      ctx.strokeStyle = flash ? '#fff' : '#ff0';
      ctx.lineWidth = 2;
      const s = this.size * (1 - this.spawnAnim / 30) * 0.8;
      ctx.strokeRect(this.cx - s/2, this.cy - s/2, s, s);
      return;
    }
 
    ctx.save();
    ctx.translate(this.cx, this.cy);
    ctx.rotate(this.dir * Math.PI / 2);
 
    const s = this.size;
    const hs = s / 2;
 
    // Flash when hit
    if (this.flashTimer > 0 && this.flashTimer % 4 < 2) {
      ctx.globalAlpha = 0.5;
    }
 
    // Body colors
    let bodyColor, turretColor, trackColor;
    if (this.isPlayer) {
      bodyColor = COLORS.playerBody;
      turretColor = COLORS.playerTurret;
      trackColor = COLORS.playerTrack;
    } else if (this.type === 'fast') {
      bodyColor = COLORS.enemyFastBody;
      turretColor = COLORS.enemyFastTurret;
      trackColor = COLORS.enemyFastBody;
    } else if (this.type === 'armor') {
      bodyColor = this.hp > 1 ? COLORS.enemyArmorBody : COLORS.enemyBody;
      turretColor = this.hp > 1 ? COLORS.enemyArmorTurret : COLORS.enemyTurret;
      trackColor = COLORS.enemyArmorBody;
    } else {
      bodyColor = COLORS.enemyBody;
      turretColor = COLORS.enemyTurret;
      trackColor = COLORS.enemyTrack;
    }
 
    // Tracks
    ctx.fillStyle = trackColor;
    ctx.fillRect(-hs, -hs, s * 0.22, s);
    ctx.fillRect(hs - s * 0.22, -hs, s * 0.22, s);
    // Track details
    ctx.fillStyle = '#000';
    for (let i = 0; i < 5; i++) {
      const ty = -hs + 3 + i * (s / 5);
      ctx.fillRect(-hs + 1, ty, s * 0.2, 2);
      ctx.fillRect(hs - s * 0.22 + 1, ty, s * 0.2, 2);
    }
 
    // Body
    ctx.fillStyle = bodyColor;
    ctx.fillRect(-hs + s * 0.22, -hs + 2, s * 0.56, s - 4);
 
    // Turret base
    ctx.fillStyle = turretColor;
    ctx.fillRect(-s * 0.18, -s * 0.18, s * 0.36, s * 0.36);
 
    // Barrel
    ctx.fillStyle = turretColor;
    ctx.fillRect(-2, -hs - 2, 4, hs);
 
    // Invincibility shield
    if (this.isPlayer && playerInvincible > 0) {
      ctx.strokeStyle = playerInvincible % 6 < 3 ? '#0ff' : '#fff';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.arc(0, 0, hs + 2, 0, Math.PI * 2);
      ctx.stroke();
    }
 
    ctx.restore();
  }
 
  hit() {
    this.hp--;
    this.flashTimer = 15;
    if (this.hp <= 0) {
      this.alive = false;
      addExplosion(this.cx, this.cy, true);
      if (!this.isPlayer) {
        score += this.type === 'armor' ? 300 : this.type === 'fast' ? 200 : 100;
        updateUI();
      }
    }
  }
}
 
// ==================== BULLET ====================
class Bullet {
  constructor(x, y, dir, isPlayerBullet, owner, speed) {
    this.x = x;
    this.y = y;
    this.dir = dir;
    this.isPlayerBullet = isPlayerBullet;
    this.owner = owner;
    this.speed = speed;
    this.alive = true;
    this.size = BULLET_SIZE;
  }
 
  update() {
    this.x += DX[this.dir] * this.speed;
    this.y += DY[this.dir] * this.speed;
 
    // Boundary
    if (this.x < 0 || this.y < 0 || this.x > W || this.y > H) {
      this.alive = false;
      addExplosion(this.x, this.y, false);
      return;
    }
 
    // Tile collision
    this.checkTileHit();
    if (!this.alive) return;
 
    // Tank collision
    this.checkTankHit();
  }
 
  checkTileHit() {
    const cx = this.x + this.size / 2;
    const cy = this.y + this.size / 2;
    const c = Math.floor(cx / TILE);
    const r = Math.floor(cy / TILE);
 
    // Check surrounding tiles
    for (let dr = -1; dr <= 1; dr++) {
      for (let dc = -1; dc <= 1; dc++) {
        const tr = r + dr;
        const tc = c + dc;
        if (tr < 0 || tr >= ROWS || tc < 0 || tc >= COLS) continue;
 
        const tx = tc * TILE;
        const ty = tr * TILE;
        if (this.x + this.size <= tx || this.x >= tx + TILE ||
            this.y + this.size <= ty || this.y >= ty + TILE) continue;
 
        const t = map[tr][tc];
        if (t === TILE_BRICK) {
          map[tr][tc] = TILE_EMPTY;
          this.alive = false;
          addExplosion(cx, cy, false);
          return;
        } else if (t === TILE_STEEL) {
          if (this.isPlayerBullet) {
            // Player bullets can't destroy steel (unless power-up)
          }
          this.alive = false;
          addExplosion(cx, cy, false);
          return;
        } else if (t === TILE_BASE) {
          map[tr][tc] = TILE_BASE_DEAD;
          baseAlive = false;
          this.alive = false;
          addExplosion(tx + TILE/2, ty + TILE/2, true);
          return;
        }
      }
    }
  }
 
  checkTankHit() {
    if (this.isPlayerBullet) {
      for (const enemy of enemies) {
        if (!enemy.alive || enemy.spawnAnim > 0) continue;
        if (this.x < enemy.x + enemy.size && this.x + this.size > enemy.x &&
            this.y < enemy.y + enemy.size && this.y + this.size > enemy.y) {
          this.alive = false;
          enemy.hit();
          return;
        }
      }
    } else {
      if (player && player.alive && player.spawnAnim <= 0) {
        if (this.x < player.x + player.size && this.x + this.size > player.x &&
            this.y < player.y + player.size && this.y + this.size > player.y) {
          this.alive = false;
          if (playerInvincible <= 0) {
            player.hit();
            if (!player.alive) {
              lives--;
              updateUI();
              if (lives > 0) {
                setTimeout(() => respawnPlayer(), 1500);
              }
            }
          }
          return;
        }
      }
    }
 
    // Bullet-bullet collision
    for (const other of bullets) {
      if (other === this || !other.alive) continue;
      if (this.isPlayerBullet !== other.isPlayerBullet) {
        if (this.x < other.x + other.size && this.x + this.size > other.x &&
            this.y < other.y + other.size && this.y + this.size > other.y) {
          this.alive = false;
          other.alive = false;
          return;
        }
      }
    }
  }
 
  draw() {
    if (!this.alive) return;
    ctx.fillStyle = COLORS.bullet;
    ctx.shadowColor = '#ff0';
    ctx.shadowBlur = 4;
    ctx.fillRect(this.x, this.y, this.size, this.size);
    ctx.shadowBlur = 0;
  }
}
 
// ==================== EXPLOSION ====================
class Explosion {
  constructor(x, y, big) {
    this.x = x;
    this.y = y;
    this.big = big;
    this.frame = 0;
    this.maxFrame = big ? 20 : 12;
    this.alive = true;
  }

  update() {
    this.frame++;
    if (this.frame >= this.maxFrame) this.alive = false;
  }

  draw() {
    if (!this.alive) return;
    const progress = this.frame / this.maxFrame;
    const radius = this.big ? 15 * (1 - progress) : 10 * (1 - progress);
    const colorIdx = Math.floor(progress * COLORS.explosion.length);
    ctx.fillStyle = COLORS.explosion[colorIdx] || COLORS.explosion[COLORS.explosion.length - 1];
    ctx.beginPath();
    ctx.arc(this.x, this.y, radius, 0, Math.PI * 2);
    ctx.fill();
  }
}

// ==================== GAME FUNCTIONS ====================
function addExplosion(x, y, big) {
  explosions.push(new Explosion(x, y, big));
}

function respawnPlayer() {
  if (lives <= 0) return;
  player = new Tank(12 * TILE, 24 * TILE, DIR.UP, true);
  playerInvincible = 120;
}

function updateUI() {
  document.getElementById('levelDisplay').textContent = level + 1;
  document.getElementById('scoreDisplay').textContent = score;
  document.getElementById('livesDisplay').textContent = lives;
  
  const enemyIcons = document.getElementById('enemyIcons');
  enemyIcons.innerHTML = '';
  for (let i = 0; i < totalEnemies; i++) {
    const icon = document.createElement('div');
    icon.className = 'enemy-icon';
    if (i < enemiesSpawned) icon.classList.add('dead');
    enemyIcons.appendChild(icon);
  }
  
  document.getElementById('finalScore').textContent = score;
  document.getElementById('winScore').textContent = score;
}

function initLevel() {
  loadMap(level);
  enemies = [];
  bullets = [];
  explosions = [];
  powerUps = [];
  enemiesRemaining = totalEnemies;
  enemiesSpawned = 0;
  spawnTimer = 0;
  baseAlive = true;
  spawnPoints = [
    { x: 0, y: 0 },
    { x: 12 * TILE, y: 0 },
    { x: 24 * TILE, y: 0 }
  ];
  
  // Reset base tiles
  for (let r = ROWS - 2; r < ROWS; r++) {
    for (let c = 11; c <= 14; c++) {
      if (map[r][c] === TILE_BASE_DEAD) map[r][c] = TILE_BASE;
    }
  }
  
  // Reset player
  if (player && player.alive) {
    player.x = 12 * TILE;
    player.y = 24 * TILE;
    player.dir = DIR.UP;
    player.spawnAnim = 30;
  } else {
    player = new Tank(12 * TILE, 24 * TILE, DIR.UP, true);
  }
  
  playerInvincible = 60;
  updateUI();
}

function spawnEnemy() {
  if (enemies.length >= MAX_ENEMIES_ON_FIELD || enemiesSpawned >= totalEnemies) return;
  
  const spawnPoint = spawnPoints[Math.floor(Math.random() * spawnPoints.length)];
  const types = ['normal', 'fast', 'armor'];
  const type = Math.random() < 0.3 ? 'armor' : (Math.random() < 0.4 ? 'fast' : 'normal');
  const enemy = new Tank(spawnPoint.x, spawnPoint.y, DIR.DOWN, false, type);
  enemies.push(enemy);
  enemiesSpawned++;
  updateUI();
}

function checkWinCondition() {
  if (enemiesRemaining <= 0) {
    gameState = 'win';
    document.getElementById('winScreen').classList.remove('hidden');
    document.getElementById('winScore').textContent = score;
  }
}

function startGame() {
  level = 0;
  score = 0;
  lives = 3;
  initLevel();
  gameState = 'playing';
  document.getElementById('startScreen').classList.add('hidden');
  document.getElementById('gameOverScreen').classList.add('hidden');
  document.getElementById('winScreen').classList.add('hidden');
}

function restartGame() {
  gameState = 'start';
  document.getElementById('gameOverScreen').classList.add('hidden');
  document.getElementById('winScreen').classList.add('hidden');
  document.getElementById('startScreen').classList.remove('hidden');
}

function nextLevel() {
  level++;
  document.getElementById('winScreen').classList.add('hidden');
  initLevel();
  gameState = 'playing';
}

// ==================== GAME LOOP ====================
function gameLoop() {
  frameCount++;
  
  if (gameState === 'playing') {
    // Update timers
    if (playerInvincible > 0) playerInvincible--;
    if (freezeTimer > 0) freezeTimer--;
    
    // Spawn enemies
    spawnTimer++;
    if (spawnTimer >= SPAWN_INTERVAL && enemiesSpawned < totalEnemies) {
      spawnTimer = 0;
      spawnEnemy();
    }
    
    // Update player
    if (player && player.alive) {
      player.update();
    }
    
    // Update enemies
    enemies = enemies.filter(enemy => enemy.alive);
    enemies.forEach(enemy => enemy.update());
    
    // Update bullets
    bullets = bullets.filter(bullet => bullet.alive);
    bullets.forEach(bullet => bullet.update());
    
    // Update explosions
    explosions = explosions.filter(explosion => explosion.alive);
    explosions.forEach(explosion => explosion.update());
    
    // Update enemy remaining count
    enemiesRemaining = totalEnemies - enemiesSpawned + enemies.length;
    
    // Check win/lose conditions
    if (enemiesRemaining <= 0) {
      checkWinCondition();
    }
    if (lives <= 0 || !baseAlive) {
      gameState = 'gameover';
      document.getElementById('gameOverScreen').classList.remove('hidden');
      document.getElementById('finalScore').textContent = score;
    }
  }
  
  // Draw everything
  ctx.clearRect(0, 0, W, H);
  
  // Draw map
  for (let r = 0; r < ROWS; r++) {
    for (let c = 0; c < COLS; c++) {
      drawTile(r, c);
    }
  }
  
  // Draw trees (above everything)
  for (let r = 0; r < ROWS; r++) {
    for (let c = 0; c < COLS; c++) {
      if (map[r][c] === TILE_TREE) {
        drawTree(r, c);
      }
    }
  }
  
  // Draw tanks
  enemies.forEach(enemy => enemy.draw());
  if (player && player.alive) player.draw();
  
  // Draw bullets
  bullets.forEach(bullet => bullet.draw());
  
  // Draw explosions
  explosions.forEach(explosion => explosion.draw());
  
  // Update UI
  updateUI();
  
  requestAnimationFrame(gameLoop);
}

// ==================== INITIALIZATION ====================
function init() {
  // Set canvas size
  canvas.width = W;
  canvas.height = H;
  
  // Add button event listeners
  const startBtn = document.querySelector('#startScreen .btn');
  const restartBtn = document.querySelector('#gameOverScreen .btn');
  const nextLevelBtn = document.querySelector('#winScreen .btn');
  
  startBtn.addEventListener('click', startGame);
  restartBtn.addEventListener('click', startGame);
  nextLevelBtn.addEventListener('nextLevel', nextLevel);
  
  // Start game loop
  requestAnimationFrame(gameLoop);
}

// Start the game when page loads
window.addEventListener('load', init);
 
// ==================== POWER-UP ====================
class PowerUp {
  constructor(x, y, type) {
    this.x = x;
    this.y = y;
    this.type = type; // 'star', 'shield', 'bomb', 'life'
    this.alive = true;
    this.timer = 600; // 10 seconds
    this.size = TILE * 2;
  }
 
  update() {
    this.timer--;
    if (this.timer <= 0) this.alive = false;
 
    if (player && player.alive) {
      if (this.x < player.x + player.size && this.x + this.size > player.x &&
          this.y < player.y + player.size && this.y + this.size > player.y) {
        this.alive = false;
        this.apply();
      }
    }
  }
 
  apply() {
    switch (this.type) {
      case 'shield':
        playerInvincible = 300;
        break;
      case 'bomb':
        enemies.forEach(e => {
          if (e.alive) {
            e.alive = false;
            addExplosion(e.cx, e.cy, true);
            score += 50;
          }
        });
        break;
      case 'life':
        lives++;
        break;
      case 'star':
        player.speed = PLAYER_SPEED * 1.3;
        break;
    }
    score += 500;
    updateUI();
  }
 
  draw() {
    if (!this.alive) return;
    if (this.timer % 20 < 10 && this.timer < 120) return; // blink when about to expire
 
    const x = this.x;
    const y = this.y;
    const s = this.size;
 
    ctx.fillStyle = '#222';
    ctx.fillRect(x, y, s, s);
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 1;
    ctx.strokeRect(x, y, s, s);
 
    ctx.font = '16px monospace';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
 
    switch (this.type) {
      case 'shield':
        ctx.fillStyle = '#0ff';
        ctx.fillText('S', x + s/2, y + s/2);
        break;
      case 'bomb':
        ctx.fillStyle = '#f44';
        ctx.fillText('B', x + s/2, y + s/2);
        break;
      case 'life':
        ctx.fillStyle = '#4f4';
        ctx.fillText('+', x + s/2, y + s/2);
        break;
      case 'star':
        ctx.fillStyle = '#ff0';
        ctx.fillText('*', x + s/2, y + s/2);
        break;
    }
  }
}
 
// ==================== GAME LOGIC ====================
function spawnEnemy() {
  if (enemiesSpawned >= totalEnemies) return;
  if (enemies.filter(e => e.alive).length >= MAX_ENEMIES_ON_FIELD) return;
 
  const sp = spawnPoints[enemiesSpawned % spawnPoints.length];
  // Check if spawn point is clear
  const allTanks = [player, ...enemies].filter(t => t && t.alive);
  for (const t of allTanks) {
    if (sp.x < t.x + t.size && sp.x + TANK_SIZE > t.x &&
        sp.y < t.y + t.size && sp.y + TANK_SIZE > t.y) {
      return; // spawn point blocked
    }
  }
 
  const types = ['normal', 'normal', 'fast', 'armor'];
  const typeIdx = Math.min(Math.floor(enemiesSpawned / 5), types.length - 1);
  const type = types[Math.floor(Math.random() * (typeIdx + 1))];
 
  const enemy = new Tank(sp.x, sp.y, DIR.DOWN, false, type);
  enemies.push(enemy);
  enemiesSpawned++;
  updateUI();
}
 
function respawnPlayer() {
  if (gameState !== 'playing') return;
  player = new Tank(4 * TILE, 24 * TILE, DIR.UP, true);
  playerInvincible = 120;
}
 
function startGame() {
  document.getElementById('startScreen').classList.add('hidden');
  document.getElementById('gameOverScreen').classList.add('hidden');
  document.getElementById('winScreen').classList.add('hidden');
 
  level = 0;
  score = 0;
  lives = 3;
  initLevel();
  gameState = 'playing';
  document.body.focus();
}
 
function nextLevel() {
  document.getElementById('winScreen').classList.add('hidden');
  level++;
  initLevel();
  gameState = 'playing';
  document.body.focus();
}
 
function initLevel() {
  loadMap(level);
  bullets = [];
  explosions = [];
  powerUps = [];
  enemies = [];
  enemiesSpawned = 0;
  totalEnemies = 20;
  enemiesRemaining = totalEnemies;
  spawnTimer = 0;
  baseAlive = true;
  playerInvincible = 120;
  freezeTimer = 0;
 
  spawnPoints = [
    { x: 0, y: 0 },
    { x: 12 * TILE, y: 0 },
    { x: 24 * TILE, y: 0 }
  ];
 
  player = new Tank(4 * TILE, 24 * TILE, DIR.UP, true);
  updateUI();
}
 
function updateUI() {
  document.getElementById('levelDisplay').textContent = level + 1;
  document.getElementById('scoreDisplay').textContent = score;
  document.getElementById('livesDisplay').textContent = lives;
 
  const remaining = totalEnemies - enemiesSpawned + enemies.filter(e => e.alive).length;
  const icons = document.getElementById('enemyIcons');
  icons.innerHTML = '';
  for (let i = 0; i < totalEnemies; i++) {
    const div = document.createElement('div');
    div.className = 'enemy-icon' + (i >= remaining ? ' dead' : '');
    icons.appendChild(div);
  }
}
 
function checkWinLose() {
  if (!baseAlive) {
    gameState = 'gameover';
    document.getElementById('finalScore').textContent = score;
    document.getElementById('gameOverScreen').classList.remove('hidden');
    return;
  }
 
  if (lives <= 0 && (!player || !player.alive)) {
    gameState = 'gameover';
    document.getElementById('finalScore').textContent = score;
    document.getElementById('gameOverScreen').classList.remove('hidden');
    return;
  }
 
  const allDead = enemies.every(e => !e.alive);
  if (enemiesSpawned >= totalEnemies && allDead) {
    gameState = 'win';
    document.getElementById('winScore').textContent = score;
    document.getElementById('winScreen').classList.remove('hidden');
  }
}
 
// ==================== MAIN LOOP ====================
function update() {
  if (gameState !== 'playing') return;
 
  frameCount++;
 
  // Spawn enemies
  spawnTimer++;
  if (spawnTimer >= SPAWN_INTERVAL) {
    spawnTimer = 0;
    spawnEnemy();
  }
 
  // Update timers
  if (playerInvincible > 0) playerInvincible--;
  if (freezeTimer > 0) freezeTimer--;
 
  // Update player
  if (player && player.alive) player.update();
 
  // Update enemies
  enemies.forEach(e => { if (e.alive) e.update(); });
 
  // Update bullets
  bullets.forEach(b => { if (b.alive) b.update(); });
  bullets = bullets.filter(b => b.alive);
 
  // Update explosions
  explosions.forEach(e => e.update());
  explosions = explosions.filter(e => e.alive);
 
  // Update power-ups
  powerUps.forEach(p => { if (p.alive) p.update(); });
  powerUps = powerUps.filter(p => p.alive);
 
  // Random power-up drop
  if (frameCount % 600 === 0 && Math.random() < 0.5) {
    const types = ['shield', 'bomb', 'life', 'star'];
    const type = types[Math.floor(Math.random() * types.length)];
    const px = Math.floor(Math.random() * (COLS - 2)) * TILE;
    const py = (4 + Math.floor(Math.random() * 16)) * TILE;
    powerUps.push(new PowerUp(px, py, type));
  }
 
  checkWinLose();
}
 
function draw() {
  // Background
  ctx.fillStyle = COLORS.bg;
  ctx.fillRect(0, 0, W, H);
 
  // Don't draw map if not initialized
  if (!map || map.length === 0) return;
 
  // Draw map (bottom layer - no trees)
  for (let r = 0; r < ROWS; r++) {
    for (let c = 0; c < COLS; c++) {
      if (map[r][c] !== TILE_EMPTY && map[r][c] !== TILE_TREE) {
        drawTile(r, c);
      }
    }
  }
 
  // Draw power-ups
  powerUps.forEach(p => p.draw());
 
  // Draw tanks
  if (player && player.alive) player.draw();
  enemies.forEach(e => { if (e.alive) e.draw(); });
 
  // Draw bullets
  bullets.forEach(b => { if (b.alive) b.draw(); });
 
  // Draw trees (top layer - covers tanks)
  for (let r = 0; r < ROWS; r++) {
    for (let c = 0; c < COLS; c++) {
      drawTree(r, c);
    }
  }
 
  // Draw explosions (topmost)
  explosions.forEach(e => e.draw());
 
  // Debug: show active keys
  const activeKeys = Object.entries(keys).filter(([k, v]) => v).map(([k]) => k);
  if (activeKeys.length > 0) {
    ctx.fillStyle = 'rgba(0,0,0,0.7)';
    ctx.fillRect(2, H - 22, 200, 20);
    ctx.fillStyle = '#0f0';
    ctx.font = '10px monospace';
    ctx.textAlign = 'left';
    ctx.textBaseline = 'middle';
    ctx.fillText('Keys: ' + activeKeys.join(', '), 6, H - 12);
  }
}
 
function gameLoop() {
  update();
  draw();
  requestAnimationFrame(gameLoop);
}
 
// Start the loop
gameLoop();
</script>
</body>
</html>

免费评分

参与人数 6吾爱币 +5 热心值 +6 收起 理由
sakiven + 1 + 1 2秒战士你好
fourme2008 + 1 改的垃圾死。速度这么快。和作者的差太远。差评
分割线有什么罪 + 1 谢谢@Thanks!
xiaosuobjsd + 2 + 1 游戏速度这么快,你要搞死我啊?!?!?!
luogx + 1 + 1 我很赞同!
xuet + 1 + 1 我很赞同!

查看全部评分

lucky2253 发表于 2026-7-4 13:02
https://yyscha.cn/games/tank/index.php         这是我做的90坦克网页版

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
望断红尘 + 1 + 1 我很赞同!

查看全部评分

Flanders 发表于 2026-7-3 15:21
最怀念烟山版90 TANK 吃3把手枪可以打草,还能吃船水上行走如在雪地上丝滑
Lcp1027 发表于 2026-7-3 16:07
根据对您提供的 HTML 文件进行解析,发现游戏无法在本地正常启动的核心原因是:开始游戏、重新开始、下一关等按钮均未绑定点击事件。您点击“开始游戏”按钮时,页面不会执行任何操作,因为 #startScreen 内的按钮没有 onclick 属性,JavaScript 中也没有通过 addEventListener 为其绑定 startGame 函数。
&#129513; 问题定位在您的代码中,以下三个关键按钮缺少事件监听器:
按钮位置
应触发的函数

开始画面 “开始游戏”startGame()
游戏结束 “再来一局”startGame() 或 resetGame()
胜利画面 “下一关”nextLevel()
虽然 startGame、nextLevel 等函数已定义,但没有任何地方调用它们,因此游戏永远停留在初始画面。
&#128736;&#65039; 修复方法在 JavaScript 代码的末尾(gameLoop() 调用之前或之后均可)添加以下事件绑定代码:
javascript// ==================== 绑定按钮事件 ====================document.querySelector('#startScreen .btn').addEventListener('click', startGame);document.querySelector('#gameOverScreen .btn').addEventListener('click', startGame);document.querySelector('#winScreen .btn').addEventListener('click', nextLevel);

&#9989; 完整修改示例(在现有代码末尾追加)找到您的 </script> 标签之前,在 gameLoop() 调用之后(或之前)添加:
javascript// 按钮事件绑定document.querySelector('#startScreen .btn').addEventListener('click', startGame);document.querySelector('#gameOverScreen .btn').addEventListener('click', startGame);document.querySelector('#winScreen .btn').addEventListener('click', nextLevel);

Mzhang2008 发表于 2026-7-3 15:12
年代感十足
SriChen 发表于 2026-7-3 15:16
怎么玩呢
xiaoguips123 发表于 2026-7-3 15:19
点开始不生效
SDQDSJW2000 发表于 2026-7-3 15:21
经典怀旧啊!支持一下!
l12345678qaz 发表于 2026-7-3 15:30
不能玩的
gy123621 发表于 2026-7-3 15:38
按钮点击无效
 楼主| Summer000 发表于 2026-7-3 15:42

保存代码,格式html,直接运行
 楼主| Summer000 发表于 2026-7-3 15:42

我本地试着好着呢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-10 18:47

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表