pcb and initial code from https://github.com/das-labor/borgware-2d.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
16 KiB
595 lines
16 KiB
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include "playfield.h"
|
|
#include "piece.h"
|
|
|
|
|
|
/***************************
|
|
* non-interface functions *
|
|
***************************/
|
|
|
|
/* Function: tetris_playfield_hoverStatus;
|
|
* Description: determines if piece is either hovering or gliding
|
|
* Argument pPl: playfield perform action on
|
|
* Return value: TETRIS_PFS_HOVERING or TETRIS_PFS_GLIDING
|
|
*/
|
|
tetris_playfield_status_t tetris_playfield_hoverStatus(tetris_playfield_t* pPl)
|
|
{
|
|
// if the piece touches the dump we ensure that the status is "gliding"
|
|
if (tetris_playfield_collision(pPl, pPl->nColumn, pPl->nRow + 1))
|
|
{
|
|
return TETRIS_PFS_GLIDING;
|
|
}
|
|
// otherwise the status must be "hovering"
|
|
else
|
|
{
|
|
return TETRIS_PFS_HOVERING;
|
|
}
|
|
}
|
|
|
|
|
|
/****************************
|
|
* construction/destruction *
|
|
****************************/
|
|
|
|
/* Function: tetris_playfield_construct
|
|
* Description: constructs a playfield with the given diemensions
|
|
* Argument nWidth: width of playfield (4 <= n <= 16)
|
|
* Argument nHeight: height of playfield (4 <= n <= 124)
|
|
* Return value: pointer to a newly created playfield
|
|
*/
|
|
tetris_playfield_t *tetris_playfield_construct(int8_t nWidth,
|
|
int8_t nHeight)
|
|
{
|
|
assert((nWidth >= 4) && (nWidth <= 16));
|
|
assert((nHeight >= 4) && (nHeight <= 124));
|
|
|
|
tetris_playfield_t *pPlayfield =
|
|
(tetris_playfield_t*) malloc(sizeof(tetris_playfield_t));
|
|
|
|
if (pPlayfield != NULL)
|
|
{
|
|
// allocating mem for dump array
|
|
pPlayfield->dump = (uint16_t*) calloc(nHeight, sizeof(uint16_t));
|
|
|
|
if (pPlayfield->dump != NULL)
|
|
{
|
|
// setting desired attributes
|
|
pPlayfield->nWidth = nWidth;
|
|
pPlayfield->nHeight = nHeight;
|
|
tetris_playfield_reset(pPlayfield);
|
|
|
|
return pPlayfield;
|
|
}
|
|
else
|
|
{
|
|
free(pPlayfield);
|
|
pPlayfield = NULL;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_destruct
|
|
* Description: destructs a playfield
|
|
* Argument pPl: pointer to the playfield to be destructed
|
|
* Return value: void
|
|
*/
|
|
void tetris_playfield_destruct(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
// if memory for the dump array has been allocated, free it
|
|
if (pPl->dump != NULL)
|
|
{
|
|
free(pPl->dump);
|
|
}
|
|
free(pPl);
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* playfield related functions *
|
|
*******************************/
|
|
|
|
/* Function: tetris_playfield_reset
|
|
* Description: resets playfield to begin a new game
|
|
* Argument pPl: playfield to perform action on
|
|
* Return value: void
|
|
*/
|
|
void tetris_playfield_reset(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
pPl->pPiece = NULL;
|
|
pPl->nColumn = 0;
|
|
pPl->nRow = 0;
|
|
pPl->nRowMask = 0;
|
|
|
|
// clear dump if it has been allocated in memory
|
|
if (pPl->dump != NULL)
|
|
{
|
|
memset(pPl->dump, 0, pPl->nHeight);
|
|
}
|
|
|
|
pPl->status = TETRIS_PFS_READY;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_insertPiece
|
|
* Description: inserts a new piece
|
|
* Argument pPl: playfield to perform action on
|
|
* Argument pPiece: piece to be inserted
|
|
* Argument ppOldPiece: [out] indirect pointer to former piece for deallocation
|
|
* Return value: void
|
|
*/
|
|
void tetris_playfield_insertPiece(tetris_playfield_t *pPl,
|
|
tetris_piece_t *pPiece,
|
|
tetris_piece_t** ppOldPiece)
|
|
{
|
|
assert((pPl != NULL) && (pPiece != NULL) && (ppOldPiece != NULL));
|
|
|
|
// a piece can only be inserted in state TETRIS_PFS_READY
|
|
assert(pPl->status == TETRIS_PFS_READY);
|
|
|
|
// row mask is now meaningless
|
|
pPl->nRowMask = 0;
|
|
|
|
// replace old piece
|
|
*ppOldPiece = pPl->pPiece;
|
|
pPl->pPiece = pPiece;
|
|
|
|
// set horizontal start position (in the middle of the top line)
|
|
pPl->nColumn = (pPl->nWidth - 2) / 2;
|
|
|
|
// set vertical start position (first piece row with matter at pos. 1)
|
|
uint16_t nPieceMap = tetris_piece_getBitmap(pPl->pPiece);
|
|
uint16_t nElementMask = 0xF000;
|
|
pPl->nRow = -3;
|
|
while ((nPieceMap & nElementMask) == 0)
|
|
{
|
|
++pPl->nRow;
|
|
nElementMask >>= 4;
|
|
}
|
|
if (pPl->nRow < 0)
|
|
{
|
|
++pPl->nRow;
|
|
}
|
|
|
|
// did we already collide with something?
|
|
if (tetris_playfield_collision(pPl, pPl->nColumn, pPl->nRow) == 1)
|
|
{
|
|
// game over man, game over!!
|
|
pPl->status = TETRIS_PFS_GAMEOVER;
|
|
}
|
|
else
|
|
{
|
|
// bring it on!
|
|
pPl->status = tetris_playfield_hoverStatus(pPl);
|
|
}
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_collision
|
|
* Description: detects if piece collides with s.th. at a given position
|
|
* Argument pPl: playfield to perform action on
|
|
* Argument nColumn: column where the piece should be moved
|
|
* Argument nRow: row where the piece should be moved
|
|
* Return value: 1 for collision, 0 otherwise
|
|
*/
|
|
uint8_t tetris_playfield_collision(tetris_playfield_t *pPl,
|
|
int8_t nColumn,
|
|
int8_t nRow)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
// only allow coordinates which are within sane ranges
|
|
assert((nColumn >= -4) && (nColumn < pPl->nWidth));
|
|
assert((nRow >= -4) && (nRow < pPl->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 playfield 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 playfield 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(pPl->pPiece);
|
|
uint16_t nPlayfieldPart;
|
|
uint16_t nPieceRowMap;
|
|
|
|
// negative nRow values indicate that the piece hasn't fully entered the
|
|
// playfield yet which requires special treatment if the piece overlaps
|
|
// with either the left or the right border
|
|
if (nRow < 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) >= pPl->nWidth)
|
|
{
|
|
nBorderMask = 0x8888 >> ((nColumn + 3) - pPl->nWidth);
|
|
}
|
|
// return if piece collides with border
|
|
if ((nPieceMap & nBorderMask) != 0)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// here we check the part which has already entered the playfield
|
|
for (int8_t y = (nRow < 0) ? -nRow : 0; y < 4; ++y)
|
|
{
|
|
// current piece row overlaps with lower border
|
|
if ((y + nRow) >= pPl->nHeight)
|
|
{
|
|
// all 4 bits represent the lower border
|
|
nPlayfieldPart = 0x000F;
|
|
}
|
|
// piece overlaps with left border
|
|
else if (nColumn < 0)
|
|
{
|
|
// clear all bits we are not interested in
|
|
nPlayfieldPart = (pPl->dump[y + nRow] & (0x000F >> -nColumn));
|
|
// add zeros to the left (the bits "behind" the left border)
|
|
nPlayfieldPart <<= -nColumn;
|
|
// set bits beyond left border to 1
|
|
nPlayfieldPart |= 0x000F >> (4 + nColumn);
|
|
}
|
|
// piece overlaps with right border
|
|
else if ((nColumn + 3) >= pPl->nWidth)
|
|
{
|
|
// align the bits we are interested in to LSB
|
|
// (thereby clearing the rest)
|
|
nPlayfieldPart = pPl->dump[y + nRow] >> nColumn;
|
|
// set bits beyond right border to 1
|
|
nPlayfieldPart |= 0xFFF8 >> (nColumn + 3 - pPl->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
|
|
nPlayfieldPart =
|
|
(pPl->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 ((nPlayfieldPart & nPieceRowMap) != 0)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// if we reach here, no collision was detected
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_advancePiece
|
|
* Description: lowers piece by one row or finally docks it
|
|
* Argument pPl: playfield to perform action on
|
|
* Return value: void
|
|
*/
|
|
void tetris_playfield_advancePiece(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
// a piece can only be lowered if it is hovering or gliding
|
|
assert ((pPl->status == TETRIS_PFS_HOVERING) ||
|
|
(pPl->status == TETRIS_PFS_GLIDING));
|
|
|
|
if (tetris_playfield_collision(pPl, pPl->nColumn, pPl->nRow + 1))
|
|
{
|
|
uint16_t nPiece = tetris_piece_getBitmap(pPl->pPiece);
|
|
|
|
// Is the playfield filled up?
|
|
if ((pPl->nRow < 0) && (nPiece & (0x0FFF >> ((3 + pPl->nRow) << 2))) != 0)
|
|
{
|
|
pPl->status = TETRIS_PFS_GAMEOVER;
|
|
}
|
|
else
|
|
{
|
|
// determine valid start point for dump index
|
|
int8_t nStartRow = ((pPl->nRow + 3) < pPl->nHeight) ?
|
|
(pPl->nRow + 3) : pPl->nHeight - 1;
|
|
for (int8_t i = nStartRow; i >= pPl->nRow; --i)
|
|
{
|
|
int8_t y = i - pPl->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 (pPl->nColumn >= 0)
|
|
{
|
|
nPieceMap <<= pPl->nColumn;
|
|
}
|
|
else
|
|
{
|
|
nPieceMap >>= -pPl->nColumn;
|
|
}
|
|
// embed piece in playfield
|
|
pPl->dump[i] |= nPieceMap;
|
|
}
|
|
|
|
// the piece has finally been docked
|
|
pPl->status = TETRIS_PFS_DOCKED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// since there is no collision the piece may continue its travel
|
|
// to the ground...
|
|
pPl->nRow++;
|
|
|
|
// are we gliding?
|
|
pPl->status = tetris_playfield_hoverStatus(pPl);
|
|
}
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_movePiece
|
|
* Description: moves piece to the given direction
|
|
* Argument pPl: playfield to perform action on
|
|
* Argument direction: direction (see tetris_playfield_direction_t)
|
|
* Return value: 1 if piece could be moved, 0 otherwise
|
|
*/
|
|
uint8_t tetris_playfield_movePiece(tetris_playfield_t *pPl,
|
|
tetris_playfield_direction_t direction)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
// a piece can only be moved if it is still hovering or gliding
|
|
assert((pPl->status == TETRIS_PFS_HOVERING) ||
|
|
(pPl->status == TETRIS_PFS_GLIDING));
|
|
|
|
int8_t nOffset = (direction == TETRIS_PFD_LEFT) ? -1 : 1;
|
|
if (tetris_playfield_collision(pPl, pPl->nColumn + nOffset, pPl->nRow) == 0)
|
|
{
|
|
pPl->nColumn += nOffset;
|
|
|
|
// are we gliding?
|
|
pPl->status = tetris_playfield_hoverStatus(pPl);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_rotatePiece
|
|
* Description: rotates piece to the given direction
|
|
* Argument pPl: playfield to perform action on
|
|
* Argument r: type of rotation (see tetris_piece_rotation_t)
|
|
* Return value: 1 if piece could be rotated, 0 otherwise
|
|
*/
|
|
uint8_t tetris_playfield_rotatePiece(tetris_playfield_t *pPl,
|
|
tetris_piece_rotation_t rotation)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
// a piece can only be rotation if it is still hovering or gliding
|
|
assert((pPl->status == TETRIS_PFS_HOVERING) ||
|
|
(pPl->status == TETRIS_PFS_GLIDING));
|
|
|
|
tetris_piece_rotate(pPl->pPiece, rotation);
|
|
|
|
// does the rotated piece cause a collision?
|
|
if (tetris_playfield_collision(pPl, pPl->nColumn, pPl->nRow) != 0)
|
|
{
|
|
// in that case we revert the rotation
|
|
if (rotation == TETRIS_PC_ROT_CW)
|
|
{
|
|
tetris_piece_rotate(pPl->pPiece, TETRIS_PC_ROT_CCW);
|
|
}
|
|
else
|
|
{
|
|
tetris_piece_rotate(pPl->pPiece, TETRIS_PC_ROT_CW);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// are we gliding?
|
|
pPl->status = tetris_playfield_hoverStatus(pPl);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_removeCompletedLines
|
|
* Description: removes completed lines (if any) and lowers the dump
|
|
* Argument pPl: playfield to perform action on
|
|
* Return value: void
|
|
*/
|
|
void tetris_playfield_removeCompleteLines(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
|
|
// rows can only be removed if we are in state TETRIS_PFS_DOCKED
|
|
assert(pPl->status == TETRIS_PFS_DOCKED);
|
|
|
|
// bit mask of a full row
|
|
uint16_t nFullRow = 0xFFFF >> (16 - pPl->nWidth);
|
|
|
|
// bit mask (only 4 bits) that tells us if the n-th row after the
|
|
// current nRow is complete (n-th bit set to 1, LSB represents nRow itself)
|
|
uint8_t nRowMask = 0;
|
|
|
|
// determine sane start and stop values for the dump' index
|
|
int8_t nStartRow =
|
|
((pPl->nRow + 3) >= pPl->nHeight) ? pPl->nHeight - 1 : pPl->nRow + 3;
|
|
int8_t nStopRow = (pPl->nRow < 0) ? 0 : pPl->nRow;
|
|
|
|
// dump index variables
|
|
// for incomplete rows, both variables will be decremented
|
|
// for complete rows, only i gets decremented
|
|
int8_t nLowestRow = nStartRow;
|
|
|
|
// this loop only considers rows which are affected by the piece
|
|
for (int8_t i = nStartRow; i >= nStopRow; --i)
|
|
{
|
|
// is current row a full row?
|
|
if ((nFullRow & pPl->dump[i]) == nFullRow)
|
|
{
|
|
// set corresponding bit for the row mask
|
|
// nRowMask |= 0x08 >> (nStartRow - i);
|
|
nRowMask |= 0x01 << (i - pPl->nRow);
|
|
}
|
|
else
|
|
{
|
|
// if nLowestRow and i differ, the dump has to be shifted
|
|
if (i < nLowestRow)
|
|
{
|
|
pPl->dump[nLowestRow] = pPl->dump[i];
|
|
}
|
|
--nLowestRow;
|
|
}
|
|
}
|
|
|
|
// if rows have been removed, this loop shifts the rest of the dump
|
|
uint8_t nComplete = nLowestRow - nStopRow + 1;
|
|
if (nComplete > 0)
|
|
{
|
|
for (int8_t i = nStopRow - 1; nLowestRow >= 0; --i)
|
|
{
|
|
// is the row we are copying from below the upper border?
|
|
if (i >= 0)
|
|
{
|
|
// just copy from that row
|
|
pPl->dump[nLowestRow] = pPl->dump[i];
|
|
}
|
|
else
|
|
{
|
|
// rows above the upper border are always empty
|
|
pPl->dump[nLowestRow] = 0;
|
|
}
|
|
--nLowestRow;
|
|
}
|
|
}
|
|
|
|
// ready to get the next piece
|
|
pPl->status = TETRIS_PFS_READY;
|
|
|
|
pPl->nRowMask = nRowMask;
|
|
}
|
|
|
|
|
|
/*****************
|
|
* get functions *
|
|
*****************/
|
|
|
|
/* Function: tetris_playfield_getWidth
|
|
* Description: returns the width of the playfield
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: width of the playfield
|
|
*/
|
|
int8_t tetris_playfield_getWidth(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->nWidth;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getHeight
|
|
* Description: returns the height of the playfield
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: height of the playfield
|
|
*/
|
|
int8_t tetris_playfield_getHeight(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->nHeight;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getPiece
|
|
* Description: returns the currently falling piece
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: pointer to the currently falling piece
|
|
*/
|
|
tetris_piece_t *tetris_playfield_getPiece(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->pPiece;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getColumn
|
|
* Description: returns the column of the currently falling piece
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: column of the currently falling piece
|
|
*/
|
|
int8_t tetris_playfield_getColumn(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->nColumn;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getRow
|
|
* Description: returns the row of the currently falling piece
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: row of the currently falling piece
|
|
*/
|
|
int8_t tetris_playfield_getRow(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->nRow;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getRowMask
|
|
* Description: returns the row mask relative to nRow
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: the first 4 bits indicate which lines (relative to nRow)
|
|
* have been removed if we are in status TETRIS_PFS_READY
|
|
* LSB is the highest line
|
|
*/
|
|
uint8_t tetris_playfield_getRowMask(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->nRowMask;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getStatus
|
|
* Description: returns the status of the playfield
|
|
* Argument pPl: the playfield we want information from
|
|
* Return value: status of the playfield (see tetris_playfield_status_t)
|
|
*/
|
|
tetris_playfield_status_t tetris_playfield_getStatus(tetris_playfield_t *pPl)
|
|
{
|
|
assert(pPl != NULL);
|
|
return pPl->status;
|
|
}
|
|
|
|
|
|
/* Function: tetris_playfield_getDumpRow
|
|
* Description: returns the given row of the dump (as bitmap)
|
|
* Argument pPl: the playfield we want information from
|
|
* Argument nRow: the number of the row (0 <= nRow < height of playfield)
|
|
* Return value: bitmap of the requested row (LSB is leftmost column)
|
|
*/
|
|
uint16_t tetris_playfield_getDumpRow(tetris_playfield_t *pPl,
|
|
int8_t nRow)
|
|
{
|
|
assert(pPl != NULL);
|
|
assert((0 <= nRow) && (nRow < pPl->nHeight));
|
|
return pPl->dump[nRow];
|
|
}
|
|
|
|
|