borgware-2d/games/tetris/tetris_main.c

209 lines
6.3 KiB
C

#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include "bearing.h"
#include "piece.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)
{
// get view dependent dimensions of the bucket
int8_t nWidth, nHeight;
tetris_view_getDimensions(&nWidth, &nHeight);
// holds the current user command which should be processed
tetris_input_command_t inCmd;
// prepare data structures that drive the game...
tetris_bucket_t *pBucket = tetris_bucket_construct(nWidth, nHeight);
void *pVariantData = pVariantMethods->construct(pBucket);
tetris_input_t *pIn = tetris_input_construct();
tetris_view_t *pView = tetris_view_construct(pVariantMethods,
pVariantData, pBucket);
// retrieve highscore
tetris_highscore_index_t nHighscoreIndex =
pVariantMethods->getHighscoreIndex(pVariantData);
uint16_t nHighscore =
tetris_highscore_retrieveHighScore(nHighscoreIndex);
uint16_t nHighscoreName =
tetris_highscore_retrieveHighScoreName(nHighscoreIndex);
// the view only monitors the variant data and the bucket object for the
// game status so we must put information like the next piece or the current
// highscore to a place where the view can find it
pVariantMethods->setHighscore(pVariantData, nHighscore);
pVariantMethods->setHighscoreName(pVariantData, nHighscoreName);
int8_t nPieceRow; // for determining skipped lines after a piece drop
tetris_piece_t *pPiece = NULL; // initialize piece
tetris_input_pace_t inPace; // pace flag
// game loop, runs as long as the game is not over
while (tetris_bucket_getStatus(pBucket) != TETRIS_BUS_GAMEOVER)
{
// what we do strongly depends on the status of the bucket
switch (tetris_bucket_getStatus(pBucket))
{
// the bucket awaits a new piece
case TETRIS_BUS_READY:
pPiece = pVariantMethods->choosePiece(pVariantData);
// destruct old piece (if it exists) since we don't need it anymore
tetris_piece_t *pOld;
if ((pOld = tetris_bucket_insertPiece(pBucket, pPiece)) != NULL)
{
tetris_piece_destruct(pOld);
}
break;
// a piece is hovering and can be controlled by the player
case TETRIS_BUS_HOVERING:
case TETRIS_BUS_GLIDING:
// if the piece is gliding the input module has to grant us
// a minimum amount of time to move it
if (tetris_bucket_getStatus(pBucket) == TETRIS_BUS_GLIDING)
{
inPace = TETRIS_INPACE_GLIDING;
}
else
{
inPace = TETRIS_INPACE_HOVERING;
}
// ensure correct view mode if the game isn't paused
if ((inCmd = tetris_input_getCommand(pIn, inPace))
!= TETRIS_INCMD_PAUSE)
{
tetris_view_setViewMode(pView, TETRIS_VIMO_RUNNING);
}
// helper variable which tells us whether the last move was possible
uint8_t bMoveOk = 1;
// what we do depends on what the input module tells us
switch (inCmd)
{
// game paused?
case TETRIS_INCMD_PAUSE:
// tell the view it should display the pause screen
tetris_view_setViewMode(pView, TETRIS_VIMO_PAUSED);
break;
// the piece was pulled down by the almighty gravity
case TETRIS_INCMD_GRAVITY:
tetris_bucket_advancePiece(pBucket);
break;
// the player has pulled down the piece herself/himself
case TETRIS_INCMD_DOWN:
tetris_bucket_advancePiece(pBucket);
// if the game still runs, reward the player with extra points
if (tetris_bucket_getStatus(pBucket) != TETRIS_BUS_GAMEOVER)
{
pVariantMethods->singleDrop(pVariantData);
}
break;
// player shifted the piece to the left
case TETRIS_INCMD_LEFT:
bMoveOk = tetris_bucket_movePiece(pBucket, TETRIS_BUD_LEFT);
break;
// player shifted the piece to the right
case TETRIS_INCMD_RIGHT:
bMoveOk = tetris_bucket_movePiece(pBucket, TETRIS_BUD_RIGHT);
break;
// player rotated the piece clockwise
case TETRIS_INCMD_ROT_CW:
bMoveOk = tetris_bucket_rotatePiece(pBucket, TETRIS_PC_ROT_CW);
break;
// player rotated the piece counter clockwise
case TETRIS_INCMD_ROT_CCW:
bMoveOk = tetris_bucket_rotatePiece(pBucket, TETRIS_PC_ROT_CCW);
break;
// the player decided to make an immediate drop
case TETRIS_INCMD_DROP:
nPieceRow = tetris_bucket_getRow(pBucket);
// emulate immediate drop
while(tetris_bucket_getStatus(pBucket) == TETRIS_BUS_GLIDING ||
tetris_bucket_getStatus(pBucket) == TETRIS_BUS_HOVERING)
{
tetris_bucket_advancePiece(pBucket);
}
// if the game still runs, reward the player with extra points
if (tetris_bucket_getStatus(pBucket) != TETRIS_BUS_GAMEOVER)
{
pVariantMethods->completeDrop(pVariantData,
tetris_bucket_getRow(pBucket) - nPieceRow);
}
break;
// avoid compiler warnings
default:
break;
}
// inform variant object about the user's last move
pVariantMethods->setLastInput(pVariantData, inCmd, bMoveOk);
// inform the input module about the requested bearing of the
// variant object
tetris_input_setBearing(pIn,
pVariantMethods->getBearing(pVariantData));
break;
// the piece has irrevocably hit the ground
case TETRIS_BUS_DOCKED:
// avoid accidentally issued "down" commands
tetris_input_resetDownKeyRepeat(pIn);
// remove complete lines (if any)
tetris_bucket_removeCompleteLines(pBucket);
// let the variant object decide how many points the player gets and
// whether the level gets changed
pVariantMethods->removedLines(pVariantData,
tetris_bucket_getRowMask(pBucket));
tetris_input_setLevel(pIn, pVariantMethods->getLevel(pVariantData));
break;
// avoid compiler warnings
default:
break;
}
// the view updates its state every loop cycle to make changes visible
tetris_view_update(pView);
}
// game is over and we provide the player with her/his results
tetris_view_showResults(pView);
// update highscore if it has been beaten
uint16_t nScore = pVariantMethods->getScore(pVariantData);
if (nScore > nHighscore)
{
nHighscore = nScore;
nHighscoreName = tetris_highscore_inputName();
tetris_highscore_saveHighScore(nHighscoreIndex, nHighscore);
tetris_highscore_saveHighScoreName(nHighscoreIndex, nHighscoreName);
}
// cleanup
tetris_view_destruct(pView);
tetris_input_destruct(pIn);
pVariantMethods->destruct(pVariantData);
tetris_bucket_destruct(pBucket);
tetris_piece_destruct(pPiece);
}