// AS1.sln  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36705.20 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AS1", "AS1.vcxproj", "{64AE7C70-79DB-4521-8C4B-63427CACF269}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {64AE7C70-79DB-4521-8C4B-63427CACF269}.Debug|x64.ActiveCfg = Debug|x64 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Debug|x64.Build.0 = Debug|x64 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Debug|x86.ActiveCfg = Debug|Win32 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Debug|x86.Build.0 = Debug|Win32 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Release|x64.ActiveCfg = Release|x64 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Release|x64.Build.0 = Release|x64 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Release|x86.ActiveCfg = Release|Win32 {64AE7C70-79DB-4521-8C4B-63427CACF269}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {74A07782-F54D-4D48-9EF6-D55B300254FB} EndGlobalSection EndGlobal // AS1.vcxproj Debug Win32 Release Win32 Debug x64 Release x64 17.0 Win32Proj {64ae7c70-79db-4521-8c4b-63427cacf269} AS1 10.0 Application true v143 Unicode Application false v143 true Unicode Application true v143 Unicode Application false v143 true Unicode Level3 true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true Console true Level3 true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true H:\SFML261\include Console true H:\SFML261\lib sfml-system-d.lib;sfml-graphics-d.lib;sfml-audio-d.lib;sfml-network-d.lib;sfml-window-d.lib;sfml-system.lib;sfml-graphics.lib;sfml-audio.lib;sfml-network.lib;sfml-window.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) Level3 true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true G:\SFML261\include Console true G:\SFML261\lib sfml-system.lib;sfml-graphics.lib;sfml-audio.lib;sfml-network.lib;sfml-window.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) // AS1.vcxproj.filters  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Source Files Header Files Header Files Header Files Header Files Header Files // AS1.vcxproj.user  // Bubble.h #pragma once #include #include "DrawableEntity.h" using namespace std; const string bubblePopSFX = "Assets/Sounds/General Sounds/Simple Damage Sounds/sfx_damage_hit7.wav"; sf::SoundBuffer bubblePopSFXBuffer; sf::Sound bubblePopSound; enum BubbleType { RED, GREEN, BLUE, YELLOW }; class Bubble : public DrawableEntity { private: void setup() { sprite.setScale(2, 2); if (!bubblePopSound.getBuffer()) { if (!bubblePopSFXBuffer.loadFromFile(bubblePopSFX)) { cout << "Error loading sound: " << bubblePopSFX << endl; } bubblePopSound = sf::Sound(bubblePopSFXBuffer); } } public: BubbleType bubbleType; using DrawableEntity::DrawableEntity; Bubble(float x, float y, BubbleType _bubbleType) : DrawableEntity(x, y, "Assets/Bubble Base.png") { setBubbleType(_bubbleType); setup(); } Bubble(float x, float y) : DrawableEntity(x, y, "Assets/Bubble Base.png") { setBubbleType((BubbleType)round(rand() % sizeof(BubbleType))); setup(); } void popSFX() { bubblePopSound.play(); } void setBubbleType(BubbleType type) { bubbleType = type; switch (type) { case BubbleType::RED: sprite.setColor(sf::Color::Red); break; case BubbleType::GREEN: sprite.setColor(sf::Color::Green); break; case BubbleType::BLUE: sprite.setColor(sf::Color::Blue); break; case BubbleType::YELLOW: sprite.setColor(sf::Color::Yellow); break; } } }; // BubbleGrid.h #pragma once #include #include #include "Bubble.h" using namespace std; const int GRIDROWS = 12, GRIDCOLS = 9; const string newBubbleRowSFX = "Assets/Sounds/General Sounds/Neutral Sounds/sfx_sound_neutral6.wav"; sf::SoundBuffer newBubbleRowSFXBuffer; class BubbleDelivery { public: BubbleType type; int count; BubbleDelivery(int _count, BubbleType _type) { type = _type; count = _count; } }; class BubbleGrid { private: Bubble* grid[GRIDROWS][GRIDCOLS]{}; sf::Sound newRowSound; public: bool gameOver = false; BubbleGrid() { addRow(); if (!newBubbleRowSFXBuffer.loadFromFile(newBubbleRowSFX)) { cout << "Error loading sound: " << newBubbleRowSFX << endl; } newRowSound = sf::Sound(newBubbleRowSFXBuffer); } void triggerGameOver() { gameOver = true; for (auto& row : getGrid()) { for (auto& bubble : row) { if (!bubble) continue; sf::Vector2i bubblePos = bubble->getRelativePosition(); // Why tf is my coords backwards? How tf did I code that?? grid[bubblePos.y][bubblePos.x] = nullptr; bubble->popSFX(); } } } void addRow() { if (gameOver || rowContainsBubbles(11)) { // Last row contains bubbles, game over condition has now been triggered triggerGameOver(); return; } // We've got space, time to add to the grid :3 // First: Move all existing bubbles down by one for (int row = (GRIDROWS - 2); row >= 0; row--) { for (int col = 0; col < GRIDCOLS; col++) { if (!grid[row][col]) continue; grid[row + 1][col] = grid[row][col]; grid[row][col] = nullptr; //cout << "moved " << row << ":" << col << " to " << row + 1 << ":" << col << endl; grid[row + 1][col]->setPosition(col * 64.0f, (row + 1) * 64.0f); } } for (int col = 0; col < GRIDCOLS; col++) { grid[0][col] = new Bubble(col * 64.0f, 0.0f); } newRowSound.play(); } bool rowContainsBubbles(int rowNumber) { for (Bubble* bubble : grid[rowNumber]) { if (!bubble) continue; return true; } return false; } Bubble* (&getGrid())[GRIDROWS][GRIDCOLS] { return grid; } int remainingBubbles() { int bubbles = 0; for (auto& row : getGrid()) { for (auto& col : row) { if (!col) continue; bubbles++; } } return bubbles; } Bubble* (&getRow(int row))[GRIDCOLS] { if (row < 0 || row >(GRIDROWS - 1)) { cout << "Expecting row between 0-11, received: " << row << endl; system("pause"); } return grid[row]; } BubbleDelivery* grabFromCol(int col, BubbleType limiter) { int row = GRIDROWS - 1; int foundBubbles = 0; while (row >= 0) { Bubble* bubble = grid[row][col]; if (!bubble || bubble == nullptr) { row--; continue; } if (limiter > -1 && bubble->bubbleType != limiter) { cout << "Bubble Found, but wrong type so ending chain of collection" << endl; row = -1; continue; } else if (limiter == -1) { cout << "Bubble found, but no limit was set before - so using this type as the limiter" << endl; limiter = bubble->bubbleType; } foundBubbles++; grid[row][col] = nullptr; row--; } return new BubbleDelivery(foundBubbles, limiter); } enum checkIncomingDirection { LEFT, RIGHT, UP, DOWN }; vector checkTouchingOfSame(int row, int col, checkIncomingDirection ignoreDirection, int depth = 0) { cout << "checking for touching in " << row << " | " << col << " | " << depth << endl; Bubble* originBubble = grid[row][col]; if ((depth > GRIDROWS * GRIDCOLS) || !originBubble || (depth > 0 && originBubble == originalCheckBubble)) return {}; // The check above is hacky to prevent infinite recursion... I need to strip out the touch check into somewhere seperate // Where I can work on it in more detail. vector similarBubbles; similarBubbles.push_back(originBubble); if (ignoreDirection != checkIncomingDirection::DOWN && row > 0) { // Check Above if (grid[row - 1][col] && grid[row - 1][col]->bubbleType == originBubble->bubbleType) { for (Bubble* bubble : checkTouchingOfSame(row - 1, col, checkIncomingDirection::UP, depth + 1)) { similarBubbles.push_back(bubble); } } } //if (ignoreDirection != checkIncomingDirection::UP && row < GRIDROWS - 1) { // // Check below // if (grid[row + 1][col] && grid[row + 1][col]->bubbleType == originBubble->bubbleType) { // for (Bubble* bubble : checkTouchingOfSame(row + 1, col, checkIncomingDirection::DOWN, depth + 1)) { // similarBubbles.push_back(bubble); // } // } //} if (ignoreDirection != checkIncomingDirection::RIGHT && col > 0) { // Check left if (grid[row][col - 1] && grid[row][col - 1]->bubbleType == originBubble->bubbleType) { for (Bubble* bubble : checkTouchingOfSame(row, col - 1, checkIncomingDirection::LEFT, depth + 1)) { similarBubbles.push_back(bubble); } } } if (ignoreDirection != checkIncomingDirection::LEFT && col < GRIDCOLS - 1) { // Check Right if (grid[row][col + 1] && grid[row][col + 1]->bubbleType == originBubble->bubbleType) { for (Bubble* bubble : checkTouchingOfSame(row, col + 1, checkIncomingDirection::RIGHT, depth + 1)) { similarBubbles.push_back(bubble); } } } return similarBubbles; } Bubble* originalCheckBubble; // This feels hacky... TODO: Revisit (Original purpose, prevent loops when checking for touching) int checkForMatches(int row, int col) { cout << "checking for match in " << row << " | " << col << endl; Bubble* originBubble = grid[row][col]; if (!originBubble) return 0; originalCheckBubble = originBubble; vector similarBubbles; for (Bubble* bubble : checkTouchingOfSame(row, col, (checkIncomingDirection)-1)) { similarBubbles.push_back(bubble); } cout << "Found " << similarBubbles.size() << " Similar bubbles" << endl; if (similarBubbles.size() >= 3) { cout << "Pop Time!" << endl; // Pop time! // TODO: Add about... maybe 2 points per bubble popped to score? for (Bubble* bubble : similarBubbles) { sf::Vector2i bubblePos = bubble->getRelativePosition(); // Why tf is my coords backwards? How tf did I code that?? cout << "Popping bubble at: " << bubblePos.y << " | " << bubblePos.x << endl; grid[bubblePos.y][bubblePos.x] = nullptr; bubble->popSFX(); } return similarBubbles.size() * 2; } return 0; } bool checkForFloaters() { bool movedBubble = false; for (int row = GRIDROWS - 1; row > 0; row--) { for (int col = GRIDCOLS - 1; col >= 0; col--) { if (!grid[row][col]) continue; if (!grid[row - 1][col]) { cout << "Floater!" << endl; grid[row - 1][col] = grid[row][col]; grid[row][col] = nullptr; grid[row - 1][col]->setPosition(col * 64.0f, (row - 1) * 64.0f); movedBubble = true; int scoreGain = checkForMatches(row - 1, col); cout << "Threw away " << to_string(scoreGain) << " Points... TODO: Add this to player score!" << endl; } } } if (movedBubble) checkForFloaters(); return movedBubble; } class AddToColResult { public: bool success = false; int scoreGain = 0; AddToColResult(bool _success = false, int _scoreGain = 0) { success = _success; scoreGain = _scoreGain; } }; AddToColResult* addToCol(int col, BubbleDelivery* incoming) { int row = GRIDROWS - 1; int startingRow = row; Bubble* lastBubble; while (row >= 0) { Bubble* bubble = grid[row][col]; if (!bubble || bubble == nullptr) { row--; continue; } startingRow = row + 1; break; } if (startingRow + incoming->count > (GRIDROWS - 1)) { // TODO: Allow if incoming count is greater or equal to pop amount, because it will pop before game over. return new AddToColResult(); } for (int i = 0; i < incoming->count; i++) { int row = startingRow + i; grid[row][col] = new Bubble(col * 64.0f, row * 64.0f, incoming->type); } int scoreGain = checkForMatches(startingRow + incoming->count - 1, col); checkForFloaters(); return new AddToColResult(true, scoreGain); } }; // DrawableEntity.h #pragma once #include #include #include using namespace std; class DrawableEntity { protected: sf::Texture texture; sf::Sprite sprite; public: DrawableEntity(float x, float y, string texturePath) { if (texturePath.length() > 0 && !texture.loadFromFile(texturePath)) { cout << "Error loading texture '" << texturePath << "' for DrawableEntity." << endl; } sprite.setTexture(texture); sprite.setPosition(x, y); } sf::Sprite getSprite() { return sprite; } void setTexture(sf::Texture& texture) { sprite.setTexture(texture, true); } void setPosition(float x, float y) { //cout << x << ":" << y << endl; sprite.setPosition(x, y); } sf::Vector2i getRelativePosition() { return sf::Vector2i(sprite.getPosition().x / 64, sprite.getPosition().y / 64); } }; // Main.cpp #include #include #include "BubbleGrid.h" #include "Player.h" #include "NPC.h" using namespace std; sf::Vector2f RESOLUTION(576 + 192, 768); sf::RenderWindow GAMEWINDOW(sf::VideoMode(RESOLUTION.x, RESOLUTION.y), "Bubble Pop"); int main() { // Seed randomise function with current time. srand(time(NULL)); bool gameOver = false; BubbleGrid* bubbleGrid = new BubbleGrid(); Player* player = new Player(0.0f, 11 * 64.0f); NPC* npc = new NPC(576 + 32, 128 + 32); sf::Clock deltaClock; sf::Clock bubbleClock; sf::Font font; font.loadFromFile("C:/Windows/Fonts/comic.ttf"); sf::Text scoreText; scoreText.setFont(font); scoreText.setPosition(576 + 8, 8); scoreText.setCharacterSize(24); scoreText.setFillColor(sf::Color::Yellow); scoreText.setOutlineColor(sf::Color::Black); scoreText.setOutlineThickness(3); scoreText.setString("Score: 0"); sf::Text bubbleCount; bubbleCount.setFont(font); bubbleCount.setPosition(576 + 8 + 32, 64 + 32); bubbleCount.setCharacterSize(24); bubbleCount.setFillColor(sf::Color::Yellow); bubbleCount.setOutlineColor(sf::Color::Black); bubbleCount.setOutlineThickness(3); bubbleCount.setString("0"); Bubble* UIHoldingBubble = new Bubble(576 + 8, 64); sf::Texture BGTEXTURE; sf::Sprite BGSPRITE; if (!BGTEXTURE.loadFromFile("Assets/Background.png")) { cout << "Error while loading background image" << endl; } BGSPRITE.setTexture(BGTEXTURE); sf::Texture GAMEOVERTEXTURE; sf::Sprite GAMEOVERSPRITE; GAMEOVERSPRITE.setPosition(0, -768); if (!GAMEOVERTEXTURE.loadFromFile("Assets/GameOver.png")) { cout << "Error while loading background image" << endl; } GAMEOVERSPRITE.setTexture(GAMEOVERTEXTURE); while (GAMEWINDOW.isOpen()) { sf::Time deltaTime = deltaClock.restart(); // deltaTime.asSeconds() - deltaClock.getElapsedTime().asSeconds(); float elapsedSeconds = bubbleClock.getElapsedTime().asSeconds(); float difficultyOffset = player->score / 350; if (elapsedSeconds > (5.0f - difficultyOffset)) { bubbleClock.restart(); bubbleGrid->addRow(); npc->setMood(NPC::NEUTRAL); } else if (elapsedSeconds > .5 && bubbleGrid->remainingBubbles() < GRIDCOLS * 3) { bubbleClock.restart(); bubbleGrid->addRow(); } if (npc->currentMood >= 2 && bubbleGrid->rowContainsBubbles(ceil(GRIDROWS * 0.7))) { npc->setMood(NPC::WORRIED); } else if (npc->currentMood >= 2 && bubbleGrid->rowContainsBubbles(ceil(GRIDROWS * 0.5))) { npc->setMood(NPC::UNSURE); } sf::Event event; while (GAMEWINDOW.pollEvent(event)) { if (event.type == sf::Event::Closed) { GAMEWINDOW.close(); } if (event.type == sf::Event::KeyPressed) { PlayerEvent* playerEvent = player->handleMovement(event, deltaTime.asSeconds() - deltaClock.getElapsedTime().asSeconds()); sf::Vector2i playerGridPos = player->getRelativePosition(); if (playerEvent->type == PlayerEventType::THROW) { // Fire currently held bubbles cout << "player wants to fire" << endl; BubbleDelivery* delivery = new BubbleDelivery(player->holdCount, player->holdingBubble); BubbleGrid::AddToColResult* addToColResult = bubbleGrid->addToCol(playerGridPos.x, delivery); if (addToColResult->success) { player->holdCount = 0; player->holdingBubble = (BubbleType) - 1; player->score += addToColResult->scoreGain; if (addToColResult->scoreGain >= 10) npc->setMood(NPC::ECSTATIC); else if (addToColResult->scoreGain > 0) npc->setMood(NPC::HAPPY); scoreText.setString("Score: " + to_string(player->score)); } } else if (playerEvent->type == PlayerEventType::GRAB) { // Check if can hold incoming bubbles, and if so pick em up cout << "player wants to grab" << endl; int limiter = -1; if (player->holdCount > 0) limiter = player->holdingBubble; BubbleDelivery* incoming = bubbleGrid->grabFromCol(playerGridPos.x, (BubbleType)limiter); if (incoming->count > 0) { player->holdingBubble = incoming->type; player->holdCount += incoming->count; } } } } GAMEWINDOW.clear(); GAMEWINDOW.draw(BGSPRITE); GAMEWINDOW.draw(npc->getSprite()); if (bubbleGrid->gameOver) { sf::Vector2f curPos = GAMEOVERSPRITE.getPosition(); float y = curPos.y; if (y < 0) y = curPos.y + 0.25; GAMEOVERSPRITE.setPosition(0, y); GAMEWINDOW.draw(GAMEOVERSPRITE); } else { for (auto& row : bubbleGrid->getGrid()) { for (auto& col : row) { if (!col) continue; GAMEWINDOW.draw(col->getSprite()); } } GAMEWINDOW.draw(player->getSprite()); if (player->holdCount > 0) { UIHoldingBubble->setBubbleType(player->holdingBubble); bubbleCount.setString(to_string(player->holdCount)); GAMEWINDOW.draw(UIHoldingBubble->getSprite()); GAMEWINDOW.draw(bubbleCount); } } GAMEWINDOW.draw(scoreText); GAMEWINDOW.display(); } return 0; } // NPC.h #pragma once #include #include #include "Bubble.h" #include "DrawableEntity.h" using namespace std; sf::Texture moodTextures[5] = {}; class NPC : public DrawableEntity { private: public: using DrawableEntity::DrawableEntity; enum MoodAsset { NEUTRAL = 2, HAPPY = 3, ECSTATIC = 4, UNSURE = 1, WORRIED = 0 }; MoodAsset currentMood = MoodAsset::NEUTRAL; NPC (float x, float y) : DrawableEntity(x, y, "Assets/NPC/0.png") { sprite.setScale(4, 4); for (int i = -2; i <= 2; i++) { moodTextures[i + 2].loadFromFile("Assets/NPC/" + to_string(i) + ".png"); } } void setMood(MoodAsset mood) { currentMood = mood; this->setTexture(moodTextures[mood]); } }; // Player.h #pragma once #include #include #include "Bubble.h" #include "DrawableEntity.h" using namespace std; enum PlayerEventType { NONE, GRAB, THROW }; class PlayerEvent { public: PlayerEventType type; PlayerEvent() { type = PlayerEventType::NONE; } }; class ThrowBubbleEvent : public PlayerEvent { public: BubbleType bubbleType; int bubbleCount; using PlayerEvent::PlayerEvent; ThrowBubbleEvent() { type = PlayerEventType::THROW; } }; class GrabBubbleEvent: public PlayerEvent { public: using PlayerEvent::PlayerEvent; GrabBubbleEvent() { type = PlayerEventType::GRAB; } }; class Player : public DrawableEntity { protected: public: BubbleType holdingBubble; int holdCount = 0; int score = 0; using DrawableEntity::DrawableEntity; Player(float x, float y) : DrawableEntity(x, y, "Assets/Jimmy.png") { sprite.setScale(2, 2); } PlayerEvent* handleMovement(sf::Event event, float deltaTime) { sf::Vector2f curPos = sprite.getPosition(); if (holdCount > 0 && event.key.code == sf::Keyboard::Up) { ThrowBubbleEvent* event = new ThrowBubbleEvent(); return event; } else if (event.key.code == sf::Keyboard::Down) { GrabBubbleEvent* event = new GrabBubbleEvent(); return event; } else if (event.key.code == sf::Keyboard::Right) { int newX = curPos.x + 64; if (curPos.x > (7 * 64)) newX = 0; sprite.setPosition(newX, curPos.y); } else if (event.key.code == sf::Keyboard::Left) { int newX = curPos.x - 64; if (curPos.x < 64) newX = 8 * 64; sprite.setPosition(newX, curPos.y); } return new PlayerEvent(); } };