Пользовательский интерфейс

Для программы был разработан интерфейс со свободной навигацией. На рисунке 3.4 приведен вид главной формы приложения.

 

 

Рисунок 3.4 – Главное окно приложения

 

В нижней части окна располагается панель управления, которую можно скрыть. Нажатие на кнопку рестарт приводит к прерыванию текущего движению и началу нового случайного. Нажатие на кнопку пауза приостанавливает движение.

На панели управления находится группа визуальных элементов, позволяющих задать параметры мяча – начальную скорость, радиус, а также начальный угол падения.

Также можно задать параметры генерации поверхности. Результирующая поверхность является результатом сложения или разности двух синусоид, параметры которых также можно задать. Это позволяет создать достаточно сложную поверхность (см. рисунок 3.5).

Рисунок 3.5 – Произвольная поверхность

Скрыть и восстановить панель управления можно из главного меню приложения.

Исходный код программы


unit MainFm;

 

interface

 

uses

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

Dialogs, StdCtrls, ExtCtrls, ActnList, Menus, Ball, XPMan, ComCtrls;

 

type

TMainForm = class(TForm)

WorkAreaPanel: TPanel;

LabMainMenu: TMainMenu;

FileMenuItem: TMenuItem;

ExitMenuItem: TMenuItem;

ParamMenuItem: TMenuItem;

AboutMenuItem: TMenuItem;

RestartMenuItem: TMenuItem;

ActionList1: TActionList;

ExitAction: TAction;

RestartAction: TAction;

AboutAction: TAction;

ManageGroupBox: TGroupBox;

RestartButton: TButton;

AnimateTimer: TTimer;

PauseButton: TButton;

XPManifest1: TXPManifest;

PauseAction: TAction;

PauseItem: TMenuItem;

SpeedEdit: TEdit;

SpeedUpDown: TUpDown;

RadiusEdit: TEdit;

RadiusUpDown: TUpDown;

AngleLabel: TLabel;

RadiusLabel: TLabel;

HideControlPanelAction: TAction;

HideControlPanelMenuItem: TMenuItem;

ButtonGroupBox: TGroupBox;

BallGroupBox: TGroupBox;

SurfaceGroupBox: TGroupBox;

AutoGenerateCheckBox: TCheckBox;

FirstSinLabel: TLabel;

FirstSinMEdit: TEdit;

FirstSinPiShiftEdit: TEdit;

OperationEdit: TEdit;

SecondSinMEdit: TEdit;

SecondSinLabel: TLabel;

SecondSinPiShiftEdit: TEdit;

Label1: TLabel;

AnglePiLabel: TLabel;

AngleEdit: TEdit;

RandomAngleCheckBox: TCheckBox;

FirstSinDividerEdit: TEdit;

SecondSinDividerEdit: TEdit;

procedure AboutActionExecute(Sender: TObject);

procedure RestartActionExecute(Sender: TObject);

procedure ExitActionExecute(Sender: TObject);

procedure AnimateTimerTimer(Sender: TObject);

procedure PauseButtonClick(Sender: TObject);

procedure FormPaint(Sender: TObject);

procedure PauseActionExecute(Sender: TObject);

procedure SpeedUpDownClick(Sender: TObject; Button: TUDBtnType);

procedure SpeedEditKeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

procedure FormCreate(Sender: TObject);

procedure RadiusUpDownClick(Sender: TObject; Button: TUDBtnType);

procedure RadiusEditKeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

procedure HideControlPanelActionExecute(Sender: TObject);

procedure WorkAreaPanelResize(Sender: TObject);

procedure AutoGenerateCheckBoxClick(Sender: TObject);

procedure RandomAngleCheckBoxClick(Sender: TObject);

private

FDrawInited: Boolean;

// Начать заново.

procedure Restart;

end;

 

var

MainForm: TMainForm;

 

implementation

 

uses Math;

 

{$R *.dfm}

 

const

MAX_START_SPEED = 10000;

 

type

EWrongOperation = class(Exception);

 

procedure TMainForm.AboutActionExecute(Sender: TObject);

const

ABOUT_MESSAGE = 'Программу разработал в 2011 г.' + sLineBreak +

'студент гр. М01-784-1 - Полин А.Ю.' + sLineBreak +

'(Структурная реализация)';

begin

ShowMessage(ABOUT_MESSAGE);

end;

 

procedure TMainForm.RestartActionExecute(Sender: TObject);

begin

Restart;

