LevelModel.java

package com.example.project.models.gameScreens;

import com.example.project.models.tiles.LetterTile;
import com.example.project.models.tiles.UpgradeTile;
import com.example.project.services.GameScenes;
import com.example.project.services.SceneManager;
import com.example.project.services.Session;
import com.example.project.models.tiles.ScrabbleLettersValues;
import com.example.project.services.sqlite.dAOs.DictionaryDAO;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;


/**
 * Represents the level model.
 */
public class LevelModel extends GameScreenModel
{
    private final ObservableList<LetterTile> wordRowTiles = FXCollections.observableArrayList();
    private final ObservableList<LetterTile> tileRackRowTiles = FXCollections.observableArrayList();
    private final ObservableList<LetterTile> redrawRowTiles = FXCollections.observableArrayList();
    private final ReadOnlyBooleanWrapper isRedrawActive = new ReadOnlyBooleanWrapper(false);
    private final ReadOnlyIntegerWrapper wordPoints = new ReadOnlyIntegerWrapper(0);
    private final ReadOnlyIntegerWrapper wordMulti = new ReadOnlyIntegerWrapper(0);
    private final ReadOnlyIntegerWrapper playersTotalPoints = new ReadOnlyIntegerWrapper(0);
    private final DictionaryDAO dictionary = new DictionaryDAO();
    private final int initialRedraws = 4;
    private final ReadOnlyIntegerWrapper currentRedraws = new ReadOnlyIntegerWrapper(initialRedraws);
    private final int initialPlays = 4;
    private final ReadOnlyIntegerWrapper currentPlays = new ReadOnlyIntegerWrapper(initialPlays);
    private final wordTileScoreChimeAscending tileScoreSoundPlayer = new wordTileScoreChimeAscending();

    /**
     * Gets the tile score sound effect player.
     * @return LevelTileScoreSoundPlayer.
     */
    public wordTileScoreChimeAscending getTileScoreSoundPlayer() { return this.tileScoreSoundPlayer; }

    /**
     * @return Read-only list of tiles currently in the word area
     */
    public ReadOnlyListProperty<LetterTile> getWordRowTilesProperty() {
        return new ReadOnlyListWrapper<>(wordRowTiles).getReadOnlyProperty();
    }

    /**
     * @return Read-only list of tiles currently in the rack
     */
    public ReadOnlyListProperty<LetterTile> getTileRackRowTilesProperty() {
        return new ReadOnlyListWrapper<>(tileRackRowTiles).getReadOnlyProperty();
    }

    /**
     * @return Read-only list of tiles currently in the redraw window.
     */
    public ReadOnlyListProperty<LetterTile> getRedrawRowTilesProperty() {
        return new ReadOnlyListWrapper<>(redrawRowTiles).getReadOnlyProperty();
    }
    /**
     * @return the total points property to observe.
     */
    public ReadOnlyIntegerProperty getPlayersTotalPoints() {
        return playersTotalPoints.getReadOnlyProperty();
    }
    /**
     * @return the sum combo points property to observe.
     */
    public ReadOnlyIntegerProperty wordPointsProperty() {
        return wordPoints.getReadOnlyProperty();
    }
    /**
     * @return the multi combo points property to observe.
     */
    public ReadOnlyIntegerProperty wordMultiProperty() {
        return wordMulti.getReadOnlyProperty();
        }

    /**
     * gets the redraw property.
     * @return returns indication if redraw active.
     */
    public ReadOnlyBooleanProperty getIsRedrawActive()
    {
        return isRedrawActive.getReadOnlyProperty();
    }

    /**
     * gets the redraws property.
     * @return the current redraws.
     */
    public ReadOnlyIntegerWrapper getCurrentRedraws(){
        return currentRedraws;
    }

    /**
     * gets the current plays.
     * @return current plays remaining.
     */
    public ReadOnlyIntegerProperty getCurrentPlays() {
        return currentPlays;
    }

