Generator now works for every Difficulty and GameType. I had to change the generator for the 12x12 Generation to leave more clues because it had a very hard time generating levels for the "easy" difficulty level.

It's not very pretty but it works in a reasonable time. Before it would just turn black and stay that way for half an hour.
This commit is contained in:
Christopher Beckmann 2015-11-22 17:45:17 +01:00
parent 606625d08b
commit b875a66ab5
5 changed files with 67 additions and 69 deletions

View file

@ -20,8 +20,6 @@ import tu_darmstadt.sudoku.game.GameType;
*/ */
public class QQWingController { public class QQWingController {
private static final String NL = System.getProperties().getProperty("line.separator");
final QQWingOptions opts = new QQWingOptions(); final QQWingOptions opts = new QQWingOptions();
private int[] level; private int[] level;
@ -30,11 +28,12 @@ public class QQWingController {
private boolean solveImpossible = false; private boolean solveImpossible = false;
public int[] generate(GameType type, GameDifficulty difficulty) { public int[] generate(GameType type, GameDifficulty difficulty) {
// TODO: GameType options.
opts.gameDifficulty = difficulty; opts.gameDifficulty = difficulty;
opts.action = Action.GENERATE; opts.action = Action.GENERATE;
opts.printSolution = false;
opts.threads = Runtime.getRuntime().availableProcessors(); opts.threads = Runtime.getRuntime().availableProcessors();
doAction(type); opts.gameType = type;
doAction();
return generated; return generated;
} }
@ -54,7 +53,8 @@ public class QQWingController {
opts.action = Action.SOLVE; opts.action = Action.SOLVE;
opts.printSolution = true; opts.printSolution = true;
opts.threads = 1; opts.threads = 1;
doAction(gameBoard.getGameType()); opts.gameType = gameBoard.getGameType();
doAction();
if(solveImpossible) { if(solveImpossible) {
// TODO: do something else. // TODO: do something else.
@ -62,11 +62,10 @@ public class QQWingController {
return solution; return solution;
} }
private void doAction(final GameType gameType) { private void doAction() {
// The number of puzzles solved or generated. // The number of puzzles solved or generated.
final AtomicInteger puzzleCount = new AtomicInteger(0); final AtomicInteger puzzleCount = new AtomicInteger(0);
final AtomicBoolean done = new AtomicBoolean(false); final AtomicBoolean done = new AtomicBoolean(false);
final AtomicIntegerArray result = new AtomicIntegerArray(new int[gameType.getSize()*gameType.getSize()]);
Thread[] threads = new Thread[opts.threads]; Thread[] threads = new Thread[opts.threads];
for (int threadCount = 0; threadCount < threads.length; threadCount++) { for (int threadCount = 0; threadCount < threads.length; threadCount++) {
@ -78,7 +77,7 @@ public class QQWingController {
private QQWing ss = createQQWing(); private QQWing ss = createQQWing();
private QQWing createQQWing() { private QQWing createQQWing() {
QQWing ss = new QQWing(gameType); QQWing ss = new QQWing(opts.gameType, opts.gameDifficulty);
ss.setRecordHistory(opts.printHistory || opts.printInstructions || opts.printStats || opts.gameDifficulty != GameDifficulty.Unspecified); ss.setRecordHistory(opts.printHistory || opts.printInstructions || opts.printStats || opts.gameDifficulty != GameDifficulty.Unspecified);
ss.setLogHistory(opts.logHistory); ss.setLogHistory(opts.logHistory);
ss.setPrintStyle(opts.printStyle); ss.setPrintStyle(opts.printStyle);
@ -94,15 +93,11 @@ public class QQWingController {
// until we have generated the specified number. // until we have generated the specified number.
while (!done.get()) { while (!done.get()) {
// iff something has been printed for this
// particular puzzle
StringBuilder output = new StringBuilder();
// Record whether the puzzle was possible or // Record whether the puzzle was possible or
// not, // not,
// so that we don't try to solve impossible // so that we don't try to solve impossible
// givens. // givens.
boolean havePuzzle = false; boolean havePuzzle;
if (opts.action == Action.GENERATE) { if (opts.action == Action.GENERATE) {
// Generate a puzzle // Generate a puzzle
@ -135,9 +130,9 @@ public class QQWingController {
// Count the solutions if requested. // Count the solutions if requested.
// (Must be done before solving, as it would // (Must be done before solving, as it would
// mess up the stats.) // mess up the stats.)
if (opts.countSolutions) { //if (opts.countSolutions) {
solutions = ss.countSolutions(); // solutions = ss.countSolutions();
} //}
// Solve the puzzle // Solve the puzzle
if (opts.printSolution || opts.printHistory || opts.printStats || opts.printInstructions || opts.gameDifficulty != GameDifficulty.Unspecified) { if (opts.printSolution || opts.printHistory || opts.printStats || opts.printInstructions || opts.gameDifficulty != GameDifficulty.Unspecified) {
@ -148,22 +143,9 @@ public class QQWingController {
// Bail out if it didn't meet the gameDifficulty // Bail out if it didn't meet the gameDifficulty
// standards for generation // standards for generation
if (opts.action == Action.GENERATE) { if (opts.action == Action.GENERATE) {
if (opts.gameDifficulty != GameDifficulty.Unspecified && opts.gameDifficulty != ss.getDifficulty()) { if (opts.gameDifficulty != GameDifficulty.Unspecified && opts.gameDifficulty == ss.getDifficulty()) {
havePuzzle = false; done.set(true);
// check if other threads have generated = ss.getPuzzle();
// finished the job
if (puzzleCount.get() >= opts.numberToGenerate) {
done.set(true);
generated = ss.getPuzzle();
}
} else {
int numDone = puzzleCount.incrementAndGet();
if (numDone >= opts.numberToGenerate) {
done.set(true);
generated = ss.getPuzzle();
}
if (numDone > opts.numberToGenerate)
havePuzzle = false;
} }
} }
} }
@ -202,6 +184,7 @@ public class QQWingController {
int numberToGenerate = 1; int numberToGenerate = 1;
boolean printStats = false; boolean printStats = false;
GameDifficulty gameDifficulty = GameDifficulty.Unspecified; GameDifficulty gameDifficulty = GameDifficulty.Unspecified;
GameType gameType = GameType.Unspecified;
Symmetry symmetry = Symmetry.NONE; Symmetry symmetry = Symmetry.NONE;
int threads = Runtime.getRuntime().availableProcessors(); int threads = Runtime.getRuntime().availableProcessors();
} }

View file

@ -132,10 +132,16 @@ public class QQWing {
*/ */
private PrintStyle printStyle = PrintStyle.READABLE; private PrintStyle printStyle = PrintStyle.READABLE;
private GameType gameType = GameType.Unspecified;
private GameDifficulty difficulty = GameDifficulty.Unspecified;
/** /**
* Create a new Sudoku board * Create a new Sudoku board
*/ */
public QQWing(GameType type) { public QQWing(GameType type, GameDifficulty difficulty) {
gameType = type;
this.difficulty = difficulty;
GRID_SIZE_ROW = type.getSectionHeight(); // 3 // 2 GRID_SIZE_ROW = type.getSectionHeight(); // 3 // 2
GRID_SIZE_COL = type.getSectionWidth(); // 3 // 3 GRID_SIZE_COL = type.getSectionWidth(); // 3 // 3
@ -221,13 +227,21 @@ public class QQWing {
* Get the gameDifficulty rating. * Get the gameDifficulty rating.
*/ */
public GameDifficulty getDifficulty() { public GameDifficulty getDifficulty() {
if (getGuessCount() > 0) return GameDifficulty.Hard; if (getGuessCount() > 0)
if (getBoxLineReductionCount() > 0) return GameDifficulty.Moderate; return GameDifficulty.Hard;
if (getPointingPairTripleCount() > 0) return GameDifficulty.Moderate; if (getBoxLineReductionCount() > 0)
if (getHiddenPairCount() > 0) return GameDifficulty.Moderate; return GameDifficulty.Moderate;
if (getNakedPairCount() > 0) return GameDifficulty.Moderate; if (getPointingPairTripleCount() > 0)
if (getHiddenSingleCount() > 0) return GameDifficulty.Easy; return GameDifficulty.Moderate;
if (getSingleCount() > 0) return GameDifficulty.Easy; if (getHiddenPairCount() > 0)
return GameDifficulty.Moderate;
if (getNakedPairCount() > 0)
return GameDifficulty.Moderate;
if (getHiddenSingleCount() > 0)
return GameDifficulty.Easy;
if (getSingleCount() > 0)
return GameDifficulty.Easy;
return GameDifficulty.Unspecified; return GameDifficulty.Unspecified;
} }
@ -434,7 +448,12 @@ public class QQWing {
// Non-guesses are even rounds // Non-guesses are even rounds
for (int i = 2; i <= lastSolveRound; i += 2) { for (int i = 2; i <= lastSolveRound; i += 2) {
rollbackRound(i); rollbackRound(i);
}
// Some hack to make easy levels on 12x12 .. because the generator wasn't able to create some
if(gameType == GameType.Default_12x12 && difficulty == GameDifficulty.Easy ) {
i += 4; // skip every 2nd round
}
}
} }
public void setPrintStyle(PrintStyle ps) { public void setPrintStyle(PrintStyle ps) {
@ -667,7 +686,7 @@ public class QQWing {
} }
private int findPositionWithFewestPossibilities() { private int findPositionWithFewestPossibilities() {
int minPossibilities = 10; int minPossibilities = ROW_COL_SEC_SIZE+1;
int bestPosition = 0; int bestPosition = 0;
for (int i = 0; i < BOARD_SIZE; i++) { for (int i = 0; i < BOARD_SIZE; i++) {
int position = randomBoardArray[i]; int position = randomBoardArray[i];

View file

@ -46,7 +46,7 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
GameType gameType = GameType.Unspecified; GameType gameType = GameType.Unspecified;
int gameDifficulty = 0; GameDifficulty gameDifficulty = GameDifficulty.Unspecified;
int loadLevelID = 0; int loadLevelID = 0;
boolean loadLevel = false; boolean loadLevel = false;
@ -56,7 +56,7 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On
if(o instanceof GameType) { if(o instanceof GameType) {
gameType = (GameType)extras.get("gameType"); gameType = (GameType)extras.get("gameType");
} }
gameDifficulty = extras.getInt("gameDifficulty"); gameDifficulty = (GameDifficulty)(extras.get("gameDifficulty"));
loadLevel = extras.getBoolean("loadLevel"); loadLevel = extras.getBoolean("loadLevel");
if(loadLevel) { if(loadLevel) {
loadLevelID = extras.getInt("loadLevelID"); loadLevelID = extras.getInt("loadLevelID");
@ -83,7 +83,7 @@ public class GameActivity extends AppCompatActivity implements NavigationView.On
gameController.loadLevel(loadableGames.get(loadLevelID)); gameController.loadLevel(loadableGames.get(loadLevelID));
} else { } else {
// load a new level // load a new level
gameController.loadNewLevel(gameType, GameDifficulty.getValidDifficultyList().get(gameDifficulty)); gameController.loadNewLevel(gameType, gameDifficulty);
} }
layout.setGame(gameController); layout.setGame(gameController);

View file

@ -12,7 +12,6 @@ import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -23,8 +22,6 @@ import android.widget.TextView;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import tu_darmstadt.sudoku.controller.SaveLoadController; import tu_darmstadt.sudoku.controller.SaveLoadController;
import tu_darmstadt.sudoku.controller.helper.GameInfoContainer; import tu_darmstadt.sudoku.controller.helper.GameInfoContainer;
@ -37,17 +34,6 @@ public class MainActivity extends AppCompatActivity {
RatingBar difficultyBar; RatingBar difficultyBar;
TextView difficultyText; TextView difficultyText;
SharedPreferences settings; SharedPreferences settings;
Timer t = new Timer();
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link FragmentPagerAdapter} derivative, which will keep every
* loaded fragment in memory. If this becomes too memory intensive, it
* may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
private SectionsPagerAdapter mSectionsPagerAdapter;
/** /**
* The {@link ViewPager} that will host the section contents. * The {@link ViewPager} that will host the section contents.
@ -67,7 +53,15 @@ public class MainActivity extends AppCompatActivity {
// Create the adapter that will return a fragment for each of the three // Create the adapter that will return a fragment for each of the three
// primary sections of the activity. // primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); /*
The {@link android.support.v4.view.PagerAdapter} that will provide
fragments for each of the sections. We use a
{@link FragmentPagerAdapter} derivative, which will keep every
loaded fragment in memory. If this becomes too memory intensive, it
may be best to switch to a
{@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter. // Set up the ViewPager with the sections adapter.
@ -92,16 +86,16 @@ public class MainActivity extends AppCompatActivity {
if (rating < 1) { if (rating < 1) {
ratingBar.setRating(1); ratingBar.setRating(1);
} }
difficultyText.setText(getString(difficultyList.get((int)ratingBar.getRating()-1).getStringResID())); difficultyText.setText(getString(difficultyList.get((int) ratingBar.getRating() - 1).getStringResID()));
} }
}); });
int lastChosenDifficulty = settings.getInt("lastChosenDifficulty", 1); GameDifficulty lastChosenDifficulty = GameDifficulty.valueOf(settings.getString("lastChosenDifficulty", "Easy"));
difficultyBar.setRating(lastChosenDifficulty); difficultyBar.setRating(GameDifficulty.getValidDifficultyList().indexOf(lastChosenDifficulty)+1);
// on first create always check for loadable levels! // on first create always check for loadable levels!
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("savesChanged", true); editor.putBoolean("savesChanged", true);
editor.commit(); editor.apply();
refreshContinueButton(); refreshContinueButton();
} }
@ -129,13 +123,14 @@ public class MainActivity extends AppCompatActivity {
break; break;
case R.id.playButton: case R.id.playButton:
GameType gameType = GameType.getValidGameTypes().get(mViewPager.getCurrentItem()); GameType gameType = GameType.getValidGameTypes().get(mViewPager.getCurrentItem());
int gameDifficulty = difficultyBar.getProgress()-1; int index = difficultyBar.getProgress()-1;
GameDifficulty gameDifficulty = GameDifficulty.getValidDifficultyList().get(index < 0 ? 0 : index);
// save current setting for later // save current setting for later
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
editor.putString("lastChosenGameType", gameType.name()); editor.putString("lastChosenGameType", gameType.name());
editor.putInt("lastChosenDifficulty", gameDifficulty); editor.putString("lastChosenDifficulty", gameDifficulty.name());
editor.commit(); editor.apply();
// send everything to game activity // send everything to game activity
i = new Intent(this, GameActivity.class); i = new Intent(this, GameActivity.class);
@ -169,7 +164,7 @@ public class MainActivity extends AppCompatActivity {
} }
} }
@Override /*@Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will // Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long // automatically handle clicks on the Home/Up button, so long
@ -177,7 +172,7 @@ public class MainActivity extends AppCompatActivity {
int id = item.getItemId(); int id = item.getItemId();
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }*/
/** /**

View file

@ -43,6 +43,7 @@
<string name="about_author_names">Christopher Beckmann, Timm Lippert</string> <string name="about_author_names">Christopher Beckmann, Timm Lippert</string>
<string name="about_affiliation">In affiliation with:</string> <string name="about_affiliation">In affiliation with:</string>
<string name="privacy_friendly">This application belongs to the Privacy Friendly Apps.</string> <string name="privacy_friendly">This application belongs to the Privacy Friendly Apps.</string>
<string name="qqwing">This application uses a modified version of QQWing v1.3.4</string>
<string name="more_info">More information can be found on:</string> <string name="more_info">More information can be found on:</string>
<string name="url"><a href="https://www.secuso.informatik.tu-darmstadt.de/en/research/results/">https://www.secuso.org</a></string> <string name="url"><a href="https://www.secuso.informatik.tu-darmstadt.de/en/research/results/">https://www.secuso.org</a></string>