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,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Werewolf - Social deduction game for /vibe
|
|
3
|
+
*
|
|
4
|
+
* A party game where villagers try to identify the werewolves
|
|
5
|
+
* before they're all eliminated.
|
|
6
|
+
*
|
|
7
|
+
* Roles:
|
|
8
|
+
* - Villager: Vote to eliminate werewolves during the day
|
|
9
|
+
* - Werewolf: Kill villagers at night, blend in during day
|
|
10
|
+
* - Seer: Can reveal one player's role each night
|
|
11
|
+
*
|
|
12
|
+
* Win conditions:
|
|
13
|
+
* - Villagers win: All werewolves eliminated
|
|
14
|
+
* - Werewolves win: Werewolves equal or outnumber villagers
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const ROLES = {
|
|
18
|
+
VILLAGER: 'villager',
|
|
19
|
+
WEREWOLF: 'werewolf',
|
|
20
|
+
SEER: 'seer'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const PHASES = {
|
|
24
|
+
LOBBY: 'lobby', // Waiting for players
|
|
25
|
+
NIGHT: 'night', // Werewolves choose victim, seer investigates
|
|
26
|
+
DAY: 'day', // Discussion
|
|
27
|
+
VOTING: 'voting', // Vote to eliminate
|
|
28
|
+
ENDED: 'ended' // Game over
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Role distribution based on player count
|
|
32
|
+
function getRoleDistribution(playerCount) {
|
|
33
|
+
if (playerCount < 4) return null;
|
|
34
|
+
|
|
35
|
+
// 4-5 players: 1 werewolf, 1 seer, rest villagers
|
|
36
|
+
// 6-7 players: 2 werewolves, 1 seer, rest villagers
|
|
37
|
+
// 8+ players: 2 werewolves, 1 seer, rest villagers
|
|
38
|
+
|
|
39
|
+
const werewolfCount = playerCount >= 6 ? 2 : 1;
|
|
40
|
+
const seerCount = 1;
|
|
41
|
+
const villagerCount = playerCount - werewolfCount - seerCount;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
[ROLES.WEREWOLF]: werewolfCount,
|
|
45
|
+
[ROLES.SEER]: seerCount,
|
|
46
|
+
[ROLES.VILLAGER]: villagerCount
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Shuffle array (Fisher-Yates)
|
|
51
|
+
function shuffle(array) {
|
|
52
|
+
const arr = [...array];
|
|
53
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
54
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
55
|
+
[arr[i], arr[j]] = [arr[j], arr[i]];
|
|
56
|
+
}
|
|
57
|
+
return arr;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create initial game state
|
|
61
|
+
function createInitialState(host) {
|
|
62
|
+
return {
|
|
63
|
+
host,
|
|
64
|
+
phase: PHASES.LOBBY,
|
|
65
|
+
players: [host],
|
|
66
|
+
roles: {}, // { handle: role }
|
|
67
|
+
alive: [], // handles of living players
|
|
68
|
+
dead: [], // { handle, role, eliminatedBy: 'werewolf'|'vote' }
|
|
69
|
+
round: 0,
|
|
70
|
+
|
|
71
|
+
// Night actions
|
|
72
|
+
werewolfTarget: null,
|
|
73
|
+
seerTarget: null,
|
|
74
|
+
seerReveals: {}, // { handle: role } - what seer has learned
|
|
75
|
+
|
|
76
|
+
// Voting
|
|
77
|
+
votes: {}, // { voter: target }
|
|
78
|
+
|
|
79
|
+
// History
|
|
80
|
+
events: [],
|
|
81
|
+
|
|
82
|
+
createdAt: new Date().toISOString()
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Join the game
|
|
87
|
+
function joinGame(gameState, player) {
|
|
88
|
+
if (gameState.phase !== PHASES.LOBBY) {
|
|
89
|
+
return { error: 'Game already started' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (gameState.players.includes(player)) {
|
|
93
|
+
return { error: 'Already in the game' };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (gameState.players.length >= 10) {
|
|
97
|
+
return { error: 'Game is full (max 10 players)' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
success: true,
|
|
102
|
+
gameState: {
|
|
103
|
+
...gameState,
|
|
104
|
+
players: [...gameState.players, player]
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Start the game
|
|
110
|
+
function startGame(gameState, player) {
|
|
111
|
+
if (player !== gameState.host) {
|
|
112
|
+
return { error: 'Only host can start the game' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (gameState.phase !== PHASES.LOBBY) {
|
|
116
|
+
return { error: 'Game already started' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const playerCount = gameState.players.length;
|
|
120
|
+
if (playerCount < 4) {
|
|
121
|
+
return { error: 'Need at least 4 players to start' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const distribution = getRoleDistribution(playerCount);
|
|
125
|
+
|
|
126
|
+
// Create role pool
|
|
127
|
+
const rolePool = [];
|
|
128
|
+
for (const [role, count] of Object.entries(distribution)) {
|
|
129
|
+
for (let i = 0; i < count; i++) {
|
|
130
|
+
rolePool.push(role);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Shuffle and assign roles
|
|
135
|
+
const shuffledRoles = shuffle(rolePool);
|
|
136
|
+
const roles = {};
|
|
137
|
+
gameState.players.forEach((p, i) => {
|
|
138
|
+
roles[p] = shuffledRoles[i];
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
gameState: {
|
|
144
|
+
...gameState,
|
|
145
|
+
phase: PHASES.NIGHT,
|
|
146
|
+
roles,
|
|
147
|
+
alive: [...gameState.players],
|
|
148
|
+
round: 1,
|
|
149
|
+
events: [`🌙 Night 1 begins. The village sleeps...`]
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Werewolf chooses target
|
|
155
|
+
function werewolfKill(gameState, werewolf, target) {
|
|
156
|
+
if (gameState.phase !== PHASES.NIGHT) {
|
|
157
|
+
return { error: 'Can only kill at night' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (gameState.roles[werewolf] !== ROLES.WEREWOLF) {
|
|
161
|
+
return { error: 'Only werewolves can kill' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!gameState.alive.includes(target)) {
|
|
165
|
+
return { error: 'Target is not alive' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (gameState.roles[target] === ROLES.WEREWOLF) {
|
|
169
|
+
return { error: 'Cannot target fellow werewolf' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
gameState: {
|
|
175
|
+
...gameState,
|
|
176
|
+
werewolfTarget: target
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Seer investigates
|
|
182
|
+
function seerInvestigate(gameState, seer, target) {
|
|
183
|
+
if (gameState.phase !== PHASES.NIGHT) {
|
|
184
|
+
return { error: 'Can only investigate at night' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (gameState.roles[seer] !== ROLES.SEER) {
|
|
188
|
+
return { error: 'Only the seer can investigate' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!gameState.alive.includes(seer)) {
|
|
192
|
+
return { error: 'You are dead' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!gameState.alive.includes(target)) {
|
|
196
|
+
return { error: 'Target is not alive' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const targetRole = gameState.roles[target];
|
|
200
|
+
const isWerewolf = targetRole === ROLES.WEREWOLF;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
gameState: {
|
|
205
|
+
...gameState,
|
|
206
|
+
seerTarget: target,
|
|
207
|
+
seerReveals: {
|
|
208
|
+
...gameState.seerReveals,
|
|
209
|
+
[target]: isWerewolf ? 'werewolf' : 'not werewolf'
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
reveal: {
|
|
213
|
+
target,
|
|
214
|
+
result: isWerewolf ? '🐺 WEREWOLF' : '👤 Not a werewolf'
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Advance from night to day
|
|
220
|
+
function advanceToDay(gameState) {
|
|
221
|
+
if (gameState.phase !== PHASES.NIGHT) {
|
|
222
|
+
return { error: 'Not night phase' };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const newState = { ...gameState };
|
|
226
|
+
const events = [...gameState.events];
|
|
227
|
+
|
|
228
|
+
// Process werewolf kill
|
|
229
|
+
if (gameState.werewolfTarget) {
|
|
230
|
+
const victim = gameState.werewolfTarget;
|
|
231
|
+
const victimRole = gameState.roles[victim];
|
|
232
|
+
|
|
233
|
+
newState.alive = gameState.alive.filter(p => p !== victim);
|
|
234
|
+
newState.dead = [
|
|
235
|
+
...gameState.dead,
|
|
236
|
+
{ handle: victim, role: victimRole, eliminatedBy: 'werewolf' }
|
|
237
|
+
];
|
|
238
|
+
|
|
239
|
+
events.push(`☀️ Day ${gameState.round} begins.`);
|
|
240
|
+
events.push(`💀 @${victim} was found dead! They were a ${victimRole}.`);
|
|
241
|
+
} else {
|
|
242
|
+
events.push(`☀️ Day ${gameState.round} begins. No one died last night.`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check win condition
|
|
246
|
+
const winCheck = checkWinCondition(newState);
|
|
247
|
+
if (winCheck.gameOver) {
|
|
248
|
+
return {
|
|
249
|
+
success: true,
|
|
250
|
+
gameState: {
|
|
251
|
+
...newState,
|
|
252
|
+
phase: PHASES.ENDED,
|
|
253
|
+
winner: winCheck.winner,
|
|
254
|
+
events: [...events, winCheck.message]
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
gameState: {
|
|
262
|
+
...newState,
|
|
263
|
+
phase: PHASES.DAY,
|
|
264
|
+
werewolfTarget: null,
|
|
265
|
+
seerTarget: null,
|
|
266
|
+
events
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Start voting phase
|
|
272
|
+
function startVoting(gameState, player) {
|
|
273
|
+
if (gameState.phase !== PHASES.DAY) {
|
|
274
|
+
return { error: 'Can only start voting during the day' };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
success: true,
|
|
279
|
+
gameState: {
|
|
280
|
+
...gameState,
|
|
281
|
+
phase: PHASES.VOTING,
|
|
282
|
+
votes: {},
|
|
283
|
+
events: [...gameState.events, `🗳️ Voting has begun! Vote to eliminate a suspect.`]
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Cast vote
|
|
289
|
+
function castVote(gameState, voter, target) {
|
|
290
|
+
if (gameState.phase !== PHASES.VOTING) {
|
|
291
|
+
return { error: 'Not in voting phase' };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!gameState.alive.includes(voter)) {
|
|
295
|
+
return { error: 'Dead players cannot vote' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (!gameState.alive.includes(target) && target !== 'skip') {
|
|
299
|
+
return { error: 'Target is not alive (use "skip" to skip vote)' };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
gameState: {
|
|
305
|
+
...gameState,
|
|
306
|
+
votes: { ...gameState.votes, [voter]: target }
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Tally votes and execute
|
|
312
|
+
function tallyVotes(gameState) {
|
|
313
|
+
if (gameState.phase !== PHASES.VOTING) {
|
|
314
|
+
return { error: 'Not in voting phase' };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const { votes, alive } = gameState;
|
|
318
|
+
const voteCounts = {};
|
|
319
|
+
|
|
320
|
+
for (const target of Object.values(votes)) {
|
|
321
|
+
voteCounts[target] = (voteCounts[target] || 0) + 1;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Find player with most votes
|
|
325
|
+
let maxVotes = 0;
|
|
326
|
+
let eliminated = null;
|
|
327
|
+
let tie = false;
|
|
328
|
+
|
|
329
|
+
for (const [target, count] of Object.entries(voteCounts)) {
|
|
330
|
+
if (target === 'skip') continue;
|
|
331
|
+
if (count > maxVotes) {
|
|
332
|
+
maxVotes = count;
|
|
333
|
+
eliminated = target;
|
|
334
|
+
tie = false;
|
|
335
|
+
} else if (count === maxVotes) {
|
|
336
|
+
tie = true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const newState = { ...gameState };
|
|
341
|
+
const events = [...gameState.events];
|
|
342
|
+
|
|
343
|
+
// Show vote breakdown
|
|
344
|
+
events.push(`📊 Votes: ${Object.entries(voteCounts).map(([t, c]) => `@${t}: ${c}`).join(', ')}`);
|
|
345
|
+
|
|
346
|
+
if (tie || !eliminated || maxVotes < 2) {
|
|
347
|
+
events.push(`🤷 No majority reached. No one is eliminated.`);
|
|
348
|
+
} else {
|
|
349
|
+
const role = gameState.roles[eliminated];
|
|
350
|
+
newState.alive = alive.filter(p => p !== eliminated);
|
|
351
|
+
newState.dead = [
|
|
352
|
+
...gameState.dead,
|
|
353
|
+
{ handle: eliminated, role, eliminatedBy: 'vote' }
|
|
354
|
+
];
|
|
355
|
+
events.push(`⚰️ @${eliminated} has been eliminated! They were a ${role}.`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Check win condition
|
|
359
|
+
const winCheck = checkWinCondition(newState);
|
|
360
|
+
if (winCheck.gameOver) {
|
|
361
|
+
return {
|
|
362
|
+
success: true,
|
|
363
|
+
gameState: {
|
|
364
|
+
...newState,
|
|
365
|
+
phase: PHASES.ENDED,
|
|
366
|
+
winner: winCheck.winner,
|
|
367
|
+
events: [...events, winCheck.message]
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Advance to next night
|
|
373
|
+
return {
|
|
374
|
+
success: true,
|
|
375
|
+
gameState: {
|
|
376
|
+
...newState,
|
|
377
|
+
phase: PHASES.NIGHT,
|
|
378
|
+
round: gameState.round + 1,
|
|
379
|
+
votes: {},
|
|
380
|
+
events: [...events, `🌙 Night ${gameState.round + 1} falls...`]
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Check win condition
|
|
386
|
+
function checkWinCondition(gameState) {
|
|
387
|
+
const { alive, roles } = gameState;
|
|
388
|
+
|
|
389
|
+
const aliveWerewolves = alive.filter(p => roles[p] === ROLES.WEREWOLF);
|
|
390
|
+
const aliveVillagers = alive.filter(p => roles[p] !== ROLES.WEREWOLF);
|
|
391
|
+
|
|
392
|
+
if (aliveWerewolves.length === 0) {
|
|
393
|
+
return {
|
|
394
|
+
gameOver: true,
|
|
395
|
+
winner: 'villagers',
|
|
396
|
+
message: `🎉 The villagers win! All werewolves have been eliminated.`
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (aliveWerewolves.length >= aliveVillagers.length) {
|
|
401
|
+
return {
|
|
402
|
+
gameOver: true,
|
|
403
|
+
winner: 'werewolves',
|
|
404
|
+
message: `🐺 The werewolves win! They now outnumber the villagers.`
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return { gameOver: false };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Format game display
|
|
412
|
+
function formatDisplay(gameState, viewerHandle) {
|
|
413
|
+
const { phase, players, roles, alive, dead, round, votes, events, host, winner } = gameState;
|
|
414
|
+
const viewerRole = roles[viewerHandle];
|
|
415
|
+
const isAlive = alive.includes(viewerHandle);
|
|
416
|
+
|
|
417
|
+
let display = `🐺 **Werewolf** `;
|
|
418
|
+
|
|
419
|
+
if (phase === PHASES.LOBBY) {
|
|
420
|
+
display += `(Lobby)\n\n`;
|
|
421
|
+
display += `Host: @${host}\n`;
|
|
422
|
+
display += `Players (${players.length}/10): ${players.map(p => `@${p}`).join(', ')}\n\n`;
|
|
423
|
+
display += `Need ${Math.max(0, 4 - players.length)} more players to start.\n`;
|
|
424
|
+
if (viewerHandle === host && players.length >= 4) {
|
|
425
|
+
display += `Use \`vibe werewolf --start\` to begin!\n`;
|
|
426
|
+
} else {
|
|
427
|
+
display += `Use \`vibe werewolf --join\` to join!\n`;
|
|
428
|
+
}
|
|
429
|
+
return display;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
display += `(Round ${round} - ${phase.toUpperCase()})\n\n`;
|
|
433
|
+
|
|
434
|
+
// Show viewer's role (secret)
|
|
435
|
+
if (viewerRole && isAlive) {
|
|
436
|
+
const roleEmoji = viewerRole === ROLES.WEREWOLF ? '🐺' : viewerRole === ROLES.SEER ? '🔮' : '👤';
|
|
437
|
+
display += `**Your role:** ${roleEmoji} ${viewerRole.toUpperCase()}\n`;
|
|
438
|
+
|
|
439
|
+
// Show fellow werewolves
|
|
440
|
+
if (viewerRole === ROLES.WEREWOLF) {
|
|
441
|
+
const fellowWolves = alive.filter(p => roles[p] === ROLES.WEREWOLF && p !== viewerHandle);
|
|
442
|
+
if (fellowWolves.length > 0) {
|
|
443
|
+
display += `Fellow werewolves: ${fellowWolves.map(p => `@${p}`).join(', ')}\n`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
display += '\n';
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Show alive players
|
|
450
|
+
display += `**Alive (${alive.length}):** ${alive.map(p => `@${p}`).join(', ')}\n`;
|
|
451
|
+
if (dead.length > 0) {
|
|
452
|
+
display += `**Dead:** ${dead.map(d => `@${d.handle} (${d.role})`).join(', ')}\n`;
|
|
453
|
+
}
|
|
454
|
+
display += '\n';
|
|
455
|
+
|
|
456
|
+
// Phase-specific info
|
|
457
|
+
if (phase === PHASES.NIGHT) {
|
|
458
|
+
if (viewerRole === ROLES.WEREWOLF && isAlive) {
|
|
459
|
+
display += `🌙 Choose your victim: \`vibe werewolf --kill @player\`\n`;
|
|
460
|
+
} else if (viewerRole === ROLES.SEER && isAlive) {
|
|
461
|
+
display += `🔮 Investigate someone: \`vibe werewolf --investigate @player\`\n`;
|
|
462
|
+
} else {
|
|
463
|
+
display += `🌙 The village sleeps...\n`;
|
|
464
|
+
}
|
|
465
|
+
} else if (phase === PHASES.DAY) {
|
|
466
|
+
display += `☀️ Discuss who might be a werewolf!\n`;
|
|
467
|
+
display += `When ready: \`vibe werewolf --startvote\`\n`;
|
|
468
|
+
} else if (phase === PHASES.VOTING) {
|
|
469
|
+
display += `🗳️ Vote to eliminate: \`vibe werewolf --vote @player\`\n`;
|
|
470
|
+
display += `Or skip: \`vibe werewolf --vote skip\`\n\n`;
|
|
471
|
+
const voteCount = Object.keys(votes).length;
|
|
472
|
+
display += `Votes cast: ${voteCount}/${alive.length}\n`;
|
|
473
|
+
} else if (phase === PHASES.ENDED) {
|
|
474
|
+
display += `🎮 **GAME OVER**\n`;
|
|
475
|
+
display += `Winner: **${winner.toUpperCase()}**\n\n`;
|
|
476
|
+
display += `**Roles were:**\n`;
|
|
477
|
+
for (const [player, role] of Object.entries(roles)) {
|
|
478
|
+
const emoji = role === ROLES.WEREWOLF ? '🐺' : role === ROLES.SEER ? '🔮' : '👤';
|
|
479
|
+
display += `${emoji} @${player}: ${role}\n`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Recent events
|
|
484
|
+
if (events.length > 0) {
|
|
485
|
+
display += `\n**Recent:**\n`;
|
|
486
|
+
events.slice(-3).forEach(e => {
|
|
487
|
+
display += `${e}\n`;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return display;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
module.exports = {
|
|
495
|
+
ROLES,
|
|
496
|
+
PHASES,
|
|
497
|
+
createInitialState,
|
|
498
|
+
joinGame,
|
|
499
|
+
startGame,
|
|
500
|
+
werewolfKill,
|
|
501
|
+
seerInvestigate,
|
|
502
|
+
advanceToDay,
|
|
503
|
+
startVoting,
|
|
504
|
+
castVote,
|
|
505
|
+
tallyVotes,
|
|
506
|
+
checkWinCondition,
|
|
507
|
+
formatDisplay
|
|
508
|
+
};
|