#include #include #include #include #include "../../random/prng.h" #include "../../compat/pgmspace.h" #include "../../menu/menu.h" #include "bearing.h" #include "piece.h" #include "highscore.h" #include "bucket.h" #include "input.h" #include "variants.h" #include "tetris_main.h" #include "variant_bastet.h" /*********** * defines * ***********/ #define TETRIS_BASTET_HEIGHT_FACTOR 5 /*************************** * non-interface functions * ***************************/ /** * 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 */ static 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_getFirstTaintedRow(pBastet->pBucket); // clear old precalculated scores for (uint8_t i = 0; i < nWidth + 3; ++i) { pBastet->pColScore[i] = 0; } // 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); uint16_t nColMask = 0x0001; for (uint8_t x = 0; x < nWidth; ++x) { if ((nDumpRow & nColMask) != 0) { pBastet->pColScore[x] = nStartRow - y + 1; } nColMask <<= 1; } } // 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 (uint8_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]; } // 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 (uint8_t x = 0; x < nWidth; ++x) { pBastet->pColScore[x] *= TETRIS_BASTET_HEIGHT_FACTOR; } } /** * calculate the predicted column heights for a given column range * @param pBastet bastet instance whose column heights should be predicted * @param pPiece the piece to be tested * @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 */ static void tetris_bastet_predictColHeights(tetris_bastet_variant_t *pBastet, tetris_piece_t *pPiece, int8_t nDeepestRow, int8_t nColumn, int8_t nStartCol, int8_t nStopCol) { // go through every row and calculate column heights tetris_bucket_iterator_t iterator; int8_t nHeight = 1; uint16_t *pDump = tetris_bucket_predictBottomRow(&iterator, pBastet->pBucket, pPiece, nDeepestRow, nColumn); while (pDump != NULL) { uint16_t nColMask = 0x0001 << nStartCol; for (int8_t x = nStartCol; x <= nStopCol; ++x) { if ((*pDump & nColMask) != 0) { pBastet->pColHeights[x] = nHeight; } nColMask <<= 1; } pDump = tetris_bucket_predictNextRow(&iterator); ++nHeight; } } /** * sorts the evaluated pieces by score in ascending order (via bubble sort) * @param pBastet the Bastet instance whose evaluated pieces should be sorted */ static void tetris_bastet_sortPieces(tetris_bastet_variant_t *pBastet) { for (uint8_t i = 7; i--;) { for (uint8_t j = 0; j < i; ++j) { if (pBastet->nPieceScore[j].nScore > pBastet->nPieceScore[j + 1].nScore) { tetris_bastet_scorepair_t tmp = pBastet->nPieceScore[j]; pBastet->nPieceScore[j] = pBastet->nPieceScore[j + 1]; pBastet->nPieceScore[j + 1] = tmp; } } } } /** * calculates a score for a piece at a given column * @param pBastet the bastet instance of interest * @param pPiece the piece to be tested * @param nColum the column where the piece should be dropped * @return score for the given move */ static int16_t tetris_bastet_evaluateMove(tetris_bastet_variant_t *pBastet, tetris_piece_t *pPiece, int8_t nColumn) { // initial score of the given piece int16_t nScore = -32000; // the row where the given piece collides int8_t nDeepestRow = tetris_bucket_predictDeepestRow(pBastet->pBucket, pPiece, pBastet->pStartingRow[nColumn + 3], 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, pPiece, nDeepestRow, nColumn); nScore += 5000 * nLines; // determine a sane range of columns whose heights we want to predict int8_t nWidth = tetris_bucket_getWidth(pBastet->pBucket); int8_t nStartCol, nStopCol; // if lines have been removed, we need to recalculate all column heights if (nLines != 0) { nStartCol = 0; nStopCol = nWidth - 1; } // if no lines were removed, we only need to recalculate a few columns else { nStartCol = (nColumn < 0) ? 0 : nColumn; nStopCol = (nColumn + 3) < nWidth ? nColumn + 3 : nWidth - 1; } // predict column heights of this move tetris_bastet_predictColHeights(pBastet, pPiece, nDeepestRow, nColumn, nStartCol, nStopCol); // modify score based on predicted column heights for (int8_t x = nWidth; x--;) { if ((x >= nStartCol) && (x <= nStopCol)) { nScore -= TETRIS_BASTET_HEIGHT_FACTOR * pBastet->pColHeights[x]; } else { nScore -= pBastet->pColScore[x]; } } return nScore; } /** * calculates the best possible score for every piece * @param pBastet the bastet instance of interest */ static void tetris_bastet_evaluatePieces(tetris_bastet_variant_t *pBastet) { // precache actual column heights 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); for (uint8_t nBlock = TETRIS_PC_LINE; nBlock <= TETRIS_PC_Z; ++nBlock) { int16_t nMaxScore = -32768; tetris_piece_setShape(pPiece, nBlock); int8_t nAngleCount = tetris_piece_getAngleCount(pPiece); for (uint8_t nAngle = TETRIS_PC_ANGLE_0; nAngle < nAngleCount; ++nAngle) { tetris_piece_setAngle(pPiece, nAngle); for (int8_t nCol = -3; nCol < nWidth; ++nCol) { int16_t nScore = tetris_bastet_evaluateMove(pBastet, pPiece, nCol); nMaxScore = nMaxScore > nScore ? nMaxScore : nScore; } } pBastet->nPieceScore[nBlock].shape = nBlock; pBastet->nPieceScore[nBlock].nScore = nMaxScore; } tetris_piece_destruct(pPiece); } /*************** * entry point * ***************/ #ifdef MENU_SUPPORT // Bastet icon, MSB is leftmost pixel static uint8_t bastet_icon[8] PROGMEM = { 0x81, 0xc3, 0xff, 0x99, 0xff, 0xff, 0x66, 0x3c }; game_descriptor_t bastet_game_descriptor __attribute__((section(".game_descriptors"))) = { &tetris_bastet, bastet_icon, }; #endif void tetris_bastet(void) { tetris_main(&tetrisBastetVariant); } /**************************** * construction/destruction * ****************************/ tetris_variant_t const tetrisBastetVariant = { &tetris_bastet_construct, &tetris_bastet_destruct, &tetris_bastet_choosePiece, &tetris_bastet_singleDrop, &tetris_bastet_completeDrop, &tetris_bastet_removedLines, &tetris_bastet_getScore, &tetris_bastet_getHighscore, &tetris_bastet_setHighscore, &tetris_bastet_getHighscoreName, &tetris_bastet_setHighscoreName, &tetris_bastet_getLevel, &tetris_bastet_getLines, &tetris_bastet_getPreviewPiece, &tetris_bastet_getHighscoreIndex, &tetris_bastet_setLastInput, &tetris_bastet_getBearing }; void *tetris_bastet_construct(tetris_bucket_t *pBucket) { tetris_bastet_variant_t *pBastet = (tetris_bastet_variant_t *) malloc(sizeof(tetris_bastet_variant_t)); memset(pBastet, 0, sizeof(tetris_bastet_variant_t)); pBastet->pBucket = pBucket; int8_t nWidth = tetris_bucket_getWidth(pBastet->pBucket); 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; } void tetris_bastet_destruct(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; free(pBastetVariant->pColScore); free(pBastetVariant->pColHeights); if (pBastetVariant->pPreviewPiece != NULL) { tetris_piece_destruct(pBastetVariant->pPreviewPiece); } free(pBastetVariant); } /**************************** * bastet related functions * ****************************/ tetris_piece_t* tetris_bastet_choosePiece(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastet = (tetris_bastet_variant_t *)pVariantData; // determine the best score for every piece tetris_bastet_evaluatePieces(pBastet); // perturb score (-2 to +2) to avoid stupid tie handling for (uint8_t i = 0; i < 7; ++i) { pBastet->nPieceScore[i].nScore += random8() % 5 - 2; } // sort pieces by their score in ascending order tetris_bastet_sortPieces(pBastet); // new "preview" piece (AKA "won't give you this one") if (pBastet->pPreviewPiece != NULL) { tetris_piece_destruct(pBastet->pPreviewPiece); } pBastet->pPreviewPiece = tetris_piece_construct(pBastet->nPieceScore[6].shape, TETRIS_PC_ANGLE_0); tetris_piece_t *pPiece = NULL; uint8_t const nPercent[4] = {191, 235, 250, 255}; uint8_t const nRnd = random8(); for (uint8_t i = 0; i < 4; ++i) { if (nRnd <= nPercent[i]) { // circumvent a trick where the line piece consecutively gets the // lowest score although it removes a line every time if ((pBastet->nPieceScore[i].shape == TETRIS_PC_LINE) && (pBastet->nPieceScore[i].nScore >= -28000)) { i += ((i == 0) ? 1 : -1); } pPiece = tetris_piece_construct(pBastet->nPieceScore[i].shape, TETRIS_PC_ANGLE_0); break; } } return pPiece; } void tetris_bastet_singleDrop(void *pVariantData) { return; } void tetris_bastet_completeDrop(void *pVariantData, uint8_t nLines) { return; } void tetris_bastet_removedLines(void *pVariantData, uint8_t nRowMask) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastet = (tetris_bastet_variant_t *)pVariantData; uint8_t nLines = tetris_bucket_calculateLines(nRowMask); pBastet->nLines += nLines; pBastet->nLevel = ((pBastet->nLines / 10) < TETRIS_INPUT_LEVELS) ? (pBastet->nLines / 10) : (TETRIS_INPUT_LEVELS - 1); pBastet->nScore += nLines; return; } /***************** * get functions * *****************/ uint16_t tetris_bastet_getScore(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; return pBastetVariant->nScore; } uint16_t tetris_bastet_getHighscore(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; return pBastetVariant->nHighscore; } void tetris_bastet_setHighscore(void *pVariantData, uint16_t nHighscore) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; pBastetVariant->nHighscore = nHighscore; } uint16_t tetris_bastet_getHighscoreName(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; return pBastetVariant->nHighscoreName; } void tetris_bastet_setHighscoreName(void *pVariantData, uint16_t nHighscoreName) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; pBastetVariant->nHighscoreName = nHighscoreName; } uint8_t tetris_bastet_getLevel(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastet = (tetris_bastet_variant_t *)pVariantData; return pBastet->nLevel; } uint16_t tetris_bastet_getLines(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastet = (tetris_bastet_variant_t *)pVariantData; return pBastet->nLines; } tetris_piece_t* tetris_bastet_getPreviewPiece(void *pVariantData) { assert(pVariantData != 0); tetris_bastet_variant_t *pBastetVariant = (tetris_bastet_variant_t *)pVariantData; return pBastetVariant->pPreviewPiece; } tetris_highscore_index_t tetris_bastet_getHighscoreIndex(void *pVariantData) { return TETRIS_HISCORE_BASTET; } void tetris_bastet_setLastInput(void *pVariantData, tetris_input_command_t inCmd) { return; } tetris_bearing_t tetris_bastet_getBearing(void *pVariantData) { return TETRIS_BEARING_0; }