jadijadi / riverraidrust

A text based river raid clone in Rust; streamed
GNU General Public License v3.0
94 stars 28 forks source link

fixes panics #59

Open panoschagias opened 7 months ago

panoschagias commented 7 months ago

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.

panoschagias commented 7 months ago

@jadijadi conflicts are resolved.

panoschagias commented 6 months ago

@jadijadi conflicts resolved.