From 628e4f30b554daa8ccbe64bf9c731c722aa20907 Mon Sep 17 00:00:00 2001 From: Bas Antonius de Jong <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:49:59 +0100 Subject: [PATCH 1/4] Main menu loader (#277) * LoadingWidget main menu * fixed garbage code * Fixed garbage code 2 * LoadWidget fix, added loading to starting the game. Removed unnecessary console output --------- Co-authored-by: ramollia <> --- app/src/main/java/org/toop/Main.java | 3 +- app/src/main/java/org/toop/app/App.java | 48 +++++++++++++++++-- app/src/main/java/org/toop/app/Server.java | 12 ++--- .../java/org/toop/app/widget/Primitive.java | 3 +- .../app/widget/complex/LoadingWidget.java | 28 +++++++---- .../localization/localization_ar.properties | 0 .../localization/localization_de.properties | 0 .../localization/localization_en.properties | 0 .../localization/localization_es.properties | 0 .../localization/localization_fr.properties | 0 .../localization/localization_hi.properties | 0 .../localization/localization_it.properties | 0 .../localization/localization_ja.properties | 0 .../localization/localization_ko.properties | 0 .../localization/localization_nl.properties | 0 .../localization/localization_ru.properties | 0 .../localization/localization_zh.properties | 0 17 files changed, 68 insertions(+), 26 deletions(-) rename app/src/main/resources/{assets => }/localization/localization_ar.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_de.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_en.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_es.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_fr.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_hi.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_it.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_ja.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_ko.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_nl.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_ru.properties (100%) rename app/src/main/resources/{assets => }/localization/localization_zh.properties (100%) diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index fa04961..ac0989c 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -13,11 +13,10 @@ public final class Main { static void main(String[] args) { initSystems(); App.run(args); - } private static void initSystems() { - ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/localization")); new Thread(() -> new NetworkingClientEventListener(new NetworkingClientManager())).start(); new Thread(() -> { diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 7b909ba..e939629 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -1,12 +1,17 @@ package org.toop.app; +import org.toop.app.widget.Primitive; +import org.toop.app.widget.Widget; import org.toop.app.widget.WidgetContainer; +import org.toop.app.widget.complex.LoadingWidget; import org.toop.app.widget.display.SongDisplay; import org.toop.app.widget.popup.QuitPopup; import org.toop.app.widget.view.MainView; import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceManager; +import org.toop.framework.resource.events.AssetLoaderEvents; import org.toop.framework.resource.resources.CssAsset; import org.toop.local.AppContext; import org.toop.local.AppSettings; @@ -47,7 +52,7 @@ public final class App extends Application { stage.setMinHeight(720); stage.setOnCloseRequest(event -> { event.consume(); - startQuit(); + quit(); }); stage.setScene(scene); @@ -63,11 +68,44 @@ public final class App extends Application { App.isQuitting = false; - AppSettings.applySettings(); - new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); + var loading = new LoadingWidget(Primitive.text( + "Loading...", false), 0, 0, 9999 + ); - WidgetContainer.add(Pos.CENTER, new MainView()); - WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); + WidgetContainer.add(Pos.CENTER, loading); + + loading.setOnSuccess(() -> { + AppSettings.applySettings(); + loading.hide(); + WidgetContainer.add(Pos.CENTER, new MainView()); + WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); + stage.setOnCloseRequest(event -> { + event.consume(); + startQuit(); + }); + }); + + var loadingFlow = new EventFlow(); + loadingFlow + .listen(AssetLoaderEvents.LoadingProgressUpdate.class, e -> { + + loading.setMaxAmount(e.isLoadingAmount()-1); + + try { + loading.setAmount(e.hasLoadedAmount()); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + if (e.hasLoadedAmount() >= e.isLoadingAmount()-1) { + loading.triggerSuccess(); + loadingFlow.unsubscribe("initloading"); + } + + }, false, "initloading"); + + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); + new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); } public static void startQuit() { diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index f28e7f3..87df0e1 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -99,16 +99,9 @@ public final class Server { a.onResponse(NetworkEvents.StartClientResponse.class, e -> { if (!e.successful()) { -// loading.triggerFailure(); return; } - try { - TimeUnit.MILLISECONDS.sleep(500); // TODO temp fix for index bug - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - WidgetContainer.getCurrentView().transitionPrevious(); a.unsubscribe("connecting"); @@ -131,6 +124,9 @@ public final class Server { () -> { try { loading.setAmount(e.amount()); + if (e.amount() >= loading.getMaxAmount()) { + loading.triggerFailure(); + } } catch (Exception ex) { throw new RuntimeException(ex); } @@ -253,7 +249,7 @@ public final class Server { } else { stopScheduler(); } - }, 0, 5, TimeUnit.SECONDS); + }, 0, 1, TimeUnit.SECONDS); } private void stopScheduler() { diff --git a/app/src/main/java/org/toop/app/widget/Primitive.java b/app/src/main/java/org/toop/app/widget/Primitive.java index 451aebe..4cb9c17 100644 --- a/app/src/main/java/org/toop/app/widget/Primitive.java +++ b/app/src/main/java/org/toop/app/widget/Primitive.java @@ -23,7 +23,7 @@ import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.util.StringConverter; -public final class Primitive { +public final class Primitive { public static Text header(String key, boolean localize) { var header = new Text(); header.getStyleClass().add("header"); @@ -78,7 +78,6 @@ public final class Primitive { button.setOnAction(_ -> { onAction.run(); playButtonSound(); - System.out.println("HI I got called button"); }); } diff --git a/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java b/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java index 3b97357..14361f5 100644 --- a/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java @@ -1,5 +1,6 @@ package org.toop.app.widget.complex; +import javafx.application.Platform; import javafx.geometry.Pos; import javafx.scene.control.ProgressBar; import javafx.scene.layout.VBox; @@ -7,6 +8,7 @@ import javafx.scene.text.Text; import org.toop.app.widget.Primitive; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; public class LoadingWidget extends ViewWidget implements Update { // TODO make of widget type private final ProgressBar progressBar; @@ -14,8 +16,8 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o private Runnable success = () -> {}; private Runnable failure = () -> {}; - private boolean successTriggered = false; - private boolean failureTriggered = false; + private AtomicBoolean successTriggered = new AtomicBoolean(false); + private AtomicBoolean failureTriggered = new AtomicBoolean(false); private int maxAmount; private int minAmount; private int amount; @@ -66,7 +68,7 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o } public boolean isTriggered() { - return (failureTriggered || successTriggered); + return (failureTriggered.get() || successTriggered.get()); } /** @@ -105,21 +107,27 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o * Forcefully trigger success. */ public void triggerSuccess() { - successTriggered = true; // TODO, else it will double call... why? - success.run(); + if (successTriggered.compareAndSet(false, true)) { + Platform.runLater(() -> { + if (success != null) success.run(); + }); + } } /** * Forcefully trigger failure. */ public void triggerFailure() { - failureTriggered = true; // TODO, else it will double call... why? - failure.run(); + if (failureTriggered.compareAndSet(false, true)) { + Platform.runLater(() -> { + if (failure != null) failure.run(); + }); + } } @Override public void update() throws Exception { // TODO Better exception - if (successTriggered || failureTriggered) { // If already triggered, throw exception. + if (successTriggered.get() || failureTriggered.get()) { // If already triggered, throw exception. throw new RuntimeException(); } @@ -133,7 +141,9 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o return; } - percentage = (float) amount / maxAmount; + if (maxAmount != 0) { + percentage = (float) amount / maxAmount; + } progressBar.setProgress(percentage); } diff --git a/app/src/main/resources/assets/localization/localization_ar.properties b/app/src/main/resources/localization/localization_ar.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_ar.properties rename to app/src/main/resources/localization/localization_ar.properties diff --git a/app/src/main/resources/assets/localization/localization_de.properties b/app/src/main/resources/localization/localization_de.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_de.properties rename to app/src/main/resources/localization/localization_de.properties diff --git a/app/src/main/resources/assets/localization/localization_en.properties b/app/src/main/resources/localization/localization_en.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_en.properties rename to app/src/main/resources/localization/localization_en.properties diff --git a/app/src/main/resources/assets/localization/localization_es.properties b/app/src/main/resources/localization/localization_es.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_es.properties rename to app/src/main/resources/localization/localization_es.properties diff --git a/app/src/main/resources/assets/localization/localization_fr.properties b/app/src/main/resources/localization/localization_fr.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_fr.properties rename to app/src/main/resources/localization/localization_fr.properties diff --git a/app/src/main/resources/assets/localization/localization_hi.properties b/app/src/main/resources/localization/localization_hi.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_hi.properties rename to app/src/main/resources/localization/localization_hi.properties diff --git a/app/src/main/resources/assets/localization/localization_it.properties b/app/src/main/resources/localization/localization_it.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_it.properties rename to app/src/main/resources/localization/localization_it.properties diff --git a/app/src/main/resources/assets/localization/localization_ja.properties b/app/src/main/resources/localization/localization_ja.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_ja.properties rename to app/src/main/resources/localization/localization_ja.properties diff --git a/app/src/main/resources/assets/localization/localization_ko.properties b/app/src/main/resources/localization/localization_ko.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_ko.properties rename to app/src/main/resources/localization/localization_ko.properties diff --git a/app/src/main/resources/assets/localization/localization_nl.properties b/app/src/main/resources/localization/localization_nl.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_nl.properties rename to app/src/main/resources/localization/localization_nl.properties diff --git a/app/src/main/resources/assets/localization/localization_ru.properties b/app/src/main/resources/localization/localization_ru.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_ru.properties rename to app/src/main/resources/localization/localization_ru.properties diff --git a/app/src/main/resources/assets/localization/localization_zh.properties b/app/src/main/resources/localization/localization_zh.properties similarity index 100% rename from app/src/main/resources/assets/localization/localization_zh.properties rename to app/src/main/resources/localization/localization_zh.properties From 740d2cf3db9f35f941653e912c46ab5e94c971a5 Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:13:57 +0100 Subject: [PATCH 2/4] Fixed systems starting, before assets being loaded (I am retarded) --- app/src/main/java/org/toop/Main.java | 24 -------- app/src/main/java/org/toop/app/App.java | 73 +++++++++++++++++++------ 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/toop/Main.java b/app/src/main/java/org/toop/Main.java index ac0989c..3115e34 100644 --- a/app/src/main/java/org/toop/Main.java +++ b/app/src/main/java/org/toop/Main.java @@ -11,30 +11,6 @@ import org.toop.framework.resource.resources.SoundEffectAsset; public final class Main { static void main(String[] args) { - initSystems(); App.run(args); } - - private static void initSystems() { - ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/localization")); - new Thread(() -> new NetworkingClientEventListener(new NetworkingClientManager())).start(); - - new Thread(() -> { - MusicManager musicManager = new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true); - SoundEffectManager soundEffectManager = new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); - AudioVolumeManager audioVolumeManager = new AudioVolumeManager() - .registerManager(VolumeControl.MASTERVOLUME, musicManager) - .registerManager(VolumeControl.MASTERVOLUME, soundEffectManager) - .registerManager(VolumeControl.FX, soundEffectManager) - .registerManager(VolumeControl.MUSIC, musicManager); - - new AudioEventListener<>( - musicManager, - soundEffectManager, - audioVolumeManager - ).initListeners("medium-button-click.wav"); - - }).start(); - } - } diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index e939629..9bdda8f 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -1,5 +1,6 @@ package org.toop.app; +import org.toop.Main; import org.toop.app.widget.Primitive; import org.toop.app.widget.Widget; import org.toop.app.widget.WidgetContainer; @@ -7,12 +8,17 @@ import org.toop.app.widget.complex.LoadingWidget; import org.toop.app.widget.display.SongDisplay; import org.toop.app.widget.popup.QuitPopup; import org.toop.app.widget.view.MainView; +import org.toop.framework.audio.*; import org.toop.framework.audio.events.AudioEvents; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.networking.NetworkingClientEventListener; +import org.toop.framework.networking.NetworkingClientManager; import org.toop.framework.resource.ResourceLoader; import org.toop.framework.resource.ResourceManager; import org.toop.framework.resource.events.AssetLoaderEvents; import org.toop.framework.resource.resources.CssAsset; +import org.toop.framework.resource.resources.MusicAsset; +import org.toop.framework.resource.resources.SoundEffectAsset; import org.toop.local.AppContext; import org.toop.local.AppSettings; @@ -37,6 +43,9 @@ public final class App extends Application { @Override public void start(Stage stage) throws Exception { + // Start loading localization + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/localization")); + final StackPane root = WidgetContainer.setup(); final Scene scene = new Scene(root); @@ -68,27 +77,17 @@ public final class App extends Application { App.isQuitting = false; - var loading = new LoadingWidget(Primitive.text( - "Loading...", false), 0, 0, 9999 + LoadingWidget loading = new LoadingWidget(Primitive.text( + "Loading...", false), 0, 0, Integer.MAX_VALUE // Just set a high default ); WidgetContainer.add(Pos.CENTER, loading); - loading.setOnSuccess(() -> { - AppSettings.applySettings(); - loading.hide(); - WidgetContainer.add(Pos.CENTER, new MainView()); - WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); - stage.setOnCloseRequest(event -> { - event.consume(); - startQuit(); - }); - }); + setOnLoadingSuccess(loading); - var loadingFlow = new EventFlow(); + EventFlow loadingFlow = new EventFlow(); loadingFlow .listen(AssetLoaderEvents.LoadingProgressUpdate.class, e -> { - loading.setMaxAmount(e.isLoadingAmount()-1); try { @@ -99,13 +98,53 @@ public final class App extends Application { if (e.hasLoadedAmount() >= e.isLoadingAmount()-1) { loading.triggerSuccess(); - loadingFlow.unsubscribe("initloading"); + loadingFlow.unsubscribe("init_loading"); } - }, false, "initloading"); + }, false, "init_loading"); + // Start loading assets ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); - new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); + } + + private void setOnLoadingSuccess(LoadingWidget loading) { + loading.setOnSuccess(() -> { + initSystems(); + AppSettings.applySettings(); + new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); + loading.hide(); + WidgetContainer.add(Pos.CENTER, new MainView()); + WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); + stage.setOnCloseRequest(event -> { + event.consume(); + startQuit(); + }); + }); + } + + private void initSystems() { // TODO Move to better place + new Thread(() -> new NetworkingClientEventListener(new NetworkingClientManager())).start(); + + new Thread(() -> { + MusicManager musicManager = + new MusicManager<>(ResourceManager.getAllOfTypeAndRemoveWrapper(MusicAsset.class), true); + + SoundEffectManager soundEffectManager = + new SoundEffectManager<>(ResourceManager.getAllOfType(SoundEffectAsset.class)); + + AudioVolumeManager audioVolumeManager = new AudioVolumeManager() + .registerManager(VolumeControl.MASTERVOLUME, musicManager) + .registerManager(VolumeControl.MASTERVOLUME, soundEffectManager) + .registerManager(VolumeControl.FX, soundEffectManager) + .registerManager(VolumeControl.MUSIC, musicManager); + + new AudioEventListener<>( + musicManager, + soundEffectManager, + audioVolumeManager + ).initListeners("medium-button-click.wav"); + + }).start(); } public static void startQuit() { From 040287ad70b87271a5d606036557ca35d4821eda Mon Sep 17 00:00:00 2001 From: lieght <49651652+BAFGdeJong@users.noreply.github.com> Date: Wed, 3 Dec 2025 18:56:08 +0100 Subject: [PATCH 3/4] Added infinite boolean, fixed loading behaviour at startup --- app/src/main/java/org/toop/app/App.java | 30 ++++++++++++------- app/src/main/java/org/toop/app/Server.java | 4 +-- .../java/org/toop/app/widget/Primitive.java | 2 -- .../app/widget/complex/LoadingWidget.java | 17 ++++++----- .../main/java/org/toop/local/AppSettings.java | 24 +++++++++------ .../resources/{assets => }/style/dark.css | 0 .../resources/{assets => }/style/general.css | 0 .../{assets => }/style/high-contrast.css | 0 .../resources/{assets => }/style/large.css | 0 .../resources/{assets => }/style/light.css | 0 .../resources/{assets => }/style/medium.css | 0 .../resources/{assets => }/style/small.css | 0 12 files changed, 47 insertions(+), 30 deletions(-) rename app/src/main/resources/{assets => }/style/dark.css (100%) rename app/src/main/resources/{assets => }/style/general.css (100%) rename app/src/main/resources/{assets => }/style/high-contrast.css (100%) rename app/src/main/resources/{assets => }/style/large.css (100%) rename app/src/main/resources/{assets => }/style/light.css (100%) rename app/src/main/resources/{assets => }/style/medium.css (100%) rename app/src/main/resources/{assets => }/style/small.css (100%) diff --git a/app/src/main/java/org/toop/app/App.java b/app/src/main/java/org/toop/app/App.java index 9bdda8f..4b4cf24 100644 --- a/app/src/main/java/org/toop/app/App.java +++ b/app/src/main/java/org/toop/app/App.java @@ -45,6 +45,7 @@ public final class App extends Application { public void start(Stage stage) throws Exception { // Start loading localization ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/localization")); + ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/style")); final StackPane root = WidgetContainer.setup(); final Scene scene = new Scene(root); @@ -67,8 +68,6 @@ public final class App extends Application { stage.setScene(scene); stage.setResizable(true); - stage.show(); - App.stage = stage; App.scene = scene; @@ -77,8 +76,10 @@ public final class App extends Application { App.isQuitting = false; + AppSettings.applySettings(); + LoadingWidget loading = new LoadingWidget(Primitive.text( - "Loading...", false), 0, 0, Integer.MAX_VALUE // Just set a high default + "Loading...", false), 0, 0, Integer.MAX_VALUE, false // Just set a high default ); WidgetContainer.add(Pos.CENTER, loading); @@ -88,7 +89,7 @@ public final class App extends Application { EventFlow loadingFlow = new EventFlow(); loadingFlow .listen(AssetLoaderEvents.LoadingProgressUpdate.class, e -> { - loading.setMaxAmount(e.isLoadingAmount()-1); + loading.setMaxAmount(e.isLoadingAmount()); try { loading.setAmount(e.hasLoadedAmount()); @@ -96,7 +97,7 @@ public final class App extends Application { throw new RuntimeException(ex); } - if (e.hasLoadedAmount() >= e.isLoadingAmount()-1) { + if (e.hasLoadedAmount() >= e.isLoadingAmount()) { loading.triggerSuccess(); loadingFlow.unsubscribe("init_loading"); } @@ -104,15 +105,17 @@ public final class App extends Application { }, false, "init_loading"); // Start loading assets - ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets")); + new Thread(() -> ResourceManager.loadAssets(new ResourceLoader("app/src/main/resources/assets"))) + .start(); + stage.show(); } private void setOnLoadingSuccess(LoadingWidget loading) { loading.setOnSuccess(() -> { initSystems(); - AppSettings.applySettings(); - new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).asyncPostEvent(); - loading.hide(); + AppSettings.applyMusicVolumeSettings(); + new EventFlow().addPostEvent(new AudioEvents.StartBackgroundMusic()).postEvent(); + loading.hide(); WidgetContainer.add(Pos.CENTER, new MainView()); WidgetContainer.add(Pos.BOTTOM_RIGHT, new SongDisplay()); stage.setOnCloseRequest(event -> { @@ -145,7 +148,14 @@ public final class App extends Application { ).initListeners("medium-button-click.wav"); }).start(); - } + + // Threads must be ready, before continue, TODO use latch instead + try { + Thread.sleep(200); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } public static void startQuit() { if (isQuitting) { diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 87df0e1..b6bdab0 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -73,10 +73,10 @@ public final class Server { return; } - final int reconnectAttempts = 5; + final int reconnectAttempts = 10; LoadingWidget loading = new LoadingWidget( - Primitive.text("connecting"), 0, 0, reconnectAttempts + Primitive.text("connecting"), 0, 0, reconnectAttempts, true ); WidgetContainer.getCurrentView().transitionNext(loading); diff --git a/app/src/main/java/org/toop/app/widget/Primitive.java b/app/src/main/java/org/toop/app/widget/Primitive.java index 4cb9c17..0b721fc 100644 --- a/app/src/main/java/org/toop/app/widget/Primitive.java +++ b/app/src/main/java/org/toop/app/widget/Primitive.java @@ -127,7 +127,6 @@ public final class Primitive { slider.setOnMouseReleased(event -> { playButtonSound(); - System.out.println("I got called!"); }); return slider; @@ -150,7 +149,6 @@ public final class Primitive { choice.valueProperty().addListener((_, _, newValue) -> { onValueChanged.accept(newValue); playButtonSound(); - System.out.println("hi i got called choice"); }); } diff --git a/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java b/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java index 14361f5..e0cff14 100644 --- a/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java +++ b/app/src/main/java/org/toop/app/widget/complex/LoadingWidget.java @@ -11,13 +11,13 @@ import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; public class LoadingWidget extends ViewWidget implements Update { // TODO make of widget type + private final Text loadingText; // TODO Make changeable private final ProgressBar progressBar; - private final Text loadingText; + private final AtomicBoolean successTriggered = new AtomicBoolean(false); + private final AtomicBoolean failureTriggered = new AtomicBoolean(false); private Runnable success = () -> {}; private Runnable failure = () -> {}; - private AtomicBoolean successTriggered = new AtomicBoolean(false); - private AtomicBoolean failureTriggered = new AtomicBoolean(false); private int maxAmount; private int minAmount; private int amount; @@ -25,6 +25,8 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o private Callable failureTrigger = () -> (amount < minAmount); private float percentage = 0.0f; + private boolean isInfiniteBar = false; + /** * * Widget that shows a loading bar. @@ -34,13 +36,15 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o * @param startAmount The starting amount. * @param maxAmount The max amount. */ - public LoadingWidget(Text loadingText, int minAmount, int startAmount, int maxAmount) { + public LoadingWidget(Text loadingText, int minAmount, int startAmount, int maxAmount, boolean infiniteBar) { + isInfiniteBar = infiniteBar; + this.maxAmount = maxAmount; this.minAmount = minAmount; amount = startAmount; - progressBar = new ProgressBar(); this.loadingText = loadingText; + progressBar = new ProgressBar(); VBox box = Primitive.vbox(this.loadingText, progressBar); add(Pos.CENTER, box); @@ -144,7 +148,6 @@ public class LoadingWidget extends ViewWidget implements Update { // TODO make o if (maxAmount != 0) { percentage = (float) amount / maxAmount; } - progressBar.setProgress(percentage); - + if (!isInfiniteBar) progressBar.setProgress(percentage); } } diff --git a/app/src/main/java/org/toop/local/AppSettings.java b/app/src/main/java/org/toop/local/AppSettings.java index 375b0a3..7f5493b 100644 --- a/app/src/main/java/org/toop/local/AppSettings.java +++ b/app/src/main/java/org/toop/local/AppSettings.java @@ -27,18 +27,24 @@ public class AppSettings { AppContext.setLocale(Locale.of(settingsData.locale)); App.setFullscreen(settingsData.fullScreen); - new EventFlow() - .addPostEvent(new AudioEvents.ChangeVolume(settingsData.volume, VolumeControl.MASTERVOLUME)) - .asyncPostEvent(); - new EventFlow() - .addPostEvent(new AudioEvents.ChangeVolume(settingsData.fxVolume, VolumeControl.FX)) - .asyncPostEvent(); - new EventFlow() - .addPostEvent(new AudioEvents.ChangeVolume(settingsData.musicVolume, VolumeControl.MUSIC)) - .asyncPostEvent(); + App.setStyle(settingsAsset.getTheme(), settingsAsset.getLayoutSize()); } + public static void applyMusicVolumeSettings() { + Settings settingsData = settingsAsset.getContent(); + + 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(); + } + public static SettingsAsset getPath() { if (settingsAsset == null) { String os = System.getProperty("os.name").toLowerCase(); diff --git a/app/src/main/resources/assets/style/dark.css b/app/src/main/resources/style/dark.css similarity index 100% rename from app/src/main/resources/assets/style/dark.css rename to app/src/main/resources/style/dark.css diff --git a/app/src/main/resources/assets/style/general.css b/app/src/main/resources/style/general.css similarity index 100% rename from app/src/main/resources/assets/style/general.css rename to app/src/main/resources/style/general.css diff --git a/app/src/main/resources/assets/style/high-contrast.css b/app/src/main/resources/style/high-contrast.css similarity index 100% rename from app/src/main/resources/assets/style/high-contrast.css rename to app/src/main/resources/style/high-contrast.css diff --git a/app/src/main/resources/assets/style/large.css b/app/src/main/resources/style/large.css similarity index 100% rename from app/src/main/resources/assets/style/large.css rename to app/src/main/resources/style/large.css diff --git a/app/src/main/resources/assets/style/light.css b/app/src/main/resources/style/light.css similarity index 100% rename from app/src/main/resources/assets/style/light.css rename to app/src/main/resources/style/light.css diff --git a/app/src/main/resources/assets/style/medium.css b/app/src/main/resources/style/medium.css similarity index 100% rename from app/src/main/resources/assets/style/medium.css rename to app/src/main/resources/style/medium.css diff --git a/app/src/main/resources/assets/style/small.css b/app/src/main/resources/style/small.css similarity index 100% rename from app/src/main/resources/assets/style/small.css rename to app/src/main/resources/style/small.css From 8d77f513551d8e3ec7e2cab687961f07f445c64b Mon Sep 17 00:00:00 2001 From: Stef <48526421+StefBuwalda@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:35:37 +0100 Subject: [PATCH 4/4] 272 remake game framework interfaces to properly represent vmc (#278) * Cleaned up a lot of old files and renamed/remade interfaces to better suit the framework * Broken commit * Fixed online play * Better file structure and closer to MVC --- .../java/org/toop/app/GameInformation.java | 4 +- app/src/main/java/org/toop/app/Server.java | 91 ++++- .../org/toop/app/canvas/Connect4Canvas.java | 4 +- .../org/toop/app/canvas/DrawPlayerHover.java | 4 +- .../java/org/toop/app/canvas/GameCanvas.java | 4 +- .../org/toop/app/canvas/ReversiCanvas.java | 8 +- .../org/toop/app/canvas/TicTacToeCanvas.java | 6 +- .../org/toop/app/game/BaseGameThread.java | 122 ------- .../java/org/toop/app/game/Connect4Game.java | 265 -------------- .../java/org/toop/app/game/ReversiGame.java | 333 ------------------ .../java/org/toop/app/game/TicTacToeGame.java | 250 ------------- .../toop/app/game/TicTacToeGameThread.java | 176 --------- .../AbstractGameController.java | 50 ++- .../gameControllers/ReversiController.java | 24 +- .../gameControllers/TicTacToeController.java | 24 +- .../app/widget/view/LocalMultiplayerView.java | 76 ++-- .../org/toop/app/widget/view/LocalView.java | 7 +- .../gameFramework/abstractClasses/GameR.java | 127 ------- .../abstractClasses/TurnBasedGameR.java | 31 -- .../UpdatesGameUI.java | 2 +- .../gameFramework/interfaces/IAIMoveR.java | 20 -- .../model/game/AbstractGame.java | 108 ++++++ .../model/game/DeepCopyable.java | 5 + .../{ => model/game}/PlayResult.java | 2 +- .../game/Playable.java} | 5 +- .../model/game/PlayerProvider.java | 7 + .../game}/SupportsOnlinePlay.java | 6 +- .../model/game/TurnBasedGame.java | 5 + .../AbstractThreadBehaviour.java | 36 ++ .../game/threadBehaviour/Controllable.java | 7 + .../game/threadBehaviour/ThreadBehaviour.java | 13 + .../AIR.java => model/player/AbstractAI.java} | 6 +- .../model/player}/AbstractPlayer.java | 25 +- .../model/player/MoveProvider.java | 7 + .../model/player/NameProvider.java | 5 + .../gameFramework/model/player/Player.java | 6 + .../gameFramework/{ => view}/GUIEvents.java | 2 +- game/src/main/java/org/toop/game/AI.java | 6 - .../java/org/toop/game/Connect4/Connect4.java | 116 ------ .../org/toop/game/Connect4/Connect4AI.java | 63 ---- game/src/main/java/org/toop/game/Game.java | 40 --- .../GameThreadStrategy.java | 24 -- .../ThreadBehaviourBase.java | 57 --- game/src/main/java/org/toop/game/Move.java | 3 + .../java/org/toop/game/TurnBasedGame.java | 25 -- .../LocalFixedRateThreadBehaviour.java | 29 +- .../LocalThreadBehaviour.java | 21 +- .../OnlineThreadBehaviour.java | 45 +-- .../OnlineWithSleepThreadBehaviour.java | 12 +- .../toop/game/games/reversi/ReversiAIR.java | 15 + .../game/{ => games}/reversi/ReversiR.java | 33 +- .../{ => games}/tictactoe/TicTacToeAIR.java | 15 +- .../{ => games}/tictactoe/TicTacToeR.java | 18 +- .../org/toop/game/interfaces/IAIMove.java | 8 - .../org/toop/game/interfaces/IPlayable.java | 9 - .../toop/game/players/ArtificialPlayer.java | 19 +- .../org/toop/game/players/LocalPlayer.java | 15 +- .../java/org/toop/game/players/MakesMove.java | 23 -- .../org/toop/game/players/OnlinePlayer.java | 5 +- .../main/java/org/toop/game/records/Move.java | 3 - .../java/org/toop/game/reversi/Reversi.java | 229 ------------ .../java/org/toop/game/reversi/ReversiAI.java | 14 - .../org/toop/game/reversi/ReversiAIR.java | 17 - .../org/toop/game/tictactoe/TicTacToe.java | 103 ------ .../org/toop/game/tictactoe/TicTacToeAI.java | 87 ----- .../org/toop/game/tictactoe/ReversiTest.java | 193 ---------- .../toop/game/tictactoe/TicTacToeAIRTest.java | 25 +- .../toop/game/tictactoe/TicTacToeAITest.java | 81 ----- 68 files changed, 524 insertions(+), 2702 deletions(-) delete mode 100644 app/src/main/java/org/toop/app/game/BaseGameThread.java delete mode 100644 app/src/main/java/org/toop/app/game/Connect4Game.java delete mode 100644 app/src/main/java/org/toop/app/game/ReversiGame.java delete mode 100644 app/src/main/java/org/toop/app/game/TicTacToeGame.java delete mode 100644 app/src/main/java/org/toop/app/game/TicTacToeGameThread.java rename app/src/main/java/org/toop/app/{game => }/gameControllers/AbstractGameController.java (64%) rename app/src/main/java/org/toop/app/{game => }/gameControllers/ReversiController.java (85%) rename app/src/main/java/org/toop/app/{game => }/gameControllers/TicTacToeController.java (73%) delete mode 100644 framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/GameR.java delete mode 100644 framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/TurnBasedGameR.java rename framework/src/main/java/org/toop/framework/gameFramework/{interfaces => controller}/UpdatesGameUI.java (76%) delete mode 100644 framework/src/main/java/org/toop/framework/gameFramework/interfaces/IAIMoveR.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/DeepCopyable.java rename framework/src/main/java/org/toop/framework/gameFramework/{ => model/game}/PlayResult.java (86%) rename framework/src/main/java/org/toop/framework/gameFramework/{interfaces/IPlayableR.java => model/game/Playable.java} (80%) create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayerProvider.java rename framework/src/main/java/org/toop/framework/gameFramework/{interfaces => model/game}/SupportsOnlinePlay.java (74%) create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/Controllable.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java rename framework/src/main/java/org/toop/framework/gameFramework/{abstractClasses/AIR.java => model/player/AbstractAI.java} (71%) rename {game/src/main/java/org/toop/game/players => framework/src/main/java/org/toop/framework/gameFramework/model/player}/AbstractPlayer.java (70%) create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/player/NameProvider.java create mode 100644 framework/src/main/java/org/toop/framework/gameFramework/model/player/Player.java rename framework/src/main/java/org/toop/framework/gameFramework/{ => view}/GUIEvents.java (95%) delete mode 100644 game/src/main/java/org/toop/game/AI.java delete mode 100644 game/src/main/java/org/toop/game/Connect4/Connect4.java delete mode 100644 game/src/main/java/org/toop/game/Connect4/Connect4AI.java delete mode 100644 game/src/main/java/org/toop/game/Game.java delete mode 100644 game/src/main/java/org/toop/game/GameThreadBehaviour/GameThreadStrategy.java delete mode 100644 game/src/main/java/org/toop/game/GameThreadBehaviour/ThreadBehaviourBase.java create mode 100644 game/src/main/java/org/toop/game/Move.java delete mode 100644 game/src/main/java/org/toop/game/TurnBasedGame.java rename game/src/main/java/org/toop/game/{GameThreadBehaviour => gameThreads}/LocalFixedRateThreadBehaviour.java (75%) rename game/src/main/java/org/toop/game/{GameThreadBehaviour => gameThreads}/LocalThreadBehaviour.java (72%) rename game/src/main/java/org/toop/game/{GameThreadBehaviour => gameThreads}/OnlineThreadBehaviour.java (61%) rename game/src/main/java/org/toop/game/{GameThreadBehaviour => gameThreads}/OnlineWithSleepThreadBehaviour.java (72%) create mode 100644 game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java rename game/src/main/java/org/toop/game/{ => games}/reversi/ReversiR.java (94%) rename game/src/main/java/org/toop/game/{ => games}/tictactoe/TicTacToeAIR.java (90%) rename game/src/main/java/org/toop/game/{ => games}/tictactoe/TicTacToeR.java (87%) delete mode 100644 game/src/main/java/org/toop/game/interfaces/IAIMove.java delete mode 100644 game/src/main/java/org/toop/game/interfaces/IPlayable.java delete mode 100644 game/src/main/java/org/toop/game/players/MakesMove.java delete mode 100644 game/src/main/java/org/toop/game/records/Move.java delete mode 100644 game/src/main/java/org/toop/game/reversi/Reversi.java delete mode 100644 game/src/main/java/org/toop/game/reversi/ReversiAI.java delete mode 100644 game/src/main/java/org/toop/game/reversi/ReversiAIR.java delete mode 100644 game/src/main/java/org/toop/game/tictactoe/TicTacToe.java delete mode 100644 game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java delete mode 100644 game/src/test/java/org/toop/game/tictactoe/ReversiTest.java delete mode 100644 game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java diff --git a/app/src/main/java/org/toop/app/GameInformation.java b/app/src/main/java/org/toop/app/GameInformation.java index d8f8390..08e3f46 100644 --- a/app/src/main/java/org/toop/app/GameInformation.java +++ b/app/src/main/java/org/toop/app/GameInformation.java @@ -3,9 +3,7 @@ package org.toop.app; public class GameInformation { public enum Type { TICTACTOE(2, 5), - REVERSI(2, 10), - CONNECT4(2, 7), - BATTLESHIP(2, 5); + REVERSI(2, 10); private final int playerCount; private final int maxDepth; diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index b6bdab0..4b149d2 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -2,9 +2,9 @@ package org.toop.app; import javafx.application.Platform; import javafx.geometry.Pos; -import org.toop.app.game.Connect4Game; -import org.toop.app.game.ReversiGame; -import org.toop.app.game.TicTacToeGame; +import org.toop.app.gameControllers.AbstractGameController; +import org.toop.app.gameControllers.ReversiController; +import org.toop.app.gameControllers.TicTacToeController; import org.toop.app.widget.Primitive; import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.complex.LoadingWidget; @@ -13,9 +13,15 @@ import org.toop.app.widget.popup.ErrorPopup; import org.toop.app.widget.popup.SendChallengePopup; import org.toop.app.widget.view.ServerView; import org.toop.framework.eventbus.EventFlow; +import org.toop.framework.gameFramework.model.player.Player; import org.toop.framework.networking.clients.TournamentNetworkingClient; import org.toop.framework.networking.events.NetworkEvents; import org.toop.framework.networking.types.NetworkingConnector; +import org.toop.game.players.ArtificialPlayer; +import org.toop.game.players.OnlinePlayer; +import org.toop.game.games.reversi.ReversiAIR; +import org.toop.game.games.reversi.ReversiR; +import org.toop.game.games.tictactoe.TicTacToeAIR; import org.toop.local.AppContext; import java.util.List; @@ -26,6 +32,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public final class Server { + // TODO: Keep track of listeners. Remove them on Server connection close so reference is deleted. private String user = ""; private long clientId = -1; @@ -35,24 +42,27 @@ public final class Server { private ServerView primary; private boolean isPolling = true; + private AbstractGameController gameController; + private final AtomicBoolean isSingleGame = new AtomicBoolean(false); private ScheduledExecutorService scheduler; + private EventFlow eventFlow = new EventFlow(); + public static GameInformation.Type gameToType(String game) { if (game.equalsIgnoreCase("tic-tac-toe")) { return GameInformation.Type.TICTACTOE; } else if (game.equalsIgnoreCase("reversi")) { return GameInformation.Type.REVERSI; - } else if (game.equalsIgnoreCase("connect4")) { - return GameInformation.Type.CONNECT4; -// } else if (game.equalsIgnoreCase("battleship")) { -// return GameInformation.Type.BATTLESHIP; } return null; } + + // Server has to deal with ALL network related listen events. This "server" can then interact with the manager to make stuff happen. + // This prevents data races where events get sent to the game manager but the manager isn't ready yet. public Server(String ip, String port, String user) { if (ip.split("\\.").length < 4) { new ErrorPopup("\"" + ip + "\" " + AppContext.getString("is-not-a-valid-ip-address")); @@ -136,9 +146,13 @@ public final class Server { ) .postEvent(); - new EventFlow() - .listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false) - .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false); + eventFlow.listen(NetworkEvents.ChallengeResponse.class, this::handleReceivedChallenge, false) + .listen(NetworkEvents.GameMatchResponse.class, this::handleMatchResponse, false) + .listen(NetworkEvents.GameResultResponse.class, this::handleGameResult, false) + .listen(NetworkEvents.GameMoveResponse.class, this::handleReceivedMove, false) + .listen(NetworkEvents.YourTurnResponse.class, this::handleYourTurn, false); + startPopulateScheduler(); + populateGameList(); } private void sendChallenge(String opponent) { @@ -151,10 +165,16 @@ public final class Server { } private void handleMatchResponse(NetworkEvents.GameMatchResponse response) { - if (!isPolling) return; + // TODO: Redo all of this mess + if (gameController != null) { + gameController.stop(); + } + + gameController = null; + + //if (!isPolling) return; String gameType = extractQuotedValue(response.gameType()); - if (response.clientId() == clientId) { isPolling = false; onlinePlayers.clear(); @@ -175,21 +195,56 @@ public final class Server { information.players[0].computerThinkTime = 1; information.players[1].name = response.opponent(); - Runnable onGameOverRunnable = isSingleGame.get()? null: this::gameOver; + Player[] players = new Player[2]; + players[(myTurn + 1) % 2] = new OnlinePlayer(response.opponent()); + + switch (type){ + case TICTACTOE ->{ + players[myTurn] = new ArtificialPlayer<>(new TicTacToeAIR(), user); + } + case REVERSI ->{ + players[myTurn] = new ArtificialPlayer<>(new ReversiAIR(), user); + } + } switch (type) { - case TICTACTOE -> - new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + case TICTACTOE ->{ + gameController = new TicTacToeController(players, false); + } case REVERSI -> - new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); - case CONNECT4 -> - new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable); + gameController = new ReversiController(players, false); default -> new ErrorPopup("Unsupported game type."); } + + if (gameController != null){ + gameController.start(); + } } } + private void handleYourTurn(NetworkEvents.YourTurnResponse response) { + if (gameController == null) { + return; + } + gameController.onYourTurn(response); + + } + + private void handleGameResult(NetworkEvents.GameResultResponse response) { + if (gameController == null) { + return; + } + gameController.gameFinished(response); + } + + private void handleReceivedMove(NetworkEvents.GameMoveResponse response) { + if (gameController == null) { + return; + } + gameController.onMoveReceived(response); + } + private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) { if (!isPolling) return; diff --git a/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java b/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java index 117b5b2..4a8c7a2 100644 --- a/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java +++ b/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java @@ -1,7 +1,7 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.AbstractGame; import java.util.function.Consumer; @@ -11,7 +11,7 @@ public class Connect4Canvas extends GameCanvas { } @Override - public void drawPlayerHover(int player, int move, TurnBasedGameR game) { + public void drawPlayerHover(int player, int move, AbstractGame game) { } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java b/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java index 09e2372..0c40a25 100644 --- a/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java +++ b/app/src/main/java/org/toop/app/canvas/DrawPlayerHover.java @@ -1,7 +1,7 @@ package org.toop.app.canvas; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.AbstractGame; public interface DrawPlayerHover { - void drawPlayerHover(int player, int move, TurnBasedGameR game); + void drawPlayerHover(int player, int move, AbstractGame game); } diff --git a/app/src/main/java/org/toop/app/canvas/GameCanvas.java b/app/src/main/java/org/toop/app/canvas/GameCanvas.java index 7ff83da..0e78ab4 100644 --- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -8,11 +8,11 @@ import javafx.scene.input.MouseButton; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.util.Duration; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.AbstractGame; import java.util.function.Consumer; -public abstract class GameCanvas implements DrawPlayerMove, DrawPlayerHover { +public abstract class GameCanvas implements DrawPlayerMove, DrawPlayerHover { 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 && diff --git a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java index 10e034d..090df29 100644 --- a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java @@ -1,9 +1,9 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; -import org.toop.game.records.Move; -import org.toop.game.reversi.ReversiR; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.game.Move; +import org.toop.game.games.reversi.ReversiR; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -90,7 +90,7 @@ public final class ReversiCanvas extends GameCanvas { } @Override - public void drawPlayerHover(int player, int move, TurnBasedGameR game) { + public void drawPlayerHover(int player, int move, AbstractGame game) { } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java index 244751b..41d398f 100644 --- a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java @@ -1,8 +1,8 @@ package org.toop.app.canvas; import javafx.scene.paint.Color; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; -import org.toop.game.tictactoe.TicTacToeR; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.game.games.tictactoe.TicTacToeR; import java.util.function.Consumer; @@ -48,7 +48,7 @@ public final class TicTacToeCanvas extends GameCanvas { } @Override - public void drawPlayerHover(int player, int move, TurnBasedGameR game) { + public void drawPlayerHover(int player, int move, AbstractGame game) { } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/BaseGameThread.java b/app/src/main/java/org/toop/app/game/BaseGameThread.java deleted file mode 100644 index 632c0d8..0000000 --- a/app/src/main/java/org/toop/app/game/BaseGameThread.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.toop.app.game; - -import org.toop.app.GameInformation; -import org.toop.app.widget.WidgetContainer; -import org.toop.app.widget.view.GameView; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.game.Game; -import org.toop.game.records.Move; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -public abstract class BaseGameThread { - protected final GameInformation information; - protected final int myTurn; - protected final Runnable onGameOver; - protected final BlockingQueue moveQueue; - - protected final TGame game; - protected final TAI ai; - - protected final GameView primary; - protected final TCanvas canvas; - - protected final AtomicBoolean isRunning = new AtomicBoolean(true); - - protected BaseGameThread( - GameInformation information, - int myTurn, - Runnable onForfeit, - Runnable onExit, - Consumer onMessage, - Runnable onGameOver, - Supplier gameSupplier, - Supplier aiSupplier, - Function, TCanvas> canvasFactory) { - - this.information = information; - this.myTurn = myTurn; - this.onGameOver = onGameOver; - this.moveQueue = new LinkedBlockingQueue<>(); - - this.game = gameSupplier.get(); - this.ai = aiSupplier.get(); - - String type = information.type.getTypeToString(); - if (onForfeit == null || onExit == null) { - primary = new GameView(null, () -> { - isRunning.set(false); - WidgetContainer.getCurrentView().transitionPrevious(); - }, null, type); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, type); - } - - this.canvas = canvasFactory.apply(this::onCellClicked); - - addCanvasToPrimary(); - - WidgetContainer.getCurrentView().transitionNext(primary); - - if (onForfeit == null || onExit == null) - new Thread(this::localGameThread).start(); - else - new EventFlow() - .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false) - .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false); - - setGameLabels(myTurn == 0); - } - - private void onCellClicked(int cell) { - if (!isRunning.get()) return; - - final int currentTurn = getCurrentTurn(); - if (!information.players[currentTurn].isHuman) return; - - final char value = getSymbolForTurn(currentTurn); - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - - protected void gameOver() { - if (onGameOver != null) { - isRunning.set(false); - onGameOver.run(); - } - } - - protected void setGameLabels(boolean isMe) { - final int currentTurn = getCurrentTurn(); - final String turnName = getNameForTurn(currentTurn); - - primary.nextPlayer( - isMe, - information.players[isMe ? 0 : 1].name, - turnName, - information.players[isMe ? 1 : 0].name - ); - } - - protected abstract void addCanvasToPrimary(); - - protected abstract int getCurrentTurn(); - protected abstract char getSymbolForTurn(int turn); - protected abstract String getNameForTurn(int turn); - - protected abstract void onMoveResponse(NetworkEvents.GameMoveResponse response); - protected abstract void onYourTurnResponse(NetworkEvents.YourTurnResponse response); - - protected abstract void localGameThread(); -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/Connect4Game.java b/app/src/main/java/org/toop/app/game/Connect4Game.java deleted file mode 100644 index bab7715..0000000 --- a/app/src/main/java/org/toop/app/game/Connect4Game.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.toop.app.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.Connect4Canvas; -import org.toop.app.widget.view.GameView; -import org.toop.app.widget.WidgetContainer; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.game.Connect4.Connect4; -import org.toop.game.Connect4.Connect4AI; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -public class Connect4Game { - private final GameInformation information; - - private final int myTurn; - private Runnable onGameOver; - private final BlockingQueue moveQueue; - - private final Connect4 game; - private final Connect4AI ai; - private final int columnSize = 7; - private final int rowSize = 6; - - private final GameView primary; - private final Connect4Canvas canvas; - - private final AtomicBoolean isRunning; - - public Connect4Game(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { - this.information = information; - this.myTurn = myTurn; - this.onGameOver = onGameOver; - moveQueue = new LinkedBlockingQueue(); - - - game = new Connect4(); - ai = new Connect4AI(); - - isRunning = new AtomicBoolean(true); - - if (onForfeit == null || onExit == null) { - primary = new GameView(null, () -> { - isRunning.set(false); - WidgetContainer.getCurrentView().transitionPrevious(); - }, null, "Connect4"); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, "Connect4"); - } - - canvas = new Connect4Canvas(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? 'X' : 'O'; - - try { - moveQueue.put(new Move(cell%columnSize, value)); - } catch (InterruptedException _) {} - } - } else { - if (information.players[0].isHuman) { - final char value = myTurn == 0? 'X' : 'O'; - - try { - moveQueue.put(new Move(cell%columnSize, value)); - } catch (InterruptedException _) {} - } - } - }); - - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary); - - if (onForfeit == null || onExit == null) { - new Thread(this::localGameThread).start(); - setGameLabels(information.players[0].isHuman); - } else { - new EventFlow() - .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false) - .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false); - - setGameLabels(myTurn == 0); - } - updateCanvas(); - } - - public Connect4Game(GameInformation information) { - this(information, 0, null, null, null, null); - } - private void localGameThread() { - while (isRunning.get()) { - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "RED" : "BLUE"; - final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); - - primary.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - currentValue, - information.players[nextTurn].name); - - Move move = null; - - if (information.players[currentTurn].isHuman) { - try { - final Move wants = moveQueue.take(); - final Move[] legalMoves = game.getLegalMoves(); - - for (final 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 = Math.abs(information.players[currentTurn].computerThinkTime * 1000L - elapsedTime); - - try { - Thread.sleep((long)(sleepTime * Math.random())); - } catch (InterruptedException _) {} - } - } - - if (move == null) { - continue; - } - - final GameState state = game.play(move); - updateCanvas(); -/* - if (move.value() == 'X') { - canvas.drawX(Color.INDIANRED, move.position()); - } else if (move.value() == 'O') { - canvas.drawO(Color.ROYALBLUE, move.position()); - } -*/ - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - primary.gameOver(true, information.players[currentTurn].name); - } else if (state == GameState.DRAW) { - primary.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? 'X' : 'O'; - } else { - playerChar = myTurn == 0? 'O' : 'X'; - } - - final Move move = new Move(Integer.parseInt(response.move()), playerChar); - final GameState state = game.play(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - if (response.player().equalsIgnoreCase(information.players[0].name)) { - primary.gameOver(true, information.players[0].name); - gameOver(); - } else { - primary.gameOver(false, information.players[1].name); - gameOver(); - } - } else if (state == GameState.DRAW) { - primary.gameOver(false, ""); - gameOver(); - } - } - - if (move.value() == 'X') { - canvas.drawDot(Color.INDIANRED, move.position()); - } else if (move.value() == 'O') { - canvas.drawDot(Color.ROYALBLUE, move.position()); - } - - updateCanvas(); - setGameLabels(game.getCurrentTurn() == myTurn); - } - - private void gameOver() { - if (onGameOver == null){ - return; - } - isRunning.set(false); - onGameOver.run(); - } - - 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 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 updateCanvas() { - canvas.clearAll(); - - for (int i = 0; i < game.getBoard().length; i++) { - if (game.getBoard()[i] == 'X') { - canvas.drawDot(Color.RED, i); - } else if (game.getBoard()[i] == 'O') { - canvas.drawDot(Color.BLUE, i); - } - } - } - - private void setGameLabels(boolean isMe) { - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "RED" : "BLUE"; - - primary.nextPlayer(isMe, - information.players[isMe? 0 : 1].name, - currentValue, - information.players[isMe? 1 : 0].name); - } -} diff --git a/app/src/main/java/org/toop/app/game/ReversiGame.java b/app/src/main/java/org/toop/app/game/ReversiGame.java deleted file mode 100644 index f06197d..0000000 --- a/app/src/main/java/org/toop/app/game/ReversiGame.java +++ /dev/null @@ -1,333 +0,0 @@ -package org.toop.app.game; - -import javafx.animation.SequentialTransition; -import org.toop.app.App; -import org.toop.app.GameInformation; -import org.toop.app.canvas.ReversiCanvas; -import org.toop.app.widget.WidgetContainer; -import org.toop.app.widget.view.GameView; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; -import org.toop.game.reversi.Reversi; -import org.toop.game.reversi.ReversiAI; - -import javafx.geometry.Pos; -import javafx.scene.paint.Color; - -import java.awt.*; -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 Runnable onGameOver; - private final BlockingQueue moveQueue; - - private final Reversi game; - private final ReversiAI ai; - - private final GameView primary; - private final ReversiCanvas canvas; - - private final AtomicBoolean isRunning; - private final AtomicBoolean isPaused; - - public ReversiGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { - this.information = information; - - this.myTurn = myTurn; - this.onGameOver = onGameOver; - moveQueue = new LinkedBlockingQueue<>(); - - game = new Reversi(); - ai = new ReversiAI(); - - isRunning = new AtomicBoolean(true); - isPaused = new AtomicBoolean(false); - - if (onForfeit == null || onExit == null) { - primary = new GameView(null, () -> { - isRunning.set(false); - WidgetContainer.getCurrentView().transitionPrevious(); - }, null, "Reversi"); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, "Reversi"); - } - - canvas = new ReversiCanvas(Color.BLACK, - (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 Move(cell, value)); - } catch (InterruptedException _) {} - } - } else { - if (information.players[0].isHuman) { - final char value = myTurn == 0? 'B' : 'W'; - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - } - },this::highlightCells); - - - - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary); - - if (onForfeit == null || onExit == null) { - new Thread(this::localGameThread).start(); - setGameLabels(information.players[0].isHuman); - } else { - new EventFlow() - .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false) - .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false); - - setGameLabels(myTurn == 0); - } - - updateCanvas(false); - } - - public ReversiGame(GameInformation information) { - this(information, 0, null, null, null,null); - } - - private void localGameThread() { - while (isRunning.get()) { - if (isPaused.get()) { - try { - Thread.sleep(200); - } catch (InterruptedException _) {} - - continue; - } - - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "BLACK" : "WHITE"; - final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); - - primary.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - currentValue, - information.players[nextTurn].name); - - Move move = null; - - if (information.players[currentTurn].isHuman) { - try { - final Move wants = moveQueue.take(); - final Move[] legalMoves = game.getLegalMoves(); - - for (final 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; - } - - canvas.setCurrentlyHighlightedMovesNull(); - final GameState state = game.play(move); - updateCanvas(true); - - if (state != GameState.NORMAL) { - if (state == GameState.TURN_SKIPPED){ - continue; - } - int winningPLayerNumber = getPlayerNumberWithHighestScore(); - if (state == GameState.WIN && winningPLayerNumber > -1) { - primary.gameOver(true, information.players[winningPLayerNumber].name); - } else if (state == GameState.DRAW || winningPLayerNumber == -1) { - primary.gameOver(false, ""); - } - - isRunning.set(false); - } - } - } - - private int getPlayerNumberWithHighestScore(){ - Reversi.Score score = game.getScore(); - if (score.player1Score() > score.player2Score()) return 0; - if (score.player1Score() < score.player2Score()) return 1; - return -1; - } - - 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 Move move = new Move(Integer.parseInt(response.move()), playerChar); - final GameState state = game.play(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - if (response.player().equalsIgnoreCase(information.players[0].name)) { - primary.gameOver(true, information.players[0].name); - gameOver(); - } else { - primary.gameOver(false, information.players[1].name); - gameOver(); - } - } else if (state == GameState.DRAW) { - primary.gameOver(false, ""); - game.play(move); - } - } - - updateCanvas(false); - setGameLabels(game.getCurrentTurn() == myTurn); - } - - private void gameOver() { - if (onGameOver == null){ - return; - } - isRunning.set(false); - onGameOver.run(); - } - - 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 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 updateCanvas(boolean animate) { - // Todo: this is very inefficient. still very fast but if the grid is bigger it might cause issues. improve. - canvas.clearAll(); - - for (int i = 0; i < game.getBoard().length; i++) { - if (game.getBoard()[i] == 'B') { - canvas.drawDot(Color.BLACK, i); - } else if (game.getBoard()[i] == 'W') { - canvas.drawDot(Color.WHITE, i); - } - } - - final Move[] flipped = game.getMostRecentlyFlippedPieces(); - - final SequentialTransition animation = new SequentialTransition(); - isPaused.set(true); - - final Color fromColor = game.getCurrentPlayer() == 'W'? Color.WHITE : Color.BLACK; - final Color toColor = game.getCurrentPlayer() == 'W'? Color.BLACK : Color.WHITE; - - if (animate && flipped != null) { - for (final Move flip : flipped) { - canvas.clear(flip.position()); - canvas.drawDot(fromColor, flip.position()); - animation.getChildren().addFirst(canvas.flipDot(fromColor, toColor, flip.position())); - } - } - - animation.setOnFinished(_ -> { - isPaused.set(false); - - if (information.players[game.getCurrentTurn()].isHuman) { - final Move[] legalMoves = game.getLegalMoves(); - - for (final Move legalMove : legalMoves) { - canvas.drawLegalPosition(legalMove.position(), game.getCurrentPlayer()); - } - } - }); - - animation.play(); - } - - private void setGameLabels(boolean isMe) { - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "BLACK" : "WHITE"; - - primary.nextPlayer(isMe, - information.players[isMe? 0 : 1].name, - currentValue, - information.players[isMe? 1 : 0].name); - } - - private void highlightCells(int cellEntered) { - if (information.players[game.getCurrentTurn()].isHuman) { - Move[] legalMoves = game.getLegalMoves(); - boolean isLegalMove = false; - for (Move move : legalMoves) { - if (move.position() == cellEntered){ - isLegalMove = true; - break; - } - } - - if (cellEntered >= 0){ - Move[] moves = null; - if (isLegalMove) { - moves = game.getFlipsForPotentialMove( - new Point(cellEntered%game.getColumnSize(),cellEntered/game.getRowSize()), - game.getCurrentPlayer()); - } - canvas.drawHighlightDots(moves); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/TicTacToeGame.java b/app/src/main/java/org/toop/app/game/TicTacToeGame.java deleted file mode 100644 index a164d72..0000000 --- a/app/src/main/java/org/toop/app/game/TicTacToeGame.java +++ /dev/null @@ -1,250 +0,0 @@ -package org.toop.app.game; - -import org.toop.app.App; -import org.toop.app.GameInformation; -import org.toop.app.canvas.TicTacToeCanvas; -import org.toop.app.widget.WidgetContainer; -import org.toop.app.widget.view.GameView; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; -import org.toop.game.tictactoe.TicTacToe; -import org.toop.game.tictactoe.TicTacToeAI; - -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 TicTacToeGame { - private final GameInformation information; - - private final int myTurn; - private final Runnable onGameOver; - private final BlockingQueue moveQueue; - - private final TicTacToe game; - private final TicTacToeAI ai; - - private final GameView primary; - private final TicTacToeCanvas canvas; - - private final AtomicBoolean isRunning; - - public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { - this.information = information; - - this.myTurn = myTurn; - this.onGameOver = onGameOver; - moveQueue = new LinkedBlockingQueue(); - - game = new TicTacToe(); - ai = new TicTacToeAI(); - - isRunning = new AtomicBoolean(true); - - if (onForfeit == null || onExit == null) { - primary = new GameView(null, () -> { - isRunning.set(false); - WidgetContainer.getCurrentView().transitionPrevious(); - }, null, "TicTacToe"); - } else { - primary = new GameView(onForfeit, () -> { - isRunning.set(false); - onExit.run(); - }, onMessage, "TicTacToe"); - } - - canvas = new TicTacToeCanvas(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? 'X' : 'O'; - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - } else { - if (information.players[0].isHuman) { - final char value = myTurn == 0? 'X' : 'O'; - - try { - moveQueue.put(new Move(cell, value)); - } catch (InterruptedException _) {} - } - } - }); - - primary.add(Pos.CENTER, canvas.getCanvas()); - WidgetContainer.getCurrentView().transitionNext(primary); - - if (onForfeit == null || onExit == null) { - new Thread(this::localGameThread).start(); - } else { - new EventFlow() - .listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse, false) - .listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse, false); - - setGameLabels(myTurn == 0); - } - } - - public TicTacToeGame(GameInformation information) { - this(information, 0, null, null, null, null); - } - - private void localGameThread() { - while (isRunning.get()) { - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "X" : "O"; - final int nextTurn = (currentTurn + 1) % information.type.getPlayerCount(); - - primary.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - currentValue, - information.players[nextTurn].name); - - Move move = null; - - if (information.players[currentTurn].isHuman) { - try { - final Move wants = moveQueue.take(); - final Move[] legalMoves = game.getLegalMoves(); - - for (final 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 GameState state = game.play(move); - - if (move.value() == 'X') { - //canvas.drawPlayer('X', Color.INDIANRED, move.position()); - } else if (move.value() == 'O') { - //canvas.drawPlayer('O', Color.ROYALBLUE, move.position()); - } - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - primary.gameOver(true, information.players[currentTurn].name); - } else if (state == GameState.DRAW) { - primary.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? 'X' : 'O'; - } else { - playerChar = myTurn == 0? 'O' : 'X'; - } - - final Move move = new Move(Integer.parseInt(response.move()), playerChar); - final GameState state = game.play(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - if (response.player().equalsIgnoreCase(information.players[0].name)) { - primary.gameOver(true, information.players[0].name); - gameOver(); - } else { - primary.gameOver(false, information.players[1].name); - gameOver(); - } - } else if (state == GameState.DRAW) { - if(game.getLegalMoves().length == 0) { //only return draw in online multiplayer if the game is actually over. - primary.gameOver(false, ""); - gameOver(); - } - } - } - - if (move.value() == 'X') { - //canvas.drawPlayer('X', Color.RED, move.position()); - } else if (move.value() == 'O') { - //canvas.drawPlayer('O', Color.BLUE, move.position()); - } - - setGameLabels(game.getCurrentTurn() == myTurn); - } - - private void gameOver() { - if (onGameOver == null){ - return; - } - isRunning.set(false); - onGameOver.run(); - } - - 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 Move 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 setGameLabels(boolean isMe) { - final int currentTurn = game.getCurrentTurn(); - final String currentValue = currentTurn == 0? "X" : "O"; - - primary.nextPlayer(isMe, - information.players[isMe? 0 : 1].name, - currentValue, - information.players[isMe? 1 : 0].name); - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/TicTacToeGameThread.java b/app/src/main/java/org/toop/app/game/TicTacToeGameThread.java deleted file mode 100644 index dda8b7f..0000000 --- a/app/src/main/java/org/toop/app/game/TicTacToeGameThread.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.toop.app.game; - -import org.toop.app.App; -import org.toop.app.GameInformation; -import org.toop.app.canvas.TicTacToeCanvas; -import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; -import org.toop.game.tictactoe.TicTacToe; -import org.toop.game.tictactoe.TicTacToeAI; -import java.util.function.Consumer; -import javafx.geometry.Pos; -import javafx.scene.paint.Color; - -public final class TicTacToeGameThread extends BaseGameThread { - public TicTacToeGameThread(GameInformation info, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage, Runnable onGameOver) { - super(info, myTurn, onForfeit, onExit, onMessage, onGameOver, - TicTacToe::new, - TicTacToeAI::new, - clickHandler -> new TicTacToeCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3, clickHandler) - ); - } - - public TicTacToeGameThread(GameInformation info) { - this(info, 0, null, null, null, null); - } - - @Override - protected void addCanvasToPrimary() { - primary.add(Pos.CENTER, canvas.getCanvas()); - } - - @Override - protected int getCurrentTurn() { - return game.getCurrentTurn(); - } - - @Override - protected char getSymbolForTurn(int turn) { - return turn == 0 ? 'X' : 'O'; - } - - @Override - protected String getNameForTurn(int turn) { - return turn == 0 ? "X" : "O"; - } - - private void drawMove(Move move) { - //if (move.value() == 'X') canvas.drawPlayer('X', Color.RED, move.position()); - //else canvas.drawPlayer('O', Color.BLUE, move.position()); - } - - @Override - protected void onMoveResponse(NetworkEvents.GameMoveResponse response) { - if (!isRunning.get()) { - return; - } - - char playerChar; - - if (response.player().equalsIgnoreCase(information.players[0].name)) { - playerChar = myTurn == 0? 'X' : 'O'; - } else { - playerChar = myTurn == 0? 'O' : 'X'; - } - - final Move move = new Move(Integer.parseInt(response.move()), playerChar); - final GameState state = game.play(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - if (response.player().equalsIgnoreCase(information.players[0].name)) { - primary.gameOver(true, information.players[0].name); - gameOver(); - } else { - primary.gameOver(false, information.players[1].name); - gameOver(); - } - } else if (state == GameState.DRAW) { - if (game.getLegalMoves().length == 0) { - primary.gameOver(false, ""); - gameOver(); - } - } - } - - drawMove(move); - setGameLabels(game.getCurrentTurn() == myTurn); - } - - @Override - protected void onYourTurnResponse(NetworkEvents.YourTurnResponse response) { - if (!isRunning.get()) { - return; - } - - moveQueue.clear(); - - int position = -1; - - if (information.players[0].isHuman) { - try { - position = moveQueue.take().position(); - } catch (InterruptedException _) {} - } else { - final Move move; - if (information.players[1].name.equalsIgnoreCase("pism")) { - move = ai.findWorstMove(game,9); - }else{ - move = ai.findBestMove(game, information.players[0].computerDifficulty); - } - - assert move != null; - position = move.position(); - } - - new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position)) - .postEvent(); - } - - @Override - protected void localGameThread() { - while (isRunning.get()) { - final int currentTurn = game.getCurrentTurn(); - setGameLabels(currentTurn == myTurn); - - Move move = null; - - if (information.players[currentTurn].isHuman) { - try { - final Move wants = moveQueue.take(); - final Move[] legalMoves = game.getLegalMoves(); - - for (final 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 GameState state = game.play(move); - drawMove(move); - - if (state != GameState.NORMAL) { - if (state == GameState.WIN) { - primary.gameOver(information.players[currentTurn].isHuman, information.players[currentTurn].name); - } else if (state == GameState.DRAW) { - primary.gameOver(false, ""); - } - - isRunning.set(false); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/gameControllers/AbstractGameController.java b/app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java similarity index 64% rename from app/src/main/java/org/toop/app/game/gameControllers/AbstractGameController.java rename to app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java index 2293a82..7d670e4 100644 --- a/app/src/main/java/org/toop/app/game/gameControllers/AbstractGameController.java +++ b/app/src/main/java/org/toop/app/gameControllers/AbstractGameController.java @@ -1,24 +1,24 @@ -package org.toop.app.game.gameControllers; +package org.toop.app.gameControllers; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.toop.framework.gameFramework.interfaces.UpdatesGameUI; -import org.toop.framework.gameFramework.GUIEvents; +import org.toop.framework.gameFramework.controller.UpdatesGameUI; +import org.toop.framework.gameFramework.view.GUIEvents; import org.toop.app.canvas.GameCanvas; import org.toop.framework.networking.events.NetworkEvents; -import org.toop.game.GameThreadBehaviour.GameThreadStrategy; +import org.toop.framework.gameFramework.model.game.threadBehaviour.ThreadBehaviour; import org.toop.app.widget.view.GameView; import org.toop.framework.eventbus.EventFlow; -import org.toop.game.GameThreadBehaviour.OnlineThreadBehaviour; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; -import org.toop.framework.gameFramework.interfaces.SupportsOnlinePlay; -import org.toop.game.players.AbstractPlayer; +import org.toop.game.gameThreads.OnlineThreadBehaviour; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay; +import org.toop.framework.gameFramework.model.player.Player; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; -public abstract class AbstractGameController implements UpdatesGameUI, GameThreadStrategy, SupportsOnlinePlay { +public abstract class AbstractGameController> implements UpdatesGameUI, ThreadBehaviour { protected final EventFlow eventFlow = new EventFlow(); protected final List> listeners = new ArrayList<>(); @@ -32,13 +32,13 @@ public abstract class AbstractGameController implement // Reference to game canvas protected final GameCanvas canvas; - private final AbstractPlayer[] players; // List of players, can't be changed. + private final Player[] players; // List of players, can't be changed. protected final T game; // Reference to game instance - private final GameThreadStrategy gameThreadBehaviour; + private final ThreadBehaviour gameThreadBehaviour; // TODO: Change gameType to automatically happen with either dependency injection or something else. // TODO: Make visualisation of moves a behaviour. - protected AbstractGameController(GameCanvas canvas, AbstractPlayer[] players, T game, GameThreadStrategy gameThreadBehaviour, String gameType) { + protected AbstractGameController(GameCanvas canvas, Player[] players, T game, ThreadBehaviour gameThreadBehaviour, String gameType) { logger.info("Creating AbstractGameController"); // Make sure player list matches expected size if (players.length != game.getPlayerCount()){ @@ -51,11 +51,6 @@ public abstract class AbstractGameController implement this.game = game; this.gameThreadBehaviour = gameThreadBehaviour; - // Let players know who they are - for(int i = 0; i < players.length; i++){ - players[i].setPlayerIndex(i); - } - primary = new GameView(null, null, null, gameType); addListeners(); } @@ -71,12 +66,12 @@ public abstract class AbstractGameController implement gameThreadBehaviour.stop(); } - public AbstractPlayer getCurrentPlayer(){ - return gameThreadBehaviour.getCurrentPlayer(); + public Player getCurrentPlayer(){ + return game.getPlayer(getCurrentPlayerIndex()); }; public int getCurrentPlayerIndex(){ - return getCurrentPlayer().getPlayerIndex(); + return game.getCurrentTurn(); } private void addListeners(){ @@ -100,7 +95,7 @@ public abstract class AbstractGameController implement stop(); } - public AbstractPlayer getPlayer(int player){ + public Player getPlayer(int player){ if (player < 0 || player >= players.length){ logger.error("Invalid player index"); throw new IllegalArgumentException("player out of range"); @@ -112,24 +107,21 @@ public abstract class AbstractGameController implement return this.gameThreadBehaviour instanceof SupportsOnlinePlay; } - @Override - public void yourTurn(NetworkEvents.YourTurnResponse event){ + public void onYourTurn(NetworkEvents.YourTurnResponse event){ if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).yourTurn(event); + ((OnlineThreadBehaviour) this.gameThreadBehaviour).onYourTurn(event); } } - @Override - public void moveReceived(NetworkEvents.GameMoveResponse event){ + public void onMoveReceived(NetworkEvents.GameMoveResponse event){ if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).moveReceived(event); + ((OnlineThreadBehaviour) this.gameThreadBehaviour).onMoveReceived(event); } } - @Override public void gameFinished(NetworkEvents.GameResultResponse event){ if (isOnline()){ - ((OnlineThreadBehaviour) this.gameThreadBehaviour).gameFinished(event); + ((OnlineThreadBehaviour) this.gameThreadBehaviour).gameFinished(event); } } } diff --git a/app/src/main/java/org/toop/app/game/gameControllers/ReversiController.java b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java similarity index 85% rename from app/src/main/java/org/toop/app/game/gameControllers/ReversiController.java rename to app/src/main/java/org/toop/app/gameControllers/ReversiController.java index 625b8dd..3c46306 100644 --- a/app/src/main/java/org/toop/app/game/gameControllers/ReversiController.java +++ b/app/src/main/java/org/toop/app/gameControllers/ReversiController.java @@ -1,4 +1,4 @@ -package org.toop.app.game.gameControllers; +package org.toop.app.gameControllers; import javafx.animation.SequentialTransition; import javafx.geometry.Pos; @@ -7,23 +7,23 @@ import org.toop.app.App; import org.toop.app.canvas.ReversiCanvas; import org.toop.app.widget.WidgetContainer; import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.abstractClasses.GameR; -import org.toop.framework.gameFramework.GUIEvents; -import org.toop.game.GameThreadBehaviour.LocalFixedRateThreadBehaviour; -import org.toop.game.GameThreadBehaviour.OnlineThreadBehaviour; -import org.toop.game.players.AbstractPlayer; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.framework.gameFramework.view.GUIEvents; +import org.toop.game.gameThreads.LocalFixedRateThreadBehaviour; +import org.toop.game.gameThreads.OnlineThreadBehaviour; import org.toop.game.players.LocalPlayer; -import org.toop.game.reversi.ReversiR; +import org.toop.framework.gameFramework.model.player.Player; +import org.toop.game.games.reversi.ReversiR; public class ReversiController extends AbstractGameController { // TODO: Refactor GUI update methods to follow designed system - public ReversiController(AbstractPlayer[] players, boolean local) { - ReversiR ReversiR = new ReversiR(); + public ReversiController(Player[] players, boolean local) { + ReversiR ReversiR = new ReversiR(players); super( new ReversiCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerAttemptedMove.class, c).postEvent();}, (c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerMoveHovered.class, c).postEvent();}), players, ReversiR, - local ? new LocalFixedRateThreadBehaviour(ReversiR, players) : new OnlineThreadBehaviour(ReversiR, players), // TODO: Player order matters here, this won't work atm + local ? new LocalFixedRateThreadBehaviour<>(ReversiR, players) : new OnlineThreadBehaviour<>(ReversiR, players), // TODO: Player order matters here, this won't work atm "Reversi"); eventFlow.listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}}, false); eventFlow.listen(GUIEvents.PlayerMoveHovered.class, this::onHoverMove, false); @@ -56,7 +56,7 @@ public class ReversiController extends AbstractGameController { //}*/ } - public ReversiController(AbstractPlayer[] players) { + public ReversiController(Player[] players) { this(players, true); } @@ -130,7 +130,7 @@ public class ReversiController extends AbstractGameController { // Draw each square for (int i = 0; i < board.length; i++){ // If square isn't empty, draw player move - if (board[i] != GameR.EMPTY){ + if (board[i] != AbstractGame.EMPTY){ canvas.drawPlayerMove(board[i], i); } } diff --git a/app/src/main/java/org/toop/app/game/gameControllers/TicTacToeController.java b/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java similarity index 73% rename from app/src/main/java/org/toop/app/game/gameControllers/TicTacToeController.java rename to app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java index 11ba925..6bd6fb0 100644 --- a/app/src/main/java/org/toop/app/game/gameControllers/TicTacToeController.java +++ b/app/src/main/java/org/toop/app/gameControllers/TicTacToeController.java @@ -1,28 +1,28 @@ -package org.toop.app.game.gameControllers; +package org.toop.app.gameControllers; import javafx.geometry.Pos; import javafx.scene.paint.Color; import org.toop.app.App; import org.toop.app.canvas.TicTacToeCanvas; import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.GUIEvents; -import org.toop.framework.gameFramework.abstractClasses.GameR; -import org.toop.game.GameThreadBehaviour.LocalThreadBehaviour; -import org.toop.game.GameThreadBehaviour.OnlineThreadBehaviour; +import org.toop.framework.gameFramework.model.player.Player; +import org.toop.framework.gameFramework.view.GUIEvents; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.game.gameThreads.LocalThreadBehaviour; +import org.toop.game.gameThreads.OnlineThreadBehaviour; import org.toop.game.players.LocalPlayer; -import org.toop.game.players.AbstractPlayer; import org.toop.app.widget.WidgetContainer; -import org.toop.game.tictactoe.TicTacToeR; +import org.toop.game.games.tictactoe.TicTacToeR; public class TicTacToeController extends AbstractGameController { - public TicTacToeController(AbstractPlayer[] players, boolean local) { - TicTacToeR ticTacToeR = new TicTacToeR(); + public TicTacToeController(Player[] players, boolean local) { + TicTacToeR ticTacToeR = new TicTacToeR(players); super( new TicTacToeCanvas(Color.GRAY, (App.getHeight() / 4) * 3, (App.getHeight() / 4) * 3,(c) -> {new EventFlow().addPostEvent(GUIEvents.PlayerAttemptedMove.class, c).postEvent();}), players, ticTacToeR, - local ? new LocalThreadBehaviour(ticTacToeR, players) : new OnlineThreadBehaviour(ticTacToeR, players), // TODO: Player order matters here, this won't work atm + local ? new LocalThreadBehaviour(ticTacToeR, players) : new OnlineThreadBehaviour<>(ticTacToeR, players), // TODO: Player order matters here, this won't work atm "TicTacToe"); initUI(); @@ -31,7 +31,7 @@ public class TicTacToeController extends AbstractGameController { //new EventFlow().listen(GUIEvents.PlayerAttemptedMove.class, event -> {if (getCurrentPlayer() instanceof LocalPlayer lp){lp.setMove(event.move());}}); } - public TicTacToeController(AbstractPlayer[] players) { + public TicTacToeController(Player[] players) { this(players, true); } @@ -55,7 +55,7 @@ public class TicTacToeController extends AbstractGameController { // Draw each square for (int i = 0; i < board.length; i++){ // If square isn't empty, draw player move - if (board[i] != GameR.EMPTY){ + if (board[i] != AbstractGame.EMPTY){ canvas.drawPlayerMove(board[i], i); } } diff --git a/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java b/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java index 870a74f..cbbf702 100644 --- a/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java +++ b/app/src/main/java/org/toop/app/widget/view/LocalMultiplayerView.java @@ -2,22 +2,18 @@ package org.toop.app.widget.view; import javafx.application.Platform; import org.toop.app.GameInformation; -import org.toop.app.game.*; -import org.toop.app.game.gameControllers.AbstractGameController; -import org.toop.app.game.gameControllers.ReversiController; -import org.toop.app.game.gameControllers.TicTacToeController; +import org.toop.app.gameControllers.AbstractGameController; +import org.toop.app.gameControllers.ReversiController; +import org.toop.app.gameControllers.TicTacToeController; +import org.toop.framework.gameFramework.model.player.Player; import org.toop.game.players.ArtificialPlayer; import org.toop.game.players.LocalPlayer; -import org.toop.game.players.AbstractPlayer; -import org.toop.app.game.gameControllers.ReversiController; -import org.toop.app.game.gameControllers.TicTacToeController; import org.toop.app.widget.Primitive; -import org.toop.app.widget.WidgetContainer; import org.toop.app.widget.complex.PlayerInfoWidget; import org.toop.app.widget.complex.ViewWidget; import org.toop.app.widget.popup.ErrorPopup; -import org.toop.game.reversi.ReversiAIR; -import org.toop.game.tictactoe.TicTacToeAIR; +import org.toop.game.games.reversi.ReversiAIR; +import org.toop.game.games.tictactoe.TicTacToeAIR; import org.toop.app.widget.tutorial.*; import org.toop.local.AppContext; @@ -29,7 +25,7 @@ import org.toop.local.AppSettings; public class LocalMultiplayerView extends ViewWidget { private final GameInformation information; - private AbstractGameController gameController; + private AbstractGameController gameController; public LocalMultiplayerView(GameInformation.Type type) { this(new GameInformation(type)); @@ -49,46 +45,46 @@ public class LocalMultiplayerView extends ViewWidget { } // TODO: Fix this temporary ass way of setting the players (Only works for TicTacToe) - AbstractPlayer[] players = new AbstractPlayer[2]; + Player[] players = new Player[2]; switch (information.type) { case TICTACTOE: - if (information.players[0].isHuman){ - players[0] = new LocalPlayer(information.players[0].name); - } - else { + if (information.players[0].isHuman) { + players[0] = new LocalPlayer<>(information.players[0].name); + } else { players[0] = new ArtificialPlayer<>(new TicTacToeAIR(), information.players[0].name); } - if (information.players[1].isHuman){ - players[1] = new LocalPlayer(information.players[1].name); - } - else { + if (information.players[1].isHuman) { + players[1] = new LocalPlayer<>(information.players[1].name); + } else { players[1] = new ArtificialPlayer<>(new TicTacToeAIR(), information.players[1].name); } if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstTTT()) { - new ShowEnableTutorialWidget( - () -> new TicTacToeTutorialWidget(() -> {gameController = new TicTacToeController(players); - gameController.start();}), - () -> Platform.runLater(() -> {gameController = new TicTacToeController(players); - gameController.start();}), + new ShowEnableTutorialWidget( + () -> new TicTacToeTutorialWidget(() -> { + gameController = new TicTacToeController(players); + gameController.start(); + }), + () -> Platform.runLater(() -> { + gameController = new TicTacToeController(players); + gameController.start(); + }), () -> AppSettings.getSettings().setFirstTTT(false) - ); + ); } else { gameController = new TicTacToeController(players); gameController.start(); } break; case REVERSI: - if (information.players[0].isHuman){ - players[0] = new LocalPlayer(information.players[0].name); - } - else { + if (information.players[0].isHuman) { + players[0] = new LocalPlayer<>(information.players[0].name); + } else { players[0] = new ArtificialPlayer<>(new ReversiAIR(), information.players[0].name); } - if (information.players[1].isHuman){ - players[1] = new LocalPlayer(information.players[1].name); - } - else { + if (information.players[1].isHuman) { + players[1] = new LocalPlayer<>(information.players[1].name); + } else { players[1] = new ArtificialPlayer<>(new ReversiAIR(), information.players[1].name); } if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) { @@ -108,19 +104,7 @@ public class LocalMultiplayerView extends ViewWidget { gameController.start(); } break; - case CONNECT4: - if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstConnect4()) { - new ShowEnableTutorialWidget( - () -> new Connect4TutorialWidget(() -> new Connect4Game(information)), - () -> Platform.runLater(() -> new Connect4Game(information)), - () -> AppSettings.getSettings().setFirstConnect4(false) - ); - } else { - new Connect4Game(information); - } - break; } - // case BATTLESHIP -> new BattleshipGame(information); }); var playerSection = setupPlayerSections(); diff --git a/app/src/main/java/org/toop/app/widget/view/LocalView.java b/app/src/main/java/org/toop/app/widget/view/LocalView.java index 90cfac6..f002bf8 100644 --- a/app/src/main/java/org/toop/app/widget/view/LocalView.java +++ b/app/src/main/java/org/toop/app/widget/view/LocalView.java @@ -16,14 +16,9 @@ public class LocalView extends ViewWidget { transitionNext(new LocalMultiplayerView(GameInformation.Type.REVERSI)); }); - var connect4Button = Primitive.button("connect4", () -> { - transitionNext(new LocalMultiplayerView(GameInformation.Type.CONNECT4)); - }); - add(Pos.CENTER, Primitive.vbox( ticTacToeButton, - reversiButton, - connect4Button + reversiButton )); } } \ No newline at end of file diff --git a/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/GameR.java b/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/GameR.java deleted file mode 100644 index 5f4d897..0000000 --- a/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/GameR.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.toop.framework.gameFramework.abstractClasses; - -import org.toop.framework.gameFramework.interfaces.IPlayableR; - -import java.util.Arrays; - -/** - * Abstract base class representing a general grid-based game. - *

- * Provides the basic structure for games with a two-dimensional board stored as a - * one-dimensional array. Tracks the board state, row and column sizes, and provides - * helper methods for accessing and modifying the board. - *

- *

- * Concrete subclasses must implement the {@link #clone()} method and can extend this - * class with specific game rules, winning conditions, and move validation logic. - *

- */ -public abstract class GameR implements IPlayableR, Cloneable { - - /** Constant representing an empty position on the board. */ - public static final int EMPTY = -1; - - /** Number of rows in the game board. */ - private final int rowSize; - - /** Number of columns in the game board. */ - private final int columnSize; - - /** The game board stored as a one-dimensional array. */ - private final int[] board; - - /** - * Constructs a new game board with the specified row and column size. - * - * @param rowSize number of rows (> 0) - * @param columnSize number of columns (> 0) - * @throws AssertionError if rowSize or columnSize is not positive - */ - - protected GameR(int rowSize, int columnSize) { - assert rowSize > 0 && columnSize > 0; - - this.rowSize = rowSize; - this.columnSize = columnSize; - - board = new int[rowSize * columnSize]; - Arrays.fill(board, EMPTY); - } - - /** - * Copy constructor for creating a deep copy of another game instance. - * - * @param copy the game instance to copy - */ - protected GameR(GameR copy) { - this.rowSize = copy.rowSize; - this.columnSize = copy.columnSize; - this.board = copy.board.clone(); - } - - /** - * Check if an array contains a value. - * - * @param array array containing ints - * @param value int to check for - * - * @return true if array contains value - */ - - public static boolean contains(int[] array, int value) { - // O(n) - for (int element : array){ - if (element == value) return true; - } - return false; - } - - /** - * Returns the number of rows in the board. - * - * @return number of rows - */ - public int getRowSize() { - return this.rowSize; - } - - /** - * Returns the number of columns in the board. - * - * @return number of columns - */ - public int getColumnSize() { - return this.columnSize; - } - - /** - * Returns a copy of the current board state. - * - * @return a cloned array representing the board - */ - public int[] getBoard() { - return this.board.clone(); - } - - /** - * Sets the value of a specific position on the board. - * - * @param position the index in the board array - * @param player the value to set (e.g., player number) - */ - protected void setBoardPosition(int position, int player) { - this.board[position] = player; - } - - /** - * Creates and returns a deep copy of this game instance. - *

- * Subclasses must implement this method to ensure proper copying of any - * additional fields beyond the base board structure. - *

- * - * @return a cloned instance of this game - */ - @Override - public abstract GameR clone(); -} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/TurnBasedGameR.java b/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/TurnBasedGameR.java deleted file mode 100644 index 31efcf9..0000000 --- a/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/TurnBasedGameR.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.toop.framework.gameFramework.abstractClasses; - -public abstract class TurnBasedGameR extends GameR { - private final int playerCount; // How many players are playing - private int turn = 0; // What turn it is in the game - - protected TurnBasedGameR(int rowSize, int columnSize, int playerCount) { - super(rowSize, columnSize); - this.playerCount = playerCount; - } - - protected TurnBasedGameR(TurnBasedGameR other){ - super(other); - this.playerCount = other.playerCount; - this.turn = other.turn; - } - - public int getPlayerCount(){return this.playerCount;} - - protected void nextTurn() { - turn += 1; - } - - public int getCurrentTurn() { - return turn % playerCount; - } - - protected void setBoard(int position) { - super.setBoardPosition(position, getCurrentTurn()); - } -} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/UpdatesGameUI.java b/framework/src/main/java/org/toop/framework/gameFramework/controller/UpdatesGameUI.java similarity index 76% rename from framework/src/main/java/org/toop/framework/gameFramework/interfaces/UpdatesGameUI.java rename to framework/src/main/java/org/toop/framework/gameFramework/controller/UpdatesGameUI.java index 47107fa..ce32bb7 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/UpdatesGameUI.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/controller/UpdatesGameUI.java @@ -1,4 +1,4 @@ -package org.toop.framework.gameFramework.interfaces; +package org.toop.framework.gameFramework.controller; /** * Interface for classes that can trigger a UI update. diff --git a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/IAIMoveR.java b/framework/src/main/java/org/toop/framework/gameFramework/interfaces/IAIMoveR.java deleted file mode 100644 index aeed1fb..0000000 --- a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/IAIMoveR.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.toop.framework.gameFramework.interfaces; - -import org.toop.framework.gameFramework.abstractClasses.GameR; - -/** - * AI interface for selecting the best move in a game. - * - * @param the type of game this AI can play, extending {@link GameR} - */ -public interface IAIMoveR { - - /** - * Determines the optimal move for the current player. - * - * @param game the current game state - * @param depth the search depth for evaluating moves - * @return an integer representing the chosen move - */ - int findBestMove(T game, int depth); -} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java new file mode 100644 index 0000000..ec0dae2 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/AbstractGame.java @@ -0,0 +1,108 @@ +package org.toop.framework.gameFramework.model.game; + +import org.toop.framework.gameFramework.model.player.Player; + +import java.util.Arrays; + +public abstract class AbstractGame> implements TurnBasedGame { + private final int playerCount; // How many players are playing + private final Player[] players; + private int turn = 0; // What turn it is in the game + + /** Constant representing an empty position on the board. */ + public static final int EMPTY = -1; + + /** Number of rows in the game board. */ + private final int rowSize; + + /** Number of columns in the game board. */ + private final int columnSize; + + /** The game board stored as a one-dimensional array. */ + private final int[] board; + + + + protected AbstractGame(int rowSize, int columnSize, int playerCount, Player[] players) { + assert rowSize > 0 && columnSize > 0; + + this.rowSize = rowSize; + this.columnSize = columnSize; + + this.players = players; + + board = new int[rowSize * columnSize]; + Arrays.fill(board, EMPTY); + + this.playerCount = playerCount; + } + + protected AbstractGame(AbstractGame other){ + this.rowSize = other.rowSize; + this.columnSize = other.columnSize; + this.board = other.board.clone(); + this.playerCount = other.playerCount; + this.turn = other.turn; + // TODO: Make this a deep copy, add deep copy interface to Player + this.players = other.players; + + } + + public static boolean contains(int[] array, int value) { + // O(n) + for (int element : array){ + if (element == value) return true; + } + return false; + } + + public Player getPlayer(int index) { + return players[index]; + } + + public int getPlayerCount(){return this.playerCount;} + + protected void nextTurn() { + turn += 1; + } + + public int getCurrentTurn() { + return turn % playerCount; + } + + protected void setBoard(int position) { + setBoard(position, getCurrentTurn()); + } + + protected void setBoard(int position, int player) { + this.board[position] = player; + } + + /** + * Returns the number of rows in the board. + * + * @return number of rows + */ + public int getRowSize() { + return this.rowSize; + } + + /** + * Returns the number of columns in the board. + * + * @return number of columns + */ + public int getColumnSize() { + return this.columnSize; + } + + /** + * Returns a copy of the current board state. + * + * @return a cloned array representing the board + */ + public int[] getBoard() { + return this.board.clone(); + } + +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/DeepCopyable.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/DeepCopyable.java new file mode 100644 index 0000000..cf3bfe4 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/DeepCopyable.java @@ -0,0 +1,5 @@ +package org.toop.framework.gameFramework.model.game; + +public interface DeepCopyable> { + T deepCopy(); +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/PlayResult.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayResult.java similarity index 86% rename from framework/src/main/java/org/toop/framework/gameFramework/PlayResult.java rename to framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayResult.java index cc18759..e900ea2 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/PlayResult.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayResult.java @@ -1,4 +1,4 @@ -package org.toop.framework.gameFramework; +package org.toop.framework.gameFramework.model.game; import org.toop.framework.gameFramework.GameState; diff --git a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/IPlayableR.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java similarity index 80% rename from framework/src/main/java/org/toop/framework/gameFramework/interfaces/IPlayableR.java rename to framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java index 932d7a0..f8013c5 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/IPlayableR.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/Playable.java @@ -1,12 +1,11 @@ -package org.toop.framework.gameFramework.interfaces; +package org.toop.framework.gameFramework.model.game; import org.toop.framework.gameFramework.GameState; -import org.toop.framework.gameFramework.PlayResult; /** * Interface for turn-based games that can be played and queried for legal moves. */ -public interface IPlayableR { +public interface Playable { /** * Returns the moves that are currently valid in the game. diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayerProvider.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayerProvider.java new file mode 100644 index 0000000..8db47a3 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/PlayerProvider.java @@ -0,0 +1,7 @@ +package org.toop.framework.gameFramework.model.game; + +import org.toop.framework.gameFramework.model.player.Player; + +public interface PlayerProvider> { + Player getPlayer(int index); +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/SupportsOnlinePlay.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java similarity index 74% rename from framework/src/main/java/org/toop/framework/gameFramework/interfaces/SupportsOnlinePlay.java rename to framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java index f523158..d610296 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/interfaces/SupportsOnlinePlay.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/SupportsOnlinePlay.java @@ -1,4 +1,4 @@ -package org.toop.framework.gameFramework.interfaces; +package org.toop.framework.gameFramework.model.game; import org.toop.framework.networking.events.NetworkEvents; @@ -10,10 +10,10 @@ import org.toop.framework.networking.events.NetworkEvents; public interface SupportsOnlinePlay { /** Called when it is this player's turn to make a move. */ - void yourTurn(NetworkEvents.YourTurnResponse event); + void onYourTurn(NetworkEvents.YourTurnResponse event); /** Called when a move from another player is received. */ - void moveReceived(NetworkEvents.GameMoveResponse event); + void onMoveReceived(NetworkEvents.GameMoveResponse event); /** Called when the game has finished, with the final result. */ void gameFinished(NetworkEvents.GameResultResponse event); diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java new file mode 100644 index 0000000..508637a --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/TurnBasedGame.java @@ -0,0 +1,5 @@ +package org.toop.framework.gameFramework.model.game; + +public interface TurnBasedGame> extends Playable, DeepCopyable, PlayerProvider { + int getCurrentTurn(); +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java new file mode 100644 index 0000000..46a6e6f --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/AbstractThreadBehaviour.java @@ -0,0 +1,36 @@ +package org.toop.framework.gameFramework.model.game.threadBehaviour; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.Player; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Base class for thread-based game behaviours. + *

+ * Provides common functionality for managing game state and execution: + * a running flag, a game reference, and a logger. + * Subclasses implement the actual game-loop logic. + */ +public abstract class AbstractThreadBehaviour> implements ThreadBehaviour { + + /** Indicates whether the game loop or event processing is active. */ + protected final AtomicBoolean isRunning = new AtomicBoolean(); + + /** The game instance controlled by this behaviour. */ + protected final T game; + + /** Logger for the subclass to report errors or debug info. */ + protected final Logger logger = LogManager.getLogger(this.getClass()); + + /** + * Creates a new base behaviour for the specified game. + * + * @param game the turn-based game to control + */ + public AbstractThreadBehaviour(T game) { + this.game = game; + } +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/Controllable.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/Controllable.java new file mode 100644 index 0000000..cefbbd4 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/Controllable.java @@ -0,0 +1,7 @@ +package org.toop.framework.gameFramework.model.game.threadBehaviour; + +public interface Controllable { + void start(); + + void stop(); +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java new file mode 100644 index 0000000..9ec6736 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/game/threadBehaviour/ThreadBehaviour.java @@ -0,0 +1,13 @@ +package org.toop.framework.gameFramework.model.game.threadBehaviour; + +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.AbstractPlayer; +import org.toop.framework.gameFramework.model.player.Player; + +/** + * Strategy interface for controlling game thread behavior. + *

+ * Defines how a game's execution is started, stopped, and which player is active. + */ +public interface ThreadBehaviour> extends Controllable { +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/AIR.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractAI.java similarity index 71% rename from framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/AIR.java rename to framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractAI.java index 623e493..ebeab4c 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/abstractClasses/AIR.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractAI.java @@ -1,6 +1,6 @@ -package org.toop.framework.gameFramework.abstractClasses; +package org.toop.framework.gameFramework.model.player; -import org.toop.framework.gameFramework.interfaces.IAIMoveR; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; /** * Abstract base class for AI implementations for games extending {@link GameR}. @@ -12,6 +12,6 @@ import org.toop.framework.gameFramework.interfaces.IAIMoveR; * * @param the specific type of game this AI can play, extending {@link GameR} */ -public abstract class AIR implements IAIMoveR { +public abstract class AbstractAI implements MoveProvider { // Concrete AI implementations should override findBestMove(T game, int depth) } diff --git a/game/src/main/java/org/toop/game/players/AbstractPlayer.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java similarity index 70% rename from game/src/main/java/org/toop/game/players/AbstractPlayer.java rename to framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java index dc6c56b..077dee3 100644 --- a/game/src/main/java/org/toop/game/players/AbstractPlayer.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/AbstractPlayer.java @@ -1,12 +1,13 @@ -package org.toop.game.players; +package org.toop.framework.gameFramework.model.player; -import org.toop.framework.gameFramework.abstractClasses.GameR; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; /** * Abstract class representing a player in a game. *

* Players are entities that can make moves based on the current state of a game. - * This class implements {@link MakesMove} and serves as a base for concrete * player types, such as human players or AI players. *

*

@@ -14,9 +15,13 @@ import org.toop.framework.gameFramework.abstractClasses.GameR; * specific move logic. *

*/ -public abstract class AbstractPlayer implements MakesMove { +public abstract class AbstractPlayer> implements Player { private int playerIndex = -1; + + private Logger logger = LogManager.getLogger(this.getClass()); + private final String name; + protected AbstractPlayer(String name) { this.name = name; } @@ -32,20 +37,12 @@ public abstract class AbstractPlayer implements MakesMove { * @return an integer representing the chosen move * @throws UnsupportedOperationException if the method is not overridden */ - @Override - public int getMove(GameR gameCopy) { + public int getMove(T gameCopy) { + logger.error("Method getMove not implemented."); throw new UnsupportedOperationException("Not supported yet."); } public String getName(){ return this.name; } - - public int getPlayerIndex() { - return playerIndex; - } - - public void setPlayerIndex(int playerIndex) { - this.playerIndex = playerIndex; - } } diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java new file mode 100644 index 0000000..29529e2 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/MoveProvider.java @@ -0,0 +1,7 @@ +package org.toop.framework.gameFramework.model.player; + +import org.toop.framework.gameFramework.model.game.TurnBasedGame; + +public interface MoveProvider { + int getMove(T game); +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/player/NameProvider.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/NameProvider.java new file mode 100644 index 0000000..850f0f2 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/NameProvider.java @@ -0,0 +1,5 @@ +package org.toop.framework.gameFramework.model.player; + +public interface NameProvider { + String getName(); +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/model/player/Player.java b/framework/src/main/java/org/toop/framework/gameFramework/model/player/Player.java new file mode 100644 index 0000000..bf49cd7 --- /dev/null +++ b/framework/src/main/java/org/toop/framework/gameFramework/model/player/Player.java @@ -0,0 +1,6 @@ +package org.toop.framework.gameFramework.model.player; + +import org.toop.framework.gameFramework.model.game.TurnBasedGame; + +public interface Player> extends NameProvider, MoveProvider { +} diff --git a/framework/src/main/java/org/toop/framework/gameFramework/GUIEvents.java b/framework/src/main/java/org/toop/framework/gameFramework/view/GUIEvents.java similarity index 95% rename from framework/src/main/java/org/toop/framework/gameFramework/GUIEvents.java rename to framework/src/main/java/org/toop/framework/gameFramework/view/GUIEvents.java index bdce829..a337869 100644 --- a/framework/src/main/java/org/toop/framework/gameFramework/GUIEvents.java +++ b/framework/src/main/java/org/toop/framework/gameFramework/view/GUIEvents.java @@ -1,4 +1,4 @@ -package org.toop.framework.gameFramework; +package org.toop.framework.gameFramework.view; import org.toop.framework.eventbus.events.EventsBase; import org.toop.framework.eventbus.events.GenericEvent; diff --git a/game/src/main/java/org/toop/game/AI.java b/game/src/main/java/org/toop/game/AI.java deleted file mode 100644 index 3f8fac4..0000000 --- a/game/src/main/java/org/toop/game/AI.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.toop.game; - -import org.toop.game.interfaces.IAIMove; - -public abstract class AI implements IAIMove { -} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/Connect4/Connect4.java b/game/src/main/java/org/toop/game/Connect4/Connect4.java deleted file mode 100644 index 2543d3e..0000000 --- a/game/src/main/java/org/toop/game/Connect4/Connect4.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.toop.game.Connect4; - -import org.toop.game.TurnBasedGame; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -import java.util.ArrayList; - -public class Connect4 extends TurnBasedGame { - private int movesLeft; - - public Connect4() { - super(6, 7, 2); - movesLeft = this.getBoard().length; - } - - public Connect4(Connect4 other) { - super(other); - movesLeft = other.movesLeft; - } - - @Override - public Move[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList<>(); - final char currentValue = getCurrentValue(); - - for (int i = 0; i < this.getColumnSize(); i++) { - if (this.getBoard()[i] == EMPTY) { - legalMoves.add(new Move(i, currentValue)); - } - } - return legalMoves.toArray(new Move[0]); - } - - @Override - public GameState play(Move move) { - assert move != null; - assert move.position() >= 0 && move.position() < this.getBoard().length; - assert move.value() == getCurrentValue(); - - int lowestEmptySpot = move.position(); - for (int i = 0; i < this.getRowSize(); i++) { - int checkMovePosition = move.position() + this.getColumnSize() * i; - if (checkMovePosition < this.getBoard().length) { - if (this.getBoard()[checkMovePosition] == EMPTY) { - lowestEmptySpot = checkMovePosition; - } - } - } - this.getBoard()[lowestEmptySpot] = move.value(); - movesLeft--; - - if (checkForWin()) { - return GameState.WIN; - } - - nextTurn(); - - - return GameState.NORMAL; - } - - private boolean checkForWin() { - char[][] boardGrid = makeBoardAGrid(); - - for (int row = 0; row < this.getRowSize(); row++) { - for (int col = 0; col < this.getColumnSize(); col++) { - char cell = boardGrid[row][col]; - if (cell == ' ' || cell == 0) continue; - - if (col + 3 < this.getColumnSize() && - cell == boardGrid[row][col + 1] && - cell == boardGrid[row][col + 2] && - cell == boardGrid[row][col + 3]) { - return true; - } - - if (row + 3 < this.getRowSize() && - cell == boardGrid[row + 1][col] && - cell == boardGrid[row + 2][col] && - cell == boardGrid[row + 3][col]) { - return true; - } - - if (row + 3 < this.getRowSize() && col + 3 < this.getColumnSize() && - cell == boardGrid[row + 1][col + 1] && - cell == boardGrid[row + 2][col + 2] && - cell == boardGrid[row + 3][col + 3]) { - return true; - } - - if (row + 3 < this.getRowSize() && col - 3 >= 0 && - cell == boardGrid[row + 1][col - 1] && - cell == boardGrid[row + 2][col - 2] && - cell == boardGrid[row + 3][col - 3]) { - return true; - } - } - } - return false; - } - - private char[][] makeBoardAGrid() { - char[][] boardGrid = new char[this.getRowSize()][this.getColumnSize()]; - for (int i = 0; i < this.getRowSize()*this.getColumnSize(); i++) { - boardGrid[i / this.getColumnSize()][i % this.getColumnSize()] = this.getBoard()[i]; //this.getBoard()Grid[y -> row] [x -> column] - } - return boardGrid; - } - - private char getCurrentValue() { - return this.getCurrentTurn() == 0 ? 'X' : 'O'; - } -} - - diff --git a/game/src/main/java/org/toop/game/Connect4/Connect4AI.java b/game/src/main/java/org/toop/game/Connect4/Connect4AI.java deleted file mode 100644 index a7e8b83..0000000 --- a/game/src/main/java/org/toop/game/Connect4/Connect4AI.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.toop.game.Connect4; - -import org.toop.game.AI; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -public class Connect4AI extends AI { - - - public Move findBestMove(Connect4 game, int depth) { - assert game != null; - assert depth >= 0; - - final Move[] legalMoves = game.getLegalMoves(); - - if (legalMoves.length <= 0) { - return null; - } - - int bestScore = -depth; - Move bestMove = null; - - for (final Move move : legalMoves) { - final int score = getMoveScore(game, depth, move, true); - - if (score > bestScore) { - bestMove = move; - bestScore = score; - } - } - - return bestMove != null? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)]; - } - - private int getMoveScore(Connect4 game, int depth, Move move, boolean maximizing) { - final Connect4 copy = new Connect4(game); - final GameState state = copy.play(move); - - switch (state) { - case GameState.DRAW: return 0; - case GameState.WIN: return maximizing? depth + 1 : -depth - 1; - } - - if (depth <= 0) { - return 0; - } - - final Move[] legalMoves = copy.getLegalMoves(); - int score = maximizing? depth + 1 : -depth - 1; - - for (final Move next : legalMoves) { - if (maximizing) { - score = Math.min(score, getMoveScore(copy, depth - 1, next, false)); - } else { - score = Math.max(score, getMoveScore(copy, depth - 1, next, true)); - } - } - - return score; - } - - -} diff --git a/game/src/main/java/org/toop/game/Game.java b/game/src/main/java/org/toop/game/Game.java deleted file mode 100644 index d0e8b59..0000000 --- a/game/src/main/java/org/toop/game/Game.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.toop.game; - -import org.toop.game.interfaces.IPlayable; -import org.toop.game.records.Move; - -import java.util.Arrays; - -public abstract class Game implements IPlayable { - - public static final char EMPTY = (char)0; // Constant - - private final int rowSize; - private final int columnSize; - private final char[] board; - - protected Game(int rowSize, int columnSize) { - assert rowSize > 0 && columnSize > 0; - - this.rowSize = rowSize; - this.columnSize = columnSize; - - board = new char[rowSize * columnSize]; - Arrays.fill(board, EMPTY); - } - - protected Game(Game other) { - rowSize = other.rowSize; - columnSize = other.columnSize; - board = Arrays.copyOf(other.board, other.board.length); - } - - public int getRowSize() {return this.rowSize;} - - public int getColumnSize() {return this.columnSize;} - - public char[] getBoard() {return this.board;} - - protected void setBoard(Move move){this.board[move.position()] = move.value();} - -} diff --git a/game/src/main/java/org/toop/game/GameThreadBehaviour/GameThreadStrategy.java b/game/src/main/java/org/toop/game/GameThreadBehaviour/GameThreadStrategy.java deleted file mode 100644 index 537c308..0000000 --- a/game/src/main/java/org/toop/game/GameThreadBehaviour/GameThreadStrategy.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.toop.game.GameThreadBehaviour; - -import org.toop.game.players.AbstractPlayer; - -/** - * Strategy interface for controlling game thread behavior. - *

- * Defines how a game's execution is started, stopped, and which player is active. - */ -public interface GameThreadStrategy { - - /** Starts the game loop or execution according to the strategy. */ - void start(); - - /** Stops the game loop or execution according to the strategy. */ - void stop(); - - /** - * Returns the player whose turn it currently is. - * - * @return the current active {@link AbstractPlayer} - */ - AbstractPlayer getCurrentPlayer(); -} diff --git a/game/src/main/java/org/toop/game/GameThreadBehaviour/ThreadBehaviourBase.java b/game/src/main/java/org/toop/game/GameThreadBehaviour/ThreadBehaviourBase.java deleted file mode 100644 index b70bd50..0000000 --- a/game/src/main/java/org/toop/game/GameThreadBehaviour/ThreadBehaviourBase.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.toop.game.GameThreadBehaviour; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; -import org.toop.game.players.AbstractPlayer; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Base class for thread-based game behaviours. - *

- * Provides common functionality for managing game state and execution: - * a running flag, a game reference, and a logger. - * Subclasses implement the actual game-loop logic. - */ -public abstract class ThreadBehaviourBase implements GameThreadStrategy { - private final AbstractPlayer[] players; - - /** Indicates whether the game loop or event processing is active. */ - protected final AtomicBoolean isRunning = new AtomicBoolean(); - - /** The game instance controlled by this behaviour. */ - protected final TurnBasedGameR game; - - /** Logger for the subclass to report errors or debug info. */ - protected final Logger logger = LogManager.getLogger(this.getClass()); - - /** - * Creates a new base behaviour for the specified game. - * - * @param game the turn-based game to control - */ - public ThreadBehaviourBase(TurnBasedGameR game, AbstractPlayer[] players) { - this.game = game; - this.players = players; - } - - /** - * Returns the player whose turn it currently is. - * - * @return the current active player - */ - @Override - public AbstractPlayer getCurrentPlayer() { - return players[game.getCurrentTurn()]; - } - - public AbstractPlayer getFirstPlayerWithName(String name) { - for (AbstractPlayer player : players){ - if (player.getName().equals(name)){ - return player; - } - } - return null; - } -} diff --git a/game/src/main/java/org/toop/game/Move.java b/game/src/main/java/org/toop/game/Move.java new file mode 100644 index 0000000..62a2b1a --- /dev/null +++ b/game/src/main/java/org/toop/game/Move.java @@ -0,0 +1,3 @@ +package org.toop.game; +// TODO: Remove this, only used in ReversiCanvas. Needs to not +public record Move(int position, char value) {} diff --git a/game/src/main/java/org/toop/game/TurnBasedGame.java b/game/src/main/java/org/toop/game/TurnBasedGame.java deleted file mode 100644 index 2e51519..0000000 --- a/game/src/main/java/org/toop/game/TurnBasedGame.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.toop.game; - -public abstract class TurnBasedGame extends Game { - private final int playerCount; // How many players are playing - private int turn = 0; // What turn it is in the game - - protected TurnBasedGame(int rowSize, int columnSize, int playerCount) { - super(rowSize, columnSize); - this.playerCount = playerCount; - } - - protected TurnBasedGame(TurnBasedGame other) { - super(other); - playerCount = other.playerCount; - turn = other.turn; - } - - protected void nextTurn() { - turn += 1; - } - - public int getCurrentTurn() { - return turn % playerCount; - } -} diff --git a/game/src/main/java/org/toop/game/GameThreadBehaviour/LocalFixedRateThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java similarity index 75% rename from game/src/main/java/org/toop/game/GameThreadBehaviour/LocalFixedRateThreadBehaviour.java rename to game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java index cf6a90f..dc13519 100644 --- a/game/src/main/java/org/toop/game/GameThreadBehaviour/LocalFixedRateThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/LocalFixedRateThreadBehaviour.java @@ -1,11 +1,12 @@ -package org.toop.game.GameThreadBehaviour; +package org.toop.game.gameThreads; import org.toop.framework.eventbus.EventFlow; import org.toop.framework.gameFramework.GameState; -import org.toop.framework.gameFramework.PlayResult; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; -import org.toop.framework.gameFramework.GUIEvents; -import org.toop.game.players.AbstractPlayer; +import org.toop.framework.gameFramework.model.game.PlayResult; +import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour; +import org.toop.framework.gameFramework.view.GUIEvents; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.Player; /** * Handles local turn-based game logic at a fixed update rate. @@ -13,10 +14,10 @@ import org.toop.game.players.AbstractPlayer; * Runs a separate thread that executes game turns at a fixed frequency (default 60 updates/sec), * applying player moves, updating the game state, and dispatching UI events. */ -public class LocalFixedRateThreadBehaviour extends ThreadBehaviourBase implements Runnable { +public class LocalFixedRateThreadBehaviour> extends AbstractThreadBehaviour implements Runnable { /** All players participating in the game. */ - private final AbstractPlayer[] players; + private final Player[] players; /** * Creates a fixed-rate behaviour for a local turn-based game. @@ -24,8 +25,8 @@ public class LocalFixedRateThreadBehaviour extends ThreadBehaviourBase implement * @param game the game instance * @param players the list of players in turn order */ - public LocalFixedRateThreadBehaviour(TurnBasedGameR game, AbstractPlayer[] players) { - super(game, players); + public LocalFixedRateThreadBehaviour(T game, Player[] players) { + super(game); this.players = players; } @@ -60,8 +61,8 @@ public class LocalFixedRateThreadBehaviour extends ThreadBehaviourBase implement if (now >= nextUpdate) { nextUpdate += UPDATE_INTERVAL; - AbstractPlayer currentPlayer = getCurrentPlayer(); - int move = currentPlayer.getMove(game.clone()); + Player currentPlayer = game.getPlayer(game.getCurrentTurn()); + int move = currentPlayer.getMove(game.deepCopy()); PlayResult result = game.play(move); new EventFlow().addPostEvent(GUIEvents.RefreshGameCanvas.class).postEvent(); @@ -85,10 +86,4 @@ public class LocalFixedRateThreadBehaviour extends ThreadBehaviourBase implement } } } - - /** Returns the player whose turn it currently is. */ - @Override - public AbstractPlayer getCurrentPlayer() { - return players[game.getCurrentTurn()]; - } } diff --git a/game/src/main/java/org/toop/game/GameThreadBehaviour/LocalThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java similarity index 72% rename from game/src/main/java/org/toop/game/GameThreadBehaviour/LocalThreadBehaviour.java rename to game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java index f0d4c0a..82ac0ca 100644 --- a/game/src/main/java/org/toop/game/GameThreadBehaviour/LocalThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/LocalThreadBehaviour.java @@ -1,11 +1,12 @@ -package org.toop.game.GameThreadBehaviour; +package org.toop.game.gameThreads; import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.GUIEvents; -import org.toop.framework.gameFramework.PlayResult; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour; +import org.toop.framework.gameFramework.view.GUIEvents; +import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.GameState; -import org.toop.game.players.AbstractPlayer; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.Player; /** * Handles local turn-based game logic in its own thread. @@ -13,7 +14,7 @@ import org.toop.game.players.AbstractPlayer; * Repeatedly gets the current player's move, applies it to the game, * updates the UI, and stops when the game ends or {@link #stop()} is called. */ -public class LocalThreadBehaviour extends ThreadBehaviourBase implements Runnable { +public class LocalThreadBehaviour> extends AbstractThreadBehaviour implements Runnable { /** * Creates a new behaviour for a local turn-based game. @@ -21,8 +22,8 @@ public class LocalThreadBehaviour extends ThreadBehaviourBase implements Runnabl * @param game the game instance * @param players the list of players in turn order */ - public LocalThreadBehaviour(TurnBasedGameR game, AbstractPlayer[] players) { - super(game, players); + public LocalThreadBehaviour(T game, Player[] players) { + super(game); } /** Starts the game loop in a new thread. */ @@ -46,8 +47,8 @@ public class LocalThreadBehaviour extends ThreadBehaviourBase implements Runnabl @Override public void run() { while (isRunning.get()) { - AbstractPlayer currentPlayer = getCurrentPlayer(); - int move = currentPlayer.getMove(game.clone()); + Player currentPlayer = game.getPlayer(game.getCurrentTurn()); + int move = currentPlayer.getMove(game.deepCopy()); PlayResult result = game.play(move); new EventFlow().addPostEvent(GUIEvents.RefreshGameCanvas.class).postEvent(); diff --git a/game/src/main/java/org/toop/game/GameThreadBehaviour/OnlineThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java similarity index 61% rename from game/src/main/java/org/toop/game/GameThreadBehaviour/OnlineThreadBehaviour.java rename to game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java index 7049d4d..fcce936 100644 --- a/game/src/main/java/org/toop/game/GameThreadBehaviour/OnlineThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/OnlineThreadBehaviour.java @@ -1,12 +1,13 @@ -package org.toop.game.GameThreadBehaviour; +package org.toop.game.gameThreads; import org.toop.framework.eventbus.EventFlow; -import org.toop.framework.gameFramework.GUIEvents; -import org.toop.framework.gameFramework.abstractClasses.GameR; +import org.toop.framework.gameFramework.model.game.threadBehaviour.AbstractThreadBehaviour; +import org.toop.framework.gameFramework.view.GUIEvents; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; -import org.toop.framework.gameFramework.interfaces.SupportsOnlinePlay; -import org.toop.game.players.AbstractPlayer; +import org.toop.framework.gameFramework.model.game.SupportsOnlinePlay; +import org.toop.framework.gameFramework.model.player.Player; import org.toop.game.players.OnlinePlayer; /** @@ -15,25 +16,27 @@ import org.toop.game.players.OnlinePlayer; * Reacts to server events, sending moves and updating the game state * for the local player while receiving moves from other players. */ -public class OnlineThreadBehaviour extends ThreadBehaviourBase implements SupportsOnlinePlay { +public class OnlineThreadBehaviour> extends AbstractThreadBehaviour implements SupportsOnlinePlay { /** The local player controlled by this client. */ - private AbstractPlayer mainPlayer; + private final Player mainPlayer; + private final int playerTurn; /** * Creates behaviour and sets the first local player * (non-online player) from the given array. */ - public OnlineThreadBehaviour(TurnBasedGameR game, AbstractPlayer[] players) { - super(game, players); - this.mainPlayer = getFirstNotOnlinePlayer(players); + public OnlineThreadBehaviour(T game, Player[] players) { + super(game); + this.playerTurn = getFirstNotOnlinePlayer(players); + this.mainPlayer = players[this.playerTurn]; } /** Finds the first non-online player in the array. */ - private AbstractPlayer getFirstNotOnlinePlayer(AbstractPlayer[] players) { - for (AbstractPlayer player : players) { - if (!(player instanceof OnlinePlayer)) { - return player; + private int getFirstNotOnlinePlayer(Player[] players) { + for (int i = 0; i < players.length; i++) { + if (!(players[i] instanceof OnlinePlayer)) { + return i; } } throw new RuntimeException("All players are online players"); @@ -56,9 +59,9 @@ public class OnlineThreadBehaviour extends ThreadBehaviourBase implements Suppor * Sends the generated move back to the server. */ @Override - public void yourTurn(NetworkEvents.YourTurnResponse event) { + public void onYourTurn(NetworkEvents.YourTurnResponse event) { if (!isRunning.get()) return; - int move = mainPlayer.getMove(game.clone()); + int move = mainPlayer.getMove(game.deepCopy()); new EventFlow().addPostEvent(NetworkEvents.SendMove.class, event.clientId(), (short) move).postEvent(); } @@ -67,7 +70,7 @@ public class OnlineThreadBehaviour extends ThreadBehaviourBase implements Suppor * Updates the game state and triggers a UI refresh. */ @Override - public void moveReceived(NetworkEvents.GameMoveResponse event) { + public void onMoveReceived(NetworkEvents.GameMoveResponse event) { if (!isRunning.get()) return; game.play(Integer.parseInt(event.move())); new EventFlow().addPostEvent(GUIEvents.RefreshGameCanvas.class).postEvent(); @@ -80,9 +83,9 @@ public class OnlineThreadBehaviour extends ThreadBehaviourBase implements Suppor @Override public void gameFinished(NetworkEvents.GameResultResponse event) { switch(event.condition().toUpperCase()){ - case "WIN" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, mainPlayer.getPlayerIndex()).postEvent(); - case "DRAW" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, false, TurnBasedGameR.EMPTY).postEvent(); - case "LOSS" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, (mainPlayer.getPlayerIndex() + 1)%2).postEvent(); + case "WIN" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, playerTurn).postEvent(); + case "DRAW" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, false, AbstractGame.EMPTY).postEvent(); + case "LOSS" -> new EventFlow().addPostEvent(GUIEvents.GameEnded.class, true, (playerTurn + 1)%2).postEvent(); default -> { logger.error("Invalid condition"); throw new RuntimeException("Unknown condition"); diff --git a/game/src/main/java/org/toop/game/GameThreadBehaviour/OnlineWithSleepThreadBehaviour.java b/game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java similarity index 72% rename from game/src/main/java/org/toop/game/GameThreadBehaviour/OnlineWithSleepThreadBehaviour.java rename to game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java index f550e5a..f4e2627 100644 --- a/game/src/main/java/org/toop/game/GameThreadBehaviour/OnlineWithSleepThreadBehaviour.java +++ b/game/src/main/java/org/toop/game/gameThreads/OnlineWithSleepThreadBehaviour.java @@ -1,8 +1,8 @@ -package org.toop.game.GameThreadBehaviour; +package org.toop.game.gameThreads; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.AbstractGame; import org.toop.framework.networking.events.NetworkEvents; -import org.toop.game.players.AbstractPlayer; +import org.toop.framework.gameFramework.model.player.AbstractPlayer; /** * Online thread behaviour that adds a fixed delay before processing @@ -19,7 +19,7 @@ public class OnlineWithSleepThreadBehaviour extends OnlineThreadBehaviour { * @param game the online-capable turn-based game * @param players the list of local and remote players */ - public OnlineWithSleepThreadBehaviour(TurnBasedGameR game, AbstractPlayer[] players) { + public OnlineWithSleepThreadBehaviour(AbstractGame game, AbstractPlayer[] players) { super(game, players); } @@ -29,7 +29,7 @@ public class OnlineWithSleepThreadBehaviour extends OnlineThreadBehaviour { * @param event the network event indicating it's this client's turn */ @Override - public void yourTurn(NetworkEvents.YourTurnResponse event) { + public void onYourTurn(NetworkEvents.YourTurnResponse event) { try { Thread.sleep(1000); @@ -37,6 +37,6 @@ public class OnlineWithSleepThreadBehaviour extends OnlineThreadBehaviour { e.printStackTrace(); } - super.yourTurn(event); + super.onYourTurn(event); } } diff --git a/game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java b/game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java new file mode 100644 index 0000000..4a038ab --- /dev/null +++ b/game/src/main/java/org/toop/game/games/reversi/ReversiAIR.java @@ -0,0 +1,15 @@ +package org.toop.game.games.reversi; + +import org.toop.framework.gameFramework.model.player.AbstractAI; + +import java.util.Random; + +public final class ReversiAIR extends AbstractAI { + public int getMove(ReversiR game) { + int[] moves = game.getLegalMoves(); + if (moves.length == 0) return -1; + + int inty = new Random().nextInt(0, moves.length); + return moves[inty]; + } +} diff --git a/game/src/main/java/org/toop/game/reversi/ReversiR.java b/game/src/main/java/org/toop/game/games/reversi/ReversiR.java similarity index 94% rename from game/src/main/java/org/toop/game/reversi/ReversiR.java rename to game/src/main/java/org/toop/game/games/reversi/ReversiR.java index d384384..56f5e28 100644 --- a/game/src/main/java/org/toop/game/reversi/ReversiR.java +++ b/game/src/main/java/org/toop/game/games/reversi/ReversiR.java @@ -1,8 +1,9 @@ -package org.toop.game.reversi; +package org.toop.game.games.reversi; import org.toop.framework.gameFramework.GameState; -import org.toop.framework.gameFramework.PlayResult; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.PlayResult; +import org.toop.framework.gameFramework.model.game.AbstractGame; +import org.toop.framework.gameFramework.model.player.Player; import java.awt.*; import java.util.ArrayList; @@ -11,21 +12,21 @@ import java.util.HashSet; import java.util.Set; -public final class ReversiR extends TurnBasedGameR { +public final class ReversiR extends AbstractGame { private int movesTaken; private Set filledCells = new HashSet<>(); private int[] mostRecentlyFlippedPieces; - // TODO: Don't hardcore for two players :) - public record Score(int player1Score, int player2Score) {} - @Override - public ReversiR clone() { + public ReversiR deepCopy() { return new ReversiR(this); } - public ReversiR() { - super(8, 8, 2); + // TODO: Don't hardcore for two players :) + public record Score(int player1Score, int player2Score) {} + + public ReversiR(Player[] players) { + super(8, 8, 2, players); addStartPieces(); } @@ -38,10 +39,10 @@ public final class ReversiR extends TurnBasedGameR { private void addStartPieces() { - this.setBoardPosition(27, 1); - this.setBoardPosition(28, 0); - this.setBoardPosition(35, 0); - this.setBoardPosition(36, 1); + this.setBoard(27, 1); + this.setBoard(28, 0); + this.setBoard(35, 0); + this.setBoard(36, 1); updateFilledCellsSet(); } private void updateFilledCellsSet() { @@ -138,7 +139,7 @@ public final class ReversiR extends TurnBasedGameR { } private boolean gameOver(){ - ReversiR gameCopy = clone(); + ReversiR gameCopy = deepCopy(); return gameCopy.getLegalMoves().length == 0 && gameCopy.skipTurn().getLegalMoves().length == 0; } @@ -202,7 +203,7 @@ public final class ReversiR extends TurnBasedGameR { if (getLegalMoves().length == 0){ PlayResult result; // Check if next turn is also a force skip - if (clone().skipTurn().getLegalMoves().length == 0){ + if (deepCopy().skipTurn().getLegalMoves().length == 0){ // Game over int winner = getWinner(); result = new PlayResult(winner == EMPTY ? GameState.DRAW : GameState.WIN, winner); diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java b/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java similarity index 90% rename from game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java rename to game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java index 8f5e2cf..07271b1 100644 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAIR.java +++ b/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeAIR.java @@ -1,7 +1,7 @@ -package org.toop.game.tictactoe; +package org.toop.game.games.tictactoe; -import org.toop.framework.gameFramework.abstractClasses.AIR; -import org.toop.framework.gameFramework.PlayResult; +import org.toop.framework.gameFramework.model.player.AbstractAI; +import org.toop.framework.gameFramework.model.game.PlayResult; import org.toop.framework.gameFramework.GameState; /** @@ -13,7 +13,7 @@ import org.toop.framework.gameFramework.GameState; * opening or when no clear best move is found. *

*/ -public final class TicTacToeAIR extends AIR { +public final class TicTacToeAIR extends AbstractAI { /** * Determines the best move for the given Tic-Tac-Toe game state. @@ -27,10 +27,9 @@ public final class TicTacToeAIR extends AIR { * @param depth the depth of lookahead for evaluating moves (non-negative) * @return the index of the best move, or -1 if no moves are available */ - @Override - public int findBestMove(TicTacToeR game, int depth) { + public int getMove(TicTacToeR game) { + int depth = 9; assert game != null; - assert depth >= 0; final int[] legalMoves = game.getLegalMoves(); // If there are no moves, return -1 @@ -73,7 +72,7 @@ public final class TicTacToeAIR extends AIR { * @return the score of the move */ private int getMoveScore(TicTacToeR game, int depth, int move, boolean maximizing) { - final TicTacToeR copy = game.clone(); + final TicTacToeR copy = game.deepCopy(); final PlayResult result = copy.play(move); GameState state = result.state(); diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java b/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java similarity index 87% rename from game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java rename to game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java index 3aa1b4d..a04b5f9 100644 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToeR.java +++ b/game/src/main/java/org/toop/game/games/tictactoe/TicTacToeR.java @@ -1,17 +1,18 @@ -package org.toop.game.tictactoe; +package org.toop.game.games.tictactoe; -import org.toop.framework.gameFramework.PlayResult; -import org.toop.framework.gameFramework.abstractClasses.TurnBasedGameR; +import org.toop.framework.gameFramework.model.game.PlayResult; +import org.toop.framework.gameFramework.model.game.AbstractGame; import org.toop.framework.gameFramework.GameState; +import org.toop.framework.gameFramework.model.player.Player; import java.util.ArrayList; import java.util.Objects; -public final class TicTacToeR extends TurnBasedGameR { +public final class TicTacToeR extends AbstractGame { private int movesLeft; - public TicTacToeR() { - super(3, 3, 2); + public TicTacToeR(Player[] players) { + super(3, 3, 2, players); movesLeft = this.getBoard().length; } @@ -101,7 +102,7 @@ public final class TicTacToeR extends TurnBasedGameR { private boolean checkForEarlyDraw() { for (final int move : this.getLegalMoves()) { - final TicTacToeR copy = this.clone(); + final TicTacToeR copy = this.deepCopy(); if (copy.play(move).state() == GameState.WIN || !copy.checkForEarlyDraw()) { return false; @@ -111,8 +112,7 @@ public final class TicTacToeR extends TurnBasedGameR { return true; } - @Override - public TicTacToeR clone() { + public TicTacToeR deepCopy() { return new TicTacToeR(this); } } diff --git a/game/src/main/java/org/toop/game/interfaces/IAIMove.java b/game/src/main/java/org/toop/game/interfaces/IAIMove.java deleted file mode 100644 index 491f7bb..0000000 --- a/game/src/main/java/org/toop/game/interfaces/IAIMove.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.toop.game.interfaces; - -import org.toop.game.Game; -import org.toop.game.records.Move; - -public interface IAIMove { - Move findBestMove(T game, int depth); -} diff --git a/game/src/main/java/org/toop/game/interfaces/IPlayable.java b/game/src/main/java/org/toop/game/interfaces/IPlayable.java deleted file mode 100644 index 2c54cd6..0000000 --- a/game/src/main/java/org/toop/game/interfaces/IPlayable.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.toop.game.interfaces; - -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -public interface IPlayable { - Move[] getLegalMoves(); - GameState play(Move move); -} diff --git a/game/src/main/java/org/toop/game/players/ArtificialPlayer.java b/game/src/main/java/org/toop/game/players/ArtificialPlayer.java index 5fba5ae..49fff2d 100644 --- a/game/src/main/java/org/toop/game/players/ArtificialPlayer.java +++ b/game/src/main/java/org/toop/game/players/ArtificialPlayer.java @@ -1,28 +1,30 @@ package org.toop.game.players; -import org.toop.framework.gameFramework.abstractClasses.AIR; -import org.toop.framework.gameFramework.abstractClasses.GameR; +import org.toop.framework.gameFramework.model.player.AbstractAI; +import org.toop.framework.gameFramework.model.player.AbstractPlayer; +import org.toop.framework.gameFramework.model.player.MoveProvider; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; /** * Represents a player controlled by an AI in a game. *

- * This player uses an {@link AIR} instance to determine its moves. The generic + * This player uses an {@link AbstractAI} instance to determine its moves. The generic * parameter {@code T} specifies the type of {@link GameR} the AI can handle. *

* * @param the specific type of game this AI player can play */ -public class ArtificialPlayer extends AbstractPlayer { +public class ArtificialPlayer> extends AbstractPlayer { /** The AI instance used to calculate moves. */ - private final AIR ai; + private final MoveProvider ai; /** * Constructs a new ArtificialPlayer using the specified AI. * * @param ai the AI instance that determines moves for this player */ - public ArtificialPlayer(AIR ai, String name) { + public ArtificialPlayer(MoveProvider ai, String name) { super(name); this.ai = ai; } @@ -39,8 +41,7 @@ public class ArtificialPlayer extends AbstractPlayer { * @return the integer representing the chosen move * @throws ClassCastException if {@code gameCopy} is not of type {@code T} */ - @Override - public int getMove(GameR gameCopy) { - return ai.findBestMove((T) gameCopy, 9); // TODO: Make depth configurable + public int getMove(T gameCopy) { + return ai.getMove(gameCopy); } } diff --git a/game/src/main/java/org/toop/game/players/LocalPlayer.java b/game/src/main/java/org/toop/game/players/LocalPlayer.java index daa6dff..e83b3f4 100644 --- a/game/src/main/java/org/toop/game/players/LocalPlayer.java +++ b/game/src/main/java/org/toop/game/players/LocalPlayer.java @@ -1,11 +1,12 @@ package org.toop.game.players; -import org.toop.framework.gameFramework.abstractClasses.GameR; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.AbstractPlayer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class LocalPlayer extends AbstractPlayer { +public class LocalPlayer> extends AbstractPlayer { // Future can be used with event system, IF unsubscribeAfterSuccess works... // private CompletableFuture LastMove = new CompletableFuture<>(); @@ -16,7 +17,7 @@ public class LocalPlayer extends AbstractPlayer { } @Override - public int getMove(GameR gameCopy) { + public int getMove(T gameCopy) { return getValidMove(gameCopy); } @@ -30,7 +31,7 @@ public class LocalPlayer extends AbstractPlayer { return false; } - private int getMove2(GameR gameCopy) { + private int getMove2(T gameCopy) { LastMove = new CompletableFuture<>(); int move = -1; try { @@ -42,16 +43,16 @@ public class LocalPlayer extends AbstractPlayer { return move; } - protected int getValidMove(GameR gameCopy){ + protected int getValidMove(T gameCopy){ // Get this player's valid moves int[] validMoves = gameCopy.getLegalMoves(); // Make sure provided move is valid // TODO: Limit amount of retries? // TODO: Stop copying game so many times - int move = getMove2(gameCopy.clone()); + int move = getMove2(gameCopy.deepCopy()); while (!contains(validMoves, move)) { System.out.println("Not a valid move, try again"); - move = getMove2(gameCopy.clone()); + move = getMove2(gameCopy.deepCopy()); } return move; } diff --git a/game/src/main/java/org/toop/game/players/MakesMove.java b/game/src/main/java/org/toop/game/players/MakesMove.java deleted file mode 100644 index 750de07..0000000 --- a/game/src/main/java/org/toop/game/players/MakesMove.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.toop.game.players; - -import org.toop.framework.gameFramework.abstractClasses.GameR; - -/** - * Interface representing an entity capable of making a move in a game. - *

- * Any class implementing this interface should provide logic to determine - * the next move given a snapshot of the current game state. - *

- */ -public interface MakesMove { - - /** - * Determines the next move based on the provided game state. - * - * @param gameCopy a copy or snapshot of the current game state - * (never null) - * @return an integer representing the chosen move. - * The interpretation of this value depends on the specific game. - */ - int getMove(GameR gameCopy); -} diff --git a/game/src/main/java/org/toop/game/players/OnlinePlayer.java b/game/src/main/java/org/toop/game/players/OnlinePlayer.java index f7cc4ed..148e19c 100644 --- a/game/src/main/java/org/toop/game/players/OnlinePlayer.java +++ b/game/src/main/java/org/toop/game/players/OnlinePlayer.java @@ -1,5 +1,8 @@ package org.toop.game.players; +import org.toop.framework.gameFramework.model.game.TurnBasedGame; +import org.toop.framework.gameFramework.model.player.AbstractPlayer; + /** * Represents a player controlled remotely or over a network. *

@@ -8,7 +11,7 @@ package org.toop.game.players; * Currently, this class is a placeholder and does not implement move logic. *

*/ -public class OnlinePlayer extends AbstractPlayer { +public class OnlinePlayer> extends AbstractPlayer { /** * Constructs a new OnlinePlayer. diff --git a/game/src/main/java/org/toop/game/records/Move.java b/game/src/main/java/org/toop/game/records/Move.java deleted file mode 100644 index 3775598..0000000 --- a/game/src/main/java/org/toop/game/records/Move.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.toop.game.records; - -public record Move(int position, char value) {} diff --git a/game/src/main/java/org/toop/game/reversi/Reversi.java b/game/src/main/java/org/toop/game/reversi/Reversi.java deleted file mode 100644 index d81a629..0000000 --- a/game/src/main/java/org/toop/game/reversi/Reversi.java +++ /dev/null @@ -1,229 +0,0 @@ -package org.toop.game.reversi; - -import org.toop.game.TurnBasedGame; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -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; - private Set filledCells = new HashSet<>(); - private Move[] mostRecentlyFlippedPieces; - - public record Score(int player1Score, int player2Score) {} - - public Reversi() { - super(8, 8, 2); - addStartPieces(); - } - - public Reversi(Reversi other) { - super(other); - this.movesTaken = other.movesTaken; - this.filledCells = other.filledCells; - this.mostRecentlyFlippedPieces = other.mostRecentlyFlippedPieces; - } - - - private void addStartPieces() { - this.setBoard(new Move(27, 'W')); - this.setBoard(new Move(28, 'B')); - this.setBoard(new Move(35, 'B')); - this.setBoard(new Move(36, 'W')); - updateFilledCellsSet(); - } - private void updateFilledCellsSet() { - for (int i = 0; i < 64; i++) { - if (this.getBoard()[i] == 'W' || this.getBoard()[i] == 'B') { - filledCells.add(new Point(i % this.getColumnSize(), i / this.getRowSize())); - } - } - } - - @Override - public Move[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList<>(); - char[][] boardGrid = makeBoardAGrid(); - char currentPlayer = (this.getCurrentTurn()==0) ? 'B' : 'W'; - Set adjCell = getAdjacentCells(boardGrid); - for (Point point : adjCell){ - Move[] moves = getFlipsForPotentialMove(point,currentPlayer); - int score = moves.length; - if (score > 0){ - legalMoves.add(new Move(point.x + point.y * this.getRowSize(), currentPlayer)); - } - } - return legalMoves.toArray(new Move[0]); - } - - private Set getAdjacentCells(char[][] boardGrid) { - Set 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] == 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 currentPlayer) { - final ArrayList movesToFlip = new ArrayList<>(); - for (int deltaColumn = -1; deltaColumn <= 1; deltaColumn++) { //for all directions - for (int deltaRow = -1; deltaRow <= 1; deltaRow++) { - if (deltaColumn == 0 && deltaRow == 0){ - continue; - } - Move[] moves = getFlipsInDirection(point,makeBoardAGrid(),currentPlayer,deltaColumn,deltaRow); - if (moves != null) { //getFlipsInDirection - 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 movesToFlip = new ArrayList<>(); - int x = point.x + dirX; - int y = point.y + dirY; - - if (!isOnBoard(x, y) || boardGrid[y][x] != opponent) { //there must first be an opponents tile - return null; - } - - while (isOnBoard(x, y) && boardGrid[y][x] == opponent) { //count the opponents tiles in this direction - - movesToFlip.add(new Move(x+y*this.getRowSize(), currentPlayer)); - x += dirX; - y += dirY; - } - if (isOnBoard(x, y) && boardGrid[y][x] == currentPlayer) { - return movesToFlip.toArray(new Move[0]); //only return the count if last tile is ours - } - return null; - } - - private boolean isOnBoard(int x, int y) { - return x >= 0 && x < this.getColumnSize() && y >= 0 && y < this.getRowSize(); - } - - private char[][] makeBoardAGrid() { - char[][] boardGrid = new char[this.getRowSize()][this.getColumnSize()]; - for (int i = 0; i < 64; i++) { - boardGrid[i / this.getRowSize()][i % this.getColumnSize()] = this.getBoard()[i]; //boardGrid[y -> row] [x -> column] - } - return boardGrid; - } - - @Override - public GameState play(Move move) { - Move[] legalMoves = getLegalMoves(); - boolean moveIsLegal = false; - for (Move legalMove : legalMoves) { //check if the move is legal - if (move.equals(legalMove)) { - moveIsLegal = true; - break; - } - } - if (!moveIsLegal) { - return null; - } - - Move[] moves = sortMovesFromCenter(getFlipsForPotentialMove(new Point(move.position()%this.getColumnSize(),move.position()/this.getRowSize()), move.value()),move); - mostRecentlyFlippedPieces = moves; - this.setBoard(move); //place the move on the board - for (Move m : moves) { - this.setBoard(m); //flip the correct pieces on the board - } - filledCells.add(new Point(move.position() % this.getRowSize(), move.position() / this.getColumnSize())); - nextTurn(); - if (getLegalMoves().length == 0) { //skip the players turn when there are no legal moves - skipMyTurn(); - if (getLegalMoves().length > 0) { - return GameState.TURN_SKIPPED; - } - else { //end the game when neither player has a legal move - Score score = getScore(); - if (score.player1Score() == score.player2Score()) { - return GameState.DRAW; - } - else { - return GameState.WIN; - } - } - } - return GameState.NORMAL; - } - - private void skipMyTurn(){ - IO.println("TURN " + getCurrentPlayer() + " SKIPPED"); - //TODO: notify user that a turn has been skipped - nextTurn(); - } - - public char getCurrentPlayer() { - if (this.getCurrentTurn() == 0){ - return 'B'; - } - else { - return 'W'; - } - } - - private char getOpponent(char currentPlayer){ - if (currentPlayer == 'B') { - return 'W'; - } - else { - return 'B'; - } - } - - public Score getScore(){ - int player1Score = 0, player2Score = 0; - for (int count = 0; count < this.getRowSize() * this.getColumnSize(); count++) { - if (this.getBoard()[count] == 'B') { - player1Score += 1; - } - if (this.getBoard()[count] == 'W') { - player2Score += 1; - } - } - return new Score(player1Score, player2Score); - } - private Move[] sortMovesFromCenter(Move[] moves, Move center) { //sorts the pieces to be flipped for animation purposes - int centerX = center.position()%this.getColumnSize(); - int centerY = center.position()/this.getRowSize(); - Arrays.sort(moves, (a, b) -> { - int dxA = a.position()%this.getColumnSize() - centerX; - int dyA = a.position()/this.getRowSize() - centerY; - int dxB = b.position()%this.getColumnSize() - centerX; - int dyB = b.position()/this.getRowSize() - centerY; - - int distA = dxA * dxA + dyA * dyA; - int distB = dxB * dxB + dyB * dyB; - - return Integer.compare(distA, distB); - }); - return moves; - } - public Move[] getMostRecentlyFlippedPieces() { - return mostRecentlyFlippedPieces; - } -} \ No newline at end of file diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAI.java b/game/src/main/java/org/toop/game/reversi/ReversiAI.java deleted file mode 100644 index 2fc78d5..0000000 --- a/game/src/main/java/org/toop/game/reversi/ReversiAI.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.toop.game.reversi; - -import org.toop.game.AI; -import org.toop.game.records.Move; - -public final class ReversiAI extends AI { - @Override - public Move findBestMove(Reversi game, int depth) { - Move[] moves = game.getLegalMoves(); - int inty = (int)(Math.random() * moves.length-.5f); - if (moves.length == 0) return null; - return moves[inty]; - } -} diff --git a/game/src/main/java/org/toop/game/reversi/ReversiAIR.java b/game/src/main/java/org/toop/game/reversi/ReversiAIR.java deleted file mode 100644 index 8effe07..0000000 --- a/game/src/main/java/org/toop/game/reversi/ReversiAIR.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.toop.game.reversi; - -import org.toop.framework.gameFramework.abstractClasses.AIR; - -import java.util.Arrays; -import java.util.Random; - -public final class ReversiAIR extends AIR { - @Override - public int findBestMove(ReversiR game, int depth) { - int[] moves = game.getLegalMoves(); - if (moves.length == 0) return -1; - - int inty = new Random().nextInt(0, moves.length); - return moves[inty]; - } -} diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java deleted file mode 100644 index 7388fe0..0000000 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToe.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.toop.game.tictactoe; - -import java.util.ArrayList; -import org.toop.game.TurnBasedGame; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -public final class TicTacToe extends TurnBasedGame { - private int movesLeft; - - public TicTacToe() { - super(3, 3, 2); - movesLeft = this.getBoard().length; - } - - public TicTacToe(TicTacToe other) { - super(other); - movesLeft = other.movesLeft; - } - - @Override - public Move[] getLegalMoves() { - final ArrayList legalMoves = new ArrayList<>(); - final char currentValue = getCurrentValue(); - - for (int i = 0; i < this.getBoard().length; i++) { - if (this.getBoard()[i] == EMPTY) { - legalMoves.add(new Move(i, currentValue)); - } - } - - return legalMoves.toArray(new Move[0]); - } - - @Override - public GameState play(Move move) { - assert move != null; - assert move.position() >= 0 && move.position() < this.getBoard().length; - assert move.value() == getCurrentValue(); - - // TODO: Make sure this move is allowed, maybe on the board side? - this.setBoard(move); - movesLeft--; - - if (checkForWin()) { - return GameState.WIN; - } - - nextTurn(); - - if (movesLeft <= 2) { - if (movesLeft <= 0 || checkForEarlyDraw()) { - return GameState.DRAW; - } - } - - return GameState.NORMAL; - } - - private boolean checkForWin() { - // Horizontal - for (int i = 0; i < 3; i++) { - final int index = i * 3; - - if (this.getBoard()[index] != EMPTY - && this.getBoard()[index] == this.getBoard()[index + 1] - && this.getBoard()[index] == this.getBoard()[index + 2]) { - return true; - } - } - - // Vertical - for (int i = 0; i < 3; i++) { - if (this.getBoard()[i] != EMPTY && this.getBoard()[i] == this.getBoard()[i + 3] && this.getBoard()[i] == this.getBoard()[i + 6]) { - return true; - } - } - - // B-Slash - if (this.getBoard()[0] != EMPTY && this.getBoard()[0] == this.getBoard()[4] && this.getBoard()[0] == this.getBoard()[8]) { - return true; - } - - // F-Slash - return this.getBoard()[2] != EMPTY && this.getBoard()[2] == this.getBoard()[4] && this.getBoard()[2] == this.getBoard()[6]; - } - - private boolean checkForEarlyDraw() { - for (final Move move : this.getLegalMoves()) { - final TicTacToe copy = new TicTacToe(this); - - if (copy.play(move) == GameState.WIN || !copy.checkForEarlyDraw()) { - return false; - } - } - - return true; - } - - private char getCurrentValue() { - return this.getCurrentTurn() == 0 ? 'X' : 'O'; - } -} diff --git a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java b/game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java deleted file mode 100644 index dd2c53c..0000000 --- a/game/src/main/java/org/toop/game/tictactoe/TicTacToeAI.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.toop.game.tictactoe; - -import org.toop.game.AI; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; - -public final class TicTacToeAI extends AI { - @Override - public Move findBestMove(TicTacToe game, int depth) { - assert game != null; - assert depth >= 0; - - final Move[] legalMoves = game.getLegalMoves(); - - if (legalMoves.length == 0) { - return null; - } - - if (legalMoves.length == 9) { - return switch ((int)(Math.random() * 4)) { - case 0 -> legalMoves[2]; - case 1 -> legalMoves[6]; - case 2 -> legalMoves[8]; - default -> legalMoves[0]; - }; - } - - int bestScore = -depth; - Move bestMove = null; - - for (final Move move : legalMoves) { - final int score = getMoveScore(game, depth, move, true); - - if (score > bestScore) { - bestMove = move; - bestScore = score; - } - } - - return bestMove != null? bestMove : legalMoves[(int)(Math.random() * legalMoves.length)]; - } - public Move findWorstMove(TicTacToe game, int depth){ - - - Move[] legalMoves = game.getLegalMoves(); - - int bestScore = -depth; - Move bestMove = null; - - for (final Move move : legalMoves) { - final int score = getMoveScore(game, depth, move, false); - - if (score > bestScore) { - bestMove = move; - bestScore = score; - } - } - return bestMove; - } - - private int getMoveScore(TicTacToe game, int depth, Move move, boolean maximizing) { - final TicTacToe copy = new TicTacToe(game); - final GameState state = copy.play(move); - - switch (state) { - case GameState.DRAW: return 0; - case GameState.WIN: return maximizing? depth + 1 : -depth - 1; - } - - if (depth <= 0) { - return 0; - } - - final Move[] legalMoves = copy.getLegalMoves(); - int score = maximizing? depth + 1 : -depth - 1; - - for (final Move next : legalMoves) { - if (maximizing) { - score = Math.min(score, getMoveScore(copy, depth - 1, next, false)); - } else { - score = Math.max(score, getMoveScore(copy, depth - 1, next, true)); - } - } - - return score; - } -} \ No newline at end of file diff --git a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java b/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java deleted file mode 100644 index 341478f..0000000 --- a/game/src/test/java/org/toop/game/tictactoe/ReversiTest.java +++ /dev/null @@ -1,193 +0,0 @@ -package org.toop.game.tictactoe; - -import java.util.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.toop.framework.gameFramework.GameState; -import org.toop.game.records.Move; -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.getBoard()[27]); - assertEquals('B',game.getBoard()[28]); - assertEquals('B',game.getBoard()[35]); - assertEquals('W',game.getBoard()[36]); - } - - @Test - void testGetLegalMovesAtStart() { - Move[] moves = game.getLegalMoves(); - List expectedMoves = List.of( - new Move(19,'B'), - new Move(26,'B'), - new Move(37,'B'), - new Move(44,'B') - ); - assertNotNull(moves); - assertTrue(moves.length > 0); - assertMovesMatchIgnoreOrder(expectedMoves, Arrays.asList(moves)); - } - - private void assertMovesMatchIgnoreOrder(List expected, List 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 Move(19, 'B')); - assertEquals('B', game.getBoard()[19]); - assertEquals('B', game.getBoard()[27], "Piece should have flipped to B"); - } - - @Test - void testMakeInvalidMoveDoesNothing() { - char[] before = game.getBoard().clone(); - game.play(new Move(0, 'B')); - assertArrayEquals(before, game.getBoard(), "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(); - Reversi.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 Move(19, 'B')); - game.play(new Move(20, 'W')); - Move[] moves = game.getLegalMoves(); - List expectedMoves = List.of( - new Move(13,'B'), - new Move(21, 'B'), - new Move(29, 'B'), - new Move(37, 'B'), - new 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(); - Move[] legalMoves = game.getLegalMoves(); - while(legalMoves.length > 0) { - game.play(legalMoves[(int)(Math.random()*legalMoves.length)]); - legalMoves = game.getLegalMoves(); - } - Reversi.Score score = game.getScore(); - IO.println(score.player1Score()); - IO.println(score.player2Score()); - - for (int r = 0; r < game.getRowSize(); r++) { - char[] row = Arrays.copyOfRange(game.getBoard(), r * game.getColumnSize(), (r + 1) * game.getColumnSize()); - IO.println(Arrays.toString(row)); - } - } - } - - @Test - void testPlayerMustSkipTurnIfNoValidMoves() { - game.play(new Move(19, 'B')); - game.play(new Move(34, 'W')); - game.play(new Move(45, 'B')); - game.play(new Move(11, 'W')); - game.play(new Move(42, 'B')); - game.play(new Move(54, 'W')); - game.play(new Move(37, 'B')); - game.play(new Move(46, 'W')); - game.play(new Move(63, 'B')); - game.play(new Move(62, 'W')); - game.play(new Move(29, 'B')); - game.play(new Move(50, 'W')); - game.play(new Move(55, 'B')); - game.play(new Move(30, 'W')); - game.play(new Move(53, 'B')); - game.play(new Move(38, 'W')); - game.play(new Move(61, 'B')); - game.play(new Move(52, 'W')); - game.play(new Move(51, 'B')); - game.play(new Move(60, 'W')); - game.play(new Move(59, 'B')); - assertEquals('B', game.getCurrentPlayer()); - game.play(ai.findBestMove(game,5)); - game.play(ai.findBestMove(game,5)); - } - - @Test - void testGameShouldEndIfNoValidMoves() { - //European Grand Prix Ghent 2017: Replay Hassan - Verstuyft J. (3-17) - game.play(new Move(19, 'B')); - game.play(new Move(20, 'W')); - game.play(new Move(29, 'B')); - game.play(new Move(22, 'W')); - game.play(new Move(21, 'B')); - game.play(new Move(34, 'W')); - game.play(new Move(23, 'B')); - game.play(new Move(13, 'W')); - game.play(new Move(26, 'B')); - game.play(new Move(18, 'W')); - game.play(new Move(12, 'B')); - game.play(new Move(4, 'W')); - game.play(new Move(17, 'B')); - game.play(new Move(31, 'W')); - GameState stateTurn15 = game.play(new Move(39, 'B')); - assertEquals(GameState.NORMAL, stateTurn15); - GameState stateTurn16 = game.play(new Move(16, 'W')); - assertEquals(GameState.WIN, stateTurn16); - GameState stateTurn17 = game.play(new Move(5, 'B')); - assertNull(stateTurn17); - Reversi.Score score = game.getScore(); - assertEquals(3, score.player1Score()); - assertEquals(17, score.player2Score()); - } - - @Test - void testAISelectsLegalMove() { - Move move = ai.findBestMove(game,4); - assertNotNull(move); - assertTrue(containsMove(game.getLegalMoves(),move), "AI should always choose a legal move"); - } - - private boolean containsMove(Move[] moves, Move move) { - for (Move m : moves) { - if (m.equals(move)) return true; - } - return false; - } -} diff --git a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAIRTest.java b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAIRTest.java index 3b349a1..72277cb 100644 --- a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAIRTest.java +++ b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAIRTest.java @@ -2,6 +2,9 @@ package org.toop.game.tictactoe; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.toop.framework.gameFramework.model.player.Player; +import org.toop.game.games.tictactoe.TicTacToeAIR; +import org.toop.game.games.tictactoe.TicTacToeR; import static org.junit.jupiter.api.Assertions.*; @@ -11,7 +14,7 @@ final class TicTacToeAIRTest { // Helper: play multiple moves in sequence on a fresh board private TicTacToeR playSequence(int... moves) { - TicTacToeR game = new TicTacToeR(); + TicTacToeR game = new TicTacToeR(new Player[2]); for (int move : moves) { game.play(move); } @@ -21,8 +24,8 @@ final class TicTacToeAIRTest { @Test @DisplayName("AI first move must choose a corner") void testFirstMoveIsCorner() { - TicTacToeR game = new TicTacToeR(); - int move = ai.findBestMove(game, 4); + TicTacToeR game = new TicTacToeR(new Player[2]); + int move = ai.getMove(game); assertTrue( move == 0 || move == 2 || move == 6 || move == 8, @@ -34,7 +37,7 @@ final class TicTacToeAIRTest { @DisplayName("AI doesn't make losing move in specific situation") void testWinningMove(){ TicTacToeR game = playSequence(new int[] { 0, 4, 5, 3, 6, 1, 7}); - int move = ai.findBestMove(game, 9); + int move = ai.getMove(game); assertEquals(8, move); } @@ -55,7 +58,7 @@ final class TicTacToeAIRTest { 1, 4 // X, O ); - int move = ai.findBestMove(game, 4); + int move = ai.getMove(game); assertEquals(2, move, "AI must take the winning move at index 2"); } @@ -73,18 +76,18 @@ final class TicTacToeAIRTest { 8, 4 // X, O (O threatens at 5) ); - int move = ai.findBestMove(game, 4); + int move = ai.getMove(game); assertEquals(5, move, "AI must block opponent at index 5"); } @Test @DisplayName("AI returns -1 when no legal moves exist") void testNoMovesAvailable() { - TicTacToeR full = new TicTacToeR(); + TicTacToeR full = new TicTacToeR(new Player[2]); // Fill board alternating for (int i = 0; i < 9; i++) full.play(i); - int move = ai.findBestMove(full, 3); + int move = ai.getMove(full); assertEquals(-1, move, "AI should return -1 when board is full"); } @@ -92,7 +95,7 @@ final class TicTacToeAIRTest { @DisplayName("Minimax depth does not cause crashes and produces valid move") void testDepthStability() { TicTacToeR game = playSequence(0, 4); // Simple mid-game state - int move = ai.findBestMove(game, 6); + int move = ai.getMove(game); assertTrue(move >= -1 && move <= 8, "AI must return a valid move index"); } @@ -108,11 +111,11 @@ final class TicTacToeAIRTest { // // Legal moves: 5, 8 // Only move 5 avoids losing. - TicTacToeR game = new TicTacToeR(); + TicTacToeR game = new TicTacToeR(new Player[2]); int[] moves = {0,1,2,4,3,6,7}; // Hard-coded board setup for (int m : moves) game.play(m); - int move = ai.findBestMove(game, 4); + int move = ai.getMove(game); assertEquals(5, move, "AI must choose the only move that avoids losing"); } } diff --git a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java b/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java deleted file mode 100644 index c1c0d85..0000000 --- a/game/src/test/java/org/toop/game/tictactoe/TicTacToeAITest.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.toop.game.tictactoe; - -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Set; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.toop.game.records.Move; - -class TicTacToeAITest { - private TicTacToe game; - private TicTacToeAI ai; - - @BeforeEach - void setup() { - game = new TicTacToe(); - ai = new TicTacToeAI(); - } - - @Test - void testBestMove_returnWinningMoveWithDepth1() { - // X X - - // O O - - // - - - - game.play(new Move(0, 'X')); - game.play(new Move(3, 'O')); - game.play(new Move(1, 'X')); - game.play(new Move(4, 'O')); - - final Move move = ai.findBestMove(game, 1); - - assertNotNull(move); - assertEquals('X', move.value()); - assertEquals(2, move.position()); - } - - @Test - void testBestMove_blockOpponentWinDepth1() { - // - - - - // O - - - // X X - - game.play(new Move(6, 'X')); - game.play(new Move(3, 'O')); - game.play(new Move(7, 'X')); - - final Move move = ai.findBestMove(game, 1); - - assertNotNull(move); - assertEquals('O', move.value()); - assertEquals(8, move.position()); - } - - @Test - void testBestMove_preferCornerOnEmpty() { - final Move move = ai.findBestMove(game, 0); - - assertNotNull(move); - assertEquals('X', move.value()); - assertTrue(Set.of(0, 2, 6, 8).contains(move.position())); - } - - @Test - void testBestMove_findBestMoveDraw() { - // O X - - // - O X - // X O X - game.play(new Move(1, 'X')); - game.play(new Move(0, 'O')); - game.play(new Move(5, 'X')); - game.play(new Move(4, 'O')); - game.play(new Move(6, 'X')); - game.play(new Move(7, 'O')); - game.play(new Move(8, 'X')); - - final Move move = ai.findBestMove(game, game.getLegalMoves().length); - - assertNotNull(move); - assertEquals('O', move.value()); - assertEquals(2, move.position()); - } -}