Added configuration for match generation that's stored in schedule state

main
noah metz 2024-01-27 00:58:08 -07:00
parent 01ddf903b4
commit 013dd3c614
1 changed files with 42 additions and 81 deletions

@ -4,8 +4,12 @@
-export([start_link/0, init/1, handle_cast/2, handle_call/3]).
-export([set_test_config/0]).
-include("erlk.hrl").
-record(division, {teams = [], practice = 0, qualification = 0}).
-record(config, {divisions = []}).
-record(state, {database_file = none, database = none, teams = [], config = none}).
start_link() ->
@ -39,49 +43,41 @@ fill_padding_schedule(Matches, Teams) ->
{Schedule, _} = lists:mapfoldl(fun fill_padding_match/2, TeamsRandom, Matches),
Schedule.
hash_teams_list(Teams) ->
[ Y || <<X:4>> <= crypto:hash(sha, Teams), Y <- integer_to_list(X,16)].
init_db(Database) ->
ok = sqlite3:create_table(Database, teams, [{name, text, [not_null]},
{inspection, blob}],
[{primary_key, [name]}]),
ok = sqlite3:create_table(Database, matches, [{division, integer, [not_null]},
{round, text, [not_null]},
{size, integer, [not_null]},
{teams, text, [not_null]},
{number, integer, [not_null]},
{seed, integer, [not_null]},
{genid, text, [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, round, size, teams, number, seed]},
[{primary_key, [division, round, number, genid]},
{check, "round IN ('practice', 'qualification', 'elimination')"},
{foreign_key, {[blue_1], teams, [name], "ON DELETE RESTRICT"}},
{foreign_key, {[blue_2], teams, [name], "ON DELETE RESTRICT"}},
{foreign_key, {[red_1], teams, [name], "ON DELETE RESTRICT"}},
{foreign_key, {[red_2], teams, [name], "ON DELETE RESTRICT"}}]),
ok = sqlite3:create_table(Database, match_scores, [{division, integer, [not_null]},
{round, text, [not_null]},
{size, integer, [not_null]},
{teams, text, [not_null]},
{number, integer, [not_null]},
{seed, integer, [not_null]},
{genid, text, [not_null]},
{instance, integer, [not_null]},
{score, blob, [not_null]}],
[{primary_key, [division, round, size, teams, number, seed, instance]},
{foreign_key, {[division, round, size, teams, number, seed], matches, [division, round, size, teams, number, seed], "ON DELETE CASCADE"}}]),
[{primary_key, [division, round, number, genid, instance]},
{foreign_key, {[division, round, number, genid], matches, [division, round, number, genid], "ON DELETE CASCADE"}}]),
ok = sqlite3:create_table(Database, match_states, [{division, integer, [not_null]},
{round, text, [not_null]},
{size, integer, [not_null]},
{teams, text, [not_null]},
{number, integer, [not_null]},
{seed, integer, [not_null]},
{genid, text, [not_null]},
{time, integer, [not_null]},
{state, blob, [not_null]}],
[{primary_key, [division, round, size, teams, number, seed, time]},
{foreign_key, {[division, round, size, teams, number, seed], matches, [division, round, size, teams, number, seed], "ON DELETE CASCADE"}}]),
[{primary_key, [division, round, number, genid, time]},
{foreign_key, {[division, round, number, genid], matches, [division, round, number, genid], "ON DELETE CASCADE"}}]),
% TODO: finals
% ok = sqlite3:create_table(Database, finals, [], [{primary_key, []}]),
@ -111,21 +107,19 @@ first_error([]) -> ok;
first_error([ok | Rest]) -> first_error(Rest);
first_error([Error | _]) -> Error.
delete_matches(Database, Division, Size, Seed, TeamsHash, Round) ->
SQL = io_lib:format("DELETE FROM matches WHERE division = ~p AND size = ~p AND seed = ~p AND teams = '~s' AND round = '~p'", [Division, Size, Seed, TeamsHash, Round]),
delete_matches(Database, Division, Round, GenID) ->
SQL = io_lib:format("DELETE FROM matches WHERE division = ~p AND genid = ~p AND round = '~p'", [Division, GenID, Round]),
sqlite3:sql_exec_script(Database, SQL).
write_matches(Database, Division, Size, Seed, TeamsHash, Round, MatchTeams) ->
write_matches(Database, Division, Round, GenID, Matches) ->
Schedule = [[{division, Division},
{round, atom_to_list(Round)},
{size, Size},
{teams, TeamsHash},
{number, N},
{seed, Seed},
{genid, GenID},
{blue_1, B1},
{blue_2, B2},
{red_1, R1},
{red_2, R2}] || {N, [B1, B2, R1, R2]} <- lists:enumerate(MatchTeams)],
{red_2, R2}] || {N, [B1, B2, R1, R2]} <- lists:enumerate(Matches)],
[ok | Resp] = sqlite3:write_many(Database, matches, Schedule),
lists:last(Resp).
@ -152,6 +146,28 @@ pick_matches(Teams, Matches) ->
pick_matches(Teams) -> pick_matches(Teams, []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(TEST_CONFIG, #config{divisions = [#division{teams = ["A", "B", "C", "D"], practice = 1}]}).
set_test_config() ->
gen_server:call(?MODULE, {set_config, ?TEST_CONFIG}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_call({set_config, Config}, _, State) when is_record(Config, config) ->
{reply, ok, State#state{config = Config}};
handle_call({new_schedule, Division, Round, Seed}, _, State) ->
DivInfo = lists:nth(Division, State#state.config#config.divisions),
Teams = DivInfo#division.teams,
Matches = case Round of
practice when DivInfo#division.practice =/= 0 ->
DivInfo#division.practice;
qualification when DivInfo#division.qualification =/= 0->
DivInfo#division.qualification
end,
MatchTeams = create_schedule(Seed, Matches, Teams),
GenID = lists:sublist([ Y || <<X:4>> <= crypto:hash(sha, Teams ++ [Seed, Matches]),
Y <- integer_to_list(X, 16)], 4),
ok = write_matches(State#state.database, Division, Round, GenID, MatchTeams),
{reply, GenID, State};
handle_call({new_db, DatabaseFile}, _, State) ->
ok = if State#state.database =:= none -> ok;
true -> sqlite3:close(State#state.database)
@ -183,61 +199,6 @@ handle_cast({delete_teams, Removed}, State) ->
handle_cast({add_teams, Teams}, State) ->
ok = first_error(add_teams(State#state.database, Teams)),
{noreply, State#state{teams = Teams}};
handle_cast({new_schedule, Division, Size, Seed, Teams, Round}, State) ->
MatchTeams = create_schedule(Seed, Size, Teams),
ok = write_matches(State#state.database, Division, Size, Seed, hash_teams_list(Teams), Round, MatchTeams),
{noreply, State};
handle_cast({delete_schedule, Division, Size, Seed, TeamsHash, Round}, State) ->
ok = first_error(delete_matches(State#state.database, Division, Size, Seed, TeamsHash, Round)),
handle_cast({delete_schedule, Division, Round, GenID}, State) ->
ok = first_error(delete_matches(State#state.database, Division, Round, GenID)),
{noreply, State}.
get_matches_from_db(Database, Division, Round, Seed, Size, TeamsHash) ->
if Size == 0 -> [];
Size > 0 -> SQL = io_lib:format("SELECT number, blue_1, blue_2, red_1, red_2 FROM
matches WHERE
division = '~p' AND
round = '~p' AND
size = '~p' AND
seed = '~p' AND
teams = '~s';",
[Division, Round, Size, Seed, TeamsHash]),
[{columns, Columns}, {rows, Rows}] = sqlite3:sql_exec(Database, SQL),
[]
end.
parse_division(Division) ->
TeamsBinary = maps:get(<<"teams">>, Division),
TeamsList = [binary_to_list(X) || X <- TeamsBinary],
TeamsHash = schedule:hash_teams_list(TeamsList),
PracticeRounds = maps:get(<<"practice_rounds">>, Division),
PracticeSeed = maps:get(<<"practice_seed">>, Division),
QualificationRounds = maps:get(<<"qualification_rounds">>, Division),
QualificationSeed = maps:get(<<"qualification_seed">>, Division),
#division_config{
fields = [binary_to_list(X) || X <- maps:get(<<"fields">>, Division)],
elimination_alliances = maps:get(<<"elimination_alliances">>, Division),
practice_rounds = PracticeRounds,
practice_seed = PracticeSeed,
qualification_rounds = QualificationRounds,
qualification_seed = QualificationSeed,
teams = TeamsList,
teams_hash = TeamsHash
}.
parse_event_config(ConfigJSON) ->
Config = jsone:decode(list_to_binary(ConfigJSON)),
Divisions = lists:map(fun parse_division/1, maps:get(<<"divisions">>, Config)),
TestingFields = [binary_to_list(X) || X <- maps:get(<<"testing_fields">>, Config)],
SkillsFields = [binary_to_list(X) || X <- maps:get(<<"skills_fields">>, Config)],
FinalsFields = [binary_to_list(X) || X <- maps:get(<<"finals_fields">>, Config)],
#event_config{
database_file = binary_to_list(maps:get(<<"database">>, Config)),
finals_fields = FinalsFields,
skills_fields = SkillsFields,
testing_fields = TestingFields,
divisions = Divisions
}.