diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 738c031..fb743aa 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES @@ -15,6 +15,7 @@ chopper,games clock,apps credits,viewers cube,demos +clix,games demystify,demos dice,games dict,apps diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 8e138d7..96ff000 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -63,6 +63,7 @@ bubbles.c calculator.c chip8.c chopper.c +clix.c demystify.c jewels.c minesweeper.c diff --git a/apps/plugins/clix.c b/apps/plugins/clix.c new file mode 100644 index 0000000..fa0c5e1 --- /dev/null +++ b/apps/plugins/clix.c @@ -0,0 +1,816 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2008 Rene Peinthor + * + * 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. + * + ****************************************************************************/ +#include "plugin.h" + +/* colors needed */ +#if LCD_DEPTH >= 16 + +#include "lib/oldmenuapi.h" +#include "lib/playback_control.h" + +PLUGIN_HEADER + +#if ( CONFIG_KEYPAD == SANSA_E200_PAD) +#define CLIX_BUTTON_QUIT BUTTON_POWER +#define CLIX_BUTTON_UP BUTTON_UP +#define CLIX_BUTTON_DOWN BUTTON_DOWN +#define CLIX_BUTTON_SCROLL_FWD BUTTON_SCROLL_FWD +#define CLIX_BUTTON_SCROLL_BACK BUTTON_SCROLL_BACK +#define CLIX_BUTTON_LEFT BUTTON_LEFT +#define CLIX_BUTTON_RIGHT BUTTON_RIGHT +#define CLIX_BUTTON_CLICK BUTTON_SELECT + +#elif (CONFIG_KEYPAD == SANSA_C200_PAD) +#define CLIX_BUTTON_QUIT BUTTON_POWER +#define CLIX_BUTTON_UP BUTTON_UP +#define CLIX_BUTTON_DOWN BUTTON_DOWN +#define CLIX_BUTTON_SCROLL_FWD BUTTON_VOL_UP +#define CLIX_BUTTON_SCROLL_BACK BUTTON_VOL_DOWN +#define CLIX_BUTTON_LEFT BUTTON_LEFT +#define CLIX_BUTTON_RIGHT BUTTON_RIGHT +#define CLIX_BUTTON_CLICK BUTTON_SELECT + +#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ + (CONFIG_KEYPAD == IPOD_3G_PAD) || \ + (CONFIG_KEYPAD == IPOD_1G2G_PAD) +#define CLIX_BUTTON_QUIT BUTTON_MENU +#define CLIX_BUTTON_UP BUTTON_SCROLL_BACK +#define CLIX_BUTTON_DOWN BUTTON_SCROLL_FWD +#define CLIX_BUTTON_CLICK BUTTON_SELECT +#define CLIX_BUTTON_RIGHT BUTTON_RIGHT +#define CLIX_BUTTON_LEFT BUTTON_LEFT + +#else +#error "no keymap" +#endif + +#define HIGHSCORE_FILE PLUGIN_GAMES_DIR "/clix_highscore.txt" +#define GAME_FILE PLUGIN_GAMES_DIR "/clix_game.txt" + +#define HS_ENTRY 5 +#define BLINK_TICKCOUNT 25 + +#define BOARD_WIDTH 18 +#define BOARD_HEIGHT 12 + +#if LCD_HEIGHT <= 100 || LCD_WIDTH <= 100 +#define CELL_SIZE 3 +#elif LCD_WIDTH <= 150 +#define CELL_SIZE 4 +#elif LCD_WIDTH <= 200 +#define CELL_SIZE 5 +#elif LCD_WIDTH <= 220 +#define CELL_SIZE 6 +#else +#define CELL_SIZE 8 +#endif + +#define XYPOS(x,y) ((y) * BOARD_WIDTH + x) + +#define VERTICAL_LAYOUT 1 + +#ifdef VERTICAL_LAYOUT +#define XOFS ((LCD_WIDTH-BOARD_WIDTH * (CELL_SIZE + 1))/2) +#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT * (CELL_SIZE + 1))/2) + 10 +#else + +#define XOFS ((LCD_WIDTH-BOARD_WIDTH * (CELL_SIZE + 1))/2) +#define YOFS ((LCD_HEIGHT-BOARD_HEIGHT * (CELL_SIZE + 1))/2) + +#endif + +static struct plugin_api* rb; + +struct clix_game_state_t { + char level; /* current level */ + signed char x,y; /* current positions of the cursor */ + signed char board[BOARD_WIDTH * BOARD_HEIGHT]; /* play board*/ + /* state of selected fields,maybe we can store this in the play board too */ + bool board_selected[ BOARD_WIDTH * BOARD_HEIGHT]; + int selected_count; + int score; /* current game score */ + signed char status; + bool blink; /* true if selected CELLS are currently white */ +}; + +/* game state enum */ +enum { + CLIX_GAMEOVER = -1, + CLIX_CONTINUE, + CLIX_CLEARED +}; + +/* cell color enum */ +enum { + CC_BLACK = -1, + CC_BLUE, + CC_GREEN, + CC_RED, + CC_YELLOW, + CC_PURPLE, + CC_ORANGE, + CC_CYAN +}; + +/* menu enum */ +enum { + CM_AUDIO_PLAYBACK = 0, + CM_RESUME, + CM_NEW, + CM_HIGHSCORE, + CM_QUIT, +}; + +void clix_save_highscore( int highscore[]) +{ + int i; + int fd; + char line[20]; + + fd = rb->open( HIGHSCORE_FILE, O_WRONLY|O_CREAT); + if( fd >= 0) + { + for( i = 0; i < HS_ENTRY; ++i) + { + rb->snprintf( line, sizeof( line), "%d\r\n", highscore[i]); + rb->write( fd, line, rb->strlen(line)); + } + } + rb->close( fd); +} + +bool clix_load_highscore( int highscore[]) +{ + int i; + int fd; + int n; + int pos, oldpos; + char buf[300]; + char line[20]; + char* curline; + + if( rb->file_exists( HIGHSCORE_FILE)) + { + fd = rb->open( HIGHSCORE_FILE, O_RDONLY); + if( fd >= 0) + { + if( (n = rb->read( fd, buf, 300)) > 0) + { + rb->close( fd); + + for( i = 0, curline = buf, oldpos = pos = 0; + i < HS_ENTRY && pos < n; + ++i, oldpos = pos + ) + { + /* search next new line, or end of buffer */ + while( pos < n && buf[pos] != '\n') pos++; + /* copy the line */ + rb->strncpy( line, curline, pos - oldpos); + /* set new starting point for the line */ + curline = &buf[++pos]; + /* convert string to integer */ + highscore[i] = rb->atoi( line); + } + /* if there are fewer lines than HS_ENTRYS, + fill the rest with 0 */ + for(; i < HS_ENTRY; ++i) highscore[i] = 0; + } + else + return false; + } + else + return false; + } + else + { + /* create an initial highscore table */ + highscore[ 0] = 30000; + highscore[ 1] = 25000; + highscore[ 2] = 20000; + highscore[ 3] = 15000; + highscore[ 4] = 10000; + clix_save_highscore( highscore); + } + + return true; +} + +int clix_update_highscore( struct clix_game_state_t* state) +{ + int highscore[HS_ENTRY]; + int helpscore, oldscore; + int i = 0; + int place = 0; + + clix_load_highscore( highscore); + + while( i < HS_ENTRY && highscore[i] > state->score) i++; + if( i < HS_ENTRY) + { + place = i + 1; + oldscore = highscore[i]; + highscore[i] = state->score; + for( i++; i < HS_ENTRY; ++i) + { + helpscore = highscore[i]; + highscore[i] = oldscore; + oldscore = helpscore; + } + + clix_save_highscore( highscore); + } + return place; +} + +/* recursiv function to check if a neighbour cell is of the same color + if so call the function with the neighbours position +*/ +void clix_set_selected( + struct clix_game_state_t* state, + const int x, const int y) +{ + state->selected_count++; + state->board_selected[ XYPOS( x, y)] = true; + int current_color = state->board[ XYPOS( x, y)]; + + if( (x - 1) >= 0 && + state->board[ XYPOS( x - 1, y)] == current_color && + state->board_selected[ XYPOS(x - 1, y)] == false) + clix_set_selected( state, x - 1, y); + + if( (y + 1) < BOARD_HEIGHT && + state->board[ XYPOS( x, y + 1)] == current_color && + state->board_selected[ XYPOS(x, y + 1)] == false) + clix_set_selected( state, x, y + 1); + + if( (x + 1) < BOARD_WIDTH && + state->board[ XYPOS( x + 1, y)] == current_color && + state->board_selected[ XYPOS(x + 1, y)] == false) + clix_set_selected( state, x + 1, y); + + if( (y - 1) >= 0 && + state->board[ XYPOS( x, y - 1)] == current_color && + state->board_selected[ XYPOS(x, y - 1)] == false) + clix_set_selected( state, x, y - 1); +} + +/* updates "blinking" cells by finding out which one is a valid neighbours */ +void clix_update_selected( struct clix_game_state_t* state) +{ + int i; + + for( i = 0; i < BOARD_WIDTH * BOARD_HEIGHT; ++i) + { + state->board_selected[i] = false; + } + state->selected_count = 0; + + /* recursion starts here */ + clix_set_selected( state, state->x, state->y); +} + +/* inits the board with new random colors according to the level */ +void clix_init_new_level( struct clix_game_state_t* state) +{ + int colors[ state->level + 1]; + int i; + int r; + + state->y = BOARD_HEIGHT / 2; + state->x = BOARD_WIDTH / 2; + for(i = 0; i < (state->level + 1); ++i) + { + colors[i] = ((BOARD_HEIGHT * BOARD_WIDTH) / (state->level + 1)) + 1; + } + + rb->srand( *rb->current_tick); + /* create a random colored board, according to the current level */ + for(i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; ++i) + { + do + { + r = rb->rand() % (state->level + 1); + } while( colors[r] == 0); + state->board[i] = r; + colors[r]--; + } +} + +/* this inits the game state structur */ +void clix_init( struct clix_game_state_t* state) +{ + state->level = 1; + state->score = 0; + state->blink = false; + state->status = CLIX_CONTINUE; + + clix_init_new_level(state); + + clix_update_selected( state); +} + +/* Function for drawing a cell */ +void clix_draw_cell( struct clix_game_state_t* state, const int x, const int y) +{ + int realx = XOFS - CELL_SIZE; + int realy = YOFS - CELL_SIZE; + + realx += x * (CELL_SIZE + 1); + realy += y * (CELL_SIZE + 1); + + if( state->blink && state->board_selected[ XYPOS( x, y)]) + { + rb->lcd_set_foreground( LCD_WHITE); + } + else + { + switch (state->board[ XYPOS( x, y)]) + { + case CC_BLUE: + rb->lcd_set_foreground( LCD_RGBPACK( 25, 25, 255)); + break; + case CC_GREEN: + rb->lcd_set_foreground( LCD_RGBPACK( 25, 255, 25)); + break; + case CC_RED: + rb->lcd_set_foreground( LCD_RGBPACK( 255, 25, 25)); + break; + case CC_YELLOW: + rb->lcd_set_foreground( LCD_RGBPACK( 229, 241, 44)); + break; + case CC_PURPLE: + rb->lcd_set_foreground( LCD_RGBPACK( 230, 15, 225)); + break; + case CC_ORANGE: + rb->lcd_set_foreground( LCD_RGBPACK( 230, 140, 15)); + break; + case CC_CYAN: + rb->lcd_set_foreground( LCD_RGBPACK( 25, 245, 230)); + break; + default: + rb->lcd_set_foreground( LCD_BLACK); + break; + } + } + + rb->lcd_fillrect( realx, realy, CELL_SIZE, CELL_SIZE); + + /* draw cursor */ + if( x == state->x && y == state->y) + { + rb->lcd_set_foreground( LCD_WHITE); + rb->lcd_drawrect( realx - 1, realy - 1, CELL_SIZE + 2, CELL_SIZE + 2); + } +} + +/* main function of drawing the whole board and score... */ +void clix_draw( struct clix_game_state_t* state) +{ + int i,j; + char level[3]; + char score[10]; + int font_height; + + /* Clear screen */ + rb->lcd_clear_display(); + + font_height = rb->font_get( FONT_UI)->height; + rb->lcd_set_foreground( LCD_RGBPACK( 229, 241, 44)); + rb->lcd_puts( 0, 0, "Score: "); + rb->snprintf( score, sizeof(score), "%d", state->score); + rb->lcd_puts( 10, 0, score); + rb->lcd_puts( 22, 0, "Level:"); + rb->snprintf( level, sizeof(level), "%d", state->level); + rb->lcd_puts( 23, 0, level); + + rb->lcd_hline( 0, LCD_WIDTH, font_height + 2); + + for( i = 0; i < BOARD_WIDTH; ++i) + { + for( j = 0; j < BOARD_HEIGHT; ++j) + { + clix_draw_cell( state, i, j); + } + } + + rb->lcd_update(); +} + +void clix_move_cursor( struct clix_game_state_t* state, const bool left) +{ + signed char x, y; + + x = state->x; + do + { + y = state->y; + while( state->board[ XYPOS( x, y)] == CC_BLACK && y < BOARD_HEIGHT) y++; + if( y < BOARD_HEIGHT) + { + state->y = y; + state->x = x; + } + else + { + if( left) + { + if( x >= 0) + x--; + else + y = state->y; + } + else + { + if( x < BOARD_WIDTH - 1) + x++; + else + x = 0; + } + } + } while ( y != state->y); + +} + +/* returns the color of the given position, if out of bounds return CC_BLACK */ +int clix_get_color( struct clix_game_state_t* state, const int x, const int y) +{ + if( x >= 0 && x < BOARD_WIDTH && y >= 0 && y < BOARD_HEIGHT) + return state->board[ XYPOS( x, y)]; + else + return CC_BLACK; +} + +int clix_clear_selected( struct clix_game_state_t* state) +{ + int i, j, x, y; + + state->status = CLIX_CLEARED; + + /* clear the selected blocks */ + for( i = 0; i < BOARD_WIDTH; ++i) + { + for( j = 0; j < BOARD_HEIGHT; ++j) + { + if( state->board_selected[ XYPOS( i, j)] ) + { + state->board_selected[ XYPOS( i, j)] = false; + state->board[ XYPOS( i, j)] = CC_BLACK; + } + } + } + + /* let blocks falling down */ + for( i = BOARD_WIDTH - 1; i >= 0; --i) + { + for( j = BOARD_HEIGHT - 1; j >= 0; --j) + { + y = j; + while( state->board[ XYPOS( i, y + 1)] == CC_BLACK && + y < BOARD_HEIGHT + ) + y++; + + if( y != j) + { + state->board[ XYPOS(i, y)] = state->board[ XYPOS( i, j)]; + state->board[ XYPOS( i, j)] = CC_BLACK; + } + } + } + + /* count score */ + state->score += state->selected_count * + state->selected_count * + state->level; + + /* check every column (from right to left) if its empty, + if so copy the contents from the right side */ + for( i = BOARD_WIDTH - 1; i >= 0; --i) + { + if( state->board[ XYPOS( i, BOARD_HEIGHT - 1)] == CC_BLACK) + { + if( (i + 1) < BOARD_WIDTH && + state->board[ XYPOS( i + 1, BOARD_HEIGHT - 1)] != CC_BLACK) + { + for( x = (i + 1); x < BOARD_WIDTH; ++x) + { + for( j = 0; j < BOARD_HEIGHT; ++j) + { + state->board[ XYPOS( x - 1, j)] = + state->board[ XYPOS( x, j)]; + + state->board[ XYPOS( x, j)] = CC_BLACK; + } + } + } + } + else + state->status = CLIX_CONTINUE; + } + + if( state->status != CLIX_CLEARED) + { + /* check if a move is still possible, otherwise the game is over. + tart from the left bottom, because there are the last fields + at the end of the game. + */ + for( i = 0; i < BOARD_WIDTH; ++i) + { + for( j = BOARD_HEIGHT - 1; j >= 0; --j) + { + if( state->board[ XYPOS( i, j)] != CC_BLACK) + { + if( state->board[ XYPOS( i, j)] == + clix_get_color( state, i - 1, j) || + state->board[ XYPOS( i, j)] == + clix_get_color( state, i + 1, j) || + state->board[ XYPOS( i, j)] == + clix_get_color( state, i, j - 1) || + state->board[ XYPOS( i, j)] == + clix_get_color( state, i, j + 1) + ) + { + /* and the loop, but in a diffrent way than usually*/ + i = BOARD_WIDTH + 1; + j = -2; + } + } + } + } + /* if the loops ended without a possible move, the game is over */ + if( i == BOARD_WIDTH && j == -1) + state->status = CLIX_GAMEOVER; + + /* set cursor to the right position */ + if( state->status == CLIX_CONTINUE) + { + clix_move_cursor( state, true); + clix_update_selected( state); + } + } + + return state->status; +} + +bool clix_move_possible( struct clix_game_state_t* state) +{ + if( state->board[ XYPOS( state->x, state->y)] != CC_BLACK) + return true; + else + return false; +} + +bool clix_handle_game( struct clix_game_state_t* state) +{ + bool exit = false; + int button; + int blink_tick = *rb->current_tick + BLINK_TICKCOUNT; + + int time; + int start; + int end; + int oldx, oldy; + + while( !exit) + { + if( blink_tick < *rb->current_tick) + { + state->blink = state->blink ? false : true; + blink_tick = *rb->current_tick + BLINK_TICKCOUNT; + } + + time = 6; /* number of ticks this function will loop reading keys */ + start = *rb->current_tick; + end = start + time; + while(end > *rb->current_tick) + { + oldx = state->x; + oldy = state->y; + + rb->button_get_w_tmo(end - *rb->current_tick); + button = rb->button_status(); + rb->button_clear_queue(); + switch( button) + { +#ifdef CLIX_BUTTON_SCROLL_BACK + case CLIX_BUTTON_SCROLL_BACK: +#endif + case CLIX_BUTTON_UP: + if( state->y == 0 || + state->board[ XYPOS( state->x, state->y - 1)] == + CC_BLACK + ) + state->y = BOARD_HEIGHT - 1; + else + state->y--; + + clix_move_cursor( state, true); + break; + case CLIX_BUTTON_RIGHT: + if( state->x == (BOARD_WIDTH - 1)) + state->x = 0; + else + state->x++; + + clix_move_cursor( state, false); + break; +#ifdef CLIX_BUTTON_SCROLL_FWD + case CLIX_BUTTON_SCROLL_FWD: +#endif + case CLIX_BUTTON_DOWN: + if( state->y == (BOARD_HEIGHT - 1)) + state->y = 0; + else + state->y++; + + clix_move_cursor( state, true); + break; + case CLIX_BUTTON_LEFT: + if( state->x == 0) + state->x = BOARD_WIDTH - 1; + else + state->x--; + + clix_move_cursor( state, true); + + break; + case CLIX_BUTTON_CLICK: + { + if( state->selected_count > 1) + { + switch( clix_clear_selected( state)) + { + case CLIX_CLEARED: + clix_draw( state); + rb->splash(HZ*3, "Great! Next Level!"); + state->score += state->level * 1000; + state->level++; + clix_init_new_level( state); + clix_update_selected( state); + break; + case CLIX_GAMEOVER: + clix_draw( state); + if( clix_update_highscore( state)) + rb->splash(HZ*3, "New Highscore"); + else + rb->splash(HZ*3, "Game Over!"); + exit = true; + break; + default: + rb->sleep( 10); /* prevent repeating clicks */ + break; + } + } + } + break; + case CLIX_BUTTON_QUIT: + exit = true; + rb->button_clear_queue(); + break; + default: + + break; + } + + if( (oldx != state->x || oldy != state->y) && + state->board_selected[ XYPOS( oldx, oldy)] != + state->board_selected[ XYPOS( state->x, state->y)] + ) + { + clix_update_selected( state); + } + clix_draw( state); + rb->sleep( time); + } + + + } + + rb->lcd_set_foreground( LCD_WHITE); + return true; +} + +void clix_show_highscore( int highscore[]) +{ + int i, j = 0; + char score[12]; + + rb->lcd_setfont(FONT_SYSFIXED); + rb->lcd_clear_display(); + rb->lcd_puts( 0, j++, "Highscore"); + rb->lcd_puts( 0, j++, "---------"); + for( i = 0; i < HS_ENTRY; ++i) + { + rb->snprintf( score, sizeof( score), "%d. %d", i + 1, highscore[i]); + rb->lcd_puts( 0, i + j, score); + } + + rb->lcd_update(); + rb->button_get( true); +} + +/* main menu */ +bool clix_menu( void) +{ + int m; + int result; + int highscore[ HS_ENTRY ]; + struct clix_game_state_t state; + + static const struct menu_item items[] = { + [CM_AUDIO_PLAYBACK] = { "Audio Playback", NULL }, + [CM_RESUME] = { "Resume", NULL }, + [CM_NEW] = { "New", NULL }, + [CM_HIGHSCORE] = { "Highscore", NULL }, + [CM_QUIT] = { "Quit", NULL }, + }; + + m = menu_init( rb, + items, + sizeof( items) / sizeof(*items), + NULL, + NULL, + NULL, + NULL); + + result = menu_show( m); + + switch( result) + { + case CM_AUDIO_PLAYBACK: + playback_control( rb); + break; + case CM_RESUME: + { + if( state.status == CLIX_CONTINUE) + clix_handle_game( &state); + else + { + rb->splash( HZ * 2, "No Game to Resume"); + clix_init( &state); + clix_handle_game( &state); + } + } + break; + case CM_NEW: + { + clix_init( &state); + clix_handle_game( &state); + } + break; + case CM_HIGHSCORE: + { + clix_load_highscore( highscore); + clix_show_highscore( highscore); + } + break; + case CM_QUIT: + /* save current game */ + menu_exit(m); + return false; + break; + default: break; + } + + menu_exit( m); + + return true; +} + +/* this is the plugin entry point */ +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) +{ + /* if you don't use the parameter, you can do like + this to avoid the compiler warning about it */ + (void)parameter; + + rb = api; /* use the "standard" rb pointer */ + + rb->lcd_set_backdrop(NULL); + rb->lcd_set_foreground(LCD_WHITE); + rb->lcd_set_background(LCD_BLACK); + + while( clix_menu()); + + rb->lcd_setfont(FONT_UI); + return PLUGIN_OK; +} + +#endif /* HAVE_LCD_BITMAP */