/***************************************************************************
 *             __________               __   ___.
 *   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"
#include "lib/configfile.h"
#include "lib/oldmenuapi.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==80) && (LCD_WIDTH==132))
/* C200, 9 cells @ 8x8 with 8 border lines */

/* Internal dimensions of a cell */
#define CELL_WIDTH  8
#define CELL_HEIGHT 8
#define SMALL_BOARD

#elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
/* iAudio M3, 9 cells @ 9x9 with 14 border lines */

/* Internal dimensions of a cell */
#define CELL_WIDTH  9
#define CELL_HEIGHT 9

#elif (LCD_HEIGHT==110) && (LCD_WIDTH==138) \
   || (LCD_HEIGHT==128) && (LCD_WIDTH==128)
/* iPod Mini - 138x110, 9 cells @ 10x10 with 14 border lines */
/* 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 */

#define CFGFILE_VERSION 0     /* Current config file version */
#define CFGFILE_MINVERSION 0  /* Minimum config file version to accept */

#ifdef HAVE_LCD_COLOR
/* settings */
struct sudoku_config {
    int number_display;
};
struct sudoku_config sudcfg_disk = { 0 };
struct sudoku_config sudcfg;

static const char cfg_filename[] =  "sudoku.cfg";
static char *number_str[2] = { "black", "coloured" };

struct configdata disk_config[] = {
   { TYPE_ENUM, 0, 2, &sudcfg_disk.number_display, "numbers", number_str, NULL },
};
#define NUMBER_TYPE (sudcfg.number_display*CELL_WIDTH)
#else
#define NUMBER_TYPE 0
#endif

/* 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(&copy,sud,sizeof(Sudoku));
    sudoku_set(&copy, x, y, num, false);
    stats->numTries += 1;
    if (solve(&copy, stats, options)){
        if (!options->allSolutions && stats->solutionFound){
            rb->memcpy(sud,&copy,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, "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, "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,NUMBER_TYPE,
                            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,NUMBER_TYPE,
                                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,NUMBER_TYPE,
                                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,NUMBER_TYPE,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,NUMBER_TYPE,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,NUMBER_TYPE,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,NUMBER_TYPE,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,NUMBER_TYPE,
                                    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,NUMBER_TYPE,
                                        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,NUMBER_TYPE,
                                        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, "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, str);
        rb->strncpy(state->filename,GAME_FILE,MAX_PATH);
    } else {
        display_board(&new_state);
        rb->splash(HZ*2, "Aborted");
    }
    return res;
}

#ifdef HAVE_LCD_COLOR
static bool numdisplay_setting(void)
{
    static const struct opt_items names[] = {
        {"Black",  -1},
        {"Coloured",  -1},
    };

    return rb->set_option("Number Display", &sudcfg.number_display, INT, names,
                          sizeof(names) / sizeof(names[0]), NULL);
}
#endif

enum {
    SM_AUDIO_PLAYBACK = 0,
#ifdef HAVE_LCD_COLOR
    SM_NUMBER_DISPLAY,
#endif
    SM_SAVE,
    SM_RELOAD,
    SM_CLEAR,
    SM_SOLVE,
    SM_GENERATE,
    SM_NEW,
    SM_QUIT,
};

bool sudoku_menu(struct sudoku_state_t* state)
{
    int m;
    int result;

    static const struct menu_item items[] = {
        [SM_AUDIO_PLAYBACK] = { "Audio Playback", NULL },
#ifdef HAVE_LCD_COLOR
        [SM_NUMBER_DISPLAY] = { "Number Display", NULL },
#endif
        [SM_SAVE]     = { "Save", NULL },
        [SM_RELOAD]   = { "Reload", NULL },
        [SM_CLEAR]    = { "Clear", NULL },
        [SM_SOLVE]    = { "Solve", NULL },
        [SM_GENERATE] = { "Generate", NULL },
        [SM_NEW]      = { "New", NULL },
        [SM_QUIT]     = { "Quit", NULL },
    };
    
    m = menu_init(rb,items, sizeof(items) / sizeof(*items),
                      NULL, NULL, NULL, NULL);

    result=menu_show(m);

    switch (result) {
        case SM_AUDIO_PLAYBACK:
            playback_control(rb, NULL);
            break;

#ifdef HAVE_LCD_COLOR
        case SM_NUMBER_DISPLAY:
            numdisplay_setting();
            break;
#endif
        case SM_SAVE:
            save_sudoku(state);
            break;

        case SM_RELOAD:
            restore_state(state);
            break;

        case SM_CLEAR:
            clear_board(state);
            break;

        case SM_SOLVE:
            sudoku_solve(state);
            break;

        case SM_GENERATE:
            sudoku_generate(state);
            break;

        case SM_NEW:
            clear_state(state);
            state->editmode=1;
            break;

        case SM_QUIT:
            save_sudoku(state);
            menu_exit(m);
            return true;
            break;

        default:
            break;
    }

    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 = menu_init(rb,items, sizeof(items) / sizeof(*items),
                      NULL, NULL, NULL, NULL);

    result=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, "Save failed");
            }
            break;

        case 1: /* Quit */
            break;

        default:
            break;
    }

    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, "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;
    int rc = PLUGIN_OK;
    long ticks;
    struct sudoku_state_t state;

    /* plugin init */
    rb = api;
    /* end of plugin init */
    
#ifdef HAVE_LCD_COLOR
    configfile_init(rb);
    configfile_load(cfg_filename, disk_config,
                    sizeof(disk_config) / sizeof(disk_config[0]),
                    CFGFILE_MINVERSION);
    rb->memcpy(&sudcfg, &sudcfg_disk, sizeof(sudcfg)); /* copy to running config */
#endif

#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, "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, "Illegal move!");
                    /* Ignore any button presses during the splash */
                    rb->button_clear_queue();
                } else {
                    save_sudoku(&state);
                    exit=true;
                }
                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, "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) {
                            rc = PLUGIN_USB_CONNECTED;
                            exit = true;
                        } else if (res == 1) { /* Quit */
                            exit = true;
                        }
                    } else {
                        if (sudoku_menu(&state)) {
                            rc = PLUGIN_USB_CONNECTED;
                            exit = true;
                        }
                    }
                }
                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 */
                    rc = PLUGIN_USB_CONNECTED;
                    exit = true;
                }
                break;
        }
        if (button != BUTTON_NONE)
            lastbutton = button;

        display_board(&state);
    }
#ifdef HAVE_LCD_COLOR
    if (rb->memcmp(&sudcfg, &sudcfg_disk, sizeof(sudcfg))) /* save settings if changed */
    {
        rb->memcpy(&sudcfg_disk, &sudcfg, sizeof(sudcfg));
        configfile_save(cfg_filename, disk_config,
                        sizeof(disk_config) / sizeof(disk_config[0]),
                        CFGFILE_VERSION);
    }
#endif
    return rc;
}

#endif
