JIT
Designing a wave-based shooting game with power-ups and scoring system
Create a game like blocktanks but make it a wave game where you shoot enemies, collect coins, collect a score for the amount of time spent, and add a shop with power-ups. Make it so that every 30 score the shop with the power-ups opens and can be closed with the 'z' buttons or can be used.

Certainly! We can set up a basic structure for a wave-based tank game with a shop system using HTML, CSS, and JavaScript.

This provides the basic structure:

  1. HTML: Sets up the canvas, UI elements for score/coins/wave, and a hidden shop div.
  2. CSS: Provides minimal styling for the canvas, UI, and shop.
  3. JavaScript (script.js):
    • Initializes the canvas and game state.
    • Includes basic player (drawing, movement, shooting), bullet, and enemy (drawing, simple movement towards player, basic spawning) logic.
    • Implements collision detection between bullets and enemies, and player and enemies (basic game over).
    • Tracks score based on time and coins from defeated enemies.
    • Includes a simple shop structure with example power-ups.
    • Implements opening/closing the shop based on score threshold (every 30 score) and the 'z' key.
    • Includes basic power-up buying and application logic (speed and fire rate boosts with duration).
    • Uses a simple game loop (requestAnimationFrame).

To run this, save the files as index.html, style.css, and script.js in the same folder and open index.html in a browser. You can move the tank with WASD or arrow keys and shoot with the spacebar. Enemies will spawn and move towards you. Defeating them gives coins. The shop will open when your score reaches 30, 60, etc., and can also be toggled with 'z'. You can buy power-ups if you have enough coins.

This is a starting point. A full game would require more features like:

  • More sophisticated enemy AI and types.
  • Wave progression (increasing difficulty, different enemy types).
  • More power-ups and a better power-up management system.
  • Visual feedback (explosions, hit effects).
  • Sound effects.
  • A proper game over/restart screen.
  • Better bullet direction based on mouse or aiming.
  • Refined collision detection.
Updated files
v1
Add a tank arm and make it so that it shoots where the user's cursor is pointing. Make the tank arm gray, make the bullets a little bit bigger, black, and round. Make it so that the bullet bounce off of the walls once before breaking and having a cool particle effect. Add post processing (or a glow and a nice UI) to everything. Change the shop so that it opens every 10 score. Also, the coins are not spawning, so please make them spawn. Give the player a health bar so that they can be hit 12 times. Lastly, add a proper Game Over screen that has a button to restart the game.

Certainly! Let's enhance the game with a tank arm, mouse aiming, bullet effects, coins, health, a shop update, and a proper game over screen.

