diff --git a/pom.xml b/pom.xml
index 0e10a8e..eb3573b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,17 +26,17 @@
${javafx.version}
-
- org.openjfx
- javafx-fxml
- ${javafx.version}
-
org.json
json
20240303
+
+ org.openjfx
+ javafx-fxml
+ ${javafx.version}
+
org.openjfx
javafx-media
diff --git a/src/main/java/com/boredxgames/tictactoeclient/App.java b/src/main/java/com/boredxgames/tictactoeclient/App.java
index f22376b..873223a 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/App.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/App.java
@@ -12,22 +12,21 @@
public class App extends Application {
- private static Scene scene;
-
@Override
public void start(Stage stage) throws IOException {
- scene = NavigationManager.init();
+ Scene scene = NavigationManager.init();
+ ThemeManager.init(scene);
stage.setScene(scene);
stage.show();
-
+
AudioManager.playDefaultMusic();
-
+
AudioManager.attachGlobalClickSoundToScene(scene);
}
public static void main(String[] args) {
launch();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java
index 53b9c92..1e27cd6 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/NavigationManager.java
@@ -23,11 +23,7 @@ public static Scene init() throws IOException {
current = new ScreenNavigationEntry(Screens.PRIMARY, null, null);
- Parent root = initRoot(current.screen().getName());
- scene = new Scene(root, 640, 480);
-
-
- ThemeManager.init(scene);
+ scene = new Scene(initRoot(current.screen().getName()), 1200, 800);
return scene;
}
@@ -105,7 +101,6 @@ private static Parent initRoot(String fxml) throws IOException {
Parent root = loader.load();
- current = new ScreenNavigationEntry(Screens.PRIMARY, null, loader.getController());
return root;
}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java
index 930c306..2d39688 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/managers/navigation/Screens.java
@@ -5,11 +5,12 @@ public enum Screens {
SECONDARY("GameModeScreen"),
SETTINGS("SettingsScreen"),
GAME_MODE("GameModeScreen"),
+ OFFLINE_MODE_SELECTION("offline_mode_selection"),
SERVER_CONNECTION("server_connection"),
AUTHENTICATION("authentication"),
+ GAME("game_screen"),
Home("home"),
PVP_SETUP("pvp_setup"),
- GAME("game_screen"),
DifficultySelection("difficulty_selection"),
RECORDINGS("RecordingsListScreen");
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/AuthResponseEntity.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/AuthResponseEntity.java
index 61d6624..873947f 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/model/AuthResponseEntity.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/AuthResponseEntity.java
@@ -11,12 +11,12 @@
public class AuthResponseEntity {
private String id ;
private String userName ;
- private int Score;
+ private int score;
public AuthResponseEntity(String id, String userName, int Score) {
this.id = id;
this.userName = userName;
- this.Score = Score;
+ this.score = Score;
}
public AuthResponseEntity() {
@@ -31,7 +31,7 @@ public void setUserName(String userName) {
}
public void setScore(int Score) {
- this.Score = Score;
+ this.score = Score;
}
public String getId() {
@@ -43,7 +43,7 @@ public String getUserName() {
}
public int getScore() {
- return Score;
+ return score;
}
public static AuthResponseEntity createAuthEntity(String id ,String username , int score)
@@ -53,7 +53,7 @@ public static AuthResponseEntity createAuthEntity(String id ,String username , i
@Override
public String toString() {
- return "AuthRequestEntity{" + "id=" + id + ", userName=" + userName + ", Score=" + Score + '}';
+ return "AuthRequestEntity{" + "id=" + id + ", userName=" + userName + ", Score=" + score + '}';
}
}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/AvailablePlayersInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/AvailablePlayersInfo.java
new file mode 100644
index 0000000..cbc1abb
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/AvailablePlayersInfo.java
@@ -0,0 +1,49 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.model;
+
+import java.util.Vector;
+
+/**
+ *
+ * @author mahmoud
+ */
+public class AvailablePlayersInfo {
+ public Vector onlinePlayers;
+ public Vector inGamePlayers;
+ public Vector pendingPlayers;
+
+ public AvailablePlayersInfo(Vector online, Vector inGame, Vector pending) {
+ this.onlinePlayers = online;
+ this.inGamePlayers = inGame;
+ this.pendingPlayers = pending;
+ }
+
+ public Vector getOnlinePlayers() {
+ return onlinePlayers;
+ }
+
+ public void setOnlinePlayers(Vector onlinePlayers) {
+ this.onlinePlayers = onlinePlayers;
+ }
+
+ public Vector getInGamePlayers() {
+ return inGamePlayers;
+ }
+
+ public void setInGamePlayers(Vector inGamePlayers) {
+ this.inGamePlayers = inGamePlayers;
+ }
+
+ public Vector getPendingPlayers() {
+ return pendingPlayers;
+ }
+
+ public void setPendingPlayers(Vector pendingPlayers) {
+ this.pendingPlayers = pendingPlayers;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameRequestInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameRequestInfo.java
new file mode 100644
index 0000000..ad7e617
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameRequestInfo.java
@@ -0,0 +1,37 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.model;
+
+/**
+ *
+ * @author mahmoud
+ */
+public class GameRequestInfo {
+ private final String requesterId;
+ private final String requesterUserName;
+ private final String targetId;
+
+ public GameRequestInfo(String requesterId, String requesterUserName, String targetId) {
+ this.requesterId = requesterId;
+ this.requesterUserName = requesterUserName;
+ this.targetId = targetId;
+ }
+
+ public String getRequesterUserName() {
+ return requesterUserName;
+ }
+
+
+
+
+ public String getRequesterId() {
+ return requesterId;
+ }
+
+ public String getTargetId() {
+ return targetId;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameResponseInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameResponseInfo.java
new file mode 100644
index 0000000..5db33b2
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameResponseInfo.java
@@ -0,0 +1,58 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.model;
+
+/**
+ *
+ * @author mahmoud
+ */
+public class GameResponseInfo {
+ private String requesterId;
+ private String responderId;
+ private String responderUserName;
+ private boolean accepted;
+
+ public String getRequesterId() {
+ return requesterId;
+ }
+
+ public GameResponseInfo(String requesterId, String responderId, String responderUserName, boolean accepted) {
+ this.requesterId = requesterId;
+ this.responderId = responderId;
+ this.responderUserName = responderUserName;
+ this.accepted = accepted;
+ }
+
+ public String getResponderUserName() {
+ return responderUserName;
+ }
+
+ public void setResponderUserName(String responderUserName) {
+ this.responderUserName = responderUserName;
+ }
+
+
+ public void setRequesterId(String requesterId) {
+ this.requesterId = requesterId;
+ }
+
+ public String getResponderId() {
+ return responderId;
+ }
+
+ public void setResponderId(String responderId) {
+ this.responderId = responderId;
+ }
+
+ public boolean isAccepted() {
+ return accepted;
+ }
+
+ public void setAccepted(boolean accepted) {
+ this.accepted = accepted;
+ }
+
+
+}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameStartInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameStartInfo.java
new file mode 100644
index 0000000..1a3110f
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/GameStartInfo.java
@@ -0,0 +1,34 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.model;
+
+/**
+ *
+ * @author moham
+ */
+public class GameStartInfo {
+ private final String roomId;
+ private final String player1;
+ private final String player2;
+
+ public GameStartInfo(String roomId, String player1, String player2) {
+ this.roomId = roomId;
+ this.player1 = player1;
+ this.player2 = player2;
+ }
+
+ public String getRoomId() {
+ return roomId;
+ }
+
+ public String getPlayer1() {
+ return player1;
+ }
+
+ public String getPlayer2() {
+ return player2;
+ }
+
+}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java
new file mode 100644
index 0000000..88cc6c3
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/MoveInfo.java
@@ -0,0 +1,51 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.model;
+
+import com.boredxgames.tictactoeclient.domain.services.communication.Action;
+import com.boredxgames.tictactoeclient.domain.services.communication.Header;
+import com.boredxgames.tictactoeclient.domain.services.communication.Message;
+import com.boredxgames.tictactoeclient.domain.services.communication.MessageType;
+import com.google.gson.Gson;
+
+/**
+ *
+ * @author mahmoud
+ */
+public class MoveInfo {
+ private final String roomId;
+ private final String playerId;
+ private final String move;
+
+ public MoveInfo(String playerId, String move, String roomId) {
+ this.playerId = playerId;
+ this.move = move;
+ this.roomId = roomId;
+ }
+
+ public String getRoomId() {
+ return roomId;
+ }
+
+ public String getPlayerId() {
+ return playerId;
+ }
+
+ public String getMove() {
+ return move;
+ }
+
+ public static MoveInfo createMoveInfo(String roomId, String playerId , Object data)
+ {
+ return new MoveInfo(playerId , toJson(data), roomId);
+ }
+
+ static private String toJson(Object data)
+ {
+ Gson gson = new Gson();
+ return gson.toJson(data);
+
+ }
+}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java
new file mode 100644
index 0000000..92229e9
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/OnlineGameState.java
@@ -0,0 +1,5 @@
+package com.boredxgames.tictactoeclient.domain.model;
+
+public class OnlineGameState {
+ public static GameStartInfo info = null;
+}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java b/src/main/java/com/boredxgames/tictactoeclient/domain/model/PlayerEntity.java
similarity index 51%
rename from src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java
rename to src/main/java/com/boredxgames/tictactoeclient/domain/model/PlayerEntity.java
index 6a634ad..1c769ae 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/model/Player.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/model/PlayerEntity.java
@@ -1,31 +1,44 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.model;
/**
- * @author Tasneem
+ *
+ * @author mahmoud
*/
-public class Player {
- private String id;
+
+public class PlayerEntity {
+ private String id;
private String username;
private int score;
- public Player(String id, String username, int score) {
+ public PlayerEntity(String id, String username, int score) {
this.id = id;
this.username = username;
this.score = score;
}
-
public String getUsername() {
return username;
}
-
+
public void setUsername(String username) {
this.username = username;
}
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
public int getScore() {
return score;
}
-
+
public void setScore(int score) {
this.score = score;
}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java b/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java
index 77f215a..8f06f41 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/network/ServerConnectionManager.java
@@ -10,6 +10,7 @@
import com.boredxgames.tictactoeclient.domain.model.AuthResponseEntity;
import com.boredxgames.tictactoeclient.domain.services.communication.Message;
import com.boredxgames.tictactoeclient.domain.services.communication.MessageRouter;
+import com.boredxgames.tictactoeclient.presentation.AuthenticationController;
import com.google.gson.Gson;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -27,9 +28,9 @@ public class ServerConnectionManager {
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
- private Thread th;
- private Gson gson = new Gson();
+ private final Gson gson = new Gson();
private AuthResponseEntity player;
+ private volatile boolean isIntentionalDisconnect = false;
public void setPlayer(AuthResponseEntity player) {
this.player = player;
@@ -48,14 +49,14 @@ public static ServerConnectionManager getInstance() {
return instance;
}
- public void connect(String host, int port) throws IOException {
- InetAddress ip = InetAddress.getByName(host);
+ public void connect(String host, int port ) throws IOException {
+ isIntentionalDisconnect = false;
+ InetAddress ip = InetAddress.getByName("localhost");
socket = new Socket(ip, port);
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
- th = new Thread(this::readMessages);
+ Thread th = new Thread(this::readMessages);
th.start();
-
}
private void readMessages() {
@@ -68,20 +69,36 @@ private void readMessages() {
MessageRouter router = MessageRouter.getInstance();
router.navigateMessage(response);
} catch (IOException ex) {
- close();
-
+ if (isIntentionalDisconnect) {
+ System.out.println("Disconnected intentionally.");
+ break;
+ }
+
+ close();
Platform.runLater(() -> {
- NavigationManager.navigate(Screens.SERVER_CONNECTION, NavigationAction.REPLACE_ALL);
- });
+ AuthenticationController.showUserAlert("Connection to server lost.");
+ NavigationManager.navigate(Screens.SERVER_CONNECTION, NavigationAction.REPLACE);
+ });
+ break;
+
}
}
}
+ public void disconnect() {
+ try {
+ isIntentionalDisconnect = true;
+ close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
public synchronized void sendMessage(Message msg) {
try {
String jsonMessage = gson.toJson(msg);
+ System.out.println(jsonMessage + "SENTTTTT");
dos.writeUTF(jsonMessage);
dos.flush();
@@ -90,6 +107,9 @@ public synchronized void sendMessage(Message msg) {
}
}
+ public AuthResponseEntity getPlayer() {
+ return player;
+}
public void close() {
try {
@@ -108,7 +128,8 @@ public void close() {
}
+
public boolean isConnected() {
return !socket.isClosed();
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java
similarity index 75%
rename from src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java
rename to src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java
index 455d07a..c7d52aa 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameService.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/GameService.java
@@ -1,7 +1,8 @@
-package com.boredxgames.tictactoeclient.domain.services.game;
+package com.boredxgames.tictactoeclient.domain.services;
import com.boredxgames.tictactoeclient.domain.model.GameState;
import com.boredxgames.tictactoeclient.domain.model.Move;
+import com.boredxgames.tictactoeclient.domain.services.game.GameBoard;
/**
* @author Tasneem
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/Action.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/Action.java
index 91ebf41..72fcdc2 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/Action.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/Action.java
@@ -9,8 +9,10 @@ public enum Action {
LOGIN(10),
REGISTER(20),
REQUEST_GAME(30),
- GAME_RESPONSE(40),
+ GAME_RESPONSE(35),
+ GAME_START(40),
SEND_GAME_UPDATE(50),
+ GAME_END(55),
USERNAME_NOT_FOUND(60),
REGISTERATION_SUCCESS(70),
INTERNAL_SERVER_ERROR(80),
@@ -24,7 +26,10 @@ public enum Action {
INVALID_OPPONENT(160),
NO_PENDING_REQUEST(170),
GET_AVAILABLE_PLAYERS(180),
- USER_IS_ONLINE(190);
+ USER_IS_ONLINE(190),
+ GET_LEADERBOARD(200);
+
+
final int id;
Action(int id) {
@@ -51,4 +56,4 @@ public String toString() {
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java
index 042d46a..b523275 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/communication/MessageRouter.java
@@ -8,25 +8,25 @@
*
* @author Hazem
*/
+
import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationAction;
import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationManager;
import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens;
-import com.boredxgames.tictactoeclient.domain.model.AuthResponseEntity;
+import com.boredxgames.tictactoeclient.domain.model.*;
import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager;
-import static com.boredxgames.tictactoeclient.domain.services.communication.Action.INTERNAL_SERVER_ERROR;
-import static com.boredxgames.tictactoeclient.domain.services.communication.Action.INVALID_CREDENTIAL;
-import static com.boredxgames.tictactoeclient.domain.services.communication.Action.LOGIN_SUCCESS;
-import static com.boredxgames.tictactoeclient.domain.services.communication.Action.REGISTERATION_SUCCESS;
-import static com.boredxgames.tictactoeclient.domain.services.communication.Action.USERNAME_NOT_FOUND;
-import static com.boredxgames.tictactoeclient.domain.services.communication.Action.USER_IS_ONLINE;
+import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService;
import com.boredxgames.tictactoeclient.presentation.AuthenticationController;
+import com.boredxgames.tictactoeclient.presentation.HomeController;
import com.google.gson.Gson;
+import javafx.application.Platform;
public class MessageRouter {
private static MessageRouter instance;
private static ServerConnectionManager connection;
private Gson gson = new Gson();
+ private static HomeController homeController;
+
private MessageRouter() {
connection = ServerConnectionManager.getInstance();
}
@@ -43,17 +43,18 @@ public void navigateMessage(String response) {
MessageType messageType = message.getHeader().getMsgType();
assert messageType != null;
- Message responseMessage;
switch (messageType) {
- case REQUEST -> responseMessage = handleRequest(message);
+ case REQUEST -> handleRequest(message);
case RESPONSE -> handleResponse(message);
- case EVENT -> {
- }
+ case EVENT -> handleEvent(message);
case ERROR -> handleError(message);
default -> System.out.println("Unknown MessageType: " + messageType);
}
- // Handle event messages
- }
+ }
+
+ public static void setHomeController(HomeController controller) {
+ homeController = controller;
+ }
private Message handleRequest(Message msg) {
Action action = msg.getHeader().getAction();
@@ -61,77 +62,177 @@ private Message handleRequest(Message msg) {
assert action != null;
return switch (action) {
- default: {
+
+ default -> {
System.out.println("Unknown Action: " + action);
yield new Message();
}
};
}
-
+
private void handleResponse(Message msg) {
Action action = msg.getHeader().getAction();
- System.out.println(action);
-
+ System.out.println(action);
+
switch (action) {
- case LOGIN_SUCCESS ->
- {
+ case LOGIN_SUCCESS -> {
System.out.println("Login Success");
- AuthResponseEntity responseData = gson.fromJson(msg.getData(),AuthResponseEntity.class);
+ AuthResponseEntity responseData = gson.fromJson(msg.getData(), AuthResponseEntity.class);
+ ServerConnectionManager.getInstance().setPlayer(responseData);
System.out.println(responseData);
- NavigationManager.navigate(Screens.SECONDARY, NavigationAction.REPLACE);
+ NavigationManager.navigate(Screens.Home, NavigationAction.REPLACE);
}
- case REGISTERATION_SUCCESS->{
+ case REGISTERATION_SUCCESS -> {
System.out.println("Registration success");
- AuthResponseEntity responseData = gson.fromJson(msg.getData(),AuthResponseEntity.class);
- AuthenticationController.showUserAlert("Registration success");
-
+ AuthResponseEntity responseData = gson.fromJson(msg.getData(), AuthResponseEntity.class);
+ AuthenticationController.showUserAlert("Registration success");
+
}
- case USERNAME_NOT_FOUND->{
+ case USERNAME_NOT_FOUND -> {
System.out.println("Username not found");
AuthenticationController.showUserAlert("Username not found");
-
+
}
+ case REQUEST_GAME -> {
+ System.out.println("Server acknowledged Game Request.");
+ }
+ case GET_AVAILABLE_PLAYERS -> {
+ AvailablePlayersInfo info = gson.fromJson(msg.getData(), AvailablePlayersInfo.class);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.updatePlayersList(info));
+ }
+ }
+ case GAME_RESPONSE -> {
+ GameResponseInfo info = gson.fromJson(msg.getData(), GameResponseInfo.class);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.handleServerGameResponse(info));
+ }
+ }
+ case GET_LEADERBOARD -> {
+ AvailablePlayersInfo info = gson.fromJson(msg.getData(), AvailablePlayersInfo.class);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.updateLeaderboardUI(info));
+ }
+ }
+ case SEND_GAME_UPDATE -> {
+ MoveInfo moveInfo = gson.fromJson(msg.getData(), MoveInfo.class);
+ OnlinePVPService.onIncomingMove(moveInfo);
+ }
+
default -> {
System.out.println("Unknown Action: " + action);
}
- };
+ }
+ ;
+ }
+
+ private void handleEvent(Message msg) {
+ Action action = msg.getHeader().getAction();
+ System.out.println(action);
+
+ switch (action) {
+ case REQUEST_GAME -> {
+ GameRequestInfo info = gson.fromJson(msg.getData(), GameRequestInfo.class);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.showIncomingGameRequest(info));
+ }
+
+ }
+ case GAME_START -> {
+ OnlineGameState.info = gson.fromJson(msg.getData(), GameStartInfo.class);
+ String player1Name = "You";
+ String player2Name = "Opponent";
+ NavigationManager.navigate(Screens.GAME, NavigationAction.REPLACE, new GameNavigationParams(
+ player1Name,player2Name, GameMode.ONLINE_PVP
+ ));
+
+ }
+
+ default -> {
+ System.out.println("Unknown Action: " + action);
+
+ }
+ }
+ ;
}
-
+
private void handleError(Message msg) {
System.out.println("my time has come");
Action action = msg.getHeader().getAction();
- System.out.println(action);
-
+ System.out.println(action);
+
switch (action) {
-
- case USERNAME_NOT_FOUND->{
+
+ case USERNAME_NOT_FOUND -> {
System.out.println("Username not found");
AuthenticationController.showUserAlert("Username not found");
-
+
}
-
- case INTERNAL_SERVER_ERROR->{
+
+ case INTERNAL_SERVER_ERROR -> {
+ String errorMessage = "Server error. Please try again.";
System.out.println("Internal Server Error");
AuthenticationController.showUserAlert("Internal Server Error");
-
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.showErrorAlert(errorMessage));
+ }
+
+ }
+ case PLAYER_BUSY -> {
+ String errorMsg = "That player is currently in a game.";
+ System.out.println(errorMsg);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.showErrorAlert(errorMsg));
+ }
}
- case INVALID_CREDENTIAL->{
- System.out.println("INVALID_CREDENTIAL");
+ case PENDING_REQUEST_EXISTS -> {
+ String errorMsg = "You already have a request pending.";
+ System.out.println(errorMsg);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.showErrorAlert(errorMsg));
+ }
+ }
+ case TARGET_HAS_PENDING_REQUEST -> {
+ String errorMsg = "That player is considering another challenge.";
+ System.out.println(errorMsg);
+ if (homeController != null) {
+ Platform.runLater(() -> homeController.showErrorAlert(errorMsg));
+ }
+ }
+
+ case ROOM_NOT_FOUND -> {
+ String errorMsg = "The game session is no longer available.";
+ if (homeController != null) {
+ // Close the "Accept/Decline" popup if open
+ homeController.dismissIncomingRequest();
+ Platform.runLater(() -> homeController.showErrorAlert(errorMsg));
+ }
+ }
+
+ case INVALID_OPPONENT -> {
+ String errorMsg = "Opponent disconnected.";
+ if (homeController != null) {
+ homeController.dismissIncomingRequest();
+ Platform.runLater(() -> homeController.showErrorAlert(errorMsg));
+ }
+ }
+ case INVALID_CREDENTIAL -> {
+ System.out.println("INVALID_CREDENTIAL");
AuthenticationController.showUserAlert("INVALID CREDENTIAL");
-
+
}
- case USER_IS_ONLINE->{
- System.out.println("User alread logged in");
+ case USER_IS_ONLINE -> {
+ System.out.println("User alread logged in");
AuthenticationController.showUserAlert("User alread logged in");
-
+
}
- case USERNAME_ALREADY_EXIST->{
- System.out.println("USERNAME_ALREADY_EXIST");
+ case USERNAME_ALREADY_EXIST -> {
+ System.out.println("USERNAME_ALREADY_EXIST");
AuthenticationController.showUserAlert("USERNAME ALREADY EXIST");
-
+
}
default -> {
System.out.println("Unknown Action: " + action);
@@ -140,4 +241,4 @@ private void handleError(Message msg) {
}
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java
index c0312ce..6a97dd0 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/GameBoard.java
@@ -28,6 +28,7 @@ public class GameBoard {
private final List moveHistory;
+
public GameBoard() {
this(PLAYER_X);
}
@@ -66,6 +67,10 @@ public boolean makeMove(int row, int col) {
}
public boolean makeMove(int row, int col, char player) {
+ if (player != currentPlayer) {
+ return false;
+ }
+
if (!isValidMove(row, col)) {
return false;
}
@@ -78,7 +83,7 @@ public boolean makeMove(int row, int col, char player) {
updateGameState();
if (gameState == GameState.IN_PROGRESS) {
- currentPlayer = (player == PLAYER_X) ? PLAYER_O : PLAYER_X;
+ currentPlayer = (player == PLAYER_X) ? PLAYER_O : PLAYER_X;
}
return true;
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java
index 16c299c..2f93b4b 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVEAIService.java
@@ -7,6 +7,7 @@
import com.boredxgames.tictactoeclient.domain.model.GameState;
import com.boredxgames.tictactoeclient.domain.model.Move;
import com.boredxgames.tictactoeclient.domain.services.AIService;
+import com.boredxgames.tictactoeclient.domain.services.GameService;
/**
*
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java
index 18c3697..c667efc 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OfflinePVPService.java
@@ -6,12 +6,13 @@
import com.boredxgames.tictactoeclient.domain.model.GameState;
import com.boredxgames.tictactoeclient.domain.model.Move;
+import com.boredxgames.tictactoeclient.domain.services.GameService;
/**
*
* @author sheri
*/
-public class OfflinePVPService implements GameService{
+public class OfflinePVPService implements GameService {
@Override
public void makeMove(Move move, char currentPlayer) {
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinGameSession.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinGameSession.java
new file mode 100644
index 0000000..f483e61
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinGameSession.java
@@ -0,0 +1,86 @@
+/*
+ * 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.boredxgames.tictactoeclient.domain.services.game;
+
+/**
+ *
+ * @author mahmoud
+ */
+
+
+import com.boredxgames.tictactoeclient.domain.model.AuthResponseEntity;
+import com.boredxgames.tictactoeclient.domain.model.GameRequestInfo;
+import com.boredxgames.tictactoeclient.domain.model.GameResponseInfo;
+import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager;
+import com.boredxgames.tictactoeclient.domain.services.communication.Action;
+import com.boredxgames.tictactoeclient.domain.services.communication.Message;
+import com.boredxgames.tictactoeclient.domain.services.communication.MessageType;
+
+public class OnlinGameSession {
+
+ private static OnlinGameSession instance;
+ private final ServerConnectionManager connection;
+ private boolean isWaitingForResponse = false;
+
+ private OnlinGameSession() {
+ this.connection = ServerConnectionManager.getInstance();
+ }
+
+ public static synchronized OnlinGameSession getInstance() {
+ if (instance == null) {
+ instance = new OnlinGameSession();
+ }
+ return instance;
+ }
+
+ public boolean isWaiting() {
+ return isWaitingForResponse;
+ }
+
+ public void setWaiting(boolean waiting) {
+ this.isWaitingForResponse = waiting;
+ System.out.println("DEBUG: User waiting state set to: " + waiting);
+ }
+
+ public void requestAvailablePlayers() {
+ Message msg = Message.createMessage(MessageType.REQUEST, Action.GET_AVAILABLE_PLAYERS, "");
+ connection.sendMessage(msg);
+ }
+
+ public void sendGameRequest(String targetPlayerId) {
+ if (isWaitingForResponse) {
+ System.out.println("Blocked: Already waiting for a response.");
+ return;
+ }
+
+ AuthResponseEntity me = connection.getPlayer();
+ if (me == null) return;
+
+ GameRequestInfo requestInfo = new GameRequestInfo(me.getId(), me.getUserName(), targetPlayerId);
+ Message msg = Message.createMessage(MessageType.REQUEST, Action.REQUEST_GAME, requestInfo);
+
+ System.out.println("Sending Game Request to: " + targetPlayerId);
+ connection.sendMessage(msg);
+ setWaiting(true);
+ }
+
+ public void sendGameResponse(GameRequestInfo originalRequest, boolean accepted) {
+ AuthResponseEntity me = connection.getPlayer();
+
+ GameResponseInfo responseInfo = new GameResponseInfo(
+ originalRequest.getRequesterId(),
+ originalRequest.getTargetId(),
+ me.getUserName(),
+ accepted
+ );
+
+ Message msg = Message.createMessage(MessageType.RESPONSE, Action.GAME_RESPONSE, responseInfo);
+ connection.sendMessage(msg);
+ }
+ public void requestLeaderboard() {
+ Message msg = Message.createMessage(MessageType.REQUEST, Action.GET_LEADERBOARD, "");
+ connection.sendMessage(msg);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java
new file mode 100644
index 0000000..b6eedb7
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/domain/services/game/OnlinePVPService.java
@@ -0,0 +1,87 @@
+package com.boredxgames.tictactoeclient.domain.services.game;
+
+import com.boredxgames.tictactoeclient.domain.model.*;
+import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager;
+import com.boredxgames.tictactoeclient.domain.services.GameService;
+import com.boredxgames.tictactoeclient.domain.services.communication.Action;
+import com.boredxgames.tictactoeclient.domain.services.communication.Message;
+import com.boredxgames.tictactoeclient.domain.services.communication.MessageType;
+import com.google.gson.Gson;
+import java.util.function.Consumer;
+
+public class OnlinePVPService implements GameService {
+
+ private static OnlinePVPService instance;
+
+ public boolean shouldPlayFirst() {
+
+ System.out.println(" Check equality"+OnlineGameState.info.getPlayer1().equalsIgnoreCase(ServerConnectionManager.getInstance().getPlayer().getId()));
+
+
+ if (OnlineGameState.info.getPlayer1().equalsIgnoreCase(ServerConnectionManager.getInstance().getPlayer().getId())) {
+ return true;
+ }
+ return false;
+ }
+
+ public static OnlinePVPService getInstance() {
+ if (instance == null) {
+ instance = new OnlinePVPService();
+ }
+ return instance;
+ }
+
+ private OnlinePVPService() {
+ }
+
+ // specific listener to bridge Network -> UI
+ private static Consumer moveListener;
+
+ public OnlinePVPService setMoveListener(Consumer listener) {
+ moveListener = listener;
+ return this;
+ }
+
+ public static void onIncomingMove(MoveInfo moveInfo) {
+ Gson gson = new Gson();
+ Move move = gson.fromJson(moveInfo.getMove(), Move.class);
+ if (moveListener != null) {
+ moveListener.accept(move);
+ }
+ }
+
+ @Override
+ public void makeMove(Move move, char currentPlayer) {
+
+ ServerConnectionManager connectionManager = ServerConnectionManager.getInstance();
+ AuthResponseEntity player = connectionManager.getPlayer();
+ GameStartInfo sessionInfo = OnlineGameState.info;
+ // Prepare the data
+ MoveInfo info = MoveInfo.createMoveInfo(sessionInfo.getRoomId(), player.getId(), move);
+
+ // Send SEND_GAME_UPDATE action to server
+ Message msg = Message.createMessage(
+ MessageType.RESPONSE,
+ Action.SEND_GAME_UPDATE,
+ info
+ );
+ connectionManager.sendMessage(msg);
+ }
+
+ @Override
+ public void makeMove(Move move, char currentPlayer, GameBoard board) {
+
+ }
+
+ @Override
+ public Move getNextMove(GameBoard board, char currentPlayer) {
+ // In online play, we don't calculate the next move locally.
+ // We wait for the server event instead.
+ return null;
+ }
+
+ @Override
+ public GameState getOutcome(GameBoard board) {
+ return board.getGameState();
+ }
+}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java
index 6d4c9ed..bebe657 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/AuthenticationController.java
@@ -109,6 +109,7 @@ private String validateInput(String username, String password) {
@FXML
private void onBackClicked() {
+ ServerConnectionManager.getInstance().disconnect();
NavigationManager.navigate(Screens.SERVER_CONNECTION, NavigationAction.REPLACE);
}
@@ -120,10 +121,39 @@ public static void showUserAlert(String message) {
alert.setHeaderText(null);
alert.setContentText(message);
+ DialogPane dialogPane = alert.getDialogPane();
+
+ // 1. Style the main pane
+ dialogPane.setStyle(
+ "-fx-background-color: #1e1e1e; " +
+ "-fx-border-color: #333333; " +
+ "-fx-border-width: 2px;"
+ );
+
+ // 2. Style all labels (Content text)
+ dialogPane.lookupAll(".label").forEach(node ->
+ node.setStyle("-fx-text-fill: #e0e0e0; -fx-font-family: 'Segoe UI';")
+ );
+
+ // 3. Style the buttons
+ dialogPane.lookupAll(".button").forEach(node -> {
+ node.setStyle(
+ "-fx-background-color: #3c3f41; " +
+ "-fx-text-fill: white; " +
+ "-fx-background-radius: 4; " +
+ "-fx-cursor: hand;"
+ );
+
+ // Add simple hover effect via code
+ node.setOnMouseEntered(e -> node.setStyle("-fx-background-color: #4b4d4d; -fx-text-fill: white; -fx-background-radius: 4;"));
+ node.setOnMouseExited(e -> node.setStyle("-fx-background-color: #3c3f41; -fx-text-fill: white; -fx-background-radius: 4;"));
+ });
+
+ // Set the owner window
javafx.stage.Window.getWindows().stream()
.filter(javafx.stage.Window::isShowing)
.findFirst()
- .ifPresent(window -> alert.initOwner(window));
+ .ifPresent(alert::initOwner);
alert.showAndWait().ifPresent(type -> {
if ("Server is out of service".equals(message) || "Internal Server Error".equals(message)) {
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java
new file mode 100644
index 0000000..1b86222
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/BackgroundHomeAnimation.java
@@ -0,0 +1,85 @@
+package com.boredxgames.tictactoeclient.presentation;
+
+/*
+ * 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
+ */
+
+
+/**
+ *
+ * @author Hazem
+ */
+
+
+import java.util.Random;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Pane;
+
+
+public abstract class BackgroundHomeAnimation{
+
+ private static final Random random = new Random();
+
+ /**
+ * Animates a container sliding up from the bottom and fading in.
+ * @param node The container (e.g., VBox) to animate.
+ */
+ public static void animateCardEntry(Node node) {
+// node.setOpacity(0);
+// node.setTranslateY(500);
+//
+// TranslateTransition tt = new TranslateTransition(Duration.millis(1500), node);
+// tt.setToY(0);
+//
+// FadeTransition ft = new FadeTransition(Duration.millis(1000), node);
+// ft.setToValue(1);
+//
+// ParallelTransition pt = new ParallelTransition(tt, ft);
+// pt.play();
+ }
+
+
+ public static void startBackgroundAnimation(Pane backgroundPane, double screenWidth, double screenHeight) {
+// int particleCount = 50;
+//
+// for (int i = 0; i < particleCount; i++) {
+// Label particle = new Label(random.nextBoolean() ? "X" : "O");
+// particle.getStyleClass().add("background-particle");
+//
+// double size = 15 + random.nextInt(50);
+// particle.setStyle("-fx-font-size: " + size + "px; -fx-text-fill: rgba(79, 94, 247, " + (0.05 + random.nextDouble() * 0.2) + ");");
+//
+// particle.setTranslateX(random.nextInt((int) screenWidth));
+// particle.setTranslateY(random.nextInt((int) screenHeight));
+//
+// backgroundPane.getChildren().add(particle);
+// animateParticle(particle, screenWidth, screenHeight);
+// }
+ }
+
+ private static void animateParticle(Label particle, double width, double height) {
+// TranslateTransition move = new TranslateTransition(Duration.seconds(15 + random.nextInt(15)), particle);
+// double endX = particle.getTranslateX() + (random.nextInt(200) - 100); // Drift sideways
+// double endY = -100;
+//
+// move.setToX(endX);
+// move.setToY(endY);
+// move.setCycleCount(1);
+//
+// RotateTransition rotate = new RotateTransition(Duration.seconds(5 + random.nextInt(10)), particle);
+// rotate.setByAngle(360);
+// rotate.setCycleCount(RotateTransition.INDEFINITE);
+//
+// move.setOnFinished(e -> {
+//
+// particle.setTranslateY(height + random.nextInt(100));
+// particle.setTranslateX(random.nextInt((int) width));
+// animateParticle(particle, width, height);
+// });
+//
+// move.play();
+// rotate.play();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java
index 78590ff..7b4e3df 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameController.java
@@ -6,20 +6,25 @@
import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens;
import com.boredxgames.tictactoeclient.domain.model.GameMode;
import com.boredxgames.tictactoeclient.domain.model.GameNavigationParams;
-import com.boredxgames.tictactoeclient.domain.model.GameRecord;
+
import com.boredxgames.tictactoeclient.domain.model.GameState;
import com.boredxgames.tictactoeclient.domain.model.Move;
+import com.boredxgames.tictactoeclient.domain.services.AIService;
+import com.boredxgames.tictactoeclient.domain.services.GameService;
import com.boredxgames.tictactoeclient.domain.services.game.GameBoard;
-import com.boredxgames.tictactoeclient.domain.services.game.GameService;
import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVEAIService;
import com.boredxgames.tictactoeclient.domain.services.game.OfflinePVPService;
+import com.boredxgames.tictactoeclient.domain.services.game.OnlinePVPService;
+import com.boredxgames.tictactoeclient.domain.model.GameRecord;
import com.boredxgames.tictactoeclient.domain.services.storage.GameRecordingService;
import java.net.URL;
import java.util.Objects;
import java.util.ResourceBundle;
import javafx.animation.KeyFrame;
+
import javafx.animation.PauseTransition;
import javafx.animation.Timeline;
+import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
@@ -77,15 +82,17 @@ public class GameController implements Initializable, NavigationParameterAware {
private String player2Name = "Player 2";
private GameService gameService;
+ private char localPlayerId;
private final GameRecordingService recordingService = new GameRecordingService();
private GameRecord replayData;
@Override
public void initialize(URL url, ResourceBundle rb) {
+
cells = new Button[][]{
- {cell00, cell01, cell02},
- {cell10, cell11, cell12},
- {cell20, cell21, cell22}
+ {cell00, cell01, cell02},
+ {cell10, cell11, cell12},
+ {cell20, cell21, cell22}
};
gameBoard = new GameBoard();
setupCellHandlers();
@@ -127,6 +134,7 @@ private void applyGameModeSettings() {
difficultyBadge.setManaged(false);
changeDifficultyButton.setVisible(false);
changeDifficultyButton.setManaged(false);
+
gameService = new OfflinePVPService();
}
case OFFLINE_PVE -> {
@@ -135,6 +143,7 @@ private void applyGameModeSettings() {
difficultyBadge.setManaged(true);
changeDifficultyButton.setVisible(true);
changeDifficultyButton.setManaged(true);
+
gameService = new OfflinePVEAIService();
}
case ONLINE_PVP -> {
@@ -143,6 +152,35 @@ private void applyGameModeSettings() {
difficultyBadge.setManaged(false);
changeDifficultyButton.setVisible(false);
changeDifficultyButton.setManaged(false);
+
+ boolean amIPlayer1 = OnlinePVPService.getInstance().shouldPlayFirst();
+ localPlayerId = amIPlayer1 ? GameBoard.PLAYER_X : GameBoard.PLAYER_O;
+
+ System.out.println("I am Player: " + localPlayerId);
+
+ gameService = OnlinePVPService.getInstance().setMoveListener((move) -> {
+ Platform.runLater(() -> {
+ char remotePlayer = (localPlayerId == GameBoard.PLAYER_X) ? GameBoard.PLAYER_O : GameBoard.PLAYER_X;
+
+ if (gameBoard.makeMove(move.getRow(), move.getCol(), remotePlayer)) {
+ updateCell(move.getRow(), move.getCol(), remotePlayer);
+
+ if (gameBoard.getGameState() != GameState.IN_PROGRESS) {
+ handleGameEnd();
+ } else {
+ updateTurnIndicator();
+ enableBoard();
+ }
+ }
+ });
+ });
+
+ if (amIPlayer1) {
+ enableBoard();
+ } else {
+ disableBoard();
+ }
+ updateTurnIndicator();
}
case REPLAY -> {
opponentTypeLabel.setText("REPLAY");
@@ -176,6 +214,10 @@ private void setupButtonHandlers() {
NavigationManager.navigate(Screens.PRIMARY, NavigationAction.REPLACE_ALL);
});
+ backButton.setOnAction(e -> {
+ NavigationManager.pop();
+ });
+
backButton.setOnAction(e -> NavigationManager.pop());
settingsButton.setOnAction(e -> NavigationManager.navigate(Screens.SETTINGS, NavigationAction.REPLACE_ALL));
changeDifficultyButton.setOnAction(e -> {});
@@ -211,60 +253,84 @@ private void handleCellClick(int row, int col) {
if (!gameBoard.isValidMove(row, col)) return;
if (gameMode == GameMode.REPLAY) return;
- char currentPlayer = gameBoard.getCurrentPlayer();
+ if (gameMode == GameMode.ONLINE_PVP && gameBoard.getCurrentPlayer() != localPlayerId) {
+ return;
+ }
switch (gameMode) {
- case OFFLINE_PVP -> {
- gameBoard.makeMove(row, col, currentPlayer);
- updateCell(row, col, currentPlayer);
- if (!checkGameEnd()) {
- updateTurnIndicator();
- }
- }
- case OFFLINE_PVE -> {
- gameBoard.makeMove(row, col, GameBoard.PLAYER_X);
- updateCell(row, col, GameBoard.PLAYER_X);
- if (checkGameEnd()) return;
+ case OFFLINE_PVP -> handleOfflinePvp(row, col);
+ case OFFLINE_PVE -> handleOfflinePve(row, col);
+ case ONLINE_PVP -> handleOnlinePvp(row, col);
+ }
+ }
- disableBoard();
- PauseTransition pause = new PauseTransition(Duration.millis(500));
- pause.setOnFinished(e -> {
- Move aiMove = gameService.getNextMove(gameBoard, GameBoard.PLAYER_O);
- gameService.makeMove(aiMove, GameBoard.PLAYER_O, gameBoard);
- if (aiMove != null) {
- updateCell(aiMove.getRow(), aiMove.getCol(), GameBoard.PLAYER_O);
- }
- if (!checkGameEnd()) {
- enableBoard();
- gameBoard.switchPlayer();
- updateTurnIndicator();
- }
- });
- pause.play();
- }
+ private void handleOfflinePvp(int row, int col) {
+ char currentPlayer = gameBoard.getCurrentPlayer();
+
+ performMove(row, col, currentPlayer);
+
+ if (!checkGameEnd()) {
+ gameBoard.switchPlayer();
+ updateTurnIndicator();
}
}
- private void startReplay() {
+ private void handleOfflinePve(int row, int col) {
+ performMove(row, col, GameBoard.PLAYER_X);
+
+ if (checkGameEnd()) {
+ return;
+ }
+
+ scheduleAiTurn();
+ }
+
+ private void handleOnlinePvp(int row, int col) {
+ performMove(row, col, localPlayerId);
+
+ Move move = new Move(row, col);
+ gameService.makeMove(move, localPlayerId);
+
disableBoard();
- Timeline timeline = new Timeline();
- int delay = 0;
- for (var move : replayData.moves()) {
- delay += 1000;
- KeyFrame frame = new KeyFrame(Duration.millis(delay), e -> {
- gameBoard.forceMove(move.row(), move.col(), move.player());
- updateCell(move.row(), move.col(), move.player());
-
- gameBoard.switchPlayer();
- updateTurnIndicator();
- checkGameEnd();
- });
- timeline.getKeyFrames().add(frame);
+ if (gameBoard.getGameState() != GameState.IN_PROGRESS) {
+ handleGameEnd();
+ } else {
+ updateTurnIndicator();
}
- timeline.play();
}
+ private void performMove(int row, int col, char player) {
+ gameBoard.makeMove(row, col, player);
+ updateCell(row, col, player);
+ }
+
+ private void scheduleAiTurn() {
+ disableBoard();
+
+ PauseTransition pause = new PauseTransition(Duration.millis(500));
+ pause.setOnFinished(e -> executeAiMove());
+ pause.play();
+ }
+
+ private void executeAiMove() {
+ Move aiMove = gameService.getNextMove(gameBoard, GameBoard.PLAYER_O);
+
+ if (aiMove != null) {
+ gameService.makeMove(aiMove, GameBoard.PLAYER_O, gameBoard);
+ performMove(aiMove.getRow(), aiMove.getCol(), GameBoard.PLAYER_O);
+ }
+
+ if (!checkGameEnd()) {
+ enableBoard();
+ gameBoard.switchPlayer();
+ updateTurnIndicator();
+ }
+ }
+
+
+
+
private boolean checkGameEnd() {
GameState state = gameBoard.getGameState();
if (state != GameState.IN_PROGRESS) {
@@ -290,11 +356,16 @@ private void updateCell(int row, int col, char player) {
}
private void updateTurnIndicator() {
- char current = gameBoard.getCurrentPlayer();
- boolean isPlayerX = (current == GameBoard.PLAYER_X);
-
+ char currentPlayer = gameBoard.getCurrentPlayer();
+
+ if (currentPlayer == GameBoard.PLAYER_X) {
+ setActiveCard(playerCard, opponentCard);
+ }
+ else {
+ setActiveCard(opponentCard, playerCard);
+ }
if (gameMode == GameMode.OFFLINE_PVP || gameMode == GameMode.OFFLINE_PVE || gameMode == GameMode.REPLAY) {
- if (isPlayerX) {
+ if (localPlayerId == GameBoard.PLAYER_X) {
setActiveCard(playerCard, opponentCard);
} else {
setActiveCard(opponentCard, playerCard);
@@ -404,8 +475,18 @@ public void resetGame() {
}
}
modalOverlay.setVisible(false);
+
updateTurnIndicator();
- enableBoard();
+
+ if (gameMode == GameMode.ONLINE_PVP) {
+ if (localPlayerId == GameBoard.PLAYER_X) {
+ enableBoard();
+ } else {
+ disableBoard();
+ }
+ } else {
+ enableBoard();
+ }
}
private void disableBoard() {
@@ -423,4 +504,24 @@ private void enableBoard() {
}
}
}
+
+ private void startReplay() {
+ disableBoard();
+ Timeline timeline = new Timeline();
+ int delay = 0;
+
+ for (var move : replayData.moves()) {
+ delay += 1000;
+ KeyFrame frame = new KeyFrame(Duration.millis(delay), e -> {
+ gameBoard.forceMove(move.row(), move.col(), move.player());
+ updateCell(move.row(), move.col(), move.player());
+
+ gameBoard.switchPlayer();
+ updateTurnIndicator();
+ checkGameEnd();
+ });
+ timeline.getKeyFrames().add(frame);
+ }
+ timeline.play();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java
index 8df99d2..d2f64b4 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/GameModeScreenController.java
@@ -35,12 +35,14 @@ public void initialize() {
}
private void setupActions() {
+
offlineCard.setOnMouseClicked(e -> {
- NavigationManager.navigate(Screens.PVP_SETUP, NavigationAction.REPLACE);
+ NavigationManager.navigate(Screens.OFFLINE_MODE_SELECTION, NavigationAction.PUSH);
});
-
+
onlineCard.setOnMouseClicked(e -> {
NavigationManager.navigate(Screens.SERVER_CONNECTION, NavigationAction.REPLACE_ALL);
+
});
}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java
new file mode 100644
index 0000000..d7d4851
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/HomeController.java
@@ -0,0 +1,143 @@
+package com.boredxgames.tictactoeclient.presentation;
+
+import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationAction;
+import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationManager;
+import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens;
+import com.boredxgames.tictactoeclient.domain.model.*;
+import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager;
+import com.boredxgames.tictactoeclient.domain.services.communication.MessageRouter;
+import com.boredxgames.tictactoeclient.domain.services.game.OnlinGameSession;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.Alert;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.StackPane;
+
+public class HomeController implements Initializable {
+
+ @FXML private StackPane rootStack;
+ @FXML private BorderPane mainContent;
+ @FXML private Pane backgroundPane;
+ @FXML private Label usernameLabel;
+ @FXML private Label scoreLabel;
+ @FXML private PlayersController playersViewController;
+@FXML private LeaderboardController leaderboardViewController;
+ private Alert currentIncomingRequestAlert;
+
+ @Override
+ public void initialize(URL url, ResourceBundle rb) {
+
+ MessageRouter.setHomeController(this);
+
+ AuthResponseEntity currentUser = ServerConnectionManager.getInstance().getPlayer();
+ if (currentUser != null) {
+ if(usernameLabel != null) usernameLabel.setText(currentUser.getUserName());
+ if(scoreLabel != null) scoreLabel.setText("Score: " + currentUser.getScore());
+ }
+
+
+ OnlinGameSession.getInstance().requestLeaderboard();
+ OnlinGameSession.getInstance().requestAvailablePlayers();
+
+
+ }
+
+ public void updatePlayersList(AvailablePlayersInfo info) {
+ if (playersViewController != null) {
+ playersViewController.updateList(info);
+ }
+ }
+
+ public void handleServerGameResponse(GameResponseInfo info) {
+ OnlinGameSession.getInstance().setWaiting(false);
+
+ if (info.isAccepted()) {
+ System.out.println("Opponent Accepted. Waiting for GAME_START event...");
+ } else {
+ Platform.runLater(() -> {
+ Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ alert.setTitle("Challenge Declined");
+ alert.setHeaderText(null);
+ alert.setContentText("Player " + info.getResponderUserName()+ " declined your challenge.");
+ alert.showAndWait();
+
+ OnlinGameSession.getInstance().requestAvailablePlayers();
+ });
+ }
+ }
+
+ public void showIncomingGameRequest(GameRequestInfo info) {
+ Platform.runLater(() -> {
+ if (currentIncomingRequestAlert != null && currentIncomingRequestAlert.isShowing()) {
+ currentIncomingRequestAlert.close();
+ }
+
+ Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
+ alert.setTitle("Incoming Challenge");
+ alert.setHeaderText("Game Request");
+ alert.setContentText(info.getRequesterUserName()+ " wants to play Tic-Tac-Toe!");
+
+ ButtonType acceptBtn = new ButtonType("Accept");
+ ButtonType declineBtn = new ButtonType("Decline");
+ alert.getButtonTypes().setAll(acceptBtn, declineBtn);
+
+ this.currentIncomingRequestAlert = alert;
+
+ alert.showAndWait().ifPresent(type -> {
+ this.currentIncomingRequestAlert = null;
+ boolean accepted = (type == acceptBtn);
+ OnlinGameSession.getInstance().sendGameResponse(info, accepted);
+ });
+ });
+ }
+
+
+ public void dismissIncomingRequest() {
+ Platform.runLater(() -> {
+ if (currentIncomingRequestAlert != null && currentIncomingRequestAlert.isShowing()) {
+ currentIncomingRequestAlert.setResult(ButtonType.CANCEL);
+ currentIncomingRequestAlert.close();
+ currentIncomingRequestAlert = null;
+ }
+ });
+ }
+
+ public void showErrorAlert(String message) {
+ OnlinGameSession.getInstance().setWaiting(false);
+ Platform.runLater(() -> {
+ Alert alert = new Alert(Alert.AlertType.ERROR);
+ alert.setTitle("Error");
+ alert.setHeaderText(null);
+ alert.setContentText(message);
+ alert.showAndWait();
+
+ OnlinGameSession.getInstance().requestAvailablePlayers();
+ });
+ }
+
+ @FXML
+ private void onSignOut() {
+ ServerConnectionManager.getInstance().disconnect();
+ ServerConnectionManager.getInstance().setPlayer(null);
+ NavigationManager.navigate(Screens.SERVER_CONNECTION, NavigationAction.REPLACE);
+ }
+
+ public void updateLeaderboardUI(AvailablePlayersInfo info) {
+ if (leaderboardViewController != null) {
+ leaderboardViewController.updateLeaderboard(info);
+ }
+}
+ @FXML
+ private void openSettings(ActionEvent event) {
+ System.out.println("Settings Button Clicked");
+ NavigationManager.navigate(Screens.SETTINGS, NavigationAction.PUSH);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/LeaderboardController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/LeaderboardController.java
new file mode 100644
index 0000000..db87a2d
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/LeaderboardController.java
@@ -0,0 +1,122 @@
+package com.boredxgames.tictactoeclient.presentation;
+
+import com.boredxgames.tictactoeclient.domain.model.AuthResponseEntity;
+import com.boredxgames.tictactoeclient.domain.model.AvailablePlayersInfo;
+import com.boredxgames.tictactoeclient.domain.model.PlayerEntity;
+import com.boredxgames.tictactoeclient.domain.network.ServerConnectionManager;
+import java.net.URL;
+import java.util.ResourceBundle;
+import java.util.Vector;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.VBox;
+import javafx.scene.shape.Circle;
+
+public class LeaderboardController implements Initializable {
+
+ @FXML private VBox leaderboardContainer;
+
+ @FXML private HBox myRankBar;
+ @FXML private Label myRankLabel;
+ @FXML private Label myNameLabel;
+ @FXML private Label myScoreLabel;
+
+ @Override
+ public void initialize(URL url, ResourceBundle rb) {
+ }
+
+ public void updateLeaderboard(AvailablePlayersInfo info) {
+ Platform.runLater(() -> {
+ leaderboardContainer.getChildren().clear();
+
+ Vector allPlayers = new Vector<>();
+ if (info.getOnlinePlayers() != null) allPlayers.addAll(info.getOnlinePlayers());
+ if (info.getInGamePlayers() != null) allPlayers.addAll(info.getInGamePlayers());
+ if (info.getPendingPlayers() != null) allPlayers.addAll(info.getPendingPlayers());
+
+ allPlayers.sort((p1, p2) -> Integer.compare(p2.getScore(), p1.getScore()));
+
+ AuthResponseEntity me = ServerConnectionManager.getInstance().getPlayer();
+ boolean foundMe = false;
+
+ if (me != null && myRankBar != null) {
+ PlayerEntity myPlayerState = allPlayers.stream()
+ .filter(p -> p.getId().equals(me.getId()))
+ .findFirst()
+ .orElse(null);
+
+ if (myPlayerState != null) {
+ foundMe = true;
+ if (myPlayerState.getScore() == 0) {
+ updateMyFooter("NA", myPlayerState);
+ } else {
+ int myRank = allPlayers.indexOf(myPlayerState) + 1;
+ updateMyFooter(String.valueOf(myRank), myPlayerState);
+ }
+ }
+ myRankBar.setVisible(foundMe);
+ }
+ int count = 0;
+ for (PlayerEntity p : allPlayers) {
+ if (count >= 5 || p.getScore() <= 0) break;
+
+ leaderboardContainer.getChildren().add(createRow(p, count + 1));
+ count++;
+ }
+ });
+ }
+
+ private void updateMyFooter(String rankText, PlayerEntity p) {
+ if (myRankLabel != null) myRankLabel.setText(rankText);
+ if (myNameLabel != null) myNameLabel.setText(p.getUsername());
+ if (myScoreLabel != null) myScoreLabel.setText(String.valueOf(p.getScore()));
+ }
+
+ private HBox createRow(PlayerEntity p, int rank) {
+ HBox row = new HBox(20);
+ row.setAlignment(Pos.CENTER_LEFT);
+ row.getStyleClass().add("leaderboard-row");
+
+ Label rankLabel = new Label();
+ rankLabel.setPrefWidth(60);
+
+ if (rank == 1) {
+ rankLabel.setText("🏆");
+ rankLabel.setStyle("-fx-text-fill: #FFD700; -fx-font-size: 20px;");
+ } else if (rank == 2) {
+ rankLabel.setText("🥈");
+ rankLabel.setStyle("-fx-text-fill: #C0C0C0; -fx-font-size: 20px;");
+ } else if (rank == 3) {
+ rankLabel.setText("🥉");
+ rankLabel.setStyle("-fx-text-fill: #CD7F32; -fx-font-size: 20px;");
+ } else {
+ rankLabel.setText(String.valueOf(rank));
+ rankLabel.setStyle("-fx-text-fill: #94a3b8; -fx-font-size: 14px; -fx-font-weight: bold;");
+ }
+
+ HBox nameBox = new HBox(10);
+ nameBox.setAlignment(Pos.CENTER_LEFT);
+ HBox.setHgrow(nameBox, Priority.ALWAYS);
+
+ Circle avatar = new Circle(18);
+ avatar.getStyleClass().add("avatar-profile");
+
+ Label nameLabel = new Label(p.getUsername());
+ nameLabel.setStyle("-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 14px;");
+
+ nameBox.getChildren().addAll(avatar, nameLabel);
+
+ Label scoreLabel = new Label(p.getScore() + " PTS");
+ scoreLabel.setPrefWidth(100);
+ scoreLabel.setAlignment(Pos.CENTER_RIGHT);
+ scoreLabel.setStyle("-fx-text-fill: #00ff9d; -fx-font-family: 'Monospaced'; -fx-font-weight: bold;");
+
+ row.getChildren().addAll(rankLabel, nameBox, scoreLabel);
+ return row;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java
new file mode 100644
index 0000000..99f8601
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/OfflineModeSelectionController.java
@@ -0,0 +1,40 @@
+package com.boredxgames.tictactoeclient.presentation;
+
+import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationAction;
+import com.boredxgames.tictactoeclient.domain.managers.navigation.NavigationManager;
+import com.boredxgames.tictactoeclient.domain.managers.navigation.Screens;
+import javafx.fxml.Initializable;
+import javafx.fxml.FXML;
+import javafx.scene.input.MouseEvent;
+import javafx.event.ActionEvent;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+
+public class OfflineModeSelectionController implements Initializable {
+ @FXML
+ @Override
+ public void initialize(URL url, ResourceBundle resourceBundle) {
+
+ }
+ @FXML
+ private void handleSettings(ActionEvent event) {
+ NavigationManager.navigate(Screens.SETTINGS, NavigationAction.PUSH);
+ }
+ @FXML
+ private void handleBackToMainMenu(ActionEvent event) {
+ NavigationManager.pop();
+ }
+
+ @FXML
+ private void handlePlayerVsPlayer(MouseEvent event) {
+ NavigationManager.navigate(Screens.PVP_SETUP, NavigationAction.PUSH);
+ }
+
+ @FXML
+ private void handlePlayerVsCPU(MouseEvent event) {
+ NavigationManager.navigate(Screens.DifficultySelection, NavigationAction.PUSH);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/PlayersController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/PlayersController.java
new file mode 100644
index 0000000..a9dddc9
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/PlayersController.java
@@ -0,0 +1,119 @@
+package com.boredxgames.tictactoeclient.presentation;
+
+import com.boredxgames.tictactoeclient.domain.model.AvailablePlayersInfo;
+import com.boredxgames.tictactoeclient.domain.model.PlayerEntity;
+import com.boredxgames.tictactoeclient.domain.services.game.OnlinGameSession;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.geometry.Pos;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.StackPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.shape.Circle;
+
+public class PlayersController {
+
+ @FXML
+ private VBox playersContainer;
+
+ public void updateList(AvailablePlayersInfo info) {
+ playersContainer.getChildren().clear();
+
+ if (info.getOnlinePlayers() != null) {
+ for (PlayerEntity p : info.getOnlinePlayers()) {
+ playersContainer.getChildren().add(createPlayerCard(p, "ONLINE", "online", true));
+ }
+ }
+
+ if (info.getInGamePlayers() != null) {
+ for (PlayerEntity p : info.getInGamePlayers()) {
+ playersContainer.getChildren().add(createPlayerCard(p, "IN GAME", "ingame", false));
+ }
+ }
+
+ if (info.getPendingPlayers() != null) {
+ for (PlayerEntity p : info.getPendingPlayers()) {
+ playersContainer.getChildren().add(createPlayerCard(p, "Pending", "pending", false));
+ }
+ }
+
+ if (playersContainer.getChildren().isEmpty()) {
+ Label placeholder = new Label("No other players connected.");
+ placeholder.setStyle("-fx-text-fill: #64748b; -fx-font-size: 14px; -fx-padding: 20;");
+ playersContainer.getChildren().add(placeholder);
+ }
+ }
+
+ private HBox createPlayerCard(PlayerEntity p, String statusText, String type, boolean canChallenge) {
+ HBox card = new HBox(16);
+ card.getStyleClass().add("player-card");
+ card.setAlignment(Pos.CENTER_LEFT);
+
+ HBox.setHgrow(card, Priority.ALWAYS);
+ card.setMaxWidth(Double.MAX_VALUE);
+
+ StackPane avatarStack = new StackPane();
+ Circle avatarBg = new Circle(24);
+ avatarBg.setStyle("-fx-fill: #334155;");
+
+ Circle statusDot = new Circle(6);
+ statusDot.getStyleClass().add("status-dot-" + type);
+ StackPane.setAlignment(statusDot, Pos.BOTTOM_RIGHT);
+ avatarStack.getChildren().addAll(avatarBg, statusDot);
+
+ VBox infoBox = new VBox(2);
+ infoBox.setAlignment(Pos.CENTER_LEFT);
+
+ Label nameLabel = new Label(p.getUsername());
+ nameLabel.getStyleClass().add("player-name");
+
+ HBox statusLine = new HBox(6);
+ statusLine.setAlignment(Pos.CENTER_LEFT);
+ Label statusLabel = new Label(statusText);
+ statusLabel.getStyleClass().add("status-text-" + type);
+ Label eloLabel = new Label("• " + p.getScore() + " pts");
+ eloLabel.getStyleClass().add("player-subtext");
+
+ statusLine.getChildren().addAll(statusLabel, eloLabel);
+ infoBox.getChildren().addAll(nameLabel, statusLine);
+
+ Pane spacer = new Pane();
+ HBox.setHgrow(spacer, Priority.ALWAYS);
+
+ Button actionBtn = new Button();
+
+ if (canChallenge) {
+ actionBtn.setText("Challenge ⚔");
+ actionBtn.getStyleClass().add("btn-challenge");
+
+ actionBtn.setOnAction(e -> {
+ System.out.println("response tyepeeeeeeeeeeeeeeeeeee : "+OnlinGameSession.getInstance().isWaiting());
+ if (OnlinGameSession.getInstance().isWaiting()) {
+ Alert alert = new Alert(Alert.AlertType.WARNING);
+ alert.setTitle("Please Wait");
+ alert.setHeaderText(null);
+ alert.setContentText("You already have a pending request. Please wait for the response.");
+ alert.showAndWait();
+ return;
+ }
+
+ actionBtn.setDisable(true);
+ actionBtn.setText("Sent...");
+ OnlinGameSession.getInstance().sendGameRequest(p.getId());
+ });
+
+ } else {
+ actionBtn.setText(statusText);
+ actionBtn.getStyleClass().add("btn-disabled");
+ actionBtn.setDisable(true);
+ }
+
+ card.getChildren().addAll(avatarStack, infoBox, spacer, actionBtn);
+ return card;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/RecordedGamesController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/RecordedGamesController.java
new file mode 100644
index 0000000..1554de2
--- /dev/null
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/RecordedGamesController.java
@@ -0,0 +1,26 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/javafx/FXMLController.java to edit this template
+ */
+package com.boredxgames.tictactoeclient.presentation;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+import javafx.fxml.Initializable;
+
+/**
+ * FXML Controller class
+ *
+ * @author moham
+ */
+public class RecordedGamesController implements Initializable {
+
+ /**
+ * Initializes the controller class.
+ */
+ @Override
+ public void initialize(URL url, ResourceBundle rb) {
+ // TODO
+ }
+
+}
diff --git a/src/main/java/com/boredxgames/tictactoeclient/presentation/Server_connectionController.java b/src/main/java/com/boredxgames/tictactoeclient/presentation/Server_connectionController.java
index 4ea4e4f..e4d88b0 100644
--- a/src/main/java/com/boredxgames/tictactoeclient/presentation/Server_connectionController.java
+++ b/src/main/java/com/boredxgames/tictactoeclient/presentation/Server_connectionController.java
@@ -42,7 +42,7 @@ public void initialize(URL location, ResourceBundle resources) {
backgroundPane.getWidth(),
backgroundPane.getHeight()
);
-
+
});
serverPresets.put("Local Server", "127.0.0.1");
@@ -52,10 +52,10 @@ public void initialize(URL location, ResourceBundle resources) {
ipComboBox.getSelectionModel().select("Local Server");
ipComboBox.setEditable(true);
-
+
}
-
+
@FXML
private void connect() {
String input = ipComboBox.getValue();
@@ -98,7 +98,7 @@ private void onBackClicked() {
stopConnectionThread();
NavigationManager.navigate(Screens.GAME_MODE, NavigationAction.REPLACE_ALL);
}
-
+
private void stopConnectionThread() {
if (connectionThread != null && connectionThread.isAlive()) {
connectionThread.interrupt();
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index a629653..701ce6e 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -5,16 +5,17 @@
requires java.prefs;
requires org.json;
requires com.google.gson;
- requires java.desktop;
+requires java.desktop;
requires javafx.media;
-
opens com.boredxgames.tictactoeclient.domain.managers.audio to javafx.fxml;
- opens com.boredxgames.tictactoeclient to javafx.fxml;
- opens com.boredxgames.tictactoeclient.domain.services.communication to com.google.gson;
- opens com.boredxgames.tictactoeclient.domain.model to com.google.gson;
exports com.boredxgames.tictactoeclient;
exports com.boredxgames.tictactoeclient.presentation;
- opens com.boredxgames.tictactoeclient.presentation to javafx.fxml;
exports com.boredxgames.tictactoeclient.domain.managers.state;
exports com.boredxgames.tictactoeclient.domain.managers.state.records;
+
+ opens com.boredxgames.tictactoeclient.presentation to javafx.fxml;
+ opens com.boredxgames.tictactoeclient to com.google.gson, javafx.fxml;
+ opens com.boredxgames.tictactoeclient.domain.model to com.google.gson;
+ opens com.boredxgames.tictactoeclient.domain.services.communication to com.google.gson;
+
}
diff --git a/src/main/resources/css/features/authentication_screen.css b/src/main/resources/css/features/authentication_screen.css
index adca663..8d82b96 100644
--- a/src/main/resources/css/features/authentication_screen.css
+++ b/src/main/resources/css/features/authentication_screen.css
@@ -122,3 +122,24 @@
-fx-text-fill: #4f5ef7;
-fx-underline: true;
}
+
+.dialog-pane {
+ -fx-background-color: #2b2b2b;
+}
+
+.dialog-pane .label {
+ -fx-text-fill: white;
+}
+
+.dialog-pane:header .header-panel {
+ -fx-background-color: #3c3f41;
+}
+
+.dialog-pane .button {
+ -fx-background-color: #4a4a4a;
+ -fx-text-fill: white;
+}
+
+.dialog-pane .button:hover {
+ -fx-background-color: #5a5a5a;
+}
\ No newline at end of file
diff --git a/src/main/resources/css/features/home.css b/src/main/resources/css/features/home.css
new file mode 100644
index 0000000..f264ef9
--- /dev/null
+++ b/src/main/resources/css/features/home.css
@@ -0,0 +1,239 @@
+
+.root {
+ -fx-background-color: #111022;
+ -fx-font-family: "Noto Sans", "Segoe UI", sans-serif;
+ -fx-text-fill: white;
+ -fx-focus-color: rgba(0,0,0,0);
+ -fx-faint-focus-color: rgba(0,0,0,0);
+}
+
+.label {
+ -fx-text-fill: white;
+}
+
+
+.scroll-pane,
+.scroll-pane .viewport,
+.scroll-pane .content {
+ -fx-background-color: rgba(0,0,0,0) !important;
+ -fx-background-insets: 0;
+ -fx-padding: 0;
+}
+
+/* Also target your specific class just in case */
+.edge-to-edge,
+.edge-to-edge .viewport,
+.edge-to-edge .content {
+ -fx-background-color: rgba(0,0,0,0) !important;
+}
+
+.flat-tabs {
+ -fx-background-color: rgba(0,0,0,0);
+}
+
+.flat-tabs .tab-header-area {
+ -fx-background-color: rgba(0,0,0,0) !important;
+ -fx-padding: 0;
+}
+
+.flat-tabs .tab-header-background {
+ -fx-background-color: rgba(0,0,0,0) !important;
+ -fx-background-insets: 0;
+}
+
+.flat-tabs .headers-region {
+ -fx-background-color: rgba(0,0,0,0) !important;
+}
+
+.flat-tabs .tab-content-area {
+ -fx-background-color: rgba(0,0,0,0) !important;
+ -fx-padding: 0;
+ -fx-background-insets: 0;
+}
+
+/* 4. TAB BUTTONS */
+.flat-tabs .tab {
+ -fx-background-color: rgba(0,0,0,0);
+ -fx-background-insets: 0;
+ -fx-background-radius: 0;
+ -fx-padding: 15;
+ -fx-border-width: 0 0 2 0;
+ -fx-border-color: rgba(0,0,0,0);
+}
+
+.flat-tabs .tab-label {
+ -fx-text-fill: #94a3b8;
+ -fx-font-weight: bold;
+ -fx-font-size: 14px;
+}
+
+.flat-tabs .tab:hover {
+ -fx-background-color: rgba(255, 255, 255, 0.05);
+}
+
+.flat-tabs .tab:selected {
+ -fx-background-color: rgba(0,0,0,0) !important;
+ -fx-border-color: transparent transparent #463eef transparent;
+}
+
+.flat-tabs .tab:selected .tab-label {
+ -fx-text-fill: #463eef !important;
+}
+
+.player-card {
+ -fx-background-color: #1c1b2e;
+ -fx-background-radius: 16;
+ -fx-border-color: rgba(255,255,255,0.05);
+ -fx-border-width: 2px;
+ -fx-border-radius: 16;
+ -fx-padding: 10;
+ -fx-cursor: hand;
+ -fx-transition: -fx-background-color 0.2s, -fx-border-color 0.2s;
+}
+
+.player-card:hover {
+ -fx-background-color: #252438 !important;
+ -fx-border-color: #463eef !important;
+ -fx-border-width: 2px; /* Must match non-hover width */
+}
+
+.player-name { -fx-text-fill: white; -fx-font-size: 15px; -fx-font-weight: bold; }
+.player-subtext { -fx-text-fill: #94a3b8; -fx-font-size: 11px; }
+
+/* 6. TOP BAR */
+.top-bar {
+ -fx-background-color: rgba(28, 27, 46, 0.95);
+ -fx-padding: 20;
+ -fx-border-color: rgba(255, 255, 255, 0.1);
+ -fx-border-width: 0 0 1 0;
+}
+
+.app-icon { -fx-text-fill: #463eef; -fx-font-size: 24px; -fx-font-weight: bold; }
+.app-title { -fx-text-fill: white; -fx-font-size: 20px; -fx-font-weight: 700; }
+.username { -fx-text-fill: white !important; -fx-font-weight: 700; -fx-font-size: 14px; }
+.user-level { -fx-text-fill: #94a3b8; -fx-font-size: 11px; }
+.avatar-profile { -fx-stroke: #463eef; -fx-stroke-width: 2; -fx-fill: #2d2d44; }
+
+.signout-btn {
+ -fx-background-color: #1c1b2e;
+ -fx-text-fill: #cbd5e1;
+ -fx-font-weight: bold;
+ -fx-background-radius: 8;
+ -fx-border-color: rgba(255,255,255,0.1);
+ -fx-border-radius: 8;
+ -fx-cursor: hand;
+ -fx-padding: 10;
+}
+.signout-btn:hover { -fx-background-color: #2a2940; -fx-border-color: rgba(255,255,255,0.2); }
+
+.quick-match-btn {
+ -fx-background-color: #463eef;
+ -fx-text-fill: white;
+ -fx-font-size: 15px;
+ -fx-font-weight: bold;
+ -fx-background-radius: 30;
+ -fx-padding: 15;
+ -fx-effect: dropshadow(gaussian, rgba(70, 62, 239, 0.6), 15, 0, 0, 4);
+ -fx-cursor: hand;
+}
+.quick-match-btn:hover { -fx-background-color: #6d66f5; }
+
+.btn-challenge { -fx-background-color: #463eef; -fx-text-fill: white; -fx-font-weight: bold; -fx-background-radius: 8; -fx-cursor: hand; -fx-padding: 8; }
+.btn-challenge:hover { -fx-background-color: #6d66f5; }
+
+.icon-btn { -fx-background-color: rgba(0,0,0,0); -fx-text-fill: #94a3b8; -fx-font-size: 16px; -fx-cursor: hand; }
+.icon-btn:hover { -fx-text-fill: white; -fx-background-color: rgba(255,255,255,0.1); -fx-background-radius: 8; }
+
+.btn-spectate { -fx-background-color: rgba(0,0,0,0); -fx-text-fill: #cbd5e1; -fx-border-color: rgba(255,255,255,0.2); -fx-border-radius: 8; -fx-padding: 8; -fx-font-weight: bold; }
+.btn-spectate:hover { -fx-background-color: rgba(255,255,255,0.05); -fx-text-fill: white; }
+.btn-disabled { -fx-background-color: rgba(0,0,0,0); -fx-text-fill: #475569; -fx-padding: 8; -fx-border-color: rgba(255,255,255,0.05); -fx-border-radius: 8; }
+
+/* Status Indicators */
+.status-text-online { -fx-text-fill: #00ff9d; -fx-font-weight: bold; -fx-font-size: 10px; }
+.status-text-ingame { -fx-text-fill: #ff9e00; -fx-font-weight: bold; -fx-font-size: 10px; }
+.status-text-busy { -fx-text-fill: #64748b; -fx-font-weight: bold; -fx-font-size: 10px; }
+.status-dot-online { -fx-fill: #00ff9d; }
+.status-dot-ingame { -fx-fill: #ff9e00; }
+.status-dot-busy { -fx-fill: #64748b; }
+
+/* --- SCROLLBAR STYLING --- */
+.modern-scroll .viewport {
+ -fx-background-color: rgba(0,0,0,0);
+}
+
+.modern-scroll .scroll-bar:vertical {
+ -fx-background-color: rgba(0,0,0,0);
+ -fx-pref-width: 12;
+}
+
+.modern-scroll .scroll-bar:vertical .track {
+ -fx-background-color: rgba(255, 255, 255, 0.05);
+ -fx-background-radius: 10;
+}
+
+.modern-scroll .scroll-bar:vertical .thumb {
+ -fx-background-color: #463eef;
+ -fx-background-radius: 10;
+ -fx-background-insets: 2;
+}
+
+.modern-scroll .scroll-bar:vertical .thumb:hover {
+ -fx-background-color: #6d66f5;
+}
+
+.modern-scroll .scroll-bar .increment-button,
+.modern-scroll .scroll-bar .decrement-button {
+ -fx-visible: false;
+ -fx-padding: 0;
+ -fx-pref-height: 0;
+}
+
+/* --- RESPONSIVE TWEAKS --- */
+.player-card {
+ -fx-min-height: 70;
+ -fx-pref-width: 100%;
+ -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 10, 0, 0, 4);
+}
+
+/* --- LEADERBOARD SPECIFIC STYLES --- */
+
+.leaderboard-card {
+ -fx-background-color: #0f0e1e; /* Slightly darker than root */
+ -fx-background-radius: 16;
+ -fx-border-color: rgba(255,255,255,0.05);
+ -fx-border-radius: 16;
+ -fx-padding: 0;
+}
+
+.leaderboard-header-row {
+ -fx-padding: 15 ;
+ -fx-border-color: rgba(255,255,255,0.05);
+ -fx-border-width: 0 0 1 0;
+}
+
+.column-header {
+ -fx-text-fill: #64748b; /* Muted text for headers */
+ -fx-font-size: 11px;
+ -fx-font-weight: bold;
+ -fx-letter-spacing: 1px;
+}
+
+.leaderboard-row {
+ -fx-padding: 15 ;
+ -fx-background-color: transparent;
+ -fx-border-width: 0 0 1 0;
+ -fx-border-color: rgba(255,255,255,0.02);
+ -fx-cursor: hand;
+}
+
+.leaderboard-row:hover {
+ -fx-background-color: rgba(255,255,255,0.03);
+}
+
+/* The Blue "My Rank" Footer */
+.my-rank-footer {
+ -fx-background-color: linear-gradient(to right, #463eef, #6d66f5);
+ -fx-background-radius: 16;
+ -fx-padding: 15 ;
+ -fx-effect: dropshadow(three-pass-box, rgba(70, 62, 239, 0.4), 20, 0, 0, 8);
+}
diff --git a/src/main/resources/css/features/offline_mode_selection.css b/src/main/resources/css/features/offline_mode_selection.css
new file mode 100644
index 0000000..870c3ba
--- /dev/null
+++ b/src/main/resources/css/features/offline_mode_selection.css
@@ -0,0 +1,193 @@
+.root {
+ -offline-card-radius: 24;
+ -icon-size: 48;
+
+ -text-badge: derive(-text-main, -50%);
+
+ -fx-background-color: -bg-main;
+ -fx-font-family: 'System';
+}
+
+.background-effects {
+ -fx-opacity: 0.15;
+}
+
+.blur-circle-top, .blur-circle-bottom {
+ -fx-background-color: -primary;
+ -fx-background-radius: 1000;
+ -fx-effect: dropshadow(gaussian, -primary, 120, 0.6, 0, 0);
+}
+
+.header {
+ -fx-border-color: derive(-bg-card, 20%);
+ -fx-border-width: 0 0 1 0;
+}
+
+.logo-section {
+ -fx-cursor: hand;
+ -fx-opacity: 1.0;
+}
+.logo-section:hover { -fx-opacity: 0.8; }
+
+.logo-icon-wrapper {
+ -fx-background-color: -primary-soft;
+ -fx-background-radius: 12;
+ -fx-alignment: center;
+}
+
+.logo-icon-wrapper SVGPath {
+ -fx-fill: -primary;
+}
+
+.logo-text {
+ -fx-font-size: 18px;
+ -fx-font-weight: bold;
+ -fx-text-fill: -text-main;
+}
+
+.icon-button {
+ -fx-background-color: -bg-card;
+ -fx-background-radius: 12;
+ -fx-cursor: hand;
+ -fx-min-width: 40px;
+ -fx-min-height: 40px;
+ -fx-padding: 0;
+}
+.icon-button:hover {
+ -fx-border-color: -primary;
+ -fx-border-width: 1;
+}
+.icon-button SVGPath {
+ -fx-fill: -text-main;
+}
+
+.title {
+ -fx-font-size: 42px;
+ -fx-font-weight: 800;
+ -fx-text-fill: -text-main;
+ -fx-alignment: center;
+}
+.subtitle {
+ -fx-font-size: 15px;
+ -fx-text-fill: -text-muted;
+ -fx-font-weight: 500;
+}
+
+.game-card {
+ -fx-background-radius: -offline-card-radius;
+ -fx-border-radius: -offline-card-radius;
+ -fx-background-color: -bg-card;
+
+ -fx-cursor: hand;
+ -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.4), 20, 0, 0, 10);
+
+ -fx-border-color: transparent;
+ -fx-border-width: 2;
+}
+
+.game-card:hover {
+ -fx-border-color: -primary;
+ -fx-background-radius: -offline-card-radius;
+ -fx-border-radius: -offline-card-radius;
+ -fx-effect: dropshadow(three-pass-box, -primary-soft, 40, 0.2, 0, 0);
+}
+
+.card-bg-image-pvp {
+ -fx-background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuBuw3YJ7m8TLhz3GNAjD8Scf5cQLafn2bp8Qn2o_Ll0YnRvc5Ab_GqZNGey-746vql56GF4b6Sb0UKlABmsHyFQGa2mOfoV1nhTqEWHwqIn3ZPTFy1766m5UtDZBWmPT7H5a4_W3sL8oBT6ZJ74opM1_UsXjR4LR-Hs8VSTtUWD1axZbvQOF6UgXhNW53aKKwV-BU0E91jRE_LkaM9uukR2NIMq35DC3CE2ggLEMvTft7WS62_B5fMAyJ1oTzUDC7Vk-CL4LrZv30EV");
+ -fx-background-size: cover;
+ -fx-background-position: center;
+ -fx-background-radius: -offline-card-radius;
+ -fx-background-insets: 0;
+}
+
+.card-bg-image-pve {
+ -fx-background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuD_55qHepZ8p-aFLKxAwRFDeAfTWN8qIelTmbf9QTc6mt5b7JBTox0lLVDujXJT3iQbXoW9F2NKgKgrykD-m9tahZevwoRma9_Ul3pm0Te6Suzje9RtNlAIsJw4o3tIupJ5NfLPGqoFFAs0sg7icvB2sTi4Zdu_4tZAVuCUq6fVujXB6tIYLLGAybHRwF3Y0mPCdWtNYj_TCtu_uOgXabYd_CeW7FP5Uc-ratqZX7ESNF0H6lpNtF91FYYWHaHW9YWDSgPBKw8T2C0t");
+ -fx-background-size: cover;
+ -fx-background-position: center;
+ -fx-background-radius: -offline-card-radius;
+ -fx-background-insets: 0;
+}
+
+.card-gradient {
+ -fx-background-radius: -offline-card-radius;
+ -fx-background-insets: 0;
+ -fx-background-color: linear-gradient(
+ to top,
+ -bg-main 0%,
+ derive(-bg-main, 20%) 40%,
+ derive(-bg-main, 80%) 100%
+ );
+ -fx-opacity: 0.9;
+}
+
+.card-icon-bg {
+ -fx-background-color: -primary-soft;
+ -fx-background-radius: 100;
+ -fx-alignment: center;
+
+ -fx-min-width: -icon-size;
+ -fx-min-height: -icon-size;
+ -fx-max-width: -icon-size;
+ -fx-max-height: -icon-size;
+}
+
+.card-icon-bg SVGPath {
+ -fx-fill: white;
+}
+
+.game-card:hover .card-icon-bg {
+ -fx-effect: dropshadow(gaussian, -primary, 10, 0.4, 0, 0);
+}
+
+.card-title {
+ -fx-font-size: 26px;
+ -fx-font-weight: bold;
+ -fx-text-fill: -text-main;
+}
+.game-card:hover .card-title {
+ -fx-text-fill: -primary;
+}
+
+.card-description {
+ -fx-font-size: 14px;
+ -fx-text-fill: -text-muted;
+ -fx-font-weight: 500;
+}
+
+.card-footer {
+ -fx-border-color: derive(-bg-card, 30%);
+ -fx-border-width: 1 0 0 0;
+}
+
+.card-badge {
+ -fx-font-size: 11px;
+ -fx-font-weight: bold;
+ -fx-text-fill: -text-badge;
+ -fx-opacity: 0.7;
+}
+
+.action-text {
+ -fx-font-size: 14px;
+ -fx-font-weight: bold;
+ -fx-text-fill: -primary;
+}
+.card-action SVGPath {
+ -fx-fill: -primary;
+}
+
+.back-button {
+ -fx-background-color: transparent;
+ -fx-padding: 10 20 10 20;
+ -fx-background-radius: 12;
+ -fx-cursor: hand;
+}
+.back-button:hover {
+ -fx-background-color: -bg-card;
+}
+.back-button .back-text, .back-button SVGPath {
+ -fx-text-fill: -text-muted;
+ -fx-fill: -text-muted;
+ -fx-font-weight: bold;
+}
+.back-button:hover .back-text { -fx-text-fill: -primary; }
+.back-button:hover SVGPath { -fx-fill: -primary; }
\ No newline at end of file
diff --git a/src/main/resources/css/themes/green.css b/src/main/resources/css/themes/green.css
deleted file mode 100644
index 66c7571..0000000
--- a/src/main/resources/css/themes/green.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.root {
- -color-bg: #0f2a1a;
- -color-bg-alt: #163a25;
- -color-text: #ffffff;
- -color-accent: #4caf50;
-}
diff --git a/src/main/resources/css/themes/purple.css b/src/main/resources/css/themes/purple.css
deleted file mode 100644
index c12155a..0000000
--- a/src/main/resources/css/themes/purple.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.root {
- -color-bg: #2b1e3a;
- -color-bg-alt: #3a2752;
- -color-text: #ffffff;
- -color-accent: #b388ff;
-}
diff --git a/src/main/resources/css/themes/red.css b/src/main/resources/css/themes/red.css
deleted file mode 100644
index 2e18dba..0000000
--- a/src/main/resources/css/themes/red.css
+++ /dev/null
@@ -1,6 +0,0 @@
-.root {
- -color-bg: #2a0f0f;
- -color-bg-alt: #3a1515;
- -color-text: #ffffff;
- -color-accent: #ff5c5c;
-}
diff --git a/src/main/resources/fxml/home.fxml b/src/main/resources/fxml/home.fxml
new file mode 100644
index 0000000..4f1fe7f
--- /dev/null
+++ b/src/main/resources/fxml/home.fxml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/fxml/leaderboard.fxml b/src/main/resources/fxml/leaderboard.fxml
new file mode 100644
index 0000000..6ac686b
--- /dev/null
+++ b/src/main/resources/fxml/leaderboard.fxml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/fxml/offline_mode_selection.fxml b/src/main/resources/fxml/offline_mode_selection.fxml
new file mode 100644
index 0000000..4301f1e
--- /dev/null
+++ b/src/main/resources/fxml/offline_mode_selection.fxml
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/fxml/players.fxml b/src/main/resources/fxml/players.fxml
new file mode 100644
index 0000000..368f5c6
--- /dev/null
+++ b/src/main/resources/fxml/players.fxml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/fxml/recordedgames.fxml b/src/main/resources/fxml/recordedgames.fxml
new file mode 100644
index 0000000..dc23334
--- /dev/null
+++ b/src/main/resources/fxml/recordedgames.fxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file