mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 19:04:49 +00:00
@@ -1,8 +1,9 @@
|
|||||||
package org.toop.app;
|
package org.toop.app;
|
||||||
|
|
||||||
import org.toop.app.view.ViewStack;
|
import org.toop.app.widget.WidgetContainer;
|
||||||
import org.toop.app.view.views.MainView;
|
import org.toop.app.widget.display.SongDisplay;
|
||||||
import org.toop.app.view.views.QuitView;
|
import org.toop.app.widget.popup.QuitPopup;
|
||||||
|
import org.toop.app.widget.view.MainView;
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.framework.resource.ResourceManager;
|
import org.toop.framework.resource.ResourceManager;
|
||||||
@@ -11,6 +12,7 @@ import org.toop.local.AppContext;
|
|||||||
import org.toop.local.AppSettings;
|
import org.toop.local.AppSettings;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -30,14 +32,17 @@ public final class App extends Application {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws Exception {
|
public void start(Stage stage) throws Exception {
|
||||||
final StackPane root = new StackPane();
|
final StackPane root = WidgetContainer.setup();
|
||||||
final Scene scene = new Scene(root);
|
final Scene scene = new Scene(root);
|
||||||
ViewStack.setup(scene);
|
|
||||||
|
|
||||||
stage.setTitle(AppContext.getString("app-title"));
|
stage.setTitle(AppContext.getString("app-title"));
|
||||||
|
stage.titleProperty().bind(AppContext.bindToKey("app-title"));
|
||||||
|
|
||||||
stage.setWidth(1080);
|
stage.setWidth(1080);
|
||||||
stage.setHeight(720);
|
stage.setHeight(720);
|
||||||
|
|
||||||
|
scene.getRoot();
|
||||||
|
|
||||||
stage.setMinWidth(1080);
|
stage.setMinWidth(1080);
|
||||||
stage.setMinHeight(720);
|
stage.setMinHeight(720);
|
||||||
stage.setOnCloseRequest(event -> {
|
stage.setOnCloseRequest(event -> {
|
||||||
@@ -61,7 +66,8 @@ public final class App extends Application {
|
|||||||
AppSettings.applySettings();
|
AppSettings.applySettings();
|
||||||
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
|
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
|
||||||
|
|
||||||
ViewStack.push(new MainView());
|
WidgetContainer.add(Pos.CENTER, new MainView());
|
||||||
|
WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startQuit() {
|
public static void startQuit() {
|
||||||
@@ -69,47 +75,32 @@ public final class App extends Application {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewStack.push(new QuitView());
|
WidgetContainer.add(Pos.CENTER, new QuitPopup());
|
||||||
isQuitting = true;
|
isQuitting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void stopQuit() {
|
public static void stopQuit() {
|
||||||
ViewStack.pop();
|
|
||||||
isQuitting = false;
|
isQuitting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void quit() {
|
public static void quit() {
|
||||||
ViewStack.cleanup();
|
|
||||||
stage.close();
|
stage.close();
|
||||||
System.exit(0); // TODO: This is like dropping a nuke
|
System.exit(0); // TODO: This is like dropping a nuke
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void reload() {
|
|
||||||
stage.setTitle(AppContext.getString("app-title"));
|
|
||||||
ViewStack.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setFullscreen(boolean fullscreen) {
|
public static void setFullscreen(boolean fullscreen) {
|
||||||
stage.setFullScreen(fullscreen);
|
stage.setFullScreen(fullscreen);
|
||||||
|
|
||||||
width = (int) stage.getWidth();
|
width = (int)stage.getWidth();
|
||||||
height = (int) stage.getHeight();
|
height = (int)stage.getHeight();
|
||||||
|
|
||||||
reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setStyle(String theme, String layoutSize) {
|
public static void setStyle(String theme, String layoutSize) {
|
||||||
final int stylesCount = scene.getStylesheets().size();
|
scene.getStylesheets().clear();
|
||||||
|
|
||||||
for (int i = 0; i < stylesCount; i++) {
|
|
||||||
scene.getStylesheets().removeLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
scene.getStylesheets().add(ResourceManager.<CssAsset>get("general.css").getUrl());
|
scene.getStylesheets().add(ResourceManager.<CssAsset>get("general.css").getUrl());
|
||||||
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
|
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
|
||||||
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
|
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
|
||||||
|
|
||||||
reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getWidth() {
|
public static int getWidth() {
|
||||||
|
|||||||
@@ -2,29 +2,26 @@ package org.toop.app;
|
|||||||
|
|
||||||
public class GameInformation {
|
public class GameInformation {
|
||||||
public enum Type {
|
public enum Type {
|
||||||
TICTACTOE,
|
TICTACTOE(2, 5),
|
||||||
REVERSI,
|
REVERSI(2, 10),
|
||||||
CONNECT4,
|
CONNECT4(2, 7),
|
||||||
BATTLESHIP;
|
BATTLESHIP(2, 5);
|
||||||
|
|
||||||
|
private final int playerCount;
|
||||||
|
private final int maxDepth;
|
||||||
|
|
||||||
public static int playerCount(Type type) {
|
Type(int playerCount, int maxDepth) {
|
||||||
return switch (type) {
|
this.playerCount = playerCount;
|
||||||
case TICTACTOE -> 2;
|
this.maxDepth = maxDepth;
|
||||||
case REVERSI -> 2;
|
}
|
||||||
case CONNECT4 -> 2;
|
|
||||||
case BATTLESHIP -> 2;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int maxDepth(Type type) {
|
public int getPlayerCount() {
|
||||||
return switch (type) {
|
return playerCount;
|
||||||
case TICTACTOE -> 5; // Todo. 5 seems to always draw or win. could increase to 9 but that might affect performance
|
}
|
||||||
case REVERSI -> 10; // Todo. 10 is a guess. might be too slow or too bad.
|
|
||||||
case CONNECT4 -> 7;
|
public int getMaxDepth() {
|
||||||
case BATTLESHIP -> 5;
|
return maxDepth;
|
||||||
};
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Player {
|
public static class Player {
|
||||||
@@ -39,7 +36,7 @@ public class GameInformation {
|
|||||||
|
|
||||||
public GameInformation(Type type) {
|
public GameInformation(Type type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
players = new Player[Type.playerCount(type)];
|
players = new Player[type.getPlayerCount()];
|
||||||
|
|
||||||
for (int i = 0; i < players.length; i++) {
|
for (int i = 0; i < players.length; i++) {
|
||||||
players[i] = new Player();
|
players[i] = new Player();
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ package org.toop.app;
|
|||||||
import org.toop.app.game.Connect4Game;
|
import org.toop.app.game.Connect4Game;
|
||||||
import org.toop.app.game.ReversiGame;
|
import org.toop.app.game.ReversiGame;
|
||||||
import org.toop.app.game.TicTacToeGame;
|
import org.toop.app.game.TicTacToeGame;
|
||||||
import org.toop.app.view.ViewStack;
|
import org.toop.app.widget.WidgetContainer;
|
||||||
import org.toop.app.view.views.ChallengeView;
|
import org.toop.app.widget.popup.ChallengePopup;
|
||||||
import org.toop.app.view.views.ErrorView;
|
import org.toop.app.widget.popup.ErrorPopup;
|
||||||
import org.toop.app.view.views.OnlineView;
|
import org.toop.app.widget.popup.SendChallengePopup;
|
||||||
import org.toop.app.view.views.SendChallengeView;
|
import org.toop.app.widget.view.ServerView;
|
||||||
import org.toop.app.view.views.ServerView;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.framework.networking.clients.TournamentNetworkingClient;
|
import org.toop.framework.networking.clients.TournamentNetworkingClient;
|
||||||
import org.toop.framework.networking.events.NetworkEvents;
|
import org.toop.framework.networking.events.NetworkEvents;
|
||||||
@@ -29,10 +28,10 @@ public final class Server {
|
|||||||
private final List<String> onlinePlayers = new CopyOnWriteArrayList<>();
|
private final List<String> onlinePlayers = new CopyOnWriteArrayList<>();
|
||||||
private final List<String> gameList = new CopyOnWriteArrayList<>();
|
private final List<String> gameList = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
private ServerView view;
|
private ServerView primary;
|
||||||
private boolean isPolling = true;
|
private boolean isPolling = true;
|
||||||
|
|
||||||
private AtomicBoolean isSingleGame = new AtomicBoolean(false);
|
private final AtomicBoolean isSingleGame = new AtomicBoolean(false);
|
||||||
|
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ public final class Server {
|
|||||||
|
|
||||||
public Server(String ip, String port, String user) {
|
public Server(String ip, String port, String user) {
|
||||||
if (ip.split("\\.").length < 4) {
|
if (ip.split("\\.").length < 4) {
|
||||||
ViewStack.push(new ErrorView("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address")));
|
new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +60,12 @@ public final class Server {
|
|||||||
try {
|
try {
|
||||||
parsedPort = Integer.parseInt(port);
|
parsedPort = Integer.parseInt(port);
|
||||||
} catch (NumberFormatException _) {
|
} catch (NumberFormatException _) {
|
||||||
ViewStack.push(new ErrorView("\"" + port + "\" " + AppContext.getString("is-not-a-valid-port")));
|
new ErrorPopup("\"" + port + "\" " + AppContext.getString("is-not-a-valid-port"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isEmpty() || user.matches("^[0-9].*")) {
|
if (user.isEmpty() || user.matches("^[0-9].*")) {
|
||||||
ViewStack.push(new ErrorView(AppContext.getString("invalid-username")));
|
new ErrorPopup(AppContext.getString("invalid-username"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +80,8 @@ public final class Server {
|
|||||||
|
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent();
|
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, user)).postEvent();
|
||||||
|
|
||||||
view = new ServerView(user, this::sendChallenge, this::disconnect);
|
primary = new ServerView(user, this::sendChallenge, this::disconnect);
|
||||||
ViewStack.push(view);
|
WidgetContainer.getCurrentView().transitionNext(primary);
|
||||||
|
|
||||||
startPopulateScheduler();
|
startPopulateScheduler();
|
||||||
populateGameList();
|
populateGameList();
|
||||||
@@ -96,38 +95,10 @@ public final class Server {
|
|||||||
private void sendChallenge(String opponent) {
|
private void sendChallenge(String opponent) {
|
||||||
if (!isPolling) return;
|
if (!isPolling) return;
|
||||||
|
|
||||||
|
new SendChallengePopup(this, opponent, (playerInformation, gameType) -> {
|
||||||
ViewStack.push(new SendChallengeView(this, opponent, (playerInformation, gameType) -> {
|
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType)).postEvent();
|
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType)).postEvent();
|
||||||
/* .listen(NetworkEvents.GameMatchResponse.class, e -> {
|
|
||||||
if (e.clientId() == clientId) {
|
|
||||||
isPolling = false;
|
|
||||||
onlinePlayers.clear();
|
|
||||||
|
|
||||||
final GameInformation.Type type = gameToType(gameType);
|
|
||||||
if (type == null) {
|
|
||||||
ViewStack.push(new ErrorView("Unsupported game type: " + gameType));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent()) ? 1 : 0;
|
|
||||||
|
|
||||||
final GameInformation information = new GameInformation(type);
|
|
||||||
information.players[0] = playerInformation;
|
|
||||||
information.players[0].name = user;
|
|
||||||
information.players[1].name = opponent;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case TICTACTOE -> new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage);
|
|
||||||
case REVERSI -> new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage);
|
|
||||||
case CONNECT4 -> new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage);
|
|
||||||
default -> ViewStack.push(new ErrorView("Unsupported game type."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) */
|
|
||||||
ViewStack.pop();
|
|
||||||
isSingleGame.set(true);
|
isSingleGame.set(true);
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleMatchResponse(NetworkEvents.GameMatchResponse response) {
|
private void handleMatchResponse(NetworkEvents.GameMatchResponse response) {
|
||||||
@@ -141,7 +112,7 @@ public final class Server {
|
|||||||
|
|
||||||
final GameInformation.Type type = gameToType(gameType);
|
final GameInformation.Type type = gameToType(gameType);
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
ViewStack.push(new ErrorView("Unsupported game type: " + gameType));
|
new ErrorPopup("Unsupported game type: " + gameType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +136,7 @@ public final class Server {
|
|||||||
new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||||
case CONNECT4 ->
|
case CONNECT4 ->
|
||||||
new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||||
default -> ViewStack.push(new ErrorView("Unsupported game type."));
|
default -> new ErrorPopup("Unsupported game type.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,17 +147,11 @@ public final class Server {
|
|||||||
String challengerName = extractQuotedValue(response.challengerName());
|
String challengerName = extractQuotedValue(response.challengerName());
|
||||||
String gameType = extractQuotedValue(response.gameType());
|
String gameType = extractQuotedValue(response.gameType());
|
||||||
final String finalGameType = gameType;
|
final String finalGameType = gameType;
|
||||||
ViewStack.push(new ChallengeView(challengerName, gameType, (playerInformation) -> {
|
new ChallengePopup(challengerName, gameType, (playerInformation) -> {
|
||||||
final int challengeId = Integer.parseInt(response.challengeId().replaceAll("\\D", ""));
|
final int challengeId = Integer.parseInt(response.challengeId().replaceAll("\\D", ""));
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent();
|
new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent();
|
||||||
ViewStack.pop();
|
|
||||||
isSingleGame.set(true);
|
isSingleGame.set(true);
|
||||||
|
});
|
||||||
//new EventFlow().listen(NetworkEvents.GameMatchResponse.class, e -> {
|
|
||||||
|
|
||||||
|
|
||||||
//});
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(String message) {
|
private void sendMessage(String message) {
|
||||||
@@ -197,7 +162,7 @@ public final class Server {
|
|||||||
new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent();
|
new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent();
|
||||||
isPolling = false;
|
isPolling = false;
|
||||||
stopScheduler();
|
stopScheduler();
|
||||||
ViewStack.push(new OnlineView());
|
primary.transitionPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forfeitGame() {
|
private void forfeitGame() {
|
||||||
@@ -206,15 +171,11 @@ public final class Server {
|
|||||||
|
|
||||||
private void exitGame() {
|
private void exitGame() {
|
||||||
forfeitGame();
|
forfeitGame();
|
||||||
ViewStack.push(view);
|
|
||||||
startPopulateScheduler();
|
startPopulateScheduler();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void gameOver(){
|
private void gameOver(){
|
||||||
ViewStack.pop();
|
|
||||||
ViewStack.push(view);
|
|
||||||
startPopulateScheduler();
|
startPopulateScheduler();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startPopulateScheduler() {
|
private void startPopulateScheduler() {
|
||||||
@@ -228,7 +189,7 @@ public final class Server {
|
|||||||
onlinePlayers.clear();
|
onlinePlayers.clear();
|
||||||
onlinePlayers.addAll(List.of(e.playerlist()));
|
onlinePlayers.addAll(List.of(e.playerlist()));
|
||||||
onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user));
|
onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user));
|
||||||
view.update(onlinePlayers);
|
primary.update(onlinePlayers);
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
|||||||
120
app/src/main/java/org/toop/app/game/BaseGameThread.java
Normal file
120
app/src/main/java/org/toop/app/game/BaseGameThread.java
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package org.toop.app.game;
|
||||||
|
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.widget.WidgetContainer;
|
||||||
|
import org.toop.app.widget.view.GameView;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.networking.events.NetworkEvents;
|
||||||
|
import org.toop.game.Game;
|
||||||
|
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
|
||||||
|
protected final GameInformation information;
|
||||||
|
protected final int myTurn;
|
||||||
|
protected final Runnable onGameOver;
|
||||||
|
protected final BlockingQueue<Game.Move> moveQueue;
|
||||||
|
|
||||||
|
protected final TGame game;
|
||||||
|
protected final TAI ai;
|
||||||
|
|
||||||
|
protected final GameView primary;
|
||||||
|
protected final TCanvas canvas;
|
||||||
|
|
||||||
|
protected final AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
protected BaseGameThread(
|
||||||
|
GameInformation information,
|
||||||
|
int myTurn,
|
||||||
|
Runnable onForfeit,
|
||||||
|
Runnable onExit,
|
||||||
|
Consumer<String> onMessage,
|
||||||
|
Runnable onGameOver,
|
||||||
|
Supplier<TGame> gameSupplier,
|
||||||
|
Supplier<TAI> aiSupplier,
|
||||||
|
Function<Consumer<Integer>, TCanvas> canvasFactory) {
|
||||||
|
|
||||||
|
this.information = information;
|
||||||
|
this.myTurn = myTurn;
|
||||||
|
this.onGameOver = onGameOver;
|
||||||
|
this.moveQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
|
this.game = gameSupplier.get();
|
||||||
|
this.ai = aiSupplier.get();
|
||||||
|
|
||||||
|
if (onForfeit == null || onExit == null) {
|
||||||
|
primary = new GameView(null, () -> {
|
||||||
|
isRunning.set(false);
|
||||||
|
WidgetContainer.getCurrentView().transitionPrevious();
|
||||||
|
}, null);
|
||||||
|
} else {
|
||||||
|
primary = new GameView(onForfeit, () -> {
|
||||||
|
isRunning.set(false);
|
||||||
|
onExit.run();
|
||||||
|
}, onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas = canvasFactory.apply(this::onCellClicked);
|
||||||
|
|
||||||
|
addCanvasToPrimary();
|
||||||
|
|
||||||
|
WidgetContainer.getCurrentView().transitionNext(primary);
|
||||||
|
|
||||||
|
if (onForfeit == null || onExit == null)
|
||||||
|
new Thread(this::localGameThread).start();
|
||||||
|
else
|
||||||
|
new EventFlow()
|
||||||
|
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
|
||||||
|
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse);
|
||||||
|
|
||||||
|
setGameLabels(myTurn == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCellClicked(int cell) {
|
||||||
|
if (!isRunning.get()) return;
|
||||||
|
|
||||||
|
final int currentTurn = getCurrentTurn();
|
||||||
|
if (!information.players[currentTurn].isHuman) return;
|
||||||
|
|
||||||
|
final char value = getSymbolForTurn(currentTurn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
moveQueue.put(new Game.Move(cell, value));
|
||||||
|
} catch (InterruptedException _) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void gameOver() {
|
||||||
|
if (onGameOver != null) {
|
||||||
|
isRunning.set(false);
|
||||||
|
onGameOver.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setGameLabels(boolean isMe) {
|
||||||
|
final int currentTurn = getCurrentTurn();
|
||||||
|
final String turnName = getNameForTurn(currentTurn);
|
||||||
|
|
||||||
|
primary.nextPlayer(
|
||||||
|
isMe,
|
||||||
|
information.players[isMe ? 0 : 1].name,
|
||||||
|
turnName,
|
||||||
|
information.players[isMe ? 1 : 0].name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void addCanvasToPrimary();
|
||||||
|
|
||||||
|
protected abstract int getCurrentTurn();
|
||||||
|
protected abstract char getSymbolForTurn(int turn);
|
||||||
|
protected abstract String getNameForTurn(int turn);
|
||||||
|
|
||||||
|
protected abstract void onMoveResponse(NetworkEvents.GameMoveResponse response);
|
||||||
|
protected abstract void onYourTurnResponse(NetworkEvents.YourTurnResponse response);
|
||||||
|
|
||||||
|
protected abstract void localGameThread();
|
||||||
|
}
|
||||||
@@ -107,7 +107,7 @@ public class Connect4Game {
|
|||||||
while (isRunning.get()) {
|
while (isRunning.get()) {
|
||||||
final int currentTurn = game.getCurrentTurn();
|
final int currentTurn = game.getCurrentTurn();
|
||||||
final String currentValue = currentTurn == 0? "RED" : "BLUE";
|
final String currentValue = currentTurn == 0? "RED" : "BLUE";
|
||||||
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
|
final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount();
|
||||||
|
|
||||||
view.nextPlayer(information.players[currentTurn].isHuman,
|
view.nextPlayer(information.players[currentTurn].isHuman,
|
||||||
information.players[currentTurn].name,
|
information.players[currentTurn].name,
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import javafx.animation.SequentialTransition;
|
|||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
import org.toop.app.canvas.ReversiCanvas;
|
import org.toop.app.canvas.ReversiCanvas;
|
||||||
import org.toop.app.view.ViewStack;
|
import org.toop.app.widget.WidgetContainer;
|
||||||
import org.toop.app.view.views.GameView;
|
import org.toop.app.widget.view.GameView;
|
||||||
import org.toop.app.view.views.LocalMultiplayerView;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.framework.networking.events.NetworkEvents;
|
import org.toop.framework.networking.events.NetworkEvents;
|
||||||
import org.toop.game.enumerators.GameState;
|
import org.toop.game.enumerators.GameState;
|
||||||
@@ -27,13 +26,13 @@ public final class ReversiGame {
|
|||||||
private final GameInformation information;
|
private final GameInformation information;
|
||||||
|
|
||||||
private final int myTurn;
|
private final int myTurn;
|
||||||
private Runnable onGameOver;
|
private final Runnable onGameOver;
|
||||||
private final BlockingQueue<Move> moveQueue;
|
private final BlockingQueue<Game.Move> moveQueue;
|
||||||
|
|
||||||
private final Reversi game;
|
private final Reversi game;
|
||||||
private final ReversiAI ai;
|
private final ReversiAI ai;
|
||||||
|
|
||||||
private final GameView view;
|
private final GameView primary;
|
||||||
private final ReversiCanvas canvas;
|
private final ReversiCanvas canvas;
|
||||||
|
|
||||||
private final AtomicBoolean isRunning;
|
private final AtomicBoolean isRunning;
|
||||||
@@ -44,7 +43,7 @@ public final class ReversiGame {
|
|||||||
|
|
||||||
this.myTurn = myTurn;
|
this.myTurn = myTurn;
|
||||||
this.onGameOver = onGameOver;
|
this.onGameOver = onGameOver;
|
||||||
moveQueue = new LinkedBlockingQueue<Move>();
|
moveQueue = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
game = new Reversi();
|
game = new Reversi();
|
||||||
ai = new ReversiAI();
|
ai = new ReversiAI();
|
||||||
@@ -53,12 +52,12 @@ public final class ReversiGame {
|
|||||||
isPaused = new AtomicBoolean(false);
|
isPaused = new AtomicBoolean(false);
|
||||||
|
|
||||||
if (onForfeit == null || onExit == null) {
|
if (onForfeit == null || onExit == null) {
|
||||||
view = new GameView(null, () -> {
|
primary = new GameView(null, () -> {
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
ViewStack.push(new LocalMultiplayerView(information));
|
WidgetContainer.getCurrentView().transitionPrevious();
|
||||||
}, null);
|
}, null);
|
||||||
} else {
|
} else {
|
||||||
view = new GameView(onForfeit, () -> {
|
primary = new GameView(onForfeit, () -> {
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
onExit.run();
|
onExit.run();
|
||||||
}, onMessage);
|
}, onMessage);
|
||||||
@@ -88,8 +87,8 @@ public final class ReversiGame {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
view.add(Pos.CENTER, canvas.getCanvas());
|
primary.add(Pos.CENTER, canvas.getCanvas());
|
||||||
ViewStack.push(view);
|
WidgetContainer.getCurrentView().transitionNext(primary);
|
||||||
|
|
||||||
if (onForfeit == null || onExit == null) {
|
if (onForfeit == null || onExit == null) {
|
||||||
new Thread(this::localGameThread).start();
|
new Thread(this::localGameThread).start();
|
||||||
@@ -97,8 +96,7 @@ public final class ReversiGame {
|
|||||||
} else {
|
} else {
|
||||||
new EventFlow()
|
new EventFlow()
|
||||||
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
|
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
|
||||||
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse)
|
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse);
|
||||||
.listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
|
|
||||||
|
|
||||||
setGameLabels(myTurn == 0);
|
setGameLabels(myTurn == 0);
|
||||||
}
|
}
|
||||||
@@ -122,9 +120,9 @@ public final class ReversiGame {
|
|||||||
|
|
||||||
final int currentTurn = game.getCurrentTurn();
|
final int currentTurn = game.getCurrentTurn();
|
||||||
final String currentValue = currentTurn == 0? "BLACK" : "WHITE";
|
final String currentValue = currentTurn == 0? "BLACK" : "WHITE";
|
||||||
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
|
final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount();
|
||||||
|
|
||||||
view.nextPlayer(information.players[currentTurn].isHuman,
|
primary.nextPlayer(information.players[currentTurn].isHuman,
|
||||||
information.players[currentTurn].name,
|
information.players[currentTurn].name,
|
||||||
currentValue,
|
currentValue,
|
||||||
information.players[nextTurn].name);
|
information.players[nextTurn].name);
|
||||||
@@ -173,9 +171,9 @@ public final class ReversiGame {
|
|||||||
}
|
}
|
||||||
int winningPLayerNumber = getPlayerNumberWithHighestScore();
|
int winningPLayerNumber = getPlayerNumberWithHighestScore();
|
||||||
if (state == GameState.WIN && winningPLayerNumber > -1) {
|
if (state == GameState.WIN && winningPLayerNumber > -1) {
|
||||||
view.gameOver(true, information.players[winningPLayerNumber].name);
|
primary.gameOver(true, information.players[winningPLayerNumber].name);
|
||||||
} else if (state == GameState.DRAW || winningPLayerNumber == -1) {
|
} else if (state == GameState.DRAW || winningPLayerNumber == -1) {
|
||||||
view.gameOver(false, "");
|
primary.gameOver(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
@@ -209,14 +207,14 @@ public final class ReversiGame {
|
|||||||
if (state != GameState.NORMAL) {
|
if (state != GameState.NORMAL) {
|
||||||
if (state == GameState.WIN) {
|
if (state == GameState.WIN) {
|
||||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||||
view.gameOver(true, information.players[0].name);
|
primary.gameOver(true, information.players[0].name);
|
||||||
gameOver();
|
gameOver();
|
||||||
} else {
|
} else {
|
||||||
view.gameOver(false, information.players[1].name);
|
primary.gameOver(false, information.players[1].name);
|
||||||
gameOver();
|
gameOver();
|
||||||
}
|
}
|
||||||
} else if (state == GameState.DRAW) {
|
} else if (state == Game.State.DRAW) {
|
||||||
view.gameOver(false, "");
|
primary.gameOver(false, "");
|
||||||
game.play(move);
|
game.play(move);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,14 +255,6 @@ public final class ReversiGame {
|
|||||||
.postEvent();
|
.postEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
|
||||||
if (!isRunning.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
view.updateChat(msg.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCanvas(boolean animate) {
|
private void updateCanvas(boolean animate) {
|
||||||
// Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve.
|
// Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve.
|
||||||
canvas.clearAll();
|
canvas.clearAll();
|
||||||
@@ -282,16 +272,14 @@ public final class ReversiGame {
|
|||||||
final SequentialTransition animation = new SequentialTransition();
|
final SequentialTransition animation = new SequentialTransition();
|
||||||
isPaused.set(true);
|
isPaused.set(true);
|
||||||
|
|
||||||
|
final Color fromColor = game.getCurrentPlayer() == 'W'? Color.WHITE : Color.BLACK;
|
||||||
|
final Color toColor = game.getCurrentPlayer() == 'W'? Color.BLACK : Color.WHITE;
|
||||||
|
|
||||||
if (animate && flipped != null) {
|
if (animate && flipped != null) {
|
||||||
for (final Move flip : flipped) {
|
for (final Move flip : flipped) {
|
||||||
canvas.clear(flip.position());
|
canvas.clear(flip.position());
|
||||||
|
canvas.drawDot(fromColor, flip.position());
|
||||||
final Color from = flip.value() == 'W' ? Color.BLACK : Color.WHITE;
|
animation.getChildren().addFirst(canvas.flipDot(fromColor, toColor, flip.position()));
|
||||||
final Color to = flip.value() == 'W' ? Color.WHITE : Color.BLACK;
|
|
||||||
|
|
||||||
canvas.drawDot(from, flip.position());
|
|
||||||
|
|
||||||
animation.getChildren().addFirst(canvas.flipDot(from, to, flip.position()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +289,7 @@ public final class ReversiGame {
|
|||||||
if (information.players[game.getCurrentTurn()].isHuman) {
|
if (information.players[game.getCurrentTurn()].isHuman) {
|
||||||
final Move[] legalMoves = game.getLegalMoves();
|
final Move[] legalMoves = game.getLegalMoves();
|
||||||
|
|
||||||
for (final Move legalMove : legalMoves) {
|
for (final Game.Move legalMove : legalMoves) {
|
||||||
canvas.drawLegalPosition(legalMove.position(), game.getCurrentPlayer());
|
canvas.drawLegalPosition(legalMove.position(), game.getCurrentPlayer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,7 +302,7 @@ public final class ReversiGame {
|
|||||||
final int currentTurn = game.getCurrentTurn();
|
final int currentTurn = game.getCurrentTurn();
|
||||||
final String currentValue = currentTurn == 0? "BLACK" : "WHITE";
|
final String currentValue = currentTurn == 0? "BLACK" : "WHITE";
|
||||||
|
|
||||||
view.nextPlayer(isMe,
|
primary.nextPlayer(isMe,
|
||||||
information.players[isMe? 0 : 1].name,
|
information.players[isMe? 0 : 1].name,
|
||||||
currentValue,
|
currentValue,
|
||||||
information.players[isMe? 1 : 0].name);
|
information.players[isMe? 1 : 0].name);
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ package org.toop.app.game;
|
|||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
import org.toop.app.canvas.TicTacToeCanvas;
|
import org.toop.app.canvas.TicTacToeCanvas;
|
||||||
import org.toop.app.view.ViewStack;
|
import org.toop.app.widget.WidgetContainer;
|
||||||
import org.toop.app.view.views.GameView;
|
import org.toop.app.widget.view.GameView;
|
||||||
import org.toop.app.view.views.LocalMultiplayerView;
|
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
import org.toop.framework.networking.events.NetworkEvents;
|
import org.toop.framework.networking.events.NetworkEvents;
|
||||||
import org.toop.game.enumerators.GameState;
|
import org.toop.game.enumerators.GameState;
|
||||||
@@ -25,13 +24,13 @@ public final class TicTacToeGame {
|
|||||||
private final GameInformation information;
|
private final GameInformation information;
|
||||||
|
|
||||||
private final int myTurn;
|
private final int myTurn;
|
||||||
private Runnable onGameOver;
|
private final Runnable onGameOver;
|
||||||
private final BlockingQueue<Move> moveQueue;
|
private final BlockingQueue<Game.Move> moveQueue;
|
||||||
|
|
||||||
private final TicTacToe game;
|
private final TicTacToe game;
|
||||||
private final TicTacToeAI ai;
|
private final TicTacToeAI ai;
|
||||||
|
|
||||||
private final GameView view;
|
private final GameView primary;
|
||||||
private final TicTacToeCanvas canvas;
|
private final TicTacToeCanvas canvas;
|
||||||
|
|
||||||
private final AtomicBoolean isRunning;
|
private final AtomicBoolean isRunning;
|
||||||
@@ -49,12 +48,12 @@ public final class TicTacToeGame {
|
|||||||
isRunning = new AtomicBoolean(true);
|
isRunning = new AtomicBoolean(true);
|
||||||
|
|
||||||
if (onForfeit == null || onExit == null) {
|
if (onForfeit == null || onExit == null) {
|
||||||
view = new GameView(null, () -> {
|
primary = new GameView(null, () -> {
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
ViewStack.push(new LocalMultiplayerView(information));
|
WidgetContainer.getCurrentView().transitionPrevious();
|
||||||
}, null);
|
}, null);
|
||||||
} else {
|
} else {
|
||||||
view = new GameView(onForfeit, () -> {
|
primary = new GameView(onForfeit, () -> {
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
onExit.run();
|
onExit.run();
|
||||||
}, onMessage);
|
}, onMessage);
|
||||||
@@ -82,16 +81,15 @@ public final class TicTacToeGame {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
view.add(Pos.CENTER, canvas.getCanvas());
|
primary.add(Pos.CENTER, canvas.getCanvas());
|
||||||
ViewStack.push(view);
|
WidgetContainer.getCurrentView().transitionNext(primary);
|
||||||
|
|
||||||
if (onForfeit == null || onExit == null) {
|
if (onForfeit == null || onExit == null) {
|
||||||
new Thread(this::localGameThread).start();
|
new Thread(this::localGameThread).start();
|
||||||
} else {
|
} else {
|
||||||
new EventFlow()
|
new EventFlow()
|
||||||
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
|
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
|
||||||
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse)
|
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse);
|
||||||
.listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
|
|
||||||
|
|
||||||
setGameLabels(myTurn == 0);
|
setGameLabels(myTurn == 0);
|
||||||
}
|
}
|
||||||
@@ -105,9 +103,9 @@ public final class TicTacToeGame {
|
|||||||
while (isRunning.get()) {
|
while (isRunning.get()) {
|
||||||
final int currentTurn = game.getCurrentTurn();
|
final int currentTurn = game.getCurrentTurn();
|
||||||
final String currentValue = currentTurn == 0? "X" : "O";
|
final String currentValue = currentTurn == 0? "X" : "O";
|
||||||
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
|
final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount();
|
||||||
|
|
||||||
view.nextPlayer(information.players[currentTurn].isHuman,
|
primary.nextPlayer(information.players[currentTurn].isHuman,
|
||||||
information.players[currentTurn].name,
|
information.players[currentTurn].name,
|
||||||
currentValue,
|
currentValue,
|
||||||
information.players[nextTurn].name);
|
information.players[nextTurn].name);
|
||||||
@@ -154,11 +152,11 @@ public final class TicTacToeGame {
|
|||||||
canvas.drawO(Color.ROYALBLUE, move.position());
|
canvas.drawO(Color.ROYALBLUE, move.position());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state != GameState.NORMAL) {
|
if (state != Game.State.NORMAL) {
|
||||||
if (state == GameState.WIN) {
|
if (state == Game.State.WIN) {
|
||||||
view.gameOver(true, information.players[currentTurn].name);
|
primary.gameOver(true, information.players[currentTurn].name);
|
||||||
} else if (state == GameState.DRAW) {
|
} else if (state == Game.State.DRAW) {
|
||||||
view.gameOver(false, "");
|
primary.gameOver(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunning.set(false);
|
isRunning.set(false);
|
||||||
@@ -185,15 +183,15 @@ public final class TicTacToeGame {
|
|||||||
if (state != GameState.NORMAL) {
|
if (state != GameState.NORMAL) {
|
||||||
if (state == GameState.WIN) {
|
if (state == GameState.WIN) {
|
||||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||||
view.gameOver(true, information.players[0].name);
|
primary.gameOver(true, information.players[0].name);
|
||||||
gameOver();
|
gameOver();
|
||||||
} else {
|
} else {
|
||||||
view.gameOver(false, information.players[1].name);
|
primary.gameOver(false, information.players[1].name);
|
||||||
gameOver();
|
gameOver();
|
||||||
}
|
}
|
||||||
} else if (state == GameState.DRAW) {
|
} else if (state == GameState.DRAW) {
|
||||||
if(game.getLegalMoves().length == 0) { //only return draw in online multiplayer if the game is actually over.
|
if(game.getLegalMoves().length == 0) { //only return draw in online multiplayer if the game is actually over.
|
||||||
view.gameOver(false, "");
|
primary.gameOver(false, "");
|
||||||
gameOver();
|
gameOver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -240,19 +238,11 @@ public final class TicTacToeGame {
|
|||||||
.postEvent();
|
.postEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
|
||||||
if (!isRunning.get()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
view.updateChat(msg.message());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setGameLabels(boolean isMe) {
|
private void setGameLabels(boolean isMe) {
|
||||||
final int currentTurn = game.getCurrentTurn();
|
final int currentTurn = game.getCurrentTurn();
|
||||||
final String currentValue = currentTurn == 0? "X" : "O";
|
final String currentValue = currentTurn == 0? "X" : "O";
|
||||||
|
|
||||||
view.nextPlayer(isMe,
|
primary.nextPlayer(isMe,
|
||||||
information.players[isMe? 0 : 1].name,
|
information.players[isMe? 0 : 1].name,
|
||||||
currentValue,
|
currentValue,
|
||||||
information.players[isMe? 1 : 0].name);
|
information.players[isMe? 1 : 0].name);
|
||||||
|
|||||||
177
app/src/main/java/org/toop/app/game/TicTacToeGameThread.java
Normal file
177
app/src/main/java/org/toop/app/game/TicTacToeGameThread.java
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package org.toop.app.game;
|
||||||
|
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.canvas.TicTacToeCanvas;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.networking.events.NetworkEvents;
|
||||||
|
import org.toop.game.Game;
|
||||||
|
import org.toop.game.tictactoe.TicTacToe;
|
||||||
|
import org.toop.game.tictactoe.TicTacToeAI;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
|
public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacToeAI, TicTacToeCanvas> {
|
||||||
|
public TicTacToeGameThread(GameInformation info, int myTurn, Runnable onForfeit, Runnable onExit, Consumer<String> onMessage, Runnable onGameOver) {
|
||||||
|
super(info, myTurn, onForfeit, onExit, onMessage, onGameOver,
|
||||||
|
TicTacToe::new,
|
||||||
|
TicTacToeAI::new,
|
||||||
|
clickHandler -> new TicTacToeCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, clickHandler)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TicTacToeGameThread(GameInformation info) {
|
||||||
|
this(info, 0, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addCanvasToPrimary() {
|
||||||
|
primary.add(Pos.CENTER, canvas.getCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getCurrentTurn() {
|
||||||
|
return game.getCurrentTurn();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected char getSymbolForTurn(int turn) {
|
||||||
|
return turn == 0 ? 'X' : 'O';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getNameForTurn(int turn) {
|
||||||
|
return turn == 0 ? "X" : "O";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawMove(Game.Move move) {
|
||||||
|
if (move.value() == 'X') canvas.drawX(Color.RED, move.position());
|
||||||
|
else canvas.drawO(Color.BLUE, move.position());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
||||||
|
if (!isRunning.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char playerChar;
|
||||||
|
|
||||||
|
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||||
|
playerChar = myTurn == 0? 'X' : 'O';
|
||||||
|
} else {
|
||||||
|
playerChar = myTurn == 0? 'O' : 'X';
|
||||||
|
}
|
||||||
|
|
||||||
|
final Game.Move move = new Game.Move(Integer.parseInt(response.move()), playerChar);
|
||||||
|
final Game.State state = game.play(move);
|
||||||
|
|
||||||
|
if (state != Game.State.NORMAL) {
|
||||||
|
if (state == Game.State.WIN) {
|
||||||
|
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||||
|
primary.gameOver(true, information.players[0].name);
|
||||||
|
gameOver();
|
||||||
|
} else {
|
||||||
|
primary.gameOver(false, information.players[1].name);
|
||||||
|
gameOver();
|
||||||
|
}
|
||||||
|
} else if (state == Game.State.DRAW) {
|
||||||
|
if (game.getLegalMoves().length == 0) {
|
||||||
|
primary.gameOver(false, "");
|
||||||
|
gameOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawMove(move);
|
||||||
|
setGameLabels(game.getCurrentTurn() == myTurn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
|
||||||
|
if (!isRunning.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveQueue.clear();
|
||||||
|
|
||||||
|
int position = -1;
|
||||||
|
|
||||||
|
if (information.players[0].isHuman) {
|
||||||
|
try {
|
||||||
|
position = moveQueue.take().position();
|
||||||
|
} catch (InterruptedException _) {}
|
||||||
|
} else {
|
||||||
|
final Game.Move move;
|
||||||
|
if (information.players[1].name.equalsIgnoreCase("pism")) {
|
||||||
|
move = ai.findWorstMove(game,9);
|
||||||
|
}else{
|
||||||
|
move = ai.findBestMove(game, information.players[0].computerDifficulty);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert move != null;
|
||||||
|
position = move.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position))
|
||||||
|
.postEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void localGameThread() {
|
||||||
|
while (isRunning.get()) {
|
||||||
|
final int currentTurn = game.getCurrentTurn();
|
||||||
|
setGameLabels(currentTurn == myTurn);
|
||||||
|
|
||||||
|
Game.Move move = null;
|
||||||
|
|
||||||
|
if (information.players[currentTurn].isHuman) {
|
||||||
|
try {
|
||||||
|
final Game.Move wants = moveQueue.take();
|
||||||
|
final Game.Move[] legalMoves = game.getLegalMoves();
|
||||||
|
|
||||||
|
for (final Game.Move legalMove : legalMoves) {
|
||||||
|
if (legalMove.position() == wants.position() &&
|
||||||
|
legalMove.value() == wants.value()) {
|
||||||
|
move = wants;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException _) {}
|
||||||
|
} else {
|
||||||
|
final long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty);
|
||||||
|
|
||||||
|
if (information.players[currentTurn].computerThinkTime > 0) {
|
||||||
|
final long elapsedTime = System.currentTimeMillis() - start;
|
||||||
|
final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep((long)(sleepTime * Math.random()));
|
||||||
|
} catch (InterruptedException _) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (move == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Game.State state = game.play(move);
|
||||||
|
drawMove(move);
|
||||||
|
|
||||||
|
if (state != Game.State.NORMAL) {
|
||||||
|
if (state == Game.State.WIN) {
|
||||||
|
primary.gameOver(information.players[currentTurn].isHuman, information.players[currentTurn].name);
|
||||||
|
} else if (state == Game.State.DRAW) {
|
||||||
|
primary.gameOver(false, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.toop.app.view.displays;
|
package org.toop.app.view.displays;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ProgressBar;
|
import javafx.scene.control.ProgressBar;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
import org.toop.framework.audio.AudioEventListener;
|
import org.toop.framework.audio.AudioEventListener;
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
@@ -13,7 +15,7 @@ import javafx.geometry.Pos;
|
|||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import org.toop.framework.eventbus.GlobalEventBus;
|
import org.toop.framework.eventbus.GlobalEventBus;
|
||||||
|
|
||||||
public class SongDisplay extends VBox {
|
public class SongDisplay extends VBox implements Widget {
|
||||||
|
|
||||||
private final Text songTitle;
|
private final Text songTitle;
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
@@ -106,6 +108,10 @@ public class SongDisplay extends VBox {
|
|||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
private String getPlayString(boolean paused) {
|
private String getPlayString(boolean paused) {
|
||||||
if (paused) {
|
if (paused) {
|
||||||
return "▶";
|
return "▶";
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public final class ChallengeView extends View {
|
|||||||
|
|
||||||
final Slider computerDifficultySlider = slider();
|
final Slider computerDifficultySlider = slider();
|
||||||
computerDifficultySlider.setMin(0);
|
computerDifficultySlider.setMin(0);
|
||||||
computerDifficultySlider.setMax(GameInformation.Type.maxDepth(Server.gameToType(game)));
|
computerDifficultySlider.setMax(Server.gameToType(game).getMaxDepth());
|
||||||
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
|
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
|
||||||
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
||||||
playerInformation.computerDifficulty = newValue.intValue();
|
playerInformation.computerDifficulty = newValue.intValue();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package org.toop.app.view.views;
|
|||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
import org.toop.app.game.Connect4Game;
|
import org.toop.app.game.Connect4Game;
|
||||||
import org.toop.app.game.ReversiGame;
|
import org.toop.app.game.ReversiGame;
|
||||||
import org.toop.app.game.TicTacToeGame;
|
import org.toop.app.game.TicTacToeGameThread;
|
||||||
import org.toop.app.view.View;
|
import org.toop.app.view.View;
|
||||||
import org.toop.app.view.ViewStack;
|
import org.toop.app.view.ViewStack;
|
||||||
import org.toop.app.view.displays.SongDisplay;
|
import org.toop.app.view.displays.SongDisplay;
|
||||||
@@ -45,10 +45,10 @@ public final class LocalMultiplayerView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (information.type) {
|
switch (information.type) {
|
||||||
case TICTACTOE: new TicTacToeGame(information); break;
|
case TICTACTOE: new TicTacToeGameThread(information); break;
|
||||||
case REVERSI: new ReversiGame(information); break;
|
case REVERSI: new ReversiGame(information); break;
|
||||||
case CONNECT4: new Connect4Game(information); break;
|
case CONNECT4: new Connect4Game(information); break;
|
||||||
//case BATTLESHIP: new BattleshipGame(information); break;
|
// case BATTLESHIP: new BattleshipGame(information); break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ public final class LocalMultiplayerView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private VBox[] setupPlayers() {
|
private VBox[] setupPlayers() {
|
||||||
final VBox[] playerBoxes = new VBox[GameInformation.Type.playerCount(information.type)];
|
final VBox[] playerBoxes = new VBox[information.type.getPlayerCount()];
|
||||||
|
|
||||||
for (int i = 0; i < playerBoxes.length; i++) {
|
for (int i = 0; i < playerBoxes.length; i++) {
|
||||||
final int index = i;
|
final int index = i;
|
||||||
@@ -141,7 +141,7 @@ public final class LocalMultiplayerView extends View {
|
|||||||
|
|
||||||
final Slider computerDifficultySlider = slider();
|
final Slider computerDifficultySlider = slider();
|
||||||
computerDifficultySlider.setMin(0);
|
computerDifficultySlider.setMin(0);
|
||||||
computerDifficultySlider.setMax(GameInformation.Type.maxDepth(information.type));
|
computerDifficultySlider.setMax(information.type.getMaxDepth());
|
||||||
computerDifficultySlider.setValue(information.players[i].computerDifficulty);
|
computerDifficultySlider.setValue(information.players[i].computerDifficulty);
|
||||||
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
||||||
information.players[index].computerDifficulty = newValue.intValue();
|
information.players[index].computerDifficulty = newValue.intValue();
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ public final class OptionsView extends View {
|
|||||||
languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
|
languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
|
||||||
AppSettings.getSettings().setLocale(newValue.toString());
|
AppSettings.getSettings().setLocale(newValue.toString());
|
||||||
AppContext.setLocale(newValue);
|
AppContext.setLocale(newValue);
|
||||||
App.reload();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
languageCombobox.setConverter(new StringConverter<>() {
|
languageCombobox.setConverter(new StringConverter<>() {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public final class SendChallengeView extends View {
|
|||||||
|
|
||||||
final Slider computerDifficultySlider = slider();
|
final Slider computerDifficultySlider = slider();
|
||||||
computerDifficultySlider.setMin(0);
|
computerDifficultySlider.setMin(0);
|
||||||
computerDifficultySlider.setMax(GameInformation.Type.maxDepth(Server.gameToType(gamesCombobox.getValue())));
|
computerDifficultySlider.setMax(Server.gameToType(gamesCombobox.getValue()).getMaxDepth());
|
||||||
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
|
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
|
||||||
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
||||||
playerInformation.computerDifficulty = newValue.intValue();
|
playerInformation.computerDifficulty = newValue.intValue();
|
||||||
|
|||||||
166
app/src/main/java/org/toop/app/widget/Primitive.java
Normal file
166
app/src/main/java/org/toop/app/widget/Primitive.java
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package org.toop.app.widget;
|
||||||
|
|
||||||
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.control.Separator;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
public final class Primitive {
|
||||||
|
public static Text header(String key) {
|
||||||
|
var header = new Text();
|
||||||
|
header.getStyleClass().add("header");
|
||||||
|
|
||||||
|
if (!key.isEmpty()) {
|
||||||
|
header.setText(AppContext.getString(key));
|
||||||
|
header.textProperty().bind(AppContext.bindToKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Text text(String key) {
|
||||||
|
var text = new Text();
|
||||||
|
text.getStyleClass().add("text");
|
||||||
|
|
||||||
|
if (!key.isEmpty()) {
|
||||||
|
text.setText(AppContext.getString(key));
|
||||||
|
text.textProperty().bind(AppContext.bindToKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Button button(String key, Runnable onAction) {
|
||||||
|
var button = new Button();
|
||||||
|
button.getStyleClass().add("button");
|
||||||
|
|
||||||
|
if (!key.isEmpty()) {
|
||||||
|
button.setText(AppContext.getString(key));
|
||||||
|
button.textProperty().bind(AppContext.bindToKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onAction != null) {
|
||||||
|
button.setOnAction(_ ->
|
||||||
|
onAction.run());
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextField input(String promptKey, String text, Consumer<String> onValueChanged) {
|
||||||
|
var input = new TextField();
|
||||||
|
input.getStyleClass().add("input");
|
||||||
|
|
||||||
|
if (!promptKey.isEmpty()) {
|
||||||
|
input.setPromptText(AppContext.getString(promptKey));
|
||||||
|
input.promptTextProperty().bind(AppContext.bindToKey(promptKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
input.setText(text);
|
||||||
|
|
||||||
|
if (onValueChanged != null) {
|
||||||
|
input.textProperty().addListener((_, _, newValue) ->
|
||||||
|
onValueChanged.accept(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Slider slider(int min, int max, int value, Consumer<Integer> onValueChanged) {
|
||||||
|
var slider = new Slider();
|
||||||
|
slider.getStyleClass().add("slider");
|
||||||
|
|
||||||
|
slider.setMin(min);
|
||||||
|
slider.setMax(max);
|
||||||
|
slider.setValue(value);
|
||||||
|
|
||||||
|
if (onValueChanged != null) {
|
||||||
|
slider.valueProperty().addListener((_, _, newValue) ->
|
||||||
|
onValueChanged.accept(newValue.intValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return slider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> ComboBox<T> choice(StringConverter<T> converter, T value, Consumer<T> onValueChanged, T... items) {
|
||||||
|
var choice = new ComboBox<T>();
|
||||||
|
choice.getStyleClass().add("choice");
|
||||||
|
|
||||||
|
if (converter != null) {
|
||||||
|
choice.setConverter(converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
choice.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onValueChanged != null) {
|
||||||
|
choice.valueProperty().addListener((_, _, newValue) ->
|
||||||
|
onValueChanged.accept(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
choice.setItems(FXCollections.observableArrayList(items));
|
||||||
|
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScrollPane scroll(Node content) {
|
||||||
|
var scroll = new ScrollPane();
|
||||||
|
scroll.getStyleClass().add("scroll");
|
||||||
|
scroll.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||||
|
scroll.setFitToWidth(true);
|
||||||
|
|
||||||
|
scroll.setContent(content);
|
||||||
|
|
||||||
|
return scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Separator separator() {
|
||||||
|
var separator = new Separator();
|
||||||
|
separator.getStyleClass().add("separator");
|
||||||
|
|
||||||
|
return separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HBox hbox(Node... nodes) {
|
||||||
|
var hbox = new HBox();
|
||||||
|
hbox.getStyleClass().add("container");
|
||||||
|
hbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
for (var node : nodes) {
|
||||||
|
if (node != null) {
|
||||||
|
hbox.getChildren().add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VBox vbox(Node... nodes) {
|
||||||
|
var vbox = new VBox();
|
||||||
|
vbox.getStyleClass().add("container");
|
||||||
|
vbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||||
|
|
||||||
|
for (var node : nodes) {
|
||||||
|
if (node != null) {
|
||||||
|
vbox.getChildren().add(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vbox;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
app/src/main/java/org/toop/app/widget/Widget.java
Normal file
21
app/src/main/java/org/toop/app/widget/Widget.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package org.toop.app.widget;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
|
||||||
|
public interface Widget {
|
||||||
|
Node getNode();
|
||||||
|
|
||||||
|
default void show(Pos position) {
|
||||||
|
WidgetContainer.add(position, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void hide() {
|
||||||
|
WidgetContainer.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void replace(Pos position, Widget widget) {
|
||||||
|
widget.show(position);
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
65
app/src/main/java/org/toop/app/widget/WidgetContainer.java
Normal file
65
app/src/main/java/org/toop/app/widget/WidgetContainer.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package org.toop.app.widget;
|
||||||
|
|
||||||
|
import org.toop.app.widget.complex.PopupWidget;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
public final class WidgetContainer {
|
||||||
|
private static StackPane root;
|
||||||
|
private static ViewWidget currentView;
|
||||||
|
|
||||||
|
public static synchronized StackPane setup() {
|
||||||
|
if (root != null) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = new StackPane();
|
||||||
|
root.getStyleClass().add("bg-view");
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void add(Pos position, Widget widget) {
|
||||||
|
if (root == null || widget == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (root.getChildren().contains(widget.getNode())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StackPane.setAlignment(widget.getNode(), position);
|
||||||
|
|
||||||
|
if (widget instanceof ViewWidget view) {
|
||||||
|
root.getChildren().addFirst(view.getNode());
|
||||||
|
currentView = view;
|
||||||
|
} else if (widget instanceof PopupWidget popup) {
|
||||||
|
currentView.add(Pos.CENTER, popup);
|
||||||
|
} else {
|
||||||
|
root.getChildren().add(widget.getNode());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove(Widget widget) {
|
||||||
|
if (root == null || widget == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (widget instanceof PopupWidget popup) {
|
||||||
|
currentView.remove(popup);
|
||||||
|
} else {
|
||||||
|
root.getChildren().remove(widget.getNode());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewWidget getCurrentView() {
|
||||||
|
return currentView;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
public class ConfirmWidget implements Widget {
|
||||||
|
private final HBox buttonsContainer;
|
||||||
|
private final Text messageText;
|
||||||
|
private final VBox container;
|
||||||
|
|
||||||
|
public ConfirmWidget(String confirm) {
|
||||||
|
buttonsContainer = Primitive.hbox();
|
||||||
|
messageText = Primitive.text("");
|
||||||
|
container = Primitive.vbox(Primitive.header(confirm), messageText, Primitive.separator(), buttonsContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String message) {
|
||||||
|
messageText.setText(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addButton(String key, Runnable onClick) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
var button = Primitive.button(key, onClick);
|
||||||
|
buttonsContainer.getChildren().add(button);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
public class LabeledChoiceWidget<T> implements Widget {
|
||||||
|
private final ComboBox<T> comboBox;
|
||||||
|
private final VBox container;
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public LabeledChoiceWidget(
|
||||||
|
String key,
|
||||||
|
StringConverter<T> converter,
|
||||||
|
T initialValue,
|
||||||
|
Consumer<T> onValueChanged,
|
||||||
|
T... items
|
||||||
|
) {
|
||||||
|
var label = Primitive.text(key);
|
||||||
|
comboBox = Primitive.choice(converter, initialValue, onValueChanged, items);
|
||||||
|
container = Primitive.vbox(label, comboBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return comboBox.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(T value) {
|
||||||
|
comboBox.setValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
public class LabeledInputWidget implements Widget {
|
||||||
|
private final TextField input;
|
||||||
|
private final VBox container;
|
||||||
|
|
||||||
|
public LabeledInputWidget(String key, String promptKey, String initialText, Consumer<String> onValueChanged) {
|
||||||
|
var label = Primitive.text(key);
|
||||||
|
input = Primitive.input(promptKey, initialText, onValueChanged);
|
||||||
|
container = Primitive.vbox(label, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return input.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String text) {
|
||||||
|
input.setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Slider;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
public class LabeledSliderWidget implements Widget {
|
||||||
|
private final Slider slider;
|
||||||
|
private final Text labelValue;
|
||||||
|
private final VBox container;
|
||||||
|
|
||||||
|
public LabeledSliderWidget(String key, int min, int max, int value, Consumer<Integer> onValueChanged) {
|
||||||
|
var label = Primitive.text(key);
|
||||||
|
|
||||||
|
labelValue = new Text(String.valueOf(value));
|
||||||
|
labelValue.getStyleClass().add("text");
|
||||||
|
|
||||||
|
slider = Primitive.slider(min, max, value, newValue -> {
|
||||||
|
labelValue.setText(String.valueOf(newValue));
|
||||||
|
|
||||||
|
if (onValueChanged != null) {
|
||||||
|
onValueChanged.accept(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var sliderRow = Primitive.hbox(slider, labelValue);
|
||||||
|
container = Primitive.vbox(label, sliderRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() {
|
||||||
|
return (int)slider.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(int newValue) {
|
||||||
|
slider.setValue(newValue);
|
||||||
|
labelValue.setText(String.valueOf(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
public class PlayerInfoWidget {
|
||||||
|
private final GameInformation.Player information;
|
||||||
|
private final VBox container;
|
||||||
|
|
||||||
|
public PlayerInfoWidget(GameInformation.Player information) {
|
||||||
|
this.information = information;
|
||||||
|
container = Primitive.vbox(
|
||||||
|
buildToggle().getNode(),
|
||||||
|
buildContent()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ToggleWidget buildToggle() {
|
||||||
|
return new ToggleWidget(
|
||||||
|
"computer", "player",
|
||||||
|
information.isHuman,
|
||||||
|
isHuman -> {
|
||||||
|
information.isHuman = isHuman;
|
||||||
|
container.getChildren().setAll(
|
||||||
|
buildToggle().getNode(),
|
||||||
|
buildContent()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node buildContent() {
|
||||||
|
if (information.isHuman) {
|
||||||
|
var nameInput = new LabeledInputWidget(
|
||||||
|
"name",
|
||||||
|
"enter-your-name",
|
||||||
|
information.name,
|
||||||
|
newName -> information.name = newName
|
||||||
|
);
|
||||||
|
|
||||||
|
return nameInput.getNode();
|
||||||
|
} else {
|
||||||
|
if (information.name == null || information.name.isEmpty()) {
|
||||||
|
information.name = "Pism Bot";
|
||||||
|
}
|
||||||
|
|
||||||
|
var playerName = Primitive.text("");
|
||||||
|
playerName.setText(information.name);
|
||||||
|
|
||||||
|
var nameDisplay = Primitive.vbox(
|
||||||
|
Primitive.text("name"),
|
||||||
|
playerName
|
||||||
|
);
|
||||||
|
|
||||||
|
var difficultySlider = new LabeledSliderWidget(
|
||||||
|
"computer-difficulty",
|
||||||
|
0, 5,
|
||||||
|
information.computerDifficulty,
|
||||||
|
newVal -> information.computerDifficulty = newVal
|
||||||
|
);
|
||||||
|
|
||||||
|
var thinkTimeSlider = new LabeledSliderWidget(
|
||||||
|
"computer-think-time",
|
||||||
|
0, 5,
|
||||||
|
information.computerThinkTime,
|
||||||
|
newVal -> information.computerThinkTime = newVal
|
||||||
|
);
|
||||||
|
|
||||||
|
return Primitive.vbox(
|
||||||
|
nameDisplay,
|
||||||
|
difficultySlider.getNode(),
|
||||||
|
thinkTimeSlider.getNode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
public abstract class PopupWidget extends StackWidget {
|
||||||
|
public PopupWidget() {
|
||||||
|
super("bg-popup");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
public abstract class StackWidget implements Widget {
|
||||||
|
private final StackPane container;
|
||||||
|
|
||||||
|
public StackWidget(String cssClass) {
|
||||||
|
container = new StackPane();
|
||||||
|
container.getStyleClass().add(cssClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Pos position, Node node) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
if (container.getChildren().contains(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StackPane.setAlignment(node, position);
|
||||||
|
container.getChildren().add(node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Pos position, Widget widget) {
|
||||||
|
add(position, widget.getNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(Node node) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
container.getChildren().remove(node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(Widget widget) {
|
||||||
|
remove(widget.getNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
public class ToggleWidget implements Widget {
|
||||||
|
private final Button button;
|
||||||
|
private final VBox container;
|
||||||
|
|
||||||
|
private final String onKey;
|
||||||
|
private final String offKey;
|
||||||
|
|
||||||
|
private boolean state;
|
||||||
|
|
||||||
|
public ToggleWidget(String onKey, String offKey, boolean initialState, Consumer<Boolean> onToggle) {
|
||||||
|
this.onKey = onKey;
|
||||||
|
this.offKey = offKey;
|
||||||
|
this.state = initialState;
|
||||||
|
|
||||||
|
button = new Button(AppContext.getString(getCurrentKey()));
|
||||||
|
button.setOnAction(_ -> {
|
||||||
|
state = !state;
|
||||||
|
updateText();
|
||||||
|
if (onToggle != null) {
|
||||||
|
onToggle.accept(state);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
container = Primitive.vbox(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCurrentKey() {
|
||||||
|
return state? offKey : onKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateText() {
|
||||||
|
button.setText(AppContext.getString(getCurrentKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(boolean newState) {
|
||||||
|
if (state != newState) {
|
||||||
|
state = newState;
|
||||||
|
updateText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package org.toop.app.widget.complex;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public abstract class ViewWidget extends StackWidget {
|
||||||
|
private ViewWidget previous = null;
|
||||||
|
|
||||||
|
public ViewWidget() {
|
||||||
|
super("bg-primary");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transition(ViewWidget view) {
|
||||||
|
view.previous = this;
|
||||||
|
replace(Pos.CENTER, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transitionNext(ViewWidget view) {
|
||||||
|
view.previous = this;
|
||||||
|
replace(Pos.CENTER, view);
|
||||||
|
|
||||||
|
var backButton = Primitive.button("back", () -> {
|
||||||
|
view.transitionPrevious();
|
||||||
|
});
|
||||||
|
|
||||||
|
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void transitionPrevious() {
|
||||||
|
if (previous == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
replace(Pos.CENTER, previous);
|
||||||
|
previous = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload(ViewWidget view) {
|
||||||
|
view.previous = previous;
|
||||||
|
replace(Pos.CENTER, view);
|
||||||
|
|
||||||
|
var backButton = Primitive.button("back", () -> {
|
||||||
|
view.transitionPrevious();
|
||||||
|
});
|
||||||
|
|
||||||
|
view.add(Pos.BOTTOM_LEFT, Primitive.vbox(backButton));
|
||||||
|
}
|
||||||
|
}
|
||||||
117
app/src/main/java/org/toop/app/widget/display/SongDisplay.java
Normal file
117
app/src/main/java/org/toop/app/widget/display/SongDisplay.java
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package org.toop.app.widget.display;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Widget;
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.framework.eventbus.GlobalEventBus;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ProgressBar;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
public class SongDisplay extends VBox implements Widget {
|
||||||
|
private final Text songTitle;
|
||||||
|
private final ProgressBar progressBar;
|
||||||
|
private final Text progressText;
|
||||||
|
|
||||||
|
public SongDisplay() {
|
||||||
|
new EventFlow()
|
||||||
|
.listen(this::updateTheSong);
|
||||||
|
|
||||||
|
setAlignment(Pos.CENTER);
|
||||||
|
setMaxHeight(Region.USE_PREF_SIZE);
|
||||||
|
getStyleClass().add("song-display");
|
||||||
|
|
||||||
|
// TODO ADD GOOD SONG TITLES WITH ARTISTS DISPLAYED
|
||||||
|
songTitle = new Text("song playing");
|
||||||
|
songTitle.getStyleClass().add("song-title");
|
||||||
|
|
||||||
|
progressBar = new ProgressBar(0);
|
||||||
|
progressBar.getStyleClass().add("progress-bar");
|
||||||
|
|
||||||
|
progressText = new Text("0:00/0:00");
|
||||||
|
progressText.getStyleClass().add("progress-text");
|
||||||
|
|
||||||
|
// TODO ADD BETTER CSS FOR THE SKIPBUTTON WHERE ITS AT A NICER POSITION
|
||||||
|
|
||||||
|
Button skipButton = new Button(">>");
|
||||||
|
Button pauseButton = new Button("⏸");
|
||||||
|
Button previousButton = new Button("<<");
|
||||||
|
|
||||||
|
skipButton.getStyleClass().setAll("skip-button");
|
||||||
|
pauseButton.getStyleClass().setAll("pause-button");
|
||||||
|
previousButton.getStyleClass().setAll("previous-button");
|
||||||
|
|
||||||
|
skipButton.setOnAction( event -> {
|
||||||
|
GlobalEventBus.post(new AudioEvents.SkipMusic());
|
||||||
|
});
|
||||||
|
|
||||||
|
pauseButton.setOnAction(event -> {
|
||||||
|
GlobalEventBus.post(new AudioEvents.PauseMusic());
|
||||||
|
if (pauseButton.getText().equals("⏸")) {
|
||||||
|
pauseButton.setText("▶");
|
||||||
|
}
|
||||||
|
else if (pauseButton.getText().equals("▶")) {
|
||||||
|
pauseButton.setText("⏸");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
previousButton.setOnAction( event -> {
|
||||||
|
GlobalEventBus.post(new AudioEvents.PreviousMusic());
|
||||||
|
});
|
||||||
|
|
||||||
|
HBox control = new HBox(10, previousButton, pauseButton, skipButton);
|
||||||
|
control.setAlignment(Pos.CENTER);
|
||||||
|
control.getStyleClass().add("controls");
|
||||||
|
|
||||||
|
getChildren().addAll(songTitle, progressBar, progressText, control);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTheSong(AudioEvents.PlayingMusic event) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
String text = event.name();
|
||||||
|
text = text.substring(0, text.length() - 4);
|
||||||
|
songTitle.setText(text);
|
||||||
|
double currentPos = event.currentPosition();
|
||||||
|
double duration = event.duration();
|
||||||
|
if (currentPos / duration > 0.05) {
|
||||||
|
double progress = currentPos / duration;
|
||||||
|
progressBar.setProgress(progress);
|
||||||
|
}
|
||||||
|
else if (currentPos / duration < 0.05) {
|
||||||
|
progressBar.setProgress(0.05);
|
||||||
|
}
|
||||||
|
progressText.setText(getTimeString(event.currentPosition(), event.duration()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTimeString(long position, long duration) {
|
||||||
|
long positionMinutes = position / 60;
|
||||||
|
long durationMinutes = duration / 60;
|
||||||
|
long positionSeconds = position % 60;
|
||||||
|
long durationSeconds = duration % 60;
|
||||||
|
String positionSecondsStr = String.valueOf(positionSeconds);
|
||||||
|
String durationSecondsStr = String.valueOf(durationSeconds);
|
||||||
|
|
||||||
|
if (positionSeconds < 10) {
|
||||||
|
positionSecondsStr = "0" + positionSeconds;
|
||||||
|
}
|
||||||
|
if (durationSeconds < 10) {
|
||||||
|
durationSecondsStr = "0" + durationSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr;
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getNode() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package org.toop.app.widget.popup;
|
||||||
|
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.PlayerInfoWidget;
|
||||||
|
import org.toop.app.widget.complex.PopupWidget;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public final class ChallengePopup extends PopupWidget {
|
||||||
|
private final GameInformation.Player playerInformation;
|
||||||
|
private final String challenger;
|
||||||
|
private final String game;
|
||||||
|
private final Consumer<GameInformation.Player> onAccept;
|
||||||
|
|
||||||
|
public ChallengePopup(String challenger, String game, Consumer<GameInformation.Player> onAccept) {
|
||||||
|
this.challenger = challenger;
|
||||||
|
this.game = game;
|
||||||
|
this.onAccept = onAccept;
|
||||||
|
|
||||||
|
this.playerInformation = new GameInformation.Player();
|
||||||
|
|
||||||
|
setupLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLayout() {
|
||||||
|
var challengeText = Primitive.text("you-were-challenged-by");
|
||||||
|
|
||||||
|
var challengerHeader = Primitive.header("");
|
||||||
|
challengerHeader.setText(challenger);
|
||||||
|
|
||||||
|
var gameText = Primitive.text("to-a-game-of");
|
||||||
|
gameText.setText(gameText.getText() + " " + game);
|
||||||
|
|
||||||
|
var acceptButton = Primitive.button("accept", () -> onAccept.accept(playerInformation));
|
||||||
|
var denyButton = Primitive.button("deny", () -> hide());
|
||||||
|
|
||||||
|
var leftSection = Primitive.vbox(
|
||||||
|
challengeText,
|
||||||
|
challengerHeader,
|
||||||
|
gameText,
|
||||||
|
Primitive.separator(),
|
||||||
|
Primitive.hbox(
|
||||||
|
acceptButton,
|
||||||
|
denyButton
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
var playerInfoWidget = new PlayerInfoWidget(playerInformation);
|
||||||
|
|
||||||
|
add(Pos.CENTER,
|
||||||
|
Primitive.hbox(
|
||||||
|
leftSection,
|
||||||
|
playerInfoWidget.getNode()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/src/main/java/org/toop/app/widget/popup/ErrorPopup.java
Normal file
16
app/src/main/java/org/toop/app/widget/popup/ErrorPopup.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package org.toop.app.widget.popup;
|
||||||
|
|
||||||
|
import org.toop.app.widget.complex.ConfirmWidget;
|
||||||
|
import org.toop.app.widget.complex.PopupWidget;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public class ErrorPopup extends PopupWidget {
|
||||||
|
public ErrorPopup(String error) {
|
||||||
|
var confirmWidget = new ConfirmWidget("error");
|
||||||
|
confirmWidget.setMessage(error);
|
||||||
|
confirmWidget.addButton("ok", this::hide);
|
||||||
|
|
||||||
|
add(Pos.CENTER, confirmWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.toop.app.widget.popup;
|
||||||
|
|
||||||
|
import org.toop.app.widget.complex.ConfirmWidget;
|
||||||
|
import org.toop.app.widget.complex.PopupWidget;
|
||||||
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public final class GameOverPopup extends PopupWidget {
|
||||||
|
public GameOverPopup(boolean iWon, String winner) {
|
||||||
|
var confirmWidget = new ConfirmWidget("game-over");
|
||||||
|
|
||||||
|
if (winner.isEmpty()) {
|
||||||
|
confirmWidget.setMessage(AppContext.getString("the-game-ended-in-a-draw"));
|
||||||
|
} else if (iWon) {
|
||||||
|
confirmWidget.setMessage(AppContext.getString("you-win"));
|
||||||
|
} else {
|
||||||
|
confirmWidget.setMessage(AppContext.getString("you-lost-against") + ": " + winner);
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmWidget.addButton("ok", () -> hide());
|
||||||
|
|
||||||
|
add(Pos.CENTER, confirmWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
app/src/main/java/org/toop/app/widget/popup/QuitPopup.java
Normal file
24
app/src/main/java/org/toop/app/widget/popup/QuitPopup.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package org.toop.app.widget.popup;
|
||||||
|
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.app.widget.complex.ConfirmWidget;
|
||||||
|
import org.toop.app.widget.complex.PopupWidget;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public class QuitPopup extends PopupWidget {
|
||||||
|
public QuitPopup() {
|
||||||
|
var confirmWidget = new ConfirmWidget("are-you-sure");
|
||||||
|
|
||||||
|
confirmWidget.addButton("yes", () -> {
|
||||||
|
App.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
confirmWidget.addButton("no", () -> {
|
||||||
|
App.stopQuit();
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
add(Pos.CENTER, confirmWidget);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package org.toop.app.widget.popup;
|
||||||
|
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.Server;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.LabeledChoiceWidget;
|
||||||
|
import org.toop.app.widget.complex.PlayerInfoWidget;
|
||||||
|
import org.toop.app.widget.complex.PopupWidget;
|
||||||
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
public final class SendChallengePopup extends PopupWidget {
|
||||||
|
private final Server server;
|
||||||
|
private final String opponent;
|
||||||
|
private final BiConsumer<GameInformation.Player, String> onSend;
|
||||||
|
|
||||||
|
private final GameInformation.Player playerInformation;
|
||||||
|
|
||||||
|
public SendChallengePopup(Server server, String opponent, BiConsumer<GameInformation.Player, String> onSend) {
|
||||||
|
this.server = server;
|
||||||
|
this.opponent = opponent;
|
||||||
|
this.onSend = onSend;
|
||||||
|
|
||||||
|
this.playerInformation = new GameInformation.Player();
|
||||||
|
|
||||||
|
setupLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLayout() {
|
||||||
|
// --- Left side: challenge text and buttons ---
|
||||||
|
var challengeText = Primitive.text("challenge");
|
||||||
|
|
||||||
|
var opponentHeader = Primitive.header(opponent);
|
||||||
|
|
||||||
|
var gameText = Primitive.text("to-a-game-of");
|
||||||
|
|
||||||
|
var games = server.getGameList();
|
||||||
|
var gameChoice = new LabeledChoiceWidget<>(
|
||||||
|
"game",
|
||||||
|
new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(String game) {
|
||||||
|
return AppContext.getString(game);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String fromString(String s) { return null; }
|
||||||
|
},
|
||||||
|
games.getFirst(),
|
||||||
|
newGame -> {
|
||||||
|
playerInformation.computerDifficulty = Math.min(
|
||||||
|
playerInformation.computerDifficulty,
|
||||||
|
Server.gameToType(newGame).getMaxDepth()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
games.toArray(new String[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
var sendButton = Primitive.button(
|
||||||
|
"send",
|
||||||
|
() -> onSend.accept(playerInformation, gameChoice.getValue())
|
||||||
|
);
|
||||||
|
|
||||||
|
var cancelButton = Primitive.button("cancel", () -> hide());
|
||||||
|
|
||||||
|
var leftSection = Primitive.vbox(
|
||||||
|
challengeText,
|
||||||
|
opponentHeader,
|
||||||
|
gameText,
|
||||||
|
gameChoice.getNode(),
|
||||||
|
Primitive.separator(),
|
||||||
|
Primitive.hbox(
|
||||||
|
sendButton,
|
||||||
|
cancelButton
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
var playerInfoWidget = new PlayerInfoWidget(playerInformation);
|
||||||
|
|
||||||
|
add(Pos.CENTER,
|
||||||
|
Primitive.hbox(
|
||||||
|
leftSection,
|
||||||
|
playerInfoWidget.getNode()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
app/src/main/java/org/toop/app/widget/view/CreditsView.java
Normal file
81
app/src/main/java/org/toop/app/widget/view/CreditsView.java
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
|
public class CreditsView extends ViewWidget {
|
||||||
|
public CreditsView() {
|
||||||
|
var scrumMasterCredit = newCredit("scrum-master", "Stef");
|
||||||
|
var productOwnerCredit = newCredit("product-owner", "Omar");
|
||||||
|
var mergeCommanderCredit = newCredit("merge-commander", "Bas");
|
||||||
|
var localizationCredit = newCredit("localization", "Ticho");
|
||||||
|
var aiCredit = newCredit("ai", "Michiel");
|
||||||
|
var developersCredit = newCredit("developers", "Michiel, Bas, Stef, Omar, Ticho");
|
||||||
|
var moralSupportCredit = newCredit("moral-support", "Wesley");
|
||||||
|
var openglCredit = newCredit("opengl", "Omar");
|
||||||
|
|
||||||
|
var topSpacer = new Region();
|
||||||
|
topSpacer.setPrefHeight(App.getHeight());
|
||||||
|
|
||||||
|
var bottomSpacer = new Region();
|
||||||
|
bottomSpacer.setPrefHeight(App.getHeight());
|
||||||
|
|
||||||
|
var creditsContainer = Primitive.vbox(
|
||||||
|
topSpacer,
|
||||||
|
|
||||||
|
scrumMasterCredit,
|
||||||
|
productOwnerCredit,
|
||||||
|
mergeCommanderCredit,
|
||||||
|
localizationCredit,
|
||||||
|
aiCredit,
|
||||||
|
developersCredit,
|
||||||
|
moralSupportCredit,
|
||||||
|
openglCredit,
|
||||||
|
|
||||||
|
bottomSpacer
|
||||||
|
);
|
||||||
|
|
||||||
|
var creditsScroll = Primitive.scroll(creditsContainer);
|
||||||
|
|
||||||
|
creditsScroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
creditsScroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
|
||||||
|
add(Pos.CENTER, creditsScroll);
|
||||||
|
|
||||||
|
animate(creditsScroll, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HBox newCredit(String key, String other) {
|
||||||
|
var credit = new Text(": " + other);
|
||||||
|
credit.getStyleClass().add("header");
|
||||||
|
|
||||||
|
var creditBox = Primitive.hbox(
|
||||||
|
Primitive.header(key),
|
||||||
|
credit
|
||||||
|
);
|
||||||
|
|
||||||
|
creditBox.setPrefHeight(App.getHeight() / 3.0);
|
||||||
|
return creditBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animate(ScrollPane scroll, int length) {
|
||||||
|
final Timeline timeline = new Timeline(
|
||||||
|
new KeyFrame(Duration.seconds(0), new KeyValue(scroll.vvalueProperty(), 0.0)),
|
||||||
|
new KeyFrame(Duration.seconds(length), new KeyValue(scroll.vvalueProperty(), 1.0))
|
||||||
|
);
|
||||||
|
|
||||||
|
timeline.setCycleCount(Timeline.INDEFINITE);
|
||||||
|
timeline.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
96
app/src/main/java/org/toop/app/widget/view/GameView.java
Normal file
96
app/src/main/java/org/toop/app/widget/view/GameView.java
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
import org.toop.app.widget.popup.GameOverPopup;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
public final class GameView extends ViewWidget {
|
||||||
|
private final Text currentPlayerHeader;
|
||||||
|
private final Text currentMoveHeader;
|
||||||
|
private final Text nextPlayerHeader;
|
||||||
|
|
||||||
|
private final Button forfeitButton;
|
||||||
|
private final Button exitButton;
|
||||||
|
|
||||||
|
private final TextField chatInput;
|
||||||
|
|
||||||
|
public GameView(Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
|
||||||
|
currentPlayerHeader = Primitive.header("");
|
||||||
|
currentMoveHeader = Primitive.header("");
|
||||||
|
nextPlayerHeader = Primitive.header("");
|
||||||
|
|
||||||
|
if (onForfeit != null) {
|
||||||
|
forfeitButton = Primitive.button("forfeit", () -> onForfeit.run());
|
||||||
|
} else {
|
||||||
|
forfeitButton = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
exitButton = Primitive.button("exit", () -> {
|
||||||
|
onExit.run();
|
||||||
|
transitionPrevious();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onMessage != null) {
|
||||||
|
chatInput = Primitive.input("enter-your-message", "", null);
|
||||||
|
chatInput.setOnAction(_ -> {
|
||||||
|
onMessage.accept(chatInput.getText());
|
||||||
|
chatInput.clear();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
chatInput = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLayout() {
|
||||||
|
var playerInfo = Primitive.vbox(
|
||||||
|
currentPlayerHeader,
|
||||||
|
Primitive.hbox(
|
||||||
|
Primitive.separator(),
|
||||||
|
currentMoveHeader,
|
||||||
|
Primitive.separator()
|
||||||
|
),
|
||||||
|
nextPlayerHeader
|
||||||
|
);
|
||||||
|
|
||||||
|
add(Pos.TOP_RIGHT, playerInfo);
|
||||||
|
|
||||||
|
var buttons = Primitive.vbox(
|
||||||
|
forfeitButton,
|
||||||
|
exitButton
|
||||||
|
);
|
||||||
|
|
||||||
|
add(Pos.BOTTOM_LEFT, buttons);
|
||||||
|
|
||||||
|
if (chatInput != null) {
|
||||||
|
add(Pos.BOTTOM_RIGHT, Primitive.vbox(chatInput));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
currentPlayerHeader.setText(currentPlayer);
|
||||||
|
currentMoveHeader.setText(currentMove);
|
||||||
|
nextPlayerHeader.setText(nextPlayer);
|
||||||
|
|
||||||
|
if (isMe) {
|
||||||
|
currentPlayerHeader.getStyleClass().add("my-turn");
|
||||||
|
} else {
|
||||||
|
currentPlayerHeader.getStyleClass().remove("my-turn");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void gameOver(boolean iWon, String winner) {
|
||||||
|
new GameOverPopup(iWon, winner).show(Pos.CENTER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.game.Connect4Game;
|
||||||
|
import org.toop.app.game.ReversiGame;
|
||||||
|
import org.toop.app.game.TicTacToeGameThread;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.PlayerInfoWidget;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
import org.toop.app.widget.popup.ErrorPopup;
|
||||||
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
public class LocalMultiplayerView extends ViewWidget {
|
||||||
|
private final GameInformation information;
|
||||||
|
|
||||||
|
public LocalMultiplayerView(GameInformation.Type type) {
|
||||||
|
this(new GameInformation(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalMultiplayerView(GameInformation information) {
|
||||||
|
this.information = information;
|
||||||
|
var playButton = Primitive.button("play", () -> {
|
||||||
|
for (var player : information.players) {
|
||||||
|
if (player.isHuman && player.name.isEmpty()) {
|
||||||
|
new ErrorPopup(AppContext.getString("please-enter-your-name")).show(Pos.CENTER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (information.type) {
|
||||||
|
case TICTACTOE -> new TicTacToeGameThread(information);
|
||||||
|
case REVERSI -> new ReversiGame(information);
|
||||||
|
case CONNECT4 -> new Connect4Game(information);
|
||||||
|
// case BATTLESHIP -> new BattleshipGame(information);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var playerSection = setupPlayerSections();
|
||||||
|
|
||||||
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
|
playerSection,
|
||||||
|
Primitive.separator(),
|
||||||
|
playButton
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScrollPane setupPlayerSections() {
|
||||||
|
int playerCount = information.type.getPlayerCount();
|
||||||
|
VBox[] playerBoxes = new VBox[playerCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < playerCount; i++) {
|
||||||
|
var player = information.players[i];
|
||||||
|
|
||||||
|
var playerHeader = Primitive.header("");
|
||||||
|
playerHeader.setText("player" + " #" + (i + 1));
|
||||||
|
|
||||||
|
var playerWidget = new PlayerInfoWidget(player);
|
||||||
|
|
||||||
|
playerBoxes[i] = Primitive.vbox(
|
||||||
|
playerHeader,
|
||||||
|
Primitive.separator(),
|
||||||
|
playerWidget.getNode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Primitive.scroll(Primitive.hbox(
|
||||||
|
playerBoxes
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
29
app/src/main/java/org/toop/app/widget/view/LocalView.java
Normal file
29
app/src/main/java/org/toop/app/widget/view/LocalView.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public class LocalView extends ViewWidget {
|
||||||
|
public LocalView() {
|
||||||
|
var ticTacToeButton = Primitive.button("tic-tac-toe", () -> {
|
||||||
|
transitionNext(new LocalMultiplayerView(GameInformation.Type.TICTACTOE));
|
||||||
|
});
|
||||||
|
|
||||||
|
var reversiButton = Primitive.button("reversi", () -> {
|
||||||
|
transitionNext(new LocalMultiplayerView(GameInformation.Type.REVERSI));
|
||||||
|
});
|
||||||
|
|
||||||
|
var connect4Button = Primitive.button("connect4", () -> {
|
||||||
|
transitionNext(new LocalMultiplayerView(GameInformation.Type.CONNECT4));
|
||||||
|
});
|
||||||
|
|
||||||
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
|
ticTacToeButton,
|
||||||
|
reversiButton,
|
||||||
|
connect4Button
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
39
app/src/main/java/org/toop/app/widget/view/MainView.java
Normal file
39
app/src/main/java/org/toop/app/widget/view/MainView.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public class MainView extends ViewWidget {
|
||||||
|
public MainView() {
|
||||||
|
var localButton = Primitive.button("local", () -> {
|
||||||
|
transitionNext(new LocalView());
|
||||||
|
});
|
||||||
|
|
||||||
|
var onlineButton = Primitive.button("online", () -> {
|
||||||
|
transitionNext(new OnlineView());
|
||||||
|
});
|
||||||
|
|
||||||
|
var creditsButton = Primitive.button("credits", () -> {
|
||||||
|
transitionNext(new CreditsView());
|
||||||
|
});
|
||||||
|
|
||||||
|
var optionsButton = Primitive.button("options", () -> {
|
||||||
|
transitionNext(new OptionsView());
|
||||||
|
});
|
||||||
|
|
||||||
|
var quitButton = Primitive.button("quit", () -> {
|
||||||
|
App.startQuit();
|
||||||
|
});
|
||||||
|
|
||||||
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
|
localButton,
|
||||||
|
onlineButton,
|
||||||
|
creditsButton,
|
||||||
|
optionsButton,
|
||||||
|
quitButton
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
38
app/src/main/java/org/toop/app/widget/view/OnlineView.java
Normal file
38
app/src/main/java/org/toop/app/widget/view/OnlineView.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.Server;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.LabeledInputWidget;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
|
||||||
|
public class OnlineView extends ViewWidget {
|
||||||
|
public OnlineView() {
|
||||||
|
var serverInformationHeader = Primitive.header("server-information");
|
||||||
|
|
||||||
|
var serverIPInput = new LabeledInputWidget("ip-address", "enter-the-server-ip", "", _ -> {});
|
||||||
|
var serverPortInput = new LabeledInputWidget("port", "enter-the-server-port", "", _ -> {});
|
||||||
|
var playerNameInput = new LabeledInputWidget("player-name", "enter-your-name", "", _ -> {});
|
||||||
|
|
||||||
|
var connectButton = Primitive.button("connect", () -> {
|
||||||
|
new Server(
|
||||||
|
serverIPInput.getValue(),
|
||||||
|
serverPortInput.getValue(),
|
||||||
|
playerNameInput.getValue()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add(Pos.CENTER, Primitive.vbox(
|
||||||
|
serverInformationHeader,
|
||||||
|
Primitive.separator(),
|
||||||
|
|
||||||
|
serverIPInput.getNode(),
|
||||||
|
serverPortInput.getNode(),
|
||||||
|
playerNameInput.getNode(),
|
||||||
|
Primitive.separator(),
|
||||||
|
|
||||||
|
connectButton
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
161
app/src/main/java/org/toop/app/widget/view/OptionsView.java
Normal file
161
app/src/main/java/org/toop/app/widget/view/OptionsView.java
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.App;
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.LabeledChoiceWidget;
|
||||||
|
import org.toop.app.widget.complex.LabeledSliderWidget;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
import org.toop.app.widget.complex.ToggleWidget;
|
||||||
|
import org.toop.framework.audio.VolumeControl;
|
||||||
|
import org.toop.framework.audio.events.AudioEvents;
|
||||||
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
|
import org.toop.local.AppContext;
|
||||||
|
import org.toop.local.AppSettings;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
|
public class OptionsView extends ViewWidget {
|
||||||
|
public OptionsView() {
|
||||||
|
add(Pos.CENTER, Primitive.hbox(
|
||||||
|
generalSection(),
|
||||||
|
volumeSection(),
|
||||||
|
styleSection()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private VBox generalSection() {
|
||||||
|
var languageWidget = new LabeledChoiceWidget<>(
|
||||||
|
"language",
|
||||||
|
new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(Locale locale) {
|
||||||
|
return AppContext.getString(locale.getDisplayName().toLowerCase());
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public Locale fromString(String s) { return null; }
|
||||||
|
},
|
||||||
|
AppContext.getLocale(),
|
||||||
|
newLocale -> {
|
||||||
|
AppSettings.getSettings().setLocale(newLocale.toString());
|
||||||
|
AppContext.setLocale(newLocale);
|
||||||
|
reload(new OptionsView());
|
||||||
|
},
|
||||||
|
AppContext.getLocalization().getAvailableLocales().toArray(new Locale[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
var fullscreenToggle = new ToggleWidget(
|
||||||
|
"fullscreen", "windowed",
|
||||||
|
AppSettings.getSettings().getFullscreen(),
|
||||||
|
fullscreen -> {
|
||||||
|
AppSettings.getSettings().setFullscreen(fullscreen);
|
||||||
|
App.setFullscreen(fullscreen);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Primitive.vbox(
|
||||||
|
Primitive.header("general"),
|
||||||
|
Primitive.separator(),
|
||||||
|
|
||||||
|
languageWidget.getNode(),
|
||||||
|
fullscreenToggle.getNode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VBox volumeSection() {
|
||||||
|
var masterVolumeWidget = new LabeledSliderWidget(
|
||||||
|
"master-volume",
|
||||||
|
0, 100,
|
||||||
|
AppSettings.getSettings().getVolume(),
|
||||||
|
val -> {
|
||||||
|
AppSettings.getSettings().setVolume(val);
|
||||||
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(val, VolumeControl.MASTERVOLUME))
|
||||||
|
.asyncPostEvent();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var effectsVolumeWidget = new LabeledSliderWidget(
|
||||||
|
"effects-volume",
|
||||||
|
0, 100,
|
||||||
|
AppSettings.getSettings().getFxVolume(),
|
||||||
|
val -> {
|
||||||
|
AppSettings.getSettings().setFxVolume(val);
|
||||||
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(val, VolumeControl.FX))
|
||||||
|
.asyncPostEvent();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var musicVolumeWidget = new LabeledSliderWidget(
|
||||||
|
"music-volume",
|
||||||
|
0, 100,
|
||||||
|
AppSettings.getSettings().getMusicVolume(),
|
||||||
|
val -> {
|
||||||
|
AppSettings.getSettings().setMusicVolume(val);
|
||||||
|
new EventFlow()
|
||||||
|
.addPostEvent(new AudioEvents.ChangeVolume(val, VolumeControl.MUSIC))
|
||||||
|
.asyncPostEvent();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Primitive.vbox(
|
||||||
|
Primitive.header("volume"),
|
||||||
|
Primitive.separator(),
|
||||||
|
|
||||||
|
masterVolumeWidget.getNode(),
|
||||||
|
effectsVolumeWidget.getNode(),
|
||||||
|
musicVolumeWidget.getNode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VBox styleSection() {
|
||||||
|
var themeWidget = new LabeledChoiceWidget<>(
|
||||||
|
"theme",
|
||||||
|
new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(String theme) {
|
||||||
|
return AppContext.getString(theme);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String fromString(String s) { return null; }
|
||||||
|
},
|
||||||
|
AppSettings.getSettings().getTheme(),
|
||||||
|
newTheme -> {
|
||||||
|
AppSettings.getSettings().setTheme(newTheme);
|
||||||
|
App.setStyle(newTheme, AppSettings.getSettings().getLayoutSize());
|
||||||
|
},
|
||||||
|
"dark", "light", "high-contrast"
|
||||||
|
);
|
||||||
|
|
||||||
|
var layoutWidget = new LabeledChoiceWidget<>(
|
||||||
|
"layout-size",
|
||||||
|
new StringConverter<>() {
|
||||||
|
@Override
|
||||||
|
public String toString(String layout) {
|
||||||
|
return AppContext.getString(layout);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String fromString(String s) { return null; }
|
||||||
|
},
|
||||||
|
AppSettings.getSettings().getLayoutSize(),
|
||||||
|
newLayout -> {
|
||||||
|
AppSettings.getSettings().setLayoutSize(newLayout);
|
||||||
|
App.setStyle(AppSettings.getSettings().getTheme(), newLayout);
|
||||||
|
},
|
||||||
|
"small", "medium", "large"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return Primitive.vbox(
|
||||||
|
Primitive.header("style"),
|
||||||
|
Primitive.separator(),
|
||||||
|
|
||||||
|
themeWidget.getNode(),
|
||||||
|
layoutWidget.getNode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
app/src/main/java/org/toop/app/widget/view/ServerView.java
Normal file
60
app/src/main/java/org/toop/app/widget/view/ServerView.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package org.toop.app.widget.view;
|
||||||
|
|
||||||
|
import org.toop.app.widget.Primitive;
|
||||||
|
import org.toop.app.widget.complex.ViewWidget;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
|
||||||
|
public final class ServerView extends ViewWidget {
|
||||||
|
private final String user;
|
||||||
|
private final Consumer<String> onPlayerClicked;
|
||||||
|
private final Runnable onDisconnect;
|
||||||
|
|
||||||
|
private final ListView<Button> listView;
|
||||||
|
|
||||||
|
public ServerView(String user, Consumer<String> onPlayerClicked, Runnable onDisconnect) {
|
||||||
|
this.user = user;
|
||||||
|
this.onPlayerClicked = onPlayerClicked;
|
||||||
|
this.onDisconnect = onDisconnect;
|
||||||
|
|
||||||
|
this.listView = new ListView<>();
|
||||||
|
|
||||||
|
setupLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupLayout() {
|
||||||
|
var playerHeader = Primitive.header(user);
|
||||||
|
|
||||||
|
var playerListSection = Primitive.vbox(
|
||||||
|
playerHeader,
|
||||||
|
Primitive.separator(),
|
||||||
|
listView
|
||||||
|
);
|
||||||
|
|
||||||
|
add(Pos.CENTER, playerListSection);
|
||||||
|
|
||||||
|
var disconnectButton = Primitive.button("disconnect", () -> {
|
||||||
|
onDisconnect.run();
|
||||||
|
transitionPrevious();
|
||||||
|
});
|
||||||
|
|
||||||
|
add(Pos.BOTTOM_LEFT, Primitive.vbox(disconnectButton));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(List<String> players) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
listView.getItems().clear();
|
||||||
|
|
||||||
|
for (String player : players) {
|
||||||
|
var playerButton = Primitive.button(player, () -> onPlayerClicked.accept(player));
|
||||||
|
listView.getItems().add(playerButton);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,18 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.toop.framework.resource.ResourceManager;
|
import org.toop.framework.resource.ResourceManager;
|
||||||
import org.toop.framework.resource.resources.LocalizationAsset;
|
import org.toop.framework.resource.resources.LocalizationAsset;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
||||||
public class AppContext {
|
public class AppContext {
|
||||||
private static final LocalizationAsset localization = ResourceManager.get("localization");
|
private static final LocalizationAsset localization = ResourceManager.get("localization");
|
||||||
private static Locale locale = Locale.forLanguageTag("en");
|
private static Locale locale = Locale.forLanguageTag("en");
|
||||||
|
|
||||||
|
private static final ObjectProperty<Locale> localeProperty = new SimpleObjectProperty<>(locale);
|
||||||
private static final Logger logger = LogManager.getLogger(AppContext.class);
|
private static final Logger logger = LogManager.getLogger(AppContext.class);
|
||||||
|
|
||||||
public static LocalizationAsset getLocalization() {
|
public static LocalizationAsset getLocalization() {
|
||||||
@@ -20,6 +28,7 @@ public class AppContext {
|
|||||||
|
|
||||||
public static void setLocale(Locale locale) {
|
public static void setLocale(Locale locale) {
|
||||||
AppContext.locale = locale;
|
AppContext.locale = locale;
|
||||||
|
localeProperty.set(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Locale getLocale() {
|
public static Locale getLocale() {
|
||||||
@@ -48,4 +57,11 @@ public class AppContext {
|
|||||||
// Default return
|
// Default return
|
||||||
return "MISSING RESOURCE";
|
return "MISSING RESOURCE";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static StringBinding bindToKey(String key) {
|
||||||
|
return Bindings.createStringBinding(
|
||||||
|
() -> localization.getString(key, locale),
|
||||||
|
localeProperty
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,36 +3,35 @@
|
|||||||
-fx-padding: 0;
|
-fx-padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container,
|
.container {
|
||||||
.credits-container {
|
|
||||||
-fx-alignment: TOP_CENTER;
|
-fx-alignment: TOP_CENTER;
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fit,
|
.scroll,
|
||||||
.fit .viewport {
|
.scroll .viewport {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-border-color: transparent;
|
-fx-border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fit .scroll-bar .decrement-arrow,
|
.scroll .scroll-bar .decrement-arrow,
|
||||||
.fit .scroll-bar .decrement-button,
|
.scroll .scroll-bar .decrement-button,
|
||||||
.fit .scroll-bar .increment-arrow,
|
.scroll .scroll-bar .increment-arrow,
|
||||||
.fit .scroll-bar .increment-button {
|
.scroll .scroll-bar .increment-button {
|
||||||
-fx-padding: 0;
|
-fx-padding: 0;
|
||||||
-fx-pref-height: 0;
|
-fx-pref-height: 0;
|
||||||
-fx-pref-width: 0;
|
-fx-pref-width: 0;
|
||||||
-fx-shape: "";
|
-fx-shape: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
.fit .scroll-bar .thumb {
|
.scroll .scroll-bar .thumb {
|
||||||
-fx-background-color: #888;
|
-fx-background-color: #888;
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
-fx-background-radius: 1px;
|
-fx-background-radius: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fit .scroll-bar:horizontal,
|
.scroll .scroll-bar:horizontal,
|
||||||
.fit .scroll-bar:vertical {
|
.scroll .scroll-bar:vertical {
|
||||||
-fx-pref-height: 4px;
|
-fx-pref-height: 4px;
|
||||||
-fx-pref-width: 4px;
|
-fx-pref-width: 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,19 +46,16 @@ public class EventFlow {
|
|||||||
/** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */
|
/** Empty constructor (event must be added via {@link #addPostEvent(Class, Object...)}). */
|
||||||
public EventFlow() {}
|
public EventFlow() {}
|
||||||
|
|
||||||
// New: accept an event instance directly
|
|
||||||
public EventFlow addPostEvent(EventType event) {
|
public EventFlow addPostEvent(EventType event) {
|
||||||
this.event = event;
|
this.event = event;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: accept a Supplier<EventType> to defer construction
|
|
||||||
public EventFlow addPostEvent(Supplier<? extends EventType> eventSupplier) {
|
public EventFlow addPostEvent(Supplier<? extends EventType> eventSupplier) {
|
||||||
this.event = eventSupplier.get();
|
this.event = eventSupplier.get();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep the old class+args version if needed
|
|
||||||
public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
|
public <T extends EventType> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
|
||||||
try {
|
try {
|
||||||
boolean isUuidEvent = UniqueEvent.class.isAssignableFrom(eventClass);
|
boolean isUuidEvent = UniqueEvent.class.isAssignableFrom(eventClass);
|
||||||
|
|||||||
Reference in New Issue
Block a user