#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 2 #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; // wait up to 200 ms for a packet if (nrf_rcv_pkt_time(200, 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]; while (1) { memcpy(pkt, poem_gPackets[pktWinner], sizeof(pkt)); nrf_snd_pkt_crc(sizeof(pkt), pkt); // wait between 100 and 500 ms (including a potential 200 ms timeout of // poem_getInputNonBlocking) to prevent polluting the channel with // winner packets delayms(100 + (getRandom() % 200)); uint8_t input = poem_getInputNonBlocking(); if (input == BTN_STANDBY) { break; } } } /** * 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; } } }