Here's a summary of the changes:

  1. HTML: Added elements for the health bar (#healthBarContainer, #healthBar) and the game over screen (#gameOverScreen, #finalScore, #restartBtn). Added .glow class to UI elements.
  2. CSS: Added styles for the health bar, game over screen, and .glow class for visual enhancement. Improved general UI and shop styling.
  3. JavaScript (script.js):
    • Tank Arm & Aiming: Added armLength, armWidth to player. Added mousemove listener to track mousePos. Modified drawPlayer to draw the arm rotated towards the mouse. Modified fireBullet to calculate bullet direction based on the angle to the mouse and start the bullet from the arm tip.
    • Bullet Style: Changed bulletSize to 8 and bulletColor to 'black'. Modified drawBullets to draw circles (arc).
    • Bullet Bounce: Added bouncesLeft property to bullets (starts at 1). Modified moveBullets to check for wall collisions. If bouncesLeft > 0, it reverses the direction and decrements bouncesLeft. If bouncesLeft is 0 on collision, the bullet is removed.
    • Particle Effect: Added particles array, createParticles, updateParticles, and drawParticles functions. Particles are created when a bullet is removed (either by hitting a wall after bouncing or hitting an enemy).
    • Glow/UI: Added calls to updateUI to manage the health bar width and color. CSS handles the glow effects.
    • Shop Trigger: Changed shopScoreThreshold to 10. Added lastShopScoreMultiple to ensure the shop only opens once when crossing a score multiple.
    • Coin Spawning/Collection: Added coinsArray, createCoin, and drawCoins functions. createCoin is called when an enemy is defeated. Added logic in checkCollisions to check for player-coin collisions, add coins, and remove the collected coin.
    • Player Health: Added health, maxHealth, lastHitTime, and hitCooldown to the player object. Modified player-enemy collision to decrease health only if the hit cooldown has passed. Added a 'Heal' power-up example.
    • Health Bar: updateUI now updates the width and color of the #healthBar element.
    • Game Over Screen: Created a gameOver function to set gameState to 'gameover', show the #gameOverScreen, update the final score, and hide the UI. Created a resetGame function to reset all variables, hide the game over screen, show the UI, set gameState back to 'playing', and restart the game loop. The restart button calls resetGame. The game starts by calling resetGame.
    • Game Loop: Added calls to the new drawing and updating functions (drawCoins, drawParticles, updateParticles). Added checks for gameState to pause movement/spawning/collision logic when in 'shop' or 'gameover' states.

Now, when you run the game, you'll see a tank with an arm that follows your mouse, bullets that bounce once, coins dropping from enemies, a health bar, a shop that opens every 10 score, and a proper game over screen with a restart option.*

Updated files
v2
Everything look great, but the game over screen is always showing.

Okay, let's add some debugging logs to see what's happening with the game over screen visibility and ensure the resetGame function is correctly hiding it at the start.

I've added console logs in the resetGame and gameOver functions to help diagnose why the game over screen might be showing unexpectedly.

When you run the game, open your browser's developer console (usually F12).

  • You should see "Resetting game..." and "Game Over screen hidden after adding class: true" when the page loads. This confirms resetGame is called and attempts to hide the screen.
  • The game over screen should then be hidden.
  • If the game over screen is still visible, check if the CSS rule .hidden { display: none; } is being applied correctly (you can inspect the element in the browser's developer tools).
  • If the game over screen is hidden initially but appears immediately, check if the "Game Over function called." log appears right away. This would indicate an issue causing the player's health to drop to 0 instantly.

Based on the code, resetGame() is called at the very beginning, and it explicitly adds the hidden class to the gameOverScreen element. This should hide it according to the CSS. The game over screen should only become visible when the gameOver() function is called (when player health reaches 0), which removes the hidden class.

Please check the console logs and the applied CSS styles if the issue persists.

Updated files
v3
I don't see the console logs and the Game Over Screen is still overlayed. At this point just remove the screen and make the game freeze when the player dies.

Okay, I will remove the dedicated game over screen and make the game freeze when the player's health reaches zero.

I have removed the #gameOverScreen element from index.html and its corresponding styles from style.css.

In script.js:

  • Removed references to the game over screen elements and the gameOver function.
  • Modified the player-enemy collision logic to simply set gameState = 'gameover' when health drops to 0.
  • Modified resetGame to remove game over screen handling and the restart button listener.
  • The gameLoop now simply returns early if gameState is 'gameover', effectively freezing the game state updates while keeping the last frame drawn on the canvas.

The game will now freeze in place when your health reaches zero.

Updated files
v4
Great Job! Make it so that the coins glow and have a nice texture. Make the gravitate toward the player, like a minecraft xp orb. Make the enemy drop a rainbow coin that is 1.5x the size of the normal coin and has a glow when the player shoots a bullet off of a wall and it hits an enemy.

Okay, let's add the glowing, gravitating coins and the special rainbow coin effect.

Here are the changes implemented:

  1. Bullet Bounce Tracking: Added a hasBounced boolean property to bullets. It's set to true when a bullet successfully bounces off a wall.
  2. Coin Creation: Modified createCoin to accept an optional isRainbow parameter. It sets the size, color ('rainbow' string or 'gold'), and value based on this flag.
  3. Rainbow Coin Drop Condition: In checkCollisions, when a bullet hits an enemy and the enemy's health drops to 0, createCoin is now called with bullet.hasBounced as the second argument. This means a rainbow coin is created only if the bullet had bounced before hitting the enemy.
  4. Coin Properties: Added normalCoinSize, rainbowCoinSize, normalCoinValue, rainbowCoinValue, coinCollectionRadius, and coinCollectSpeed constants. Coin objects now store isCollected, collectSpeed, and collectionRadius.
  5. Coin Gravitation (updateCoins):
    • A new function updateCoins is added.
    • It iterates through the coinsArray.
    • If a coin is within coinCollectionRadius of the player, its isCollected flag is set to true.
    • If isCollected is true, the coin's position is updated to move towards the player using a fraction (coinCollectSpeed) of the distance.
    • If a coin gets very close to the player (within player.size / 2), its value is added to the player's coins, the UI is updated, and the coin is removed from the array.
  6. Coin Drawing (drawCoins):
    • Modified drawCoins to handle both normal and rainbow coins.
    • It draws a semi-transparent glow circle behind each coin. The glow color is gold for normal coins and a radial rainbow gradient for rainbow coins.
    • The coin itself is then drawn on top, using a solid gold color for normal coins and a radial rainbow gradient for rainbow coins.
  7. Game Loop: Added a call to updateCoins() within the gameState === 'playing' block to handle coin movement and collection.
  8. Collision Update: Removed the old player-coin collision check from checkCollisions as it's now handled by the gravitation logic in updateCoins.
  9. Reset: Ensured coinsArray is cleared in resetGame.

Now, enemies will drop normal gold coins by default. If you manage to hit and defeat an enemy with a bullet that has bounced off a wall, they will drop a larger, glowing rainbow coin that gives more coins. All coins will gravitate towards the player when they get close.

Updated files
v5
Add an animation for when the enemy damages the player, like a red glow effect on the screen that pulses when hit. When health is low add the same effect but constant and not pulsing, make it get stronger the less health the player has. Make the coins constantly gravitate toward the player. Make the rainbow coin constantly pulse different color. Currently there isn't a second wave, so on the top right of the screen add an enemies you need to kill for next wave meter and number. In wave two add a bigger (1.5x), and darker red enemy that can take 2 hits which can be seen with a small health bar above its head. Finally, make all of the health bars glow and give the game the blocktanks font (Passion One).

Okay, let's integrate these new features to make the game more dynamic and visually engaging.

Here's a breakdown of the changes:

  1. HTML:
    • Added <link> for the Passion One font from Google Fonts.
    • Added <div id="damageOverlay"> for the screen effect.
    • Added <div id="enemiesRemainingDisplay"> to the UI.
  2. CSS:
    • Set font-family: 'Passion One', sans-serif; on the body.
    • Styled #enemiesRemainingDisplay and adjusted #waveDisplay position.
    • Added styles for #damageOverlay including the hit class and @keyframes hit-pulse for the animation.
    • Added low-health class for the constant low health effect (opacity controlled by JS).
    • Modified #healthBarContainer to use filter: drop-shadow() for the glow effect, which looks better than box-shadow for this purpose and can be transitioned.
    • Removed box-shadow from #healthBar.
  3. JavaScript (script.js):
    • Damage/Low Health Overlay:
      • Got reference to damageOverlay.
      • Added triggerDamageOverlay() function to apply the hit class and force the pulse animation restart.
      • Called triggerDamageOverlay() in checkCollisions when the player is hit.
      • In updateUI, calculated lowHealthOpacity based on health percentage when health is <= 30.
      • Added/removed the low-health class and set the backgroundColor opacity of damageOverlay based on low health status.
    • Coin Gravitation:
      • Removed collectionRadius from coin properties.
      • Modified updateCoins to always calculate the direction towards the player and move the coin.
      • Added a speed multiplier (distance / 50 + 1) to make coins move faster as they get closer, like Minecraft XP orbs.
      • Adjusted coinCollectSpeed slightly.
    • Rainbow Coin Pulse:
      • Added rainbowColorIndex variable.
      • Incremented rainbowColorIndex in drawCoins.
      • Modified the drawing logic for rainbow coins (both the glow and the coin itself) to use hsl() color based on rainbowColorIndex and the coin's position, creating a pulsing/shifting color effect. Used shadowBlur for the glow on canvas.
    • Wave Progression:
      • Added enemiesPerWave array to define wave configurations.
      • Added totalEnemiesThisWave and enemiesRemainingThisWave variables.
      • Added startWave(waveNumber) function to initialize wave variables, clear entities, and start spawning enemies for that wave using an interval.
      • Added checkWaveCompletion() function, called when an enemy is defeated, to check if enemiesRemainingThisWave is 0 and advance the wave after a delay.
      • Called startWave(1) in resetGame to begin the first wave.
      • Removed the old spawnEnemy() call from the main game loop as spawning is now managed by the wave interval.
    • Wave 2 Enemy:
      • Added toughEnemySize, toughEnemyColor, toughEnemySpeed constants.
      • Modified spawnEnemy to accept a type parameter ('normal' or 'tough') and set enemy properties accordingly (size, color, speed, health, maxHealth, type).
      • Modified checkCollisions to handle enemies with maxHealth > 1.
      • Modified drawEnemies to draw a small health bar above enemies with maxHealth > 1.
    • Health Bar Glow:
      • Updated updateUI to change the filter property of healthBarContainer based on health percentage to change the glow color.
      • Added canvas drawing for the tough enemy health bar glow using ctx.shadowBlur and ctx.shadowColor.
    • Reset: Updated resetGame to clear wave variables, clear coins/particles, hide the damage overlay, and call startWave(1).

Now you have a more complete wave system, a tougher enemy type in wave 2 with a health bar, coins that gravitate and glow/pulse, a damage/low health screen effect, and the Passion One font!

Updated files
v6
Revert the changes to the font.

Okay, I will revert the font back to the previous one.

I have removed the link to the Passion One font in index.html and reverted the font-family and font-size properties in style.css back to their previous values. The script.js file remains unchanged as the font was a styling concern.

Updated files
v7
The tank is gone, so are the enemies. The text is weird and overlapping and over sized. Please fix these issues.

Okay, let's address these issues. It sounds like something went wrong with the game loop or entity management after the last change, and the UI text styling needs adjustment now that the font size is smaller.

I'll add some console logs to help diagnose why the entities might not be drawing and adjust the UI text positioning in the CSS.

<jitProject id="wave-tank-game" title="Wave Tank Game"> <jitFile type="file" filePath="index.html"> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Wave Tank Game</title> <link rel="stylesheet" href="style.css"> <!-- Removed Passion One font link --> </head> <body> <canvas id="gameCanvas"></canvas>
  <div id="gameUI">
    <div id="scoreDisplay" class="glow">Score: 0</div>
    <div id="coinDisplay" class="glow">Coins: 0</div>
    <div id="waveDisplay" class="glow">Wave: 1</div>
    <div id="enemiesRemainingDisplay" class="glow">Enemies: 0</div> <!-- New UI element -->
    <div id="healthBarContainer">
      <div id="healthBar"></div>
    </div>
  </div>

  <div id="shop" class="hidden">
    <h2>Shop</h2>
    <div id="shopItems">
      <!-- Power-up items will be added here by JavaScript -->
    </div>
    <button id="closeShopBtn">Close Shop (Z)</button>
  </div>

  <!-- Overlay for damage/low health effects -->
  <div id="damageOverlay" class=""></div>


  <script src="script.js"></script>
</body>
</html>
</jitFile> <jitFile type="file" filePath="style.css"> body { margin: 0; overflow: hidden; background-color: #1a1a1a; /* Darker background */ display: flex; justify-content: center; align-items: center; height: 100vh; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Reverted font */ color: #eee; /* Lighter text */ }
canvas {
  border: 2px solid #555;
  background-color: #282828; /* Dark canvas */
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); /* Subtle shadow */
}

#gameUI {
  position: absolute;
  top: 15px;
  left: 15px;
  display: flex;
  flex-direction: column;
  gap: 8px; /* Increased gap */
  font-size: 1.3em; /* Reverted font size */
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); /* Text shadow for readability */
}

