BlinkenArea - GitList
Repositories
Blog
Wiki
Blinker
Code
Commits
Branches
Tags
Search
Tree:
75aef1e
Branches
Tags
master
Blinker
src
common
Pong.cpp
pong: make computer perfect
Stefan Schuermans
commited
75aef1e
at 2019-06-15 14:24:14
Pong.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 <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 "OutStreamFile.h" #include "Pong.h" #include "Time.h" #include "TimeCallee.h" namespace Blinker { /** * @brief constructor * @param[in] name module name * @param[in] mgrs managers * @param[in] dirBase base directory */ Pong::Pong(const std::string &name, Mgrs &mgrs, const Directory &dirBase): Game(name, mgrs, dirBase), m_fileBallColor(dirBase.getFile("ballColor")), m_fileLineColor(dirBase.getFile("lineColor")), m_filePadColor(dirBase.getFile("padColor")), m_ballColor(), m_lineColor(), m_padColor(), m_ballPosX(-1), m_ballPosY(-1), m_ballDirX(0), m_ballDirY(0), m_padSize(0), m_leftPosY(0), m_rightPosY(0) { // FIXME: activate at begin for initial development only activate(); } /// virtual destructor Pong::~Pong() { // cancel time callback request m_mgrs.m_callMgr.cancelTimeCall(this); } /// check for update of configuration (derived game), return true on update bool Pong::updateConfigGame() { bool ret = false; // color file was modified -> convert color, return true for update if (colorUpdate(m_fileBallColor, m_ballColor) || colorUpdate(m_fileLineColor, m_lineColor) || colorUpdate(m_filePadColor, m_padColor)) { ret = true; } return ret; } /// re-initialize game (e.g. due to config change) void Pong::reinitialize() { // compute parameters m_padSize = (m_height + 1) / 3; m_leftPosY = (m_height - m_padSize) / 2; m_rightPosY = (m_height - m_padSize) / 2; // convert colors color2data(m_fileBallColor, m_ballColor); color2data(m_fileLineColor, m_lineColor); color2data(m_filePadColor, m_padColor); // FIXME: start ball for development startBall(); } /// redraw current game image, expected to call sendFrame() at end void Pong::redraw() { int y, x; // draw background rectFill(0, m_height, 0, m_width, m_backgroundColor); // draw middle line: single line on odd width, two dashed lines at even width for (y = 0; y < m_height; ++y) { x = (m_width - (y & 1)) / 2; pixel(y, x, m_lineColor); } // draw pads lineVert(m_leftPosY, m_leftPosY + m_padSize - 1, 0, m_padColor); lineVert(m_rightPosY, m_rightPosY + m_padSize - 1, m_width - 1, m_padColor); // draw ball pixel(m_ballPosY, m_ballPosX, m_ballColor); // send updated image buffer as frame sendFrame(); } /// callback when requested time reached void Pong::timeCall() { // computer player: move pads computerLeft(); computerRight(); // bounce ball bounceBall(); // move ball m_ballPosX += m_ballDirX; m_ballPosY += m_ballDirY; // draw and send frame redraw(); // request next call if needed planTimeCall(); } /** * @brief computation of ideal pad position for computer players * @param[in] padBallX x coordinate of position of ball when hitting the pad * @param[in] padY current y coordinate of pad * @param[out] padYmin minimum ideal y position of pad * @param[out] padYmax maximum ideal y position of pad */ void Pong::computerComputePadPos(int padBallX, int padY, int &padYmin, int &padYmax) const { // ball not moving towards pad if ((padBallX - m_ballPosX) * m_ballDirX <= 0) { // do not move if ball is still close to pad if (abs(padBallX - m_ballPosX) <= 2) { padYmin = padY; padYmax = padY; return; } // move pad to middle (might be 2 pixels wide) padYmin = (m_height - m_padSize) / 2; padYmax = (m_height - m_padSize + 1) / 2; return; } // compute expected ball position at pad int ballPosX = m_ballPosX; // simulate were ball is going int ballPosY = m_ballPosY; int ballDirY = m_ballDirY; while ((padBallX - ballPosX) * m_ballDirX > 0) { // while moving to pad int deltaX = padBallX - ballPosX; int deltaY = deltaX * m_ballDirX * ballDirY; if (deltaY < -ballPosY) { deltaY = -ballPosY; ballPosX += deltaY * m_ballDirX * ballDirY; ballPosY = 0; ballDirY = 1; } else if (deltaY > m_height - 1 - ballPosY) { deltaY = m_height - 1 - ballPosY; ballPosX += deltaY * m_ballDirX * ballDirY; ballPosY = m_height - 1; ballDirY = -1; } else { ballPosX += deltaX; ballPosY += deltaY; } } // compute pad position to hit ball with center of pad padYmin = ballPosY - m_padSize / 2; padYmax = ballPosY - (m_padSize - 1) / 2; } /** * @brief move pad for computer players * @param[in] padYmin minimum desired y position of pad * @param[in] padYmax maximum desired y position of pad * @param[in,out] padPosY y position of pad */ void Pong::computerMovePad(int padYmin, int padYmax, int &padPosY) const { // move pad, do not move pad out of field if (padPosY > padYmax && padPosY > 0) { --padPosY; } else if (padPosY < padYmin && padPosY < m_height - m_padSize) { ++padPosY; } } /// computer player for left pad void Pong::computerLeft() { int padYmin, padYmax; computerComputePadPos(1, m_leftPosY, padYmin, padYmax); computerMovePad(padYmin, padYmax, m_leftPosY); } /// computer player for right pad void Pong::computerRight() { int padYmin, padYmax; computerComputePadPos(m_width - 2, m_rightPosY, padYmin, padYmax); computerMovePad(padYmin, padYmax, m_rightPosY); } /// bounce ball void Pong::bounceBall() { bounceBallSide(); // must be done before player bounce to be safe bounceBallLeft(); bounceBallRight(); bounceBallSide(); // must also be done after player bounce to be safe } /// bounce ball at sides void Pong::bounceBallSide() { if (m_ballPosY <= 0 && m_ballDirY < 0) { m_ballDirY = 1; } if (m_ballPosY >= m_height - 1 && m_ballDirY > 0) { m_ballDirY = -1; } if (m_ballPosX <= 0 && m_ballDirX < 0) { m_ballDirX = 1; } if (m_ballPosX >= m_width - 1 && m_ballDirX > 0) { m_ballDirX = -1; } } /// bounce ball at left pad void Pong::bounceBallLeft() { if (m_ballPosX != 1 || m_ballDirX >= 0) { return; } // top corner if (m_ballPosY == m_leftPosY - 1 && m_ballDirY > 0) { m_ballDirX = 1; m_ballDirY = -1; } // bottom corner else if (m_ballPosY == m_leftPosY + m_padSize && m_ballDirY < 0) { m_ballDirX = 1; m_ballDirY = 1; } // pad edge else if (m_ballPosY >= m_leftPosY && m_ballPosY < m_leftPosY + m_padSize) { m_ballDirX = 1; } } /// bounce ball at right pad void Pong::bounceBallRight() { if (m_ballPosX != m_width - 2 || m_ballDirX <= 0) { return; } // top corner if (m_ballPosY == m_rightPosY - 1 && m_ballDirY > 0) { m_ballDirX = -1; m_ballDirY = -1; } // bottom corner else if (m_ballPosY == m_rightPosY + m_padSize && m_ballDirY < 0) { m_ballDirX = -1; m_ballDirY = 1; } // pad edge else if (m_ballPosY >= m_rightPosY && m_ballPosY < m_rightPosY + m_padSize) { m_ballDirX = -1; } } /// request next time call - or cancel request if not needed void Pong::planTimeCall() { if (m_ballPosX < 0 || m_ballPosY < 0) { m_mgrs.m_callMgr.cancelTimeCall(this); return; } Time stepTime; stepTime.fromMs(100); m_mgrs.m_callMgr.requestTimeCall(this, Time::now() + stepTime); } /// move ball out of the field and halt it void Pong::hideBall() { m_ballPosX = -1; m_ballPosY = -1; m_ballDirX = 0; m_ballDirY = 0; // update time call request planTimeCall(); } /// start ball void Pong::startBall() { // ball starts horizontally at middle of field, vertically random m_ballPosX = (m_width - (rand() & 1)) / 2; m_ballPosY = rand() % m_height; // random diagonal direction m_ballDirX = (rand() & 1) * 2 - 1; m_ballDirY = (rand() & 1) * 2 - 1; // request first time call if needed planTimeCall(); } } // namespace Blinker