dopaminejs 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +71 -0
- package/CHANGELOG.md +36 -0
- package/README.md +73 -0
- package/dist/dopamine.mjs +360 -283
- package/dist/dopamine.umd.js +12 -12
- package/package.json +4 -2
- package/src/audio/SoundManager.js +118 -12
- package/src/effects/ParticleSystem.js +201 -131
package/dist/dopamine.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
class
|
|
1
|
+
class g {
|
|
2
2
|
constructor() {
|
|
3
3
|
this.listeners = /* @__PURE__ */ new Map();
|
|
4
4
|
}
|
|
@@ -8,28 +8,28 @@ class p {
|
|
|
8
8
|
* @param {Function} callback - Callback function
|
|
9
9
|
* @returns {Function} Unsubscribe function
|
|
10
10
|
*/
|
|
11
|
-
on(
|
|
12
|
-
return this.listeners.has(
|
|
11
|
+
on(e, t) {
|
|
12
|
+
return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), () => this.off(e, t);
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Unsubscribe from an event
|
|
16
16
|
* @param {string} event - Event name
|
|
17
17
|
* @param {Function} callback - Callback function
|
|
18
18
|
*/
|
|
19
|
-
off(
|
|
20
|
-
this.listeners.has(
|
|
19
|
+
off(e, t) {
|
|
20
|
+
this.listeners.has(e) && this.listeners.get(e).delete(t);
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Emit an event
|
|
24
24
|
* @param {string} event - Event name
|
|
25
25
|
* @param {*} data - Event data
|
|
26
26
|
*/
|
|
27
|
-
emit(
|
|
28
|
-
this.listeners.has(
|
|
27
|
+
emit(e, t) {
|
|
28
|
+
this.listeners.has(e) && this.listeners.get(e).forEach((s) => {
|
|
29
29
|
try {
|
|
30
|
-
|
|
31
|
-
} catch (
|
|
32
|
-
console.error(`Error in listener for event '${
|
|
30
|
+
s(t);
|
|
31
|
+
} catch (i) {
|
|
32
|
+
console.error(`Error in listener for event '${e}':`, i);
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
}
|
|
@@ -40,9 +40,9 @@ class p {
|
|
|
40
40
|
this.listeners.clear();
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
class
|
|
44
|
-
constructor(
|
|
45
|
-
super(), this.dataService =
|
|
43
|
+
class x extends g {
|
|
44
|
+
constructor(e, t = {}) {
|
|
45
|
+
super(), this.dataService = e, this.player = null, this.achievements = { ...this._initAchievements(), ...t.achievements || {} };
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Initialize the reward system for current player
|
|
@@ -86,28 +86,28 @@ class m extends p {
|
|
|
86
86
|
* @param {string} reason - Why XP was awarded (for notifications)
|
|
87
87
|
* @returns {Object} { leveledUp: boolean, newLevel: number, xpGained: number }
|
|
88
88
|
*/
|
|
89
|
-
async addXP(
|
|
90
|
-
const
|
|
91
|
-
this.player.xp +=
|
|
92
|
-
const
|
|
93
|
-
return a && (this.player.level =
|
|
89
|
+
async addXP(e, t = "") {
|
|
90
|
+
const s = this.player.level;
|
|
91
|
+
this.player.xp += e;
|
|
92
|
+
const i = this._calculateLevel(this.player.xp), a = i > s;
|
|
93
|
+
return a && (this.player.level = i), await this.save(), this.emit("xp_gained", { amount: e, reason: t, leveledUp: a, newLevel: i }), a && this.emit("level_up", { oldLevel: s, newLevel: i }), { leveledUp: a, newLevel: i, xpGained: e };
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
96
|
* Calculate level from XP (exponential curve)
|
|
97
97
|
*/
|
|
98
|
-
_calculateLevel(
|
|
99
|
-
const
|
|
100
|
-
return Math.max(1,
|
|
98
|
+
_calculateLevel(e) {
|
|
99
|
+
const t = Math.floor((1 + Math.sqrt(1 + 8 * e / 50)) / 2);
|
|
100
|
+
return Math.max(1, t);
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* Get XP needed for next level
|
|
104
104
|
*/
|
|
105
105
|
getXPForNextLevel() {
|
|
106
|
-
const
|
|
106
|
+
const e = this.player.level + 1, t = 50 * e * (e - 1), s = 50 * this.player.level * (this.player.level - 1);
|
|
107
107
|
return {
|
|
108
|
-
total:
|
|
109
|
-
needed:
|
|
110
|
-
progress: (this.player.xp -
|
|
108
|
+
total: t,
|
|
109
|
+
needed: t - this.player.xp,
|
|
110
|
+
progress: (this.player.xp - s) / (t - s)
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
@@ -115,74 +115,74 @@ class m extends p {
|
|
|
115
115
|
* @param {string} gameName - Unique ID for the game
|
|
116
116
|
* @param {Object} result - Game-specific result data
|
|
117
117
|
*/
|
|
118
|
-
async recordGame(
|
|
119
|
-
this.player.stats[
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
a !== "score" && (
|
|
118
|
+
async recordGame(e, t) {
|
|
119
|
+
this.player.stats[e] || (this.player.stats[e] = { totalPlays: 0, highScore: 0 });
|
|
120
|
+
const s = this.player.stats[e];
|
|
121
|
+
s.totalPlays++, this.player.totalGamesPlayed++, t.score !== void 0 && t.score > s.highScore && (s.highScore = t.score, this.emit("new_high_score", { gameName: e, score: t.score })), Object.keys(t).forEach((a) => {
|
|
122
|
+
a !== "score" && (s[a] = (s[a] || 0) + (typeof t[a] == "number" ? t[a] : 0));
|
|
123
123
|
});
|
|
124
|
-
let
|
|
125
|
-
|
|
124
|
+
let i = 10;
|
|
125
|
+
t.score && (i += Math.floor(t.score / 5)), i = Math.floor(i * this.getStreakMultiplier()), await this.addXP(i, `Played ${e}`), await this.checkAchievements(e, t), await this.save();
|
|
126
126
|
}
|
|
127
127
|
/**
|
|
128
128
|
* Check and unlock achievements
|
|
129
129
|
*/
|
|
130
|
-
async checkAchievements(
|
|
131
|
-
const
|
|
132
|
-
for (const [
|
|
133
|
-
this.player.achievements[
|
|
134
|
-
return
|
|
130
|
+
async checkAchievements(e, t) {
|
|
131
|
+
const s = [];
|
|
132
|
+
for (const [i, a] of Object.entries(this.achievements))
|
|
133
|
+
this.player.achievements[i] || a.check(this.player, e, t) && (await this.unlockAchievement(i), s.push(a));
|
|
134
|
+
return s;
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
137
|
* Unlock an achievement
|
|
138
138
|
*/
|
|
139
|
-
async unlockAchievement(
|
|
140
|
-
const
|
|
141
|
-
return
|
|
139
|
+
async unlockAchievement(e) {
|
|
140
|
+
const t = this.achievements[e];
|
|
141
|
+
return t ? (this.player.achievements[e] = {
|
|
142
142
|
unlockedAt: Date.now(),
|
|
143
143
|
seen: !1
|
|
144
|
-
}, await this.addXP(
|
|
144
|
+
}, await this.addXP(t.xp, `Achievement: ${t.name}`), this.emit("achievement_unlocked", t), await this.save(), !0) : !1;
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
147
147
|
* Get all unlocked achievements
|
|
148
148
|
*/
|
|
149
149
|
getUnlockedAchievements() {
|
|
150
|
-
return Object.entries(this.player.achievements).map(([
|
|
151
|
-
...this.achievements[
|
|
152
|
-
id:
|
|
153
|
-
...
|
|
150
|
+
return Object.entries(this.player.achievements).map(([e, t]) => ({
|
|
151
|
+
...this.achievements[e],
|
|
152
|
+
id: e,
|
|
153
|
+
...t
|
|
154
154
|
}));
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* Get achievements user hasn't seen the notification for yet
|
|
158
158
|
*/
|
|
159
159
|
getUnseenAchievements() {
|
|
160
|
-
return this.getUnlockedAchievements().filter((
|
|
160
|
+
return this.getUnlockedAchievements().filter((e) => !e.seen);
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
163
|
* Mark achievements as seen
|
|
164
164
|
*/
|
|
165
|
-
async markAchievementsSeen(
|
|
166
|
-
for (const
|
|
167
|
-
this.player.achievements[
|
|
165
|
+
async markAchievementsSeen(e) {
|
|
166
|
+
for (const t of e)
|
|
167
|
+
this.player.achievements[t] && (this.player.achievements[t].seen = !0);
|
|
168
168
|
await this.save();
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
171
|
* Update daily streak
|
|
172
172
|
*/
|
|
173
173
|
_updateDailyStreak() {
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
174
|
+
const e = this._getTodayDateString(), t = this.player.streak.lastPlayDate;
|
|
175
|
+
if (t === e)
|
|
176
176
|
return;
|
|
177
|
-
const
|
|
178
|
-
|
|
177
|
+
const s = this._getYesterdayDateString();
|
|
178
|
+
t === s ? (this.player.streak.current++, this.player.streak.current > this.player.streak.longest && (this.player.streak.longest = this.player.streak.current)) : this.player.streak.current = 1, this.player.streak.lastPlayDate = e;
|
|
179
179
|
}
|
|
180
180
|
/**
|
|
181
181
|
* Get streak multiplier for XP
|
|
182
182
|
*/
|
|
183
183
|
getStreakMultiplier() {
|
|
184
|
-
const
|
|
185
|
-
return
|
|
184
|
+
const e = this.player.streak.current;
|
|
185
|
+
return e >= 7 ? 2 : e >= 5 ? 1.5 : e >= 3 ? 1.25 : 1;
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
188
|
* Get today's date as YYYY-MM-DD string
|
|
@@ -194,8 +194,8 @@ class m extends p {
|
|
|
194
194
|
* Get yesterday's date as YYYY-MM-DD string
|
|
195
195
|
*/
|
|
196
196
|
_getYesterdayDateString() {
|
|
197
|
-
const
|
|
198
|
-
return
|
|
197
|
+
const e = /* @__PURE__ */ new Date();
|
|
198
|
+
return e.setDate(e.getDate() - 1), e.toISOString().split("T")[0];
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
201
201
|
* Initialize achievement definitions
|
|
@@ -209,54 +209,54 @@ class m extends p {
|
|
|
209
209
|
description: "Play your first game",
|
|
210
210
|
icon: "🎮",
|
|
211
211
|
xp: 50,
|
|
212
|
-
check: (
|
|
212
|
+
check: (e) => e.totalGamesPlayed >= 1
|
|
213
213
|
},
|
|
214
214
|
level_5: {
|
|
215
215
|
name: "Rising Star",
|
|
216
216
|
description: "Reach level 5",
|
|
217
217
|
icon: "⭐",
|
|
218
218
|
xp: 100,
|
|
219
|
-
check: (
|
|
219
|
+
check: (e) => e.level >= 5
|
|
220
220
|
},
|
|
221
221
|
level_10: {
|
|
222
222
|
name: "Expert Player",
|
|
223
223
|
description: "Reach level 10",
|
|
224
224
|
icon: "🏆",
|
|
225
225
|
xp: 250,
|
|
226
|
-
check: (
|
|
226
|
+
check: (e) => e.level >= 10
|
|
227
227
|
},
|
|
228
228
|
streak_3: {
|
|
229
229
|
name: "Dedicated",
|
|
230
230
|
description: "Play 3 days in a row",
|
|
231
231
|
icon: "🔥",
|
|
232
232
|
xp: 100,
|
|
233
|
-
check: (
|
|
233
|
+
check: (e) => e.streak.current >= 3
|
|
234
234
|
},
|
|
235
235
|
streak_7: {
|
|
236
236
|
name: "Unstoppable",
|
|
237
237
|
description: "Play 7 days in a row",
|
|
238
238
|
icon: "💥",
|
|
239
239
|
xp: 300,
|
|
240
|
-
check: (
|
|
240
|
+
check: (e) => e.streak.current >= 7
|
|
241
241
|
}
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
244
|
}
|
|
245
|
-
class
|
|
246
|
-
constructor(
|
|
247
|
-
this.storage =
|
|
245
|
+
class w {
|
|
246
|
+
constructor(e = {}) {
|
|
247
|
+
this.storage = e.storage || window.localStorage, this.prefix = e.prefix || "dopamine_";
|
|
248
248
|
}
|
|
249
249
|
/**
|
|
250
250
|
* Save data to local storage
|
|
251
251
|
* @param {string} key
|
|
252
252
|
* @param {any} data
|
|
253
253
|
*/
|
|
254
|
-
async save(
|
|
254
|
+
async save(e, t) {
|
|
255
255
|
try {
|
|
256
|
-
const
|
|
257
|
-
return this.storage.setItem(this.prefix +
|
|
258
|
-
} catch (
|
|
259
|
-
return console.error("Error saving data:",
|
|
256
|
+
const s = JSON.stringify(t);
|
|
257
|
+
return this.storage.setItem(this.prefix + e, s), !0;
|
|
258
|
+
} catch (s) {
|
|
259
|
+
return console.error("Error saving data:", s), !1;
|
|
260
260
|
}
|
|
261
261
|
}
|
|
262
262
|
/**
|
|
@@ -264,25 +264,25 @@ class y {
|
|
|
264
264
|
* @param {string} key
|
|
265
265
|
* @param {any} defaultValue
|
|
266
266
|
*/
|
|
267
|
-
async load(
|
|
267
|
+
async load(e, t = null) {
|
|
268
268
|
try {
|
|
269
|
-
const
|
|
270
|
-
return
|
|
271
|
-
} catch (
|
|
272
|
-
return console.error("Error loading data:",
|
|
269
|
+
const s = this.storage.getItem(this.prefix + e);
|
|
270
|
+
return s ? JSON.parse(s) : t;
|
|
271
|
+
} catch (s) {
|
|
272
|
+
return console.error("Error loading data:", s), t;
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
/**
|
|
276
276
|
* Clear specific data
|
|
277
277
|
* @param {string} key
|
|
278
278
|
*/
|
|
279
|
-
async clear(
|
|
280
|
-
this.storage.removeItem(this.prefix +
|
|
279
|
+
async clear(e) {
|
|
280
|
+
this.storage.removeItem(this.prefix + e);
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
-
class
|
|
284
|
-
constructor(
|
|
285
|
-
this.particleSystem =
|
|
283
|
+
class S {
|
|
284
|
+
constructor(e) {
|
|
285
|
+
this.particleSystem = e, this.container = null, this.xpBar = null, this.levelDisplay = null, this.streakDisplay = null, this.init();
|
|
286
286
|
}
|
|
287
287
|
/**
|
|
288
288
|
* Initialize UI overlay
|
|
@@ -312,234 +312,248 @@ class v {
|
|
|
312
312
|
/**
|
|
313
313
|
* Update XP bar
|
|
314
314
|
*/
|
|
315
|
-
updateXP(
|
|
316
|
-
const
|
|
317
|
-
this.xpBar.style.width = `${Math.min(100,
|
|
315
|
+
updateXP(e, t, s) {
|
|
316
|
+
const i = e / s * 100;
|
|
317
|
+
this.xpBar.style.width = `${Math.min(100, i)}%`, this.xpText.textContent = `${e} / ${s} XP`, this.xpBar.style.animation = "none", setTimeout(() => {
|
|
318
318
|
this.xpBar.style.animation = "xp-pulse 0.3s ease-out";
|
|
319
319
|
}, 10);
|
|
320
320
|
}
|
|
321
321
|
/**
|
|
322
322
|
* Update level display
|
|
323
323
|
*/
|
|
324
|
-
updateLevel(
|
|
325
|
-
this.levelDisplay.textContent =
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
324
|
+
updateLevel(e) {
|
|
325
|
+
this.levelDisplay.textContent = e;
|
|
326
|
+
const t = document.getElementById("level-badge");
|
|
327
|
+
t.style.animation = "none", setTimeout(() => {
|
|
328
|
+
t.style.animation = "level-pulse 0.5s ease-out";
|
|
329
329
|
}, 10);
|
|
330
330
|
}
|
|
331
331
|
/**
|
|
332
332
|
* Update streak display
|
|
333
333
|
*/
|
|
334
|
-
updateStreak(
|
|
335
|
-
this.streakDisplay.textContent =
|
|
336
|
-
const
|
|
337
|
-
|
|
334
|
+
updateStreak(e) {
|
|
335
|
+
this.streakDisplay.textContent = e;
|
|
336
|
+
const t = document.getElementById("streak-badge");
|
|
337
|
+
e >= 7 ? t.style.background = "linear-gradient(135deg, #ff6b00 0%, #ff4400 100%)" : e >= 3 && (t.style.background = "linear-gradient(135deg, #ff8800 0%, #ff6b00 100%)");
|
|
338
338
|
}
|
|
339
339
|
/**
|
|
340
340
|
* Show achievement unlocked popup
|
|
341
341
|
*/
|
|
342
|
-
showAchievement(
|
|
343
|
-
const
|
|
344
|
-
|
|
342
|
+
showAchievement(e) {
|
|
343
|
+
const t = document.createElement("div");
|
|
344
|
+
t.className = "achievement-popup", t.innerHTML = `
|
|
345
345
|
<div class="achievement-shine"></div>
|
|
346
346
|
<div class="achievement-content">
|
|
347
|
-
<div class="achievement-icon">${
|
|
347
|
+
<div class="achievement-icon">${e.icon}</div>
|
|
348
348
|
<div class="achievement-info">
|
|
349
349
|
<div class="achievement-title">Achievement Unlocked!</div>
|
|
350
|
-
<div class="achievement-name">${
|
|
351
|
-
<div class="achievement-desc">${
|
|
352
|
-
<div class="achievement-xp">+${
|
|
350
|
+
<div class="achievement-name">${e.name}</div>
|
|
351
|
+
<div class="achievement-desc">${e.description}</div>
|
|
352
|
+
<div class="achievement-xp">+${e.xp} XP</div>
|
|
353
353
|
</div>
|
|
354
354
|
</div>
|
|
355
|
-
`, document.body.appendChild(
|
|
356
|
-
const
|
|
357
|
-
this.particleSystem.confetti(
|
|
355
|
+
`, document.body.appendChild(t), setTimeout(() => {
|
|
356
|
+
const s = t.getBoundingClientRect();
|
|
357
|
+
this.particleSystem.confetti(s.left + s.width / 2, s.top + s.height / 2, 40);
|
|
358
358
|
}, 300), setTimeout(() => {
|
|
359
|
-
|
|
359
|
+
t.style.animation = "slideOut 0.3s ease-in forwards", setTimeout(() => t.remove(), 300);
|
|
360
360
|
}, 4e3);
|
|
361
361
|
}
|
|
362
362
|
/**
|
|
363
363
|
* Show level up screen
|
|
364
364
|
*/
|
|
365
|
-
showLevelUp(
|
|
366
|
-
const
|
|
367
|
-
|
|
365
|
+
showLevelUp(e, t) {
|
|
366
|
+
const s = document.createElement("div");
|
|
367
|
+
s.className = "level-up-overlay", s.innerHTML = `
|
|
368
368
|
<div class="level-up-content">
|
|
369
369
|
<div class="level-up-title">LEVEL UP!</div>
|
|
370
|
-
<div class="level-up-number">${
|
|
370
|
+
<div class="level-up-number">${t}</div>
|
|
371
371
|
<div class="level-up-subtitle">Amazing progress!</div>
|
|
372
372
|
</div>
|
|
373
|
-
`, document.body.appendChild(
|
|
374
|
-
for (let
|
|
373
|
+
`, document.body.appendChild(s), setTimeout(() => {
|
|
374
|
+
for (let i = 0; i < 5; i++)
|
|
375
375
|
setTimeout(() => {
|
|
376
376
|
const a = Math.random() * window.innerWidth, n = Math.random() * window.innerHeight * 0.6;
|
|
377
377
|
this.particleSystem.confetti(a, n, 30);
|
|
378
|
-
},
|
|
378
|
+
}, i * 200);
|
|
379
379
|
}, 300), setTimeout(() => {
|
|
380
|
-
|
|
380
|
+
s.style.opacity = "0", setTimeout(() => s.remove(), 500);
|
|
381
381
|
}, 2500);
|
|
382
382
|
}
|
|
383
383
|
/**
|
|
384
384
|
* Show floating text (e.g., "+50 XP", "COMBO!")
|
|
385
385
|
*/
|
|
386
|
-
showFloatingText(t,
|
|
386
|
+
showFloatingText(e, t, s, i = "#ffd700", a = "24px") {
|
|
387
387
|
const n = document.createElement("div");
|
|
388
|
-
n.className = "floating-text", n.textContent =
|
|
388
|
+
n.className = "floating-text", n.textContent = e, n.style.left = t + "px", n.style.top = s + "px", n.style.color = i, n.style.fontSize = a, document.body.appendChild(n), setTimeout(() => n.remove(), 2e3);
|
|
389
389
|
}
|
|
390
390
|
/**
|
|
391
391
|
* Show combo multiplier
|
|
392
392
|
*/
|
|
393
|
-
showCombo(
|
|
394
|
-
this.showFloatingText(`${
|
|
393
|
+
showCombo(e, t, s) {
|
|
394
|
+
this.showFloatingText(`${e}× COMBO!`, t, s, "#ff6b6b", "32px"), this.particleSystem.fire(t, s, 20);
|
|
395
395
|
}
|
|
396
396
|
/**
|
|
397
397
|
* Show "near miss"indicator
|
|
398
398
|
*/
|
|
399
|
-
showNearMiss(
|
|
400
|
-
this.showFloatingText("CLOSE CALL!",
|
|
399
|
+
showNearMiss(e, t) {
|
|
400
|
+
this.showFloatingText("CLOSE CALL!", e, t, "#45b7d1", "20px"), this.particleSystem.sparkle(e, t, 15, "#45b7d1");
|
|
401
401
|
}
|
|
402
402
|
/**
|
|
403
403
|
* Show lucky moment (random multiplier)
|
|
404
404
|
*/
|
|
405
|
-
showLuckyMoment(
|
|
406
|
-
this.showFloatingText(`LUCKY ${
|
|
405
|
+
showLuckyMoment(e, t, s) {
|
|
406
|
+
this.showFloatingText(`LUCKY ${e}×!`, t, s, "#ffd700", "36px"), this.particleSystem.starBurst(t, s, 12), this.particleSystem.confetti(t, s, 25);
|
|
407
407
|
}
|
|
408
408
|
/**
|
|
409
409
|
* Show generic notification
|
|
410
410
|
*/
|
|
411
|
-
showNotification(
|
|
412
|
-
const
|
|
413
|
-
|
|
411
|
+
showNotification(e, t = "normal") {
|
|
412
|
+
const s = window.innerWidth / 2, i = window.innerHeight / 3;
|
|
413
|
+
t === "legendary" ? this.showLuckyMoment(e, s, i) : t === "rare" ? this.showCombo(e, s, i) : this.showFloatingText(e, s, i, "#fff", "24px");
|
|
414
414
|
}
|
|
415
415
|
/**
|
|
416
416
|
* Show Game Over Summary
|
|
417
417
|
*/
|
|
418
|
-
showSummary(
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
let
|
|
422
|
-
|
|
418
|
+
showSummary(e) {
|
|
419
|
+
const t = document.createElement("div");
|
|
420
|
+
t.className = "message-box", t.style.display = "flex", t.style.position = "absolute", t.style.top = "50%", t.style.left = "50%", t.style.transform = "translate(-50%, -50%)", t.style.zIndex = "1000";
|
|
421
|
+
let s = "";
|
|
422
|
+
e.metrics && (s = Object.entries(e.metrics).map(([i, a]) => `<div style="display:flex;justify-content:space-between;width:100%"><span>${i}:</span> <strong>${a}</strong></div>`).join("")), t.innerHTML = `
|
|
423
423
|
<h1>Game Over</h1>
|
|
424
|
-
<div style="font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;">${
|
|
424
|
+
<div style="font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;">${e.score}</div>
|
|
425
425
|
<div style="width: 100%; margin-bottom: 20px;">
|
|
426
|
-
${
|
|
426
|
+
${s}
|
|
427
427
|
</div>
|
|
428
428
|
<button class="btn" onclick="location.reload()">Play Again</button>
|
|
429
429
|
<button class="btn" onclick="window.location.href='index.html'" style="margin-top: 10px; background: #543847;">Exit</button>
|
|
430
|
-
`, document.body.appendChild(
|
|
430
|
+
`, document.body.appendChild(t);
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
|
-
class
|
|
434
|
-
constructor(
|
|
435
|
-
this.canvasId =
|
|
433
|
+
class b {
|
|
434
|
+
constructor(e = {}) {
|
|
435
|
+
this.container = e.container ? typeof e.container == "string" ? document.querySelector(e.container) : e.container : document.body, this.canvasId = e.canvasId || "dopamine-particle-canvas", this.canvas = document.getElementById(this.canvasId), this.canvas || (this.canvas = document.createElement("canvas"), this.canvas.id = this.canvasId, this.canvas.style.pointerEvents = "none", this.canvas.style.zIndex = e.zIndex || "9999", this.container === document.body ? (this.canvas.style.position = "fixed", this.canvas.style.top = "0", this.canvas.style.left = "0") : (this.canvas.style.position = "absolute", this.canvas.style.top = "0", this.canvas.style.left = "0", window.getComputedStyle(this.container).position === "static" && (this.container.style.position = "relative")), this.container.appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d"), this.particles = [], this.pool = [], this.sprites = /* @__PURE__ */ new Map(), this.customEffects = /* @__PURE__ */ new Map(), this.isAnimating = !1, this._resize(), window.ResizeObserver && this.container !== document.body ? new ResizeObserver(() => this._resize()).observe(this.container) : window.addEventListener("resize", () => this._resize());
|
|
436
436
|
}
|
|
437
437
|
_resize() {
|
|
438
|
-
this.canvas.width = window.innerWidth, this.canvas.height = window.innerHeight;
|
|
439
|
-
}
|
|
440
|
-
/**
|
|
441
|
-
* Confetti explosion (for achievements, level ups)
|
|
442
|
-
*/
|
|
443
|
-
confetti(t, e, i = 50) {
|
|
444
|
-
const s = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#f7dc6f", "#c06c84", "#6c5ce7"];
|
|
445
|
-
for (let a = 0; a < i; a++)
|
|
446
|
-
this.particles.push({
|
|
447
|
-
x: t,
|
|
448
|
-
y: e,
|
|
449
|
-
vx: (Math.random() - 0.5) * 10,
|
|
450
|
-
vy: (Math.random() - 0.5) * 10 - 5,
|
|
451
|
-
rotation: Math.random() * Math.PI * 2,
|
|
452
|
-
rotationSpeed: (Math.random() - 0.5) * 0.2,
|
|
453
|
-
color: s[Math.floor(Math.random() * s.length)],
|
|
454
|
-
size: Math.random() * 8 + 4,
|
|
455
|
-
life: 1,
|
|
456
|
-
decay: 0.015 + Math.random() * 0.01,
|
|
457
|
-
gravity: 0.3,
|
|
458
|
-
type: "confetti"
|
|
459
|
-
});
|
|
460
|
-
this._startAnimation();
|
|
438
|
+
this.container === document.body ? (this.canvas.width = window.innerWidth, this.canvas.height = window.innerHeight) : (this.canvas.width = this.container.clientWidth, this.canvas.height = this.container.clientHeight);
|
|
461
439
|
}
|
|
462
440
|
/**
|
|
463
|
-
*
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
y: e,
|
|
471
|
-
vx: Math.cos(n) * r,
|
|
472
|
-
vy: Math.sin(n) * r,
|
|
473
|
-
color: s,
|
|
474
|
-
size: Math.random() * 4 + 2,
|
|
475
|
-
life: 1,
|
|
476
|
-
decay: 0.02,
|
|
477
|
-
gravity: -0.1,
|
|
478
|
-
// Slight upward drift
|
|
479
|
-
type: "spark"
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
this._startAnimation();
|
|
441
|
+
* Register a custom sprite image
|
|
442
|
+
* @param {string} key
|
|
443
|
+
* @param {string} url
|
|
444
|
+
*/
|
|
445
|
+
registerSprite(e, t) {
|
|
446
|
+
const s = new Image();
|
|
447
|
+
s.src = t, this.sprites.set(e, s);
|
|
483
448
|
}
|
|
484
449
|
/**
|
|
485
|
-
*
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
y: e - Math.random() * 50,
|
|
492
|
-
vx: (Math.random() - 0.5) * 3,
|
|
493
|
-
vy: Math.random() * -2 - 3,
|
|
494
|
-
color: "#ffd700",
|
|
495
|
-
size: 12,
|
|
496
|
-
life: 1,
|
|
497
|
-
decay: 0.012,
|
|
498
|
-
gravity: 0.4,
|
|
499
|
-
type: "coin"
|
|
500
|
-
});
|
|
501
|
-
this._startAnimation();
|
|
450
|
+
* Register a custom effect
|
|
451
|
+
* @param {string} name
|
|
452
|
+
* @param {Function} callback - (x, y, ...args) => void
|
|
453
|
+
*/
|
|
454
|
+
registerEffect(e, t) {
|
|
455
|
+
this.customEffects.set(e, t);
|
|
502
456
|
}
|
|
503
457
|
/**
|
|
504
|
-
*
|
|
505
|
-
*/
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
this.particles.push({
|
|
510
|
-
x: t + (Math.random() - 0.5) * 20,
|
|
511
|
-
y: e,
|
|
512
|
-
vx: (Math.random() - 0.5) * 2,
|
|
513
|
-
vy: -Math.random() * 3 - 2,
|
|
514
|
-
color: s[Math.floor(Math.random() * s.length)],
|
|
515
|
-
size: Math.random() * 6 + 3,
|
|
516
|
-
life: 1,
|
|
517
|
-
decay: 0.025,
|
|
518
|
-
gravity: -0.2,
|
|
519
|
-
// Upward
|
|
520
|
-
type: "fire"
|
|
521
|
-
});
|
|
522
|
-
this._startAnimation();
|
|
458
|
+
* Play a registered effect
|
|
459
|
+
*/
|
|
460
|
+
play(e, t, s, ...i) {
|
|
461
|
+
const a = this.customEffects.get(e);
|
|
462
|
+
a ? a(t, s, ...i) : console.warn(`[DopamineJS] Effect '${e}' not found.`);
|
|
523
463
|
}
|
|
524
464
|
/**
|
|
525
|
-
*
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
465
|
+
* Emit particles with custom config
|
|
466
|
+
* @param {Object} config
|
|
467
|
+
*/
|
|
468
|
+
emit(e) {
|
|
469
|
+
const {
|
|
470
|
+
x: t,
|
|
471
|
+
y: s,
|
|
472
|
+
count: i = 10,
|
|
473
|
+
speed: a = 5,
|
|
474
|
+
gravity: n = 0.1,
|
|
475
|
+
decay: o = 0.02,
|
|
476
|
+
life: h = 1,
|
|
477
|
+
color: l = "#ffd700",
|
|
478
|
+
sprite: d = null,
|
|
479
|
+
spread: y = Math.PI * 2,
|
|
480
|
+
angle: v = 0,
|
|
481
|
+
size: f = 5
|
|
482
|
+
} = e;
|
|
483
|
+
for (let p = 0; p < i; p++) {
|
|
484
|
+
const r = this._getParticle(), u = v + (Math.random() - 0.5) * y, m = Math.random() * a;
|
|
485
|
+
r.x = t, r.y = s, r.vx = Math.cos(u) * m, r.vy = Math.sin(u) * m, r.life = h, r.decay = o * (0.8 + Math.random() * 0.4), r.gravity = n, r.color = Array.isArray(l) ? l[Math.floor(Math.random() * l.length)] : l, r.size = f * (0.8 + Math.random() * 0.4), r.sprite = d, r.rotation = Math.random() * Math.PI * 2, r.rotationSpeed = (Math.random() - 0.5) * 0.2, this.particles.push(r);
|
|
486
|
+
}
|
|
487
|
+
this._startAnimation();
|
|
488
|
+
}
|
|
489
|
+
_getParticle() {
|
|
490
|
+
return this.pool.length > 0 ? this.pool.pop() : {};
|
|
491
|
+
}
|
|
492
|
+
_recycleParticle(e) {
|
|
493
|
+
this.pool.push(e);
|
|
494
|
+
}
|
|
495
|
+
// --- Built-in Effects ---
|
|
496
|
+
confetti(e, t, s = 50) {
|
|
497
|
+
const i = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#f7dc6f", "#c06c84", "#6c5ce7"];
|
|
498
|
+
this.emit({
|
|
499
|
+
x: e,
|
|
500
|
+
y: t,
|
|
501
|
+
count: s,
|
|
502
|
+
color: i,
|
|
503
|
+
speed: 10,
|
|
504
|
+
gravity: 0.3,
|
|
505
|
+
decay: 0.015,
|
|
506
|
+
size: 8,
|
|
507
|
+
spread: Math.PI * 2
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
coinShower(e, t, s = 10) {
|
|
511
|
+
this.emit({
|
|
512
|
+
x: e,
|
|
513
|
+
y: t,
|
|
514
|
+
count: s,
|
|
515
|
+
color: "#ffd700",
|
|
516
|
+
speed: 4,
|
|
517
|
+
gravity: 0.4,
|
|
518
|
+
decay: 0.012,
|
|
519
|
+
size: 12,
|
|
520
|
+
angle: -Math.PI / 2,
|
|
521
|
+
// Upwards initially
|
|
522
|
+
spread: Math.PI / 2
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
sparkle(e, t, s = 20, i = "#ffd700") {
|
|
526
|
+
this.emit({
|
|
527
|
+
x: e,
|
|
528
|
+
y: t,
|
|
529
|
+
count: s,
|
|
530
|
+
color: i,
|
|
531
|
+
speed: 3,
|
|
532
|
+
gravity: -0.05,
|
|
533
|
+
// Float up
|
|
534
|
+
decay: 0.03,
|
|
535
|
+
size: 3
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
fire(e, t, s = 15) {
|
|
539
|
+
const i = ["#ff6b00", "#ff8800", "#ffaa00", "#ff4400"];
|
|
540
|
+
this.emit({
|
|
541
|
+
x: e,
|
|
542
|
+
y: t,
|
|
543
|
+
count: s,
|
|
544
|
+
color: i,
|
|
545
|
+
speed: 3,
|
|
546
|
+
gravity: -0.2,
|
|
547
|
+
decay: 0.04,
|
|
548
|
+
size: 5,
|
|
549
|
+
angle: -Math.PI / 2,
|
|
550
|
+
spread: Math.PI / 4
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
starBurst(e, t, s = 8) {
|
|
554
|
+
for (let i = 0; i < s; i++) {
|
|
555
|
+
const a = this._getParticle(), n = Math.PI * 2 / s * i, o = 4;
|
|
556
|
+
a.x = e, a.y = t, a.vx = Math.cos(n) * o, a.vy = Math.sin(n) * o, a.life = 1, a.decay = 0.03, a.gravity = 0, a.color = "#ffffff", a.size = 15, a.sprite = null, a.type = "star", a.rotation = n, a.rotationSpeed = 0, this.particles.push(a);
|
|
543
557
|
}
|
|
544
558
|
this._startAnimation();
|
|
545
559
|
}
|
|
@@ -548,42 +562,45 @@ class u {
|
|
|
548
562
|
}
|
|
549
563
|
_animate() {
|
|
550
564
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
551
|
-
for (let
|
|
552
|
-
const
|
|
553
|
-
if (
|
|
554
|
-
this.particles.splice(
|
|
565
|
+
for (let e = this.particles.length - 1; e >= 0; e--) {
|
|
566
|
+
const t = this.particles[e];
|
|
567
|
+
if (t.vy += t.gravity, t.x += t.vx, t.y += t.vy, t.life -= t.decay, t.rotationSpeed && (t.rotation += t.rotationSpeed), t.life <= 0) {
|
|
568
|
+
this._recycleParticle(t), this.particles.splice(e, 1);
|
|
555
569
|
continue;
|
|
556
570
|
}
|
|
557
|
-
this.ctx.save(), this.ctx.globalAlpha =
|
|
571
|
+
if (this.ctx.save(), this.ctx.globalAlpha = t.life, this.ctx.translate(t.x, t.y), this.ctx.rotate(t.rotation), t.sprite && this.sprites.has(t.sprite)) {
|
|
572
|
+
const s = this.sprites.get(t.sprite);
|
|
573
|
+
s.complete && this.ctx.drawImage(s, -t.size / 2, -t.size / 2, t.size, t.size);
|
|
574
|
+
} else t.type === "star" ? (this.ctx.fillStyle = t.color, this._drawStarPath(0, 0, 5, t.size, t.size / 2), this.ctx.fill()) : (this.ctx.fillStyle = t.color, this.ctx.beginPath(), this.ctx.arc(0, 0, t.size, 0, Math.PI * 2), this.ctx.fill());
|
|
575
|
+
this.ctx.restore();
|
|
558
576
|
}
|
|
559
577
|
this.particles.length > 0 ? requestAnimationFrame(() => this._animate()) : this.isAnimating = !1;
|
|
560
578
|
}
|
|
561
|
-
|
|
562
|
-
let
|
|
563
|
-
const
|
|
564
|
-
this.ctx.beginPath(), this.ctx.moveTo(
|
|
565
|
-
for (let d = 0; d <
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
this.ctx.lineTo(t, e - s), this.ctx.closePath(), this.ctx.fill();
|
|
579
|
+
_drawStarPath(e, t, s, i, a) {
|
|
580
|
+
let n = Math.PI / 2 * 3, o = e, h = t;
|
|
581
|
+
const l = Math.PI / s;
|
|
582
|
+
this.ctx.beginPath(), this.ctx.moveTo(e, t - i);
|
|
583
|
+
for (let d = 0; d < s; d++)
|
|
584
|
+
o = e + Math.cos(n) * i, h = t + Math.sin(n) * i, this.ctx.lineTo(o, h), n += l, o = e + Math.cos(n) * a, h = t + Math.sin(n) * a, this.ctx.lineTo(o, h), n += l;
|
|
585
|
+
this.ctx.lineTo(e, t - i), this.ctx.closePath();
|
|
570
586
|
}
|
|
571
|
-
/**
|
|
572
|
-
* Clear all particles
|
|
573
|
-
*/
|
|
574
587
|
clear() {
|
|
575
|
-
this.particles = [], this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
588
|
+
this.particles.forEach((e) => this._recycleParticle(e)), this.particles = [], this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
576
589
|
}
|
|
577
590
|
}
|
|
578
|
-
class
|
|
579
|
-
constructor(
|
|
580
|
-
this.audioContext = null, this.storageKey =
|
|
591
|
+
class P {
|
|
592
|
+
constructor(e = {}) {
|
|
593
|
+
this.audioContext = null, this.storageKey = e.storageKey || "dopamineSoundsMuted", this.muted = localStorage.getItem(this.storageKey) === "true", this.assets = /* @__PURE__ */ new Map(), this.soundPack = e.soundPack || "synth", this.customSounds = e.customSounds || {}, Object.keys(this.customSounds).length > 0 && this.preloadSounds(this.customSounds);
|
|
581
594
|
}
|
|
582
595
|
/**
|
|
583
596
|
* Initialize Audio Context (must be called after user interaction)
|
|
584
597
|
*/
|
|
585
598
|
initAudio() {
|
|
586
|
-
|
|
599
|
+
if (!this.audioContext) {
|
|
600
|
+
const e = window.AudioContext || window.webkitAudioContext;
|
|
601
|
+
this.audioContext = new e();
|
|
602
|
+
}
|
|
603
|
+
this.audioContext.state === "suspended" && this.audioContext.resume();
|
|
587
604
|
}
|
|
588
605
|
/**
|
|
589
606
|
* Toggle mute state
|
|
@@ -592,42 +609,102 @@ class f {
|
|
|
592
609
|
toggleMute() {
|
|
593
610
|
return this.muted = !this.muted, localStorage.setItem(this.storageKey, this.muted), this.muted;
|
|
594
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Register a custom sound URL
|
|
614
|
+
* @param {string} key - Unique identifier (e.g., 'jump', 'win')
|
|
615
|
+
* @param {string} url - Path to audio file
|
|
616
|
+
*/
|
|
617
|
+
registerSound(e, t) {
|
|
618
|
+
this.customSounds[e] = t, this.audioContext && this.loadSound(e, t);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Preload multiple sounds
|
|
622
|
+
* @param {Object} soundMap - { key: url }
|
|
623
|
+
*/
|
|
624
|
+
async preloadSounds(e) {
|
|
625
|
+
const t = Object.entries(e).map(([s, i]) => this.loadSound(s, i));
|
|
626
|
+
await Promise.allSettled(t);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Load and decode a sound file
|
|
630
|
+
*/
|
|
631
|
+
async loadSound(e, t) {
|
|
632
|
+
try {
|
|
633
|
+
if (!this.audioContext)
|
|
634
|
+
return;
|
|
635
|
+
const i = await (await fetch(t)).arrayBuffer(), a = await this.audioContext.decodeAudioData(i);
|
|
636
|
+
this.assets.set(e, a);
|
|
637
|
+
} catch (s) {
|
|
638
|
+
console.warn(`[DopamineJS] Failed to load sound '${e}':`, s);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Play a sound by key (custom) or fallback to synth
|
|
643
|
+
* @param {string} key - 'jump', 'score', 'success', etc.
|
|
644
|
+
*/
|
|
645
|
+
async play(e) {
|
|
646
|
+
if (this.muted) return;
|
|
647
|
+
if (this.initAudio(), this.assets.has(e)) {
|
|
648
|
+
this._playBuffer(this.assets.get(e));
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (this.customSounds[e] && !this.assets.has(e) && (await this.loadSound(e, this.customSounds[e]), this.assets.has(e))) {
|
|
652
|
+
this._playBuffer(this.assets.get(e));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const t = {
|
|
656
|
+
jump: () => this.playJump(!0),
|
|
657
|
+
// true = force synth
|
|
658
|
+
score: () => this.playScore(!0),
|
|
659
|
+
success: () => this.playSuccess(!0),
|
|
660
|
+
click: () => this.playClick(!0),
|
|
661
|
+
error: () => this.playError(!0),
|
|
662
|
+
gameover: () => this.playGameOver(!0)
|
|
663
|
+
};
|
|
664
|
+
t[e] && t[e]();
|
|
665
|
+
}
|
|
666
|
+
_playBuffer(e) {
|
|
667
|
+
const t = this.audioContext.createBufferSource();
|
|
668
|
+
t.buffer = e, t.connect(this.audioContext.destination), t.start(0);
|
|
669
|
+
}
|
|
595
670
|
/**
|
|
596
671
|
* Play a synthesized tone
|
|
597
|
-
* @param {number} frequency
|
|
598
|
-
* @param {number} duration
|
|
599
|
-
* @param {string} type - 'sine', 'square', 'sawtooth', 'triangle'
|
|
600
|
-
* @param {number} volume
|
|
601
672
|
*/
|
|
602
|
-
playTone(
|
|
673
|
+
playTone(e, t, s = "sine", i = 0.3) {
|
|
603
674
|
if (this.muted) return;
|
|
604
675
|
this.initAudio();
|
|
605
676
|
const a = this.audioContext.createOscillator(), n = this.audioContext.createGain();
|
|
606
|
-
a.connect(n), n.connect(this.audioContext.destination), a.frequency.value =
|
|
677
|
+
a.connect(n), n.connect(this.audioContext.destination), a.frequency.value = e, a.type = s, n.gain.setValueAtTime(i, this.audioContext.currentTime), n.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + t), a.start(this.audioContext.currentTime), a.stop(this.audioContext.currentTime + t);
|
|
607
678
|
}
|
|
608
|
-
// --- Presets ---
|
|
609
|
-
playJump() {
|
|
679
|
+
// --- Presets (Synth) ---
|
|
680
|
+
playJump(e = !1) {
|
|
681
|
+
if (!e && this.assets.has("jump")) return this.play("jump");
|
|
610
682
|
this.playTone(400, 0.1, "square", 0.2);
|
|
611
683
|
}
|
|
612
|
-
playScore() {
|
|
684
|
+
playScore(e = !1) {
|
|
685
|
+
if (!e && this.assets.has("score")) return this.play("score");
|
|
613
686
|
this.playTone(800, 0.15, "sine", 0.25), setTimeout(() => this.playTone(1e3, 0.15, "sine", 0.25), 50);
|
|
614
687
|
}
|
|
615
|
-
playGameOver() {
|
|
688
|
+
playGameOver(e = !1) {
|
|
689
|
+
if (!e && this.assets.has("gameover")) return this.play("gameover");
|
|
616
690
|
this.playTone(300, 0.2, "sawtooth", 0.3), setTimeout(() => this.playTone(200, 0.2, "sawtooth", 0.3), 100), setTimeout(() => this.playTone(150, 0.3, "sawtooth", 0.3), 200);
|
|
617
691
|
}
|
|
618
|
-
playClick() {
|
|
692
|
+
playClick(e = !1) {
|
|
693
|
+
if (!e && this.assets.has("click")) return this.play("click");
|
|
619
694
|
this.playTone(600, 0.05, "sine", 0.1);
|
|
620
695
|
}
|
|
621
|
-
playSuccess() {
|
|
696
|
+
playSuccess(e = !1) {
|
|
697
|
+
if (!e && this.assets.has("success")) return this.play("success");
|
|
622
698
|
this.playTone(600, 0.1, "sine", 0.2), setTimeout(() => this.playTone(800, 0.2, "sine", 0.2), 100);
|
|
623
699
|
}
|
|
624
|
-
playError() {
|
|
700
|
+
playError(e = !1) {
|
|
701
|
+
if (!e && this.assets.has("error")) return this.play("error");
|
|
625
702
|
this.playTone(200, 0.2, "sawtooth", 0.3);
|
|
626
703
|
}
|
|
627
704
|
}
|
|
628
|
-
class
|
|
629
|
-
constructor(
|
|
630
|
-
this.config =
|
|
705
|
+
class M {
|
|
706
|
+
constructor(e = {}) {
|
|
707
|
+
this.config = e, this.dataService = new w(e.data || {}), this.rewardSystem = new x(this.dataService, e.rewards || {}), this.soundManager = new P(e.sound || {}), this.particleSystem = new b(e.particles || {}), this.gameUI = new S(this.particleSystem), this._bindEvents();
|
|
631
708
|
}
|
|
632
709
|
async init() {
|
|
633
710
|
return await this.rewardSystem.init(), console.log("DopamineJS Initialized 🚀"), {
|
|
@@ -638,23 +715,23 @@ class g {
|
|
|
638
715
|
};
|
|
639
716
|
}
|
|
640
717
|
_bindEvents() {
|
|
641
|
-
this.rewardSystem.on("xp_gained", (
|
|
642
|
-
const { total:
|
|
643
|
-
this.gameUI.updateXP(this.rewardSystem.player.xp,
|
|
644
|
-
}), this.rewardSystem.on("level_up", (
|
|
645
|
-
this.gameUI.updateLevel(
|
|
646
|
-
}), this.rewardSystem.on("achievement_unlocked", (
|
|
647
|
-
this.gameUI.showAchievement(
|
|
648
|
-
}), this.rewardSystem.on("new_high_score", (
|
|
649
|
-
this.gameUI.showNotification(`New High Score: ${
|
|
718
|
+
this.rewardSystem.on("xp_gained", (e) => {
|
|
719
|
+
const { total: t, needed: s } = this.rewardSystem.getXPForNextLevel();
|
|
720
|
+
this.gameUI.updateXP(this.rewardSystem.player.xp, s, t), e.xpGained > 0;
|
|
721
|
+
}), this.rewardSystem.on("level_up", (e) => {
|
|
722
|
+
this.gameUI.updateLevel(e.newLevel), this.gameUI.showLevelUp(e.oldLevel, e.newLevel);
|
|
723
|
+
}), this.rewardSystem.on("achievement_unlocked", (e) => {
|
|
724
|
+
this.gameUI.showAchievement(e);
|
|
725
|
+
}), this.rewardSystem.on("new_high_score", (e) => {
|
|
726
|
+
this.gameUI.showNotification(`New High Score: ${e.score}!`, "legendary"), this.soundManager.playSuccess();
|
|
650
727
|
});
|
|
651
728
|
}
|
|
652
729
|
}
|
|
653
730
|
export {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
731
|
+
w as DataService,
|
|
732
|
+
S as GameUI,
|
|
733
|
+
b as ParticleSystem,
|
|
734
|
+
x as RewardSystem,
|
|
735
|
+
P as SoundManager,
|
|
736
|
+
M as default
|
|
660
737
|
};
|