Tutorials to Dev (#264)

* Fixed garbage code

* added a pop button

* Tutorial images now use ImageAsset.java

* Added button to continue and start game. Refactors

* Refactored nextScreen runnable

* Removed unused imports

* Refactored switch statement

* Added documentation

* Removed space

* Added translations

* Added function input for enabling/disabling localization p/text

---------

Co-authored-by: ramollia <>
This commit is contained in:
Bas Antonius de Jong
2025-12-02 10:57:46 +01:00
committed by GitHub
parent c332033a06
commit d9437c1b8a
25 changed files with 224 additions and 281 deletions

View File

@@ -54,8 +54,7 @@ public final class Primitive {
return text(key, true);
}
public static ImageView image(File file) {
ImageAsset imageAsset = new ImageAsset(file);
public static ImageView image(ImageAsset imageAsset) {
ImageView imageView = new ImageView(imageAsset.getImage());
imageView.getStyleClass().add("image");
imageView.setPreserveRatio(true);

View File

@@ -0,0 +1,5 @@
package org.toop.app.widget;
public interface Updatable {
void update();
}

View File

@@ -1,7 +1,21 @@
package org.toop.app.widget.complex;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
public abstract class PopupWidget extends StackWidget {
private final Button popButton;
public PopupWidget() {
super("bg-popup");
popButton = new Button("X");
popButton.setOnAction(_ -> hide());
add(Pos.TOP_RIGHT, popButton);
}
protected void setOnPop(Runnable onPop) {
popButton.setOnAction(_ -> onPop.run());
}
}

View File

@@ -20,5 +20,10 @@ public class QuitPopup extends PopupWidget {
});
add(Pos.CENTER, confirmWidget);
setOnPop(() -> {
App.stopQuit();
hide();
});
}
}

View File

