start adding world record info from speedrun.com to timer and settings.

This commit is contained in:
olivier 2018-02-12 19:33:44 +01:00
parent d7b3e24411
commit bcc1003d1d
12 changed files with 490 additions and 2 deletions

View file

@ -23,6 +23,7 @@ dependencies {
compile fileTree(dir: 'lib', include: ['*.jar'])
compile 'com.1stleg:jnativehook:2.0.2'
compile 'com.thoughtworks.xstream:xstream:1.4.4'
compile group: 'org.json', name: 'json', version: '20180130'
}
macAppBundle {

View file

@ -0,0 +1,32 @@
package org.fenix.WorldRecord;
/**
* Category class to represent a speedrun.com category
* @author 4ilo 2018
*/
public class Category
{
private String name = "";
private String id = "";
public Category(String name, String id)
{
this.name = name;
this.id = id;
}
public String getName()
{
return name;
}
public String getId()
{
return id;
}
public String toString()
{
return this.name;
}
}

View file

@ -0,0 +1,32 @@
package org.fenix.WorldRecord;
/**
* Game class to represent a speedrun.com game
* @author 4ilo 2018
*/
public class Game
{
private String title;
private String id;
public Game(String title, String id)
{
this.title = title;
this.id = id;
}
public String getTitle()
{
return title;
}
public String getId()
{
return id;
}
public String toString()
{
return this.title;
}
}

View file

@ -0,0 +1,58 @@
package org.fenix.WorldRecord;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
/**
* JSONReader class to fetch JSON from api
* @author 4ilo 2018
*/
public class JSONReader
{
/**
* Get the content of the given reader as a string
* @param reader a reader object
* @return String with the data of the reader
* @throws IOException
*/
private static String readAll(Reader reader) throws IOException
{
StringBuilder builder = new StringBuilder();
int cp;
while((cp = reader.read()) != -1)
{
builder.append((char) cp);
}
return builder.toString();
}
/**
* Read the json from the given json-api url
* @param url the api url
* @return JSONObject with the data from the url
* @throws IOException
* @throws JSONException
*/
public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException
{
InputStream stream = new URL(url).openStream();
try
{
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName("UTF-8")));
String jsonText = readAll(reader);
return new JSONObject(jsonText);
} finally
{
stream.close();
}
}
}

View file

@ -0,0 +1,186 @@
package org.fenix.WorldRecord;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
public class RecordDialog extends JDialog
{
private JLabel searchLabel = new JLabel("Search game:");
private JTextField searchField = new JTextField();
private JButton searchButton = new JButton("Search");
private JLabel gamesLabel = new JLabel("Games:");
private DefaultComboBoxModel<Game> gameListModel = new DefaultComboBoxModel<>();
private JComboBox<Game> games = new JComboBox<>(gameListModel);
private JLabel categoriesLabel = new JLabel("Categories:");
private DefaultComboBoxModel<Category> categoryListModel = new DefaultComboBoxModel<>();
private JComboBox<Category> categories = new JComboBox<>(categoryListModel);
private JLabel worldRecord = new JLabel();
private JButton ok = new JButton("Ok");
private JButton close = new JButton("Close");
private String category_id = "";
public RecordDialog()
{
JPanel searchPanel = new JPanel(new FlowLayout());
{
searchField.setPreferredSize(new Dimension(200,30));
searchPanel.add(searchLabel);
searchPanel.add(searchField);
searchPanel.add(searchButton);
searchButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
searchGame(searchField.getText());
}
});
}
JPanel gamesPanel = new JPanel(new GridLayout(1,2));
{
gamesPanel.add(gamesLabel);
gamesPanel.add(games);
games.setEnabled(false);
games.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
getCategories((Game) games.getSelectedItem());
}
});
}
JPanel categoriesPanel = new JPanel(new GridLayout(1,2));
{
categoriesPanel.add(categoriesLabel);
categoriesPanel.add(categories);
categories.setEnabled(false);
categories.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
getWorldRecord((Category) categories.getSelectedItem());
}
});
}
JPanel buttonPanel = new JPanel(new FlowLayout());
{
buttonPanel.add(ok);
buttonPanel.add(close);
ok.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
actionOk();
}
});
close.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
close();
}
});
}
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));
this.add(searchPanel);
this.add(gamesPanel);
this.add(categoriesPanel);
this.add(worldRecord);
worldRecord.setAlignmentX(Component.CENTER_ALIGNMENT);
this.add(buttonPanel);
this.setTitle("World Record selection");
pack();
}
private void searchGame(String name)
{
ArrayList<Game> games = new ArrayList<>();
try {
games = WorldRecordParser.searchGames(name);
} catch (IOException e)
{
System.out.println("fout");
}
this.setGames(games);
}
private void getCategories(Game game)
{
ArrayList<Category> categories = new ArrayList<>();
try {
categories = WorldRecordParser.getCategories(game);
} catch (IOException e)
{
System.out.println("fout");
}
this.setCategories(categories);
}
private void getWorldRecord(Category category)
{
String worldRecord = "";
try {
worldRecord = WorldRecordParser.getRecord(category);
} catch (IOException e)
{
System.out.println("fout");
}
this.worldRecord.setText(worldRecord);
this.category_id = category.getId();
}
private void setGames(ArrayList<Game> games)
{
gameListModel.removeAllElements();
for(Game game: games)
{
gameListModel.addElement(game);
}
this.games.setEnabled(true);
}
private void setCategories(ArrayList<Category> categories)
{
categoryListModel.removeAllElements();
for(Category category: categories)
{
categoryListModel.addElement(category);
}
this.categories.setEnabled(true);
}
private void close()
{
this.setVisible(false);
}
private void actionOk()
{
this.category_id = ((Category) categories.getSelectedItem()).getId();
this.setVisible(false);
}
}

