Record game histories. Compute user stats and show them in the user panel.

This commit is contained in:
Me Car 2016-01-20 12:47:58 +09:00
parent 9f3e2aa4c4
commit 550648ccbe
22 changed files with 866 additions and 60 deletions

2
.gitignore vendored
View file

@ -94,3 +94,5 @@ Mage.Client/serverlist.txt
/target/
client_secrets.json
dependency-reduced-pom.xml

View file

@ -61,7 +61,7 @@ public class PlayersChatPanel extends javax.swing.JPanel {
private final List<String> players = new ArrayList<>();
private final UserTableModel userTableModel;
private static final int[] defaultColumnsWidth = {20, 100, 100, 80, 80};
private static final int[] defaultColumnsWidth = {20, 100, 100, 100, 80, 80};
/*
@ -118,7 +118,7 @@ public class PlayersChatPanel extends javax.swing.JPanel {
class UserTableModel extends AbstractTableModel {
private final String[] columnNames = new String[]{"Loc", "Players", "Info", "Games", "Connection"};
private final String[] columnNames = new String[]{"Loc", "Players", "History", "Info", "Games", "Connection"};
private UsersView[] players = new UsersView[0];
public void loadData(Collection<RoomUsersView> roomUserInfoList) throws MageRemoteException {
@ -154,10 +154,12 @@ public class PlayersChatPanel extends javax.swing.JPanel {
case 1:
return players[arg0].getUserName();
case 2:
return players[arg0].getInfoState();
return players[arg0].getHistory();
case 3:
return players[arg0].getInfoGames();
return players[arg0].getInfoState();
case 4:
return players[arg0].getInfoGames();
case 5:
return players[arg0].getInfoPing();
}
return "";

View file

@ -39,12 +39,14 @@ public class UsersView implements Serializable {
private final String flagName;
private final String userName;
private final String history;
private final String infoState;
private final String infoGames;
private final String infoPing;
public UsersView(String flagName, String userName, String infoState, String infoGames, String infoPing) {
public UsersView(String flagName, String userName, String history, String infoState, String infoGames, String infoPing) {
this.flagName = flagName;
this.history = history;
this.userName = userName;
this.infoState = infoState;
this.infoGames = infoGames;
@ -59,6 +61,10 @@ public class UsersView implements Serializable {
return userName;
}
public String getHistory() {
return history;
}
public String getInfoState() {
return infoState;
}

View file

@ -199,6 +199,11 @@
<artifactId>jersey-multipart</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.7.2</version>
</dependency>
</dependencies>
<build>

View file

@ -438,7 +438,7 @@ public class MageServerImpl implements MageServer {
// }
@Override
public boolean startMatch(final String sessionId, final UUID roomId, final UUID tableId) throws MageException {
if (!TableManager.getInstance().getController(tableId).changeTableState(TableState.STARTING)) {
if (!TableManager.getInstance().getController(tableId).changeTableStateToStarting()) {
return false;
}
execute("startMatch", sessionId, new Action() {
@ -463,7 +463,7 @@ public class MageServerImpl implements MageServer {
// }
@Override
public boolean startTournament(final String sessionId, final UUID roomId, final UUID tableId) throws MageException {
if (!TableManager.getInstance().getController(tableId).changeTableState(TableState.STARTING)) {
if (!TableManager.getInstance().getController(tableId).changeTableStateToStarting()) {
return false;
}
execute("startTournament", sessionId, new Action() {

View file

@ -32,10 +32,20 @@ import java.io.FilenameFilter;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanServer;
import mage.cards.repository.CardScanner;
import mage.game.match.MatchType;
import mage.game.result.ResultProtos.MatchPlayerProto;
import mage.game.result.ResultProtos.MatchProto;
import mage.game.result.ResultProtos.TableProto;
import mage.game.result.ResultProtos.TourneyPlayerProto;
import mage.game.result.ResultProtos.TourneyProto;
import mage.game.result.ResultProtos.UserStatsProto;
import mage.game.tournament.TournamentType;
import mage.interfaces.MageServer;
import mage.remote.Connection;
@ -43,6 +53,10 @@ import mage.server.draft.CubeFactory;
import mage.server.game.DeckValidatorFactory;
import mage.server.game.GameFactory;
import mage.server.game.PlayerFactory;
import mage.server.record.TableRecord;
import mage.server.record.TableRecordRepository;
import mage.server.record.UserStats;
import mage.server.record.UserStatsRepository;
import mage.server.tournament.TournamentFactory;
import mage.server.util.ConfigSettings;
import mage.server.util.PluginClassLoader;
@ -88,6 +102,8 @@ public class Main {
protected static boolean testMode;
protected static boolean fastDbMode;
private static final ScheduledExecutorService updateUserStatsTaskExecutor = Executors.newSingleThreadScheduledExecutor();
/**
* @param args the command line arguments
*/
@ -174,6 +190,13 @@ public class Main {
} catch (Exception ex) {
logger.fatal("Failed to start server - " + connection.toString(), ex);
}
updateUserStatsTaskExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
updateUserStats();
}
}, 60, 60, TimeUnit.SECONDS);
}
static void initStatistics() {
@ -368,4 +391,68 @@ public class Main {
public static boolean isTestMode() {
return testMode;
}
private static void updateUserStats() {
long latestEndTimeMs = UserStatsRepository.instance.getLatestEndTimeMs();
List<TableRecord> records = TableRecordRepository.instance.getAfter(latestEndTimeMs);
for (TableRecord record : records) {
TableProto table = record.getProto();
if (table.getControllerName().equals("System")) {
// This is a sub table within a tournament, so it's already handled by the main
// tournament table.
continue;
}
if (table.hasMatch()) {
MatchProto match = table.getMatch();
for (MatchPlayerProto player : match.getPlayersList()) {
UserStats userStats = UserStatsRepository.instance.getUser(player.getName());
UserStatsProto proto = userStats != null ? userStats.getProto() :
UserStatsProto.newBuilder().setName(player.getName()).build();
UserStatsProto.Builder builder = UserStatsProto.newBuilder(proto)
.setMatches(proto.getMatches() + 1);
switch (player.getQuit()) {
case IDLE_TIMEOUT:
builder.setMatchesIdleTimeout(proto.getMatchesIdleTimeout() + 1);
break;
case TIMER_TIMEOUT:
builder.setMatchesTimerTimeout(proto.getMatchesTimerTimeout() + 1);
break;
case QUIT:
builder.setMatchesQuit(proto.getMatchesQuit() + 1);
break;
}
if (userStats == null) {
UserStatsRepository.instance.add(new UserStats(builder.build(), table.getEndTimeMs()));
} else {
UserStatsRepository.instance.update(new UserStats(builder.build(), table.getEndTimeMs()));
}
}
} else if (table.hasTourney()) {
TourneyProto tourney = table.getTourney();
for (TourneyPlayerProto player : tourney.getPlayersList()) {
UserStats userStats = UserStatsRepository.instance.getUser(player.getName());
UserStatsProto proto = userStats != null ? userStats.getProto() :
UserStatsProto.newBuilder().setName(player.getName()).build();
UserStatsProto.Builder builder = UserStatsProto.newBuilder(proto)
.setTourneys(proto.getTourneys() + 1);
switch (player.getQuit()) {
case DURING_ROUND:
builder.setTourneysQuitDuringRound(proto.getTourneysQuitDuringRound() + 1);
break;
case DURING_DRAFTING:
builder.setTourneysQuitDuringDrafting(proto.getTourneysQuitDuringDrafting() + 1);
break;
case DURING_CONSTRUCTION:
builder.setTourneysQuitDuringConstruction(proto.getTourneysQuitDuringConstruction() + 1);
break;
}
if (userStats == null) {
UserStatsRepository.instance.add(new UserStats(builder.build(), table.getEndTimeMs()));
} else {
UserStatsRepository.instance.update(new UserStats(builder.build(), table.getEndTimeMs()));
}
}
}
}
}
}

View file

@ -62,6 +62,7 @@ import mage.server.game.DeckValidatorFactory;
import mage.server.game.GameFactory;
import mage.server.game.GameManager;
import mage.server.game.PlayerFactory;
import mage.server.record.TableRecorderImpl;
import mage.server.services.LogKeys;
import mage.server.services.impl.LogServiceImpl;
import mage.server.tournament.TournamentController;
@ -105,7 +106,7 @@ public class TableController {
} else {
controllerName = "System";
}
table = new Table(roomId, options.getGameType(), options.getName(), controllerName, DeckValidatorFactory.getInstance().createDeckValidator(options.getDeckType()), options.getPlayerTypes(), match);
table = new Table(roomId, options.getGameType(), options.getName(), controllerName, DeckValidatorFactory.getInstance().createDeckValidator(options.getDeckType()), options.getPlayerTypes(), TableRecorderImpl.getInstance(), match);
chatId = ChatManager.getInstance().createChatSession("Match Table " + table.getId());
init();
}
@ -124,7 +125,7 @@ public class TableController {
} else {
controllerName = "System";
}
table = new Table(roomId, options.getTournamentType(), options.getName(), controllerName, DeckValidatorFactory.getInstance().createDeckValidator(options.getMatchOptions().getDeckType()), options.getPlayerTypes(), tournament);
table = new Table(roomId, options.getTournamentType(), options.getName(), controllerName, DeckValidatorFactory.getInstance().createDeckValidator(options.getMatchOptions().getDeckType()), options.getPlayerTypes(), TableRecorderImpl.getInstance(), tournament);
chatId = ChatManager.getInstance().createChatSession("Tourn. table " + table.getId());
}
@ -237,6 +238,7 @@ public class TableController {
TournamentPlayer newTournamentPlayer = tournament.getPlayer(newPlayer.getId());
newTournamentPlayer.setState(oldTournamentPlayer.getState());
newTournamentPlayer.setReplacedTournamentPlayer(oldTournamentPlayer);
DraftManager.getInstance().getController(table.getId()).replacePlayer(oldPlayer, newPlayer);
return true;
@ -957,26 +959,16 @@ public class TableController {
return getTable().getState();
}
public synchronized boolean changeTableState(TableState newTableState) {
switch (newTableState) {
case WAITING:
if (getTable().getState().equals(TableState.STARTING)) {
// tournament already started
return false;
}
break;
case STARTING:
if (!getTable().getState().equals(TableState.READY_TO_START)) {
// tournament is not ready, can't start
return false;
}
if (!table.allSeatsAreOccupied()) {
logger.debug("Not alle Seats are occupied: stop start tableId:" + table.getId());
return false;
}
break;
public synchronized boolean changeTableStateToStarting() {
if (!getTable().getState().equals(TableState.READY_TO_START)) {
// tournament is not ready, can't start
return false;
}
getTable().setState(newTableState);
if (!table.allSeatsAreOccupied()) {
logger.debug("Not alle Seats are occupied: stop start tableId:" + table.getId());
return false;
}
getTable().setState(TableState.STARTING);
return true;
}
}

View file

@ -43,11 +43,14 @@ import mage.constants.TableState;
import mage.game.GameException;
import mage.game.Table;
import mage.game.match.MatchOptions;
import mage.game.result.ResultProtos.UserStatsProto;
import mage.game.tournament.TournamentOptions;
import mage.server.RoomImpl;
import mage.server.TableManager;
import mage.server.User;
import mage.server.UserManager;
import mage.server.record.UserStats;
import mage.server.record.UserStatsRepository;
import mage.server.tournament.TournamentManager;
import mage.server.util.ConfigSettings;
import mage.server.util.ThreadExecutor;
@ -91,6 +94,74 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
return tableView;
}
private static void joinStrings(StringBuilder joined, List<String> strings, String separator) {
for (int i = 0; i < strings.size(); ++i) {
if (i > 0) {
joined.append(separator);
}
joined.append(strings.get(i));
}
}
private static String joinBuilders(List<StringBuilder> builders) {
if (builders.isEmpty()) {
return null;
}
StringBuilder builder = builders.get(0);
for (int i = 1; i < builders.size(); ++i) {
builder.append(" ");
builder.append(builders.get(i));
}
return builder.toString();
}
private static String userStatsToString(UserStatsProto proto) {
List<StringBuilder> builders = new ArrayList();
if (proto.getMatches() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("Matches:");
builder.append(proto.getMatches());
List<String> quit = new ArrayList();
if (proto.getMatchesIdleTimeout() > 0) {
quit.add("I:" + Integer.toString(proto.getMatchesIdleTimeout()));
}
if (proto.getMatchesTimerTimeout() > 0) {
quit.add("T:" + Integer.toString(proto.getMatchesTimerTimeout()));
}
if (proto.getMatchesQuit() > 0) {
quit.add("Q:" + Integer.toString(proto.getMatchesQuit()));
}
if (quit.size() > 0) {
builder.append(" (");
joinStrings(builder, quit, " ");
builder.append(")");
}
builders.add(builder);
}
if (proto.getTourneys() > 0) {
StringBuilder builder = new StringBuilder();
builder.append("Tourneys:");
builder.append(proto.getTourneys());
List<String> quit = new ArrayList();
if (proto.getTourneysQuitDuringDrafting() > 0) {
quit.add("D:" + Integer.toString(proto.getTourneysQuitDuringDrafting()));
}
if (proto.getTourneysQuitDuringConstruction() > 0) {
quit.add("C:" + Integer.toString(proto.getTourneysQuitDuringConstruction()));
}
if (proto.getTourneysQuitDuringRound() > 0) {
quit.add("R:" + Integer.toString(proto.getTourneysQuitDuringRound()));
}
if (quit.size() > 0) {
builder.append(" (");
joinStrings(builder, quit, " ");
builder.append(")");
}
builders.add(builder);
}
return joinBuilders(builders);
}
private void update() {
ArrayList<TableView> tableList = new ArrayList<>();
ArrayList<MatchView> matchList = new ArrayList<>();
@ -100,11 +171,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
if (table.getState() != TableState.FINISHED) {
tableList.add(new TableView(table));
} else if (matchList.size() < 50) {
if (table.isTournament()) {
matchList.add(new MatchView(table));
} else {
matchList.add(new MatchView(table));
}
matchList.add(new MatchView(table));
} else {
// more since 50 matches finished since this match so remove it
if (table.isTournament()) {
@ -117,13 +184,19 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
matchView = matchList;
List<UsersView> users = new ArrayList<>();
for (User user : UserManager.getInstance().getUsers()) {
String history = null;
UserStats stats = UserStatsRepository.instance.getUser(user.getName());
if (stats != null) {
history = userStatsToString(stats.getProto());
}
try {
users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), user.getInfo(), user.getGameInfo(), user.getPingInfo()));
users.add(new UsersView(user.getUserData().getFlagName(), user.getName(), history, user.getInfo(), user.getGameInfo(), user.getPingInfo()));
} catch (Exception ex) {
logger.fatal("User update exception: " + user.getName() + " - " + ex.toString(), ex);
users.add(new UsersView(
(user.getUserData() != null && user.getUserData().getFlagName() != null) ? user.getUserData().getFlagName() : "world",
user.getName() != null ? user.getName() : "<no name>",
history != null ? history : "<no history>",
user.getInfo() != null ? user.getInfo() : "<no info>",
"[exception]",
user.getPingInfo() != null ? user.getPingInfo() : "<no ping>"));

View file

@ -0,0 +1,37 @@
package mage.server.record;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import mage.game.result.ResultProtos.TableProto;
import org.mage.mage.shaded.protobuf.InvalidProtocolBufferException;
import org.apache.log4j.Logger;
@DatabaseTable(tableName = "table_history")
public class TableRecord {
private static final Logger logger = Logger.getLogger(TableRecord.class);
@DatabaseField(dataType = DataType.BYTE_ARRAY, indexName = "proto_index", unique = true)
protected byte[] proto;
@DatabaseField(indexName = "end_time_ms")
protected long endTimeMs;
public TableRecord() {
}
public TableRecord(TableProto proto, long endTimeMs) {
this.proto = proto.toByteArray();
this.endTimeMs = endTimeMs;
}
public TableProto getProto() {
try {
return TableProto.parseFrom(this.proto);
} catch (InvalidProtocolBufferException ex) {
logger.error("Failed to parse serialized proto", ex);
}
return null;
}
}

View file

@ -0,0 +1,77 @@
package mage.server.record;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableUtils;
import java.io.File;
import java.sql.SQLException;
import java.util.List;
import mage.cards.repository.RepositoryUtil;
import org.apache.log4j.Logger;
public enum TableRecordRepository {
instance;
private static final String JDBC_URL = "jdbc:sqlite:./db/table_record.db";
private static final String VERSION_ENTITY_NAME = "table_record";
// raise this if db structure was changed
private static final long DB_VERSION = 0;
private Dao<TableRecord, Object> dao;
private TableRecordRepository() {
File file = new File("db");
if (!file.exists()) {
file.mkdirs();
}
try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION);
if (obsolete) {
TableUtils.dropTable(connectionSource, TableRecord.class, true);
}
TableUtils.createTableIfNotExists(connectionSource, TableRecord.class);
dao = DaoManager.createDao(connectionSource, TableRecord.class);
} catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error creating table_record repository - ", ex);
}
}
public void add(TableRecord tableHistory) {
try {
dao.create(tableHistory);
} catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error adding a table_record to DB - ", ex);
}
}
public List<TableRecord> getAfter(long endTimeMs) {
try {
QueryBuilder<TableRecord, Object> qb = dao.queryBuilder();
qb.where().gt("endTimeMs", endTimeMs);
qb.orderBy("endTimeMs", true);
return dao.query(qb.prepare());
} catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error getting table_records from DB - ", ex);
}
return null;
}
public void closeDB() {
try {
if (dao != null && dao.getConnectionSource() != null) {
DatabaseConnection conn = dao.getConnectionSource().getReadWriteConnection();
conn.executeStatement("shutdown compact", 0);
}
} catch (SQLException ex) {
Logger.getLogger(TableRecordRepository.class).error("Error closing table_record repository - ", ex);
}
}
}

