diff --git a/src/commands/CommandBenchmark.java b/src/commands/CommandBenchmark.java index 75623fb..61c8582 100644 --- a/src/commands/CommandBenchmark.java +++ b/src/commands/CommandBenchmark.java @@ -2,6 +2,7 @@ package commands; import core.Command; import core.LocStrings; +import core.benchmark.BenchmarkFormattable; import core.benchmark.BenchmarkFramework; import dataStructures.KittyChannel; import dataStructures.KittyGuild; @@ -36,18 +37,18 @@ public class CommandBenchmark extends Command return; } - String result = null; + BenchmarkFormattable commandOutput = null; synchronized(framework) { - result = framework.Run(input.args.trim()); + commandOutput = framework.Run(input.args.trim()); } - if(result == null) + if(commandOutput == null) { res.Call(LocStrings.Stub("BenchmarkInvalid")); return; } - res.Call(result); + commandOutput.Call(res); } } \ No newline at end of file diff --git a/src/commands/benchmark/BenchmarkCommandCompare.java b/src/commands/benchmark/BenchmarkCommandCompare.java index e58c32e..182a335 100644 --- a/src/commands/benchmark/BenchmarkCommandCompare.java +++ b/src/commands/benchmark/BenchmarkCommandCompare.java @@ -4,19 +4,20 @@ import java.util.List; import core.benchmark.BenchmarkCommand; import core.benchmark.BenchmarkEntry; +import core.benchmark.BenchmarkFormattable; import core.benchmark.BenchmarkInput; import core.benchmark.BenchmarkManager; public class BenchmarkCommandCompare extends BenchmarkCommand { - public String OnRun(BenchmarkManager manager, BenchmarkInput input) + public BenchmarkFormattable OnRun(BenchmarkManager manager, BenchmarkInput input) { final String lineDelimiter = "\n"; String output = ""; String[] inputSplit = input.value.trim().split("\\s+"); if(inputSplit.length < 2) - return "You must to specify 2 models to compare!"; + return new BenchmarkFormattable("You must to specify 2 models to compare!"); for(int i = 0; i < inputSplit.length; ++i) { @@ -38,6 +39,6 @@ public class BenchmarkCommandCompare extends BenchmarkCommand output += "----------"; } - return output; + return new BenchmarkFormattable(output); } } diff --git a/src/commands/benchmark/BenchmarkCommandFind.java b/src/commands/benchmark/BenchmarkCommandFind.java index fa3e000..f0ac9e8 100644 --- a/src/commands/benchmark/BenchmarkCommandFind.java +++ b/src/commands/benchmark/BenchmarkCommandFind.java @@ -6,10 +6,10 @@ import core.benchmark.*; public class BenchmarkCommandFind extends BenchmarkCommand { - private final char lineDelimiter = '\n'; + private final String lineDelimiter = "\n"; private final int listMax = 30; - public String OnRun(BenchmarkManager manager, BenchmarkInput input) + public BenchmarkFormattable OnRun(BenchmarkManager manager, BenchmarkInput input) { String output = ""; String searchString = input.value.trim(); @@ -42,6 +42,6 @@ public class BenchmarkCommandFind extends BenchmarkCommand output = "Couldn't find any models matching the search `" + searchString + "`!"; } - return output; + return new BenchmarkFormattable(output); } } diff --git a/src/commands/benchmark/BenchmarkCommandInfo.java b/src/commands/benchmark/BenchmarkCommandInfo.java index a66898c..2fb7dae 100644 --- a/src/commands/benchmark/BenchmarkCommandInfo.java +++ b/src/commands/benchmark/BenchmarkCommandInfo.java @@ -4,8 +4,11 @@ import java.util.List; import core.benchmark.BenchmarkCommand; import core.benchmark.BenchmarkEntry; +import core.benchmark.BenchmarkFormattable; import core.benchmark.BenchmarkInput; import core.benchmark.BenchmarkManager; +import dataStructures.KittyEmbed; +import java.awt.Color; public class BenchmarkCommandInfo extends BenchmarkCommand { @@ -20,27 +23,76 @@ public class BenchmarkCommandInfo extends BenchmarkCommand return output; } + + public static KittyEmbed FormatInfoEmbed(BenchmarkEntry entry) + { + // Populate general embed info + KittyEmbed embed = new KittyEmbed(); + + embed.title = "" + entry.brand + " " + entry.model + "" + lineDelimiter; + embed.authorLink = entry.url; + embed.authorImage = "https://kittybot.net/assets/generic/baseline_assessment_black_48dp.png"; + embed.authorText = "[view online]"; + embed.color = Color.LIGHT_GRAY; + embed.descriptionText = "Average Bench: " + entry.benchmark + "%"; + + // Brand coloring + String brandClean = entry.brand.toLowerCase().trim(); + + if(brandClean.contains("intel")) + embed.color = Color.getHSBColor((float) (205.0/360), 1.00f, 0.75f); + else if(brandClean.contains("amd")) + embed.color = Color.getHSBColor((float) (357.0/360), 0.88f, 0.92f); + else if (brandClean.contains("nvidia")) + embed.color = Color.getHSBColor((float) (081.0/360), 1.00f, 0.72f); + + // Add relative performance numbers + switch(entry.type) + { + case CPU: + embed.footerText = "(100% ~ Intel i7-7700k)"; + break; + case GPU: + embed.footerText = "(100% ~ Nvidia GTX 1070)"; + break; + case SSD: + embed.footerText = "(100% ~ Samsung 850 Pro)"; + break; + case HDD: + embed.footerText = "(100% ~ Seagate Baracuda 3TB (2016))"; + break; + case RAM: + embed.footerText = "(100% ~ Corsair Vengence LPX 2666 C15 4x8GB w/ Intel Skylake)"; + break; + case USB: + embed.footerText = "(100% ~ Sandisk Extreme 64GB)"; + break; + default: + } + + return embed; + } @Override - public String OnRun(BenchmarkManager manager, BenchmarkInput input) + public BenchmarkFormattable OnRun(BenchmarkManager manager, BenchmarkInput input) { String searchString = input.value.trim(); - - String output = ""; - output += lineDelimiter; - + long start = System.currentTimeMillis(); List entries = manager.FindModel(searchString); if(entries.size() < 1) - output += "Couldn't find any models containing `" + searchString + "`!"; + return new BenchmarkFormattable("Couldn't find any models containing `" + searchString + "`!"); - output += FormatInfo(entries.get(0)); - output += lineDelimiter; + List sortedEntries = manager.EvaluateLevenshteinDistance(entries, searchString); + BenchmarkEntry entry = sortedEntries.get(0); + KittyEmbed embed = FormatInfoEmbed(entry); if(entries.size() > 1) - output += "_chose " + entries.get(0).model + " from " + entries.size() + " potential components. To see others, try the command `benchmark find " + searchString +"`_)"; + embed.footerText += " (Chose the " + entry.model + " from " + entries.size() + " potential components. To see others, try the command 'benchmark find " + searchString +"')"; - return output; + embed.footerText += " (took " + (System.currentTimeMillis() - start) + "ms)"; + + return new BenchmarkFormattable(embed); } } diff --git a/src/core/benchmark/BenchmarkCommand.java b/src/core/benchmark/BenchmarkCommand.java index edbfe34..96a808e 100644 --- a/src/core/benchmark/BenchmarkCommand.java +++ b/src/core/benchmark/BenchmarkCommand.java @@ -1,8 +1,10 @@ package core.benchmark; -public abstract class BenchmarkCommand { - public BenchmarkCommand() { } +public abstract class BenchmarkCommand +{ + public BenchmarkCommand() + { } // OVERRIDE ME - public abstract String OnRun(BenchmarkManager manager, BenchmarkInput input); + public abstract BenchmarkFormattable OnRun(BenchmarkManager manager, BenchmarkInput input); } diff --git a/src/core/benchmark/BenchmarkFormattable.java b/src/core/benchmark/BenchmarkFormattable.java new file mode 100644 index 0000000..7c75bfa --- /dev/null +++ b/src/core/benchmark/BenchmarkFormattable.java @@ -0,0 +1,40 @@ +package core.benchmark; + +import dataStructures.KittyEmbed; +import dataStructures.Response; + +// Only one isn't null! +public class BenchmarkFormattable +{ + public final KittyEmbed resEmbed; + public final String resString; + + // Default constructor is hidden and disabled. + // If somehow it is called, defaults to an empty string and no embed. + @SuppressWarnings("unused") + private BenchmarkFormattable() + { + this.resEmbed = null; + this.resString = ""; + } + + public BenchmarkFormattable(String res) + { + this.resEmbed = null; + this.resString = res; + } + + public BenchmarkFormattable(KittyEmbed embed) + { + this.resEmbed = embed; + this.resString = null; + } + + public void Call(Response res) + { + if(resEmbed == null) + res.Call(resString); + else + res.CallEmbed(resEmbed); + } +} diff --git a/src/core/benchmark/BenchmarkFramework.java b/src/core/benchmark/BenchmarkFramework.java index c072767..5a4b375 100644 --- a/src/core/benchmark/BenchmarkFramework.java +++ b/src/core/benchmark/BenchmarkFramework.java @@ -22,7 +22,7 @@ public class BenchmarkFramework } // Runs a command if possible. - public String Run(String args) + public BenchmarkFormattable Run(String args) { BenchmarkInput input = new BenchmarkInput(args); return ExecuteCommand(input.key, input); @@ -39,7 +39,7 @@ public class BenchmarkFramework } // Executes a command with the specified name, and provides it with some extra input data. - private String ExecuteCommand(String name, BenchmarkInput input) + private BenchmarkFormattable ExecuteCommand(String name, BenchmarkInput input) { BenchmarkCommand command = benchmarkCommand.get(name.toLowerCase()); diff --git a/src/core/benchmark/BenchmarkManager.java b/src/core/benchmark/BenchmarkManager.java index d54b75a..9125503 100644 --- a/src/core/benchmark/BenchmarkManager.java +++ b/src/core/benchmark/BenchmarkManager.java @@ -2,8 +2,10 @@ package core.benchmark; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import dataStructures.Pair; import utils.FileUtils; import utils.directoryMonitor.DirectoryMonitor; import utils.directoryMonitor.MonitoredFile; @@ -67,6 +69,78 @@ public class BenchmarkManager } } + // Re-evaluates and re-orders the input list based on the Levenshtein distance of the + // original search and the contents of the provided list of benchmark entries. + public List EvaluateLevenshteinDistance(List entries, String search) + { + // Populate cost list with heuristic results + List> cost = new ArrayList>(); + int bestSoFar = Integer.MAX_VALUE; + for(int i = 0; i < entries.size(); ++i) + { + BenchmarkEntry entry = entries.get(i); + String entryString = entry.brand + " " + entry.model; + + int heuristic = LevenshteinHeuristic(entryString, search, bestSoFar); + if(heuristic < bestSoFar) + bestSoFar = heuristic; + + cost.add(new Pair(entry, heuristic)); + } + + // Sort list + Collections.sort(cost, (i1, i2) -> i1.Second.compareTo(i2.Second)); + + // Build new output list + List sortedEntries = new ArrayList(); + for(int i = 0; i < cost.size(); ++i) + sortedEntries.add(cost.get(i).First); + + return sortedEntries; + } + + // Finds the minimum integer in an array of ints and returns it. + private int min(int[] arr) + { + int min = Integer.MAX_VALUE; + for(int i = 0; i < arr.length; ++i) + { + if(arr[i] < min) + min = arr[i]; + } + + return min; + } + + // Returns cost based on distance of characters from string. This is a kinda sloppy way + // to do it, but because I keep tabs on the best result so far, it could be much worse. + // Still chucks a lot - a non-recursive result w/ memoization would be best but this will do. + private int LevenshteinHeuristic(String str1, String str2, int bestSoFar) + { + int cost; + + if(str1.length() <= 0) + return str2.length(); + + if(str2.length() <= 0) + return str1.length(); + + if(str1.charAt(0) == str2.charAt(0)) + cost = 0; + else + cost = 1; + + int distance = Math.abs(str1.length() - str2.length()); + if(distance > bestSoFar) + return distance; + + int s1 = LevenshteinHeuristic(str1.substring(1), str2, bestSoFar) + 1; + int s2 = LevenshteinHeuristic(str1, str2.substring(1), bestSoFar) + 1; + int s3 = LevenshteinHeuristic(str1.substring(1), str2.substring(1), bestSoFar) + cost; + + return min(new int[] {s1, s2, s3 }); + } + // Search for a substring in the model name public List FindModel(String modelSubstr) { diff --git a/src/dataStructures/KittyEmbed.java b/src/dataStructures/KittyEmbed.java new file mode 100644 index 0000000..812a0a0 --- /dev/null +++ b/src/dataStructures/KittyEmbed.java @@ -0,0 +1,19 @@ +package dataStructures; + +import java.awt.Color; + +public class KittyEmbed +{ + public String title = null; + public Color color = null; + public String footerText = null; + public String authorText = null; + public String authorLink = null; + public String authorImage = null; + public String descriptionText = null; + + public KittyEmbed() + { + this.title = "Untitled"; + } +} diff --git a/src/dataStructures/Response.java b/src/dataStructures/Response.java index b1cbd7d..530cd4d 100644 --- a/src/dataStructures/Response.java +++ b/src/dataStructures/Response.java @@ -5,26 +5,52 @@ import java.io.InputStream; import net.dv8tion.jda.core.JDA; import net.dv8tion.jda.core.entities.TextChannel; import net.dv8tion.jda.core.events.message.guild.*; +import net.dv8tion.jda.core.EmbedBuilder; import utils.GlobalLog; import utils.LogFilter; -// NOTE(wisp): This isn't constructed with the factory at this time, mostly -// because we need it to handle all the response queue behavior and everything -// internally. So long as commands aren't exposed to the GuildMessageReceivedEvent -// then we should be fine. +// This isn't constructed with the factory at this time, mostly because we need it +// to handle all the response queue behavior and everything internally. So long as +// commands aren't exposed to the GuildMessageReceivedEvent then we should be fine. public class Response { + // Variables private GuildMessageReceivedEvent event; private JDA kitty; private final int discordMessageMax = 2000; private final int kittyMessageMax = 1950; + // Constructor public Response(GuildMessageReceivedEvent event, JDA kitty) { this.event = event; this.kitty = kitty; } + // Builds a nicely formatted embedded message based on information provided + public void CallEmbed(KittyEmbed embedInfo) + { + EmbedBuilder embed = new EmbedBuilder(); + + if(embedInfo.title != null) + embed.setTitle(embedInfo.title); + + if(embedInfo.color != null) + embed.setColor(embedInfo.color); + + if(embedInfo.descriptionText != null) + embed.setDescription(embedInfo.descriptionText); + + if(embedInfo.footerText != null) + embed.setFooter(embedInfo.footerText, null); + + if(embedInfo.authorImage != null || embedInfo.authorLink != null || embedInfo.authorText != null) + embed.setAuthor(embedInfo.authorText, embedInfo.authorLink, embedInfo.authorImage); + + event.getChannel().sendMessage(embed.build()).queue(); + } + + // Queues a standard text-based message response to the channel that issued the command. public void Call(String toRespondWith) { GlobalLog.Log(LogFilter.Response, "Sending response: " + toRespondWith); @@ -38,6 +64,7 @@ public class Response } } + // Queues a standard text-based message response to the specified channel. public void CallToChannel(String toRespondWith, String channelID) { TextChannel channel; @@ -52,6 +79,7 @@ public class Response } } + // Immediately dispatches the message to the channel that issued the command. public void CallImmediate(String toRespondWith) { GlobalLog.Log(LogFilter.Response, "Sending immediate response: " + toRespondWith); @@ -65,13 +93,14 @@ public class Response } } - // This is for responding with a file rather than a String + // Queues a file response to the channel that issued the command. public void CallFile(File toRespondWith, String extension) { GlobalLog.Log(LogFilter.Response, "Sending file response"); event.getChannel().sendFile(toRespondWith, "return." + extension).queue(); } + // Queues an input stream response to the channel that issued the command. public void CallInput(InputStream in, String extension) { GlobalLog.Log(LogFilter.Response, "Sending input stream response");