diff --git a/.gitignore b/.gitignore index d6ebce9..bfad6e0 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,9 @@ shelf/ *.iml *.ipr *.iws +misc.xml +uiDesigner.xml + ############################## ## Eclipse @@ -103,4 +106,9 @@ build newgamesver-release-V1.jar server.properties gameserver.log.* -gameserver.log \ No newline at end of file +gameserver.log + +############################## +## JPackage +############################## +jpackage-input \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index 5f1391a..46f4d3b 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -6,6 +6,7 @@ dcompile errorprone flushnl + gaaf gamelist playerlist tictactoe diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..c168b80 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/resourceBundles.xml b/.idea/resourceBundles.xml new file mode 100644 index 0000000..af8f6fc --- /dev/null +++ b/.idea/resourceBundles.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + localization + + + + + + + + + + + + + + + localization + + + \ No newline at end of file diff --git a/app/pom.xml b/app/pom.xml index e6a8434..eb93b3b 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -9,16 +9,21 @@ org.toop.Main 25 25 - + 25 UTF-8 + com.diffplug.spotless spotless-maven-plugin 2.46.1 - + + com.google.code.gson + gson + 2.10.1 + org.toop pism_framework @@ -31,38 +36,46 @@ 0.1 compile + + + org.openjfx + javafx-controls + 25 + org.apache.maven.plugins - maven-compiler-plugin - 3.14.1 - - 25 - 25 - 25 - UTF-8 - - - - - - - - - - - - - - - - - - - + maven-shade-plugin + 3.6.1 + + + package + + shade + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + org.toop.Main + + + pism + + + com.diffplug.spotless @@ -99,6 +112,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 25 + 25 + + \ No newline at end of file diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index 3bf2ce1..8456819 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -1,16 +1,21 @@ package org.toop; -import org.toop.app.gui.LocalServerSelector; +import org.toop.app.App; +import org.toop.framework.asset.ResourceLoader; +import org.toop.framework.asset.ResourceManager; +import org.toop.framework.audio.SoundManager; import org.toop.framework.networking.NetworkingClientManager; import org.toop.framework.networking.NetworkingInitializationException; -public class Main { - static void main(String[] args) { +public final class Main { + public static void main(String[] args) { initSystems(); - javax.swing.SwingUtilities.invokeLater(LocalServerSelector::new); + App.run(args); } private static void initSystems() throws NetworkingInitializationException { - new NetworkingClientManager(); + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); + new Thread(NetworkingClientManager::new).start(); + new Thread(SoundManager::new).start(); } } diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java new file mode 100644 index 0000000..c4c9251 --- /dev/null +++ b/app/src/main/java/org/toop/app/App.java @@ -0,0 +1,167 @@ +package org.toop.app; + +import java.util.Stack; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import org.toop.app.layer.Layer; +import org.toop.app.layer.layers.MainLayer; +import org.toop.app.layer.layers.QuitPopup; +import org.toop.framework.asset.ResourceManager; +import org.toop.framework.asset.resources.CssAsset; +import org.toop.framework.audio.events.AudioEvents; +import org.toop.framework.eventbus.EventFlow; +import org.toop.local.AppContext; +import org.toop.local.AppSettings; + +public final class App extends Application { + private static Stage stage; + private static Scene scene; + private static StackPane root; + + private static Stack stack; + private static int height; + private static int width; + + private static boolean isQuitting; + + public static void run(String[] args) { + launch(args); + } + + @Override + public void start(Stage stage) throws Exception { + final StackPane root = new StackPane(); + final Scene scene = new Scene(root); + + stage.setTitle(AppContext.getString("appTitle")); + stage.setWidth(1080); + stage.setHeight(720); + + stage.setOnCloseRequest( + event -> { + event.consume(); + + if (!isQuitting) { + quitPopup(); + } + }); + + stage.setScene(scene); + stage.setResizable(false); + + stage.show(); + + App.stage = stage; + App.scene = scene; + App.root = root; + + App.stack = new Stack<>(); + + App.width = (int) stage.getWidth(); + App.height = (int) stage.getHeight(); + + App.isQuitting = false; + + final AppSettings settings = new AppSettings(); + settings.applySettings(); + + new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); + activate(new MainLayer()); + } + + public static void activate(Layer layer) { + Platform.runLater( + () -> { + popAll(); + push(layer); + }); + } + + public static void push(Layer layer) { + Platform.runLater( + () -> { + root.getChildren().addLast(layer.getLayer()); + stack.push(layer); + }); + } + + public static void pop() { + Platform.runLater( + () -> { + root.getChildren().removeLast(); + stack.pop(); + + isQuitting = false; + }); + } + + public static void popAll() { + Platform.runLater( + () -> { + final int childrenCount = root.getChildren().size(); + + for (int i = 0; i < childrenCount; i++) { + try { + root.getChildren().removeLast(); + } catch (Exception e) { + IO.println(e); + } + } + + stack.removeAllElements(); + }); + } + + public static void quitPopup() { + Platform.runLater( + () -> { + push(new QuitPopup()); + isQuitting = true; + }); + } + + public static void quit() { + stage.close(); + } + + public static void reloadAll() { + stage.setTitle(AppContext.getString("appTitle")); + + for (final Layer layer : stack) { + layer.reload(); + } + } + + public static void setFullscreen(boolean fullscreen) { + stage.setFullScreen(fullscreen); + + width = (int) stage.getWidth(); + height = (int) stage.getHeight(); + + reloadAll(); + } + + public static void setStyle(String theme, String layoutSize) { + final int stylesCount = scene.getStylesheets().size(); + + for (int i = 0; i < stylesCount; i++) { + scene.getStylesheets().removeLast(); + } + + scene.getStylesheets().add(ResourceManager.get(theme + ".css").getUrl()); + scene.getStylesheets().add(ResourceManager.get(layoutSize + ".css").getUrl()); + + reloadAll(); + } + + public static int getWidth() { + return width; + } + + public static int getHeight() { + return height; + } +} diff --git a/app/src/main/java/org/toop/app/GameInformation.java b/app/src/main/java/org/toop/app/GameInformation.java new file mode 100644 index 0000000..2a3e56f --- /dev/null +++ b/app/src/main/java/org/toop/app/GameInformation.java @@ -0,0 +1,10 @@ +package org.toop.app; + +public record GameInformation( + String[] playerName, + boolean[] isPlayerHuman, + int[] computerDifficulty, + int[] computerThinkTime, + boolean isConnectionLocal, + String serverIP, + String serverPort) {} diff --git a/app/src/main/java/org/toop/app/canvas/GameCanvas.java b/app/src/main/java/org/toop/app/canvas/GameCanvas.java new file mode 100644 index 0000000..5ed5775 --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -0,0 +1,130 @@ +package org.toop.app.canvas; + +import java.util.function.Consumer; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.input.MouseButton; +import javafx.scene.paint.Color; + +public abstract class GameCanvas { + protected record Cell(float x, float y, float width, float height) {} + + protected final Canvas canvas; + protected final GraphicsContext graphics; + + protected final Color color; + + protected int width; + protected int height; + + protected final int rows; + protected final int columns; + + protected final int gapSize; + protected final boolean edges; + + protected final Cell[] cells; + + protected GameCanvas( + Color color, + int width, + int height, + int rows, + int columns, + int gapSize, + boolean edges, + Consumer onCellClicked) { + canvas = new Canvas(width, height); + graphics = canvas.getGraphicsContext2D(); + + this.color = color; + + this.width = width; + this.height = height; + + this.rows = rows; + this.columns = columns; + + this.gapSize = gapSize; + this.edges = edges; + + cells = new Cell[rows * columns]; + + final float cellWidth = ((float) width - (rows - 1) * gapSize) / rows; + final float cellHeight = ((float) height - (columns - 1) * gapSize) / columns; + + for (int y = 0; y < columns; y++) { + final float startY = y * cellHeight + y * gapSize; + + for (int x = 0; x < rows; x++) { + final float startX = x * cellWidth + x * gapSize; + cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight); + } + } + + canvas.setOnMouseClicked( + event -> { + if (event.getButton() != MouseButton.PRIMARY) { + return; + } + + final int column = (int) ((event.getX() / width) * rows); + final int row = (int) ((event.getY() / height) * columns); + + event.consume(); + onCellClicked.accept(row * rows + column); + }); + + render(); + } + + public void clear() { + graphics.clearRect(0, 0, width, height); + } + + public void render() { + graphics.setFill(color); + + for (int x = 1; x < rows; x++) { + graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height); + } + + for (int y = 1; y < columns; y++) { + graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize); + } + + if (edges) { + graphics.fillRect(-gapSize, 0, gapSize, height); + graphics.fillRect(0, -gapSize, width, gapSize); + + graphics.fillRect(width - gapSize, 0, gapSize, height); + graphics.fillRect(0, height - gapSize, width, gapSize); + } + } + + public void draw(Color color, int cell) { + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.setFill(color); + graphics.fillRect(x, y, width, height); + } + + public void resize(int width, int height) { + canvas.setWidth(width); + canvas.setHeight(height); + + this.width = width; + this.height = height; + + clear(); + render(); + } + + public Canvas getCanvas() { + return canvas; + } +} diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java new file mode 100644 index 0000000..1838335 --- /dev/null +++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java @@ -0,0 +1,37 @@ +package org.toop.app.canvas; + +import java.util.function.Consumer; +import javafx.scene.paint.Color; + +public class TicTacToeCanvas extends GameCanvas { + public TicTacToeCanvas(Color color, int width, int height, Consumer onCellClicked) { + super(color, width, height, 3, 3, 10, false, onCellClicked); + } + + public void drawX(Color color, int cell) { + graphics.setStroke(color); + graphics.setLineWidth(gapSize); + + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.strokeLine(x, y, x + width, y + height); + graphics.strokeLine(x + width, y, x, y + height); + } + + public void drawO(Color color, int cell) { + graphics.setStroke(color); + graphics.setLineWidth(gapSize); + + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.strokeOval(x, y, width, height); + } +} diff --git a/app/src/main/java/org/toop/app/gui/BackgroundPanel.java b/app/src/main/java/org/toop/app/gui/BackgroundPanel.java deleted file mode 100644 index bd5c6d2..0000000 --- a/app/src/main/java/org/toop/app/gui/BackgroundPanel.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.toop.app.gui; - -import java.awt.*; -import javax.swing.*; - -public class BackgroundPanel extends JPanel { - private Image backgroundImage; - - public void setBackgroundImage(Image image) { - this.backgroundImage = image; - repaint(); - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - if (backgroundImage != null) { - g.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), this); - } - } -} diff --git a/app/src/main/java/org/toop/app/gui/LocalGameSelector.form b/app/src/main/java/org/toop/app/gui/LocalGameSelector.form deleted file mode 100644 index 0816519..0000000 --- a/app/src/main/java/org/toop/app/gui/LocalGameSelector.form +++ /dev/null @@ -1,37 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/app/src/main/java/org/toop/app/gui/LocalGameSelector.java b/app/src/main/java/org/toop/app/gui/LocalGameSelector.java deleted file mode 100644 index cf4a44b..0000000 --- a/app/src/main/java/org/toop/app/gui/LocalGameSelector.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.toop.app.gui; - -import java.awt.*; -import javax.swing.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.tictactoe.LocalTicTacToe; -import org.toop.tictactoe.gui.UIGameBoard; - -public class LocalGameSelector extends JFrame { - private static final Logger logger = LogManager.getLogger(LocalGameSelector.class); - - private JPanel panel1; - private JComboBox gameSelectionComboBox; - private JButton startGame; - private JComboBox playerTypeSelectionBox; - private JButton deleteSave; - - private JPanel cards; // CardLayout panel - private CardLayout cardLayout; - - private UIGameBoard tttBoard; - - public LocalGameSelector() { - setTitle("Local Game Selector"); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(1920, 1080); - setLocationRelativeTo(null); - - // Setup CardLayout - cardLayout = new CardLayout(); - cards = new JPanel(cardLayout); - setContentPane(cards); - - // --- Main menu panel --- - panel1 = new JPanel(); - panel1.setLayout(new FlowLayout()); - gameSelectionComboBox = new JComboBox<>(); - gameSelectionComboBox.addItem("Tic Tac Toe"); - gameSelectionComboBox.addItem("Reversi"); - - playerTypeSelectionBox = new JComboBox<>(); - playerTypeSelectionBox.addItem("Player vs Player"); - playerTypeSelectionBox.addItem("Player vs AI"); - playerTypeSelectionBox.addItem("AI vs Player"); - - panel1.add(gameSelectionComboBox); - panel1.add(playerTypeSelectionBox); - - startGame = new JButton("Start Game"); - panel1.add(startGame); - - deleteSave = new JButton("Delete Save"); - panel1.add(deleteSave); - deleteSave.setEnabled(false); - deleteSave.addActionListener( - e -> { - tttBoard = null; - deleteSave.setEnabled(false); - }); - - cards.add(panel1, "MainMenu"); - - // Start button action - startGame.addActionListener(e -> startGameClicked()); - - setVisible(true); - } - - private void startGameClicked() { - String playerTypes = (String) playerTypeSelectionBox.getSelectedItem(); - String selectedGame = (String) gameSelectionComboBox.getSelectedItem(); - - LocalTicTacToe lttt = null; - - if (playerTypes.equals("Player vs Player")) { - logger.info("Player vs Player"); - lttt = LocalTicTacToe.createLocal(new boolean[] {false, false}); - } else { - if (playerTypes.equals("Player vs AI")) { - logger.info("Player vs AI"); - lttt = LocalTicTacToe.createLocal(new boolean[] {false, true}); - } else { - logger.info("AI vs Player"); - lttt = LocalTicTacToe.createLocal(new boolean[] {true, false}); - } - } - - if ("Tic Tac Toe".equalsIgnoreCase(selectedGame)) { - if (tttBoard == null) { - tttBoard = new UIGameBoard(lttt, this); - cards.add(tttBoard.getTTTPanel(), "TicTacToe"); - } - cardLayout.show(cards, "TicTacToe"); - } - lttt.startThreads(); - } - - public void showMainMenu() { - cardLayout.show(cards, "MainMenu"); - gameSelectionComboBox.setSelectedIndex(0); - playerTypeSelectionBox.setSelectedIndex(0); - if (tttBoard != null) { - deleteSave.setEnabled(true); - } - } -} diff --git a/app/src/main/java/org/toop/app/gui/LocalServerSelector.form b/app/src/main/java/org/toop/app/gui/LocalServerSelector.form deleted file mode 100644 index 68623a2..0000000 --- a/app/src/main/java/org/toop/app/gui/LocalServerSelector.form +++ /dev/null @@ -1,39 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/app/src/main/java/org/toop/app/gui/LocalServerSelector.java b/app/src/main/java/org/toop/app/gui/LocalServerSelector.java deleted file mode 100644 index 50eb60b..0000000 --- a/app/src/main/java/org/toop/app/gui/LocalServerSelector.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.toop.app.gui; - -import javax.swing.*; - -public class LocalServerSelector { - private JPanel panel1; - private JButton serverButton; - private JButton localButton; - private final JFrame frame; - - public LocalServerSelector() { - frame = new JFrame("Server Selector"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setContentPane(panel1); - frame.setSize(1920, 1080); - frame.setLocationRelativeTo(null); // Sets to center - frame.setVisible(true); - - serverButton.addActionListener(e -> onServerClicked()); - localButton.addActionListener(e -> onLocalClicked()); - } - - private void onServerClicked() { - frame.dispose(); - new RemoteGameSelector(); - } - - private void onLocalClicked() { - frame.dispose(); - new LocalGameSelector(); - } -} diff --git a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.form b/app/src/main/java/org/toop/app/gui/RemoteGameSelector.form deleted file mode 100644 index df43ead..0000000 --- a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.form +++ /dev/null @@ -1,147 +0,0 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.java b/app/src/main/java/org/toop/app/gui/RemoteGameSelector.java deleted file mode 100644 index 3e09acc..0000000 --- a/app/src/main/java/org/toop/app/gui/RemoteGameSelector.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.toop.app.gui; - -import java.awt.event.ActionEvent; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import javax.swing.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.NetworkingGameClientHandler; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.tictactoe.LocalTicTacToe; -import org.toop.tictactoe.gui.UIGameBoard; - -public class RemoteGameSelector { - private static final Logger logger = LogManager.getLogger(RemoteGameSelector.class); - - private JPanel mainMenu; - private JTextField nameTextField; - private JTextField name2TextField; - private JTextField ipTextField; - private JTextField portTextField; - private JButton connectButton; - private JComboBox gameSelectorBox; - private JPanel cards; - private JPanel gameSelector; - private JFrame frame; - private JLabel fillAllFields; - - private LocalTicTacToe localTicTacToe; - - public RemoteGameSelector() { - gameSelectorBox.addItem("Tic Tac Toe"); - gameSelectorBox.addItem("Reversi"); - // todo get supported games from server and add to gameSelectorBox - frame = new JFrame("Game Selector"); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(1920, 1080); - frame.setResizable(true); - - init(); - frame.add(mainMenu); - frame.setVisible(true); - // GlobalEventBus.subscribeAndRegister() Todo add game panel to frame when connection - // succeeds - - } - - private void init() { - connectButton.addActionListener( - (ActionEvent e) -> { - if (!nameTextField.getText().isEmpty() - && !name2TextField.getText().isEmpty() - && !ipTextField.getText().isEmpty() - && !portTextField.getText().isEmpty()) { - - AtomicReference clientId = new AtomicReference<>(); - new EventFlow() - .addPostEvent( - NetworkEvents.StartClient.class, - (Supplier) - new NetworkingGameClientHandler(clientId.get()), - "127.0.0.1", - 5001) - .onResponse( - NetworkEvents.StartClientResponse.class, - (response) -> { - clientId.set(response.clientId()); - }) - .asyncPostEvent(); - - // GlobalEventBus.subscribeAndRegister( - // NetworkEvents.ReceivedMessage.class, - // event -> { - // if - // (event.message().equalsIgnoreCase("ok")) { - // logger.info("received ok from - // server."); - // } else if - // (event.message().toLowerCase().startsWith("gameid")) { - // String gameId = - // event.message() - // .toLowerCase() - // .replace("gameid - // ", ""); - // GlobalEventBus.post( - // new - // NetworkEvents.SendCommand( - // "start_game " + - // gameId)); - // } else { - // logger.info("{}", - // event.message()); - // } - // }); - frame.remove(mainMenu); - UIGameBoard ttt = new UIGameBoard(localTicTacToe, this); - localTicTacToe.startThreads(); - frame.add(ttt.getTTTPanel()); // TODO: Fix later - frame.revalidate(); - frame.repaint(); - } else { - fillAllFields.setVisible(true); - } - }); - } - - public void showMainMenu() { - frame.removeAll(); - frame.add(mainMenu); - frame.revalidate(); - frame.repaint(); - } -} diff --git a/app/src/main/java/org/toop/app/layer/Container.java b/app/src/main/java/org/toop/app/layer/Container.java new file mode 100644 index 0000000..89e6436 --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/Container.java @@ -0,0 +1,12 @@ +package org.toop.app.layer; + +import javafx.scene.Node; +import javafx.scene.layout.Region; + +public abstract class Container { + public abstract Region getContainer(); + + public abstract void addNodes(Node... nodes); + + public abstract void addContainer(Container container, boolean fill); +} diff --git a/app/src/main/java/org/toop/app/layer/Layer.java b/app/src/main/java/org/toop/app/layer/Layer.java new file mode 100644 index 0000000..d357200 --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/Layer.java @@ -0,0 +1,86 @@ +package org.toop.app.layer; + +import javafx.geometry.Pos; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import org.toop.app.App; +import org.toop.app.canvas.GameCanvas; + +public abstract class Layer { + protected StackPane layer; + protected Region background; + + protected Layer(String... backgroundStyles) { + layer = new StackPane(); + + background = new Region(); + background.getStyleClass().addAll(backgroundStyles); + background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); + + layer.getChildren().addLast(background); + } + + protected void addContainer( + Container container, + Pos position, + int xOffset, + int yOffset, + int widthPercent, + int heightPercent) { + StackPane.setAlignment(container.getContainer(), position); + + final double widthUnit = App.getWidth() / 100.0; + final double heightUnit = App.getHeight() / 100.0; + + if (widthPercent > 0) { + container.getContainer().setMaxWidth(widthPercent * widthUnit); + } else { + container.getContainer().setMaxWidth(Region.USE_PREF_SIZE); + } + + if (heightPercent > 0) { + container.getContainer().setMaxHeight(heightPercent * heightUnit); + } else { + container.getContainer().setMaxHeight(Region.USE_PREF_SIZE); + } + + container.getContainer().setTranslateX(xOffset * widthUnit); + container.getContainer().setTranslateY(yOffset * heightUnit); + + layer.getChildren().addLast(container.getContainer()); + } + + protected void addGameCanvas(GameCanvas canvas, Pos position, int xOffset, int yOffset) { + StackPane.setAlignment(canvas.getCanvas(), position); + + final double widthUnit = App.getWidth() / 100.0; + final double heightUnit = App.getHeight() / 100.0; + + canvas.getCanvas().setTranslateX(xOffset * widthUnit); + canvas.getCanvas().setTranslateY(yOffset * heightUnit); + + layer.getChildren().addLast(canvas.getCanvas()); + } + + protected void pop() { + if (layer.getChildren().size() <= 1) { + return; + } + + layer.getChildren().removeLast(); + } + + protected void popAll() { + final int containers = layer.getChildren().size(); + + for (int i = 1; i < containers; i++) { + layer.getChildren().removeLast(); + } + } + + public StackPane getLayer() { + return layer; + } + + public abstract void reload(); +} diff --git a/app/src/main/java/org/toop/app/layer/NodeBuilder.java b/app/src/main/java/org/toop/app/layer/NodeBuilder.java new file mode 100644 index 0000000..b55a70d --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/NodeBuilder.java @@ -0,0 +1,140 @@ +package org.toop.app.layer; + +import java.util.function.Consumer; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.geometry.Orientation; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.text.Text; +import org.toop.framework.audio.events.AudioEvents; +import org.toop.framework.eventbus.EventFlow; + +public final class NodeBuilder { + public static void addCss(Node node, String... cssClasses) { + node.getStyleClass().addAll(cssClasses); + } + + public static void setCss(Node node, String... cssClasses) { + node.getStyleClass().removeAll(); + node.getStyleClass().addAll(cssClasses); + } + + public static Text header(String x) { + final Text element = new Text(x); + setCss(element, "text-primary", "text-header"); + + return element; + } + + public static Text text(String x) { + final Text element = new Text(x); + setCss(element, "text-secondary", "text-normal"); + + return element; + } + + public static Label button(String x, Runnable runnable) { + final Label element = new Label(x); + setCss(element, "button", "text-normal"); + + element.setOnMouseClicked( + _ -> { + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); + runnable.run(); + }); + + return element; + } + + public static Label toggle(String x1, String x2, boolean toggled, Consumer consumer) { + final Label element = new Label(toggled ? x2 : x1); + setCss(element, "toggle", "text-normal"); + + final BooleanProperty checked = new SimpleBooleanProperty(toggled); + + element.setOnMouseClicked( + _ -> { + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); + checked.set(!checked.get()); + + if (checked.get()) { + element.setText(x1); + } else { + element.setText(x2); + } + + consumer.accept(checked.get()); + }); + + return element; + } + + public static Slider slider(int max, int initial, Consumer consumer) { + final Slider element = new Slider(0, max, initial); + setCss(element, "bg-slider-track"); + + element.setMinorTickCount(0); + element.setMajorTickUnit(1); + element.setBlockIncrement(1); + + element.setSnapToTicks(true); + element.setShowTickLabels(true); + + element.setOnMouseClicked( + _ -> { + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); + }); + + element.valueProperty() + .addListener( + (_, _, newValue) -> { + consumer.accept(newValue.intValue()); + }); + + return element; + } + + public static TextField input(String x, Consumer consumer) { + final TextField element = new TextField(x); + setCss(element, "input", "text-normal"); + + element.setOnMouseClicked( + _ -> { + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); + }); + + element.textProperty() + .addListener( + (_, _, newValue) -> { + consumer.accept(newValue); + }); + + return element; + } + + public static ChoiceBox choiceBox(Consumer consumer) { + final ChoiceBox element = new ChoiceBox<>(); + setCss(element, "choice-box", "text-normal"); + + element.setOnMouseClicked( + _ -> { + new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent(); + }); + + element.valueProperty() + .addListener( + (_, _, newValue) -> { + consumer.accept(newValue); + }); + + return element; + } + + public static Separator separator() { + final Separator element = new Separator(Orientation.HORIZONTAL); + setCss(element, "separator"); + + return element; + } +} diff --git a/app/src/main/java/org/toop/app/layer/Popup.java b/app/src/main/java/org/toop/app/layer/Popup.java new file mode 100644 index 0000000..7e498df --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/Popup.java @@ -0,0 +1,20 @@ +package org.toop.app.layer; + +import org.toop.app.App; + +public abstract class Popup extends Layer { + protected Popup(boolean popOnBackground, String... backgroundStyles) { + super(backgroundStyles); + + if (popOnBackground) { + background.setOnMouseClicked( + _ -> { + App.pop(); + }); + } + } + + protected Popup(boolean popOnBackground) { + this(popOnBackground, "bg-popup"); + } +} diff --git a/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java b/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java new file mode 100644 index 0000000..2350216 --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/containers/HorizontalContainer.java @@ -0,0 +1,59 @@ +package org.toop.app.layer.containers; + +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import org.toop.app.layer.Container; + +public final class HorizontalContainer extends Container { + private final HBox container; + + public HorizontalContainer(int spacing, String... cssClasses) { + container = new HBox(spacing); + container.getStyleClass().addAll(cssClasses); + } + + public HorizontalContainer(int spacing) { + this(spacing, "container"); + } + + @Override + public Region getContainer() { + return container; + } + + @Override + public void addNodes(Node... nodes) { + container.getChildren().addAll(nodes); + } + + @Override + public void addContainer(Container container, boolean fill) { + if (fill) { + container.getContainer().setMinSize(0, 0); + container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + HBox.setHgrow(container.getContainer(), Priority.ALWAYS); + } else { + container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + } + + this.container.getChildren().add(container.getContainer()); + + if (fill) { + balanceChildWidths(); + } + } + + private void balanceChildWidths() { + final ObservableList children = container.getChildren(); + final double widthPerChild = container.getWidth() / children.size(); + + for (final Node child : children) { + if (child instanceof Region) { + ((Region) child).setPrefWidth(widthPerChild); + } + } + } +} diff --git a/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java b/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java new file mode 100644 index 0000000..56d610c --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/containers/VerticalContainer.java @@ -0,0 +1,59 @@ +package org.toop.app.layer.containers; + +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import org.toop.app.layer.Container; + +public final class VerticalContainer extends Container { + private final VBox container; + + public VerticalContainer(int spacing, String... cssClasses) { + container = new VBox(spacing); + container.getStyleClass().addAll(cssClasses); + } + + public VerticalContainer(int spacing) { + this(spacing, "container"); + } + + @Override + public Region getContainer() { + return container; + } + + @Override + public void addNodes(Node... nodes) { + container.getChildren().addAll(nodes); + } + + @Override + public void addContainer(Container container, boolean fill) { + if (fill) { + container.getContainer().setMinSize(0, 0); + container.getContainer().setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE); + VBox.setVgrow(container.getContainer(), Priority.ALWAYS); + } else { + container.getContainer().setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE); + } + + this.container.getChildren().add(container.getContainer()); + + if (fill) { + balanceChildHeights(); + } + } + + private void balanceChildHeights() { + final ObservableList children = container.getChildren(); + final double heightPerChild = container.getHeight() / children.size(); + + for (final Node child : children) { + if (child instanceof Region) { + ((Region) child).setPrefHeight(heightPerChild); + } + } + } +} diff --git a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java new file mode 100644 index 0000000..b255c3d --- /dev/null +++ b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java @@ -0,0 +1,235 @@ +package org.toop.app.layer.layers; + +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import javafx.application.Platform; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import org.toop.app.App; +import org.toop.app.GameInformation; +import org.toop.app.layer.Container; +import org.toop.app.layer.Layer; +import org.toop.app.layer.NodeBuilder; +import org.toop.app.layer.Popup; +import org.toop.app.layer.containers.HorizontalContainer; +import org.toop.app.layer.containers.VerticalContainer; +import org.toop.app.layer.layers.game.TicTacToeLayer; +import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.events.NetworkEvents; +import org.toop.local.AppContext; + +public final class ConnectedLayer extends Layer { + private static Timer pollTimer = new Timer(); + + private static class ChallengePopup extends Popup { + private final GameInformation information; + + private final String challenger; + private final String game; + + private final long clientID; + private final int challengeID; + + public ChallengePopup( + GameInformation information, + String challenger, + String game, + long clientID, + String challengeID) { + super(false, "bg-popup"); + + this.information = information; + + this.challenger = challenger; + this.game = game; + + this.clientID = clientID; + this.challengeID = + Integer.parseInt(challengeID.substring(18, challengeID.length() - 2)); + + reload(); + } + + @Override + public void reload() { + popAll(); + + final var challengeText = NodeBuilder.header(AppContext.getString("challengeText")); + final var challengerNameText = NodeBuilder.header(challenger); + + final var gameText = NodeBuilder.text(AppContext.getString("gameIsText")); + final var gameNameText = NodeBuilder.text(game); + + final var acceptButton = + NodeBuilder.button( + AppContext.getString("accept"), + () -> { + pollTimer.cancel(); + + new EventFlow() + .addPostEvent( + new NetworkEvents.SendAcceptChallenge( + clientID, challengeID)) + .postEvent(); + App.activate(new TicTacToeLayer(information, clientID)); + }); + + final var denyButton = + NodeBuilder.button( + AppContext.getString("deny"), + () -> { + App.pop(); + }); + + final Container controlContainer = new HorizontalContainer(30); + controlContainer.addNodes(acceptButton, denyButton); + + final Container mainContainer = new VerticalContainer(30); + mainContainer.addNodes(challengeText, challengerNameText); + mainContainer.addNodes(gameText, gameNameText); + + mainContainer.addContainer(controlContainer, false); + + addContainer(mainContainer, Pos.CENTER, 0, 0, 30, 30); + } + } + + GameInformation information; + long clientId; + String user; + List onlinePlayers = new CopyOnWriteArrayList<>(); + + public ConnectedLayer(GameInformation information) { + super("bg-primary"); + + this.information = information; + + new EventFlow() + .addPostEvent( + NetworkEvents.StartClient.class, + information.serverIP(), + Integer.parseInt(information.serverPort())) + .onResponse( + NetworkEvents.StartClientResponse.class, + e -> { + clientId = e.clientId(); + user = information.playerName()[0].replaceAll("\\s+", ""); + + new EventFlow() + .addPostEvent( + new NetworkEvents.SendLogin(this.clientId, this.user)) + .postEvent(); + + Thread popThread = new Thread(this::populatePlayerList); + popThread.setDaemon(false); + popThread.start(); + }) + .postEvent(); + + new EventFlow().listen(this::handleReceivedChallenge); + + reload(); + } + + private void populatePlayerList() { + EventFlow sendGetPlayerList = + new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId)); + new EventFlow() + .listen( + NetworkEvents.PlayerlistResponse.class, + e -> { + if (e.clientId() == this.clientId) { + List playerList = + new java.util.ArrayList<>( + List.of(e.playerlist())); // TODO: Garbage, + // but works + playerList.removeIf(name -> name.equalsIgnoreCase(user)); + if (this.onlinePlayers != playerList) { + this.onlinePlayers.clear(); + this.onlinePlayers.addAll(playerList); + } + } + }); + + TimerTask task = + new TimerTask() { + public void run() { + sendGetPlayerList.postEvent(); + Platform.runLater(() -> reload()); + } + }; + + pollTimer.schedule(task, 0L, 5000L); // TODO: Block app exit, fix later + } + + private void sendChallenge(String oppUsername, String gameType) { + final AtomicInteger challengeId = new AtomicInteger(-1); + + if (onlinePlayers.contains(oppUsername)) { + new EventFlow() + .addPostEvent( + new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType)) + .listen( + NetworkEvents.ChallengeResponse.class, + e -> { + challengeId.set( + Integer.parseInt( + e.challengeId() + .substring( + 18, e.challengeId().length() - 2))); + }) + .listen( + NetworkEvents.GameMatchResponse.class, + e -> { + if (e.clientId() == this.clientId) { + pollTimer.cancel(); + App.activate(new TicTacToeLayer(information, this.clientId)); + } + }, + false) + .postEvent(); + // ^ + // | + // | + // | + } + } + + private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) { + App.push( + new ChallengePopup( + information, + response.challengerName(), + response.gameType(), + clientId, + response.challengeId())); + } + + @Override + public void reload() { + popAll(); + + ListView