Skip to content

Database layer #47

Merged
merged 14 commits into from
Mar 24, 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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.group14.regicidechess.Main;
import com.group14.regicidechess.database.DatabaseManager;

/** Launches the Android application. */
public class AndroidLauncher extends AndroidApplication {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration configuration = new AndroidApplicationConfiguration();
configuration.useImmersiveMode = true; // Recommended, but not required.
initialize(new Main(new AndroidAPI()), configuration);

// Initialise Firebase bridge before starting the game
AndroidFirebase firebase = new AndroidFirebase();
DatabaseManager.getInstance().init(firebase);

AndroidApplicationConfiguration configuration = new AndroidApplicationConfiguration();
configuration.useImmersiveMode = true;
initialize(new Main(firebase), configuration);
}
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/com/group14/regicidechess/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.badlogic.gdx.Game;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.group14.regicidechess.screens.MainMenuScreen;
import com.group14.regicidechess.screens.mainmenu.MainMenuScreen;
import com.group14.regicidechess.utils.ResourceManager;

public class Main extends Game {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.group14.regicidechess.database;

public class DatabaseManager {

private static DatabaseManager instance;
private FirebaseAPI api;

private DatabaseManager() {}

public static DatabaseManager getInstance() {
if (instance == null) instance = new DatabaseManager();
return instance;
}

public void init(FirebaseAPI api) {
this.api = api;
}

/**
* Returns the FirebaseAPI implementation.
* Throws a clear IllegalStateException if init() was never called,
* so the cause is obvious instead of a confusing NullPointerException.
*/
public FirebaseAPI getApi() {
if (api == null) {
throw new IllegalStateException(
"DatabaseManager not initialised! " +
"Call DatabaseManager.getInstance().init(firebase) " +
"in your launcher before starting the game.");
}
return api;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.group14.regicidechess.database;

import com.group14.regicidechess.model.Lobby;
import com.group14.regicidechess.model.Move;

public interface FirebaseAPI {

// ── Lobby ─────────────────────────────────────────────────────────────────

void createLobby(Lobby lobby, Callback<String> onSuccess, Callback<String> onError);
void joinLobby(String gameId, Callback<Lobby> onSuccess, Callback<String> onError);

/** Reads lobby settings without any side effects. Used by MainMenuScreen to validate. */
void fetchLobby(String gameId, Callback<Lobby> onSuccess, Callback<String> onError);

/**
* Fires once when lobbies/{gameId}/status == "joined".
* HOST calls this to know a second player has entered the lobby.
*/
void listenForOpponentReady(String gameId, Runnable onReady);

/**
* Sets lobbies/{gameId}/status = "started".
* HOST calls this when pressing "Start Game" so the joiner navigates to SetupScreen.
*/
void startGame(String gameId);

/**
* Fires once when lobbies/{gameId}/status == "started".
* JOINER calls this to know the host has pressed "Start Game".
*/
void listenForGameStart(String gameId, Runnable onStart);

// ── Setup ─────────────────────────────────────────────────────────────────

/**
* Saves this player's board layout and marks them as setup-ready.
* Sets games/{gameId}/bothReady = true once BOTH players have called this.
*/
void confirmSetup(String gameId, boolean isWhite, int[][] board, Runnable onSuccess);

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

/**
* Fires once when games/{gameId}/bothReady == true.
* BOTH players call this in SetupScreen after confirming their boards.
* NOTE: Completely separate from listenForGameStart() which is lobby-phase only.
*/
void listenForBothSetupReady(String gameId, Runnable onBothReady);

// ── In-match ──────────────────────────────────────────────────────────────

void saveMove(String gameId, Move move, Runnable onSuccess);

/**
* Fires for every move added to games/{gameId}/moves.
* Returns int[]{fromCol, fromRow, toCol, toRow, isWhite(1=white/0=black)}.
* Caller filters out own moves by comparing isWhite to localPlayer.isWhite().
*/
void listenForOpponentMove(String gameId, Callback<int[]> onMove);

void sendHeartbeat(String gameId, boolean isWhite);

/**
* Listens for the opponent's heartbeat.
* @param onHeartbeat called with the latency (delay) in ms when a heartbeat arrives.
* @param onTimeout called if no heartbeat is received within timeoutMs.
*/
void listenForHeartbeat(String gameId, boolean listenForWhite, long timeoutMs,
Callback<Long> onHeartbeat, Runnable onTimeout);

/** e.g. "forfeit:white", "forfeit:black" */
void signalGameOver(String gameId, String reason);
void listenForGameOver(String gameId, Callback<String> onGameOver);

// ─────────────────────────────────────────────────────────────────────────

interface Callback<T> {
void call(T value);
}
}
4 changes: 4 additions & 0 deletions core/src/main/java/com/group14/regicidechess/model/Board.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public ChessPiece movePiece(int fromCol, int fromRow, int toCol, int toRow) {
ChessPiece moving = removePiece(fromCol, fromRow);
ChessPiece captured = pieces[toCol][toRow]; // may be null
placePiece(moving, toCol, toRow);
// Notify pawn it has moved so the two-square rule is disabled next turn
if (moving instanceof com.group14.regicidechess.model.pieces.Pawn) {
((com.group14.regicidechess.model.pieces.Pawn) moving).markMoved();
}
return captured;
}

Expand Down
16 changes: 12 additions & 4 deletions core/src/main/java/com/group14/regicidechess/model/Move.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class Move {
private final Vector2 to;
private final ChessPiece piece;
private final Player player;
private String promotion; // e.g. "Queen", "Rook", etc.

public Move(Vector2 from, Vector2 to, ChessPiece piece, Player player) {
this.from = from.cpy();
Expand All @@ -18,8 +19,15 @@ public Move(Vector2 from, Vector2 to, ChessPiece piece, Player player) {
this.player = player;
}

public Vector2 getFrom() { return from.cpy(); }
public Vector2 getTo() { return to.cpy(); }
public ChessPiece getPiece() { return piece; }
public Player getPlayer() { return player; }
public Move(Vector2 from, Vector2 to, ChessPiece piece, Player player, String promotion) {
this(from, to, piece, player);
this.promotion = promotion;
}

public Vector2 getFrom() { return from.cpy(); }
public Vector2 getTo() { return to.cpy(); }
public ChessPiece getPiece() { return piece; }
public Player getPlayer() { return player; }
public String getPromotion() { return promotion; }
public void setPromotion(String promotion) { this.promotion = promotion; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@
import java.util.ArrayList;
import java.util.List;

/** Placement: core/src/main/java/com/group14/regicidechess/model/pieces/Pawn.java */
public class Pawn extends ChessPiece {

private boolean hasMoved = false;

public Pawn(Player owner) {
super(owner, 1);
}

@Override
public List<Vector2> validMoves() {
List<Vector2> moves = new ArrayList<>();
// White moves up (+row), black moves down (-row)
int dir = owner.isWhite() ? 1 : -1;
int c = (int) position.x;
int r = (int) position.y;

// Forward — only if square is empty (FR1.1)
// One square forward
if (board.inBounds(c, r + dir) && board.getPieceAt(c, r + dir) == null) {
moves.add(new Vector2(c, r + dir));

// Two squares forward on first move (FR1.2)
if (!hasMoved && board.inBounds(c, r + 2 * dir)
&& board.getPieceAt(c, r + 2 * dir) == null) {
moves.add(new Vector2(c, r + 2 * dir));
}
}

// Diagonal captures
Expand All @@ -36,9 +42,15 @@ public List<Vector2> validMoves() {
}
}
}
// No en passant (§2.1.3)
return moves;
}

/** Called by Board.movePiece() after the pawn is moved. */
public void markMoved() {
hasMoved = true;
}

public boolean hasMoved() { return hasMoved; }

@Override public String getTypeName() { return "Pawn"; }
}
Loading