function draw() {
background(0); // Set background to black
if (!isGameOver) { // Only run game logic if not game over
// --- Circle Logic (Movement, Boundary, Health, Fading) ---
if (circleHealth > 0) { // Only process movement and collision if not defeated
if (keyIsDown(87)) { // W key
circleY -= circleSpeed;
}
if (keyIsDown(83)) { // S key
circleY += circleSpeed;
}
if (keyIsDown(65)) { // A key
circleX -= circleSpeed;
}
if (keyIsDown(68)) { // D key
circleX += circleSpeed;
}
// Circle Boundary Check
circleX = constrain(circleX, circleRadius, width - circleRadius);
circleY = constrain(circleY, circleRadius, height - circleRadius);
} else {
// If health is 0 or less, start fading out
circleFading = true;
}
// Fade out the circle if fading flag is true
if (circleFading) {
circleAlpha -= fadeSpeed;
circleAlpha = constrain(circleAlpha, 0, 255); // Ensure alpha stays between 0 and 255
if (circleAlpha <= 0) {
// Once fully transparent, stop fading and mark as gone
circleFading = false;
}
}
// --- Square Logic (Movement, Boundary, Health, Fading) ---
if (squareHealth > 0) { // Only process movement and collision if not defeated
if (keyIsDown(73)) { // I key
squareY -= squareSpeed;
}
if (keyIsDown(75)) { // K key
squareY += squareSpeed;
}
if (keyIsDown(74)) { // J key
squareX -= squareSpeed;
}
if (keyIsDown(76)) { // L key
squareX += squareSpeed;
}
// Square Boundary Check
// Constrain based on half its size from the edges
squareX = constrain(squareX, squareSize / 2, width - squareSize / 2);
squareY = constrain(squareY, squareSize / 2, height - squareSize / 2);
} else {
// If health is 0 or less, start fading out
squareFading = true;
}
// Fade out the square if fading flag is true
if (squareFading) {
squareAlpha -= fadeSpeed;
squareAlpha = constrain(squareAlpha, 0, 255); // Ensure alpha stays between 0 and 255
if (squareAlpha <= 0) {
// Once fully transparent, stop fading and mark as gone
squareFading = false;
}
}
// --- Alpha Omega Hitbox Logic (stationary) ---
const aoOriginalW = alphaOmegaImage.width * 0.5;
const aoOriginalH = alphaOmegaImage.height * 0.5;
// aoOriginalY is the center Y of the IMAGE *without* bobbing
const aoOriginalY = height * 0.25;
// Calculate hitbox dimensions and position to cut the bottom 20%
const aoX = width / 2;
const aoH = aoOriginalH * 0.8; // Hitbox height is 80% of original image height
// To keep the hitbox's top edge in the same place, adjust aoY upwards
// This aoY is for the HITBOX, so it does NOT include the bobOffset.
const aoY = aoOriginalY - (aoOriginalH * 0.1);
const aoW = aoOriginalW; // Hitbox width remains the same
// Check for circle collision with Alpha Omega ONLY if circle is alive and vulnerable
if (circleHealth > 0 && !circleInvincible && rectCircleCollision(aoX, aoY, aoW, aoH, circleX, circleY, circleRadius)) {
circleHealth -= playerDamageAmount;
circleInvincible = true;
circleInvincibleTimer = millis(); // Record current time for invincibility
console.log("Circle took damage! Health:", circleHealth, "Invincible until:", millis() + invincibilityDuration);
}
// Check for square collision with Alpha Omega ONLY if square is alive and vulnerable
if (squareHealth > 0 && !squareInvincible && rectRectCollision(aoX, aoY, aoW, aoH, squareX, squareY, squareSize, squareSize)) {
squareHealth -= playerDamageAmount;
squareInvincible = true;
squareInvincibleTimer = millis(); // Record current time for invincibility
console.log("Square took damage! Health:", squareHealth, "Invincible until:", millis() + invincibilityDuration);
}
// Manage invincibility timers
if (circleInvincible && millis() - circleInvincibleTimer > invincibilityDuration) {
circleInvincible = false;
}
if (squareInvincible && millis() - squareInvincibleTimer > invincibilityDuration) {
squareInvincible = false;
}
// --- Knife Logic ---
// Loop backwards to safely remove knives from the array
for (let i = knives.length - 1; i >= 0; i--) {
let knife = knives[i];
if (knife.active) {
knife.display(); // Draw the knife
let hit = knife.update(); // Update its position and check for target hit
if (hit) {
alphaOmegaHealth -= knifeDamageAmount; // Alpha Omega takes damage
isAlphaOmegaJiggling = true; // Trigger jiggle effect!
alphaOmegaJiggleTimer = millis(); // Record jiggle start time
knife.active = false; // Mark knife for removal
console.log("Alpha Omega took knife damage! Health:", alphaOmegaHealth);
// Check for Alpha Omega defeat
if (alphaOmegaHealth <= 0) {
isGameOver = true;
gameOverMessage = "PLAYERS WIN!";
noLoop();
console.log("Alpha Omega defeated! PLAYERS WIN!");
}
}
} else {
knives.splice(i, 1); // Remove inactive knife from array
}
}
// --- Dagger Logic ---
// Loop backwards to safely remove daggers from the array
for (let i = daggers.length - 1; i >= 0; i--) {
let dagger = daggers[i];
if (dagger.active) {
dagger.display(); // Draw the dagger
let hit = dagger.update(); // Update its position and check for target hit
if (hit) {
alphaOmegaHealth -= daggerDamageAmount; // Alpha Omega takes dagger damage
isAlphaOmegaJiggling = true; // Trigger jiggle effect!
alphaOmegaJiggleTimer = millis(); // Record jiggle start time
dagger.active = false; // Mark dagger for removal
console.log("Alpha Omega took dagger damage! Health:", alphaOmegaHealth);
// Check for Alpha Omega defeat
if (alphaOmegaHealth <= 0) {
isGameOver = true;
gameOverMessage = "PLAYERS WIN!";
noLoop();
console.log("Alpha Omega defeated! PLAYERS WIN!");
}
}
} else {
daggers.splice(i, 1); // Remove inactive dagger from array
}
}
// --- Fire Ball Logic (New!) ---
// Loop backwards to safely remove fire balls from the array
for (let i = fireBalls.length - 1; i >= 0; i--) {
let fireBall = fireBalls[i];
if (fireBall.active) {
fireBall.display(); // Draw the fire ball
let hit = fireBall.update(); // Update its position and check for target hit
if (hit) {
alphaOmegaHealth -= fireBallDamageAmount; // Alpha Omega takes fire ball damage
isAlphaOmegaJiggling = true; // Trigger jiggle effect!
alphaOmegaJiggleTimer = millis(); // Record jiggle start time
fireBall.active = false; // Mark fire ball for removal
console.log("Alpha Omega took fireball damage! Health:", alphaOmegaHealth);
// Check for Alpha Omega defeat
if (alphaOmegaHealth <= 0) {
isGameOver = true;
gameOverMessage = "PLAYERS WIN!";
noLoop();
console.log("Alpha Omega defeated! PLAYERS WIN!");
}
}
} else {
fireBalls.splice(i, 1); // Remove inactive fire ball from array
}
}
// NEW: Ice Ball Logic
// Loop backwards to safely remove ice balls from the array
for (let i = ices.length - 1; i >= 0; i--) {
let iceBall = ices[i];
if (iceBall.active) {
iceBall.display(); // Draw the ice ball
let hit = iceBall.update(); // Update its position and check for target hit
if (hit) {
alphaOmegaHealth -= iceBallDamageAmount; // Alpha Omega takes ice ball damage
isAlphaOmegaJiggling = true; // Trigger jiggle effect!
alphaOmegaJiggleTimer = millis(); // Record jiggle start time
iceBall.active = false; // Mark ice ball for removal
console.log("Alpha Omega took iceball damage! Health:", alphaOmegaHealth);
// Check for Alpha Omega defeat
if (alphaOmegaHealth <= 0) {
isGameOver = true;
gameOverMessage = "PLAYERS WIN!";
noLoop();
console.log("Alpha Omega defeated! PLAYERS WIN!");
}
}
} else {
ices.splice(i, 1); // Remove inactive ice ball from array
}
}
// NEW: Lightning Logic
// Loop backwards to safely remove lightnings from the array
for (let i = lightnings.length - 1; i >= 0; i--) {
let lightning = lightnings[i];
if (lightning.active) {
lightning.display(); // Draw the lightning
let hit = lightning.update(); // Update its position and check for target hit
if (hit) {
alphaOmegaHealth -= lightningDamageAmount; // Alpha Omega takes lightning damage
isAlphaOmegaJiggling = true; // Trigger jiggle effect!
alphaOmegaJiggleTimer = millis(); // Record jiggle start time
lightning.active = false; // Mark lightning for removal
console.log("Alpha Omega took lightning damage! Health:", alphaOmegaHealth);
// Check for Alpha Omega defeat
if (alphaOmegaHealth <= 0) {
isGameOver = true;
gameOverMessage = "PLAYERS WIN!";
noLoop();
console.log("Alpha Omega defeated! PLAYERS WIN!");
}
}
} else {
lightnings.splice(i, 1); // Remove inactive lightning from array
}
}
// Manage Alpha Omega jiggling timer
if (isAlphaOmegaJiggling && millis() - alphaOmegaJiggleTimer > alphaOmegaJiggleDuration) {
isAlphaOmegaJiggling = false; // Stop jiggling
}
// Game Over Condition Check for Players
if (circleAlpha <= 0 && squareAlpha <= 0) {
isGameOver = true;
gameOverMessage = "ALPHA OMEGA WINS!";
noLoop(); // Stop the draw loop
console.log("Both players defeated! ALPHA OMEGA WINS!");
}
}
// --- Drawing Section (always draw, even if game is over, to show final state) ---
// Only draw the circle if it's not completely faded out
if (circleAlpha > 0) {
noStroke(); // No border for the circle
// If invincible, flash the circle
if (circleInvincible && frameCount % 10 < 5) {
fill(255, 255, 255, circleAlpha); // White with current alpha
} else {
fill(173, 216, 230, circleAlpha); // Light blue with current alpha
}
circle(circleX, circleY, circleRadius * 2); // Draw circle with diameter
drawHealthBar(circleX, circleY - circleRadius - 10, circleHealth, 100);
// New: Draw cooldown bars above the blue circle's health bar
const barWidth = 100; // Same width as health bar
const barHeight = 10; // Slightly thinner cooldown bar
const padding = 5; // Space between health bar and cooldown bar
// Fire Ball Cooldown Bar (highest)
let fireBallCooldownRemaining = fireBallCooldownTimer + fireBallCooldownDuration - millis();
if (fireBallCooldownRemaining > 0) {
let fillWidth = map(fireBallCooldownRemaining, fireBallCooldownDuration, 0, 0, barWidth);
fill(150); // Gray for cooldown
rectMode(CENTER);
let fbBarY = circleY - circleRadius - 10 - (barHeight * 3) - (padding * 3);
rect(circleX, fbBarY, barWidth, barHeight); // Background bar
fill(255, 150, 0); // Orange-yellow for fireball cooldown progress
rect(circleX - (barWidth - fillWidth) / 2, fbBarY, fillWidth, barHeight); // Progress bar
stroke(255); // White border
noFill();
rect(circleX, fbBarY, barWidth, barHeight); // Border
noStroke();
}
// Ice Ball Cooldown Bar (middle)
let iceBallCooldownRemaining = iceBallCooldownTimer + iceBallCooldownDuration - millis();
if (iceBallCooldownRemaining > 0) {
let fillWidth = map(iceBallCooldownRemaining, iceBallCooldownDuration, 0, 0, barWidth);
fill(150); // Gray for cooldown
rectMode(CENTER);
let ibBarY = circleY - circleRadius - 10 - (barHeight * 2) - (padding * 2);
rect(circleX, ibBarY, barWidth, barHeight); // Background bar
fill(0, 150, 255); // Blue for iceball cooldown progress
rect(circleX - (barWidth - fillWidth) / 2, ibBarY, fillWidth, barHeight); // Progress bar
stroke(255); // White border
noFill();
rect(circleX, ibBarY, barWidth, barHeight); // Border
noStroke();
}
// Lightning Cooldown Bar (lowest of the three, above health)
let lightningCooldownRemaining = lightningCooldownTimer + lightningCooldownDuration - millis();
if (lightningCooldownRemaining > 0) {
let fillWidth = map(lightningCooldownRemaining, lightningCooldownDuration, 0, 0, barWidth);
fill(150); // Gray for cooldown
rectMode(CENTER);
let lBarY = circleY - circleRadius - 10 - barHeight - padding;
rect(circleX, lBarY, barWidth, barHeight); // Background bar
fill(255, 255, 0); // Yellow for lightning cooldown progress
rect(circleX - (barWidth - fillWidth) / 2, lBarY, fillWidth, barHeight); // Progress bar
stroke(255); // White border
noFill();
rect(circleX, lBarY, barWidth, barHeight); // Border
noStroke();
}
}
// Only draw the square if it's not completely faded out
if (squareAlpha > 0) {
noStroke(); // No border for the square
// If invincible, flash the square
if (squareInvincible && frameCount % 10 < 5) {
fill(255, 255, 255, squareAlpha); // White with current alpha
} else {
fill(255, 0, 0, squareAlpha); // Red with current alpha
}
rectMode(CENTER); // Draw rectangle from its center
rect(squareX, squareY, squareSize, squareSize);
drawHealthBar(squareX, squareY - squareSize / 2 - 10, squareHealth, 100);
// New: Draw cooldown bars above the red square's health bar
const barWidth = 100; // Same width as health bar
const barHeight = 10; // Slightly thinner cooldown bar
const padding = 5; // Space between health bar and cooldown bar
// Knife Cooldown Bar
let knifeCooldownRemaining = knifeCooldownTimer + knifeCooldownDuration - millis();
if (knifeCooldownRemaining > 0) {
let fillWidth = map(knifeCooldownRemaining, knifeCooldownDuration, 0, 0, barWidth);
fill(150); // Gray for cooldown
rectMode(CENTER);
rect(squareX, squareY - squareSize / 2 - 10 - barHeight - padding, barWidth, barHeight); // Background bar
fill(255, 100, 0); // Orange for knife cooldown progress
rect(squareX - (barWidth - fillWidth) / 2, squareY - squareSize / 2 - 10 - barHeight - padding, fillWidth, barHeight); // Progress bar
stroke(255); // White border
noFill();
rect(squareX, squareY - squareSize / 2 - 10 - barHeight - padding, barWidth, barHeight); // Border
noStroke();
}
// Dagger Cooldown Bar
let daggerCooldownRemaining = daggerCooldownTimer + daggerCooldownDuration - millis();
if (daggerCooldownRemaining > 0) {
let fillWidth = map(daggerCooldownRemaining, daggerCooldownDuration, 0, 0, barWidth);
fill(150); // Gray for cooldown
rectMode(CENTER);
rect(squareX, squareY - squareSize / 2 - 10 - (barHeight * 2) - (padding * 2), barWidth, barHeight); // Background bar
fill(100, 100, 255); // Blue for dagger cooldown progress
rect(squareX - (barWidth - fillWidth) / 2, squareY - squareSize / 2 - 10 - (barHeight * 2) - (padding * 2), fillWidth, barHeight); // Progress bar
stroke(255); // White border
noFill();
rect(squareX, squareY - squareSize / 2 - 10 - (barHeight * 2) - (padding * 2), barWidth, barHeight); // Border
noStroke();
}
}
// --- Draw the Alpha Omega image (with bobbing and jiggling) ---
imageMode(CENTER);
// Calculate bobbing offset for the IMAGE *only*
let bobOffset = sin(frameCount * bobSpeed) * bobAmplitude;
// Calculate jiggling offset for the IMAGE *only*
let jiggleOffset = 0;
if (isAlphaOmegaJiggling) {
// Oscillate between -jiggleAmount and +jiggleAmount
jiggleOffset = sin(frameCount * 0.5) * alphaOmegaJiggleAmount; // Adjust 0.5 for jiggle speed
}
// Use the original image Y position + bobOffset for drawing
// Add jiggleOffset to the X position
image(alphaOmegaImage, width / 2 + jiggleOffset, height * 0.25 + bobOffset, alphaOmegaImage.width * 0.5, alphaOmegaImage.height * 0.5);
// --- Visualize Alpha Omega hitbox with a red border (stationary) ---
const aoOriginalW = alphaOmegaImage.width * 0.5;
const aoOriginalH = alphaOmegaImage.height * 0.5;
const aoOriginalY = height * 0.25; // Base Y for hitbox calculation
const aoX = width / 2;
const aoH = aoOriginalH * 0.8;
// This aoY is for the HITBOX border, so it does NOT include the bobOffset.
const aoY = aoOriginalY - (aoOriginalH * 0.1);
const aoW = aoOriginalW;
noFill();
stroke(255, 0, 0); // Red border
rect(aoX, aoY, aoW, aoH);
noStroke(); // Reset stroke for other drawings
fill(0); // Reset fill for other drawings (or specific colors for players)
// --- Draw Alpha Omega's health bar in the top-left corner ---
// Using a barWidth of 200 and barHeight of 20, with 20 pixels padding from the edge
// The drawHealthBar function takes x, y as the center of the bar
const aoBarWidth = 200;
const aoBarHeight = 20;
const aoPadding = 20;
const aoBarX = aoPadding + aoBarWidth / 2;
const aoBarY = aoPadding + aoBarHeight / 2;
drawHealthBar(aoBarX, aoBarY, alphaOmegaHealth, 10000, aoBarWidth, aoBarHeight);
// Game Over Screen
if (isGameOver) {
fill(255);
textSize(50);
textAlign(CENTER, CENTER);
text(gameOverMessage, width / 2, height / 2);
textSize(20);
text("Press SPACE to Restart", width / 2, height / 2 + 60);
}
}