FOP-2425-Marathon/src/main/java/h02/template/InputHandler.java
Oshgnacknak a136c9b98e Squashed 'H02/' content from commit c53b833
git-subtree-dir: H02
git-subtree-split: c53b8335cebf2a8ed06ca14f89263c558a06ef6b
2025-01-11 16:40:58 +01:00

201 lines
6.9 KiB
Java

package h02.template;
import fopbot.RobotFamily;
import fopbot.World;
import h02.FourWins;
import org.tudalgo.algoutils.student.annotation.DoNotTouch;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.beans.PropertyChangeEvent;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
* The {@link InputHandler} handles the input of the users.
*/
@DoNotTouch
public class InputHandler {
/**
* The input queue.
*/
private final BlockingDeque<Integer> inputQueue = new LinkedBlockingDeque<>();
/**
* The {@link FourWins} instance.
*/
private final FourWins fourWins;
/**
* Whether the row select mode is active.
*/
private final AtomicBoolean rowSelectMode = new AtomicBoolean(false);
/**
* The status label.
*/
private final JLabel statusLabel = new JLabel("", SwingConstants.CENTER);
/**
* Creates a new {@link InputHandler} instance.
*
* @param fourWins the {@link FourWins} instance
*/
public InputHandler(final FourWins fourWins) {
this.fourWins = fourWins;
final int padding = 4; // Padding in pixels
statusLabel.setBorder(new EmptyBorder(padding, padding, padding, padding));
}
/**
* Sets the color of the given column to the given color.
*
* @param column the column to set the color of
* @param colorSupplier the color to set
*/
private void setColumnColor(final int column, final Supplier<Color> colorSupplier) {
for (int i = 0; i < World.getHeight(); i++) {
final int finalI = i;
SwingUtilities.invokeLater(() -> World.getGlobalWorld().getField(column, finalI).setFieldColor(colorSupplier));
}
}
/**
* Executes the given action only if the game is running.
*
* @param action the action to execute
*/
private void whenGameIsRunning(final Runnable action) {
if (!fourWins.isFinished()) {
action.run();
}
}
/**
* Installs the input handler to the fopbot world.
*/
@SuppressWarnings("UnstableApiUsage")
public void install() {
final var guiPanel = World.getGlobalWorld().getGuiPanel();
final var guiFrame = World.getGlobalWorld().getGuiFrame();
World.getGlobalWorld().getInputHandler().addFieldClickListener(
e -> whenGameIsRunning(() -> addInput(e.getField().getX()))
);
World.getGlobalWorld().getInputHandler().addFieldHoverListener(e -> whenGameIsRunning(() -> {
// deselect last hovered field, if any
if (e.getPreviousField() != null) {
setColumnColor(e.getPreviousField().getX(), () -> null);
}
if (rowSelectMode.get()) {
// select current hovered field
if (e.getField() != null) {
setColumnColor(
e.getField().getX(),
() -> guiPanel.isDarkMode()
? Color.yellow
: Color.orange
);
}
}
}));
statusLabel.setFont(statusLabel.getFont().deriveFont(guiPanel.scale(20.0f)));
guiFrame.add(statusLabel, BorderLayout.NORTH);
guiFrame.pack();
guiPanel.addDarkModeChangeListener(this::onDarkModeChange);
guiPanel.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent e) {
statusLabel.setFont(
statusLabel.getFont().deriveFont(
Math.max(20f, 0.04f * Math.min(guiPanel.getWidth(), guiPanel.getHeight()))
)
);
}
});
// trigger dark mode change to set the correct color
guiPanel.setDarkMode(World.getGlobalWorld().getGuiPanel().isDarkMode());
}
/**
* Called when the dark mode changes.
*
* @param e the property change event
*/
@SuppressWarnings("UnstableApiUsage")
public void onDarkModeChange(final PropertyChangeEvent e) {
final var darkMode = (boolean) e.getNewValue();
statusLabel.setForeground(darkMode ? Color.white : Color.black);
World.getGlobalWorld().getGuiFrame().getContentPane().setBackground(darkMode ? Color.black : Color.white);
}
/**
* Adds an input to the input queue. When {@link #getNextInput(RobotFamily, RobotFamily[][])} is called, the program
* will wait until this method is called.
*
* @param input the input to add
*/
public void addInput(final int input) {
inputQueue.add(input);
}
/**
* Returns the next input from the input queue. If the input is invalid, the user will be prompted to enter a new
* input. The program will halt until a valid input is entered.
*
* @param currentPlayer the current player
* @param stones the current state of the game board
* @return the next input from the input queue
*/
public int getNextInput(final RobotFamily currentPlayer, final RobotFamily[][] stones) {
rowSelectMode.set(true);
statusLabel.setText(
"<html>Click on a column to insert a disc.<br>Current Player: %s</html>".formatted(currentPlayer.getName())
);
try {
final int input = inputQueue.take();
System.out.println("Received column input: " + input);
if (!FourWins.validateInput(input, stones)) {
System.out.println("Invalid column input, please try again.");
return getNextInput(currentPlayer, stones);
}
rowSelectMode.set(false);
return input;
} catch (final InterruptedException e) {
rowSelectMode.set(false);
throw new RuntimeException(e);
}
}
/**
* Sets a status message, saying that the game has ended in a draw.
*/
public void displayDrawStatus() {
statusLabel.setText("<html>No valid columns found. <br>Hence, game ends with a draw.</html>");
}
/**
* Sets a status message, saying that the game has ended with a winner.
*
* @param winner the winner of the game
*/
public void displayWinnerStatus(final RobotFamily winner) {
statusLabel.setText("<html>Player %s has won the game!</html>".formatted(winner.getName()));
}
/**
* Returns the {@link #statusLabel} of this {@link InputHandler}.
*
* <p>Use the {@link JLabel#getText()} method to get the current text of the label, and the
* {@link JLabel#setText(String)} method to update the text.
*
* @return the {@link #statusLabel} of this {@link InputHandler}
*/
public JLabel getStatusLabel() {
return statusLabel;
}
}