Skip to content

Commit

Permalink
Massive progress on double elims
Browse files Browse the repository at this point in the history
optimize memory usage and avoid stack overflows
will need to test some more and continue implementation
still needs grand finale and optional rebuttal
  • Loading branch information
ismellike committed Jun 3, 2024
1 parent 3920a9e commit a2a43cd
Show file tree
Hide file tree
Showing 4 changed files with 375 additions and 162 deletions.
272 changes: 110 additions & 162 deletions contracts/arena-tournament-module/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::contract::CompetitionModule;
use crate::msg::{MatchResultMsg, Tournament};
use crate::state::{EliminationType, Match, MatchResult, MATCHES};
use crate::ContractError;
use crate::{ContractError, NestedArray};
use cosmwasm_std::{Addr, MessageInfo, StdError, Storage};
use cosmwasm_std::{DepsMut, Response, StdResult, Uint128};
use cw_balance::{Distribution, MemberPercentage};
use itertools::Itertools;
use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
use std::iter::repeat;

pub fn instantiate_tournament(
deps: DepsMut,
Expand Down Expand Up @@ -43,65 +45,14 @@ pub fn instantiate_tournament(
.add_attribute("tournament_id", tournament_id.to_string()))
}

#[derive(Debug, Clone)]
enum NestedArray<T> {
Single(Vec<T>),
Nested(Vec<NestedArray<T>>),
}

// The list passed in here should have a length of power 2
fn nest_array<T: Clone>(arr: NestedArray<T>) -> NestedArray<T> {
match arr {
NestedArray::Single(vec) => {
if vec.len() == 2 {
NestedArray::Single(vec)
} else {
let len = vec.len();
let mut nested_pairs = Vec::with_capacity(len / 2);

// Create pairs from outermost elements
for i in 0..(len / 2) {
nested_pairs.push(NestedArray::Single(vec![
vec[i].clone(),
vec[len - 1 - i].clone(),
]));
}

// Recursively nest these pairs
nest_array(NestedArray::Nested(nested_pairs))
}
}
NestedArray::Nested(nested_vec) => {
if nested_vec.len() == 2 {
NestedArray::Nested(nested_vec)
} else {
let len = nested_vec.len();
let mut nested_pairs = Vec::with_capacity(len / 2);

// Create pairs from outermost elements
for i in 0..(len / 2) {
nested_pairs.push(NestedArray::Nested(vec![
nested_vec[i].clone(),
nested_vec[len - 1 - i].clone(),
]));
}

// Recursively nest these pairs
nest_array(NestedArray::Nested(nested_pairs))
}
}
}
}

