diff --git a/Match3/Block.cpp b/Match3/Block.cpp new file mode 100644 index 0000000..0fec6b6 --- /dev/null +++ b/Match3/Block.cpp @@ -0,0 +1,586 @@ +#include "Block.h" +#include + +const int BLOCK_MARK_TIME = 150; +const int BLOCK_MOVE_TIME = 300; +const int BLOCK_KILL_TIME = 300; +const double FALL_ACCELERATION = 25.0; //board units per second squared + +const double MAX_MARKER_OPACITY = 0.5; + +enum class BlockState +{ + Normal, + Moving, + Falling, + Disappearing, + Dead, +}; + +enum class BlockMarkerState +{ + None, + Marking, + Unmarking, + Marked +}; + +struct Block::impl +{ + Renderer &renderer; + + int boardX; + int boardY; + TextureID texture; + BlockState state; + + BlockMarkerState markerState; + double currentMarkerOpacity; + unsigned int markerChangeStartTime; + bool selected; + + unsigned int moveStartTime; + int moveFromScreenX; + int moveFromScreenY; + bool moveOnTop; + + unsigned int killStartTime; + + unsigned int fallStartTime; + int fallFromScreenX; + int fallFromScreenY; + + void moveTo(unsigned int currentTime, int newBoardX, int newBoardY, bool topLayer = false); + + int getScreenXPos() const; + int getScreenYPos() const; + void renderMarker() const; + void renderNormal() const; + void renderMoving(const unsigned int currentTime) const; + void renderDisappearing(const unsigned int currentTime) const; + void renderFalling(const unsigned int currentTime) const; + + impl(Renderer &r); + ~impl(); + +//exposed methods + void init(const int boardX, const int boardY, const TextureID texture); + + bool isInside(const int x, const int y) const; + bool isSelected() const; + bool isDead() const; + bool canMove() const; + bool isNeighbor(std::unique_ptr &block) const; + + int getBoardX() const; + int getBoardY() const; + int getType() const; + void getSwapDirection(const int x, const int y, int &dx, int &dy) const; + + void mark(const unsigned int currentTime); + void unmark(const unsigned int currentTime); + void select(const unsigned int currentTime); + void unselect(const unsigned int currentTime); + void swapWith(const unsigned int currentTime, std::unique_ptr &block); + void kill(const unsigned int currentTime); + void fallTo(const unsigned int currentTime, const int targetX, const int targetY); + void simulate(const unsigned int currentTime); + + void render(const unsigned int currentTime) const; + void renderOverlay(const unsigned int currentTime) const; + +}; + +Block::impl::impl(Renderer &r) : +renderer(r), +markerState(BlockMarkerState::None), +currentMarkerOpacity(0.0), +markerChangeStartTime(0), +selected(false), +boardX(0), +boardY(0), +state(BlockState::Normal), +texture(TID_LAST), +moveStartTime(0), +moveFromScreenX(0), +moveFromScreenY(0), +killStartTime(0), +fallStartTime(0), +fallFromScreenX(0), +fallFromScreenY(0) +{ +} + +Block::impl::~impl() +{ +} + +void Block::impl::init(const int bX, const int bY, const TextureID tex) +{ + boardX = bX; + boardY = bY; + state = BlockState::Normal; + texture = tex; +} + +int Block::impl::getScreenXPos() const +{ + return BOARD_POS_X + boardX * BLOCK_SIZE_X; +} + +int Block::impl::getScreenYPos() const +{ + return BOARD_POS_Y + boardY * BLOCK_SIZE_Y; +} + +bool Block::impl::isInside(const int x, const int y) const +{ + if(BlockState::Normal != state) + { + throw new NotImplementedException(); + } + int posX = getScreenXPos(); + if(x < posX) + { + return false; + } + if(x >= posX + BLOCK_SIZE_X) + { + return false; + } + int posY = getScreenYPos(); + if(y < posY) + { + return false; + } + if(y >= posY + BLOCK_SIZE_Y) + { + return false; + } + return true; +} + +bool Block::impl::isSelected() const +{ + return selected; +} + +bool Block::impl::isDead() const +{ + return state == BlockState::Dead; +} + +bool Block::impl::canMove() const +{ + return BlockState::Normal == state; +} + +bool Block::impl::isNeighbor(std::unique_ptr &block) const +{ + int diffX = abs(boardX - block->boardX); + int diffY = abs(boardY - block->boardY); + return ((0 == diffX && 1 == diffY) || (1 == diffX && 0 == diffY)); +} + +int Block::impl::getBoardX() const +{ + return boardX; +} + +int Block::impl::getBoardY() const +{ + return boardY; +} + +int Block::impl::getType() const +{ + if(state != BlockState::Normal) + { + return -1; + } + return texture; +} + +void Block::impl::getSwapDirection(const int x, const int y, int &dx, int &dy) const +{ + int posX = (int)(BOARD_POS_X + (boardX + 0.5) * BLOCK_SIZE_X); + int posY = (int)(BOARD_POS_Y + (boardY + 0.5) * BLOCK_SIZE_Y); + int pdx = x - posX; + int pdy = y - posY; + if(abs(pdx) > abs(pdy)) + { + dy = 0; + if(pdx > 0) + { + dx = 1; + } + else + { + dx = -1; + } + } + else + { + dx = 0; + if(pdy > 0) + { + dy = 1; + } + else + { + dy = -1; + } + } +} + +void Block::impl::mark(const unsigned int currentTime) +{ + if(BlockMarkerState::Marking == markerState || + BlockMarkerState::Marked == markerState) + { + return; + } + if(BlockMarkerState::None == markerState) + { + markerChangeStartTime = currentTime; + } + if(BlockMarkerState::Unmarking == markerState) + { + markerChangeStartTime = currentTime - (unsigned int)(currentMarkerOpacity * BLOCK_MARK_TIME); + } + markerState = BlockMarkerState::Marking; +} + +void Block::impl::unmark(const unsigned int currentTime) +{ + if(BlockMarkerState::None == markerState || + BlockMarkerState::Unmarking == markerState) + { + return; + } + if(selected) + { + return; + } + if(BlockMarkerState::Marked == markerState) + { + markerChangeStartTime = currentTime; + } + if(BlockMarkerState::Marking == markerState) + { + markerChangeStartTime = currentTime - (unsigned int)((1.0 - currentMarkerOpacity) * BLOCK_MARK_TIME); + } + markerState = BlockMarkerState::Unmarking; +} + +void Block::impl::select(const unsigned int currentTime) +{ + selected = true; + mark(currentTime); +} + +void Block::impl::unselect(const unsigned int currentTime) +{ + selected = false; + unmark(currentTime); +} + +void Block::impl::moveTo(const unsigned int currentTime, const int newBoardX, + const int newBoardY, const bool topLayer) +{ + unselect(currentTime); + if(BlockState::Normal != state) + { + return; + } + moveStartTime = currentTime; + state = BlockState::Moving; + moveOnTop = topLayer; + moveFromScreenX = getScreenXPos(); + moveFromScreenY = getScreenYPos(); + boardX = newBoardX; + boardY = newBoardY; +} + +void Block::impl::swapWith(const unsigned int currentTime, std::unique_ptr &block) +{ + int oldBoardX = boardX; + int oldBoardY = boardY; + moveTo(currentTime, block->boardX, block->boardY, true); + block->moveTo(currentTime, oldBoardX, oldBoardY, false); +} + +void Block::impl::kill(const unsigned int currentTime) +{ + unselect(currentTime); + if(BlockState::Normal != state) + { + return; + } + killStartTime = currentTime; + state = BlockState::Disappearing; +} + +void Block::impl::fallTo(const unsigned int currentTime, const int targetX, const int targetY) +{ + unselect(currentTime); + if(BlockState::Normal != state) + { + return; + } + fallStartTime = currentTime; + state = BlockState::Falling; + fallFromScreenX = getScreenXPos(); + fallFromScreenY = getScreenYPos(); + boardX = targetX; + boardY = targetY; +} + +void Block::impl::simulate(const unsigned int currentTime) +{ + if(BlockMarkerState::Marking == markerState) + { + if(currentTime > markerChangeStartTime + BLOCK_MARK_TIME) + { + markerState = BlockMarkerState::Marked; + currentMarkerOpacity = 1.0; + } + else + { + currentMarkerOpacity = (currentTime - markerChangeStartTime)/(double)BLOCK_MARK_TIME; + } + } + if(BlockMarkerState::Unmarking == markerState) + { + if(currentTime > markerChangeStartTime + BLOCK_MARK_TIME) + { + markerState = BlockMarkerState::None; + } + else + { + currentMarkerOpacity = 1.0 - (currentTime - markerChangeStartTime)/(double)BLOCK_MARK_TIME; + } + } + if(BlockState::Moving == state) + { + if(currentTime > moveStartTime + BLOCK_MOVE_TIME) + { + state = BlockState::Normal; + } + } + if(BlockState::Disappearing == state) + { + if(currentTime > killStartTime + BLOCK_KILL_TIME) + { + state = BlockState::Dead; + } + } + if(BlockState::Falling == state) + { + double t = (currentTime - fallStartTime) * 0.001; + double s = FALL_ACCELERATION * t * t * 0.5; + int newY = (int)(fallFromScreenY + s * BLOCK_SIZE_Y); + int posY = BOARD_POS_Y + boardY * BLOCK_SIZE_Y; + if(newY >= posY) + { + state = BlockState::Normal; + } + } +} + +void Block::impl::render(const unsigned int currentTime) const +{ + renderMarker(); + if(BlockState::Normal == state) + { + renderNormal(); + } + if(BlockState::Moving == state) + { + if(!moveOnTop) + { + renderMoving(currentTime); + } + } + if(BlockState::Disappearing == state) + { + renderDisappearing(currentTime); + } + if(BlockState::Falling == state) + { + renderFalling(currentTime); + } +} + +void Block::impl::renderOverlay(const unsigned int currentTime) const +{ + if(BlockState::Moving == state) + { + if(moveOnTop) + { + renderMoving(currentTime); + } + } +} + +void Block::impl::renderMarker() const +{ + if(BlockMarkerState::None == markerState) + { + return; + } + int posX = getScreenXPos(); + int posY = getScreenYPos(); + renderer.setColor(255, 255, 255, (unsigned char)(255 * currentMarkerOpacity * MAX_MARKER_OPACITY)); + renderer.drawFilledRectangle(posX, posY, BLOCK_SIZE_X, BLOCK_SIZE_Y); +} + +void Block::impl::renderNormal() const +{ + int posX = getScreenXPos(); + int posY = getScreenYPos(); + + renderer.drawTextureCentered(texture, posX, posY, BLOCK_SIZE_X, BLOCK_SIZE_Y); +} + +void Block::impl::renderMoving(const unsigned int currentTime) const +{ + int destPosX = getScreenXPos(); + int destPosY = getScreenYPos(); + + double lerpFactor = (currentTime - moveStartTime) / (double)BLOCK_MOVE_TIME; + int dx = (int)((destPosX - moveFromScreenX) * lerpFactor); + int dy = (int)((destPosY - moveFromScreenY) * lerpFactor); + int posX = moveFromScreenX + dx; + int posY = moveFromScreenY + dy; + + renderer.drawTextureCentered(texture, posX, posY, BLOCK_SIZE_X, BLOCK_SIZE_Y); +} + +void Block::impl::renderDisappearing(const unsigned int currentTime) const +{ + double scalingFactor = 1.0 - (currentTime - killStartTime) / (double)BLOCK_KILL_TIME; + int posX = getScreenXPos(); + int posY = getScreenYPos(); + + renderer.drawTextureCentered(texture, posX, posY, BLOCK_SIZE_X, BLOCK_SIZE_Y, scalingFactor); +} + +void Block::impl::renderFalling(const unsigned int currentTime) const +{ + double t = (currentTime - fallStartTime) * 0.001; + double s = FALL_ACCELERATION * t * t * 0.5; + int posX = BOARD_POS_X + boardX * BLOCK_SIZE_X; + int posY = (int)(fallFromScreenY + s * BLOCK_SIZE_Y); + + renderer.drawTextureCentered(texture, posX, posY, BLOCK_SIZE_X, BLOCK_SIZE_Y); +} + +Block::Block(Renderer &r) +{ + pimpl = std::unique_ptr(new impl(r)); +} + +Block::~Block() +{ +} + +void Block::init(int boardX, int boardY, TextureID texture) +{ + pimpl->init(boardX, boardY, texture); +} + +bool Block::isInside(int x, int y) const +{ + return pimpl->isInside(x, y); +} + +bool Block::isSelected() const +{ + return pimpl->isSelected(); +} + +bool Block::isDead() const +{ + return pimpl->isDead(); +} + +bool Block::canMove() const +{ + return pimpl->canMove(); +} + +bool Block::isNeighbor(BlockPtr block) const +{ + return pimpl->isNeighbor(block->pimpl); +} + +int Block::getBoardX() const +{ + return pimpl->getBoardX(); +} + +int Block::getBoardY() const +{ + return pimpl->getBoardY(); +} + +int Block::getType() const +{ + return pimpl->getType(); +} + +void Block::getSwapDirection(const int x, const int y, int &dx, int &dy) const +{ + pimpl->getSwapDirection(x, y, dx, dy); +} + +void Block::mark(const unsigned int currentTime) +{ + pimpl->mark(currentTime); +} + +void Block::unmark(const unsigned int currentTime) +{ + pimpl->unmark(currentTime); +} + +void Block::select(const unsigned int currentTime) +{ + pimpl->select(currentTime); +} + +void Block::unselect(const unsigned int currentTime) +{ + pimpl->unselect(currentTime); +} + +void Block::swapWith(const unsigned int currentTime, BlockPtr block) +{ + pimpl->swapWith(currentTime, block->pimpl); +} + +void Block::kill(const unsigned int currentTime) +{ + pimpl->kill(currentTime); +} + +void Block::fallTo(const unsigned int currentTime, const int targetX, const int targetY) +{ + pimpl->fallTo(currentTime, targetX, targetY); +} + +void Block::simulate(const unsigned int currentTime) +{ + pimpl->simulate(currentTime); +} + +void Block::render(const unsigned int currentTime) const +{ + pimpl->render(currentTime); +} + +void Block::renderOverlay(const unsigned int currentTime) const +{ + pimpl->renderOverlay(currentTime); +} diff --git a/Match3/Block.h b/Match3/Block.h new file mode 100644 index 0000000..251df3d --- /dev/null +++ b/Match3/Block.h @@ -0,0 +1,60 @@ + +#ifndef _BLOCK_H_ +#define _BLOCK_H_ + +#include "Renderer.h" + +class Block; +typedef std::shared_ptr BlockPtr; +typedef std::shared_ptr ConstBlockPtr; + +const int BLOCK_SIZE_X = 42; +const int BLOCK_SIZE_Y = 42; +const int BOARD_POS_X = 330; +const int BOARD_POS_Y = 105; + +struct NotImplementedException : public std::exception +{ +}; + +class Block +{ + struct impl; + std::unique_ptr pimpl; +public: + Block(Renderer &r); + ~Block(); + + void init(int boardX, int boardY, TextureID texture); + + bool isInside(int x, int y) const; + bool isSelected() const; + bool isDead() const; + bool canMove() const; + bool isNeighbor(BlockPtr block) const; + + int getBoardX() const; + int getBoardY() const; + //return some numeric block type or -1 for empty/inactive block + int getType() const; + //return best swap direction given mouse coordinates + //sets (dx, dy) to one of (-1, 0), (1, 0), (0, -1), (0, 1) + void getSwapDirection(const int x, const int y, int &dx, int &dy) const; + + void mark(unsigned int currentTime); + void unmark(unsigned int currentTime); + void select(unsigned int currentTime); + void unselect(const unsigned int currentTime); + + void swapWith(unsigned int currentTime, BlockPtr block); + void kill(unsigned int currentTime); + void fallTo(unsigned int currentTime, const int targetX, const int targetY); + + void simulate(unsigned int currentTime); + + void render(unsigned int currentTime) const; + void renderOverlay(unsigned int currentTime) const; +}; + + +#endif \ No newline at end of file diff --git a/Match3/Board.cpp b/Match3/Board.cpp new file mode 100644 index 0000000..c868f55 --- /dev/null +++ b/Match3/Board.cpp @@ -0,0 +1,136 @@ +#include "GameImpl.h" +#include +#include + +Board::Board(Renderer &r) : +renderer(r) +{ + rng.seed((unsigned int)std::time(0)); +} + +void Board::applyToAllBlocks(const std::function &f) +{ + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + if(!blocks[i][j]) + { + continue; + } + f(blocks[i][j]); + } + } +} + +BlockPtr Board::findBlock(const std::function &predicate) +{ + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + if(!blocks[i][j]) + { + continue; + } + if(predicate(blocks[i][j])) + { + return blocks[i][j]; + } + } + } + return nullptr; +} + +void Board::generate() +{ + std::uniform_int<> block_dist(TID_BLOCK_1, TID_BLOCK_1 + NUM_BLOCK_TYPES - 1); + + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + BlockPtr block = std::make_shared(renderer); + block->init(j, i, (TextureID)block_dist(rng)); + blocks[i][j] = block; + } + } +} + +int Board::simulateKills(const unsigned int currentTime) +{ + KillCalculator killCalculator(*this); + killCalculator.calculateKills(); + + int killCount = 0; + applyToAllBlocks([&] (BlockPtr b) { + if(killCalculator.hasKillAt(b->getBoardX(), b->getBoardY())) + { + b->kill(currentTime); + killCount++; + } + }); + return killCount; +} + +void Board::removeDeadBlocks() +{ + applyToAllBlocks([] (BlockPtr &b) { + if(b->isDead()) + { + b = nullptr; + } + }); +} + +void Board::simulateFalling(const unsigned int currentTime) +{ + std::uniform_int<> block_dist(TID_BLOCK_1, TID_BLOCK_1 + NUM_BLOCK_TYPES - 1); + int numBlocksGenerated[NUM_BLOCK_COLUMNS]; + for(int i = 0; i < NUM_BLOCK_COLUMNS; ++i) + { + numBlocksGenerated[i] = 0; + } + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + + int reverseRowIndex = NUM_BLOCK_ROWS - i - 1; + if(blocks[reverseRowIndex][j]) + { + continue; + } + //search for block that can fall to this position + bool blockFound = false; + int testRow; + for(testRow = reverseRowIndex - 1; testRow >= 0; --testRow) + { + if(!blocks[testRow][j]) + { + continue; + } + if(blocks[testRow][j]->canMove()) + { + blockFound = true; + break; + } + } + if(blockFound) + { + blocks[testRow][j]->fallTo(currentTime, j, reverseRowIndex); + blocks[reverseRowIndex][j] = blocks[testRow][j]; + blocks[testRow][j] = nullptr; + } + else if(testRow == -1) + { + //generate new + BlockPtr block(new Block(renderer)); + block->init(j, -(numBlocksGenerated[j] + 1), (TextureID)block_dist(rng)); + numBlocksGenerated[j]++; + block->fallTo(currentTime, j, reverseRowIndex); + blocks[reverseRowIndex][j] = block; + } + } + } +} diff --git a/Match3/Board.h b/Match3/Board.h new file mode 100644 index 0000000..bb95b82 --- /dev/null +++ b/Match3/Board.h @@ -0,0 +1,49 @@ +#ifndef _BOARD_H_ +#define _BOARD_H_ + +template +void mapTable(SrcT mapFrom[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS], + DstT mapTo[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS], + const std::function &f) +{ + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + mapTo[i][j] = f(mapFrom[i][j]); + } + } +} + +struct Board +{ + Renderer &renderer; + std::mt19937 rng; + + BlockPtr blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS]; + BlockPtr mouseDownBlock; + + Board(Renderer &renderer); + + void applyToAllBlocks(const std::function &f); + BlockPtr findBlock(const std::function &predicate); + + template + void mapBlocks(T mapTo[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS], + const std::function &f) const + { + mapTable(blocks, mapTo, f); + } + +//board logic + void generate(); + //return number of blocks killed + int simulateKills(unsigned int currentTime); + void removeDeadBlocks(); + //check for falling blocks/new blocks + void simulateFalling(unsigned int currentTime); +}; + +typedef std::shared_ptr BoardPtr; + +#endif \ No newline at end of file diff --git a/Match3/Game.cpp b/Match3/Game.cpp new file mode 100644 index 0000000..7c22f6f --- /dev/null +++ b/Match3/Game.cpp @@ -0,0 +1,84 @@ +#include "GameImpl.h" +#include "SDL.h" +#include + +Game::impl::impl(Renderer &r) : +renderer(r), +board(new Board(r)), +gameStarted(false), +gameStartTime(0), +gameStopTime(0), +timeLeftSeconds(TIME_LIMIT), +score(0), +firstGame(true) +{ +} + +Game::impl::~impl() +{ +} + +void Game::impl::render(const unsigned int currentTime) +{ + renderer.clear(); + renderer.drawBackground(TID_BACKGROUND); + + renderer.setClipRect(BOARD_POS_X, BOARD_POS_Y, NUM_BLOCK_COLUMNS * BLOCK_SIZE_X, NUM_BLOCK_ROWS * BLOCK_SIZE_Y); + + board->applyToAllBlocks([&] (BlockPtr b) { + b->simulate(currentTime); + b->render(currentTime); + }); + + + board->applyToAllBlocks([&] (BlockPtr b) { + b->renderOverlay(currentTime); + }); + + renderer.resetClipRect(); + + std::ostringstream timeStream; + timeStream << "Time: " << timeLeftSeconds; + renderer.drawText(timeStream.str().c_str(), 25, 125); + + std::ostringstream scoreStream; + scoreStream << "Score: " << score; + renderer.drawText(scoreStream.str().c_str(), 25, 175); + + renderer.present(); +} + +void Game::impl::runEventLoop() +{ + board->generate(); + + bool quit = false; + + while(!quit) + { + unsigned int currentTime = SDL_GetTicks(); + + if(pollEvents(currentTime)) + { + quit = true; + } + + simulate(currentTime); + + render(currentTime); + } +} + +Game::Game(Renderer &r) +{ + pimpl = std::unique_ptr(new impl(r)); +} + +Game::~Game() +{ +} + +void Game::runEventLoop() +{ + pimpl->runEventLoop(); +} diff --git a/Match3/Game.h b/Match3/Game.h new file mode 100644 index 0000000..77cfc8d --- /dev/null +++ b/Match3/Game.h @@ -0,0 +1,22 @@ + +#ifndef _GAME_H_ +#define _GAME_H_ + +#include "Block.h" + +const int NUM_BLOCK_TYPES = 5; +const int NUM_BLOCK_COLUMNS = 8; +const int NUM_BLOCK_ROWS = 8; + +class Game +{ + struct impl; + std::unique_ptr pimpl; +public: + Game(Renderer &r); + ~Game(); + + void runEventLoop(); +}; + +#endif \ No newline at end of file diff --git a/Match3/GameImpl.h b/Match3/GameImpl.h new file mode 100644 index 0000000..ce7bed3 --- /dev/null +++ b/Match3/GameImpl.h @@ -0,0 +1,59 @@ +#ifndef _BOARD_IMPL_H_ +#define _BOARD_IMPL_H_ + +#include +#include + +#include "Game.h" +#include "Board.h" +#include "KillCalculator.h" + +const int TIME_LIMIT = 60; +const int POST_GAME_TIME = 1; + +struct Game::impl +{ + Renderer &renderer; + + BoardPtr board; + + bool gameStarted; + unsigned int gameStartTime; + unsigned int gameStopTime; + int timeLeftSeconds; + int score; + bool firstGame; + + impl(Renderer &r); + ~impl(); + +//game logic + bool tryGameStart(unsigned int currentTime); + void simulate(unsigned int currentTime); + +//rendering + void render(unsigned int currentTime); + +//user input processing + bool trySwap(unsigned int currentTime, BlockPtr src, BlockPtr dst); + BlockPtr getSelectedBlock(); + + void processMouseMotion(unsigned int currentTime, int x, int y); + void processMouseDown(unsigned int currentTime, int x, int y); + //simple block click (mouse down and up in the same block) + void processBlockClick(unsigned int currentTime); + //mouse up outside of mouse down block + void processBlockDrag(unsigned int currentTime, int x, int y); + void processMouseUp(unsigned int currentTime, int x, int y); + bool pollEvents(unsigned int currentTime); + +//main game loop + void runEventLoop(); +}; + +//for(int i = 0; i < NUM_BLOCK_ROWS; ++i) +//{ +// for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) +// { + +#endif \ No newline at end of file diff --git a/Match3/GameInput.cpp b/Match3/GameInput.cpp new file mode 100644 index 0000000..16deeb1 --- /dev/null +++ b/Match3/GameInput.cpp @@ -0,0 +1,165 @@ +#include "GameImpl.h" +#include "SDL.h" + +BlockPtr Game::impl::getSelectedBlock() +{ + return board->findBlock([] (BlockPtr b) -> bool { + return b->isSelected(); + }); +} + + +bool Game::impl::trySwap(const unsigned int currentTime, BlockPtr src, BlockPtr dst) +{ + if(!src->isNeighbor(dst)) + { + return false; + } + if(!src->canMove()) + { + return false; + } + if(!dst->canMove()) + { + return false; + } + + int srcX = src->getBoardX(); + int srcY = src->getBoardY(); + int dstX = dst->getBoardX(); + int dstY = dst->getBoardY(); + + KillCalculator killCalculator(*board); + killCalculator.swapTypes(srcX, srcY, dstX, dstY); + killCalculator.calculateKills(); + if(!killCalculator.hasKills()) + { + return false; + } + + std::swap(board->blocks[dstY][dstX], board->blocks[srcY][srcX]); + src->swapWith(currentTime, dst); + + return true; +} + +void Game::impl::processMouseMotion(const unsigned int currentTime, int x, int y) +{ + if(!gameStarted) + { + return; + } + board->applyToAllBlocks([&] (BlockPtr b) { + if(b->canMove() && b->isInside(x, y)) + { + b->mark(currentTime); + } + else + { + b->unmark(currentTime); + } + }); +} + +void Game::impl::processMouseDown(const unsigned int currentTime, int x, int y) +{ + if(!gameStarted) + { + if(!tryGameStart(currentTime)) + { + return; + } + } + board->mouseDownBlock = board->findBlock([=] (BlockPtr b) -> bool { + return b->canMove() && b->isInside(x, y); + }); +} + +void Game::impl::processBlockClick(const unsigned int currentTime) +{ + BlockPtr selectedBlock = getSelectedBlock(); + if(!selectedBlock) + { + board->mouseDownBlock->select(currentTime); + return; + } + if(board->mouseDownBlock == selectedBlock) + { + return; + } + //"select swap" + if(!trySwap(currentTime, selectedBlock, board->mouseDownBlock)) + { + selectedBlock->unselect(currentTime); + board->mouseDownBlock->select(currentTime); + } +} + +void Game::impl::processBlockDrag(const unsigned int currentTime, int x, int y) +{ + //"drag swap" + int dx, dy; + board->mouseDownBlock->getSwapDirection(x, y, dx, dy); + int srcX = board->mouseDownBlock->getBoardX(); + int srcY = board->mouseDownBlock->getBoardY(); + int dstX = srcX + dx; + int dstY = srcY + dy; + if(dstX >= 0 && dstX < NUM_BLOCK_COLUMNS && + dstY >= 0 && dstY < NUM_BLOCK_ROWS) + { + if(board->blocks[dstY][dstX]) + { + trySwap(currentTime, board->mouseDownBlock, board->blocks[dstY][dstX]); + } + } +} + +void Game::impl::processMouseUp(const unsigned int currentTime, int x, int y) +{ + if(!gameStarted) + { + return; + } + if(!board->mouseDownBlock) + { + return; + } + BlockPtr mouseUpBlock = board->findBlock([=] (BlockPtr b) -> bool { + return b->canMove() && b->isInside(x, y); + }); + + if(mouseUpBlock == board->mouseDownBlock) + { + processBlockClick(currentTime); + } + else if(board->mouseDownBlock->canMove() && !board->mouseDownBlock->isInside(x, y)) + { + processBlockDrag(currentTime, x, y); + } + board->mouseDownBlock = nullptr; +} + +bool Game::impl::pollEvents(const unsigned int currentTime) +{ + SDL_Event e; + while(SDL_PollEvent(&e)) + { + switch(e.type) + { + case SDL_QUIT: + //case SDL_KEYDOWN: + return true; + break; + case SDL_MOUSEBUTTONDOWN: + processMouseDown(currentTime, e.motion.x, e.motion.y); + break; + case SDL_MOUSEMOTION: + processMouseMotion(currentTime, e.motion.x, e.motion.y); + break; + case SDL_MOUSEBUTTONUP: + processMouseUp(currentTime, e.motion.x, e.motion.y); + break; + } + } + return false; +} diff --git a/Match3/GameLogic.cpp b/Match3/GameLogic.cpp new file mode 100644 index 0000000..e9c37ee --- /dev/null +++ b/Match3/GameLogic.cpp @@ -0,0 +1,52 @@ +#include "GameImpl.h" + +bool Game::impl::tryGameStart(unsigned int currentTime) +{ + if(!firstGame && (currentTime - gameStopTime < POST_GAME_TIME * 1000)) + { + return false; + } + if(!firstGame) + { + board->generate(); + } + gameStarted = true; + gameStartTime = currentTime; + timeLeftSeconds = TIME_LIMIT; + score = 0; + firstGame = false; + return true; +} + +void Game::impl::simulate(unsigned int currentTime) +{ + if(firstGame && !gameStarted) + { + return; + } + + int killCount = board->simulateKills(currentTime); + if(killCount) + { + score += 2 + (killCount - 1) * (killCount - 2) / 2; + } + + board->removeDeadBlocks(); + + board->simulateFalling(currentTime); + + if(gameStarted) + { + if(currentTime - gameStartTime > TIME_LIMIT * 1000) + { + gameStopTime = currentTime; + gameStarted = false; + board->mouseDownBlock = nullptr; + timeLeftSeconds = 0; + } + else + { + timeLeftSeconds = TIME_LIMIT - (currentTime - gameStartTime) / 1000; + } + } +} diff --git a/Match3/KillCalculator.cpp b/Match3/KillCalculator.cpp new file mode 100644 index 0000000..98ac517 --- /dev/null +++ b/Match3/KillCalculator.cpp @@ -0,0 +1,97 @@ +#include "GameImpl.h" + +KillCalculator::KillCalculator(const Board &board) +{ + initBlockTypes(board); +} + +void KillCalculator::initBlockTypes(const Board &board) +{ + board.mapBlocks(blockTypes, [](BlockPtr b) -> int { + if(!b) + { + return -1; + } + return b->getType(); + }); +} + +int KillCalculator::getBlockType(int x, int y) const +{ + if(y < 0 || y >= NUM_BLOCK_ROWS || + x < 0 || x >= NUM_BLOCK_COLUMNS) + { + return -1; + } + return blockTypes[y][x]; +} + +void KillCalculator::swapTypes(int srcX, int srcY, int dstX, int dstY) +{ + std::swap(blockTypes[dstY][dstX], blockTypes[srcY][srcX]); +} + +bool KillCalculator::verticalKillSearch(int x, int y) const +{ + for(int baseY = 0; baseY <= 2; ++baseY) + { + if((getBlockType(x, y + baseY - 2) == getBlockType(x, y + baseY - 1)) && + (getBlockType(x, y + baseY - 1) == getBlockType(x, y + baseY))) + { + return true; + } + } + return false; +} + +bool KillCalculator::horizontalKillSearch(int x, int y) const +{ + for(int baseX = 0; baseX <= 2; ++baseX) + { + if((getBlockType(x + baseX - 2, y) == getBlockType(x + baseX - 1, y)) && + (getBlockType(x + baseX - 1, y) == getBlockType(x + baseX, y))) + { + return true; + } + } + return false; +} + +void KillCalculator::calculateKills() +{ + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + blockKills[i][j] = false; + if(getBlockType(j, i) == -1) + { + continue; + } + if(horizontalKillSearch(j, i) || verticalKillSearch(j, i)) + { + blockKills[i][j] = true; + } + } + } +} + +bool KillCalculator::hasKills() const +{ + for(int i = 0; i < NUM_BLOCK_ROWS; ++i) + { + for(int j = 0; j < NUM_BLOCK_COLUMNS; ++j) + { + if(blockKills[i][j]) + { + return true; + } + } + } + return false; +} + +bool KillCalculator::hasKillAt(int x, int y) const +{ + return blockKills[y][x]; +} diff --git a/Match3/KillCalculator.h b/Match3/KillCalculator.h new file mode 100644 index 0000000..1ed8db4 --- /dev/null +++ b/Match3/KillCalculator.h @@ -0,0 +1,22 @@ +#ifndef _KILL_CALCULATOR_H_ +#define _KILL_CALCULATOR_H_ + +class KillCalculator +{ + int blockTypes[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS]; + bool blockKills[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS]; + + void initBlockTypes(const Board &board); + int getBlockType(int x, int y) const; + bool verticalKillSearch(int x, int y) const; + bool horizontalKillSearch(int x, int y) const; +public: + KillCalculator(const Board &board); + + void swapTypes(int srcX, int srcY, int dstX, int dstY); + void calculateKills(); + bool hasKills() const; + bool hasKillAt(int x, int y) const; +}; + +#endif diff --git a/Match3/KillTable.cpp b/Match3/KillTable.cpp new file mode 100644 index 0000000..4a7f621 --- /dev/null +++ b/Match3/KillTable.cpp @@ -0,0 +1,82 @@ +#include "GameImpl.h" + +KillTable::KillTable(BlockPtr blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS]) +{ + ITERATE_ALL_BEGIN + blockKills[i][j] = false; + ITERATE_ALL_END + initBlockTypes(blocks); +} + +void KillTable::initBlockTypes(BlockPtr blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS]) +{ + ITERATE_ALL_BEGIN + if(!blocks[i][j]) + { + blockTypes[i][j] = -1; + } + else + { + blockTypes[i][j] = blocks[i][j]->getType(); + } + ITERATE_ALL_END +} + +int KillTable::getBlockType(int x, int y) const +{ + if(y < 0 || y >= NUM_BLOCK_ROWS || + x < 0 || x >= NUM_BLOCK_COLUMNS) + { + return -1; + } + return blockTypes[y][x]; +} + +void KillTable::swapTypes(int srcX, int srcY, int dstX, int dstY) +{ + std::swap(blockTypes[dstY][dstX], blockTypes[srcY][srcX]); +} + +void KillTable::calculateKills() +{ + ITERATE_ALL_BEGIN + if(getBlockType(j, i) == -1) + { + continue; + } + //horizontal search + for(int baseX = 0; baseX <= 2; ++baseX) + { + if((getBlockType(j + baseX - 2, i) == getBlockType(j + baseX - 1, i)) && + (getBlockType(j + baseX - 1, i) == getBlockType(j + baseX, i))) + { + blockKills[i][j] = true; + } + } + //vertical search + for(int baseY = 0; baseY <= 2; ++baseY) + { + if((getBlockType(j, i + baseY - 2) == getBlockType(j, i + baseY - 1)) && + (getBlockType(j, i + baseY - 1) == getBlockType(j, i + baseY))) + { + blockKills[i][j] = true; + } + } + ITERATE_ALL_END +} + +bool KillTable::hasKills() const +{ + ITERATE_ALL_BEGIN + if(blockKills[i][j]) + { + return true; + } + ITERATE_ALL_END + return false; +} + +bool KillTable::hasKillAt(int x, int y) const +{ + return blockKills[y][x]; +} diff --git a/Match3/NullRenderer.cpp b/Match3/NullRenderer.cpp new file mode 100644 index 0000000..f06ee4d --- /dev/null +++ b/Match3/NullRenderer.cpp @@ -0,0 +1,49 @@ +#include "Renderer.h" + +NullRenderer::NullRenderer() +{ +} + +NullRenderer::~NullRenderer() +{ +} + +void NullRenderer::clear() +{ +} + +void NullRenderer::setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ +} + +void NullRenderer::setClipRect(int x, int y, int w, int h) +{ +} + +void NullRenderer::resetClipRect() +{ +} + +void NullRenderer::drawBackground(TextureID tid) +{ +} + +void NullRenderer::drawTexture(TextureID tid, int x, int y) +{ +} + +void NullRenderer::drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale) +{ +} + +void NullRenderer::drawFilledRectangle(int x, int y, int w, int h) +{ +} + +void NullRenderer::drawText(const char *text, int x, int y) +{ +} + +void NullRenderer::present() +{ +} diff --git a/Match3/Renderer.h b/Match3/Renderer.h new file mode 100644 index 0000000..4d993db --- /dev/null +++ b/Match3/Renderer.h @@ -0,0 +1,80 @@ + +#ifndef _RENDERER_H_ +#define _RENDERER_H_ + +#include +#include + +enum TextureID { + TID_BACKGROUND, + TID_BLOCK_1, + TID_BLOCK_2, + TID_BLOCK_3, + TID_BLOCK_4, + TID_BLOCK_5, + TID_LAST +}; + +struct RendererException : public std::exception +{ + std::string error; + + RendererException(const std::string &error) : error(error) {}; +}; + +class Renderer +{ +public: + virtual void clear() = 0; + virtual void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) = 0; + virtual void setClipRect(int x, int y, int w, int h) = 0; + virtual void resetClipRect() = 0; + virtual void drawBackground(TextureID tid) = 0; + virtual void drawTexture(TextureID tid, int x, int y) = 0; + virtual void drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale = 1.0) = 0; + virtual void drawFilledRectangle(int x, int y, int w, int h) = 0; + virtual void drawText(const char *text, int x, int y) = 0; + virtual void present() = 0; +}; + +class SDLRenderer : public Renderer +{ + struct impl; + std::unique_ptr pimpl; + +public: + SDLRenderer(); + ~SDLRenderer(); + + void clear(); + void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + void setClipRect(int x, int y, int w, int h); + void resetClipRect(); + void drawBackground(TextureID tid); + void drawTexture(TextureID tid, int x, int y); + void drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale = 1.0); + void drawFilledRectangle(int x, int y, int w, int h); + void drawText(const char *text, int x, int y); + void present(); +}; + +//mock renderer class for testing +class NullRenderer : public Renderer +{ +public: + NullRenderer(); + ~NullRenderer(); + + void clear(); + void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + void setClipRect(int x, int y, int w, int h); + void resetClipRect(); + void drawBackground(TextureID tid); + void drawTexture(TextureID tid, int x, int y); + void drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale = 1.0); + void drawFilledRectangle(int x, int y, int w, int h); + void drawText(const char *text, int x, int y); + void present(); +}; + +#endif \ No newline at end of file diff --git a/Match3/SDLRenderer.cpp b/Match3/SDLRenderer.cpp new file mode 100644 index 0000000..c609aea --- /dev/null +++ b/Match3/SDLRenderer.cpp @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include + +#include "Renderer.h" + +const int WIN_WIDTH = 755; +const int WIN_HEIGHT = 600; + +static const char *textureNames[] = { + "../assets/RS_bg.jpg", + "../assets/RS_gem_blue.png", + "../assets/RS_gem_green.png", + "../assets/RS_gem_purple.png", + "../assets/RS_gem_red.png", + "../assets/RS_gem_yellow.png", +}; + +struct SDLRenderer::impl +{ + SDL_Window *win; + SDL_Renderer *ren; + std::vector textures; + TTF_Font *defaultFont; + + impl(); + ~impl(); + + void validateTexture(TextureID tid); + + void clear(); + void setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + void setClipRect(int x, int y, int w, int h); + void resetClipRect(); + void drawBackground(TextureID tid); + void drawTexture(TextureID tid, int x, int y); + void drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale); + void drawFilledRectangle(int x, int y, int w, int h); + void drawText(const char *text, int x, int y); + void present(); + +}; + +SDLRenderer::impl::impl() : +win(nullptr), +ren(nullptr), +defaultFont(nullptr) +{ + std::ostringstream errorStream; + if(SDL_Init(SDL_INIT_EVERYTHING) != 0) + { + errorStream << "SDL_Init Error: " << SDL_GetError(); + throw new RendererException(errorStream.str()); + } + + win = SDL_CreateWindow("Match 3 game", 100, 100, WIN_WIDTH, WIN_HEIGHT, SDL_WINDOW_SHOWN); + if(nullptr == win) + { + errorStream << "SDL_CreateWindow Error: " << SDL_GetError(); + throw new RendererException(errorStream.str()); + } + + ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if(nullptr == ren) + { + errorStream << "SDL_CreateRenderer Error: " << SDL_GetError(); + throw new RendererException(errorStream.str()); + } + + static_assert(TID_LAST == sizeof(textureNames)/sizeof(char*), "textureNames array size must match TextureID enumeration!"); + + for(int i = 0; i < TID_LAST; ++i) + { + SDL_Texture *tex = IMG_LoadTexture(ren, textureNames[i]); + if(nullptr == tex) + { + errorStream << "IMG_LoadTexture Error: " << SDL_GetError(); + throw new RendererException(errorStream.str()); + } + textures.push_back(tex); + } + + //init ttf font + if(TTF_Init() != 0) + { + errorStream << "TTF init error."; + throw new RendererException(errorStream.str()); + } + + defaultFont = TTF_OpenFont("../assets/Bangers.ttf", 48); + if (defaultFont == nullptr) + { + errorStream << "Font loading error."; + throw new RendererException(errorStream.str()); + } +} + +SDLRenderer::impl::~impl() +{ + TTF_CloseFont(defaultFont); + for(auto tex: textures) + { + SDL_DestroyTexture(tex); + } + textures.clear(); + if(ren) + { + SDL_DestroyRenderer(ren); + } + if(win) + { + SDL_DestroyWindow(win); + } + SDL_Quit(); +} + +void SDLRenderer::impl::validateTexture(TextureID tid) +{ + if(tid >= TID_LAST || tid < 0) + { + std::ostringstream errorStream; + errorStream << "Invalid texture id: " << tid; + throw new RendererException(errorStream.str()); + } +} + +void SDLRenderer::impl::clear() +{ + SDL_RenderClear(ren); +} + +void SDLRenderer::impl::setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + SDL_SetRenderDrawColor(ren, r, g, b, a); + SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND); +} + +void SDLRenderer::impl::setClipRect(int x, int y, int w, int h) +{ + SDL_Rect cr; + cr.x = x; + cr.y = y; + cr.w = w; + cr.h = h; + SDL_RenderSetClipRect(ren, &cr); +} + +void SDLRenderer::impl::resetClipRect() +{ + SDL_RenderSetClipRect(ren, NULL); +} + +void SDLRenderer::impl::drawBackground(TextureID tid) +{ + validateTexture(tid); + SDL_RenderCopy(ren, textures[tid], NULL, NULL); +} + +void SDLRenderer::impl::drawTexture(TextureID tid, int x, int y) +{ + validateTexture(tid); + SDL_Rect dst; + dst.x = x; + dst.y = y; + SDL_QueryTexture(textures[tid], NULL, NULL, &dst.w, &dst.h); + SDL_RenderCopy(ren, textures[tid], NULL, &dst); +} + +void SDLRenderer::impl::drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale) +{ + validateTexture(tid); + int textureWidth, textureHeight; + SDL_QueryTexture(textures[tid], NULL, NULL, &textureWidth, &textureHeight); + + textureWidth = (int)(textureWidth * scale); + textureHeight = (int)(textureHeight * scale); + + SDL_Rect dst; + dst.x = x + (w - textureWidth) / 2; + dst.y = y + (h - textureHeight) / 2; + dst.w = textureWidth; + dst.h = textureHeight; + SDL_RenderCopy(ren, textures[tid], NULL, &dst); +} + +void SDLRenderer::impl::drawFilledRectangle(int x, int y, int w, int h) +{ + SDL_Rect rectangle; + + rectangle.x = x; + rectangle.y = y; + rectangle.w = w; + rectangle.h = h; + SDL_RenderFillRect(ren, &rectangle); +} + +void SDLRenderer::impl::drawText(const char *text, int x, int y) +{ + SDL_Color col; + col.r = col.g = col.b = 255; + col.a = 225; + SDL_Surface *surf = TTF_RenderText_Blended(defaultFont, text, col); + if(nullptr == surf) + { + return; + } + SDL_Texture *texture = SDL_CreateTextureFromSurface(ren, surf); + if(nullptr == texture) + { + return; + } + SDL_FreeSurface(surf); + + SDL_Rect dst; + dst.x = x; + dst.y = y; + SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h); + SDL_RenderCopy(ren, texture, NULL, &dst); + + SDL_DestroyTexture(texture); +} + +void SDLRenderer::impl::present() +{ + SDL_RenderPresent(ren); +} + +SDLRenderer::SDLRenderer() +{ + pimpl = std::unique_ptr(new impl()); +} + +SDLRenderer::~SDLRenderer() +{ +} + +void SDLRenderer::clear() +{ + pimpl->clear(); +} + +void SDLRenderer::setColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + pimpl->setColor(r, g, b, a); +} + +void SDLRenderer::setClipRect(int x, int y, int w, int h) +{ + pimpl->setClipRect(x, y, w, h); +} + +void SDLRenderer::resetClipRect() +{ + pimpl->resetClipRect(); +} + +void SDLRenderer::drawBackground(TextureID tid) +{ + pimpl->drawBackground(tid); +} + +void SDLRenderer::drawTexture(TextureID tid, int x, int y) +{ + pimpl->drawTexture(tid, x, y); +} + +void SDLRenderer::drawTextureCentered(TextureID tid, int x, int y, int w, int h, double scale) +{ + pimpl->drawTextureCentered(tid, x, y, w, h, scale); +} + +void SDLRenderer::drawFilledRectangle(int x, int y, int w, int h) +{ + pimpl->drawFilledRectangle(x, y, w, h); +} + +void SDLRenderer::drawText(const char *text, int x, int y) +{ + pimpl->drawText(text, x, y); +} + +void SDLRenderer::present() +{ + pimpl->present(); +} diff --git a/Match3/main.cpp b/Match3/main.cpp new file mode 100644 index 0000000..6502faa --- /dev/null +++ b/Match3/main.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +#include "Game.h" + +int main(int argc, char **argv) +{ + try + { + SDLRenderer ren; + Game game(ren); + + game.runEventLoop(); + } + catch(RendererException &re) + { + std::cout << re.error << std::endl; + return 1; + } + + return 0; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..52227f7 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +Match-3 game +========= + +![Screenshot](screen.png) + +A quick exploration into SDL 2.0 library, and a toy project for refactoring C++ code. + +Graphics was created by my friend Przemysław Piekarski, again as a last minute favor :) Thanks a lot! diff --git a/assets/Bangers.ttf b/assets/Bangers.ttf new file mode 100644 index 0000000..cd8a773 Binary files /dev/null and b/assets/Bangers.ttf differ diff --git a/assets/RS_bg.jpg b/assets/RS_bg.jpg new file mode 100644 index 0000000..d4ea6ee Binary files /dev/null and b/assets/RS_bg.jpg differ diff --git a/assets/RS_gem_blue.png b/assets/RS_gem_blue.png new file mode 100644 index 0000000..0e730bf Binary files /dev/null and b/assets/RS_gem_blue.png differ diff --git a/assets/RS_gem_green.png b/assets/RS_gem_green.png new file mode 100644 index 0000000..3f2ab23 Binary files /dev/null and b/assets/RS_gem_green.png differ diff --git a/assets/RS_gem_purple.png b/assets/RS_gem_purple.png new file mode 100644 index 0000000..cbf9404 Binary files /dev/null and b/assets/RS_gem_purple.png differ diff --git a/assets/RS_gem_red.png b/assets/RS_gem_red.png new file mode 100644 index 0000000..1dd6b01 Binary files /dev/null and b/assets/RS_gem_red.png differ diff --git a/assets/RS_gem_yellow.png b/assets/RS_gem_yellow.png new file mode 100644 index 0000000..2871d4d Binary files /dev/null and b/assets/RS_gem_yellow.png differ diff --git a/screen.jpg b/screen.jpg new file mode 100644 index 0000000..235160e Binary files /dev/null and b/screen.jpg differ