Random Number Engine with UNIQUE property (Views: 30)
Problem/Question/Abstract: This class can be used to generate random numbers from MinNumber to MaxNumber returning a number only once if Unique is set. A check is also available to see if the numbers has been used. A dynamic TBit array is used to ensure numbers are returned only once and to see if ALL the numbers have been used. Useful particularly for card games eg. random numbers from 1 to 52, and once drawn do not draw same card again until pack is empty. properties MinNumber Random Number from. MaxNumber Random Number to. NumbersTotal Total available numbers. NumbersUsed Unique numbers used of total. NumbersFree Unique numbers free of total. Unique If true then a number will not be repeated. If false then numbers may be repeated. methods IsUsed(Index) Returns true if UNIQUE used and number has been used. Reset Resets the NumbersUsed and NumbersFree (Used by UNIQUE) GetRandom Returns a random number from MinNumber to MaxNumber (will not repeat a number if UNIQUE is set) // ========================= // Simple Example // ========================= procedure TForm1.Button2Click(Sender: TObject); var R: TRandomEngine; begin R := TRandomEngine.Create; R.MinNumber := 1; R.MaxNumber := 52; R.Unique := true; repeat Memo1.Lines.Add(inttostr(R.GetRandom) + ' ' + inttostr(R.NumbersTotal) + ' ' + inttostr(R.NumbersUsed) + ' ' + inttostr(R.NumbersFree)); until R.NumbersFree = 0; R.Free; end; Answer: unit REngine; interface uses Windows, Classes; type TRandomEngine = class(TObject) private FSelected: TBits; FArrSize, FNumbersUsed: longint; FMinNumber, FMaxNumber: longint; FUnique: boolean; procedure SizeSelArray; procedure SetFMinNumber(NewValue: longint); procedure SetFMaxNumber(NewValue: longint); function GetFNumbersFree: longint; public constructor Create; destructor Destroy; override; procedure Reset; function GetRandom: longint; function IsUsed(Index: longint): boolean; property MinNumber: longint read FMinNumber write SetFMinNumber; property MaxNumber: longint read FMinNumber write SetFMaxNumber; property Unique: boolean read FUnique write FUnique; property NumbersUsed: longint read FNumbersUsed; property NumbersTotal: longint read FArrSize; property NumbersFree: longint read GetFNumbersFree; end; // ------------------------------------------------------------- implementation // =================================== // Create and Free the Object // =================================== constructor TRandomEngine.Create; begin FSelected := TBits.Create; FNumbersUsed := 0; FMinNumber := 0; FMaxNumber := 0; FArrSize := 0; FUnique := false; Randomize; end; destructor TRandomEngine.Destroy; begin inherited Destroy; FSelected.Free; end; // =========================== // Property Get/Set methods // =========================== procedure TRandomEngine.SetFMinNumber(NewValue: longint); begin if (NewValue <> FMinNumber) then begin FMinNumber := NewValue; if FMinNumber > FMaxNumber then FMaxNumber := FMinNumber; SizeSelArray; end; end; procedure TRandomEngine.SetFMaxNumber(NewValue: longint); begin if (NewValue <> FMaxNumber) then begin FMaxNumber := NewValue; if FMaxNumber < FMinNumber then FMinNumber := FMaxNumber; SizeSelArray; end; end; function TRandomEngine.GetFNumbersFree: longint; begin Result := FArrSize - FNumbersUsed; end; // ======================================= // Resize the boolean array (FSelected) // ======================================= procedure TRandomEngine.SizeSelArray; var i: longint; begin FArrSize := FMaxNumber - FMinNumber + 1; if FArrSize > 0 then begin FSelected.Size := FArrSize; for i := 0 to FArrSize - 1 do FSelected[i] := false; end; FNumbersUsed := 0; end; // ======================================= // Reset avail,used and free numbers. // Reset FSelected array to false for // IsUsed() // ======================================= procedure TRandomEngine.Reset; begin SizeSelArray; end; // =================================================== // Return true/false if numbers has been used if // Unique is set // =================================================== function TRandomEngine.IsUsed(Index: longint): boolean; var Retvar: boolean; begin if (Index < FMinNumber) or (Index > FMaxNumber) then Retvar := false else RetVar := FSelected[Index - FMinNumber]; Result := RetVar; end; // =================================================== // Return a random number based on Min - Max // If Unique then generate based on FSelected // array (ie. make sure number has not been used // =================================================== function TRandomEngine.GetRandom: longint; var V: longint; NumSelected: boolean; begin if FUnique and (FNumbersUsed = FArrSize) then V := 0 else begin repeat V := Random(FMaxNumber - FMinNumber + 1) + FMinNumber; if not FUnique then NumSelected := true else begin if FSelected[V - FMinNumber] then NumSelected := false else begin NumSelected := true; FSelected[V - FMinNumber] := true; inc(FNumbersUsed); end; end; until NumSelected; end; Result := V; end; end. |