View file

@ -0,0 +1,132 @@
package org.fenix.WorldRecord;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
/**
* @author 4ilo 2018
*/
public class WorldRecordParser
{
/**
* Search the speedrun.com database for the game with the given name
* @param name The name of the game you want to search
* @return List of games
* @throws IOException
*/
public static ArrayList<Game> searchGames(String name) throws IOException
{
String url = "https://www.speedrun.com/api/v1/games?name=" + name;
JSONArray json_games;
ArrayList<Game> games = new ArrayList<>();
JSONObject json = JSONReader.readJsonFromUrl(url);
json_games = json.getJSONArray("data");
for(Object game: json_games)
{
JSONObject obj = (JSONObject) game;
games.add(new Game(
obj.getJSONObject("names").get("international").toString(),
obj.get("id").toString()
));
}
return games;
}
/**
* Get the speedrun.com categories for the given game
* @param game WorldRecord.Game object received from a game search
* @return List of categories
* @throws IOException
*/
public static ArrayList<Category> getCategories(Game game) throws IOException
{
String url = "https://www.speedrun.com/api/v1/games/" + game.getId() + "/categories";
ArrayList<Category> categories = new ArrayList<>();
JSONObject json = JSONReader.readJsonFromUrl(url);
JSONArray json_categories = json.getJSONArray("data");
for(Object category: json_categories)
{
JSONObject obj = (JSONObject) category;
categories.add(new Category(
obj.get("name").toString(),
obj.get("id").toString()
));
}
return categories;
}
/**
* Get the world record string for the given speedrun.com category
* @param category WorldRecord.Category object received from a category search
* @return The world record string
* @throws IOException
*/
public static String getRecord(Category category) throws IOException
{
String url = "https://www.speedrun.com/api/v1/categories/" + category.getId() + "/records";
JSONObject json = JSONReader.readJsonFromUrl(url);
JSONArray json_runs = json.getJSONArray("data").getJSONObject(0).getJSONArray("runs");
JSONObject wr_run = json_runs.getJSONObject(0).getJSONObject("run");
String player_name = getPlayerName(wr_run.getJSONArray("players").getJSONObject(0));
return "World record: " + parseTime(wr_run.getJSONObject("times").getFloat("primary_t")) + " by " + player_name;
}
/**
* Get the speedrun.com player name for the given player JSONObject
* @param player The player JSONObject extracted from a run object
* @return The players name
* @throws IOException
*/
private static String getPlayerName(JSONObject player) throws IOException
{
String uri = player.get("uri").toString();
JSONObject json = JSONReader.readJsonFromUrl(uri);
return json.getJSONObject("data").getJSONObject("names").get("international").toString();
}
/**
* Parse a time in seconds and miliseconds to a HH:MM:SS.sss format
* @param time_seconds the time in seconds
* @return Time in HH:MM:SS.sss format
*/
private static String parseTime(float time_seconds)
{
int hours = (int) time_seconds / 3600;
int secLeft = (int) time_seconds - hours*3600;
int minutes = secLeft / 60;
int seconds = secLeft - minutes * 60;
String time = "";
if(hours != 0)
time += hours + ":";
time += minutes + ":" + seconds;
if(time_seconds % 1 != 0)
{
int miliseconds = Math.round((time_seconds%1) * 1000);
time += "." + miliseconds;
}
return time;
}
}