#waveDisplay, #enemiesRemainingDisplay {
    position: absolute;
    top: 15px;
    right: 15px;
    font-size: 1.3em; /* Reverted font size */
    text-align: right;
    width: 200px; /* Give it some width */
}

#enemiesRemainingDisplay {
    top: 40px; /* Adjusted position */
}


.glow {
    /* Basic glow effect */
    text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #007bff, 0 0 20px #007bff, 0 0 25px #007bff, 0 0 30px #007bff, 0 0 35px #007bff;
}

#healthBarContainer {
    width: 150px; /* Fixed width */
    height: 20px;
    background-color: #555;
    border: 1px solid #888;
    border-radius: 5px;
    overflow: hidden;
    margin-top: 10px;
    box-shadow: inset 0 0 5px rgba(0,0,0,0.3);
    /* Player Health Bar Glow */
    filter: drop-shadow(0 0 5px #28a745); /* Default green glow */
    transition: filter 0.3s ease-in-out;
}

#healthBar {
    height: 100%;
    width: 100%; /* Will be updated by JS */
    background-color: #28a745; /* Green */
    transition: width 0.3s ease-in-out, background-color 0.3s ease-in-out; /* Smooth transition */
    /* box-shadow handled by container filter */
}

#shop {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: rgba(30, 30, 30, 0.95); /* Darker, slightly more opaque */
  padding: 30px; /* More padding */
  border: 3px solid gold; /* Thicker border */
  border-radius: 15px; /* More rounded corners */
  text-align: center;
  z-index: 10;
  box-shadow: 0 0 30px gold; /* Gold glow */
}

#shop h2 {
  margin-top: 0;
  color: gold;
  text-shadow: 0 0 10px gold; /* Glow for title */
}

