Skip to content

Commit

Permalink
Added genetic similarity heatmap
Browse files Browse the repository at this point in the history
  • Loading branch information
TCA166 committed Jul 4, 2024
1 parent 1a96373 commit 1acc868
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 13 deletions.
23 changes: 23 additions & 0 deletions src/display/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,27 @@ impl GameMap {
.unwrap();
});
}

/// Creates a new map from the province map with the colors of the provinces in id_list changed to a color determined by assoc
pub fn create_map_graph<F>(
&self,
assoc:F,
output_path: &str
) where F: Fn(&str) -> [u8; 3] {
let new_map = self.create_map(self.title_color_map.keys().map(|x| GameString::new(x.to_owned())).collect(), assoc);
let width = self.width;
let height = self.height;
let output_path = output_path.to_owned();
//we move the writing process out into a thread because it's an IO heavy operation
thread::spawn(move || {
save_buffer(
output_path,
&new_map,
width,
height,
image::ExtendedColorType::Rgb8,
)
.unwrap();
});
}
}
19 changes: 18 additions & 1 deletion src/display/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,37 @@ use minijinja::Environment;

use serde::Serialize;

use super::super::{game_object::GameId, structures::GameObjectDerived};
use super::super::{game_object::GameId, structures::GameObjectDerived, game_state::GameState};
use super::{graph::Grapher, localizer::Localizer, map::GameMap, RenderableType};

/// A struct that renders objects into html pages.
/// It holds a reference to the [Environment] that is used to render the templates, tracks which objects have been rendered and holds the root path.
/// Additionally holds references to the [GameMap] and [Grapher] objects, should they exist of course.
/// It is meant to be used in the [Renderable] trait to render objects and generally act as a helper for rendering objects.
pub struct Renderer<'a> {
/// The [minijinja] environment object that is used to render the templates.
env: &'a Environment<'a>,
/// A hashmap that tracks which objects have been rendered.
rendered: HashMap<&'static str, HashSet<GameId>>,
/// The path where the objects will be rendered to.
path: String,
/// The game map object, if it exists.
/// It may be utilized during the rendering process to render the map.
game_map: Option<&'a GameMap>,
/// The grapher object, if it exists.
/// It may be utilized during the rendering process to render a variety of graphs.
grapher: Option<&'a Grapher>,
/// The game state object.
/// It is used to access the game state during rendering, especially for gathering of data for rendering of optional graphs.
state: &'a GameState,
}

impl<'a> Renderer<'a> {
/// Create a new Renderer with the given [Environment] and path.
pub fn new(
env: &'a Environment<'a>,
path: String,
state: &'a GameState,
game_map: Option<&'a GameMap>,
grapher: Option<&'a Grapher>,
) -> Self {
Expand All @@ -34,6 +45,7 @@ impl<'a> Renderer<'a> {
path,
game_map,
grapher,
state,
}
}

Expand Down Expand Up @@ -86,6 +98,11 @@ impl<'a> Renderer<'a> {
pub fn get_map(&self) -> Option<&GameMap> {
self.game_map
}

/// Returns the [GameState] object.
pub fn get_state(&self) -> &GameState {
self.state
}
}

/// Trait for objects that can be rendered into a html page.
Expand Down
2 changes: 1 addition & 1 deletion src/game_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ impl GameState {
return res;
}

/// Returns a hashmap year->number of deaths for a given dynasty
/// Returns a iterator over the titles
pub fn get_title_iter(&self) -> Iter<GameId, Shared<Title>> {
self.titles.iter()
}
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ fn main() {
create_dir_maybe(format!("{}/cultures", &folder_name).as_str());
player.set_depth(depth, &localizer);
println!("Tree traversed");
let mut renderer = Renderer::new(&env, folder_name.clone(), map.as_ref(), grapher.as_ref());
let mut renderer = Renderer::new(&env, folder_name.clone(), &game_state, map.as_ref(), grapher.as_ref());
let mut queue = vec![RenderableType::Player(player)];
if !no_vis {
timeline
Expand Down
19 changes: 19 additions & 0 deletions src/structures/character.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,25 @@ impl Character {
}
return provinces;
}

/// Gets the DNA similarity of the character with another character
pub fn dna_similarity(&self, other: Shared<Character>) -> f32{
if self.dna.is_none(){
return 0.0;
}
//TODO improve this algorithm, it's not very granular, similarity tends to jump in 0.3 increments
let dna = self.dna.as_ref().unwrap().as_str();
let mut similarity = 0;
let mut dna_chars = dna.chars();
let other = other.get_internal();
let mut other_chars = other.dna.as_ref().unwrap().chars();
while let (Some(d), Some(o)) = (dna_chars.next(), other_chars.next()){
if d == o{
similarity += 1;
}
}
return similarity as f32 / dna.len() as f32;
}
}

impl DummyInit for Character {
Expand Down
50 changes: 40 additions & 10 deletions src/structures/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use super::super::{
};

use super::{Character, FromGameObject, GameId, GameObjectDerived, LineageNode, Shared};
use std::fs::File;
use std::{collections::HashMap, fs::File};

/// A struct representing a player in the game
pub struct Player {
Expand Down Expand Up @@ -118,20 +118,50 @@ impl Renderable for Player {
gif_encoder.encode_frame(frame).unwrap();
}
gif_encoder.set_repeat(Repeat::Infinite).unwrap();
// genetic similarity gradient rendering
let last = self.lineage.last().unwrap().get_character();
let title_iter = renderer.get_state().get_title_iter();
let mut sim = HashMap::new();
for (_, title) in title_iter {
let title = title.get_internal();
let key = title.get_key();
if key.is_none() {
continue;
}
let key = key.unwrap();
if !(key.starts_with("c_") || key.starts_with("b_")) {
continue;
}
let similarity;
let ruler = title.get_holder();
if ruler.is_none() {
similarity = 0.0;
} else {
let ruler = ruler.as_ref().unwrap().get_internal();
similarity = ruler.dna_similarity(last.clone());
}
for barony in title.get_de_jure_barony_keys() {
if sim.contains_key(barony.as_ref()) && similarity < *sim.get(barony.as_ref()).unwrap(){
continue;
}
sim.insert(barony.as_ref().clone(), similarity);
}
}
map.create_map_graph(|key:&str| [255, 255, (255.0 * (1.0 - sim.get(&key.to_owned()).unwrap())) as u8], &format!("{}/sim.png", renderer.get_path()))
}
for char in self.lineage.iter() {
char.get_character()
.get_internal()
.render_all(stack, renderer);
let grapher = renderer.get_grapher();
if grapher.is_some() {
let last = self.lineage.last().unwrap().get_character();
grapher.unwrap().create_tree_graph::<Character>(
last,
true,
&format!("{}/line.svg", renderer.get_path()),
);
}
}
let grapher = renderer.get_grapher();
if grapher.is_some() {
let last = self.lineage.last().unwrap().get_character();
grapher.unwrap().create_tree_graph::<Character>(
last,
true,
&format!("{}/line.svg", renderer.get_path()),
);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions templates/homeTemplate.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<a class="btn btn-outline-primary btn-sm" href="line.svg">
Royal Lineage Graph
</a>
<a class="btn btn-outline-primary btn-sm" href="sim.png">
Genetic similarity heatmap
</a>
{%endif%}
<a class="btn btn-outline-secondary" href="https://github.com/TCA166/CK3-history-extractor">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
Expand Down

0 comments on commit 1acc868

Please sign in to comment.