/* * xhtml backend for Halibut * (initial implementation by James Aylett) * * Still to do: * * +++ doesn't handle non-breaking hyphens. Not sure how to yet. * +++ entity names (from a file -- ideally supply normal SGML files) * +++ configuration directive to file split where the current layout * code wouldn't. Needs changes to _ponder_layout() and _do_paras(), * perhaps others. * * Limitations: * * +++ biblio/index references target the nearest section marker, rather * than having a dedicated target themselves. In large bibliographies * this will cause problems. (The solution is to fake up a response * from xhtml_find_section(), probably linking it into the sections * chain just in case we need it again, and to make freeing it up * easier.) docsrc.pl used to work as we do, however, and SGT agrees that * this is acceptable for now. * +++ can't cope with leaf-level == 0. It's all to do with the * top-level file not being normal, probably not even having a valid * section level, and stuff like that. I question whether this is an * issue, frankly; small manuals that fit on one page should probably * not be written in halibut at all. */ #include #include #include #include #include "halibut.h" /* * FILENAME_TEMPLATE (overridable in config of course) allows you * to choose the general form for your HTML file names. It is * slightly printf-styled (% followed by a single character is a * formatting directive, %% is a literal %). Formatting directives * are: * * - %n is the section type-plus-number, minus whitespace (`Chapter1.2'). * - %b is the section number on its own (`1.2'). * - %k is the section's _internal_ keyword. * - %N is the section's visible title in the output, again minus * whitespace. * * %n, %b and %k will all default to %N if the section is * unnumbered (`Bibliography' is often a good example). * * FRAGMENT_TEMPLATE is the same, but defines the * markers used to cross-reference to particular subsections of a * file. */ #define FILENAME_SINGLE "Manual.html" #define FILENAME_CONTENTS "Contents.html" #define FILENAME_INDEX "IndexPage.html" #define FILENAME_TEMPLATE "%n.html" #define FRAGMENT_TEMPLATE "%b" struct xhtmlsection_Struct { struct xhtmlsection_Struct *next; /* next sibling (NULL if split across files) */ struct xhtmlsection_Struct *child; /* NULL if split across files */ struct xhtmlsection_Struct *parent; /* NULL if split across files */ struct xhtmlsection_Struct *chain; /* single structure independent of weird trees */ paragraph *para; struct xhtmlfile_Struct *file; /* which file is this a part of? */ char *fragment; /* fragment id within the file */ int level; }; struct xhtmlfile_Struct { struct xhtmlfile_Struct *next; struct xhtmlfile_Struct *child; struct xhtmlfile_Struct *parent; char *filename; struct xhtmlsection_Struct *sections; /* sections within this file (only one for non-leaf) */ int is_leaf; /* is this file a leaf file, ie does it not have any children? */ }; typedef struct xhtmlsection_Struct xhtmlsection; typedef struct xhtmlfile_Struct xhtmlfile; typedef struct xhtmlindex_Struct xhtmlindex; struct xhtmlindex_Struct { int nsection; int size; xhtmlsection **sections; }; typedef struct { int just_numbers; wchar_t *number_suffix; } xhtmlheadfmt; typedef struct { int contents_depth[6]; int leaf_contains_contents; int leaf_level; int leaf_smallest_contents; int include_version_id; wchar_t *author, *description; wchar_t *head_end, *body, *body_start, *body_end, *address_start, *address_end, *nav_attrs; int suppress_address; xhtmlheadfmt fchapter, *fsect; int nfsect; char *contents_filename, *index_filename; char *single_filename, *template_filename, *template_fragment; } xhtmlconfig; /*static void xhtml_level(paragraph *, int); static void xhtml_level_0(paragraph *); static void xhtml_docontents(FILE *, paragraph *, int); static void xhtml_dosections(FILE *, paragraph *, int); static void xhtml_dobody(FILE *, paragraph *, int);*/ static void xhtml_doheader(FILE *, word *); static void xhtml_dofooter(FILE *); static void xhtml_versionid(FILE *, word *, int); static void xhtml_utostr(wchar_t *, char **); static int xhtml_para_level(paragraph *); static int xhtml_reservedchar(int); static int xhtml_convert(wchar_t *, int, char **, int); static void xhtml_rdaddwc(rdstringc *, word *, word *, int); static void xhtml_para(FILE *, word *, int); static void xhtml_codepara(FILE *, word *); static void xhtml_heading(FILE *, paragraph *, int); /* File-global variables are much easier than passing these things * all over the place. Evil, but easier. We can replace this with a single * structure at some point. */ static xhtmlconfig conf; static keywordlist *keywords; static indexdata *idx; static xhtmlfile *topfile; static xhtmlsection *topsection; static paragraph *sourceparas; static xhtmlfile *lastfile; static xhtmlfile *xhtml_last_file = NULL; static int last_level=-1, start_level; static xhtmlsection *currentsection; static xhtmlconfig xhtml_configure(paragraph *source) { xhtmlconfig ret; /* * Defaults. */ ret.contents_depth[0] = 2; ret.contents_depth[1] = 3; ret.contents_depth[2] = 4; ret.contents_depth[3] = 5; ret.contents_depth[4] = 6; ret.contents_depth[5] = 7; ret.leaf_level = 2; ret.leaf_smallest_contents = 4; ret.leaf_contains_contents = FALSE; ret.include_version_id = TRUE; ret.author = NULL; ret.description = NULL; ret.head_end = NULL; ret.body = NULL; ret.body_start = NULL; ret.body_end = NULL; ret.address_start = NULL; ret.address_end = NULL; ret.nav_attrs = NULL; ret.suppress_address = FALSE; ret.fchapter.just_numbers = FALSE; ret.fchapter.number_suffix = L": "; ret.nfsect = 2; ret.fsect = mknewa(xhtmlheadfmt, ret.nfsect); ret.fsect[0].just_numbers = FALSE; ret.fsect[0].number_suffix = L": "; ret.fsect[1].just_numbers = TRUE; ret.fsect[1].number_suffix = L" "; ret.contents_filename = strdup(FILENAME_CONTENTS); ret.single_filename = strdup(FILENAME_SINGLE); ret.index_filename = strdup(FILENAME_INDEX); ret.template_filename = strdup(FILENAME_TEMPLATE); ret.template_fragment = strdup(FRAGMENT_TEMPLATE); for (; source; source = source->next) { if (source->type == para_Config) { if (!ustricmp(source->keyword, L"xhtml-contents-filename")) { sfree(ret.contents_filename); ret.contents_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-single-filename")) { sfree(ret.single_filename); ret.single_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-index-filename")) { sfree(ret.index_filename); ret.index_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-template-filename")) { sfree(ret.template_filename); ret.template_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-template-fragment")) { sfree(ret.template_fragment); ret.template_fragment = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-0")) { ret.contents_depth[0] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-1")) { ret.contents_depth[1] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-2")) { ret.contents_depth[2] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-3")) { ret.contents_depth[3] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-4")) { ret.contents_depth[4] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-5")) { ret.contents_depth[5] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-leaf-level")) { ret.leaf_level = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-leaf-smallest-contents")) { ret.leaf_smallest_contents = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-versionid")) { ret.include_version_id = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-leaf-contains-contents")) { ret.leaf_contains_contents = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-suppress-address")) { ret.suppress_address = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-author")) { ret.author = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-description")) { ret.description = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-head-end")) { ret.head_end = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-body-start")) { ret.body_start = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-body-tag")) { ret.body = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-body-end")) { ret.body_end = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-address-start")) { ret.address_start = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-address-end")) { ret.address_end = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-navigation-attributes")) { ret.nav_attrs = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-chapter-numeric")) { ret.fchapter.just_numbers = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-chapter-suffix")) { ret.fchapter.number_suffix = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-section-numeric")) { wchar_t *p = uadv(source->keyword); int n = 0; if (uisdigit(*p)) { n = utoi(p); p = uadv(p); } if (n >= ret.nfsect) { int i; ret.fsect = resize(ret.fsect, n+1); for (i = ret.nfsect; i <= n; i++) ret.fsect[i] = ret.fsect[ret.nfsect-1]; ret.nfsect = n+1; } ret.fsect[n].just_numbers = utob(p); } else if (!ustricmp(source->keyword, L"xhtml-section-suffix")) { wchar_t *p = uadv(source->keyword); int n = 0; if (uisdigit(*p)) { n = utoi(p); p = uadv(p); } if (n >= ret.nfsect) { int i; ret.fsect = resize(ret.fsect, n+1); for (i = ret.nfsect; i <= n; i++) ret.fsect[i] = ret.fsect[ret.nfsect-1]; ret.nfsect = n+1; } ret.fsect[n].number_suffix = p; } } } /* printf(" !!! leaf_level = %i\n", ret.leaf_level); printf(" !!! contentdepth-0 = %i\n", ret.contents_depth[0]); printf(" !!! contentdepth-1 = %i\n", ret.contents_depth[1]); printf(" !!! contentdepth-2 = %i\n", ret.contents_depth[2]); printf(" !!! contentdepth-3 = %i\n", ret.contents_depth[3]); printf(" !!! contentdepth-4 = %i\n", ret.contents_depth[4]); printf(" !!! contentdepth-5 = %i\n", ret.contents_depth[5]); printf(" !!! leaf_contains_contents = %i\n", ret.leaf_contains_contents);*/ return ret; } paragraph *xhtml_config_filename(char *filename) { /* * If the user passes in a single filename as a parameter to * the `--html' command-line option, then we should assume it * to imply _two_ config directives: * \cfg{xhtml-single-filename}{whatever} and * \cfg{xhtml-leaf-level}{0}; the rationale being that the user * wants their output _in that file_. */ paragraph *p[2]; int i, len; wchar_t *ufilename, *up; for (i = 0; i < 2; i++) { p[i] = mknew(paragraph); memset(p[i], 0, sizeof(*p[i])); p[i]->type = para_Config; p[i]->next = NULL; p[i]->fpos.filename = ""; p[i]->fpos.line = p[i]->fpos.col = -1; } ufilename = ufroma_dup(filename); len = ustrlen(ufilename) + 2 + lenof(L"xhtml-single-filename"); p[0]->keyword = mknewa(wchar_t, len); up = p[0]->keyword; ustrcpy(up, L"xhtml-single-filename"); up = uadv(up); ustrcpy(up, ufilename); up = uadv(up); *up = L'\0'; assert(up - p[0]->keyword < len); sfree(ufilename); len = lenof(L"xhtml-leaf-level") + lenof(L"0") + 1; p[1]->keyword = mknewa(wchar_t, len); up = p[1]->keyword; ustrcpy(up, L"xhtml-leaf-level"); up = uadv(up); ustrcpy(up, L"0"); up = uadv(up); *up = L'\0'; assert(up - p[1]->keyword < len); p[0]->next = p[1]; return p[0]; } static xhtmlsection *xhtml_new_section(xhtmlsection *last) { xhtmlsection *ret = mknew(xhtmlsection); ret->next=NULL; ret->child=NULL; ret->parent=NULL; ret->chain=last; ret->para=NULL; ret->file=NULL; ret->fragment=NULL; ret->level=-1; /* marker: end of chain */ return ret; } /* Returns NULL or the section that marks that paragraph */ static xhtmlsection *xhtml_find_section(paragraph *p) { xhtmlsection *ret = topsection; if (xhtml_para_level(p)==-1) { /* first, we back-track to a section paragraph */ paragraph *p2 = sourceparas; paragraph *p3 = NULL; while (p2 && p2!=p) { if (xhtml_para_level(p2)!=-1) { p3 = p2; } p2=p2->next; } if (p3==NULL) { /* for some reason, we couldn't find a section before this paragraph ... ? */ /* Note that this can happen, if you have a cross-reference to before the first chapter starts. * So don't do that, then. */ return NULL; } p=p3; } while (ret && ret->para != p) { /* printf(" xhtml_find_section(): checking %s for para @ %p\n", ret->fragment, p);*/ ret=ret->chain; } return ret; } static void xhtml_format(paragraph *p, char *template_string, rdstringc *r) { char *c, *t; word *w; wchar_t *ws; t = template_string; while (*t) { if (*t == '%' && t[1]) { int fmt; t++; fmt = *t++; if (fmt == '%') { rdaddc(r, fmt); continue; } w = NULL; ws = NULL; if (p->kwtext && fmt == 'n') w = p->kwtext; else if (p->kwtext2 && fmt == 'b') w = p->kwtext2; else if (p->keyword && *p->keyword && fmt == 'k') ws = p->keyword; else w = p->words; while (w) { switch (removeattr(w->type)) { case word_Normal: /*case word_Emph: case word_Code: case word_WeakCode:*/ xhtml_utostr(w->text, &c); rdaddsc(r,c); sfree(c); break; } w = w->next; } if (ws) { xhtml_utostr(ws, &c); rdaddsc(r,c); sfree(c); } } else { rdaddc(r, *t++); } } } static xhtmlfile *xhtml_new_file(xhtmlsection *sect) { xhtmlfile *ret = mknew(xhtmlfile); ret->next=NULL; ret->child=NULL; ret->parent=NULL; ret->filename=NULL; ret->sections=sect; ret->is_leaf=(sect!=NULL && sect->level==conf.leaf_level); if (sect==NULL) { if (conf.leaf_level==0) { /* currently unused */ ret->filename = smalloc(strlen(conf.single_filename)+1); sprintf(ret->filename, conf.single_filename); } else { ret->filename = smalloc(strlen(conf.contents_filename)+1); sprintf(ret->filename, conf.contents_filename); } } else { paragraph *p = sect->para; rdstringc fname_c = { 0, 0, NULL }; xhtml_format(p, conf.template_filename, &fname_c); ret->filename = rdtrimc(&fname_c); } /* printf(" ! new file '%s', is_leaf == %s\n", ret->filename, (ret->is_leaf)?("true"):("false"));*/ return ret; } /* * Walk the tree fixing up files which are actually leaf (ie * have no children) but aren't at leaf level, so they have the * leaf flag set. */ void xhtml_fixup_layout(xhtmlfile* file) { if (file->child==NULL) { file->is_leaf = TRUE; } else { xhtml_fixup_layout(file->child); } if (file->next) xhtml_fixup_layout(file->next); } /* * Create the tree structure so we know where everything goes. * Method: * * Ignoring file splitting, we have three choices with each new section: * * +-----------------+-----------------+ * | | | * X +----X----+ (1) * | | * Y (2) * | * (3) * * Y is the last section we added (currentsect). * If sect is the section we want to add, then: * * (1) if sect->level < currentsect->level * (2) if sect->level == currentsect->level * (3) if sect->level > currentsect->level * * This requires the constraint that you never skip section numbers * (so you can't have a.b.c.d without all of a, a.b and a.b.c existing). * * Note that you _can_ have 1.1.1.1 followed by 1.2 - you can change * more than one level at a time. Lots of asserts, and probably part of * the algorithm here, rely on this being true. (It currently isn't * enforced by halibut, however.) * * File splitting makes this harder. For instance, say we added at (3) * above and now need to add another section. We are splitting at level * 2, ie the level of Y. Z is the last section we added: * * +-----------------+-----------------+ * | | | * X +----X----+ (1) * | | * +----Y----+ (1) * | | * Z (2) * | * (3) * * The (1) case is now split; we need to search upwards to find where * to actually link in. The other two cases remain the same (and will * always be like this). * * File splitting makes this harder, however. The decision of whether * to split to a new file is always on the same condition, however (is * the level of this section higher than the leaf_level configuration * value or not). * * Treating the cases backwards: * * (3) same file if sect->level > conf.leaf_level, otherwise new file * * if in the same file, currentsect->child points to sect * otherwise the linking is done through the file tree (which works * in more or less the same way, ie currentfile->child points to * the new file) * * (2) same file if sect->level > conf.leaf_level, otherwise new file * * if in the same file, currentsect->next points to sect * otherwise file linking and currentfile->next points to the new * file (we know that Z must have caused a new file to be created) * * (1) same file if sect->level > conf.leaf_level, otherwise new file * * this is actually effectively the same case as (2) here, * except that we first have to travel up the sections to figure * out which section this new one will be a sibling of. In doing * so, we may disappear off the top of a file and have to go up * to its parent in the file tree. * */ static void xhtml_ponder_layout(paragraph *p) { xhtmlsection *lastsection; xhtmlsection *currentsect; xhtmlfile *currentfile; lastfile = NULL; topsection = xhtml_new_section(NULL); topfile = xhtml_new_file(NULL); lastsection = topsection; currentfile = topfile; currentsect = topsection; if (conf.leaf_level == 0) { topfile->is_leaf = 1; topfile->sections = topsection; topsection->file = topfile; } for (; p; p=p->next) { int level = xhtml_para_level(p); if (level>0) /* actually a section */ { xhtmlsection *sect; rdstringc frag_c = { 0, 0, NULL }; sect = xhtml_new_section(lastsection); lastsection = sect; sect->para = p; xhtml_format(p, conf.template_fragment, &frag_c); sect->fragment = rdtrimc(&frag_c); sect->level = level; /* printf(" ! adding para @ %p as sect %s, level %i\n", sect->para, sect->fragment, level);*/ if (level>currentsect->level) { /* case (3) */ if (level>conf.leaf_level) { /* same file */ assert(currentfile->is_leaf); currentsect->child = sect; sect->parent=currentsect; sect->file=currentfile; /* printf("connected '%s' to existing file '%s' [I]\n", sect->fragment, currentfile->filename);*/ currentsect=sect; } else { /* new file */ xhtmlfile *file = xhtml_new_file(sect); assert(!currentfile->is_leaf); currentfile->child=file; sect->file=file; file->parent=currentfile; /* printf("connected '%s' to new file '%s' [I]\n", sect->fragment, file->filename);*/ currentfile=file; currentsect=sect; } } else if (level >= currentsect->file->sections->level) { /* Case (1) or (2) *AND* still under the section that starts * the current file. * * I'm not convinced that this couldn't be rolled in with the * final else {} leg further down. It seems a lot of effort * this way. */ if (level>conf.leaf_level) { /* stick within the same file */ assert(currentfile->is_leaf); sect->file = currentfile; while (currentsect && currentsect->level > level && currentsect->file==currentsect->parent->file) { currentsect = currentsect->parent; } assert(currentsect); currentsect->next = sect; assert(currentsect->level == sect->level); sect->parent = currentsect->parent; currentsect = sect; /* printf("connected '%s' to existing file '%s' [II]\n", sect->fragment, currentfile->filename);*/ } else { /* new file */ xhtmlfile *file = xhtml_new_file(sect); sect->file=file; currentfile->next=file; file->parent=currentfile->parent; file->is_leaf=(level==conf.leaf_level); file->sections=sect; /* printf("connected '%s' to new file '%s' [II]\n", sect->fragment, file->filename);*/ currentfile=file; currentsect=sect; } } else { /* Case (1) or (2) and we must move up the file tree first */ /* this loop is now probably irrelevant - we know we can't connect * to anything in the current file */ while (currentsect && levellevel) { currentsect=currentsect->parent; if (currentsect) { /* printf(" * up one level to '%s'\n", currentsect->fragment);*/ } else { /* printf(" * up one level (off top of current file)\n");*/ } } if (currentsect) { /* I'm pretty sure this can now never fire */ assert(currentfile->is_leaf); /* printf("connected '%s' to existing file '%s' [III]\n", sect->fragment, currentfile->filename);*/ sect->file = currentfile; currentsect->next=sect; currentsect=sect; } else { /* find a file we can attach to */ while (currentfile && currentfile->sections && levelsections->level) { currentfile=currentfile->parent; if (currentfile) { /* printf(" * up one file level to '%s'\n", currentfile->filename);*/ } else { /* printf(" * up one file level (off top of tree)\n");*/ } } if (currentfile) { /* new file (we had to skip up a file to get here, so we must be dealing with a level no lower than the configured leaf_level */ xhtmlfile *file = xhtml_new_file(sect); currentfile->next=file; sect->file=file; file->parent=currentfile->parent; file->is_leaf=(level==conf.leaf_level); file->sections=sect; /* printf("connected '%s' to new file '%s' [III]\n", sect->fragment, file->filename);*/ currentfile=file; currentsect=sect; } else { fatal(err_whatever, "Ran off the top trying to connect sibling: strange document."); } } } } } topsection = lastsection; /* get correct end of the chain */ xhtml_fixup_layout(topfile); /* leaf files not at leaf level marked as such */ } static void xhtml_do_index(); static void xhtml_do_file(xhtmlfile *file); static void xhtml_do_top_file(xhtmlfile *file, paragraph *sourceform); static void xhtml_do_paras(FILE *fp, paragraph *p, paragraph *end, int indexable); static int xhtml_do_contents_limit(FILE *fp, xhtmlfile *file, int limit); static int xhtml_do_contents_section_limit(FILE *fp, xhtmlsection *section, int limit); static int xhtml_add_contents_entry(FILE *fp, xhtmlsection *section, int limit); static int xhtml_do_contents(FILE *fp, xhtmlfile *file); static int xhtml_do_naked_contents(FILE *fp, xhtmlfile *file); static void xhtml_do_sections(FILE *fp, xhtmlsection *sections); /* * Do all the files in this structure. */ static void xhtml_do_files(xhtmlfile *file) { xhtml_do_file(file); if (file->child) xhtml_do_files(file->child); if (file->next) xhtml_do_files(file->next); } /* * Free up all memory used by the file tree from 'xfile' downwards */ static void xhtml_free_file(xhtmlfile* xfile) { if (xfile==NULL) { return; } if (xfile->filename) { sfree(xfile->filename); } xhtml_free_file(xfile->child); xhtml_free_file(xfile->next); sfree(xfile); } /* * Main function. */ void xhtml_backend(paragraph *sourceform, keywordlist *in_keywords, indexdata *in_idx) { /* int i;*/ indexentry *ientry; int ti; xhtmlsection *xsect; sourceparas = sourceform; conf = xhtml_configure(sourceform); keywords = in_keywords; idx = in_idx; /* Clear up the index entries backend data pointers */ for (ti=0; (ientry = (indexentry *)index234(idx->entries, ti))!=NULL; ti++) { ientry->backend_data=NULL; } xhtml_ponder_layout(sourceform); /* old system ... (writes to *.alt, but gets some stuff wrong and is ugly) */ /* xhtml_level_0(sourceform); for (i=1; i<=conf.leaf_level; i++) { xhtml_level(sourceform, i); }*/ /* new system ... (writes to *.html, but isn't fully trusted) */ xhtml_do_top_file(topfile, sourceform); assert(!topfile->next); /* shouldn't have a sibling at all */ if (topfile->child) { xhtml_do_files(topfile->child); xhtml_do_index(); } /* release file, section, index data structures */ xsect = topsection; while (xsect) { xhtmlsection *tmp = xsect->chain; if (xsect->fragment) { sfree(xsect->fragment); } sfree(xsect); xsect = tmp; } xhtml_free_file(topfile); for (ti = 0; (ientry=(indexentry *)index234(idx->entries, ti))!=NULL; ti++) { if (ientry->backend_data!=NULL) { xhtmlindex *xi = (xhtmlindex*) ientry->backend_data; if (xi->sections!=NULL) { sfree(xi->sections); } sfree(xi); } ientry->backend_data = NULL; } sfree(conf.fsect); } static int xhtml_para_level(paragraph *p) { switch (p->type) { case para_Title: return 0; break; case para_UnnumberedChapter: case para_Chapter: case para_Appendix: return 1; break; /* case para_BiblioCited: return 2; break;*/ case para_Heading: case para_Subsect: return p->aux+2; break; default: return -1; break; } } /* Output the nav links for the current file. * file == NULL means we're doing the index */ static void xhtml_donavlinks(FILE *fp, xhtmlfile *file) { xhtmlfile *xhtml_next_file = NULL; fprintf(fp, "", conf.nav_attrs); } else { fprintf(fp, ">"); } if (xhtml_last_file==NULL) { fprintf(fp, "Previous | "); } else { fprintf(fp, "Previous | ", xhtml_last_file->filename); } fprintf(fp, "Contents | ", conf.contents_filename); if (file == NULL) { fprintf(fp, "Index | "); } else { fprintf(fp, "Index | ", conf.index_filename); } if (file != NULL) { /* otherwise we're doing nav links for the index */ if (xhtml_next_file==NULL) xhtml_next_file = file->child; if (xhtml_next_file==NULL) xhtml_next_file = file->next; if (xhtml_next_file==NULL) xhtml_next_file = file->parent->next; } if (xhtml_next_file==NULL) { if (file==NULL) { /* index, so no next file */ fprintf(fp, "Next "); } else { fprintf(fp, "Next", conf.index_filename); } } else { fprintf(fp, "Next", xhtml_next_file->filename); } fprintf(fp, "

