From 4ae422b47b8aa9974867b76ee27896753aca8402 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Jul 2017 21:50:23 +1200 Subject: [PATCH 1/4] When a player connects to the server, lobby will now show their user id and profile instead of showing all the profile gif at the beginning, #story[1055] #pair[hyi25, zyt10] --- .../seng302/client/ClientToServerThread.java | 2 +- .../seng302/controllers/LobbyController.java | 65 +++++++------------ 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/src/main/java/seng302/client/ClientToServerThread.java b/src/main/java/seng302/client/ClientToServerThread.java index ba5caf37..1e76bd07 100644 --- a/src/main/java/seng302/client/ClientToServerThread.java +++ b/src/main/java/seng302/client/ClientToServerThread.java @@ -153,7 +153,7 @@ public class ClientToServerThread implements Runnable { try { socket.close(); } catch (IOException e) { - System.out.println("IO error in server thread upon trying to close socket"); + clientLog("Failed to close the socket", 0); } } diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java index af2b0793..5e5c4aea 100644 --- a/src/main/java/seng302/controllers/LobbyController.java +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -1,10 +1,6 @@ package seng302.controllers; import java.io.IOException; -import java.io.InputStream; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.NetworkInterface; import java.net.URL; import java.util.*; @@ -84,6 +80,10 @@ public class LobbyController implements Initializable, Observer{ private static ObservableList seventhCompetitor = FXCollections.observableArrayList(); private static ObservableList eighthCompetitor = FXCollections.observableArrayList(); private ClientStateQueryingRunnable clientStateQueryingRunnable; + private static List gifImageViews; + private static List listViews; + + private int MAX_NUM_PLAYERS = 8; private Boolean switchedPane = false; private MainServerThread mainServerThread; @@ -113,8 +113,18 @@ public class LobbyController implements Initializable, Observer{ lobbyIpText.setText("Connected to IP: "); readyButton.setDisable(true); } + + gifImageViews = new ArrayList<>(); + Collections.addAll(gifImageViews, firstImageView, secondImageView, thirdImageView, fourthImageView, + fifthImageView, sixthImageView, seventhImageView, eighthImageView); + listViews = new ArrayList<>(); + Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView, + sixthListView, seventhListView, eighthListView); + competitors = new ArrayList<>(); + Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor, + fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor); + initialiseListView(); -// initialiseLobbyControllerThread(); initialiseImageView(); // parrot gif init // set up client state query thread, so that when it receives the race-started packet @@ -142,45 +152,16 @@ public class LobbyController implements Initializable, Observer{ } private void initialiseListView() { - firstListView.getItems().clear(); - secondListView.getItems().clear(); - thirdListView.getItems().clear(); - fourthListView.getItems().clear(); - fifthListView.getItems().clear(); - sixthListView.getItems().clear(); - seventhListView.getItems().clear(); - eighthListView.getItems().clear(); + listViews.forEach(listView -> listView.getItems().clear()); + gifImageViews.forEach(gif -> gif.setVisible(false)); + competitors.forEach(ol -> ol.removeAll()); - competitors = new ArrayList<>(); - Collections.addAll(competitors, firstCompetitor, secondCompetitor, thirdCompetitor, - fourthCompetitor, fifthCompetitor, sixthCompetitor, seventhCompetitor, eighthCompetitor); - - for (ObservableList ol : competitors) { - ol.removeAll(); + List ids = new ArrayList<>(ClientState.getBoats().keySet()); + for (int i = 0; i < ids.size(); i++) { + competitors.get(i).add(String.format("Player ID: %d", ids.get(i))); + listViews.get(i).setItems(competitors.get(i)); + gifImageViews.get(i).setVisible(true); } - - firstCompetitor.add(ClientState.getClientSourceId()); - - int competitorIndex = 1; - for (Integer yachtId : ClientState.getBoats().keySet()) { - // break if there are more than 7 competitors - if (competitorIndex >= 8) { - break; - } - if (!yachtId.equals(Integer.parseInt(ClientState.getClientSourceId()))) { - competitors.get(competitorIndex).add(String.valueOf(yachtId)); - competitorIndex++; - } - } - - firstListView.setItems(firstCompetitor); - secondListView.setItems(secondCompetitor); - thirdListView.setItems(thirdCompetitor); - fourthListView.setItems(fourthCompetitor); - fifthListView.setItems(fifthCompetitor); - sixthListView.setItems(sixthCompetitor); - seventhListView.setItems(seventhCompetitor); - eighthListView.setItems(eighthCompetitor); } private void initialiseLobbyControllerThread() { From a56dac1e87777ea96e61076dd08cba2d4edf010b Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 25 Jul 2017 22:19:03 +1200 Subject: [PATCH 2/4] Polar velocities should now work as intended. Snapping to VMG still needs to be implemented. Still an issue of not being able to pass the total upwind or downwind point tags: #story[986] --- .../seng302/fxObjects/BoatAnnotations.java | 2 +- .../java/seng302/gameServer/GameState.java | 21 ++++++---- .../gameServer/ServerToClientThread.java | 14 ++----- src/main/java/seng302/models/Yacht.java | 40 +++++++++++++------ .../server/messages/BoatLocationMessage.java | 1 - 5 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/main/java/seng302/fxObjects/BoatAnnotations.java b/src/main/java/seng302/fxObjects/BoatAnnotations.java index c8848418..26931ce9 100644 --- a/src/main/java/seng302/fxObjects/BoatAnnotations.java +++ b/src/main/java/seng302/fxObjects/BoatAnnotations.java @@ -83,7 +83,7 @@ public class BoatAnnotations extends Group{ } void update () { - velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocity()))); + velocityObject.setText(String.format(String.format("%.2f m/s", boat.getVelocityMMS()))); if (boat.getTimeTillNext() != null) { DateFormat format = new SimpleDateFormat("mm:ss"); diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 90c649ca..326caf52 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -2,6 +2,7 @@ package seng302.gameServer; import java.util.*; +import seng302.client.ClientPacketParser; import seng302.models.Player; import seng302.models.Yacht; @@ -80,10 +81,14 @@ public class GameState { return windDirection; } - public static Double getWindSpeed() { + public static Double getWindSpeedMMS() { return windSpeed; } + public static Double getWindSpeedKnots() { + return windSpeed / 1000 * ClientPacketParser.MS_TO_KNOTS; + } + public static Map getYachts() { return yachts; } @@ -93,6 +98,7 @@ public class GameState { // System.out.println("-----------------------"); switch (actionType) { case VMG: + playerYacht.turnToVMG(); // System.out.println("Snapping to VMG"); // TODO: 22/07/17 wmu16 - Add in the vmg calculation code here break; @@ -118,12 +124,13 @@ public class GameState { break; } -// System.out.println("-----------------------"); -// System.out.println("Heading: " + playerYacht.getHeading()); -// System.out.println("Sails are in: " + playerYacht.getSailIn()); -// System.out.println("Lat: " + playerYacht.getLocation().getLat()); -// System.out.println("Lng: " + playerYacht.getLocation().getLng()); -// System.out.println("-----------------------\n"); + System.out.println("-----------------------"); + System.out.println("Sails are in: " + playerYacht.getSailIn()); + System.out.println("Heading: " + playerYacht.getHeading()); + System.out.println("Velocity: " + playerYacht.getVelocityMMS() / 1000); + System.out.println("Lat: " + playerYacht.getLocation().getLat()); + System.out.println("Lng: " + playerYacht.getLocation().getLng()); + System.out.println("-----------------------\n"); } public static void update() { diff --git a/src/main/java/seng302/gameServer/ServerToClientThread.java b/src/main/java/seng302/gameServer/ServerToClientThread.java index 0fd3768f..5931aa40 100644 --- a/src/main/java/seng302/gameServer/ServerToClientThread.java +++ b/src/main/java/seng302/gameServer/ServerToClientThread.java @@ -12,10 +12,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Observable; import java.util.Observer; -import java.util.Random; import java.util.zip.CRC32; import java.util.zip.Checksum; -import org.apache.commons.io.IOUtils; + import seng302.models.Player; import seng302.models.Yacht; import seng302.models.stream.packets.PacketType; @@ -29,18 +28,11 @@ import seng302.server.messages.BoatStatus; import seng302.server.messages.BoatSubMessage; import seng302.server.messages.Message; -import java.io.*; -import java.net.Socket; -import java.util.zip.CRC32; -import java.util.zip.Checksum; import seng302.server.messages.RaceStatus; import seng302.server.messages.RaceStatusMessage; import seng302.server.messages.RaceType; import seng302.server.messages.XMLMessage; import seng302.server.messages.XMLMessageSubType; -import seng302.server.messages.XMLMessage; -import seng302.server.messages.XMLMessageSubType; -import seng302.utilities.GeoPoint; /** * A class describing a single connection to a Client for the purposes of sending and receiving on @@ -306,7 +298,7 @@ public class ServerToClientThread implements Runnable, Observer { yacht.getLocation().getLat(), yacht.getLocation().getLng(), yacht.getHeading(), - (long) yacht.getVelocity()); + (long) yacht.getVelocityMMS()); sendMessage(boatLocationMessage); } @@ -348,7 +340,7 @@ public class ServerToClientThread implements Runnable, Observer { } sendMessage(new RaceStatusMessage(1, raceStatus, startTime, GameState.getWindDirection(), - GameState.getWindSpeed().longValue(), GameState.getPlayers().size(), + GameState.getWindSpeedMMS().longValue(), GameState.getPlayers().size(), RaceType.MATCH_RACE, 1, boatSubMessages)); } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index 443c9e55..5cb9b837 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -4,6 +4,7 @@ import static seng302.utilities.GeoUtility.getGeoCoordinate; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import javafx.scene.paint.Color; import seng302.client.ClientPacketParser; @@ -110,8 +111,8 @@ public class Yacht { this.position = "-"; this.sailIn = false; this.location = new GeoPoint(57.670341, 11.826856); - this.heading = 120.0; - this.velocity = 50000.0; + this.heading = 120.0; //In degrees + this.velocity = 0d; //in mms-1 } /** @@ -120,13 +121,14 @@ public class Yacht { public void update(Long timeInterval) { if (sailIn) { Double secondsElapsed = timeInterval / 1000000.0; - Double thisHeading = ((double) Math.floorMod(heading.longValue(), 360L)); - Double windSpeedKnots = 0d; - Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, thisHeading); - velocity = boatSpeedInKnots / ClientPacketParser.MS_TO_KNOTS * 3000; - //System.out.println("velocity = " + velocity); + Double windSpeedKnots = GameState.getWindSpeedKnots(); + Double trueWindAngle = Math.abs(GameState.getWindDirection() - heading); + Double boatSpeedInKnots = PolarTable.getBoatSpeed(windSpeedKnots, trueWindAngle); + velocity = boatSpeedInKnots / ClientPacketParser.MS_TO_KNOTS * 1000; Double metersCovered = velocity * secondsElapsed; location = getGeoCoordinate(location, heading, metersCovered); + } else { + velocity = 0d; } } @@ -136,13 +138,16 @@ public class Yacht { } public void adjustHeading(Double amount) { + Double newVal = heading + amount; lastHeading = heading; // TODO: 24/07/17 wmu16 - '%' in java does remainder, we need modulo. All this must be changed here, this is why we have neg values! - heading = (heading + amount) % 360.0; + heading = (double) Math.floorMod(newVal.longValue(), 360L); } public void tackGybe(Double windDirection) { - adjustHeading(-2 * ((heading - windDirection) % 360)); + Double normalizedHeading = heading - GameState.windDirection; + normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360); + adjustHeading(-2 * normalizedHeading); } public void toggleSailIn() { @@ -150,7 +155,8 @@ public class Yacht { } public void turnUpwind() { - Double normalizedHeading = (heading - GameState.windDirection) % 360; + Double normalizedHeading = heading - GameState.windDirection; + normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360); if (normalizedHeading == 0) { if (lastHeading < 180) { adjustHeading(-TURN_STEP); @@ -171,7 +177,8 @@ public class Yacht { } public void turnDownwind() { - Double normalizedHeading = (heading - GameState.windDirection) % 360; + Double normalizedHeading = heading - GameState.windDirection; + normalizedHeading = (double) Math.floorMod(normalizedHeading.longValue(), 360); if (normalizedHeading == 0) { if (lastHeading < 180) { adjustHeading(TURN_STEP); @@ -191,6 +198,11 @@ public class Yacht { } } + public void turnToVMG() { + // TODO: 25/07/17 wmu16 - Fix this so it grabs the optimal value from the optimal Polar + } + + public String getBoatType() { return boatType; @@ -294,10 +306,14 @@ public class Yacht { this.markRoundTime = markRoundingTime; } - public double getVelocity() { + public double getVelocityMMS() { return velocity; } + public Double getVelocityKnots() { + return velocity / 1000 * ClientPacketParser.MS_TO_KNOTS; + } + public Long getTimeTillNext() { return timeTillNext; } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index b4b273a4..ec0a4c0e 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -39,7 +39,6 @@ public class BoatLocationMessage extends Message { * @param boatSpeed The boats speed */ public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ - boatSpeed /= 10; messageVersionNumber = 1; time = System.currentTimeMillis(); this.sourceId = sourceId; From 37c745c139c9e9eab9fdcd6ee366a5206324127c Mon Sep 17 00:00:00 2001 From: William Muir Date: Tue, 25 Jul 2017 22:27:26 +1200 Subject: [PATCH 3/4] Polar velocities should now work as intended. Snapping to VMG still needs to be implemented. Still an issue of not being able to pass the total upwind or downwind point tags: #story[986] --- .../seng302/controllers/LobbyController.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java index 5e5c4aea..32578ac3 100644 --- a/src/main/java/seng302/controllers/LobbyController.java +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -17,8 +17,6 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import javafx.scene.media.Media; -import javafx.scene.media.MediaPlayer; import javafx.scene.text.Text; import seng302.client.ClientState; import seng302.client.ClientStateQueryingRunnable; @@ -210,29 +208,29 @@ public class LobbyController implements Initializable, Observer{ @FXML public void readyButtonPressed() { // setContentPane("/views/RaceView.fxml"); - playTheme(); +// playTheme(); GameState.setCurrentStage(GameStages.RACING); mainServerThread.startGame(); } - private static MediaPlayer mediaPlayer; - - private void playTheme() { - Random random = new Random(System.currentTimeMillis()); - Integer rand = random.nextInt(); - if(rand == 10) { - URL file = getClass().getResource("/music/Disturbed - down with the sickness.mp3"); - Media hit = new Media(file.toString()); - mediaPlayer = new MediaPlayer(hit); - mediaPlayer.play(); - } else if(rand == 9) { - URL file = getClass().getResource("/music/Owl City - Fireflies.mp3"); - Media hit = new Media(file.toString()); - mediaPlayer = new MediaPlayer(hit); - mediaPlayer.play(); - } - } +// private static MediaPlayer mediaPlayer; +// +// private void playTheme() { +// Random random = new Random(System.currentTimeMillis()); +// Integer rand = random.nextInt(); +// if(rand == 10) { +// URL file = getClass().getResource("/music/Disturbed - down with the sickness.mp3"); +// Media hit = new Media(file.toString()); +// mediaPlayer = new MediaPlayer(hit); +// mediaPlayer.play(); +// } else if(rand == 9) { +// URL file = getClass().getResource("/music/Owl City - Fireflies.mp3"); +// Media hit = new Media(file.toString()); +// mediaPlayer = new MediaPlayer(hit); +// mediaPlayer.play(); +// } +// } private void switchToRaceView() { if (!switchedPane) { From 0135426dfea8a4efd183264838691bde14843332 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Jul 2017 23:05:52 +1200 Subject: [PATCH 4/4] Changed lobby player profile to a lower risk pictures #story[1055] --- .../seng302/controllers/LobbyController.java | 60 +++++++----------- .../{ParrotGif => pics}/alistair.gif | Bin .../resources/{ParrotGif => pics}/calum.gif | Bin .../resources/{ParrotGif => pics}/haoming.gif | Bin .../resources/{ParrotGif => pics}/kusal.gif | Bin .../resources/{ParrotGif => pics}/michael.gif | Bin .../resources/{ParrotGif => pics}/parrot.gif | Bin .../resources/{ParrotGif => pics}/peter.gif | Bin .../resources/{ParrotGif => pics}/ryan.gif | Bin src/main/resources/pics/sail.png | Bin 0 -> 9080 bytes .../resources/{ParrotGif => pics}/will.gif | Bin 11 files changed, 24 insertions(+), 36 deletions(-) rename src/main/resources/{ParrotGif => pics}/alistair.gif (100%) rename src/main/resources/{ParrotGif => pics}/calum.gif (100%) rename src/main/resources/{ParrotGif => pics}/haoming.gif (100%) rename src/main/resources/{ParrotGif => pics}/kusal.gif (100%) rename src/main/resources/{ParrotGif => pics}/michael.gif (100%) rename src/main/resources/{ParrotGif => pics}/parrot.gif (100%) rename src/main/resources/{ParrotGif => pics}/peter.gif (100%) rename src/main/resources/{ParrotGif => pics}/ryan.gif (100%) create mode 100644 src/main/resources/pics/sail.png rename src/main/resources/{ParrotGif => pics}/will.gif (100%) diff --git a/src/main/java/seng302/controllers/LobbyController.java b/src/main/java/seng302/controllers/LobbyController.java index 5e5c4aea..b96a39b3 100644 --- a/src/main/java/seng302/controllers/LobbyController.java +++ b/src/main/java/seng302/controllers/LobbyController.java @@ -80,7 +80,7 @@ public class LobbyController implements Initializable, Observer{ private static ObservableList seventhCompetitor = FXCollections.observableArrayList(); private static ObservableList eighthCompetitor = FXCollections.observableArrayList(); private ClientStateQueryingRunnable clientStateQueryingRunnable; - private static List gifImageViews; + private static List imageViews; private static List listViews; private int MAX_NUM_PLAYERS = 8; @@ -114,8 +114,8 @@ public class LobbyController implements Initializable, Observer{ readyButton.setDisable(true); } - gifImageViews = new ArrayList<>(); - Collections.addAll(gifImageViews, firstImageView, secondImageView, thirdImageView, fourthImageView, + imageViews = new ArrayList<>(); + Collections.addAll(imageViews, firstImageView, secondImageView, thirdImageView, fourthImageView, fifthImageView, sixthImageView, seventhImageView, eighthImageView); listViews = new ArrayList<>(); Collections.addAll(listViews, firstListView, secondListView, thirdListView, fourthListView, fifthListView, @@ -153,49 +153,37 @@ public class LobbyController implements Initializable, Observer{ private void initialiseListView() { listViews.forEach(listView -> listView.getItems().clear()); - gifImageViews.forEach(gif -> gif.setVisible(false)); + imageViews.forEach(gif -> gif.setVisible(false)); competitors.forEach(ol -> ol.removeAll()); List ids = new ArrayList<>(ClientState.getBoats().keySet()); for (int i = 0; i < ids.size(); i++) { competitors.get(i).add(String.format("Player ID: %d", ids.get(i))); listViews.get(i).setItems(competitors.get(i)); - gifImageViews.get(i).setVisible(true); + imageViews.get(i).setVisible(true); } } - private void initialiseLobbyControllerThread() { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - Platform.runLater(new Runnable() { - @Override - public void run() { - - } - }); - } - }); - thread.start(); - } - private void initialiseImageView() { - Image image1 = new Image(getClass().getResourceAsStream("/ParrotGif/alistair.gif")); - firstImageView.setImage(image1); - Image image2 = new Image(getClass().getResourceAsStream("/ParrotGif/calum.gif")); - secondImageView.setImage(image2); - Image image3 = new Image(getClass().getResourceAsStream("/ParrotGif/haoming.gif")); - thirdImageView.setImage(image3); - Image image4 = new Image(getClass().getResourceAsStream("/ParrotGif/kusal.gif")); - fourthImageView.setImage(image4); - Image image5 = new Image(getClass().getResourceAsStream("/ParrotGif/michael.gif")); - fifthImageView.setImage(image5); - Image image6 = new Image(getClass().getResourceAsStream("/ParrotGif/peter.gif")); - sixthImageView.setImage(image6); - Image image7 = new Image(getClass().getResourceAsStream("/ParrotGif/ryan.gif")); - seventhImageView.setImage(image7); - Image image8 = new Image(getClass().getResourceAsStream("/ParrotGif/will.gif")); - eighthImageView.setImage(image8); + for (int i = 0; i < MAX_NUM_PLAYERS; i++) { + imageViews.get(i).setImage(new Image(getClass().getResourceAsStream("/pics/sail.png"))); + } +// Image image1 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// firstImageView.setImage(image1); +// Image image2 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// secondImageView.setImage(image2); +// Image image3 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// thirdImageView.setImage(image3); +// Image image4 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// fourthImageView.setImage(image4); +// Image image5 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// fifthImageView.setImage(image5); +// Image image6 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// sixthImageView.setImage(image6); +// Image image7 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// seventhImageView.setImage(image7); +// Image image8 = new Image(getClass().getResourceAsStream("/pics/sail.png")); +// eighthImageView.setImage(image8); } @FXML diff --git a/src/main/resources/ParrotGif/alistair.gif b/src/main/resources/pics/alistair.gif similarity index 100% rename from src/main/resources/ParrotGif/alistair.gif rename to src/main/resources/pics/alistair.gif diff --git a/src/main/resources/ParrotGif/calum.gif b/src/main/resources/pics/calum.gif similarity index 100% rename from src/main/resources/ParrotGif/calum.gif rename to src/main/resources/pics/calum.gif diff --git a/src/main/resources/ParrotGif/haoming.gif b/src/main/resources/pics/haoming.gif similarity index 100% rename from src/main/resources/ParrotGif/haoming.gif rename to src/main/resources/pics/haoming.gif diff --git a/src/main/resources/ParrotGif/kusal.gif b/src/main/resources/pics/kusal.gif similarity index 100% rename from src/main/resources/ParrotGif/kusal.gif rename to src/main/resources/pics/kusal.gif diff --git a/src/main/resources/ParrotGif/michael.gif b/src/main/resources/pics/michael.gif similarity index 100% rename from src/main/resources/ParrotGif/michael.gif rename to src/main/resources/pics/michael.gif diff --git a/src/main/resources/ParrotGif/parrot.gif b/src/main/resources/pics/parrot.gif similarity index 100% rename from src/main/resources/ParrotGif/parrot.gif rename to src/main/resources/pics/parrot.gif diff --git a/src/main/resources/ParrotGif/peter.gif b/src/main/resources/pics/peter.gif similarity index 100% rename from src/main/resources/ParrotGif/peter.gif rename to src/main/resources/pics/peter.gif diff --git a/src/main/resources/ParrotGif/ryan.gif b/src/main/resources/pics/ryan.gif similarity index 100% rename from src/main/resources/ParrotGif/ryan.gif rename to src/main/resources/pics/ryan.gif diff --git a/src/main/resources/pics/sail.png b/src/main/resources/pics/sail.png new file mode 100644 index 0000000000000000000000000000000000000000..7335918c6a98f3a826a73dd6c52d1b083289c2b9 GIT binary patch literal 9080 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