2021-11-06 00:33:52 +00:00
'use strict' ;
const config = require ( 'config' ) ;
const timers = require ( 'timers/promises' ) ;
2021-11-15 19:29:13 +00:00
const { decode } = require ( 'html-entities' ) ;
2021-11-06 00:33:52 +00:00
const questions = require ( '../../assets/jeopardy.json' ) ;
2021-11-15 14:51:10 +00:00
const shuffle = require ( '../utils/shuffle' ) ;
2021-11-06 00:33:52 +00:00
2021-11-08 01:48:26 +00:00
const settings = { ... config . trivia } ;
2022-01-07 21:52:44 +00:00
const help = {
mode : '\'first\' or \'timeout\'' ,
rounds : 'rounds per game as a number' ,
timeout : 'seconds as a number' ,
} ;
2021-11-06 00:33:52 +00:00
let game = null ;
function scoreRound ( context , round ) {
if ( game . answers . length === 0 ) {
return ` No one scored in round ${ round + 1 } , better luck next time! ` ;
}
return game . answers . map ( ( { user } ) => {
2021-11-09 13:51:37 +00:00
if ( user ) {
context . setPoints ( user , 1 ) ;
game . points [ user . username ] = ( game . points [ user . username ] || 0 ) + 1 ;
2021-11-06 00:33:52 +00:00
2021-11-09 13:51:37 +00:00
return ` **@ ${ user . username } ** gets a point ` ;
}
return null ;
} ) . filter ( Boolean ) . join ( ', ' ) ;
2021-11-06 00:33:52 +00:00
}
async function playRound ( context , round = 0 ) {
const ac = new AbortController ( ) ; // eslint-disable-line no-undef
const now = new Date ( ) ;
game . answers = [ ] ;
game . round = round ;
game . ac = ac ;
const question = game . questions [ round ] ;
2021-11-12 15:43:20 +00:00
context . sendMessage ( ` **Question ${ round + 1 } / ${ game . questions . length } ** ( ${ question . category } ): ${ question . question } ` , context . room . id ) ;
2021-11-06 00:33:52 +00:00
context . logger . info ( ` Trivia asked " ${ question . question } " with answer: ${ question . answer } ` ) ;
try {
2021-11-14 16:56:07 +00:00
await timers . setTimeout ( ( game . timeout / 3 ) * 1000 , null , {
signal : ac . signal ,
} ) ;
// replace space with U+2003 Em Space to separate words, since a single space separates the placeholders, and double spaces are removed during Markdown render
2021-11-15 22:59:31 +00:00
context . sendMessage ( ` ** ${ Math . floor ( game . timeout / 3 ) * 2 } seconds** left, first hint for **question ${ round + 1 } / ${ game . questions . length } **: ** ${ question . answer . slice ( 0 , 1 ) } ${ question . answer . slice ( 1 ) . replace ( /\s/g , ' ' ) . replace ( /[^\s]/g , '_ ' ) . trim ( ) } ** ` , context . room . id ) ;
2021-11-14 16:56:07 +00:00
await timers . setTimeout ( ( game . timeout / 3 ) * 1000 , null , {
signal : ac . signal ,
} ) ;
if ( question . answer . length > 3 ) {
2021-11-15 14:51:10 +00:00
context . sendMessage ( ` ** ${ Math . floor ( game . timeout / 3 ) } seconds** left, second hint for **question ${ round + 1 } / ${ game . questions . length } **: ** ${ question . answer . slice ( 0 , 1 ) } ${ question . answer . slice ( 1 , - 1 ) . replace ( /\s/g , ' ' ) . replace ( /[^\s]/g , '_ ' ) } ${ question . answer . slice ( - 1 ) } ** ` , context . room . id ) ;
2021-11-14 16:56:07 +00:00
}
await timers . setTimeout ( ( game . timeout / 3 ) * 1000 , null , {
2021-11-06 00:33:52 +00:00
signal : ac . signal ,
} ) ;
} catch ( error ) {
2021-11-14 16:56:07 +00:00
// abort expected, probably not an error
2021-11-06 00:33:52 +00:00
}
if ( ! ac . signal . aborted ) {
ac . abort ( ) ;
}
if ( game . stopped ) {
2021-11-18 00:14:35 +00:00
context . sendMessage ( ` The game was stopped by @ ${ game . stopped . username } . The answer to the last question was: ** ${ question . answer } ** ` , context . room . id ) ;
2021-11-06 00:33:52 +00:00
game = null ;
return ;
}
if ( game . answers . length === 0 ) {
context . sendMessage ( ` **TIME'S UP!** No one guessed the answer: ** ${ question . answer } ** ` , context . room . id ) ;
} else {
const scores = scoreRound ( context , round ) ;
if ( game . mode === 'first' ) {
2021-11-14 16:56:07 +00:00
context . sendMessage ( ` ** ${ question . fullAnswer || question . answer } ** is the right answer, played in ** ${ ( ( new Date ( ) - now ) / 1000 ) . toFixed ( 3 ) } s**! ${ scores } ` , context . room . id ) ;
2021-11-06 00:33:52 +00:00
}
if ( game . mode === 'timeout' ) {
2022-01-07 21:52:44 +00:00
context . sendMessage ( ` **STOP!** The correct answer is ** ${ question . fullAnswer || question . answer } **. ${ scores } ` , context . room . id ) ;
2021-11-06 00:33:52 +00:00
}
}
if ( round < game . questions . length - 1 ) {
await timers . setTimeout ( 5000 ) ;
if ( game . stopped ) {
context . sendMessage ( ` The game was stopped by ${ game . stopped . username } ` , context . room . id ) ;
game = null ;
return ;
}
playRound ( context , round + 1 ) ;
return ;
}
2021-11-08 01:48:26 +00:00
await timers . setTimeout ( 2000 ) ;
2021-11-06 00:33:52 +00:00
const leaders = Object . entries ( game . points ) . sort ( ( [ , scoreA ] , [ , scoreB ] ) => scoreB - scoreA ) . map ( ( [ username , score ] , index ) => {
if ( index === 0 ) {
return ` **@ ${ username } ** with ** ${ score } ** points ` ;
}
return ` **@ ${ username } ** with ** ${ score } ** points ` ;
} ) . join ( ', ' ) ;
2021-11-08 01:48:26 +00:00
2021-11-06 00:33:52 +00:00
context . sendMessage ( ` That's the end of the game! Best players: ${ leaders } ` , context . room . id ) ;
game = null ;
}
async function start ( context ) {
const roundQuestions = shuffle ( questions , settings . rounds ) ;
game = {
round : 0 ,
questions : roundQuestions ,
answers : [ ] ,
points : { } ,
... settings ,
} ;
playRound ( context , 0 ) ;
}
async function stop ( context ) {
game . stopped = context . user ;
game . ac . abort ( ) ;
}
function onCommand ( args , context ) {
if ( ! context . subcommand && ! game ) {
start ( context ) ;
return ;
}
if ( ! context . subcommand && game ) {
2021-11-14 16:56:07 +00:00
context . sendMessage ( ` There is already a game going on! Use ${ config . prefix } trivia:stop to reset. The current question for round ${ game . round + 1 } is: ${ game . questions [ game . round ] . question } ` , context . room . id ) ;
2021-11-06 00:33:52 +00:00
return ;
}
if ( context . subcommand === 'stop' && game ) {
stop ( context ) ;
}
if ( context . subcommand === 'stop' && ! game ) {
context . sendMessage ( ` There is no game going on at the moment. Start one with ${ config . prefix } trivia! ` , context . room . id ) ;
}
2021-11-09 12:48:21 +00:00
2022-01-07 21:52:44 +00:00
if ( [ 'help' , 'commands' ] . includes ( context . subcommand ) ) {
context . sendMessage ( ` Available subcommands for ${ config . prefix } trivia are :stop, :mode, :rounds and :timeout ` , context . room . id ) ;
}
2021-11-09 12:48:21 +00:00
const subcommand = context . subcommand ? . toLowerCase ( ) ;
2021-11-14 16:56:07 +00:00
if ( subcommand && settings [ subcommand ] ) {
2022-01-07 21:52:44 +00:00
if ( args [ 0 ] ) {
settings [ subcommand ] = typeof settings [ subcommand ] === 'number' ? ( Number ( args [ 0 ] ) || settings [ subcommand ] ) : args [ 0 ] ;
2021-11-09 12:48:21 +00:00
2022-01-07 21:52:44 +00:00
context . sendMessage ( ` ${ subcommand } set to ${ settings [ subcommand ] } ` , context . room . id ) ;
} else if ( help [ subcommand ] ) {
context . sendMessage ( ` Please give ${ help [ subcommand ] } ` , context . room . id ) ;
}
2021-11-09 12:48:21 +00:00
}
2021-11-06 00:33:52 +00:00
}
async function onMessage ( message , context ) {
2021-11-29 17:44:44 +00:00
if ( ! game || context . user ? . id === config . user ? . id ) {
2021-11-06 00:33:52 +00:00
return ;
}
2022-01-07 21:52:44 +00:00
const { answer , fullAnswer } = game . questions [ game . round ] ;
2021-11-06 00:33:52 +00:00
2021-11-15 20:55:42 +00:00
if ( new RegExp ( answer , 'i' ) . test ( decode ( message . originalBody || message . body ) ) ) { // resolve HTML entities in case original body is not available, such as & to &
2021-11-06 00:33:52 +00:00
game . answers . push ( {
user : context . user ,
answer : message . body ,
} ) ;
if ( settings . mode === 'first' ) {
game . ac . abort ( ) ;
}
2022-01-07 21:52:44 +00:00
if ( settings . mode === 'timeout' ) {
if ( message . type === 'message' ) {
context . sendMessage ( ` ** ${ fullAnswer || answer } ** is the correct answer! You might want to **/whisper** the answer to me instead, so others can't leech off your impeccable knowledge. You will receive a point at the end of the round. ` , context . room . id , null , context . user . username ) ;
} else {
context . sendMessage ( ` ** ${ fullAnswer || answer } ** is the correct answer! You will receive a point at the end of the round. ` , context . room . id , null , context . user . username ) ;
}
}
2021-11-06 00:33:52 +00:00
}
}
module . exports = {
name : 'Trivia' ,
onCommand ,
onMessage ,
} ;