bot dificulty

This commit is contained in:
Bluwww 2025-12-13 13:13:24 +07:00
parent e193c09280
commit ca53b4d5b3

103
Script.js
View File

@ -1,8 +1,7 @@
"use strict"; "use strict";
const DEBUG_HITBOX = true; const DEBUG_HITBOX = false;
// ================== BGM SYSTEM ==================
const bgmList = [ const bgmList = [
{ normal: "music/Scary.mp3", gameover: "music/ScaryGO.mp3" }, { normal: "music/Scary.mp3", gameover: "music/ScaryGO.mp3" },
{ normal: "music/Fear.mp3", gameover: "music/FearGO.mp3" }, { normal: "music/Fear.mp3", gameover: "music/FearGO.mp3" },
@ -20,7 +19,6 @@ function pickRandomBGM() {
gameOverBGM.src = bgm.gameover; gameOverBGM.src = bgm.gameover;
} }
// ================== GLOBALS ==================
var canvasWidth = 1280; var canvasWidth = 1280;
var canvasHeight = 650; var canvasHeight = 650;
var c, ctx; var c, ctx;
@ -116,7 +114,6 @@ window.onload = function () {
init(); init();
}; };
// Init
function init() { function init() {
c = document.getElementById("canvas"); c = document.getElementById("canvas");
ctx = c.getContext("2d"); ctx = c.getContext("2d");
@ -160,7 +157,6 @@ function gameLoop(timestamp) {
requestAnimationFrame(gameLoop); requestAnimationFrame(gameLoop);
} }
// ================== INPUT ==================
let gamePaused = false; let gamePaused = false;
function keyDownPressed(e) { function keyDownPressed(e) {
@ -209,7 +205,6 @@ function fireBullet() {
); );
} }
// ================== CORE UPDATE / DRAW ==================
function clearGame() { function clearGame() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight); ctx.clearRect(0, 0, canvasWidth, canvasHeight);
} }
@ -217,17 +212,19 @@ function clearGame() {
function updateGame() { function updateGame() {
game.frames++; game.frames++;
game.level = 1 + Math.floor(player1.score / 500);
game.speed = 1 + game.level * 0.1;
updateStarField(); updateStarField();
addShips(); addShips();
maybeSpawnAbilityToken(); maybeSpawnAbilityToken();
// Auto-fire when holding space
if (keys.fire && !player1.dead && game.frames % 8 === 0) { if (keys.fire && !player1.dead && game.frames % 8 === 0) {
fireBullet(); fireBullet();
} }
// Player update / respawn
if (!player1.dead) { if (!player1.dead) {
player1.update(); player1.update();
if (player1.invincible > 0) player1.invincible--; if (player1.invincible > 0) player1.invincible--;
@ -260,17 +257,13 @@ function drawGame() {
if (currentPlanet) currentPlanet.draw(); if (currentPlanet) currentPlanet.draw();
// Particles (background layer)
drawParticles(); drawParticles();
// Ability tokens
for (let i = 0; i < abilityTokens.length; i++) { for (let i = 0; i < abilityTokens.length; i++) {
const t = abilityTokens[i]; const t = abilityTokens[i];
t.draw(); t.draw();
t.update(); t.update();
// Hitbox token sederhana (tidak perlu .getHitbox karena bentuknya simple)
// Tapi kita pakai logika bounding box manual di sini
if ( if (
!player1.dead && !player1.dead &&
Tabrakan(player1.getHitbox(), { Tabrakan(player1.getHitbox(), {
@ -293,14 +286,11 @@ function drawGame() {
} }
} }
// Player
if (!player1.dead) { if (!player1.dead) {
player1.draw(); player1.draw();
// DEBUG: Lihat Hitbox Player
if (DEBUG_HITBOX) drawDebugHitbox(player1.getHitbox(), "lime"); if (DEBUG_HITBOX) drawDebugHitbox(player1.getHitbox(), "lime");
} }
// Enemies
for (let i = 0; i < enemyShipArray.length; i++) { for (let i = 0; i < enemyShipArray.length; i++) {
let s = enemyShipArray[i]; let s = enemyShipArray[i];
s.draw(); s.draw();
@ -314,8 +304,10 @@ function drawGame() {
continue; continue;
} }
// Enemy shooting (aimed, non-homing) let shootChance = 0.02 + game.level * 0.003;
if (!player1.dead && Math.random() < 0.01 && s.x > player1.x + 50) { if (shootChance > 0.1) shootChance = 0.1;
if (!player1.dead && Math.random() < shootChance && s.x > player1.x + 50) {
const ex = s.x; const ex = s.x;
const ey = s.y + s.height / 2; const ey = s.y + s.height / 2;
const px = player1.x + player1.width / 2; const px = player1.x + player1.width / 2;
@ -323,7 +315,6 @@ function drawGame() {
enemyBulletsArray.push(new EnemyBullet(ex, ey, px, py)); enemyBulletsArray.push(new EnemyBullet(ex, ey, px, py));
} }
// Collision with player (hitbox vs hitbox)
if (!player1.dead && Tabrakan(player1.getHitbox(), s.getHitbox())) { if (!player1.dead && Tabrakan(player1.getHitbox(), s.getHitbox())) {
explosions.push(new Explosion(s.x + s.width / 2, s.y + s.height / 2)); explosions.push(new Explosion(s.x + s.width / 2, s.y + s.height / 2));
createParticles(s.x + s.width / 2, s.y + s.height / 2, 20, "#ff6600"); createParticles(s.x + s.width / 2, s.y + s.height / 2, 20, "#ff6600");
@ -334,7 +325,6 @@ function drawGame() {
} }
} }
// Player bullets
for (let i = 0; i < missilesArray.length; i++) { for (let i = 0; i < missilesArray.length; i++) {
let m = missilesArray[i]; let m = missilesArray[i];
m.draw(); m.draw();
@ -346,23 +336,26 @@ function drawGame() {
for (let j = 0; j < enemyShipArray.length; j++) { for (let j = 0; j < enemyShipArray.length; j++) {
let en = enemyShipArray[j]; let en = enemyShipArray[j];
// PERBAIKAN: Gunakan .getHitbox() untuk kedua objek
if (Tabrakan(m.getHitbox(), en.getHitbox())) { if (Tabrakan(m.getHitbox(), en.getHitbox())) {
player1.score += 100; en.health -= 100;
createParticles(
en.x + en.width / 2,
en.y + en.height / 2,
5,
"#ff9900"
);
missilesArray.splice(i, 1);
hit = true;
if (en.health <= 0) {
player1.score += 100 + game.level * 10;
explosion_enemy.currentTime = 0; explosion_enemy.currentTime = 0;
explosion_enemy.play(); explosion_enemy.play();
explosions.push( explosions.push(
new Explosion(en.x + en.width / 2, en.y + en.height / 2) new Explosion(en.x + en.width / 2, en.y + en.height / 2)
); );
createParticles(
en.x + en.width / 2,
en.y + en.height / 2,
15,
"#ff9900"
);
missilesArray.splice(i, 1);
enemyShipArray.splice(j, 1); enemyShipArray.splice(j, 1);
hit = true; }
break; break;
} }
} }
@ -378,7 +371,6 @@ function drawGame() {
} }
} }
// Enemy bullets
for (let i = 0; i < enemyBulletsArray.length; i++) { for (let i = 0; i < enemyBulletsArray.length; i++) {
let b = enemyBulletsArray[i]; let b = enemyBulletsArray[i];
b.draw(); b.draw();
@ -386,7 +378,6 @@ function drawGame() {
if (DEBUG_HITBOX) drawDebugHitbox(b.getHitbox(), "orange"); if (DEBUG_HITBOX) drawDebugHitbox(b.getHitbox(), "orange");
// PERBAIKAN: Gunakan b.getHitbox() agar collision lebih akurat (tidak kena glow)
if (!player1.dead && Tabrakan(b.getHitbox(), player1.getHitbox())) { if (!player1.dead && Tabrakan(b.getHitbox(), player1.getHitbox())) {
explosions.push( explosions.push(
new Explosion( new Explosion(
@ -417,7 +408,6 @@ function drawGame() {
} }
} }
// Explosions
for (let i = 0; i < explosions.length; i++) { for (let i = 0; i < explosions.length; i++) {
let ex = explosions[i]; let ex = explosions[i];
ex.draw(); ex.draw();
@ -444,6 +434,7 @@ function drawDebugHitbox(rect, color) {
function drawUI() { function drawUI() {
drawNewText(" " + player1.score, 1400, 760, "white"); drawNewText(" " + player1.score, 1400, 760, "white");
drawNewText("LVL " + game.level, 1400, 50, "#00ff00");
let livesText = "Lives: "; let livesText = "Lives: ";
for (let i = 0; i < player1.lives; i++) { for (let i = 0; i < player1.lives; i++) {
@ -488,7 +479,6 @@ class PlayerObject {
}; };
} }
getHitbox() { getHitbox() {
const h = this.height * 0.05; const h = this.height * 0.05;
@ -601,7 +591,6 @@ function handlePlayerHit() {
respawnCounter = 80 * 3; respawnCounter = 80 * 3;
} }
// ================== TEXT ==================
function drawNewText(txt, x, y, color) { function drawNewText(txt, x, y, color) {
ctx.font = "20px Arial"; ctx.font = "20px Arial";
ctx.fillStyle = color; ctx.fillStyle = color;
@ -657,7 +646,6 @@ function drawStarField() {
background1a.draw(); background1a.draw();
} }
// panning
function updateCamera() { function updateCamera() {
const offset = player1.y + player1.height / 2 - canvasHeight / 2; const offset = player1.y + player1.height / 2 - canvasHeight / 2;
const target = -offset * 0.7; const target = -offset * 0.7;
@ -669,7 +657,6 @@ function updateCamera() {
cameraY += (clamped - cameraY) * 0.1; cameraY += (clamped - cameraY) * 0.1;
} }
//
class LaserBullet { class LaserBullet {
constructor(x, y) { constructor(x, y) {
this.x = x; this.x = x;
@ -705,7 +692,6 @@ class LaserBullet {
} }
} }
//
class EnemyObj { class EnemyObj {
constructor(x, y, speed, img, pattern = "straight") { constructor(x, y, speed, img, pattern = "straight") {
this.x = x; this.x = x;
@ -720,7 +706,6 @@ class EnemyObj {
this.angle = 0; this.angle = 0;
} }
getHitbox() { getHitbox() {
const w = this.width * 0.55; const w = this.width * 0.55;
const h = this.height * 0.55; const h = this.height * 0.55;
@ -760,7 +745,6 @@ class EnemyBullet {
this.vy = (dy / len) * speed; this.vy = (dy / len) * speed;
} }
getHitbox() { getHitbox() {
const padding = 1; const padding = 1;
return { return {
@ -840,9 +824,7 @@ function addShips() {
} else { } else {
if (enemyShipArray.length === 0) { if (enemyShipArray.length === 0) {
currentWave = null; currentWave = null;
waveCooldown = 120; waveCooldown = Math.max(60, 120 - game.level * 2);
game.level++;
game.speed += 0.1;
} }
} }
} else { } else {
@ -855,10 +837,16 @@ function addShips() {
} }
function startNewWave() { function startNewWave() {
const patterns = ["line", "v", "sine"]; const patterns = ["line", "v", "sine", "scatter"];
const pattern = patterns[Math.floor(Math.random() * patterns.length)]; const pattern = patterns[Math.floor(Math.random() * patterns.length)];
const count = 4 + Math.floor(Math.random() * 4);
const spacing = 30 + Math.floor(Math.random() * 20); let baseCount = 3;
let scalingCount = Math.floor(game.level / 2);
let count = Math.min(
15,
baseCount + scalingCount + Math.floor(Math.random() * 3)
);
let spacing = Math.max(20, 50 - game.level);
currentWave = { currentWave = {
pattern: pattern, pattern: pattern,
@ -871,7 +859,7 @@ function startNewWave() {
function spawnEnemyFromWave(wave) { function spawnEnemyFromWave(wave) {
const baseY = canvasHeight / 2; const baseY = canvasHeight / 2;
const spread = 200; const spread = 250;
const index = wave.spawned; const index = wave.spawned;
let y = Math.random() * (canvasHeight - 120) + 60; let y = Math.random() * (canvasHeight - 120) + 60;
@ -880,30 +868,32 @@ function spawnEnemyFromWave(wave) {
y = baseY - spread + step * index; y = baseY - spread + step * index;
} else if (wave.pattern === "v") { } else if (wave.pattern === "v") {
const centerIndex = (wave.count - 1) / 2; const centerIndex = (wave.count - 1) / 2;
const offset = (index - centerIndex) * 40; const offset = (index - centerIndex) * 50;
y = baseY + Math.abs(offset) * 1.5; y = baseY + Math.abs(offset) * 1.2;
} else if (wave.pattern === "sine") { } else if (wave.pattern === "sine") {
const angle = (index / wave.count) * Math.PI * 2; const angle = (index / wave.count) * Math.PI * 2;
y = baseY + Math.sin(angle) * spread; y = baseY + Math.sin(angle) * spread;
} else if (wave.pattern === "scatter") {
y = Math.random() * (canvasHeight - 150) + 75;
} }
y = Math.max(40, Math.min(canvasHeight - 140, y)); y = Math.max(60, Math.min(canvasHeight - 100, y));
const randomShip = Math.floor(Math.random() * enemyImgArray.length); const randomShip = Math.floor(Math.random() * enemyImgArray.length);
const speed = 5 + Math.random() * 4; const speed = 4 + Math.random() * 3 + game.level * 0.25;
enemyShipArray.push( let enemy = new EnemyObj(
new EnemyObj(
canvasWidth + 50, canvasWidth + 50,
y, y,
speed + game.speed, speed,
enemyImgArray[randomShip], enemyImgArray[randomShip],
wave.pattern === "sine" ? "sine" : "straight" wave.pattern === "sine" ? "sine" : "straight"
)
); );
enemy.health = 100 + game.level * 25;
enemyShipArray.push(enemy);
} }
// bombs
class AbilityToken { class AbilityToken {
constructor(x, y) { constructor(x, y) {
this.x = x; this.x = x;
@ -946,7 +936,6 @@ function useAbility() {
explosion_enemy.currentTime = 0; explosion_enemy.currentTime = 0;
explosion_enemy.play(); explosion_enemy.play();
// Explosions for all enemies
enemyShipArray.forEach((e) => { enemyShipArray.forEach((e) => {
explosions.push(new Explosion(e.x + e.width / 2, e.y + e.height / 2)); explosions.push(new Explosion(e.x + e.width / 2, e.y + e.height / 2));
createParticles(e.x + e.width / 2, e.y + e.height / 2, 20, "#ff9900"); createParticles(e.x + e.width / 2, e.y + e.height / 2, 20, "#ff9900");
@ -959,7 +948,6 @@ function useAbility() {
damageFlash = 10; damageFlash = 10;
} }
// ================== PARTICLES ==================
class Particle { class Particle {
constructor(x, y, color) { constructor(x, y, color) {
this.x = x; this.x = x;
@ -1069,7 +1057,6 @@ class Explosion {
} }
} }
// GAME OVER / PAUSE
function drawGameOver() { function drawGameOver() {
ctx.fillStyle = "rgba(53, 0, 0, 0.7)"; ctx.fillStyle = "rgba(53, 0, 0, 0.7)";
ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.fillRect(0, 0, canvasWidth, canvasHeight);