Загрузка данных


unit Parking;

{$mode objfpc}{$H+}
{$codepage UTF-8}

interface

uses
  {$ifdef unix}cwstring,{$endif}
  SysUtils;

type
  { --- Иерархия исключений --- }
  
  EParkingError = class(Exception);

  EParkingFull = class(EParkingError)
  strict private
    FCapacity: Integer;
  public
    constructor Create(ACapacity: Integer);
    property Capacity: Integer read FCapacity;
  end;

  ESpotOccupied = class(EParkingError)
  strict private
    FSpotNumber: Integer;
    FOccupiedBy: string;
  public
    constructor Create(ASpotNumber: Integer; const AOccupiedBy: string);
    property SpotNumber: Integer read FSpotNumber;
    property OccupiedBy: string read FOccupiedBy;
  end;

  EInvalidSpot = class(EParkingError)
  strict private
    FSpotNumber: Integer;
    FCapacity: Integer;
  public
    constructor Create(ASpotNumber, ACapacity: Integer);
    property SpotNumber: Integer read FSpotNumber;
    property Capacity: Integer read FCapacity;
  end;

  ECarNotFound = class(EParkingError)
  strict private
    FLicensePlate: string;
  public
    constructor Create(const ALicensePlate: string);
    property LicensePlate: string read FLicensePlate;
  end;

  { --- Класс парковки --- }
  
  TParkingLot = class
  strict private
    FSpots: array of string;
    FCapacity: Integer;
    
    function FindFreeSpot: Integer;
    procedure OccupySpot(ASpotIndex: Integer; const ALicensePlate: string);
    
    function GetCount: Integer;
    function GetIsFull: Boolean;
  public
    constructor Create(ACapacity: Integer);
    destructor Destroy; override;
    
    function Park(const ALicensePlate: string): Integer;
    procedure ParkAtSpot(ASpotNumber: Integer; const ALicensePlate: string);
    procedure Leave(const ALicensePlate: string);
    
    function TryFindCar(const ALicensePlate: string; out ASpotNumber: Integer): Boolean;
    function FindCar(const ALicensePlate: string): Integer;
    procedure MoveCar(const ALicensePlate: string; ANewSpotNumber: Integer);
    
    procedure PrintStatus;
    
    property Capacity: Integer read FCapacity;
    property Count: Integer read GetCount;
    property IsFull: Boolean read GetIsFull;
  end;

implementation

{ EParkingFull }
constructor EParkingFull.Create(ACapacity: Integer);
begin
  inherited Create(Format('парковка заполнена (вместимость: %d)', [ACapacity]));
  FCapacity := ACapacity;
end;

{ ESpotOccupied }
constructor ESpotOccupied.Create(ASpotNumber: Integer; const AOccupiedBy: string);
begin
  inherited Create(Format('место %d занято машиной %s', [ASpotNumber, AOccupiedBy]));
  FSpotNumber := ASpotNumber;
  FOccupiedBy := AOccupiedBy;
end;

{ EInvalidSpot }
constructor EInvalidSpot.Create(ASpotNumber, ACapacity: Integer);
begin
  inherited Create(Format('номер места %d вне диапазона [1..%d]', [ASpotNumber, ACapacity]));
  FSpotNumber := ASpotNumber;
  FCapacity := ACapacity;
end;

{ ECarNotFound }
constructor ECarNotFound.Create(const ALicensePlate: string);
begin
  inherited Create(Format('машина %s не найдена', [ALicensePlate]));
  FLicensePlate := ALicensePlate;
end;

{ TParkingLot }

constructor TParkingLot.Create(ACapacity: Integer);
begin
  if ACapacity <= 0 then
    raise EParkingError.Create('Вместимость парковки должна быть больше нуля');
  
  FCapacity := ACapacity;
  SetLength(FSpots, FCapacity);
end;

destructor TParkingLot.Destroy;
begin
  FSpots := nil;
  inherited Destroy;
end;

{ Приватные контрактные методы }

function TParkingLot.FindFreeSpot: Integer;
var
  I: Integer;
