d9002eb94a
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@24024 a1c6a512-1295-4272-9138-f99709370657
1186 lines
30 KiB
C
1186 lines
30 KiB
C
/* sudoku.c - sudoku game
|
|
*
|
|
* Writing a fun Su-Do-Ku game has turned out to be a difficult exercise.
|
|
* The biggest difficulty is keeping the game fun - and this means allowing
|
|
* the user to make mistakes. The game is not much fun if it prevents the
|
|
* user from making moves, or if it informs them of an incorrect move.
|
|
* With movement constraints, the 'game' is little more than an automated
|
|
* solver (and no fun at all).
|
|
*
|
|
* Another challenge is generating good puzzles that are entertaining to
|
|
* solve. It is certainly true that there is an art to creating good
|
|
* Su-Do-Ku puzzles, and that good hand generated puzzles are more
|
|
* entertaining than many computer generated puzzles - I just hope that
|
|
* the algorithm implemented here provides fun puzzles. It is an area
|
|
* that needs work. The puzzle classification is very simple, and could
|
|
* also do with work. Finally, understanding the automatically generated
|
|
* hints is sometimes more work than solving the puzzle - a better, and
|
|
* more human friendly, mechanism is needed.
|
|
*
|
|
* Comments, suggestions, and contributions are always welcome - send email
|
|
* to: mike 'at' laurasia.com.au. Note that this code assumes a single
|
|
* threaded process, makes extensive use of global variables, and has
|
|
* not been written to be reused in other applications. The code makes no
|
|
* use of dynamic memory allocation, and hence, requires no heap. It should
|
|
* also run with minimal stack space.
|
|
*
|
|
* This code and accompanying files have been placed into the public domain
|
|
* by Michael Kennett, July 2005. It is provided without any warranty
|
|
* whatsoever, and in no event shall Michael Kennett be liable for
|
|
* any damages of any kind, however caused, arising from this software.
|
|
*/
|
|
|
|
#include "plugin.h"
|
|
|
|
#include "sudoku.h"
|
|
#include "templates.h"
|
|
|
|
#define assert(x)
|
|
|
|
/* Common state encoding in a 32-bit integer:
|
|
* bits 0-6 index
|
|
* 7-15 state [bit high signals digits not possible]
|
|
* 16-19 digit
|
|
* 20 fixed [set if digit initially fixed]
|
|
* 21 choice [set if solver chose this digit]
|
|
* 22 ignore [set if ignored by reapply()]
|
|
* 23 unused
|
|
* 24-26 hint
|
|
* 27-31 unused
|
|
*/
|
|
#define INDEX_MASK 0x0000007f
|
|
#define GET_INDEX(val) (INDEX_MASK&(val))
|
|
#define SET_INDEX(val) (val)
|
|
|
|
#define STATE_MASK 0x0000ff80
|
|
#define STATE_SHIFT (7-1) /* digits 1..9 */
|
|
#define DIGIT_STATE(digit) BIT_N(STATE_SHIFT+(digit))
|
|
|
|
#define DIGIT_MASK 0x000f0000
|
|
#define DIGIT_SHIFT 16
|
|
#define GET_DIGIT(val) (((val)&DIGIT_MASK)>>(DIGIT_SHIFT))
|
|
#define SET_DIGIT(val) ((val)<<(DIGIT_SHIFT))
|
|
|
|
#define FIXED 0x00100000
|
|
#define CHOICE 0x00200000
|
|
#define IGNORED 0x00400000
|
|
|
|
/* Hint codes (c.f. singles(), pairs(), findmoves()) */
|
|
#define HINT_ROW 0x01000000
|
|
#define HINT_COLUMN 0x02000000
|
|
#define HINT_BLOCK 0x04000000
|
|
|
|
/* For a general board it may be necessary to do backtracking (i.e. to
|
|
* rewind the board to an earlier state), and make choices during the
|
|
* solution process. This can be implemented naturally using recursion,
|
|
* but it is more efficient to maintain a single board.
|
|
*/
|
|
static int board[ 81 ];
|
|
|
|
/* Addressing board elements: linear array 0..80 */
|
|
#define ROW(idx) ((idx)/9)
|
|
#define COLUMN(idx) ((idx)%9)
|
|
#define BLOCK(idx) (3*(ROW(idx)/3)+(COLUMN(idx)/3))
|
|
#define INDEX(row,col) (9*(row)+(col))
|
|
|
|
/* Blocks indexed 0..9 */
|
|
#define IDX_BLOCK(row,col) (3*((row)/3)+((col)/3))
|
|
#define TOP_LEFT(block) (INDEX(block/3,block%3))
|
|
|
|
/* Board state */
|
|
#define STATE(idx) ((board[idx])&STATE_MASK)
|
|
#define DIGIT(idx) (GET_DIGIT(board[idx]))
|
|
#define HINT(idx) ((board[idx])&HINT_MASK)
|
|
#define IS_EMPTY(idx) (0 == DIGIT(idx))
|
|
#define DISALLOWED(idx,digit) ((board[idx])&DIGIT_STATE(digit))
|
|
#define IS_FIXED(idx) (board[idx]&FIXED)
|
|
|
|
/* Record move history, and maintain a counter for the current
|
|
* move number. Concessions are made for the user interface, and
|
|
* allow digit 0 to indicate clearing a square. The move history
|
|
* is used to support 'undo's for the user interface, and hence
|
|
* is larger than required - there is sufficient space to solve
|
|
* the puzzle, undo every move, and then redo the puzzle - and
|
|
* if the user requires more space, then the full history will be
|
|
* lost.
|
|
*/
|
|
static int idx_history;
|
|
static int history[ 3 * 81 ];
|
|
|
|
/* Possible moves for a given board (c.f. fillmoves()).
|
|
* Also used by choice() when the deterministic solver has failed,
|
|
* and for calculating user hints. The number of hints is stored
|
|
* in num_hints, or -1 if no hints calculated. The number of hints
|
|
* requested by the user since their last move is stored in req_hints;
|
|
* if the user keeps requesting hints, start giving more information.
|
|
* Finally, record the last hint issued to the user; attempt to give
|
|
* different hints each time.
|
|
*/
|
|
static int idx_possible;
|
|
static int possible[ 81 ];
|
|
|
|
static int pass; /* count # passes of deterministic solver */
|
|
|
|
/* Support for template file */
|
|
static int tmplt[ 81 ]; /* Template indices */
|
|
static int len_tmplt; /* Number of template indices */
|
|
|
|
/* Reset global state */
|
|
static
|
|
void
|
|
reset( void )
|
|
{
|
|
rb->memset( board, 0x00, sizeof( board ) );
|
|
rb->memset( history, 0x00, sizeof( history ) );
|
|
idx_history = 0;
|
|
pass = 0;
|
|
}
|
|
|
|
/* Management of the move history - compression */
|
|
static
|
|
void
|
|
compress( int limit )
|
|
{
|
|
int i, j;
|
|
for( i = j = 0 ; i < idx_history && j < limit ; ++i )
|
|
if( !( history[ i ] & IGNORED ) )
|
|
history[ j++ ] = history[ i ];
|
|
for( ; i < idx_history ; ++i )
|
|
history[ j++ ] = history[ i ];
|
|
idx_history = j;
|
|
}
|
|
|
|
/* Management of the move history - adding a move */
|
|
static
|
|
void
|
|
add_move( int idx, int digit, int choice )
|
|
{
|
|
int i;
|
|
|
|
if( sizeof( history ) / sizeof( int ) == idx_history )
|
|
compress( 81 );
|
|
|
|
/* Never ignore the last move */
|
|
history[ idx_history++ ] = SET_INDEX( idx ) | SET_DIGIT( digit ) | choice;
|
|
|
|
/* Ignore all previous references to idx */
|
|
for( i = idx_history - 2 ; 0 <= i ; --i )
|
|
if( GET_INDEX( history[ i ] ) == idx )
|
|
{
|
|
history[ i ] |= IGNORED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Iteration over rows/columns/blocks handled by specialised code.
|
|
* Each function returns a block index - call must manage element/idx.
|
|
*/
|
|
static
|
|
int
|
|
idx_row( int el, int idx ) /* Index within a row */
|
|
{
|
|
return INDEX( el, idx );
|
|
}
|
|
|
|
static
|
|
int
|
|
idx_column( int el, int idx ) /* Index within a column */
|
|
{
|
|
return INDEX( idx, el );
|
|
}
|
|
|
|
static
|
|
int
|
|
idx_block( int el, int idx ) /* Index within a block */
|
|
{
|
|
return INDEX( 3 * ( el / 3 ) + idx / 3, 3 * ( el % 3 ) + idx % 3 );
|
|
}
|
|
|
|
/* Update board state after setting a digit (clearing not handled)
|
|
*/
|
|
static
|
|
void
|
|
update( int idx )
|
|
{
|
|
const int row = ROW( idx );
|
|
const int col = COLUMN( idx );
|
|
const int block = IDX_BLOCK( row, col );
|
|
const int mask = DIGIT_STATE( DIGIT( idx ) );
|
|
int i;
|
|
|
|
board[ idx ] |= STATE_MASK; /* filled - no choice possible */
|
|
|
|
/* Digit cannot appear in row, column or block */
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
board[ idx_row( row, i ) ] |= mask;
|
|
board[ idx_column( col, i ) ] |= mask;
|
|
board[ idx_block( block, i ) ] |= mask;
|
|
}
|
|
}
|
|
|
|
/* Refresh board state, given move history. Note that this can yield
|
|
* an incorrect state if the user has made errors - return -1 if an
|
|
* incorrect state is generated; else return 0 for a correct state.
|
|
*/
|
|
static
|
|
int
|
|
reapply( void )
|
|
{
|
|
int digit, idx, j;
|
|
int allok = 0;
|
|
rb->memset( board, 0x00, sizeof( board ) );
|
|
for( j = 0 ; j < idx_history ; ++j )
|
|
if( !( history[ j ] & IGNORED ) && 0 != GET_DIGIT( history[ j ] ) )
|
|
{
|
|
idx = GET_INDEX( history[ j ] );
|
|
digit = GET_DIGIT( history[ j ] );
|
|
if( !IS_EMPTY( idx ) || DISALLOWED( idx, digit ) )
|
|
allok = -1;
|
|
board[ idx ] = SET_DIGIT( digit );
|
|
if( history[ j ] & FIXED )
|
|
board[ idx ] |= FIXED;
|
|
update( idx );
|
|
}
|
|
return allok;
|
|
}
|
|
|
|
/* Clear moves, leaving fixed squares
|
|
*/
|
|
static
|
|
void
|
|
clear_moves( void )
|
|
{
|
|
for( idx_history = 0 ; history[ idx_history ] & FIXED ; ++idx_history )
|
|
;
|
|
reapply( );
|
|
}
|
|
|
|
static int digits[ 9 ]; /* # digits expressed in element square */
|
|
static int counts[ 9 ]; /* Count of digits (c.f. count_set_digits()) */
|
|
|
|
/* Count # set bits (within STATE_MASK) */
|
|
static
|
|
int
|
|
numset( int mask )
|
|
{
|
|
int i, n = 0;
|
|
for( i = STATE_SHIFT + 1 ; i <= STATE_SHIFT + 9 ; ++i )
|
|
if( mask & BIT_N(i) )
|
|
++n;
|
|
else
|
|
++counts[ i - STATE_SHIFT - 1 ];
|
|
return n;
|
|
}
|
|
|
|
static
|
|
void
|
|
count_set_digits( int el, int (*idx_fn)( int, int ) )
|
|
{
|
|
int i;
|
|
rb->memset( counts, 0x00, sizeof( counts ) );
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
digits[ i ] = numset( board[ (*idx_fn)( el, i ) ] );
|
|
}
|
|
|
|
/* Fill square with given digit, and update state.
|
|
* Returns 0 on success, else -1 on error (i.e. invalid fill)
|
|
*/
|
|
static
|
|
int
|
|
fill( int idx, int digit )
|
|
{
|
|
assert( 0 != digit );
|
|
|
|
if( !IS_EMPTY( idx ) )
|
|
return ( DIGIT( idx ) == digit ) ? 0 : -1;
|
|
|
|
if( DISALLOWED( idx, digit ) )
|
|
return -1;
|
|
|
|
board[ idx ] = SET_DIGIT( digit );
|
|
update( idx );
|
|
add_move( idx, digit, 0 );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Find all squares with a single digit allowed -- do not mutate board */
|
|
static
|
|
void
|
|
singles( int el, int (*idx_fn)( int, int ), int hintcode )
|
|
{
|
|
int i, j, idx;
|
|
|
|
count_set_digits( el, idx_fn );
|
|
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
if( 1 == counts[ i ] )
|
|
{
|
|
/* Digit 'i+1' appears just once in the element */
|
|
for( j = 0 ; j < 9 ; ++j )
|
|
{
|
|
idx = (*idx_fn)( el, j );
|
|
if( !DISALLOWED( idx, i + 1 ) && idx_possible < 81 )
|
|
possible[ idx_possible++ ] = SET_INDEX( idx )
|
|
| SET_DIGIT( i + 1 )
|
|
| hintcode;
|
|
}
|
|
}
|
|
if( 8 == digits[ i ] )
|
|
{
|
|
/* 8 digits are masked at this position - just one remaining */
|
|
idx = (*idx_fn)( el, i );
|
|
for( j = 1 ; j <= 9 ; ++j )
|
|
if( 0 == ( STATE( idx ) & DIGIT_STATE( j ) ) && idx_possible < 81 )
|
|
possible[ idx_possible++ ] = SET_INDEX( idx )
|
|
| SET_DIGIT( j )
|
|
| hintcode;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Given the board state, find all possible 'moves' (i.e. squares with just
|
|
* a single digit).
|
|
*
|
|
* Returns the number of (deterministic) moves (and fills the moves array),
|
|
* or 0 if no moves are possible. This function does not mutate the board
|
|
* state, and hence, can return the same move multiple times (with
|
|
* different hints).
|
|
*/
|
|
static
|
|
int
|
|
findmoves( void )
|
|
{
|
|
int i;
|
|
|
|
rb->yield();
|
|
|
|
idx_possible = 0;
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
singles( i, idx_row, HINT_ROW );
|
|
singles( i, idx_column, HINT_COLUMN );
|
|
singles( i, idx_block, HINT_BLOCK );
|
|
}
|
|
return idx_possible;
|
|
}
|
|
|
|
/* Strategies for refining the board state
|
|
* - 'pairs' if there are two unfilled squares in a given row/column/
|
|
* block with the same state, and just two possibilities,
|
|
* then all other unfilled squares in the row/column/block
|
|
* CANNOT be either of these digits.
|
|
* - 'block' if the unknown squares in a block all appear in the same
|
|
* row or column, then all unknown squares outside the block
|
|
* and in the same row/column cannot be any of the unknown
|
|
* squares in the block.
|
|
* - 'common' if all possible locations for a digit in a block appear
|
|
* in a row or column, then that digit cannot appear outside
|
|
* the block in the same row or column.
|
|
* - 'position2' if the positions of 2 unknown digits in a block match
|
|
* identically in precisely 2 positions, then those 2 positions
|
|
* can only contain the 2 unknown digits.
|
|
*
|
|
* Recall that each state bit uses a 1 to prevent a digit from
|
|
* filling that square.
|
|
*/
|
|
|
|
static
|
|
void
|
|
pairs( int el, int (*idx_fn)( int, int ) )
|
|
{
|
|
int i, j, k, mask, idx;
|
|
|
|
rb->yield();
|
|
|
|
for( i = 0 ; i < 8 ; ++i )
|
|
if( 7 == digits[ i ] ) /* 2 digits unknown */
|
|
for( j = i + 1 ; j < 9 ; ++j )
|
|
{
|
|
idx = (*idx_fn)( el, i );
|
|
if( STATE( idx ) == STATE( (*idx_fn)( el, j ) ) )
|
|
{
|
|
/* Found a row/column pair - mask other entries */
|
|
mask = STATE_MASK ^ (STATE_MASK & board[ idx ] );
|
|
for( k = 0 ; k < i ; ++k )
|
|
board[ (*idx_fn)( el, k ) ] |= mask;
|
|
for( k = i + 1 ; k < j ; ++k )
|
|
board[ (*idx_fn)( el, k ) ] |= mask;
|
|
for( k = j + 1 ; k < 9 ; ++k )
|
|
board[ (*idx_fn)( el, k ) ] |= mask;
|
|
digits[ j ] = -1; /* now processed */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Worker: mask elements outside block */
|
|
static
|
|
void
|
|
exmask( int mask, int block, int el, int (*idx_fn)( int, int ) )
|
|
{
|
|
int i, idx;
|
|
|
|
rb->yield();
|
|
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
idx = (*idx_fn)( el, i );
|
|
if( block != BLOCK( idx ) && IS_EMPTY( idx ) )
|
|
board[ idx ] |= mask;
|
|
}
|
|
}
|
|
|
|
/* Worker for block() */
|
|
static
|
|
void
|
|
exblock( int block, int el, int (*idx_fn)( int, int ) )
|
|
{
|
|
int i, idx, mask;
|
|
|
|
rb->yield();
|
|
|
|
/* By assumption, all unknown squares in the block appear in the
|
|
* same row/column, so to construct a mask for these squares, it
|
|
* is sufficient to invert the mask for the known squares in the
|
|
* block.
|
|
*/
|
|
mask = 0;
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
idx = idx_block( block, i );
|
|
if( !IS_EMPTY( idx ) )
|
|
mask |= DIGIT_STATE( DIGIT( idx ) );
|
|
}
|
|
exmask( mask ^ STATE_MASK, block, el, idx_fn );
|
|
}
|
|
|
|
static
|
|
void
|
|
block( int el )
|
|
{
|
|
int i, idx = 0, row, col;
|
|
|
|
rb->yield();
|
|
|
|
/* Find first unknown square */
|
|
for( i = 0 ; i < 9 && !IS_EMPTY( idx = idx_block( el, i ) ) ; ++i )
|
|
;
|
|
if( i < 9 )
|
|
{
|
|
assert( IS_EMPTY( idx ) );
|
|
row = ROW( idx );
|
|
col = COLUMN( idx );
|
|
for( ++i ; i < 9 ; ++i )
|
|
{
|
|
idx = idx_block( el, i );
|
|
if( IS_EMPTY( idx ) )
|
|
{
|
|
if( ROW( idx ) != row )
|
|
row = -1;
|
|
if( COLUMN( idx ) != col )
|
|
col = -1;
|
|
}
|
|
}
|
|
if( 0 <= row )
|
|
exblock( el, row, idx_row );
|
|
if( 0 <= col )
|
|
exblock( el, col, idx_column );
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
common( int el )
|
|
{
|
|
int i, idx, row, col, digit, mask;
|
|
|
|
rb->yield();
|
|
|
|
for( digit = 1 ; digit <= 9 ; ++digit )
|
|
{
|
|
mask = DIGIT_STATE( digit );
|
|
row = col = -1; /* Value '9' indicates invalid */
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
/* Digit possible? */
|
|
idx = idx_block( el, i );
|
|
if( IS_EMPTY( idx ) && 0 == ( board[ idx ] & mask ) )
|
|
{
|
|
if( row < 0 )
|
|
row = ROW( idx );
|
|
else
|
|
if( row != ROW( idx ) )
|
|
row = 9; /* Digit appears in multiple rows */
|
|
if( col < 0 )
|
|
col = COLUMN( idx );
|
|
else
|
|
if( col != COLUMN( idx ) )
|
|
col = 9; /* Digit appears in multiple columns */
|
|
}
|
|
}
|
|
if( -1 != row && row < 9 )
|
|
exmask( mask, el, row, idx_row );
|
|
if( -1 != col && col < 9 )
|
|
exmask( mask, el, col, idx_column );
|
|
}
|
|
}
|
|
|
|
/* Encoding of positions of a digit (c.f. position2()) - abuse DIGIT_STATE */
|
|
static int posn_digit[ 10 ];
|
|
|
|
static
|
|
void
|
|
position2( int el )
|
|
{
|
|
int digit, digit2, i, mask, mask2, posn, count, idx;
|
|
|
|
rb->yield();
|
|
|
|
/* Calculate positions of each digit within block */
|
|
for( digit = 1 ; digit <= 9 ; ++digit )
|
|
{
|
|
mask = DIGIT_STATE( digit );
|
|
posn_digit[ digit ] = count = posn = 0;
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
if( 0 == ( mask & board[ idx_block( el, i ) ] ) )
|
|
{
|
|
++count;
|
|
posn |= DIGIT_STATE( i );
|
|
}
|
|
if( 2 == count )
|
|
posn_digit[ digit ] = posn;
|
|
}
|
|
/* Find pairs of matching positions, and mask */
|
|
for( digit = 1 ; digit < 9 ; ++digit )
|
|
if( 0 != posn_digit[ digit ] )
|
|
for( digit2 = digit + 1 ; digit2 <= 9 ; ++digit2 )
|
|
if( posn_digit[ digit ] == posn_digit[ digit2 ] )
|
|
{
|
|
mask = STATE_MASK
|
|
^ ( DIGIT_STATE( digit ) | DIGIT_STATE( digit2 ) );
|
|
mask2 = DIGIT_STATE( digit );
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
idx = idx_block( el, i );
|
|
if( 0 == ( mask2 & board[ idx ] ) )
|
|
{
|
|
assert( 0 == (DIGIT_STATE(digit2) & board[idx]) );
|
|
board[ idx ] |= mask;
|
|
}
|
|
}
|
|
posn_digit[ digit ] = posn_digit[ digit2 ] = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Find some moves for the board; starts with a simple approach (finding
|
|
* singles), and if no moves found, starts using more involved strategies
|
|
* until a move is found. The more advanced strategies can mask states
|
|
* in the board, making this an efficient mechanism, but difficult for
|
|
* a human to understand.
|
|
*/
|
|
static
|
|
int
|
|
allmoves( void )
|
|
{
|
|
int i, n;
|
|
|
|
rb->yield();
|
|
|
|
n = findmoves( );
|
|
if( 0 < n )
|
|
return n;
|
|
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
count_set_digits( i, idx_row );
|
|
pairs( i, idx_row );
|
|
|
|
count_set_digits( i, idx_column );
|
|
pairs( i, idx_column );
|
|
|
|
count_set_digits( i, idx_block );
|
|
pairs( i, idx_block );
|
|
}
|
|
n = findmoves( );
|
|
if( 0 < n )
|
|
return n;
|
|
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
block( i );
|
|
common( i );
|
|
position2( i );
|
|
}
|
|
return findmoves( );
|
|
}
|
|
|
|
/* Helper: sort based on index */
|
|
static
|
|
int
|
|
cmpindex( const void * a, const void * b )
|
|
{
|
|
return GET_INDEX( *((const int *)b) ) - GET_INDEX( *((const int *)a) );
|
|
}
|
|
|
|
/* Return number of hints. The hints mechanism should attempt to find
|
|
* 'easy' moves first, and if none are possible, then try for more
|
|
* cryptic moves.
|
|
*/
|
|
int
|
|
findhints( void )
|
|
{
|
|
int i, n, mutated = 0;
|
|
|
|
rb->yield();
|
|
|
|
n = findmoves( );
|
|
if( n < 2 )
|
|
{
|
|
/* Each call to pairs() can mutate the board state, making the
|
|
* hints very, very cryptic... so later undo the mutations.
|
|
*/
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
count_set_digits( i, idx_row );
|
|
pairs( i, idx_row );
|
|
|
|
count_set_digits( i, idx_column );
|
|
pairs( i, idx_column );
|
|
|
|
count_set_digits( i, idx_block );
|
|
pairs( i, idx_block );
|
|
}
|
|
mutated = 1;
|
|
n = findmoves( );
|
|
}
|
|
if( n < 2 )
|
|
{
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
{
|
|
block( i );
|
|
common( i );
|
|
}
|
|
mutated = 1;
|
|
n = findmoves( );
|
|
}
|
|
|
|
/* Sort the possible moves, and allow just one hint per square */
|
|
if( 0 < n )
|
|
{
|
|
int i, j;
|
|
|
|
rb->qsort( possible, n, sizeof( int ), cmpindex );
|
|
for( i = 0, j = 1 ; j < n ; ++j )
|
|
{
|
|
if( GET_INDEX( possible[ i ] ) == GET_INDEX( possible[ j ] ) )
|
|
{
|
|
/* Let the user make mistakes - do not assume the
|
|
* board is in a consistent state.
|
|
*/
|
|
if( GET_DIGIT( possible[i] ) == GET_DIGIT( possible[j] ) )
|
|
possible[ i ] |= possible[ j ];
|
|
}
|
|
else
|
|
i = j;
|
|
}
|
|
n = i + 1;
|
|
}
|
|
|
|
/* Undo any mutations of the board state */
|
|
if( mutated )
|
|
reapply( );
|
|
|
|
return n;
|
|
}
|
|
|
|
/* Deterministic solver; return 0 on success, else -1 on error.
|
|
*/
|
|
static
|
|
int
|
|
deterministic( void )
|
|
{
|
|
int i, n;
|
|
|
|
rb->yield();
|
|
|
|
n = allmoves( );
|
|
while( 0 < n )
|
|
{
|
|
++pass;
|
|
for( i = 0 ; i < n ; ++i )
|
|
if( -1 == fill( GET_INDEX( possible[ i ] ),
|
|
GET_DIGIT( possible[ i ] ) ) )
|
|
return -1;
|
|
n = allmoves( );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Return index of square for choice.
|
|
*
|
|
* If no choice is possible (i.e. board solved or inconsistent),
|
|
* return -1.
|
|
*
|
|
* The current implementation finds a square with the minimum
|
|
* number of unknown digits (i.e. maximum # masked digits).
|
|
*/
|
|
static
|
|
int
|
|
cmp( const void * e1, const void * e2 )
|
|
{
|
|
return GET_DIGIT( *(const int *)e2 ) - GET_DIGIT( *(const int *)e1 );
|
|
}
|
|
|
|
static
|
|
int
|
|
choice( void )
|
|
{
|
|
int i, n;
|
|
|
|
rb->yield();
|
|
|
|
for( n = i = 0 ; i < 81 ; ++i )
|
|
if( IS_EMPTY( i ) )
|
|
{
|
|
possible[ n ] = SET_INDEX( i ) | SET_DIGIT( numset( board[ i ] ) );
|
|
|
|
/* Inconsistency if square unknown, but nothing possible */
|
|
if( 9 == GET_DIGIT( possible[ n ] ) )
|
|
return -2;
|
|
++n;
|
|
}
|
|
|
|
if( 0 == n )
|
|
return -1; /* All squares known */
|
|
|
|
rb->qsort( possible, n, sizeof( possible[ 0 ] ), cmp );
|
|
return GET_INDEX( possible[ 0 ] );
|
|
}
|
|
|
|
/* Choose a digit for the given square.
|
|
* The starting digit is passed as a parameter.
|
|
* Returns -1 if no choice possible.
|
|
*/
|
|
static
|
|
int
|
|
choose( int idx, int digit )
|
|
{
|
|
rb->yield();
|
|
|
|
for( ; digit <= 9 ; ++digit )
|
|
if( !DISALLOWED( idx, digit ) )
|
|
{
|
|
board[ idx ] = SET_DIGIT( digit );
|
|
update( idx );
|
|
add_move( idx, digit, CHOICE );
|
|
return digit;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Backtrack to a previous choice point, and attempt to reseed
|
|
* the search. Return -1 if no further choice possible, or
|
|
* the index of the changed square.
|
|
*
|
|
* Assumes that the move history and board are valid.
|
|
*/
|
|
static
|
|
int
|
|
backtrack( void )
|
|
{
|
|
int digit, idx;
|
|
|
|
rb->yield();
|
|
|
|
for( ; 0 <= --idx_history ; )
|
|
if( history[ idx_history ] & CHOICE )
|
|
{
|
|
/* Remember the last choice, and advance */
|
|
idx = GET_INDEX( history[ idx_history ] );
|
|
digit = GET_DIGIT( history[ idx_history ] ) + 1;
|
|
reapply( );
|
|
if( -1 != choose( idx, digit ) )
|
|
return idx;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Attempt to solve 'board'; return 0 on success else -1 on error.
|
|
*
|
|
* The solution process attempts to fill-in deterministically as
|
|
* much of the board as possible. Once that is no longer possible,
|
|
* need to choose a square to fill in.
|
|
*/
|
|
static
|
|
int
|
|
solve( void )
|
|
{
|
|
int idx;
|
|
|
|
rb->yield();
|
|
|
|
while( 1 )
|
|
{
|
|
if( 0 == deterministic( ) )
|
|
{
|
|
/* Solved, make a new choice, or rewind a previous choice */
|
|
idx = choice( );
|
|
if( -1 == idx )
|
|
return 0;
|
|
else
|
|
if( ( idx < 0 || -1 == choose( idx, 1 ) ) && -1 == backtrack( ) )
|
|
return -1;
|
|
}
|
|
else /* rewind to a previous choice */
|
|
if( -1 == backtrack( ) )
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static
|
|
int
|
|
init_template( int template )
|
|
{
|
|
int i, row, col;
|
|
int mask;
|
|
|
|
reset( );
|
|
len_tmplt = 0;
|
|
|
|
/* Consume grid - allow leading spaces and comments at end */
|
|
for( row = 0 ; row < 9 ; ++row )
|
|
{
|
|
mask=0x100;
|
|
for( col = 0 ; col < 9 ; ++col )
|
|
{
|
|
if (templates[template][row] & mask)
|
|
tmplt[ len_tmplt++ ] = INDEX( row, col );
|
|
mask /= 2;
|
|
}
|
|
}
|
|
|
|
/* Construct move history for a template */
|
|
idx_history = 0;
|
|
for( i = 0 ; i < 81 ; ++i )
|
|
if( 0 != DIGIT( i ) )
|
|
history[ idx_history++ ] = i | (DIGIT( i )<<8);
|
|
|
|
/* Finally, markup all of these moves as 'fixed' */
|
|
for( i = 0 ; i < idx_history ; ++i )
|
|
history[ i ] |= FIXED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Classify a SuDoKu, given its solution.
|
|
*
|
|
* The classification is based on the average number of possible moves
|
|
* for each pass of the deterministic solver - it is a rather simplistic
|
|
* measure, but gives reasonable results. Note also that the classification
|
|
* is based on the first solution found (but does handle the pathological
|
|
* case of multiple solutions). Note that the average moves per pass
|
|
* depends just on the number of squares initially set... this simplifies
|
|
* the statistics collection immensely, requiring just the number of passes
|
|
* to be counted.
|
|
*
|
|
* Return 0 on error, else a string classification.
|
|
*/
|
|
|
|
static
|
|
char *
|
|
classify( void )
|
|
{
|
|
int i, score;
|
|
|
|
rb->yield();
|
|
|
|
pass = 0;
|
|
clear_moves( );
|
|
if( -1 == solve( ) )
|
|
return 0;
|
|
|
|
score = 81;
|
|
for( i = 0 ; i < 81 ; ++i )
|
|
if( IS_FIXED( i ) )
|
|
--score;
|
|
|
|
assert( 81 == idx_history );
|
|
|
|
for( i = 0 ; i < 81 ; ++i )
|
|
if( history[ i ] & CHOICE )
|
|
score -= 5;
|
|
|
|
if( 15 * pass < score )
|
|
return "very easy";
|
|
else
|
|
if( 11 * pass < score )
|
|
return "easy";
|
|
else
|
|
if( 7 * pass < score )
|
|
return "medium";
|
|
else
|
|
if( 4 * pass < score )
|
|
return "hard";
|
|
else
|
|
return "fiendish";
|
|
}
|
|
|
|
/* exchange disjoint, identical length blocks of data */
|
|
static
|
|
void
|
|
exchange( int * a, int * b, int len )
|
|
{
|
|
int i, tmp;
|
|
for( i = 0 ; i < len ; ++i )
|
|
{
|
|
tmp = a[ i ];
|
|
a[ i ] = b[ i ];
|
|
b[ i ] = tmp;
|
|
}
|
|
}
|
|
|
|
/* rotate left */
|
|
static
|
|
void
|
|
rotate1_left( int * a, int len )
|
|
{
|
|
int i, tmp;
|
|
tmp = a[ 0 ];
|
|
for( i = 1 ; i < len ; ++i )
|
|
a[ i - 1 ] = a[ i ];
|
|
a[ len - 1 ] = tmp;
|
|
}
|
|
|
|
/* rotate right */
|
|
static
|
|
void
|
|
rotate1_right( int * a, int len )
|
|
{
|
|
int i, tmp;
|
|
tmp = a[ len - 1 ];
|
|
for( i = len - 1 ; 0 < i ; --i )
|
|
a[ i ] = a[ i - 1 ];
|
|
a[ 0 ] = tmp;
|
|
}
|
|
|
|
/* Generalised left rotation - there is a naturally recursive
|
|
* solution that is best implementation using iteration.
|
|
* Note that it is not necessary to do repeated unit rotations.
|
|
*
|
|
* This function is analogous to 'cutting' a 'pack of cards'.
|
|
*
|
|
* On entry: 0 < idx < len
|
|
*/
|
|
static
|
|
void
|
|
rotate( int * a, int len, int idx )
|
|
{
|
|
int xdi = len - idx;
|
|
int delta = idx - xdi;
|
|
|
|
while( 0 != delta && 0 != idx )
|
|
{
|
|
if( delta < 0 )
|
|
{
|
|
if( 1 == idx )
|
|
{
|
|
rotate1_left( a, len );
|
|
idx = 0;
|
|
}
|
|
else
|
|
{
|
|
exchange( a, a + xdi, idx );
|
|
len = xdi;
|
|
}
|
|
}
|
|
else /* 0 < delta */
|
|
{
|
|
if( 1 == xdi )
|
|
{
|
|
rotate1_right( a, len );
|
|
idx = 0;
|
|
}
|
|
else
|
|
{
|
|
exchange( a, a + idx, xdi );
|
|
a += xdi;
|
|
len = idx;
|
|
idx -= xdi;
|
|
}
|
|
}
|
|
xdi = len - idx;
|
|
delta = idx - xdi;
|
|
}
|
|
if( 0 < idx )
|
|
exchange( a, a + idx, idx );
|
|
}
|
|
|
|
/* Shuffle an array of integers */
|
|
static
|
|
void
|
|
shuffle( int * a, int len )
|
|
{
|
|
int i, j, tmp;
|
|
|
|
i = len;
|
|
while( 1 <= i )
|
|
{
|
|
j = rb->rand( ) % i;
|
|
tmp = a[ --i ];
|
|
a[ i ] = a[ j ];
|
|
a[ j ] = tmp;
|
|
}
|
|
}
|
|
|
|
/* Generate a SuDoKu puzzle
|
|
*
|
|
* The generation process selects a random template, and then attempts
|
|
* to fill in the exposed squares to generate a board. The order of the
|
|
* digits and of filling in the exposed squares are random.
|
|
*/
|
|
|
|
/* Select random template; sets tmplt, len_tmplt */
|
|
static
|
|
void
|
|
select_template( void )
|
|
{
|
|
int i = rb->rand( ) % NUM_TEMPLATES;
|
|
init_template( i );
|
|
}
|
|
|
|
static bool check_buttons(void)
|
|
{
|
|
int button = rb->button_get(false);
|
|
return (button && (!(button & BUTTON_REL)) && (!(button & BUTTON_REPEAT)));
|
|
}
|
|
|
|
static
|
|
bool
|
|
generate( void )
|
|
{
|
|
static int digits[ 9 ];
|
|
|
|
int i;
|
|
|
|
start:
|
|
/* Allow the user to abort generation by pressing any button */
|
|
if (check_buttons())
|
|
return false;
|
|
|
|
for( i = 0 ; i < 9 ; ++i )
|
|
digits[ i ] = i + 1;
|
|
|
|
rotate( digits, 9, 1 + rb->rand( ) % 8 );
|
|
shuffle( digits, 9 );
|
|
select_template( );
|
|
|
|
rotate( tmplt, len_tmplt, 1 + rb->rand( ) % ( len_tmplt - 1 ) );
|
|
shuffle( tmplt, len_tmplt );
|
|
|
|
reset( ); /* construct a new board */
|
|
|
|
for( i = 0 ; i < len_tmplt ; ++i )
|
|
fill( tmplt[ i ], digits[ i % 9 ] );
|
|
|
|
/* Allow the user to abort generation by pressing any button */
|
|
if (check_buttons())
|
|
return false;
|
|
|
|
rb->yield();
|
|
|
|
if( 0 != solve( ) || idx_history < 81 )
|
|
goto start;
|
|
|
|
/* Allow the user to abort generation by pressing any button */
|
|
if (check_buttons())
|
|
return false;
|
|
|
|
rb->yield();
|
|
|
|
for( i = 0 ; i < len_tmplt ; ++i )
|
|
board[ tmplt[ i ] ] |= FIXED;
|
|
|
|
/* Construct fixed squares */
|
|
for( idx_history = i = 0 ; i < 81 ; ++i )
|
|
if( IS_FIXED( i ) )
|
|
history[ idx_history++ ] = SET_INDEX( i )
|
|
| SET_DIGIT( DIGIT( i ) )
|
|
| FIXED;
|
|
clear_moves( );
|
|
|
|
if( 0 != solve( ) || idx_history < 81 )
|
|
goto start;
|
|
if( -1 != backtrack( ) && 0 == solve( ) )
|
|
goto start;
|
|
|
|
clear_moves( );
|
|
|
|
return true;
|
|
}
|
|
|
|
bool sudoku_generate_board(struct sudoku_state_t* state, char** difficulty)
|
|
{
|
|
int r,c,i;
|
|
|
|
rb->srand(*rb->current_tick);
|
|
|
|
rb->button_clear_queue();
|
|
|
|
if (!generate()) {
|
|
/* User has aborted with a button press */
|
|
return false;
|
|
}
|
|
|
|
i=0;
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
if( IS_EMPTY( i ) )
|
|
state->startboard[r][c]='0';
|
|
else
|
|
state->startboard[r][c]='0'+GET_DIGIT( board[ i ] );
|
|
|
|
state->currentboard[r][c]=state->startboard[r][c];
|
|
i++;
|
|
}
|
|
}
|
|
|
|
*difficulty = classify( );
|
|
return true;
|
|
}
|
|
|
|
bool sudoku_solve_board(struct sudoku_state_t* state)
|
|
{
|
|
bool ret;
|
|
int r,c,i;
|
|
|
|
reset( );
|
|
i=0;
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
if( state->startboard[r][c]!='0' )
|
|
{
|
|
fill( i, state->startboard[r][c] - '0' );
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
ret = ( 0 == solve( ) && 81 == idx_history );
|
|
|
|
if (ret) {
|
|
i=0;
|
|
for (r=0;r<9;r++) {
|
|
for (c=0;c<9;c++) {
|
|
state->currentboard[r][c]='0'+GET_DIGIT( board[ i ] );
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|