Skip to content

Bug fix #50

Merged
merged 2 commits into from
Mar 30, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,19 @@ private int getInt(DataSnapshot snapshot, String key) {
if (val instanceof Integer) return (Integer) val;
return 0;
}

@Override
public void unconfirmSetup(String gameId, boolean isWhite, Runnable onSuccess, Callback<String> onError) {
String player = isWhite ? "white" : "black";
DatabaseReference gameRef = db.child("games").child(gameId);

// Clear the ready flag for this player and reset bothReady
gameRef.child("setup").child(player).removeValue()
.addOnSuccessListener(v -> {
gameRef.child("bothReady").removeValue()
.addOnSuccessListener(v2 -> onSuccess.run())
.addOnFailureListener(e -> onError.call(e.getMessage()));
})
.addOnFailureListener(e -> onError.call(e.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// FirebaseAPI.java - Fix unconfirmSetup signature
package com.group14.regicidechess.database;

import com.group14.regicidechess.model.Lobby;
Expand Down Expand Up @@ -39,6 +40,11 @@ public interface FirebaseAPI {
*/
void confirmSetup(String gameId, boolean isWhite, int[][] board, Runnable onSuccess);

/**
* Unconfirms the setup, marking this player as not ready.
*/
void unconfirmSetup(String gameId, boolean isWhite, Runnable onSuccess, Callback<String> onError);

/** Fetches the opponent's stored board layout. */
void getOpponentBoard(String gameId, boolean localIsWhite, Callback<int[][]> onBoard);

Expand Down Expand Up @@ -79,4 +85,4 @@ void listenForHeartbeat(String gameId, boolean listenForWhite, long timeoutMs,
interface Callback<T> {
void call(T value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ public void render(float delta) {
stage.act(delta);
stage.draw();

// Draw a semi-transparent dimmer behind any visible overlay
if (overlayManager.isAnyOverlayVisible()) {
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
Expand All @@ -217,7 +216,7 @@ public void render(float delta) {
sr.end();
sr.dispose();
Gdx.gl.glDisable(GL20.GL_BLEND);
stage.draw(); // redraw so overlay card appears above dimmer
stage.draw();
}
}

Expand Down Expand Up @@ -295,7 +294,6 @@ private void executeMove(Vector2 from, Vector2 to) {
ChessPiece movingPiece = inMatchState.getBoard().getPieceAt(from);
if (movingPiece == null) { deselect(); return; }

// Execute locally — this also switches the turn inside InMatchState
ChessPiece captured = inMatchState.executeMove(from, to);
deselect();

Expand All @@ -305,7 +303,6 @@ private void executeMove(Vector2 from, Vector2 to) {
overlayManager.showGameOver(true);

} else if (isPawnPromotion(movingPiece, to)) {
// Do NOT save yet — wait for the player to pick a promotion piece
pendingPromotionMove = new Move(from, to, movingPiece, localPlayer);
overlayManager.showPromotion();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public interface HostUIListener {
private Label statusLabel;
private TextButton createBtn;
private TextButton startBtn;
private Table sliderSection;

public LobbyHostUI(Skin skin, HostUIListener listener) {
this.skin = skin;
Expand Down Expand Up @@ -82,19 +83,21 @@ public void changed(ChangeEvent event, Actor actor) {
}
});

// Layout
container.add(buildRow(boardSizeLabel, boardSizeValueLabel))
// Sliders grouped so they can be hidden after lobby is created
sliderSection = new Table();
sliderSection.add(buildRow(boardSizeLabel, boardSizeValueLabel))
.expandX().fillX().padBottom(8).row();
container.add(boardSlider)
sliderSection.add(boardSlider)
.width(LobbyScreenConfig.SLIDER_WIDTH)
.height(LobbyScreenConfig.SLIDER_HEIGHT)
.padBottom(24).row();
container.add(buildRow(budgetLabel, budgetValueLabel))
sliderSection.add(buildRow(budgetLabel, budgetValueLabel))
.expandX().fillX().padBottom(8).row();
container.add(budgetSlider)
sliderSection.add(budgetSlider)
.width(LobbyScreenConfig.SLIDER_WIDTH)
.height(LobbyScreenConfig.SLIDER_HEIGHT)
.padBottom(32).row();
container.add(sliderSection).expandX().fillX().row();
container.add(createBtn)
.width(LobbyScreenConfig.BUTTON_WIDTH)
.height(LobbyScreenConfig.BUTTON_HEIGHT)
Expand Down Expand Up @@ -166,6 +169,7 @@ public void showCreatingState() {
}

public void showLobbyCreated(String gameId) {
sliderSection.setVisible(false);
createBtn.setVisible(false);
startBtn.setVisible(true);
startBtn.setDisabled(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// File: core/src/main/java/com/group14/regicidechess/screens/setup/SetupBoardInputHandler.java
// SetupBoardInputHandler.java - Updated version
package com.group14.regicidechess.screens.setup;

import com.group14.regicidechess.model.Player;
Expand All @@ -14,6 +14,7 @@ public class SetupBoardInputHandler {
public interface BoardActionListener {
void onPiecePlaced(ChessPiece piece, int col, int row);
void onPieceRemoved(int col, int row);
void onPieceReplaced(ChessPiece oldPiece, ChessPiece newPiece, int col, int row);
void onInvalidPlacement(String reason);
void onStateChanged();
}
Expand All @@ -23,18 +24,32 @@ public interface BoardActionListener {
private final SetupPaletteWidget palette;
private final Player localPlayer;
private final BoardActionListener listener;
private final boolean isLocked;

public SetupBoardInputHandler(SetupState setupState, SetupBoardRenderer renderer,
SetupPaletteWidget palette, Player localPlayer,
BoardActionListener listener) {
this(setupState, renderer, palette, localPlayer, listener, false);
}

public SetupBoardInputHandler(SetupState setupState, SetupBoardRenderer renderer,
SetupPaletteWidget palette, Player localPlayer,
BoardActionListener listener, boolean isLocked) {
this.setupState = setupState;
this.renderer = renderer;
this.palette = palette;
this.localPlayer = localPlayer;
this.listener = listener;
this.isLocked = isLocked;
}

public boolean handleTap(float worldX, float worldY) {
// If locked, ignore all board interactions
if (isLocked) {
listener.onInvalidPlacement("Setup is locked. Unconfirm to make changes.");
return false;
}

float cellSize = renderer.getCellSize();
float boardLeft = renderer.getBoardLeft();
float boardBottom = renderer.getBoardBottom();
Expand All @@ -49,26 +64,62 @@ public boolean handleTap(float worldX, float worldY) {

ChessPiece existing = setupState.getBoard().getPieceAt(col, row);

if (existing != null) {
// Remove existing piece
if (palette.hasSelection()) {
ChessPiece newPiece = palette.createSelectedPiece(localPlayer);

if (existing != null) {
// Check if it's the same piece type
boolean isSameType = existing.getClass().equals(newPiece.getClass());

if (isSameType) {
// Same piece type = remove
setupState.removePiece(col, row);
listener.onPieceRemoved(col, row);
// Keep selection for future placements
listener.onStateChanged();
} else {
// Different piece type = replace
// Check if we can afford the new piece after refunding the old one
int costDiff = newPiece.getPointCost() - existing.getPointCost();

if (costDiff <= setupState.getPlayer().getBudgetRemaining()) {
// First remove old piece (this refunds its cost)
setupState.removePiece(col, row);
// Then place new piece (this spends its cost)
boolean success = setupState.placePiece(newPiece, col, row);

if (success) {
listener.onPieceReplaced(existing, newPiece, col, row);
palette.afterPiecePlaced();
listener.onStateChanged();
} else {
// If placement fails, put the old piece back
setupState.placePiece(existing, col, row);
listener.onInvalidPlacement("Cannot replace piece - check budget and placement rules.");
}
} else {
listener.onInvalidPlacement("Not enough budget to replace with " + newPiece.getTypeName() +
" (needs " + costDiff + " more)");
}
}
} else {
// Empty square - normal placement
boolean success = setupState.placePiece(newPiece, col, row);

if (success) {
listener.onPiecePlaced(newPiece, col, row);
palette.afterPiecePlaced();
listener.onStateChanged();
} else {
String reason = getPlacementFailureReason(newPiece);
listener.onInvalidPlacement(reason);
}
}
} else if (existing != null) {
// No piece selected, clicking on existing piece = remove
setupState.removePiece(col, row);
listener.onPieceRemoved(col, row);
listener.onStateChanged();
} else if (palette.hasSelection()) {
ChessPiece piece = palette.createSelectedPiece(localPlayer);
boolean success = setupState.placePiece(piece, col, row);

if (success) {
// IMPORTANT: Do NOT clear selection - keep it so player can place more
// palette.afterPiecePlaced() is called but we don't clear selection
// The palette's keepSelectionAfterPlace flag is true by default
palette.afterPiecePlaced(); // This won't clear if keepSelectionAfterPlace is true
listener.onPiecePlaced(piece, col, row);
listener.onStateChanged();
} else {
String reason = getPlacementFailureReason(piece);
listener.onInvalidPlacement(reason);
}
}

return true;
Expand All @@ -87,4 +138,7 @@ private boolean kingIsOnBoard() {
}
return false;
}

public void setLocked(boolean locked) {
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// File: core/src/main/java/com/group14/regicidechess/screens/setup/SetupFlowController.java
// SetupFlowController.java - Updated version
package com.group14.regicidechess.screens.setup;

import com.badlogic.gdx.Gdx;
Expand All @@ -17,6 +17,8 @@ public interface FlowListener {
void onBothReady();
void onOpponentBoardFetched(Board finalBoard, Player opponent);
void onError(String message);
void onUnconfirmSuccess();
void onUnconfirmError(String message);
}

private final String gameId;
Expand All @@ -26,6 +28,7 @@ public interface FlowListener {
private final BoardFetchRetryPolicy retryPolicy;

private int boardFetchRetries = 0;
private boolean isConfirmed = false;

public SetupFlowController(String gameId, Player localPlayer, SetupState setupState, FlowListener listener) {
this(gameId, localPlayer, setupState, listener, new BoardFetchRetryPolicy());
Expand All @@ -41,6 +44,11 @@ public SetupFlowController(String gameId, Player localPlayer, SetupState setupSt
}

public void confirm() {
if (isConfirmed) {
listener.onError("Setup already confirmed!");
return;
}

// Check UI readiness (King placed)
if (!setupState.isReadyForConfirm()) {
listener.onError("Place your King before confirming.");
Expand All @@ -53,6 +61,7 @@ public void confirm() {
return;
}

isConfirmed = true;
listener.onUploadComplete();

int[][] encoded = SetupBoardCodec.encode(setupState.getBoard());
Expand All @@ -63,6 +72,31 @@ public void confirm() {
this::listenForBothReady
);
}

public void unconfirm() {
if (!isConfirmed) {
listener.onError("Setup not confirmed yet!");
return;
}

// Reset the player's ready flag
setupState.getPlayer().resetReady();
isConfirmed = false;

DatabaseManager.getInstance().getApi().unconfirmSetup(
gameId,
localPlayer.isWhite(),
() -> Gdx.app.postRunnable(() -> {
listener.onUnconfirmSuccess();
}),
error -> Gdx.app.postRunnable(() -> {
listener.onUnconfirmError(error);
// Revert flag if unconfirm fails
isConfirmed = true;
setupState.setReady();
})
);
}

private void listenForBothReady() {
DatabaseManager.getInstance().getApi().listenForBothSetupReady(
Expand Down Expand Up @@ -106,4 +140,8 @@ private void fetchOpponentBoard() {
})
);
}

public boolean isConfirmed() {
return isConfirmed;
}
}
Loading