From 4c7faafa7e534b4328d450f38bd2bd6dc3df26ff Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 4 Apr 2020 12:52:02 -0600 Subject: [PATCH 1/8] Add get_bmi and get_weight_string tests --- tests/char_healthy_test.cpp | 133 ++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/char_healthy_test.cpp diff --git a/tests/char_healthy_test.cpp b/tests/char_healthy_test.cpp new file mode 100644 index 0000000000000..883884f3ef261 --- /dev/null +++ b/tests/char_healthy_test.cpp @@ -0,0 +1,133 @@ +#include "avatar.h" + +#include "catch/catch.hpp" +#include "player_helpers.h" + +TEST_CASE( "stored kcals and body mass index", "[healthy][kcal][bmi]" ) +{ + avatar dummy; + + GIVEN( "character with normal calorie requirements" ) { + + // Assuming healthy_calories is set to 55000 in Character constructor + REQUIRE( dummy.get_healthy_kcal() == 55000 ); + + WHEN( "stored calories are exactly equal to healthy value" ) { + dummy.set_stored_kcal( dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 1.0f ); + + // 1.0*12 +13 = 25 + THEN( "BMI = 25, on the cusp of 'Normal' to 'Overweight'" ) { + CHECK( dummy.get_bmi() == Approx( 25.0f ) ); + CHECK( dummy.get_bmi() == Approx( character_weight_category::overweight ) ); + CHECK( dummy.get_weight_string() == "Normal" ); + } + } + + WHEN( "stored calories are 3/4 of healthy value" ) { + dummy.set_stored_kcal( 0.75f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 0.75f ); + + // 0.75*12 +13 = 22 + THEN( "BMI = 22, 'Normal'" ) { + CHECK( dummy.get_bmi() == Approx( 22.0f ) ); + CHECK( dummy.get_bmi() < character_weight_category::overweight ); + CHECK( dummy.get_bmi() > character_weight_category::normal ); + CHECK( dummy.get_weight_string() == "Normal" ); + } + } + + WHEN( "stored calories are 1/2 of healthy value" ) { + dummy.set_stored_kcal( 0.5f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 0.5f ); + + // 0.5*12 +13 = 19 + THEN( "BMI = 19, 'Normal'" ) { + CHECK( dummy.get_bmi() == Approx( 19.0f ) ); + CHECK( dummy.get_bmi() < character_weight_category::overweight ); + CHECK( dummy.get_bmi() > character_weight_category::normal ); + CHECK( dummy.get_weight_string() == "Normal" ); + } + } + + WHEN( "stored calories are 1/3 of healthy value" ) { + dummy.set_stored_kcal( 1.0f / 3 * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == Approx( 1.0f / 3 ).margin( 0.001f ) ); + + // 0.33*12 +13 = 17 + THEN( "BMI = 17, 'Underweight'" ) { + CHECK( dummy.get_bmi() == Approx( 17.0f ).margin( 0.001f ) ); + CHECK( dummy.get_bmi() < character_weight_category::normal ); + CHECK( dummy.get_bmi() > character_weight_category::underweight ); + CHECK( dummy.get_weight_string() == "Underweight" ); + } + } + + WHEN( "stored calories are 1/4 of healthy value" ) { + dummy.set_stored_kcal( 0.25f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 0.25f ); + + // 0.25*12 +13 = 16 + THEN( "BMI = 16, on the cusp of 'Underweight' to 'Emaciated'" ) { + CHECK( dummy.get_bmi() == Approx( 16.0f ) ); + CHECK( dummy.get_bmi() == Approx( character_weight_category::underweight ) ); + CHECK( dummy.get_weight_string() == "Emaciated" ); + } + } + + WHEN( "stored calories are 1.25x healthy value" ) { + dummy.set_stored_kcal( 1.25f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 1.25f ); + + // 1.25*12 +13 = 28 + THEN( "BMI = 28, 'Overweight'" ) { + CHECK( dummy.get_bmi() == Approx( 28.0f ) ); + CHECK( dummy.get_bmi() > character_weight_category::overweight ); + CHECK( dummy.get_bmi() < character_weight_category::obese ); + CHECK( dummy.get_weight_string() == "Overweight" ); + } + } + WHEN( "stored calories are 1.5x healthy value" ) { + dummy.set_stored_kcal( 1.5f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 1.5f ); + + // 1.5*12 +13 = 31 + THEN( "BMI = 31, 'Obese'" ) { + CHECK( dummy.get_bmi() == Approx( 31.0f ) ); + CHECK( dummy.get_bmi() > character_weight_category::obese ); + CHECK( dummy.get_bmi() < character_weight_category::very_obese ); + CHECK( dummy.get_weight_string() == "Obese" ); + } + } + + WHEN( "stored calories are 2x healthy value" ) { + dummy.set_stored_kcal( 2.0f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 2.0f ); + + // 2*12 +13 = 37 + THEN( "BMI = 37, 'Very Obese'" ) { + CHECK( dummy.get_bmi() == Approx( 37.0f ) ); + CHECK( dummy.get_bmi() > character_weight_category::very_obese ); + CHECK( dummy.get_bmi() < character_weight_category::morbidly_obese ); + CHECK( dummy.get_weight_string() == "Very Obese" ); + } + } + + WHEN( "stored calories are 2.5x healthy value" ) { + dummy.set_stored_kcal( 2.5f * dummy.get_healthy_kcal() ); + REQUIRE( dummy.get_kcal_percent() == 2.5f ); + + // 2.5*12 +13 = 43 + THEN( "BMI = 43, 'Morbidly Obese'" ) { + CHECK( dummy.get_bmi() == Approx( 43.0f ) ); + CHECK( dummy.get_bmi() > character_weight_category::morbidly_obese ); + CHECK( dummy.get_weight_string() == "Morbidly Obese" ); + } + } + } + +} + +TEST_CASE( "maximum healthiness", "[healthy][max]" ) +{ +} From 2184635818a0a157c9cb5a9aaf8f4600efe7677a Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 4 Apr 2020 16:18:32 -0600 Subject: [PATCH 2/8] Refactor and add kcal-to-BMI-to-healthy tests --- tests/char_healthy_test.cpp | 258 +++++++++++++++++++----------------- 1 file changed, 137 insertions(+), 121 deletions(-) diff --git a/tests/char_healthy_test.cpp b/tests/char_healthy_test.cpp index 883884f3ef261..4b985565424da 100644 --- a/tests/char_healthy_test.cpp +++ b/tests/char_healthy_test.cpp @@ -1,133 +1,149 @@ #include "avatar.h" #include "catch/catch.hpp" -#include "player_helpers.h" -TEST_CASE( "stored kcals and body mass index", "[healthy][kcal][bmi]" ) +// Return the player's `get_bmi` at `kcal_percent` (actually a ratio of stored_kcal to healthy_kcal) +static float bmi_at_kcal_ratio( player &dummy, float kcal_percent ) +{ + dummy.set_stored_kcal( dummy.get_healthy_kcal() * kcal_percent ); + REQUIRE( dummy.get_kcal_percent() == Approx( kcal_percent ).margin( 0.001f ) ); + + return dummy.get_bmi(); +} + +// Return the `kcal_ratio` needed to reach the given `body_mass_index` +// BMI = 13 + (12 * kcal_ratio) +// kcal_ratio = (BMI - 13) / 12 +static float bmi_to_kcal_ratio( float body_mass_index ) +{ + return ( body_mass_index - 13 ) / 12; +} + +// Return the player's `get_max_healthy` value at the given body mass index +static int max_healthy_at_bmi( player &dummy, float bmi ) +{ + dummy.set_stored_kcal( dummy.get_healthy_kcal() * bmi_to_kcal_ratio( bmi ) ); + REQUIRE( dummy.get_bmi() == Approx( bmi ).margin( 0.001f ) ); + + return dummy.get_max_healthy(); +} + +// Return the player's `get_weight_string` at the given body mass index +static std::string weight_string_at_bmi( player &dummy, float bmi ) +{ + dummy.set_stored_kcal( dummy.get_healthy_kcal() * bmi_to_kcal_ratio( bmi ) ); + REQUIRE( dummy.get_bmi() == Approx( bmi ).margin( 0.001f ) ); + + return dummy.get_weight_string(); +} + + +TEST_CASE( "body mass index determines weight description", "[healthy][bmi][weight]" ) { avatar dummy; - GIVEN( "character with normal calorie requirements" ) { - - // Assuming healthy_calories is set to 55000 in Character constructor - REQUIRE( dummy.get_healthy_kcal() == 55000 ); - - WHEN( "stored calories are exactly equal to healthy value" ) { - dummy.set_stored_kcal( dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 1.0f ); - - // 1.0*12 +13 = 25 - THEN( "BMI = 25, on the cusp of 'Normal' to 'Overweight'" ) { - CHECK( dummy.get_bmi() == Approx( 25.0f ) ); - CHECK( dummy.get_bmi() == Approx( character_weight_category::overweight ) ); - CHECK( dummy.get_weight_string() == "Normal" ); - } - } - - WHEN( "stored calories are 3/4 of healthy value" ) { - dummy.set_stored_kcal( 0.75f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 0.75f ); - - // 0.75*12 +13 = 22 - THEN( "BMI = 22, 'Normal'" ) { - CHECK( dummy.get_bmi() == Approx( 22.0f ) ); - CHECK( dummy.get_bmi() < character_weight_category::overweight ); - CHECK( dummy.get_bmi() > character_weight_category::normal ); - CHECK( dummy.get_weight_string() == "Normal" ); - } - } - - WHEN( "stored calories are 1/2 of healthy value" ) { - dummy.set_stored_kcal( 0.5f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 0.5f ); - - // 0.5*12 +13 = 19 - THEN( "BMI = 19, 'Normal'" ) { - CHECK( dummy.get_bmi() == Approx( 19.0f ) ); - CHECK( dummy.get_bmi() < character_weight_category::overweight ); - CHECK( dummy.get_bmi() > character_weight_category::normal ); - CHECK( dummy.get_weight_string() == "Normal" ); - } - } - - WHEN( "stored calories are 1/3 of healthy value" ) { - dummy.set_stored_kcal( 1.0f / 3 * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == Approx( 1.0f / 3 ).margin( 0.001f ) ); - - // 0.33*12 +13 = 17 - THEN( "BMI = 17, 'Underweight'" ) { - CHECK( dummy.get_bmi() == Approx( 17.0f ).margin( 0.001f ) ); - CHECK( dummy.get_bmi() < character_weight_category::normal ); - CHECK( dummy.get_bmi() > character_weight_category::underweight ); - CHECK( dummy.get_weight_string() == "Underweight" ); - } - } - - WHEN( "stored calories are 1/4 of healthy value" ) { - dummy.set_stored_kcal( 0.25f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 0.25f ); - - // 0.25*12 +13 = 16 - THEN( "BMI = 16, on the cusp of 'Underweight' to 'Emaciated'" ) { - CHECK( dummy.get_bmi() == Approx( 16.0f ) ); - CHECK( dummy.get_bmi() == Approx( character_weight_category::underweight ) ); - CHECK( dummy.get_weight_string() == "Emaciated" ); - } - } - - WHEN( "stored calories are 1.25x healthy value" ) { - dummy.set_stored_kcal( 1.25f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 1.25f ); - - // 1.25*12 +13 = 28 - THEN( "BMI = 28, 'Overweight'" ) { - CHECK( dummy.get_bmi() == Approx( 28.0f ) ); - CHECK( dummy.get_bmi() > character_weight_category::overweight ); - CHECK( dummy.get_bmi() < character_weight_category::obese ); - CHECK( dummy.get_weight_string() == "Overweight" ); - } - } - WHEN( "stored calories are 1.5x healthy value" ) { - dummy.set_stored_kcal( 1.5f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 1.5f ); - - // 1.5*12 +13 = 31 - THEN( "BMI = 31, 'Obese'" ) { - CHECK( dummy.get_bmi() == Approx( 31.0f ) ); - CHECK( dummy.get_bmi() > character_weight_category::obese ); - CHECK( dummy.get_bmi() < character_weight_category::very_obese ); - CHECK( dummy.get_weight_string() == "Obese" ); - } - } - - WHEN( "stored calories are 2x healthy value" ) { - dummy.set_stored_kcal( 2.0f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 2.0f ); - - // 2*12 +13 = 37 - THEN( "BMI = 37, 'Very Obese'" ) { - CHECK( dummy.get_bmi() == Approx( 37.0f ) ); - CHECK( dummy.get_bmi() > character_weight_category::very_obese ); - CHECK( dummy.get_bmi() < character_weight_category::morbidly_obese ); - CHECK( dummy.get_weight_string() == "Very Obese" ); - } - } - - WHEN( "stored calories are 2.5x healthy value" ) { - dummy.set_stored_kcal( 2.5f * dummy.get_healthy_kcal() ); - REQUIRE( dummy.get_kcal_percent() == 2.5f ); - - // 2.5*12 +13 = 43 - THEN( "BMI = 43, 'Morbidly Obese'" ) { - CHECK( dummy.get_bmi() == Approx( 43.0f ) ); - CHECK( dummy.get_bmi() > character_weight_category::morbidly_obese ); - CHECK( dummy.get_weight_string() == "Morbidly Obese" ); - } - } - } + CHECK( weight_string_at_bmi( dummy, 13.0f ) == "Skeletal" ); + CHECK( weight_string_at_bmi( dummy, 13.9f ) == "Skeletal" ); + CHECK( weight_string_at_bmi( dummy, 14.1f ) == "Emaciated" ); + CHECK( weight_string_at_bmi( dummy, 15.9f ) == "Emaciated" ); + CHECK( weight_string_at_bmi( dummy, 16.1f ) == "Underweight" ); + CHECK( weight_string_at_bmi( dummy, 18.4f ) == "Underweight" ); + CHECK( weight_string_at_bmi( dummy, 18.6f ) == "Normal" ); + CHECK( weight_string_at_bmi( dummy, 24.9f ) == "Normal" ); + CHECK( weight_string_at_bmi( dummy, 25.1f ) == "Overweight" ); + CHECK( weight_string_at_bmi( dummy, 29.9f ) == "Overweight" ); + CHECK( weight_string_at_bmi( dummy, 30.1f ) == "Obese" ); + CHECK( weight_string_at_bmi( dummy, 34.9f ) == "Obese" ); + CHECK( weight_string_at_bmi( dummy, 35.1f ) == "Very Obese" ); + CHECK( weight_string_at_bmi( dummy, 39.9f ) == "Very Obese" ); + CHECK( weight_string_at_bmi( dummy, 40.1f ) == "Morbidly Obese" ); + CHECK( weight_string_at_bmi( dummy, 41.0f ) == "Morbidly Obese" ); +} +TEST_CASE( "stored kcal ratio determines body mass index", "[healthy][kcal][bmi]" ) +{ + avatar dummy; + + // Skeletal (<14) + CHECK( bmi_at_kcal_ratio( dummy, 0.01f ) == Approx( 13.12f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.05f ) == Approx( 13.6f ).margin( 0.01f ) ); + // Emaciated (14) + CHECK( bmi_at_kcal_ratio( dummy, 0.1f ) == Approx( 14.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.2f ) == Approx( 15.4f ).margin( 0.01f ) ); + // Underweight (16) + CHECK( bmi_at_kcal_ratio( dummy, 0.3f ) == Approx( 16.6f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.4f ) == Approx( 17.8f ).margin( 0.01f ) ); + // Normal (18.5) + CHECK( bmi_at_kcal_ratio( dummy, 0.5f ) == Approx( 19.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.6f ) == Approx( 20.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.7f ) == Approx( 21.4f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.8f ) == Approx( 22.6f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.9f ) == Approx( 23.8f ).margin( 0.01f ) ); + // Overweight (25) + CHECK( bmi_at_kcal_ratio( dummy, 1.0f ) == Approx( 25.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.1f ) == Approx( 26.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.2f ) == Approx( 27.4f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.3f ) == Approx( 28.6f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.4f ) == Approx( 29.8f ).margin( 0.01f ) ); + // Obese (30) + CHECK( bmi_at_kcal_ratio( dummy, 1.5f ) == Approx( 31.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.6f ) == Approx( 32.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.7f ) == Approx( 33.4f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.8f ) == Approx( 34.6f ).margin( 0.01f ) ); + // Very obese (35) + CHECK( bmi_at_kcal_ratio( dummy, 1.9f ) == Approx( 35.8f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 2.0f ) == Approx( 37.0f ).margin( 0.01f ) ); + // Morbidly obese (40) + CHECK( bmi_at_kcal_ratio( dummy, 2.25f ) == Approx( 40.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 2.5f ) == Approx( 43.0f ).margin( 0.01f ) ); } -TEST_CASE( "maximum healthiness", "[healthy][max]" ) +TEST_CASE( "body mass effect on maximum healthiness", "[healthy][bmi][max]" ) { + avatar dummy; + + // Skeletal (<14) + CHECK( max_healthy_at_bmi( dummy, 8.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 9.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 10.0f ) == -183 ); + CHECK( max_healthy_at_bmi( dummy, 11.0f ) == -115 ); + CHECK( max_healthy_at_bmi( dummy, 12.0f ) == -53 ); + CHECK( max_healthy_at_bmi( dummy, 13.0f ) == 2 ); + // Emaciated (14) + CHECK( max_healthy_at_bmi( dummy, 14.0f ) == 51 ); + CHECK( max_healthy_at_bmi( dummy, 15.0f ) == 95 ); + // Underweight (16) + CHECK( max_healthy_at_bmi( dummy, 16.0f ) == 133 ); + CHECK( max_healthy_at_bmi( dummy, 17.0f ) == 164 ); + CHECK( max_healthy_at_bmi( dummy, 18.0f ) == 189 ); + // Normal (18.5) + CHECK( max_healthy_at_bmi( dummy, 19.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 20.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 21.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 22.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 23.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 24.0f ) == 200 ); + // Overweight (25) + CHECK( max_healthy_at_bmi( dummy, 25.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 26.0f ) == 178 ); + CHECK( max_healthy_at_bmi( dummy, 27.0f ) == 149 ); + CHECK( max_healthy_at_bmi( dummy, 28.0f ) == 115 ); + CHECK( max_healthy_at_bmi( dummy, 29.0f ) == 74 ); + // Obese (30) + CHECK( max_healthy_at_bmi( dummy, 30.0f ) == 28 ); + CHECK( max_healthy_at_bmi( dummy, 31.0f ) == -25 ); + CHECK( max_healthy_at_bmi( dummy, 32.0f ) == -83 ); + CHECK( max_healthy_at_bmi( dummy, 33.0f ) == -148 ); + CHECK( max_healthy_at_bmi( dummy, 34.0f ) == -200 ); + // Very obese (35) + CHECK( max_healthy_at_bmi( dummy, 35.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 36.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 37.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 38.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 39.0f ) == -200 ); + // Morbidly obese (40) + CHECK( max_healthy_at_bmi( dummy, 40.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 41.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 42.0f ) == -200 ); } + From d6ea090b825d8b73a13c7394e4fc79ab5826fa69 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 4 Apr 2020 17:35:08 -0600 Subject: [PATCH 3/8] Rename and tidy up --- .../{char_healthy_test.cpp => char_bmi_test.cpp} | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) rename tests/{char_healthy_test.cpp => char_bmi_test.cpp} (95%) diff --git a/tests/char_healthy_test.cpp b/tests/char_bmi_test.cpp similarity index 95% rename from tests/char_healthy_test.cpp rename to tests/char_bmi_test.cpp index 4b985565424da..0dc6810e064a6 100644 --- a/tests/char_healthy_test.cpp +++ b/tests/char_bmi_test.cpp @@ -11,12 +11,12 @@ static float bmi_at_kcal_ratio( player &dummy, float kcal_percent ) return dummy.get_bmi(); } -// Return the `kcal_ratio` needed to reach the given `body_mass_index` +// Return the `kcal_ratio` needed to reach the given `bmi` // BMI = 13 + (12 * kcal_ratio) // kcal_ratio = (BMI - 13) / 12 -static float bmi_to_kcal_ratio( float body_mass_index ) +static float bmi_to_kcal_ratio( float bmi ) { - return ( body_mass_index - 13 ) / 12; + return ( bmi - 13 ) / 12; } // Return the player's `get_max_healthy` value at the given body mass index @@ -44,18 +44,25 @@ TEST_CASE( "body mass index determines weight description", "[healthy][bmi][weig CHECK( weight_string_at_bmi( dummy, 13.0f ) == "Skeletal" ); CHECK( weight_string_at_bmi( dummy, 13.9f ) == "Skeletal" ); + // 14 CHECK( weight_string_at_bmi( dummy, 14.1f ) == "Emaciated" ); CHECK( weight_string_at_bmi( dummy, 15.9f ) == "Emaciated" ); + // 16 CHECK( weight_string_at_bmi( dummy, 16.1f ) == "Underweight" ); CHECK( weight_string_at_bmi( dummy, 18.4f ) == "Underweight" ); + // 18.5 CHECK( weight_string_at_bmi( dummy, 18.6f ) == "Normal" ); CHECK( weight_string_at_bmi( dummy, 24.9f ) == "Normal" ); + // 25 CHECK( weight_string_at_bmi( dummy, 25.1f ) == "Overweight" ); CHECK( weight_string_at_bmi( dummy, 29.9f ) == "Overweight" ); + // 30 CHECK( weight_string_at_bmi( dummy, 30.1f ) == "Obese" ); CHECK( weight_string_at_bmi( dummy, 34.9f ) == "Obese" ); + // 35 CHECK( weight_string_at_bmi( dummy, 35.1f ) == "Very Obese" ); CHECK( weight_string_at_bmi( dummy, 39.9f ) == "Very Obese" ); + // 40 CHECK( weight_string_at_bmi( dummy, 40.1f ) == "Morbidly Obese" ); CHECK( weight_string_at_bmi( dummy, 41.0f ) == "Morbidly Obese" ); } @@ -98,7 +105,7 @@ TEST_CASE( "stored kcal ratio determines body mass index", "[healthy][kcal][bmi] CHECK( bmi_at_kcal_ratio( dummy, 2.5f ) == Approx( 43.0f ).margin( 0.01f ) ); } -TEST_CASE( "body mass effect on maximum healthiness", "[healthy][bmi][max]" ) +TEST_CASE( "body mass index determines maximum healthiness", "[healthy][bmi][max]" ) { avatar dummy; From 14a4425a6d5fef5d2777501feaf25f81581ed610 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 5 Apr 2020 10:08:42 -0600 Subject: [PATCH 4/8] Add biometrics test for height/bodyweight Plus stub test cases for BMR and activity level --- tests/char_biometrics_test.cpp | 133 +++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 tests/char_biometrics_test.cpp diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp new file mode 100644 index 0000000000000..ba58065d45559 --- /dev/null +++ b/tests/char_biometrics_test.cpp @@ -0,0 +1,133 @@ +#include "avatar.h" +#include "game_constants.h" + +#include "catch/catch.hpp" + +// Return `bodyweight` in kilograms for a player at the given body mass index. +static float bodyweight_kg_at_bmi( player &dummy, float bmi ) +{ + // Set enough stored calories to reach target BMI + dummy.set_stored_kcal( dummy.get_healthy_kcal() * ( bmi - 13 ) / 12 ); + REQUIRE( dummy.get_bmi() == Approx( bmi ).margin( 0.001f ) ); + + return to_kilogram( dummy.bodyweight() ); +} + +TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight]" ) +{ + avatar dummy; + + // NOTE: As of this writing, Character `init_height` is hard-coded to 175 (cm) in the class + // definition in character.h, as a protected member with no functions to access it. Calling + // `height()` now is an indirect way to verify it before continuing. + REQUIRE( dummy.height() == 175 ); + + // Body weight is calculated as ( BMI * (height/100)^2 ). At any given height, body weight + // varies based on body mass index (which in turn depends on the amount of stored calories). + + GIVEN( "character is normal-sized (medium)" ) { + REQUIRE_FALSE( dummy.has_trait( trait_id( "SMALL" ) ) ); + REQUIRE_FALSE( dummy.has_trait( trait_id( "LARGE" ) ) ); + REQUIRE_FALSE( dummy.has_trait( trait_id( "HUGE" ) ) ); + REQUIRE( dummy.get_size() == MS_MEDIUM ); + + THEN( "height is 175cm" ) { + CHECK( dummy.height() == 175 ); + } + THEN( "bodyweight varies from ~49-107kg" ) { + // BMI [16-35] is "Emaciated/Underweight" to "Obese/Very Obese" + CHECK( bodyweight_kg_at_bmi( dummy, 16.0 ) == Approx( 49.0 ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 25.0 ) == Approx( 76.6 ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 35.0 ) == Approx( 107.2 ).margin( 0.1f ) ); + } + } + + GIVEN( "character is small" ) { + dummy.toggle_trait( trait_id( "SMALL" ) ); + REQUIRE( dummy.has_trait( trait_id( "SMALL" ) ) ); + REQUIRE( dummy.get_size() == MS_SMALL ); + + THEN( "height is 125cm" ) { + CHECK( dummy.height() == 125 ); + } + THEN( "bodyweight varies from ~25-55kg" ) { + CHECK( bodyweight_kg_at_bmi( dummy, 16.0f ) == Approx( 25.0f ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 25.0f ) == Approx( 39.1f ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 35.0f ) == Approx( 54.7f ).margin( 0.1f ) ); + } + } + + GIVEN( "character is large" ) { + dummy.toggle_trait( trait_id( "LARGE" ) ); + REQUIRE( dummy.has_trait( trait_id( "LARGE" ) ) ); + REQUIRE( dummy.get_size() == MS_LARGE ); + + THEN( "height is 225cm" ) { + CHECK( dummy.height() == 225 ); + } + THEN( "bodyweight varies from ~81-177kg" ) { + CHECK( bodyweight_kg_at_bmi( dummy, 16.0f ) == Approx( 81.0f ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 25.0f ) == Approx( 126.6f ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 35.0f ) == Approx( 177.2f ).margin( 0.1f ) ); + } + } + + GIVEN( "character is huge" ) { + dummy.toggle_trait( trait_id( "HUGE" ) ); + REQUIRE( dummy.has_trait( trait_id( "HUGE" ) ) ); + REQUIRE( dummy.get_size() == MS_HUGE ); + + THEN( "height is 275cm" ) { + CHECK( dummy.height() == 275 ); + } + THEN( "bodyweight varies from ~121-265kg" ) { + CHECK( bodyweight_kg_at_bmi( dummy, 16.0f ) == Approx( 121.0f ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 25.0f ) == Approx( 189.1f ).margin( 0.1f ) ); + CHECK( bodyweight_kg_at_bmi( dummy, 35.0f ) == Approx( 264.7f ).margin( 0.1f ) ); + } + } +} + +TEST_CASE( "character activity level", "[biometrics][activity]" ) +{ + // Activity level is a PROTECTED floating-point number + // but it is only set to discrete values(??) + // + // NO_EXERCISE = 1.2f; + // LIGHT_EXERCISE = 1.375f; + // MODERATE_EXERCISE = 1.55f; + // ACTIVE_EXERCISE = 1.725f; + // EXTRA_EXERCISE = 1.9f; + + // Functions: + // activity_level_str (return string constant for each range) + // reset_activity_level (to NO_EXERCISE) + // increase_activity_level + // decrease_activity_level + + avatar dummy; + CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); +} + +TEST_CASE( "character basal metabolic rate", "[biometrics][bmr]" ) +{ + avatar dummy; + + CHECK( dummy.height() == 175 ); + CHECK( dummy.bodyweight() == 76562500_milligram ); + CHECK( dummy.metabolic_rate_base() == 1.0f ); + CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); + CHECK( dummy.get_bmr() == 2087 ); + + // metabolic_rate_base: + // PLAYER_HUNGER_RATE option, metabolism_modifier (based on mutation) + + // metabolic_rate (filled with TODOs) + // Based on four thresholds for hunger level(?) + // Penalize fast survivors + // Cold decreases metabolism + + // get_bmr: + // bodyweight, height, metabolic_base_rate, activity_level +} + From 17c4c0ec9a1d6d1554e0545d376e702c691c87a4 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 5 Apr 2020 13:42:09 -0600 Subject: [PATCH 5/8] Add metabolic rate and activity level tests --- tests/char_biometrics_test.cpp | 120 +++++++++++++++++++++++++++++++-- tests/char_bmi_test.cpp | 2 +- 2 files changed, 115 insertions(+), 7 deletions(-) diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp index ba58065d45559..8a204d2964ae3 100644 --- a/tests/char_biometrics_test.cpp +++ b/tests/char_biometrics_test.cpp @@ -1,5 +1,6 @@ #include "avatar.h" #include "game_constants.h" +#include "options.h" #include "catch/catch.hpp" @@ -13,6 +14,15 @@ static float bodyweight_kg_at_bmi( player &dummy, float bmi ) return to_kilogram( dummy.bodyweight() ); } +// Return player `metabolic_rate_base` with a given mutation +static float metabolic_rate_with_mutation( player &dummy, std::string trait_name ) +{ + dummy.empty_traits(); + dummy.toggle_trait( trait_id( trait_name ) ); + + return dummy.metabolic_rate_base(); +} + TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight]" ) { avatar dummy; @@ -90,8 +100,7 @@ TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight] TEST_CASE( "character activity level", "[biometrics][activity]" ) { - // Activity level is a PROTECTED floating-point number - // but it is only set to discrete values(??) + // Activity level is a floating-point number, but only set to discrete values: // // NO_EXERCISE = 1.2f; // LIGHT_EXERCISE = 1.375f; @@ -99,14 +108,113 @@ TEST_CASE( "character activity level", "[biometrics][activity]" ) // ACTIVE_EXERCISE = 1.725f; // EXTRA_EXERCISE = 1.9f; - // Functions: + // Functions tested: // activity_level_str (return string constant for each range) // reset_activity_level (to NO_EXERCISE) - // increase_activity_level - // decrease_activity_level + // increase_activity_level (only if greater than current) + // decrease_activity_level (only if less than current) avatar dummy; - CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); + + SECTION( "reset activity level to NO_EXERCISE" ) { + // Start at no exercise + dummy.reset_activity_level(); + CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); + // Increase and confirm + dummy.increase_activity_level( MODERATE_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + // Reset back and ensure it worked + dummy.reset_activity_level(); + CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); + } + + SECTION( "increase_activity_level" ) { + // Start at the lowest level + dummy.reset_activity_level(); + REQUIRE( dummy.activity_level_str() == "NO_EXERCISE" ); + + // Increase level a couple times + dummy.increase_activity_level( LIGHT_EXERCISE ); + CHECK( dummy.activity_level_str() == "LIGHT_EXERCISE" ); + dummy.increase_activity_level( MODERATE_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + // Cannot 'increase' to lower level + dummy.increase_activity_level( LIGHT_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + dummy.increase_activity_level( NO_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + // Increase to highest level + dummy.increase_activity_level( ACTIVE_EXERCISE ); + CHECK( dummy.activity_level_str() == "ACTIVE_EXERCISE" ); + dummy.increase_activity_level( EXTRA_EXERCISE ); + CHECK( dummy.activity_level_str() == "EXTRA_EXERCISE" ); + // Cannot increase beyond the highest + dummy.increase_activity_level( EXTRA_EXERCISE ); + CHECK( dummy.activity_level_str() == "EXTRA_EXERCISE" ); + + } + + SECTION( "decrease_activity_level" ) { + // Start at the highest level + dummy.reset_activity_level(); + dummy.increase_activity_level( EXTRA_EXERCISE ); + REQUIRE( dummy.activity_level_str() == "EXTRA_EXERCISE" ); + + // Decrease level a couple times + dummy.decrease_activity_level( ACTIVE_EXERCISE ); + CHECK( dummy.activity_level_str() == "ACTIVE_EXERCISE" ); + dummy.decrease_activity_level( MODERATE_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + // Cannot 'decrease' to higher level + dummy.decrease_activity_level( EXTRA_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + dummy.decrease_activity_level( ACTIVE_EXERCISE ); + CHECK( dummy.activity_level_str() == "MODERATE_EXERCISE" ); + // Decrease to lowest level + dummy.decrease_activity_level( LIGHT_EXERCISE ); + CHECK( dummy.activity_level_str() == "LIGHT_EXERCISE" ); + dummy.decrease_activity_level( NO_EXERCISE ); + CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); + // Cannot decrease below lowest + dummy.decrease_activity_level( NO_EXERCISE ); + CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); + } +} + +TEST_CASE( "character metabolic rate", "[biometrics][metabolism]" ) +{ + avatar dummy; + + // Metabolic base rate uses PLAYER_HUNGER_RATE from game_balance.json, described as "base hunger + // rate per 5 minutes". With no metabolism-affecting mutations, metabolism should be this value. + const float normal_metabolic_rate = get_option( "PLAYER_HUNGER_RATE" ); + dummy.empty_traits(); + CHECK( dummy.metabolic_rate_base() == normal_metabolic_rate ); + + // The remaining checks assume the configured base rate is 1.0; if this is ever changed in the + // game balance JSON, the below tests are likely to fail and need adjustment. + REQUIRE( normal_metabolic_rate == 1.0f ); + + // Mutations with a "metabolism_modifier" in mutations.json add/subtract to the base rate. + // For example the rapid / fast / very fast / extreme metabolisms: + // + // MET_RAT (+0.333), HUNGER (+0.5), HUNGER2 (+1.0), HUNGER3 (+2.0) + // + // And the light eater / heat dependent / cold blooded spectrum: + // + // LIGHTEATER (-0.333), COLDBLOOD (-0.333), COLDBLOOD2/3/4 (-0.5) + // + // If metabolism modifiers are changed, the below check(s) need to be adjusted as well. + + CHECK( metabolic_rate_with_mutation( dummy, "MET_RAT" ) == Approx( 1.333f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "HUNGER" ) == Approx( 1.5f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "HUNGER2" ) == Approx( 2.0f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "HUNGER3" ) == Approx( 3.0f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "LIGHTEATER" ) == Approx( 0.667f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "COLDBLOOD" ) == Approx( 0.667f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "COLDBLOOD2" ) == Approx( 0.5f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "COLDBLOOD3" ) == Approx( 0.5f ) ); + CHECK( metabolic_rate_with_mutation( dummy, "COLDBLOOD4" ) == Approx( 0.5f ) ); } TEST_CASE( "character basal metabolic rate", "[biometrics][bmr]" ) diff --git a/tests/char_bmi_test.cpp b/tests/char_bmi_test.cpp index 0dc6810e064a6..fe0712a57cc56 100644 --- a/tests/char_bmi_test.cpp +++ b/tests/char_bmi_test.cpp @@ -56,7 +56,7 @@ TEST_CASE( "body mass index determines weight description", "[healthy][bmi][weig // 25 CHECK( weight_string_at_bmi( dummy, 25.1f ) == "Overweight" ); CHECK( weight_string_at_bmi( dummy, 29.9f ) == "Overweight" ); - // 30 + // 30 CHECK( weight_string_at_bmi( dummy, 30.1f ) == "Obese" ); CHECK( weight_string_at_bmi( dummy, 34.9f ) == "Obese" ); // 35 From f2d88610091aa88b4965c1a520f409eddff49ee6 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 5 Apr 2020 13:48:13 -0600 Subject: [PATCH 6/8] Merge all biometrics tests into one file --- tests/char_biometrics_test.cpp | 161 ++++++++++++++++++++++++++++++++- tests/char_bmi_test.cpp | 156 -------------------------------- 2 files changed, 157 insertions(+), 160 deletions(-) delete mode 100644 tests/char_bmi_test.cpp diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp index 8a204d2964ae3..0a70e6749463d 100644 --- a/tests/char_biometrics_test.cpp +++ b/tests/char_biometrics_test.cpp @@ -4,13 +4,48 @@ #include "catch/catch.hpp" -// Return `bodyweight` in kilograms for a player at the given body mass index. -static float bodyweight_kg_at_bmi( player &dummy, float bmi ) +// Return the `kcal_ratio` needed to reach the given `bmi` +// BMI = 13 + (12 * kcal_ratio) +// kcal_ratio = (BMI - 13) / 12 +static float bmi_to_kcal_ratio( float bmi ) +{ + return ( bmi - 13 ) / 12; +} + +// Set enough stored calories to reach target BMI +static void set_player_bmi( player &dummy, float bmi ) { - // Set enough stored calories to reach target BMI - dummy.set_stored_kcal( dummy.get_healthy_kcal() * ( bmi - 13 ) / 12 ); + dummy.set_stored_kcal( dummy.get_healthy_kcal() * bmi_to_kcal_ratio( bmi ) ); REQUIRE( dummy.get_bmi() == Approx( bmi ).margin( 0.001f ) ); +} + +// Return the player's `get_bmi` at `kcal_percent` (actually a ratio of stored_kcal to healthy_kcal) +static float bmi_at_kcal_ratio( player &dummy, float kcal_percent ) +{ + dummy.set_stored_kcal( dummy.get_healthy_kcal() * kcal_percent ); + REQUIRE( dummy.get_kcal_percent() == Approx( kcal_percent ).margin( 0.001f ) ); + + return dummy.get_bmi(); +} +// Return the player's `get_max_healthy` value at the given body mass index +static int max_healthy_at_bmi( player &dummy, float bmi ) +{ + set_player_bmi( dummy, bmi ); + return dummy.get_max_healthy(); +} + +// Return the player's `get_weight_string` at the given body mass index +static std::string weight_string_at_bmi( player &dummy, float bmi ) +{ + set_player_bmi( dummy, bmi ); + return dummy.get_weight_string(); +} + +// Return `bodyweight` in kilograms for a player at the given body mass index. +static float bodyweight_kg_at_bmi( player &dummy, float bmi ) +{ + set_player_bmi( dummy, bmi ); return to_kilogram( dummy.bodyweight() ); } @@ -19,10 +54,128 @@ static float metabolic_rate_with_mutation( player &dummy, std::string trait_name { dummy.empty_traits(); dummy.toggle_trait( trait_id( trait_name ) ); + REQUIRE( dummy.has_trait( trait_id( trait_name ) ) ); return dummy.metabolic_rate_base(); } +TEST_CASE( "body mass index determines weight description", "[biometrics][bmi][weight]" ) +{ + avatar dummy; + + CHECK( weight_string_at_bmi( dummy, 13.0f ) == "Skeletal" ); + CHECK( weight_string_at_bmi( dummy, 13.9f ) == "Skeletal" ); + // 14 + CHECK( weight_string_at_bmi( dummy, 14.1f ) == "Emaciated" ); + CHECK( weight_string_at_bmi( dummy, 15.9f ) == "Emaciated" ); + // 16 + CHECK( weight_string_at_bmi( dummy, 16.1f ) == "Underweight" ); + CHECK( weight_string_at_bmi( dummy, 18.4f ) == "Underweight" ); + // 18.5 + CHECK( weight_string_at_bmi( dummy, 18.6f ) == "Normal" ); + CHECK( weight_string_at_bmi( dummy, 24.9f ) == "Normal" ); + // 25 + CHECK( weight_string_at_bmi( dummy, 25.1f ) == "Overweight" ); + CHECK( weight_string_at_bmi( dummy, 29.9f ) == "Overweight" ); + // 30 + CHECK( weight_string_at_bmi( dummy, 30.1f ) == "Obese" ); + CHECK( weight_string_at_bmi( dummy, 34.9f ) == "Obese" ); + // 35 + CHECK( weight_string_at_bmi( dummy, 35.1f ) == "Very Obese" ); + CHECK( weight_string_at_bmi( dummy, 39.9f ) == "Very Obese" ); + // 40 + CHECK( weight_string_at_bmi( dummy, 40.1f ) == "Morbidly Obese" ); + CHECK( weight_string_at_bmi( dummy, 41.0f ) == "Morbidly Obese" ); +} + +TEST_CASE( "stored kcal ratio determines body mass index", "[biometrics][kcal][bmi]" ) +{ + avatar dummy; + + // Skeletal (<14) + CHECK( bmi_at_kcal_ratio( dummy, 0.01f ) == Approx( 13.12f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.05f ) == Approx( 13.6f ).margin( 0.01f ) ); + // Emaciated (14) + CHECK( bmi_at_kcal_ratio( dummy, 0.1f ) == Approx( 14.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.2f ) == Approx( 15.4f ).margin( 0.01f ) ); + // Underweight (16) + CHECK( bmi_at_kcal_ratio( dummy, 0.3f ) == Approx( 16.6f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.4f ) == Approx( 17.8f ).margin( 0.01f ) ); + // Normal (18.5) + CHECK( bmi_at_kcal_ratio( dummy, 0.5f ) == Approx( 19.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.6f ) == Approx( 20.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.7f ) == Approx( 21.4f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.8f ) == Approx( 22.6f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 0.9f ) == Approx( 23.8f ).margin( 0.01f ) ); + // Overweight (25) + CHECK( bmi_at_kcal_ratio( dummy, 1.0f ) == Approx( 25.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.1f ) == Approx( 26.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.2f ) == Approx( 27.4f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.3f ) == Approx( 28.6f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.4f ) == Approx( 29.8f ).margin( 0.01f ) ); + // Obese (30) + CHECK( bmi_at_kcal_ratio( dummy, 1.5f ) == Approx( 31.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.6f ) == Approx( 32.2f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.7f ) == Approx( 33.4f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 1.8f ) == Approx( 34.6f ).margin( 0.01f ) ); + // Very obese (35) + CHECK( bmi_at_kcal_ratio( dummy, 1.9f ) == Approx( 35.8f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 2.0f ) == Approx( 37.0f ).margin( 0.01f ) ); + // Morbidly obese (40) + CHECK( bmi_at_kcal_ratio( dummy, 2.25f ) == Approx( 40.0f ).margin( 0.01f ) ); + CHECK( bmi_at_kcal_ratio( dummy, 2.5f ) == Approx( 43.0f ).margin( 0.01f ) ); +} + +TEST_CASE( "body mass index determines maximum healthiness", "[biometrics][bmi][max]" ) +{ + avatar dummy; + + // Skeletal (<14) + CHECK( max_healthy_at_bmi( dummy, 8.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 9.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 10.0f ) == -183 ); + CHECK( max_healthy_at_bmi( dummy, 11.0f ) == -115 ); + CHECK( max_healthy_at_bmi( dummy, 12.0f ) == -53 ); + CHECK( max_healthy_at_bmi( dummy, 13.0f ) == 2 ); + // Emaciated (14) + CHECK( max_healthy_at_bmi( dummy, 14.0f ) == 51 ); + CHECK( max_healthy_at_bmi( dummy, 15.0f ) == 95 ); + // Underweight (16) + CHECK( max_healthy_at_bmi( dummy, 16.0f ) == 133 ); + CHECK( max_healthy_at_bmi( dummy, 17.0f ) == 164 ); + CHECK( max_healthy_at_bmi( dummy, 18.0f ) == 189 ); + // Normal (18.5) + CHECK( max_healthy_at_bmi( dummy, 19.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 20.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 21.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 22.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 23.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 24.0f ) == 200 ); + // Overweight (25) + CHECK( max_healthy_at_bmi( dummy, 25.0f ) == 200 ); + CHECK( max_healthy_at_bmi( dummy, 26.0f ) == 178 ); + CHECK( max_healthy_at_bmi( dummy, 27.0f ) == 149 ); + CHECK( max_healthy_at_bmi( dummy, 28.0f ) == 115 ); + CHECK( max_healthy_at_bmi( dummy, 29.0f ) == 74 ); + // Obese (30) + CHECK( max_healthy_at_bmi( dummy, 30.0f ) == 28 ); + CHECK( max_healthy_at_bmi( dummy, 31.0f ) == -25 ); + CHECK( max_healthy_at_bmi( dummy, 32.0f ) == -83 ); + CHECK( max_healthy_at_bmi( dummy, 33.0f ) == -148 ); + CHECK( max_healthy_at_bmi( dummy, 34.0f ) == -200 ); + // Very obese (35) + CHECK( max_healthy_at_bmi( dummy, 35.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 36.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 37.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 38.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 39.0f ) == -200 ); + // Morbidly obese (40) + CHECK( max_healthy_at_bmi( dummy, 40.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 41.0f ) == -200 ); + CHECK( max_healthy_at_bmi( dummy, 42.0f ) == -200 ); +} + + TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight]" ) { avatar dummy; diff --git a/tests/char_bmi_test.cpp b/tests/char_bmi_test.cpp deleted file mode 100644 index fe0712a57cc56..0000000000000 --- a/tests/char_bmi_test.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "avatar.h" - -#include "catch/catch.hpp" - -// Return the player's `get_bmi` at `kcal_percent` (actually a ratio of stored_kcal to healthy_kcal) -static float bmi_at_kcal_ratio( player &dummy, float kcal_percent ) -{ - dummy.set_stored_kcal( dummy.get_healthy_kcal() * kcal_percent ); - REQUIRE( dummy.get_kcal_percent() == Approx( kcal_percent ).margin( 0.001f ) ); - - return dummy.get_bmi(); -} - -// Return the `kcal_ratio` needed to reach the given `bmi` -// BMI = 13 + (12 * kcal_ratio) -// kcal_ratio = (BMI - 13) / 12 -static float bmi_to_kcal_ratio( float bmi ) -{ - return ( bmi - 13 ) / 12; -} - -// Return the player's `get_max_healthy` value at the given body mass index -static int max_healthy_at_bmi( player &dummy, float bmi ) -{ - dummy.set_stored_kcal( dummy.get_healthy_kcal() * bmi_to_kcal_ratio( bmi ) ); - REQUIRE( dummy.get_bmi() == Approx( bmi ).margin( 0.001f ) ); - - return dummy.get_max_healthy(); -} - -// Return the player's `get_weight_string` at the given body mass index -static std::string weight_string_at_bmi( player &dummy, float bmi ) -{ - dummy.set_stored_kcal( dummy.get_healthy_kcal() * bmi_to_kcal_ratio( bmi ) ); - REQUIRE( dummy.get_bmi() == Approx( bmi ).margin( 0.001f ) ); - - return dummy.get_weight_string(); -} - - -TEST_CASE( "body mass index determines weight description", "[healthy][bmi][weight]" ) -{ - avatar dummy; - - CHECK( weight_string_at_bmi( dummy, 13.0f ) == "Skeletal" ); - CHECK( weight_string_at_bmi( dummy, 13.9f ) == "Skeletal" ); - // 14 - CHECK( weight_string_at_bmi( dummy, 14.1f ) == "Emaciated" ); - CHECK( weight_string_at_bmi( dummy, 15.9f ) == "Emaciated" ); - // 16 - CHECK( weight_string_at_bmi( dummy, 16.1f ) == "Underweight" ); - CHECK( weight_string_at_bmi( dummy, 18.4f ) == "Underweight" ); - // 18.5 - CHECK( weight_string_at_bmi( dummy, 18.6f ) == "Normal" ); - CHECK( weight_string_at_bmi( dummy, 24.9f ) == "Normal" ); - // 25 - CHECK( weight_string_at_bmi( dummy, 25.1f ) == "Overweight" ); - CHECK( weight_string_at_bmi( dummy, 29.9f ) == "Overweight" ); - // 30 - CHECK( weight_string_at_bmi( dummy, 30.1f ) == "Obese" ); - CHECK( weight_string_at_bmi( dummy, 34.9f ) == "Obese" ); - // 35 - CHECK( weight_string_at_bmi( dummy, 35.1f ) == "Very Obese" ); - CHECK( weight_string_at_bmi( dummy, 39.9f ) == "Very Obese" ); - // 40 - CHECK( weight_string_at_bmi( dummy, 40.1f ) == "Morbidly Obese" ); - CHECK( weight_string_at_bmi( dummy, 41.0f ) == "Morbidly Obese" ); -} - -TEST_CASE( "stored kcal ratio determines body mass index", "[healthy][kcal][bmi]" ) -{ - avatar dummy; - - // Skeletal (<14) - CHECK( bmi_at_kcal_ratio( dummy, 0.01f ) == Approx( 13.12f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.05f ) == Approx( 13.6f ).margin( 0.01f ) ); - // Emaciated (14) - CHECK( bmi_at_kcal_ratio( dummy, 0.1f ) == Approx( 14.2f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.2f ) == Approx( 15.4f ).margin( 0.01f ) ); - // Underweight (16) - CHECK( bmi_at_kcal_ratio( dummy, 0.3f ) == Approx( 16.6f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.4f ) == Approx( 17.8f ).margin( 0.01f ) ); - // Normal (18.5) - CHECK( bmi_at_kcal_ratio( dummy, 0.5f ) == Approx( 19.0f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.6f ) == Approx( 20.2f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.7f ) == Approx( 21.4f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.8f ) == Approx( 22.6f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 0.9f ) == Approx( 23.8f ).margin( 0.01f ) ); - // Overweight (25) - CHECK( bmi_at_kcal_ratio( dummy, 1.0f ) == Approx( 25.0f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.1f ) == Approx( 26.2f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.2f ) == Approx( 27.4f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.3f ) == Approx( 28.6f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.4f ) == Approx( 29.8f ).margin( 0.01f ) ); - // Obese (30) - CHECK( bmi_at_kcal_ratio( dummy, 1.5f ) == Approx( 31.0f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.6f ) == Approx( 32.2f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.7f ) == Approx( 33.4f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 1.8f ) == Approx( 34.6f ).margin( 0.01f ) ); - // Very obese (35) - CHECK( bmi_at_kcal_ratio( dummy, 1.9f ) == Approx( 35.8f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 2.0f ) == Approx( 37.0f ).margin( 0.01f ) ); - // Morbidly obese (40) - CHECK( bmi_at_kcal_ratio( dummy, 2.25f ) == Approx( 40.0f ).margin( 0.01f ) ); - CHECK( bmi_at_kcal_ratio( dummy, 2.5f ) == Approx( 43.0f ).margin( 0.01f ) ); -} - -TEST_CASE( "body mass index determines maximum healthiness", "[healthy][bmi][max]" ) -{ - avatar dummy; - - // Skeletal (<14) - CHECK( max_healthy_at_bmi( dummy, 8.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 9.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 10.0f ) == -183 ); - CHECK( max_healthy_at_bmi( dummy, 11.0f ) == -115 ); - CHECK( max_healthy_at_bmi( dummy, 12.0f ) == -53 ); - CHECK( max_healthy_at_bmi( dummy, 13.0f ) == 2 ); - // Emaciated (14) - CHECK( max_healthy_at_bmi( dummy, 14.0f ) == 51 ); - CHECK( max_healthy_at_bmi( dummy, 15.0f ) == 95 ); - // Underweight (16) - CHECK( max_healthy_at_bmi( dummy, 16.0f ) == 133 ); - CHECK( max_healthy_at_bmi( dummy, 17.0f ) == 164 ); - CHECK( max_healthy_at_bmi( dummy, 18.0f ) == 189 ); - // Normal (18.5) - CHECK( max_healthy_at_bmi( dummy, 19.0f ) == 200 ); - CHECK( max_healthy_at_bmi( dummy, 20.0f ) == 200 ); - CHECK( max_healthy_at_bmi( dummy, 21.0f ) == 200 ); - CHECK( max_healthy_at_bmi( dummy, 22.0f ) == 200 ); - CHECK( max_healthy_at_bmi( dummy, 23.0f ) == 200 ); - CHECK( max_healthy_at_bmi( dummy, 24.0f ) == 200 ); - // Overweight (25) - CHECK( max_healthy_at_bmi( dummy, 25.0f ) == 200 ); - CHECK( max_healthy_at_bmi( dummy, 26.0f ) == 178 ); - CHECK( max_healthy_at_bmi( dummy, 27.0f ) == 149 ); - CHECK( max_healthy_at_bmi( dummy, 28.0f ) == 115 ); - CHECK( max_healthy_at_bmi( dummy, 29.0f ) == 74 ); - // Obese (30) - CHECK( max_healthy_at_bmi( dummy, 30.0f ) == 28 ); - CHECK( max_healthy_at_bmi( dummy, 31.0f ) == -25 ); - CHECK( max_healthy_at_bmi( dummy, 32.0f ) == -83 ); - CHECK( max_healthy_at_bmi( dummy, 33.0f ) == -148 ); - CHECK( max_healthy_at_bmi( dummy, 34.0f ) == -200 ); - // Very obese (35) - CHECK( max_healthy_at_bmi( dummy, 35.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 36.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 37.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 38.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 39.0f ) == -200 ); - // Morbidly obese (40) - CHECK( max_healthy_at_bmi( dummy, 40.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 41.0f ) == -200 ); - CHECK( max_healthy_at_bmi( dummy, 42.0f ) == -200 ); -} - From 193fe78448681f5356d461b24ea82ae0b877473d Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 5 Apr 2020 17:22:52 -0600 Subject: [PATCH 7/8] Add basal metabolic rate tests Plus a nice confirmation from Wikipedia on the accuracy of our `max_healthy` tests in relation to BMI --- tests/char_biometrics_test.cpp | 150 +++++++++++++++++++++++++++------ 1 file changed, 124 insertions(+), 26 deletions(-) diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp index 0a70e6749463d..6c5fb9068f0a9 100644 --- a/tests/char_biometrics_test.cpp +++ b/tests/char_biometrics_test.cpp @@ -49,16 +49,32 @@ static float bodyweight_kg_at_bmi( player &dummy, float bmi ) return to_kilogram( dummy.bodyweight() ); } -// Return player `metabolic_rate_base` with a given mutation -static float metabolic_rate_with_mutation( player &dummy, std::string trait_name ) +// Clear player traits and give them a single trait by name +static void set_single_trait( player &dummy, std::string trait_name ) { dummy.empty_traits(); dummy.toggle_trait( trait_id( trait_name ) ); REQUIRE( dummy.has_trait( trait_id( trait_name ) ) ); +} +// Return player `metabolic_rate_base` with a given mutation +static float metabolic_rate_with_mutation( player &dummy, std::string trait_name ) +{ + set_single_trait( dummy, trait_name ); return dummy.metabolic_rate_base(); } +// Return player `get_bmr` (basal metabolic rate) with the given BMI and activity level. +static int bmr_at_bmi_act_level( player &dummy, float bmi, float activity_level ) +{ + set_player_bmi( dummy, bmi ); + dummy.reset_activity_level(); + dummy.increase_activity_level( activity_level ); + + return dummy.get_bmr(); +} + + TEST_CASE( "body mass index determines weight description", "[biometrics][bmi][weight]" ) { avatar dummy; @@ -128,6 +144,11 @@ TEST_CASE( "stored kcal ratio determines body mass index", "[biometrics][kcal][b TEST_CASE( "body mass index determines maximum healthiness", "[biometrics][bmi][max]" ) { + // "BMIs under 20 and over 25 have been associated with higher all-causes mortality, + // with the risk increasing with distance from the 20–25 range." + // + // https://en.wikipedia.org/wiki/Body_mass_index + avatar dummy; // Skeletal (<14) @@ -151,8 +172,8 @@ TEST_CASE( "body mass index determines maximum healthiness", "[biometrics][bmi][ CHECK( max_healthy_at_bmi( dummy, 22.0f ) == 200 ); CHECK( max_healthy_at_bmi( dummy, 23.0f ) == 200 ); CHECK( max_healthy_at_bmi( dummy, 24.0f ) == 200 ); - // Overweight (25) CHECK( max_healthy_at_bmi( dummy, 25.0f ) == 200 ); + // Overweight (25) CHECK( max_healthy_at_bmi( dummy, 26.0f ) == 178 ); CHECK( max_healthy_at_bmi( dummy, 27.0f ) == 149 ); CHECK( max_healthy_at_bmi( dummy, 28.0f ) == 115 ); @@ -176,7 +197,7 @@ TEST_CASE( "body mass index determines maximum healthiness", "[biometrics][bmi][ } -TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight]" ) +TEST_CASE( "size determines height and body weight", "[biometrics][height][bodyweight]" ) { avatar dummy; @@ -206,8 +227,7 @@ TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight] } GIVEN( "character is small" ) { - dummy.toggle_trait( trait_id( "SMALL" ) ); - REQUIRE( dummy.has_trait( trait_id( "SMALL" ) ) ); + set_single_trait( dummy, "SMALL" ); REQUIRE( dummy.get_size() == MS_SMALL ); THEN( "height is 125cm" ) { @@ -221,8 +241,7 @@ TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight] } GIVEN( "character is large" ) { - dummy.toggle_trait( trait_id( "LARGE" ) ); - REQUIRE( dummy.has_trait( trait_id( "LARGE" ) ) ); + set_single_trait( dummy, "LARGE" ); REQUIRE( dummy.get_size() == MS_LARGE ); THEN( "height is 225cm" ) { @@ -236,8 +255,7 @@ TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight] } GIVEN( "character is huge" ) { - dummy.toggle_trait( trait_id( "HUGE" ) ); - REQUIRE( dummy.has_trait( trait_id( "HUGE" ) ) ); + set_single_trait( dummy, "HUGE" ); REQUIRE( dummy.get_size() == MS_HUGE ); THEN( "height is 275cm" ) { @@ -251,7 +269,7 @@ TEST_CASE( "character height and body weight", "[biometrics][height][bodyweight] } } -TEST_CASE( "character activity level", "[biometrics][activity]" ) +TEST_CASE( "activity level reset, increase and decrease", "[biometrics][activity]" ) { // Activity level is a floating-point number, but only set to discrete values: // @@ -334,7 +352,7 @@ TEST_CASE( "character activity level", "[biometrics][activity]" ) } } -TEST_CASE( "character metabolic rate", "[biometrics][metabolism]" ) +TEST_CASE( "mutations may affect character metabolic rate", "[biometrics][metabolism]" ) { avatar dummy; @@ -370,25 +388,105 @@ TEST_CASE( "character metabolic rate", "[biometrics][metabolism]" ) CHECK( metabolic_rate_with_mutation( dummy, "COLDBLOOD4" ) == Approx( 0.5f ) ); } -TEST_CASE( "character basal metabolic rate", "[biometrics][bmr]" ) +TEST_CASE( "basal metabolic rate with various metabolism", "[biometrics][bmr]" ) { avatar dummy; - CHECK( dummy.height() == 175 ); - CHECK( dummy.bodyweight() == 76562500_milligram ); - CHECK( dummy.metabolic_rate_base() == 1.0f ); - CHECK( dummy.activity_level_str() == "NO_EXERCISE" ); - CHECK( dummy.get_bmr() == 2087 ); + // Basal metabolic rate depends on size (height), bodyweight (BMI), and activity level + // scaled by metabolic base rate. Assume default metabolic rate. + REQUIRE( dummy.metabolic_rate_base() == 1.0f ); + + // Tests cover: + // - normal, very fast, and cold-blooded metabolisms for normal body size + // - BMI from 16.0 (Emaciated/Underweight) to 35.0 (Obese/Very Obese) + // - three different levels of exercise (none, moderate, extra) + + // CHECK expressions have expected value on the left hand side for better readability. + + SECTION( "normal body size" ) { + REQUIRE( dummy.get_size() == MS_MEDIUM ); + + SECTION( "normal metabolism" ) { + CHECK( 1757 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); + CHECK( 2087 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); + CHECK( 2454 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); + + CHECK( 2269 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); + CHECK( 2696 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); + CHECK( 3170 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); - // metabolic_rate_base: - // PLAYER_HUNGER_RATE option, metabolism_modifier (based on mutation) + CHECK( 2782 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); + CHECK( 3304 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); + CHECK( 3886 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + } + + SECTION( "very fast metabolism" ) { + set_single_trait( dummy, "HUNGER2" ); + REQUIRE( dummy.metabolic_rate_base() == 2.0f ); + + CHECK( 3514 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); + CHECK( 4174 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); + CHECK( 4908 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); + + CHECK( 4538 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); + CHECK( 5391 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); + CHECK( 6339 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); + + CHECK( 5563 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); + CHECK( 6608 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); + CHECK( 7771 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + } + + SECTION( "very slow (cold-blooded) metabolism" ) { + set_single_trait( dummy, "COLDBLOOD3" ); + REQUIRE( dummy.metabolic_rate_base() == 0.5f ); + + CHECK( 879 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); + CHECK( 1044 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); + CHECK( 1227 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); + + CHECK( 1135 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); + CHECK( 1348 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); + CHECK( 1585 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); + + CHECK( 1391 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); + CHECK( 1652 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); + CHECK( 1943 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + } + } + + SECTION( "small body size" ) { + set_single_trait( dummy, "SMALL" ); + REQUIRE( dummy.get_size() == MS_SMALL ); - // metabolic_rate (filled with TODOs) - // Based on four thresholds for hunger level(?) - // Penalize fast survivors - // Cold decreases metabolism + CHECK( 1094 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); + CHECK( 1262 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); + CHECK( 1449 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); + + CHECK( 1413 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); + CHECK( 1630 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); + CHECK( 1872 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); + + CHECK( 1732 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); + CHECK( 1998 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); + CHECK( 2294 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + } - // get_bmr: - // bodyweight, height, metabolic_base_rate, activity_level + SECTION( "large body size" ) { + set_single_trait( dummy, "LARGE" ); + REQUIRE( dummy.get_size() == MS_LARGE ); + + CHECK( 2516 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); + CHECK( 3062 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); + CHECK( 3669 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); + + CHECK( 3250 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); + CHECK( 3955 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); + CHECK( 4739 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); + + CHECK( 3983 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); + CHECK( 4848 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); + CHECK( 5809 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + } } From 320442940d77d8a6bbeba5f09fe67a10b1a8c9fb Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 5 Apr 2020 18:47:56 -0600 Subject: [PATCH 8/8] Reduce and simplify BMR test --- tests/char_biometrics_test.cpp | 82 +++++++++------------------------- 1 file changed, 22 insertions(+), 60 deletions(-) diff --git a/tests/char_biometrics_test.cpp b/tests/char_biometrics_test.cpp index 6c5fb9068f0a9..73598c9c9649f 100644 --- a/tests/char_biometrics_test.cpp +++ b/tests/char_biometrics_test.cpp @@ -64,10 +64,9 @@ static float metabolic_rate_with_mutation( player &dummy, std::string trait_name return dummy.metabolic_rate_base(); } -// Return player `get_bmr` (basal metabolic rate) with the given BMI and activity level. -static int bmr_at_bmi_act_level( player &dummy, float bmi, float activity_level ) +// Return player `get_bmr` (basal metabolic rate) at the given activity level. +static int bmr_at_act_level( player &dummy, float activity_level ) { - set_player_bmi( dummy, bmi ); dummy.reset_activity_level(); dummy.increase_activity_level( activity_level ); @@ -388,7 +387,7 @@ TEST_CASE( "mutations may affect character metabolic rate", "[biometrics][metabo CHECK( metabolic_rate_with_mutation( dummy, "COLDBLOOD4" ) == Approx( 0.5f ) ); } -TEST_CASE( "basal metabolic rate with various metabolism", "[biometrics][bmr]" ) +TEST_CASE( "basal metabolic rate with various size and metabolism", "[biometrics][bmr]" ) { avatar dummy; @@ -396,9 +395,12 @@ TEST_CASE( "basal metabolic rate with various metabolism", "[biometrics][bmr]" ) // scaled by metabolic base rate. Assume default metabolic rate. REQUIRE( dummy.metabolic_rate_base() == 1.0f ); + // To keep things simple, use normal BMI for al tests + set_player_bmi( dummy, 25.0f ); + REQUIRE( dummy.get_bmi() == Approx( 25.0f ).margin( 0.001f ) ); + // Tests cover: // - normal, very fast, and cold-blooded metabolisms for normal body size - // - BMI from 16.0 (Emaciated/Underweight) to 35.0 (Obese/Very Obese) // - three different levels of exercise (none, moderate, extra) // CHECK expressions have expected value on the left hand side for better readability. @@ -407,51 +409,27 @@ TEST_CASE( "basal metabolic rate with various metabolism", "[biometrics][bmr]" ) REQUIRE( dummy.get_size() == MS_MEDIUM ); SECTION( "normal metabolism" ) { - CHECK( 1757 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); - CHECK( 2087 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); - CHECK( 2454 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); - - CHECK( 2269 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); - CHECK( 2696 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); - CHECK( 3170 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); - - CHECK( 2782 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); - CHECK( 3304 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); - CHECK( 3886 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + CHECK( 2087 == bmr_at_act_level( dummy, NO_EXERCISE ) ); + CHECK( 2696 == bmr_at_act_level( dummy, MODERATE_EXERCISE ) ); + CHECK( 3304 == bmr_at_act_level( dummy, EXTRA_EXERCISE ) ); } SECTION( "very fast metabolism" ) { set_single_trait( dummy, "HUNGER2" ); REQUIRE( dummy.metabolic_rate_base() == 2.0f ); - CHECK( 3514 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); - CHECK( 4174 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); - CHECK( 4908 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); - - CHECK( 4538 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); - CHECK( 5391 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); - CHECK( 6339 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); - - CHECK( 5563 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); - CHECK( 6608 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); - CHECK( 7771 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + CHECK( 4174 == bmr_at_act_level( dummy, NO_EXERCISE ) ); + CHECK( 5391 == bmr_at_act_level( dummy, MODERATE_EXERCISE ) ); + CHECK( 6608 == bmr_at_act_level( dummy, EXTRA_EXERCISE ) ); } SECTION( "very slow (cold-blooded) metabolism" ) { set_single_trait( dummy, "COLDBLOOD3" ); REQUIRE( dummy.metabolic_rate_base() == 0.5f ); - CHECK( 879 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); - CHECK( 1044 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); - CHECK( 1227 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); - - CHECK( 1135 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); - CHECK( 1348 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); - CHECK( 1585 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); - - CHECK( 1391 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); - CHECK( 1652 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); - CHECK( 1943 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + CHECK( 1044 == bmr_at_act_level( dummy, NO_EXERCISE ) ); + CHECK( 1348 == bmr_at_act_level( dummy, MODERATE_EXERCISE ) ); + CHECK( 1652 == bmr_at_act_level( dummy, EXTRA_EXERCISE ) ); } } @@ -459,34 +437,18 @@ TEST_CASE( "basal metabolic rate with various metabolism", "[biometrics][bmr]" ) set_single_trait( dummy, "SMALL" ); REQUIRE( dummy.get_size() == MS_SMALL ); - CHECK( 1094 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); - CHECK( 1262 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); - CHECK( 1449 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); - - CHECK( 1413 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); - CHECK( 1630 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); - CHECK( 1872 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); - - CHECK( 1732 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); - CHECK( 1998 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); - CHECK( 2294 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + CHECK( 1262 == bmr_at_act_level( dummy, NO_EXERCISE ) ); + CHECK( 1630 == bmr_at_act_level( dummy, MODERATE_EXERCISE ) ); + CHECK( 1998 == bmr_at_act_level( dummy, EXTRA_EXERCISE ) ); } SECTION( "large body size" ) { set_single_trait( dummy, "LARGE" ); REQUIRE( dummy.get_size() == MS_LARGE ); - CHECK( 2516 == bmr_at_bmi_act_level( dummy, 16.0, NO_EXERCISE ) ); - CHECK( 3062 == bmr_at_bmi_act_level( dummy, 25.0, NO_EXERCISE ) ); - CHECK( 3669 == bmr_at_bmi_act_level( dummy, 35.0, NO_EXERCISE ) ); - - CHECK( 3250 == bmr_at_bmi_act_level( dummy, 16.0, MODERATE_EXERCISE ) ); - CHECK( 3955 == bmr_at_bmi_act_level( dummy, 25.0, MODERATE_EXERCISE ) ); - CHECK( 4739 == bmr_at_bmi_act_level( dummy, 35.0, MODERATE_EXERCISE ) ); - - CHECK( 3983 == bmr_at_bmi_act_level( dummy, 16.0, EXTRA_EXERCISE ) ); - CHECK( 4848 == bmr_at_bmi_act_level( dummy, 25.0, EXTRA_EXERCISE ) ); - CHECK( 5809 == bmr_at_bmi_act_level( dummy, 35.0, EXTRA_EXERCISE ) ); + CHECK( 3062 == bmr_at_act_level( dummy, NO_EXERCISE ) ); + CHECK( 3955 == bmr_at_act_level( dummy, MODERATE_EXERCISE ) ); + CHECK( 4848 == bmr_at_act_level( dummy, EXTRA_EXERCISE ) ); } }