/*
* Andoku - a sudoku puzzle game for Android.
* Copyright (C) 2009, 2010 Markus Wiederkehr
*
* This file is part of Andoku.
*
* Andoku is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Andoku is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Andoku. If not, see <http://www.gnu.org/licenses/>.
*/
package com.googlecode.andoku;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.PointF;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.text.Html;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.googlecode.andoku.commands.AndokuContext;
import com.googlecode.andoku.commands.EliminateValuesCommand;
import com.googlecode.andoku.commands.SetValuesCommand;
import com.googlecode.andoku.db.AndokuDatabase;
import com.googlecode.andoku.db.GameStatistics;
import com.googlecode.andoku.db.PuzzleId;
import com.googlecode.andoku.history.Command;
import com.googlecode.andoku.history.History;
import com.googlecode.andoku.im.InputMethod;
import com.googlecode.andoku.im.InputMethodTarget;
import com.googlecode.andoku.model.AndokuPuzzle;
import com.googlecode.andoku.model.Difficulty;
import com.googlecode.andoku.model.Position;
import com.googlecode.andoku.model.PuzzleType;
import com.googlecode.andoku.model.ValueSet;
import com.googlecode.andoku.source.PuzzleHolder;
import com.googlecode.andoku.source.PuzzleSource;
import com.googlecode.andoku.source.PuzzleSourceIds;
import com.googlecode.andoku.source.PuzzleSourceResolver;
public class AndokuActivity extends Activity
implements OnTouchListener, OnKeyListener, TickListener {
private static final String TAG = AndokuActivity.class.getName();
private static final int DIALOG_CONFIRM_RESET_PUZZLE = 0;
private static final int DIALOG_CONFIRM_RESET_ALL_PUZZLES = 1;
private static final int MENU_CHECK_PUZZLE = Menu.FIRST;
private static final int MENU_PAUSE_RESUME_PUZZLE = Menu.FIRST + 1;
private static final int MENU_ELIMINATE_VALUES = Menu.FIRST + 2;
private static final int MENU_RESET_PUZZLE = Menu.FIRST + 3;
private static final int MENU_RESET_ALL_PUZZLES = Menu.FIRST + 4;
private static final int MENU_SETTINGS = Menu.FIRST + 5;
private static final String APP_STATE_PUZZLE_SOURCE_ID = "puzzleSourceId";
private static final String APP_STATE_PUZZLE_NUMBER = "puzzleNumber";
private static final String APP_STATE_GAME_STATE = "gameState";
private static final String APP_STATE_HIGHLIGHTED_DIGIT = "highlightedDigit";
private static final String APP_STATE_HISTORY = "history";
private static final int REQUEST_CODE_SETTINGS = 0;
private static final int GAME_STATE_NEW_ACTIVITY_STARTED = 0;
private static final int GAME_STATE_ACTIVITY_STATE_RESTORED = 1;
private static final int GAME_STATE_READY = 2;
private static final int GAME_STATE_PLAYING = 3;
private static final int GAME_STATE_SOLVED = 4;
private int gameState;
private AndokuDatabase db;
private Vibrator vibrator;
private PuzzleSource source;
private int puzzleNumber;
private AndokuPuzzle puzzle;
private TickTimer timer = new TickTimer(this);
private History<AndokuContext> history = new History<AndokuContext>(new AndokuContext() {
public TickTimer getTimer() {
return timer;
}
public AndokuPuzzle getPuzzle() {
return puzzle;
}
});
private ViewGroup background;
private TextView puzzleNameView;
private TextView puzzleDifficultyView;
private TextView puzzleSourceView;
private AndokuPuzzleView andokuView;
private TextView timerView;
private ViewGroup keypad;
private KeypadToggleButton[] keypadToggleButtons;
private ImageButton undoButton;
private ImageButton redoButton;
private TextView congratsView;
private Button dismissCongratsButton;
private ImageButton backButton;
private ImageButton nextButton;
private Button startOrResetButton;
private FingertipView fingertipView;
private Toast toast;
private final int[] andokuViewScreenLocation = new int[2];
private final int[] fingertipViewScreenLocation = new int[2];
private final InputMethodTarget inputMethodTarget = new InputMethodTarget() {
public int getPuzzleSize() {
return puzzle.getSize();
}
public Position getMarkedPosition() {
return andokuView.getMarkedPosition();
}
public void setMarkedPosition(Position position) {
andokuView.markPosition(position);
cancelToast();
}
public boolean isClue(Position position) {
return puzzle.isClue(position.row, position.col);
}
public ValueSet getCellValues(Position position) {
return puzzle.getValues(position.row, position.col);
}
public void setCellValues(Position position, ValueSet values) {
AndokuActivity.this.setCellValues(position, values);
}
public int getNumberOfDigitButtons() {
return keypadToggleButtons.length;
}
public void checkButton(int digit, boolean checked) {
keypadToggleButtons[digit].setChecked(checked);
}
public void highlightDigit(Integer digit) {
andokuView.highlightDigit(digit);
}
};
private InputMethodPolicy inputMethodPolicy;
private InputMethod inputMethod;
/** Called when the activity is first created. */
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Constants.LOG_V)
Log.v(TAG, "onCreate(" + savedInstanceState + ")");
Util.setFullscreenMode(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.andoku);
db = new AndokuDatabase(this);
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
background = (ViewGroup) findViewById(R.id.background);
puzzleNameView = (TextView) findViewById(R.id.labelPuzzleName);
puzzleDifficultyView = (TextView) findViewById(R.id.labelPuzzleDifficulty);
puzzleSourceView = (TextView) findViewById(R.id.labelPuzzleSource);
andokuView = (AndokuPuzzleView) findViewById(R.id.viewPuzzle);
andokuView.setOnKeyListener(this);
timerView = (TextView) findViewById(R.id.labelTimer);
keypad = (ViewGroup) findViewById(R.id.keypad);
keypadToggleButtons = new KeypadToggleButton[9];
keypadToggleButtons[0] = (KeypadToggleButton) findViewById(R.id.input_1);
keypadToggleButtons[1] = (KeypadToggleButton) findViewById(R.id.input_2);
keypadToggleButtons[2] = (KeypadToggleButton) findViewById(R.id.input_3);
keypadToggleButtons[3] = (KeypadToggleButton) findViewById(R.id.input_4);
keypadToggleButtons[4] = (KeypadToggleButton) findViewById(R.id.input_5);
keypadToggleButtons[5] = (KeypadToggleButton) findViewById(R.id.input_6);
keypadToggleButtons[6] = (KeypadToggleButton) findViewById(R.id.input_7);
keypadToggleButtons[7] = (KeypadToggleButton) findViewById(R.id.input_8);
keypadToggleButtons[8] = (KeypadToggleButton) findViewById(R.id.input_9);
for (int i = 0; i < 9; i++) {
final int digit = i;
keypadToggleButtons[i].setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onKeypad(digit);
}
});
}
KeypadButton clearButton = (KeypadButton) findViewById(R.id.input_clear);
clearButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onClear();
}
});
KeypadButton invertButton = (KeypadButton) findViewById(R.id.input_invert);
invertButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onInvert();
}
});
undoButton = (ImageButton) findViewById(R.id.input_undo);
undoButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onUndo();
}
});
redoButton = (ImageButton) findViewById(R.id.input_redo);
redoButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onRedo();
}
});
congratsView = (TextView) findViewById(R.id.labelCongrats);
dismissCongratsButton = (Button) findViewById(R.id.buttonDismissCongrats);
dismissCongratsButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onDismissCongratsButton();
}
});
backButton = (ImageButton) findViewById(R.id.buttonBack);
backButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onGotoPuzzleRelative(-1);
}
});
nextButton = (ImageButton) findViewById(R.id.buttonNext);
nextButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onGotoPuzzleRelative(1);
}
});
startOrResetButton = (Button) findViewById(R.id.buttonStart);
startOrResetButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
onStartOrResetButton();
}
});
fingertipView = new FingertipView(this);
fingertipView.setOnTouchListener(this);
addContentView(fingertipView, new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
createThemeFromPreferences();
createPuzzle(savedInstanceState);
createInputMethod();
if (isRestoreSavedInstanceState(savedInstanceState)) {
inputMethod.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState.containsKey(APP_STATE_HIGHLIGHTED_DIGIT))
andokuView.highlightDigit(savedInstanceState.getInt(APP_STATE_HIGHLIGHTED_DIGIT));
history.restoreInstanceState(savedInstanceState.getBundle(APP_STATE_HISTORY));
undoButton.setEnabled(history.canUndo());
redoButton.setEnabled(history.canRedo());
}
}
private void createThemeFromPreferences() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
ColorTheme.Builder builder = new ColorTheme.Builder(getResources());
builder.areaColorPolicy = AreaColorPolicy.valueOf(settings.getString(
Settings.KEY_COLORED_REGIONS, AreaColorPolicy.STANDARD_X_HYPER_SQUIGGLY.name()));
builder.highlightDigitsPolicy = HighlightDigitsPolicy.valueOf(settings.getString(
Settings.KEY_HIGHLIGHT_DIGITS, HighlightDigitsPolicy.ONLY_SINGLE_VALUES.name()));
ColorThemePolicy colorThemePolicy = ColorThemePolicy.valueOf(settings.getString(
Settings.KEY_COLOR_THEME, ColorThemePolicy.CLASSIC.name()));
colorThemePolicy.customize(builder);
Theme theme = builder.build();
setTheme(theme);
}
private void setTheme(Theme theme) {
background.setBackgroundDrawable(theme.getBackground());
puzzleNameView.setTextColor(theme.getNameTextColor());
puzzleDifficultyView.setTextColor(theme.getDifficultyTextColor());
puzzleSourceView.setTextColor(theme.getSourceTextColor());
timerView.setTextColor(theme.getTimerTextColor());
andokuView.setTheme(theme);
}
private void createInputMethod() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
InputMethodPolicy inputMethodPolicy = InputMethodPolicy.valueOf(settings.getString(
Settings.KEY_INPUT_METHOD, InputMethodPolicy.CELL_THEN_VALUES.name()));
if (inputMethodPolicy != this.inputMethodPolicy) {
this.inputMethodPolicy = inputMethodPolicy;
this.inputMethod = inputMethodPolicy.createInputMethod(inputMethodTarget);
this.inputMethod.reset();
}
}
@Override
protected void onPause() {
if (Constants.LOG_V)
Log.v(TAG, "onPause(" + timer + ")");
super.onPause();
if (gameState == GAME_STATE_PLAYING) {
timer.stop();
autoSavePuzzle();
}
setKeepScreenOn(false);
}
@Override
protected void onResume() {
if (Constants.LOG_V)
Log.v(TAG, "onResume(" + timer + ")");
super.onResume();
if (gameState == GAME_STATE_PLAYING) {
setKeepScreenOn(true);
timer.start();
}
}
@Override
protected void onDestroy() {
if (Constants.LOG_V)
Log.v(TAG, "onDestroy()");
super.onDestroy();
if (source != null) {
source.close();
}
if (db != null) {
db.close();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (Constants.LOG_V)
Log.v(TAG, "onSaveInstanceState(" + timer + ")");
super.onSaveInstanceState(outState);
if (source != null) {
outState.putString(APP_STATE_PUZZLE_SOURCE_ID, source.getSourceId());
outState.putInt(APP_STATE_PUZZLE_NUMBER, puzzleNumber);
outState.putInt(APP_STATE_GAME_STATE, gameState);
Integer highlightedDigit = andokuView.getHighlightedDigit();
if (highlightedDigit != null)
outState.putInt(APP_STATE_HIGHLIGHTED_DIGIT, highlightedDigit);
outState.putBundle(APP_STATE_HISTORY, history.saveInstanceState());
inputMethod.onSaveInstanceState(outState);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (Constants.LOG_V)
Log.v(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")");
switch (requestCode) {
case REQUEST_CODE_SETTINGS:
onReturnedFromSettings();
break;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, MENU_CHECK_PUZZLE, Menu.NONE, R.string.menu_check_puzzle).setIcon(
android.R.drawable.ic_menu_help);
menu.add(Menu.NONE, MENU_PAUSE_RESUME_PUZZLE, Menu.NONE, "");
menu.add(Menu.NONE, MENU_ELIMINATE_VALUES, Menu.NONE, R.string.menu_eliminate_values)
.setIcon(R.drawable.ic_menu_eliminate);
menu.add(Menu.NONE, MENU_RESET_PUZZLE, Menu.NONE, R.string.menu_reset_puzzle).setIcon(
android.R.drawable.ic_menu_close_clear_cancel);
menu.add(Menu.NONE, MENU_RESET_ALL_PUZZLES, Menu.NONE, R.string.menu_reset_all_puzzles)
.setIcon(android.R.drawable.ic_menu_delete);
menu.add(Menu.NONE, MENU_SETTINGS, Menu.NONE, R.string.menu_settings).setIcon(
android.R.drawable.ic_menu_preferences);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(MENU_CHECK_PUZZLE).setVisible(gameState == GAME_STATE_PLAYING);
boolean paused = gameState == GAME_STATE_READY && !puzzle.isSolved() && timer.getTime() > 0;
menu.findItem(MENU_PAUSE_RESUME_PUZZLE).setTitle(
paused ? R.string.menu_resume : R.string.menu_pause).setIcon(
paused ? R.drawable.ic_menu_resume : R.drawable.ic_menu_pause).setVisible(
gameState == GAME_STATE_PLAYING || paused);
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
menu.findItem(MENU_ELIMINATE_VALUES).setVisible(
gameState == GAME_STATE_PLAYING
&& settings.getBoolean(Settings.KEY_ENABLE_ELIMINATE_VALUES, false)).setEnabled(
puzzle.canEliminateValues());
menu.findItem(MENU_RESET_PUZZLE).setVisible(gameState == GAME_STATE_PLAYING);
menu.findItem(MENU_RESET_ALL_PUZZLES).setVisible(gameState == GAME_STATE_READY);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case MENU_CHECK_PUZZLE:
onCheckPuzzle();
return true;
case MENU_PAUSE_RESUME_PUZZLE:
onPauseResumeGame();
return true;
case MENU_ELIMINATE_VALUES:
onEliminateValues();
return true;
case MENU_RESET_PUZZLE:
onResetPuzzle(false);
return true;
case MENU_RESET_ALL_PUZZLES:
onResetAllPuzzles(false);
return true;
case MENU_SETTINGS:
onSettings();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
void onKeypad(int digit) {
if (gameState != GAME_STATE_PLAYING)
return;
inputMethod.onKeypad(digit);
cancelToast();
}
void onClear() {
if (gameState != GAME_STATE_PLAYING)
return;
inputMethod.onClear();
cancelToast();
}
void onInvert() {
if (gameState != GAME_STATE_PLAYING)
return;
inputMethod.onInvert();
cancelToast();
}
void onUndo() {
if (history.undo())
onCommandExecuted();
}
void onRedo() {
if (history.redo())
onCommandExecuted();
}
private void setCellValues(Position position, ValueSet values) {
execute(new SetValuesCommand(position, values));
}
private void execute(Command<AndokuContext> command) {
if (history.execute(command))
onCommandExecuted();
}
private void onCommandExecuted() {
undoButton.setEnabled(history.canUndo());
redoButton.setEnabled(history.canRedo());
if (puzzle.isCompletelyFilled()) {
if (puzzle.isSolved()) {
timer.stop();
autoSavePuzzle();
enterGameState(GAME_STATE_SOLVED);
return;
}
else {
showInfo(R.string.info_invalid_solution);
}
}
updateKeypadHighlighing();
andokuView.invalidate();
inputMethod.onValuesChanged();
}
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (gameState != GAME_STATE_PLAYING)
return false;
if (event.getAction() != KeyEvent.ACTION_DOWN)
return false;
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
inputMethod.onMoveMark(-1, 0);
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMethod.onMoveMark(1, 0);
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMethod.onMoveMark(0, -1);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMethod.onMoveMark(0, 1);
return true;
case KeyEvent.KEYCODE_1:
case KeyEvent.KEYCODE_2:
case KeyEvent.KEYCODE_3:
case KeyEvent.KEYCODE_4:
case KeyEvent.KEYCODE_5:
case KeyEvent.KEYCODE_6:
case KeyEvent.KEYCODE_7:
case KeyEvent.KEYCODE_8:
case KeyEvent.KEYCODE_9:
onKeypad(keyCode - KeyEvent.KEYCODE_1);
return true;
default:
return false;
}
}
public boolean onTouch(View view, MotionEvent event) {
if (gameState != GAME_STATE_PLAYING)
return false;
fingertipView.getLocationOnScreen(fingertipViewScreenLocation);
andokuView.getLocationOnScreen(andokuViewScreenLocation);
// translate event x/y from fingertipView to andokuView coordinates
float x = event.getX() + fingertipViewScreenLocation[0] - andokuViewScreenLocation[0];
float y = event.getY() + fingertipViewScreenLocation[1] - andokuViewScreenLocation[1];
Position position = andokuView.getPositionAt(x, y, 0.5f);
int action = event.getAction();
if (position == null && action == MotionEvent.ACTION_DOWN)
return false;
boolean editable = position != null && !puzzle.isClue(position.row, position.col);
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
if (position == null) {
fingertipView.hide();
}
else {
PointF center = andokuView.getPositionCenterPoint(position);
// translate center from andokuView coordinates to fingertipView coordinates
center.x += andokuViewScreenLocation[0] - fingertipViewScreenLocation[0];
center.y += andokuViewScreenLocation[1] - fingertipViewScreenLocation[1];
fingertipView.show(center, editable);
}
inputMethod.onSweep();
}
else if (action == MotionEvent.ACTION_UP) {
fingertipView.hide();
inputMethod.onTap(position, editable);
}
else { // MotionEvent.ACTION_CANCEL
fingertipView.hide();
inputMethod.onSweep();
}
return true;
}
// callback from tick timer
public void onTick(long time) {
timerView.setText(DateUtil.formatTime(time));
}
private void onGotoPuzzleRelative(int offset) {
int number = puzzleNumber + offset;
int numPuzzles = source.numberOfPuzzles();
if (number < 0)
number = numPuzzles - 1;
if (number >= numPuzzles)
number = 0;
gotoPuzzle(number);
}
private void gotoPuzzle(int number) {
setPuzzle(number);
inputMethod.reset();
enterGameState(GAME_STATE_READY);
}
void onCheckPuzzle() {
if (Constants.LOG_V)
Log.v(TAG, "onCheckPuzzle()");
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
boolean checkAgainstSolution = settings.getBoolean(Settings.KEY_CHECK_AGAINST_SOLUTION, true);
if (checkAgainstSolution) {
if (puzzle.hasSolution()) {
checkPuzzle(true);
}
else {
new ComputeSolutionAndCheckPuzzleTask().execute();
}
}
else {
checkPuzzle(false);
}
}
private void checkPuzzle(boolean checkAgainstSolution) {
boolean errors = puzzle.checkForErrors(checkAgainstSolution);
if (errors) {
showWarning(R.string.warn_puzzle_errors);
}
else {
int missing = puzzle.getMissingValuesCount();
if (missing == 1) {
showInfo(R.string.info_puzzle_ok_1);
}
else {
CharSequence text = getText(R.string.info_puzzle_ok_n);
showInfo(String.format(text.toString(), puzzle.getMissingValuesCount()));
}
}
andokuView.invalidate();
}
void onStartOrResetButton() {
if (Constants.LOG_V)
Log.v(TAG, "onStartOrResetButton()");
if (gameState == GAME_STATE_READY && puzzle.isSolved()) {
onResetPuzzle(false);
}
else {
enterGameState(GAME_STATE_PLAYING);
}
}
void onDismissCongratsButton() {
if (Constants.LOG_V)
Log.v(TAG, "onDismissCongratsButton()");
enterGameState(GAME_STATE_READY);
}
// callback from reset puzzle dialog
void onResetPuzzle(boolean confirmed) {
if (!confirmed && !puzzle.isModified())
confirmed = true;
if (!confirmed) {
showDialog(DIALOG_CONFIRM_RESET_PUZZLE);
}
else {
timer.stop();
deleteAutoSavedPuzzle();
gotoPuzzle(puzzleNumber);
}
}
// callback from reset all puzzles dialog
void onResetAllPuzzles(boolean confirmed) {
if (!confirmed) {
showDialog(DIALOG_CONFIRM_RESET_ALL_PUZZLES);
}
else {
resetAllPuzzles();
}
}
private void resetAllPuzzles() {
String sourceId = source.getSourceId();
if (Constants.LOG_V)
Log.v(TAG, "deleting all puzzles for " + sourceId);
db.deleteAll(sourceId);
gotoPuzzle(0);
}
void onEliminateValues() {
execute(new EliminateValuesCommand());
}
void onSettings() {
Intent intent = new Intent(this, SettingsActivity.class);
startActivityForResult(intent, REQUEST_CODE_SETTINGS);
}
private void onReturnedFromSettings() {
createThemeFromPreferences();
createInputMethod();
setTimerVisibility(gameState);
andokuView.invalidate();
}
private void createPuzzle(Bundle savedInstanceState) {
if (isRestoreSavedInstanceState(savedInstanceState))
createPuzzleFromSavedInstanceState(savedInstanceState);
else
createPuzzleFromIntent();
}
private boolean isRestoreSavedInstanceState(Bundle savedInstanceState) {
return savedInstanceState != null
&& savedInstanceState.getString(APP_STATE_PUZZLE_SOURCE_ID) != null;
}
private void createPuzzleFromSavedInstanceState(Bundle savedInstanceState) {
String puzzleSourceId = savedInstanceState.getString(APP_STATE_PUZZLE_SOURCE_ID);
int number = savedInstanceState.getInt(APP_STATE_PUZZLE_NUMBER);
if (Constants.LOG_V)
Log.v(TAG, "createPuzzleFromSavedInstanceState(): " + puzzleSourceId + ":" + number);
initializePuzzle(puzzleSourceId, number);
gameState = GAME_STATE_ACTIVITY_STATE_RESTORED;
enterGameState(savedInstanceState.getInt(APP_STATE_GAME_STATE));
}
private void createPuzzleFromIntent() {
final Intent intent = getIntent();
String puzzleSourceId = intent.getStringExtra(Constants.EXTRA_PUZZLE_SOURCE_ID);
if (puzzleSourceId == null)
puzzleSourceId = PuzzleSourceIds.forAssetFolder("standard_n_1");
int number = intent.getIntExtra(Constants.EXTRA_PUZZLE_NUMBER, 0);
if (Constants.LOG_V)
Log.v(TAG, "createPuzzleFromIntent(): " + puzzleSourceId + ":" + number);
initializePuzzle(puzzleSourceId, number);
gameState = GAME_STATE_NEW_ACTIVITY_STARTED;
boolean start = getIntent().getBooleanExtra(Constants.EXTRA_START_PUZZLE, false);
enterGameState(start ? GAME_STATE_PLAYING : GAME_STATE_READY);
}
private void initializePuzzle(String puzzleSourceId, int number) {
source = PuzzleSourceResolver.resolveSource(this, puzzleSourceId);
setPuzzle(number);
}
private void setPuzzle(int number) {
puzzleNumber = number;
puzzle = createAndokuPuzzle(number);
history.clear();
undoButton.setEnabled(false);
redoButton.setEnabled(false);
andokuView.setPuzzle(puzzle);
puzzleNameView.setText(getPuzzleName());
puzzleDifficultyView.setText(getPuzzleDifficulty());
puzzleSourceView.setText(getPuzzleSource());
if (!restoreAutoSavedPuzzle()) {
Log.w(TAG, "unable to restore auto-saved puzzle");
timer.reset();
}
}
private AndokuPuzzle createAndokuPuzzle(int number) {
PuzzleHolder holder = source.load(number);
return new AndokuPuzzle(holder.getName(), holder.getPuzzle(), holder.getDifficulty());
}
private String getPuzzleName() {
String name = puzzle.getName();
if (name != null && name.length() > 0)
return name;
PuzzleType puzzleType = puzzle.getPuzzleType();
return Util.getPuzzleName(getResources(), puzzleType);
}
private String getPuzzleDifficulty() {
final Difficulty difficulty = puzzle.getDifficulty();
if (difficulty == Difficulty.UNKNOWN)
return "";
final Resources resources = getResources();
String[] difficulties = resources.getStringArray(R.array.difficulties);
return difficulties[difficulty.ordinal()];
}
private String getPuzzleSource() {
final String suffix = "#" + (puzzleNumber + 1) + "/" + source.numberOfPuzzles();
final String sourceId = source.getSourceId();
if (PuzzleSourceIds.isDbSource(sourceId)) {
return Util.getFolderName(db, sourceId) + " " + suffix;
}
else {
return suffix;
}
}
private void onPauseResumeGame() {
// TODO: remove RESUME menu entry?
if (gameState == GAME_STATE_PLAYING) {
timer.stop();
autoSavePuzzle();
enterGameState(GAME_STATE_READY);
}
else if (gameState == GAME_STATE_READY) {
enterGameState(GAME_STATE_PLAYING);
}
else
throw new IllegalStateException("pause/resume");
}
private void enterGameState(int newGameState) {
if (Constants.LOG_V)
Log.v(TAG, "enterGameState(" + newGameState + ")");
setKeepScreenOn(newGameState == GAME_STATE_PLAYING);
switch (newGameState) {
case GAME_STATE_READY:
if (puzzle.isSolved())
startOrResetButton.setText(R.string.button_reset_game);
else if (timer.getTime() > 0)
startOrResetButton.setText(R.string.button_resume_game);
else
startOrResetButton.setText(R.string.button_start_game);
break;
case GAME_STATE_PLAYING:
if (!puzzle.isRestored())
autoSavePuzzle(); // save for correct 'date-created' timestamp
timer.start();
break;
case GAME_STATE_SOLVED:
updateCongrats();
break;
default:
throw new IllegalStateException();
}
boolean showNameAndDifficulty = newGameState != GAME_STATE_PLAYING
|| hasSufficientVerticalSpace();
puzzleNameView.setVisibility(showNameAndDifficulty ? View.VISIBLE : View.GONE);
puzzleDifficultyView.setVisibility(showNameAndDifficulty ? View.VISIBLE : View.GONE);
congratsView.setVisibility(newGameState == GAME_STATE_SOLVED ? View.VISIBLE : View.GONE);
dismissCongratsButton.setVisibility(newGameState == GAME_STATE_SOLVED
? View.VISIBLE
: View.GONE);
int visibilityBackStartNext = newGameState == GAME_STATE_READY ? View.VISIBLE : View.GONE;
backButton.setVisibility(visibilityBackStartNext);
startOrResetButton.setVisibility(visibilityBackStartNext);
nextButton.setVisibility(visibilityBackStartNext);
boolean enableNav = newGameState == GAME_STATE_READY;
backButton.setEnabled(enableNav);
nextButton.setEnabled(enableNav);
final boolean showKeypad = newGameState == GAME_STATE_PLAYING;
keypad.setVisibility(showKeypad ? View.VISIBLE : View.GONE);
if (showKeypad)
updateKeypadHighlighing();
andokuView.setPaused(newGameState == GAME_STATE_READY && !puzzle.isSolved()
&& timer.getTime() > 0);
andokuView.setPreview(newGameState == GAME_STATE_READY);
setTimerVisibility(newGameState);
if (gameState != newGameState)
andokuView.invalidate();
this.gameState = newGameState;
}
// 320x240 device (e.g. HTC Tattoo) does not have enough vertical space to display title and name)
private boolean hasSufficientVerticalSpace() {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
float aspect = ((float) displayMetrics.heightPixels) / displayMetrics.widthPixels;
return aspect >= 480f / 320;
}
private void setTimerVisibility(int forGameState) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
boolean showTimerWhilePlaying = settings.getBoolean(Settings.KEY_SHOW_TIMER, true);
boolean showTimer = showTimerWhilePlaying || forGameState != GAME_STATE_PLAYING;
timerView.setVisibility(showTimer ? View.VISIBLE : View.INVISIBLE);
}
private void setKeepScreenOn(boolean keepScreenOn) {
andokuView.setKeepScreenOn(keepScreenOn);
}
private void updateCongrats() {
String congrats = getResources().getString(R.string.message_congrats);
String title = getStatisticsTitle();
String details = getStatisticsDetails();
String message = congrats + "<br/><br/>" + title + "<br/><br/>" + details;
congratsView.setText(Html.fromHtml(message));
}
private String getStatisticsTitle() {
final String sourceId = source.getSourceId();
if (PuzzleSourceIds.isDbSource(sourceId)) {
String folderName = Util.getFolderName(db, sourceId);
return getResources().getString(R.string.message_statistics_title_db, folderName);
}
else {
String name = getPuzzleName();
String difficulty = getPuzzleDifficulty();
return getResources()
.getString(R.string.message_statistics_title_assets, name, difficulty);
}
}
private String getStatisticsDetails() {
GameStatistics stats = db.getStatistics(source.getSourceId());
return getResources().getString(R.string.message_statistics_details, stats.numGamesSolved,
DateUtil.formatTime(stats.getAverageTime()), DateUtil.formatTime(stats.minTime));
}
private void updateKeypadHighlighing() {
final int size = puzzle.getSize();
int[] counter = new int[size];
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
ValueSet values = puzzle.getValues(row, col);
if (values.size() == 1) {
int value = values.nextValue(0);
counter[value]++;
}
}
}
for (int digit = 0; digit < size; digit++) {
final boolean digitCompleted = counter[digit] == size;
keypadToggleButtons[digit].setHighlighted(digitCompleted);
}
}
private void autoSavePuzzle() {
PuzzleId puzzleId = getCurrentPuzzleId();
if (Constants.LOG_V)
Log.v(TAG, "auto-saving puzzle " + puzzleId);
db.saveGame(puzzleId, puzzle, timer);
}
private void deleteAutoSavedPuzzle() {
PuzzleId puzzleId = getCurrentPuzzleId();
if (Constants.LOG_V)
Log.v(TAG, "deleting auto-save game " + puzzleId);
db.delete(puzzleId);
}
private boolean restoreAutoSavedPuzzle() {
PuzzleId puzzleId = getCurrentPuzzleId();
if (Constants.LOG_V)
Log.v(TAG, "restoring auto-save game " + puzzleId);
return db.loadGame(puzzleId, puzzle, timer);
}
private PuzzleId getCurrentPuzzleId() {
return new PuzzleId(source.getSourceId(), puzzleNumber);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_CONFIRM_RESET_PUZZLE:
return createConfirmResetPuzzleDialog();
case DIALOG_CONFIRM_RESET_ALL_PUZZLES:
return createConfirmResetAllPuzzlesDialog();
default:
return null;
}
}
private Dialog createConfirmResetPuzzleDialog() {
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(
R.string.dialog_reset_puzzle).setMessage(R.string.message_reset_puzzle)
.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
onResetPuzzle(true);
}
}).setNegativeButton(R.string.alert_dialog_cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).create();
}
private Dialog createConfirmResetAllPuzzlesDialog() {
final String sourceId = source.getSourceId();
int messageId = PuzzleSourceIds.isDbSource(sourceId)
? R.string.message_reset_all_puzzles_in_folder
: R.string.message_reset_all_puzzles_in_variation;
return new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(
R.string.dialog_reset_all_puzzles).setMessage(messageId).setPositiveButton(
R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
onResetAllPuzzles(true);
}
}).setNegativeButton(R.string.alert_dialog_cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
}).create();
}
private void showInfo(int resId) {
showInfo(getText(resId));
}
private void showInfo(CharSequence message) {
showToast(message, false);
}
private void showWarning(int resId) {
showWarning(getText(resId));
}
private void showWarning(CharSequence message) {
vibrator.vibrate(new long[] { 0, 80, 80, 120 }, -1);
showToast(message, true);
}
private void showToast(CharSequence message, boolean warning) {
cancelToast();
toast = Toast.makeText(this, message, Toast.LENGTH_LONG);
// TODO: change toast background to indicate warning..
toast.show();
}
private void cancelToast() {
if (toast != null) {
toast.cancel();
toast = null;
}
}
private final class ComputeSolutionAndCheckPuzzleTask extends AsyncTask<Void, Integer, Boolean> {
private boolean timerRunning;
private ProgressDialog progressDialog;
@Override
protected void onPreExecute() {
timerRunning = timer.isRunning();
timer.stop();
String message = getResources().getString(R.string.message_computing_solution);
progressDialog = ProgressDialog.show(AndokuActivity.this, "", message, true);
}
@Override
protected Boolean doInBackground(Void... params) {
return puzzle.computeSolution();
}
@Override
protected void onPostExecute(Boolean solved) {
progressDialog.dismiss();
if (timerRunning)
timer.start();
if (solved)
checkPuzzle(true);
else
showWarning(R.string.warn_invalid_puzzle);
}
}
}
|