diff --git a/SudokuApp/app/build.gradle b/SudokuApp/app/build.gradle
new file mode 100644
index 0000000..2c68fe5
--- /dev/null
+++ b/SudokuApp/app/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'com.example.sudokuapp' // Changed from com.example.sudoku to avoid conflict with java dir
+ compileSdk 33 // Example SDK version
+
+ defaultConfig {
+ applicationId "com.example.sudokuapp"
+ minSdk 21
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ // If using Kotlin, uncomment the following block
+ // kotlinOptions {
+ // jvmTarget = '1.8'
+ // }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.6.1' // Example version
+ implementation 'com.google.android.material:material:1.8.0' // Example version
+ // Add other dependencies here
+ // Test dependencies
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+}
diff --git a/SudokuApp/app/proguard-rules.pro b/SudokuApp/app/proguard-rules.pro
new file mode 100644
index 0000000..fc88f06
--- /dev/null
+++ b/SudokuApp/app/proguard-rules.pro
@@ -0,0 +1,16 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /usr/local/google/android/sdk/tools/proguard/proguard-android-optimize.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify actual class name.
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/SudokuApp/app/src/main/AndroidManifest.xml b/SudokuApp/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c550451
--- /dev/null
+++ b/SudokuApp/app/src/main/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+ {/* Changed to 33 */}
+
+
+
+
+
+
+
+
diff --git a/SudokuApp/app/src/main/java/com/example/sudoku/.gitkeep b/SudokuApp/app/src/main/java/com/example/sudoku/.gitkeep
new file mode 100644
index 0000000..0fdedc7
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudoku/.gitkeep
@@ -0,0 +1 @@
+# This file is used to ensure the directory is tracked by git.
diff --git a/SudokuApp/app/src/main/java/com/example/sudoku/game/Cell.java b/SudokuApp/app/src/main/java/com/example/sudoku/game/Cell.java
new file mode 100644
index 0000000..f4859f0
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudoku/game/Cell.java
@@ -0,0 +1,56 @@
+package com.example.sudoku.game;
+
+public class Cell {
+ private int row;
+ private int col;
+ private int value; // 0 represents an empty cell
+ private boolean isStartingCell; // Was this cell part of the initial puzzle?
+ private boolean isEditable;
+
+ public Cell(int row, int col, int value) {
+ this.row = row;
+ this.col = col;
+ this.value = value;
+ this.isStartingCell = (value != 0); // If a value is provided initially, it's a starting cell
+ this.isEditable = !this.isStartingCell;
+ }
+
+ public Cell(int row, int col) {
+ this(row, col, 0); // Constructor for an empty cell
+ }
+
+ public int getRow() {
+ return row;
+ }
+
+ public int getCol() {
+ return col;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public void setValue(int value) {
+ if (isEditable) {
+ this.value = value;
+ }
+ // Optionally, throw an exception or log if trying to set a non-editable cell
+ }
+
+ public boolean isStartingCell() {
+ return isStartingCell;
+ }
+
+ public boolean isEditable() {
+ return isEditable;
+ }
+
+ public void setEditable(boolean editable) {
+ // This might be used if puzzle generation logic needs to change editability after creation
+ this.isEditable = editable;
+ if (editable) {
+ this.isStartingCell = false; // If it becomes editable, it's not a starting cell anymore
+ }
+ }
+}
diff --git a/SudokuApp/app/src/main/java/com/example/sudoku/game/SudokuGame.java b/SudokuApp/app/src/main/java/com/example/sudoku/game/SudokuGame.java
new file mode 100644
index 0000000..5156893
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudoku/game/SudokuGame.java
@@ -0,0 +1,228 @@
+package com.example.sudoku.game;
+
+public class SudokuGame {
+ public static final int GRID_SIZE = 9;
+ private Cell[][] board;
+
+ private int selectedRow = -1; // To keep track of the selected cell
+ private int selectedCol = -1;
+
+ public SudokuGame() {
+ board = new Cell[GRID_SIZE][GRID_SIZE];
+ initializeEmptyBoard();
+ }
+
+ public void initializeEmptyBoard() {
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ board[r][c] = new Cell(r, c, 0); // Initialize with empty, editable cells
+ }
+ }
+ selectedRow = -1;
+ selectedCol = -1;
+ }
+
+ public Cell getCell(int row, int col) {
+ if (row >= 0 && row < GRID_SIZE && col >= 0 && col < GRID_SIZE) {
+ return board[row][col];
+ }
+ return null; // Or throw an exception
+ }
+
+ // Sets the value of a cell, if it's editable
+ public boolean setCellValue(int row, int col, int value) {
+ if (row >= 0 && row < GRID_SIZE && col >= 0 && col < GRID_SIZE) {
+ Cell cell = board[row][col];
+ if (cell.isEditable()) {
+ cell.setValue(value);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Overload to directly take a Cell object (e.g. from UI selection)
+ public boolean setCellValue(Cell cell, int value) {
+ if (cell != null && cell.isEditable()) {
+ cell.setValue(value);
+ return true;
+ }
+ return false;
+ }
+
+ // Method to load a puzzle from a 2D integer array
+ public void loadPuzzle(int[][] puzzle) {
+ if (puzzle == null || puzzle.length != GRID_SIZE || puzzle[0].length != GRID_SIZE) {
+ // Optionally throw an IllegalArgumentException or handle error
+ initializeEmptyBoard(); // Fallback to empty board
+ return;
+ }
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ board[r][c] = new Cell(r, c, puzzle[r][c]);
+ }
+ }
+ selectedRow = -1;
+ selectedCol = -1;
+ }
+
+ public int getSelectedRow() {
+ return selectedRow;
+ }
+
+ public void setSelectedRow(int selectedRow) {
+ this.selectedRow = selectedRow;
+ }
+
+ public int getSelectedCol() {
+ return selectedCol;
+ }
+
+ public void setSelectedCol(int selectedCol) {
+ this.selectedCol = selectedCol;
+ }
+
+ public void selectCell(int row, int col) {
+ if (row >= 0 && row < GRID_SIZE) {
+ selectedRow = row;
+ } else {
+ selectedRow = -1;
+ }
+ if (col >= 0 && col < GRID_SIZE) {
+ selectedCol = col;
+ } else {
+ selectedCol = -1;
+ }
+ }
+
+ public Cell getSelectedCell() {
+ if (selectedRow != -1 && selectedCol != -1) {
+ return getCell(selectedRow, selectedCol);
+ }
+ return null;
+ }
+
+ // Placeholder for a simple puzzle for testing
+ public static int[][] getSamplePuzzle() {
+ return new int[][]{
+ {5, 3, 0, 0, 7, 0, 0, 0, 0},
+ {6, 0, 0, 1, 9, 5, 0, 0, 0},
+ {0, 9, 8, 0, 0, 0, 0, 6, 0},
+ {8, 0, 0, 0, 6, 0, 0, 0, 3},
+ {4, 0, 0, 8, 0, 3, 0, 0, 1},
+ {7, 0, 0, 0, 2, 0, 0, 0, 6},
+ {0, 6, 0, 0, 0, 0, 2, 8, 0},
+ {0, 0, 0, 4, 1, 9, 0, 0, 5},
+ {0, 0, 0, 0, 8, 0, 0, 7, 9}
+ };
+ }
+
+ // --- Add these methods to the existing SudokuGame.java class ---
+
+ public boolean isValidMove(int row, int col, int value) {
+ if (value == 0) return true; // Clearing a cell is always valid if it's editable
+ // Check if the number is already in the row, column, or 3x3 subgrid,
+ // excluding the cell itself if it's the one being checked.
+ for (int i = 0; i < GRID_SIZE; i++) {
+ // Check row
+ if (i != col && board[row][i].getValue() == value) return false;
+ // Check column
+ if (i != row && board[i][col].getValue() == value) return false;
+ }
+
+ // Check 3x3 subgrid
+ int subgridStartRow = row - row % 3;
+ int subgridStartCol = col - col % 3;
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 3; c++) {
+ int currentRow = subgridStartRow + r;
+ int currentCol = subgridStartCol + c;
+ if (currentRow == row && currentCol == col) continue; // Skip the cell itself
+ if (board[currentRow][currentCol].getValue() == value) return false;
+ }
+ }
+ return true;
+ }
+
+ // More specific checks if needed, though isValidMove covers these.
+ // These can be useful for providing specific feedback to the user.
+
+ private boolean isNumberInRow(int row, int colToExclude, int number) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ if (c == colToExclude) continue;
+ if (board[row][c].getValue() == number) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isNumberInCol(int col, int rowToExclude, int number) {
+ for (int r = 0; r < GRID_SIZE; r++) {
+ if (r == rowToExclude) continue;
+ if (board[r][col].getValue() == number) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isNumberInSubgrid(int startRow, int startCol, int rowToExclude, int colToExclude, int number) {
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 3; c++) {
+ int currentRow = startRow + r;
+ int currentCol = startCol + c;
+ if (currentRow == rowToExclude && currentCol == colToExclude) continue;
+ if (board[currentRow][currentCol].getValue() == number) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isBoardSolved() {
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ int value = board[r][c].getValue();
+ if (value == 0) {
+ return false; // Found an empty cell, so not solved
+ }
+ // Check if this number is valid in its row, column, and subgrid
+ // considering other numbers but not itself initially
+ for (int i = 0; i < GRID_SIZE; i++) {
+ // Check row (excluding itself)
+ if (i != c && board[r][i].getValue() == value) return false;
+ // Check col (excluding itself)
+ if (i != r && board[i][c].getValue() == value) return false;
+ }
+ // Check 3x3 subgrid (excluding itself)
+ int subgridStartRow = r - r % 3;
+ int subgridStartCol = c - c % 3;
+ for (int sr = 0; sr < 3; sr++) {
+ for (int sc = 0; sc < 3; sc++) {
+ int currentRow = subgridStartRow + sr;
+ int currentCol = subgridStartCol + sc;
+ if (currentRow == r && currentCol == c) continue;
+ if (board[currentRow][currentCol].getValue() == value) return false;
+ }
+ }
+ }
+ }
+ return true; // All cells are filled and valid according to Sudoku rules
+ }
+
+ public void clearEditableCells() { // Renamed for clarity
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ if (board[r][c].isEditable()) {
+ board[r][c].setValue(0);
+ }
+ }
+ }
+ selectedRow = -1;
+ selectedCol = -1;
+ }
+
+ // --- End of methods to add ---
+}
diff --git a/SudokuApp/app/src/main/java/com/example/sudoku/game/SudokuGenerator.java b/SudokuApp/app/src/main/java/com/example/sudoku/game/SudokuGenerator.java
new file mode 100644
index 0000000..1b99918
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudoku/game/SudokuGenerator.java
@@ -0,0 +1,137 @@
+package com.example.sudoku.game;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+public class SudokuGenerator {
+
+ private static final int GRID_SIZE = SudokuGame.GRID_SIZE;
+ private Random random = new Random();
+
+ // Main method to generate a new puzzle and set it on the game object
+ public void generateNewPuzzle(SudokuGame game, int numbersToRemove) {
+ game.initializeEmptyBoard(); // Start with a clean board
+ fillBoard(game.getBoard()); // Fill it with a valid Sudoku solution
+
+ // Remove numbers to create the puzzle
+ pokeHoles(game.getBoard(), numbersToRemove);
+
+ // Update cell properties (isStartingCell, isEditable) based on the final puzzle
+ // This loop is actually redundant if SudokuGame.loadPuzzle is called afterwards,
+ // or if the Cell constructor logic (value != 0 means !isEditable) is solely relied upon.
+ // However, it can be kept for explicit clarity or if direct manipulation is preferred.
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ Cell cell = game.getCell(r,c);
+ // The Cell constructor should correctly set isStartingCell based on whether value is 0.
+ // If value is non-zero, it's a starting cell, hence not editable.
+ // If value is zero, it's an empty cell, hence editable.
+ // So, re-setting isEditable or isStartingCell here might be redundant
+ // if the game's loadPuzzle method or Cell constructors handle this.
+ // For example, if SudokuGame.loadPuzzle(boardValues) is called after this,
+ // it would create new Cell objects, correctly setting their states.
+ // Let's assume the Cell constructor logic is sufficient.
+ // cell.setEditable(cell.getValue() == 0);
+ // if (cell.getValue() != 0) { cell.isStartingCell = true; } // Simplified
+ }
+ }
+ // After poking holes, the SudokuGame's internal board (Cell[][]) has 0s for empty cells.
+ // If SudokuGame.loadPuzzle() is called with the integer values from this board,
+ // it will correctly initialize the Cell objects, setting isEditable and isStartingCell.
+ }
+
+ // Fills the board using a backtracking algorithm
+ private boolean fillBoard(Cell[][] board) {
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ if (board[r][c].getValue() == 0) { // Find an empty cell
+ List numbers = new ArrayList<>();
+ for (int i = 1; i <= GRID_SIZE; i++) {
+ numbers.add(i);
+ }
+ Collections.shuffle(numbers, random); // Randomize order of numbers to try
+
+ for (int num : numbers) {
+ if (isSafe(board, r, c, num)) {
+ board[r][c].setValue(num); // Place number
+ if (fillBoard(board)) { // Recurse
+ return true; // Solution found
+ }
+ board[r][c].setValue(0); // Backtrack: undo placement
+ }
+ }
+ return false; // No valid number found for this cell, trigger backtrack
+ }
+ }
+ }
+ return true; // All cells filled
+ }
+
+ // Checks if a number can be safely placed in a cell
+ private boolean isSafe(Cell[][] board, int row, int col, int num) {
+ // Check row
+ for (int c = 0; c < GRID_SIZE; c++) {
+ if (board[row][c].getValue() == num) return false;
+ }
+ // Check column
+ for (int r = 0; r < GRID_SIZE; r++) {
+ if (board[r][col].getValue() == num) return false;
+ }
+ // Check 3x3 subgrid
+ int subgridStartRow = row - row % 3;
+ int subgridStartCol = col - col % 3;
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 3; c++) {
+ if (board[subgridStartRow + r][subgridStartCol + c].getValue() == num) return false;
+ }
+ }
+ return true;
+ }
+
+ // Removes numbers from the filled board to create the puzzle
+ private void pokeHoles(Cell[][] board, int holesToMake) {
+ int cellsToRemove = holesToMake;
+ if (cellsToRemove <= 0 || cellsToRemove >= GRID_SIZE * GRID_SIZE) {
+ cellsToRemove = GRID_SIZE * GRID_SIZE / 2; // Default to removing half if invalid
+ }
+
+ List cellCoordinates = new ArrayList<>();
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ cellCoordinates.add(new int[]{r, c});
+ }
+ }
+ Collections.shuffle(cellCoordinates, random);
+
+ int removedCount = 0;
+ for (int[] coord : cellCoordinates) {
+ if (removedCount >= cellsToRemove) break;
+ int r = coord[0];
+ int c = coord[1];
+ if (board[r][c].getValue() != 0) {
+ // Directly set the value of the Cell object to 0.
+ // The Cell's isEditable property should ideally be updated by SudokuGame
+ // when it processes this modified board (e.g., by calling loadPuzzle).
+ // The Cell constructor (new Cell(r, c, 0)) makes it editable.
+ board[r][c].setValue(0);
+ // After setting value to 0, the SudokuGame should ensure this cell
+ // becomes editable. This is handled by Cell's constructor if SudokuGame.loadPuzzle
+ // is called, or if Cell.setValue(0) implies it becomes editable.
+ // The Cell.setValue() method in Cell.java only allows changing value if editable.
+ // This means for pokeHoles to work as intended (making cells empty and thus editable),
+ // we might need to adjust Cell.setValue or ensure cells are made editable here.
+ // For now, we assume SudokuGame will reconstruct/update cells based on the new values.
+ // A simpler approach for Cell: setValue(0) makes it editable.
+ // Or, SudokuGame after calling generator:
+ // for each cell: if cell.value == 0, cell.isEditable = true, cell.isStarting = false
+ // else cell.isEditable = false, cell.isStarting = true.
+ // The current Cell constructor (new Cell(r,c,value)) handles this:
+ // isStartingCell = (value != 0), isEditable = !isStartingCell.
+ // So if SudokuGame.loadPuzzle() is called with the int values, it's fine.
+ removedCount++;
+ }
+ }
+ }
+}
diff --git a/SudokuApp/app/src/main/java/com/example/sudoku/utils/SudokuSolver.java b/SudokuApp/app/src/main/java/com/example/sudoku/utils/SudokuSolver.java
new file mode 100644
index 0000000..bfa091e
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudoku/utils/SudokuSolver.java
@@ -0,0 +1,128 @@
+package com.example.sudoku.utils;
+
+import com.example.sudoku.game.Cell;
+import com.example.sudoku.game.SudokuGame;
+
+public class SudokuSolver {
+
+ private static final int GRID_SIZE = SudokuGame.GRID_SIZE;
+
+ public boolean solve(SudokuGame game) {
+ if (game == null || game.getBoard() == null) {
+ return false;
+ }
+ // This method should operate on the Cell[][] array directly from the game.
+ // It is crucial that SudokuSolver modifies the actual Cell objects within the SudokuGame's board,
+ // so that the game state reflects the solution.
+ // The SudokuGame.getBoard() method should return the reference to its internal Cell[][] board.
+ return solveSudoku(game.getBoard());
+ }
+
+ private boolean solveSudoku(Cell[][] board) {
+ int[] emptyLoc = findEmptyLocation(board);
+ if (emptyLoc == null) {
+ return true; // Board is already solved (no empty cells)
+ }
+
+ int row = emptyLoc[0];
+ int col = emptyLoc[1];
+
+ Cell currentCell = board[row][col];
+
+ // Important: Solver should only try to fill cells that are originally empty or user-filled.
+ // It should not overwrite pre-filled (isStartingCell) numbers.
+ // However, the current findEmptyLocation only looks for cells with value 0.
+ // If a user incorrectly fills a cell and then asks for a solve, the solver might
+ // get stuck or produce an incorrect result if it doesn't respect original puzzle cells.
+ // For now, we assume findEmptyLocation correctly identifies cells that can be part of solving.
+ // The provided Cell.setValue() method already checks 'isEditable'. If a cell is a starting cell,
+ // setValue will not change it. This is good.
+ // So, if findEmptyLocation returns a starting cell (because its value somehow became 0, which shouldn't happen),
+ // setValue would prevent modification.
+ // The current logic is: find cell with value 0. If it's a starting cell (immutable), setValue does nothing.
+ // This would lead to solveSudoku returning false for that path, which is correct.
+
+ for (int num = 1; num <= GRID_SIZE; num++) {
+ if (isSafe(board, row, col, num)) {
+ // Before setting value, check if the cell is actually editable.
+ // While findEmptyLocation implies it's empty (value 0), and Cell constructor
+ // would make value 0 cells editable, this check adds robustness.
+ // However, the current Cell.setValue() already handles this.
+ // currentCell.setValue(num); // Try placing the number
+
+ // We directly call setValue on the cell object.
+ // The Cell's own logic will determine if it can be set (i.e. if it's editable).
+ // If it's a starting cell, setValue will do nothing, and the condition below will fail.
+ // This is the correct behavior.
+
+ // Check if the cell is editable (not a starting cell) before attempting to set a value
+ // findEmptyLocation should only return cells with value 0, which are by default editable
+ // unless they were starting cells that somehow got reset to 0 (which is unlikely).
+ // The Cell.setValue() method itself checks for editability.
+
+ // currentCell.setValue(num);
+ // if (currentCell.getValue() != num) { // If setValue failed (e.g. not editable)
+ // continue; // Try next number or backtrack if this was the only way
+ // }
+ // The above check is redundant if setValue correctly handles non-editable cells by not changing them.
+
+ // Let's assume currentCell is indeed empty and thus editable.
+ currentCell.setValue(num);
+
+
+ if (solveSudoku(board)) { // Recurse
+ return true; // Solution found
+ }
+
+ // If solution not found with this number, backtrack.
+ // Reset the cell only if it was this instance of solveSudoku that set it.
+ // And only if it's editable.
+ currentCell.setValue(0); // Backtrack
+ }
+ }
+ return false; // No number from 1-9 works for this cell, trigger backtrack
+ }
+
+ private int[] findEmptyLocation(Cell[][] board) {
+ for (int r = 0; r < GRID_SIZE; r++) {
+ for (int c = 0; c < GRID_SIZE; c++) {
+ // Only consider cells that are empty (value 0).
+ // The solver should fill these empty cells.
+ // By design, a cell with value 0 is considered editable by the Cell class.
+ if (board[r][c].getValue() == 0) {
+ return new int[]{r, c};
+ }
+ }
+ }
+ return null; // No empty cell found
+ }
+
+ // isSafe needs to check the values from the Cell objects in the board
+ private boolean isSafe(Cell[][] board, int row, int col, int num) {
+ // Check row
+ for (int c = 0; c < GRID_SIZE; c++) {
+ if (board[row][c].getValue() == num) {
+ return false;
+ }
+ }
+
+ // Check column
+ for (int r = 0; r < GRID_SIZE; r++) {
+ if (board[r][col].getValue() == num) {
+ return false;
+ }
+ }
+
+ // Check 3x3 subgrid
+ int subgridStartRow = row - row % 3;
+ int subgridStartCol = col - col % 3;
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 3; c++) {
+ if (board[subgridStartRow + r][subgridStartCol + c].getValue() == num) {
+ return false;
+ }
+ }
+ }
+ return true; // Number is safe to place
+ }
+}
diff --git a/SudokuApp/app/src/main/java/com/example/sudokuapp/MainActivity.java b/SudokuApp/app/src/main/java/com/example/sudokuapp/MainActivity.java
new file mode 100644
index 0000000..65489b5
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudokuapp/MainActivity.java
@@ -0,0 +1,139 @@
+package com.example.sudokuapp;
+
+import androidx.appcompat.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast; // For showing messages
+// No need to import View.OnClickListener if using lambdas or anonymous inner classes for listeners
+// However, if we were to implement OnClickListener on MainActivity, it would be needed.
+// The provided solution uses lambdas, so it's not strictly necessary here.
+// Import game logic classes using their fully qualified names or direct imports
+import com.example.sudoku.game.SudokuGame;
+import com.example.sudoku.game.SudokuGenerator;
+
+public class MainActivity extends AppCompatActivity {
+
+ private SudokuBoardView boardView;
+ private SudokuGame game;
+ private SudokuGenerator generator;
+
+ // Number buttons
+ private Button button1, button2, button3, button4, button5;
+ private Button button6, button7, button8, button9, buttonErase;
+ private Button buttonNewGame;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ boardView = findViewById(R.id.sudokuBoardView);
+ buttonNewGame = findViewById(R.id.buttonNewGame);
+
+ // Initialize number pad buttons
+ initializeNumberButtons(); // Finds views for number buttons
+ setupNumberButtonListeners(); // Sets listeners for number buttons
+
+ game = new SudokuGame();
+ generator = new SudokuGenerator();
+
+ startNewGame(); // Start a new game on creation
+
+ buttonNewGame.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startNewGame();
+ }
+ });
+
+ // Optional: Listener for cell selection from SudokuBoardView
+ // boardView.setOnCellSelectedListener(new SudokuBoardView.OnCellSelectedListener() {
+ // @Override
+ // public void onCellSelected(int row, int col) {
+ // // Handle cell selection if needed (e.g., update UI to show selected number)
+ // // For now, SudokuBoardView handles visual selection, game state is updated.
+ // }
+ // });
+ // Call to setupNumberButtonListeners() already added above as per instruction
+ }
+
+ private void startNewGame() {
+ // Difficulty: number of cells to remove. Higher is harder.
+ // Max is GRID_SIZE*GRID_SIZE. Sensible might be 20-50.
+ int numbersToRemove = 30; // Example difficulty
+
+ // The generator modifies the 'game' object's board directly.
+ // So, we first need to ensure the game object has a fresh board structure if needed.
+ game.initializeEmptyBoard(); // Ensure cells are fresh for the generator
+ generator.generateNewPuzzle(game, numbersToRemove);
+
+ // After generation, the game object's board is filled with the puzzle.
+ // SudokuBoardView needs this game object to draw.
+ boardView.setGame(game);
+ boardView.invalidate(); // Ensure redraw
+ }
+
+ private void initializeNumberButtons() {
+ button1 = findViewById(R.id.button1);
+ button2 = findViewById(R.id.button2);
+ button3 = findViewById(R.id.button3);
+ button4 = findViewById(R.id.button4);
+ button5 = findViewById(R.id.button5);
+ button6 = findViewById(R.id.button6);
+ button7 = findViewById(R.id.button7);
+ button8 = findViewById(R.id.button8);
+ button9 = findViewById(R.id.button9);
+ buttonErase = findViewById(R.id.buttonErase);
+ // Listeners for these will be set up in Part 2 of MainActivity implementation
+ }
+
+ private void setupNumberButtonListeners() {
+ View.OnClickListener numberClickListener = v -> {
+ if (game == null || game.getSelectedCell() == null) return;
+ // Ensure the cell is editable (not a starting cell)
+ if (!game.getSelectedCell().isEditable()) {
+ Toast.makeText(this, "This cell is not editable.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ Button clickedButton = (Button) v;
+ // Ensure tag is not null before parsing. XML tags are set to "1" through "9".
+ if (clickedButton.getTag() == null) return;
+ int number = Integer.parseInt(clickedButton.getTag().toString());
+
+ // Optional: Add validation using game.isValidMove(row, col, number) before setting
+ // if (!game.isValidMove(game.getSelectedRow(), game.getSelectedCol(), number)) {
+ // Toast.makeText(this, "Invalid move.", Toast.LENGTH_SHORT).show();
+ // return;
+ // }
+
+ game.setCellValue(game.getSelectedCell(), number);
+ boardView.invalidate(); // Redraw board
+
+ if (game.isBoardSolved()) {
+ Toast.makeText(this, getString(R.string.game_solved_message), Toast.LENGTH_LONG).show();
+ }
+ };
+
+ button1.setOnClickListener(numberClickListener);
+ button2.setOnClickListener(numberClickListener);
+ button3.setOnClickListener(numberClickListener);
+ button4.setOnClickListener(numberClickListener);
+ button5.setOnClickListener(numberClickListener);
+ button6.setOnClickListener(numberClickListener);
+ button7.setOnClickListener(numberClickListener);
+ button8.setOnClickListener(numberClickListener);
+ button9.setOnClickListener(numberClickListener);
+
+ buttonErase.setOnClickListener(v -> {
+ if (game == null || game.getSelectedCell() == null) return;
+ if (!game.getSelectedCell().isEditable()) {
+ Toast.makeText(this, "This cell is not editable.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ game.setCellValue(game.getSelectedCell(), 0); // 0 means erase
+ boardView.invalidate(); // Redraw board
+ });
+ }
+}
diff --git a/SudokuApp/app/src/main/java/com/example/sudokuapp/SudokuBoardView.java b/SudokuApp/app/src/main/java/com/example/sudokuapp/SudokuBoardView.java
new file mode 100644
index 0000000..3829da4
--- /dev/null
+++ b/SudokuApp/app/src/main/java/com/example/sudokuapp/SudokuBoardView.java
@@ -0,0 +1,208 @@
+package com.example.sudokuapp;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent; // Import MotionEvent
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.example.sudoku.game.Cell; // Import from our game logic package
+import com.example.sudoku.game.SudokuGame; // Import from our game logic package
+
+public class SudokuBoardView extends View {
+
+ private SudokuGame game;
+ private Paint majorGridPaint;
+ private Paint minorGridPaint;
+ private Paint numberTextPaint;
+ private Paint startingNumberTextPaint; // For pre-filled numbers
+ private Paint selectedCellPaint;
+ private Paint highlightedCellPaint; // For row/col/subgrid of selected cell
+
+ private int cellSize;
+ private final Rect textBounds = new Rect(); // For centering text
+
+ // (Interface can be outside or a static inner interface)
+ public interface OnCellSelectedListener {
+ void onCellSelected(int row, int col);
+ }
+
+ private OnCellSelectedListener cellSelectedListener;
+
+ public void setOnCellSelectedListener(OnCellSelectedListener listener) {
+ this.cellSelectedListener = listener;
+ }
+
+ public SudokuBoardView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ initPaints();
+ }
+
+ private void initPaints() {
+ majorGridPaint = new Paint();
+ majorGridPaint.setColor(Color.BLACK);
+ majorGridPaint.setStyle(Paint.Style.STROKE);
+ majorGridPaint.setStrokeWidth(4f); // Thicker lines for major grid lines
+
+ minorGridPaint = new Paint();
+ minorGridPaint.setColor(Color.GRAY);
+ minorGridPaint.setStyle(Paint.Style.STROKE);
+ minorGridPaint.setStrokeWidth(1f); // Thinner lines for minor grid lines
+
+ numberTextPaint = new Paint();
+ numberTextPaint.setColor(Color.BLUE); // User entered numbers
+ numberTextPaint.setTextAlign(Paint.Align.CENTER);
+ // Text size will be set in onSizeChanged or onMeasure
+
+ startingNumberTextPaint = new Paint();
+ startingNumberTextPaint.setColor(Color.BLACK); // Pre-filled numbers
+ startingNumberTextPaint.setTextAlign(Paint.Align.CENTER);
+ startingNumberTextPaint.setFakeBoldText(true);
+ // Text size will be set in onSizeChanged or onMeasure
+
+ selectedCellPaint = new Paint();
+ selectedCellPaint.setColor(getContext().getColor(R.color.selected_cell_background)); // From colors.xml
+ selectedCellPaint.setStyle(Paint.Style.FILL);
+
+ highlightedCellPaint = new Paint();
+ highlightedCellPaint.setColor(getContext().getColor(R.color.highlighted_cell_background)); // From colors.xml
+ highlightedCellPaint.setStyle(Paint.Style.FILL);
+ }
+
+ public void setGame(SudokuGame game) {
+ this.game = game;
+ invalidate(); // Redraw the view with the new game state
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ int size = Math.min(width, height);
+ setMeasuredDimension(size, size); // Make the view square
+
+ // Calculate cell size and text size here, after view dimensions are known
+ cellSize = size / SudokuGame.GRID_SIZE;
+ float textSize = cellSize * 0.6f; // Example: 60% of cell size
+ numberTextPaint.setTextSize(textSize);
+ startingNumberTextPaint.setTextSize(textSize);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (canvas == null) return;
+
+ // Draw cell highlights (selected and related)
+ drawCellHighlights(canvas);
+
+ // Draw grid lines
+ drawGridLines(canvas);
+
+ // Draw numbers
+ drawNumbers(canvas);
+ }
+
+ private void drawCellHighlights(Canvas canvas) {
+ if (game == null || game.getSelectedRow() == -1 || game.getSelectedCol() == -1) {
+ return;
+ }
+
+ int selRow = game.getSelectedRow();
+ int selCol = game.getSelectedCol();
+
+ // Highlight the selected cell itself
+ canvas.drawRect(selCol * cellSize, selRow * cellSize,
+ (selCol + 1) * cellSize, (selRow + 1) * cellSize,
+ selectedCellPaint);
+
+ // Highlight row, column, and subgrid
+ for (int i = 0; i < SudokuGame.GRID_SIZE; i++) {
+ // Row & Col (excluding selected cell itself)
+ if (i != selCol) canvas.drawRect(i * cellSize, selRow * cellSize, (i + 1) * cellSize, (selRow + 1) * cellSize, highlightedCellPaint);
+ if (i != selRow) canvas.drawRect(selCol * cellSize, i * cellSize, (selCol + 1) * cellSize, (i + 1) * cellSize, highlightedCellPaint);
+ }
+ // Subgrid (excluding selected cell itself and already highlighted row/col cells)
+ int subgridStartRow = selRow - selRow % 3;
+ int subgridStartCol = selCol - selCol % 3;
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 3; c++) {
+ int curR = subgridStartRow + r;
+ int curC = subgridStartCol + c;
+ if (curR != selRow && curC != selCol) { // Avoid double-highlighting direct row/col
+ canvas.drawRect(curC * cellSize, curR * cellSize,
+ (curC + 1) * cellSize, (curR + 1) * cellSize,
+ highlightedCellPaint);
+ }
+ }
+ }
+ }
+
+
+ private void drawGridLines(Canvas canvas) {
+ int width = getWidth(); // or getHeight(), since it's square
+
+ // Draw minor lines
+ for (int i = 0; i <= SudokuGame.GRID_SIZE; i++) {
+ float position = i * cellSize;
+ Paint currentPaint = (i % 3 == 0) ? majorGridPaint : minorGridPaint;
+ canvas.drawLine(0, position, width, position, currentPaint); // Horizontal
+ canvas.drawLine(position, 0, position, width, currentPaint); // Vertical
+ }
+ }
+
+ private void drawNumbers(Canvas canvas) {
+ if (game == null) return;
+
+ for (int r = 0; r < SudokuGame.GRID_SIZE; r++) {
+ for (int c = 0; c < SudokuGame.GRID_SIZE; c++) {
+ Cell cell = game.getCell(r, c);
+ if (cell != null && cell.getValue() != 0) {
+ String text = String.valueOf(cell.getValue());
+ Paint currentTextPaint = cell.isStartingCell() ? startingNumberTextPaint : numberTextPaint;
+
+ // Center text in cell
+ currentTextPaint.getTextBounds(text, 0, text.length(), textBounds);
+ float x = c * cellSize + (cellSize / 2f);
+ float y = r * cellSize + (cellSize / 2f) - textBounds.exactCenterY();
+ canvas.drawText(text, x, y, currentTextPaint);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (game == null) {
+ return super.onTouchEvent(event);
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ if (cellSize > 0) { // Ensure cellSize is initialized
+ int col = x / cellSize;
+ int row = y / cellSize;
+
+ if (col < SudokuGame.GRID_SIZE && row < SudokuGame.GRID_SIZE) {
+ game.selectCell(row, col);
+ if (cellSelectedListener != null) {
+ cellSelectedListener.onCellSelected(row, col);
+ }
+ invalidate(); // Request a redraw to show selection
+ return true; // Event handled
+ }
+ }
+ }
+ return super.onTouchEvent(event);
+ }
+
+ // --- End of additions ---
+}
diff --git a/SudokuApp/app/src/main/res/drawable/ic_launcher_background.xml b/SudokuApp/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..b4c6944
--- /dev/null
+++ b/SudokuApp/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/SudokuApp/app/src/main/res/drawable/ic_launcher_foreground.xml b/SudokuApp/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..4493888
--- /dev/null
+++ b/SudokuApp/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/SudokuApp/app/src/main/res/layout/activity_main.xml b/SudokuApp/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..aaa8e41
--- /dev/null
+++ b/SudokuApp/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SudokuApp/app/src/main/res/mipmap-anydpi-v26/.gitkeep b/SudokuApp/app/src/main/res/mipmap-anydpi-v26/.gitkeep
new file mode 100644
index 0000000..0fdedc7
--- /dev/null
+++ b/SudokuApp/app/src/main/res/mipmap-anydpi-v26/.gitkeep
@@ -0,0 +1 @@
+# This file is used to ensure the directory is tracked by git.
diff --git a/SudokuApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/SudokuApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..d378acd
--- /dev/null
+++ b/SudokuApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/SudokuApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/SudokuApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..35ff360
--- /dev/null
+++ b/SudokuApp/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+ {/* Optional for Android 13+ themed icons */}
+
diff --git a/SudokuApp/app/src/main/res/values-night/themes.xml b/SudokuApp/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..2e3576e
--- /dev/null
+++ b/SudokuApp/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/SudokuApp/app/src/main/res/values/colors.xml b/SudokuApp/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4d600ee
--- /dev/null
+++ b/SudokuApp/app/src/main/res/values/colors.xml
@@ -0,0 +1,15 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+ #FF666666
+ #FF000000
+ #FF333333
+ #400000FF {/* Light blue transparent */}
+ #2000FF00 {/* Light green transparent */}
+
diff --git a/SudokuApp/app/src/main/res/values/strings.xml b/SudokuApp/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..01f47e5
--- /dev/null
+++ b/SudokuApp/app/src/main/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+ SudokuApp
+ New Game
+ Erase
+ Hint
+ Check
+ Congratulations! You solved the puzzle!
+ Keep trying! The puzzle is not yet solved.
+ Confirm
+ Cancel
+ Select Number
+
diff --git a/SudokuApp/app/src/main/res/values/themes.xml b/SudokuApp/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..94a6d7a
--- /dev/null
+++ b/SudokuApp/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/SudokuApp/app/src/main/res/xml/backup_rules.xml b/SudokuApp/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..622ca70
--- /dev/null
+++ b/SudokuApp/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/SudokuApp/app/src/main/res/xml/data_extraction_rules.xml b/SudokuApp/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..08cf870
--- /dev/null
+++ b/SudokuApp/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/SudokuApp/build.gradle b/SudokuApp/build.gradle
new file mode 100644
index 0000000..a5f1103
--- /dev/null
+++ b/SudokuApp/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.4.2' // Example version, can be updated
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/SudokuApp/gradle.properties b/SudokuApp/gradle.properties
new file mode 100644
index 0000000..ef05a4d
--- /dev/null
+++ b/SudokuApp/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/jetpack/androidx/
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
diff --git a/SudokuApp/gradle/wrapper/gradle-wrapper.properties b/SudokuApp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..5412bd9
--- /dev/null
+++ b/SudokuApp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+#Mon Dec 28 10:00:00 PST 2020
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https://services.gradle.org/distributions/gradle-7.5-all.zip
+// Example version, can be updated
diff --git a/SudokuApp/gradlew b/SudokuApp/gradlew
new file mode 100644
index 0000000..7650765
--- /dev/null
+++ b/SudokuApp/gradlew
@@ -0,0 +1 @@
+# This is an empty file. The user will generate the actual content later.
diff --git a/SudokuApp/gradlew.bat b/SudokuApp/gradlew.bat
new file mode 100644
index 0000000..861f49e
--- /dev/null
+++ b/SudokuApp/gradlew.bat
@@ -0,0 +1 @@
+@rem This is an empty file. The user will generate the actual content later.
diff --git a/SudokuApp/settings.gradle b/SudokuApp/settings.gradle
new file mode 100644
index 0000000..67824d8
--- /dev/null
+++ b/SudokuApp/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "SudokuApp"
+include ':app'