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.
- package/CHANGELOG.md +29 -0
- package/README.md +34 -3
- package/package.json +3 -2
- package/src/ab-testing.js +698 -0
- package/src/advanced-scheduler.js +577 -0
- package/src/ai-features.js +459 -0
- package/src/ai-integration.js +2 -1
- package/src/analytics-manager.js +458 -0
- package/src/business-manager.js +362 -0
- package/src/client.js +447 -39
- package/src/console-logger.js +256 -0
- package/src/core.js +28 -3
- package/src/cross-platform.js +538 -0
- package/src/database-manager.js +766 -0
- package/src/device-manager.js +1 -1
- package/src/easy-bot-fixed.js +341 -0
- package/src/easy-bot.js +503 -22
- package/src/error-handler.js +230 -0
- package/src/gaming-manager.js +842 -0
- package/src/http-client.js +1 -1
- package/src/index.js +15 -0
- package/src/message.js +197 -94
- package/src/multi-client.js +26 -12
- package/src/plugin-manager.js +59 -10
- package/src/prefix-manager.js +48 -1
- package/src/qr-terminal-fix.js +239 -0
- package/src/qr.js +170 -27
- package/src/quick-bot.js +63 -0
- package/src/reporting-manager.js +867 -0
- package/src/scheduler.js +14 -1
- package/src/security-manager.js +678 -0
- package/src/session-manager-old.js +314 -0
- package/src/session-manager.js +429 -24
- package/src/storage.js +254 -194
- package/src/ui-components.js +560 -0
|
@@ -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
|
+
}
|