Merge remote-tracking branch 'origin/UI' into UI

# Conflicts:
#	app/src/main/java/org/toop/app/App.java
#	app/src/main/java/org/toop/app/layer/layers/MainLayer.java
#	app/src/main/java/org/toop/app/layer/layers/OptionsPopup.java
#	app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java
#	app/src/main/java/org/toop/local/AppSettings.java
This commit is contained in:
lieght
2025-10-15 21:05:33 +02:00
31 changed files with 957 additions and 309 deletions

View File

@@ -1,15 +1,8 @@
package org.toop.app;
import java.util.Stack;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.toop.app.layer.Layer;
import org.toop.app.layer.layers.MainLayer;
import org.toop.app.layer.layers.QuitPopup;
import org.toop.framework.audio.VolumeControl;
import org.toop.app.view.ViewStack;
import org.toop.app.view.views.MainView;
import org.toop.app.view.views.QuitView;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.resource.ResourceManager;
@@ -17,154 +10,110 @@ import org.toop.framework.resource.resources.CssAsset;
import org.toop.local.AppContext;
import org.toop.local.AppSettings;
public final class App extends Application {
private static Stage stage;
private static Scene scene;
private static StackPane root;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public final class App extends Application {
private static Stage stage;
private static Scene scene;
private static Stack<Layer> stack;
private static int height;
private static int width;
private static boolean isQuitting;
private static boolean isQuitting;
public static void run(String[] args) {
launch(args);
}
public static void run(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) throws Exception {
@Override
public void start(Stage stage) throws Exception {
final StackPane root = new StackPane();
final Scene scene = new Scene(root);
final Scene scene = new Scene(root);
ViewStack.setup(scene);
stage.setTitle(AppContext.getString("appTitle"));
stage.setWidth(1080);
stage.setHeight(720);
stage.setTitle(AppContext.getString("app-title"));
stage.setWidth(1080);
stage.setHeight(720);
stage.setOnCloseRequest(
event -> {
event.consume();
stage.setOnCloseRequest(event -> {
event.consume();
startQuit();
});
if (!isQuitting) {
quitPopup();
}
});
stage.setScene(scene);
stage.setResizable(false);
stage.setScene(scene);
stage.setResizable(false);
stage.show();
stage.show();
App.stage = stage;
App.scene = scene;
App.stage = stage;
App.scene = scene;
App.root = root;
App.width = (int)stage.getWidth();
App.height = (int)stage.getHeight();
App.stack = new Stack<>();
App.isQuitting = false;
App.width = (int) stage.getWidth();
App.height = (int) stage.getHeight();
AppSettings.applySettings();
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
App.isQuitting = false;
ViewStack.push(new MainView());
}
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).postEvent();
public static void startQuit() {
if (isQuitting) {
return;
}
final AppSettings settings = new AppSettings();
settings.applySettings();
ViewStack.push(new QuitView());
isQuitting = true;
}
activate(new MainLayer());
}
public static void stopQuit() {
ViewStack.pop();
isQuitting = false;
}
public static void activate(Layer layer) {
Platform.runLater(
() -> {
popAll();
push(layer);
});
}
public static void quit() {
ViewStack.cleanup();
stage.close();
}
public static void push(Layer layer) {
Platform.runLater(
() -> {
root.getChildren().addLast(layer.getLayer());
stack.push(layer);
});
}
public static void reload() {
stage.setTitle(AppContext.getString("app-title"));
ViewStack.reload();
}
public static void pop() {
Platform.runLater(
() -> {
root.getChildren().removeLast();
stack.pop();
public static void setFullscreen(boolean fullscreen) {
stage.setFullScreen(fullscreen);
isQuitting = false;
});
}
width = (int) stage.getWidth();
height = (int) stage.getHeight();
public static void popAll() {
Platform.runLater(
() -> {
final int childrenCount = root.getChildren().size();
reload();
}
for (int i = 0; i < childrenCount; i++) {
try {
root.getChildren().removeLast();
} catch (Exception e) {
IO.println(e); // TODO: Use logger
}
}
public static void setStyle(String theme, String layoutSize) {
final int stylesCount = scene.getStylesheets().size();
stack.removeAllElements();
});
}
for (int i = 0; i < stylesCount; i++) {
scene.getStylesheets().removeLast();
}
public static void quitPopup() {
Platform.runLater(
() -> {
push(new QuitPopup());
isQuitting = true;
});
}
scene.getStylesheets().add(ResourceManager.<CssAsset>get("general.css").getUrl());
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
public static void quit() {
new EventFlow().addPostEvent(new AudioEvents.StopAudioManager()).postEvent();
stage.close();
}
reload();
}
public static void reloadAll() {
stage.setTitle(AppContext.getString("appTitle"));
public static int getWidth() {
return width;
}
for (final Layer layer : stack) {
layer.reload();
}
}
public static void setFullscreen(boolean fullscreen) {
stage.setFullScreen(fullscreen);
width = (int) stage.getWidth();
height = (int) stage.getHeight();
reloadAll();
}
public static void setStyle(String theme, String layoutSize) {
final int stylesCount = scene.getStylesheets().size();
for (int i = 0; i < stylesCount; i++) {
scene.getStylesheets().removeLast();
}
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
reloadAll();
}
public static int getWidth() {
return width;
}
public static int getHeight() {
return height;
}
}
public static int getHeight() {
return height;
}
}

