Skip to content

Commit a8bc14e

Browse files
authored
Add space invaders (#328)
1 parent a980c91 commit a8bc14e

File tree

3 files changed

+279
-118
lines changed

3 files changed

+279
-118
lines changed

src/assets/css/style.css

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,11 @@ a:hover {
918918
left: 0;
919919
width: 100vw;
920920
height: 100vh;
921-
z-index: 9999;
922-
pointer-events: none; /* Allow clicks to pass through to the site if needed */
921+
z-index: 9999; /* Ensures it sits ABOVE your website content */
922+
pointer-events: none; /* Let's start with none so it doesn't block the heart */
923+
}
924+
925+
/* Ensure the canvas itself fills the container */
926+
#phaser-container canvas {
927+
display: block;
923928
}

src/assets/js/eggs.js

Lines changed: 272 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,304 @@
1+
/**
2+
* eggs.js - The Space Invaders Easter Egg
3+
* Logic: 5 Heart Clicks -> Emoji Explosion -> 80s Space Invaders
4+
*/
5+
6+
// 1. GLOBAL CONSTANTS & STATE
7+
const emojiBurst = [
8+
"🎮",
9+
"🕹️",
10+
"👾",
11+
"🚀",
12+
"✨",
13+
"⭐",
14+
"🔥",
15+
"💥",
16+
"🌈",
17+
"🎉",
18+
"💖",
19+
"💎",
20+
"🤖",
21+
"👻",
22+
"🦄",
23+
"🍄",
24+
"🌍",
25+
"⚡",
26+
"🏆",
27+
"🎯",
28+
"🛸",
29+
"👽",
30+
"👾",
31+
"🐙",
32+
"🦖",
33+
"🪐",
34+
"🌌",
35+
"🌠",
36+
"☄️",
37+
"🌙",
38+
];
39+
40+
let heartClickCount = 0;
41+
let phaserStarted = false;
42+
let gameInstance;
43+
let player;
44+
let cursors;
45+
let aliens;
46+
let bullets;
47+
let lastFired = 0;
48+
49+
// 2. DOM TRIGGER LOGIC (Integrate this with your footer heart)
50+
const heart = document.getElementById("footer-heart");
51+
52+
if (heart) {
53+
heart.style.cursor = "pointer";
54+
heart.style.display = "inline-block"; // Necessary for transforms
55+
56+
heart.addEventListener("click", () => {
57+
if (phaserStarted) return;
58+
59+
heartClickCount++;
60+
61+
// Visual feedback: Heart grows
62+
const scaleAmount = 1 + heartClickCount * 0.3;
63+
heart.style.transition =
64+
"transform 0.1s cubic-bezier(0.17, 0.67, 0.83, 0.67)";
65+
heart.style.transform = `scale(${scaleAmount})`;
66+
67+
if (heartClickCount === 5) {
68+
phaserStarted = true;
69+
heart.innerHTML = "🎮"; // Swap to gamer emoji
70+
heart.style.transform = "scale(1.5)";
71+
72+
setTimeout(() => {
73+
heart.style.opacity = "0"; // Fade out the heart
74+
initPhaserGame();
75+
}, 300);
76+
}
77+
});
78+
}
79+
80+
// 3. PHASER ENGINE INITIALIZATION
181
function initPhaserGame() {
82+
// Create dedicated Canvas
83+
const canvas = document.createElement("canvas");
84+
canvas.id = "phaser-game-canvas";
85+
Object.assign(canvas.style, {
86+
position: "fixed",
87+
top: "0",
88+
left: "0",
89+
width: "100vw",
90+
height: "100vh",
91+
zIndex: "10000",
92+
pointerEvents: "none", // Start as click-through
93+
});
94+
document.body.appendChild(canvas);
95+
296
const config = {
3-
type: Phaser.AUTO,
97+
type: Phaser.CANVAS,
98+
canvas: canvas,
499
width: window.innerWidth,
5100
height: window.innerHeight,
6-
parent: "phaser-container", // Make sure this div exists in your HTML
7101
transparent: true,
8102
physics: {
9103
default: "arcade",
10-
arcade: { gravity: { y: 300 } },
104+
arcade: { gravity: { y: 0 }, debug: false },
11105
},
12106
scene: {
13107
preload: preload,
14108
create: create,
109+
update: update,
15110
},
16111
};
17112

18-
const game = new Phaser.Game(config);
113+
gameInstance = new Phaser.Game(config);
19114
}
20115