    /**
     * gets the upgrades tiles observable property.
     * @return the user's session upgrade tiles.
     */
    public ReadOnlyListProperty<UpgradeTile> getUpgradeTilesProprety(){
        return this.session.getUpgradeTilesProperty();
    }

    /**
     * gets the max word size.
     * @return int.
     */
    public int getMaxWordSize() { return session.getWordSize(); }

    /**
     * gets points need to win the current level.
     * @return points need to win the current level.
     */
    public int getLevelRequirement()
    {
        return this.session.getLevelRequirement();
    }

    /**
     * gets the hand size.
     * @return int.
     */
    public int getHandSize() { return session.getHandSize(); }

    /**
     * gets the redraw window size (number of slots in the window).
     * @return int.
     */
    public Integer getRedrawWindowSize() { return session.getRedrawWindowSize(); }

    /**
     * @param session game session.
     */
    public LevelModel(Session session)
    {
        super(session);
        generateLetters();
    }

    /**
     * Called when the level has been lost.
     * resets the players session info and logs back out to the login screen.
     */
    public void onLostLevel()
    {
        this.resetLevelVariables();
        this.session.resetGame();
        SceneManager.getInstance().switchScene(GameScenes.LOGIN);
    }

    /**
     * Called when level has been won.
     * reset the per level info: redraws plays. Goes to shop window.
     */
    public void onWonLevel()
    {
        this.resetLevelVariables();
        SceneManager.getInstance().switchScene(GameScenes.SHOP);
    }

    /**
     * returns true if player has won.
     * @return value indicating if player has won.
     */
    public boolean hasWon()
    {
        return (this.getLevelRequirement() <= this.playersTotalPoints.get());
    }

    /**
     * true if player has lost
     * @return value indicating if player has lost.
     */
    public boolean hasLost()
    {
        return !hasWon() && this.currentPlays.get() == 0;
    }

    /**
     * Attempts to move a tile from rack to word area
     * @param tile The tile to move
     * @return true if move was successful, false otherwise
     */
    private boolean tryMoveTileToWordArea(LetterTile tile) {
        if (tileRackRowTiles.contains(tile) && wordRowTiles.size() < session.getWordSize()) {
            tileRackRowTiles.remove(tile);
            wordRowTiles.add(tile);
        }
        return wordRowTiles.contains(tile);
    }

    /**
     * Attempts to move a tile from rack to redraw area
     * @param tile The tile to move
     * @return true if move was successful, false otherwise
     */
    public boolean tryMoveTileToRedrawArea(LetterTile tile)
    {
        if (redrawRowTiles.size() < session.getRedrawWindowSize() && !redrawRowTiles.contains(tile))
        {
            var rowItsIn = tileRackRowTiles.contains(tile) ? tileRackRowTiles : wordRowTiles;
            rowItsIn.remove(tile);
            redrawRowTiles.add(tile);
        }

        return redrawRowTiles.contains(tile);
    }

    /**
     * Attempts to move a tile from word area or redraw area to rack
     * @param tile The tile to move
     * @return true if move was successful, false otherwise
     */
    private boolean tryMoveTileToRack(LetterTile tile) {
        if (wordRowTiles.contains(tile)) {
            wordRowTiles.remove(tile);
            tileRackRowTiles.add(tile);
        } else if (redrawRowTiles.contains(tile)) {
            redrawRowTiles.remove(tile);
            tileRackRowTiles.add(tile);
        }

        return tileRackRowTiles.contains(tile);
    }

    /**
     * determines where tile should go and moves it
     * @param tile The tile to move
     * @return true if move was successful, false otherwise
     */
    public boolean tryMoveTile(LetterTile tile) {
        boolean moved = false;

        // When no redraw window is open.
        if (!isRedrawActive.get()) {
            if (tileRackRowTiles.contains((tile))) {
                moved = tryMoveTileToWordArea(tile);
            }
            else if (wordRowTiles.contains(tile)) {
                moved = tryMoveTileToRack(tile);
            }
        }
        // when redraw window is open.
        else
        {
            if (wordRowTiles.contains(tile)){
                moved = tryMoveTileToRedrawArea(tile);
            }
            else if (!redrawRowTiles.contains((tile))) {
                moved = tryMoveTileToRedrawArea(tile);
            }
            else {
                moved = tryMoveTileToRack(tile);
            }
        }

        if (moved) { tile.getClackSoundPlayer().play(); }
        return moved;
    }

