Java-Adventure-Game / Java-Based Adventure Game main / AdventureGameViews / AdventureGameView.java
AdventureGameView.java
Raw
package AdventureGameViews;

import AdventureGameModel.AdventureGame;
import javafx.animation.PauseTransition;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.paint.Color;
import javafx.scene.layout.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.util.Duration;
import javafx.scene.AccessibleRole;
import java.io.IOException;
import java.util.Objects;

// background colour imports
import javafx.scene.control.ColorPicker;
import javafx.stage.FileChooser;
import java.io.File;
import javafx.scene.layout.BackgroundImage;
import javafx.scene.layout.BackgroundRepeat;
import javafx.scene.layout.BackgroundPosition;
import javafx.scene.layout.BackgroundSize;

/**
 * Class AdventureGameView
 *
 * Visualizes model in GUI. Class handles or makes references to all UI elements
 */
public class AdventureGameView {

    AdventureGame model; // model of the game
    Stage stage; // stage on which everything is rendered
    Button saveButton, loadButton, helpButton, menuButton; // buttons
    Boolean helpToggle = false; // check if help is on display
    GridPane gridPane = new GridPane(); // to hold images and buttons
    Label roomDescLabel = new Label(); // to hold room description and/or instructions
    VBox objectsInRoom = new VBox(); // to hold room items
    VBox objectsInInventory = new VBox(); // to hold inventory items
    ImageView roomImageView; // to hold room image
    TextField inputTextField; // for user input

    public MediaPlayer mediaPlayer; // to play audio
    public MediaPlayer musicPlayer; // to play music
    public boolean mediaPlaying; // to know if the audio is playing
    private boolean musicPlaying; // to know if music is playing

    /**
     * Adventure Game View Constructor
     * __________________________
     * Initializes attributes
     */
    public AdventureGameView(AdventureGame model, Stage stage) {
        this.model = model;
        this.stage = stage;
        intiUI(); // initializes the UI
    }