end;

 

procedure TMainForm.ExitActionExecute(Sender: TObject);

begin

Close;

end;

 

procedure TMainForm.AnimateTimerTimer(Sender: TObject);

begin

AnimateTimer.Enabled := False;

Ball.Iterate;

AnimateTimer.Enabled := True;

end;

 

procedure TMainForm.PauseButtonClick(Sender: TObject);

begin

AnimateTimer.Enabled := False;

end;

 

procedure TMainForm.FormPaint(Sender: TObject);

begin

if FDrawInited then

Ball.Draw;

end;

 

procedure TMainForm.PauseActionExecute(Sender: TObject);

const

PAUSE_CAPTION = 'Пауза';

PLAY_CAPTION = 'Возобновить';

begin

AnimateTimer.Enabled := not AnimateTimer.Enabled;

if AnimateTimer.Enabled then

PauseAction.Caption := PAUSE_CAPTION

else

PauseAction.Caption := PLAY_CAPTION;

end;

 

procedure TMainForm.SpeedUpDownClick(Sender: TObject; Button: TUDBtnType);

begin

SpeedEdit.Text := FloatToStr(SpeedUpDown.Position / 10);

end;

 

procedure TMainForm.SpeedEditKeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

var

Value: Double;

begin

if Key = VK_RETURN then

try

Value := StrToFloat(SpeedEdit.Text);

if Value < 0 then

SpeedEdit.Text := '0'

else

if Value > MAX_START_SPEED then

SpeedEdit.Text := FloatToStr(MAX_START_SPEED);

SpeedUpDown.Position := Round(StrToFloat(SpeedEdit.Text) * 10);

except

SpeedEdit.Text := FloatToStr(SpeedUpDown.Position / 10);

end;

end;

 

procedure TMainForm.FormCreate(Sender: TObject);

begin

SpeedUpDown.Max := High(SmallInt);

end;

 

procedure TMainForm.RadiusUpDownClick(Sender: TObject;

Button: TUDBtnType);

begin

RadiusEdit.Text := FloatToStr(RadiusUpDown.Position / 10);

end;

 

procedure TMainForm.Restart;

const

PAUSE_CAPTION = 'Пауза';

OPERATION_MINUS = '-';

OPERATION_PLUS = '+';

E_CONVERT_ERROR = '''%s'' не является вещественным значением';

var

Buffer: String;

WrongFloatValue: String;

begin

try

if not FDrawInited then

begin

InitDrawing(WorkAreaPanel);

FDrawInited := True;

end;

PauseAction.Enabled := True;

PauseAction.Caption := PAUSE_CAPTION;

AnimateTimer.Enabled := False;

BallDefaultZeroSpeed := SpeedUpDown.Position / 10;

BallVariable.Radius := RadiusUpDown.Position * 10;

if not AutoGenerateCheckBox.Checked then

with SurfaceGenerationParams do

begin

FirstSinusoidMultiplier := StrToFloat(FirstSinMEdit.Text);

FirstSinusoidPIShift := StrToFloat(FirstSinPiShiftEdit.Text);

FirstSinusoidDivider := StrToFloat(FirstSinDividerEdit.Text);

SecondSinusoidMultiplier := StrToFloat(SecondSinMEdit.Text);

SecondSinusoidPIShift := StrToFloat(SecondSinPiShiftEdit.Text);

SecondSinusoidDivider := StrToFloat(SecondSinDividerEdit.Text);

if Trim(OperationEdit.Text) = OPERATION_PLUS then

SinOperation := 1

else

if Trim(OperationEdit.Text) = OPERATION_MINUS then

SinOperation := -1

else

raise EWrongOperation.Create('Операция может быть только + или -');

end;

if not RandomAngleCheckBox.Checked then

MovementParams.Alpha := Pi / StrToFloat(AngleEdit.Text);

Ball.Restart(WorkAreaPanel.ClientWidth, WorkAreaPanel.ClientHeight,

AutoGenerateCheckBox.Checked, RandomAngleCheckBox.Checked);

if AutoGenerateCheckBox.Checked then

with SurfaceGenerationParams do

begin

FirstSinMEdit.Text := FloatToStr(FirstSinusoidMultiplier);

FirstSinPiShiftEdit.Text := FloatToStr(FirstSinusoidPIShift);

FirstSinDividerEdit.Text := FloatToStr(FirstSinusoidDivider);

SecondSinMEdit.Text := FloatToStr(SecondSinusoidMultiplier);

SecondSinPiShiftEdit.Text := FloatToStr(SecondSinusoidPIShift);

SecondSinDividerEdit.Text := FloatToStr(SecondSinusoidDivider);

case SinOperation of

-1:

OperationEdit.Text := OPERATION_MINUS;

1:

OperationEdit.Text := OPERATION_PLUS;

end;

end;

if RandomAngleCheckBox.Checked then

AngleEdit.Text := FloatToStr(Pi / MovementParams.Alpha);

AnimateTimer.Enabled := True;

except

on E: EConvertError do

begin

Buffer := Copy(E.Message, 2, Length(E.Message) - 1);

WrongFloatValue := Copy(Buffer, 1, Pos('''', Buffer) - 1);

