-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgame.js
executable file
·730 lines (644 loc) · 27.8 KB
/
game.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
/*
Main game logic lives in this file
*/
const Messaging = require("./messaging");
const Events = require("./events");
module.exports = {
debugAssignRoles: debugAssignRoles,
assignRoles: assignRoles,
registerAccusation: registerAccusation,
startMafiaGroup: startMafiaGroup
};
/*
After roles get assigned, we start a loop
- Nighttime (self contained function)
- Begin day (results of nighttime - game loss/win, deaths, saves, etc)
- Daytime main - villager voting. Voting ends when they kill 1 person or after 3 tries without killing someone
- Announce game end if applicable
- rinse and repeat
*/
/*
gameState object is laid out like this:
- there is a dictionary called players that is keyed with the userid and values are objects that contain role, alive status, etc
- Other info about the game state as needed
*/
let gameState = {players: {}, running: false, savedThisTurn: "", mafiaAttemptThisTurn: "", accusedThisTurn: [], playerVotesThisTurn: {}, mafiaVotesThisTurn: {}, mafiaChannelID: ""};
// Give array of userids in array of strings that aren't prettyprint-able
function assignRoles(userArray){
// Only run if enough players
if(userArray.length < 5){
console.log("Not enough players");
//Alert users
//undefined for channel ID uses default channel ID, which is going to be the one that the game is in
//We do this because we don't want to mess with Slack API channel ids in the main game logic here
Messaging.channelMsg(undefined, "Sorry, there are not enough active users in this channel to play Mafia.");
return;
}
// For low number players, specify player counts
let mafiaNumber;
switch(userArray.length){
case 5: // 1 Mafia, 1 Doctor, 1 Investigator, 2 Villagers
mafiaNumber = 1;
break;
case 6: // 1 Mafia, 1 Doctor, 1 Investigator, 3 Villagers
mafiaNumber = 1;
break;
default: // 1/3 total Mafia, 1 Doctor, 1 Investigator, the rest Villagers
mafiaNumber = Math.floor(userArray.length/3);
}
// Prune array one at a time
let assigningUser;
// Assigning Mafia
for(let i = 0; i < mafiaNumber; i++){
assigningUser = removeElement(userArray);
setRole(assigningUser, 'mafia');
console.log(assigningUser + " is Mafia.");
}
// Assigning Doctor
assigningUser = removeElement(userArray);
setRole(assigningUser, 'doctor');
console.log(assigningUser + " is the Doctor.");
// Assigning Investigator
assigningUser = removeElement(userArray);
setRole(assigningUser, 'detective');
console.log(assigningUser + " is the Detective.");
// Assign rest to villager
while(userArray.length > 0){
assigningUser = removeElement(userArray);
setRole(assigningUser, 'villager');
console.log(assigningUser + " is a Villager.");
}
startMafiaGroup().then(function() {
//summon the devil
gameFlow();
});
}
function debugAssignRoles(userArray) {
//Make nathan the mafia
setRole("UDR58191C", "mafia");
//Remove nathan object from array
userArray.splice(userArray.indexOf("UDR58191C"), 1);
let assigningUser;
// Assign rest to villager
while(userArray.length > 0) {
assigningUser = removeElement(userArray);
setRole(assigningUser, 'villager');
console.log(assigningUser + " is a Villager.");
}
startMafiaGroup();
//start the unholy control flow loop
gameFlow();
}
let votingReadyResolve = function(){console.log("***!*!*!*!*!*!*VRResolve UNSET!!")};
async function gameFlow() {
Messaging.channelMsg(undefined, "The game has started!");
while(1) {
console.log("NEW DAY ITERATION!!!!");
let results = await nighttime().then(
//SUCCESS ON NIGHTTIME!
function() {
if(checkGameOver()) {
//game did end
console.log("CHECK GAME OVER INDICATED THAT GAME SHOULD END 1");
return Promise.reject();
} else {
//game did not end
console.log("RESOLVING GAME OVER CHECK PROMISE, GAME CONT");
return Promise.resolve();
}
},
//FAILURE ON NIGHTTIME!
function() {
console.log("FAILURE CALLBACK ON NIGHTTIME!!");
return Promise.reject();
}).then(
//SUCCESS ON CHECK GAME OVER
function() {
return new Promise(function(resolve, reject) {
console.log("SET VOTING READY RESOLVE!!!!!");
votingReadyResolve = resolve;
});
},
//FAIL ON CHECK GAME OVER
function() {
//FAIL CASCADED FROM ^^^
console.log("SOME KIND OF FAIL CASCADED FROM SOMEWHERE!");
return Promise.reject();
}).then(startVillagerVoting, function() {
//failure callback for previous
//means the game actually did end and we should NOT be having villager voting
//game exit
console.log("REJECT PROMISE CHAIN?????");
return Promise.reject();
}).then(function() {
//villager vote end successfully
if(checkGameOver()) {
//game ended, return false
console.log("GAME ENDED");
return false;
} else {
//game is NOT over
//do nothing
console.log("GAME CONTINUING");
return true;
}
}, function() {
//villager vote never happened (game over chained from previous)
console.log("GAME ENDING 2");
return false;
});
if(!results) {
break;
}
}
}
function checkGameOver() {
//We need to figure out if the game has been won
//Check if the mafia are at >=50% or at 0%
let allMafiaUsers = getUsersFromRole("mafia");
let aliveMafiaUsers = [];
/*for(let user in fakeMafiaUsers){
if(gameState.players[user].alive){
mafiaUsers.push(user);
}
}*/
console.log("ALL MAFIA USERS: ", allMafiaUsers);
for(let i=0; i<allMafiaUsers.length; i++) {
if(gameState.players[allMafiaUsers[i]].alive) {
aliveMafiaUsers.push(allMafiaUsers[i]);
}
}
console.log("ALIVE MAFIA USERS:", aliveMafiaUsers);
let aliveUsers = aliveCount();
if((aliveMafiaUsers.length / aliveUsers) >= 0.5) {
console.log("CHECK GAME OVER: GAME SHOULD END MAFIA 50%");
console.log("MAFIA USERS:", aliveMafiaUsers);
console.log("ALIVE USERS:", aliveUsers);
//Mafia win
Messaging.channelMsg(undefined, "The game has ended and the Mafia won! Better luck next time...");
gameCleanup();
return true;
} else if(aliveMafiaUsers.length === 0) {
console.log("CHECK GAME OVER: GAME SHOULD END MAFIA 0%");
//Villagers win
Messaging.channelMsg(undefined, "The game has ended. The villagers were triumphant and killed all the Mafia!");
gameCleanup();
return true;
} else {
console.log("CHECK GAME OVER: GAME SHOULD ******NOT***** END!!");
return false;
}
}
function gameCleanup() {
console.log("GAMECLEANUP called.");
Messaging.closeConversation(gameState.mafiaChannelID);
gameState.mafiaChannelID = "";
gameState.mafiaAttemptThisTurn = "";
gameState.savedThisTurn = "";
gameState.mafiaVotesThisTurn = {};
}
function aliveCount() {
let count = 0;
for(let i=0; i<Object.keys(gameState.players).length; i++) {
let key = Object.keys(gameState.players)[i];
if(gameState.players[key].alive === true) {
console.log("Player", key, "is alive");
count++;
} else {
console.log("Player", key, "is NOT alive");
}
}
return count;
}
//This function registers an accusation on the given user. Also notifies the chat that they have been accused and who did it
//Accusation counts as a second if the user has already been accused
function registerAccusation(userID, accuserID, errCB) {
console.log("REGISTER ACCUSATION:", userID, accuserID);
// if voting is ongoing, accusations cannot be made while voting
if(votingTimeout) {
console.log("**DIS ACCUSATION VOTING ONGOING");
errCB("You cannot accuse while voting is ongoing!");
return;
}
//Cannot accuse a dead user
if(gameState.players[userID].alive === false) {
console.log("**DIS ACCUSATION DEAD PLAYER");
errCB("That player is already dead, you cannot accuse them!");
return;
}
//And dead people cannot accuse
if(gameState.players[accuserID].alive === false) {
console.log("**DIS ACCUSATION DEAD ACCUSER");
errCB("You are dead...you can't accuse people!");
return;
}
//condition will be true if anything exists in the accusation array, ie if they have been accused this turn
//if not accused, condition will be false
if(alreadyAccused(userID)) {
//second
console.log("SECOND!!");
//Check that the accuser ID isn't the same as the original accuser ID, so that the accuser cannot second their own accusation
//console.log("already accused value:", alreadyAccused(userID));
if(alreadyAccused(userID) !== accuserID) {
//Register a second
Messaging.channelMsg(undefined, "<@"+accuserID+"> has seconded the accusation on <@"+userID+">!");
//begin voting process
console.log("voting process begin!");
//votingResolvedPromise = startVillagerVoting(userID);
//votingReadyPromise = Promise.resolve(userID);
votingReadyResolve(userID);
} else {
console.log("CANNOT SECOND OWN ACCUSATION!");
errCB("You cannot second your own accusation!");
}
} else {
console.log("REGULAR ACCUSATION!!");
//Regular accusation
gameState.accusedThisTurn.push({accuserID: accuserID, userID: userID});
Messaging.channelMsg(undefined, "<@"+accuserID+"> has accused <@"+userID+">! Second by using /accuse on this person as well!");
}
}
//This function checks the accusation information to see if a user has been already accused
//If so, it returns their accuser's ID, otherwise it returns undefined
function alreadyAccused(userID) {
for(let i=0; i<gameState.accusedThisTurn.length; i++) {
if(gameState.accusedThisTurn[i].userID === userID) {
return gameState.accusedThisTurn[i].accuserID;
}
}
return undefined;
}
//This function manages the voting process for who to kill
//The voting process gets started when there is an accusation and a second
let votingTimeout;
let callbackUUID;
let votingUserID;
function startVillagerVoting(userID) {
return new Promise(function (resolve, reject) {
Messaging.channelMsg(undefined, "The voting process on <@"+ userID+ "> has started! Write 'yes' to vote to kill and write 'no' to vote against killing.");
//Set a timeout to stop the voting, 45 second voting period for now
votingTimeout = setTimeout(function() {stopVillagerVoting(resolve)}, 45000);
votingUserID = userID;
//Register callback for villager votes
callbackUUID = Events.registerCallbackChannelReply(Messaging.getDefaultChannelID(), villagerVoteCallback);
});
}
//This function is called whenever anyone says anything in the chat during the voting period.
//Its responsibility is to determine which messages are kill votes and tally them accordingly
function villagerVoteCallback(eventData) {
if(eventData.text === "yes") {
//Yes vote
console.log("RECEIVED YES VOTE FROM USER:", eventData.user);
//dead people can't vote
if(gameState.players[eventData.user].alive) {
gameState.playerVotesThisTurn[eventData.user] = "yes";
} else {
Messaging.ephemeralMsg(undefined, eventData.user, "You are dead and cannot vote, sorry.");
}
} else if(eventData.text === "no") {
//No vote
console.log("RECEIVED NO VOTE FROM USER:", eventData.user);
//dead people can't vote
if(gameState.players[eventData.user].alive) {
gameState.playerVotesThisTurn[eventData.user] = "no";
}
}
//Now that vote has been recorded, check if everyone has voted
console.log("THERE ARE THIS MANY PLAYERS:", Object.keys(gameState.players).length);
//Determine the number of players that are alive
let alivePlayersCount = aliveCount();
console.log("THIS MANY PLAYERS ARE ALIVE:", alivePlayersCount);
if(Object.keys(gameState.playerVotesThisTurn).length === alivePlayersCount) {
//All players have submitted some kind of vote, check if they are all yes
let allYes = true;
for(let i=0; i<Object.keys(gameState.playerVotesThisTurn).length; i++) {
let key = Object.keys(gameState.playerVotesThisTurn)[i];
if(gameState.playerVotesThisTurn[key] === "no" || gameState.playerVotesThisTurn[key] === undefined) {
allYes = false;
console.log("EVERYONE HAS VOTED BUT SOMEONE VOTED NO!");
break;
}
}
if(allYes) {
console.log("EVERYONE HAS VOTED AND THEY ALL VOTED YES!!!");
//Everyone has voted AND they all voted yes
//End the voting early
//First kill the timeout
clearTimeout(votingTimeout);
//Next cancel this callback so we don't get more updates
//Next call the stopVillagerVoting function which will compute the results of the voting
stopVillagerVoting();
}
}
}
function stopVillagerVoting(resolve) {
Events.deregisterCallback(callbackUUID);
callbackUUID = "";
clearTimeout(votingTimeout); //clear the timeout if it didn't get cleared already
Messaging.channelMsg(undefined, "The voting process on <@"+ votingUserID+ "> has stopped.");
//COMPUTE RESULTS AND THEN CLEAR VOTES FOR THIS TURN!
//Step 1: compute results
let yesCount = 0;
let noCount = 0;
for(let i=0; i<Object.keys(gameState.playerVotesThisTurn).length; i++) {
let key = Object.keys(gameState.playerVotesThisTurn)[i];
if(gameState.playerVotesThisTurn[key] === "no") {
noCount++;
} else {
yesCount++;
}
}
/*console.log("VOTES DICT:", JSON.stringify(gameState.playerVotesThisTurn, null, 4));
console.log("TALLYING VOTES, YES COUNT IS:", yesCount);
console.log("NO COUNT IS:", noCount);*/
if(yesCount > noCount) {
//Voted to kill
let deadID = votingUserID;
//Cleanup before we leave
votingUserID = "";
votingTimeout = undefined;
gameState.playerVotesThisTurn = {};
lynchPerson(deadID);
} else if(noCount > yesCount) {
//Voted not to kill
Messaging.channelMsg(undefined, "<@"+votingUserID+"> was found not guilty.");
} else {
//Tie, person doesn't die
Messaging.channelMsg(undefined, "<@"+votingUserID+"> was found not guilty because the vote was tied.");
}
//Cleanup before we leave
votingUserID = "";
votingTimeout = undefined;
gameState.playerVotesThisTurn = {};
//So that prior accusations reset properly.
gameState.accusedThisTurn = [];
//This resolves the promise that was started when voting was started
resolve();
}
// Making a group with all of the Mafia in them
function startMafiaGroup() {
return new Promise(function(resolve, reject) {
// Get all Mafia members
let mafiaMembers = getUsersFromRole('mafia');
console.log("IDENTIFIED ALL MAFIA MEMBERS:", mafiaMembers);
// Make a group for them
console.log("Making a mafia groupchat with: " + mafiaMembers);
Messaging.groupMessage(mafiaMembers, "Hello Mafia, get to know each other. The others are not aware of this channel, you will need to come back here to vote on who to kill.", function(reply, convID){
console.log("Making mafia group, storing convID:" + convID + ".");
gameState.mafiaChannelID = convID;
resolve();
});
});
}
function setRole(userID, role) {
/*
{
players: {
"nreed7": {role: "mafia", alive: true}
},
running: true,
...
}
*/
//If the player doesn't exist in the game state array, add them
gameState.players[userID] = {};
//First set the role in the gameState object for our internal state keeping
gameState.players[userID].role = role;
//Everybody starts out alive, so make them alive as well
gameState.players[userID].alive = true;
//Next DM the user that they have been selected for the role
//No callback, we don't care about their reply to this message
Messaging.dmUser(userID, "You have been selected for the " + role + " role.");
}
// The nighttime activities function, with a callback
function nighttime(){
// Make a "set" of vote promises, so that once everyone who does stuff in the night is done, can move on, resolve on each one
let votingPromises = [];
votingPromises.push(new Promise((resolve) => doctorVote(resolve)).then(()=>{console.log("DOCTOR Resolved."); return Promise.resolve();}));
votingPromises.push(new Promise((resolve) => mafiaVote(resolve)).then(()=>{console.log("MAFIA Resolved."); return Promise.resolve();}));
votingPromises.push(new Promise((resolve) => detectiveVote(resolve)).then(()=>{console.log("DETECTIVE Resolved."); return Promise.resolve();}));
return Promise.all(votingPromises).then(function() {
console.log("Made it through all of the promises for doctors, etc.");
if(gameState.mafiaAttemptThisTurn === gameState.savedThisTurn){
// Attempted killing
Messaging.channelMsg(undefined, "During the night the mafia attempted to kill <@" + gameState.mafiaAttemptThisTurn + ">, yet thankfully the doctor saved them.");
gameState.mafiaAttemptThisTurn = undefined;
} else if(gameState.mafiaAttemptThisTurn !== undefined) {
// Kill person
mafiaKillsPerson(gameState.mafiaAttemptThisTurn);
} else {
// No attempted killing, ie. Mafia didn't nominate anyone
Messaging.channelMsg(undefined, "Nothing happened during the night, the mafia must have taken a nap.");
}
return Promise.resolve();
});
}
// Called to get the doctor's vote
let doctorResolve;
function doctorVote(resolve) {
//TODO Timer handling as well
doctorResolve = resolve;
let userID = getUsersFromRole('doctor')[0];
// Only get the vote if alive
if(gameState.players[userID].alive){
console.log("Doctor alive, prompting for patient.");
Messaging.dmUser(userID, "Who do you want to save tonight?", doctorPromptCallback);
} else {
console.log("Doctor is dead, resolving anyways.");
gameState.savedThisTurn = undefined;
resolve();
}
}
// Helper function for the doctor prompting callback
function doctorPromptCallback(reply) {
// If they @mentioned someone
let mentions = reply.text.match(/<@(.*?)>/); // Match the first @mention
if (mentions !== null) {
if (!doctorSaveAttempt(mentions[1])) { // If unsuccessful in first save attempt
console.log("Invalid input for doctor save, prompting again.");
Messaging.dmUser(reply.user, "Please @mention your choice, you can only pick one living person and not the same person two turns in a row.", doctorPromptCallback);
} else {
Messaging.dmUser(reply.user, "You saved <@" + mentions[1] + ">.");
doctorResolve();
}
}
}
// Helper for doctor selecting person to save
function doctorSaveAttempt(userID) {
// If the same person was saved last time or they're dead
if(userID !== gameState.savedThisTurn && gameState.players[userID].alive){
console.log("Doctor succeeded in selecting " + userID + " to save.");
gameState.savedThisTurn = userID;
return true;
} else {
// Failing, saved twice
console.log("Doctor tried to save " + userID + " twice in a row, failing.");
return false;
}
}
// Detective "Voting" for the person to investigate. Still need safety checks because could be non player
let detectiveResolve;
function detectiveVote(resolve) {
//TODO Timer handling as well
detectiveResolve = resolve;
let userID = getUsersFromRole('detective')[0];
// Only get the vote if alive
if(gameState.players[userID].alive){
console.log("Detective alive, prompting for suspect.");
Messaging.dmUser(userID, "Who do you want to investigate tonight?", detectivePromptCallback);
} else {
console.log("Detective is dead, resolving anyways.");
gameState.savedThisTurn = undefined;
resolve();
}
}
// Callback for detective prompting
function detectivePromptCallback(reply) {
// If they @mentioned someone
let mentions = reply.text.match(/<@(.*?)>/); // Match the first @mention
if (mentions !== null && gameState.players[mentions[1]] !== undefined && gameState.players[mentions[1]].alive) {
if(gameState.players[mentions[1]].role === 'mafia'){
Messaging.dmUser(reply.user, "<@" + mentions[1] + "> is a member of the mafia.");
} else {
Messaging.dmUser(reply.user, "<@" + mentions[1] + "> is not a member of the mafia.");
}
console.log("Detective investigated " + mentions[1] + " who is a " + gameState.players[mentions[1]].role);
detectiveResolve();
} else {
console.log("Invalid input for detective investigate, prompting again.");
Messaging.dmUser(reply.user, "Please @mention your choice, you can only pick one living person and not the same person two turns in a row.", detectivePromptCallback);
}
}
// Mafia voting (special since using group channel, and consensus, and everything
let mafiaVoteTimeout;
let mafiaCallbackUUID;
let mafiaResolve;
function mafiaVote(resolve) {
console.log("Mafia voting, channel id " + gameState.mafiaChannelID + ".");
// Message the channel
Messaging.channelMsg(gameState.mafiaChannelID, "<!channel> please vote on who to kill. @mention people to nominate them, once everyone has reached a consensus or the timer has run out the majority nominee will be killed.");
mafiaVoteTimeout = setTimeout(function() {mafiaVoteTimerCallback()}, 45000);
// Trying to make callback
mafiaCallbackUUID = Events.registerCallbackChannelReply(gameState.mafiaChannelID, mafiaVoteCallback);
mafiaResolve = resolve;
}
function mafiaVoteCallback(eventData) {
console.log("Mafia voting callback.");
// If the user @ mentions a player (and only that player)
let userID = parseAtMention(eventData.text, true);
if(userID !== undefined){
// The mentions[1] userID is a valid username, storing it as this mafioso's vote
gameState.mafiaVotesThisTurn[eventData.user] = userID;
// Check if all mafia have voted for same person (last voted for)
let allSame = true;
for(let userIDiterating in gameState.players){
// If mafia and alive and not voting for the same person
if(gameState.players[userIDiterating].role === 'mafia' && gameState.players[userIDiterating].alive && gameState.mafiaVotesThisTurn[userIDiterating] !== userID){
allSame = false;
break;
}
}
// If consensus reached, just kill the person and break the timeouts or whatever
if(allSame){
console.log("Mafia consensus reached.");
Messaging.channelMsg(gameState.mafiaChannelID, "A consensus was reached, <@" + userID + "> will be killed.");
gameState.mafiaAttemptThisTurn = userID;
// Clean up the mess
wipeMafiaVoteTemporaries();
}
} else {
Messaging.ephemeralMsg(gameState.mafiaChannelID, eventData.user, "That was an invalid nominee.");
}
}
function mafiaVoteTimerCallback() {
// No actual easy way to check if no votes
if(Object.keys(gameState.mafiaVotesThisTurn).length === 0){
Messaging.channelMsg(gameState.mafiaChannelID, "Times up and nobody to kill, guess you took this round off.");
// There isn't a nominee, so reflect that
gameState.mafiaAttemptThisTurn = "";
} else {
// Get the user most voted for, kill them
let mafiaVoteValues = Object.values(gameState.mafiaVotesThisTurn);
// Code to get the most occurring victim: https://stackoverflow.com/a/1053865/3196151
let modeMap = {};
let maxEl = mafiaVoteValues[0], maxCount = 1;
for(let i = 0; i < mafiaVoteValues.length; i++)
{
let el = mafiaVoteValues[i];
if(modeMap[el] == null)
modeMap[el] = 1;
else
modeMap[el]++;
if(modeMap[el] > maxCount)
{
maxEl = el;
maxCount = modeMap[el];
}
}
// Kill them and let them know the final selection
Messaging.channelMsg(gameState.mafiaChannelID, "The votes are in, <@" + maxEl + "> is set to be killed.");
gameState.mafiaAttemptThisTurn = maxEl;
}
// All done (will resolve the promise) either way
wipeMafiaVoteTemporaries();
}
function wipeMafiaVoteTemporaries() {
// Clear and wipe down everything
gameState.mafiaVotesThisTurn = {};
clearTimeout(mafiaVoteTimeout);
mafiaVoteTimeout = undefined;
Events.deregisterCallback(mafiaCallbackUUID);
mafiaCallbackUUID = "";
// Resolve that promise made by the original call
mafiaResolve();
}
// When the town votes on a person to kill
function lynchPerson(userID){
console.log("Lynching " + userID);
let name = "<@" + userID + ">";
gameState.players[userID].alive = false;
// Alert the channel of their death and alliance
if(gameState.players[userID].role === 'mafia'){
Messaging.channelMsg(undefined, "The vote passed and " + name + " is brought to the gallows and hanged until dead. In their final breaths they reveal that they were part of the Mafia.");
} else {
Messaging.channelMsg(undefined, "The vote passed and " + name + " is brought to the gallows and hanged until dead. However, they were not part of the Mafia.");
}
}
// When the mafia kills someone
function mafiaKillsPerson(userID){
console.log("Mafia is killing " + userID);
gameState.players[userID].alive = false;
// Alert the channel of their death, don't reveal alliance
Messaging.channelMsg(undefined, "During the night the mafia killed <@" + userID + ">.");
}
// Takes in a roll and returns userID for those with that roll, even if dead
function getUsersFromRole(role) {
let matchingUserIDs = [];
for(let userID in gameState.players){
//console.log("CHECKING USERID", userID, "role:", gameState.players[userID].role)
if(gameState.players[userID].role === role){
matchingUserIDs.push(userID);
}
}
return matchingUserIDs;
}
// Check if the a string matches a user @mention
function parseAtMention(messageText, onlyMention) {
// Match a @mention in the text (only one and nothing else in string if onlyMention == true)
let mentions;
if(onlyMention === true){
mentions = messageText.match(/^\s*<@(.*?)>\s*$/);
} else {
mentions = messageText.match(/<@(.*?)>/);
}
// Only return if it's a mention, of a valid player, who's alive
if (mentions !== null && gameState.players[mentions[1]] !== undefined && gameState.players[mentions[1]].alive) {
return mentions[1];
} else {
return undefined;
}
}
// Helper to sample a randomly selected array element
function removeElement(array){
return array.splice(Math.floor(Math.random() * array.length), 1)[0]; // Cut out one element and store it
}