/* 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_fileStoneIColor(dirBase.getFile("stoneIColor")),
  m_fileStoneLColor(dirBase.getFile("stoneLColor")),
  m_fileStoneJColor(dirBase.getFile("stoneJColor")),
  m_fileStoneTColor(dirBase.getFile("stoneTColor")),
  m_fileStoneOColor(dirBase.getFile("stoneOColor")),
  m_fileStoneZColor(dirBase.getFile("stoneZColor")),
  m_fileStoneSColor(dirBase.getFile("stoneSColor")),
  m_fileFixedIColor(dirBase.getFile("fixedIColor")),
  m_fileFixedLColor(dirBase.getFile("fixedLColor")),
  m_fileFixedJColor(dirBase.getFile("fixedJColor")),
  m_fileFixedTColor(dirBase.getFile("fixedTColor")),
  m_fileFixedOColor(dirBase.getFile("fixedOColor")),
  m_fileFixedZColor(dirBase.getFile("fixedZColor")),
  m_fileFixedSColor(dirBase.getFile("fixedSColor")),
  m_fileDelay(dirBase.getFile("delay")),
  m_fileDropDelay(dirBase.getFile("dropDelay")),
  m_fileBlinkDelay(dirBase.getFile("blinkDelay")),
  m_fileGameOverDelay(dirBase.getFile("gameOverDelay")),
  m_fileStartSound(dirBase.getFile("startSound")),
  m_fileRowCompleteSound(dirBase.getFile("rowCompleteSound")),
  m_fileGameOverSound(dirBase.getFile("gameOverSound")),
  m_delay(c_delayDescr.default_),
  m_dropDelay(c_dropDelayDescr.default_),
  m_blinkDelay(c_blinkDelayDescr.default_),
  m_gameOverDelay(c_gameOverDelayDescr.default_),
  m_pConn(NULL),
  m_stone(-1), m_rot(-1), m_posX(-1), m_posY(-1),
  m_dropping(false), m_blinking(0), m_completed(0), m_gameOver(false),
  m_field(), m_rowsBlink()
{
  // 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;
  }
}

/**
 * @brief check for update of configuration (derived game)
 * @param[in,out] doReinit set to true to ask for reinitialization
 * @param[in,out] doRedraw set to true to ask for redrawing
 */
void Tetris::updateConfigGame(bool &doReinit, bool &doRedraw)
{
  (void)doReinit;

  // color file was modified -> convert color, ask for redraw
  if (colorUpdate(m_fileStoneIColor, 255, m_stoneColors[Active][StoneI])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileStoneLColor, 255, m_stoneColors[Active][StoneL])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileStoneJColor, 255, m_stoneColors[Active][StoneJ])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileStoneTColor, 255, m_stoneColors[Active][StoneT])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileStoneOColor, 255, m_stoneColors[Active][StoneO])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileStoneZColor, 255, m_stoneColors[Active][StoneZ])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileStoneSColor, 255, m_stoneColors[Active][StoneS])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedIColor, 192, m_stoneColors[Fixed][StoneI])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedLColor, 192, m_stoneColors[Fixed][StoneL])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedJColor, 192, m_stoneColors[Fixed][StoneJ])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedTColor, 192, m_stoneColors[Fixed][StoneT])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedOColor, 192, m_stoneColors[Fixed][StoneO])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedZColor, 192, m_stoneColors[Fixed][StoneZ])) {
    doRedraw = true;
  }
  if (colorUpdate(m_fileFixedSColor, 192, m_stoneColors[Fixed][StoneS])) {
    doRedraw = true;
  }

  // delay cfg value file was updated -> read new delay cfg value, no re-*
  valueUpdate(m_fileDelay, c_delayDescr, m_delay);
  valueUpdate(m_fileDropDelay, c_dropDelayDescr, m_dropDelay);
  valueUpdate(m_fileBlinkDelay, c_blinkDelayDescr, m_blinkDelay);
  valueUpdate(m_fileGameOverDelay, c_gameOverDelayDescr, m_gameOverDelay);

  // sound name file was modified -> re-read sound name, no reinit/draw needed
  soundUpdate(m_fileStartSound);
  soundUpdate(m_fileRowCompleteSound);
  soundUpdate(m_fileGameOverSound);
}

