diff --git a/app/src/main/java/tu_darmstadt/sudoku/controller/GameController.java b/app/src/main/java/tu_darmstadt/sudoku/controller/GameController.java index a65fea2..aac942d 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/controller/GameController.java +++ b/app/src/main/java/tu_darmstadt/sudoku/controller/GameController.java @@ -9,7 +9,7 @@ import java.util.Timer; import java.util.TimerTask; import java.util.logging.Handler; -import tu_darmstadt.sudoku.controller.generator.Generator; +import tu_darmstadt.sudoku.controller.solver.Solver; import tu_darmstadt.sudoku.game.CellConflict; import tu_darmstadt.sudoku.game.CellConflictList; import tu_darmstadt.sudoku.game.GameBoard; @@ -42,11 +42,13 @@ public class GameController implements IModelChangedListener { private CellConflictList errorList = new CellConflictList(); private int selectedValue; private LinkedList solvedListeners = new LinkedList<>(); + private boolean notifiedOnSolvedListeners = false; private Timer timer; private android.os.Handler handler = new android.os.Handler(); private TimerTask timerTask; private int time = 0; private LinkedList timerListeners = new LinkedList<>(); + private boolean timerRunning = false; // private Solver solver; // private SudokuGenerator generator; @@ -70,8 +72,11 @@ public class GameController implements IModelChangedListener { return gameID; } - public void loadNewLevel(GameType type, int difficulty) { - Generator generator = new Generator(); + public void loadNewLevel(GameType type, GameDifficulty difficulty) { + //Generator generator = new Generator(type, difficulty); + //GameBoard randomBoard = generator.getGameBoard(); + + // TODO call methods to generate level. switch(type) { @@ -85,7 +90,7 @@ public class GameController implements IModelChangedListener { 0,3,0,5,0,1}, null,null)); break; case Default_12x12: - loadLevel(new GameInfoContainer(2, GameDifficulty.Easy, GameType.Default_12x12, + loadLevel(new GameInfoContainer(2, GameDifficulty.Hard, GameType.Default_12x12, new int[] {0, 2, 1, 0, 0, 6, 0, 0, 0, 8, 9, 0, 10, 0,12, 0, 0, 2, 1,11, 0, 0, 0, 6, 6, 0, 0, 4, 0,12, 0, 0, 0, 0, 2, 1, @@ -103,7 +108,7 @@ public class GameController implements IModelChangedListener { case Default_9x9: case Unspecified: default: - loadLevel(new GameInfoContainer(3, GameDifficulty.Easy, GameType.Default_9x9, + loadLevel(new GameInfoContainer(3, GameDifficulty.Moderate, GameType.Default_9x9, new int[]{5, 0, 1, 9, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 4, 9, 5, 0, 3, 9, 0, 7, 0, 0, 0, 2, 6, @@ -117,12 +122,17 @@ public class GameController implements IModelChangedListener { } } + public int getTime() { + return time; + } + public void loadLevel(GameInfoContainer gic) { int[] fixedValues = gic.getFixedValues(); int[] setValues = gic.getSetValues(); boolean[][] setNotes = gic.getSetNotes(); this.gameID = gic.getID(); this.difficulty = gic.getDifficulty(); + this.time = gic.getTime(); setGameType(gic.getGameType()); this.gameBoard = new GameBoard(gic.getGameType()); @@ -245,7 +255,7 @@ public class GameController implements IModelChangedListener { } public T actionOnCells(ICellAction ca, T existing) { - return gameBoard.actionOnCells(ca,existing); + return gameBoard.actionOnCells(ca, existing); } public boolean checkIfBoardIsFilled() { @@ -261,7 +271,12 @@ public class GameController implements IModelChangedListener { gameID = settings.getInt("lastGameID", 0)+1; SharedPreferences.Editor editor = settings.edit(); - editor.putInt("lastGameID", gameID); + // is anyone ever gonna play so many levels? :) + if(gameID == Integer.MAX_VALUE-1) { + editor.putInt("lastGameID", 1); + } else { + editor.putInt("lastGameID", gameID); + } editor.commit(); } @@ -414,11 +429,19 @@ public class GameController implements IModelChangedListener { if(gameBoard.isFilled()) { List errorList = new LinkedList<>(); if(gameBoard.isSolved(errorList)) { - notifySolvedListeners(); + if(!notifiedOnSolvedListeners) { + notifiedOnSolvedListeners = true; + notifySolvedListeners(); + //TODO disable controls and play animation in view. onSolved method is called. + } } else { + //notifiedOnSolvedListeners = false; + // TODO: errorList now holds all the errors // TODO: display errors .. notify some view? } + } else { + notifiedOnSolvedListeners = false; } } @@ -451,30 +474,35 @@ public class GameController implements IModelChangedListener { } } - public void initTimer() { - + private void initTimer() { timerTask = new TimerTask() { @Override public void run() { handler.post(new Runnable() { @Override public void run() { - notifyTimerListener(time++); + if(timerRunning) { + notifyTimerListener(time++); + } } }); } }; timer = new Timer(); - } - - public void startTimer() { - timer.scheduleAtFixedRate(timerTask,0,1000); } + public void startTimer() { + if(!timerRunning) { + timerRunning = true; + } + } + public void pauseTimer(){ - timer.cancel(); + if(timerRunning) { + timerRunning = false; + } } } diff --git a/app/src/main/java/tu_darmstadt/sudoku/controller/SaveLoadController.java b/app/src/main/java/tu_darmstadt/sudoku/controller/SaveLoadController.java index d558963..c8c0976 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/controller/SaveLoadController.java +++ b/app/src/main/java/tu_darmstadt/sudoku/controller/SaveLoadController.java @@ -76,11 +76,15 @@ public class SaveLoadController { // fill the container String id = file.getName().substring(5, file.getName().lastIndexOf(".")); + int i = 0; gic.setID(Integer.valueOf(id)); // save_x.txt - gic.parseGameType(values[0]); - gic.parseFixedValues(values[1]); - gic.parseSetValues(values[2]); - gic.parseNotes(values[3]); + gic.parseGameType(values[i++]); + gic.parseTime(values[i++]); + gic.parseDate(values[i++]); + gic.parseDifficulty(values[i++]); + gic.parseFixedValues(values[i++]); + gic.parseSetValues(values[i++]); + gic.parseNotes(values[i++]); } catch(IllegalArgumentException e) { file.delete(); continue; diff --git a/app/src/main/java/tu_darmstadt/sudoku/controller/generator/Generator.java b/app/src/main/java/tu_darmstadt/sudoku/controller/generator/Generator.java index d183f4f..9a2dbb1 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/controller/generator/Generator.java +++ b/app/src/main/java/tu_darmstadt/sudoku/controller/generator/Generator.java @@ -1,8 +1,164 @@ package tu_darmstadt.sudoku.controller.generator; +import android.graphics.Point; +import android.support.v7.util.SortedList; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import tu_darmstadt.sudoku.controller.solver.Solver; +import tu_darmstadt.sudoku.game.GameBoard; +import tu_darmstadt.sudoku.game.GameCell; +import tu_darmstadt.sudoku.game.GameDifficulty; +import tu_darmstadt.sudoku.game.GameType; +import tu_darmstadt.sudoku.game.ICellAction; + /** * Created by Chris on 19.11.2015. */ public class Generator { + private Random random; + private int size; + private int sectionHeight; + private int sectionWidth; + private GameBoard gameBoard; + + public Generator(GameType type, GameDifficulty difficulty) { + this.size = type.getSize(); + this.sectionHeight = type.getSectionHeight(); + this.sectionWidth = type.getSectionWidth(); + this.gameBoard = new GameBoard(type); + gameBoard.initCells(new int[size*size]); + setNotes(gameBoard); + + // generate a random valid board. + gameBoard = generate(); + + // produce a level out of it. + gameBoard = createLevel(difficulty); + } + + public GameBoard createLevel(GameDifficulty difficulty) { + return gameBoard; + } + + public GameBoard getGameBoard() { + return gameBoard; + } + + + public GameBoard generate() { + random = new Random(); + + GameBoard workingBoard = gameBoard; + GameBoard board = gameBoard; + + /*GameCell chosen = gameBoard.getCell(random.nextInt(9), random.nextInt(9)); + while(chosen.getNoteCount() <= 2) { + chosen = gameBoard.getCell(random.nextInt(9), random.nextInt(9)); + }*/ + + while(workingBoard != null && !workingBoard.isFilled()) { + + // clone board + try { + workingBoard = board.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + + // get all candidates + List candidates = getListOfCandidates(workingBoard); + // choose one of them + GameCell chosen = candidates.get(random.nextInt(candidates.size())); + // get all possible values + List possibleValues = getPossibleValues(chosen); + // set a random value of that pool + chosen.setValue(possibleValues.get(random.nextInt(possibleValues.size()))); + + deleteConnectedValues(workingBoard, chosen); + + Solver solver = new Solver(workingBoard); + if (solver.solve(solver.getGameBoard())) { + List solutions = solver.getSolutions(); + switch(solutions.size()) { + case 0: // if we get no solution .. revert change + continue; + case 1: // if we get 1 solution we are done and return the solution + return solutions.get(0); + default: // if we get more than 1 solution .. keep setting numbers + board = workingBoard; + continue; + } + } else { + + } + } + + + + + return null; + } + + private boolean deleteConnectedValues(final GameBoard gameBoard, final GameCell cell) { + return gameBoard.actionOnCells(new ICellAction() { + @Override + public Boolean action(GameCell gc, Boolean existing) { + if (!gc.hasValue() && !gc.equals(cell)) { + if (gc.getRow() == cell.getRow() + || gc.getCol() == cell.getCol() + || (int) (Math.floor(gc.getRow() / sectionHeight) * sectionHeight + Math.floor(gc.getCol() / sectionWidth)) == + (int) (Math.floor(cell.getRow() / sectionHeight) * sectionHeight + Math.floor(cell.getCol() / sectionWidth))) { + gc.deleteNote(cell.getValue()); + if(gc.getNoteCount() == 0) { + existing = true; + } + } + } + return existing; + } + }, false); + } + + private List getPossibleValues(GameCell c) { + List result = new LinkedList<>(); + for(int i = 0; i < size; i++) { + if(c.getNotes()[i]) { + result.add(i+1); + } + } + return result; + } + + private List getListOfCandidates(GameBoard gameBoard) { + final ArrayList candidates = new ArrayList<>(); + + gameBoard.actionOnCells(new ICellAction>() { + @Override + public ArrayList action(GameCell gc, ArrayList existing) { + if(gc.getNoteCount() > 1) { + existing.add(gc); + } + return existing; + } + }, candidates); + + return candidates; + } + + private void setNotes(GameBoard gameBoard) { + for(int i = 0; i < gameBoard.getSize(); i++) { + for(int j = 0; j < gameBoard.getSize(); j++) { + for(int k = 1; k <= gameBoard.getSize(); k++) { + gameBoard.getCell(i,j).setNote(k); + } + } + } + } + } diff --git a/app/src/main/java/tu_darmstadt/sudoku/controller/helper/GameInfoContainer.java b/app/src/main/java/tu_darmstadt/sudoku/controller/helper/GameInfoContainer.java index 090255d..1c3f717 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/controller/helper/GameInfoContainer.java +++ b/app/src/main/java/tu_darmstadt/sudoku/controller/helper/GameInfoContainer.java @@ -31,9 +31,9 @@ public class GameInfoContainer { } public GameInfoContainer(int ID, GameDifficulty difficulty, Date lastTimePlayed, int timePlayed, GameType gameType, int[] fixedValues, int[] setValues, boolean[][] setNotes) { this.ID = ID; + this.timePlayed = timePlayed; this.difficulty = difficulty; this.gameType = gameType; - this.timePlayed = timePlayed; this.lastTimePlayed = lastTimePlayed; this.fixedValues = fixedValues; this.setValues = setValues; @@ -46,6 +46,36 @@ public class GameInfoContainer { public void parseGameType(String s) { gameType = Enum.valueOf(GameType.class, s); + if(gameType == null) { + throw new IllegalArgumentException("GameInfoContainer: gameType could not be set."); + } + } + + public int getTime() { + return timePlayed; + } + + public void parseTime(String s) { + try { + this.timePlayed = Integer.valueOf(s); + } catch(NumberFormatException e) { + throw new IllegalArgumentException("GameInfoContainer: Can not parse time.", e); + } + } + + public void parseDate(String s) { + try { + this.lastTimePlayed = new Date(Long.valueOf(s)); + } catch(NumberFormatException e) { + throw new IllegalArgumentException("GameInfoContainer: LastTimePlayed Date can not be extracted.", e); + } + } + + public void parseDifficulty(String s) { + difficulty = Enum.valueOf(GameDifficulty.class, s); + if(difficulty == null) { + throw new IllegalArgumentException("GameInfoContainer: difficulty could not be set."); + } } public void parseFixedValues(String s){ @@ -127,10 +157,16 @@ public class GameInfoContainer { public static String getGameInfo(GameController controller) { StringBuilder sb = new StringBuilder(); - + Date today = new Date(); // TODO add some game information sb.append(controller.getGameType().name()); sb.append("/"); + sb.append(controller.getTime()); + sb.append("/"); + sb.append(today.getTime()); + sb.append("/"); + sb.append(controller.getDifficulty().name()); + sb.append("/"); sb.append(getFixedCells(controller)); sb.append("/"); sb.append(getSetCells(controller)); diff --git a/app/src/main/java/tu_darmstadt/sudoku/controller/Solver.java b/app/src/main/java/tu_darmstadt/sudoku/controller/solver/Solver.java similarity index 94% rename from app/src/main/java/tu_darmstadt/sudoku/controller/Solver.java rename to app/src/main/java/tu_darmstadt/sudoku/controller/solver/Solver.java index 9714aa3..45f37d1 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/controller/Solver.java +++ b/app/src/main/java/tu_darmstadt/sudoku/controller/solver/Solver.java @@ -1,4 +1,4 @@ -package tu_darmstadt.sudoku.controller; +package tu_darmstadt.sudoku.controller.solver; import android.graphics.Point; import android.util.Log; @@ -88,8 +88,7 @@ public class Solver { checkSolvedCells(gameBoard); - String string = gameBoard.toString(); - + //String string = gameBoard.toString(); if(isDone(gameBoard)) { solutions.add(gameBoard); return true; @@ -98,6 +97,9 @@ public class Solver { if(showPossibles(gameBoard)) return solve(gameBoard); + if(isImpossible(gameBoard)) + return false; + if(searchHiddenSingles(gameBoard)) return solve(gameBoard); @@ -135,6 +137,11 @@ public class Solver { result = solve(gameBoardCopy); + if(solutions.size() > 1) { + // don't search for more than 1 solution + return result; + } + //if (result) { // stop after we found 1 solution //return true; @@ -152,6 +159,15 @@ public class Solver { return result; } + public boolean isImpossible(GameBoard gameBoard) { + return gameBoard.actionOnCells(new ICellAction() { + @Override + public Boolean action(GameCell gc, Boolean existing) { + return (gc.getNoteCount() == 0 && !gc.hasValue()) ? true : existing; + } + }, false); + } + public boolean calculateNextPossibleStep() { return false; diff --git a/app/src/main/java/tu_darmstadt/sudoku/game/GameCell.java b/app/src/main/java/tu_darmstadt/sudoku/game/GameCell.java index e3e3e15..f795672 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/game/GameCell.java +++ b/app/src/main/java/tu_darmstadt/sudoku/game/GameCell.java @@ -71,24 +71,24 @@ public class GameCell implements Cloneable { if(!isFixed()) { noteCount = notes[val - 1] ? noteCount - 1 : noteCount + 1; notes[val - 1] = !notes[val - 1]; + notifyListeners(); } - notifyListeners(); } public void setNote(int val) { if(!isFixed()) { noteCount = notes[val - 1] ? noteCount : noteCount + 1; notes[val - 1] = true; + notifyListeners(); } - notifyListeners(); } public void deleteNote(int val) { if(!isFixed()) { noteCount = notes[val - 1] ? noteCount - 1 : noteCount; notes[val - 1] = false; + notifyListeners(); } - notifyListeners(); } public int getNoteCount() { diff --git a/app/src/main/java/tu_darmstadt/sudoku/ui/GameActivity.java b/app/src/main/java/tu_darmstadt/sudoku/ui/GameActivity.java index c9ce34a..34eee7b 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/ui/GameActivity.java +++ b/app/src/main/java/tu_darmstadt/sudoku/ui/GameActivity.java @@ -14,6 +14,7 @@ import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.widget.RatingBar; import android.widget.TextView; +import android.widget.Toast; import java.util.List; import java.util.Timer; @@ -36,9 +37,7 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On SudokuFieldLayout layout; SudokuKeyboardLayout keyboard; SudokuSpecialButtonLayout specialButtonLayout; - Timer t = new Timer(); TextView timerView; - boolean isActive = true; TextView viewName ; RatingBar ratingBar; @@ -84,7 +83,7 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On gameController.loadLevel(loadableGames.get(loadLevelID)); } else { // load a new level - gameController.loadNewLevel(gameType, gameDifficulty); + gameController.loadNewLevel(gameType, GameDifficulty.getValidDifficultyList().get(gameDifficulty)); } layout.setGame(gameController); @@ -141,12 +140,13 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On @Override public void onPause(){ super.onPause(); - isActive = false; + gameController.saveGame(this); + gameController.pauseTimer(); } @Override public void onResume(){ super.onResume(); - isActive = true; + gameController.startTimer(); } @@ -156,7 +156,6 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { - gameController.saveGame(getBaseContext()); finish(); super.onBackPressed(); } @@ -181,7 +180,6 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On case R.id.nav_newgame: //create new game intent = new Intent(this, MainActivity.class); - gameController.saveGame(getBaseContext()); finish(); startActivity(intent); break; @@ -189,7 +187,6 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On case R.id.nav_continue: //create new game intent = new Intent(this, LoadGameActivity.class); - gameController.saveGame(getBaseContext()); finish(); startActivity(intent); break; @@ -197,8 +194,6 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On case R.id.menu_settings: //open settings intent = new Intent(this,SettingsActivity.class); - gameController.saveGame(getBaseContext()); - finish(); startActivity(intent); break; @@ -211,8 +206,6 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On case R.id.menu_about: //open about page intent = new Intent(this,AboutActivity.class); - gameController.saveGame(getBaseContext()); - finish(); startActivity(intent); break; @@ -231,6 +224,8 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On @Override public void onSolved() { + Toast t = Toast.makeText(this,"Congratulations you have solved the puzzle!", Toast.LENGTH_SHORT); + t.show(); // TODO: WE WON.. do something awesome :) } @@ -244,6 +239,6 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On s = (seconds< 10)? "0"+String.valueOf(seconds):String.valueOf(seconds); m = (minutes< 10)? "0"+String.valueOf(minutes):String.valueOf(minutes); h = (hours< 10)? "0"+String.valueOf(hours):String.valueOf(hours); - timerView.setText(h+":"+m+":"+s); + timerView.setText(h + ":" + m + ":" + s); } } diff --git a/app/src/main/java/tu_darmstadt/sudoku/ui/MainActivity.java b/app/src/main/java/tu_darmstadt/sudoku/ui/MainActivity.java index f0674c3..24f44ec 100644 --- a/app/src/main/java/tu_darmstadt/sudoku/ui/MainActivity.java +++ b/app/src/main/java/tu_darmstadt/sudoku/ui/MainActivity.java @@ -129,7 +129,7 @@ public class MainActivity extends AppCompatActivity { break; case R.id.playButton: GameType gameType = GameType.getValidGameTypes().get(mViewPager.getCurrentItem()); - int gameDifficulty = difficultyBar.getProgress(); + int gameDifficulty = difficultyBar.getProgress()-1; // save current setting for later SharedPreferences.Editor editor = settings.edit(); diff --git a/app/src/main/res/layout/app_bar_game_view.xml b/app/src/main/res/layout/app_bar_game_view.xml index 3906391..8a8ab42 100644 --- a/app/src/main/res/layout/app_bar_game_view.xml +++ b/app/src/main/res/layout/app_bar_game_view.xml @@ -29,7 +29,7 @@ result = controller.solve(); + + assertEquals(0, result.size()); + } + }