Skip to content
This repository has been archived by the owner on Sep 20, 2023. It is now read-only.

Implement invulnerability after shrinking #695

Merged
merged 8 commits into from
May 10, 2023
Merged

Implement invulnerability after shrinking #695

merged 8 commits into from
May 10, 2023

Conversation

EvoLandEco
Copy link
Contributor

@EvoLandEco EvoLandEco commented Dec 23, 2022

fixes #535
The logic is:

  1. When a player loses, it shrinks and becomes passive for a while
  2. When a player is passive, it cannot grow or shrink, it cannot make other players grow or shrink either, it will be like a ghost
  3. After a while, an passive player will become active again

@EvoLandEco EvoLandEco linked an issue Dec 23, 2022 that may be closed by this pull request
@EvoLandEco EvoLandEco self-assigned this Dec 23, 2022
@codecov-commenter
Copy link

codecov-commenter commented Dec 23, 2022

Codecov Report

Merging #695 (46d00f4) into develop (8dd1863) will increase coverage by 0.94%.
The diff coverage is 100.00%.

@@             Coverage Diff             @@
##           develop     #695      +/-   ##
===========================================
+ Coverage    86.96%   87.91%   +0.94%     
===========================================
  Files           37       37              
  Lines         1634     1737     +103     
  Branches       124      133       +9     