/**
 * @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 and game can be activated
  return ! m_pConn && canActivate();
}

/**
 * @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) {
    if (activate()) {
      m_pConn = pConn;
      requestOpConnSound(m_pConn, m_fileStartSound);
    } else {
      // activation failed (interlock), close connection as soon as possible
      requestOpConnClose(pConn);
    }
  }
  // close imcoming connection as soon as possible
  else {
    requestOpConnClose(pConn);
  }
}

/**
 * @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,
      deactivated if dropping stone, rows blinking or end of game */
  if (m_dropping || m_blinking > 0 || m_gameOver) {
    return;
  }

  // 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, Active);
      sendFrame();
    }
    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, Active);
      sendFrame();
    }
    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, Active);
      sendFrame();
    }
    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, Active);
      sendFrame();
    }
    return;
  }

  // drop stone
  if (key == '8') {
    m_dropping = true;
    planTimeStep(); // stone falls faster now -> update time callback
    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_fileStoneIColor, 255, m_stoneColors[Active][StoneI]);
  color2data(m_fileStoneLColor, 255, m_stoneColors[Active][StoneL]);
  color2data(m_fileStoneJColor, 255, m_stoneColors[Active][StoneJ]);
  color2data(m_fileStoneTColor, 255, m_stoneColors[Active][StoneT]);
  color2data(m_fileStoneOColor, 255, m_stoneColors[Active][StoneO]);
  color2data(m_fileStoneZColor, 255, m_stoneColors[Active][StoneZ]);
  color2data(m_fileStoneSColor, 255, m_stoneColors[Active][StoneS]);
  color2data(m_fileFixedIColor, 255, m_stoneColors[Fixed][StoneI]);
  color2data(m_fileFixedLColor, 255, m_stoneColors[Fixed][StoneL]);
  color2data(m_fileFixedJColor, 255, m_stoneColors[Fixed][StoneJ]);
  color2data(m_fileFixedTColor, 255, m_stoneColors[Fixed][StoneT]);
  color2data(m_fileFixedOColor, 255, m_stoneColors[Fixed][StoneO]);
  color2data(m_fileFixedZColor, 255, m_stoneColors[Fixed][StoneZ]);
  color2data(m_fileFixedSColor, 255, m_stoneColors[Fixed][StoneS]);

  // get values
  valueFromFile(m_fileDelay, c_delayDescr, m_delay);
  valueFromFile(m_fileDropDelay, c_dropDelayDescr, m_dropDelay);
  valueFromFile(m_fileBlinkDelay, c_blinkDelayDescr, m_blinkDelay);
  valueFromFile(m_fileGameOverDelay, c_gameOverDelayDescr, m_gameOverDelay);

  // initialize field: empty
  m_field.clear();
  m_field.resize(m_height * m_width, -1);

  // initialize blinking rows: no row blinking
  m_rowsBlink.clear();
  m_rowsBlink.resize(m_height, false);

  // no rows blinking or completed, game not over yet
  m_blinking = 0;
  m_completed = 0;
  m_gameOver = false;

  // 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, respect blinking rows
  for (int y = 0, i = 0; y < m_height; ++y) {
    if (! (m_blinking & 1) || ! m_rowsBlink.at(y)) {
      for (int x = 0; x < m_width; ++x, ++i) {
        if (m_field.at(i) >= 0) {
          pixel(y, x, m_stoneColors[Fixed][m_field.at(i)]);
        }
      }
    }
  }

  // draw current stone
  drawStone(m_stone, m_rot, m_posY, m_posX, Active);

  // send updated image buffer as frame
  sendFrame();
}

/// process next time step of game
void Tetris::timeStep()
{
  // count time at end of game
  if (m_gameOver) {
    timeGameOver();
  // blinking of completed rows
  } else if (m_blinking > 0) {
    timeBlinkRows();
  // falling stone
  } else {
    timeStone();
  }

  // request next time step
  planTimeStep();
}

/// count time at end of game
void Tetris::timeGameOver()
{
  // close operator connection
  if (m_pConn) {
    forgetOpConn(m_pConn); // remove from requests (if it was in)
    m_pConn->close();
    m_pConn = NULL;
  }

  // deactivate game
  deactivate();
}

