feat: sample from transformations
This commit is contained in:
parent
bd8b0fecf1
commit
b37386b9e8
@ -42,13 +42,13 @@ pub mod v4 {
|
|||||||
pub problems: BTreeSet<Problem>,
|
pub problems: BTreeSet<Problem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Problem {
|
pub struct Problem {
|
||||||
pub pattern: Pattern,
|
pub pattern: Pattern,
|
||||||
pub method: v2::Method,
|
pub method: v2::Method,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Pattern {
|
pub struct Pattern {
|
||||||
pub pattern: BTreeMap<v1::HoldPosition, v1::HoldRole>,
|
pub pattern: BTreeMap<v1::HoldPosition, v1::HoldRole>,
|
||||||
}
|
}
|
||||||
@ -160,7 +160,7 @@ pub mod v2 {
|
|||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||||
pub struct ProblemUid(pub uuid::Uuid);
|
pub struct ProblemUid(pub uuid::Uuid);
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Display, Copy)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Display, Copy, Hash)]
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
#[display("Feet follow hands")]
|
#[display("Feet follow hands")]
|
||||||
FeetFollowHands,
|
FeetFollowHands,
|
||||||
|
@ -2,6 +2,22 @@ use super::*;
|
|||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
impl Problem {
|
||||||
|
/// Returns all possible transformations for the pattern. Not for the method.
|
||||||
|
#[must_use]
|
||||||
|
pub fn transformations(&self, wall_dimensions: WallDimensions) -> HashSet<Self> {
|
||||||
|
self.pattern
|
||||||
|
.transformations(wall_dimensions)
|
||||||
|
.into_iter()
|
||||||
|
.map(|pattern| Self {
|
||||||
|
pattern,
|
||||||
|
method: self.method,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Pattern {
|
impl Pattern {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -88,6 +104,23 @@ impl Pattern {
|
|||||||
|
|
||||||
pattern
|
pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns all possible transformations for the pattern
|
||||||
|
#[must_use]
|
||||||
|
pub fn transformations(&self, wall_dimensions: WallDimensions) -> HashSet<Self> {
|
||||||
|
let mut transformations = HashSet::new();
|
||||||
|
|
||||||
|
let pattern = self.canonicalize();
|
||||||
|
for mut pat in [pattern.mirror(), pattern] {
|
||||||
|
transformations.insert(pat.clone());
|
||||||
|
while let Some(p) = pat.shift_right(wall_dimensions, 1) {
|
||||||
|
transformations.insert(p.clone());
|
||||||
|
pat = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transformations
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserInteraction {
|
impl UserInteraction {
|
||||||
|
@ -13,6 +13,7 @@ use leptos::prelude::*;
|
|||||||
use leptos_router::params::Params;
|
use leptos_router::params::Params;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[derive(Params, PartialEq, Clone)]
|
#[derive(Params, PartialEq, Clone)]
|
||||||
@ -60,7 +61,7 @@ struct Context {
|
|||||||
wall: Signal<models::Wall>,
|
wall: Signal<models::Wall>,
|
||||||
user_interactions: Signal<BTreeMap<models::Problem, models::UserInteraction>>,
|
user_interactions: Signal<BTreeMap<models::Problem, models::UserInteraction>>,
|
||||||
problem: Signal<Option<models::Problem>>,
|
problem: Signal<Option<models::Problem>>,
|
||||||
filtered_problems: Signal<BTreeSet<models::Problem>>,
|
filtered_problem_transformations: Signal<Vec<HashSet<models::Problem>>>,
|
||||||
user_interaction: Signal<Option<models::UserInteraction>>,
|
user_interaction: Signal<Option<models::UserInteraction>>,
|
||||||
todays_attempt: Signal<Option<models::Attempt>>,
|
todays_attempt: Signal<Option<models::Attempt>>,
|
||||||
latest_attempt: Signal<Option<models::DatedAttempt>>,
|
latest_attempt: Signal<Option<models::DatedAttempt>>,
|
||||||
@ -70,6 +71,9 @@ struct Context {
|
|||||||
cb_next_problem: Callback<()>,
|
cb_next_problem: Callback<()>,
|
||||||
cb_set_problem: Callback<models::Problem>,
|
cb_set_problem: Callback<models::Problem>,
|
||||||
cb_upsert_todays_attempt: Callback<server_functions::UpsertTodaysAttempt>,
|
cb_upsert_todays_attempt: Callback<server_functions::UpsertTodaysAttempt>,
|
||||||
|
|
||||||
|
#[expect(dead_code)]
|
||||||
|
filtered_problems: Signal<BTreeSet<models::Problem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@ -93,7 +97,12 @@ fn Controller(
|
|||||||
|
|
||||||
// Derive signals
|
// Derive signals
|
||||||
let user_interaction = signals::user_interaction(user_interactions.into(), problem.into());
|
let user_interaction = signals::user_interaction(user_interactions.into(), problem.into());
|
||||||
|
let problem_transformations = signals::problem_transformations(wall);
|
||||||
|
|
||||||
|
// TODO: still used?
|
||||||
let filtered_problems = signals::filtered_problems(wall, filter_holds.into());
|
let filtered_problems = signals::filtered_problems(wall, filter_holds.into());
|
||||||
|
|
||||||
|
let filtered_problem_transformations = signals::filtered_problem_transformations(problem_transformations.into(), filter_holds.into());
|
||||||
let todays_attempt = signals::todays_attempt(user_interaction);
|
let todays_attempt = signals::todays_attempt(user_interaction);
|
||||||
let latest_attempt = signals::latest_attempt(user_interaction);
|
let latest_attempt = signals::latest_attempt(user_interaction);
|
||||||
|
|
||||||
@ -111,14 +120,23 @@ fn Controller(
|
|||||||
// Callback: Set next problem to a random problem
|
// Callback: Set next problem to a random problem
|
||||||
let cb_set_random_problem: Callback<()> = Callback::new(move |_| {
|
let cb_set_random_problem: Callback<()> = Callback::new(move |_| {
|
||||||
// TODO: remove current problem from population
|
// TODO: remove current problem from population
|
||||||
let population = filtered_problems.read();
|
let population = filtered_problem_transformations.read();
|
||||||
let population = population.deref();
|
let population = population.deref();
|
||||||
|
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
let mut rng = rand::rng();
|
let mut rng = rand::rng();
|
||||||
let problem = population.iter().choose(&mut rng);
|
|
||||||
|
|
||||||
set_problem.set(problem.cloned());
|
// Pick pattern
|
||||||
|
let Some(problem_set) = population.iter().choose(&mut rng) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pick problem out of pattern transformations
|
||||||
|
let Some(problem) = problem_set.iter().choose(&mut rng) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
set_problem.set(Some(problem.clone()));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Callback: On click hold, Add/Remove hold position to problem filter
|
// Callback: On click hold, Add/Remove hold position to problem filter
|
||||||
@ -161,6 +179,7 @@ fn Controller(
|
|||||||
todays_attempt,
|
todays_attempt,
|
||||||
filter_holds: filter_holds.into(),
|
filter_holds: filter_holds.into(),
|
||||||
filtered_problems: filtered_problems.into(),
|
filtered_problems: filtered_problems.into(),
|
||||||
|
filtered_problem_transformations: filtered_problem_transformations.into(),
|
||||||
user_interactions: user_interactions.into(),
|
user_interactions: user_interactions.into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -315,9 +334,11 @@ fn Filter() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let problems_count = ctx.filtered_problem_transformations.read().iter().map(|set| set.len()).sum::<usize>();
|
||||||
|
|
||||||
let problems_counter = {
|
let problems_counter = {
|
||||||
let name = view! { <p class="mr-4 font-light text-right text-orange-300">{"Problems:"}</p> };
|
let name = view! { <p class="mr-4 font-light text-right text-orange-300">{"Problems:"}</p> };
|
||||||
let value = view! { <p class="text-white">{ctx.filtered_problems.read().len()}</p> };
|
let value = view! { <p class="text-white">{problems_count}</p> };
|
||||||
view! {
|
view! {
|
||||||
<div class="grid grid-rows-none gap-x-0.5 gap-y-1 grid-cols-[auto_1fr]">
|
<div class="grid grid-rows-none gap-x-0.5 gap-y-1 grid-cols-[auto_1fr]">
|
||||||
{name} {value}
|
{name} {value}
|
||||||
@ -336,13 +357,15 @@ fn Filter() -> impl IntoView {
|
|||||||
let mut interaction_counters = InteractionCounters::default();
|
let mut interaction_counters = InteractionCounters::default();
|
||||||
let interaction_counters_view = {
|
let interaction_counters_view = {
|
||||||
let user_ints = ctx.user_interactions.read();
|
let user_ints = ctx.user_interactions.read();
|
||||||
for problem in ctx.filtered_problems.read().iter() {
|
for problem_set in ctx.filtered_problem_transformations.read().iter() {
|
||||||
if let Some(user_int) = user_ints.get(problem) {
|
for problem in problem_set {
|
||||||
match user_int.best_attempt().map(|da| da.attempt) {
|
if let Some(user_int) = user_ints.get(problem) {
|
||||||
Some(models::Attempt::Flash) => interaction_counters.flash += 1,
|
match user_int.best_attempt().map(|da| da.attempt) {
|
||||||
Some(models::Attempt::Send) => interaction_counters.send += 1,
|
Some(models::Attempt::Flash) => interaction_counters.flash += 1,
|
||||||
Some(models::Attempt::Attempt) => interaction_counters.attempt += 1,
|
Some(models::Attempt::Send) => interaction_counters.send += 1,
|
||||||
None => {}
|
Some(models::Attempt::Attempt) => interaction_counters.attempt += 1,
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -575,6 +598,7 @@ mod signals {
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub fn latest_attempt(user_interaction: Signal<Option<models::UserInteraction>>) -> Signal<Option<models::DatedAttempt>> {
|
pub fn latest_attempt(user_interaction: Signal<Option<models::UserInteraction>>) -> Signal<Option<models::DatedAttempt>> {
|
||||||
Signal::derive(move || user_interaction.read().as_ref().and_then(models::UserInteraction::latest_attempt))
|
Signal::derive(move || user_interaction.read().as_ref().and_then(models::UserInteraction::latest_attempt))
|
||||||
@ -600,6 +624,17 @@ mod signals {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps each problem to a set of problems comprising all transformation of the problem pattern.
|
||||||
|
pub(crate) fn problem_transformations(wall: Signal<models::Wall>) -> Memo<Vec<HashSet<models::Problem>>> {
|
||||||
|
Memo::new(move |_prev_val| {
|
||||||
|
let wall = wall.read();
|
||||||
|
wall.problems
|
||||||
|
.iter()
|
||||||
|
.map(|problem| problem.transformations(wall.wall_dimensions))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn filtered_problems(
|
pub(crate) fn filtered_problems(
|
||||||
wall: Signal<models::Wall>,
|
wall: Signal<models::Wall>,
|
||||||
filter_holds: Signal<BTreeSet<models::HoldPosition>>,
|
filter_holds: Signal<BTreeSet<models::HoldPosition>>,
|
||||||
@ -616,6 +651,28 @@ mod signals {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn filtered_problem_transformations(
|
||||||
|
problem_transformations: Signal<Vec<HashSet<models::Problem>>>,
|
||||||
|
filter_holds: Signal<BTreeSet<models::HoldPosition>>,
|
||||||
|
) -> Memo<Vec<HashSet<models::Problem>>> {
|
||||||
|
Memo::new(move |_prev_val| {
|
||||||
|
let filter_holds = filter_holds.read();
|
||||||
|
let problem_transformations = problem_transformations.read();
|
||||||
|
|
||||||
|
problem_transformations
|
||||||
|
.iter()
|
||||||
|
.map(|problem_set| {
|
||||||
|
problem_set
|
||||||
|
.iter()
|
||||||
|
.filter(|problem| filter_holds.iter().all(|hold_pos| problem.pattern.pattern.contains_key(hold_pos)))
|
||||||
|
.map(|problem| problem.clone())
|
||||||
|
.collect::<HashSet<models::Problem>>()
|
||||||
|
})
|
||||||
|
.filter(|set| !set.is_empty())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn hold_role(problem: Signal<Option<models::Problem>>, hold_position: models::HoldPosition) -> Signal<Option<models::HoldRole>> {
|
pub(crate) fn hold_role(problem: Signal<Option<models::Problem>>, hold_position: models::HoldPosition) -> Signal<Option<models::HoldRole>> {
|
||||||
Signal::derive(move || problem.get().and_then(|p| p.pattern.pattern.get(&hold_position).copied()))
|
Signal::derive(move || problem.get().and_then(|p| p.pattern.pattern.get(&hold_position).copied()))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user