BlinkenArea - GitList
Repositories
Blog
Wiki
Blinker
Code
Commits
Branches
Tags
Search
Tree:
7ab9f1d
Branches
Tags
master
Blinker
src
common
Tetris.cpp
tetris WIP: move/freeze stones
Stefan Schuermans
commited
7ab9f1d
at 2019-07-14 17:51:57
Tetris.cpp
Blame
History
Raw
/* Blinker Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org> Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html a blinkenarea.org project */ #include <cmath> #include <stdlib.h> #include <string> #include <vector> #include <BlinkenLib/BlinkenFrame.h> #include "File.h" #include "Format.h" #include "FormatFile.h" #include "Game.h" #include "Mgrs.h" #include "Module.h" #include "NameFile.h" #include "OpConn.h" #include "OpConnIf.h" #include "OpReqIf.h" #include "OutStreamFile.h" #include "Tetris.h" #include "Time.h" #include "TimeCallee.h" #include "UIntFile.h" namespace Blinker { /** * @brief constructor * @param[in] name module name * @param[in] mgrs managers * @param[in] dirBase base directory */ Tetris::Tetris(const std::string &name, Mgrs &mgrs, const Directory &dirBase): Game(name, mgrs, dirBase), m_fileStoneColor(dirBase.getFile("stoneColor")), m_fileDelay(dirBase.getFile("delay")), m_fileStartSound(dirBase.getFile("startSound")), m_stoneColor(), m_delay(c_delayDescr.default_), m_pConn(NULL), m_stone(-1), m_rot(-1), m_posX(-1), m_posY(-1), m_field() { // open operator connection interfaces for player m_mgrs.m_opMgr.open(m_name, this); } /// virtual destructor Tetris::~Tetris() { // close operator connection interface m_mgrs.m_opMgr.close(m_name); // close open operator connection if (m_pConn) { m_pConn->close(); m_pConn = NULL; } } /// check for update of configuration (derived game), return true on update bool Tetris::updateConfigGame() { bool ret = false; // color file was modified -> convert color, return true for update // cfg value file was updated -> read new cfg value, return true for update if (colorUpdate(m_fileStoneColor, m_stoneColor) || valueUpdate(m_fileDelay, c_delayDescr, m_delay)) { ret = true; } // sound name file was modified -> re-read sound name, no other update needed soundUpdate(m_fileStartSound); return ret; } /** * @brief check if accepting new operator connection is possible * @param[in] name operator interface name * @return if accepting new connection is possible */ bool Tetris::acceptNewOpConn(const std::string &name) { (void)name; // accept player if no one there yet return ! m_pConn; } /** * @brief new operator connection * @param[in] name operator interface name * @param[in] pConn operator connection object * * The new connection may not yet be used for sending inside this callback. */ void Tetris::newOpConn(const std::string &name, OpConn *pConn) { (void)name; // player arrives and starts game if (! m_pConn) { m_pConn = pConn; requestOpConnSound(m_pConn, m_fileStartSound); activate(); } // close imcoming connection as soon as possible, nothing else happens else { requestOpConnClose(pConn); return; } } /** * @brief key command received on operator connection * @param[in] pConn operator connection object * @param[in] key key that was pressed */ void Tetris::opConnRecvKey(OpConn *pConn, char key) { // hash -> hang up if (key == '#') { opConnClose(pConn); pConn->close(); return; } // star -> inform player about game if (key == '*') { playOpConnSound(pConn, m_fileStartSound); return; } // normal keys for controlling game // move left if (key == '4') { if (checkStoneFit(m_stone, m_rot, m_posY, m_posX - 1)) { wipeStone(m_stone, m_rot, m_posY, m_posX); m_posX -= 1; drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color } return; } // move right if (key == '6') { if (checkStoneFit(m_stone, m_rot, m_posY, m_posX + 1)) { wipeStone(m_stone, m_rot, m_posY, m_posX); m_posX += 1; drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color } return; } // rotate left if (key == '1') { int new_rot = m_rot - 1; if (new_rot < 0) { new_rot = c_rotCnt - 1; } if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) { wipeStone(m_stone, m_rot, m_posY, m_posX); m_rot = new_rot; drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color } return; } // rotate right if (key == '2' || key == '3') { int new_rot = m_rot + 1; if (new_rot >= c_rotCnt) { new_rot = 0; } if (checkStoneFit(m_stone, new_rot, m_posY, m_posX)) { wipeStone(m_stone, m_rot, m_posY, m_posX); m_rot = new_rot; drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color } return; } // drop stone if (key == '8') { // TODO return; } } /** * @brief play command received on operator connection * @param[in] pConn operator connection object * @param[in] sound name of sound to play */ void Tetris::opConnRecvPlay(OpConn *pConn, const std::string &sound) { (void)pConn; (void)sound; } /** * @brief operator connection is closed * @param[in] pConn operator connection object * * The connection may not be used for sending any more in this callback. */ void Tetris::opConnClose(OpConn *pConn) { // remove coperator connection from requests (if it was in) forgetOpConn(pConn); // player leaves -> deactivate game if (pConn == m_pConn) { m_pConn = NULL; deactivate(); } } /// re-initialize game (e.g. due to config change) void Tetris::reinitialize() { // convert colors color2data(m_fileStoneColor, m_stoneColor); // get values valueFromFile(m_fileDelay, c_delayDescr, m_delay); // initialize field: empty m_field.clear(); m_field.resize(m_height * m_width, -1); // start with new stone newStone(); // redraw image and send frame redraw(); // request first time step if needed planTimeStep(); } /// redraw current game image, expected to call sendFrame() at end void Tetris::redraw() { // draw background rectFill(0, m_height, 0, m_width, m_backgroundColor); // draw fixed pixels for (int y = 0, i = 0; y < m_height; ++y) { for (int x = 0; x < m_width; ++x, ++i) { if (m_field.at(i) >= 0) { pixel(y, x, m_stoneColor); } } } // draw current stone drawStone(m_stone, m_rot, m_posY, m_posX); // send updated image buffer as frame sendFrame(); } /// process next time step of game void Tetris::timeStep() { // stone can move down by one pixel if (checkStoneFit(m_stone, m_rot, m_posY + 1, m_posX)) { // move stone down by one pixel wipeStone(m_stone, m_rot, m_posY, m_posX); m_posY += 1; drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: active color } // stone cannot move down by one pixel else { // add stone permanently to field at current position freezeStone(m_stone, m_rot, m_posY, m_posX); drawStone(m_stone, m_rot, m_posY, m_posX); // TODO: frozen color // prepare new stone newStone(); } // send updated image buffer as frame sendFrame(); // request next time step planTimeStep(); } /// set up a new stone void Tetris::newStone() { // random stone, random rotation m_stone = rand() % c_stoneCnt; m_rot = rand() % c_rotCnt; // postion: two pixels above top middle m_posX = (m_width - 1) / 2; m_posY = -2; } /// set time for next time step of game - or unset if not needed void Tetris::planTimeStep() { // no time call needed if not active if (! isActive()) { unsetTimeStep(); return; } // compute interval based on score and bounce count float interval = 1e-3f * m_delay; // request next time call Time stepTime; stepTime.fromFloatSec(interval); setTimeStep(Time::now() + stepTime); } /// get rotatation of stone from stone/rotation index (or NULL in invalid) Tetris::RotStone const * Tetris::getRotStone(int stone, int rot) { if (! checkLimitInt(stone, 0, c_stoneCnt -1) || ! checkLimitInt(rot, 0, c_rotCnt - 1)) { return NULL; // invalid stone or rotation } return &c_stones[stone].rot[rot]; } /// check if stone fits at position bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const { // get rotation of stone RotStone const *rotStone = getRotStone(stone, rot); if (! rotStone) { return false; // invalid stone or rotation -> does not fit } // check pixels for (int p = 0; p < c_pixelCnt; ++p) { int py = y + rotStone->pixels[p].y; int px = x + rotStone->pixels[p].x; if (py > m_height - 1 || ! checkLimitInt(px, 0, m_width - 1)) { return false; // outside field (except at top) -> does not fit } if (py >= 0) { // do not check above top int pi = py * m_width + px; if (m_field.at(pi) >= 0) { return false; // occupixed pixel -> does not fit } } } // all checks passed -> stone fits return true; } /// freeze stone to field at position void Tetris::freezeStone(int stone, int rot, int y, int x) { // get rotation of stone RotStone const *rotStone = getRotStone(stone, rot); if (! rotStone) { return; // invalid stone or rotation -> nothing to do } // add pixels to field for (int p = 0; p < c_pixelCnt; ++p) { int py = y + rotStone->pixels[p].y; int px = x + rotStone->pixels[p].x; if (checkLimitInt(py, 0, m_height - 1) && checkLimitInt(px, 0, m_width - 1)) { int pi = py * m_width + px; m_field.at(pi) = stone; // mark pixel in field with stone index } } } /// draw a stone to image buffer void Tetris::drawStone(int stone, int rot, int y, int x) { colorStone(stone, rot, y, x, m_stoneColor); } /// wipe a stone from image buffer (i.e. replace it with background color) void Tetris::wipeStone(int stone, int rot, int y, int x) { colorStone(stone, rot, y, x, m_backgroundColor); } /// set shape of stone to color in image buffer void Tetris::colorStone(int stone, int rot, int y, int x, ColorData const &color) { // get rotation of stone RotStone const *rotStone = getRotStone(stone, rot); if (! rotStone) { return; // invalid stone or rotation -> nothing to do } // color pixels for (int p = 0; p < c_pixelCnt; ++p) { pixel(y + rotStone->pixels[p].y, x + rotStone->pixels[p].x, color); } } /// stone data Tetris::Stone const Tetris::c_stones[7] = { // the I { { { { { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -2 }, { 0, -1 }, { 0, 0 }, { 0, 1 } } }, { { { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -2 }, { 0, -1 }, { 0, 0 }, { 0, 1 } } }, } }, // the L { { { { { 1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { 0, 0 }, { 0, 1 }, { 1, 1 } } }, { { { -1, 0 }, { 0, 0 }, { 1, 0 }, { -1, 1 } } }, { { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } } }, } }, // the J { { { { { -1, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { 1, -1 }, { 0, 0 }, { 0, 1 } } }, { { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 1, 1 } } }, { { { 0, -1 }, { 0, 0 }, { -1, 1 }, { 0, 1 } } }, } }, // the T { { { { { 0, -1 }, { -1, 0 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 0, 1 } } }, { { { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } } }, { { { 0, -1 }, { -1, 0 }, { 0, 0 }, { 0, 1 } } }, } }, // the O { { { { { 0, -1 }, { 1, -1 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { 1, -1 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { 1, -1 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { 1, -1 }, { 0, 0 }, { 1, 0 } } }, } }, // the Z { { { { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { -1, 0 }, { 0, 0 }, { -1, 1 } } }, { { { -1, -1 }, { 0, -1 }, { 0, 0 }, { 1, 0 } } }, { { { 0, -1 }, { -1, 0 }, { 0, 0 }, { -1, 1 } } }, } }, // the S { { { { { 0, -1 }, { 1, -1 }, { -1, 0 }, { 0, 0 } } }, { { { -1, -1 }, { -1, 0 }, { 0, 0 }, { 0, 1 } } }, { { { 0, -1 }, { 1, -1 }, { -1, 0 }, { 0, 0 } } }, { { { -1, -1 }, { -1, 0 }, { 0, 0 }, { 0, 1 } } }, } }, }; /// number of stones int const Tetris::c_stoneCnt = sizeof(Tetris::c_stones) / sizeof(Tetris::c_stones[0]); /// number of rotations per stone int const Tetris::c_rotCnt = sizeof(Tetris::Stone::rot) / sizeof(Tetris::Stone::rot[0]); /// number of pixels per stone int const Tetris::c_pixelCnt = sizeof(Tetris::RotStone::pixels) / sizeof(Tetris::RotStone::pixels[0]); /// descriptor for delay value Tetris::ValueDescr const Tetris::c_delayDescr = { 400, 200, 1000 }; } // namespace Blinker