diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java index 2a7c9fc7f87..89cdff4f2ca 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTableDialog.java @@ -34,9 +34,8 @@ import java.util.Arrays; import java.util.List; import java.util.ListIterator; import java.util.UUID; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JOptionPane; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; + import mage.cards.decks.importer.DeckImporterUtil; import mage.client.MageFrame; import mage.client.components.MageComponents; @@ -105,6 +104,7 @@ public class NewTableDialog extends MageDialog { lblGameType = new javax.swing.JLabel(); cbGameType = new javax.swing.JComboBox(); chkRollbackTurnsAllowed = new javax.swing.JCheckBox(); + chkRated = new javax.swing.JCheckBox(); lblFreeMulligans = new javax.swing.JLabel(); spnFreeMulligans = new javax.swing.JSpinner(); lblNumPlayers = new javax.swing.JLabel(); @@ -153,6 +153,9 @@ public class NewTableDialog extends MageDialog { chkRollbackTurnsAllowed.setText("Allow rollbacks"); chkRollbackTurnsAllowed.setToolTipText("Allow to rollback to the start of previous turns
\nif all players agree.\n"); + chkRated.setText("Rated"); + chkRated.setToolTipText("Indicates if matches will be rated."); + lblFreeMulligans.setText("Free Mulligans:"); lblFreeMulligans.setToolTipText("The number of mulligans a player can use without decreasing the number of drawn cards."); @@ -250,7 +253,9 @@ public class NewTableDialog extends MageDialog { .addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 125, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGroup(layout.createSequentialGroup() .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, 332, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(chkRated) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lblQuitRatio) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)))) @@ -310,6 +315,7 @@ public class NewTableDialog extends MageDialog { .addComponent(cbDeckType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(lbDeckType) .addComponent(lblQuitRatio) + .addComponent(chkRated) .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -387,6 +393,7 @@ public class NewTableDialog extends MageDialog { options.setRange((RangeOfInfluence) this.cbRange.getSelectedItem()); options.setWinsNeeded((Integer) this.spnNumWins.getValue()); options.setRollbackTurnsAllowed(chkRollbackTurnsAllowed.isSelected()); + options.setRated(chkRated.isSelected()); options.setFreeMulligans((Integer) this.spnFreeMulligans.getValue()); options.setPassword(this.txtPassword.getText()); options.setQuitRatio((Integer) this.spnQuitRatio.getValue()); @@ -627,6 +634,7 @@ public class NewTableDialog extends MageDialog { } this.spnNumWins.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_WINS, "2"))); this.chkRollbackTurnsAllowed.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_ROLLBACK_TURNS_ALLOWED, "Yes").equals("Yes")); + this.chkRated.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_RATED, "No").equals("Yes")); this.spnFreeMulligans.setValue(Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_FREE_MULLIGANS, "0"))); int range = Integer.parseInt(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TABLE_RANGE, "1")); @@ -669,6 +677,7 @@ public class NewTableDialog extends MageDialog { PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_GAME_TYPE, options.getGameType()); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_WINS, Integer.toString(options.getWinsNeeded())); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_ROLLBACK_TURNS_ALLOWED, options.isRollbackTurnsAllowed() ? "Yes" : "No"); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_RATED, options.isRated() ? "Yes" : "No"); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_OF_FREE_MULLIGANS, Integer.toString(options.getFreeMulligans())); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_DECK_FILE, deckFile); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TABLE_NUMBER_PLAYERS, spnNumPlayers.getValue().toString()); @@ -698,6 +707,7 @@ public class NewTableDialog extends MageDialog { private javax.swing.JComboBox cbSkillLevel; private javax.swing.JComboBox cbTimeLimit; private javax.swing.JCheckBox chkRollbackTurnsAllowed; + private javax.swing.JCheckBox chkRated; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JSeparator jSeparator1; diff --git a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java index c89e580f93a..72ae40ffa84 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/NewTournamentDialog.java @@ -175,6 +175,7 @@ public class NewTournamentDialog extends MageDialog { lblPlayer1 = new javax.swing.JLabel(); lblConstructionTime = new javax.swing.JLabel(); chkRollbackTurnsAllowed = new javax.swing.JCheckBox(); + chkRated = new javax.swing.JCheckBox(); spnConstructTime = new javax.swing.JSpinner(); player1Panel = new mage.client.table.NewPlayerPanel(); pnlPlayers = new javax.swing.JPanel(); @@ -314,6 +315,9 @@ public class NewTournamentDialog extends MageDialog { chkRollbackTurnsAllowed.setText("Allow rollbacks"); chkRollbackTurnsAllowed.setToolTipText("Allow to rollback to the start of previous turns
if all players agree. "); + chkRated.setText("Rated"); + chkRated.setToolTipText("Indicates if matches will be rated."); + spnConstructTime.setToolTipText("The time players have to build their deck."); player1Panel.setPreferredSize(new java.awt.Dimension(400, 44)); @@ -416,7 +420,9 @@ public class NewTournamentDialog extends MageDialog { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lblQuitRatio) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(chkRated)) .addComponent(cbTournamentType, javax.swing.GroupLayout.PREFERRED_SIZE, 290, javax.swing.GroupLayout.PREFERRED_SIZE))) .addGroup(layout.createSequentialGroup() .addComponent(lblName) @@ -462,7 +468,8 @@ public class NewTournamentDialog extends MageDialog { .addComponent(lblNumWins) .addComponent(spnNumWins, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(lblQuitRatio) - .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(spnQuitRatio, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(chkRated)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(cbTournamentType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -594,6 +601,7 @@ public class NewTournamentDialog extends MageDialog { tOptions.getMatchOptions().setAttackOption(MultiplayerAttackOption.LEFT); tOptions.getMatchOptions().setRange(RangeOfInfluence.ALL); tOptions.getMatchOptions().setRollbackTurnsAllowed(this.chkRollbackTurnsAllowed.isSelected()); + tOptions.getMatchOptions().setRated(this.chkRated.isSelected()); saveTournamentSettingsToPrefs(tOptions); table = session.createTournamentTable(roomId, tOptions); @@ -969,6 +977,7 @@ public class NewTournamentDialog extends MageDialog { } this.cbAllowSpectators.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS, "Yes").equals("Yes")); this.chkRollbackTurnsAllowed.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS, "Yes").equals("Yes")); + this.chkRated.setSelected(PreferencesDialog.getCachedValue(PreferencesDialog.KEY_NEW_TOURNAMENT_RATED, "No").equals("Yes")); } private void loadBoosterPacks(String packString) { @@ -1041,6 +1050,7 @@ public class NewTournamentDialog extends MageDialog { } PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_SPECTATORS, (tOptions.isWatchingAllowed()?"Yes":"No")); PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS, (tOptions.getMatchOptions().isRollbackTurnsAllowed()?"Yes":"No")); + PreferencesDialog.saveValue(PreferencesDialog.KEY_NEW_TOURNAMENT_RATED, (tOptions.getMatchOptions().isRated() ? "Yes" : "No")); } @@ -1061,6 +1071,7 @@ public class NewTournamentDialog extends MageDialog { private javax.swing.JComboBox cbTimeLimit; private javax.swing.JComboBox cbTournamentType; private javax.swing.JCheckBox chkRollbackTurnsAllowed; + private javax.swing.JCheckBox chkRated; private javax.swing.JLabel jLabel6; private javax.swing.JLabel lbDeckType; private javax.swing.JLabel lbSkillLevel; diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 2d423c03b45..ccc197fc18a 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -210,6 +210,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TABLE_NUMBER_PLAYERS = "newTableNumberPlayers"; public static final String KEY_NEW_TABLE_PLAYER_TYPES = "newTablePlayerTypes"; public static final String KEY_NEW_TABLE_QUIT_RATIO = "newTableQuitRatio"; + public static final String KEY_NEW_TABLE_RATED = "newTableRated"; // pref setting for new tournament dialog public static final String KEY_NEW_TOURNAMENT_NAME = "newTournamentName"; @@ -229,6 +230,7 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_NEW_TOURNAMENT_ALLOW_ROLLBACKS = "newTournamentAllowRollbacks"; public static final String KEY_NEW_TOURNAMENT_DECK_FILE = "newTournamentDeckFile"; public static final String KEY_NEW_TOURNAMENT_QUIT_RATIO = "newTournamentQuitRatio"; + public static final String KEY_NEW_TOURNAMENT_RATED = "newTournamentRated"; // pref setting for deck generator public static final String KEY_NEW_DECK_GENERATOR_DECK_SIZE = "newDeckGeneratorDeckSize"; diff --git a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java index bae1bb18ca4..83effb54552 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java @@ -335,11 +335,17 @@ public class TableWaitingDialog extends MageDialog { class TableWaitModel extends AbstractTableModel { - private final String[] columnNames = new String[]{"Seat", "Loc", "Player Name", "Player Type", "History"}; + private final String[] columnNames = new String[]{"Seat", "Loc", "Player Name", "Constructed Rating", "Player Type", "History"}; private SeatView[] seats = new SeatView[0]; + private boolean limited; public void loadData(TableView table) { seats = table.getSeats().toArray(new SeatView[0]); + if (limited != table.isLimited()) { + limited = table.isLimited(); + columnNames[3] = limited ? "Limited Rating" : "Constructed Rating"; + this.fireTableStructureChanged(); + } this.fireTableDataChanged(); } @@ -368,8 +374,10 @@ class TableWaitModel extends AbstractTableModel { case 2: return seats[arg0].getPlayerName(); case 3: - return seats[arg0].getPlayerType(); + return limited ? seats[arg0].getLimitedRating() : seats[arg0].getConstructedRating(); case 4: + return seats[arg0].getPlayerType(); + case 5: return seats[arg0].getHistory(); } } @@ -392,6 +400,8 @@ class TableWaitModel extends AbstractTableModel { switch (columnIndex) { case 1: return Icon.class; + case 3: + return Integer.class; default: return String.class; } diff --git a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java index 5c3bad66f13..24c1c189996 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayerPanelExt.java @@ -316,6 +316,8 @@ public class PlayerPanelExt extends javax.swing.JPanel { } basicTooltipText = "Name: " + player.getName() + "
Country: " + countryname + + "
Constructed rating: " + player.getUserData().getConstructedRating() + + "
Limited rating: " + player.getUserData().getLimitedRating() + "
Deck hash code: " + player.getDeckHashCode() + "
This match wins: " + player.getWins() + " of " + player.getWinsNeeded() + " (to win the match)" + (player.getUserData() == null ? "" : "
History: " + player.getUserData().getHistory()); diff --git a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java index 9fb16a23aa9..32ffc8ecde4 100644 --- a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java @@ -72,7 +72,7 @@ public class PlayersChatPanel extends javax.swing.JPanel { private final List players = new ArrayList<>(); private final UserTableModel userTableModel; - private static final int[] DEFAULT_COLUMNS_WIDTH = {20, 100, 40, 100, 40, 100, 80, 80}; + private static final int[] DEFAULT_COLUMNS_WIDTH = {20, 100, 40, 40, 40, 100, 40, 100, 80, 80}; /* @@ -152,7 +152,7 @@ public class PlayersChatPanel extends javax.swing.JPanel { class UserTableModel extends AbstractTableModel { - private final String[] columnNames = new String[]{"Loc", "Players", "Matches", "MQP", "Tourneys", "TQP", "Games", "Connection"}; + private final String[] columnNames = new String[]{"Loc", "Players", "Constructed Rating", "Limited Rating", "Matches", "MQP", "Tourneys", "TQP", "Games", "Connection"}; private UsersView[] players = new UsersView[0]; public void loadData(Collection roomUserInfoList) throws MageRemoteException { @@ -162,7 +162,7 @@ public class PlayersChatPanel extends javax.swing.JPanel { TableColumnModel tcm = th.getColumnModel(); tcm.getColumn(jTablePlayers.convertColumnIndexToView(1)).setHeaderValue("Players (" + this.players.length + ")"); - tcm.getColumn(jTablePlayers.convertColumnIndexToView(6)).setHeaderValue( + tcm.getColumn(jTablePlayers.convertColumnIndexToView(8)).setHeaderValue( "Games " + roomUserInfo.getNumberActiveGames() + (roomUserInfo.getNumberActiveGames() != roomUserInfo.getNumberGameThreads() ? " (T:" + roomUserInfo.getNumberGameThreads() : " (") + " limit: " + roomUserInfo.getNumberMaxGames() + ")"); @@ -188,16 +188,20 @@ public class PlayersChatPanel extends javax.swing.JPanel { case 1: return players[arg0].getUserName(); case 2: - return players[arg0].getMatchHistory(); + return players[arg0].getConstructedRating(); case 3: - return players[arg0].getMatchQuitRatio(); + return players[arg0].getLimitedRating(); case 4: - return players[arg0].getTourneyHistory(); + return players[arg0].getMatchHistory(); case 5: - return players[arg0].getTourneyQuitRatio(); + return players[arg0].getMatchQuitRatio(); case 6: - return players[arg0].getInfoGames(); + return players[arg0].getTourneyHistory(); case 7: + return players[arg0].getTourneyQuitRatio(); + case 8: + return players[arg0].getInfoGames(); + case 9: return players[arg0].getInfoPing(); } return ""; @@ -217,26 +221,32 @@ public class PlayersChatPanel extends javax.swing.JPanel { + "
(the number behind the header text is the number of currently connected users to the server)"; break; case 2: + tooltipText = "Constructed player rating"; + break; + case 3: + tooltipText = "Limited player rating"; + break; + case 4: tooltipText = "Number of matches the user played so far" + "
Q = number of matches quit" + "
I = number of matches lost because of idle timeout" + "
T = number of matches lost because of match timeout"; break; - case 3: + case 5: tooltipText = "Percent-Ratio of matches played related to matches quit" + "
this calculation does not include tournament matches"; break; - case 4: + case 6: tooltipText = "Number of tournaments the user played so far" + "
D = number of tournaments left during draft phase" + "
C = number of tournaments left during constructing phase" + "
R = number of tournaments left during rounds"; break; - case 5: + case 7: tooltipText = "Percent-Ratio of tournament matches played related to tournament matches quit" + "
this calculation does not include non tournament matches"; break; - case 6: + case 8: tooltipText = "Current activities of the player" + "
the header itself shows the number of currently active games" + "
T: = number of games threads " @@ -245,7 +255,7 @@ public class PlayersChatPanel extends javax.swing.JPanel { + "
(if the number of started games exceed that limit, the games have to wait" + "
until active games end)
"; break; - case 7: + case 9: tooltipText = "Latency of the user's connection to the server"; break; } @@ -270,8 +280,10 @@ public class PlayersChatPanel extends javax.swing.JPanel { switch (columnIndex) { case 0: return Icon.class; + case 2: case 3: case 5: + case 7: return Integer.class; default: return String.class; diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 527a29df8ea..e03aaad7814 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -107,7 +107,7 @@ import org.apache.log4j.Logger; public class TablesPanel extends javax.swing.JPanel { private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); - private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 120, 180, 80, 120, 80, 60, 40, 60}; + private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 120, 180, 80, 120, 80, 60, 40, 40, 60}; private TableTableModel tableModel; private MatchesTableModel matchesModel; @@ -158,7 +158,7 @@ public class TablesPanel extends javax.swing.JPanel { filterButtons = new JToggleButton[]{btnStateWaiting, btnStateActive, btnStateFinished, btnTypeMatch, btnTypeTourneyConstructed, btnTypeTourneyLimited, btnFormatBlock, btnFormatStandard, btnFormatModern, btnFormatLegacy, btnFormatVintage, btnFormatCommander, btnFormatTinyLeader, btnFormatLimited, btnFormatOther, - btnSkillBeginner, btnSkillCasual, btnSkillSerious}; + btnSkillBeginner, btnSkillCasual, btnSkillSerious, btnRated, btnUnrated}; JComponent[] components = new JComponent[]{chatPanelMain, jSplitPane1, jScrollPaneTablesActive, jScrollPaneTablesFinished, jPanelTop, jPanelTables}; for (JComponent component : components) { @@ -618,7 +618,16 @@ public class TablesPanel extends javax.swing.JPanel { skillFilterList.add(RowFilter.regexFilter(SkillLevel.SERIOUS.toString(), TableTableModel.COLUMN_SKILL)); } - if (stateFilterList.isEmpty() || typeFilterList.isEmpty() || formatFilterList.isEmpty() || skillFilterList.isEmpty()) { // no selection + List> ratingFilterList = new ArrayList<>(); + if (btnRated.isSelected()){ + ratingFilterList.add(RowFilter.regexFilter("^Rated", TableTableModel.COLUMN_RATING)); + } + if (btnUnrated.isSelected()){ + ratingFilterList.add(RowFilter.regexFilter("^Unrated", TableTableModel.COLUMN_RATING)); + } + + if (stateFilterList.isEmpty() || typeFilterList.isEmpty() || formatFilterList.isEmpty() + || skillFilterList.isEmpty() || ratingFilterList.isEmpty()) { // no selection activeTablesSorter.setRowFilter(RowFilter.regexFilter("Nothing", TableTableModel.COLUMN_SKILL)); } else { List> filterList = new ArrayList<>(); @@ -647,6 +656,12 @@ public class TablesPanel extends javax.swing.JPanel { filterList.addAll(skillFilterList); } + if (ratingFilterList.size() > 1) { + filterList.add(RowFilter.orFilter(ratingFilterList)); + } else if (ratingFilterList.size() == 1) { + filterList.addAll(ratingFilterList); + } + if (filterList.size() == 1) { activeTablesSorter.setRowFilter(filterList.get(0)); } else { @@ -680,6 +695,9 @@ public class TablesPanel extends javax.swing.JPanel { btnSkillBeginner = new javax.swing.JToggleButton(); btnSkillCasual = new javax.swing.JToggleButton(); btnSkillSerious = new javax.swing.JToggleButton(); + jSeparator5 = new javax.swing.JToolBar.Separator(); + btnRated = new javax.swing.JToggleButton(); + btnUnrated = new javax.swing.JToggleButton(); filterBar2 = new javax.swing.JToolBar(); btnFormatBlock = new javax.swing.JToggleButton(); btnFormatStandard = new javax.swing.JToggleButton(); @@ -883,6 +901,41 @@ public class TablesPanel extends javax.swing.JPanel { }); filterBar1.add(btnSkillSerious); + filterBar1.add(jSeparator4); + + btnRated.setSelected(true); + btnRated.setText("Rated"); + btnRated.setToolTipText("Shows all rated tables."); + btnRated.setFocusPainted(false); + btnRated.setFocusable(false); + btnRated.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnRated.setRequestFocusEnabled(false); + btnRated.setVerifyInputWhenFocusTarget(false); + btnRated.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + btnRated.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFilterActionPerformed(evt); + } + }); + filterBar1.add(btnRated); + + btnUnrated.setSelected(true); + btnUnrated.setText("Unrated"); + btnUnrated.setToolTipText("Shows all unrated tables."); + btnUnrated.setFocusPainted(false); + btnUnrated.setFocusable(false); + btnUnrated.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + btnUnrated.setRequestFocusEnabled(false); + btnUnrated.setVerifyInputWhenFocusTarget(false); + btnUnrated.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + btnUnrated.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + btnFilterActionPerformed(evt); + } + }); + filterBar1.add(btnUnrated); + + // second filter line filterBar2.setFloatable(false); filterBar2.setFocusable(false); filterBar2.setOpaque(false); @@ -1241,6 +1294,8 @@ public class TablesPanel extends javax.swing.JPanel { private javax.swing.JToggleButton btnSkillBeginner; private javax.swing.JToggleButton btnSkillCasual; private javax.swing.JToggleButton btnSkillSerious; + private javax.swing.JToggleButton btnRated; + private javax.swing.JToggleButton btnUnrated; private javax.swing.JToggleButton btnStateActive; private javax.swing.JToggleButton btnStateFinished; private javax.swing.JToggleButton btnStateWaiting; @@ -1262,6 +1317,7 @@ public class TablesPanel extends javax.swing.JPanel { private javax.swing.JToolBar.Separator jSeparator2; private javax.swing.JToolBar.Separator jSeparator3; private javax.swing.JToolBar.Separator jSeparator4; + private javax.swing.JToolBar.Separator jSeparator5; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JSplitPane jSplitPaneTables; private javax.swing.JTable tableCompleted; @@ -1282,10 +1338,11 @@ class TableTableModel extends AbstractTableModel { public static final int COLUMN_INFO = 4; public static final int COLUMN_STATUS = 5; public static final int COLUMN_SKILL = 7; - public static final int COLUMN_QUIT_RATIO = 8; - public static final int ACTION_COLUMN = 9; // column the action is located (starting with 0) + public static final int COLUMN_RATING = 8; + public static final int COLUMN_QUIT_RATIO = 9; + public static final int ACTION_COLUMN = 10; // column the action is located (starting with 0) - private final String[] columnNames = new String[]{"M/T", "Deck Type", "Owner / Players", "Game Type", "Info", "Status", "Created / Started", "Skill Level", "Quit %", "Action"}; + private final String[] columnNames = new String[]{"M/T", "Deck Type", "Owner / Players", "Game Type", "Info", "Status", "Created / Started", "Skill Level", "Rating", "Quit %", "Action"}; private TableView[] tables = new TableView[0]; private static final DateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss"); @@ -1332,8 +1389,10 @@ class TableTableModel extends AbstractTableModel { case 7: return tables[arg0].getSkillLevel(); case 8: - return tables[arg0].getQuitRatio(); + return tables[arg0].isRated() ? "Rated" : "Unrated"; case 9: + return tables[arg0].getQuitRatio(); + case 10: switch (tables[arg0].getTableState()) { case WAITING: @@ -1360,14 +1419,14 @@ class TableTableModel extends AbstractTableModel { default: return ""; } - case 10: - return tables[arg0].isTournament(); case 11: + return tables[arg0].isTournament(); + case 12: if (!tables[arg0].getGames().isEmpty()) { return tables[arg0].getGames().get(0); } return null; - case 12: + case 13: return tables[arg0].getTableId(); } return ""; @@ -1495,9 +1554,9 @@ class UpdatePlayersTask extends SwingWorker> { class MatchesTableModel extends AbstractTableModel { - public static final int ACTION_COLUMN = 6; // column the action is located (starting with 0) - public static final int GAMES_LIST_COLUMN = 7; - private final String[] columnNames = new String[]{"Deck Type", "Players", "Game Type", "Result", "Start Time", "End Time", "Action"}; + public static final int ACTION_COLUMN = 7; // column the action is located (starting with 0) + public static final int GAMES_LIST_COLUMN = 8; + private final String[] columnNames = new String[]{"Deck Type", "Players", "Game Type", "Rating", "Result", "Start Time", "End Time", "Action"}; private MatchView[] matches = new MatchView[0]; private static final DateFormat timeFormatter = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); @@ -1526,20 +1585,22 @@ class MatchesTableModel extends AbstractTableModel { case 2: return matches[arg0].getGameType(); case 3: - return matches[arg0].getResult(); + return matches[arg0].isRated() ? "Rated" : "Unrated"; case 4: + return matches[arg0].getResult(); + case 5: if (matches[arg0].getStartTime() != null) { return timeFormatter.format(matches[arg0].getStartTime()); } else { return ""; } - case 5: + case 6: if (matches[arg0].getEndTime() != null) { return timeFormatter.format(matches[arg0].getEndTime()); } else { return ""; } - case 6: + case 7: if (matches[arg0].isTournament()) { return "Show"; } else if (matches[arg0].isReplayAvailable()) { @@ -1547,7 +1608,7 @@ class MatchesTableModel extends AbstractTableModel { } else { return "None"; } - case 7: + case 8: return matches[arg0].getGames(); } return ""; diff --git a/Mage.Common/src/mage/view/MatchView.java b/Mage.Common/src/mage/view/MatchView.java index 29b3bbbae4e..6b808ebd237 100644 --- a/Mage.Common/src/mage/view/MatchView.java +++ b/Mage.Common/src/mage/view/MatchView.java @@ -58,6 +58,7 @@ public class MatchView implements Serializable { private Date endTime; private boolean replayAvailable; private final boolean isTournament; + private boolean rated; public MatchView(Table table) { this.tableId = table.getId(); @@ -117,7 +118,7 @@ public class MatchView implements Serializable { this.startTime = match.getStartTime(); this.endTime = match.getEndTime(); this.replayAvailable = match.isReplayAvailable(); - + this.rated = match.getOptions().isRated(); } // used for tournaments @@ -153,6 +154,7 @@ public class MatchView implements Serializable { this.startTime = table.getTournament().getStartTime(); this.endTime = table.getTournament().getEndTime(); this.replayAvailable = false; + this.rated = table.getTournament().getOptions().getMatchOptions().isRated(); } public UUID getMatchId() { @@ -207,4 +209,7 @@ public class MatchView implements Serializable { return tableId; } + public boolean isRated() { + return rated; + } } diff --git a/Mage.Common/src/mage/view/SeatView.java b/Mage.Common/src/mage/view/SeatView.java index 49d53db0917..dff17d5e08c 100644 --- a/Mage.Common/src/mage/view/SeatView.java +++ b/Mage.Common/src/mage/view/SeatView.java @@ -45,6 +45,9 @@ public class SeatView implements Serializable { private final String playerName; private final String playerType; private final String history; + private final int generalRating; + private final int constructedRating; + private final int limitedRating; public SeatView(Seat seat) { if (seat.getPlayer() != null) { @@ -53,15 +56,24 @@ public class SeatView implements Serializable { if (seat.getPlayer().getUserData() == null) { this.flagName = UserData.getDefaultFlagName(); this.history = ""; + this.generalRating = 0; + this.constructedRating = 0; + this.limitedRating = 0; } else { this.flagName = seat.getPlayer().getUserData().getFlagName(); this.history = seat.getPlayer().getUserData().getHistory(); + this.generalRating = seat.getPlayer().getUserData().getGeneralRating(); + this.constructedRating = seat.getPlayer().getUserData().getConstructedRating(); + this.limitedRating = seat.getPlayer().getUserData().getLimitedRating(); } } else { // Empty seat this.playerName = ""; this.flagName = ""; this.history = ""; + this.generalRating = 0; + this.constructedRating = 0; + this.limitedRating = 0; } this.playerType = seat.getPlayerType(); } @@ -86,4 +98,15 @@ public class SeatView implements Serializable { return history; } + public int getGeneralRating() { + return generalRating; + } + + public int getConstructedRating() { + return constructedRating; + } + + public int getLimitedRating() { + return limitedRating; + } } diff --git a/Mage.Common/src/mage/view/TableView.java b/Mage.Common/src/mage/view/TableView.java index 3cda470098c..2b7c711b95b 100644 --- a/Mage.Common/src/mage/view/TableView.java +++ b/Mage.Common/src/mage/view/TableView.java @@ -63,6 +63,8 @@ public class TableView implements Serializable { private List seats = new ArrayList<>(); private List games = new ArrayList<>(); private final String quitRatio; + private final boolean limited; + private final boolean rated; public TableView(Table table) { this.tableId = table.getId(); @@ -132,6 +134,8 @@ public class TableView implements Serializable { this.additionalInfo = addInfo.toString(); this.skillLevel = table.getMatch().getOptions().getSkillLevel(); this.quitRatio = Integer.toString(table.getMatch().getOptions().getQuitRatio()); + this.limited = table.getMatch().getOptions().isLimited(); + this.rated = table.getMatch().getOptions().isRated(); } else { // TOURNAMENT if (table.getTournament().getOptions().getNumberRounds() > 0) { @@ -179,6 +183,8 @@ public class TableView implements Serializable { this.deckType = table.getDeckType() + " " + table.getTournament().getBoosterInfo() + (tableNameInfo != null ? tableNameInfo : ""); this.skillLevel = table.getTournament().getOptions().getMatchOptions().getSkillLevel(); this.quitRatio = Integer.toString(table.getTournament().getOptions().getQuitRatio()); + this.limited = table.getTournament().getOptions().getMatchOptions().isLimited(); + this.rated = table.getTournament().getOptions().getMatchOptions().isRated(); } } @@ -236,4 +242,12 @@ public class TableView implements Serializable { public String getQuitRatio() { return quitRatio; } + + public boolean isLimited() { + return limited; + } + + public boolean isRated() { + return rated; + } } diff --git a/Mage.Common/src/mage/view/UsersView.java b/Mage.Common/src/mage/view/UsersView.java index 1ad012b8da5..033dedf7fba 100644 --- a/Mage.Common/src/mage/view/UsersView.java +++ b/Mage.Common/src/mage/view/UsersView.java @@ -45,9 +45,13 @@ public class UsersView implements Serializable { private final int tourneyQuitRatio; private final String infoGames; private final String infoPing; + private final int generalRating; + private final int constructedRating; + private final int limitedRating; public UsersView(String flagName, String userName, String matchHistory, int matchQuitRatio, - String tourneyHistory, int tourneyQuitRatio, String infoGames, String infoPing) { + String tourneyHistory, int tourneyQuitRatio, String infoGames, String infoPing, + int generalRating, int constructedRating, int limitedRating) { this.flagName = flagName; this.matchHistory = matchHistory; this.matchQuitRatio = matchQuitRatio; @@ -56,6 +60,9 @@ public class UsersView implements Serializable { this.userName = userName; this.infoGames = infoGames; this.infoPing = infoPing; + this.generalRating = generalRating; + this.constructedRating = constructedRating; + this.limitedRating = limitedRating; } public String getFlagName() { @@ -90,4 +97,15 @@ public class UsersView implements Serializable { return infoPing; } + public int getGeneralRating() { + return generalRating; + } + + public int getConstructedRating() { + return constructedRating; + } + + public int getLimitedRating() { + return limitedRating; + } } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 17a07913c8d..644440b976f 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -48,6 +48,8 @@ import mage.players.net.UserData; import mage.server.draft.DraftSession; import mage.server.game.GameManager; import mage.server.game.GameSessionPlayer; +import mage.server.rating.GlickoRating; +import mage.server.rating.GlickoRatingSystem; import mage.server.record.UserStats; import mage.server.record.UserStatsRepository; import mage.server.tournament.TournamentController; @@ -108,7 +110,6 @@ public class User { this.watchedGames = new ArrayList<>(); this.tablesToDelete = new ArrayList<>(); this.sessionId = ""; - this.userStats = null; } public String getName() { @@ -535,15 +536,23 @@ public class User { } userStats = UserStatsRepository.instance.getUser(this.userName); if (userStats != null) { - userData.setMatchHistory(userStatsToMatchHistory(userStats.getProto())); - userData.setMatchQuitRatio(userStatsToMatchQuitRatio(userStats.getProto())); - userData.setTourneyHistory(userStatsToTourneyHistory(userStats.getProto())); - userData.setTourneyQuitRatio(userStatsToTourneyQuitRatio(userStats.getProto())); + ResultProtos.UserStatsProto userStatsProto = userStats.getProto(); + + userData.setMatchHistory(userStatsToMatchHistory(userStatsProto)); + userData.setMatchQuitRatio(userStatsToMatchQuitRatio(userStatsProto)); + userData.setTourneyHistory(userStatsToTourneyHistory(userStatsProto)); + userData.setTourneyQuitRatio(userStatsToTourneyQuitRatio(userStatsProto)); + userData.setGeneralRating(userStatsToGeneralRating(userStatsProto)); + userData.setConstructedRating(userStatsToConstructedRating(userStatsProto)); + userData.setLimitedRating(userStatsToLimitedRating(userStatsProto)); } else { userData.setMatchHistory("0"); userData.setMatchQuitRatio(0); userData.setTourneyHistory("0"); userData.setTourneyQuitRatio(0); + userData.setGeneralRating(GlickoRatingSystem.getDefaultDisplayedRating()); + userData.setConstructedRating(GlickoRatingSystem.getDefaultDisplayedRating()); + userData.setLimitedRating(GlickoRatingSystem.getDefaultDisplayedRating()); } } @@ -569,8 +578,11 @@ public class User { } public static String userStatsToHistory(ResultProtos.UserStatsProto proto) { + // todo: add preference to hide rating? return "Matches:" + userStatsToMatchHistory(proto) - + " Tourneys: " + userStatsToTourneyHistory(proto); + + ", Tourneys: " + userStatsToTourneyHistory(proto) + + ", Constructed Rating: " + userStatsToConstructedRating(proto) + + ", Limited Rating: " + userStatsToLimitedRating(proto); } public int getTourneyQuitRatio() { @@ -644,6 +656,48 @@ public class User { return 100 * quits / tourneys; } + private static int userStatsToGeneralRating(ResultProtos.UserStatsProto proto) { + GlickoRating glickoRating; + if (proto.hasGeneralGlickoRating()) { + ResultProtos.GlickoRatingProto glickoRatingProto = proto.getGeneralGlickoRating(); + glickoRating = new GlickoRating( + glickoRatingProto.getRating(), + glickoRatingProto.getRatingDeviation(), + glickoRatingProto.getLastGameTimeMs()); + } else { + glickoRating = GlickoRatingSystem.getInitialRating(); + } + return GlickoRatingSystem.getDisplayedRating(glickoRating); + } + + private static int userStatsToConstructedRating(ResultProtos.UserStatsProto proto) { + GlickoRating glickoRating; + if (proto.hasConstructedGlickoRating()) { + ResultProtos.GlickoRatingProto glickoRatingProto = proto.getConstructedGlickoRating(); + glickoRating = new GlickoRating( + glickoRatingProto.getRating(), + glickoRatingProto.getRatingDeviation(), + glickoRatingProto.getLastGameTimeMs()); + } else { + glickoRating = GlickoRatingSystem.getInitialRating(); + } + return GlickoRatingSystem.getDisplayedRating(glickoRating); + } + + private static int userStatsToLimitedRating(ResultProtos.UserStatsProto proto) { + GlickoRating glickoRating; + if (proto.hasLimitedGlickoRating()) { + ResultProtos.GlickoRatingProto glickoRatingProto = proto.getLimitedGlickoRating(); + glickoRating = new GlickoRating( + glickoRatingProto.getRating(), + glickoRatingProto.getRatingDeviation(), + glickoRatingProto.getLastGameTimeMs()); + } else { + glickoRating = GlickoRatingSystem.getInitialRating(); + } + return GlickoRatingSystem.getDisplayedRating(glickoRating); + } + private static void joinStrings(StringBuilder joined, List strings, String separator) { for (int i = 0; i < strings.size(); ++i) { if (i > 0) { diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 1cb20d01ec8..b7aeb5e1b76 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -199,14 +199,16 @@ public class UserManager { public String getUserHistory(String userName) { User user = getUserByName(userName); - if (user == null) { - UserStats userStats = UserStatsRepository.instance.getUser(userName); - if (userStats == null) { - return "User " + userName + " not found"; - } - return "History of user " + userName + ": " + User.userStatsToHistory(userStats.getProto()); + if (user != null) { + return "History of user " + userName + " - " + user.getUserData().getHistory(); } - return "History of user " + userName + ": " + user.getUserData().getHistory(); + + UserStats userStats = UserStatsRepository.instance.getUser(userName); + if (userStats != null) { + return "History of user " + userName + " - " + User.userStatsToHistory(userStats.getProto()); + } + + return "User " + userName + " not found"; } public void updateUserHistory() { diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java index da804121e47..85a868464b4 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomImpl.java @@ -116,7 +116,9 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { try { users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), user.getMatchHistory(), user.getMatchQuitRatio(), user.getTourneyHistory(), - user.getTourneyQuitRatio(), user.getGameInfo(), user.getPingInfo())); + user.getTourneyQuitRatio(), user.getGameInfo(), user.getPingInfo(), + user.getUserData().getGeneralRating(), user.getUserData().getConstructedRating(), + user.getUserData().getLimitedRating())); } catch (Exception ex) { LOGGER.fatal("User update exception: " + user.getName() + " - " + ex.toString(), ex); users.add(new UsersView( @@ -127,7 +129,10 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable { user.getTourneyHistory() != null ? user.getTourneyHistory() : "", user.getTourneyQuitRatio(), "[exception]", - user.getPingInfo() != null ? user.getPingInfo() : "")); + user.getPingInfo() != null ? user.getPingInfo() : "", + user.getUserData() != null ? user.getUserData().getGeneralRating() : 0, + user.getUserData() != null ? user.getUserData().getConstructedRating() : 0, + user.getUserData() != null ? user.getUserData().getLimitedRating() : 0)); } } diff --git a/Mage.Server/src/main/java/mage/server/rating/GlickoRating.java b/Mage.Server/src/main/java/mage/server/rating/GlickoRating.java new file mode 100644 index 00000000000..f540a734e22 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/rating/GlickoRating.java @@ -0,0 +1,76 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.server.rating; + +/** + * + * @author Quercitron + */ +public class GlickoRating { + + private double rating; + + private double ratingDeviation; + + private long lastGameTimeMs; + + public GlickoRating(double rating, double ratingDeviation) { + this(rating, ratingDeviation, 0); + } + + public GlickoRating(double rating, double ratingDeviation, long lastGameTimeMs) { + this.rating = rating; + this.ratingDeviation = ratingDeviation; + this.lastGameTimeMs = lastGameTimeMs; + } + + public double getRating() { + return rating; + } + + public void setRating(double rating) { + this.rating = rating; + } + + public double getRatingDeviation() { + return ratingDeviation; + } + + public void setRatingDeviation(double ratingDeviation) { + this.ratingDeviation = ratingDeviation; + } + + public long getLastGameTimeMs() { + return lastGameTimeMs; + } + + public void setLastGameTimeMs(long lastGameTimeMs) { + this.lastGameTimeMs = lastGameTimeMs; + } + +} diff --git a/Mage.Server/src/main/java/mage/server/rating/GlickoRatingSystem.java b/Mage.Server/src/main/java/mage/server/rating/GlickoRatingSystem.java new file mode 100644 index 00000000000..9d1252b60c1 --- /dev/null +++ b/Mage.Server/src/main/java/mage/server/rating/GlickoRatingSystem.java @@ -0,0 +1,112 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.server.rating; + +import java.util.Date; + +/** + * + * @author Quercitron + */ +public class GlickoRatingSystem { + + // rating deviation will grow back from 50 to max 350 in 2 years + public static final double C = 0.00137934314767061324980397708525; + + public static final double BaseRating = 1500; + public static final double BaseRD = 350; + public static final double MinRD = 30; + + private static final double Q = Math.log(10) / 400; + + public static GlickoRating getInitialRating() { + return new GlickoRating(GlickoRatingSystem.BaseRating, GlickoRatingSystem.BaseRD, 0); + } + + public static int getDisplayedRating(GlickoRating rating) { + long currentTime = new Date().getTime(); + double updatedRatingDeviation = getUpdatedRD(rating, currentTime); + return (int) Math.max(rating.getRating() - 2 * updatedRatingDeviation, 0); + } + + public static int getDefaultDisplayedRating() { + return getDisplayedRating(getInitialRating()); + } + + public void updateRating(GlickoRating playerRating, GlickoRating opponentRating, double outcome, long gameTimeMs) { + playerRating.setRatingDeviation(getUpdatedRD(playerRating, gameTimeMs)); + opponentRating.setRatingDeviation(getUpdatedRD(opponentRating, gameTimeMs)); + + GlickoRating newPlayerRating = getNewRating(playerRating, opponentRating, outcome); + GlickoRating newOpponentRating = getNewRating(opponentRating, playerRating, 1 - outcome); + + playerRating.setRating(newPlayerRating.getRating()); + playerRating.setRatingDeviation(newPlayerRating.getRatingDeviation()); + playerRating.setLastGameTimeMs(gameTimeMs); + + opponentRating.setRating(newOpponentRating.getRating()); + opponentRating.setRatingDeviation(newOpponentRating.getRatingDeviation()); + opponentRating.setLastGameTimeMs(gameTimeMs); + } + + private static double getUpdatedRD(GlickoRating rating, long gameTimeMs) { + double newRatingDeviation; + if (rating.getLastGameTimeMs() != 0) + { + double newRD = Math.sqrt( + rating.getRatingDeviation() * rating.getRatingDeviation() + + C * C * Math.max(gameTimeMs - rating.getLastGameTimeMs(), 0)); + newRatingDeviation = Math.max(Math.min(BaseRD, newRD), MinRD); + } + else + { + newRatingDeviation = BaseRD; + } + return newRatingDeviation; + } + + private GlickoRating getNewRating(GlickoRating playerRating, GlickoRating opponentRating, double outcome) { + double RD = playerRating.getRatingDeviation(); + + double g = gFunc(opponentRating.getRatingDeviation()); + double p = -g * (playerRating.getRating() - opponentRating.getRating()) / 400; + double e = 1 / (1 + Math.pow(10, p)); + double d2 = 1 / (Q * Q * g * g * e * (1 - e)); + + // todo: set minimum K? + double newRating = playerRating.getRating() + Q / (1 / RD / RD + 1 / d2) * g * (outcome - e); + double newRD = Math.sqrt(1 / (1 / RD / RD + 1 / d2)); + + return new GlickoRating(newRating, newRD); + } + + private double gFunc(double rd) { + return 1 / Math.sqrt(1 + 3 * Q * Q * rd * rd / Math.PI / Math.PI); + } + +} diff --git a/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java b/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java index 80bfd0123d0..0784ee448e8 100644 --- a/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java +++ b/Mage.Server/src/main/java/mage/server/record/UserStatsRepository.java @@ -14,6 +14,8 @@ import java.util.HashSet; import java.util.List; import mage.cards.repository.RepositoryUtil; import mage.game.result.ResultProtos; +import mage.server.rating.GlickoRating; +import mage.server.rating.GlickoRatingSystem; import org.apache.log4j.Logger; public enum UserStatsRepository { @@ -120,7 +122,9 @@ public enum UserStatsRepository { ResultProtos.MatchProto match = table.getMatch(); for (ResultProtos.MatchPlayerProto player : match.getPlayersList()) { UserStats userStats = this.getUser(player.getName()); - ResultProtos.UserStatsProto proto = userStats != null ? userStats.getProto() + ResultProtos.UserStatsProto proto = + userStats != null + ? userStats.getProto() : ResultProtos.UserStatsProto.newBuilder().setName(player.getName()).build(); ResultProtos.UserStatsProto.Builder builder = ResultProtos.UserStatsProto.newBuilder(proto) .setMatches(proto.getMatches() + 1); @@ -142,6 +146,7 @@ public enum UserStatsRepository { } updatedUsers.add(player.getName()); } + updateRating(match, table.getEndTimeMs()); } else if (table.hasTourney()) { ResultProtos.TourneyProto tourney = table.getTourney(); for (ResultProtos.TourneyPlayerProto player : tourney.getPlayersList()) { @@ -168,12 +173,196 @@ public enum UserStatsRepository { } updatedUsers.add(player.getName()); } + + for (ResultProtos.TourneyRoundProto round : tourney.getRoundsList()) { + for (ResultProtos.MatchProto match : round.getMatchesList()) { + updateRating(match, table.getEndTimeMs()); + } + } } } } return new ArrayList<>(updatedUsers); } + private void updateRating(ResultProtos.MatchProto match, long tableEndTimeMs) { + long matchEndTimeMs; + if (match.hasEndTimeMs()) { + matchEndTimeMs = match.getEndTimeMs(); + } else { + matchEndTimeMs = tableEndTimeMs; + } + + // process only match with options + if (!match.hasMatchOptions()) { + return; + } + ResultProtos.MatchOptionsProto matchOptions = match.getMatchOptions(); + + // process only rated matches + if (!matchOptions.getRated()) { + return; + } + + // rating only for duels + if (match.getPlayersCount() != 2) { + return; + } + + ResultProtos.MatchPlayerProto player1 = match.getPlayers(0); + ResultProtos.MatchPlayerProto player2 = match.getPlayers(1); + + // rate only games between human players + if (!player1.getHuman() || !player2.getHuman()) { + return; + } + + double outcome; + if ((player1.getQuit() == ResultProtos.MatchQuitStatus.NO_MATCH_QUIT && player1.getWins() > player2.getWins()) + || player2.getQuit() != ResultProtos.MatchQuitStatus.NO_MATCH_QUIT) { + // player1 won + outcome = 1; + } else if ((player2.getQuit() == ResultProtos.MatchQuitStatus.NO_MATCH_QUIT && player1.getWins() < player2.getWins()) + || player1.getQuit() != ResultProtos.MatchQuitStatus.NO_MATCH_QUIT) { + // player2 won + outcome = 0; + } else { + // draw + outcome = 0.5; + } + + ResultProtos.UserStatsProto player1StatsProto = getUserStatsProto(player1.getName(), matchEndTimeMs); + ResultProtos.UserStatsProto player2StatsProto = getUserStatsProto(player2.getName(), matchEndTimeMs); + + ResultProtos.UserStatsProto.Builder player1StatsBuilder = + ResultProtos.UserStatsProto.newBuilder(player1StatsProto); + ResultProtos.UserStatsProto.Builder player2StatsBuilder = + ResultProtos.UserStatsProto.newBuilder(player2StatsProto); + + // update general rating + ResultProtos.GlickoRatingProto player1GeneralRatingProto = null; + if (player1StatsProto.hasGeneralGlickoRating()) { + player1GeneralRatingProto = player1StatsProto.getGeneralGlickoRating(); + } + + ResultProtos.GlickoRatingProto player2GeneralRatingProto = null; + if (player2StatsProto.hasGeneralGlickoRating()) { + player2GeneralRatingProto = player2StatsProto.getGeneralGlickoRating(); + } + + ResultProtos.GlickoRatingProto.Builder player1GeneralGlickoRatingBuilder = + player1StatsBuilder.getGeneralGlickoRatingBuilder(); + + ResultProtos.GlickoRatingProto.Builder player2GeneralGlickoRatingBuilder = + player2StatsBuilder.getGeneralGlickoRatingBuilder(); + + updateRating(player1GeneralRatingProto, player2GeneralRatingProto, outcome, matchEndTimeMs, + player1GeneralGlickoRatingBuilder, player2GeneralGlickoRatingBuilder); + + if (matchOptions.hasLimited()) { + if (matchOptions.getLimited()) { + // update limited rating + ResultProtos.GlickoRatingProto player1LimitedRatingProto = null; + if (player1StatsProto.hasLimitedGlickoRating()) { + player1LimitedRatingProto = player1StatsProto.getLimitedGlickoRating(); + } + + ResultProtos.GlickoRatingProto player2LimitedRatingProto = null; + if (player2StatsProto.hasLimitedGlickoRating()) { + player2LimitedRatingProto = player2StatsProto.getLimitedGlickoRating(); + } + + ResultProtos.GlickoRatingProto.Builder player1LimitedGlickoRatingBuilder = + player1StatsBuilder.getLimitedGlickoRatingBuilder(); + + ResultProtos.GlickoRatingProto.Builder player2LimitedGlickoRatingBuilder = + player2StatsBuilder.getLimitedGlickoRatingBuilder(); + + updateRating(player1LimitedRatingProto, player2LimitedRatingProto, outcome, matchEndTimeMs, + player1LimitedGlickoRatingBuilder, player2LimitedGlickoRatingBuilder); + } else { + // update constructed rating + + ResultProtos.GlickoRatingProto player1ConstructedRatingProto = null; + if (player1StatsProto.hasConstructedGlickoRating()) { + player1ConstructedRatingProto = player1StatsProto.getConstructedGlickoRating(); + } + + ResultProtos.GlickoRatingProto player2ConstructedRatingProto = null; + if (player2StatsProto.hasConstructedGlickoRating()) { + player2ConstructedRatingProto = player2StatsProto.getConstructedGlickoRating(); + } + + ResultProtos.GlickoRatingProto.Builder player1ConstructedGlickoRatingBuilder = + player1StatsBuilder.getConstructedGlickoRatingBuilder(); + + ResultProtos.GlickoRatingProto.Builder player2ConstructedGlickoRatingBuilder = + player2StatsBuilder.getConstructedGlickoRatingBuilder(); + + updateRating(player1ConstructedRatingProto, player2ConstructedRatingProto, outcome, matchEndTimeMs, + player1ConstructedGlickoRatingBuilder, player2ConstructedGlickoRatingBuilder); + } + } + + + this.update(new UserStats(player1StatsBuilder.build(), matchEndTimeMs)); + this.update(new UserStats(player2StatsBuilder.build(), matchEndTimeMs)); + } + + private void updateRating( + ResultProtos.GlickoRatingProto player1RatingProto, + ResultProtos.GlickoRatingProto player2RatingProto, + double outcome, + long tableEndTimeMs, + ResultProtos.GlickoRatingProto.Builder player1GlickoRatingBuilder, + ResultProtos.GlickoRatingProto.Builder player2GlickoRatingBuilder) { + + GlickoRating player1GlickoRating; + if (player1RatingProto != null) { + player1GlickoRating = new GlickoRating( + player1RatingProto.getRating(), + player1RatingProto.getRatingDeviation(), + player1RatingProto.getLastGameTimeMs()); + } else { + player1GlickoRating = GlickoRatingSystem.getInitialRating(); + } + + GlickoRating player2GlickoRating; + if (player2RatingProto != null) { + player2GlickoRating = new GlickoRating( + player2RatingProto.getRating(), + player2RatingProto.getRatingDeviation(), + player2RatingProto.getLastGameTimeMs()); + } else { + player2GlickoRating = GlickoRatingSystem.getInitialRating(); + } + + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + glickoRatingSystem.updateRating(player1GlickoRating, player2GlickoRating, outcome, tableEndTimeMs); + + player1GlickoRatingBuilder + .setRating(player1GlickoRating.getRating()) + .setRatingDeviation(player1GlickoRating.getRatingDeviation()) + .setLastGameTimeMs(tableEndTimeMs); + + player2GlickoRatingBuilder + .setRating(player2GlickoRating.getRating()) + .setRatingDeviation(player2GlickoRating.getRatingDeviation()) + .setLastGameTimeMs(tableEndTimeMs); + } + + private ResultProtos.UserStatsProto getUserStatsProto(String playerName, long endTimeMs) { + UserStats player1Stats = this.getUser(playerName); + ResultProtos.UserStatsProto player1StatsProto; + if (player1Stats != null) { + player1StatsProto = player1Stats.getProto(); + } else { + player1StatsProto = ResultProtos.UserStatsProto.newBuilder().setName(playerName).build(); + this.add(new UserStats(player1StatsProto, endTimeMs)); + } + return player1StatsProto; + } + public void closeDB() { try { if (dao != null && dao.getConnectionSource() != null) { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/rating/GlickoRatingSystemTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/rating/GlickoRatingSystemTest.java new file mode 100644 index 00000000000..77e25823ac9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/rating/GlickoRatingSystemTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 2011 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.serverside.rating; + +import org.junit.Assert; +import mage.server.rating.GlickoRating; +import mage.server.rating.GlickoRatingSystem; +import org.junit.Test; + +import java.util.Random; + +/** + * + * @author Quercitron + */ +public class GlickoRatingSystemTest { + + private final Random random = new Random(); + + @Test + public void testRatingsAreEqualAfterDraws() { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + int count = 1000; + for (int i = 0; i < count; i++) { + double startRating = random.nextDouble() * 2500 + 500; + double startRatingDeviation = Math.min(random.nextDouble() * 300 + 100, GlickoRatingSystem.BaseRD); + GlickoRating player1 = new GlickoRating(startRating, startRatingDeviation, 1); + GlickoRating player2 = new GlickoRating(startRating, startRatingDeviation, 1); + + int gamesCount = random.nextInt(50) + 1; + + for (int j = 0; j < gamesCount; j++) { + glickoRatingSystem.updateRating(player1, player2, 0.5, j + 2); + Assert.assertEquals(player1.getRating(), player2.getRating(), 1e-5); + Assert.assertEquals(player1.getRatingDeviation(), player2.getRatingDeviation(), 1e-5); + } + } + } + + @Test + public void testRatingChangesAreSymmetric() { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + int count = 1000; + for (int i = 0; i < count; i++) { + double startRating1 = random.nextDouble() * 2500 + 500; + double startRating2 = random.nextDouble() * 2500 + 500; + double startRatingDeviation = Math.min(random.nextDouble() * 300 + 100, GlickoRatingSystem.BaseRD); + GlickoRating player1 = new GlickoRating(startRating1, startRatingDeviation, 1); + GlickoRating player2 = new GlickoRating(startRating2, startRatingDeviation, 1); + + glickoRatingSystem.updateRating(player1, player2, random.nextDouble(), 1); + Assert.assertEquals(player1.getRating() - startRating1, startRating2 - player2.getRating(), 1e-5); + Assert.assertEquals(player1.getRatingDeviation(), player2.getRatingDeviation(), 1e-5); + } + } + + @Test + public void testExactResult1() + { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + GlickoRating player1 = new GlickoRating(1500, 350, 1); + GlickoRating player2 = new GlickoRating(1500, 350, 1); + + glickoRatingSystem.updateRating(player1, player2, 1, 1); + + Assert.assertEquals(1662, player1.getRating(), 1); + Assert.assertEquals(290.2, player1.getRatingDeviation(), 0.1); + + Assert.assertEquals(1338, player2.getRating(), 1); + Assert.assertEquals(290.2, player2.getRatingDeviation(), 0.1); + } + + @Test + public void testExactResult2() + { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + GlickoRating player1 = new GlickoRating(1500, 350, 1); + GlickoRating player2 = new GlickoRating(1200, 50, 1); + + glickoRatingSystem.updateRating(player1, player2, 1, 1); + + Assert.assertEquals(1571, player1.getRating(), 1); + Assert.assertEquals(284.3, player1.getRatingDeviation(), 0.1); + + Assert.assertEquals(1198, player2.getRating(), 1); + Assert.assertEquals(49.8, player2.getRatingDeviation(), 0.1); + } + + @Test + public void testExactResult3() + { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + GlickoRating player1 = new GlickoRating(1500, 350, 1); + GlickoRating player2 = new GlickoRating(1200, 50, 1); + + glickoRatingSystem.updateRating(player1, player2, 0, 1); + + Assert.assertEquals(1111, player1.getRating(), 1); + Assert.assertEquals(284.3, player1.getRatingDeviation(), 0.1); + + Assert.assertEquals(1207, player2.getRating(), 1); + Assert.assertEquals(49.8, player2.getRatingDeviation(), 0.1); + } + + @Test + public void testExactResult4() + { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + GlickoRating player1 = new GlickoRating(1500, 250, 1); + GlickoRating player2 = new GlickoRating(2000, 100, 1); + + glickoRatingSystem.updateRating(player1, player2, 0.5, 1); + + Assert.assertEquals(1636, player1.getRating(), 1); + Assert.assertEquals(237.6, player1.getRatingDeviation(), 0.1); + + Assert.assertEquals(1982, player2.getRating(), 1); + Assert.assertEquals(99.1, player2.getRatingDeviation(), 0.1); + } + + @Test + public void testExactResult5() + { + GlickoRatingSystem glickoRatingSystem = new GlickoRatingSystem(); + + GlickoRating player1 = new GlickoRating(1500, 100, 1); + GlickoRating player2 = new GlickoRating(2000, 100, 1); + + glickoRatingSystem.updateRating(player1, player2, 1, 1); + + Assert.assertEquals(1551, player1.getRating(), 1); + Assert.assertEquals(99.2, player1.getRatingDeviation(), 0.1); + + Assert.assertEquals(1949, player2.getRating(), 1); + Assert.assertEquals(99.2, player2.getRatingDeviation(), 0.1); + } + +} diff --git a/Mage/src/main/java/mage/game/match/MatchImpl.java b/Mage/src/main/java/mage/game/match/MatchImpl.java index 30f7cd53ee0..a864f1f5c6e 100644 --- a/Mage/src/main/java/mage/game/match/MatchImpl.java +++ b/Mage/src/main/java/mage/game/match/MatchImpl.java @@ -431,7 +431,8 @@ public abstract class MatchImpl implements Match { if (getDraws() > 0) { sb.append(" Draws: ").append(getDraws()).append("
"); } - sb.append("
").append("You have to win ").append(this.getWinsNeeded()).append(this.getWinsNeeded() == 1 ? " game" : " games").append(" to win the complete match
"); + sb.append("
").append("Match is ").append(this.getOptions().isRated() ? "" : "not ").append("rated
"); + sb.append("You have to win ").append(this.getWinsNeeded()).append(this.getWinsNeeded() == 1 ? " game" : " games").append(" to win the complete match
"); sb.append("
Game has started

"); return sb.toString(); } @@ -497,7 +498,9 @@ public abstract class MatchImpl implements Match { .setGameType(this.getOptions().getGameType()) .setDeckType(this.getOptions().getDeckType()) .setGames(this.getNumGames()) - .setDraws(this.getDraws()); + .setDraws(this.getDraws()) + .setMatchOptions(this.getOptions().toProto()) + .setEndTimeMs((this.getEndTime() != null ? this.getEndTime() : new Date()).getTime()); for (MatchPlayer matchPlayer : this.getPlayers()) { MatchQuitStatus status = !matchPlayer.hasQuit() ? MatchQuitStatus.NO_MATCH_QUIT : matchPlayer.getPlayer().hasTimerTimeout() ? MatchQuitStatus.TIMER_TIMEOUT : @@ -505,6 +508,7 @@ public abstract class MatchImpl implements Match { MatchQuitStatus.QUIT; builder.addPlayersBuilder() .setName(matchPlayer.getName()) + .setHuman(matchPlayer.getPlayer().isHuman()) .setQuit(status) .setWins(matchPlayer.getWins()); } diff --git a/Mage/src/main/java/mage/game/match/MatchOptions.java b/Mage/src/main/java/mage/game/match/MatchOptions.java index 28479dda0ab..930f88555fc 100644 --- a/Mage/src/main/java/mage/game/match/MatchOptions.java +++ b/Mage/src/main/java/mage/game/match/MatchOptions.java @@ -35,6 +35,7 @@ import mage.constants.MatchTimeLimit; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; import mage.constants.SkillLevel; +import mage.game.result.ResultProtos; /** * @@ -55,6 +56,7 @@ public class MatchOptions implements Serializable { protected SkillLevel skillLevel; protected boolean rollbackTurnsAllowed; protected int quitRatio; + protected boolean rated; /** * Time each player has during the game to play using his\her priority. @@ -177,4 +179,36 @@ public class MatchOptions implements Serializable { public void setQuitRatio(int quitRatio) { this.quitRatio = quitRatio; } + + public boolean isRated() { + return rated; + } + + public void setRated(boolean rated) { + this.rated = rated; + } + + public ResultProtos.MatchOptionsProto toProto() { + ResultProtos.MatchOptionsProto.Builder builder = ResultProtos.MatchOptionsProto.newBuilder() + .setName(this.getName()) + .setLimited(this.isLimited()) + .setRated(this.isRated()) + .setWinsNeeded(this.getWinsNeeded()); + + ResultProtos.SkillLevel skillLevel = ResultProtos.SkillLevel.BEGINNER; + switch (this.getSkillLevel()) { + case BEGINNER: + skillLevel = ResultProtos.SkillLevel.BEGINNER; + break; + case CASUAL: + skillLevel = ResultProtos.SkillLevel.CASUAL; + break; + case SERIOUS: + skillLevel = ResultProtos.SkillLevel.SERIOUS; + break; + } + builder.setSkillLevel(skillLevel); + + return builder.build(); + } } diff --git a/Mage/src/main/java/mage/game/tournament/TournamentImpl.java b/Mage/src/main/java/mage/game/tournament/TournamentImpl.java index 177c299e9e6..ae2fea34ce7 100644 --- a/Mage/src/main/java/mage/game/tournament/TournamentImpl.java +++ b/Mage/src/main/java/mage/game/tournament/TournamentImpl.java @@ -584,7 +584,9 @@ public abstract class TournamentImpl implements Tournament { .setGames(match.getNumGames()) .setDraws(match.getDraws()) .addPlayers(matchToProto(match, pair.getPlayer1())) - .addPlayers(matchToProto(match, pair.getPlayer2())); + .addPlayers(matchToProto(match, pair.getPlayer2())) + .setMatchOptions(match.getOptions().toProto()) + .setEndTimeMs((match.getEndTime() != null ? match.getEndTime() : new Date()).getTime()); } } for (TournamentPlayer tp : round.getPlayerByes()) { @@ -602,6 +604,7 @@ public abstract class TournamentImpl implements Tournament { MatchQuitStatus.QUIT; return MatchPlayerProto.newBuilder() .setName(player.getPlayer().getName()) + .setHuman(player.getPlayer().isHuman()) .setWins(matchPlayer.getWins()) .setQuit(quit) .build(); diff --git a/Mage/src/main/java/mage/players/net/UserData.java b/Mage/src/main/java/mage/players/net/UserData.java index 040ba9aa738..9174b4c3a87 100644 --- a/Mage/src/main/java/mage/players/net/UserData.java +++ b/Mage/src/main/java/mage/players/net/UserData.java @@ -29,6 +29,10 @@ public class UserData implements Serializable { protected String tourneyHistory; protected int tourneyQuitRatio; + private int generalRating; + private int constructedRating; + private int limitedRating; + public UserData(UserGroup userGroup, int avatarId, boolean showAbilityPickerForced, boolean allowRequestShowHandCards, boolean confirmEmptyManaPool, UserSkipPrioritySteps userSkipPrioritySteps, String flagName, boolean askMoveToGraveOrder, boolean manaPoolAutomatic, boolean manaPoolAutomaticRestricted, @@ -68,6 +72,7 @@ public class UserData implements Serializable { this.passPriorityActivation = userData.passPriorityActivation; this.autoOrderTrigger = userData.autoOrderTrigger; this.useFirstManaAbility = userData.useFirstManaAbility; + // todo: why we don't copy user stats here? } public static UserData getDefaultUserDataView() { @@ -190,7 +195,10 @@ public class UserData implements Serializable { if (UserGroup.COMPUTER.equals(this.groupId)) { return ""; } - return "Matches: " + this.matchHistory + " (" + this.matchQuitRatio + "%) Tourneys: " + this.tourneyHistory + " (" + this.tourneyQuitRatio + "%)"; + // todo: add preference to hide rating? + return "Matches: " + this.matchHistory + " (" + this.matchQuitRatio + "%), Tourneys: " + this.tourneyHistory + " (" + this.tourneyQuitRatio + "%)" + + ", Constructed Rating: " + getConstructedRating() + + ", Limited Rating: " + getLimitedRating(); } public void setMatchHistory(String history) { @@ -225,8 +233,31 @@ public class UserData implements Serializable { return tourneyQuitRatio; } + public int getGeneralRating() { + return generalRating; + } + + public void setGeneralRating(int generalRating) { + this.generalRating = generalRating; + } + + public int getConstructedRating() { + return constructedRating; + } + + public void setConstructedRating(int constructedRating) { + this.constructedRating = constructedRating; + } + + public int getLimitedRating() { + return limitedRating; + } + + public void setLimitedRating(int limitedRating) { + this.limitedRating = limitedRating; + } + public static String getDefaultFlagName() { return "world.png"; } - } diff --git a/Mage/src/main/proto/result.proto b/Mage/src/main/proto/result.proto index b24d4714f5f..819f72d96aa 100644 --- a/Mage/src/main/proto/result.proto +++ b/Mage/src/main/proto/result.proto @@ -20,6 +20,22 @@ message MatchProto { optional int32 games = 4; optional int32 draws = 5; repeated MatchPlayerProto players = 6; + optional MatchOptionsProto match_options = 7; + optional int64 end_time_ms = 8; +} + +message MatchOptionsProto { + optional string name = 1; + optional bool limited = 2; + optional bool rated = 3; + optional SkillLevel skill_level = 4; + optional int32 wins_needed = 5; +} + +enum SkillLevel { + BEGINNER = 0; + CASUAL = 1; + SERIOUS = 2; } message MatchPlayerProto { @@ -27,6 +43,7 @@ message MatchPlayerProto { optional int32 wins = 2; optional MatchQuitStatus quit = 3; optional bool bye = 4; + optional bool human = 5; } enum MatchQuitStatus { @@ -74,4 +91,14 @@ message UserStatsProto { optional int32 matches_idle_timeout = 7; optional int32 matches_timer_timeout = 8; optional int32 matches_quit = 9; + + optional GlickoRatingProto general_glicko_rating = 10; + optional GlickoRatingProto constructed_glicko_rating = 11; + optional GlickoRatingProto limited_glicko_rating = 12; +} + +message GlickoRatingProto { + required double rating = 1; + required double rating_deviation = 2; + optional int64 last_game_time_ms = 3; }