Skip to content

Commit

Permalink
Merge pull request cataclysmbnteam#435 from cataclysmbnteam/throwing-…
Browse files Browse the repository at this point in the history
…test-fix

Fix throwing tests being too fragile
  • Loading branch information
Coolthulhu authored Apr 3, 2021
2 parents 7118486 + de3a862 commit 8f5929f
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 7 deletions.
23 changes: 23 additions & 0 deletions src/ballistics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "avatar.h"
#include "calendar.h"
#include "cata_utility.h" // for normal_cdf
#include "creature.h"
#include "damage.h"
#include "debug.h"
Expand Down Expand Up @@ -455,3 +456,25 @@ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tri

return attack;
}

namespace ranged
{

double hit_chance( const dispersion_sources &dispersion, double range, double target_size,
double missed_by )
{
if( range <= 0 ) {
return 1.0;
}

double missed_by_tiles = missed_by * target_size;

// T = (2*D**2 * (1 - cos V)) ** 0.5 (from iso_tangent)
// cos V = 1 - T**2 / (2*D**2)
double cosV = 1 - missed_by_tiles * missed_by_tiles / ( 2 * range * range );
double needed_dispersion = ( cosV < -1.0 ? M_PI : acos( cosV ) ) * 180 * 60 / M_PI;

return normal_cdf( needed_dispersion, dispersion.avg(), dispersion.avg() / 2 );
}

}
16 changes: 16 additions & 0 deletions src/ballistics.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,20 @@ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tri
const tripoint &target_arg, const dispersion_sources &dispersion,
Creature *origin = nullptr, const vehicle *in_veh = nullptr );

namespace ranged
{

/**
* The chance that a fired shot reaches required accuracy - by default grazing shot.
*
* @param dispersion accuracy of the shot. Must be a purely normal distribution.
* @param range distance between the shooter and the target.
* @param target_size size of the target, in the range (0, 1].
* @param missed_by maximum degree of miss, in the range (0, 1]. Effectively a multiplier on @param target_size.
*/
double hit_chance( const dispersion_sources &dispersion, double range, double target_size,
double missed_by = 1.0 );

} // namespace ranged

#endif // CATA_SRC_BALLISTICS_H
5 changes: 5 additions & 0 deletions src/cata_utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ double logarithmic_range( int min, int max, int pos )
return ( raw_logistic - LOGI_MIN ) / LOGI_RANGE;
}

double normal_cdf( double x, double mean, double stddev )
{
return 0.5 * ( 1.0 + std::erf( ( x - mean ) / ( stddev * M_SQRT2 ) ) );
}

