#include #include #include #include #include #include "../../autoconf.h" #include "../../pixel.h" #include "../../util.h" #include "../../scrolltext/scrolltext.h" #include "variants.h" #include "piece.h" #include "bucket.h" #include "view.h" #define WAIT(ms) wait(ms) /** * \defgroup TetrisViewDefinesPrivate View: Internal constants */ /*@{*/ /*********** * defines * ***********/ /** how often should the border blink (to indicate level up) */ #define TETRIS_VIEW_BORDER_BLINK_COUNT 2 /** amount of time (in ms) between border color changes */ #define TETRIS_VIEW_BORDER_BLINK_DELAY 100 /** how often should the lines blink when they get removed */ #define TETRIS_VIEW_LINE_BLINK_COUNT 3 /** amount of time (in ms) between line color changes */ #define TETRIS_VIEW_LINE_BLINK_DELAY 75 /** color of space */ #define TETRIS_VIEW_COLORSPACE 0 /** color of border */ #define TETRIS_VIEW_COLORBORDER 1 /** color of fading lines */ #define TETRIS_VIEW_COLORFADE 2 /** color of a piece */ #define TETRIS_VIEW_COLORPIECE 3 /** color of pause mode */ #define TETRIS_VIEW_COLORPAUSE 1 /** color of line counter */ #define TETRIS_VIEW_COLORCOUNTER 2 #ifdef GAME_TETRIS_FP #if NUM_ROWS < NUM_COLS #define VIEWCOLS NUM_ROWS #define VIEWROWS NUM_ROWS #elif NUM_ROWS > NUM_COLS #define VIEWCOLS NUM_COLS #define VIEWROWS NUM_COLS #else #define VIEWCOLS NUM_COLS #define VIEWROWS NUM_ROWS #endif #else #define VIEWCOLS NUM_COLS #define VIEWROWS NUM_ROWS #endif #if VIEWROWS >= 20 #define TETRIS_VIEW_YOFFSET_DUMP ((VIEWROWS - 20) / 2) #define TETRIS_VIEW_HEIGHT_DUMP 20 #else #define TETRIS_VIEW_YOFFSET_DUMP 0 #define TETRIS_VIEW_HEIGHT_DUMP VIEWROWS #endif #if VIEWCOLS >= 16 #define TETRIS_VIEW_XOFFSET_DUMP (((VIEWCOLS - 16) / 2) + 1) #define TETRIS_VIEW_WIDTH_DUMP 10 #if VIEWROWS >= 16 #define TETRIS_VIEW_XOFFSET_COUNTER \ (TETRIS_VIEW_XOFFSET_DUMP + TETRIS_VIEW_WIDTH_DUMP + 1) #define TETRIS_VIEW_YOFFSET_COUNT100 ((VIEWCOLS - 14) / 2) #define TETRIS_VIEW_YOFFSET_COUNT10 (TETRIS_VIEW_YOFFSET_COUNT100 + 2) #define TETRIS_VIEW_YOFFSET_COUNT1 (TETRIS_VIEW_YOFFSET_COUNT10 + 4) #define TETRIS_VIEW_XOFFSET_PREVIEW \ (TETRIS_VIEW_XOFFSET_DUMP + TETRIS_VIEW_WIDTH_DUMP + 1) #define TETRIS_VIEW_YOFFSET_PREVIEW (TETRIS_VIEW_YOFFSET_COUNT1 + 4) #elif VIEWROWS < 16 && VIEWROWS >= 4 #define TETRIS_VIEW_XOFFSET_PREVIEW \ (TETRIS_VIEW_XOFFSET_DUMP + TETRIS_VIEW_WIDTH_DUMP + 1) #define TETRIS_VIEW_YOFFSET_PREVIEW ((VIEWROWS - 4) / 2) #endif #elif (VIEWCOLS < 16) && (VIEWCOLS >= 12) #define TETRIS_VIEW_XOFFSET_DUMP ((VIEWCOLS - 10) / 2) #define TETRIS_VIEW_WIDTH_DUMP 10 #elif VIEWCOLS == 11 #define TETRIS_VIEW_XOFFSET_DUMP 1 #define TETRIS_VIEW_WIDTH_DUMP 10 #else #define TETRIS_VIEW_XOFFSET_DUMP 0 #define TETRIS_VIEW_WIDTH_DUMP VIEWCOLS #endif /*@}*/ /** * \defgroup TetrisViewNoInterface View: Internal non-interface functions */ /*@{*/ /*************************** * non-interface functions * ***************************/ /** * setpixel replacement which may transform the pixel coordinates * @param nBearing bearing of the view * @param x x-coordinate of the pixel * @param y y-coordinate of the pixel * @param nColor Color of the pixel */ void tetris_view_setpixel(tetris_bearing_t nBearing, uint8_t x, uint8_t y, uint8_t nColor) { x = VIEWCOLS - 1 - x; switch (nBearing) { case TETRIS_BEARING_0: setpixel((pixel){x, y}, nColor); break; case TETRIS_BEARING_90: setpixel((pixel){y, VIEWCOLS - 1 - x}, nColor); break; case TETRIS_BEARING_180: setpixel((pixel){VIEWCOLS - 1 - x, VIEWROWS - 1 - y}, nColor); break; case TETRIS_BEARING_270: setpixel((pixel){VIEWROWS - 1 - y, x}, nColor); break; } } /** * draws a horizontal line * @param nBearing bearing of the view * @param x1 first x-coordinate of the line * @param x2 second x-coordinate of the line * @param y y-coordinate of the line * @param nColor Color of the line */ void tetris_view_drawHLine(tetris_bearing_t nBearing, uint8_t x1, uint8_t x2, uint8_t y, uint8_t nColor) { assert(x1 <= x2); for (uint8_t x = x1; x <= x2; ++x) { tetris_view_setpixel(nBearing, x, y, nColor); } } /** * draws a vertical line * @param nBearing bearing of the view * @param x x-coordinate of the line * @param y1 first y-coordinate of the line * @param y2 second y-coordinate of the line * @param nColor Color of the line */ void tetris_view_drawVLine(tetris_bearing_t nBearing, uint8_t x, uint8_t y1, uint8_t y2, uint8_t nColor) { assert(y1 <= y2); for (uint8_t y = y1; y <= y2; ++y) { tetris_view_setpixel(nBearing, x, y, nColor); } } /** * helper function to dim the piece color if game is paused * @param pV pointer to the view whose pause status is of interest */ uint8_t tetris_view_getPieceColor(tetris_view_t *pV) { if (pV->modeCurrent == TETRIS_VIMO_RUNNING) { return TETRIS_VIEW_COLORPIECE; } else { return TETRIS_VIEW_COLORPAUSE; } } /** * redraws the dump and the falling piece (if necessary) * @param pV pointer to the view on which the dump should be drawn */ void tetris_view_drawDump(tetris_view_t *pV) { assert(pV->pBucket != NULL); if (tetris_bucket_getRow(pV->pBucket) <= -4) { return; } tetris_bearing_t nBearing = pV->pVariantMethods->getBearing(pV->pVariant); int8_t nPieceRow = tetris_bucket_getRow(pV->pBucket); uint16_t nRowMap; uint16_t nElementMask; tetris_bucket_status_t status = tetris_bucket_getStatus(pV->pBucket); for (int8_t nRow = TETRIS_VIEW_HEIGHT_DUMP - 1; nRow >= 0; --nRow) { nRowMap = tetris_bucket_getDumpRow(pV->pBucket, nRow); // if a piece is hovering or gliding it needs to be drawn if ((status == TETRIS_BUS_HOVERING) || (status == TETRIS_BUS_GLIDING) || (status == TETRIS_BUS_GAMEOVER)) { if ((nRow >= nPieceRow) && (nRow <= nPieceRow + 3)) { int8_t y = nRow - nPieceRow; int8_t nColumn = tetris_bucket_getColumn(pV->pBucket); uint16_t nPieceMap = tetris_piece_getBitmap(tetris_bucket_getPiece(pV->pBucket)); // clear all bits of the piece we are not interested in and // align the remaining row to LSB nPieceMap = (nPieceMap & (0x000F << (y << 2))) >> (y << 2); // shift remaining part to current column if (nColumn >= 0) { nPieceMap <<= nColumn; } else { nPieceMap >>= -nColumn; } // cut off unwanted stuff // nPieceMap &= 0x03ff; // finally embed piece into the view nRowMap |= nPieceMap; } } nElementMask = 0x0001; for (int8_t x = 0; x < TETRIS_VIEW_WIDTH_DUMP; ++x) { unsigned char nColor; if ((nRowMap & nElementMask) != 0) { nColor = tetris_view_getPieceColor(pV); } else { nColor = TETRIS_VIEW_COLORSPACE; } tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_DUMP + x, TETRIS_VIEW_YOFFSET_DUMP + nRow, nColor); nElementMask <<= 1; } } } #ifdef TETRIS_VIEW_XOFFSET_PREVIEW /** * redraws the preview window * @param pV pointer to the view on which the piece should be drawn * @param pPc pointer to the piece for the preview window (may be NULL) */ void tetris_view_drawPreviewPiece(tetris_view_t *pV, tetris_piece_t *pPc) { tetris_bearing_t nBearing = pV->pVariantMethods->getBearing(pV->pVariant); if (pPc != NULL) { uint8_t nColor; uint16_t nElementMask = 0x0001; uint16_t nPieceMap; if (pV->modeCurrent == TETRIS_VIMO_RUNNING) { nPieceMap = tetris_piece_getBitmap(pPc); } else { // an iconized "P" nPieceMap = 0x26a6; } for (uint8_t y = 0; y < 4; ++y) { for (uint8_t x = 0; x < 4; ++x) { if ((nPieceMap & nElementMask) != 0) { nColor = TETRIS_VIEW_COLORPIECE; } else { nColor = TETRIS_VIEW_COLORSPACE; } tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_PREVIEW + x, TETRIS_VIEW_YOFFSET_PREVIEW + y, nColor); nElementMask <<= 1; } } } else { for (uint8_t y = 0; y < 4; ++y) { for (uint8_t x = 0; x < 4; ++x) { tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_PREVIEW + x, TETRIS_VIEW_YOFFSET_PREVIEW + y, TETRIS_VIEW_COLORSPACE); } } } } #endif /** * draws borders in the given color * @param pV pointer to the view on which the borders should be drawn * @param nColor the color for the border */ void tetris_view_drawBorders(tetris_view_t *pV, uint8_t nColor) { tetris_bearing_t nBearing = pV->pVariantMethods->getBearing(pV->pVariant); #if TETRIS_VIEW_YOFFSET_DUMP != 0 // fill upper space if required for (uint8_t y = 0; y < TETRIS_VIEW_YOFFSET_DUMP; ++y) { tetris_view_drawHLine(nBearing, 0, VIEWCOLS - 1, y, nColor); } #endif #if VIEWROWS > TETRIS_VIEW_HEIGHT_DUMP // fill lower space if required uint8_t y = TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP; for (; y < VIEWROWS; ++y) { tetris_view_drawHLine(nBearing, 0, VIEWCOLS - 1, y, nColor); } #endif #if TETRIS_VIEW_XOFFSET_DUMP != 0 // fill left space if required for (uint8_t x = 0; x < TETRIS_VIEW_XOFFSET_DUMP; ++x) { tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); } #endif #if VIEWCOLS > 16 // fill right space if required uint8_t x = TETRIS_VIEW_XOFFSET_DUMP + TETRIS_VIEW_WIDTH_DUMP + 5; for (; x < VIEWCOLS; ++x) { tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); } #endif #ifdef TETRIS_VIEW_XOFFSET_COUNTER tetris_view_drawVLine(nBearing, TETRIS_VIEW_XOFFSET_COUNTER - 1, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); for (uint8_t x = TETRIS_VIEW_XOFFSET_COUNTER; x < TETRIS_VIEW_XOFFSET_COUNTER + 3; ++x) { tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_COUNT100 - 1, nColor); tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_PREVIEW + 4, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); } tetris_view_drawVLine(nBearing, TETRIS_VIEW_XOFFSET_COUNTER + 3, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_COUNT1 + 3, nColor); tetris_view_drawVLine(nBearing, TETRIS_VIEW_XOFFSET_COUNTER + 3, TETRIS_VIEW_YOFFSET_PREVIEW + 4, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); tetris_view_drawHLine(nBearing, TETRIS_VIEW_XOFFSET_COUNTER, TETRIS_VIEW_XOFFSET_COUNTER + 3, TETRIS_VIEW_YOFFSET_COUNT100 + 1, nColor); tetris_view_drawHLine(nBearing, TETRIS_VIEW_XOFFSET_COUNTER, TETRIS_VIEW_XOFFSET_COUNTER + 3, TETRIS_VIEW_YOFFSET_COUNT10 + 3, nColor); tetris_view_drawHLine(nBearing, TETRIS_VIEW_XOFFSET_COUNTER, TETRIS_VIEW_XOFFSET_COUNTER + 3, TETRIS_VIEW_YOFFSET_COUNT1 + 3, nColor); #elif defined TETRIS_VIEW_XOFFSET_PREVIEW tetris_view_drawVLine(nBearing, TETRIS_VIEW_XOFFSET_PREVIEW - 1, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); for (uint8_t x = TETRIS_VIEW_XOFFSET_PREVIEW; x < TETRIS_VIEW_XOFFSET_PREVIEW + 4; ++x) { tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_PREVIEW - 1, nColor); tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_PREVIEW + 4, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); } #elif TETRIS_VIEW_WIDTH_DUMP < VIEWCOLS for (uint8_t x = TETRIS_VIEW_XOFFSET_DUMP + TETRIS_VIEW_WIDTH_DUMP; x < VIEWCOLS; ++x) { tetris_view_drawVLine(nBearing, x, TETRIS_VIEW_YOFFSET_DUMP, TETRIS_VIEW_YOFFSET_DUMP + TETRIS_VIEW_HEIGHT_DUMP - 1, nColor); } #endif } /** * lets the borders blink to notify player of a level change * @param pV pointer to the view whose borders should blink */ void tetris_view_blinkBorders(tetris_view_t *pV) { for (uint8_t i = 0; i < TETRIS_VIEW_BORDER_BLINK_COUNT; ++i) { tetris_view_drawBorders(pV, TETRIS_VIEW_COLORPIECE); WAIT(TETRIS_VIEW_BORDER_BLINK_DELAY); tetris_view_drawBorders(pV, TETRIS_VIEW_COLORBORDER); WAIT(TETRIS_VIEW_BORDER_BLINK_DELAY); } } /** * lets complete lines blink to emphasize their removal * @param pV pointer to the view whose complete lines should blink */ void tetris_view_blinkLines(tetris_view_t *pV) { // reduce necessity of pointer arithmetic int8_t nRow = tetris_bucket_getRow(pV->pBucket); uint8_t nRowMask = tetris_bucket_getRowMask(pV->pBucket); tetris_bearing_t nBearing = pV->pVariantMethods->getBearing(pV->pVariant); // don't try to draw below the border int8_t nDeepestRowOffset = ((nRow + 3) < TETRIS_VIEW_HEIGHT_DUMP ? 3 : TETRIS_VIEW_HEIGHT_DUMP - (nRow + 1)); // this loop controls how often the lines should blink for (uint8_t i = 0; i < TETRIS_VIEW_LINE_BLINK_COUNT; ++i) { // this loop determines the color of the line to be drawn for (uint8_t nColIdx = 0; nColIdx < 2; ++nColIdx) { // iterate through the possibly complete lines for (uint8_t j = 0; j <= nDeepestRowOffset; ++j) { // is current line a complete line? if ((nRowMask & (0x01 << j)) != 0) { // draw line in current color uint8_t y = nRow + j; for (uint8_t x = 0; x < 10; ++x) { uint8_t nColor = (nColIdx == 0 ? TETRIS_VIEW_COLORFADE : TETRIS_VIEW_COLORPIECE); // setpixel((pixel){14 - x, y}, nColor); tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_DUMP + x, TETRIS_VIEW_YOFFSET_DUMP + y, nColor); } } } // wait a few ms to make the blink effect visible WAIT(TETRIS_VIEW_LINE_BLINK_DELAY); } } } #ifdef TETRIS_VIEW_XOFFSET_COUNTER /** * displays completed Lines (0-99) * @param pV pointer to the view */ void tetris_view_showLineNumbers(tetris_view_t *pV) { tetris_bearing_t nBearing = pV->pVariantMethods->getBearing(pV->pVariant); // get number of completed lines uint16_t nLines = pV->pVariantMethods->getLines(pV->pVariant); // get decimal places int8_t nOnes = nLines % 10; int8_t nTens = (nLines / 10) % 10; int8_t nHundreds = (nLines / 100) % 10; // draws the decimal places as 3x3 squares with 9 pixels for (int i = 0, x = 0, y = 0; i < 9; ++i) { // pick drawing color for the ones uint8_t nOnesPen = nOnes > i ? TETRIS_VIEW_COLORCOUNTER : TETRIS_VIEW_COLORSPACE; tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_COUNTER + x, TETRIS_VIEW_YOFFSET_COUNT1 + y, nOnesPen); // pick drawing color for the tens uint8_t nTensPen = nTens > i ? TETRIS_VIEW_COLORCOUNTER : TETRIS_VIEW_COLORSPACE; tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_COUNTER + x, TETRIS_VIEW_YOFFSET_COUNT10 + y, nTensPen); // a maximum of 399 lines can be displayed if (i < 3) { // pick drawing color for the hundreds uint8_t nHundredsPen = nHundreds > i ? TETRIS_VIEW_COLORCOUNTER : TETRIS_VIEW_COLORSPACE; tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_COUNTER + x, TETRIS_VIEW_YOFFSET_COUNT100 + y, nHundredsPen); } // wrap lines if required if ((++x % 3) == 0) { ++y; x = 0; } } } #endif /** * unpacks the champion's initials from the uint16_t packed form * @param nHighscoreName the champion's initials packed into a uint16_t * @param pszName pointer to an array of char for the unpacked initials */ void tetris_view_formatHighscoreName(uint16_t nHighscoreName, char *pszName) { pszName[0] = ((nHighscoreName >> 10) & 0x1F) + 65; if (pszName[0] == '_') { pszName[0] = ' '; } pszName[1] = ((nHighscoreName >> 5) & 0x1F) + 65; if (pszName[1] == '_') { pszName[1] = ' '; } pszName[2] = (nHighscoreName & 0x1F) + 65; if (pszName[2] == '_') { pszName[2] = ' '; } pszName[3] = '\0'; } /*@}*/ /**************************** * construction/destruction * ****************************/ tetris_view_t *tetris_view_construct(const tetris_variant_t *const pVarMethods, void *pVariantData, tetris_bucket_t *pBucket) { // memory allocation assert((pVariantData != NULL) && (pBucket != NULL)); tetris_view_t *pView = (tetris_view_t *) malloc(sizeof(tetris_view_t)); assert(pView != NULL); // init memset(pView, 0, sizeof(tetris_view_t)); pView->pVariantMethods = pVarMethods; pView->pVariant = pVariantData; pView->pBucket = pBucket; pView->modeCurrent = pView->modeOld = TETRIS_VIMO_RUNNING; // drawing some first stuff clear_screen(0); tetris_view_drawBorders(pView, TETRIS_VIEW_COLORBORDER); return pView; } void tetris_view_destruct(tetris_view_t *pView) { assert(pView != NULL); free(pView); } /*************************** * view related functions * ***************************/ void tetris_view_getDimensions(int8_t *w, int8_t *h) { assert((w != NULL) && (h != NULL)); *w = TETRIS_VIEW_WIDTH_DUMP; *h = TETRIS_VIEW_HEIGHT_DUMP; } void tetris_view_setViewMode(tetris_view_t *pV, tetris_view_mode_t vm) { pV->modeOld = pV->modeCurrent; pV->modeCurrent = vm; } void tetris_view_update(tetris_view_t *pV) { assert(pV != NULL); tetris_view_drawBorders(pV, TETRIS_VIEW_COLORBORDER); #ifdef TETRIS_VIEW_XOFFSET_PREVIEW // draw preview piece tetris_view_drawPreviewPiece(pV, pV->pVariantMethods->getPreviewPiece(pV->pVariant)); #endif // let complete lines blink (if there are any) if (tetris_bucket_getRowMask(pV->pBucket) != 0) { tetris_view_blinkLines(pV); } #ifdef TETRIS_VIEW_XOFFSET_COUNTER // update line counter tetris_view_showLineNumbers(pV); #endif // draw dump tetris_view_drawDump(pV); // visual feedback to inform about a level change uint8_t nLevel = pV->pVariantMethods->getLevel(pV->pVariant); if (nLevel != pV->nOldLevel) { tetris_view_blinkBorders(pV); pV->nOldLevel = nLevel; } } void tetris_view_showResults(tetris_view_t *pV) { #ifdef SCROLLTEXT_SUPPORT char pszResults[55], pszHighscoreName[4]; uint16_t nScore = pV->pVariantMethods->getScore(pV->pVariant); uint16_t nHighscore = pV->pVariantMethods->getHighscore(pV->pVariant); uint16_t nLines = pV->pVariantMethods->getLines(pV->pVariant); uint16_t nHighscoreName = pV->pVariantMethods->getHighscoreName(pV->pVariant); tetris_view_formatHighscoreName(nHighscoreName, pszHighscoreName); if (nScore <= nHighscore) { snprintf(pszResults, sizeof(pszResults), "