#shopItems {
  display: flex;
  flex-direction: column;
  gap: 15px; /* Increased gap */
  margin-bottom: 20px; /* More margin */
}

.shop-item {
  background-color: #444;
  padding: 15px; /* More padding */
  border-radius: 8px; /* More rounded */
  cursor: pointer;
  transition: background-color 0.2s ease, transform 0.1s ease;
  border: 1px solid #666;
  box-shadow: 0 0 5px rgba(255, 255, 255, 0.2); /* Subtle item glow */
}

.shop-item:hover {
  background-color: #555;
  transform: translateY(-2px); /* Slight lift effect */
}

.shop-item span {
    font-size: 1.1em;
}

.shop-item button {
  margin-left: 15px; /* More margin */
  padding: 8px 15px; /* More padding */
  cursor: pointer;
  background-color: #007bff; /* Blue button */
  color: white;
  border: none;
  border-radius: 5px;
  transition: background-color 0.2s ease;
}

.shop-item button:hover {
  background-color: #0056b3; /* Darker blue */
}

#closeShopBtn {
  padding: 12px 25px; /* More padding */
  cursor: pointer;
  background-color: #dc3545; /* Red button */
  color: white;
  border: none;
  border-radius: 5px;
  transition: background-color 0.2s ease;
}

#closeShopBtn:hover {
    background-color: #c82333; /* Darker red */
}

.hidden {
  display: none;
}

#damageOverlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(255, 0, 0, 0); /* Start fully transparent */
    pointer-events: none; /* Allow clicks/mouse events to pass through */
    z-index: 5; /* Below UI, above canvas */
    transition: background-color 0.1s ease-out; /* Smooth fade out */
}

#damageOverlay.hit {
    background-color: rgba(255, 0, 0, 0.3); /* Pulse opacity */
    animation: hit-pulse 0.5s ease-out;
}

@keyframes hit-pulse {
    0% { background-color: rgba(255, 0, 0, 0.3); }
    100% { background-color: rgba(255, 0, 0, 0); }
}

#damageOverlay.low-health {
    /* Opacity set by JS */
    animation: none; /* Disable pulse animation */
}
</jitFile> <jitFile type="file" filePath="script.js"> // Basic game setup and main loop const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const gameUI = document.getElementById('gameUI'); const scoreDisplay = document.getElementById('scoreDisplay'); const coinDisplay = document.getElementById('coinDisplay'); const waveDisplay = document.getElementById('waveDisplay'); const enemiesRemainingDisplay = document.getElementById('enemiesRemainingDisplay'); // New UI element const healthBarContainer = document.getElementById('healthBarContainer'); // Get container for glow const healthBar = document.getElementById('healthBar'); const shopElement = document.getElementById('shop'); const shopItemsElement = document.getElementById('shopItems'); const closeShopBtn = document.getElementById('closeShopBtn'); const damageOverlay = document.getElementById('damageOverlay'); // Damage overlay element
canvas.width = 800;
canvas.height = 600;

let gameState = 'playing'; // 'playing', 'shop', 'gameover'
let score = 0;
let coins = 0;
let wave = 1;
let timeElapsed = 0; // in seconds
const scorePerSecond = 1;
const shopScoreThreshold = 10; // Shop opens every 10 score
let lastShopScoreMultiple = 0; // To track when shop was last opened by score

// Wave Management
let totalEnemiesThisWave = 0;
let enemiesRemainingThisWave = 0;
const enemiesPerWave = [
    { normal: 10, tough: 0 }, // Wave 1: 10 normal enemies
    { normal: 15, tough: 5 }  // Wave 2: 15 normal, 5 tough enemies
    // Add more waves here
];

// Player
const player = {
  x: canvas.width / 2,
  y: canvas.height / 2,
  size: 30,
  color: 'green',
  speed: 5,
  armLength: 40,
  armWidth: 10,
  bullets: [],
  fireRate: 200, // ms between shots
  lastShotTime: 0,
  health: 12,
  maxHealth: 12,
  lastHitTime: 0,
  hitCooldown: 500, // ms cooldown after being hit
  powerups: {} // Active powerups
};

// Mouse position
const mousePos = { x: 0, y: 0 };
canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  mousePos.x = e.clientX - rect.left;
  mousePos.y = e.clientY - rect.top;
});

// Enemies
let enemies = [];
const normalEnemySize = 25;
const toughEnemySize = normalEnemySize * 1.5; // 1.5x bigger
const normalEnemyColor = 'red';
const toughEnemyColor = '#8B0000'; // Darker red
const normalEnemySpeed = 2;
const toughEnemySpeed = 1.5; // Slightly slower? Or faster? Let's make them a bit slower but tougher
const enemySpawnRate = 1000; // ms
let lastEnemySpawnTime = 0;
let spawnIntervalId = null; // To keep track of the spawn interval

// Bullets
const bulletSize = 8; // Bigger
const bulletColor = 'black'; // Black
const bulletSpeed = 7;

// Particles
let particles = [];

// Coins
let coinsArray = [];
const normalCoinSize = 15;
const rainbowCoinSize = normalCoinSize * 1.5;
const coinColor = 'gold';
const rainbowCoinValue = 50; // Value for rainbow coin
const normalCoinValue = 10; // Value for normal coin
const coinCollectSpeed = 0.05; // Speed at which coins move towards player (adjusted for constant pull)
let rainbowColorIndex = 0; // For rainbow coin pulse animation

// Input handling
const keys = {};
document.addEventListener('keydown', (e) => {
  keys[e.key] = true;
  if (e.key === 'z' && gameState !== 'gameover') {
    toggleShop();
  }
});
document.addEventListener('keyup', (e) => {
  keys[e.key] = false;
});