ShowMessage(Format(E_CONVERT_ERROR, [WrongFloatValue]));

end;

on E: EWrongOperation do

ShowMessage(E.Message);

end;

end;

 

procedure TMainForm.RadiusEditKeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

var

Value: Double;

begin

if Key = VK_RETURN then

try

Value := StrToFloat(RadiusEdit.Text);

if Value < 0 then

RadiusEdit.Text := '0'

else

if Value > 0.5 then

RadiusEdit.Text := '0.5';

RadiusUpDown.Position := Round(StrToFloat(RadiusEdit.Text) * 10);

except

RadiusEdit.Text := FloatToStr(RadiusUpDown.Position / 10);

end;

end;

 

procedure TMainForm.HideControlPanelActionExecute(Sender: TObject);

const

HIDE_ACTION_CAPTION = 'Скрыть панель управления';

SHOW_ACTION_CAPTION = 'Показать панель управления';

begin

ManageGroupBox.Visible := not ManageGroupBox.Visible;

case ManageGroupBox.Visible of

True:

HideControlPanelAction.Caption := HIDE_ACTION_CAPTION;

False:

HideControlPanelAction.Caption := SHOW_ACTION_CAPTION;

end;

end;

 

procedure TMainForm.WorkAreaPanelResize(Sender: TObject);

begin

if FDrawInited then

ResizeBox(WorkAreaPanel.ClientWidth, WorkAreaPanel.ClientHeight);

end;

 

procedure TMainForm.AutoGenerateCheckBoxClick(Sender: TObject);

begin

FirstSinMEdit.Enabled := not FirstSinMEdit.Enabled;

FirstSinPiShiftEdit.Enabled := not FirstSinPiShiftEdit.Enabled;

OperationEdit.Enabled := not OperationEdit.Enabled;

SecondSinMEdit.Enabled := not SecondSinMEdit.Enabled;

SecondSinPiShiftEdit.Enabled := not SecondSinPiShiftEdit.Enabled;

FirstSinLabel.Enabled := not FirstSinLabel.Enabled;

SecondSinLabel.Enabled := not SecondSinLabel.Enabled;

FirstSinDividerEdit.Enabled := not FirstSinDividerEdit.Enabled;

SecondSinDividerEdit.Enabled := not SecondSinDividerEdit.Enabled;

end;

 

procedure TMainForm.RandomAngleCheckBoxClick(Sender: TObject);

begin

AnglePiLabel.Enabled := not AngleLabel.Enabled;

AngleEdit.Enabled := not AngleEdit.Enabled;

end;

 

end.

unit Ball;

 

interface

 

uses

Windows, Graphics, Controls, VectoryAlgebra;

 

type

// Мяч.

TBall = record

// Координата X центра.

X: Double;

// Координата Y центра.

Y: Double;

// Радиус.

Radius: Double;

// Скорость.

Speed: Double;

end;

 

// Поверхность.

TSurface = array of TPoint;

 

// Параметры генерации поверхности из двух синусоид.

TSurfaceGenerationParams = record

// Множитель первой синусоиды.

FirstSinusoidMultiplier: Double;

// Множитель второй синусоиды.

SecondSinusoidMultiplier: Double;

// Делитель аргумента первой синусоиды.

FirstSinusoidDivider: Double;

// Делитель аргумента второй синусоиды.

SecondSinusoidDivider: Double;

// Делитель смещения первой синусоиды.

FirstSinusoidPIShift: Double;

// Делитель смещения второй синусоиды.

SecondSinusoidPIShift: Double;

// Операция применяемая над синусоидами.

SinOperation: Integer;

// Минимальное значение координаты Y в точках поверхности.

SurfaceMinY: Integer;

end;

 