===========================================
+ Hits          1421     1527     +106     
+ Misses         213      210       -3     
Impacted Files Coverage Δ
src/game.h 100.00% <ø> (ø)
src/game.cpp 97.36% <100.00%> (+0.60%) ⬆️
src/player.cpp 99.61% <100.00%> (+0.78%) ⬆️
src/player.h 100.00% <100.00%> (ø)
src/player_state.cpp 100.00% <100.00%> (ø)
src/game_functions.cpp 80.00% <0.00%> (+1.66%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@TheoPannetier
Copy link
Contributor

I've assigned you to #381 #382 #463; please make sure that you assign yourself to issues before you start working on them! Otherwise other people may decide to work on it too, come up with a different solution, eventually causing a merge conflict (and some wasted time)!

@EvoLandEco
Copy link
Contributor Author

I've assigned you to #381 #382 #463; please make sure that you assign yourself to issues before you start working on them! Otherwise other people may decide to work on it too, come up with a different solution, eventually causing a merge conflict (and some wasted time)!

Yes, I should have done it before this PR. I also assigned myself #469 😸

@EvoLandEco
Copy link
Contributor Author

When I was thinking about adding visual effect for invulnerability, I feel like the current implementation is bad. The reason is, we already have a player_state class, a better option would be to expand this enum class, add some states into it, and build the whole thing on top of it, rather than inventing a new flag m_invulnerability in the player class.

@TheoPannetier @swom apologies for inviting you as reviewers prematurely and (maybe) have already wasted a bit of your time. I will re-implement invulnerability,

@EvoLandEco EvoLandEco linked an issue Dec 25, 2022 that may be closed by this pull request
@@ -129,8 +129,8 @@ void add_action(player& p, action_type action) noexcept

bool are_colliding(const player &lhs, const player &rhs) noexcept
{
if (lhs.get_state() == player_state::active
&& rhs.get_state() == player_state::active)
Copy link
Contributor Author

@EvoLandEco EvoLandEco Dec 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things become more complicated here: how do we define colliding? Simply two players are overlapping? Or they should all be non-passive and/or non-invulnerable? Here I chose to define colliding as they are simply overlapping, so as long as they are not out/dead, they can collide. This will not break the other part of the code. We might need to discuss those mechanics in depth later.

We could add a feature later to make non-passive players unable to overlap with each other

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think it confused me too the first time I saw this; colliding means overlapping AND active, i.e. capable to interact. If two players (or other objects) are overlapping but one or either is not active, there is no collision. I can't think of a better name for this, unfortunately!

src/game.cpp Outdated
Comment on lines 632 to 671
#define FIX_ISSUE_381
#ifdef FIX_ISSUE_381
///A player can become invulnerable
/// A player can become passive
{
game g;

assert(is_active(g.get_player(0)));
g.set_player_state_passive(g.get_player(0));
assert(is_passive(g.get_player(0)));
}

/// A player can become passive, using player.set_state_passive()
{
game g;

assert(is_active(g.get_player(0)));
become_invulnerable(g.get_player(0));
assert(is_invulnerable(g.get_player(0)));
g.get_player(0).set_state_passive();
assert(is_passive(g.get_player(0)));
}

/// A player can become non-passive again
{
game g;

assert(is_active(g.get_player(0)));
g.set_player_state_passive(g.get_player(0));
g.remove_player_state_passive(g.get_player(0));
assert(!is_passive(g.get_player(0)));
}

/// A player can become non-passive again, using player.remove_state_passive()
{
game g;

assert(is_active(g.get_player(0)));
g.get_player(0).set_state_passive();
g.get_player(0).remove_state_passive();
assert(!is_passive(g.get_player(0)));
}
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you changed the tests here. I see that you're being more exhaustive, systematically asserting that all setters exist. I don't see the necessity for this; the short test I had written was enough to reach the goal of the issue ("A player can become passive/invulnerable"). Being able to turn it back is not a necessity to reach this.
I believe (I'm not sure, can't read it from your commit history) what you are doing here is characteristic of unit-testing, that is writing tests, typically after the code, to ensure that the concerned bit of code keeps working as intended. This is ok, but we prefer test-driven development. The difference is that in TDD, we write the test before the code and even before thinking of the implementation. The test states our intended goal, and will pass when this goal is reached. Then we seek to implement a solution.

This is good work, your tests are functional and do satisfy the end goal, I just want to raise the point that I believe this is excessive, maybe premature testing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheoPannetier Thanks for pointing out this, it was that I feel like the tests I had written beforehand were not enough to detect all the changes in the future that might be done intentionally or unintentionally by me or by others, I am sometimes too obsessive that I force myself to add a lot of probably excessive or premature tests. 😢

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sometimes too obsessive

I definitely can't blame you for caring about the future of the game! I confirm I was too harsh with my initial comment, and I'd like to apologise for this.

These tests are indeed a bit premature at this stage, but they don't remove anything or add anything dangerous to the game AFAICS and may pave the way for the next steps with the passive state. Take the above as advice on how to better design tests, but what you're doing is an important step too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sometimes too obsessive

I definitely can't blame you for caring about the future of the game! I confirm I was too harsh with my initial comment, and I'd like to apologise for this.

These tests are indeed a bit premature at this stage, but they don't remove anything or add anything dangerous to the game AFAICS and may pave the way for the next steps with the passive state. Take the above as advice on how to better design tests, but what you're doing is an important step too.

This mechanism really involves a lot of worth-thinking issues, I've been a bit busy with my paper recently, I would find a time to rethink about it, and try to improve this PR

#ifdef FIX_ISSUE_382
///An invulnerable player cannot shrink
/// An passive player cannot grow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// An passive player cannot grow
/// A passive player cannot grow

assert(inv_player_size_after == inv_player_size_before);
}

/// An passive player cannot shrink
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// An passive player cannot shrink
/// A passive player cannot shrink

#ifdef FIX_ISSUE_463
// Players lose invulnerability after a short time
// Can get and set a player's passive state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Can get and set a player's passive state
// Can get and set a player's passive state duration

Comment on lines +728 to +738
{
game g;
player& p = g.get_player(0);

// Check default duration
assert(p.get_duration_passive() == 2000);

int new_duration = 200;
p.set_duration_passive(new_duration);
assert(p.get_duration_passive() == new_duration);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see why you would change the duration of the passive state at the player level; shouldn't it be a fixed parameter of the game?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheoPannetier I was thinking about possible future extension of the power-ups etc. For a minimal viable game, it should also be good to just let the duration be a fixed parameter of the game. What is your opinion about this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've figured out my opinion already 😉
I believe anticipating possible extensions beyond the minimal viable game is premature optimisation, and a wise man once told me it's the root of all evil.

So for now, I'd rather have the duration as a fixed parameter. It will be easy to make it free again when/if we need it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you've figured out my opinion already 😉 I believe anticipating possible extensions beyond the minimal viable game is premature optimisation, and a wise man once told me it's the root of all evil.

So for now, I'd rather have the duration as a fixed parameter. It will be easy to make it free again when/if we need it :)

I'll change it in the following commits

Comment on lines +740 to +756
// Game cannot increment a player's passive timer unless it is passive
{
game g;
player& p = g.get_player(0);
assert(!is_passive(p));
assert(p.get_passive_timer() == 0);

g.increment_passive_timers();
assert(p.get_passive_timer() == 0);

int timer_before = p.get_passive_timer();
p.set_state_passive();
g.increment_passive_timers();
int timer_after = p.get_passive_timer();
int difference = timer_after - timer_before;
assert(difference == 1);
}
Copy link
Contributor

@TheoPannetier TheoPannetier Jan 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above for test 381; I don't think this adds anything that is not already (implicitly) required by the test I had written (below).

Comment on lines +758 to +805
// A player's passive timer cannot be bigger than its passive duration,
// Otherwise it will be reset to 0
{
game g;
player& p = g.get_player(0);
g.set_player_state_passive(p);
int duration = p.get_duration_passive();
for (int i = 0; i < duration - 1; i++)
{
assert(p.get_state() == player_state::passive);
int timer_before = p.get_passive_timer();
g.tick();
int timer_after = p.get_passive_timer();
int difference = timer_after - timer_before;
assert(difference == 1);
assert(timer_after <= duration);
}
g.tick();
assert(!p.get_passive_timer());
int timer = p.get_passive_timer();
assert(timer == 0);
}

// Game cannot reset a player's passive timer if it's less than duration
{
game g;
player& p = g.get_player(0);

int timer = p.get_passive_timer();
assert(timer == 0);

p.set_state_passive();
int duration = p.get_duration_passive();

for (int i = 0; i < duration - 1; i++)
{
int timer_before = p.get_passive_timer();
g.tick();
int timer_after = p.get_passive_timer();
int difference = timer_after - timer_before;
assert(difference == 1);
assert(timer_after <= duration);
timer_before = p.get_passive_timer();
g.reset_passive_timers();
timer_after = p.get_passive_timer();
assert(timer_after == timer_before);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, I don't think it's necessary to explicitly test for the timer?
Unless you think the issue needed to be broken down in smaller steps - in which case, sure, go ahead and create smaller intermediate issues towards losing invulnerability after some time :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added these tests for debugging purposes as I encountered quite a lot of issues. I was making sure that all the parts I had added were working as I expected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main test (if it's good!) should already ensure that everything works as expected. If players lose invulnerability after some time and not earlier, as #463 demands, then we have our feature and we're happy!
I'd argue we only care about the underlying mechanisms and potential issues if a problem indeed manifests after completing the test. Then it's time to make a new issue to signal the problem, and write a test that ensures it is fixed.

That being said, using tests as debug towards completing another test is entirely fine, but then no need to leave it in the code once you have resolved the final test!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main test (if it's good!) should already ensure that everything works as expected. If players lose invulnerability after some time and not earlier, as #463 demands, then we have our feature and we're happy! I'd argue we only care about the underlying mechanisms and potential issues if a problem indeed manifests after completing the test. Then it's time to make a new issue to signal the problem, and write a test that ensures it is fixed.

That being said, using tests as debug towards completing another test is entirely fine, but then no need to leave it in the code once you have resolved the final test!

Yes that's true, I mean I would add extra debugging tests besides the main tests and remove them right before merging, this could be a better way than what was done in this PR 😉

src/game.cpp Outdated
winning_player.grow();
const int loser_index = get_losing_player_index(g, first_player_index, second_player_index);
player& losing_player = g.get_player(loser_index);
if (!is_passive(winning_player) && !is_passive(losing_player))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think passive states shouldn't be caught here; instead if either player is passive, there is no collision (and thus, growing/shrinking won't be called)

@@ -143,6 +143,12 @@ void game::tick()
shrink_losing_player(*this);
}

/// PASSIVE STATE ///
increment_passive_timers();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with the refactoring of invulnerable into passive. I think the former conveys more explicit information than the latter. passive is too general, could refer to the player being inactive for any reason, e.g. having been killed, stunned, or recovering from a defeat, and suggest the player cannot interact with anything, including foods etc. invulnerable simply means the player cannot be attacked, and lose a fight

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we do need to have some sound definitions of these states. For the passive state, I was referring to a game state that is the same as in GTA5, which means a player is alive but cannot be killed by the others players, and of course, a passive player cannot hurt the other players. A passive player can collect things and do jobs.

Considering foods or power-ups, it is worthwhile to think thoroughly. Maybe we could find a more proper name for this state I was referring to? As for invulnerable, I still feel like it could be a state after collecting a power-up or triggering some game mechanics.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I agree with the reasoning, although again I prefer invulnerable over passive to refer to what you describe: passive implies you cannot do anything other than move, so no item or task collection. On the other hand, invulnerable does suggest you could still attack other players, which is not true (or is it?).
I'll let you make the final call :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, I agree with the reasoning, although again I prefer invulnerable over passive to refer to what you describe: passive implies you cannot do anything other than move, so no item or task collection. On the other hand, invulnerable does suggest you could still attack other players, which is not true (or is it?). I'll let you make the final call :)

I think what is important is to have two types of invulnerable (not receiving damage) states, one you can still deal damage while the other one you cannot.

To be more specific:

  • Invulnerable State A: If a player receives damage, it shrinks and becomes State A, in this state it won't receive more damage and won't deal damage or won't collide, it is like passive we've discussed.
  • Invulnerable State B: If a player collects a power-up or something, it just won't receive any damage, but it still deals damage to the others.

Is there any possibility we find two words to well describe them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invulnerable State B: If a player collects a power-up or something, it just won't receive any damage, but it still deals damage to the others

We don't have power-ups are plans to add any before 1.0, so let's not worry about it for now ;)
We can always make changes later if the current implementation becomes a barrier to new desired features.
Let's keep it to what's referred to in the title of this PR.
So for this "invulnerable state A", I guess either passive or invulnerable are ok names; neither is entirely satisfying but they do an ok job.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheoPannetier That's true, I will refactor passive back to invulnerable for now

Copy link
Contributor

@TheoPannetier TheoPannetier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been a bit harsh and grumpy in my comments, but overall this is good work and this contribution definitely helps the game move forward :)
So there is only one point where I request a change before merging: collision should not happen if either member is passive, and in turn there is no need to check for state in grow and shrink.
I disagree with the name passive instead of invulnerable, but the last word is yours for this 👍

@EvoLandEco
Copy link
Contributor Author

EvoLandEco commented Feb 15, 2023

@TheoPannetier An open discussion is always helpful, thanks for the opinions, and let's keep talking and find the best solution 😉

@TheoPannetier
Copy link
Contributor

Hello @EvoLandEco !
I think the discussion has been a bit dense and dispersed, so to make your life easier here are the two things I'd like to see addressed before I accept this PR:

  1. When resolving collision and player growth/shrinkage, you check that players involved in the collision are both active before resolving the effects of the collision. This is good, but you do the check inside the growing/shrinking functions. I feel that if either player is not active, there should not be any collision at all (and no need to call the growth/shrinkage functions). So the check for player active/passive state should be done earlier, and has_any_player_collision should return FALSE if either player is passive.
  2. Make passive duration a fixed parameter of either the player or game, and remove the corresponding setter.

(and as usual, resolve any pending merge conflict!)

@EvoLandEco
Copy link
Contributor Author

Hello @EvoLandEco ! I think the discussion has been a bit dense and dispersed, so to make your life easier here are the two things I'd like to see addressed before I accept this PR:

  1. When resolving collision and player growth/shrinkage, you check that players involved in the collision are both active before resolving the effects of the collision. This is good, but you do the check inside the growing/shrinking functions. I feel that if either player is not active, there should not be any collision at all (and no need to call the growth/shrinkage functions). So the check for player active/passive state should be done earlier, and has_any_player_collision should return FALSE if either player is passive.
  2. Make passive duration a fixed parameter of either the player or game, and remove the corresponding setter.

(and as usual, resolve any pending merge conflict!)

Let's focus on what this PR is meant to be. 😉 I remember I did status check in a later stage due to a limitation from how collision check was implemented, but I will surely revisit this part and see if I can do any better.

For the passive duration, I agree to make it align with what we did in the shoot cooldown system: maybe make it a static variable.

@EvoLandEco
Copy link
Contributor Author

Hello @EvoLandEco ! I think the discussion has been a bit dense and dispersed, so to make your life easier here are the two things I'd like to see addressed before I accept this PR:

  1. When resolving collision and player growth/shrinkage, you check that players involved in the collision are both active before resolving the effects of the collision. This is good, but you do the check inside the growing/shrinking functions. I feel that if either player is not active, there should not be any collision at all (and no need to call the growth/shrinkage functions). So the check for player active/passive state should be done earlier, and has_any_player_collision should return FALSE if either player is passive.
  2. Make passive duration a fixed parameter of either the player or game, and remove the corresponding setter.

(and as usual, resolve any pending merge conflict!)

@TheoPannetier We have the game_options class here, what about putting passive duration in this class?

@TheoPannetier
Copy link
Contributor

@TheoPannetier We have the game_options class here, what about putting passive duration in this class?

Indeed, it looks appropriate :)
(sorry, only seeing this now)

@TheoPannetier
Copy link
Contributor

Merged to not lose progress

@TheoPannetier TheoPannetier reopened this May 10, 2023
@TheoPannetier TheoPannetier merged commit 55b0d45 into develop May 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expand player_state, add a passive state [Game Play] Too much 'grow' and 'shrink'
3 participants