slashvibe-mcp 0.3.20 → 0.3.21

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.
Files changed (167) hide show
  1. package/README.md +47 -252
  2. package/analytics.js +107 -0
  3. package/auth-store.js +148 -0
  4. package/auto-update.js +130 -0
  5. package/bridges/bridge-monitor.js +388 -0
  6. package/bridges/discord-bot.js +431 -0
  7. package/bridges/farcaster.js +299 -0
  8. package/bridges/telegram.js +261 -0
  9. package/bridges/webhook-health.js +420 -0
  10. package/bridges/webhook-server.js +437 -0
  11. package/bridges/whatsapp.js +441 -0
  12. package/bridges/x-webhook.js +423 -0
  13. package/config.js +27 -15
  14. package/games/arcade.js +406 -0
  15. package/games/chess.js +451 -0
  16. package/games/colorguess.js +343 -0
  17. package/games/crossword-words.js +171 -0
  18. package/games/crossword.js +461 -0
  19. package/games/drawing.js +347 -0
  20. package/games/gameroulette.js +300 -0
  21. package/games/gamerouter.js +336 -0
  22. package/games/gamestatus.js +337 -0
  23. package/games/guessnumber.js +209 -0
  24. package/games/hangman.js +279 -0
  25. package/games/memory.js +338 -0
  26. package/games/multiplayer-tictactoe.js +389 -0
  27. package/games/pixelart.js +399 -0
  28. package/games/quickduel.js +354 -0
  29. package/games/riddle.js +371 -0
  30. package/games/rockpaperscissors.js +291 -0
  31. package/games/snake.js +406 -0
  32. package/games/storybuilder.js +343 -0
  33. package/games/tictactoe.js +345 -0
  34. package/games/twentyquestions.js +286 -0
  35. package/games/twotruths.js +207 -0
  36. package/games/werewolf.js +508 -0
  37. package/games/wordassociation.js +247 -0
  38. package/games/wordchain.js +135 -0
  39. package/index.js +116 -159
  40. package/intelligence/index.js +9 -2
  41. package/intelligence/interests.js +369 -0
  42. package/notification-emitter.js +77 -0
  43. package/notify.js +5 -1
  44. package/package.json +21 -16
  45. package/prompts.js +1 -1
  46. package/protocol/index.js +73 -0
  47. package/setup.js +480 -0
  48. package/smart-inbox.js +276 -0
  49. package/store/api.js +536 -215
  50. package/store/profiles.js +160 -12
  51. package/tools/_actions.js +362 -21
  52. package/tools/_discovery.js +119 -26
  53. package/tools/_shared/index.js +64 -0
  54. package/tools/_shared.js +234 -0
  55. package/tools/_work-context.js +338 -0
  56. package/tools/_work-context.manual-test.js +199 -0
  57. package/tools/_work-context.test.js +260 -0
  58. package/tools/activity.js +220 -0
  59. package/tools/analytics.js +191 -0
  60. package/tools/approve.js +197 -0
  61. package/tools/artifact-create.js +14 -3
  62. package/tools/artifacts-price.js +107 -0
  63. package/tools/available.js +120 -0
  64. package/tools/broadcast.js +325 -0
  65. package/tools/chat.js +202 -0
  66. package/tools/collaborative-drawing.js +1 -1
  67. package/tools/connection-status.js +178 -0
  68. package/tools/discover.js +350 -34
  69. package/tools/dm.js +80 -8
  70. package/tools/earnings.js +126 -0
  71. package/tools/feed.js +35 -4
  72. package/tools/follow.js +224 -0
  73. package/tools/friends.js +207 -0
  74. package/tools/gig-browse.js +206 -0
  75. package/tools/gig-complete.js +144 -0
  76. package/tools/health.js +87 -0
  77. package/tools/help.js +3 -3
  78. package/tools/idea.js +9 -2
  79. package/tools/inbox.js +289 -105
  80. package/tools/init.js +131 -34
  81. package/tools/invite.js +15 -4
  82. package/tools/leaderboard.js +117 -0
  83. package/tools/lib/git-apply.js +206 -0
  84. package/tools/lib/git-bundle.js +407 -0
  85. package/tools/migrate.js +3 -3
  86. package/tools/multiplayer-game.js +1 -1
  87. package/tools/onboarding.js +7 -7
  88. package/tools/open.js +143 -12
  89. package/tools/party-game.js +1 -1
  90. package/tools/plan.js +225 -0
  91. package/tools/proof-of-work.js +144 -0
  92. package/tools/reply.js +166 -0
  93. package/tools/report.js +1 -1
  94. package/tools/request.js +17 -3
  95. package/tools/schedule.js +367 -0
  96. package/tools/search-messages.js +123 -0
  97. package/tools/session.js +467 -0
  98. package/tools/session_price.js +128 -0
  99. package/tools/settings.js +90 -2
  100. package/tools/ship.js +30 -7
  101. package/tools/smart-check.js +201 -0
  102. package/tools/start.js +147 -12
  103. package/tools/status.js +53 -6
  104. package/tools/streak.js +147 -0
  105. package/tools/stuck.js +297 -0
  106. package/tools/subscribe.js +148 -0
  107. package/tools/subscriptions.js +134 -0
  108. package/tools/suggest-tags.js +6 -8
  109. package/tools/tag-suggestions.js +1 -1
  110. package/tools/tip.js +150 -77
  111. package/tools/token.js +4 -4
  112. package/tools/update.js +1 -1
  113. package/tools/wallet.js +221 -79
  114. package/tools/watch.js +157 -0
  115. package/tools/who.js +30 -1
  116. package/tools/withdraw.js +145 -0
  117. package/tools/work-summary.js +96 -0
  118. package/version.json +10 -8
  119. package/LICENSE +0 -21
  120. package/store/sqlite.js +0 -347
  121. /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
  122. /package/tools/{away.js → _deprecated/away.js} +0 -0
  123. /package/tools/{back.js → _deprecated/back.js} +0 -0
  124. /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
  125. /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
  126. /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
  127. /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
  128. /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
  129. /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
  130. /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
  131. /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
  132. /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
  133. /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
  134. /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
  135. /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
  136. /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
  137. /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
  138. /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
  139. /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
  140. /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
  141. /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
  142. /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
  143. /package/tools/{draw.js → _deprecated/draw.js} +0 -0
  144. /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
  145. /package/tools/{forget.js → _deprecated/forget.js} +0 -0
  146. /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
  147. /package/tools/{games.js → _deprecated/games.js} +0 -0
  148. /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
  149. /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
  150. /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
  151. /package/tools/{mute.js → _deprecated/mute.js} +0 -0
  152. /package/tools/{recall.js → _deprecated/recall.js} +0 -0
  153. /package/tools/{remember.js → _deprecated/remember.js} +0 -0
  154. /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
  155. /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
  156. /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
  157. /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
  158. /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
  159. /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
  160. /package/tools/{skills.js → _deprecated/skills.js} +0 -0
  161. /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
  162. /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
  163. /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
  164. /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
  165. /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
  166. /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
  167. /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Collaborative Drawing game implementation for /vibe
