@ -4,8 +4,12 @@
- export ( [ start_link / 0 , init / 1 , handle_cast / 2 , handle_call / 3 ] ) .
- export ( [ start_link / 0 , init / 1 , handle_cast / 2 , handle_call / 3 ] ) .
- export ( [ set_test_config / 0 ] ) .
- include ( " erlk.hrl " ) .
- include ( " erlk.hrl " ) .
- record ( division , { teams = [ ] , practice = 0 , qualification = 0 } ) .
- record ( config , { divisions = [ ] } ) .
- record ( state , { database_file = none , database = none , teams = [ ] , config = none } ) .
- record ( state , { database_file = none , database = none , teams = [ ] , config = none } ) .
start_link ( ) - >
start_link ( ) - >
@ -39,49 +43,41 @@ fill_padding_schedule(Matches, Teams) ->
{ Schedule , _ } = lists : mapfoldl ( fun fill_padding_match / 2 , TeamsRandom , Matches ) ,
{ Schedule , _ } = lists : mapfoldl ( fun fill_padding_match / 2 , TeamsRandom , Matches ) ,
Schedule .
Schedule .
hash_teams_list ( Teams ) - >
[ Y | | < < X : 4 > > < = crypto : hash ( sha , Teams ) , Y < - integer_to_list ( X , 16 ) ] .
init_db ( Database ) - >
init_db ( Database ) - >
ok = sqlite3 : create_table ( Database , teams , [ { name , text , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , teams , [ { name , text , [ not_null ] } ,
{ inspection , blob } ] ,
{ inspection , blob } ] ,
[ { primary_key , [ name ] } ] ) ,
[ { primary_key , [ name ] } ] ) ,
ok = sqlite3 : create_table ( Database , matches , [ { division , integer , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , matches , [ { division , integer , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed, integer , [ not_null ] } ,
{ genid , text , [ not_null ] } ,
{ red_1 , text , [ not_null ] } ,
{ red_1 , text , [ not_null ] } ,
{ red_2 , text , [ not_null ] } ,
{ red_2 , text , [ not_null ] } ,
{ blue_1 , text , [ not_null ] } ,
{ blue_1 , text , [ not_null ] } ,
{ blue_2 , text , [ not_null ] } ] ,
{ blue_2 , text , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , see d] } ,
[ { primary_key , [ division , round , number , geni d] } ,
{ check , " round IN ('practice', 'qualification', 'elimination') " } ,
{ check , " round IN ('practice', 'qualification', 'elimination') " } ,
{ foreign_key , { [ blue_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ blue_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ blue_2 ] , 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_1 ] , teams , [ name ] , " ON DELETE RESTRICT " } } ,
{ foreign_key , { [ red_2 ] , 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 ] } ,
ok = sqlite3 : create_table ( Database , match_scores , [ { division , integer , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed, integer , [ not_null ] } ,
{ genid , text , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ instance , integer , [ not_null ] } ,
{ score , blob , [ not_null ] } ] ,
{ score , blob , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , see d, instance ] } ,
[ { primary_key , [ division , round , number , geni d, instance ] } ,
{ foreign_key , { [ division , round , size , teams , number , see d] , matches , [ division , round , size , teams , number , see d] , " ON DELETE CASCADE " } } ] ) ,
{ foreign_key , { [ division , round , number , geni d] , matches , [ division , round , number , geni d] , " ON DELETE CASCADE " } } ] ) ,
ok = sqlite3 : create_table ( Database , match_states , [ { division , integer , [ not_null ] } ,
ok = sqlite3 : create_table ( Database , match_states , [ { division , integer , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ round , text , [ not_null ] } ,
{ size , integer , [ not_null ] } ,
{ teams , text , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ number , integer , [ not_null ] } ,
{ seed, integer , [ not_null ] } ,
{ genid , text , [ not_null ] } ,
{ time , integer , [ not_null ] } ,
{ time , integer , [ not_null ] } ,
{ state , blob , [ not_null ] } ] ,
{ state , blob , [ not_null ] } ] ,
[ { primary_key , [ division , round , size , teams , number , see d, time ] } ,
[ { primary_key , [ division , round , number , geni d, time ] } ,
{ foreign_key , { [ division , round , size , teams , number , see d] , matches , [ division , round , size , teams , number , see d] , " ON DELETE CASCADE " } } ] ) ,
{ foreign_key , { [ division , round , number , geni d] , matches , [ division , round , number , geni d] , " ON DELETE CASCADE " } } ] ) ,
% TODO : finals
% TODO : finals
% ok = sqlite3 : create_table ( Database , finals , [ ] , [ { primary_key , [ ] } ] ) ,
% ok = sqlite3 : create_table ( Database , finals , [ ] , [ { primary_key , [ ] } ] ) ,
@ -111,21 +107,19 @@ first_error([]) -> ok;
first_error ( [ ok | Rest ] ) - > first_error ( Rest ) ;
first_error ( [ ok | Rest ] ) - > first_error ( Rest ) ;
first_error ( [ Error | _ ] ) - > Error .
first_error ( [ Error | _ ] ) - > Error .
delete_matches ( Database , Division , Size, Seed , TeamsHash , Round ) - >
delete_matches ( Database , Division , Round, GenID ) - >
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 ] ) ,
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 ) .
sqlite3 : sql_exec_script ( Database , SQL ) .
write_matches ( Database , Division , Size, Seed , TeamsHash , Round , MatchTeam s) - >
write_matches ( Database , Division , Round, GenID , Matche s) - >
Schedule = [ [ { division , Division } ,
Schedule = [ [ { division , Division } ,
{ round , atom_to_list ( Round ) } ,
{ round , atom_to_list ( Round ) } ,
{ size , Size } ,
{ teams , TeamsHash } ,
{ number , N } ,
{ number , N } ,
{ seed, Seed } ,
{ genid , GenID } ,
{ blue_1 , B1 } ,
{ blue_1 , B1 } ,
{ blue_2 , B2 } ,
{ blue_2 , B2 } ,
{ red_1 , R1 } ,
{ red_1 , R1 } ,
{ red_2 , R2 } ] | | { N , [ B1 , B2 , R1 , R2 ] } < - lists : enumerate ( Match T eam s) ] ,
{ red_2 , R2 } ] | | { N , [ B1 , B2 , R1 , R2 ] } < - lists : enumerate ( Match es) ] ,
[ ok | Resp ] = sqlite3 : write_many ( Database , matches , Schedule ) ,
[ ok | Resp ] = sqlite3 : write_many ( Database , matches , Schedule ) ,
lists : last ( Resp ) .
lists : last ( Resp ) .
@ -152,6 +146,28 @@ pick_matches(Teams, Matches) ->
pick_matches ( Teams ) - > pick_matches ( Teams , [ ] ) .
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 ) - >
handle_call ( { new_db , DatabaseFile } , _ , State ) - >
ok = if State #state.database =:= none - > ok ;
ok = if State #state.database =:= none - > ok ;
true - > sqlite3 : close ( State #state.database )
true - > sqlite3 : close ( State #state.database )
@ -183,61 +199,6 @@ handle_cast({delete_teams, Removed}, State) ->
handle_cast ( { add_teams , Teams } , State ) - >
handle_cast ( { add_teams , Teams } , State ) - >
ok = first_error ( add_teams ( State #state.database , Teams ) ) ,
ok = first_error ( add_teams ( State #state.database , Teams ) ) ,
{ noreply , State #state { teams = Teams } } ;
{ noreply , State #state { teams = Teams } } ;
handle_cast ( { new_schedule , Division , Size , Seed , Teams , Round } , State ) - >
handle_cast ( { delete_schedule , Division , Round , GenID } , State ) - >
MatchTeams = create_schedule ( Seed , Size , Teams ) ,
ok = first_error ( delete_matches ( State #state.database , Division , Round , GenID ) ) ,
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 ) ) ,
{ noreply , State } .
{ 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
} .