View file

@ -0,0 +1,22 @@
package mage.server.record;
import mage.game.Table;
import mage.game.Table.TableRecorder;
import mage.game.result.ResultProtos.TableProto;
import org.apache.log4j.Logger;
public class TableRecorderImpl implements TableRecorder {
private static TableRecorderImpl INSTANCE = new TableRecorderImpl();
private static final Logger logger = Logger.getLogger(TableRecorderImpl.class);
public static TableRecorderImpl getInstance() {
return INSTANCE;
}
public void record(Table table) {
TableProto proto = table.toProto();
logger.info("Adding record:\n" + proto.toString());
TableRecordRepository.instance.add(new TableRecord(proto, proto.getEndTimeMs()));
}
}

View file

@ -0,0 +1,45 @@
package mage.server.record;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import mage.game.result.ResultProtos.UserStatsProto;
import org.mage.mage.shaded.protobuf.InvalidProtocolBufferException;
import org.apache.log4j.Logger;
@DatabaseTable(tableName = "user_stats")
public class UserStats {
private static final Logger logger = Logger.getLogger(TableRecord.class);
@DatabaseField(indexName = "user_name_index", unique = true, id = true)
protected String userName;
@DatabaseField(dataType = DataType.BYTE_ARRAY)
protected byte[] proto;
@DatabaseField(indexName = "end_time_ms_index")
protected long endTimeMs;
public UserStats() {
}
public UserStats(UserStatsProto proto, long endTimeMs) {
this.userName = proto.getName();
this.proto = proto.toByteArray();
this.endTimeMs = endTimeMs;
}
public UserStatsProto getProto() {
try {
return UserStatsProto.parseFrom(this.proto);
} catch (InvalidProtocolBufferException ex) {
logger.error("Failed to parse serialized proto", ex);
}
return null;
}
public long getEndTimeMs() {
return this.endTimeMs;
}
}

