Codename Hamster - For olcCodeJam 2024
 
 
 
hamster/src/main.cpp

364 lines
12 KiB

#include "olcPixelGameEngine.h"
#include "olcPGEX_MiniAudio.h"
class OneLoneCoder_Asteroids : public olc::PixelGameEngine
{
public:
OneLoneCoder_Asteroids()
{
sAppName = "Asteroids";
}
private:
struct sSpaceObject
{
int nSize;
float x;
float y;
float dx;
float dy;
float angle;
};
std::vector<sSpaceObject> vecAsteroids;
std::vector<sSpaceObject> vecBullets;
sSpaceObject player;
bool bDead = false;
int nScore = 0;
std::vector<std::pair<float, float>> vecModelShip;
std::vector<std::pair<float, float>> vecModelAsteroid;
std::map<std::string, olc::Renderable*> gfx;
std::map<std::string, int> sfx;
olc::MiniAudio audio;
public:
bool OnUserCreate() override
{
auto loadGraphic = [&](const std::string& key, const std::string& filepath)
{
olc::Renderable* renderable = new olc::Renderable();
renderable->Load(filepath);
gfx[key] = renderable;
};
auto loadSound = [&](const std::string& key, const std::string& filepath)
{
sfx[key] = audio.LoadSound(filepath);
};
loadGraphic("background", "assets/gfx/space.png");
loadSound("bg-music", "assets/sounds/bg-music.wav");
loadSound("laser", "assets/sounds/Laser_Shoot11.wav");
loadSound("explosion", "assets/sounds/Explosions1.wav");
loadSound("lose", "assets/sounds/lose9.wav");
loadSound("thruster", "assets/sounds/thruster.wav");
vecModelShip =
{
{ 0.0f, -5.0f},
{-2.5f, +2.5f},
{+2.5f, +2.5f}
}; // A simple Isoceles Triangle
// Create a "jagged" circle for the asteroid. It's important it remains
// mostly circular, as we do a simple collision check against a perfect
// circle.
int verts = 20;
for (int i = 0; i < verts; i++)
{
float noise = (float)rand() / (float)RAND_MAX * 0.4f + 0.8f;
vecModelAsteroid.push_back(std::make_pair(noise * sinf(((float)i / (float)verts) * 6.28318f),
noise * cosf(((float)i / (float)verts) * 6.28318f)));
}
backgroundLayer = CreateLayer();
EnableLayer(backgroundLayer, true);
SetDrawTarget(backgroundLayer);
DrawSprite(0,0, gfx.at("background")->Sprite());
SetDrawTarget(nullptr);
ResetGame();
audio.Play(sfx.at("bg-music"), true);
return true;
}
bool OnUserDestroy() override
{
for(auto it = gfx.begin(); it != gfx.end(); ++it)
{
delete it->second;
}
return true;
}
int backgroundLayer = -1;
void ResetGame()
{
// Initialise Player Position
player.x = ScreenWidth() / 2.0f;
player.y = ScreenHeight() / 2.0f;
player.dx = 0.0f;
player.dy = 0.0f;
player.angle = 0.0f;
vecBullets.clear();
vecAsteroids.clear();
// Put in two asteroids
vecAsteroids.push_back({ (int)16, player.x - 80.0f, player.y, 10.0f, 40.0f, 0.0f });
vecAsteroids.push_back({ (int)16, player.x + 80.0f, player.y, -10.0f, -40.0f, 0.0f });
// Reset game
bDead = false;
nScore = false;
}
// Implements "wrap around" for various in-game sytems
void WrapCoordinates(float ix, float iy, float &ox, float &oy)
{
ox = ix;
oy = iy;
if (ix < 0.0f) ox = ix + (float)ScreenWidth();
if (ix >= (float)ScreenWidth()) ox = ix - (float)ScreenWidth();
if (iy < 0.0f) oy = iy + (float)ScreenHeight();
if (iy >= (float)ScreenHeight()) oy = iy - (float)ScreenHeight();
}
// Overriden to handle toroidal drawing routines
bool Draw(int x, int y, olc::Pixel col = olc::WHITE) override
{
float fx, fy;
WrapCoordinates(x, y, fx, fy);
return olc::PixelGameEngine::Draw(fx, fy, col);
}
bool IsPointInsideCircle(float cx, float cy, float radius, float x, float y)
{
return sqrt((x-cx)*(x-cx) + (y-cy)*(y-cy)) < radius;
}
// Called by olcConsoleGameEngine
bool OnUserUpdate(float fElapsedTime) override
{
if (bDead)
ResetGame();
// Clear Screen
Clear(olc::BLANK);
// Steer Ship
if (GetKey(olc::LEFT).bHeld)
player.angle -= 5.0f * fElapsedTime;
if (GetKey(olc::RIGHT).bHeld)
player.angle += 5.0f * fElapsedTime;
// Thrust? Apply ACCELERATION
if (GetKey(olc::UP).bHeld)
{
// ACCELERATION changes VELOCITY (with respect to time)
player.dx += sin(player.angle) * 20.0f * fElapsedTime;
player.dy += -cos(player.angle) * 20.0f * fElapsedTime;
}
if(GetKey(olc::UP).bPressed)
audio.Play(sfx.at("thruster"), true);
if(GetKey(olc::UP).bReleased)
audio.Stop(sfx.at("thruster"));
// VELOCITY changes POSITION (with respect to time)
player.x += player.dx * fElapsedTime;
player.y += player.dy * fElapsedTime;
// Keep ship in gamespace
WrapCoordinates(player.x, player.y, player.x, player.y);
// Check ship collision with asteroids
for (auto &a : vecAsteroids)
if (IsPointInsideCircle(a.x, a.y, a.nSize, player.x, player.y))
{
bDead = true; // Uh oh...
audio.Play(sfx.at("lose"));
}
// Fire Bullet in direction of player
if (GetKey(olc::SPACE).bReleased)
{
vecBullets.push_back({ 0, player.x, player.y, 150.0f * sinf(player.angle), -150.0f * cosf(player.angle), 100.0f });
audio.Play(sfx.at("laser"));
}
// Update and draw asteroids
for (auto &a : vecAsteroids)
{
// VELOCITY changes POSITION (with respect to time)
a.x += a.dx * fElapsedTime;
a.y += a.dy * fElapsedTime;
a.angle += 0.5f * fElapsedTime; // Add swanky rotation :)
// Asteroid coordinates are kept in game space (toroidal mapping)
WrapCoordinates(a.x, a.y, a.x, a.y);
// Draw Asteroids
DrawWireFrameModel(vecModelAsteroid, a.x, a.y, a.angle, (float)a.nSize, olc::YELLOW);
}
// Any new asteroids created after collision detection are stored
// in a temporary vector, so we don't interfere with the asteroids
// vector iterator in the for(auto)
std::vector<sSpaceObject> newAsteroids;
// Update Bullets
for (auto &b : vecBullets)
{
b.x += b.dx * fElapsedTime;
b.y += b.dy * fElapsedTime;
WrapCoordinates(b.x, b.y, b.x, b.y);
b.angle -= 1.0f * fElapsedTime;
// Check collision with asteroids
for (auto &a : vecAsteroids)
{
//if (IsPointInsideRectangle(a.x, a.y, a.x + a.nSize, a.y + a.nSize, b.x, b.y))
if(IsPointInsideCircle(a.x, a.y, a.nSize, b.x, b.y))
{
// Asteroid Hit - Remove bullet
// We've already updated the bullets, so force bullet to be offscreen
// so it is cleaned up by the removal algorithm.
b.x = -100;
// Create child asteroids
if (a.nSize > 4)
{
float angle1 = ((float)rand() / (float)RAND_MAX) * 6.283185f;
float angle2 = ((float)rand() / (float)RAND_MAX) * 6.283185f;
newAsteroids.push_back({ (int)a.nSize >> 1 ,a.x, a.y, 30.0f * sinf(angle1), 30.0f * cosf(angle1), 0.0f });
newAsteroids.push_back({ (int)a.nSize >> 1 ,a.x, a.y, 30.0f * sinf(angle2), 30.0f * cosf(angle2), 0.0f });
}
// Remove asteroid - Same approach as bullets
a.x = -100;
nScore += 100; // Small score increase for hitting asteroid
audio.Play(sfx.at("explosion"));
}
}
}
// Append new asteroids to existing vector
for(auto a:newAsteroids)
vecAsteroids.push_back(a);
// Clear up dead objects - They are out of game space
// Remove asteroids that have been blown up
if (vecAsteroids.size() > 0)
{
auto i = remove_if(vecAsteroids.begin(), vecAsteroids.end(), [&](sSpaceObject o) { return (o.x < 0); });
if (i != vecAsteroids.end())
vecAsteroids.erase(i);
}
if (vecAsteroids.empty()) // If no asteroids, level complete! :) - you win MORE asteroids!
{
// Level Clear
nScore += 1000; // Large score for level progression
vecAsteroids.clear();
vecBullets.clear();
// Add two new asteroids, but in a place where the player is not, we'll simply
// add them 90 degrees left and right to the player, their coordinates will
// be wrapped by th enext asteroid update
vecAsteroids.push_back({ (int)16, 80.0f * sinf(player.angle - 3.14159f/2.0f) + player.x,
80.0f * cosf(player.angle - 3.14159f/2.0f) + player.y,
60.0f * sinf(player.angle), 60.0f*cosf(player.angle), 0.0f });
vecAsteroids.push_back({ (int)16, 80.0f * sinf(player.angle + 3.14159f/2.0f) + player.x,
80.0f * cosf(player.angle + 3.14159f/2.0f) + player.y,
60.0f * sinf(-player.angle), 60.0f*cosf(-player.angle), 0.0f });
}
// Remove bullets that have gone off screen
if (vecBullets.size() > 0)
{
auto i = remove_if(vecBullets.begin(), vecBullets.end(), [&](sSpaceObject o) { return (o.x < 1 || o.y < 1 || o.x >= ScreenWidth() - 1 || o.y >= ScreenHeight() - 1); });
if (i != vecBullets.end())
vecBullets.erase(i);
}
// Draw Bullets
for (auto b : vecBullets)
Draw(b.x, b.y);
// Draw Ship
DrawWireFrameModel(vecModelShip, player.x, player.y, player.angle);
// Draw Score
DrawString(2, 2, "SCORE: " + std::to_string(nScore));
return !GetKey(olc::ESCAPE).bPressed;
}
void DrawWireFrameModel(const std::vector<std::pair<float, float>> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, olc::Pixel col = olc::WHITE)
{
// pair.first = x coordinate
// pair.second = y coordinate
// Create translated model vector of coordinate pairs
std::vector<std::pair<float, float>> vecTransformedCoordinates;
int verts = vecModelCoordinates.size();
vecTransformedCoordinates.resize(verts);
// Rotate
for (int i = 0; i < verts; i++)
{
vecTransformedCoordinates[i].first = vecModelCoordinates[i].first * cosf(r) - vecModelCoordinates[i].second * sinf(r);
vecTransformedCoordinates[i].second = vecModelCoordinates[i].first * sinf(r) + vecModelCoordinates[i].second * cosf(r);
}
// Scale
for (int i = 0; i < verts; i++)
{
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first * s;
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second * s;
}
// Translate
for (int i = 0; i < verts; i++)
{
vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first + x;
vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second + y;
}
// Draw Closed Polygon
for (int i = 0; i < verts + 1; i++)
{
int j = (i + 1);
DrawLine(vecTransformedCoordinates[i % verts].first, vecTransformedCoordinates[i % verts].second,
vecTransformedCoordinates[j % verts].first, vecTransformedCoordinates[j % verts].second, col);
}
}
};
int main()
{
OneLoneCoder_Asteroids game;
if(game.Construct(320, 180, 4, 4))
game.Start();
return 0;
}