From 8af621d837a21e042ab07c2579547750b6a170ea Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 05:29:37 +0000 Subject: [PATCH 1/7] Added UiConstants and clean selection highlight reset flow Added shared UiConstants for tile/card render modes Refactored OtherClicked to clear highlights using Board tile access Refactored Heartbeat to use shared UI constants and safe cleanup Finalised selection/highlight integration contract for teammates. Small comment fix in board class. Can be ignored --- .../app/events/Heartbeat.java | 14 +++++++++----- .../app/events/OtherClicked.java | 17 ++++++++--------- .../app/events/UnitClicked.java | 12 ++++++++++++ .../app/game/Board.java | 2 +- .../app/game/ui/UiConstants.java | 19 +++++++++++++++++++ .../app/structures/GameState.java | 5 +++++ 6 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/UnitClicked.java create mode 100644 ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java index 2fe23ba..b0a43c4 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java @@ -9,6 +9,7 @@ import structures.basic.Card; import structures.basic.Tile; import utils.BasicObjectBuilders; +import game.ui.UiConstants; /** * In the user’s browser, the game is running in an infinite loop, where there is around a 1 second delay @@ -24,8 +25,6 @@ */ public class Heartbeat implements EventProcessor{ - private static final int TILE_MODE_NORMAL = 0; - private static final int CARD_MODE_NORMAL = 0; @Override public void processEvent(ActorRef out, GameState gameState, JsonNode message) { if (gameState == null || !gameState.gameInitalised) return; @@ -33,15 +32,20 @@ public void processEvent(ActorRef out, GameState gameState, JsonNode message) { //clear highlighted tiles Set tilesToClear = gameState.consumeHighlightedTiles(); for (GameState.Coord c : tilesToClear) { - Tile t = BasicObjectBuilders.loadTile(c.x, c.y); - BasicCommands.drawTile(out, t, TILE_MODE_NORMAL); + Tile t = gameState.board.getTile(c.x, c.y); + if (t != null) { + BasicCommands.drawTile(out, t, UiConstants.TILE_NORMAL); + } } + //clear highlighted cards Set handToClear = gameState.consumeHighlightedHandPositions(); for (Integer pos : handToClear) { if (pos == null) continue; Card card = gameState.getP1HandAtPos(pos); - if (card != null) BasicCommands.drawCard(out, card, pos, CARD_MODE_NORMAL); + if (card != null) { + BasicCommands.drawCard(out, card, pos, UiConstants.CARD_NORMAL); + } } } diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java index 163f400..9c874b6 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java @@ -10,6 +10,7 @@ import structures.basic.Card; import structures.basic.Tile; import utils.BasicObjectBuilders; +import game.ui.UiConstants; /** * Indicates that the user has clicked an object on the game canvas, in this case @@ -24,19 +25,17 @@ */ public class OtherClicked implements EventProcessor{ - //mode 0 is the "normal" render mode for tiles and cards in the template. - private static final int TILE_MODE_NORMAL = 0; - private static final int CARD_MODE_NORMAL = 0; - - @Override - public void processEvent(ActorRef out, GameState gameState, JsonNode message) { + @Override + public void processEvent(ActorRef out, GameState gameState, JsonNode message) { if (gameState == null || !gameState.gameInitalised) return; //clear any highlighted tiles by drawing them again in normal mode Set tilesToClear = gameState.consumeHighlightedTiles(); for (GameState.Coord c : tilesToClear) { - Tile t = BasicObjectBuilders.loadTile(c.x, c.y); - BasicCommands.drawTile(out, t, TILE_MODE_NORMAL); + Tile t = gameState.board.getTile(c.x, c.y); + if (t != null) { + BasicCommands.drawTile(out, t, UiConstants.TILE_NORMAL); + } } //clear any highlighted hand cards for player 1 by drawing them again in normal mode Set handPositionsToClear = gameState.consumeHighlightedHandPositions(); @@ -44,7 +43,7 @@ public void processEvent(ActorRef out, GameState gameState, JsonNode message) { if (pos == null) continue; Card card = gameState.getP1HandAtPos(pos); if (card != null) { - BasicCommands.drawCard(out, card, pos, CARD_MODE_NORMAL); + BasicCommands.drawCard(out, card, pos, UiConstants.CARD_NORMAL); } } //clear selection state diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/UnitClicked.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/UnitClicked.java new file mode 100644 index 0000000..2e82ac7 --- /dev/null +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/UnitClicked.java @@ -0,0 +1,12 @@ +package events; + +//Core behaviour of this class is as follows: +//If nothing selected: +// select this unit +//If another unit selected: +// switch selection +//If same unit selected: +// cancel selection + +public class UnitClicked { +} diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/Board.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/Board.java index 799579f..9040cb4 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/Board.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/Board.java @@ -8,7 +8,7 @@ public class Board { public static final int WIDTH = 9; public static final int HEIGHT = 5; - //I used 1 based coordinates here which means x = 1 to 9 and y = 1 to 5 + //I used 0 based coordinates here which means x = 0 to 8 and y = 0 to 4 private final int minX; private final int minY; diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java new file mode 100644 index 0000000..759146e --- /dev/null +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java @@ -0,0 +1,19 @@ +package game.ui; + +/** + * Shared UI render modes. + * Use these instead of hardcoded numbers in event/logic files. + */ +public final class UiConstants { + //tile highlight modes + public static final int TILE_NORMAL = 0; + public static final int TILE_MOVE = 1; + public static final int TILE_ATTACK = 2; + //card render modes + public static final int CARD_NORMAL = 0; + public static final int CARD_SELECTED = 1; + + private UiConstants() { + //prevent creating instantiations + } +} diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java index 24f57d2..e9328fa 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.Set; import game.Board; +import structures.basic.Tile; import structures.basic.Card; /** @@ -14,6 +15,10 @@ * */ public class GameState { + //small helper to get tiles + public Tile getTile(int x, int y) { + return board.getTile(x, y); + } public boolean gameInitalised = false; public boolean something = false; From 37786e95b3190f959bd42e0a431e50d5e8e1752f Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 06:04:21 +0000 Subject: [PATCH 2/7] Added SelectionManager for selection switching --- .../app/game/selection/SelectionManager.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java new file mode 100644 index 0000000..18b77f9 --- /dev/null +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java @@ -0,0 +1,73 @@ +package game.selection; + +import java.util.Set; + +import akka.actor.ActorRef; +import commands.BasicCommands; +import game.ui.UiConstants; +import structures.GameState; +import structures.basic.Card; +import structures.basic.Tile; + +/** + * This class serves as a central helper for safe selection switching and highlight cleanup. + * It keeps the selection logic out of event files as much as possible. + */ +public final class SelectionManager { + + private SelectionManager() {} + + public static void clearSelectionAndHighlights(ActorRef out, GameState gameState) { + if (gameState == null) return; + + Set tilesToClear = gameState.consumeHighlightedTiles(); + for (GameState.Coord c : tilesToClear) { + Tile tile = gameState.board.getTile(c.x, c.y); + if (tile != null) { + BasicCommands.drawTile(out, tile, UiConstants.TILE_NORMAL); + } + } + + Set handToClear = gameState.consumeHighlightedHandPositions(); + for (Integer pos : handToClear) { + if (pos == null) continue; + + Card card = gameState.getP1HandAtPos(pos); + if (card != null) { + BasicCommands.drawCard(out, card, pos, UiConstants.CARD_NORMAL); + } + } + + gameState.clearSelection(); + } + + public static void selectCard(ActorRef out, GameState gameState, int handPos) { + if (gameState == null) return; + + clearSelectionAndHighlights(out, gameState); + + Card card = gameState.getP1HandAtPos(handPos); + if (card == null) return; + + gameState.selectHandCardForTargeting(handPos); + gameState.addHighlightedHandPos(handPos); + BasicCommands.drawCard(out, card, handPos, UiConstants.CARD_SELECTED); + } + + public static void selectUnit(ActorRef out, GameState gameState, int unitId) { + if (gameState == null) return; + + clearSelectionAndHighlights(out, gameState); + gameState.selectUnit(unitId); + } + + public static boolean isCardSelected(GameState gameState) { + return gameState != null + && gameState.getSelectionMode() == GameState.SelectionState.Mode.CARD_SELECTED_TARGETING; + } + + public static boolean isUnitSelected(GameState gameState) { + return gameState != null + && gameState.getSelectionMode() == GameState.SelectionState.Mode.UNIT_SELECTED; + } +} \ No newline at end of file From 32556a2970959317792a6f48694edf5122f22aba Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 06:34:59 +0000 Subject: [PATCH 3/7] Created RangeFinder for adjacent tile and basic legality queries Added shared helper for adjacent tile calculation Supports future move and attack highlight generation Keeps legality/range logic separate from overlay rendering Provides reusable base for D3 and later D4/H6 --- .../app/game/selection/RangeFinder.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java new file mode 100644 index 0000000..fa11980 --- /dev/null +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java @@ -0,0 +1,42 @@ +package game.selection; +import java.util.ArrayList; +import java.util.List; +import structures.GameState; + +public final class RangeFinder { + private RangeFinder() {} + public static List getAdjacentTiles(GameState gameState, int x, int y) { + List result = new ArrayList<>(); + if (gameState == null) return result; + addIfInBounds(gameState, result, x, y - 1); // up + addIfInBounds(gameState, result, x, y + 1); // down + addIfInBounds(gameState, result, x - 1, y); // left + addIfInBounds(gameState, result, x + 1, y); // right + return result; + } + public static List getAdjacentFreeTiles(GameState gameState, int x, int y) { + List result = new ArrayList<>(); + if (gameState == null) return result; + for (GameState.Coord c : getAdjacentTiles(gameState, x, y)) { + if (!gameState.board.isOccupied(c.x, c.y)) { + result.add(c); + } + } + return result; + } + public static List getAdjacentOccupiedTiles(GameState gameState, int x, int y) { + List result = new ArrayList<>(); + if (gameState == null) return result; + for (GameState.Coord c : getAdjacentTiles(gameState, x, y)) { + if (gameState.board.isOccupied(c.x, c.y)) { + result.add(c); + } + } + return result; + } + private static void addIfInBounds(GameState gameState, List result, int x, int y) { + if (gameState.board.inBounds(x, y)) { + result.add(new GameState.Coord(x, y)); + } + } +} \ No newline at end of file From c9d0b3cc9bc1badbbf362fe80695762ae90ddf7c Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 07:39:38 +0000 Subject: [PATCH 4/7] SC-A3/D3/E2: centralize selection state and range highlighting Adds the shared selection foundation. Extends GameState with selection and board lookup helpers, upgrades RangeFinder for legal move, adjacent attack, and move-then-attack reach, hardens SelectionGuards and SelectionManager against stale or invalid state, and simplifies cleanup in OtherClicked and Heartbeat. --- .../app/events/Heartbeat.java | 24 +-- .../app/events/OtherClicked.java | 31 +--- .../app/game/selection/RangeFinder.java | 144 ++++++++++++++++-- .../app/game/selection/SelectionGuards.java | 135 ++++++++++++++++ .../app/game/selection/TargetingOverlay.java | 80 ++++++++++ .../app/game/ui/UiConstants.java | 4 +- .../app/structures/GameState.java | 42 ++++- 7 files changed, 390 insertions(+), 70 deletions(-) create mode 100644 ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionGuards.java create mode 100644 ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java index b0a43c4..bb4312a 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/Heartbeat.java @@ -1,15 +1,9 @@ package events; -import java.util.Set; import com.fasterxml.jackson.databind.JsonNode; import akka.actor.ActorRef; import structures.GameState; -import commands.BasicCommands; -import structures.basic.Card; -import structures.basic.Tile; -import utils.BasicObjectBuilders; -import game.ui.UiConstants; /** * In the user’s browser, the game is running in an infinite loop, where there is around a 1 second delay @@ -29,24 +23,8 @@ public class Heartbeat implements EventProcessor{ public void processEvent(ActorRef out, GameState gameState, JsonNode message) { if (gameState == null || !gameState.gameInitalised) return; if (gameState.getSelectionMode() != GameState.SelectionState.Mode.NONE) return; - //clear highlighted tiles - Set tilesToClear = gameState.consumeHighlightedTiles(); - for (GameState.Coord c : tilesToClear) { - Tile t = gameState.board.getTile(c.x, c.y); - if (t != null) { - BasicCommands.drawTile(out, t, UiConstants.TILE_NORMAL); - } - } - //clear highlighted cards - Set handToClear = gameState.consumeHighlightedHandPositions(); - for (Integer pos : handToClear) { - if (pos == null) continue; - Card card = gameState.getP1HandAtPos(pos); - if (card != null) { - BasicCommands.drawCard(out, card, pos, UiConstants.CARD_NORMAL); - } - } + game.selection.SelectionManager.clearSelectionAndHighlights(out, gameState); } } diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java index 9c874b6..4c1fe44 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/OtherClicked.java @@ -1,16 +1,9 @@ package events; -import java.util.Set; - import com.fasterxml.jackson.databind.JsonNode; import akka.actor.ActorRef; -import commands.BasicCommands; import structures.GameState; -import structures.basic.Card; -import structures.basic.Tile; -import utils.BasicObjectBuilders; -import game.ui.UiConstants; /** * Indicates that the user has clicked an object on the game canvas, in this case @@ -28,28 +21,8 @@ public class OtherClicked implements EventProcessor{ @Override public void processEvent(ActorRef out, GameState gameState, JsonNode message) { if (gameState == null || !gameState.gameInitalised) return; - - //clear any highlighted tiles by drawing them again in normal mode - Set tilesToClear = gameState.consumeHighlightedTiles(); - for (GameState.Coord c : tilesToClear) { - Tile t = gameState.board.getTile(c.x, c.y); - if (t != null) { - BasicCommands.drawTile(out, t, UiConstants.TILE_NORMAL); - } - } - //clear any highlighted hand cards for player 1 by drawing them again in normal mode - Set handPositionsToClear = gameState.consumeHighlightedHandPositions(); - for (Integer pos : handPositionsToClear) { - if (pos == null) continue; - Card card = gameState.getP1HandAtPos(pos); - if (card != null) { - BasicCommands.drawCard(out, card, pos, UiConstants.CARD_NORMAL); - } - } - //clear selection state - gameState.clearSelection(); - - } + game.selection.SelectionManager.clearSelectionAndHighlights(out, gameState); + } } diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java index fa11980..a0fcca4 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/RangeFinder.java @@ -1,23 +1,39 @@ package game.selection; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import structures.GameState; public final class RangeFinder { private RangeFinder() {} public static List getAdjacentTiles(GameState gameState, int x, int y) { + return getCardinalAdjacentTiles(gameState, x, y); + } + public static List getCardinalAdjacentTiles(GameState gameState, int x, int y) { + List result = new ArrayList<>(); + if (gameState == null || gameState.board == null) return result; + addIfInBounds(gameState, result, x, y - 1); + addIfInBounds(gameState, result, x, y + 1); + addIfInBounds(gameState, result, x - 1, y); + addIfInBounds(gameState, result, x + 1, y); + return result; + } + public static List getAdjacentTilesIncludingDiagonal(GameState gameState, int x, int y) { List result = new ArrayList<>(); - if (gameState == null) return result; - addIfInBounds(gameState, result, x, y - 1); // up - addIfInBounds(gameState, result, x, y + 1); // down - addIfInBounds(gameState, result, x - 1, y); // left - addIfInBounds(gameState, result, x + 1, y); // right + if (gameState == null || gameState.board == null) return result; + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx == 0 && dy == 0) continue; + addIfInBounds(gameState, result, x + dx, y + dy); + } + } return result; } public static List getAdjacentFreeTiles(GameState gameState, int x, int y) { List result = new ArrayList<>(); - if (gameState == null) return result; - for (GameState.Coord c : getAdjacentTiles(gameState, x, y)) { + if (gameState == null || gameState.board == null) return result; + for (GameState.Coord c : getCardinalAdjacentTiles(gameState, x, y)) { if (!gameState.board.isOccupied(c.x, c.y)) { result.add(c); } @@ -26,17 +42,127 @@ public static List getAdjacentFreeTiles(GameState gameState, in } public static List getAdjacentOccupiedTiles(GameState gameState, int x, int y) { List result = new ArrayList<>(); - if (gameState == null) return result; - for (GameState.Coord c : getAdjacentTiles(gameState, x, y)) { + if (gameState == null || gameState.board == null) return result; + for (GameState.Coord c : getCardinalAdjacentTiles(gameState, x, y)) { if (gameState.board.isOccupied(c.x, c.y)) { result.add(c); } } return result; } + public static List getAdjacentEnemyTiles(GameState gameState, int x, int y, int friendlyPlayerId) { + return filterEnemyTiles(gameState, getCardinalAdjacentTiles(gameState, x, y), friendlyPlayerId); + } + public static List getAdjacentAllyTiles(GameState gameState, int x, int y, int friendlyPlayerId) { + List result = new ArrayList<>(); + if (gameState == null || gameState.board == null) return result; + for (GameState.Coord c : getAdjacentOccupiedTiles(gameState, x, y)) { + Integer unitId = gameState.board.getUnitIdAt(c.x, c.y); + if (unitId == null) continue; + + Integer ownerPlayerId = gameState.getUnitOwnerPlayerId(unitId); + if (ownerPlayerId != null && ownerPlayerId.intValue() == friendlyPlayerId) { + result.add(c); + } + } + return result; + } + public static List getAdjacentFreeOrEnemyTiles(GameState gameState, int x, int y, int friendlyPlayerId) { + List result = new ArrayList<>(); + if (gameState == null || gameState.board == null) return result; + for (GameState.Coord c : getCardinalAdjacentTiles(gameState, x, y)) { + if (!gameState.board.isOccupied(c.x, c.y)) { + result.add(c); + continue; + } + Integer unitId = gameState.board.getUnitIdAt(c.x, c.y); + if (unitId == null) continue; + Integer ownerPlayerId = gameState.getUnitOwnerPlayerId(unitId); + if (ownerPlayerId != null && ownerPlayerId.intValue() != friendlyPlayerId) { + result.add(c); + } + } + return result; + } + /** + * Standard movement: + * - 1 or 2 tiles in cardinal directions + * - 1 tile diagonally + * - destination must be free + * - 2-step cardinal move cannot jump through an occupied middle tile + */ + public static List getStandardMoveTiles(GameState gameState, int x, int y) { + Set result = new LinkedHashSet<>(); + if (gameState == null || gameState.board == null) return new ArrayList<>(result); + addIfFree(gameState, result, x - 1, y); + addIfFree(gameState, result, x + 1, y); + addIfFree(gameState, result, x, y - 1); + addIfFree(gameState, result, x, y + 1); + addTwoStepCardinalIfFree(gameState, result, x, y, -1, 0); + addTwoStepCardinalIfFree(gameState, result, x, y, 1, 0); + addTwoStepCardinalIfFree(gameState, result, x, y, 0, -1); + addTwoStepCardinalIfFree(gameState, result, x, y, 0, 1); + addIfFree(gameState, result, x - 1, y - 1); + addIfFree(gameState, result, x - 1, y + 1); + addIfFree(gameState, result, x + 1, y - 1); + addIfFree(gameState, result, x + 1, y + 1); + + return new ArrayList<>(result); + } + public static List getStandardAttackTiles(GameState gameState, int x, int y, int friendlyPlayerId) { + return filterEnemyTiles(gameState, getAdjacentTilesIncludingDiagonal(gameState, x, y), friendlyPlayerId); + } + + public static List getStandardMoveThenAttackTiles(GameState gameState, int x, int y, int friendlyPlayerId) { + Set result = new LinkedHashSet<>(); + if (gameState == null || gameState.board == null) return new ArrayList<>(result); + result.addAll(getStandardAttackTiles(gameState, x, y, friendlyPlayerId)); + for (GameState.Coord moveTile : getStandardMoveTiles(gameState, x, y)) { + result.addAll(getStandardAttackTiles(gameState, moveTile.x, moveTile.y, friendlyPlayerId)); + } + return new ArrayList<>(result); + } + + public static boolean isOrthogonallyAdjacent(int x1, int y1, int x2, int y2) { + int dx = Math.abs(x1 - x2); + int dy = Math.abs(y1 - y2); + return dx + dy == 1; + } + private static List filterEnemyTiles(GameState gameState, Iterable coords, int friendlyPlayerId) { + List result = new ArrayList<>(); + if (gameState == null || gameState.board == null || coords == null) return result; + + for (GameState.Coord c : coords) { + if (c == null) continue; + + Integer unitId = gameState.board.getUnitIdAt(c.x, c.y); + if (unitId == null) continue; + + Integer ownerPlayerId = gameState.getUnitOwnerPlayerId(unitId); + if (ownerPlayerId != null && ownerPlayerId.intValue() != friendlyPlayerId) { + result.add(c); + } + } + return result; + } private static void addIfInBounds(GameState gameState, List result, int x, int y) { if (gameState.board.inBounds(x, y)) { result.add(new GameState.Coord(x, y)); } } + private static void addIfFree(GameState gameState, Set result, int x, int y) { + if (!gameState.board.inBounds(x, y)) return; + if (gameState.board.isOccupied(x, y)) return; + result.add(new GameState.Coord(x, y)); + } + private static void addTwoStepCardinalIfFree(GameState gameState, Set result, int x, int y, int dx, int dy) { + int midX = x + dx; + int midY = y + dy; + int targetX = x + (dx * 2); + int targetY = y + (dy * 2); + if (!gameState.board.inBounds(midX, midY) || !gameState.board.inBounds(targetX, targetY)) return; + if (gameState.board.isOccupied(midX, midY)) return; + if (gameState.board.isOccupied(targetX, targetY)) return; + result.add(new GameState.Coord(targetX, targetY)); + } } \ No newline at end of file diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionGuards.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionGuards.java new file mode 100644 index 0000000..fbfcc55 --- /dev/null +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionGuards.java @@ -0,0 +1,135 @@ +package game.selection; + +import structures.GameState; +import structures.basic.Card; + +/* + * Okay so this should check whether a selection-related action is safe before the game tries to use it. + * Check if a hand position is valid + * If the hand position is between 1 and 6, allow it. + * Otherwise reject it. + * Check if a card actually exists in that hand slot + * If the slot is valid and contains a card, return true. + * Otherwise return false. + * Check if a board tile is valid + * If the tile is inside the board boundaries, allow it. + * Otherwise reject it. + * Check if a tile is currently highlighted + * Look in the stored highlighted tiles. + * If the tile is there, return true. + * Check if a hand position is highlighted + * Look in the stored highlighted hand positions. + * If it is there, return true. + * Check if a unit is currently selected + * If selection mode is UNIT_SELECTED and there is a selected unit id, return true. + * Check if a card is currently selected + * If selection mode is CARD_SELECTED_TARGETING and the selected hand position still contains a card, return true. + * Check if there is no selection + * If selection mode is NONE, return true. + * Check if the current selection has become stale or invalid + * For example: + * selected card position is empty + * selected unit id is missing + * selection mode says something is selected but the actual object is gone + * If selection is stale, clear it + * Reset selection and highlights so the game does not get stuck or crash. + * Check if the clicked card is the same card already selected + * If yes, return true. + * Check if the clicked unit is the same unit already selected + * If yes, return true. + * Check if anything is highlighted at all + * If highlighted tiles or highlighted hand positions are not empty, return true. + */ + +/** + * Defensive guard helpers for selection and targeting flows. + * These methods are intentionally simple and side-effect free where possible. + * Purpose: + * - prevent invalid clicks/positions from corrupting state + * - provide one place for common safety checks + * - support SC-E2 basics (illegal actions never corrupt state / crash game) + */ +public final class SelectionGuards { + + private SelectionGuards() {} + + public static boolean isValidHandPosition(int handPos) { + return handPos >= 1 && handPos <= 6; + } + public static boolean hasCardInHand(GameState gameState, int handPos) { + if (gameState == null) return false; + if (!isValidHandPosition(handPos)) return false; + + Card card = gameState.getP1HandAtPos(handPos); + return card != null; + } + public static boolean isValidBoardTile(GameState gameState, int x, int y) { + if (gameState == null || gameState.board == null) return false; + return gameState.board.inBounds(x, y); + } + + public static boolean isHighlightedTile(GameState gameState, int x, int y) { + if (!isValidBoardTile(gameState, x, y)) return false; + return gameState.selection.highlightedTiles.contains(new GameState.Coord(x, y)); + } + public static boolean isHighlightedHandPosition(GameState gameState, int handPos) { + if (gameState == null) return false; + if (!isValidHandPosition(handPos)) return false; + return gameState.selection.highlightedHandPositions.contains(handPos); + } + + public static boolean hasSelectedUnit(GameState gameState) { + return gameState != null + && gameState.getSelectionMode() == GameState.SelectionState.Mode.UNIT_SELECTED + && gameState.getSelectedUnitId() != null; + } + public static boolean hasSelectedCard(GameState gameState) { + return gameState != null + && gameState.getSelectionMode() == GameState.SelectionState.Mode.CARD_SELECTED_TARGETING + && gameState.getSelectedHandPos() != null + && hasCardInHand(gameState, gameState.getSelectedHandPos()); + } + + public static boolean hasNoSelection(GameState gameState) { + return gameState == null + || gameState.getSelectionMode() == GameState.SelectionState.Mode.NONE; + } + + public static boolean hasStaleSelection(GameState gameState) { + if (gameState == null) return true; + GameState.SelectionState.Mode mode = gameState.getSelectionMode(); + if (mode == GameState.SelectionState.Mode.NONE) return false; + if (mode == GameState.SelectionState.Mode.UNIT_SELECTED) { + Integer selectedUnitId = gameState.getSelectedUnitId(); + if (selectedUnitId == null) return true; + return !gameState.isUnitOnBoard(selectedUnitId); + } + if (mode == GameState.SelectionState.Mode.CARD_SELECTED_TARGETING) { + Integer selectedHandPos = gameState.getSelectedHandPos(); + if (selectedHandPos == null) return true; + return !hasCardInHand(gameState, selectedHandPos); + } + return true; + } + public static void clearStaleSelectionIfNeeded(GameState gameState) { + if (gameState == null) return; + if (hasStaleSelection(gameState)) { + gameState.clearAllSelectionsAndHighlights(); + } + } + public static boolean isSameSelectedCard(GameState gameState, int handPos) { + return hasSelectedCard(gameState) + && gameState.getSelectedHandPos() != null + && gameState.getSelectedHandPos().intValue() == handPos; + } + public static boolean isSameSelectedUnit(GameState gameState, int unitId) { + return hasSelectedUnit(gameState) + && gameState.getSelectedUnitId() != null + && gameState.getSelectedUnitId().intValue() == unitId; + } + public static boolean hasAnyHighlights(GameState gameState) { + return gameState != null + && (!gameState.selection.highlightedTiles.isEmpty() + || !gameState.selection.highlightedHandPositions.isEmpty()); + } +} \ No newline at end of file diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java new file mode 100644 index 0000000..f264502 --- /dev/null +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java @@ -0,0 +1,80 @@ +package game.selection; + +import akka.actor.ActorRef; +import commands.BasicCommands; +import game.ui.UiConstants; +import structures.GameState; +import structures.basic.Tile; + +/** + * The purpose of this class is to handle drawing and registering tile highlights for movement, + * attacks, and targeting. It will only manage overlay rendering and state bookkeeping. + * Note: It does not decide game legality. + */ +public final class TargetingOverlay { + + private TargetingOverlay() {} + public static void clearAll(ActorRef out, GameState gameState) { + SelectionManager.clearSelectionAndHighlights(out, gameState); + } + public static void clearTileHighlights(ActorRef out, GameState gameState) { + if (gameState == null) return; + + for (GameState.Coord c : gameState.consumeHighlightedTiles()) { + Tile tile = gameState.board.getTile(c.x, c.y); + if (tile != null) { + BasicCommands.drawTile(out, tile, UiConstants.TILE_NORMAL); + } + } + } + public static void highlightMoveTile(ActorRef out, GameState gameState, int x, int y) { + highlightTile(out, gameState, x, y, UiConstants.TILE_MOVE); + } + public static void highlightAttackTile(ActorRef out, GameState gameState, int x, int y) { + highlightTile(out, gameState, x, y, UiConstants.TILE_ATTACK); + } + public static void highlightTargetTile(ActorRef out, GameState gameState, int x, int y) { + highlightTile(out, gameState, x, y, UiConstants.TILE_ATTACK); + } + public static void showMoveHighlights(ActorRef out, GameState gameState, Iterable coords) { + clearTileHighlights(out, gameState); + if (coords == null) return; + + for (GameState.Coord c : coords) { + if (c == null) continue; + highlightMoveTile(out, gameState, c.x, c.y); + } + } + + /** + * Highlight a list of attack tiles. + */ + public static void showAttackHighlights(ActorRef out, GameState gameState, Iterable coords) { + clearTileHighlights(out, gameState); + if (coords == null) return; + + for (GameState.Coord c : coords) { + if (c == null) continue; + highlightAttackTile(out, gameState, c.x, c.y); + } + } + public static void showTargetHighlights(ActorRef out, GameState gameState, Iterable coords) { + clearTileHighlights(out, gameState); + if (coords == null) return; + + for (GameState.Coord c : coords) { + if (c == null) continue; + highlightTargetTile(out, gameState, c.x, c.y); + } + } + private static void highlightTile(ActorRef out, GameState gameState, int x, int y, int mode) { + if (gameState == null) return; + if (!gameState.board.inBounds(x, y)) return; + + Tile tile = gameState.board.getTile(x, y); + if (tile == null) return; + + gameState.addHighlightedTile(x, y); + BasicCommands.drawTile(out, tile, mode); + } +} \ No newline at end of file diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java index 759146e..1b7a0f4 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/ui/UiConstants.java @@ -13,7 +13,5 @@ public final class UiConstants { public static final int CARD_NORMAL = 0; public static final int CARD_SELECTED = 1; - private UiConstants() { - //prevent creating instantiations - } + private UiConstants() {} } diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java index e9328fa..7cb2423 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/structures/GameState.java @@ -1,6 +1,8 @@ package structures; import java.util.HashSet; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Set; import game.Board; @@ -19,24 +21,52 @@ public class GameState { public Tile getTile(int x, int y) { return board.getTile(x, y); } - + public Integer getSelectedUnitId() { + return selection.selectedUnitId; + } + public Integer getSelectedHandPos() { + return selection.selectedHandPos; + } + public Coord findUnitCoord(int unitId) { + for (int x = board.minX(); x <= board.maxX(); x++) { + for (int y = board.minY(); y <= board.maxY(); y++) { + Integer current = board.getUnitIdAt(x, y); + if (current != null && current.intValue() == unitId) { + return new Coord(x, y); + } + } + } + return null; + } + public boolean isUnitOnBoard(int unitId) { + return findUnitCoord(unitId) != null; + } + public void registerUnitOwner(int unitId, int ownerPlayerId) { + unitOwnerById.put(unitId, ownerPlayerId); + } + public Integer getUnitOwnerPlayerId(int unitId) { + return unitOwnerById.get(unitId); + } + public void removeUnitOwner(int unitId) { + unitOwnerById.remove(unitId); + } public boolean gameInitalised = false; public boolean something = false; public final SelectionState selection = new SelectionState(); - - //Store the current Player 1 hand so we can redraw cards (unhighlight) reliably. - //Index 0 to 5 relates to UI positions 1 to 6. - //If we implement a full Hand model later on, we can replace this + //When we implement a full Hand model later on, we can replace this public final Card[] p1Hand = new Card[6]; - public final Board board = new Board(); //default 1 based coordinates + public final Board board = new Board(0,0); //default 0 based coordinates + public final Map unitOwnerById = new HashMap<>(); + //we call these once during initialize public void resetForNewGame() { gameInitalised = true; selection.reset(); clearP1Hand(); board.clearOccupancy(); //added to reset unit placements without recreating tiles + unitOwnerById.clear(); } public void clearP1Hand() { From d3b90095d968c0d1772677db178ed46f985764bb Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 07:55:56 +0000 Subject: [PATCH 5/7] cleanups finishing SC-D4 --- .../app/game/selection/SelectionManager.java | 117 +++++++++++++++++- .../app/game/selection/TargetingOverlay.java | 30 ++--- 2 files changed, 128 insertions(+), 19 deletions(-) diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java index 18b77f9..53a73e5 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/SelectionManager.java @@ -10,16 +10,25 @@ import structures.basic.Tile; /** - * This class serves as a central helper for safe selection switching and highlight cleanup. - * It keeps the selection logic out of event files as much as possible. + * Event files should call into this class instead of duplicating selection logic. */ public final class SelectionManager { private SelectionManager() {} + /* + * Full cancel! clears board highlights, hand highlights, and selection state. + */ public static void clearSelectionAndHighlights(ActorRef out, GameState gameState) { if (gameState == null) return; + clearTileHighlights(out, gameState); + clearHandHighlights(out, gameState); + gameState.clearSelection(); + } + public static void clearTileHighlights(ActorRef out, GameState gameState) { + if (gameState == null) return; + Set tilesToClear = gameState.consumeHighlightedTiles(); for (GameState.Coord c : tilesToClear) { Tile tile = gameState.board.getTile(c.x, c.y); @@ -27,6 +36,9 @@ public static void clearSelectionAndHighlights(ActorRef out, GameState gameState BasicCommands.drawTile(out, tile, UiConstants.TILE_NORMAL); } } + } + public static void clearHandHighlights(ActorRef out, GameState gameState) { + if (gameState == null) return; Set handToClear = gameState.consumeHighlightedHandPositions(); for (Integer pos : handToClear) { @@ -37,8 +49,6 @@ public static void clearSelectionAndHighlights(ActorRef out, GameState gameState BasicCommands.drawCard(out, card, pos, UiConstants.CARD_NORMAL); } } - - gameState.clearSelection(); } public static void selectCard(ActorRef out, GameState gameState, int handPos) { @@ -54,12 +64,111 @@ public static void selectCard(ActorRef out, GameState gameState, int handPos) { BasicCommands.drawCard(out, card, handPos, UiConstants.CARD_SELECTED); } + /* + * Keep the same card selected while refreshing targeting tiles. + * Use this when the same selected card needs its valid board targets redrawn. It clears board + * highlights only, not the card selection itself. + */ + public static void refreshCardTargeting(ActorRef out, GameState gameState, int handPos) { + if (gameState == null) return; + + if (!SelectionGuards.hasCardInHand(gameState, handPos)) { + clearSelectionAndHighlights(out, gameState); + return; + } + + if (!SelectionGuards.isSameSelectedCard(gameState, handPos)) { + selectCard(out, gameState, handPos); + return; + } + + clearTileHighlights(out, gameState); + redrawSelectedCardIfNeeded(out, gameState); + } + /* + * Re-draw the currently selected card in selected mode. + * to be used after tile refresh or cleanup. + */ + public static void redrawSelectedCardIfNeeded(ActorRef out, GameState gameState) { + if (gameState == null) return; + if (!isCardSelected(gameState)) return; + + Integer handPos = gameState.getSelectedHandPos(); + if (handPos == null) { + clearSelectionAndHighlights(out, gameState); + return; + } + + Card card = gameState.getP1HandAtPos(handPos); + if (card == null) { + clearSelectionAndHighlights(out, gameState); + return; + } + + gameState.addHighlightedHandPos(handPos); + BasicCommands.drawCard(out, card, handPos, UiConstants.CARD_SELECTED); + } public static void selectUnit(ActorRef out, GameState gameState, int unitId) { if (gameState == null) return; clearSelectionAndHighlights(out, gameState); gameState.selectUnit(unitId); } + /* + * to be used from TileClicked when a friendly occupied tile is clicked. + * should return true if the click was handled as a friendly-unit selection action. + */ + public static boolean handleFriendlyUnitTileClick( + ActorRef out, + GameState gameState, + int tileX, + int tileY, + int friendlyPlayerId + ) { + if (gameState == null) return false; + if (!SelectionGuards.isValidBoardTile(gameState, tileX, tileY)) return false; + + Integer unitId = gameState.board.getUnitIdAt(tileX, tileY); + if (unitId == null) return false; + + Integer ownerPlayerId = gameState.getUnitOwnerPlayerId(unitId); + if (ownerPlayerId == null || ownerPlayerId.intValue() != friendlyPlayerId) return false; + + if (SelectionGuards.isSameSelectedUnit(gameState, unitId)) { + clearSelectionAndHighlights(out, gameState); + return true; + } + + selectUnit(out, gameState, unitId); + renderStandardUnitActionHighlights(out, gameState, unitId, friendlyPlayerId); + return true; + } + + /* + * Draw standard movement and attack reach for the selected unit. + * Green means legal move tile + * Red means enemy target tile + */ + public static void renderStandardUnitActionHighlights( + ActorRef out, + GameState gameState, + int unitId, + int friendlyPlayerId + ) { + if (gameState == null) return; + GameState.Coord unitPos = gameState.findUnitCoord(unitId); + if (unitPos == null) { + clearSelectionAndHighlights(out, gameState); + return; + } + TargetingOverlay.clearTileHighlights(out, gameState); + TargetingOverlay.appendMoveHighlights( + out, gameState, RangeFinder.getStandardMoveTiles(gameState, unitPos.x, unitPos.y) + ); + TargetingOverlay.appendAttackHighlights( + out, gameState, RangeFinder.getStandardMoveThenAttackTiles(gameState, unitPos.x, unitPos.y, friendlyPlayerId) + ); + } public static boolean isCardSelected(GameState gameState) { return gameState != null diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java index f264502..b135e8e 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/game/selection/TargetingOverlay.java @@ -18,14 +18,7 @@ public static void clearAll(ActorRef out, GameState gameState) { SelectionManager.clearSelectionAndHighlights(out, gameState); } public static void clearTileHighlights(ActorRef out, GameState gameState) { - if (gameState == null) return; - - for (GameState.Coord c : gameState.consumeHighlightedTiles()) { - Tile tile = gameState.board.getTile(c.x, c.y); - if (tile != null) { - BasicCommands.drawTile(out, tile, UiConstants.TILE_NORMAL); - } - } + SelectionManager.clearTileHighlights(out, gameState); } public static void highlightMoveTile(ActorRef out, GameState gameState, int x, int y) { highlightTile(out, gameState, x, y, UiConstants.TILE_MOVE); @@ -38,6 +31,17 @@ public static void highlightTargetTile(ActorRef out, GameState gameState, int x, } public static void showMoveHighlights(ActorRef out, GameState gameState, Iterable coords) { clearTileHighlights(out, gameState); + appendMoveHighlights(out, gameState, coords); + } + public static void showAttackHighlights(ActorRef out, GameState gameState, Iterable coords) { + clearTileHighlights(out, gameState); + appendAttackHighlights(out, gameState, coords); + } + public static void showTargetHighlights(ActorRef out, GameState gameState, Iterable coords) { + clearTileHighlights(out, gameState); + appendTargetHighlights(out, gameState, coords); + } + public static void appendMoveHighlights(ActorRef out, GameState gameState, Iterable coords) { if (coords == null) return; for (GameState.Coord c : coords) { @@ -46,11 +50,7 @@ public static void showMoveHighlights(ActorRef out, GameState gameState, Iterabl } } - /** - * Highlight a list of attack tiles. - */ - public static void showAttackHighlights(ActorRef out, GameState gameState, Iterable coords) { - clearTileHighlights(out, gameState); + public static void appendAttackHighlights(ActorRef out, GameState gameState, Iterable coords) { if (coords == null) return; for (GameState.Coord c : coords) { @@ -58,8 +58,8 @@ public static void showAttackHighlights(ActorRef out, GameState gameState, Itera highlightAttackTile(out, gameState, c.x, c.y); } } - public static void showTargetHighlights(ActorRef out, GameState gameState, Iterable coords) { - clearTileHighlights(out, gameState); + + public static void appendTargetHighlights(ActorRef out, GameState gameState, Iterable coords) { if (coords == null) return; for (GameState.Coord c : coords) { From 974d44e5870ca73a693983266ca48cbd8f03e6aa Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 08:08:05 +0000 Subject: [PATCH 6/7] Update CardClicked for Wenbo Set up for Wenbo --- .../app/events/CardClicked.java | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/CardClicked.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/CardClicked.java index 29c37f9..1a57d99 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/CardClicked.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/CardClicked.java @@ -5,6 +5,10 @@ import akka.actor.ActorRef; import structures.GameState; +import game.selection.SelectionGuards; +import game.selection.SelectionManager; +import game.selection.TargetingOverlay; +import structures.basic.Card; /** * Indicates that the user has clicked an object on the game canvas, in this case a card. @@ -20,12 +24,41 @@ */ public class CardClicked implements EventProcessor{ - @Override - public void processEvent(ActorRef out, GameState gameState, JsonNode message) { - - int handPosition = message.get("position").asInt(); - - - } + @Override + public void processEvent(ActorRef out, GameState gameState, JsonNode message) { + if (gameState == null || !gameState.gameInitalised) return; + int handPosition = message.get("position").asInt(); + if (!SelectionGuards.isValidHandPosition(handPosition)) return; + SelectionGuards.clearStaleSelectionIfNeeded(gameState); + + if (!SelectionGuards.hasCardInHand(gameState, handPosition)) { + SelectionManager.clearSelectionAndHighlights(out, gameState); + return; + } + if (SelectionGuards.isSameSelectedCard(gameState, handPosition)) { + SelectionManager.clearSelectionAndHighlights(out, gameState); + return; + } + + SelectionManager.selectCard(out, gameState, handPosition); + Card card = gameState.getP1HandAtPos(handPosition); + if (card == null) { + SelectionManager.clearSelectionAndHighlights(out, gameState); + return; + } + + // Wenbo’s responsibility starts here: + //compute legal summon/spell targets for this card + //then highlight those tiles + //The important rule for Wenbo is: + // use SelectionManager.selectCard(...) when switching to a card + // use TargetingOverlay.showTargetHighlights(...) to draw legal targets + // use SelectionManager.clearSelectionAndHighlights(...) only for real cancel + // do not wipe card selection just to redraw tiles, + // because my (abdullah) D4 responsibility is that the selected card stays visibly selected during targeting + //example only: + // Iterable legalTargets = ... + // TargetingOverlay.showTargetHighlights(out, gameState, legalTargets); + } } From e938590f23cf30cba5148861a2d6ab70eafb868f Mon Sep 17 00:00:00 2001 From: Abdullah Tariq Date: Fri, 6 Mar 2026 08:20:16 +0000 Subject: [PATCH 7/7] Added skeleton for TileClicked Added a skeleton to integrate my (abdullah's) helpers to be used by Yang --- .../app/events/TileClicked.java | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/TileClicked.java b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/TileClicked.java index 87f6857..289d9ab 100644 --- a/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/TileClicked.java +++ b/ITSD-DT2025-26-Template/ITSD-DT2025-26-Template/app/events/TileClicked.java @@ -5,6 +5,8 @@ import akka.actor.ActorRef; import structures.GameState; +import game.selection.SelectionGuards; +import game.selection.SelectionManager; /** * Indicates that the user has clicked an object on the game canvas, in this case a tile. @@ -22,16 +24,48 @@ */ public class TileClicked implements EventProcessor{ - @Override - public void processEvent(ActorRef out, GameState gameState, JsonNode message) { + @Override + public void processEvent(ActorRef out, GameState gameState, JsonNode message) { + if (gameState == null || !gameState.gameInitalised) return; - int tilex = message.get("tilex").asInt(); - int tiley = message.get("tiley").asInt(); - - if (gameState.something == true) { - // do some logic - } - - } + int tilex = message.get("tilex").asInt(); + int tiley = message.get("tiley").asInt(); -} + if (!SelectionGuards.isValidBoardTile(gameState, tilex, tiley)) return; + SelectionGuards.clearStaleSelectionIfNeeded(gameState); + //Assumption: human player is player 1 + int friendlyPlayerId = 1; + + //for friendly occupied tile use Abdullah's selection helper + if (SelectionManager.handleFriendlyUnitTileClick(out, gameState, tilex, tiley, friendlyPlayerId)) { + return; + } + + //if a selected unit exists and clicked tile is highlighted, + // Yang handles movement/combat from here + if (SelectionManager.isUnitSelected(gameState) + && SelectionGuards.isHighlightedTile(gameState, tilex, tiley)) { + + Integer selectedUnitId = gameState.getSelectedUnitId(); + if (selectedUnitId == null) { + SelectionManager.clearSelectionAndHighlights(out, gameState); + return; + } + + Integer clickedUnitId = gameState.board.getUnitIdAt(tilex, tiley); + + if (clickedUnitId == null) { + // Yang: movement logic here + // move selected unit to (tilex, tiley) + return; + } else { + // Yang: combat logic here + // selected unit attacks clicked unit + return; + } + } + + //Anything else would clear selection + SelectionManager.clearSelectionAndHighlights(out, gameState); + } +} \ No newline at end of file