fn generate_matches(
nested: &NestedArray<usize>,
teams: &[Addr],
matches: &mut Vec<Match>,
match_map: &mut HashMap<u128, usize>,
layer_map: &mut BTreeMap<usize, BTreeSet<u128>>,
) {
let mut queue: VecDeque<(NestedArray<usize>, Option<Uint128>, usize)> = VecDeque::new();
queue.push_back((nested.clone(), None, 0));
let mut queue = VecDeque::new();
queue.push_back((nested, None::<Uint128>, 0));

while let Some((current_nested, parent_match_number, layer)) = queue.pop_front() {
match current_nested {
Expand All @@ -117,9 +68,8 @@ fn generate_matches(
// Handle advancing teams
if let Some(team) = advancing_team {
if let Some(parent_num) = parent_match_number {
if let Some(match_index) = match_map.get(&parent_num.u128()) {
if let Some(match_) = matches.get_mut(parent_num.u128() as usize - 1) {
// Update the match with the advancing team
let match_ = &mut matches[*match_index];
if match_.team_1.is_none() {
match_.team_1 = Some(team);
} else {
Expand All @@ -139,25 +89,24 @@ fn generate_matches(
}
} else {
// No byes, create match normally
let match_number = create_match(team_1, team_2, parent_match_number, matches);
match_map.insert(match_number.u128(), matches.len() - 1); // Map the match number to its index
let match_number =
create_match(team_1, team_2, parent_match_number, matches, None);

// Insert into layer
let layer_values = layer_map.entry(layer).or_default();
layer_values.insert(match_number.u128());
}
}
NestedArray::Nested(nested_vec) => {
let match_number = create_match(None, None, parent_match_number, matches);
match_map.insert(match_number.u128(), matches.len() - 1);
let match_number = create_match(None, None, parent_match_number, matches, None);

// Insert into layer
let layer_values = layer_map.entry(layer).or_default();
layer_values.insert(match_number.u128());

// Enqueue all nested elements
for nested_element in nested_vec {
queue.push_back((nested_element.clone(), Some(match_number), layer + 1));
queue.push_back((nested_element, Some(match_number), layer + 1));
}
}
}
Expand Down Expand Up @@ -189,6 +138,7 @@ fn create_match(
team_2: Option<Addr>,
parent_match_number: Option<Uint128>,
matches: &mut Vec<Match>,
is_losers_bracket: Option<bool>,
) -> Uint128 {
let match_number = Uint128::new(matches.len() as u128 + 1); // Assuming match numbers are sequential
matches.push(Match {
Expand All @@ -198,7 +148,7 @@ fn create_match(
result: None,
next_match_winner: parent_match_number,
next_match_loser: None,
is_losers_bracket: None,
is_losers_bracket,
});

match_number
Expand All @@ -210,20 +160,16 @@ fn generate_single_elimination_bracket(
tournament_id: u128,
play_third_place_match: bool,
) -> StdResult<()> {
// Get the bracket structure
let n = teams.len().next_power_of_two();
let sorted_indexes = nest_array(NestedArray::Single((0..n).collect::<Vec<_>>()));
let mut matches = vec![];
let mut matches_map = HashMap::new();
let mut layer_map = BTreeMap::new();

generate_matches(
&sorted_indexes,
teams,
&mut matches,
&mut matches_map,
&mut layer_map,
);
{
// Get the bracket structure
let n = teams.len().next_power_of_two();
let sorted_indexes = NestedArray::Single((0..n).collect::<Vec<_>>()).nest();

generate_matches(&sorted_indexes, teams, &mut matches, &mut layer_map);
}

// Optionally add a third place match
if play_third_place_match {
Expand Down Expand Up @@ -301,113 +247,115 @@ fn generate_double_elimination_bracket(
teams: &[Addr],
tournament_id: u128,
) -> StdResult<()> {
let mut match_number = 1u128;
let mut winners_match_numbers = Vec::new();
let mut losers_match_numbers = Vec::new();
let mut matches = HashMap::new();
let num_teams = teams.len();

// Generate leaf nodes first for the winners bracket
for i in (0..num_teams).step_by(2) {
let team_1 = teams.get(i).cloned();
let team_2 = teams.get(i + 1).cloned();

let match_info = Match {
match_number: Uint128::new(match_number),
team_1,
team_2,
result: None,
next_match_winner: None,
next_match_loser: None,
is_losers_bracket: Some(false),
};

winners_match_numbers.push(match_number);
matches.insert(match_number, match_info);
match_number += 1;
}
let mut matches = vec![];
let mut layer_map = BTreeMap::new();

// Generate the losers bracket and link matches
while winners_match_numbers.len() > 1 || !losers_match_numbers.is_empty() {
let mut next_round_winners = Vec::new();
let mut next_round_losers = Vec::new();
{
// Get the bracket structure
let n = teams.len().next_power_of_two();
let sorted_indexes = NestedArray::Single((0..n).collect::<Vec<_>>()).nest();

for i in (0..winners_match_numbers.len()).step_by(2) {
let match_1 = winners_match_numbers[i];
let match_2 = *winners_match_numbers.get(i + 1).unwrap_or(&match_1);
generate_matches(&sorted_indexes, teams, &mut matches, &mut layer_map);
}

let next_match_number = Uint128::new(match_number);
// Once we have the winner's bracket, we can generate the loser's bracket + additional matches
let mut next_layer_matches = BTreeSet::new();
let layers = layer_map.keys().rev().cloned().collect_vec();
for layer in layers {
// Add any new matches to the layer
layer_map

Check failure on line 266 in contracts/arena-tournament-module/src/execute.rs

View workflow job for this annotation

GitHub Actions / Lints

using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)`
.get_mut(&layer)
.and_then(|x| Some(x.append(&mut next_layer_matches)));

Check failure on line 268 in contracts/arena-tournament-module/src/execute.rs

View workflow job for this annotation

GitHub Actions / Lints

passing a unit value to a function
let layer_matches = layer_map[&layer].len();
let n = layer_matches.next_power_of_two();
let mut adjusted_matches = NestedArray::Single(
repeat(0u128)
.take(n - layer_matches)
.interleave(layer_map[&layer].iter().rev().cloned())
.collect_vec(),
)
.nest_flat();

// Create the base matches
let mut bye_match = None;
let mut prev_match = None;
dbg!(adjusted_matches.clone());
while !adjusted_matches.is_empty() {
let mut first = adjusted_matches.pop_front().unwrap_or(0);
let mut second = adjusted_matches.pop_front().unwrap_or(0);

// If either of the matches are byes, then queue up the match for pairing
let mut is_bye = (first == 0) ^ (second == 0);
if is_bye {
if let Some(match_) = bye_match {
// If a bye already exists, then let's just pair up these two byes
if first == 0 {
first = match_;
} else {
second = match_;
}

let match_info = Match {
match_number: next_match_number,
team_1: None,
team_2: None,
result: None,
next_match_winner: None,
next_match_loser: None,
is_losers_bracket: Some(false),
};
bye_match = None; // Clear the stored bye match
is_bye = false;
}
}

// Update previous matches to point to this one
if let Some(match_1_info) = matches.get_mut(&match_1) {
match_1_info.next_match_winner = Some(next_match_number);
// Create match
let match_number = create_match(
None,
None,
bye_match.map(Uint128::new),
&mut matches,
Some(true),
);
if let Some(bye) = bye_match {
next_layer_matches.insert(bye);
}

if match_1 != match_2 {
if let Some(match_2_info) = matches.get_mut(&match_2) {
match_2_info.next_match_winner = Some(next_match_number);
if let Some(prev) = prev_match {
// Handle the case where the bye happens in the last node
if bye_match.is_none() {
if is_bye {
matches[prev as usize - 1].next_match_winner = Some(match_number);
} else {
next_layer_matches.insert(prev);
}
next_layer_matches.insert(match_number.u128());
}
}

next_round_winners.push(match_number);
matches.insert(match_number, match_info);
match_number += 1;
}
winners_match_numbers = next_round_winners;

// Create losers bracket matches and link them
if !losers_match_numbers.is_empty() {
for i in (0..losers_match_numbers.len()).step_by(2) {
let match_1 = losers_match_numbers[i];
let match_2 = *losers_match_numbers.get(i + 1).unwrap_or(&match_1);

let next_match_number = Uint128::new(match_number);

let match_info = Match {
match_number: next_match_number,
team_1: None,
team_2: None,
result: None,
next_match_winner: None,
next_match_loser: None,
is_losers_bracket: Some(true),
};
prev_match = None;
bye_match = None;
} else {
prev_match = Some(match_number.u128());

// Update previous matches to point to this one
if let Some(match_1_info) = matches.get_mut(&match_1) {
match_1_info.next_match_winner = Some(next_match_number);
if is_bye {
bye_match = Some(match_number.u128());
}
}

if match_1 != match_2 {
if let Some(match_2_info) = matches.get_mut(&match_2) {
match_2_info.next_match_winner = Some(next_match_number);
}
// Update previous matches for next_match_loser
if first > 0 {
let i = first as usize - 1;
if matches[i].is_losers_bracket.is_none() {
matches[i].next_match_loser = Some(match_number);
} else {
matches[i].next_match_winner = Some(match_number);
}
}
if second > 0 {
let i = second as usize - 1;
if matches[i].is_losers_bracket.is_none() {
matches[i].next_match_loser = Some(match_number);
} else {
matches[i].next_match_winner = Some(match_number);
}

next_round_losers.push(match_number);
matches.insert(match_number, match_info);
match_number += 1;
}
losers_match_numbers = next_round_losers;
}
}

// Save all matches to storage
for (num, match_info) in matches {
MATCHES.save(deps.storage, (tournament_id, num), &match_info)?;
}
// dbg!(matches.clone());

Ok(())
save_matches(&mut matches, tournament_id, deps.storage)
}

pub fn process_matches(
Expand Down
2 changes: 2 additions & 0 deletions contracts/arena-tournament-module/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ mod error;
pub mod execute;
mod migrate;
pub mod msg;
mod nested_array;
pub mod query;
pub mod state;

pub use crate::error::ContractError;
pub use nested_array::NestedArray;
Loading

0 comments on commit a2a43cd

Please sign in to comment.