From 7e11b5a58a9f34049a5078139446ca080ff9b50b Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Mon, 2 May 2016 13:52:00 +0200 Subject: Implement client.ImageCollection --- src/ch/epfl/xblast/client/ImageCollection.java | 91 ++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/ch/epfl/xblast/client/ImageCollection.java (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/ImageCollection.java b/src/ch/epfl/xblast/client/ImageCollection.java new file mode 100644 index 0000000..231d84c --- /dev/null +++ b/src/ch/epfl/xblast/client/ImageCollection.java @@ -0,0 +1,91 @@ +package ch.epfl.xblast.client; + +import javax.imageio.ImageIO; +import java.util.HashMap; +import java.util.Map; +import java.awt.Image; +import java.io.File; +import java.util.NoSuchElementException; + +/** + * @author Timothée FLOURE (257420) + */ +public final class ImageCollection { + + /** + * Relative directory. + */ + private final String directory; + + /** + * Get the directory Object from the relative directory Name using Java Resource. + * + * @param directoryName the relative name of the directory + * @throws NoSuchElementException + * @return the File Object related to the directory + */ + private static File getDirectory(String directoryName) { + try { // Ugly! Probably a better way... + return new File(ImageCollection.class + .getClassLoader() + .getResource(directoryName) + .toURI()); + } catch (java.net.URISyntaxException e) { + throw new NoSuchElementException(); + } + } + + /** + * Maps the directory's files with IDs parsed from their names. + * + * @return a map of id to files + */ + private Map mapFilesId() { + HashMap mappedFiles = new HashMap<>(); + + for (File file : this.getDirectory(this.directory).listFiles()) { + mappedFiles.put(Integer.parseInt(file.getName()), file); + } + + return mappedFiles; + } + + /** + * Instantiates a new ImageCollection. + * + * @param directory the relative directory name. + */ + public ImageCollection(String directory) { + this.directory = directory; + } + + /** + * Returns the image corresponding to the given id. + * + * @throws java.util.NoSuchElementException if the requested image does not exists. + * @return the image corresponding to the given id + */ + public Image image(int id) { + if (this.imageOrNull(id) == null) + throw new NoSuchElementException(); + else + return this.imageOrNull(id); + } + + /** + * Returns the image corresponding to the given id. + * + * @return the image corresponding to the given id or null + */ + public Image imageOrNull(int id){ + // Ugly! Probably a better way... + if (mapFilesId().containsKey(id)) + try { + return ImageIO.read(mapFilesId().get(id)); + } catch (java.io.IOException e) { + return null; + } + else + return null; + } +} -- cgit v1.2.3 From 0769993db09bbcfe328d3611405151cfb09357e8 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Mon, 2 May 2016 14:24:28 +0200 Subject: Add the client's GameState --- src/ch/epfl/xblast/client/GameState.java | 123 +++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/ch/epfl/xblast/client/GameState.java (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameState.java b/src/ch/epfl/xblast/client/GameState.java new file mode 100644 index 0000000..154a452 --- /dev/null +++ b/src/ch/epfl/xblast/client/GameState.java @@ -0,0 +1,123 @@ +package ch.epfl.xblast.client; + +import ch.epfl.xblast.PlayerID; +import ch.epfl.xblast.SubCell; + +import java.awt.*; +import java.util.List; + +/** + * @author Timothée FLOURE (257420) + */ +public class GameState { + private final List players; + private final List board; + private final List explosions; + private final List scores; + private final List ticks; + + /** + * Player. + */ + public static final class Player { + private final PlayerID id; + private final int lives; + private final SubCell position; + private final Image image; + + /** + * Instantiates a new Player. + * + * @param id id of the player + * @param lives number of lives of the player + * @param position position of the player + * @param image image related to the actual state of the player + */ + public Player(PlayerID id, int lives, SubCell position, Image image) { + this.id = id; + this.lives = lives; + this.position = position; + this.image = image; + } + + /** + * @return the player ID + */ + public PlayerID id() { + return this.id; + } + + /** + * @return the number of lives of the player + */ + public int lives() { + return this.lives; + } + + /** + * @return the position of the player + */ + public SubCell position() { + return this.position; + } + + /** + * @return the image related to the actual state of the player + */ + public Image image() { + return this.image; + } + } + + /** + * Instantiates a new GameState. + * + * @param players list containing the players + * @param board list containing all the images composing the board + * @param explosions list containing all the images composing the blasts and the bombs + * @param scores list containing all the images composing the actual score + * @param ticks list containing all the images composing the time "line" + */ + public GameState(List players, List board, List explosions, List scores, List ticks) { + this.players = players; + this.board = board; + this.explosions = explosions; + this.scores = scores; + this.ticks = ticks; + } + + /** + * @return list containing the players + */ + public List players() { + return this.players; + } + + /** + * @return list containing all the images composing the board + */ + public List board() { + return this.board; + } + + /** + * @return ist containing all the images composing the blasts and the bombs + */ + public List explosions() { + return this.explosions; + } + + /** + * @return list containing all the images composing the actual score + */ + public List scores() { + return this.scores; + } + + /** + * @return list containing all the images composing the time "line" + */ + public List ticks() { + return this.ticks; + } +} -- cgit v1.2.3 From 440734b4330b08cfad3ee1ee1cf75607ebc08d11 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Mon, 2 May 2016 14:27:24 +0200 Subject: ImageCollection : Store a FIle instead of the given String --- src/ch/epfl/xblast/client/ImageCollection.java | 34 +++++++++----------------- 1 file changed, 12 insertions(+), 22 deletions(-) (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/ImageCollection.java b/src/ch/epfl/xblast/client/ImageCollection.java index 231d84c..1efe5e1 100644 --- a/src/ch/epfl/xblast/client/ImageCollection.java +++ b/src/ch/epfl/xblast/client/ImageCollection.java @@ -13,27 +13,9 @@ import java.util.NoSuchElementException; public final class ImageCollection { /** - * Relative directory. + * Directory. */ - private final String directory; - - /** - * Get the directory Object from the relative directory Name using Java Resource. - * - * @param directoryName the relative name of the directory - * @throws NoSuchElementException - * @return the File Object related to the directory - */ - private static File getDirectory(String directoryName) { - try { // Ugly! Probably a better way... - return new File(ImageCollection.class - .getClassLoader() - .getResource(directoryName) - .toURI()); - } catch (java.net.URISyntaxException e) { - throw new NoSuchElementException(); - } - } + private final File directory; /** * Maps the directory's files with IDs parsed from their names. @@ -43,7 +25,7 @@ public final class ImageCollection { private Map mapFilesId() { HashMap mappedFiles = new HashMap<>(); - for (File file : this.getDirectory(this.directory).listFiles()) { + for (File file : this.directory.listFiles()) { mappedFiles.put(Integer.parseInt(file.getName()), file); } @@ -53,10 +35,18 @@ public final class ImageCollection { /** * Instantiates a new ImageCollection. * + * @throws NoSuchElementException * @param directory the relative directory name. */ public ImageCollection(String directory) { - this.directory = directory; + try { // Ugly! Probably a better way... + this.directory = new File(ImageCollection.class + .getClassLoader() + .getResource(directory) + .toURI()); + } catch (java.net.URISyntaxException e) { + throw new NoSuchElementException(); + } } /** -- cgit v1.2.3 From 4327d5bc70db49b72119773fc422d5e9b8954fc3 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Mon, 2 May 2016 18:45:56 +0200 Subject: GameStateDeserializer Class --- .../epfl/xblast/client/GameStateDeserializer.java | 185 +++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 src/ch/epfl/xblast/client/GameStateDeserializer.java (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameStateDeserializer.java b/src/ch/epfl/xblast/client/GameStateDeserializer.java new file mode 100644 index 0000000..f95c515 --- /dev/null +++ b/src/ch/epfl/xblast/client/GameStateDeserializer.java @@ -0,0 +1,185 @@ +package ch.epfl.xblast.client; + +import ch.epfl.xblast.PlayerID; +import ch.epfl.xblast.RunLengthEncoder; +import ch.epfl.xblast.SubCell; +import ch.epfl.xblast.client.GameState.Player; +import ch.epfl.xblast.server.Ticks; + +import java.util.ArrayList; +import java.util.List; +import java.awt.Image; +import java.util.stream.Collectors; + +/** + * @author Timothée FLOURE (257420) + */ +public final class GameStateDeserializer { + /** + * Image collections folders + */ + private final static String BOARD_IMAGES_FOLDER = "block"; + private final static String EXPLOSION_IMAGES_FOLDER = "explosion"; + private final static String PLAYER_IMAGES_FOLDER = "player"; + private final static String TIME_IMAGES_FOLDER = "score"; + private final static String SCORE_IMAGES_FOLDER = "score"; + + /** + * Serialized data chunks + */ + private final static int NUMBER_OF_PLAYERS = 4; + private final static int PLAYER_CHUNK_SIZE = 6; + private final static int PLAYER_SIZE = NUMBER_OF_PLAYERS * PLAYER_CHUNK_SIZE; + private final static int TIME_LINE_SIZE = (Ticks.GAME_DURATION / 2); + + /** + * Images ID + */ + private final static int TIME_LINE_LED_ON_IMAGE_ID = 21; + private final static int TIME_LINE_LED_OFF_IMAGE_ID = 20; + private final static int SCORE_TEXT_MIDDLE_IMAGE_ID = 10; + private final static int SCORE_TEXT_RIGHT_IMAGE_ID = 11; + private final static int SCORE_TILE_VOID_IMAGE_ID = 12; + + /** + * Deserialize an encoded chunk of data and link it to images. + * + * @param data serialized data + * @param imageCollection ImageCollection related to the type of data + * @return a list of Images corresponding to the serialized element + */ + private static List deserializeChunk(List data, ImageCollection imageCollection) { + return RunLengthEncoder.decode(data).stream() + .map(c -> imageCollection.imageOrNull(Byte.toUnsignedInt(c))) + .collect(Collectors.toList()); + } + + /** + * Build a list of players given the serialized data.. + * + * @param serializedPlayers the serialized players + * @return a list of players built from the serialized players + */ + private static List deserializePlayers(List serializedPlayers) { + List players = new ArrayList<>(); + + for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { + players.add( + deserializePlayer( + serializedPlayers.subList(i * PLAYER_CHUNK_SIZE, (i+1) * PLAYER_CHUNK_SIZE), i + ) + ); + } + + return players; + } + + /** + * Generate a Player from the serialized data. + * + * @param serializedPlayer a serialized player + * @return a player built from the serialized player + */ + private static Player deserializePlayer(List serializedPlayer, int i) { + PlayerID id = PlayerID.values()[i]; + int lives = Byte.toUnsignedInt(serializedPlayer.get(0)); + SubCell position = new SubCell( + Byte.toUnsignedInt(serializedPlayer.get(1)), + Byte.toUnsignedInt(serializedPlayer.get(2)) + ); + Image image = (new ImageCollection(PLAYER_IMAGES_FOLDER)).imageOrNull(Byte.toUnsignedInt(serializedPlayer.get(3))); + + return new Player(id, lives, position, image); + } + + /** + * Generate the list of images composing the scores. + * + * @param players players + * @return the scores of the given players + */ + private static List buildScores(List players) { + List scores = new ArrayList<>(); + ImageCollection imageCollection = new ImageCollection(SCORE_IMAGES_FOLDER); + + scores.addAll(buildPlayerScore(players.get(0), imageCollection)); + + return scores; + } + + /** + * Generate the images composing the score of one player. + * + * @param player serialized data corresponding to the player + * @param imageCollection image collection related to the scores + * @return the score of the given player + */ + private static List buildPlayerScore(Player player, ImageCollection imageCollection) { + List playerScore = new ArrayList<>(); + + if (player.lives() < 1) + playerScore.add(imageCollection.imageOrNull(0 + (player.id().ordinal()*2))); + else + playerScore.add(imageCollection.imageOrNull(1 + (player.id().ordinal()*2))); + + playerScore.add(imageCollection.imageOrNull(SCORE_TEXT_MIDDLE_IMAGE_ID)); + playerScore.add(imageCollection.imageOrNull(SCORE_TEXT_RIGHT_IMAGE_ID)); + return playerScore; + } + /** + * Generate the list of images composing the time "line". + * + * @param time the remaining time + * @return the list of images composing the time "line" + */ + private static List buildTimeLine(Byte time) { + List ticks = new ArrayList<>(); + ImageCollection imageCollection = new ImageCollection(TIME_IMAGES_FOLDER); + + for (int i = 0; i < TIME_LINE_SIZE; i++) { + if (i < Byte.toUnsignedInt(time)) + ticks.add(imageCollection.imageOrNull(TIME_LINE_LED_ON_IMAGE_ID)); + else + ticks.add(imageCollection.imageOrNull(TIME_LINE_LED_OFF_IMAGE_ID)); + } + return ticks; + } + + /** + * Deserialize an incoming GameState. + * + * @param serializedData the serialized gameState + * @return a new GameState build from the serialized data. + */ + public static GameState deserialize(List serializedData) { + + // Indexes + int boardIndex = 1; + int boardSize = serializedData.get(boardIndex); + int explosionsIndex = boardIndex + boardSize; + int explosionsSize = serializedData.get(explosionsIndex); + int playersIndex = explosionsIndex + explosionsSize; + Byte time = serializedData.get(serializedData.size()); + + // Deserialize the Board + List board = deserializeChunk( + serializedData.subList(boardIndex, boardSize), + new ImageCollection(BOARD_IMAGES_FOLDER) + ); + + // Deserialize the explosions + List explosions = deserializeChunk( + serializedData.subList(boardIndex,boardSize), + new ImageCollection(EXPLOSION_IMAGES_FOLDER) + ); + + // Deserialize the players + List players = deserializePlayers(serializedData.subList(playersIndex,PLAYER_SIZE)); + + // Generate the scores and the time "line" + List scores = buildScores(players); + List timeLine = buildTimeLine(time); + + return new GameState(players, board, explosions, scores, timeLine); + } +} -- cgit v1.2.3 From b5b09a7130b9f7e50c540ab99e298a676ecfbe29 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Sun, 8 May 2016 14:12:39 +0200 Subject: Fix bug in ImageCollection (now able to fetch an image ID from its filename) --- src/ch/epfl/xblast/client/ImageCollection.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/ImageCollection.java b/src/ch/epfl/xblast/client/ImageCollection.java index 1efe5e1..4f54f3b 100644 --- a/src/ch/epfl/xblast/client/ImageCollection.java +++ b/src/ch/epfl/xblast/client/ImageCollection.java @@ -11,9 +11,14 @@ import java.util.NoSuchElementException; * @author Timothée FLOURE (257420) */ public final class ImageCollection { + /** + * Bounds of the file ID in the filename. + */ + private final int FILENAME_ID_BEGIN_INDEX = 0; + private final int FILENAME_ID_END_INDEX = 2; /** - * Directory. + * Directory related to the ImageCollection. */ private final File directory; @@ -26,7 +31,7 @@ public final class ImageCollection { HashMap mappedFiles = new HashMap<>(); for (File file : this.directory.listFiles()) { - mappedFiles.put(Integer.parseInt(file.getName()), file); + mappedFiles.put(Integer.parseInt(file.getName().substring(FILENAME_ID_BEGIN_INDEX,FILENAME_ID_END_INDEX)), file); } return mappedFiles; @@ -35,8 +40,8 @@ public final class ImageCollection { /** * Instantiates a new ImageCollection. * - * @throws NoSuchElementException - * @param directory the relative directory name. + * @throws NoSuchElementException if the requested directory is not valid + * @param directory the relative directory name */ public ImageCollection(String directory) { try { // Ugly! Probably a better way... @@ -44,7 +49,7 @@ public final class ImageCollection { .getClassLoader() .getResource(directory) .toURI()); - } catch (java.net.URISyntaxException e) { + } catch (Exception e) { throw new NoSuchElementException(); } } -- cgit v1.2.3 From e2f344f4ff6683239dc78dc241b099570180864a Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Sun, 8 May 2016 14:14:34 +0200 Subject: Rewrite few comments in client.GameState (minor) --- src/ch/epfl/xblast/client/GameState.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameState.java b/src/ch/epfl/xblast/client/GameState.java index 154a452..4906094 100644 --- a/src/ch/epfl/xblast/client/GameState.java +++ b/src/ch/epfl/xblast/client/GameState.java @@ -9,7 +9,10 @@ import java.util.List; /** * @author Timothée FLOURE (257420) */ -public class GameState { +public final class GameState { + /** + * GameState's parameters. + */ private final List players; private final List board; private final List explosions; @@ -17,9 +20,12 @@ public class GameState { private final List ticks; /** - * Player. + * A Player. */ public static final class Player { + /** + * Player's parameters. + */ private final PlayerID id; private final int lives; private final SubCell position; -- cgit v1.2.3 From 03a6be12726147c494e145b543230ecbf64905e7 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Sun, 8 May 2016 14:15:26 +0200 Subject: Debug the GameStateDeserializer Class --- .../epfl/xblast/client/GameStateDeserializer.java | 35 ++++++++++++---------- 1 file changed, 19 insertions(+), 16 deletions(-) (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameStateDeserializer.java b/src/ch/epfl/xblast/client/GameStateDeserializer.java index f95c515..66be2d9 100644 --- a/src/ch/epfl/xblast/client/GameStateDeserializer.java +++ b/src/ch/epfl/xblast/client/GameStateDeserializer.java @@ -28,8 +28,9 @@ public final class GameStateDeserializer { * Serialized data chunks */ private final static int NUMBER_OF_PLAYERS = 4; - private final static int PLAYER_CHUNK_SIZE = 6; - private final static int PLAYER_SIZE = NUMBER_OF_PLAYERS * PLAYER_CHUNK_SIZE; + private final static int PLAYER_CHUNK_SIZE = 4; + private final static int PLAYER_TOTAL_SIZE = NUMBER_OF_PLAYERS * PLAYER_CHUNK_SIZE; + private final static int TIME_LINE_SIZE = (Ticks.GAME_DURATION / 2); /** @@ -55,10 +56,10 @@ public final class GameStateDeserializer { } /** - * Build a list of players given the serialized data.. + * Build a list of players given the serialized data. * * @param serializedPlayers the serialized players - * @return a list of players built from the serialized players + * @return a list of players built from the serialized data */ private static List deserializePlayers(List serializedPlayers) { List players = new ArrayList<>(); @@ -75,10 +76,10 @@ public final class GameStateDeserializer { } /** - * Generate a Player from the serialized data. + * Generate a Player from its serialized data. * * @param serializedPlayer a serialized player - * @return a player built from the serialized player + * @return a player built from its serialized data */ private static Player deserializePlayer(List serializedPlayer, int i) { PlayerID id = PlayerID.values()[i]; @@ -126,6 +127,7 @@ public final class GameStateDeserializer { playerScore.add(imageCollection.imageOrNull(SCORE_TEXT_RIGHT_IMAGE_ID)); return playerScore; } + /** * Generate the list of images composing the time "line". * @@ -153,28 +155,29 @@ public final class GameStateDeserializer { */ public static GameState deserialize(List serializedData) { - // Indexes - int boardIndex = 1; - int boardSize = serializedData.get(boardIndex); - int explosionsIndex = boardIndex + boardSize; - int explosionsSize = serializedData.get(explosionsIndex); - int playersIndex = explosionsIndex + explosionsSize; - Byte time = serializedData.get(serializedData.size()); + // Build the indexes + int boardBeginIndex = 0; // First element + int boardEndIndex = (boardBeginIndex) + Byte.toUnsignedInt(serializedData.get(boardBeginIndex)); + int explosionsBeginIndex = boardEndIndex + 1; + int explosionsEndIndex = explosionsBeginIndex + Byte.toUnsignedInt(serializedData.get(explosionsBeginIndex)); + int playersBeginIndex = explosionsEndIndex + 1; + int playersEndIndex = (playersBeginIndex - 1) + PLAYER_TOTAL_SIZE; + Byte time = serializedData.get(playersEndIndex + 1); // Last element // Deserialize the Board List board = deserializeChunk( - serializedData.subList(boardIndex, boardSize), + serializedData.subList(boardBeginIndex + 1, boardEndIndex + 1), new ImageCollection(BOARD_IMAGES_FOLDER) ); // Deserialize the explosions List explosions = deserializeChunk( - serializedData.subList(boardIndex,boardSize), + serializedData.subList(explosionsBeginIndex + 1,explosionsEndIndex + 1), new ImageCollection(EXPLOSION_IMAGES_FOLDER) ); // Deserialize the players - List players = deserializePlayers(serializedData.subList(playersIndex,PLAYER_SIZE)); + List players = deserializePlayers(serializedData.subList(playersBeginIndex,playersEndIndex + 1)); // Generate the scores and the time "line" List scores = buildScores(players); -- cgit v1.2.3 From 089af244870bd45fa55ad782e71403d98eed2c6a Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Sun, 8 May 2016 14:25:15 +0200 Subject: Fix the scores "line" generation in GameStateDeserializer --- src/ch/epfl/xblast/client/GameStateDeserializer.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameStateDeserializer.java b/src/ch/epfl/xblast/client/GameStateDeserializer.java index 66be2d9..1fb1c61 100644 --- a/src/ch/epfl/xblast/client/GameStateDeserializer.java +++ b/src/ch/epfl/xblast/client/GameStateDeserializer.java @@ -31,7 +31,11 @@ public final class GameStateDeserializer { private final static int PLAYER_CHUNK_SIZE = 4; private final static int PLAYER_TOTAL_SIZE = NUMBER_OF_PLAYERS * PLAYER_CHUNK_SIZE; + /** + * Various + */ private final static int TIME_LINE_SIZE = (Ticks.GAME_DURATION / 2); + private final static int SCORE_SEPARATION_SIZE = 8; /** * Images ID @@ -103,7 +107,14 @@ public final class GameStateDeserializer { List scores = new ArrayList<>(); ImageCollection imageCollection = new ImageCollection(SCORE_IMAGES_FOLDER); - scores.addAll(buildPlayerScore(players.get(0), imageCollection)); + scores.addAll(buildPlayerScore(players.get(0), imageCollection)); // PLAYER_1 + scores.addAll(buildPlayerScore(players.get(1), imageCollection)); // PLAYER_2 + + // Separation tiles + for (int i = 0; i < SCORE_SEPARATION_SIZE; i++){ scores.add(imageCollection.imageOrNull(SCORE_TILE_VOID_IMAGE_ID)); } + + scores.addAll(buildPlayerScore(players.get(2), imageCollection)); // PLAYER_3 + scores.addAll(buildPlayerScore(players.get(3), imageCollection)); // PLAYER_4 return scores; } -- cgit v1.2.3 From 085610b35b7e85cfbec43a1ceb7c38a4b15fe3c2 Mon Sep 17 00:00:00 2001 From: Timothée Floure Date: Sun, 8 May 2016 14:33:25 +0200 Subject: Minor changes to the comments and variables --- src/ch/epfl/xblast/client/GameStateDeserializer.java | 11 +++++++---- src/ch/epfl/xblast/client/ImageCollection.java | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameStateDeserializer.java b/src/ch/epfl/xblast/client/GameStateDeserializer.java index 1fb1c61..f1327e3 100644 --- a/src/ch/epfl/xblast/client/GameStateDeserializer.java +++ b/src/ch/epfl/xblast/client/GameStateDeserializer.java @@ -32,7 +32,7 @@ public final class GameStateDeserializer { private final static int PLAYER_TOTAL_SIZE = NUMBER_OF_PLAYERS * PLAYER_CHUNK_SIZE; /** - * Various + * Scores & Time */ private final static int TIME_LINE_SIZE = (Ticks.GAME_DURATION / 2); private final static int SCORE_SEPARATION_SIZE = 8; @@ -68,7 +68,7 @@ public final class GameStateDeserializer { private static List deserializePlayers(List serializedPlayers) { List players = new ArrayList<>(); - for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { + for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { // ugly players.add( deserializePlayer( serializedPlayers.subList(i * PLAYER_CHUNK_SIZE, (i+1) * PLAYER_CHUNK_SIZE), i @@ -87,11 +87,14 @@ public final class GameStateDeserializer { */ private static Player deserializePlayer(List serializedPlayer, int i) { PlayerID id = PlayerID.values()[i]; + int lives = Byte.toUnsignedInt(serializedPlayer.get(0)); + SubCell position = new SubCell( Byte.toUnsignedInt(serializedPlayer.get(1)), Byte.toUnsignedInt(serializedPlayer.get(2)) ); + Image image = (new ImageCollection(PLAYER_IMAGES_FOLDER)).imageOrNull(Byte.toUnsignedInt(serializedPlayer.get(3))); return new Player(id, lives, position, image); @@ -164,9 +167,9 @@ public final class GameStateDeserializer { * @param serializedData the serialized gameState * @return a new GameState build from the serialized data. */ - public static GameState deserialize(List serializedData) { + public static GameState deserialize(List serializedData) { // Ugly - // Build the indexes + // Build the indexes (Ugly) int boardBeginIndex = 0; // First element int boardEndIndex = (boardBeginIndex) + Byte.toUnsignedInt(serializedData.get(boardBeginIndex)); int explosionsBeginIndex = boardEndIndex + 1; diff --git a/src/ch/epfl/xblast/client/ImageCollection.java b/src/ch/epfl/xblast/client/ImageCollection.java index 4f54f3b..ee0e8cf 100644 --- a/src/ch/epfl/xblast/client/ImageCollection.java +++ b/src/ch/epfl/xblast/client/ImageCollection.java @@ -14,8 +14,8 @@ public final class ImageCollection { /** * Bounds of the file ID in the filename. */ - private final int FILENAME_ID_BEGIN_INDEX = 0; - private final int FILENAME_ID_END_INDEX = 2; + private static final int FILENAME_ID_BEGIN_INDEX = 0; + private static final int FILENAME_ID_END_INDEX = 2; /** * Directory related to the ImageCollection. -- cgit v1.2.3 From f19b688112598273ab5e1147c185c4ab2a3fe749 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Sun, 8 May 2016 20:26:28 +0200 Subject: Refactor deserialization --- src/ch/epfl/xblast/client/GameState.java | 70 +++-- .../epfl/xblast/client/GameStateDeserializer.java | 283 ++++++++++----------- src/ch/epfl/xblast/client/ImageCollection.java | 154 ++++++++--- .../epfl/xblast/client/painter/ScorePainter.java | 107 ++++++++ .../xblast/client/painter/TimeLinePainter.java | 39 +++ 5 files changed, 440 insertions(+), 213 deletions(-) create mode 100644 src/ch/epfl/xblast/client/painter/ScorePainter.java create mode 100644 src/ch/epfl/xblast/client/painter/TimeLinePainter.java (limited to 'src/ch') diff --git a/src/ch/epfl/xblast/client/GameState.java b/src/ch/epfl/xblast/client/GameState.java index 4906094..7652cd5 100644 --- a/src/ch/epfl/xblast/client/GameState.java +++ b/src/ch/epfl/xblast/client/GameState.java @@ -1,31 +1,29 @@ package ch.epfl.xblast.client; +import ch.epfl.xblast.ArgumentChecker; +import ch.epfl.xblast.Lists; import ch.epfl.xblast.PlayerID; import ch.epfl.xblast.SubCell; +import ch.epfl.xblast.client.painter.ScorePainter; +import ch.epfl.xblast.client.painter.TimeLinePainter; import java.awt.*; import java.util.List; +import java.util.Objects; /** + * The client representation of a game state. + * * @author Timothée FLOURE (257420) + * @author Pacien TRAN-GIRARD (261948) */ public final class GameState { - /** - * GameState's parameters. - */ - private final List players; - private final List board; - private final List explosions; - private final List scores; - private final List ticks; /** * A Player. */ public static final class Player { - /** - * Player's parameters. - */ + private final PlayerID id; private final int lives; private final SubCell position; @@ -34,16 +32,16 @@ public final class GameState { /** * Instantiates a new Player. * - * @param id id of the player - * @param lives number of lives of the player + * @param id id of the player + * @param lives number of lives of the player * @param position position of the player - * @param image image related to the actual state of the player + * @param image image related to the actual state of the player */ public Player(PlayerID id, int lives, SubCell position, Image image) { this.id = id; this.lives = lives; - this.position = position; - this.image = image; + this.position = Objects.requireNonNull(position); + this.image = Objects.requireNonNull(image); } /** @@ -73,23 +71,42 @@ public final class GameState { public Image image() { return this.image; } + + } + + private final List players; + private final List board; + private final List explosions; + private final List scores; + private final List ticks; + + /** + * Instantiates a new GameState. + * + * @param players list containing the players + * @param board list containing all the images composing the board + * @param explosions list containing all the images composing the blasts and the bombs + * @param scores list containing all the images composing the actual score + * @param ticks list containing all the images composing the time "line" + */ + public GameState(List players, List board, List explosions, List scores, List ticks) { + this.players = Lists.immutableList(ArgumentChecker.requireNonEmpty(players)); + this.board = Lists.immutableList(ArgumentChecker.requireNonEmpty(board)); + this.explosions = Lists.immutableList(Objects.requireNonNull(explosions)); + this.scores = Lists.immutableList(ArgumentChecker.requireNonEmpty(scores)); + this.ticks = Lists.immutableList(ArgumentChecker.requireNonEmpty(ticks)); } /** * Instantiates a new GameState. * - * @param players list containing the players - * @param board list containing all the images composing the board + * @param players list containing the players + * @param board list containing all the images composing the board * @param explosions list containing all the images composing the blasts and the bombs - * @param scores list containing all the images composing the actual score - * @param ticks list containing all the images composing the time "line" + * @param ticks the ticks */ - public GameState(List players, List board, List explosions, List scores, List ticks) { - this.players = players; - this.board = board; - this.explosions = explosions; - this.scores = scores; - this.ticks = ticks; + public GameState(List players, List board, List explosions, int ticks) { + this(players, board, explosions, ScorePainter.buildScorePanel(players), TimeLinePainter.buildTimeLine(ticks)); } /** @@ -126,4 +143,5 @@ public final class GameState { public List ticks() { return this.ticks; } + } diff --git a/src/ch/epfl/xblast/client/GameStateDeserializer.java b/src/ch/epfl/xblast/client/GameStateDeserializer.java index f1327e3..20b44f9 100644 --- a/src/ch/epfl/xblast/client/GameStateDeserializer.java +++ b/src/ch/epfl/xblast/client/GameStateDeserializer.java @@ -1,164 +1,170 @@ package ch.epfl.xblast.client; +import ch.epfl.xblast.Lists; import ch.epfl.xblast.PlayerID; import ch.epfl.xblast.RunLengthEncoder; import ch.epfl.xblast.SubCell; import ch.epfl.xblast.client.GameState.Player; -import ch.epfl.xblast.server.Ticks; -import java.util.ArrayList; +import java.awt.*; +import java.util.*; import java.util.List; -import java.awt.Image; import java.util.stream.Collectors; /** + * The client game state deserializer. + * + * @author Pacien TRAN-GIRARD (261948) * @author Timothée FLOURE (257420) */ public final class GameStateDeserializer { - /** - * Image collections folders - */ - private final static String BOARD_IMAGES_FOLDER = "block"; - private final static String EXPLOSION_IMAGES_FOLDER = "explosion"; - private final static String PLAYER_IMAGES_FOLDER = "player"; - private final static String TIME_IMAGES_FOLDER = "score"; - private final static String SCORE_IMAGES_FOLDER = "score"; - /** - * Serialized data chunks - */ - private final static int NUMBER_OF_PLAYERS = 4; - private final static int PLAYER_CHUNK_SIZE = 4; - private final static int PLAYER_TOTAL_SIZE = NUMBER_OF_PLAYERS * PLAYER_CHUNK_SIZE; + private static final int SERIALIZED_PLAYER_LENGTH = 4; + private static final int SERIALIZED_PLAYERS_LENGTH = PlayerID.values().length * SERIALIZED_PLAYER_LENGTH; + private static final int SERIALIZED_TICKS_LENGTH = 1; /** - * Scores & Time + * Represents a length-prefixed chunk of byte-encoded data. */ - private final static int TIME_LINE_SIZE = (Ticks.GAME_DURATION / 2); - private final static int SCORE_SEPARATION_SIZE = 8; + private static class VariableLengthChunk { - /** - * Images ID - */ - private final static int TIME_LINE_LED_ON_IMAGE_ID = 21; - private final static int TIME_LINE_LED_OFF_IMAGE_ID = 20; - private final static int SCORE_TEXT_MIDDLE_IMAGE_ID = 10; - private final static int SCORE_TEXT_RIGHT_IMAGE_ID = 11; - private final static int SCORE_TILE_VOID_IMAGE_ID = 12; + private static final int PREFIX_SHIFT = 1; - /** - * Deserialize an encoded chunk of data and link it to images. - * - * @param data serialized data - * @param imageCollection ImageCollection related to the type of data - * @return a list of Images corresponding to the serialized element - */ - private static List deserializeChunk(List data, ImageCollection imageCollection) { - return RunLengthEncoder.decode(data).stream() - .map(c -> imageCollection.imageOrNull(Byte.toUnsignedInt(c))) - .collect(Collectors.toList()); - } + private final List bytes; - /** - * Build a list of players given the serialized data. - * - * @param serializedPlayers the serialized players - * @return a list of players built from the serialized data - */ - private static List deserializePlayers(List serializedPlayers) { - List players = new ArrayList<>(); - - for (int i = 0; i < NUMBER_OF_PLAYERS; i++) { // ugly - players.add( - deserializePlayer( - serializedPlayers.subList(i * PLAYER_CHUNK_SIZE, (i+1) * PLAYER_CHUNK_SIZE), i - ) - ); + /** + * Instantiates a VariableLengthChunk from a byte sequence. + * + * @param l the byte sequence + */ + public VariableLengthChunk(List l) { + this.bytes = Lists.immutableList(l); } - return players; - } + /** + * Returns the length of the chunk given by its prefix. + * + * @return the length of the chunk + */ + public int length() { + if (this.bytes.isEmpty()) + return 0; + else + return Byte.toUnsignedInt(this.bytes.get(0)); + } - /** - * Generate a Player from its serialized data. - * - * @param serializedPlayer a serialized player - * @return a player built from its serialized data - */ - private static Player deserializePlayer(List serializedPlayer, int i) { - PlayerID id = PlayerID.values()[i]; + /** + * Returns the head of the desired length of the chunk. + * + * @param takeFirst the desired length + * @return the head + */ + public List head(int takeFirst) { + return this.bytes.subList(0, takeFirst); + } - int lives = Byte.toUnsignedInt(serializedPlayer.get(0)); + /** + * Returns the head of the size-prefixed chunk. + * + * @return the head + */ + public List head() { + if (this.length() < 1) + return Collections.emptyList(); + else + return this.bytes.subList(PREFIX_SHIFT, this.length() + PREFIX_SHIFT); + } - SubCell position = new SubCell( - Byte.toUnsignedInt(serializedPlayer.get(1)), - Byte.toUnsignedInt(serializedPlayer.get(2)) - ); + /** + * Returns the tail of the chunk, with the head of the given length dropped. + * + * @param dropFirst the length to drop + * @return the tail + */ + public VariableLengthChunk tail(int dropFirst) { + return new VariableLengthChunk( + this.bytes.subList(dropFirst, this.bytes.size())); + } - Image image = (new ImageCollection(PLAYER_IMAGES_FOLDER)).imageOrNull(Byte.toUnsignedInt(serializedPlayer.get(3))); + /** + * Returns the tail of the size-prefixed chunk. + * + * @return the tail + */ + public VariableLengthChunk tail() { + return new VariableLengthChunk( + this.bytes.subList(this.length() + PREFIX_SHIFT, this.bytes.size())); + } - return new Player(id, lives, position, image); + } + + private GameStateDeserializer() { + // Static class } /** - * Generate the list of images composing the scores. + * Deserialize an encoded chunk of data and link it to images. * - * @param players players - * @return the scores of the given players + * @param data serialized data + * @param imageCollection ImageCollection related to the type of data + * @return a list of Images corresponding to the serialized element */ - private static List buildScores(List players) { - List scores = new ArrayList<>(); - ImageCollection imageCollection = new ImageCollection(SCORE_IMAGES_FOLDER); - - scores.addAll(buildPlayerScore(players.get(0), imageCollection)); // PLAYER_1 - scores.addAll(buildPlayerScore(players.get(1), imageCollection)); // PLAYER_2 - - // Separation tiles - for (int i = 0; i < SCORE_SEPARATION_SIZE; i++){ scores.add(imageCollection.imageOrNull(SCORE_TILE_VOID_IMAGE_ID)); } - - scores.addAll(buildPlayerScore(players.get(2), imageCollection)); // PLAYER_3 - scores.addAll(buildPlayerScore(players.get(3), imageCollection)); // PLAYER_4 - - return scores; + private static List deserializeImageChunk(List data, ImageCollection imageCollection) { + return RunLengthEncoder.decode(data).stream() + .map(Byte::toUnsignedInt) + .map(imageCollection::imageOrNull) + .collect(Collectors.toList()); } /** - * Generate the images composing the score of one player. + * Unserializes a byte representation of a Player. * - * @param player serialized data corresponding to the player - * @param imageCollection image collection related to the scores - * @return the score of the given player + * @param id the PlayerID + * @param serializedPlayer the serialized player + * @return the player + * @throws IllegalArgumentException if the byte chunk is invalid. */ - private static List buildPlayerScore(Player player, ImageCollection imageCollection) { - List playerScore = new ArrayList<>(); + private static Player deserializePlayer(PlayerID id, List serializedPlayer) { + if (Objects.isNull(serializedPlayer) || serializedPlayer.size() != SERIALIZED_PLAYER_LENGTH) + throw new IllegalArgumentException(); - if (player.lives() < 1) - playerScore.add(imageCollection.imageOrNull(0 + (player.id().ordinal()*2))); - else - playerScore.add(imageCollection.imageOrNull(1 + (player.id().ordinal()*2))); + List intPlayerData = serializedPlayer.stream() + .map(Byte::toUnsignedInt) + .collect(Collectors.toList()); - playerScore.add(imageCollection.imageOrNull(SCORE_TEXT_MIDDLE_IMAGE_ID)); - playerScore.add(imageCollection.imageOrNull(SCORE_TEXT_RIGHT_IMAGE_ID)); - return playerScore; + Integer lives = intPlayerData.get(0); + SubCell pos = new SubCell(intPlayerData.get(1), intPlayerData.get(2)); + Image img = ImageCollection.PLAYERS_IMAGES.imageOrNull(intPlayerData.get(3)); + return new Player(id, lives, pos, img); } /** - * Generate the list of images composing the time "line". + * Unserializes a byte representation of players. * - * @param time the remaining time - * @return the list of images composing the time "line" + * @param serializedPlayers the serialized players + * @return the list of deserialized players + * @throws IllegalArgumentException if the byte chunk is invalid. */ - private static List buildTimeLine(Byte time) { - List ticks = new ArrayList<>(); - ImageCollection imageCollection = new ImageCollection(TIME_IMAGES_FOLDER); + private static List deserializePlayers(List serializedPlayers) { + if (Objects.isNull(serializedPlayers) || serializedPlayers.size() != SERIALIZED_PLAYERS_LENGTH) + throw new IllegalArgumentException(); + + return Collections.unmodifiableList(Arrays.stream(PlayerID.values()) + .map(pid -> { + List chunk = serializedPlayers.subList( + pid.ordinal() * SERIALIZED_PLAYER_LENGTH, + (pid.ordinal() + 1) * SERIALIZED_PLAYER_LENGTH); + return new AbstractMap.SimpleImmutableEntry<>(pid, chunk); + }) + .map(e -> deserializePlayer(e.getKey(), e.getValue())) + .collect(Collectors.toList())); + } - for (int i = 0; i < TIME_LINE_SIZE; i++) { - if (i < Byte.toUnsignedInt(time)) - ticks.add(imageCollection.imageOrNull(TIME_LINE_LED_ON_IMAGE_ID)); - else - ticks.add(imageCollection.imageOrNull(TIME_LINE_LED_OFF_IMAGE_ID)); - } - return ticks; + private static int deserializeTicks(List serializedTicks) { + if (Objects.isNull(serializedTicks) || serializedTicks.size() != SERIALIZED_TICKS_LENGTH) + throw new IllegalArgumentException(); + + return Byte.toUnsignedInt(serializedTicks.get(0)); } /** @@ -167,36 +173,21 @@ public final class GameStateDeserializer { * @param serializedData the serialized gameState * @return a new GameState build from the serialized data. */ - public static GameState deserialize(List serializedData) { // Ugly - - // Build the indexes (Ugly) - int boardBeginIndex = 0; // First element - int boardEndIndex = (boardBeginIndex) + Byte.toUnsignedInt(serializedData.get(boardBeginIndex)); - int explosionsBeginIndex = boardEndIndex + 1; - int explosionsEndIndex = explosionsBeginIndex + Byte.toUnsignedInt(serializedData.get(explosionsBeginIndex)); - int playersBeginIndex = explosionsEndIndex + 1; - int playersEndIndex = (playersBeginIndex - 1) + PLAYER_TOTAL_SIZE; - Byte time = serializedData.get(playersEndIndex + 1); // Last element - - // Deserialize the Board - List board = deserializeChunk( - serializedData.subList(boardBeginIndex + 1, boardEndIndex + 1), - new ImageCollection(BOARD_IMAGES_FOLDER) - ); - - // Deserialize the explosions - List explosions = deserializeChunk( - serializedData.subList(explosionsBeginIndex + 1,explosionsEndIndex + 1), - new ImageCollection(EXPLOSION_IMAGES_FOLDER) - ); - - // Deserialize the players - List players = deserializePlayers(serializedData.subList(playersBeginIndex,playersEndIndex + 1)); - - // Generate the scores and the time "line" - List scores = buildScores(players); - List timeLine = buildTimeLine(time); - - return new GameState(players, board, explosions, scores, timeLine); + public static GameState deserialize(List serializedData) { + VariableLengthChunk chunks = new VariableLengthChunk(serializedData); + + List board = deserializeImageChunk(chunks.head(), ImageCollection.BOARD_IMAGES); + chunks = chunks.tail(); + + List explosions = deserializeImageChunk(chunks.head(), ImageCollection.EXPLOSIONS_IMAGES); + chunks = chunks.tail(); + + List players = deserializePlayers(chunks.head(SERIALIZED_PLAYERS_LENGTH)); + chunks = chunks.tail(SERIALIZED_PLAYERS_LENGTH); + + int ticks = deserializeTicks(chunks.head(SERIALIZED_TICKS_LENGTH)); + + return new GameState(players, board, explosions, ticks); } + } diff --git a/src/ch/epfl/xblast/client/ImageCollection.java b/src/ch/epfl/xblast/client/ImageCollection.java index ee0e8cf..09ffd59 100644 --- a/src/ch/epfl/xblast/client/ImageCollection.java +++ b/src/ch/epfl/xblast/client/ImageCollection.java @@ -1,86 +1,158 @@ package ch.epfl.xblast.client; import javax.imageio.ImageIO; -import java.util.HashMap; -import java.util.Map; -import java.awt.Image; +import java.awt.*; import java.io.File; -import java.util.NoSuchElementException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.*; +import java.util.List; +import java.util.stream.Collectors; /** + * An image collection. + * + * @author Pacien TRAN-GIRARD (261948) * @author Timothée FLOURE (257420) */ public final class ImageCollection { + + private static final String FILENAME_SEPARATOR = "_"; + + public final static ImageCollection BOARD_IMAGES = new ImageCollection("block"); + public final static ImageCollection EXPLOSIONS_IMAGES = new ImageCollection("explosion"); + public final static ImageCollection PLAYERS_IMAGES = new ImageCollection("player"); + public final static ImageCollection SCORES_IMAGES = new ImageCollection("score"); + public final static ImageCollection TIME_IMAGES = SCORES_IMAGES; + /** - * Bounds of the file ID in the filename. + * Returns the URL of the requested directory, or raise an Exception if it cannot be accessed. + * + * @param dir the requested directory + * @return the directory URL + * @throws NoSuchElementException if the requested directory is not valid */ - private static final int FILENAME_ID_BEGIN_INDEX = 0; - private static final int FILENAME_ID_END_INDEX = 2; + private static URL getDirectoryURL(String dir) { + URL url = ImageCollection.class + .getClassLoader() + .getResource(dir); + + if (Objects.isNull(url)) + throw new NoSuchElementException(dir); + + return url; + } /** - * Directory related to the ImageCollection. + * Returns the URI of the requested directory, or raise an Exception if it cannot be accessed. + * + * @param dir the requested directory + * @return the directory URI + * @throws NoSuchElementException if the requested directory is not valid + */ + private static URI getDirectoryURI(String dir) { + try { + return getDirectoryURL(dir).toURI(); + } catch (URISyntaxException e) { + throw new NoSuchElementException(dir); + } + } + + /** + * Returns the content of the directory at the given path. + * + * @param dirName the directory + * @return its content + * @throws NoSuchElementException if the given path is unreachable */ - private final File directory; + private static List getDirectoryContent(String dirName) { + File dir = new File(getDirectoryURI(dirName)); + if (!dir.isDirectory()) + throw new NoSuchElementException(dirName); + + File[] files = dir.listFiles(); + if (Objects.isNull(files)) + throw new NoSuchElementException(dirName); + + return Arrays.asList(files); + } /** - * Maps the directory's files with IDs parsed from their names. + * Parses the ID of an image from its name, or returns null on error. * - * @return a map of id to files + * @param filename the name of the file + * @return the ID, or null */ - private Map mapFilesId() { - HashMap mappedFiles = new HashMap<>(); + private static Integer parseImageID(String filename) { + try { + return Integer.parseInt(filename.substring(0, filename.indexOf(FILENAME_SEPARATOR))); + } catch (NumberFormatException | IndexOutOfBoundsException e) { + return null; + } + } - for (File file : this.directory.listFiles()) { - mappedFiles.put(Integer.parseInt(file.getName().substring(FILENAME_ID_BEGIN_INDEX,FILENAME_ID_END_INDEX)), file); + /** + * Reads an image from the given File or returns null on error. + * + * @param file the file + * @return the Image, or null + */ + private static Image readImage(File file) { + try { + return ImageIO.read(file); + } catch (IOException e) { + return null; } + } - return mappedFiles; + /** + * Builds and returns a mapping ID->Image, with all invalid elements discarded. + * + * @param fileList the list of images files + * @return the ID->Image mapping + */ + private static Map buildImageMap(List fileList) { + return fileList.stream() + .map(f -> new AbstractMap.SimpleImmutableEntry<>(parseImageID(f.getName()), readImage(f))) + .filter(e -> !Objects.isNull(e.getKey()) && !Objects.isNull(e.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } + private final Map images; + /** * Instantiates a new ImageCollection. * + * @param dir the relative directory name * @throws NoSuchElementException if the requested directory is not valid - * @param directory the relative directory name */ - public ImageCollection(String directory) { - try { // Ugly! Probably a better way... - this.directory = new File(ImageCollection.class - .getClassLoader() - .getResource(directory) - .toURI()); - } catch (Exception e) { - throw new NoSuchElementException(); - } + public ImageCollection(String dir) { + this.images = buildImageMap(getDirectoryContent(dir)); } /** * Returns the image corresponding to the given id. * - * @throws java.util.NoSuchElementException if the requested image does not exists. * @return the image corresponding to the given id + * @throws java.util.NoSuchElementException if the requested image does not exists */ public Image image(int id) { - if (this.imageOrNull(id) == null) - throw new NoSuchElementException(); - else - return this.imageOrNull(id); + return this.images.get(id); } /** * Returns the image corresponding to the given id. * - * @return the image corresponding to the given id or null + * @return the image corresponding to the given id or null if not found */ - public Image imageOrNull(int id){ - // Ugly! Probably a better way... - if (mapFilesId().containsKey(id)) - try { - return ImageIO.read(mapFilesId().get(id)); - } catch (java.io.IOException e) { - return null; - } - else + public Image imageOrNull(int id) { + try { + return this.image(id); + } catch (NoSuchElementException e) { return null; + } } + } diff --git a/src/ch/epfl/xblast/client/painter/ScorePainter.java b/src/ch/epfl/xblast/client/painter/ScorePainter.java new file mode 100644 index 0000000..e412dd8 --- /dev/null +++ b/src/ch/epfl/xblast/client/painter/ScorePainter.java @@ -0,0 +1,107 @@ +package ch.epfl.xblast.client.painter; + +import ch.epfl.xblast.PlayerID; +import ch.epfl.xblast.client.GameState; +import ch.epfl.xblast.client.ImageCollection; + +import java.awt.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * The score painter. + * + * @author Pacien TRAN-GIRARD (261948) + * @author Timothée FLOURE (257420) + */ +public class ScorePainter { + + private static final int SCORE_SEPARATOR_SIZE = 8; + private static final int PLAYER_IMG_MULTIPLIER_OFFSET = 2; + private static final int PLAYER_IMG_DEAD_OFFSET = 1; + + private static final Image TEXT_MIDDLE_IMG = ImageCollection.SCORES_IMAGES.imageOrNull(10); + private static final Image TEXT_RIGHT_IMG = ImageCollection.SCORES_IMAGES.imageOrNull(11); + private static final Image VOID_IMG = ImageCollection.SCORES_IMAGES.imageOrNull(12); + + private ScorePainter() { + // Static class + } + + /** + * Builds the score panel for the given players. + * + * @param players the players + * @return the score panel + */ + public static List buildScorePanel(List players) { + if (Objects.isNull(players)) + throw new IllegalArgumentException(); + + return players.stream() + .flatMap(p -> Stream.concat( + buildPlayerScorePanel(p).stream(), + p.id().ordinal() % 2 == 0 ? buildSeparator().stream() : Stream.empty())) + .collect(Collectors.toList()); + } + + /** + * Builds the score panel for the given player. + * + * @param p the player + * @return the score panel + */ + private static List buildPlayerScorePanel(GameState.Player p) { + if (Objects.isNull(p)) + throw new IllegalArgumentException(); + + return Arrays.asList( + ImageCollection.PLAYERS_IMAGES.imageOrNull(imageIDForPlayer(p)), + TEXT_MIDDLE_IMG, + TEXT_RIGHT_IMG); + } + + /** + * Returns the image ID for the given player. + * + * @param p the player + * @return the image ID + */ + private static int imageIDForPlayer(GameState.Player p) { + return imageIDOffsetForPlayerID(p.id()) + imageIDOffsetForLifeState(p.lives()); + } + + /** + * Returns the image ID offset corresponding to a player ID. + * + * @param pid the player ID + * @return the image ID offset + */ + private static int imageIDOffsetForPlayerID(PlayerID pid) { + return pid.ordinal() * PLAYER_IMG_MULTIPLIER_OFFSET; + } + + /** + * Returns the image ID offset corresponding to a life state. + * + * @param lives the number of remaining lives + * @return the image ID offset + */ + private static int imageIDOffsetForLifeState(int lives) { + return lives == 0 ? PLAYER_IMG_DEAD_OFFSET : 0; + } + + /** + * Builds and returns a separator image sequence. + * + * @return the separator + */ + private static List buildSeparator() { + return Collections.nCopies(SCORE_SEPARATOR_SIZE, VOID_IMG); + } + +} diff --git a/src/ch/epfl/xblast/client/painter/TimeLinePainter.java b/src/ch/epfl/xblast/client/painter/TimeLinePainter.java new file mode 100644 index 0000000..7912d88 --- /dev/null +++ b/src/ch/epfl/xblast/client/painter/TimeLinePainter.java @@ -0,0 +1,39 @@ +package ch.epfl.xblast.client.painter; + +import ch.epfl.xblast.Lists; +import ch.epfl.xblast.client.ImageCollection; +import ch.epfl.xblast.server.Ticks; + +import java.awt.*; +import java.util.Collections; +import java.util.List; + +/** + * The time line painter. + * + * @author Pacien TRAN-GIRARD (261948) + */ +public class TimeLinePainter { + + private static final int TIME_LINE_SIZE = Ticks.GAME_DURATION / 2; + + private static final Image LED_OFF_IMG = ImageCollection.TIME_IMAGES.imageOrNull(20); + private static final Image LED_ON_IMG = ImageCollection.TIME_IMAGES.imageOrNull(21); + + private TimeLinePainter() { + // Static class + } + + /** + * Builds the time line image list + * + * @param t the remaining time + * @return the list of images composing the time "line" + */ + public static List buildTimeLine(int t) { + return Lists.concatenated( + Collections.nCopies(t, LED_ON_IMG), + Collections.nCopies(TIME_LINE_SIZE - t, LED_OFF_IMG)); + } + +} -- cgit v1.2.3