// Тип соприкосновения с поверхностью.

TContactType = (

// С левой стороной коробки.

ctBoxLeft,

// С правой стороной коробки.

ctBoxRight,

// С верхом коробки.

ctBoxTop,

// С дном коробки.

ctBoxBottom,

// С поверхностью.

ctSurface);

 

// Параметры движения мяча.

TBallMovementParams = record

// Текущий момент времени для расчета параболы.

TimeMoment: Double;

// Угол альфа для расчета параболы.

Alpha: Double;

// Предыдущее значение координат мяча.

PreviousPoint: TVector2R;

// 0-точка движения по параболе.

ZeroPoint: TVector2R;

// Скорость в 0 точке.

ZeroPointSpeed: Double;

end;

 

// Отрисовать.

procedure Draw;

// Изменить размер коробки.

procedure ResizeBox(

// Ширина коробки.

const ABoxWidth: Integer;

// Высота коробки.

const ABoxHeight: Integer);

// Начать заново.

procedure Restart(

// Ширина коробки.

const ABoxWidth: Integer;

// Высота коробки.

const ABoxHeight: Integer;

// Автоматически генерировать поверхность.

const AIsAutoGeneratedSurface: Boolean;

// Случайный угол падения мяча.

const AIsRandomAngle: Boolean);

// Конструктор.

procedure InitDrawing(

// Окно на котором идет отрисовка.

const AWindow: TWinControl);

// Выполнить итерацию.

procedure Iterate;

 

 

var

// Скорость в 0 точке при рестарте.

BallDefaultZeroSpeed: Double;

// Коэффициент передачи энергии при ударе.

EnegryTransmissionMultiplier: Double;

// Мяч.

BallVariable: TBall;

// Параметры генерации поверхности.

SurfaceGenerationParams: TSurfaceGenerationParams;

// Параметры движения мяча.

MovementParams: TBallMovementParams;

 

implementation

 

uses

Math, Types, SysUtils, Classes;

 

var

// Высота коробки

BoxHeight: Integer;

// Ширина коробки.

BoxWidth: Integer;

// Поверхность.

Surface: TSurface;

// Окно на котором идет отрисовка.

DrawingWindow: TWinControl;

// Задний план.

Background: TBitmap;

 

function GetBallRect: TRect;

begin

Result.TopLeft.X := Round(BallVariable.X - BallVariable.Radius);

Result.TopLeft.Y := Round(BallVariable.Y + BallVariable.Radius);

Result.BottomRight.X := Round(BallVariable.X + BallVariable.Radius);

Result.BottomRight.Y := Round(BallVariable.Y - BallVariable.Radius);

end;

 

procedure CalculateAlpha(

const AContactType: TContactType; const ASurfacePointIndex: Integer);

var

ContactPoint, PreviousPointMove, Perpendicular: TVector2R;

BufferAlpha, Teta, AxisDifferenceAngle: Double;

begin

// Определить точку контакта.

case AContactType of

ctBoxLeft:

ContactPoint := AddVect2R(Vector2R(BallVariable.X, BallVariable.Y), Vector2R(- BallVariable.Radius, 0));

ctBoxRight:

ContactPoint := AddVect2R(Vector2R(BallVariable.X, BallVariable.Y), Vector2R(BallVariable.Radius, 0));

ctBoxTop:

ContactPoint := AddVect2R(Vector2R(BallVariable.X, BallVariable.Y), Vector2R(0, BallVariable.Radius));

ctBoxBottom:

ContactPoint := AddVect2R(Vector2R(BallVariable.X, BallVariable.Y), Vector2R(0, - BallVariable.Radius));

ctSurface:

ContactPoint := Vector2R(Surface[ASurfacePointIndex].X, Surface[ASurfacePointIndex].Y);

end;

with MovementParams do

begin

PreviousPointMove := SubVect2R(PreviousPoint,

Vector2R(BallVariable.X, BallVariable.Y));

Perpendicular := RightPerpendicularVector2R(SubVect2R(Vector2R(BallVariable.X, BallVariable.Y), ContactPoint));

BufferAlpha := AngelFromVectorToVector(Perpendicular, PreviousPointMove);

Teta := Pi - BufferAlpha;

AxisDifferenceAngle := AngelFromVectorToVector2Pi(Vector2R(1, 0), Perpendicular);

Alpha := Teta + AxisDifferenceAngle;

TimeMoment := 0;

ZeroPoint.X := BallVariable.X;

