Skip to content

Commit bb1b685

Browse files
author
Chinonye
committed
Add player and enemy warship images, implement life mechanics, and create win screen
1 parent 4c3e32e commit bb1b685

6 files changed

Lines changed: 335 additions & 16 deletions

File tree

images/Enemies_warship.avif

273 KB
Binary file not shown.

images/Player-Warship.avif

560 KB
Binary file not shown.

images/seaboard.avif

300 KB
Binary file not shown.

src/index.js

Lines changed: 167 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ class PlayerWarship {
33
this.width = 100;
44
this.height = 50;
55
this.speed = 5;
6+
this.maxLife = 10;
7+
this.life = this.maxLife;
8+
this.isDead = false;
9+
this.direction = 'up';
610
this.warshipAction = document.getElementById('player-warship');
711
this.container = this.warshipAction?.parentElement || document.body;
12+
this.lifeLabel = this.createLifeLabel();
813
this.positionX = Math.max(0, this.container.clientWidth / 2 - this.width / 2);
914
this.positionY = Math.max(0, this.container.clientHeight - this.height - 10);
1015
this.motion();
@@ -17,24 +22,29 @@ class PlayerWarship {
1722
this.warshipAction.style.width = this.width + 'px';
1823
this.warshipAction.style.height = this.height + 'px';
1924
this.getPosition();
25+
this.updateLifeLabel();
2026
}
2127

2228
motionLeft() {
29+
this.direction = 'left';
2330
const minX = 0;
2431
this.positionX = Math.max(minX, this.positionX - this.speed);
2532
this.motion();
2633
}
2734
motionRight() {
35+
this.direction = 'right';
2836
const maxX = this.container.clientWidth - this.width;
2937
this.positionX = Math.min(maxX, this.positionX + this.speed);
3038
this.motion();
3139
}
3240

3341
motionUp() {
42+
this.direction = 'up';
3443
this.positionY = Math.max(0, this.positionY - this.speed);
3544
this.motion();
3645
}
3746
motionDown() {
47+
this.direction = 'down';
3848
const maxY = this.container.clientHeight - this.height;
3949
this.positionY = Math.min(maxY, this.positionY + this.speed);
4050
this.motion();
@@ -44,11 +54,70 @@ class PlayerWarship {
4454
getPosition() {
4555
return { x: this.positionX, y: this.positionY };
4656
}
57+
58+
createLifeLabel() {
59+
if (!this.warshipAction) {
60+
return null;
61+
}
62+
63+
const bar = document.createElement('div');
64+
bar.className = 'life-bar life-bar-player';
65+
const fill = document.createElement('div');
66+
fill.className = 'life-fill life-fill-player';
67+
bar.appendChild(fill);
68+
this.warshipAction.appendChild(bar);
69+
return { bar, fill };
70+
}
71+
72+
updateLifeLabel() {
73+
if (!this.lifeLabel) {
74+
return;
75+
}
76+
const percent = Math.max(0, Math.min(100, (this.life / this.maxLife) * 100));
77+
this.lifeLabel.fill.style.width = `${percent}%`;
78+
}
79+
80+
takeDamage(amount) {
81+
if (this.isDead) {
82+
return;
83+
}
84+
this.life = Math.max(0, this.life - amount);
85+
this.updateLifeLabel();
86+
if (this.life === 0) {
87+
this.isDead = true;
88+
if (!gameEnded) {
89+
gameEnded = true;
90+
window.location.href = './gemeOver.html';
91+
}
92+
}
93+
}
4794
}
4895
const newPlayer = new PlayerWarship();
4996

5097
const enemies = [];
5198
const SPAWN_GAP = 20;
99+
const MAX_ENEMIES = 6;
100+
const WIN_KILLS = 5;
101+
let kills = 0;
102+
let gameEnded = false;
103+
104+
function createScoreboard() {
105+
const board = document.createElement('div');
106+
board.className = 'scoreboard';
107+
board.textContent = `Kills: ${kills}/${WIN_KILLS}`;
108+
const seaboard = document.getElementById('seaboard');
109+
(seaboard || document.body).appendChild(board);
110+
return board;
111+
}
112+
113+
const scoreboard = createScoreboard();
114+
115+
function updateScoreboard() {
116+
if (!scoreboard) {
117+
return;
118+
}
119+
scoreboard.textContent = `Kills: ${kills}/${WIN_KILLS}`;
120+
}
52121

53122
function rectanglesOverlap(a, b) {
54123
return (
@@ -93,8 +162,12 @@ class EnemyWarship {
93162
this.width = 100;
94163
this.height = 50;
95164
this.speed = 1;
165+
this.maxLife = 5;
166+
this.life = this.maxLife;
167+
this.isDestroyed = false;
96168
this.el = element;
97169
this.container = this.el.parentElement || document.body;
170+
this.lifeLabel = this.createLifeLabel();
98171
this.positionX = startX;
99172
this.positionY = startY;
100173
this.updateDom();
@@ -107,6 +180,7 @@ class EnemyWarship {
107180
this.el.style.top = this.positionY + 'px';
108181
this.el.style.width = this.width + 'px';
109182
this.el.style.height = this.height + 'px';
183+
this.updateLifeLabel();
110184
}
111185

112186
chasePlayer() {
@@ -134,27 +208,71 @@ class EnemyWarship {
134208
}
135209

136210
checkCollision(player) {
211+
if (this.isDestroyed) {
212+
return;
213+
}
137214
const hit =
138215
this.positionX < player.x + newPlayer.width &&
139216
this.positionX + this.width > player.x &&
140217
this.positionY < player.y + newPlayer.height &&
141218
this.positionY + this.height > player.y;
142219

143220
if (hit) {
144-
window.location.href = './gemeOver.html';
221+
newPlayer.takeDamage(1);
222+
this.takeDamage(this.life);
145223
}
146224
}
147225

148226
destroy() {
227+
if (this.isDestroyed) {
228+
return;
229+
}
230+
this.isDestroyed = true;
149231
clearInterval(this.chaseInterval);
150232
clearInterval(this.shootInterval);
151233
this.el.remove();
234+
const idx = enemies.indexOf(this);
235+
if (idx !== -1) {
236+
enemies.splice(idx, 1);
237+
}
152238
}
153239

154240
shoot() {
241+
if (this.isDestroyed) {
242+
return;
243+
}
155244
const bulletX = this.positionX + this.width / 2 - 4;
156245
const bulletY = this.positionY + this.height;
157-
enemyBullets.push(new Bullet(bulletX, bulletY, 4, 'enemy'));
246+
enemyBullets.push(new Bullet(bulletX, bulletY, 0, 4, 'enemy'));
247+
}
248+
249+
createLifeLabel() {
250+
const bar = document.createElement('div');
251+
bar.className = 'life-bar life-bar-enemy';
252+
const fill = document.createElement('div');
253+
fill.className = 'life-fill life-fill-enemy';
254+
bar.appendChild(fill);
255+
this.el.appendChild(bar);
256+
return { bar, fill };
257+
}
258+
259+
updateLifeLabel() {
260+
if (!this.lifeLabel) {
261+
return;
262+
}
263+
const percent = Math.max(0, Math.min(100, (this.life / this.maxLife) * 100));
264+
this.lifeLabel.fill.style.width = `${percent}%`;
265+
}
266+
267+
takeDamage(amount) {
268+
if (this.isDestroyed) {
269+
return;
270+
}
271+
this.life = Math.max(0, this.life - amount);
272+
this.updateLifeLabel();
273+
if (this.life === 0) {
274+
this.destroy();
275+
}
158276
}
159277
}
160278
function addElement() {
@@ -164,7 +282,11 @@ function addElement() {
164282
return;
165283
}
166284

167-
if (enemies.length >= 6) {
285+
if (gameEnded) {
286+
return;
287+
}
288+
289+
if (enemies.length >= MAX_ENEMIES) {
168290
return;
169291
}
170292

@@ -212,7 +334,7 @@ function addElement() {
212334
newWarship.style.position = 'absolute';
213335
newWarship.style.width = `${width}px`;
214336
newWarship.style.height = `${height}px`;
215-
newWarship.style.backgroundColor = 'red';
337+
newWarship.style.backgroundColor = 'transparent';
216338
newWarship.style.zIndex = '2';
217339
newWarship.style.left = `${spawn.x}px`;
218340
newWarship.style.top = `${spawn.y}px`;
@@ -228,9 +350,10 @@ const playerBullets = [];
228350
const enemyBullets = [];
229351

230352
class Bullet {
231-
constructor(x, y, dy, owner) {
353+
constructor(x, y, dx, dy, owner) {
232354
this.width = 8;
233355
this.height = 16;
356+
this.dx = dx;
234357
this.dy = dy;
235358
this.owner = owner;
236359
this.el = document.createElement('div');
@@ -252,13 +375,20 @@ class Bullet {
252375
}
253376

254377
update() {
378+
this.positionX += this.dx;
255379
this.positionY += this.dy;
256380
this.updateDom();
257381
}
258382

259383
isOffscreen() {
260384
const maxY = this.container.clientHeight;
261-
return this.positionY < -this.height || this.positionY > maxY + this.height;
385+
const maxX = this.container.clientWidth;
386+
return (
387+
this.positionY < -this.height ||
388+
this.positionY > maxY + this.height ||
389+
this.positionX < -this.width ||
390+
this.positionX > maxX + this.width
391+
);
262392
}
263393

264394
destroy() {
@@ -281,8 +411,15 @@ function updateBullets() {
281411
if (hitEnemy) {
282412
bullet.destroy();
283413
playerBullets.splice(i, 1);
284-
enemy.destroy();
285-
enemies.splice(j, 1);
414+
enemy.takeDamage(1);
415+
if (enemy.isDestroyed) {
416+
kills += 1;
417+
updateScoreboard();
418+
if (kills >= WIN_KILLS && !gameEnded) {
419+
gameEnded = true;
420+
window.location.href = './win.html';
421+
}
422+
}
286423
break;
287424
}
288425
}
@@ -314,7 +451,7 @@ function updateBullets() {
314451
if (hitPlayer) {
315452
bullet.destroy();
316453
enemyBullets.splice(i, 1);
317-
window.location.href = './gemeOver.html';
454+
newPlayer.takeDamage(1);
318455
return;
319456
}
320457

@@ -339,9 +476,27 @@ document.addEventListener('keydown', (e) => {
339476
}
340477

341478
if (e.code === 'Space') {
342-
const bulletX = newPlayer.positionX + newPlayer.width / 2 - 4;
343-
const bulletY = newPlayer.positionY - 16;
344-
playerBullets.push(new Bullet(bulletX, bulletY, -6, 'player'));
479+
let bulletX = newPlayer.positionX + newPlayer.width / 2 - 4;
480+
let bulletY = newPlayer.positionY - 16;
481+
let dx = 0;
482+
let dy = -6;
483+
484+
if (newPlayer.direction === 'down') {
485+
bulletY = newPlayer.positionY + newPlayer.height;
486+
dy = 6;
487+
} else if (newPlayer.direction === 'left') {
488+
bulletX = newPlayer.positionX - 16;
489+
bulletY = newPlayer.positionY + newPlayer.height / 2 - 4;
490+
dx = -6;
491+
dy = 0;
492+
} else if (newPlayer.direction === 'right') {
493+
bulletX = newPlayer.positionX + newPlayer.width;
494+
bulletY = newPlayer.positionY + newPlayer.height / 2 - 4;
495+
dx = 6;
496+
dy = 0;
497+
}
498+
499+
playerBullets.push(new Bullet(bulletX, bulletY, dx, dy, 'player'));
345500
}
346501

347502
if (e.code === 'ArrowRight') {

0 commit comments

Comments
 (0)