// Shop Power-ups (Example)
const powerups = {
  speedBoost: {
    name: 'Speed Boost',
    cost: 50,
    duration: 10000, // ms
    effect: (p) => { p.speed *= 1.5; },
    endEffect: (p) => { p.speed /= 1.5; }
  },
  fireRateBoost: {
    name: 'Fire Rate Boost',
    cost: 75,
    duration: 15000, // ms
    effect: (p) => { p.fireRate *= 0.6; },
    endEffect: (p) => { p.fireRate /= 0.6; }
  },
  healthBoost: {
      name: 'Heal (3 HP)',
      cost: 30,
      effect: (p) => { p.health = Math.min(p.maxHealth, p.health + 3); updateUI(); },
      duration: 0 // Instant effect
  }
  // Add more powerups here
};

function drawPlayer() {
  // Draw tank body (square)
  ctx.fillStyle = player.color;
  ctx.fillRect(player.x - player.size / 2, player.y - player.size / 2, player.size, player.size);

  // Draw tank arm (rotated rectangle)
  const angle = Math.atan2(mousePos.y - player.y, mousePos.x - player.x);
  ctx.save(); // Save current canvas state
  ctx.translate(player.x, player.y); // Move origin to player center
  ctx.rotate(angle); // Rotate
  ctx.fillStyle = 'gray'; // Gray arm
  // Draw arm starting from player center, extending outwards
  ctx.fillRect(0, -player.armWidth / 2, player.armLength, player.armWidth);
  ctx.restore(); // Restore canvas state
}

function movePlayer() {
  if (gameState !== 'playing') return;
  if (keys['ArrowUp'] || keys['w']) player.y -= player.speed;
  if (keys['ArrowDown'] || keys['s']) player.y += player.speed;
  if (keys['ArrowLeft'] || keys['a']) player.x -= player.speed;
  if (keys['ArrowRight'] || keys['d']) player.x += player.speed;

  // Keep player within bounds
  player.x = Math.max(player.size / 2, Math.min(canvas.width - player.size / 2, player.x));
  player.y = Math.max(player.size / 2, Math.min(canvas.height - player.size / 2, player.y));
}

function fireBullet() {
  if (gameState !== 'playing') return;
  const now = Date.now();
  if (keys[' '] && now - player.lastShotTime > player.fireRate) {
    const angle = Math.atan2(mousePos.y - player.y, mousePos.x - player.x);
    const directionX = Math.cos(angle);
    const directionY = Math.sin(angle);

    // Start bullet slightly ahead of the tank arm tip
    const startX = player.x + directionX * (player.size / 2 + player.armLength);
    const startY = player.y + directionY * (player.size / 2 + player.armLength);


    player.bullets.push({
      x: startX,
      y: startY,
      size: bulletSize,
      color: bulletColor,
      speed: bulletSpeed,
      directionX: directionX,
      directionY: directionY,
      bouncesLeft: 1, // Can bounce once
      hasBounced: false // Flag to track if it has bounced
    });
    player.lastShotTime = now;
  }
}

function drawBullets() {
  ctx.fillStyle = bulletColor;
  player.bullets.forEach(bullet => {
    ctx.beginPath();
    ctx.arc(bullet.x, bullet.y, bullet.size / 2, 0, Math.PI * 2); // Draw circle
    ctx.fill();
  });
}

function moveBullets() {
  player.bullets = player.bullets.filter(bullet => {
    bullet.x += bullet.directionX * bullet.speed;
    bullet.y += bullet.directionY * bullet.speed;

    let removed = false;

    // Check for wall collisions
    const hitLeft = bullet.x - bullet.size / 2 < 0;
    const hitRight = bullet.x + bullet.size / 2 > canvas.width;
    const hitTop = bullet.y - bullet.size / 2 < 0;
    const hitBottom = bullet.y + bullet.size / 2 > canvas.height;

    if ((hitLeft || hitRight || hitTop || hitBottom)) {
        if (bullet.bouncesLeft > 0) {
            // Bounce
            if (hitLeft || hitRight) {
                bullet.directionX *= -1;
                // Adjust position slightly to prevent sticking
                bullet.x = hitLeft ? bullet.size / 2 : canvas.width - bullet.size / 2;
            }
            if (hitTop || hitBottom) {
                bullet.directionY *= -1;
                 // Adjust position slightly to prevent sticking
                bullet.y = hitTop ? bullet.size / 2 : canvas.height - bullet.size / 2;
            }
            bullet.bouncesLeft--;
            bullet.hasBounced = true; // Mark that it has bounced
        } else {
            // No bounces left, remove bullet and create particles
            createParticles(bullet.x, bullet.y, 15, bullet.color); // 15 particles
            removed = true;
        }
    }


    // Remove bullets that are off-screen after bouncing is exhausted
    if (!removed && (bullet.x < -bullet.size || bullet.x > canvas.width + bullet.size || bullet.y < -bullet.size || bullet.y > canvas.height + bullet.size)) {
         removed = true;
    }


    return !removed; // Keep bullet if not removed
  });
}

function drawEnemies() {
  enemies.forEach(enemy => {
    ctx.fillStyle = enemy.color;
    ctx.fillRect(enemy.x - enemy.size / 2, enemy.y - enemy.size / 2, enemy.size, enemy.size);

    // Draw health bar for tough enemies
    if (enemy.maxHealth > 1) {
        const barWidth = enemy.size * 0.8;
        const barHeight = 5;
        const barX = enemy.x - barWidth / 2;
        const barY = enemy.y - enemy.size / 2 - barHeight - 5; // 5px above enemy

        // Background
        ctx.fillStyle = '#555';
        ctx.fillRect(barX, barY, barWidth, barHeight);

        // Health
        const healthWidth = (enemy.health / enemy.maxHealth) * barWidth;
        ctx.fillStyle = '#28a745'; // Green health
        ctx.fillRect(barX, barY, healthWidth, barHeight);

        // Health bar glow (drawn on canvas)
        ctx.save();
        ctx.shadowBlur = 8;
        ctx.shadowColor = '#28a745'; // Green glow
        ctx.fillStyle = '#28a745';
        ctx.fillRect(barX, barY, healthWidth, barHeight); // Redraw to apply glow
        ctx.restore();
    }
  });
}

