package com.oldterns.vilebot.handlers.user; import com.oldterns.vilebot.Vilebot; import com.oldterns.vilebot.db.KarmaDB; import com.oldterns.vilebot.util.BaseNick; import info.debatty.java.stringsimilarity.NormalizedLevenshtein; import org.jsoup.Jsoup; import org.pircbotx.hooks.ListenerAdapter; import org.pircbotx.hooks.events.MessageEvent; import org.pircbotx.hooks.types.GenericMessageEvent; import twitter4j.JSONArray; import twitter4j.JSONObject; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by eunderhi on 25/07/16. Simple Jeopardy implementation. */ public class Trivia extends ListenerAdapter { private static final Pattern questionPattern = Pattern.compile( "^!jeopardy" ); private static final Pattern answerPattern = Pattern.compile( "^!(whatis|whois) (.*)" ); private static TriviaGame currentGame = null; private static final String JEOPARDY_CHANNEL = Vilebot.getConfig().get( "jeopardyChannel" ); private static final long TIMEOUT = 30000L; private static final String RED = "\u000304"; private static final String RESET = "\u000f"; private static final String BLUE = "\u000302"; private static final String GREEN = "\u000303"; private static ExecutorService timer = Executors.newScheduledThreadPool( 1 ); private static final Integer RECURSION_LIMIT = 10; private static Integer CURRENT_RECURSION_DEPTH = 0; private static final String WELCOME_STRING = "Welcome to Bot Jeopardy!"; @Override public void onGenericMessage( final GenericMessageEvent event ) { String text = event.getMessage(); Matcher questionMatcher = questionPattern.matcher( text ); Matcher answerMatcher = answerPattern.matcher( text ); try { if ( questionMatcher.matches() && shouldStartGame( event ) ) { startGame( event ); } else if ( answerMatcher.matches() && shouldStartGame( event ) ) { String answer = answerMatcher.group( 2 ); finishGame( event, answer ); } } catch ( Exception e ) { event.respondWith( "I don't feel like playing." ); e.printStackTrace(); } } private boolean shouldStartGame( GenericMessageEvent event ) { if ( event instanceof MessageEvent && ( (MessageEvent) event ).getChannel().getName().equals( JEOPARDY_CHANNEL ) ) { return true; } event.respondWith( "To play jeopardy join: " + JEOPARDY_CHANNEL ); return false; } private synchronized void startGame( GenericMessageEvent event ) throws Exception { if ( currentGame != null ) { for ( String msg : currentGame.getAlreadyPlayingString() ) { event.respondWith( msg ); } } else { currentGame = new TriviaGame(); event.respondWith( WELCOME_STRING ); for ( String msg : currentGame.getIntroString() ) { event.respondWith( msg ); } startTimer( event ); } } private void startTimer( final GenericMessageEvent event ) { timer.submit( () -> { try { Thread.sleep( TIMEOUT ); timeoutTimer( event ); } catch ( InterruptedException e ) { e.printStackTrace(); } } ); } private void timeoutTimer( GenericMessageEvent event ) { for ( String msg : currentGame.getTimeoutString() ) { event.respondWith( msg ); } currentGame = null; } private void stopTimer() { timer.shutdownNow(); timer = Executors.newFixedThreadPool( 1 ); } private synchronized void finishGame( GenericMessageEvent event, String answer ) { String answerer = BaseNick.toBaseNick( event.getUser().getNick() ); if ( currentGame != null ) { if ( currentGame.isCorrect( answer ) ) { stopTimer(); event.respondWith( String.format( "Congrats %s, you win %d karma!", answerer, currentGame.getStakes() ) ); KarmaDB.modNounKarma( answerer, currentGame.getStakes() ); currentGame = null; } else { event.respondWith( String.format( "Sorry %s! That is incorrect, you lose %d karma.", answerer, currentGame.getStakes() ) ); KarmaDB.modNounKarma( answerer, -1 * currentGame.getStakes() ); } } else { event.respondWith( "No active game. Start a new one with !jeopardy" ); } } private static class TriviaGame { private int stakes; private String question; private String answer; private String category; private static final String API_URL = "http://jservice.io/api/random"; TriviaGame() throws Exception { JSONObject triviaJSON = getQuestionJSON(); if ( triviaJSON != null ) { question = triviaJSON.getString( "question" ); category = triviaJSON.getJSONObject( "category" ).getString( "title" ); answer = Jsoup.parse( triviaJSON.getString( "answer" ) ).text(); stakes = getStakes( triviaJSON ); } else { question = "Could not find a question"; category = "Could not find a question"; answer = "Could not find a question"; stakes = 0; } } private int getStakes( JSONObject trivia ) throws Exception { if ( trivia.has( "value" ) && !trivia.isNull( "value" ) ) { return trivia.getInt( "value" ) / 100; } return 5; } String getQuestion() { return question; } String getAnswer() { return answer; } int getStakes() { return stakes; } private boolean isCorrect( String answer ) { String formattedUserAnswer = formatAnswer( answer ); String formattedActualAnswer = formatAnswer( this.answer ); double distance = new NormalizedLevenshtein().distance( formattedActualAnswer, formattedUserAnswer ); return distance < 0.5; } private String formatAnswer( String answer ) { return answer.toLowerCase().replaceAll( "^the ", "" ).replaceAll( "^a ", "" ).replaceAll( "^an ", "" ).replaceAll( "\\(.*\\)", "" ).replaceAll( "/.*", "" ).replaceAll( "&", "and" ).replaceAll( "[^A-Za-z\\d]", "" ); } private List<String> getQuestionBlurb() { List<String> questionBlurb = new ArrayList<>(); questionBlurb.add( "Your category is: " + RED + category + RESET ); questionBlurb.add( "For " + GREEN + stakes + RESET + " karma:" ); questionBlurb.add( BLUE + question + RESET ); return questionBlurb; } List<String> getIntroString() { List<String> introString = new ArrayList<>( getQuestionBlurb() ); introString.add( "30 seconds on the clock." ); return introString; } List<String> getAlreadyPlayingString() { List<String> alreadyPlayingString = new ArrayList<>(); alreadyPlayingString.add( "A game is already in session!" ); alreadyPlayingString.addAll( getQuestionBlurb() ); return alreadyPlayingString; } List<String> getTimeoutString() { List<String> timeoutString = new ArrayList<>(); timeoutString.add( "Your 30 seconds is up! The answer we were looking for was:" ); timeoutString.add( BLUE + answer + RESET ); return timeoutString; } private String getQuestionContent() throws Exception { String content; URLConnection connection; connection = new URL( API_URL ).openConnection(); connection.addRequestProperty( "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" ); Scanner scanner = new Scanner( connection.getInputStream() ); scanner.useDelimiter( "\\Z" ); content = scanner.next(); return content; } private JSONObject getQuestionJSON() throws Exception { String triviaContent = getQuestionContent(); JSONObject triviaJSON = new JSONArray( triviaContent ).getJSONObject( 0 ); String question = triviaJSON.getString( "question" ).trim(); boolean invalidFlag = !( triviaJSON.getString( "invalid_count" ).equals( "null" ) ); if ( ( question.equals( "" ) || question.contains( "seen here" ) || invalidFlag ) && ( CURRENT_RECURSION_DEPTH < RECURSION_LIMIT ) ) { CURRENT_RECURSION_DEPTH += 1; return getQuestionJSON(); } else if ( CURRENT_RECURSION_DEPTH >= RECURSION_LIMIT ) { return null; } return triviaJSON; } } }