package data;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import common.Log;
import controller.action.ActionBoard;
/**
* @author Michel Bartsch
*
* This class extends the GameControlData that is send to the robots. It
* contains all the additional informations the GameControler needs to
* represent a state of the game, for example time in millis.
*
* There are no synchronized get and set methods because in this architecture
* only actions in their perform method are allowed to write into this and they
* are all in the same thread. Look in the EventHandler for more information.
*/
public class AdvancedData extends GameControlData implements Cloneable
{
private static final long serialVersionUID = 2720243434306304319L;
/** This message is set when the data is put into the timeline */
public String message = "";
/** How much time summed up before the current state? (ms)*/
public long timeBeforeCurrentGameState;
/** When was switched to the current state? (ms) */
public long whenCurrentGameStateBegan;
/** When was the last drop-in? (ms, 0 = never) */
public long whenDropIn;
/** When was each player penalized last (ms, 0 = never)? */
public long[][] whenPenalized = Rules.league.isCoachAvailable ? new long[2][Rules.league.teamSize+1] : new long[2][Rules.league.teamSize];
/** How often was each robot penalized with each league-specific penalty? */
public int[][][] penaltyCount = Rules.league.isCoachAvailable ? new int[2][Rules.league.teamSize + 1][Rules.league.penaltyTime.length]
: new int[2][Rules.league.teamSize][Rules.league.penaltyTime.length];
/** Which players were already ejected? */
public boolean [][] ejected = Rules.league.isCoachAvailable ? new boolean[2][Rules.league.teamSize+1] : new boolean[2][Rules.league.teamSize];
/** Pushing counters for each team, 0:left side, 1:right side. */
public int[] pushes = {0, 0};
/** If true, the referee set a timeout */
public boolean refereeTimeout = false;
/** If true, this team is currently taking a timeOut, 0:left side, 1:right side. */
public boolean[] timeOutActive = {false, false};
/** TimeOut counters for each team, 0:left side, 1:right side. */
public boolean[] timeOutTaken = {false, false};
/** If true, left side has the kickoff. */
public boolean leftSideKickoff = true;
/** If true, the colors change automatically. */
public boolean colorChangeAuto;
/** If true, the testmode has been activated. */
public boolean testmode = false;
/** If true, the clock has manually been paused in the testmode. */
public boolean manPause = false;
/** If true, the clock has manually been started in the testmode. */
public boolean manPlay = false;
/** When was the last manual intervention to the clock? */
public long manWhenClockChanged;
/** Time offset resulting from manually stopping the clock. */
public long manTimeOffset;
/** Time offset resulting from starting the clock when it should be stopped. */
public long manRemainingGameTimeOffset;
/** Used to backup the secondary game state during a timeout. */
public byte previousSecGameState = STATE2_NORMAL;
/** Keeps the penalties for the players if there are substituted */
public ArrayList<ArrayList<PenaltyQueueData>> penaltyQueueForSubPlayers = new ArrayList<ArrayList<PenaltyQueueData>>();
/** Keep the timestamp when a coach message was received*/
public long timestampCoachPackage[] = {0, 0};
/** Keep the coach messages*/
public ArrayList<SPLCoachMessage> splCoachMessageQueue = new ArrayList<SPLCoachMessage>();
/**
* Creates a new AdvancedData.
*/
public AdvancedData()
{
if (Rules.league.startWithPenalty) {
secGameState = GameControlData.STATE2_PENALTYSHOOT;
}
for (int i=0; i<2; i++) {
for (int j=0; j < team[i].player.length; j++) {
if (j >= Rules.league.robotsPlaying) {
team[i].player[j].penalty = PlayerInfo.PENALTY_SUBSTITUTE;
}
}
penaltyQueueForSubPlayers.add(new ArrayList<PenaltyQueueData>());
}
}
/**
* Generically clone this object. Everything referenced must be Serializable.
* @return A deep copy of this object.
*/
public Object clone()
{
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
new ObjectOutputStream(out).writeObject(this);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
return new ObjectInputStream(in).readObject();
} catch (ClassNotFoundException e) {
System.out.println(e.getClass().getName() + ": " + e.getMessage());
} catch (IOException e) {
System.out.println(e.getClass().getName() + ": " + e.getMessage());
}
return null; // Should never be reached
}
/**
* Returns the side on which a team plays. The team should be playing
* via this GameController.
*
* @param teamNumber The unique teamNumber.
*
* @return The side of the team, 0:left side, 1:right side.
*/
public int getSide(short teamNumber)
{
return teamNumber == team[0].teamNumber ? 0 : 1;
}
/**
* Returns the current time. Can be stopped in test mode.
* @return The current time in ms. May become incompatible to
* the time delivered by System.currentTimeMillis().
*/
public long getTime()
{
return manPause ? manWhenClockChanged : System.currentTimeMillis() + manTimeOffset;
}
/**
* Returns the number of seconds since a certain timestamp.
* @param millis The timestamp in ms.
* @return The number of seconds since the timestamp.
*/
public int getSecondsSince(long millis) {
return millis == 0 ? 100000 : (int) (getTime() - millis) / 1000;
}
/**
* The number of seconds until a certion duration is over. The time
* already passed is specified as a timestamp when it began.
* @param millis The timestamp in ms.
* @param duration The full duration in s.
* @param The number of seconds that still remain from the duration.
* Can be negative.
*/
public int getRemainingSeconds(long millis, int durationInSeconds) {
return durationInSeconds - getSecondsSince(millis);
}
/**
* Update all durations in the GameControlData packet.
*/
public void updateTimes()
{
secsRemaining = (short) getRemainingGameTime(false);
dropInTime = whenDropIn == 0 ? -1 : (short) getSecondsSince(whenDropIn);
Integer subT = getSecondaryTime(0);
if (subT == null) {
secondaryTime = 0;
} else {
secondaryTime = (short)(int)subT;
}
for (int side = 0; side < team.length; ++side) {
for (int number = 0; number < team[side].player.length; ++number) {
PlayerInfo player = team[side].player[number];
player.secsTillUnpenalised = player.penalty == PlayerInfo.PENALTY_NONE
? 0 : (byte) getRemainingPenaltyTime(side, number);
}
}
}
/**
* Add the time passed in the current game state to the time that already passed before.
* Is usually called during changes of the game state.
*/
public void addTimeInCurrentState()
{
timeBeforeCurrentGameState += getTime() - whenCurrentGameStateBegan;
}
/**
* Calculates the remaining game time in the current phase of the game.
* This is what the primary clock will show.
* @param real If true, the real time will be returned. If false, the first number of seconds in the playing state
* in play-off games will not be updated.
* @return The remaining number of seconds.
*/
public int getRemainingGameTime(boolean real)
{
int regularNumberOfPenaltyShots = (gameType == GAME_PLAYOFF) ? Rules.league.numberOfPenaltyShotsLong : Rules.league.numberOfPenaltyShotsShort;
int duration = secGameState == STATE2_TIMEOUT ? secsRemaining :
secGameState == STATE2_NORMAL ? Rules.league.halfTime
: secGameState == STATE2_OVERTIME ? Rules.league.overtimeTime
: Math.max(team[0].penaltyShot, team[1].penaltyShot) > regularNumberOfPenaltyShots
? Rules.league.penaltyShotTimeSuddenDeath
: Rules.league.penaltyShotTime;
int timePlayed = gameState == STATE_INITIAL// during timeouts
|| (gameState == STATE_READY || gameState == STATE_SET)
&& ((gameType == GAME_PLAYOFF) && Rules.league.playOffTimeStop || timeBeforeCurrentGameState == 0)
|| gameState == STATE_FINISHED
? (int) ((timeBeforeCurrentGameState + manRemainingGameTimeOffset + (manPlay ? System.currentTimeMillis() - manWhenClockChanged : 0)) / 1000)
: real || gameType != GAME_PLAYOFF || secGameState != STATE2_NORMAL || gameState != STATE_PLAYING
|| getSecondsSince(whenCurrentGameStateBegan) >= Rules.league.playOffDelayedSwitchToPlaying
? getSecondsSince(whenCurrentGameStateBegan - timeBeforeCurrentGameState - manRemainingGameTimeOffset)
: (int) ((timeBeforeCurrentGameState - manRemainingGameTimeOffset) / 1000);
return duration - timePlayed;
}
/**
* The method returns the remaining pause time.
* @return The remaining number of seconds of the game pause or null if there currently is no pause.
*/
public Integer getRemainingPauseTime()
{
if (Rules.league.dropInPlayerMode) {
return null;
} else if (secGameState == GameControlData.STATE2_NORMAL
&& (gameState == STATE_INITIAL && firstHalf != C_TRUE && !timeOutActive[0] && !timeOutActive[1]
|| gameState == STATE_FINISHED && firstHalf == C_TRUE)) {
return getRemainingSeconds(whenCurrentGameStateBegan, Rules.league.pauseTime);
} else if (Rules.league.pausePenaltyShootOutTime != 0 && (gameType == GAME_PLAYOFF) && team[0].score == team[1].score
&& (gameState == STATE_INITIAL && secGameState == STATE2_PENALTYSHOOT && !timeOutActive[0] && !timeOutActive[1]
|| gameState == STATE_FINISHED && firstHalf != C_TRUE)) {
return getRemainingSeconds(whenCurrentGameStateBegan, Rules.league.pausePenaltyShootOutTime);
} else {
return null;
}
}
/**
* Resets the penalize time of all players to 0.
* This does not unpenalize them.
*/
public void resetPenaltyTimes()
{
for (long[] players : whenPenalized) {
for (int i = 0; i < players.length; ++i) {
players[i] = 0;
}
}
}
/**
* Resets all penalties.
*/
public void resetPenalties()
{
for (int i = 0; i < team.length; ++i) {
pushes[i] = 0;
for (int j = 0; j < Rules.league.teamSize; j++) {
if (!ActionBoard.robot[i][j].isCoach(this) && team[i].player[j].penalty != PlayerInfo.PENALTY_SUBSTITUTE) {
team[i].player[j].penalty = PlayerInfo.PENALTY_NONE;
if (Rules.league.resetEjectedRobotsOnHalftime) {
ejected[i][j] = false;
}
}
if (Rules.league.resetPenaltyCountOnHalftime) {
for (int k = 0; k < Rules.league.penaltyTime.length; k++) {
penaltyCount[i][j][k] = 0;
}
}
}
}
resetPenaltyTimes();
for (int i = 0; i < 2; i++) {
penaltyQueueForSubPlayers.get(i).clear();
}
}
/**
* Calculates the remaining time a certain robot has to stay penalized.
* @param side 0 or 1 depending on whether the robot's team is shown left or right.
* @param number The robot's number starting with 0.
* @return The number of seconds the robot has to stay penalized.
*/
public int getRemainingPenaltyTime(int side, int number)
{
int penalty = team[side].player[number].penalty;
int penaltyTime = -1;
if (penalty != PlayerInfo.PENALTY_MANUAL && penalty != PlayerInfo.PENALTY_SUBSTITUTE) {
// System.out.println("penalty: " + penalty);
// System.out.println("penalty count: " + penaltyCount[side][number][penalty]);
penaltyTime = Rules.league.penaltyTime[penalty][((penaltyCount[side][number][penalty] > Rules.league.penaltyTime[penalty].length)
? Rules.league.penaltyTime[penalty].length
: penaltyCount[side][number][penalty]) - 1];
}
// System.out.println("penalty time: " + penaltyTime);
assert penalty == PlayerInfo.PENALTY_MANUAL || penalty == PlayerInfo.PENALTY_SUBSTITUTE || penaltyTime != -1;
return penalty == PlayerInfo.PENALTY_MANUAL || penalty == PlayerInfo.PENALTY_SUBSTITUTE ? 0
: gameState == STATE_READY && Rules.league.returnRobotsInGameStoppages && whenPenalized[side][number] >= whenCurrentGameStateBegan
? Rules.league.readyTime - getSecondsSince(whenCurrentGameStateBegan)
: Math.max(0, getRemainingSeconds(whenPenalized[side][number], penaltyTime));
}
/**
* Calculates the Number of robots in play (not substitute) on one side
* @param side 0 or 1 depending on whether the team is shown left or right.
* @return The number of robots without substitute penalty on the side
*/
public int getNumberOfRobotsInPlay(int side)
{
int count = 0;
for (int i=0; i<team[side].player.length; i++) {
if (team[side].player[i].penalty != PlayerInfo.PENALTY_SUBSTITUTE) {
count++;
}
}
return count;
}
/**
* Determines the secondary time. Although this is a GUI feature, the secondary time
* will also be encoded in the network packet.
* @param timeKickOffBlockedOvertime In case the kickOffBlocked time is delivered, this
* parameter specified how long negative values will
* be returned before the time is switched off.
* @return The secondary time in seconds or null if there currently is none.
*/
public Integer getSecondaryTime(int timeKickOffBlockedOvertime)
{
if(timeKickOffBlockedOvertime == 0 // preparing data packet
&& secGameState == STATE2_NORMAL && gameState == STATE_PLAYING
&& getSecondsSince(whenCurrentGameStateBegan) < Rules.league.playOffDelayedSwitchToPlaying) {
return null;
}
int timeKickOffBlocked = getRemainingSeconds(whenCurrentGameStateBegan, Rules.league.kickoffTime);
if (kickOffTeam == DROPBALL) {
timeKickOffBlocked = 0;
}
if (gameState == STATE_INITIAL && (timeOutActive[0] || timeOutActive[1])) {
return getRemainingSeconds(whenCurrentGameStateBegan, Rules.league.timeOutTime);
}
else if (gameState == STATE_INITIAL && (refereeTimeout)) {
return getRemainingSeconds(whenCurrentGameStateBegan, Rules.league.refereeTimeout);
}
else if (gameState == STATE_READY) {
return getRemainingSeconds(whenCurrentGameStateBegan, Rules.league.readyTime);
} else if (gameState == STATE_PLAYING && secGameState != STATE2_PENALTYSHOOT
&& timeKickOffBlocked >= -timeKickOffBlockedOvertime) {
if (timeKickOffBlocked > 0) {
return timeKickOffBlocked;
} else {
return null;
}
} else {
return getRemainingPauseTime();
}
}
/**
* Dispatch the coach messages. Since coach messages are texts, the messages are zeroed
* after the first zero character, to avoid the transport of information the
* GameStateVisualizer would not show.
*/
public void updateCoachMessages()
{
int i = 0;
while (i < splCoachMessageQueue.size()) {
if (splCoachMessageQueue.get(i).getRemainingTimeToSend() == 0) {
for (int j = 0; j < 2; j++) {
if (team[j].teamNumber == splCoachMessageQueue.get(i).team) {
byte[] message = splCoachMessageQueue.get(i).message;
// All chars after the first zero are zeroed, too
int k = 0;
while (k < message.length && message[k] != 0) {
k++;
}
while (k < message.length) {
message[k++] = 0;
}
team[j].coachSequence = splCoachMessageQueue.get(i).sequence;
team[j].coachMessage = message;
Log.toFile("Coach Message Team "+ Rules.league.teamColorName[team[j].teamColor]+" "+ new String(message));
splCoachMessageQueue.remove(i);
break;
}
}
} else {
i++;
}
}
}
public void updatePenalties() {
if (secGameState == STATE2_NORMAL && gameState == STATE_PLAYING
&& getSecondsSince(whenCurrentGameStateBegan) >= Rules.league.playOffDelayedSwitchToPlaying) {
for (TeamInfo t : team) {
for (PlayerInfo p : t.player) {
if (p.penalty == PlayerInfo.PENALTY_SPL_ILLEGAL_MOTION_IN_SET) {
p.penalty = PlayerInfo.PENALTY_NONE;
}
}
}
}
}
public class PenaltyQueueData implements Serializable {
private static final long serialVersionUID = 7536004813202642582L;
public long whenPenalized;
public byte penalty;
public PenaltyQueueData(long whenPenalized, byte penalty) {
this.whenPenalized = whenPenalized;
this.penalty = penalty;
}
}
public void addToPenaltyQueue(int side, long whenPenalized, byte penalty) {
penaltyQueueForSubPlayers.get(side).add(new PenaltyQueueData(whenPenalized, penalty));
}
}