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;
}