Class GameLogic

java.lang.Object
GameLogic.GameLogic

public class GameLogic extends Object
Handles the core logic of the Pokémon battle game.

This class manages the full gameplay experience, including:

  • Tracking all players and their trainers.
  • Handling Pokémon selection and evolution mechanics.
  • Managing turn-based attacks, health, poison, and invincibility status.
  • Implementing revival mechanics and faint counters for trainers.
  • Displaying ASCII art, game instructions, and round/turn information.
  • Maintaining timers for in-game delays and animations.
  • Determining game outcomes, including wins and draws.

The game supports 2–5 players in a Free-For-All (FFA) mode. Each trainer takes turns selecting a player to attack, and attacks are resolved according to Pokémon type, attack selection, and special mechanics like invincibility or poisoning. The class also integrates with PokemonFactory to instantiate Pokémon dynamically.

  • Field Details

    • listOfAllPlayers

      private final ArrayList<Trainer> listOfAllPlayers
      List of all trainers participating in the game.

      Each Trainer in this list represents a player currently registered and actively taking part in the Free-For-All battle.

    • pokemonFactory

      private final PokemonFactory pokemonFactory
      Factory used to create new Pokemon instances dynamically.

      This allows assigning Pokémon to trainers and handling evolution during gameplay.

    • fiveSecondTimer

      private Thread fiveSecondTimer
      Thread used to implement a 5-second delay timer during gameplay.

      Commonly used to give players a short pause to view information or animations before proceeding.

    • threeSecondTimer

      private Thread threeSecondTimer
      Thread used to implement a 3-second delay timer during gameplay.

      Typically used for shorter pauses, such as showing attack results or faint notifications.

    • playerAmount

      private int playerAmount
      Total number of players participating in the current game session.

      Used to control game loops, turn order, and end-of-game conditions.

  • Constructor Details

    • GameLogic

      public GameLogic()
  • Method Details

    • startGame

      public void startGame()
      Starts the Pokémon battle game and manages the entire game flow.

      This method performs the following actions in sequence:

      1. Displays the Pokémon game logo and decorative screen elements.
      2. Introduces the game and explains the rules and game mode.
      3. Prompts the user to confirm if they want to play the game.
      4. If the user agrees, initializes timing threads for delays and animations.
      5. Asks how many players will participate and collects their names.
      6. Allows each player to choose a starter Pokémon and displays the corresponding ASCII art.
      7. Displays detailed game instructions, including turn-based mechanics and evolution/respawn rules.
      8. Executes the main game loop, where players take turns attacking, and Pokémon faint, revive, or evolve.

      The method handles player input validation, updates Pokémon and trainer status, and determines the game's winner or if the game ends in a draw.

    • gameIntroduction

      private void gameIntroduction()
      Displays an introduction message to the players.

      Explains the game mode (Free-For-All), the turn-based mechanics, and what players need to do before the game begins, such as setting their names and choosing starter Pokémon.

    • populatePlayerInformation

      private void populatePlayerInformation()
      Collects and registers information for all players participating in the game.

      For each player, prompts for their trainer name and validates it according to:

      • Minimum and maximum length (2–20 characters)
      • Alphabetical characters only
      • Uniqueness among all previously entered player names
      Once a valid name is entered, the player is added to the game and prompted to select their starter Pokémon.
    • checkStringInputContainsOnlyLetters

      private boolean checkStringInputContainsOnlyLetters(String input)
      Determines if a given string contains only alphabetical letters (A-Z), ignoring case.

      This method is used to validate player names during registration to ensure that they do not contain numbers, spaces, or special characters.

      Parameters:
      input - the string to validate
      Returns:
      true if the string consists solely of letters (A-Z or a-z), false otherwise
    • isNameUnique

      private boolean isNameUnique(ArrayList<Trainer> listOfAllPlayers, String newPlayer)
      Determines if a given trainer name is unique among the currently registered trainers.

      This method is used during player registration to prevent duplicate trainer names. The comparison is case-insensitive.

      Parameters:
      listOfAllPlayers - the list of trainers already registered in the game
      newPlayer - the trainer name to validate for uniqueness
      Returns:
      true if no existing trainer has the same name (case-insensitive), false if the name is already taken
    • givePlayerTheirPokemon

      private void givePlayerTheirPokemon(int specificPlayer)
      Prompts a specific player to select their Pokémon and assigns it using the PokemonFactory.

      The player is presented with a choice of starter Pokémon (Squirtle, Charmander, Bulbasaur, or Pikachu). Input is validated to ensure a valid selection. Once a valid choice is made:

      • The chosen Pokémon is assigned to the player's Trainer.currentPokemon.
      • The ASCII art of the selected Pokémon is displayed.
      • A 5-second timer delay is executed before clearing the screen.
      Parameters:
      specificPlayer - the 1-based index of the player currently selecting a Pokémon
      Throws:
      RuntimeException - if the 5-second timer thread is interrupted unexpectedly
    • printAsciiArtPokemon

      private void printAsciiArtPokemon(String pokemon)
      Displays the ASCII art representation of a given Pokémon.

      The method converts the Pokémon name to uppercase and matches it against known Pokémon names. If a match is found, the corresponding ASCII art from PokemonArt is printed to the console.

      If the provided Pokémon name is not recognized, a message is printed indicating that ASCII art for the Pokémon is not yet available.

      Parameters:
      pokemon - the name of the Pokémon whose ASCII art should be displayed
    • gameInstructions

      private void gameInstructions()
      Displays the game instructions and important gameplay information to the players.

      This includes rules about:

      • How to select a player to attack during a turn
      • The revival mechanics of Pokémon after fainting, including evolution chances
      • The order of play based on player registration
      After displaying the instructions, the method waits for player input to acknowledge they are ready, then clears the screen and begins the game.
    • gameLogic

      private void gameLogic()
      Executes the main game loop for the Pokémon battle game.

      This method handles all rounds and turns, including:

      • Checking which trainers are alive and eligible to take a turn
      • Updating Pokémon status effects such as invincibility and poison
      • Displaying round information and prompting players to select opponents
      • Resolving attacks, including damage, fainting, revival, and evolution
      • Determining when the game ends (either a single winner or a draw)

      The method repeatedly loops through all registered trainers until only one trainer remains alive, reversing the order of play at the start to give the last-registered player the first turn. Status updates and round information are displayed between turns, and ASCII art is printed for Pokémon as needed.

      Exceptions related to Pokémon permanently dying during a turn are caught internally to allow the loop to continue without crashing.

    • askUserIfTheyWantToPlay

      private boolean askUserIfTheyWantToPlay()
      Prompts the user to confirm whether they want to play the Free-For-All (FFA) Pokémon game.

      Continuously asks the user until a valid response is entered:

      • "Yes" (case-insensitive) – the game will start
      • "No" (case-insensitive) – the game will not start
      Any other input will trigger a reprompt until a recognized response is given.
      Returns:
      true if the user declines to play (responds "No"); false if the user agrees to play (responds "Yes")
    • askHowManyPlayersArePlaying

      private int askHowManyPlayersArePlaying()
      Prompts the user to enter the number of players for the Free-For-All (FFA) game.

      Continuously requests input until the user enters a valid number between 2 and 5 (inclusive), representing the total players including themselves. Invalid input will trigger a reprompt with guidance.

      Returns:
      the total number of players participating in the game (2-5)
    • initThreads

      private void initThreads()
      Initializes the 3-second and 5-second timer threads used for gameplay pauses.

      The 5-second timer is used when displaying a newly chosen Pokémon to the player, giving them time to view it. The 3-second timer is used for short delays, such as between turns or after an attack. Each timer runs on a separate Thread.

      Both timers print a countdown to the console, pausing for 1.5 seconds between each number.

    • clearScreen

      private void clearScreen()
      Clears the console screen by printing multiple new lines.

      This method simulates a screen clear in the console to improve readability between different sections of the game, such as rounds, Pokémon selection, and attack results.

    • screenDetail

      private void screenDetail()
      Prints a consistent screen divider for UI formatting.

      This method is used to separate sections of the game in the console, such as between rounds, player turns, or informational messages, providing a clear visual structure for the user.

    • updatePlayerInvincibleStatus

      private void updatePlayerInvincibleStatus(Trainer currentTrainer)
      Updates the invincibility status of the current trainer's Pokémon at the start of their turn.

      If the Pokémon was previously invincible, this method removes the invincibility and notifies the player that their Pokémon is now vulnerable to attacks.

      Parameters:
      currentTrainer - the trainer whose Pokémon status is being updated
    • dealWithPlayerPoisonStatus

      private void dealWithPlayerPoisonStatus(Trainer currentTrainer) throws pokemonDiedPermanentlyDuringItsTurnException
      Applies poison effects to the current trainer's Pokémon at the start of their turn.

      If the Pokémon is poisoned, it loses health according to poison rules. If the Pokémon's health drops to zero, the faint counter for the trainer is decreased, and the Pokémon's death is handled (including potential permanent death or revival). Otherwise, a message is displayed indicating the health loss due to poison.

      Parameters:
      currentTrainer - the trainer whose Pokémon is affected by poison
      Throws:
      pokemonDiedPermanentlyDuringItsTurnException - if the Pokémon dies permanently during this turn
    • roundIntroductionForPlayerTurn

      private void roundIntroductionForPlayerTurn(Trainer currentTrainer, int roundNumber)
      Displays information about the current round and the active player's turn.

      This includes the round number, the trainer's name, and the attack and damage details of the trainer's current Pokémon. Prompts the player to choose an opponent to attack.

      Parameters:
      currentTrainer - the trainer whose turn is being displayed
      roundNumber - the current round number in the game
    • selectPlayerToAttack

      private void selectPlayerToAttack(Trainer currentTrainer)
      Allows the current trainer to select an opponent to attack during their turn.

      This method first generates a list of all alive opponents (excluding the current trainer) and displays their names in a readable format. It then prompts the player to type the name of the trainer they wish to attack. Input is validated to ensure it matches an available target. Once a valid opponent is selected, the method proceeds to handle the attack logic for the current trainer's Pokémon.

      Parameters:
      currentTrainer - the trainer whose turn it is to attack
    • findTrainerByName

      private Trainer findTrainerByName(String currentTrainer)
      Searches for and returns a Trainer object by name from the list of all registered players.

      The search is case-insensitive. If no trainer with the specified name exists in listOfAllPlayers, this method throws a NoSuchElementException.

      Parameters:
      currentTrainer - the name of the trainer to find
      Returns:
      the Trainer instance whose name matches currentTrainer
      Throws:
      NoSuchElementException - if no trainer with the given name exists in listOfAllPlayers
    • checkPlayerInputChoiceToAttackPlayer

      private boolean checkPlayerInputChoiceToAttackPlayer(String playerToBeAttacked, ArrayList<String> attackablePlayerNames)
      Determines whether the selected target for an attack is valid.

      The check is case-insensitive. The method compares the provided player name against a list of currently attackable player names to ensure the selection is allowed.

      Parameters:
      playerToBeAttacked - the name of the player the current trainer wants to attack
      attackablePlayerNames - the list of valid player names that can be attacked
      Returns:
      true if playerToBeAttacked is in attackablePlayerNames; false otherwise
    • checkPokemonAttacks

      private void checkPokemonAttacks(Trainer currentTrainer, Trainer targetTrainer)
      Handles a trainer selecting an attack for their Pokémon and executes it on the target trainer.

      If the current trainer's Pokémon has two available attacks, the user is prompted to choose between attack 1 or attack 2. The method validates the input to ensure only "1" or "2" is accepted. If the Pokémon has only one attack, it is automatically used.

      Parameters:
      currentTrainer - the trainer whose Pokémon is performing the attack
      targetTrainer - the trainer whose Pokémon is being targeted by the attack
    • hasTwoAttacks

      private boolean hasTwoAttacks(Trainer currentTrainer)
      Determines whether the current trainer's Pokémon has two distinct attacks.

      This method checks the Pokémon's name against the TwoAttackPokemon enum, which contains all Pokémon that have two possible attacks. If the Pokémon is found in this enum, it is considered to have two attacks; otherwise, it has only one.

      Parameters:
      currentTrainer - the trainer whose Pokémon is being checked
      Returns:
      true if the Pokémon has two attacks; false otherwise
    • playerWasChosenToBeAttacked

      private void playerWasChosenToBeAttacked(Trainer currentTrainer, Trainer targetTrainer, int numInput)
      Executes an attack from the current trainer's Pokémon on a target trainer's Pokémon.

      This method handles all aspects of a Pokémon attack:

      • Checks if the target Pokémon is invincible. If so, the attack does nothing and a 3-second delay is applied for game pacing.
      • If the target is not invincible, the current Pokémon attacks using the specified attack index. Single-attack Pokémon use numInput = 0.
      • After the attack, the results are handled, including updating health, fainting, and displaying ASCII art of the Pokémon.
      • Applies a 3-second pause after the attack to allow players to see the results, then clears the screen and displays the consistent UI divider.
      Parameters:
      currentTrainer - the trainer whose Pokémon is performing the attack
      targetTrainer - the trainer whose Pokémon is being attacked
      numInput - the attack index to use; 0 if the Pokémon has only one attack
      Throws:
      RuntimeException - if the thread sleep is interrupted during the 3-second pause
    • handleAttackResult

      private void handleAttackResult(Trainer currentTrainer, String howPokemonDied)
      Handles the outcome of an attack on a trainer's Pokémon.

      This method is a convenience overload that allows the caller to provide a description of how the Pokémon died without specifying a custom message prefix. It delegates to handleAttackResult(Trainer, String, String) with an empty prefix.

      Parameters:
      currentTrainer - the trainer whose Pokémon is affected by the attack
      howPokemonDied - a description of the cause of death or fainting event
    • handleAttackResult

      private void handleAttackResult(Trainer currentTrainer, String message, String howPokemonDied)
      Handles the outcome of an attack on a trainer's Pokémon, including fainting and possible permanent death.

      If the Pokémon's health is zero or below, this method:

      Otherwise, it prints the provided message describing the attack or event.
      Parameters:
      currentTrainer - the trainer whose Pokémon is affected
      message - a custom message to display if the Pokémon survives the attack
      howPokemonDied - a description of how the Pokémon fainted or died (used if health ≤ 0)
    • playerPokemonDied

      private void playerPokemonDied(Trainer currentTrainer, String message) throws pokemonDiedPermanentlyDuringItsTurnException
      Determines the outcome when a trainer's Pokémon faints during the game.

      This method uses a random chance to decide whether the Pokémon revives or dies permanently. The probability of revival decreases as the trainer's faint counter increases:

      Parameters:
      currentTrainer - the trainer whose Pokémon fainted
      message - a description of the cause of the Pokémon's fainting
      Throws:
      pokemonDiedPermanentlyDuringItsTurnException - if the Pokémon dies permanently during this turn
    • pokemonDiedPermanently

      private void pokemonDiedPermanently(Trainer currentTrainer, String message) throws pokemonDiedPermanentlyDuringItsTurnException
      Handles the permanent death of a trainer's Pokémon.

      This method performs the following actions:

      • Decrements the total number of active players.
      • Marks the trainer as no longer alive in the game.
      • Prints a message describing the permanent elimination.

      If the Pokémon dies permanently during its turn due to poison, the method also triggers a 3-second pause before throwing a pokemonDiedPermanentlyDuringItsTurnException to signal that the turn ended early.

      Parameters:
      currentTrainer - the trainer whose Pokémon has permanently fainted
      message - a descriptive message explaining the cause of permanent death
      Throws:
      pokemonDiedPermanentlyDuringItsTurnException - if the Pokémon died permanently during its turn due to poison
    • pokemonRevivedAfterDying

      private void pokemonRevivedAfterDying(Trainer currentTrainer, String message)
      Handles the revival of a trainer's Pokémon after fainting.

      When a Pokémon faints, this method determines whether it revives and whether it evolves. Revival and evolution chances are random:

      • Pokémon in its first evolution may evolve to the next stage with a 50% chance.
      • Pokémon in its second evolution may evolve to its final stage with a ~33% chance.
      • Otherwise, the Pokémon revives at its current stage without evolving.
      The method prints messages indicating the faint, revival, and any evolution events.
      Parameters:
      currentTrainer - the trainer whose Pokémon has fainted and may revive
      message - a descriptive message explaining the cause of fainting
    • checkIfPokemonIsFirstEvolution

      private boolean checkIfPokemonIsFirstEvolution(Trainer currentTrainer)
      Checks whether the current Pokémon is in its first evolution stage.

      The method compares the name of the trainer's current Pokémon against all Pokémon defined in the firstEvolutionPokemon enum. This helps determine if the Pokémon is eligible for first-stage evolution upon revival.

      Parameters:
      currentTrainer - the trainer whose Pokémon is being checked
      Returns:
      true if the Pokémon is in the first evolution stage; false otherwise
    • checkIfPokemonIsSecondEvolution

      private boolean checkIfPokemonIsSecondEvolution(Trainer currentTrainer)
      Checks whether the current Pokémon is in its second (final) evolution stage.

      The method compares the name of the trainer's current Pokémon against all Pokémon defined in the SecondEvolutionPokemon enum. This is used to determine if the Pokémon can evolve further upon revival or for display purposes.

      Parameters:
      currentTrainer - the trainer whose Pokémon is being checked
      Returns:
      true if the Pokémon is in the second evolution stage; false otherwise
    • printOutPlayersPokemonFaintStats

      private void printOutPlayersPokemonFaintStats(Trainer currentTrainer, int evolutionStage, String message)
      Updates a trainer's Pokémon after it faints and handles potential revival and evolution.

      Depending on the evolutionStage, this method uses the PokemonFactory to generate the correct Pokémon instance. It then prints an informative message including:

      • The trainer's name
      • The cause of fainting or revival
      • The Pokémon's updated name after evolution (if any)
      • The trainer's current chance for their Pokémon to revive again
      Parameters:
      currentTrainer - the trainer whose Pokémon is being updated
      evolutionStage - the evolution stage to assign: 0 = no evolution, 1 = first evolution, 2 = second evolution
      message - a descriptive message explaining the faint or revival event
    • tellsFactoryWhichPokemonToCreate

      private String tellsFactoryWhichPokemonToCreate(Trainer currentTrainer, int evolutionStage)
      Determines the correct Pokémon name to create in the factory based on the trainer's current Pokémon and the desired evolution stage.

      This method is used by PokemonFactory.pokemonFactory(String) to generate a new Pokémon instance that corresponds to either the current stage, the first evolution, or the second evolution stage. If no valid Pokémon exists for the given evolution stage, an IllegalArgumentException is thrown.

      Parameters:
      currentTrainer - the trainer whose Pokémon is being evolved or revived
      evolutionStage - the evolution stage to create:
      • 0 = default (current Pokémon)
      • 1 = first evolution
      • 2 = second (final) evolution
      Returns:
      the name of the Pokémon corresponding to the desired stage, as a String
      Throws:
      IllegalArgumentException - if no valid Pokémon exists for the given evolution stage