|  |  |  | @ -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])), | 
		
	
	
		
			
				
					|  |  |  | 
 |