Skip to content
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 @@ -10,7 +10,8 @@ public enum Screens {
Home("home"),
PVP_SETUP("pvp_setup"),
GAME("game_screen"),
DifficultySelection("difficulty_selection");
DifficultySelection("difficulty_selection"),
RECORDINGS("RecordingsListScreen");


private final String name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum GameMode {
OFFLINE_PVP,
OFFLINE_PVE,
ONLINE_PVP
ONLINE_PVP,
REPLAY
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
/**
* @author Tasneem
*/
public record GameNavigationParams(String player1, String player2, GameMode mode) { }

public record GameNavigationParams(
String player1,
String player2,
GameMode mode,
GameRecord replayData
) {
public GameNavigationParams(String player1, String player2, GameMode mode) {
this(player1, player2, mode, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Record.java to edit this template
*/
package com.boredxgames.tictactoeclient.domain.model;

import java.util.List;

/**
*
* @author Hazem
*/
public record GameRecord(
String date,
String player1,
String player2,
char winner,
List<RecordedMove> moves
) {
public String getResultDescription() {
if (winner == 'X') return player1 + " (X) Won";
if (winner == 'O') return player2 + " (O) Won";
return "Draw";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Record.java to edit this template
*/
package com.boredxgames.tictactoeclient.domain.model;

/**
*
* @author Hazem
*/
public record RecordedMove(int row, int col, char player) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package com.boredxgames.tictactoeclient.domain.services.storage;

import com.boredxgames.tictactoeclient.domain.model.GameRecord;
import com.boredxgames.tictactoeclient.domain.model.RecordedMove;
import com.boredxgames.tictactoeclient.domain.services.game.GameBoard;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
* @author Hazem
*/
public class GameRecordingService {

private static final String HEADER_SIGNATURE = "TICTACTOE_REC_V1";
private static final String DIRECTORY_PATH = "recordings";

public void saveGame(GameBoard board, String p1Name, String p2Name, String filename) throws IOException {
File directory = new File(DIRECTORY_PATH);
if (!directory.exists()) {
directory.mkdirs();
}

if (!filename.endsWith(".dat")) {
filename += ".dat";
}

File file = new File(directory, filename);

try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(file)))) {

dos.writeUTF(HEADER_SIGNATURE);

dos.writeUTF(LocalDateTime.now().toString());
dos.writeUTF(p1Name);
dos.writeUTF(p2Name);

// 3. Winner
dos.writeChar(board.getWinner());

// 4. Moves
List<RecordedMove> history = board.getMoveHistory();
dos.writeInt(history.size());

for (RecordedMove move : history) {
dos.writeByte(move.row());
dos.writeByte(move.col());
dos.writeChar(move.player());
}
}
}

public GameRecord readGame(File file) throws IOException {
if (!file.exists()) {
throw new IOException("File not found");
}

try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(file)))) {

String header = dis.readUTF();
if (!HEADER_SIGNATURE.equals(header)) {
throw new IOException("Invalid file format");
}

String date = dis.readUTF();
String p1Name = dis.readUTF();
String p2Name = dis.readUTF();
char winner = dis.readChar();

int moveCount = dis.readInt();
List<RecordedMove> moves = new ArrayList<>(moveCount);

for (int i = 0; i < moveCount; i++) {
int row = dis.readByte();
int col = dis.readByte();
char player = dis.readChar();
moves.add(new RecordedMove(row, col, player));
}

return new GameRecord(date, p1Name, p2Name, winner, moves);
}
}

public List<File> getAllRecordings() {
File directory = new File(DIRECTORY_PATH);
if (!directory.exists() || !directory.isDirectory()) {
return new ArrayList<>();
}
File[] files = directory.listFiles((dir, name) -> name.endsWith(".dat"));
return files != null ? List.of(files) : new ArrayList<>();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package com.boredxgames.tictactoeclient.domain.services.game;

import com.boredxgames.tictactoeclient.domain.model.GameState;

import com.boredxgames.tictactoeclient.domain.model.RecordedMove;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
*
* @author Tasneem
*/
public class GameBoard {
Expand All @@ -21,13 +25,16 @@ public class GameBoard {
private char currentPlayer;
private GameState gameState;
private int movesCount;

private final List<RecordedMove> moveHistory;

public GameBoard() {
this(PLAYER_X);
}

public GameBoard(char startingPlayer) {
board = new char[BOARD_SIZE][BOARD_SIZE];
moveHistory = new ArrayList<>();
initializeBoard();
currentPlayer = startingPlayer;
gameState = GameState.IN_PROGRESS;
Expand All @@ -48,6 +55,7 @@ public void resetGame() {

public void resetGame(char startingPlayer) {
initializeBoard();
moveHistory.clear();
currentPlayer = startingPlayer;
gameState = GameState.IN_PROGRESS;
movesCount = 0;
Expand All @@ -64,6 +72,9 @@ public boolean makeMove(int row, int col, char player) {

board[row][col] = player;
movesCount++;

moveHistory.add(new RecordedMove(row, col, player));

updateGameState();

if (gameState == GameState.IN_PROGRESS) {
Expand All @@ -82,11 +93,9 @@ public boolean isValidMove(int row, int col) {
if (gameState != GameState.IN_PROGRESS) {
return false;
}

if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) {
return false;
}

return board[row][col] == EMPTY;
}

Expand All @@ -109,100 +118,63 @@ public boolean isBoardFull() {
}

public boolean checkWin(char player) {
// rows
for (int i = 0; i < BOARD_SIZE; i++) {
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) {
return true;
}
if (board[i][0] == player && board[i][1] == player && board[i][2] == player) return true;
}

// col
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[0][j] == player && board[1][j] == player && board[2][j] == player) {
return true;
}
}

// diagonal left -> right
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) {
return true;
}

// diagonal right -> left
if (board[0][2] == player && board[1][1] == player && board[2][0] == player) {
return true;
if (board[0][j] == player && board[1][j] == player && board[2][j] == player) return true;
}
if (board[0][0] == player && board[1][1] == player && board[2][2] == player) return true;
if (board[0][2] == player && board[1][1] == player && board[2][0] == player) return true;

return false;
}

public int[] getWinningLine() {
if (gameState == GameState.IN_PROGRESS || gameState == GameState.DRAW) {
return null;
}

if (gameState == GameState.IN_PROGRESS || gameState == GameState.DRAW) return null;
char winner = (gameState == GameState.X_WINS) ? PLAYER_X : PLAYER_O;

// row
for (int i = 0; i < BOARD_SIZE; i++) {
if (board[i][0] == winner && board[i][1] == winner && board[i][2] == winner) {
if (board[i][0] == winner && board[i][1] == winner && board[i][2] == winner)
return new int[]{i, 0, i, 1, i, 2};
}
}

// col
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[0][j] == winner && board[1][j] == winner && board[2][j] == winner) {
if (board[0][j] == winner && board[1][j] == winner && board[2][j] == winner)
return new int[]{0, j, 1, j, 2, j};
}
}

// diagonal left -> right
if (board[0][0] == winner && board[1][1] == winner && board[2][2] == winner) {
if (board[0][0] == winner && board[1][1] == winner && board[2][2] == winner)
return new int[]{0, 0, 1, 1, 2, 2};
}

// diagonal right -> left
if (board[0][2] == winner && board[1][1] == winner && board[2][0] == winner) {
if (board[0][2] == winner && board[1][1] == winner && board[2][0] == winner)
return new int[]{0, 2, 1, 1, 2, 0};
}


return null;
}

public char getWinner() {
if (gameState == GameState.X_WINS) {
return PLAYER_X;
} else if (gameState == GameState.O_WINS) {
return PLAYER_O;
}
if (gameState == GameState.X_WINS) return PLAYER_X;
if (gameState == GameState.O_WINS) return PLAYER_O;
return EMPTY;
}

public char getCurrentPlayer() {
return currentPlayer;
}

public GameState getGameState() {
return gameState;
}

public char getCurrentPlayer() { return currentPlayer; }
public GameState getGameState() { return gameState; }

public char getCellValue(int row, int col) {
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) {
return EMPTY;
}
if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return EMPTY;
return board[row][col];
}

public List<int[]> getAvailableMoves() {
List<int[]> availableMoves = new ArrayList<>();
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] == EMPTY) {
availableMoves.add(new int[]{i, j});
}
if (board[i][j] == EMPTY) availableMoves.add(new int[]{i, j});
}
}
return availableMoves;
}
}

public List<RecordedMove> getMoveHistory() {
return Collections.unmodifiableList(moveHistory);
}
}
Loading