From 89ed5af5d32025c6c7eeb799b9849b478c24c230 Mon Sep 17 00:00:00 2001 From: Artanemus <69775305+Artanemus@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:44:05 +1000 Subject: [PATCH 1/6] Auto-Build relays. DNA chromosome algorithm for testing --- AutoBuildRelayAlgorithm.pas | 629 ++++++++++++++++++++++-------------- dlgAutoBuild_Relay.dfm | 1 - dlgAutoBuild_Relay.pas | 58 +++- dlgPreferences.pas | 8 +- frmMain.dfm | 2 +- 5 files changed, 456 insertions(+), 242 deletions(-) diff --git a/AutoBuildRelayAlgorithm.pas b/AutoBuildRelayAlgorithm.pas index 650cc15..312533d 100644 --- a/AutoBuildRelayAlgorithm.pas +++ b/AutoBuildRelayAlgorithm.pas @@ -3,317 +3,472 @@ interface uses -System.Classes; +System.Classes, FireDAC.Comp.Client, FireDAC.Stan.Param, dmSCM, dmSCMHelper, + SCMUtility, System.SysUtils; // Bin packing algorithm in Delphi using a genetic algorithm // Define the container and item types type - TLane = record - Capacity: Integer; - Swimmers: array of Integer; - end; - TSwimmer = record - RaceTime: Integer; - NormalizedRaceTime: Double; + TTeam = record + // L A N E >>> R E L A Y - T E A M . + // (Typically a relay-team holds four swimmers) + ID: Integer; // IDENITY of the Team in dbo.Team. + Lane: Integer; // Lane number. + RaceTime: TTime; // The SUM of all PBs for swimmers in the relay-team. + Entrants: array of integer; // Holds memberID - 4 x swimmers per lane. + HeatID: Integer; end; - TChromosome = record - Genes: array of Integer; - Fitness: Double; + TChromosome = array of TTeam; + + + THeat = record + // H E A T . + ID: integer; + HeatNum: integer; + EventID: integer; end; -const - POPULATION_SIZE = 100; - MUTATION_RATE = 0.05; - CROSSOVER_RATE = 0.8; - MAX_GENERATIONS = 1000; + TSwimmer = record + // S W I M M E R . + NomineeID: Integer; // Nomination ID for the swimmer in dbo.Nominee. + MemberID: Integer; // Member ID for the swimmer used in dbo.Member table + TTB: TTime; // Time-to-beat. Estimated (calculated) swimming for XDistance. + PB: TTime; // Personal Best swimming time for the swimmer for the event. + end; -// var -// Population: array[0..POPULATION_SIZE - 1] of TChromosome; +var + NumOfNominees: integer; // Count of nominees. + NumOfTeams: integer; // Count of relay-teams. + NumOfHeats: integer; // Count of heats. + NumOfTeamsPerHeat: integer; // Count of relay-teams in each heat. + NumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. + NumOfEntrantsPerTeam: integer; + + Population: array of TChromosome; + PopulationSize: Integer = 100; + Generations: Integer = 1000; + MutationRate: Double = 0.01; + + // D Y N A M I C A R R A Y S . + // Swimmers who nominated for the event. + Swimmers: array of TSwimmer; + // [HeatID][TeamID]. An array of heats - each containing a relay-team. + Heats: array of THeat; + // If the number of Relay-Teams exceeds the number of lanes + // then multi-heats are required. + Teams: array of TTeam; + + function GetStrokeID(EventID: integer): integer; + function GetXDistanceID(EventID, teamSize: integer): integer; implementation -uses System.Math, system.Generics.Collections; +uses System.Math, system.Generics.Collections, System.IniFiles, System.Variants; -// Define the comparison function for sorting the chromosomes by their fitness -function CompareChromosomes(Item1, Item2: Pointer): Integer; +function InitializePopulation: TChromosome; +var + i, j: Integer; + Chromosome: TChromosome; begin - if TChromosome(Item1^).Fitness > TChromosome(Item2^).Fitness then - Result := -1 - else if TChromosome(Item1^).Fitness < TChromosome(Item2^).Fitness then - Result := 1 - else - Result := 0; + SetLength(Chromosome, NumOfTeams); + for i := 0 to NumOfTeams - 1 do + begin + Chromosome[i].ID := i; + Chromosome[i].Lane := i mod NumOfPoolLanes; + SetLength(Chromosome[i].Entrants, NumOfEntrantsPerTeam); + for j := 0 to NumOfEntrantsPerTeam - 1 do + begin + Chromosome[i].Entrants[j] := Random(NumOfNominees); + end; + end; + Result := Chromosome; end; -// Define the quicksort function -procedure QuickSort(var A: array of TChromosome; iLo, iHi: Integer; Compare: TListSortCompare); +function Fitness(Chromosome: TChromosome): Double; var - Lo, Hi: Integer; - Pivot, T: TChromosome; + i, j: Integer; + TotalTime, MaxTime, MinTime: TTime; begin - Lo := iLo; - Hi := iHi; - Pivot := A[(Lo + Hi) div 2]; - repeat - while Compare(@A[Lo], @Pivot) < 0 do Inc(Lo); - while Compare(@A[Hi], @Pivot) > 0 do Dec(Hi); - if Lo <= Hi then + MaxTime := 0; + MinTime := MaxInt; + TotalTime := 0; + for i := 0 to High(Chromosome) do + begin + Chromosome[i].RaceTime := 0; + for j := 0 to High(Chromosome[i].Entrants) do begin - T := A[Lo]; - A[Lo] := A[Hi]; - A[Hi] := T; - Inc(Lo); - Dec(Hi); + Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; end; - until Lo > Hi; - if Hi > iLo then QuickSort(A, iLo, Hi, Compare); - if Lo < iHi then QuickSort(A, Lo, iHi, Compare); + if Chromosome[i].RaceTime > MaxTime then + MaxTime := Chromosome[i].RaceTime; + if Chromosome[i].RaceTime < MinTime then + MinTime := Chromosome[i].RaceTime; + TotalTime := TotalTime + Chromosome[i].RaceTime; + end; + Result := MaxTime - MinTime; end; -// Define the mean function -function Mean(Data: array of Integer): Double; +function TournamentSelection: TChromosome; var - i, Sum: Integer; + i, BestIndex: Integer; + BestFitness, CurrentFitness: Double; begin - Sum := 0; - for i := 0 to High(Data) do Inc(Sum, Data[i]); - Result := Sum / Length(Data); + BestIndex := Random(PopulationSize); + BestFitness := Fitness(Population[BestIndex]); + for i := 1 to 4 do + begin + CurrentFitness := Fitness(Population[Random(PopulationSize)]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestIndex := i; + end; + end; + Result := Population[BestIndex]; end; -// Define the standard deviation function -function StandardDeviation(Data: array of Integer): Double; +function Crossover(Parent1, Parent2: TChromosome): TChromosome; var - i: Integer; - MeanValue, SumOfSquares: Double; + i, CrossoverPoint: Integer; + Child: TChromosome; begin - MeanValue := Mean(Data); - SumOfSquares := 0; - for i := 0 to High(Data) do SumOfSquares := SumOfSquares + Sqr(Data[i] - MeanValue); - Result := Sqrt(SumOfSquares / Length(Data)); + SetLength(Child, Length(Parent1)); + CrossoverPoint := Random(Length(Parent1)); + for i := 0 to CrossoverPoint do + Child[i] := Parent1[i]; + for i := CrossoverPoint + 1 to High(Parent2) do + Child[i] := Parent2[i]; + Result := Child; end; -// Define the fitness function -function CalculateFitness(Chromosome: TChromosome; Lanes: array of TLane; Swimmers: array of TSwimmer): Double; +procedure Swap(var A, B: Integer); var - i, j: Integer; - LaneRaceTimes: array of Integer; + Temp: Integer; begin - // Initialize the lane race times to zero - SetLength(LaneRaceTimes, Length(Lanes)); - for i := 0 to High(LaneRaceTimes) do LaneRaceTimes[i] := 0; - - // Calculate the total race time for each lane - for i := 0 to High(Chromosome.Genes) do begin - j := Chromosome.Genes[i]; - Inc(LaneRaceTimes[j], Swimmers[i].RaceTime); - end; - - // Calculate the fitness as the inverse of the standard deviation of the lane race times - // Result := Mean(LaneRaceTimes); - Result := StandardDeviation(LaneRaceTimes); - if Result <> 0 then Result := 1 / Result else Result := MaxInt; + Temp := A; + A := B; + B := Temp; end; -// Define the selection function -function SelectParent(Population: array of TChromosome): Integer; + +procedure Mutate(var Chromosome: TChromosome); var - i: Integer; - TotalFitness, CumulativeFitness, RandomValue: Double; + Team1, Team2, Swimmer1, Swimmer2: Integer; begin - // Calculate the total fitness of the population - TotalFitness := 0; - for i := 0 to High(Population) do - TotalFitness := TotalFitness + Population[i].Fitness; - - // Select a parent using roulette wheel selection - RandomValue := Random * TotalFitness; - CumulativeFitness := 0; - for i := 0 to High(Population) do begin - CumulativeFitness := CumulativeFitness + Population[i].Fitness; - if CumulativeFitness >= RandomValue then begin - Result := i; - Exit; - end; + if Random < MutationRate then + begin + Team1 := Random(Length(Chromosome)); + Team2 := Random(Length(Chromosome)); + Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); + Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); + Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); end; - - // If no parent was selected, return the last chromosome in the population - Result := High(Population); end; -// Define the crossover function -procedure Crossover(var Chromosome1, Chromosome2: TChromosome); +procedure GeneticAlgorithm; var - i, j, k, CrossoverPoint: Integer; + i, j: Integer; + Parent1, Parent2, Child: TChromosome; + BestChromosome: TChromosome; + BestFitness, CurrentFitness: Double; begin - // Perform single-point crossover - if Random < CROSSOVER_RATE then begin - CrossoverPoint := RandomRange(0, Length(Chromosome1.Genes)); - for i := CrossoverPoint to High(Chromosome1.Genes) do begin - j := Chromosome1.Genes[i]; - k := Chromosome2.Genes[i]; - Chromosome1.Genes[i] := k; - Chromosome2.Genes[i] := j; + SetLength(Population, PopulationSize); + for i := 0 to PopulationSize - 1 do + Population[i] := InitializePopulation; + + for i := 0 to Generations - 1 do + begin + for j := 0 to PopulationSize - 1 do + begin + Parent1 := TournamentSelection; + Parent2 := TournamentSelection; + Child := Crossover(Parent1, Parent2); + Mutate(Child); + Population[j] := Child; end; end; + + BestChromosome := Population[0]; + BestFitness := Fitness(BestChromosome); + for i := 1 to PopulationSize - 1 do + begin + CurrentFitness := Fitness(Population[i]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestChromosome := Population[i]; + end; + end; + + // Use BestChromosome to set your final teams + SetLength(Teams, Length(BestChromosome)); + for i := 0 to High(BestChromosome) do + Teams[i] := BestChromosome[i]; end; -// Define the mutation function -procedure Mutate(var Chromosome: TChromosome); + + +function GetStrokeID(EventID: integer): integer; var - i, j, k: Integer; +v: variant; begin - // Perform swap mutation on each gene with a small probability - for i := 0 to High(Chromosome.Genes) do begin - if Random < MUTATION_RATE then begin - j := Chromosome.Genes[i]; - k := RandomRange(0, Length(Chromosome.Genes)); - Chromosome.Genes[i] := Chromosome.Genes[k]; - Chromosome.Genes[k] := j; - end; - end; + result := 0; + v := SCM.scmConnection.ExecSQLScalar('SELECT StrokeID FROM [dbo].[Event] WHERE [EventID] = :ID',[EventID]); + if not VarIsNull(v) then + result := v; end; -// Define the bin packing function using a genetic algorithm -function BinPack(Lanes: array of TLane; Swimmers: array of TSwimmer): TArray; +function GetXDistanceID(EventID, teamSize: Integer): Integer; var - i, j, k, Generation: Integer; - Population: array[0..POPULATION_SIZE - 1] of TChromosome; - Parent1, Parent2, Offspring1, Offspring2: TChromosome; - BestChromosome: TChromosome; + v: variant; + meters: Integer; begin - // Initialize the population with random chromosomes - for i := 0 to High(Population) do begin - SetLength(Population[i].Genes, Length(Swimmers)); - for j := 0 to High(Population[i].Genes) do Population[i].Genes[j] := Random(Length(Lanes)); - Population[i].Fitness := CalculateFitness(Population[i], Lanes, Swimmers); - end; + result := 0; + v := SCM.scmConnection.ExecSQLScalar(''' + + SELECT[meters] + FROM[SwimClubMeet].[dbo].[Distance] + INNER JOIN[dbo].[Event] ON[Distance].[DistanceID] = [Event].[DistanceID] + WHERE EventID = :ID + ''', [EventID]); + if not VarIsNull(v) then + begin + // divide the total relay distance by the number of swimmers in the relay + meters := Floor(v/teamSize); - // Initialize the best chromosome - BestChromosome.Fitness := 0; - - // Run the genetic algorithm for a fixed number of generations - for Generation := 1 to MAX_GENERATIONS do begin - // Sort the population by fitness - QuickSort(Population, Length(Population), SizeOf(TChromosome), CompareChromosomes); - - // Update the best chromosome - if Population[0].Fitness > BestChromosome.Fitness then BestChromosome := Population[0]; - - // Create a new population using selection, crossover, and mutation - for i := 0 to High(Population) div 2 do begin - // Select two parents - j := SelectParent(Population); - k := SelectParent(Population); - Parent1 := Population[j]; - Parent2 := Population[k]; - - // Create two offspring using crossover - Offspring1 := Parent1; - Offspring2 := Parent2; - Crossover(Offspring1, Offspring2); - - // Mutate the offspring - Mutate(Offspring1); - Mutate(Offspring2); - - // Calculate the fitness of the offspring - Offspring1.Fitness := CalculateFitness(Offspring1, Lanes, Swimmers); - Offspring2.Fitness := CalculateFitness(Offspring2, Lanes, Swimmers); - - // Add the offspring to the new population - Population[i * 2] := Offspring1; - Population[i * 2 + 1] := Offspring2; + // cross reference value 'meters' to find the XDistanceID for a swimmer. + v := SCM.scmConnection.ExecSQLScalar(''' + + SELECT[DistanceID] + FROM[SwimClubMeet].[dbo].[Distance] + WHERE[meters] = :ID + ''', [meters]); + if not VarIsNull(v) then + begin + result := v; end; end; - - // Return the best solution found by the genetic algorithm - SetLength(Result, Length(BestChromosome.Genes)); - for i := Low(BestChromosome.Genes) to High(BestChromosome.Genes) do - Result[i] := BestChromosome.Genes[i]; end; -(* -procedure QuickSortI(lLowBound, lHighBound: integer; lCompare: TListSortCompare; - lSwap: TListSortSwap); + +procedure DistributeTeams(FDQuery: TFDQuery); var - lLeft: Integer; - lRight: Integer; - lPivot: Integer; - lLeftCompare: Integer; - lRightCompare: Integer; - lStack: array of integer; - lStackLen: integer; + i, j, k, m, n, AHeatID, AEventID, ATeamID, ATeamEntrantID, ALaneNum: Integer; + TeamRaceTime: TTIME; begin - if lHighBound > lLowBound then + i := 0; + // iterate through the list of nominees + while not FDQuery.Eof do begin - lStackLen := 2; - SetLength(lStack, lStackLen); - lStack[lStackLen - 1] := lLowBound; - lStack[lStackLen - 2] := lHighBound; - - repeat - lLowBound := lStack[lStackLen - 1]; - lHighBound := lStack[lStackLen - 2]; - SetLength(lStack, lStackLen - 2); - Dec(lStackLen, 2); - - lLeft := lLowBound; - lRight := lHighBound; - lPivot := (lLowBound + lHighBound) div 2; - repeat - lLeftCompare := lCompare(lLeft, lPivot); - while lLeftCompare < 0 do - begin - Inc(lLeft); - lLeftCompare := lCompare(lLeft, lPivot); - end; - lRightCompare := lCompare(lRight, lPivot); - while lRightCompare > 0 do + Swimmers[i].MemberID := FDQuery.FieldByName('MemberID').AsInteger; + Swimmers[i].NomineeID := FDQuery.FieldByName('NomineeID').AsInteger; + Swimmers[i].TTB := FDQuery.FieldByName('TTB').AsDateTime; + Inc(i); + FDQuery.Next; + end; + + // Distribute swimmers into teams + i := 0; // Nominees - Swimmers + m := 0; // 0 .. NumOfTeams-1 + for j := 0 to NumOfHeats - 1 do + begin + // HEAT. + Heats[j].ID := j; + Heats[j].HeatNum := j+1; + Heats[j].EventID := AEventID; + for k := 0 to NumOfTeamsPerHeat - 1 do + begin + // TEAM. + Teams[m].ID := m; // Identifier 0...NumOfTeams-1 + inc(m, 1); + ALaneNum := ScatterLanes(k, NumOfPoolLanes); // index is base 0 + Teams[m].Lane := ALaneNum; + Teams[m].RaceTime := 0; + + // set the number of swimmers permitted in a relay team + SetLength(Teams[k].Entrants, NumOfEntrantsPerTeam); + TeamRaceTime := 0; + // fill relay team with entrants. + for n := 0 to NumOfEntrantsPerTeam - 1 do + begin + if (i <= high(Swimmers)) then begin - Dec(lRight); - lRightCompare := lCompare(lRight, lPivot); + // TEAMENTRANT. + Teams[k].Entrants[n] := i; + TeamRaceTime := TeamRaceTime + Swimmers[i].TTB; end; + inc(i, 1); + end; + // total est.racetime for this relay-team + Teams[k].RaceTime := TeamRaceTime; + end; + // TEST : exceed number of teams in total + if m >= NumOfTeams then break; + end; +end; - if lLeft <= lRight then + +procedure InsertIntoTeams(); +var + qry1, qry2, qry3: TFDQuery; + AHeatID: integer; + ATeamID: integer; + ATeamEntrantID: integer; + i: integer; + j: integer; + k: integer; + m: integer; + n: integer; + p: integer; + r: integer; +begin + k := 0; + + for j := Low(Heats) to High(Heats) do + Begin + // Insert into dbo.HeatIndividual + qry1.SQL.Text := 'INSERT INTO HeatIndividual (HeatNum, OpenDT, HeatTypeID, HeatStatusID) VALUES (:HEATNUM, GETDATE(), 1, 1)'; + qry1.ParamByName('HEATNUM').AsInteger := Heats[j].HeatNum; + qry1.ExecSQL; + // Get the new heat record's ID + AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); + + for i := 0 to NumOfTeamsPerHeat - 1 do + begin + // Insert into Team table + qry2.SQL.Text := 'INSERT INTO Team (Lane, TeamNameID, HeatID) VALUES (:LANE, :TEAMNAMEID, :HEATID)'; + qry2.ParamByName('LANE').AsInteger := Teams[i].Lane; + // dbo.TeamName is a table filled with ID's starting from 1, ending in 26 and Caption's TeamA...TeamZ. + // i is base 0. + // i is range 0 to NumOfTeams (unlikely, though not impossible, to exceed 25 (the number of team names - 1). + qry2.ParamByName('TEAMNAMEID').AsInteger := Teams[i].ID+1; // Base zero. + qry2.ParamByName('HEATID').AsInteger := AHeatID; // Base zero. + qry2.ExecSQL; + // Get the new team record's ID + ATeamID := qry2.Connection.GetLastAutoGenValue('TeamID'); + p := 1; // Swimming order 1 to NumOfEntrantsPerTeam. + // Insert into TeamEntrant table + for m := Low(Teams[i].Entrants) to High(Teams[i].Entrants) do + begin + qry3.SQL.Text := 'INSERT INTO TeamEntrant (MemberID, Lane, TeamID, PersonalBest, TimeToBeat) VALUES (:MemberID, :Lane, :TeamID, :PersonalBest, :TimeToBeat)'; + qry3.ParamByName('MemberID').AsInteger := Teams[i].Entrants[m]; + qry3.ParamByName('Lane').AsInteger := p; + qry3.ParamByName('TeamID').AsInteger := ATeamID; + // locate swimmer in Swimmers... + for r := Low(Swimmers) to High(Swimmers) do begin - if not ((lLeftCompare = 0) and (lRightCompare = 0)) then + if Swimmers[r].MemberID = Teams[i].Entrants[m] then begin - lSwap(lRight, lLeft); - - if lPivot = lLeft then - lPivot := lRight - else if lPivot = lRight then - lPivot := lLeft; + qry3.ParamByName('PersonalBest').AsTime := Swimmers[r].PB; + qry3.ParamByName('TimeToBeat').AsTime := Swimmers[r].TTB; + break; end; - Inc(lLeft); - Dec(lRight); end; - until lLeft > lRight; - - if (lHighBound > lLeft) then - begin - Inc(lStackLen, 2); - SetLength(lStack, lStackLen); - lStack[lStackLen - 1] := lLeft; - lStack[lStackLen - 2] := lHighBound; + qry3.ExecSQL; + inc(p, 1); end; + inc(k, 1); + end; + End; +end; - if (lLowBound < lRight) then - begin - Inc(lStackLen, 2); - SetLength(lStack, lStackLen); - lStack[lStackLen - 1] := lLowBound; - lStack[lStackLen - 2] := lRight; - end; - until lStackLen = 0; + +{ +Retrieve Swimmer Data: Fetch the 16 swimmers’ data from the Nominee table, +focusing on their MemberID and PB (Personal Best) times. +} + + +procedure RetriveSwimmingData(AEventID, poolLanes, teamSize: Integer); +var + FDQuery: TFDQuery; + IniFileName: TFilename; + StrokeID, xDistanceID: integer; +begin + + FDQuery := TFDQuery.Create(nil); + try + + StrokeID := GetStrokeID(AEventID); + xDistanceID := GetXDistanceID(AEventID, teamSize); + NumOfPoolLanes := poolLanes; + NumOfEntrantsPerTeam := teamSize; + + FDQuery.Connection := SCM.scmConnection; // Your FireDAC connection + // Sort swimmers by personal best (PB) + // Order by ascending - FASTEST to SLOWEST + FDQuery.SQL.Text := ''' + SELECT + [NomineeID] + ,[PB] + ,[SeedTime] + ,[AutoBuildFlag] + ,[EventID] + ,[MemberID] + -- Re-Calculate TimeToBeat... + -- algorithm 1 ... average of the 3 fastest racetimes + -- 1, 50%, MemberID, 25m, freestyle, SesssionStart. + ,dbo.TimeToBeat(1, default, default, [MemberID], :XDISTANCEID, :STROKEID, :SESSIONSTART) AS TTB + FROM [dbo].[Nominee] + WHERE [EventID] = :EVENTID + ORDER BY [TTB] ASC +'''; + + + //'SELECT MemberID, PB FROM dbo.Nominee WHERE EventID = :ID ORDER BY PB ASC'; + + + FDQuery.ParamByName('EVENTID').AsInteger := AEventID; + FDQuery.ParamByName('SESSIONSTART').AsDateTime := SCM.Session_Start; + FDQuery.ParamByName('XDISTANCEID').AsDateTime := xDistanceID; + FDQuery.ParamByName('STROKEID').AsDateTime := StrokeID; + FDQuery.Open; + + if FDQuery.IsEmpty then + begin + FDQuery.Close; + FDQuery.Free; + exit; + end; + + // the number of club members who nominated for the relay event. + NumOfNominees := FDQuery.RecordCount; + // the number of teams + NumOfTeams := Ceil(NumOfNominees / teamsize); + if NumOfTeams = 0 then exit; + // check that the number of relay-teams doesn't exceed the number of lanes. + if NumOfTeams > poolLanes then + NumOfHeats := Ceil(NumOfTeams / poolLanes); + + NumOfTeamsPerHeat := Ceil(NumOfTeams / NumOfHeats); + + SetLength(Swimmers, NumOfNominees); + SetLength(Heats, NumOfHeats); + SetLength(Teams, NumOfTeams); + + // Clean OUT ALL HEATs, TEAMs, TEAMSPLITs. + SCM.Heat_DeleteAll(AEventID); + + // Process the data + // DistributeTeams(FDQuery); + + // Process the data using Genetic Algorithm + GeneticAlgorithm(); + + InsertIntoTeams(); + + finally + FDQuery.Free; end; end; -*) + + + end. diff --git a/dlgAutoBuild_Relay.dfm b/dlgAutoBuild_Relay.dfm index 1df390b..926382b 100644 --- a/dlgAutoBuild_Relay.dfm +++ b/dlgAutoBuild_Relay.dfm @@ -21,7 +21,6 @@ object AutoBuild_Relay: TAutoBuild_Relay BevelKind = bkFlat BevelOuter = bvNone TabOrder = 0 - ExplicitWidth = 508 object btnCancel: TButton Left = 257 Top = 6 diff --git a/dlgAutoBuild_Relay.pas b/dlgAutoBuild_Relay.pas index 6df487f..7cedd12 100644 --- a/dlgAutoBuild_Relay.pas +++ b/dlgAutoBuild_Relay.pas @@ -4,7 +4,7 @@ interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, - Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls; + Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, dmSCM; type TAutoBuild_Relay = class(TForm) @@ -13,6 +13,13 @@ TAutoBuild_Relay = class(TForm) btnOk: TButton; private { Private declarations } + prefExcludeOutsideLanes: boolean; + prefNumOfSwimmersPerTeam: integer; + + function GetPoolLaneCount(): integer; + + procedure ReadPreferences(IniFileName: string); + public { Public declarations } end; @@ -24,4 +31,53 @@ implementation {$R *.dfm} +uses System.IniFiles, SCMUtility; + +{ TAutoBuild_Relay } + +function TAutoBuild_Relay.GetPoolLaneCount: integer; +var +IniFileName: TFileName; +Lanes: integer; +begin + result := 0; + + // r e a d p r e f e r e n c e . + IniFileName := SCMUtility.GetSCMPreferenceFileName(); + if (FileExists(IniFileName)) then + ReadPreferences(IniFileName); + + // Get number of lanes from dbo.SwimClub.NumOfLanes + Lanes := SCM.SwimClub_NumberOfLanes; + if Lanes = 0 then + Exit; + + // Adjust lanes if excluding outside lanes + if prefExcludeOutsideLanes then + Dec(Lanes, 2); + + // Ensure there is at least one lane + if Lanes < 1 then + Exit; + + result := Lanes; +end; + +procedure TAutoBuild_Relay.ReadPreferences(IniFileName: string); +var + iFile: TIniFile; + i: Integer; +begin + iFile := TIniFile.Create(IniFileName); + + // When true gutter lanes are not used in events. + prefExcludeOutsideLanes := (iFile.ReadInteger('Preferences', + 'ExcludeOutsideLanes', 0) = 1); + // Relay teams, by default, have four swimmers. + prefNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', + 'NumOfSwimmersPerTeam', 4); + + iFile.free; +end; + end. diff --git a/dlgPreferences.pas b/dlgPreferences.pas index bcefdfd..9b1565b 100644 --- a/dlgPreferences.pas +++ b/dlgPreferences.pas @@ -402,8 +402,12 @@ procedure TPreferences.ReadPreferences(IniFileName: string); i, iUnChecked, iChecked: integer; begin iFile := TIniFile.Create(IniFileName); - iUnChecked := integer(TCheckBoxState.cbUnchecked); - iChecked := integer(TCheckBoxState.cbChecked); +{ +Using Ord is generally considered best practice when converting an +enumeration to its corresponding integer value in Delphi. +} + iUnChecked := Ord(TCheckBoxState.cbUnchecked); + iChecked := Ord(TCheckBoxState.cbChecked); // 2024/08/31 Add more screen real estate. i := iFile.ReadInteger('Preferences', 'HideTitlePanel', iUnChecked); diff --git a/frmMain.dfm b/frmMain.dfm index 344a282..c1a8d7b 100644 --- a/frmMain.dfm +++ b/frmMain.dfm @@ -2693,7 +2693,7 @@ object Main: TMain Top = 0 Width = 1344 Height = 737 - ActivePage = TabSheet1 + ActivePage = TabSheet3 Align = alClient Style = tsFlatButtons TabOrder = 0 From 9e56203d8f4220919ff41ed109cc5ae6dc7529a2 Mon Sep 17 00:00:00 2001 From: Artanemus <69775305+Artanemus@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:24:49 +1000 Subject: [PATCH 2/6] AutoBuild Relays WIP --- AUTOBUILD/dlgABRelay.dfm | 409 ++++++++++++++++++++++++ AUTOBUILD/dlgABRelay.pas | 183 +++++++++++ AUTOBUILD/dmABRelayData.dfm | 614 ++++++++++++++++++++++++++++++++++++ AUTOBUILD/dmABRelayData.pas | 110 +++++++ AUTOBUILD/uABRelayExec.pas | 577 +++++++++++++++++++++++++++++++++ AUTOBUILD/uABRelayV1.pas | 240 ++++++++++++++ AutoBuildRelayAlgorithm.pas | 474 ---------------------------- SwimClubMeet.dpr | 9 +- SwimClubMeet.dproj | 57 +--- SwimClubMeet.res | Bin 212832 -> 212832 bytes TOOLS/dlgAutoSchedule.dfm | 4 - dlgAutoBuild_Relay.dfm | 45 --- dlgAutoBuild_Relay.pas | 83 ----- 13 files changed, 2151 insertions(+), 654 deletions(-) create mode 100644 AUTOBUILD/dlgABRelay.dfm create mode 100644 AUTOBUILD/dlgABRelay.pas create mode 100644 AUTOBUILD/dmABRelayData.dfm create mode 100644 AUTOBUILD/dmABRelayData.pas create mode 100644 AUTOBUILD/uABRelayExec.pas create mode 100644 AUTOBUILD/uABRelayV1.pas delete mode 100644 AutoBuildRelayAlgorithm.pas delete mode 100644 dlgAutoBuild_Relay.dfm delete mode 100644 dlgAutoBuild_Relay.pas diff --git a/AUTOBUILD/dlgABRelay.dfm b/AUTOBUILD/dlgABRelay.dfm new file mode 100644 index 0000000..f90e760 --- /dev/null +++ b/AUTOBUILD/dlgABRelay.dfm @@ -0,0 +1,409 @@ +object ABRelay: TABRelay + Left = 0 + Top = 0 + Caption = 'AutoBuild Team Event ...' + ClientHeight = 557 + ClientWidth = 507 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Segoe UI' + Font.Style = [] + OnCreate = FormCreate + OnDestroy = FormDestroy + OnShow = FormShow + TextHeight = 21 + object Panel2: TPanel + Left = 0 + Top = 511 + Width = 507 + Height = 46 + Align = alBottom + BevelEdges = [beTop] + BevelKind = bkFlat + BevelOuter = bvNone + TabOrder = 0 + object btnCancel: TButton + Left = 256 + Top = 10 + Width = 82 + Height = 33 + Cancel = True + Caption = 'No' + ModalResult = 2 + TabOrder = 0 + end + object btnOk: TButton + Left = 168 + Top = 10 + Width = 82 + Height = 33 + Caption = 'Yes' + Default = True + ModalResult = 1 + TabOrder = 1 + end + end + object pnlHeader: TPanel + Left = 0 + Top = 0 + Width = 507 + Height = 61 + Align = alTop + BevelOuter = bvNone + TabOrder = 1 + object Label1: TLabel + Left = 86 + Top = 20 + Width = 297 + Height = 21 + Caption = 'Answer '#39'Yes'#39' to Auto-Build RELAY-TEAMS ?' + end + object Image1: TImage + Left = 22 + Top = 4 + Width = 50 + Height = 50 + Picture.Data = { + 0A544A504547496D616765550B0000FFD8FFE000104A46494600010100000100 + 010000FFDB004300030202020202030202020303030304060404040404080606 + 050609080A0A090809090A0C0F0C0A0B0E0B09090D110D0E0F101011100A0C12 + 131210130F101010FFDB00430103030304030408040408100B090B1010101010 + 1010101010101010101010101010101010101010101010101010101010101010 + 10101010101010101010101010FFC00011080032003203011100021101031101 + FFC4001D000001050100030000000000000000000007030405060809000102FF + C400301000010303030303030207010000000000010203040506110007211213 + 3122415108143209151623246171819153FFC4001C0100000701010000000000 + 000000000000000304050607080102FFC4003211000103040004040601030500 + 00000000010203110004052106123141071351611422237181A1F04252911532 + 62B1D1FFDA000C03010002110311003F00E98EE05C29B5ED0A9D5D1528B0A433 + 1D7F6CB905382EE3D2900FE4A27000F724688BA538DDBB8EB7D529277D343BFB + 57B6C052D29577228546E0DC2A5A5F7ADDB9132644B6FA1CFDE3ADE42159C871 + 094E0215E474801278E38D53184F11EEAD1E5AF2692F20F40084C1F6D1D7EFDE + A5577826DD4816E7948FCCD0F6F96EEAB5ED77F709371D66A577D27A25AA6332 + 1D4B6EFF0035256831C128ECA50543A7A7F119CE7274E780E3BBEC9711B6DBCE + 06ED5D5805060A529EDB2241FF0096B7445E61D962C494A799C48EBDC9FE76AD + 0FB55B9F6EEEBD9F4FBA6873182B92D664454B814B8EE8E16850F3807C1F7041 + F7D5C9796CAB3794CABB7EC7635166D61C4850AA97D45FD4CD8DF4E56FC7A85C + 6DBD51AAD47A853E9519412E3FD3F92D4A3C2103201510793800E9463B18EE45 + 6423407535E1E792C8DD661B23F55DB7E5DCECD3371B6DD546A3C97437FB8C19 + C6418C09C7538DA9092A48F72939C78074F4FF000BAD28E665727D088A4C8BD0 + 4C2856F5833E154E147A953E537222CB690FB0F36ACA5C6D4014A81F7041046A + 2A414983D69748359C5DA424D5BBD78D1B3714374C8757247594B8B24F71A392 + 928F212478031C11819F78B2E7376978F5BDE38A0DB8642428F214CEB5EDE953 + 8C63568EB495B490549D4C6C1ABE58F639BB20FF0010D6E749660BCB5A61C48C + BED95A12A29EEB8B1EAF5104848200182724F130E15E07B172C9179904F3A962 + 40920007A74EA69A72597792F16983006BEF55BBE1CA158B7753ED766E96A639 + 574385A80FBA95CB8E509EA24E392D948382AE411E559E1B38D78259C5DA7FAA + 5824A5B04050EA04F4209FD8A5388CAAAE5DF8778CAA2450D2ADBE3B79B2FBA1 + 67D0A5424534CF91D52E4C68E86D88F15DEA6C97318EA0561278F1D1D47C005C + 3C2FC1E5B2D70EE4D0B969039552492A31200FB759FC515C43756D6CDA6DCA7E + 63B11DA831FAA6DA17345BDEDDDC84B6EC8B726D2D14E448402A6E3C8438B5F4 + 12381D695820FBE0FC6B44F0C3C8F294C7F5033F8A845EA4F30576AE7BCA5C99 + F29B870D971F912161A69A6D254B716A384A52072492400352990912690F5AED + 8ED56C9EE0D1B6BECFA3D52E47A2CC8340A7C690C159CB4EA23A12A41FF0411F + EB55ADCDDB6B7D6A48D127FEE9E50DA824026A06E0BCAA759B9274EB856C469E + 975507ECD38488E9696B01B19E5672492AF7C8C0031ACD3C6F90BAC86516DBC8 + E50D4A447A75927DEAC4C2DB36CDB0520CF36FF3E9553BE779F79ADAB559B3AC + 4A5FF468EE04D5218EECB4A14A2A0DF41FC3A7380B00E401F89D59FC01C6581F + 8345B669CF2DC6C4099E5501D0C89DFA83151ECDE1AF7CE2E5A27992ADEBA8A1 + 6ED1D9376A6F27370AF6FB861D690E761129DEB90FBAE0E9538BE490024A80C9 + C927C0C728FC51F1031D99B24E1B10799120A951035D0267DF64D1FC3982B8B5 + 74DD5D08310077DF7353FBABB2940DFE9B4F6225799A45650D9422A6F2FF00A5 + 6A2272B5A9E4E40291CF4A811EA5019C1D36785794CD61723F0896CFC3AFE650 + 293E900A4FA9D7B1147712DB59DCB1E6950E74E841FD1AD1545BEBE9CA8BB5F4 + 9DA0DC9DE9B06E8440A6334C9C6A55388A4CBEDA4272A429640F031CE460739D + 5CCE3376B7D570CB6A4C99100EAA1E14D84F22940D23B49B19F46141B91BBB76 + 92DFB1A55610AEE31261D451396C28FBB414E2C367E0A402342EAF720E23CBB8 + 2A8F711410DB20CA62B4069AE8FAE76DF5715FF67C19AEB376C7A9C3A8B65A79 + D9D083E4E07A7ADD521450460614543181C8C6A76F61B1F9042D2E309248DF41 + 3FFB4DCDBCFB4A052B3AA613F7962336FC77ED669371D4425087584480D94700 + 15A89CF956000339278E013ACB563E1A655FBE75AC9A7E1594024AD63E502740 + 1900FF009AB36E3882D9A612AB7FA8B3A091D7F348BFB972AE0B167A26C572DF + ADC98CFB2CB4E3C82942F0421CEE2B08E9CF90AC118231F2B19F0E3296798695 + 6A90FDB0295F99D1B28064CECFA1D6E885E7ED9FB3587250E191CBFD40F6A0ED + DF40A857534D57D41EFD565D72645790DD2D4B74B4CA828A1A5A31E94323093C + A139C60606ACB7B8D33178FDCB1C2B62975B6543EA2760A62488312A3B88263D + 0D479AC15AB6DB6BC93C52A583F29D19EC7EDF7A1E5E5B57B3362D4CDBD594DD + 354A8A1969E5BB0EA31D0C6169C8C659CE71CFB8C11CEACCE0EE215718D81C83 + 2D96D214530AD93113D0FE298F258938D7BC95A828C4EBDEA1699686C7B9290B + 4D5AFAA0380E5321A5C793D07E7D21B57FCD4A976AF46A0D37F923B1A24351DA + 6DA4371BEB7AF56994A425B6CB73814240E0603D8E06901B093B613FAAEF2AFF + 00BA9F773ED267DE581B87558D39D504861C926521F51380929E49249C783A36 + EAF19B2615717C025B48924EA00EF47B6DADE586D0249E82AE92DDA358B44A15 + D77158ED4DBCDF4175696DC0D1EE8E14F2D4AE1270A1C6091D58038E3315BB59 + DF10F2D7D8BC7DFA8D9731512A9E5E59F94048DF6D0D0D49AB0546CB076CCDC3 + CCFD58881D67BEE997DA595B8B6F56AF0A36DEB4F5CECBED2A4C45BFDC57595A + 495A0A4949CA428F00138208F93273BC059BB2C265EF8A6C819047FB0A0C8208 + 22627441989915C22D33366EDDDAB3F57F73EA3F9BA85ABD776DAF06589174D4 + DCA7D569519A86FA1486643B20052BA908EE8F4292A2AC93CE08CF3A9262EC38 + BF84DE5B38169B758BC2A5A0A79B911E867400298806476A6F7DEC764501578A + 5256D000CC4ABD7F75037DD4EA177D6BF70B66BF4D620C46C4582C86C36F1612 + 063B8AC12A390719F1FEF560786BC30BE19C615DF2542E1D24B8099483263940 + D090774CF9DC8A6FEE21B8E44E93A831AEB50D4D917231D6C57EDAA3D6A31E14 + 1E4368748F94B88C1CFF009D588B2D9DA1441A64A5CC1B0492556056524F2426 + 68201F81EAF1AE73BBFDE3FC50A19C4A93F025333A0BEEB1223B8975A75B3852 + 16939041F9075DBDB6B7C8DBAED2E91CCDAC4281E841ED4634EAD95871B30474 + AD18F33696F45A96A512A9B891D1772229702DA49529C5287F31B711E91D5E91 + C641CA490307594EC6FB35E1AE5EFB216560AF82E7292153004FCB0ADFAE8EC6 + E0EEAC275AB5CEDB32CBAF0F36275FBD524EBB6B6CDD8373D06D3BFE2BB76256 + DAE42D40073AC2827B6DA391D41255F3827271EC6A9797F12388EC72197B15FC + 1A8F2A4241E5E5D924AB5A9D93A90205798B6C1D8BCCDB3C3CD1B33D67EDFCF7 + ACF12A63D264392A5B8E38F3EB538E38B395294A3924FF0072493AD556AC3560 + C22D984F2A100000760340557CE38A75656B324ECD7CA5CE7282723DC694F34D + 78A9162BD5165B0DF794A03C152B2744A99E6334294FE22A87FEA7FEEB9F0E28 + 545600F0069401429C41972A0CE8F2E149763BED3A9536EB4B285A0E7C82391A + 67E2061ABAC6BAD3E90A491B04020EC763AA5168B536F2548307DA929521F94F + BB264BCE3CF3AE2D6E38E28A94B51272493C93FDF47E25A6EDF1ECB4D2425212 + 2001007D80AF370A52DD529464CD333C9E74AA89AF68FCC680EB429CE8FA15E6 + 850AFFD9} + end + end + object pnlPrefences: TPanel + Left = 0 + Top = 61 + Width = 507 + Height = 450 + Align = alClient + BevelOuter = bvNone + TabOrder = 2 + object lbl7: TLabel + Left = 22 + Top = 130 + Width = 238 + Height = 21 + Caption = 'For members without race times ...' + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -16 + Font.Name = 'Segoe UI' + Font.Style = [fsUnderline] + ParentFont = False + end + object lbl8: TLabel + Left = 106 + Top = 187 + Width = 333 + Height = 21 + Caption = 'percent. (With consideration to age and gender.)' + end + object lblSeedDepth: TLabel + Left = 362 + Top = 387 + Width = 81 + Height = 21 + Caption = 'Seed depth:' + Enabled = False + end + object lblSwimmersPerTeam: TLabel + Left = 80 + Top = 239 + Width = 143 + Height = 21 + Caption = 'Swimmers per team.' + end + object vimgHint1: TVirtualImage + Left = 110 + Top = 284 + Width = 24 + Height = 24 + ImageCollection = imgcolABRelay + ImageWidth = 0 + ImageHeight = 0 + ImageIndex = 0 + ImageName = 'Info' + OnClick = vimgHint1Click + end + object vimgHint2: TVirtualImage + Left = 159 + Top = 404 + Width = 24 + Height = 24 + ImageCollection = imgcolABRelay + ImageWidth = 0 + ImageHeight = 0 + ImageIndex = 0 + ImageName = 'Info' + OnClick = vimgHint2Click + end + object vimgHint3: TVirtualImage + Left = 183 + Top = 374 + Width = 24 + Height = 24 + ImageCollection = imgcolABRelay + ImageWidth = 0 + ImageHeight = 0 + ImageIndex = 0 + ImageName = 'Info' + OnClick = vimgHint3Click + end + object prefHeatAlgorithm: TRadioGroup + Left = 22 + Top = 6 + Width = 361 + Height = 118 + Caption = 'Entrant'#39's TimeToBeat (TTB) - Heat algorithm ... ' + ItemIndex = 1 + Items.Strings = ( + 'Use the last known race time.' + 'Use the average of the 3 fastest race times.' + 'Use particpants personal best.') + TabOrder = 0 + end + object prefUseDefRaceTime: TCheckBox + Left = 22 + Top = 157 + Width = 523 + Height = 24 + Caption = 'Calculate a race time based on the mean average of the bottom ' + Checked = True + State = cbChecked + TabOrder = 1 + WordWrap = True + end + object prefRaceTimeTopPercent: TSpinEdit + Left = 48 + Top = 187 + Width = 52 + Height = 31 + MaxValue = 100 + MinValue = 0 + TabOrder = 2 + Value = 50 + end + object prefExcludeOutsideLanes: TCheckBox + Left = 22 + Top = 314 + Width = 187 + Height = 24 + Caption = 'Exclude outside lanes.' + TabOrder = 4 + end + object prefSeperateGender: TCheckBox + Left = 22 + Top = 344 + Width = 155 + Height = 24 + Caption = 'Seperate gender.' + Enabled = False + TabOrder = 3 + end + object rgpSeedMethod: TRadioGroup + Left = 253 + Top = 284 + Width = 244 + Height = 94 + Hint = 'Decides what lane an entrant is given.' + Caption = 'Seed Method.' + Enabled = False + ItemIndex = 0 + Items.Strings = ( + 'SwimClubMeet (default)' + 'Circle Seeding') + TabOrder = 5 + end + object spnSeedDepth: TSpinEdit + Left = 449 + Top = 384 + Width = 48 + Height = 31 + Enabled = False + MaxValue = 10 + MinValue = 0 + TabOrder = 6 + Value = 3 + end + object prefDoHouseRelays: TCheckBox + Left = 22 + Top = 374 + Width = 155 + Height = 24 + Caption = 'Arrange by house.' + Enabled = False + TabOrder = 7 + end + object prefNumOfSwimmersPerTeam: TSpinEdit + Left = 22 + Top = 236 + Width = 52 + Height = 31 + MaxValue = 12 + MinValue = 2 + TabOrder = 8 + Value = 4 + end + object prefVerbose: TCheckBox + Left = 22 + Top = 284 + Width = 82 + Height = 24 + Caption = 'Verbose.' + TabOrder = 9 + end + object prefTrimPartialTeams: TCheckBox + Left = 22 + Top = 404 + Width = 131 + Height = 24 + Caption = 'Remove partial.' + Checked = True + Enabled = False + State = cbChecked + TabOrder = 10 + end + end + object bhintABRelay: TBalloonHint + Left = 424 + Top = 120 + end + object imgcolABRelay: TImageCollection + Images = < + item + Name = 'Info' + SourceImages = < + item + Image.Data = { + 89504E470D0A1A0A0000000D49484452000000300000003008060000005702F9 + 87000000017352474200AECE1CE90000042E494441546843ED9947A815491486 + 3FB72E0C881B130AAE467414845117868563449C3163405147174E7010441046 + 712328C6013161CE19CC2E745C184031A12B41316D440C0BD7F20F558FBA7DBB + 6F5755973E1E78A0B9977BEB9C3A7F8513FE6E450B97562DDC7FBE0368EE1D4C + B9035D8021407FA0BBF308E373E7B903FC07BC4A01BE2A801F8079C020E0A740 + 876E0337801DC09340DDA6E1B100DA007F038B017DAF229F80F5C03A40DF8324 + 06C04C6029D02B33D353E000F0D21C0FFBA9613A5E5D9DCFE940CF8CFE63602D + B03B044128800DC09F9909AE03FB8DF39F3D276F0D08C40C6070464700E678DA + 090AA3E781518EE177C002E084EF6405E326005B810ECEFFBAF43D7CECFAEEC0 + 26E077C7E023606A95CB97714EC1E030D0DBF9FDAE89680D71F800F8CDAC9035 + 740C98ECB33A11638E02931CBD65C0EA4676CA004C312B636DFC9BD989081F4B + 5536038B9C51BA2B078BB41A015078549CB6D1E60AF073E9F469065C06861B53 + 8A4ECA33B921B6118015C03FC68842E240E0751AFF4AAD74066E9AD0ABC12B01 + F95327450074A964C026A95F8153A5D3D60E9806FC627E92EEA1407DE99E343A + 5A7D2D605DC62E02A0CCF89751569C578D132A0AAF022E91230A97A1A29AC9E6 + 09E52065FE1A2902700FE86B462A0A6D0F9D19980DEC327A4A4C4119D6E8CD07 + B699EFF7817E3E0054493E3303551EFC08F866D8ACFD8EE687B7110B201565EC + 074ED9A1E4A624D724793BE0AE5CE1E589742846CD0D26753B9907C03DFF2A15 + EC16C64C9E42C74DA475F7200F8022C67833F358E05C0A2F2AD818039C35FAA7 + 9DC8F6FF4F7900DC0BDC0750DD13234381AB46F11A302CC688A98F1E165DE43C + 00EF817646419F1F23274E05A02DF0C1F8A0CFF66597D80520E5E02EC94C900A + 8092A95D442F00EE11521D14DBAFA602A0AA40F590A42E17945DE291C0A5663E + 4223808B2197D80DA3621C76363380B986B9901B5E6154F5B77A5C49954496EA + 08B9894C3DB4888326C93B426210543E4BAA94122900644B09311B3584585131 + 77CB21AA628BB91400DC624E44D880EC712E022092C996AEB1E5740A006E39AD + BB2932AD467C1B9A8911F4495500EA1F8E1B6F831B1AE9B997471C901A8B909C + 50058062BF76DE7245C12DA500649B7AD544AA8DBE85A8F6B11C5174532F47C5 + 83EE753CFE9A9C909D26CB0DCD02F615AD5A192F24BD35C012C7C0D7E486B29C + D046A737CFC5E003408A6780718E057144EA8E52D12CA251D43F5B2E48535D00 + 46979D575F00B2B307D0765A51B213531D4AB7647D127DA2955692B2A29DF8A3 + CC79FD1F0240E317025B328653D3EB416D6C2800F92E567A7983171C6FCCD17A + 01E89174338F8E4A27F36E20EF05C72AE088CFCADB313100A4DBA25F31B90BA4 + 84A37A4549CB1261BE0BA8E644BDB248B3900459633F7607F29C1421262022C2 + 1ABD66155125C76B082A5FD4D9712901C4FA5049EF3B804ACB9740B9C5EFC017 + 4EABDD310AD690EA0000000049454E44AE426082} + end> + end> + Left = 424 + Top = 64 + end +end diff --git a/AUTOBUILD/dlgABRelay.pas b/AUTOBUILD/dlgABRelay.pas new file mode 100644 index 0000000..baa0093 --- /dev/null +++ b/AUTOBUILD/dlgABRelay.pas @@ -0,0 +1,183 @@ +unit dlgABRelay; + +interface + +uses + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, + Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, dmSCM, + dmSCMHelper, SCMDefines, Vcl.Imaging.jpeg, Vcl.Samples.Spin, + Vcl.BaseImageCollection, Vcl.ImageCollection, Vcl.VirtualImage, + System.ImageList, Vcl.ImgList, Vcl.VirtualImageList; + +type + TABRelay = class(TForm) + Panel2: TPanel; + btnCancel: TButton; + btnOk: TButton; + pnlHeader: TPanel; + Label1: TLabel; + Image1: TImage; + pnlPrefences: TPanel; + lbl7: TLabel; + lbl8: TLabel; + lblSeedDepth: TLabel; + prefHeatAlgorithm: TRadioGroup; + prefUseDefRaceTime: TCheckBox; + prefRaceTimeTopPercent: TSpinEdit; + prefExcludeOutsideLanes: TCheckBox; + prefSeperateGender: TCheckBox; + rgpSeedMethod: TRadioGroup; + spnSeedDepth: TSpinEdit; + prefDoHouseRelays: TCheckBox; + prefNumOfSwimmersPerTeam: TSpinEdit; + lblSwimmersPerTeam: TLabel; + prefVerbose: TCheckBox; + prefTrimPartialTeams: TCheckBox; + bhintABRelay: TBalloonHint; + imgcolABRelay: TImageCollection; + vimgHint1: TVirtualImage; + vimgHint2: TVirtualImage; + vimgHint3: TVirtualImage; + procedure FormDestroy(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure vimgHint1Click(Sender: TObject); + procedure vimgHint2Click(Sender: TObject); + procedure vimgHint3Click(Sender: TObject); + private + { Private declarations } + procedure ReadPreferences(IniFileName: string); + procedure WritePreferences(IniFileName: string); + public + { Public declarations } + end; + +var + ABRelay: TABRelay; + +implementation + +{$R *.dfm} + +uses System.IniFiles, SCMUtility; + +procedure TABRelay.FormDestroy(Sender: TObject); +var +iniFileName: string; +begin + iniFileName := GetSCMPreferenceFileName(); + if FileExists(iniFileName) then + WritePreferences(iniFileName); +end; + +procedure TABRelay.FormCreate(Sender: TObject); +var + IniFileName: TFileName; +begin + // r e a d p r e f e r e n c e . + IniFileName := SCMUtility.GetSCMPreferenceFileName(); + if (FileExists(IniFileName)) then + ReadPreferences(IniFileName); +end; + +procedure TABRelay.FormShow(Sender: TObject); +begin + btnOk.SetFocus; +end; + +{ TAutoBuild_Relay } + +procedure TABRelay.ReadPreferences(IniFileName: string); +var + iFile: TIniFile; +begin + iFile := TIniFile.Create(IniFileName); + // TTB defaults to (1) .. the entrant's average of top 3 race-times + prefHeatAlgorithm.ItemIndex := + (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); + // default to - get an average race-time of other swimmers + prefUseDefRaceTime.State := TCheckBoxState(iFile.ReadInteger('Preferences', + 'UseDefRaceTime', integer(cbChecked))); + // The bottom percent to select from ... default is 50% + prefRaceTimeTopPercent.Value := + (iFile.ReadInteger('Preferences', 'RaceTimeTopPercent', 50)); + // auto-create heats - this option will omit gutter lanes + prefExcludeOutsideLanes.State := + TCheckBoxState(iFile.ReadInteger('Preferences', 'ExcludeOutsideLanes', + integer(cbUnchecked))); + prefSeperateGender.State := TCheckBoxState(iFile.ReadInteger('Preferences', + 'SeperateGender', integer(cbUnchecked))); +// prefGroupBy.ItemIndex := iFile.ReadInteger('Preferences', 'GroupBy', 0); + rgpSeedMethod.ItemIndex := iFile.ReadInteger('Preferences', 'SeedMethod', 0); + // 2020-11-01 auto-build v2 seed depth for Circle Seed */ + spnSeedDepth.Value := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); + + // 2024-09-10 + // Relay teams, by default, have four swimmers. + prefNumOfSwimmersPerTeam.Value := iFile.ReadInteger('Preferences', + 'NumOfSwimmersPerTeam', 4); + + iFile.Free; +end; + +procedure TABRelay.vimgHint1Click(Sender: TObject); +begin + bhintABRelay.Title := 'Verbose.'; + bhintABRelay.Description := ''' + If a problem is encounted when trying to Auto-Build, + display an error message, prior to aborting. + '''; + bhintABRelay.ShowHint(vimgHint1); +end; + +procedure TABRelay.vimgHint2Click(Sender: TObject); +begin + bhintABRelay.Title := 'Insuffient swimmers in team.'; + bhintABRelay.Description := ''' + If a team doesn't have a full complement of swimmers, + that team will be removed. + '''; + bhintABRelay.ShowHint(vimgHint1); +end; + +procedure TABRelay.vimgHint3Click(Sender: TObject); +begin + bhintABRelay.Title := 'Group by Club-House .'; + bhintABRelay.Description := ''' + Relay teams will only be allowed swimmers that share + the same 'House' (as designated in the member's profile). + '''; + bhintABRelay.ShowHint(vimgHint1); + +end; + +procedure TABRelay.WritePreferences(IniFileName: string); +var + iFile: TIniFile; +begin + iFile := TIniFile.Create(IniFileName); + iFile.WriteInteger('Preferences', 'HeatAlgorithm', + prefHeatAlgorithm.ItemIndex); + iFile.WriteInteger('Preferences', 'UseDefRaceTime', + integer(prefUseDefRaceTime.State)); + iFile.WriteInteger('Preferences', 'RaceTimeTopPercent', + prefRaceTimeTopPercent.Value); + iFile.WriteInteger('Preferences', 'ExcludeOutsideLanes', + integer(prefExcludeOutsideLanes.State)); + iFile.WriteInteger('Preferences', 'SeperateGender', + integer(prefSeperateGender.State)); +// iFile.WriteInteger('Preferences', 'GroupBy', prefGroupBy.ItemIndex); + iFile.WriteInteger('Preferences', 'SeedMethod', rgpSeedMethod.ItemIndex); + // 2020-11-01 auto-build v2 seed depth for Circle Seed */ + iFile.WriteInteger('Preferences', 'SeedDepth', (spnSeedDepth.Value)); + + // 2024-09-10 + // Relay teams, by default, have four swimmers. + iFile.WriteInteger('Preferences', 'NumOfSwimmersPerTeam', + (prefNumOfSwimmersPerTeam.Value)); + iFile.Free; +end; + + + +end. diff --git a/AUTOBUILD/dmABRelayData.dfm b/AUTOBUILD/dmABRelayData.dfm new file mode 100644 index 0000000..61f80f2 --- /dev/null +++ b/AUTOBUILD/dmABRelayData.dfm @@ -0,0 +1,614 @@ +object ABRelayData: TABRelayData + Height = 480 + Width = 640 + object qryXNominees: TFDQuery + ActiveStoredUsage = [auDesignTime] + Connection = SCM.scmConnection + SQL.Strings = ( + ' SELECT' + #9#9' [NomineeID]' + #9#9',[SeedTime]' + #9#9',[AutoBuildFlag]' + #9#9',[EventID]' + #9#9',[MemberID]' + ' -- Re-Calculate TimeToBeat...' + + ' -- algorithm default ... average of the 3 fastest raceti' + + 'mes' + ' -- percentage default ... 50%' + ' -- MemberID, 25m, freestyle, SesssionStart.' + + ' ,dbo.TimeToBeat(1, :ALGORITHM, :PERCENT, [MemberID], :XDISTA' + + 'NCEID, :STROKEID, :SESSIONSTART) AS TTB' + + ' ,dbo.PersonalBest([MemberID],:XDISTANCEID, :STROKEID, :SESSI' + + 'ONSTART) AS PB' + ' FROM [dbo].[Nominee]' + ' WHERE [EventID] = :EVENTID' + ' ORDER BY [TTB] ASC') + Left = 128 + Top = 248 + ParamData = < + item + Name = 'ALGORITHM' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end + item + Name = 'PERCENT' + DataType = ftInteger + ParamType = ptInput + Value = 50 + end + item + Name = 'XDISTANCEID' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end + item + Name = 'STROKEID' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end + item + Name = 'SESSIONSTART' + DataType = ftDate + ParamType = ptInput + Value = Null + end + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + end + object dsXNominees: TDataSource + DataSet = qryXNominees + Left = 200 + Top = 248 + end + object FDCommandUpdateEntrant: TFDCommand + CommandText.Strings = ( + 'USE [SwimClubMeet];' + '' + 'DECLARE @MemberID AS Integer;' + 'DECLARE @ID AS Integer;' + 'DECLARE @TTB AS DateTime;' + 'DECLARE @PB AS DateTime;' + 'DECLARE @EventType AS integer;' + '' + 'SET @MemberID = :MEMBERID;' + + 'SET @ID = :ID; -- TeamEntrant.TeamEntrantID or Entrant.Entrant' + + 'ID' + 'SET @TTB = :TTB;' + 'SET @PB = :PB;' + 'SET @EventType = :EVENTTYPE;' + '' + 'IF (@EventType = 1)' + 'BEGIN' + ' UPDATE [dbo].[Entrant]' + ' SET [MemberID] = @MemberID' + ' ,[RaceTime] = NULL' + ' ,[TimeToBeat] = @TTB' + ' ,[PersonalBest] = @PB' + ' ,[IsDisqualified] = 0' + ' ,[IsScratched] = 0' + ' ,[DisqualifyCodeID] = NULL' + ' WHERE EntrantID = @ID;' + 'END ' + 'ELSE IF (@EventType = 2)' + 'BEGIN' + ' UPDATE [dbo].[TeamEntrant]' + ' SET [MemberID] = @MemberID' + ' ,[RaceTime] = NULL' + ' ,[TimeToBeat] = @TTB' + ' ,[PersonalBest] = @PB' + ' ,[IsDisqualified] = 0' + ' ,[IsScratched] = 0' + ' ,[DisqualifyCodeID] = NULL' + ' WHERE TeamEntrantID = @ID;' + 'END' + '' + '') + ParamData = < + item + Name = 'MEMBERID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end + item + Name = 'ID' + ParamType = ptInput + Value = Null + end + item + Name = 'TTB' + DataType = ftTime + ParamType = ptInput + Value = Null + end + item + Name = 'PB' + DataType = ftTime + ParamType = ptInput + Value = Null + end + item + Name = 'EVENTTYPE' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end> + Left = 128 + Top = 40 + end + object qryRNominee: TFDQuery + ActiveStoredUsage = [auDesignTime] + FilterOptions = [foCaseInsensitive] + Filter = '[FName] LIKE '#39'%b%'#39 + Indexes = < + item + Active = True + Selected = True + Name = 'idxMemberFName' + Fields = 'FName' + end + item + Active = True + Name = 'idxMemberFNameDESC' + Fields = 'FName' + DescFields = 'FName' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxTTB' + Fields = 'TTB' + end + item + Active = True + Name = 'idxTTBDESC' + Fields = 'TTB' + DescFields = 'TTB' + Options = [soDescending] + end + item + Active = True + Name = 'idxPB' + Fields = 'PB' + end + item + Active = True + Name = 'idxPBDESC' + Fields = 'PB' + DescFields = 'PB' + Options = [soDescending] + end + item + Active = True + Name = 'idxAge' + Fields = 'AGE' + end + item + Active = True + Name = 'idxAgeDESC' + Fields = 'AGE' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxGender' + Fields = 'GenderID' + end + item + Active = True + Name = 'idxGenderDESC' + Fields = 'GenderID' + Options = [soDescNullLast, soDescending] + end> + IndexName = 'idxMemberFName' + DetailFields = 'MemberID' + Connection = SCM.scmConnection + FormatOptions.AssignedValues = [fvFmtDisplayTime] + FormatOptions.FmtDisplayTime = 'nn:ss.zzz' + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate, uvCheckReadOnly] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' + UpdateOptions.KeyFields = 'MemberID' + SQL.Strings = ( + 'USE SwimClubMeet;' + '' + 'DECLARE @EventID AS INT;' + 'DECLARE @Algorithm INT;' + 'DECLARE @DistanceID AS INT;' + 'DECLARE @StrokeID AS INT;' + 'DECLARE @SessionStart DATETIME;' + 'DECLARE @ToggleName BIT;' + 'DECLARE @Order INT;' + 'DECLARE @CalcDefault INT;' + 'DECLARE @BottomPercent FLOAT;' + 'DECLARE @EventType AS INT;' + '' + 'SET @EventID = :EVENTID;' + 'SET @Algorithm = :ALGORITHM;' + 'SET @ToggleName = :TOGGLENAME;' + 'SET @CalcDefault = :CALCDEFAULT' + 'SET @BottomPercent = :BOTTOMPERCENT' + 'SET @EventType = :EVENTTYPE' + 'SET @DistanceID = :DISTANCEID' + '' + 'SET @StrokeID =' + '(' + ' SELECT StrokeID FROM Event WHERE Event.EventID = @EventID' + ');' + '' + 'SET @SessionStart =' + '(' + ' SELECT Session.SessionStart' + ' FROM Event' + ' INNER JOIN Session' + ' ON Event.SessionID = Session.SessionID' + ' WHERE Event.EventID = @EventID' + ');' + '' + '' + '-- Drop a temporary table called '#39'#tmpID'#39 + 'IF OBJECT_ID('#39'tempDB..#tmpID'#39', '#39'U'#39') IS NOT NULL' + ' DROP TABLE #tmpID;' + '' + 'CREATE TABLE #tmpID' + '(' + ' MemberID INT' + ')' + '' + '-- Members given a swimming lane in the given event ' + 'IF @EventType = 1' + 'BEGIN' + ' INSERT INTO #tmpID' + ' SELECT Entrant.MemberID' + ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' + ' INNER JOIN Entrant' + ' ON Entrant.HeatID = HeatIndividual.HeatID' + ' WHERE HeatIndividual.EventID = @EventID;' + 'END' + 'ELSE' + 'BEGIN' + ' INSERT INTO #tmpID' + ' SELECT TeamEntrant.MemberID' + ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' + ' INNER JOIN Team' + ' ON HeatIndividual.HeatID = Team.HeatID' + ' INNER JOIN TeamEntrant' + ' ON Team.TeamID = TeamEntrant.TeamID' + ' WHERE HeatIndividual.EventID = @EventID;' + 'END' + '' + '' + 'SELECT Nominee.EventID' + ' , Nominee.MemberID' + ' , Member.GenderID' + ' , dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE' + ' , dbo.SwimmerGenderToString(Member.MemberID) AS Gender' + + ' , dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, ' + + 'Member.MemberID, @DistanceID, @StrokeID, @SessionStart) AS TTB' + + ' , dbo.PersonalBest(Member.MemberID, @DistanceID, @StrokeID,' + + ' @SessionStart) AS PB' + ' , CASE' + ' WHEN @ToggleName = 0 THEN' + + ' SUBSTRING(CONCAT(UPPER([LastName]), '#39', '#39', [FirstN' + + 'ame]), 0, 30)' + ' WHEN @ToggleName = 1 THEN' + + ' SUBSTRING(CONCAT([FirstName], '#39', '#39', UPPER([LastNa' + + 'me])), 0, 48)' + ' END AS FName' + 'FROM Nominee' + ' LEFT OUTER JOIN #tmpID' + ' ON #tmpID.MemberID = Nominee.MemberID' + ' LEFT OUTER JOIN Member' + ' ON Nominee.MemberID = Member.MemberID' + 'WHERE Nominee.EventID = @EventID' + ' AND #tmpID.MemberID IS NULL ;' + '') + Left = 128 + Top = 152 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = 65 + end + item + Name = 'ALGORITHM' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end + item + Name = 'TOGGLENAME' + DataType = ftBoolean + ParamType = ptInput + Value = True + end + item + Name = 'CALCDEFAULT' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end + item + Name = 'BOTTOMPERCENT' + DataType = ftFloat + ParamType = ptInput + Value = 50.000000000000000000 + end + item + Name = 'EVENTTYPE' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end + item + Name = 'DISTANCEID' + DataType = ftInteger + ParamType = ptInput + Value = 1 + end> + object qryRNomineeFName: TWideStringField + DisplayLabel = 'Nominees' + DisplayWidth = 30 + FieldName = 'FName' + Origin = 'FName' + ReadOnly = True + Size = 60 + end + object qryRNomineeTTB: TTimeField + Alignment = taCenter + DisplayLabel = 'TimeToBeat' + DisplayWidth = 12 + FieldName = 'TTB' + Origin = 'TTB' + ReadOnly = True + DisplayFormat = 'nn:ss.zzz' + end + object qryRNomineePB: TTimeField + Alignment = taCenter + DisplayLabel = 'Personal Best' + DisplayWidth = 12 + FieldName = 'PB' + Origin = 'PB' + ReadOnly = True + DisplayFormat = 'nn:ss.zzz' + end + object qryRNomineeAGE: TIntegerField + Alignment = taLeftJustify + DisplayLabel = ' AGE' + DisplayWidth = 5 + FieldName = 'AGE' + Origin = 'AGE' + ReadOnly = True + DisplayFormat = '##0' + end + object qryRNomineeGender: TWideStringField + Alignment = taCenter + DisplayWidth = 9 + FieldName = 'Gender' + Origin = 'Gender' + ReadOnly = True + Size = 2 + end + object qryRNomineeMemberID: TIntegerField + FieldName = 'MemberID' + Origin = 'MemberID' + ProviderFlags = [pfInUpdate, pfInWhere, pfInKey] + end + object qryRNomineeEventID: TIntegerField + FieldName = 'EventID' + Origin = 'EventID' + end + object qryRNomineeGenderID: TIntegerField + FieldName = 'GenderID' + Origin = 'GenderID' + end + end + object dsRNominee: TDataSource + DataSet = qryRNominee + Left = 200 + Top = 152 + end + object qryCountRNominee: TFDQuery + ActiveStoredUsage = [auDesignTime] + FilterOptions = [foCaseInsensitive] + Filter = '[FName] LIKE '#39'%b%'#39 + Indexes = < + item + Active = True + Selected = True + Name = 'idxMemberFName' + Fields = 'FName' + end + item + Active = True + Name = 'idxMemberFNameDESC' + Fields = 'FName' + DescFields = 'FName' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxTTB' + Fields = 'TTB' + end + item + Active = True + Name = 'idxTTBDESC' + Fields = 'TTB' + DescFields = 'TTB' + Options = [soDescending] + end + item + Active = True + Name = 'idxPB' + Fields = 'PB' + end + item + Active = True + Name = 'idxPBDESC' + Fields = 'PB' + DescFields = 'PB' + Options = [soDescending] + end + item + Active = True + Name = 'idxAge' + Fields = 'AGE' + end + item + Active = True + Name = 'idxAgeDESC' + Fields = 'AGE' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxGender' + Fields = 'GenderID' + end + item + Active = True + Name = 'idxGenderDESC' + Fields = 'GenderID' + Options = [soDescNullLast, soDescending] + end> + IndexName = 'idxMemberFName' + DetailFields = 'MemberID' + Connection = SCM.scmConnection + FormatOptions.AssignedValues = [fvFmtDisplayTime] + FormatOptions.FmtDisplayTime = 'nn:ss.zzz' + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate, uvCheckReadOnly] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' + UpdateOptions.KeyFields = 'MemberID' + SQL.Strings = ( + 'USE SwimClubMeet;' + '' + '-- NOTE: event type must be 2 (TEAM)' + '-- find nominees in relay event.' + '-- exclude raced and closed heats.' + '--' + '-- count nominees NO in event.' + '' + 'DECLARE @EventID AS INT;' + 'SET @EventID = :EVENTID;' + '' + '-- Drop a temporary table called '#39'#tmpA'#39 + 'IF OBJECT_ID('#39'tempDB..#tmpA'#39', '#39'U'#39') IS NOT NULL' + ' DROP TABLE #tmpA;' + '' + 'CREATE TABLE #tmpA' + '(' + ' MemberID INT' + ')' + '' + '-- Members given a swimming lane in the given event ' + '' + ' INSERT INTO #tmpA' + ' SELECT TeamEntrant.MemberID' + ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' + ' INNER JOIN Team' + ' ON HeatIndividual.HeatID = Team.HeatID' + ' INNER JOIN TeamEntrant' + ' ON Team.TeamID = TeamEntrant.TeamID' + ' WHERE HeatIndividual.EventID = @EventID ' + ' AND HeatIndividual.HeatStatusID = 1;' + '' + 'SELECT Count(NomineeID) AS CountNominees' + 'FROM Nominee' + ' LEFT OUTER JOIN #tmpA' + ' ON #tmpA.MemberID = Nominee.MemberID' + ' LEFT OUTER JOIN Member' + ' ON Nominee.MemberID = Member.MemberID' + 'WHERE Nominee.EventID = @EventID' + ' AND #tmpA.MemberID IS NULL ;' + '') + Left = 400 + Top = 48 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + object WideStringField: TWideStringField + DisplayLabel = 'Nominees' + DisplayWidth = 30 + FieldName = 'FName' + Origin = 'FName' + ReadOnly = True + Size = 60 + end + object TimeField1: TTimeField + Alignment = taCenter + DisplayLabel = 'TimeToBeat' + DisplayWidth = 12 + FieldName = 'TTB' + Origin = 'TTB' + ReadOnly = True + DisplayFormat = 'nn:ss.zzz' + end + object TimeField2: TTimeField + Alignment = taCenter + DisplayLabel = 'Personal Best' + DisplayWidth = 12 + FieldName = 'PB' + Origin = 'PB' + ReadOnly = True + DisplayFormat = 'nn:ss.zzz' + end + object IntegerField1: TIntegerField + Alignment = taLeftJustify + DisplayLabel = ' AGE' + DisplayWidth = 5 + FieldName = 'AGE' + Origin = 'AGE' + ReadOnly = True + DisplayFormat = '##0' + end + object WideStringField2: TWideStringField + Alignment = taCenter + DisplayWidth = 9 + FieldName = 'Gender' + Origin = 'Gender' + ReadOnly = True + Size = 2 + end + object IntegerField2: TIntegerField + FieldName = 'MemberID' + Origin = 'MemberID' + ProviderFlags = [pfInUpdate, pfInWhere, pfInKey] + end + object IntegerField3: TIntegerField + FieldName = 'EventID' + Origin = 'EventID' + end + object IntegerField4: TIntegerField + FieldName = 'GenderID' + Origin = 'GenderID' + end + end +end diff --git a/AUTOBUILD/dmABRelayData.pas b/AUTOBUILD/dmABRelayData.pas new file mode 100644 index 0000000..0febc6d --- /dev/null +++ b/AUTOBUILD/dmABRelayData.pas @@ -0,0 +1,110 @@ +unit dmABRelayData; + +interface + +uses + System.SysUtils, System.Classes, FireDAC.Stan.Intf, FireDAC.Stan.Option, + FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, + FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, Data.DB, + FireDAC.Comp.DataSet, FireDAC.Comp.Client, dmSCM, + Windows, Winapi.Messages; + +type + TABRelayData = class(TDataModule) + qryXNominees: TFDQuery; + dsXNominees: TDataSource; + FDCommandUpdateEntrant: TFDCommand; + qryRNominee: TFDQuery; + qryRNomineeFName: TWideStringField; + qryRNomineeTTB: TTimeField; + qryRNomineePB: TTimeField; + qryRNomineeAGE: TIntegerField; + qryRNomineeGender: TWideStringField; + qryRNomineeMemberID: TIntegerField; + qryRNomineeEventID: TIntegerField; + qryRNomineeGenderID: TIntegerField; + dsRNominee: TDataSource; + qryCountRNominee: TFDQuery; + WideStringField: TWideStringField; + TimeField1: TTimeField; + TimeField2: TTimeField; + IntegerField1: TIntegerField; + WideStringField2: TWideStringField; + IntegerField2: TIntegerField; + IntegerField3: TIntegerField; + IntegerField4: TIntegerField; + private + { Private declarations } + fHandle: HWND; + fAutoBuildRelayDataActive: Boolean; + FConnection: TFDConnection; + + public + { Public declarations } + constructor CreateWithConnection(AOwner: TComponent; AConnection: TFDConnection); + constructor Create(AOwner: TComponent); override; + destructor Destroy(); override; + procedure ReadPreferences(aIniFileName: string); + procedure ActivateTable(); + + + end; + +var + ABRelayData: TABRelayData; + +implementation + +{%CLASSGROUP 'Vcl.Controls.TControl'} + +uses SCMUtility; + +{$R *.dfm} + +{ TAutoBuildRelayData } + +procedure TABRelayData.ActivateTable; +begin + fAutoBuildRelayDataActive := false; + if Assigned(FConnection) and FConnection.Connected then + begin + qryRNominee.Connection := FConnection; + qryRNominee.Open; + if qryRNominee.Active then + begin + fAutoBuildRelayDataActive := true; + end; + end; +end; + +constructor TABRelayData.Create(AOwner: TComponent); +var +IniFileName: string; +begin + inherited; +// fHandle := AllocateHWnd(WndProc); + + // r e a d p r e f e r e n c e . + IniFileName := SCMUtility.GetSCMPreferenceFileName(); + if (FileExists(IniFileName)) then ReadPreferences(IniFileName); +end; + +constructor TABRelayData.CreateWithConnection(AOwner: TComponent; + AConnection: TFDConnection); +begin + self.Create(AOwner); + FConnection := AConnection; +end; + +destructor TABRelayData.Destroy; +begin +// DeallocateHWND(fHandle); + inherited; +end; + +procedure TABRelayData.ReadPreferences(aIniFileName: string); +begin + ; +end; + +end. diff --git a/AUTOBUILD/uABRelayExec.pas b/AUTOBUILD/uABRelayExec.pas new file mode 100644 index 0000000..2c23360 --- /dev/null +++ b/AUTOBUILD/uABRelayExec.pas @@ -0,0 +1,577 @@ +unit uABRelayExec; + +interface + +uses +Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, +System.Classes, dmSCM, dmSCMHelper, +FireDAC.Comp.Client, FireDAC.Stan.Param, +Vcl.Forms, SCMDefines, Data.DB; + +type + + TABRelayExec = class(TComponent) + private + { private declarations } + prefExcludeOutsideLanes: boolean; + prefNumOfSwimmersPerTeam: integer; + prefVerbose: boolean; + prefHeatAlgorithm: integer; + prefRaceTimeTopPercent: integer; + + prefUseDefRaceTime: boolean; + prefSeperateGender: boolean; + rgpSeedMethod: integer; + spnSeedDepth: integer; + + FConnection: TFDConnection; + fEventID: integer; + fXDistanceID: integer; + fStrokeID: integer; + + function GetPoolLaneCount: integer; + function GetXDistanceID(EventID, teamSize: integer): integer; + function GetStrokeID(EventID: integer): integer; + + procedure ReadPreferences(IniFileName: string); + procedure RetriveSwimmingData(AEventID, poolLanes, teamSize: Integer); + procedure InsertIntoTeams(); + +// procedure Distribute(FDQuery: TFDQuery; AEventID: integer); + + protected + { protected declarations } + + public + { public declarations } + procedure ExecAutoBuildRelay(); + procedure Prepare(AConnection: TFDConnection; ASwimClubID, AEventID: Integer); + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + + published + { published declarations } + + end; + +implementation + +{ TAutoBuildRelayExec } + +uses System.Math, System.IniFiles, SCMUtility, uABRelayV1, + dmABRelayData; + +{ +procedure TAutoBuildRelayExec.Distribute(FDQuery: TFDQuery; AEventID: integer); +var + i, j, k, m, n, ALaneNum: Integer; + TeamRaceTime: TTIME; +begin + // Distribute swimmers into teams + i := 0; // Nominees - Swimmers + m := 0; // 0 .. NumOfTeams-1 + for j := 0 to NumOfHeats - 1 do + begin + // HEAT. + Heats[j].ID := j; + Heats[j].HeatNum := j+1; + Heats[j].EventID := AEventID; + for k := 0 to NumOfTeamsPerHeat - 1 do + begin + // TEAM. + Teams[m].ID := m; // Identifier 0...NumOfTeams-1 + inc(m, 1); + ALaneNum := ScatterLanes(k, NumOfPoolLanes); // index is base 0 + Teams[m].Lane := ALaneNum; + Teams[m].RaceTime := 0; + + // set the number of swimmers permitted in a relay team + SetLength(Teams[k].Entrants, NumOfEntrantsPerTeam); + TeamRaceTime := 0; + // fill relay team with entrants. + for n := 0 to NumOfEntrantsPerTeam - 1 do + begin + if (i <= high(Swimmers)) then + begin + // TEAMENTRANT. + Teams[k].Entrants[n] := i; + TeamRaceTime := TeamRaceTime + Swimmers[i].TTB; + end; + inc(i, 1); + end; + // total est.racetime for this relay-team + Teams[k].RaceTime := TeamRaceTime; + end; + // TEST : exceed number of teams in total + if m >= NumOfTeams then break; + end; +end; +} + + +constructor TABRelayExec.Create(AOwner: TComponent); +begin + inherited; + fEventID := 0; + FConnection := nil; + fXDistanceID := 1; + fStrokeID := 1; + + // I N I T I A L I Z E P R E F E R E N C E S . + prefExcludeOutsideLanes := false; + prefHeatAlgorithm := 1; + prefNumOfSwimmersPerTeam := 4; + prefRaceTimeTopPercent := 50; + prefVerbose := false; + prefRaceTimeTopPercent := 50; + prefUseDefRaceTime := true; + prefSeperateGender := false; + rgpSeedMethod := 0; + spnSeedDepth := 3; + +end; + +destructor TABRelayExec.Destroy; +begin + if assigned(ABRelayData) then + FreeAndNil( ABRelayData); + inherited; +end; + +procedure TABRelayExec.ExecAutoBuildRelay(); +var + IniFileName: TFileName; + v: variant; + poolLanes: integer; +begin + // A S S E R T . + // data module : use Prepare to initialize... + if not assigned(ABRelayData) then exit; + if fEventID = 0 then exit; + // is this a team event. + + prefExcludeOutsideLanes := false; + prefNumOfSwimmersPerTeam := 4; + + // r e a d p r e f e r e n c e . + IniFileName := SCMUtility.GetSCMPreferenceFileName(); + if (FileExists(IniFileName)) then + ReadPreferences(IniFileName); + + v := + SCM.scmConnection.ExecSQLScalar + ('SELECT EventTypeID FROM [dbo].[Event] WHERE EventID = :ID', [fEventID]); + if VarIsNull(v) then + begin + if prefVerbose then + Application.MessageBox('Event type not assigned.', 'SCM Error', MB_OK) + else + exit; // error - NULL. + end; + + if v <> 2 then + begin + if prefVerbose then + Application.MessageBox('The event isn''t a relay', 'SCM Error', MB_OK) + else + exit; // error - event type not a team event. + end; + + v := + SCM.scmConnection.ExecSQLScalar + ('SELECT StrokeID FROM [dbo].[Event] WHERE EventID = :ID', [fEventID]); + if VarIsNull(v) then exit; // error - unknow swimming stroke. + + if v = 5 then + if prefVerbose then + Application.MessageBox(''' + Medley relays cannot be auto-built by SwimClubMeet. + Auto-build will abort. + ''', + 'SwimClubMeet Error', + MB_ICONERROR or MB_OK) + else + exit; // error - relay-type = Medley + + + poolLanes := GetPoolLaneCount; + // ASSERT + if poolLanes = 0 then + if prefVerbose then + Application.MessageBox(''' + No pool lanes are available. Check swim-club preferences. + Note: Nominees for relays that have been raced or close are excluded. + Auto-build will abort. + ''', + 'SwimClubMeet Error', + MB_ICONERROR or MB_OK) + else + exit; + + // Test for nominees + v := 0; + ABRelayData.qryCountRNominee.Close; + ABRelayData.qryCountRNominee.ParamByName('EVENTID').AsInteger := fEventID; + ABRelayData.qryCountRNominee.Prepare; + ABRelayData.qryCountRNominee.Open; + if ABRelayData.qryCountRNominee.Active then + v := ABRelayData.qryCountRNominee.FieldByName('CountNominees').AsInteger; + + if VarIsNull(v) OR v=0 then + if prefVerbose then + Application.MessageBox(''' + No nominees were found for the team event. + Note: Nominees for this event that have been raced or close are excluded. + Auto-build will abort. + ''', + 'SwimClubMeet Error', + MB_ICONERROR or MB_OK) + else + exit; // error - NULL. + + + + {TODO -oBSA -cGeneral : Progress bar and captions ....} + + // Clean OUT ALL HEATs, TEAMs, TEAMEntrants, TEAMSPLITs. + // Does not remove raced or closed heats . + SCM.Heat_DeleteAll(fEventID, true); + + // perform Auto-Build. + RetriveSwimmingData(fEventID, poolLanes, prefNumOfSwimmersPerTeam); + + // Refresh UI and data + if Owner is TForm then + SendMessage(TForm(Owner).Handle, SCM_AUTOBUILDRELAYSFIN, 0, 0); +end; + +function TABRelayExec.GetPoolLaneCount: integer; +var + Lanes: integer; +begin + result := 0; + // Get number of lanes from dbo.SwimClub.NumOfLanes + Lanes := SCM.SwimClub_NumberOfLanes; + // Adjust lanes if excluding outside lanes + if (prefExcludeOutsideLanes) then Dec(Lanes, 2); + // Ensure there is at least one lane + if (Lanes < 1) then Exit; + result := Lanes; +end; + +function TABRelayExec.GetStrokeID(EventID: integer): integer; +var + v: variant; +begin + result := 0; + v := SCM.scmConnection.ExecSQLScalar + ('SELECT StrokeID FROM [dbo].[Event] WHERE [EventID] = :ID', [EventID]); + if not VarIsNull(v) then + result := v; +end; + +function TABRelayExec.GetXDistanceID(EventID, + teamSize: integer): integer; +var + v: variant; + meters: Integer; +begin + result := 0; + v := SCM.scmConnection.ExecSQLScalar(''' + SELECT[meters] + FROM[SwimClubMeet].[dbo].[Distance] + INNER JOIN[dbo].[Event] ON[Distance].[DistanceID] = [Event].[DistanceID] + WHERE EventID = :ID + ''', [EventID]); + if not VarIsNull(v) then + begin + // divide the total relay distance by the number of swimmers in the relay + meters := Floor(v/teamSize); + // cross reference value 'meters' to find the XDistanceID for a swimmer. + v := SCM.scmConnection.ExecSQLScalar(''' + SELECT[DistanceID] + FROM[SwimClubMeet].[dbo].[Distance] + WHERE[meters] = :ID + ''', [meters]); + if not VarIsNull(v) then + begin + result := v; + end; + end; +end; + +procedure TABRelayExec.InsertIntoTeams; +var + qry1, qry2, qry3: TFDQuery; + AHeatID: integer; + ATeamID: integer; + ATeamEntrantID: integer; + i, j, m, n, p, r: integer; +begin + + + + qry1 := TFDQuery.Create(nil); + qry2 := TFDQuery.Create(nil); + qry3 := TFDQuery.Create(nil); + + qry1.Connection := SCM.scmConnection; + qry2.Connection := SCM.scmConnection; + qry3.Connection := SCM.scmConnection; + + for j := Low(Heats) to High(Heats) do + Begin + // Insert into dbo.HeatIndividual + qry1.SQL.Text := 'INSERT INTO HeatIndividual (HeatNum, OpenDT, HeatTypeID, HeatStatusID) VALUES (:HEATNUM, GETDATE(), 1, 1)'; + qry1.ParamByName('HEATNUM').AsInteger := Heats[j].HeatNum; + qry1.ExecSQL; + // Get the new heat record's ID + AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); + + for i := 0 to NumOfTeamsPerHeat - 1 do + begin + // Insert into Team table + qry2.SQL.Text := 'INSERT INTO Team (Lane, TeamNameID, HeatID) VALUES (:LANE, :TEAMNAMEID, :HEATID)'; + qry2.ParamByName('LANE').AsInteger := Teams[i].Lane; + // dbo.TeamName is a table filled with ID's starting from 1, ending in 26 and Caption's TeamA...TeamZ. + // i is base 0. + // i is range 0 to NumOfTeams (unlikely, though not impossible, to exceed 25 (the number of team names - 1). + qry2.ParamByName('TEAMNAMEID').AsInteger := Teams[i].ID+1; // Base zero. + qry2.ParamByName('HEATID').AsInteger := AHeatID; // Base zero. + qry2.ExecSQL; + // Get the new team record's ID + ATeamID := qry2.Connection.GetLastAutoGenValue('TeamID'); + p := 1; // Swimming order 1 to NumOfEntrantsPerTeam. + // Insert into TeamEntrant table + for m := Low(Teams[i].Entrants) to High(Teams[i].Entrants) do + begin + qry3.SQL.Text := 'INSERT INTO TeamEntrant (MemberID, Lane, TeamID, PersonalBest, TimeToBeat) VALUES (:MemberID, :Lane, :TeamID, :PersonalBest, :TimeToBeat)'; + qry3.ParamByName('MemberID').AsInteger := Teams[i].Entrants[m]; + qry3.ParamByName('Lane').AsInteger := p; + qry3.ParamByName('TeamID').AsInteger := ATeamID; + // locate swimmer in Swimmers... + for r := Low(Swimmers) to High(Swimmers) do + begin + if Swimmers[r].MemberID = Teams[i].Entrants[m] then + begin + qry3.ParamByName('PersonalBest').AsTime := Swimmers[r].PB; + qry3.ParamByName('TimeToBeat').AsTime := Swimmers[r].TTB; + break; + end; + end; + qry3.ExecSQL; + inc(p, 1); + end; + end; + End; + + qry3.Close; + qry2.Close; + qry1.Close; + + qry1.Free; + qry2.Free; + qry3.Free; + +end; + +procedure TABRelayExec.Prepare(AConnection: TFDConnection; ASwimClubID, + AEventID: Integer); +begin + FConnection := AConnection; + fEventID := AEventID; + // ---------------------------------------------------- + // C R E A T E D A T A M O D U L E S C M . + // ---------------------------------------------------- + try + ABRelayData := TABRelayData.CreateWithConnection(Self, + FConnection); + finally + // with ManageMemberData created and the essential tables are open then + // asserting the connection should be true + if not assigned(ABRelayData) then + raise Exception.Create('Auto-Build Relay''s Data Module creation error.'); + end; +end; + +procedure TABRelayExec.ReadPreferences(IniFileName: string); +var + iFile: TIniFile; + i: Integer; +begin + iFile := TIniFile.Create(IniFileName); + + // When true gutter lanes are not used in events. + i := iFile.ReadInteger('Preferences', 'ExcludeOutsideLanes', 0); + prefExcludeOutsideLanes := (i = 1); + + // 2024-09-10 + // Relay teams, by default, have four swimmers. + prefNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', 'NumOfSwimmersPerTeam', 4); + // Verbose. Message all errors. + i := iFile.ReadInteger('Preferences', 'Verbose', 0); + prefVerbose := (i = 1); + + // TTB defaults to (1) .. the entrant's average of top 3 race-times + prefHeatAlgorithm := + (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); + + // default to - get an average race-time of other swimmers + i := iFile.ReadInteger('Preferences', 'UseDefRaceTime', 1); + prefUseDefRaceTime := (i = 1); + + // The bottom percent to select from ... default is 50% + prefRaceTimeTopPercent := + (iFile.ReadInteger('Preferences', 'RaceTimeTopPercent', 50)); + + i := iFile.ReadInteger('Preferences', 'SeperateGender', 0); + prefSeperateGender := (i = 1); + + // SCM default seeding. + rgpSeedMethod := iFile.ReadInteger('Preferences', 'SeedMethod', 0); + + // 2020-11-01 auto-build v2 seed depth for Circle Seed */ + spnSeedDepth := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); + + iFile.free; +end; + +procedure TABRelayExec.RetriveSwimmingData(AEventID, poolLanes, + teamSize: Integer); +var + FDQuery: TFDQuery; + IniFileName: TFilename; + StrokeID, xDistanceID, i: integer; +begin +{ +Retrieve Swimmer Data: Fetch swimmers’ data from the Nominee table, +focusing on their MemberID TTB (TimeToBeat) and PB (Personal Best) times. +} + + { + SET @EventID = :EVENTID; + SET @Algorithm = :ALGORITHM; + SET @ToggleName = :TOGGLENAME; + SET @CalcDefault = :CALCDEFAULT + SET @BottomPercent = :BOTTOMPERCENT + SET @EventType = :EVENTTYPE + SET @DistanceID = :DISTANCEID + + + ABRelayData.qryRNominee.Close; + ABRelayData.qryRNominee.ParamByName('EVENTID').AsInteger := fEventID; + ABRelayData.qryRNominee.ParamByName('ALGORITHM').AsInteger := prefHeatAlgorithm; + ABRelayData.qryRNominee.ParamByName('TOGGLENAME').AsBoolean := false; + ABRelayData.qryRNominee.ParamByName('CALCDEFAULT').AsInteger := prefRaceTimeTopPercent; + ABRelayData.qryRNominee.ParamByName('BOTTOMPERCENT').AsInteger := prefRaceTimeTopPercent; + ABRelayData.qryRNominee.ParamByName('EVENTTYPE').AsInteger := prefRaceTimeTopPercent; + ABRelayData.qryRNominee.ParamByName('DISTANCEID').AsInteger := XDistanceID; + + } + + +{ + poolLanes = swimming lanes - prefExcludeOutsideLanes + teamSize = number of swimmers in a relay-team. Default = 4; +} + + // r e a d p r e f e r e n c e . + IniFileName := SCMUtility.GetSCMPreferenceFileName(); + if (FileExists(IniFileName)) then + ReadPreferences(IniFileName); + + // Freestyle, backstroke, breaststroke, butterfly. + // This routine doesn't autobuild Medley-Relays + // StrokeID = 5 (Medley-Relays) illegal. + StrokeID := GetStrokeID(AEventID); + // XDistance = the distance each entrant in relay-team swims. + // WIT: 4x25m relay Total 100m ... entrants swim 25m. + xDistanceID := GetXDistanceID(AEventID, teamSize); + + // Assign params passed. + NumOfPoolLanes := poolLanes; + NumOfEntrantsPerTeam := teamSize; + + FDQuery := TFDQuery.Create(nil); + try + + FDQuery.Connection := SCM.scmConnection; // Your FireDAC connection + // Sort swimmers by personal best (PB) + // Order by ascending - FASTEST to SLOWEST + FDQuery.SQL.Text := ''' + SELECT + [NomineeID] + ,[SeedTime] + ,[AutoBuildFlag] + ,[EventID] + ,[MemberID] + -- Re-Calculate TimeToBeat... + -- algorithm default ... average of the 3 fastest racetimes + -- percentage default ... 50% + -- MemberID, 25m, freestyle, SesssionStart. + ,dbo.TimeToBeat(1, :ALGORITHM, :PERCENT, [MemberID], :XDISTANCEID, :STROKEID, :SESSIONSTART) AS TTB + ,dbo.PersonalBest([MemberID],:XDISTANCEID, :STROKEID, :SESSIONSTART) AS PB + FROM [dbo].[Nominee] + WHERE [EventID] = :EVENTID + ORDER BY [TTB] ASC +'''; + + FDQuery.ParamByName('EVENTID').AsInteger := AEventID; + FDQuery.ParamByName('SESSIONSTART').AsDateTime := SCM.Session_Start; + FDQuery.ParamByName('XDISTANCEID').AsDateTime := xDistanceID; + FDQuery.ParamByName('STROKEID').AsDateTime := StrokeID; + FDQuery.ParamByName('ALGORITHM').AsDateTime := prefHeatAlgorithm; + FDQuery.ParamByName('PERCENT').AsDateTime := prefRaceTimeTopPercent; + FDQuery.Open; + + if FDQuery.IsEmpty then + begin + FDQuery.Close; + FDQuery.Free; + exit; + end; + + // the number of club members who nominated for the relay event. + NumOfNominees := FDQuery.RecordCount; + // the number of teams + NumOfTeams := Ceil(NumOfNominees / teamsize); + // ASSERT + if NumOfTeams = 0 then exit; + // check that the number of relay-teams doesn't exceed the number of lanes. + if NumOfTeams > poolLanes then + NumOfHeats := Ceil(NumOfTeams / poolLanes); + + NumOfTeamsPerHeat := Ceil(NumOfTeams / NumOfHeats); + + SetLength(Swimmers, NumOfNominees); + + // iterate through the list of nominees + i := 0; + while not FDQuery.Eof do + begin + Swimmers[i].MemberID := FDQuery.FieldByName('MemberID').AsInteger; + Swimmers[i].NomineeID := FDQuery.FieldByName('NomineeID').AsInteger; + Swimmers[i].TTB := FDQuery.FieldByName('TTB').AsDateTime; + Swimmers[i].pB := FDQuery.FieldByName('pB').AsDateTime; + Inc(i, 1); + FDQuery.Next; + end; + + SetLength(Heats, NumOfHeats); + SetLength(Teams, NumOfTeams); + + + // Process the data + // DistributeTeams(FDQuery); + + // Process the data using Genetic Algorithm + GeneticAlgorithm(); + + InsertIntoTeams(); + + finally + FDQuery.Free; + end; +end; + +end. diff --git a/AUTOBUILD/uABRelayV1.pas b/AUTOBUILD/uABRelayV1.pas new file mode 100644 index 0000000..681dee0 --- /dev/null +++ b/AUTOBUILD/uABRelayV1.pas @@ -0,0 +1,240 @@ +unit uABRelayV1; + +interface + +uses +System.Classes, FireDAC.Comp.Client, FireDAC.Stan.Param, dmSCM, dmSCMHelper, + SCMUtility, System.SysUtils; + +// Bin packing algorithm in Delphi using a genetic algorithm +// Define the container and item types +type + + TTeam = record + // L A N E >>> R E L A Y - T E A M . + // (Typically a relay-team holds four swimmers) + ID: Integer; // IDENITY of the Team in dbo.Team. + Lane: Integer; // Lane number. + RaceTime: TTime; // The SUM of all PBs for swimmers in the relay-team. + Entrants: array of integer; // Holds memberID - 4 x swimmers per lane. + HeatID: Integer; + end; + + TChromosome = array of TTeam; + + THeat = record + // H E A T . + ID: integer; + HeatNum: integer; + EventID: integer; + end; + + TSwimmer = record + // S W I M M E R . + NomineeID: Integer; // Nomination ID for the swimmer in dbo.Nominee. + MemberID: Integer; // Member ID for the swimmer used in dbo.Member table + TTB: TTime; // Time-to-beat. Estimated (calculated) swimming for XDistance. + PB: TTime; // Personal Best swimming time for the swimmer for the event. + end; + +var + + NumOfNominees: integer; // Count of nominees. + NumOfTeams: integer; // Count of relay-teams. + NumOfHeats: integer; // Count of heats. + NumOfTeamsPerHeat: integer; // Count of relay-teams in each heat. + NumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. + NumOfEntrantsPerTeam: integer; + + Population: array of TChromosome; + PopulationSize: Integer = 100; + Generations: Integer = 1000; + MutationRate: Double = 0.01; + + // D Y N A M I C A R R A Y S . + // Swimmers who nominated for the event. + Swimmers: array of TSwimmer; + // [HeatID][TeamID]. An array of heats - each containing a relay-team. + Heats: array of THeat; + // If the number of Relay-Teams exceeds the number of lanes + // then multi-heats are required. + Teams: array of TTeam; + + // R E F E R E N C E S . + prefExcludeOutsideLanes: boolean; + prefHeatAlgorithm: integer; + prefNumOfSwimmersPerTeam: integer; + prefRaceTimeTopPercent: integer; + + procedure ReadPreferences(IniFileName: string); + procedure GeneticAlgorithm(); + +implementation + +uses System.Math, system.Generics.Collections, System.IniFiles, +System.Variants, Data.DB; + +procedure ReadPreferences(IniFileName: string); +var + iFile: TIniFile; +begin + iFile := TIniFile.Create(IniFileName); + + // When true gutter lanes are not used in events. + prefExcludeOutsideLanes := (iFile.ReadInteger('Preferences', + 'ExcludeOutsideLanes', 0) = 1); + // Relay teams, by default, have four swimmers. + prefNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', + 'NumOfSwimmersPerTeam', 4); + // TTB defaults to (1) .. the entrant's average of top 3 race-times + prefHeatAlgorithm := (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); + + // The bottom percent to select from ... default is 50% + prefRaceTimeTopPercent := + (iFile.ReadInteger('Preferences', 'RaceTimeTopPercent', 50)); + + iFile.free; +end; + +function InitializePopulation: TChromosome; +var + i, j: Integer; + Chromosome: TChromosome; +begin + SetLength(Chromosome, NumOfTeams); + for i := 0 to NumOfTeams - 1 do + begin + Chromosome[i].ID := i; + Chromosome[i].Lane := i mod NumOfPoolLanes; + SetLength(Chromosome[i].Entrants, NumOfEntrantsPerTeam); + for j := 0 to NumOfEntrantsPerTeam - 1 do + begin + Chromosome[i].Entrants[j] := Random(NumOfNominees); + end; + end; + Result := Chromosome; +end; + +function Fitness(Chromosome: TChromosome): Double; +var + i, j: Integer; + TotalTime, MaxTime, MinTime: TTime; +begin + MaxTime := 0; + MinTime := MaxInt; + TotalTime := 0; + for i := 0 to High(Chromosome) do + begin + Chromosome[i].RaceTime := 0; + for j := 0 to High(Chromosome[i].Entrants) do + begin + Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; + end; + if Chromosome[i].RaceTime > MaxTime then + MaxTime := Chromosome[i].RaceTime; + if Chromosome[i].RaceTime < MinTime then + MinTime := Chromosome[i].RaceTime; + TotalTime := TotalTime + Chromosome[i].RaceTime; + end; + Result := MaxTime - MinTime; +end; + +function TournamentSelection: TChromosome; +var + i, BestIndex: Integer; + BestFitness, CurrentFitness: Double; +begin + BestIndex := Random(PopulationSize); + BestFitness := Fitness(Population[BestIndex]); + for i := 1 to 4 do + begin + CurrentFitness := Fitness(Population[Random(PopulationSize)]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestIndex := i; + end; + end; + Result := Population[BestIndex]; +end; + +function Crossover(Parent1, Parent2: TChromosome): TChromosome; +var + i, CrossoverPoint: Integer; + Child: TChromosome; +begin + SetLength(Child, Length(Parent1)); + CrossoverPoint := Random(Length(Parent1)); + for i := 0 to CrossoverPoint do + Child[i] := Parent1[i]; + for i := CrossoverPoint + 1 to High(Parent2) do + Child[i] := Parent2[i]; + Result := Child; +end; + +procedure Swap(var A, B: Integer); +var + Temp: Integer; +begin + Temp := A; + A := B; + B := Temp; +end; + +procedure Mutate(var Chromosome: TChromosome); +var + Team1, Team2, Swimmer1, Swimmer2: Integer; +begin + if Random < MutationRate then + begin + Team1 := Random(Length(Chromosome)); + Team2 := Random(Length(Chromosome)); + Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); + Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); + Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); + end; +end; + +procedure GeneticAlgorithm; +var + i, j: Integer; + Parent1, Parent2, Child: TChromosome; + BestChromosome: TChromosome; + BestFitness, CurrentFitness: Double; +begin + SetLength(Population, PopulationSize); + for i := 0 to PopulationSize - 1 do + Population[i] := InitializePopulation; + + for i := 0 to Generations - 1 do + begin + for j := 0 to PopulationSize - 1 do + begin + Parent1 := TournamentSelection; + Parent2 := TournamentSelection; + Child := Crossover(Parent1, Parent2); + Mutate(Child); + Population[j] := Child; + end; + end; + + BestChromosome := Population[0]; + BestFitness := Fitness(BestChromosome); + for i := 1 to PopulationSize - 1 do + begin + CurrentFitness := Fitness(Population[i]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestChromosome := Population[i]; + end; + end; + + // Use BestChromosome to set your final teams + SetLength(Teams, Length(BestChromosome)); + for i := 0 to High(BestChromosome) do + Teams[i] := BestChromosome[i]; +end; + + +end. diff --git a/AutoBuildRelayAlgorithm.pas b/AutoBuildRelayAlgorithm.pas deleted file mode 100644 index 312533d..0000000 --- a/AutoBuildRelayAlgorithm.pas +++ /dev/null @@ -1,474 +0,0 @@ -unit AutoBuildRelayAlgorithm; - -interface - -uses -System.Classes, FireDAC.Comp.Client, FireDAC.Stan.Param, dmSCM, dmSCMHelper, - SCMUtility, System.SysUtils; - -// Bin packing algorithm in Delphi using a genetic algorithm - -// Define the container and item types -type - - TTeam = record - // L A N E >>> R E L A Y - T E A M . - // (Typically a relay-team holds four swimmers) - ID: Integer; // IDENITY of the Team in dbo.Team. - Lane: Integer; // Lane number. - RaceTime: TTime; // The SUM of all PBs for swimmers in the relay-team. - Entrants: array of integer; // Holds memberID - 4 x swimmers per lane. - HeatID: Integer; - end; - - TChromosome = array of TTeam; - - - THeat = record - // H E A T . - ID: integer; - HeatNum: integer; - EventID: integer; - end; - - TSwimmer = record - // S W I M M E R . - NomineeID: Integer; // Nomination ID for the swimmer in dbo.Nominee. - MemberID: Integer; // Member ID for the swimmer used in dbo.Member table - TTB: TTime; // Time-to-beat. Estimated (calculated) swimming for XDistance. - PB: TTime; // Personal Best swimming time for the swimmer for the event. - end; - -var - NumOfNominees: integer; // Count of nominees. - NumOfTeams: integer; // Count of relay-teams. - NumOfHeats: integer; // Count of heats. - NumOfTeamsPerHeat: integer; // Count of relay-teams in each heat. - NumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. - NumOfEntrantsPerTeam: integer; - - Population: array of TChromosome; - PopulationSize: Integer = 100; - Generations: Integer = 1000; - MutationRate: Double = 0.01; - - // D Y N A M I C A R R A Y S . - // Swimmers who nominated for the event. - Swimmers: array of TSwimmer; - // [HeatID][TeamID]. An array of heats - each containing a relay-team. - Heats: array of THeat; - // If the number of Relay-Teams exceeds the number of lanes - // then multi-heats are required. - Teams: array of TTeam; - - function GetStrokeID(EventID: integer): integer; - function GetXDistanceID(EventID, teamSize: integer): integer; - - -implementation - -uses System.Math, system.Generics.Collections, System.IniFiles, System.Variants; - -function InitializePopulation: TChromosome; -var - i, j: Integer; - Chromosome: TChromosome; -begin - SetLength(Chromosome, NumOfTeams); - for i := 0 to NumOfTeams - 1 do - begin - Chromosome[i].ID := i; - Chromosome[i].Lane := i mod NumOfPoolLanes; - SetLength(Chromosome[i].Entrants, NumOfEntrantsPerTeam); - for j := 0 to NumOfEntrantsPerTeam - 1 do - begin - Chromosome[i].Entrants[j] := Random(NumOfNominees); - end; - end; - Result := Chromosome; -end; - -function Fitness(Chromosome: TChromosome): Double; -var - i, j: Integer; - TotalTime, MaxTime, MinTime: TTime; -begin - MaxTime := 0; - MinTime := MaxInt; - TotalTime := 0; - for i := 0 to High(Chromosome) do - begin - Chromosome[i].RaceTime := 0; - for j := 0 to High(Chromosome[i].Entrants) do - begin - Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; - end; - if Chromosome[i].RaceTime > MaxTime then - MaxTime := Chromosome[i].RaceTime; - if Chromosome[i].RaceTime < MinTime then - MinTime := Chromosome[i].RaceTime; - TotalTime := TotalTime + Chromosome[i].RaceTime; - end; - Result := MaxTime - MinTime; -end; - -function TournamentSelection: TChromosome; -var - i, BestIndex: Integer; - BestFitness, CurrentFitness: Double; -begin - BestIndex := Random(PopulationSize); - BestFitness := Fitness(Population[BestIndex]); - for i := 1 to 4 do - begin - CurrentFitness := Fitness(Population[Random(PopulationSize)]); - if CurrentFitness < BestFitness then - begin - BestFitness := CurrentFitness; - BestIndex := i; - end; - end; - Result := Population[BestIndex]; -end; - -function Crossover(Parent1, Parent2: TChromosome): TChromosome; -var - i, CrossoverPoint: Integer; - Child: TChromosome; -begin - SetLength(Child, Length(Parent1)); - CrossoverPoint := Random(Length(Parent1)); - for i := 0 to CrossoverPoint do - Child[i] := Parent1[i]; - for i := CrossoverPoint + 1 to High(Parent2) do - Child[i] := Parent2[i]; - Result := Child; -end; - -procedure Swap(var A, B: Integer); -var - Temp: Integer; -begin - Temp := A; - A := B; - B := Temp; -end; - - -procedure Mutate(var Chromosome: TChromosome); -var - Team1, Team2, Swimmer1, Swimmer2: Integer; -begin - if Random < MutationRate then - begin - Team1 := Random(Length(Chromosome)); - Team2 := Random(Length(Chromosome)); - Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); - Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); - Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); - end; -end; - -procedure GeneticAlgorithm; -var - i, j: Integer; - Parent1, Parent2, Child: TChromosome; - BestChromosome: TChromosome; - BestFitness, CurrentFitness: Double; -begin - SetLength(Population, PopulationSize); - for i := 0 to PopulationSize - 1 do - Population[i] := InitializePopulation; - - for i := 0 to Generations - 1 do - begin - for j := 0 to PopulationSize - 1 do - begin - Parent1 := TournamentSelection; - Parent2 := TournamentSelection; - Child := Crossover(Parent1, Parent2); - Mutate(Child); - Population[j] := Child; - end; - end; - - BestChromosome := Population[0]; - BestFitness := Fitness(BestChromosome); - for i := 1 to PopulationSize - 1 do - begin - CurrentFitness := Fitness(Population[i]); - if CurrentFitness < BestFitness then - begin - BestFitness := CurrentFitness; - BestChromosome := Population[i]; - end; - end; - - // Use BestChromosome to set your final teams - SetLength(Teams, Length(BestChromosome)); - for i := 0 to High(BestChromosome) do - Teams[i] := BestChromosome[i]; -end; - - - -function GetStrokeID(EventID: integer): integer; -var -v: variant; -begin - result := 0; - v := SCM.scmConnection.ExecSQLScalar('SELECT StrokeID FROM [dbo].[Event] WHERE [EventID] = :ID',[EventID]); - if not VarIsNull(v) then - result := v; -end; - -function GetXDistanceID(EventID, teamSize: Integer): Integer; -var - v: variant; - meters: Integer; -begin - result := 0; - v := SCM.scmConnection.ExecSQLScalar(''' - - SELECT[meters] - FROM[SwimClubMeet].[dbo].[Distance] - INNER JOIN[dbo].[Event] ON[Distance].[DistanceID] = [Event].[DistanceID] - WHERE EventID = :ID - ''', [EventID]); - if not VarIsNull(v) then - begin - // divide the total relay distance by the number of swimmers in the relay - meters := Floor(v/teamSize); - - // cross reference value 'meters' to find the XDistanceID for a swimmer. - v := SCM.scmConnection.ExecSQLScalar(''' - - SELECT[DistanceID] - FROM[SwimClubMeet].[dbo].[Distance] - WHERE[meters] = :ID - ''', [meters]); - if not VarIsNull(v) then - begin - result := v; - end; - end; -end; - - -procedure DistributeTeams(FDQuery: TFDQuery); -var - i, j, k, m, n, AHeatID, AEventID, ATeamID, ATeamEntrantID, ALaneNum: Integer; - TeamRaceTime: TTIME; -begin - i := 0; - // iterate through the list of nominees - while not FDQuery.Eof do - begin - Swimmers[i].MemberID := FDQuery.FieldByName('MemberID').AsInteger; - Swimmers[i].NomineeID := FDQuery.FieldByName('NomineeID').AsInteger; - Swimmers[i].TTB := FDQuery.FieldByName('TTB').AsDateTime; - Inc(i); - FDQuery.Next; - end; - - // Distribute swimmers into teams - i := 0; // Nominees - Swimmers - m := 0; // 0 .. NumOfTeams-1 - for j := 0 to NumOfHeats - 1 do - begin - // HEAT. - Heats[j].ID := j; - Heats[j].HeatNum := j+1; - Heats[j].EventID := AEventID; - for k := 0 to NumOfTeamsPerHeat - 1 do - begin - // TEAM. - Teams[m].ID := m; // Identifier 0...NumOfTeams-1 - inc(m, 1); - ALaneNum := ScatterLanes(k, NumOfPoolLanes); // index is base 0 - Teams[m].Lane := ALaneNum; - Teams[m].RaceTime := 0; - - // set the number of swimmers permitted in a relay team - SetLength(Teams[k].Entrants, NumOfEntrantsPerTeam); - TeamRaceTime := 0; - // fill relay team with entrants. - for n := 0 to NumOfEntrantsPerTeam - 1 do - begin - if (i <= high(Swimmers)) then - begin - // TEAMENTRANT. - Teams[k].Entrants[n] := i; - TeamRaceTime := TeamRaceTime + Swimmers[i].TTB; - end; - inc(i, 1); - end; - // total est.racetime for this relay-team - Teams[k].RaceTime := TeamRaceTime; - end; - // TEST : exceed number of teams in total - if m >= NumOfTeams then break; - end; -end; - - -procedure InsertIntoTeams(); -var - qry1, qry2, qry3: TFDQuery; - AHeatID: integer; - ATeamID: integer; - ATeamEntrantID: integer; - i: integer; - j: integer; - k: integer; - m: integer; - n: integer; - p: integer; - r: integer; -begin - k := 0; - - for j := Low(Heats) to High(Heats) do - Begin - // Insert into dbo.HeatIndividual - qry1.SQL.Text := 'INSERT INTO HeatIndividual (HeatNum, OpenDT, HeatTypeID, HeatStatusID) VALUES (:HEATNUM, GETDATE(), 1, 1)'; - qry1.ParamByName('HEATNUM').AsInteger := Heats[j].HeatNum; - qry1.ExecSQL; - // Get the new heat record's ID - AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); - - for i := 0 to NumOfTeamsPerHeat - 1 do - begin - // Insert into Team table - qry2.SQL.Text := 'INSERT INTO Team (Lane, TeamNameID, HeatID) VALUES (:LANE, :TEAMNAMEID, :HEATID)'; - qry2.ParamByName('LANE').AsInteger := Teams[i].Lane; - // dbo.TeamName is a table filled with ID's starting from 1, ending in 26 and Caption's TeamA...TeamZ. - // i is base 0. - // i is range 0 to NumOfTeams (unlikely, though not impossible, to exceed 25 (the number of team names - 1). - qry2.ParamByName('TEAMNAMEID').AsInteger := Teams[i].ID+1; // Base zero. - qry2.ParamByName('HEATID').AsInteger := AHeatID; // Base zero. - qry2.ExecSQL; - // Get the new team record's ID - ATeamID := qry2.Connection.GetLastAutoGenValue('TeamID'); - p := 1; // Swimming order 1 to NumOfEntrantsPerTeam. - // Insert into TeamEntrant table - for m := Low(Teams[i].Entrants) to High(Teams[i].Entrants) do - begin - qry3.SQL.Text := 'INSERT INTO TeamEntrant (MemberID, Lane, TeamID, PersonalBest, TimeToBeat) VALUES (:MemberID, :Lane, :TeamID, :PersonalBest, :TimeToBeat)'; - qry3.ParamByName('MemberID').AsInteger := Teams[i].Entrants[m]; - qry3.ParamByName('Lane').AsInteger := p; - qry3.ParamByName('TeamID').AsInteger := ATeamID; - // locate swimmer in Swimmers... - for r := Low(Swimmers) to High(Swimmers) do - begin - if Swimmers[r].MemberID = Teams[i].Entrants[m] then - begin - qry3.ParamByName('PersonalBest').AsTime := Swimmers[r].PB; - qry3.ParamByName('TimeToBeat').AsTime := Swimmers[r].TTB; - break; - end; - end; - qry3.ExecSQL; - inc(p, 1); - end; - inc(k, 1); - end; - End; -end; - - - -{ -Retrieve Swimmer Data: Fetch the 16 swimmers’ data from the Nominee table, -focusing on their MemberID and PB (Personal Best) times. -} - - -procedure RetriveSwimmingData(AEventID, poolLanes, teamSize: Integer); -var - FDQuery: TFDQuery; - IniFileName: TFilename; - StrokeID, xDistanceID: integer; -begin - - FDQuery := TFDQuery.Create(nil); - try - - StrokeID := GetStrokeID(AEventID); - xDistanceID := GetXDistanceID(AEventID, teamSize); - NumOfPoolLanes := poolLanes; - NumOfEntrantsPerTeam := teamSize; - - FDQuery.Connection := SCM.scmConnection; // Your FireDAC connection - // Sort swimmers by personal best (PB) - // Order by ascending - FASTEST to SLOWEST - FDQuery.SQL.Text := ''' - SELECT - [NomineeID] - ,[PB] - ,[SeedTime] - ,[AutoBuildFlag] - ,[EventID] - ,[MemberID] - -- Re-Calculate TimeToBeat... - -- algorithm 1 ... average of the 3 fastest racetimes - -- 1, 50%, MemberID, 25m, freestyle, SesssionStart. - ,dbo.TimeToBeat(1, default, default, [MemberID], :XDISTANCEID, :STROKEID, :SESSIONSTART) AS TTB - FROM [dbo].[Nominee] - WHERE [EventID] = :EVENTID - ORDER BY [TTB] ASC -'''; - - - //'SELECT MemberID, PB FROM dbo.Nominee WHERE EventID = :ID ORDER BY PB ASC'; - - - FDQuery.ParamByName('EVENTID').AsInteger := AEventID; - FDQuery.ParamByName('SESSIONSTART').AsDateTime := SCM.Session_Start; - FDQuery.ParamByName('XDISTANCEID').AsDateTime := xDistanceID; - FDQuery.ParamByName('STROKEID').AsDateTime := StrokeID; - FDQuery.Open; - - if FDQuery.IsEmpty then - begin - FDQuery.Close; - FDQuery.Free; - exit; - end; - - // the number of club members who nominated for the relay event. - NumOfNominees := FDQuery.RecordCount; - // the number of teams - NumOfTeams := Ceil(NumOfNominees / teamsize); - if NumOfTeams = 0 then exit; - // check that the number of relay-teams doesn't exceed the number of lanes. - if NumOfTeams > poolLanes then - NumOfHeats := Ceil(NumOfTeams / poolLanes); - - NumOfTeamsPerHeat := Ceil(NumOfTeams / NumOfHeats); - - SetLength(Swimmers, NumOfNominees); - SetLength(Heats, NumOfHeats); - SetLength(Teams, NumOfTeams); - - // Clean OUT ALL HEATs, TEAMs, TEAMSPLITs. - SCM.Heat_DeleteAll(AEventID); - - // Process the data - // DistributeTeams(FDQuery); - - // Process the data using Genetic Algorithm - GeneticAlgorithm(); - - InsertIntoTeams(); - - finally - FDQuery.Free; - end; -end; - - - - - -end. diff --git a/SwimClubMeet.dpr b/SwimClubMeet.dpr index 37d5c70..2998d28 100644 --- a/SwimClubMeet.dpr +++ b/SwimClubMeet.dpr @@ -13,7 +13,6 @@ uses dlgNewSession in 'dlgNewSession.pas' {NewSession}, dlgPointsScored in 'dlgPointsScored.pas' {PointsScored}, dlgPreferences in 'dlgPreferences.pas' {Preferences}, - dmAutoBuildV2 in 'dmAutoBuildV2.pas' {AutoBuildV2: TDataModule}, dmReports in 'dmReports.pas' {RPTS: TDataModule}, dmSCM in 'dmSCM.pas' {SCM: TDataModule}, dmSCMNom in 'dmSCMNom.pas' {SCMNom: TDataModule}, @@ -68,8 +67,6 @@ uses frame_TEAM in 'FRAME\frame_TEAM.pas' {frameTEAM: TFrame}, frame_INDV in 'FRAME\frame_INDV.pas' {frameINDV: TFrame}, dlgDCodePicker in 'dlgDCodePicker.pas' {DCodePicker}, - AutoBuildRelayAlgorithm in 'AutoBuildRelayAlgorithm.pas', - dlgAutoBuild_Relay in 'dlgAutoBuild_Relay.pas' {AutoBuild_Relay}, dlgTeamNameMenu in 'FRAME\dlgTeamNameMenu.pas' {TeamNameMenu}, SCMHelpers in '..\SCM_SHARED\SCMHelpers.pas', dmSCMHelper in 'dmSCMHelper.pas', @@ -85,7 +82,11 @@ uses dlgSwimClubManage in 'dlgSwimClubManage.pas' {SwimClubManage}, dlgMemberClub in 'MEMBERS\dlgMemberClub.pas' {MemberClub}, rptMemberCheckData in 'MEMBERS\rptMemberCheckData.pas' {MemberCheckData: TDataModule}, - dmMemberHouse in 'MEMBERS\dmMemberHouse.pas' {MemberHouse: TDataModule}; + dmMemberHouse in 'MEMBERS\dmMemberHouse.pas' {MemberHouse: TDataModule}, + uABRelayV1 in 'AUTOBUILD\uABRelayV1.pas', + uABRelayExec in 'AUTOBUILD\uABRelayExec.pas', + dmABRelayData in 'AUTOBUILD\dmABRelayData.pas' {ABRelayData: TDataModule}, + dlgABRelay in 'AUTOBUILD\dlgABRelay.pas' {ABRelay}; {$R *.res} diff --git a/SwimClubMeet.dproj b/SwimClubMeet.dproj index 3d39af0..2ceae29 100644 --- a/SwimClubMeet.dproj +++ b/SwimClubMeet.dproj @@ -4,7 +4,7 @@ 20.1 VCL True - Release + Debug Win32 1 Application @@ -64,7 +64,7 @@ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - DBXSqliteDriver;RESTComponents;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;fsDB27;vclFireDAC;bindcompvclsmp;emsclientfiredac;tethering;svnui;DataSnapFireDAC;FireDACADSDriver;frx27;DBXMSSQLDriver;fsTee27;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;frxIBX27;DBXOracleDriver;inetdb;fs27;FmxTeeUI;emsedge;fmx;FireDACIBDriver;fmxdae;vcledge;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;frxTee27;dsnap;emsclient;DataSnapCommon;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;CloudService;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;IndyIPCommon;bindcompdbx;fsFD27;frxDB27;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;bindcompvclwinx;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;frxIntIOIndy27;TeeDB;FireDAC;fsIBX27;emshosting;frxIntIO27;frxOpenFiltersTemp27;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;frxFD27;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;emsserverresource;DbxClientDriver;ibxbindings;DBXSybaseASADriver;CustomIPTransport;vcldsnap;DOSCommandDR;bindcomp;appanalytics;frxADO27;DBXInformixDriver;IndyIPClient;fsADO27;bindcompvcl;frxe27;TeeUI;dbxcds;VclSmp;frxDBX27;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;frxcs27;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;$(DCC_UsePackage) + DBXSqliteDriver;RESTComponents;fmxase;DBXDb2Driver;DBXInterBaseDriver;vclactnband;fsDB27;vclFireDAC;bindcompvclsmp;emsclientfiredac;tethering;svnui;DataSnapFireDAC;FireDACADSDriver;frx27;DBXMSSQLDriver;fsTee27;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;frxIBX27;DBXOracleDriver;inetdb;fs27;FmxTeeUI;emsedge;fmx;FireDACIBDriver;fmxdae;vcledge;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;frxTee27;dsnap;emsclient;DataSnapCommon;FireDACCommon;RESTBackendComponents;DataSnapConnectors;VCLRESTComponents;soapserver;vclie;bindengine;DBXMySQLDriver;CloudService;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;FireDACCommonDriver;DataSnapClient;inet;IndyIPCommon;bindcompdbx;fsFD27;frxDB27;vcl;IndyIPServer;DBXSybaseASEDriver;IndySystem;FireDACDb2Driver;bindcompvclwinx;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;frxIntIOIndy27;TeeDB;FireDAC;fsIBX27;emshosting;frxIntIO27;frxOpenFiltersTemp27;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;frxFD27;DBXOdbcDriver;FireDACTDataDriver;FMXTee;soaprtl;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;vclwinx;FireDACDSDriver;rtl;emsserverresource;DbxClientDriver;ibxbindings;DBXSybaseASADriver;CustomIPTransport;vcldsnap;DOSCommandDR;bindcomp;appanalytics;frxADO27;DBXInformixDriver;IndyIPClient;fsADO27;bindcompvcl;frxe27;TeeUI;dbxcds;VclSmp;frxDBX27;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;frxcs27;dsnapxml;DataSnapProviderClient;dbrtl;IndyProtocols;inetdbxpress;FireDACMongoDBDriver;DataSnapServerMidas;JclDeveloperTools;Jcl;JclVcl;JclContainers;JvCore;JvSystem;JvStdCtrls;JvAppFrm;JvBands;JvCustom;JvControls;JvDB;JvDlgs;JvDocking;JvDotNetCtrls;JvCrypt;JvGlobus;JvHMI;JvPascalInterpreter;JvJans;JvManagedThreads;JvCmp;JvMM;JvNet;JvPageComps;JvPluginSystem;JvPrintPreview;JvRuntimeDesign;JvTimeFramework;JvWizards;JvXPCtrls;$(DCC_UsePackage) Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) Debug true @@ -154,11 +154,6 @@
Preferences
dfm - -
AutoBuildV2
- dfm - TDataModule -
RPTS
dfm @@ -384,11 +379,6 @@
DCodePicker
dfm
- - -
AutoBuild_Relay
- dfm -
TeamNameMenu
dfm @@ -453,6 +443,17 @@ dfm TDataModule
+ + + +
ABRelayData
+ dfm + TDataModule +
+ +
ABRelay
+ dfm +
@@ -476,38 +477,6 @@ SwimClubMeet.dpr - JCL Debug IDE extension - JCL Project Analyzer - JCL Open and Save IDE dialogs with favorite folders - JCL Package containing repository wizards - JCL Debug Window of XMM registers - JCL Stack Trace Viewer - JVCL Application and Form Components - JVCL Band Objects - JVCL Non-Visual Components - JVCL Visual Controls - JVCL Encryption and Compression - JVCL Custom Controls - JVCL Database Components - JVCL Dialog Components - JVCL Docking Components - JVCL DotNet Controls - JVCL Globus Components - JVCL HMI Controls - JVCL Jans Components - JVCL Managed Threads - JVCL Multimedia and Image Components - JVCL Network Components - JVCL Page Style Components - JVCL Interpreter Components - JVCL Plugin Components - JVCL Print Preview Components - JVCL Runtime Design Components - JVCL Standard Controls - JVCL System Components - JVCL Time Framework - JVCL Wizard - JVCL XP Controls Embarcadero C++Builder Office 2000 Servers Package Embarcadero C++Builder Office XP Servers Package Microsoft Office 2000 Sample Automation Server Wrapper Components diff --git a/SwimClubMeet.res b/SwimClubMeet.res index 221554c537aea72c3208636f7318c2dc619fc577..3871fd538708d68a945bbb8eeb7e290fef1a57b8 100644 GIT binary patch delta 66 zcmaFxkLSTZo(Uy_j0_A6tPCKMfx#Y#nKrftGqM^m=rI^hzRTFpXuLU>$)AzYw0SRc R`(9>7AZFUWmzjC7H2^U25m*2K delta 66 zcmaFxkLSTZo(Uy_3=E76>_7|x_8?+oYcM0LA%h-+!Q{J){fs7?bD8`Z8O@vbGPmz# PW&~oU?R%M-7h3}WF-Q?x diff --git a/TOOLS/dlgAutoSchedule.dfm b/TOOLS/dlgAutoSchedule.dfm index a5e6441..bae7afb 100644 --- a/TOOLS/dlgAutoSchedule.dfm +++ b/TOOLS/dlgAutoSchedule.dfm @@ -131,8 +131,6 @@ object AutoSchedule: TAutoSchedule Align = alBottom BevelOuter = bvNone TabOrder = 0 - ExplicitTop = 318 - ExplicitWidth = 667 DesignSize = ( 671 46) @@ -145,7 +143,6 @@ object AutoSchedule: TAutoSchedule Caption = 'Cancel' TabOrder = 0 OnClick = btnCancelClick - ExplicitLeft = 190 end object btnOk: TButton Left = 275 @@ -156,7 +153,6 @@ object AutoSchedule: TAutoSchedule Caption = 'AUTO Schedule' TabOrder = 1 OnClick = btnOkClick - ExplicitLeft = 271 end end object tpHeatInterval: TTimePicker diff --git a/dlgAutoBuild_Relay.dfm b/dlgAutoBuild_Relay.dfm deleted file mode 100644 index 926382b..0000000 --- a/dlgAutoBuild_Relay.dfm +++ /dev/null @@ -1,45 +0,0 @@ -object AutoBuild_Relay: TAutoBuild_Relay - Left = 0 - Top = 0 - Caption = 'AutoBuild Selected Relay ...' - ClientHeight = 441 - ClientWidth = 624 - Color = clBtnFace - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -12 - Font.Name = 'Segoe UI' - Font.Style = [] - TextHeight = 15 - object Panel2: TPanel - Left = 0 - Top = 395 - Width = 624 - Height = 46 - Align = alBottom - BevelEdges = [beTop] - BevelKind = bkFlat - BevelOuter = bvNone - TabOrder = 0 - object btnCancel: TButton - Left = 257 - Top = 6 - Width = 75 - Height = 27 - Cancel = True - Caption = 'No' - ModalResult = 2 - TabOrder = 0 - end - object btnOk: TButton - Left = 176 - Top = 6 - Width = 75 - Height = 27 - Caption = 'Yes' - Default = True - ModalResult = 1 - TabOrder = 1 - end - end -end diff --git a/dlgAutoBuild_Relay.pas b/dlgAutoBuild_Relay.pas deleted file mode 100644 index 7cedd12..0000000 --- a/dlgAutoBuild_Relay.pas +++ /dev/null @@ -1,83 +0,0 @@ -unit dlgAutoBuild_Relay; - -interface - -uses - Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, - Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, dmSCM; - -type - TAutoBuild_Relay = class(TForm) - Panel2: TPanel; - btnCancel: TButton; - btnOk: TButton; - private - { Private declarations } - prefExcludeOutsideLanes: boolean; - prefNumOfSwimmersPerTeam: integer; - - function GetPoolLaneCount(): integer; - - procedure ReadPreferences(IniFileName: string); - - public - { Public declarations } - end; - -var - AutoBuild_Relay: TAutoBuild_Relay; - -implementation - -{$R *.dfm} - -uses System.IniFiles, SCMUtility; - -{ TAutoBuild_Relay } - -function TAutoBuild_Relay.GetPoolLaneCount: integer; -var -IniFileName: TFileName; -Lanes: integer; -begin - result := 0; - - // r e a d p r e f e r e n c e . - IniFileName := SCMUtility.GetSCMPreferenceFileName(); - if (FileExists(IniFileName)) then - ReadPreferences(IniFileName); - - // Get number of lanes from dbo.SwimClub.NumOfLanes - Lanes := SCM.SwimClub_NumberOfLanes; - if Lanes = 0 then - Exit; - - // Adjust lanes if excluding outside lanes - if prefExcludeOutsideLanes then - Dec(Lanes, 2); - - // Ensure there is at least one lane - if Lanes < 1 then - Exit; - - result := Lanes; -end; - -procedure TAutoBuild_Relay.ReadPreferences(IniFileName: string); -var - iFile: TIniFile; - i: Integer; -begin - iFile := TIniFile.Create(IniFileName); - - // When true gutter lanes are not used in events. - prefExcludeOutsideLanes := (iFile.ReadInteger('Preferences', - 'ExcludeOutsideLanes', 0) = 1); - // Relay teams, by default, have four swimmers. - prefNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', - 'NumOfSwimmersPerTeam', 4); - - iFile.free; -end; - -end. From 9629e44f8db6850a3f1523ffe0558cf4ddf04d82 Mon Sep 17 00:00:00 2001 From: Artanemus <69775305+Artanemus@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:37:49 +1000 Subject: [PATCH 3/6] Auto-Build Relay consolidation WIP --- AUTOBUILD/dlgABRelay.dfm | 4 + AUTOBUILD/dmABRelayData.dfm | 227 +----------- AUTOBUILD/dmABRelayData.pas | 56 +-- AUTOBUILD/uABRelayExec.pas | 717 ++++++++++++++++++++++++------------ AUTOBUILD/uABRelayV1.pas | 65 ++-- SwimClubMeet.dpr | 1 - SwimClubMeet.dproj | 1 - dmSCM.dfm | 252 +++++++++++++ dmSCM.pas | 25 ++ frmMain.pas | 62 +++- 10 files changed, 884 insertions(+), 526 deletions(-) diff --git a/AUTOBUILD/dlgABRelay.dfm b/AUTOBUILD/dlgABRelay.dfm index f90e760..76dffea 100644 --- a/AUTOBUILD/dlgABRelay.dfm +++ b/AUTOBUILD/dlgABRelay.dfm @@ -202,6 +202,8 @@ object ABRelay: TABRelay Width = 143 Height = 21 Caption = 'Swimmers per team.' + Enabled = False + Visible = False end object vimgHint1: TVirtualImage Left = 110 @@ -329,10 +331,12 @@ object ABRelay: TABRelay Top = 236 Width = 52 Height = 31 + Enabled = False MaxValue = 12 MinValue = 2 TabOrder = 8 Value = 4 + Visible = False end object prefVerbose: TCheckBox Left = 22 diff --git a/AUTOBUILD/dmABRelayData.dfm b/AUTOBUILD/dmABRelayData.dfm index 61f80f2..c083a73 100644 --- a/AUTOBUILD/dmABRelayData.dfm +++ b/AUTOBUILD/dmABRelayData.dfm @@ -1,76 +1,6 @@ object ABRelayData: TABRelayData Height = 480 Width = 640 - object qryXNominees: TFDQuery - ActiveStoredUsage = [auDesignTime] - Connection = SCM.scmConnection - SQL.Strings = ( - ' SELECT' - #9#9' [NomineeID]' - #9#9',[SeedTime]' - #9#9',[AutoBuildFlag]' - #9#9',[EventID]' - #9#9',[MemberID]' - ' -- Re-Calculate TimeToBeat...' - - ' -- algorithm default ... average of the 3 fastest raceti' + - 'mes' - ' -- percentage default ... 50%' - ' -- MemberID, 25m, freestyle, SesssionStart.' - - ' ,dbo.TimeToBeat(1, :ALGORITHM, :PERCENT, [MemberID], :XDISTA' + - 'NCEID, :STROKEID, :SESSIONSTART) AS TTB' - - ' ,dbo.PersonalBest([MemberID],:XDISTANCEID, :STROKEID, :SESSI' + - 'ONSTART) AS PB' - ' FROM [dbo].[Nominee]' - ' WHERE [EventID] = :EVENTID' - ' ORDER BY [TTB] ASC') - Left = 128 - Top = 248 - ParamData = < - item - Name = 'ALGORITHM' - DataType = ftInteger - ParamType = ptInput - Value = 1 - end - item - Name = 'PERCENT' - DataType = ftInteger - ParamType = ptInput - Value = 50 - end - item - Name = 'XDISTANCEID' - DataType = ftInteger - ParamType = ptInput - Value = 1 - end - item - Name = 'STROKEID' - DataType = ftInteger - ParamType = ptInput - Value = 1 - end - item - Name = 'SESSIONSTART' - DataType = ftDate - ParamType = ptInput - Value = Null - end - item - Name = 'EVENTID' - DataType = ftInteger - ParamType = ptInput - Value = Null - end> - end - object dsXNominees: TDataSource - DataSet = qryXNominees - Left = 200 - Top = 248 - end object FDCommandUpdateEntrant: TFDCommand CommandText.Strings = ( 'USE [SwimClubMeet];' @@ -148,7 +78,7 @@ object ABRelayData: TABRelayData Left = 128 Top = 40 end - object qryRNominee: TFDQuery + object qryRelayNominee: TFDQuery ActiveStoredUsage = [auDesignTime] FilterOptions = [foCaseInsensitive] Filter = '[FName] LIKE '#39'%b%'#39 @@ -235,15 +165,16 @@ object ABRelayData: TABRelayData 'DECLARE @Order INT;' 'DECLARE @CalcDefault INT;' 'DECLARE @BottomPercent FLOAT;' - 'DECLARE @EventType AS INT;' + 'DECLARE @TopNumber INT;' + '' '' 'SET @EventID = :EVENTID;' 'SET @Algorithm = :ALGORITHM;' 'SET @ToggleName = :TOGGLENAME;' 'SET @CalcDefault = :CALCDEFAULT' 'SET @BottomPercent = :BOTTOMPERCENT' - 'SET @EventType = :EVENTTYPE' - 'SET @DistanceID = :DISTANCEID' + 'SET @DistanceID = :XDISTANCEID' + 'SET @TopNumber = :TOPNUMBER' '' 'SET @StrokeID =' '(' @@ -270,17 +201,7 @@ object ABRelayData: TABRelayData ')' '' '-- Members given a swimming lane in the given event ' - 'IF @EventType = 1' - 'BEGIN' - ' INSERT INTO #tmpID' - ' SELECT Entrant.MemberID' - ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' - ' INNER JOIN Entrant' - ' ON Entrant.HeatID = HeatIndividual.HeatID' - ' WHERE HeatIndividual.EventID = @EventID;' - 'END' - 'ELSE' - 'BEGIN' + '' ' INSERT INTO #tmpID' ' SELECT TeamEntrant.MemberID' ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' @@ -288,11 +209,11 @@ object ABRelayData: TABRelayData ' ON HeatIndividual.HeatID = Team.HeatID' ' INNER JOIN TeamEntrant' ' ON Team.TeamID = TeamEntrant.TeamID' - ' WHERE HeatIndividual.EventID = @EventID;' - 'END' - '' + ' WHERE HeatIndividual.EventID = @EventID' + ' AND HeatIndividual.HeatStatusID = 1;' + ' ' '' - 'SELECT Nominee.EventID' + 'SELECT TOP @TopNumber Nominee.EventID' ' , Nominee.MemberID' ' , Member.GenderID' ' , dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE' @@ -319,7 +240,9 @@ object ABRelayData: TABRelayData ' LEFT OUTER JOIN Member' ' ON Nominee.MemberID = Member.MemberID' 'WHERE Nominee.EventID = @EventID' - ' AND #tmpID.MemberID IS NULL ;' + ' AND #tmpID.MemberID IS NULL ' + 'ORDER BY TTB ASC ' + ' ;' '') Left = 128 Top = 152 @@ -355,77 +278,21 @@ object ABRelayData: TABRelayData Value = 50.000000000000000000 end item - Name = 'EVENTTYPE' + Name = 'XDISTANCEID' DataType = ftInteger ParamType = ptInput Value = 1 end item - Name = 'DISTANCEID' + Name = 'TOPNUMBER' DataType = ftInteger ParamType = ptInput - Value = 1 + Value = 1000 end> - object qryRNomineeFName: TWideStringField - DisplayLabel = 'Nominees' - DisplayWidth = 30 - FieldName = 'FName' - Origin = 'FName' - ReadOnly = True - Size = 60 - end - object qryRNomineeTTB: TTimeField - Alignment = taCenter - DisplayLabel = 'TimeToBeat' - DisplayWidth = 12 - FieldName = 'TTB' - Origin = 'TTB' - ReadOnly = True - DisplayFormat = 'nn:ss.zzz' - end - object qryRNomineePB: TTimeField - Alignment = taCenter - DisplayLabel = 'Personal Best' - DisplayWidth = 12 - FieldName = 'PB' - Origin = 'PB' - ReadOnly = True - DisplayFormat = 'nn:ss.zzz' - end - object qryRNomineeAGE: TIntegerField - Alignment = taLeftJustify - DisplayLabel = ' AGE' - DisplayWidth = 5 - FieldName = 'AGE' - Origin = 'AGE' - ReadOnly = True - DisplayFormat = '##0' - end - object qryRNomineeGender: TWideStringField - Alignment = taCenter - DisplayWidth = 9 - FieldName = 'Gender' - Origin = 'Gender' - ReadOnly = True - Size = 2 - end - object qryRNomineeMemberID: TIntegerField - FieldName = 'MemberID' - Origin = 'MemberID' - ProviderFlags = [pfInUpdate, pfInWhere, pfInKey] - end - object qryRNomineeEventID: TIntegerField - FieldName = 'EventID' - Origin = 'EventID' - end - object qryRNomineeGenderID: TIntegerField - FieldName = 'GenderID' - Origin = 'GenderID' - end end - object dsRNominee: TDataSource - DataSet = qryRNominee - Left = 200 + object dsRelayNominee: TDataSource + DataSet = qryRelayNominee + Left = 232 Top = 152 end object qryCountRNominee: TFDQuery @@ -554,61 +421,5 @@ object ABRelayData: TABRelayData ParamType = ptInput Value = Null end> - object WideStringField: TWideStringField - DisplayLabel = 'Nominees' - DisplayWidth = 30 - FieldName = 'FName' - Origin = 'FName' - ReadOnly = True - Size = 60 - end - object TimeField1: TTimeField - Alignment = taCenter - DisplayLabel = 'TimeToBeat' - DisplayWidth = 12 - FieldName = 'TTB' - Origin = 'TTB' - ReadOnly = True - DisplayFormat = 'nn:ss.zzz' - end - object TimeField2: TTimeField - Alignment = taCenter - DisplayLabel = 'Personal Best' - DisplayWidth = 12 - FieldName = 'PB' - Origin = 'PB' - ReadOnly = True - DisplayFormat = 'nn:ss.zzz' - end - object IntegerField1: TIntegerField - Alignment = taLeftJustify - DisplayLabel = ' AGE' - DisplayWidth = 5 - FieldName = 'AGE' - Origin = 'AGE' - ReadOnly = True - DisplayFormat = '##0' - end - object WideStringField2: TWideStringField - Alignment = taCenter - DisplayWidth = 9 - FieldName = 'Gender' - Origin = 'Gender' - ReadOnly = True - Size = 2 - end - object IntegerField2: TIntegerField - FieldName = 'MemberID' - Origin = 'MemberID' - ProviderFlags = [pfInUpdate, pfInWhere, pfInKey] - end - object IntegerField3: TIntegerField - FieldName = 'EventID' - Origin = 'EventID' - end - object IntegerField4: TIntegerField - FieldName = 'GenderID' - Origin = 'GenderID' - end end end diff --git a/AUTOBUILD/dmABRelayData.pas b/AUTOBUILD/dmABRelayData.pas index 0febc6d..f76bce0 100644 --- a/AUTOBUILD/dmABRelayData.pas +++ b/AUTOBUILD/dmABRelayData.pas @@ -11,43 +11,18 @@ interface type TABRelayData = class(TDataModule) - qryXNominees: TFDQuery; - dsXNominees: TDataSource; FDCommandUpdateEntrant: TFDCommand; - qryRNominee: TFDQuery; - qryRNomineeFName: TWideStringField; - qryRNomineeTTB: TTimeField; - qryRNomineePB: TTimeField; - qryRNomineeAGE: TIntegerField; - qryRNomineeGender: TWideStringField; - qryRNomineeMemberID: TIntegerField; - qryRNomineeEventID: TIntegerField; - qryRNomineeGenderID: TIntegerField; - dsRNominee: TDataSource; + qryRelayNominee: TFDQuery; + dsRelayNominee: TDataSource; qryCountRNominee: TFDQuery; - WideStringField: TWideStringField; - TimeField1: TTimeField; - TimeField2: TTimeField; - IntegerField1: TIntegerField; - WideStringField2: TWideStringField; - IntegerField2: TIntegerField; - IntegerField3: TIntegerField; - IntegerField4: TIntegerField; private - { Private declarations } - fHandle: HWND; fAutoBuildRelayDataActive: Boolean; FConnection: TFDConnection; - public - { Public declarations } constructor CreateWithConnection(AOwner: TComponent; AConnection: TFDConnection); constructor Create(AOwner: TComponent); override; destructor Destroy(); override; - procedure ReadPreferences(aIniFileName: string); procedure ActivateTable(); - - end; var @@ -57,36 +32,22 @@ implementation {%CLASSGROUP 'Vcl.Controls.TControl'} -uses SCMUtility; - {$R *.dfm} -{ TAutoBuildRelayData } - procedure TABRelayData.ActivateTable; begin fAutoBuildRelayDataActive := false; if Assigned(FConnection) and FConnection.Connected then begin - qryRNominee.Connection := FConnection; - qryRNominee.Open; - if qryRNominee.Active then - begin - fAutoBuildRelayDataActive := true; - end; + qryRelayNominee.Connection := FConnection; + qryCountRNominee.Connection := FConnection; + fAutoBuildRelayDataActive := true; end; end; constructor TABRelayData.Create(AOwner: TComponent); -var -IniFileName: string; begin inherited; -// fHandle := AllocateHWnd(WndProc); - - // r e a d p r e f e r e n c e . - IniFileName := SCMUtility.GetSCMPreferenceFileName(); - if (FileExists(IniFileName)) then ReadPreferences(IniFileName); end; constructor TABRelayData.CreateWithConnection(AOwner: TComponent; @@ -94,17 +55,14 @@ constructor TABRelayData.CreateWithConnection(AOwner: TComponent; begin self.Create(AOwner); FConnection := AConnection; + ActivateTable; end; destructor TABRelayData.Destroy; begin -// DeallocateHWND(fHandle); inherited; end; -procedure TABRelayData.ReadPreferences(aIniFileName: string); -begin - ; -end; + end. diff --git a/AUTOBUILD/uABRelayExec.pas b/AUTOBUILD/uABRelayExec.pas index 2c23360..c480a2a 100644 --- a/AUTOBUILD/uABRelayExec.pas +++ b/AUTOBUILD/uABRelayExec.pas @@ -8,49 +8,119 @@ interface FireDAC.Comp.Client, FireDAC.Stan.Param, Vcl.Forms, SCMDefines, Data.DB; + + type - TABRelayExec = class(TComponent) - private - { private declarations } - prefExcludeOutsideLanes: boolean; - prefNumOfSwimmersPerTeam: integer; - prefVerbose: boolean; - prefHeatAlgorithm: integer; - prefRaceTimeTopPercent: integer; + TTeam = record + // L A N E >>> R E L A Y - T E A M . + // (Typically a relay-team holds four swimmers) + ID: Integer; // IDENITY of the Team in dbo.Team. + Lane: Integer; // Lane number. + RaceTime: TTime; // The SUM of all PBs for swimmers in the relay-team. + Entrants: array of integer; // Holds memberID - 4 x swimmers per lane. + HeatID: Integer; + end; - prefUseDefRaceTime: boolean; - prefSeperateGender: boolean; - rgpSeedMethod: integer; - spnSeedDepth: integer; + TChromosome = array of TTeam; - FConnection: TFDConnection; - fEventID: integer; - fXDistanceID: integer; - fStrokeID: integer; + THeat = record + // H E A T . + ID: integer; + HeatNum: integer; + EventID: integer; + end; - function GetPoolLaneCount: integer; - function GetXDistanceID(EventID, teamSize: integer): integer; - function GetStrokeID(EventID: integer): integer; + TSwimmer = record + // S W I M M E R . + NomineeID: Integer; // Nomination ID for the swimmer in dbo.Nominee. + MemberID: Integer; // Member ID for the swimmer used in dbo.Member table + TTB: TTime; // Time-to-beat. Estimated (calculated) swimming for XDistance. + PB: TTime; // Personal Best swimming time for the swimmer for the event. + end; - procedure ReadPreferences(IniFileName: string); - procedure RetriveSwimmingData(AEventID, poolLanes, teamSize: Integer); - procedure InsertIntoTeams(); + TABRelayExec = class(TComponent) + private -// procedure Distribute(FDQuery: TFDQuery; AEventID: integer); + { private declarations } + fExcludeOutsideLanes: boolean; + fVerbose: boolean; + fHeatAlgorithm: integer; + fRaceTimeBottomPercent: integer; + fUseDefRaceTime: boolean; + fSeperateGender: boolean; + fSeedMethod: integer; + fSeedDepth: integer; + fSuccess: boolean; + FConnection: TFDConnection; + fEventID: integer; + fDistanceID: integer; + fStrokeID: integer; + + fTeamDistanceMetres: integer; + fIndvDistanceMetres: integer; + + fNumOfTeams: integer; + fNumOfNominees: integer; + fNumOfSwimmers: integer; + fNumOfHeats: integer; + fNumOfSwimmersPerTeam: integer; + fNumOfTeamsPerHeat: integer; + fNumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. + + // D Y N A M I C A R R A Y S . + // Nominee Database Pool : swimmers who nominated for the event. + Swimmers: array of TSwimmer; + // An array of heats. + Heats: array of THeat; + // An array of teams - each team will contain MemberID x fNumOfSwimmersPerTeam. + Teams: array of TTeam; + // An array of swimmers, used by 'bin packing routine' using a genetic algorithm. + Population: array of TChromosome; + + PopulationSize: Integer; // ... fDivNumOfSwimmers. + Generations: Integer; // = 1000; + MutationRate: Double; // = 0.01; + + + function GetPoolLaneCount: integer; + function GetNumberOfSwimmersPerTeam(EventID: integer): integer; + function GetIndvDistanceMetres(EventID, fNumOfSwimmersPerTeam: integer): integer; + function GetStrokeID(AEventID: integer): integer; + function GetDistanceID(EventID: integer): integer; + + procedure ReadPreferences(IniFileName: string); + procedure RetriveSwimmingData(AEventID, fNumOfPoolLanes, fNumOfSwimmersPerTeam: integer); + procedure InsertIntoTeams(); + + // B i n p a c k i n g r o u t i n e s . + function Crossover(Parent1, Parent2: TChromosome): TChromosome; + function Fitness(Chromosome: TChromosome): Double; + procedure GeneticAlgorithm; + function GetNumberOfNominees(AEventID: Integer): integer; + function InitializePopulation: TChromosome; + procedure Mutate(var Chromosome: TChromosome); + procedure Swap(var A, B: Integer); + function TournamentSelection: TChromosome; + function GetXDistanceID(EventID, fNumOfSwimmersPerTeam: integer): integer; + + + + // procedure Distribute(FDQuery: TFDQuery; AEventID: integer); protected { protected declarations } + public { public declarations } procedure ExecAutoBuildRelay(); - procedure Prepare(AConnection: TFDConnection; ASwimClubID, AEventID: Integer); + procedure Prepare(AConnection: TFDConnection; AEventID: integer); constructor Create(AOwner: TComponent); override; destructor Destroy; override; - published { published declarations } + property Success: boolean read fSuccess write fSuccess; end; @@ -58,8 +128,8 @@ implementation { TAutoBuildRelayExec } -uses System.Math, System.IniFiles, SCMUtility, uABRelayV1, - dmABRelayData; +uses System.Math, System.IniFiles, SCMUtility, + dmABRelayData, system.Generics.Collections; { procedure TAutoBuildRelayExec.Distribute(FDQuery: TFDQuery; AEventID: integer); @@ -113,21 +183,28 @@ constructor TABRelayExec.Create(AOwner: TComponent); begin inherited; fEventID := 0; + fDistanceID := 1; FConnection := nil; - fXDistanceID := 1; + fTeamDistanceMetres:=0; + fIndvDistanceMetres:=0; fStrokeID := 1; + fSuccess := false; // I N I T I A L I Z E P R E F E R E N C E S . - prefExcludeOutsideLanes := false; - prefHeatAlgorithm := 1; - prefNumOfSwimmersPerTeam := 4; - prefRaceTimeTopPercent := 50; - prefVerbose := false; - prefRaceTimeTopPercent := 50; - prefUseDefRaceTime := true; - prefSeperateGender := false; - rgpSeedMethod := 0; - spnSeedDepth := 3; + fExcludeOutsideLanes := false; + fHeatAlgorithm := 1; + fNumOfSwimmersPerTeam := 4; + fRaceTimeBottomPercent := 50; + fVerbose := false; + fRaceTimeBottomPercent := 50; + fUseDefRaceTime := true; + fSeperateGender := false; + fSeedMethod := 0; + fSeedDepth := 3; + + PopulationSize := 100; + Generations := 1000; + MutationRate := 0.01; end; @@ -142,107 +219,204 @@ procedure TABRelayExec.ExecAutoBuildRelay(); var IniFileName: TFileName; v: variant; - poolLanes: integer; + fNumOfPoolLanes: integer; + s: string; begin // A S S E R T . + fSuccess := false; // data module : use Prepare to initialize... if not assigned(ABRelayData) then exit; if fEventID = 0 then exit; - // is this a team event. - - prefExcludeOutsideLanes := false; - prefNumOfSwimmersPerTeam := 4; - // r e a d p r e f e r e n c e . IniFileName := SCMUtility.GetSCMPreferenceFileName(); - if (FileExists(IniFileName)) then - ReadPreferences(IniFileName); - + if (FileExists(IniFileName)) then ReadPreferences(IniFileName); + // C h e c k e v e n t t y p e . v := SCM.scmConnection.ExecSQLScalar ('SELECT EventTypeID FROM [dbo].[Event] WHERE EventID = :ID', [fEventID]); - if VarIsNull(v) then + + if VarIsNull(v) then // Is this a team event? begin - if prefVerbose then - Application.MessageBox('Event type not assigned.', 'SCM Error', MB_OK) - else - exit; // error - NULL. + if fVerbose then + begin + s := ''' + Event type isn't known by Auto-Build. + Auto-Build ENDED. + '''; + Application.MessageBox(PChar(s), 'Auto Build Relays Error', MB_OK); + end; + exit; end; + if (v <> 2) then + begin + if fVerbose then + begin + s := ''' + This event isn''t a relay. + Auto-Build ENDED. + '''; + Application.MessageBox(PChar(s), 'Auto Build Relays Error', MB_OK); + end; + exit; // event type not a team event. + end; + + // S t r o k e I D . + fStrokeID := GetStrokeID(fEventID); + // C h e c k s w i m m i n g s t r o k e . - if v <> 2 then + if (fStrokeID = 0) or (fStrokeID > 4) then begin - if prefVerbose then - Application.MessageBox('The event isn''t a relay', 'SCM Error', MB_OK) - else - exit; // error - event type not a team event. + if fVerbose then + begin + case fStrokeID of + 5: // Is Medley team relays OK? + s := ''' + Medley Relays cannot be constructed by Auto-Built. + Auto-Build ENDED. + '''; + else // NOT FS,BK, BS, BF ? + s := ''' + The swimming stroke is not accepted by Auto-Build. + Auto-Build ENDED. + '''; + end; + Application.MessageBox(PChar(s), 'Auto Build Relays Error', MB_ICONERROR or MB_OK); + end; + exit; end; - v := - SCM.scmConnection.ExecSQLScalar - ('SELECT StrokeID FROM [dbo].[Event] WHERE EventID = :ID', [fEventID]); - if VarIsNull(v) then exit; // error - unknow swimming stroke. - - if v = 5 then - if prefVerbose then - Application.MessageBox(''' - Medley relays cannot be auto-built by SwimClubMeet. - Auto-build will abort. - ''', - 'SwimClubMeet Error', - MB_ICONERROR or MB_OK) - else - exit; // error - relay-type = Medley - - - poolLanes := GetPoolLaneCount; - // ASSERT - if poolLanes = 0 then - if prefVerbose then - Application.MessageBox(''' - No pool lanes are available. Check swim-club preferences. - Note: Nominees for relays that have been raced or close are excluded. - Auto-build will abort. - ''', - 'SwimClubMeet Error', - MB_ICONERROR or MB_OK) - else - exit; + // D i s t a n c e I D . + fDistanceID := GetDistanceID(fEventID); + // C h e c k P o o l l a n e s . + fNumOfPoolLanes := GetPoolLaneCount; + // A S S E R T . + if fNumOfPoolLanes = 0 then + begin + if fVerbose then + begin + s := ''' + No pool lanes are available. Check your swim-club preferences. + Auto-Build ENDED. + '''; + Application.MessageBox(PChar(s), 'SwimClubMeet Error', MB_ICONERROR or MB_OK) + end; + exit; + end; - // Test for nominees - v := 0; - ABRelayData.qryCountRNominee.Close; - ABRelayData.qryCountRNominee.ParamByName('EVENTID').AsInteger := fEventID; - ABRelayData.qryCountRNominee.Prepare; - ABRelayData.qryCountRNominee.Open; - if ABRelayData.qryCountRNominee.Active then - v := ABRelayData.qryCountRNominee.FieldByName('CountNominees').AsInteger; + // use ABREV in dbo.Distance to calculate NumOfSwimmersInTeam. + // TODO: + fNumOfSwimmersPerTeam := GetNumberOfSwimmersPerTeam(fDistanceID); + // use Meters in dbo.Distance to calculate team distance. + fTeamDistanceMetres:= 0; + // Individual Distance = the distance swum by each swimmer in the team. + // WIT: 4x25m relay Total 100m ... entrants swim 25m. + fIndvDistanceMetres := GetIndvDistanceMetres(fEventID, fNumOfSwimmersPerTeam); + // use ABREV in dbo.Distance to calculate indv distance. + fIndvDistanceMetres:= 0; - if VarIsNull(v) OR v=0 then - if prefVerbose then - Application.MessageBox(''' + fNumOfNominees := GetNumberOfNominees(fEventID); + if fNumOfNominees = 0 then // No swimmers!. + begin + if fVerbose then + begin + s := ''' No nominees were found for the team event. Note: Nominees for this event that have been raced or close are excluded. - Auto-build will abort. - ''', - 'SwimClubMeet Error', - MB_ICONERROR or MB_OK) - else - exit; // error - NULL. + Auto-build ENDED. + '''; + Application.MessageBox(Pchar(s), 'Auto Build Relay', MB_ICONEXCLAMATION or MB_OK); + end; + exit; + end; + // Perfect number of swimmers - no partial teams. + fNumOfSwimmers := v - (v mod fNumOfSwimmersPerTeam); + // Perfect number of teams. + fNumOfTeams := v div fNumOfSwimmers; + + {TODO -oBSA -cAutoBuild Relay : Enable the user to select nominees to trim. } + if (fNumOfNominees > fNumOfSwimmers) then + begin + if fVerbose then + begin + s := ''' + Not enough swimmers to complete all teams. + Entrants will be removed (slowest swimmer first. Nomination will remain). + Manually assign these nominees - should you wish them to participate. + '''; + // if fVerbose then + Application.MessageBox(Pchar(s), 'SwimClubMeet Warning', MB_ICONERROR or MB_OK); + end; + end; + // D I S R I B U T E - TEAMS ACROSS MULIT-HEATS. + // check that the number of relay-teams doesn't exceed the number of lanes. + if fNumOfTeams > fNumOfPoolLanes then + fNumOfHeats := Ceil(fNumOfTeams / fNumOfPoolLanes); + // distribute evenly relay teams accross heats. + fNumOfTeamsPerHeat := Ceil(fNumOfTeams / fNumOfHeats); - {TODO -oBSA -cGeneral : Progress bar and captions ....} + + + + // Assign params passed. + fNumOfPoolLanes := fNumOfPoolLanes; + + + {TODO -oBSA -cGeneral : Thread: AutoBuild Relays - Progress bar ....} // Clean OUT ALL HEATs, TEAMs, TEAMEntrants, TEAMSPLITs. // Does not remove raced or closed heats . SCM.Heat_DeleteAll(fEventID, true); + // D i s t r i b u t e 1 . (Gender, house, etc) - // perform Auto-Build. - RetriveSwimmingData(fEventID, poolLanes, prefNumOfSwimmersPerTeam); + // R e t r i v e . Check - fSuccess. + RetriveSwimmingData(fEventID, fNumOfPoolLanes, fNumOfSwimmersPerTeam); + if fSuccess then + begin + // Process the data using Genetic Algorithm + GeneticAlgorithm(); + if fSuccess then InsertIntoTeams(); + // D i s t r i b u t e 2 . (Gender, house, etc) + // if fSuccess then DistributeTeams(FDQuery); + end; // Refresh UI and data - if Owner is TForm then - SendMessage(TForm(Owner).Handle, SCM_AUTOBUILDRELAYSFIN, 0, 0); + // if Owner is TForm then + // SendMessage(TForm(Owner).Handle, SCM_AUTOBUILDRELAYSFIN, 0, 0); +end; + +function TABRelayExec.GetDistanceID(EventID: integer): integer; +var + v: variant; +begin + result := 0; + v := SCM.scmConnection.ExecSQLScalar + ('SELECT DistanceID FROM [dbo].[Event] WHERE EventID = :ID', [EventID]); + if not VarIsNull(v) then + result := v +end; + +function TABRelayExec.GetIndvDistanceMetres(EventID, + fNumOfSwimmersPerTeam: integer): integer; +begin + +end; + +function TABRelayExec.GetNumberOfSwimmersPerTeam(EventID: integer): integer; +var + v: variant; + s: string; +begin + result := 0; + s := ''' + SELECT ABREV FROM [SwimClubMeet].[dbo].[Event] + INNER JOIN [dbo].[Distance] ON [Event].DistanceID = [Distance].[DistanceID] + WHERE EventID = :ID + '''; + v := SCM.scmConnection.ExecSQLScalar(s, [EventID]); + if VarIsNull(v) then exit; + end; function TABRelayExec.GetPoolLaneCount: integer; @@ -253,25 +427,25 @@ function TABRelayExec.GetPoolLaneCount: integer; // Get number of lanes from dbo.SwimClub.NumOfLanes Lanes := SCM.SwimClub_NumberOfLanes; // Adjust lanes if excluding outside lanes - if (prefExcludeOutsideLanes) then Dec(Lanes, 2); + if (fExcludeOutsideLanes) then Dec(Lanes, 2); // Ensure there is at least one lane if (Lanes < 1) then Exit; result := Lanes; end; -function TABRelayExec.GetStrokeID(EventID: integer): integer; +function TABRelayExec.GetStrokeID(AEventID: integer): integer; var v: variant; begin result := 0; v := SCM.scmConnection.ExecSQLScalar - ('SELECT StrokeID FROM [dbo].[Event] WHERE [EventID] = :ID', [EventID]); + ('SELECT StrokeID FROM [dbo].[Event] WHERE [EventID] = :ID', [AEventID]); if not VarIsNull(v) then result := v; end; function TABRelayExec.GetXDistanceID(EventID, - teamSize: integer): integer; + fNumOfSwimmersPerTeam: integer): integer; var v: variant; meters: Integer; @@ -286,12 +460,12 @@ function TABRelayExec.GetXDistanceID(EventID, if not VarIsNull(v) then begin // divide the total relay distance by the number of swimmers in the relay - meters := Floor(v/teamSize); + meters := Floor(v/fNumOfSwimmersPerTeam); // cross reference value 'meters' to find the XDistanceID for a swimmer. v := SCM.scmConnection.ExecSQLScalar(''' SELECT[DistanceID] FROM[SwimClubMeet].[dbo].[Distance] - WHERE[meters] = :ID + WHERE[meters] = :ID AND [EventTypeID] = 1 ''', [meters]); if not VarIsNull(v) then begin @@ -328,7 +502,7 @@ procedure TABRelayExec.InsertIntoTeams; // Get the new heat record's ID AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); - for i := 0 to NumOfTeamsPerHeat - 1 do + for i := 0 to fNumOfTeamsPerHeat - 1 do begin // Insert into Team table qry2.SQL.Text := 'INSERT INTO Team (Lane, TeamNameID, HeatID) VALUES (:LANE, :TEAMNAMEID, :HEATID)'; @@ -375,8 +549,7 @@ procedure TABRelayExec.InsertIntoTeams; end; -procedure TABRelayExec.Prepare(AConnection: TFDConnection; ASwimClubID, - AEventID: Integer); +procedure TABRelayExec.Prepare(AConnection: TFDConnection; AEventID: Integer); begin FConnection := AConnection; fEventID := AEventID; @@ -392,6 +565,7 @@ procedure TABRelayExec.Prepare(AConnection: TFDConnection; ASwimClubID, if not assigned(ABRelayData) then raise Exception.Create('Auto-Build Relay''s Data Module creation error.'); end; + end; procedure TABRelayExec.ReadPreferences(IniFileName: string); @@ -403,175 +577,252 @@ procedure TABRelayExec.ReadPreferences(IniFileName: string); // When true gutter lanes are not used in events. i := iFile.ReadInteger('Preferences', 'ExcludeOutsideLanes', 0); - prefExcludeOutsideLanes := (i = 1); + fExcludeOutsideLanes := (i = 1); // 2024-09-10 // Relay teams, by default, have four swimmers. - prefNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', 'NumOfSwimmersPerTeam', 4); + fNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', 'NumOfSwimmersPerTeam', 4); // Verbose. Message all errors. i := iFile.ReadInteger('Preferences', 'Verbose', 0); - prefVerbose := (i = 1); + fVerbose := (i = 1); // TTB defaults to (1) .. the entrant's average of top 3 race-times - prefHeatAlgorithm := + fHeatAlgorithm := (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); // default to - get an average race-time of other swimmers i := iFile.ReadInteger('Preferences', 'UseDefRaceTime', 1); - prefUseDefRaceTime := (i = 1); + fUseDefRaceTime := (i = 1); // The bottom percent to select from ... default is 50% - prefRaceTimeTopPercent := + fRaceTimeBottomPercent := (iFile.ReadInteger('Preferences', 'RaceTimeTopPercent', 50)); i := iFile.ReadInteger('Preferences', 'SeperateGender', 0); - prefSeperateGender := (i = 1); + fSeperateGender := (i = 1); // SCM default seeding. - rgpSeedMethod := iFile.ReadInteger('Preferences', 'SeedMethod', 0); + fSeedMethod := iFile.ReadInteger('Preferences', 'SeedMethod', 0); // 2020-11-01 auto-build v2 seed depth for Circle Seed */ - spnSeedDepth := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); + fSeedDepth := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); iFile.free; end; -procedure TABRelayExec.RetriveSwimmingData(AEventID, poolLanes, - teamSize: Integer); +procedure TABRelayExec.RetriveSwimmingData(AEventID, fNumOfPoolLanes, + fNumOfSwimmersPerTeam: Integer); var - FDQuery: TFDQuery; - IniFileName: TFilename; StrokeID, xDistanceID, i: integer; + qry: TFDQuery; begin { Retrieve Swimmer Data: Fetch swimmers’ data from the Nominee table, focusing on their MemberID TTB (TimeToBeat) and PB (Personal Best) times. } - { - SET @EventID = :EVENTID; - SET @Algorithm = :ALGORITHM; - SET @ToggleName = :TOGGLENAME; - SET @CalcDefault = :CALCDEFAULT - SET @BottomPercent = :BOTTOMPERCENT - SET @EventType = :EVENTTYPE - SET @DistanceID = :DISTANCEID + qry := ABRelayData.qryRelayNominee; // improves reability. - ABRelayData.qryRNominee.Close; - ABRelayData.qryRNominee.ParamByName('EVENTID').AsInteger := fEventID; - ABRelayData.qryRNominee.ParamByName('ALGORITHM').AsInteger := prefHeatAlgorithm; - ABRelayData.qryRNominee.ParamByName('TOGGLENAME').AsBoolean := false; - ABRelayData.qryRNominee.ParamByName('CALCDEFAULT').AsInteger := prefRaceTimeTopPercent; - ABRelayData.qryRNominee.ParamByName('BOTTOMPERCENT').AsInteger := prefRaceTimeTopPercent; - ABRelayData.qryRNominee.ParamByName('EVENTTYPE').AsInteger := prefRaceTimeTopPercent; - ABRelayData.qryRNominee.ParamByName('DISTANCEID').AsInteger := XDistanceID; + qry.Close; + qry.ParamByName('TOPNUMBER').AsInteger := fNumOfSwimmers; + qry.ParamByName('EVENTID').AsInteger := AEventID; + qry.ParamByName('SESSIONSTART').AsDateTime := SCM.Session_Start; + qry.ParamByName('XDISTANCEID').AsDateTime := xDistanceID; + qry.ParamByName('STROKEID').AsDateTime := StrokeID; + qry.ParamByName('ALGORITHM').AsDateTime := fHeatAlgorithm; + qry.ParamByName('BOTTOMPERCENT').AsDateTime := fRaceTimeBottomPercent; + qry.Prepare; + qry.Open; - } + if not qry.Active then exit; -{ - poolLanes = swimming lanes - prefExcludeOutsideLanes - teamSize = number of swimmers in a relay-team. Default = 4; -} + if qry.IsEmpty then + begin + qry.Close; + fSuccess := false; + exit; + end; - // r e a d p r e f e r e n c e . - IniFileName := SCMUtility.GetSCMPreferenceFileName(); - if (FileExists(IniFileName)) then - ReadPreferences(IniFileName); - - // Freestyle, backstroke, breaststroke, butterfly. - // This routine doesn't autobuild Medley-Relays - // StrokeID = 5 (Medley-Relays) illegal. - StrokeID := GetStrokeID(AEventID); - // XDistance = the distance each entrant in relay-team swims. - // WIT: 4x25m relay Total 100m ... entrants swim 25m. - xDistanceID := GetXDistanceID(AEventID, teamSize); + // DIM ARRAY - + SetLength(Swimmers, fNumOfSwimmers); + // iterate through the list of nominees + for I := Low(Swimmers) to High(Swimmers) do + begin + Swimmers[i].MemberID := qry.FieldByName('MemberID').AsInteger; + Swimmers[i].NomineeID := qry.FieldByName('NomineeID').AsInteger; + Swimmers[i].TTB := qry.FieldByName('TTB').AsDateTime; + Swimmers[i].PB := qry.FieldByName('pB').AsDateTime; + qry.Next; + end; - // Assign params passed. - NumOfPoolLanes := poolLanes; - NumOfEntrantsPerTeam := teamSize; + SetLength(Heats, fNumOfHeats); + SetLength(Teams, fNumOfTeams); - FDQuery := TFDQuery.Create(nil); - try - FDQuery.Connection := SCM.scmConnection; // Your FireDAC connection - // Sort swimmers by personal best (PB) - // Order by ascending - FASTEST to SLOWEST - FDQuery.SQL.Text := ''' - SELECT - [NomineeID] - ,[SeedTime] - ,[AutoBuildFlag] - ,[EventID] - ,[MemberID] - -- Re-Calculate TimeToBeat... - -- algorithm default ... average of the 3 fastest racetimes - -- percentage default ... 50% - -- MemberID, 25m, freestyle, SesssionStart. - ,dbo.TimeToBeat(1, :ALGORITHM, :PERCENT, [MemberID], :XDISTANCEID, :STROKEID, :SESSIONSTART) AS TTB - ,dbo.PersonalBest([MemberID],:XDISTANCEID, :STROKEID, :SESSIONSTART) AS PB - FROM [dbo].[Nominee] - WHERE [EventID] = :EVENTID - ORDER BY [TTB] ASC -'''; - - FDQuery.ParamByName('EVENTID').AsInteger := AEventID; - FDQuery.ParamByName('SESSIONSTART').AsDateTime := SCM.Session_Start; - FDQuery.ParamByName('XDISTANCEID').AsDateTime := xDistanceID; - FDQuery.ParamByName('STROKEID').AsDateTime := StrokeID; - FDQuery.ParamByName('ALGORITHM').AsDateTime := prefHeatAlgorithm; - FDQuery.ParamByName('PERCENT').AsDateTime := prefRaceTimeTopPercent; - FDQuery.Open; - - if FDQuery.IsEmpty then - begin - FDQuery.Close; - FDQuery.Free; - exit; - end; - // the number of club members who nominated for the relay event. - NumOfNominees := FDQuery.RecordCount; - // the number of teams - NumOfTeams := Ceil(NumOfNominees / teamsize); - // ASSERT - if NumOfTeams = 0 then exit; - // check that the number of relay-teams doesn't exceed the number of lanes. - if NumOfTeams > poolLanes then - NumOfHeats := Ceil(NumOfTeams / poolLanes); +end; - NumOfTeamsPerHeat := Ceil(NumOfTeams / NumOfHeats); - SetLength(Swimmers, NumOfNominees); +function TABRelayExec.InitializePopulation: TChromosome; +var + i, j: Integer; + Chromosome: TChromosome; +begin + SetLength(Chromosome, fNumOfTeams); + for i := 0 to fNumOfTeams - 1 do + begin + Chromosome[i].ID := i; + Chromosome[i].Lane := i mod fNumOfPoolLanes; + SetLength(Chromosome[i].Entrants, fNumOfSwimmersPerTeam); + for j := 0 to fNumOfSwimmersPerTeam - 1 do + begin + Chromosome[i].Entrants[j] := Random(fNumOfSwimmers); + end; + end; + Result := Chromosome; +end; - // iterate through the list of nominees - i := 0; - while not FDQuery.Eof do +function TABRelayExec.Fitness(Chromosome: TChromosome): Double; +var + i, j: Integer; + TotalTime, MaxTime, MinTime: TTime; +begin + MaxTime := 0; + MinTime := MaxInt; + TotalTime := 0; + for i := 0 to High(Chromosome) do + begin + Chromosome[i].RaceTime := 0; + for j := 0 to High(Chromosome[i].Entrants) do begin - Swimmers[i].MemberID := FDQuery.FieldByName('MemberID').AsInteger; - Swimmers[i].NomineeID := FDQuery.FieldByName('NomineeID').AsInteger; - Swimmers[i].TTB := FDQuery.FieldByName('TTB').AsDateTime; - Swimmers[i].pB := FDQuery.FieldByName('pB').AsDateTime; - Inc(i, 1); - FDQuery.Next; + Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; end; + if Chromosome[i].RaceTime > MaxTime then + MaxTime := Chromosome[i].RaceTime; + if Chromosome[i].RaceTime < MinTime then + MinTime := Chromosome[i].RaceTime; + TotalTime := TotalTime + Chromosome[i].RaceTime; + end; + Result := MaxTime - MinTime; +end; - SetLength(Heats, NumOfHeats); - SetLength(Teams, NumOfTeams); +function TABRelayExec.TournamentSelection: TChromosome; +var + i, BestIndex: Integer; + BestFitness, CurrentFitness: Double; +begin + BestIndex := Random(PopulationSize); + BestFitness := Fitness(Population[BestIndex]); + for i := 1 to 4 do + begin + CurrentFitness := Fitness(Population[Random(PopulationSize)]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestIndex := i; + end; + end; + Result := Population[BestIndex]; +end; +function TABRelayExec.Crossover(Parent1, Parent2: TChromosome): TChromosome; +var + i, CrossoverPoint: Integer; + Child: TChromosome; +begin + SetLength(Child, Length(Parent1)); + CrossoverPoint := Random(Length(Parent1)); + for i := 0 to CrossoverPoint do + Child[i] := Parent1[i]; + for i := CrossoverPoint + 1 to High(Parent2) do + Child[i] := Parent2[i]; + Result := Child; +end; - // Process the data - // DistributeTeams(FDQuery); +procedure TABRelayExec.Swap(var A, B: Integer); +var + Temp: Integer; +begin + Temp := A; + A := B; + B := Temp; +end; - // Process the data using Genetic Algorithm - GeneticAlgorithm(); +procedure TABRelayExec.Mutate(var Chromosome: TChromosome); +var + Team1, Team2, Swimmer1, Swimmer2: Integer; +begin + if Random < MutationRate then + begin + Team1 := Random(Length(Chromosome)); + Team2 := Random(Length(Chromosome)); + Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); + Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); + Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); + end; +end; - InsertIntoTeams(); +procedure TABRelayExec.GeneticAlgorithm; +var + i, j: Integer; + Parent1, Parent2, Child: TChromosome; + BestChromosome: TChromosome; + BestFitness, CurrentFitness: Double; +begin + SetLength(Population, PopulationSize); + for i := 0 to PopulationSize - 1 do + Population[i] := InitializePopulation; - finally - FDQuery.Free; + for i := 0 to Generations - 1 do + begin + for j := 0 to PopulationSize - 1 do + begin + Parent1 := TournamentSelection; + Parent2 := TournamentSelection; + Child := Crossover(Parent1, Parent2); + Mutate(Child); + Population[j] := Child; + end; end; + + BestChromosome := Population[0]; + BestFitness := Fitness(BestChromosome); + for i := 1 to PopulationSize - 1 do + begin + CurrentFitness := Fitness(Population[i]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestChromosome := Population[i]; + end; + end; + + // Use BestChromosome to set your final teams + SetLength(Teams, Length(BestChromosome)); + for i := 0 to High(BestChromosome) do + Teams[i] := BestChromosome[i]; end; +function TABRelayExec.GetNumberOfNominees(AEventID: Integer): integer; +var + v: variant; + s: string; +begin + Result := 0; + // Test for nominees. Count total number of nominees. + ABRelayData.qryCountRNominee.Close; + ABRelayData.qryCountRNominee.ParamByName('EVENTID').AsInteger := AEventID; + ABRelayData.qryCountRNominee.Prepare; + ABRelayData.qryCountRNominee.Open; + if ABRelayData.qryCountRNominee.Active then + v := ABRelayData.qryCountRNominee.FieldByName('CountNominees').AsInteger; + ABRelayData.qryCountRNominee.Close; + if VarIsNull(v) or (v=0) then exit; + Result := v; +end; + + + end. diff --git a/AUTOBUILD/uABRelayV1.pas b/AUTOBUILD/uABRelayV1.pas index 681dee0..fe00bb3 100644 --- a/AUTOBUILD/uABRelayV1.pas +++ b/AUTOBUILD/uABRelayV1.pas @@ -6,10 +6,7 @@ interface System.Classes, FireDAC.Comp.Client, FireDAC.Stan.Param, dmSCM, dmSCMHelper, SCMUtility, System.SysUtils; -// Bin packing algorithm in Delphi using a genetic algorithm -// Define the container and item types type - TTeam = record // L A N E >>> R E L A Y - T E A M . // (Typically a relay-team holds four swimmers) @@ -37,20 +34,10 @@ TSwimmer = record PB: TTime; // Personal Best swimming time for the swimmer for the event. end; +// Bin packing algorithm in Delphi using a genetic algorithm +// Define the container and item types var - NumOfNominees: integer; // Count of nominees. - NumOfTeams: integer; // Count of relay-teams. - NumOfHeats: integer; // Count of heats. - NumOfTeamsPerHeat: integer; // Count of relay-teams in each heat. - NumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. - NumOfEntrantsPerTeam: integer; - - Population: array of TChromosome; - PopulationSize: Integer = 100; - Generations: Integer = 1000; - MutationRate: Double = 0.01; - // D Y N A M I C A R R A Y S . // Swimmers who nominated for the event. Swimmers: array of TSwimmer; @@ -60,15 +47,35 @@ TSwimmer = record // then multi-heats are required. Teams: array of TTeam; + fNumOfNominees: integer; // Count of nominees. + fNumOfTeams: integer; // Count of relay-teams. + fNumOfHeats: integer; // Count of heats. + fNumOfTeamsPerHeat: integer; // Count of relay-teams in each heat. + fNumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. + fNumOfEntrantsPerTeam: integer; + // R E F E R E N C E S . - prefExcludeOutsideLanes: boolean; - prefHeatAlgorithm: integer; - prefNumOfSwimmersPerTeam: integer; - prefRaceTimeTopPercent: integer; + fExcludeOutsideLanes: boolean; + fHeatAlgorithm: integer; + fNumOfSwimmersPerTeam: integer; + fRaceTimeBottomPercent: integer; + + Population: array of TChromosome; + PopulationSize: Integer = 100; + Generations: Integer = 1000; + MutationRate: Double = 0.01; + procedure ReadPreferences(IniFileName: string); procedure GeneticAlgorithm(); + + + + + +end; + implementation uses System.Math, system.Generics.Collections, System.IniFiles, @@ -81,16 +88,16 @@ procedure ReadPreferences(IniFileName: string); iFile := TIniFile.Create(IniFileName); // When true gutter lanes are not used in events. - prefExcludeOutsideLanes := (iFile.ReadInteger('Preferences', + fExcludeOutsideLanes := (iFile.ReadInteger('Preferences', 'ExcludeOutsideLanes', 0) = 1); // Relay teams, by default, have four swimmers. - prefNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', + fNumOfSwimmersPerTeam := iFile.ReadInteger('Preferences', 'NumOfSwimmersPerTeam', 4); // TTB defaults to (1) .. the entrant's average of top 3 race-times - prefHeatAlgorithm := (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); + fHeatAlgorithm := (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); // The bottom percent to select from ... default is 50% - prefRaceTimeTopPercent := + fRaceTimeBottomPercent := (iFile.ReadInteger('Preferences', 'RaceTimeTopPercent', 50)); iFile.free; @@ -101,15 +108,15 @@ function InitializePopulation: TChromosome; i, j: Integer; Chromosome: TChromosome; begin - SetLength(Chromosome, NumOfTeams); - for i := 0 to NumOfTeams - 1 do + SetLength(Chromosome, fNumOfTeams); + for i := 0 to fNumOfTeams - 1 do begin Chromosome[i].ID := i; - Chromosome[i].Lane := i mod NumOfPoolLanes; - SetLength(Chromosome[i].Entrants, NumOfEntrantsPerTeam); - for j := 0 to NumOfEntrantsPerTeam - 1 do + Chromosome[i].Lane := i mod fNumOfPoolLanes; + SetLength(Chromosome[i].Entrants, fNumOfEntrantsPerTeam); + for j := 0 to fNumOfEntrantsPerTeam - 1 do begin - Chromosome[i].Entrants[j] := Random(NumOfNominees); + Chromosome[i].Entrants[j] := Random(fNumOfNominees); end; end; Result := Chromosome; diff --git a/SwimClubMeet.dpr b/SwimClubMeet.dpr index 2998d28..88402d4 100644 --- a/SwimClubMeet.dpr +++ b/SwimClubMeet.dpr @@ -83,7 +83,6 @@ uses dlgMemberClub in 'MEMBERS\dlgMemberClub.pas' {MemberClub}, rptMemberCheckData in 'MEMBERS\rptMemberCheckData.pas' {MemberCheckData: TDataModule}, dmMemberHouse in 'MEMBERS\dmMemberHouse.pas' {MemberHouse: TDataModule}, - uABRelayV1 in 'AUTOBUILD\uABRelayV1.pas', uABRelayExec in 'AUTOBUILD\uABRelayExec.pas', dmABRelayData in 'AUTOBUILD\dmABRelayData.pas' {ABRelayData: TDataModule}, dlgABRelay in 'AUTOBUILD\dlgABRelay.pas' {ABRelay}; diff --git a/SwimClubMeet.dproj b/SwimClubMeet.dproj index 2ceae29..c1c0301 100644 --- a/SwimClubMeet.dproj +++ b/SwimClubMeet.dproj @@ -443,7 +443,6 @@ dfm TDataModule
-
ABRelayData
diff --git a/dmSCM.dfm b/dmSCM.dfm index a58c3f1..9b75000 100644 --- a/dmSCM.dfm +++ b/dmSCM.dfm @@ -2440,4 +2440,256 @@ object SCM: TSCM Left = 256 Top = 280 end + object qryCountTEAMNominee: TFDQuery + ActiveStoredUsage = [auDesignTime] + FilterOptions = [foCaseInsensitive] + Filter = '[FName] LIKE '#39'%b%'#39 + Indexes = < + item + Active = True + Selected = True + Name = 'idxMemberFName' + Fields = 'FName' + end + item + Active = True + Name = 'idxMemberFNameDESC' + Fields = 'FName' + DescFields = 'FName' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxTTB' + Fields = 'TTB' + end + item + Active = True + Name = 'idxTTBDESC' + Fields = 'TTB' + DescFields = 'TTB' + Options = [soDescending] + end + item + Active = True + Name = 'idxPB' + Fields = 'PB' + end + item + Active = True + Name = 'idxPBDESC' + Fields = 'PB' + DescFields = 'PB' + Options = [soDescending] + end + item + Active = True + Name = 'idxAge' + Fields = 'AGE' + end + item + Active = True + Name = 'idxAgeDESC' + Fields = 'AGE' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxGender' + Fields = 'GenderID' + end + item + Active = True + Name = 'idxGenderDESC' + Fields = 'GenderID' + Options = [soDescNullLast, soDescending] + end> + IndexName = 'idxMemberFName' + DetailFields = 'MemberID' + Connection = scmConnection + FormatOptions.AssignedValues = [fvFmtDisplayTime] + FormatOptions.FmtDisplayTime = 'nn:ss.zzz' + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate, uvCheckReadOnly] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' + UpdateOptions.KeyFields = 'MemberID' + SQL.Strings = ( + 'USE SwimClubMeet;' + '' + '-- NOTE: event type must be 2 (TEAM)' + '-- find nominees in relay event.' + '-- exclude raced and closed heats.' + '--' + '-- count nominees NO in event.' + '' + 'DECLARE @EventID AS INT;' + 'SET @EventID = :EVENTID;' + '' + '-- Drop a temporary table called '#39'#tmpA'#39 + 'IF OBJECT_ID('#39'tempDB..#tmpA'#39', '#39'U'#39') IS NOT NULL' + ' DROP TABLE #tmpA;' + '' + 'CREATE TABLE #tmpA' + '(' + ' MemberID INT' + ')' + '-- TEAM EVENT' + '-- Members given a swimming lane in the given event ' + '-- HEAT IS NOT OPEN' + ' INSERT INTO #tmpA' + ' SELECT TeamEntrant.MemberID' + ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' + ' INNER JOIN Team' + ' ON HeatIndividual.HeatID = Team.HeatID' + ' INNER JOIN TeamEntrant' + ' ON Team.TeamID = TeamEntrant.TeamID' + ' WHERE HeatIndividual.EventID = @EventID ' + ' AND HeatIndividual.HeatStatusID <> 1;' + '' + 'SELECT Count(NomineeID) AS CountNominees' + 'FROM Nominee' + ' LEFT OUTER JOIN #tmpA' + ' ON #tmpA.MemberID = Nominee.MemberID' + ' LEFT OUTER JOIN Member' + ' ON Nominee.MemberID = Member.MemberID' + 'WHERE Nominee.EventID = @EventID' + ' AND #tmpA.MemberID IS NULL ;' + '') + Left = 544 + Top = 16 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + end + object qryCountINDVNominee: TFDQuery + ActiveStoredUsage = [auDesignTime] + FilterOptions = [foCaseInsensitive] + Filter = '[FName] LIKE '#39'%b%'#39 + Indexes = < + item + Active = True + Selected = True + Name = 'idxMemberFName' + Fields = 'FName' + end + item + Active = True + Name = 'idxMemberFNameDESC' + Fields = 'FName' + DescFields = 'FName' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxTTB' + Fields = 'TTB' + end + item + Active = True + Name = 'idxTTBDESC' + Fields = 'TTB' + DescFields = 'TTB' + Options = [soDescending] + end + item + Active = True + Name = 'idxPB' + Fields = 'PB' + end + item + Active = True + Name = 'idxPBDESC' + Fields = 'PB' + DescFields = 'PB' + Options = [soDescending] + end + item + Active = True + Name = 'idxAge' + Fields = 'AGE' + end + item + Active = True + Name = 'idxAgeDESC' + Fields = 'AGE' + Options = [soDescNullLast, soDescending] + end + item + Active = True + Name = 'idxGender' + Fields = 'GenderID' + end + item + Active = True + Name = 'idxGenderDESC' + Fields = 'GenderID' + Options = [soDescNullLast, soDescending] + end> + IndexName = 'idxMemberFName' + DetailFields = 'MemberID' + Connection = scmConnection + FormatOptions.AssignedValues = [fvFmtDisplayTime] + FormatOptions.FmtDisplayTime = 'nn:ss.zzz' + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate, uvCheckReadOnly] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' + UpdateOptions.KeyFields = 'MemberID' + SQL.Strings = ( + 'USE SwimClubMeet;' + '' + '-- NOTE: event type must be 2 (TEAM)' + '-- find nominees in relay event.' + '-- exclude raced and closed heats.' + '--' + '-- count nominees NO in event.' + '' + 'DECLARE @EventID AS INT;' + 'SET @EventID = :EVENTID;' + '' + '-- Drop a temporary table called '#39'#tmpA'#39 + 'IF OBJECT_ID('#39'tempDB..#tmpA'#39', '#39'U'#39') IS NOT NULL' + ' DROP TABLE #tmpA;' + '' + 'CREATE TABLE #tmpA' + '(' + ' MemberID INT' + ')' + '-- INDIVIDUAL EVENT...' + '-- Members given a swimming lane in the given event ' + '-- HEAT IS NOT OPEN' + ' INSERT INTO #tmpA' + ' SELECT Entrant.MemberID' + ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' + ' INNER JOIN Entrant' + ' ON Entrant.HeatID = HeatIndividual.HeatID' + ' WHERE HeatIndividual.EventID = @EventID' + ' AND HeatIndividual.HeatStatusID <> 1;' + '' + 'SELECT Count(NomineeID) AS CountNominees' + 'FROM Nominee' + ' LEFT OUTER JOIN #tmpA' + ' ON #tmpA.MemberID = Nominee.MemberID' + ' LEFT OUTER JOIN Member' + ' ON Nominee.MemberID = Member.MemberID' + 'WHERE Nominee.EventID = @EventID' + ' AND #tmpA.MemberID IS NULL ;' + '') + Left = 544 + Top = 72 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + end end diff --git a/dmSCM.pas b/dmSCM.pas index 6ff2341..8020042 100644 --- a/dmSCM.pas +++ b/dmSCM.pas @@ -194,6 +194,8 @@ TSCM = class(TDataModule) qrySwimClubSwimClubTypeID: TIntegerField; qryDistance: TFDQuery; dsDistance: TDataSource; + qryCountTEAMNominee: TFDQuery; + qryCountINDVNominee: TFDQuery; procedure DataModuleCreate(Sender: TObject); procedure qryEntrantAfterScroll(DataSet: TDataSet); procedure qryEntrantTIMEGetText(Sender: TField; var Text: string; @@ -326,6 +328,7 @@ TSCM = class(TDataModule) function MoveUpLane(ADataSet: TDataSet): Boolean; function MoveUpToPrevHeat(ADataSet: TDataSet): Boolean; // NOMINATE + function CountTEAMNominee(): integer; function Nominate_Locate(MemberID: integer): Boolean; function Nominate_LocateEventNum(ADataSet: TDataSet; EventNum: integer): Boolean; @@ -714,6 +717,28 @@ function TSCM.CountLanes(aHeatID: integer): integer; if not VarIsNull(v) or not VarIsEmpty(v) then result := v; end; +function TSCM.CountTEAMNominee: integer; +begin + { Count number of nominees (swimmers) for event. + Exclude entrants (swimmers) in RACED or CLOSED heats. + INCLUDE nominees (swimmers) in OPEN heats. + INCLUDE pooled nominees (swimmers) not assigned a lane to the event. + } + result := 0; + SCM.qryCountTEAMNominee.Close; + SCM.qryCountTEAMNominee.ParamByName('EVENTID').AsInteger := Event_ID; + SCM.qryCountTEAMNominee.Prepare; + try + SCM.qryCountTEAMNominee.Open; + if SCM.qryCountTEAMNominee.Active then + begin + result := SCM.qryCountTEAMNominee.FieldByName('CountNominees').AsInteger; + end; + finally + SCM.qryCountTEAMNominee.Close; + end; +end; + function TSCM.CountTeamSwimmers(aTeamID: integer): integer; var SQL: string; diff --git a/frmMain.pas b/frmMain.pas index 7cf1cef..d78cc79 100644 --- a/frmMain.pas +++ b/frmMain.pas @@ -484,6 +484,7 @@ TMain = class(TForm) SCMEventList: TObjectList; function AssertConnection(): boolean; // Check connection to MSSQL DATABASE + procedure Heat_AutoBuildRelayExecute(Sender: TObject); procedure DBGridWndProc(var Msg: TMessage); procedure DrawEventStatus(oGrid: TObject; Rect: TRect; Column: TColumn); // procedure EnableEvent_GridEllipse(); @@ -564,7 +565,54 @@ implementation UEnvVars, dlgEntrantPicker, dlgEntrantPickerCTRL, dmSCMNom, dlgSwapLanes, dlgDBVerInfo, rptHeatReportA, rptHeatReportB, frmDisqualificationCodes, dlgAutoSchedule, dlgDCodePicker, dmSCMHelper, rptMarshallReportC, - dlgSplitTimeTEAM, dlgSplitTimeINDV, dlgSwimClubSwitch, dlgSwimClubManage; + dlgSplitTimeTEAM, dlgSplitTimeINDV, dlgSwimClubSwitch, dlgSwimClubManage, + dlgABRelay, uABRelayExec; + +procedure TMain.Heat_AutoBuildRelayExecute(Sender: TObject); +var + i, rtnValue: Integer; + dlg: TABRelay; + ABRelay: TABRelayExec; +begin + // NOTE: actn..Update determines if this routine is accessable. + { Count number of nominees (swimmers) for event. + Exclude entrants (swimmers) in RACED or CLOSED heats. + INCLUDE nominees (swimmers) in OPEN heats. + INCLUDE pooled nominees (swimmers) not assigned a lane to the event. + } + i := SCM.CountTEAMNominee(); + if (i = 0) then + begin + MessageDlg(''' + No available nominees were found for this event. + Note: nominees (swimmers) in closed or raced heats are excluded. + Auto-Build TEAM-RELAYs was aborted. + ''', mtError, [mbOK], 0, mbOK); + exit; + end; + {open the RELAY_TEAM dialogue.} + dlg := TABRelay.Create(self); + rtnValue := dlg.ShowModal; + {closing the form here ensures prefences have been + written out to the preference ini file.} + dlg.Free; + if not IsPositiveResult(rtnValue) then exit; + {TODO -oBSA -cAutoBuildRelay : Partial team. Exclude nominee dialogue.} + {Hand over process to uABRelayExec.} + ABRelay := TABRelayExec.Create(Self); + ABRelay.Prepare(SCM.scmConnection, SCM.Event_ID); + ABRelay.ExecAutoBuildRelay(); + if ABRelay.Success then + begin + Refresh_Heat; + Refresh_IndvTeam; + // Requery SCM.qryEvent to update entrant count. + PostMessage(Handle, SCM_UPDATEENTRANTCOUNT, 0, 0); + // Set flag for statusbar update. + PostMessage(Handle, SCM_UPDATESTATUSBAR, 0, 0); + end; + ABRelay.free; +end; procedure TMain.ActionManager1Update(Action: TBasicAction; var Handled: boolean); @@ -2289,8 +2337,14 @@ procedure TMain.Heat_AutoBuildExecute(Sender: TObject); success: boolean; EventID, rtnValue: integer; begin - // actn..Update determines if this routine is accessable. + if SCM.CurrEventType = etINDV then + begin + Heat_AutoBuildRelayExecute(Sender); + exit; + end; + + // actn..Update determines if this routine is accessable. EventID := SCM.dsEvent.DataSet.FieldByName('EventID').AsInteger; // 'Quick' check if we have nominees to auto-create heats. @@ -2360,9 +2414,7 @@ procedure TMain.Heat_AutoBuildUpdate(Sender: TObject); // A distance and stroke is needed before a new heat can be built if not SCM.dsEvent.DataSet.FieldByName('DistanceID').IsNull and not SCM.dsEvent.DataSet.FieldByName('StrokeID').IsNull then - // no auto-build for TEAM EVENTS - yet... - { TODO -oBSA -cGeneral : AutoBuildUpdate TEAM events. } - if SCM.CurrEventType = etINDV then DoEnable := true; + DoEnable := true; end; TAction(Sender).Enabled := DoEnable; end; From 103a37abdcc6779b9ca17a7108b9a9c6a7f0f1b6 Mon Sep 17 00:00:00 2001 From: Artanemus <69775305+Artanemus@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:24:07 +1000 Subject: [PATCH 4/6] Debug AutoBuild Relays - nearly done --- AUTOBUILD/dlgABRelay.dfm | 2 + AUTOBUILD/dlgABRelay.pas | 4 +- AUTOBUILD/dmABRelayData.dfm | 206 +++++------ AUTOBUILD/dmABRelayData.pas | 19 ++ AUTOBUILD/uABRelayExec.pas | 658 +++++++++++++++++++----------------- dmSCM.dfm | 126 +------ frmMain.pas | 4 +- 7 files changed, 479 insertions(+), 540 deletions(-) diff --git a/AUTOBUILD/dlgABRelay.dfm b/AUTOBUILD/dlgABRelay.dfm index 76dffea..a8f168f 100644 --- a/AUTOBUILD/dlgABRelay.dfm +++ b/AUTOBUILD/dlgABRelay.dfm @@ -1,6 +1,7 @@ object ABRelay: TABRelay Left = 0 Top = 0 + BorderStyle = bsDialog Caption = 'AutoBuild Team Event ...' ClientHeight = 557 ClientWidth = 507 @@ -10,6 +11,7 @@ object ABRelay: TABRelay Font.Height = -16 Font.Name = 'Segoe UI' Font.Style = [] + Position = poOwnerFormCenter OnCreate = FormCreate OnDestroy = FormDestroy OnShow = FormShow diff --git a/AUTOBUILD/dlgABRelay.pas b/AUTOBUILD/dlgABRelay.pas index baa0093..382cba6 100644 --- a/AUTOBUILD/dlgABRelay.pas +++ b/AUTOBUILD/dlgABRelay.pas @@ -137,7 +137,7 @@ procedure TABRelay.vimgHint2Click(Sender: TObject); If a team doesn't have a full complement of swimmers, that team will be removed. '''; - bhintABRelay.ShowHint(vimgHint1); + bhintABRelay.ShowHint(vimgHint2); end; procedure TABRelay.vimgHint3Click(Sender: TObject); @@ -147,7 +147,7 @@ procedure TABRelay.vimgHint3Click(Sender: TObject); Relay teams will only be allowed swimmers that share the same 'House' (as designated in the member's profile). '''; - bhintABRelay.ShowHint(vimgHint1); + bhintABRelay.ShowHint(vimgHint3); end; diff --git a/AUTOBUILD/dmABRelayData.dfm b/AUTOBUILD/dmABRelayData.dfm index c083a73..52c6158 100644 --- a/AUTOBUILD/dmABRelayData.dfm +++ b/AUTOBUILD/dmABRelayData.dfm @@ -2,6 +2,7 @@ object ABRelayData: TABRelayData Height = 480 Width = 640 object FDCommandUpdateEntrant: TFDCommand + Connection = SCM.scmConnection CommandText.Strings = ( 'USE [SwimClubMeet];' '' @@ -154,7 +155,8 @@ object ABRelayData: TABRelayData UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' UpdateOptions.KeyFields = 'MemberID' SQL.Strings = ( - 'USE SwimClubMeet;' + '--USE SwimClubMeet' + '--GO' '' 'DECLARE @EventID AS INT;' 'DECLARE @Algorithm INT;' @@ -167,14 +169,16 @@ object ABRelayData: TABRelayData 'DECLARE @BottomPercent FLOAT;' 'DECLARE @TopNumber INT;' '' + 'DECLARE @SQL NVARChar(MAX);' + '' '' 'SET @EventID = :EVENTID;' 'SET @Algorithm = :ALGORITHM;' 'SET @ToggleName = :TOGGLENAME;' - 'SET @CalcDefault = :CALCDEFAULT' - 'SET @BottomPercent = :BOTTOMPERCENT' - 'SET @DistanceID = :XDISTANCEID' - 'SET @TopNumber = :TOPNUMBER' + 'SET @CalcDefault = :CALCDEFAULT;' + 'SET @BottomPercent = :BOTTOMPERCENT;' + 'SET @DistanceID = :XDISTANCEID;' + 'SET @TopNumber = :TOPNUMBER;' '' 'SET @StrokeID =' '(' @@ -211,38 +215,40 @@ object ABRelayData: TABRelayData ' ON Team.TeamID = TeamEntrant.TeamID' ' WHERE HeatIndividual.EventID = @EventID' ' AND HeatIndividual.HeatStatusID = 1;' - ' ' + ' ' + ' ' + '-- Construct dynamic SQL' '' - 'SELECT TOP @TopNumber Nominee.EventID' - ' , Nominee.MemberID' - ' , Member.GenderID' - ' , dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE' - ' , dbo.SwimmerGenderToString(Member.MemberID) AS Gender' + 'SELECT TOP (@TopNumber) ' + 'Nominee.NomineeID,' + ' Nominee.EventID,' + ' Nominee.MemberID,' + ' Member.GenderID,' + ' dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE,' + ' dbo.SwimmerGenderToString(Member.MemberID) AS Gender,' - ' , dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, ' + - 'Member.MemberID, @DistanceID, @StrokeID, @SessionStart) AS TTB' + ' dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, Mem' + + 'ber.MemberID, @DistanceID, @StrokeID, @SessionStart) AS TTB,' - ' , dbo.PersonalBest(Member.MemberID, @DistanceID, @StrokeID,' + - ' @SessionStart) AS PB' - ' , CASE' - ' WHEN @ToggleName = 0 THEN' + ' dbo.PersonalBest(Member.MemberID, @DistanceID, @StrokeID, @S' + + 'essionStart) AS PB,' + ' CASE ' - ' SUBSTRING(CONCAT(UPPER([LastName]), '#39', '#39', [FirstN' + - 'ame]), 0, 30)' - ' WHEN @ToggleName = 1 THEN' + ' WHEN @ToggleName = 0 THEN SUBSTRING(CONCAT(UPPER([LastNa' + + 'me]), '#39#39', '#39#39', [FirstName]), 0, 30)' - ' SUBSTRING(CONCAT([FirstName], '#39', '#39', UPPER([LastNa' + - 'me])), 0, 48)' - ' END AS FName' + ' WHEN @ToggleName = 1 THEN SUBSTRING(CONCAT([FirstName], ' + + #39#39', '#39#39', UPPER([LastName])), 0, 48)' + ' END AS FName' 'FROM Nominee' - ' LEFT OUTER JOIN #tmpID' - ' ON #tmpID.MemberID = Nominee.MemberID' - ' LEFT OUTER JOIN Member' - ' ON Nominee.MemberID = Member.MemberID' + 'LEFT OUTER JOIN #tmpID ON #tmpID.MemberID = Nominee.MemberID' + 'LEFT OUTER JOIN Member ON Nominee.MemberID = Member.MemberID' 'WHERE Nominee.EventID = @EventID' - ' AND #tmpID.MemberID IS NULL ' - 'ORDER BY TTB ASC ' - ' ;' + 'AND #tmpID.MemberID IS NULL' + 'ORDER BY TTB ASC;' + ' ' + ' ' + ' ' '') Left = 128 Top = 152 @@ -292,75 +298,12 @@ object ABRelayData: TABRelayData end object dsRelayNominee: TDataSource DataSet = qryRelayNominee - Left = 232 + Left = 272 Top = 152 end object qryCountRNominee: TFDQuery ActiveStoredUsage = [auDesignTime] FilterOptions = [foCaseInsensitive] - Filter = '[FName] LIKE '#39'%b%'#39 - Indexes = < - item - Active = True - Selected = True - Name = 'idxMemberFName' - Fields = 'FName' - end - item - Active = True - Name = 'idxMemberFNameDESC' - Fields = 'FName' - DescFields = 'FName' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxTTB' - Fields = 'TTB' - end - item - Active = True - Name = 'idxTTBDESC' - Fields = 'TTB' - DescFields = 'TTB' - Options = [soDescending] - end - item - Active = True - Name = 'idxPB' - Fields = 'PB' - end - item - Active = True - Name = 'idxPBDESC' - Fields = 'PB' - DescFields = 'PB' - Options = [soDescending] - end - item - Active = True - Name = 'idxAge' - Fields = 'AGE' - end - item - Active = True - Name = 'idxAgeDESC' - Fields = 'AGE' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxGender' - Fields = 'GenderID' - end - item - Active = True - Name = 'idxGenderDESC' - Fields = 'GenderID' - Options = [soDescNullLast, soDescending] - end> - IndexName = 'idxMemberFName' - DetailFields = 'MemberID' Connection = SCM.scmConnection FormatOptions.AssignedValues = [fvFmtDisplayTime] FormatOptions.FmtDisplayTime = 'nn:ss.zzz' @@ -403,7 +346,7 @@ object ABRelayData: TABRelayData ' WHERE HeatIndividual.EventID = @EventID ' ' AND HeatIndividual.HeatStatusID = 1;' '' - 'SELECT Count(NomineeID) AS CountNominees' + 'SELECT ISNULL(Count(NomineeID), 0) AS CountNominees' 'FROM Nominee' ' LEFT OUTER JOIN #tmpA' ' ON #tmpA.MemberID = Nominee.MemberID' @@ -422,4 +365,77 @@ object ABRelayData: TABRelayData Value = Null end> end + object qryTNum: TFDQuery + Connection = SCM.scmConnection + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + SQL.Strings = ( + '' + '' + 'DECLARE @TeamNumExists AS BIT;' + 'DECLARE @DistanceID AS INTEGER;' + '' + 'SET @DistanceID = :DISTANCEID;' + '' + 'IF EXISTS (SELECT * ' + ' FROM INFORMATION_SCHEMA.COLUMNS ' + ' WHERE TABLE_NAME = '#39'Distance'#39' ' + ' AND COLUMN_NAME = '#39'TeamNum'#39')' + 'BEGIN' + ' SET @TeamNumExists = 1' + 'END' + 'ELSE' + 'BEGIN' + ' SET @TeamNumExists = 0' + 'END' + '' + 'SELECT ' + ' [ABREV], ' + #9'CASE ' + #9#9'WHEN @TeamNumExists = 1 THEN '#39'[TeamNum]'#39 + #9#9'ELSE 0' + #9'END AS TNum' + 'FROM ' + ' [dbo].[Distance]' + 'WHERE ' + ' DistanceID = @DistanceID;' + '' + '' + '') + Left = 128 + Top = 232 + ParamData = < + item + Name = 'DISTANCEID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + end + object qryLastTeamNameID: TFDQuery + ActiveStoredUsage = [auDesignTime] + Connection = SCM.scmConnection + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + SQL.Strings = ( + 'DECLARE @EventID INTEGER;' + 'SET @EventID = :EVENTID;' + '' + 'SELECT ISNULL(MAX(TeamNameID), 0) AS LastTeamNameID FROM Team ' + 'INNER JOIN HeatIndividual on Team.HeatID = HeatIndividual.HeatID' + 'WHERE EventID = @EventID;') + Left = 128 + Top = 304 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = 65 + end> + end end diff --git a/AUTOBUILD/dmABRelayData.pas b/AUTOBUILD/dmABRelayData.pas index f76bce0..0e623a4 100644 --- a/AUTOBUILD/dmABRelayData.pas +++ b/AUTOBUILD/dmABRelayData.pas @@ -15,6 +15,8 @@ TABRelayData = class(TDataModule) qryRelayNominee: TFDQuery; dsRelayNominee: TDataSource; qryCountRNominee: TFDQuery; + qryTNum: TFDQuery; + qryLastTeamNameID: TFDQuery; private fAutoBuildRelayDataActive: Boolean; FConnection: TFDConnection; @@ -23,6 +25,7 @@ TABRelayData = class(TDataModule) constructor Create(AOwner: TComponent); override; destructor Destroy(); override; procedure ActivateTable(); + function GetLastTeamNameID(AEventID: integer): integer; end; var @@ -65,4 +68,20 @@ destructor TABRelayData.Destroy; +function TABRelayData.GetLastTeamNameID(AEventID: integer): integer; +begin + result := 0; + if AEventID = 0 then exit; + if Assigned(FConnection) and FConnection.Connected then + begin + qryLastTeamNameID.Close; + qryLastTeamNameID.ParamByName('EVENTID').AsInteger := AEventID; + qryLastTeamNameID.Prepare; + qryLastTeamNameID.Open; + if qryLastTeamNameID.Active then + result := qryLastTeamNameID.FieldByName('LastTeamNameID').AsInteger; + qryLastTeamNameID.Close; + end; +end; + end. diff --git a/AUTOBUILD/uABRelayExec.pas b/AUTOBUILD/uABRelayExec.pas index c480a2a..8da923a 100644 --- a/AUTOBUILD/uABRelayExec.pas +++ b/AUTOBUILD/uABRelayExec.pas @@ -41,87 +41,66 @@ TSwimmer = record TABRelayExec = class(TComponent) private - + FConnection: TFDConnection; + fDistanceID: integer; + fEventID: integer; { private declarations } fExcludeOutsideLanes: boolean; - fVerbose: boolean; fHeatAlgorithm: integer; - fRaceTimeBottomPercent: integer; - fUseDefRaceTime: boolean; - fSeperateGender: boolean; - fSeedMethod: integer; - fSeedDepth: integer; - fSuccess: boolean; - FConnection: TFDConnection; - fEventID: integer; - fDistanceID: integer; - fStrokeID: integer; - - fTeamDistanceMetres: integer; fIndvDistanceMetres: integer; - - fNumOfTeams: integer; + fNumOfHeats: integer; fNumOfNominees: integer; + fNumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. fNumOfSwimmers: integer; - fNumOfHeats: integer; fNumOfSwimmersPerTeam: integer; + fNumOfTeams: integer; fNumOfTeamsPerHeat: integer; - fNumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. - + fRaceTimeBottomPercent: integer; + fSeedDepth: integer; + fSeedMethod: integer; + fSeperateGender: boolean; + fStrokeID: integer; + fSuccess: boolean; + fTeamDistanceMetres: integer; + fUseDefRaceTime: boolean; + fVerbose: boolean; + Generations: Integer; // = 1000; + // An array of heats. + Heats: array of THeat; + MutationRate: Double; // = 0.01; + // An array of swimmers, used by 'bin packing routine' using a genetic algorithm. + Population: array of TChromosome; + PopulationSize: Integer; // ... fNumOfSwimmers. // D Y N A M I C A R R A Y S . // Nominee Database Pool : swimmers who nominated for the event. Swimmers: array of TSwimmer; - // An array of heats. - Heats: array of THeat; // An array of teams - each team will contain MemberID x fNumOfSwimmersPerTeam. Teams: array of TTeam; - // An array of swimmers, used by 'bin packing routine' using a genetic algorithm. - Population: array of TChromosome; - - PopulationSize: Integer; // ... fDivNumOfSwimmers. - Generations: Integer; // = 1000; - MutationRate: Double; // = 0.01; - - - function GetPoolLaneCount: integer; - function GetNumberOfSwimmersPerTeam(EventID: integer): integer; - function GetIndvDistanceMetres(EventID, fNumOfSwimmersPerTeam: integer): integer; - function GetStrokeID(AEventID: integer): integer; - function GetDistanceID(EventID: integer): integer; - - procedure ReadPreferences(IniFileName: string); - procedure RetriveSwimmingData(AEventID, fNumOfPoolLanes, fNumOfSwimmersPerTeam: integer); - procedure InsertIntoTeams(); - // B i n p a c k i n g r o u t i n e s . function Crossover(Parent1, Parent2: TChromosome): TChromosome; function Fitness(Chromosome: TChromosome): Double; - procedure GeneticAlgorithm; + function GeneticAlgorithm: Boolean; + function GetDistanceID(EventID: integer): integer; function GetNumberOfNominees(AEventID: Integer): integer; + function GetNumberOfSwimmersPerTeam(ADistanceID: integer): integer; + function GetPoolLanes: integer; + function GetStrokeID(AEventID: integer): integer; + function GetTeamDistanceMetres(ADistanceID: integer): integer; function InitializePopulation: TChromosome; + function InsertIntoTeams(): boolean; procedure Mutate(var Chromosome: TChromosome); + procedure ReadPreferences(IniFileName: string); + function RetriveSwimmingData(): boolean; procedure Swap(var A, B: Integer); function TournamentSelection: TChromosome; - function GetXDistanceID(EventID, fNumOfSwimmersPerTeam: integer): integer; - - - - // procedure Distribute(FDQuery: TFDQuery; AEventID: integer); - - protected - { protected declarations } - - public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; { public declarations } procedure ExecAutoBuildRelay(); procedure Prepare(AConnection: TFDConnection; AEventID: integer); - constructor Create(AOwner: TComponent); override; - destructor Destroy; override; - { published declarations } property Success: boolean read fSuccess write fSuccess; - end; implementation @@ -129,7 +108,7 @@ implementation { TAutoBuildRelayExec } uses System.Math, System.IniFiles, SCMUtility, - dmABRelayData, system.Generics.Collections; + dmABRelayData, system.Generics.Collections, System.StrUtils; { procedure TAutoBuildRelayExec.Distribute(FDQuery: TFDQuery; AEventID: integer); @@ -215,11 +194,24 @@ destructor TABRelayExec.Destroy; inherited; end; +function TABRelayExec.Crossover(Parent1, Parent2: TChromosome): TChromosome; +var + i, CrossoverPoint: Integer; + Child: TChromosome; +begin + SetLength(Child, Length(Parent1)); + CrossoverPoint := Random(Length(Parent1)); + for i := 0 to CrossoverPoint do + Child[i] := Parent1[i]; + for i := CrossoverPoint + 1 to High(Parent2) do + Child[i] := Parent2[i]; + Result := Child; +end; + procedure TABRelayExec.ExecAutoBuildRelay(); var IniFileName: TFileName; v: variant; - fNumOfPoolLanes: integer; s: string; begin // A S S E R T . @@ -231,9 +223,13 @@ procedure TABRelayExec.ExecAutoBuildRelay(); IniFileName := SCMUtility.GetSCMPreferenceFileName(); if (FileExists(IniFileName)) then ReadPreferences(IniFileName); // C h e c k e v e n t t y p e . - v := - SCM.scmConnection.ExecSQLScalar - ('SELECT EventTypeID FROM [dbo].[Event] WHERE EventID = :ID', [fEventID]); + s:= ''' + SELECT EventTypeID FROM [dbo].[Event] + INNER JOIN [dbo].[Distance] ON Event.DistanceID = Distance.DistanceID + WHERE EventID = :ID + '''; + + v := SCM.scmConnection.ExecSQLScalar(s, [fEventID]); if VarIsNull(v) then // Is this a team event? begin @@ -288,7 +284,8 @@ procedure TABRelayExec.ExecAutoBuildRelay(); // D i s t a n c e I D . fDistanceID := GetDistanceID(fEventID); // C h e c k P o o l l a n e s . - fNumOfPoolLanes := GetPoolLaneCount; + // Note: checks preferences - excluded outside lanes... + fNumOfPoolLanes := GetPoolLanes; // A S S E R T . if fNumOfPoolLanes = 0 then begin @@ -303,17 +300,18 @@ procedure TABRelayExec.ExecAutoBuildRelay(); exit; end; - // use ABREV in dbo.Distance to calculate NumOfSwimmersInTeam. - // TODO: + // use TeamNum or ABREV in dbo.Distance to calculate NumOfSwimmersInTeam. + // On error returns default - 4 swimmers per team. fNumOfSwimmersPerTeam := GetNumberOfSwimmersPerTeam(fDistanceID); // use Meters in dbo.Distance to calculate team distance. - fTeamDistanceMetres:= 0; + // On error returns 0. + fTeamDistanceMetres:= GetTeamDistanceMetres(fDistanceID); + if (fTeamDistanceMetres = 0) then exit; // Individual Distance = the distance swum by each swimmer in the team. // WIT: 4x25m relay Total 100m ... entrants swim 25m. - fIndvDistanceMetres := GetIndvDistanceMetres(fEventID, fNumOfSwimmersPerTeam); - // use ABREV in dbo.Distance to calculate indv distance. - fIndvDistanceMetres:= 0; - + fIndvDistanceMetres := (fTeamDistanceMetres div fNumOfSwimmersPerTeam); + // number of members who indicated that they wanted to swim the relay event. + // Does not include members in raced or closed heats for this relay event. fNumOfNominees := GetNumberOfNominees(fEventID); if fNumOfNominees = 0 then // No swimmers!. begin @@ -329,10 +327,14 @@ procedure TABRelayExec.ExecAutoBuildRelay(); exit; end; + // D i s t r i b u t e 1 . (Gender, house, etc) + {TODO -oBSA -cAutoBuild Relays : Seperate gender} + {TODO -oBSA -cAutoBuild Relays : Group by house} + // Perfect number of swimmers - no partial teams. - fNumOfSwimmers := v - (v mod fNumOfSwimmersPerTeam); + fNumOfSwimmers := fNumOfNominees - (fNumOfNominees mod fNumOfSwimmersPerTeam); // Perfect number of teams. - fNumOfTeams := v div fNumOfSwimmers; + fNumOfTeams := fNumOfSwimmers div fNumOfSwimmersPerTeam; {TODO -oBSA -cAutoBuild Relay : Enable the user to select nominees to trim. } if (fNumOfNominees > fNumOfSwimmers) then @@ -341,7 +343,7 @@ procedure TABRelayExec.ExecAutoBuildRelay(); begin s := ''' Not enough swimmers to complete all teams. - Entrants will be removed (slowest swimmer first. Nomination will remain). + Spare entrants will be removed. (Slowest swimmers first. Nominations will remain). Manually assign these nominees - should you wish them to participate. '''; // if fVerbose then @@ -352,33 +354,37 @@ procedure TABRelayExec.ExecAutoBuildRelay(); // D I S R I B U T E - TEAMS ACROSS MULIT-HEATS. // check that the number of relay-teams doesn't exceed the number of lanes. if fNumOfTeams > fNumOfPoolLanes then - fNumOfHeats := Ceil(fNumOfTeams / fNumOfPoolLanes); + fNumOfHeats := Ceil(fNumOfTeams / fNumOfPoolLanes) + else + fNumOfHeats := 1; // distribute evenly relay teams accross heats. fNumOfTeamsPerHeat := Ceil(fNumOfTeams / fNumOfHeats); - - - - // Assign params passed. - fNumOfPoolLanes := fNumOfPoolLanes; - - {TODO -oBSA -cGeneral : Thread: AutoBuild Relays - Progress bar ....} // Clean OUT ALL HEATs, TEAMs, TEAMEntrants, TEAMSPLITs. // Does not remove raced or closed heats . SCM.Heat_DeleteAll(fEventID, true); - // D i s t r i b u t e 1 . (Gender, house, etc) - // R e t r i v e . Check - fSuccess. - RetriveSwimmingData(fEventID, fNumOfPoolLanes, fNumOfSwimmersPerTeam); + // D I M A R R A Y S . . . + SetLength(Swimmers, fNumOfSwimmers); + SetLength(Heats, fNumOfHeats); + SetLength(Teams, fNumOfTeams); + + // R e t r i v e S w i m m e r s . Check - fSuccess. + fSuccess := RetriveSwimmingData(); + if fSuccess then begin - // Process the data using Genetic Algorithm - GeneticAlgorithm(); - if fSuccess then InsertIntoTeams(); - // D i s t r i b u t e 2 . (Gender, house, etc) - // if fSuccess then DistributeTeams(FDQuery); + // Process the data using Genetic Algorithm. + PopulationSize := fNumOfSwimmers; // perfect number of swimmers. + fSuccess := GeneticAlgorithm(); + if fSuccess then + begin + fSuccess := InsertIntoTeams(); + // D i s t r i b u t e 2 . (Gender, house, etc) + // if fSuccess then DistributeTeams(FDQuery); + end; end; // Refresh UI and data @@ -386,6 +392,74 @@ procedure TABRelayExec.ExecAutoBuildRelay(); // SendMessage(TForm(Owner).Handle, SCM_AUTOBUILDRELAYSFIN, 0, 0); end; +function TABRelayExec.Fitness(Chromosome: TChromosome): Double; +var + i, j: Integer; + TotalTime, MaxTime, MinTime: TTime; +begin + MaxTime := 0; + MinTime := MaxInt; + TotalTime := 0; + for i := 0 to High(Chromosome) do + begin + Chromosome[i].RaceTime := 0; + for j := 0 to High(Chromosome[i].Entrants) do + begin + Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; + end; + if Chromosome[i].RaceTime > MaxTime then + MaxTime := Chromosome[i].RaceTime; + if Chromosome[i].RaceTime < MinTime then + MinTime := Chromosome[i].RaceTime; + TotalTime := TotalTime + Chromosome[i].RaceTime; + end; + Result := MaxTime - MinTime; +end; + +function TABRelayExec.GeneticAlgorithm: boolean; +var + i, j: Integer; + Parent1, Parent2, Child: TChromosome; + BestChromosome: TChromosome; + BestFitness, CurrentFitness: Double; +begin + + SetLength(Population, PopulationSize); + for i := 0 to PopulationSize - 1 do + Population[i] := InitializePopulation; + + for i := 0 to Generations - 1 do + begin + for j := 0 to PopulationSize - 1 do + begin + Parent1 := TournamentSelection; + Parent2 := TournamentSelection; + Child := Crossover(Parent1, Parent2); + Mutate(Child); + Population[j] := Child; + end; + end; + + BestChromosome := Population[0]; + BestFitness := Fitness(BestChromosome); + for i := 1 to PopulationSize - 1 do + begin + CurrentFitness := Fitness(Population[i]); + if CurrentFitness < BestFitness then + begin + BestFitness := CurrentFitness; + BestChromosome := Population[i]; + end; + end; + + // Use BestChromosome to set your final teams + SetLength(Teams, Length(BestChromosome)); + for i := 0 to High(BestChromosome) do + Teams[i] := BestChromosome[i]; + + result := true; +end; + function TABRelayExec.GetDistanceID(EventID: integer): integer; var v: variant; @@ -397,29 +471,56 @@ function TABRelayExec.GetDistanceID(EventID: integer): integer; result := v end; -function TABRelayExec.GetIndvDistanceMetres(EventID, - fNumOfSwimmersPerTeam: integer): integer; +function TABRelayExec.GetNumberOfNominees(AEventID: Integer): integer; +var + v: variant; begin - + Result := 0; + // Test for nominees. Count total number of nominees. + ABRelayData.qryCountRNominee.Close; + ABRelayData.qryCountRNominee.ParamByName('EVENTID').AsInteger := AEventID; + ABRelayData.qryCountRNominee.Prepare; + ABRelayData.qryCountRNominee.Open; + if ABRelayData.qryCountRNominee.Active then + v := ABRelayData.qryCountRNominee.FieldByName('CountNominees').AsInteger; + ABRelayData.qryCountRNominee.Close; + // The query is unlikely to return NULL as this is trapped in the + // MS SQL script - using ... + // SELECT ISNULL(Count(NomineeID), 0) AS CountNominees... + if VarIsNull(v) or (v=0) then exit; + Result := v; end; -function TABRelayExec.GetNumberOfSwimmersPerTeam(EventID: integer): integer; +function TABRelayExec.GetNumberOfSwimmersPerTeam(ADistanceID: integer): integer; var - v: variant; s: string; + i: integer; begin - result := 0; - s := ''' - SELECT ABREV FROM [SwimClubMeet].[dbo].[Event] - INNER JOIN [dbo].[Distance] ON [Event].DistanceID = [Distance].[DistanceID] - WHERE EventID = :ID - '''; - v := SCM.scmConnection.ExecSQLScalar(s, [EventID]); - if VarIsNull(v) then exit; - + result := 4; // Default is 4 swimmers in a each team. + if not assigned(ABRelayData) then exit; + // Use TeamNum column (db1.1.5.4) - if available + ABRelayData.qryTNum.Close; + ABRelayData.qryTNum.ParamByName('DistanceID').AsInteger := ADistanceID; + ABRelayData.qryTNum.Prepare; + ABRelayData.qryTNum.Open; + + if not ABRelayData.qryTNum.Active then exit; + i := ABRelayData.qryTNum.FieldByName('TNum').AsInteger; + if (i > 0) then + result := i // + else + begin + // use ABREV : extract swimmers per team. (example text '4x100') + s := ABRelayData.qryTNum.FieldByName('ABREV').AsString; + if (Pos('x', s) = 0) then exit; // bad syntax + s := Copy(s, 1, Pos('x', s) - 1); // the team size. Swimmers per team. + i := StrToIntDef(s, 0); // bad syntax - not a number + if (i > 0) then + result := i; // return team size else use default (4). + end; end; -function TABRelayExec.GetPoolLaneCount: integer; +function TABRelayExec.GetPoolLanes: integer; var Lanes: integer; begin @@ -444,47 +545,52 @@ function TABRelayExec.GetStrokeID(AEventID: integer): integer; result := v; end; -function TABRelayExec.GetXDistanceID(EventID, - fNumOfSwimmersPerTeam: integer): integer; +function TABRelayExec.GetTeamDistanceMetres(ADistanceID: integer): integer; var v: variant; - meters: Integer; + s: string; begin result := 0; - v := SCM.scmConnection.ExecSQLScalar(''' - SELECT[meters] - FROM[SwimClubMeet].[dbo].[Distance] - INNER JOIN[dbo].[Event] ON[Distance].[DistanceID] = [Event].[DistanceID] - WHERE EventID = :ID - ''', [EventID]); + { NOTE: Using American English 'Meters' NOT British English 'metres'} + s := ''' + SELECT Meters FROM [dbo].[Distance] + WHERE DistanceID = :ID + '''; + v := SCM.scmConnection.ExecSQLScalar(s, [ADistanceID]); if not VarIsNull(v) then + result := v; +end; + +function TABRelayExec.InitializePopulation: TChromosome; +var + i, j: Integer; + Chromosome: TChromosome; +begin + SetLength(Chromosome, fNumOfTeams); + for i := 0 to fNumOfTeams - 1 do begin - // divide the total relay distance by the number of swimmers in the relay - meters := Floor(v/fNumOfSwimmersPerTeam); - // cross reference value 'meters' to find the XDistanceID for a swimmer. - v := SCM.scmConnection.ExecSQLScalar(''' - SELECT[DistanceID] - FROM[SwimClubMeet].[dbo].[Distance] - WHERE[meters] = :ID AND [EventTypeID] = 1 - ''', [meters]); - if not VarIsNull(v) then + Chromosome[i].ID := i; + Chromosome[i].Lane := i mod fNumOfPoolLanes; + SetLength(Chromosome[i].Entrants, fNumOfSwimmersPerTeam); + for j := 0 to fNumOfSwimmersPerTeam - 1 do begin - result := v; + Chromosome[i].Entrants[j] := Random(fNumOfSwimmers); end; end; + Result := Chromosome; end; -procedure TABRelayExec.InsertIntoTeams; +function TABRelayExec.InsertIntoTeams: Boolean; var qry1, qry2, qry3: TFDQuery; AHeatID: integer; ATeamID: integer; ATeamEntrantID: integer; - i, j, m, n, p, r: integer; + ATeamNameID: integer; + s: string; + i, j, m, n, p, r, lanenum: integer; begin - - - + result := false; qry1 := TFDQuery.Create(nil); qry2 := TFDQuery.Create(nil); qry3 := TFDQuery.Create(nil); @@ -496,40 +602,69 @@ procedure TABRelayExec.InsertIntoTeams; for j := Low(Heats) to High(Heats) do Begin // Insert into dbo.HeatIndividual - qry1.SQL.Text := 'INSERT INTO HeatIndividual (HeatNum, OpenDT, HeatTypeID, HeatStatusID) VALUES (:HEATNUM, GETDATE(), 1, 1)'; - qry1.ParamByName('HEATNUM').AsInteger := Heats[j].HeatNum; - qry1.ExecSQL; - // Get the new heat record's ID - AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); + // HeatTypeID - Heat, HeatStatus - open, HeatNum - (1 ... ) + s := ''' + INSERT INTO HeatIndividual (EventID, HeatNum, OpenDT, HeatTypeID, HeatStatusID) + VALUES (:EVENTID, :HEATNUM, GETDATE(), 1, 1)'; + '''; + qry1.SQL.Text := s; + qry1.ParamByName('EVENTID').AsInteger := fEventID; + qry1.ParamByName('HEATNUM').AsInteger := Heats[j].HeatNum; + qry1.ExecSQL; + // Get the newly created heat's record ID. + AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); + + { T E A M N A M E I D . } + // dbo.TeamName is a table filled with ID's starting from 1 .. 26 + // and Caption's (1)'TeamA' ... (26)'TeamZ'. + ATeamNameID := ABRelayData.GetLastTeamNameID(fEventID); for i := 0 to fNumOfTeamsPerHeat - 1 do begin + s := ''' + INSERT INTO Team (Lane, TeamNameID, HeatID) + VALUES (:LANE, :TEAMNAMEID, :HEATID); + '''; // Insert into Team table - qry2.SQL.Text := 'INSERT INTO Team (Lane, TeamNameID, HeatID) VALUES (:LANE, :TEAMNAMEID, :HEATID)'; - qry2.ParamByName('LANE').AsInteger := Teams[i].Lane; - // dbo.TeamName is a table filled with ID's starting from 1, ending in 26 and Caption's TeamA...TeamZ. - // i is base 0. - // i is range 0 to NumOfTeams (unlikely, though not impossible, to exceed 25 (the number of team names - 1). - qry2.ParamByName('TEAMNAMEID').AsInteger := Teams[i].ID+1; // Base zero. + qry2.SQL.Text := s; + // place team in lane - using center lanes first. + lanenum := SCMUtility.ScatterLanes(Teams[i].Lane, fNumOfPoolLanes); + qry2.ParamByName('LANE').AsInteger := lanenum; + inc(ATeamNameID, 1); + qry2.ParamByName('TEAMNAMEID').AsInteger := ATeamNameID; qry2.ParamByName('HEATID').AsInteger := AHeatID; // Base zero. qry2.ExecSQL; // Get the new team record's ID ATeamID := qry2.Connection.GetLastAutoGenValue('TeamID'); + + {TODO -oBSA -cAutoBuild Relay : + Sort entrants - Fastest last, 2nd fastest first, etc... } p := 1; // Swimming order 1 to NumOfEntrantsPerTeam. - // Insert into TeamEntrant table + + // Insert into TeamEntrant table. for m := Low(Teams[i].Entrants) to High(Teams[i].Entrants) do begin - qry3.SQL.Text := 'INSERT INTO TeamEntrant (MemberID, Lane, TeamID, PersonalBest, TimeToBeat) VALUES (:MemberID, :Lane, :TeamID, :PersonalBest, :TimeToBeat)'; - qry3.ParamByName('MemberID').AsInteger := Teams[i].Entrants[m]; - qry3.ParamByName('Lane').AsInteger := p; - qry3.ParamByName('TeamID').AsInteger := ATeamID; + s := ''' + INSERT INTO TeamEntrant + (MemberID, Lane, TeamID, PersonalBest, TimeToBeat, StrokeID, IsDisqualified) + VALUES + (:MEMBERID, :LANE, :TEAMID, :PB, :TTB, :STROKEID, 0); + '''; + qry3.SQL.Text := s; + qry3.ParamByName('MEMBERID').AsInteger := Teams[i].Entrants[m]; + qry3.ParamByName('LANE').AsInteger := p; + qry3.ParamByName('TEAMID').AsInteger := ATeamID; + qry3.ParamByName('STROKEID').AsInteger := fStrokeID; + // TeamEntrant.Disqualified - BIT : NOT NULL + // qry3.ParamByName('DISQUALIFIED').AsBoolean := false; + // locate swimmer in Swimmers... for r := Low(Swimmers) to High(Swimmers) do begin if Swimmers[r].MemberID = Teams[i].Entrants[m] then begin - qry3.ParamByName('PersonalBest').AsTime := Swimmers[r].PB; - qry3.ParamByName('TimeToBeat').AsTime := Swimmers[r].TTB; + qry3.ParamByName('PB').AsTime := Swimmers[r].PB; + qry3.ParamByName('TTB').AsTime := Swimmers[r].TTB; break; end; end; @@ -547,6 +682,22 @@ procedure TABRelayExec.InsertIntoTeams; qry2.Free; qry3.Free; + result := true; + +end; + +procedure TABRelayExec.Mutate(var Chromosome: TChromosome); +var + Team1, Team2, Swimmer1, Swimmer2: Integer; +begin + if Random < MutationRate then + begin + Team1 := Random(Length(Chromosome)); + Team2 := Random(Length(Chromosome)); + Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); + Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); + Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); + end; end; procedure TABRelayExec.Prepare(AConnection: TFDConnection; AEventID: Integer); @@ -610,102 +761,72 @@ procedure TABRelayExec.ReadPreferences(IniFileName: string); iFile.free; end; -procedure TABRelayExec.RetriveSwimmingData(AEventID, fNumOfPoolLanes, - fNumOfSwimmersPerTeam: Integer); +function TABRelayExec.RetriveSwimmingData(): boolean; var - StrokeID, xDistanceID, i: integer; + i: integer; qry: TFDQuery; begin -{ -Retrieve Swimmer Data: Fetch swimmers’ data from the Nominee table, -focusing on their MemberID TTB (TimeToBeat) and PB (Personal Best) times. -} - - - qry := ABRelayData.qryRelayNominee; // improves reability. - + { + Retrieve Swimmer Data: Fetch swimmers’ data from the Nominee table, + focusing on their MemberID TTB (TimeToBeat) and PB (Personal Best) times. + } + result := false; + qry := ABRelayData.qryRelayNominee; // improves reability. + + qry.Close; + // Relay Event ID. + qry.ParamByName('EVENTID').AsInteger := fEventID; + // 0,1,2 - Default: 1. The avgerage of the 3 fastest race times. + qry.ParamByName('ALGORITHM').AsInteger := fHeatAlgorithm; + // Lastname, firstname (format for display of entrants name) + qry.ParamByName('TOGGLENAME').AsBoolean := true; + // if then entrant has no race history then ... + // Calculate a race time based on the mean average of the bottom... + qry.ParamByName('CALCDEFAULT').AsInteger := 1; + // ... bottom precent (Default is 50%) + qry.ParamByName('BOTTOMPERCENT').AsFloat := fRaceTimeBottomPercent; + // Distance swum by a swimmer in the Team-Relay. + // Relay's total distance DIV number of swimmers in the relay-team. + qry.ParamByName('XDISTANCEID').AsInteger := fIndvDistanceMetres; + // Perfect number of swimmers to fill ALL relay-teams perfectly. + qry.ParamByName('TOPNUMBER').AsInteger := fNumOfSwimmers; + qry.Prepare; + qry.Open; + + if not qry.Active then exit; + + if qry.IsEmpty then + begin qry.Close; - qry.ParamByName('TOPNUMBER').AsInteger := fNumOfSwimmers; - qry.ParamByName('EVENTID').AsInteger := AEventID; - qry.ParamByName('SESSIONSTART').AsDateTime := SCM.Session_Start; - qry.ParamByName('XDISTANCEID').AsDateTime := xDistanceID; - qry.ParamByName('STROKEID').AsDateTime := StrokeID; - qry.ParamByName('ALGORITHM').AsDateTime := fHeatAlgorithm; - qry.ParamByName('BOTTOMPERCENT').AsDateTime := fRaceTimeBottomPercent; - qry.Prepare; - qry.Open; - - if not qry.Active then exit; - - - if qry.IsEmpty then - begin - qry.Close; - fSuccess := false; - exit; - end; + exit; + end; - // DIM ARRAY - - SetLength(Swimmers, fNumOfSwimmers); - // iterate through the list of nominees + // Populate the array with swimmers. + // ARRAY SIZE: Perfect number of swimmers = qry.RecordCount. + try for I := Low(Swimmers) to High(Swimmers) do begin Swimmers[i].MemberID := qry.FieldByName('MemberID').AsInteger; Swimmers[i].NomineeID := qry.FieldByName('NomineeID').AsInteger; Swimmers[i].TTB := qry.FieldByName('TTB').AsDateTime; - Swimmers[i].PB := qry.FieldByName('pB').AsDateTime; + Swimmers[i].PB := qry.FieldByName('PB').AsDateTime; qry.Next; end; + except on E: Exception do + exit; + end; - SetLength(Heats, fNumOfHeats); - SetLength(Teams, fNumOfTeams); - - + result := true; end; - -function TABRelayExec.InitializePopulation: TChromosome; -var - i, j: Integer; - Chromosome: TChromosome; -begin - SetLength(Chromosome, fNumOfTeams); - for i := 0 to fNumOfTeams - 1 do - begin - Chromosome[i].ID := i; - Chromosome[i].Lane := i mod fNumOfPoolLanes; - SetLength(Chromosome[i].Entrants, fNumOfSwimmersPerTeam); - for j := 0 to fNumOfSwimmersPerTeam - 1 do - begin - Chromosome[i].Entrants[j] := Random(fNumOfSwimmers); - end; - end; - Result := Chromosome; -end; - -function TABRelayExec.Fitness(Chromosome: TChromosome): Double; +procedure TABRelayExec.Swap(var A, B: Integer); var - i, j: Integer; - TotalTime, MaxTime, MinTime: TTime; + Temp: Integer; begin - MaxTime := 0; - MinTime := MaxInt; - TotalTime := 0; - for i := 0 to High(Chromosome) do - begin - Chromosome[i].RaceTime := 0; - for j := 0 to High(Chromosome[i].Entrants) do - begin - Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; - end; - if Chromosome[i].RaceTime > MaxTime then - MaxTime := Chromosome[i].RaceTime; - if Chromosome[i].RaceTime < MinTime then - MinTime := Chromosome[i].RaceTime; - TotalTime := TotalTime + Chromosome[i].RaceTime; - end; - Result := MaxTime - MinTime; + Temp := A; + A := B; + B := Temp; end; function TABRelayExec.TournamentSelection: TChromosome; @@ -727,101 +848,6 @@ function TABRelayExec.TournamentSelection: TChromosome; Result := Population[BestIndex]; end; -function TABRelayExec.Crossover(Parent1, Parent2: TChromosome): TChromosome; -var - i, CrossoverPoint: Integer; - Child: TChromosome; -begin - SetLength(Child, Length(Parent1)); - CrossoverPoint := Random(Length(Parent1)); - for i := 0 to CrossoverPoint do - Child[i] := Parent1[i]; - for i := CrossoverPoint + 1 to High(Parent2) do - Child[i] := Parent2[i]; - Result := Child; -end; - -procedure TABRelayExec.Swap(var A, B: Integer); -var - Temp: Integer; -begin - Temp := A; - A := B; - B := Temp; -end; - -procedure TABRelayExec.Mutate(var Chromosome: TChromosome); -var - Team1, Team2, Swimmer1, Swimmer2: Integer; -begin - if Random < MutationRate then - begin - Team1 := Random(Length(Chromosome)); - Team2 := Random(Length(Chromosome)); - Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); - Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); - Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); - end; -end; - -procedure TABRelayExec.GeneticAlgorithm; -var - i, j: Integer; - Parent1, Parent2, Child: TChromosome; - BestChromosome: TChromosome; - BestFitness, CurrentFitness: Double; -begin - SetLength(Population, PopulationSize); - for i := 0 to PopulationSize - 1 do - Population[i] := InitializePopulation; - - for i := 0 to Generations - 1 do - begin - for j := 0 to PopulationSize - 1 do - begin - Parent1 := TournamentSelection; - Parent2 := TournamentSelection; - Child := Crossover(Parent1, Parent2); - Mutate(Child); - Population[j] := Child; - end; - end; - - BestChromosome := Population[0]; - BestFitness := Fitness(BestChromosome); - for i := 1 to PopulationSize - 1 do - begin - CurrentFitness := Fitness(Population[i]); - if CurrentFitness < BestFitness then - begin - BestFitness := CurrentFitness; - BestChromosome := Population[i]; - end; - end; - - // Use BestChromosome to set your final teams - SetLength(Teams, Length(BestChromosome)); - for i := 0 to High(BestChromosome) do - Teams[i] := BestChromosome[i]; -end; - -function TABRelayExec.GetNumberOfNominees(AEventID: Integer): integer; -var - v: variant; - s: string; -begin - Result := 0; - // Test for nominees. Count total number of nominees. - ABRelayData.qryCountRNominee.Close; - ABRelayData.qryCountRNominee.ParamByName('EVENTID').AsInteger := AEventID; - ABRelayData.qryCountRNominee.Prepare; - ABRelayData.qryCountRNominee.Open; - if ABRelayData.qryCountRNominee.Active then - v := ABRelayData.qryCountRNominee.FieldByName('CountNominees').AsInteger; - ABRelayData.qryCountRNominee.Close; - if VarIsNull(v) or (v=0) then exit; - Result := v; -end; diff --git a/dmSCM.dfm b/dmSCM.dfm index 9b75000..3c469e7 100644 --- a/dmSCM.dfm +++ b/dmSCM.dfm @@ -6,6 +6,7 @@ object SCM: TSCM Params.Strings = ( 'ConnectionDef=MSSQL_SwimClubMeet') ConnectedStoredUsage = [auDesignTime] + Connected = True LoginPrompt = False AfterDisconnect = scmConnectionAfterDisconnect Left = 80 @@ -2442,69 +2443,6 @@ object SCM: TSCM end object qryCountTEAMNominee: TFDQuery ActiveStoredUsage = [auDesignTime] - FilterOptions = [foCaseInsensitive] - Filter = '[FName] LIKE '#39'%b%'#39 - Indexes = < - item - Active = True - Selected = True - Name = 'idxMemberFName' - Fields = 'FName' - end - item - Active = True - Name = 'idxMemberFNameDESC' - Fields = 'FName' - DescFields = 'FName' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxTTB' - Fields = 'TTB' - end - item - Active = True - Name = 'idxTTBDESC' - Fields = 'TTB' - DescFields = 'TTB' - Options = [soDescending] - end - item - Active = True - Name = 'idxPB' - Fields = 'PB' - end - item - Active = True - Name = 'idxPBDESC' - Fields = 'PB' - DescFields = 'PB' - Options = [soDescending] - end - item - Active = True - Name = 'idxAge' - Fields = 'AGE' - end - item - Active = True - Name = 'idxAgeDESC' - Fields = 'AGE' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxGender' - Fields = 'GenderID' - end - item - Active = True - Name = 'idxGenderDESC' - Fields = 'GenderID' - Options = [soDescNullLast, soDescending] - end> - IndexName = 'idxMemberFName' DetailFields = 'MemberID' Connection = scmConnection FormatOptions.AssignedValues = [fvFmtDisplayTime] @@ -2570,68 +2508,6 @@ object SCM: TSCM object qryCountINDVNominee: TFDQuery ActiveStoredUsage = [auDesignTime] FilterOptions = [foCaseInsensitive] - Filter = '[FName] LIKE '#39'%b%'#39 - Indexes = < - item - Active = True - Selected = True - Name = 'idxMemberFName' - Fields = 'FName' - end - item - Active = True - Name = 'idxMemberFNameDESC' - Fields = 'FName' - DescFields = 'FName' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxTTB' - Fields = 'TTB' - end - item - Active = True - Name = 'idxTTBDESC' - Fields = 'TTB' - DescFields = 'TTB' - Options = [soDescending] - end - item - Active = True - Name = 'idxPB' - Fields = 'PB' - end - item - Active = True - Name = 'idxPBDESC' - Fields = 'PB' - DescFields = 'PB' - Options = [soDescending] - end - item - Active = True - Name = 'idxAge' - Fields = 'AGE' - end - item - Active = True - Name = 'idxAgeDESC' - Fields = 'AGE' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxGender' - Fields = 'GenderID' - end - item - Active = True - Name = 'idxGenderDESC' - Fields = 'GenderID' - Options = [soDescNullLast, soDescending] - end> - IndexName = 'idxMemberFName' DetailFields = 'MemberID' Connection = scmConnection FormatOptions.AssignedValues = [fvFmtDisplayTime] diff --git a/frmMain.pas b/frmMain.pas index d78cc79..a1a8c56 100644 --- a/frmMain.pas +++ b/frmMain.pas @@ -2337,8 +2337,8 @@ procedure TMain.Heat_AutoBuildExecute(Sender: TObject); success: boolean; EventID, rtnValue: integer; begin - - if SCM.CurrEventType = etINDV then + // A U T O - B U I L D R E L A Y TE A M . + if SCM.CurrEventType = etTEAM then begin Heat_AutoBuildRelayExecute(Sender); exit; From b97c25c74cd872aa0826c5258a7c218f8f6dbb7a Mon Sep 17 00:00:00 2001 From: Artanemus <69775305+Artanemus@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:50:20 +1000 Subject: [PATCH 5/6] AutoBuild Relays - running --- AUTOBUILD/dlgABRelay.dfm | 83 ++- AUTOBUILD/dlgABRelay.pas | 21 +- AUTOBUILD/dmABRelayData.dfm | 414 ++++++------ AUTOBUILD/dmABRelayData.pas | 7 +- AUTOBUILD/uABRelayExec.pas | 926 +++++++++++++++++++-------- SCM_PlusTestSnippet2.groupproj | 48 ++ SCM_PlusTestSnippets.groupproj | 48 ++ SQL/AutoBuildRelays_ListNominees.sql | 83 +++ SQL/dump.sql | 86 +++ dmSCM.pas | 4 +- frmMain.pas | 18 +- 11 files changed, 1216 insertions(+), 522 deletions(-) create mode 100644 SCM_PlusTestSnippet2.groupproj create mode 100644 SCM_PlusTestSnippets.groupproj create mode 100644 SQL/AutoBuildRelays_ListNominees.sql create mode 100644 SQL/dump.sql diff --git a/AUTOBUILD/dlgABRelay.dfm b/AUTOBUILD/dlgABRelay.dfm index a8f168f..0ad9cf5 100644 --- a/AUTOBUILD/dlgABRelay.dfm +++ b/AUTOBUILD/dlgABRelay.dfm @@ -3,7 +3,7 @@ object ABRelay: TABRelay Top = 0 BorderStyle = bsDialog Caption = 'AutoBuild Team Event ...' - ClientHeight = 557 + ClientHeight = 616 ClientWidth = 507 Color = clBtnFace Font.Charset = DEFAULT_CHARSET @@ -18,7 +18,7 @@ object ABRelay: TABRelay TextHeight = 21 object Panel2: TPanel Left = 0 - Top = 511 + Top = 570 Width = 507 Height = 46 Align = alBottom @@ -166,7 +166,7 @@ object ABRelay: TABRelay Left = 0 Top = 61 Width = 507 - Height = 450 + Height = 509 Align = alClient BevelOuter = bvNone TabOrder = 2 @@ -191,8 +191,8 @@ object ABRelay: TABRelay Caption = 'percent. (With consideration to age and gender.)' end object lblSeedDepth: TLabel - Left = 362 - Top = 387 + Left = 344 + Top = 334 Width = 81 Height = 21 Caption = 'Seed depth:' @@ -200,48 +200,50 @@ object ABRelay: TABRelay end object lblSwimmersPerTeam: TLabel Left = 80 - Top = 239 + Top = 334 Width = 143 Height = 21 Caption = 'Swimmers per team.' Enabled = False - Visible = False end object vimgHint1: TVirtualImage Left = 110 - Top = 284 + Top = 406 Width = 24 - Height = 24 + Height = 25 ImageCollection = imgcolABRelay ImageWidth = 0 ImageHeight = 0 ImageIndex = 0 ImageName = 'Info' OnClick = vimgHint1Click + OnMouseLeave = vimgHintMouseLeave end object vimgHint2: TVirtualImage - Left = 159 - Top = 404 + Left = 390 + Top = 436 Width = 24 - Height = 24 + Height = 25 ImageCollection = imgcolABRelay ImageWidth = 0 ImageHeight = 0 ImageIndex = 0 ImageName = 'Info' OnClick = vimgHint2Click + OnMouseLeave = vimgHintMouseLeave end object vimgHint3: TVirtualImage - Left = 183 - Top = 374 + Left = 414 + Top = 406 Width = 24 - Height = 24 + Height = 25 ImageCollection = imgcolABRelay ImageWidth = 0 ImageHeight = 0 ImageIndex = 0 ImageName = 'Info' OnClick = vimgHint3Click + OnMouseLeave = vimgHintMouseLeave end object prefHeatAlgorithm: TRadioGroup Left = 22 @@ -279,26 +281,26 @@ object ABRelay: TABRelay end object prefExcludeOutsideLanes: TCheckBox Left = 22 - Top = 314 + Top = 436 Width = 187 - Height = 24 + Height = 25 Caption = 'Exclude outside lanes.' TabOrder = 4 end object prefSeperateGender: TCheckBox Left = 22 - Top = 344 + Top = 466 Width = 155 - Height = 24 + Height = 25 Caption = 'Seperate gender.' Enabled = False TabOrder = 3 end object rgpSeedMethod: TRadioGroup Left = 253 - Top = 284 - Width = 244 - Height = 94 + Top = 230 + Width = 225 + Height = 95 Hint = 'Decides what lane an entrant is given.' Caption = 'Seed Method.' Enabled = False @@ -309,8 +311,8 @@ object ABRelay: TABRelay TabOrder = 5 end object spnSeedDepth: TSpinEdit - Left = 449 - Top = 384 + Left = 431 + Top = 331 Width = 48 Height = 31 Enabled = False @@ -320,17 +322,17 @@ object ABRelay: TABRelay Value = 3 end object prefDoHouseRelays: TCheckBox - Left = 22 - Top = 374 + Left = 253 + Top = 406 Width = 155 - Height = 24 + Height = 25 Caption = 'Arrange by house.' Enabled = False TabOrder = 7 end object prefNumOfSwimmersPerTeam: TSpinEdit Left = 22 - Top = 236 + Top = 331 Width = 52 Height = 31 Enabled = False @@ -338,29 +340,42 @@ object ABRelay: TABRelay MinValue = 2 TabOrder = 8 Value = 4 - Visible = False end object prefVerbose: TCheckBox Left = 22 - Top = 284 + Top = 406 Width = 82 - Height = 24 + Height = 25 Caption = 'Verbose.' TabOrder = 9 end object prefTrimPartialTeams: TCheckBox - Left = 22 - Top = 404 + Left = 253 + Top = 436 Width = 131 - Height = 24 + Height = 25 Caption = 'Remove partial.' Checked = True Enabled = False State = cbChecked TabOrder = 10 end + object rgrpAlgorithm: TRadioGroup + Left = 22 + Top = 230 + Width = 225 + Height = 95 + Caption = 'Pack Method.' + Enabled = False + ItemIndex = 0 + Items.Strings = ( + 'SCM bin pack routine.' + 'Generic Algorithm.') + TabOrder = 11 + end end object bhintABRelay: TBalloonHint + Delay = 200 Left = 424 Top = 120 end diff --git a/AUTOBUILD/dlgABRelay.pas b/AUTOBUILD/dlgABRelay.pas index 382cba6..4e41762 100644 --- a/AUTOBUILD/dlgABRelay.pas +++ b/AUTOBUILD/dlgABRelay.pas @@ -38,10 +38,12 @@ TABRelay = class(TForm) vimgHint1: TVirtualImage; vimgHint2: TVirtualImage; vimgHint3: TVirtualImage; + rgrpAlgorithm: TRadioGroup; procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); procedure vimgHint1Click(Sender: TObject); + procedure vimgHintMouseLeave(Sender: TObject); procedure vimgHint2Click(Sender: TObject); procedure vimgHint3Click(Sender: TObject); private @@ -92,6 +94,12 @@ procedure TABRelay.ReadPreferences(IniFileName: string); iFile: TIniFile; begin iFile := TIniFile.Create(IniFileName); + + // 2024-08-26 AUTO-BUILD RELAYS. + // Pack Method. + rgrpAlgorithm.ItemIndex := + (iFile.ReadInteger('Preferences', 'PackAlgorithm', 0)); + // TTB defaults to (1) .. the entrant's average of top 3 race-times prefHeatAlgorithm.ItemIndex := (iFile.ReadInteger('Preferences', 'HeatAlgorithm', 1)); @@ -125,11 +133,16 @@ procedure TABRelay.vimgHint1Click(Sender: TObject); bhintABRelay.Title := 'Verbose.'; bhintABRelay.Description := ''' If a problem is encounted when trying to Auto-Build, - display an error message, prior to aborting. + then an error message will be displayed, prior to aborting. '''; bhintABRelay.ShowHint(vimgHint1); end; +procedure TABRelay.vimgHintMouseLeave(Sender: TObject); +begin + bhintABRelay.HideHint; +end; + procedure TABRelay.vimgHint2Click(Sender: TObject); begin bhintABRelay.Title := 'Insuffient swimmers in team.'; @@ -156,6 +169,12 @@ procedure TABRelay.WritePreferences(IniFileName: string); iFile: TIniFile; begin iFile := TIniFile.Create(IniFileName); + + // 2024-08-26 AUTO-BUILD RELAYS. + // Pack Method. + iFile.WriteInteger('Preferences', 'PackAlgorithm', + rgrpAlgorithm.ItemIndex); + iFile.WriteInteger('Preferences', 'HeatAlgorithm', prefHeatAlgorithm.ItemIndex); iFile.WriteInteger('Preferences', 'UseDefRaceTime', diff --git a/AUTOBUILD/dmABRelayData.dfm b/AUTOBUILD/dmABRelayData.dfm index 52c6158..36f0890 100644 --- a/AUTOBUILD/dmABRelayData.dfm +++ b/AUTOBUILD/dmABRelayData.dfm @@ -79,72 +79,13 @@ object ABRelayData: TABRelayData Left = 128 Top = 40 end - object qryRelayNominee: TFDQuery + object dsRNominee: TDataSource + Left = 272 + Top = 152 + end + object qryCountRNominee: TFDQuery ActiveStoredUsage = [auDesignTime] FilterOptions = [foCaseInsensitive] - Filter = '[FName] LIKE '#39'%b%'#39 - Indexes = < - item - Active = True - Selected = True - Name = 'idxMemberFName' - Fields = 'FName' - end - item - Active = True - Name = 'idxMemberFNameDESC' - Fields = 'FName' - DescFields = 'FName' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxTTB' - Fields = 'TTB' - end - item - Active = True - Name = 'idxTTBDESC' - Fields = 'TTB' - DescFields = 'TTB' - Options = [soDescending] - end - item - Active = True - Name = 'idxPB' - Fields = 'PB' - end - item - Active = True - Name = 'idxPBDESC' - Fields = 'PB' - DescFields = 'PB' - Options = [soDescending] - end - item - Active = True - Name = 'idxAge' - Fields = 'AGE' - end - item - Active = True - Name = 'idxAgeDESC' - Fields = 'AGE' - Options = [soDescNullLast, soDescending] - end - item - Active = True - Name = 'idxGender' - Fields = 'GenderID' - end - item - Active = True - Name = 'idxGenderDESC' - Fields = 'GenderID' - Options = [soDescNullLast, soDescending] - end> - IndexName = 'idxMemberFName' - DetailFields = 'MemberID' Connection = SCM.scmConnection FormatOptions.AssignedValues = [fvFmtDisplayTime] FormatOptions.FmtDisplayTime = 'nn:ss.zzz' @@ -154,6 +95,140 @@ object ABRelayData: TABRelayData UpdateOptions.EnableUpdate = False UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' UpdateOptions.KeyFields = 'MemberID' + SQL.Strings = ( + 'USE SwimClubMeet;' + '' + '-- NOTE: event type must be 2 (TEAM)' + '-- find nominees in relay event.' + '-- exclude raced and closed heats.' + '--' + '-- count nominees NO in event.' + '' + 'DECLARE @EventID AS INT;' + 'SET @EventID = :EVENTID;' + '' + '-- Drop a temporary table called '#39'#tmpA'#39 + 'IF OBJECT_ID('#39'tempDB..#tmpA'#39', '#39'U'#39') IS NOT NULL' + ' DROP TABLE #tmpA;' + '' + 'CREATE TABLE #tmpA' + '(' + ' MemberID INT' + ')' + '' + '-- Members given a swimming lane in the given event ' + '' + ' INSERT INTO #tmpA' + ' SELECT TeamEntrant.MemberID' + ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' + ' INNER JOIN Team' + ' ON HeatIndividual.HeatID = Team.HeatID' + ' INNER JOIN TeamEntrant' + ' ON Team.TeamID = TeamEntrant.TeamID' + ' WHERE HeatIndividual.EventID = @EventID ' + ' AND HeatIndividual.HeatStatusID <> 1;' + '' + 'SELECT ISNULL(Count(NomineeID), 0) AS CountNominees' + 'FROM Nominee' + ' LEFT OUTER JOIN #tmpA' + ' ON #tmpA.MemberID = Nominee.MemberID' + ' LEFT OUTER JOIN Member' + ' ON Nominee.MemberID = Member.MemberID' + 'WHERE Nominee.EventID = @EventID' + ' AND #tmpA.MemberID IS NULL ;' + '') + Left = 400 + Top = 48 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + end + object qryTNum: TFDQuery + Connection = SCM.scmConnection + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + SQL.Strings = ( + '' + '' + 'DECLARE @TeamNumExists AS BIT;' + 'DECLARE @DistanceID AS INTEGER;' + '' + 'SET @DistanceID = :DISTANCEID;' + '' + 'IF EXISTS (SELECT * ' + ' FROM INFORMATION_SCHEMA.COLUMNS ' + ' WHERE TABLE_NAME = '#39'Distance'#39' ' + ' AND COLUMN_NAME = '#39'TeamNum'#39')' + 'BEGIN' + ' SET @TeamNumExists = 1' + 'END' + 'ELSE' + 'BEGIN' + ' SET @TeamNumExists = 0' + 'END' + '' + 'SELECT ' + ' [ABREV], ' + #9'CASE ' + #9#9'WHEN @TeamNumExists = 1 THEN '#39'[TeamNum]'#39 + #9#9'ELSE 0' + #9'END AS TNum' + 'FROM ' + ' [dbo].[Distance]' + 'WHERE ' + ' DistanceID = @DistanceID;' + '' + '' + '') + Left = 128 + Top = 232 + ParamData = < + item + Name = 'DISTANCEID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end> + end + object qryLastTeamNameID: TFDQuery + ActiveStoredUsage = [auDesignTime] + Connection = SCM.scmConnection + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] + UpdateOptions.EnableDelete = False + UpdateOptions.EnableInsert = False + UpdateOptions.EnableUpdate = False + SQL.Strings = ( + 'DECLARE @EventID INTEGER;' + 'SET @EventID = :EVENTID;' + '' + 'SELECT ISNULL(MAX(TeamNameID), 0) AS LastTeamNameID FROM Team ' + 'INNER JOIN HeatIndividual on Team.HeatID = HeatIndividual.HeatID' + 'WHERE EventID = @EventID;') + Left = 128 + Top = 304 + ParamData = < + item + Name = 'EVENTID' + DataType = ftInteger + ParamType = ptInput + Value = 65 + end> + end + object qryRNominee: TFDQuery + IndexFieldNames = 'NomineeID' + DetailFields = 'NomineeID' + Connection = SCM.scmConnection + FormatOptions.AssignedValues = [fvFmtDisplayTime] + FormatOptions.FmtDisplayTime = 'nn:ss.zzz' + UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] + UpdateOptions.UpdateTableName = 'SwimClubMeet.dbo.Nominee' + UpdateOptions.KeyFields = 'NomineeID' SQL.Strings = ( '--USE SwimClubMeet' '--GO' @@ -214,7 +289,7 @@ object ABRelayData: TABRelayData ' INNER JOIN TeamEntrant' ' ON Team.TeamID = TeamEntrant.TeamID' ' WHERE HeatIndividual.EventID = @EventID' - ' AND HeatIndividual.HeatStatusID = 1;' + ' AND HeatIndividual.HeatStatusID <> 1;' ' ' ' ' '-- Construct dynamic SQL' @@ -225,20 +300,20 @@ object ABRelayData: TABRelayData ' Nominee.MemberID,' ' Member.GenderID,' ' dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE,' - ' dbo.SwimmerGenderToString(Member.MemberID) AS Gender,' + ' dbo.SwimmerGenderToString(Nominee.MemberID) AS Gender,' - ' dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, Mem' + - 'ber.MemberID, @DistanceID, @StrokeID, @SessionStart) AS TTB,' + ' dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, Nom' + + 'inee.MemberID, @DistanceID, @StrokeID, @SessionStart) AS xTTB,' - ' dbo.PersonalBest(Member.MemberID, @DistanceID, @StrokeID, @S' + - 'essionStart) AS PB,' + ' dbo.PersonalBest(Nominee.MemberID, @DistanceID, @StrokeID, @' + + 'SessionStart) AS xPB,' ' CASE ' ' WHEN @ToggleName = 0 THEN SUBSTRING(CONCAT(UPPER([LastNa' + - 'me]), '#39#39', '#39#39', [FirstName]), 0, 30)' + 'me]), '#39' '#39', [FirstName]), 0, 30)' ' WHEN @ToggleName = 1 THEN SUBSTRING(CONCAT([FirstName], ' + - #39#39', '#39#39', UPPER([LastName])), 0, 48)' + #39' '#39', UPPER([LastName])), 0, 48)' ' END AS FName' 'FROM Nominee' 'LEFT OUTER JOIN #tmpID ON #tmpID.MemberID = Nominee.MemberID' @@ -250,14 +325,14 @@ object ABRelayData: TABRelayData ' ' ' ' '') - Left = 128 + Left = 136 Top = 152 ParamData = < item Name = 'EVENTID' DataType = ftInteger ParamType = ptInput - Value = 65 + Value = 1506 end item Name = 'ALGORITHM' @@ -279,9 +354,9 @@ object ABRelayData: TABRelayData end item Name = 'BOTTOMPERCENT' - DataType = ftFloat + DataType = ftInteger ParamType = ptInput - Value = 50.000000000000000000 + Value = 50 end item Name = 'XDISTANCEID' @@ -296,146 +371,87 @@ object ABRelayData: TABRelayData Value = 1000 end> end - object dsRelayNominee: TDataSource - DataSet = qryRelayNominee - Left = 272 - Top = 152 - end - object qryCountRNominee: TFDQuery - ActiveStoredUsage = [auDesignTime] - FilterOptions = [foCaseInsensitive] + object cmdInsertTeamEntrant: TFDCommand Connection = SCM.scmConnection - FormatOptions.AssignedValues = [fvFmtDisplayTime] - FormatOptions.FmtDisplayTime = 'nn:ss.zzz' - UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate, uvCheckReadOnly] - UpdateOptions.EnableDelete = False - UpdateOptions.EnableInsert = False - UpdateOptions.EnableUpdate = False - UpdateOptions.UpdateTableName = 'SwimClubMeet..Nominee' - UpdateOptions.KeyFields = 'MemberID' - SQL.Strings = ( - 'USE SwimClubMeet;' - '' - '-- NOTE: event type must be 2 (TEAM)' - '-- find nominees in relay event.' - '-- exclude raced and closed heats.' - '--' - '-- count nominees NO in event.' - '' - 'DECLARE @EventID AS INT;' - 'SET @EventID = :EVENTID;' - '' - '-- Drop a temporary table called '#39'#tmpA'#39 - 'IF OBJECT_ID('#39'tempDB..#tmpA'#39', '#39'U'#39') IS NOT NULL' - ' DROP TABLE #tmpA;' + CommandText.Strings = ( + 'DECLARE @MemberID AS INT;' + 'DECLARE @Lane AS INT;' + 'DECLARE @TeamID AS INT;' + 'DECLARE @PB AS TTIME;' + 'DECLARE @TTB AS TTIME;' + 'DECLARE @StrokeID AS INT;' + 'DECLARE @IsDisqualified AS BIT;' + 'DECLARE @IsScratched AS BIT;' '' - 'CREATE TABLE #tmpA' - '(' - ' MemberID INT' - ')' + 'SET @MemberID = :MEMBERID;' + 'SET @Lane = :LANE;' + 'SET @TeamID = :TEAMID;' + 'SET @PB = :PB;' + 'SET @TTB = :TTB;' + 'SET @StrokeID = :STROKEID;' + 'SET @IsDisqualified = :ISDISQUALIFIED;' + 'SET @IsScratched = :ISSCRATCHED;' '' - '-- Members given a swimming lane in the given event ' '' - ' INSERT INTO #tmpA' - ' SELECT TeamEntrant.MemberID' - ' FROM [SwimClubMeet].[dbo].[HeatIndividual]' - ' INNER JOIN Team' - ' ON HeatIndividual.HeatID = Team.HeatID' - ' INNER JOIN TeamEntrant' - ' ON Team.TeamID = TeamEntrant.TeamID' - ' WHERE HeatIndividual.EventID = @EventID ' - ' AND HeatIndividual.HeatStatusID = 1;' '' - 'SELECT ISNULL(Count(NomineeID), 0) AS CountNominees' - 'FROM Nominee' - ' LEFT OUTER JOIN #tmpA' - ' ON #tmpA.MemberID = Nominee.MemberID' - ' LEFT OUTER JOIN Member' - ' ON Nominee.MemberID = Member.MemberID' - 'WHERE Nominee.EventID = @EventID' - ' AND #tmpA.MemberID IS NULL ;' - '') - Left = 400 - Top = 48 + 'INSERT INTO dbo.TeamEntrant' + + '(MemberID, Lane, TeamID, PersonalBest, TimeToBeat, StrokeID, IsD' + + 'isqualified, IsScratched)' + 'VALUES' + + '(@MEMBERID, @LANE, @TEAMID, @PB, @TTB, @STROKEID, @ISDISQUALIFIE' + + 'D, @ISSCRATCHED);') ParamData = < item - Name = 'EVENTID' + Name = 'MEMBERID' DataType = ftInteger ParamType = ptInput Value = Null - end> - end - object qryTNum: TFDQuery - Connection = SCM.scmConnection - UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] - UpdateOptions.EnableDelete = False - UpdateOptions.EnableInsert = False - UpdateOptions.EnableUpdate = False - SQL.Strings = ( - '' - '' - 'DECLARE @TeamNumExists AS BIT;' - 'DECLARE @DistanceID AS INTEGER;' - '' - 'SET @DistanceID = :DISTANCEID;' - '' - 'IF EXISTS (SELECT * ' - ' FROM INFORMATION_SCHEMA.COLUMNS ' - ' WHERE TABLE_NAME = '#39'Distance'#39' ' - ' AND COLUMN_NAME = '#39'TeamNum'#39')' - 'BEGIN' - ' SET @TeamNumExists = 1' - 'END' - 'ELSE' - 'BEGIN' - ' SET @TeamNumExists = 0' - 'END' - '' - 'SELECT ' - ' [ABREV], ' - #9'CASE ' - #9#9'WHEN @TeamNumExists = 1 THEN '#39'[TeamNum]'#39 - #9#9'ELSE 0' - #9'END AS TNum' - 'FROM ' - ' [dbo].[Distance]' - 'WHERE ' - ' DistanceID = @DistanceID;' - '' - '' - '') - Left = 128 - Top = 232 - ParamData = < + end item - Name = 'DISTANCEID' + Name = 'LANE' DataType = ftInteger ParamType = ptInput Value = Null - end> - end - object qryLastTeamNameID: TFDQuery - ActiveStoredUsage = [auDesignTime] - Connection = SCM.scmConnection - UpdateOptions.AssignedValues = [uvEDelete, uvEInsert, uvEUpdate] - UpdateOptions.EnableDelete = False - UpdateOptions.EnableInsert = False - UpdateOptions.EnableUpdate = False - SQL.Strings = ( - 'DECLARE @EventID INTEGER;' - 'SET @EventID = :EVENTID;' - '' - 'SELECT ISNULL(MAX(TeamNameID), 0) AS LastTeamNameID FROM Team ' - 'INNER JOIN HeatIndividual on Team.HeatID = HeatIndividual.HeatID' - 'WHERE EventID = @EventID;') - Left = 128 - Top = 304 - ParamData = < + end item - Name = 'EVENTID' + Name = 'TEAMID' DataType = ftInteger ParamType = ptInput - Value = 65 + Value = Null + end + item + Name = 'PB' + DataType = ftTime + ParamType = ptInput + Value = Null + end + item + Name = 'TTB' + DataType = ftTime + ParamType = ptInput + Value = Null + end + item + Name = 'STROKEID' + DataType = ftInteger + ParamType = ptInput + Value = Null + end + item + Name = 'ISDISQUALIFIED' + DataType = ftBoolean + ParamType = ptInput + Value = Null + end + item + Name = 'ISSCRATCHED' + DataType = ftBoolean + ParamType = ptInput + Value = Null end> + Left = 408 + Top = 224 end end diff --git a/AUTOBUILD/dmABRelayData.pas b/AUTOBUILD/dmABRelayData.pas index 0e623a4..16db1c6 100644 --- a/AUTOBUILD/dmABRelayData.pas +++ b/AUTOBUILD/dmABRelayData.pas @@ -12,11 +12,12 @@ interface type TABRelayData = class(TDataModule) FDCommandUpdateEntrant: TFDCommand; - qryRelayNominee: TFDQuery; - dsRelayNominee: TDataSource; + dsRNominee: TDataSource; qryCountRNominee: TFDQuery; qryTNum: TFDQuery; qryLastTeamNameID: TFDQuery; + qryRNominee: TFDQuery; + cmdInsertTeamEntrant: TFDCommand; private fAutoBuildRelayDataActive: Boolean; FConnection: TFDConnection; @@ -42,7 +43,7 @@ procedure TABRelayData.ActivateTable; fAutoBuildRelayDataActive := false; if Assigned(FConnection) and FConnection.Connected then begin - qryRelayNominee.Connection := FConnection; + qryRNominee.Connection := FConnection; qryCountRNominee.Connection := FConnection; fAutoBuildRelayDataActive := true; end; diff --git a/AUTOBUILD/uABRelayExec.pas b/AUTOBUILD/uABRelayExec.pas index 8da923a..9485dbc 100644 --- a/AUTOBUILD/uABRelayExec.pas +++ b/AUTOBUILD/uABRelayExec.pas @@ -12,33 +12,34 @@ interface type + // T E A M . TTeam = record - // L A N E >>> R E L A Y - T E A M . - // (Typically a relay-team holds four swimmers) - ID: Integer; // IDENITY of the Team in dbo.Team. + TeamID: Integer; // IDENITY of dbo.TeamID. + TeamNameID: integer; // IDENITY of dbo.TeamNameID. Lane: Integer; // Lane number. - RaceTime: TTime; // The SUM of all PBs for swimmers in the relay-team. - Entrants: array of integer; // Holds memberID - 4 x swimmers per lane. + idxSwimmer: array of integer; // Holds index of TSwimmer. HeatID: Integer; + SumNormTTB: TTime; // The sum of NormTTB of each swimmer. + Deviation: double; // Deviation from the average relay swimming time end; - - TChromosome = array of TTeam; - + // H E A T . THeat = record - // H E A T . ID: integer; HeatNum: integer; EventID: integer; end; - + // S W I M M E R . TSwimmer = record - // S W I M M E R . NomineeID: Integer; // Nomination ID for the swimmer in dbo.Nominee. MemberID: Integer; // Member ID for the swimmer used in dbo.Member table TTB: TTime; // Time-to-beat. Estimated (calculated) swimming for XDistance. PB: TTime; // Personal Best swimming time for the swimmer for the event. + NormTTB: double; // normalized TTB. end; + TTeamArray = array of TTeam; // genetic algorithm + + TABRelayExec = class(TComponent) private FConnection: TFDConnection; @@ -47,7 +48,8 @@ TABRelayExec = class(TComponent) { private declarations } fExcludeOutsideLanes: boolean; fHeatAlgorithm: integer; - fIndvDistanceMetres: integer; + fIndvDistanceMetres: integer; // distance swum by each swimmer in relay. + fxDistanceID: integer; // lookup value gained from fIndvDistanceMetres. fNumOfHeats: integer; fNumOfNominees: integer; fNumOfPoolLanes: integer; // takes into consideration preferences for gutter lanes. @@ -64,35 +66,44 @@ TABRelayExec = class(TComponent) fTeamDistanceMetres: integer; fUseDefRaceTime: boolean; fVerbose: boolean; - Generations: Integer; // = 1000; - // An array of heats. - Heats: array of THeat; - MutationRate: Double; // = 0.01; - // An array of swimmers, used by 'bin packing routine' using a genetic algorithm. - Population: array of TChromosome; - PopulationSize: Integer; // ... fNumOfSwimmers. + fAcceptedMarginOfDeviation: double; // packing bin algorithm + fIterations: Integer; // genetic algorithm default = 1000. + fPackAlgorithm: integer; + + { C A L C R A C E - T I M E . } + fAvgINDVNormTTB: double; // Average optimum INDV TTB - normalized. + fAvgTEAMNormTTB: double; // Average optimum TEAM TTB - normalized. + // D Y N A M I C A R R A Y S . + Heats: array of THeat; // An array of heats. // Nominee Database Pool : swimmers who nominated for the event. Swimmers: array of TSwimmer; // An array of teams - each team will contain MemberID x fNumOfSwimmersPerTeam. Teams: array of TTeam; - // B i n p a c k i n g r o u t i n e s . - function Crossover(Parent1, Parent2: TChromosome): TChromosome; - function Fitness(Chromosome: TChromosome): Double; - function GeneticAlgorithm: Boolean; + function GetDistanceID(EventID: integer): integer; + function GetxDistanceID(AMeters: integer): integer; function GetNumberOfNominees(AEventID: Integer): integer; function GetNumberOfSwimmersPerTeam(ADistanceID: integer): integer; function GetPoolLanes: integer; function GetStrokeID(AEventID: integer): integer; function GetTeamDistanceMetres(ADistanceID: integer): integer; - function InitializePopulation: TChromosome; function InsertIntoTeams(): boolean; - procedure Mutate(var Chromosome: TChromosome); procedure ReadPreferences(IniFileName: string); function RetriveSwimmingData(): boolean; - procedure Swap(var A, B: Integer); - function TournamentSelection: TChromosome; + + + // B i n p a c k i n g r o u t i n e s . + { R O U T I N E S . } + procedure GetAvgDeviation(var LowAvg: double; var HighAvg: double); + function CalculateAcceptableMargin(): double; + procedure NormalizeINDV_TTB; + procedure FirstFit; + procedure SecondFit; + procedure ThirdFit; + procedure GeneticAlgorithm; + + public constructor Create(AOwner: TComponent); override; destructor Destroy; override; @@ -110,64 +121,33 @@ implementation uses System.Math, System.IniFiles, SCMUtility, dmABRelayData, system.Generics.Collections, System.StrUtils; -{ -procedure TAutoBuildRelayExec.Distribute(FDQuery: TFDQuery; AEventID: integer); +function TABRelayExec.CalculateAcceptableMargin: double; var - i, j, k, m, n, ALaneNum: Integer; - TeamRaceTime: TTIME; + LowAvg, HighAvg: double; begin - // Distribute swimmers into teams - i := 0; // Nominees - Swimmers - m := 0; // 0 .. NumOfTeams-1 - for j := 0 to NumOfHeats - 1 do - begin - // HEAT. - Heats[j].ID := j; - Heats[j].HeatNum := j+1; - Heats[j].EventID := AEventID; - for k := 0 to NumOfTeamsPerHeat - 1 do - begin - // TEAM. - Teams[m].ID := m; // Identifier 0...NumOfTeams-1 - inc(m, 1); - ALaneNum := ScatterLanes(k, NumOfPoolLanes); // index is base 0 - Teams[m].Lane := ALaneNum; - Teams[m].RaceTime := 0; - - // set the number of swimmers permitted in a relay team - SetLength(Teams[k].Entrants, NumOfEntrantsPerTeam); - TeamRaceTime := 0; - // fill relay team with entrants. - for n := 0 to NumOfEntrantsPerTeam - 1 do - begin - if (i <= high(Swimmers)) then - begin - // TEAMENTRANT. - Teams[k].Entrants[n] := i; - TeamRaceTime := TeamRaceTime + Swimmers[i].TTB; - end; - inc(i, 1); - end; - // total est.racetime for this relay-team - Teams[k].RaceTime := TeamRaceTime; - end; - // TEST : exceed number of teams in total - if m >= NumOfTeams then break; - end; -end; -} + // Calculate average deviations + GetAvgDeviation(LowAvg, HighAvg); + // Check for division by zero + if (LowAvg = 0) and (HighAvg = 0) then + Result := 0 + else + Result := 0.1 * (LowAvg + HighAvg) / 2; // 10% of the average deviation +end; constructor TABRelayExec.Create(AOwner: TComponent); begin inherited; fEventID := 0; - fDistanceID := 1; + fDistanceID := 1; // 25m + fStrokeID := 1; // feestyle + fxDistanceID := 0; FConnection := nil; fTeamDistanceMetres:=0; fIndvDistanceMetres:=0; - fStrokeID := 1; fSuccess := false; + fAcceptedMarginOfDeviation := 0; + fIterations := 1000; // I N I T I A L I Z E P R E F E R E N C E S . fExcludeOutsideLanes := false; @@ -180,10 +160,8 @@ constructor TABRelayExec.Create(AOwner: TComponent); fSeperateGender := false; fSeedMethod := 0; fSeedDepth := 3; - - PopulationSize := 100; - Generations := 1000; - MutationRate := 0.01; + fNumOfPoolLanes := 8; + fPackAlgorithm := 0; end; @@ -194,20 +172,6 @@ destructor TABRelayExec.Destroy; inherited; end; -function TABRelayExec.Crossover(Parent1, Parent2: TChromosome): TChromosome; -var - i, CrossoverPoint: Integer; - Child: TChromosome; -begin - SetLength(Child, Length(Parent1)); - CrossoverPoint := Random(Length(Parent1)); - for i := 0 to CrossoverPoint do - Child[i] := Parent1[i]; - for i := CrossoverPoint + 1 to High(Parent2) do - Child[i] := Parent2[i]; - Result := Child; -end; - procedure TABRelayExec.ExecAutoBuildRelay(); var IniFileName: TFileName; @@ -229,7 +193,7 @@ procedure TABRelayExec.ExecAutoBuildRelay(); WHERE EventID = :ID '''; - v := SCM.scmConnection.ExecSQLScalar(s, [fEventID]); + v := fConnection.ExecSQLScalar(s, [fEventID]); if VarIsNull(v) then // Is this a team event? begin @@ -310,6 +274,9 @@ procedure TABRelayExec.ExecAutoBuildRelay(); // Individual Distance = the distance swum by each swimmer in the team. // WIT: 4x25m relay Total 100m ... entrants swim 25m. fIndvDistanceMetres := (fTeamDistanceMetres div fNumOfSwimmersPerTeam); + fxDistanceID := GetxDistanceID(fIndvDistanceMetres); + + // number of members who indicated that they wanted to swim the relay event. // Does not include members in raced or closed heats for this relay event. fNumOfNominees := GetNumberOfNominees(fEventID); @@ -372,92 +339,278 @@ procedure TABRelayExec.ExecAutoBuildRelay(); SetLength(Teams, fNumOfTeams); // R e t r i v e S w i m m e r s . Check - fSuccess. + // populate array Swimmers. + // ORDER BY fastest first. fSuccess := RetriveSwimmingData(); - - if fSuccess then + if not fSuccess then begin - // Process the data using Genetic Algorithm. - PopulationSize := fNumOfSwimmers; // perfect number of swimmers. - fSuccess := GeneticAlgorithm(); - if fSuccess then + if fVerbose then begin - fSuccess := InsertIntoTeams(); - // D i s t r i b u t e 2 . (Gender, house, etc) - // if fSuccess then DistributeTeams(FDQuery); + s := ''' + While reteving data from the SCM database an unknown error occurred. + Auto-build ENDED. + '''; + Application.MessageBox(Pchar(s), 'SwimClubMeet Error', MB_ICONERROR or MB_OK); end; + exit; end; + // Normalize the TTB for all swimmers. + // this routine also calculates the average TTB (normalized) for a swimmer. + NormalizeINDV_TTB; + + // Optimum swimming time for all teams. + fAvgTEAMNormTTB := fAvgINDVNormTTB * fNumOfSwimmersPerTeam; + if fPackAlgorithm = 0 then + begin + FirstFit; // First fit. Scatter swimmers across teams. +// SecondFit; // Find most (+/-) deviation and swap swimmers. +// ThirdFit; // Refine deviation until within margin or looped fIterations. + end + else + begin + FirstFit; // First fit. Scatter swimmers across teams. + GeneticAlgorithm; // A Generic Algorithm - slower, perhaps better? + end; + + + // D i s t r i b u t e 2 . (Gender, house, etc) + // if fSuccess then DistributeTeams(FDQuery); + fSuccess := InsertIntoTeams(); + // Refresh UI and data // if Owner is TForm then // SendMessage(TForm(Owner).Handle, SCM_AUTOBUILDRELAYSFIN, 0, 0); end; -function TABRelayExec.Fitness(Chromosome: TChromosome): Double; +procedure TABRelayExec.FirstFit; var - i, j: Integer; - TotalTime, MaxTime, MinTime: TTime; + I, j, k, swimmer: Integer; + d: TTime; + fwd: Boolean; begin - MaxTime := 0; - MinTime := MaxInt; - TotalTime := 0; - for i := 0 to High(Chromosome) do + { first fit of swimmers into teams.} + I := 0; + + { number of swimmers per team - initialize ARRAY: idxSwimmer in TTeams.} + for j := Low(Teams) to High(Teams) do + SetLength(Teams[j].idxSwimmer, fNumOfSwimmersPerTeam); + + { Scatter swimmers } + fwd := True; + for k := 0 to (fNumOfSwimmersPerTeam - 1) do begin - Chromosome[i].RaceTime := 0; - for j := 0 to High(Chromosome[i].Entrants) do + if fwd then begin - Chromosome[i].RaceTime := Chromosome[i].RaceTime + Swimmers[Chromosome[i].Entrants[j]].TTB; + for j := Low(Teams) to High(Teams) do + begin + if I > High(Swimmers) then break; + Teams[j].idxSwimmer[k] := I; + inc(I); + end; + end + else + begin + for j := High(Teams) downto Low(Teams) do + begin + if I > High(Swimmers) then break; + Teams[j].idxSwimmer[k] := I; + inc(I); + end; + end; + fwd := not fwd; { Toggle the direction } + if I > High(Swimmers) then break; + end; + + { calculate SumNormTTB and deviation } + for j := Low(Teams) to High(Teams) do + begin + d := 0; + for k := Low(Teams[j].idxSwimmer) to High(Teams[j].idxSwimmer) do + begin + swimmer := Teams[j].idxSwimmer[k]; + d := d + Swimmers[Swimmer].NormTTB; end; - if Chromosome[i].RaceTime > MaxTime then - MaxTime := Chromosome[i].RaceTime; - if Chromosome[i].RaceTime < MinTime then - MinTime := Chromosome[i].RaceTime; - TotalTime := TotalTime + Chromosome[i].RaceTime; + Teams[j].SumNormTTB := d; + Teams[j].Deviation := fAvgTEAMNormTTB - d; end; - Result := MaxTime - MinTime; end; -function TABRelayExec.GeneticAlgorithm: boolean; +procedure TABRelayExec.GeneticAlgorithm; +const + PopulationSize = 16; + MaxGenerations = 100; + MutationRate = 0.01; var - i, j: Integer; - Parent1, Parent2, Child: TChromosome; - BestChromosome: TChromosome; - BestFitness, CurrentFitness: Double; -begin + Population: array of TTeamArray; + NewPopulation: array of TTeamArray; + Generation, i, j, k: Integer; + BestFitness: Double; + + procedure CopyPopulation(var Dest: array of TTeamArray; const Src: array of TTeamArray); + var + i, j: Integer; + begin + SetLength(Population, Length(Src)); + for i := Low(Src) to High(Src) do + begin + SetLength(Dest[i], Length(Src[i])); + for j := Low(Src[i]) to High(Src[i]) do + begin + Dest[i][j] := Src[i][j]; + end; + end; + end; - SetLength(Population, PopulationSize); - for i := 0 to PopulationSize - 1 do - Population[i] := InitializePopulation; + function Fitness(Teams: TTeamArray): Double; + var + i: Integer; + TotalDeviation: Double; + begin + TotalDeviation := 0; + for i := Low(Teams) to High(Teams) do + TotalDeviation := TotalDeviation + Abs(Teams[i].Deviation); + Result := 1 / (1 + TotalDeviation); // Lower deviation means higher fitness + end; - for i := 0 to Generations - 1 do + procedure ShuffleTeams(var Teams: TTeamArray); + var + i, j, k, temp: Integer; begin - for j := 0 to PopulationSize - 1 do + for i := Low(Teams) to High(Teams) do begin - Parent1 := TournamentSelection; - Parent2 := TournamentSelection; - Child := Crossover(Parent1, Parent2); - Mutate(Child); - Population[j] := Child; + for j := Low(Teams[i].idxSwimmer) to High(Teams[i].idxSwimmer) do + begin + k := Random(High(Teams[i].idxSwimmer) - Low(Teams[i].idxSwimmer) + 1) + Low(Teams[i].idxSwimmer); + temp := Teams[i].idxSwimmer[j]; + Teams[i].idxSwimmer[j] := Teams[i].idxSwimmer[k]; + Teams[i].idxSwimmer[k] := temp; + end; end; end; - BestChromosome := Population[0]; - BestFitness := Fitness(BestChromosome); - for i := 1 to PopulationSize - 1 do + procedure InitializePopulation; + var + i, j: Integer; begin - CurrentFitness := Fitness(Population[i]); - if CurrentFitness < BestFitness then + SetLength(Population, PopulationSize); + for i := 0 to PopulationSize - 1 do begin - BestFitness := CurrentFitness; - BestChromosome := Population[i]; + SetLength(Population[i], Length(Teams)); + for j := Low(Teams) to High(Teams) do + Population[i][j] := Teams[j]; // Copy initial teams + ShuffleTeams(Population[i]); // Randomize team compositions end; end; - // Use BestChromosome to set your final teams - SetLength(Teams, Length(BestChromosome)); - for i := 0 to High(BestChromosome) do - Teams[i] := BestChromosome[i]; + procedure Crossover(Parent1, Parent2: TTeamArray; var Child1, Child2: TTeamArray); + var + i, crossoverPoint: Integer; + begin + crossoverPoint := Random(Length(Parent1)); + for i := 0 to crossoverPoint do + begin + Child1[i] := Parent1[i]; + Child2[i] := Parent2[i]; + end; + for i := crossoverPoint + 1 to High(Parent1) do + begin + Child1[i] := Parent2[i]; + Child2[i] := Parent1[i]; + end; + end; - result := true; + procedure Mutate(var Teams: TTeamArray); + var + i, j, k, temp: Integer; + begin + for i := Low(Teams) to High(Teams) do + begin + if Random < MutationRate then + begin + j := Random(High(Teams[i].idxSwimmer) - Low(Teams[i].idxSwimmer) + 1) + Low(Teams[i].idxSwimmer); + k := Random(High(Teams[i].idxSwimmer) - Low(Teams[i].idxSwimmer) + 1) + Low(Teams[i].idxSwimmer); + temp := Teams[i].idxSwimmer[j]; + Teams[i].idxSwimmer[j] := Teams[i].idxSwimmer[k]; + Teams[i].idxSwimmer[k] := temp; + end; + end; + end; + + procedure EvaluatePopulation; + var + i, j: Integer; + dfitness: Double; + begin + BestFitness := 0; + for i := 0 to PopulationSize - 1 do + begin + dfitness := Fitness(Population[i]); + if dfitness > BestFitness then + begin + BestFitness := dfitness; + // Explicitly copy each team from the best individual in the population + for j := Low(Population[i]) to High(Population[i]) do + begin + Teams[j] := Population[i][j]; + end; + end; + end; + end; + + + +begin + Randomize; + InitializePopulation; + for Generation := 1 to MaxGenerations do + begin + SetLength(NewPopulation, PopulationSize); + for i := 0 to PopulationSize div 2 - 1 do + begin + // Selection (simple random selection for demonstration) + j := Random(PopulationSize); + k := Random(PopulationSize); + Crossover(Population[j], Population[k], NewPopulation[2 * i], NewPopulation[2 * i + 1]); + Mutate(NewPopulation[2 * i]); + Mutate(NewPopulation[2 * i + 1]); + end; + CopyPopulation(Population, NewPopulation); + EvaluatePopulation; + if BestFitness >= 1 / (1 + fAcceptedMarginOfDeviation) then + Break; // Stop if we reach an acceptable fitness level + end; +end; + +procedure TABRelayExec.GetAvgDeviation(var LowAvg, HighAvg: double); +var +LowDeviation, HighDeviation: double; +LowCount, HighCount, i: integer; +begin + LowDeviation := 0; + HighDeviation := 0; + LowCount := 0; + HighCount := 0; + LowAvg := 0; + HighAvg := 0; + for i := Low(Teams) to High(Teams) do + begin + LowDeviation := LowDeviation + Teams[i].Deviation; + if Teams[i].Deviation <= 0 then + begin + LowDeviation := LowDeviation + Abs(Teams[i].Deviation); + Inc(LowCount); + end; + if Teams[i].Deviation > 0 then + begin + HighDeviation := HighDeviation + Teams[i].Deviation; + Inc(HighCount); + end; + end; + if (LowDeviation > 0) then + LowAvg := LowDeviation / LowCount; + if (HighDeviation > 0) then + HighAvg := HighDeviation / HighCount; end; function TABRelayExec.GetDistanceID(EventID: integer): integer; @@ -465,7 +618,7 @@ function TABRelayExec.GetDistanceID(EventID: integer): integer; v: variant; begin result := 0; - v := SCM.scmConnection.ExecSQLScalar + v := fConnection.ExecSQLScalar ('SELECT DistanceID FROM [dbo].[Event] WHERE EventID = :ID', [EventID]); if not VarIsNull(v) then result := v @@ -539,7 +692,7 @@ function TABRelayExec.GetStrokeID(AEventID: integer): integer; v: variant; begin result := 0; - v := SCM.scmConnection.ExecSQLScalar + v := fConnection.ExecSQLScalar ('SELECT StrokeID FROM [dbo].[Event] WHERE [EventID] = :ID', [AEventID]); if not VarIsNull(v) then result := v; @@ -556,148 +709,226 @@ function TABRelayExec.GetTeamDistanceMetres(ADistanceID: integer): integer; SELECT Meters FROM [dbo].[Distance] WHERE DistanceID = :ID '''; - v := SCM.scmConnection.ExecSQLScalar(s, [ADistanceID]); + v := fConnection.ExecSQLScalar(s, [ADistanceID]); if not VarIsNull(v) then result := v; end; -function TABRelayExec.InitializePopulation: TChromosome; +function TABRelayExec.GetxDistanceID(AMeters: integer): integer; var - i, j: Integer; - Chromosome: TChromosome; + v: variant; + s: string; begin - SetLength(Chromosome, fNumOfTeams); - for i := 0 to fNumOfTeams - 1 do - begin - Chromosome[i].ID := i; - Chromosome[i].Lane := i mod fNumOfPoolLanes; - SetLength(Chromosome[i].Entrants, fNumOfSwimmersPerTeam); - for j := 0 to fNumOfSwimmersPerTeam - 1 do - begin - Chromosome[i].Entrants[j] := Random(fNumOfSwimmers); - end; - end; - Result := Chromosome; + { + Get the distance ID using the metres swum value. + ABS(Meters - @SwumDistance) calculates the absolute difference + between the Meters column and the swum distance. + } + result := 0; + s := ''' + SELECT TOP 1 DistanceID + FROM SwimClubMeet.dbo.Distance + WHERE EventTypeID = 1 AND ABS(Meters - :SwumDistance) <= 10 + ORDER BY ABS(Meters - :SwumDistance); + '''; + v := fConnection.ExecSQLScalar(s, [AMeters]); + if not VarIsNull(v) then + result := v end; function TABRelayExec.InsertIntoTeams: Boolean; var - qry1, qry2, qry3: TFDQuery; + qry1, qry2: TFDQuery; AHeatID: integer; ATeamID: integer; ATeamEntrantID: integer; ATeamNameID: integer; - s: string; - i, j, m, n, p, r, lanenum: integer; + s, s2: string; + i, j, m, n, p, r, t, laneidx, lanenum, idx, lanes: integer; + v: variant; + ATeamTTB: TTime; begin result := false; qry1 := TFDQuery.Create(nil); qry2 := TFDQuery.Create(nil); - qry3 := TFDQuery.Create(nil); - qry1.Connection := SCM.scmConnection; - qry2.Connection := SCM.scmConnection; - qry3.Connection := SCM.scmConnection; + qry1.Connection := fConnection; + qry2.Connection := fConnection; for j := Low(Heats) to High(Heats) do Begin + // Insert into dbo.HeatIndividual // HeatTypeID - Heat, HeatStatus - open, HeatNum - (1 ... ) + qry1.Close; s := ''' INSERT INTO HeatIndividual (EventID, HeatNum, OpenDT, HeatTypeID, HeatStatusID) - VALUES (:EVENTID, :HEATNUM, GETDATE(), 1, 1)'; + VALUES (:EVENTID, :HEATNUM, GETDATE(), 1, 1); '''; qry1.SQL.Text := s; qry1.ParamByName('EVENTID').AsInteger := fEventID; - qry1.ParamByName('HEATNUM').AsInteger := Heats[j].HeatNum; + qry1.ParamByName('HEATNUM').AsInteger := (Heats[j].HeatNum + 1); + qry1.Prepare; qry1.ExecSQL; - // Get the newly created heat's record ID. - AHeatID := qry1.Connection.GetLastAutoGenValue('HeatID'); + // Get the newly created heat's record IDENTITY. + v := qry1.Connection.GetLastAutoGenValue(''); + if VarIsNull(v) or VarIsEmpty(v) then + raise Exception.Create('Failed to retrieve AHeatID'); + AHeatID := v; + + // ---------------------------------------- + // Build all the lanes for this new heat... + // Assigns [HeatID], [Lane], BIT values, etc . + // Not assigned is TeamNameID. (wit: indicates empty Lane). + // returns number of lanes. + // ---------------------------------------- + lanes := SCM.PadLanes(AHeatID); + + laneidx := 0; + { P L A C E R E L A Y T EA M .} + // Iterate accross all TTeams over multi-heats ... + for i := (j * fNumOfHeats) to (j * fNumOfHeats) + (fNumOfTeamsPerHeat - 1) do + begin + if (i > High(Teams)) then break; // Out of range ... - { T E A M N A M E I D . } - // dbo.TeamName is a table filled with ID's starting from 1 .. 26 - // and Caption's (1)'TeamA' ... (26)'TeamZ'. - ATeamNameID := ABRelayData.GetLastTeamNameID(fEventID); + { L A N E N U M B E R .} + lanenum := SCMUtility.ScatterLanes(laneidx, fNumOfPoolLanes); - for i := 0 to fNumOfTeamsPerHeat - 1 do - begin + { T E A M N A M E I D . } + // dbo.TeamName is a table filled with ID's starting from 1 .. 26 + // and Caption's (1)'TeamA' ... (26)'TeamZ'. + ATeamNameID := ABRelayData.GetLastTeamNameID(fEventID) + 1; + + { L O O K U P T E A M I D . } + s := ''' + SELECT TeamID FROM SwimClubMeet.dbo.Team + WHERE HeatID = :HEATID AND Lane = :LANENUM; + '''; + v := fConnection.ExecSQLScalar(s, [AHeatID, lanenum]); + if VarIsNull(v) or VarIsEmpty(v) then + raise Exception.Create('Failed to retrieve ATeamID'); + ATeamID := v; + ATeamTTB := 0; + + { A S S I G N T E A M D A T A (Part-A).} s := ''' - INSERT INTO Team (Lane, TeamNameID, HeatID) - VALUES (:LANE, :TEAMNAMEID, :HEATID); + UPDATE [dbo].[Team] + SET [TeamNameID] = :TEAMNAMEID + WHERE TeamID = :TEAMID; '''; - // Insert into Team table - qry2.SQL.Text := s; - // place team in lane - using center lanes first. - lanenum := SCMUtility.ScatterLanes(Teams[i].Lane, fNumOfPoolLanes); - qry2.ParamByName('LANE').AsInteger := lanenum; - inc(ATeamNameID, 1); - qry2.ParamByName('TEAMNAMEID').AsInteger := ATeamNameID; - qry2.ParamByName('HEATID').AsInteger := AHeatID; // Base zero. - qry2.ExecSQL; - // Get the new team record's ID - ATeamID := qry2.Connection.GetLastAutoGenValue('TeamID'); + fConnection.ExecSQL(s, [ATeamNameID, ATeamID]); + + { ASSIGN SWIMMERS TO TEAM } + // --------------------------------------------------------------------- {TODO -oBSA -cAutoBuild Relay : Sort entrants - Fastest last, 2nd fastest first, etc... } - p := 1; // Swimming order 1 to NumOfEntrantsPerTeam. - // Insert into TeamEntrant table. - for m := Low(Teams[i].Entrants) to High(Teams[i].Entrants) do + p := 1; // Swimming order for relay team members. + + // Insert into fNumOfSwimmersInTeam x TeamEntrant table. + for m := Low(Teams[i].idxSwimmer) to High(Teams[i].idxSwimmer) do begin + idx := Teams[i].idxSwimmer[m]; // TSwimming index to lookup swimmers. + qry2.Close; s := ''' - INSERT INTO TeamEntrant - (MemberID, Lane, TeamID, PersonalBest, TimeToBeat, StrokeID, IsDisqualified) + INSERT INTO dbo.TeamEntrant + (MemberID, Lane, TeamID, PersonalBest, TimeToBeat, StrokeID, IsDisqualified, IsScratched) VALUES - (:MEMBERID, :LANE, :TEAMID, :PB, :TTB, :STROKEID, 0); + (:MEMBERID, :LANE, :TEAMID, :PB, :TTB, :STROKEID, :ISDISQUALIFIED, :ISSCRATCHED); '''; - qry3.SQL.Text := s; - qry3.ParamByName('MEMBERID').AsInteger := Teams[i].Entrants[m]; - qry3.ParamByName('LANE').AsInteger := p; - qry3.ParamByName('TEAMID').AsInteger := ATeamID; - qry3.ParamByName('STROKEID').AsInteger := fStrokeID; - // TeamEntrant.Disqualified - BIT : NOT NULL - // qry3.ParamByName('DISQUALIFIED').AsBoolean := false; - - // locate swimmer in Swimmers... - for r := Low(Swimmers) to High(Swimmers) do - begin - if Swimmers[r].MemberID = Teams[i].Entrants[m] then - begin - qry3.ParamByName('PB').AsTime := Swimmers[r].PB; - qry3.ParamByName('TTB').AsTime := Swimmers[r].TTB; - break; - end; - end; - qry3.ExecSQL; + qry2.SQL.Text := s; + // S e t D a t a T y p e . +// qry2.ParamByName('ISDISQUALIFIED').DataType := ftBoolean; +// qry2.ParamByName('ISSCRATCHED').DataType := ftBoolean; +// qry2.ParamByName('PB').DataType := ftTime; +// qry2.ParamByName('TTB').DataType := ftTime; + // A s s i g n v a l u e . + qry2.ParamByName('MEMBERID').AsInteger := Swimmers[idx].MemberID; + qry2.ParamByName('LANE').AsInteger := p; + qry2.ParamByName('TEAMID').AsInteger := ATeamID; + qry2.ParamByName('PB').AsTime := Swimmers[idx].PB; + qry2.ParamByName('TTB').AsTime := Swimmers[idx].TTB; + qry2.ParamByName('STROKEID').AsInteger := fStrokeID; + qry2.ParamByName('ISDISQUALIFIED').AsBoolean := false; + qry2.ParamByName('ISSCRATCHED').AsBoolean := false; + + qry2.Prepare; + qry2.ExecSQL; + ATeamTTB := ATeamTTB + Swimmers[idx].TTB; inc(p, 1); - end; - end; - End; + end; { N E X T T E A M - S W I M M E R . } + + // --------------------------------------------------------------------- + + { A S S I G N T E A M D A T A (Part-B).} + // Had to make a new var 's2' else ExecSQL uses cached 's' ...! + s2 := ''' + UPDATE dbo.Team + SET TimeToBeat = :ATEAMTTB + WHERE TeamID = :ATEAMID + '''; + // Explicently assign ATyes. + fConnection.ExecSQL(s2, [ATeamTTB, ATeamID],[ftTime, ftInteger]); + + // next lane index for the current heat. + inc(laneIdx); + + end; { N E X T T E A M . } + + End; { N E X T H E A T . } - qry3.Close; qry2.Close; qry1.Close; qry1.Free; qry2.Free; - qry3.Free; result := true; end; -procedure TABRelayExec.Mutate(var Chromosome: TChromosome); +procedure TABRelayExec.NormalizeINDV_TTB; var - Team1, Team2, Swimmer1, Swimmer2: Integer; + I: integer; + minTTB, maxTTB: TTime; + range, totalNormTTB: TTime; + begin - if Random < MutationRate then + if Length(Swimmers) = 0 then Exit; + + // Initialize min and max with the first swimmer's TTB + minTTB := Swimmers[Low(Swimmers)].TTB; + maxTTB := Swimmers[Low(Swimmers)].TTB; + + // Find the min and max TTB values + for I := Low(Swimmers) to High(Swimmers) do + begin + if Swimmers[I].TTB < minTTB then + minTTB := Swimmers[I].TTB; + if Swimmers[I].TTB > maxTTB then + maxTTB := Swimmers[I].TTB; + end; + + // Calculate the range + range := maxTTB - minTTB; + + // Normalize each swimmer's TTB to a value between 0 and 1 + totalNormTTB := 0; + for I := Low(Swimmers) to High(Swimmers) do begin - Team1 := Random(Length(Chromosome)); - Team2 := Random(Length(Chromosome)); - Swimmer1 := Random(Length(Chromosome[Team1].Entrants)); - Swimmer2 := Random(Length(Chromosome[Team2].Entrants)); - Swap(Chromosome[Team1].Entrants[Swimmer1], Chromosome[Team2].Entrants[Swimmer2]); + if range = 0 then + Swimmers[I].NormTTB := 0 // Avoid division by zero + else + Swimmers[I].NormTTB := (Swimmers[I].TTB - minTTB) / range; + totalNormTTB := totalNormTTB + Swimmers[I].NormTTB; end; + + // Calculate the normalized average TTB + if Length(Swimmers) > 0 then + fAvgINDVNormTTB := totalNormTTB / Length(Swimmers) + else + fAvgINDVNormTTB := 0; end; procedure TABRelayExec.Prepare(AConnection: TFDConnection; AEventID: Integer); @@ -726,6 +957,13 @@ procedure TABRelayExec.ReadPreferences(IniFileName: string); begin iFile := TIniFile.Create(IniFileName); + // 2024-08-26 AUTO-BUILD RELAYS. + // Pack Method. + fPackAlgorithm := + (iFile.ReadInteger('Preferences', 'PackAlgorithm', 0)); + + + // When true gutter lanes are not used in events. i := iFile.ReadInteger('Preferences', 'ExcludeOutsideLanes', 0); fExcludeOutsideLanes := (i = 1); @@ -764,53 +1002,57 @@ procedure TABRelayExec.ReadPreferences(IniFileName: string); function TABRelayExec.RetriveSwimmingData(): boolean; var i: integer; - qry: TFDQuery; begin { Retrieve Swimmer Data: Fetch swimmers’ data from the Nominee table, focusing on their MemberID TTB (TimeToBeat) and PB (Personal Best) times. } result := false; - qry := ABRelayData.qryRelayNominee; // improves reability. - - qry.Close; - // Relay Event ID. - qry.ParamByName('EVENTID').AsInteger := fEventID; - // 0,1,2 - Default: 1. The avgerage of the 3 fastest race times. - qry.ParamByName('ALGORITHM').AsInteger := fHeatAlgorithm; - // Lastname, firstname (format for display of entrants name) - qry.ParamByName('TOGGLENAME').AsBoolean := true; - // if then entrant has no race history then ... - // Calculate a race time based on the mean average of the bottom... - qry.ParamByName('CALCDEFAULT').AsInteger := 1; - // ... bottom precent (Default is 50%) - qry.ParamByName('BOTTOMPERCENT').AsFloat := fRaceTimeBottomPercent; - // Distance swum by a swimmer in the Team-Relay. - // Relay's total distance DIV number of swimmers in the relay-team. - qry.ParamByName('XDISTANCEID').AsInteger := fIndvDistanceMetres; - // Perfect number of swimmers to fill ALL relay-teams perfectly. - qry.ParamByName('TOPNUMBER').AsInteger := fNumOfSwimmers; - qry.Prepare; - qry.Open; - - if not qry.Active then exit; - - if qry.IsEmpty then + with ABRelayData.qryRNominee do begin - qry.Close; - exit; + //Connection := fConnection; + Close; + // Relay Event ID. + ParamByName('EVENTID').AsInteger := fEventID; + // 0,1,2 - Default: 1. The avgerage of the 3 fastest race times. + ParamByName('ALGORITHM').AsInteger := fHeatAlgorithm; + // Lastname, firstname (format for display of entrants name) + ParamByName('TOGGLENAME').AsBoolean := true; + // if then entrant has no race history then ... + // Calculate a race time based on the mean average of the bottom... + ParamByName('CALCDEFAULT').AsInteger := 1; + // ... bottom precent (Default is 50%) + ParamByName('BOTTOMPERCENT').AsFloat := fRaceTimeBottomPercent; + // Distance swum by a swimmer in the Team-Relay. + // Relay's total distance DIV number of swimmers in the relay-team. + // LOOKUP DISTANCE to determine ID number ... + ParamByName('XDISTANCEID').AsInteger := fxDistanceID; + // Perfect number of swimmers to fill ALL relay-teams perfectly. + ParamByName('TOPNUMBER').AsInteger := fNumOfSwimmers; + Prepare; + // DEBUG ... + // SQL.SaveToFile('C:\Users\Ben\Documents\GitHub\SCM_SwimClubMeet-R\SQL\dump.sql'); + Open; + if not ABRelayData.qryRNominee.Active then exit; + if ABRelayData.qryRNominee.IsEmpty then exit; end; + // ASSERT array size. + i:= ABRelayData.qryRNominee.RecordCount; + if (Length(Swimmers) <> i) then + SetLength(Swimmers, i); + // Populate the array with swimmers. // ARRAY SIZE: Perfect number of swimmers = qry.RecordCount. + ABRelayData.qryRNominee.First; try for I := Low(Swimmers) to High(Swimmers) do begin - Swimmers[i].MemberID := qry.FieldByName('MemberID').AsInteger; - Swimmers[i].NomineeID := qry.FieldByName('NomineeID').AsInteger; - Swimmers[i].TTB := qry.FieldByName('TTB').AsDateTime; - Swimmers[i].PB := qry.FieldByName('PB').AsDateTime; - qry.Next; + Swimmers[i].MemberID := ABRelayData.qryRNominee.FieldByName('MemberID').AsInteger; + Swimmers[i].NomineeID := ABRelayData.qryRNominee.FieldByName('NomineeID').AsInteger; + Swimmers[i].TTB := TTime(ABRelayData.qryRNominee.FieldByName('xTTB').AsDateTime); + Swimmers[i].PB := TTime(ABRelayData.qryRNominee.FieldByName('xPB').AsDateTime); + ABRelayData.qryRNominee.Next; end; except on E: Exception do exit; @@ -820,35 +1062,155 @@ function TABRelayExec.RetriveSwimmingData(): boolean; end; -procedure TABRelayExec.Swap(var A, B: Integer); +procedure TABRelayExec.SecondFit; var - Temp: Integer; + i, j, k, teamLow, teamHigh, swimmerLow, swimmerHigh: Integer; + minDeviation, maxDeviation, fAcceptedMarginOfDeviation: double; + tempSwimmer, iter: Integer; begin - Temp := A; - A := B; - B := Temp; + // Define your acceptable margin here + fAcceptedMarginOfDeviation := CalculateAcceptableMargin; + fIterations := 3000; + iter := 0; // Initialize iter + repeat + // Find the team with the lowest and highest deviation + minDeviation := MaxDouble; + maxDeviation := -MaxDouble; + teamLow := -1; + teamHigh := -1; + + for i := Low(Teams) to High(Teams) do + begin + if Teams[i].Deviation < minDeviation then + begin + minDeviation := Teams[i].Deviation; + teamLow := i; + end; + if Teams[i].Deviation > maxDeviation then + begin + maxDeviation := Teams[i].Deviation; + teamHigh := i; + end; + end; + + // If deviations are within the acceptable margin, break the loop + if Abs(maxDeviation - minDeviation) <= fAcceptedMarginOfDeviation then + Break; + + // Try to find a swimmer to swap between the two teams + for j := Low(Teams[teamLow].idxSwimmer) to High(Teams[teamLow].idxSwimmer) + do + begin + swimmerLow := Teams[teamLow].idxSwimmer[j]; + for k := Low(Teams[teamHigh].idxSwimmer) to + High(Teams[teamHigh].idxSwimmer) do + begin + swimmerHigh := Teams[teamHigh].idxSwimmer[k]; + + // Swap the swimmers + tempSwimmer := Teams[teamLow].idxSwimmer[j]; + Teams[teamLow].idxSwimmer[j] := Teams[teamHigh].idxSwimmer[k]; + Teams[teamHigh].idxSwimmer[k] := tempSwimmer; + + // Recalculate deviations + Teams[teamLow].SumNormTTB := Teams[teamLow].SumNormTTB - + Swimmers[swimmerLow].NormTTB + Swimmers[swimmerHigh].NormTTB; + Teams[teamHigh].SumNormTTB := Teams[teamHigh].SumNormTTB - + Swimmers[swimmerHigh].NormTTB + Swimmers[swimmerLow].NormTTB; + Teams[teamLow].Deviation := fAvgTEAMNormTTB - Teams[teamLow].SumNormTTB; + Teams[teamHigh].Deviation := fAvgTEAMNormTTB - + Teams[teamHigh].SumNormTTB; + + // Check if the deviations are now within the acceptable margin + if (Abs(Teams[teamLow].Deviation) <= fAcceptedMarginOfDeviation) and + (Abs(Teams[teamHigh].Deviation) <= fAcceptedMarginOfDeviation) then + Exit; + end; + end; + inc(iter); + + until False OR (iter > fIterations); end; -function TABRelayExec.TournamentSelection: TChromosome; +procedure TABRelayExec.ThirdFit; var - i, BestIndex: Integer; - BestFitness, CurrentFitness: Double; + i, j, k, teamLow, teamHigh, swimmerLow, swimmerHigh: Integer; + minDeviation, maxDeviation, fAcceptedMarginOfDeviation: double; + tempSwimmer, iter: Integer; + improvement: Boolean; begin - BestIndex := Random(PopulationSize); - BestFitness := Fitness(Population[BestIndex]); - for i := 1 to 4 do - begin - CurrentFitness := Fitness(Population[Random(PopulationSize)]); - if CurrentFitness < BestFitness then + // Define your acceptable margin here + fAcceptedMarginOfDeviation := CalculateAcceptableMargin; + fIterations := 3000; + iter := 0; // Initialize iter + + repeat + improvement := False; + + // Find the team with the lowest and highest deviation + minDeviation := MaxDouble; + maxDeviation := -MaxDouble; + teamLow := -1; + teamHigh := -1; + + for i := Low(Teams) to High(Teams) do begin - BestFitness := CurrentFitness; - BestIndex := i; + if Teams[i].Deviation < minDeviation then + begin + minDeviation := Teams[i].Deviation; + teamLow := i; + end; + if Teams[i].Deviation > maxDeviation then + begin + maxDeviation := Teams[i].Deviation; + teamHigh := i; + end; end; - end; - Result := Population[BestIndex]; + + // If deviations are within the acceptable margin, break the loop + if Abs(maxDeviation - minDeviation) <= fAcceptedMarginOfDeviation then + Break; + + // Try to find a swimmer to swap between the two teams + for j := Low(Teams[teamLow].idxSwimmer) to High(Teams[teamLow].idxSwimmer) do + begin + swimmerLow := Teams[teamLow].idxSwimmer[j]; + for k := Low(Teams[teamHigh].idxSwimmer) to High(Teams[teamHigh].idxSwimmer) do + begin + swimmerHigh := Teams[teamHigh].idxSwimmer[k]; + + // Swap the swimmers + tempSwimmer := Teams[teamLow].idxSwimmer[j]; + Teams[teamLow].idxSwimmer[j] := Teams[teamHigh].idxSwimmer[k]; + Teams[teamHigh].idxSwimmer[k] := tempSwimmer; + + // Recalculate deviations + Teams[teamLow].SumNormTTB := Teams[teamLow].SumNormTTB - + Swimmers[swimmerLow].NormTTB + Swimmers[swimmerHigh].NormTTB; + Teams[teamHigh].SumNormTTB := Teams[teamHigh].SumNormTTB - + Swimmers[swimmerHigh].NormTTB + Swimmers[swimmerLow].NormTTB; + Teams[teamLow].Deviation := fAvgTEAMNormTTB - Teams[teamLow].SumNormTTB; + Teams[teamHigh].Deviation := fAvgTEAMNormTTB - Teams[teamHigh].SumNormTTB; + + // Check if the deviations are now within the acceptable margin + if (Abs(Teams[teamLow].Deviation) <= fAcceptedMarginOfDeviation) and + (Abs(Teams[teamHigh].Deviation) <= fAcceptedMarginOfDeviation) then + Exit; + + // If the swap improved the balance, mark improvement + if (Abs(Teams[teamLow].Deviation) < minDeviation) or (Abs(Teams[teamHigh].Deviation) < maxDeviation) then + improvement := True; + end; + end; + + Inc(iter); + + until (iter > fIterations) or not improvement; end; + + end. diff --git a/SCM_PlusTestSnippet2.groupproj b/SCM_PlusTestSnippet2.groupproj new file mode 100644 index 0000000..79010a8 --- /dev/null +++ b/SCM_PlusTestSnippet2.groupproj @@ -0,0 +1,48 @@ + + + {02A125AE-F1C3-4AE2-926C-664C63FDE63C} + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SCM_PlusTestSnippets.groupproj b/SCM_PlusTestSnippets.groupproj new file mode 100644 index 0000000..65652f9 --- /dev/null +++ b/SCM_PlusTestSnippets.groupproj @@ -0,0 +1,48 @@ + + + {9CFB542B-3FC9-4807-9303-81BC025CC293} + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQL/AutoBuildRelays_ListNominees.sql b/SQL/AutoBuildRelays_ListNominees.sql new file mode 100644 index 0000000..c59ba94 --- /dev/null +++ b/SQL/AutoBuildRelays_ListNominees.sql @@ -0,0 +1,83 @@ +USE SwimClubMeet +GO + +DECLARE @EventID AS INT; +DECLARE @Algorithm INT; +DECLARE @DistanceID AS INT; +DECLARE @StrokeID AS INT; +DECLARE @SessionStart DATETIME; +DECLARE @ToggleName BIT; +DECLARE @Order INT; +DECLARE @CalcDefault INT; +DECLARE @BottomPercent FLOAT; +DECLARE @TopNumber INT; + +DECLARE @SQL NVARChar(MAX); + + +SET @EventID = 1506; --:EVENTID; +SET @Algorithm = 1; -- :ALGORITHM; +SET @ToggleName = 1; -- :TOGGLENAME; +SET @CalcDefault = 1; --:CALCDEFAULT; +SET @BottomPercent = 50; --:BOTTOMPERCENT; +SET @DistanceID = 1; --:XDISTANCEID; +SET @TopNumber = 16; --:TOPNUMBER; + +SET @StrokeID = +( + SELECT StrokeID FROM Event WHERE Event.EventID = @EventID +); + +SET @SessionStart = +( + SELECT Session.SessionStart + FROM Event + INNER JOIN Session + ON Event.SessionID = Session.SessionID + WHERE Event.EventID = @EventID +); + + +-- Drop a temporary table called '#tmpID' +IF OBJECT_ID('tempDB..#tmpID', 'U') IS NOT NULL + DROP TABLE #tmpID; + +CREATE TABLE #tmpID +( + MemberID INT +) + +-- Members given a swimming lane in the given event + + INSERT INTO #tmpID + SELECT TeamEntrant.MemberID + FROM [SwimClubMeet].[dbo].[HeatIndividual] + INNER JOIN Team + ON HeatIndividual.HeatID = Team.HeatID + INNER JOIN TeamEntrant + ON Team.TeamID = TeamEntrant.TeamID + WHERE HeatIndividual.EventID = @EventID + AND HeatIndividual.HeatStatusID = 1; + + +-- Construct dynamic SQL + +SELECT TOP (@TopNumber) +Nominee.NomineeID, + Nominee.EventID, + Nominee.MemberID, + Member.GenderID, + dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE, + dbo.SwimmerGenderToString(Nominee.MemberID) AS Gender, + dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, Nominee.MemberID, @DistanceID, @StrokeID, @SessionStart) AS xTTB, + dbo.PersonalBest(Nominee.MemberID, @DistanceID, @StrokeID, @SessionStart) AS xPB, + CASE + WHEN @ToggleName = 0 THEN SUBSTRING(CONCAT(UPPER([LastName]), ' ', [FirstName]), 0, 30) + WHEN @ToggleName = 1 THEN SUBSTRING(CONCAT([FirstName], ' ', UPPER([LastName])), 0, 48) + END AS FName +FROM Nominee +LEFT OUTER JOIN #tmpID ON #tmpID.MemberID = Nominee.MemberID +LEFT OUTER JOIN Member ON Nominee.MemberID = Member.MemberID +WHERE Nominee.EventID = @EventID +AND #tmpID.MemberID IS NULL +ORDER BY TTB ASC; \ No newline at end of file diff --git a/SQL/dump.sql b/SQL/dump.sql new file mode 100644 index 0000000..9d7d9c7 --- /dev/null +++ b/SQL/dump.sql @@ -0,0 +1,86 @@ +--USE SwimClubMeet +--GO + +DECLARE @EventID AS INT; +DECLARE @Algorithm INT; +DECLARE @DistanceID AS INT; +DECLARE @StrokeID AS INT; +DECLARE @SessionStart DATETIME; +DECLARE @ToggleName BIT; +DECLARE @Order INT; +DECLARE @CalcDefault INT; +DECLARE @BottomPercent FLOAT; +DECLARE @TopNumber INT; + +DECLARE @SQL NVARChar(MAX); + + +SET @EventID = :EVENTID; +SET @Algorithm = :ALGORITHM; +SET @ToggleName = :TOGGLENAME; +SET @CalcDefault = :CALCDEFAULT; +SET @BottomPercent = :BOTTOMPERCENT; +SET @DistanceID = :XDISTANCEID; +SET @TopNumber = :TOPNUMBER; + +SET @StrokeID = +( + SELECT StrokeID FROM Event WHERE Event.EventID = @EventID +); + +SET @SessionStart = +( + SELECT Session.SessionStart + FROM Event + INNER JOIN Session + ON Event.SessionID = Session.SessionID + WHERE Event.EventID = @EventID +); + + +-- Drop a temporary table called '#tmpID' +IF OBJECT_ID('tempDB..#tmpID', 'U') IS NOT NULL + DROP TABLE #tmpID; + +CREATE TABLE #tmpID +( + MemberID INT +) + +-- Members given a swimming lane in the given event + + INSERT INTO #tmpID + SELECT TeamEntrant.MemberID + FROM [SwimClubMeet].[dbo].[HeatIndividual] + INNER JOIN Team + ON HeatIndividual.HeatID = Team.HeatID + INNER JOIN TeamEntrant + ON Team.TeamID = TeamEntrant.TeamID + WHERE HeatIndividual.EventID = @EventID + AND HeatIndividual.HeatStatusID = 1; + + +-- Construct dynamic SQL + +SELECT TOP (@TopNumber) +Nominee.NomineeID, + Nominee.EventID, + Nominee.MemberID, + Member.GenderID, + dbo.SwimmerAge(@SessionStart, Member.DOB) AS AGE, + dbo.SwimmerGenderToString(Nominee.MemberID) AS Gender, + dbo.TimeToBeat(@Algorithm, @CalcDefault, @BottomPercent, Nominee.MemberID, @DistanceID, @StrokeID, @SessionStart) AS xTTB, + dbo.PersonalBest(Nominee.MemberID, @DistanceID, @StrokeID, @SessionStart) AS xPB, + CASE + WHEN @ToggleName = 0 THEN SUBSTRING(CONCAT(UPPER([LastName]), ' ', [FirstName]), 0, 30) + WHEN @ToggleName = 1 THEN SUBSTRING(CONCAT([FirstName], ' ', UPPER([LastName])), 0, 48) + END AS FName +FROM Nominee +LEFT OUTER JOIN #tmpID ON #tmpID.MemberID = Nominee.MemberID +LEFT OUTER JOIN Member ON Nominee.MemberID = Member.MemberID +WHERE Nominee.EventID = @EventID +AND #tmpID.MemberID IS NULL +ORDER BY TTB ASC; + + + diff --git a/dmSCM.pas b/dmSCM.pas index 8020042..83b1145 100644 --- a/dmSCM.pas +++ b/dmSCM.pas @@ -268,7 +268,6 @@ TSCM = class(TDataModule) function InsertEmptyLanes(aHeatID: integer): integer; function LastLaneNum(aHeatID: integer): integer; function LocateLane(aIndvTeamID: integer; aEventType: scmEventType): boolean; - function PadLanes(aHeatID: integer): integer; function RenumberEvents(aSessionID: integer; DoLocate: Boolean = true): integer; function RenumberHeats(aEventID: integer; DoLocate: Boolean = true): integer; function RenumberLanes(aHeatID: integer): integer; @@ -276,6 +275,9 @@ TSCM = class(TDataModule) function SessionIsLocked(aIndvTeamID: integer; aEventType: scmEventType): Boolean; procedure WndProc(var wndMsg: TMessage); virtual; public + + function PadLanes(aHeatID: integer): integer; // Made public 2024.08.28. + { Public declarations } procedure ActivateTable(); procedure DeActivateTable(); diff --git a/frmMain.pas b/frmMain.pas index a1a8c56..46721ea 100644 --- a/frmMain.pas +++ b/frmMain.pas @@ -604,8 +604,22 @@ procedure TMain.Heat_AutoBuildRelayExecute(Sender: TObject); ABRelay.ExecAutoBuildRelay(); if ABRelay.Success then begin - Refresh_Heat; - Refresh_IndvTeam; + SCM.qryTeamEntrant.DisableControls; + SCM.qryTeam.DisableControls; + SCM.qryHeat.DisableControls; + SCM.qryTeamEntrant.Close; + SCM.qryTeam.Close; + SCM.qryHeat.Close; + SCM.qryHeat.Open; + SCM.qryTeam.Open; + SCM.qryTeamEntrant.Open; + SCM.qryHeat.EnableControls; + SCM.qryTeamEntrant.EnableControls; + SCM.qryTeam.EnableControls; + +// Refresh_Heat; +// Refresh_IndvTeam; +// // Requery SCM.qryEvent to update entrant count. PostMessage(Handle, SCM_UPDATEENTRANTCOUNT, 0, 0); // Set flag for statusbar update. From bbf3636bdb1690443b10702797f0244b64636aa4 Mon Sep 17 00:00:00 2001 From: Artanemus <69775305+Artanemus@users.noreply.github.com> Date: Thu, 3 Oct 2024 13:39:27 +1000 Subject: [PATCH 6/6] Fix Clone Session + AutoBuild relays running (v1) --- AUTOBUILD/dlgABRelay.dfm | 103 +- AUTOBUILD/dlgABRelay.pas | 44 +- AUTOBUILD/uABRelayExec.pas | 103 +- SwimClubMeet.dproj | 21 +- SwimClubMeet.res | Bin 212832 -> 212832 bytes dlgCloneSession.dfm | 2789 ++---------------------------------- dlgCloneSession.pas | 21 +- frmMain.pas | 5 +- 8 files changed, 264 insertions(+), 2822 deletions(-) diff --git a/AUTOBUILD/dlgABRelay.dfm b/AUTOBUILD/dlgABRelay.dfm index 0ad9cf5..7771f5c 100644 --- a/AUTOBUILD/dlgABRelay.dfm +++ b/AUTOBUILD/dlgABRelay.dfm @@ -170,7 +170,7 @@ object ABRelay: TABRelay Align = alClient BevelOuter = bvNone TabOrder = 2 - object lbl7: TLabel + object lblMemberMissingData: TLabel Left = 22 Top = 130 Width = 238 @@ -183,24 +183,16 @@ object ABRelay: TABRelay Font.Style = [fsUnderline] ParentFont = False end - object lbl8: TLabel + object lblPercent: TLabel Left = 106 Top = 187 Width = 333 Height = 21 Caption = 'percent. (With consideration to age and gender.)' end - object lblSeedDepth: TLabel - Left = 344 - Top = 334 - Width = 81 - Height = 21 - Caption = 'Seed depth:' - Enabled = False - end object lblSwimmersPerTeam: TLabel Left = 80 - Top = 334 + Top = 372 Width = 143 Height = 21 Caption = 'Swimmers per team.' @@ -245,6 +237,39 @@ object ABRelay: TABRelay OnClick = vimgHint3Click OnMouseLeave = vimgHintMouseLeave end + object lblAcceptableMargin: TLabel + Left = 229 + Top = 283 + Width = 230 + Height = 21 + Caption = 'Acceptable margin (default 20%)' + end + object vimgPackMethod: TVirtualImage + Left = 479 + Top = 240 + Width = 24 + Height = 25 + ImageCollection = imgcolABRelay + ImageWidth = 0 + ImageHeight = 0 + ImageIndex = 0 + ImageName = 'Info' + OnClick = vimgPackMethodClick + OnMouseLeave = vimgHintMouseLeave + end + object rgrpAlgorithm: TRadioGroup + Left = 22 + Top = 232 + Width = 451 + Height = 117 + Caption = 'Pack Method.' + ItemIndex = 0 + Items.Strings = ( + 'SCM Basic bin pack.' + 'SCM Refined.' + 'Generic Algorithm.') + TabOrder = 9 + end object prefHeatAlgorithm: TRadioGroup Left = 22 Top = 6 @@ -296,31 +321,6 @@ object ABRelay: TABRelay Enabled = False TabOrder = 3 end - object rgpSeedMethod: TRadioGroup - Left = 253 - Top = 230 - Width = 225 - Height = 95 - Hint = 'Decides what lane an entrant is given.' - Caption = 'Seed Method.' - Enabled = False - ItemIndex = 0 - Items.Strings = ( - 'SwimClubMeet (default)' - 'Circle Seeding') - TabOrder = 5 - end - object spnSeedDepth: TSpinEdit - Left = 431 - Top = 331 - Width = 48 - Height = 31 - Enabled = False - MaxValue = 10 - MinValue = 0 - TabOrder = 6 - Value = 3 - end object prefDoHouseRelays: TCheckBox Left = 253 Top = 406 @@ -328,17 +328,17 @@ object ABRelay: TABRelay Height = 25 Caption = 'Arrange by house.' Enabled = False - TabOrder = 7 + TabOrder = 5 end object prefNumOfSwimmersPerTeam: TSpinEdit Left = 22 - Top = 331 + Top = 369 Width = 52 Height = 31 Enabled = False MaxValue = 12 MinValue = 2 - TabOrder = 8 + TabOrder = 6 Value = 4 end object prefVerbose: TCheckBox @@ -347,7 +347,7 @@ object ABRelay: TABRelay Width = 82 Height = 25 Caption = 'Verbose.' - TabOrder = 9 + TabOrder = 7 end object prefTrimPartialTeams: TCheckBox Left = 253 @@ -358,20 +358,17 @@ object ABRelay: TABRelay Checked = True Enabled = False State = cbChecked - TabOrder = 10 + TabOrder = 8 end - object rgrpAlgorithm: TRadioGroup - Left = 22 - Top = 230 - Width = 225 - Height = 95 - Caption = 'Pack Method.' - Enabled = False - ItemIndex = 0 - Items.Strings = ( - 'SCM bin pack routine.' - 'Generic Algorithm.') - TabOrder = 11 + object spnAcceptableMargin: TSpinEdit + Left = 160 + Top = 283 + Width = 63 + Height = 31 + MaxValue = 100 + MinValue = 10 + TabOrder = 10 + Value = 20 end end object bhintABRelay: TBalloonHint diff --git a/AUTOBUILD/dlgABRelay.pas b/AUTOBUILD/dlgABRelay.pas index 4e41762..bc42bab 100644 --- a/AUTOBUILD/dlgABRelay.pas +++ b/AUTOBUILD/dlgABRelay.pas @@ -18,16 +18,13 @@ TABRelay = class(TForm) Label1: TLabel; Image1: TImage; pnlPrefences: TPanel; - lbl7: TLabel; - lbl8: TLabel; - lblSeedDepth: TLabel; + lblMemberMissingData: TLabel; + lblPercent: TLabel; prefHeatAlgorithm: TRadioGroup; prefUseDefRaceTime: TCheckBox; prefRaceTimeTopPercent: TSpinEdit; prefExcludeOutsideLanes: TCheckBox; prefSeperateGender: TCheckBox; - rgpSeedMethod: TRadioGroup; - spnSeedDepth: TSpinEdit; prefDoHouseRelays: TCheckBox; prefNumOfSwimmersPerTeam: TSpinEdit; lblSwimmersPerTeam: TLabel; @@ -39,6 +36,9 @@ TABRelay = class(TForm) vimgHint2: TVirtualImage; vimgHint3: TVirtualImage; rgrpAlgorithm: TRadioGroup; + spnAcceptableMargin: TSpinEdit; + lblAcceptableMargin: TLabel; + vimgPackMethod: TVirtualImage; procedure FormDestroy(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormShow(Sender: TObject); @@ -46,6 +46,7 @@ TABRelay = class(TForm) procedure vimgHintMouseLeave(Sender: TObject); procedure vimgHint2Click(Sender: TObject); procedure vimgHint3Click(Sender: TObject); + procedure vimgPackMethodClick(Sender: TObject); private { Private declarations } procedure ReadPreferences(IniFileName: string); @@ -115,10 +116,15 @@ procedure TABRelay.ReadPreferences(IniFileName: string); integer(cbUnchecked))); prefSeperateGender.State := TCheckBoxState(iFile.ReadInteger('Preferences', 'SeperateGender', integer(cbUnchecked))); + // prefGroupBy.ItemIndex := iFile.ReadInteger('Preferences', 'GroupBy', 0); - rgpSeedMethod.ItemIndex := iFile.ReadInteger('Preferences', 'SeedMethod', 0); +// rgpSeedMethod.ItemIndex := iFile.ReadInteger('Preferences', 'SeedMethod', 0); + // 2020-11-01 auto-build v2 seed depth for Circle Seed */ - spnSeedDepth.Value := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); +// spnSeedDepth.Value := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); + + // 2024-10-3 auto-build relays acceptable margin for refined algorithm */ + spnAcceptableMargin.Value := (iFile.ReadInteger('Preferences', 'AcceptableMargin', 20)); // 2024-09-10 // Relay teams, by default, have four swimmers. @@ -158,12 +164,25 @@ procedure TABRelay.vimgHint3Click(Sender: TObject); bhintABRelay.Title := 'Group by Club-House .'; bhintABRelay.Description := ''' Relay teams will only be allowed swimmers that share - the same 'House' (as designated in the member's profile). + the same House ...as designated in the member's profile. '''; bhintABRelay.ShowHint(vimgHint3); end; +procedure TABRelay.vimgPackMethodClick(Sender: TObject); +begin + bhintABRelay.Title := 'Pack Method'; + bhintABRelay.Description := ''' + The Basic pack method is ideal when members' race times vary widely. It uses this broad range to build teams. + The Refined method should be used when members' Time-To-Beat (TTB) values are closely grouped together. + The Acceptable margin sets a threshold for team member selection; default is 20%, adjustable from 10% to 60%. Lower margins are recommended for groups with less variation in TTB. + The Genetic Algorithm takes longer and may yield unpredictable results but often outperforms other methods by simulating natural selection principles to optimize team composition. + '''; + bhintABRelay.ShowHint(vimgPackMethod); +end; + + procedure TABRelay.WritePreferences(IniFileName: string); var iFile: TIniFile; @@ -186,9 +205,14 @@ procedure TABRelay.WritePreferences(IniFileName: string); iFile.WriteInteger('Preferences', 'SeperateGender', integer(prefSeperateGender.State)); // iFile.WriteInteger('Preferences', 'GroupBy', prefGroupBy.ItemIndex); - iFile.WriteInteger('Preferences', 'SeedMethod', rgpSeedMethod.ItemIndex); +// iFile.WriteInteger('Preferences', 'SeedMethod', rgpSeedMethod.ItemIndex); + // 2020-11-01 auto-build v2 seed depth for Circle Seed */ - iFile.WriteInteger('Preferences', 'SeedDepth', (spnSeedDepth.Value)); +// iFile.WriteInteger('Preferences', 'SeedDepth', (spnSeedDepth.Value)); + + // 2024-10-3 auto-build relays acceptable margin for refined algorithm */ + iFile.WriteInteger('Preferences', 'AcceptableMargin', (spnAcceptableMargin.Value)); + // 2024-09-10 // Relay teams, by default, have four swimmers. diff --git a/AUTOBUILD/uABRelayExec.pas b/AUTOBUILD/uABRelayExec.pas index 9485dbc..b7214e0 100644 --- a/AUTOBUILD/uABRelayExec.pas +++ b/AUTOBUILD/uABRelayExec.pas @@ -69,6 +69,7 @@ TABRelayExec = class(TComponent) fAcceptedMarginOfDeviation: double; // packing bin algorithm fIterations: Integer; // genetic algorithm default = 1000. fPackAlgorithm: integer; + fAcceptableMargin: integer; // Refined Algorithm - default 20 percent. { C A L C R A C E - T I M E . } fAvgINDVNormTTB: double; // Average optimum INDV TTB - normalized. @@ -96,11 +97,11 @@ TABRelayExec = class(TComponent) // B i n p a c k i n g r o u t i n e s . { R O U T I N E S . } procedure GetAvgDeviation(var LowAvg: double; var HighAvg: double); - function CalculateAcceptableMargin(): double; + function CalculateAcceptableMargin(Percent: integer): double; procedure NormalizeINDV_TTB; procedure FirstFit; - procedure SecondFit; - procedure ThirdFit; + procedure SecondFit(Percent: integer); + procedure ThirdFit(Percent: integer); procedure GeneticAlgorithm; @@ -121,7 +122,7 @@ implementation uses System.Math, System.IniFiles, SCMUtility, dmABRelayData, system.Generics.Collections, System.StrUtils; -function TABRelayExec.CalculateAcceptableMargin: double; +function TABRelayExec.CalculateAcceptableMargin(Percent: integer): double; var LowAvg, HighAvg: double; begin @@ -132,7 +133,7 @@ function TABRelayExec.CalculateAcceptableMargin: double; if (LowAvg = 0) and (HighAvg = 0) then Result := 0 else - Result := 0.1 * (LowAvg + HighAvg) / 2; // 10% of the average deviation + Result := (Percent/100) * (LowAvg + HighAvg) / 2; // 60% of the average deviation end; constructor TABRelayExec.Create(AOwner: TComponent); @@ -148,6 +149,7 @@ constructor TABRelayExec.Create(AOwner: TComponent); fSuccess := false; fAcceptedMarginOfDeviation := 0; fIterations := 1000; + fAcceptableMargin := 20; // I N I T I A L I Z E P R E F E R E N C E S . fExcludeOutsideLanes := false; @@ -177,6 +179,7 @@ procedure TABRelayExec.ExecAutoBuildRelay(); IniFileName: TFileName; v: variant; s: string; + i: integer; begin // A S S E R T . fSuccess := false; @@ -361,19 +364,35 @@ procedure TABRelayExec.ExecAutoBuildRelay(); // Optimum swimming time for all teams. fAvgTEAMNormTTB := fAvgINDVNormTTB * fNumOfSwimmersPerTeam; - if fPackAlgorithm = 0 then - begin - FirstFit; // First fit. Scatter swimmers across teams. -// SecondFit; // Find most (+/-) deviation and swap swimmers. -// ThirdFit; // Refine deviation until within margin or looped fIterations. - end - else - begin - FirstFit; // First fit. Scatter swimmers across teams. - GeneticAlgorithm; // A Generic Algorithm - slower, perhaps better? + case fPackAlgorithm of + 0: + begin + FirstFit; // First fit. Scatter swimmers across teams. + SecondFit(60); // Find most (+/-) deviation and swap swimmers. + SecondFit(50); // Find most (+/-) deviation and swap swimmers. + SecondFit(40); // Find most (+/-) deviation and swap swimmers. + end; + 1: + begin + FirstFit; // First fit. Scatter swimmers across teams. + // Find most (+/-) deviation and swap swimmers. default 20 percent + SecondFit(fAcceptableMargin); // Find most (+/-) deviation and swap swimmers. + i := (fAcceptableMargin - 10); + if (i<10) then i := 10; + ThirdFit(i); // Refine deviation until within margin or looped fIterations. + end; + 2: + begin + FirstFit; // First fit. Scatter swimmers across teams. +// GeneticAlgorithm; // A Generic Algorithm - slower, perhaps better? + s := ''' + The Genetic Algorithm option is not available for this build. + Auto-build ENDED. + '''; + Application.MessageBox(Pchar(s), 'SwimClubMeet AutoBuild', MB_ICONINFORMATION or MB_OK); + end; end; - // D i s t r i b u t e 2 . (Gender, house, etc) // if fSuccess then DistributeTeams(FDQuery); fSuccess := InsertIntoTeams(); @@ -438,7 +457,7 @@ procedure TABRelayExec.FirstFit; procedure TABRelayExec.GeneticAlgorithm; const - PopulationSize = 16; + PopulationSize = 1000; MaxGenerations = 100; MutationRate = 0.01; var @@ -449,19 +468,37 @@ procedure TABRelayExec.GeneticAlgorithm; procedure CopyPopulation(var Dest: array of TTeamArray; const Src: array of TTeamArray); var - i, j: Integer; + i, j, k: Integer; begin - SetLength(Population, Length(Src)); + +// caller .... +// CopyPopulation(Population, NewPopulation); + + SetLength(Population, Length(Src)); // Dest = Population for i := Low(Src) to High(Src) do begin SetLength(Dest[i], Length(Src[i])); for j := Low(Src[i]) to High(Src[i]) do begin - Dest[i][j] := Src[i][j]; + // Initialize the TTeam record + Dest[i][j].TeamID := Src[i][j].TeamID; + Dest[i][j].TeamNameID := Src[i][j].TeamNameID; + Dest[i][j].Lane := Src[i][j].Lane; + Dest[i][j].HeatID := Src[i][j].HeatID; + Dest[i][j].SumNormTTB := Src[i][j].SumNormTTB; + Dest[i][j].Deviation := Src[i][j].Deviation; + + // Set the length of idxSwimmer and copy the values + SetLength(Dest[i][j].idxSwimmer, Length(Src[i][j].idxSwimmer)); + for k := Low(Src[i][j].idxSwimmer) to High(Src[i][j].idxSwimmer) do + begin + Dest[i][j].idxSwimmer[k] := Src[i][j].idxSwimmer[k]; + end; end; end; end; + function Fitness(Teams: TTeamArray): Double; var i: Integer; @@ -996,6 +1033,10 @@ procedure TABRelayExec.ReadPreferences(IniFileName: string); // 2020-11-01 auto-build v2 seed depth for Circle Seed */ fSeedDepth := (iFile.ReadInteger('Preferences', 'SeedDepth', 3)); + // 2024-10-3 auto-build relays acceptable margin for refined algorithm */ + fAcceptableMargin := (iFile.ReadInteger('Preferences', 'AcceptableMargin', 20)); + + iFile.free; end; @@ -1062,14 +1103,14 @@ function TABRelayExec.RetriveSwimmingData(): boolean; end; -procedure TABRelayExec.SecondFit; +procedure TABRelayExec.SecondFit(Percent: integer); var i, j, k, teamLow, teamHigh, swimmerLow, swimmerHigh: Integer; minDeviation, maxDeviation, fAcceptedMarginOfDeviation: double; - tempSwimmer, iter: Integer; + tempSwimmer, lowSwimmer, highSwimmer, iter: Integer; begin // Define your acceptable margin here - fAcceptedMarginOfDeviation := CalculateAcceptableMargin; + fAcceptedMarginOfDeviation := CalculateAcceptableMargin(Percent); fIterations := 3000; iter := 0; // Initialize iter repeat @@ -1122,9 +1163,15 @@ procedure TABRelayExec.SecondFit; Teams[teamHigh].SumNormTTB; // Check if the deviations are now within the acceptable margin - if (Abs(Teams[teamLow].Deviation) <= fAcceptedMarginOfDeviation) and - (Abs(Teams[teamHigh].Deviation) <= fAcceptedMarginOfDeviation) then - Exit; + if ((Abs(Teams[teamLow].Deviation) <= fAcceptedMarginOfDeviation)) then + begin + if ((Abs(Teams[teamHigh].Deviation) <= fAcceptedMarginOfDeviation)) then + begin + highSwimmer := Teams[teamHigh].idxSwimmer[k]; + lowSwimmer := Teams[teamLow].idxSwimmer[j]; + Exit; + end; + end; end; end; inc(iter); @@ -1132,7 +1179,7 @@ procedure TABRelayExec.SecondFit; until False OR (iter > fIterations); end; -procedure TABRelayExec.ThirdFit; +procedure TABRelayExec.ThirdFit(Percent: integer); var i, j, k, teamLow, teamHigh, swimmerLow, swimmerHigh: Integer; minDeviation, maxDeviation, fAcceptedMarginOfDeviation: double; @@ -1140,7 +1187,7 @@ procedure TABRelayExec.ThirdFit; improvement: Boolean; begin // Define your acceptable margin here - fAcceptedMarginOfDeviation := CalculateAcceptableMargin; + fAcceptedMarginOfDeviation := CalculateAcceptableMargin(Percent); fIterations := 3000; iter := 0; // Initialize iter diff --git a/SwimClubMeet.dproj b/SwimClubMeet.dproj index c1c0301..8b7283e 100644 --- a/SwimClubMeet.dproj +++ b/SwimClubMeet.dproj @@ -4,7 +4,7 @@ 20.1 VCL True - Debug + Release Win32 1 Application @@ -110,9 +110,10 @@ true 1033 7 - CompanyName=Artanemus;FileDescription=$(MSBuildProjectName) - Manage club members and run your club nights.;FileVersion=1.7.1.0;InternalName=SwimClubMeet;LegalCopyright=Copyright (C) 2019-2024 Artanemus. All rights reserved.;LegalTrademarks=;OriginalFilename=SwimClubMeet.exe;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.7;Comments=Manage club members and run your club nights. + CompanyName=Artanemus;FileDescription=$(MSBuildProjectName) - Manage club members and run your club nights.;FileVersion=1.7.1.2;InternalName=SwimClubMeet;LegalCopyright=Copyright (C) 2019-2024 Artanemus. All rights reserved.;LegalTrademarks=;OriginalFilename=SwimClubMeet.exe;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.7;Comments=Manage club members and run your club nights. SwimClubMeet.ico 1 + 2
@@ -453,8 +454,6 @@
ABRelay
dfm - - Base @@ -481,22 +480,10 @@ Microsoft Office 2000 Sample Automation Server Wrapper Components Microsoft Office XP Sample Automation Server Wrapper Components - + - - - .\ - true - - - - - .\ - true - - diff --git a/SwimClubMeet.res b/SwimClubMeet.res index 3871fd538708d68a945bbb8eeb7e290fef1a57b8..50359bcbcb005f49b34d3db5e48f5b8cd130f166 100644 GIT binary patch delta 66 zcmaFxkLSTZo(Uy_Obm<+>_7|x_8?+oYcM0LA%h-+(d4^~{fs7?bD8`Z8O@vbGPmz# PW&~oU?R%M-7h3}WG58T* delta 66 zcmaFxkLSTZo(Uy_j0_A6tPCKMfx#Y#nKrftGqM^m=rI^hzRTFpXuLU>$)AzYw0SRc R`(9>7AZFUWmzjC7H2^U25m*2K diff --git a/dlgCloneSession.dfm b/dlgCloneSession.dfm index b859e6b..3bd277e 100644 --- a/dlgCloneSession.dfm +++ b/dlgCloneSession.dfm @@ -3,12 +3,12 @@ object CloneSession: TCloneSession Top = 0 BorderStyle = bsDialog Caption = 'Clone Session ...' - ClientHeight = 164 - ClientWidth = 343 + ClientHeight = 211 + ClientWidth = 424 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText - Font.Height = -11 + Font.Height = -16 Font.Name = 'Tahoma' Font.Style = [] KeyPreview = True @@ -16,2736 +16,82 @@ object CloneSession: TCloneSession OnCreate = FormCreate OnDestroy = FormDestroy OnKeyDown = FormKeyDown - TextHeight = 13 - object Image1: TImage - Left = 18 - Top = 16 - Width = 32 - Height = 32 - AutoSize = True - Picture.Data = { - 055449636F6E0000010005003030000001002000A82500005600000020200000 - 01002000A8100000FE250000101000000100200068040000A636000018180000 - 01002000880900000E3B00008080000001002000280801009644000028000000 - 3000000060000000010020000000000080250000000000000000000000000000 - 000000008E3435FF8E3536FF8E3536FF8D3334FF97322FFF8D8393FF48ABDCFF - 43A6D8FF3F9FCFFF594E5FFF8F2A28FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3435FF8C3031FF9B4645FF89899CFF50B6E6FF2AA3D8FF205672FF - 476076FF64ACCFFF2E6C8DFF74231FFF853130FF833230FF823030FF842C2AFF - 844244FF3296C5FF265B78FF476379FF3E94BEFF20A6E0FF3890BFFF694653FF - 8B2D2CFF8D3435FF8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF863232FF853232FF873232FF862D2DFF9C4644FF62A2C6FF1E7EAAFF - 26769CFF268FBFFF2D779DFF7F2A29FF923435FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3334FF912C2AFF90727EFF39A1CFFF237095FF285F7CFF32576FFF - 54748BFF50AAD3FF41485AFF842824FF833231FF833231FF833130FF822F2EFF - 8C312CFF5C89A9FF216E92FF3E5C74FF3A6A87FF29698AFF2682ADFF2889B7FF - 6E3439FF933231FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF823130FF823130FF81302FFF822825FF94676CFF3A99C3FF27495FFF - 385369FF48728CFF3198C6FF60333AFF91302FFF8C3435FF8C3435FF8C3435FF - 8C2F30FF912C2AFF972C2AFF888295FF2380A9FF344455FF435E75FF3F647FFF - 587C95FF41A3CEFF543742FF8B2D29FF823230FF823130FF823231FF81302FFF - 8B2A25FF77859BFF2179A0FF395970FF3D617BFF3E556BFF58758AFF39A0CDFF - 5E3640FF95302FFF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF872E2BFF843130FF802E2DFF8A241DFF898494FF2683ACFF35546BFF - 3F6683FF4E6E87FF40A2CFFF454455FF8B241DFF843231FF812E2EFF882925FF - 974745FF795763FF84221CFF826F7DFF229FD4FF2B87B2FF377597FF3A5D75FF - 51738BFF3EA3D0FF4F4151FF912B27FF843231FF81302FFF813130FF802E2DFF - 902E28FF7795AEFF1F6F94FF3A596FFF547890FF5593B4FF50ABD6FF2792C0FF - 643137FF953231FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF6B383DFF822924FF90302AFF97595AFF63A6CAFF1F6789FF3C5C73FF - 3D6681FF43637BFF4B9CC3FF337AA0FF733335FF87251FFF92332DFF8F717AFF - 5BA1C6FF229DD0FF395E79FF752826FF544E5EFF366C8DFF2694C7FF266382FF - 445D72FF4498C1FF2E7499FF79221CFF892D29FF82302EFF802D2BFF8C2622FF - A46567FF4BA5CFFF1F5470FF516B80FF63AFD3FF2C88B3FF345A75FF4E3944FF - 7E2D2CFF893332FF893333FF8A3334FF8C3434FF8D3435FF8E3435FF8E3435FF - 8E3436FF1E8FC1FF447697FF668CABFF3D9ECAFF1F769DFF2F546CFF406680FF - 3F6781FF40647DFF44738FFF2687B3FF248CBCFF4B6F8EFF6B90ACFF339BC9FF - 1C6182FF335E77FF2398CCFF355E7AFF82231BFF8B2118FF6E6D81FF2489B6FF - 34546AFF446E89FF2798CBFF366A8AFF783638FF8E322FFF953A38FF9B6A72FF - 5CACD3FF1C6A8FFF375267FF6894AEFF3E9BC6FF582A2FFF8C241EFF8C2E2BFF - 863433FF833332FF833332FF843332FF853333FF863433FF883434FF893434FF - 8A3434FF356A89FF2682ACFF1D749CFF28546EFF38556BFF41657FFF416783FF - 406682FF406782FF3F627CFF3C5A71FF326481FF2381ACFF1D749CFF2A5169FF - 3E5C73FF41617BFF49728EFF2EA8DBFF45485AFF97251EFF997882FF2C97C5FF - 2C536BFF41627CFF3D6783FF2686B3FF288BB9FF4489B2FF4B94BBFF3496C2FF - 1D6587FF355268FF44647DFF5593B4FF2B89B5FF742728FF963434FF8D3536FF - 8A3737FF893838FF883938FF883938FF883938FF873938FF873937FF873937FF - 873937FF43647DFF3E5D75FF3C5A71FF42657FFF406783FF3E647FFF40627CFF - 41627CFF41637DFF416680FF426884FF42657FFF3E5D74FF3B5A71FF426680FF - 406783FF3E607AFF6D96AFFF399CC8FF592E36FFA64643FF6CA6C7FF1F6789FF - 39576FFF456680FF446178FF405B72FF316380FF25698AFF236281FF295169FF - 3E576CFF47667EFF41627BFF456A84FF3099C9FF405871FF8D2A28FF8F3335FF - 8D3435FF8E3E3DFF8D413FFF8D413FFF8D403FFF8D403FFF8D403FFF8D403FFF - 8D403EFF416782FF416783FF406783FF3C627DFF48667EFF537892FF4B81A0FF - 4481A2FF40799AFF3E6883FF405F78FF426681FF416883FF426883FF406681FF - 3B627DFF5A778DFF59B6DEFF37546CFF901F18FF916D79FF2BA5D8FF294D63FF - 59748AFF5EA3C5FF3191BDFF366A89FF415F77FF41627BFF406079FF537289FF - 5F97B6FF3F9DC9FF2D698AFF465C70FF5FA0C2FF218DBBFF6D282BFF923131FF - 913F3EFF944947FF934947FF934947FF934947FF934947FF934947FF934947FF - 934847FF406681FF3E6480FF3F617AFF5E7F97FF5AA7CBFF3AA1CEFF2F8BB5FF - 3085ADFF2B8DBAFF2495C6FF2A7FA7FF3A5E76FF43657EFF426A85FF406782FF - 43657EFF69A5C3FF3685AAFF6F2425FF942F2FFF8D3233FF4A7698FF2597C8FF - 44A8D3FF3583AAFF366B8BFF2996C7FF2A6D8EFF3A5972FF56758DFF62B1D5FF - 308CB7FF376787FF2587B5FF3298C6FF3BA5D2FF405165FF8C3937FF9B4D4BFF - 9B5451FF9A5250FF9A5250FF9A5250FF9A524FFF9A524FFF9A524FFF9A524FFF - 9A5250FF3E6782FF41647DFF6A91A9FF4FB3DDFF34799AFF60515BFF834C4BFF - 8D4E4BFF885250FF6F626CFF408CB2FF218FBEFF345E76FF456982FF426C87FF - 496B82FF4FA4CAFF347292FF8E3530FF964141FF923534FF913231FF4B6E8CFF - 39556EFF732626FF91201AFF757287FF268CB7FF334C60FF6A91AAFF3DA2CDFF - 5C383EFF9B362EFF845358FF437B9BFF49596BFF9C4D46FFA7605CFFA35F5CFF - A35F5CFFA35F5BFFA25D5AFFA25D5AFFA25D5AFFA35E5BFFA35F5CFFA35F5CFF - A35E5BFF3C637DFF688CA2FF52B6DFFF3F657CFF9B534BFFB5665FFFB26A66FF - B16C67FFB46E69FFB86961FFAE625AFF638EAAFF218FBDFF365A71FF456B86FF - 416781FF4E809CFF329FCDFF745053FFB4564DFFA95E59FFA95E5AFFA6554FFF - A4534DFFAA5C59FFAC5D58FFAD7C7CFF39A4D2FF307DA1FF57B1DAFF3480A3FF - 9B554EFFB36D69FFB16761FFAA5E57FFAC645DFFB16E6BFFAC6C68FFAA6764FF - A55F5DFFA35B59FFA6615EFFA86562FFA76360FFA55E5CFF9F5553FF984746FF - 913C3CFF617A8DFF64B4D7FF3B7896FFA55E55FFBC7772FFB57773FFB77B76FF - B47571FFA8625FFF9D504FFF9A413FFF9C3531FF5491B6FF23789CFF3C5971FF - 3F6581FF48657DFF46A4CEFF3E82A6FF795663FF8D474AFF9B3C38FFA04543FF - A55C5AFFB0706DFFB47470FFBB7771FF668CA4FF368CB1FF3789ACFF616873FF - B8736CFFB67874FFB37470FFB16F6BFFAB6562FFA25756FF9A4B4AFF943F3FFF - 8F3737FF913A3AFF964343FF974444FF923D3EFF8C3233FF8A2E30FF923B3CFF - 9A4A49FF83AEC4FF42A3CBFF7C6568FFCB867EFFC08783FFBA7D79FFA9615FFF - 954141FF8B3031FF882C2DFF86292BFF8C2120FF8A565FFF359DC7FF315870FF - 426883FF436680FF42738FFF2C86ACFF2893BEFF3395BFFF417EA1FF745058FF - B66E68FFBB7E7AFFBA7E7AFFBC7F7AFFB36E68FF9F5954FF9A504BFFA25450FF - A25655FF9C4C4BFF964242FF90393AFF8D3334FF8B3031FF8B3031FF8C3233FF - 8D3334FF8D3334FF8B3031FF872A2CFF8A2E30FF9A4949FFAD6865FFB97C77FF - BC827DFF72C7EBFF418EADFFA96F69FFBD7874FFA65B59FF943E3EFF882C2DFF - 85282AFF892D2FFF913A3BFF9D4E4DFFAA5E5CFFBF7873FF60ADCEFF2A6884FF - 486E87FF48738DFF476E87FF45667DFF3E637AFF386A83FF34A8D5FF578499FF - C7837AFFC58B87FFC38A85FFB97B77FFA15150FF8F3233FF8A2A2BFF88292AFF - 86292BFF882C2DFF8A2E30FF8B3132FF8C3234FF8D3334FF8C3334FF8B3132FF - 892E30FF882C2DFF8C3334FF9E4E4DFFB67571FFC58E89FFC7928CFFC48C86FF - C08580FF61D3FDFF427B97FF852A28FF8D3031FF85282AFF842628FF8E3536FF - A35654FFB97875FFC8908BFFD09D97FFD4A29BFFE1A69DFF7CB9D4FF276F8DFF - 486D85FF49748EFF49748EFF49758FFF48738EFF4F7087FF5BA9CBFF57859AFF - CB8C83FFCD9690FFC78F8AFFC28782FFC0837FFFBC7D79FFB26D6AFFA75D5BFF - 9E4F4EFF974444FF923C3DFF8F3838FF8E3636FF8E3536FF903838FF943F3FFF - 9E4F4EFFAE6865FFC18581FFCE9A94FFCE9B95FFC28883FFB16D6AFFA15352FF - 984545FF60CDF6FF4386A3FF954642FF9F4A49FF9D4D4CFFB26C6AFFCB948FFF - DCADA7FFDDAFA8FFD4A29CFFC78D88FFB4706CFFBA6B67FF75B4D0FF266986FF - 41627BFF416883FF3E647FFF40627CFF45657DFF557187FF5FB7DAFF486E85FF - 9A3C38FF9A4646FF964142FF954041FF984545FFA05150FFA95F5DFFAF6966FF - B46F6DFFB67470FFB87672FFB87773FFB97874FFBB7A77FFBE7F7BFFC28581FF - C38883FFC0837EFFBA7976FFB4706DFFA45857FF933D3DFF8A2F31FF892E2FFF - 8A3031FF67B0CFFF41A1C4FFA37976FFD89A94FFDAAAA3FFE3B7B0FFDAA9A3FF - BE7E7BFFA35655FF943E3FFF923A3BFF9A3E3DFFB17070FF58B2D4FF2E6079FF - 476D87FF446D87FF557991FF5E97B4FF52A6C9FF48ACD3FF37A7CEFF505260FF - 8E2927FF882A2CFF85282AFF862A2CFF8C2726FF912421FF90221FFF8E2929FF - 8A3032FF8D3435FF913A3AFF954040FF984444FF994746FF984545FF954040FF - 903939FF8D3334FF8C3334FF8B3132FF892E2FFF8B3132FF8D3435FF8C3233FF - 8A2F30FF5E8197FF45B0D8FF718F9DFFEBADA1FFDFAEA8FFC78A86FFA75656FF - A04B4BFFB36867FFC68885FFD29F9AFFE6A89CFFB9D7E4FF3191B3FF3C6075FF - 497791FF4F748BFF76B8D4FF48B1D5FF5690A7FF7D929FFFAB9595FFD6A39CFF - D9A19CFFC48682FFAC6361FF9E4745FF916D77FF778398FF717A8EFF754148FF - 8C2B2BFF8C3233FF8C3233FF8C3133FF8B3132FF8B3032FF8B3032FF8B3132FF - 8C3233FF8D3334FF8D3334FF8D3334FF8C3233FF882D2EFF872A2CFF8E3536FF - 994545FF4D6E85FF5295B4FF3BB1D9FF9A9092FFE7A398FFCC918CFFB98883FF - B18881FFA88078FF996B61FF945647FF9C9496FF54BDE1FF285B73FF3C637BFF - 37627CFF5A7E93FF60C5E9FF3D474EFF722E1FFF884F43FF9A665CFFAB756BFF - C8978DFFE1B8B1FFF0C0B9FFECC1BBFF68C8E9FF2991B5FF38B9E4FF44839EFF - 8C2D2AFF8C2F31FF86292BFF8F2827FF902D2DFF892E30FF892E2FFF892E2FFF - 892D2FFF872B2DFF862A2BFF872B2CFF8E3536FF9F4F4EFFB87472FFD39C97FF - E2B2ACFF4D7A93FF4E758CFF449ABCFF40B3DAFF7A8F9AFF916359FF965546FF - 964F40FF9A5040FFA26458FF98A3AAFF58C1E5FF296983FF3C5C72FF416B84FF - 41657CFF72B0CAFF499BB8FF6B3125FF81392AFF6E2C1FFF6E2415FF6A5C5DFF - 4D3C3CFF642315FF864232FFB0A8AAFF48AFD2FF335063FF5F9AB5FF49ADCFFF - B88882FFE1958BFFCC827CFF98818BFF825257FFA74C48FFA04E4EFF9F4D4DFF - A45453FFAE6160FFBB7573FFCE908CFFE3AFAAFFF5C9C2FFF9D1CAFFEEC5BEFF - E0B8B2FF47758FFF44708AFF41667DFF3E829FFF35A6CCFF50ABC9FF77A7BBFF - 83A7B6FF81B0C2FF69C0DDFF3DA6C9FF286077FF38586EFF3F6A83FF3C667FFF - 406379FF5BB2D4FF4489A1FF732919FF783124FF803323FF968C8EFF5CCCF2FF - 3DB0D6FF4F6E7CFF6A6163FF66B7D2FF3487A5FF305268FF4F7A93FF4DBCE2FF - 73B5CBFFCBD1D7FF9FDDF2FF49C4EAFF3FABCEFFA79696FFE6B4ABFFDEB3ADFF - D9B0A9FFD5AEA7FFCEA8A0FFC09B93FFAD867FFF996F68FF875B53FF6A3A31FF - 58281EFF426E87FF446F88FF467089FF44677EFF3C647BFF337691FF2F84A3FF - 3188A8FF3185A4FF30718CFF386076FF446E86FF477892FF467690FF467690FF - 44718BFF4F85A1FF49BEE4FF5A666BFF8E5B4FFF8D7F7DFF5BC8ECFF265D74FF - 346881FF3FADD3FF43B5DAFF307C97FF2D5468FF385D75FF395E76FF396F88FF - 3290B1FF3AB0D6FF2A7D9AFF274E63FF47A5C8FF42A9C9FF522D26FF5E281CFF - 572419FF511C11FF4D160AFF4A1004FF490D00FF4A0B00FF4F0D00FF551002FF - 550E00FF416B85FF416881FF436B83FF45738DFF487791FF4A758FFF4A748DFF - 4B758EFF4D7790FF517F99FF5285A1FF4F829EFF517F9AFF53819BFF5386A1FF - 5285A0FF537D97FF54A8CAFF52B7D7FFE4E4E3FFF3FFFFFF57C6E9FF2E667FFF - 446981FF3C677FFF2D576DFF345065FF486B82FF48768FFF3E6C87FF36566DFF - 36546AFF31596FFF2F5268FF446177FF74C0DCFF3F8BA4FF4C0A00FF5D1406FF - 5D1709FF5F190BFF611B0DFF611A0CFF5E1608FF550E00FF490500FF490E02FF - 55261CFF60849AFF63A5C1FF4993B1FF4D7993FF527E98FF5386A1FF5285A0FF - 5284A0FF5286A1FF477994FF3E657DFF52778EFF67ACC8FF4C97B6FF4A748CFF - 52809BFF4D7E9AFF6690A7FF4DC0E5FF7EAAB8FFFFFFFFFFC2F4FFFF3CA4C5FF - 447188FF4F819DFF5D859DFF6EAEC8FF54B8DAFF48B6DAFF40B2D7FF389CBFFF - 356881FF37586FFF365D77FF6898AFFF5FC4E3FF3D1F1BFF580700FF530D00FF - 4E0800FF490500FF460500FF480D02FF532118FF6E4942FF957F7BFFC9C1C0FF - EFF0F0FF64C9E9FF57BCDAFF49B5D5FF3DAACEFF4591AFFF4B7991FF51839EFF - 51839DFF4D7F9BFF537C93FF6AADC7FF61C9EAFF52B3D0FF4BB4D3FF40AACDFF - 3E758FFF587C93FF7DC9E4FF56B8D5FFBEC9CDFFFFFFFEFFEEFFFFFF49B2D2FF - 38677EFF6087A0FF7CC6E0FF54B9D7FF83AFBDFFABC2C8FFAFC6CCFF8DCEE2FF - 45BCDFFF2E6880FF37586FFF5DA1BDFF4AADCBFF54281FFF764338FF79534CFF - 866963FF98827EFFAE9F9CFFCDC7C6FFECECECFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF6FA4B3FFBBC5C8FFD9DCDDFF99CCDBFF5CCFEFFF3A7B94FF4F7B95FF - 50839EFF4F7B95FF75B6CEFF5CC1DEFF5E757DFF895950FF97665CFF669DAFFF - 40B6D9FF55C3E6FF48A5C0FF6E615FFFE1D6D3FFDFFFFFFF93EEFFFF3685A0FF - 4B6F87FF7DB9D2FF5ABFDCFFB2BEC1FFFFFEFBFFFFFFFFFFFFFFFFFFFFFEFAFF - C2F2FFFF42B4D6FF3D6B82FF5A87A1FF56C4E5FF7DCDE4FFD8F9FFFFFDFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFF7F4FFFFFFFFFFFFFFFFFFFFFFFDFFBAF3FFFF3590AEFF487089FF - 4E829EFF5D869EFF72CFEDFF4F6C76FFAF5744FFBB7668FFB26C5EFFAA5D4DFF - 847676FF558C9DFF734942FF9D7973FF63C5E0FF43ABC9FF32849FFF325D73FF - 587E95FF6CC9E6FF6E9FADFFF6EDE9FFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF93E6FEFF33809BFF4F768FFF5190ACFF3D9DBCFF44B9DDFF5CBCD8FF - D3D7D8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE2FEFFFF47ADCBFF3F657BFF - 4D7894FF6D94ABFF67CBE8FF7E8F95FF9E6F64FF985A4EFFAF6F62FFAF7063FF - AC6454FFA25748FFA85645FFA3AEB3FF40A7C6FF294052FF39576DFF3A627BFF - 567D93FF66D0EEFF4E5154FF743B2FFF795149FF84615AFF8B6C66FF937C77FF - E5DDDAFFC7FBFFFF368FACFF48728AFF4D7C97FF446981FF56859EFF4DBBDCFF - B1C5CAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCFFFFFF67D0EEFF419DBDFF - 5AABCAFF69C4E3FF4FB0CCFFD7DADBFFF4F4F4FFAF9C97FF8B5A50FF9C5C4FFF - AD6D60FFA96A5DFFA85B4CFF9BA3A9FF48C8EDFF428BA9FF43748EFF3B6179FF - 537990FF62CEEEFF5A5557FF853423FF793528FF712D20FF6C271AFF530F02FF - 82554BFFC1FDFFFF378DA9FF477089FF5B89A2FF669DB7FF71BED9FF50BFE0FF - BFD3D8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA4DBEBFF51B5D1FF - 5DBBD4FF52B4D0FF74A4B1FFF7F1EFFFFFFFFFFFFFFFFFFFDFDCDBFF9F827DFF - 885146FF9F5E51FFAC6658FF9A7069FF5F92A2FF51B4D0FF4ECAECFF376D84FF - 425F75FF5FC0DFFF518B9CFF7F2F1EFF874133FF803F32FF7D3B2EFF6D2112FF - 906A62FF89E9FFFF32738BFF577B94FF7FC4DDFF63D3F1FF64C1DAFF618F9CFF - A9A09FFFC3BAB9FFDDDAD9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF6F4FFE9E3E2FF - E8E3E2FFE6E2E0FFF6F1F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - CDC4C3FF906C64FF884D42FFA25B4DFFA25545FF914A3BFF7BA8B6FF40A3C1FF - 365469FF4C819BFF52D1F5FF5A757EFF823727FF873422FF863423FF844F45FF - 86C9DCFF42A0BDFF3E657DFF75A4BCFF6BD0ECFF6D878FFF70483FFF5A1E11FF - 501206FF490C00FF4A140AFF67413CFFBDB3B1FFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFF9FBFCFFB9AAA6FF855950FF8E4E41FFA75544FFAB9491FF54C7E6FF - 315B70FF3F6179FF44829DFF49C0E2FF5AACC3FF7399A6FF7CAAB9FF6BCBE4FF - 409EBAFF3C667CFF507B96FF74BCD7FF51B6D2FF49160CFF621708FF672214FF - 672214FF652012FF611B0DFF520B00FF3D0100FF9A8783FFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFEAEAEAFFA28983FF925548FF95C6D4FF3B91ACFF - 36586EFF3E657EFF3D5A71FF3C637BFF3A85A0FF3B96B3FF368CA8FF36758EFF - 41677EFF517A95FF52819CFF5185A0FF50C4E7FF4D5458FF6D1E0DFF69271AFF - 662417FF642115FF621F12FF621F11FF5E180AFF3C0000FFBBB0AEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCEE0E3FF54C7E8FF2B5266FF - 4C677DFF679EB7FF4C9CBAFF3C647CFF3C5A71FF37596FFF396078FF567D97FF - 6FA3BCFF5CAFCCFF447C97FF355066FF63ABC7FF4DB8D6FF582319FF6D2617FF - 682518FF652316FF642114FF621F12FF611E11FF530C00FF60372EFFFAFBFBFF - FFFFFFFFFFFFFFFFFFFFFFFFDED8D7FFA3857FFFAC8E88FFCEC4C2FFFBFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAFFFFFF5FC8E5FF3DA0C0FF - 69BEDCFF5EC5E2FF4FB0CBFF4BC2E4FF39728AFF375F78FF5C88A1FF7DC5DEFF - 5BC5E3FF5BBDD7FF4BBFE0FF4B9FBDFF6ACFEEFF457786FF632316FF6E291BFF - 69271AFF672417FF652215FF632013FF611E11FF611B0DFF460B00FFD0CAC9FF - FFFFFFFFFFFFFFFFFAFBFBFFA3827BFFC08073FFC88A7DFFAC756AFFA88780FF - CEC4C2FFFBFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBCBC7FF815C56FF5EBBD6FF - 4A97AEFF6D4F4AFF894232FF7BA2AFFF41A6C4FF395D74FF75A4BCFF62CAE6FF - 8EACB5FFE7DEDCFF738085FF4AAAC6FF447787FF651F11FF742D1FFF6D2C1FFF - 6B291BFF692619FF662417FF642114FF621F12FF631E11FF460500FFAB9C98FF - FFFFFFFFFFFFFFFFF7F9F9FF9F7971FFD19184FFD99C90FFD79A8DFFC7897DFF - A97268FFA7867FFFCDC3C1FFFBFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAFCFCFF906E67FFA05749FF985C51FF - 905144FFA25A4BFFA15444FF956E67FF51C0DFFF44809AFF76BFDBFF58B1CAFF - E7E0DEFFFFFFFFFF774237FF692619FF6E2C1FFF783224FF723124FF6F2D20FF - 6C2A1DFF6A281BFF682518FF662316FF642114FF631F12FF480500FF9C8884FF - FFFFFFFFFFFFFFFFFFFFFFFFC5B9B6FF9E6E65FFC28478FFD6988CFFD69A8EFF - D4978BFFC5867AFFA77065FFA5837DFFCCC2C0FFFAFDFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBDB0ADFF8D5145FFAD6E62FFA9675AFF - A66558FFA16255FF924F41FF926860FF7BD4EDFF42B6D7FF45B4D4FF72A3B1FF - FDF8F6FFFFFFFFFF7B564EFF772F20FF7D382BFF773529FF743225FF712F22FF - 6E2C1FFF6B291CFF69271AFF672417FF652215FF652114FF480600FFA4928EFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFDCDBFFB29994FFA8766CFFC28478FF - D4978AFFD4978BFFD29488FFC28477FFA56D62FFA38079FFC9BFBCFFF9FCFCFF - FFFFFFFFFFFFFFFFFFFFFFFFEEEEEEFF8D6158FFAD6C5FFFAD6E62FFA86A5EFF - A5665AFFA46356FF7A4338FFD5CDCAFFEFF4F6FFC2CDD0FFC1CBCDFFE5E4E4FF - FFFFFFFFFFFFFFFF9D8782FF672518FF7F3D30FF79372BFF763427FF733124FF - 702E21FF6D2B1EFF6B291CFF682619FF662417FF662114FF470B00FFC9C2C0FF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDDD9D8FFB19792FF - A7756CFFC18377FFD29488FFD19488FFCF9185FFC08174FFA26A5FFFA07C75FF - C7BCB9FFFBFEFEFFFFFFFFFFB09C97FF9C5E51FFB5776AFFAE7064FFAA6C60FF - AA6B5FFF8D4E41FF9F8681FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFE2E0DFFF623026FF7B3629FF7C3B2EFF783629FF753326FF - 723023FF6F2D20FF6C2A1DFF6A281BFF6A2719FF581204FF623B32FFF9FAFAFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - DCD7D6FFB09590FFA67469FFBF8175FFD09285FFCE9185FFCC8E82FFBD7E72FF - 9E665BFFA7867FFFBDA7A2FFA16E64FFB7776BFFB4766AFFB07266FFAD6F63FF - A66559FF84564DFFECEBEBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFB8ACA9FF5A2015FF793527FF7D3A2DFF773528FF - 743225FF712F22FF6F2C1FFF6F2B1EFF631C0FFF450D02FFC8C0BEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFDAD5D4FFAE938DFFA47167FFBD7F73FFCD8F82FFCA8D81FF - C88A7EFFB8796DFFAD6E62FFBA7C70FFB97C70FFB5786CFFB27468FFB37467FF - 8A4F43FFBEB1AEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB7ABA8FF612F25FF652215FF732E21FF - 753124FF722D20FF692416FF551104FF511F15FFBAAFACFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD8D2D1FFAC8F89FFA36E64FFBC7D71FF - CA8B7FFFC6897DFFC28579FFBE8175FFBA7D71FFB77A6EFFB7796CFFA46457FF - 96756EFFFCFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0DEDDFF967E79FF6F443BFF - 612E23FF5F2C22FF6A3F36FF917974FFE1DFDEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD5CFCDFFA88A84FF - A16B61FFBB7C6FFFC6887CFFC18478FFBC8073FFBA7D71FFB9796CFF8C584EFF - DCD8D7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9FBFBFF - E5E3E3FFE6E4E4FFFAFCFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - D1C9C7FFA4837CFFA0695EFFBB7B6FFFC38377FFBE7D70FF94564AFFAF9C98FF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFCFFFFFFCAC0BDFF9F7B74FF9F6E65FF9B6F66FFA7908BFFF5F6F6FF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFF1F6F6FFC5C0BFFFDEDEDEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF00000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000028000000200000004000000001002000000000008010000000000000 - 0000000000000000000000008C3434FF8C3434FF902C2BFF90656FFF41A7D7FF - 2F9FD4FF555569FF932C2AFF8E3536FF8E3536FF8D3435FF8F2E2EFF90626BFF - 4EA6D2FF217BA4FF41637AFF4D96B9FF61343AFF872E2CFF82302FFF892F2CFF - 4C7897FF2F627FFF3C7FA3FF2196CAFF4F5F7AFF8A2F2EFF8E3434FF8D3435FF - 8E3536FF8E3536FF8E3536FF843231FF833130FF8D2E2AFF717F95FF256E91FF - 317598FF3C7495FF872C2BFF8F3435FF8D3334FF8F2D2CFF9A2C28FF728199FF - 266F92FF365870FF4C7893FF4883A4FF762B2AFF85302FFF823130FF872A25FF - 6C6E82FF2B6D8EFF3B5E77FF396B88FF3888AFFF7A3437FF923233FF8E3536FF - 8E3536FF8E3536FF8E3536FF872A26FF852622FF953C38FF518AA9FF2D5973FF - 486A83FF3F8DB3FF6F3032FF8E2722FF8F302CFF845C65FF784046FF69677AFF - 2385B1FF336F90FF48708AFF4185A9FF7C2A29FF882E2CFF802E2DFF8D2822FF - 767C90FF296787FF507690FF4995BAFF397FA3FF7A3032FF913333FF8E3435FF - 8E3436FF8E3436FF8E3436FF475E78FF6F5A68FF65839EFF2E7395FF3A5E77FF - 41657EFF3E83A6FF397093FF6D525FFF6C7D94FF3384AAFF257DA7FF58404CFF - 6B3E45FF407C9FFF32637FFF3885ACFF475D76FF852F2CFF90322FFF92666FFF - 3F86A9FF3D657DFF549ABDFF4F4C5EFF723031FF853130FF863332FF873333FF - 893434FF8B3434FF8C3435FF2E789DFF2A7BA2FF2A6684FF395C75FF41647FFF - 40647EFF3F627CFF326B8AFF2A7CA3FF2A6A8BFF375870FF447897FF3586ADFF - 8B2924FF778197FF256788FF405F77FF2E799EFF38789DFF4B7E9FFF347CA0FF - 2D5771FF4C738DFF3A86ADFF7D2B2BFF953130FF893736FF873938FF883938FF - 883938FF883938FF883938FF42647DFF3E5F79FF406079FF486E89FF437694FF - 3D7393FF3E6884FF43617AFF40617AFF3F627CFF42647EFF5996B4FF4D6178FF - 914B4EFF3E8EB4FF3D627AFF4A88A9FF366A88FF36617CFF30607CFF456B84FF - 4384A6FF39647FFF4190B7FF4A5870FF912A28FF903F3EFF914644FF914543FF - 914543FF904543FF904543FF3D637FFF44657EFF548BA9FF4491B6FF437B9BFF - 437A9AFF3284ACFF2D7497FF40637CFF406782FF4E7994FF4B92B4FF762D30FF - 933536FF466F90FF348FB8FF486C88FF3A799EFF2F6785FF54728AFF509ABDFF - 456884FF347FA5FF388EB5FF684D57FF9C4945FF9C5553FF9C5452FF9B5451FF - 9C5452FF9C5553FF9D5653FF45657DFF5A95B3FF4D89A7FF835B5DFFA45C56FF - A9615AFF9C6766FF5C87A3FF2A789BFF41647CFF497590FF3D8EB4FF874B4CFF - AB463FFF9C4E4CFF79565DFF9A3C36FF986A70FF2E88B1FF4695B9FF52768EFF - A94C43FF976667FF856266FFAE645EFFA96763FFA45F5CFFA5605DFFA76360FF - A45F5CFF9E5351FF984A49FF689AB4FF5095B5FF9B6865FFC47C74FFB77873FF - A8605DFF9E4543FF993634FF4F7C9BFF2F6886FF426079FF4287A9FF4980A0FF - 776372FF8E4B4EFFAB5652FFB67570FFB97B77FF6B889DFF4E809BFF88696DFF - B7706BFFAF6561FFAA5A56FF9C4E4DFF944040FF913A3AFF944040FF913C3CFF - 8F3738FF974444FFA05352FF5ABAE0FF6C7D8DFFC3766EFFA9605FFF913A3AFF - 86292BFF8C3234FF9E3F3CFF997C85FF3582A3FF426780FF466E88FF377592FF - 2E81A5FF3A8BB0FFA57977FFC98D86FFBF837EFFAA5651FF983936FF943837FF - 8D3435FF8A2F30FF892D2EFF882C2EFF882C2EFF882C2DFF8B3032FF9C4B4AFF - B67572FFC28984FFC18984FF4AC2ECFF6A5864FF95302EFF892E2FFF9D4C4CFF - BB7B78FFCB958FFFD0958EFFC5A2A3FF438DACFF3F6780FF45708AFF456982FF - 4E6D84FF4E9EBFFF98787BFFC47F7AFFB77572FFB16C69FFAD6563FFA65B59FF - A15251FF9D4D4CFF9C4B4AFF9D4C4CFFA25453FFAD6664FFBD7F7BFFC78F8BFF - BA7A77FFA35756FF984545FF4BB7E0FF707D8CFFC77871FFCC9590FFD4A19BFF - C38481FFB16968FFAD5552FFA4838AFF3D87A6FF3F637CFF4A6F89FF5089A7FF - 4E9CBFFF4999BAFF7B393CFF8D2B2CFF86292AFF932E2DFF9B3734FF9C4442FF - 9E4F4FFFA35654FFA65B59FFA95F5DFFA95E5DFFA45756FF9E4E4EFF964242FF - 8A3031FF86292BFF85282AFF508EADFF53A2C0FFD1A7A1FFE3A49CFFB4706EFF - A96563FFB1726CFFC98C82FF87B9CEFF2F6B86FF446A83FF63A1BDFF4F92ABFF - 717E87FFA88C8AFFD59991FFCF908CFFB8706DFF7B8294FF5C819BFF723E45FF - 872222FF872627FF8C2A2AFF872B2DFF872A2CFF862A2BFF85282AFF852729FF - 892E2FFF9A4747FFAD6261FF4F7289FF4391B2FF60A7C1FFA58784FFA66D62FF - A2685AFF9D6E64FF79A2B1FF357F9CFF375C73FF4A748DFF5CA7C3FF633831FF - 7B2A18FF81473CFF7D6967FF9A5D4FFFBFABA9FF46A1C0FF3994B6FF6B94A7FF - C96E64FFAB6C6DFF8A4E53FFA04543FFA04C4DFFA45152FFAF6261FFC17D7BFF - D8A19DFFE5B9B3FFE3BBB4FF45728CFF446B83FF3A7E9BFF3C91AFFF5D95AAFF - 699BAEFF57A1BBFF357B97FF396078FF3F6983FF497994FF4EA1BEFF6D3B31FF - 7F3C2EFF7096A6FF3795B7FF4B7788FF5695ABFF306982FF416880FF50A8C7FF - 98C9DAFF69B5CFFF42A0C1FF9C9497FFB7847CFFAE817AFFA87C74FF9D7169FF - 8C5E54FF733F35FF5E251AFF426A82FF446C84FF456A83FF42718AFF3E7994FF - 3F7B97FF41748FFF48738DFF507E99FF4F7E98FF4E7C97FF4A9ABBFF72A7B8FF - D6D5D5FF50AECDFF325F77FF3981A0FF32667FFF3E6880FF386B86FF315D76FF - 2C6E8BFF2D576EFF56A4C3FF405E6BFF520600FF500E00FF4C0700FF490200FF - 450000FF4A0900FF571F13FF5FA3BEFF4AA2C1FF4088A7FF50809BFF53839EFF - 51829EFF4C7992FF558FAAFF57B0CFFF4593B2FF4A7791FF6197B2FF60BDDAFF - FBF5F3FFB1EBFCFF377C99FF587E97FF61A6C1FF60B2CDFF62B5CFFF439AB8FF - 2D5871FF49718AFF5CAAC3FF4C170CFF5B1506FF5B2116FF66342AFF7B534CFF - 9C827EFFCBC0BEFFEBEAEAFF81BCCDFFABD0DAFF86CEE4FF4698B5FF4B7993FF - 507F99FF65A7C1FF63A3B7FF7E787AFF6C93A1FF45A4C4FF53A8C4FF87969BFF - E5F9FDFF8FD9EEFF3D748EFF6CB0CBFF93CEDFFFEFF3F4FFFFFEFDFFD9F9FFFF - 51ABC8FF42718BFF54ABC9FF8BADB6FFCBCCCDFFE8E5E4FFF7F8F9FFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFF9F7FFFFFFFFFFFFFFFFFF75C4DBFF3B6C86FF - 5A85A0FF66B7D1FF815E57FFB3604FFFB26657FF897877FF7C5F5BFF8E9599FF - 429BB7FF2F6780FF4E7A92FF62AFC7FFAC9E9BFFD8CCC9FFDFD8D7FFFAF3F1FF - BAF1FEFF3A7D98FF4D829DFF469CBBFF6AC4DFFFEDF3F4FFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA6E2F4FF3590B1FF - 53A5C4FF6BB8CFFFDAD3D1FFB08E88FF9A5E52FFA95E4FFFB25E4CFF85ABB8FF - 3391B1FF3A667FFF4D7B94FF5CA4BBFF6D2B1DFF723023FF692B1FFF763C30FF - B2DAE3FF3F87A3FF557F99FF5D9DBAFF5DBDDAFFE7F3F5FFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE4F3F8FF8DCADBFF - 87C5D6FFB2CBD3FFFFFFFFFFFFFFFFFFD2C7C5FF9E746CFF985446FF936963FF - 6D8E99FF51A9C3FF3C677FFF54ABC9FF6C6565FF893220FF812B19FF80493EFF - 70BACFFF457992FF74BBD5FF6BB3C6FF646E72FF8F726DFFA58F8BFFDED9D8FF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFDFF - FFFFFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F9F9FFBFAEAAFF945C51FF - 9F4330FF8CA8B2FF367B95FF3F6D88FF4AA5C2FF678994FF798990FF64A9BEFF - 3D7D97FF5D8EA9FF60B5CEFF5C2A1FFF641606FF571103FF4F0900FF4F1409FF - AE9C98FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBE9E8FF - B59E99FF66AABFFF376178FF49758FFF3B6882FF397A95FF367F9AFF457A94FF - 5286A1FF497590FF4FA4C2FF55565AFF6C1E0EFF672417FF642114FF5C1507FF - 4A0C01FFD2CBC9FFFFFFFFFFFFFFFFFFF3F1F0FFBFA7A3FFCDBBB7FFF2F3F3FF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FDFFFFFF55B2CEFF4C9EBCFF5FAFC8FF4BA3C0FF36657EFF4F758EFF6EB7D2FF - 65BED7FF47A0BDFF56B2D1FF545E64FF6C2111FF682619FF652215FF632013FF - 500900FF8B6D66FFFFFFFFFFFFFFFFFFC9B8B5FFB9776AFFC6887CFFB58980FF - CBB9B5FFF2F2F2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - D7C6C2FF866D6AFF6A8690FF884E42FF887E7EFF3E93B0FF5B98B3FF7EC5D9FF - E2DBD9FF6B7175FF555B60FF6F2719FF6F2D20FF6A281BFF672417FF642114FF - 591204FF73483FFFFFFFFFFFFFFFFFFFE1DAD9FFB2847BFFC98A7EFFD39285FF - C18377FFB2857CFFC9B7B3FFF1F1F1FFFFFFFFFFFFFFFFFFFFFFFFFFF6F7F7FF - 9C7269FFA75F51FFA85D4EFFA05647FF966A61FF6FC5DEFF4FAECAFFAECDD5FF - FFFCFAFF783D30FF7A2A1AFF763427FF712F22FF6D2B1EFF69271AFF662316FF - 5B1406FF73483FFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEEFFC7B2ADFFB7877DFF - C7887CFFD08E81FFBD7F72FFAE8178FFC5B3AFFFF1F1F1FFFFFFFFFFC2B0ADFF - A06154FFAE6F63FFA9695CFF8D5044FFCEC1BEFFF4F7F8FFE2E7E8FFFBF9F8FF - FFFFFFFF967670FF70291CFF7A382BFF743225FF702E21FF6C2A1DFF6A271AFF - 520B00FF947873FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEDECECFF - C5B0ABFFB5847AFFC38478FFCB897CFFB8796DFFAC8077FFC2ABA6FFAB796FFF - B47468FFB07266FFA15E51FFA5857EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFE7E4E4FF74443AFF6F291BFF773325FF743123FF6E2A1CFF5D1507FF - 5E2A20FFE5E2E1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFEBEAE9FFC3ACA7FFB28076FFC08073FFC58477FFB8786CFFBB7C70FF - B87B6FFFB37366FF976258FFE7E4E3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFE4E0DFFF8B6760FF6A3024FF642518FF61251AFF7A524AFF - DBD6D4FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFEAE8E7FFC0A8A3FFAE7A70FFBA7A6EFFC28276FF - BE7F72FFA36356FFC0ADA9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE1DDDCFFCEC4C3FFDED9D8FFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE7E4E4FFBCA09BFFAE7A6FFF - AB7267FFAA8982FFF8F9FAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC9C1BFFF - B9ABA8FFF4F7F8FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000002800000010000000200000000100200000000000 - 40040000000000000000000000000000000000008A231FFF933937FF3995BFFF - 5B5264FF9A231DFFA72017FF5E7C98FF2C7FA3FF645765FF8E1C14FF6A4E5AFF - 29789DFF40789BFF912F2EFF8F3536FF8E3536FF822B29FF765964FF317495FF - 4C6882FF873B3CFF745460FF4D627BFF31799DFF595B6EFF972117FF6F5D6BFF - 3B7FA0FF526981FF8A2D2DFF8C3334FF8D3334FF386E8EFF366C8BFF366A88FF - 336C8DFF376C8AFF307C9FFF645A6CFF4A6F8AFF297296FF4B6680FF3A7595FF - 397B9EFF803032FF8D3835FF893A39FF8A3B3AFF406984FF4C7C98FF6B7789FF - 527791FF336480FF487D9BFF8A4A4FFF696172FF516880FF2E86ABFF5C788FFF - 5B778DFF945656FFA35955FF9B5350FF964B49FF549CBCFF957275FFB14D45FF - A64844FF5A7389FF367493FF516F88FF956062FFC67D75FF737686FF9C5452FF - A74843FF963F3FFF954343FFA65C5AFFAF6A67FF5898B5FFAD5C58FFB56B6AFF - CC736BFF938E98FF356C89FF378CB1FF6E8A9FFFB85852FFB0443CFF9A3E3DFF - 984141FF9C4B4BFFA65B59FFA15452FF943E3EFF3E8DAFFF9BA6AFFFC48176FF - AA7E79FF537F95FF437E9AFF626368FF9A6A64FFAE7875FF58839CFF945F63FF - 9C4646FF9F494AFF9F4C4DFFAB605FFFBA7976FF376682FF30708EFF54879CFF - 49859EFF36708FFF4684A1FF8E7F7DFF5F8798FF3B7991FF257292FF4D8AA4FF - 5A95ACFF77443DFF732F23FF6E362AFF6D3B30FF83BBCEFF66A5BDFF417693FF - 53869FFF648A9AFF458CA9FFB2DEEBFF76B1C7FF62A1BAFFB8E8F6FF63A1BAFF - 3D7C95FF7B6A68FFA38681FFC8B9B6FFE8E4E3FFFFFFFFFFEFFFFFFF3991B2FF - 81AEBDFFBC7E6EFF97594CFF638F9FFF3284A5FF7F898EFFBA6652FFB0BCBFFF - 428FAFFF7ED3EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDBF3F9FF - EFF9FBFFFFFFFFFFD0B0A9FF946E67FF51889FFF41748BFF674D48FF527A8CFF - 5CA1BBFF654A45FF6E362EFFC5B6B3FFFFFFFFFFFAFAFAFFDDCAC6FFFDF1EEFF - FFFFFFFFFFFFFFFFFFFFFFFFF7F2EFFF5C96AAFF42809AFF3681A0FF5EA5C0FF - 4F92AAFF65281CFF590700FF5B1D10FFE1DCDBFFF1F3F3FFBF9289FFBA796DFF - CEABA4FFEFECEBFFFFFFFFFFDDCFCCFF93594EFF916A63FF71BDD5FFCEDDE0FF - 743C31FF722C1FFF6D2A1DFF5B1609FFBDAFABFFFFFFFFFFFBFEFEFFDFCCC8FF - BD867BFFB17266FFCAA9A2FFB6847AFF9F5243FFD2BDB9FFFFFFFFFFFFFFFFFF - A47C74FF601708FF550900FF7E5047FFF5F5F5FFFFFFFFFFFFFFFFFFFFFFFFFF - F9FDFDFFDBC7C3FFB88075FFB57163FFB99189FFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFCDBDBAFFC0ADA9FFFAFBFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFF2F5F6FFB08F88FFE9E5E4FFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000280000001800000030000000 - 010020000000000060090000000000000000000000000000000000008A3334FF - 8C2B2AFF925257FF3DA3D2FF2E84AFFF843133FF933334FF8D3233FF962B28FF - 807485FF2A89B3FF3A6D89FF507A96FF7A2826FF872C29FF7F4146FF387293FF - 327192FF2485B4FF70444EFF932F2DFF8D3435FF8E3536FF8E3536FF892B27FF - 8E2119FF7B6876FF2B6D8EFF367FA3FF6D424CFF95251FFF92302EFF9C312CFF - 5F7A95FF216D90FF457793FF546D85FF88241FFF802D2BFF8D3735FF467795FF - 385F78FF3891B9FF59596EFF962A27FF8E3536FF8E3536FF8E3436FF644652FF - 7F525BFF4C7D9AFF325B75FF447997FF426E8CFF7C4146FF687388FF3D7191FF - 5B4856FF456B89FF367392FF3C799CFF75363AFF932A24FF88636EFF397695FF - 4F86A4FF4F657DFF73373AFF8B302FFF8A3333FF8B3334FF8C3334FF2E7A9FFF - 2F769AFF325E79FF42627BFF42617AFF366B8AFF2F799EFF2E6583FF387B9CFF - 4B6680FF8D5256FF337291FF396884FF357295FF4C708DFF387292FF3D657FFF - 4281A4FF7F292AFF922F2DFF863A39FF873938FF883938FF893938FF3E5E78FF - 426079FF477E9EFF3A80A3FF317BA0FF346A89FF415F77FF42647EFF508EADFF - 70444EFF6A6378FF2D84AAFF3E81A5FF2D6888FF3B647EFF4783A3FF31769AFF - 2F8DB6FF644856FF9B3C38FF954C4AFF954B49FF954C4AFF964D4BFF476880FF - 4E8FAFFF60798EFF8A6569FF8B6D73FF57839FFF2F7192FF476A83FF438BAEFF - 8D403FFFA1413EFF676678FF844A4EFF4D7C9AFF3591B7FF64778CFF885F65FF - 6D7382FFA25E5AFFA6615EFFA4605DFFA4605DFF9E5653FF994D4BFF58A4C4FF - 6E889AFFC27368FFB96A64FFA34541FF9B332FFF4E728EFF346480FF417A9AFF - 4E7F9CFF775E6BFFA7534EFFC7756BFF998086FF52758DFF946060FFB65E57FF - AA504AFF9A4847FF8F393AFF8F3838FF913B3CFF9D4E4DFFA8605EFF4AAFD5FF - 8B5F63FFA24340FF8D3536FFA05151FFBC6861FFA3929AFF357998FF476A82FF - 396A85FF308AB0FF8B8A94FFD28F87FFBC736DFFAA4B46FF9A4140FF90393AFF - 8E3435FF8E3536FF943F3FFFA35554FFBA7A76FFBD807CFFB4726FFF43AED5FF - 8E6569FFBF6C66FFC88C88FFC58885FFC5726BFFA2919AFF357796FF486982FF - 4D84A2FF4BA2C5FF79626CFF9B3836FF973938FFA74743FFA34E4CFFA25655FF - A65A58FFA85E5CFFAB6260FFAD6563FFA15352FF892E2FFF812224FF4190B3FF - 77A9BDFFE9ABA0FFBE726AFFA95B52FFBD8179FF6DA2B8FF31607BFF5A94B0FF - 578496FF837070FFC07F75FFCF7C72FF997F87FF50829FFF7B3E43FF962521FF - 932927FF8A292BFF87282AFF87282AFF903335FFAB5B5BFFC37E7CFF4A6E87FF - 3D87A7FF5D92A7FF878082FF8B8788FF5D94A8FF2E6A85FF40657DFF539CB7FF - 6A291BFF7D3A2DFF5D7F8DFF7D7373FF639FB4FF2A7D9FFF7FADC0FFB5A7ACFF - 6B7D8FFFAE7270FFB77673FFB87E7AFFC08E89FFBC9088FFA77A72FF436B84FF - 41667EFF3A728EFF3E87A6FF418AA8FF3C708CFF4A7791FF4C7791FF4690B0FF - 85959CFF9BBDC9FF2D81A3FF367F9DFF326983FF32637EFF327592FF377B96FF - 43A1C2FF585454FF641B0CFF56180AFF490800FF440400FF4C1206FF5BA5C0FF - 4EA0BDFF4483A0FF527D97FF517C96FF5695B1FF56A6C2FF428DACFF4C95B5FF - 93CDDFFFFDFFFFFF458CA8FF5686A0FF74B9D0FF88C8DCFF4D96B1FF325D77FF - 599DB5FF68372CFF783F33FF825A52FFA08480FFCCC0BEFFEBE9E9FFD2E2E6FF - E3F2F5FF80C8DDFF3A6B86FF5E9AB6FF6A8F9BFF9E5E51FF7F898FFF5D8796FF - 939EA3FF7CBED1FF447D97FF6FB0C6FFDFE1E1FFFFFFFFFFF1FFFFFF529AB5FF - 4587A3FF77BFD6FFD9F2F9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFD0F2FBFF318BACFF4EA6C5FFB3BCBFFFBD877BFFA15343FFAF5440FF - 74A2B2FF1C7295FF477994FF658D9BFF833624FF763B2FFFAD867CFF77BED4FF - 45728DFF45A8CBFFA7DDECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFF9FCFDFFB2DDE9FFAFD2DCFFFEFFFFFFFFFFFFFFCDBDB9FFA06A5FFF - 88564DFF7295A1FF41849FFF4D94AEFF745955FF843423FF747A7DFF4F95B0FF - 6AAFC8FF657E85FF714D46FF88645EFFCDC3C0FFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F4F5FF - BF948AFF998784FF408099FF3A6A86FF428AA5FF5290A3FF478BA6FF497995FF - 55A1BCFF5D2B20FF631100FF520800FF470500FFC2B5B2FFFFFFFFFFFFFFFFFF - E2D8D6FFCCB7B2FFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFA7DBEAFF3992B1FF5BA0B7FF3A7892FF416B85FF6AB0CAFF57A7C2FF - 49A7C6FF5F4A48FF6C1E0FFF652316FF560B00FF6E3F35FFFCFEFEFFFFFFFFFF - BA978FFFBD7264FFBF8A7FFFCBB3AFFFEEEDEDFFFFFFFFFFFFFFFFFFFFFFFFFF - FCFFFFFFA8817AFF817170FF965244FF618C9BFF3A9CBFFFB3E0ECFFB2A09CFF - 593D3AFF72291BFF6D2B1EFF682518FF60190BFF632A1FFFE8E9EAFFFFFFFFFF - EAE5E4FFC5A49DFFC28478FFC17B6DFFBA857AFFC7B0ABFFEDECECFFFFFFFFFF - CCBCB9FF9D594CFFB06252FF995547FFC3CCCFFFA5D7E4FFF1F8FAFFC7ADA8FF - 6B1807FF79372AFF702E21FF6B291CFF5F1608FF69352BFFF5F7F7FFFFFFFFFF - FFFFFFFFFFFFFFFFE2DBDAFFC4A29BFFBD7E72FFBB7466FFB58176FFC5ACA7FF - AF7D73FFB37265FF9D5A4CFFB79F9AFFFFFFFFFFFFFFFFFFFFFFFFFFF6F7F7FF - 875D54FF661C0EFF6E2719FF641C0DFF4D0700FFB29F9BFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0D9D7FFC09C95FFB7776BFFB77062FF - BF7F72FFB67366FFA27268FFF6F7F7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - F6F7F7FFA88D88FF805148FF82574FFFBFB0ADFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDED5D3FFBC968EFF - B77C70FFA76D62FFD8CDCBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCFFFFFF - B7A29EFFC4B5B2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000028000000 - 8000000000010000010020000000000000080100000000000000000000000000 - 000000008C3334FF8D3536FF8D3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8D3536FF8D3435FF8D3435FF8D3435FF8C3435FF923F40FFA65655FF9A7078FF - 7E7A8CFF6A7E98FF677E99FF67819CFF65809DFF657D98FF627995FF6C6E84FF - 725868FF7A373BFF802928FF873335FF8E3435FF8C3334FF8D3435FF8E3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8C3334FF8D3536FF923E3FFFA14340FFAB4A44FFA35A5AFF8B7985FF6B9FBEFF - 58B3DEFF52B0DCFF3CAADCFF2894C7FF1D688CFF294557FF385A72FF3E6580FF - 426984FF688397FF81B3CDFF51B4E1FF2692C4FF1E678AFF4E2324FF6E2827FF - 7F302FFF823231FF82302FFF813130FF833230FF833231FF833230FF833230FF - 833231FF833230FF833231FF81302FFF823232FF8C312DFF5A809EFF2CACE5FF - 288FBFFF1F6A8DFF2C485BFF395D75FF3E6681FF406882FF516F86FF5199BEFF - 37A8DDFF2A98CBFF2890C1FF2895C8FF368CB7FF5C5F75FF7C383CFF8D241EFF - 8E241EFF872F2EFF883334FF893334FF8A3333FF893334FF8A3434FF8A3434FF - 8A3434FF8A3334FF8C3435FF8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3435FF8E3435FF8D3435FF8D3435FF8D3435FF8D3435FF8D3435FF - 8E3435FF8E3536FF8E3536FF8D3435FF8D3638FFA04C4BFFA08791FF5FB8E1FF - 42A9D9FF38A6DAFF35A6D9FF35A5D9FF34A5D9FF34A5D9FF33A5D8FF31A3D8FF - 2C9DD2FF287EA8FF5A363DFF7F2B2AFF8C3536FF8D3435FF8D3334FF8D3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8D3435FF923D3DFFA2595AFF96808CFF7398B4FF5AA7CFFF4AADDBFF40AADBFF - 3AA5D7FF329FD2FF2994C6FF1F7EA9FF1F5671FF2F495CFF3B617BFF3D6580FF - 4B6F88FF7693A6FF73BBDDFF3CA5D7FF1C80AEFF37404EFF66201DFF7B2F2FFF - 813130FF823130FF813130FF823231FF833231FF833230FF833230FF833231FF - 833231FF833230FF833231FF823130FF823231FF893330FF81575FFF3EA8D9FF - 2C9CCFFF207BA5FF254E65FF355369FF3D6580FF3F6681FF486A82FF4E809DFF - 31A2D7FF2797CBFF2996C9FF2B9ACDFF2A9CD0FF299CD0FF3092C3FF417B9FFF - 5B5467FF723134FF822E2FFF8C3435FF8E3536FF8D3435FF8D3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8B3434FF893333FF873332FF873333FF883333FF893333FF8A3334FF - 8B3334FF8B3434FF8C3435FF8C3334FF903C3DFFAC605EFF89ACC4FF47B4E4FF - 2D9DD0FF2998CBFF2996C9FF2997C9FF2997CAFF2A98CBFF2A99CCFF2A9BCFFF - 2B9CD0FF1D8BBCFF334F66FF722422FF893435FF8E3536FF8D3435FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8C3436FF9B4343FFA18088FF62C0EAFF3EAFE2FF30A4D8FF2B9ACDFF2690C1FF - 2187B5FF1F7BA6FF1E6C90FF1E5773FF29485BFF37586EFF3E667FFF3E6680FF - 57778DFF7CA3B9FF5EB9E4FF2A97C9FF206789FF522424FF752A28FF823231FF - 823130FF81302FFF823231FF833130FF833230FF833231FF833230FF833230FF - 833231FF833231FF833231FF833231FF81302FFF823535FF933C38FF658CA9FF - 33AAE0FF2489B8FF215C79FF304B5EFF3C627CFF3E6580FF416782FF426680FF - 317498FF23789FFF227EA9FF2388B6FF268FC0FF2895C7FF289ACFFF259ED4FF - 1B93C6FF335973FF722524FF893334FF8D3435FF8C3334FF8C3536FF8D3435FF - 8D3435FF8D3435FF8D3435FF8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF833130FF833130FF833131FF843231FF843131FF853231FF853232FF - 863231FF863232FF873333FF873232FF934141FFAA7577FF6FB8DBFF34A3D7FF - 2284B1FF207094FF206D92FF207096FF207298FF20749BFF2178A0FF2588B6FF - 2A9BD0FF2693C5FF246586FF622728FF842F30FF8E3435FF8C3334FF8C3435FF - 8D3435FF8D3435FF8D3435FF8D3435FF8D3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8C3536FF9F4745FF988A96FF51B7E4FF2D9ACDFF2284B2FF1E7096FF1F6282FF - 225670FF254D63FF2A475BFF30495CFF37576EFF3D647DFF3F667FFF406882FF - 627E92FF79ADC7FF4DB2E1FF218DBEFF2B5169FF62201EFF7C302FFF833130FF - 81302FFF823130FF833130FF823231FF833230FF833230FF833230FF833230FF - 833231FF833231FF833231FF823230FF823130FF813232FF8E3A36FF886F79FF - 43B0E1FF2894C6FF1E688BFF2C485BFF3A5E77FF3F6680FF3E6680FF3D647EFF - 38576EFF304F64FF2B5169FF275974FF236484FF22749AFF258CBCFF2C9CD2FF - 2692C4FF236485FF642728FF863131FF8E3536FF8D3435FF8D3435FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF833230FF823230FF823230FF823130FF823130FF833130FF833131FF - 833130FF833231FF833131FF833232FF984947FF9E8B94FF59B9E5FF2995C7FF - 1F6587FF263F4FFF2B485AFF2D4B5FFF2C4B5FFF2B4B5FFF2C4E63FF366581FF - 389FD0FF2E9ED2FF1E77A0FF503138FF7F2A29FF8D3536FF8D3435FF8D3334FF - 8E3536FF8E3536FF8D3536FF8D3536FF8D3435FF8D3435FF8D3536FF8D3435FF - 8D3435FF8D3435FF8D3435FF8D3535FF8D3435FF8D3435FF8E3536FF8C3334FF - 8D3637FFA04947FF958B99FF4AB5E4FF2690C2FF20607FFF264051FF2D485AFF - 324D60FF375469FF3A5B73FF3D627CFF3F6580FF406681FF3F6580FF436A83FF - 688497FF72B1D0FF42ACDDFF1E86B5FF364353FF6B221FFF803131FF823130FF - 81302FFF823231FF833130FF823230FF823231FF833230FF833231FF833231FF - 833230FF833231FF833231FF833231FF833130FF813130FF8B3B39FF986467FF - 56AFD9FF2C9BCFFF1E6F95FF29485CFF385A72FF3E6581FF3D6580FF3E6580FF - 3D647EFF3B6079FF395A71FF375469FF334F63FF335167FF3D7494FF3BA6D8FF - 2998CBFF22678AFF602729FF852F30FF8E3536FF8D3435FF8D3334FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF833130FF833231FF833231FF833230FF823231FF823230FF823230FF - 823230FF823130FF82302FFF833535FF9F5552FF8DA1B4FF48B3E3FF2388B8FF - 205671FF2D4658FF385C74FF3A5E76FF3A5D74FF3A5C73FF3A5E75FF4E6D83FF - 55A3C8FF37A7DAFF1E84B2FF3C4252FF752422FF893435FF8D3435FF8D3435FF - 8D3435FF8D3436FF8E3435FF8E3535FF8E3536FF8E3536FF8E3436FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF8D3435FF8C3434FF - 8C3638FFA24B49FF9290A0FF48B4E4FF248CBCFF205773FF2F4557FF3B5E78FF - 3F657FFF3F6681FF406782FF406782FF406681FF416682FF406681FF456B84FF - 6A869AFF6CB2D4FF3CA8DAFF1D81AEFF3D3C49FF712522FF833232FF833131FF - 823130FF823130FF823231FF833131FF833130FF833231FF833231FF833230FF - 833231FF833231FF833230FF833231FF823231FF82302FFF8A3B39FF9B6667FF - 5DB0D8FF2F9ED1FF1E7299FF27485CFF375870FF3F6681FF3E657FFF3E6680FF - 3F6681FF406782FF406782FF406782FF3D647EFF466881FF668AA1FF55B2DDFF - 2A9ACDFF22688BFF612629FF863030FF8E3536FF8D3435FF8D3435FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF833231FF823230FF833230FF833230FF833130FF833230FF833230FF - 833231FF833131FF81302FFF873A39FFA46564FF7AB1CEFF3BA9DBFF1F7EA9FF - 234E65FF335166FF3E6681FF3F6681FF3F6681FF3E6681FF3F6782FF54738AFF - 609BBAFF40ADDFFF228EBFFF2B5670FF672220FF813131FF883333FF883333FF - 893333FF8A3334FF8B3434FF8B3334FF8B3435FF8C3435FF8C3435FF8D3435FF - 8D3435FF8D3435FF8D3535FF8D3435FF8D3435FF8D3435FF8E3536FF8D3435FF - 8D3638FFA34B4AFF8F91A2FF46B4E5FF248BBBFF205A77FF314C61FF3E6580FF - 416883FF3F6681FF406681FF406681FF406681FF406681FF3F6680FF456A85FF - 68859AFF66B0D3FF3AA6D9FF1D80ADFF413B47FF732624FF853333FF853231FF - 843131FF833130FF823130FF823230FF823231FF823230FF833231FF833231FF - 833230FF833230FF823230FF833231FF823130FF813130FF8A3B3AFF9C6869FF - 5FB1D9FF2F9ED1FF1D7198FF27485CFF385970FF3F6781FF406680FF406681FF - 406681FF406681FF406681FF406782FF406882FF54758DFF779BB1FF5AB5DFFF - 2A99CCFF22688BFF612729FF863030FF8E3536FF8D3435FF8D3435FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF823130FF823130FF833231FF823231FF833231FF833231FF833231FF - 823231FF833231FF823130FF8E413EFFA3797CFF67B8DFFF319ED0FF1D7097FF - 27475BFF37576FFF3E6580FF3D647FFF3E657FFF3E6580FF3E6580FF4C6D85FF - 618EA8FF49AFDFFF2996C8FF21688AFF582526FF7A2D2CFF843231FF843131FF - 853231FF853232FF863232FF863232FF863232FF873232FF873333FF883233FF - 883333FF883333FF883233FF893535FF8B3535FF8B3434FF8B3435FF8B3334FF - 8B3637FFA04947FF8E8E9FFF46B3E4FF258FC0FF246282FF354D60FF3F5F77FF - 41617AFF3F637DFF406681FF406782FF416782FF416782FF406681FF446984FF - 638095FF64ACCFFF3BA8DBFF1D85B3FF3D4150FF742523FF863434FF873232FF - 853231FF843131FF833130FF823130FF823130FF833230FF833231FF833231FF - 823230FF823131FF833131FF833231FF833231FF813030FF8B3D3CFFA06D6FFF - 61B3DAFF2E9CCFFF1D6E93FF29485AFF395B72FF406681FF3F6680FF3F6580FF - 3F6680FF426882FF466A84FF4C6D86FF527188FF667E91FF7A9EB5FF54B3DFFF - 2998CBFF22688BFF602729FF852F2FFF8D3435FF8D3435FF8D3334FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF823130FF833130FF823130FF833230FF823130FF823130FF823231FF - 833231FF823130FF823332FF974946FF9A8E98FF55B9E6FF2993C4FF1E6283FF - 2B4659FF3A5E77FF3F6580FF3D647FFF3E667FFF3F6681FF3D6580FF466A84FF - 5F8299FF53ADD8FF309ED1FF1D79A2FF493037FF742725FF823232FF823130FF - 823130FF833130FF833231FF833131FF833130FF843231FF843231FF843131FF - 843131FF843434FF8A3C3CFF964442FF914241FF822E2CFF7E2D2CFF833333FF - 853434FF994542FF8C8796FF47B2E2FF2B98CBFF2688B6FF2D83ACFF3480A6FF - 397393FF3C6782FF3E6178FF3D5E76FF3D627CFF3F6680FF3F6580FF426883FF - 5B788EFF63A4C5FF3EABDEFF208BBBFF344D63FF702423FF873334FF893333FF - 873332FF853232FF843231FF833131FF82312FFF823231FF823231FF823130FF - 833131FF833230FF823230FF823130FF823130FF813231FF914442FFA07F84FF - 5CB7E1FF2A96C8FF1D6789FF2B475AFF3A5E76FF3F6781FF406680FF456B84FF - 4F7188FF59778CFF5E7F94FF628AA2FF6197B4FF60A4C6FF5BAED6FF40AADBFF - 2693C5FF236383FF622628FF863031FF8E3536FF8C3334FF8C3334FF8D3536FF - 8D3435FF8D3435FF8D3435FF8D3435FF8D3435FF8E3435FF8D3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF823231FF823130FF823231FF823130FF833231FF833231FF833231FF - 833231FF81302FFF833635FF9F5552FF8BA1B6FF46B2E3FF2388B7FF205671FF - 2F4A5DFF3C627DFF3E6580FF3E6580FF3F6580FF3E6580FF3E6480FF426883FF - 5A788FFF5BA6CBFF38A6D9FF1E85B4FF394151FF6D2320FF803232FF823130FF - 81302FFF823231FF823130FF823230FF823230FF823130FF823130FF823231FF - 863A39FF934543FFA05B58FF937F89FF589ABEFF3E6D8CFF652827FF762927FF - 7F3333FF923A36FF7E7281FF2DAEE5FF2796CAFF2A97CBFF2A9DD2FF2AA0D6FF - 2A9ED3FF2C96C8FF2F88B4FF306D8DFF33556BFF3A5E75FF3F667FFF406781FF - 527187FF6096B3FF43AEE0FF2593C5FF266282FF632525FF833031FF8B3434FF - 883232FF853231FF843131FF823130FF823130FF823230FF833231FF833231FF - 823230FF823130FF823130FF833231FF833130FF843636FF9F5350FF9999A7FF - 4EB6E5FF258CBDFF1E5C7AFF2E495BFF3B627BFF3E6680FF456B84FF5F7E94FF - 6F96ACFF66A3C2FF58AAD1FF4DAEDCFF43AEDFFF3AA8DBFF32A0D3FF2995C7FF - 1A82B0FF27536DFF6A2423FF873233FF8D3435FF8D3435FF8D3435FF8E3435FF - 8E3536FF8D3536FF8E3536FF8D3536FF8E3536FF8D3536FF8E3435FF8D3435FF - 8D3435FF8D3435FF8D3435FF8E3435FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF7D2F2EFF803131FF823231FF823130FF823130FF823130FF823130FF - 813130FF823433FF8B403FFFAA6662FF7AB1CFFF3AA8DBFF1F7CA7FF234C62FF - 335166FF3D6580FF3F6681FF3E6480FF3F6680FF3F6681FF3D647FFF3F6782FF - 527188FF5F99B8FF40ABDDFF228FC0FF2C556EFF661E1AFF7C3030FF813332FF - 823130FF823130FF823230FF823130FF823130FF823130FF833636FF8F413FFF - A05551FF997C84FF76A8C6FF51B7E5FF35ABE0FF2095C9FF29688AFF5A2B2DFF - 742724FF833331FF7E3B3DFF43627CFF26749AFF2082AFFF208DBEFF2493C6FF - 2897CAFF2B9BCFFF299CD1FF228BBBFF235C79FF324E62FF3C637EFF3E6680FF - 476B83FF5B849CFF49ACDAFF2E9CCFFF1D7DA8FF473741FF742421FF823233FF - 833130FF823130FF823130FF823230FF833231FF833231FF823231FF823130FF - 823130FF833131FF853231FF873332FF873333FF924444FFAD706FFF7BB6D4FF - 3CA9DBFF1F7FABFF224F67FF334E62FF3D647FFF3E6680FF55768CFF7B9DB2FF - 6BBCE2FF45AFE0FF34A3D8FF2B9ACEFF2492C4FF1E89B9FF1C7CA8FF1F6A8DFF - 2A4E66FF4F2D32FF762928FF873233FF8A3334FF8A3334FF8B3434FF8C3434FF - 8D3435FF8D3435FF8D3435FF8E3435FF8D3435FF8E3435FF8E3536FF8E3536FF - 8D3536FF8D3536FF8D3536FF8D3536FF8D3435FF8D3536FF8D3435FF8D3435FF - 8D3536FF6E2927FF792724FF7D2F2EFF803232FF823131FF813332FF833736FF - 8A3D3BFF964643FF9F5D5BFF998B95FF5DB5DEFF2E9BCEFF1E6E93FF284659FF - 375970FF3E6681FF3E6580FF3D6580FF3F6581FF3F6581FF3E6580FF3E6680FF - 4B6D85FF618BA5FF4AAEDCFF2998CCFF24749AFF57363CFF792825FF812925FF - 7F2E2DFF803232FF813231FF823130FF823534FF8A3E3EFF9B4E4BFF9F7072FF - 81A0B7FF58B7E3FF3DADE0FF2D99CBFF2793C5FF2A94C5FF228EBFFF246D91FF - 552E32FF732623FF7A2E2CFF77241FFF622929FF4F363FFF3C4C5FFF2D627FFF - 24759CFF218FC0FF2A9BD0FF2792C3FF1F6A8DFF2B485BFF395D76FF3E6580FF - 416883FF527188FF4E9CC4FF33A5DAFF248FC0FF266485FF5C2221FF752B2AFF - 803131FF823130FF81302FFF823130FF833230FF833230FF823230FF82312FFF - 833131FF853232FF883333FF8C3435FF914041FFAC5F5DFF98A1B2FF52B9E7FF - 2B95C6FF1D6B8FFF284658FF385970FF3E6680FF436A83FF698598FF7DB3CDFF - 4FB2E0FF2996C9FF2080ADFF206C90FF275C78FF33495DFF43353FFF542729FF - 66211FFF752927FF803030FF853231FF853231FF863232FF863232FF873332FF - 883333FF893333FF8A3334FF8B3333FF8C3434FF8D3435FF8D3435FF8D3435FF - 8E3435FF8E3436FF8E3435FF8E3435FF8E3536FF8D3435FF8D3536FF8D3536FF - 8D3535FF2E769BFF4E4E5EFF722A27FF7E2925FF843433FF91403DFF9B4D49FF - 9B6465FF888797FF6BA8C9FF51B4E1FF3BA7DAFF258CBCFF1E5F7EFF2C4759FF - 3A6079FF3F6681FF3D657FFF3E667FFF3E6681FF3E6681FF3F6681FF3D6580FF - 456B84FF5D8096FF50AAD4FF31A1D6FF2692C5FF288CBAFF3B7699FF595161FF - 733233FF7E2824FF802D2BFF853838FF954845FFA26261FF8F909FFF65B5DBFF - 45B2E3FF319ED1FF2389B8FF1E7299FF256686FF288BBAFF2A9BCFFF248EBFFF - 236F94FF532F34FF712724FF7B3233FF7C2E2DFF782825FF722421FF682523FF - 5E2B2DFF564E5EFF329BCDFF2C9FD3FF217CA6FF254E65FF345368FF3D6580FF - 3F6781FF466982FF4D7E9AFF37A6D9FF2C9BCFFF2088B7FF31536AFF66211DFF - 782E2DFF813332FF823130FF823130FF823131FF833231FF823231FF823130FF - 833131FF863232FF8B3535FF924041FFAA5856FFA3939DFF62BBE3FF36A2D5FF - 1F80ACFF21526BFF314A5CFF3D637CFF3F6680FF4D7189FF7A96A8FF71B9DCFF - 3AA5D7FF1D84B3FF304A5EFF512224FF602322FF6B221FFF722623FF792B2AFF - 7D3030FF823131FF833130FF833131FF833130FF833131FF843231FF843231FF - 853231FF853232FF863232FF863232FF873332FF883333FF893333FF8A3333FF - 8B3434FF8C3434FF8C3435FF8D3435FF8D3435FF8E3536FF8E3435FF8E3436FF - 8E3436FF2697CBFF2291C3FF327498FF5F4550FF8B403FFF907179FF7A97AFFF - 5EAED6FF47B3E4FF38A8DCFF2D9ACDFF258CBCFF1C739AFF214E65FF324F63FF - 3D657EFF3F6780FF3E6680FF3F6780FF3F6780FF3F6780FF3F6780FF3E667FFF - 416983FF517085FF4493BAFF279DD4FF2793C5FF2998CBFF269DD2FF2499CEFF - 2E86B2FF4A5F76FF71373BFF953F3AFF987D85FF75ADCBFF4FB6E5FF37A5D8FF - 2891C2FF1F7AA5FF1E5D7CFF26495CFF31495BFF366A87FF2A9ACDFF2B9ACFFF - 258EBFFF226F94FF542F34FF742825FF803433FF833534FF813434FF7F3232FF - 7D2E2DFF8E2F29FF6287A4FF32ACE3FF258BBBFF21607FFF2F4A5CFF3B617AFF - 3F6780FF406882FF47687FFF3D8AB0FF2BA4DCFF2B96C8FF1F85B3FF365369FF - 692522FF7C2B28FF803434FF823434FF833433FF833433FF823433FF843433FF - 883535FF8D393AFF964344FFAC5957FFA1909BFF66BAE1FF3CA8DAFF238BBAFF - 1D6282FF2A4557FF395A72FF406680FF426883FF607D92FF82AAC0FF5BB8E2FF - 2B97C9FF1D6B90FF50282AFF732725FF803232FF823333FF843434FF853433FF - 853332FF843332FF843331FF833331FF833332FF833232FF843332FF843332FF - 843332FF843332FF843332FF853332FF853232FF853333FF863332FF863332FF - 873333FF883333FF893333FF893434FF8A3434FF8B3434FF8B3435FF8C3435FF - 8C3435FF2896C9FF2A97CAFF2599CDFF2597CBFF4299C5FF49B0DFFF3DB1E4FF - 31A2D6FF2892C4FF2183B1FF1E739AFF1E5F7EFF234C63FF2E4A5DFF3A5E77FF - 3F6680FF3F6680FF3E6580FF3F6781FF3F6681FF3F6681FF3F6681FF3E6580FF - 406781FF426781FF3B6580FF2B6D8EFF237297FF2382AEFF268FBFFF2995C8FF - 279ACFFF219BD0FF2E8EBDFF578DAFFF52B3E0FF3DADE0FF2C98CAFF2083B0FF - 1D688BFF224D64FF2C4556FF355368FF3B627BFF3E5F77FF3A708FFF2A99CDFF - 2B9BCFFF248EBFFF236D91FF582D30FF772A28FF833535FF863634FF863635FF - 853736FF91403DFF88727CFF42B0E2FF2C98CBFF1F769FFF284C61FF37566CFF - 3E6680FF3F6780FF406781FF41667FFF328CB8FF299FD6FF2A95C7FF1F8BBBFF - 326685FF64363AFF802A25FF882D29FF89322EFF8B3532FF8F3735FF953938FF - 9B3E3CFFA34A47FFA96768FF909BAEFF5DBAE4FF3AA7DAFF258DBDFF1C698DFF - 26465AFF355267FF3F657FFF3F6681FF4A6F88FF758EA1FF79B9D9FF43ABDCFF - 1E88B7FF2F4B61FF6B2321FF853335FF8B3535FF8A3535FF893534FF883434FF - 873434FF863434FF863434FF863534FF853534FF843533FF853534FF843534FF - 853533FF853534FF843433FF853433FF853433FF853433FF853433FF853433FF - 863434FF863434FF863433FF873434FF873434FF883434FF883434FF883434FF - 893434FF297AA2FF268BBBFF2896C9FF2B9BCFFF2BA2D8FF2D9BCFFF268EBEFF - 1F7DA8FF1E6789FF22546DFF27485CFF2D485AFF345066FF3A5E77FF3F6580FF - 406681FF3F6580FF406681FF406782FF406782FF406782FF406782FF3F6680FF - 406681FF3F6681FF3D617AFF38556BFF324F64FF2D566EFF286888FF247EA8FF - 258EBEFF2994C6FF299CD1FF2BA6DDFF2E9BCEFF2489B9FF1E7298FF205570FF - 284558FF324D61FF3A5E76FF3E6580FF3F6681FF3F6782FF3F6078FF3A7090FF - 2A9BCFFF2A9CD2FF238DBDFF256889FF5F2B2CFF7C2F2DFF873737FF883736FF - 873636FF8E403FFF9B6465FF5AACD3FF31A3D7FF2286B4FF205B78FF314C60FF - 3D637DFF3F6681FF3F6781FF3F657FFF3E637CFF3084AEFF279ACFFF2A96C8FF - 2493C6FF2888B5FF476C89FF675767FF7E4C54FF8A4B51FF8F5258FF905E66FF - 8B727EFF7E8DA3FF66ADD1FF49B5E4FF33A0D3FF2389B8FF1D698CFF25475BFF - 334E62FF3D627CFF406681FF3F6681FF53738AFF779EB4FF61BAE3FF329FD2FF - 1F769EFF4D2C31FF782929FF8C3536FF8D3435FF8C3435FF8B3535FF8A3535FF - 8A3635FF893636FF883635FF873635FF873736FF873735FF863736FF863735FF - 863735FF863735FF863735FF863735FF863635FF863735FF863735FF863635FF - 863735FF863735FF863735FF863735FF863635FF873735FF873635FF873635FF - 863635FF39556CFF32617DFF287BA2FF248CBCFF2489B7FF207BA4FF1E6484FF - 234E65FF2B4659FF324C60FF38576EFF3C607AFF3F657FFF406681FF406681FF - 406681FF406681FF416782FF416781FF416781FF416781FF416781FF406681FF - 406681FF406681FF406681FF3F6580FF3D617BFF3A5B72FF375369FF32556CFF - 2A6483FF257BA4FF258AB9FF2487B6FF1F78A0FF1F5D7BFF26485CFF30485BFF - 385870FF3E647EFF406681FF406681FF406681FF416681FF3F6782FF41617AFF - 437C9CFF39A5D7FF2F9DD0FF1F86B4FF2C546DFF6B2928FF873434FF8C3737FF - 8A3536FF944747FFAC6E6EFF6CAED0FF33A5DAFF2385B3FF1D5E7CFF2E4A5EFF - 3D627BFF406782FF3F6580FF3F6781FF3E647EFF3C5E76FF317699FF268FBFFF - 2894C6FF2997CAFF259ED5FF26A2DAFF30A1D6FF3AA1D2FF40A4D3FF43A9D9FF - 3FAEE0FF39ADE1FF33A3D6FF2992C3FF1F7DA8FF1E5F7EFF27465AFF334E63FF - 3C617BFF406680FF406682FF406782FF4F6F87FF6494AFFF4BB1E0FF2D9CCFFF - 2078A1FF4F2D33FF792928FF8B3536FF8E3435FF8D3435FF8D3435FF8D3535FF - 8B3536FF8B3636FF8A3736FF8A3737FF893837FF893837FF883938FF883938FF - 883938FF883937FF883938FF883938FF883938FF883938FF883938FF883938FF - 883937FF883937FF883938FF883937FF883937FF883937FF883937FF883937FF - 883937FF3E647EFF3C5C74FF38566CFF2D617DFF226282FF244E65FF2C4557FF - 344E62FF3A5C74FF3E647EFF406782FF406782FF406681FF406681FF406681FF - 416781FF416782FF406681FF406782FF406782FF406782FF406782FF416782FF - 416781FF406681FF406681FF406681FF416782FF406782FF3F657FFF3C5F77FF - 39556BFF34546AFF2A5F7BFF216282FF244C63FF2D4557FF365369FF3D617BFF - 406681FF406681FF406681FF406681FF416781FF406682FF3F6580FF496E88FF - 688194FF5FA9CEFF36A6DAFF2288B7FF225A77FF642728FF883232FF8E3435FF - 8E383AFFA65554FFA196A2FF58BAE6FF2D99CBFF1E749CFF244A5FFF355065FF - 3F657FFF416681FF406581FF406681FF406781FF3F647EFF3C5970FF34617CFF - 28779DFF2387B5FF268EBEFF2894C6FF2999CDFF299CD0FF2A9CD0FF2A9ACDFF - 2894C6FF248AB9FF1F7BA6FF1D6689FF224F66FF2C4557FF365369FF3E637DFF - 406681FF406681FF406681FF3F6681FF456A84FF54768EFF47A2CEFF31A2D7FF - 218CBDFF2C5975FF6B2423FF843132FF8D3536FF8D3435FF8D3435FF8E3435FF - 8C3435FF8C3536FF8C3737FF8B3838FF8B3938FF8B3A39FF8A3B3AFF8A3C3AFF - 893C3AFF8A3C3AFF8A3B3AFF893C3AFF8A3B3AFF8A3C3AFF893B3AFF8A3B3AFF - 8A3B3AFF893B3AFF8A3B3AFF893B3AFF893B3AFF893B3AFF8A3B3AFF893B3AFF - 893B3AFF406681FF406681FF3D637DFF3A586FFF355166FF355267FF3A5D75FF - 3F647FFF406681FF406681FF406681FF406681FF406681FF416782FF416782FF - 406682FF406681FF416782FF416782FF416782FF416782FF416781FF416781FF - 406682FF416782FF416782FF406681FF406681FF406681FF406681FF406681FF - 3F6681FF3D607AFF39566DFF345065FF355267FF3B5E77FF3F6580FF406681FF - 406681FF406681FF406681FF416782FF416782FF406681FF426883FF5E7B91FF - 80A4BAFF60B9E2FF319CCEFF1B7AA4FF433742FF762624FF8A3536FF8D3536FF - 984546FFAF7678FF79B8D8FF3EABDDFF2285B3FF205874FF2E4759FF3C5F79FF - 406781FF406681FF406681FF406681FF406681FF406681FF3F6580FF3C5C75FF - 37556BFF2E5A74FF256585FF227094FF21769DFF21779FFF20769EFF1F7298FF - 1F698CFF205C79FF244E65FF2B4759FF334D61FF3A5C75FF3F657FFF406681FF - 406681FF406681FF416781FF406681FF416882FF4A6A82FF4E86A5FF37A8DCFF - 2B98CBFF1C7EAAFF463C49FF792625FF8A3435FF8E3536FF8C3334FF8D3435FF - 8D3435FF8D3435FF8D3536FF8D3838FF8C3A39FF8C3B3BFF8C3D3CFF8C3E3DFF - 8C3E3CFF8C3E3DFF8C3E3DFF8C3E3DFF8C3E3CFF8C3E3CFF8C3E3CFF8B3E3CFF - 8C3E3CFF8B3E3DFF8C3E3CFF8B3E3CFF8C3E3CFF8B3E3CFF8C3E3CFF8B3E3CFF - 8B3E3CFF406681FF406681FF406781FF3F6681FF3F657FFF3F6680FF406781FF - 406681FF406681FF406681FF416681FF416782FF416782FF416782FF416782FF - 416782FF416782FF416782FF416782FF416782FF416782FF416782FF416682FF - 416781FF416781FF406681FF406681FF416782FF416781FF406681FF406681FF - 406681FF406781FF406681FF3F647FFF3F6680FF406681FF406681FF406681FF - 406681FF416781FF416782FF406681FF406681FF406681FF4D718AFF7891A3FF - 79B9D8FF46AEDEFF228CBCFF265974FF632222FF823031FF8D3435FF903C3DFF - AB5B59FF98A1B3FF52B9E6FF2A96C8FF1D6C90FF274559FF37556CFF406681FF - 406681FF406680FF406681FF406782FF406782FF406682FF406681FF406681FF - 3E637DFF3A5970FF355167FF304E63FF2C4E63FF2A4D63FF2A4C61FF2A4B5FFF - 2D495DFF304A5EFF355166FF3A5C74FF3E647EFF406681FF406681FF406782FF - 406782FF406782FF406681FF416682FF406681FF436983FF4E6E86FF4399C3FF - 30A4DAFF2490C2FF256687FF622728FF822E2FFF8D3536FF8D3435FF8C3334FF - 8D3536FF8D3335FF8D3435FF8D3838FF8D3B3AFF8E3D3DFF8E403FFF8E413FFF - 8E413FFF8E413FFF8E413FFF8E413FFF8E413FFF8E413FFF8E413FFF8E413FFF - 8E413FFF8E413FFF8E413FFF8E413FFF8D413FFF8E413FFF8D413FFF8D413FFF - 8D403FFF416782FF406781FF416682FF416782FF416782FF416782FF416781FF - 406681FF416781FF416782FF416782FF416782FF416782FF406682FF406681FF - 406681FF406681FF406682FF406682FF406782FF406782FF406782FF416782FF - 406682FF406682FF406682FF406681FF406681FF406682FF416782FF416781FF - 406681FF416782FF416782FF416782FF416782FF416781FF406682FF416781FF - 416782FF406682FF406781FF416782FF406580FF446A84FF668195FF83ACC3FF - 5DB9E3FF2E9ACCFF1C759CFF48323AFF782726FF8A3436FF8D3537FF9C4A49FF - AE8184FF6DBCE1FF38A5D7FF1F81AEFF22516AFF314A5DFF3D627CFF416782FF - 3F6580FF416782FF4A6E88FF4E6E86FF44647CFF3E627BFF3F6681FF406782FF - 406781FF406781FF3E647FFF3D617AFF3C5D75FF3A5A72FF395A71FF3A5C73FF - 3C5F78FF3E637DFF3F6680FF406781FF406681FF416882FF486D87FF4D6E85FF - 44647DFF3E627CFF3F6681FF406781FF406681FF406782FF466881FF4C7A96FF - 39A6D8FF2D9ED2FF1E85B2FF3C4658FF752422FF893435FF8E3536FF8D3435FF - 8D3435FF8D3435FF8D3535FF8E3738FF8F3D3DFF904241FF904442FF904442FF - 904442FF904442FF904442FF904442FF904442FF904442FF904442FF904442FF - 904442FF904442FF904442FF8F4442FF904442FF904442FF904442FF904442FF - 904442FF406681FF416682FF406681FF406681FF406681FF406681FF406681FF - 416781FF416782FF416782FF406682FF406681FF3F6681FF406782FF416883FF - 436983FF446983FF456982FF456882FF446881FF436781FF426680FF416781FF - 406781FF406782FF406782FF416782FF416781FF406681FF416781FF416782FF - 416782FF406681FF406681FF406681FF406681FF406681FF416781FF406682FF - 406781FF416782FF416782FF406681FF406681FF53758DFF7E99ACFF74BDDFFF - 3FA9DAFF1E87B6FF2F4E64FF692120FF843233FF8D3334FF8F393BFFA75B5AFF - 8EA8BEFF4DB6E4FF2A96C8FF1F688AFF294354FF385971FF406680FF406681FF - 466B86FF56758DFF6D8699FF6297B5FF3C84AAFF385E78FF3C5970FF3E6079FF - 3F6580FF406782FF406782FF416782FF416782FF416782FF416783FF416783FF - 406782FF406681FF406781FF436983FF4A6D87FF57758CFF688397FF6192AEFF - 3D81A6FF375971FF3B596FFF3E637DFF406681FF406681FF426883FF537187FF - 5996B6FF3DACDFFF2894C6FF216D91FF5A2B2FFF812D2DFF8D3436FF8D3334FF - 8D3536FF8D3435FF8D3435FF8F3C3CFF924543FF924745FF924745FF924745FF - 924745FF924745FF924745FF924745FF924745FF924745FF924745FF924745FF - 924745FF924745FF924745FF924745FF924745FF924745FF924745FF924645FF - 924744FF416782FF416782FF416782FF416782FF416782FF416681FF416781FF - 416782FF416682FF406681FF406681FF426883FF486C86FF507088FF56748AFF - 57768DFF557890FF527892FF517993FF4F7893FF4C748FFF47708BFF456A84FF - 42647DFF406078FF3F617AFF3F657FFF406782FF406681FF406681FF406681FF - 416781FF416782FF416782FF416782FF416782FF416782FF416782FF416782FF - 416682FF416782FF406782FF406680FF476D86FF6D879AFF82B4CDFF54B7E3FF - 2A95C6FF1E6A8DFF542A2EFF7C2B2BFF8C3536FF8C3434FF91393AFF9F595AFF - 5FAFD6FF3AAADCFF2995C6FF226889FF2C4253FF385A72FF426984FF50718AFF - 657F94FF7598AEFF6FB2D2FF50B4E2FF2EA3D8FF298CBAFF307598FF37627DFF - 3C5C74FF3E5E76FF3E637DFF3F6580FF406681FF406681FF406681FF406681FF - 406681FF456A84FF507189FF5C798FFF67869BFF6D9AB4FF68AED0FF4FB3E1FF - 2DA1D6FF2881AAFF30637FFF38556CFF3C5C75FF3E6580FF4D718AFF6E869AFF - 75A5BFFF48B0DFFF2D98CAFF1A79A2FF3C3F4FFF7C2726FF8C3537FF8C3435FF - 8C3334FF8D3334FF913E3DFF944947FF954B48FF954A48FF944A48FF944A48FF - 954A48FF944A48FF944A48FF954A48FF944A48FF944A48FF944A48FF944A48FF - 944A48FF944A48FF944A48FF944A48FF944A48FF944A48FF944A48FF944A48FF - 944A48FF416782FF416782FF416782FF416782FF406681FF416782FF406682FF - 406681FF406681FF426983FF4B6F88FF5A788EFF658399FF6792ABFF60A0C0FF - 55A6CCFF4BA7D2FF44A8D6FF42A8D7FF3FA7D7FF3CA5D5FF39A2D3FF379CCBFF - 3592BFFF367FA5FF396A87FF3C5D75FF3D5C74FF3E637DFF406681FF406681FF - 406681FF406681FF406681FF406681FF406681FF406681FF406681FF406681FF - 406781FF406681FF406681FF416882FF5A7990FF81A1B5FF6CBEE4FF38A4D5FF - 1E81ACFF3B3F4EFF702322FF873335FF8E3535FF8D3334FF8D3739FF973A39FF - 71687CFF2BA9E0FF2D9BCEFF2588B5FF256281FF335065FF4E6C84FF708FA4FF - 72AAC7FF60B8E0FF46AFDFFF34A2D4FF2E9DCFFF2D9BCDFF2A9ACCFF2A92C2FF - 3083ACFF336C8AFF36576EFF3B5E76FF406680FF406682FF416681FF406680FF - 476C86FF607E95FF7195ACFF69A7C5FF5CB0D7FF4FB4E1FF40ABDCFF33A1D3FF - 2D9BCEFF2B97C9FF278FBEFF2A789DFF335D77FF3E5D75FF657F94FF7EACC5FF - 61BAE3FF38A6D8FF248AB9FF22607DFF58292CFF7F2D2DFF8C3334FF8D3334FF - 903939FF954645FF974E4CFF974E4CFF974E4BFF974E4BFF974E4BFF974D4BFF - 974D4BFF974E4BFF974E4BFF974E4BFF974E4BFF974E4BFF974D4BFF974D4BFF - 974D4BFF974D4BFF974D4BFF974D4BFF974D4BFF974D4BFF974D4BFF974D4BFF - 964D4BFF416682FF406782FF416782FF406782FF416782FF416682FF406681FF - 416781FF496E87FF5B798FFF6E8B9FFF70A3BDFF63B2D7FF52B5E2FF43B1E2FF - 38A9DCFF32A3D6FF2E9FD2FF2C9ED0FF2B9DCFFF2B9ED0FF2D9FD2FF2EA2D6FF - 2EA4D9FF2DA3D7FF2C9ACCFF2E87B1FF336B8AFF38586FFF3B5C73FF3E657FFF - 406882FF406782FF406782FF416782FF416882FF416882FF416883FF416882FF - 416882FF416882FF406781FF4B6F89FF748DA0FF7EBAD6FF4BB3E2FF2490C1FF - 265E7AFF602222FF802F2FFF8D3536FF8D3435FF8C3435FF8C3435FF8C3537FF - 942C29FF5B6C87FF26A8E0FF2D9ACCFF268BB9FF2C7BA1FF4F8EB0FF63B7DDFF - 4DB5E3FF36A4D6FF2692C3FF2084AFFF2283AEFF2291C0FF2798C9FF2C9BCDFF - 2C9FD2FF2590BFFF26617FFF334E62FF3E637EFF416781FF406681FF416782FF - 57768DFF7CA0B5FF6CBFE4FF49B2E2FF36A6D9FF2B99CCFF218DBCFF2081ACFF - 2288B4FF2698CBFF2D9ACCFF2A97C8FF278DBAFF3681A7FF5D9FC0FF5CBAE4FF - 3AA7D8FF248EBEFF1F6788FF4F292EFF782525FF8A3536FF923D3DFF964847FF - 9A514FFF9A5350FF9A514FFF9A514FFF99514FFF99514FFF9A514FFF99514FFF - 99514EFF9A514FFF99514FFF99514FFF99514FFF99514FFF99514FFF99514FFF - 99514EFF99514FFF99514FFF99514EFF99514EFF99514EFF99514EFF99514EFF - 99514EFF416782FF416782FF416682FF416782FF406681FF406681FF426983FF - 51738CFF6A8599FF77A1B9FF6AB7D9FF52B7E4FF3EABDCFF309DCEFF2691C1FF - 2186B2FF217BA3FF247397FF256F92FF266F92FF267296FF247AA0FF2284AFFF - 248FBEFF2997C8FF2E9CCDFF2E9DCFFF2A97C6FF2B7FA6FF32607AFF38576CFF - 3D637CFF416A84FF426A84FF426A84FF426A84FF426A84FF426A84FF426A84FF - 426984FF416983FF416984FF5B7B91FF7FA6BBFF65BDE5FF33A1D2FF1F789FFF - 47333CFF772625FF8A3436FF8D3535FF8D3435FF8D3536FF8C3435FF8D3435FF - 8C3435FF8F2E2CFF566F8BFF27A6DDFF2F9CCDFF2DA1D5FF37ACDFFF3AA7D9FF - 2B97C7FF1F83AEFF256381FF443A46FF51353EFF424F63FF306F90FF2792C2FF - 2EA1D5FF2B96C7FF217093FF2C495DFF3B5D75FF416782FF3F6681FF446A84FF - 698397FF7DB4CFFF51B6E3FF2F9DCFFF2389B7FF226E92FF325066FF4B3640FF - 553A44FF3B6A88FF2594C4FF2A9CCDFF2E9BCCFF2FA4D9FF3AACDFFF36A3D5FF - 248DBBFF1F6889FF4F3137FF792F2DFF904645FF9A504DFF9C5552FF9D5653FF - 9C5552FF9C5552FF9C5552FF9C5552FF9C5552FF9C5552FF9C5552FF9C5552FF - 9C5552FF9C5452FF9C5452FF9C5552FF9C5552FF9C5452FF9C5552FF9C5552FF - 9C5452FF9C5452FF9C5452FF9C5452FF9C5452FF9C5452FF9C5452FF9C5452FF - 9C5452FF406681FF406681FF406781FF406681FF406681FF436A84FF57788FFF - 7690A3FF79B2CEFF5EBCE6FF41AEDEFF2D9BCBFF2188B5FF217297FF2F5B73FF - 444856FF563F45FF623B3DFF673B3CFF693D3EFF6A3F40FF664447FF5C4C54FF - 495B6DFF327190FF2588B3FF2698CAFF2F9CCEFF2D9ACBFF278AB7FF2B6886FF - 355266FF3C6078FF426B84FF436C86FF426B85FF436B85FF426B85FF426B85FF - 426B85FF416A84FF436B86FF5E7D93FF6EAFCEFF4DB4E3FF2899CBFF2E5C76FF - 62201EFF803031FF8D3435FF8C3334FF8C3334FF8E3435FF8D3536FF8D3435FF - 8D3536FF8B3436FF8E2D2AFF576B86FF27A5DAFF2F9ED0FF2C97C7FF218AB7FF - 216D90FF3A4252FF5E2425FF742523FF7D2929FF7B2625FF772726FF695261FF - 3AA7D8FF31A3D5FF237EA6FF274E64FF37556BFF406681FF3F6580FF4A6E88FF - 7590A3FF72BADCFF3FABDCFF238DBBFF2B5771FF532527FF6B2120FF792625FF - 7F2827FF7D2726FF5E4754FF327DA2FF2396C7FF2B9BCDFF2E99CAFF2289B6FF - 236685FF573A3DFF81403CFF975553FFA05A57FF9F5957FF9F5856FF9F5855FF - 9F5856FF9F5856FF9F5856FF9F5856FF9F5855FF9F5856FF9F5856FF9F5856FF - 9F5855FF9F5856FF9F5855FF9F5856FF9F5855FF9F5855FF9F5855FF9F5855FF - 9F5855FF9F5855FF9F5855FF9E5855FF9F5855FF9E5855FF9E5855FF9F5855FF - 9E5855FF426883FF416983FF426983FF416982FF456C86FF5C7C93FF7C99ACFF - 79BCDAFF55BAE6FF36A4D5FF238DBBFF217093FF3C5061FF5F3E40FF783D39FF - 874440FF914D49FF96524FFF985652FF9A5753FF995753FF985450FF95514CFF - 8F4B46FF834945FF6B545AFF45718BFF2692C1FF2A9FD1FF309CCCFF278FBCFF - 276D8DFF325165FF3C5E76FF426B85FF446C86FF436B85FF436C86FF436C86FF - 436C86FF436B85FF436C86FF53748BFF5F97B4FF45B2E2FF2D9DCEFF247498FF - 5B383CFF863837FF913E3EFF903A3AFF8F3737FF8D3536FF8D3435FF8C3334FF - 8D3234FF8C3435FF8B3334FF8D2A27FF595F75FF238AB7FF207499FF314D62FF - 54292DFF702220FF802D2EFF893335FF8C3435FF8A3335FF8A3131FF9C3C39FF - 629CBEFF36AFE2FF2689B5FF245770FF344F63FF3F6580FF406782FF53748CFF - 7C9CB1FF66BCE3FF34A2D3FF1F789EFF4F3035FF7B2D2AFF8D3F3FFF934242FF - 954545FF944746FF933F3BFF834543FF576374FF2E7CA1FF1B7CA6FF295F79FF - 613F40FF884944FF9B5A58FFA25C5AFFA25C59FFA25C59FFA25C59FFA25C59FF - A15C59FFA25C59FFA25C59FFA25C59FFA15C59FFA15C59FFA15C59FFA15C59FF - A25C59FFA15C59FFA25C59FFA15C59FFA15C59FFA15C59FFA15C59FFA15C59FF - A15C59FFA15C59FFA15C59FFA15C59FFA15B59FFA15B59FFA15C59FFA15C59FF - A15B59FF436B86FF436C86FF436B85FF456E88FF5B7D93FF7E9CAFFF78BEDDFF - 51B8E5FF319ECEFF1F82ABFF2F5D75FF604245FF824540FF935450FF9E5E5BFF - A46260FFA66360FFA76360FFA76360FFA76360FFA76360FFA76360FFA56360FF - A36260FF9F5F5CFF9C5853FF95514BFF746068FF3B89AFFF27A1D4FF319ECEFF - 2991BDFF266D8DFF314F63FF3C6077FF436C86FF436C86FF426C86FF436D86FF - 446D87FF446D87FF436C86FF497089FF597F97FF4CACD6FF36A8DBFF228DBBFF - 3E586BFF83403CFF995352FF9D5150FF9B4D4CFF994948FF964545FF954241FF - 933E3FFF923C3CFF903A3BFF8D3839FF892D2BFF603E47FF56333AFF6D2221FF - 7D2C2CFF883536FF8E3738FF903839FF903A3AFF923B3CFF934142FFA7524FFF - 8392A6FF3EB5E7FF2B93C2FF236482FF314D60FF3F6780FF436D88FF608095FF - 80ACC3FF59BAE5FF2A98C8FF286582FF6E3E3DFF975653FFA45E5BFFA35E5CFF - A45F5CFFA5615DFFA3615FFF9F5C59FF99514BFF7E4F4EFF5A515AFF714544FF - 8E4F4CFF9F5E5CFFA4605DFFA4605DFFA4605DFFA4605DFFA5605DFFA4605DFF - A4605DFFA4605DFFA4605DFFA4605DFFA4605DFFA4605DFFA4605DFFA4605DFF - A4605DFFA4605DFFA4605DFFA4605DFFA4605CFFA4605DFFA4605DFFA4605DFF - A45F5CFFA4605DFFA4605CFFA4605DFFA4605DFFA4605DFFA4605DFFA4605DFF - A4605DFF446D87FF446D87FF456E88FF567B92FF7D99ABFF7DBFDCFF52B8E4FF - 309DCDFF207BA2FF405362FF774440FF935652FFA16461FFA86764FFAA6764FF - A96763FFA96663FFA96663FFA96663FFA96663FFA96663FFA96663FFA96663FF - A96663FFA96763FFA86764FFA46562FFA45C56FF985C59FF5385A2FF29A4D9FF - 33A0D1FF298EBAFF266785FF334F61FF3E647DFF446D87FF446D87FF436D87FF - 446E88FF446E88FF436D87FF456F89FF53748BFF5798B9FF3FB2E3FF2D9ACBFF - 277293FF704848FF9A5B58FFA76461FFA7625FFFA6615EFFA55F5CFFA45D5AFF - A25A58FFA15856FFA05654FF9F5452FF9A5251FF954845FF914644FF954E4DFF - 9C514FFF9E5250FF9F5352FFA05654FFA15856FFA35B59FFA35F5CFFB16964FF - A08F98FF49B7E5FF309DCDFF247497FF314F61FF42647BFF4E738BFF70899CFF - 7CB7D3FF4CB4E2FF238EBCFF37596EFF834945FFA26360FFA86562FFA86561FF - A76461FFA86461FFA86461FFA76461FFA46462FFA05E5BFF9C5853FF9C5D5AFF - A46360FFA76461FFA76461FFA76461FFA76460FFA76461FFA76460FFA76461FF - A76460FFA76460FFA76461FFA76461FFA76461FFA76360FFA6615EFFA55F5DFF - A5615EFFA7625FFFA76360FFA76360FFA76360FFA76460FFA76360FFA76360FF - A76360FFA7635FFFA6625FFFA6615EFFA5605DFFA45E5BFFA35A58FF9F5453FF - 9A4B4AFF446E88FF446E87FF4E768FFF7590A3FF83BAD4FF5ABDE7FF33A0CFFF - 1F7CA3FF46515EFF814945FF9B605DFFA96A67FFAC6A67FFAC6A67FFAC6A67FF - AB6A66FFAC6A67FFAC6A67FFAC6A67FFAC6A67FFAC6A66FFAC6A66FFAC6A66FF - AB6A66FFAB6A66FFAB6A66FFAC6A67FFAA6B68FFA96863FFAA635DFF618BA6FF - 2AABE2FF33A0D0FF2889B3FF285D77FF365366FF416982FF446C86FF436B86FF - 436B85FF436B86FF436B85FF436B86FF496F88FF59829BFF4CB0DCFF36A7D8FF - 2286B0FF4F505BFF90504BFFA76563FFAA6764FFAA6764FFAB6865FFAB6865FF - AB6865FFAB6865FFAA6864FFAA6864FFAA6864FFAA6966FFA96865FFAA6864FF - AA6864FFAA6864FFAA6865FFAB6965FFAB6966FFAB6966FFAA6966FFB06E6AFF - B28585FF5AB3DBFF35A7D8FF298BB5FF32708FFF437E9DFF548CA9FF6B9BB4FF - 67B8DCFF3EABDBFF2083ADFF4D505AFF91534EFFA86865FFAA6865FFAA6865FF - AA6865FFAA6865FFAA6865FFAA6865FFAB6865FFAA6965FFAA6966FFAA6865FF - AA6865FFAA6864FFAA6864FFAA6864FFAA6764FFAA6764FFAA6864FFAA6965FF - AB6865FFAA6864FFA96562FFA7615FFFA45D5BFFA25957FFA15856FFA45C59FF - A6605DFFA7625FFFA76360FFA86360FFA86360FFA76360FFA7625FFFA6615EFF - A55F5DFFA45D5AFFA25957FF9F5352FF9B4D4CFF974645FF943F3FFF8F3738FF - 8C3233FF446D87FF48718BFF668599FF87AFC3FF69C3EAFF3AA8D7FF2083ACFF - 415463FF844C47FFA06663FFAE6F6BFFAF6E6BFFAF6E6BFFAF6E6AFFAF6E6BFF - AF6E6BFFAF6E6BFFAF6E6AFFAF6E6AFFAF6E6BFFAF6E6AFFAF6E6AFFAF6E6AFF - AF6F6BFFB0706CFFB0706CFFAF6E6AFFAD6A67FFA96664FFA65F5DFFA7544FFF - 5889AAFF2EB0E6FF329DCCFF257FA4FF2A4F64FF38566CFF406681FF416782FF - 406681FF406681FF416782FF406681FF426984FF537187FF58A0C2FF40B2E3FF - 2996C4FF315E77FF742F2DFF924747FF9C504FFF9E5352FFA15755FFA45B58FF - A65E5CFFA7615FFFA96461FFAA6662FFAA6764FFAB6865FFAB6966FFAC6A67FF - AD6B68FFAD6C68FFAE6C69FFAE6D69FFAE6D69FFAE6D69FFAD6C68FFAE706CFF - BD7C76FF75AAC6FF3BB0E3FF33A1D1FF32A5D7FF39AFE2FF41B2E2FF47B4E3FF - 44B2E2FF339FCFFF237396FF664A4CFF9D5F5BFFAD6D69FFAD6C68FFAD6C69FF - AD6C68FFAD6C69FFAD6C68FFAD6C68FFAD6C68FFAD6C68FFAD6C68FFAD6C68FF - AD6B68FFAD6C68FFAD6C69FFAE6D69FFAF6E6BFFAE6E6AFFAD6C68FFAA6764FF - A65F5DFFA25755FF9D4F4EFF9A4948FF984746FF994948FF9D5150FFA15755FF - A35A58FFA45C5AFFA45C5AFFA45C5AFFA45C59FFA35A57FFA15755FF9E5250FF - 9B4C4BFF974545FF933E3EFF903839FF8D3536FF8D3334FF8D3334FF8D3334FF - 8D3435FF446E88FF557B93FF7F9CAEFF7BC3E2FF49B4E1FF268FBBFF366075FF - 7F4C48FFA16865FFB1726EFFB2726EFFB2726EFFB2726FFFB2726FFFB2726EFF - B2726FFFB2726EFFB2726EFFB1726EFFB1726EFFB2726FFFB37470FFB2726EFF - AF6D6AFFA96461FFA35957FF9C4D4CFF964343FF923C3CFF8E393AFF8F3435FF - 923B3BFF4B9BC3FF34AFE2FF2E96C3FF256A88FF2F495DFF3C5F79FF406681FF - 3F6580FF406680FF406682FF406681FF406782FF4B6C84FF5D8AA5FF4CB6E3FF - 33A2D2FF277FA5FF5F343BFF872825FF902B2AFF902F2EFF8F3535FF8F3B3CFF - 913F40FF944343FF984746FF9B4C4BFF9E5150FFA15654FFA45B59FFA65F5CFF - A86360FFAA6663FFAD6A67FFAF6E6AFFB1716DFFB1716DFFB0706DFFAF726FFF - BD7771FF9194A3FF2BABE0FF2A98C7FF2B97C5FF2B98C7FF2A98C7FF2998C6FF - 2996C4FF1F84AFFF2D5F78FF804F4BFFA76A67FFB1716DFFB0706CFFB0706CFF - B0706CFFB0706CFFB06F6BFFB06F6BFFB06F6CFFB0706CFFB0716DFFB1726EFF - B2736FFFB2726EFFB06F6CFFAC6966FFA7605EFFA15654FF9B4B4AFF964242FF - 923B3BFF8F3738FF8E3536FF8D3435FF8E3536FF923D3DFF974544FF994948FF - 9A4B4AFF9B4C4BFF9B4B4AFF994948FF984645FF954141FF923C3DFF903839FF - 8E3536FF8D3334FF8D3334FF8D3435FF8D3435FF8D3335FF8C3234FF8E3536FF - 90393AFF49748EFF69889CFF86B5CBFF60C2EAFF32A0CFFF257192FF6B4F4FFF - 9E6460FFB37672FFB57772FFB47672FFB57672FFB57672FFB57672FFB57672FF - B57672FFB47672FFB47672FFB57773FFB67874FFB3736FFFAA6462FF9F5251FF - 964343FF903939FF8D3435FF8C3233FF8C3233FF8C3334FF8D3334FF8C3638FF - 953230FF835C67FF40B4E4FF35A7D7FF2885ADFF295268FF365368FF406681FF - 406681FF406681FF416781FF416782FF3F6681FF456A84FF597990FF55AED5FF - 3CAEDEFF2F9DCBFF3590B8FF4A81A2FF62687EFF744F5BFF83393DFF8C2D2CFF - 8F2A28FF8D2C2CFF8B3233FF8B3537FF8E3839FF923D3DFF974545FF9C4E4DFF - A35856FFAA6461FFB06F6BFFB37470FFB47571FFB47571FFB37471FFB37571FF - B77773FFAC7776FF577B92FF3A6C85FF396982FF376A83FF376981FF396880FF - 3A657BFF3E5C6EFF625257FF99605BFFAF726EFFB3746FFFB3736FFFB37470FF - B47571FFB47672FFB57773FFB57874FFB57773FFB47571FFB1706CFFAC6865FF - A65D5BFF9F5150FF994646FF933D3DFF8F3637FF8D3334FF8C3233FF8C3234FF - 8D3334FF8D3435FF8D3435FF8D3435FF8D3435FF8D3435FF8E3536FF8F3637FF - 8F3738FF8F3738FF8F3637FF8E3536FF8D3435FF8D3334FF8D3334FF8D3435FF - 8E3536FF8E3536FF8D3435FF8C3233FF8D3435FF923D3DFF9A4A49FFA35957FF - AA6461FF587D94FF7B99ACFF7BC3E2FF48B5E2FF238BB5FF465967FF915B55FF - B07774FFB87B77FFB87A76FFB87A76FFB87A76FFB77A76FFB87A76FFB77A76FF - B77A76FFB97C78FFB97D78FFB47571FFA8605EFF994747FF8F3738FF8C3233FF - 8C3233FF8D3334FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8D3739FF9B3B39FF6890ADFF3AB8EBFF2F98C4FF266885FF30495DFF3D617BFF - 416782FF406681FF406681FF416782FF406681FF416883FF4F6D84FF5094B6FF - 37B1E4FF32A2D1FF32A7D7FF2FAEE1FF2FB1E6FF32ADDFFF3B9DC9FF4D84A5FF - 61697FFF754A53FF863233FF8D2A27FF892B2BFF872E2FFF913B3CFFA15553FF - AD6A67FFB57672FFB77975FFB67975FFB77874FFB67975FFB67874FFB67974FF - B67976FFB27470FFA5655EFF935B56FF8E5853FF8C5853FF8D5954FF905A55FF - 915C57FF985F59FFA36B65FFB27974FFB97D79FFB97D79FFB87C78FFB77A75FF - B57571FFB16F6BFFAC6764FFA65C5AFFA05151FF994746FF933E3DFF8F3637FF - 8C3234FF8B3132FF8C3133FF8C3334FF8D3435FF8D3435FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF - 8D3435FF8D3435FF8D3435FF8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF - 8C3334FF8C3233FF903839FF994747FFA45A58FFAE6A67FFB37470FFB57873FF - B67974FF6B8799FF82ABC0FF68C5EBFF36A6D4FF277595FF725454FFA76F6BFF - BA7F7BFFBB7E7AFFBB7E7AFFBA7E7AFFBA7E7AFFBA7E79FFBB7E7AFFBB807BFF - BB807BFFB57572FFA75E5CFF974343FF8D3435FF8B3133FF8D3435FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF8E3536FF8D3435FF - 8D3537FF953A39FF8F646CFF4CBAE7FF36A7D6FF287EA2FF2A4C60FF38586FFF - 406681FF3F6580FF3F6580FF406681FF3F6580FF406681FF446882FF406984FF - 327C9FFF2A7FA3FF2B89B0FF2E93BEFF319BC8FF33A1D0FF33A8D8FF30AFE1FF - 2FB0E3FF33A8D8FF3F95BDFF4E718CFF694750FF934F4DFFAF706DFFBA7D79FF - BA7F7AFFBA7E79FFB97D78FFB97D78FFB97D78FFB97C78FFB97D78FFB97C78FF - B97C78FFB87C78FFB67C78FFB67E79FFB67E7AFFB57C78FFB37975FFB17572FF - AF726EFFAE6E6BFFAD6966FFAA6361FFA65C5AFFA15453FF9D4D4CFF984545FF - 933D3DFF903838FF8D3334FF8C3132FF8B3132FF8C3133FF8C3334FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF - 8D3435FF8E3536FF8E3536FF8D3535FF8D3435FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8C3334FF8C3334FF - 933D3DFF9F5150FFAC6764FFB57571FFB87B77FFB97C78FFB87C77FFB87B77FF - B87B76FF7B91A1FF7EB9D2FF56BFE9FF2997C4FF3C6478FF8F5E58FFB47D79FF - BE837EFFBD827DFFBD817DFFBD827DFFBE837EFFBF8580FFBD837EFFB67572FF - A65C5AFF964141FF8C3234FF8C3233FF8D3435FF8E3536FF8E3536FF8E3536FF - 8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF8E3636FF8E3536FF8D3435FF - 8B3233FF8D3638FF9B4646FF68A2C2FF3DB6E6FF2C8EB7FF28586FFF355065FF - 406882FF426984FF426984FF436A85FF436A85FF436B85FF446C86FF416780FF - 3B596FFF335468FF2F596FFF2D637DFF2B718FFF2A7EA1FF2C8AB0FF2F93BDFF - 329BC7FF34A4D2FF34ADDDFF29A5D5FF317B9AFF8D6460FFB47C78FFBE827DFF - BC807CFFBC807CFFBC817CFFBC817CFFBC807CFFBC807CFFBC807CFFBC817CFF - BD827EFFBE837FFFBB7E7AFFB16F6BFFA65B59FF9D4C4BFF974343FF943D3DFF - 913939FF8F3637FF8D3334FF8C3132FF8B3032FF8B3031FF8B3032FF8B3133FF - 8C3234FF8D3435FF8D3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8D3536FF8D3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF - 8D3435FF8E3536FF8E3536FF8E3536FF8C3334FF8C3234FF933D3EFFA25554FF - B06D6AFFB97C77FFBC807BFFBB807BFFBB7F7BFFBB7E7AFFBB7F7AFFBB7F7AFF - BB7F7AFF849DAEFF77C0DFFF4AB7E4FF248BB3FF555C66FFA16B65FFBD8580FF - C18682FFC18782FFC28884FFC28884FFBE817DFFB3706DFFA25655FF933D3EFF - 8C3233FF8C3233FF8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF8E3536FF - 8E3536FF8E3536FF8E3536FF8D3435FF8C3233FF8A3031FF8B3031FF8D3435FF - 913B3BFF974748FFAE5C58FF8F99AAFF46BDECFF329BC6FF286883FF355164FF - 446D86FF47728CFF47718BFF47728BFF48728CFF47718CFF48728BFF47728BFF - 45708AFF436B83FF40637AFF3C5D71FF39586BFF34576AFF305A70FF2D637CFF - 2C7290FF3190B7FF38ACDCFF36A5D3FF2A7899FF7A5D5DFFB17974FFC08581FF - BF8480FFBF8580FFBF8580FFBF8480FFBF8580FFBF8580FFC08581FFBF847FFF - B97A76FFAC6563FF9A4848FF8C3334FF8A2F30FF8B3032FF8B3233FF8C3334FF - 8D3334FF8D3435FF8E3536FF8E3536FF8E3536FF8E3636FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8E3536FF8E3536FF - 8E3536FF8E3536FF8C3234FF8C3233FF933D3DFFA25554FFB2706DFFBC7F7BFF - BE847FFFBE837FFFBE837EFFBE837EFFBE837EFFBE827EFFBE827EFFBE837EFF - BE837EFF86ABBEFF6EC2E5FF41B1DDFF2681A5FF6D5E61FFB07B75FFC58E89FF - C48C87FFC08480FFB77673FFAA615FFF9A4848FF8F3738FF8B3032FF8C3233FF - 8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF - 8C3233FF8A2F31FF8A2F30FF8D3435FF954041FFA05150FFAC6462FFB67470FF - BD807CFFC08883FFCE938CFFB8A9AEFF52BDE9FF37A5D1FF287392FF325062FF - 426A82FF48738DFF47718BFF47728BFF47728CFF47728CFF47728CFF47728CFF - 47728CFF48738DFF47738DFF46728CFF456F89FF436A82FF40647AFF3D5D72FF - 3A5A6EFF47748DFF4EB3DDFF3AAEDDFF297DA0FF775F60FFB27B76FFC38A85FF - C28883FFC28984FFC28884FFC28884FFC28984FFC18783FFBD817CFFB4726FFF - A9605EFF9D4E4DFF943F40FF8E3637FF8C3334FF8D3334FF8D3435FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8E3536FF8D3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8B3133FF8C3233FF933E3EFFA35755FFB4726FFFBE837EFFC28883FFC18883FF - C18782FFC18682FFC18782FFC18782FFC18782FFC18783FFC18783FFC18782FF - C18682FF83B5CDFF67C3E7FF3BACD9FF2A799AFF775858FFAA6C69FFB06B69FF - A55A58FF9A4848FF91393AFF8C3233FF8B3132FF8D3435FF8E3536FF8E3536FF - 8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF8C3334FF8A2F30FF8B3132FF - 933D3EFF9F504FFFAC6563FFB97975FFC28783FFC78F8AFFC8928CFFC8918CFF - C78F8AFFC58D89FFCB938DFFC8A7A6FF64C0E5FF3AACDAFF297C9DFF2F5063FF - 41677EFF48738DFF47728CFF47728CFF48738DFF48738DFF48738DFF48738DFF - 48738CFF48738CFF48738CFF48738DFF48738DFF48738DFF48738DFF47728CFF - 4B748EFF688CA1FF67BDE0FF40B2E0FF287FA2FF725F62FFB27C77FFC58E89FF - C58C87FFC58C87FFC58C87FFC58C87FFC48B86FFC18681FFBC7E7AFFB77773FF - B3706DFFAE6866FFA9605EFFA25554FF9A4848FF933E3EFF8E3637FF8C3334FF - 8C3233FF8C3334FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8C3233FF8B3132FF8E3536FF - 974444FFA75C5BFFB77572FFC18681FFC48B86FFC48B86FFC48B86FFC38A86FF - C48B86FFC48B86FFC48B86FFC48B86FFC38984FFC18682FFBE837EFFBB7D7AFF - B97975FF7FBBD5FF62C2E8FF38AAD6FF2D728FFF652E2FFF873435FF8D3435FF - 8A3031FF8B3132FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8E3536FF8E3637FF8E3536FF8B3032FF8A2F31FF923B3CFFA35554FFB57370FF - C38883FFC9928DFFCB9690FFCA958FFFC9928DFFC8918CFFC8908BFFC8908BFF - C8918CFFC8908BFFCC9691FFD2A9A6FF72C2E2FF3EB1DFFF2B82A5FF2D5164FF - 40647BFF48748EFF48738DFF47728CFF48738DFF48738DFF48738DFF48738DFF - 48738DFF48738DFF49738DFF48738DFF48738DFF48738DFF48738DFF48738CFF - 507991FF7193A8FF6DC0E2FF42B4E1FF2881A5FF6E6064FFB37E78FFC8918CFF - C78F8AFFC78F8AFFC78F8AFFC78F8AFFC78F8AFFC68E89FFC58D88FFC48B86FF - C38884FFC08581FFBE817DFFBA7C78FFB67471FFB06B68FFA85F5DFFA05251FF - 984646FF933D3DFF8F3738FF8D3334FF8C3233FF8C3233FF8C3234FF8D3334FF - 8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8D3435FF8C3334FF8C3233FF8C3233FF8F3637FF964040FFA15251FFAF6866FF - BC7E7AFFC48B86FFC78F8AFFC78F8AFFC68F8AFFC68F89FFC78F8AFFC78F8AFF - C68E89FFC58C87FFC28883FFBF817DFFB97875FFB26D6AFFAA605FFFA25453FF - 9A4747FF79BEDBFF5FC1E8FF38AAD5FF2D708DFF612426FF853030FF8E3536FF - 8D3435FF8D3536FF8D3435FF8C3334FF8D3435FF8E3536FF8E3536FF8E3637FF - 8C3234FF892E2FFF8C3233FF9B4848FFB16B68FFC58A85FFCE9993FFCF9A95FF - CD9792FFCB9590FFCB948FFFCB958FFFCB9590FFCB958FFFCB958FFFCB958FFF - CB948FFFCA938EFFCE9994FFD4ACA9FF76C3E3FF41B4E1FF2B85A8FF2C5265FF - 406479FF49748FFF49748EFF48748DFF49748EFF49748EFF49758EFF49748EFF - 49758EFF49748EFF49748EFF49748EFF49748EFF49748EFF49748EFF48738DFF - 507991FF7193A8FF6DC1E3FF43B5E2FF2782A6FF6F6467FFB7837DFFCC9892FF - CC9791FFCC9792FFCD9892FFCD9892FFCD9892FFCD9892FFCC9792FFCC9791FF - CC9691FFCC9691FFCC9691FFCB9590FFCA938EFFC88F8AFFC58A86FFC08480FF - BB7C78FFB5726FFFAE6765FFA75C5AFFA05251FF9B4949FF964242FF923C3CFF - 8F3738FF8D3435FF8C3334FF8C3233FF8B3132FF8B3132FF8B3132FF8B3132FF - 8B3132FF8B3132FF8B3132FF8B3132FF8B3132FF8B3132FF8C3233FF8D3435FF - 8F3838FF943F3FFF9B4A49FFA55958FFB16B69FFBC7D79FFC58B86FFC9928DFF - CB948FFFCA948FFFC9938EFFC9918CFFC9928DFFC9928DFFC8908BFFC68C87FF - C18581FFBA7A77FFB16B69FFA65A59FF9B4949FF933D3DFF8E3637FF8C3233FF - 8B3132FF77BAD7FF5EC1E8FF39ACD7FF2E728FFF602426FF832D2EFF8B3133FF - 8A3031FF8C3233FF903738FF933D3DFF923C3CFF8D3536FF8A3031FF8A2E30FF - 923A3BFFA55857FFBD7E7AFFCD9792FFD19E98FFCF9B96FFCE9893FFCD9892FF - CE9893FFCE9993FFCE9893FFCD9893FFCD9792FFCD9792FFCD9892FFCE9993FF - CF9A95FFCF9B96FFD4A29CFFDAB4B0FF78C5E4FF41B4E0FF2C84A6FF2D5164FF - 40637AFF48738DFF47718BFF46708BFF46708AFF466F89FF46708AFF466F89FF - 467089FF46708AFF47708BFF47718BFF47718BFF48728CFF48738CFF47728CFF - 4F7890FF7092A7FF6DC1E3FF44B6E2FF2882A6FF6D5F62FFB17974FFC48985FF - C18580FFBE807CFFBB7B78FFB87673FFB4706EFFB16C69FFAF6866FFAE6764FF - AE6865FFB06A68FFB4706DFFB97774FFBE807CFFC38783FFC78D88FFC9918CFF - CA938EFFCA938EFFC9918CFFC78E89FFC48985FFC08380FFBC7D79FFB77572FF - B26E6BFFAE6664FFA9605EFFA65A58FFA35554FFA05150FF9E4E4DFF9D4C4BFF - 9C4B4BFF9C4B4AFF9D4C4BFF9E4E4DFFA05251FFA45755FFA85E5CFFAE6765FF - B5726FFFBC7D7AFFC38883FFC9918CFFCB9691FFCD9792FFCC9691FFCA948EFF - C9918CFFC78F8AFFC68D88FFC68D88FFC78E89FFC48984FFBD7E7BFFB4706DFF - A85D5CFF9C4A4AFF923C3CFF8D3435FF8C3233FF8C3334FF8D3435FF8E3536FF - 8E3536FF75B1CCFF60C2E9FF3CAFDBFF2D7B9AFF643637FF914342FFA15251FF - A65B59FFAA605FFFA45857FF964242FF8B3132FF8D3435FF9C4A4AFFB36E6BFF - C88F8AFFD3A09AFFD4A19BFFD19D97FFD09B95FFD09C96FFD09C97FFD09C96FF - D09B96FFCF9B95FFD09B96FFD19D98FFD3A09AFFD4A29CFFD4A19BFFD09B95FF - CA918CFFC28682FFC28480FFC59C9CFF74C7E8FF41B3DEFF2B7F9FFF2C4A5DFF - 3B5C74FF426883FF406681FF406681FF406781FF406681FF406681FF406681FF - 416681FF416782FF416782FF416782FF406782FF406782FF416883FF416883FF - 4B7089FF6D8CA1FF6FC2E2FF45B7E3FF2B83A5FF58383CFF873736FF954040FF - 923C3CFF90393AFF8F3737FF8E3536FF8D3334FF8C3233FF8C3233FF8C3233FF - 8C3233FF8C3334FF8E3536FF903839FF933E3EFF984545FF9E4E4DFFA55857FF - AB6260FFB26D6AFFB87673FFBE7F7BFFC18581FFC58A86FFC78E89FFC8908BFF - C9918CFFC9908BFFC88F8AFFC78D89FFC68B87FFC58A85FFC48884FFC38783FF - C28782FFC38782FFC38783FFC58A85FFC68C88FFC88F8AFFCA938DFFCC9590FF - CD9792FFCD9892FFCD9792FFCB948FFFC9908BFFC68C87FFC38783FFC0837FFF - BD7E7BFFBB7B78FFBD7E7AFFBD7E7AFFB67370FFAB6260FF9E4F4EFF943F3FFF - 8E3536FF8C3233FF8C3334FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF - 8D3435FF75A5BDFF62C2E7FF42B5E0FF2B86A9FF756365FFB7807BFFC38884FF - B26E6BFF9E4E4DFF933D3DFF9A4747FFAF6765FFC48984FFD39F99FFD7A6A0FF - D5A39DFFD39F9AFFD29F99FFD3A09AFFD39F9AFFD29E99FFD29E99FFD4A19BFF - D6A59EFFD7A7A1FFD6A49EFFD09A95FFC58985FFB77572FFA95F5DFF9E4D4CFF - 953F40FF8F383AFF9B4545FFA68087FF6ACBEFFF3EADD8FF2A7694FF2B4659FF - 3B5D76FF406681FF3F6580FF3F6580FF406681FF406681FF406681FF406681FF - 406681FF406681FF406681FF406782FF426983FF456A84FF496C85FF4E6E86FF - 5B768CFF7590A3FF6EC2E3FF43B7E2FF2C82A3FF562C30FF7F2A2AFF8C3234FF - 8B3133FF8B3233FF8D3435FF8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3435FF8D3435FF8D3334FF8C3234FF8B3133FF8B3132FF8B3132FF - 8C3233FF8D3435FF903839FF933D3EFF984343FF9D4C4BFFA25453FFA85D5CFF - AE6664FFB46F6CFFB87673FFBC7C79FFBF817DFFC28581FFC48984FFC58B86FF - C78D88FFC78E89FFC88E89FFC88F8AFFC88E8AFFC78D89FFC68B87FFC48884FF - C28480FFBE807CFFBA7976FFB67370FFB26C6AFFAD6563FFAA605EFFA75C5AFF - A75B5AFFA95E5DFFA85C5BFF9E4E4DFF953F3FFF8F3636FF8C3233FF8C3234FF - 8D3435FF8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF8E3536FF8E3536FF - 8E3536FF7192A7FF65BEE1FF49BCE7FF2B92B8FF63666DFF9F5F5BFFA55C5BFF - A65A58FFB4706DFFC88D89FFD5A39DFFDAAAA4FFD9A8A2FFD6A49EFFD5A29CFF - D5A39DFFD5A39DFFD5A29CFFD5A29CFFD7A59FFFDAABA4FFDAAAA4FFD39F99FF - C68B87FFB6726FFFA65957FF974343FF8F3637FF8B3031FF8A2F30FF8A3031FF - 8A2F31FF8A3335FFA14D4CFFA19BA7FF60CDF4FF39A5CEFF296A85FF2E4659FF - 3D627BFF406681FF406681FF406681FF406782FF416782FF406781FF406781FF - 426883FF456A84FF4A6C85FF506F87FF57738AFF5D7C93FF6289A1FF6698B2FF - 69AAC8FF6BBCDCFF5BC4ECFF3FB2DDFF2D7E9DFF5A2D30FF822F2EFF8E3537FF - 8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3435FF8D3335FF8C3234FF8C3233FF8B3132FF8B3032FF8B3132FF - 8C3233FF8E3536FF903839FF933C3DFF964141FF994545FF9B4A49FF9E4E4EFF - A05150FFA25453FFA45655FFA35655FFA35554FFA25453FFA15251FF9F4F4EFF - 9C4A4AFF994646FF964142FF943E3EFF913A3BFF903839FF8F3737FF8F3637FF - 903939FF913A3AFF8D3435FF8B3133FF8C3233FF8D3435FF8E3536FF8E3536FF - 8E3536FF8D3435FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF678396FF66B2D1FF50C4EEFF31A0C9FF46697BFF9E6A65FFCA9994FF - DCABA5FFDDAEA7FFDBABA4FFD8A7A1FFD7A59FFFD7A69FFFD8A6A0FFD8A6A0FF - D7A59FFFD8A6A0FFDBACA5FFDDAEA7FFD4A09AFFBF7F7CFFA85C5BFF974242FF - 8E3435FF8A2F31FF8B3031FF8B3132FF8B3032FF8A2F30FF8A2F30FF8C3133FF - 8F3737FF984849FFB77270FF94BCD0FF53C5EFFF3399BFFF2A5D74FF365266FF - 446E87FF46708AFF46708AFF47718AFF47718BFF47718AFF48728CFF517991FF - 5D7E94FF648499FF678FA6FF689DB7FF67ABC9FF64B9DBFF5FC3E9FF58C6EFFF - 52C5EFFF4ABDE8FF3FB1DCFF2F9CC3FF286C87FF5F2526FF822D2EFF8B3133FF - 8B3132FF8C3233FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF - 8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF8D3435FF8D3435FF8D3435FF - 8E3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3435FF8D3335FF8C3334FF8C3233FF8C3233FF8B3132FF8B3132FF - 8B3132FF8C3133FF8C3233FF8C3233FF8C3233FF8C3233FF8C3233FF8C3233FF - 8C3233FF8C3233FF8C3233FF8C3334FF8D3334FF8D3435FF8D3435FF8D3435FF - 8D3435FF8D3335FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF5E7B90FF669EB8FF58C7F0FF3DAFD9FF35829FFF937876FFC99C96FF - DAA9A3FFD9A8A2FFD9A8A2FFDAA9A3FFDAA9A3FFDAA9A3FFD9A9A2FFDBABA4FF - DEAFA9FFDBABA5FFCA908BFFAC6361FF943F3FFF8A2F31FF8A3031FF8C3334FF - 8C3233FF8A2F30FF892E30FF8D3435FF974343FFA55756FFB46E6CFFC0827EFF - C9908CFFD7A49FFFD6C1C1FF7BCFEFFF47B8E2FF2E88A8FF2E5466FF406379FF - 4B7790FF4B7690FF4A7690FF4B7790FF4B7790FF4A7690FF577F97FF7496AAFF - 7BB3CBFF6CBDDDFF61C4E9FF5AC7F0FF52C5EFFF4BBEE9FF43B5DFFF39AAD4FF - 309DC6FF2C8FB3FF2F809EFF3A6D83FF5A555DFF8A4B49FF9E5353FF9D4C4BFF - 974343FF923C3CFF8F3637FF8C3334FF8C3233FF8C3334FF8D3435FF8E3536FF - 8D3435FF8D3435FF8D3637FF8D3738FF8D3739FF8D3739FF8D3739FF8D3738FF - 8C3738FF8D3638FF8D3536FF8E3435FF8D3536FF8D3435FF8D3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8E3536FF8E3536FF - 8E3536FF537B93FF6289A0FF5EBFE3FF49BFEAFF2E96BCFF62727CFFB98A83FF - D7AAA4FFDDADA6FFDCACA6FFDCACA6FFDCACA6FFDEAFA8FFDEB0A9FFD8A6A0FF - C48682FFA45654FF8B3132FF882C2DFF8A2F30FF8A2F30FF882D2EFF8B3031FF - 953F3FFFA65958FFBA7875FFCC938EFFD8A6A0FFDEAFA8FFDFB2ABFFDFB1AAFF - DDB2ACFFEDC1BAFFB2D0DEFF5DCAF2FF3CA9D1FF2B718BFF345263FF466F87FF - 4C7791FF4A7690FF4B7790FF4B7791FF4A7690FF4D7992FF6A8B9FFF8AB7CCFF - 72D2F5FF4FC3EDFF42B7E2FF38ABD4FF2F9DC5FF2B91B6FF2D85A5FF397992FF - 4D7181FF686E75FF876F6EFF9F746EFFB4817BFFC5918DFFCB928EFFC68A86FF - BF807CFFB87572FFAF6866FFA65958FF9C4B4AFF933E3EFF8D3536FF8C3233FF - 8C3435FF913C3EFF9A3F3EFF9B3D3BFF983937FF973735FF983735FF973635FF - 963231FF912B29FF8B2F2FFF8A3435FF8D3435FF8D3435FF8D3435FF8E3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF8E3435FF8E3536FF - 8D3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3536FF - 8E3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF8D3334FF - 8C3233FF4D7A94FF597D94FF60A6C4FF50C7F2FF3CADD6FF37839FFF947A77FF - CA9D97FFDDAFA8FFDEAFA9FFDFB0AAFFDEAEA8FFD6A39DFFC58984FFAE6664FF - 9B4948FF903838FF8D3434FF8D3435FF923B3CFFA0504FFFB36E6CFFC78D88FF - D7A49EFFE0B3ACFFE2B5AEFFE2B4ADFFE2B2ACFFE3B3ACFFE4B4ADFFE5B6AFFF - ECBFB7FFDBCACAFF7CD0EEFF4CBEE8FF3292B5FF2D5B6FFF3D5D71FF4A7690FF - 4C7892FF4B7791FF4C7892FF4C7892FF4B7791FF557F98FF7C9AADFF85C9E4FF - 59C6EDFF3AA8D1FF378FB0FF408099FF557888FF6A747CFF827475FF9B7975FF - AE837CFFBD8F88FFC89B95FFD2A6A0FFDAADA6FFDDADA7FFDCABA5FFDAA9A3FF - D7A49EFFD39E99FFCE9792FFC88D88FFC0807CFFB5716EFFA85D5CFF9B4A49FF - 964445FFA45959FF9A858FFF7B98AEFF6F94ADFF6F93ACFF6E93ACFF6D91AAFF - 698DA7FF627388FF6C3C43FF7F2B2BFF8B3435FF8E3436FF8C3334FF8D3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8D3334FF8B3133FF8C3233FF913A3AFF - 964243FF4C7892FF517B93FF5D89A1FF56C1E7FF48BFE9FF329FC5FF527B8CFF - B2857DFFD2A8A2FFE0B1AAFFDAA9A3FFCF9792FFC1827EFFB5716FFFAF6765FF - AB6260FFAC6462FFB4706EFFC58985FFD7A39EFFE5B7B0FFECC0B8FFEEC1BAFF - EDBEB7FFE9BAB3FFE4B5AFFFDDAFA9FFD3A7A1FFC89D96FFBC938CFFB6928CFF - C7A39CFF9ACEE3FF5BCAF2FF3DABD2FF2B728CFF294859FF376077FF3D6A84FF - 3C6983FF3B6982FF3C6983FF3C6983FF3C6A83FF527990FF80A5B7FF78D0F1FF - 48BAE3FF2F89A9FF4C5054FF785A54FF9A736BFFB38880FFC99D96FFD9AEA8FF - E4B8B2FFEABCB6FFEABAB4FFE6B6B0FFE1B2ABFFDEAFA8FFDDAEA8FFDEAFA9FF - DEB0A9FFDFAFA9FFDDADA7FFDAA9A3FFD6A39DFFD19B96FFCB918DFFC28683FF - C58581FFB4A6AEFF71D4F7FF52CCF6FF45C7F4FF45C6F3FF45C6F3FF45C6F3FF - 44C7F5FF36B8E4FF3B6D85FF6E2322FF873334FF8E3536FF8D3435FF8D3435FF - 8D3435FF8E3536FF8E3536FF8E3435FF8D3536FF8E3536FF8E3536FF8D3536FF - 8E3435FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8C3334FF8B3132FF8C3233FF923B3CFF9E4D4CFFAD6462FFBB7A77FF - C58985FF4C7891FF4D7A94FF557A91FF589EBDFF4DC8F3FF45B7DFFF3096B9FF - 6A7A83FFBD8F86FFD2A6A1FFD7A49EFFD19B95FFCD9490FFCA918CFFCC938FFF - D5A09BFFE4B3ADFFEFC2BBFFF0C4BCFFE7BBB4FFD8ACA6FFC59B94FFB18982FF - 9D7670FF88635CFF78534CFF6C473FFF633D35FF5E3830FF66423BFF855D55FF - 89A7B3FF69D1F6FF46B7E0FF318CACFF274F61FF2E4F63FF39647DFF3A667FFF - 39657EFF39667EFF3A667FFF39657EFF3D6981FF618093FF85B9CFFF67CFF4FF - 3DADD5FF2E677CFF39160EFF462018FF4C251EFF512B23FF5C372FFF6D4740FF - 805B53FF99726BFFB38A83FFCCA19AFFDFB3ACFFEABCB5FFEEBFB8FFEBBCB5FF - E5B7B0FFE1B3ACFFDFB1ABFFDFB2ABFFE0B3ACFFE1B3ACFFE0B2ABFFDFB2ACFF - E9BDB7FFA3D1E4FF5BC9F0FF42B2DBFF3AA0C5FF379ABDFF389BBFFF3CA8CFFF - 44B9E3FF41B1D9FF3183A1FF592A2DFF7F2B2BFF8D3435FF8D3435FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8D3536FF8D3436FF8E3435FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8D3435FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8C3334FF8B3132FF - 8C3233FF923B3CFF9F4E4EFFB06865FFC1827EFFCE9792FFD8A59FFFDDADA7FF - DFB0AAFF4B7792FF4D7892FF4F7A94FF537B93FF4FB1D6FF49C6F1FF43B2D9FF - 3192B4FF767E85FFC29189FFD6AAA5FFE1B2ACFFE6B6B0FFECBCB6FFECBFB8FF - E1B6AFFFCAA29AFFAD867FFF916C65FF7B5750FF6C473FFF643D35FF623931FF - 663A31FF6B3D34FF724138FF78453BFF7E4B41FF8B5A4FFFA77368FF9EAFB8FF - 74D7FAFF4EC0E8FF379CBFFF2A6177FF2D485AFF3B637AFF3F6981FF3E6882FF - 3E6881FF3F6981FF3E6981FF3E6881FF4C738AFF7896A7FF83CFEAFF55C5EDFF - 349CC1FF3A474FFF5C261BFF6C362BFF6B3227FF642E23FF5D281EFF542218FF - 4C1D13FF471A11FF451B13FF4B241BFF5B352DFF734F47FF916C65FFB48B84FF - D1A6A0FFE5B9B2FFEFC1BAFFEFC0BAFFE9BBB4FFE4B6AFFFE1B3ACFFE4B9B2FF - E7CAC6FF85D2EDFF4EC1EAFF3698BBFF2E6278FF2F5A6EFF326278FF3D7994FF - 4AB9E2FF49BEE7FF3298BBFF524A53FF833332FF8F393AFF8C3233FF8A3031FF - 8B3032FF8C3233FF8D3334FF8D3435FF8D3435FF8D3435FF8D3537FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8D3435FF8C3334FF8B3132FF8B3031FF8C3334FF933C3CFF9F4E4EFF - AF6765FFC1837FFFD09994FFDAA8A2FFE0B1AAFFE2B4AEFFE3B5AEFFE3B5AEFF - E2B5AEFF4C7893FF4C7892FF4D7A92FF4E7891FF4E7F99FF48B9E0FF47C2ECFF - 42B2D9FF3496B8FF74858EFFC3958CFFD5ABA3FFC9A39DFFAA857FFF8B665FFF - 724D46FF674139FF683E36FF6F423AFF7A4940FF845046FF8B554BFF90594DFF - 935A4FFF945A4EFF955B4FFF976055FFA4695DFFB48075FF9DB5C1FF70D4F7FF - 50C3ECFF3BA3C8FF2C6F87FF2B4656FF385D73FF3F6A82FF406A82FF406881FF - 3F6982FF406982FF3F6881FF426B84FF607E92FF87B2C5FF72D5F7FF45B7DFFF - 3183A0FF4E312DFF703529FF7C3D31FF7A3B2EFF79392DFF77382CFF74372BFF - 723529FF6E3327FF693024FF622B20FF5B281FFF521E13FF46140AFF411911FF - 4B241CFF603A33FF805A53FFA78078FFCBA19AFFE4B8B1FFEFC2BCFFF6C9C1FF - D9D0D2FF70D1F2FF48B9E1FF30829FFF2F4B5BFF3C5A6DFF42667CFF50738AFF - 63B5D6FF54C8F1FF35A3CAFF4E7282FFA36E69FFBA7D7BFFB46F6CFFA85D5BFF - 9E4D4DFF964141FF903839FF8C3334FF8B3335FF8D3637FF913131FF8A3032FF - 8B3234FF8C3334FF8D3335FF8D3435FF8E3536FF8E3536FF8E3536FF8E3536FF - 8E3536FF8E3536FF8D3435FF8D3435FF8C3334FF8C3233FF8B3132FF8B3031FF - 8B3132FF8D3435FF923B3BFF9A4747FFA65958FFB5706DFFC58884FFD39D97FF - DCABA5FFE2B3ADFFE4B7B0FFE4B7B0FFE4B7B0FFE4B6B0FFE4B6AFFFE3B6AFFF - E3B6AFFF4D7A92FF4C7993FF4C7992FF4D7B93FF4C768EFF4A819CFF48B8DFFF - 48C1EBFF44B4DAFF37A1C6FF59889AFF654E49FF663A2FFF6B4138FF764B43FF - 84554CFF905C52FF976055FF9B6256FF9D6357FF9E6257FF9E6256FF9E6256FF - 9F6357FFA0655AFFA76B5FFFB37568FFB1938FFF8FC2D6FF69D3F8FF50C2E9FF - 3CA6CAFF2F7690FF2A4859FF36576CFF3F6881FF406A83FF406A82FF406982FF - 406982FF406A83FF3E6982FF4B738AFF7592A3FF86CCE5FF5DCCF2FF38A7CDFF - 385F6EFF622A1FFF7A3E33FF803F32FF7E3D30FF7D3C2FFF7C3B2EFF7B392DFF - 79382BFF773629FF763529FF7A3F34FF894C40FF7E5C57FF663C36FF601F10FF - 592318FF501F16FF46160DFF40140BFF471E16FF5A342CFF7C5851FFBB938BFF - B4C9D3FF63D1F7FF41AFD5FF2F748DFF375667FF47738BFF4C7B95FF5D8298FF - 6FABC5FF5CCDF4FF3DAFD7FF3E7E96FFA3837EFFD6ABA5FFE0B1ABFFDAA8A2FF - D39E98FFCB908CFFC18380FFB97977FFBC7572FFB77372FF91646AFF8B3F3FFF - 8B3938FF8E3A3AFF8F3737FF8D3435FF8C3334FF8C3334FF8C3334FF8C3334FF - 8C3334FF8D3434FF8E3536FF903838FF923C3CFF974242FF9C4A4AFFA45655FF - AE6563FFB97673FFC58783FFD09893FFD9A6A0FFDFB0AAFFE4B6AFFFE5B9B2FF - E6B9B2FFE5B9B2FFE5B8B1FFE4B7B0FFE3B7B0FFE2B6AFFFE3B7B0FFE5B8B1FF - E7BAB3FF4C7993FF4C7993FF4C7993FF4D7994FF4E7C95FF4A738CFF437892FF - 42ADD2FF47C0E9FF47B9E1FF3AAFD7FF3D94B3FF627079FF8B5E56FFA15D4EFF - A86253FFA96759FFA9695CFFA96A5DFFA9695DFFA8695DFFA9695CFFAB6A5CFF - B06D5FFFB57668FFB08C86FF9AB1BCFF78D2F0FF5ED1F9FF4BBDE5FF3AA2C4FF - 2E748CFF2A495AFF35566AFF3F6880FF416A83FF406983FF406A83FF406A83FF - 416982FF406982FF406A83FF597B90FF84ADC0FF77D6F7FF4ABDE5FF328BA9FF - 4B3A39FF713428FF804135FF804033FF7F3E32FF7E3D30FF7D3C2FFF7C3B2EFF - 7B392CFF7A392CFF804438FF97594CFF999396FF6DCCEDFF49B0D5FF4E626EFF - 5D2A21FF652113FF672C20FF642B20FF5C241AFF501D13FF4D2017FF6E443BFF - 83B6CAFF5DD1F9FF3BA3C7FF2C6278FF375669FF4A7791FF4E7C95FF598198FF - 6F9DB4FF64CDF1FF48BBE3FF328BAAFF917C7BFFD4A79FFFE4BCB5FFE6BCB5FF - E6BCB5FFE6BDB6FFEDBFB8FFEFC3BCFFCDC6CAFF90CBE2FF50C8F0FF568DA4FF - 976A67FFB37671FFBA7C7AFFBB7875FFB77270FFB5706DFFB46E6CFFB46E6CFF - B5706DFFB87471FFBC7A77FFC0817DFFC78985FFCD938FFFD39D98FFDAA7A1FF - DFAFA9FFE3B5AEFFE5B8B1FFE6BAB3FFE6BAB3FFE6BAB3FFE5B9B2FFE5B9B2FF - E5B9B2FFE7BAB3FFEABDB6FFEDC1BAFFF2C5BEFFF5C8C1FFF1C5BEFFE8BDB6FF - E0B7AFFF4D7A94FF4D7A94FF4D7A93FF497690FF42708AFF3D6C85FF3A637BFF - 3B667FFF4199BAFF45B9E0FF49BCE2FF46BCE3FF41BAE2FF4CABCCFF6A94A6FF - 898387FF9E7873FFA9736AFFAC7469FFAC7469FFAD766BFFAB7D74FFA78985FF - 9D9DA2FF8AB8CAFF72CEEDFF60D3F9FF52C6EEFF44B3D8FF3795B5FF2D687FFF - 2B4758FF36586DFF3F6981FF416B83FF416A82FF406A83FF406A83FF406A83FF - 416A83FF406881FF416C84FF5B7B90FF79BED8FF64D1F6FF41B8E0FF3A6D81FF - 5C291EFF793E33FF834236FF814134FF803F33FF7F3E31FF7E3D30FF7D3C2FFF - 7C3B2EFF83483DFF9A6054FF9CA2A8FF77D8F9FF58CBF2FF48C0EAFF40BAE3FF - 4195B3FF515055FF622417FF6A2416FF723023FF7A392CFF874B3FFF938180FF - 79CFEDFF50C5EDFF3897B7FF265063FF2B4E63FF38657FFF3C6A83FF49758DFF - 668CA2FF6BC7E8FF51C6EEFF37A3C8FF698A98FFC09B95FFE2AFA6FFEAB9B1FF - EDC0B8FFF3C6BEFFDDCECEFF9ED2E5FF6BD1F4FF57CBF3FF4CC1EAFF38ABD2FF - 488BA4FFA18683FFCDA19BFFDDB2ACFFE2B4ADFFE1B2ACFFE0B1ABFFE0B2ABFF - E0B2ACFFE1B3ACFFE2B4AEFFE3B6AFFFE4B7B0FFE5B8B2FFE5B9B2FFE6BAB3FF - E7BBB4FFE8BCB5FFEABEB7FFECC0B9FFEFC2BBFFF2C5BEFFF3C7C0FFF4C7C0FF - F1C5BEFFEBC0B9FFE0B6AFFFCFA69FFFB68F87FF926C65FF6A453DFF542E26FF - 4A251CFF4B7892FF46748EFF3F6D87FF3C6983FF3D6A83FF406C85FF426F87FF - 416880FF3D6177FF3D7D98FF40A4C5FF44B6DBFF48BAE1FF47C0E9FF43C6F1FF - 46C7F2FF4FC4ECFF5CBFE2FF62BEDDFF64BEDCFF67C0DFFF67C6E7FF63CDF1FF - 5DD3FAFF56D1F9FF4FC5EDFF46B6DBFF3BA1C2FF327F9AFF2B576AFF2E495AFF - 385C72FF3F6982FF416B83FF406A83FF406A83FF3F6A83FF406A83FF406A83FF - 406A83FF3F6982FF406B83FF4D7087FF609CB6FF5CD0F7FF48BCE4FF3790AEFF - 4F3D3CFF733629FF814337FF824135FF814134FF803F33FF7F3E31FF7F3E31FF - 84493DFF9C6256FF9DA6ADFF77DBFCFF54C7EDFF44B5DBFF44B5DBFF48BAE0FF - 45BCE4FF3FB3DAFF46869EFF614A49FF7B534DFF838489FF7EAFC2FF72D0EFFF - 5DCFF4FF46B7DEFF3384A0FF274759FF32576EFF37627CFF356079FF38647DFF - 4B6E84FF64B3D1FF56CEF7FF47BBE2FF3EB0D6FF4FA9C9FF8BABBAFFCCBABBFF - D4C7C8FFA8CFDFFF73D2F2FF59CDF4FF4CBFE6FF44B4DAFF45B9E0FF49BBE2FF - 3AAAD0FF4189A3FF9C8583FFD2A79FFFE9BEB8FFF0C3BCFFF0C3BCFFF1C4BDFF - F2C6BFFFF3C7C0FFF4C8C1FFF5C8C1FFF5C8C1FFF5C8C1FFF2C6BFFFF0C4BDFF - EBBFB8FFE4B9B3FFDBB1AAFFCFA69FFFC09891FFAF8881FF9C756EFF876159FF - 724C45FF5E3830FF4C251CFF3E160DFF360B02FF350700FF3C0A00FF440F04FF - 481106FF3D6B84FF3C6A83FF3F6B84FF426D87FF446F88FF456F88FF457089FF - 467189FF446D85FF3E6075FF3A6177FF3A7E99FF3D9BBAFF41ACCEFF46B5D9FF - 48BBE0FF48C0E7FF48C4EDFF49C6EFFF4AC7F0FF4AC6EFFF4BC5EDFF4AC1E7FF - 47B9DEFF42AED1FF3A9CBCFF32819BFF2B5F73FF294859FF304E61FF386078FF - 3C6880FF3C6880FF3C6780FF3B667FFF3B6780FF3B667FFF3A667FFF3B667FFF - 3A667FFF3B667FFF3A667FFF3F6981FF4A6F86FF59B9DBFF52CCF4FF3FB1D6FF - 397185FF58281EFF713B31FF7B4135FF7C4035FF7C3F33FF7C3E31FF81473CFF - 9A5E52FF9FA4AAFF78DCFDFF54C8EFFF3DA7CAFF317A93FF33748CFF40A8CAFF - 47BCE3FF49BCE3FF45C1E9FF49C3EBFF5BC9EDFF60D7FEFF59D4FCFF4EC5ECFF - 45B6DBFF399BBBFF2B667CFF2B4758FF386078FF3A647DFF39637CFF3A657EFF - 42657CFF4C89A4FF4CC6EEFF46BAE1FF48BCE3FF43BDE5FF3DBEE9FF4EC6EEFF - 5ECCF1FF58CEF6FF50C6EDFF43B2D8FF3795B4FF31738CFF3A8AA8FF48C1E8FF - 4BC0E7FF3AAAD0FF4389A2FF927975FFB78F88FFC59F98FFC29A93FFBC948DFF - B89089FFAD877FFFA37C75FF9B736CFF8D6860FF815B54FF744F48FF68423AFF - 5B362EFF502921FF461F16FF3E150DFF380E05FF340900FF340600FF370700FF - 3B0900FF410C02FF481106FF4F150AFF54190DFF581B0FFF5A1C0FFF5B1C0FFF - 5C1C0FFF416D86FF436E88FF457089FF467089FF467089FF467089FF467089FF - 467089FF467089FF457088FF41687FFF3B5B70FF365B70FF35697FFF367C95FF - 3889A5FF3A91AEFF3B96B4FF3B97B4FF3B96B4FF3A94B2FF3891AFFF3589A4FF - 317C95FF2D697FFF285568FF284859FF2C4B5EFF345B72FF396780FF3C6A83FF - 3C6A84FF3D6C85FF3E6D86FF3E6D87FF3F6E88FF3F6E88FF3F6E88FF3F6E88FF - 3E6D87FF3E6D87FF3C6B85FF3D6D86FF416B82FF518BA6FF59D0F6FF4CC1E7FF - 3AA1C3FF39454BFF461A10FF52241BFF56241AFF5B261CFF643127FF824F44FF - 9C9698FF7EDBF9FF57C9EFFF3EA8CBFF2E6F87FF274353FF2E495BFF346076FF - 3C8CAAFF44B3D7FF49BEE5FF4CC4EBFF4DC6EEFF49BBE1FF41ACCFFF3896B5FF - 317992FF2A5C70FF294A5BFF33566CFF3B647DFF3B647DFF3A637CFF3A647CFF - 3C657EFF386078FF366D87FF37829EFF3C9BBAFF44B1D5FF4ABCE2FF4BC3EBFF - 4CC3EAFF46B7DBFF3B9FBFFF307890FF264F62FF264254FF2D4E63FF3E92B2FF - 4CCAF3FF4CC2EAFF3EADD1FF2F6E83FF2C110CFF361007FF3A1007FF380C04FF - 370B02FF360A00FF350800FF360700FF370700FF390800FF3C0900FF3F0B01FF - 430E03FF471105FF4C1408FF4F160BFF53190DFF561A0EFF581B0FFF5A1C10FF - 5B1C10FF5B1C0FFF5C1C0FFF5C1C0FFF5D1B0EFF5E1C0EFF5F1C0EFF5F1C0FFF - 5F1C0FFF457089FF467189FF47718AFF47718AFF46718AFF467089FF467089FF - 467089FF457088FF456F88FF457088FF426E87FF3E657DFF385A70FF325266FF - 2E4F62FF2C5063FF2B5164FF2B5265FF2B5366FF2C5365FF2D5467FF2F5366FF - 315366FF35576AFF3B6176FF436F87FF497A94FF4C7E99FF4E809BFF4F819CFF - 50829DFF50839EFF51839EFF51849FFF52849FFF51849FFF52849FFF52849FFF - 51849FFF51839EFF50839EFF50829DFF52849FFF59849CFF5DB6D6FF54D0F7FF - 46B7DCFF37859FFF767372FF968784FF907D79FF7A625CFF6B4C45FF785D57FF - 7EC9E2FF64D4F8FF48BCE0FF327E98FF243F4EFF305367FF39647CFF375D73FF - 34556AFF376D85FF409DBDFF44B3D6FF40A7C9FF378FACFF2E6C82FF295062FF - 294657FF2D4B5DFF35596FFF3A627BFF3B647DFF3B647DFF3B647DFF3A647DFF - 3A657DFF39627AFF34566BFF2F4D61FF2E5367FF336E86FF3C94B3FF42ADD0FF - 3EA4C4FF34849FFF295A6DFF264253FF2C4B5FFF335C74FF355F78FF3B6078FF - 55A3C2FF5AD1F8FF4BBCE2FF379BBCFF314047FF450C00FF531A0FFF54190EFF - 551A0EFF571B0FFF581B10FF591C10FF5A1C10FF5B1D10FF5B1C10FF5C1C10FF - 5C1C10FF5C1C0FFF5D1C0FFF5D1C0FFF5D1C0EFF5E1C0FFF5E1C0FFF5E1C0FFF - 5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF - 5E1C0FFF47718AFF47718AFF47718AFF46718AFF467089FF457089FF456F88FF - 446F88FF426E87FF406C85FF3E6A83FF3D6982FF3C6983FF3C6A84FF3C6A83FF - 3D6A83FF3F6B83FF416C84FF436E85FF446F86FF467289FF48748DFF4A7891FF - 4D7C96FF4F809BFF51839EFF51849FFF51839EFF51839EFF51839EFF51839EFF - 51839EFF51829EFF50829DFF50829DFF50829DFF50829DFF50829DFF50829DFF - 50829DFF50829DFF50829DFF50829DFF51829EFF55849EFF5E93ADFF5CCBF0FF - 51C8EFFF38A3C6FF6B9BAAFFD9D5D4FFFFFFFFFFFFFFFFFFF9FBFAFFEDEDEDFF - 95DAF1FF52CBF3FF4BBDE2FF338099FF264455FF315A71FF36637CFF37637CFF - 36617AFF34586EFF33576DFF35758EFF306F86FF294C5EFF2B4657FF315166FF - 375E75FF3A647DFF3B657DFF3B647DFF3B647DFF3B647DFF3B657DFF3B647DFF - 3B647DFF3A647DFF3A657DFF39627AFF355A71FF315065FF305266FF326A81FF - 2E6A80FF274758FF2B4759FF33596FFF38627AFF39627BFF3F6880FF5B798DFF - 77A8BEFF67D6FAFF48B9DFFF3488A4FF3A2422FF521408FF5D1C10FF5D1B0FFF - 5D1B0EFF5E1C0EFF5E1B0EFF5E1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF - 5F1C0FFF5F1C0FFF5F1D0FFF601C0FFF5F1C0FFF601C0FFF601C0EFF5F1C0EFF - 5F1C0FFF5F1C0FFF5F1C0FFF5F1C0EFF5F1C0FFF5F1C0FFF5D1C0EFF5B1B0FFF - 5A1C10FF47718AFF47718AFF467189FF457089FF436E87FF416C86FF3E6A83FF - 3C6982FF3C6983FF3E6B85FF416F89FF45758FFF497B95FF4D7F9AFF50839EFF - 5285A0FF5285A1FF5285A0FF5285A0FF5284A0FF51849FFF51849FFF51839FFF - 51839EFF51839EFF50829DFF50829DFF50829DFF51839EFF51839EFF50829DFF - 50829DFF51839EFF50839DFF50829DFF51839EFF51839EFF50839EFF51839EFF - 50839EFF50829DFF51839EFF51839EFF50819CFF51849FFF59839CFF60ACCAFF - 58D3FAFF4ABEE3FF3590ADFF929FA3FFE1DCDBFFFBFBFBFFFEFFFFFFFFFFFFFF - E2F5FBFF59CDF2FF4FC7EDFF3FA5C6FF34697EFF3D6176FF457690FF41718BFF - 3B6A84FF386680FF335D75FF2D5065FF2C4C5FFF305368FF376179FF3A657EFF - 3B657FFF3B667FFF3D677FFF3E687FFF3F677FFF3E657EFF3D647DFF3C647CFF - 3B647EFF3B657EFF3A657EFF3A657DFF3B647DFF3A647DFF375E76FF325267FF - 2F4D61FF32556BFF386179FF39647CFF39637BFF3A647CFF4F7288FF7C9BACFF - 86D9F4FF5CCEF3FF3DA9CCFF34515DFF480C00FF5B1C10FF601D0FFF5F1C0FFF - 5F1C0FFF601C0FFF601C0EFF601C0FFF601C0EFF601C0FFF601C0FFF601C0FFF - 601C0FFF601C0EFF5F1C0FFF5F1C0EFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF - 5F1C0FFF5F1C0FFF5E1C0FFF5D1C0EFF5C1B0FFF5B1C10FF571C10FF4D1409FF - 3E0800FF457089FF436F88FF406C85FF3E6B84FF3D6B84FF3F6E87FF43728CFF - 477791FF4B7C97FF4F809BFF51839EFF52849FFF52849FFF51839EFF51839EFF - 50829DFF50829DFF50829DFF50829DFF50829DFF50829DFF50829DFF50829DFF - 50829DFF50829DFF50829DFF52849FFF52849FFF50829DFF51839EFF52849FFF - 51849FFF50829EFF50839EFF51839FFF51839EFF50829DFF51829DFF50829DFF - 51829DFF51839EFF50839EFF50829DFF50829DFF50829DFF54849EFF5D8CA5FF - 5EC5E8FF55D0F7FF3FADD0FF508DA0FFC2BEBDFFF0F0F1FFFEFEFEFFFBFDFDFF - FFFFFEFF9BDFF5FF4CCBF4FF4CBDE1FF388CA7FF385E71FF49768DFF5284A0FF - 51839EFF4D7F9AFF497A94FF41718CFF3B6983FF37657EFF35627BFF37637DFF - 3E6881FF476D83FF4E7085FF517389FF51778EFF4E788FFF49748DFF426C85FF - 3B627AFF385B73FF385C74FF39627AFF3A657DFF3A647DFF3A647DFF3A637DFF - 3A637CFF3A637CFF3A637DFF3A647CFF39627BFF436B82FF6B8697FF8BC5DBFF - 6FD9FAFF48BBE0FF358199FF3D1B15FF541609FF5F1C10FF5F1C0FFF5E1C0FFF - 5F1C0FFF5F1C0FFF5F1C0FFF5F1C0EFF5F1C0EFF5F1C0FFF5F1C0FFF5F1C0FFF - 5F1C0EFF5F1C0EFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5E1C0FFF5D1B0EFF - 5C1C0FFF5C1D10FF5A1D11FF561B0FFF4C1409FF3E0800FF320200FF3E1810FF - 5F443EFF3D6A83FF3E6C85FF47758EFF547E96FF527D96FF4D7A95FF50819CFF - 52849FFF51839FFF50839DFF50829DFF50829DFF50829DFF50829DFF50829DFF - 50829DFF50829DFF50829DFF50829DFF50829DFF50829DFF50829DFF51839EFF - 50829DFF50829DFF51839EFF487994FF3F6F88FF3D6B85FF3D6C86FF41718BFF - 4A7C96FF5788A1FF5F8AA2FF56819BFF4D7B95FF4E809BFF50829DFF50829DFF - 50829DFF50829DFF51829DFF51839EFF51839EFF50829DFF51839EFF57839CFF - 62A3BFFF5ED5FAFF50C5EAFF3597B6FF889DA4FFDFDAD9FFFBFCFCFFFDFEFEFF - FEFEFEFFEDF8FBFF64D1F3FF50CBF1FF43ACCDFF357289FF3D6074FF4D7D97FF - 51829DFF50829DFF52839EFF5285A0FF51839EFF4D7F9AFF4C7C96FF537C93FF - 5D8095FF6793A8FF6DADC6FF6ABEDBFF65C7E7FF5FCAECFF5AC8ECFF54C3E7FF - 4CB3D7FF4496B6FF3B728CFF355970FF35576DFF396279FF3A647CFF3A637CFF - 3A637CFF3A637CFF3A637CFF3A637CFF3C657EFF56778BFF84A9BBFF81DCF9FF - 56C8EDFF3BA2C4FF364147FF4A0E01FF5C1D10FF601C0FFF5E1C0FFF5F1C0FFF - 601C0FFF601C0FFF601C0FFF601C0FFF5F1C0FFF5F1C0FFF5F1C0FFF5F1C0FFF - 5F1C0FFF5E1C0FFF5E1B0FFF5C1B0FFF5C1C0FFF5B1D10FF5A1D11FF571C10FF - 50160BFF430C02FF380400FF340600FF431E17FF684F4AFFA39693FFDFDCDBFF - FFFFFFFF4D7D97FF5D88A0FF7599ADFF7CAFC6FF60B6D6FF4988A3FF476F87FF - 4B768EFF4E7F9AFF50829EFF50829DFF50829DFF50829DFF50829DFF51839DFF - 51839EFF50839EFF51829DFF51839EFF51839EFF51839EFF50829DFF50829DFF - 50829DFF51839FFF43738DFF38657FFF3B6781FF3E6A83FF406C85FF456F87FF - 51748AFF65879BFF74A9C0FF5DB6D6FF46819CFF466C83FF4B7992FF4F819CFF - 50829DFF50829DFF50829DFF50829DFF51839EFF50829DFF4F819CFF5889A3FF - 7196ABFF74C9E6FF5BD3F9FF40AED0FF47869AFFBDBCBCFFF5F5F5FFFFFFFFFF - FCFEFEFFFFFFFFFFB5E7F7FF50CEF6FF50C4E8FF3B98B4FF366073FF456D84FF - 4F819DFF50829DFF4F819CFF50819CFF51829DFF5B8BA4FF7398ADFF85B0C4FF - 81CBE4FF74D9F9FF68DCFFFF5FD7FCFF58D2F7FF55CFF5FF54CEF4FF53CFF5FF - 53D1F7FF52CFF5FF4DC3E7FF43A2C1FF376E85FF305064FF355A70FF3A637CFF - 3A637CFF3A637CFF3B637CFF39637CFF456C84FF6E8A9CFF8CCFE6FF69D7FAFF - 45B7DBFF347185FF411208FF57190DFF601D0FFF5F1C0FFF5F1C0FFF5F1C0FFF - 5F1D0FFF5F1C0FFF5E1C0FFF5E1C0EFF5D1C0FFF5D1B0EFF5C1C0FFF5C1C10FF - 5B1D10FF5A1D11FF571C10FF51170CFF491005FF3F0800FF350200FF320200FF - 3B130CFF573832FF84716DFFB9B1AFFFE9E9E8FFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF7598ADFF80ABC0FF85CAE2FF76DBF9FF5BD7FCFF4CC1E4FF479FBDFF - 46819AFF48748CFF4B768FFF4E7D98FF50829DFF50839EFF50829DFF50829DFF - 50829DFF51829DFF51839EFF51839EFF51839EFF51839EFF51839EFF4F819CFF - 51849FFF477893FF396680FF416E87FF48728BFF50768DFF5C7D92FF6B8DA1FF - 78AAC0FF7ACAE4FF70D9FAFF59D5FAFF49B8DBFF4391ADFF427188FF466F86FF - 4D7D97FF50829EFF50829DFF50829CFF50829DFF50819CFF5586A1FF6E95ABFF - 8BB3C6FF7DD7F3FF5AD0F5FF3EA6C7FF407F92FFB7B7B8FFF4F4F4FFFFFFFFFF - FDFDFDFFFDFEFEFFF8FCFCFF7FD9F6FF54CEF4FF45AFCFFF34778EFF3D6175FF - 4E7E98FF51839EFF50829CFF52839EFF5E8CA5FF799CB0FF8BBFD5FF7FDBF7FF - 67D8FBFF54CAEEFF46B8DBFF3DAACCFF3AA2C3FF3AA0C0FF3AA0C0FF3AA1C1FF - 3CA7C8FF41B3D7FF4CC1E6FF50C6EAFF48B8DAFF3A87A1FF2D5164FF325469FF - 39627BFF3A647DFF3A647CFF3A647DFF4C6E85FF79A4B8FF7FDFFCFF57CBF0FF - 3C9CBAFF372A2BFF4C1206FF5C1D11FF5D1C10FF5C1C10FF5C1D10FF5C1D10FF - 5C1D11FF5B1E11FF5A1D11FF581C10FF551B0FFF52180CFF4B1308FF440D02FF - 3D0700FF360400FF340500FF3A1108FF4A2720FF654A44FF8A7875FFB7ADABFF - E0DDDCFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFDFDFFFCFBFBFFFDFDFDFF - FEFEFEFF87C9E0FF7ED9F5FF6BDCFDFF5AD0F4FF51C8EDFF52C8ECFF51C8ECFF - 4CBFE2FF49A9C8FF498EAAFF4A7C96FF4B758EFF4C7892FF4E7F9AFF50829DFF - 50829DFF50829DFF51829DFF51829DFF51839EFF51839EFF50829DFF50829DFF - 50829DFF3F6E87FF487189FF57798FFF628296FF6E95A9FF79B3CAFF7BD0EBFF - 72DDFEFF63D7FAFF54CAEEFF4DC6EAFF50C4E7FF4DC0E3FF45ABCBFF3E809AFF - 3D667CFF467088FF4F819CFF52849FFF51829DFF5586A1FF6C94A9FF8AB1C4FF - 88DAF3FF67D9FCFF47B7D9FF37869EFF909A9DFFDAD8D7FFF9FAFBFFFEFEFEFF - FEFEFEFFFDFEFEFFFEFEFDFF96E0F7FF58D0F5FF45AECFFF337489FF3C5F73FF - 4E7E98FF51829EFF51829DFF5E8CA5FF7C9EB2FF8EC7DCFF7CDFFDFF5ED1F5FF - 44B5D7FF3795B2FF4A8BA0FF628F9DFF7896A0FF809BA3FF819CA4FF829DA6FF - 7B9CA7FF639DAFFF46AAC8FF43BCE1FF51C5EAFF4ABADCFF3B8FAAFF2A4F62FF - 2E5166FF36617AFF38627BFF38637CFF456980FF689BB1FF70DCFDFF53CAEEFF - 3D98B4FF331D19FF441006FF4E160BFF4C1307FF491106FF460E03FF410B00FF - 3D0700FF390400FF350200FF330200FF340700FF3A1108FF46221AFF583933FF - 725B55FF92817DFFB3A9A7FFD5D1D0FFF1F1F0FFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFCFBFBFFFCFBFBFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFF - FFFFFFFF6EDDFEFF5FD4F6FF4ABDE0FF3CA2C1FF429EBAFF3FACCCFF47BBDFFF - 51C6EAFF53CAEEFF50C8ECFF4EBCDFFF4AA2C0FF447B93FF446D83FF4C7C96FF - 50829DFF4F829CFF50819CFF50839EFF51839EFF51839EFF4F819CFF51839EFF - 4C7E99FF4A748BFF6B8EA2FF7AAFC4FF78C5E0FF75D7F5FF6DDDFEFF60D5F8FF - 51C6EAFF42B3D5FF3D99B5FF4790A8FF46B9DBFF4EC7EBFF51C4E8FF4BBDDFFF - 409BB8FF366B82FF375B71FF426F87FF51849EFF6C94AAFF8BB0C3FF8ADAF3FF - 69D9FBFF47B8DBFF3588A2FF839398FFD1CDCBFFF3F4F5FFFEFEFEFFFDFDFDFF - FDFDFDFFFEFEFFFFE6F6FBFF7BDBF8FF56CBEEFF3C99B6FF335D70FF42697FFF - 4F819CFF50829DFF5A8AA3FF7A9DB1FF90C8DCFF7BDFFDFF59CEF2FF3DA7C8FF - 43889DFF829399FFB4B2B1FFCEC9C7FFDAD5D4FFDFDBD9FFE1DDDBFFE1DCDAFF - DFD9D8FFD8D2D1FFBFC9CCFF76BDD3FF44C0E5FF51C8ECFF4AB9DBFF398CA6FF - 2A4E61FF30586EFF36647DFF36637DFF3B667FFF4D748BFF66C7E6FF5AD2F7FF - 45B3D5FF315561FF310B03FF3F1C14FF47221BFF4C2922FF54352DFF61443DFF - 6D554FFF806B66FF958581FFAB9F9CFFC2BBB8FFD9D5D4FFEDEDECFFFDFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFDFDFFFCFBFBFFFCFBFBFF - FDFCFCFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF4CC1E3FF3CA6C6FF3C89A0FF708E98FFA3A7A9FF8CABB4FF5CA3B8FF - 41A7C5FF42B5D8FF4DC4E7FF54CCF0FF50C9EDFF44AAC9FF386D82FF436A80FF - 4F819CFF50829DFF4F829DFF51829DFF51839EFF51839EFF50829DFF51849FFF - 4C7D97FF608094FF8BC2D7FF7EE5FFFF63DAFDFF57CFF3FF4BC0E3FF3FAECFFF - 3893AEFF436F7EFF62504FFF7E4A40FF6E7378FF4DABC8FF48C5EAFF51C8EBFF - 51C6E9FF49B7D8FF3C89A3FF305C72FF42657BFF7CA3B6FF8BDDF6FF69DAFCFF - 4ABDDFFF3D93ADFF889CA2FFD3CECDFFF2F3F3FFFDFDFDFFFCFDFDFFFCFDFDFF - FDFEFEFFFFFFFEFFBBEAF9FF68D8FAFF4BBBDCFF357B91FF37586BFF4B7993FF - 50829DFF5586A1FF7297ACFF91C1D5FF80E1FEFF5BD0F3FF39A3C3FF4C8495FF - A4A4A4FFD8D2D1FFEEEEEEFFF8F9FAFFFCFDFDFFFDFEFEFFFEFFFFFFFDFEFEFF - FCFDFEFFF8F9FAFFF6F3F2FFF4EBE8FF9FD3E3FF49C7EDFF53CCF1FF49B7D7FF - 39829AFF3A5D71FF487790FF4D7F9AFF4D7F9AFF568098FF68ABC6FF63DAFEFF - 4DC2E5FF3C90A9FF959798FFD7D3D1FFEEF0EFFFF5F7F7FFFCFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FDFCFCFFFCFBFBFFFCFCFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF3889A2FF5D8A98FF9FA3A4FFCEC9C7FFE3E1E0FFE6E1DFFFD2CFCEFF - ABB9BEFF75AABAFF4AA7C3FF42B9DDFF54CDF1FF4BBADAFF377F96FF3B5D71FF - 4D7D97FF51839EFF4F819CFF50819CFF51839EFF51839EFF50829DFF50839FFF - 52819AFF7191A3FF8AD7F0FF65D7F9FF4ABCDEFF3DA0BDFF3D8399FF4C6872FF - 66524FFF814D43FF945A4FFF9E655AFF9E5C4EFF8A5F57FF628998FF47B8DAFF - 4CC7ECFF52C6E9FF4FC2E4FF49B2D2FF539FBCFF77D0EDFF6ADCFDFF4CBEE1FF - 358AA4FF475155FF9B908CFFE0E2E2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFF - FFFFFEFFE9F6FAFF89DEF8FF5CD3F6FF40A4C2FF326275FF40657AFF4F819BFF - 51839EFF648FA7FF8BB0C3FF8BDEF8FF63D6F8FF3FABCCFF4B8495FFAAA8A7FF - DDDCDCFFF8F9FAFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFFFFFEFFFDFEFEFFF9FBFBFFFFFAF7FFABE1F2FF4CCEF6FF54CBEFFF - 42A8C6FF366B80FF41667BFF4F819CFF51839FFF5787A1FF6997AEFF6CD2F2FF - 5AD1F5FF3CA9CAFF7CAAB9FFE7E3E2FFFFFFFEFFFFFFFEFFFFFEFCFFFDFBFBFF - F9FAFBFFFBFBFBFFFBFAFAFFFBFBFBFFFCFCFBFFFDFDFDFFFEFDFDFFFEFEFEFF - FEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF9BA0A2FFC1BEBDFFE3E2E1FFF5F7F7FFFDFEFEFFFBFCFDFFF5F6F6FF - EFEBEBFFDFDAD8FFC8CCCDFFA1D3E3FF5BD5F8FF53C9ECFF3D93ADFF365C6FFF - 48758DFF51839EFF50829DFF4F829DFF51829DFF51839EFF50829DFF51839EFF - 5D889FFF7BA2B4FF82DFFAFF55CCEFFF3E93ACFF595556FF764F47FF8C554AFF - 9B6155FFA56B60FFAB6F63FFAC6E62FFA86D61FFA26559FF9A584BFF7C6866FF - 569AAFFF48BFE2FF4EC6EAFF54CAEDFF5BD9FEFF5DD4F8FF49BBDCFF398BA3FF - 474243FF552920FF5E372FFF785149FF957B74FFB8B6B5FFCFE9F2FFB3E9F9FF - 91E0F8FF79DBF8FF67D9FBFF50C5E7FF3989A1FF35576AFF47748CFF4F819DFF - 5586A0FF779BAFFF93CEE3FF74DFFFFF4ABCDEFF418AA0FF9FA1A1FFDCDBDAFF - F8F9FAFFFFFFFFFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFEFEFFFFFFFEFF99E1F7FF4FD2F8FF - 50C1E3FF3A8CA5FF375C6EFF49768FFF50829DFF52849FFF628AA2FF6EC0DCFF - 5FD9FDFF50C6E9FF4BB8DAFF64C5E3FF89D6EEFFADDFEEFFD2E8EFFFEDEEEFFF - F8F1EFFFF6F4F4FFFAFBFBFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFE2E1E0FFF2F2F2FFFEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FEFFFFFFF8FAFAFFFCFAF9FFFFFFFEFF83DDF8FF57D2F6FF44A8C5FF356377FF - 436B81FF50829DFF51839EFF50829DFF51839EFF51839EFF4F829DFF52849FFF - 6B91A7FF87BACEFF78E1FFFF49BEE0FF427484FF7F4A3FFFA1695EFFAD7267FF - AF7367FFB07165FFAE7064FFAC6E62FFAB6D61FFAA6D60FFA56A5EFFA15D4FFF - 925549FF6F7277FF4EA3BDFF47C0E3FF4DC1E4FF43B0D1FF398399FF574442FF - 763F34FF8D574CFF9F7067FF94979BFF79B2C5FF69C7E3FF64D6F8FF5BD5FAFF - 58D2F7FF55CDF1FF4FC2E4FF41A5C1FF326D81FF3D5F73FF4F809BFF5184A0FF - 608DA5FF87ACC0FF8ADFF9FF5FD4F6FF399DBAFF748C94FFD1CAC8FFF5F7F7FF - FDFDFDFFFDFDFDFFFEFEFEFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFF - FFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFDFEFEFFFEFFFFFFF7FBFCFF73D9F8FF - 55D0F5FF44A9C7FF35677AFF42687EFF4F819CFF51839EFF58849DFF5E9CB8FF - 55CFF3FF4EC4E5FF50C6E9FF4DCAEFFF49CBF2FF47CBF1FF4FC9EDFF63C7E4FF - 88C1D2FFC1C9CBFFE8E6E6FFFCFCFCFFFFFFFFFFFDFDFDFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFFFFFFFFFCFEFFFFFDFEFEFFA9E6F9FF5CD6FAFF4BB8D7FF367287FF - 3E6176FF4E809AFF50829DFF50829DFF51829DFF50829DFF50829DFF5586A0FF - 789BB0FF8FD1E7FF6CDCFCFF41B1D1FF4E646CFF8C564BFFAE7469FFB27568FF - B17367FFAF7265FFAE7064FFAD6F63FFAC6E62FFAB6D60FFAA6B5FFFA76B5FFF - A2665BFF9B584BFF88574DFF64757DFF4391A9FF3F7789FF61443FFF824A3EFF - 945B50FFA6675AFFAC9B99FF84E8FFFF69E1FFFF5BD5F8FF53C7E9FF4CBBDAFF - 46ADCAFF3F9BB6FF37849BFF2E6679FF294C5EFF365F75FF42728BFF477993FF - 648BA2FF8CBCD0FF7DE3FFFF4FC4E6FF4491A7FFAEB2B3FFF3F3F3FFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFDFCFCFFFCFBFBFFFCFBFBFFFCFBFBFFFCFCFBFF - FCFCFBFFFCFCFCFFFDFDFDFFFEFEFEFFFFFFFFFFFCFDFEFFFFFFFEFFB8EAF9FF - 5AD5FAFF4DBCDDFF377B91FF3C5F73FF4D7E98FF50829DFF52839FFF4F7F9AFF - 46859EFF3F8BA5FF419BB6FF46ACCAFF4CB9D8FF51C3E4FF54CAEDFF50CCF0FF - 39B0D3FF5995A8FFCBC7C6FFF6F6F7FFFEFEFEFFFEFEFEFFFDFDFDFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFF - FEFEFEFFFEFEFEFFFCFDFDFFFEFEFEFFCCEFFAFF63D9FBFF52C5E6FF39849BFF - 395C6FFF4C7B94FF50829EFF50829DFF51829DFF51839EFF4F819CFF5B89A3FF - 82A6B9FF8ADDF5FF60D5F6FF3C9EBBFF494445FF88564CFFA87066FFAD7166FF - B07367FFB07366FFAF7165FFAD7064FFAD6F63FFAB6D61FFAA6C60FFA96B5FFF - A86A5EFFA4695DFF9F6255FF955345FF7C5148FF774A41FF8B5045FF995E53FF - 9D6054FFAB6B5EFFA9A8ABFF75E2FDFF55CAEDFF44A7C4FF38849CFF337085FF - 2F5F72FF2C5163FF2B495BFF2E4C5FFF345A70FF39637CFF38647DFF3A6780FF - 5C7D91FF86C3D9FF73E2FEFF49BCDDFF417787FF807470FFBFB9B7FFDAD7D5FF - EAE9E8FFF8FAF9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFCFCFCFFFCFDFDFFFEFEFEFFE2F5FBFF - 6CDAFAFF53C8EAFF3B8CA5FF385C6FFF4A7891FF51829EFF50829DFF4F809BFF - 48728AFF40667BFF3C6579FF3A6B81FF3B798FFF3F91AAFF4DBBDCFF59D5F9FF - 4EC1E3FF448FA6FFB2B3B4FFF2F1F1FFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFF - FFFFFFFFFEFEFEFFFDFEFEFFFFFFFEFFEAF7FBFF74DCFAFF57CEF1FF3E98B2FF - 365D70FF48738CFF51839FFF4F829DFF51829DFF50829DFF50829DFF638DA5FF - 89B4C7FF82E3FEFF54C9EAFF4191A9FF5C514FFF633C35FF8A584FFFA16B61FF - A96F63FFAD7165FFAF7266FFAE7165FFAD7063FFAC6E62FFAB6D61FFAA6C5FFF - A96A5EFFA8695DFFA6685CFFA2665BFF9E6054FF9C5F53FF9F6256FFA06154FF - 9E6155FFAE6E61FFA5ADB2FF70E3FEFF4FC1E2FF337388FF273D4CFF2F4C5EFF - 345266FF385A70FF3C637AFF3E6880FF3F6982FF3F6982FF3D6881FF436D85FF - 668598FF86CDE4FF6ADEFEFF48B9D9FF375A66FF3D1209FF4C221AFF50271FFF - 563129FF61413AFF6F544DFF7E6762FF8E7C77FF9F908CFFACA19DFFBDB5B2FF - C3BAB8FFC8C2BFFFE2E0DFFFFFFFFFFFFFFFFFFFFCFCFCFFFFFFFFFFF4FAFCFF - 7EDEFAFF56CEF0FF3D97B1FF365D6FFF48738BFF51839FFF50819CFF50829DFF - 50829DFF4E7F9AFF4B7992FF477188FF42687EFF436980FF599BB7FF66DCFEFF - 50C7E9FF4393ABFFADB0B1FFF0EFEEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFDFDFFFEFEFFFFFDFDFDFF8BE1FAFF5AD5F7FF45AAC7FF - 356679FF426A80FF50829DFF50829DFF50829CFF4F819CFF51839FFF6D93A9FF - 8DC3D7FF78E4FFFF49BBDCFF4F8A9CFFBFBDBCFFBBB4B1FF795B55FF71443BFF - 925F55FFA36C61FFA96F63FFAD7064FFAE7064FFAD6F63FFAC6E62FFAB6C60FF - A96B5FFFA86A5EFFA7695CFFA6675BFFA5665AFFA36559FFA26357FFA06255FF - 9F6356FFAF7063FFA2B2B9FF6EE3FFFF4DBDDDFF326E82FF2E495BFF3E677FFF - 416B84FF416A83FF416B84FF406A83FF416A83FF406983FF3F6982FF466E86FF - 69899CFF83D1EAFF67DCFEFF45B4D3FF415961FF612C21FF743D33FF70382DFF - 6A3328FF622C22FF5B261CFF542016FF4F1C12FF4A190FFF48180FFF471910FF - 45180FFF431910FF48241CFF634B45FFBDB7B4FFFFFFFFFFFEFDFDFFF6FBFCFF - 84DFF9FF59D0F3FF3F9CB6FF355E70FF467088FF51839EFF4F829DFF4F829DFF - 50829DFF51839EFF51839EFF50839EFF4F819CFF58859FFF76A7BEFF76DEFDFF - 52C9ECFF4595ACFFAEB1B2FFF0EEEFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFCFEFEFFFFFFFEFFABE8F9FF5ED8FBFF4CB9D9FF - 377488FF3E6277FF50819CFF53849FFF53839EFF52839EFF5887A1FF799BAFFF - 8ED1E7FF6DDFFFFF3FAAC9FF698D98FFD2CBC9FFFFFFFFFFF4F4F4FFA79995FF - 6B4841FF7A4A41FF99645AFFA56C61FFA96D62FFAC6F63FFAD6F63FFAB6D61FF - AA6C60FFA96B5FFFA8695DFFA6685CFFA5665AFFA46559FFA36458FFA16256FF - A06458FFB07063FFA0B2BAFF6DE3FFFF4EBEDDFF347186FF304C5FFF406B83FF - 426D86FF416B84FF416A84FF406A82FF406A83FF416A83FF406881FF466D86FF - 66869AFF7FD0E9FF66DCFEFF45B3D3FF455960FF6B3024FF804337FF804135FF - 7E3F33FF7C3F32FF7A3D31FF793C30FF763B2FFF74392DFF72372BFF6F3529FF - 6C3327FF683025FF602A1FFF4B190FFF2F0901FFA9A09DFFFFFFFFFFF4F9FAFF - 86E0FAFF59D1F3FF3F9CB6FF355E70FF467088FF51849FFF50829DFF50829CFF - 50829DFF50829DFF50829DFF51839EFF52849FFF618CA4FF80AABFFF7CDEFBFF - 53CAEDFF4595ACFFAEB1B2FFF0EEEFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFCFDFDFFFEFEFFFFD1F1FAFF65DAFBFF52C8E9FF - 3F91AAFF426B81FF5786A1FF5E8CA5FF5E8CA5FF5E8DA6FF6593ABFF7EA7BBFF - 84DAF4FF5FD6F7FF3B9AB5FF89999EFFE1DDDCFFFBFBFBFFFFFFFFFFFFFFFFFF - E0DDDCFF907A75FF6B423AFF835147FF9D675DFFA56B5FFFA96D61FFAB6E61FF - AB6D61FFAA6C60FFA86A5EFFA7695CFFA6685BFFA5665AFFA46559FFA26357FF - A16458FFB06F62FFA1AEB5FF6DE2FEFF50C5E5FF3B849DFF355569FF3E627AFF - 3E627AFF3F657DFF416981FF416C84FF406A83FF416B83FF3F6982FF446C85FF - 607F93FF7BC9E2FF67DEFFFF46B6D6FF445E67FF6B3125FF834539FF844336FF - 824235FF814034FF803F33FF7E3E31FF7D3C2FFF7B3B2EFF7A3A2DFF79382BFF - 77372AFF753529FF723427FF6C3327FF501F15FF482C25FFF3F1F0FFF6FDFEFF - 82DFFAFF58CFF1FF3D96B0FF355C6EFF47728BFF50839FFF50819CFF50829DFF - 52849EFF5586A0FF5887A0FF5D88A1FF638CA3FF7095ABFF83AFC4FF79DDFAFF - 53CBEDFF4494ABFFADB1B2FFF0EFEFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFDFDFDFFFFFFFFFFF0F9FCFF77DCFAFF59D3F5FF - 4FBFDFFF53BCDDFF61CBEBFF66CDECFF66CEEEFF67CFEFFF6AD2F1FF71D6F4FF - 6CDDFDFF50C6E7FF438EA3FFA8A9AAFFEDECEBFFFFFFFFFFFDFDFDFFFCFBFBFF - FFFFFFFFFFFFFFFFC9C3C0FF7B605AFF6B3F36FF8A574DFF9F685DFFA56A5FFF - A96C60FFAA6D60FFA96B5FFFA86A5DFFA7695CFFA6675BFFA4665AFFA36558FF - A16459FFAF6D5FFFA2A8ADFF6EE2FFFF57CFF1FF50C0E0FF4FB9D9FF4EABCAFF - 4891ADFF437993FF3E667EFF3B5C73FF3D647BFF3F6982FF3F6A83FF426C84FF - 57778CFF76BBD3FF6AE1FEFF4ABDDEFF426976FF673024FF814539FF854437FF - 834235FF824134FF814033FF7F3F32FF7E3D31FF7D3C2FFF7C3B2EFF7B3A2DFF - 7A382BFF79372AFF773629FF713326FF5F2A1FFF411D16FFDED6D3FFEBFFFFFF - 76DCFAFF55C9EAFF3A8AA2FF375A6CFF4A7791FF51839EFF51829DFF5888A2FF - 648DA4FF6C93A9FF709DB4FF73ABC3FF76BED7FF78CFEAFF77DBF8FF6ADDFFFF - 50C4E6FF438EA4FFAFB0B1FFF1F1F0FFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFDFEFEFFFFFEFEFF95E2F9FF58D6FAFF - 5AD3F6FF5AD8FBFF5DD9FCFF5ED9FBFF5ED9FCFF5ED8FBFF5ED8FAFF5DD7FAFF - 5BD4F6FF41ADCBFF598A99FFC6C1BFFFF6F7F7FFFFFFFFFFFDFDFDFFFEFEFEFF - FDFDFDFFFDFCFCFFFFFFFFFFF9FAFAFFB2A7A3FF71504AFF72443BFF915E54FF - A0685DFFA4695DFFA86B5EFFA96B5EFFA7695DFFA6685CFFA5675BFFA46559FF - A2655AFFAC6759FF9B9394FF58DDFFFF50C9EBFF53CAEBFF57D1F4FF59D6F9FF - 59D7FBFF56CFF2FF50BADAFF428BA6FF35596EFF3A5F76FF406A83FF416B84FF - 4E7086FF6BA2B9FF6EE1FFFF51C7E9FF40869BFF5E322AFF7C4034FF854538FF - 844337FF834235FF814134FF803F33FF7F3E31FF7E3D30FF7C3B2FFF7B3A2EFF - 7A392CFF79382BFF78372AFF733428FF60291EFF4C2922FFE9E0DEFFC7F5FFFF - 6ADAFBFF4DBCDBFF36768BFF3B5D70FF4D7E98FF50829DFF5687A1FF7198ADFF - 85BACFFF7DD0EAFF74D9F7FF6EDFFFFF69DFFFFF62DAFCFF59D0F2FF4DC0E1FF - 339AB8FF488191FFBFBBBAFFF5F5F5FFFEFEFEFFFDFCFCFFFBFBFBFFFCFBFBFF - FCFBFBFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCFDFDFFFEFFFEFFC6EEFBFF42BEE1FF - 42AECDFF41AECDFF41AFCFFF41AECEFF41AFCFFF41AFCEFF41AFCEFF41AFCEFF - 3EA6C4FF2F869FFF84979DFFDEDAD9FFFCFDFDFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFEBEAEAFF9D8C87FF68423AFF - 79483FFF966157FFA0675BFFA5685CFFA7695DFFA7695DFFA6685BFFA4665AFF - A36559FFA5665AFF9E665BFF6B7F87FF4A8DA1FF44A0BBFF45B3D3FF4ABFE0FF - 50C7EAFF56CEF1FF57D2F5FF4CBEDDFF357287FF324E62FF3E6880FF406A83FF - 476E85FF5D849AFF6ED6F5FF5BD3F5FF43ADCBFF4C5256FF713225FF82463AFF - 854437FF834336FF824135FF814033FF803F32FF7E3E31FF7D3D30FF7C3B2EFF - 7B3A2DFF7A392CFF79382BFF723428FF58241AFF6C4F49FFF1F7F8FF91E4FCFF - 5FD5F6FF42A5C1FF336174FF41677DFF4F819CFF50829EFF648FA6FF8BB1C4FF - 8BE3FCFF6ADFFFFF5AD3F5FF4FC5E7FF45B6D7FF3BA4C2FF3A95AFFF4C93A7FF - 6C98A5FFAAB6B9FFE5E6E6FFFDFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFDFDFDFFFCFBFBFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFDFEFEFFFFFEFFFFF4F4F4FFA6BFC7FF - 7C9FAAFF719BA7FF729BA7FF729BA7FF729BA6FF729BA6FF739BA7FF719BA6FF - 6F95A0FF899BA1FFC6C5C4FFF1F1F1FFFFFFFFFFFDFDFDFFFDFDFDFFFFFFFFFF - FFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFD8D5D3FF - 88706BFF663C34FF804E45FF996358FFA0665AFFA5685CFFA6685CFFA6675BFF - A46559FFA36559FF9E5F53FF934F41FF7F493EFF6D4E49FF5D5F63FF4F7582FF - 4994AAFF4BC1E3FF56D3F7FF52C5E6FF3D8FA8FF2E4D5EFF3B6077FF406B84FF - 436D85FF4E7085FF65B3CFFF63DFFFFF4FC3E5FF4092AAFF5B3731FF773A2EFF - 84463AFF854437FF834236FF824134FF814033FF7F3E32FF7E3D31FF7D3C2FFF - 7C3B2EFF7B3A2DFF78372AFF6F3328FF552920FFAB9690FFC7F8FFFF6CDBFBFF - 51C4E4FF38859CFF355769FF49768EFF50829DFF5486A0FF769AAEFF93CDE1FF - 76E1FFFF51C8EAFF41A3C0FF428DA2FF558C9CFF7DA0AAFF9CACB0FF9E9C9CFF - 8D7F7AFF76635EFF674F4AFF5C3F38FF5A3B35FF5F443DFF705954FF897875FF - B2A9A6FFE2E0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFCFCFCFFFEFEFEFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFDFDFDFFFDFDFDFFFEFEFEFFF8F8F8FFEFEAE8FF - E2DCDAFFDCD6D4FFDBD5D3FFDCD6D4FFDCD6D4FFDCD5D4FFDBD5D3FFDBD5D3FF - DCD6D5FFE4E1E0FFF1F1F1FFFDFDFDFFFEFEFEFFFDFDFDFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFF - FFFFFFFFC3BBB9FF765952FF6A3D34FF88544BFF9B6358FFA06559FFA4675AFF - A5665AFFA46659FFA26458FF9F6358FF9A5E52FF94574AFF8D4D40FF83473AFF - 7D453AFF75787DFF5BD5F7FF58D3F7FF45AAC6FF2F5C6EFF355468FF406A83FF - 416B84FF456C84FF537F97FF63D5F6FF59D4F7FF4ABCDCFF428093FF62332AFF - 793B2FFF83463AFF844337FF834236FF814134FF803F33FF7F3E31FF7E3D30FF - 7C3B2FFF7B3A2DFF77392DFF6E3B31FF764D45FFBCD5DCFF84E6FFFF5CD2F4FF - 41A3BFFF326275FF3F6277FF4E809AFF50829DFF5F8CA4FF88ABBEFF8CDEF6FF - 63D8F9FF3DA5C3FF658893FFB1B0B0FFC9C8C6FFA19591FF624A44FF401D16FF - 370B03FF3C0A01FF420C02FF450F04FF450F04FF430C02FF3D0800FF370400FF - 330500FF3D170FFF634A46FFAFA6A3FFF7F8F8FFFFFFFFFFFEFDFDFFFEFEFEFF - FFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFFFFFFFDFEFEFF - FCFEFEFFFBFDFDFFFBFCFCFFFBFDFDFFFBFDFDFFFBFDFDFFFBFCFDFFFBFCFDFF - FCFDFDFFFDFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFDFDFDFF - FEFDFDFFFFFFFFFFF6F6F6FFAB9D9AFF6B4841FF704037FF8F5A50FF9B6358FF - A06458FFA36559FFA36559FFA36457FFA16356FFA06256FF9D6054FF9A5E52FF - 975A4EFFA35646FF7FB0C0FF5EE1FFFF4EBDDDFF377C92FF2F4B5DFF3D657DFF - 416A83FF426D85FF45687FFF529AB7FF5DDFFFFF56CDEFFF48BADBFF468295FF - 643A32FF7A3628FF814438FF824539FF824337FF804135FF7F4034FF7E3F33FF - 7E4034FF7F4337FF81463AFF805248FF8D9FA6FF87E6FEFF62D6F9FF48B4D2FF - 35768AFF38586BFF4A7891FF50829DFF53859FFF7095ABFF92C3D6FF7EE4FFFF - 51C5E7FF3F8DA4FFA9AEAFFFB1ABA8FF5F4741FF380E06FF421006FF531B10FF - 5C2116FF5F2216FF602216FF602115FF5F2014FF5E2013FF5D1F13FF5B1F13FF - 571D11FF4E150BFF3D0800FF310300FF50322DFFB4ABA8FFFFFFFFFFFFFFFFFF - FDFCFCFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFF - FEFEFEFFFCFCFCFFFFFFFFFFFFFFFFFFE5E3E2FF93807BFF643C34FF78463DFF - 935D53FF9C6256FFA06357FFA26458FFA16357FFA06155FF9F6054FF9E5F53FF - 9C5F53FFA46356FF9D8E8EFF6BE0FEFF57CDEFFF41A0BAFF2E5567FF37586DFF - 406A83FF406A83FF426C84FF41677EFF4FA9C9FF58DAFEFF55CBEDFF4BBFE0FF - 4898B0FF625555FF7A382CFF833828FF853C2DFF853F31FF864133FF884335FF - 8E493AFF91574BFF8E7A78FF88BBC9FF7BE4FFFF61D6F8FF4BB9D7FF378297FF - 345769FF446F86FF4F819CFF50829DFF5C8BA4FF83A6B9FF8FDBF2FF6ADCFCFF - 42B0CFFF5C8B99FF6C5A55FF39140CFF461208FF5B2217FF612519FF632317FF - 642316FF642215FF642114FF632114FF632013FF621F13FF611F12FF601E11FF - 5E1D10FF5C1C10FF5B1D11FF551C10FF430D04FF2F0300FF735F5BFFF0F0EFFF - FFFFFFFFFDFCFCFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFFFFFFFFFEFEFEFFFCFCFCFFFFFFFFFFFFFFFFFFD0CBC9FF7E655FFF - 63382FFF7F4C42FF965E53FF9C6155FFA06256FFA06256FFA06155FF9F6053FF - 9D5E51FFA16559FFAE7E75FF81D2EBFF5ED9FCFF49B4D2FF347488FF314E61FF - 3F6880FF406A83FF406B83FF406A82FF3E637BFF4BA2C0FF55D2F5FF55CCEEFF - 50C9EBFF4CC2E3FF58A0B6FF6C7B83FF7B6564FF83625EFF886864FF8B7877FF - 8B959BFF84BED1FF7AE1FDFF6CE5FFFF59D0F2FF47B2D0FF378197FF325667FF - 42697FFF4E7F9AFF51829DFF50839EFF618BA2FF83BAD0FF7EE5FFFF5AD1F4FF - 419FBAFF302B2BFF350A01FF582117FF63271BFF652518FF672518FF672518FF - 672417FF662316FF652215FF642214FF632113FF632013FF621F12FF621E11FF - 611E11FF601E10FF5F1D0FFF5C1B0EFF5A1C0FFF531A0FFF310000FF4F322EFF - E3E2E0FFFFFFFFFFFCFCFCFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFDFDFFFDFCFCFFFFFFFFFFFDFEFEFF - BAB0ADFF6E4F48FF693B31FF885449FF975F54FF9C5F54FF9F6154FF9F6155FF - 9E5F53FFA76D62FFB7918AFF87D9F1FF5ED7FAFF47AFCDFF327185FF304E60FF - 3F6880FF416B84FF3F6982FF416B84FF3F6880FF3B5E75FF4389A3FF4DBBDBFF - 52C7E8FF54CAEBFF53D3F6FF54DAFFFF5ADAFFFF60D8FAFF65D9FAFF68DEFFFF - 68E3FFFF63DEFFFF5AD0F3FF4DBDDEFF40A0BAFF357388FF345769FF426A80FF - 4D7E99FF50829DFF50829DFF50839EFF5B87A0FF70A5BDFF6FDEFCFF59D2F4FF - 42A5C1FF322A2BFF501B10FF65291DFF69281BFF69271AFF692719FF682518FF - 672517FF662417FF662316FF652215FF642114FF632013FF632013FF621F12FF - 611E11FF601E10FF601D10FF5F1C10FF5E1C0EFF5A1A0EFF571D12FF3A0600FF - 4A2B25FFE6E5E4FFFFFFFFFFFDFCFCFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFEFFFDFCFCFFFEFEFEFF - FFFFFFFFF0F0EFFFA2938EFF654038FF713F36FF8E594EFF985E52FF9C5E53FF - A0655AFFB57C70FFACBCC3FF77E5FFFF54C8E9FF3C93ACFF2C4E5FFF37586DFF - 416A83FF416A83FF406A83FF406A83FF406B83FF3F6880FF39596FFF39667DFF - 408FA9FF48ADCAFF4DB9D8FF51C1E1FF52C8E9FF54CBEEFF54CBEDFF53C8E9FF - 50C1E0FF49B4D2FF409FBAFF388197FF336073FF39596CFF457188FF4F809AFF - 50829DFF50829CFF50829DFF50829DFF53859FFF608BA3FF6AC8E6FF5FDAFDFF - 4DC0E1FF3D7A8DFF4C1C12FF64271BFF6B291CFF6A281BFF69271AFF682619FF - 682518FF672417FF662316FF652315FF642215FF642114FF632014FF622013FF - 611F12FF611F11FF601D10FF601D10FF5F1C0FFF5E1C0FFF5B1A0EFF591E12FF - 380500FF5A3F39FFF7F7F6FFFCFCFCFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFF - FDFCFCFFFFFFFFFFFFFFFFFFDDDAD9FF846E68FF60352DFF7B483EFF925B51FF - A56C61FFB89B95FF8EE0F7FF63D9FBFF46AFCCFF306679FF2F4A5CFF3E6780FF - 426B84FF416A82FF416A83FF406A82FF406A83FF406A83FF3F6981FF3B5E75FF - 345368FF335C70FF367086FF39839AFF3D8EA7FF3E94ADFF3D92ABFF3B89A1FF - 357C92FF2F697DFF335F71FF37596BFF41667BFF4B7A93FF50819CFF50829DFF - 50829DFF50829DFF51839EFF4F819CFF52849FFF538099FF528CA5FF64DBFDFF - 57CEF1FF43AFCEFF42484DFF5A1A0DFF692A1EFF6B291CFF6A281AFF69271AFF - 682619FF682518FF672417FF662316FF652215FF642215FF632113FF632013FF - 622012FF621E12FF611E11FF601E10FF5F1D0FFF5F1D0FFF5E1C0FFF5B1B0EFF - 571D11FF2C0000FF8B7B76FFFFFFFFFFFCFCFCFFFFFFFFFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF - FFFFFFFFFEFEFEFFFCFCFCFFFFFFFFFFFFFFFFFFC1B9B6FF6F504AFF70453CFF - A8776DFFA2C7D3FF72E4FFFF50C3E5FF39869DFF2C4859FF395D73FF416B84FF - 416B84FF416B83FF416A83FF416C84FF416C85FF406B84FF406A83FF406A82FF - 3E6780FF395D74FF345368FF314F62FF2F4D60FF2E4E61FF2C4D60FF2B4A5CFF - 274658FF35586CFF456E84FF4A7992FF4F809BFF50829DFF50829DFF50839EFF - 50839EFF50839EFF50829DFF50829DFF51839EFF3E6E88FF3F6279FF5CB1CEFF - 60DEFFFF50C4E6FF3E8CA3FF4B241DFF622417FF6C2A1DFF6B281BFF69271AFF - 692619FF682518FF672518FF662416FF662316FF652215FF642114FF632113FF - 631F13FF621F12FF611F11FF611D10FF601D10FF5F1D10FF5F1C0EFF5E1C0FFF - 5A1B0EFF4E170CFF340D06FFD3CFCDFFFEFEFEFFFCFCFCFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFFFFFFFFFDFDFDFFFEFDFDFFFFFFFFFFF1F1F0FFA99691FF - 9C9291FF88E4FDFF5ED3F5FF42A7C3FF2E5B6DFF335063FF406981FF416B84FF - 416B83FF446E86FF4B728AFF4E7087FF43667DFF3D637CFF3F6A82FF406B83FF - 416982FF406A83FF3F6982FF3D677FFF3C637BFF3A6077FF395F76FF355E75FF - 3A6780FF50819CFF50829DFF50829DFF50829DFF5485A0FF5C8AA3FF5B869EFF - 507C97FF4E7D97FF4F819CFF52849FFF44748FFF35627BFF3E667EFF4B7790FF - 62D2F2FF5BD6F8FF46B5D5FF405B64FF57180CFF692A1EFF6C291CFF6A281BFF - 69271AFF682619FF682518FF672417FF662416FF652315FF652115FF642114FF - 632013FF622013FF621F12FF611E11FF611D10FF5F1D10FF5F1D0FFF5F1C0EFF - 5D1B0EFF591D11FF390600FF6F5953FFFFFFFFFFFDFCFCFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFF - FDFDFDFFFDFDFDFFFEFEFEFFFEFDFDFFFDFCFCFFFDFDFDFFFEFEFEFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFCFCFBFFFFFFFFFFFFFFFFFF - ABE1F1FF6EDDFCFF53C9EAFF388198FF2A4353FF3B6278FF426B83FF426C85FF - 4A738BFF597B90FF6C8A9DFF6EA8BFFF54A4C2FF3E7089FF395A70FF3C6077FF - 3F677FFF406A83FF406982FF3F6A83FF3F6982FF3E6982FF3B6781FF38667FFF - 4C7E98FF51839EFF51829EFF5586A0FF5D8AA2FF6B91A7FF79A1B6FF70BBD6FF - 52A3C1FF477890FF497189FF497892FF37657FFF39647DFF3D6881FF4D6D83FF - 67A2BAFF6AE0FEFF52C6E8FF3D93ACFF472E2AFF622215FF6C2A1EFF6B291BFF - 6A281BFF69271AFF682619FF682518FF662417FF662316FF652215FF642214FF - 642114FF632013FF621F12FF621F12FF611E11FF601D10FF5F1D10FF5F1C0FFF - 5F1C0FFF5B1B0EFF50170CFF371008FFDAD7D5FFFFFFFFFFFCFCFCFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFDFDFF - FFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFF9FBFCFF - 84E0FBFF5ED6F9FF54CAEBFF3F95AFFF2D4E5FFF37586DFF456F87FF55788EFF - 6B889BFF7EACC1FF83D5EEFF74E3FFFF5DDEFFFF51C4E5FF479DB9FF3F768FFF - 3B6178FF3A5B72FF3D647CFF3F6981FF3E6881FF3D6780FF36637CFF44748EFF - 5384A0FF5686A0FF628CA4FF6F95AAFF7AA6BCFF81C1D9FF80DAF5FF6DE3FFFF - 57D7FAFF4DB7D6FF4790AAFF365F75FF2F5268FF39637BFF487188FF698395FF - 81B4C9FF71E1FFFF53C7E8FF3B9EBAFF3D464BFF5E1E11FF6C2C1FFF6B291CFF - 6A281BFF6A271AFF692619FF682518FF672417FF662417FF662316FF652215FF - 642114FF632013FF632013FF621F12FF611E11FF611E11FF601D10FF5F1D0FFF - 5F1C0FFF5D1B0EFF591D11FF330100FF91827DFFFFFFFFFFFBFAFAFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - CDC7C6FF9A8884FF907975FF95817CFFAFA39FFFDFDDDBFFFFFFFFFFFFFFFFFF - FEFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFCFEFEFFFFFEFEFF - C3EDF9FF56D9FFFF58D1F5FF4EBEDEFF3E90A8FF33586CFF4B6B81FF779DAFFF - 85CBE1FF7CE3FFFF68DCFDFF57CFF1FF52CBEEFF55CBEDFF55CFF1FF53C9EBFF - 4DB2D1FF3E8099FF34556AFF3A6077FF3E6881FF39647EFF3C6B85FF50839EFF - 5888A2FF7098ADFF82B0C5FF83CCE4FF7ADDFAFF6FE1FFFF63D9FBFF55CCEEFF - 51C9EBFF56CCEDFF50C5E6FF46A5C2FF377188FF34586EFF587589FF87BACDFF - 82E5FFFF5ED3F4FF43AFCDFF396370FF4E1E14FF65281CFF6D2B1EFF6C2A1DFF - 6B291CFF6A281BFF69271AFF682619FF682518FF672417FF662416FF652316FF - 642214FF642114FF632013FF621F12FF621F12FF611E11FF601D11FF5F1D10FF - 601D0FFF5F1C0FFF5B1D10FF420C02FF593D36FFFCFDFDFFFEFDFDFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFDFDFDFFFEFEFEFFA49693FF - 6D4943FF895E56FF96685FFF93655CFF845951FF7C5952FF9B8783FFDCD9D8FF - FFFFFFFFFFFFFFFFFDFCFCFFFDFDFDFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFDFEFEFF - FFFFFFFF86BBCBFF4CD1F8FF58D1F3FF4EBEDEFF47A6C3FF5CA8C3FF7DDEFAFF - 6FE0FFFF5ACFF1FF48BADBFF409BB5FF4791A7FF47B7D6FF4EC8EAFF55CBEDFF - 56CEF1FF4BBAD8FF357186FF304E62FF3A657DFF38657FFF4C7E98FF52849FFF - 668FA6FF8BB9CDFF88E4FEFF6CDEFFFF5CD3F5FF4DC0E1FF3DA7C6FF449AB3FF - 50A7C1FF46BCDEFF53CAECFF54C8E9FF4FC0E0FF49A5C3FF65ADC7FF7DE3FFFF - 5FD2F4FF43B1D0FF3A6877FF4C1E15FF62261AFF6D2D20FF6E2B1FFF6D2A1DFF - 6C291DFF6B291CFF6A281BFF69271AFF682619FF672518FF662417FF652316FF - 652215FF642115FF642114FF632013FF621F12FF611F11FF611E11FF601D10FF - 5F1C0FFF5F1C0FFF5C1C0FFF4F160BFF3C150CFFE0DEDDFFFFFFFFFFFCFCFCFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFCFCFCFFFFFFFFFFBEB5B2FF6E4840FF - A97970FFBE897FFFC48D82FFC38D82FFBD887EFFAD7B71FF8F6058FF7C5750FF - 9D8985FFDFDCDAFFFFFFFFFFFFFFFFFFFDFDFDFFFEFDFDFFFFFFFFFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFDFDFFFEFEFEFF - F2F4F3FF76493EFF668F9EFF52DCFFFF57CFF1FF57D4F8FF5FDFFFFF5ED5F8FF - 4DC1E3FF3DA3C0FF3E7484FF574542FF6C3B31FF635D5FFF4F8EA2FF4ABDDDFF - 55D2F6FF52C5E6FF3D90A9FF2C4C5EFF325A71FF457791FF52849FFF5485A0FF - 7699ADFF90CFE5FF73E1FFFF51C7E9FF3FA2BFFF458BA0FF688D98FF9FA4A6FF - BCBDBEFF87B9C8FF4CB6D5FF50CDF1FF55CBEDFF59D7FAFF61E0FFFF5AD0F2FF - 43B1D0FF396877FF4D1E15FF63271BFF6F2F22FF6F2D20FF6E2C1FFF6D2B1EFF - 6D2A1DFF6C291CFF6A281BFF6A271AFF692619FF682518FF672417FF662417FF - 662316FF642215FF642114FF632113FF632013FF621F12FF621F11FF601D11FF - 601D10FF5F1D0FFF5D1B0EFF561B10FF330500FFBBB4B1FFFEFEFEFFFBFBFBFF - FFFFFFFFFFFFFFFFFFFFFFFFFDFDFDFFFEFEFEFFF8F8F9FF816761FF9A6C63FF - C18B81FFCE9388FFD0958AFFD1958AFFCE9388FFC88F84FFC08A80FFAF7D73FF - 906259FF7F5B53FFA08D89FFDFDBDAFFFFFFFFFFFFFFFFFFFDFCFCFFFDFDFDFF - FEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFCFCFCFFFFFFFFFF - A39490FF6B3D34FF955548FF7296A3FF50D3F8FF55CCEEFF50C1E1FF41ACCBFF - 3D8398FF505356FF6D3C32FF82483CFF8B5146FF89483CFF7F4236FF796564FF - 63CAE8FF58D5F9FF44A5C1FF2A5466FF355B71FF51849FFF4F819CFF5A89A2FF - 80A3B7FF8BDAF2FF64D9FAFF3FA6C4FF688A94FFAAA7A7FFCFCBC9FFE4E2E2FF - F1F0F1FFDFD7D4FF4B3F3DFF497E90FF4CBFE0FF51CBEEFF52C3E3FF43ADCBFF - 3B6977FF4E2016FF64291EFF703124FF712F22FF702E21FF6F2D20FF6E2C1FFF - 6D2B1EFF6C2A1DFF6B291CFF6A281BFF69271AFF682619FF682518FF672417FF - 662316FF652215FF642214FF642114FF632013FF632012FF621F12FF611E11FF - 611E10FF601D10FF5E1C0FFF591D11FF340200FF9C8F8BFFFFFFFFFFFBFAFAFF - FFFFFFFFFFFFFFFFFFFFFFFFFDFCFDFFFFFFFFFFDEDBDAFF79564FFFAF7E74FF - CA9186FFD2968AFFD2978BFFD2978BFFD2968BFFD1958AFFCE9387FFC88E83FF - BF8A7FFFAE7B71FF8D5F56FF7D5A52FFA08E89FFDFDCDBFFFFFFFFFFFFFFFFFF - FDFCFCFFFDFDFDFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFDFCFCFFFFFFFFFFDBD8D6FF - 633D35FF8A574DFF9C6257FFA1594AFF728A93FF43B1D0FF3D8EA5FF4A5E66FF - 67413AFF7F4639FF8F554AFF975A4EFF99594DFF95584DFF92564AFFA05343FF - 84B2C1FF60E1FFFF4AB3D1FF30677AFF3D6176FF50829CFF50829DFF618CA5FF - 88B0C3FF84E1FCFF58CEEFFF3C91AAFF989D9EFFDEDCDBFFF7F9FAFFFCFDFDFF - FFFFFFFFD4D2D1FF441910FF5D2317FF5B4E4FFF4C90A5FF3DA3C0FF3B6370FF - 502219FF672B1FFF713226FF733124FF723023FF712F22FF702D21FF6E2D1FFF - 6D2C1FFF6C2A1DFF6C291DFF6B291CFF6A281BFF69271AFF682619FF672518FF - 662417FF662316FF652315FF642214FF632114FF622013FF621F13FF611F12FF - 611E11FF601E10FF5F1D10FF5B1E12FF370300FF85736EFFFFFFFFFFFBFAFAFF - FFFFFFFFFFFFFFFFFFFFFFFFFCFDFCFFFEFEFEFFD2CCCAFF7B554DFFB58278FF - CC9287FFD2978BFFD1978AFFD1968BFFD2978BFFD2978BFFD1968AFFD09589FF - CD9287FFC78E82FFBE887EFFAC7A70FF8D5E55FF7C5851FF9F8D89FFDFDCDAFF - FFFFFFFFFFFFFFFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFFFEFEFEFFFDFDFDFFFDFEFEFF836B65FF - 75443AFF9B6257FFA2665AFFA3665AFF9A5749FF715F5DFF694D48FF7E4438FF - 8F5549FF995D51FF9C5D51FF9B5B4FFF995A4EFF98584CFF97594DFFA15F52FF - 949EA5FF69E4FEFF4FBFDFFF397F95FF3B5D71FF4C7D98FF5285A0FF6B93A9FF - 8EC0D4FF7AE3FFFF4BBFE0FF498A9EFFB5B3B3FFF1F2F2FFFFFFFFFFFCFCFBFF - FEFEFEFFC1BBB8FF471C13FF6C362BFF712D1FFF652E22FF514141FF57271DFF - 692D21FF743428FF743326FF733125FF723023FF712F23FF702E21FF6F2D20FF - 6E2C1FFF6D2B1EFF6C2A1DFF6B291CFF6A281BFF6A271AFF692619FF682518FF - 672418FF662417FF662316FF652215FF642114FF632014FF622012FF621F12FF - 611E11FF601D10FF601D10FF5B1D11FF3B0600FF745F59FFFFFFFFFFFCFBFBFF - FFFFFFFFFFFFFFFFFFFFFFFFFDFCFCFFFFFFFFFFDAD6D5FF79554EFFB17F75FF - CA9186FFD2968BFFD2978BFFD2978BFFD2968AFFD2968BFFD1968BFFD1968AFF - D09589FFCF9488FFCC9186FFC68D82FFBD887DFFAB796FFF8B5C54FF7B5850FF - 9F8C88FFDFDCDBFFFFFFFFFFFFFFFFFFFEFDFDFFFEFDFDFFFFFFFFFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFCFCFCFFFFFFFFFFBEB6B3FF653A32FF - 945F55FFA1655AFFA6685CFFA6675BFFA06459FF9B594CFF97594DFF9B6054FF - 9F6054FF9E5F53FF9C5D51FF9B5C50FF9A5B4EFF99594DFF96584CFF995A4DFF - 958281FF71E0FFFF56CCEDFF3F97B1FF395D70FF4A738CFF5986A0FF7897ABFF - 8FCFE4FF6FE0FFFF40AFCEFF5E8B98FFCBC6C4FFF7F8F9FFFEFEFEFFFBFBFBFF - FFFFFFFFB6ABA9FF46190FFF72382DFF7A3B2FFF76372BFF712E20FF723428FF - 763629FF773528FF753327FF743225FF733124FF723023FF712F22FF702E21FF - 6F2D20FF6E2C1FFF6D2B1EFF6C2A1DFF6B291CFF6A281BFF69271AFF682619FF - 672518FF662417FF662316FF652315FF652215FF642114FF632013FF622012FF - 621F12FF611E11FF601D10FF5C1E12FF3C0700FF6D544EFFFFFFFFFFFBFCFBFF - FFFFFFFFFFFFFFFFFFFFFFFFFDFDFDFFFEFEFEFFF5F5F5FF7C615BFF9E6F66FF - C38C81FFCF9488FFD1968AFFD2968BFFD2968AFFD2978AFFD2968AFFD1968AFF - D1958AFFD09589FFD09489FFCE9387FFCB9085FFC58C81FFBD877CFFAB796FFF - 8C5D54FF7B564FFF9D8A85FFDDDAD9FFFFFFFFFFFFFFFFFFFDFDFDFFFDFDFDFF - FEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFDFDFDFFFDFDFDFFF2F2F1FF70514AFF855248FF - A0665AFFA7695DFFA7695DFFA6685BFFA5675AFFA4675AFFA36559FFA26356FF - A06155FF9E6053FF9D5E52FF9C5D51FF9B5C4FFF995A4EFF95574AFF8E554AFF - 866059FF71C9E4FF5ED9FBFF4BB8D7FF4595B0FF5194B1FF63A5C0FF79B1C8FF - 80D9F4FF61D7F9FF3B9DBAFF7D949BFFDCD7D6FFFCFDFDFFFEFEFEFFFBFBFBFF - FFFFFFFFB6ABA9FF471910FF72382DFF7B3A2EFF7C3B2EFF7A3A2EFF79382BFF - 78372AFF773629FF763428FF753326FF743225FF733124FF713023FF702F22FF - 702E20FF6E2D1FFF6D2B1EFF6D2A1DFF6C291CFF6B281BFF6A281BFF692719FF - 682619FF672517FF662417FF662316FF652215FF642214FF632014FF622013FF - 621F12FF611E12FF611E11FF5D1E12FF3D0800FF6D534EFFFFFFFFFFFBFCFCFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFCFCFCFFFFFFFFFFB4AAA7FF724B43FF - AE7E74FFC28C81FFCB9286FFD09589FFD1968AFFD2968AFFD2978BFFD1978AFF - D1968BFFD19589FFD09489FFD09488FFCF9387FFCD9286FFCA8F84FFC48B80FF - BC867BFFAB786EFF8C5E55FF7B5750FF9B8884FFDCD8D7FFFFFFFFFFFFFFFFFF - FDFDFCFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFEFEFEFFFCFCFCFFFFFFFFFFA2938FFF6F4037FF9C655AFF - A66A5EFFA96B5FFFA86A5EFFA7695CFFA6675BFFA5665AFFA36559FFA26357FF - A16256FFA06154FF9E5F53FF9D5E52FF9B5D50FF995A4EFF905449FF7C4940FF - 785047FF90D0E2FF61DDFEFF58D0F2FF56D0F3FF5CDBFEFF63DDFFFF69DDFEFF - 67DDFEFF54C7E8FF3F8EA5FFA0A5A6FFE9E7E7FFFEFFFFFFFDFDFDFFFCFCFCFF - FEFEFEFFC3BDBBFF481D14FF70372CFF7B3B2EFF7C3B2EFF7B3A2DFF7A382CFF - 79372BFF783629FF763528FF753427FF743326FF733125FF723024FF712F22FF - 702E21FF6F2D20FF6E2C1FFF6D2B1EFF6C2A1DFF6B291CFF6A281BFF69271AFF - 692619FF682518FF672418FF672416FF652315FF652215FF642114FF632013FF - 632012FF621F12FF601E11FF5D1E13FF3B0600FF745F59FFFFFFFFFFFCFBFBFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFDFDFDFFFCFDFDFF9D8D89FF - 724C44FF9B6C62FFB9857BFFC58E83FFCC9287FFD0958AFFD2968AFFD2978BFF - D2978BFFD1968AFFD09589FFD09489FFD09388FFCF9387FFCE9286FFCC9185FF - C98E83FFC38A7FFFBB857AFFAA776DFF8A5B52FF79544CFF9B8783FFDCD8D7FF - FFFFFFFFFFFFFFFFFEFEFEFFFDFDFDFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFCFCFCFFFFFFFFFFE0DDDCFF69443DFF905C52FFA46A5EFF - AA6D61FFAA6C60FFA96B5FFFA86A5EFFA6685CFFA6675BFFA46559FFA36458FF - A26357FFA06155FF9F6054FF9E5F53FF9C5D51FF97594DFF8A5348FF5B2E25FF - AE9A94FFDBFEFFFF48CAF0FF49BFE0FF49BCDEFF4ABEDFFF49BFE0FF48BDDFFF - 47BADCFF349CBAFF518493FFC2BDBCFFF3F4F4FFFFFFFFFFFEFEFEFFFDFDFDFF - FFFFFFFFDCD9D8FF4C251DFF6B3429FF7B3C2FFF7D3C2FFF7C3B2EFF7B392DFF - 7A382BFF78372AFF773629FF763528FF753427FF743225FF733124FF723023FF - 712F22FF702E21FF6F2D20FF6E2C1FFF6D2B1EFF6C2A1DFF6B291CFF6A281BFF - 69271AFF682619FF672518FF672417FF662316FF652315FF652215FF642114FF - 632013FF621F12FF611F11FF5C1F13FF370400FF887773FFFFFFFFFFFBFAFAFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - C7C0BEFF8C726DFF835A51FF9F6F66FFBA867CFFC58E83FFCB9287FFD09589FF - D1968AFFD1968AFFD19589FFD09589FFD09488FFCF9388FFCE9387FFCE9286FF - CD9185FFCB9084FFC88D81FFC2897EFFBA8479FFAA776DFF8B5D53FF7A554DFF - 998580FFD9D4D3FFFFFFFFFFFFFFFFFFFDFDFDFFFDFDFDFFFEFEFEFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFCFCFCFFFFFFFFFF8B7570FF794940FFA16A5FFFAA6E62FF - AC6F63FFAB6D61FFAA6C60FFA96B5EFFA7695DFFA6685CFFA5665AFFA46559FF - A26457FFA16256FFA06155FF9E6053FF9B5D51FF92584CFF723F35FF6C4F48FF - F6F6F5FFFAFCFBFF8AC1D2FF599AADFF5594A7FF5495A8FF5394A7FF5594A6FF - 5792A3FF598A98FF9AA5A8FFE0DEDDFFFBFCFCFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFF6F7F7FF5D3F37FF612C22FF793C30FF7E3D30FF7D3B2FFF7B3A2DFF - 7A392CFF79382BFF78362AFF773528FF763427FF753326FF743225FF723124FF - 723023FF712F22FF6F2E21FF6E2C20FF6D2C1FFF6D2A1EFF6C2A1CFF6B281BFF - 6A281BFF69271AFF682619FF672518FF672417FF662316FF652215FF642114FF - 632114FF632013FF601F12FF5A1F13FF340400FFA89D99FFFEFEFEFFFBFBFAFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFEFDFDFF - FFFFFFFFFAFBFBFFC4BBB8FF8B716BFF825951FFA07066FFBA877CFFC58E83FF - CC9287FFD09589FFD1968AFFD1958AFFD09589FFCF9488FFCF9387FFCE9287FF - CE9286FFCD9185FFCC9084FFCA8F83FFC78C80FFC2887DFFBA8378FFA9766CFF - 8A5B52FF79534BFF97837EFFD6D1CFFFFFFFFFFFFFFFFFFFFEFDFDFFFDFDFDFF - FEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF - FFFFFFFFFCFCFCFFFFFFFFFFCBC5C3FF694038FF99655AFFA96D62FFAD7064FF - AD7063FFAC6E62FFAB6D61FFAA6B5FFFA96A5EFFA7695DFFA6675BFFA4665AFF - A36558FFA26357FFA16256FF9E6054FF975B4FFF875247FF592F27FFC7C1BEFF - FFFFFFFFF7F6F6FFE8E1DFFFCDC9C8FFC5C2C1FFC5C1C1FFC5C1C0FFC5C2C1FF - C7C2C1FFD0CAC9FFE2E0DFFFF6F7F7FFFEFEFEFFFDFDFDFFFEFEFEFFFFFFFFFF - FCFBFBFFFFFFFFFF85726EFF511F16FF773D31FF7D3D30FF7D3C2FFF7C3B2EFF - 7B3A2DFF7A382CFF79372BFF783629FF763528FF753427FF753326FF733124FF - 723023FF712F22FF702E21FF6F2D20FF6E2C1FFF6D2B1EFF6C2A1DFF6B291CFF - 6A281BFF69271AFF692619FF682518FF672418FF662417FF652316FF642215FF - 642114FF642113FF601F12FF551B10FF370D05FFCFCBC9FFFFFFFFFFFCFBFBFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFF - FDFDFDFFFFFFFFFFFFFFFFFFF9FBFBFFC1B9B6FF8A6F69FF825950FFA17067FF - BA877CFFC68E83FFCC9287FFCF9489FFD09589FFD09589FFD09488FFCF9388FF - CE9287FFCD9286FFCD9185FFCC9084FFCB8F83FFC98E82FFC78B7FFFC1877BFF - B98277FFA8756BFF8A5B51FF765048FF947F7AFFD4CFCEFFFFFFFFFFFFFFFFFF - FEFEFEFFFDFDFDFFFEFEFEFFFFFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFEFEFF - FEFEFEFFFDFDFDFFF9F9F9FF7C625BFF845248FFA66D62FFAE7165FFAF7265FF - AE7064FFAD6F63FFAB6E61FFAA6C60FFA96B5FFFA86A5DFFA6685CFFA5675BFF - A46659FFA36458FFA26357FF9C5F53FF925A4FFF68372EFF8A7570FFFFFFFFFF - FCFCFCFFFDFDFEFFFAFBFBFFF8F9F9FFF7F7F7FFF6F7F7FFF7F7F7FFF6F7F7FF - F7F8F8FFF9FAFAFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFF - FCFBFBFFFFFFFFFFCAC4C2FF481E15FF70392EFF7B3C30FF7E3D30FF7D3C2FFF - 7C3A2EFF7B392DFF7A382BFF78372AFF773629FF763528FF753327FF743225FF - 733124FF723023FF712F22FF702E21FF6F2D20FF6E2C1FFF6D2B1DFF6C2A1DFF - 6B291CFF6A281BFF69271AFF682619FF682518FF672417FF662416FF652316FF - 642215FF632114FF5F2014FF481207FF4F2F28FFF7F8F7FFFEFEFEFFFDFDFDFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF - FFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFF8F8F8FFC1B8B5FF886C67FF - 835951FFA17168FFBB877CFFC58D82FFCB9186FFCF9488FFD09489FFD09488FF - CE9387FFCE9286FFCD9185FFCC9085FFCB9084FFCB8F83FFCA8E82FFC88D81FF - C58A7EFFBF867AFFB88176FFA9756CFF8A5B52FF765048FF917B76FFD2CDCBFF - FFFFFFFFFFFFFFFFFFFEFEFFFDFDFDFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FCFCFCFFFFFFFFFFB9AFACFF6D4139FFA06A60FFAD7165FFB17367FFB07266FF - AF7165FFAE7064FFAC6E62FFAB6D61FFAA6C60FFA96B5EFFA8695DFFA6685CFF - A5665AFFA36559FFA16357FF985E52FF804C42FF623F37FFE7E6E5FFFEFEFEFF - FDFCFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFDFDFDFFFCFCFCFF684D47FF5A271DFF783D31FF7D3E31FF7E3D30FF - 7D3B2FFF7B3A2DFF7A392CFF79382BFF78372AFF773528FF763427FF753326FF - 743225FF733124FF723023FF702E21FF6F2E21FF6F2C20FF6D2B1EFF6C2A1DFF - 6B291CFF6B281BFF6A271BFF69271AFF682619FF672518FF672417FF652316FF - 652215FF622114FF5D2116FF360400FF8A7975FFFFFFFFFFFBFBFBFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFF7F8F8FF - C0B5B3FF896D67FF845951FFA27269FFBB877DFFC58D82FFCB9185FFCE9387FF - CF9387FFCE9287FFCE9286FFCD9185FFCC9085FFCB8F84FFCB8F83FFCA8E82FF - C88D81FFC78B80FFC4897DFFBF857AFFB78075FFA9756BFF8B5B52FF754E46FF - 8E7772FFCFCAC8FFFFFFFFFFFFFFFFFFFDFDFCFFFDFDFDFFFEFEFEFFFDFDFDFF - FDFDFDFFF2F1F1FF775952FF8D5B51FFAA7065FFB27569FFB27569FFB17367FF - B07266FFAE7165FFAD6F63FFAC6E62FFAB6D60FFAA6B5FFFA96A5EFFA7695CFF - A6675BFFA3665AFF9E6155FF915A50FF60332AFFAEA19EFFFFFFFFFFFCFBFBFF - FFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FFFFFFFFFCFBFBFFFFFFFFFFC4BDBAFF471C14FF6F392EFF7A3C30FF7E3D31FF - 7D3C2FFF7C3B2EFF7B3A2DFF7A382CFF79372AFF783629FF773528FF753427FF - 743326FF733225FF723024FF712F22FF702E21FF6F2D20FF6E2C1FFF6D2B1EFF - 6C2A1DFF6B291CFF6B281BFF6A271AFF692619FF682618FF672518FF662417FF - 652316FF602014FF531B11FF39120BFFDBD8D7FFFEFEFEFFFCFCFCFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFF - FFFFFFFFF5F6F5FFBCB1AFFF886C66FF855B52FFA37369FFBB877CFFC48C81FF - CA9084FFCD9286FFCE9286FFCE9285FFCD9185FFCC9084FFCB8F83FFCA8E82FF - C98D82FFC98C80FFC78B80FFC68A7EFFC3877CFFBD8378FFB67F74FFA8746AFF - 8C5C52FF764E46FF8B746FFFD2CCCBFFFFFFFFFFFFFFFFFFFEFDFDFFFBFAFAFF - FFFFFFFFB3A6A2FF76483FFFA46E64FFB07569FFB4776AFFB37569FFB27468FF - B17367FFAF7265FFAE7064FFAD6F63FFAB6E61FFAA6C60FFA96B5FFFA86A5DFF - A7685CFFA36559FF996054FF76443BFF765C56FFF9F9F9FFFDFCFCFFFEFEFEFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFCFCFCFFFFFFFFFF806C67FF4F1F16FF763C31FF7B3C30FF - 7D3D30FF7D3C2FFF7C3B2EFF7B392CFF7A382BFF78372AFF773629FF763428FF - 753327FF743225FF733124FF723023FF712F22FF702E21FF6F2D20FF6E2C1FFF - 6D2B1EFF6C2A1DFF6B291CFF6A281BFF69271AFF682619FF682518FF672417FF - 632215FF5E2317FF390700FF7F6D68FFFFFFFFFFFCFCFCFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFF - FDFCFCFFFFFFFFFFFFFFFFFFF5F6F5FFBAAFACFF856862FF83584FFFA37268FF - BA867BFFC48C81FFCA8F84FFCC9185FFCD9185FFCC9185FFCC9084FFCB8F83FF - CA8E82FFC98D81FFC88C80FFC78C7FFFC68A7FFFC5897DFFC2867BFFBD8277FF - B67E73FFA77369FF87584EFF724B44FF9F8C88FFE0DDDCFFFFFFFFFFFFFFFFFF - E6E4E3FF7F5B53FF976359FFAE7469FFB5786CFFB5776BFFB4766AFFB27569FF - B17467FFB07266FFAF7165FFAD7063FFAC6F62FFAB6D61FFAA6C60FFA96A5EFF - A6695DFF9F6357FF8C574DFF5F382FFFD1CCCBFFFFFFFFFFFCFCFCFFFFFFFFFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFDFDFDFFFEFDFDFFEDECECFF583933FF5A261CFF773C31FF - 7B3C2FFF7D3D30FF7D3B2FFF7B3A2DFF7A392CFF79382BFF78362AFF773528FF - 763427FF753326FF733225FF723124FF723023FF702E22FF6F2E21FF6F2D20FF - 6E2B1EFF6C2A1DFF6B291CFF6B281BFF6A271AFF69271AFF682619FF652417FF - 602317FF49140AFF44221AFFEAE9E8FFFEFDFEFFFDFDFDFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF - FFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFF4F4F3FFB9ADAAFF866962FF - 865B52FFA4736AFFBA867BFFC38B80FFC88E82FFCB9084FFCC9084FFCB8F84FF - CB8F83FFCA8E82FFC98D81FFC88C80FFC78B7FFFC68A7EFFC5897DFFC4877CFF - C1857AFFBB8176FFB37C72FFA47066FF85564CFF7D564EFF957972FFA08781FF - 8B655DFF946157FFAB7368FFB4786CFFB7796EFFB6786CFFB4776BFFB3766AFF - B27568FFB17367FFB07266FFAF7164FFAD6F63FFAC6E62FFAB6D61FFA96B5FFF - A4675BFF986156FF6A3B32FF968480FFFFFFFFFFFCFCFBFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFEFEFEFFFFFFFFFFFCFCFCFFFFFFFFFFD5D1D0FF4A261FFF602D23FF - 763C30FF7A3B2FFF7D3C2FFF7C3B2EFF7B3A2DFF7A392CFF79372BFF783629FF - 763528FF753427FF743326FF733125FF723023FF712F22FF702E21FF6F2D20FF - 6E2C1FFF6D2B1EFF6C2A1DFF6B291CFF6A281BFF69271AFF662519FF612418FF - 551F14FF310801FFBDB6B4FFFFFFFFFFFCFCFCFFFFFFFFFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFF2F2F2FF - B7ABA8FF83655FFF855A51FFA47369FFBA857BFFC28A7FFFC88D82FFCA8F83FF - CB8F83FFCA8E83FFC98D82FFC98D81FFC88C80FFC78B7FFFC68A7EFFC5897DFF - C4887CFFC3867BFFBF8378FFB97F74FFB37C71FFA57066FF976358FF966056FF - A26B61FFAF766CFFB4796DFFB87B6FFFB77A6EFFB6796DFFB5786CFFB4776AFF - B37669FFB27468FFB17367FFAF7266FFAE7064FFAD6F63FFAB6E61FFA96B5FFF - 9F655AFF835047FF694942FFEEEEEDFFFEFDFDFFFDFCFCFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFEFFFFFFFCFBFBFFFFFFFFFFC9C4C1FF4B2820FF - 5D2A20FF753C30FF793A2EFF7C3B2EFF7C3B2EFF7B392DFF79382BFF78372AFF - 773629FF763528FF753327FF743225FF733124FF723023FF712F22FF702E21FF - 6F2D20FF6E2C1FFF6D2B1EFF6C2A1DFF6A291CFF67271AFF62261AFF561F15FF - 330700FFA39794FFFFFFFFFFFCFBFBFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFDFDFDFFFFFFFFFF - FFFFFFFFF1F1F0FFB5A9A6FF82635CFF84584FFFA47268FFB9857AFFC28A7EFF - C78D81FFCA8E82FFCA8E82FFC98D81FFC88C80FFC78B7FFFC78A7EFFC58A7DFF - C5887DFFC4877CFFC3867AFFC18479FFBE8277FFBB8074FFB97F73FFB87E73FF - B87C71FFB97D71FFBA7D71FFB97C70FFB87B6FFFB77A6EFFB6796DFFB5786BFF - B4766AFFB37569FFB17467FFB07366FFAF7165FFAE7064FFAC6E62FFA5695DFF - 966056FF64392FFFBAB0AEFFFFFFFFFFFCFCFBFFFFFFFFFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFCFCFBFFFFFFFFFFD1CCCAFF - 54332CFF4F1D14FF70392DFF773A2EFF7A3A2DFF7B3A2DFF7B392CFF79382BFF - 78372AFF773528FF763427FF743326FF733225FF723124FF713023FF702F21FF - 702D21FF6F2C1FFF6D2B1EFF6B2A1DFF67271BFF63281CFF4D180EFF330900FF - A19591FFFFFFFFFFFCFBFBFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFF - FDFCFCFFFFFFFFFFFFFFFFFFF0F0F0FFB2A6A2FF82625CFF865A51FFA47369FF - B9847AFFC1897DFFC68B7FFFC88D81FFC88D81FFC88C80FFC78B7FFFC68A7EFF - C5897DFFC4887CFFC3877BFFC2867AFFC28579FFC18478FFBF8377FFBE8176FF - BE8175FFBC8073FFBB7E72FFBA7D71FFB97C70FFB87B6FFFB7796DFFB6786CFF - B5776BFFB3766AFFB27568FFB17367FFB07266FFAE7165FFAA6D61FF9F675CFF - 78473EFF836A65FFFDFEFEFFFDFCFCFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFCFCFBFFFFFFFFFF - E6E5E4FF705953FF471A11FF612C22FF733A2EFF76392DFF77382CFF79382BFF - 78372AFF783629FF773528FF753427FF743326FF733225FF723124FF712F22FF - 6F2E21FF6D2C1FFF6A2B1EFF662B1FFF5C251AFF400E04FF44221BFFBBB3B0FF - FFFFFFFFFCFCFCFFFEFEFEFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFF - FFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFEEEDEDFFAFA19DFF81615AFF - 855950FFA5736AFFB98379FFC0887CFFC58A7FFFC78B80FFC78B80FFC78A7FFF - C68A7EFFC5897DFFC4887CFFC3877BFFC2867AFFC18579FFC08478FFBF8277FF - BE8175FFBD8074FFBC7F73FFBB7E72FFBA7D71FFB87B6FFFB77A6EFFB6796DFF - B5786CFFB4776AFFB37569FFB27468FFB07367FFAE7164FFA56B5FFF8F5B51FF - 664139FFDCD8D7FFFFFFFFFFFCFCFCFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFCFCFCFF - FFFFFFFFFEFFFFFFA99E9AFF54332CFF481910FF5E291FFF6E362AFF73382CFF - 74372BFF753529FF743428FF743427FF733326FF713125FF703024FF6E2F23FF - 6B2E23FF672C21FF5B2419FF451208FF3C130AFF76615CFFE4E3E1FFFFFFFFFF - FCFBFBFFFEFEFEFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFFFFFFFFFFEBEAEAFF - AB9C99FF7F5D56FF875950FFA6746AFFB88378FFC0877BFFC4897EFFC68A7FFF - C78A7FFFC6897EFFC5887CFFC4877BFFC3867AFFC28579FFC18478FFC08377FF - BF8276FFBE8175FFBD8074FFBC7F73FFBA7D71FFB97C70FFB87B6FFFB77A6EFF - B6786DFFB5776BFFB4766AFFB27569FFB17468FFAB6F63FF9E685DFF6C3E35FF - A3938FFFFFFFFFFFFDFCFCFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFF - FDFDFDFFFEFDFDFFFFFFFFFFEBEAEAFF9A8B87FF593933FF451A10FF4D1B11FF - 5A241AFF632C21FF672F24FF693025FF692F24FF662C21FF60281EFF582116FF - 4B160DFF3E0F06FF441D15FF745D57FFC8C3C1FFFFFFFFFFFFFFFFFFFCFBFBFF - FFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFDFCFCFFFFFFFFFF - FFFFFFFFE9E8E8FFA99995FF7C5A53FF865850FFA67469FFB88277FFBF867BFF - C4897CFFC5897EFFC5897DFFC4887CFFC3877BFFC2867AFFC18579FFC08478FF - C08377FFBE8276FFBD8175FFBC8073FFBB7E72FFBA7D71FFB97C70FFB87B6FFF - B7796DFFB5786CFFB4776BFFB3766AFFAF7367FFA66D61FF855349FF73554EFF - F4F4F4FFFDFDFDFFFDFDFDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFF - FFFFFFFFFEFEFEFFFCFCFCFFFFFFFFFFFFFFFFFFF5F6F5FFBEB7B4FF887570FF - 62453EFF4E2921FF461D15FF43170FFF42170EFF431A11FF482119FF55352EFF - 745E58FFA59996FFE0DEDDFFFFFFFFFFFFFFFFFFFDFDFDFFFEFDFDFFFFFFFFFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFF - FCFCFCFFFFFFFFFFFFFFFFFFE6E4E3FFA69591FF7B5851FF885A51FFA7746BFF - B88277FFBE8579FFC3877CFFC4887CFFC4887CFFC3877BFFC2867AFFC18479FF - C08377FFBF8276FFBE8175FFBD8074FFBC7F73FFBB7E72FFBA7D71FFB97B6FFF - B77A6EFFB6796DFFB5786CFFB4766AFFAB7065FF9A665BFF683E36FFC7C0BDFF - FFFFFFFFFCFCFCFFFFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFCFBFBFFFFFFFFFFFFFFFFFFFFFFFFFF - FCFDFDFFE6E5E4FFD2CECCFFC7BFBDFFC6BEBCFFCEC9C7FFE0DEDCFFF5F5F5FF - FFFFFFFFFFFFFFFFFFFFFFFFFDFCFCFFFDFCFCFFFEFEFEFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFF - FFFFFFFFFEFDFDFFFCFCFCFFFFFFFFFFFFFFFFFFE2E0DFFFA1908BFF7A564FFF - 8A5B52FFA8756BFFB88176FFBE8479FFC2867BFFC3877BFFC3867BFFC28579FF - C18478FFC08377FFBF8276FFBE8175FFBC8074FFBC7F73FFBB7E71FFB97C70FF - B87B6FFFB77A6EFFB6786CFFB17569FFA46E63FF794A40FF8E7974FFFFFFFFFF - FCFCFCFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFEFEFEFFFCFCFCFFFCFBFBFF - FFFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FDFCFCFFFCFBFBFFFDFDFDFFFEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFFFFFFFFFEFEFEFFFDFDFDFFFFFFFFFFFFFFFFFFE0DDDCFF - 9C8985FF78534CFF8A5B51FFA9766CFFB78075FFBD8378FFC1857AFFC2857AFF - C18579FFC08478FFBF8377FFBE8276FFBD8074FFBC8073FFBB7E72FFBA7D71FF - B97C70FFB87B6EFFB5796DFFAB7266FF915E54FF6D4A43FFE6E5E4FFFFFEFEFF - FDFCFCFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFF - FEFEFEFFFEFEFDFFFDFDFDFFFDFCFCFFFDFCFCFFFDFDFDFFFDFDFDFFFEFEFEFF - FEFEFEFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFEFDFDFFFDFCFCFFFFFFFFFF - FFFFFFFFDBD8D6FF97827DFF79534BFF8E5E55FFAA776DFFB78075FFBD8276FF - C08478FFC08478FFC08377FFBF8276FFBE8175FFBD8074FFBC7F73FFBB7E72FF - BA7D71FFB87B6FFFB0766AFFA26D62FF6F443BFFB3A7A4FFFFFFFFFFFCFCFCFF - FFFFFFFFFEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFDFDFDFF - FDFCFCFFFFFFFFFFFFFFFFFFD4CFCDFF907A74FF774F47FF916057FFAC786EFF - B77F74FFBC8176FFBF8378FFBF8377FFBF8276FFBE8175FFBD8074FFBC7E73FF - B97D71FFB4796DFFA97267FF825248FF816862FFFBFBFBFFFDFCFCFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFF - FFFFFFFFFEFDFDFFFEFDFDFFFFFFFFFFFFFFFFFFCCC5C4FF8A716CFF784F47FF - 96655BFFAF796FFFB67D72FFBB8074FFBC8175FFBC8074FFBA7E72FFB67B70FF - B1786DFFA87268FF8B5B52FF5F3C34FFD8D5D3FFFFFFFFFFFCFCFCFFFFFFFFFF - FEFEFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FEFEFEFFFEFEFEFFFFFFFFFFFDFDFDFFFEFEFEFFFFFFFFFFFDFFFFFFC2B9B7FF - 81645EFF794D44FF98665DFFA77469FFAD776CFFAD776CFFA97369FF9F6C62FF - 8F5E54FF75493FFF64443DFFB8AFACFFFFFFFFFFFDFCFCFFFEFEFEFFFEFEFEFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFEFEFEFFFEFEFEFFFEFEFEFFFDFCFCFFFFFEFEFFFFFFFFFF - F3F4F3FFA99B97FF73544DFF714A42FF764C43FF774C43FF744A42FF724D46FF - 7A5E58FFA2938FFFE4E2E0FFFFFFFFFFFDFDFDFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFFFEFEFEFFFCFCFCFF - FFFFFFFFFFFFFFFFECEBEAFFC8C1BFFFB5AAA7FFB5A9A6FFC3BBB8FFDCD8D7FF - F7F7F7FFFFFFFFFFFFFFFFFFFDFCFCFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFEFEFFFFFFFFFF - FEFEFEFFFCFBFBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFBFBFBFFFDFCFCFFFFFFFFFFFEFEFEFFFEFEFEFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - FFFFFFFF00000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 0000000000000000000000000000000000000000000000000000000000000000 - 00000000} - end - object Label1: TLabel - Left = 63 - Top = 16 - Width = 244 - Height = 73 + TextHeight = 19 + object lblDescription: TLabel + Left = 94 + Top = 8 + Width = 322 + Height = 113 AutoSize = False Caption = 'Clone the current selected session, including all of it'#39's events' + '.'#13#10#13#10'A new session will be created and assigned todays'#39' date. ' WordWrap = True end - object Panel1: TPanel + object vimgClone: TVirtualImage + Left = 0 + Top = 8 + Width = 73 + Height = 65 + ImageCollection = imgcolClone + ImageWidth = 0 + ImageHeight = 0 + ImageIndex = 0 + ImageName = 'Clone' + end + object pnlFooter: TPanel Left = 0 - Top = 122 - Width = 343 - Height = 42 + Top = 158 + Width = 424 + Height = 53 Align = alBottom + BevelEdges = [beTop] + BevelKind = bkFlat + BevelOuter = bvNone TabOrder = 0 - ExplicitTop = 121 - ExplicitWidth = 339 - object btnCancel: TButton - Left = 96 - Top = 8 - Width = 75 - Height = 25 - Caption = 'Cancel' - TabOrder = 0 - OnClick = btnCancelClick - end + ExplicitTop = 326 object btnClone: TButton - Left = 177 - Top = 8 - Width = 75 - Height = 25 + Left = 215 + Top = 10 + Width = 99 + Height = 31 Caption = 'Clone' Default = True - TabOrder = 1 + TabOrder = 0 OnClick = btnCloneClick end + object btnCancel: TButton + Left = 110 + Top = 10 + Width = 99 + Height = 31 + Caption = 'Cancel' + TabOrder = 1 + OnClick = btnCancelClick + end end object qrySrcEvent: TFDQuery ActiveStoredUsage = [auDesignTime] Connection = SCM.scmConnection SQL.Strings = ( - 'USE [SwimClubMeet]' - '' '' - 'SELECT [EventID]' + 'SELECT' + ' [EventID]' ' ,[EventNum]' ' ,[Caption]' ' ,[SessionID]' + ' ,[RallyOpenDT]' ' ,[StrokeID]' + ' ,[RallyCloseDT]' ' ,[DistanceID]' + ' ,[OpenDT]' ' ,[EventStatusID]' + ' ,[CloseDT]' + ' ,[ScheduleDT]' ' FROM [dbo].[Event]' 'WHERE [SessionID] = :SESSIONID') - Left = 24 - Top = 72 + Left = 344 + Top = 96 ParamData = < item Name = 'SESSIONID' @@ -2761,8 +107,8 @@ object CloneSession: TCloneSession UpdateOptions.UpdateTableName = 'SwimClubMeet..Session' UpdateOptions.KeyFields = 'SessionID' TableName = 'SwimClubMeet..Session' - Left = 288 - Top = 64 + Left = 144 + Top = 96 end object tblEvent: TFDTable ActiveStoredUsage = [auDesignTime] @@ -2771,7 +117,32 @@ object CloneSession: TCloneSession UpdateOptions.UpdateTableName = 'SwimClubMeet..Event' UpdateOptions.KeyFields = 'EventID' TableName = 'SwimClubMeet..Event' - Left = 288 - Top = 112 + Left = 248 + Top = 96 + end + object imgcolClone: TImageCollection + Images = < + item + Name = 'Clone' + SourceImages = < + item + Image.Data = { + 89504E470D0A1A0A0000000D49484452000000300000003008060000005702F9 + 87000000017352474200AECE1CE90000014A494441546843ED993D6E02311085 + BFBD4E2E00220D54D4A4E50C697290349C216D52A7A301910B701DA2510A5628 + B647B36BAF40CFD54AF6BE99F7DEF867BD1D77DEBA3BCF1F1198DAC1A803AFC0 + 16985522F0037C00BB127E84C006F82C018FD4FF027CE5B022040EC062A4044B + 3047E0796C02971E60448052D2D6EF8E1149C00DEEC93431C61DE3A1092C7BEA + EC7BCF6E75A6762095A808389C718B949B0372C0A1746A881CC86D266E75E4C0 + 9F02D9BD4A93784099E45E7597A91C9003FF2BA012D23E30706EA88454422AA1 + AB023A0BDD9E08DD2BC4803272C7D0596880CA3A8D96BE76527578AA78AD7EEB + 8A5DB3CFA397BB2902F64FC048B46896BC9148B6C82436B027E01D585762F10D + BC01E7127E944009B759BF0834933A1128E740ABD5C6AEEE57512172045AAD36 + D508B4586D2C46550251679BBD17F947D62C394F2011F0A85473CC2F06826B31 + 8E670DDF0000000049454E44AE426082} + end> + end> + Left = 24 + Top = 96 end end diff --git a/dlgCloneSession.pas b/dlgCloneSession.pas index d0fe523..5ec72c3 100644 --- a/dlgCloneSession.pas +++ b/dlgCloneSession.pas @@ -8,13 +8,20 @@ interface Vcl.Controls, Vcl.Forms, Vcl.Dialogs, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt, FireDAC.Comp.Client, - Data.DB, FireDAC.Comp.DataSet, Vcl.StdCtrls, Vcl.ExtCtrls, dmSCM; + Data.DB, FireDAC.Comp.DataSet, Vcl.StdCtrls, Vcl.ExtCtrls, dmSCM, + Vcl.VirtualImage, Vcl.BaseImageCollection, Vcl.ImageCollection; type TCloneSession = class(TForm) qrySrcEvent: TFDQuery; tblSession: TFDTable; tblEvent: TFDTable; + lblDescription: TLabel; + pnlFooter: TPanel; + btnClone: TButton; + btnCancel: TButton; + vimgClone: TVirtualImage; + imgcolClone: TImageCollection; procedure FormCreate(Sender: TObject); procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure btnCancelClick(Sender: TObject); @@ -27,8 +34,8 @@ TCloneSession = class(TForm) { Public declarations } end; -var - CloneSession: TCloneSession; +//var +// CloneSession: TCloneSession; implementation @@ -45,7 +52,6 @@ procedure TCloneSession.btnCloneClick(Sender: TObject); ModalResult := mrOk else begin - Beep; ModalResult := mrCancel; end; end; @@ -107,6 +113,13 @@ function TCloneSession.CloneExecute: boolean; tblSession.FieldByName('SessionID').AsInteger; // Status = open tblEvent.FieldByName('EventStatusID').AsInteger := 1; + + // Schedule TTime for the event... + tblEvent.FieldByName('ScheduleDT').AsDateTime := + qrySrcEvent.FieldByName('ScheduleDT').AsDateTime; + + // RallyOpenDT .. RallyCloseDT .. OpenDT .. CloseDT + tblEvent.Post(); i := i + 1; qrySrcEvent.Next; diff --git a/frmMain.pas b/frmMain.pas index 46721ea..d9f870e 100644 --- a/frmMain.pas +++ b/frmMain.pas @@ -4107,7 +4107,10 @@ procedure TMain.Session_CloneExecute(Sender: TObject); try dlg := TCloneSession.Create(self); // raises exception if SCM not assigned. - if IsPositiveResult(dlg.ShowModal) then SCM_RefreshExecute(self); + if IsPositiveResult(dlg.ShowModal) then + begin + SCM_RefreshExecute(self); + end; dlg.Free; except on E: Exception do ShowMessage(E.Message);