ZeroPoint.Y := BallVariable.Y;

ZeroPointSpeed := BallVariable.Speed;

end;

end;

 

procedure CheckContact;

var

I, MinSurfaceDistanceIndex: Integer;

MinSurfaceDistance, Distance: Double;

begin

with MovementParams do

begin

// Проверить касание коробки.

if (GetBallRect.Left <= 0) and (BallVariable.X <= PreviousPoint.X) then

CalculateAlpha(ctBoxLeft, 0) else

if (GetBallRect.Right >= BoxWidth) and (BallVariable.X >= PreviousPoint.X) then

CalculateAlpha(ctBoxRight, 0)else

if (GetBallRect.Top >= BoxHeight) and (BallVariable.Y >= PreviousPoint.Y) then

CalculateAlpha(ctBoxTop, 0)else

if (GetBallRect.Bottom <= 0) and (BallVariable.Y <= PreviousPoint.Y) then

CalculateAlpha(ctBoxBottom, 0)else

begin

// Проверить касание поверхности.

MinSurfaceDistance := 1.7e308;

MinSurfaceDistanceIndex := -1;

for I := 0 to Length(Surface) - 1 do

begin

Distance := DistBetweenPoints2R(Vector2R(Surface[I].X, Surface[I].Y),

Vector2R(BallVariable.X, BallVariable.Y));

if Distance < MinSurfaceDistance then

begin

MinSurfaceDistance := Distance;

MinSurfaceDistanceIndex := I;

end;

end;

if (MinSurfaceDistanceIndex <> -1) and (MinSurfaceDistance <= BallVariable.Radius) and

(MinSurfaceDistance < DistBetweenPoints2R(Vector2R(Surface[MinSurfaceDistanceIndex].X,

Surface[MinSurfaceDistanceIndex].Y), PreviousPoint)) then

CalculateAlpha(ctSurface, MinSurfaceDistanceIndex);

end;

end;

end;

 

procedure CalculateSurfaceMinY;

var

I, CalculatedValue: Integer;

begin

with SurfaceGenerationParams do

begin

SurfaceMinY := High(Integer);

// Минимальное значение функции встречается на [0; 2Pi].

for I := 0 to 628 do

begin

CalculatedValue := Round((FirstSinusoidMultiplier * Sin(I / FirstSinusoidDivider) -

pi / FirstSinusoidPIShift) + SinOperation * (SecondSinusoidMultiplier *

Sin(I / SecondSinusoidDivider) + pi / SecondSinusoidPIShift));

if CalculatedValue < SurfaceMinY then

SurfaceMinY := CalculatedValue;

end;

SurfaceMinY := Abs(SurfaceMinY) + 10;

end;

end;

 

procedure GenerateRandomSurfaceParams;

begin

with SurfaceGenerationParams do

begin

Randomize;

FirstSinusoidMultiplier := 25 * (Random(5) + 1);

SecondSinusoidMultiplier := 15 * (Random(3) + 1);

FirstSinusoidDivider := 100;

SecondSinusoidDivider := 50;

FirstSinusoidPIShift := Random(3) + 1;

SecondSinusoidPIShift := Random(6) + 1;

SinOperation := Random(2) - 1;

if SinOperation = 0 then

Inc(SinOperation);

end;

end;

 

procedure GenerateSurface;

var

I: Integer;

begin

SetLength(Surface, 0);

SetLength(Surface, BoxWidth);

with SurfaceGenerationParams do

begin

for I := 0 to BoxWidth - 1 do

begin

Surface[I].X := I;

// Сложить 2 синуосоиды.

Surface[I].Y := Round(

(FirstSinusoidMultiplier * Sin(I / FirstSinusoidDivider) - pi / FirstSinusoidPIShift) +

SinOperation * (SecondSinusoidMultiplier * Sin(I/ SecondSinusoidDivider) +

pi / SecondSinusoidPIShift));;

end;

// Необходимо поднять поверхность, чтобы она отображалась целиком.

for I := 0 to BoxWidth - 1 do

Surface[I].Y := Surface[I].Y + Abs(SurfaceMinY);

end;

end;

 

procedure Iterate;

 

var

I: Integer;

IterateCount: Integer;

begin

if BallVariable.Speed > 1 then

IterateCount := Round(BallVariable.Speed) + 10

else

IterateCount := 1;

with MovementParams do

for I := 1 to IterateCount do

begin

TimeMoment := TimeMoment + 0.01 / IterateCount;