/// blink completed rows
void Tetris::timeBlinkRows()
{
  // blink rows
  ++m_blinking;

  // end of blinking -> remove blinking rows, new stone
  if (m_blinking >= 8) {
    // remove blinking rows
    for (int b = 0; b < m_height; ++b) {
      if (m_rowsBlink.at(b)) {
        // move rows 0..b-1 one row down, i.e., to rows 1..b
        for (int y = b, i = b * m_width + m_width - 1; y > 0; --y) {
          for (int x = m_width - 1; x >= 0; --x, --i) {
            m_field.at(i) = m_field.at(i - m_width);
          }
        }
        // clear first row
        for (int x = 0; x < m_width; ++x) {
          m_field.at(x) = -1;
        }
        // row not blinking any more
        m_rowsBlink.at(b) = false;
      }
    }
    // blinking done
    m_blinking = 0;
    // new stone
    newStone();
  }

  // redraw image and send frame
  redraw();
}

/// falling stone
void Tetris::timeStone()
{
  // 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, Active);
  }

  // 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, Fixed);

    // overflow of game field -> game over
    if (checkStoneOverflow(m_stone, m_rot, m_posY, m_posX)) {
      // unset stone
      m_stone = -1;
      // game over
      m_gameOver = true;
      playOpConnSound(m_pConn, m_fileGameOverSound);
    }

    // no overflow
    else {
      // no current stone any more
      m_stone = -1;
      // check for completed rows, (afterwards: new stone)
      checkComplete();
    }
  }

  // send updated image buffer as frame
  sendFrame();
}

/// check for completed rows to disappear (new stone afterwards)
void Tetris::checkComplete()
{
  // collect y coordinated of completed rows -> m_rowsBlink
  bool blink = false;
  for (int y = 0, i = 0; y < m_height; ++y) {
    bool complete = true;
    for (int x = 0; x < m_width; ++x, ++i) {
      if (m_field.at(i) < 0) {
        complete = false;
      }
    }
    m_rowsBlink.at(y) = complete;
    if (complete) {
      blink = true;
    }
  }

  // no completed rows -> new stone
  if (! blink) {
    newStone();
  }

  // start blinking (start with rows visible, last bit == 0)
  else {
    m_blinking = 2;
    playOpConnSound(m_pConn, m_fileRowCompleteSound);
  }
}

/// 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;

  // stone is not being dropped yet
  m_dropping = false;
}

/// 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 game state
  int interval_ms = m_delay;
  int speedup = m_completed;
  if (m_gameOver) {
    interval_ms = m_gameOverDelay;
    speedup = 0;
  } else if (m_blinking > 0) {
    interval_ms = m_blinkDelay;
  } else if (m_dropping) {
    interval_ms = m_dropDelay;
  }
  float scale = 0.3f + 0.7f * expf(-0.3f * speedup);
  float interval = 1e-3f * interval_ms * scale;

  // 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;
}

/// check if stone overflow game field
bool Tetris::checkStoneOverflow(int stone, int rot, int y, int x) const
{
  // get rotation of stone
  RotStone const *rotStone = getRotStone(stone, rot);
  if (! rotStone) {
    return true; // invalid stone or rotation -> overflow
  }

  // check pixels for overflow
  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)) {
      return true; // outside field (including top) -> overflow
    }
  }

  // all checks passed -> no overflow
  return false;
}

/// 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, State state)
{
  colorStone(stone, rot, y, x, m_stoneColors[state][stone]);
}

/// 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 (make sure it matches the StoneIndex enum)
Tetris::Stone const Tetris::c_stones[Tetris::StoneCnt] = {
  // 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 };

/// descriptor for delay value during dropping a stone
Tetris::ValueDescr const Tetris::c_dropDelayDescr = { 100, 50, 250 };

/// descriptor for delay value during blinking of disappearing rows
Tetris::ValueDescr const Tetris::c_blinkDelayDescr = { 50, 50, 250 };

/// descriptor for delay value at end of game
Tetris::ValueDescr const Tetris::c_gameOverDelayDescr = { 2000, 100, 5000 };

} // namespace Blinker