View file

@ -0,0 +1,111 @@
package mage.server.record;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableUtils;
import java.io.File;
import java.sql.SQLException;
import java.util.List;
import mage.cards.repository.RepositoryUtil;
import org.apache.log4j.Logger;
public enum UserStatsRepository {
instance;
private static final String JDBC_URL = "jdbc:sqlite:./db/user_stats.db";
private static final String VERSION_ENTITY_NAME = "user_stats";
// raise this if db structure was changed
private static final long DB_VERSION = 0;
private Dao<UserStats, Object> dao;
private UserStatsRepository() {
File file = new File("db");
if (!file.exists()) {
file.mkdirs();
}
try {
ConnectionSource connectionSource = new JdbcConnectionSource(JDBC_URL);
boolean obsolete = RepositoryUtil.isDatabaseObsolete(connectionSource, VERSION_ENTITY_NAME, DB_VERSION);
if (obsolete) {
TableUtils.dropTable(connectionSource, UserStats.class, true);
}
TableUtils.createTableIfNotExists(connectionSource, UserStats.class);
dao = DaoManager.createDao(connectionSource, UserStats.class);
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error creating user_stats repository - ", ex);
}
}
public void add(UserStats userStats) {
try {
dao.create(userStats);
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error adding a user_stats to DB - ", ex);
}
}
public void update(UserStats userStats) {
try {
dao.update(userStats);
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error updating a user_stats in DB - ", ex);
}
}
public UserStats getUser(String userName) {
try {
QueryBuilder<UserStats, Object> qb = dao.queryBuilder();
qb.where().eq("userName", userName);
List<UserStats> users = dao.query(qb.prepare());
if (users.size() == 1) {
return users.get(0);
}
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error getting a user from DB - ", ex);
}
return null;
}
public List<UserStats> getAllUsers() {
try {
QueryBuilder<UserStats, Object> qb = dao.queryBuilder();
return dao.query(qb.prepare());
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error getting all users from DB - ", ex);
}
return null;
}
public long getLatestEndTimeMs() {
try {
QueryBuilder<UserStats, Object> qb = dao.queryBuilder();
qb.orderBy("endTimeMs", false).limit(1);
List<UserStats> users = dao.query(qb.prepare());
if (users.size() == 1) {
return users.get(0).getEndTimeMs();
}
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error getting the latest end time from DB - ", ex);
}
return 0;
}
public void closeDB() {
try {
if (dao != null && dao.getConnectionSource() != null) {
DatabaseConnection conn = dao.getConnectionSource().getReadWriteConnection();
conn.executeStatement("shutdown compact", 0);
}
} catch (SQLException ex) {
Logger.getLogger(UserStatsRepository.class).error("Error closing user_stats repository - ", ex);
}
}
}

