diff --git a/games/tetris/bucket.c b/games/tetris/bucket.c index d5b5ba9..f002094 100644 --- a/games/tetris/bucket.c +++ b/games/tetris/bucket.c @@ -2,6 +2,7 @@ #include #include #include +#include "../../compat/pgmspace.h" #include "../../config.h" #include "bucket.h" #include "piece.h" @@ -14,10 +15,12 @@ /** * determines if piece is either hovering or gliding * @param pBucket the bucket we want information from - * @return TETRIS_PFS_HOVERING or TETRIS_PFS_GLIDING + * @return TETRIS_BUS_HOVERING or TETRIS_BUS_GLIDING */ tetris_bucket_status_t tetris_bucket_hoverStatus(tetris_bucket_t* pBucket) { + assert(pBucket != NULL); + // if the piece touches the dump we ensure that the status is "gliding" if (tetris_bucket_collision(pBucket, pBucket->nColumn, pBucket->nRow + 1)) { @@ -115,7 +118,7 @@ void tetris_bucket_reset(tetris_bucket_t *pBucket) // clear dump if it has been allocated in memory if (pBucket->dump != NULL) { - memset(pBucket->dump, 0, pBucket->nHeight); + memset(pBucket->dump, 0, pBucket->nHeight * sizeof(uint16_t)); } pBucket->status = TETRIS_BUS_READY; @@ -148,7 +151,7 @@ void tetris_bucket_insertPiece(tetris_bucket_t *pBucket, { assert((pBucket != NULL) && (pPiece != NULL) && (ppOldPiece != NULL)); - // a piece can only be inserted in state TETRIS_PFS_READY + // a piece can only be inserted in state TETRIS_BUS_READY assert(pBucket->status == TETRIS_BUS_READY); // row mask is now meaningless @@ -182,94 +185,50 @@ uint8_t tetris_bucket_collision(tetris_bucket_t *pBucket, int8_t nColumn, int8_t nRow) { - assert(pBucket != NULL); + // A piece is represented by 16 bits (4 bits per row where the LSB marks the + // left most position). The part of the bucket which is covered by the piece + // is converted to this format (including the bucket borders) so that a + // simple bitwise 'AND' tells us if the piece and the dump overlap. // only allow coordinates which are within sane ranges + assert(pBucket != NULL); assert((nColumn > -4) && (nColumn < pBucket->nWidth)); assert((nRow > -4) && (nRow < pBucket->nHeight)); - // The rows of a piece get compared with the background one by one - // until either a collision occures or all rows are compared. Both the - // piece row and the part of the bucket it covers are represented in - // 4 bits which were singled out from their corresponding uint16_t - // values and are aligned to LSB. In case where a piece overlaps with - // either the left or the right border we "enhance" the bucket part - // via bit shifting and set all bits representing the border to 1. - // - // NOTE: LSB represents the left most position. - uint16_t nPieceMap = tetris_piece_getBitmap(pBucket->pPiece); - uint16_t nBucketPart; - uint16_t nPieceRowMap; - - // negative nRow values indicate that the piece hasn't fully entered the - // bucket yet which requires special treatment if the piece overlaps - // with either the left or the right border - if (nRow < 0) + // left and right borders + uint16_t nBucketPart = 0; + if (nColumn < 0) { - uint16_t nBorderMask = 0x0000; - // piece overlaps with left border - if (nColumn < 0) - { - nBorderMask = 0x1111 << (-nColumn - 1); - } - // piece overlaps with right border - else if ((nColumn + 3) >= pBucket->nWidth) - { - nBorderMask = 0x8888 >> ((nColumn + 3) - pBucket->nWidth); - } - // return if piece collides with border - if ((nPieceMap & nBorderMask) != 0) - { - return 1; - } + static uint16_t const nLeftPart[] PROGMEM = {0x7777, 0x3333, 0x1111}; + nBucketPart = pgm_read_word(&nLeftPart[nColumn + 3]); } - - // here we check the part which has already entered the bucket - for (int8_t y = (nRow < 0) ? -nRow : 0; y < 4; ++y) + else if (nColumn >= pBucket->nWidth - 3) { - // current piece row overlaps with lower border - if ((y + nRow) >= pBucket->nHeight) - { - // all 4 bits represent the lower border - nBucketPart = 0x000F; - } - // piece overlaps with left border - else if (nColumn < 0) - { - // clear all bits we are not interested in - nBucketPart = (pBucket->dump[y + nRow] & (0x000F >> -nColumn)); - // add zeros to the left (the bits "behind" the left border) - nBucketPart <<= -nColumn; - // set bits beyond left border to 1 - nBucketPart |= 0x000F >> (4 + nColumn); - } - // piece overlaps with right border - else if ((nColumn + 3) >= pBucket->nWidth) - { - // align the bits we are interested in to LSB - // (thereby clearing the rest) - nBucketPart = pBucket->dump[y + nRow] >> nColumn; - // set bits beyond right border to 1 - nBucketPart |= 0xFFF8 >> (nColumn + 3 - pBucket->nWidth); - } - // current row neither overlaps with left, right nor lower border - else - { - // clear all bits we are not interested in and align the - // remaing row to LSB - nBucketPart = - (pBucket->dump[y + nRow] & (0x000F << nColumn)) >> nColumn; - } - - // clear all bits of the piece we are not interested in and - // align the remaing row to LSB - nPieceRowMap = (nPieceMap & (0x000F << (y << 2))) >> (y << 2); + static uint16_t const nRightPart[] PROGMEM = {0xEEEE, 0xCCCC, 0x8888}; + nBucketPart = pgm_read_word(&nRightPart[pBucket->nWidth - nColumn - 1]); + } + // lower border + if (nRow > pBucket->nHeight - 4) + { + nBucketPart |= 0xFFFF << ((pBucket->nHeight - nRow) * 4); + } - // finally check for a collision - if ((nBucketPart & nPieceRowMap) != 0) + int8_t const nStop = (nRow + 3) < pBucket->nHeight ? + nRow + 3 : pBucket->nHeight - 1; + // mask those blocks which are not covered by the piece + uint16_t nDumpMask = nColumn >= 0 ? 0x000F << nColumn : 0x000F >> -nColumn; + // value for shifting blocks to the corresponding part of the piece + int8_t nShift = -nColumn + (nRow < 0 ? 4 * -nRow : 0); + for (int8_t y = nRow >= 0 ? nRow : 0; y <= nStop; ++y) + { + uint16_t nTemp = pBucket->dump[y] & nDumpMask; + nBucketPart |= nShift >= 0 ? nTemp << nShift : nTemp >> -nShift; + if ((tetris_piece_getBitmap(pBucket->pPiece) & nBucketPart) != 0) { + // collision return 1; } + nShift += 4; } // if we reach here, no collision was detected @@ -412,7 +371,7 @@ void tetris_bucket_removeCompleteLines(tetris_bucket_t *pBucket) { assert(pBucket != NULL); - // rows can only be removed if we are in state TETRIS_PFS_DOCKED + // rows can only be removed if we are in state TETRIS_BUS_DOCKED assert(pBucket->status == TETRIS_BUS_DOCKED); // bit mask of a full row @@ -560,32 +519,49 @@ uint16_t tetris_bucket_getDumpRow(tetris_bucket_t *pBucket, int8_t tetris_bucket_predictDeepestRow(tetris_bucket_t *pBucket, tetris_piece_t *pPiece, + int8_t nStartingRow, int8_t nColumn) { - int8_t nRow = tetris_bucket_getPieceStartPos(pPiece); + assert(pBucket != NULL); + assert(pPiece != NULL); + assert(nStartingRow >= -1 && nStartingRow < pBucket->nHeight); + assert(nColumn >= -3 && nColumn < pBucket->nWidth); + + // exchange current piece of the bucket (to use its collision detection) tetris_piece_t *pActualPiece = pBucket->pPiece; pBucket->pPiece = pPiece; - // is it actually possible to use this piece? - if (tetris_bucket_collision(pBucket, (pBucket->nWidth - 2) / 2, nRow) || - (tetris_bucket_collision(pBucket, nColumn, nRow))) + // determine empty rows of the bottom of piece which may overlap the dump + uint16_t nMap = tetris_piece_getBitmap(pPiece); + int8_t nOffset = 0; + if ((nMap & 0xF000) != 0) + nOffset = 3; + else if ((nMap & 0xFF00) != 0) + nOffset = 2; + else if ((nMap & 0xFFF0) != 0) + nOffset = 1; + int8_t nRow = nStartingRow - nOffset; + + // check if the piece collides with the left or the right wall + if ((nRow < -3) || (((nColumn < 0) || (nColumn >= pBucket->nWidth - 3)) && + tetris_bucket_collision(pBucket, nColumn, nRow))) { - // restore real piece - pBucket->pPiece = pActualPiece; - - return -4; + nRow = TETRIS_BUCKET_INVALIDROW; } - // determine deepest row - nRow = (nRow < pBucket->nFirstMatterRow - 4) ? - pBucket->nFirstMatterRow - 4 : nRow; - while ((nRow < pBucket->nHeight) && - (!tetris_bucket_collision(pBucket, nColumn, nRow + 1))) + else { + while (!tetris_bucket_collision(pBucket, nColumn, nRow + 1)) + { ++nRow; + } + if ((nRow < 0) && (((nRow + 4) * 4) << nMap)) + { + nRow = TETRIS_BUCKET_INVALIDROW; + } } - // restore real piece + // restore actual bucket piece pBucket->pPiece = pActualPiece; return nRow; @@ -597,44 +573,42 @@ int8_t tetris_bucket_predictCompleteLines(tetris_bucket_t *pBucket, int8_t nRow, int8_t nColumn) { + assert(nRow > -4); int8_t nCompleteRows = 0; // bit mask of a full row uint16_t nFullRow = 0xFFFF >> (16 - pBucket->nWidth); - if (nRow > -4) - { - // determine sane start and stop values for the dump's index - int8_t nStartRow = - ((nRow + 3) >= pBucket->nHeight) ? pBucket->nHeight - 1 : nRow + 3; - int8_t nStopRow = (nRow < 0) ? 0 : nRow; + // determine sane start and stop values for the dump's index + int8_t nStartRow = ((nRow + 3) >= pBucket->nHeight) ? + pBucket->nHeight - 1 : nRow + 3; + int8_t nStopRow = (nRow < 0) ? 0 : nRow; - uint16_t nPiece = tetris_piece_getBitmap(pPiece); + uint16_t nPiece = tetris_piece_getBitmap(pPiece); - for (int8_t i = nStartRow; i >= nStopRow; --i) - { - int8_t y = i - nRow; + for (int8_t i = nStartRow; i >= nStopRow; --i) + { + int8_t y = i - nRow; - // clear all bits of the piece we are not interested in and - // align the rest to LSB - uint16_t nPieceMap = (nPiece & (0x000F << (y << 2))) >> (y << 2); - // shift the remaining content to the current column - if (nColumn >= 0) - { - nPieceMap <<= nColumn; - } - else - { - nPieceMap >>= -nColumn; - } - // embed piece in dump map - uint16_t nDumpMap = pBucket->dump[i] | nPieceMap; + // clear all bits of the piece we are not interested in and align the + // rest to LSB + uint16_t nPieceMap = (nPiece & (0x000F << (y << 2))) >> (y << 2); + // shift the remaining content to the current column + if (nColumn >= 0) + { + nPieceMap <<= nColumn; + } + else + { + nPieceMap >>= -nColumn; + } + // embed piece in dump map + uint16_t nDumpMap = pBucket->dump[i] | nPieceMap; - // is current row a full row? - if ((nFullRow & nDumpMap) == nFullRow) - { - ++nCompleteRows; - } + // is current row a full row? + if ((nFullRow & nDumpMap) == nFullRow) + { + ++nCompleteRows; } } diff --git a/games/tetris/bucket.h b/games/tetris/bucket.h index 0939130..c306f0d 100644 --- a/games/tetris/bucket.h +++ b/games/tetris/bucket.h @@ -6,6 +6,13 @@ #include "piece.h" +/*********** + * defines * + ***********/ + +#define TETRIS_BUCKET_INVALIDROW -4 + + /********* * types * *********/ @@ -245,11 +252,13 @@ uint16_t tetris_bucket_getDumpRow(tetris_bucket_t *pBucket, * returns the deepest possible row for a given piece * @param pBucket the bucket on which we want to test a piece * @param pPiece the piece which should be tested + * @param nStartingRow the row where the collision detection should start * @param nColumn the column where the piece should be dropped * @return the row of the piece (bucket compliant coordinates) */ int8_t tetris_bucket_predictDeepestRow(tetris_bucket_t *pBucket, tetris_piece_t *pPiece, + int8_t nStartingRow, int8_t nColumn); diff --git a/games/tetris/tetris_main.c b/games/tetris/tetris_main.c index 1ff18f3..c3e06ee 100644 --- a/games/tetris/tetris_main.c +++ b/games/tetris/tetris_main.c @@ -1,14 +1,14 @@ #include #include #include - -#include "tetris_main.h" -#include "variants.h" +#include "bearing.h" #include "piece.h" +#include "highscore.h" #include "bucket.h" -#include "view.h" #include "input.h" -#include "highscore.h" +#include "variants.h" +#include "view.h" +#include "tetris_main.h" void tetris_main(tetris_variant_t const *const pVariantMethods) diff --git a/games/tetris/variant_bastet.c b/games/tetris/variant_bastet.c index 8a4d823..fe08e81 100644 --- a/games/tetris/variant_bastet.c +++ b/games/tetris/variant_bastet.c @@ -27,17 +27,21 @@ ***************************/ /** - * calculate the score impact of every column (without any prediction) - * @param pBastet bastet instance whose column heights should be calculated + * Preprocess values like sane starting points for the collision detection or + * the score impact of every unchanged column to speed up prediction routines. + * @param pBastet bastet instance which should be preprocessed */ -void tetris_bastet_calcActualColumnsScoreImpact(tetris_bastet_variant_t *pBastet) +void tetris_bastet_doPreprocessing(tetris_bastet_variant_t *pBastet) { // retrieve sane start and stop values for the column and row indices int8_t nWidth = tetris_bucket_getWidth(pBastet->pBucket); int8_t nStartRow = tetris_bucket_getHeight(pBastet->pBucket) - 1; int8_t nStopRow = tetris_bucket_getFirstMatterRow(pBastet->pBucket); +// printf("%d ", nStopRow); // calculate the column heights of the actual bucket configuration + // NOTE: in this loop, pColScore contains the actual column heights, + // later it will contain the "score impact" of every unchanged column for (int8_t y = nStartRow; y >= nStopRow; --y) { uint16_t nDumpRow = tetris_bucket_getDumpRow(pBastet->pBucket, y); @@ -46,16 +50,65 @@ void tetris_bastet_calcActualColumnsScoreImpact(tetris_bastet_variant_t *pBastet { if ((nDumpRow & nColMask) != 0) { - pBastet->pActualColScoreImpact[x] = nStartRow - y + 1; + pBastet->pColScore[x] = nStartRow - y + 1; } nColMask <<= 1; } } + +// printf("-------------------------------------------\n "); +// for (int i = 0; i < nWidth; ++i) +// { +// printf("%2d ", pBastet->pColScore[i]); +// } +// printf("\n"); + + + // starting points for collision detection (to speedup things) + // calculate the maxima of the 4-tuples from column -3 to -1 + pBastet->pStartingRow[0] = pBastet->pColScore[0]; + pBastet->pStartingRow[1] = pBastet->pColScore[0] > pBastet->pColScore[1] ? + pBastet->pColScore[0] : pBastet->pColScore[1]; + pBastet->pStartingRow[2] = pBastet->pStartingRow[1] > pBastet->pColScore[2]? + pBastet->pStartingRow[1] : pBastet->pColScore[2]; + // calculate the maxima of the 4-tuples from column 0 to width-1 + for (int8_t i = 0; i < nWidth; ++i) + { + int8_t t0 = pBastet->pColScore[i] > pBastet->pColScore[i + 1] ? + i : i + 1; + int8_t t1 = pBastet->pColScore[i + 2] > pBastet->pColScore[i + 3] ? + i + 2 : i + 3; + pBastet->pStartingRow[i + 3] = + pBastet->pColScore[t0] > pBastet->pColScore[t1] ? + pBastet->pColScore[t0] : pBastet->pColScore[t1]; + } +// for (int i = 0; i < nWidth + 3; ++i) +// { +// printf("%2d ", pBastet->pStartingRow[i]); +// if (i == 2) +// printf("| "); +// } +// printf("\n"); + // normalize to bucket geometry + for (uint8_t i = 0; i < nWidth + 3; ++i) + { + pBastet->pStartingRow[i] = nStartRow - pBastet->pStartingRow[i]; + } + // calculate the score impact of every column for (int x = 0; x < nWidth; ++x) { - pBastet->pActualColScoreImpact[x] *= TETRIS_BASTET_HEIGHT_FACTOR; + pBastet->pColScore[x] *= TETRIS_BASTET_HEIGHT_FACTOR; } + +// for (int i = 0; i < nWidth + 3; ++i) +// { +// printf("%2d ", pBastet->pStartingRow[i]); +// if (i == 2) +// printf("| "); +// } +// printf("\n"); + } @@ -66,9 +119,8 @@ void tetris_bastet_calcActualColumnsScoreImpact(tetris_bastet_variant_t *pBastet * @param nColum the column where the piece should be dropped * @param nStartCol the first column of the range to be predicted * @param nStopCol the last column of the range to be predicted - * @return 0 if dropped piece would cause an overflow, 1 if prediction succeeds */ -uint8_t tetris_bastet_calcPredictedColHeights(tetris_bastet_variant_t *pBastet, +void tetris_bastet_calcPredictedColHeights(tetris_bastet_variant_t *pBastet, tetris_piece_t *pPiece, int8_t nDeepestRow, int8_t nColumn, @@ -80,11 +132,6 @@ uint8_t tetris_bastet_calcPredictedColHeights(tetris_bastet_variant_t *pBastet, int8_t nHeight = 1; uint16_t *pDump = tetris_bucket_predictBottomRow(&iterator, pBastet->pBucket, pPiece, nDeepestRow, nColumn); - if (pDump == NULL) - { - // an immediately returned NULL is caused by a full dump -> low score - return 0; - } while (pDump != NULL) { uint16_t nColMask = 0x0001 << nStartCol; @@ -99,7 +146,6 @@ uint8_t tetris_bastet_calcPredictedColHeights(tetris_bastet_variant_t *pBastet, pDump = tetris_bucket_predictNextRow(&iterator); ++nHeight; } - return 1; } @@ -186,7 +232,8 @@ void *tetris_bastet_construct(tetris_bucket_t *pBucket) pBastet->pBucket = pBucket; int8_t nWidth = tetris_bucket_getWidth(pBastet->pBucket); - pBastet->pActualColScoreImpact = (int8_t*) calloc(nWidth, sizeof(int8_t)); + pBastet->pColScore = (uint16_t*) calloc(nWidth + 3, sizeof(uint16_t)); + pBastet->pStartingRow = (int8_t*) calloc(nWidth + 3, sizeof(int8_t)); pBastet->pColHeights = (int8_t*) calloc(nWidth, sizeof(int8_t)); return pBastet; @@ -198,9 +245,9 @@ void tetris_bastet_destruct(void *pVariantData) assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; - if (pBastetVariant->pActualColScoreImpact != NULL) + if (pBastetVariant->pColScore != NULL) { - free(pBastetVariant->pActualColScoreImpact); + free(pBastetVariant->pColScore); } if (pBastetVariant->pColHeights != NULL) { @@ -228,7 +275,13 @@ int16_t tetris_bastet_evaluateMove(tetris_bastet_variant_t *pBastet, // the row where the given piece collides int8_t nDeepestRow = tetris_bucket_predictDeepestRow(pBastet->pBucket, - pPiece, nColumn); + pPiece, pBastet->pStartingRow[nColumn], nColumn); + + // in case the prediction fails we return the lowest possible score + if (nDeepestRow == TETRIS_BUCKET_INVALIDROW) + { + return -32766; + } // modify score based on complete lines int8_t nLines = tetris_bucket_predictCompleteLines(pBastet->pBucket, @@ -251,13 +304,9 @@ int16_t tetris_bastet_evaluateMove(tetris_bastet_variant_t *pBastet, nStopCol = (nColumn + 3) < nWidth ? nColumn + 3 : nWidth - 1; } - // predicted column heights - if (!tetris_bastet_calcPredictedColHeights(pBastet, pPiece, nDeepestRow, - nColumn, nStartCol, nStopCol)) - { - // in case the prediction fails we return the lowest possible score - return -32766; - } + // predict column heights of this move + tetris_bastet_calcPredictedColHeights(pBastet, pPiece, nDeepestRow, nColumn, + nStartCol, nStopCol); // modify score based on predicted column heights for (int x = 0; x < nWidth; ++x) @@ -268,7 +317,7 @@ int16_t tetris_bastet_evaluateMove(tetris_bastet_variant_t *pBastet, } else { - nScore -= pBastet->pActualColScoreImpact[x]; + nScore -= pBastet->pColScore[x]; } } @@ -279,7 +328,7 @@ int16_t tetris_bastet_evaluateMove(tetris_bastet_variant_t *pBastet, void tetris_bastet_evaluatePieces(tetris_bastet_variant_t *pBastet) { // precache actual column heights - tetris_bastet_calcActualColumnsScoreImpact(pBastet); + tetris_bastet_doPreprocessing(pBastet); int8_t nWidth = tetris_bucket_getWidth(pBastet->pBucket); tetris_piece_t *pPiece = tetris_piece_construct(TETRIS_PC_LINE, TETRIS_PC_ANGLE_0); diff --git a/games/tetris/variant_bastet.h b/games/tetris/variant_bastet.h index d6fa202..523975e 100644 --- a/games/tetris/variant_bastet.h +++ b/games/tetris/variant_bastet.h @@ -40,7 +40,8 @@ typedef struct tetris_bastet_variant_t uint16_t nLines; /** number of completed lines */ tetris_piece_t *pPreviewPiece; /** the piece for the preview */ tetris_bucket_t *pBucket; /** bucket to be examined */ - int8_t *pActualColScoreImpact; /** score impact of every column*/ + uint16_t *pColScore; /** score impact of every column*/ + int8_t *pStartingRow; /** starting point for collision*/ int8_t *pColHeights; /** predicted column heights */ tetris_bastet_scorepair_t nPieceScore[7]; /** score for every piece */ }