mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 19:04:49 +00:00
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:
@@ -1,15 +1,8 @@
|
|||||||
package org.toop.app;
|
package org.toop.app;
|
||||||
|
|
||||||
import java.util.Stack;
|
import org.toop.app.view.ViewStack;
|
||||||
import javafx.application.Application;
|
import org.toop.app.view.views.MainView;
|
||||||
import javafx.application.Platform;
|
import org.toop.app.view.views.QuitView;
|
||||||
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.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;
|
||||||
@@ -17,154 +10,110 @@ import org.toop.framework.resource.resources.CssAsset;
|
|||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
import org.toop.local.AppSettings;
|
import org.toop.local.AppSettings;
|
||||||
|
|
||||||
public final class App extends Application {
|
import javafx.application.Application;
|
||||||
private static Stage stage;
|
import javafx.scene.Scene;
|
||||||
private static Scene scene;
|
import javafx.scene.layout.StackPane;
|
||||||
private static StackPane root;
|
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 height;
|
||||||
private static int width;
|
private static int width;
|
||||||
|
|
||||||
private static boolean isQuitting;
|
private static boolean isQuitting;
|
||||||
|
|
||||||
public static void run(String[] args) {
|
public static void run(String[] args) {
|
||||||
launch(args);
|
launch(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws Exception {
|
public void start(Stage stage) throws Exception {
|
||||||
final StackPane root = new StackPane();
|
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.setTitle(AppContext.getString("app-title"));
|
||||||
stage.setWidth(1080);
|
stage.setWidth(1080);
|
||||||
stage.setHeight(720);
|
stage.setHeight(720);
|
||||||
|
|
||||||
stage.setOnCloseRequest(
|
stage.setOnCloseRequest(event -> {
|
||||||
event -> {
|
event.consume();
|
||||||
event.consume();
|
startQuit();
|
||||||
|
});
|
||||||
|
|
||||||
if (!isQuitting) {
|
stage.setScene(scene);
|
||||||
quitPopup();
|
stage.setResizable(false);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.setScene(scene);
|
stage.show();
|
||||||
stage.setResizable(false);
|
|
||||||
|
|
||||||
stage.show();
|
App.stage = stage;
|
||||||
|
App.scene = scene;
|
||||||
|
|
||||||
App.stage = stage;
|
App.width = (int)stage.getWidth();
|
||||||
App.scene = scene;
|
App.height = (int)stage.getHeight();
|
||||||
App.root = root;
|
|
||||||
|
|
||||||
App.stack = new Stack<>();
|
App.isQuitting = false;
|
||||||
|
|
||||||
App.width = (int) stage.getWidth();
|
AppSettings.applySettings();
|
||||||
App.height = (int) stage.getHeight();
|
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();
|
ViewStack.push(new QuitView());
|
||||||
settings.applySettings();
|
isQuitting = true;
|
||||||
|
}
|
||||||
|
|
||||||
activate(new MainLayer());
|
public static void stopQuit() {
|
||||||
}
|
ViewStack.pop();
|
||||||
|
isQuitting = false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void activate(Layer layer) {
|
public static void quit() {
|
||||||
Platform.runLater(
|
ViewStack.cleanup();
|
||||||
() -> {
|
stage.close();
|
||||||
popAll();
|
}
|
||||||
push(layer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void push(Layer layer) {
|
public static void reload() {
|
||||||
Platform.runLater(
|
stage.setTitle(AppContext.getString("app-title"));
|
||||||
() -> {
|
ViewStack.reload();
|
||||||
root.getChildren().addLast(layer.getLayer());
|
}
|
||||||
stack.push(layer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void pop() {
|
public static void setFullscreen(boolean fullscreen) {
|
||||||
Platform.runLater(
|
stage.setFullScreen(fullscreen);
|
||||||
() -> {
|
|
||||||
root.getChildren().removeLast();
|
|
||||||
stack.pop();
|
|
||||||
|
|
||||||
isQuitting = false;
|
width = (int) stage.getWidth();
|
||||||
});
|
height = (int) stage.getHeight();
|
||||||
}
|
|
||||||
|
|
||||||
public static void popAll() {
|
reload();
|
||||||
Platform.runLater(
|
}
|
||||||
() -> {
|
|
||||||
final int childrenCount = root.getChildren().size();
|
|
||||||
|
|
||||||
for (int i = 0; i < childrenCount; i++) {
|
public static void setStyle(String theme, String layoutSize) {
|
||||||
try {
|
final int stylesCount = scene.getStylesheets().size();
|
||||||
root.getChildren().removeLast();
|
|
||||||
} catch (Exception e) {
|
|
||||||
IO.println(e); // TODO: Use logger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.removeAllElements();
|
for (int i = 0; i < stylesCount; i++) {
|
||||||
});
|
scene.getStylesheets().removeLast();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void quitPopup() {
|
scene.getStylesheets().add(ResourceManager.<CssAsset>get("general.css").getUrl());
|
||||||
Platform.runLater(
|
scene.getStylesheets().add(ResourceManager.<CssAsset>get(theme + ".css").getUrl());
|
||||||
() -> {
|
scene.getStylesheets().add(ResourceManager.<CssAsset>get(layoutSize + ".css").getUrl());
|
||||||
push(new QuitPopup());
|
|
||||||
isQuitting = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void quit() {
|
reload();
|
||||||
new EventFlow().addPostEvent(new AudioEvents.StopAudioManager()).postEvent();
|
}
|
||||||
stage.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void reloadAll() {
|
public static int getWidth() {
|
||||||
stage.setTitle(AppContext.getString("appTitle"));
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
for (final Layer layer : stack) {
|
public static int getHeight() {
|
||||||
layer.reload();
|
return height;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -14,8 +14,8 @@ public class GameInformation {
|
|||||||
|
|
||||||
public static int maxDepth(Type type) {
|
public static int maxDepth(Type type) {
|
||||||
return switch (type) {
|
return switch (type) {
|
||||||
case TICTACTOE -> 5;
|
case TICTACTOE -> 5; // Todo. 5 seems to always draw or win. could increase to 9 but that might affect performance
|
||||||
case REVERSI -> 0; // Todo
|
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 static class Player {
|
||||||
public String name = "";
|
public String name = "";
|
||||||
public boolean isHuman = true;
|
public boolean isHuman = true;
|
||||||
public int computerDifficulty = 0;
|
public int computerDifficulty = 1;
|
||||||
public int computerThinkTime = 1;
|
public int computerThinkTime = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.toop.app;
|
package org.toop.app;
|
||||||
|
|
||||||
|
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.view.ViewStack;
|
||||||
import org.toop.app.view.views.ChallengeView;
|
import org.toop.app.view.views.ChallengeView;
|
||||||
import org.toop.app.view.views.ErrorView;
|
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.SendChallengeView;
|
||||||
import org.toop.app.view.views.ServerView;
|
import org.toop.app.view.views.ServerView;
|
||||||
import org.toop.framework.eventbus.EventFlow;
|
import org.toop.framework.eventbus.EventFlow;
|
||||||
@@ -11,6 +13,7 @@ import org.toop.framework.networking.events.NetworkEvents;
|
|||||||
import org.toop.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
@@ -93,6 +96,10 @@ public final class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendChallenge(String opponent) {
|
private void sendChallenge(String opponent) {
|
||||||
|
if (!isPolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ViewStack.push(new SendChallengeView(this, opponent, (playerInformation, gameType) -> {
|
ViewStack.push(new SendChallengeView(this, opponent, (playerInformation, gameType) -> {
|
||||||
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType))
|
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType))
|
||||||
.listen(NetworkEvents.GameMatchResponse.class, e -> {
|
.listen(NetworkEvents.GameMatchResponse.class, e -> {
|
||||||
@@ -108,13 +115,20 @@ public final class Server {
|
|||||||
information.players[0].name = user;
|
information.players[0].name = user;
|
||||||
information.players[1].name = opponent;
|
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();
|
}).postEvent();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
|
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
|
||||||
|
if (!isPolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String challengerName = response.challengerName();
|
String challengerName = response.challengerName();
|
||||||
challengerName = challengerName.substring(challengerName.indexOf("\"") + 1);
|
challengerName = challengerName.substring(challengerName.indexOf("\"") + 1);
|
||||||
challengerName = challengerName.substring(0, challengerName.indexOf("\""));
|
challengerName = challengerName.substring(0, challengerName.indexOf("\""));
|
||||||
@@ -145,23 +159,25 @@ public final class Server {
|
|||||||
information.players[1].name = e.opponent();
|
information.players[1].name = e.opponent();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TICTACTOE:
|
case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
|
||||||
new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame);
|
case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break;
|
||||||
break;
|
|
||||||
case REVERSI:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendMessage(String message) {
|
||||||
|
new EventFlow().addPostEvent(new NetworkEvents.SendMessage(clientId, message)).postEvent();
|
||||||
|
}
|
||||||
|
|
||||||
private void disconnect() {
|
private void disconnect() {
|
||||||
// Todo
|
new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent();
|
||||||
|
ViewStack.push(new OnlineView());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forfeitGame() {
|
private void forfeitGame() {
|
||||||
// Todo
|
new EventFlow().addPostEvent(new NetworkEvents.SendForfeit(clientId)).postEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void exitGame() {
|
private void exitGame() {
|
||||||
@@ -182,6 +198,12 @@ public final class Server {
|
|||||||
public List<String> getGamesList() {
|
public List<String> getGamesList() {
|
||||||
final List<String> list = new ArrayList<String>();
|
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("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;
|
return list;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
public abstract class GameCanvas {
|
public abstract class GameCanvas {
|
||||||
protected record Cell(float x, float y, float width, float height) {
|
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;
|
protected final Canvas canvas;
|
||||||
@@ -19,18 +23,15 @@ public abstract class GameCanvas {
|
|||||||
protected final int width;
|
protected final int width;
|
||||||
protected final int height;
|
protected final int height;
|
||||||
|
|
||||||
protected final int rows;
|
protected final int rowSize;
|
||||||
protected final int columns;
|
protected final int columnSize;
|
||||||
|
|
||||||
protected final int gapSize;
|
protected final int gapSize;
|
||||||
protected final boolean edges;
|
protected final boolean edges;
|
||||||
|
|
||||||
protected final Cell[] cells;
|
protected final Cell[] cells;
|
||||||
|
|
||||||
protected GameCanvas(Color color, int width, int height, int rows, int columns, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
|
protected GameCanvas(Color color, int width, int height, int rowSize, int columnSize, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
|
||||||
width += gapSize * 2;
|
|
||||||
height += gapSize * 2;
|
|
||||||
|
|
||||||
canvas = new Canvas(width, height);
|
canvas = new Canvas(width, height);
|
||||||
graphics = canvas.getGraphicsContext2D();
|
graphics = canvas.getGraphicsContext2D();
|
||||||
|
|
||||||
@@ -39,23 +40,23 @@ public abstract class GameCanvas {
|
|||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
|
||||||
this.rows = rows;
|
this.rowSize = rowSize;
|
||||||
this.columns = columns;
|
this.columnSize = columnSize;
|
||||||
|
|
||||||
this.gapSize = gapSize;
|
this.gapSize = gapSize;
|
||||||
this.edges = edges;
|
this.edges = edges;
|
||||||
|
|
||||||
cells = new Cell[rows * columns];
|
cells = new Cell[rowSize * columnSize];
|
||||||
|
|
||||||
final float cellWidth = ((float) width - gapSize * rows) / rows;
|
final float cellWidth = ((float)width - gapSize * rowSize - gapSize) / rowSize;
|
||||||
final float cellHeight = ((float) height - gapSize * columns) / columns;
|
final float cellHeight = ((float)height - gapSize * columnSize - gapSize) / columnSize;
|
||||||
|
|
||||||
for (int y = 0; y < columns; y++) {
|
for (int y = 0; y < columnSize; y++) {
|
||||||
final float startY = gapSize + y * cellHeight + y * gapSize;
|
final float startY = y * cellHeight + y * gapSize + gapSize;
|
||||||
|
|
||||||
for (int x = 0; x < rows; x++) {
|
for (int x = 0; x < rowSize; x++) {
|
||||||
final float startX = gapSize + x * cellWidth + x * gapSize;
|
final float startX = x * cellWidth + x * gapSize + gapSize;
|
||||||
cells[y * rows + x] = new Cell(startX, startY, cellWidth, cellHeight);
|
cells[x + y * rowSize] = new Cell(startX, startY, cellWidth, cellHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +65,15 @@ public abstract class GameCanvas {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int column = (int)((event.getX() / this.width) * rows);
|
final int column = (int)((event.getX() / this.width) * rowSize);
|
||||||
final int row = (int)((event.getY() / this.height) * columns);
|
final int row = (int)((event.getY() / this.height) * columnSize);
|
||||||
|
|
||||||
event.consume();
|
final Cell cell = cells[column + row * rowSize];
|
||||||
onCellClicked.accept(column + row * rows);
|
|
||||||
|
if (cell.isInside(event.getX(), event.getY())) {
|
||||||
|
event.consume();
|
||||||
|
onCellClicked.accept(column + row * rowSize);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
render();
|
render();
|
||||||
@@ -81,20 +86,22 @@ public abstract class GameCanvas {
|
|||||||
public void render() {
|
public void render() {
|
||||||
graphics.setFill(color);
|
graphics.setFill(color);
|
||||||
|
|
||||||
for (int x = 1; x < rows; x++) {
|
for (int x = 0; x < rowSize - 1; x++) {
|
||||||
graphics.fillRect(cells[x].x() - gapSize, 0, gapSize, height + gapSize);
|
final float start = cells[x].x + cells[x].width;
|
||||||
|
graphics.fillRect(start, gapSize, gapSize, height - gapSize * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 1; y < columns; y++) {
|
for (int y = 0; y < columnSize - 1; y++) {
|
||||||
graphics.fillRect(0, cells[y * rows].y() - gapSize, width + gapSize, gapSize);
|
final float start = cells[y * rowSize].y + cells[y * rowSize].height;
|
||||||
|
graphics.fillRect(gapSize, start, width - gapSize * 2, gapSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edges) {
|
if (edges) {
|
||||||
graphics.fillRect(0, 0, gapSize, height + gapSize);
|
graphics.fillRect(0, 0, width, gapSize);
|
||||||
graphics.fillRect(0, 0, width + gapSize, gapSize);
|
graphics.fillRect(0, 0, gapSize, height);
|
||||||
|
|
||||||
graphics.fillRect(width + gapSize, 0, gapSize, height + gapSize * 2);
|
graphics.fillRect(width - gapSize, 0, gapSize, height);
|
||||||
graphics.fillRect(0, height + gapSize, width + gapSize * 2, gapSize);
|
graphics.fillRect(0, height - gapSize, width, gapSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
app/src/main/java/org/toop/app/canvas/ReversiCanvas.java
Normal file
34
app/src/main/java/org/toop/app/canvas/ReversiCanvas.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
257
app/src/main/java/org/toop/app/game/ReversiGame.java
Normal file
257
app/src/main/java/org/toop/app/game/ReversiGame.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ import javafx.scene.paint.Color;
|
|||||||
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public final class TicTacToeGame {
|
public final class TicTacToeGame {
|
||||||
private final GameInformation information;
|
private final GameInformation information;
|
||||||
@@ -30,7 +32,9 @@ public final class TicTacToeGame {
|
|||||||
private final GameView view;
|
private final GameView view;
|
||||||
private final TicTacToeCanvas canvas;
|
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.information = information;
|
||||||
|
|
||||||
this.myTurn = myTurn;
|
this.myTurn = myTurn;
|
||||||
@@ -39,12 +43,18 @@ public final class TicTacToeGame {
|
|||||||
game = new TicTacToe();
|
game = new TicTacToe();
|
||||||
ai = new TicTacToeAI();
|
ai = new TicTacToeAI();
|
||||||
|
|
||||||
|
isRunning = new AtomicBoolean(true);
|
||||||
|
|
||||||
if (onForfeit == null || onExit == null) {
|
if (onForfeit == null || onExit == null) {
|
||||||
view = new GameView(null, () -> {
|
view = new GameView(null, () -> {
|
||||||
|
isRunning.set(false);
|
||||||
ViewStack.push(new LocalMultiplayerView(information));
|
ViewStack.push(new LocalMultiplayerView(information));
|
||||||
});
|
}, null);
|
||||||
} else {
|
} else {
|
||||||
view = new GameView(onForfeit, onExit);
|
view = new GameView(onForfeit, () -> {
|
||||||
|
isRunning.set(false);
|
||||||
|
onExit.run();
|
||||||
|
}, onMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas = new TicTacToeCanvas(Color.GRAY,
|
canvas = new TicTacToeCanvas(Color.GRAY,
|
||||||
@@ -84,10 +94,12 @@ public final class TicTacToeGame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void localGameThread() {
|
public TicTacToeGame(GameInformation information) {
|
||||||
boolean isRunning = true;
|
this(information, 0, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
while (isRunning) {
|
private void localGameThread() {
|
||||||
|
while (isRunning.get()) {
|
||||||
final int currentTurn = game.getCurrentTurn();
|
final int currentTurn = game.getCurrentTurn();
|
||||||
final char currentValue = currentTurn == 0? 'X' : 'O';
|
final char currentValue = currentTurn == 0? 'X' : 'O';
|
||||||
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
|
final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type);
|
||||||
@@ -146,12 +158,16 @@ public final class TicTacToeGame {
|
|||||||
view.gameOver(false, "");
|
view.gameOver(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunning = false;
|
isRunning.set(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
||||||
|
if (!isRunning.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
char playerChar;
|
char playerChar;
|
||||||
|
|
||||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||||
@@ -185,6 +201,10 @@ public final class TicTacToeGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
|
private void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
|
||||||
|
if (!isRunning.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
moveQueue.clear();
|
moveQueue.clear();
|
||||||
|
|
||||||
int position = -1;
|
int position = -1;
|
||||||
@@ -205,7 +225,11 @@ public final class TicTacToeGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
private void onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
||||||
view.updateChat("anon", msg.message());
|
if (!isRunning.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
view.updateChat(msg.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setGameLabels(boolean isMe) {
|
private void setGameLabels(boolean isMe) {
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import org.toop.local.AppContext;
|
|||||||
|
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ListView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public final class GameView extends View {
|
public final class GameView extends View {
|
||||||
private static class GameOverView extends View {
|
private static class GameOverView extends View {
|
||||||
private final boolean iWon;
|
private final boolean iWon;
|
||||||
@@ -65,7 +69,10 @@ public final class GameView extends View {
|
|||||||
|
|
||||||
private final Text nextPlayerHeader;
|
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;
|
assert onExit != null;
|
||||||
|
|
||||||
super(true, "bg-primary");
|
super(true, "bg-primary");
|
||||||
@@ -78,6 +85,19 @@ public final class GameView extends View {
|
|||||||
forfeitButton = null;
|
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 = button();
|
||||||
exitButton.setText(AppContext.getString("exit"));
|
exitButton.setText(AppContext.getString("exit"));
|
||||||
exitButton.setOnAction(_ -> onExit.run());
|
exitButton.setOnAction(_ -> onExit.run());
|
||||||
@@ -110,6 +130,15 @@ public final class GameView extends View {
|
|||||||
exitButton
|
exitButton
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (chatListView != null) {
|
||||||
|
add(Pos.BOTTOM_RIGHT,
|
||||||
|
fit(vboxFill(
|
||||||
|
chatListView,
|
||||||
|
chatInput
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) {
|
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) {
|
public void updateChat(String message) {
|
||||||
// Todo
|
if (chatListView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Text messageText = text();
|
||||||
|
messageText.setText(message);
|
||||||
|
|
||||||
|
chatListView.getItems().add(messageText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void gameOver(boolean iWon, String winner) {
|
public void gameOver(boolean iWon, String winner) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.toop.app.view.views;
|
package org.toop.app.view.views;
|
||||||
|
|
||||||
import org.toop.app.GameInformation;
|
import org.toop.app.GameInformation;
|
||||||
|
import org.toop.app.game.ReversiGame;
|
||||||
import org.toop.app.game.TicTacToeGame;
|
import org.toop.app.game.TicTacToeGame;
|
||||||
import org.toop.app.view.View;
|
import org.toop.app.view.View;
|
||||||
import org.toop.app.view.ViewStack;
|
import org.toop.app.view.ViewStack;
|
||||||
@@ -42,8 +43,8 @@ public final class LocalMultiplayerView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (information.type) {
|
switch (information.type) {
|
||||||
case TICTACTOE: new TicTacToeGame(information, 0, null, null);
|
case TICTACTOE: new TicTacToeGame(information); break;
|
||||||
case REVERSI: break;
|
case REVERSI: new ReversiGame(information); break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public final class LocalView extends View {
|
|||||||
|
|
||||||
final Button reversiButton = button();
|
final Button reversiButton = button();
|
||||||
reversiButton.setText(AppContext.getString("reversi"));
|
reversiButton.setText(AppContext.getString("reversi"));
|
||||||
reversiButton.setOnAction(_ -> {});
|
reversiButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.REVERSI)); });
|
||||||
|
|
||||||
add(Pos.CENTER,
|
add(Pos.CENTER,
|
||||||
fit(vboxFill(
|
fit(vboxFill(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.toop.app.view.views;
|
|||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
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.framework.audio.VolumeControl;
|
||||||
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.local.AppContext;
|
import org.toop.local.AppContext;
|
||||||
@@ -143,7 +144,7 @@ public final class OptionsView extends View {
|
|||||||
|
|
||||||
masterVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
masterVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||||
AppSettings.getSettings().setVolume(newValue.intValue());
|
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) -> {
|
effectsVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||||
AppSettings.getSettings().setFxVolume(newValue.intValue());
|
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) -> {
|
musicVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||||
AppSettings.getSettings().setMusicVolume(newValue.intValue());
|
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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package org.toop.local;
|
package org.toop.local;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Locale;
|
|
||||||
import org.toop.app.App;
|
import org.toop.app.App;
|
||||||
import org.toop.framework.audio.VolumeControl;
|
import org.toop.framework.audio.VolumeControl;
|
||||||
import org.toop.framework.audio.events.AudioEvents;
|
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.resource.resources.SettingsAsset;
|
||||||
import org.toop.framework.settings.Settings;
|
import org.toop.framework.settings.Settings;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class AppSettings {
|
public class AppSettings {
|
||||||
|
private static SettingsAsset settingsAsset;
|
||||||
|
|
||||||
private SettingsAsset settingsAsset;
|
public static void applySettings() {
|
||||||
|
settingsAsset = getPath();
|
||||||
|
if (!settingsAsset.isLoaded()) {
|
||||||
|
settingsAsset.load();
|
||||||
|
}
|
||||||
|
|
||||||
public void applySettings() {
|
Settings settingsData = settingsAsset.getContent();
|
||||||
this.settingsAsset = getPath();
|
|
||||||
if (!this.settingsAsset.isLoaded()) {
|
|
||||||
this.settingsAsset.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
public static SettingsAsset getPath() {
|
||||||
App.setFullscreen(settingsData.fullScreen);
|
if (settingsAsset == null) {
|
||||||
new EventFlow()
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
.addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume, VolumeControl.MASTERVOLUME))
|
String basePath;
|
||||||
.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 SettingsAsset getPath() {
|
if (os.contains("win")) {
|
||||||
if (this.settingsAsset == null) {
|
basePath = System.getenv("APPDATA");
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
if (basePath == null) {
|
||||||
String basePath;
|
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")) {
|
File settingsFile =
|
||||||
basePath = System.getenv("APPDATA");
|
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
|
||||||
if (basePath == null) {
|
// this.settingsAsset = new SettingsAsset(settingsFile);
|
||||||
basePath = System.getProperty("user.home");
|
ResourceManager.addAsset(new ResourceMeta<>("settings.json", new SettingsAsset(settingsFile)));
|
||||||
}
|
}
|
||||||
} else if (os.contains("mac")) {
|
return ResourceManager.get("settings.json");
|
||||||
basePath = System.getProperty("user.home") + "/Library/Application Support";
|
}
|
||||||
} else {
|
|
||||||
basePath = System.getProperty("user.home") + "/.config";
|
|
||||||
}
|
|
||||||
|
|
||||||
File settingsFile =
|
public static SettingsAsset getSettings() {
|
||||||
new File(basePath + File.separator + "ISY1" + File.separator + "settings.json");
|
return settingsAsset;
|
||||||
|
}
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
BIN
app/src/main/resources/assets/audio/music/blitzkrieg.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/blitzkrieg.mp3
Normal file
Binary file not shown.
BIN
app/src/main/resources/assets/audio/music/getlucky.mp3
Normal file
BIN
app/src/main/resources/assets/audio/music/getlucky.mp3
Normal file
Binary file not shown.
Binary file not shown.
@@ -73,11 +73,6 @@
|
|||||||
-fx-text-fill: #e0f2e9;
|
-fx-text-fill: #e0f2e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
|
||||||
-fx-background-color: linear-gradient(to bottom, #2b3a3e, #1a2224);
|
|
||||||
-fx-background-radius: 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
-fx-background-color: linear-gradient(to bottom, #1f3a3b, #124040);
|
-fx-background-color: linear-gradient(to bottom, #1f3a3b, #124040);
|
||||||
-fx-background-radius: 6;
|
-fx-background-radius: 6;
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
.container,
|
.container,
|
||||||
.credits-container {
|
.credits-container {
|
||||||
-fx-alignment: TOP_CENTER;
|
-fx-alignment: TOP_CENTER;
|
||||||
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fit {
|
.fit,
|
||||||
|
.fit .viewport {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-border-color: transparent;
|
-fx-border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,11 +73,6 @@
|
|||||||
-fx-text-fill: #f0fff0;
|
-fx-text-fill: #f0fff0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
|
||||||
-fx-background-color: linear-gradient(to bottom, #121e1c, #0c1210);
|
|
||||||
-fx-background-radius: 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
-fx-background-color: linear-gradient(to bottom, #15331a, #0e2b15);
|
-fx-background-color: linear-gradient(to bottom, #15331a, #0e2b15);
|
||||||
-fx-background-radius: 6;
|
-fx-background-radius: 6;
|
||||||
|
|||||||
@@ -23,11 +23,6 @@
|
|||||||
-fx-spacing: 14;
|
-fx-spacing: 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-container {
|
|
||||||
-fx-alignment: CENTER;
|
|
||||||
-fx-padding: 28;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-player {
|
.current-player {
|
||||||
-fx-font-size: 32px;
|
-fx-font-size: 32px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
.bg-primary {
|
.bg-primary {
|
||||||
-fx-background-color: linear-gradient(to bottom, #f0f6f4, #dbe9e5);
|
-fx-background-color: linear-gradient(to bottom, #e8f1ef, #cfded9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-secondary {
|
.bg-secondary {
|
||||||
-fx-background-color: linear-gradient(to bottom, #d0e0db, #b7cdc6);
|
-fx-background-color: linear-gradient(to bottom, #c3d6d1, #a9c2bb);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-popup {
|
.bg-popup {
|
||||||
-fx-background-color: rgba(240, 248, 250, 0.9);
|
-fx-background-color: rgba(224, 240, 242, 0.95);
|
||||||
-fx-background-radius: 8;
|
-fx-background-radius: 8;
|
||||||
-fx-effect: dropshadow(gaussian, #a0c4b088, 6, 0, 0, 2);
|
-fx-effect: dropshadow(gaussian, #a0c4b088, 6, 0, 0, 2);
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
-fx-border-color: #5caf5c;
|
-fx-border-color: #5caf5c;
|
||||||
-fx-cursor: hand;
|
-fx-cursor: hand;
|
||||||
-fx-effect: dropshadow(gaussian, #7abf7aaa, 5, 0, 0, 2);
|
-fx-effect: dropshadow(gaussian, #7abf7aaa, 5, 0, 0, 2);
|
||||||
-fx-text-fill: #2a3a2a;
|
-fx-text-fill: #2e4d2e;
|
||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
.combo-box .list-cell {
|
.combo-box .list-cell {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-text-fill: #588758;
|
-fx-text-fill: #2e4d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.combo-box .list-view {
|
.combo-box .list-view {
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.combo-box-popup .list-cell {
|
.combo-box-popup .list-cell {
|
||||||
-fx-text-fill: #588758;
|
-fx-text-fill: #2e4d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.combo-box-popup .list-cell:hover {
|
.combo-box-popup .list-cell:hover {
|
||||||
@@ -70,19 +70,14 @@
|
|||||||
|
|
||||||
.combo-box-popup .list-cell:selected {
|
.combo-box-popup .list-cell:selected {
|
||||||
-fx-background-color: #7ac27a;
|
-fx-background-color: #7ac27a;
|
||||||
-fx-text-fill: #f0faf0;
|
-fx-text-fill: #ffffff;
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
-fx-background-color: linear-gradient(to bottom, #e9f2ee, #cde3d9);
|
|
||||||
-fx-background-radius: 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
-fx-background-color: linear-gradient(to bottom, #e6f0ec, #c8dbcd);
|
-fx-background-color: linear-gradient(to bottom, #e6f0ec, #c8dbcd);
|
||||||
-fx-background-radius: 6;
|
-fx-background-radius: 6;
|
||||||
-fx-border-color: #5caf5c;
|
-fx-border-color: #5caf5c;
|
||||||
-fx-text-fill: #588758;
|
-fx-text-fill: #2e4d2e;
|
||||||
-fx-font-weight: normal;
|
-fx-font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,13 +122,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
-fx-fill: #588758;
|
-fx-fill: #2e4d2e;
|
||||||
-fx-font-weight: normal;
|
-fx-font-weight: normal;
|
||||||
-fx-text-fill: #588758;
|
-fx-text-fill: #2e4d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
-fx-fill: #aad3aa;
|
-fx-fill: #2b5c2b;
|
||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
-fx-text-fill: #aad3aa;
|
-fx-text-fill: #2b5c2b;
|
||||||
}
|
}
|
||||||
@@ -23,11 +23,6 @@
|
|||||||
-fx-spacing: 10;
|
-fx-spacing: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-container {
|
|
||||||
-fx-alignment: CENTER;
|
|
||||||
-fx-padding: 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-player {
|
.current-player {
|
||||||
-fx-font-size: 24px;
|
-fx-font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,6 @@
|
|||||||
-fx-spacing: 6;
|
-fx-spacing: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credits-container {
|
|
||||||
-fx-alignment: CENTER;
|
|
||||||
-fx-padding: 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-player {
|
.current-player {
|
||||||
-fx-font-size: 16px;
|
-fx-font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ public abstract class Game {
|
|||||||
NORMAL,
|
NORMAL,
|
||||||
DRAW,
|
DRAW,
|
||||||
WIN,
|
WIN,
|
||||||
|
MOVE_SKIPPED,
|
||||||
}
|
}
|
||||||
|
|
||||||
public record Move(int position, char value) {}
|
public record Move(int position, char value) {}
|
||||||
|
|
||||||
public static final char EMPTY = (char) 0;
|
public record Score(int player1Score, int player2Score) {}
|
||||||
|
|
||||||
|
public static final char EMPTY = (char)0;
|
||||||
|
|
||||||
public final int rowSize;
|
public final int rowSize;
|
||||||
public final int columnSize;
|
public final int columnSize;
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package org.toop.game.othello;
|
|
||||||
|
|
||||||
import org.toop.game.TurnBasedGame;
|
|
||||||
|
|
||||||
public final class Othello extends TurnBasedGame {
|
|
||||||
Othello() {
|
|
||||||
super(8, 8, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Move[] getLegalMoves() {
|
|
||||||
return new Move[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public State play(Move move) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package org.toop.game.othello;
|
|
||||||
|
|
||||||
import org.toop.game.AI;
|
|
||||||
import org.toop.game.Game;
|
|
||||||
|
|
||||||
public final class OthelloAI extends AI<Othello> {
|
|
||||||
@Override
|
|
||||||
public Game.Move findBestMove(Othello game, int depth) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
196
game/src/main/java/org/toop/game/reversi/Reversi.java
Normal file
196
game/src/main/java/org/toop/game/reversi/Reversi.java
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
package org.toop.game.reversi;
|
||||||
|
|
||||||
|
import org.toop.game.Game;
|
||||||
|
import org.toop.game.TurnBasedGame;
|
||||||
|
import org.toop.game.tictactoe.TicTacToe;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
public final class Reversi extends TurnBasedGame {
|
||||||
|
private int movesTaken;
|
||||||
|
public static final char FIRST_MOVE = 'B';
|
||||||
|
private Set<Point> filledCells = new HashSet<>();
|
||||||
|
|
||||||
|
public Reversi() {
|
||||||
|
super(8, 8, 2);
|
||||||
|
addStartPieces();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Reversi(Reversi other) {
|
||||||
|
super(other);
|
||||||
|
this.movesTaken = other.movesTaken;
|
||||||
|
this.filledCells = other.filledCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addStartPieces() {
|
||||||
|
board[27] = 'W';
|
||||||
|
board[28] = 'B';
|
||||||
|
board[35] = 'B';
|
||||||
|
board[36] = 'W';
|
||||||
|
updateFilledCellsSet();
|
||||||
|
}
|
||||||
|
private void updateFilledCellsSet() {
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
if (board[i] == 'W' || board[i] == 'B') {
|
||||||
|
filledCells.add(new Point(i % columnSize, i / rowSize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Move[] getLegalMoves() {
|
||||||
|
final ArrayList<Move> legalMoves = new ArrayList<>();
|
||||||
|
char[][] boardGrid = makeBoardAGrid();
|
||||||
|
char currentPlayer = (currentTurn==0) ? 'B' : 'W';
|
||||||
|
Set<Point> adjCell = getAdjacentCells(boardGrid);
|
||||||
|
for (Point point : adjCell){
|
||||||
|
Move[] moves = getFlipsForPotentialMove(point,boardGrid,currentPlayer);
|
||||||
|
int score = moves.length;
|
||||||
|
if (score > 0){
|
||||||
|
legalMoves.add(new Move(point.x + point.y * rowSize, currentPlayer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return legalMoves.toArray(new Move[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Point> getAdjacentCells(char[][] boardGrid) {
|
||||||
|
Set<Point> possibleCells = new HashSet<>();
|
||||||
|
for (Point point : filledCells) { //for every filled cell
|
||||||
|
for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++){ //check adjacent cells
|
||||||
|
for (int deltaRow = -1; deltaRow <= 1; deltaRow++){ //orthogonally and diagonally
|
||||||
|
int newX = point.x + deltaColumn, newY = point.y + deltaRow;
|
||||||
|
if (deltaColumn == 0 && deltaRow == 0 //continue if out of bounds
|
||||||
|
|| !isOnBoard(newX, newY)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (boardGrid[newY][newX] == Game.EMPTY) { //check if the cell is empty
|
||||||
|
possibleCells.add(new Point(newX, newY)); //and then add it to the set of possible moves
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return possibleCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Move[] getFlipsForPotentialMove(Point point, char[][] boardGrid, char currentPlayer) {
|
||||||
|
final ArrayList<Move> movesToFlip = new ArrayList<>();
|
||||||
|
for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) {
|
||||||
|
for (int deltaRow = -1; deltaRow <= 1; deltaRow++) {
|
||||||
|
if (deltaColumn == 0 && deltaRow == 0){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Move[] moves = getFlipsInDirection(point,boardGrid,currentPlayer,deltaColumn,deltaRow);
|
||||||
|
if (moves != null) {
|
||||||
|
movesToFlip.addAll(Arrays.asList(moves));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return movesToFlip.toArray(new Move[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Move[] getFlipsInDirection(Point point, char[][] boardGrid, char currentPlayer, int dirX, int dirY) {
|
||||||
|
char opponent = getOpponent(currentPlayer);
|
||||||
|
final ArrayList<Move> movesToFlip = new ArrayList<>();
|
||||||
|
int x = point.x + dirX;
|
||||||
|
int y = point.y + dirY;
|
||||||
|
|
||||||
|
if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isOnBoard(x, y) && boardGrid[y][x] == opponent) {
|
||||||
|
|
||||||
|
movesToFlip.add(new Move(x+y*rowSize, currentPlayer));
|
||||||
|
x += dirX;
|
||||||
|
y += dirY;
|
||||||
|
}
|
||||||
|
if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) {
|
||||||
|
return movesToFlip.toArray(new Move[0]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOnBoard(int x, int y) {
|
||||||
|
return x >= 0 && x < columnSize && y >= 0 && y < rowSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[][] makeBoardAGrid() {
|
||||||
|
char[][] boardGrid = new char[rowSize][columnSize];
|
||||||
|
for (int i = 0; i < 64; i++) {
|
||||||
|
boardGrid[i / rowSize][i % columnSize] = board[i]; //boardGrid[y / row] [x / column]
|
||||||
|
}
|
||||||
|
return boardGrid;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public State play(Move move) {
|
||||||
|
Move[] legalMoves = getLegalMoves();
|
||||||
|
boolean moveIsLegal = false;
|
||||||
|
for (Move legalMove : legalMoves) {
|
||||||
|
if (move.equals(legalMove)) {
|
||||||
|
moveIsLegal = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (moveIsLegal) {
|
||||||
|
Move[] moves = getFlipsForPotentialMove(new Point(move.position()%columnSize,move.position()/rowSize), makeBoardAGrid(), move.value());
|
||||||
|
board[move.position()] = move.value();
|
||||||
|
IO.println(move.position() +" "+ move.value());
|
||||||
|
for (Move m : moves) {
|
||||||
|
board[m.position()] = m.value();
|
||||||
|
}
|
||||||
|
filledCells.add(new Point(move.position() % rowSize, move.position() / columnSize));
|
||||||
|
//updateFilledCellsSet();
|
||||||
|
nextTurn();
|
||||||
|
if (getLegalMoves().length == 0) {
|
||||||
|
skipMyTurn();
|
||||||
|
return State.MOVE_SKIPPED;
|
||||||
|
}
|
||||||
|
return State.NORMAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skipMyTurn(){
|
||||||
|
IO.println("TURN " + getCurrentPlayer() + " SKIPPED");
|
||||||
|
nextTurn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public char getCurrentPlayer() {
|
||||||
|
if (currentTurn == 0){
|
||||||
|
return 'B';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'W';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private char getOpponent(char currentPlayer){
|
||||||
|
if (currentPlayer == 'B') {
|
||||||
|
return 'W';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'B';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Game.Score getScore(){
|
||||||
|
int player1Score = 0, player2Score = 0;
|
||||||
|
for (int count = 0; count < rowSize * columnSize; count++) {
|
||||||
|
if (board[count] == 'W') {
|
||||||
|
player1Score += 1;
|
||||||
|
}
|
||||||
|
if (board[count] == 'B') {
|
||||||
|
player2Score += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Game.Score(player1Score, player2Score);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
11
game/src/main/java/org/toop/game/reversi/ReversiAI.java
Normal file
11
game/src/main/java/org/toop/game/reversi/ReversiAI.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package org.toop.game.reversi;
|
||||||
|
|
||||||
|
import org.toop.game.AI;
|
||||||
|
import org.toop.game.Game;
|
||||||
|
|
||||||
|
public final class ReversiAI extends AI<Reversi> {
|
||||||
|
@Override
|
||||||
|
public Game.Move findBestMove(Reversi game, int depth) {
|
||||||
|
return game.getLegalMoves()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
165
game/src/test/java/org/toop/game/tictactoe/ReversiTest.java
Normal file
165
game/src/test/java/org/toop/game/tictactoe/ReversiTest.java
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package org.toop.game.tictactoe;
|
||||||
|
|
||||||
|
import org.toop.game.Game;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.toop.game.reversi.Reversi;
|
||||||
|
import org.toop.game.reversi.ReversiAI;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class ReversiTest {
|
||||||
|
private Reversi game;
|
||||||
|
private ReversiAI ai;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
game = new Reversi();
|
||||||
|
ai = new ReversiAI();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCorrectStartPiecesPlaced() {
|
||||||
|
assertNotNull(game);
|
||||||
|
assertEquals('W',game.board[27]);
|
||||||
|
assertEquals('B',game.board[28]);
|
||||||
|
assertEquals('B',game.board[35]);
|
||||||
|
assertEquals('W',game.board[36]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetLegalMovesAtStart() {
|
||||||
|
Game.Move[] moves = game.getLegalMoves();
|
||||||
|
List<Game.Move> expectedMoves = List.of(
|
||||||
|
new Game.Move(19,'B'),
|
||||||
|
new Game.Move(26,'B'),
|
||||||
|
new Game.Move(37,'B'),
|
||||||
|
new Game.Move(44,'B')
|
||||||
|
);
|
||||||
|
assertNotNull(moves);
|
||||||
|
assertTrue(moves.length > 0);
|
||||||
|
assertMovesMatchIgnoreOrder(expectedMoves, Arrays.asList(moves));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMovesMatchIgnoreOrder(List<Game.Move> expected, List<Game.Move> actual) {
|
||||||
|
assertEquals(expected.size(), actual.size());
|
||||||
|
for (int i = 0; i < expected.size(); i++) {
|
||||||
|
assertTrue(actual.contains(expected.get(i)));
|
||||||
|
assertTrue(expected.contains(actual.get(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMakeValidMoveFlipsPieces() {
|
||||||
|
game.play(new Game.Move(19, 'B'));
|
||||||
|
assertEquals('B', game.board[19]);
|
||||||
|
assertEquals('B', game.board[27], "Piece should have flipped to B");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testMakeInvalidMoveDoesNothing() {
|
||||||
|
char[] before = game.board.clone();
|
||||||
|
game.play(new Game.Move(0, 'B'));
|
||||||
|
assertArrayEquals(before, game.board, "Board should not change on invalid move");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTurnSwitchesAfterValidMove() {
|
||||||
|
char current = game.getCurrentPlayer();
|
||||||
|
game.play(game.getLegalMoves()[0]);
|
||||||
|
assertNotEquals(current, game.getCurrentPlayer(), "Player turn should switch after a valid move");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCountScoreCorrectlyAtStart() {
|
||||||
|
long start = System.nanoTime();
|
||||||
|
Game.Score score = game.getScore();
|
||||||
|
assertEquals(2, score.player1Score()); // Black
|
||||||
|
assertEquals(2, score.player2Score()); // White
|
||||||
|
long end = System.nanoTime();
|
||||||
|
IO.println((end-start));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void zLegalMovesInCertainPosition() {
|
||||||
|
game.play(new Game.Move(19, 'B'));
|
||||||
|
game.play(new Game.Move(20, 'W'));
|
||||||
|
Game.Move[] moves = game.getLegalMoves();
|
||||||
|
List<Game.Move> expectedMoves = List.of(
|
||||||
|
new Game.Move(13,'B'),
|
||||||
|
new Game.Move(21, 'B'),
|
||||||
|
new Game.Move(29, 'B'),
|
||||||
|
new Game.Move(37, 'B'),
|
||||||
|
new Game.Move(45, 'B'));
|
||||||
|
assertNotNull(moves);
|
||||||
|
assertTrue(moves.length > 0);
|
||||||
|
IO.println(Arrays.toString(moves));
|
||||||
|
assertMovesMatchIgnoreOrder(expectedMoves, Arrays.asList(moves));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCountScoreCorrectlyAtEnd() {
|
||||||
|
for (int i = 0; i < 1; i++){
|
||||||
|
game = new Reversi();
|
||||||
|
Game.Move[] legalMoves = game.getLegalMoves();
|
||||||
|
while(legalMoves.length > 0) {
|
||||||
|
game.play(legalMoves[(int)(Math.random()*legalMoves.length)]);
|
||||||
|
legalMoves = game.getLegalMoves();
|
||||||
|
}
|
||||||
|
Game.Score score = game.getScore();
|
||||||
|
IO.println(score.player1Score());
|
||||||
|
IO.println(score.player2Score());
|
||||||
|
char[][] grid = game.makeBoardAGrid();
|
||||||
|
for (char[] chars : grid) {
|
||||||
|
IO.println(Arrays.toString(chars));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPlayerMustSkipTurnIfNoValidMoves() {
|
||||||
|
game.play(new Game.Move(19, 'B'));
|
||||||
|
game.play(new Game.Move(34, 'W'));
|
||||||
|
game.play(new Game.Move(45, 'B'));
|
||||||
|
game.play(new Game.Move(11, 'W'));
|
||||||
|
game.play(new Game.Move(42, 'B'));
|
||||||
|
game.play(new Game.Move(54, 'W'));
|
||||||
|
game.play(new Game.Move(37, 'B'));
|
||||||
|
game.play(new Game.Move(46, 'W'));
|
||||||
|
game.play(new Game.Move(63, 'B'));
|
||||||
|
game.play(new Game.Move(62, 'W'));
|
||||||
|
game.play(new Game.Move(29, 'B'));
|
||||||
|
game.play(new Game.Move(50, 'W'));
|
||||||
|
game.play(new Game.Move(55, 'B'));
|
||||||
|
game.play(new Game.Move(30, 'W'));
|
||||||
|
game.play(new Game.Move(53, 'B'));
|
||||||
|
game.play(new Game.Move(38, 'W'));
|
||||||
|
game.play(new Game.Move(61, 'B'));
|
||||||
|
game.play(new Game.Move(52, 'W'));
|
||||||
|
game.play(new Game.Move(51, 'B'));
|
||||||
|
game.play(new Game.Move(60, 'W'));
|
||||||
|
game.play(new Game.Move(59, 'B'));
|
||||||
|
assertEquals('B', game.getCurrentPlayer());
|
||||||
|
game.play(ai.findBestMove(game,5));
|
||||||
|
game.play(ai.findBestMove(game,5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAISelectsLegalMove() {
|
||||||
|
Game.Move move = ai.findBestMove(game,4);
|
||||||
|
assertNotNull(move);
|
||||||
|
assertTrue(containsMove(game.getLegalMoves(),move), "AI should always choose a legal move");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsMove(Game.Move[] moves, Game.Move move) {
|
||||||
|
for (Game.Move m : moves) {
|
||||||
|
if (m.equals(move)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user