diff --git a/firmware/applications/awake.c b/firmware/applications/awake.c new file mode 100644 index 0000000..b30ffee --- /dev/null +++ b/firmware/applications/awake.c @@ -0,0 +1,212 @@ +#include +#include +#include "basic/basic.h" +#include "funk/nrf24l01p.h" +//#include "usetable.h" + + +#define AWAKE_PACKET_RETRIES 20 +#define AWAKE_MIN_WINNERS 3 +#define AWAKE_FONT_HEIGHT 8 + + +typedef enum packet_type_e { + PCKT_WINNER0, + PCKT_WINNER1, + PCKT_WINNER2, + PCKT_WINNER3, + PCKT_WINNER4, + PCKT_WINNER5, + PCKT_WINNER6, + PCKT_WINNER7, + PCKT_WAKEUP, + PCKT_STANDBY, + PCKT_NONE +} packet_type_t; + + +static char const *const awake_gPackets[] = { + "winner0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner7\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "wake up\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "standby\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +}; + + +/** + * Initializes wireless stuff. + */ +static void awake_initNrf(void) { + nrf_init(); + static struct NRF_CFG config = { + .channel = 81, + .txmac = "\x1\x2\x3\x2\x1", + .nrmacs = 1, + .mac0 = "\x1\x2\x3\x2\x1", + .maclen = "\x20" + }; + nrf_config_set(&config); + nrf_set_strength(3); +} + + +/** + * Polls for a special packet which indicates the success of a player. + */ +static packet_type_t awake_waitForPacket(void) { + uint8_t buffer[32]; + if (nrf_rcv_pkt_time(100, 32, buffer) == 32) { + for (packet_type_t p = PCKT_WINNER0; p < PCKT_NONE; ++p) { + unsigned int bEqual = 1; + for (unsigned int i = 0; i < 8; ++i) { + if (buffer[i] != awake_gPackets[p][i]) { + bEqual = 0; + break; + } + } + if (bEqual) { + return p; + } + } + } + return PCKT_NONE; +} + + +/** + * Waits for either pushed buttons or a signal on the hacker bus. + */ +static void awake_waitForKeyPressOrHackerbus(void) { + gpioSetDir(RB_HB0, gpioDirection_Input); + // watch out for pushed buttons and/or hacker bus activity + while (gpioGetValue(RB_HB0) != 0) + { + if (getInput() != BTN_NONE) + { + getInputWaitRelease(); + break; + } + } + while (gpioGetValue(RB_HB0) == 0); +} + + +/** + * The greeter! + */ +static void awake_promptUserBegin(void) { + gpioSetDir(RB_HB0, gpioDirection_Input); + + lcdFill(0); + DoString(0, 0, "Eine Taste"); + DoString(0, 8, "druecken um"); + DoString(0,16, "das Spiel"); + DoString(0,24, "zu starten!"); + lcdDisplay(); + + awake_waitForKeyPressOrHackerbus(); + + lcdFill(0); + DoString(0, 0, "Spiel laeuft!"); + DoString(0, 16, "FEUERTASTE"); + DoString(0, 24, "fuer Standby"); + lcdDisplay(); +} + + +/** + * Informs about the happy end! + */ +static void awake_promptUserEnd(void){ + lcdFill(0); + DoString(0, 0, "Es wurden ge-"); + DoString(0, 8, "nug Gewinner "); + DoString(0, 16, "ermittelt! "); + lcdDisplay(); + + // toggle RB_HB1 pin from 0V to 3,3 for 200ms + GPIO_GPIO0DATA |= (1u << 10); + delayms_queue(200); + GPIO_GPIO0DATA &= ~(1u << 10); + + awake_waitForKeyPressOrHackerbus(); +} + + +/** + * Explains a brief moment of unresponsiveness. + */ +static void awake_promptStandby(void){ + lcdFill(0); + DoString(0, 0, "Bitte warten!"); + DoString(0, 8, "Sende Standby"); + DoString(0, 16, "Pakete... "); + lcdDisplay(); +} + + +void awake_initHackerBus() { + // set RB_HB1 to GPIO mode (output) + #define IOCON_PIO0_10 (*((REG32*) (0x40044068))) + IOCON_PIO0_10 &= ~(00000007); + IOCON_PIO0_10 |= 0x00000001; + GPIO_GPIO0DIR |= (1 << 10); + GPIO_GPIO0DATA &= ~(1 << 10); +} + +/** + * Main function of the l0dable. + */ +void main_awake(void) { + awake_initNrf(); + awake_initHackerBus(); + + while (1) { + awake_promptUserBegin(); + uint8_t joyinput = BTN_NONE; + unsigned int nWinnerFlags = 0, nWinnerCount = 0; + while ((joyinput != BTN_ENTER) && (nWinnerCount < AWAKE_MIN_WINNERS)) { + if ((joyinput = getInput()) != BTN_NONE) + { + getInputWaitRelease(); + } + + // send a "wake up" packet every loop cycle so that in case a r0ket + // is rebooted, the player can continue the game + uint8_t packet[32]; + memcpy(packet, awake_gPackets[PCKT_WAKEUP], 32); + delayms(10); + nrf_snd_pkt_crc(32, packet); + + // watch out for winners! + packet_type_t const ePacket = awake_waitForPacket(); + unsigned int const nWinnerMask = (1 << ePacket) & 0xFF; + if ((ePacket <= PCKT_WINNER7) && !(nWinnerFlags & nWinnerMask)) { + nWinnerFlags |= nWinnerMask; + ++nWinnerCount; + DoIntX(0, 32, nWinnerMask); + lcdDisplay(); + } + } + + if (nWinnerCount >= AWAKE_MIN_WINNERS) { + awake_promptUserEnd(); + } + + awake_promptStandby(); + uint8_t packet[32]; + for (int i = 0; i < AWAKE_PACKET_RETRIES; ++i) { + delayms_queue(50); + memcpy(packet, awake_gPackets[PCKT_STANDBY], 32); + nrf_snd_pkt_crc(32, packet); + } + } + + return; +} diff --git a/firmware/applications/poem.c b/firmware/applications/poem.c new file mode 100644 index 0000000..cbdf686 --- /dev/null +++ b/firmware/applications/poem.c @@ -0,0 +1,649 @@ +#include +#include + +#include "basic/basic.h" +#include "basic/random.h" +#include "basic/config.h" +#include "lcd/render.h" +#include "lcd/backlight.h" +#include "lcd/allfonts.h" +#include "funk/nrf24l01p.h" + + +#define POEM_ID 4 + +#define BTN_WAKEUP (1 << 5) +#define BTN_STANDBY (1 << 6) + +#define POEM_FONT_WIDTH 5 +#define POEM_FONT_HEIGHT 8 +#define POEM_FONT Font_5x8 + +#define POEM_COUNT 5u +#define POEM_VERSE_COUNT 4u +#define POEM_MAX_LINE_COUNT (RESY / POEM_FONT_HEIGHT) +#define POEM_LINE_LIMIT ((RESX - 4) / POEM_FONT_WIDTH) + + +#define POEM_PACKET_RETRIES 10 + +typedef enum packet_type_e { + PCKT_WINNER0, + PCKT_WINNER1, + PCKT_WINNER2, + PCKT_WINNER3, + PCKT_WINNER4, + PCKT_WINNER5, + PCKT_WINNER6, + PCKT_WINNER7, + PCKT_WAKEUP, + PCKT_STANDBY, + PCKT_NONE +} packet_type_t; + + +static char const *const poem_gPackets[10] = { + "winner0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "winner7\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "wake up\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + "standby\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" +}; + + +typedef enum poem_state_e { + POEM_STATE_STANDBY, + POEM_STATE_USAGE, + POEM_STATE_PLAYING, + POEM_STATE_WON +} poem_state_t; + + +typedef struct poem_break_marker_s { + unsigned int nStart; + unsigned int nStop; +} poem_break_marker_t; + + +typedef struct poem_s { + char const * const pszVerses[POEM_VERSE_COUNT]; + unsigned int nLineCounts[POEM_VERSE_COUNT]; + unsigned int nPermut[POEM_VERSE_COUNT]; + poem_break_marker_t pszMarkers[POEM_VERSE_COUNT][POEM_MAX_LINE_COUNT]; +} poem_t; + + +static poem_t g_poem_aPoems[POEM_COUNT]= + { +#if POEM_ID == 0 + // Goethe - An den Mond, 3. Vers + {{"Jeden Nachklang fuehlt mein Herz", + "Froh und trueber Zeit,", + "Wandle zwischen Freud' und Schmerz", + "In der Einsamkeit."}}, + + // Goethe - Gefunden, 3. Vers + {{"Ich wollt es brechen,", + "Da sagt es fein:", + "Soll ich zum Welken", + "Gebrochen sein?"}}, + + // Grillparzer - In der Fremde, 2. Vers + {{"So willst Du denn nach Hause?", + "O nein! Nur nicht nach Haus!", + "Dort stirbt des Lebens Leben", + "Im Einerlei mir aus."}}, + + // Friedrich Schiller - Pilgrim, 1. Vers + {{"Noch in meines Lebens Lenze", + "War ich, und ich wandert' aus,", + "Und der Jugend frohe Taenze", + "Liess ich in des Vaters Haus."}}, + + // Frank Wedekind - Erdgeist, 2. Vers + {{"Meide nicht die ird'schen Schaetze:", + "Wo sie liegen, nimm sie mit.", + "Hat die Welt doch nur Gesetze,", + "Dass man sie mit Fuessen tritt."}}, +#elif POEM_ID == 1 + // Clemens Brentano - Loreley + {{"Zu Bacharach am Rheine", + "Wohnt eine Zauberin,", + "Die war so schoen und feine", + "Und riss viel Herzen hin."}}, + + // Wilhelm Busch - Frueher, da ich unerfahren + {{"Frueher, da ich unerfahren", + "Und bescheidner war als heute,", + "Hatten meine hoechste Achtung", + "Andre Leute."}}, + + // Heinz Erhardt - Warum die Zitronen sauer wurden + {{"Bis sie einst sprachen: 'Wir Zitronen,", + "wir wollen gross sein wie Melonen!", + "Auch finden wir das Gelb abscheulich,", + "wir wollen rot sein oder blaeulich!'"}}, + + // Joseph von Eichendorff - Mondnacht + {{"Die Luft ging durch die Felder,", + "Die Aehren wogten sacht,", + "Es rauschten leis' die Waelder,", + "So sternklar war die Nacht."}}, + + // Theodor Fontane - Trost + {{"Harre, hoffe. Nicht vergebens", + "zaehlest du der Stunden Schlag:", + "Wechsel ist das Los des Lebens,", + "Und - es kommt ein andrer Tag."}}, +#elif POEM_ID == 2 + // Goethe - Der Zauberlehrling + {{"Ach, da kommt der Meister!", + "Herr, die Not ist gross!", + "Die ich rief, die Geister,", + "Werd ich nun nicht los."}}, + + // Schiller - Das Lied von der Glocke + {{"Fest gemauert in der Erden", + "Steht die Form, aus Lehm gebrannt.", + "Heute muss die Glocke werden!", + "Frisch, Gesellen, seid zur Hand!"}}, + + // Hermann Hesse - Im Nebel + {{"Voll von Freunden war mir die Welt", + "Als noch mein Leben licht war;", + "Nun, da der Nebel faellt,", + "Ist keiner mehr sichtbar."}}, + + // irgend ein Kinderlied + {{"Schoen ist der Zylinderhut", + "Wenn man ihn besitzen tut", + "Doch von ganz besondrer Guete", + "Sind stets zwei Zylinderhuete"}}, + + // Conrad Ferdinand Meyer - Alles war ein Spiel + {{"In diesen Liedern suche du", + "Nach keinem ernsten Ziel!", + "Ein wenig Schmerz, ein wenig Lust,", + "Und alles war ein Spiel."}}, +#elif POEM_ID == 3 + // Friedrich Nietzsche - Vereinsamt + {{"Die Kraehen schrein", + "Und ziehen schwirren Flugs zur Stadt:", + "Bald wird es schnein, -", + "Wohl dem, der jetzt noch - Heimat hat!"}}, + + // Christian Morgenstern - Der Werwolf + {{"Dem Werwolf schmeichelten die Faelle,", + "er rollte seine Augenbaelle.", + "Indessen, bat er, fuege doch", + "zur Einzahl auch die Mehrzahl noch!"}}, + + // Wilhelm Busch - Dummheit, die man bei den anderen sieht + {{"Wenn andere klueger sind als wir,", + "Das macht uns selten nur Plaesier,", + "Doch die Gewissheit, dass sie duemmer,", + "Erfreut fast immer."}}, + + // August von Kotzebue - Gesellschaftslied, 2. Vers + {{"Wir sitzen so froehlich beisammen", + "Wir haben uns alle so lieb,", + "Wir heitern einander das Leben,", + "Ach wenn es doch immer so blieb'!"}}, + + // Gotthold Ephraim Lessing - Antwort eines trunknen Dichters, 1. Vers + {{"Ein trunkner Dichter leerte", + "Sein Glas auf jeden Zug;", + "Ihn warnte sein Gefaehrte:", + "Hoer' auf! du hast genug."}}, +#elif POEM_ID == 4 + // Hermann von Lingg - Das Krokodil, 1. Vers + {{"Im heil'gen Teich zu Singapur,", + "Da liegt ein altes Krokodil", + "Von aeusserst graemlicher Natur", + "Und kaut an einem Lotosstiel."}}, + + // Hermann Loens - Wegewarte, 2. Vers + {{"Ich stand an dem Wege,", + "Hielt auf meine Hand,", + "Du hast Deine Augen", + "Von mir abgewandt."}}, + + // Christian Morgenstern - An meine Taschenuhr, 1. und einziger Vers + {{"Du schlimme Uhr, du gehst mir viel zu schnell;", + "und doch - dich schauend, sah ich selber hell.", + "Unschuldig Raederwerk, was schalt ich dich?", + "Ich geh zu langsam, ach zu langsam - ich."}}, + + // Eduard Moerike - Jaegerlied, 2.Vers + {{"In die Luefte hoch der Reiher steigt,", + "dahin weder Pfeil noch Kugel fleugt:", + "Tausendmal so hoch und so geschwind", + "die Gedanken treuer Liebe sind."}}, + + // Erich Muehsam - Liebesweh, 3. Vers + {{"Ach, es ist der Traum der Liebe,", + "den ich durch die Seele siebe.", + "Ach, es ist der Liebe Weh,", + "das mich zwickt vom Kopf zur Zeh."}}, +#elif POEM_ID == 5 + // Wilhelm Mueller - Der Glockenguss zu Breslau, 6. Vers + {{"Wie hat der gute Meister", + "So treu das Werk bedacht!", + "Wie hat er seine Haende", + "Geruehrt bei Tag und Nacht!"}}, + + // Ludwig Pfau - Der Geiger von Oppenau, 2. Vers + {{"Wo seine Fiedel geklungen,", + "Da konnte kein Fuss mehr stehn,", + "Da sprangen die Alten und Jungen,", + "Die Stube fing an zu drehn."}}, + + // Robert Reinick - Der Faule, 2. Vers + {{"Doch die Zeit wird lang mir werden,", + "Und wie bring' ich sie herum?", + "Spitz! komm her! dich will ich lehren", + "Hund, du bist mir viel zu dumm!"}}, + + // Rainer Maria Rilke - Herbsttag, 2. Vers + {{"Befiehl den letzten Fruechten voll zu sein;", + "gib ihnen noch zwei suedlichere Tage,", + "draenge sie zur Vollendung hin und jage", + "die letzte Suesse in den schweren Wein."}}, + + // Joachim Ringelnatz - Ehrgeiz + {{"Ich habe meinen Soldaten aus Blei", + "Als Kind Verdienstkreuzchen eingeritzt.", + "Mir selber ging alle Ehre vorbei,", + "Bis auf zwei Orden, die jeder besitzt."}} +#endif + }; + + +/** + * Prepares markers for line wrapping and determines the number of lines + * required to display a verse. + */ +static void poem_prepareLineWrappingMarkers(void) { + for (unsigned int iPoem = 0; iPoem < POEM_COUNT; ++iPoem) { + for (unsigned int iVerse = 0; iVerse < POEM_VERSE_COUNT; ++iVerse) { + char const * const psz = g_poem_aPoems[iPoem].pszVerses[iVerse]; + size_t const nLen = strlen(psz); + unsigned int nPos = 0, nCharCnt = 0, nLineCount = 0; + int nLastSpc = -1; + + poem_break_marker_t *pMarker = + &g_poem_aPoems[iPoem].pszMarkers[iVerse][nLineCount]; + while ((nPos < nLen) && (nLineCount < POEM_MAX_LINE_COUNT)) { + if (nCharCnt < POEM_LINE_LIMIT && (nPos != (nLen - 1))) { + if (psz[nPos] == ' ') { + nLastSpc = nPos; + } + ++nCharCnt; + } else { + pMarker->nStart = nPos - nCharCnt; + if (nPos == (nLen - 1)) { + pMarker->nStop = nPos; + } else if (nLastSpc < (nPos - nCharCnt)) { + pMarker->nStop = nPos - 1; + } else { + pMarker->nStop = nLastSpc; + nPos = nLastSpc; + } + pMarker = &g_poem_aPoems[iPoem].pszMarkers[iVerse][++nLineCount]; + nCharCnt = 0; + } + ++nPos; + } + g_poem_aPoems[iPoem].nLineCounts[iVerse] = nLineCount; + } + } +} + + +/** + * Prints a physical line (that is one row of displayed characters) from a given + * poem and verse. + * @param pPoem The poem where to look for that line. + * @param nVerse The verse where the to be printed line resides. + * @param nLine The position (counting from 0) of that line within that verse. + * @param y The vertical offset where text should be rendered. + */ +static void poem_printLine(poem_t const *const pPoem, unsigned int nVerse, + unsigned int nLine, int y) { + char pszLine[POEM_LINE_LIMIT + 1] = { 0 }; + poem_break_marker_t const * const pMarker = + &pPoem->pszMarkers[nVerse][nLine]; + for (unsigned int k = pMarker->nStart; k <= pMarker->nStop; ++k) { + pszLine[k - pMarker->nStart] = pPoem->pszVerses[nVerse][k]; + } + DoString(2, y, pszLine); +} + + +/** + * Shuffles the verses of a given poem. + * @param pPoem The poem whose verses need to be shuffled. + */ +static void poem_initPermutation(poem_t *const pPoem) { + for (unsigned int i = POEM_VERSE_COUNT; i--;) { + pPoem->nPermut[i] = i; + } + while ((pPoem->nPermut[0] < pPoem->nPermut[1]) + && (pPoem->nPermut[1] < pPoem->nPermut[2]) + && (pPoem->nPermut[2] < pPoem->nPermut[3])) { + for (unsigned int i = 4; i--;) { + unsigned int const nIndexA = getRandom() % POEM_VERSE_COUNT; + unsigned int const nIndexB = getRandom() % POEM_VERSE_COUNT; + + unsigned int nSwap = pPoem->nPermut[nIndexA]; + pPoem->nPermut[nIndexA] = pPoem->nPermut[nIndexB]; + pPoem->nPermut[nIndexB] = nSwap; + } + } +} + +uint8_t poem_identifyPacket(uint8_t *packet){ + uint8_t input = BTN_NONE; + if (strncmp((char *)packet, poem_gPackets[PCKT_STANDBY], 8) == 0) { + input = BTN_STANDBY; + } else if (strncmp((char *)packet, poem_gPackets[PCKT_WAKEUP], 8) == 0) { + input = BTN_WAKEUP; + } + return input; +} + + +/** + * Watches for both joystick movements and standby NRF packets. It's blocks the + * control flow as long as there are neither appropriate joystick activities nor + * standby/wake up packets received. + * @param filter A mask of events to which this function should react. + * @return One of BTN_(UP|DOWN|LEFT|RIGHT|ENTER|STANDBY|WAKEUP). + */ +uint8_t poem_getInputBlocking(uint8_t filter) { + uint8_t pkt[32]; + uint8_t input = BTN_NONE; + nrf_rcv_pkt_start(); + do { + if ((nrf_rcv_pkt_poll(sizeof(pkt), pkt) != 32) || + !((input = poem_identifyPacket(pkt)) & filter)) { + input = getInput(); + } + } while (!(filter & input)); + nrf_rcv_pkt_end(); + return input; +} + + +/** + * Watches for both joystick movements and standby NRF packets. It returns + * BTN_NONE if there's neither a NRF packet nor joystick input. + * @return One of BTN_(NONE|UP|DOWN|LEFT|RIGHT|ENTER|STANDBY|WAKEUP). + */ +uint8_t poem_getInputNonBlocking(void) { + uint8_t pkt[32]; + uint8_t input; + if (nrf_rcv_pkt_poll(sizeof(pkt), pkt) == 32) { + input = poem_identifyPacket(pkt); + if (input == BTN_NONE) + input == getInput(); + } + else { + input = getInput(); + } + return input; +} + + +/** + * Displays a small description of the game. + */ +static void poem_printUsage(void) { + // show usage + int dy = 0; + lcdFill(0); + DoString(0, dy, "Sortiere die Verse "); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "in die richtige "); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "Reihenfolge! Der "); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "Knopf ist ein Joy- "); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "stick. Waehle einen"); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "Vers mit OBEN oder "); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "UNTEN. Verschiebe "); dy += POEM_FONT_HEIGHT; + DoString(0, dy, "ihn per FEUERTASTE."); dy += POEM_FONT_HEIGHT; + lcdDisplay(); +} + + +/** + * Initializes the wireless stuff. + */ +static void poem_initRadio(void) { + nrf_init(); + static struct NRF_CFG config = { + .channel = 81, + .txmac = "\x1\x2\x3\x2\x1", + .nrmacs = 1, + .mac0 = "\x1\x2\x3\x2\x1", + .maclen = "\x20", + }; + nrf_config_set(&config); + delayms(50); +} + + +/** + * Turns on the backlight and switches to dark text on a light background. + */ +static void poem_turnOnBacklight(void) { + GLOBAL(daytrig) = 255; + GLOBAL(daytrighyst) = 50; + GLOBAL(dayinvert) = 0; + GLOBAL(lcdinvert) = 1; + backlightSetBrightness(100); + lcdSetInvert(1); + lcdFill(0); + lcdDisplay(); +} + + +/** + * Turns off the backlight and switches to light text on a dark background. + */ +static void poem_turnOffBacklight(void) { + GLOBAL(daytrig) = 0; + GLOBAL(daytrighyst) = 50; + GLOBAL(dayinvert) = 0; + GLOBAL(lcdinvert) = 0; + backlightSetBrightness(0); + lcdSetInvert(0); + lcdFill(0); + lcdDisplay(); +} + + +/** + * Highlights the specified item by drawing a bounding box around it (to + * indicate a selection) or by inverting its representation (dragging). + * @param bIsDragging Whether the item is just selected or about to be dragged. + * @param nStartY The (absolute) start offset (in pixels) of the item. + * @param nStopY The (absolute) stop offset (in pixels) of the item. + * @param nScroll The vertical scrolling offset. + */ +static void poem_hightlightSelectedItem(unsigned int const bIsDragging, + int nStartY, int const nStopY, int const nScroll) { + // invert or draw a bounding box around the selected verse + if (bIsDragging) { + // dragging mode: invert the to be dragged item + for (int y = nStartY; y < nStopY; ++y) { + for (int x = 0; x < RESX; ++x) { + lcdSetPixel(x, y - nScroll, !lcdGetPixel(x, y - nScroll)); + } + } + } else { + // selection mode: draw a border around the selected item + for (int x = 0; x < RESX; ++x) { + lcdSetPixel(x, nStartY - nScroll, 1); + lcdSetPixel(x, nStopY - nScroll, 1); + } + for (int y = nStartY; y < nStopY; ++y) { + lcdSetPixel(0, y - nScroll, 1); + lcdSetPixel(RESX - 1, y - nScroll, 1); + } + } +} + + +/** + * Informs the master device that the player has accomplished the task and + * blocks as long as there are no "standby" or "wakeup" packets. + */ +static void poem_sendSuccessMessage(packet_type_t pktWinner) { + // send our winner information packet + static uint8_t pkt[32]; + + nrf_rcv_pkt_start(); + while (1) { + memcpy(pkt, poem_gPackets[pktWinner], sizeof(pkt)); + nrf_snd_pkt_crc(sizeof(pkt), pkt); + delayms_queue(80 + (getRandom() % 50)); + uint8_t input = poem_getInputNonBlocking(); + if (input == BTN_STANDBY) { + break; + } + } + nrf_rcv_pkt_end(); +} + + +/** + * Starts a game where the user has to sort verses of a poem into the right + * order. + */ +void main_poem(void) { + packet_type_t pktWinner = POEM_ID; + + // display initialization + font = &POEM_FONT; + + // init wireless stuff + poem_initRadio(); + poem_prepareLineWrappingMarkers(); + + poem_state_t state = POEM_STATE_STANDBY; + + while (1) { + switch (state) { + case POEM_STATE_STANDBY: + poem_turnOffBacklight(); + lcdFill(0); + DoInt(0, 0, pktWinner); + lcdDisplay(); + poem_getInputBlocking(BTN_WAKEUP); + state = POEM_STATE_USAGE; + break; + + case POEM_STATE_USAGE: + poem_turnOnBacklight(); + poem_printUsage(); + { + uint8_t input = poem_getInputBlocking(BTN_UP | BTN_DOWN | + BTN_LEFT | BTN_RIGHT | BTN_ENTER | BTN_STANDBY); + if (input == BTN_STANDBY) { + state = POEM_STATE_STANDBY; + } + else if (input != BTN_WAKEUP) { + state = POEM_STATE_PLAYING; + } + } + DoString(20,0, "bla"); + lcdDisplay(); + break; + + case POEM_STATE_PLAYING: + { + // print the verses + poem_t *const pPoem = &g_poem_aPoems[getRandom() % POEM_COUNT]; + poem_initPermutation(pPoem); + unsigned int nSelectedVerse = 0, nNextSelected = 0; + unsigned int bIsDragging = 0; + while (state == POEM_STATE_PLAYING) { + // Calculate position and size of the bounding box of the selected + // verse. Also, calculate a vertical scrolling offset to ensure + // that the selected verse is visible. + int nStartY = 0; + for (unsigned int i = 0; i < nSelectedVerse; ++i) { + unsigned int index = pPoem->nPermut[i]; + nStartY += POEM_FONT_HEIGHT * pPoem->nLineCounts[index] + 3; + } + const int nStopY = 2 + nStartY + (POEM_FONT_HEIGHT + * pPoem->nLineCounts[pPoem->nPermut[nSelectedVerse]]); + int nScroll = nStopY >= RESY ? nStopY - RESY + 1 : 0; + // display verses + lcdFill(0); + int dy = 2; + for (unsigned int i = 0; i < POEM_VERSE_COUNT; ++i) { + unsigned int const index = pPoem->nPermut[i]; + for (unsigned int j = 0; j < pPoem->nLineCounts[index]; ++j) { + poem_printLine(pPoem, index, j, dy - nScroll); + dy += POEM_FONT_HEIGHT; + } + dy += 3; + } + // draw a bounding box around the selected verse (or invert it) + poem_hightlightSelectedItem(bIsDragging, nStartY, nStopY, nScroll); + // flush display buffer + lcdDisplay(); + + // both query joystick and listen for "standby" packets; + switch (poem_getInputBlocking(BTN_UP | BTN_DOWN | + BTN_ENTER | BTN_STANDBY)) { + case BTN_ENTER: + bIsDragging = !bIsDragging; + if (!bIsDragging && pPoem->nPermut[0] < pPoem->nPermut[1] + && pPoem->nPermut[1] < pPoem->nPermut[2] + && pPoem->nPermut[2] < pPoem->nPermut[3]) { + state = POEM_STATE_WON; + } + break; + + case BTN_UP: + nNextSelected = nSelectedVerse > 0 ? + nSelectedVerse - 1 : nSelectedVerse; + break; + + case BTN_DOWN: + nNextSelected = nSelectedVerse < (POEM_VERSE_COUNT - 1) ? + nSelectedVerse + 1 : nSelectedVerse; + break; + + case BTN_STANDBY: + state = POEM_STATE_STANDBY; + break; + } + + + if (nSelectedVerse != nNextSelected && bIsDragging) { + unsigned int temp = pPoem->nPermut[nSelectedVerse]; + pPoem->nPermut[nSelectedVerse] = pPoem->nPermut[nNextSelected]; + pPoem->nPermut[nNextSelected] = temp; + } + nSelectedVerse = nNextSelected; + } + } + break; + + case POEM_STATE_WON: + lcdFill(0); + DoString(22, 30, "Geschafft!"); + lcdDisplay(); + poem_sendSuccessMessage(pktWinner); + state = POEM_STATE_STANDBY; + break; + } + } +}