From 2daec3e90b234ffa36db3cdf2c7d51c79234f0bc Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Wed, 31 Jan 2024 22:20:09 -0700 Subject: [PATCH] Playing around with functions to generate brackets --- src/database.erl | 150 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 115 insertions(+), 35 deletions(-) diff --git a/src/database.erl b/src/database.erl index 4a7bda1..9702ce3 100644 --- a/src/database.erl +++ b/src/database.erl @@ -3,13 +3,72 @@ -behaviour(gen_server). -export([start_link/1, init/1, handle_cast/2, handle_call/3, terminate/2]). --export([get_tick/0]). +-export([get_tick/0, generate_bracket/1, print_bracket/1]). -record(state, {database_file = none, database = none, tick_start = none}). +-record(bracket, {depth = none, num = none, red = none, blue = none}). + +first_error([]) -> ok; +first_error([ok | Rest]) -> first_error(Rest); +first_error([{rowid, _} | Rest]) -> first_error(Rest); +first_error([Error | _]) -> Error. + +sql_begin(Database, Script) -> + ok = sqlite3:sql_exec(Database, "BEGIN;"), + Result = sqlite3:sql_exec_script(Database, Script), + case first_error(Result) of + ok -> sqlite3:sql_exec(Database, "COMMIT;"); + _ -> + ok = sqlite3:sql_exec(Database, "ROLLBACK;"), + Result + end. start_link(DatabaseFile) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [DatabaseFile], []). +higher_pow2(N) -> round(math:pow(2, ceil(math:log2(N)))). + +first_layer(BracketSize, _, N) when N > BracketSize/2 -> []; +first_layer(BracketSize, Size, N) when BracketSize - N + 1 > Size -> + [N | first_layer(BracketSize, Size, N+1)]; +first_layer(BracketSize, Size, N) -> + [{N, BracketSize - N + 1} | first_layer(BracketSize, Size, N+1)]. + +first_layer(Size) when Size > 1 -> + BracketSize = higher_pow2(Size), + first_layer(BracketSize, Size, 1). + +merge_layer([Element], []) -> Element; +merge_layer([], Merged) -> + Final = lists:reverse(Merged), + merge_layer(Final); +merge_layer([First | Rest], Merged) -> + Last = lists:last(Rest), + List = lists:droplast(Rest), + merge_layer(List, [{First, Last} | Merged]). +merge_layer(List) -> merge_layer(List, []). + +label_bracket({Left, Right}, [N | Counters], Depth) -> + {LC, L} = label_bracket(Left, Counters, Depth+1), + {RC, R} = label_bracket(Right, LC, Depth+1), + {[N+1 | RC], #bracket{depth = Depth, num=N, red=L, blue=R}}; +label_bracket(Elem, Counters, _) -> {Counters, Elem}. + +label_bracket(Bracket, Depth) -> + {_, Labeled} = label_bracket(Bracket, lists:duplicate(Depth, 1), 1), + Labeled. + +generate_bracket(Size) -> + label_bracket(merge_layer(first_layer(Size)), ceil(math:log2(Size))). + +print_bracket(Bracket) -> + RF = fun(R,L) when R == element(1,Bracket) -> + Flds = record_info(fields, bracket), + true = (L == length(Flds)), + Flds + end, + io:fwrite([io_lib_pretty:print(Bracket, RF),"\n"]). + open_db(DatabaseFile) -> {ok, DB} = sqlite3:open(event_db, [{file, DatabaseFile}]), ok = sqlite3:sql_exec(DB, "PRAGMA foreign_keys = ON;"), @@ -174,35 +233,27 @@ init_db(Database) -> {foreign_key, {[team], teams, [team], ""}}]), ok. -first_error([]) -> ok; -first_error([ok | Rest]) -> first_error(Rest); -first_error([{rowid, _} | Rest]) -> first_error(Rest); -first_error([Error | _]) -> Error. - delete_teams(Database, Teams) -> - SQL = lists:append(["BEGIN; ", - lists:append([io_lib:format("DELETE FROM teams WHERE team = ~p; ", - [Team]) || Team <- Teams]), - "COMMIT;"]), - first_error(sqlite3:sql_exec_script(Database, SQL)). + SQL = lists:append([io_lib:format("DELETE FROM teams WHERE team = ~p; ", + [Team]) + || Team <- Teams]), + sql_begin(Database, SQL). add_teams(Database, Teams) -> - SQL = lists:append(["BEGIN; ", - lists:append([io_lib:format("INSERT INTO teams(team) VALUES('~s') ON CONFLICT(team) DO NOTHING; ", - [Team]) || Team <- Teams]), - "COMMIT;"]), - first_error(sqlite3:sql_exec_script(Database, SQL)). + SQL = lists:append([io_lib:format("INSERT INTO teams(team) VALUES('~s') ON CONFLICT(team) DO NOTHING; ", + [Team]) + || Team <- Teams]), + sql_begin(Database, SQL). assign_divisions(Database, Teams) -> - SQL = lists:append(["BEGIN; ", - lists:append([io_lib:format("UPDATE teams SET division = '~s' WHERE team = '~s'; ", [Division, Team]) || {Team, Division} <- Teams]), - "COMMIT;"]), - first_error(sqlite3:sql_exec_script(Database, SQL)). + SQL = lists:append([io_lib:format("UPDATE teams SET division = '~s' WHERE team = '~s'; ", + [Division, Team]) + || {Team, Division} <- Teams]), + sql_begin(Database, SQL). write_division_matches(Database, Division, Type, Matches) -> Round = atom_to_list(Type), - SQL = lists:append(["BEGIN; ", - io_lib:format("DELETE FROM matches WHERE division = '~s' AND type = '~s';", + SQL = lists:append([io_lib:format("DELETE FROM matches WHERE division = '~s' AND type = '~s'; ", [Division, Round]), lists:append([io_lib:format( "INSERT INTO matches(division, type, number) VALUES ('~s', '~s', ~p); @@ -211,9 +262,8 @@ write_division_matches(Database, Division, Type, Matches) -> INSERT INTO match_teams(division, type, number, side, anum, team) VALUES ('~s', '~s', ~p, 'blue', 1, '~s'); INSERT INTO match_teams(division, type, number, side, anum, team) VALUES ('~s', '~s', ~p, 'blue', 2, '~s');", [Division, Round, N, Division, Round, N, B1, Division, Round, N, B2, Division, Round, N, R1, Division, Round, N, R2]) - || {N, [B1, B2, R1, R2]} <- lists:enumerate(Matches)]), - "COMMIT;"]), - first_error(sqlite3:sql_exec_script(Database, SQL)). + || {N, [B1, B2, R1, R2]} <- lists:enumerate(Matches)])]), + sql_begin(Database, SQL). index_of(Item, List) -> index_of(Item, List, 1). index_of(Item, [Element | _], N) when Item == Element -> N; @@ -278,8 +328,6 @@ handle_call({load_db, DatabaseFile}, _, State) -> end, Teams))} || {Name} <- Divisions], - [_, {rows, Alliances}] = sqlite3:sql_exec(Database, "SELECT team, div"), - % Read matches and match_teams tables and publish to match/{div}/{round}/{number} % Read match_sates and publish to match/{div}/{round}/{number}/state % Read match_scores and publish to match/{div}/{round}/{number}/state @@ -352,16 +400,48 @@ handle_call({delete_division, Division}, _, State) -> DELETE FROM divisions WHERE division = '~s';", [Division, Division]))), {reply, ok, State}; -handle_call({generate_division, Division, Round, Size}, _, State) -> +handle_call({generate_practice, Division, Size}, _, State) -> Teams = get_div_teams(State#state.database, Division), - Seed = rand:uniform(10000000), - io:fwrite("Generating division ~s/~s with teams ~p, size ~p, and seed ~p~n", [Division, Round, Teams, Size, Seed]), - case schedule:create(Seed, Size, Teams) of - error -> {reply, error, State}; - Matches -> {reply, write_division_matches(State#state.database, Division, Round, Matches), State} - end; + Resp = case schedule:create(rand:uniform(10000000), Size, Teams) of + error -> error; + Matches -> write_division_matches(State#state.database, Division, practice, Matches) + end, + {reply, Resp, State}; +handle_call({generate_qualification, Division, Size}, _, State) -> + Teams = get_div_teams(State#state.database, Division), + Resp = case schedule:create(rand:uniform(10000000), Size, Teams) of + error -> error; + Matches -> write_division_matches(State#state.database, Division, qualification, Matches) + end, + {reply, Resp, State}; +handle_call({generate_elimination, Division, Size}, _, State) -> + BracketSize = higher_pow2(Size), + Total = Size-1, + SQL = lists:append( + [ + io_lib:format("DELETE FROM alliances WHERE division = '~s'; ", [Division]), + io_lib:format("DELETE FROM matches WHERE division = '~s' AND type = 'elimination'; ", [Division]), + lists:append([io_lib:format("INSERT INTO alliances(division, number) VALUES('~s', ~p); ", + [Division, N]) + || N <- lists:seq(1, Total)]), + lists:append([io_lib:format("INSERT INTO matches(division, type, number) + VALUES('~s', 'elimination', ~p); + INSERT INTO elim_alliances(division, type, number, side, anum) + VALUES('~s', 'elimination', ~p, 'red', ~p); + INSERT INTO elim_alliances(division, type, number, side, anum) + VALUES('~s', 'elimination', ~p, 'blue', ~p);", + [Division, N, Division, N, BracketSize/2+N+1, Division, N, BracketSize/2-N]) + || N <- lists:seq(1, Size - BracketSize/2)]), + lists:append([io_lib:format("INSERT INTO elim_alliances(division, type, number, side, anum) + VALUES('~s', 'elimination', ~p, 'red', ~p);", + [Division, N, BracketSize/2-N+1]) + || N <- lists:seq(Size - BracketSize/2+1, BracketSize/2)])]), + {reply, sql_begin(State#state.database, SQL), State}; +handle_call(generate_finals, _, State) -> + [{columns, _}, {rows, [{Size}]}] = sqlite3:sql_exec(State#state.database, "SELECT COUNT(*) FROM divisions;"), + {reply, noimpl, State}; handle_call(_, _, State) -> - {reply, nofunc, State}. + {reply, noimpl, State}. get_div_teams(Database, Division) -> [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, io_lib:format("SELECT team FROM teams WHERE division = ~p;", [Division])),