This commit is contained in:
ramollia
2025-10-02 19:59:14 +02:00
parent 6626157052
commit 3c699cde01
14 changed files with 284 additions and 199 deletions

View File

@@ -8,7 +8,7 @@ import org.toop.framework.networking.NetworkingClientManager;
import org.toop.framework.networking.NetworkingInitializationException; import org.toop.framework.networking.NetworkingInitializationException;
public final class Main { public final class Main {
static void main(String[] args) { public static void main(String[] args) {
initSystems(); initSystems();
App.run(args); App.run(args);
} }

View File

@@ -13,6 +13,8 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.scene.text.Text; import javafx.scene.text.Text;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.toop.framework.asset.AssetManager;
import org.toop.framework.asset.resources.CssAsset;
public final class App extends Application { public final class App extends Application {
private static Stage stage; private static Stage stage;
@@ -42,7 +44,7 @@ public final class App extends Application {
box.setMaxHeight(200); box.setMaxHeight(200);
pane = new StackPane(background, box); pane = new StackPane(background, box);
pane.getStylesheets().add(App.class.getResource("/style/quit.css").toExternalForm()); pane.getStylesheets().add(((CssAsset)AssetManager.get("quit.css")).getUrl());
} }
} }
@@ -55,7 +57,7 @@ public final class App extends Application {
final StackPane root = new StackPane(new MainMenu().getPane()); final StackPane root = new StackPane(new MainMenu().getPane());
final Scene scene = new Scene(root); final Scene scene = new Scene(root);
scene.getStylesheets().add(App.class.getResource("/style/app.css").toExternalForm()); scene.getStylesheets().add(((CssAsset)AssetManager.get("app.css")).getUrl());
stage.setTitle("pism"); stage.setTitle("pism");
stage.setMinWidth(1080); stage.setMinWidth(1080);

View File

@@ -1,22 +0,0 @@
package org.toop.app.menu;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.toop.app.GameType;
public class GameMenu extends Menu {
public GameMenu(GameType type) {
final Region background = createBackground();
ComboBox<String> selectedGame = new ComboBox<>();
TextField serverIpField = new TextField();
serverIpField.setPromptText("Enter here your server ip address");
VBox box = new VBox(selectedGame, serverIpField);
pane = new StackPane(background, box);
}
}

View File

@@ -0,0 +1,33 @@
package org.toop.app.menu;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.toop.app.GameType;
public class GameSelectMenu extends Menu {
public GameSelectMenu(GameType type) {
final Region background = createBackground();
final ComboBox<String> selectedGame = new ComboBox<>();
selectedGame.getItems().add(GameType.toName(GameType.TICTACTOE));
selectedGame.getItems().add(GameType.toName(GameType.REVERSI));
selectedGame.setValue(GameType.toName(type));
final ComboBox<String> selectedMode = new ComboBox<>();
selectedMode.getItems().add("Local");
selectedMode.getItems().add("Online");
selectedMode.setValue("Local");
final HBox selectedContainer = new HBox(10, selectedGame, selectedMode);
final TextField serverIpField = new TextField();
serverIpField.setPromptText("Enter here your server ip address");
VBox box = new VBox(selectedContainer, serverIpField);
pane = new StackPane(background, box);
}
}

View File

@@ -2,18 +2,19 @@ package org.toop.app.menu;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.GameType; import org.toop.app.GameType;
import org.toop.app.screen.TicTacToeScreen;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import org.toop.app.menu.game.TicTacToeMenu;
import org.toop.game.tictactoe.TicTacToe;
public final class MainMenu extends Menu { public final class MainMenu extends Menu {
public MainMenu() { public MainMenu() {
final Region background = createBackground(); final Region background = createBackground();
final Button tictactoe = createButton("Tic Tac Toe", () -> { App.activate(new GameMenu(GameType.TICTACTOE)); }); final Button tictactoe = createButton("Tic Tac Toe", () -> { App.activate(new TicTacToeMenu(new TicTacToe("player 1", true, "player 2", true))); });
final Button reversi = createButton("Reversi", () -> { App.activate(new GameMenu(GameType.REVERSI)); }); final Button reversi = createButton("Reversi", () -> { App.activate(new GameSelectMenu(GameType.REVERSI)); });
final VBox gamesBox = new VBox(10, tictactoe, reversi); final VBox gamesBox = new VBox(10, tictactoe, reversi);
gamesBox.setAlignment(Pos.TOP_LEFT); gamesBox.setAlignment(Pos.TOP_LEFT);
@@ -32,14 +33,5 @@ public final class MainMenu extends Menu {
controlBox.setTranslateX(25); controlBox.setTranslateX(25);
pane = new StackPane(background, gamesBox, controlBox); pane = new StackPane(background, gamesBox, controlBox);
tictactoe.setOnMouseEntered(_ -> {
final TicTacToeScreen screen = new TicTacToeScreen((int)pane.getHeight());
screen.simulate(1000);
push(screen.getCanvas());
});
tictactoe.setOnMouseExited(_ -> { pop(); });
} }
} }

View File

@@ -10,14 +10,6 @@ public abstract class Menu {
protected Pane pane; protected Pane pane;
public Pane getPane() { return pane; } public Pane getPane() { return pane; }
public void push(Node node) {
pane.getChildren().addLast(node);
}
public void pop() {
pane.getChildren().removeLast();
}
public Region createBackground(String css) { public Region createBackground(String css) {
final Region background = new Region(); final Region background = new Region();
background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); background.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE);

View File

@@ -0,0 +1,99 @@
package org.toop.app.menu.game;
import javafx.geometry.Pos;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import org.toop.app.App;
import org.toop.app.menu.MainMenu;
import org.toop.app.menu.Menu;
public abstract class GameMenu extends Menu {
protected final class Cell {
public float x;
public float y;
public float width;
public float height;
public Cell(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public boolean check(float x, float y) {
return x >= this.x && y >= this.y && x <= this.x + width && y <= this.y + height;
}
}
protected final int size;
protected final Canvas canvas;
protected final GraphicsContext graphics;
protected final int rows;
protected final int columns;
protected final int gapSize;
protected final Cell[] cells;
protected GameMenu(int rows, int columns, int gapSize) {
final int size = Math.min(App.getWidth(), App.getHeight()) / 5 * 4;
final Canvas canvas = new Canvas(size, size);
final GraphicsContext graphics = canvas.getGraphicsContext2D();
this.size = size;
this.canvas = canvas;
this.graphics = graphics;
this.rows = rows;
this.columns = columns;
this.gapSize = gapSize;
cells = new Cell[rows * columns];
final float cellWidth = ((float)size - (rows - 1) * gapSize) / rows;
final float cellHeight = ((float)size - (columns - 1) * gapSize) / rows;
for (int y = 0; y < columns; y++) {
final float startY = y * cellHeight + y * gapSize;
for (int x = 0; x < rows; x++) {
final float startX = x * cellWidth + x * gapSize;
cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
}
}
final Region background = createBackground();
final Text player1 = createText("player_1", "Player 1");
final Text player2 = createText("player_2", "Player 2");
final HBox playersContainer = new HBox(100, player1, player2);
playersContainer.setAlignment(Pos.TOP_CENTER);
playersContainer.setPickOnBounds(false);
playersContainer.setTranslateY(50);
final Button hint = createButton("Hint", () -> {});
final Button back = createButton("Back", () -> { App.activate(new MainMenu()); });
final VBox controlContainer = new VBox(hint, back);
StackPane.setAlignment(controlContainer, Pos.BOTTOM_LEFT);
controlContainer.setPickOnBounds(false);
pane = new StackPane(background, canvas, playersContainer, controlContainer);
}
}

View File

@@ -0,0 +1,137 @@
package org.toop.app.menu.game;
import javafx.scene.paint.Color;
import org.toop.game.Game;
import org.toop.game.Player;
import org.toop.game.tictactoe.TicTacToe;
import org.toop.game.tictactoe.TicTacToeAI;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public final class TicTacToeMenu extends GameMenu {
private final TicTacToe game;
private final TicTacToeAI ai;
private final ExecutorService executor = Executors.newFixedThreadPool(1);
private final BlockingQueue<Game.Move> moveQueue = new LinkedBlockingQueue<>();
public TicTacToeMenu(TicTacToe game) {
super(3, 3, 10);
graphics.setFill(Color.CYAN);
for (int x = 1; x < rows; x++) {
graphics.fillRect(cells[x].x - gapSize, 0, gapSize, size);
}
for (int y = 1; y < columns; y++) {
graphics.fillRect(0, cells[y * rows].y - gapSize, size, gapSize);
}
this.game = game;
ai = new TicTacToeAI();
canvas.setOnMouseClicked(event -> {
for (int i = 0; i < cells.length; i++) {
if (cells[i].check((float) event.getX(), (float) event.getY())) {
final Game.Move move = new Game.Move(i, game.getCurrentPlayer().values()[0]);
play(move);
}
}
});
this.executor.submit(this::gameThread);
}
private void play(Game.Move move) {
final Game.Move[] legalMoves = game.getLegalMoves();
boolean isLegal = false;
for (final Game.Move legalMove : legalMoves) {
if (legalMove.position() == move.position() && legalMove.value() == move.value()) {
isLegal = true;
break;
}
}
if (!isLegal) {
return;
}
try { moveQueue.put(move); }
catch (InterruptedException _) {}
}
private void placeX(int cell) {
graphics.setStroke(Color.ORANGERED);
graphics.setLineWidth(gapSize);
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.strokeLine(x, y, x + width, y + height);
graphics.strokeLine(x + width, y, x, y + height);
}
private void placeO(int cell) {
graphics.setStroke(Color.DEEPSKYBLUE);
graphics.setLineWidth(gapSize);
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.strokeOval(x, y, width, height);
}
private void gameThread() {
boolean running = true;
while(running) {
final Player currentPlayer = game.getCurrentPlayer();
try {
Game.Move move;
if (!currentPlayer.isAI()) {
try { move = moveQueue.take(); }
catch (InterruptedException _) { return; }
} else {
move = ai.findBestMove(game, 9);
}
assert move != null;
final Game.State state = game.play(move);
if (move.value() == 'X') {
placeX(move.position());
} else {
placeO(move.position());
}
switch (state) {
case NORMAL: break;
case DRAW:
case LOSE:
case WIN:
running = false;
break;
}
} catch (RuntimeException e) {
return;
}
}
executor.close();
}
}

View File

@@ -1,71 +0,0 @@
package org.toop.app.screen;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
public abstract class Screen {
protected final class Cell {
public float x;
public float y;
public float width;
public float height;
public Cell(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public boolean check(int x, int y) {
return x >= this.x && y >= this.y && x <= this.x + width && y <= this.y + height;
}
}
protected final Canvas canvas;
protected final GraphicsContext graphics;
protected final int width;
protected final int height;
protected final int rowSize;
protected final int columnSize;
protected final int gapSize;
protected final Cell[] cells;
protected Screen(int width, int height, int rowSize, int columnSize, int gapSize) {
final Canvas canvas = new Canvas(width, height);
final GraphicsContext graphics = canvas.getGraphicsContext2D();
this.canvas = canvas;
this.graphics = graphics;
this.width = width;
this.height = height;
this.rowSize = rowSize;
this.columnSize = columnSize;
this.gapSize = gapSize;
cells = new Cell[rowSize * columnSize];
final float cellWidth = ((float)width - (rowSize - 1) * gapSize) / rowSize;
final float cellHeight = ((float)height - (columnSize - 1) * gapSize) / rowSize;
for (int y = 0; y < columnSize; y++) {
final float startY = y * cellHeight + y * gapSize;
for (int x = 0; x < rowSize; x++) {
final float startX = x * cellWidth + x * gapSize;
cells[y * rowSize + x] = new Cell(startX, startY, cellWidth, cellHeight);
}
}
}
public Canvas getCanvas() { return canvas; }
}

View File

@@ -1,78 +0,0 @@
package org.toop.app.screen;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TicTacToeScreen extends Screen {
public TicTacToeScreen(int size) {
super(size, size, 3, 3, 10);
graphics.setFill(Color.CYAN);
for (int x = 1; x < rowSize; x++) {
graphics.fillRect(cells[x].x - gapSize, 0, gapSize, height - gapSize);
}
for (int y = 1; y < columnSize; y++) {
graphics.fillRect(0, cells[y * rowSize].y - gapSize, width - gapSize, gapSize);
}
}
public void placeX(int cell) {
graphics.setFill(Color.WHITE);
graphics.setLineWidth(gapSize);
final float x = cells[cell].x;
final float y = cells[cell].y;
final float width = cells[cell].width;
final float height = cells[cell].height;
graphics.strokeLine(x, y, x + width, y + height);
graphics.strokeLine(x + width, y, x, y + height);
}
public void placeO(int cell) {
graphics.setFill(Color.WHITE);
graphics.setLineWidth(gapSize);
final float x = cells[cell].x;
final float y = cells[cell].y;
final float width = cells[cell].width;
final float height = cells[cell].height;
graphics.strokeOval(x, y, width, height);
}
public void simulate(int speedInMilliseconds) {
final Random random = new Random();
final List<Integer> playedCells = new ArrayList<>();
final Timeline timeline = new Timeline(new KeyFrame(Duration.millis(speedInMilliseconds), _ ->{
int cell;
do {
cell = random.nextInt(cells.length);
} while (playedCells.contains(cell));
if (playedCells.size() % 2 == 0) {
placeX(cell);
} else {
placeO(cell);
}
playedCells.add(cell);
}));
timeline.setCycleCount(9);
timeline.play();
}
}

View File

@@ -1,5 +1,5 @@
.background { .background {
-fx-background-image: url("/image/lowpoly.png"); -fx-background-color: linear-gradient(to bottom right, #21a7b2, #8f32b9);
} }
.text { .text {

View File

@@ -4,7 +4,7 @@ import java.util.Arrays;
public abstract class Game { public abstract class Game {
public enum State { public enum State {
NORMAL, LOSE, DRAW, WIN, NORMAL, DRAW, LOSE, WIN,
} }
public record Move(int position, char value) {} public record Move(int position, char value) {}

View File

@@ -1,3 +1,3 @@
package org.toop.game; package org.toop.game;
public record Player(String name, char... values) {} public record Player(String name, boolean isAI, char... values) {}

View File

@@ -8,8 +8,8 @@ import java.util.ArrayList;
public final class TicTacToe extends Game { public final class TicTacToe extends Game {
private int movesLeft; private int movesLeft;
public TicTacToe(String player1, String player2) { public TicTacToe(String player1, boolean isPlayer1AI, String player2, boolean isPlayer2AI) {
super(3, 3, new Player(player1, 'X'), new Player(player2, 'O')); super(3, 3, new Player(player1, isPlayer1AI, 'X'), new Player(player2, isPlayer2AI, 'O'));
movesLeft = board.length; movesLeft = board.length;
} }
@@ -51,6 +51,7 @@ public final class TicTacToe extends Game {
return State.DRAW; return State.DRAW;
} }
} }
return State.NORMAL; return State.NORMAL;
} }