View file

@ -109,6 +109,7 @@ public enum Language {
setting_footer_multiline,
setting_footer_deltaLabels,
setting_footer_sumOfBest,
setting_footer_worldRecord,
// Accuracy
accuracy_seconds,

View file

@ -124,6 +124,7 @@ public class Settings {
public static final Property<Boolean> footerMultiline = new Property<>( "footer.multiline" );
public static final Property<Boolean> footerShowDeltaLabels = new Property<>( "footer.deltaLabels" );
public static final Property<Boolean> footerShowSumOfBest = new Property<>( "footer.sumOfBest" );
public static final Property<Boolean> footerShowWorldRecord = new Property<>("footer.worldRecord");
private static Configuration global = null;
private static Run run = null;
@ -294,6 +295,7 @@ public class Settings {
setDefault( footerMultiline.key, true, force );
setDefault( footerShowDeltaLabels.key, true, force );
setDefault( footerShowSumOfBest.key, false, force );
setDefault( footerShowWorldRecord.key, true, force);
}
/**

View file

@ -4,6 +4,8 @@ import org.fenix.llanfair.*;
import org.fenix.llanfair.config.Accuracy;
import org.fenix.utils.gui.GBC;
import org.fenix.WorldRecord.*;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
@ -107,6 +109,14 @@ implements ActionListener, ListSelectionListener {
private JTextField runDelayedStart;
/**
* World record data from spedrun.com
*/
private JLabel recordLabel;
private JButton selectRecord;
private RecordDialog recordSelector;
// ----------------------------------------------------------- CONSTRUCTEURS
/**
@ -159,6 +169,9 @@ implements ActionListener, ListSelectionListener {
moveUp = new JButton(Llanfair.getResources().getIcon("ARROW_UP.png"));
moveDown = new JButton(Llanfair.getResources().getIcon("ARROW_DOWN.png"));
segmented = new JCheckBox("" + Language.ED_SEGMENTED, run.isSegmented());
recordLabel = new JLabel("World record");
selectRecord = new JButton("Select record");
recordSelector = new RecordDialog();
placeComponents();
setBehavior();
@ -186,10 +199,13 @@ implements ActionListener, ListSelectionListener {
add(moveUp, GBC.grid(3, 5).insets(0, 4).anchor(GBC.FIRST_LINE_START));
add(moveDown, GBC.grid(3, 6).insets(4, 4).anchor(GBC.FIRST_LINE_START));
add(recordLabel, GBC.grid(0,7).insets(5,4,4,0).anchor(GBC.LINE_END));
add(selectRecord, GBC.grid(1,7).insets(4,0,0,4).anchor(GBC.LINE_START));
JPanel controls = new JPanel();
controls.add(save);
controls.add(cancel);
add(controls, GBC.grid(0, 7, 4, 1));
add(controls, GBC.grid(0, 8, 4, 1));
}
/**
@ -227,6 +243,7 @@ implements ActionListener, ListSelectionListener {
moveDown.addActionListener(this);
cancel.addActionListener(this);
save.addActionListener(this);
selectRecord.addActionListener(this);
// Insertion des délégués de rendus et dédition.
segments.setDefaultRenderer(Icon.class, new IconRenderer());
@ -317,6 +334,8 @@ implements ActionListener, ListSelectionListener {
int selected = segments.getSelectedRow();
run.moveSegmentDown(selected);
segments.setRowSelectionInterval(selected + 1, selected + 1);
} else if (source.equals(selectRecord)) {
recordSelector.setVisible(true);
}
}

View file

@ -30,6 +30,7 @@ public class TabComponents extends SettingsTab
SCB_SETTINGS.add(Settings.footerShowBestTime);
SCB_SETTINGS.add(Settings.footerMultiline);
SCB_SETTINGS.add(Settings.footerVerbose);
SCB_SETTINGS.add(Settings.footerShowWorldRecord);
SCB_SETTINGS.add(Settings.footerShowSumOfBest);
SCB_SETTINGS.add(Settings.coreShowSegmentName);
SCB_SETTINGS.add(Settings.coreShowSplitTime);
@ -432,6 +433,7 @@ public class TabComponents extends SettingsTab
footerPanel.add(checkBoxes.get(Settings.footerShowBestTime.getKey()), GBC.grid(1, 1).anchor(GBC.LINE_START));
footerPanel.add(checkBoxes.get(Settings.footerMultiline.getKey()), GBC.grid(1, 2).anchor(GBC.LINE_START));
footerPanel.add(checkBoxes.get(Settings.footerShowSumOfBest.getKey()), GBC.grid(0, 3).anchor(GBC.LINE_START));
footerPanel.add(checkBoxes.get(Settings.footerShowWorldRecord.getKey()), GBC.grid(1,3).anchor(GBC.LINE_START));
footerPanel.setBorder(
BorderFactory.createTitledBorder("" + Language.FOOTER)
);

View file

@ -62,6 +62,8 @@ class Footer extends JPanel {
private boolean resize;
private Dimension preferredSize;
private JLabel worldRecord;
/**
* Creates a default panel displaying informations for the given run.
*
@ -88,6 +90,8 @@ class Footer extends JPanel {
preferredSize = null;
resize = false;
worldRecord = new JLabel("World record: 5 by xem92");
setRun(run);
setOpaque(false);
@ -130,6 +134,7 @@ class Footer extends JPanel {
boolean ftVerbose = Settings.footerVerbose.get();
boolean ftTwoLines = Settings.footerMultiline.get();
boolean ftSumOfBest = Settings.footerShowSumOfBest.get();
boolean ftWorldRecord = Settings.footerShowWorldRecord.get();
int height = Math.max(timeH, labelH);
int width = prevW + timeW + smtmW + INSET * 2;
@ -155,6 +160,10 @@ class Footer extends JPanel {
if (ftSumOfBest) {
height += labelH;
}
if (ftWorldRecord) {
height += labelH;
}
preferredSize = new Dimension(width, height);
setMinimumSize(new Dimension(50, height));
resize = false;
@ -220,7 +229,8 @@ class Footer extends JPanel {
updateVisibility(BEST);
forceResize();
} else if (Settings.footerShowDeltaLabels.equals(property)) {
} else if (Settings.footerShowDeltaLabels.equals(property)
|| Settings.footerShowWorldRecord.equals(property)) {
updateVisibility(TEXT);
forceResize();
} else if (Settings.footerVerbose.equals(property)) {
@ -328,11 +338,20 @@ class Footer extends JPanel {
);
panelSumOfBest.setOpaque(false);
}
JPanel panelWorldRecord= new JPanel(new GridBagLayout());
{
panelWorldRecord.add(
worldRecord,
GBC.grid(0, 0).insets(0, 0, 0, INSET)
);
panelWorldRecord.setOpaque(false);
}
add(timePanel, GBC.grid(0, 0).anchor(GBC.LINE_START).weight(0.5, 0.0));
add(deltaPanel, GBC.grid(1, 0).anchor(GBC.LINE_END).weight(0.5, 0.0));
add(panelBest, GBC.grid(0, 1).anchor(GBC.LINE_START).weight(0.5, 0.0));
add(panelDeltaBest, GBC.grid(1, 1).anchor(GBC.LINE_END).weight(0.5, 0.0));
add(panelSumOfBest, GBC.grid(0, 2).anchor(GBC.LINE_START).weight(0.5, 0.0));
add(panelWorldRecord, GBC.grid(0,3, 2, 1).weight(0.5,0.0));
}
private void updateVisibility(int identifier) {
@ -352,6 +371,7 @@ class Footer extends JPanel {
labelDelta.setVisible(ftLabels && !ftVerbose);
labelDeltaBest.setVisible(ftLabels);
labelSumOfBest.setVisible(Settings.footerShowSumOfBest.get());
worldRecord.setVisible(Settings.footerShowWorldRecord.get());
}
if ((identifier & VERBOSE) == VERBOSE) {
boolean ftVerbose = Settings.footerVerbose.get();
@ -416,6 +436,7 @@ class Footer extends JPanel {
labelBest.setForeground(color);
labelDeltaBest.setForeground(color);
labelSumOfBest.setForeground(color);
worldRecord.setForeground(color);
}
}
@ -445,6 +466,7 @@ class Footer extends JPanel {
labelBest.setFont(Settings.coreFont.get());
labelDeltaBest.setFont(Settings.coreFont.get());
labelSumOfBest.setFont(Settings.coreFont.get());
worldRecord.setFont(Settings.coreFont.get());
}
}

View file

@ -90,6 +90,7 @@ setting_footer_bestTime = Best Time
setting_footer_multiline = Display on Two Lines
setting_footer_deltaLabels = Delta Labels
setting_footer_sumOfBest = Sum of Best
setting_footer_worldRecord = World Record
# Accuracy
accuracy_seconds = Seconds