Title: Random Number Engine with UNIQUE property
Question: 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 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 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.