116+
// 4. PHASER SCENE FUNCTIONS
21117
function preload() {
22-
// No need to preload images if we are only using text/emojis!
118+
// No assets to load - we use emojis!
23119
}
24120

25121
function create() {
26-
const emojis = [
27-
// Gaming & Tech
28-
"🎮",
29-
"🕹️",
30-
"👾",
31-
"🚀",
32-
"💻",
33-
"📱",
34-
"⌨️",
35-
"🖱️",
36-
"🔋",
37-
"🔌",
38-
// Magic & Space
39-
"✨",
40-
"⭐",
41-
"🌟",
42-
"🔮",
43-
"🌌",
44-
"🌠",
45-
"🌙",
46-
"☄️",
47-
"🛸",
48-
"👽",
49-
// Action & Fun
50-
"🔥",
51-
"💥",
52-
"🧨",
53-
"⚡",
54-
"🌈",
55-
"🎉",
56-
"🎊",
57-
"🎈",
58-
"🎁",
59-
"💎",
60-
// Hearts & Expressions
61-
"💖",
62-
"🎯",
63-
"🏆",
64-
"🥇",
65-
"🧿",
66-
"🍀",
67-
"🍕",
68-
"🍭",
69-
"🍦",
70-
"🍩",
71-
// Creatures & Icons
72-
"🤖",
73-
"👻",
74-
"🐲",
75-
"🦄",
76-
"🦊",
77-
"🐱",
78-
"🐧",
79-
"🦖",
80-
"🍄",
81-
"🌍",
82-
];
122+
const particles = spawnExplosion(this);
123+
124+
// After 5 seconds, clear explosion and start the real game
125+
this.time.delayedCall(5000, () => {
126+
this.tweens.add({
127+
targets: particles.getChildren(),
128+
alpha: 0,
129+
duration: 1000,
130+
onComplete: () => {
131+
particles.clear(true, true);
132+
133+
// Make the game interactive
134+
const canvas = document.getElementById("phaser-game-canvas");
135+
if (canvas) canvas.style.pointerEvents = "auto";
136+
137+
setupSpaceInvaders.call(this);
138+
},
139+
});
140+
});
141+
}
142+
143+
function update() {
144+
if (!player || !player.body) return;
145+
146+
// Movement
147+
if (cursors.left.isDown) {
148+
player.body.setVelocityX(-400);
149+
} else if (cursors.right.isDown) {
150+
player.body.setVelocityX(400);
151+
} else {
152+
player.body.setVelocityX(0);
153+
}
154+
155+
// Shooting
156+
if (cursors.space.isDown) {
157+
fireBullet(this);
158+
}
159+
}
160+
161+
// 5. HELPER FUNCTIONS (The Mechanics)
162+
163+
function spawnExplosion(scene) {
83164
const heartRect = document
84165
.getElementById("footer-heart")
85166
.getBoundingClientRect();
167+
const particles = scene.add.group();
86168

87-
for (let i = 0; i < 75; i++) {
88-
// 1. Pick a random emoji
89-
const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];
90-
91-
// 2. Create the emoji at the heart's location
92-
// We use this.add.text instead of this.physics.add.image
93-
const particle = this.add.text(heartRect.left, heartRect.top, randomEmoji, {
169+
for (let i = 0; i < 40; i++) {
170+
const emoji = Phaser.Utils.Array.GetRandom(emojiBurst);
171+
const p = scene.add.text(heartRect.left, heartRect.top, emoji, {
94172
fontSize: "32px",
95173
});
96174

97-
// 3. Manually add physics to the text object
98-
this.physics.add.existing(particle);
99-
100-
// 4. Apply the "Explosion" physics
101-
// Shoots them out in a cone shape upward
102-
particle.body.setVelocity(
103-
Phaser.Math.Between(-300, 300),
104-
Phaser.Math.Between(-500, -1000),
175+
scene.physics.add.existing(p);
176+
p.body.setVelocity(
177+
Phaser.Math.Between(-400, 400),
178+
Phaser.Math.Between(-600, -1200),
105179
);
180+
p.body.setBounce(0.6);
181+
p.body.setCollideWorldBounds(true);
182+
p.body.setAngularVelocity(Phaser.Math.Between(-200, 200));
183+
184+
particles.add(p);
185+
}
186+
return particles;
187+
}
188+
189+
function setupSpaceInvaders() {
190+
const scene = this;
191+
192+
// Player Rocket
193+
player = scene.add.text(
194+
window.innerWidth / 2,
195+
window.innerHeight - 80,
196+
"🚀",
197+
{ fontSize: "50px" },
198+
);
199+
scene.physics.add.existing(player);
200+
player.body.setCollideWorldBounds(true);
201+
202+
// Bullets
203+
bullets = scene.physics.add.group();
204+
205+
// Aliens Grid - Adjusted for smaller size
206+
aliens = scene.physics.add.group();
207+
const rows = 5;
208+
const cols = 10;
209+
const spacingX = 50; // Tighter horizontal spacing
210+
const spacingY = 45; // Tighter vertical spacing
211+
212+
for (let y = 0; y < rows; y++) {
213+
for (let x = 0; x < cols; x++) {
214+
let alienEmoji = ["👾", "👽", "🛸", "🐙", "👾"][y];
215+
// Shrink from 35px to 24px
216+
let alien = scene.add.text(
217+
x * spacingX + 80,
218+
y * spacingY + 80,
219+
alienEmoji,
220+
{ fontSize: "24px" },
221+
);
222+
223+
scene.physics.add.existing(alien);
224+
alien.body.setAllowGravity(false);
225+
// Shrink the collision box to match the smaller emoji
226+
alien.body.setSize(24, 24);
227+
228+
aliens.add(alien);
229+
}
230+
}
231+
232+
// Alien Movement Timer
233+
scene.alienDirection = 1;
234+
scene.time.addEvent({
235+
delay: 800,
236+
callback: moveAliens,
237+
callbackScope: scene,
238+
loop: true,
239+
});
106240

107-
particle.body.setCollideWorldBounds(true);
108-
particle.body.setBounce(0.7);
241+
// Collisions
242+
scene.physics.add.overlap(bullets, aliens, (bullet, alien) => {
243+
bullet.destroy();
244+
alien.destroy();
245+
if (aliens.countActive() === 0) {
246+
alert("INVADERS REPELLED! YOU WIN!");
247+
window.location.reload();
248+
}
249+
});
109250

110-
// Optional: Add a little random rotation for flair
111-
particle.setAngle(Phaser.Math.Between(0, 360));
251+
cursors = scene.input.keyboard.createCursorKeys();
252+
}
253+
254+
function moveAliens() {
255+
let hitEdge = false;
256+
const padding = 60;
257+
const children = aliens.getChildren();
258+
259+
children.forEach((alien) => {
260+
if (this.alienDirection === 1 && alien.x > window.innerWidth - padding)
261+
hitEdge = true;
262+
if (this.alienDirection === -1 && alien.x < padding) hitEdge = true;
263+
});
264+
265+
if (hitEdge) {
266+
this.alienDirection *= -1;
267+
children.forEach((alien) => {
268+
alien.y += 40;
269+
alien.x += this.alienDirection * 10;
270+
});
271+
} else {
272+
children.forEach((alien) => {
273+
alien.x += 25 * this.alienDirection;
274+
});
112275
}
113276
}
277+
function fireBullet(scene) {
278+
const now = scene.time.now;
279+
if (now - lastFired < 400) return;
280+
281+
// 1. Create the bullet slightly above the player's center
282+
const bullet = scene.add.text(
283+
player.x + player.width / 2 - 10,
284+
player.y - 20,
285+
"🔥",
286+
{
287+
fontSize: "20px",
288+
},
289+
);
290+
291+
// 2. Add to physics and the group
292+
scene.physics.add.existing(bullet);
293+
bullets.add(bullet);
294+
295+
// 3. FAIL-SAFES
296+
bullet.body.setAllowGravity(false); // Ensure gravity isn't pulling it down
297+
bullet.body.setImmovable(false); // Ensure it's allowed to move
298+
bullet.body.setVelocityY(-600); // Set the upward speed
299+
300+
// 4. Force a sync between the physics body and the Text object
301+
bullet.body.isCircle = true; // Often helps with collision detection for small objects
302+
303+
lastFired = now;
304+
}

0 commit comments

Comments
 (0)