    /**
     * Initialize the UI
     */
    public void intiUI() {

        // setting up the stage
        this.stage.setTitle("Harsh Nair's Adventure Game");

        // Inventory + Room nodes
        objectsInInventory.setSpacing(10);
        objectsInInventory.setAlignment(Pos.TOP_CENTER);
        objectsInRoom.setSpacing(10);
        objectsInRoom.setAlignment(Pos.TOP_CENTER);

        // set up the grid pane, making Node elements transparent
        gridPane.setPadding(new Insets(20));
        Color transparentColor = Color.rgb(0, 0, 0, 0);
        gridPane.setBackground(new Background(new BackgroundFill(
                transparentColor,
                new CornerRadii(0),
                new Insets(0)
        )));

        // three columns, three rows for the GridPane
        ColumnConstraints column1 = new ColumnConstraints(150);
        ColumnConstraints column2 = new ColumnConstraints(650);
        ColumnConstraints column3 = new ColumnConstraints(150);
        column3.setHgrow( Priority.SOMETIMES ); // let some columns grow to take any extra space
        column1.setHgrow( Priority.SOMETIMES );

        // row constraints
        RowConstraints row1 = new RowConstraints();
        RowConstraints row2 = new RowConstraints( 550 );
        RowConstraints row3 = new RowConstraints();
        row1.setVgrow( Priority.SOMETIMES );
        row3.setVgrow( Priority.SOMETIMES );

        gridPane.getColumnConstraints().addAll( column1 , column2 , column1 );
        gridPane.getRowConstraints().addAll( row1 , row2 , row1 );

        // button creation
        saveButton = new Button("Save");
        saveButton.setId("Save");
        customizeButton(saveButton, 100, 50);
        makeButtonAccessible(saveButton, "Save Button", "This button saves the game.", "This button saves the game. Click it in order to save your current progress, so you can play more later.");
        addSaveEvent();

        loadButton = new Button("Load");
        loadButton.setId("Load");
        customizeButton(loadButton, 100, 50);
        makeButtonAccessible(loadButton, "Load Button", "This button loads a game from a file.", "This button loads the game from a file. Click it in order to load a game that you saved at a prior date.");
        addLoadEvent();

        helpButton = new Button("Instructions");
        helpButton.setId("Instructions");
        customizeButton(helpButton, 200, 50);
        makeButtonAccessible(helpButton, "Help Button", "This button gives game instructions.", "This button gives instructions on the game controls. Click it to learn how to play.");
        addInstructionEvent();

        // settings menu button
        menuButton = new Button("");
        menuButton.setId("Settings Menu");
        customizeButton(menuButton, 50, 50);
        makeButtonAccessible(menuButton, "Settings Button", "This button toggles the settings menu", "This button opens a settings menu that gives some additional settings options to players");
        Image gear = new Image("Games/TinyGame/gear.png");
        ImageView gearView = new ImageView(gear);
        gearView.setFitHeight(35);
        gearView.setFitWidth(35);
        menuButton.setGraphic(gearView);
        addSettingsEvent();

        HBox topButtons = new HBox();
        topButtons.getChildren().addAll(saveButton, helpButton, loadButton, menuButton);
        topButtons.setSpacing(10);
        topButtons.setAlignment(Pos.CENTER);

        inputTextField = new TextField();
        inputTextField.setFont(new Font("Arial", 16));
        inputTextField.setFocusTraversable(true);

        inputTextField.setAccessibleRole(AccessibleRole.TEXT_AREA);
        inputTextField.setAccessibleRoleDescription("Text Entry Box");
        inputTextField.setAccessibleText("Enter commands in this box.");
        inputTextField.setAccessibleHelp("This is the area in which you can enter commands you would like to play.  Enter a command and hit return to continue.");
        addTextHandlingEvent(); // attach an event to this input field

        // labels for inventory and room items
        Label objLabel =  new Label("Objects in Room");
        objLabel.setAlignment(Pos.CENTER);
        objLabel.setStyle("-fx-text-fill: white;");
        objLabel.setFont(new Font("Arial", 16));

        Label invLabel =  new Label("Your Inventory");
        invLabel.setAlignment(Pos.CENTER);
        invLabel.setStyle("-fx-text-fill: white;");
        invLabel.setFont(new Font("Arial", 16));

        // add all the widgets to the GridPane
        gridPane.add( objLabel, 0, 0, 1, 1 );
        gridPane.add( topButtons, 1, 0, 1, 1 );
        gridPane.add( invLabel, 2, 0, 1, 1 );

        // input text field label
        Label commandLabel = new Label("What would you like to do?");
        commandLabel.setStyle("-fx-text-fill: white;");
        commandLabel.setFont(new Font("Arial", 16));

        updateScene(""); // method displays an image and whatever text is supplied
        updateItems(); // update items shows inventory and objects in rooms
        updateMusic();

        // adding the text area and submit button to a VBox
        VBox textEntry = new VBox();
        // more transparency
        textEntry.setStyle("-fx-background-color: transparent;");
        textEntry.setPadding(new Insets(20, 20, 20, 20));
        textEntry.getChildren().addAll(commandLabel, inputTextField);
        textEntry.setSpacing(10);
        textEntry.setAlignment(Pos.CENTER);
        gridPane.add( textEntry, 0, 2, 3, 1 );

        // render everything
        var scene = new Scene( gridPane ,  1000, 800);
        // more transparency
        scene.setFill(transparentColor);
        this.stage.setScene(scene);
        this.stage.setResizable(false);
        this.stage.show();

        // background colour functionality
        ColorPicker colorPicker = new ColorPicker();
        colorPicker.setValue(Color.BLACK); // set the initial color to black

        // create a button for applying the selected color
        Button applyColorButton = new Button("Apply Color");
        applyColorButton.setOnAction(e -> {
            Color selectedColor = colorPicker.getValue();
            setCustomBackgroundColor(selectedColor);
        });

        // create a button for custom image background import
        Button selectImageButton = new Button("Select Image");
        selectImageButton.setOnAction(e -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif"));
            File selectedFile = fileChooser.showOpenDialog(stage);

            if (selectedFile != null) {
                setCustomBackgroundImage(selectedFile.toURI().toString());
            }
        });

