/** * When saving a map, we are looping through the map that was built and adding the fixed roads to * a new map. To prevent adding the same bits of roads again, delete all the bits from the already * added road * * @param oldMap the map from which to delete some number of components * @param start the start coordinate from where to begin deleting components * @param end the end coordinate to which we must delete all components */ private void deleteFromOldMap(Map oldMap, Coordinate start, Coordinate end) { int startX = start.getX(); int startY = start.getY(); int endX = end.getX(); int endY = end.getY(); if (startY == endY) { // horizontal for (int i = startX; i <= endX; i++) { oldMap.clearCell(new Coordinate(i, startY)); } } else { // vertical for (int i = startY; i <= endY; i++) { oldMap.clearCell(new Coordinate(startX, i)); } } }
/** * Adds a "grass" component to the map, aka a null component Useful if the user wants to delete a * map component they placed in the map * * @param x the x coordinate where to add the grass * @param y the y coordinate where to add the grass * @param map the map to add the grass to * @param mapGridGUIDecorator the GUI decorator associated with this map * @param mapGridPane the gridPane that would need to be updated with the new view * @param imgView the associated image to place in the x,y cell */ private void addGrass( int x, int y, Map map, MapGridGUIDecorator mapGridGUIDecorator, GridPane mapGridPane, ImageView imgView) { Coordinate coord = new Coordinate(x, y); map.clearCell(coord); StackPane sp = mapGridGUIDecorator.redrawCell(x, y, mapGridPane); sp.setOnMouseClicked( click -> { ComponentType currentFocused = MapMakerController.getCurrentFocused(); if (currentFocused == ComponentType.INTERSECTION) { addIntersection(x, y, map, mapGridGUIDecorator, mapGridPane, intersectionImgView); } else if (currentFocused == ComponentType.ROADNS) { addRoadNS(x, y, map, mapGridGUIDecorator, mapGridPane, roadNSImgView); } else if (currentFocused == ComponentType.ROADEW) { addRoadEW(x, y, map, mapGridGUIDecorator, mapGridPane, roadEWImgView); } }); // put focus back on Grass MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.GRASS); imgView.requestFocus(); }
/** * Adds a section of road with 2 lanes that travels in the directions east and west * * @param x the x coordinate where to add the road * @param y the y coordinate where to add the road * @param map the map to add the road to * @param mapGridGUIDecorator the GUI decorator associated with this map * @param mapGridPane the gridPane that would need to be updated with the new view * @param imgView the associated image to place in the x,y cell */ private void addRoadEW( int x, int y, Map map, MapGridGUIDecorator mapGridGUIDecorator, GridPane mapGridPane, ImageView imgView) { Coordinate coord = new Coordinate(x, y); Road road = new Road(coord, coord); try { road.addLane(new Lane(coord, coord, MapDirection.EAST)); road.addLane(new Lane(coord, coord, MapDirection.WEST)); map.addRoad(road); StackPane sp = mapGridGUIDecorator.redrawCell(x, y, mapGridPane); sp.setOnMouseClicked( click -> { ComponentType currentFocused = MapMakerController.getCurrentFocused(); if (currentFocused == ComponentType.INTERSECTION) { addIntersection(x, y, map, mapGridGUIDecorator, mapGridPane, intersectionImgView); } else if (currentFocused == ComponentType.ROADNS) { addRoadNS(x, y, map, mapGridGUIDecorator, mapGridPane, roadNSImgView); } else if (currentFocused == ComponentType.GRASS) { addGrass(x, y, map, mapGridGUIDecorator, mapGridPane, grassImgView); } }); // put focus back on RoadEW MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.ROADEW); imgView.requestFocus(); } catch (Exception e) { e.printStackTrace(); } }
/** * Takes a map with disconnected Roads and Intersections and connects them. This is like * connecting nodes (intersections) to edges (roads) in a directed graph. * * @param fixed the map where components need to be connected */ private void assignIntersectionsToRoads(Map fixed) throws Exception { ArrayList<Intersection> intersections = fixed.getIntersections(); for (int i = 0; i < intersections.size(); i++) { Intersection current = intersections.get(i); Coordinate coord = current.getLocation(); int x = coord.getX(); int y = coord.getY(); Coordinate north = (y - 1 >= 0) ? new Coordinate(x, y - 1) : null; Coordinate south = (y + 1 < height) ? new Coordinate(x, y + 1) : null; Coordinate east = (x + 1 < width) ? new Coordinate(x + 1, y) : null; Coordinate west = (x - 1 >= 0) ? new Coordinate(x - 1, y) : null; if (north != null) { Component component = fixed.getAtLocation(north); if (component instanceof Road) { current.setNorthRoad((Road) component); } } if (south != null) { Component component = fixed.getAtLocation(south); if (component instanceof Road) { current.setSouthRoad((Road) component); } } if (east != null) { Component component = fixed.getAtLocation(east); if (component instanceof Road) { current.setEastRoad((Road) component); } } if (west != null) { Component component = fixed.getAtLocation(west); if (component instanceof Road) { current.setWestRoad((Road) component); } } current.setDefaultTrafficLightsForRoads(); } }
/** * Draws the MapMaker screen and displays it to the user * * @param primaryStage the stage to show it in * @throws Exception */ public void drawScreen(Stage primaryStage) throws Exception { // Create the base BorderPane for the whole window BorderPane borderPane = new BorderPane(); borderPane.setStyle("-fx-background-color: papayawhip"); // Add some instructions to the user String text = "Instructions:\n" + "1. Click on the map component that you would like to place in the map\n" + "2. Click on the place in the map where you want to place the component\n" + "3. Repeat until you built the map you want!\n" + "4. Hit the 'Save' button when you are done"; Label instructions = new Label(text); instructions.setFont(Font.font("Arial", FontWeight.BOLD, 12)); instructions.setPadding(new Insets(5, 5, 5, 5)); borderPane.setTop(instructions); // Create the blank Map Pane mapPane = new Pane(); Map map = new Map(width, height); MapGridGUIDecorator mapGridGUIDecorator = new MapGridGUIDecorator(map.getGrid()); ResizeFactor rf = ResizeFactor.getSuggestedResizeFactor(width, height); mapGridGUIDecorator.setResizeFactor(rf); GridPane mapGridPane = mapGridGUIDecorator.drawComponents(); mapGridPane.setPadding(new Insets(0, 0, 5, 5)); mapPane.getChildren().add(mapGridPane); borderPane.setCenter(mapPane); MapMakerController.setCurrentFocused(ComponentType.NOTHING); VBox sideComponents = new VBox(); /* Add "Components" label */ Label componentsLabel = new Label("Components"); componentsLabel.setFont(Font.font("Arial", FontWeight.EXTRA_BOLD, 14)); componentsLabel.setPadding(new Insets(15, 5, 0, 20)); sideComponents.getChildren().add(componentsLabel); /* Add Intersection square image */ VBox intersectionPane = new VBox(); Label intersectionLabel = new Label("Intersection"); intersectionLabel.setPadding(new Insets(5, 5, 0, 30)); intersectionLabel.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 12)); Image intersectionImg = new Image("IntersectionX.png", 60, 60, true, false); intersectionImgView = new ImageView(intersectionImg); StackPane intersectionStackPane = new StackPane(intersectionImgView); intersectionStackPane.setPadding(new Insets(0, 10, 10, 10)); intersectionPane.getChildren().add(intersectionLabel); intersectionPane.getChildren().add(intersectionStackPane); sideComponents.getChildren().add(intersectionPane); /* Add RoadNS square image */ VBox roadNSPane = new VBox(); Label roadNSLabel = new Label("Road (North-South)"); roadNSLabel.setPadding(new Insets(5, 5, 0, 15)); roadNSLabel.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 12)); Image roadNSImg = new Image("RoadBackgroundNS.png", 60, 60, true, false); roadNSImgView = new ImageView(roadNSImg); StackPane roadNSStackPane = new StackPane(roadNSImgView); roadNSStackPane.setPadding(new Insets(0, 10, 10, 10)); roadNSPane.getChildren().add(roadNSLabel); roadNSPane.getChildren().add(roadNSStackPane); sideComponents.getChildren().add(roadNSPane); /* Add RoadEW square image */ VBox roadEWPane = new VBox(); Label roadEWLabel = new Label("Road (East-West)"); roadEWLabel.setPadding(new Insets(5, 5, 0, 15)); roadEWLabel.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 12)); Image roadEWImg = new Image("RoadBackgroundEW.png", 60, 60, true, false); roadEWImgView = new ImageView(roadEWImg); StackPane roadEWStackPane = new StackPane(roadEWImgView); roadEWStackPane.setPadding(new Insets(0, 10, 10, 10)); roadEWPane.getChildren().add(roadEWLabel); roadEWPane.getChildren().add(roadEWStackPane); sideComponents.getChildren().add(roadEWPane); /* Add Grass square image to empty out cells */ VBox grassPane = new VBox(); Label grassLabel = new Label("Grass (clear square)"); grassLabel.setPadding(new Insets(5, 5, 0, 15)); grassLabel.setFont(Font.font("Arial", FontWeight.SEMI_BOLD, 12)); Image grassImg = new Image("Grass.png", 60, 60, true, false); grassImgView = new ImageView(grassImg); StackPane grassStackPane = new StackPane(grassImgView); grassStackPane.setPadding(new Insets(0, 10, 10, 10)); grassPane.getChildren().add(grassLabel); grassPane.getChildren().add(grassStackPane); sideComponents.getChildren().add(grassPane); /* Add Save, Reset buttons */ VBox buttonsPane = new VBox(); buttonsPane.setPadding(new Insets(0, 0, 0, 10)); Label toolsLabel = new Label("Tools"); toolsLabel.setFont(Font.font("Arial", FontWeight.EXTRA_BOLD, 14)); toolsLabel.setPadding(new Insets(15, 5, 5, 35)); buttonsPane.getChildren().add(toolsLabel); Insets padding = new Insets(0, 0, 5, 0); Button saveButton = new Button("Save Map"); StackPane saveButtonPane = new StackPane(saveButton); saveButtonPane.setPadding(padding); saveButton.setStyle("-fx-base:Gold"); saveButton.setFont(Font.font("System Bold Italic", FontWeight.BOLD, 13)); buttonsPane.getChildren().add(saveButtonPane); Button resetButton = new Button("Reset Map"); resetButton.setStyle("-fx-base:Gold"); resetButton.setFont(Font.font("System Bold Italic", FontWeight.BOLD, 13)); StackPane resetButtonPane = new StackPane(resetButton); resetButtonPane.setPadding(padding); buttonsPane.getChildren().add(resetButtonPane); Button backButton = new Button("Go Back"); backButton.setStyle("-fx-base:Gold"); backButton.setFont(Font.font("System Bold Italic", FontWeight.BOLD, 13)); StackPane backButtonPane = new StackPane(backButton); backButtonPane.setPadding(padding); buttonsPane.getChildren().add(backButtonPane); sideComponents.getChildren().add(buttonsPane); Ticker.start(); /* Add click processing for Map grid squares */ for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { Node current = getNodeFromIndex(i, j, mapGridPane); final int x = j; final int y = i; current.setOnMouseClicked( (MouseEvent click) -> { MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.MAP_SQUARE); current.requestFocus(); }); current .focusedProperty() .addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { ComponentType previous = MapMakerController.getPreviousFocused(); if (previous == ComponentType.INTERSECTION) { addIntersection( x, y, map, mapGridGUIDecorator, mapGridPane, intersectionImgView); } else if (previous == ComponentType.ROADNS) { addRoadNS(x, y, map, mapGridGUIDecorator, mapGridPane, roadNSImgView); } else if (previous == ComponentType.ROADEW) { addRoadEW(x, y, map, mapGridGUIDecorator, mapGridPane, roadEWImgView); } else if (previous == ComponentType.GRASS) { addGrass(x, y, map, mapGridGUIDecorator, mapGridPane, grassImgView); } }); } } /* Add intersection icon click processing */ DropShadow ds = new DropShadow(15, Color.BLUE); intersectionImgView.setOnMouseClicked( click -> { MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.INTERSECTION); intersectionImgView.requestFocus(); }); intersectionImgView .focusedProperty() .addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (newValue) intersectionImgView.setEffect(ds); else intersectionImgView.setEffect(null); }); /* Add roadNS icon click processing */ roadNSImgView.setOnMouseClicked( click -> { MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.ROADNS); roadNSImgView.requestFocus(); }); roadNSImgView .focusedProperty() .addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (newValue) roadNSImgView.setEffect(ds); else roadNSImgView.setEffect(null); }); /* Add roadEW icon click processing */ roadEWImgView.setOnMouseClicked( click -> { MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.ROADEW); roadEWImgView.requestFocus(); }); roadEWImgView .focusedProperty() .addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (newValue) roadEWImgView.setEffect(ds); else roadEWImgView.setEffect(null); }); /* Add grass icon click processing */ grassImgView.setOnMouseClicked( click -> { MapMakerController.setPreviousFocused(MapMakerController.getCurrentFocused()); MapMakerController.setCurrentFocused(ComponentType.GRASS); grassImgView.requestFocus(); }); grassImgView .focusedProperty() .addListener( (ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> { if (newValue) grassImgView.setEffect(ds); else grassImgView.setEffect(null); }); /* Add save button functionality */ saveButton.setOnMouseClicked( click -> { TextInputDialog nameDialog = new TextInputDialog(); nameDialog.setTitle("Save Map"); nameDialog.setHeaderText( "Please provide a name for your map (no spaces or special characters).\nSaved maps go into the /maps directory of your working directory."); nameDialog.setContentText("File name"); Button btOk = (Button) nameDialog.getDialogPane().lookupButton(ButtonType.OK); TextField textfield = nameDialog.getEditor(); Platform.runLater(() -> textfield.requestFocus()); btOk.setDisable(true); textfield .textProperty() .addListener( ((observable, oldValue, newValue) -> { btOk.setDisable(newValue.trim().isEmpty()); })); Optional<String> result = nameDialog.showAndWait(); result.ifPresent( name -> { name = name.concat(".map"); try { Map finalMap = buildAndSaveMap(map); finalMap.saveMap(name); goBack(primaryStage); } catch (Exception e) { e.printStackTrace(); } }); }); resetButton.setOnMouseClicked( click -> { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Component component = map.getAtLocation(new Coordinate(x, y)); if (component instanceof Road || component instanceof Intersection) { addGrass(x, y, map, mapGridGUIDecorator, mapGridPane, grassImgView); } } } }); backButton.setOnMouseClicked( click -> { try { goBack(primaryStage); } catch (Exception e) { e.printStackTrace(); } }); borderPane.setRight(sideComponents); Scene scene = new Scene(borderPane); primaryStage.setScene(scene); primaryStage.centerOnScreen(); primaryStage.setResizable(false); }
/** * Build the map that the user drew into a complete and connected map * * @param map the map that the user built * @return a fixed map that has all roads and intersections connected * @throws Exception */ private Map buildAndSaveMap(Map map) throws Exception { System.out.println("Building and saving map..."); int width = map.getWidth(); int height = map.getHeight(); Map fixed = new Map(width, height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Component current = map.getGrid().get(x, y); if (current instanceof Intersection) { Coordinate location = new Coordinate(x, y); Intersection i = new Intersection(location); fixed.addIntersection(i); deleteFromOldMap(map, location, location); } else if (current instanceof Road) { Road road = (Road) current; Coordinate lastKnownCoord = road.getEndLocation(); if (road.runsVertically()) { if (lastKnownCoord.getY() != height - 1) { Component next = map.getGrid().get(x, y++); while (next != null && next instanceof Road) { lastKnownCoord = ((Road) next).getEndLocation(); if (y == height) break; next = map.getGrid().get(x, y++); } y = road.getStartLocation().getY(); // go back to the row we started at } Coordinate start = road.getStartLocation(); Coordinate end = lastKnownCoord; Road newRoad = new Road(start, end); newRoad.addLane(new Lane(end, start, MapDirection.NORTH)); newRoad.addLane(new Lane(start, end, MapDirection.SOUTH)); fixed.addRoad(newRoad); deleteFromOldMap(map, start, end); } else { if (lastKnownCoord.getX() != width - 1) { Component next = map.getGrid().get(x++, y); while (next != null && next instanceof Road) { lastKnownCoord = ((Road) next).getEndLocation(); if (x == width) break; next = map.getGrid().get(x++, y); } x = x - 2; // we overshot by 1, so go back, and loop will increment, so go back // another } Coordinate start = road.getStartLocation(); Coordinate end = lastKnownCoord; Road newRoad = new Road(start, end); newRoad.addLane(new Lane(start, end, MapDirection.EAST)); newRoad.addLane(new Lane(end, start, MapDirection.WEST)); fixed.addRoad(newRoad); deleteFromOldMap(map, start, end); } } } } assignIntersectionsToRoads(fixed); return fixed; }