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