cb94b3ae2e
rb core allows you to load custom keyboard layouts this patch adds the ability to load a keyboard layout in a buffer the custom layout is temporary and does not overwrite the current layout use like so: unsigned short kbd[64]; unsigned short *kbd_p = kbd; if (!kbd_create_layout("ABCD1234\n", kbd, sizeof(kbd))) kbd_p = NULL; rb->kbd_input(buf,sizeof(buf), kbd_p); Change-Id: I7be2bd4a1b4797a147fa70228a9749dc56ac052a
1311 lines
41 KiB
C
1311 lines
41 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2005 Dave Chapman
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* 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/playback_control.h>
|
|
#include "sudoku.h"
|
|
#include "generator.h"
|
|
|
|
/* The bitmaps */
|
|
#include "pluginbitmaps/sudoku_normal.h"
|
|
#include "pluginbitmaps/sudoku_inverse.h"
|
|
#include "pluginbitmaps/sudoku_start.h"
|
|
|
|
#define BITMAP_HEIGHT (BMPHEIGHT_sudoku_normal/10)
|
|
#define BITMAP_STRIDE STRIDE(SCREEN_MAIN, BMPWIDTH_sudoku_normal, BMPHEIGHT_sudoku_normal)
|
|
|
|
#if (LCD_DEPTH>2)
|
|
#define BITMAP_WIDTH (BMPWIDTH_sudoku_normal/2)
|
|
#else
|
|
#define BITMAP_WIDTH BMPWIDTH_sudoku_normal
|
|
#endif
|
|
|
|
|
|
|
|
/* 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 || LCD_WIDTH==128)) || (LCD_HEIGHT == 96 && LCD_WIDTH == 96))
|
|
/* Archos Recorders and Ondios - 112x64, 9 cells @ 8x6 with 10 border lines */
|
|
#define SMALL_BOARD
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 1 /* Pixels between two marks */
|
|
#define MARK_SIZE 1 /* Mark width and height */
|
|
|
|
#elif ((LCD_HEIGHT==80) && (LCD_WIDTH==132))
|
|
/* C200, 9 cells @ 8x8 with 8 border lines */
|
|
#define SMALL_BOARD
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 1 /* Pixels between two marks */
|
|
#define MARK_SIZE 1 /* Mark width and height */
|
|
|
|
#elif ((LCD_HEIGHT==96) && (LCD_WIDTH==128))
|
|
/* iAudio M3, Samsung YH820, 9 cells @ 9x9 with 14 border lines */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 2 /* Pixels between two marks */
|
|
#define MARK_SIZE 1 /* Mark width and height */
|
|
|
|
#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 */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 1 /* Pixels between two marks */
|
|
#define MARK_SIZE 2 /* Mark width and height */
|
|
|
|
#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 */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 2 /* Pixels between two marks */
|
|
#define MARK_SIZE 2 /* Mark width and height */
|
|
|
|
#elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220))
|
|
/* Iriver h300, iPod Color/Photo - 220x176, 9 cells @ 16x16 with 14 border lines */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 1 /* Pixels between two marks */
|
|
#define MARK_SIZE 4 /* Mark width and height */
|
|
|
|
#elif (LCD_HEIGHT==240) && (LCD_WIDTH==320)
|
|
/* iPod Video - 320x240, 9 cells @ 24x24 with 14 border lines */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 2 /* Pixels between two marks */
|
|
#define MARK_SIZE 6 /* Mark width and height */
|
|
|
|
#elif (LCD_HEIGHT==480) && (LCD_WIDTH==640)
|
|
/* M:Robe 500 - 640x480, 9 cells @ 48x48 with 14 border lines */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 2 /* Pixels between two marks */
|
|
#define MARK_SIZE 6 /* Mark width and height */
|
|
|
|
#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 */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 1 /* Pixels between two marks */
|
|
#define MARK_SIZE 4 /* Mark width and height */
|
|
|
|
#elif (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
|
|
/* Gigabeat - 240x320, 9 cells @ 24x24 with 14 border lines */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 2 /* Pixels between two marks */
|
|
#define MARK_SIZE 6 /* Mark width and height */
|
|
|
|
#elif ((LCD_HEIGHT==160) && (LCD_WIDTH==128))
|
|
/* Philips GoGear SA9200 - 128x160, 9 cells @ 10x10 with 14 border tiles */
|
|
#define MARK_OFFS 1 /* Pixels between border and mark */
|
|
#define MARK_SPACE 1 /* Pixels between two marks */
|
|
#define MARK_SIZE 2 /* Mark width and height */
|
|
|
|
#else
|
|
#error SUDOKU: Unsupported LCD size
|
|
#endif
|
|
|
|
#endif /* Layout */
|
|
|
|
#define CELL_WIDTH BITMAP_WIDTH
|
|
#define CELL_HEIGHT BITMAP_HEIGHT
|
|
|
|
#ifdef SUDOKU_BUTTON_CHANGEDIR
|
|
int invertdir=0;
|
|
#else
|
|
#define invertdir 0
|
|
#endif
|
|
|
|
#define CFGFILE_VERSION 0 /* Current config file version */
|
|
#define CFGFILE_MINVERSION 0 /* Minimum config file version to accept */
|
|
|
|
#if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
|
|
/* settings */
|
|
struct sudoku_config {
|
|
#ifdef HAVE_LCD_COLOR
|
|
int number_display;
|
|
#endif
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
int show_markings;
|
|
#endif
|
|
};
|
|
|
|
struct sudoku_config sudcfg_disk = {
|
|
#ifdef HAVE_LCD_COLOR
|
|
0,
|
|
#endif
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
1,
|
|
#endif
|
|
};
|
|
struct sudoku_config sudcfg;
|
|
|
|
static const char cfg_filename[] = "sudoku.cfg";
|
|
#ifdef HAVE_LCD_COLOR
|
|
static char *number_str[2] = { "black", "coloured" };
|
|
#endif
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
static char *mark_str[2] = { "hide", "show" };
|
|
#endif
|
|
|
|
struct configdata disk_config[] = {
|
|
#ifdef HAVE_LCD_COLOR
|
|
{ TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.number_display }, "numbers",
|
|
number_str },
|
|
#endif
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
{ TYPE_ENUM, 0, 2, { .int_p = &sudcfg_disk.show_markings }, "markings",
|
|
mark_str },
|
|
#endif
|
|
};
|
|
#endif
|
|
#ifdef HAVE_LCD_COLOR
|
|
#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 int 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 int 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 int 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 int 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
|
|
|
|
#define BLOCK 3
|
|
#define SIZE (BLOCK*BLOCK)
|
|
|
|
static void sudoku_solve(struct sudoku_state_t* state)
|
|
{
|
|
bool ret = sudoku_solve_board(state);
|
|
|
|
if (!ret) {
|
|
rb->splash(HZ*2, "Solve failed");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Copies the current to the saved board */
|
|
static void save_state(struct sudoku_state_t *state)
|
|
{
|
|
rb->memcpy(state->savedboard, state->currentboard,
|
|
sizeof(state->savedboard));
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
rb->memcpy(state->savedpossible, state->possiblevals,
|
|
sizeof(state->savedpossible));
|
|
#endif
|
|
}
|
|
|
|
/* Copies the saved to the current board */
|
|
static void restore_state(struct sudoku_state_t *state)
|
|
{
|
|
rb->memcpy(state->currentboard, state->savedboard,
|
|
sizeof(state->savedboard));
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
rb->memcpy(state->possiblevals, state->savedpossible,
|
|
sizeof(state->possiblevals));
|
|
#endif
|
|
}
|
|
|
|
static void default_state(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
|
|
rb->strlcpy(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
|
|
}
|
|
}
|
|
|
|
/* initialize the saved board so reload function works */
|
|
save_state(state);
|
|
|
|
state->x=0;
|
|
state->y=0;
|
|
state->editmode=0;
|
|
}
|
|
|
|
static void clear_state(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
|
|
rb->strlcpy(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 */
|
|
static 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.
|
|
*/
|
|
static bool load_sudoku(struct sudoku_state_t* state, char* filename)
|
|
{
|
|
int fd;
|
|
size_t n;
|
|
int r = 0, c = 0, d = 0;
|
|
unsigned int i;
|
|
int valid=0;
|
|
char buf[500]; /* 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->strlcpy(state->filename,filename,MAX_PATH);
|
|
n=rb->read(fd,buf,500);
|
|
if (n <= 0) {
|
|
return(false);
|
|
}
|
|
rb->close(fd);
|
|
r=0;
|
|
c=0;
|
|
i=0;
|
|
d=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;
|
|
d = 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++;
|
|
}
|
|
if((buf[i]>='a' && buf[i] <= 'z') && i < (n-1)
|
|
&& (buf[i+1] >= 'a' && buf[i+1] <= 'z')) {
|
|
state->possiblevals[r][d]
|
|
= (((buf[i]-'a') * 26 + buf[i+1]-'a')<<1);
|
|
i++;
|
|
d++;
|
|
}
|
|
/* Ignore any other characters */
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
/* Check that the board is valid - we need to check every row/column
|
|
and block individually */
|
|
for (state->y = 0; state->y < 9; state->y++) {
|
|
state->x = (state->y%3)*3 + (state->y/3);
|
|
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 */
|
|
save_state(state);
|
|
return(true);
|
|
}
|
|
|
|
static bool save_sudoku(struct sudoku_state_t* state)
|
|
{
|
|
int fd;
|
|
int r,c;
|
|
int i;
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
int x;
|
|
char line[41]="...|...|... ; \r\n";
|
|
#else
|
|
char line[13]="...|...|...\r\n";
|
|
#endif
|
|
char sep[13]="-----------\r\n";
|
|
|
|
rb->splash(0, "Saving...");
|
|
|
|
if (state->filename[0]==0) {
|
|
return false;
|
|
}
|
|
|
|
fd=rb->open(state->filename, O_WRONLY|O_CREAT, 0666);
|
|
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++;
|
|
}
|
|
}
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
i+=2;
|
|
for(c=0; c<9; c++) {
|
|
x = ((state->possiblevals[r][c]>>1)/26);
|
|
line[i++] = x + 'a';
|
|
x = ((state->possiblevals[r][c]>>1)%26);
|
|
line[i++] = x + 'a';
|
|
}
|
|
#endif
|
|
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 */
|
|
save_state(state);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
|
|
|
|
static void display_board(struct sudoku_state_t* state)
|
|
{
|
|
int r,c;
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
int i;
|
|
#endif
|
|
|
|
/* 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]&BIT_N(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]&BIT_N(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]&BIT_N(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]&BIT_N(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);
|
|
}
|
|
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
/* Draw the possible number markings on the board */
|
|
if(sudcfg.show_markings && state->startboard[r][c]=='0'
|
|
&& state->currentboard[r][c]=='0') {
|
|
for(i=0;i<9;i++) {
|
|
if(state->possiblevals[r][c]&(2<<i)) {
|
|
#if LCD_DEPTH > 1
|
|
/* draw markings in dark grey */
|
|
rb->lcd_set_foreground(LCD_DARKGRAY);
|
|
#endif
|
|
rb->lcd_fillrect(XOFS+cellxpos[c]+MARK_OFFS
|
|
+(i%3)*(MARK_SIZE+MARK_SPACE),
|
|
YOFS+cellypos[r]+MARK_OFFS
|
|
+(i/3)*(MARK_SIZE+MARK_SPACE),
|
|
MARK_SIZE,
|
|
MARK_SIZE);
|
|
#if LCD_DEPTH > 1
|
|
rb->lcd_set_foreground(LCD_BLACK);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
#endif /* SUDOKU_BUTTON_POSSIBLE */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* update the screen */
|
|
rb->lcd_update();
|
|
}
|
|
|
|
static 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->strlcpy(state->filename,GAME_FILE,MAX_PATH);
|
|
} else {
|
|
display_board(&new_state);
|
|
rb->splash(HZ*2, "Aborted");
|
|
}
|
|
/* initialize the saved board so reload function works */
|
|
save_state(state);
|
|
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
|
|
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
static bool showmarkings_setting(void)
|
|
{
|
|
static const struct opt_items names[] = {
|
|
{"Hide", -1},
|
|
{"Show", -1},
|
|
};
|
|
|
|
return rb->set_option("Show Markings", &sudcfg.show_markings, INT, names,
|
|
sizeof(names) / sizeof(names[0]), NULL);
|
|
}
|
|
#endif
|
|
|
|
enum {
|
|
SM_AUDIO_PLAYBACK = 0,
|
|
#ifdef HAVE_LCD_COLOR
|
|
SM_NUMBER_DISPLAY,
|
|
#endif
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
SM_SHOW_MARKINGS,
|
|
#endif
|
|
SM_SAVE,
|
|
SM_RELOAD,
|
|
SM_CLEAR,
|
|
SM_SOLVE,
|
|
SM_GENERATE,
|
|
SM_NEW,
|
|
SM_QUIT,
|
|
};
|
|
|
|
static int sudoku_menu(struct sudoku_state_t* state)
|
|
{
|
|
int result;
|
|
|
|
MENUITEM_STRINGLIST(menu, "Sudoku Menu", NULL,
|
|
"Audio Playback",
|
|
#ifdef HAVE_LCD_COLOR
|
|
"Number Display",
|
|
#endif
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
"Show Markings",
|
|
#endif
|
|
"Save", "Reload", "Clear", "Solve",
|
|
"Generate", "New", "Quit");
|
|
|
|
result = rb->do_menu(&menu, NULL, NULL, false);
|
|
|
|
switch (result) {
|
|
case SM_AUDIO_PLAYBACK:
|
|
playback_control(NULL);
|
|
break;
|
|
|
|
#ifdef HAVE_LCD_COLOR
|
|
case SM_NUMBER_DISPLAY:
|
|
numdisplay_setting();
|
|
break;
|
|
#endif
|
|
|
|
#ifdef SUDOKU_BUTTON_POSSIBLE
|
|
case SM_SHOW_MARKINGS:
|
|
showmarkings_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);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Menu used when user is in edit mode - i.e. creating a new game manually */
|
|
static int sudoku_edit_menu(struct sudoku_state_t* state)
|
|
{
|
|
int result;
|
|
|
|
MENUITEM_STRINGLIST(menu, "Edit Menu", NULL,
|
|
"Save as", "Quit");
|
|
|
|
result = rb->do_menu(&menu, NULL, NULL, false);
|
|
|
|
switch (result) {
|
|
case 0: /* Save new game */
|
|
rb->kbd_input(state->filename,MAX_PATH, NULL);
|
|
if (save_sudoku(state)) {
|
|
state->editmode=0;
|
|
} else {
|
|
rb->splash(HZ*2, "Save failed");
|
|
}
|
|
break;
|
|
|
|
case 1: /* Quit */
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static 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(const void* parameter)
|
|
{
|
|
bool exit;
|
|
int button;
|
|
#if defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE)
|
|
int lastbutton = BUTTON_NONE;
|
|
#endif
|
|
int res;
|
|
int rc = PLUGIN_OK;
|
|
long ticks;
|
|
struct sudoku_state_t state;
|
|
|
|
#if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
|
|
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&&invertdir==0) || (state.y==0&&invertdir==1) ) {
|
|
#ifndef SUDOKU_BUTTON_UP
|
|
if ( (state.y==0&&invertdir==0) || (state.x==0&&invertdir==1)) {
|
|
move_cursor(&state,8,8);
|
|
} else {
|
|
if (invertdir==0) {
|
|
move_cursor(&state,8,state.y-1);
|
|
} else {
|
|
move_cursor(&state,state.x-1,8);
|
|
}
|
|
}
|
|
#else
|
|
move_cursor(&state,8,state.y);
|
|
#endif
|
|
} else {
|
|
if (invertdir==0) {
|
|
move_cursor(&state,state.x-1,state.y);
|
|
} else {
|
|
move_cursor(&state,state.x,state.y-1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
/* move cursor right */
|
|
case SUDOKU_BUTTON_RIGHT:
|
|
case (SUDOKU_BUTTON_RIGHT | BUTTON_REPEAT):
|
|
if ( (state.x==8&&invertdir==0) || (state.y==8&&invertdir==1) ) {
|
|
#ifndef SUDOKU_BUTTON_DOWN
|
|
if ( (state.y==8&&invertdir==0) || (state.x==8&&invertdir==1) ) {
|
|
move_cursor(&state,0,0);
|
|
} else {
|
|
if (invertdir==0) {
|
|
move_cursor(&state,0,state.y+1);
|
|
} else {
|
|
move_cursor(&state,state.x+1,0);
|
|
}
|
|
}
|
|
#else
|
|
move_cursor(&state,0,state.y);
|
|
#endif
|
|
} else {
|
|
if (invertdir==0) {
|
|
move_cursor(&state,state.x+1,state.y);
|
|
} else {
|
|
move_cursor(&state,state.x,state.y+1);
|
|
}
|
|
}
|
|
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 {
|
|
res = sudoku_menu(&state);
|
|
if (res == MENU_ATTACHED_USB) {
|
|
rc = PLUGIN_USB_CONNECTED;
|
|
exit = true;
|
|
} else if (res == SM_QUIT) {
|
|
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]^=
|
|
BIT_N(state.currentboard[state.y][state.x] - '0');
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
#ifdef SUDOKU_BUTTON_CHANGEDIR
|
|
case SUDOKU_BUTTON_CHANGEDIR:
|
|
/* Change scroll wheel direction */
|
|
invertdir=!invertdir;
|
|
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 defined(SUDOKU_BUTTON_TOGGLE_PRE) || defined(SUDOKU_BUTTON_MENU_PRE)
|
|
if (button != BUTTON_NONE)
|
|
lastbutton = button;
|
|
#endif
|
|
|
|
display_board(&state);
|
|
}
|
|
#if defined(HAVE_LCD_COLOR) || defined(SUDOKU_BUTTON_POSSIBLE)
|
|
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;
|
|
}
|