View File

@@ -14,8 +14,8 @@ public class GameInformation {
public static int maxDepth(Type type) {
return switch (type) {
case TICTACTOE -> 5;
case REVERSI -> 0; // Todo
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.
};
}
}
@@ -23,7 +23,7 @@ public class GameInformation {
public static class Player {
public String name = "";
public boolean isHuman = true;
public int computerDifficulty = 0;
public int computerDifficulty = 1;
public int computerThinkTime = 1;
}

View File

@@ -1,9 +1,11 @@
package org.toop.app;
import org.toop.app.game.ReversiGame;
import org.toop.app.game.TicTacToeGame;
import org.toop.app.view.ViewStack;
import org.toop.app.view.views.ChallengeView;
import org.toop.app.view.views.ErrorView;
import org.toop.app.view.views.OnlineView;
import org.toop.app.view.views.SendChallengeView;
import org.toop.app.view.views.ServerView;
import org.toop.framework.eventbus.EventFlow;
@@ -11,6 +13,7 @@ import org.toop.framework.networking.events.NetworkEvents;
import org.toop.local.AppContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -93,6 +96,10 @@ public final class Server {
}
private void sendChallenge(String opponent) {
if (!isPolling) {
return;
}
ViewStack.push(new SendChallengeView(this, opponent, (playerInformation, gameType) -> {
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType))
.listen(NetworkEvents.GameMatchResponse.class, e -> {
@@ -108,13 +115,20 @@ public final class Server {
information.players[0].name = user;
information.players[1].name = opponent;
new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame);
switch (type) {
case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
}
}
}).postEvent();
}));
}
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
if (!isPolling) {
return;
}
String challengerName = response.challengerName();
challengerName = challengerName.substring(challengerName.indexOf("\"") + 1);
challengerName = challengerName.substring(0, challengerName.indexOf("\""));
@@ -145,23 +159,25 @@ public final class Server {
information.players[1].name = e.opponent();
switch (type) {
case TICTACTOE:
new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame);
break;
case REVERSI:
break;
case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
}
}
});
}));
}
private void sendMessage(String message) {
new EventFlow().addPostEvent(new NetworkEvents.SendMessage(clientId, message)).postEvent();
}
private void disconnect() {
// Todo
new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent();
ViewStack.push(new OnlineView());
}
private void forfeitGame() {
// Todo
new EventFlow().addPostEvent(new NetworkEvents.SendForfeit(clientId)).postEvent();
}
private void exitGame() {
@@ -182,6 +198,12 @@ public final class Server {
public List<String> getGamesList() {
final List<String> list = new ArrayList<String>();
list.add("tic-tac-toe"); // Todo: get games list from server and check if the game is supported
list.add("reversi");
new EventFlow().addPostEvent(new NetworkEvents.SendGetGamelist(clientId))
.listen(NetworkEvents.GamelistResponse.class, e -> {
System.out.println(Arrays.toString(e.gamelist()));
}).postEvent();
return list;
}

View File

@@ -9,6 +9,10 @@ import java.util.function.Consumer;
public abstract class GameCanvas {
protected record Cell(float x, float y, float width, float height) {
public boolean isInside(double x, double y) {
return x >= this.x && x <= this.x + width &&
y >= this.y && y <= this.y + height;
}
}
protected final Canvas canvas;
@@ -19,18 +23,15 @@ public abstract class GameCanvas {
protected final int width;
protected final int height;
protected final int rows;
protected final int columns;
protected final int rowSize;
protected final int columnSize;
protected final int gapSize;
protected final boolean edges;
protected final Cell[] cells;
protected GameCanvas(Color color, int width, int height, int rows, int columns, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
width += gapSize * 2;
height += gapSize * 2;
protected GameCanvas(Color color, int width, int height, int rowSize, int columnSize, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
canvas = new Canvas(width, height);
graphics = canvas.getGraphicsContext2D();
@@ -39,23 +40,23 @@ public abstract class GameCanvas {
this.width = width;
this.height = height;
this.rows = rows;
this.columns = columns;
this.rowSize = rowSize;
this.columnSize = columnSize;
this.gapSize = gapSize;
this.edges = edges;
cells = new Cell[rows * columns];
cells = new Cell[rowSize * columnSize];
final float cellWidth = ((float) width - gapSize * rows) / rows;
final float cellHeight = ((float) height - gapSize * columns) / columns;
final float cellWidth = ((float)width - gapSize * rowSize - gapSize) / rowSize;
final float cellHeight = ((float)height - gapSize * columnSize - gapSize) / columnSize;
for (int y = 0; y < columns; y++) {
final float startY = gapSize + y * cellHeight + y * gapSize;
for (int y = 0; y < columnSize; y++) {
final float startY = y * cellHeight + y * gapSize + gapSize;
for (int x = 0; x < rows; x++) {
final float startX = gapSize + x * cellWidth + x * gapSize;
cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
for (int x = 0; x < rowSize; x++) {
final float startX = x * cellWidth + x * gapSize + gapSize;
cells[x + y * rowSize] = new Cell(startX, startY, cellWidth, cellHeight);
}
}
@@ -64,11 +65,15 @@ public abstract class GameCanvas {
return;
}
final int column = (int)((event.getX() / this.width) * rows);
final int row = (int)((event.getY() / this.height) * columns);
final int column = (int)((event.getX() / this.width) * rowSize);
final int row = (int)((event.getY() / this.height) * columnSize);
event.consume();
onCellClicked.accept(column + row * rows);
final Cell cell = cells[column + row * rowSize];
if (cell.isInside(event.getX(), event.getY())) {
event.consume();
onCellClicked.accept(column + row * rowSize);
}
});
render();
@@ -81,20 +86,22 @@ public abstract class GameCanvas {
public void render() {
graphics.setFill(color);
for (int x = 1; x < rows; x++) {
graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height + gapSize);
for (int x = 0; x < rowSize - 1; x++) {
final float start = cells[x].x + cells[x].width;
graphics.fillRect(start, gapSize, gapSize, height - gapSize * 2);
}
for (int y = 1; y < columns; y++) {
graphics.fillRect(0, cells[y * rows].y() - gapSize, width + gapSize, gapSize);
for (int y = 0; y < columnSize - 1; y++) {
final float start = cells[y * rowSize].y + cells[y * rowSize].height;
graphics.fillRect(gapSize, start, width - gapSize * 2, gapSize);
}
if (edges) {
graphics.fillRect(0, 0, gapSize, height + gapSize);
graphics.fillRect(0, 0, width + gapSize, gapSize);
graphics.fillRect(0, 0, width, gapSize);
graphics.fillRect(0, 0, gapSize, height);
graphics.fillRect(width + gapSize, 0, gapSize, height + gapSize * 2);
graphics.fillRect(0, height + gapSize, width + gapSize * 2, gapSize);
graphics.fillRect(width - gapSize, 0, gapSize, height);
graphics.fillRect(0, height - gapSize, width, gapSize);
}
}

View File

@@ -0,0 +1,34 @@
package org.toop.app.canvas;
import javafx.scene.paint.Color;
import java.util.function.Consumer;
public final class ReversiCanvas extends GameCanvas {
public ReversiCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
super(color, width, height, 8, 8, 10, true, onCellClicked);
drawStartingDots();
}
public void drawDot(Color color, int cell) {
final float x = cells[cell].x() + gapSize;
final float y = cells[cell].y() + gapSize;
final float width = cells[cell].width() - gapSize * 2;
final float height = cells[cell].height() - gapSize * 2;
graphics.setFill(color);
graphics.fillOval(x, y, width, height);
}
public void drawStartingDots() {
drawDot(Color.BLACK, 28);
drawDot(Color.WHITE, 36);
drawDot(Color.BLACK, 35);
drawDot(Color.WHITE, 27);
}
public void drawLegalPosition(int cell) {
drawDot(new Color(1.0f, 0.0f, 0.0f, 0.5f), cell);
}
}

View File

@@ -0,0 +1,257 @@
package org.toop.app.game;
import org.toop.app.App;
import org.toop.app.GameInformation;
import org.toop.app.canvas.ReversiCanvas;
import org.toop.app.view.ViewStack;
import org.toop.app.view.views.GameView;
import org.toop.app.view.views.LocalMultiplayerView;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents;
import org.toop.game.Game;
import org.toop.game.reversi.Reversi;
import org.toop.game.reversi.ReversiAI;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public final class ReversiGame {
private final GameInformation information;
private final int myTurn;
private final BlockingQueue<Game.Move> moveQueue;
private final Reversi game;
private final ReversiAI ai;
private final GameView view;
private final ReversiCanvas canvas;
private final AtomicBoolean isRunning;
public ReversiGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
this.information = information;
this.myTurn = myTurn;
moveQueue = new LinkedBlockingQueue<Game.Move>();
game = new Reversi();
ai = new ReversiAI();
isRunning = new AtomicBoolean(true);
if (onForfeit == null || onExit == null) {
view = new GameView(null, () -> {
isRunning.set(false);
ViewStack.push(new LocalMultiplayerView(information));
}, null);
} else {
view = new GameView(onForfeit, () -> {
isRunning.set(false);
onExit.run();
}, onMessage);
}
canvas = new ReversiCanvas(Color.GRAY,
(App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,
(cell) -> {
if (onForfeit == null || onExit == null) {
if (information.players[game.getCurrentTurn()].isHuman) {
final char value = game.getCurrentTurn() == 0? 'B' : 'W';
try {
moveQueue.put(new Game.Move(cell, value));
} catch (InterruptedException _) {}
}
} else {
if (information.players[0].isHuman) {
final char value = myTurn == 0? 'B' : 'W';
try {
moveQueue.put(new Game.Move(cell, value));
} catch (InterruptedException _) {}
}
}
});
view.add(Pos.CENTER, canvas.getCanvas());
ViewStack.push(view);
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)
.listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
setGameLabels(myTurn == 0);
}
updateCanvas();
}
public ReversiGame(GameInformation information) {
this(information, 0, null, null, null);
}
private void localGameThread() {
while (isRunning.get()) {
final int currentTurn = game.getCurrentTurn();
final char currentValue = currentTurn == 0? 'B' : 'W';
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
view.nextPlayer(information.players[currentTurn].isHuman,
information.players[currentTurn].name,
String.valueOf(currentValue),
information.players[nextTurn].name);
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);
updateCanvas();
if (state != Game.State.NORMAL) {
if (state == Game.State.WIN) {
view.gameOver(true, information.players[currentTurn].name);
} else if (state == Game.State.DRAW) {
view.gameOver(false, "");
}
isRunning.set(false);
}
}
}
private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
if (!isRunning.get()) {
return;
}
char playerChar;
if (response.player().equalsIgnoreCase(information.players[0].name)) {
playerChar = myTurn == 0? 'B' : 'W';
} else {
playerChar = myTurn == 0? 'W' : 'B';
}
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)) {
view.gameOver(true, information.players[0].name);
} else {
view.gameOver(false, information.players[1].name);
}
} else if (state == Game.State.DRAW) {
view.gameOver(false, "");
}
}
updateCanvas();
setGameLabels(game.getCurrentTurn() == myTurn);
}
private 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 = ai.findBestMove(game, information.players[0].computerDifficulty);
assert move != null;
position = move.position();
}
new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position))
.postEvent();
}
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
if (!isRunning.get()) {
return;
}
view.updateChat(msg.message());
}
private void updateCanvas() {
// Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve.
canvas.clear();
canvas.render();
for (int i = 0; i < game.board.length; i++) {
if (game.board[i] == 'B') {
canvas.drawDot(Color.BLACK, i);
} else if (game.board[i] == 'W') {
canvas.drawDot(Color.WHITE, i);
}
}
final Game.Move[] legalMoves = game.getLegalMoves();
for (final Game.Move legalMove : legalMoves) {
canvas.drawLegalPosition(legalMove.position());
}
}
private void setGameLabels(boolean isMe) {
final int currentTurn = game.getCurrentTurn();
final char currentValue = currentTurn == 0? 'B' : 'W';
view.nextPlayer(isMe,
information.players[isMe? 0 : 1].name,
String.valueOf(currentValue),
information.players[isMe? 1 : 0].name);
}
}

