Skip to content

Commit

Permalink
flesh out game rules
Browse files Browse the repository at this point in the history
  • Loading branch information
fallahn committed Oct 11, 2024
1 parent 82f2063 commit c85aafe
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 62 deletions.
146 changes: 96 additions & 50 deletions samples/scratchpad/src/scrub/ScrubGameState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ namespace
}
#endif

std::vector<float> scrubTimes;

/*
Handle default position is 0 on y
and -StrokeDistance when fully inserted
Expand Down Expand Up @@ -123,8 +121,7 @@ namespace
ScrubGameState::ScrubGameState(cro::StateStack& stack, cro::State::Context context)
: cro::State (stack, context),
m_gameScene (context.appInstance.getMessageBus()),
m_uiScene (context.appInstance.getMessageBus()),
m_soapCount (3)
m_uiScene (context.appInstance.getMessageBus())
{
//this is a pre-cached state
//context.mainWindow.loadResources([this]() {
Expand All @@ -135,18 +132,6 @@ ScrubGameState::ScrubGameState(cro::StateStack& stack, cro::State::Context conte
//});
}

ScrubGameState::~ScrubGameState()
{
LogI << "Remove me" << std::endl;
float total = 0.f;
for (auto t : scrubTimes)
{
total += t;
}
total /= scrubTimes.size();
LogI << "Avg time: " << total << std::endl;
}

//public
bool ScrubGameState::handleEvent(const cro::Event& evt)
{
Expand All @@ -171,18 +156,19 @@ bool ScrubGameState::handleEvent(const cro::Event& evt)
//TODO these should all be moved to funcs so we can also use controller input
if (evt.key.keysym.sym == m_sharedData.inputBinding.keys[InputBinding::Left])
{
m_ball.filth = std::max(0.f, m_ball.filth - m_handle.switchDirection(Handle::Down));
m_ball.scrub(m_handle.switchDirection(Handle::Down));
}
else if (evt.key.keysym.sym == m_sharedData.inputBinding.keys[InputBinding::Right])
{
m_ball.filth = std::max(0.f, m_ball.filth - m_handle.switchDirection(Handle::Up));
m_ball.scrub(m_handle.switchDirection(Handle::Up));
}
else if (evt.key.keysym.sym == m_sharedData.inputBinding.keys[InputBinding::PrevClub])
{
//insert ball
if (m_handle.progress == 0
&& m_handle.speed == 0
&& !m_handle.hasBall)
&& !m_handle.hasBall
&& m_ball.state == Ball::State::Idle)
{
m_handle.locked = true;

Expand All @@ -204,9 +190,9 @@ bool ScrubGameState::handleEvent(const cro::Event& evt)
}
else if (evt.key.keysym.sym == m_sharedData.inputBinding.keys[InputBinding::Action])
{
if (m_soapCount)
if (m_handle.soap.count)
{
m_soapCount--;
m_handle.soap.count--;
m_handle.soap.refresh();
}
}
Expand All @@ -228,21 +214,24 @@ bool ScrubGameState::simulate(float dt)
{
if (m_score.gameRunning)
{
m_score.totalRunTime += dt;
m_score.remainingTime = std::max(m_score.remainingTime - dt, 0.f);
if (m_score.remainingTime == 0)
{
m_score.gameRunning = false;
m_score.totalScore += static_cast<std::int32_t>(std::floor(m_score.avgCleanliness));
m_score.totalScore += static_cast<std::int32_t>(std::floor(m_score.totalRunTime));

//game over, show scores.
const auto& font = m_resources.fonts.get(FontID::UI);

glm::vec2 size(cro::App::getWindow().getSize());
auto entity = m_uiScene.createEntity();
entity.addComponent<cro::Transform>().setPosition({ size.x / 2.f, size.y / 2.f });
entity.addComponent<cro::Drawable2D>();
entity.addComponent<cro::Text>(font).setString("Game Over");
entity.addComponent<cro::Text>(font).setString("Game Over\nTotal Score: " + std::to_string(m_score.totalScore));
entity.getComponent<cro::Text>().setCharacterSize(8 * 4);
entity.getComponent<cro::Text>().setAlignment(cro::Text::Alignment::Centre);

m_score.gameRunning = false;
}
}

Expand Down Expand Up @@ -328,8 +317,10 @@ void ScrubGameState::createScene()
cam.resizeCallback = resize;
resize(cam);

camera.getComponent<cro::Transform>().setPosition({ 0.f, 0.05f, 0.25f });
camera.getComponent<cro::Transform>().rotate(cro::Transform::X_AXIS, -0.1f);
//camera.getComponent<cro::Transform>().setPosition({ 0.f, 0.05f, 0.25f });
//camera.getComponent<cro::Transform>().rotate(cro::Transform::X_AXIS, -0.1f);

camera.getComponent<cro::Transform>().setLocalTransform(glm::inverse(glm::lookAt(glm::vec3(-0.04f, 0.15f, 0.28f), glm::vec3(0.f, 0.01f, 0.f), cro::Transform::Y_AXIS)));

m_gameScene.getSunlight().getComponent<cro::Transform>().rotate(cro::Transform::X_AXIS, -1.2f);
m_gameScene.getSunlight().getComponent<cro::Transform>().rotate(cro::Transform::Y_AXIS, -0.6f);
Expand All @@ -352,28 +343,23 @@ void ScrubGameState::createUI()
ImGui::Text("Handle Speed: %3.2f", m_handle.speed);
ImGui::Text("Handle Direction: %3.2f", m_handle.direction);

//TODO make this steeper as we progress in the game (idk, water is getting dirtier or something)
ImGui::Text("Handle Stroke: %3.2f", cro::Util::Easing::easeInQuad(m_handle.stroke));
ImGui::Text("Handle Stroke: %3.2f", m_handle.stroke);
ImGui::Text("Handle Progress: %3.2f", m_handle.progress);

ImGui::NewLine();
ImGui::Separator();
ImGui::NewLine();

ImGui::Text("Ball Filth: %3.2f", m_ball.filth);
ImVec4 c = (Ball::MaxFilth - m_ball.filth) > m_score.threshold ? ImVec4(0.f, 1.f, 0.f, 1.f) : ImVec4(1.f, 0.f, 0.f, 1.f);
ImGui::SameLine();
ImGui::ColorButton("##buns", c, 0, { 12.f, 12.f });

ImGui::Text("Soap Level: %3.2f", m_handle.soap.amount);
ImGui::Text("Soap Bars: %d", m_soapCount);
ImGui::Text("Soap Bars: %d", m_handle.soap.count);
ImGui::Text("Soap LifeTime %3.3f", m_handle.soap.lifeTime);
ImGui::Text("Soap Reduction %3.3f", m_handle.soap.getReduction());


static float oldFilth = 100.f;
if (m_ball.filth == 0 && oldFilth != 0)
{
scrubTimes.push_back(soapTimer.elapsed().asSeconds());
}
oldFilth = m_ball.filth;

ImGui::End();
});

Expand Down Expand Up @@ -451,7 +437,7 @@ void ScrubGameState::handleCallback(cro::Entity e, float dt)
{
if (m_handle.speed != 0)
{
m_ball.filth = std::max(0.f, m_ball.filth - m_handle.calcStroke());
m_ball.scrub(m_handle.calcStroke());
}
m_handle.speed = 0.f;
}
Expand All @@ -476,8 +462,6 @@ void ScrubGameState::ballCallback(cro::Entity e, float dt)
m_handle.locked = false;
m_handle.hasBall = true;
m_ball.state = Ball::State::Clean;

soapTimer.restart();
}
}
break;
Expand All @@ -494,7 +478,7 @@ void ScrubGameState::ballCallback(cro::Entity e, float dt)
updateScore();

m_ball.state = Ball::State::Idle;
m_ball.filth = 100.f;
m_ball.filth = Ball::MaxFilth;
pos.x = -BallOffsetPos;

m_ball.colourIndex += cro::Util::Random::value(1, 3);
Expand All @@ -509,25 +493,87 @@ void ScrubGameState::ballCallback(cro::Entity e, float dt)

void ScrubGameState::updateScore()
{
const float cleanliness = 100.f - m_ball.filth;
const float cleanliness = Ball::MaxFilth - m_ball.filth;

if (cleanliness < m_score.threshold)
{
//TODO display notification
return;
}

m_score.ballsWashed++;
m_score.cleanlinessSum += cleanliness;
m_score.avgCleanliness = m_score.cleanlinessSum / m_score.ballsWashed;

//TODO penalise time for very grubby balls?
m_score.remainingTime += Score::TimeBonus * (cleanliness / 100.f);
m_score.totalScore += static_cast<std::int32_t>(std::floor(cleanliness));

//this might happen just as the time runs out - we want to
//keep the score but not add time in this case
if (m_score.gameRunning)
{
m_score.remainingTime += Score::TimeBonus * (cleanliness / 100.f);
}


//track bonus runs of 3x 100%, 5x 100% and 10x 100%
if (cleanliness == 100.f)
{
m_score.bonusRun++;
switch (m_score.bonusRun)
{
//this should never be 0, but just in case...
case 0: break;
default:
//every 10 after
if (m_score.bonusRun % 10 == 0)
{
m_score.totalScore += 10000;
m_score.remainingTime + 10.f;
}
break;
case 3:
m_score.totalScore += 3000;
m_score.remainingTime += 0.5f;
break;
case 5:
m_score.totalScore += 5000;
m_score.remainingTime + 2.f;
break;
}

//TODO display notification
}
else
{
m_score.bonusRun = 0;
}

//TODO track bonus runs of 3x 100%, 5x 100% and 10x 100%

//TODO develop some actual scoring system and calc the score
if (m_score.ballsWashed % 5 == 0)
{
//new soap in 3.. 2.. 1..
auto ent = m_gameScene.createEntity();
ent.addComponent<cro::Callback>().active = true;
ent.getComponent<cro::Callback>().setUserData<float>(3.f);
ent.getComponent<cro::Callback>().function =
[&](cro::Entity e, float dt)
{
auto& currTime = e.getComponent<cro::Callback>().getUserData<float>();
currTime -= dt;

//TODO every X balls is a new soap, or every X points?
if (currTime < 0)
{
m_score.totalScore += 500;

m_handle.soap.count = std::min(MaxSoapBars, m_handle.soap.count + 1);
e.getComponent<cro::Callback>().active = false;
m_gameScene.destroyEntity(e);
}
};

//TODO increase the scub curve - I've forgotten what I meant by this...
m_score.threshold = std::min(Ball::MaxFilth, m_score.threshold + 4.f);
m_score.remainingTime += 0.5f;
}
}

//handle funcs
Expand All @@ -547,7 +593,7 @@ float ScrubGameState::Handle::switchDirection(float d)

if (hasBall)
{
soap.amount = std::max(1.f, soap.amount - soap.getReduction());
soap.amount = std::max(Soap::MinSoap, soap.amount - soap.getReduction());
}
}
return ret;
Expand All @@ -561,7 +607,7 @@ float ScrubGameState::Handle::calcStroke()

if (hasBall)
{
return stroke * soap.amount;
return cro::Util::Easing::easeInQuad(stroke) * soap.amount;
}
return 0.f;
}
41 changes: 29 additions & 12 deletions samples/scratchpad/src/scrub/ScrubGameState.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ScrubGameState final : public cro::State, public cro::GuiClient
{
public:
ScrubGameState(cro::StateStack&, cro::State::Context);
~ScrubGameState(); //TODO remove this

cro::StateID getStateID() const override { return States::ScratchPad::Scrub; }

bool handleEvent(const cro::Event&) override;
Expand All @@ -45,7 +45,7 @@ class ScrubGameState final : public cro::State, public cro::GuiClient
float progress = 0.f;
float speed = 0.f;

static constexpr float MaxSpeed = 6.f;
static constexpr float MaxSpeed = 8.f;

float stroke = 0.f;
float strokeStart = 0.f;
Expand All @@ -61,14 +61,16 @@ class ScrubGameState final : public cro::State, public cro::GuiClient
struct Soap final
{
static constexpr float MaxSoap = 10.f;
static constexpr float MinSoap = 1.f;
static constexpr float Reduction = 0.5f;
static constexpr float MinSoap = 3.f;
static constexpr float Reduction = 0.7f;
float amount = MaxSoap;

//the older the soap is the more quickly it diminishes
float lifeTime = 0.f;
static constexpr float MaxLifetime = 12.f;

std::int32_t count = 1;

float getReduction() const
{
return (Soap::Reduction * cro::Util::Easing::easeInExpo(std::min((lifeTime / Soap::MaxLifetime), 1.f)));
Expand All @@ -82,14 +84,12 @@ class ScrubGameState final : public cro::State, public cro::GuiClient
}soap;

}m_handle;
std::int32_t m_soapCount;

cro::Clock soapTimer; //TODO remove this


struct Ball final
{
float filth = 100.f;
static constexpr float MaxFilth = 100.f;
float filth = MaxFilth;

struct State final
{
Expand All @@ -105,10 +105,23 @@ class ScrubGameState final : public cro::State, public cro::GuiClient
std::size_t colourIndex = 0;

cro::Entity entity;

void scrub(float amt)
{
//make the ball harder to clean the nearer it is to
//actually being clean... TODO we could reduce this
//over time to make the game increase in difficulty
const auto multiplier = 0.35f + (0.65f * (filth / MaxFilth));
amt *= multiplier;

filth = std::max(0.f, filth - amt);
}
}m_ball;

struct Score final
{
float threshold = 80.f; //ball must be cleaner than this to score

float remainingTime = 30.f;
std::int32_t ballsWashed = 0;
float avgCleanliness = 0.f;
Expand All @@ -118,12 +131,16 @@ class ScrubGameState final : public cro::State, public cro::GuiClient

bool gameRunning = false;

//how many balls were 100% in a row
std::int32_t bonusRun = 0;

float totalRunTime = 0.f;
std::int32_t totalScore = 0;


//this is the amount of time added multiplied
//this is the amount of time awarded multiplied
//by how clean the ball is when it's removed.
//currently calc'd by the avg time it takes to
//scrub a ball clean with 50% soap available.
static constexpr float TimeBonus = 6.f;
static constexpr float TimeBonus = 3.f;
}m_score;

void addSystems();
Expand Down

0 comments on commit c85aafe

Please sign in to comment.