From fb8acbe228ab73b5914a54740e6393ecc2726d26 Mon Sep 17 00:00:00 2001 From: ramollia <> Date: Sat, 25 Oct 2025 17:37:50 +0200 Subject: [PATCH] add simple flip animations and fixed(?) server somewhat --- app/src/main/java/org/toop/app/Server.java | 151 +++++++++--------- .../org/toop/app/canvas/Connect4Canvas.java | 6 +- .../java/org/toop/app/canvas/GameCanvas.java | 116 +++++++++----- .../org/toop/app/canvas/ReversiCanvas.java | 11 -- .../org/toop/app/canvas/TicTacToeCanvas.java | 25 +++ .../java/org/toop/app/game/Connect4Game.java | 27 ++-- .../java/org/toop/app/game/ReversiGame.java | 67 +++++--- .../java/org/toop/app/game/TicTacToeGame.java | 14 +- .../toop/app/layer/layers/ConnectedLayer.java | 0 9 files changed, 243 insertions(+), 174 deletions(-) delete mode 100644 app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java diff --git a/app/src/main/java/org/toop/app/Server.java b/app/src/main/java/org/toop/app/Server.java index 2437b37..91a89ef 100644 --- a/app/src/main/java/org/toop/app/Server.java +++ b/app/src/main/java/org/toop/app/Server.java @@ -1,6 +1,5 @@ package org.toop.app; -import com.google.common.util.concurrent.AbstractScheduledService; import org.toop.app.game.Connect4Game; import org.toop.app.game.ReversiGame; import org.toop.app.game.TicTacToeGame; @@ -13,12 +12,9 @@ import org.toop.app.view.views.ServerView; import org.toop.framework.eventbus.EventFlow; import org.toop.framework.networking.clients.TournamentNetworkingClient; import org.toop.framework.networking.events.NetworkEvents; -import org.toop.framework.networking.interfaces.NetworkingClient; import org.toop.framework.networking.types.NetworkingConnector; import org.toop.local.AppContext; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executors; @@ -27,25 +23,26 @@ import java.util.concurrent.TimeUnit; public final class Server { private String user = ""; - private long clientId = -1; - private List onlinePlayers = new CopyOnWriteArrayList(); - private List gameList = new CopyOnWriteArrayList<>(); + + private final List onlinePlayers = new CopyOnWriteArrayList<>(); + private final List gameList = new CopyOnWriteArrayList<>(); private ServerView view; - private boolean isPolling = true; + private ScheduledExecutorService scheduler; + public static GameInformation.Type gameToType(String game) { if (game.equalsIgnoreCase("tic-tac-toe")) { return GameInformation.Type.TICTACTOE; } else if (game.equalsIgnoreCase("reversi")) { return GameInformation.Type.REVERSI; } else if (game.equalsIgnoreCase("connect4")) { - return GameInformation.Type.CONNECT4; - } else if (game.equalsIgnoreCase("battleship")) { - return GameInformation.Type.BATTLESHIP; - } + return GameInformation.Type.CONNECT4; + } else if (game.equalsIgnoreCase("battleship")) { + return GameInformation.Type.BATTLESHIP; + } return null; } @@ -56,7 +53,7 @@ public final class Server { return; } - int parsedPort = -1; + int parsedPort; try { parsedPort = Integer.parseInt(port); @@ -72,11 +69,10 @@ public final class Server { new EventFlow() .addPostEvent(NetworkEvents.StartClient.class, - new TournamentNetworkingClient(), - new NetworkingConnector(ip, parsedPort, 10, 1, TimeUnit.SECONDS) + new TournamentNetworkingClient(), + new NetworkingConnector(ip, parsedPort, 10, 1, TimeUnit.SECONDS) ) .onResponse(NetworkEvents.StartClientResponse.class, e -> { - // TODO add if unsuccessful this.user = user; clientId = e.clientId(); @@ -86,29 +82,15 @@ public final class Server { ViewStack.push(view); startPopulateScheduler(); - - populateGameList(); + populateGameList(); }).postEvent(); new EventFlow().listen(this::handleReceivedChallenge); } - private void populatePlayerList(ScheduledExecutorService scheduler, Runnable populatingTask) { - - final long DELAY = 5; - - if (!isPolling) scheduler.shutdown(); - else { - populatingTask.run(); - scheduler.schedule(() -> populatePlayerList(scheduler, populatingTask), DELAY, TimeUnit.SECONDS); - } - } - private void sendChallenge(String opponent) { - if (!isPolling) { - return; - } + if (!isPolling) return; ViewStack.push(new SendChallengeView(this, opponent, (playerInformation, gameType) -> { new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(clientId, opponent, gameType)) @@ -118,7 +100,12 @@ public final class Server { onlinePlayers.clear(); final GameInformation.Type type = gameToType(gameType); - final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent())? 1 : 0; + if (type == null) { + ViewStack.push(new ErrorView("Unsupported game type: " + gameType)); + return; + } + + final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent()) ? 1 : 0; final GameInformation information = new GameInformation(type); information.players[0] = playerInformation; @@ -126,9 +113,10 @@ public final class Server { information.players[1].name = opponent; switch (type) { - case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break; - case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break; - case CONNECT4: new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break; + case TICTACTOE -> new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); + case REVERSI -> new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); + case CONNECT4 -> new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); + default -> ViewStack.push(new ErrorView("Unsupported game type.")); } } }).postEvent(); @@ -136,22 +124,14 @@ public final class Server { } private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) { - if (!isPolling) { - return; - } - - String challengerName = response.challengerName(); - challengerName = challengerName.substring(challengerName.indexOf("\"") + 1); - challengerName = challengerName.substring(0, challengerName.indexOf("\"")); - - String gameType = response.gameType(); - gameType = gameType.substring(gameType.indexOf("\"") + 1); - gameType = gameType.substring(0, gameType.indexOf("\"")); + if (!isPolling) return; + String challengerName = extractQuotedValue(response.challengerName()); + String gameType = extractQuotedValue(response.gameType()); final String finalGameType = gameType; ViewStack.push(new ChallengeView(challengerName, gameType, (playerInformation) -> { - final int challengeId = Integer.parseInt(response.challengeId().substring(18, response.challengeId().length() - 2)); + final int challengeId = Integer.parseInt(response.challengeId().replaceAll("\\D", "")); new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(clientId, challengeId)).postEvent(); ViewStack.pop(); @@ -162,7 +142,12 @@ public final class Server { onlinePlayers.clear(); final GameInformation.Type type = gameToType(finalGameType); - final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent())? 1 : 0; + if (type == null) { + ViewStack.push(new ErrorView("Unsupported game type: " + finalGameType)); + return; + } + + final int myTurn = e.playerToMove().equalsIgnoreCase(e.opponent()) ? 1 : 0; final GameInformation information = new GameInformation(type); information.players[0] = playerInformation; @@ -170,9 +155,10 @@ public final class Server { information.players[1].name = e.opponent(); switch (type) { - case TICTACTOE: new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break; - case REVERSI: new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break; - case CONNECT4: new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); break; + case TICTACTOE -> new TicTacToeGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); + case REVERSI -> new ReversiGame(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); + case CONNECT4 -> new Connect4Game(information, myTurn, this::forfeitGame, this::exitGame, this::sendMessage); + default -> ViewStack.push(new ErrorView("Unsupported game type.")); } } }); @@ -186,6 +172,7 @@ public final class Server { private void disconnect() { new EventFlow().addPostEvent(new NetworkEvents.CloseClient(clientId)).postEvent(); isPolling = false; + stopScheduler(); ViewStack.push(new OnlineView()); } @@ -195,41 +182,61 @@ public final class Server { private void exitGame() { forfeitGame(); - ViewStack.push(view); startPopulateScheduler(); } private void startPopulateScheduler() { isPolling = true; + stopScheduler(); - EventFlow getPlayerlistFlow = new EventFlow() - .addPostEvent(new NetworkEvents.SendGetPlayerlist(clientId)) + new EventFlow() .listen(NetworkEvents.PlayerlistResponse.class, e -> { - if (e.clientId() == clientId) { - onlinePlayers = new ArrayList<>(List.of(e.playerlist())); - onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user)); + if (e.clientId() == clientId) { + onlinePlayers.clear(); + onlinePlayers.addAll(List.of(e.playerlist())); + onlinePlayers.removeIf(name -> name.equalsIgnoreCase(user)); + view.update(onlinePlayers); + } + }, false); - view.update(onlinePlayers); + scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.scheduleAtFixedRate(() -> { + if (isPolling) { + new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(clientId)).postEvent(); + } else { + stopScheduler(); } - }, false); - - final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - scheduler.schedule(() -> populatePlayerList(scheduler, getPlayerlistFlow::postEvent), 0, TimeUnit.MILLISECONDS); + }, 0, 5, TimeUnit.SECONDS); } - private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) { - gameList.addAll(List.of(event.gamelist())); - } + private void stopScheduler() { + if (scheduler != null && !scheduler.isShutdown()) { + scheduler.shutdownNow(); + } + } + + private void gamesListFromServerHandler(NetworkEvents.GamelistResponse event) { + gameList.clear(); + gameList.addAll(List.of(event.gamelist())); + } public void populateGameList() { new EventFlow().addPostEvent(new NetworkEvents.SendGetGamelist(clientId)) - .listen(NetworkEvents.GamelistResponse.class, - this::gamesListFromServerHandler, true - ).postEvent(); + .listen(NetworkEvents.GamelistResponse.class, this::gamesListFromServerHandler, true) + .postEvent(); } - public List getGameList() { - return gameList; - } + public List getGameList() { + return gameList; + } + + private String extractQuotedValue(String s) { + int first = s.indexOf('"'); + int last = s.lastIndexOf('"'); + if (first >= 0 && last > first) { + return s.substring(first + 1, last); + } + return s; + } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java b/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java index 7842832..a641c6a 100644 --- a/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java +++ b/app/src/main/java/org/toop/app/canvas/Connect4Canvas.java @@ -6,8 +6,6 @@ import java.util.function.Consumer; public class Connect4Canvas extends GameCanvas { public Connect4Canvas(Color color, int width, int height, Consumer onCellClicked) { - super(color, width, height, 6, 7, 10, true, onCellClicked); + super(color, width, height, 7, 6, 10, true, onCellClicked); } - - -} +} \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/canvas/GameCanvas.java b/app/src/main/java/org/toop/app/canvas/GameCanvas.java index 3854f82..42526a3 100644 --- a/app/src/main/java/org/toop/app/canvas/GameCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/GameCanvas.java @@ -1,11 +1,13 @@ package org.toop.app.canvas; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.input.MouseButton; import javafx.scene.paint.Color; +import javafx.util.Duration; -import java.util.Arrays; import java.util.function.Consumer; public abstract class GameCanvas { @@ -49,54 +51,46 @@ public abstract class GameCanvas { cells = new Cell[rowSize * columnSize]; - final float cellWidth = ((float)width - gapSize * columnSize - gapSize) / columnSize; - final float cellHeight = ((float)height - gapSize * rowSize - gapSize) / rowSize; + final float cellWidth = ((float) width - gapSize * rowSize - gapSize) / rowSize; + final float cellHeight = ((float) height - gapSize * columnSize - gapSize) / columnSize; - for (int y = 0; y < rowSize; y++) { + for (int y = 0; y < columnSize; y++) { final float startY = y * cellHeight + y * gapSize + gapSize; - for (int x = 0; x < columnSize; x++) { + for (int x = 0; x < rowSize; x++) { final float startX = x * cellWidth + x * gapSize + gapSize; - cells[x + y * columnSize] = new Cell(startX, startY, cellWidth, cellHeight); + cells[x + y * rowSize] = new Cell(startX, startY, cellWidth, cellHeight); } } + canvas.setOnMouseClicked(event -> { if (event.getButton() != MouseButton.PRIMARY) { return; } - final int column = (int)((event.getX() / this.width) * columnSize); - final int row = (int)((event.getY() / this.height) * rowSize); + final int column = (int) ((event.getX() / this.width) * rowSize); + final int row = (int) ((event.getY() / this.height) * columnSize); - final Cell cell = cells[column + row * columnSize]; + final Cell cell = cells[column + row * rowSize]; if (cell.isInside(event.getX(), event.getY())) { event.consume(); - onCellClicked.accept(column + row * columnSize); + onCellClicked.accept(column + row * rowSize); } }); render(); } - public void clear() { - graphics.clearRect(0, 0, width, height); - } - - public void clearCell(int cellIndex) { - Cell cell = cells[cellIndex]; - graphics.clearRect(cell.x, cell.y, cell.width, cell.height); - } - - public void render() { + private void render() { graphics.setFill(color); - for (int x = 0; x < columnSize - 1; x++) { + for (int x = 0; x < rowSize - 1; x++) { final float start = cells[x].x + cells[x].width; graphics.fillRect(start, gapSize, gapSize, height - gapSize * 2); } - for (int y = 0; y < rowSize; y++) { + for (int y = 0; y < columnSize - 1; y++) { final float start = cells[y * rowSize].y + cells[y * rowSize].height; graphics.fillRect(gapSize, start, width - gapSize * 2, gapSize); } @@ -121,32 +115,72 @@ public abstract class GameCanvas { graphics.fillRect(x, y, width, height); } - public void drawX(Color color, int cell) { - graphics.setStroke(color); - graphics.setLineWidth(gapSize); + public void clear(int cell) { + final float x = cells[cell].x(); + final float y = cells[cell].y(); - final float x = cells[cell].x() + gapSize; - final float y = cells[cell].y() + gapSize; + final float width = cells[cell].width(); + final float height = cells[cell].height(); - final float width = cells[cell].width() - gapSize * 2; - final float height = cells[cell].height() - gapSize * 2; + graphics.clearRect(x, y, width, height); + } - graphics.strokeLine(x, y, x + width, y + height); - graphics.strokeLine(x + width, y, x, y + height); - } + public void clearAll() { + for (int i = 0; i < cells.length; i++) { + clear(i); + } + } - public void drawO(Color color, int cell) { - graphics.setStroke(color); - graphics.setLineWidth(gapSize); + public void drawDot(Color color, int cell) { + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; - final float x = cells[cell].x() + gapSize; - final float y = cells[cell].y() + gapSize; + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; - final float width = cells[cell].width() - gapSize * 2; - final float height = cells[cell].height() - gapSize * 2; + graphics.setFill(color); + graphics.fillOval(x, y, width, height); + } - graphics.strokeOval(x, y, width, height); - } + private void drawDotScaled(Color color, int cell, double scale) { + final float cx = cells[cell].x() + gapSize; + final float cy = cells[cell].y() + gapSize; + + final float fullWidth = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + final float scaledWidth = (float)(fullWidth * scale); + final float offsetX = (fullWidth - scaledWidth) / 2; + + graphics.setFill(color); + graphics.fillOval(cx + offsetX, cy, scaledWidth, height); + } + + public Timeline flipDot(Color fromColor, Color toColor, int cell) { + final int steps = 60; + final long duration = 250; + final double interval = duration / (double) steps; + + final Timeline timeline = new Timeline(); + + for (int i = 0; i <= steps; i++) { + final double t = i / (double) steps; + final KeyFrame keyFrame = new KeyFrame(Duration.millis(i * interval), + _ -> { + clear(cell); + + final double scale = t <= 0.5 ? 1 - 2 * t : 2 * t - 1; + final Color currentColor = t < 0.5 ? fromColor : toColor; + + drawDotScaled(currentColor, cell, scale); + } + ); + + timeline.getKeyFrames().add(keyFrame); + } + + return timeline; + } public Canvas getCanvas() { return canvas; diff --git a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java index 277937c..395dc4f 100644 --- a/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/ReversiCanvas.java @@ -10,17 +10,6 @@ public final class ReversiCanvas extends GameCanvas { drawStartingDots(); } - public void drawDot(Color color, int cell) { - final float x = cells[cell].x() + gapSize; - final float y = cells[cell].y() + gapSize; - - final float width = cells[cell].width() - gapSize * 2; - final float height = cells[cell].height() - gapSize * 2; - - graphics.setFill(color); - graphics.fillOval(x, y, width, height); - } - public void drawStartingDots() { drawDot(Color.BLACK, 28); drawDot(Color.WHITE, 36); diff --git a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java index 451fd3e..06d0b31 100644 --- a/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java +++ b/app/src/main/java/org/toop/app/canvas/TicTacToeCanvas.java @@ -9,5 +9,30 @@ public final class TicTacToeCanvas extends GameCanvas { super(color, width, height, 3, 3, 30, false, onCellClicked); } + public void drawX(Color color, int cell) { + graphics.setStroke(color); + graphics.setLineWidth(gapSize); + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.strokeLine(x, y, x + width, y + height); + graphics.strokeLine(x + width, y, x, y + height); + } + + public void drawO(Color color, int cell) { + graphics.setStroke(color); + graphics.setLineWidth(gapSize); + + final float x = cells[cell].x() + gapSize; + final float y = cells[cell].y() + gapSize; + + final float width = cells[cell].width() - gapSize * 2; + final float height = cells[cell].height() - gapSize * 2; + + graphics.strokeOval(x, y, width, height); + } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/Connect4Game.java b/app/src/main/java/org/toop/app/game/Connect4Game.java index d31c91f..77bda9c 100644 --- a/app/src/main/java/org/toop/app/game/Connect4Game.java +++ b/app/src/main/java/org/toop/app/game/Connect4Game.java @@ -33,7 +33,7 @@ public class Connect4Game { private final GameView view; private final Connect4Canvas canvas; - private AtomicBoolean isRunning; + private final AtomicBoolean isRunning; public Connect4Game(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage) { this.information = information; @@ -101,14 +101,8 @@ public class Connect4Game { } private void localGameThread() { while (isRunning.get()) { - final int currentTurn = game.getCurrentTurn(); - final char currentValue = currentTurn == 0? 'X' : 'O'; - final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type); - - view.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - String.valueOf(currentValue), - information.players[nextTurn].name); + final int currentTurn = game.getCurrentTurn(); + setGameLabels(information.players[currentTurn].isHuman); Game.Move move = null; @@ -194,9 +188,9 @@ public class Connect4Game { } if (move.value() == 'X') { - canvas.drawX(Color.INDIANRED, move.position()); + canvas.drawDot(Color.INDIANRED, move.position()); } else if (move.value() == 'O') { - canvas.drawO(Color.ROYALBLUE, move.position()); + canvas.drawDot(Color.ROYALBLUE, move.position()); } updateCanvas(); @@ -239,25 +233,24 @@ public class Connect4Game { } private void updateCanvas() { - canvas.clear(); - canvas.render(); + canvas.clearAll(); for (int i = 0; i < game.board.length; i++) { if (game.board[i] == 'X') { - canvas.drawX(Color.RED, i); + canvas.drawDot(Color.RED, i); } else if (game.board[i] == 'O') { - canvas.drawO(Color.BLUE, i); + canvas.drawDot(Color.BLUE, i); } } } private void setGameLabels(boolean isMe) { final int currentTurn = game.getCurrentTurn(); - final char currentValue = currentTurn == 0? 'X' : 'O'; + final String currentValue = currentTurn == 0? "RED" : "BLUE"; view.nextPlayer(isMe, information.players[isMe? 0 : 1].name, - String.valueOf(currentValue), + currentValue, information.players[isMe? 1 : 0].name); } } diff --git a/app/src/main/java/org/toop/app/game/ReversiGame.java b/app/src/main/java/org/toop/app/game/ReversiGame.java index 18527fe..f1b8045 100644 --- a/app/src/main/java/org/toop/app/game/ReversiGame.java +++ b/app/src/main/java/org/toop/app/game/ReversiGame.java @@ -1,5 +1,8 @@ package org.toop.app.game; +import javafx.animation.SequentialTransition; +import javafx.animation.Timeline; +import javafx.util.Duration; import org.toop.app.App; import org.toop.app.GameInformation; import org.toop.app.canvas.ReversiCanvas; @@ -33,6 +36,7 @@ public final class ReversiGame { private final ReversiCanvas canvas; private final AtomicBoolean isRunning; + private final AtomicBoolean isPaused; public ReversiGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage) { this.information = information; @@ -44,6 +48,7 @@ public final class ReversiGame { ai = new ReversiAI(); isRunning = new AtomicBoolean(true); + isPaused = new AtomicBoolean(false); if (onForfeit == null || onExit == null) { view = new GameView(null, () -> { @@ -93,7 +98,7 @@ public final class ReversiGame { setGameLabels(myTurn == 0); } - updateCanvas(); + updateCanvas(false); } public ReversiGame(GameInformation information) { @@ -102,14 +107,16 @@ public final class ReversiGame { private void localGameThread() { while (isRunning.get()) { - final int currentTurn = game.getCurrentTurn(); - final char currentValue = currentTurn == 0? 'B' : 'W'; - final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type); + if (isPaused.get()) { + try { + Thread.sleep(200); + } catch (InterruptedException _) {} - view.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - String.valueOf(currentValue), - information.players[nextTurn].name); + continue; + } + + final int currentTurn = game.getCurrentTurn(); + setGameLabels(information.players[currentTurn].isHuman); Game.Move move = null; @@ -146,7 +153,7 @@ public final class ReversiGame { } final Game.State state = game.play(move); - updateCanvas(); + updateCanvas(true); if (state != Game.State.NORMAL) { if (state == Game.State.WIN) { @@ -188,7 +195,7 @@ public final class ReversiGame { } } - updateCanvas(); + updateCanvas(false); setGameLabels(game.getCurrentTurn() == myTurn); } @@ -224,11 +231,9 @@ public final class ReversiGame { view.updateChat(msg.message()); } - private void updateCanvas() { + 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.clear(); - canvas.render(); + canvas.clearAll(); for (int i = 0; i < game.board.length; i++) { if (game.board[i] == 'B') { @@ -238,20 +243,44 @@ public final class ReversiGame { } } - final Game.Move[] legalMoves = game.getLegalMoves(); + final Game.Move[] flipped = game.getMostRecentlyFlippedPieces(); - for (final Game.Move legalMove : legalMoves) { - canvas.drawLegalPosition(legalMove.position()); + final SequentialTransition animation = new SequentialTransition(); + isPaused.set(true); + + if (animate && flipped != null) { + for (final Game.Move flip : flipped) { + canvas.clear(flip.position()); + + final Color from = flip.value() == 'W' ? Color.BLACK : Color.WHITE; + final Color to = flip.value() == 'W' ? Color.WHITE : Color.BLACK; + + canvas.drawDot(from, flip.position()); + + animation.getChildren().addFirst(canvas.flipDot(from, to, flip.position())); + } } + + animation.setOnFinished(_ -> { + isPaused.set(false); + + final Game.Move[] legalMoves = game.getLegalMoves(); + + for (final Game.Move legalMove : legalMoves) { + canvas.drawLegalPosition(legalMove.position()); + } + }); + + animation.play(); } private void setGameLabels(boolean isMe) { final int currentTurn = game.getCurrentTurn(); - final char currentValue = currentTurn == 0? 'B' : 'W'; + final String currentValue = currentTurn == 0? "BLACK" : "WHITE"; view.nextPlayer(isMe, information.players[isMe? 0 : 1].name, - String.valueOf(currentValue), + currentValue, information.players[isMe? 1 : 0].name); } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/game/TicTacToeGame.java b/app/src/main/java/org/toop/app/game/TicTacToeGame.java index cddef0a..6448835 100644 --- a/app/src/main/java/org/toop/app/game/TicTacToeGame.java +++ b/app/src/main/java/org/toop/app/game/TicTacToeGame.java @@ -32,7 +32,7 @@ public final class TicTacToeGame { private final GameView view; private final TicTacToeCanvas canvas; - private AtomicBoolean isRunning; + private final AtomicBoolean isRunning; public TicTacToeGame(GameInformation information, int myTurn, Runnable onForfeit, Runnable onExit, Consumer onMessage) { this.information = information; @@ -101,13 +101,7 @@ public final class TicTacToeGame { private void localGameThread() { while (isRunning.get()) { final int currentTurn = game.getCurrentTurn(); - final char currentValue = currentTurn == 0? 'X' : 'O'; - final int nextTurn = (currentTurn + 1) % GameInformation.Type.playerCount(information.type); - - view.nextPlayer(information.players[currentTurn].isHuman, - information.players[currentTurn].name, - String.valueOf(currentValue), - information.players[nextTurn].name); + setGameLabels(information.players[currentTurn].isHuman); Game.Move move = null; @@ -234,11 +228,11 @@ public final class TicTacToeGame { private void setGameLabels(boolean isMe) { final int currentTurn = game.getCurrentTurn(); - final char currentValue = currentTurn == 0? 'X' : 'O'; + final String currentValue = currentTurn == 0? "X" : "O"; view.nextPlayer(isMe, information.players[isMe? 0 : 1].name, - String.valueOf(currentValue), + currentValue, information.players[isMe? 1 : 0].name); } } \ No newline at end of file diff --git a/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java b/app/src/main/java/org/toop/app/layer/layers/ConnectedLayer.java deleted file mode 100644 index e69de29..0000000