BlinkenArea - GitList
Repositories
Blog
Wiki
Blinker
Code
Commits
Branches
Tags
Search
Tree:
e614c38
Branches
Tags
master
Blinker
src
common
Tetris.cpp
tetris game WIP
Stefan Schuermans
commited
e614c38
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 // TODO } /** * @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() { // FIXME if (m_posY >= m_height + 2) { newStone(); } wipeStone(m_stone, m_rot, m_posY, m_posX); m_posY += 1; drawStone(m_stone, m_rot, m_posY, m_posX); // 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); } /// check if stone fits at position bool Tetris::checkStoneFit(int stone, int rot, int y, int x) const { // get rotation of stone if (! checkLimitInt(stone, 0, c_stoneCnt -1) || ! checkLimitInt(rot, 0, c_rotCnt - 1)) { return false; // invalid stone or rotation -> does not fit } RotStone const &rotStone = c_stones[stone].rot[rot]; // 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 } 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; } /// 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 if (! checkLimitInt(stone, 0, c_stoneCnt -1) || ! checkLimitInt(rot, 0, c_rotCnt - 1)) { return; // invalid stone or rotation -> nothing to do } RotStone const &rotStone = c_stones[stone].rot[rot]; // 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 = { 200, 100, 500 }; } // namespace Blinker