@ -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 ) .