int bound_mod_to_vals( int val, int mod, int max, int min )
{
if( val + mod > max && max != 0 ) {
Expand Down
11 changes: 11 additions & 0 deletions src/cata_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ double logarithmic( double t );
*/
double logarithmic_range( int min, int max, int pos );

/**
* Cumulative distribution function of a certain normal distribution.
*
* @param x point at which the CDF of the distribution is measured
* @param mean mean of the normal distribution
* @param stddev standard deviation of the normal distribution
*
* @return The probability that a random point from the distribution will be lesser than @param x
*/
double normal_cdf( double x, double mean, double stddev );

/**
* Clamp the value of a modifier in order to bound the resulting value
*
Expand Down
29 changes: 22 additions & 7 deletions tests/throwing_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
#include <vector>

#include "avatar.h"
#include "ballistics.h"
#include "calendar.h"
#include "catch/catch.hpp"
#include "damage.h"
#include "dispersion.h"
#include "game.h"
#include "game_constants.h"
#include "inventory.h"
Expand Down Expand Up @@ -105,6 +107,19 @@ static void test_throwing_player_versus(
monster &mon = spawn_test_monster( mon_id, monster_start );
mon.set_moves( 0 );

double actual_hit_chance = ranged::hit_chance(
dispersion_sources( p.throwing_dispersion( it, &mon ) ),
range, mon.ranged_target_size() );
if( std::fabs( actual_hit_chance - hit_thresh.midpoint ) > hit_thresh.epsilon / 2.0 ) {
CAPTURE( hit_thresh.midpoint );
CAPTURE( hit_thresh.epsilon / 2.0 );
CAPTURE( actual_hit_chance );
CAPTURE( range );
CAPTURE( mon.ranged_target_size() );
FAIL_CHECK( "Expected and calculated midpoints must be within epsilon/2 or the test is too fragile" );
return;
}

auto atk = p.throw_item( mon.pos(), it );
data.hits.add( atk.hit_critter != nullptr );
data.dmg.add( atk.dealt_dam.total_damage() );
Expand Down Expand Up @@ -175,16 +190,16 @@ TEST_CASE( "basic_throwing_sanity_tests", "[throwing],[balance]" )
test_throwing_player_versus( p, "mon_zombie", "rock", 5, lo_skill_base_stats, { 0.77, 0.10 }, { 5.5, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 10, lo_skill_base_stats, { 0.27, 0.10 }, { 2, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 15, lo_skill_base_stats, { 0.13, 0.10 }, { 1, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 20, lo_skill_base_stats, { 0.03, 0.10 }, { 0.5, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 20, lo_skill_base_stats, { 0.095, 0.10 }, { 0.5, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 25, lo_skill_base_stats, { 0.03, 0.10 }, { 0.5, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 30, lo_skill_base_stats, { 0.03, 0.10 }, { 0.5, 2 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 30, lo_skill_base_stats, { 0.06, 0.10 }, { 0.5, 2 } );
}

SECTION( "test_player_vs_zombie_javelin_iron_basestats" ) {
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 1, lo_skill_base_stats, { 0.95, 0.10 }, { 28, 5 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 1, lo_skill_base_stats, { 1.00, 0.10 }, { 28, 5 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 5, lo_skill_base_stats, { 0.64, 0.10 }, { 13, 3 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 10, lo_skill_base_stats, { 0.20, 0.10 }, { 4, 2 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 15, lo_skill_base_stats, { 0.03, 0.10 }, { 1.29, 3 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 15, lo_skill_base_stats, { 0.11, 0.10 }, { 1.29, 3 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 20, lo_skill_base_stats, { 0.03, 0.10 }, { 1.66, 2 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 25, lo_skill_base_stats, { 0.03, 0.10 }, { 1.0, 2 } );
}
Expand All @@ -195,7 +210,7 @@ TEST_CASE( "basic_throwing_sanity_tests", "[throwing],[balance]" )
test_throwing_player_versus( p, "mon_zombie", "rock", 10, hi_skill_athlete_stats, { 1.00, 0.10 }, { 16.27, 6 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 15, hi_skill_athlete_stats, { 0.97, 0.10 }, { 12.83, 4 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 20, hi_skill_athlete_stats, { 0.82, 0.10 }, { 9.10, 4 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 25, hi_skill_athlete_stats, { 0.64, 0.10 }, { 6.54, 4 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 25, hi_skill_athlete_stats, { 0.58, 0.10 }, { 6.54, 4 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 30, hi_skill_athlete_stats, { 0.47, 0.10 }, { 4.90, 3 } );
}

Expand All @@ -205,7 +220,7 @@ TEST_CASE( "basic_throwing_sanity_tests", "[throwing],[balance]" )
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 10, hi_skill_athlete_stats, { 1.00, 0.10 }, { 34.16, 8 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 15, hi_skill_athlete_stats, { 0.97, 0.10 }, { 25.21, 6 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 20, hi_skill_athlete_stats, { 0.82, 0.10 }, { 18.90, 5 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 25, hi_skill_athlete_stats, { 0.63, 0.10 }, { 13.59, 5 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 25, hi_skill_athlete_stats, { 0.58, 0.10 }, { 13.59, 5 } );
test_throwing_player_versus( p, "mon_zombie", "javelin_iron", 30, hi_skill_athlete_stats, { 0.48, 0.10 }, { 10.00, 4 } );
}
}
Expand All @@ -220,7 +235,7 @@ TEST_CASE( "throwing_skill_impact_test", "[throwing],[balance]" )
// the throwing skill has while the sanity tests are more explicit.
SECTION( "mid_skill_basestats_rock" ) {
test_throwing_player_versus( p, "mon_zombie", "rock", 5, mid_skill_base_stats, { 1.00, 0.10 }, { 12, 6 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 10, mid_skill_base_stats, { 0.86, 0.10 }, { 7, 4 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 10, mid_skill_base_stats, { 0.92, 0.10 }, { 7, 4 } );
test_throwing_player_versus( p, "mon_zombie", "rock", 15, mid_skill_base_stats, { 0.62, 0.10 }, { 5, 2 } );
}

Expand Down

0 comments on commit 8f5929f

Please sign in to comment.