optimized collision detection and better preprocessing for bastet (bastet algorithm almost twice as fast)

This commit is contained in:
Christian Kroll 2010-12-09 19:34:03 +00:00
parent a246779eff
commit 7deb41c236
5 changed files with 189 additions and 156 deletions

View File

@ -2,6 +2,7 @@
#include <string.h>
#include <assert.h>
#include <stdint.h>
#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]);
}
else if (nColumn >= pBucket->nWidth - 3)
{
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);
}
// here we check the part which has already entered the bucket
for (int8_t y = (nRow < 0) ? -nRow : 0; y < 4; ++y)
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)
{
// 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);
// finally check for a collision
if ((nBucketPart & nPieceRowMap) != 0)
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;
uint16_t nPiece = tetris_piece_getBitmap(pPiece);
for (int8_t i = nStartRow; i >= nStopRow; --i)
{
// 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;
int8_t y = i - nRow;
uint16_t nPiece = tetris_piece_getBitmap(pPiece);
for (int8_t i = nStartRow; i >= nStopRow; --i)
// 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)
{
int8_t y = i - nRow;
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;
}
}

View File

@ -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);

View File

@ -1,14 +1,14 @@
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include "tetris_main.h"
#include "variants.h"
#include "bearing.h"
#include "piece.h"
#include "bucket.h"
#include "view.h"
#include "input.h"
#include "highscore.h"
#include "bucket.h"
#include "input.h"
#include "variants.h"
#include "view.h"
#include "tetris_main.h"
void tetris_main(tetris_variant_t const *const pVariantMethods)

View File

@ -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);

View File

@ -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 */
}