PreviousPoint := Vector2R(BallVariable.X, BallVariable.Y);

BallVariable.X := ZeroPoint.X + Cos(Alpha) * ZeroPointSpeed * TimeMoment * 100;

BallVariable.Y := ZeroPoint.Y + (Sin(Alpha) * ZeroPointSpeed * TimeMoment -

4.9 * Sqr(TimeMoment)) * 100;

BallVariable.Speed := Sqrt(Sqr(ZeroPointSpeed * Cos(Alpha)) +

Sqr(ZeroPointSpeed * Sin(Alpha) - 9.8 * TimeMoment));

CheckContact;

end;

Draw;

end;

 

function InvertRect(ARect: TRect): TRect;

begin

Result := ARect;

Result.TopLeft.Y := BoxHeight - Result.TopLeft.Y;

Result.BottomRight.Y := BoxHeight - Result.BottomRight.Y;

end;

 

function Invert(APoint: TPoint): TPoint;

begin

Result.X := APoint.X;

Result.Y := BoxHeight - APoint.Y;

end;

 

procedure GenerateBackground;

var

DrawableSurface: array of TPoint;

I: Integer;

begin

if not Assigned(Background) then

Background := TBitmap.Create;

Background.Width := BoxWidth;

Background.Height := BoxHeight;

SetLength(DrawableSurface, Length(Surface) + 2);

try

for I := 0 to Length(Surface) - 1 do

DrawableSurface[I] := Invert(Surface[I]);

DrawableSurface[Length(Surface)].X := BoxWidth;

DrawableSurface[Length(Surface)].Y := BoxHeight;

DrawableSurface[Length(Surface) + 1].X := 0;

DrawableSurface[Length(Surface) + 1].Y := BoxHeight;

 

with Background.Canvas do

begin

Pen.Color := clRed;

Brush.Color := clWhite;

Rectangle(DrawingWindow.ClientRect);

Brush.Color := clRed;

Polygon(DrawableSurface);

end;

finally

SetLength(DrawableSurface, 0);

end;

end;

 

procedure ResizeBox(const ABoxWidth,

ABoxHeight: Integer);

begin

BoxWidth := ABoxWidth;

BoxHeight := ABoxHeight;

GenerateSurface;

GenerateBackground;

end;

 

procedure Restart(const ABoxWidth,

ABoxHeight: Integer; const AIsAutoGeneratedSurface,

AIsRandomAngle: Boolean);

var

RandomAnglePart: Integer;

begin

BoxWidth := ABoxWidth;

BoxHeight := ABoxHeight;

if AIsAutoGeneratedSurface then

GenerateRandomSurfaceParams;

CalculateSurfaceMinY;

GenerateSurface;

BallVariable.X := BoxWidth / 2;

BallVariable.Y := BoxHeight * 3 / 4;

BallVariable.Speed := 0;

with MovementParams do

begin

PreviousPoint := Vector2R(BallVariable.X, BallVariable.Y);

ZeroPoint := Vector2R(BallVariable.X, BallVariable.Y);

ZeroPointSpeed := BallDefaultZeroSpeed;

TimeMoment := 0;

if AIsRandomAngle then

begin

Randomize;

RandomAnglePart := Random(11) - 5;

if InRange (RandomAnglePart, -1, 1) then

RandomAnglePart := - 3;

Alpha := pi / RandomAnglePart;

end;

end;

GenerateBackground;

Draw;

end;

 

procedure InitDrawing(

const AWindow: TWinControl);

begin

DrawingWindow := AWindow;

end;

 

procedure Draw;

var

BufferBitmap: TBitmap;

DC: HDC;

begin

BufferBitmap := TBitmap.Create;

try

BufferBitmap.Width := BoxWidth;

BufferBitmap.Height := BoxHeight;

if Assigned(Background) then

BitBlt(BufferBitmap.Canvas.Handle, 0, 0, BoxWidth, BoxHeight,

Background.Canvas.Handle, 0, 0, SRCCOPY);

with BufferBitmap.Canvas do

begin

Pen.Color := clBlue;

Brush.Color := clBlue;

Ellipse(Ball.InvertRect(GetBallRect));

end;

DC := GetWindowDC(DrawingWindow.Handle);

try

BitBlt(DC, 0, 0, BoxWidth, BoxHeight,

BufferBitmap.Canvas.Handle, 0, 0, SRCCOPY);

finally