\n"); } /* Write out the index file */ static void xhtml_do_index_body(FILE *fp) { indexentry *y; int ti; if (count234(idx->entries) == 0) return; /* don't write anything at all */ fprintf(fp, "
\n"); /* iterate over idx->entries using the tree functions and display everything */ for (ti = 0; (y = (indexentry *)index234(idx->entries, ti)) != NULL; ti++) { if (y->backend_data) { int i; xhtmlindex *xi; fprintf(fp, "
"); xhtml_para(fp, y->text, FALSE); fprintf(fp, "
\n
"); xi = (xhtmlindex*) y->backend_data; for (i=0; insection; i++) { xhtmlsection *sect = xi->sections[i]; if (sect) { fprintf(fp, "", sect->file->filename, sect->fragment); if (sect->para->kwtext) { xhtml_para(fp, sect->para->kwtext, FALSE); } else if (sect->para->words) { xhtml_para(fp, sect->para->words, FALSE); } fprintf(fp, ""); if (i+1nsection) { fprintf(fp, ", "); } } } fprintf(fp, "
\n"); } } fprintf(fp, "
\n"); } static void xhtml_do_index() { word temp_word = { NULL, NULL, word_Normal, 0, 0, L"Index", { NULL, 0, 0} }; FILE *fp = fopen(conf.index_filename, "w"); if (fp==NULL) fatal(err_cantopenw, conf.index_filename); xhtml_doheader(fp, &temp_word); xhtml_donavlinks(fp, NULL); xhtml_do_index_body(fp); xhtml_donavlinks(fp, NULL); xhtml_dofooter(fp); fclose(fp); } /* Output the given file. This includes whatever contents at beginning and end, etc. etc. */ static void xhtml_do_file(xhtmlfile *file) { FILE *fp = fopen(file->filename, "w"); if (fp==NULL) fatal(err_cantopenw, file->filename); if (file->sections->para->words) { xhtml_doheader(fp, file->sections->para->words); } else if (file->sections->para->kwtext) { xhtml_doheader(fp, file->sections->para->kwtext); } else { xhtml_doheader(fp, NULL); } xhtml_donavlinks(fp, file); if (file->is_leaf && conf.leaf_contains_contents && xhtml_do_contents(NULL, file)>=conf.leaf_smallest_contents) xhtml_do_contents(fp, file); xhtml_do_sections(fp, file->sections); if (!file->is_leaf) xhtml_do_naked_contents(fp, file); xhtml_donavlinks(fp, file); xhtml_dofooter(fp); fclose(fp); xhtml_last_file = file; } /* Output the top-level file. */ static void xhtml_do_top_file(xhtmlfile *file, paragraph *sourceform) { paragraph *p; int done=FALSE; FILE *fp = fopen(file->filename, "w"); if (fp==NULL) fatal(err_cantopenw, file->filename); /* Do the title -- only one allowed */ for (p = sourceform; p && !done; p = p->next) { if (p->type == para_Title) { xhtml_doheader(fp, p->words); done=TRUE; } } if (!done) xhtml_doheader(fp, NULL /* Eek! */); /* * Display the title. */ for (p = sourceform; p; p = p->next) { if (p->type == para_Title) { xhtml_heading(fp, p, FALSE); break; } } /* Do the preamble */ for (p = sourceform; p; p = p->next) { if (p->type == para_Chapter || p->type == para_Heading || p->type == para_Subsect || p->type == para_Appendix || p->type == para_UnnumberedChapter) { /* * We've found the end of the preamble. Do every normal * paragraph up to there. */ xhtml_do_paras(fp, sourceform, p, FALSE); break; } } xhtml_do_contents(fp, file); xhtml_do_sections(fp, file->sections); /* * Put the index in the top file if we're in single-file mode * (leaf-level 0). */ if (conf.leaf_level == 0 && count234(idx->entries) > 0) { fprintf(fp, "

Index

