package server;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import server.event.ServerCompletedMatchEvent;
import server.event.ServerNewGameStateEvent;
import server.event.ServerNewMatchEvent;
import server.event.ServerNewMovesEvent;
import server.event.ServerTimeEvent;
import server.threads.PlayRequestThread;
import server.threads.StartRequestThread;
import server.threads.StopRequestThread;
import util.gdl.grammar.GdlSentence;
import util.match.Match;
import util.match.MatchPublisher;
import util.observer.Event;
import util.observer.Observer;
import util.observer.Subject;
import util.statemachine.MachineState;
import util.statemachine.Move;
import util.statemachine.Role;
import util.statemachine.StateMachine;
import util.statemachine.exceptions.GoalDefinitionException;
import util.statemachine.exceptions.MoveDefinitionException;
import util.statemachine.implementation.prover.ProverMachineState;
import util.statemachine.implementation.prover.ProverStateMachine;
public final class GameServer extends Thread implements Subject
{
private final Match match;
private final StateMachine stateMachine;
private MachineState currentState;
private final List<String> hosts;
private final List<Integer> ports;
private final List<String> playerNames;
private final Boolean[] playerGetsUnlimitedTime;
private final List<Observer> observers;
private List<Move> previousMoves;
private List<String> history;
private String spectatorServerURL;
private boolean forceUsingEntireClock;
public GameServer(Match match, List<String> hosts, List<Integer> ports, List<String> playerNames) {
this.match = match;
this.hosts = hosts;
this.ports = ports;
this.playerNames = playerNames;
playerGetsUnlimitedTime = new Boolean[hosts.size()];
Arrays.fill(playerGetsUnlimitedTime, Boolean.FALSE);
stateMachine = new ProverStateMachine();
stateMachine.initialize(match.getGame().getRules());
currentState = stateMachine.getInitialState();
previousMoves = null;
match.appendState(currentState.getContents());
history = new ArrayList<String>();
observers = new ArrayList<Observer>();
spectatorServerURL = null;
forceUsingEntireClock = false;
}
public String startPublishingToSpectatorServer(String theURL) {
spectatorServerURL = theURL;
return publishWhenNecessary();
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public List<Integer> getGoals() throws GoalDefinitionException {
List<Integer> goals = new ArrayList<Integer>();
for (Role role : stateMachine.getRoles()) {
goals.add(stateMachine.getGoal(currentState, role));
}
return goals;
}
public StateMachine getStateMachine() {
return stateMachine;
}
public void notifyObservers(Event event) {
for (Observer observer : observers) {
observer.observe(event);
}
}
@Override
public void run() {
try {
notifyObservers(new ServerNewMatchEvent(stateMachine.getRoles()));
notifyObservers(new ServerTimeEvent(match.getStartClock() * 1000));
sendStartRequests();
while (!stateMachine.isTerminal(currentState)) {
publishWhenNecessary();
notifyObservers(new ServerNewGameStateEvent((ProverMachineState)currentState));
notifyObservers(new ServerTimeEvent(match.getPlayClock() * 1000));
previousMoves = sendPlayRequests();
notifyObservers(new ServerNewMovesEvent(previousMoves));
history.add(currentState.toXML());
currentState = stateMachine.getNextState(currentState, previousMoves);
List<GdlSentence> movesAsGDL = new ArrayList<GdlSentence>();
for(Move m : previousMoves)
movesAsGDL.add(m.getContents());
match.appendMoves(movesAsGDL);
match.appendState(currentState.getContents());
}
match.markCompleted(stateMachine.getGoals(currentState));
publishWhenNecessary();
notifyObservers(new ServerNewGameStateEvent((ProverMachineState)currentState));
notifyObservers(new ServerCompletedMatchEvent(getGoals()));
sendStopRequests(previousMoves);
} catch (Exception e) {
e.printStackTrace();
}
}
private String publishWhenNecessary() {
if (spectatorServerURL != null) {
try {
return MatchPublisher.publishToSpectatorServer(spectatorServerURL, match);
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private synchronized List<Move> sendPlayRequests() throws InterruptedException, MoveDefinitionException {
List<PlayRequestThread> threads = new ArrayList<PlayRequestThread>(hosts.size());
for (int i = 0; i < hosts.size(); i++) {
List<Move> legalMoves = stateMachine.getLegalMoves(currentState, stateMachine.getRoles().get(i));
threads.add(new PlayRequestThread(this, match, previousMoves, legalMoves, stateMachine.getRoles().get(i), hosts.get(i), ports.get(i), playerNames.get(i), playerGetsUnlimitedTime[i]));
}
for (PlayRequestThread thread : threads) {
thread.start();
}
if (forceUsingEntireClock) {
Thread.sleep(match.getPlayClock() * 1000);
}
List<Move> moves = new ArrayList<Move>();
for (PlayRequestThread thread : threads) {
thread.join();
moves.add(thread.getMove());
}
return moves;
}
private synchronized void sendStartRequests() throws InterruptedException {
List<StartRequestThread> threads = new ArrayList<StartRequestThread>(hosts.size());
for (int i = 0; i < hosts.size(); i++) {
threads.add(new StartRequestThread(this, match, stateMachine.getRoles().get(i), hosts.get(i), ports.get(i), playerNames.get(i)));
}
for (StartRequestThread thread : threads) {
thread.start();
}
if (forceUsingEntireClock) {
Thread.sleep(match.getStartClock() * 1000);
}
for (StartRequestThread thread : threads) {
thread.join();
}
}
private synchronized void sendStopRequests(List<Move> previousMoves) throws InterruptedException {
List<StopRequestThread> threads = new ArrayList<StopRequestThread>(hosts.size());
for (int i = 0; i < hosts.size(); i++) {
threads.add(new StopRequestThread(this, match, previousMoves, stateMachine.getRoles().get(i), hosts.get(i), ports.get(i), playerNames.get(i)));
}
for (StopRequestThread thread : threads) {
thread.start();
}
for (StopRequestThread thread : threads) {
thread.join();
}
}
public List<String> getHistory() {
return history;
}
public String getGameXML() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < history.size(); i++) sb.append(history.get(i));
return sb.toString();
}
public void givePlayerUnlimitedTime(int i) {
playerGetsUnlimitedTime[i] = true;
}
// Why would you want to force the game server to wait for the entire clock?
// This can be used to rate-limit matches, so that you don't overload supporting
// services like the repository server, spectator server, players, etc.
public void setForceUsingEntireClock() {
forceUsingEntireClock = true;
}
public Match getMatch() {
return match;
}
}