View file

@ -43,6 +43,7 @@ import mage.game.events.TableEvent;
import static mage.game.events.TableEvent.EventType.CONSTRUCT;
import mage.game.match.Match;
import mage.game.match.MatchOptions;
import mage.game.result.ResultProtos.TourneyQuitStatus;
import mage.game.tournament.Tournament;
import mage.game.tournament.TournamentPairing;
import mage.game.tournament.TournamentPlayer;
@ -351,31 +352,34 @@ public class TournamentController {
tournamentSession.setKilled();
if (tournamentPlayer.isInTournament()) {
String info;
TourneyQuitStatus status;
if (tournament.isDoneConstructing()) {
info = new StringBuilder("during round ").append(tournament.getRounds().size()).toString();
// quit active matches of that tournament
TableManager.getInstance().userQuitTournamentSubTables(tournament.getId(), userId);
} else {
if (tournamentPlayer.getState().equals(TournamentPlayerState.DRAFTING)) {
info = "during Draft phase";
if (!checkToReplaceDraftPlayerByAi(userId, tournamentPlayer)) {
this.abortDraftTournament();
} else {
DraftController draftController = DraftManager.getInstance().getController(tableId);
if (draftController != null) {
DraftSession draftSession = draftController.getDraftSession(playerId);
if (draftSession != null) {
DraftManager.getInstance().kill(draftSession.getDraftId(), userId);
}
status = TourneyQuitStatus.DURING_ROUND;
} else if (tournamentPlayer.getState().equals(TournamentPlayerState.DRAFTING)) {
info = "during Draft phase";
if (!checkToReplaceDraftPlayerByAi(userId, tournamentPlayer)) {
this.abortDraftTournament();
} else {
DraftController draftController = DraftManager.getInstance().getController(tableId);
if (draftController != null) {
DraftSession draftSession = draftController.getDraftSession(playerId);
if (draftSession != null) {
DraftManager.getInstance().kill(draftSession.getDraftId(), userId);
}
}
} else if (tournamentPlayer.getState().equals(TournamentPlayerState.CONSTRUCTING)) {
info = "during Construction phase";
} else {
info = "";
}
status = TourneyQuitStatus.DURING_DRAFTING;
} else if (tournamentPlayer.getState().equals(TournamentPlayerState.CONSTRUCTING)) {
info = "during Construction phase";
status = TourneyQuitStatus.DURING_CONSTRUCTION;
} else {
info = "";
status = TourneyQuitStatus.NO_TOURNEY_QUIT;
}
tournamentPlayer.setQuit(info);
tournamentPlayer.setQuit(info, status);
tournament.quit(playerId);
tournamentSession.quit();
ChatManager.getInstance().broadcast(chatId, "", tournamentPlayer.getPlayer().getLogName() + " has quit the tournament", MessageColor.BLACK, true, MessageType.STATUS, SoundToPlay.PlayerQuitTournament);

View file

@ -36,6 +36,11 @@
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
</dependencies>
<build>
@ -51,11 +56,141 @@
</configuration>
</plugin>
<!-- http://vlkan.com/blog/post/2015/11/27/maven-protobuf/ -->
<!-- copy protoc binary into build directory -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${maven-dependency-plugin.version}</version>
<executions>
<execution>
<id>copy-protoc</id>
<phase>generate-sources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.google.protobuf</groupId>
<artifactId>protoc</artifactId>
<version>${protobuf.version}</version>
<classifier>${os.detected.classifier}</classifier>
<type>exe</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<!-- compile proto buffer files using copied protoc binary -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven-antrun-plugin.version}</version>
<executions>
<execution>
<id>exec-protoc</id>
<phase>generate-sources</phase>
<configuration>
<target>
<property name="protoc.filename" value="protoc-${protobuf.version}-${os.detected.classifier}.exe"/>
<property name="protoc.filepath" value="${project.build.directory}/${protoc.filename}"/>
<chmod file="${protoc.filepath}" perm="ugo+rx"/>
<mkdir dir="${protobuf.output.directory}" />
<path id="protobuf.input.filepaths.path">
<fileset dir="${protobuf.input.directory}">
<include name="**/*.proto"/>
</fileset>
</path>
<pathconvert pathsep=" " property="protobuf.input.filepaths" refid="protobuf.input.filepaths.path"/>
<exec executable="${protoc.filepath}" failonerror="true">
<arg value="-I"/>
<arg value="${protobuf.input.directory}"/>
<arg value="--java_out"/>
<arg value="${protobuf.output.directory}"/>
<arg line="${protobuf.input.filepaths}"/>
</exec>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- add generated proto buffer classes into the package -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
<executions>
<execution>
<id>add-classes</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${protobuf.output.directory}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<!-- shade protobuf to avoid version conflicts -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>${maven-shade-plugin.version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.google.protobuf</pattern>
<shadedPattern>${project.groupId}.${project.artifactId}.shaded.protobuf</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<finalName>mage</finalName>
<!-- http://vlkan.com/blog/post/2015/11/27/maven-protobuf/ -->
<extensions>
<!-- provides os.detected.classifier (i.e. linux-x86_64, osx-x86_64) property -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os-maven-plugin.version}</version>
</extension>
</extensions>
</build>
<properties/>
<!-- http://vlkan.com/blog/post/2015/11/27/maven-protobuf/ -->
<properties>
<!-- protobuf paths -->
<protobuf.input.directory>${project.basedir}/src/main/proto</protobuf.input.directory>
<protobuf.output.directory>${project.build.directory}/generated-sources</protobuf.output.directory>
<!-- library versions -->
<build-helper-maven-plugin.version>1.9.1</build-helper-maven-plugin.version>
<maven-antrun-plugin.version>1.8</maven-antrun-plugin.version>
<maven-dependency-plugin.version>2.10</maven-dependency-plugin.version>
<maven-shade-plugin.version>2.4.2</maven-shade-plugin.version>
<os-maven-plugin.version>1.4.1.Final</os-maven-plugin.version>
<protobuf.version>3.0.0-beta-1</protobuf.version>
</properties>
</project>

View file

@ -38,6 +38,7 @@ import mage.game.events.Listener;
import mage.game.events.TableEvent;
import mage.game.events.TableEventSource;
import mage.game.match.Match;
import mage.game.result.ResultProtos.TableProto;
import mage.game.tournament.Tournament;
import mage.players.Player;
@ -61,24 +62,29 @@ public class Table implements Serializable {
private TableState state;
private Match match;
private Tournament tournament;
private TableRecorder recorder;
public interface TableRecorder {
void record(Table table);
};
protected TableEventSource tableEventSource = new TableEventSource();
public Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List<String> playerTypes, Tournament tournament) {
this(roomId, gameType, name, controllerName, validator, playerTypes);
public Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List<String> playerTypes, TableRecorder recorder, Tournament tournament) {
this(roomId, gameType, name, controllerName, validator, playerTypes, recorder);
this.tournament = tournament;
this.isTournament = true;
setState(TableState.WAITING);
}
public Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List<String> playerTypes, Match match) {
this(roomId, gameType, name, controllerName, validator, playerTypes);
public Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List<String> playerTypes, TableRecorder recorder, Match match) {
this(roomId, gameType, name, controllerName, validator, playerTypes, recorder);
this.match = match;
this.isTournament = false;
setState(TableState.WAITING);
}
protected Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List<String> playerTypes) {
protected Table(UUID roomId, String gameType, String name, String controllerName, DeckValidator validator, List<String> playerTypes, TableRecorder recorder) {
tableId = UUID.randomUUID();
this.roomId = roomId;
this.numSeats = playerTypes.size();
@ -88,6 +94,7 @@ public class Table implements Serializable {
this.createTime = new Date();
createSeats(playerTypes);
this.validator = validator;
this.recorder = recorder;
}
private void createSeats(List<String> playerTypes) {
@ -235,6 +242,9 @@ public class Table implements Serializable {
if (isTournament()) {
getTournament().setTournamentState(state.toString());
}
if (state == TableState.FINISHED) {
this.recorder.record(this);
}
}
public TableState getState() {
@ -296,5 +306,21 @@ public class Table implements Serializable {
return match.getEndTime();
}
}
public TableProto toProto() {
TableProto.Builder builder = TableProto.newBuilder();
if (this.isTournament()) {
builder.getTourneyBuilder().mergeFrom(this.getTournament().toProto());
} else {
builder.getMatchBuilder().mergeFrom(this.getMatch().toProto());
}
return builder.setGameType(this.getGameType())
.setName(this.getName())
.setGameType(this.getGameType())
.setDeckType(this.getDeckType())
.setControllerName(this.getControllerName())
.setStartTimeMs(this.getStartTime().getTime())
.setEndTimeMs(this.getEndTime().getTime())
.build();
}
}

View file

@ -39,6 +39,7 @@ import java.util.Date;
import java.util.List;
import java.util.UUID;
import mage.game.GameInfo;
import mage.game.result.ResultProtos.MatchProto;
/**
*
@ -111,4 +112,6 @@ public interface Match {
void setTableId(UUID tableId);
void setTournamentRound(int round);
MatchProto toProto();
}

View file

@ -41,6 +41,8 @@ import mage.game.events.Listener;
import mage.game.events.TableEvent;
import mage.game.events.TableEvent.EventType;
import mage.game.events.TableEventSource;
import mage.game.result.ResultProtos.MatchProto;
import mage.game.result.ResultProtos.MatchQuitStatus;
import mage.players.Player;
import mage.util.DateFormat;
import org.apache.log4j.Logger;
@ -488,4 +490,25 @@ public abstract class MatchImpl implements Match {
this.getGames().clear();
}
@Override
public MatchProto toProto() {
MatchProto.Builder builder = MatchProto.newBuilder()
.setName(this.getName())
.setGameType(this.getOptions().getGameType())
.setDeckType(this.getOptions().getDeckType())
.setGames(this.getNumGames())
.setDraws(this.getDraws());
for (MatchPlayer matchPlayer : this.getPlayers()) {
MatchQuitStatus status = !matchPlayer.hasQuit() ? MatchQuitStatus.NO_MATCH_QUIT :
matchPlayer.getPlayer().hasTimerTimeout() ? MatchQuitStatus.TIMER_TIMEOUT :
matchPlayer.getPlayer().hasIdleTimeout() ? MatchQuitStatus.IDLE_TIMEOUT :
MatchQuitStatus.QUIT;
builder.addPlayersBuilder()
.setName(matchPlayer.getName())
.setQuit(status)
.setWins(matchPlayer.getWins());
}
return builder.build();
}
}

View file

@ -38,6 +38,7 @@ import mage.game.draft.Draft;
import mage.game.events.Listener;
import mage.game.events.PlayerQueryEvent;
import mage.game.events.TableEvent;
import mage.game.result.ResultProtos.TourneyProto;
import mage.players.Player;
/**
@ -98,4 +99,6 @@ public interface Tournament {
void clearDraft();
Draft getDraft();
TourneyProto toProto();
}

View file

@ -51,6 +51,11 @@ import mage.game.events.TableEvent.EventType;
import mage.game.events.TableEventSource;
import mage.game.match.Match;
import mage.game.match.MatchPlayer;
import mage.game.result.ResultProtos.MatchPlayerProto;
import mage.game.result.ResultProtos.MatchProto;
import mage.game.result.ResultProtos.MatchQuitStatus;
import mage.game.result.ResultProtos.TourneyProto;
import mage.game.result.ResultProtos.TourneyRoundProto;
import mage.players.Player;
import org.apache.log4j.Logger;
@ -555,4 +560,51 @@ public abstract class TournamentImpl implements Tournament {
return draft;
}
@Override
public TourneyProto toProto() {
TourneyProto.Builder tourneyBuilder = TourneyProto.newBuilder()
.setBoosterInfo(this.getBoosterInfo());
for (TournamentPlayer player : players.values()) {
TournamentPlayer replacedPlayer = player.getReplacedTournamentPlayer();
if (replacedPlayer != null) {
player = replacedPlayer;
}
tourneyBuilder.addPlayersBuilder().mergeFrom(player.toProto());
}
for (Round round : rounds) {
TourneyRoundProto.Builder roundBuilder = tourneyBuilder.addRoundsBuilder()
.setRound(round.getRoundNumber());
for (TournamentPairing pair : round.getPairs()) {
Match match = pair.getMatch();
if (match != null && match.hasEnded()) {
MatchProto.Builder matchBuilder = roundBuilder.addMatchesBuilder()
.setName(match.getName())
.setGameType(match.getOptions().getGameType())
.setDeckType(match.getOptions().getDeckType())
.setGames(match.getNumGames())
.setDraws(match.getDraws())
.addPlayers(matchToProto(match, pair.getPlayer1()))
.addPlayers(matchToProto(match, pair.getPlayer2()));
}
}
for (TournamentPlayer tp : round.getPlayerByes()) {
roundBuilder.addByes(tp.getPlayer().getName());
}
}
return tourneyBuilder.build();
}
private MatchPlayerProto matchToProto(Match match, TournamentPlayer player) {
MatchPlayer matchPlayer = match.getPlayer(player.getPlayer().getId());
MatchQuitStatus quit = !matchPlayer.hasQuit() ? MatchQuitStatus.NO_MATCH_QUIT :
matchPlayer.getPlayer().hasIdleTimeout() ? MatchQuitStatus.IDLE_TIMEOUT :
matchPlayer.getPlayer().hasTimerTimeout() ? MatchQuitStatus.TIMER_TIMEOUT :
MatchQuitStatus.QUIT;
return MatchPlayerProto.newBuilder()
.setName(player.getPlayer().getName())
.setWins(matchPlayer.getWins())
.setQuit(quit)
.build();
}
}

View file

@ -31,6 +31,8 @@ package mage.game.tournament;
import java.util.Set;
import mage.cards.decks.Deck;
import mage.constants.TournamentPlayerState;
import mage.game.result.ResultProtos.TourneyPlayerProto;
import mage.game.result.ResultProtos.TourneyQuitStatus;
import mage.players.Player;
import mage.util.TournamentUtil;
@ -52,6 +54,8 @@ public class TournamentPlayer {
protected boolean quit = false;
protected boolean doneConstructing;
protected boolean joined = false;
protected TourneyQuitStatus quitStatus = TourneyQuitStatus.NO_TOURNEY_QUIT;
protected TournamentPlayer replacedTournamentPlayer;
public TournamentPlayer(Player player, String playerType) {
this.player = player;
@ -60,7 +64,6 @@ public class TournamentPlayer {
this.stateInfo = "";
this.disconnectInfo = "";
this.results = "";
}
public Player getPlayer() {
@ -185,12 +188,13 @@ public class TournamentPlayer {
return quit;
}
public void setQuit(String info) {
public void setQuit(String info, TourneyQuitStatus status) {
setEliminated();
this.setState(TournamentPlayerState.CANCELED);
this.setStateInfo(info);
this.quit = true;
this.doneConstructing = true;
this.quitStatus = status;
}
/**
@ -216,5 +220,23 @@ public class TournamentPlayer {
&& !this.getState().equals(TournamentPlayerState.ELIMINATED)
&& !this.getState().equals(TournamentPlayerState.FINISHED);
}
public TournamentPlayer getReplacedTournamentPlayer() {
return this.replacedTournamentPlayer;
}
public void setReplacedTournamentPlayer(TournamentPlayer player) {
this.replacedTournamentPlayer = player;
}
public TourneyPlayerProto toProto() {
return TourneyPlayerProto.newBuilder()
.setName(this.player.getName())
.setPlayerType(this.playerType)
.setStateInfo(this.stateInfo)
.setDisconnectInfo(this.disconnectInfo)
.setQuit(this.quitStatus)
.build();
}
}

View file

@ -0,0 +1,79 @@
package mage.game.result;
option java_outer_classname = "ResultProtos";
message TableProto {
optional MatchProto match = 1;
optional TourneyProto tourney = 2;
optional string game_type = 3;
optional string deck_type = 4;
optional string name = 5;
optional string controller_name = 6;
optional int64 start_time_ms = 7;
optional int64 end_time_ms = 8;
}
message MatchProto {
optional string name = 1;
optional string game_type = 2;
optional string deck_type = 3;
optional int32 games = 4;
optional int32 draws = 5;
repeated MatchPlayerProto players = 6;
}
message MatchPlayerProto {
optional string name = 1;
optional int32 wins = 2;
optional MatchQuitStatus quit = 3;
optional bool bye = 4;
}
enum MatchQuitStatus {
NO_MATCH_QUIT = 0;
IDLE_TIMEOUT = 1; // I
TIMER_TIMEOUT = 2; // T
QUIT = 3; // Q
}
message TourneyProto {
optional string booster_info = 1;
repeated TourneyPlayerProto players = 2;
repeated TourneyRoundProto rounds = 3;
}
message TourneyPlayerProto {
optional string name = 1;
optional string player_type = 2;
optional string replaced_player_name = 3;
optional string state_info = 4;
optional string disconnect_info = 5;
optional TourneyQuitStatus quit = 6;
}
enum TourneyQuitStatus {
NO_TOURNEY_QUIT = 0;
DURING_ROUND = 1;
DURING_DRAFTING = 2;
DURING_CONSTRUCTION = 3;
}
message TourneyRoundProto {
optional int32 round = 1;
repeated MatchProto matches = 2;
repeated string byes = 3;
}
message UserStatsProto {
optional string name = 1;
optional int32 tourneys = 2;
optional int32 tourneys_quit_during_round = 3;
optional int32 tourneys_quit_during_drafting = 4;
optional int32 tourneys_quit_during_construction = 5;
optional int32 matches = 6;
optional int32 matches_idle_timeout = 7;
optional int32 matches_timer_timeout = 8;
optional int32 matches_quit = 9;
}