forked from len0rd/rockbox
Add Jewels, Spacerocks, Wormlet, Rockboy and Sudoku for the e200. Also Includes manual changes for plugins. Add X5 keymappings for wormlet to the manual. Add help text for Jewels on the H10 and give a warning if help text is not defined. Fix bug in spacerocks lives drawing on large screens (larger than Ondio). Change spacerocks comments to C style. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12018 a1c6a512-1295-4272-9138-f99709370657
1362 lines
40 KiB
C
1362 lines
40 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 Dave Chapman
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/***
|
|
Sudoku by Dave Chapman
|
|
|
|
User instructions
|
|
-----------------
|
|
|
|
Use the arrow keys to move cursor, and press SELECT/ON/F2 to increment
|
|
the number under the cursor.
|
|
|
|
At any time during the game, press On to bring up the game menu with
|
|
further options:
|
|
|
|
Save
|
|
Reload
|
|
Clear
|
|
Solve
|
|
|
|
Sudoku is implemented as a "viewer" for a ".ss" file, as generated by
|
|
Simple Sudoku and other applications - http://angusj.com/sudoku/
|
|
|
|
In-progress game positions are saved in the original .ss file, with
|
|
A-I used to indicate numbers entered by the user.
|
|
|
|
Example ".ss" file, and one with a saved state:
|
|
|
|
...|...|... ...|...|...
|
|
2..|8.4|9.1 2.C|8.4|9.1
|
|
...|1.6|32. E..|1.6|32.
|
|
----------- -----------
|
|
...|..5|.4. ...|..5|.4.
|
|
8..|423|..6 8..|423|..6
|
|
.3.|9..|... .3D|9..|A..
|
|
----------- -----------
|
|
.63|7.9|... .63|7.9|...
|
|
4.9|5.2|..8 4.9|5.2|.C8
|
|
...|...|... ...|...|...
|
|
|
|
*/
|
|
|
|
#include "plugin.h"
|
|
|
|
#ifdef HAVE_LCD_BITMAP
|
|
|
|
#include <lib/playback_control.h>
|
|
#include "sudoku.h"
|
|
#include "generator.h"
|
|
|
|
/* The bitmaps */
|
|
#include "sudoku_normal.h"
|
|
#include "sudoku_inverse.h"
|
|
#include "sudoku_start.h"
|
|
|
|
#define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
|
|
#define BITMAP_STRIDE BMPWIDTH_sudoku_normal
|
|
|
|
PLUGIN_HEADER
|
|
|
|
/* here is a global api struct pointer. while not strictly necessary,
|
|
it's nice not to have to pass the api pointer in all function calls
|
|
in the plugin */
|
|
|
|
struct plugin_api* rb;
|
|
|
|
/* Default game - used to initialise sudoku.ss if it doesn't exist. */
|
|
static const char default_game[9][9] =
|
|
{
|
|
{ '0','1','0', '3','0','7', '0','0','4' },
|
|
{ '0','0','0', '0','6','0', '1','0','2' },
|
|
{ '0','0','0', '0','8','0', '5','6','0' },
|
|
|
|
{ '0','6','0', '0','0','0', '0','2','9' },
|
|
{ '0','0','0', '5','0','3', '0','0','0' },
|
|
{ '7','9','0', '0','0','0', '0','3','0' },
|
|
|
|
{ '0','8','5', '0','3','0', '0','0','0' },
|
|
{ '1','0','2', '0','7','0', '0','0','0' },
|
|
{ '0','0','0', '4','0','8', '0','5','0' },
|
|
};
|
|
|
|
#if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout, scratchpad at the left */
|
|
|
|
#if (LCD_HEIGHT==64) && (LCD_WIDTH==112)
|
|
/* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 8
|
|
#define CELL_HEIGHT 6
|
|
#define SMALL_BOARD
|
|
|
|
#elif (LCD_HEIGHT==110) && (LCD_WIDTH==138)
|
|
/* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 10
|
|
#define CELL_HEIGHT 10
|
|
|
|
#elif (LCD_HEIGHT==128) && (LCD_WIDTH==128)
|
|
/* iriver H10 5-6GB - 128x128, 9 cells @ 10x10 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 10
|
|
#define CELL_HEIGHT 10
|
|
|
|
#elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) || \
|
|
((LCD_HEIGHT==132) && (LCD_WIDTH==176))
|
|
/* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
|
|
/* iPod Nano - 176x132, 9 cells @ 12x12 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 12
|
|
#define CELL_HEIGHT 12
|
|
|
|
#elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
|
|
/* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 16
|
|
#define CELL_HEIGHT 16
|
|
|
|
#elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320)
|
|
/* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 24
|
|
#define CELL_HEIGHT 24
|
|
|
|
#else
|
|
#error SUDOKU: Unsupported LCD size
|
|
#endif
|
|
|
|
#else /* Vertical layout, scratchpad at the bottom */
|
|
#define VERTICAL_LAYOUT
|
|
|
|
#if ((LCD_HEIGHT==220) && (LCD_WIDTH==176))
|
|
/* e200, 9 cells @ 16x16 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 16
|
|
#define CELL_HEIGHT 16
|
|
|
|
#elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
|
|
/* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
|
|
|
|
/* Internal dimensions of a cell */
|
|
#define CELL_WIDTH 24
|
|
#define CELL_HEIGHT 24
|
|
|
|
#else
|
|
#error SUDOKU: Unsupported LCD size
|
|
#endif
|
|
|
|
#endif /* Layout */
|
|
|
|
/* Size dependent build-time calculations */
|
|
#ifdef SMALL_BOARD
|
|
#define BOARD_WIDTH (CELL_WIDTH*9+10)
|
|
#define BOARD_HEIGHT (CELL_HEIGHT*9+10)
|
|
static unsigned char cellxpos[9]={
|
|
1, (CELL_WIDTH+2), (2*CELL_WIDTH+3),
|
|
(3*CELL_WIDTH+4), (4*CELL_WIDTH+5), (5*CELL_WIDTH+6),
|
|
(6*CELL_WIDTH+7), (7*CELL_WIDTH+8), (8*CELL_WIDTH+9)
|
|
};
|
|
static unsigned char cellypos[9]={
|
|
1, (CELL_HEIGHT+2), (2*CELL_HEIGHT+3),
|
|
(3*CELL_HEIGHT+4), (4*CELL_HEIGHT+5), (5*CELL_HEIGHT+6),
|
|
(6*CELL_HEIGHT+7), (7*CELL_HEIGHT+8), (8*CELL_HEIGHT+9)
|
|
};
|
|
#else /* !SMALL_BOARD */
|
|
#define BOARD_WIDTH (CELL_WIDTH*9+10+4)
|
|
#define BOARD_HEIGHT (CELL_HEIGHT*9+10+4)
|
|
static unsigned char cellxpos[9]={
|
|
2, (CELL_WIDTH +3), (2*CELL_WIDTH +4),
|
|
(3*CELL_WIDTH +6), (4*CELL_WIDTH +7), (5*CELL_WIDTH +8),
|
|
(6*CELL_WIDTH+10), (7*CELL_WIDTH+11), (8*CELL_WIDTH+12)
|
|
};
|
|
static unsigned char cellypos[9]={
|
|
2, (CELL_HEIGHT +3), (2*CELL_HEIGHT +4),
|
|
(3*CELL_HEIGHT +6), (4*CELL_HEIGHT +7), (5*CELL_HEIGHT +8),
|
|
(6*CELL_HEIGHT+10), (7*CELL_HEIGHT+11), (8*CELL_HEIGHT+12)
|
|
};
|
|
#endif
|
|
|
|
#ifdef VERTICAL_LAYOUT
|
|
#define XOFS ((LCD_WIDTH-BOARD_WIDTH)/2)
|
|
#define YOFS ((LCD_HEIGHT-(BOARD_HEIGHT+CELL_HEIGHT*2+2))/2)
|
|
#define YOFSSCRATCHPAD (YOFS+BOARD_HEIGHT+CELL_WIDTH)
|
|
#else
|
|
#define XOFSSCRATCHPAD ((LCD_WIDTH-(BOARD_WIDTH+CELL_WIDTH*2+2))/2)
|
|
#define XOFS (XOFSSCRATCHPAD+CELL_WIDTH*2+2)
|
|
#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT)/2)
|
|
#endif
|
|
|
|
/****** Solver routine by Tom Shackell <shackell@cs.york.ac.uk>
|
|
|
|
Downloaded from:
|
|
|
|
http://www-users.cs.york.ac.uk/~shackell/sudoku/Sudoku.html
|
|
|
|
Released under GPLv2
|
|
|
|
*/
|
|
|
|
typedef unsigned int Bitset;
|
|
|
|
#define BLOCK 3
|
|
#define SIZE (BLOCK*BLOCK)
|
|
|
|
#define true 1
|
|
#define false 0
|
|
|
|
typedef struct _Sudoku {
|
|
Bitset table[SIZE][SIZE];
|
|
}Sudoku;
|
|
|
|
typedef struct _Stats {
|
|
int numTries;
|
|
int backTracks;
|
|
int numEmpty;
|
|
bool solutionFound;
|
|
}Stats;
|
|
|
|
typedef struct _Options {
|
|
bool allSolutions;
|
|
bool uniquenessCheck;
|
|
}Options;
|
|
|
|
void sudoku_init(Sudoku* sud);
|
|
void sudoku_set(Sudoku* sud, int x, int y, int num, bool original);
|
|
int sudoku_get(Sudoku* sud, int x, int y, bool* original);
|
|
|
|
#define BIT(n) ((Bitset)(1<<(n)))
|
|
#define BIT_TEST(v,n) ((((Bitset)v) & BIT(n)) != 0)
|
|
#define BIT_CLEAR(v,n) (v) &= ~BIT(n)
|
|
#define MARK_BIT BIT(0)
|
|
#define ORIGINAL_BIT BIT(SIZE+1)
|
|
|
|
#define ALL_BITS (BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | BIT(6) | BIT(7) | BIT(8) | BIT(9))
|
|
|
|
/* initialize a sudoku problem, should be called before using set or get */
|
|
void sudoku_init(Sudoku* sud)
|
|
{
|
|
int y, x;
|
|
for (y = 0; y < SIZE; y++){
|
|
for (x = 0; x < SIZE; x++){
|
|
sud->table[x][y] = ALL_BITS;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set the number at a particular x and y column */
|
|
void sudoku_set(Sudoku* sud, int x, int y, int num, bool original)
|
|
{
|
|
int i, j;
|
|
int bx, by;
|
|
Bitset orig;
|
|
|
|
/* clear the row and columns */
|
|
for (i = 0; i < SIZE; i++){
|
|
BIT_CLEAR(sud->table[i][y], num);
|
|
BIT_CLEAR(sud->table[x][i], num);
|
|
}
|
|
/* clear the block */
|
|
bx = x - (x % BLOCK);
|
|
by = y - (y % BLOCK);
|
|
for (i = 0; i < BLOCK; i++){
|
|
for (j = 0; j < BLOCK; j++){
|
|
BIT_CLEAR(sud->table[bx+j][by+i], num);
|
|
}
|
|
}
|
|
/* mark the table */
|
|
orig = original ? ORIGINAL_BIT : 0;
|
|
sud->table[x][y] = BIT(num) | MARK_BIT | orig;
|
|
}
|
|
|
|
/* get the number at a particular x and y column, if this
|
|
is not unique return 0 */
|
|
int sudoku_get(Sudoku* sud, int x, int y, bool* original)
|
|
{
|
|
Bitset val = sud->table[x][y];
|
|
int result = 0;
|
|
int i;
|
|
|
|
if (original) {
|
|
*original = val & ORIGINAL_BIT;
|
|
}
|
|
for (i = 1; i <= SIZE; i++){
|
|
if (BIT_TEST(val, i)){
|
|
if (result != 0){
|
|
return 0;
|
|
}
|
|
result = i;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* returns true if this is a valid problem, this is necessary because the input
|
|
problem might be degenerate which breaks the solver algorithm. */
|
|
static bool is_valid(const Sudoku* sud)
|
|
{
|
|
int x, y;
|
|
|
|
for (y = 0; y < SIZE; y++){
|
|
for (x = 0; x < SIZE; x++){
|
|
if ((sud->table[x][y] & ALL_BITS) == 0){
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* scan the table for the most constrained item, giving all it's options, sets
|
|
the best x and y coordinates, the number of options and the options for
|
|
that coordinate and returns true if the puzzle is finished */
|
|
static bool scan(const Sudoku* sud, int* rX, int* rY, int *num, int* options)
|
|
{
|
|
int x, y, i, j;
|
|
int bestCount = SIZE+1;
|
|
Bitset val;
|
|
bool allMarked = true;
|
|
|
|
for (y = 0; y < SIZE; y++){
|
|
for (x = 0; x < SIZE; x++){
|
|
Bitset val = sud->table[x][y];
|
|
int i;
|
|
int count = 0;
|
|
|
|
if (val & MARK_BIT) {
|
|
/* already set */
|
|
continue;
|
|
}
|
|
allMarked = false;
|
|
for (i = 1; i <= SIZE; i++){
|
|
if (BIT_TEST(val, i)){
|
|
count++;
|
|
}
|
|
}
|
|
if (count < bestCount){
|
|
bestCount = count;
|
|
*rX = x;
|
|
*rY = y;
|
|
if (count == 0){
|
|
/* can't possibly be beaten */
|
|
*num = 0;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* now copy into options */
|
|
*num = bestCount;
|
|
val = sud->table[*rX][*rY];
|
|
for (i = 1, j = 0; i <= SIZE; i++){
|
|
if (BIT_TEST(val, i)){
|
|
options[j++] = i;
|
|
}
|
|
}
|
|
return allMarked;
|
|
}
|
|
|
|
static bool solve(Sudoku* sud, Stats* stats, const Options* options);
|
|
|
|
/* try a particular option and return true if that gives a solution or false
|
|
if it doesn't, restores board on backtracking */
|
|
static bool spawn_option(Sudoku* sud, Stats* stats, const Options* options,
|
|
int x, int y, int num)
|
|
{
|
|
Sudoku copy;
|
|
|
|
rb->memcpy(©,sud,sizeof(Sudoku));
|
|
sudoku_set(©, x, y, num, false);
|
|
stats->numTries += 1;
|
|
if (solve(©, stats, options)){
|
|
if (!options->allSolutions && stats->solutionFound){
|
|
rb->memcpy(sud,©,sizeof(Sudoku));
|
|
}
|
|
return true;
|
|
}else{
|
|
stats->backTracks++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* solve a sudoku problem, returns true if there is a solution and false
|
|
otherwise. stats is used to track statisticss */
|
|
static bool solve(Sudoku* sud, Stats* stats, const Options* options)
|
|
{
|
|
while (true){
|
|
int x = 0;
|
|
int y = 0;
|
|
int i, num;
|
|
int places[SIZE];
|
|
|
|
if (scan(sud, &x, &y, &num, places)){
|
|
/* a solution was found! */
|
|
if (options->uniquenessCheck && stats->solutionFound){
|
|
/*printf("\n\t... But the solution is not unique!\n"); */
|
|
return true;
|
|
}
|
|
stats->solutionFound = true;
|
|
if (options->allSolutions || options->uniquenessCheck){
|
|
/*printf("\n\tSolution after %d iterations\n", stats->numTries); */
|
|
/*sudoku_print(sud); */
|
|
return false;
|
|
}
|
|
else{
|
|
return true;
|
|
}
|
|
}
|
|
if (num == 0){
|
|
/* can't be satisfied */
|
|
return false;
|
|
}
|
|
/* try all the places (except the last one) */
|
|
for (i = 0; i < num-1; i++){
|
|
if (spawn_option(sud, stats, options, x, y, places[i])){
|
|
/* solution found! */
|
|
if (!options->allSolutions && stats->solutionFound){
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
/* take the last place ourself */
|
|
stats->numTries += 1;
|
|
sudoku_set(sud, x, y, places[num-1], false);
|
|
}
|
|
}
|
|
|
|
/******** END OF IMPORTED CODE */
|
|
|
|
|
|
/* A wrapper function between the Sudoku plugin and the above solver code */
|
|
void sudoku_solve(struct sudoku_state_t* state)
|
|
{
|
|
bool ret;
|
|
Stats stats;
|
|
Options options;
|
|
Sudoku sud;
|
|
bool original;
|
|
int r,c;
|
|
|
|
/* Initialise the parameters */
|
|
sudoku_init(&sud);
|
|
rb->memset(&stats,0,sizeof(stats));
|
|
options.allSolutions=false;
|
|
options.uniquenessCheck=false;
|
|
|
|
/* Convert Rockbox format into format for solver */
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
if (state->startboard[r][c]!='0') {
|
|
sudoku_set(&sud, c, r, state->startboard[r][c]-'0', true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* need to check for degenerate input problems ... */
|
|
if (is_valid(&sud)){
|
|
ret = solve(&sud, &stats, &options);
|
|
} else {
|
|
ret = false;
|
|
}
|
|
|
|
if (ret) {
|
|
/* Populate the board with the solution. */
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
state->currentboard[r][c]='0'+
|
|
sudoku_get(&sud, c, r, &original);
|
|
}
|
|
}
|
|
} else {
|
|
rb->splash(HZ*2, true, "Solve failed");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
void default_state(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
|
|
rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
state->startboard[r][c]=default_game[r][c];
|
|
state->currentboard[r][c]=default_game[r][c];
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
state->possiblevals[r][c]=0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
state->x=0;
|
|
state->y=0;
|
|
state->editmode=0;
|
|
}
|
|
|
|
void clear_state(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
|
|
rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
state->startboard[r][c]='0';
|
|
state->currentboard[r][c]='0';
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
state->possiblevals[r][c]=0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
state->x=0;
|
|
state->y=0;
|
|
state->editmode=0;
|
|
}
|
|
|
|
/* Check the status of the board, assuming a change at the cursor location */
|
|
bool check_status(struct sudoku_state_t* state)
|
|
{
|
|
int check[9];
|
|
int r,c;
|
|
int r1,c1;
|
|
int cell;
|
|
|
|
/* First, check the column */
|
|
for (cell=0;cell<9;cell++) {
|
|
check[cell]=0;
|
|
}
|
|
for (r=0;r<9;r++) {
|
|
cell=state->currentboard[r][state->x];
|
|
if (cell!='0') {
|
|
if (check[cell-'1']==1) {
|
|
return true;
|
|
}
|
|
check[cell-'1']=1;
|
|
}
|
|
}
|
|
|
|
/* Second, check the row */
|
|
for (cell=0;cell<9;cell++) {
|
|
check[cell]=0;
|
|
}
|
|
for (c=0;c<9;c++) {
|
|
cell=state->currentboard[state->y][c];
|
|
if (cell!='0') {
|
|
if (check[cell-'1']==1) {
|
|
return true;
|
|
}
|
|
check[cell-'1']=1;
|
|
}
|
|
}
|
|
|
|
/* Finally, check the 3x3 sub-grid */
|
|
for (cell=0;cell<9;cell++) {
|
|
check[cell]=0;
|
|
}
|
|
r1=(state->y/3)*3;
|
|
c1=(state->x/3)*3;
|
|
for (r=r1;r<r1+3;r++) {
|
|
for (c=c1;c<c1+3;c++) {
|
|
cell=state->currentboard[r][c];
|
|
if (cell!='0') {
|
|
if (check[cell-'1']==1) {
|
|
return true;
|
|
}
|
|
check[cell-'1']=1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We passed all the checks :) */
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Load game - only ".ss" is officially supported, but any sensible
|
|
text representation (one line per row) may load.
|
|
*/
|
|
bool load_sudoku(struct sudoku_state_t* state, char* filename)
|
|
{
|
|
int fd;
|
|
size_t n;
|
|
int r = 0, c = 0;
|
|
unsigned int i;
|
|
int valid=0;
|
|
char buf[300]; /* A buffer to read a sudoku board from */
|
|
|
|
fd=rb->open(filename, O_RDONLY);
|
|
if (fd < 0) {
|
|
LOGF("Invalid sudoku file: %s\n",filename);
|
|
return(false);
|
|
}
|
|
|
|
rb->strncpy(state->filename,filename,MAX_PATH);
|
|
n=rb->read(fd,buf,300);
|
|
if (n <= 0) {
|
|
return(false);
|
|
}
|
|
rb->close(fd);
|
|
|
|
r=0;
|
|
c=0;
|
|
i=0;
|
|
while ((i < n) && (r < 9)) {
|
|
switch (buf[i]){
|
|
case ' ': case '\t':
|
|
if (c > 0)
|
|
valid=1;
|
|
break;
|
|
case '|':
|
|
case '*':
|
|
case '-':
|
|
case '\r':
|
|
break;
|
|
case '\n':
|
|
if (valid) {
|
|
r++;
|
|
valid=0;
|
|
}
|
|
c = 0;
|
|
break;
|
|
case '_': case '.':
|
|
valid=1;
|
|
if (c >= SIZE || r >= SIZE){
|
|
LOGF("ERROR: sudoku problem is the wrong size (%d,%d)\n",
|
|
c, r);
|
|
return(false);
|
|
}
|
|
c++;
|
|
break;
|
|
default:
|
|
if (((buf[i]>='A') && (buf[i]<='I')) ||
|
|
((buf[i]>='0') && (buf[i]<='9'))) {
|
|
valid=1;
|
|
if (r >= SIZE || c >= SIZE){
|
|
LOGF("ERROR: sudoku problem is the wrong size "
|
|
"(%d,%d)\n", c, r);
|
|
return(false);
|
|
}
|
|
if ((buf[i]>='0') && (buf[i]<='9')) {
|
|
state->startboard[r][c]=buf[i];
|
|
state->currentboard[r][c]=buf[i];
|
|
} else {
|
|
state->currentboard[r][c]='1'+(buf[i]-'A');
|
|
}
|
|
c++;
|
|
}
|
|
/* Ignore any other characters */
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* Check that the board is valid - we need to check every row/column
|
|
individually, so we check the diagonal from top-left to bottom-right */
|
|
for (state->x = 0; state->x < 9; state->x++) {
|
|
state->y = state->x;
|
|
if (check_status(state)) return false;
|
|
}
|
|
state->x = 0;
|
|
state->y = 0;
|
|
|
|
/* Save a copy of the saved state - so we can reload without using the
|
|
disk */
|
|
rb->memcpy(state->savedboard,state->currentboard,81);
|
|
return(true);
|
|
}
|
|
|
|
bool save_sudoku(struct sudoku_state_t* state)
|
|
{
|
|
int fd;
|
|
int r,c;
|
|
int i;
|
|
char line[13];
|
|
char sep[13];
|
|
|
|
rb->splash(0, true, "Saving...");
|
|
rb->memcpy(line,"...|...|...\r\n",13);
|
|
rb->memcpy(sep,"-----------\r\n",13);
|
|
|
|
if (state->filename[0]==0) {
|
|
return false;
|
|
}
|
|
|
|
fd=rb->open(state->filename, O_WRONLY|O_CREAT);
|
|
if (fd >= 0) {
|
|
for (r=0;r<9;r++) {
|
|
i=0;
|
|
for (c=0;c<9;c++) {
|
|
if (state->startboard[r][c]!='0') {
|
|
line[i]=state->startboard[r][c];
|
|
} else if (state->currentboard[r][c]!='0') {
|
|
line[i]='A'+(state->currentboard[r][c]-'1');
|
|
} else {
|
|
line[i]='.';
|
|
}
|
|
i++;
|
|
if ((c==2) || (c==5)) {
|
|
i++;
|
|
}
|
|
}
|
|
rb->write(fd,line,sizeof(line));
|
|
if ((r==2) || (r==5)) {
|
|
rb->write(fd,sep,sizeof(sep));
|
|
}
|
|
}
|
|
/* Add a blank line at end */
|
|
rb->write(fd,"\r\n",2);
|
|
rb->close(fd);
|
|
rb->reload_directory();
|
|
/* Save a copy of the saved state - so we can reload without
|
|
using the disk */
|
|
rb->memcpy(state->savedboard,state->currentboard,81);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void restore_state(struct sudoku_state_t* state)
|
|
{
|
|
rb->memcpy(state->currentboard,state->savedboard,81);
|
|
}
|
|
|
|
void clear_board(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
state->currentboard[r][c]=state->startboard[r][c];
|
|
}
|
|
}
|
|
state->x=0;
|
|
state->y=0;
|
|
}
|
|
|
|
void update_cell(struct sudoku_state_t* state, int r, int c)
|
|
{
|
|
/* We have four types of cell:
|
|
1) User-entered number
|
|
2) Starting number
|
|
3) Cursor in cell
|
|
*/
|
|
|
|
if ((r==state->y) && (c==state->x)) {
|
|
rb->lcd_bitmap_part(sudoku_inverse,0,
|
|
BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
|
|
BITMAP_STRIDE,
|
|
XOFS+cellxpos[c],YOFS+cellypos[r],CELL_WIDTH,
|
|
CELL_HEIGHT);
|
|
} else {
|
|
if (state->startboard[r][c]!='0') {
|
|
rb->lcd_bitmap_part(sudoku_start,0,
|
|
BITMAP_HEIGHT*(state->startboard[r][c]-'0'),
|
|
BITMAP_STRIDE,
|
|
XOFS+cellxpos[c],YOFS+cellypos[r],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
} else {
|
|
rb->lcd_bitmap_part(sudoku_normal,0,
|
|
BITMAP_HEIGHT*(state->currentboard[r][c]-'0'),
|
|
BITMAP_STRIDE,
|
|
XOFS+cellxpos[c],YOFS+cellypos[r],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
}
|
|
}
|
|
|
|
rb->lcd_update_rect(cellxpos[c],cellypos[r],CELL_WIDTH,CELL_HEIGHT);
|
|
}
|
|
|
|
|
|
void display_board(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
|
|
/* Clear the display buffer */
|
|
rb->lcd_clear_display();
|
|
|
|
/* Draw the gridlines - differently for different targets */
|
|
|
|
#ifdef SMALL_BOARD
|
|
/* Small targets - draw dotted/single lines */
|
|
for (r=0;r<9;r++) {
|
|
if ((r % 3)==0) {
|
|
/* Solid Line */
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
|
|
rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
} else {
|
|
/* Dotted line */
|
|
for (c=XOFS;c<XOFS+BOARD_WIDTH;c+=2) {
|
|
rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
|
|
}
|
|
for (c=YOFS;c<YOFS+BOARD_HEIGHT;c+=2) {
|
|
rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
|
|
}
|
|
}
|
|
}
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
|
|
rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
#else
|
|
/* Large targets - draw single/double lines */
|
|
for (r=0;r<9;r++) {
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-1);
|
|
rb->lcd_vline(XOFS+cellxpos[r]-1,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
if ((r % 3)==0) {
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[r]-2);
|
|
rb->lcd_vline(XOFS+cellxpos[r]-2,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
}
|
|
}
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT);
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFS+cellypos[8]+CELL_HEIGHT+1);
|
|
rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
#endif
|
|
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
#ifdef VERTICAL_LAYOUT
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD);
|
|
rb->lcd_hline(XOFS,XOFS+BOARD_WIDTH-1,YOFSSCRATCHPAD+CELL_HEIGHT+1);
|
|
for (r=0;r<9;r++) {
|
|
#ifdef SMALL_BOARD
|
|
/* Small targets - draw dotted/single lines */
|
|
if ((r % 3)==0) {
|
|
/* Solid Line */
|
|
rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
|
|
YOFSSCRATCHPAD+CELL_HEIGHT+1);
|
|
} else {
|
|
/* Dotted line */
|
|
for (c=YOFSSCRATCHPAD;c<YOFSSCRATCHPAD+CELL_HEIGHT+1;c+=2) {
|
|
rb->lcd_drawpixel(XOFS+cellxpos[r]-1,c);
|
|
}
|
|
}
|
|
#else
|
|
/* Large targets - draw single/double lines */
|
|
rb->lcd_vline(XOFS+cellxpos[r]-1,YOFSSCRATCHPAD,
|
|
YOFSSCRATCHPAD+CELL_HEIGHT+1);
|
|
if ((r % 3)==0)
|
|
rb->lcd_vline(XOFS+cellxpos[r]-2,YOFSSCRATCHPAD,
|
|
YOFSSCRATCHPAD+CELL_HEIGHT+1);
|
|
#endif
|
|
if ((r>0) && state->possiblevals[state->y][state->x]&(1<<(r)))
|
|
rb->lcd_bitmap_part(sudoku_normal,0,BITMAP_HEIGHT*r,BITMAP_STRIDE,
|
|
XOFS+cellxpos[r-1],YOFSSCRATCHPAD+1,
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
}
|
|
rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH,YOFSSCRATCHPAD,
|
|
YOFSSCRATCHPAD+CELL_HEIGHT+1);
|
|
#ifndef SMALL_BOARD
|
|
rb->lcd_vline(XOFS+cellxpos[8]+CELL_WIDTH+1,YOFSSCRATCHPAD,
|
|
YOFSSCRATCHPAD+CELL_HEIGHT+1);
|
|
#endif
|
|
if (state->possiblevals[state->y][state->x]&(1<<(r)))
|
|
rb->lcd_bitmap_part(sudoku_normal,0,BITMAP_HEIGHT*r,BITMAP_STRIDE,
|
|
XOFS+cellxpos[8],YOFSSCRATCHPAD+1,
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
#else /* Horizontal layout */
|
|
rb->lcd_vline(XOFSSCRATCHPAD,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
rb->lcd_vline(XOFSSCRATCHPAD+CELL_WIDTH+1,YOFS,YOFS+BOARD_HEIGHT-1);
|
|
for (r=0;r<9;r++) {
|
|
#ifdef SMALL_BOARD
|
|
/* Small targets - draw dotted/single lines */
|
|
if ((r % 3)==0) {
|
|
/* Solid Line */
|
|
rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
|
|
YOFS+cellypos[r]-1);
|
|
} else {
|
|
/* Dotted line */
|
|
for (c=XOFSSCRATCHPAD;c<XOFSSCRATCHPAD+CELL_WIDTH+1;c+=2) {
|
|
rb->lcd_drawpixel(c,YOFS+cellypos[r]-1);
|
|
}
|
|
}
|
|
#else
|
|
/* Large targets - draw single/double lines */
|
|
rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
|
|
YOFS+cellypos[r]-1);
|
|
if ((r % 3)==0)
|
|
rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
|
|
YOFS+cellypos[r]-2);
|
|
#endif
|
|
if ((r>0) && state->possiblevals[state->y][state->x]&(1<<(r)))
|
|
rb->lcd_bitmap_part(sudoku_normal,0,BITMAP_HEIGHT*r,BITMAP_STRIDE,
|
|
XOFSSCRATCHPAD+1,YOFS+cellypos[r-1],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
}
|
|
rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
|
|
YOFS+cellypos[8]+CELL_HEIGHT);
|
|
#ifndef SMALL_BOARD
|
|
rb->lcd_hline(XOFSSCRATCHPAD,XOFSSCRATCHPAD+CELL_WIDTH+1,
|
|
YOFS+cellypos[8]+CELL_HEIGHT+1);
|
|
#endif
|
|
if (state->possiblevals[state->y][state->x]&(1<<(r)))
|
|
rb->lcd_bitmap_part(sudoku_normal,0,BITMAP_HEIGHT*r,BITMAP_STRIDE,
|
|
XOFSSCRATCHPAD+1,YOFS+cellypos[8],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
#endif /* Layout */
|
|
#endif /* SUDOKU_BUTTON_POSSIBLE */
|
|
|
|
/* Draw the numbers */
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
/* We have four types of cell:
|
|
1) User-entered number
|
|
2) Starting number
|
|
3) Cursor in cell
|
|
*/
|
|
|
|
if ((r==state->y) && (c==state->x)) {
|
|
rb->lcd_bitmap_part(sudoku_inverse,0,
|
|
BITMAP_HEIGHT*(state->currentboard[r][c]-
|
|
'0'),
|
|
BITMAP_STRIDE,
|
|
XOFS+cellxpos[c],YOFS+cellypos[r],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
} else {
|
|
if (state->startboard[r][c]!='0') {
|
|
rb->lcd_bitmap_part(sudoku_start,0,
|
|
BITMAP_HEIGHT*(state->startboard[r][c]-
|
|
'0'),
|
|
BITMAP_STRIDE,
|
|
XOFS+cellxpos[c],YOFS+cellypos[r],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
} else {
|
|
rb->lcd_bitmap_part(sudoku_normal,0,
|
|
BITMAP_HEIGHT*
|
|
(state->currentboard[r][c]-'0'),
|
|
BITMAP_STRIDE,
|
|
XOFS+cellxpos[c],YOFS+cellypos[r],
|
|
CELL_WIDTH,CELL_HEIGHT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update the screen */
|
|
rb->lcd_update();
|
|
}
|
|
|
|
bool sudoku_generate(struct sudoku_state_t* state)
|
|
{
|
|
char* difficulty;
|
|
char str[80];
|
|
bool res;
|
|
struct sudoku_state_t new_state;
|
|
|
|
clear_state(&new_state);
|
|
display_board(&new_state);
|
|
rb->splash(0, true, "Generating...");
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(true);
|
|
#endif
|
|
|
|
res = sudoku_generate_board(&new_state,&difficulty);
|
|
|
|
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
|
|
rb->cpu_boost(false);
|
|
#endif
|
|
|
|
if (res) {
|
|
rb->memcpy(state,&new_state,sizeof(new_state));
|
|
rb->snprintf(str,sizeof(str),"Difficulty: %s",difficulty);
|
|
display_board(state);
|
|
rb->splash(HZ*3, true, str);
|
|
rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
|
|
} else {
|
|
display_board(&new_state);
|
|
rb->splash(HZ*2, true, "Aborted");
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool sudoku_menu(struct sudoku_state_t* state)
|
|
{
|
|
int m;
|
|
int result;
|
|
|
|
static const struct menu_item items[] = {
|
|
{ "Audio Playback", NULL },
|
|
{ "Save", NULL },
|
|
{ "Reload", NULL },
|
|
{ "Clear", NULL },
|
|
{ "Solve", NULL },
|
|
{ "Generate", NULL },
|
|
{ "New", NULL },
|
|
{ "Quit", NULL },
|
|
};
|
|
|
|
m = rb->menu_init(items, sizeof(items) / sizeof(*items),
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
result=rb->menu_show(m);
|
|
|
|
switch (result) {
|
|
case 0: /* Audio playback */
|
|
playback_control(rb);
|
|
break;
|
|
|
|
case 1: /* Save state */
|
|
save_sudoku(state);
|
|
break;
|
|
|
|
case 2: /* Restore state */
|
|
restore_state(state);
|
|
break;
|
|
|
|
case 3: /* Clear all */
|
|
clear_board(state);
|
|
break;
|
|
|
|
case 4: /* Solve */
|
|
sudoku_solve(state);
|
|
break;
|
|
|
|
case 5: /* Generate Game */
|
|
sudoku_generate(state);
|
|
break;
|
|
|
|
case 6: /* Create a new game manually */
|
|
clear_state(state);
|
|
state->editmode=1;
|
|
break;
|
|
|
|
case 7: /* Quit */
|
|
save_sudoku(state);
|
|
rb->menu_exit(m);
|
|
return true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
rb->menu_exit(m);
|
|
|
|
return (result==MENU_ATTACHED_USB);
|
|
}
|
|
|
|
/* Menu used when user is in edit mode - i.e. creating a new game manually */
|
|
int sudoku_edit_menu(struct sudoku_state_t* state)
|
|
{
|
|
int m;
|
|
int result;
|
|
|
|
static const struct menu_item items[] = {
|
|
{ "Save as", NULL },
|
|
{ "Quit", NULL },
|
|
};
|
|
|
|
m = rb->menu_init(items, sizeof(items) / sizeof(*items),
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
result=rb->menu_show(m);
|
|
|
|
switch (result) {
|
|
case 0: /* Save new game */
|
|
rb->kbd_input(state->filename,MAX_PATH);
|
|
if (save_sudoku(state)) {
|
|
state->editmode=0;
|
|
} else {
|
|
rb->splash(HZ*2, true, "Save failed");
|
|
}
|
|
break;
|
|
|
|
case 1: /* Quit */
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
rb->menu_exit(m);
|
|
|
|
return result;
|
|
}
|
|
|
|
void move_cursor(struct sudoku_state_t* state, int newx, int newy)
|
|
{
|
|
int oldx, oldy;
|
|
|
|
/* Check that the character at the cursor position is legal */
|
|
if (check_status(state)) {
|
|
rb->splash(HZ*2, true, "Illegal move!");
|
|
/* Ignore any button presses during the splash */
|
|
rb->button_clear_queue();
|
|
return;
|
|
}
|
|
|
|
/* Move Cursor */
|
|
oldx=state->x;
|
|
oldy=state->y;
|
|
state->x=newx;
|
|
state->y=newy;
|
|
|
|
/* Redraw current and old cells */
|
|
update_cell(state,oldx,oldy);
|
|
update_cell(state,newx,newy);
|
|
}
|
|
|
|
/* plugin entry point */
|
|
enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
|
|
{
|
|
bool exit;
|
|
int button;
|
|
int lastbutton = BUTTON_NONE;
|
|
int res;
|
|
long ticks;
|
|
struct sudoku_state_t state;
|
|
|
|
/* plugin init */
|
|
rb = api;
|
|
/* end of plugin init */
|
|
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_backdrop(NULL);
|
|
rb->lcd_set_foreground(LCD_BLACK);
|
|
rb->lcd_set_background(LCD_WHITE);
|
|
#endif
|
|
|
|
clear_state(&state);
|
|
|
|
if (parameter==NULL) {
|
|
/* We have been started as a plugin - try default sudoku.ss */
|
|
if (!load_sudoku(&state,GAME_FILE)) {
|
|
/* No previous game saved, use the default */
|
|
default_state(&state);
|
|
}
|
|
} else {
|
|
if (!load_sudoku(&state,(char*)parameter)) {
|
|
rb->splash(HZ*2, true, "Load error");
|
|
return(PLUGIN_ERROR);
|
|
}
|
|
}
|
|
|
|
|
|
display_board(&state);
|
|
|
|
/* The main game loop */
|
|
exit=false;
|
|
ticks=0;
|
|
while(!exit) {
|
|
button = rb->button_get(true);
|
|
|
|
switch(button){
|
|
#ifdef SUDOKU_BUTTON_QUIT
|
|
/* Exit game */
|
|
case SUDOKU_BUTTON_QUIT:
|
|
if (check_status(&state)) {
|
|
rb->splash(HZ*2, true, "Illegal move!");
|
|
/* Ignore any button presses during the splash */
|
|
rb->button_clear_queue();
|
|
} else {
|
|
save_sudoku(&state);
|
|
exit=1;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
/* Increment digit */
|
|
#ifdef SUDOKU_BUTTON_ALTTOGGLE
|
|
case SUDOKU_BUTTON_ALTTOGGLE | BUTTON_REPEAT:
|
|
#endif
|
|
case SUDOKU_BUTTON_TOGGLE | BUTTON_REPEAT:
|
|
/* Slow down the repeat speed to 1/3 second */
|
|
if ((*rb->current_tick-ticks) < (HZ/3)) {
|
|
break;
|
|
}
|
|
|
|
#ifdef SUDOKU_BUTTON_ALTTOGGLE
|
|
case SUDOKU_BUTTON_ALTTOGGLE:
|
|
#endif
|
|
case SUDOKU_BUTTON_TOGGLE:
|
|
#ifdef SUDOKU_BUTTON_TOGGLE_PRE
|
|
if ((button == SUDOKU_BUTTON_TOGGLE)
|
|
&& (lastbutton != SUDOKU_BUTTON_TOGGLE_PRE))
|
|
break;
|
|
#endif
|
|
/* Increment digit */
|
|
ticks=*rb->current_tick;
|
|
if (state.editmode) {
|
|
if (state.startboard[state.y][state.x]=='9') {
|
|
state.startboard[state.y][state.x]='0';
|
|
state.currentboard[state.y][state.x]='0';
|
|
} else {
|
|
state.startboard[state.y][state.x]++;
|
|
state.currentboard[state.y][state.x]++;
|
|
}
|
|
} else {
|
|
if (state.startboard[state.y][state.x]=='0') {
|
|
if (state.currentboard[state.y][state.x]=='9') {
|
|
state.currentboard[state.y][state.x]='0';
|
|
} else {
|
|
state.currentboard[state.y][state.x]++;
|
|
}
|
|
}
|
|
}
|
|
update_cell(&state,state.y,state.x);
|
|
break;
|
|
|
|
#ifdef SUDOKU_BUTTON_TOGGLEBACK
|
|
case SUDOKU_BUTTON_TOGGLEBACK | BUTTON_REPEAT:
|
|
/* Slow down the repeat speed to 1/3 second */
|
|
if ((*rb->current_tick-ticks) < (HZ/3)) {
|
|
break;
|
|
}
|
|
|
|
case SUDOKU_BUTTON_TOGGLEBACK:
|
|
/* Decrement digit */
|
|
ticks=*rb->current_tick;
|
|
if (state.editmode) {
|
|
if (state.startboard[state.y][state.x]=='0') {
|
|
state.startboard[state.y][state.x]='9';
|
|
state.currentboard[state.y][state.x]='9';
|
|
} else {
|
|
state.startboard[state.y][state.x]--;
|
|
state.currentboard[state.y][state.x]--;
|
|
}
|
|
} else {
|
|
if (state.startboard[state.y][state.x]=='0') {
|
|
if (state.currentboard[state.y][state.x]=='0') {
|
|
state.currentboard[state.y][state.x]='9';
|
|
} else {
|
|
state.currentboard[state.y][state.x]--;
|
|
}
|
|
}
|
|
}
|
|
update_cell(&state,state.y,state.x);
|
|
break;
|
|
#endif
|
|
|
|
/* move cursor left */
|
|
case SUDOKU_BUTTON_LEFT:
|
|
case (SUDOKU_BUTTON_LEFT | BUTTON_REPEAT):
|
|
if (state.x==0) {
|
|
#ifndef SUDOKU_BUTTON_UP
|
|
if (state.y==0) {
|
|
move_cursor(&state,8,8);
|
|
} else {
|
|
move_cursor(&state,8,state.y-1);
|
|
}
|
|
#else
|
|
move_cursor(&state,8,state.y);
|
|
#endif
|
|
} else {
|
|
move_cursor(&state,state.x-1,state.y);
|
|
}
|
|
break;
|
|
|
|
/* move cursor right */
|
|
case SUDOKU_BUTTON_RIGHT:
|
|
case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
|
|
if (state.x==8) {
|
|
#ifndef SUDOKU_BUTTON_DOWN
|
|
if (state.y==8) {
|
|
move_cursor(&state,0,0);
|
|
} else {
|
|
move_cursor(&state,0,state.y+1);
|
|
}
|
|
#else
|
|
move_cursor(&state,0,state.y);
|
|
#endif
|
|
} else {
|
|
move_cursor(&state,state.x+1,state.y);
|
|
}
|
|
break;
|
|
|
|
#ifdef SUDOKU_BUTTON_UP
|
|
/* move cursor up */
|
|
case SUDOKU_BUTTON_UP:
|
|
case (SUDOKU_BUTTON_UP | BUTTON_REPEAT):
|
|
if (state.y==0) {
|
|
move_cursor(&state,state.x,8);
|
|
} else {
|
|
move_cursor(&state,state.x,state.y-1);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef SUDOKU_BUTTON_DOWN
|
|
/* move cursor down */
|
|
case SUDOKU_BUTTON_DOWN:
|
|
case (SUDOKU_BUTTON_DOWN | BUTTON_REPEAT):
|
|
if (state.y==8) {
|
|
move_cursor(&state,state.x,0);
|
|
} else {
|
|
move_cursor(&state,state.x,state.y+1);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case SUDOKU_BUTTON_MENU:
|
|
#ifdef SUDOKU_BUTTON_MENU_PRE
|
|
if (lastbutton != SUDOKU_BUTTON_MENU_PRE)
|
|
break;
|
|
#endif
|
|
/* Don't let the user leave a game in a bad state */
|
|
if (check_status(&state)) {
|
|
rb->splash(HZ*2, true, "Illegal move!");
|
|
/* Ignore any button presses during the splash */
|
|
rb->button_clear_queue();
|
|
} else {
|
|
if (state.editmode) {
|
|
res = sudoku_edit_menu(&state);
|
|
if (res == MENU_ATTACHED_USB) {
|
|
return PLUGIN_USB_CONNECTED;
|
|
} else if (res == 1) { /* Quit */
|
|
return PLUGIN_OK;
|
|
}
|
|
} else {
|
|
if (sudoku_menu(&state)) {
|
|
return PLUGIN_USB_CONNECTED;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
case SUDOKU_BUTTON_POSSIBLE:
|
|
/* Toggle current number in the possiblevals structure */
|
|
if (state.currentboard[state.y][state.x]!='0') {
|
|
state.possiblevals[state.y][state.x]^=
|
|
(1 << (state.currentboard[state.y][state.x] - '0'));
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
|
|
/* Quit if USB has been connected */
|
|
return PLUGIN_USB_CONNECTED;
|
|
}
|
|
break;
|
|
}
|
|
if (button != BUTTON_NONE)
|
|
lastbutton = button;
|
|
|
|
display_board(&state);
|
|
}
|
|
|
|
return PLUGIN_OK;
|
|
}
|
|
|
|
#endif
|