3
+ * A shared canvas where multiple users can draw together in real-time
4
+ * Create art, doodles, or play drawing games like Pictionary!
5
+ */
6
+
7
+ // Drawing canvas dimensions (character-based art)
8
+ const CANVAS_WIDTH = 20;
9
+ const CANVAS_HEIGHT = 12;
10
+
11
+ // Drawing tools and colors (using Unicode characters)
12
+ const DRAWING_CHARS = {
13
+ empty: '⬜', // Empty space
14
+ dot: '⚫', // Small dot
15
+ circle: '⚪', // Circle
16
+ square: '⬛', // Filled square
17
+ star: '⭐', // Star
18
+ heart: 'ā¤ļø', // Heart
19
+ tree: '🌲', // Tree
20
+ house: 'šŸ ', // House
21
+ sun: 'ā˜€ļø', // Sun
22
+ moon: 'šŸŒ™', // Moon
23
+ water: '🌊', // Wave
24
+ mountain: 'ā›°ļø', // Mountain
25
+ person: 'šŸ§', // Person
26
+ cat: '🐱', // Cat
27
+ dog: 'šŸ•', // Dog
28
+ car: 'šŸš—', // Car
29
+ plane: 'āœˆļø', // Plane
30
+ flower: '🌸', // Flower
31
+ umbrella: 'ā˜‚ļø', // Umbrella
32
+ rainbow: '🌈' // Rainbow
33
+ };
34
+
35
+ // Create initial drawing game state
36
+ function createInitialDrawingState() {
37
+ // Initialize empty canvas
38
+ const canvas = Array(CANVAS_HEIGHT).fill(null).map(() =>
39
+ Array(CANVAS_WIDTH).fill(DRAWING_CHARS.empty)
40
+ );
41
+
42
+ return {
43
+ canvas: canvas,
44
+ players: [],
45
+ moves: [],
46
+ maxPlayers: 8,
47
+ gameOver: false,
48
+ createdAt: new Date().toISOString(),
49
+ lastActivity: new Date().toISOString(),
50
+ theme: null, // Optional drawing theme/prompt
51
+ mode: 'freeform' // 'freeform', 'pictionary', 'collaborative'
52
+ };
53
+ }
54
+
55
+ // Add player to drawing session
56
+ function addPlayer(gameState, playerHandle) {
57
+ if (gameState.players.includes(playerHandle)) {
58
+ return { error: 'Player already in the drawing session' };
59
+ }
60
+
61
+ if (gameState.players.length >= gameState.maxPlayers) {
62
+ return { error: `Drawing session is full (max ${gameState.maxPlayers} players)` };
63
+ }
64
+
65
+ const newPlayers = [...gameState.players, playerHandle];
66
+
67
+ return {
68
+ success: true,
69
+ gameState: {
70
+ ...gameState,
71
+ players: newPlayers,
72
+ lastActivity: new Date().toISOString()
73
+ }
74
+ };
75
+ }
76
+
77
+ // Make a drawing move (place character at position)
78
+ function makeMove(gameState, x, y, char, playerHandle) {
79
+ const { canvas, players, moves } = gameState;
80
+
81
+ // Validate player
82
+ if (!players.includes(playerHandle)) {
83
+ return { error: 'You need to join the drawing session first!' };
84
+ }
85
+
86
+ // Validate position
87
+ if (x < 0 || x >= CANVAS_WIDTH || y < 0 || y >= CANVAS_HEIGHT) {
88
+ return { error: `Position out of bounds. Canvas is ${CANVAS_WIDTH}x${CANVAS_HEIGHT}` };
89
+ }
90
+
91
+ // Validate character
92
+ const validChars = Object.values(DRAWING_CHARS);
93
+ if (!validChars.includes(char)) {
94
+ return { error: `Invalid character. Use one of: ${Object.keys(DRAWING_CHARS).join(', ')}` };
95
+ }
96
+
97
+ // Update canvas
98
+ const newCanvas = canvas.map(row => [...row]);
99
+ newCanvas[y][x] = char;
100
+
101
+ // Record the move
102
+ const move = {
103
+ x, y, char,
104
+ player: playerHandle,
105
+ timestamp: new Date().toISOString(),
106
+ moveNumber: moves.length + 1
107
+ };
108
+
109
+ const newMoves = [...moves, move];
110
+
111
+ const newGameState = {
112
+ ...gameState,
113
+ canvas: newCanvas,
114
+ moves: newMoves,
115
+ lastActivity: new Date().toISOString()
116
+ };
117
+
118
+ return { success: true, gameState: newGameState };
119
+ }
120
+
121
+ // Draw a line between two points (simple Bresenham-like algorithm)
122
+ function drawLine(gameState, x0, y0, x1, y1, char, playerHandle) {
123
+ const moves = [];
124
+
125
+ // Simple line drawing - just plot points between start and end
126
+ const dx = Math.abs(x1 - x0);
127
+ const dy = Math.abs(y1 - y0);
128
+ const steps = Math.max(dx, dy);
129
+
130
+ if (steps === 0) {
131
+ // Single point
132
+ const result = makeMove(gameState, x0, y0, char, playerHandle);
133
+ return result;
134
+ }
135
+
136
+ const xInc = (x1 - x0) / steps;
137
+ const yInc = (y1 - y0) / steps;
138
+
139
+ let currentGameState = gameState;
140
+
141
+ for (let i = 0; i <= steps; i++) {
142
+ const x = Math.round(x0 + i * xInc);
143
+ const y = Math.round(y0 + i * yInc);
144
+
145
+ const result = makeMove(currentGameState, x, y, char, playerHandle);
146
+ if (result.error) {
147
+ // Stop on first error but return what we accomplished
148
+ break;
149
+ }
150
+ currentGameState = result.gameState;
151
+ }
152
+
153
+ return { success: true, gameState: currentGameState };
154
+ }
155
+
156
+ // Clear a region of the canvas
157
+ function clearRegion(gameState, x0, y0, x1, y1, playerHandle) {
158
+ const { players } = gameState;
159
+
160
+ if (!players.includes(playerHandle)) {
161
+ return { error: 'You need to join the drawing session first!' };
162
+ }
163
+
164
+ // Ensure coordinates are in bounds and properly ordered
165
+ const minX = Math.max(0, Math.min(x0, x1));
166
+ const maxX = Math.min(CANVAS_WIDTH - 1, Math.max(x0, x1));
167
+ const minY = Math.max(0, Math.min(y0, y1));
168
+ const maxY = Math.min(CANVAS_HEIGHT - 1, Math.max(y0, y1));
169
+
170
+ const newCanvas = gameState.canvas.map(row => [...row]);
171
+
172
+ for (let y = minY; y <= maxY; y++) {
173
+ for (let x = minX; x <= maxX; x++) {
174
+ newCanvas[y][x] = DRAWING_CHARS.empty;
175
+ }
176
+ }
177
+
178
+ const clearMove = {
179
+ action: 'clear',
180
+ x0: minX, y0: minY, x1: maxX, y1: maxY,
181
+ player: playerHandle,
182
+ timestamp: new Date().toISOString(),
183
+ moveNumber: gameState.moves.length + 1
184
+ };
185
+
186
+ return {
187
+ success: true,
188
+ gameState: {
189
+ ...gameState,
190
+ canvas: newCanvas,
191
+ moves: [...gameState.moves, clearMove],
192
+ lastActivity: new Date().toISOString()
193
+ }
194
+ };
195
+ }
196
+
197
+ // Format drawing canvas for display
198
+ function formatDrawingDisplay(gameState) {
199
+ const { canvas, players, moves, theme, mode } = gameState;
200
+
201
+ let display = `šŸŽØ **Collaborative Drawing** (${players.length} artist${players.length !== 1 ? 's' : ''})\n\n`;
202
+
203
+ // Show theme if set
204
+ if (theme) {
205
+ display += `šŸŽÆ **Theme:** ${theme}\n\n`;
206
+ }
207
+
208
+ // Show canvas with coordinate labels
209
+ display += '```\n';
210
+
211
+ // Top coordinate labels
212
+ let topLabels = ' ';
213
+ for (let x = 0; x < CANVAS_WIDTH; x++) {
214
+ if (x < 10) {
215
+ topLabels += x + ' ';
216
+ } else {
217
+ topLabels += String.fromCharCode(55 + x); // A, B, C... for 10+
218
+ }
219
+ }
220
+ display += topLabels + '\n';
221
+
222
+ // Canvas rows with left coordinate labels
223
+ for (let y = 0; y < CANVAS_HEIGHT; y++) {
224
+ let row = (y < 10 ? ' ' + y : String.fromCharCode(55 + y)) + ' ';
225
+ row += canvas[y].join('');
226
+ display += row + '\n';
227
+ }
228
+
229
+ display += '```\n\n';
230
+
231
+ // Show players
232
+ if (players.length > 0) {
233
+ display += `**Artists:** ${players.map(p => `@${p}`).join(', ')}\n\n`;
234
+ }
235
+
236
+ // Show recent activity
237
+ if (moves.length > 0) {
238
+ const recentMoves = moves.slice(-3); // Last 3 moves
239
+ display += '**Recent activity:**\n';
240
+ for (const move of recentMoves) {
241
+ if (move.action === 'clear') {
242
+ display += `• @${move.player} cleared region (${move.x0},${move.y0}) to (${move.x1},${move.y1})\n`;
243
+ } else {
244
+ display += `• @${move.player} drew ${move.char} at (${move.x},${move.y})\n`;
245
+ }
246
+ }
247
+ display += '\n';
248
+ }
249
+
250
+ // Show available characters
251
+ display += '**Available characters:**\n';
252
+ const charEntries = Object.entries(DRAWING_CHARS);
253
+ const charGroups = [];
254
+ for (let i = 0; i < charEntries.length; i += 5) {
255
+ const group = charEntries.slice(i, i + 5);
256
+ charGroups.push(group.map(([name, char]) => `${char} (${name})`).join(' '));
257
+ }
258
+ display += charGroups.join('\n') + '\n\n';
259
+
260
+ return display;
261
+ }
262
+
263
+ // Set drawing theme/prompt
264
+ function setTheme(gameState, theme, playerHandle) {
265
+ const { players } = gameState;
266
+
267
+ if (!players.includes(playerHandle)) {
268
+ return { error: 'You need to join the drawing session first!' };
269
+ }
270
+
271
+ return {
272
+ success: true,
273
+ gameState: {
274
+ ...gameState,
275
+ theme: theme,
276
+ lastActivity: new Date().toISOString()
277
+ }
278
+ };
279
+ }
280
+
281
+ // Generate drawing tips based on theme
282
+ function getDrawingTips(theme) {
283
+ const tips = {
284
+ 'house': ['Start with a ⬛ base', 'Add a roof with ā›°ļø', 'Use šŸ  for details'],
285
+ 'landscape': ['Use 🌲 for trees', 'ā˜€ļø for sun', '🌊 for water', 'ā›°ļø for mountains'],
286
+ 'portrait': ['Use šŸ§ for people', '⚪⚫ for eyes', 'ā¤ļø for heart'],
287
+ 'animals': ['Try šŸ±šŸ• for pets', '🌲 for habitat', '⭐ for magical touches'],
288
+ 'vehicle': ['šŸš— for cars', 'āœˆļø for planes', '⬛ for roads'],
289
+ 'nature': ['🌸 for flowers', '🌲 for trees', 'ā˜€ļøšŸŒ™ for sky', '🌈 for color']
290
+ };
291
+
292
+ const defaultTips = ['Use ā¬›ā¬œ for shapes', 'Add ā­ā¤ļø for details', 'Try 🌸🌲 for nature'];
293
+
294
+ return tips[theme?.toLowerCase()] || defaultTips;
295
+ }
296
+
297
+ // Get canvas statistics
298
+ function getCanvasStats(gameState) {
299
+ const { canvas, moves, players } = gameState;
300
+
301
+ // Count characters used
302
+ const charCount = {};
303
+ for (const row of canvas) {
304
+ for (const char of row) {
305
+ if (char !== DRAWING_CHARS.empty) {
306
+ charCount[char] = (charCount[char] || 0) + 1;
307
+ }
308
+ }
309
+ }
310
+
311
+ // Count moves per player
312
+ const playerMoves = {};
313
+ for (const move of moves) {
314
+ if (move.player) {
315
+ playerMoves[move.player] = (playerMoves[move.player] || 0) + 1;
316
+ }
317
+ }
318
+
319
+ const totalDrawnCells = Object.values(charCount).reduce((a, b) => a + b, 0);
320
+ const totalCells = CANVAS_WIDTH * CANVAS_HEIGHT;
321
+ const fillPercentage = Math.round((totalDrawnCells / totalCells) * 100);
322
+
323
+ return {
324
+ totalMoves: moves.length,
325
+ totalDrawnCells,
326
+ fillPercentage,
327
+ uniqueCharsUsed: Object.keys(charCount).length,
328
+ charCount,
329
+ playerMoves,
330
+ mostUsedChar: Object.entries(charCount).sort(([,a], [,b]) => b - a)[0]
331
+ };
332
+ }
333
+
334
+ module.exports = {
335
+ createInitialDrawingState,
336
+ addPlayer,
337
+ makeMove,
338
+ drawLine,
339
+ clearRegion,
340
+ formatDrawingDisplay,
341
+ setTheme,
342
+ getDrawingTips,
343
+ getCanvasStats,
344
+ DRAWING_CHARS,
345
+ CANVAS_WIDTH,
346
+ CANVAS_HEIGHT
347
+ };
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Game Roulette - Discover random games from the /vibe workshop
3
+ * Perfect for when you want to play something but don't know what!
4
+ */
5
+
6
+ const arcade = require('./arcade');
7
+
8
+ // Game difficulty weights for smart recommendations
9
+ const DIFFICULTY_WEIGHTS = {
10
+ 'Easy': 0.4,
11
+ 'Medium': 0.4,
12
+ 'Hard': 0.2
13
+ };
14
+
15
+ // Player count weights for recommendations
16
+ const PLAYER_WEIGHTS = {
17
+ 'Solo': 0.3,
18
+ '1v1': 0.4,
19
+ '1v2': 0.1,
20
+ 'Multiplayer': 0.2
21
+ };
22
+
23
+ // Create initial game roulette state
24
+ function createInitialGameRouletteState() {
25
+ return {
26
+ lastRecommendation: null,
27
+ userPreferences: {},
28
+ playHistory: [],
29
+ sessionStarted: new Date().toISOString(),
30
+ totalRecommendations: 0
31
+ };
32
+ }
33
+
34
+ // Get a random game recommendation
35
+ function getRandomGame(gameState, userHandle = null, preferences = {}) {
36
+ const { GAMES } = arcade;
37
+ const gameIds = Object.keys(GAMES);
38
+
39
+ // Apply preferences if provided
40
+ let filteredGames = gameIds;
41
+
42
+ if (preferences.difficulty) {
43
+ filteredGames = filteredGames.filter(id =>
44
+ GAMES[id].difficulty.toLowerCase() === preferences.difficulty.toLowerCase()
45
+ );
46
+ }
47
+
48
+ if (preferences.category) {
49
+ filteredGames = filteredGames.filter(id =>
50
+ GAMES[id].category === preferences.category
51
+ );
52
+ }
53
+
54
+ if (preferences.players) {
55
+ filteredGames = filteredGames.filter(id =>
56
+ GAMES[id].players === preferences.players
57
+ );
58
+ }
59
+
60
+ // Avoid recommending the same game twice in a row
61
+ if (gameState.lastRecommendation && filteredGames.length > 1) {
62
+ filteredGames = filteredGames.filter(id => id !== gameState.lastRecommendation.id);
63
+ }
64
+
65
+ if (filteredGames.length === 0) {
66
+ return { error: 'No games match your preferences!' };
67
+ }
68
+
69
+ // Pick random game
70
+ const randomId = filteredGames[Math.floor(Math.random() * filteredGames.length)];
71
+ const game = { id: randomId, ...GAMES[randomId] };
72
+
73
+ // Update game state
74
+ const newGameState = {
75
+ ...gameState,
76
+ lastRecommendation: game,
77
+ totalRecommendations: gameState.totalRecommendations + 1,
78
+ playHistory: [...gameState.playHistory.slice(-9), game] // Keep last 10
79
+ };
80
+
81
+ if (userHandle) {
82
+ // Track user preferences
83
+ newGameState.userPreferences[userHandle] = {
84
+ ...gameState.userPreferences[userHandle],
85
+ lastSeen: new Date().toISOString(),
86
+ totalRequests: (gameState.userPreferences[userHandle]?.totalRequests || 0) + 1
87
+ };
88
+ }
89
+
90
+ return { success: true, gameState: newGameState, recommendation: game };
91
+ }
92
+
93
+ // Get smart recommendation based on user history
94
+ function getSmartRecommendation(gameState, userHandle, mood = null) {
95
+ const { GAMES, CATEGORIES } = arcade;
96
+
97
+ // Mood-based filtering
98
+ const moodCategories = {
99
+ 'chill': ['word', 'puzzle'],
100
+ 'competitive': ['classic', 'action'],
101
+ 'social': ['social', 'creative'],
102
+ 'quick': ['classic', 'action'],
103
+ 'thoughtful': ['puzzle', 'word'],
104
+ 'creative': ['creative', 'social']
105
+ };
106
+
107
+ const preferences = {};
108
+ if (mood && moodCategories[mood]) {
109
+ preferences.categoryList = moodCategories[mood];
110
+ }
111
+
112
+ return getRecommendationWithFilters(gameState, userHandle, preferences);
113
+ }
114
+
115
+ // Get recommendation with advanced filters
116
+ function getRecommendationWithFilters(gameState, userHandle, filters = {}) {
117
+ const { GAMES } = arcade;
118
+ let candidateGames = Object.keys(GAMES);
119
+
120
+ // Apply category list filter
121
+ if (filters.categoryList) {
122
+ candidateGames = candidateGames.filter(id =>
123
+ filters.categoryList.includes(GAMES[id].category)
124
+ );
125
+ }
126
+
127
+ // Apply other filters
128
+ if (filters.difficulty) {
129
+ candidateGames = candidateGames.filter(id =>
130
+ GAMES[id].difficulty.toLowerCase() === filters.difficulty.toLowerCase()
131
+ );
132
+ }
133
+
134
+ if (filters.maxPlayers && filters.maxPlayers < 4) {
135
+ candidateGames = candidateGames.filter(id =>
136
+ GAMES[id].players === 'Solo' || GAMES[id].players === '1v1'
137
+ );
138
+ }
139
+
140
+ // Weighted random selection
141
+ if (candidateGames.length > 1) {
142
+ const weights = candidateGames.map(id => {
143
+ const game = GAMES[id];
144
+ let weight = 1.0;
145
+
146
+ // Prefer easier games slightly
147
+ if (game.difficulty === 'Easy') weight *= 1.2;
148
+ else if (game.difficulty === 'Hard') weight *= 0.8;
149
+
150
+ // Prefer variety - reduce weight if recently recommended
151
+ const recentHistory = gameState.playHistory.slice(-3);
152
+ if (recentHistory.some(h => h.id === id)) {
153
+ weight *= 0.3;
154
+ }
155
+
156
+ return weight;
157
+ });
158
+
159
+ // Weighted random selection
160
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
161
+ let random = Math.random() * totalWeight;
162
+
163
+ for (let i = 0; i < candidateGames.length; i++) {
164
+ random -= weights[i];
165
+ if (random <= 0) {
166
+ const gameId = candidateGames[i];
167
+ const game = { id: gameId, ...GAMES[gameId] };
168
+
169
+ const newGameState = {
170
+ ...gameState,
171
+ lastRecommendation: game,
172
+ totalRecommendations: gameState.totalRecommendations + 1,
173
+ playHistory: [...gameState.playHistory.slice(-9), game]
174
+ };
175
+
176
+ return { success: true, gameState: newGameState, recommendation: game };
177
+ }
178
+ }
179
+ }
180
+
181
+ // Fallback to simple random
182
+ return getRandomGame(gameState, userHandle, filters);
183
+ }
184
+
185
+ // Get games by current "vibe"
186
+ function getGamesByVibe(vibe) {
187
+ const { GAMES } = arcade;
188
+ const vibes = {
189
+ 'competitive': ['chess', 'tictactoe', 'multiplayer-tictactoe', 'quickduel', 'rockpaperscissors'],
190
+ 'social': ['storybuilder', 'drawing', 'twotruths', 'werewolf', 'wordassociation'],
191
+ 'solo': ['snake', 'memory', 'hangman', 'riddle', 'guessnumber', 'colorguess'],
192
+ 'quick': ['rockpaperscissors', 'guessnumber', 'colorguess', 'quickduel'],
193
+ 'creative': ['drawing', 'storybuilder'],
194
+ 'thinking': ['chess', 'riddle', 'twentyquestions', 'hangman'],
195
+ 'party': ['werewolf', 'twotruths', 'wordassociation', 'drawing']
196
+ };
197
+
198
+ const gameIds = vibes[vibe.toLowerCase()] || [];
199
+ return gameIds.map(id => ({ id, ...GAMES[id] })).filter(g => g.name);
200
+ }
201
+
202
+ // Format roulette display
203
+ function formatRouletteDisplay(gameState, recommendation, userHandle = null) {
204
+ if (!recommendation) {
205
+ return 'Game Roulette ready! Spin the wheel to discover your next game!';
206
+ }
207
+
208
+ const { icon, name, description, category, players, difficulty } = recommendation;
209
+
210
+ let display = `šŸŽ² **GAME ROULETTE** šŸŽ²\n\n`;
211
+ display += `${icon} **${name}**\n\n`;
212
+ display += `**Description:** ${description}\n`;
213
+ display += `**Category:** ${category.charAt(0).toUpperCase() + category.slice(1)}\n`;
214
+ display += `**Players:** ${players}\n`;
215
+ display += `**Difficulty:** ${difficulty}\n\n`;
216
+
217
+ // Add contextual launch instructions
218
+ if (['tictactoe', 'chess'].includes(recommendation.id)) {
219
+ display += `**How to play:** \`vibe game @username\` to challenge someone!\n`;
220
+ } else if (recommendation.id === 'drawing') {
221
+ display += `**How to play:** Join the collaborative canvas and start drawing!\n`;
222
+ } else if (recommendation.id === 'arcade') {
223
+ display += `**How to play:** Browse all games in the Workshop Arcade!\n`;
224
+ } else {
225
+ display += `**How to play:** Launch ${name} and dive in!\n`;
226
+ }
227
+
228
+ display += `\nšŸŽÆ **Feeling lucky?** Spin again for another recommendation!`;
229
+
230
+ if (gameState.totalRecommendations > 1) {
231
+ display += `\n\n*This is recommendation #${gameState.totalRecommendations} in your session*`;
232
+ }
233
+
234
+ return display;
235
+ }
236
+
237
+ // Get roulette statistics
238
+ function getRouletteStats(gameState) {
239
+ const { playHistory, totalRecommendations } = gameState;
240
+
241
+ if (playHistory.length === 0) {
242
+ return null;
243
+ }
244
+
245
+ // Category distribution
246
+ const categoryCount = {};
247
+ const difficultyCount = {};
248
+ const playerCount = {};
249
+
250
+ for (const game of playHistory) {
251
+ categoryCount[game.category] = (categoryCount[game.category] || 0) + 1;
252
+ difficultyCount[game.difficulty] = (difficultyCount[game.difficulty] || 0) + 1;
253
+ playerCount[game.players] = (playerCount[game.players] || 0) + 1;
254
+ }
255
+
256
+ const mostRecommendedCategory = Object.entries(categoryCount)
257
+ .sort(([,a], [,b]) => b - a)[0];
258
+
259
+ const mostRecommendedDifficulty = Object.entries(difficultyCount)
260
+ .sort(([,a], [,b]) => b - a)[0];
261
+
262
+ return {
263
+ totalRecommendations,
264
+ sessionGames: playHistory.length,
265
+ favoriteCategory: mostRecommendedCategory ? mostRecommendedCategory[0] : null,
266
+ favoriteDifficulty: mostRecommendedDifficulty ? mostRecommendedDifficulty[0] : null,
267
+ categoryDistribution: categoryCount,
268
+ difficultyDistribution: difficultyCount,
269
+ playerDistribution: playerCount
270
+ };
271
+ }
272
+
273
+ // Generate fun recommendation messages
274
+ function getRandomRouletteMessage() {
275
+ const messages = [
276
+ "šŸŽ² The roulette wheel is spinning...",
277
+ "šŸŽÆ Searching for your perfect game match...",
278
+ "šŸŽ° Rolling the dice of destiny...",
279
+ "šŸ”® Consulting the gaming crystal ball...",
280
+ "šŸŽŖ Welcome to the game carnival!",
281
+ "šŸš€ Launching game discovery sequence...",
282
+ "šŸŽØ Painting your gaming adventure...",
283
+ "⚔ Generating gaming lightning in a bottle...",
284
+ "🧩 Assembling your perfect game puzzle...",
285
+ "🌟 Aligning the gaming stars for you..."
286
+ ];
287
+
288
+ return messages[Math.floor(Math.random() * messages.length)];
289
+ }
290
+
291
+ module.exports = {
292
+ createInitialGameRouletteState,
293
+ getRandomGame,
294
+ getSmartRecommendation,
295
+ getRecommendationWithFilters,
296
+ getGamesByVibe,
297
+ formatRouletteDisplay,
298
+ getRouletteStats,
299
+ getRandomRouletteMessage
300
+ };