|
|
|
@ -3,10 +3,9 @@
|
|
|
|
|
-behaviour(gen_server).
|
|
|
|
|
|
|
|
|
|
-export([start_link/1, init/1, handle_cast/2, handle_call/3, terminate/2]).
|
|
|
|
|
-export([get_tick/0, generate_bracket/1, print_bracket/1]).
|
|
|
|
|
-export([get_tick/0]).
|
|
|
|
|
|
|
|
|
|
-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);
|
|
|
|
@ -26,49 +25,6 @@ sql_begin(Database, Script) ->
|
|
|
|
|
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;"),
|
|
|
|
@ -122,14 +78,22 @@ init_db(Database) ->
|
|
|
|
|
[{primary_key, [team]},
|
|
|
|
|
{foreign_key, {[division], divisions, [division], "ON DELETE SET NULL"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, matches, [{division, text},
|
|
|
|
|
ok = sqlite3:create_table(Database, matches, [{division, text, [not_null]},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]}],
|
|
|
|
|
[{primary_key, [division, type, number]},
|
|
|
|
|
{check, "type IN ('practice', 'qualification', 'elimination', 'final')"},
|
|
|
|
|
{check, "type IN ('practice', 'qualification')"},
|
|
|
|
|
{foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, match_teams, [{division, text},
|
|
|
|
|
ok = sqlite3:create_table(Database, match_states, [{division, text, [not_null]},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{tick, integer, [not_null]},
|
|
|
|
|
{state, text, [not_null]}],
|
|
|
|
|
[{primary_key, [division, type, number, tick]},
|
|
|
|
|
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, match_teams, [{division, text, [not_null]},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{side, text, [not_null]},
|
|
|
|
@ -142,65 +106,84 @@ init_db(Database) ->
|
|
|
|
|
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[team], teams, [team], ""}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, elim_alliances, [{division, text},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
ok = sqlite3:create_table(Database, match_scores, [{division, text, [not_null]},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{history, integer, [not_null]},
|
|
|
|
|
{uuid, integer, [not_null]}],
|
|
|
|
|
[{primary_key, [division, type, number, history]},
|
|
|
|
|
{foreign_key, {[uuid], scores, [uuid], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[division, type, number],
|
|
|
|
|
matches,
|
|
|
|
|
[division, type, number],
|
|
|
|
|
"ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, brackets, [{division, text},
|
|
|
|
|
{number, integer, [not_null]}],
|
|
|
|
|
[{primary_key, [division, number]},
|
|
|
|
|
{foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, bracket_states, [{division, text},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{side, text, [not_null]},
|
|
|
|
|
{anum, integer, [not_null]}],
|
|
|
|
|
[{primary_key, [division, type, number, side]},
|
|
|
|
|
{tick, integer, [not_null]},
|
|
|
|
|
{state, text, [not_null]}],
|
|
|
|
|
[{primary_key, [division, number, tick]},
|
|
|
|
|
{foreign_key, {[division, number], brackets, [division, number], "ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, elimination_alliances, [{division, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{side, text, [not_null]},
|
|
|
|
|
{anum, integer, [not_null]}],
|
|
|
|
|
[{primary_key, [division, number, side]},
|
|
|
|
|
{check, "side IN ('red', 'blue')"},
|
|
|
|
|
{check, "type == 'elimination'"},
|
|
|
|
|
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[division, number], brackets, [division, number], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[division, anum], alliances, [division, number], ""}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, finals_alliances, [{division, text},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{side, text, [not_null]},
|
|
|
|
|
{adiv, text, [not_null]}],
|
|
|
|
|
[{primary_key, [division, type, number, side]},
|
|
|
|
|
[{primary_key, [division, number, side]},
|
|
|
|
|
{check, "side IN ('red', 'blue')"},
|
|
|
|
|
{check, "division IS NULL"},
|
|
|
|
|
{check, "type == 'final'"},
|
|
|
|
|
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[division, number], brackets, [division, number], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[adiv], winners, [division], ""}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, match_scores, [{division, text},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{instance, integer, [not_null]},
|
|
|
|
|
{history, integer, [not_null]},
|
|
|
|
|
{autonomous, integer, [not_null]},
|
|
|
|
|
{red_elevation_1, integer, [not_null]},
|
|
|
|
|
{red_elevation_2, integer, [not_null]},
|
|
|
|
|
{red_goal, integer, [not_null]},
|
|
|
|
|
{red_zone, integer, [not_null]},
|
|
|
|
|
{red_agoal, integer, [not_null]},
|
|
|
|
|
{red_azone, integer, [not_null]},
|
|
|
|
|
{red_1, text},
|
|
|
|
|
{red_2, text},
|
|
|
|
|
{blue_elevation_1, integer, [not_null]},
|
|
|
|
|
{blue_elevation_2, integer, [not_null]},
|
|
|
|
|
{blue_goal, integer, [not_null]},
|
|
|
|
|
{blue_zone, integer, [not_null]},
|
|
|
|
|
{blue_agoal, integer, [not_null]},
|
|
|
|
|
{blue_azone, integer, [not_null]},
|
|
|
|
|
{blue_1, text},
|
|
|
|
|
{blue_2, text}],
|
|
|
|
|
[{primary_key, [division, type, number, instance, history]},
|
|
|
|
|
ok = sqlite3:create_table(Database, bracket_scores, [{division, text},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{instance, integer, [not_null]},
|
|
|
|
|
{history, integer, [not_null]},
|
|
|
|
|
{uuid, integer, [not_null]}],
|
|
|
|
|
[{primary_key, [division, number, instance, history]},
|
|
|
|
|
{foreign_key, {[uuid], scores, [uuid], "ON DELETE CASCADE"}},
|
|
|
|
|
{foreign_key, {[division, number],
|
|
|
|
|
brackets,
|
|
|
|
|
[division, number],
|
|
|
|
|
"ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, scores, [{uuid, integer, [not_null]},
|
|
|
|
|
{autonomous, integer, [not_null]},
|
|
|
|
|
{red_elevation_1, integer, [not_null]},
|
|
|
|
|
{red_elevation_2, integer, [not_null]},
|
|
|
|
|
{red_goal, integer, [not_null]},
|
|
|
|
|
{red_zone, integer, [not_null]},
|
|
|
|
|
{red_agoal, integer, [not_null]},
|
|
|
|
|
{red_azone, integer, [not_null]},
|
|
|
|
|
{red_1, text},
|
|
|
|
|
{red_2, text},
|
|
|
|
|
{blue_elevation_1, integer, [not_null]},
|
|
|
|
|
{blue_elevation_2, integer, [not_null]},
|
|
|
|
|
{blue_goal, integer, [not_null]},
|
|
|
|
|
{blue_zone, integer, [not_null]},
|
|
|
|
|
{blue_agoal, integer, [not_null]},
|
|
|
|
|
{blue_azone, integer, [not_null]},
|
|
|
|
|
{blue_1, text},
|
|
|
|
|
{blue_2, text}],
|
|
|
|
|
[{primary_key, [uuid]},
|
|
|
|
|
{check, "red_1 IS NULL OR red_1 IN ('dq', 'no show')"},
|
|
|
|
|
{check, "red_2 IS NULL OR red_2 IN ('dq', 'no show')"},
|
|
|
|
|
{check, "blue_1 IS NULL OR blue_1 IN ('dq', 'no show')"},
|
|
|
|
|
{check, "blue_2 IS NULL OR blue_2 IN ('dq', 'no show')"},
|
|
|
|
|
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, match_states, [{division, text},
|
|
|
|
|
{type, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]},
|
|
|
|
|
{tick, integer, [not_null]},
|
|
|
|
|
{state, text, [not_null]}],
|
|
|
|
|
[{primary_key, [division, type, number, tick]},
|
|
|
|
|
{foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}}]),
|
|
|
|
|
{check, "blue_2 IS NULL OR blue_2 IN ('dq', 'no show')"}]),
|
|
|
|
|
|
|
|
|
|
ok = sqlite3:create_table(Database, alliances, [{division, text, [not_null]},
|
|
|
|
|
{number, integer, [not_null]}],
|
|
|
|
@ -257,7 +240,7 @@ write_division_matches(Database, Division, Type, Matches) ->
|
|
|
|
|
[Division, Round]),
|
|
|
|
|
lists:append([io_lib:format(
|
|
|
|
|
"INSERT INTO matches(division, type, number) VALUES ('~s', '~s', ~p);
|
|
|
|
|
INSERT INTO match_teams(division, type, number, side, anum, team) VALUES ('~s', '~s', ~p, 'red', 1, '~s');
|
|
|
|
|
INSERT INTO match_teams(division, type, number, side, anum, team) VALUES ('~s', '~s', ~p, 'red', 1, '~s');
|
|
|
|
|
INSERT INTO match_teams(division, type, number, side, anum, team) VALUES ('~s', '~s', ~p, 'red', 2, '~s');
|
|
|
|
|
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');",
|
|
|
|
@ -403,42 +386,33 @@ handle_call({delete_division, Division}, _, State) ->
|
|
|
|
|
handle_call({generate_practice, 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, practice, Matches)
|
|
|
|
|
end,
|
|
|
|
|
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,
|
|
|
|
|
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,
|
|
|
|
|
Bracket = schedule:generate_bracket(Size),
|
|
|
|
|
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)])]),
|
|
|
|
|
[
|
|
|
|
|
io_lib:format("DELETE FROM alliances WHERE division = '~s'; ", [Division]),
|
|
|
|
|
io_lib:format("DELETE FROM brackets WHERE division = '~s'; ", [Division]),
|
|
|
|
|
lists:append([io_lib:format("INSERT INTO alliances(division, number) VALUES('~s', ~p); ",
|
|
|
|
|
[Division, N])
|
|
|
|
|
|| N <- lists:seq(1, Size)]),
|
|
|
|
|
lists:append([io_lib:format("INSERT INTO brackets(division, number)
|
|
|
|
|
VALUES('~s', ~p);",
|
|
|
|
|
[Division, N])
|
|
|
|
|
|| N <- lists:seq(1, Size-1)])]),
|
|
|
|
|
|
|
|
|
|
{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, noimpl, State}.
|
|
|
|
|