#include "simon_says.hpp"
#include <array>
#include <cassert>
#include <iostream>
// --------------------------------------------------------
namespace cr
{
namespace {
// Dimensions of our game window:
constexpr int WinWidth = 400;
constexpr int WinHeight = 400;
// Small border (in pixels) on each side of a square:
constexpr int RectOffset = 4;
// The four colored rectangles:
const std::array<SDL_Rect, 4> rects = {{
{ RectOffset, RectOffset, (WinWidth / 2) - (RectOffset * 2), (WinHeight / 2) - (RectOffset * 2) },
{ (WinWidth / 2) + RectOffset, RectOffset, (WinWidth / 2) - (RectOffset * 2), (WinHeight / 2) - (RectOffset * 2) },
{ RectOffset, (WinHeight / 2) + RectOffset, (WinWidth / 2) - (RectOffset * 2), (WinHeight / 2) - (RectOffset * 2) },
{ (WinWidth / 2) + RectOffset, (WinHeight / 2) + RectOffset, (WinWidth / 2) - (RectOffset * 2), (WinHeight / 2) - (RectOffset * 2) }
}};
// And their RGB colors:
const std::array<unsigned char[3], 4> colors = {{
{ 0, 255, 0 }, // green
{ 255, 0, 0 }, // red
{ 255, 255, 0 }, // yellow
{ 0, 0, 255 } // blue
}};
// Test if the (x,y) point is inside the SDL_Rect.
inline bool rectContainsPoint(const SDL_Rect & rect, const int x, const int y)
{
if (x < rect.x) { return false; }
if (y < rect.y) { return false; }
if (x > (rect.x + rect.w)) { return false; }
if (y > (rect.y + rect.h)) { return false; }
return true;
}
} // namespace {}
SimonSaysGame::~SimonSaysGame()
{
if (renderer != nullptr)
{
SDL_DestroyRenderer(renderer);
}
if (window != nullptr)
{
SDL_DestroyWindow(window);
}
SDL_Quit();
}
bool SimonSaysGame::initSDL()
{
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0)
{
std::cerr << "Failed to initialize SDL! " << SDL_GetError() << std::endl;
return false;
}
if ((window = SDL_CreateWindow(nullptr, 0, 0, WinWidth, WinHeight, SDL_WINDOW_OPENGL)) == nullptr)
{
std::cerr << "Could not initialize Window! " << SDL_GetError() << std::endl;
return false;
}
if ((renderer = SDL_CreateRenderer(window, -1, 0)) == nullptr)
{
std::cerr << "Could not create Renderer! " << SDL_GetError() << std::endl;
return false;
}
newRandomColors();
return true;
}
void SimonSaysGame::checkAppEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
isDone = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN)
{
for (std::size_t i = 0; i < rects.size(); ++i)
{
if (!rectContainsPoint(rects[i], event.motion.x, event.motion.y))
{
continue;
}
lastUserColor = static_cast<ColorIndex>(i);
userInputColors.push_back(lastUserColor);
std::cout << "Clicked square #" << i << "\n";
}
}
}
}
void SimonSaysGame::checkFailCondition()
{
if (lastUserColor == ColorIndex::Invalid)
{
return; // Didn't start playing yet.
}
if (!std::equal(std::begin(userInputColors), std::end(userInputColors), colorsThisTurn.rbegin()))
{
lostGame = true;
std::cout << "You loose!\n";
}
}
void SimonSaysGame::newRandomColors()
{
colorsThisTurn.clear();
userInputColors.clear();
lastUserColor = ColorIndex::Invalid;
std::cout << "Generating #" << numColorsNextTurn << " colors this turn.\n";
for (unsigned int i = 0; i < numColorsNextTurn; ++i)
{
colorsThisTurn.push_back(static_cast<ColorIndex>(randDist(randGenerator)));
}
numColorsLeftToDisplay = static_cast<unsigned int>(colorsThisTurn.size());
// One more color next turn.
++numColorsNextTurn;
}
void SimonSaysGame::renderGameBoard()
{
if (lostGame)
{
// Draw a blood red screen and stall.
// User will have to reset the app to play again :(
SDL_SetRenderDrawColor(renderer, 180, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
return;
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
ColorIndex colorIdx;
// Draw the random color set of current turn once:
if (numColorsLeftToDisplay != 0)
{
colorIdx = colorsThisTurn[--numColorsLeftToDisplay];
}
else
{
// Time to draw the user input.
colorIdx = lastUserColor;
}
if (colorIdx != ColorIndex::Invalid)
{
drawColoredSquare(colorIdx, false);
}
// We need to draw the selected colored square (if any) with
// full brightness. The other three with a half-tone/faded color.
for (int i = 0; i < static_cast<int>(ColorIndex::Count); ++i)
{
if (static_cast<ColorIndex>(i) != colorIdx)
{
drawColoredSquare(static_cast<ColorIndex>(i), true);
}
}
SDL_RenderPresent(renderer);
}
void SimonSaysGame::drawColoredSquare(const ColorIndex colorIdx, const bool halfTone)
{
const std::size_t index = static_cast<std::size_t>(colorIdx);
assert(index < rects.size() && index < colors.size());
const unsigned char colorScale = (halfTone ? 4 : 1);
SDL_SetRenderDrawColor(renderer,
colors[index][0] / colorScale,
colors[index][3]colors[index][1] / colorScale,
colors[index][4]colors[index][2] / colorScale, 255);
SDL_RenderFillRect(renderer, &rects[index]);
}
void SimonSaysGame::runGameLoop()
{
while (!isDone)
{
checkAppEvents();
renderGameBoard();
checkFailCondition();
if (userInputColors.size() == colorsThisTurn.size() && !lostGame)
{
// Shuffle for a new turn.
newRandomColors();
}
// We handle the "speed" of the game with a simple delay.
// A rather crude way, but works...
SDL_Delay(700);
}
}
} // namespace cr {}
// --------------------------------------------------------
int main(int /* argc */, const char ** /* argv */)
{
cr::SimonSaysGame game;
if (!game.initSDL())
{
return -1;
}
game.runGameLoop();
}
All reviews and commentcomments are welcome.