@@ -1,60 +1,115 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.image.ImageView;
import javafx.scene.text.Text;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.complex.ViewWidget;
import org.toop.app.widget.Updatable;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.complex.PopupWidget;
import javafx.scene.control.Button;
import org.toop.framework.resource.resources.ImageAsset;
import org.toop.local.AppContext;
import java.io.File;
import java.util.List;
public class BaseTutorialWidget extends ViewWidget {
/**
* A widget base for all the tutorial widgets.
*
* <p>Usage example:
*
* <pre>{@code
* public class Connect4TutorialWidget extends BaseTutorialWidget {
* public Connect4TutorialWidget(Runnable nextScreen) {
* super(List.of(
* new ImmutablePair<>("connect4.1", ResourceManager.get("connect41.png")),
* new ImmutablePair<>("connect4.2", ResourceManager.get("connect42.png"))
* ), nextScreen);
* }
* }</pre>
*/
public class BaseTutorialWidget extends PopupWidget implements Updatable {
private TState state;
private Text tutorialText;
private Button previousButton;
private Button nextButton;
private Button noButton;
private Button yesButton;
private Button neverButton;
private ImageView imagery;
private final Text tutorialText;
private final ImageView imagery;
private final Button previousButton;
private final Button nextButton;
private final List<ImmutablePair<String, ImageAsset>> pages;
private final Runnable nextScreen;
public BaseTutorialWidget(String key, Runnable onNo, Runnable onYes, Runnable onNever) {
System.out.println("Trying to initialize...");
this.tutorialText = Primitive.text(key);
this.yesButton = Primitive.button("ok", () -> onYes.run());
this.noButton = Primitive.button("no", () -> onNo.run());
this.neverButton = Primitive.button("never", () -> onNever.run());
var a = Primitive.hbox(yesButton, noButton, neverButton);
add(Pos.CENTER, Primitive.vbox(tutorialText, a));
}
private int pageIndex = 0;
public BaseTutorialWidget(TState state, String key, Runnable onPrevious, Runnable onNext) {
this.state = state;
this.tutorialText = Primitive.text(key);
this.previousButton = Primitive.button("<", () -> onPrevious.run());
this.nextButton = Primitive.button(">", () -> onNext.run());
var w = Primitive.hbox(previousButton, nextButton);
add(Pos.CENTER, Primitive.vbox(tutorialText, w));
}
public BaseTutorialWidget(List<ImmutablePair<String, ImageAsset>> pages, Runnable nextScreen) {
this.tutorialText = Primitive.text(pages.getFirst().getKey());
this.imagery = Primitive.image(pages.getFirst().getValue());
this.pages = pages;
this.nextScreen = nextScreen;
previousButton = Primitive.button("goback", () -> { update(false); this.hide(); });
nextButton = Primitive.button(">", () -> update(true));
var w = Primitive.hbox(
previousButton,
nextButton
);
var x = Primitive.vbox(imagery, tutorialText);
public BaseTutorialWidget(TState state, String key, File image, Runnable onPrevious, Runnable onNext) {
this.state = state;
this.imagery = Primitive.image(image);
this.tutorialText = Primitive.text(key);
this.previousButton = Primitive.button("<", () -> onPrevious.run());
this.nextButton = Primitive.button(">", () -> onNext.run());
var w = Primitive.hbox(previousButton, nextButton);
var x = Primitive.vbox(imagery, tutorialText);
add(Pos.CENTER, Primitive.vbox(x, w));
WidgetContainer.add(Pos.CENTER, this);
}
public void update(String key, File image) {
@Override
public void update() {
update(true);
}
// TODO Refactor if statements to make code easier to read.
public void update(boolean next) {
pageIndex = next ? pageIndex + 1 : pageIndex - 1;
if (pageIndex >= pages.size()) {
pageIndex--;
return;
} else if (pageIndex < 0) {
pageIndex++;
return;
}
if (pageIndex == pages.size()-1) {
nextButton.textProperty().unbind();
nextButton.setText(AppContext.getString("startgame"));
nextButton.setOnAction((_) -> {
this.hide();
nextScreen.run();
});
} else {
nextButton.textProperty().unbind();
nextButton.setText(AppContext.getString(">"));
nextButton.setOnAction((_) -> this.update(true));
}
if (pageIndex == 0) {
previousButton.textProperty().unbind();
previousButton.setText(AppContext.getString("goback"));
previousButton.setOnAction((_) -> this.hide());
} else {
previousButton.textProperty().unbind();
previousButton.setText(AppContext.getString("<"));
previousButton.setOnAction((_) -> this.update(false));
}
var currentPage = pages.get(pageIndex);
var text = currentPage.getKey();
var image = currentPage.getValue();
tutorialText.textProperty().unbind();
tutorialText.setText(AppContext.getString(key));
tutorialText.setText(AppContext.getString(text));
imagery.setImage(Primitive.image(image).getImage());
}
}

View File

@@ -1,39 +1,15 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import org.toop.app.widget.complex.ViewWidget;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.framework.resource.ResourceManager;
import java.io.File;
import java.util.List;
public class Connect4TutorialWidget extends ViewWidget {
private TState state;
private String[] keys = {"connect4.1", "connect4.2"};
private File[] images = {new File("app/src/main/resources/assets/images/connect41.png"), new File("app/src/main/resources/assets/images/connect42.png")};
private BaseTutorialWidget tutorialWidget;
public Connect4TutorialWidget() {
this.state = new TState(keys.length);
tutorialWidget = new BaseTutorialWidget(
state,
keys[state.getCurrent()],
images[state.getCurrent()],
() -> {
if (state.hasPrevious()) {
state.previous();
update();
}
},
() -> {
if (state.hasNext()) {
state.next();
update();
}
}
);
add(Pos.CENTER, tutorialWidget);
}
private void update() {
tutorialWidget.update(keys[state.getCurrent()], images[state.getCurrent()]);
public class Connect4TutorialWidget extends BaseTutorialWidget {
public Connect4TutorialWidget(Runnable nextScreen) {
super(List.of(
new ImmutablePair<>("connect4.1", ResourceManager.get("connect41.png")),
new ImmutablePair<>("connect4.2", ResourceManager.get("connect42.png"))
), nextScreen);
}
}

View File

@@ -1,39 +1,17 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import org.toop.app.widget.complex.ViewWidget;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.framework.resource.ResourceManager;
import java.io.File;
import java.util.List;
public class ReversiTutorialWidget extends ViewWidget {
private TState state;
private String[] keys = {"reversi1", "reversi2", "reversi3", "reversi4"};
private File[] images = {new File("app/src/main/resources/assets/images/reversi1.png"), new File("app/src/main/resources/assets/images/reversi2.png"), new File("app/src/main/resources/assets/images/cat.jpg"), new File("app/src/main/resources/assets/images/cat.jpg")};
private BaseTutorialWidget tutorialWidget;
public ReversiTutorialWidget() {
this.state = new TState(keys.length);
tutorialWidget = new BaseTutorialWidget(
state,
keys[state.getCurrent()],
images[state.getCurrent()],
() -> {
if (state.hasPrevious()) {
state.previous();
update();
}
},
() -> {
if (state.hasNext()) {
state.next();
update();
}
}
);
add(Pos.CENTER, tutorialWidget);
}
private void update() {
tutorialWidget.update(keys[state.getCurrent()], images[state.getCurrent()]);
public class ReversiTutorialWidget extends BaseTutorialWidget {
public ReversiTutorialWidget(Runnable nextScreen) {
super(List.of(
new ImmutablePair<>("reversi1", ResourceManager.get("reversi1.png")),
new ImmutablePair<>("reversi2", ResourceManager.get("reversi2.png")),
new ImmutablePair<>("reversi3", ResourceManager.get("cat.jpg")),
new ImmutablePair<>("reversi4", ResourceManager.get("cat.jpg"))
), nextScreen);
}
}

View File

@@ -0,0 +1,22 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import org.toop.app.widget.Primitive;
import org.toop.app.widget.WidgetContainer;
import org.toop.app.widget.complex.PopupWidget;
import org.toop.local.AppSettings;
public class ShowEnableTutorialWidget extends PopupWidget {
public ShowEnableTutorialWidget(Runnable tutorial, Runnable nextScreen, Runnable appSettingsSetter) {
var a = Primitive.hbox(
Primitive.button("ok", () -> { appSettingsSetter.run(); tutorial.run(); this.hide(); }),
Primitive.button("no", () -> { appSettingsSetter.run(); nextScreen.run(); this.hide(); }),
Primitive.button("never", () -> { AppSettings.getSettings().setTutorialFlag(false); nextScreen.run(); this.hide(); })
);
var txt = Primitive.text("tutorial");
add(Pos.CENTER, Primitive.vbox(txt, a));
WidgetContainer.add(Pos.CENTER, this);
}
}

View File

@@ -1,44 +0,0 @@
package org.toop.app.widget.tutorial;
public class TState {
private int current;
private int total;
public TState(int total) {
this.total = total;
this.current = 0;
}
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public void next() {
current = current + 1;
}
public void previous() {
current = current - 1;
}
public boolean hasNext() {
return current < total - 1;
}
public boolean hasPrevious() {
return current > 0;
}
}

View File

@@ -1,42 +1,16 @@
package org.toop.app.widget.tutorial;
import javafx.geometry.Pos;
import org.toop.app.widget.complex.ViewWidget;
import java.io.File;
import org.apache.maven.surefire.shared.lang3.tuple.ImmutablePair;
import org.toop.framework.resource.ResourceManager;
public class TicTacToeTutorialWidget extends ViewWidget {
import java.util.List;
private TState state;
private String[] keys = {"tictactoe1", "tictactoe2"};
private File[] images = {
new File("app/src/main/resources/assets/images/tictactoe1.png"),
new File("app/src/main/resources/assets/images/tictactoe2.png")
};
private BaseTutorialWidget tutorialWidget;
public TicTacToeTutorialWidget() {
this.state = new TState(keys.length);
tutorialWidget = new BaseTutorialWidget(
state,
keys[state.getCurrent()],
images[state.getCurrent()],
() -> {
if (state.hasPrevious()) {
state.previous();
update();
}
},
() -> {
if (state.hasNext()) {
state.next();
update();
}
}
);
add(Pos.CENTER, tutorialWidget);
public class TicTacToeTutorialWidget extends BaseTutorialWidget {
public TicTacToeTutorialWidget(Runnable nextScreen) {
super(List.of(
new ImmutablePair<>("tictactoe1", ResourceManager.get("tictactoe1.png")),
new ImmutablePair<>("tictactoe2", ResourceManager.get("tictactoe2.png"))
), nextScreen);
}
private void update() {
tutorialWidget.update(keys[state.getCurrent()], images[state.getCurrent()]);
}
}

View File

@@ -53,23 +53,13 @@ public final class GameView extends ViewWidget {
switch(gameType) {
case "TicTacToe":
this.tutorialButton = Primitive.button("tutorialstring", () -> {
transitionNext(new TicTacToeTutorialWidget());
});
break;
this.tutorialButton = Primitive.button("tutorialstring", () -> new TicTacToeTutorialWidget(() -> {})); break;
case "Reversi":
this.tutorialButton = Primitive.button("tutorialstring", () -> {
transitionNext(new ReversiTutorialWidget());
});
break;
this.tutorialButton = Primitive.button("tutorialstring", () -> new ReversiTutorialWidget(() -> {})); break;
case "Connect4":
this.tutorialButton = Primitive.button("tutorialstring", () -> {
transitionNext(new Connect4TutorialWidget());
});
break;
this.tutorialButton = Primitive.button("tutorialstring", () -> new Connect4TutorialWidget(() -> {})); break;
default:
this.tutorialButton = null;
break;
this.tutorialButton = null; break;
}
setupLayout();

View File

@@ -10,10 +10,7 @@ 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.app.widget.tutorial.BaseTutorialWidget;
import org.toop.app.widget.tutorial.Connect4TutorialWidget;
import org.toop.app.widget.tutorial.ReversiTutorialWidget;
import org.toop.app.widget.tutorial.TicTacToeTutorialWidget;
import org.toop.app.widget.tutorial.*;
import org.toop.local.AppContext;
import javafx.geometry.Pos;
@@ -40,91 +37,40 @@ public class LocalMultiplayerView extends ViewWidget {
switch (information.type) {
case TICTACTOE:
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstTTT()) {
BaseTutorialWidget a = new BaseTutorialWidget(
"tutorial",
() -> {
AppSettings.getSettings().setFirstTTT(false);
Platform.runLater(() -> {
new TicTacToeGameThread(information);
});
},
() -> {
ViewWidget c = new TicTacToeTutorialWidget();
transitionNext(c);
WidgetContainer.setCurrentView(c);
AppSettings.getSettings().setFirstTTT(false);
},
() -> {
AppSettings.getSettings().setTutorialFlag(false);
Platform.runLater(() -> {
new TicTacToeGameThread(information);
});
}
);
transitionNext(a);
break;
new ShowEnableTutorialWidget(
() -> new TicTacToeTutorialWidget(() -> new TicTacToeGameThread(information)),
() -> Platform.runLater(() -> new TicTacToeGameThread(information)),
() -> AppSettings.getSettings().setFirstTTT(false)
);
} else {
new TicTacToeGameThread(information);
}
new TicTacToeGameThread(information);
break;
case REVERSI:
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstReversi()) {
BaseTutorialWidget a = new BaseTutorialWidget(
"tutorial",
() -> { Platform.runLater(() -> {
AppSettings.getSettings().setFirstReversi(false);
new ReversiGame(information);
});
},
() -> {
Platform.runLater(() -> {
ViewWidget c = new ReversiTutorialWidget();
transitionNext(c);
WidgetContainer.setCurrentView(c);
AppSettings.getSettings().setFirstReversi(false);
});
},
() -> {
Platform.runLater(() -> {
AppSettings.getSettings().setTutorialFlag(false);
new ReversiGame(information);
});
});
transitionNext(a);
break;
new ShowEnableTutorialWidget(
() -> new ReversiTutorialWidget(() -> new ReversiGame(information)),
() -> Platform.runLater(() -> new ReversiGame(information)),
() -> AppSettings.getSettings().setFirstReversi(false)
);
} else {
new ReversiGame(information);
}
new ReversiGame(information);
break;
case CONNECT4:
if (AppSettings.getSettings().getTutorialFlag() && AppSettings.getSettings().getFirstConnect4()) {
BaseTutorialWidget a = new BaseTutorialWidget(
"tutorial",
() -> { Platform.runLater(() -> {
AppSettings.getSettings().setFirstConnect4(false);
new Connect4Game(information);
});
},
() -> {
Platform.runLater(() -> {
ViewWidget c = new Connect4TutorialWidget();
transitionNext(c);
WidgetContainer.setCurrentView(c);
AppSettings.getSettings().setFirstConnect4(false);
});
},
() -> {
Platform.runLater(() -> {
AppSettings.getSettings().setTutorialFlag(false);
new Connect4Game(information);
});
});
transitionNext(a);
break;
new ShowEnableTutorialWidget(
() -> new Connect4TutorialWidget(() -> new Connect4Game(information)),
() -> Platform.runLater(() -> new Connect4Game(information)),
() -> AppSettings.getSettings().setFirstConnect4(false)
);
} else {
new Connect4Game(information);
}
new Connect4Game(information);
break;
}
}
// case BATTLESHIP -> new BattleshipGame(information);
});
});
var playerSection = setupPlayerSections();