BlinkenArea - GitList
Repositories
Blog
Wiki
Blinker
Code
Commits
Branches
Tags
Search
Tree:
92efc5f
Branches
Tags
master
Blinker
src
common
Game.cpp
use default colors when missing game color config
Stefan Schuermans
commited
92efc5f
at 2019-08-14 19:27:45
Game.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 <map> #include <set> #include <sstream> #include <string> #include <vector> #include <BlinkenLib/BlinkenFrame.h> #include "File.h" #include "Format.h" #include "FormatFile.h" #include "Game.h" #include "LockNameFile.h" #include "Mgrs.h" #include "Module.h" #include "NameFile.h" #include "OutStreamFile.h" #include "UIntFile.h" namespace Blinker { /** * @brief constructor * @param[in] name module name * @param[in] mgrs managers * @param[in] dirBase base directory */ Game::Game(const std::string &name, Mgrs &mgrs, const Directory &dirBase): Module(name, mgrs, dirBase), m_fileFormat(dirBase.getFile("format")), m_fileBackgroundColor(dirBase.getFile("backgroundColor")), m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr), m_height(0), m_width(0), m_channels(0), m_imgBuf(), m_backgroundColor(), m_fileLockName(dirBase.getFile("lockName"), mgrs.m_lockMgr), m_haveTimeStep(false), m_timeStepTime(), m_opConnSounds(), m_opConnsClose() { } /// virtual destructor Game::~Game() { // clean up deactivate(); // cancel time callback request m_mgrs.m_callMgr.cancelTimeCall(this); } /// check for update of configuration void Game::updateConfig() { bool doReinit = false; bool doRedraw = false; // format file was modified -> re-create canvas and schedule redraw if (m_fileFormat.checkModified()) { m_fileFormat.update(); createImgBuf(); doReinit = true; } // color file was modified -> convert color, schedule redraw if (colorUpdate(m_fileBackgroundColor, 0, m_backgroundColor)) { doRedraw = true; } // output stream name file was modified -> re-get output stream if (m_fileOutStream.checkModified()) { m_fileOutStream.update(); } // process lock name updates m_fileLockName.updateIfModified(); // check config update of derived game updateConfigGame(doReinit, doRedraw); // re-initialize / redraw if (doReinit) { reinitialize(); doRedraw = true; } if (doRedraw) { redraw(); } } /// callback when requested time reached void Game::timeCall() { // play sounds on operator connections for (auto & opConnSound : m_opConnSounds) { playOpConnSound(opConnSound.first, *opConnSound.second); } m_opConnSounds.clear(); // close operator connections for (OpConn *pConn : m_opConnsClose) { pConn->close(); } m_opConnsClose.clear(); // time step of game has been reached -> call game if (m_haveTimeStep && Time::now() >= m_timeStepTime) { m_haveTimeStep = false; timeStep(); } // plan next time call - if any planTimeCall(); } /** * @brief check if game can be actiavted: if interlock is free * @return whether game can be activated (activate() might still fail) */ bool Game::canActivate() { return m_fileLockName.isfree(); } /** * @brief activate game: * take interlock, set up image buffer, call redraw() * @return whether game could be activated */ bool Game::activate() { if (! m_fileLockName.take()) { return false; } createImgBuf(); reinitialize(); redraw(); return true; } /** * @brief deactivate game: * tear down image buffer, deactivate output, release interlock */ void Game::deactivate() { destroyImgBuf(); sendFrame(); m_fileLockName.release(); } /// check if game is active bool Game::isActive() const { return ! m_imgBuf.empty(); // game is active if there is an image buffer } /// set pixel in image buffer void Game::pixel(int y, int x, ColorData const &cd) { if (m_imgBuf.empty() || ! checkLimitInt(y, 0, m_height - 1) || ! checkLimitInt(x, 0, m_width - 1)) { return; } int pos = (y * m_width + x) * m_channels; std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos); } /// draw horizontal line to image buffer void Game::lineHor(int y, int x1, int x2, ColorData const &cd) { if (m_imgBuf.empty() || ! checkLimitInt(y, 0, m_height - 1) || ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) { return; } int pos = (y * m_width + x1) * m_channels; int dx = m_channels; for (int x = x1; x <= x2; ++x) { std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos); pos += dx; } } /// draw vertical line to image buffer void Game::lineVert(int y1, int y2, int x, ColorData const &cd) { if (m_imgBuf.empty() || ! checkIntRangeLimit(y1, y2, 0, m_height - 1) || ! checkLimitInt(x, 0, m_width - 1)) { return; } int pos = (y1 * m_width + x) * m_channels; int dy = m_width * m_channels; for (int y = y1; y <= y2; ++y) { std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos); pos += dy; } } /// draw non-filled rectangle to image buffer void Game::rect(int y1, int y2, int x1, int x2, ColorData const &cd) { lineHor(y1, x1, x2, cd); lineHor(y2, x1, x2, cd); lineVert(y1, y2, x1, cd); lineVert(y1, y2, x2, cd); } /// draw filled rectangle to image buffer void Game::rectFill(int y1, int y2, int x1, int x2, ColorData const &cd) { if (m_imgBuf.empty() || ! checkIntRangeLimit(y1, y2, 0, m_height - 1) || ! checkIntRangeLimit(x1, x2, 0, m_width - 1)) { return; } int pos = (y1 * m_width + x1) * m_channels; int dx = m_channels; int dy = m_width - (x2 - x1 + 1) * m_channels; for (int y = y1; y <= y2; ++y) { for (int x = x1; x <= x2; ++x) { std::copy(cd.begin(), cd.end(), m_imgBuf.begin() + pos); pos += dx; } pos += dy; } } /** * @brief draw a small digit (3x5 pixels) to image buffer * @param[in] topY y coordinate of top of digit * @param[in] leftX x coordinate of left of digit * @param[in] digit to draw ('0'..'9') * @param[in] cd color to use for drawing */ void Game::digit3x5(int topY, int leftX, char digit, ColorData const &cd) { /** * digit data in octal, * each row is 3 bits, msb of row is left, * msb row is top */ static const unsigned int digitData[10] = { 075557, 022222, 071747, 071717, 055711, 074717, 074757, 071111, 075757, 075717 }; if (digit < '0' || digit > '9') { return; } int idx = digit - '0'; unsigned int data = digitData[idx]; for (int y = topY; y < topY + 5; ++y) { for (int x = leftX; x < leftX + 3; ++x) { data <<= 1; if (data & 0100000) { pixel(y, x, cd); } } } } /** * @brief draw small digits (3x5 each, 1 pixel gap) to image buffer * @param[in] topY y coordinate of top of digits * @param[in] leftX x coordinate of left of digits * @param[in] digits to draw (string of '0'..'9') * @param[in] cd color to use for drawing */ void Game::digits3x5(int topY, int leftX, std::string const &digits, ColorData const &cd) { for (char digit: digits) { digit3x5(topY, leftX, digit, cd); leftX += 4; } } /** * @brief draw number using 3x5 digits to image buffer * @param[in] anchorY y coordinate of anchor point * @param[in] anchorX x coordinate of anchor point * @param[in] alignY y alignment, -1 for top, 0 for center, 1 for bottom * @param[in] alignX x alignment, -1 for left, 0 for center, 1 for right * @param[in] number non-negative number to draw * @param[in] cd color to use for drawing */ void Game::number3x5(int anchorY, int anchorX, int alignY, int alignX, int number, ColorData const &cd) { // convert number to digits string if (number < 0) { return; } std::stringstream digitsStrm; digitsStrm << number; std::string const &digits = digitsStrm.str(); // compute top left position int topY, leftX; switch (alignY) { case -1: topY = anchorY; break; case 0: topY = anchorY - 2; break; case 1: topY = anchorY - 4; break; default: return; } switch (alignX) { case -1: leftX = anchorX; break; case 0: leftX = anchorX - 2 * digits.length() + 1; break; case 1: leftX = anchorX - 4 * digits.length() + 2; break; default: return; } // draw digits digits3x5(topY, leftX, digits, cd); } /** * @brief process update of color file * @param[in] colorFile file containing color as string * @param[in] def default color (grayscale) in case file is invalid (0..255) * @pram[out] data color data * @return true on update */ bool Game::colorUpdate(ColorFile &colorFile, unsigned char def, ColorData &data) const { if (colorFile.checkModified()) { colorFile.update(); color2data(colorFile, def, data); return true; } else { return false; } } /** * @brief convert color to raw color data * @param[in] colorFile file containing color as string * @param[in] def default color (grayscale) in case file is invalid (0..255) * @pram[out] data color data */ void Game::color2data(ColorFile const &colorFile, unsigned char def, ColorData &data) const { unsigned int channels = m_fileFormat.m_obj.m_channels; unsigned int maxval = m_fileFormat.m_obj.m_maxval; if (! m_fileFormat.m_valid || ! colorFile.m_valid) { data.resize(m_fileFormat.m_obj.m_channels, (unsigned char)(def * maxval / 255.0 + 0.5)); } else { data.resize(m_fileFormat.m_obj.m_channels); Color const &color = colorFile.m_obj; if (channels == 1) { // single channel // convert to monochrome according to CIE XYZ double val = 0.2125 * color.m_red + 0.7154 * color.m_green + 0.0721 * color.m_blue; // adapt to maxval and round data.at(0) = (unsigned char)(val * maxval / 255.0 + 0.5); } else if (channels == 2) { // two channels // adapt to maxval and round, ignore blue data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5); data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5); } else if (channels >= 3) { // three channels (more than three channels: further channels are dark) // adapt to maxval and round data.at(0) = (unsigned char)(color.m_red * maxval / 255.0 + 0.5); data.at(1) = (unsigned char)(color.m_green * maxval / 255.0 + 0.5); data.at(2) = (unsigned char)(color.m_blue * maxval / 255.0 + 0.5); } } } /// process update of value file, return true on update bool Game::valueUpdate(UIntFile &valueFile, ValueDescr const &descr, unsigned int &value) { if (valueFile.checkModified()) { valueFile.update(); valueFromFile(valueFile, descr, value); return true; } else { return false; } } /// get value from value file void Game::valueFromFile(UIntFile &valueFile, ValueDescr const &descr, unsigned int &value) { if (! valueFile.m_valid) { value = descr.default_; } else { value = valueFile.m_obj.m_uint; if (value < descr.minimum) { value = descr.minimum; } else if (value > descr.maximum) { value = descr.maximum; } } } /// process update of sound name file void Game::soundUpdate(NameFile &soundFile) { if (soundFile.checkModified()) { soundFile.update(); } } /// send current image buffer as frame to output stream void Game::sendFrame() { // image buffer available -> send its contents as frame if (! m_imgBuf.empty()) { Format const &fmt = m_fileFormat.m_obj; stBlinkenFrame *pFrame = BlinkenFrameNew(fmt.m_height, fmt.m_width, fmt.m_channels, fmt.m_maxval, 1); BlinkenFrameSetPixelData(pFrame, 0, m_height, 0, m_width, 0, m_channels, m_imgBuf.data()); m_fileOutStream.setFrame(pFrame); BlinkenFrameFree(pFrame); } // no image buffer available -> send "no frame" information else { m_fileOutStream.setFrame(NULL); } } /// set next game time step - plan timed action of game void Game::setTimeStep(const Time& timeStepTime) { m_haveTimeStep = true; m_timeStepTime = timeStepTime; planTimeCall(); } /// unset game time step - do timed action for game void Game::unsetTimeStep() { m_haveTimeStep = false; planTimeCall(); } /// play a sound on operator connection (NULL ok) (direct) void Game::playOpConnSound(OpConn *pConn, NameFile const &soundFile) { if (pConn && soundFile.m_valid) { pConn->sendPlay(soundFile.m_obj.m_str); } } //// request playing a sound on operator connection (NULL ok) (via time call) void Game::requestOpConnSound(OpConn *pConn, NameFile const &soundFile) { if (pConn) { m_opConnSounds[pConn] = &soundFile; planTimeCall(); } } /// request closing operator connection (NULL ok) (closed via time call) void Game::requestOpConnClose(OpConn *pConn) { if (pConn) { m_opConnsClose.insert(pConn); planTimeCall(); } } /// remove operator connection from requests (call when op conn is closed) void Game::forgetOpConn(OpConn *pConn) { m_opConnSounds.erase(pConn); m_opConnsClose.erase(pConn); planTimeCall(); } /// (re-)create image buffer void Game::createImgBuf() { // get rid of old image buffer destroyImgBuf(); // read format from format file m_fileFormat.update(); if (! m_fileFormat.m_valid) return; // read background color m_fileBackgroundColor.update(); if (! m_fileBackgroundColor.m_valid) return; // store parameters m_height = m_fileFormat.m_obj.m_height; m_width = m_fileFormat.m_obj.m_width; m_channels = m_fileFormat.m_obj.m_channels; // create image buffer m_imgBuf.resize(m_height * m_width * m_channels); // convert background color color2data(m_fileBackgroundColor, 0, m_backgroundColor); } /// tear down image buffer void Game::destroyImgBuf() { m_imgBuf.clear(); m_backgroundColor.clear(); } /// request next time call - or cancel request if not needed void Game::planTimeCall() { // request immediate time call if there are internal time-based actions if (! m_opConnSounds.empty() || ! m_opConnsClose.empty()) { m_mgrs.m_callMgr.requestTimeCall(this, Time::now()); return; } // no time step requested by game if (! m_haveTimeStep) { m_mgrs.m_callMgr.cancelTimeCall(this); return; } // request next time call m_mgrs.m_callMgr.requestTimeCall(this, m_timeStepTime); } } // namespace Blinker