mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 10:54:51 +00:00
Revert "merge widgets with development"
This reverts commit 38681c5db0.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
package org.toop.app;
|
||||
|
||||
import org.toop.app.game.Connect4GameThread;
|
||||
import org.toop.app.game.ReversiGameThread;
|
||||
import org.toop.app.game.TicTacToeGameThread;
|
||||
import org.toop.app.game.Connect4Game;
|
||||
import org.toop.app.game.ReversiGame;
|
||||
import org.toop.app.game.TicTacToeGame;
|
||||
import org.toop.app.widget.WidgetContainer;
|
||||
import org.toop.app.widget.popup.ChallengePopup;
|
||||
import org.toop.app.widget.popup.ErrorPopup;
|
||||
@@ -131,11 +131,11 @@ public final class Server {
|
||||
|
||||
switch (type) {
|
||||
case TICTACTOE ->
|
||||
new TicTacToeGameThread(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||
new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||
case REVERSI ->
|
||||
new ReversiGameThread(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||
new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||
case CONNECT4 ->
|
||||
new Connect4GameThread(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||
new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage, onGameOverRunnable);
|
||||
default -> new ErrorPopup("Unsupported game type.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import java.util.function.Consumer;
|
||||
|
||||
public class Connect4Canvas extends GameCanvas {
|
||||
public Connect4Canvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
|
||||
super(color, Color.TRANSPARENT, width, height, 7, 6, 10, true, onCellClicked);
|
||||
super(color, Color.TRANSPARENT, width, height, 7, 6, 10, true, onCellClicked,null);
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,8 @@ public abstract class GameCanvas {
|
||||
protected final boolean edges;
|
||||
|
||||
protected final Cell[] cells;
|
||||
protected Cell currentCell;
|
||||
|
||||
protected GameCanvas(Color color, Color backgroundColor, int width, int height, int rowSize, int columnSize, int gapSize, boolean edges, Consumer<Integer> onCellClicked) {
|
||||
protected GameCanvas(Color color, Color backgroundColor, int width, int height, int rowSize, int columnSize, int gapSize, boolean edges, Consumer<Integer> onCellClicked, Consumer<Integer> newCellEntered) {
|
||||
canvas = new Canvas(width, height);
|
||||
graphics = canvas.getGraphicsContext2D();
|
||||
|
||||
@@ -54,7 +53,6 @@ public abstract class GameCanvas {
|
||||
this.edges = edges;
|
||||
|
||||
cells = new Cell[rowSize * columnSize];
|
||||
currentCell = null;
|
||||
|
||||
final float cellWidth = ((float) width - gapSize * rowSize - gapSize) / rowSize;
|
||||
final float cellHeight = ((float) height - gapSize * columnSize - gapSize) / columnSize;
|
||||
@@ -84,24 +82,12 @@ public abstract class GameCanvas {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
public void setOnCellEntered(Consumer<Integer> onCellEntered) {
|
||||
canvas.setOnMouseMoved(event -> {
|
||||
final int column = (int) ((event.getX() / this.width) * rowSize);
|
||||
final int row = (int) ((event.getY() / this.height) * columnSize);
|
||||
|
||||
final Cell cell = cells[column + row * rowSize];
|
||||
|
||||
if (currentCell != cell && cell.isInside(event.getX(), event.getY())) {
|
||||
event.consume();
|
||||
currentCell = cell;
|
||||
onCellEntered.accept(column + row * rowSize);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void render() {
|
||||
graphics.setFill(backgroundColor);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
|
||||
@@ -7,38 +7,61 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ReversiCanvas extends GameCanvas {
|
||||
private Move[] currentlyHighlightedMoves = null;
|
||||
|
||||
public ReversiCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
|
||||
super(color, new Color(0f, 0.4f, 0.2f, 1f), width, height, 8, 8, 5, true, onCellClicked);
|
||||
private Move[] currentlyHighlightedMoves = null;
|
||||
public ReversiCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked, Consumer<Integer> newCellEntered) {
|
||||
super(color, new Color(0f,0.4f,0.2f,1f), width, height, 8, 8, 5, true, onCellClicked, newCellEntered);
|
||||
drawStartingDots();
|
||||
|
||||
final AtomicReference<Cell> lastHoveredCell = new AtomicReference<>(null);
|
||||
|
||||
canvas.setOnMouseMoved(event -> {
|
||||
double mouseX = event.getX();
|
||||
double mouseY = event.getY();
|
||||
int cellId = -1;
|
||||
|
||||
Cell hovered = null;
|
||||
for (Cell cell : cells) {
|
||||
if (cell.isInside(mouseX, mouseY)) {
|
||||
hovered = cell;
|
||||
cellId = turnCoordsIntoCellId(mouseX, mouseY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Cell previous = lastHoveredCell.get();
|
||||
|
||||
if (hovered != previous) {
|
||||
lastHoveredCell.set(hovered);
|
||||
newCellEntered.accept(cellId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setCurrentlyHighlightedMovesNull() {
|
||||
currentlyHighlightedMoves = null;
|
||||
}
|
||||
public void setCurrentlyHighlightedMovesNull() {
|
||||
currentlyHighlightedMoves = null;
|
||||
}
|
||||
|
||||
public void drawHighlightDots(Move[] moves) {
|
||||
if (currentlyHighlightedMoves != null) {
|
||||
for (final Move move : currentlyHighlightedMoves) {
|
||||
Color color = move.value() == 'W' ? Color.BLACK : Color.WHITE;
|
||||
drawInnerDot(color, move.position(), true);
|
||||
}
|
||||
}
|
||||
currentlyHighlightedMoves = moves;
|
||||
if (moves != null) {
|
||||
for (Move move : moves) {
|
||||
Color color = move.value() == 'B' ? Color.BLACK : Color.WHITE;
|
||||
drawInnerDot(color, move.position(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void drawHighlightDots(Move[] moves){
|
||||
if (currentlyHighlightedMoves != null){
|
||||
for (final Move move : currentlyHighlightedMoves){
|
||||
Color color = move.value() == 'W'? Color.BLACK: Color.WHITE;
|
||||
drawInnerDot(color, move.position(), true);
|
||||
}
|
||||
}
|
||||
currentlyHighlightedMoves = moves;
|
||||
if (moves != null) {
|
||||
for (Move move : moves) {
|
||||
Color color = move.value() == 'B' ? Color.BLACK : Color.WHITE;
|
||||
drawInnerDot(color, move.position(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int turnCoordsIntoCellId(double x, double y) {
|
||||
final int column = (int) ((x / this.width) * rowSize);
|
||||
final int row = (int) ((y / this.height) * columnSize);
|
||||
return column + row * rowSize;
|
||||
}
|
||||
private int turnCoordsIntoCellId(double x, double y) {
|
||||
final int column = (int) ((x / this.width) * rowSize);
|
||||
final int row = (int) ((y / this.height) * columnSize);
|
||||
return column + row * rowSize;
|
||||
}
|
||||
|
||||
public void drawStartingDots() {
|
||||
drawDot(Color.BLACK, 28);
|
||||
@@ -48,14 +71,16 @@ public final class ReversiCanvas extends GameCanvas {
|
||||
}
|
||||
|
||||
public void drawLegalPosition(int cell, char player) {
|
||||
Color innerColor;
|
||||
|
||||
if (player == 'B') {
|
||||
innerColor = new Color(0.0f, 0.0f, 0.0f, 0.6f);
|
||||
} else {
|
||||
innerColor = new Color(1.0f, 1.0f, 1.0f, 0.75f);
|
||||
}
|
||||
|
||||
drawInnerDot(innerColor, cell, false);
|
||||
Color innerColor;
|
||||
if (player == 'B') {
|
||||
innerColor = new Color(0.0f, 0.0f, 0.0f, 0.6f);
|
||||
}
|
||||
else {
|
||||
innerColor = new Color(1.0f, 1.0f, 1.0f, 0.75f);
|
||||
}
|
||||
drawInnerDot(innerColor, cell,false);
|
||||
public void drawLegalPosition(Color color, int cell) {
|
||||
drawDot(new Color(color.getRed(), color.getGreen(), color.getBlue(), 0.25), cell);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import java.util.function.Consumer;
|
||||
|
||||
public final class TicTacToeCanvas extends GameCanvas {
|
||||
public TicTacToeCanvas(Color color, int width, int height, Consumer<Integer> onCellClicked) {
|
||||
super(color, Color.TRANSPARENT, width, height, 3, 3, 30, false, onCellClicked);
|
||||
super(color, Color.TRANSPARENT, width, height, 3, 3, 30, false, onCellClicked,null);
|
||||
}
|
||||
|
||||
public void drawX(Color color, int cell) {
|
||||
|
||||
@@ -6,7 +6,6 @@ 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;
|
||||
@@ -19,7 +18,7 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
|
||||
protected final GameInformation information;
|
||||
protected final int myTurn;
|
||||
protected final Runnable onGameOver;
|
||||
protected final BlockingQueue<Move> moveQueue;
|
||||
protected final BlockingQueue<Game.Move> moveQueue;
|
||||
|
||||
protected final TGame game;
|
||||
protected final TAI ai;
|
||||
@@ -28,7 +27,6 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
|
||||
protected final TCanvas canvas;
|
||||
|
||||
protected final AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||
protected final AtomicBoolean isPaused = new AtomicBoolean(false);
|
||||
|
||||
protected BaseGameThread(
|
||||
GameInformation information,
|
||||
@@ -78,7 +76,7 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
|
||||
}
|
||||
|
||||
private void onCellClicked(int cell) {
|
||||
if (!isRunning.get() || isPaused.get()) return;
|
||||
if (!isRunning.get()) return;
|
||||
|
||||
final int currentTurn = getCurrentTurn();
|
||||
if (!information.players[currentTurn].isHuman) return;
|
||||
@@ -86,9 +84,8 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
|
||||
final char value = getSymbolForTurn(currentTurn);
|
||||
|
||||
try {
|
||||
moveQueue.put(new Move(cell, value));
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
moveQueue.put(new Game.Move(cell, value));
|
||||
} catch (InterruptedException _) {}
|
||||
}
|
||||
|
||||
protected void gameOver() {
|
||||
@@ -113,13 +110,10 @@ public abstract class BaseGameThread<TGame extends Game, TAI, TCanvas> {
|
||||
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();
|
||||
|
||||
275
app/src/main/java/org/toop/app/game/Connect4Game.java
Normal file
275
app/src/main/java/org/toop/app/game/Connect4Game.java
Normal file
@@ -0,0 +1,275 @@
|
||||
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.view.ViewStack;
|
||||
import org.toop.app.view.views.GameView;
|
||||
import org.toop.app.view.views.LocalMultiplayerView;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
import org.toop.game.Connect4.Connect4;
|
||||
import org.toop.game.Connect4.Connect4AI;
|
||||
import org.toop.game.enumerators.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<Move> moveQueue;
|
||||
|
||||
private final Connect4 game;
|
||||
private final Connect4AI ai;
|
||||
private final int columnSize = 7;
|
||||
private final int rowSize = 6;
|
||||
|
||||
private final GameView view;
|
||||
private final Connect4Canvas canvas;
|
||||
|
||||
private final AtomicBoolean isRunning;
|
||||
|
||||
public Connect4Game(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer<String> onMessage, Runnable onGameOver) {
|
||||
this.information = information;
|
||||
this.myTurn = myTurn;
|
||||
this.onGameOver = onGameOver;
|
||||
moveQueue = new LinkedBlockingQueue<Move>();
|
||||
|
||||
|
||||
game = new Connect4();
|
||||
ai = new Connect4AI();
|
||||
|
||||
isRunning = new AtomicBoolean(true);
|
||||
|
||||
if (onForfeit == null || onExit == null) {
|
||||
view = new GameView(null, () -> {
|
||||
isRunning.set(false);
|
||||
ViewStack.push(new LocalMultiplayerView(information));
|
||||
}, null);
|
||||
} else {
|
||||
view = new GameView(onForfeit, () -> {
|
||||
isRunning.set(false);
|
||||
onExit.run();
|
||||
}, onMessage);
|
||||
}
|
||||
|
||||
canvas = new 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 _) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view.add(Pos.CENTER, canvas.getCanvas());
|
||||
ViewStack.push(view);
|
||||
|
||||
if (onForfeit == null || onExit == null) {
|
||||
new Thread(this::localGameThread).start();
|
||||
setGameLabels(information.players[0].isHuman);
|
||||
} else {
|
||||
new EventFlow()
|
||||
.listen(NetworkEvents.GameMoveResponse.class, this::onMoveResponse)
|
||||
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse)
|
||||
.listen(NetworkEvents.ReceivedMessage.class, this::onReceivedMessage);
|
||||
|
||||
setGameLabels(myTurn == 0);
|
||||
}
|
||||
updateCanvas();
|
||||
}
|
||||
|
||||
public 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();
|
||||
|
||||
view.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) {
|
||||
view.gameOver(true, information.players[currentTurn].name);
|
||||
} else if (state == GameState.DRAW) {
|
||||
view.gameOver(false, "");
|
||||
}
|
||||
|
||||
isRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
||||
if (!isRunning.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char playerChar;
|
||||
|
||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||
playerChar = myTurn == 0? '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)) {
|
||||
view.gameOver(true, information.players[0].name);
|
||||
gameOver();
|
||||
} else {
|
||||
view.gameOver(false, information.players[1].name);
|
||||
gameOver();
|
||||
}
|
||||
} else if (state == GameState.DRAW) {
|
||||
view.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 onReceivedMessage(NetworkEvents.ReceivedMessage msg) {
|
||||
if (!isRunning.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
view.updateChat(msg.message());
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
view.nextPlayer(isMe,
|
||||
information.players[isMe? 0 : 1].name,
|
||||
currentValue,
|
||||
information.players[isMe? 1 : 0].name);
|
||||
}
|
||||
}
|
||||
@@ -1,188 +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.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.game.enumerators.GameState;
|
||||
import org.toop.game.records.Move;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class Connect4GameThread extends BaseGameThread<Connect4, Connect4AI, Connect4Canvas> {
|
||||
private static final int COLS = 7;
|
||||
|
||||
public Connect4GameThread(
|
||||
GameInformation info,
|
||||
int myTurn,
|
||||
Runnable onForfeit,
|
||||
Runnable onExit,
|
||||
Consumer<String> onMessage,
|
||||
Runnable onGameOver
|
||||
) {
|
||||
super(
|
||||
info,
|
||||
myTurn,
|
||||
onForfeit,
|
||||
onExit,
|
||||
onMessage,
|
||||
onGameOver,
|
||||
Connect4::new,
|
||||
Connect4AI::new,
|
||||
clickHandler -> new Connect4Canvas(
|
||||
Color.GRAY,
|
||||
(App.getHeight() / 4) * 3,
|
||||
(App.getHeight() / 4) * 3,
|
||||
cell -> clickHandler.accept(cell % COLS)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Connect4GameThread(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 ? "RED" : "BLUE";
|
||||
}
|
||||
|
||||
private void drawMove(Move move) {
|
||||
if (move.value() == 'X')
|
||||
canvas.drawDot(Color.RED, move.position());
|
||||
else
|
||||
canvas.drawDot(Color.BLUE, move.position());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
||||
if (!isRunning.get()) return;
|
||||
|
||||
char symbol =
|
||||
response.player().equalsIgnoreCase(information.players[0].name)
|
||||
? (myTurn == 0 ? 'X' : 'O')
|
||||
: (myTurn == 0 ? 'O' : 'X');
|
||||
|
||||
final Move move = new Move(Integer.parseInt(response.move()), symbol);
|
||||
final GameState state = game.play(move);
|
||||
|
||||
drawMove(move);
|
||||
updateCanvas();
|
||||
|
||||
if (state != GameState.NORMAL) {
|
||||
if (state == GameState.WIN) {
|
||||
boolean p0 = response.player().equalsIgnoreCase(information.players[0].name);
|
||||
primary.gameOver(p0, information.players[p0 ? 0 : 1].name);
|
||||
gameOver();
|
||||
} else if (state == GameState.DRAW) {
|
||||
primary.gameOver(false, "");
|
||||
gameOver();
|
||||
}
|
||||
}
|
||||
|
||||
setGameLabels(game.getCurrentTurn() == myTurn);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onYourTurnResponse(NetworkEvents.YourTurnResponse response) {
|
||||
if (!isRunning.get()) return;
|
||||
|
||||
moveQueue.clear();
|
||||
int col = -1;
|
||||
|
||||
if (information.players[0].isHuman) {
|
||||
try {
|
||||
col = moveQueue.take().position();
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
} else {
|
||||
final Move move = ai.findBestMove(game, information.players[0].computerDifficulty);
|
||||
assert move != null;
|
||||
col = move.position();
|
||||
}
|
||||
|
||||
new EventFlow()
|
||||
.addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) col))
|
||||
.postEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void localGameThread() {
|
||||
while (isRunning.get()) {
|
||||
final int current = game.getCurrentTurn();
|
||||
setGameLabels(current == myTurn);
|
||||
|
||||
Move move = null;
|
||||
|
||||
if (information.players[current].isHuman) {
|
||||
try {
|
||||
final Move wants = moveQueue.take();
|
||||
for (final Move legal : game.getLegalMoves()) {
|
||||
if (legal.position() == wants.position() &&
|
||||
legal.value() == wants.value()) {
|
||||
move = wants;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
} else {
|
||||
final long start = System.currentTimeMillis();
|
||||
move = ai.findBestMove(game, information.players[current].computerDifficulty);
|
||||
|
||||
if (information.players[current].computerThinkTime > 0) {
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
long sleep = information.players[current].computerThinkTime * 1000L - elapsed;
|
||||
try {
|
||||
Thread.sleep((long) (sleep * Math.random()));
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (move == null) continue;
|
||||
|
||||
GameState state = game.play(move);
|
||||
drawMove(move);
|
||||
updateCanvas();
|
||||
|
||||
if (state != GameState.NORMAL) {
|
||||
if (state == GameState.WIN)
|
||||
primary.gameOver(information.players[current].isHuman, information.players[current].name);
|
||||
else if (state == GameState.DRAW)
|
||||
primary.gameOver(false, "");
|
||||
isRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCanvas() {
|
||||
canvas.clearAll();
|
||||
|
||||
for (int i = 0; i < game.getBoard().length; i++) {
|
||||
char c = game.getBoard()[i];
|
||||
if (c == 'X') canvas.drawDot(Color.RED, i);
|
||||
else if (c == 'O') canvas.drawDot(Color.BLUE, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
327
app/src/main/java/org/toop/app/game/ReversiGame.java
Normal file
327
app/src/main/java/org/toop/app/game/ReversiGame.java
Normal file
@@ -0,0 +1,327 @@
|
||||
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.game.enumerators.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<Game.Move> moveQueue;
|
||||
private Runnable onGameOver;
|
||||
private final BlockingQueue<Move> 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<String> 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);
|
||||
} else {
|
||||
primary = new GameView(onForfeit, () -> {
|
||||
isRunning.set(false);
|
||||
onExit.run();
|
||||
}, onMessage);
|
||||
}
|
||||
|
||||
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)
|
||||
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse);
|
||||
|
||||
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 != Game.State.NORMAL) {
|
||||
if (state == Game.State.WIN) {
|
||||
primary.gameOver(true, information.players[currentTurn].name);
|
||||
} else if (state == Game.State.DRAW) {
|
||||
primary.gameOver(false, "");
|
||||
if (state != GameState.NORMAL) {
|
||||
if (state == GameState.WIN) {
|
||||
view.gameOver(true, information.players[currentTurn].name);
|
||||
} else if (state == GameState.DRAW) {
|
||||
view.gameOver(false, "");
|
||||
}
|
||||
|
||||
isRunning.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
||||
if (!isRunning.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char playerChar;
|
||||
|
||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||
playerChar = myTurn == 0? 'B' : 'W';
|
||||
} else {
|
||||
playerChar = myTurn == 0? 'W' : 'B';
|
||||
}
|
||||
|
||||
final 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) {
|
||||
view.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);
|
||||
|
||||
final Move[] legalMoves = game.getLegalMoves();
|
||||
|
||||
for (final Move legalMove : legalMoves) {
|
||||
canvas.drawLegalPosition(legalMove.position(), game.getCurrentPlayer());
|
||||
for (final Game.Move legalMove : legalMoves) {
|
||||
canvas.drawLegalPosition(fromColor, legalMove.position());
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
package org.toop.app.game;
|
||||
|
||||
import javafx.animation.SequentialTransition;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.toop.app.App;
|
||||
import org.toop.app.GameInformation;
|
||||
import org.toop.app.canvas.ReversiCanvas;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
import org.toop.game.enumerators.GameState;
|
||||
import org.toop.game.records.Move;
|
||||
import org.toop.game.reversi.Reversi;
|
||||
import org.toop.game.reversi.ReversiAI;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ReversiGameThread extends BaseGameThread<Reversi, ReversiAI, ReversiCanvas> {
|
||||
public ReversiGameThread(GameInformation info, int myTurn,
|
||||
Runnable onForfeit, Runnable onExit, Consumer<String> onMessage, Runnable onGameOver) {
|
||||
super(info, myTurn, onForfeit, onExit, onMessage, onGameOver,
|
||||
Reversi::new,
|
||||
ReversiAI::new,
|
||||
clickHandler -> new ReversiCanvas(
|
||||
Color.BLACK,
|
||||
(App.getHeight() / 4) * 3,
|
||||
(App.getHeight() / 4) * 3,
|
||||
clickHandler
|
||||
)
|
||||
);
|
||||
|
||||
canvas.setOnCellEntered(this::highlightCells);
|
||||
}
|
||||
|
||||
public ReversiGameThread(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 ? 'B' : 'W';
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getNameForTurn(int turn) {
|
||||
return turn == 0 ? "BLACK" : "WHITE";
|
||||
}
|
||||
|
||||
private void drawMove(Move move) {
|
||||
if (move.value() == 'B') canvas.drawDot(Color.BLACK, move.position());
|
||||
else canvas.drawDot(Color.WHITE, move.position());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMoveResponse(NetworkEvents.GameMoveResponse response) {
|
||||
if (!isRunning.get()) return;
|
||||
|
||||
char playerChar =
|
||||
response.player().equalsIgnoreCase(information.players[0].name)
|
||||
? (myTurn == 0 ? 'B' : 'W')
|
||||
: (myTurn == 0 ? 'W' : 'B');
|
||||
|
||||
final Move move = new Move(Integer.parseInt(response.move()), playerChar);
|
||||
final GameState state = game.play(move);
|
||||
|
||||
updateCanvas(true);
|
||||
|
||||
if (state != GameState.NORMAL) {
|
||||
if (state == GameState.WIN) {
|
||||
boolean p0 = response.player().equalsIgnoreCase(information.players[0].name);
|
||||
primary.gameOver(p0, information.players[p0 ? 0 : 1].name);
|
||||
gameOver();
|
||||
} else if (state == GameState.DRAW) {
|
||||
primary.gameOver(false, "");
|
||||
gameOver();
|
||||
}
|
||||
}
|
||||
|
||||
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 = 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()) {
|
||||
if (isPaused.get()) {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
} catch (InterruptedException _) {}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
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 {
|
||||
long start = System.currentTimeMillis();
|
||||
move = ai.findBestMove(game, information.players[currentTurn].computerDifficulty);
|
||||
|
||||
if (information.players[currentTurn].computerThinkTime > 0) {
|
||||
long elapsed = System.currentTimeMillis() - start;
|
||||
long sleep = information.players[currentTurn].computerThinkTime * 1000L - elapsed;
|
||||
|
||||
try {
|
||||
Thread.sleep((long) (sleep * Math.random()));
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (move == null) continue;
|
||||
|
||||
GameState state = game.play(move);
|
||||
updateCanvas(true);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCanvas(boolean animate) {
|
||||
canvas.clearAll();
|
||||
|
||||
for (int i = 0; i < game.getBoard().length; i++) {
|
||||
char c = game.getBoard()[i];
|
||||
if (c == 'B') canvas.drawDot(Color.BLACK, i);
|
||||
else if (c == 'W') canvas.drawDot(Color.WHITE, i);
|
||||
}
|
||||
|
||||
final Move[] flipped = game.getMostRecentlyFlippedPieces();
|
||||
|
||||
final SequentialTransition anim = new SequentialTransition();
|
||||
isPaused.set(true);
|
||||
|
||||
final Color from = game.getCurrentPlayer() == 'W' ? Color.WHITE : Color.BLACK;
|
||||
final Color to = game.getCurrentPlayer() == 'W' ? Color.BLACK : Color.WHITE;
|
||||
|
||||
if (animate && flipped != null) {
|
||||
for (final Move flip : flipped) {
|
||||
canvas.clear(flip.position());
|
||||
canvas.drawDot(from, flip.position());
|
||||
anim.getChildren().addFirst(canvas.flipDot(from, to, flip.position()));
|
||||
}
|
||||
}
|
||||
|
||||
anim.setOnFinished(_ -> {
|
||||
isPaused.set(false);
|
||||
|
||||
for (final Move m : game.getLegalMoves()) {
|
||||
canvas.drawLegalPosition(m.position(), game.getCurrentPlayer());
|
||||
}
|
||||
});
|
||||
|
||||
anim.play();
|
||||
}
|
||||
|
||||
private void highlightCells(int cell) {
|
||||
Move[] legal = game.getLegalMoves();
|
||||
boolean isLegal = false;
|
||||
|
||||
for (Move m : legal) {
|
||||
if (m.position() == cell) {
|
||||
isLegal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cell >= 0) {
|
||||
Move[] flips = null;
|
||||
|
||||
if (isLegal) {
|
||||
flips = game.getFlipsForPotentialMove(
|
||||
new Point(cell % game.getColumnSize(), cell / game.getRowSize()),
|
||||
game.getCurrentPlayer()
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawHighlightDots(flips);
|
||||
}
|
||||
}
|
||||
}
|
||||
250
app/src/main/java/org/toop/app/game/TicTacToeGame.java
Normal file
250
app/src/main/java/org/toop/app/game/TicTacToeGame.java
Normal file
@@ -0,0 +1,250 @@
|
||||
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.game.enumerators.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 Runnable onGameOver;
|
||||
private final BlockingQueue<Move> 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<String> onMessage, Runnable onGameOver) {
|
||||
this.information = information;
|
||||
|
||||
this.myTurn = myTurn;
|
||||
this.onGameOver = onGameOver;
|
||||
moveQueue = new LinkedBlockingQueue<Move>();
|
||||
|
||||
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);
|
||||
} else {
|
||||
primary = new GameView(onForfeit, () -> {
|
||||
isRunning.set(false);
|
||||
onExit.run();
|
||||
}, onMessage);
|
||||
}
|
||||
|
||||
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)
|
||||
.listen(NetworkEvents.YourTurnResponse.class, this::onYourTurnResponse);
|
||||
|
||||
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.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) {
|
||||
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.drawX(Color.RED, move.position());
|
||||
} else if (move.value() == 'O') {
|
||||
canvas.drawO(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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
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.TicTacToeCanvas;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.framework.networking.events.NetworkEvents;
|
||||
import org.toop.game.enumerators.GameState;
|
||||
import org.toop.game.records.Move;
|
||||
import org.toop.game.Game;
|
||||
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<TicTacToe, TicTacToeAI, TicTacToeCanvas> {
|
||||
public TicTacToeGameThread(GameInformation info, int myTurn, Runnable onForfeit, Runnable onExit, Consumer<String> onMessage, Runnable onGameOver) {
|
||||
super(info, myTurn, onForfeit, onExit, onMessage, onGameOver,
|
||||
@@ -47,7 +47,7 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
return turn == 0 ? "X" : "O";
|
||||
}
|
||||
|
||||
private void drawMove(Move move) {
|
||||
private void drawMove(Game.Move move) {
|
||||
if (move.value() == 'X') canvas.drawX(Color.RED, move.position());
|
||||
else canvas.drawO(Color.BLUE, move.position());
|
||||
}
|
||||
@@ -61,16 +61,16 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
char playerChar;
|
||||
|
||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||
playerChar = myTurn == 0 ? 'X' : 'O';
|
||||
playerChar = myTurn == 0? 'X' : 'O';
|
||||
} else {
|
||||
playerChar = myTurn == 0 ? 'O' : 'X';
|
||||
playerChar = myTurn == 0? 'O' : 'X';
|
||||
}
|
||||
|
||||
final Move move = new Move(Integer.parseInt(response.move()), playerChar);
|
||||
final GameState state = game.play(move);
|
||||
final Game.Move move = new Game.Move(Integer.parseInt(response.move()), playerChar);
|
||||
final Game.State state = game.play(move);
|
||||
|
||||
if (state != GameState.NORMAL) {
|
||||
if (state == GameState.WIN) {
|
||||
if (state != Game.State.NORMAL) {
|
||||
if (state == Game.State.WIN) {
|
||||
if (response.player().equalsIgnoreCase(information.players[0].name)) {
|
||||
primary.gameOver(true, information.players[0].name);
|
||||
gameOver();
|
||||
@@ -78,7 +78,7 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
primary.gameOver(false, information.players[1].name);
|
||||
gameOver();
|
||||
}
|
||||
} else if (state == GameState.DRAW) {
|
||||
} else if (state == Game.State.DRAW) {
|
||||
if (game.getLegalMoves().length == 0) {
|
||||
primary.gameOver(false, "");
|
||||
gameOver();
|
||||
@@ -103,13 +103,12 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
if (information.players[0].isHuman) {
|
||||
try {
|
||||
position = moveQueue.take().position();
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
} catch (InterruptedException _) {}
|
||||
} else {
|
||||
final Move move;
|
||||
final Game.Move move;
|
||||
if (information.players[1].name.equalsIgnoreCase("pism")) {
|
||||
move = ai.findWorstMove(game, 9);
|
||||
} else {
|
||||
move = ai.findWorstMove(game,9);
|
||||
}else{
|
||||
move = ai.findBestMove(game, information.players[0].computerDifficulty);
|
||||
}
|
||||
|
||||
@@ -117,7 +116,7 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
position = move.position();
|
||||
}
|
||||
|
||||
new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short) position))
|
||||
new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(), (short)position))
|
||||
.postEvent();
|
||||
}
|
||||
|
||||
@@ -127,22 +126,21 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
final int currentTurn = game.getCurrentTurn();
|
||||
setGameLabels(currentTurn == myTurn);
|
||||
|
||||
Move move = null;
|
||||
Game.Move move = null;
|
||||
|
||||
if (information.players[currentTurn].isHuman) {
|
||||
try {
|
||||
final Move wants = moveQueue.take();
|
||||
final Move[] legalMoves = game.getLegalMoves();
|
||||
final Game.Move wants = moveQueue.take();
|
||||
final Game.Move[] legalMoves = game.getLegalMoves();
|
||||
|
||||
for (final Move legalMove : legalMoves) {
|
||||
for (final Game.Move legalMove : legalMoves) {
|
||||
if (legalMove.position() == wants.position() &&
|
||||
legalMove.value() == wants.value()) {
|
||||
legalMove.value() == wants.value()) {
|
||||
move = wants;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
} catch (InterruptedException _) {}
|
||||
} else {
|
||||
final long start = System.currentTimeMillis();
|
||||
|
||||
@@ -153,9 +151,8 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
final long sleepTime = information.players[currentTurn].computerThinkTime * 1000L - elapsedTime;
|
||||
|
||||
try {
|
||||
Thread.sleep((long) (sleepTime * Math.random()));
|
||||
} catch (InterruptedException _) {
|
||||
}
|
||||
Thread.sleep((long)(sleepTime * Math.random()));
|
||||
} catch (InterruptedException _) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,13 +160,13 @@ public final class TicTacToeGameThread extends BaseGameThread<TicTacToe, TicTacT
|
||||
continue;
|
||||
}
|
||||
|
||||
final GameState state = game.play(move);
|
||||
final Game.State state = game.play(move);
|
||||
drawMove(move);
|
||||
|
||||
if (state != GameState.NORMAL) {
|
||||
if (state == GameState.WIN) {
|
||||
if (state != Game.State.NORMAL) {
|
||||
if (state == Game.State.WIN) {
|
||||
primary.gameOver(information.players[currentTurn].isHuman, information.players[currentTurn].name);
|
||||
} else if (state == GameState.DRAW) {
|
||||
} else if (state == Game.State.DRAW) {
|
||||
primary.gameOver(false, "");
|
||||
}
|
||||
|
||||
|
||||
400
app/src/main/java/org/toop/app/view/View.java
Normal file
400
app/src/main/java/org/toop/app/view/View.java
Normal file
@@ -0,0 +1,400 @@
|
||||
package org.toop.app.view;
|
||||
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class View {
|
||||
private final boolean mainView;
|
||||
|
||||
private final StackPane view;
|
||||
private final Map<String, Node> nodeMap;
|
||||
|
||||
protected View(boolean mainView, String cssClass) {
|
||||
this.mainView = mainView;
|
||||
|
||||
view = new StackPane();
|
||||
view.getStyleClass().add(cssClass);
|
||||
|
||||
nodeMap = new HashMap<String, Node>();
|
||||
}
|
||||
|
||||
public void add(Pos position, Node node) {
|
||||
assert node != null;
|
||||
|
||||
StackPane.setAlignment(node, position);
|
||||
view.getChildren().add(node);
|
||||
}
|
||||
|
||||
protected Region hspacer() {
|
||||
final Region hspacer = new Region();
|
||||
hspacer.getStyleClass().add("hspacer");
|
||||
|
||||
return hspacer;
|
||||
}
|
||||
|
||||
protected Region vspacer() {
|
||||
final Region vspacer = new Region();
|
||||
vspacer.getStyleClass().add("vspacer");
|
||||
|
||||
return vspacer;
|
||||
}
|
||||
|
||||
protected ScrollPane fit(String identifier, String cssClass, Node node) {
|
||||
assert node != null;
|
||||
|
||||
final ScrollPane fit = new ScrollPane(node);
|
||||
fit.getStyleClass().add(cssClass);
|
||||
|
||||
fit.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||
fit.setFitToWidth(true);
|
||||
fit.setFitToHeight(true);
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, fit);
|
||||
}
|
||||
|
||||
return fit;
|
||||
}
|
||||
|
||||
protected ScrollPane fit(String identifier, Node node) {
|
||||
return fit(identifier, "fit", node);
|
||||
}
|
||||
|
||||
protected ScrollPane fit(Node node) {
|
||||
return fit("", node);
|
||||
}
|
||||
|
||||
protected HBox hbox(String identifier, String cssClass, Node... nodes) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final HBox hbox = new HBox();
|
||||
hbox.getStyleClass().add(cssClass);
|
||||
hbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||
|
||||
for (final Node node : nodes) {
|
||||
if (node != null) {
|
||||
hbox.getChildren().add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, hbox);
|
||||
}
|
||||
|
||||
return hbox;
|
||||
}
|
||||
|
||||
protected HBox hbox(String identifier, Node... nodes) {
|
||||
return hbox(identifier, "container", nodes);
|
||||
}
|
||||
|
||||
protected HBox hbox(Node... nodes) {
|
||||
return hbox("", nodes);
|
||||
}
|
||||
|
||||
protected HBox hboxFill(String identifier, String cssClass, Node... nodes) {
|
||||
final HBox hbox = hbox(identifier, cssClass, nodes);
|
||||
|
||||
for (final Node node : hbox.getChildren()) {
|
||||
if (node instanceof Region) {
|
||||
((Region)node).setMaxHeight(Double.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return hbox;
|
||||
}
|
||||
|
||||
protected HBox hboxFill(String identifier, Node... nodes) {
|
||||
final HBox hbox = hbox(identifier, nodes);
|
||||
|
||||
for (final Node node : hbox.getChildren()) {
|
||||
if (node instanceof Region) {
|
||||
((Region)node).setMaxHeight(Double.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return hbox;
|
||||
}
|
||||
|
||||
protected HBox hboxFill(Node... nodes) {
|
||||
final HBox hbox = hbox(nodes);
|
||||
|
||||
for (final Node node : hbox.getChildren()) {
|
||||
if (node instanceof Region) {
|
||||
((Region)node).setMaxHeight(Double.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return hbox;
|
||||
}
|
||||
|
||||
protected VBox vbox(String identifier, String cssClass, Node... nodes) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final VBox vbox = new VBox();
|
||||
vbox.getStyleClass().add(cssClass);
|
||||
vbox.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
|
||||
|
||||
for (final Node node : nodes) {
|
||||
if (node != null) {
|
||||
vbox.getChildren().add(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, vbox);
|
||||
}
|
||||
|
||||
return vbox;
|
||||
}
|
||||
|
||||
protected VBox vbox(String identifier, Node... nodes) {
|
||||
return vbox(identifier, "container", nodes);
|
||||
}
|
||||
|
||||
protected VBox vbox(Node... nodes) {
|
||||
return vbox("", nodes);
|
||||
}
|
||||
|
||||
protected VBox vboxFill(String identifier, String cssClass, Node... nodes) {
|
||||
final VBox vbox = vbox(identifier, cssClass, nodes);
|
||||
|
||||
for (final Node node : vbox.getChildren()) {
|
||||
if (node instanceof Region) {
|
||||
((Region)node).setMaxWidth(Double.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return vbox;
|
||||
}
|
||||
|
||||
protected VBox vboxFill(String identifier, Node... nodes) {
|
||||
final VBox vbox = vbox(identifier, nodes);
|
||||
|
||||
for (final Node node : vbox.getChildren()) {
|
||||
if (node instanceof Region) {
|
||||
((Region)node).setMaxWidth(Double.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return vbox;
|
||||
}
|
||||
|
||||
protected VBox vboxFill(Node... nodes) {
|
||||
final VBox vbox = vbox(nodes);
|
||||
|
||||
for (final Node node : vbox.getChildren()) {
|
||||
if (node instanceof Region) {
|
||||
((Region)node).setMaxWidth(Double.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
return vbox;
|
||||
}
|
||||
|
||||
protected Separator separator(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final Separator separator = new Separator();
|
||||
separator.getStyleClass().add(cssClass);
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, separator);
|
||||
}
|
||||
|
||||
return separator;
|
||||
}
|
||||
|
||||
protected Separator separator(String identifier) {
|
||||
return separator(identifier, "separator");
|
||||
}
|
||||
|
||||
protected Separator separator() {
|
||||
return separator("");
|
||||
}
|
||||
|
||||
protected Text header(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final Text header = new Text();
|
||||
header.getStyleClass().add(cssClass);
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, header);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
protected Text header(String identifier) {
|
||||
return header(identifier, "header");
|
||||
}
|
||||
|
||||
protected Text header() {
|
||||
return header("");
|
||||
}
|
||||
|
||||
protected Text text(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final Text text = new Text();
|
||||
text.getStyleClass().add(cssClass);
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
protected Text text(String identifier) {
|
||||
return text(identifier, "text");
|
||||
}
|
||||
|
||||
protected Text text() {
|
||||
return text("");
|
||||
}
|
||||
|
||||
protected Button button(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final Button button = new Button();
|
||||
button.getStyleClass().add(cssClass);
|
||||
|
||||
button.setOnMouseClicked(_ -> {
|
||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||
});
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, button);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
protected Button button(String identifier) {
|
||||
return button(identifier, "button");
|
||||
}
|
||||
|
||||
protected Button button() {
|
||||
return button("");
|
||||
}
|
||||
|
||||
protected Slider slider(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final Slider slider = new Slider();
|
||||
slider.getStyleClass().add(cssClass);
|
||||
|
||||
slider.setMinorTickCount(0);
|
||||
slider.setMajorTickUnit(1);
|
||||
slider.setBlockIncrement(1);
|
||||
|
||||
slider.setSnapToTicks(true);
|
||||
slider.setShowTickLabels(true);
|
||||
|
||||
slider.setOnMouseClicked(_ -> {
|
||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||
});
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, slider);
|
||||
}
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
protected Slider slider(String identifier) {
|
||||
return slider(identifier, "slider");
|
||||
}
|
||||
|
||||
protected Slider slider() {
|
||||
return slider("");
|
||||
}
|
||||
|
||||
protected TextField input(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final TextField input = new TextField();
|
||||
input.getStyleClass().add(cssClass);
|
||||
|
||||
input.setOnMouseClicked(_ -> {
|
||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||
});
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, input);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
protected TextField input(String identifier) {
|
||||
return input(identifier, "input");
|
||||
}
|
||||
|
||||
protected TextField input() {
|
||||
return input("");
|
||||
}
|
||||
|
||||
protected <T> ComboBox<T> combobox(String identifier, String cssClass) {
|
||||
assert !nodeMap.containsKey(identifier);
|
||||
|
||||
final ComboBox<T> combobox = new ComboBox<T>();
|
||||
combobox.getStyleClass().add(cssClass);
|
||||
|
||||
combobox.setOnMouseClicked(_ -> {
|
||||
new EventFlow().addPostEvent(new AudioEvents.ClickButton()).asyncPostEvent();
|
||||
});
|
||||
|
||||
if (!identifier.isEmpty()) {
|
||||
nodeMap.put(identifier, combobox);
|
||||
}
|
||||
|
||||
return combobox;
|
||||
}
|
||||
|
||||
protected <T> ComboBox<T> combobox(String identifier) {
|
||||
return combobox(identifier, "combo-box");
|
||||
}
|
||||
|
||||
protected <T> ComboBox<T> combobox() {
|
||||
return combobox("");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Node> T get(String identifier) {
|
||||
assert nodeMap.containsKey(identifier);
|
||||
return (T) nodeMap.get(identifier);
|
||||
}
|
||||
|
||||
protected void clear() {
|
||||
view.getChildren().clear();
|
||||
nodeMap.clear();
|
||||
}
|
||||
|
||||
public boolean isMainView() { return mainView; }
|
||||
public Region getView() { return view; }
|
||||
|
||||
public abstract void setup();
|
||||
public void cleanup() {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
105
app/src/main/java/org/toop/app/view/ViewStack.java
Normal file
105
app/src/main/java/org/toop/app/view/ViewStack.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package org.toop.app.view;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
public final class ViewStack {
|
||||
private static boolean setup = false;
|
||||
|
||||
private static StackPane root;
|
||||
|
||||
private static View active;
|
||||
private static Stack<View> stack;
|
||||
|
||||
public static void setup(Scene scene) {
|
||||
assert scene != null;
|
||||
|
||||
if (setup) {
|
||||
return;
|
||||
}
|
||||
|
||||
root = new StackPane();
|
||||
|
||||
active = null;
|
||||
stack = new Stack<View>();
|
||||
|
||||
scene.setRoot(root);
|
||||
|
||||
setup = true;
|
||||
}
|
||||
|
||||
public static void cleanup() {
|
||||
assert setup;
|
||||
|
||||
final var count = stack.size();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
pop();
|
||||
}
|
||||
|
||||
if (active != null) {
|
||||
active.cleanup();
|
||||
}
|
||||
|
||||
setup = false;
|
||||
}
|
||||
|
||||
public static void reload() {
|
||||
assert setup;
|
||||
|
||||
for (final var view : stack) {
|
||||
view.cleanup();
|
||||
}
|
||||
|
||||
if (active != null) {
|
||||
active.cleanup();
|
||||
active.setup();
|
||||
}
|
||||
|
||||
for (final var view : stack) {
|
||||
view.setup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void push(View view) {
|
||||
assert setup;
|
||||
assert view != null;
|
||||
|
||||
if (view.isMainView()) {
|
||||
Platform.runLater(() -> {
|
||||
if (active != null) {
|
||||
root.getChildren().removeFirst();
|
||||
active.cleanup();
|
||||
}
|
||||
|
||||
root.getChildren().addFirst(view.getView());
|
||||
view.setup();
|
||||
|
||||
active = view;
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
stack.push(view);
|
||||
root.getChildren().addLast(view.getView());
|
||||
view.setup();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void pop() {
|
||||
assert setup;
|
||||
|
||||
if (stack.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
final var last = stack.pop();
|
||||
root.getChildren().removeLast();
|
||||
last.cleanup();
|
||||
});
|
||||
}
|
||||
}
|
||||
129
app/src/main/java/org/toop/app/view/displays/SongDisplay.java
Normal file
129
app/src/main/java/org/toop/app/view/displays/SongDisplay.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package org.toop.app.view.displays;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.toop.app.widget.Widget;
|
||||
import org.toop.framework.audio.AudioEventListener;
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.text.Text;
|
||||
import org.toop.framework.eventbus.GlobalEventBus;
|
||||
|
||||
public class SongDisplay extends VBox implements Widget {
|
||||
|
||||
private final Text songTitle;
|
||||
private final ProgressBar progressBar;
|
||||
private final Text progressText;
|
||||
private boolean paused = false;
|
||||
|
||||
public SongDisplay() {
|
||||
new EventFlow()
|
||||
.listen(this::updateTheSong);
|
||||
|
||||
setAlignment(Pos.CENTER);
|
||||
getStyleClass().add("song-display");
|
||||
|
||||
songTitle = new Text("song playing");
|
||||
songTitle.getStyleClass().add("song-title");
|
||||
|
||||
progressBar = new ProgressBar(0);
|
||||
progressBar.getStyleClass().add("progress-bar");
|
||||
|
||||
progressText = new Text("0:00/0:00");
|
||||
progressText.getStyleClass().add("progress-text");
|
||||
|
||||
Button skipButton = new Button(">>");
|
||||
Button pauseButton = new Button("⏸");
|
||||
Button previousButton = new Button("<<");
|
||||
|
||||
skipButton.getStyleClass().setAll("skip-button");
|
||||
pauseButton.getStyleClass().setAll("pause-button");
|
||||
previousButton.getStyleClass().setAll("previous-button");
|
||||
|
||||
skipButton.setOnAction( event -> {
|
||||
GlobalEventBus.post(new AudioEvents.SkipMusic());
|
||||
paused = false;
|
||||
pauseButton.setText(getPlayString(paused));
|
||||
});
|
||||
|
||||
pauseButton.setOnAction(event -> {
|
||||
GlobalEventBus.post(new AudioEvents.PauseMusic());
|
||||
paused = !paused;
|
||||
pauseButton.setText(getPlayString(paused));
|
||||
});
|
||||
|
||||
previousButton.setOnAction( event -> {
|
||||
GlobalEventBus.post(new AudioEvents.PreviousMusic());
|
||||
paused = false;
|
||||
pauseButton.setText(getPlayString(paused));
|
||||
});
|
||||
|
||||
HBox control = new HBox(10, previousButton, pauseButton, skipButton);
|
||||
control.setAlignment(Pos.CENTER);
|
||||
control.getStyleClass().add("controls");
|
||||
|
||||
getChildren().addAll(songTitle, progressBar, progressText, control);
|
||||
}
|
||||
|
||||
private void updateTheSong(AudioEvents.PlayingMusic event) {
|
||||
Platform.runLater(() -> {
|
||||
String text = event.name();
|
||||
text = text.substring(0, text.length() - 4);
|
||||
songTitle.setText(text);
|
||||
double currentPos = event.currentPosition();
|
||||
double duration = event.duration();
|
||||
if (currentPos / duration > 0.05) {
|
||||
double progress = currentPos / duration;
|
||||
progressBar.setProgress(progress);
|
||||
}
|
||||
else if (currentPos / duration < 0.05) {
|
||||
progressBar.setProgress(0.05);
|
||||
}
|
||||
progressText.setText(getTimeString(event.currentPosition(), event.duration()));
|
||||
});
|
||||
}
|
||||
|
||||
private String getTimeString(long position, long duration) {
|
||||
long positionMinutes = position / 60;
|
||||
long durationMinutes = duration / 60;
|
||||
long positionSeconds = position % 60;
|
||||
long durationSeconds = duration % 60;
|
||||
String positionSecondsStr = String.valueOf(positionSeconds);
|
||||
String durationSecondsStr = String.valueOf(durationSeconds);
|
||||
|
||||
if (positionSeconds < 10) {
|
||||
positionSecondsStr = "0" + positionSeconds;
|
||||
}
|
||||
if (durationSeconds < 10) {
|
||||
durationSecondsStr = "0" + durationSeconds;
|
||||
}
|
||||
|
||||
String time = positionMinutes + ":" + positionSecondsStr + " / " + durationMinutes + ":" + durationSecondsStr;
|
||||
return time;
|
||||
}
|
||||
|
||||
private String getPlayString(boolean paused) {
|
||||
if (paused) {
|
||||
return "▶";
|
||||
}
|
||||
else {
|
||||
return "⏸";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getNode() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
127
app/src/main/java/org/toop/app/view/views/ChallengeView.java
Normal file
127
app/src/main/java/org/toop/app/view/views/ChallengeView.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.GameInformation;
|
||||
import org.toop.app.Server;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ChallengeView extends View {
|
||||
private final GameInformation.Player playerInformation;
|
||||
|
||||
private final String challenger;
|
||||
private final String game;
|
||||
|
||||
private final Consumer<GameInformation.Player> onAccept;
|
||||
|
||||
public ChallengeView(String challenger, String game, Consumer<GameInformation.Player> onAccept) {
|
||||
super(false, "bg-popup");
|
||||
|
||||
playerInformation = new GameInformation.Player();
|
||||
|
||||
this.challenger = challenger;
|
||||
this.game = game;
|
||||
|
||||
this.onAccept = onAccept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text challengeText = text();
|
||||
challengeText.setText(AppContext.getString("you-were-challenged-by"));
|
||||
|
||||
final Text challengerHeader = header();
|
||||
challengerHeader.setText(challenger);
|
||||
|
||||
final Text gameText = text();
|
||||
gameText.setText(AppContext.getString("to-a-game-of") + " " + game);
|
||||
|
||||
final Button acceptButton = button();
|
||||
acceptButton.setText(AppContext.getString("accept"));
|
||||
acceptButton.setOnAction(_ -> {
|
||||
onAccept.accept(playerInformation);
|
||||
});
|
||||
|
||||
final Button denyButton = button();
|
||||
denyButton.setText(AppContext.getString("deny"));
|
||||
denyButton.setOnAction(_ -> {
|
||||
ViewStack.pop();
|
||||
});
|
||||
|
||||
final List<Node> nodes = new ArrayList<>();
|
||||
|
||||
if (playerInformation.isHuman) {
|
||||
final Button playerToggle = button();
|
||||
playerToggle.setText(AppContext.getString("player"));
|
||||
playerToggle.setOnAction(_ -> {
|
||||
playerInformation.isHuman = false;
|
||||
cleanup();
|
||||
setup();
|
||||
});
|
||||
|
||||
nodes.add(vbox(playerToggle));
|
||||
} else {
|
||||
final Button computerToggle = button();
|
||||
computerToggle.setText(AppContext.getString("computer"));
|
||||
computerToggle.setOnAction(_ -> {
|
||||
playerInformation.isHuman = true;
|
||||
cleanup();
|
||||
setup();
|
||||
});
|
||||
|
||||
nodes.add(vbox(computerToggle));
|
||||
|
||||
final Text computerDifficultyText = text();
|
||||
computerDifficultyText.setText(AppContext.getString("computer-difficulty"));
|
||||
|
||||
final Slider computerDifficultySlider = slider();
|
||||
computerDifficultySlider.setMin(0);
|
||||
computerDifficultySlider.setMax(Server.gameToType(game).getMaxDepth());
|
||||
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
|
||||
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
playerInformation.computerDifficulty = newValue.intValue();
|
||||
});
|
||||
|
||||
nodes.add(vbox(computerDifficultyText, computerDifficultySlider));
|
||||
}
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(hboxFill(
|
||||
vboxFill(
|
||||
challengeText,
|
||||
challengerHeader,
|
||||
gameText,
|
||||
separator(),
|
||||
|
||||
hboxFill(
|
||||
acceptButton,
|
||||
denyButton
|
||||
)
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
nodes.toArray(new Node[0])
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
110
app/src/main/java/org/toop/app/view/views/CreditsView.java
Normal file
110
app/src/main/java/org/toop/app/view/views/CreditsView.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.App;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public final class CreditsView extends View {
|
||||
public CreditsView() {
|
||||
super(false, "bg-primary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text scrumMasterHeader = header();
|
||||
scrumMasterHeader.setText(AppContext.getString("scrum-master") + ": Stef");
|
||||
|
||||
final Text productOwnerHeader = header();
|
||||
productOwnerHeader.setText(AppContext.getString("product-owner") + ": Omar");
|
||||
|
||||
final Text mergeCommanderHeader = header();
|
||||
mergeCommanderHeader.setText(AppContext.getString("merge-commander") + ": Bas");
|
||||
|
||||
final Text localizationHeader = header();
|
||||
localizationHeader.setText(AppContext.getString("localization") + ": Ticho");
|
||||
|
||||
final Text aiHeader = header();
|
||||
aiHeader.setText(AppContext.getString("ai") + ": Michiel");
|
||||
|
||||
final Text developersHeader = header();
|
||||
developersHeader.setText(AppContext.getString("developers") + ": Michiel, Bas, Stef, Omar, Ticho");
|
||||
|
||||
final Text moralSupportHeader = header();
|
||||
moralSupportHeader.setText(AppContext.getString("moral-support") + ": Wesley");
|
||||
|
||||
final Text openglHeader = header();
|
||||
openglHeader.setText(AppContext.getString("opengl") + ": Omar");
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit("credits-fit", vboxFill("credits-container", "credits-container",
|
||||
vbox("credits-spacer-top", ""),
|
||||
|
||||
scrumMasterHeader,
|
||||
productOwnerHeader,
|
||||
mergeCommanderHeader,
|
||||
localizationHeader,
|
||||
aiHeader,
|
||||
developersHeader,
|
||||
moralSupportHeader,
|
||||
openglHeader,
|
||||
|
||||
vbox("credits-spacer-bottom", "")
|
||||
))
|
||||
);
|
||||
|
||||
final Button backButton = button();
|
||||
backButton.setText(AppContext.getString("back"));
|
||||
backButton.setOnAction(_ -> { ViewStack.pop(); });
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
backButton
|
||||
)
|
||||
);
|
||||
|
||||
playCredits(100, 20);
|
||||
}
|
||||
|
||||
private void playCredits(int lineHeight, int length) {
|
||||
final ScrollPane creditsFit = get("credits-fit");
|
||||
creditsFit.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
creditsFit.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
|
||||
final VBox creditsContainer = get("credits-container");
|
||||
creditsContainer.setSpacing(lineHeight);
|
||||
|
||||
final VBox creditsSpacerTop = get("credits-spacer-top");
|
||||
creditsSpacerTop.setMinHeight(App.getHeight() - lineHeight);
|
||||
|
||||
final VBox creditsSpacerBottom = get("credits-spacer-bottom");
|
||||
creditsSpacerBottom.setMinHeight(App.getHeight() - lineHeight);
|
||||
|
||||
final Timeline timeline = new Timeline(
|
||||
new KeyFrame(Duration.seconds(0), new KeyValue(creditsFit.vvalueProperty(), 0.0)),
|
||||
new KeyFrame(Duration.seconds(length), new KeyValue(creditsFit.vvalueProperty(), 1.0))
|
||||
);
|
||||
|
||||
timeline.setCycleCount(Timeline.INDEFINITE);
|
||||
timeline.play();
|
||||
}
|
||||
}
|
||||
45
app/src/main/java/org/toop/app/view/views/ErrorView.java
Normal file
45
app/src/main/java/org/toop/app/view/views/ErrorView.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public final class ErrorView extends View {
|
||||
private final String error;
|
||||
|
||||
public ErrorView(String error) {
|
||||
super(false, "bg-popup");
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text errorHeader = header();
|
||||
errorHeader.setText(AppContext.getString("error"));
|
||||
|
||||
final Text errorText = text();
|
||||
errorText.setText(error);
|
||||
|
||||
final Button okButton = button();
|
||||
okButton.setText(AppContext.getString("ok"));
|
||||
okButton.setOnAction(_ -> { ViewStack.pop(); });
|
||||
|
||||
add(Pos.CENTER,
|
||||
vboxFill(
|
||||
errorHeader,
|
||||
separator(),
|
||||
|
||||
vspacer(),
|
||||
errorText,
|
||||
vspacer(),
|
||||
|
||||
separator(),
|
||||
okButton
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
184
app/src/main/java/org/toop/app/view/views/GameView.java
Normal file
184
app/src/main/java/org/toop/app/view/views/GameView.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class GameView extends View {
|
||||
private static class GameOverView extends View {
|
||||
private final boolean iWon;
|
||||
private final String winner;
|
||||
|
||||
public GameOverView(boolean iWon, String winner) {
|
||||
super(false, "bg-popup");
|
||||
|
||||
this.iWon = iWon;
|
||||
this.winner = winner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text gameOverHeader = header();
|
||||
gameOverHeader.setText(AppContext.getString("game-over"));
|
||||
|
||||
final Button okButton = button();
|
||||
okButton.setText(AppContext.getString("ok"));
|
||||
okButton.setOnAction(_ -> { ViewStack.pop(); });
|
||||
|
||||
Text gameOverText = text();
|
||||
|
||||
if (winner.isEmpty()) {
|
||||
gameOverText.setText(AppContext.getString("the-game-ended-in-a-draw"));
|
||||
} else {
|
||||
if (iWon) {
|
||||
gameOverText.setText(AppContext.getString("you-win") + " " + winner);
|
||||
} else {
|
||||
gameOverText.setText(AppContext.getString("you-lost-against") + " " + winner);
|
||||
}
|
||||
}
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vboxFill(
|
||||
gameOverHeader,
|
||||
separator(),
|
||||
|
||||
vspacer(),
|
||||
gameOverText,
|
||||
vspacer(),
|
||||
|
||||
separator(),
|
||||
okButton
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private final Button forfeitButton;
|
||||
private final Button exitButton;
|
||||
|
||||
private final Text currentPlayerHeader;
|
||||
private final Text currentMoveHeader;
|
||||
|
||||
private final Text nextPlayerHeader;
|
||||
|
||||
private final ListView<Text> chatListView;
|
||||
private final TextField chatInput;
|
||||
|
||||
public GameView(Runnable onForfeit, Runnable onExit, Consumer<String> onMessage) {
|
||||
assert onExit != null;
|
||||
|
||||
super(true, "bg-primary");
|
||||
|
||||
if (onForfeit != null) {
|
||||
forfeitButton = button();
|
||||
forfeitButton.setText(AppContext.getString("forfeit"));
|
||||
forfeitButton.setOnAction(_ -> onForfeit.run());
|
||||
} else {
|
||||
forfeitButton = null;
|
||||
}
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
if (onMessage != null) {
|
||||
chatListView = new ListView<Text>();
|
||||
|
||||
chatInput = input();
|
||||
chatInput.setOnAction(_ -> {
|
||||
onMessage.accept(chatInput.getText());
|
||||
chatInput.setText("");
|
||||
});
|
||||
} else {
|
||||
chatListView = null;
|
||||
chatInput = null;
|
||||
}
|
||||
|
||||
exitButton = button();
|
||||
exitButton.setText(AppContext.getString("exit"));
|
||||
exitButton.setOnAction(_ -> onExit.run());
|
||||
|
||||
currentPlayerHeader = header("", "current-player");
|
||||
currentMoveHeader = header();
|
||||
|
||||
nextPlayerHeader = header();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
add(Pos.TOP_RIGHT,
|
||||
fit(vboxFill(
|
||||
currentPlayerHeader,
|
||||
|
||||
hboxFill(
|
||||
separator(),
|
||||
currentMoveHeader,
|
||||
separator()
|
||||
),
|
||||
|
||||
nextPlayerHeader
|
||||
))
|
||||
);
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
forfeitButton,
|
||||
exitButton
|
||||
)
|
||||
);
|
||||
|
||||
if (chatListView != null) {
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
chatListView,
|
||||
chatInput
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public void nextPlayer(boolean isMe, String currentPlayer, String currentMove, String nextPlayer) {
|
||||
Platform.runLater(() -> {
|
||||
currentPlayerHeader.setText(currentPlayer);
|
||||
currentMoveHeader.setText(currentMove);
|
||||
|
||||
nextPlayerHeader.setText(nextPlayer);
|
||||
|
||||
if (isMe) {
|
||||
currentPlayerHeader.getStyleClass().add("my-turn");
|
||||
} else {
|
||||
currentPlayerHeader.getStyleClass().remove("my-turn");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void updateChat(String message) {
|
||||
if (chatListView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Text messageText = text();
|
||||
messageText.setText(message);
|
||||
|
||||
chatListView.getItems().add(messageText);
|
||||
}
|
||||
|
||||
public void gameOver(boolean iWon, String winner) {
|
||||
ViewStack.push(new GameOverView(iWon, winner));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.GameInformation;
|
||||
import org.toop.app.game.Connect4Game;
|
||||
import org.toop.app.game.ReversiGame;
|
||||
import org.toop.app.game.TicTacToeGameThread;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class LocalMultiplayerView extends View {
|
||||
private final GameInformation information;
|
||||
|
||||
public LocalMultiplayerView(GameInformation information) {
|
||||
super(true, "bg-primary");
|
||||
this.information = information;
|
||||
}
|
||||
|
||||
public LocalMultiplayerView(GameInformation.Type type) {
|
||||
this(new GameInformation(type));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Button playButton = button();
|
||||
playButton.setText(AppContext.getString("play"));
|
||||
playButton.setOnAction(_ -> {
|
||||
for (final GameInformation.Player player : information.players) {
|
||||
if (player.name.isEmpty()) {
|
||||
ViewStack.push(new ErrorView(AppContext.getString("please-enter-your-name")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (information.type) {
|
||||
case TICTACTOE: new TicTacToeGameThread(information); break;
|
||||
case REVERSI: new ReversiGame(information); break;
|
||||
case CONNECT4: new Connect4Game(information); break;
|
||||
// case BATTLESHIP: new BattleshipGame(information); break;
|
||||
}
|
||||
});
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vboxFill(
|
||||
hbox(
|
||||
setupPlayers()
|
||||
),
|
||||
|
||||
separator(),
|
||||
playButton
|
||||
))
|
||||
);
|
||||
|
||||
final Button backButton = button();
|
||||
backButton.setText(AppContext.getString("back"));
|
||||
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
backButton
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private VBox[] setupPlayers() {
|
||||
final VBox[] playerBoxes = new VBox[information.type.getPlayerCount()];
|
||||
|
||||
for (int i = 0; i < playerBoxes.length; i++) {
|
||||
final int index = i;
|
||||
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
|
||||
final Text playerHeader = header();
|
||||
playerHeader.setText(AppContext.getString("player") + " #" + (i + 1));
|
||||
|
||||
nodes.add(playerHeader);
|
||||
nodes.add(separator());
|
||||
|
||||
final Text nameText = text();
|
||||
nameText.setText(AppContext.getString("name"));
|
||||
|
||||
if (information.players[i].isHuman) {
|
||||
final Button playerToggle = button();
|
||||
playerToggle.setText(AppContext.getString("player"));
|
||||
playerToggle.setOnAction(_ -> {
|
||||
information.players[index].isHuman = false;
|
||||
cleanup();
|
||||
setup();
|
||||
});
|
||||
|
||||
nodes.add(vboxFill(playerToggle));
|
||||
|
||||
final TextField playerNameInput = input();
|
||||
playerNameInput.setPromptText(AppContext.getString("enter-your-name"));
|
||||
playerNameInput.setText(information.players[i].name);
|
||||
playerNameInput.textProperty().addListener((_, _, newValue) -> {
|
||||
information.players[index].name = newValue;
|
||||
});
|
||||
|
||||
nodes.add(vboxFill(nameText, playerNameInput));
|
||||
} else {
|
||||
final Button computerToggle = button();
|
||||
computerToggle.setText(AppContext.getString("computer"));
|
||||
computerToggle.setOnAction(_ -> {
|
||||
information.players[index].isHuman = true;
|
||||
cleanup();
|
||||
setup();
|
||||
});
|
||||
|
||||
nodes.add(vboxFill(computerToggle));
|
||||
|
||||
information.players[i].name = "Pism Bot V" + i;
|
||||
|
||||
final Text computerNameText = text();
|
||||
computerNameText.setText(information.players[index].name);
|
||||
|
||||
nodes.add(vboxFill(nameText, computerNameText));
|
||||
|
||||
final Text computerDifficultyText = text();
|
||||
computerDifficultyText.setText(AppContext.getString("computer-difficulty"));
|
||||
|
||||
final Slider computerDifficultySlider = slider();
|
||||
computerDifficultySlider.setMin(0);
|
||||
computerDifficultySlider.setMax(information.type.getMaxDepth());
|
||||
computerDifficultySlider.setValue(information.players[i].computerDifficulty);
|
||||
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
information.players[index].computerDifficulty = newValue.intValue();
|
||||
});
|
||||
|
||||
nodes.add(vboxFill(computerDifficultyText, computerDifficultySlider));
|
||||
|
||||
final Text computerThinkTimeText = text();
|
||||
computerThinkTimeText.setText(AppContext.getString("computer-think-time"));
|
||||
|
||||
final Slider computerThinkTimeSlider = slider();
|
||||
computerThinkTimeSlider.setMin(0);
|
||||
computerThinkTimeSlider.setMax(5);
|
||||
computerThinkTimeSlider.setValue(information.players[i].computerThinkTime);
|
||||
computerThinkTimeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
information.players[index].computerThinkTime = newValue.intValue();
|
||||
});
|
||||
|
||||
nodes.add(vboxFill(computerThinkTimeText, computerThinkTimeSlider));
|
||||
}
|
||||
|
||||
playerBoxes[i] = vboxFill(nodes.toArray(new Node[0]));
|
||||
}
|
||||
|
||||
return playerBoxes;
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/org/toop/app/view/views/LocalView.java
Normal file
57
app/src/main/java/org/toop/app/view/views/LocalView.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.GameInformation;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
|
||||
public final class LocalView extends View {
|
||||
public LocalView() {
|
||||
super(true, "bg-primary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Button ticTacToeButton = button();
|
||||
ticTacToeButton.setText(AppContext.getString("tic-tac-toe"));
|
||||
ticTacToeButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.TICTACTOE)); });
|
||||
|
||||
final Button reversiButton = button();
|
||||
reversiButton.setText(AppContext.getString("reversi"));
|
||||
reversiButton.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.REVERSI)); });
|
||||
|
||||
final Button connect4Button = button();
|
||||
connect4Button.setText(AppContext.getString("connect4"));
|
||||
connect4Button.setOnAction(_ -> { ViewStack.push(new LocalMultiplayerView(GameInformation.Type.CONNECT4)); });
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vboxFill(
|
||||
ticTacToeButton,
|
||||
reversiButton,
|
||||
connect4Button
|
||||
))
|
||||
);
|
||||
|
||||
final Button backButton = button();
|
||||
backButton.setText(AppContext.getString("back"));
|
||||
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
backButton
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
55
app/src/main/java/org/toop/app/view/views/MainView.java
Normal file
55
app/src/main/java/org/toop/app/view/views/MainView.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.App;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.local.AppContext;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
|
||||
public final class MainView extends View {
|
||||
public MainView() {
|
||||
super(true, "bg-primary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Button localButton = button();
|
||||
localButton.setText(AppContext.getString("local"));
|
||||
localButton.setOnAction(_ -> { ViewStack.push(new LocalView()); });
|
||||
|
||||
final Button onlineButton = button();
|
||||
onlineButton.setText(AppContext.getString("online"));
|
||||
onlineButton.setOnAction(_ -> { ViewStack.push(new OnlineView()); });
|
||||
|
||||
final Button creditsButton = button();
|
||||
creditsButton.setText(AppContext.getString("credits"));
|
||||
creditsButton.setOnAction(_ -> { ViewStack.push(new CreditsView()); });
|
||||
|
||||
final Button optionsButton = button();
|
||||
optionsButton.setText(AppContext.getString("options"));
|
||||
optionsButton.setOnAction(_ -> { ViewStack.push(new OptionsView()); });
|
||||
|
||||
final Button quitButton = button();
|
||||
quitButton.setText(AppContext.getString("quit"));
|
||||
quitButton.setOnAction(_ -> { App.startQuit(); });
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vboxFill(
|
||||
localButton,
|
||||
onlineButton,
|
||||
creditsButton,
|
||||
optionsButton,
|
||||
quitButton
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
91
app/src/main/java/org/toop/app/view/views/OnlineView.java
Normal file
91
app/src/main/java/org/toop/app/view/views/OnlineView.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.Server;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public class OnlineView extends View {
|
||||
public OnlineView() {
|
||||
super(true, "bg-primary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text serverInformationHeader = header();
|
||||
serverInformationHeader.setText(AppContext.getString("server-information"));
|
||||
|
||||
final Text serverIPText = text();
|
||||
serverIPText.setText(AppContext.getString("ip-address"));
|
||||
|
||||
final TextField serverIPInput = input();
|
||||
serverIPInput.setPromptText(AppContext.getString("enter-the-server-ip"));
|
||||
|
||||
final Text serverPortText = text();
|
||||
serverPortText.setText(AppContext.getString("port"));
|
||||
|
||||
final TextField serverPortInput = input();
|
||||
serverPortInput.setPromptText(AppContext.getString("enter-the-server-port"));
|
||||
|
||||
final Text playerNameText = text();
|
||||
playerNameText.setText(AppContext.getString("player-name"));
|
||||
|
||||
final TextField playerNameInput = input();
|
||||
playerNameInput.setPromptText(AppContext.getString("enter-your-name"));
|
||||
|
||||
final Button connectButton = button();
|
||||
connectButton.setText(AppContext.getString("connect"));
|
||||
connectButton.setOnAction(_ -> {
|
||||
new Server(serverIPInput.getText(), serverPortInput.getText(), playerNameInput.getText());
|
||||
});
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vboxFill(
|
||||
serverInformationHeader,
|
||||
separator(),
|
||||
|
||||
vboxFill(
|
||||
serverIPText,
|
||||
serverIPInput
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
serverPortText,
|
||||
serverPortInput
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
playerNameText,
|
||||
playerNameInput
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
connectButton
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
final Button backButton = button();
|
||||
backButton.setText(AppContext.getString("back"));
|
||||
backButton.setOnAction(_ -> { ViewStack.push(new MainView()); });
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
backButton
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
258
app/src/main/java/org/toop/app/view/views/OptionsView.java
Normal file
258
app/src/main/java/org/toop/app/view/views/OptionsView.java
Normal file
@@ -0,0 +1,258 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.App;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.framework.audio.VolumeControl;
|
||||
import org.toop.framework.audio.events.AudioEvents;
|
||||
import org.toop.framework.eventbus.EventFlow;
|
||||
import org.toop.local.AppContext;
|
||||
import org.toop.local.AppSettings;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class OptionsView extends View {
|
||||
public OptionsView() {
|
||||
super(false, "bg-secondary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text generalHeader = header();
|
||||
generalHeader.setText(AppContext.getString("general"));
|
||||
|
||||
final Text volumeHeader = header();
|
||||
volumeHeader.setText(AppContext.getString("volume"));
|
||||
|
||||
final Text styleHeader = header();
|
||||
styleHeader.setText(AppContext.getString("style"));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(hboxFill(
|
||||
vboxFill(
|
||||
generalHeader,
|
||||
separator(),
|
||||
|
||||
vboxFill(
|
||||
text("language-text"),
|
||||
combobox("language-combobox")
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
button("fullscreen-button")
|
||||
)
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
volumeHeader,
|
||||
separator(),
|
||||
|
||||
vboxFill(
|
||||
text("master-volume-text"),
|
||||
slider("master-volume-slider")
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
text("effects-volume-text"),
|
||||
slider("effects-volume-slider")
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
text("music-volume-text"),
|
||||
slider("music-volume-slider")
|
||||
)
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
styleHeader,
|
||||
separator(),
|
||||
|
||||
vboxFill(
|
||||
text("theme-text"),
|
||||
combobox("theme-combobox")
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
text("layout-text"),
|
||||
combobox("layout-combobox")
|
||||
)
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
setupLanguageOption();
|
||||
setupMasterVolumeOption();
|
||||
setupEffectsVolumeOption();
|
||||
setupMusicVolumeOption();
|
||||
setupThemeOption();
|
||||
setupLayoutOption();
|
||||
setupFullscreenOption();
|
||||
|
||||
final Button backButton = button();
|
||||
backButton.setText(AppContext.getString("back"));
|
||||
backButton.setOnAction(_ -> { ViewStack.pop(); });
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
backButton
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void setupLanguageOption() {
|
||||
final Text languageText = get("language-text");
|
||||
languageText.setText(AppContext.getString("language"));
|
||||
|
||||
final ComboBox<Locale> languageCombobox = get("language-combobox");
|
||||
languageCombobox.getItems().addAll(AppContext.getLocalization().getAvailableLocales());
|
||||
languageCombobox.setValue(AppContext.getLocale());
|
||||
|
||||
languageCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
|
||||
AppSettings.getSettings().setLocale(newValue.toString());
|
||||
AppContext.setLocale(newValue);
|
||||
});
|
||||
|
||||
languageCombobox.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Locale locale) {
|
||||
return AppContext.getString(locale.getDisplayName().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale fromString(String s) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupMasterVolumeOption() {
|
||||
final Text masterVolumeText = get("master-volume-text");
|
||||
masterVolumeText.setText(AppContext.getString("master-volume"));
|
||||
|
||||
final Slider masterVolumeSlider = get("master-volume-slider");
|
||||
masterVolumeSlider.setMin(0);
|
||||
masterVolumeSlider.setMax(100);
|
||||
masterVolumeSlider.setValue(AppSettings.getSettings().getVolume());
|
||||
|
||||
masterVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
AppSettings.getSettings().setVolume(newValue.intValue());
|
||||
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MASTERVOLUME)).asyncPostEvent();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupEffectsVolumeOption() {
|
||||
final Text effectsVolumeText = get("effects-volume-text");
|
||||
effectsVolumeText.setText(AppContext.getString("effects-volume"));
|
||||
|
||||
final Slider effectsVolumeSlider = get("effects-volume-slider");
|
||||
effectsVolumeSlider.setMin(0);
|
||||
effectsVolumeSlider.setMax(100);
|
||||
effectsVolumeSlider.setValue(AppSettings.getSettings().getFxVolume());
|
||||
|
||||
effectsVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
AppSettings.getSettings().setFxVolume(newValue.intValue());
|
||||
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.FX)).asyncPostEvent();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupMusicVolumeOption() {
|
||||
final Text musicVolumeText = get("music-volume-text");
|
||||
musicVolumeText.setText(AppContext.getString("music-volume"));
|
||||
|
||||
final Slider musicVolumeSlider = get("music-volume-slider");
|
||||
musicVolumeSlider.setMin(0);
|
||||
musicVolumeSlider.setMax(100);
|
||||
musicVolumeSlider.setValue(AppSettings.getSettings().getMusicVolume());
|
||||
|
||||
musicVolumeSlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
AppSettings.getSettings().setMusicVolume(newValue.intValue());
|
||||
new EventFlow().addPostEvent(new AudioEvents.ChangeVolume(newValue.doubleValue(), VolumeControl.MUSIC)).asyncPostEvent();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupThemeOption() {
|
||||
final Text themeText = get("theme-text");
|
||||
themeText.setText(AppContext.getString("theme"));
|
||||
|
||||
final ComboBox<String> themeCombobox = get("theme-combobox");
|
||||
themeCombobox.getItems().addAll("dark", "light", "high-contrast");
|
||||
themeCombobox.setValue(AppSettings.getSettings().getTheme());
|
||||
|
||||
themeCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
|
||||
AppSettings.getSettings().setTheme(newValue);
|
||||
App.setStyle(newValue, AppSettings.getSettings().getLayoutSize());
|
||||
});
|
||||
|
||||
themeCombobox.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(String theme) {
|
||||
return AppContext.getString(theme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fromString(String s) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupLayoutOption() {
|
||||
final Text layoutText = get("layout-text");
|
||||
layoutText.setText(AppContext.getString("layout-size"));
|
||||
|
||||
final ComboBox<String> layoutCombobox = get("layout-combobox");
|
||||
layoutCombobox.getItems().addAll("small", "medium", "large");
|
||||
layoutCombobox.setValue(AppSettings.getSettings().getLayoutSize());
|
||||
|
||||
layoutCombobox.getSelectionModel().selectedItemProperty().addListener((_, _, newValue) -> {
|
||||
AppSettings.getSettings().setLayoutSize(newValue);
|
||||
App.setStyle(AppSettings.getSettings().getTheme(), newValue);
|
||||
});
|
||||
|
||||
layoutCombobox.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(String layout) {
|
||||
return AppContext.getString(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fromString(String s) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupFullscreenOption() {
|
||||
final Button fullscreenButton = get("fullscreen-button");
|
||||
|
||||
if (AppSettings.getSettings().getFullscreen()) {
|
||||
fullscreenButton.setText(AppContext.getString("windowed"));
|
||||
fullscreenButton.setOnAction(_ -> {
|
||||
AppSettings.getSettings().setFullscreen(false);
|
||||
App.setFullscreen(false);
|
||||
});
|
||||
} else {
|
||||
fullscreenButton.setText(AppContext.getString("fullscreen"));
|
||||
fullscreenButton.setOnAction(_ -> {
|
||||
AppSettings.getSettings().setFullscreen(true);
|
||||
App.setFullscreen(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/src/main/java/org/toop/app/view/views/QuitView.java
Normal file
40
app/src/main/java/org/toop/app/view/views/QuitView.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.App;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
public final class QuitView extends View {
|
||||
public QuitView() {
|
||||
super(false, "bg-popup");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text sureHeader = header();
|
||||
sureHeader.setText(AppContext.getString("are-you-sure"));
|
||||
|
||||
final Button yesButton = button();
|
||||
yesButton.setText(AppContext.getString("yes"));
|
||||
yesButton.setOnAction(_ -> { App.quit(); });
|
||||
|
||||
final Button noButton = button();
|
||||
noButton.setText(AppContext.getString("no"));
|
||||
noButton.setOnAction(_ -> { App.stopQuit(); });
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vbox(
|
||||
sureHeader,
|
||||
|
||||
hbox(
|
||||
yesButton,
|
||||
noButton
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
119
app/src/main/java/org/toop/app/view/views/SendChallengeView.java
Normal file
119
app/src/main/java/org/toop/app/view/views/SendChallengeView.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.GameInformation;
|
||||
import org.toop.app.Server;
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public final class SendChallengeView extends View {
|
||||
private final Server server;
|
||||
private final String opponent;
|
||||
private final BiConsumer<GameInformation.Player, String> onSend;
|
||||
|
||||
private final GameInformation.Player playerInformation;
|
||||
|
||||
public SendChallengeView(Server server, String opponent, BiConsumer<GameInformation.Player, String> onSend) {
|
||||
super(false, "bg-popup");
|
||||
|
||||
this.server = server;
|
||||
this.opponent = opponent;
|
||||
this.onSend = onSend;
|
||||
|
||||
playerInformation = new GameInformation.Player();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text challengeText = text();
|
||||
challengeText.setText(AppContext.getString("challenge"));
|
||||
|
||||
final Text opponentHeader = header();
|
||||
opponentHeader.setText(opponent);
|
||||
|
||||
final Text gameText = text();
|
||||
gameText.setText(AppContext.getString("to-a-game-of"));
|
||||
|
||||
final ComboBox<String> gamesCombobox = combobox();
|
||||
gamesCombobox.getItems().addAll(server.getGameList());
|
||||
gamesCombobox.setValue(gamesCombobox.getItems().getFirst());
|
||||
|
||||
final Button sendButton = button();
|
||||
sendButton.setText(AppContext.getString("send"));
|
||||
sendButton.setOnAction(_ -> { onSend.accept(playerInformation, gamesCombobox.getValue()); });
|
||||
|
||||
final Button cancelButton = button();
|
||||
cancelButton.setText(AppContext.getString("cancel"));
|
||||
cancelButton.setOnAction(_ -> {
|
||||
ViewStack.pop(); });
|
||||
|
||||
final List<Node> nodes = new ArrayList<>();
|
||||
|
||||
if (playerInformation.isHuman) {
|
||||
final Button playerToggle = button();
|
||||
playerToggle.setText(AppContext.getString("player"));
|
||||
playerToggle.setOnAction(_ -> {
|
||||
playerInformation.isHuman = false;
|
||||
cleanup();
|
||||
setup();
|
||||
});
|
||||
|
||||
nodes.add(vbox(playerToggle));
|
||||
} else {
|
||||
final Button computerToggle = button();
|
||||
computerToggle.setText(AppContext.getString("computer"));
|
||||
computerToggle.setOnAction(_ -> {
|
||||
playerInformation.isHuman = true;
|
||||
cleanup();
|
||||
setup();
|
||||
});
|
||||
|
||||
nodes.add(vbox(computerToggle));
|
||||
|
||||
final Text computerDifficultyText = text();
|
||||
computerDifficultyText.setText(AppContext.getString("computer-difficulty"));
|
||||
|
||||
final Slider computerDifficultySlider = slider();
|
||||
computerDifficultySlider.setMin(0);
|
||||
computerDifficultySlider.setMax(Server.gameToType(gamesCombobox.getValue()).getMaxDepth());
|
||||
computerDifficultySlider.setValue(playerInformation.computerDifficulty);
|
||||
computerDifficultySlider.valueProperty().addListener((_, _, newValue) -> {
|
||||
playerInformation.computerDifficulty = newValue.intValue();
|
||||
});
|
||||
|
||||
nodes.add(vbox(computerDifficultyText, computerDifficultySlider));
|
||||
}
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(hboxFill(
|
||||
vboxFill(
|
||||
challengeText,
|
||||
opponentHeader,
|
||||
gameText,
|
||||
gamesCombobox,
|
||||
separator(),
|
||||
|
||||
hboxFill(
|
||||
sendButton,
|
||||
cancelButton
|
||||
)
|
||||
),
|
||||
|
||||
vboxFill(
|
||||
nodes.toArray(new Node[0])
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
89
app/src/main/java/org/toop/app/view/views/ServerView.java
Normal file
89
app/src/main/java/org/toop/app/view/views/ServerView.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package org.toop.app.view.views;
|
||||
|
||||
import org.toop.app.view.View;
|
||||
import org.toop.app.view.ViewStack;
|
||||
import org.toop.app.view.displays.SongDisplay;
|
||||
import org.toop.local.AppContext;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.text.Text;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ServerView extends View {
|
||||
private final String user;
|
||||
private final Consumer<String> onPlayerClicked;
|
||||
private final Runnable onDisconnect;
|
||||
|
||||
private ListView<Button> listView;
|
||||
|
||||
public ServerView(String user, Consumer<String> onPlayerClicked, Runnable onDisconnect) {
|
||||
super(true, "bg-primary");
|
||||
|
||||
this.user = user;
|
||||
this.onPlayerClicked = onPlayerClicked;
|
||||
this.onDisconnect = onDisconnect;
|
||||
}
|
||||
|
||||
public void update(List<String> players) {
|
||||
Platform.runLater(() -> {
|
||||
listView.getItems().clear();
|
||||
|
||||
for (int i = 0; i < players.size(); i++) {
|
||||
final int finalI = i;
|
||||
|
||||
final Button button = button();
|
||||
button.setText(players.get(i));
|
||||
button.setOnAction(_ -> {
|
||||
onPlayerClicked.accept(players.get(finalI));
|
||||
});
|
||||
|
||||
listView.getItems().add(button);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup() {
|
||||
final Text playerHeader = header();
|
||||
playerHeader.setText(user);
|
||||
|
||||
listView = new ListView<Button>();
|
||||
|
||||
final SongDisplay songdisplay = new SongDisplay();
|
||||
|
||||
|
||||
add(Pos.BOTTOM_RIGHT,
|
||||
fit(vboxFill(
|
||||
songdisplay
|
||||
)));
|
||||
|
||||
add(Pos.CENTER,
|
||||
fit(vboxFill(
|
||||
vbox(
|
||||
playerHeader,
|
||||
separator()
|
||||
),
|
||||
|
||||
listView
|
||||
))
|
||||
);
|
||||
|
||||
final Button disconnectButton = button();
|
||||
disconnectButton.setText(AppContext.getString("disconnect"));
|
||||
disconnectButton.setOnAction(_ -> {
|
||||
onDisconnect.run();
|
||||
ViewStack.push(new OnlineView());
|
||||
});
|
||||
|
||||
add(Pos.BOTTOM_LEFT,
|
||||
vboxFill(
|
||||
disconnectButton
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.toop.app.widget.view;
|
||||
|
||||
import org.toop.app.GameInformation;
|
||||
import org.toop.app.game.Connect4GameThread;
|
||||
import org.toop.app.game.ReversiGameThread;
|
||||
import org.toop.app.game.Connect4Game;
|
||||
import org.toop.app.game.ReversiGame;
|
||||
import org.toop.app.game.TicTacToeGameThread;
|
||||
import org.toop.app.widget.Primitive;
|
||||
import org.toop.app.widget.complex.PlayerInfoWidget;
|
||||
@@ -33,8 +33,8 @@ public class LocalMultiplayerView extends ViewWidget {
|
||||
|
||||
switch (information.type) {
|
||||
case TICTACTOE -> new TicTacToeGameThread(information);
|
||||
case REVERSI -> new ReversiGameThread(information);
|
||||
case CONNECT4 -> new Connect4GameThread(information);
|
||||
case REVERSI -> new ReversiGame(information);
|
||||
case CONNECT4 -> new Connect4Game(information);
|
||||
// case BATTLESHIP -> new BattleshipGame(information);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package org.toop.local;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.framework.resource.ResourceManager;
|
||||
import org.toop.framework.resource.resources.LocalizationAsset;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
|
||||
Reference in New Issue
Block a user