/*
Copyright (C) 2009 Martin Günther <mintar@gmx.de>
2009 Stephan Schiffel <stephan.schiffel@gmx.de>
This file is part of GGP Server.
GGP Server is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
GGP Server is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GGP Server. If not, see <http://www.gnu.org/licenses/>.
*/
package tud.ggpserver.datamodel.matches;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.collections.map.ReferenceMap;
import tud.gamecontroller.auxiliary.Pair;
import tud.gamecontroller.game.GameInterface;
import tud.gamecontroller.game.RoleInterface;
import tud.gamecontroller.game.impl.Match;
import tud.gamecontroller.game.impl.State;
import tud.gamecontroller.logging.GameControllerErrorMessage;
import tud.gamecontroller.players.PlayerInfo;
import tud.gamecontroller.term.TermInterface;
import tud.ggpserver.collectionviews.ListView;
import tud.ggpserver.collectionviews.MapView;
import tud.ggpserver.collectionviews.Mapping;
import tud.ggpserver.collectionviews.Mappings;
import tud.ggpserver.datamodel.AbstractDBConnector;
import tud.ggpserver.datamodel.User;
import tud.ggpserver.formhandlers.ViewState;
import tud.ggpserver.util.StateXMLExporter;
public abstract class ServerMatch<TermType extends TermInterface, ReasonerStateInfoType>
extends Match<TermType, ReasonerStateInfoType>{
private final class GoalValueToWeightedMapping implements
Mapping<Integer, Double> {
@Override
public Double map(Integer o) {
return o.intValue()*weight;
}
@Override
public Integer reverseMap(Double o) {
return (int)Math.round(o.doubleValue()/weight);
}
}
public static final String STATUS_NEW = "new";
public static final String STATUS_RUNNING = "running";
public static final String STATUS_FINISHED = "finished";
public static final String STATUS_ABORTED = "aborted";
public static final String STATUS_SCHEDULED = "scheduled";
private final Date startTime;
protected final Map<? extends RoleInterface<?>, ? extends PlayerInfo> rolesToPlayerInfos;
protected List<PlayerInfo> orderedPlayerInfos = null;
private List<Integer> orderedGoalValues;
private final boolean scrambled;
private final String tournamentID;
private final double weight;
private User owner;
/**
* State 0 = initial state, State 1 = state after first joint move, ..., State n = final state
*/
protected List<Pair<Date, String>> stringStates = null;
protected Map<Pair<Integer, String>, String> xmlStates = null;
/**
* - errors from the start message and first play message go to index 0
* - errors from the second play message go to index 1
* - ...
* - errors from the last (n^th) play message go to index n-1
* - errors from the stop message go to index n
* The match has n+1 states, n play messages and the stop message, therefore there is an entry in errorMessages for each state.
*/
protected List<List<GameControllerErrorMessage>> errorMessages;
private final AbstractDBConnector<TermType, ReasonerStateInfoType> db;
/**
* Don't use this constructor directly, use DBConnectorFactory.getDBConnector().getMatch() instead.
*/
public ServerMatch(
String matchID,
GameInterface<TermType, State<TermType, ReasonerStateInfoType>> game,
int startclock,
int playclock,
Map<? extends RoleInterface<TermType>, ? extends PlayerInfo> rolesToPlayerInfos,
Date startTime,
boolean scrambled,
String tournamentID,
double weight,
User owner,
AbstractDBConnector<TermType, ReasonerStateInfoType> db) {
super(matchID, game, startclock, playclock);
this.rolesToPlayerInfos = rolesToPlayerInfos;
this.startTime = new Date(startTime.getTime());
this.scrambled = scrambled;
this.tournamentID = tournamentID;
this.weight = weight;
this.owner = owner;
this.db = db;
}
/////////////////////// start time ///////////////////////
public Date getStartTime() {
return new Date(startTime.getTime());
}
/////////////////////// status ///////////////////////
public abstract String getStatus();
/////////////////////// player infos ///////////////////////
public List<? extends PlayerInfo> getOrderedPlayerInfos() {
if (orderedPlayerInfos == null) {
orderedPlayerInfos = new LinkedList<PlayerInfo>();
for (RoleInterface<TermType> role : getGame().getOrderedRoles()) {
PlayerInfo playerInfo = rolesToPlayerInfos.get(role);
assert (playerInfo != null);
orderedPlayerInfos.add(playerInfo);
}
}
return new ArrayList<PlayerInfo>(orderedPlayerInfos); // defensive copy, needed e.g. in EditableMatch
}
public PlayerInfo getPlayerInfo(RoleInterface<?> role) {
return rolesToPlayerInfos.get(role);
}
public Collection<? extends PlayerInfo> getPlayerInfos() {
return new ArrayList<PlayerInfo>(rolesToPlayerInfos.values());
}
public Map<? extends RoleInterface<?>, ? extends PlayerInfo> getRolesToPlayerInfos() {
return Collections.unmodifiableMap(rolesToPlayerInfos);
}
public List<String> getOrderedPlayerNames() {
ArrayList<String> orderedPlayerNames = new ArrayList<String>();
for (PlayerInfo info: getOrderedPlayerInfos()) {
orderedPlayerNames.add(info.getName());
}
return orderedPlayerNames;
}
/////////////////////// goal values ///////////////////////
/**
* This method can be overridden by subclasses to provide goal values.
*
* @return May return <code>null</code> if there are no goal values for
* this match yet. <br>
*/
public Map<RoleInterface<TermType>, Integer> getGoalValues() {
return null;
}
/**
* @return May return <code>null</code> if there are no goal values for
* this match yet. <br>
*/
public List<Integer> getOrderedGoalValues() {
Map<RoleInterface<TermType>, Integer> goalValues = getGoalValues();
if (goalValues == null) {
return null;
}
if (orderedGoalValues == null) {
orderedGoalValues = new LinkedList<Integer>();
for (RoleInterface<TermType> role : getGame().getOrderedRoles()) {
orderedGoalValues.add(goalValues.get(role));
}
}
return new ArrayList<Integer>(orderedGoalValues);
}
public List<? extends RoleInterface<TermType>> getOrderedPlayerRoles () {
return this.getGame().getOrderedRoles();
}
public Map<? extends RoleInterface<TermType>, Double> getWeightedGoalValues() {
return new MapView<RoleInterface<TermType>, Double, RoleInterface<TermType>, Integer>(
getGoalValues(),
Mappings.<RoleInterface<TermType>>identity(),
new GoalValueToWeightedMapping()
);
}
public List<Double> getWeightedOrderedGoalValues() {
return new ListView<Double, Integer>(getOrderedGoalValues(), new GoalValueToWeightedMapping());
}
public double getWeight() {
return weight;
}
/////////////////////// joint moves strings ///////////////////////
public abstract List<List<String>> getJointMovesStrings();
/////////////////////// String states (Pair<String timestamp, String state>)///////////////////////
public abstract List<Pair<Date,String>> getStringStates();
/////////////////////// error messages ///////////////////////
public abstract List<List<GameControllerErrorMessage>> getErrorMessages();
public boolean getHasErrors() {
for (List<GameControllerErrorMessage> errorMessagesSingleState : getErrorMessages()) {
if (!errorMessagesSingleState.isEmpty()) {
return true;
}
}
return false;
}
public boolean getHasErrorsSinglePlayer(PlayerInfo player) {
for (List<GameControllerErrorMessage> errorMessagesSingleState : getErrorMessagesForPlayer(player)) {
if (!errorMessagesSingleState.isEmpty()) {
return true;
}
}
return false;
}
public Map<String, Boolean> getHasErrorsAllPlayers() {
Map<String, Boolean> result = new HashMap<String, Boolean>();
for (PlayerInfo player : getPlayerInfos()) {
result.put(player.getName(), getHasErrorsSinglePlayer(player));
}
return result;
}
public List<List<GameControllerErrorMessage>> getErrorMessagesForPlayer(String playerName) {
for (PlayerInfo playerInfo : getPlayerInfos()) {
if (playerInfo.getName().equals(playerName)) {
return getErrorMessagesForPlayer(playerInfo);
}
}
throw new IllegalArgumentException("No player info for name " + playerName + " in match " + this);
}
public List<List<GameControllerErrorMessage>> getErrorMessagesForPlayer(PlayerInfo player) {
if (!getPlayerInfos().contains(player)) {
throw new IllegalArgumentException("Player info " + player + " not found in match " + this);
}
List<List<GameControllerErrorMessage>> result = new LinkedList<List<GameControllerErrorMessage>>();
for (List<GameControllerErrorMessage> errorsSingleState : getErrorMessages()) {
List<GameControllerErrorMessage> playerErrorsSingleState = new LinkedList<GameControllerErrorMessage>();
for (GameControllerErrorMessage message : errorsSingleState) {
if (player.getName().equals(message.getPlayerName())) {
playerErrorsSingleState.add(message);
}
}
result.add(playerErrorsSingleState);
}
return result;
}
/////////////////////// scrambling ///////////////////////
public boolean isScrambled() {
return scrambled;
}
/////////////////////// everything else ///////////////////////
public String getTournamentID() {
return tournamentID;
}
public User getOwner() {
return owner;
}
protected AbstractDBConnector<TermType, ReasonerStateInfoType> getDB() {
return db;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[ServerMatch:");
buffer.append(" matchID: ");
buffer.append(getMatchID());
buffer.append(" game: ");
buffer.append(getGame());
buffer.append(" startclock: ");
buffer.append(getStartclock());
buffer.append(" playclock: ");
buffer.append(getPlayclock());
buffer.append(" startTime: ");
buffer.append(startTime);
buffer.append(" rolesToPlayerInfos: ");
buffer.append(rolesToPlayerInfos);
buffer.append(" number of states: ");
buffer.append(getStringStates().size());
buffer.append(" jointMovesStrings: ");
buffer.append(getJointMovesStrings());
buffer.append(" errorMessages: ");
buffer.append(getErrorMessages());
buffer.append("]");
return buffer.toString();
}
@SuppressWarnings("unchecked")
protected Map<Pair<Integer, String>, String> getXmlStates() {
if (xmlStates == null)
xmlStates = new ReferenceMap(ReferenceMap.SOFT, ReferenceMap.SOFT);
return xmlStates;
}
public String getXmlState(int stepNumber, String roleName) {
Map<Pair<Integer, String>, String> xmlStates = getXmlStates();
String xmlState = xmlStates.get(stepNumber);
List<Pair<Date,String>> stringStates = getStringStates();
int numberOfStates = stringStates.size();
if (xmlState == null) {
Logger.getLogger(ViewState.class.getName()).info("StateXMLExporter.getStepXML(match, stringStates, "+stepNumber+", roleName)");
xmlState = StateXMLExporter.getStepXML(this, stringStates, stepNumber, roleName);
if (stepNumber<=numberOfStates && stepNumber>0) {
xmlStates.put(new Pair<Integer, String>(stepNumber, roleName), xmlState);
}
}
return xmlState;
}
}