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