begin
  Assert(not IsFull, 'Контракт нарушен: FindFreeSpot вызван при пустой парковке');
  for I := 0 to FCapacity - 1 do
    if FSpots[I] = '' then
      Exit(I);
  Result := -1;
end;

procedure TParkingLot.OccupySpot(ASpotIndex: Integer; const ALicensePlate: string);
begin
  Assert((ASpotIndex >= 0) and (ASpotIndex < FCapacity),
    Format('Контракт нарушен: индекс %d вне диапазона', [ASpotIndex]));
  Assert(FSpots[ASpotIndex] = '',
    Format('Контракт нарушен: место %d уже занято', [ASpotIndex + 1]));
  FSpots[ASpotIndex] := ALicensePlate;
end;

{ Публичные защищенные методы }

function TParkingLot.Park(const ALicensePlate: string): Integer;
var
  Index: Integer;
begin
  if IsFull then
    raise EParkingFull.Create(FCapacity);
  Index := FindFreeSpot;
  OccupySpot(Index, ALicensePlate);
  Result := Index + 1;
end;

procedure TParkingLot.ParkAtSpot(ASpotNumber: Integer; const ALicensePlate: string);
var
  SpotIndex: Integer;
begin
  if (ASpotNumber < 1) or (ASpotNumber > FCapacity) then
    raise EInvalidSpot.Create(ASpotNumber, FCapacity);
    
  SpotIndex := ASpotNumber - 1;
  
  if FSpots[SpotIndex] <> '' then
    raise ESpotOccupied.Create(ASpotNumber, FSpots[SpotIndex]);
    
  OccupySpot(SpotIndex, ALicensePlate);
end;

procedure TParkingLot.Leave(const ALicensePlate: string);
var
  SpotNumber: Integer;
begin
  SpotNumber := FindCar(ALicensePlate); 
  FSpots[SpotNumber - 1] := '';
end;

function TParkingLot.TryFindCar(const ALicensePlate: string; out ASpotNumber: Integer): Boolean;
var
  I: Integer;
begin
  for I := 0 to FCapacity - 1 do
  begin
    if FSpots[I] = ALicensePlate then
    begin
      ASpotNumber := I + 1;
      Exit(True);
    end;
  end;
  ASpotNumber := 0;
  Result := False;
end;

function TParkingLot.FindCar(const ALicensePlate: string): Integer;
begin
  if not TryFindCar(ALicensePlate, Result) then
    raise ECarNotFound.Create(ALicensePlate);
end;

procedure TParkingLot.MoveCar(const ALicensePlate: string; ANewSpotNumber: Integer);
var
  OldIndex, NewIndex: Integer;
begin
  OldIndex := FindCar(ALicensePlate) - 1; 
  if (ANewSpotNumber < 1) or (ANewSpotNumber > FCapacity) then
    raise EInvalidSpot.Create(ANewSpotNumber, FCapacity);
  NewIndex := ANewSpotNumber - 1;
  if (NewIndex <> OldIndex) and (FSpots[NewIndex] <> '') then
    raise ESpotOccupied.Create(ANewSpotNumber, FSpots[NewIndex]);

  if NewIndex <> OldIndex then
  begin
    FSpots[OldIndex] := '';
    OccupySpot(NewIndex, ALicensePlate);
  end;
end;

procedure TParkingLot.PrintStatus;
var
  I: Integer;
begin
  for I := 0 to FCapacity - 1 do
  begin
    Write(Format('Место %d: ', [I + 1]));
    if FSpots[I] = '' then
      WriteLn('свободно')
    else
      WriteLn(FSpots[I]);
  end;
end;

function TParkingLot.GetCount: Integer;
var
  I, Acc: Integer;
begin
  Acc := 0;
  for I := 0 to FCapacity - 1 do
    if FSpots[I] <> '' then 
      Inc(Acc);
  Result := Acc;
end;

function TParkingLot.GetIsFull: Boolean;
begin
  Result := (GetCount = FCapacity);
end;

end.