/* A game chooser for borgs * by: Christian Kroll * date: Thursday, 2008/03/16 */ #include #include #include // architecture dependent stuff #include "../compat/pgmspace.h" #include "menu.h" #include "../config.h" #include "../util.h" #include "../pixel.h" #include "../joystick.h" extern game_descriptor_t _game_descriptors_start__[]; extern game_descriptor_t _game_descriptors_end__[]; // defines #define MENU_ITEM_MAX (((int)_game_descriptors_end__ - (int)_game_descriptors_start__)/sizeof(game_descriptor_t)) #define MENU_WIDTH_ICON 8 #define MENU_HEIGHT_ICON 8 #define MENU_WIDTH_DELIMITER 2 #define MENU_POLL_INTERVAL 10 #define MENU_TIMEOUT_ITERATIONS 2000 #define MENU_WAIT_CHATTER 60 #define MENU_WAIT_INITIAL 40 #define MENU_WAIT_INCREMENT 0 #define MENU_NEXTITEM(item) ((item + 1) % MENU_ITEM_MAX) #define MENU_PREVITEM(item) ((item + MENU_ITEM_MAX - 1) % MENU_ITEM_MAX) void menu() { // don't let WAIT() query fire button to prevent endless circular jumps waitForFire = 0; clear_screen(0); // wait as long the fire button is pressed to prevent unwanted selections while (JOYISFIRE) { wait(MENU_POLL_INTERVAL); } // set initial menu item static uint8_t miSelection = 0; // scroll in currently selected menu item menu_animate(MENU_PREVITEM(miSelection), MENU_DIRECTION_LEFT); uint16_t nMenuIterations= MENU_TIMEOUT_ITERATIONS; while (1) { // the user has made her/his choice if (JOYISFIRE) { // prevent unwanted selections while (JOYISFIRE) { wait(MENU_POLL_INTERVAL); } // work against the chatter effects of dump joysticks wait(MENU_WAIT_CHATTER); // call corresponding function _game_descriptors_start__[miSelection].run(); break; } // change selected item and do some scrolling else if (JOYISRIGHT) { menu_animate(miSelection, MENU_DIRECTION_LEFT); miSelection = MENU_NEXTITEM(miSelection); nMenuIterations = MENU_TIMEOUT_ITERATIONS; } else if (JOYISLEFT) { menu_animate(miSelection, MENU_DIRECTION_RIGHT); miSelection = MENU_PREVITEM(miSelection); nMenuIterations = MENU_TIMEOUT_ITERATIONS; } // exit menu else if (JOYISUP) { break; } // return if timeout is reached else { wait(MENU_POLL_INTERVAL); if (--nMenuIterations == 0) break; } } waitForFire = 1; return; } uint8_t menu_getIconPixel(uint8_t item, int8_t x, int8_t y) { /* // MSB is leftmost pixel static uint8_t nIcon[][8] PROGMEM = {{0xff, 0x81, 0xbd, 0xa5, 0xa5, 0xad, 0xa1, 0xbf}, // Snake icon {0x66, 0x18, 0x3c, 0x5a, 0xff, 0xbd, 0xa5, 0x18}, // Invaders icon {0x0f, 0x0f, 0xc3, 0xdb, 0xdb, 0xc3, 0xf0, 0xf0}}; // Tetris icon */ // is x within the icon or do we have reached the delimiter? if (x < MENU_WIDTH_ICON) { // return pixel return (0x80 >> x) & pgm_read_word(&_game_descriptors_start__[item].icon[y]); } else { // delimiter return 0; } } void menu_animate(uint8_t miInitial, menu_direction_t direction) { int16_t nWait= MENU_WAIT_INITIAL; // space between left border and the icon in the middle int8_t nWidthSide = (NUM_COLS - MENU_WIDTH_ICON) / 2; // determine the icon at the leftmost position uint8_t mi = miInitial + MENU_ITEM_MAX; int8_t nBack = nWidthSide / (MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER); if ((nWidthSide % (MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER)) != 0) { ++nBack; } mi = (mi + MENU_ITEM_MAX - (nBack % MENU_ITEM_MAX)) % MENU_ITEM_MAX; // start and stop offsets for the scrolling icons (both are 0 for stills) int8_t nStart, nStop; if (direction == MENU_DIRECTION_STILL) { nStart = 0; nStop = 0; } else { nStart = 1; nStop = MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER; } // draw menu screen for each offset within the nStart/nStop range int8_t i; for (i = nStart; i <= nStop; ++i) { int8_t nOffset; if (direction == MENU_DIRECTION_LEFT) nOffset = i; else nOffset = -i; // offset of the left most icon if it is cut by the left border int8_t nInitialSideOffset = (((MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER) - (nWidthSide % (MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER))) + nOffset + (MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER)) % (MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER); // an initial side offset of 0 means the leftmost icon was changed // if we are scrolling to the left, increment value for leftmost item if (direction == MENU_DIRECTION_LEFT) { if (nInitialSideOffset == 0) { mi = MENU_NEXTITEM(mi); } } // draw the icons from the leftmost position (line by line) int8_t y; for (y = 0; y < MENU_HEIGHT_ICON; ++y) { uint8_t miCurrent = mi; int8_t nIconOffset = nInitialSideOffset; int8_t x; for (x = 0; x < NUM_COLS; ++x) { int8_t nPixel = menu_getIconPixel(miCurrent, nIconOffset, y); menu_setpixel(x, ((NUM_ROWS - MENU_HEIGHT_ICON) / 2) + y, nPixel); if (++nIconOffset >= (MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER)) { nIconOffset = 0; miCurrent = MENU_NEXTITEM(miCurrent); } } } // an initial side offset of 0 means the leftmost icon was changed // if we are scrolling to the right, decrement value for leftmost item if (direction == MENU_DIRECTION_RIGHT) { if (nInitialSideOffset == 0) { mi = MENU_PREVITEM(mi); } } // wait between the frames so that the animation can be seen wait(nWait); // animation speed can be throtteled nWait += MENU_WAIT_INCREMENT; } } void menu_setpixel(int8_t x, int8_t y, int8_t isSet) { uint8_t nColor; // mirror mirror on the wall, what's the quirkiest API of them all... x = NUM_COLS - 1 - x; uint8_t nMiddle = (NUM_COLS - MENU_WIDTH_ICON) / 2; if (isSet != 0) { if ((x >= nMiddle - MENU_WIDTH_DELIMITER) && (x < (nMiddle + MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER))) { nColor = 3; } else if ((x == (nMiddle - MENU_WIDTH_DELIMITER - 1)) || (x == (nMiddle + MENU_WIDTH_ICON + MENU_WIDTH_DELIMITER))) { nColor = 2; } else { nColor = 1; } } else { nColor = 0; } setpixel((pixel){x, y}, nColor); }