crashtest-r0ket/firmware/applications/poem.c

650 lines
19 KiB
C

#include <sysinit.h>
#include <string.h>
#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;
}
}
}