View File

@@ -17,6 +17,8 @@ import javafx.scene.paint.Color;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
public final class TicTacToeGame {
private final GameInformation information;
@@ -30,7 +32,9 @@ public final class TicTacToeGame {
private final GameView view;
private final TicTacToeCanvas canvas;
public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit) {
private AtomicBoolean isRunning;
public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
this.information = information;
this.myTurn = myTurn;
@@ -39,12 +43,18 @@ public final class TicTacToeGame {
game = new TicTacToe();
ai = new TicTacToeAI();
isRunning = new AtomicBoolean(true);
if (onForfeit == null || onExit == null) {
view = new GameView(null, () -> {
isRunning.set(false);
ViewStack.push(new LocalMultiplayerView(information));
});
}, null);
} else {
view = new GameView(onForfeit, onExit);
view = new GameView(onForfeit, () -> {
isRunning.set(false);
onExit.run();
}, onMessage);
}
canvas = new TicTacToeCanvas(Color.GRAY,
@@ -84,10 +94,12 @@ public final class TicTacToeGame {
}
}
private void localGameThread() {
boolean isRunning = true;
public TicTacToeGame(GameInformation information) {
this(information, 0, null, null, null);
}
while (isRunning) {
private void localGameThread() {
while (isRunning.get()) {
final int currentTurn = game.getCurrentTurn();
final char currentValue = currentTurn == 0? 'X' : 'O';
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
@@ -146,12 +158,16 @@ public final class TicTacToeGame {
view.gameOver(false, "");
}
isRunning = false;
isRunning.set(false);
}
}
}
private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
if (!isRunning.get()) {
return;
}
char playerChar;
if (response.player().equalsIgnoreCase(information.players[0].name)) {
@@ -185,6 +201,10 @@ public final class TicTacToeGame {
}
private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
if (!isRunning.get()) {
return;
}
moveQueue.clear();
int position = -1;
@@ -205,7 +225,11 @@ public final class TicTacToeGame {
}
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
view.updateChat("anon", msg.message());
if (!isRunning.get()) {
return;
}
view.updateChat(msg.message());
}
private void setGameLabels(boolean isMe) {

View File

@@ -6,8 +6,12 @@ import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.text.Text;
import java.util.function.Consumer;
public final class GameView extends View {
private static class GameOverView extends View {
private final boolean iWon;
@@ -65,7 +69,10 @@ public final class GameView extends View {
private final Text nextPlayerHeader;
public GameView(Runnable onForfeit, Runnable onExit) {
private final ListView<Text> chatListView;
private final TextField chatInput;
public GameView(Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
assert onExit != null;
super(true, "bg-primary");
@@ -78,6 +85,19 @@ public final class GameView extends View {
forfeitButton = null;
}
if (onMessage != null) {
chatListView = new ListView<Text>();
chatInput = input();
chatInput.setOnAction(_ -> {
onMessage.accept(chatInput.getText());
chatInput.setText("");
});
} else {
chatListView = null;
chatInput = null;
}
exitButton = button();
exitButton.setText(AppContext.getString("exit"));
exitButton.setOnAction(_ -> onExit.run());
@@ -110,6 +130,15 @@ public final class GameView extends View {
exitButton
)
);
if (chatListView != null) {
add(Pos.BOTTOM_RIGHT,
fit(vboxFill(
chatListView,
chatInput
)
));
}
}
public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) {
@@ -125,8 +154,15 @@ public final class GameView extends View {
}
}
public void updateChat(String player, String message) {
// Todo
public void updateChat(String message) {
if (chatListView == null) {
return;
}
final Text messageText = text();
messageText.setText(message);
chatListView.getItems().add(messageText);
}
public void gameOver(boolean iWon, String winner) {

View File

@@ -1,6 +1,7 @@
package org.toop.app.view.views;
import org.toop.app.GameInformation;
import org.toop.app.game.ReversiGame;
import org.toop.app.game.TicTacToeGame;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
@@ -42,8 +43,8 @@ public final class LocalMultiplayerView extends View {
}
switch (information.type) {
case TICTACTOE: new TicTacToeGame(information, 0, null, null);
case REVERSI: break;
case TICTACTOE: new TicTacToeGame(information); break;
case REVERSI: new ReversiGame(information); break;
}
});

View File

@@ -21,7 +21,7 @@ public final class LocalView extends View {
final Button reversiButton = button();
reversiButton.setText(AppContext.getString("reversi"));
reversiButton.setOnAction(_ -> {});
reversiButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.REVERSI)); });
add(Pos.CENTER,
fit(vboxFill(

View File

@@ -3,6 +3,7 @@ package org.toop.app.view.views;
import org.toop.app.App;
import org.toop.app.view.View;
import org.toop.app.view.ViewStack;
import org.toop.framework.audio.VolumeControl;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext;
@@ -143,7 +144,7 @@ public final class OptionsView extends View {
masterVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setVolume(newValue.intValue());
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue())).asyncPostEvent();
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MASTERVOLUME)).asyncPostEvent();
});
}
@@ -158,7 +159,7 @@ public final class OptionsView extends View {
effectsVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setFxVolume(newValue.intValue());
new EventFlow().addPostEvent(new AudioEvents.ChangeFxVolume(newValue.doubleValue())).asyncPostEvent();
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.FX)).asyncPostEvent();
});
}
@@ -173,7 +174,7 @@ public final class OptionsView extends View {
musicVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
AppSettings.getSettings().setMusicVolume(newValue.intValue());
new EventFlow().addPostEvent(new AudioEvents.ChangeMusicVolume(newValue.doubleValue())).asyncPostEvent();
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MUSIC)).asyncPostEvent();
});
}

