borgware-2d/src/games/tetris/view.c

677 lines
18 KiB
C

/**
* \addtogroup tetris
* @{
*/
/**
* @file view.c
* @brief Implementation of Tetris' graphical output routines.
* @author Christian Kroll
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#include "../../config.h"
#include "../../pixel.h"
#include "../../util.h"
#include "../../scrolltext/scrolltext.h"
#include "bucket.h"
#include "piece.h"
#include "variants.h"
#include "view.h"
/***********
* 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
#if (((NUM_ROWS < 16) && (NUM_COLS > NUM_ROWS)) || \
((NUM_ROWS == 16) && (NUM_COLS == 80))) && (!defined GAME_TETRIS_FP)
# define VIEWCOLS NUM_ROWS
# define VIEWROWS NUM_COLS
# define VIEW_TILT
#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 + \
TETRIS_VIEW_YOFFSET_DUMP + ((TETRIS_VIEW_HEIGHT_DUMP - 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
/***************************
* 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
*/
static void tetris_view_setpixel(tetris_bearing_t nBearing,
uint8_t x,
uint8_t y,
uint8_t nColor)
{
#ifdef VIEW_TILT
// tilt counter clockwise
nBearing = (nBearing + 3) % 4u;
#endif
x = VIEWCOLS - 1 - x;
pixel px;
switch (nBearing)
{
default:
case TETRIS_BEARING_0:
px = (pixel){x, y};
break;
case TETRIS_BEARING_90:
px = (pixel){y, VIEWCOLS - 1 - x};
break;
case TETRIS_BEARING_180:
px = (pixel){VIEWCOLS - 1 - x, VIEWROWS - 1 - y};
break;
case TETRIS_BEARING_270:
px = (pixel){VIEWROWS - 1 - y, x};
break;
}
setpixel(px, nColor);
}
/**
* 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
*/
inline static 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
*/
inline static 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
*/
inline static 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
*/
static void tetris_view_drawDump(tetris_view_t *pV)
{
assert(pV->pBucket != NULL);
if (tetris_bucket_getRow(pV->pBucket) <= -4)
{
return;
}
tetris_bearing_t const nBearing =
pV->pVariantMethods->getBearing(pV->pVariant);
for (int8_t nRow = TETRIS_VIEW_HEIGHT_DUMP - 1; nRow >= 0; --nRow)
{
uint16_t nRowMap = tetris_bucket_getDumpRow(pV->pBucket, nRow);
// if a piece is hovering or gliding it needs to be drawn
int8_t nPieceRow = tetris_bucket_getRow(pV->pBucket);
tetris_bucket_status_t status = tetris_bucket_getStatus(pV->pBucket);
if (((status == TETRIS_BUS_HOVERING) || (status == TETRIS_BUS_GLIDING)
|| (status == TETRIS_BUS_GAMEOVER)) && (nRow >= nPieceRow)
&& (nRow <= nPieceRow + 3))
{
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
uint8_t y = (uint8_t)(nRow - nPieceRow);
nPieceMap = (nPieceMap & (0x000Fu << (y * 4u))) >> (y * 4u);
// shift remaining part to current column and embed piece into view
nRowMap |= nColumn >= 0 ?
nPieceMap << nColumn : nPieceMap >> -nColumn;
}
uint16_t nElementMask = 0x0001;
for (uint8_t x = 0; x < TETRIS_VIEW_WIDTH_DUMP; ++x)
{
unsigned char nColor = (nRowMap & nElementMask) ?
tetris_view_getPieceColor(pV) : TETRIS_VIEW_COLORSPACE;
tetris_view_setpixel(nBearing, TETRIS_VIEW_XOFFSET_DUMP + x,
TETRIS_VIEW_YOFFSET_DUMP + (uint8_t)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)
*/
static 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
*/
static 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
*/
static void tetris_view_blinkBorders(tetris_view_t *pV)
{
for (uint8_t i = TETRIS_VIEW_BORDER_BLINK_COUNT * 2; i--;)
{
tetris_view_drawBorders(pV, (i & 0x01) ?
TETRIS_VIEW_COLORBORDER : TETRIS_VIEW_COLORPIECE);
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
*/
static void tetris_view_blinkLines(tetris_view_t *pV)
{
// reduce necessity of pointer arithmetic
int8_t nRow = tetris_bucket_getRow(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, nMask = 0x01; j <= nDeepestRowOffset; ++j)
{
// is current line a complete line?
if ((tetris_bucket_getRowMask(pV->pBucket) & nMask) != 0)
{
// draw line in current color
int8_t y = nRow + j;
for (int8_t x = tetris_bucket_getWidth(pV->pBucket); x--;)
{
uint8_t nColor = (nColIdx == 0 ? TETRIS_VIEW_COLORFADE
: TETRIS_VIEW_COLORPIECE);
tetris_view_setpixel(nBearing,
TETRIS_VIEW_XOFFSET_DUMP + (uint8_t)x,
TETRIS_VIEW_YOFFSET_DUMP + (uint8_t)y,
nColor);
}
}
nMask <<= 1;
}
// wait a few ms to make the blink effect visible
wait(TETRIS_VIEW_LINE_BLINK_DELAY);
}
}
}
#ifdef TETRIS_VIEW_XOFFSET_COUNTER
/**
* draws counter of completed rows (0-399)
* @param pV pointer to the view
*/
static void tetris_view_drawLineCounter(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
uint8_t nOnes = nLines % 10;
uint8_t nTens = (nLines / 10) % 10;
uint8_t nHundreds = (nLines / 100) % 10;
// draws the decimal places as 3x3 squares with 9 pixels
for (uint8_t 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
*/
static void tetris_view_formatHighscoreName(uint16_t nHighscoreName,
char *pszName)
{
for (uint8_t i = 3; i--; nHighscoreName >>= 5)
{
if ((pszName[i] = (nHighscoreName & 0x1F) + 65) == '_')
{
pszName[i] = ' ';
}
}
pszName[3] = '\0';
}
/****************************
* construction/destruction *
****************************/
tetris_view_t *tetris_view_construct(tetris_variant_t const *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;
}
/***************************
* 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_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_drawLineCounter(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),
"</#Lines %u Score %u Highscore %u (%s)",
nLines, nScore, nHighscore, pszHighscoreName);
}
else
{
snprintf(pszResults, sizeof(pszResults),
"</#Lines %u New Highscore %u", nLines, nScore);
}
scrolltext(pszResults);
#endif
}
/*@}*/