2012-05-07 06:56:00 +00:00
|
|
|
/**
|
|
|
|
* \defgroup tetris Tetris, the popular puzzle game.
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @file tetris_main.c
|
|
|
|
* @brief Main loop of the Tetris module.
|
|
|
|
* @author Christian Kroll
|
|
|
|
*/
|
|
|
|
|
2010-04-01 03:11:59 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <assert.h>
|
2010-08-24 23:00:40 +00:00
|
|
|
#include <stdint.h>
|
2010-12-09 19:34:03 +00:00
|
|
|
#include "bearing.h"
|
2010-04-01 03:11:59 +00:00
|
|
|
#include "piece.h"
|
2010-12-09 19:34:03 +00:00
|
|
|
#include "highscore.h"
|
2010-08-22 03:07:46 +00:00
|
|
|
#include "bucket.h"
|
2010-04-01 03:11:59 +00:00
|
|
|
#include "input.h"
|
2010-12-09 19:34:03 +00:00
|
|
|
#include "variants.h"
|
|
|
|
#include "view.h"
|
|
|
|
#include "tetris_main.h"
|
2010-04-01 03:11:59 +00:00
|
|
|
|
2010-08-24 23:00:40 +00:00
|
|
|
void tetris_main(tetris_variant_t const *const pVariantMethods)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
2010-08-22 03:07:46 +00:00
|
|
|
// get view dependent dimensions of the bucket
|
2011-02-06 22:56:26 +00:00
|
|
|
int8_t nWidth, nHeight;
|
2010-04-01 03:11:59 +00:00
|
|
|
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...
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_t *pBucket = tetris_bucket_construct(nWidth, nHeight);
|
|
|
|
void *pVariantData = pVariantMethods->construct(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
tetris_input_t *pIn = tetris_input_construct();
|
|
|
|
tetris_view_t *pView = tetris_view_construct(pVariantMethods,
|
2010-08-22 03:07:46 +00:00
|
|
|
pVariantData, pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
|
|
|
|
// retrieve highscore
|
|
|
|
tetris_highscore_index_t nHighscoreIndex =
|
|
|
|
pVariantMethods->getHighscoreIndex(pVariantData);
|
|
|
|
uint16_t nHighscore =
|
2012-03-23 23:21:46 +00:00
|
|
|
tetris_highscore_retrieveHighScore(nHighscoreIndex);
|
2010-04-01 03:11:59 +00:00
|
|
|
uint16_t nHighscoreName =
|
2012-03-23 23:21:46 +00:00
|
|
|
tetris_highscore_retrieveHighScoreName(nHighscoreIndex);
|
2010-04-01 03:11:59 +00:00
|
|
|
|
2010-08-22 03:07:46 +00:00
|
|
|
// the view only monitors the variant data and the bucket object for the
|
2010-04-01 03:11:59 +00:00
|
|
|
// 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
|
2010-08-22 03:07:46 +00:00
|
|
|
while (tetris_bucket_getStatus(pBucket) != TETRIS_BUS_GAMEOVER)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
2010-08-22 03:07:46 +00:00
|
|
|
// what we do strongly depends on the status of the bucket
|
|
|
|
switch (tetris_bucket_getStatus(pBucket))
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
2010-08-22 03:07:46 +00:00
|
|
|
// the bucket awaits a new piece
|
|
|
|
case TETRIS_BUS_READY:
|
2010-04-01 03:11:59 +00:00
|
|
|
pPiece = pVariantMethods->choosePiece(pVariantData);
|
|
|
|
// destruct old piece (if it exists) since we don't need it anymore
|
2011-01-30 22:47:44 +00:00
|
|
|
tetris_piece_t *pOld;
|
|
|
|
if ((pOld = tetris_bucket_insertPiece(pBucket, pPiece)) != NULL)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
2011-01-30 22:47:44 +00:00
|
|
|
tetris_piece_destruct(pOld);
|
2010-04-01 03:11:59 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// a piece is hovering and can be controlled by the player
|
2010-08-22 03:07:46 +00:00
|
|
|
case TETRIS_BUS_HOVERING:
|
|
|
|
case TETRIS_BUS_GLIDING:
|
2010-04-01 03:11:59 +00:00
|
|
|
// if the piece is gliding the input module has to grant us
|
|
|
|
// a minimum amount of time to move it
|
2010-08-22 03:07:46 +00:00
|
|
|
if (tetris_bucket_getStatus(pBucket) == TETRIS_BUS_GLIDING)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-03-07 00:31:18 +00:00
|
|
|
// helper variable which tells us whether the last move was possible
|
|
|
|
uint8_t bMoveOk = 1;
|
|
|
|
|
2010-04-01 03:11:59 +00:00
|
|
|
// 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:
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_advancePiece(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// the player has pulled down the piece herself/himself
|
|
|
|
case TETRIS_INCMD_DOWN:
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_advancePiece(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
// if the game still runs, reward the player with extra points
|
2010-08-22 03:07:46 +00:00
|
|
|
if (tetris_bucket_getStatus(pBucket) != TETRIS_BUS_GAMEOVER)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
2011-02-06 22:56:26 +00:00
|
|
|
pVariantMethods->singleDrop(pVariantData);
|
2010-04-01 03:11:59 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// player shifted the piece to the left
|
|
|
|
case TETRIS_INCMD_LEFT:
|
2011-03-07 00:31:18 +00:00
|
|
|
bMoveOk = tetris_bucket_movePiece(pBucket, TETRIS_BUD_LEFT);
|
2010-04-01 03:11:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// player shifted the piece to the right
|
|
|
|
case TETRIS_INCMD_RIGHT:
|
2011-03-07 00:31:18 +00:00
|
|
|
bMoveOk = tetris_bucket_movePiece(pBucket, TETRIS_BUD_RIGHT);
|
2010-04-01 03:11:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// player rotated the piece clockwise
|
|
|
|
case TETRIS_INCMD_ROT_CW:
|
2011-03-07 00:31:18 +00:00
|
|
|
bMoveOk = tetris_bucket_rotatePiece(pBucket, TETRIS_PC_ROT_CW);
|
2010-04-01 03:11:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// player rotated the piece counter clockwise
|
|
|
|
case TETRIS_INCMD_ROT_CCW:
|
2011-03-07 00:31:18 +00:00
|
|
|
bMoveOk = tetris_bucket_rotatePiece(pBucket, TETRIS_PC_ROT_CCW);
|
2010-04-01 03:11:59 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
// the player decided to make an immediate drop
|
|
|
|
case TETRIS_INCMD_DROP:
|
2010-08-22 03:07:46 +00:00
|
|
|
nPieceRow = tetris_bucket_getRow(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
// emulate immediate drop
|
2010-08-22 03:07:46 +00:00
|
|
|
while(tetris_bucket_getStatus(pBucket) == TETRIS_BUS_GLIDING ||
|
|
|
|
tetris_bucket_getStatus(pBucket) == TETRIS_BUS_HOVERING)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_advancePiece(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
}
|
|
|
|
// if the game still runs, reward the player with extra points
|
2010-08-22 03:07:46 +00:00
|
|
|
if (tetris_bucket_getStatus(pBucket) != TETRIS_BUS_GAMEOVER)
|
2010-04-01 03:11:59 +00:00
|
|
|
{
|
|
|
|
pVariantMethods->completeDrop(pVariantData,
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_getRow(pBucket) - nPieceRow);
|
2010-04-01 03:11:59 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
// avoid compiler warnings
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-08-22 03:07:46 +00:00
|
|
|
// inform variant object about the user's last move
|
2011-03-07 00:31:18 +00:00
|
|
|
pVariantMethods->setLastInput(pVariantData, inCmd, bMoveOk);
|
2010-08-22 03:07:46 +00:00
|
|
|
|
|
|
|
// inform the input module about the requested bearing of the
|
|
|
|
// variant object
|
|
|
|
tetris_input_setBearing(pIn,
|
|
|
|
pVariantMethods->getBearing(pVariantData));
|
2010-04-01 03:11:59 +00:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
// the piece has irrevocably hit the ground
|
2010-08-22 03:07:46 +00:00
|
|
|
case TETRIS_BUS_DOCKED:
|
2010-04-01 03:11:59 +00:00
|
|
|
// avoid accidentally issued "down" commands
|
|
|
|
tetris_input_resetDownKeyRepeat(pIn);
|
|
|
|
|
|
|
|
// remove complete lines (if any)
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_removeCompleteLines(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
|
|
|
|
// let the variant object decide how many points the player gets and
|
|
|
|
// whether the level gets changed
|
|
|
|
pVariantMethods->removedLines(pVariantData,
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_getRowMask(pBucket));
|
2010-04-01 03:11:59 +00:00
|
|
|
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();
|
2012-03-23 23:21:46 +00:00
|
|
|
tetris_highscore_saveHighScore(nHighscoreIndex, nHighscore);
|
|
|
|
tetris_highscore_saveHighScoreName(nHighscoreIndex, nHighscoreName);
|
2010-04-01 03:11:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// cleanup
|
|
|
|
tetris_view_destruct(pView);
|
|
|
|
tetris_input_destruct(pIn);
|
|
|
|
pVariantMethods->destruct(pVariantData);
|
2010-08-22 03:07:46 +00:00
|
|
|
tetris_bucket_destruct(pBucket);
|
2010-04-01 03:11:59 +00:00
|
|
|
tetris_piece_destruct(pPiece);
|
|
|
|
}
|
2012-05-07 06:56:00 +00:00
|
|
|
|
|
|
|
/*@}*/
|