waengine 1.7.3 → 1.7.4

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.
@@ -0,0 +1,842 @@
1
+ import { getStorage } from "./storage.js";
2
+ import { ErrorHandler } from "./error-handler.js";
3
+
4
+ export class GamingManager {
5
+ constructor(client) {
6
+ this.client = client;
7
+ this.storage = getStorage();
8
+ this.errorHandler = new ErrorHandler();
9
+ this.activeGames = new Map();
10
+ this.gameTemplates = new Map();
11
+ this.playerStats = new Map();
12
+ this.leaderboards = new Map();
13
+ this.tournaments = new Map();
14
+
15
+ this.initializeGaming();
16
+ }
17
+
18
+ // ===== INITIALIZATION =====
19
+
20
+ initializeGaming() {
21
+ this.loadGameData();
22
+ this.registerDefaultGames();
23
+ this.startGameMaintenanceJob();
24
+ }
25
+
26
+ loadGameData() {
27
+ try {
28
+ const gameData = this.storage.read.from("gaming").get("data") || {};
29
+ this.playerStats = new Map(Object.entries(gameData.playerStats || {}));
30
+ this.leaderboards = new Map(Object.entries(gameData.leaderboards || {}));
31
+ this.tournaments = new Map(Object.entries(gameData.tournaments || {}));
32
+ } catch (error) {
33
+ this.errorHandler.handle(error, 'GamingManager.loadGameData');
34
+ }
35
+ }
36
+
37
+ registerDefaultGames() {
38
+ // Quiz Game
39
+ this.registerGame('quiz', {
40
+ name: 'Quiz Game',
41
+ description: 'Answer questions to earn points',
42
+ minPlayers: 1,
43
+ maxPlayers: 10,
44
+ duration: 300000, // 5 minutes
45
+ gameLogic: this.quizGameLogic.bind(this)
46
+ });
47
+
48
+ // Number Guessing Game
49
+ this.registerGame('guess', {
50
+ name: 'Number Guessing',
51
+ description: 'Guess the secret number',
52
+ minPlayers: 1,
53
+ maxPlayers: 5,
54
+ duration: 180000, // 3 minutes
55
+ gameLogic: this.guessGameLogic.bind(this)
56
+ });
57
+
58
+ // Word Chain Game
59
+ this.registerGame('wordchain', {
60
+ name: 'Word Chain',
61
+ description: 'Create a chain of words',
62
+ minPlayers: 2,
63
+ maxPlayers: 8,
64
+ duration: 600000, // 10 minutes
65
+ gameLogic: this.wordChainLogic.bind(this)
66
+ });
67
+
68
+ // Trivia Game
69
+ this.registerGame('trivia', {
70
+ name: 'Trivia Challenge',
71
+ description: 'Test your knowledge',
72
+ minPlayers: 1,
73
+ maxPlayers: 15,
74
+ duration: 420000, // 7 minutes
75
+ gameLogic: this.triviaGameLogic.bind(this)
76
+ });
77
+
78
+ // Math Challenge
79
+ this.registerGame('math', {
80
+ name: 'Math Challenge',
81
+ description: 'Solve math problems quickly',
82
+ minPlayers: 1,
83
+ maxPlayers: 6,
84
+ duration: 240000, // 4 minutes
85
+ gameLogic: this.mathGameLogic.bind(this)
86
+ });
87
+ }
88
+
89
+ startGameMaintenanceJob() {
90
+ setInterval(() => {
91
+ this.cleanupExpiredGames();
92
+ this.updateLeaderboards();
93
+ }, 60000); // Every minute
94
+ }
95
+
96
+ // ===== GAME MANAGEMENT =====
97
+
98
+ registerGame(gameId, gameTemplate) {
99
+ this.gameTemplates.set(gameId, {
100
+ id: gameId,
101
+ ...gameTemplate,
102
+ createdAt: Date.now()
103
+ });
104
+
105
+ console.log(`šŸŽ® Game registered: ${gameTemplate.name}`);
106
+ }
107
+
108
+ async startGame(chatId, gameId, options = {}) {
109
+ try {
110
+ const template = this.gameTemplates.get(gameId);
111
+ if (!template) {
112
+ throw new Error(`Game not found: ${gameId}`);
113
+ }
114
+
115
+ const gameInstanceId = this.generateGameId();
116
+ const game = {
117
+ id: gameInstanceId,
118
+ gameId,
119
+ chatId,
120
+ template,
121
+ players: new Map(),
122
+ state: 'waiting',
123
+ data: {},
124
+ startTime: null,
125
+ endTime: null,
126
+ winner: null,
127
+ scores: new Map(),
128
+ options: {
129
+ autoStart: options.autoStart || false,
130
+ maxWaitTime: options.maxWaitTime || 60000,
131
+ ...options
132
+ },
133
+ createdAt: Date.now()
134
+ };
135
+
136
+ this.activeGames.set(gameInstanceId, game);
137
+
138
+ await this.client.sendMessage(chatId,
139
+ `šŸŽ® *${template.name}* wurde gestartet!\n\n` +
140
+ `šŸ“ ${template.description}\n` +
141
+ `šŸ‘„ Spieler: ${template.minPlayers}-${template.maxPlayers}\n` +
142
+ `ā±ļø Dauer: ${Math.floor(template.duration / 60000)} Minuten\n\n` +
143
+ `Schreibe *!join* um mitzuspielen!`
144
+ );
145
+
146
+ // Auto-start timer if enabled
147
+ if (game.options.autoStart) {
148
+ setTimeout(() => {
149
+ this.forceStartGame(gameInstanceId);
150
+ }, game.options.maxWaitTime);
151
+ }
152
+
153
+ return gameInstanceId;
154
+ } catch (error) {
155
+ this.errorHandler.handle(error, 'GamingManager.startGame');
156
+ throw error;
157
+ }
158
+ }
159
+
160
+ async joinGame(chatId, playerId, playerName) {
161
+ try {
162
+ const game = this.findActiveGameByChat(chatId);
163
+ if (!game) {
164
+ await this.client.sendMessage(chatId, 'āŒ Kein aktives Spiel gefunden!');
165
+ return false;
166
+ }
167
+
168
+ if (game.state !== 'waiting') {
169
+ await this.client.sendMessage(chatId, 'āŒ Das Spiel hat bereits begonnen!');
170
+ return false;
171
+ }
172
+
173
+ if (game.players.has(playerId)) {
174
+ await this.client.sendMessage(chatId, 'āŒ Du bist bereits im Spiel!');
175
+ return false;
176
+ }
177
+
178
+ if (game.players.size >= game.template.maxPlayers) {
179
+ await this.client.sendMessage(chatId, 'āŒ Das Spiel ist bereits voll!');
180
+ return false;
181
+ }
182
+
183
+ game.players.set(playerId, {
184
+ id: playerId,
185
+ name: playerName,
186
+ score: 0,
187
+ joinedAt: Date.now(),
188
+ isActive: true
189
+ });
190
+
191
+ await this.client.sendMessage(chatId,
192
+ `āœ… ${playerName} ist dem Spiel beigetreten!\n` +
193
+ `šŸ‘„ Spieler: ${game.players.size}/${game.template.maxPlayers}\n\n` +
194
+ `${game.players.size >= game.template.minPlayers ?
195
+ 'Schreibe *!start* um das Spiel zu beginnen!' :
196
+ `Noch ${game.template.minPlayers - game.players.size} Spieler benƶtigt!`}`
197
+ );
198
+
199
+ return true;
200
+ } catch (error) {
201
+ this.errorHandler.handle(error, 'GamingManager.joinGame');
202
+ return false;
203
+ }
204
+ }
205
+
206
+ async forceStartGame(gameInstanceId) {
207
+ try {
208
+ const game = this.activeGames.get(gameInstanceId);
209
+ if (!game || game.state !== 'waiting') return false;
210
+
211
+ if (game.players.size < game.template.minPlayers) {
212
+ await this.client.sendMessage(game.chatId,
213
+ `āŒ Nicht genügend Spieler! Mindestens ${game.template.minPlayers} Spieler benƶtigt.`
214
+ );
215
+ this.endGame(gameInstanceId, 'cancelled');
216
+ return false;
217
+ }
218
+
219
+ game.state = 'active';
220
+ game.startTime = Date.now();
221
+
222
+ await this.client.sendMessage(game.chatId,
223
+ `šŸš€ *${game.template.name}* beginnt jetzt!\n\n` +
224
+ `šŸ‘„ Spieler: ${Array.from(game.players.values()).map(p => p.name).join(', ')}\n` +
225
+ `ā±ļø Zeit: ${Math.floor(game.template.duration / 60000)} Minuten`
226
+ );
227
+
228
+ // Start game logic
229
+ await game.template.gameLogic(game);
230
+
231
+ // Set end timer
232
+ setTimeout(() => {
233
+ this.endGame(gameInstanceId, 'timeout');
234
+ }, game.template.duration);
235
+
236
+ return true;
237
+ } catch (error) {
238
+ this.errorHandler.handle(error, 'GamingManager.forceStartGame');
239
+ return false;
240
+ }
241
+ }
242
+
243
+ async endGame(gameInstanceId, reason = 'completed') {
244
+ try {
245
+ const game = this.activeGames.get(gameInstanceId);
246
+ if (!game) return;
247
+
248
+ game.state = 'ended';
249
+ game.endTime = Date.now();
250
+
251
+ // Calculate final scores and winner
252
+ const sortedPlayers = Array.from(game.players.values())
253
+ .sort((a, b) => b.score - a.score);
254
+
255
+ if (sortedPlayers.length > 0) {
256
+ game.winner = sortedPlayers[0];
257
+ }
258
+
259
+ // Update player statistics
260
+ for (const player of game.players.values()) {
261
+ this.updatePlayerStats(player.id, game.gameId, player.score, player === game.winner);
262
+ }
263
+
264
+ // Send results
265
+ await this.sendGameResults(game, reason);
266
+
267
+ // Update leaderboards
268
+ this.updateGameLeaderboard(game.gameId, sortedPlayers);
269
+
270
+ // Clean up
271
+ this.activeGames.delete(gameInstanceId);
272
+
273
+ } catch (error) {
274
+ this.errorHandler.handle(error, 'GamingManager.endGame');
275
+ }
276
+ }
277
+
278
+ // ===== GAME LOGIC IMPLEMENTATIONS =====
279
+
280
+ async quizGameLogic(game) {
281
+ const questions = [
282
+ { q: "Was ist die Hauptstadt von Deutschland?", a: ["berlin"], points: 10 },
283
+ { q: "Wie viele Kontinente gibt es?", a: ["7", "sieben"], points: 10 },
284
+ { q: "Welcher Planet ist der Sonne am nƤchsten?", a: ["merkur"], points: 15 },
285
+ { q: "In welchem Jahr fiel die Berliner Mauer?", a: ["1989"], points: 20 },
286
+ { q: "Wie heißt der größte Ozean der Welt?", a: ["pazifik", "pazifischer ozean"], points: 15 }
287
+ ];
288
+
289
+ game.data.questions = questions;
290
+ game.data.currentQuestion = 0;
291
+ game.data.questionStartTime = Date.now();
292
+
293
+ await this.askNextQuestion(game);
294
+ }
295
+
296
+ async askNextQuestion(game) {
297
+ if (game.data.currentQuestion >= game.data.questions.length) {
298
+ this.endGame(game.id, 'completed');
299
+ return;
300
+ }
301
+
302
+ const question = game.data.questions[game.data.currentQuestion];
303
+ game.data.questionStartTime = Date.now();
304
+ game.data.answered = new Set();
305
+
306
+ await this.client.sendMessage(game.chatId,
307
+ `ā“ *Frage ${game.data.currentQuestion + 1}/${game.data.questions.length}*\n\n` +
308
+ `${question.q}\n\n` +
309
+ `šŸ’° Punkte: ${question.points}\n` +
310
+ `ā±ļø Zeit: 30 Sekunden`
311
+ );
312
+
313
+ // Set question timeout
314
+ setTimeout(() => {
315
+ if (game.state === 'active') {
316
+ this.nextQuestion(game);
317
+ }
318
+ }, 30000);
319
+ }
320
+
321
+ async nextQuestion(game) {
322
+ const question = game.data.questions[game.data.currentQuestion];
323
+
324
+ await this.client.sendMessage(game.chatId,
325
+ `ā° Zeit abgelaufen!\n` +
326
+ `āœ… Richtige Antwort: ${question.a[0]}\n\n` +
327
+ `NƤchste Frage in 3 Sekunden...`
328
+ );
329
+
330
+ game.data.currentQuestion++;
331
+
332
+ setTimeout(() => {
333
+ if (game.state === 'active') {
334
+ this.askNextQuestion(game);
335
+ }
336
+ }, 3000);
337
+ }
338
+
339
+ async guessGameLogic(game) {
340
+ game.data.secretNumber = Math.floor(Math.random() * 100) + 1;
341
+ game.data.attempts = new Map();
342
+ game.data.maxAttempts = 10;
343
+
344
+ await this.client.sendMessage(game.chatId,
345
+ `šŸ”¢ *Zahlen-Raten*\n\n` +
346
+ `Ich denke an eine Zahl zwischen 1 und 100!\n` +
347
+ `Du hast ${game.data.maxAttempts} Versuche.\n\n` +
348
+ `Schreibe einfach eine Zahl um zu raten!`
349
+ );
350
+ }
351
+
352
+ async wordChainLogic(game) {
353
+ game.data.usedWords = new Set();
354
+ game.data.currentWord = null;
355
+ game.data.currentPlayer = Array.from(game.players.keys())[0];
356
+ game.data.chain = [];
357
+
358
+ const startWords = ['apfel', 'baum', 'computer', 'deutschland', 'elefant'];
359
+ const startWord = startWords[Math.floor(Math.random() * startWords.length)];
360
+
361
+ game.data.currentWord = startWord;
362
+ game.data.usedWords.add(startWord);
363
+ game.data.chain.push(startWord);
364
+
365
+ await this.client.sendMessage(game.chatId,
366
+ `šŸ”— *Wort-Kette*\n\n` +
367
+ `Startwort: *${startWord}*\n\n` +
368
+ `Das nƤchste Wort muss mit "${startWord.slice(-1).toUpperCase()}" beginnen!\n` +
369
+ `Spieler: ${game.players.get(game.data.currentPlayer).name}`
370
+ );
371
+ }
372
+
373
+ async triviaGameLogic(game) {
374
+ const categories = ['Geschichte', 'Wissenschaft', 'Sport', 'Geographie', 'Kultur'];
375
+ game.data.categories = categories;
376
+ game.data.currentCategory = 0;
377
+ game.data.questionsPerCategory = 2;
378
+ game.data.currentCategoryQuestion = 0;
379
+
380
+ await this.startTriviaCategory(game);
381
+ }
382
+
383
+ async mathGameLogic(game) {
384
+ game.data.problems = [];
385
+ game.data.currentProblem = 0;
386
+ game.data.totalProblems = 10;
387
+
388
+ // Generate math problems
389
+ for (let i = 0; i < game.data.totalProblems; i++) {
390
+ const problem = this.generateMathProblem(i + 1);
391
+ game.data.problems.push(problem);
392
+ }
393
+
394
+ await this.askMathProblem(game);
395
+ }
396
+
397
+ // ===== MESSAGE HANDLING =====
398
+
399
+ async handleGameMessage(chatId, playerId, message) {
400
+ try {
401
+ const game = this.findActiveGameByChat(chatId);
402
+ if (!game || game.state !== 'active') return false;
403
+
404
+ const player = game.players.get(playerId);
405
+ if (!player || !player.isActive) return false;
406
+
407
+ switch (game.gameId) {
408
+ case 'quiz':
409
+ return await this.handleQuizAnswer(game, playerId, message);
410
+ case 'guess':
411
+ return await this.handleGuessAnswer(game, playerId, message);
412
+ case 'wordchain':
413
+ return await this.handleWordChainAnswer(game, playerId, message);
414
+ case 'trivia':
415
+ return await this.handleTriviaAnswer(game, playerId, message);
416
+ case 'math':
417
+ return await this.handleMathAnswer(game, playerId, message);
418
+ default:
419
+ return false;
420
+ }
421
+ } catch (error) {
422
+ this.errorHandler.handle(error, 'GamingManager.handleGameMessage');
423
+ return false;
424
+ }
425
+ }
426
+
427
+ async handleQuizAnswer(game, playerId, message) {
428
+ if (game.data.currentQuestion >= game.data.questions.length) return false;
429
+ if (game.data.answered.has(playerId)) return false;
430
+
431
+ const question = game.data.questions[game.data.currentQuestion];
432
+ const answer = message.toLowerCase().trim();
433
+ const isCorrect = question.a.some(a => a.toLowerCase() === answer);
434
+
435
+ game.data.answered.add(playerId);
436
+
437
+ if (isCorrect) {
438
+ const player = game.players.get(playerId);
439
+ const timeBonus = Math.max(0, 30 - Math.floor((Date.now() - game.data.questionStartTime) / 1000));
440
+ const points = question.points + timeBonus;
441
+
442
+ player.score += points;
443
+
444
+ await this.client.sendMessage(game.chatId,
445
+ `āœ… ${player.name} hat richtig geantwortet!\n` +
446
+ `šŸ’° +${points} Punkte (${timeBonus} Zeitbonus)`
447
+ );
448
+
449
+ // Move to next question after short delay
450
+ setTimeout(() => {
451
+ if (game.state === 'active') {
452
+ this.nextQuestion(game);
453
+ }
454
+ }, 2000);
455
+ }
456
+
457
+ return true;
458
+ }
459
+
460
+ async handleGuessAnswer(game, playerId, message) {
461
+ const guess = parseInt(message.trim());
462
+ if (isNaN(guess) || guess < 1 || guess > 100) return false;
463
+
464
+ const player = game.players.get(playerId);
465
+ const attempts = game.data.attempts.get(playerId) || 0;
466
+
467
+ if (attempts >= game.data.maxAttempts) {
468
+ await this.client.sendMessage(game.chatId,
469
+ `āŒ ${player.name}, du hast bereits alle Versuche aufgebraucht!`
470
+ );
471
+ return true;
472
+ }
473
+
474
+ game.data.attempts.set(playerId, attempts + 1);
475
+
476
+ if (guess === game.data.secretNumber) {
477
+ const points = Math.max(10, 100 - (attempts * 10));
478
+ player.score += points;
479
+
480
+ await this.client.sendMessage(game.chatId,
481
+ `šŸŽ‰ ${player.name} hat die Zahl ${game.data.secretNumber} erraten!\n` +
482
+ `šŸ’° +${points} Punkte`
483
+ );
484
+
485
+ this.endGame(game.id, 'completed');
486
+ } else {
487
+ const hint = guess < game.data.secretNumber ? 'hƶher' : 'niedriger';
488
+ const remaining = game.data.maxAttempts - attempts - 1;
489
+
490
+ await this.client.sendMessage(game.chatId,
491
+ `${guess < game.data.secretNumber ? 'ā¬†ļø' : 'ā¬‡ļø'} ${player.name}: ${guess} ist zu ${hint}!\n` +
492
+ `Versuche übrig: ${remaining}`
493
+ );
494
+ }
495
+
496
+ return true;
497
+ }
498
+
499
+ // ===== PLAYER STATISTICS =====
500
+
501
+ updatePlayerStats(playerId, gameId, score, isWinner) {
502
+ const stats = this.playerStats.get(playerId) || {
503
+ totalGames: 0,
504
+ totalWins: 0,
505
+ totalScore: 0,
506
+ gameStats: {},
507
+ achievements: [],
508
+ level: 1,
509
+ experience: 0
510
+ };
511
+
512
+ stats.totalGames++;
513
+ stats.totalScore += score;
514
+ stats.experience += score;
515
+
516
+ if (isWinner) {
517
+ stats.totalWins++;
518
+ stats.experience += 50; // Bonus for winning
519
+ }
520
+
521
+ // Game-specific stats
522
+ if (!stats.gameStats[gameId]) {
523
+ stats.gameStats[gameId] = {
524
+ played: 0,
525
+ won: 0,
526
+ bestScore: 0,
527
+ totalScore: 0
528
+ };
529
+ }
530
+
531
+ const gameStats = stats.gameStats[gameId];
532
+ gameStats.played++;
533
+ gameStats.totalScore += score;
534
+ gameStats.bestScore = Math.max(gameStats.bestScore, score);
535
+
536
+ if (isWinner) {
537
+ gameStats.won++;
538
+ }
539
+
540
+ // Level calculation
541
+ const newLevel = Math.floor(stats.experience / 1000) + 1;
542
+ if (newLevel > stats.level) {
543
+ stats.level = newLevel;
544
+ // Achievement for leveling up
545
+ this.checkAchievements(playerId, stats);
546
+ }
547
+
548
+ this.playerStats.set(playerId, stats);
549
+ this.saveGameData();
550
+ }
551
+
552
+ checkAchievements(playerId, stats) {
553
+ const achievements = [];
554
+
555
+ // Level achievements
556
+ if (stats.level >= 5 && !stats.achievements.includes('level_5')) {
557
+ achievements.push({ id: 'level_5', name: 'Erfahrener Spieler', description: 'Level 5 erreicht' });
558
+ }
559
+
560
+ // Win achievements
561
+ if (stats.totalWins >= 10 && !stats.achievements.includes('winner_10')) {
562
+ achievements.push({ id: 'winner_10', name: 'Gewinner', description: '10 Spiele gewonnen' });
563
+ }
564
+
565
+ // Game-specific achievements
566
+ if (stats.totalGames >= 50 && !stats.achievements.includes('veteran')) {
567
+ achievements.push({ id: 'veteran', name: 'Veteran', description: '50 Spiele gespielt' });
568
+ }
569
+
570
+ // Add new achievements
571
+ achievements.forEach(achievement => {
572
+ if (!stats.achievements.includes(achievement.id)) {
573
+ stats.achievements.push(achievement.id);
574
+ }
575
+ });
576
+
577
+ return achievements;
578
+ }
579
+
580
+ // ===== LEADERBOARDS =====
581
+
582
+ updateGameLeaderboard(gameId, players) {
583
+ const leaderboard = this.leaderboards.get(gameId) || [];
584
+
585
+ players.forEach(player => {
586
+ const existingIndex = leaderboard.findIndex(p => p.id === player.id);
587
+
588
+ if (existingIndex >= 0) {
589
+ // Update existing player
590
+ const existing = leaderboard[existingIndex];
591
+ existing.totalScore += player.score;
592
+ existing.gamesPlayed++;
593
+ existing.lastPlayed = Date.now();
594
+
595
+ if (player.score > existing.bestScore) {
596
+ existing.bestScore = player.score;
597
+ }
598
+ } else {
599
+ // Add new player
600
+ leaderboard.push({
601
+ id: player.id,
602
+ name: player.name,
603
+ totalScore: player.score,
604
+ bestScore: player.score,
605
+ gamesPlayed: 1,
606
+ lastPlayed: Date.now()
607
+ });
608
+ }
609
+ });
610
+
611
+ // Sort by total score
612
+ leaderboard.sort((a, b) => b.totalScore - a.totalScore);
613
+
614
+ // Keep only top 50
615
+ if (leaderboard.length > 50) {
616
+ leaderboard.splice(50);
617
+ }
618
+
619
+ this.leaderboards.set(gameId, leaderboard);
620
+ this.saveGameData();
621
+ }
622
+
623
+ async showLeaderboard(chatId, gameId = 'overall', limit = 10) {
624
+ try {
625
+ let leaderboard;
626
+ let title;
627
+
628
+ if (gameId === 'overall') {
629
+ // Overall leaderboard based on total experience
630
+ leaderboard = Array.from(this.playerStats.entries())
631
+ .map(([id, stats]) => ({
632
+ id,
633
+ name: stats.name || id,
634
+ score: stats.experience,
635
+ level: stats.level,
636
+ wins: stats.totalWins,
637
+ games: stats.totalGames
638
+ }))
639
+ .sort((a, b) => b.score - a.score)
640
+ .slice(0, limit);
641
+
642
+ title = 'šŸ† *Gesamt-Bestenliste*';
643
+ } else {
644
+ leaderboard = (this.leaderboards.get(gameId) || []).slice(0, limit);
645
+ const game = this.gameTemplates.get(gameId);
646
+ title = `šŸ† *${game ? game.name : gameId} Bestenliste*`;
647
+ }
648
+
649
+ if (leaderboard.length === 0) {
650
+ await this.client.sendMessage(chatId, 'šŸ“Š Noch keine EintrƤge in der Bestenliste!');
651
+ return;
652
+ }
653
+
654
+ let message = title + '\n\n';
655
+
656
+ leaderboard.forEach((player, index) => {
657
+ const medal = index === 0 ? 'šŸ„‡' : index === 1 ? '🄈' : index === 2 ? 'šŸ„‰' : `${index + 1}.`;
658
+
659
+ if (gameId === 'overall') {
660
+ message += `${medal} ${player.name}\n`;
661
+ message += ` šŸ’° ${player.score} XP | šŸŽÆ Level ${player.level}\n`;
662
+ message += ` šŸ† ${player.wins}/${player.games} Siege\n\n`;
663
+ } else {
664
+ message += `${medal} ${player.name}\n`;
665
+ message += ` šŸ’° ${player.totalScore} Punkte\n`;
666
+ message += ` šŸŽÆ Bester Score: ${player.bestScore}\n`;
667
+ message += ` šŸŽ® ${player.gamesPlayed} Spiele\n\n`;
668
+ }
669
+ });
670
+
671
+ await this.client.sendMessage(chatId, message);
672
+ } catch (error) {
673
+ this.errorHandler.handle(error, 'GamingManager.showLeaderboard');
674
+ }
675
+ }
676
+
677
+ // ===== TOURNAMENTS =====
678
+
679
+ async createTournament(chatId, gameId, options = {}) {
680
+ try {
681
+ const template = this.gameTemplates.get(gameId);
682
+ if (!template) {
683
+ throw new Error(`Game not found: ${gameId}`);
684
+ }
685
+
686
+ const tournamentId = this.generateTournamentId();
687
+ const tournament = {
688
+ id: tournamentId,
689
+ gameId,
690
+ chatId,
691
+ name: options.name || `${template.name} Turnier`,
692
+ description: options.description || `Turnier für ${template.name}`,
693
+ maxParticipants: options.maxParticipants || 16,
694
+ entryFee: options.entryFee || 0,
695
+ prizePool: options.prizePool || 0,
696
+ participants: new Map(),
697
+ rounds: [],
698
+ currentRound: 0,
699
+ state: 'registration',
700
+ startTime: options.startTime || (Date.now() + 300000), // 5 minutes from now
701
+ createdAt: Date.now(),
702
+ createdBy: options.createdBy
703
+ };
704
+
705
+ this.tournaments.set(tournamentId, tournament);
706
+
707
+ await this.client.sendMessage(chatId,
708
+ `šŸ† *${tournament.name}*\n\n` +
709
+ `šŸŽ® Spiel: ${template.name}\n` +
710
+ `šŸ‘„ Max. Teilnehmer: ${tournament.maxParticipants}\n` +
711
+ `šŸ’° Eintritt: ${tournament.entryFee} Punkte\n` +
712
+ `šŸ… Preispool: ${tournament.prizePool} Punkte\n\n` +
713
+ `Schreibe *!tournament join ${tournamentId}* um teilzunehmen!\n` +
714
+ `Start: ${new Date(tournament.startTime).toLocaleString()}`
715
+ );
716
+
717
+ return tournamentId;
718
+ } catch (error) {
719
+ this.errorHandler.handle(error, 'GamingManager.createTournament');
720
+ throw error;
721
+ }
722
+ }
723
+
724
+ // ===== UTILITY METHODS =====
725
+
726
+ findActiveGameByChat(chatId) {
727
+ for (const game of this.activeGames.values()) {
728
+ if (game.chatId === chatId && game.state !== 'ended') {
729
+ return game;
730
+ }
731
+ }
732
+ return null;
733
+ }
734
+
735
+ generateGameId() {
736
+ return `game_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
737
+ }
738
+
739
+ generateTournamentId() {
740
+ return `tournament_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
741
+ }
742
+
743
+ generateMathProblem(difficulty) {
744
+ const operations = ['+', '-', '*'];
745
+ const operation = operations[Math.floor(Math.random() * operations.length)];
746
+
747
+ let a, b, answer;
748
+
749
+ switch (operation) {
750
+ case '+':
751
+ a = Math.floor(Math.random() * (10 * difficulty)) + 1;
752
+ b = Math.floor(Math.random() * (10 * difficulty)) + 1;
753
+ answer = a + b;
754
+ break;
755
+ case '-':
756
+ a = Math.floor(Math.random() * (10 * difficulty)) + 10;
757
+ b = Math.floor(Math.random() * a) + 1;
758
+ answer = a - b;
759
+ break;
760
+ case '*':
761
+ a = Math.floor(Math.random() * (5 + difficulty)) + 1;
762
+ b = Math.floor(Math.random() * (5 + difficulty)) + 1;
763
+ answer = a * b;
764
+ break;
765
+ }
766
+
767
+ return {
768
+ question: `${a} ${operation} ${b} = ?`,
769
+ answer,
770
+ points: difficulty * 5
771
+ };
772
+ }
773
+
774
+ cleanupExpiredGames() {
775
+ const now = Date.now();
776
+ const expiredGames = [];
777
+
778
+ for (const [gameId, game] of this.activeGames) {
779
+ const maxGameTime = game.template.duration + 300000; // 5 minutes grace period
780
+ if (game.createdAt + maxGameTime < now) {
781
+ expiredGames.push(gameId);
782
+ }
783
+ }
784
+
785
+ expiredGames.forEach(gameId => {
786
+ this.endGame(gameId, 'expired');
787
+ });
788
+ }
789
+
790
+ saveGameData() {
791
+ try {
792
+ const gameData = {
793
+ playerStats: Object.fromEntries(this.playerStats),
794
+ leaderboards: Object.fromEntries(this.leaderboards),
795
+ tournaments: Object.fromEntries(this.tournaments)
796
+ };
797
+
798
+ this.storage.write.to("gaming").set("data", gameData);
799
+ } catch (error) {
800
+ this.errorHandler.handle(error, 'GamingManager.saveGameData');
801
+ }
802
+ }
803
+
804
+ // ===== PUBLIC API METHODS =====
805
+
806
+ getAvailableGames() {
807
+ return Array.from(this.gameTemplates.values());
808
+ }
809
+
810
+ getActiveGames() {
811
+ return Array.from(this.activeGames.values());
812
+ }
813
+
814
+ getPlayerStats(playerId) {
815
+ return this.playerStats.get(playerId);
816
+ }
817
+
818
+ async sendGameResults(game, reason) {
819
+ const players = Array.from(game.players.values()).sort((a, b) => b.score - a.score);
820
+
821
+ let message = `šŸŽ® *${game.template.name}* beendet!\n\n`;
822
+
823
+ if (reason === 'timeout') {
824
+ message += 'ā° Zeit abgelaufen!\n\n';
825
+ } else if (reason === 'completed') {
826
+ message += 'āœ… Spiel abgeschlossen!\n\n';
827
+ }
828
+
829
+ message += 'šŸ† *Endergebnis:*\n';
830
+
831
+ players.forEach((player, index) => {
832
+ const medal = index === 0 ? 'šŸ„‡' : index === 1 ? '🄈' : index === 2 ? 'šŸ„‰' : `${index + 1}.`;
833
+ message += `${medal} ${player.name}: ${player.score} Punkte\n`;
834
+ });
835
+
836
+ if (game.winner) {
837
+ message += `\nšŸŽ‰ Gewinner: ${game.winner.name}!`;
838
+ }
839
+
840
+ await this.client.sendMessage(game.chatId, message);
841
+ }
842
+ }