add: some more languages

This commit is contained in:
ramollia
2025-10-05 16:59:37 +02:00
parent 043b789da1
commit 2362333d46
29 changed files with 656 additions and 188 deletions

View File

@@ -5,6 +5,8 @@ import org.toop.app.layer.layers.MainLayer;
import org.toop.app.layer.layers.QuitLayer;
import org.toop.framework.asset.ResourceManager;
import org.toop.framework.asset.resources.CssAsset;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import org.toop.local.AppContext;
import javafx.application.Application;
@@ -61,6 +63,7 @@ public final class App extends Application {
App.isQuitting = false;
new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent();
activate(new MainLayer());
}

View File

@@ -0,0 +1,5 @@
package org.toop.app;
public record GameInformation(String[] playerName, boolean[] isPlayerHuman, int[] computerDifficulty,
boolean isConnectionLocal, String serverIP, String serverPort) {
}

View File

@@ -3,31 +3,49 @@ package org.toop.app.canvas;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
import java.util.function.Consumer;
public abstract class GameCanvas {
protected record Cell(float x, float y, float width, float height) {}
protected final int width;
protected final int height;
protected record Cell(float x, float y, float width, float height) {
}
protected final Canvas canvas;
protected final GraphicsContext graphics;
protected final Color color;
protected int width;
protected int height;
protected final int rows;
protected final int columns;
protected final int gapSize;
protected final boolean edges;
protected final Cell[] cells;
protected GameCanvas(int width, int height, int rows, int columns, int gapSize) {
final Canvas canvas = new Canvas(width, height);
final GraphicsContext graphics = canvas.getGraphicsContext2D();
protected GameCanvas(Color color, int width, int height, int rows, int columns, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
canvas = new Canvas(width, height);
graphics = canvas.getGraphicsContext2D();
final Cell[] cells = new Cell[rows * columns];
this.color = color;
final float cellWidth = ((float)width - (rows - 1) * gapSize) / rows;
final float cellHeight = ((float)height - (columns - 1) * gapSize) / columns;
this.width = width;
this.height = height;
this.rows = rows;
this.columns = columns;
this.gapSize = gapSize;
this.edges = edges;
cells = new Cell[rows * columns];
final float cellWidth = ((float) width - (rows - 1) * gapSize) / rows;
final float cellHeight = ((float) height - (columns - 1) * gapSize) / columns;
for (int y = 0; y < columns; y++) {
final float startY = y * cellHeight + y * gapSize;
@@ -39,39 +57,67 @@ public abstract class GameCanvas {
}
canvas.setOnMouseClicked(event -> {
final MouseButton button = event.getButton();
if (button != MouseButton.PRIMARY && button != MouseButton.SECONDARY) {
if (event.getButton() != MouseButton.PRIMARY) {
return;
}
final int column = (int)((event.getX() / width) * rows);
final int row = (int)((event.getY() / height) * columns);
final int column = (int) ((event.getX() / width) * rows);
final int row = (int) ((event.getY() / height) * columns);
event.consume();
onCellClicked(row * rows + column, button == MouseButton.PRIMARY);
onCellClicked.accept(row * rows + column);
});
render();
}
public void clear() {
graphics.clearRect(0, 0, width, height);
}
public void render() {
graphics.setFill(color);
for (int x = 1; x < rows; x++) {
graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height);
}
for (int y = 1; y < columns; y++) {
graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize);
}
if (edges) {
graphics.fillRect(-gapSize, 0, gapSize, height);
graphics.fillRect(0, -gapSize, width, gapSize);
graphics.fillRect(width - gapSize, 0, gapSize, height);
graphics.fillRect(0, height - gapSize, width, gapSize);
}
}
public void draw(Color color, int cell) {
final float x = cells[cell].x() + gapSize;
final float y = cells[cell].y() + gapSize;
final float width = cells[cell].width() - gapSize * 2;
final float height = cells[cell].height() - gapSize * 2;
graphics.setFill(color);
graphics.fillRect(x, y, width, height);
}
public void resize(int width, int height) {
canvas.setWidth(width);
canvas.setHeight(height);
this.width = width;
this.height = height;
this.canvas = canvas;
this.graphics = graphics;
this.rows = rows;
this.columns = columns;
this.gapSize = gapSize;
this.cells = cells;
clear();
render();
}
protected void clearCell(int cell) {
assert cell >= 0 && cell < cells.length;
graphics.clearRect(cells[cell].x(), cells[cell].y(), cells[cell].width(), cells[cell].height());
public Canvas getCanvas() {
return canvas;
}
protected abstract void onCellClicked(int cell, boolean primary);
public Canvas getCanvas() { return canvas; }
}

View File

@@ -1,30 +1,16 @@
package org.toop.app.canvas;
import org.toop.game.Game;
import org.toop.game.tictactoe.TicTacToe;
import javafx.scene.paint.Color;
import java.util.function.Consumer;
public class TicTacToeCanvas extends GameCanvas {
private final TicTacToe game;
public TicTacToeCanvas(int width, int height) {
super(width, height, 3, 3, 10);
game = new TicTacToe();
graphics.setFill(Color.CYAN);
for (int x = 1; x < rows; x++) {
graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height);
}
for (int y = 1; y < columns; y++) {
graphics.fillRect(0, cells[y * rows].y() - gapSize, width, gapSize);
}
public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
super(color, width, height, 3, 3, 10, false, onCellClicked);
}
public void placeX(int cell) {
graphics.setStroke(Color.ORANGERED);
public void drawX(Color color, int cell) {
graphics.setStroke(color);
graphics.setLineWidth(gapSize);
final float x = cells[cell].x() + gapSize;
@@ -37,8 +23,8 @@ public class TicTacToeCanvas extends GameCanvas {
graphics.strokeLine(x + width, y, x, y + height);
}
public void placeO(int cell) {
graphics.setStroke(Color.DEEPSKYBLUE);
public void drawO(Color color, int cell) {
graphics.setStroke(color);
graphics.setLineWidth(gapSize);
final float x = cells[cell].x() + gapSize;
@@ -49,33 +35,4 @@ public class TicTacToeCanvas extends GameCanvas {
graphics.strokeOval(x, y, width, height);
}
@Override
protected void onCellClicked(int cell, boolean primary) {
for (final Game.Move move : game.getLegalMoves()) {
if (move.position() == cell) {
if (move.value() == 'X') {
placeX(cell);
} else {
placeO(cell);
}
final Game.State state = game.play(move);
if (state == Game.State.WIN) {
for (int i = 0; i < game.board.length; i++) {
if (game.board[i] != move.value()) {
clearCell(i);
}
}
graphics.setFill(Color.GREEN);
graphics.fillRect(cells[4].x(), cells[4].y(), cells[4].width(), cells[4].height());
} else if (state == Game.State.DRAW) {
graphics.setFill(Color.DARKORANGE);
graphics.fillRect(cells[4].x(), cells[4].y(), cells[4].width(), cells[4].height());
}
}
}
}
}

View File

@@ -4,5 +4,5 @@ import org.toop.framework.eventbus.events.EventWithoutSnowflake;
import org.toop.framework.eventbus.events.EventsBase;
public class AppEvents extends EventsBase {
public record OnNodeHover() implements EventWithoutSnowflake {}
public record OnNodeClick() implements EventWithoutSnowflake {}
}

View File

@@ -1,7 +1,8 @@
package org.toop.app.layer;
import org.toop.app.events.AppEvents;
import org.toop.framework.eventbus.GlobalEventBus;
import org.toop.framework.audio.events.AudioEvents;
import org.toop.framework.eventbus.EventFlow;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@@ -42,11 +43,8 @@ public abstract class Container {
final Label element = new Label(x);
element.getStyleClass().add(cssClass);
element.setOnMouseEntered(_ -> {
GlobalEventBus.post(new AppEvents.OnNodeHover());
});
element.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
runnable.run();
});
@@ -64,11 +62,8 @@ public abstract class Container {
final BooleanProperty checked = new SimpleBooleanProperty(toggled);
element.setOnMouseEntered(_ -> {
GlobalEventBus.post(new AppEvents.OnNodeHover());
});
element.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
checked.set(!checked.get());
if (checked.get()) {
@@ -99,8 +94,8 @@ public abstract class Container {
element.setSnapToTicks(true);
element.setShowTickLabels(true);
element.setOnMouseEntered(_ -> {
GlobalEventBus.post(new AppEvents.OnNodeHover());
element.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
element.valueProperty().addListener((_, _, newValue) -> {
@@ -119,8 +114,8 @@ public abstract class Container {
final TextField element = new TextField(input);
element.getStyleClass().add(cssClass);
element.setOnMouseEntered(_ -> {
GlobalEventBus.post(new AppEvents.OnNodeHover());
element.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
element.textProperty().addListener((_, _, newValue) -> {
@@ -139,8 +134,8 @@ public abstract class Container {
final ChoiceBox<T> element = new ChoiceBox<>();
element.getStyleClass().add(cssClass);
element.setOnMouseEntered(_ -> {
GlobalEventBus.post(new AppEvents.OnNodeHover());
element.setOnMouseClicked(_ -> {
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
});
element.valueProperty().addListener((_, _, newValue) -> {

View File

@@ -1,6 +1,7 @@
package org.toop.app.layer;
import org.toop.app.App;
import org.toop.app.canvas.GameCanvas;
import org.toop.framework.asset.ResourceManager;
import org.toop.framework.asset.resources.CssAsset;
@@ -47,6 +48,18 @@ public abstract class Layer {
layer.getChildren().addLast(container.getContainer());
}
protected void addGameCanvas(GameCanvas canvas, Pos position, int xOffset, int yOffset) {
StackPane.setAlignment(canvas.getCanvas(), position);
final double widthUnit = App.getWidth() / 100.0;
final double heightUnit = App.getHeight() / 100.0;
canvas.getCanvas().setTranslateX(xOffset * widthUnit);
canvas.getCanvas().setTranslateY(yOffset * heightUnit);
layer.getChildren().addLast(canvas.getCanvas());
}
protected void pop() {
if (layer.getChildren().size() <= 1) {
return;

View File

@@ -1,29 +0,0 @@
package org.toop.app.layer.layers;
import org.toop.app.App;
import org.toop.app.layer.Container;
import org.toop.app.layer.Layer;
import org.toop.app.layer.containers.VerticalContainer;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
public class GameLayer extends Layer {
public GameLayer() {
super("game.css");
reload();
}
@Override
public void reload() {
App.popAll();
final Container controlContainer = new VerticalContainer(5);
controlContainer.addButton(AppContext.getString("back"), () -> {
App.activate(new MainLayer());
});
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0);
}
}

View File

@@ -23,11 +23,11 @@ public final class MainLayer extends Layer {
final Container gamesContainer = new VerticalContainer(5);
gamesContainer.addButton(AppContext.getString("tictactoe"), () -> {
App.activate(new MultiplayerLayer<TicTacToe>());
App.activate(new MultiplayerLayer());
});
gamesContainer.addButton(AppContext.getString("othello"), () -> {
App.activate(new MultiplayerLayer<Othello>());
App.activate(new MultiplayerLayer());
});
final Container controlContainer = new VerticalContainer(5);

View File

@@ -1,16 +1,17 @@
package org.toop.app.layer.layers;
import org.toop.app.App;
import org.toop.app.GameInformation;
import org.toop.app.layer.Container;
import org.toop.app.layer.Layer;
import org.toop.app.layer.containers.HorizontalContainer;
import org.toop.app.layer.containers.VerticalContainer;
import org.toop.game.TurnBasedGame;
import org.toop.app.layer.layers.game.TicTacToeLayer;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
public final class MultiplayerLayer<T extends TurnBasedGame> extends Layer {
public final class MultiplayerLayer extends Layer {
private boolean isConnectionLocal = true;
private boolean isPlayer1Human = true;
@@ -54,13 +55,13 @@ public final class MultiplayerLayer<T extends TurnBasedGame> extends Layer {
playersContainer.addContainer(player2Container, true);
if (isConnectionLocal) {
mainContainer.addButton(AppContext.getString("start"), () -> {
});
} else {
mainContainer.addButton(AppContext.getString("connect"), () -> {
});
}
mainContainer.addButton(isConnectionLocal? AppContext.getString("start") : AppContext.getString("connect"), () -> {
App.activate(new TicTacToeLayer(new GameInformation(
new String[] { player1Name, player2Name },
new boolean[] { isPlayer1Human, isPlayer2Human },
new int[] { computer1Difficulty, computer2Difficulty },
isConnectionLocal, serverIP, serverPort)));
});
player1Container.addToggle(AppContext.getString("human"), AppContext.getString("computer"), !isPlayer1Human, (computer) -> {
isPlayer1Human = !computer;

View File

@@ -14,7 +14,7 @@ import javafx.scene.control.ChoiceBox;
import java.util.Locale;
public final class OptionsLayer extends Layer {
private static int currentVolume = 25;
private static int currentVolume = 0;
private static boolean isWindowed = true;
OptionsLayer() {
@@ -84,7 +84,7 @@ public final class OptionsLayer extends Layer {
private void addVolumeSlider(Container container) {
container.addSlider(100, currentVolume, (volume) -> {
currentVolume = volume;
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue() / 100.0)).asyncPostEvent();
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(volume.doubleValue())).asyncPostEvent();
});
}

View File

@@ -0,0 +1,128 @@
package org.toop.app.layer.layers.game;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import org.toop.app.App;
import org.toop.app.GameInformation;
import org.toop.app.canvas.TicTacToeCanvas;
import org.toop.app.layer.Container;
import org.toop.app.layer.Layer;
import org.toop.app.layer.containers.VerticalContainer;
import org.toop.app.layer.layers.MainLayer;
import org.toop.game.Game;
import org.toop.game.tictactoe.TicTacToe;
import org.toop.game.tictactoe.TicTacToeAI;
import org.toop.local.AppContext;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public final class TicTacToeLayer extends Layer {
private TicTacToeCanvas canvas;
private TicTacToe ticTacToe;
private TicTacToeAI ticTacToeAI;
private GameInformation information;
private final BlockingQueue<Game.Move> playerMoveQueue = new LinkedBlockingQueue<>();
public TicTacToeLayer(GameInformation information) {
super("game.css");
canvas = new TicTacToeCanvas(Color.WHITE, (App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> {
try {
playerMoveQueue.put(new Game.Move(cell, 'X'));
} catch (InterruptedException e) {
return;
}
});
ticTacToe = new TicTacToe();
ticTacToeAI = new TicTacToeAI();
this.information = information;
if (information.isConnectionLocal()) {
new Thread(this::localGameThread).start();
}
reload();
}
@Override
public void reload() {
popAll();
canvas.resize((App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75);
for (int i = 0; i < ticTacToe.board.length; i++) {
final char value = ticTacToe.board[i];
if (value == 'X') {
canvas.drawX(Color.RED, i);
} else if (value == 'O') {
canvas.drawO(Color.BLUE, i);
}
}
final Container controlContainer = new VerticalContainer(5);
if (information.isPlayerHuman()[0] || information.isConnectionLocal() && information.isPlayerHuman()[1]) {
controlContainer.addButton(AppContext.getString("hint"), () -> {
});
}
controlContainer.addButton(AppContext.getString("back"), () -> {
App.activate(new MainLayer());
});
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0);
addGameCanvas(canvas, Pos.CENTER, 0, 0);
}
private int compurterDifficultyToDepth(int maxDifficulty, int difficulty) {
return (int) (((float) maxDifficulty / difficulty) * 9);
}
private void localGameThread() {
boolean running = true;
while (running) {
final int currentPlayer = ticTacToe.getCurrentTurn();
System.out.println("test");
Game.Move move = null;
if (information.isPlayerHuman()[currentPlayer]) {
try {
move = playerMoveQueue.take();
} catch (InterruptedException exception) {
return;
}
} else {
move = ticTacToeAI.findBestMove(ticTacToe, compurterDifficultyToDepth(9, information.computerDifficulty()[currentPlayer]));
}
assert move != null;
final Game.State state = ticTacToe.play(move);
if (move.value() == 'X') {
canvas.drawX(Color.RED, move.position());
} else if (move.value() == 'O') {
canvas.drawO(Color.BLUE, move.position());
}
if (state != Game.State.NORMAL) {
if (state == Game.State.WIN) {
// Win logic
} else if (state == Game.State.DRAW) {
// Draw logic
}
running = false;
}
}
}
}