slashvibe-mcp 0.3.21 → 0.3.22
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/LICENSE +21 -0
- package/README.md +280 -47
- package/config.js +36 -31
- package/crypto.js +1 -6
- package/discord.js +19 -19
- package/index.js +217 -207
- package/intelligence/index.js +2 -9
- package/intelligence/infer.js +10 -16
- package/intelligence/patterns.js +23 -18
- package/intelligence/proactive.js +16 -15
- package/intelligence/serendipity.js +57 -20
- package/memory.js +13 -8
- package/notify.js +39 -14
- package/package.json +27 -20
- package/presence.js +2 -2
- package/prompts.js +5 -9
- package/protocol/index.js +123 -87
- package/protocol/telegram-commands.js +36 -37
- package/store/api.js +358 -529
- package/store/local.js +9 -10
- package/store/profiles.js +48 -192
- package/store/reservations.js +2 -9
- package/store/skills.js +69 -71
- package/store/sqlite.js +355 -0
- package/tools/_actions.js +48 -387
- package/tools/_connection-queue.js +45 -56
- package/tools/_discovery-enhanced.js +52 -57
- package/tools/_discovery.js +87 -185
- package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
- package/tools/{shipback.js → _experimental/shipback.js} +4 -3
- package/tools/_proactive-discovery.js +60 -73
- package/tools/_shared/index.js +41 -64
- package/tools/admin-inbox.js +10 -15
- package/tools/agents.js +1 -1
- package/tools/artifact-create.js +13 -23
- package/tools/artifact-view.js +4 -4
- package/tools/{_deprecated/back.js → back.js} +1 -1
- package/tools/bye.js +3 -5
- package/tools/consent.js +2 -2
- package/tools/context.js +9 -10
- package/tools/crossword.js +3 -2
- package/tools/discover.js +94 -356
- package/tools/dm.js +27 -86
- package/tools/doctor.js +12 -41
- package/tools/drawing.js +34 -20
- package/tools/echo.js +11 -11
- package/tools/feed.js +30 -58
- package/tools/follow.js +64 -187
- package/tools/{_deprecated/forget.js → forget.js} +4 -7
- package/tools/game.js +144 -48
- package/tools/handoff.js +6 -8
- package/tools/help.js +3 -3
- package/tools/idea.js +15 -27
- package/tools/inbox.js +121 -293
- package/tools/init.js +54 -151
- package/tools/invite.js +8 -21
- package/tools/migrate.js +27 -24
- package/tools/multiplayer-game.js +50 -40
- package/tools/{_deprecated/mute.js → mute.js} +4 -3
- package/tools/notifications.js +58 -48
- package/tools/observe.js +12 -15
- package/tools/onboarding.js +8 -11
- package/tools/open.js +13 -144
- package/tools/party-game.js +23 -12
- package/tools/patterns.js +2 -1
- package/tools/ping.js +5 -7
- package/tools/react.js +28 -30
- package/tools/{_deprecated/recall.js → recall.js} +5 -10
- package/tools/release.js +4 -2
- package/tools/{_deprecated/remember.js → remember.js} +4 -6
- package/tools/report.js +2 -2
- package/tools/request.js +6 -26
- package/tools/reserve.js +1 -1
- package/tools/session-fork.js +97 -0
- package/tools/session-save.js +109 -0
- package/tools/settings.js +30 -99
- package/tools/ship.js +74 -56
- package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
- package/tools/social-inbox.js +22 -28
- package/tools/social-post.js +24 -27
- package/tools/solo-game.js +54 -46
- package/tools/start.js +14 -148
- package/tools/status.js +21 -68
- package/tools/submit.js +4 -2
- package/tools/suggest-tags.js +36 -33
- package/tools/summarize.js +19 -16
- package/tools/tag-suggestions.js +72 -73
- package/tools/test.js +1 -1
- package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
- package/tools/token.js +4 -4
- package/tools/update.js +1 -2
- package/tools/watch.js +132 -112
- package/tools/who.js +20 -40
- package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
- package/tools/workshop-buddy.js +52 -53
- package/tools/x-mentions.js +0 -1
- package/tools/x-reply.js +0 -1
- package/twitter.js +14 -20
- package/version.json +8 -10
- package/analytics.js +0 -107
- package/auth-store.js +0 -148
- package/auto-update.js +0 -130
- package/bridges/bridge-monitor.js +0 -388
- package/bridges/discord-bot.js +0 -431
- package/bridges/farcaster.js +0 -299
- package/bridges/telegram.js +0 -261
- package/bridges/webhook-health.js +0 -420
- package/bridges/webhook-server.js +0 -437
- package/bridges/whatsapp.js +0 -441
- package/bridges/x-webhook.js +0 -423
- package/games/arcade.js +0 -406
- package/games/chess.js +0 -451
- package/games/colorguess.js +0 -343
- package/games/crossword-words.js +0 -171
- package/games/crossword.js +0 -461
- package/games/drawing.js +0 -347
- package/games/gameroulette.js +0 -300
- package/games/gamerouter.js +0 -336
- package/games/gamestatus.js +0 -337
- package/games/guessnumber.js +0 -209
- package/games/hangman.js +0 -279
- package/games/memory.js +0 -338
- package/games/multiplayer-tictactoe.js +0 -389
- package/games/pixelart.js +0 -399
- package/games/quickduel.js +0 -354
- package/games/riddle.js +0 -371
- package/games/rockpaperscissors.js +0 -291
- package/games/snake.js +0 -406
- package/games/storybuilder.js +0 -343
- package/games/tictactoe.js +0 -345
- package/games/twentyquestions.js +0 -286
- package/games/twotruths.js +0 -207
- package/games/werewolf.js +0 -508
- package/games/wordassociation.js +0 -247
- package/games/wordchain.js +0 -135
- package/intelligence/interests.js +0 -369
- package/notification-emitter.js +0 -77
- package/setup.js +0 -480
- package/smart-inbox.js +0 -276
- package/tools/_deprecated/auto-suggest-connections.js +0 -304
- package/tools/_deprecated/bootstrap-skills.js +0 -231
- package/tools/_deprecated/bridge-dashboard.js +0 -342
- package/tools/_deprecated/bridge-health.js +0 -400
- package/tools/_deprecated/bridge-live.js +0 -384
- package/tools/_deprecated/bridges.js +0 -383
- package/tools/_deprecated/colorguess.js +0 -281
- package/tools/_deprecated/discover-insights.js +0 -379
- package/tools/_deprecated/discover-momentum.js +0 -256
- package/tools/_deprecated/discovery-analytics.js +0 -345
- package/tools/_deprecated/discovery-auto-suggest.js +0 -275
- package/tools/_deprecated/discovery-bootstrap.js +0 -267
- package/tools/_deprecated/discovery-daily.js +0 -375
- package/tools/_deprecated/discovery-dashboard.js +0 -385
- package/tools/_deprecated/discovery-digest.js +0 -314
- package/tools/_deprecated/discovery-hub.js +0 -357
- package/tools/_deprecated/discovery-insights.js +0 -384
- package/tools/_deprecated/discovery-momentum.js +0 -281
- package/tools/_deprecated/discovery-monitor.js +0 -319
- package/tools/_deprecated/discovery-proactive.js +0 -300
- package/tools/_deprecated/draw.js +0 -317
- package/tools/_deprecated/farcaster.js +0 -307
- package/tools/_deprecated/games-catalog.js +0 -376
- package/tools/_deprecated/games.js +0 -313
- package/tools/_deprecated/guessnumber.js +0 -194
- package/tools/_deprecated/hangman.js +0 -129
- package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
- package/tools/_deprecated/riddle.js +0 -240
- package/tools/_deprecated/run-bootstrap.js +0 -69
- package/tools/_deprecated/skills-analytics.js +0 -349
- package/tools/_deprecated/skills-bootstrap.js +0 -301
- package/tools/_deprecated/skills-dashboard.js +0 -268
- package/tools/_deprecated/skills.js +0 -380
- package/tools/_deprecated/smart-intro.js +0 -353
- package/tools/_deprecated/storybuilder.js +0 -331
- package/tools/_deprecated/telegram-bot.js +0 -183
- package/tools/_deprecated/telegram-setup.js +0 -214
- package/tools/_deprecated/twentyquestions.js +0 -143
- package/tools/_shared.js +0 -234
- package/tools/_work-context.js +0 -338
- package/tools/_work-context.manual-test.js +0 -199
- package/tools/_work-context.test.js +0 -260
- package/tools/activity.js +0 -220
- package/tools/agent-treasury.js +0 -288
- package/tools/analytics.js +0 -191
- package/tools/approve.js +0 -197
- package/tools/arcade.js +0 -173
- package/tools/artifacts-price.js +0 -107
- package/tools/ask-expert.js +0 -160
- package/tools/available.js +0 -120
- package/tools/become-expert.js +0 -150
- package/tools/broadcast.js +0 -325
- package/tools/chat.js +0 -202
- package/tools/collaborative-drawing.js +0 -286
- package/tools/connection-status.js +0 -178
- package/tools/earnings.js +0 -126
- package/tools/friends.js +0 -207
- package/tools/genesis.js +0 -233
- package/tools/gig-browse.js +0 -206
- package/tools/gig-complete.js +0 -144
- package/tools/health.js +0 -87
- package/tools/leaderboard.js +0 -117
- package/tools/lib/git-apply.js +0 -206
- package/tools/lib/git-bundle.js +0 -407
- package/tools/mint.js +0 -377
- package/tools/plan.js +0 -225
- package/tools/profile.js +0 -219
- package/tools/proof-of-work.js +0 -144
- package/tools/pulse.js +0 -218
- package/tools/reply.js +0 -166
- package/tools/reputation.js +0 -175
- package/tools/schedule.js +0 -367
- package/tools/search-messages.js +0 -123
- package/tools/session.js +0 -467
- package/tools/session_price.js +0 -128
- package/tools/smart-check.js +0 -201
- package/tools/social-processor.js +0 -445
- package/tools/streak.js +0 -147
- package/tools/stuck.js +0 -297
- package/tools/subscribe.js +0 -148
- package/tools/subscriptions.js +0 -134
- package/tools/tip.js +0 -193
- package/tools/wallet.js +0 -269
- package/tools/webhook-test.js +0 -388
- package/tools/withdraw.js +0 -145
- package/tools/work-summary.js +0 -96
- package/tools/workshop.js +0 -327
- /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
- /package/tools/{l2.js → _experimental/l2.js} +0 -0
- /package/tools/{_deprecated/away.js → away.js} +0 -0
package/games/wordassociation.js
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Word Association game implementation for /vibe
|
|
3
|
-
* Players take turns saying words that associate with the previous word
|
|
4
|
-
* Build creative chains of connected ideas!
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Simple word validation
|
|
8
|
-
function isValidWord(word) {
|
|
9
|
-
// Basic checks: alphabetic, reasonable length, not too short
|
|
10
|
-
if (!/^[a-zA-Z\-']+$/.test(word)) return false; // Allow hyphens and apostrophes
|
|
11
|
-
if (word.length < 2) return false;
|
|
12
|
-
if (word.length > 30) return false;
|
|
13
|
-
|
|
14
|
-
// Allow single meaningful words
|
|
15
|
-
if (word.length === 1) {
|
|
16
|
-
return ['a', 'i'].includes(word.toLowerCase());
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Create initial word association state
|
|
23
|
-
function createInitialWordAssociationState() {
|
|
24
|
-
return {
|
|
25
|
-
words: [],
|
|
26
|
-
players: [],
|
|
27
|
-
currentPlayer: null,
|
|
28
|
-
moves: 0,
|
|
29
|
-
gameOver: false,
|
|
30
|
-
winner: null,
|
|
31
|
-
lastWord: null,
|
|
32
|
-
usedWords: new Set(), // Track words to prevent exact repeats
|
|
33
|
-
playerTurnIndex: 0,
|
|
34
|
-
maxWords: 30, // End game after 30 words for now
|
|
35
|
-
associations: [], // Store word -> word pairs for fun stats
|
|
36
|
-
startedAt: new Date().toISOString()
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Add player to game
|
|
41
|
-
function addPlayer(gameState, playerHandle) {
|
|
42
|
-
if (gameState.players.includes(playerHandle)) {
|
|
43
|
-
return { error: 'Player already in the game' };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (gameState.players.length >= 6) {
|
|
47
|
-
return { error: 'Game is full (max 6 players)' };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const newPlayers = [...gameState.players, playerHandle];
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
success: true,
|
|
54
|
-
gameState: {
|
|
55
|
-
...gameState,
|
|
56
|
-
players: newPlayers,
|
|
57
|
-
currentPlayer: gameState.currentPlayer || playerHandle // First player starts
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Make a word association move
|
|
63
|
-
function makeMove(gameState, word, playerHandle) {
|
|
64
|
-
const { words, players, currentPlayer, moves, usedWords, lastWord, maxWords } = gameState;
|
|
65
|
-
|
|
66
|
-
// Normalize word
|
|
67
|
-
const normalizedWord = word.toLowerCase().trim().replace(/['"]/g, '');
|
|
68
|
-
|
|
69
|
-
// Validate basic word format
|
|
70
|
-
if (!isValidWord(normalizedWord)) {
|
|
71
|
-
return { error: 'Invalid word format. Use only letters (2-30 characters).' };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Check if it's the right player's turn (if multiplayer)
|
|
75
|
-
if (players.length > 1 && currentPlayer !== playerHandle) {
|
|
76
|
-
return { error: `Not your turn! Waiting for @${currentPlayer}` };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check if word was already used (allow some flexibility)
|
|
80
|
-
if (usedWords.has(normalizedWord)) {
|
|
81
|
-
return { error: 'Word already used! Try a different word.' };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Check if it's the same word as last word
|
|
85
|
-
if (lastWord && normalizedWord === lastWord.toLowerCase()) {
|
|
86
|
-
return { error: 'Cannot repeat the same word immediately!' };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Valid move - update game state
|
|
90
|
-
const newWords = [...words, { word: normalizedWord, player: playerHandle, timestamp: new Date().toISOString() }];
|
|
91
|
-
const newUsedWords = new Set([...usedWords, normalizedWord]);
|
|
92
|
-
const newAssociations = lastWord ? [...gameState.associations, `${lastWord} → ${normalizedWord}`] : gameState.associations;
|
|
93
|
-
|
|
94
|
-
// Determine next player
|
|
95
|
-
let nextPlayerIndex = gameState.playerTurnIndex;
|
|
96
|
-
let nextPlayer = currentPlayer;
|
|
97
|
-
|
|
98
|
-
if (players.length > 1) {
|
|
99
|
-
nextPlayerIndex = (gameState.playerTurnIndex + 1) % players.length;
|
|
100
|
-
nextPlayer = players[nextPlayerIndex];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Check if game should end
|
|
104
|
-
const shouldEnd = moves + 1 >= maxWords;
|
|
105
|
-
|
|
106
|
-
const newGameState = {
|
|
107
|
-
...gameState,
|
|
108
|
-
words: newWords,
|
|
109
|
-
currentPlayer: shouldEnd ? null : nextPlayer,
|
|
110
|
-
playerTurnIndex: nextPlayerIndex,
|
|
111
|
-
moves: moves + 1,
|
|
112
|
-
gameOver: shouldEnd,
|
|
113
|
-
winner: shouldEnd ? 'everyone' : null,
|
|
114
|
-
lastWord: normalizedWord,
|
|
115
|
-
usedWords: newUsedWords,
|
|
116
|
-
associations: newAssociations
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
return { success: true, gameState: newGameState };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Format word association for display
|
|
123
|
-
function formatWordAssociationDisplay(gameState) {
|
|
124
|
-
const { words, players, currentPlayer, moves, gameOver, maxWords, associations } = gameState;
|
|
125
|
-
|
|
126
|
-
let display = `🧠 **Word Association** (${moves}/${maxWords} words)\n\n`;
|
|
127
|
-
|
|
128
|
-
if (gameOver) {
|
|
129
|
-
display += '🎉 **Game Complete!** What a creative journey!\n\n';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (words.length === 0) {
|
|
133
|
-
if (players.length === 0) {
|
|
134
|
-
display += '**Start with any word!**\n\n';
|
|
135
|
-
display += 'Word association is about connecting ideas. Each new word should relate to the previous one.\n';
|
|
136
|
-
} else {
|
|
137
|
-
display += `**@${currentPlayer}, start us off with any word!**\n\n`;
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
// Show the association chain (last 8 words to keep it readable)
|
|
141
|
-
const recentWords = words.slice(-8);
|
|
142
|
-
const wordChain = recentWords.map(w => {
|
|
143
|
-
const capitalizedWord = w.word.charAt(0).toUpperCase() + w.word.slice(1);
|
|
144
|
-
return `**${capitalizedWord}** (@${w.player})`;
|
|
145
|
-
}).join(' → ');
|
|
146
|
-
|
|
147
|
-
display += '**Chain:** ' + wordChain;
|
|
148
|
-
|
|
149
|
-
if (words.length > 8) {
|
|
150
|
-
display += ` (showing last 8 of ${words.length})`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
display += '\n\n';
|
|
154
|
-
|
|
155
|
-
if (!gameOver) {
|
|
156
|
-
const lastWordEntry = words[words.length - 1];
|
|
157
|
-
const lastWord = lastWordEntry.word;
|
|
158
|
-
display += `**"${lastWord.charAt(0).toUpperCase() + lastWord.slice(1)}" makes you think of...**\n\n`;
|
|
159
|
-
|
|
160
|
-
if (players.length > 1) {
|
|
161
|
-
display += `Turn: **@${currentPlayer}**`;
|
|
162
|
-
} else {
|
|
163
|
-
display += '**Your turn!** What word comes to mind?';
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Show players if multiplayer
|
|
169
|
-
if (players.length > 1) {
|
|
170
|
-
display += `\n\n**Players (${players.length}):** ${players.map(p => `@${p}`).join(', ')}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Show some fun stats at the end
|
|
174
|
-
if (gameOver && associations.length > 0) {
|
|
175
|
-
display += '\n\n**Creative Journey:**\n';
|
|
176
|
-
// Show the full chain condensed
|
|
177
|
-
const fullChain = words.map(w => w.word.charAt(0).toUpperCase() + w.word.slice(1)).join(' → ');
|
|
178
|
-
display += fullChain;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return display;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Get game stats for fun
|
|
185
|
-
function getGameStats(gameState) {
|
|
186
|
-
const { words, players, associations } = gameState;
|
|
187
|
-
|
|
188
|
-
if (words.length === 0) return null;
|
|
189
|
-
|
|
190
|
-
// Player contribution stats
|
|
191
|
-
const contributions = {};
|
|
192
|
-
words.forEach(w => {
|
|
193
|
-
contributions[w.player] = (contributions[w.player] || 0) + 1;
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
totalWords: words.length,
|
|
198
|
-
contributors: Object.keys(contributions).length,
|
|
199
|
-
contributions,
|
|
200
|
-
longestWord: Math.max(...words.map(w => w.word.length)),
|
|
201
|
-
shortestWord: Math.min(...words.map(w => w.word.length)),
|
|
202
|
-
uniqueWords: new Set(words.map(w => w.word)).size
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Check for interesting patterns or themes
|
|
207
|
-
function findThemes(gameState) {
|
|
208
|
-
const { words } = gameState;
|
|
209
|
-
|
|
210
|
-
if (words.length < 3) return [];
|
|
211
|
-
|
|
212
|
-
const themes = [];
|
|
213
|
-
const wordList = words.map(w => w.word.toLowerCase());
|
|
214
|
-
|
|
215
|
-
// Look for color words
|
|
216
|
-
const colors = ['red', 'blue', 'green', 'yellow', 'orange', 'purple', 'pink', 'black', 'white', 'brown', 'gray', 'grey'];
|
|
217
|
-
const colorWords = wordList.filter(w => colors.includes(w));
|
|
218
|
-
if (colorWords.length >= 2) {
|
|
219
|
-
themes.push(`🌈 Colors: ${colorWords.join(', ')}`);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Look for animal words
|
|
223
|
-
const animals = ['cat', 'dog', 'bird', 'fish', 'mouse', 'lion', 'tiger', 'bear', 'wolf', 'fox', 'rabbit', 'horse', 'cow', 'pig', 'sheep'];
|
|
224
|
-
const animalWords = wordList.filter(w => animals.some(animal => w.includes(animal) || animal.includes(w)));
|
|
225
|
-
if (animalWords.length >= 2) {
|
|
226
|
-
themes.push(`🐾 Animals: ${animalWords.join(', ')}`);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Look for nature words
|
|
230
|
-
const nature = ['tree', 'flower', 'water', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'mountain', 'ocean', 'forest'];
|
|
231
|
-
const natureWords = wordList.filter(w => nature.some(n => w.includes(n) || n.includes(w)));
|
|
232
|
-
if (natureWords.length >= 2) {
|
|
233
|
-
themes.push(`🌿 Nature: ${natureWords.join(', ')}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return themes;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
module.exports = {
|
|
240
|
-
createInitialWordAssociationState,
|
|
241
|
-
addPlayer,
|
|
242
|
-
makeMove,
|
|
243
|
-
formatWordAssociationDisplay,
|
|
244
|
-
isValidWord,
|
|
245
|
-
getGameStats,
|
|
246
|
-
findThemes
|
|
247
|
-
};
|
package/games/wordchain.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Word Chain game implementation for /vibe
|
|
3
|
-
* Players take turns saying words that start with the last letter of the previous word
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// Simple word validation - checks if it's a reasonable English word
|
|
7
|
-
// In a full implementation, this could use a proper dictionary API
|
|
8
|
-
function isValidWord(word) {
|
|
9
|
-
// Basic checks: alphabetic, reasonable length, not too short
|
|
10
|
-
if (!/^[a-zA-Z]+$/.test(word)) return false;
|
|
11
|
-
if (word.length < 2) return false;
|
|
12
|
-
if (word.length > 20) return false;
|
|
13
|
-
|
|
14
|
-
// Some very basic filtering of common valid words vs nonsense
|
|
15
|
-
// This is a simplified check - a real game would use a dictionary
|
|
16
|
-
const commonPrefixes = ['th', 'he', 'in', 'er', 'an', 're', 'ed', 'nd', 'ou', 'ea', 'ti', 'to', 'it', 'st', 'io', 'le', 'is', 'on', 'ur', 'ar', 'nt', 'al'];
|
|
17
|
-
const commonSuffixes = ['ed', 'er', 'ing', 'ly', 'est', 'ion', 'tion', 'sion', 'ness', 'ment', 'able', 'ible'];
|
|
18
|
-
|
|
19
|
-
const lowerWord = word.toLowerCase();
|
|
20
|
-
|
|
21
|
-
// Allow single letters for now (like 'a', 'i')
|
|
22
|
-
if (word.length === 1) {
|
|
23
|
-
return ['a', 'i'].includes(lowerWord);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Check for common patterns that suggest it's a real word
|
|
27
|
-
const hasCommonPrefix = commonPrefixes.some(prefix => lowerWord.startsWith(prefix));
|
|
28
|
-
const hasCommonSuffix = commonSuffixes.some(suffix => lowerWord.endsWith(suffix));
|
|
29
|
-
|
|
30
|
-
// Very permissive check - if it looks word-like, allow it
|
|
31
|
-
// Real implementation would check against dictionary
|
|
32
|
-
return hasCommonPrefix || hasCommonSuffix || lowerWord.length >= 3;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Create initial word chain state
|
|
36
|
-
function createInitialWordChainState() {
|
|
37
|
-
return {
|
|
38
|
-
words: [],
|
|
39
|
-
currentPlayer: 'player1', // player1 starts (the person who initiated the game)
|
|
40
|
-
moves: 0,
|
|
41
|
-
gameOver: false,
|
|
42
|
-
winner: null,
|
|
43
|
-
lastLetter: null,
|
|
44
|
-
usedWords: new Set() // Track words to prevent repeats
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Make a word chain move
|
|
49
|
-
function makeMove(gameState, word, isPlayer1Move) {
|
|
50
|
-
const { words, currentPlayer, moves, usedWords, lastLetter } = gameState;
|
|
51
|
-
|
|
52
|
-
// Normalize word
|
|
53
|
-
const normalizedWord = word.toLowerCase().trim();
|
|
54
|
-
|
|
55
|
-
// Validate basic word format
|
|
56
|
-
if (!isValidWord(normalizedWord)) {
|
|
57
|
-
return { error: 'Invalid word format. Use only letters, 2-20 characters.' };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check if it's the right player's turn
|
|
61
|
-
const expectedPlayer = currentPlayer === 'player1' ? true : false;
|
|
62
|
-
if (isPlayer1Move !== expectedPlayer) {
|
|
63
|
-
return { error: 'Not your turn!' };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check if word was already used
|
|
67
|
-
if (usedWords.has(normalizedWord)) {
|
|
68
|
-
return { error: 'Word already used! Try a different word.' };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Check if word starts with the required letter
|
|
72
|
-
if (lastLetter && normalizedWord[0] !== lastLetter) {
|
|
73
|
-
return { error: `Word must start with "${lastLetter.toUpperCase()}"` };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Valid move - update game state
|
|
77
|
-
const newWords = [...words, normalizedWord];
|
|
78
|
-
const newUsedWords = new Set([...usedWords, normalizedWord]);
|
|
79
|
-
const newLastLetter = normalizedWord[normalizedWord.length - 1];
|
|
80
|
-
const newCurrentPlayer = currentPlayer === 'player1' ? 'player2' : 'player1';
|
|
81
|
-
|
|
82
|
-
const newGameState = {
|
|
83
|
-
words: newWords,
|
|
84
|
-
currentPlayer: newCurrentPlayer,
|
|
85
|
-
moves: moves + 1,
|
|
86
|
-
gameOver: false,
|
|
87
|
-
winner: null,
|
|
88
|
-
lastLetter: newLastLetter,
|
|
89
|
-
usedWords: newUsedWords,
|
|
90
|
-
lastWord: normalizedWord
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return { success: true, gameState: newGameState };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Format word chain for display
|
|
97
|
-
function formatWordChainDisplay(gameState) {
|
|
98
|
-
const { words, currentPlayer, moves, lastLetter, lastWord } = gameState;
|
|
99
|
-
|
|
100
|
-
let display = `🔗 **Word Chain** (${moves} words)\n\n`;
|
|
101
|
-
|
|
102
|
-
if (words.length === 0) {
|
|
103
|
-
display += '**Start the chain with any word!**\n\n';
|
|
104
|
-
display += 'Next: **Player 1**';
|
|
105
|
-
} else {
|
|
106
|
-
// Show the chain
|
|
107
|
-
display += '**Chain:** ' + words.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' → ') + '\n\n';
|
|
108
|
-
|
|
109
|
-
if (lastLetter) {
|
|
110
|
-
display += `**Next word must start with: "${lastLetter.toUpperCase()}"**\n\n`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
display += `Turn: **${currentPlayer === 'player1' ? 'Player 1' : 'Player 2'}**`;
|
|
114
|
-
|
|
115
|
-
if (lastWord) {
|
|
116
|
-
display += `\nLast word: "${lastWord.charAt(0).toUpperCase() + lastWord.slice(1)}"`;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return display;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Check if a letter is considered "difficult" (fewer common words start with it)
|
|
124
|
-
function isDifficultLetter(letter) {
|
|
125
|
-
const difficultLetters = ['x', 'z', 'q', 'j', 'v'];
|
|
126
|
-
return difficultLetters.includes(letter.toLowerCase());
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
module.exports = {
|
|
130
|
-
createInitialWordChainState,
|
|
131
|
-
makeMove,
|
|
132
|
-
formatWordChainDisplay,
|
|
133
|
-
isValidWord,
|
|
134
|
-
isDifficultLetter
|
|
135
|
-
};
|