diff --git a/src/main/java/com/mycompany/tictactoeserver/domain/entity/PlayerRow.java b/src/main/java/com/mycompany/tictactoeserver/domain/entity/PlayerRow.java new file mode 100644 index 0000000..7b28eab --- /dev/null +++ b/src/main/java/com/mycompany/tictactoeserver/domain/entity/PlayerRow.java @@ -0,0 +1,39 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.mycompany.tictactoeserver.domain.entity; + +/** + * + * @author Hazem + */ +public class PlayerRow { + private final String username ; + private final String status ; + + public PlayerRow(String username, String Status) { + this.username = username; + this.status = Status; + } + + + public String getUsername() { + return username; + } + + public String getStatus() { + return status; + } + + @Override + public String toString() { + return "PlayerRow{" + "username=" + username + ", Status=" + status + '}'; + } + + public PlayerRow(String username) { + this.username = username; + this.status = "Offline"; + } + +} diff --git a/src/main/java/com/mycompany/tictactoeserver/domain/server/GameServerManager.java b/src/main/java/com/mycompany/tictactoeserver/domain/server/GameServerManager.java index 70443e4..b50eef1 100644 --- a/src/main/java/com/mycompany/tictactoeserver/domain/server/GameServerManager.java +++ b/src/main/java/com/mycompany/tictactoeserver/domain/server/GameServerManager.java @@ -5,9 +5,8 @@ import com.mycompany.tictactoeserver.domain.entity.PlayerEntity; import com.mycompany.tictactoeserver.domain.entity.PlayerStatus; import com.mycompany.tictactoeserver.domain.services.communication.*; -import com.mycompany.tictactoeserver.domain.services.playerSession.PlayerSessionService; -import com.mycompany.tictactoeserver.domain.services.communication.MessageRouter; import com.mycompany.tictactoeserver.domain.services.game.GameManager; +import com.mycompany.tictactoeserver.domain.services.playerSession.PlayerSessionService; import com.mycompany.tictactoeserver.domain.services.statistics.StatisticsService; import com.mycompany.tictactoeserver.domain.utils.callbacks.PlayerHandlerCallback; import com.mycompany.tictactoeserver.domain.utils.callbacks.VoidCallback; @@ -20,6 +19,7 @@ import java.net.SocketException; import java.util.List; import java.util.Vector; + public class GameServerManager { private final Vector players = new Vector<>(); @@ -55,13 +55,14 @@ public PlayerConnectionHandler getPlayerById(String playerId) { synchronized (lock) { for (PlayerConnectionHandler player : players) { if (player.getPlayer() != null - &&player.getPlayer().getId().equals(playerId)) { + && player.getPlayer().getId().equals(playerId)) { return player; } } } return null; } + public Vector getAvailablePlayerData() { Vector available = new Vector<>(); synchronized (lock) { @@ -76,37 +77,37 @@ public Vector getAvailablePlayerData() { public Message getLeaderboardMessage() { StatisticsService statsService = new StatisticsService(); - + List topPlayersList = statsService.getLeaderboard(); - - Vector leaderboardVector = new Vector<>(topPlayersList); - AvailablePlayersInfo info = new AvailablePlayersInfo(leaderboardVector, null, null); - - return Message.createMessage(MessageType.RESPONSE, Action.GET_LEADERBOARD, info); -} + Vector leaderboardVector = new Vector<>(topPlayersList); -public void broadcastLeaderboard() { - Message msg = getLeaderboardMessage(); - String jsonMsg = gson.toJson(msg); - - synchronized (lock) { - for (PlayerConnectionHandler player : players) { - try { - if (player.getPlayer() != null) { - player.sendMessageToPlayer(jsonMsg); + AvailablePlayersInfo info = new AvailablePlayersInfo(leaderboardVector, null, null); + + return Message.createMessage(MessageType.RESPONSE, Action.GET_LEADERBOARD, info); + } + + public void broadcastLeaderboard() { + Message msg = getLeaderboardMessage(); + String jsonMsg = gson.toJson(msg); + + synchronized (lock) { + for (PlayerConnectionHandler player : players) { + try { + if (player.getPlayer() != null) { + player.sendMessageToPlayer(jsonMsg); + } + } catch (Exception e) { + System.out.println("Error broadcasting leaderboard: " + e.getMessage()); } - } catch (Exception e) { - System.out.println("Error broadcasting leaderboard: " + e.getMessage()); } } } -} -public Message getAvailablePlayersMessage(PlayerConnectionHandler requester) { - Vector online = new Vector<>(); - Vector inGame = new Vector<>(); - Vector pending = new Vector<>(); + public Message getAvailablePlayersMessage(PlayerConnectionHandler requester) { + Vector online = new Vector<>(); + Vector inGame = new Vector<>(); + Vector pending = new Vector<>(); synchronized (lock) { for (PlayerConnectionHandler handler : players) { @@ -115,7 +116,7 @@ public Message getAvailablePlayersMessage(PlayerConnectionHandler requester) { } if (requester.getPlayer() != null - &&handler.getPlayer().getId().equals(requester.getPlayer().getId())) { + && handler.getPlayer().getId().equals(requester.getPlayer().getId())) { continue; } PlayerEntity player = new PlayerEntity(handler.getPlayer().getId(), handler.getPlayer().getUsername(), handler.getPlayer().getScore()); @@ -134,7 +135,7 @@ public Message getAvailablePlayersMessage(PlayerConnectionHandler requester) { AvailablePlayersInfo info = new AvailablePlayersInfo(online, inGame, pending); return Message.createMessage(MessageType.RESPONSE, Action.GET_AVAILABLE_PLAYERS, info); -} + } public void start() { synchronized (lock) { @@ -142,6 +143,7 @@ public void start() { runnable.toggleRunning(); } } + public void stop() { System.out.println("Stopping GameServerManager"); runnable.toggleRunning(); @@ -157,7 +159,7 @@ public void stop() { } } - public void terminate(){ + public void terminate() { stop(); thread.interrupt(); } @@ -175,7 +177,17 @@ public void broadcastPlayerList() { } } } + } + } + + public java.util.Vector getAllPlayers() { + + synchronized (lock) { + + return new java.util.Vector<>(players); + } + } public void broadcastMessage(String message) throws PlayerSendMessageException { @@ -195,8 +207,8 @@ public void sendMessage(String message, PlayerConnectionHandler player) throws P public void onReceiveMessage(String message, PlayerConnectionHandler sender) { try { MessageRouter router = MessageRouter.getInstance(); - System.out.println("testingggggggggg"); - router.navigateMessage(message, sender); + + router.navigateMessage(message, sender); } catch (PlayerSendMessageException ex) { System.getLogger(GameServerManager.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); } @@ -212,9 +224,8 @@ public void addPlayer(PlayerConnectionHandler player) { public void removePlayer(PlayerConnectionHandler player) { GameManager.getInstance().handlePlayerDisconnect(player); synchronized (lock) { - - if(player.getPlayer() != null) - { + + if (player.getPlayer() != null) { System.out.println("Player is not nulll-----------------------------------------------"); String playerID = player.getPlayer().getId(); playerSessionService.endPlayerSession(playerID); @@ -282,7 +293,7 @@ public boolean isPlayerOnline(String username) { synchronized (lock) { for (PlayerConnectionHandler player : players) { - if (player.getPlayer()!=null&&username.equals(player.getPlayer().getUsername())) { + if (player.getPlayer() != null && username.equals(player.getPlayer().getUsername())) { return true; } } diff --git a/src/main/java/com/mycompany/tictactoeserver/presentation/DashboardController.java b/src/main/java/com/mycompany/tictactoeserver/presentation/DashboardController.java index 6ad6e0e..57c1c7e 100644 --- a/src/main/java/com/mycompany/tictactoeserver/presentation/DashboardController.java +++ b/src/main/java/com/mycompany/tictactoeserver/presentation/DashboardController.java @@ -1,13 +1,27 @@ package com.mycompany.tictactoeserver.presentation; +import com.mycompany.tictactoeserver.datasource.model.Player; import com.mycompany.tictactoeserver.domain.entity.ActivityPoint; +import com.mycompany.tictactoeserver.domain.entity.PlayerRow; import com.mycompany.tictactoeserver.domain.server.GameServerManager; +import com.mycompany.tictactoeserver.domain.server.PlayerConnectionHandler; import com.mycompany.tictactoeserver.domain.services.statistics.StatisticsService; import com.mycompany.tictactoeserver.domain.utils.BackgroundTaskScheduler; import com.mycompany.tictactoeserver.domain.utils.DeviceManager; +import java.net.SocketException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.ResourceBundle; +import java.util.Vector; +import java.util.concurrent.TimeUnit; import javafx.application.Platform; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.chart.CategoryAxis; @@ -18,12 +32,6 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; -import java.net.SocketException; -import java.net.URL; -import java.util.Arrays; -import java.util.List; -import java.util.ResourceBundle; -import java.util.concurrent.TimeUnit; public class DashboardController implements Initializable { @@ -46,33 +54,20 @@ public class DashboardController implements Initializable { @FXML private ToggleButton filterAllBtn; - @FXML - private ToggleButton filterOnlineBtn; - @FXML - private ToggleButton filterOfflineBtn; + @FXML private ToggleButton filterAvailableBtn; @FXML private ToggleButton filterInGameBtn; @FXML - private TableView playersTable; - @FXML - private TableColumn userColumn; - @FXML - private TableColumn statusColumn; - @FXML - private TableColumn gameStateColumn; - @FXML - private TableColumn lastSeenColumn; - - @FXML - private Label paginationLabel; + private TableView playersTable; @FXML - private Button prevPageBtn; + private TableColumn userColumn; @FXML - private Button nextPageBtn; + private TableColumn statusColumn; + @FXML private ScrollPane monitoringScrollPane; @@ -118,11 +113,18 @@ public class DashboardController implements Initializable { IntegerProperty connectedPlayers = new SimpleIntegerProperty(0); IntegerProperty inGamePlayers = new SimpleIntegerProperty(0); + private final ObservableList playersList = FXCollections.observableArrayList(); + + private FilteredList filteredData; + + private final ToggleGroup filterGroup = new ToggleGroup(); + @Override public void initialize(URL location, ResourceBundle resources) { serverManager = GameServerManager.getInstance(); statsService = new StatisticsService(); - + setupFilters(); + setupTable(); try { String ip = DeviceManager.getIpv4Address(); subtitleLabel.setText("IP Address: " + ip); @@ -130,7 +132,6 @@ public void initialize(URL location, ResourceBundle resources) { System.out.println("Failed to get ipv4 address in main controller"); } - availablePlayers.addListener((observable, oldValue, newValue) -> availableCountLabel.setText(newValue + "")); offlinePlayers.addListener((observable, oldValue, newValue) -> offlineCountLabel.setText(newValue + "")); connectedPlayers.addListener((observable, oldValue, newValue) -> connectedCountLabel.setText(newValue + "")); @@ -138,8 +139,9 @@ public void initialize(URL location, ResourceBundle resources) { offlinePlayers.addListener((observable, oldValue, newValue) -> notifyChart()); connectedPlayers.addListener((observable, oldValue, newValue) -> { - if (oldValue.intValue() > newValue.intValue()) + if (oldValue.intValue() > newValue.intValue()) { notifyChart(); + } }); serverManager.addPlayerCountListener(this::updateMonitorStats); @@ -161,7 +163,6 @@ private void onToggleServer() { return; } - serverManager.stop(); serverStatusLabel.setText("STOPPED"); serverStatusLabel.getStyleClass().removeAll("status-running"); @@ -169,7 +170,6 @@ private void onToggleServer() { serverStatusLabel.getStyleClass().add("status-stopped"); } - } catch (Exception e) { System.out.println("Msg: " + e.getMessage()); System.out.println("Trace: " + Arrays.toString(e.getStackTrace())); @@ -246,7 +246,7 @@ private void updateMonitorStats() { offlinePlayers.set(statsService.getOfflinePlayersCount()); connectedPlayers.set(statsService.getConnectedPlayersCount()); inGamePlayers.set(statsService.getInGamePlayersCount()); - + refreshTableData(); }); } @@ -255,22 +255,26 @@ private void notifyChart() { int selectedMode = 30; - if (range5mBtn.isSelected()) + if (range5mBtn.isSelected()) { selectedMode = 5; - else if (range24hBtn.isSelected()) + } else if (range24hBtn.isSelected()) { selectedMode = 24; - + } points = switch (selectedMode) { - case 5 -> statsService.getLast5MinutesPoints(); - case 24 -> statsService.getLast24HoursPoints(); - default -> statsService.getLast30MinutesPoints(); + case 5 -> + statsService.getLast5MinutesPoints(); + case 24 -> + statsService.getLast24HoursPoints(); + default -> + statsService.getLast30MinutesPoints(); }; mapDataToChart(points); - if (chartUpdater != null) + if (chartUpdater != null) { chartUpdater.stopTask(); + } setChartUpdater(selectedMode); startChartUpdater(selectedMode); } @@ -312,24 +316,27 @@ private void updatePlayersCount(List points) { private void setChartUpdater(int selectedMode) { Runnable task; task = switch (selectedMode) { - case 5 -> () -> { - Platform.runLater(() -> { - List points = statsService.getLast5MinutesPoints(); - mapDataToChart(points); - }); - }; - case 24 -> () -> { - Platform.runLater(() -> { - List points = statsService.getLast24HoursPoints(); - mapDataToChart(points); - }); - }; - default -> () -> { - Platform.runLater(() -> { - List points = statsService.getLast30MinutesPoints(); - mapDataToChart(points); - }); - }; + case 5 -> + () -> { + Platform.runLater(() -> { + List points = statsService.getLast5MinutesPoints(); + mapDataToChart(points); + }); + }; + case 24 -> + () -> { + Platform.runLater(() -> { + List points = statsService.getLast24HoursPoints(); + mapDataToChart(points); + }); + }; + default -> + () -> { + Platform.runLater(() -> { + List points = statsService.getLast30MinutesPoints(); + mapDataToChart(points); + }); + }; }; if (chartUpdater == null) { @@ -354,4 +361,64 @@ private void startChartUpdater(int selectedMode) { } ; } -} \ No newline at end of file + + private void setupFilters() { + + filterAllBtn.setToggleGroup(filterGroup); + filterAvailableBtn.setToggleGroup(filterGroup); + filterInGameBtn.setToggleGroup(filterGroup); + + filterAllBtn.setSelected(true); + + filterGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> applyFilters()); + searchField.textProperty().addListener((obs, oldVal, newVal) -> applyFilters()); + } + + private void applyFilters() { + String searchText = searchField.getText().toLowerCase().trim(); + Toggle selectedBtn = filterGroup.getSelectedToggle(); + filteredData.setPredicate(player -> { + if (!searchText.isEmpty() && !player.getUsername().toLowerCase().contains(searchText)) { + return false; + } + if (selectedBtn == filterAvailableBtn) { + return player.getStatus().equalsIgnoreCase("ONLINE"); + } + if (selectedBtn == filterInGameBtn) { + return player.getStatus().equalsIgnoreCase("IN_GAME"); + } + return true; + }); + } + + private void refreshTableData() { + playersList.clear(); + + Vector connections = serverManager.getAllPlayers(); + + for (PlayerConnectionHandler handler : connections) { + Player p = handler.getPlayer(); + + String username = (p != null) ? p.getUsername() : "Someone connecting 😀"; + String status = handler.getStatus().toString(); + + playersList.add(new PlayerRow(username, status)); + } + + } + + private void setupTable() { + + + userColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getUsername())); + + statusColumn.setCellValueFactory(cellData -> + new SimpleStringProperty(cellData.getValue().getStatus())); + + filteredData = new FilteredList<>(playersList, p -> true); + + playersTable.setItems(filteredData); + playersTable.setPlaceholder(new Label("No players found")); + } +} diff --git a/src/main/resources/presentation/css/dashboard-players.css b/src/main/resources/presentation/css/dashboard-players.css index e69de29..9887653 100644 --- a/src/main/resources/presentation/css/dashboard-players.css +++ b/src/main/resources/presentation/css/dashboard-players.css @@ -0,0 +1,92 @@ + +.players-table { + -fx-background-color: transparent; + -fx-base: transparent; + -fx-control-inner-background: transparent; + -fx-background-insets: 0; + -fx-padding: 0; +} + +.players-table .virtual-flow .clipped-container .sheet { + -fx-background-color: transparent; +} + + +.players-table .column-header-background { + -fx-background-color: #111827; + -fx-border-color: transparent transparent #1f2937 transparent; + -fx-border-width: 0 0 1px 0; +} + +.players-table .column-header { + -fx-background-color: transparent; + -fx-size: 45px; + -fx-border-width: 0; + -fx-padding: 0 10 0 10; +} + +.players-table .column-header .label { + -fx-text-fill: #9ca3af; + -fx-font-weight: 600; + -fx-font-size: 13px; + -fx-alignment: CENTER_LEFT; +} + +.players-table .table-row-cell { + -fx-background-color: transparent; + -fx-border-width: 0 0 1px 0; + -fx-border-color: #1f2937; + -fx-cell-size: 50px; +} + +.players-table .table-row-cell:hover { + -fx-background-color: #1a2332; + -fx-cursor: hand; +} + +.players-table .table-row-cell:selected { + -fx-background-color: rgba(6, 182, 212, 0.15); /* Cyan tint */ +} + +.players-table .table-cell { + -fx-alignment: CENTER_LEFT; + -fx-padding: 0 0 0 15px; + -fx-border-color: transparent; + -fx-text-fill: #f9fafb; +} + +.players-table .table-cell .text { + -fx-fill: #f9fafb; + -fx-font-size: 14px; +} + +.table-col-status .text { + -fx-font-weight: 500; +} + +.players-table .placeholder .label { + -fx-text-fill: #6b7280; + -fx-font-size: 14px; +} + +.players-table .scroll-bar:vertical, +.players-table .scroll-bar:horizontal { + -fx-background-color: transparent; +} + +.players-table .scroll-bar .track { + -fx-background-color: transparent; +} + +.players-table .scroll-bar .thumb { + -fx-background-color: #374151; + -fx-background-radius: 4px; +} + +.players-table .scroll-bar .thumb:hover { + -fx-background-color: #4b5563; +} + +.players-table .corner { + -fx-background-color: transparent; +} \ No newline at end of file diff --git a/src/main/resources/presentation/dashboard.fxml b/src/main/resources/presentation/dashboard.fxml index 787e97f..44de4a6 100644 --- a/src/main/resources/presentation/dashboard.fxml +++ b/src/main/resources/presentation/dashboard.fxml @@ -7,7 +7,8 @@ - + + @@ -40,14 +41,11 @@