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.
- package/README.md +47 -252
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js → _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js → _deprecated/away.js} +0 -0
- /package/tools/{back.js → _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js → _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js → _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js → _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js → _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js → _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js → _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js → _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js → _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js → _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js → _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js → _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js → _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js → _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js → _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js → _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js → _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js → _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js → _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js → _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js → _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js → _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js → _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js → _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js → _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js → _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js → _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js → _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js → _deprecated/mute.js} +0 -0
- /package/tools/{recall.js → _deprecated/recall.js} +0 -0
- /package/tools/{remember.js → _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js → _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js → _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js → _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js → _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js → _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js → _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js → _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js → _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js → _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js → _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js → _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js → _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js → _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js → _deprecated/wordassociation.js} +0 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 20 Questions game implementation for /vibe
|
|
3
|
+
* One player thinks of something, the other tries to guess it in 20 yes/no questions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Categories and example items for random selection
|
|
7
|
+
const CATEGORIES = {
|
|
8
|
+
animals: [
|
|
9
|
+
'elephant', 'dolphin', 'penguin', 'tiger', 'butterfly', 'octopus', 'kangaroo',
|
|
10
|
+
'giraffe', 'owl', 'shark', 'panda', 'wolf', 'eagle', 'whale', 'lion',
|
|
11
|
+
'monkey', 'bear', 'rabbit', 'snake', 'turtle', 'fox', 'deer', 'bat'
|
|
12
|
+
],
|
|
13
|
+
objects: [
|
|
14
|
+
'smartphone', 'guitar', 'bicycle', 'telescope', 'clock', 'umbrella', 'camera',
|
|
15
|
+
'mirror', 'keyboard', 'lamp', 'backpack', 'compass', 'hammer', 'scissors',
|
|
16
|
+
'calculator', 'headphones', 'pencil', 'book', 'chair', 'door', 'window', 'table'
|
|
17
|
+
],
|
|
18
|
+
food: [
|
|
19
|
+
'pizza', 'chocolate', 'apple', 'sushi', 'ice cream', 'hamburger', 'pasta',
|
|
20
|
+
'banana', 'cheese', 'bread', 'cookie', 'sandwich', 'orange', 'cake',
|
|
21
|
+
'popcorn', 'honey', 'soup', 'salad', 'coffee', 'tea', 'yogurt', 'rice'
|
|
22
|
+
],
|
|
23
|
+
places: [
|
|
24
|
+
'library', 'beach', 'mountain', 'forest', 'castle', 'hospital', 'school',
|
|
25
|
+
'museum', 'theater', 'restaurant', 'park', 'bridge', 'lighthouse', 'cave',
|
|
26
|
+
'waterfall', 'desert', 'island', 'city', 'village', 'garden', 'zoo', 'airport'
|
|
27
|
+
],
|
|
28
|
+
activities: [
|
|
29
|
+
'swimming', 'reading', 'dancing', 'cooking', 'painting', 'singing', 'running',
|
|
30
|
+
'climbing', 'writing', 'fishing', 'gardening', 'photography', 'camping',
|
|
31
|
+
'hiking', 'surfing', 'skiing', 'cycling', 'shopping', 'traveling', 'studying'
|
|
32
|
+
]
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Get random item from a category or all categories
|
|
36
|
+
function getRandomItem(category = null) {
|
|
37
|
+
if (category && CATEGORIES[category]) {
|
|
38
|
+
const items = CATEGORIES[category];
|
|
39
|
+
return {
|
|
40
|
+
item: items[Math.floor(Math.random() * items.length)],
|
|
41
|
+
category: category
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
// Random from all categories
|
|
45
|
+
const allCategories = Object.keys(CATEGORIES);
|
|
46
|
+
const randomCategory = allCategories[Math.floor(Math.random() * allCategories.length)];
|
|
47
|
+
return getRandomItem(randomCategory);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create initial 20 questions state
|
|
52
|
+
function createInitialTwentyQuestionsState(mode = 'guess', customItem = null, category = null) {
|
|
53
|
+
const randomSelection = customItem ?
|
|
54
|
+
{ item: customItem.toLowerCase(), category: 'custom' } :
|
|
55
|
+
getRandomItem(category);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
mode: mode, // 'guess' = player guesses AI's item, 'think' = AI guesses player's item
|
|
59
|
+
item: randomSelection.item,
|
|
60
|
+
category: randomSelection.category,
|
|
61
|
+
questions: [],
|
|
62
|
+
questionsLeft: 20,
|
|
63
|
+
gameOver: false,
|
|
64
|
+
won: false,
|
|
65
|
+
currentQuestion: null,
|
|
66
|
+
moves: 0,
|
|
67
|
+
guesses: [] // Track actual guesses (not just questions)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Process a question (for 'guess' mode where player asks questions)
|
|
72
|
+
function askQuestion(gameState, question) {
|
|
73
|
+
const { item, questions, questionsLeft, gameOver, moves } = gameState;
|
|
74
|
+
|
|
75
|
+
if (gameOver) {
|
|
76
|
+
return { error: 'Game is over! Start a new game to play again.' };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (questionsLeft <= 0) {
|
|
80
|
+
return { error: 'No questions left! Make your final guess.' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Normalize question
|
|
84
|
+
const normalizedQuestion = question.toLowerCase().trim();
|
|
85
|
+
|
|
86
|
+
// Check if it's a guess rather than a yes/no question
|
|
87
|
+
if (normalizedQuestion.startsWith('is it ')) {
|
|
88
|
+
const guess = normalizedQuestion.substring(6);
|
|
89
|
+
return processGuess(gameState, guess);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Simple AI to answer yes/no questions
|
|
93
|
+
const answer = answerQuestion(normalizedQuestion, item, gameState.category);
|
|
94
|
+
|
|
95
|
+
const newQuestions = [...questions, { question, answer, questionNumber: moves + 1 }];
|
|
96
|
+
const newQuestionsLeft = questionsLeft - 1;
|
|
97
|
+
const newMoves = moves + 1;
|
|
98
|
+
|
|
99
|
+
const newGameState = {
|
|
100
|
+
...gameState,
|
|
101
|
+
questions: newQuestions,
|
|
102
|
+
questionsLeft: newQuestionsLeft,
|
|
103
|
+
moves: newMoves,
|
|
104
|
+
currentQuestion: { question, answer }
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Check if no questions left
|
|
108
|
+
if (newQuestionsLeft === 0) {
|
|
109
|
+
return {
|
|
110
|
+
success: true,
|
|
111
|
+
gameState: {
|
|
112
|
+
...newGameState,
|
|
113
|
+
message: `That's all 20 questions! What's your final guess?`
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { success: true, gameState: newGameState };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Process a final guess
|
|
122
|
+
function processGuess(gameState, guess) {
|
|
123
|
+
const { item, guesses, gameOver } = gameState;
|
|
124
|
+
|
|
125
|
+
if (gameOver) {
|
|
126
|
+
return { error: 'Game is already over!' };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const normalizedGuess = guess.toLowerCase().trim();
|
|
130
|
+
const normalizedItem = item.toLowerCase().trim();
|
|
131
|
+
|
|
132
|
+
// Check for exact match or close match
|
|
133
|
+
const isCorrect = normalizedGuess === normalizedItem ||
|
|
134
|
+
normalizedItem.includes(normalizedGuess) ||
|
|
135
|
+
normalizedGuess.includes(normalizedItem);
|
|
136
|
+
|
|
137
|
+
const newGuesses = [...guesses, { guess: normalizedGuess, correct: isCorrect }];
|
|
138
|
+
|
|
139
|
+
const newGameState = {
|
|
140
|
+
...gameState,
|
|
141
|
+
guesses: newGuesses,
|
|
142
|
+
gameOver: true,
|
|
143
|
+
won: isCorrect,
|
|
144
|
+
finalGuess: normalizedGuess
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return { success: true, gameState: newGameState };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Simple AI to answer yes/no questions about an item
|
|
151
|
+
function answerQuestion(question, item, category) {
|
|
152
|
+
const q = question.toLowerCase();
|
|
153
|
+
const itemLower = item.toLowerCase();
|
|
154
|
+
|
|
155
|
+
// Size-related questions
|
|
156
|
+
if (q.includes('big') || q.includes('large')) {
|
|
157
|
+
const bigThings = ['elephant', 'whale', 'giraffe', 'mountain', 'castle', 'tree', 'building', 'car', 'house'];
|
|
158
|
+
return bigThings.some(thing => itemLower.includes(thing) || thing.includes(itemLower)) ? 'yes' : 'no';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (q.includes('small') || q.includes('tiny')) {
|
|
162
|
+
const smallThings = ['butterfly', 'mouse', 'ant', 'coin', 'key', 'pencil', 'ring', 'watch'];
|
|
163
|
+
return smallThings.some(thing => itemLower.includes(thing) || thing.includes(itemLower)) ? 'yes' : 'no';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Category questions
|
|
167
|
+
if (q.includes('animal') || q.includes('creature') || q.includes('living thing')) {
|
|
168
|
+
return category === 'animals' ? 'yes' : 'no';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (q.includes('food') || q.includes('eat') || q.includes('edible')) {
|
|
172
|
+
return category === 'food' ? 'yes' : 'no';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (q.includes('place') || q.includes('location') || q.includes('building')) {
|
|
176
|
+
return category === 'places' ? 'yes' : 'no';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Color questions
|
|
180
|
+
if (q.includes('red')) {
|
|
181
|
+
const redThings = ['apple', 'fire truck', 'rose', 'blood', 'strawberry', 'tomato'];
|
|
182
|
+
return redThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (q.includes('blue')) {
|
|
186
|
+
const blueThings = ['ocean', 'sky', 'whale', 'blueberry'];
|
|
187
|
+
return blueThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (q.includes('green')) {
|
|
191
|
+
const greenThings = ['tree', 'grass', 'frog', 'leaf', 'lettuce', 'cucumber'];
|
|
192
|
+
return greenThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Physical properties
|
|
196
|
+
if (q.includes('fly') || q.includes('flies')) {
|
|
197
|
+
const flyingThings = ['bird', 'butterfly', 'bat', 'plane', 'helicopter', 'eagle', 'owl'];
|
|
198
|
+
return flyingThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (q.includes('swim')) {
|
|
202
|
+
const swimmingThings = ['fish', 'dolphin', 'shark', 'whale', 'turtle', 'penguin'];
|
|
203
|
+
return swimmingThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (q.includes('water')) {
|
|
207
|
+
const waterThings = ['ocean', 'river', 'fish', 'boat', 'swimming', 'whale', 'dolphin'];
|
|
208
|
+
return waterThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Usage questions
|
|
212
|
+
if (q.includes('use') || q.includes('tool')) {
|
|
213
|
+
return category === 'objects' ? 'yes' : 'no';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (q.includes('electronic') || q.includes('electricity') || q.includes('power')) {
|
|
217
|
+
const electronicThings = ['phone', 'computer', 'tv', 'radio', 'camera', 'calculator', 'lamp'];
|
|
218
|
+
return electronicThings.some(thing => itemLower.includes(thing)) ? 'yes' : 'no';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Default responses for unhandled questions
|
|
222
|
+
const yesWords = ['can', 'does', 'has', 'would', 'could'];
|
|
223
|
+
const hasYesWord = yesWords.some(word => q.includes(word));
|
|
224
|
+
|
|
225
|
+
// Give somewhat random but consistent answers for unrecognized questions
|
|
226
|
+
const hash = question.split('').reduce((a, b) => {
|
|
227
|
+
a = ((a << 5) - a) + b.charCodeAt(0);
|
|
228
|
+
return a & a;
|
|
229
|
+
}, 0);
|
|
230
|
+
|
|
231
|
+
return Math.abs(hash) % 2 === 0 ? 'yes' : 'no';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Format 20 questions display
|
|
235
|
+
function formatTwentyQuestionsDisplay(gameState) {
|
|
236
|
+
const { questions, questionsLeft, gameOver, won, category, currentQuestion, guesses, finalGuess, item } = gameState;
|
|
237
|
+
|
|
238
|
+
let display = `🤔 **20 Questions** (${20 - questionsLeft}/20 questions used)\n\n`;
|
|
239
|
+
|
|
240
|
+
if (gameOver) {
|
|
241
|
+
if (won) {
|
|
242
|
+
display += `🎉 **You got it!** The answer was "${item}"!\n`;
|
|
243
|
+
display += `Solved in ${questions.length} questions!`;
|
|
244
|
+
} else {
|
|
245
|
+
display += `💀 **Game Over!** The answer was "${item}"\n`;
|
|
246
|
+
if (finalGuess) {
|
|
247
|
+
display += `Your guess: "${finalGuess}"`;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
display += `**Category hint:** ${category}\n`;
|
|
252
|
+
display += `**Questions remaining:** ${questionsLeft}\n\n`;
|
|
253
|
+
|
|
254
|
+
// Show recent questions
|
|
255
|
+
if (questions.length > 0) {
|
|
256
|
+
display += '**Recent questions:**\n';
|
|
257
|
+
const recentQuestions = questions.slice(-3); // Show last 3
|
|
258
|
+
for (const q of recentQuestions) {
|
|
259
|
+
display += `${q.questionNumber}. "${q.question}" → **${q.answer}**\n`;
|
|
260
|
+
}
|
|
261
|
+
display += '\n';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (currentQuestion) {
|
|
265
|
+
display += `Last answer: **${currentQuestion.answer}**\n\n`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (questionsLeft > 0) {
|
|
269
|
+
display += '**Ask a yes/no question or make a guess!**\n';
|
|
270
|
+
display += 'Examples: "Is it alive?", "Is it bigger than a car?", "Is it a cat?"';
|
|
271
|
+
} else {
|
|
272
|
+
display += '**No questions left! What\'s your final guess?**';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return display;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = {
|
|
280
|
+
createInitialTwentyQuestionsState,
|
|
281
|
+
askQuestion,
|
|
282
|
+
processGuess,
|
|
283
|
+
formatTwentyQuestionsDisplay,
|
|
284
|
+
getRandomItem,
|
|
285
|
+
CATEGORIES
|
|
286
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two Truths and a Lie - Social ice breaker game for /vibe
|
|
3
|
+
*
|
|
4
|
+
* How it works:
|
|
5
|
+
* 1. One player shares 3 statements about themselves
|
|
6
|
+
* 2. Other players guess which one is the lie
|
|
7
|
+
* 3. Points for correct guesses, points for fooling others
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Create initial game state
|
|
11
|
+
function createInitialState(host) {
|
|
12
|
+
return {
|
|
13
|
+
host,
|
|
14
|
+
phase: 'setup', // setup, guessing, reveal
|
|
15
|
+
statements: [], // Array of 3 statements
|
|
16
|
+
lieIndex: null, // Which statement is the lie (0, 1, or 2)
|
|
17
|
+
guesses: {}, // { playerHandle: guessIndex }
|
|
18
|
+
scores: {}, // { playerHandle: score }
|
|
19
|
+
round: 1,
|
|
20
|
+
createdAt: new Date().toISOString()
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Host submits their 3 statements
|
|
25
|
+
function submitStatements(gameState, statements, lieIndex) {
|
|
26
|
+
if (gameState.phase !== 'setup') {
|
|
27
|
+
return { error: 'Statements already submitted' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!Array.isArray(statements) || statements.length !== 3) {
|
|
31
|
+
return { error: 'Must provide exactly 3 statements' };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (lieIndex < 0 || lieIndex > 2) {
|
|
35
|
+
return { error: 'Lie index must be 0, 1, or 2' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
gameState: {
|
|
41
|
+
...gameState,
|
|
42
|
+
statements,
|
|
43
|
+
lieIndex,
|
|
44
|
+
phase: 'guessing'
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Player makes a guess
|
|
50
|
+
function makeGuess(gameState, player, guessIndex) {
|
|
51
|
+
if (gameState.phase !== 'guessing') {
|
|
52
|
+
return { error: 'Not in guessing phase' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (player === gameState.host) {
|
|
56
|
+
return { error: 'Host cannot guess their own statements' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (guessIndex < 0 || guessIndex > 2) {
|
|
60
|
+
return { error: 'Guess must be 0, 1, or 2 (which statement is the lie)' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const newGuesses = { ...gameState.guesses, [player]: guessIndex };
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
gameState: {
|
|
68
|
+
...gameState,
|
|
69
|
+
guesses: newGuesses
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Reveal the answer and calculate scores
|
|
75
|
+
function reveal(gameState) {
|
|
76
|
+
if (gameState.phase !== 'guessing') {
|
|
77
|
+
return { error: 'Not in guessing phase' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { lieIndex, guesses, host } = gameState;
|
|
81
|
+
const newScores = { ...gameState.scores };
|
|
82
|
+
|
|
83
|
+
// Initialize host score if needed
|
|
84
|
+
if (!newScores[host]) newScores[host] = 0;
|
|
85
|
+
|
|
86
|
+
const correctGuessers = [];
|
|
87
|
+
const fooledPlayers = [];
|
|
88
|
+
|
|
89
|
+
for (const [player, guess] of Object.entries(guesses)) {
|
|
90
|
+
if (!newScores[player]) newScores[player] = 0;
|
|
91
|
+
|
|
92
|
+
if (guess === lieIndex) {
|
|
93
|
+
// Correct guess: +1 point to guesser
|
|
94
|
+
newScores[player] += 1;
|
|
95
|
+
correctGuessers.push(player);
|
|
96
|
+
} else {
|
|
97
|
+
// Wrong guess: +1 point to host for fooling them
|
|
98
|
+
newScores[host] += 1;
|
|
99
|
+
fooledPlayers.push(player);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
gameState: {
|
|
106
|
+
...gameState,
|
|
107
|
+
phase: 'reveal',
|
|
108
|
+
scores: newScores
|
|
109
|
+
},
|
|
110
|
+
results: {
|
|
111
|
+
lieIndex,
|
|
112
|
+
lieStatement: gameState.statements[lieIndex],
|
|
113
|
+
correctGuessers,
|
|
114
|
+
fooledPlayers,
|
|
115
|
+
scores: newScores
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Start a new round with a new host
|
|
121
|
+
function newRound(gameState, newHost) {
|
|
122
|
+
return {
|
|
123
|
+
success: true,
|
|
124
|
+
gameState: {
|
|
125
|
+
...gameState,
|
|
126
|
+
host: newHost,
|
|
127
|
+
phase: 'setup',
|
|
128
|
+
statements: [],
|
|
129
|
+
lieIndex: null,
|
|
130
|
+
guesses: {},
|
|
131
|
+
round: gameState.round + 1
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Format game display
|
|
137
|
+
function formatDisplay(gameState, viewerHandle) {
|
|
138
|
+
const { host, phase, statements, guesses, scores, round, lieIndex } = gameState;
|
|
139
|
+
const isHost = viewerHandle === host;
|
|
140
|
+
|
|
141
|
+
let display = `🎭 **Two Truths and a Lie** (Round ${round})\n`;
|
|
142
|
+
display += `Host: @${host}\n\n`;
|
|
143
|
+
|
|
144
|
+
if (phase === 'setup') {
|
|
145
|
+
if (isHost) {
|
|
146
|
+
display += `**Your turn!** Share 3 statements about yourself.\n`;
|
|
147
|
+
display += `Use: \`vibe twotruths --statements "truth1" "truth2" "lie" --lie 2\`\n`;
|
|
148
|
+
display += `(--lie indicates which statement number is the lie: 0, 1, or 2)\n`;
|
|
149
|
+
} else {
|
|
150
|
+
display += `Waiting for @${host} to share their statements...\n`;
|
|
151
|
+
}
|
|
152
|
+
} else if (phase === 'guessing') {
|
|
153
|
+
display += `**Which one is the lie?**\n\n`;
|
|
154
|
+
statements.forEach((stmt, i) => {
|
|
155
|
+
const marker = isHost ? (i === lieIndex ? '🤫' : '✓') : `${i + 1}.`;
|
|
156
|
+
display += `${marker} ${stmt}\n`;
|
|
157
|
+
});
|
|
158
|
+
display += '\n';
|
|
159
|
+
|
|
160
|
+
if (isHost) {
|
|
161
|
+
const guessCount = Object.keys(guesses).length;
|
|
162
|
+
display += `${guessCount} player(s) have guessed.\n`;
|
|
163
|
+
display += `Use \`vibe twotruths --reveal\` when ready to reveal!\n`;
|
|
164
|
+
} else {
|
|
165
|
+
if (guesses[viewerHandle] !== undefined) {
|
|
166
|
+
display += `✓ You guessed statement #${guesses[viewerHandle] + 1}\n`;
|
|
167
|
+
} else {
|
|
168
|
+
display += `Use \`vibe twotruths --guess 1\` (or 2 or 3) to guess the lie!\n`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else if (phase === 'reveal') {
|
|
172
|
+
display += `**The lie was:**\n`;
|
|
173
|
+
statements.forEach((stmt, i) => {
|
|
174
|
+
const marker = i === lieIndex ? '❌ LIE:' : '✓ TRUE:';
|
|
175
|
+
display += `${marker} ${stmt}\n`;
|
|
176
|
+
});
|
|
177
|
+
display += '\n';
|
|
178
|
+
|
|
179
|
+
// Show who guessed what
|
|
180
|
+
display += `**Guesses:**\n`;
|
|
181
|
+
for (const [player, guess] of Object.entries(guesses)) {
|
|
182
|
+
const correct = guess === lieIndex;
|
|
183
|
+
display += `@${player}: #${guess + 1} ${correct ? '✓' : '✗'}\n`;
|
|
184
|
+
}
|
|
185
|
+
display += '\n';
|
|
186
|
+
|
|
187
|
+
// Show scores
|
|
188
|
+
display += `**Scores:**\n`;
|
|
189
|
+
const sortedScores = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
190
|
+
sortedScores.forEach(([player, score]) => {
|
|
191
|
+
display += `@${player}: ${score} pts\n`;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
display += `\nUse \`vibe twotruths --newround @someone\` to start next round!\n`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return display;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
createInitialState,
|
|
202
|
+
submitStatements,
|
|
203
|
+
makeGuess,
|
|
204
|
+
reveal,
|
|
205
|
+
newRound,
|
|
206
|
+
formatDisplay
|
|
207
|
+
};
|