        // add background image elements to vbox for display on main grid pane
        VBox colorPickerBox = new VBox(colorPicker, applyColorButton, selectImageButton);
        colorPickerBox.setAlignment(Pos.CENTER);
        colorPickerBox.setSpacing(10);
        gridPane.add(colorPickerBox, 0, 3, 3, 1);
    }

    /**
     * setCustomBackgroundImage
     * Set a custom background colour
     * __________________________
     * @param color the color of the background selected by a click event from the user
     */
    private void setCustomBackgroundColor(Color color) {

        gridPane.setBackground(new Background(new BackgroundFill(
                color,
                new CornerRadii(0),
                new Insets(0)
        )));
    }

    /**
     * setCustomBackgroundImage
     * Set a custom background image
     * __________________________
     * @param imageUrl the url of the image the user selects from their computer
     */

    private void setCustomBackgroundImage(String imageUrl) {
        Image image = new Image(imageUrl, 1000, 800, false, true);

        // sets the background image with reduced opacity
        gridPane.setBackground(new Background(new BackgroundImage(
                image,
                BackgroundRepeat.NO_REPEAT,
                BackgroundRepeat.NO_REPEAT,
                BackgroundPosition.DEFAULT,
                new BackgroundSize(1.0, 1.0, true, true, false, true)
        )));

    }

    /**
     * makeButtonAccessible
     * __________________________
     * Renders buttons accessible for players with disabilities as per ARIA standards of accessibility:
     * <a href="https://www.w3.org/WAI/standards-guidelines/aria/">...</a>
     *
     * @param inputButton the button to add screenreader hooks to
     * @param name ARIA name
     * @param shortString ARIA accessible text
     * @param longString ARIA accessible help text
     */
    public static void makeButtonAccessible(Button inputButton, String name, String shortString, String longString) {
        inputButton.setAccessibleRole(AccessibleRole.BUTTON);
        inputButton.setAccessibleRoleDescription(name);
        inputButton.setAccessibleText(shortString);
        inputButton.setAccessibleHelp(longString);
        inputButton.setFocusTraversable(true);
    }

    /**
     * customizeButton
     * __________________________
     * Adjusts size, font/font size and font colours of buttons
     *
     * @param inputButton the button to customize
     * @param w width
     * @param h height
     */
    private void customizeButton(Button inputButton, int w, int h) {
        inputButton.setPrefSize(w, h);
        inputButton.setFont(new Font("Arial", 16));
        inputButton.setStyle("-fx-background-color: #17871b; -fx-text-fill: white;");
    }

    /**
     * addTextHandlingEvent
     * __________________________
     * Add an event handler to the inputTextField attribute
     *
     * Event handler responds when users
     * hits the ENTER or TAB KEY. If the user hits 
     * the ENTER Key, strips white space from the
     * input to inputTextField and passes the stripped
     * string to submitEvent for processing
     *
     * If the user hits the TAB key, moves the focus
     * of the scene onto any other node in the scene 
     * graph by invoking requestFocus method
     */
    private void addTextHandlingEvent() {

        // if user hits the enter key
        this.inputTextField.setOnAction((ActionEvent event) -> {
            String usersInput = this.inputTextField.getText();
            submitEvent(usersInput.strip());
        });

        // if user hits the tab key
        this.inputTextField.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
            if (event.getCode() == KeyCode.TAB) {
                gridPane.requestFocus();
            }
        });
    }

    /**
     * submitEvent
     * __________________________
     * Begins processing of player's inputted commands
     *
     * @param text the command that needs to be processed
     */
    private void submitEvent(String text) {

        text = text.strip(); // get rid of white space
        stopArticulation();

        if (text.equalsIgnoreCase("LOOK") || text.equalsIgnoreCase("L")) {
            String roomDesc = this.model.getPlayer().getCurrentRoom().getRoomDescription();
            String objectString = this.model.getPlayer().getCurrentRoom().getObjectString();
            if (!objectString.isEmpty()) roomDescLabel.setText(roomDesc + "\n\nObjects in this room:\n" + objectString);
            articulateRoomDescription(); // all we want, if we are looking, is to repeat description.
            return;
        } else if (text.equalsIgnoreCase("HELP") || text.equalsIgnoreCase("H")) {
            showInstructions();
            return;
        } else if (text.equalsIgnoreCase("COMMANDS") || text.equalsIgnoreCase("C")) {
            showCommands();
            return;
        }

        String output = this.model.interpretAction(text); // process the given command

        /* this condition covers all viable output options that do not transition the player to a new room,
        so in this case we don't want to update the music, instead we want to leave it playing */
        if ((Objects.equals(output, "INVALID COMMAND.")) || (Objects.equals(output, "INVENTORY IS EMPTY")) || (Objects.equals(output, "THE TAKE COMMAND REQUIRES AN OBJECT")) ||
                (Objects.equals(output, "THE DROP COMMAND REQUIRES AN OBJECT")) || (Objects.equals(output, "THESE OBJECTS ARE IN YOUR INVENTORY:\n" + this.model.player.getInventory().toString()))) {
            updateScene(output);
            updateItems();

        } else if (output == null || (!output.equals("GAME OVER") && !output.equals("FORCED") && !output.equals("HELP"))) {
            updateScene(output);
            updateItems();
            updateMusic();

        } else if (output.equals("GAME OVER")) {
            updateScene("");
            updateItems();
            updateMusic();
            // set a pause long enough for the victory track music to play out once
            PauseTransition pause = new PauseTransition(Duration.seconds(34));
            pause.setOnFinished(event -> {
                // if the game has ended, we terminate the program
                Platform.exit();
            });
            pause.play();
        } else if (output.equals("FORCED")) {
            /* handles "FORCED" events (rooms that do not allow users to input commands) recursively.
            Displays the image in the current room, pauses, then transitions to the next forced room */

            updateScene("");
            updateItems();
            updateMusic();
            PauseTransition pause = new PauseTransition(Duration.seconds(5));
            inputTextField.setDisable(true);
            pause.setOnFinished(event -> {
                submitEvent("FORCED");
                inputTextField.setDisable(false);
            });
            pause.play();
        }
    }

    /**
     * showCommands
     * __________________________
     * Update the text in the GUI (within roomDescLabel)
     * to show all the moves that are possible from the 
     * current room.
     * Call articulateCommands() to play a voice line
     * listing out the directions a user can currently go
     */
    private void showCommands() {
        updateScene("You can move in these directions:\n\n" + this.model.player.getCurrentRoom().getCommands());
        articulateCommands(this.model.player.getCurrentRoom().getRoomNumber());
    }

    /**
     * updateMusic
     * __________________________
     * updateMusic refreshes each time a new room is entered,
     * playing an ambient music file specific to that room.
     * When the user leaves a room, updateMusic will stop
     * playing the music file from the previous room, instead
     * switching to and playing the music file for the current
     * room
     */
    public void updateMusic() {
        Integer currentRoomNum = this.model.player.getCurrentRoom().getRoomNumber();
        if (musicPlaying) {
            musicPlayer.stop();
            musicPlaying = false;
        }
        playAmbience(currentRoomNum);
    }

    /**
     * updateScene
     * __________________________
     * Show the current room, and print some text below it.
     * If the input parameter is not null, it will be displayed
     * below the image.
     * Otherwise, the current room description will be displayed
     * below the image.
     * 
     * @param textToDisplay the text to display below the image.
     */
    public void updateScene(String textToDisplay) {

        getRoomImage(); // get the image of the current room
        formatText(textToDisplay); // format the text to display, just changes the label font and position
        roomDescLabel.setPrefWidth(500);
        roomDescLabel.setPrefHeight(500);
        roomDescLabel.setTextOverrun(OverrunStyle.CLIP);
        roomDescLabel.setWrapText(true);
        VBox roomPane = new VBox(roomImageView,roomDescLabel);
        roomPane.setPadding(new Insets(10));
        roomPane.setAlignment(Pos.TOP_CENTER);
        roomPane.setStyle("-fx-background-color: transparent;");

        gridPane.add(roomPane, 1, 1);
        stage.sizeToScene();

        // finally, articulate the description
        if (textToDisplay == null || textToDisplay.isBlank()) articulateRoomDescription();
    }

    /**
     * formatText
     * __________________________
     * Format text for display.
     * 
     * @param textToDisplay the text to be formatted for display.
     */
    private void formatText(String textToDisplay) {
        if (textToDisplay == null || textToDisplay.isBlank()) {
            String roomDesc = this.model.getPlayer().getCurrentRoom().getRoomDescription() + "\n";
            String objectString = this.model.getPlayer().getCurrentRoom().getObjectString();
            if (objectString != null && !objectString.isEmpty()) roomDescLabel.setText(roomDesc + "\n\nObjects in this room:\n" + objectString);
            else roomDescLabel.setText(roomDesc);
        } else roomDescLabel.setText(textToDisplay);
        roomDescLabel.setStyle("-fx-text-fill: white;");
        roomDescLabel.setFont(new Font("Arial", 16));
        roomDescLabel.setAlignment(Pos.CENTER);
    }

    /**
     * getRoomImage
     * __________________________
     * Get the image for the current room and place 
     * it in the roomImageView 
     */
    private void getRoomImage() {

        int roomNumber = this.model.getPlayer().getCurrentRoom().getRoomNumber();
        String roomImage = this.model.getDirectoryName() + "/RoomImagesAssets/" + roomNumber + ".png";

        Image roomImageFile = new Image(roomImage);
        roomImageView = new ImageView(roomImageFile);
        roomImageView.setPreserveRatio(true);
        roomImageView.setFitWidth(400);
        roomImageView.setFitHeight(400);

        // set accessible text
        roomImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW);
        roomImageView.setAccessibleText(this.model.getPlayer().getCurrentRoom().getRoomDescription());
        roomImageView.setFocusTraversable(true);
        roomImageView.setStyle("-fx-background-color: transparent;");
    }

    /**
     * updateItems
     * __________________________
     * This method populates the objectsInRoom and objectsInInventory VBoxes.
     * Each VBox contains a collection of nodes, where each node represents a different object
     * 
     * Images of each object are in the assets folders of the given adventure game
     */
    public void updateItems() {

        // clear new rooms of previous objects
        this.objectsInRoom.getChildren().clear();

        // code specific to dropping/picking up BIRD object
        for (int i = 0; i < this.model.player.getCurrentRoom().objectsInRoom.size(); i++) {
            if (Objects.equals(this.model.player.getCurrentRoom().objectsInRoom.get(i).getName(), "BIRD")) {
                String objectImage = this.model.getDirectoryName() + "/ObjectImagesAssets/" + "BIRD" + ".jpg";
                ImageView objectImageView = new ImageView(new Image(objectImage));
                objectImageView.setPreserveRatio(true);
                objectImageView.setFitWidth(100);
                objectImageView.setFitHeight(100);

                objectImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW);
                objectImageView.setAccessibleText("Image of a bird");
                objectImageView.setFocusTraversable(true);

                Button birdButton = new Button("", objectImageView);

                makeButtonAccessible(birdButton, "Bird Button", "Interact with Bird", "Used to pick up or drop the bird");
                birdButton.setOnAction(event -> {
                    if (this.objectsInRoom.getChildren().contains(birdButton)) {
                        this.objectsInRoom.getChildren().remove(birdButton);
                        this.objectsInInventory.getChildren().add(birdButton);
                        this.model.player.takeObject("BIRD");

                    } else {
                        this.objectsInInventory.getChildren().remove(birdButton);
                        this.objectsInRoom.getChildren().add(birdButton);
                        this.model.player.dropObject("BIRD");
                    }
                });
                this.objectsInRoom.getChildren().add(birdButton);

            } else if (Objects.equals(this.model.player.getCurrentRoom().objectsInRoom.get(i).getName(), "CHEST")) {
                String objectImage = this.model.getDirectoryName() + "/ObjectImagesAssets/" + "CHEST" + ".jpg";
                ImageView objectImageView = new ImageView(new Image(objectImage));
                objectImageView.setPreserveRatio(true);
                objectImageView.setFitWidth(100);
                objectImageView.setFitHeight(100);

                objectImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW);
                objectImageView.setAccessibleText("Image of a chest");
                objectImageView.setFocusTraversable(true);

                Button chestButton = new Button("", objectImageView);

                makeButtonAccessible(chestButton, "Chest Button", "Interact with Chest", "Used to pick up or drop the chest");
                chestButton.setOnAction(event -> {
                    if (this.objectsInRoom.getChildren().contains(chestButton)) {

                        this.model.player.takeObject("CHEST");
                        this.objectsInRoom.getChildren().remove(chestButton);
                        this.objectsInInventory.getChildren().add(chestButton);

                    } else {
                        this.model.player.dropObject("CHEST");
                        this.objectsInInventory.getChildren().remove(chestButton);
                        this.objectsInRoom.getChildren().add(chestButton);
                    }
                });
                this.objectsInRoom.getChildren().add(chestButton);

            } else if (Objects.equals(this.model.player.getCurrentRoom().objectsInRoom.get(i).getName(), "BOOK")) {
                String objectImage = this.model.getDirectoryName() + "/ObjectImagesAssets/" + "BOOK" + ".jpg";
                ImageView objectImageView = new ImageView(new Image(objectImage));
                objectImageView.setPreserveRatio(true);
                objectImageView.setFitWidth(100);
                objectImageView.setFitHeight(100);

                objectImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW);
                objectImageView.setAccessibleText("Image of a book");
                objectImageView.setFocusTraversable(true);

                Button bookButton = new Button("", objectImageView);

                makeButtonAccessible(bookButton, "Book Button", "Interact with Book", "Used to pick up or drop the book");
                bookButton.setOnAction(event -> {
                    if (this.objectsInRoom.getChildren().contains(bookButton)) {

                        this.model.player.takeObject("BOOK");
                        this.objectsInRoom.getChildren().remove(bookButton);
                        this.objectsInInventory.getChildren().add(bookButton);

                    } else {
                        this.model.player.dropObject("BOOK");
                        this.objectsInInventory.getChildren().remove(bookButton);
                        this.objectsInRoom.getChildren().add(bookButton);
                    }
                });
                this.objectsInRoom.getChildren().add(bookButton);
            }
        }

        // add scroll tabs to GUI
        ScrollPane scO = new ScrollPane(objectsInRoom);
        scO.setPadding(new Insets(10));
        scO.setFitToWidth(true);
        gridPane.add(scO,0,1);

        ScrollPane scI = new ScrollPane(objectsInInventory);
        scI.setFitToWidth(true);
        gridPane.add(scI,2,1);
    }

    /**
     * Show the game instructions.
     *
     * If helpToggle is False:
     * -- displays the help text in the CENTRE of the gridPane (i.e. within cell 1,1)
     * -- sets the helpToggle to TRUE
     * -- removes whatever nodes are within the cell beforehand
     *
     * If helpToggle is True:
     * -- redraws the room image in the CENTRE of the gridPane (i.e. within cell 1,1)
     * -- sets the helpToggle to FALSE
     * -- Removes whatever nodes are within the cell beforehand
     */
    public void showInstructions() {
        // custom code, separate from personal

        // toggles help instructions, does not disappear upon entering a new room
        if (!helpToggle) {
            helpToggle = true;
            Label label = new Label();
            label.setId("Instruction");
            gridPane.add(label, 1, 1);
            label.setWrapText(true);
            label.autosize();
            label.setTextFill(Color.WHITE);
            label.setText(model.getInstructions());
            label.setStyle("-fx-background-color: black");
        } else {
            helpToggle = false;
            for (Node a: gridPane.getChildren()) {
                if (a.getId() != null) {
                    if (a.getId().equals("Instruction")) {
                        gridPane.getChildren().remove(a);
                        break;
                    }
                }
            }
        }
    }

    /**
     * This method handles the event related to the
     * help button.
     */
    public void addInstructionEvent() {
        helpButton.setOnAction(e -> {
            stopArticulation();
            showInstructions();
        });
    }

    /**
     * This method handles the event related to the
     * save button.
     */
    public void addSaveEvent() {
        saveButton.setOnAction(e -> {
            gridPane.requestFocus();
            SaveView saveView = new SaveView(this);
        });
    }

    /**
     * This method handles the event related to the
     * load button.
     */
    public void addLoadEvent() {
        loadButton.setOnAction(e -> {
            gridPane.requestFocus();
            try {
                LoadView loadView = new LoadView(this);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        });
    }

    /**
     * This method handles the event related to the
     * settings menu button
     */
    public void addSettingsEvent() {
        menuButton.setOnAction(e -> {
            gridPane.requestFocus();
            SettingsView settingsView = new SettingsView(this);
        });
    }

    /**
     * This method articulates Room Descriptions
     */
    public void articulateRoomDescription() {
        String musicFile;
        String adventureName = this.model.getDirectoryName();
        String roomName = this.model.getPlayer().getCurrentRoom().getRoomName();

        // room articulations are pulled from SoundAssets directory in TinyGame or equivalent
        if (!this.model.getPlayer().getCurrentRoom().getVisited()) musicFile = "./" + adventureName + "/SoundAssets/" + roomName.toLowerCase() + "-long.mp3" ;
        else musicFile = "./" + adventureName + "/SoundAssets/" + roomName.toLowerCase() + "-short.mp3" ;
        musicFile = musicFile.replace(" ","-");

        Media sound = new Media(new File(musicFile).toURI().toString());

        mediaPlayer = new MediaPlayer(sound);
        mediaPlayer.play();
        mediaPlaying = true;

    }

    /**
     * This method is called when the user asks to see game "COMMANDS",
     * gathering a voice file specific to the room the user is in and playing its audio.
     * Audio is halted when user interrupts the voice line by entering another command
     */
    public void articulateCommands(Integer room_number) {
        String musicFile;
        String adventureName = this.model.getDirectoryName();

        // direction articulations are pulled from SoundAssets directory in TinyGame or equivalent
        musicFile = "./" + adventureName + "/SoundAssets/" + String.valueOf(room_number) + "-line.mp3";
        musicFile = musicFile.replace(" ","-");

        Media sound = new Media(new File(musicFile).toURI().toString());

        mediaPlayer = new MediaPlayer(sound);
        mediaPlayer.play();
        mediaPlaying = true;
    }

    /**
     * This method is called by updateMusic whenever the user enters a new room,
     * it searches for and plays a music file from Saved/TinyGame/SoundAssets specific
     * to that room, looping it
     */
    public void playAmbience(Integer room_number) {
        String musicFile;
        String adventureName = this.model.getDirectoryName();

        musicFile = "./" + adventureName + "/SoundAssets/" + String.valueOf(room_number) + "-ambient.mp3";
        musicFile = musicFile.replace(" ","-");

        Media sound = new Media(new File(musicFile).toURI().toString());

        musicPlayer = new MediaPlayer(sound);
        musicPlayer.play();

        musicPlayer.setOnEndOfMedia(() -> {
            /* loop the room music everytime a track ends so that the
            music keeps playing while the user is in the room */
            musicPlayer.seek(Duration.ZERO);
            musicPlayer.play();
        });
        musicPlaying = true;
    }

    /**
     * This method stops room articulations.
     * Useful when transitioning to a new room or loading a new game
     */
    public void stopArticulation() {
        if (mediaPlaying) {
            mediaPlayer.stop();
            mediaPlaying = false;
        }
    }
}