function moveEnemies() {
  if (gameState !== 'playing') return;
  enemies.forEach(enemy => {
    // Simple AI: Move towards the player
    const dx = player.x - enemy.x;
    const dy = player.y - enemy.y;
    const distance = Math.sqrt(dx * dx + dy * dy);

    if (distance > 0) {
      enemy.x += (dx / distance) * enemy.speed;
      enemy.y += (dy / distance) * enemy.speed;
    }
  });
}

function spawnEnemy(type = 'normal') {
  if (gameState !== 'playing') return;

  // Spawn enemy at a random edge of the canvas
  let x, y;
  const edge = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
  const size = type === 'tough' ? toughEnemySize : normalEnemySize;

  switch (edge) {
    case 0: // Top
      x = Math.random() * canvas.width;
      y = -size;
      break;
    case 1: // Right
      x = canvas.width + size;
      y = Math.random() * canvas.height;
      break;
    case 2: // Bottom
      x = Math.random() * canvas.width;
      y = canvas.height + size;
      break;
    case 3: // Left
      x = -size;
      y = Math.random() * canvas.height;
      break;
  }

  enemies.push({
    x: x,
    y: y,
    size: size,
    color: type === 'tough' ? toughEnemyColor : normalEnemyColor,
    speed: type === 'tough' ? toughEnemySpeed : normalEnemySpeed,
    health: type === 'tough' ? 2 : 1, // Tough enemies have 2 HP
    maxHealth: type === 'tough' ? 2 : 1, // Store max health for bar
    type: type // Store enemy type
  });
  enemiesRemainingThisWave++; // Increment count when spawning
  updateUI();
  console.log(`Spawned ${type} enemy. Total enemies remaining: ${enemiesRemainingThisWave}`); // Debug log
}

function startWave(waveNumber) {
    console.log(`Attempting to start Wave ${waveNumber}`); // Debug log
    wave = waveNumber;
    enemies = []; // Clear existing enemies
    coinsArray = []; // Clear existing coins
    particles = []; // Clear existing particles
    lastEnemySpawnTime = 0; // Reset spawn timer

    // Clear any existing spawn interval
    if (spawnIntervalId) {
        clearInterval(spawnIntervalId);
    }

    const waveConfig = enemiesPerWave[waveNumber - 1]; // Get config for current wave (0-indexed)
    if (!waveConfig) {
        console.log("Game Complete or No more waves defined!");
        gameState = 'gameover'; // Or loop waves, or show win screen
        return;
    }

    totalEnemiesThisWave = waveConfig.normal + waveConfig.tough;
    enemiesRemainingThisWave = 0; // Will be incremented by spawnEnemy

    let spawnedCount = 0;
    spawnIntervalId = setInterval(() => {
        if (gameState !== 'playing') {
             clearInterval(spawnIntervalId);
             spawnIntervalId = null;
             return;
        }
        if (spawnedCount < waveConfig.normal) {
            spawnEnemy('normal');
            spawnedCount++;
        } else if (spawnedCount < totalEnemiesThisWave) {
            spawnEnemy('tough');
            spawnedCount++;
        } else {
            clearInterval(spawnIntervalId); // Stop spawning when all enemies for the wave are queued
            spawnIntervalId = null;
            console.log(`All ${totalEnemiesThisWave} enemies for Wave ${wave} queued.`); // Debug log
        }
    }, enemySpawnRate);

    updateUI();
    console.log(`Starting Wave ${wave} with ${totalEnemiesThisWave} enemies.`); // Debug log
}


function createParticles(x, y, count, color) {
    for (let i = 0; i < count; i++) {
        const angle = Math.random() * Math.PI * 2;
        const speed = Math.random() * 3 + 1;
        particles.push({
            x: x,
            y: y,
            size: Math.random() * 4 + 1,
            color: color,
            velocityX: Math.cos(angle) * speed,
            velocityY: Math.sin(angle) * speed,
            lifetime: 60 // frames
        });
    }
}

function updateParticles() {
    particles = particles.filter(particle => {
        particle.x += particle.velocityX;
        particle.y += particle.velocityY;
        particle.lifetime--;
        return particle.lifetime > 0;
    });
}

function createCoin(x, y, isRainbow = false) {
    coinsArray.push({
        x: x,
        y: y,
        size: isRainbow ? rainbowCoinSize : normalCoinSize,
        color: isRainbow ? 'rainbow' : coinColor, // Use 'rainbow' string to indicate type
        value: isRainbow ? rainbowCoinValue : normalCoinValue,
        collectSpeed: coinCollectSpeed,
        // No longer need collectionRadius or isCollected flag for constant pull
    });
}

function drawCoins() {
    // Update rainbow color index for pulsing effect
    rainbowColorIndex = (rainbowColorIndex + 1) % 360; // Cycle through hues (0-359)

    coinsArray.forEach(coin => {
        const radius = coin.size / 2;

        // Draw glow effect
        ctx.save();
        ctx.globalAlpha = 0.5; // Semi-transparent glow
        if (coin.color === 'rainbow') {
             // Rainbow glow - pulse hue
             const pulsedHue = (rainbowColorIndex + (coin.x + coin.y) * 0.1) % 360; // Offset hue slightly based on position
             ctx.shadowBlur = 15; // Stronger glow
             ctx.shadowColor = `hsl(${pulsedHue}, 100%, 70%)`; // Pulsing color glow
             ctx.fillStyle = `hsl(${pulsedHue}, 100%, 70%)`; // Fill with glow color for shadow effect
             ctx.beginPath();
             ctx.arc(coin.x, coin.y, radius * 1.5, 0, Math.PI * 2); // Larger glow radius
             ctx.fill();
        } else {
             // Gold glow
             ctx.shadowBlur = 10; // Gold glow
             ctx.shadowColor = 'rgba(255, 215, 0, 0.8)';
             ctx.fillStyle = 'rgba(255, 215, 0, 0.8)';
             ctx.beginPath();
             ctx.arc(coin.x, coin.y, radius * 1.2, 0, Math.PI * 2); // Slightly larger glow
             ctx.fill();
        }
        ctx.restore(); // Restore shadow and alpha

        // Draw the coin itself
        if (coin.color === 'rainbow') {
            // Draw rainbow coin with pulsing color
            const pulsedHue = (rainbowColorIndex + (coin.x + coin.y) * 0.1) % 360;
            ctx.fillStyle = `hsl(${pulsedHue}, 100%, 50%)`; // Pulsing color
        } else {
            // Draw normal gold coin
            ctx.fillStyle = coin.color;
        }

        ctx.beginPath();
        ctx.arc(coin.x, coin.y, radius, 0, Math.PI * 2);
        ctx.fill();
    });
}

