Skip to content

Commit

Permalink
Implement weighted snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
Qrox committed Jan 1, 2024
1 parent 71294fa commit 0aa0ba8
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 64 deletions.
48 changes: 6 additions & 42 deletions data/json/snippets/names.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,19 @@
{
"type": "snippet",
"category": "<male_full_name>",
"//TODO": "Currently repeating the entries to assign weight, should probably implement it properly",
"text": [
"<male_backer_name>",
"<male_given_name> '<nick_name>' <family_name>",
"<male_given_name> '<nick_name>' <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>",
"<male_given_name> <family_name>"
{ "text": "<male_backer_name>", "weight": 10 },
{ "text": "<male_given_name> '<nick_name>' <family_name>", "weight": 19 },
{ "text": "<male_given_name> <family_name>", "weight": 171 }
]
},
{
"type": "snippet",
"category": "<female_full_name>",
"//TODO": "Currently repeating the entries to assign weight, should probably implement it properly",
"text": [
"<female_backer_name>",
"<female_given_name> '<nick_name>' <family_name>",
"<female_given_name> '<nick_name>' <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>",
"<female_given_name> <family_name>"
{ "text": "<female_backer_name>", "weight": 10 },
{ "text": "<female_given_name> '<nick_name>' <family_name>", "weight": 19 },
{ "text": "<female_given_name> <family_name>", "weight": 171 }
]
}
]
77 changes: 57 additions & 20 deletions src/text_snippets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ void snippet_library::add_snippets_from_json( const std::string &category, const
if( !entry.read( text ) ) {
entry.throw_error( "Error reading snippet from JSON array" );
}
snippets_by_category[category].no_id.emplace_back( text );
std::vector<weighted_translation> &no_id = snippets_by_category[category].no_id;
const size_t weight_acc = no_id.empty() ? 1 : no_id.back().weight_acc + 1;
no_id.emplace_back( weighted_translation{ weight_acc, text } );
} else {
JsonObject jo = entry.get_object();
add_snippet_from_json( category, jo );
Expand All @@ -54,8 +56,10 @@ void snippet_library::add_snippet_from_json( const std::string &category, const
debugmsg( "snippet_library::add_snippet_from_json called after snippet_library::migrate_hash_to_id." );
}
hash_to_id_migration = std::nullopt;
size_t weight = 1;
translation text;
mandatory( jo, false, "text", text );
optional( jo, false, "weight", weight, 1 );
if( jo.has_member( "id" ) ) {
snippet_id id;
jo.read( "id", id );
Expand All @@ -65,7 +69,9 @@ void snippet_library::add_snippet_from_json( const std::string &category, const
if( snippets_by_id.find( id ) != snippets_by_id.end() ) {
jo.throw_error_at( "id", "Duplicate snippet id" );
}
snippets_by_category[category].ids.emplace_back( id );
std::vector<weighted_id> &ids = snippets_by_category[category].ids;
const size_t weight_acc = ids.empty() ? weight : ids.back().weight_acc + weight;
ids.emplace_back( weighted_id{ weight_acc, id } );
snippets_by_id[id] = text;
if( jo.has_member( "effect_on_examine" ) ) {
EOC_by_id[id] = talk_effect_t( jo, "effect_on_examine" );
Expand All @@ -74,7 +80,9 @@ void snippet_library::add_snippet_from_json( const std::string &category, const
optional( jo, false, "name", name );
name_by_id[id] = name;
} else {
snippets_by_category[category].no_id.emplace_back( text );
std::vector<weighted_translation> &no_id = snippets_by_category[category].no_id;
const size_t weight_acc = no_id.empty() ? weight : no_id.back().weight_acc + weight;
no_id.emplace_back( weighted_translation{ weight_acc, text } );
}
}

Expand Down Expand Up @@ -136,13 +144,15 @@ void snippet_library::reload_names( const cata_path &path )
if( jo.has_array( "name" ) ) {
for( const std::string n : jo.get_array( "name" ) ) {
for( category_snippets *const cat : cats ) {
cat->no_id.emplace_back( no_translation( n ) );
const size_t weight_acc = cat->no_id.empty() ? 1 : cat->no_id.back().weight_acc + 1;
cat->no_id.emplace_back( weighted_translation{ weight_acc, no_translation( n ) } );
}
}
} else {
const std::string n = jo.get_string( "name" );
for( category_snippets *const cat : cats ) {
cat->no_id.emplace_back( no_translation( n ) );
const size_t weight_acc = cat->no_id.empty() ? 1 : cat->no_id.back().weight_acc + 1;
cat->no_id.emplace_back( weighted_translation{ weight_acc, no_translation( n ) } );
}
}
}
Expand All @@ -152,14 +162,14 @@ void snippet_library::reload_names( const cata_path &path )
const usage_info &use = kv.second;
if( use.gendered ) {
if( use.male_cat->no_id.empty() ) {
use.male_cat->no_id.emplace_back( to_translation( "Tom" ) );
use.male_cat->no_id.emplace_back( weighted_translation{ 1, to_translation( "Tom" ) } );
}
if( use.female_cat->no_id.empty() ) {
use.female_cat->no_id.emplace_back( to_translation( "Tom" ) );
use.female_cat->no_id.emplace_back( weighted_translation{ 1, to_translation( "Tom" ) } );
}
} else {
if( use.cat->no_id.empty() ) {
use.cat->no_id.emplace_back( to_translation( "Tom" ) );
use.cat->no_id.emplace_back( weighted_translation{ 1, to_translation( "Tom" ) } );
}
}
}
Expand Down Expand Up @@ -253,10 +263,24 @@ snippet_id snippet_library::random_id_from_category( const std::string &cat ) co
if( !it->second.no_id.empty() ) {
debugmsg( "ids are required, but not specified for some snippets in category %s", cat );
}
if( it->second.ids.empty() ) {
const size_t weight_sum =
it->second.ids.empty() ? 0 : it->second.ids.back().weight_acc;
if( weight_sum == 0 ) {
return snippet_id::NULL_ID();
}
return random_entry( it->second.ids );
// uniform_int_distribution always returns zero when the random engine is
// cata_default_random_engine aka std::minstd_rand0 and the seed is small,
// so std::mt19937 is used instead. This engine is deterministically seeded,
// so acceptable.
// NOLINTNEXTLINE(cata-determinism)
std::mt19937 generator( rng_bits() );
std::uniform_int_distribution<size_t> dis( 0, weight_sum - 1 );
const auto sit = std::upper_bound(
it->second.ids.begin(), it->second.ids.end(), dis( generator ),
[]( const size_t loc, const weighted_id & wid ) {
return loc < wid.weight_acc;
} );
return sit->value;
}

std::optional<translation> snippet_library::random_from_category( const std::string &cat ) const
Expand All @@ -271,22 +295,35 @@ std::optional<translation> snippet_library::random_from_category( const std::str
if( it == snippets_by_category.end() ) {
return std::nullopt;
}
if( it->second.ids.empty() && it->second.no_id.empty() ) {
const size_t ids_weighted_sum =
it->second.ids.empty() ? 0 : it->second.ids.back().weight_acc;
const size_t weight_sum = ids_weighted_sum +
( it->second.no_id.empty() ? 0 : it->second.no_id.back().weight_acc );
if( weight_sum == 0 ) {
return std::nullopt;
}
const size_t count = it->second.ids.size() + it->second.no_id.size();
// uniform_int_distribution always returns zero when the random engine is
// cata_default_random_engine aka std::minstd_rand0 and the seed is small,
// so std::mt19937 is used instead. This engine is deterministically seeded,
// so acceptable.
// NOLINTNEXTLINE(cata-determinism)
std::mt19937 generator( seed );
std::uniform_int_distribution<size_t> dis( 0, count - 1 );
const size_t index = dis( generator );
if( index < it->second.ids.size() ) {
return get_snippet_by_id( it->second.ids[index] );
std::uniform_int_distribution<size_t> dis( 0, weight_sum - 1 );
const size_t loc = dis( generator );
if( loc < ids_weighted_sum ) {
const auto sit = std::upper_bound(
it->second.ids.begin(), it->second.ids.end(), loc,
[]( const size_t loc, const weighted_id & wid ) {
return loc < wid.weight_acc;
} );
return get_snippet_by_id( sit->value );
} else {
return it->second.no_id[index - it->second.ids.size()];
const auto sit = std::upper_bound(
it->second.no_id.begin(), it->second.no_id.end(), loc - ids_weighted_sum,
[]( const size_t loc, const weighted_translation & wt ) {
return loc < wt.weight_acc;
} );
return sit->value;
}
}

Expand Down Expand Up @@ -322,9 +359,9 @@ std::vector<std::pair<snippet_id, std::string>> snippet_library::get_snippets_by
if( add_null_id && !snipps.ids.empty() ) {
ret.emplace_back( snippet_id::NULL_ID(), "" );
}
for( snippet_id id : snipps.ids ) {
std::string desc = get_snippet_ref_by_id( id ).translated();
ret.emplace_back( id, desc );
for( weighted_id id : snipps.ids ) {
std::string desc = get_snippet_ref_by_id( id.value ).translated();
ret.emplace_back( id.value, desc );
}
}
return ret;
Expand Down
16 changes: 14 additions & 2 deletions src/text_snippets.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,21 @@ class snippet_library
std::unordered_map<snippet_id, translation> name_by_id;
std::unordered_map<snippet_id, talk_effect_t> EOC_by_id;

struct weighted_id {
// Accumulated weight that increases in the direction of the vector, used for randomization
size_t weight_acc;
snippet_id value;
};

struct weighted_translation {
// Accumulated weight that increases in the direction of the vector, used for randomization
size_t weight_acc;
translation value;
};

struct category_snippets {
std::vector<snippet_id> ids;
std::vector<translation> no_id;
std::vector<weighted_id> ids;
std::vector<weighted_translation> no_id;
};
std::unordered_map<std::string, category_snippets> snippets_by_category;

Expand Down

0 comments on commit 0aa0ba8

Please sign in to comment.