diff --git a/src/database.erl b/src/database.erl index da9875a..7142df4 100644 --- a/src/database.erl +++ b/src/database.erl @@ -46,82 +46,97 @@ init([DatabaseFile]) -> end. init_db(Database) -> - ok = sqlite3:create_table(Database, divisions, [{division, integer, [not_null]}, - {psize, integer, [not_null]}, - {qsize, integer, [not_null]}, - {esize, integer, [not_null]}], + ok = sqlite3:create_table(Database, divisions, [{division, integer, [not_null]}], [{primary_key, [division]}]), ok = sqlite3:create_table(Database, teams, [{team, text, [not_null]}, {division, integer}, - {alliance, integer}, {inspection, blob}], [{primary_key, [team]}, {foreign_key, {[division], divisions, [division], ""}}]), - ok = sqlite3:create_table(Database, matches, [{division, integer, [not_null]}, + ok = sqlite3:create_table(Database, matches, [{division, integer}, {type, text, [not_null]}, {number, integer, [not_null]}, - {red_1, text, [not_null]}, - {red_2, text, [not_null]}, - {blue_1, text, [not_null]}, - {blue_2, text, [not_null]}], - [{primary_key, [division, type, number]}, - {check, "type IN ('practice', 'qualification', 'elimination')"}, - {foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}}, - {foreign_key, {[blue_1], teams, [team], ""}}, - {foreign_key, {[blue_2], teams, [team], ""}}, - {foreign_key, {[red_1], teams, [team], ""}}, - {foreign_key, {[red_2], teams, [team], ""}}]), + {instance, integer, [not_null]}], + [{primary_key, [division, type, number, instance]}, + {check, "type IN ('practice', 'qualification', 'elimination', 'final')"}, + {foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}}]), + + ok = sqlite3:create_table(Database, match_teams, [{division, integer, [not_null]}, + {type, text, [not_null]}, + {number, integer, [not_null]}, + {instance, integer, [not_null]}, + {side, text, [not_null]}, + {anum, integer, [not_null]}, + {team, text, [not_null]}], + [{primary_key, [division, type, number, instance, side, anum]}, + {check, "side IN ('red', 'blue')"}, + {check, "anum >= 1 AND anum <= 2"}, + {foreign_key, {[division, type, number, instance], matches, [division, type, number, instance], "ON DELETE CASCADE"}}, + {foreign_key, {[team], teams, [team], ""}}]), - ok = sqlite3:create_table(Database, match_scores, [{division, integer, [not_null]}, + ok = sqlite3:create_table(Database, match_scores, [{division, integer}, {type, text, [not_null]}, {number, integer, [not_null]}, {instance, integer, [not_null]}, - {red, integer, [not_null]}, - {blue, integer, [not_null]}, - {score, blob, [not_null]}], - [{primary_key, [division, type, number, instance]}, - {foreign_key, {[division, type, number], matches, [division, type, number], "ON DELETE CASCADE"}}]), - - ok = sqlite3:create_table(Database, match_states, [{division, 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]}, + {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, instance], matches, [division, type, number, instance], "ON DELETE CASCADE"}}]), + + ok = sqlite3:create_table(Database, match_states, [{division, integer}, {type, text, [not_null]}, {number, integer, [not_null]}, + {instance, 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, finals, [{number, integer, [not_null]}, - {red_1, text, [not_null]}, - {red_2, text, [not_null]}, - {blue_1, text, [not_null]}, - {blue_2, text, [not_null]}], - [{primary_key, [number]}, - {foreign_key, {[blue_1], teams, [team], ""}}, - {foreign_key, {[blue_2], teams, [team], ""}}, - {foreign_key, {[red_1], teams, [team], ""}}, - {foreign_key, {[red_2], teams, [team], ""}}]), - - - ok = sqlite3:create_table(Database, finals_scores, [{number, integer, [not_null]}, - {instance, integer, [not_null]}, - {red, integer, [not_null]}, - {blue, integer, [not_null]}, - {score, blob, [not_null]}], - [{primary_key, [number, instance]}, - {foreign_key, {[number], finals, [number], ""}}]), - - ok = sqlite3:create_table(Database, finals_states, [{number, integer, [not_null]}, - {tick, integer, [not_null]}, - {state, blob, [not_null]}], - [{primary_key, [number, tick]}, - {foreign_key, {[number], finals, [number], ""}}]), + [{primary_key, [division, type, number, instance, tick]}, + {foreign_key, {[division, type, number, instance], matches, [division, type, number, instance], "ON DELETE CASCADE"}}]), + + ok = sqlite3:create_table(Database, alliances, [{division, integer, [not_null]}, + {number, integer, [not_null]}, + {team1, text, [not_null]}, + {team2, text, [not_null]}], + [{primary_key, [division, number]}, + {foreign_key, {[team1], teams, [team], "ON DELETE CASCADE"}}, + {foreign_key, {[team2], teams, [team], "ON DELETE CASCADE"}}]), + + ok = sqlite3:create_table(Database, rankings, [{division, integer, [not_null]}, + {rank, integer, [not_null]}, + {team, text, [not_null]}], + [{primary_key, [division, rank]}, + {foreign_key, {[division], divisions, [division], "ON DELETE CASCADE"}}, + {foreign_key, {[team], teams, [team], "ON DELETE CASCADE"}}]), ok = sqlite3:create_table(Database, skills_scores, [{team, text, [not_null]}, {type, text, [not_null]}, {attempt, integer, [not_null]}, - {score, blob, [not_null]}], + {elevation, integer, [not_null]}, + {goal, integer, [not_null]}, + {zone, integer, [not_null]}, + {agoal, integer, [not_null]}, + {azone, integer, [not_null]}], [{primary_key, [team, type, attempt]}, {check, "type IN ('driver', 'autonomous')"}, {foreign_key, {[team], teams, [team], ""}}]), @@ -153,12 +168,17 @@ assign_divisions(Database, Teams) -> first_error(sqlite3:sql_exec_script(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 = ~p AND type = '~s';", - [Division, atom_to_list(Type)]), + [Division, Round]), lists:append([io_lib:format( - "INSERT INTO matches(division, type, number, blue_1, blue_2, red_1, red_2) VALUES (~p, '~s', ~p, '~s', '~s', '~s', '~s'); ", - [Division, atom_to_list(Type), N, B1, B2, R1, R2]) + "INSERT INTO matches(division, type, number, instance) VALUES (~p, '~s', ~p, 1); + INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'red', 1, '~s'); + INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'red', 2, '~s'); + INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, 'blue', 1, '~s'); + INSERT INTO match_teams(division, type, number, instance, side, anum, team) VALUES (~p, '~s', ~p, 1, '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)). @@ -222,77 +242,43 @@ handle_call({delete_teams, Removed}, _, State) -> ok = delete_teams(State#state.database, Removed), {reply, ok, State}; -handle_call({match_score, Division, Round, Number, Red, Blue, Score}, _, State) -> +handle_call({match_score, Division, Round, Number, Instance, Red, Blue, Score}, _, State) -> [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database, io_lib:format( - "SELECT instance FROM match_scores WHERE Division = ~p AND type = '~s' AND number = ~p;", - [Division, atom_to_list(Round), Number])), - Instances = get_column("instance", Columns, Rows), + "SELECT history FROM match_scores WHERE Division = ~p AND type = '~s' AND number = ~p AND instance = ~p;", + [Division, atom_to_list(Round), Number, Instance])), + Instances = get_column("history", Columns, Rows), NextInstance = if length(Instances) == 0 -> 1; true -> lists:max(Instances) + 1 end, ok = first_error(sqlite3:sql_exec_script(State#state.database, lists:append( - [io_lib:format("INSERT INTO match_scores(division, type, number, instance, red, blue, score) VALUES(~p, '~s', ~p, ~p, ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, NextInstance, Red, Blue, Score]), - io_lib:format("INSERT INTO match_states(division, type, number, tick, state) VALUES(~p, '~s', ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, get_tick(State), "scored"])]))), + [io_lib:format("INSERT INTO match_scores(division, type, number, instance, history, red, blue, score) VALUES(~p, '~s', ~p, ~p, ~p, ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, Instance, NextInstance, Red, Blue, Score]), + io_lib:format("INSERT INTO match_states(division, type, number, instance, tick, state) VALUES(~p, '~s', ~p, ~p, ~p, '~s');", [Division, atom_to_list(Round), Number, Instance, get_tick(State), "scored"])]))), {reply, ok, State}; -handle_call({match_state, Division, Round, Number, MatchState}, _, State) -> +handle_call({match_state, Division, Round, Number, Instance, MatchState}, _, State) -> {rowid, _} = sqlite3:sql_exec(State#state.database, - io_lib:format("INSERT INTO match_states(division, type, number, tick, state) VALUES(~p, '~s', ~p, ~p, '~s');", - [Division, atom_to_list(Round), Number, get_tick(State), MatchState])), + io_lib:format("INSERT INTO match_states(division, type, number, instance, tick, state) VALUES(~p, '~s', ~p, ~p, ~p, '~s');", + [Division, atom_to_list(Round), Number, Instance, get_tick(State), MatchState])), {reply, ok, State}; -handle_call({finals_score, Number, Red, Blue, Score}, _, State) -> - [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database, - io_lib:format( - "SELECT instance FROM finals_scores WHERE number = ~p;", - [Number])), - Instances = get_column("instance", Columns, Rows), - NextInstance = if length(Instances) == 0 -> 1; - true -> lists:max(Instances) + 1 - end, - ok = first_error(sqlite3:sql_exec_script(State#state.database, lists:append( - [io_lib:format("INSERT INTO finals_scores(number, instance, red, blue, score) VALUES(~p, ~p, ~p, ~p, '~s');", [Number, NextInstance, Red, Blue, Score]), - io_lib:format("INSERT INTO finals_states(number, tick, state) VALUES(~p, ~p, '~s');", [Number, get_tick(State), "scored"])]))), - {reply, ok, State}; -handle_call({finals_state, Number, MatchState}, _, State) -> - {rowid, _} = sqlite3:sql_exec(State#state.database, - io_lib:format("INSERT INTO finals_states(number, tick, state) VALUES(~p, ~p, '~s');", - [Number, get_tick(State), MatchState])), - {reply, ok, State}; - -handle_call({add_division, PSize, QSize, ESize}, _, State) -> +handle_call(add_division, _, State) -> [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(State#state.database, "SELECT division FROM divisions;"), Divisions = lists:sort(get_column("division", Columns, Rows)), NextDivision = first_empty([0 | Divisions]), {rowid, _} = sqlite3:sql_exec(State#state.database, - io_lib:format("INSERT INTO divisions(division, psize, qsize, esize) VALUES(~p, ~p, ~p, ~p);", - [NextDivision, PSize, QSize, ESize])), - {reply, ok, State}; -handle_call({edit_division, Division, PSize, QSize, ESize}, _, State) -> - ok = sqlite3:sql_exec(State#state.database, - io_lib:format( - "UPDATE divisions SET psize = ~p, qsize = ~p, esize = ~p WHERE division = ~p;", - [PSize, QSize, ESize, Division])), + io_lib:format("INSERT INTO divisions(division) VALUES(~p);", + [NextDivision])), {reply, ok, State}; handle_call({delete_division, Division}, _, State) -> ok = first_error(sqlite3:sql_exec_script(State#state.database, io_lib:format("UPDATE teams SET division = NULL WHERE division = ~p; DELETE FROM divisions WHERE division = ~p;", [Division, Division]))), {reply, ok, State}; -handle_call({generate_division, Division, Round, Seed}, _, State) -> - Size = get_div_size(State#state.database, Division, Round), +handle_call({generate_division, Division, Round, Size, Seed}, _, State) -> Teams = get_div_teams(State#state.database, Division), io:fwrite("Generating division ~p/~s with teams ~p, size ~p, and seed ~p~n", [Division, Round, Teams, Size, Seed]), Matches = schedule:create(Seed, Size, Teams), ok = write_division_matches(State#state.database, Division, Round, Matches), {reply, ok, State}. -get_div_size(Database, Division, Round) -> - Column = if Round =:= practice -> "psize"; - Round =:= qualification -> "qsize" - end, - [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, io_lib:format("SELECT ~s FROM divisions WHERE division = ~p;", [Column, Division])), - [Size] = get_column(Column, Columns, Rows), - Size. - get_div_teams(Database, Division) -> [{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, io_lib:format("SELECT team FROM teams WHERE division = ~p;", [Division])), get_column("team", Columns, Rows).