package com.charlesmadere.android.classygames.models; import android.content.res.Resources; import com.charlesmadere.android.classygames.R; import com.charlesmadere.android.classygames.server.Server; import com.charlesmadere.android.classygames.utilities.Utilities; import org.json.JSONException; import org.json.JSONObject; /** * Class representing a single Game. This is most obviously represented when * seen in the app's Games List. */ public final class Game { /** * Boolean representing who's turn it is. It can either be the current * user's turn (TURN_YOURS => true), or the opponent's turn (TURN_THEIRS => * false). */ private boolean turn; public final static boolean TURN_THEIRS = false; public final static boolean TURN_YOURS = true; /** * Boolean representing what type of game this is. This is not to be * confused with the whichGame byte variable. Having this variable is * basically a hack, as Android only allows one object type in its * ListView layouts. If this is set to TYPE_SEPARATOR (true), then instead * of drawing a game to the Android ListView, we draw a separator. A * separator tells the user that all game's below this separator are a * certain person's turn. */ private boolean type; public final static boolean TYPE_GAME = false; public final static boolean TYPE_SEPARATOR = true; /** * Byte representing which game this is. It could be checkers, chess... */ private byte whichGame; private final static byte WHICH_GAME_NULL = 0; public final static byte WHICH_GAME_CHECKERS = 1; public final static byte WHICH_GAME_CHESS = 2; /** * The Unix Epoch as downloaded from the server. */ private long timestamp; /** * The person to show in the Games List as your opponent. */ private Person person; /** * The ID of this Game object as downloaded from the server. This is a * unique hash. */ private String id; /** * A human readable version of the Unix Epoch. */ private String timestampFormatted; /** * Creates a Game object. This constructor should only be used within the * GenericGameFragment class. * * @param person * The opposing player. If I am Charles Madere and my opponent is Geonathan * Sena, then this Person object will be for Geonathan Sena. */ public Game(final Person person) { this.person = person; type = TYPE_GAME; } /** * Creates a Game object. * * @param person * The opposing player. If I am Charles Madere and my opponent is Geonathan * Sena, then this Person object will be for Geonathan Sena. * * @param whichGame * The game type. Could be checkers, chess... Make sure to use the public * Game.WHICH_GAME_* byte variables for this. */ public Game(final Person person, final byte whichGame) { this.person = person; this.whichGame = whichGame; type = TYPE_GAME; } /** * Creates a Game object. * * @param person * The opposing player. If I am Charles Madere and my opponent is Geonathan * Sena, then this Person object will be for Geonathan Sena. * * @param whichGame * The type of game that this will be. Use one of this class's WHICH_GAME_* * variables for this input. * * @param id * This game's ID as received from the Classy Games server. This should be * a rather long String that resembles a hash. */ public Game(final Person person, final byte whichGame, final String id) { this.person = person; this.whichGame = whichGame; this.id = id; type = TYPE_GAME; } /** * Use this constructor for creating a separator in the games list. * * @param turn * Use either Game.TURN_YOURS or Game.TURN_THEIRS for this parameter. */ public Game(final boolean turn) { this.turn = turn; timestamp = (System.currentTimeMillis() / 1000) + 14400; type = TYPE_SEPARATOR; whichGame = WHICH_GAME_NULL; } /** * Creates a single Game object out of some JSON data as received from * the server. * * @param gameData * JSON data pertaining to a single Game object. * * @param whichTurn * Who's turn is this? This variable's value should be one of the * Game.TURN_* variables. * * @throws JSONException * If a glitch or something happened while trying to create this JSONObject * then a JSONException will be thrown. */ public Game(final JSONObject gameData, final boolean whichTurn) throws JSONException { id = gameData.getString(Server.POST_DATA_GAME_ID); timestamp = gameData.getLong(Server.POST_DATA_LAST_MOVE); turn = whichTurn; final long personId = gameData.getLong(Server.POST_DATA_ID); final String personName = gameData.getString(Server.POST_DATA_NAME); person = new Person(personId, personName); whichGame = (byte) gameData.optInt(Server.POST_DATA_GAME_TYPE); } /** * @return * This Game object's ID. This is a unique hash. */ public String getId() { return id; } /** * @return * The person that the current Android device's user is playing against. */ public Person getPerson() { return person; } /** * Returns the raw Unix Epoch. If your purpose is for something that a human is * going to want to read, then you should use the getTimestampFormatted() * method instead of this one. * * @return * Returns the raw Unix Epoch as a long. */ public long getTimestamp() { return timestamp; } /** * Reads the Unix Epoch as stored for this Game object and compares it to * the current Android system's Unix Epoch. Generates a human readable * version of the difference between those two times. * * @return * A human readable version of the Unix Epoch. */ public String getTimestampFormatted(final Resources resources) { if (!Utilities.validString(timestampFormatted)) // Check to see if we've already created a formatted timestamp String // for this game object. If we've already created a formatted timestamp // String, then we can just skip the whole algorithm below and return // the existing String. { // find out the between the time NOW versus the time of this game's // last move final long timeDifference = (System.currentTimeMillis() / 1000l) - timestamp; // calculate the number of WEEKS in the difference between the two // times long timeAgo = timeDifference / 604800l; if (timeAgo >= 1) { timestampFormatted = resources.getQuantityString(R.plurals.x_weeks_ago, (int) timeAgo, timeAgo); } else { // calculate the number of DAYS in the difference between the // two times timeAgo = timeDifference / 86400l; if (timeAgo >= 1) { if (timeAgo >= 1 && timeAgo <= 5) { timestampFormatted = resources.getQuantityString(R.plurals.x_days_ago, (int) timeAgo, timeAgo); } else { timestampFormatted = resources.getString(R.string.almost_a_week_ago); } } else { // calculate the number of HOURS in the difference between // the two times timeAgo = timeDifference / 3600l; if (timeAgo >= 1) { if (timeAgo >= 1 && timeAgo <= 12) { timestampFormatted = resources.getQuantityString(R.plurals.x_hours_ago, (int) timeAgo, timeAgo); } else if (timeAgo > 12 && timeAgo <= 18) { timestampFormatted = resources.getString(R.string.about_half_a_day_ago); } else { timestampFormatted = resources.getString(R.string.almost_a_day_ago); } } else { // calculate the number of MINUTES in the difference // between the two times timeAgo = timeDifference / 60l; if (timeAgo >= 1) { if (timeAgo >= 1 && timeAgo <= 45) { timestampFormatted = resources.getQuantityString(R.plurals.x_minutes_ago, (int) timeAgo, timeAgo); } else { timestampFormatted = resources.getString(R.string.almost_an_hour_ago); } } else { timestampFormatted = resources.getString(R.string.just_now); } } } } } return timestampFormatted; } /** * When using this method you're probably going to want to compare the * value returned against some of this class's public data members, mainly * the WHICH_GAME_* bytes. So that could be WHICH_GAME_CHECKERS, * WHICH_GAME_CHESS... * * @return * A byte that represents which game this Game object represents. Could be * Checkers, Chess... */ public byte getWhichGame() { return whichGame; } /** * Checks to see if this Game object represents a game of checkers. * * @return * Returns true if this Game object represents a game of checkers. */ public boolean isGameCheckers() { return whichGame == WHICH_GAME_CHECKERS; } /** * Checks to see if this Game object represents a game of chess. * * @return * Returns true if this Game object represents a game of chess. */ public boolean isGameChess() { return whichGame == WHICH_GAME_CHESS; } /** * Checks to see if this Game object's turn is the opponent player's turn. * * @return * Returns true if this Game object's turn is the opponent player's turn. */ public boolean isTurnTheirs() { return turn == TURN_THEIRS; } /** * Checks to see if this Game object's turn is the current player's turn. * * @return * Returns true if this Game object's turn is the current player's turn. */ public boolean isTurnYours() { return turn == TURN_YOURS; } /** * Checks to see if this Game object's type is a game type, not a separator * type. * * @return * Returns true if this Game object's type is a game type. */ public boolean isTypeGame() { return type == TYPE_GAME; } /** * Checks to see if this Game object's type is a separator type. * * @return * Returns true if this Game object's type is a separator type. */ public boolean isTypeSeparator() { return type == TYPE_SEPARATOR; } /** * Checks to see that this Game object is valid. Valid means four things: * <ol> * <li>This Game's ID is not null.</li> * <li>This Game's ID has a length of greater than or equal to 32.</li> * <li>This Game's Person object is valid.</li> * <li>This Game's whichGame byte is valid.</li> * </ol> * * @return * Returns true if all of the above conditions are true. Returns false if * any single one of the above conditions are false. */ public boolean isValid() { return isIdValid(id) && person.isValid() && isWhichGameValid(whichGame); } /** * Checks the given ID to be sure that it is a valid Game ID. Valid means * two things: * <ol> * <li>This ID is not null.</li> * <li>This ID has a length of greater than or equal to 32.</li> * </ol> * * @param id * The Game ID to check for validity. * * @return * Returns true if all of the above conditions are true for the given ID. * Returns false if any single one of the above conditions are false. */ public static boolean isIdValid(final String id) { return id != null && id.length() >= 32; } /** * Checks to the given whichGame to be sure that it is a valid whichGame. * Valid means one thing: * <ol> * <li>This whichGame is equal to any single one of the other public final * static WHICH_GAME_* bytes in this class.</li> * </ol> * * @param whichGame * The whichGame byte to check for validity. * * @return * Returns true if the the above condition is true for the given whichGame * byte. */ public static boolean isWhichGameValid(final byte whichGame) { switch (whichGame) { case WHICH_GAME_CHECKERS: case WHICH_GAME_CHESS: return true; default: return false; } } }