View File

@@ -1,7 +1,5 @@
package org.toop.local;
import java.io.File;
import java.util.Locale;
import org.toop.app.App;
import org.toop.framework.audio.VolumeControl;
import org.toop.framework.audio.events.AudioEvents;
@@ -11,57 +9,59 @@ import org.toop.framework.resource.ResourceMeta;
import org.toop.framework.resource.resources.SettingsAsset;
import org.toop.framework.settings.Settings;
import java.io.File;
import java.util.Locale;
public class AppSettings {
private static SettingsAsset settingsAsset;
private SettingsAsset settingsAsset;
public static void applySettings() {
settingsAsset = getPath();
if (!settingsAsset.isLoaded()) {
settingsAsset.load();
}
public void applySettings() {
this.settingsAsset = getPath();
if (!this.settingsAsset.isLoaded()) {
this.settingsAsset.load();
}
Settings settingsData = settingsAsset.getContent();
Settings settingsData = this.settingsAsset.getContent();
AppContext.setLocale(Locale.of(settingsData.locale));
App.setFullscreen(settingsData.fullScreen);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume, VolumeControl.MASTERVOLUME))
.asyncPostEvent();
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.fxVolume, VolumeControl.FX))
.asyncPostEvent();
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.musicVolume, VolumeControl.MUSIC))
.asyncPostEvent();
App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize());
}
AppContext.setLocale(Locale.of(settingsData.locale));
App.setFullscreen(settingsData.fullScreen);
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume, VolumeControl.MASTERVOLUME))
.asyncPostEvent();
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.fxVolume, VolumeControl.FX))
.asyncPostEvent();
new EventFlow()
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.musicVolume, VolumeControl.MUSIC))
.asyncPostEvent();
App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize());
}
public static SettingsAsset getPath() {
if (settingsAsset == null) {
String os = System.getProperty("os.name").toLowerCase();
String basePath;
public SettingsAsset getPath() {
if (this.settingsAsset == null) {
String os = System.getProperty("os.name").toLowerCase();
String basePath;
if (os.contains("win")) {
basePath = System.getenv("APPDATA");
if (basePath == null) {
basePath = System.getProperty("user.home");
}
} else if (os.contains("mac")) {
basePath = System.getProperty("user.home") + "/Library/Application Support";
} else {
basePath = System.getProperty("user.home") + "/.config";
}
if (os.contains("win")) {
basePath = System.getenv("APPDATA");
if (basePath == null) {
basePath = System.getProperty("user.home");
}
} else if (os.contains("mac")) {
basePath = System.getProperty("user.home") + "/Library/Application Support";
} else {
basePath = System.getProperty("user.home") + "/.config";
}
File settingsFile =
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
// this.settingsAsset = new SettingsAsset(settingsFile);
ResourceManager.addAsset(new ResourceMeta<>("settings.json", new SettingsAsset(settingsFile)));
}
return ResourceManager.get("settings.json");
}
File settingsFile =
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
return new SettingsAsset(settingsFile);
// this.settingsAsset = new SettingsAsset(settingsFile); // TODO
// ResourceManager.addAsset(new ResourceMeta<>("settings.json", new SettingsAsset(settingsFile))); // TODO
}
return this.settingsAsset;
// return ResourceManager.get("settings.json"); // TODO
}
}
public static SettingsAsset getSettings() {
return settingsAsset;
}
}