Fixes #12, fixes #29.
With the help of God mode #55 , I was able to let the game crash a couple of times for debugging purposes.
While examining the stack traces of multiple panics I found the following:
The function that crashes is gen_range, however on some occasions it was called by create_enemy
while on other occasions it was called by create_fuel.
Both create_enemy and create_fuel call rng.gen_range(world.map[0].0..world.map[0].1).
In our case this range sometimes is empty which means that the program eventually comes to a state where the left border of the river is greater than (at the right side of) the right border.
Possible scenario of reaching such an unlucky state:
Assume that next_left border is at position 13 and next_right border is at position 16 and after
world.next_left = rng.gen_range(world.next_left.saturating_sub(5)..world.next_left + 5);world.next_right = rng.gen_range(world.next_right - 5..world.next_right + 5);
next_left is at position 17 (original 13 plus 4) and next_right is at position 13 (original 16 minus 3).
Now, world.next_right.abs_diff(world.next_left) will evaluate to 4 so the following block
if world.next_right.abs_diff(world.next_left) < 3 { world.next_right += 3; }
won't be executed.
At the next iterations left and right will be adjusted accordingly. So if we are unlucky enough, that state where left is at the right side of right can arise and if create_fuel or create_enemy call rng.gen_range(world.map[0].0..world.map[0].1) at this specific time, the program will panic.
How did I approach this:
after the next_right and next_left have been calculated, if next_left is beyond next_right:
if world.next_left >= world.next_right
then I need to untangle them. However, those cases can arise when we are close to the borders of the screen and thus we must first check whether we have enough space to bring next_right at the right side of next_left:
if world.next_left <= world.maxc - 4 (we already know at this point that next_left is greater than next_right)
then move next_right at the right side of next_left (world.next_right = world.next_left + 3)
else (if there is not enough space to bring next_right over to the right side of next_left while also having a 3 character gap between them)
move the next_left at the left side of the next_right (world.next_left = world.next_right - 3;)
We know for sure that there is enough space to expand to the left side since there was not enough space to expand at the right side. I did not account for the scenario where there is not enough space on both sides, i.e. very very small terminal width.
Now that they are untangled, the last piece of code:
if world.next_right.abs_diff(world.next_left) < 3 { world.next_right = world.next_left + 3; }
ensures the 3 character distance between them.
This last bit is important because even though we create the gap manually in the previous block, that block might not be executed (case of if world.next_left >= world.next_right == false), i.e. they were not tangled, just too close.
This way we know that next_left is at the left side of next_right while also ensuring they have, at least, a 3 character gap.
Fixes #12, fixes #29. With the help of God mode #55 , I was able to let the game crash a couple of times for debugging purposes.
While examining the stack traces of multiple panics I found the following: The function that crashes is
gen_range
, however on some occasions it was called bycreate_enemy
while on other occasions it was called bycreate_fuel
.Both
create_enemy
andcreate_fuel
callrng.gen_range(world.map[0].0..world.map[0].1)
. In our case this range sometimes is empty which means that the program eventually comes to a state where the left border of the river is greater than (at the right side of) the right border.Possible scenario of reaching such an unlucky state:
Assume that
next_left
border is at position 13 andnext_right
border is at position 16 and afterworld.next_left = rng.gen_range(world.next_left.saturating_sub(5)..world.next_left + 5);
world.next_right = rng.gen_range(world.next_right - 5..world.next_right + 5);
next_left
is at position 17 (original 13 plus 4) andnext_right
is at position 13 (original 16 minus 3).Now,
world.next_right.abs_diff(world.next_left)
will evaluate to 4 so the following blockif world.next_right.abs_diff(world.next_left) < 3 { world.next_right += 3; }
won't be executed. At the next iterationsleft
andright
will be adjusted accordingly. So if we are unlucky enough, that state whereleft
is at the right side ofright
can arise and ifcreate_fuel
orcreate_enemy
callrng.gen_range(world.map[0].0..world.map[0].1)
at this specific time, the program will panic.How did I approach this:
after the
next_right
andnext_left
have been calculated, ifnext_left
is beyondnext_right
:if world.next_left >= world.next_right
then I need to untangle them. However, those cases can arise when we are close to the borders of the screen and thus we must first check whether we have enough space to bringnext_right
at the right side ofnext_left
:if world.next_left <= world.maxc - 4
(we already know at this point thatnext_left
is greater thannext_right
) then movenext_right
at the right side ofnext_left
(world.next_right = world.next_left + 3
)else
(if there is not enough space to bringnext_right
over to the right side ofnext_left
while also having a 3 character gap between them) move thenext_left
at the left side of thenext_right
(world.next_left = world.next_right - 3;
) We know for sure that there is enough space to expand to the left side since there was not enough space to expand at the right side. I did not account for the scenario where there is not enough space on both sides, i.e. very very small terminal width.Now that they are untangled, the last piece of code:
if world.next_right.abs_diff(world.next_left) < 3 { world.next_right = world.next_left + 3; }
ensures the 3 character distance between them. This last bit is important because even though we create the gap manually in the previous block, that block might not be executed (case ofif world.next_left >= world.next_right
== false), i.e. they were not tangled, just too close.This way we know that
next_left
is at the left side ofnext_right
while also ensuring they have, at least, a 3 character gap.