ReleaseDC(DrawingWindow.Handle, DC);

end;

finally

BufferBitmap.Free;

end;

end;

 

procedure FreeObjects;

begin

SetLength(Surface, 0);

FreeAndNil(Background);

end;

 

initialization

 

BallVariable.Radius := 20.0;

BallDefaultZeroSpeed := 0;

EnegryTransmissionMultiplier := 1;

 

finalization

 

FreeObjects;

 

end.


Вывод

Поставленная цель выполнена – с использованием структурного подхода было разработано и реализовано приложение, решающее поставленную задачу.

 


Лабораторная работа № 2 «Технология тестирования ПО при структурном подходе»

Постановка задачи

Разработать и провести тесты для приложения, реализованного в лабораторной работе № 1, по методу «белого ящика».

Цель работы

Изучить метод «белого ящика» тестирования ПО и применить полученные знания на практике.

Проектирование тестов

Далее приведено проектирование тестов по методу «белого ящика» для узкого места приложения – метода определения касания. Формирование тестовых наборов осуществляется по принципу покрытия условий.

Код метода проверки касания.

function CheckContact: Integer;

var

Index: Integer;

MinSurfaceDistance: Double;

begin

Result := 0;

with MovementParams do

begin

// Проверить касание коробки.

if (GetBallRect.Left <= 0) and (BallVariable.X <= PreviousPoint.X) then

begin

Result := 1;

CalculateAlpha(ctBoxLeft, 0);

end

else

if (GetBallRect.Right >= BoxWidth) and (BallVariable.X >= PreviousPoint.X) then

begin

Result := 2;

CalculateAlpha(ctBoxRight, 0);

end

else

if (GetBallRect.Top >= BoxHeight) and (BallVariable.Y >= PreviousPoint.Y) then

begin

Result := 3;

CalculateAlpha(ctBoxTop, 0);

end

else

if (GetBallRect.Bottom <= 0) and (BallVariable.Y <= PreviousPoint.Y) then

begin

Result := 4;

CalculateAlpha(ctBoxBottom, 0)

end

else

begin

CalculateMinSurfaceDistance(Index, MinSurfaceDistance);

if (Index <> -1) and (MinSurfaceDistance <= BallVariable.Radius) and

(MinSurfaceDistance < DistBetweenPoints2R(Vector2R(Surface[Index].X,

Surface[Index].Y), PreviousPoint)) then

begin

Result := 5;

CalculateAlpha(ctSurface, Index);

end;

end;

end;

end;

 

Входными данными являются параметры передвижения MovementParams, параметры мяча BallVariable, и поверхность Surface.

Представление метода в виде графа изображено на рисунке 3.1.

Рисунок 3.1 – Метод CheckContact, представленный в виде графа

Как видно из рисунка в методе имеется 5 условий, выполнение каждого из которых делает невозможным выполнение остальных. Таким образом, необходимо сформировать данные для следующих переходов 1-2-3-12, 1-2-4-5-12, 1-2-4-6-7-12, 1-2-4-6-8-9-12, 1-2-4-6-8-10-11-12 и 1-2-4-6-8-10-12. В таблице 3.1 приведены данные удовлетворяющие переходам.

Таблица 3.1.

Переход Значения параметров Ожидаемый результат
1-2-3-12 BallVariable.Radius = 20; BallVariable.X = 20; PreviousPointX = 20
1-2-4-5-12 BoxWidth = 600; BallVariable.Radius = 20; BallVariable.X = 581; PreviousPointX = 580
1-2-4-6-7-12 BoxHeight = 480; BoxWidth = 600; BallVariable.Radius = 20; BallVariable.Y = 461; BallVariable.X = 300; PreviousPointY = 460; PreviousPointX = 300
1-2-4-6-8-9-12 BoxHeight = 480; BoxWidth = 600; BallVariable.Radius = 20; BallVariable.Y = 20; BallVariable.X = 300; PreviousPointY = 21; PreviousPointX = 300
1-2-4-6-8-10-11-12 BoxHeight = 480; BoxWidth = 600; BallVariable.Radius = 20; BallVariable.Y = 40; BallVariable.X = 300; PreviousPointY = 41; PreviousPointX = 300;Surface[0] .X =300; Surface[0] .Y =20;
1-2-4-6-8-10-12 BoxHeight = 480; BoxWidth = 600; BallVariable.Radius = 20; BallVariable.Y = 40; BallVariable.X = 300; PreviousPointY = 20; PreviousPointX = 300;Surface[0] .X =300; Surface[0] .Y =20; Length(Surface) = 1;

 