\n"); xhtml_do_index_body(fp); } xhtml_dofooter(fp); fclose(fp); } /* Convert a Unicode string to an ASCII one. '?' is * used for unmappable characters. */ static void xhtml_utostr(wchar_t *in, char **out) { int l = ustrlen(in); int i; *out = smalloc(l+1); for (i=0; i=32 && in[i]<=126) (*out)[i]=(char)in[i]; else (*out)[i]='?'; } (*out)[i]=0; } /* * Write contents for the given file, and subfiles, down to * the appropriate contents depth. Returns the number of * entries written. */ static int xhtml_do_contents(FILE *fp, xhtmlfile *file) { int level, limit, count = 0; if (!file) return 0; level = (file->sections)?(file->sections->level):(0); limit = conf.contents_depth[(level>5)?(5):(level)]; start_level = (file->is_leaf) ? (level-1) : (level); last_level = start_level; count += xhtml_do_contents_section_limit(fp, file->sections, limit); count += xhtml_do_contents_limit(fp, file->child, limit); if (fp!=NULL) { while (last_level > start_level) { last_level--; fprintf(fp, "\n"); } } return count; } /* As above, but doesn't do anything in the current file */ static int xhtml_do_naked_contents(FILE *fp, xhtmlfile *file) { int level, limit, start_level, count = 0; if (!file) return 0; level = (file->sections)?(file->sections->level):(0); limit = conf.contents_depth[(level>5)?(5):(level)]; start_level = (file->is_leaf) ? (level-1) : (level); last_level = start_level; count = xhtml_do_contents_limit(fp, file->child, limit); if (fp!=NULL) { while (last_level > start_level) { last_level--; fprintf(fp, "\n"); } } return count; } /* * Write contents for the given file, children, and siblings, down to * given limit contents depth. */ static int xhtml_do_contents_limit(FILE *fp, xhtmlfile *file, int limit) { int count = 0; while (file) { count += xhtml_do_contents_section_limit(fp, file->sections, limit); count += xhtml_do_contents_limit(fp, file->child, limit); file = file->next; } return count; } /* * Write contents entries for the given section tree, down to the * limit contents depth. */ static int xhtml_do_contents_section_deep_limit(FILE *fp, xhtmlsection *section, int limit) { int count = 0; while (section) { if (!xhtml_add_contents_entry(fp, section, limit)) return 0; else count++; count += xhtml_do_contents_section_deep_limit(fp, section->child, limit); section = section->next; } return count; } /* * Write contents entries for the given section tree, down to the * limit contents depth. */ static int xhtml_do_contents_section_limit(FILE *fp, xhtmlsection *section, int limit) { int count = 0; if (!section) return 0; xhtml_add_contents_entry(fp, section, limit); count=1; count += xhtml_do_contents_section_deep_limit(fp, section->child, limit); /* section=section->child; while (section && xhtml_add_contents_entry(fp, section, limit)) { section = section->next; }*/ return count; } /* * Add a section entry, unless we're exceeding the limit, in which * case return FALSE (otherwise return TRUE). */ static int xhtml_add_contents_entry(FILE *fp, xhtmlsection *section, int limit) { if (!section || section->level > limit) return FALSE; if (fp==NULL || section->level < 0) return TRUE; if (last_level > section->level) { while (last_level > section->level) { last_level--; fprintf(fp, "\n"); } fprintf(fp, "\n"); } else if (last_level < section->level) { assert(last_level == section->level - 1); last_level++; fprintf(fp, "
    \n"); } else { fprintf(fp, "\n"); } fprintf(fp, "
  • ", section->file->filename, section->fragment); if (section->para->kwtext) { xhtml_para(fp, section->para->kwtext, FALSE); if (section->para->words) { fprintf(fp, ": "); } } if (section->para->words) { xhtml_para(fp, section->para->words, FALSE); } fprintf(fp, "\n"); return TRUE; } /* * Write all the sections in this file. Do all paragraphs in this section, then all * children (recursively), then go on to the next one (tail recursively). */ static void xhtml_do_sections(FILE *fp, xhtmlsection *sections) { while (sections) { currentsection = sections; xhtml_do_paras(fp, sections->para, NULL, TRUE); xhtml_do_sections(fp, sections->child); sections = sections->next; } } /* Write this list of paragraphs. Close off all lists at the end. */ static void xhtml_do_paras(FILE *fp, paragraph *p, paragraph *end, int indexable) { int last_type = -1, ptype, first=TRUE; stack lcont_stack = stk_new(); if (!p) return; /* for (; p && (xhtml_para_level(p)>limit || xhtml_para_level(p)==-1 || first); p=p->next) {*/ for (; p && p != end && (xhtml_para_level(p)==-1 || first); p=p->next) { first=FALSE; switch (ptype = p->type) { /* * Things we ignore because we've already processed them or * aren't going to touch them in this pass. */ case para_IM: case para_BR: case para_Biblio: /* only touch BiblioCited */ case para_VersionID: case para_NoCite: case para_Title: break; /* * Chapter titles. */ case para_Chapter: case para_Appendix: case para_UnnumberedChapter: xhtml_heading(fp, p, indexable); break; case para_Heading: case para_Subsect: xhtml_heading(fp, p, indexable); break; case para_Rule: fprintf(fp, "\n
    \n"); break; case para_Normal: case para_Copyright: fprintf(fp, "\n

    "); xhtml_para(fp, p->words, indexable); fprintf(fp, "

    \n"); break; case para_LcontPush: { int *p; p = mknew(int); *p = last_type; stk_push(lcont_stack, p); last_type = para_Normal; } break; case para_LcontPop: { int *p = stk_pop(lcont_stack); assert(p); ptype = last_type = *p; sfree(p); goto closeofflist; /* ick */ } break; case para_QuotePush: fprintf(fp, "
    \n"); break; case para_QuotePop: fprintf(fp, "
    \n"); break; case para_Bullet: case para_NumberedList: case para_Description: case para_DescribedThing: case para_BiblioCited: if (last_type!=p->type && !(last_type==para_DescribedThing && p->type==para_Description) && !(last_type==para_Description && p->type==para_DescribedThing)) { /* start up list if necessary */ if (p->type == para_Bullet) { fprintf(fp, "
      \n"); } else if (p->type == para_NumberedList) { fprintf(fp, "
        \n"); } else if (p->type == para_BiblioCited || p->type == para_DescribedThing || p->type == para_Description) { fprintf(fp, "
        \n"); } } if (p->type == para_Bullet || p->type == para_NumberedList) { fprintf(fp, "
      1. "); } else if (p->type == para_DescribedThing) { fprintf(fp, "
        "); } else if (p->type == para_Description) { fprintf(fp, "
        "); } else if (p->type == para_BiblioCited) { fprintf(fp, "
        "); xhtml_para(fp, p->kwtext, indexable); fprintf(fp, "
        \n
        "); } xhtml_para(fp, p->words, indexable); { paragraph *p2 = p->next; if (p2 && xhtml_para_level(p2)==-1 && p2->type == para_LcontPush) break; } closeofflist: if (ptype == para_BiblioCited) { fprintf(fp, "
        \n"); } else if (ptype == para_DescribedThing) { fprintf(fp, ""); } else if (ptype == para_Description) { fprintf(fp, ""); } else if (ptype == para_Bullet || ptype == para_NumberedList) { fprintf(fp, "
      2. "); } if (ptype == para_Bullet || ptype == para_NumberedList || ptype == para_BiblioCited || ptype == para_Description || ptype == para_DescribedThing) /* close off list if necessary */ { paragraph *p2 = p->next; int close_off=FALSE; /* if (p2 && (xhtml_para_level(p2)>limit || xhtml_para_level(p2)==-1)) {*/ if (p2 && xhtml_para_level(p2)==-1) { if (p2->type != ptype && !(p2->type==para_DescribedThing && ptype==para_Description) && !(p2->type==para_Description && ptype==para_DescribedThing) && p2->type != para_LcontPush) close_off=TRUE; } else { close_off=TRUE; } if (close_off) { if (ptype == para_Bullet) { fprintf(fp, "
    \n"); } else if (ptype == para_NumberedList) { fprintf(fp, "\n"); } else if (ptype == para_BiblioCited || ptype == para_Description || ptype == para_DescribedThing) { fprintf(fp, "\n"); } } } break; case para_Code: xhtml_codepara(fp, p->words); break; } last_type = ptype; } stk_free(lcont_stack); } /* * Output a header for this XHTML file. */ static void xhtml_doheader(FILE *fp, word *title) { fprintf(fp, "\n"); fprintf(fp, "\n\n\n"); if (title==NULL) fprintf(fp, "The thing with no name!"); else xhtml_para(fp, title, FALSE); fprintf(fp, "\n"); fprintf(fp, "\n", version); if (conf.author) fprintf(fp, "\n", conf.author); if (conf.description) fprintf(fp, "\n", conf.description); if (conf.head_end) fprintf(fp, "%ls\n", conf.head_end); fprintf(fp, "\n\n"); if (conf.body) fprintf(fp, "%ls\n", conf.body); else fprintf(fp, "\n"); if (conf.body_start) fprintf(fp, "%ls\n", conf.body_start); } /* * Output a footer for this XHTML file. */ static void xhtml_dofooter(FILE *fp) { fprintf(fp, "\n
    \n\n"); if (conf.body_end) fprintf(fp, "%ls\n", conf.body_end); if (!conf.suppress_address) { fprintf(fp,"
    \n"); if (conf.address_start) fprintf(fp, "%ls\n", conf.address_start); /* Do the version ID */ if (conf.include_version_id) { paragraph *p; int started = 0; for (p = sourceparas; p; p = p->next) if (p->type == para_VersionID) { xhtml_versionid(fp, p->words, started); started = 1; } } if (conf.address_end) fprintf(fp, "%ls\n", conf.address_end); fprintf(fp, "
    \n"); } fprintf(fp, "\n\n\n"); } /* * Output the versionid paragraph. Typically this is a version control * ID string (such as $Id...$ in RCS). */ static void xhtml_versionid(FILE *fp, word *text, int started) { rdstringc t = { 0, 0, NULL }; rdaddc(&t, '['); /* FIXME: configurability */ xhtml_rdaddwc(&t, text, NULL, FALSE); rdaddc(&t, ']'); /* FIXME: configurability */ if (started) fprintf(fp, "
    \n"); fprintf(fp, "%s\n", t.text); sfree(t.text); } /* Is this an XHTML reserved character? */ static int xhtml_reservedchar(int c) { if (c=='&' || c=='<' || c=='>' || c=='"') return TRUE; else return FALSE; } /* * Convert a wide string into valid XHTML: Anything outside ASCII will * be fixed up as an entity. Currently we don't worry about constraining the * encoded character set, which we should probably do at some point (we can * still fix up and return FALSE - see the last comment here). We also don't * currently * * Because this is only used for words, spaces are HARD spaces (any other * spaces will be word_Whitespace not word_Normal). So they become   * Unless hard_spaces is FALSE, of course (code paragraphs break the above * rule). * * If `result' is non-NULL, mallocs the resulting string and stores a pointer to * it in `*result'. If `result' is NULL, merely checks whether all * characters in the string are feasible. * * Return is nonzero if all characters are OK. If not all * characters are OK but `result' is non-NULL, a result _will_ * still be generated! */ static int xhtml_convert(wchar_t *s, int maxlen, char **result, int hard_spaces) { int doing = (result != 0); int ok = TRUE; char *p = NULL; int plen = 0, psize = 0; if (maxlen <= 0) maxlen = -1; for (; *s && maxlen != 0; s++, maxlen--) { wchar_t c = *s; #define ensure_size(i) if (i>=psize) { psize = i+256; p = resize(p, psize); } if (((c == 32 && !hard_spaces) || (c > 32 && c <= 126 && !xhtml_reservedchar(c)))) { /* Char is OK. */ if (doing) { ensure_size(plen); p[plen++] = (char)c; } } else { /* Char needs fixing up. */ /* ok = FALSE; -- currently we never return FALSE; we * might want to when considering a character set for the * encoded document. */ if (doing) { if (c==32) { /* a space in a word is a hard space */ ensure_size(plen+6); /* includes space for the NUL, which is subsequently stomped on */ sprintf(p+plen, " "); plen+=6; } else { /* FIXME: entity names! */ ensure_size(plen+8); /* includes space for the NUL, which is subsequently stomped on */ plen+=sprintf(p+plen, "&#%04i;", (int)c); } } } } if (doing) { p = resize(p, plen+1); p[plen] = '\0'; *result = p; } return ok; } /* * This formats the given words as XHTML. * * `indexable', if FALSE, prohibits adding any index references. * You might use this, for example, if an index reference occurred * in a section title, to prevent phony index references when the * section title is processed in strange places such as contents * sections. */ static void xhtml_rdaddwc(rdstringc *rs, word *text, word *end, int indexable) { char *c; keyword *kwl; xhtmlsection *sect; indextag *itag; int ti; for (; text && text != end; text = text->next) { switch (text->type) { case word_HyperLink: xhtml_utostr(text->text, &c); rdaddsc(rs, ""); sfree(c); break; case word_UpperXref: case word_LowerXref: kwl = kw_lookup(keywords, text->text); if (kwl) { sect=xhtml_find_section(kwl->para); if (sect) { rdaddsc(rs, "file->filename); rdaddc(rs, '#'); rdaddsc(rs, sect->fragment); rdaddsc(rs, "\">"); } else { rdaddsc(rs, ""); error(err_whatever, "Couldn't locate cross-reference! (Probably a bibliography entry.)"); } } else { rdaddsc(rs, ""); error(err_whatever, "Couldn't locate cross-reference! (Wasn't in source file.)"); } break; case word_IndexRef: /* in theory we could make an index target here */ /* rdaddsc(rs, "text, &c); rdaddsc(rs, c); sfree(c); rdaddsc(rs, "\">");*/ /* what we _do_ need to do is to fix up the backend data * for any indexentry this points to. */ if (!indexable) break; for (ti=0; (itag = (indextag *)index234(idx->tags, ti))!=NULL; ti++) { /* FIXME: really ustricmp() and not ustrcmp()? */ if (ustricmp(itag->name, text->text)==0) { break; } } if (itag!=NULL) { if (itag->refs!=NULL) { int i; for (i=0; inrefs; i++) { xhtmlindex *idx_ref; indexentry *ientry; ientry = itag->refs[i]; if (ientry->backend_data==NULL) { idx_ref = (xhtmlindex*) smalloc(sizeof(xhtmlindex)); if (idx_ref==NULL) fatal(err_nomemory); idx_ref->nsection = 0; idx_ref->size = 4; idx_ref->sections = (xhtmlsection**) smalloc(idx_ref->size * sizeof(xhtmlsection*)); if (idx_ref->sections==NULL) fatal(err_nomemory); ientry->backend_data = idx_ref; } else { idx_ref = ientry->backend_data; if (idx_ref->nsection+1 > idx_ref->size) { int new_size = idx_ref->size * 2; idx_ref->sections = srealloc(idx_ref->sections, new_size * sizeof(xhtmlsection)); if (idx_ref->sections==NULL) { fatal(err_nomemory); } idx_ref->size = new_size; } } idx_ref->sections[idx_ref->nsection++] = currentsection; #if 0 #endif } } else { fatal(err_whatever, "Index tag had no entries!"); } } else { fprintf(stderr, "Looking for index entry '%ls'\n", text->text); fatal(err_whatever, "Couldn't locate index entry! (Wasn't in index.)"); } break; case word_HyperEnd: case word_XrefEnd: rdaddsc(rs, ""); break; case word_Normal: case word_Emph: case word_Code: case word_WeakCode: case word_WhiteSpace: case word_EmphSpace: case word_CodeSpace: case word_WkCodeSpace: case word_Quote: case word_EmphQuote: case word_CodeQuote: case word_WkCodeQuote: assert(text->type != word_CodeQuote && text->type != word_WkCodeQuote); if (towordstyle(text->type) == word_Emph && (attraux(text->aux) == attr_First || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); else if ((towordstyle(text->type) == word_Code || towordstyle(text->type) == word_WeakCode) && (attraux(text->aux) == attr_First || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); if (removeattr(text->type) == word_Normal) { if (xhtml_convert(text->text, 0, &c, TRUE)) /* spaces in the word are hard */ rdaddsc(rs, c); else xhtml_rdaddwc(rs, text->alt, NULL, indexable); sfree(c); } else if (removeattr(text->type) == word_WhiteSpace) { rdaddc(rs, ' '); } else if (removeattr(text->type) == word_Quote) { rdaddsc(rs, """); } if (towordstyle(text->type) == word_Emph && (attraux(text->aux) == attr_Last || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); else if ((towordstyle(text->type) == word_Code || towordstyle(text->type) == word_WeakCode) && (attraux(text->aux) == attr_Last || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); break; } } } /* Output a heading, formatted as XHTML. */ static void xhtml_heading(FILE *fp, paragraph *p, int indexable) { rdstringc t = { 0, 0, NULL }; word *tprefix = p->kwtext; word *nprefix = p->kwtext2; word *text = p->words; int level = xhtml_para_level(p); xhtmlsection *sect = xhtml_find_section(p); xhtmlheadfmt *fmt; char *fragment; if (sect) { fragment = sect->fragment; } else { if (p->type == para_Title) fragment = "title"; else { fragment = ""; /* FIXME: what else can we do? */ error(err_whatever, "Couldn't locate heading cross-reference!"); } } if (p->type == para_Title) fmt = NULL; else if (level == 1) fmt = &conf.fchapter; else if (level-1 < conf.nfsect) fmt = &conf.fsect[level-1]; else fmt = &conf.fsect[conf.nfsect-1]; if (fmt && fmt->just_numbers && nprefix) { xhtml_rdaddwc(&t, nprefix, NULL, indexable); if (fmt) { char *c; if (xhtml_convert(fmt->number_suffix, 0, &c, FALSE)) { rdaddsc(&t, c); sfree(c); } } } else if (fmt && !fmt->just_numbers && tprefix) { xhtml_rdaddwc(&t, tprefix, NULL, indexable); if (fmt) { char *c; if (xhtml_convert(fmt->number_suffix, 0, &c, FALSE)) { rdaddsc(&t, c); sfree(c); } } } xhtml_rdaddwc(&t, text, NULL, indexable); /* * If we're outputting in single-file mode, we need to lower * the level of each heading by one, because the overall * document title will be sitting right at the top as an

    * and so chapters and sections should start at

    . * * Even if not, the document title will come back from * xhtml_para_level() as level zero, so we must increment that * no matter what leaf_level is set to. */ if (conf.leaf_level == 0 || level == 0) level++; fprintf(fp, "%s\n", fragment, level, t.text, level); sfree(t.text); } /* Output a paragraph. Styles are handled by xhtml_rdaddwc(). * This looks pretty simple; I may have missed something ... */ static void xhtml_para(FILE *fp, word *text, int indexable) { rdstringc out = { 0, 0, NULL }; xhtml_rdaddwc(&out, text, NULL, indexable); fprintf(fp, "%s", out.text); sfree(out.text); } /* Output a code paragraph. I'm treating this as preformatted, which * may not be entirely correct. See xhtml_para() for my worries about * this being overly-simple; however I think that most of the complexity * of the text backend came entirely out of word wrapping anyway. */ static void xhtml_codepara(FILE *fp, word *text) { fprintf(fp, "
    ");
        for (; text; text = text->next) if (text->type == word_WeakCode) {
    	word *here, *next;
    	char *c;
    
    	/*
    	 * See if this WeakCode is followed by an Emph to indicate
    	 * emphasis.
    	 */
    	here = text;
    	if (text->next && text->next->type == word_Emph) {
    	    next = text = text->next;
    	} else
    	    next = NULL;
    
    	if (next) {
    	    wchar_t *t, *e;
    	    int n;
    
    	    t = here->text;
    	    e = next->text;
    
    	    while (*e) {
    		int ec = *e;
    
    		for (n = 0; t[n] && e[n] && e[n] == ec; n++);
    		xhtml_convert(t, n, &c, FALSE);
    		fprintf(fp, "%s%s%s",
    			(ec == 'i' ? "" : ec == 'b' ? "" : ""),
    			c,
    			(ec == 'i' ? "" : ec == 'b' ? "" : ""));
    		sfree(c);
    
    		t += n;
    		e += n;
    	    }
    
    	    xhtml_convert(t, 0, &c, FALSE);
    	    fprintf(fp, "%s\n", c);
    	    sfree(c);
    	} else {
    	    xhtml_convert(here->text, 0, &c, FALSE);
    	    fprintf(fp, "%s\n", c);
    	    sfree(c);
    	}
        }
      fprintf(fp, "
    \n"); } '#n1475'>1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470
    /***************************************************************************
    *             __________               __   ___.
    *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
    *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
    *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
    *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
    *                     \/            \/     \/    \/            \/
    * $Id$
    *
    * Copyright (C) 2005 Adam Boot
    *
    * Color graphics from Frozen Bubble (http://www.frozen-bubble.org/)
    *
    * 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.
    *
    ****************************************************************************/
    
    #include "plugin.h"
    
    #ifdef HAVE_LCD_BITMAP
    
    #include "lib/xlcd.h"
    #include "lib/pluginlib_actions.h"
    #include "lib/fixedpoint.h"
    #include "lib/playback_control.h"
    #include "lib/highscore.h"
    
    PLUGIN_HEADER
    
    /* files */
    #define SCORE_FILE PLUGIN_GAMES_DIR "/bubbles.score"
    #define SAVE_FILE  PLUGIN_GAMES_DIR "/bubbles.save"
    
    /* final game return status */
    #define BB_NONE 5
    #define BB_WIN  4
    #define BB_END  3
    #define BB_USB  2
    #define BB_QUIT 1
    #define BB_LOSE 0
    
    /* play board dimension */
    #define BB_HEIGHT 12
    #define BB_WIDTH  8
    #define BB_LEVEL_HEIGHT 10
    
    /* various amounts */
    #define NUM_SCORES   5
    #define NUM_LEVELS   100
    #define NUM_QUEUE    2
    #define NUM_BUBBLES  8
    #define MIN_ANGLE    -76
    #define MAX_ANGLE    76
    #define NUM_COMPRESS 9
    #define MAX_SHOTTIME 1000
    
    /* keyboard layouts */
    
    #if (CONFIG_KEYPAD != SANSA_E200_PAD) && \
          (CONFIG_KEYPAD != SANSA_FUZE_PAD)
    /* sansas use the wheel instead of left/right if available */
    #define BUBBLES_LEFT        PLA_LEFT
    #define BUBBLES_LEFT_REP    PLA_LEFT_REPEAT
    #define BUBBLES_RIGHT       PLA_RIGHT
    #define BUBBLES_RIGHT_REP   PLA_RIGHT_REPEAT
    #define ANGLE_STEP  4
    #define ANGLE_STEP_REP 4
    #else
    #define BUBBLES_LEFT        PLA_UP
    #define BUBBLES_LEFT_REP    PLA_UP_REPEAT
    #define BUBBLES_RIGHT       PLA_DOWN
    #define BUBBLES_RIGHT_REP   PLA_DOWN_REPEAT
    #define ANGLE_STEP  2
    #define ANGLE_STEP_REP 4
    #endif
    
    #define BUBBLES_QUIT        PLA_QUIT
    #define BUBBLES_START       PLA_START
    #define BUBBLES_SELECT      PLA_FIRE
    #define BUBBLES_RESUME      PLA_MENU
    
    #if CONFIG_KEYPAD != ONDIO_PAD
    
    #define BUBBLES_LVLINC      PLA_UP
    #define BUBBLES_LVLINC_REP  PLA_UP_REPEAT
    #define BUBBLES_LVLDEC      PLA_DOWN
    #define BUBBLES_LVLDEC_REP  PLA_DOWN_REPEAT
    
    #else /* ondio keys */
    
    #define BUBBLES_LVLINC      PLA_RIGHT
    #define BUBBLES_LVLINC_REP  PLA_RIGHT_REPEAT
    #define BUBBLES_LVLDEC      PLA_LEFT
    #define BUBBLES_LVLDEC_REP  PLA_LEFT_REPEAT
    
    #endif
    
    /* external bitmaps */
    #ifdef HAVE_LCD_COLOR 
    #include "pluginbitmaps/bubbles_background.h"
    #endif
    #include "pluginbitmaps/bubbles_bubble.h"
    #include "pluginbitmaps/bubbles_emblem.h"
    
    #define BUBBLE_WIDTH  BMPWIDTH_bubbles_bubble
    #define BUBBLE_HEIGHT BMPHEIGHT_bubbles_bubble
    #define EMBLEM_WIDTH  BMPWIDTH_bubbles_emblem
    #define EMBLEM_HEIGHT (BMPHEIGHT_bubbles_emblem/8)
    
    /* bubbles will consume height of ROW_HEIGHT*(BB_HEIGHT-1)+BUBBLE_HEIGHT*3/2 */
    /* 44x44 bubbles (m:robe 500) */
    #if (LCD_WIDTH == 640) && (LCD_HEIGHT == 480)
    #define XOFS          144
    #define MAX_FPS       40
    
    #elif (LCD_WIDTH == 480) && (LCD_HEIGHT == 640)
    #define XOFS          128
    #define MAX_FPS       40
    
    /* 22x22 bubbles (iPod Video) */
    #elif (LCD_HEIGHT == 240) && (LCD_WIDTH == 320)
    #define XOFS          72
    #define MAX_FPS       40
    
    /* 22x22 bubbles (Gigabeat, Onda VX747) */
    #elif ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400)) && (LCD_WIDTH == 240)
    #define XOFS          64
    #define MAX_FPS       30
    
    /* 16x16 bubbles (H300, iPod Color) */
    #elif (LCD_HEIGHT == 176) && (LCD_WIDTH == 220)
    #define XOFS          46
    #define MAX_FPS       30
    
    /* 16x16 bubbles (Sansa E200) */
    #elif (LCD_HEIGHT == 220) && (LCD_WIDTH == 176)
    #define XOFS          48
    #define MAX_FPS       30
    
    /* 12x12 bubbles (iPod Nano) */
    #elif (LCD_HEIGHT == 132) && (LCD_WIDTH == 176)
    #define XOFS          40
    #define MAX_FPS       40
    
    /* 12x12 bubbles (H100, H10, iAudio X5, iPod 3G, iPod 4G grayscale) */
    #elif (LCD_HEIGHT == 128) && ((LCD_WIDTH == 160) || (LCD_WIDTH == 128))
    #define XOFS          33
    #define MAX_FPS       30
    
    /* 10x10 bubbles (iPod Mini) */
    #elif (LCD_HEIGHT == 110) && (LCD_WIDTH == 138)
    #define XOFS          33
    #define MAX_FPS       30
    
    /* 9x9 bubbles (iAudio M3) */
    #elif (LCD_HEIGHT == 96) && (LCD_WIDTH == 128)
    #define XOFS          45
    #define MAX_FPS       30
    
    /* 8x8 bubbles (Sansa C200) */
    #elif ((LCD_HEIGHT == 80) && (LCD_WIDTH == 132))
    #define XOFS          45
    #define MAX_FPS       30
    
    /* 7x7 bubbles (Sansa Clip/m200) */
    #elif (LCD_HEIGHT == 64 && LCD_WIDTH == 128)
    #define XOFS          33
    #define ROW_HEIGHT     5
    #define MAX_FPS       30
    
    /* 8x7 bubbles (Archos recorder, Ondio) */
    #elif (LCD_HEIGHT == 64) && (LCD_WIDTH == 112)
    #define XOFS          33
    #define ROW_HEIGHT     5
    #define MAX_FPS       20
    
    #else
        #error BUBBLES: Unsupported LCD type
    #endif
    
    #if !defined(ROW_HEIGHT)
    #define ROW_HEIGHT    (BUBBLE_WIDTH-(BUBBLE_WIDTH-EMBLEM_WIDTH)/2)
    #endif
    
    #define ROW_INDENT    (BUBBLE_WIDTH/2)
    
    #define TEXT_LINES (LCD_HEIGHT/8)
    
    /* shot position */
    #define SHOTX XOFS+ROW_INDENT+BUBBLE_WIDTH*3
    #define SHOTY ROW_HEIGHT*(BB_HEIGHT-1)+BUBBLE_HEIGHT/2
    
    /* collision distance squared */
    #define MIN_DISTANCE ((BUBBLE_WIDTH*8)/10)*((BUBBLE_HEIGHT*8)/10)
    
    /* levels */
    char level[NUM_LEVELS][BB_LEVEL_HEIGHT][BB_WIDTH] = {
        {{ 6,  6,  4,  4,  2,  2,  3,  3},
         { 6,  6,  4,  4,  2,  2,  3, -1},
         { 2,  2,  3,  3,  6,  6,  4,  4},
         { 2,  3,  3,  6,  6,  4,  4, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  7,  7,  7,  7,  7,  7, -1},
         {-1,  1,  1,  1,  1,  1, -1, -1},
         {-1, -1,  2,  2,  2,  2, -1, -1},
         {-1, -1, -1,  2, -1, -1, -1, -1},
         {-1, -1, -1,  2,  2, -1, -1, -1},
         {-1, -1, -1,  5, -1, -1, -1, -1},
         {-1, -1, -1,  5,  5, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  7, -1, -1,  7, -1, -1},
         {-1, -1,  7,  1,  7, -1, -1, -1},
         {-1, -1, -1,  1,  2, -1, -1, -1},
         {-1, -1,  1,  2,  1, -1, -1, -1},
         {-1, -1, -1,  2,  5, -1, -1, -1},
         {-1, -1,  3,  5,  3, -1, -1, -1},
         {-1, -1, -1,  5,  3, -1, -1, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  0,  0, -1, -1, -1},
         {-1, -1,  5,  0,  1, -1, -1, -1},
         {-1, -1,  3,  5,  1,  6, -1, -1},
         {-1,  4,  3, -1,  6,  7, -1, -1},
         {-1,  7,  4, -1, -1,  7,  4, -1},
         { 6,  7, -1, -1, -1,  4,  3, -1},
         { 1,  6, -1, -1, -1, -1,  3,  5},
         { 1, -1, -1, -1, -1, -1,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  0,  0,  0,  0, -1, -1},
         {-1,  0,  1,  1,  1,  0, -1, -1},
         {-1,  0,  1,  0,  0,  1,  0, -1},
         {-1,  0,  1,  1,  1,  0, -1, -1},
         {-1, -1,  0,  0,  0,  0, -1, -1},
         {-1, -1,  7, -1,  7, -1, -1, -1},
         {-1, -1,  7,  7,  7,  7, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  4,  4,  4,  6,  6,  6, -1},
         { 4, -1, -1, -1, -1, -1,  6, -1},
         {-1,  4, -1, -1, -1, -1,  6, -1},
         { 4,  2,  3,  1,  2,  3,  6, -1},
         {-1,  3,  1,  2,  3,  1,  2, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  4,  4,  4,  6,  6,  6, -1},
         { 4, -1, -1, -1, -1, -1,  6, -1},
         {-1,  4, -1, -1, -1, -1,  6, -1},
         { 4,  2,  3,  1,  2,  3,  6, -1},
         {-1,  3,  1,  2,  3,  1,  2, -1},
         {-1,  2,  3,  1,  2,  3, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  0,  0, -1, -1,  2,  2, -1},
         {-1,  5, -1, -1, -1,  3, -1, -1},
         {-1,  0, -1, -1, -1,  6, -1, -1},
         {-1,  3, -1, -1, -1,  0, -1, -1},
         {-1,  4, -1, -1, -1,  5, -1, -1},
         {-1,  2, -1, -1, -1,  3, -1, -1},
         {-1,  2, -1, -1, -1,  1, -1, -1},
         {-1,  3, -1, -1, -1,  4, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 3, -1, -1, -1, -1, -1, -1,  3},
         { 6,  3,  2,  4,  6,  3,  2, -1},
         { 4, -1, -1, -1, -1, -1, -1,  4},
         { 2,  4,  6,  3,  2,  4,  6, -1},
         {-1, -1, -1,  6, -1, -1, -1, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  2, -1,  1, -1,  1, -1,  2},
         { 1,  2, -1,  2,  1, -1,  1, -1},
         { 1, -1,  1, -1,  2, -1,  2, -1},
         { 2,  1, -1,  1,  2, -1,  2, -1},
         {-1,  2, -1,  2, -1,  2, -1,  2},
         { 1,  2, -1,  2,  1, -1,  1, -1},
         { 1, -1,  1, -1,  2, -1,  1, -1},
         { 2,  2, -1,  1,  1, -1,  2, -1},
         {-1,  2, -1,  1, -1,  1, -1,  1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  7,  7, -1, -1,  5,  5, -1},
         { 1, -1, -1, -1, -1, -1,  4, -1},
         { 2,  1, -1, -1, -1, -1,  4,  3},
         { 2, -1, -1, -1, -1, -1,  3, -1},
         { 1,  2, -1, -1, -1, -1,  3,  4},
         { 1, -1, -1, -1, -1, -1,  4, -1},
         { 7,  1, -1, -1, -1, -1,  4,  5},
         { 7,  7, -1, -1, -1,  5,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 7,  7, -1, -1, -1, -1,  5,  5},
         { 1,  5, -1, -1, -1,  7,  4, -1},
         { 2,  1, -1, -1, -1, -1,  4,  3},
         { 2, -1, -1, -1, -1, -1,  3, -1},
         { 1,  5, -1, -1, -1, -1,  7,  4},
         { 1, -1, -1, -1, -1, -1,  4, -1},
         { 7,  1, -1, -1, -1, -1,  4,  5},
         { 7,  5, -1, -1, -1,  7,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  0,  0, -1, -1, -1},
         {-1, -1,  5,  0,  1, -1, -1, -1},
         {-1, -1,  3,  5,  1,  6, -1, -1},
         {-1,  4,  3,  2,  6,  2, -1, -1},
         {-1,  7,  4,  7,  2,  2,  4, -1},
         { 6,  7,  7,  3,  3,  4,  3, -1},
         { 1,  6,  1,  1,  1,  3,  3,  5},
         { 1,  1, -1, -1, -1, -1,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  0, -1, -1,  0, -1, -1},
         {-1,  3,  3, -1,  3,  3, -1, -1},
         {-1,  0,  2,  0,  0,  2,  0, -1},
         {-1,  3,  3, -1,  3,  3, -1, -1},
         {-1, -1,  0, -1, -1,  0, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  1,  1, -1, -1, -1},
         {-1, -1,  2,  2,  2, -1, -1, -1},
         {-1, -1,  3,  3,  3,  3, -1, -1},
         {-1,  4,  4,  4,  4,  4, -1, -1},
         {-1,  5,  5,  5,  5,  5,  5, -1},
         {-1, -1, -1,  6, -1, -1, -1, -1},
         {-1, -1, -1,  7,  7, -1, -1, -1},
         {-1, -1, -1,  0, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  2,  5, -1, -1, -1},
         {-1,  4,  3, -1, -1, -1, -1, -1},
         { 6,  7, -1,  5,  2, -1, -1, -1},
         {-1, -1, -1, -1,  3,  4, -1, -1},
         {-1, -1, -1,  2,  5, -1,  7,  6},
         {-1,  4,  3, -1, -1, -1, -1, -1},
         { 6,  7, -1,  5,  2, -1, -1, -1},
         {-1, -1, -1, -1,  3,  4, -1, -1},
         {-1, -1, -1, -1, -1, -1,  7,  6},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  5,  5, -1, -1, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1,  1, -1, -1, -1, -1},
         {-1, -1, -1,  7, -1, -1, -1, -1},
         {-1, -1, -1,  2, -1, -1, -1, -1},
         {-1, -1, -1,  4, -1, -1, -1, -1},
         {-1, -1, -1,  5, -1, -1, -1, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  0,  1, -1, -1, -1},
         {-1, -1,  0,  2,  7,  7, -1, -1},
         {-1, -1, -1,  0,  1,  7, -1, -1},
         {-1,  0,  0,  0,  0, -1, -1, -1},
         {-1,  0,  0,  0,  1,  1, -1, -1},
         { 0,  0,  0,  1,  1,  1, -1, -1},
         {-1,  0,  0,  1,  1,  1, -1, -1},
         {-1,  0,  0,  0,  7,  7, -1, -1},
         {-1, -1,  7,  7, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  1, -1, -1, -1, -1, -1, -1},
         { 1, -1, -1, -1, -1, -1, -1, -1},
         {-1,  2,  3,  4,  7,  6,  5, -1},
         {-1, -1, -1, -1, -1, -1,  1, -1},
         {-1, -1, -1, -1, -1, -1,  1, -1},
         {-1,  2,  3,  4,  7,  6, -1, -1},
         {-1,  1, -1, -1, -1, -1, -1, -1},
         { 1, -1, -1, -1, -1, -1, -1, -1},
         {-1,  2,  3,  4,  7,  6,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  6, -1, -1, -1, -1, -1, -1},
         { 5, -1, -1, -1, -1, -1, -1, -1},
         { 2,  3,  4,  7,  6,  5,  2,  3},
         {-1, -1, -1, -1, -1, -1,  4, -1},
         {-1, -1, -1, -1, -1, -1,  7, -1},
         {-1,  4,  3,  2,  5,  6, -1, -1},
         {-1,  7, -1, -1, -1, -1, -1, -1},
         { 6, -1, -1, -1, -1, -1, -1, -1},
         { 5,  2,  3,  4,  7,  6,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 3,  2,  1,  0,  0,  1,  2,  3},
         { 3,  2,  1,  0,  1,  2,  3, -1},
         { 4,  3,  2,  1,  1,  2,  3,  4},
         { 4,  3,  2,  1,  2,  3,  4, -1},
         { 5,  4,  3,  2,  2,  3,  4,  5},
         { 5,  4,  3,  2,  3,  4,  5, -1},
         { 6,  5,  4,  3,  3,  4,  5,  6},
         { 6,  5,  4,  3,  4,  5,  6, -1},
         { 7,  6,  5,  4,  4,  5,  6,  7},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  5,  5, -1, -1, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1,  2,  4, -1, -1, -1},
         {-1, -1, -1,  6, -1, -1, -1, -1},
         {-1, -1, -1,  2,  4, -1, -1, -1},
         {-1,  2, -1,  5, -1,  4, -1, -1},
         { 1,  0,  1,  0,  1,  0,  1,  0},
         { 3, -1,  3, -1,  2, -1,  6, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1,  1, -1, -1, -1},
         { 7,  4,  3,  5, -1, -1, -1, -1},
         { 6, -1, -1,  1, -1, -1, -1, -1},
         {-1, -1, -1,  5,  3,  4,  7, -1},
         { 6, -1, -1, -1,  1, -1, -1,  6},
         { 7,  4,  3,  5, -1, -1, -1, -1},
         {-1, -1, -1,  1, -1, -1, -1,  6},
         {-1, -1, -1,  5,  3,  4,  7, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1,  7,  3,  6, -1},
         {-1, -1,  3,  7,  3,  6,  3, -1},
         {-1, -1,  5,  7,  3,  6,  3, -1},
         {-1,  6,  7,  3,  6,  7, -1, -1},
         {-1,  7,  7,  3,  6,  1, -1, -1},
         { 3,  7,  3,  6,  3, -1, -1, -1},
         { 5,  6,  2,  7,  1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 5, -1, -1, -1, -1, -1, -1,  5},
         { 5, -1,  6,  6,  6, -1,  5, -1},
         {-1,  5,  4, -1, -1,  4,  5, -1},
         {-1,  3, -1, -1, -1,  3, -1, -1},
         {-1,  6,  0, -1, -1,  0,  6, -1},
         {-1,  3, -1, -1, -1,  3, -1, -1},
         {-1, -1,  4, -1, -1,  4, -1, -1},
         {-1, -1,  6,  6,  6, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  7,  0, -1, -1,  0,  7, -1},
         { 7, -1,  0, -1,  0, -1,  7, -1},
         { 7,  1, -1,  0,  0, -1,  1,  7},
         { 7,  1,  2,  0,  2,  1,  7, -1},
         { 7,  6,  3,  2,  2,  3,  6,  7},
         { 7, -1,  3,  2,  3, -1,  7, -1},
         {-1,  7,  7,  3,  3,  7,  7, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  3, -1,  1, -1,  7, -1,  6},
         { 5, -1,  7, -1,  7, -1,  6, -1},
         { 6, -1,  0, -1,  5, -1,  3, -1},
         {-1,  2, -1,  1, -1,  5, -1, -1},
         {-1,  4, -1,  3, -1,  4, -1, -1},
         { 2, -1,  3, -1,  2, -1, -1, -1},
         {-1, -1,  4, -1,  6, -1, -1, -1},
         {-1, -1, -1,  5, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1,  1, -1, -1, -1},
         {-1, -1, -1, -1,  3, -1, -1, -1},
         { 6,  1,  3,  1,  2,  1,  4,  1},
         {-1, -1, -1, -1,  6, -1, -1, -1},
         {-1, -1, -1,  4,  1, -1, -1, -1},
         {-1, -1,  1, -1,  3, -1, -1, -1},
         {-1, -1, -1,  2,  1, -1, -1, -1},
         {-1, -1, -1, -1,  4, -1, -1, -1},
         {-1, -1, -1,  6,  1, -1, -1, -1},
         {-1, -1, -1,  6, -1, -1, -1, -1}},
        {{-1, -1, -1,  5,  4, -1, -1, -1},
         {-1, -1,  4,  1,  0, -1, -1, -1},
         {-1, -1, -1,  2,  3, -1, -1, -1},
         {-1,  1,  4, -1,  2,  2, -1, -1},
         {-1,  3,  1,  2,  5,  1,  4, -1},
         {-1,  4,  2, -1,  0,  4, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1,  1, -1, -1, -1},
         {-1, -1, -1,  1, -1, -1, -1, -1},
         {-1,  2, -1, -1,  1, -1,  5, -1},
         { 5, -1, -1,  1, -1, -1,  0, -1},
         {-1,  6, -1, -1,  1, -1,  4, -1},
         {-1,  0, -1,  1, -1,  5, -1, -1},
         {-1, -1,  5,  5,  0,  1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  6,  3, -1, -1, -1},
         {-1, -1,  3,  2,  6, -1, -1, -1},
         {-1, -1,  2,  6,  3,  2, -1, -1},
         {-1,  6,  3,  2,  6,  3, -1, -1},
         {-1,  3,  2,  6,  3,  2,  6, -1},
         { 2,  6,  3,  2,  6,  3,  2, -1},
         { 6,  3,  2,  6,  3,  2,  6,  3},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6,  6,  6,  6,  6,  6,  6,  6},
         { 4, -1, -1, -1, -1, -1, -1, -1},
         {-1,  3,  2,  5,  7,  6,  4,  3},
         {-1,  5, -1, -1, -1, -1, -1, -1},
         {-1, -1,  7,  6,  4,  3,  2,  5},
         {-1, -1,  4, -1, -1, -1, -1, -1},
         {-1, -1, -1,  3,  2,  5,  7,  6},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1, -1,  7, -1, -1,  6, -1,  2},
         { 6, -1,  1, -1,  6,  1,  3, -1},
         {-1,  4, -1,  7,  2, -1,  7, -1},
         { 2,  7, -1, -1, -1,  4, -1, -1},
         { 6, -1,  3,  5,  0,  2, -1,  7},
         { 1, -1, -1, -1, -1, -1,  1, -1},
         {-1,  1,  4,  5,  7,  5,  1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6,  6,  6, -1, -1,  6,  6,  6},
         {-1, -1,  6, -1,  6, -1, -1, -1},
         {-1, -1,  2,  3,  3,  2, -1, -1},
         {-1,  3, -1,  5, -1,  3, -1, -1},
         {-1, -1,  5,  3,  3,  5, -1, -1},
         {-1, -1,  6,  1,  6, -1, -1, -1},
         {-1,  4,  2, -1, -1,  2,  4, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  5,  5, -1, -1, -1},
         {-1, -1,  5, -1, -1, -1, -1, -1},
         {-1,  3,  4,  6,  6, -1, -1,  5},
         { 3,  3,  4,  6,  5, -1,  5, -1},
         { 3,  2,  3,  6,  6,  5,  5, -1},
         { 3,  3,  4,  6,  5, -1,  5, -1},
         {-1,  3,  4,  6,  6, -1, -1,  5},
         {-1, -1,  5, -1, -1, -1, -1, -1},
         {-1, -1, -1,  5,  5, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1, -1, -1, -1, -1, -1, -1,  1},
         { 1, -1,  2,  2,  2, -1,  1, -1},
         {-1,  1,  2,  3,  3,  2,  1, -1},
         { 6,  2,  3, -1,  3,  2,  6, -1},
         { 6,  2,  3, -1, -1,  3,  2,  6},
         { 6,  2,  3, -1,  3,  2,  6, -1},
         { 3,  3,  3,  7,  7,  3,  3,  3},
         { 0,  5,  0,  2,  0,  5,  0, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  7,  7,  7, -1, -1, -1},
         {-1,  7,  2,  2,  7, -1, -1, -1},
         {-1,  7,  5,  5,  5,  7, -1, -1},
         { 7,  7,  7,  7,  7,  7, -1, -1},
         {-1, -1,  6, -1,  6, -1, -1, -1},
         {-1,  6, -1, -1,  6, -1, -1, -1},
         {-1,  6,  4,  4, -1,  6,  4,  4},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  3,  3, -1,  3,  3,  3, -1},
         { 3,  7,  5,  4,  6,  5,  3, -1},
         { 1,  3,  3,  3, -1,  3,  3,  1},
         { 2,  1,  2,  1,  2,  1,  2, -1},
         { 1,  3,  3, -1,  3,  3,  3,  1},
         { 3,  5,  6,  4,  5,  7,  3, -1},
         { 2,  3,  3,  3, -1,  3,  3,  2},
         { 1,  1,  2,  2,  2,  1,  1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  6,  5, -1, -1, -1, -1, -1},
         { 3,  1,  3, -1, -1, -1, -1, -1},
         {-1,  5,  6, -1, -1, -1, -1, -1},
         {-1, -1,  5,  3, -1, -1, -1, -1},
         {-1, -1,  6,  1,  6, -1, -1, -1},
         {-1, -1,  3,  5, -1, -1, -1, -1},
         {-1, -1, -1, -1,  3,  6, -1, -1},
         {-1, -1, -1,  5,  6,  5, -1, -1},
         {-1, -1, -1, -1,  6,  3, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6,  3,  7,  4,  5,  1,  6,  3},
         { 5,  1,  6,  3,  7,  4,  5, -1},
         { 6,  3,  7,  4,  5,  1,  6,  3},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1, -1, -1,  4,  4},
         {-1, -1,  7,  7,  7,  4,  4, -1},
         {-1, -1, -1, -1, -1, -1,  4,  4},
         {-1,  1, -1, -1, -1,  7, -1, -1},
         {-1,  1,  1, -1, -1,  7, -1, -1},
         { 3,  3,  3, -1,  7, -1, -1, -1},
         { 3, -1,  2,  3,  3,  3, -1,  3},
         {-1,  2, -1,  3, -1,  3,  3, -1},
         {-1,  2, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  4, -1, -1, -1, -1, -1},
         {-1,  7,  4, -1, -1, -1, -1, -1},
         {-1, -1,  7,  4, -1, -1, -1, -1},
         {-1,  4,  7,  4, -1, -1, -1, -1},
         { 1,  1,  1,  1,  1,  1,  1, -1},
         { 1,  2,  1,  2,  1,  1, -1, -1},
         { 2,  2,  2,  2,  2,  2,  2,  2},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 0, -1, -1, -1, -1, -1, -1,  6},
         { 6,  1,  4,  3,  7,  5,  0, -1},
         { 0, -1, -1, -1, -1, -1, -1,  6},
         { 6,  1,  4,  3,  7,  5,  0, -1},
         { 0, -1, -1, -1, -1, -1, -1,  6},
         { 6,  1,  4,  3,  7,  5,  0, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 3,  3,  4,  6,  6,  4,  3,  3},
         { 0,  3,  4,  6,  4,  3,  1, -1},
         { 5,  1,  3,  4,  4,  3,  0,  1},
         { 0,  1,  3,  4,  3,  1,  0, -1},
         { 2,  1,  6,  3,  3,  0,  0,  1},
         { 0,  3,  4,  3,  6,  1,  5, -1},
         { 6,  1,  2,  6,  4,  0,  0,  2},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6,  6, -1, -1, -1, -1,  4,  4},
         { 4,  0, -1, -1, -1,  3,  6, -1},
         { 0,  6, -1, -1, -1, -1,  4,  2},
         { 7, -1, -1, -1, -1, -1,  7, -1},
         { 4,  4, -1, -1, -1, -1,  5,  6},
         { 6,  4,  7,  7,  5,  6,  4, -1},
         {-1,  7,  6,  4,  6,  4,  7, -1},
         {-1,  0, -1,  7, -1,  7, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  5, -1, -1, -1, -1,  4, -1},
         {-1,  5, -1, -1, -1,  4, -1, -1},
         {-1, -1,  5,  6,  6,  4, -1, -1},
         {-1, -1,  2, -1,  2, -1, -1, -1},
         { 0,  0,  6, -1, -1,  6,  1,  1},
         {-1, -1,  2, -1,  2, -1, -1, -1},
         {-1, -1,  7,  6,  6,  3, -1, -1},
         {-1,  7, -1, -1, -1,  3, -1, -1},
         {-1,  7, -1, -1, -1, -1,  3, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  6, -1, -1, -1, -1,  2, -1},
         { 1,  7,  1,  1,  1,  3,  1, -1},
         {-1, -1,  4,  1,  1,  4, -1, -1},
         {-1,  1,  3,  1,  7,  1, -1, -1},
         {-1, -1, -1,  2,  6, -1, -1, -1},
         {-1, -1,  1,  5,  1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 7,  7,  7,  7,  7,  7,  7,  7},
         { 7, -1, -1, -1, -1, -1,  7, -1},
         { 7, -1, -1,  2,  0,  5,  2,  2},
         { 7, -1, -1, -1,  0,  3,  6, -1},
         { 7, -1, -1, -1, -1, -1,  4,  0},
         { 5,  5, -1, -1, -1, -1, -1, -1},
         { 4,  3,  6,  2, -1, -1, -1, -1},
         { 0,  2,  0,  4, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  1, -1, -1,  1, -1, -1},
         {-1,  4, -1, -1,  5, -1, -1, -1},
         {-1,  7, -1, -1,  1,  1,  1, -1},
         { 6, -1, -1, -1, -1,  7, -1, -1},
         { 1,  1,  1,  1, -1,  4, -1, -1},
         {-1, -1,  5, -1, -1, -1, -1, -1},
         {-1, -1,  0, -1, -1, -1, -1, -1},
         {-1,  3, -1, -1, -1, -1, -1, -1},
         {-1,  1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  7,  7, -1, -1,  7,  7, -1},
         { 6, -1,  4, -1,  4, -1,  6, -1},
         { 5, -1, -1,  3,  3, -1, -1,  5},
         { 6, -1, -1, -1, -1, -1,  6, -1},
         {-1,  7, -1, -1, -1, -1,  7, -1},
         {-1,  4, -1, -1, -1,  4, -1, -1},
         {-1, -1,  3, -1, -1,  3, -1, -1},
         {-1, -1,  2, -1,  2, -1, -1, -1},
         {-1, -1, -1,  5,  5, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  0,  0, -1, -1,  0,  0, -1},
         { 7,  4,  6,  6,  6,  4,  3, -1},
         { 5,  6,  6,  6,  2,  6,  6,  3},
         { 7,  4,  6,  6,  6,  4,  3, -1},
         {-1,  0,  0, -1, -1,  0,  0, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1, -1,  7,  7,  7},
         {-1, -1, -1, -1,  2,  7,  7, -1},
         {-1,  0,  7,  7,  7, -1,  7,  7},
         { 6,  7,  7,  7, -1, -1, -1, -1},
         { 6, -1, -1, -1,  7,  7,  7,  7},
         { 6, -1, -1, -1, -1, -1, -1, -1},
         { 4,  2,  2,  2,  4, -1,  3, -1},
         { 4,  4,  4,  4,  3,  3,  3, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 4, -1, -1,  7, -1,  6, -1,  7},
         { 7,  6,  7, -1, -1,  7,  4, -1},
         {-1, -1,  7, -1, -1,  7, -1, -1},
         {-1,  0,  0,  0,  0,  0,  3, -1},
         {-1, -1,  0,  2,  2,  0,  6,  4},
         {-1, -1,  0,  0,  0,  1,  3, -1},
         {-1, -1, -1,  0,  0, -1,  3,  4},
         {-1, -1, -1,  6, -1,  5,  6, -1},
         {-1, -1, -1, -1, -1, -1,  1,  0},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  5, -1, -1, -1, -1,  5, -1},
         { 0, -1, -1,  0, -1, -1,  0, -1},
         { 0,  0,  0,  2,  2,  0,  0,  0},
         { 0, -1, -1,  0, -1, -1,  0, -1},
         {-1,  7, -1,  3, -1, -1,  7, -1},
         {-1, -1,  3,  6, -1, -1, -1, -1},
         {-1, -1, -1,  6, -1, -1, -1, -1},
         {-1,  3,  6, -1, -1, -1, -1, -1},
         {-1,  3, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  6,  5, -1, -1, -1},
         {-1, -1,  2,  6,  3, -1, -1, -1},
         {-1, -1,  5,  4,  7,  1, -1, -1},
         {-1,  6,  2,  2,  3,  4, -1, -1},
         {-1, -1,  3,  7,  3,  6, -1, -1},
         {-1, -1,  1,  3,  2, -1, -1, -1},
         {-1, -1, -1,  4,  5, -1, -1, -1},
         {-1, -1, -1,  4, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 7,  7, -1,  2,  2, -1,  6,  6},
         { 6, -1, -1,  6, -1, -1,  3, -1},
         { 2, -1, -1,  1, -1, -1,  2, -1},
         { 5, -1, -1,  3, -1, -1,  2, -1},
         { 1, -1, -1,  2, -1, -1,  1, -1},
         { 5, -1, -1,  2, -1, -1,  2, -1},
         { 6, -1, -1,  1, -1, -1,  7, -1},
         { 5, -1, -1,  5, -1, -1,  4, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  6,  6, -1, -1, -1},
         {-1,  0,  4,  4,  4,  0, -1, -1},
         {-1, -1, -1,  6,  6, -1, -1, -1},
         {-1, -1,  2,  7,  2, -1, -1, -1},
         {-1, -1, -1,  6,  6, -1, -1, -1},
         {-1,  0,  5,  5,  5,  0, -1, -1},
         {-1, -1, -1,  3,  3, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  4,  1,  3, -1, -1, -1},
         {-1,  1, -1, -1,  1, -1, -1, -1},
         {-1, -1,  4,  1,  3,  4,  1, -1},
         {-1,  1,  3,  4, -1, -1,  4, -1},
         {-1,  3, -1, -1,  3,  4,  1, -1},
         {-1,  1,  3,  4,  1,  3, -1, -1},
         {-1, -1,  4,  1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  6,  4, -1,  3,  2,  5, -1},
         { 0, -1, -1, -1, -1, -1,  1, -1},
         {-1,  2,  3,  5, -1,  4,  6, -1},
         { 0, -1, -1, -1, -1, -1,  1, -1},
         {-1,  4,  6, -1,  2,  5,  3, -1},
         { 0, -1, -1, -1, -1, -1,  1, -1},
         {-1,  5,  2,  3, -1,  4,  6, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  6,  6, -1, -1, -1},
         {-1, -1,  7,  6,  4, -1, -1, -1},
         {-1,  2,  1,  7,  4,  1,  3, -1},
         { 2,  1,  1,  1,  1,  1,  3, -1},
         {-1,  2,  2,  2,  3,  3,  3, -1},
         {-1, -1, -1,  5, -1, -1, -1, -1},
         {-1, -1, -1,  2,  3, -1, -1, -1},
         {-1, -1, -1,  5, -1, -1, -1, -1},
         {-1, -1,  2,  2,  3,  3, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 4, -1,  5, -1, -1,  3, -1,  6},
         { 2, -1,  3, -1,  2, -1,  4, -1},
         { 4, -1, -1,  1,  0, -1, -1,  6},
         { 6, -1,  2,  3,  5, -1,  4, -1},
         { 4, -1, -1,  0,  1, -1, -1,  6},
         { 2, -1,  5, -1,  3, -1,  4, -1},
         { 4, -1,  3, -1, -1,  2, -1,  6},
         { 6, -1, -1, -1, -1, -1,  4, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 2,  6,  0,  5,  5,  1,  3,  4},
         { 1, -1, -1,  2, -1, -1,  0, -1},
         { 4, -1, -1,  3,  6, -1, -1,  2},
         {-1, -1, -1,  0, -1, -1, -1, -1},
         {-1, -1, -1,  1,  4, -1, -1, -1},
         {-1, -1, -1,  2, -1, -1, -1, -1},
         {-1, -1, -1,  6,  3, -1, -1, -1},
         {-1, -1, -1,  5, -1, -1, -1, -1},
         {-1, -1, -1,  4,  1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1, -1,  5,  1,  1,  3},
         { 0,  5,  1,  0,  5,  3,  3, -1},
         { 5,  1,  0,  5,  1,  0,  5,  1},
         { 0,  5,  1,  0,  5,  1,  6, -1},
         {-1, -1, -1, -1,  1,  6,  5,  1},
         {-1, -1, -1, -1,  5,  1,  6, -1},
         {-1, -1, -1, -1,  1,  0,  5,  1},
         {-1, -1, -1, -1,  5,  1,  0, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  0,  7,  3, -1, -1,  2,  2},
         {-1,  0,  7,  3, -1, -1,  2, -1},
         {-1,  0,  7,  3, -1, -1,  2,  2},
         {-1,  0,  7,  3, -1,  3,  1, -1},
         {-1,  0,  7,  3, -1,  6,  4,  5},
         {-1,  0,  7,  3, -1,  7,  0, -1},
         {-1,  0,  7,  3, -1,  2,  3,  4},
         {-1,  0,  7,  3, -1,  5,  6, -1},
         {-1, -1, -1, -1, -1,  7,  0,  1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  7,  7,  7,  7, -1},
         { 3,  4,  5, -1, -1, -1,  7, -1},
         { 2, -1, -1, -1, -1, -1, -1,  3},
         { 7, -1, -1, -1, -1, -1,  4, -1},
         { 7, -1, -1, -1,  3,  4,  5,  6},
         { 7, -1, -1,  2,  0,  1,  2, -1},
         { 6, -1, -1, -1,  3,  4,  5,  6},
         { 0,  1, -1, -1, -1, -1, -1, -1},
         { 2,  3,  4, -1, -1, -1, -1, -1},
         { 5,  6,  0, -1, -1, -1, -1, -1}},
        {{-1,  7, -1, -1, -1, -1,  2, -1},
         { 1,  1, -1, -1, -1,  3,  3, -1},
         {-1,  2, -1, -1, -1, -1,  4, -1},
         { 3,  3, -1, -1, -1,  5,  5, -1},
         {-1,  4, -1, -1, -1, -1,  6, -1},
         { 5,  5, -1, -1, -1,  1,  1, -1},
         {-1,  6, -1, -1, -1, -1,  7, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  4, -1, -1, -1, -1,  4, -1},
         { 2, -1, -1,  1, -1, -1,  2, -1},
         { 5, -1, -1,  0,  0, -1, -1,  5},
         { 5, -1, -1,  1, -1, -1,  6, -1},
         {-1,  4,  2,  7,  7,  5,  4, -1},
         {-1, -1, -1,  6, -1, -1, -1, -1},
         {-1, -1, -1,  3,  3, -1, -1, -1},
         {-1, -1, -1,  7, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  1, -1, -1,  2,  3,  4, -1},
         { 2, -1, -1,  3,  0,  4, -1, -1},
         { 4, -1, -1,  2,  3,  1, -1, -1},
         { 3, -1,  4,  3,  0, -1, -1, -1},
         { 4, -1, -1,  2,  5,  1, -1, -1},
         { 3, -1,  4,  5,  0,  4, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 2, -1, -1,  1,  1, -1, -1,  2},
         { 2, -1,  3,  3,  3, -1,  2, -1},
         {-1,  2, -1,  4,  4, -1,  2, -1},
         {-1,  7,  7,  0,  7,  7, -1, -1},
         {-1, -1, -1,  4,  4, -1, -1, -1},
         {-1, -1,  5,  7,  5, -1, -1, -1},
         { 6,  3,  2,  6,  4,  2,  3,  6},
         { 5, -1, -1, -1, -1, -1,  1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 4,  2,  3,  5,  7,  1,  3,  6},
         { 1, -1, -1,  1, -1, -1,  1, -1},
         { 3,  0,  1,  3,  2,  4,  3,  5},
         { 4, -1, -1,  4, -1, -1,  4, -1},
         {-1,  5, -1, -1,  5, -1, -1,  5},
         { 0,  3,  2,  0,  4,  5,  0, -1},
         {-1,  6, -1, -1,  6, -1, -1,  6},
         { 7, -1, -1,  7, -1, -1,  7, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  5,  4, -1,  1,  1, -1, -1},
         { 5, -1,  4,  1, -1,  1, -1, -1},
         { 0, -1, -1, -1, -1, -1,  0, -1},
         { 0,  6,  4, -1, -1,  4,  2, -1},
         {-1,  4,  3,  5,  2,  6,  3,  6},
         {-1,  2,  6, -1, -1,  5,  4, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  6,  6, -1, -1, -1},
         {-1, -1,  5,  5,  4, -1, -1, -1},
         {-1, -1,  1,  6,  6,  4, -1, -1},
         {-1,  1,  7,  2,  5,  3, -1, -1},
         {-1,  2,  7,  2,  1,  5,  3, -1},
         { 2,  1,  3,  1,  4,  2,  7, -1},
         {-1,  3,  1,  3,  4,  2,  7, -1},
         {-1,  3,  5,  5,  6,  6, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  7,  3, -1, -1, -1, -1},
         {-1,  1,  7,  6, -1, -1, -1, -1},
         {-1,  3,  7,  5,  1,  5, -1, -1},
         { 7,  7,  0,  2,  4,  0,  4, -1},
         { 7,  1,  4,  6,  5,  6,  5,  7},
         { 1,  7,  7,  1,  7,  7,  1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  1, -1, -1,  1, -1, -1},
         {-1,  5,  6,  1,  5,  6, -1, -1},
         {-1,  1,  1,  2,  2,  1,  1, -1},
         { 4,  7,  1,  0,  1,  7,  4, -1},
         {-1,  3,  7,  5,  7,  5,  3, -1},
         {-1,  1,  1,  1,  1,  1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 4, -1, -1, -1,  5, -1, -1,  4},
         { 6,  6,  7,  6, -1,  4,  5, -1},
         { 4,  2,  7,  5,  2,  2,  6,  4},
         {-1, -1,  4,  1, -1,  5,  2, -1},
         {-1,  5,  2,  7,  7, -1,  7,  4},
         { 4,  6,  5,  4, -1,  4,  2, -1},
         {-1, -1, -1,  4, -1,  4,  1, -1},
         { 0,  0,  0,  5, -1, -1, -1, -1},
         {-1, -1, -1, -1,  0,  0,  0,  0},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1, -1, -1, -1,  0,  0, -1, -1},
         { 2, -1, -1,  0,  1,  0, -1, -1},
         { 3, -1, -1,  0,  2,  2,  0, -1},
         { 4, -1,  0,  1,  1,  1,  0, -1},
         { 5, -1, -1,  0,  4,  4,  0, -1},
         { 6, -1, -1,  4,  4,  4, -1, -1},
         { 7, -1, -1, -1,  4,  4, -1, -1},
         {-1, -1, -1,  0,  1,  0, -1, -1},
         {-1, -1, -1,  0,  1,  1,  0, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  3, -1, -1,  1,  7, -1},
         {-1,  7,  4, -1, -1,  4,  3, -1},
         { 1, -1, -1,  0,  2,  0, -1, -1},
         { 5,  4, -1,  3, -1, -1, -1, -1},
         { 4, -1,  3,  6,  1,  1,  6, -1},
         {-1,  1, -1, -1,  4, -1,  1, -1},
         {-1,  7,  5, -1, -1, -1,  3, -1},
         {-1, -1,  3, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1, -1, -1, -1,  1, -1, -1, -1},
         { 2, -1, -1, -1,  2, -1, -1, -1},
         {-1,  3, -1, -1,  3,  3, -1, -1},
         {-1,  4, -1,  4, -1,  4, -1, -1},
         {-1,  5, -1, -1,  5,  5, -1, -1},
         { 6, -1, -1,  7,  1,  7, -1, -1},
         { 7, -1, -1, -1,  6,  6, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 2, -1, -1,  6, -1,  2,  5,  1},
         { 5, -1,  4, -1,  4, -1,  4, -1},
         { 6, -1, -1,  3, -1, -1, -1,  3},
         { 4,  2,  0, -1, -1, -1,  5, -1},
         {-1, -1, -1,  6, -1,  3,  6, -1},
         {-1, -1,  5, -1,  5, -1, -1, -1},
         {-1, -1, -1,  3, -1,  4,  2,  5},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6, -1, -1, -1,  4, -1, -1,  3},
         { 0,  3, -1, -1,  6, -1,  0, -1},
         {-1, -1,  7, -1,  1, -1,  3, -1},
         { 7, -1,  4,  7, -1,  2, -1, -1},
         { 5,  2,  3,  2,  1,  6, -1,  3},
         {-1, -1,  0,  4,  3,  5,  4, -1},
         {-1,  7,  6, -1, -1,  0, -1, -1},
         { 4,  3, -1, -1, -1,  4,  2, -1},
         { 0, -1, -1, -1, -1, -1,  6, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6,  1,  2,  5,  1,  6,  3,  0},
         {-1, -1, -1, -1, -1, -1,  4, -1},
         { 0,  5,  2,  7,  1,  6,  2, -1},
         { 3, -1, -1, -1, -1, -1, -1, -1},
         { 6,  7,  6,  4,  0,  5,  2,  6},
         {-1, -1, -1, -1, -1, -1,  1, -1},
         { 6,  1,  4,  0,  6,  2,  3, -1},
         { 0, -1, -1, -1, -1, -1, -1, -1},
         {-1,  0,  4,  5,  3,  7,  6,  0},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  0,  1, -1, -1, -1},
         {-1, -1,  0,  7,  0, -1, -1, -1},
         {-1, -1,  1,  2,  2,  0, -1, -1},
         {-1,  0,  7,  0,  7,  0, -1, -1},
         {-1,  6, -1,  7,  7, -1,  6, -1},
         { 4,  1,  6,  6,  6,  4,  1, -1},
         {-1,  5, -1,  7,  7, -1,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  5,  6, -1, -1, -1},
         {-1, -1,  3,  3,  3, -1, -1, -1},
         {-1, -1,  7,  5,  3,  7, -1, -1},
         {-1,  3, -1,  6, -1,  3, -1, -1},
         { 2, -1, -1,  3,  7, -1, -1,  1},
         { 2,  2, -1,  3, -1,  1,  1, -1},
         {-1,  0,  2,  5,  6,  1,  0, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1,  3,  7, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  6, -1, -1, -1, -1,  2, -1},
         {-1,  2,  6,  0,  6,  0, -1, -1},
         {-1,  0, -1, -1, -1, -1, -1, -1},
         { 6, -1, -1, -1, -1, -1, -1, -1},
         {-1,  3,  3,  2,  0,  6,  0,  0},
         {-1,  6, -1, -1, -1, -1,  0, -1},
         {-1, -1, -1,  6,  0,  2,  6, -1},
         {-1,  2,  0, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 0,  7, -1, -1, -1, -1, -1, -1},
         { 1,  5, -1, -1, -1, -1, -1, -1},
         { 7,  2,  5, -1, -1, -1, -1, -1},
         { 6,  3,  4, -1, -1, -1, -1, -1},
         { 5,  5,  4,  4, -1, -1, -1, -1},
         { 3,  3,  5,  3, -1, -1, -1, -1},
         { 1,  2,  2,  5,  3, -1, -1, -1},
         { 1,  0,  0,  7,  6, -1, -1, -1},
         { 3,  3,  5,  5,  7,  6, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  2,  6,  6,  2, -1, -1},
         {-1,  2,  1,  1,  0,  2, -1, -1},
         {-1,  2,  3,  2,  2,  0,  2, -1},
         { 2,  3,  2,  5,  2,  7,  2, -1},
         { 2,  4,  2,  5,  2,  7,  2,  0},
         { 2,  4,  2,  6,  6,  2,  0, -1},
         {-1,  2,  5,  2,  2,  2,  7,  2},
         {-1,  2,  5,  6,  6,  7,  2, -1},
         {-1, -1,  2,  2,  2,  2,  2, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  0, -1, -1,  0, -1, -1},
         { 1,  0,  0,  1,  0,  0,  1, -1},
         { 1,  7,  7,  5,  5,  7,  7,  1},
         { 3,  2, -1,  2, -1,  2,  3, -1},
         { 3,  7, -1,  6,  6, -1,  7,  3},
         { 7, -1, -1,  6, -1, -1,  7, -1},
         { 4,  4,  5, -1, -1,  5,  4,  4},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  6,  3, -1, -1,  3,  6, -1},
         { 6, -1,  2, -1,  2, -1,  6, -1},
         { 2, -1,  0,  1,  1,  0, -1,  2},
         { 5,  0, -1,  7, -1,  0,  5, -1},
         {-1,  5, -1,  6,  6, -1,  5, -1},
         { 7,  1,  4, -1,  4,  1,  7, -1},
         { 7, -1,  4, -1, -1,  4, -1,  7},
         { 2,  0, -1, -1, -1,  0,  2, -1},
         {-1,  2, -1, -1, -1, -1,  2, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 6,  1, -1, -1, -1, -1,  4,  0},
         { 2,  7,  5,  5,  5,  7,  3, -1},
         { 6,  1, -1, -1, -1, -1,  4,  0},
         { 2,  5,  7,  7,  7,  5,  3, -1},
         { 6,  1, -1, -1, -1, -1,  4,  0},
         { 2,  0,  6,  6,  6,  0,  3, -1},
         { 6,  1, -1, -1, -1, -1,  4,  0},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 5, -1, -1,  1,  1, -1, -1,  5},
         { 5, -1,  4, -1,  4, -1,  5, -1},
         {-1,  2,  4, -1, -1,  4,  2, -1},
         { 7,  2, -1, -1, -1,  2,  7, -1},
         { 0, -1,  0,  4,  4,  0, -1,  0},
         { 7,  2, -1, -1, -1,  2,  7, -1},
         {-1,  2,  3, -1, -1,  3,  2, -1},
         { 5, -1,  3, -1,  3, -1,  5, -1},
         { 5, -1, -1,  6,  6, -1, -1,  5},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 2,  2, -1, -1, -1, -1,  5,  5},
         { 5, -1, -1, -1, -1, -1,  2, -1},
         { 5, -1, -1, -1, -1, -1, -1,  2},
         { 1, -1,  1,  5,  1, -1,  3, -1},
         { 5,  2,  5,  3,  1,  2,  5,  2},
         { 2,  0,  5, -1,  2,  0,  5, -1},
         {-1,  3,  7, -1, -1,  3,  7, -1},
         {-1, -1,  2,  0,  5, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 0,  6,  5,  2,  3,  4,  1,  7},
         {-1, -1, -1, -1,  1, -1, -1, -1},
         {-1, -1, -1,  1,  1, -1, -1, -1},
         {-1, -1,  1, -1, -1, -1, -1, -1},
         { 7,  1,  4,  3,  2,  5,  6,  0},
         {-1, -1, -1, -1,  1, -1, -1, -1},
         {-1, -1, -1,  1,  1, -1, -1, -1},
         {-1, -1,  1, -1, -1, -1, -1, -1},
         { 0,  6,  5,  2,  3,  4,  1,  7},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1,  1, -1, -1,  1, -1, -1},
         {-1,  2,  4, -1,  2,  4, -1, -1},
         {-1,  2,  3,  6,  5,  3,  2, -1},
         {-1,  6,  5, -1,  6,  5, -1, -1},
         {-1, -1, -1,  7,  7, -1, -1, -1},
         {-1, -1, -1,  7, -1, -1, -1, -1},
         { 1, -1, -1,  7,  7, -1, -1,  3},
         { 2, -1, -1,  7, -1, -1,  2, -1},
         {-1,  3,  4,  5,  6,  4,  1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1, -1, -1,  2,  2, -1, -1,  2},
         { 1,  3,  7,  3,  7,  4,  2, -1},
         {-1,  1,  6, -1, -1,  6,  2, -1},
         { 6, -1,  7,  3,  7, -1,  6, -1},
         {-1,  4,  2, -1, -1,  1,  3, -1},
         {-1, -1,  2,  6,  1, -1, -1, -1},
         {-1,  4,  3,  3,  4,  4,  3, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1, -1, -1,  5,  6, -1, -1, -1},
         {-1, -1, -1,  3, -1, -1, -1, -1},
         {-1, -1, -1,  1,  2, -1, -1, -1},
         {-1, -1, -1,  4, -1, -1, -1, -1},
         {-1, -1, -1,  5,  7, -1, -1, -1},
         {-1, -1, -1,  2, -1, -1, -1, -1},
         { 6,  5,  4,  3,  2,  1,  7,  5},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{-1,  0, -1,  1, -1,  2, -1, -1},
         {-1,  4, -1,  5, -1,  6, -1, -1},
         {-1,  7, -1,  0, -1,  2, -1, -1},
         {-1,  6, -1,  3, -1,  6, -1, -1},
         {-1,  1, -1,  1, -1,  2, -1, -1},
         {-1,  3, -1,  5, -1,  0, -1, -1},
         {-1,  2, -1,  4, -1,  6, -1, -1},
         {-1,  3, -1,  6, -1,  7, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1,  1,  2,  2,  3,  3,  4,  4},
         { 5,  5,  6,  7,  6,  5,  5, -1},
         { 6,  4,  3,  3,  2,  2,  1,  6},
         { 4,  6,  5,  7,  6,  3,  1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 7,  4, -1,  1,  2, -1,  4,  7},
         { 5,  5, -1,  2, -1,  4,  4, -1},
         {-1,  5, -1,  7,  7, -1,  4, -1},
         { 1,  0,  6,  7,  6,  0,  2, -1},
         {-1,  2, -1,  5,  3, -1,  1, -1},
         { 1,  1, -1, -1, -1,  2,  2, -1},
         { 6,  1,  4, -1, -1,  4,  2,  6},
         { 5,  3, -1, -1, -1,  3,  5, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 1,  5,  1,  0,  0,  1,  5,  1},
         { 1,  2,  5, -1,  5,  2,  1, -1},
         { 3,  6,  1,  2,  2,  1,  6,  3},
         { 4,  3,  4, -1,  4,  3,  4, -1},
         { 3,  4,  6,  5,  5,  6,  4,  3},
         { 0,  2,  3, -1,  3,  2,  0, -1},
         { 2,  3,  1,  5,  5,  1,  3,  2},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}},
        {{ 3,  0,  2,  7,  5,  7,  6,  5},
         { 6, -1,  1, -1,  2, -1,  1, -1},
         {-1,  6,  4,  0,  3,  4,  5, -1},
         {-1,  5, -1,  1, -1,  4, -1, -1},
         {-1,  7,  3,  5,  6,  5,  3, -1},
         { 1, -1,  2, -1,  4, -1,  2, -1},
         { 6,  4,  4,  6,  6,  5,  5,  1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1},
         {-1, -1, -1, -1, -1, -1, -1, -1}}
    };
    
    /* the tile struct
     * type is the bubble number 0-7
     * fallx is the x axis movement for the falling bubble
     * fallvel is the initial upward velocity for the falling bubble
     * ingroup denotes a bubble that is part of a group to be removed
     * anchored denotes a bubble that is anchored to the ceiling
     */
    struct tile {
        int type;
        int fallx;
        int fallvel;
        bool ingroup;
        bool anchored;
        bool delete;
    };
    
    /* the game context struct
     * score is the current score
     * level is the current level
     * highlevel is the highest level beaten
     * highscores is the list of high scores
     * angle is the current cannon direction
     * shots is the number of shots fired since last compression
     * compress is the height of the compressor
     * onboardcnt is the number of unique bubbles on the playing board
     * onboard is the unique bubbles on the playing board
     * nextinq is the pointer to the next bubble in the firing queue
     * queue is the circular buffer of bubbles to be fired
     * elapsedlvl is level elapsed time in 1/100s of seconds
     * elapsedshot is the shot elapsed time in 1/100s of seconds
     * startedshot is when the current shot began
     * resume denotes whether to resume the currently loaded game
     * playboard is the game playing board
     */
    struct game_context {
        unsigned int score;
        unsigned int level;
        struct highscore highlevel;
        struct highscore highscores[NUM_SCORES];
        int angle;
        int shots;
        int compress;
        int onboardcnt;
        int onboard[NUM_BUBBLES];
        int nextinq;
        int queue[NUM_QUEUE];
        long elapsedlvl;
        long elapsedshot;
        long startedshot;
        bool resume;
        struct tile playboard[BB_HEIGHT][BB_WIDTH];
    };
    
    static void bubbles_init(struct game_context* bb);
    static bool bubbles_nextlevel(struct game_context* bb);
    static void bubbles_getonboard(struct game_context* bb);
    static void bubbles_drawboard(struct game_context* bb);
    static int  bubbles_fire(struct game_context* bb);
    static bool bubbles_collision(struct game_context* bb, int y, int x,
                                  int nearrow, int nearcol);
    static bool bubbles_ingroup(struct game_context* bb, int row, int col);
    static int  bubbles_searchgroup(struct game_context* bb, int row, int col);
    static int  bubbles_remove(struct game_context* bb);
    static void bubbles_anchored(struct game_context* bb, int row, int col);
    static int  bubbles_fall(struct game_context* bb);
    static int  bubbles_checklevel(struct game_context* bb);
    static void bubbles_recordscore(struct game_context* bb);
    static void bubbles_savescores(struct game_context* bb);
    static bool bubbles_loadgame(struct game_context* bb);
    static void bubbles_savegame(struct game_context* bb);
    static inline void bubbles_setcolors(void);
    static void bubbles_callback(void* param);
    static int  bubbles_handlebuttons(struct game_context* bb, bool animblock,
                                      int timeout);
    static int  bubbles(struct game_context* bb);
    
    /*****************************************************************************
    * bubbles_init() initializes bubbles data structures.
    ******************************************************************************/
    static void bubbles_init(struct game_context* bb) {
        bubbles_setcolors();
        /* seed the rand generator */
        rb->srand(*rb->current_tick);
    
        /* check for resumed game */
        if(bb->resume) {
            bb->resume = false;
            return;
        }
    
        bb->score = 0;
        bubbles_nextlevel(bb);
    }
    
    /*****************************************************************************
    * bubbles_nextlevel() sets up the game for the next level, returns false if
    *     there are no more levels.
    ******************************************************************************/
    static bool bubbles_nextlevel(struct game_context* bb) {
        int i, j, pos;
    
        bb->level++;
    
        /* check if there are no more levels */
        if(bb->level > NUM_LEVELS) return false;
    
        /* set up the play board */
        rb->memset(bb->playboard, 0, sizeof(bb->playboard));
        for(i=0; i<BB_LEVEL_HEIGHT; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                pos = (int)level[bb->level-1][i][j];
                if(pos >=0 && pos < NUM_BUBBLES) {
                    bb->playboard[i][j].type = pos;
                } else {
                    bb->playboard[i][j].type = -1;
                }
            }
        }
        for(i=BB_LEVEL_HEIGHT; i<BB_HEIGHT; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                bb->playboard[i][j].type = -1;
            }
        }
    
        /* fill first bubbles in shot queue */
        bubbles_getonboard(bb);
        for(i=0; i<NUM_QUEUE; i++) {
            bb->queue[i] = bb->onboard[rb->rand()%bb->onboardcnt];
        }
    
        bb->angle = 0;
        bb->shots = 0;
        bb->compress = 0;
        bb->nextinq = 0;
        bb->elapsedlvl = 0;
        bb->elapsedshot = 0;
    
        return true;
    }
    
    /*****************************************************************************
    * bubbles_getonboard() determines which bubble types are on the play board.
    ******************************************************************************/
    static void bubbles_getonboard(struct game_context* bb) {
        int i, j, k;
        bool found;
    
        bb->onboardcnt = 0;
        rb->memset(bb->onboard, -1, sizeof(bb->onboard));
    
        for(i=0; i<BB_HEIGHT; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                if(bb->playboard[i][j].type >= 0) {
                    found = false;
    
                    for(k=0; k<bb->onboardcnt; k++) {
                        if(bb->playboard[i][j].type == bb->onboard[k]) {
                            found = true;
                            break;
                        }
                    }
    
                    if(!found) {
                        bb->onboard[bb->onboardcnt] = bb->playboard[i][j].type;
                        bb->onboardcnt++;
                    }
    
                    if(bb->onboardcnt == NUM_BUBBLES) return;
                }
            }
        }
    }
    
    /*****************************************************************************
    * bubbles_drawboard() draws the game board to the buffer but does not update
    *     the lcd.
    ******************************************************************************/
    static void bubbles_drawboard(struct game_context* bb) {
        int i, j;
        int w, h;
        int colmax, indent;
        int tipx, tipy;
        bool evenline = false;
        char *level = "Level";
        char *score = "Score";
        char *next = "Next";
        char *hurry = "HURRY!";
        char str[11];
    
        /* clear screen */
        rb->lcd_clear_display();
    
        /* draw background */
    #ifdef HAVE_LCD_COLOR
        rb->lcd_bitmap(bubbles_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
    #endif
    
        /* display play board */
        for(i=0; i<BB_HEIGHT; i++) {
            colmax = BB_WIDTH;
            if(evenline) {
                colmax--;
                indent = ROW_INDENT;
            } else {
                indent = 0;
            }
            evenline = !evenline;
    
            for(j=0; j<colmax; j++) {
                if(bb->playboard[i][j].type >= 0 && !bb->playboard[i][j].delete) {
                    rb->lcd_bitmap_part(bubbles_emblem,
                      0, EMBLEM_HEIGHT*bb->playboard[i][j].type, EMBLEM_WIDTH,
                      XOFS+indent+BUBBLE_WIDTH*j+(BUBBLE_WIDTH-EMBLEM_WIDTH)/2,
                      ROW_HEIGHT*i+(BUBBLE_HEIGHT-EMBLEM_HEIGHT)/2+bb->compress*ROW_HEIGHT,
                      EMBLEM_WIDTH, EMBLEM_HEIGHT);
                    rb->lcd_set_drawmode(DRMODE_FG);
                    rb->lcd_mono_bitmap((const unsigned char *)bubbles_bubble,
                                        XOFS+indent+BUBBLE_WIDTH*j,
                                        ROW_HEIGHT*i+bb->compress*ROW_HEIGHT,
                                        BUBBLE_WIDTH, BUBBLE_HEIGHT);
                    rb->lcd_set_drawmode(DRMODE_SOLID);
                }
            }
        }
    
        /* display bubble to be shot */
        rb->lcd_bitmap_part(bubbles_emblem,
                   0, EMBLEM_HEIGHT*bb->queue[bb->nextinq], EMBLEM_WIDTH,
                   SHOTX+(BUBBLE_WIDTH-EMBLEM_WIDTH)/2,
                   SHOTY+(BUBBLE_HEIGHT-EMBLEM_HEIGHT)/2,
                   EMBLEM_WIDTH, EMBLEM_HEIGHT);
        rb->lcd_set_drawmode(DRMODE_FG);
        rb->lcd_mono_bitmap((const unsigned char *)bubbles_bubble,
                            SHOTX, SHOTY,
                            BUBBLE_WIDTH, BUBBLE_HEIGHT);
        rb->lcd_set_drawmode(DRMODE_SOLID);
    
        /* display next bubble to be shot */
        rb->lcd_bitmap_part(bubbles_emblem,
                   0, EMBLEM_HEIGHT*bb->queue[(bb->nextinq+1)%NUM_QUEUE], EMBLEM_WIDTH,
                   XOFS/2-BUBBLE_WIDTH/2+(BUBBLE_WIDTH-EMBLEM_WIDTH)/2,
                   SHOTY+(BUBBLE_HEIGHT-EMBLEM_HEIGHT)/2,
                   EMBLEM_WIDTH, EMBLEM_HEIGHT);
        rb->lcd_set_drawmode(DRMODE_FG);
        rb->lcd_mono_bitmap((const unsigned char *)bubbles_bubble,
                            XOFS/2-BUBBLE_WIDTH/2, SHOTY,
                            BUBBLE_WIDTH, BUBBLE_HEIGHT);
        rb->lcd_set_drawmode(DRMODE_SOLID);
    
        /* draw bounding lines */
    #ifndef HAVE_LCD_COLOR
        rb->lcd_vline(XOFS-1, 0, LCD_HEIGHT);
        rb->lcd_vline(XOFS+BUBBLE_WIDTH*BB_WIDTH, 0, LCD_HEIGHT);
    #endif
        rb->lcd_hline(XOFS, XOFS+BUBBLE_WIDTH*BB_WIDTH-1, bb->compress*ROW_HEIGHT-1);
        rb->lcd_hline(XOFS, XOFS+BUBBLE_WIDTH*BB_WIDTH-1,
                      ROW_HEIGHT*(BB_HEIGHT-2)+BUBBLE_HEIGHT);
    
        /* draw arrow */
        tipx = SHOTX+BUBBLE_WIDTH/2+(((fp14_sin(bb->angle)>>4)*BUBBLE_WIDTH*3/2)>>10);
        tipy = SHOTY+BUBBLE_HEIGHT/2-(((fp14_cos(bb->angle)>>4)*BUBBLE_HEIGHT*3/2)>>10);
    
        rb->lcd_drawline(SHOTX+BUBBLE_WIDTH/2+(((fp14_sin(bb->angle)>>4)*BUBBLE_WIDTH/2)>>10),
                         SHOTY+BUBBLE_HEIGHT/2-(((fp14_cos(bb->angle)>>4)*BUBBLE_HEIGHT/2)>>10),
                         tipx, tipy);
        xlcd_filltriangle(tipx, tipy,
                          tipx+(((fp14_sin(bb->angle-135)>>4)*BUBBLE_WIDTH/3)>>10),
                          tipy-(((fp14_cos(bb->angle-135)>>4)*BUBBLE_HEIGHT/3)>>10),
                          tipx+(((fp14_sin(bb->angle+135)>>4)*BUBBLE_WIDTH/3)>>10),
                          tipy-(((fp14_cos(bb->angle+135)>>4)*BUBBLE_HEIGHT/3)>>10));
    
        /* draw text */
        rb->lcd_getstringsize(level, &w, &h);
        rb->lcd_putsxy(XOFS/2-w/2, 2, level);
    
        rb->snprintf(str, 4, "%d", bb->level);
        rb->lcd_getstringsize(str, &w, &h);
        rb->lcd_putsxy(XOFS/2-w/2, 11, str);
    
        rb->lcd_getstringsize(score, &w, &h);
        rb->lcd_putsxy(XOFS/2-w/2, 29, score);
    
        rb->snprintf(str, 10, "%d", bb->score);
        rb->lcd_getstringsize(str, &w, &h);
        rb->lcd_putsxy(XOFS/2-w/2, 38, str);
    
        rb->lcd_getstringsize(next, &w, &h);
        rb->lcd_putsxy(XOFS/2-w/2, SHOTY-9, next);
    
        if(bb->elapsedshot >= (MAX_SHOTTIME*7)/10) {
            rb->lcd_getstringsize(hurry, &w, &h);
            rb->lcd_putsxy(LCD_WIDTH/2-w/2, LCD_HEIGHT/2-h/2, hurry);
        }
    }
    
    /*****************************************************************************
    * bubbles_fire() fires the current bubble, reloads the cannon, attaches
    *     bubble to playboard, removes appropriate bubbles, and advances the
    *     the compressor.
    ******************************************************************************/
    static int bubbles_fire(struct game_context* bb) {
        int bubblecur;
        long shotxinc, shotyinc;
        long shotxofs, shotyofs;
        int shotxdirec = 1;
        long tempxofs, tempyofs;
        int nearrow, nearcol;
        int lastrow = BB_HEIGHT-1;
        int lastcol = (BB_WIDTH-1)/2;
        int buttonres;
        long lasttick, currenttick;
    
        /* get current bubble */
        bubblecur = bb->queue[bb->nextinq];
        shotxinc = ((fp14_sin(bb->angle)>>4)*BUBBLE_WIDTH)/3;
        shotyinc = ((-1*(fp14_cos(bb->angle)>>4))*BUBBLE_HEIGHT)/3;
        shotxofs = shotyofs = 0;
    
        /* advance the queue */
        bb->queue[bb->nextinq] = bb->onboard[rb->rand()%bb->onboardcnt];
        bb->nextinq = (bb->nextinq+1)%NUM_QUEUE;
        bubbles_drawboard(bb);
        rb->lcd_update_rect(0, 0, XOFS, LCD_HEIGHT);
    
        /* move the bubble across the play board */
        lasttick = *rb->current_tick;
    
        while(true) {
            /* move the bubble one step */
            shotyofs += shotyinc;
            shotxofs += shotxinc*shotxdirec;
    
            /* check for bounce off sides */
            if(SHOTX+(shotxofs>>10) < XOFS) {
                shotxofs += 2*((XOFS<<10)-(((SHOTX)<<10)+shotxofs));
                shotxdirec *= -1;
            } else if(SHOTX+(shotxofs>>10) > XOFS+(BB_WIDTH-1)*BUBBLE_WIDTH) {
                shotxofs -= 2*((((SHOTX)<<10)+shotxofs)-
                            ((XOFS<<10)+(((BB_WIDTH-1)*BUBBLE_WIDTH)<<10)));
                shotxdirec *= -1;
            }
    
            tempxofs = shotxofs>>10;
            tempyofs = shotyofs>>10;
    
            /* display shot */
            bubbles_drawboard(bb);
            rb->lcd_bitmap_part(bubbles_emblem, 0, EMBLEM_HEIGHT*bubblecur, EMBLEM_WIDTH,
                           SHOTX+tempxofs+(BUBBLE_WIDTH-EMBLEM_WIDTH)/2,
                           SHOTY+tempyofs+(BUBBLE_HEIGHT-EMBLEM_HEIGHT)/2,
                           EMBLEM_WIDTH, EMBLEM_HEIGHT);
            rb->lcd_set_drawmode(DRMODE_FG);
            rb->lcd_mono_bitmap((const unsigned char *)bubbles_bubble,
                                SHOTX+tempxofs, SHOTY+tempyofs,
                                BUBBLE_WIDTH, BUBBLE_HEIGHT);
            rb->lcd_set_drawmode(DRMODE_SOLID);
            rb->lcd_update_rect(XOFS, 0, BB_WIDTH*BUBBLE_WIDTH, LCD_HEIGHT);
    
            /* find nearest position */
            nearrow = ((SHOTY+tempyofs)-
                          (bb->compress*ROW_HEIGHT)+
                          (ROW_HEIGHT/2))/ROW_HEIGHT;
            if(nearrow >= BB_HEIGHT) nearrow = BB_HEIGHT-1;
    
            if(nearrow%2) { /* odd row */
                nearcol = ((SHOTX+tempxofs)-
                              (XOFS+ROW_INDENT)+
                              (BUBBLE_WIDTH/2))/BUBBLE_WIDTH;
                if(nearcol >= BB_WIDTH-1) nearcol = BB_WIDTH-2;
            } else {        /* even row */
                nearcol = ((SHOTX+tempxofs)-XOFS+(BUBBLE_WIDTH/2))/BUBBLE_WIDTH;
                if(nearcol >= BB_WIDTH) nearcol = BB_WIDTH-1;
            }
            if(nearcol < 0) nearcol = 0;
    
            /* if nearest position is occupied attach to last position */
            if(bb->playboard[nearrow][nearcol].type >= 0) {
                bb->playboard[lastrow][lastcol].type = bubblecur;
                break;
            }
    
            /* save last position */
            lastrow = nearrow;
            lastcol = nearcol;
    
            /* if collision with neighbor then attach shot */
            if(bubbles_collision(bb, SHOTY+tempyofs, SHOTX+tempxofs,
                                 nearrow, nearcol)) {
                bb->playboard[nearrow][nearcol].type = bubblecur;
                break;
            }
    
            /* if at top then attach shot to the ceiling */
            if(nearrow == 0 && SHOTY+tempyofs <= bb->compress*ROW_HEIGHT) {
                bb->playboard[nearrow][nearcol].type = bubblecur;
                break;
            }
    
            /* handle button events */
            buttonres = bubbles_handlebuttons(bb, true, 0);
            if(buttonres != BB_NONE) return buttonres;
    
            /* framerate limiting */
            currenttick = *rb->current_tick;
            if(currenttick-lasttick < HZ/MAX_FPS) {
                rb->sleep((HZ/MAX_FPS)-(currenttick-lasttick));
            } else {
                rb->yield();
            }
            lasttick = currenttick;
        }
    
        bubbles_drawboard(bb);
        rb->lcd_update();
    
        /* clear appropriate bubbles from playing board */
        if(bubbles_ingroup(bb, lastrow, lastcol)) {
            buttonres = bubbles_remove(bb);
            if(buttonres != BB_NONE) return buttonres;
        }
    
        /* update shots and compress amount */
        bb->shots++;
        if(bb->shots >= NUM_COMPRESS) {
            bb->shots = 0;
            bb->compress++;
        }
    
        return BB_NONE;
    }
    
    /*****************************************************************************
    * bubbles_collision() determines if a fired bubble has collided with another
    *     bubble.
    ******************************************************************************/
    static bool bubbles_collision(struct game_context* bb, int y, int x,
                                  int nearrow, int nearcol) {
        int nx, ny;
        int adj = nearrow%2;
    
        /* check neighbors */
        if(nearcol-1 >= 0) {
            if(bb->playboard[nearrow][nearcol-1].type >= 0) {
                nx = XOFS+(nearrow%2 ? ROW_INDENT : 0)+BUBBLE_WIDTH*(nearcol-1);
                ny = ROW_HEIGHT*nearrow+bb->compress*ROW_HEIGHT;
                if((x-nx)*(x-nx)+(y-ny)*(y-ny) < MIN_DISTANCE) return true;
            }
        }
    
        if(nearcol-1+adj >= 0) {
            if(nearrow-1 >= 0) {
                if(bb->playboard[nearrow-1][nearcol-1+adj].type >= 0) {
                    nx = XOFS+((nearrow-1)%2 ? ROW_INDENT : 0)+
                         BUBBLE_WIDTH*(nearcol-1+adj);
                    ny = ROW_HEIGHT*(nearrow-1)+bb->compress*ROW_HEIGHT;
                    if((x-nx)*(x-nx)+(y-ny)*(y-ny) < MIN_DISTANCE) return true;
                }
            }
    
            if(nearrow+1 < BB_HEIGHT) {
                if(bb->playboard[nearrow+1][nearcol-1+adj].type >= 0) {
                    nx = XOFS+((nearrow+1)%2 ? ROW_INDENT : 0)+
                         BUBBLE_WIDTH*(nearcol-1+adj);
                    ny = ROW_HEIGHT*(nearrow+1)+bb->compress*ROW_HEIGHT;
                    if((x-nx)*(x-nx)+(y-ny)*(y-ny) < MIN_DISTANCE) return true;
                }
            }
        }
    
        if(nearcol+adj >= 0) {
            if(nearrow-1 >= 0) {
                if(bb->playboard[nearrow-1][nearcol+adj].type >= 0) {
                    nx = XOFS+((nearrow-1)%2 ? ROW_INDENT : 0)+
                         BUBBLE_WIDTH*(nearcol+adj);
                    ny = ROW_HEIGHT*(nearrow-1)+bb->compress*ROW_HEIGHT;
                    if((x-nx)*(x-nx)+(y-ny)*(y-ny) < MIN_DISTANCE) return true;
                }
            }
    
            if(nearrow+1 < BB_HEIGHT) {
                if(bb->playboard[nearrow+1][nearcol+adj].type >= 0) {
                    nx = XOFS+((nearrow+1)%2 ? ROW_INDENT : 0)+
                         BUBBLE_WIDTH*(nearcol+adj);
                    ny = ROW_HEIGHT*(nearrow+1)+bb->compress*ROW_HEIGHT;
                    if((x-nx)*(x-nx)+(y-ny)*(y-ny) < MIN_DISTANCE) return true;
                }
            }
        }
    
        if(nearcol+1 < BB_WIDTH-adj) {
            if(bb->playboard[nearrow][nearcol+1].type >= 0) {
                nx = XOFS+(nearrow%2 ? ROW_INDENT : 0)+BUBBLE_WIDTH*(nearcol+1);
                ny = ROW_HEIGHT*nearrow+bb->compress*ROW_HEIGHT;
                if((x-nx)*(x-nx)+(y-ny)*(y-ny) < MIN_DISTANCE) return true;
            }
        }
    
        return false;
    }
    
    /*****************************************************************************
    * bubbles_ingroup() marks all bubbles that form the current group.
    ******************************************************************************/
    static bool bubbles_ingroup(struct game_context* bb, int row, int col) {
        int i, j;
        int count;
    
        count = bubbles_searchgroup(bb, row, col);
    
        /* unmark group if too small */
        if(count < 3) {
            for(i=0; i<BB_HEIGHT; i++) {
                for(j=0; j<BB_WIDTH; j++) {
                    bb->playboard[i][j].ingroup = false;
                }
            }
    
            return false;
        }
    
        return true;
    }
    
    /*****************************************************************************
    * bubbles_searchgroup() return the size of the group of bubbles of the same
    *     type that the current bubble belongs to.
    ******************************************************************************/
    static int bubbles_searchgroup(struct game_context* bb, int row, int col) {
        int i, adj;
        int myrow, mycol, mytype;
        int count = 0;
    
        struct coord {
            int row;
            int col;
        } search[(2*BB_WIDTH-1)*(BB_HEIGHT/2)];
    
        /* search initial bubble */
        bb->playboard[row][col].ingroup = true;
        search[count].row = row;
        search[count].col = col;
        count++;
    
        /* breadth-first search neighbors */
        for(i=0; i<count; i++) {
            myrow = search[i].row;
            mycol = search[i].col;
            mytype = bb->playboard[myrow][mycol].type;
            adj = myrow%2;
    
            if(mycol-1 >= 0) {
                if(bb->playboard[myrow][mycol-1].type == mytype &&
                   !bb->playboard[myrow][mycol-1].ingroup) {
                    bb->playboard[myrow][mycol-1].ingroup = true;
                    search[count].row = myrow;
                    search[count].col = mycol-1;
                    count++;
                }
            }
    
            if(mycol-1+adj >= 0) {
                if(myrow-1 >= 0) {
                    if(bb->playboard[myrow-1][mycol-1+adj].type == mytype &&
                       !bb->playboard[myrow-1][mycol-1+adj].ingroup) {
                        bb->playboard[myrow-1][mycol-1+adj].ingroup = true;
                        search[count].row = myrow-1;
                        search[count].col = mycol-1+adj;
                        count++;
                    }
                }
    
                if(myrow+1 < BB_HEIGHT) {
                    if(bb->playboard[myrow+1][mycol-1+adj].type == mytype &&
                       !bb->playboard[myrow+1][mycol-1+adj].ingroup) {
                        bb->playboard[myrow+1][mycol-1+adj].ingroup = true;
                        search[count].row = myrow+1;
                        search[count].col = mycol-1+adj;
                        count++;
                    }
                }
            }
    
            if(mycol+adj >= 0) {
                if(myrow-1 >= 0) {
                    if(bb->playboard[myrow-1][mycol+adj].type == mytype &&
                       !bb->playboard[myrow-1][mycol+adj].ingroup) {
                        bb->playboard[myrow-1][mycol+adj].ingroup = true;
                        search[count].row = myrow-1;
                        search[count].col = mycol+adj;
                        count++;
                    }
                }
    
                if(myrow+1 < BB_HEIGHT) {
                    if(bb->playboard[myrow+1][mycol+adj].type == mytype &&
                       !bb->playboard[myrow+1][mycol+adj].ingroup) {
                        bb->playboard[myrow+1][mycol+adj].ingroup = true;
                        search[count].row = myrow+1;
                        search[count].col = mycol+adj;
                        count++;
                    }
                }
            }
    
            if(mycol+1 < BB_WIDTH-adj) {
                if(bb->playboard[myrow][mycol+1].type == mytype &&
                   !bb->playboard[myrow][mycol+1].ingroup) {
                    bb->playboard[myrow][mycol+1].ingroup = true;
                    search[count].row = myrow;
                    search[count].col = mycol+1;
                    count++;
                }
            }
        }
    
        return count;
    }
    
    /*****************************************************************************
    * bubbles_remove() removes all bubbles in the current group and all
    *     unanchored bubbles from the play board.
    ******************************************************************************/
    static int bubbles_remove(struct game_context* bb) {
        int i, j;
        int buttonres;
    
        /* determine all anchored bubbles */
        for(j=0; j<BB_WIDTH; j++) {
            if(bb->playboard[0][j].type >= 0 && !bb->playboard[0][j].ingroup) {
                bubbles_anchored(bb, 0, j);
            }
        }
    
        /* mark bubbles to be deleted */
        for(i=0; i<BB_HEIGHT; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                if(bb->playboard[i][j].type >= 0 &&
                  (!bb->playboard[i][j].anchored || bb->playboard[i][j].ingroup)) {
                    bb->playboard[i][j].delete = true;
                }
            }
        }
    
        /* animate falling bubbles */
        buttonres = bubbles_fall(bb);
        if(buttonres != BB_NONE) return buttonres;
    
        /* remove bubbles */
        for(i=0; i<BB_HEIGHT; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                if(bb->playboard[i][j].delete) {
                    bb->playboard[i][j].ingroup = false;
                    bb->playboard[i][j].type = -1;
                    bb->playboard[i][j].delete = false;
                } else {
                    bb->playboard[i][j].anchored = false;
                }
            }
        }
    
        bubbles_getonboard(bb);
    
        return BB_NONE;
    }
    
    /*****************************************************************************
    * bubbles_anchored() marks all bubbles that are anchored in some way to the
    *     current bubble.
    ******************************************************************************/
    static void bubbles_anchored(struct game_context* bb, int row, int col) {
        int i, adj;
        int myrow, mycol, mytype;
        int count = 0;
    
        struct coord {
            int row;
            int col;
        } search[(2*BB_WIDTH-1)*(BB_HEIGHT/2)];
    
        /* search initial bubble */
        bb->playboard[row][col].anchored = true;
        search[count].row = row;
        search[count].col = col;
        count++;
    
        /* breadth-first search neighbors */
        for(i=0; i<count; i++) {
            myrow = search[i].row;
            mycol = search[i].col;
            mytype = bb->playboard[myrow][mycol].type;
            adj = myrow%2;
    
            if(mycol-1 >= 0) {
                if(bb->playboard[myrow][mycol-1].type >= 0 &&
                   !bb->playboard[myrow][mycol-1].ingroup &&
                   !bb->playboard[myrow][mycol-1].anchored) {
                    bb->playboard[myrow][mycol-1].anchored = true;
                    search[count].row = myrow;
                    search[count].col = mycol-1;
                    count++;
                }
            }
    
            if(mycol-1+adj >= 0) {
                if(myrow-1 >= 0) {
                    if(bb->playboard[myrow-1][mycol-1+adj].type >= 0 &&
                       !bb->playboard[myrow-1][mycol-1+adj].ingroup &&
                       !bb->playboard[myrow-1][mycol-1+adj].anchored) {
                        bb->playboard[myrow-1][mycol-1+adj].anchored = true;
                        search[count].row = myrow-1;
                        search[count].col = mycol-1+adj;
                        count++;
                    }
                }
    
                if(myrow+1 < BB_HEIGHT) {
                    if(bb->playboard[myrow+1][mycol-1+adj].type >= 0 &&
                       !bb->playboard[myrow+1][mycol-1+adj].ingroup &&
                       !bb->playboard[myrow+1][mycol-1+adj].anchored) {
                        bb->playboard[myrow+1][mycol-1+adj].anchored = true;
                        search[count].row = myrow+1;
                        search[count].col = mycol-1+adj;
                        count++;
                    }
                }
            }
    
            if(mycol+adj >= 0) {
                if(myrow-1 >= 0) {
                    if(bb->playboard[myrow-1][mycol+adj].type >= 0 &&
                       !bb->playboard[myrow-1][mycol+adj].ingroup &&
                       !bb->playboard[myrow-1][mycol+adj].anchored) {
                        bb->playboard[myrow-1][mycol+adj].anchored = true;
                        search[count].row = myrow-1;
                        search[count].col = mycol+adj;
                        count++;
                    }
                }
    
                if(myrow+1 < BB_HEIGHT) {
                    if(bb->playboard[myrow+1][mycol+adj].type >= 0 &&
                       !bb->playboard[myrow+1][mycol+adj].ingroup &&
                       !bb->playboard[myrow+1][mycol+adj].anchored) {
                        bb->playboard[myrow+1][mycol+adj].anchored = true;
                        search[count].row = myrow+1;
                        search[count].col = mycol+adj;
                        count++;
                    }
                }
            }
    
            if(mycol+1 < BB_WIDTH-adj) {
                if(bb->playboard[myrow][mycol+1].type >= 0 &&
                   !bb->playboard[myrow][mycol+1].ingroup &&
                   !bb->playboard[myrow][mycol+1].anchored) {
                    bb->playboard[myrow][mycol+1].anchored = true;
                    search[count].row = myrow;
                    search[count].col = mycol+1;
                    count++;
                }
            }
        }
    }
    
    /*****************************************************************************
    * bubbles_fall() makes removed bubbles fall from the screen.
    ******************************************************************************/
    static int bubbles_fall(struct game_context* bb) {
        int i, j;
        int count;
        int indent;
        int xofs, yofs;
        int buttonres;
        bool onscreen;
        long lasttick, currenttick;
    
        /* give all falling bubbles an x axis movement */
        for(i=0; i<BB_HEIGHT; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                if(bb->playboard[i][j].delete) {
                    bb->playboard[i][j].fallx = rb->rand()%25 - 12;
                    bb->playboard[i][j].fallvel = rb->rand()%5 + 6;
                }
            }
        }
    
        /* draw bubbles falling off the screen
         * follows y=x^2-8x scaled to bubble size
         */
        lasttick = *rb->current_tick;
    
        for(count=1; ;count++) {
            onscreen = false;
            bubbles_drawboard(bb);
    
            for(i=0; i<BB_HEIGHT; i++) {
                for(j=0; j<BB_WIDTH; j++) {
                    if(bb->playboard[i][j].delete) {
                        indent = (i%2 ? ROW_INDENT : 0);
                        xofs = ((bb->playboard[i][j].fallx*count)*BUBBLE_WIDTH)/48;
                        yofs = ((count*count - bb->playboard[i][j].fallvel*count)*
                               BUBBLE_HEIGHT)/20;
    
                        /* draw bubble if it is still on the screen */
                        if(ROW_HEIGHT*i+bb->compress*ROW_HEIGHT+yofs
                           <= LCD_HEIGHT) {
                            onscreen = true;
    
                            rb->lcd_bitmap_part(bubbles_emblem, 0,
                                    EMBLEM_HEIGHT*bb->playboard[i][j].type, EMBLEM_WIDTH,
                                    XOFS+indent+BUBBLE_WIDTH*j+
                                        (BUBBLE_WIDTH-EMBLEM_WIDTH)/2+xofs,
                                    ROW_HEIGHT*i+(BUBBLE_HEIGHT-EMBLEM_HEIGHT)/2+
                                        bb->compress*ROW_HEIGHT+yofs,
                                    EMBLEM_WIDTH, EMBLEM_HEIGHT);
                            rb->lcd_set_drawmode(DRMODE_FG);
                            rb->lcd_mono_bitmap(
                                    (const unsigned char *)bubbles_bubble,
                                    XOFS+indent+BUBBLE_WIDTH*j+xofs,
                                    ROW_HEIGHT*i+bb->compress*ROW_HEIGHT+yofs,
                                    BUBBLE_WIDTH, BUBBLE_HEIGHT);
                            rb->lcd_set_drawmode(DRMODE_SOLID);
                        }
                    }
                }
            }
    
            rb->lcd_update();
    
            /* break out if all bubbles are off the screen */
            if(!onscreen) break;
    
            /* handle button events */
            buttonres = bubbles_handlebuttons(bb, true, 0);
            if(buttonres != BB_NONE) return buttonres;
    
            /* framerate limiting */
            currenttick = *rb->current_tick;
            if(currenttick-lasttick < HZ/MAX_FPS) {
                rb->sleep((HZ/MAX_FPS)-(currenttick-lasttick));
            } else {
                rb->yield();
            }
            lasttick = currenttick;
        }
    
        return BB_NONE;
    }
    
    /*****************************************************************************
    * bubbles_checklevel() checks the state of the playboard for a win or loss.
    ******************************************************************************/
    static int bubbles_checklevel(struct game_context* bb) {
        int i, j;
        int points;
        char str[13];
    
        bubbles_drawboard(bb);
        rb->lcd_update();
    
        /* check for bubbles below cut off point */
        for(i=0; i<=bb->compress; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                if(bb->playboard[BB_HEIGHT-1-i][j].type >= 0) return BB_LOSE;
          }
        }
    
        /* check for bubbles above cut off point */
        for(i=0; i<BB_HEIGHT-1-bb->compress; i++) {
            for(j=0; j<BB_WIDTH; j++) {
                if(bb->playboard[i][j].type >= 0) return BB_NONE;
            }
        }
    
        /* level complete, record score */
        points = 100 - bb->elapsedlvl/100;
        if(points > 0) {
            bb->score += points;
        } else {
            points = 0;
        }
    
        rb->snprintf(str, 12, "%d points", points);
        rb->splash(HZ, str);
    
        /* advance to the next level */
        if(!bubbles_nextlevel(bb)) {
            return BB_WIN;
        }
    
        bubbles_drawboard(bb);
        rb->lcd_update();
        rb->snprintf(str, 12, "Level %d", bb->level);
        rb->splash(HZ, str);
        bubbles_drawboard(bb);
        rb->lcd_update();
    
        return BB_NONE;
    }
    
    /*****************************************************************************
    * bubbles_recordscore() inserts a high score into the high scores list and
    *     returns the high score position.
    ******************************************************************************/
    static void bubbles_recordscore(struct game_context* bb) {
    
        int position;
    
        position = highscore_update(bb->score, bb->level, "",
                                    bb->highscores, NUM_SCORES);
        if (position==0)
            rb->splash(HZ*2, "New High Score");
        if (position != -1)
            highscore_show(position, bb->highscores, NUM_SCORES);
    }
    
    /*****************************************************************************
    * bubbles_loadscores() loads the high scores saved file.
    ******************************************************************************/
    static void bubbles_loadscores(struct game_context* bb) {
    
        /* highlevel and highscores */
        highscore_load(SCORE_FILE, &bb->highlevel, NUM_SCORES+1);
    
        if( bb->highlevel.level >= NUM_LEVELS )
            bb->highlevel.level = NUM_LEVELS - 1;
    }
    
    /*****************************************************************************
    * bubbles_savescores() saves the high scores saved file.
    ******************************************************************************/
    static void bubbles_savescores(struct game_context* bb) {
    
        /* highlevel and highscores */
        highscore_save(SCORE_FILE, &bb->highlevel, NUM_SCORES+1);
    }
    
    /*****************************************************************************
    * bubbles_loadgame() loads the saved game and returns load success.
    ******************************************************************************/
    static bool bubbles_loadgame(struct game_context* bb) {
        int fd;
        bool loaded = false;
    
        /* open game file */
        fd = rb->open(SAVE_FILE, O_RDONLY);
        if(fd < 0) return loaded;
    
        /* read in saved game */
        while(true) {
            if(rb->read(fd, &bb->score, sizeof(bb->score)) <= 0) break;
            if(rb->read(fd, &bb->level, sizeof(bb->level)) <= 0) break;
            if(rb->read(fd, &bb->angle, sizeof(bb->angle)) <= 0) break;
            if(rb->read(fd, &bb->shots, sizeof(bb->shots)) <= 0) break;
            if(rb->read(fd, &bb->compress, sizeof(bb->compress)) <= 0) break;
            if(rb->read(fd, &bb->onboardcnt, sizeof(bb->onboardcnt)) <= 0) break;
            if(rb->read(fd, bb->onboard, sizeof(bb->onboard)) <= 0) break;
            if(rb->read(fd, &bb->nextinq, sizeof(bb->nextinq)) <= 0) break;
            if(rb->read(fd, bb->queue, sizeof(bb->queue)) <= 0) break;
            if(rb->read(fd, &bb->elapsedlvl, sizeof(bb->elapsedlvl)) <= 0) break;
            if(rb->read(fd, bb->playboard, sizeof(bb->playboard)) <= 0) break;
            bb->resume = true;
            loaded = true;
            break;
        }
    
        rb->close(fd);
    
        /* delete saved file */
        rb->remove(SAVE_FILE);
        return loaded;
    }
    
    /*****************************************************************************
    * bubbles_savegame() saves the current game state.
    ******************************************************************************/
    static void bubbles_savegame(struct game_context* bb) {
        int fd;
    
        /* write out the game state to the save file */
        fd = rb->open(SAVE_FILE, O_WRONLY|O_CREAT);
        rb->write(fd, &bb->score, sizeof(bb->score));
        rb->write(fd, &bb->level, sizeof(bb->level));
        rb->write(fd, &bb->angle, sizeof(bb->angle));
        rb->write(fd, &bb->shots, sizeof(bb->shots));
        rb->write(fd, &bb->compress, sizeof(bb->compress));
        rb->write(fd, &bb->onboardcnt, sizeof(bb->onboardcnt));
        rb->write(fd, bb->onboard, sizeof(bb->onboard));
        rb->write(fd, &bb->nextinq, sizeof(bb->nextinq));
        rb->write(fd, bb->queue, sizeof(bb->queue));
        rb->write(fd, &bb->elapsedlvl, sizeof(bb->elapsedlvl));
        rb->write(fd, bb->playboard, sizeof(bb->playboard));
        rb->close(fd);
    
        bb->resume = true;
    }
    
    /*****************************************************************************
    * bubbles_setcolors() set the foreground and background colors.
    ******************************************************************************/
    static inline void bubbles_setcolors(void) {
    #ifdef HAVE_LCD_COLOR
        rb->lcd_set_background(LCD_RGBPACK(181,181,222));
        rb->lcd_set_foreground(LCD_BLACK);
    #endif
    }
    
    /*****************************************************************************
    * bubbles_callback() is the default event handler callback which is called
    *     on usb connect and shutdown.
    ******************************************************************************/
    static void bubbles_callback(void* param) {
        struct game_context* bb = (struct game_context*) param;
        bubbles_savescores(bb);
    }
    
    /*****************************************************************************
    * bubbles_handlebuttons() handles button events during a game.
    ******************************************************************************/
    static int bubbles_handlebuttons(struct game_context* bb, bool animblock,
                                     int timeout) {
        int button;
        int buttonres;
        long start;
        const struct button_mapping *plugin_contexts[]
    #if (CONFIG_KEYPAD != SANSA_E200_PAD) && \
          (CONFIG_KEYPAD != SANSA_FUZE_PAD)
                         = {generic_left_right_fire,generic_actions};
    #else
                         = {generic_directions,generic_actions};
    #endif
    
        if (timeout < 0)
            timeout = 0;
        button = pluginlib_getaction(timeout,plugin_contexts,2);
    #if defined(HAS_BUTTON_HOLD) && !defined(HAVE_REMOTE_LCD_AS_MAIN)
        /* FIXME: Should probably check remote hold here */
        if (rb->button_hold())
            button = BUBBLES_START;
    #endif
    
        switch(button){
            case BUBBLES_LEFT_REP:
                if(bb->angle > MIN_ANGLE) bb->angle -= ANGLE_STEP_REP;
            case BUBBLES_LEFT:   /* change angle to the left */
                if(bb->angle > MIN_ANGLE) bb->angle -= ANGLE_STEP;
                break;
    
            case BUBBLES_RIGHT_REP:
                if(bb->angle < MAX_ANGLE) bb->angle += ANGLE_STEP_REP;
            case BUBBLES_RIGHT:  /* change angle to the right */
                if(bb->angle < MAX_ANGLE) bb->angle += ANGLE_STEP;
                break;
    
            case BUBBLES_SELECT: /* fire the shot */
                if(!animblock) {
                    bb->elapsedlvl += bb->elapsedshot;
                    bb->elapsedshot = 0;
                    buttonres = bubbles_fire(bb);
                    if(buttonres != BB_NONE) return buttonres;
                    buttonres = bubbles_checklevel(bb);
                    if(buttonres != BB_NONE) return buttonres;
                    bb->startedshot = *rb->current_tick;
                }
                break;
    
            case BUBBLES_START:  /* pause the game */
                start = *rb->current_tick;
                rb->splash(0, "Paused");
                while(pluginlib_getaction(TIMEOUT_BLOCK,plugin_contexts,2)
                     != (BUBBLES_START));
                bb->startedshot += *rb->current_tick-start;
                bubbles_drawboard(bb);
                rb->lcd_update();
                break;
    
            case BUBBLES_RESUME: /* save and end the game */
                if(!animblock) {
                    rb->splash(HZ/2, "Saving game...");
                    bubbles_savegame(bb);
                    return BB_END;
                }
                break;
            case BUBBLES_QUIT:   /* end the game */
                return BB_END;
    
            case ACTION_UNKNOWN:
            case ACTION_NONE:    /* no button pressed */
                break;
    
            default:
                if(rb->default_event_handler_ex(button, bubbles_callback,
                   (void*) bb) == SYS_USB_CONNECTED)
                    return BB_USB;
                break;
        }
    
        return BB_NONE;
    }
    
    /*****************************************************************************
    * bubbles() is the main game subroutine, it returns the final game status.
    ******************************************************************************/
    static int bubbles(struct game_context* bb) {
        int buttonres;
        unsigned int startlevel = 0;
        bool startgame = false;
        long timeout;
    
        /* don't resume by default */
        bb->resume = false;
    
        /********************
        *       menu        *
        ********************/
        MENUITEM_STRINGLIST(menu,"Bubbles Menu",NULL,
                            "Start New Game", "Resume Game",
                            "Level", "High Scores", "Playback Control",
                            "Quit");
        while(!startgame){
            switch (rb->do_menu(&menu, NULL, NULL, false))
            {
                case 0: /* new game */
                    bb->level = startlevel;
                    startgame = true;
                    break;
                case 1: /* resume game */
                    if(!bubbles_loadgame(bb)) {
                        rb->splash(HZ*2, "Nothing to resume");
                    } else {
                        startgame = true;
                    }
                    break;
                case 2: /* choose level */
                    startlevel++;
                    rb->set_int("Choose start level", "", UNIT_INT, &startlevel,
                                NULL, 1, 1, bb->highlevel.level+1, NULL);
                    startlevel--;
                    break;
                case 3: /* High scores */
                    highscore_show(NUM_SCORES, bb->highscores, NUM_SCORES);
                    break;
                case 4: /* Playback Control */
                    playback_control(NULL);
                    break;
                case 5: /* quit */
                    return BB_QUIT;
                case MENU_ATTACHED_USB:
                    bubbles_callback(bb);
                    return BB_USB;
            }
        }
        /********************
        *       init        *
        ********************/
        bubbles_init(bb);
        bubbles_drawboard(bb);
        rb->lcd_update();
    
        /**********************
        *        play         *
        **********************/
        bb->startedshot = *rb->current_tick;
    
        while(true) {
            /* refresh the board */
            bubbles_drawboard(bb);
            rb->lcd_update();
    
            /* manange idle framerate */
            bb->elapsedshot = *rb->current_tick-bb->startedshot;
    
            if(MAX_SHOTTIME-bb->elapsedshot < HZ/2) {
                timeout = MAX_SHOTTIME-bb->elapsedshot;
            } else {
                timeout = HZ/2;
            }
    
            /* handle button events */
            buttonres = bubbles_handlebuttons(bb, false, timeout);
            if(buttonres != BB_NONE) return buttonres;
    
            /* handle timing */
            bb->elapsedshot = *rb->current_tick-bb->startedshot;
    
            if(bb->elapsedshot > MAX_SHOTTIME) {
                bb->elapsedlvl += bb->elapsedshot;
                bb->elapsedshot = 0;
                buttonres = bubbles_fire(bb);
                if(buttonres != BB_NONE) return buttonres;
                buttonres = bubbles_checklevel(bb);
                if(buttonres != BB_NONE) return buttonres;
                bb->startedshot = *rb->current_tick;
            }
        }
    }
    
    /*****************************************************************************
    * plugin entry point.
    ******************************************************************************/
    enum plugin_status plugin_start(const void* parameter) {
        struct game_context bb;
        bool exit = false;
    
        /* plugin init */
        (void)parameter;
        /* end of plugin init */
    
        /* load files */
        bubbles_loadscores(&bb);
        rb->lcd_clear_display();
    
        /* start app */
    #if LCD_DEPTH > 1
        rb->lcd_set_backdrop(NULL);
    #endif
        rb->lcd_setfont(FONT_SYSFIXED);
    
        while(!exit) {
            switch(bubbles(&bb)){
                case BB_WIN:
                    rb->splash(HZ*2, "You Win!");
                    /* record high level */
                    if( NUM_LEVELS-1 > bb.highlevel.level) {
                        bb.highlevel.level = NUM_LEVELS-1;
                    }
                    /* record high score */
                    bubbles_recordscore(&bb);
                    break;
    
                case BB_LOSE:
                    rb->splash(HZ*2, "Game Over");
                    /* fall through to BB_END */
    
                case BB_END:
                    if(!bb.resume) {
                        /* record high level */
                        if((int)bb.level-1 > bb.highlevel.level) {
                            bb.highlevel.score = -1;
                            highscore_update(0, bb.level-1, "", &bb.highlevel, 1);
                        }
                        /* record high score */
                        bubbles_recordscore(&bb);
                    }
                    break;
    
                case BB_USB:
                    rb->lcd_setfont(FONT_UI);
                    return PLUGIN_USB_CONNECTED;
    
                case BB_QUIT:
                    bubbles_savescores(&bb);
                    exit = true;
                    break;
    
                default:
                    break;
            }
        }
    
        rb->lcd_setfont(FONT_UI);
        return PLUGIN_OK;
    }
    
    #endif