function updateCoins() {
    if (gameState !== 'playing') return;

    coinsArray = coinsArray.filter(coin => {
        const dx = player.x - coin.x;
        const dy = player.y - coin.y;
        const distance = Math.sqrt(dx * dx + dy * dy);

        // Always move towards player
        if (distance > 1) { // Avoid division by zero and jitter when very close
            coin.x += (dx / distance) * coin.collectSpeed * (distance / 50 + 1); // Speed up as it gets closer
            coin.y += (dy / distance) * coin.collectSpeed * (distance / 50 + 1);
        }


        // Check if coin has reached the player
        if (distance < player.size / 2) { // Or a smaller threshold
            coins += coin.value;
            updateUI();
            return false; // Remove coin
        }

        return true; // Keep coin if not collected yet
    });
}


function checkCollisions() {
  if (gameState !== 'playing') return;
  const now = Date.now();

  // Bullet-Enemy collision
  player.bullets = player.bullets.filter(bullet => {
    let hit = false;
    enemies = enemies.filter(enemy => {
      const dx = bullet.x - enemy.x;
      const dy = bullet.y - enemy.y;
      const distance = Math.sqrt(dx * dx + dy * dy);

      if (distance < bullet.size / 2 + enemy.size / 2) {
        // Collision detected
        enemy.health--;
        hit = true; // Mark bullet for removal
        createParticles(bullet.x, bullet.y, 10, bullet.color); // Particles on hit
        if (enemy.health <= 0) {
          // Check if bullet bounced before hitting this enemy
          createCoin(enemy.x, enemy.y, bullet.hasBounced); // Create coin (rainbow if bounced)
          enemiesRemainingThisWave--; // Decrement count when enemy is defeated
          checkWaveCompletion(); // Check if wave is complete
          return false; // Remove enemy
        }
        return true; // Keep enemy
      }
      return true; // Keep enemy
    });
    return !hit; // Remove bullet if it hit something
  });

  // Player-Enemy collision
  enemies.forEach(enemy => {
    const dx = player.x - enemy.x;
    const dy = player.y - enemy.y;
    const distance = Math.sqrt(dx * dx + dy * dy);

    if (distance < player.size / 2 + enemy.size / 2) {
      // Collision detected
      if (now - player.lastHitTime > player.hitCooldown) {
          player.health--;
          player.lastHitTime = now;
          triggerDamageOverlay(); // Trigger damage animation
          updateUI();
          if (player.health <= 0) {
              // Game Over - Freeze the game
              gameState = 'gameover';
              console.log('Game Over! Player health reached 0.'); // Log game over
              // Clear the spawn interval on game over
              if (spawnIntervalId) {
                  clearInterval(spawnIntervalId);
                  spawnIntervalId = null;
              }
          }
      }
    }
  });

  // Player-Coin collision is now handled by updateCoins gravitation logic
}

function checkWaveCompletion() {
    // Check if all enemies that were *supposed* to spawn this wave have been defeated
    // This requires tracking total enemies spawned vs total enemies defeated
    // A simpler approach for now: check if enemiesRemainingThisWave is 0 AND there are no enemies currently on screen.
    // This assumes all enemies for the wave have finished spawning by the time the last one is killed.
    // A more robust system would track spawned vs killed counts separately.
    // Let's refine this: check if enemiesRemainingThisWave is 0 AND the spawn interval has finished.
    if (enemiesRemainingThisWave <= 0 && spawnIntervalId === null && enemies.length === 0) {
        // All enemies for the current wave are defeated and no more are spawning
        console.log(`Wave ${wave} complete!`);
        // Give a short delay before starting next wave or shop
        setTimeout(() => {
             // Decide whether to open shop or start next wave
             // For now, let's just advance wave after a delay
             startWave(wave + 1);
        }, 3000); // 3 second delay
    }
}


function updateScore(deltaTime) {
    if (gameState === 'playing') {
        timeElapsed += deltaTime / 1000; // Convert ms to seconds
        score = timeElapsed * scorePerSecond;

        // Check if shop should open based on score threshold
        const currentScoreMultiple = Math.floor(score / shopScoreThreshold);
        if (currentScoreMultiple > lastShopScoreMultiple && currentScoreMultiple > 0) {
             openShop();
             lastShopScoreMultiple = currentScoreMultiple;
        }
    }
    updateUI();
}