    /**
     * Gets the current word formed by tiles in the word area
     * @return returns current word string.
     */
    public String getCurrentWord() {
        StringBuilder word = new StringBuilder();
        for (LetterTile tile : wordRowTiles) {
            word.append(tile.getLetter());
        }
        return word.toString();
    }

    /**
     * returns true if word is in dictionary.
     * @return value indicating if word is valid.
     */
    public boolean isWordValid(){
        return dictionary.isWordInDictionary(this.getCurrentWord());
    }

    /**
     * redraws tiles into the tile rack and removes from redraw window.
     */
    public void redrawTiles()
    {
        this.currentRedraws.set(this.currentRedraws.get() - 1);
        refillTileTack();
        this.redrawRowTiles.clear();
    }

    /**
     * add combo sum and multiCombo
     * TODO: this will changed when implementing modifiers
     * @param tile tile.
     */
    public void addToCombo(LetterTile tile)
    {
        this.wordPoints.set(this.wordPoints.get() + tile.getValue());
        this.wordMulti.set(this.wordMulti.get() + 1);
    }

    /**
     * TODO: adding modifiers
     * @return total score int
     */
    public int calcTotalScore()
    {
        // TODO add modifiers to totalPoints
        return this.wordPoints.get() * this.wordMulti.get();
    }

    /**
     * @param totalScore from calcTotalScore
     * sets Total Score
     */
    public void setTotalScore(int totalScore)
    {
        this.playersTotalPoints.set(totalScore);
    }

    /**
     * clears the word row tiles. and refills the tile rack. and decreases the plays left.
     */
    public void playTiles()
    {
        this.wordRowTiles.clear();
        this.refillTileTack();
        this.currentPlays.set(this.currentPlays.get() - 1);
    }

    /**
     * sends the selected redraw tiles back to the rack
     */
    public void returnRedrawTilesToTheRack() {
        for (int i = 0; i < redrawRowTiles.size();){
            tryMoveTileToRack(redrawRowTiles.get(i));
        }
    }

    /**
     * changes active redraw status
     */
    public void toggleRedrawState() {
        isRedrawActive.set(!isRedrawActive.get());
    }

    /**
     * Initialise new level. Clears word row, redraw rack. draws new tiles for the player's tile rack.
     */
    public void setupNewLevel()
    {
        this.wordRowTiles.clear();
        this.returnRedrawTilesToTheRack();
        isRedrawActive.set(false);
        this.currentRedraws.set(initialRedraws);
        this.currentPlays.set(initialPlays);
    }

    /**
     * resets counts for sum and multi in combo
     */
    public void resetCombo()
    {
        this.wordPoints.set(0);
        this.wordMulti.set(0);
    }

    private void resetLevelVariables()
    {
        this.playersTotalPoints.set(0);
        this.currentRedraws.set(initialRedraws);
        this.currentPlays.set(initialPlays);
    }

    private void generateLetters() {
        for (int i = 0; i < session.getHandSize(); i++) {
            var newLetter = new LetterTile(ScrabbleLettersValues.drawRandomTile());
            this.tileRackRowTiles.add(newLetter); // Start all tiles in rack
        }
    }

    /**
     * Refills tile rack. Only up to the hand size allowed this level.
     */
    private void refillTileTack()
    {
        var tilesPlayerHas = tileRackRowTiles.size() + wordRowTiles.size() + redrawRowTiles.size();
        var tilesToReplace = (getHandSize() - tilesPlayerHas);
        for (int i = 0; i < tilesToReplace; i++){
            tileRackRowTiles.add(new LetterTile(ScrabbleLettersValues.drawRandomTile()));
        }
    }
}