Результаты тестирования

Для проведения тестирования реализуем метод, задающий тестовые параметры, вызывающий метод CheckContact, анализирующий код возврата работы функции, и протоколирующий результаты выполнения тестов. Ниже приведен исходный код из модуля тестирования, относящийся к проверке метода CheckContact.

 

type

TTestSet = record

InputParams: TBallMovementParams;

InputBall: TBall;

SurfacePoint: TPoint;

ExpectedResult: Integer;

end;

 

const

TestData: array[0..5] of TTestSet = (

(InputParams : (PreviousPoint:(X: 20));

InputBall:(X: 20; Radius: 20); ExpectedResult: 1),

(InputParams : (PreviousPoint:(X: 580));

InputBall:(X: 581; Radius: 20); ExpectedResult: 2),

(InputParams : (PreviousPoint:(X: 300; Y:460));

InputBall:(X: 300; Y: 461; Radius: 20); ExpectedResult: 3),

(InputParams : (PreviousPoint:(X: 300; Y:21));

InputBall:(X: 300; Y: 20; Radius: 20); ExpectedResult: 4),

(InputParams : (PreviousPoint:(X: 300; Y:41));

InputBall:(X: 300; Y: 40; Radius: 20); SurfacePoint: (X: 300; Y: 20);

ExpectedResult: 5),

(InputParams : (PreviousPoint:(X: 300; Y:20));

InputBall:(X: 300; Y: 40; Radius: 20); SurfacePoint: (X: 300; Y: 20);

ExpectedResult: 0));

 

procedure CheckContactTest;

const

WINDOW_WIDTH = 600;

WINDOW_HEIGHT = 480;

LOG_NAME = 'CheckContactLog.txt';

RESULT_FORMAT = 'Result = %d and Expected result = %d => Test %s' + sLineBreak;

SUCCEED_RESULT = 'Succeed';

FAILED_RESULT = 'Failed';

var

I: Integer;

LogStream: TStringStream;

SaveStream: TFileStream;

CallResult: Integer;

begin

LogStream := TStringStream.Create('');

try

Restart(WINDOW_WIDTH, WINDOW_HEIGHT, True, True);

for I := 0 to Length(TestData) - 1 do

begin

MovementParams.PreviousPoint := TestData[I].InputParams.PreviousPoint;

BallVariable.X := TestData[I].InputBall.X;

BallVariable.Y := TestData[I].InputBall.Y;

SetLength(Surface, 0);

SetLength(Surface, 1);

Surface[0] := TestData[I].SurfacePoint;

CallResult := CheckContact;

if CallResult = TestData[I].ExpectedResult then

LogStream.WriteString(Format(RESULT_FORMAT,

[CallResult, TestData[I].ExpectedResult, SUCCEED_RESULT]))

else

LogStream.WriteString(Format(RESULT_FORMAT,

[CallResult, TestData[I].ExpectedResult, FAILED_RESULT]));

end;

finally

SaveStream := TFileStream.Create(LOG_NAME, fmCreate);

try

LogStream.Position := 0;

SaveStream.CopyFrom(LogStream, LogStream.Size);

finally

SaveStream.Free;

end;

LogStream.Free;

end;

end;

 

В результате работы тестового метода в файле протокола находятся следующие результаты.

Result = 1 and Expected result = 1 => Test Succeed

Result = 2 and Expected result = 2 => Test Succeed

Result = 3 and Expected result = 3 => Test Succeed

Result = 4 and Expected result = 4 => Test Succeed

Result = 5 and Expected result = 5 => Test Succeed

Result = 0 and Expected result = 0 => Test Succeed

 

Полученные результаты говорят о верности работы метода CheckContact.

Вывод

Поставленная цель выполнена – был изучена методология тестирования программного обеспечения по принципу «белого ящика», полученные знания были применены на практике для проектирования и реализации автотеста, проверяющего корректность работы метода CheckContact.

 


Лабораторная работа № 3 «Технология разработки ПО при объектном подходе»

Постановка задачи

Разработать программную модель мяча, подпрыгивающего на рельефной поверхности под действием сил гравитации. При разработке применять технологию объектно-ориентированного программирования.

Цель работы

Изучить технологию разработки ПО при объектно-ориентированном подходе. Применить полученные знания на практике, разработав программу.



/cgi-bin/footer.php"; ?>