diff --git a/solvers/UAplenty.pas b/solvers/UAplenty.pas index 31f9c2a..4d32c30 100644 --- a/solvers/UAplenty.pas +++ b/solvers/UAplenty.pas @@ -22,27 +22,289 @@ unit UAplenty; interface uses - Classes, SysUtils, USolver; + Classes, SysUtils, Generics.Collections, USolver; type + TMachinePartCategory = (mpcExtremelyCoolLookingCategoryIndex, mpcMusicalCategoryIndex, mpcAerodynamicCategoryIndex, + mpcShinyCategoryIndex); + + { TMachinePart } + + TMachinePart = class + private + FCategories: array[TMachinePartCategory] of Integer; + function GetCategory(const AIndex: TMachinePartCategory): Integer; + public + property Category[AIndex: TMachinePartCategory]: Integer read GetCategory; + constructor Create(const ALine: string); + function CalcRating: Integer; + end; + + TWorkflow = class; + + TWorkflowRuleEffectType = (wetAccept, wetReject, wetMove); + + { TWorkflowRuleEffect } + + TWorkflowRuleEffect = class + private + FEffectType: TWorkflowRuleEffectType; + FMoveDestinationName: string; + FMoveDestination: TWorkflow; + public + property EffectType: TWorkflowRuleEffectType read FEffectType; + property MoveDestinationName: string read FMoveDestinationName; + property MoveDestination: TWorkflow read FMoveDestination write FMoveDestination; + constructor Create(const ALine: string); + function IsEqualTo(const AOther: TWorkflowRuleEffect): Boolean; + end; + + { TWorkflowRule } + + TWorkflowRule = class + private + FCategory: TMachinePartCategory; + FLessThan: Boolean; + FThreshold: Integer; + FEffect: TWorkflowRuleEffect; + public + property Effect: TWorkflowRuleEffect read FEffect; + constructor Create(const ALine: string); + destructor Destroy; override; + function IsMatch(const AMachinePart: TMachinePart): Boolean; + end; + + TWorkflowRules = specialize TObjectList; + + { TWorkflow } + + TWorkflow = class + private + FName: string; + FRules: TWorkflowRules; + FLastEffect: TWorkflowRuleEffect; + public + property Name: string read FName; + constructor Create(const ALine: string); + destructor Destroy; override; + function Process(const AMachinePart: TMachinePart): TWorkflowRuleEffect; + end; + + TWorkflows = specialize TObjectList; { TAplenty } TAplenty = class(TSolver) + private + FIsReadingWorkflows: Boolean; + FWorkflows: TWorkflows; + FStart: TWorkflow; + procedure ProcessWorkflowLine(const ALine: string); + procedure ProcessMachinePartLine(const ALine: string); public + constructor Create; + destructor Destroy; override; + procedure Init; override; procedure ProcessDataLine(const ALine: string); override; procedure Finish; override; function GetDataFileName: string; override; function GetPuzzleName: string; override; end; +const + CAccepted = 'A'; + CRejected = 'R'; + CLessThanChar = '<'; + CStartWorkflowName = 'in'; + implementation +{ TMachinePart } + +function TMachinePart.GetCategory(const AIndex: TMachinePartCategory): Integer; +begin + Result := FCategories[AIndex]; +end; + +constructor TMachinePart.Create(const ALine: string); +var + split: TStringArray; +begin + split := ALine.Split(','); + FCategories[mpcExtremelyCoolLookingCategoryIndex] := StrToInt(Copy(split[0], 4, Length(split[0]) - 3)); + FCategories[mpcMusicalCategoryIndex] := StrToInt(Copy(split[1], 3, Length(split[1]) - 2)); + FCategories[mpcAerodynamicCategoryIndex] := StrToInt(Copy(split[2], 3, Length(split[2]) - 2)); + FCategories[mpcShinyCategoryIndex] := StrToInt(Copy(split[3], 3, Length(split[3]) - 3)); +end; + +function TMachinePart.CalcRating: Integer; +var + cat: Integer; +begin + Result := 0; + for cat in FCategories do + Inc(Result, cat); +end; + +{ TWorkflowRuleEffect } + +constructor TWorkflowRuleEffect.Create(const ALine: string); +begin + if ALine = CAccepted then + FEffectType := wetAccept + else if ALine = CRejected then + FEffectType := wetReject + else begin + FEffectType := wetMove; + FMoveDestinationName := ALine; + FMoveDestination := nil; + end; +end; + +function TWorkflowRuleEffect.IsEqualTo(const AOther: TWorkflowRuleEffect): Boolean; +begin + Result := (FEffectType = AOther.FEffectType) and (FMoveDestinationName = AOther.FMoveDestinationName); +end; + +{ TWorkflowRule } + +constructor TWorkflowRule.Create(const ALine: string); +var + split: TStringArray; +begin + case ALine[1] of + 'x': FCategory := mpcExtremelyCoolLookingCategoryIndex; + 'm': FCategory := mpcMusicalCategoryIndex; + 'a': FCategory := mpcAerodynamicCategoryIndex; + 's': FCategory := mpcShinyCategoryIndex; + end; + FLessThan := Aline[2] = CLessThanChar; + + split := ALine.Split(':'); + FThreshold := StrToInt(Copy(split[0], 3, Length(split[0]) - 2)); + FEffect := TWorkflowRuleEffect.Create(split[1]); +end; + +destructor TWorkflowRule.Destroy; +begin + FEffect.Free; + inherited Destroy; +end; + +function TWorkflowRule.IsMatch(const AMachinePart: TMachinePart): Boolean; +begin + if FLessThan then + Result := AMachinePart.Category[FCategory] < FThreshold + else + Result := AMachinePart.Category[FCategory] > FThreshold; +end; + +{ TWorkflow } + +constructor TWorkflow.Create(const ALine: string); +var + split: TStringArray; + i: Integer; +begin + split := ALine.Split([',', '{', '}']); + FName := split[0]; + FRules := TWorkflowRules.Create; + for i := 1 to Length(split) - 3 do + FRules.Add(TWorkflowRule.Create(split[i])); + FLastEffect := TWorkflowRuleEffect.Create(split[Length(split) - 2]); + + i := FRules.Count; + while (i > 0) and (FRules[i - 1].Effect.IsEqualTo(FLastEffect)) do + Dec(i); + if i < FRules.Count then + FRules.DeleteRange(i, FRules.Count - i); +end; + +destructor TWorkflow.Destroy; +begin + FRules.Free; + FLastEffect.Free; + inherited Destroy; +end; + +function TWorkflow.Process(const AMachinePart: TMachinePart): TWorkflowRuleEffect; +var + rule: TWorkflowRule; +begin + for rule in FRules do + if rule.IsMatch(AMachinePart) then + begin + Result := rule.Effect; + Exit; + end; + Result := FLastEffect; +end; + { TAplenty } +procedure TAplenty.ProcessWorkflowLine(const ALine: string); +var + workflow: TWorkflow; +begin + workflow := TWorkflow.Create(ALine); + FWorkflows.Add(workflow); + if workflow.Name = CStartWorkflowName then + FStart := workflow; +end; + +procedure TAplenty.ProcessMachinePartLine(const ALine: string); +var + part: TMachinePart; + workflow, search: TWorkflow; + effect: TWorkflowRuleEffect; +begin + part := TMachinePart.Create(ALine); + workflow := FStart; + repeat + effect := workflow.Process(part); + if effect.EffectType = wetMove then + begin + if effect.MoveDestination = nil then + for search in FWorkflows do + if search.Name = effect.MoveDestinationName then + begin + effect.MoveDestination := search; + Break; + end; + workflow := effect.MoveDestination; + end; + until effect.EffectType <> wetMove; + + if effect.EffectType = wetAccept then + Inc(FPart1, part.CalcRating); + part.Free; +end; + +constructor TAplenty.Create; +begin + FWorkflows := TWorkflows.Create; +end; + +destructor TAplenty.Destroy; +begin + FWorkflows.Free; + inherited Destroy; +end; + +procedure TAplenty.Init; +begin + inherited Init; + FIsReadingWorkflows := True; +end; + procedure TAplenty.ProcessDataLine(const ALine: string); begin - + if ALine = '' then + FIsReadingWorkflows := False + else if FIsReadingWorkflows then + ProcessWorkflowLine(ALine) + else + ProcessMachinePartLine(ALine); end; procedure TAplenty.Finish; diff --git a/tests/UAplentyTestCases.pas b/tests/UAplentyTestCases.pas index f53e6de..6ed66a8 100644 --- a/tests/UAplentyTestCases.pas +++ b/tests/UAplentyTestCases.pas @@ -57,7 +57,7 @@ end; procedure TAplentyFullDataTestCase.TestPart1; begin - AssertEquals(-1, FSolver.GetResultPart1); + AssertEquals(331208, FSolver.GetResultPart1); end; procedure TAplentyFullDataTestCase.TestPart2;