function updateUI() {
  scoreDisplay.textContent = 'Score: ' + Math.floor(score);
  coinDisplay.textContent = 'Coins: ' + coins;
  waveDisplay.textContent = 'Wave: ' + wave;
  enemiesRemainingDisplay.textContent = 'Enemies: ' + enemiesRemainingThisWave; // Update enemies remaining

  // Update health bar width and color
  const healthPercentage = (player.health / player.maxHealth) * 100;
  healthBar.style.width = healthPercentage + '%';

  let healthColor = '#28a745'; // Green
  let glowColor = '#28a745'; // Green glow
  let lowHealthOpacity = 0; // Overlay opacity

  if (healthPercentage <= 60 && healthPercentage > 30) {
      healthColor = '#ffc107'; // Yellow
      glowColor = '#ffc107'; // Yellow glow
  } else if (healthPercentage <= 30) {
      healthColor = '#dc3545'; // Red
      glowColor = '#dc3545'; // Red glow
      // Low health overlay opacity increases as health decreases
      lowHealthOpacity = 0.1 + (1 - healthPercentage / 30) * 0.4; // From 0.1 to 0.5
      lowHealthOpacity = Math.min(lowHealthOpacity, 0.5); // Cap opacity
  }

  healthBar.style.backgroundColor = healthColor;
  healthBarContainer.style.filter = `drop-shadow(0 0 5px ${glowColor})`; // Update container glow

  // Manage low health overlay
  if (gameState === 'playing' && healthPercentage <= 30) {
      damageOverlay.classList.add('low-health');
      damageOverlay.style.backgroundColor = `rgba(255, 0, 0, ${lowHealthOpacity})`;
  } else {
      damageOverlay.classList.remove('low-health');
      damageOverlay.style.backgroundColor = 'rgba(255, 0, 0, 0)'; // Ensure transparent when not low health
  }
}

function triggerDamageOverlay() {
    damageOverlay.classList.remove('hit'); // Reset animation
    // Use a timeout to re-add the class after a brief moment, forcing the animation to restart
    setTimeout(() => {
        damageOverlay.classList.add('hit');
    }, 10); // Small delay
}


function populateShop() {
  shopItemsElement.innerHTML = ''; // Clear previous items
  for (const key in powerups) {
    const item = powerups[key];
    const itemDiv = document.createElement('div');
    itemDiv.classList.add('shop-item');
    itemDiv.innerHTML = `
      <span>${item.name} - Cost: ${item.cost}</span>
      <button data-powerup="${key}">Buy</button>
    `;
    shopItemsElement.appendChild(itemDiv);
  }

  // Add event listeners to buy buttons
  shopItemsElement.querySelectorAll('.shop-item button').forEach(button => {
    button.addEventListener('click', handleBuyPowerup);
  });
}

function handleBuyPowerup(event) {
  const powerupKey = event.target.dataset.powerup;
  const powerup = powerups[powerupKey];

  if (coins >= powerup.cost) {
    coins -= powerup.cost;
    applyPowerup(powerupKey);
    updateUI();
    // Optionally close shop or update item state
  } else {
    // Simple feedback
    console.log('Not enough coins for ' + powerup.name);
  }
}

function applyPowerup(key) {
    const powerup = powerups[key];
    const now = Date.now();

    // If powerup is already active and has duration, clear old timer
    if (powerup.duration > 0 && player.powerups[key] && player.powerups[key].timer) {
         clearTimeout(player.powerups[key].timer);
         // Revert the old effect before applying the new one if it's a stacking boost
         if (powerup.endEffect) {
             powerup.endEffect(player);
         }
    }

    // Apply the effect
    if (powerup.effect) {
        powerup.effect(player);
    }

    // Set a timer to end the effect if it has a duration
    if (powerup.duration > 0) {
        const timer = setTimeout(() => {
            if (powerup.endEffect) {
                powerup.endEffect(player);
            }
            delete player.powerups[key]; // Remove from active powerups
            console.log(`${powerup.name} effect ended.`);
        }, powerup.duration);

        // Store the active powerup info
        player.powerups[key] = {
            endTime: now + powerup.duration,
            timer: timer // Store timer ID to clear if reapplied
        };
         console.log(`${powerup.name} applied for ${powerup.duration / 1000} seconds.`);
    } else {
         // Instant powerups don't need a timer or to be stored in player.powerups
         console.log(`${powerup.name} applied instantly.`);
    }
}


function openShop() {
  gameState = 'shop';
  populateShop();
  shopElement.classList.remove('hidden');
  // Pause game elements that shouldn't update in shop
}

function closeShop() {
  gameState = 'playing';
  shopElement.classList.add('hidden');
  // Resume game elements
}

function toggleShop() {
  if (gameState === 'playing') {
    openShop();
  } else if (gameState === 'shop') {
    closeShop();
  }
}

function resetGame() {
    console.log('Resetting game...'); // Debug log
    // Reset all game state variables
    gameState = 'playing';
    score = 0;
    coins = 0;
    timeElapsed = 0;
    lastShopScoreMultiple = 0;

    player.x = canvas.width / 2;
    player.y = canvas.height / 2;
    player.speed = 5; // Reset speed if powerup was active
    player.fireRate = 200; // Reset fire rate
    player.bullets = [];
    player.lastShotTime = 0;
    player.health = player.maxHealth;
    player.lastHitTime = 0;
    player.powerups = {}; // Clear active powerups

    enemies = [];
    lastEnemySpawnTime = 0;

    particles = [];
    coinsArray = []; // Clear coins

    // Ensure shop is hidden on reset
    shopElement.classList.add('hidden');
    gameUI.classList.remove('hidden'); // Ensure UI is visible
    damageOverlay.classList.remove('hit', 'low-health'); // Hide overlays
    damageOverlay.style.backgroundColor = 'rgba(255, 0, 0, 0)';

    // Clear any active spawn interval
    if (spawnIntervalId) {
        clearInterval(spawnIntervalId);
        spawnIntervalId = null;
    }

    updateUI(); // Update UI to show reset values
    lastTime = 0; // Reset time for game loop

    startWave(1); // Start the first wave

    requestAnimationFrame(gameLoop); // Restart the game loop
}


// Close shop button event
closeShopBtn.addEventListener('click', closeShop);


// Game Loop
let lastTime = 0;
function gameLoop(currentTime) {
  // console.log('Game Loop running. State:', gameState); // Debug log
  // Simple test drawing - remove later
  // ctx.fillStyle = 'blue';
  // ctx.fillRect(0, 0,
Updated files
v8
Revert the code to the version for I asked for the font changes and the new wave.