Index: tools/buildzip.pl
===================================================================
--- tools/buildzip.pl	(revision 13077)
+++ tools/buildzip.pl	(working copy)
@@ -266,6 +266,7 @@
     if($bitmap) {
         `cp $ROOT/apps/plugins/sokoban.levels .rockbox/rocks/`; # sokoban levels
         `cp $ROOT/apps/plugins/snake2.levels .rockbox/rocks/`; # snake2 levels
+        `cp $ROOT/apps/plugins/target_wordlist.dat .rockbox/rocks/`; # target wordlist
     }
 
     if($image) {
Index: apps/plugins/target.c
===================================================================
--- apps/plugins/target.c	(revision 0)
+++ apps/plugins/target.c	(revision 0)
@@ -0,0 +1,445 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Will Robertson
+ *
+ * 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"
+#define WORDLIST PLUGIN_DIR "/target_wordlist.dat"
+
+PLUGIN_HEADER
+
+static struct plugin_api* rb;
+
+struct game {
+    char words[50][10];
+    char centre;
+    int good;
+    int vgood;
+    int excellent;
+    char board[3][3];
+    char userwords[50][10];
+    int score;
+} game;
+
+int cursorx=1;
+int cursory=1;
+char temp[10];
+
+#define MARGIN_X 5
+#define MARGIN_Y 5
+#define BOX_WIDTH 25
+#define BOX_HEIGHT 20
+
+#if CONFIG_KEYPAD == IRIVER_H100_PAD || CONFIG_KEYPAD == IRIVER_H300_PAD
+#define TARGET_LEFT BUTTON_LEFT
+#define TARGET_RIGHT BUTTON_RIGHT
+#define TARGET_UP BUTTON_UP
+#define TARGET_DOWN BUTTON_DOWN
+#define TARGET_SELECT BUTTON_SELECT
+#define TARGET_MENU BUTTON_MODE
+#define TARGET_ENTER BUTTON_ON
+#define TARGET_DELETE BUTTON_REC
+
+#elif CONFIG_KEYPAD == IPOD_4G_PAD
+#define TARGET_LEFT BUTTON_SCROLL_BACK
+#define TARGET_RIGHT BUTTON_SCROLL_FWD
+#define TARGET_SELECT BUTTON_SELECT
+#define TARGET_MENU BUTTON_MENU
+#define TARGET_ENTER BUTTON_PLAY
+#define TARGET_DELETE BUTTON_LEFT
+
+#elif CONFIG_KEYPAD == IRIVER_H10_PAD
+#define TARGET_UP BUTTON_SCROLL_UP
+#define TARGET_DOWN BUTTON_SCROLL_DOWN
+#define TARGET_SELECT BUTTON_RIGHT
+#define TARGET_MENU BUTTON_STOP
+#define TARGET_ENTER BUTTON_PLAY
+#define TARGET_DELETE BUTTON_LEFT
+
+#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
+#define TARGET_LEFT BUTTON_LEFT
+#define TARGET_RIGHT BUTTON_RIGHT
+#define TARGET_UP BUTTON_UP
+#define TARGET_DOWN BUTTON_DOWN
+#define TARGET_SELECT BUTTON_SELECT
+#define TARGET_MENU BUTTON_REC
+#define TARGET_ENTER BUTTON_PLAY
+#define TARGET_DELETE BUTTON_POWER
+
+#endif
+
+void shuffle_word(char* word) {
+    bool first = true;
+    int i,x,y;
+    rb->srand(*rb->current_tick);
+    
+    if(!game.centre) {
+        game.centre = word[rb->rand()%9];
+    }
+    game.board[1][1] = game.centre;
+    for(i=0;i<9;i++) {
+        if(word[i] != game.centre || (word[i] == game.centre && !first)) {
+            x = rb->rand()%3;
+            y = rb->rand()%3;
+            while(game.board[x][y]) {
+                x = rb->rand()%3;
+                y = rb->rand()%3;
+            }
+            game.board[x][y] = word[i];
+        } else {
+            first = false;
+        }
+    }
+}
+
+int menu(void) {
+    int selection = 0;
+
+    MENUITEM_STRINGLIST(main_menu,"Target wordgame",NULL,
+                    "Play Target","Pick a word","Quit");
+
+    while(1) {
+        selection=rb->do_menu(&main_menu,&selection);
+        switch(selection) {
+            case 0:
+                return 0; /* start playing */
+                break;
+            case 1:
+                rb->kbd_input(temp, 10);
+                if(rb->strlen(temp)<9) {
+                    rb->splash(HZ, "Needs to be at exactly 9 letters");
+                } else {
+                    rb->memset(game.board, 0x00, sizeof(game.board));
+                    rb->memset(game.userwords, 0x00, sizeof(game.userwords));
+                    game.score = 0;
+                    game.centre = '\0';
+                    shuffle_word(temp);
+                    rb->snprintf(game.words[0], sizeof(game.words[0]), 
+                                    "%s",temp);
+                    rb->memset(temp, 0x00, sizeof(temp));
+                }
+                return 0;
+                break;
+            default:
+                return 2; /* quit program */
+                break;
+        }
+    }
+}
+
+void draw_cursor(void) {
+    rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
+    rb->lcd_fillrect(MARGIN_X+BOX_WIDTH*cursorx,MARGIN_Y+BOX_HEIGHT*cursory,
+                    BOX_WIDTH+1,BOX_HEIGHT+1);
+    rb->lcd_set_drawmode(DRMODE_SOLID);
+    rb->lcd_update();
+}
+
+int init_board(void) {
+    int fd;
+    int i,count,line;
+    char buffer[3000];
+    char* buffer2;
+    char* store;
+
+    fd = rb->open(WORDLIST, O_RDONLY);
+
+    if(fd < 0) {
+        return -1;
+    }
+    rb->read_line(fd, buffer, sizeof(buffer));
+    count = rb->atoi(buffer);
+    rb->srand(*rb->current_tick);
+    line = (rb->rand()%count)+1;
+    for(i=0;i<line;i++) {
+        rb->read_line(fd, buffer, sizeof(buffer));
+    }
+    i=0;
+    buffer2 = rb->strtok_r(buffer, " ", &store);
+    while(buffer2) {
+        if(i==0) {
+            game.good = rb->atoi(buffer2);
+        } else if(i==1) {
+            game.vgood = rb->atoi(buffer2);
+        } else if(i==2) {
+            game.excellent = rb->atoi(buffer2);
+        } else if(i==3) {
+            game.centre = buffer2[0];
+        } else {
+            rb->snprintf(game.words[i-4], 10, buffer2);
+        }
+        i++;
+        buffer2 = rb->strtok_r(NULL, " ", &store);
+    }
+    shuffle_word(game.words[0]);
+    return 0;
+}
+
+void show_grid(void) {
+    int i, j;
+    char buf[20];
+    rb->lcd_clear_display();
+    for(i=0;i<=3;i++) {  /* Vertical lines */
+        rb->lcd_drawline(MARGIN_X+(BOX_WIDTH*i), MARGIN_Y, 
+                        MARGIN_X+(BOX_WIDTH*i), 
+                        MARGIN_Y+(3*BOX_HEIGHT));
+    }
+    for(i=0;i<=3;i++) {  /* Horizontal lines */
+        rb->lcd_drawline(MARGIN_X, MARGIN_Y+(i*BOX_HEIGHT), 
+                        MARGIN_X+(3*BOX_WIDTH), 
+                        MARGIN_Y+(BOX_HEIGHT*i));
+    }
+    rb->lcd_set_foreground(LCD_LIGHTGRAY);
+    rb->lcd_fillrect(MARGIN_X+BOX_WIDTH+1,MARGIN_Y+BOX_HEIGHT+1,
+                    BOX_WIDTH-1,BOX_HEIGHT-1);
+    rb->lcd_set_foreground(LCD_BLACK);
+    for(i=0;i<3;i++) {
+        for(j=0;j<3;j++) {
+            rb->snprintf(buf, sizeof(buf), "%c", game.board[j][i]-32);
+            if(i==1 && j==1) {
+                rb->lcd_set_background(LCD_LIGHTGRAY);
+            }
+            rb->lcd_putsxy(MARGIN_X+(j*BOX_WIDTH)+9, 
+                            MARGIN_Y+(i*BOX_HEIGHT)+7, buf);
+            if(i==1 && j==1) {
+                rb->lcd_set_background(LCD_DEFAULT_BG);
+            }
+        }
+    }
+    rb->snprintf(buf, sizeof(buf), "%d words: good", game.good);
+    rb->lcd_putsxy(MARGIN_X*2+BOX_WIDTH*3, MARGIN_Y*2, buf);
+    rb->snprintf(buf, sizeof(buf), "%d words: very good", game.vgood);
+    rb->lcd_putsxy(MARGIN_X*2+BOX_WIDTH*3, MARGIN_Y*2+10, buf);
+    rb->snprintf(buf, sizeof(buf), "%d words: excellent", game.excellent);
+    rb->lcd_putsxy(MARGIN_X*2+BOX_WIDTH*3, MARGIN_Y*2+20, buf);
+    rb->lcd_drawline(0, MARGIN_Y*2+BOX_HEIGHT*3, LCD_WIDTH, 
+                    MARGIN_Y*2+BOX_HEIGHT*3);
+    rb->lcd_drawline(60, MARGIN_Y*2+BOX_HEIGHT*3, 60, LCD_HEIGHT);
+    rb->lcd_drawline(120, MARGIN_Y*2+BOX_HEIGHT*3, 120, LCD_HEIGHT);
+    rb->lcd_drawline(180, MARGIN_Y*2+BOX_HEIGHT*3, 180, LCD_HEIGHT);
+    for(i=0;i<game.score;i++) {
+        rb->snprintf(buf, sizeof(buf), "%s", game.userwords[i]);
+        if(i<10) {
+            rb->lcd_putsxy(2, MARGIN_Y*2+BOX_HEIGHT*3+10*(i)+2, buf);
+        } else if(i<20 && i>=10) {
+            rb->lcd_putsxy(2+60, MARGIN_Y*2+BOX_HEIGHT*3+10*(i-10)+2, buf);
+        } else {
+            rb->lcd_putsxy(2+120, MARGIN_Y*2+BOX_HEIGHT*3+10*(i-20)+2, buf);
+        }
+    }
+    rb->lcd_putsxy(MARGIN_X*2+BOX_WIDTH*3,MARGIN_Y+40, temp);
+    draw_cursor();
+    rb->lcd_update();
+}
+
+bool is_valid(char* word) {
+    int i;
+    for(i=0;i<game.excellent;i++) {
+        if(!rb->strcmp(game.words[i], word)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+int ingame_menu(void) {
+    int selection = 0;
+
+    MENUITEM_STRINGLIST(main_menu,"Target wordgame",NULL,
+                    "Reshuffle grid","New game","Pick custom word", 
+                    "Return to game", "Quit");
+
+    while(1) {
+        selection=rb->do_menu(&main_menu,&selection);
+        switch(selection) {
+            case 0:
+                rb->memset(game.board, 0x00, sizeof(game.board));
+                shuffle_word(game.words[0]);
+                return 0;
+                break;
+            case 1:
+                rb->memset(game.board, 0x00, sizeof(game.board));
+                rb->memset(game.userwords, 0x00, sizeof(game.userwords));
+                game.score = 0;
+                game.centre = '\0';
+                if(init_board()<0) {
+                    rb->splash(HZ, "Error reading wordlist file");
+                } else {
+                    return 0;
+                }
+                break;
+            case 2:
+                rb->kbd_input(temp, 10);
+                if(rb->strlen(temp)<9) {
+                    rb->splash(HZ, "Needs to be at exactly 9 letters");
+                } else {
+                    rb->memset(game.board, 0x00, sizeof(game.board));
+                    rb->memset(game.userwords, 0x00, sizeof(game.userwords));
+                    game.score = 0;
+                    game.centre = '\0';
+                    shuffle_word(temp);
+                    rb->snprintf(game.words[0], sizeof(game.words[0]), 
+                                    "%s",temp);
+                    rb->memset(temp, 0x00, sizeof(temp));
+                }
+                return 0;
+            case 3:
+                return 0;
+                break;
+            case 4:
+                return -1;
+                break;
+            case MENU_ATTACHED_USB:
+                return PLUGIN_USB_CONNECTED;
+                break;
+        }
+    }
+}
+
+enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
+{
+    (void)parameter;
+    rb = api;
+    int button;
+    rb->memset(temp, 0x00, sizeof(temp));
+    bool statusbar_setting;
+    game.score = 0;
+    statusbar_setting = rb->global_settings->statusbar;
+    rb->global_settings->statusbar = false;
+    bool play = false;
+    while(!play) {
+        switch(menu()) {
+            case 0:
+                if(init_board()<0) {
+                    rb->splash(HZ, "Couldn't load wordlist.");
+                    rb->global_settings->statusbar = statusbar_setting;
+                    return PLUGIN_ERROR;
+                }
+                play = true;
+                break;
+            case 2:
+                rb->global_settings->statusbar = statusbar_setting;
+                return PLUGIN_OK;
+                break;
+        }
+    }
+    show_grid();
+    while(1) {
+        button = rb->button_get(true);
+        switch(button) {
+            case TARGET_SELECT:
+                if(rb->strlen(temp)<9) {
+                    temp[rb->strlen(temp)] = game.board[cursorx][cursory];
+                }
+                break;
+            case TARGET_DELETE:
+                if(temp) {
+                    temp[rb->strlen(temp)-1] = 0x00;
+                }
+                break;
+#if CONFIG_KEYPAD != IRIVER_H10_PAD
+            case TARGET_LEFT:
+            case TARGET_LEFT|BUTTON_REPEAT:
+                if(cursorx == 0) {
+#if CONFIG_KEYPAD == IPOD_4G_PAD
+                    if(cursory==0) {
+                        cursory=2;
+                    } else {
+                        cursory--;
+                    }
+#endif
+                    cursorx=2;
+                } else {
+                    cursorx--;
+                }
+                break;
+            case TARGET_RIGHT:
+            case TARGET_RIGHT|BUTTON_REPEAT:
+                if(cursorx == 2) {
+#if CONFIG_KEYPAD == IPOD_4G_PAD
+                    if(cursory==2) {
+                        cursory=0;
+                    } else {
+                        cursory++;
+                    }
+#endif
+                    cursorx=0;
+                } else {
+                    cursorx++;
+                }
+                break;
+#endif
+#if CONFIG_KEYPAD != IPOD_4G_PAD
+            case TARGET_UP:
+            case TARGET_UP|BUTTON_REPEAT:
+                if(cursory == 0) {
+#if CONFIG_KEYPAD == IRIVER_H10_PAD
+                    if(cursorx == 0) {
+                        cursorx = 2;
+                    } else {
+                        cursorx--;
+                    }
+#endif
+                    cursory = 2;
+                } else  {
+                    cursory--;
+                }
+                break;
+            case TARGET_DOWN:
+            case TARGET_DOWN|BUTTON_REPEAT:
+                if(cursory == 2) {
+#if CONFIG_KEYPAD == IRIVER_H10_PAD
+                    if(cursorx == 2) {
+                        cursorx = 0;
+                    } else {
+                        cursorx++;
+                    }
+#endif
+                    cursory = 0;
+                } else  {
+                    cursory++;
+                }
+                break;
+#endif
+            case TARGET_ENTER:
+                if(is_valid(temp)) {
+                    rb->snprintf(game.userwords[game.score], 10, temp);
+                    game.score++;
+                } else {
+                    rb->splash(HZ, "Invalid word");
+                }
+                rb->memset(temp, 0x00, sizeof(temp));
+                break;
+            case TARGET_MENU:
+                switch(ingame_menu()) {
+                    case -1:
+                        rb->global_settings->statusbar = statusbar_setting;
+                        return PLUGIN_OK;
+                        break;
+                    case PLUGIN_USB_CONNECTED:
+                        rb->global_settings->statusbar = statusbar_setting;
+                        return PLUGIN_USB_CONNECTED;
+                        break;
+                }
+                break;
+        }
+        show_grid();
+    }
+
+
+    rb->global_settings->statusbar = statusbar_setting;
+    return PLUGIN_OK;
+}
Index: apps/plugins/SOURCES
===================================================================
--- apps/plugins/SOURCES	(revision 13077)
+++ apps/plugins/SOURCES	(working copy)
@@ -35,6 +35,7 @@
 
 #ifdef HAVE_LCD_BITMAP             /* Not for the Player */
 mazezam.c
+target.c
 text_editor.c
 
 /* Plugins needing the grayscale lib on low-depth LCDs */
Index: apps/plugins/target_wordlist.dat
===================================================================
--- apps/plugins/target_wordlist.dat	(revision 0)
+++ apps/plugins/target_wordlist.dat	(revision 0)
@@ -0,0 +1,11 @@
+10
+21 32 43 c lecherous cheer choler chore chorus chose churl close closer closure clue cohere cole coleus core cosh course creel cruel crush cure curl curse echo euchre lecher leech loch locus lucre lurch ochre ouch recluse rescue ruche score scour secure slouch source such ulcer
+20 30 41 n chronicle chin chine chlorine chronic cinch clench clinch clincher clone coin conch cone conic corn cornice crone enrich enrol heroin heron hone horn icon inch iron lichen lien line liner lino lion loin lone loner nice nicer niche once rein rhino
+15 23 31 r pimpernel emir empire ermine leer leper liner mere merlin miner mire nipper peer peril perm pier piper preen prep prim prime primp reel rein repel repine rile rime ripe ripen ripple
+14 21 28 t territory otter retort retry riot rioter rite rort rote terror terry tier tire titre tore torr tort torte tote trey trier trio trite trot troy tyre tyro yeti
+13 20 27 n opportune nope note onto open  pent peon pone prone pronto proton prune punt punter rent rune runt tenor tern tone toner torn tune tuner turn unto upon
+21 31 41 u magnitude adieu ague atune audit augment aunt auntie datum daunt duet dune dung gamut gaunt geum guide guinea indue menu minuet minute mung mute muted nude nudge nutmeg tedium tumid tuna tune tuned tung unit unite united unmade untamed untie untied
+17 25 33 o pathology ahoy alto apology atop gaol gloat goal goat halo holt holy hoop hoot hotly loath logo loop loot loth oath opal photo plot ploy pogo polo pooh pool toga tool typo yoga
+15 22 31 n venerable baleen bane barn bean been bran earn elan eleven enable enabler even lane lean leaner learn leaven nave navel near nerve neve never raven renal vane venal veneer verbena vernal
+21 32 42 d fraudster dare dart darter date dater deaf dear deft draft drafter drat duet dust duster fade fader fared fasted fated feud fraud furred fused rased rated read retard rude rudest rued rusted sated stared starred stead stud sued surd tarred trade trader tread trued used
+20 30 39 c utterance acne acre acute cane cant canter care careen caret cart carte cater cent centaur centre crane crate create cruet curate cure curt cute cutter enact erect nacre nectar race react recant recent tact trace tract trance truce truncate
Index: apps/FILES
===================================================================
--- apps/FILES	(revision 13077)
+++ apps/FILES	(working copy)
@@ -40,6 +40,7 @@
 plugins/plugin.lds
 plugins/snake2.levels
 plugins/sokoban.levels
+plugins/target_wordlist.dat
 plugins/viewers.config
 plugins/SOURCES
 plugins/SUBDIRS
