Простейший пример приложения под Android, написанный на Delphi.
Справка: Пятнашки - популярная головоломка, придуманная в 1878 году Ноем Чепмэном. Представляет собой набор одинаковых квадратных костяшек с нанесёнными числами, заключённых в квадратную коробку. Длина стороны коробки в четыре раза больше длины стороны костяшек для набора из 15 элементов (и в три раза больше для набора в 8 элементов), соответственно в коробке остаётся незаполненным одно квадратное поле. Цель игры — перемещая костяшки по коробке, добиться упорядочивания их по номерам, желательно сделав как можно меньше перемещений. (wiki)
Update 29.05.14 Добавил исходный код и описание.
Update 20.05.14 Внесены изменения в исходный код.
Update 20.05.14 Внесены изменения в исходный код.
Процесс написания оказался достаточно простым, поэтому я решил не описывать его.
Все компоненты (кроме анимации) созданы в дизайн-тайме, хотя можно создать их все динамически. Мне же удобнее показать структуру на картинке.
Играть можно на любых Android устройствах, при запуске игры, поле автоматически подстраивается под экран устройства.
Поле для игры – это «TRectangle».
Костяшки – это «TRectangle» и «TLabel».
«TRectangle» используется для того чтобы можно было легко поменять вид костяшек и поля.
Структура вкладки «Игра»:
Для перемещения костяшек используется анимация «TFloatAnimation».
Update 29.05.14 Добавил исходный код и описание.
По просьбам читателей, решил выложить и разобрать исходный код (Как всегда, на идеал не претендует).
Кода получилось немного. Кстати приложение без проблем работает на Android иWindows 7.
Что нам необходимо:
- Кладём «TabControl», первая вкладка - ЛОГО+МЕНЮ, вторая вкладка – Поле для игры.
- Поле для игры, в котором будут находиться костяшки – это «TRectangle» с именем «Field1»
- Костяшки, которые будут перемещаться между собой - это «TRectangle» с именем «Cell*», также для пользователя в каждом «TRectangle» добавим «TLabel» с именем «Counter*», в нём будут написаны числа от 1 до 16.
Всё это можно создавать как динамически, так и заранее (в дизайн-тайме).
В коде используем:
- У каждой костяшек используем свойства «Name», «Tag», «TagString», «Visible», «Position.X», «Position.Y».
- «Name» - используется для того чтобы находить нужную костяшку (например при проверке на выигрыш)
- «Tag» - храним значение от 1 до 16, значения меняются между собой при перемещении костяшек.
- «TagString» - храним значение от 1 до 15 и пустое значение для 16-ой костяшки. Не изменяются. Необходимо для того чтобы на 100% быть уверенным, что перемещение осуществляется между обычной костяшкой и пустой. Можно обойтись и без этого свойства!
- «Visible», «Position.X», «Position.Y» - эти значения меняются при перемещении костяшек.
- У всех костяшек (кроме 16-ой) прописано событие «OnMouseDown».
- У 16-ой костяшки прописано только событие «OnMouseMove».
Исходный код:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects,
FMX.StdCtrls, FMX.Colors, FMX.Ani, FMX.TabControl, FMX.Layouts;
type
TForm1 = class(TForm)
Field1: TRectangle;
Cell1: TRectangle;
Cell2: TRectangle;
Cell3: TRectangle;
Cell4: TRectangle;
Cell5: TRectangle;
Cell6: TRectangle;
Cell7: TRectangle;
Cell8: TRectangle;
Cell9: TRectangle;
Cell10: TRectangle;
Cell11: TRectangle;
Cell12: TRectangle;
Cell13: TRectangle;
Cell14: TRectangle;
Cell15: TRectangle;
Cell16: TRectangle;
Counter1: TLabel;
Counter2: TLabel;
Counter3: TLabel;
Counter4: TLabel;
Counter5: TLabel;
Counter6: TLabel;
Counter7: TLabel;
Counter8: TLabel;
Counter9: TLabel;
Counter10: TLabel;
Counter11: TLabel;
Counter12: TLabel;
Counter13: TLabel;
Counter14: TLabel;
Counter15: TLabel;
Counter16: TLabel;
TabControl1: TTabControl;
TabItem1: TTabItem;
TabItem2: TTabItem;
sbPlay: TSpeedButton;
ImageLogo: TImage;
ToolBar1: TToolBar;
sbBackMenu: TSpeedButton;
ToolBar2: TToolBar;
ImageSteps: TImage;
LabelSteps: TLabel;
ImageTime: TImage;
LabelTime: TLabel;
sbGameUpdate: TSpeedButton;
Timer1: TTimer;
Layout1: TLayout;
sbStatistics: TSpeedButton;
procedure RectangleAllMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
procedure RectangleAllMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Single);
procedure FormCreate(Sender: TObject);
procedure sbPlayClick(Sender: TObject);
procedure sbBackMenuClick(Sender: TObject);
procedure sbGameUpdateClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
Shift: TShiftState);
private
{ Private declarations }
FXPosOld, FYPosOld: Single;
FPressKey, FAnimateNotRun: Boolean;
FSendItem: TRectangle;
public
{ Public declarations }
function CheckVictory: Boolean;
procedure ClearGame;
procedure AnimateFloatWaitStatus(const AParent: TFmxObject;
const APropertyName: string; const NewValue: Single;
Duration: Single = 0.2; AType: TAnimationType = TAnimationType.In;
AInterpolation: TInterpolationType = TInterpolationType.Linear);
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
uses
System.DateUtils, System.Math;
procedure TForm1.RectangleAllMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Single);
var
ItemOne, ItemTwo: TRectangle;
XPosDiff, YPosDiff: Single;
ItemTagTemp: integer;
begin
if (FPressKey = FAnimateNotRun) AND (Sender is TRectangle) then
begin
ItemOne := TRectangle(Sender); // Над ним проносится курсор или палец :)
ItemTwo := FSendItem; // Выбранный итем
if ItemOne.TagString = '' then
begin
XPosDiff := Abs(ItemOne.Position.X - ItemTwo.Position.X);
YPosDiff := Abs(ItemOne.Position.Y - ItemTwo.Position.Y);
if ((ItemOne.Position.X = ItemTwo.Position.X) OR
(ItemOne.Position.Y = ItemTwo.Position.Y)) AND
((XPosDiff <= ItemTwo.Width) AND (YPosDiff <= ItemTwo.Height)) then
begin
ItemTagTemp := ItemTwo.Tag;
ItemTwo.Tag := ItemOne.Tag;
ItemOne.Tag := ItemTagTemp;
if (ItemOne.Position.X = ItemTwo.Position.X) then
begin
ItemOne.Visible := False;
AnimateFloatWaitStatus(ItemTwo, 'Position.Y', ItemOne.Position.Y, 0.2,
TAnimationType.In, TInterpolationType.Linear);
ItemOne.Visible := True;
ItemOne.Position.Y := FYPosOld;
end
else
begin
ItemOne.Visible := False;
AnimateFloatWaitStatus(ItemTwo, 'Position.X', ItemOne.Position.X, 0.2,
TAnimationType.In, TInterpolationType.Linear);
ItemOne.Visible := True;
ItemOne.Position.X := FXPosOld;
end;
if CheckVictory then
begin
Timer1.Enabled := False;
showmessage('Победа!');
end;
LabelSteps.Text := IntToStr(StrToInt(LabelSteps.Text) + 1);
end;
FPressKey := False;
end;
end;
end;
procedure TForm1.sbBackMenuClick(Sender: TObject);
begin
TabControl1.ActiveTab := TabItem1;
ClearGame;
end;
procedure TForm1.sbGameUpdateClick(Sender: TObject);
begin
ClearGame;
sbGameUpdate.Enabled := False;
sbPlayClick(Self);
sbGameUpdate.Enabled := True;
end;
procedure TForm1.sbPlayClick(Sender: TObject);
var
i, RndNumOne, RndNumTwo, ItemTagTemp: integer;
ItemOne, ItemTwo: TRectangle;
ItemOnePosX, ItemOnePosY: Single;
const
NamePrefixCell = 'Cell';
NumberOfCells = 16;
begin
TabControl1.ActiveTab := TabItem2;
for i := 1 to 8 do
begin
RndNumOne := RandomRange(1, NumberOfCells);
RndNumTwo := RandomRange(1, NumberOfCells);
if FAnimateNotRun then
begin
if RndNumOne <> RndNumTwo then
begin
ItemOne := TRectangle
(FindComponent(NamePrefixCell + IntToStr(RndNumOne)));
ItemTwo := TRectangle
(FindComponent(NamePrefixCell + IntToStr(RndNumTwo)));
ItemOnePosX := ItemOne.Position.X;
ItemOnePosY := ItemOne.Position.Y;
AnimateFloatWaitStatus(ItemOne, 'Position.Y', ItemTwo.Position.Y, 0.2,
TAnimationType.In, TInterpolationType.Linear);
AnimateFloatWaitStatus(ItemOne, 'Position.X', ItemTwo.Position.X, 0.2,
TAnimationType.In, TInterpolationType.Linear);
AnimateFloatWaitStatus(ItemTwo, 'Position.Y', ItemOnePosY, 0.2,
TAnimationType.In, TInterpolationType.Linear);
AnimateFloatWaitStatus(ItemTwo, 'Position.X', ItemOnePosX, 0.2,
TAnimationType.In, TInterpolationType.Linear);
ItemTagTemp := ItemTwo.Tag;
ItemTwo.Tag := ItemOne.Tag;
ItemOne.Tag := ItemTagTemp;
end;
end;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
LabelTime.Text := TimeToStr(IncSecond(StrToTime(LabelTime.Text), 1));
end;
procedure TForm1.AnimateFloatWaitStatus(const AParent: TFmxObject;
const APropertyName: string; const NewValue: Single; Duration: Single;
AType: TAnimationType; AInterpolation: TInterpolationType);
var
A: TFloatAnimation;
begin
StopPropertyAnimation(APropertyName);
A := TFloatAnimation.Create(Self);
try
A.Parent := AParent;
A.AnimationType := AType;
A.Interpolation := AInterpolation;
A.Duration := Duration;
A.PropertyName := APropertyName;
A.StartFromCurrent := True;
A.StopValue := NewValue;
A.Start;
while A.Running do
begin
FAnimateNotRun := False;
Application.ProcessMessages;
Sleep(0);
end;
finally
FAnimateNotRun := True;
A.DisposeOf;
end;
end;
function TForm1.CheckVictory: Boolean;
var
i: integer;
compares: Boolean;
item_one, item_two: TRectangle;
const
NamePrefix = 'Cell';
NumberOfCells = 15; // -1 ячейку
begin
compares := True;
for i := 1 to NumberOfCells do
begin
item_one := TRectangle(FindComponent(NamePrefix + IntToStr(i)));
item_two := TRectangle(FindComponent(NamePrefix + IntToStr(i + 1)));
if (item_one.Tag > item_two.Tag) then
begin
compares := False;
break;
end;
end;
Result := compares;
end;
procedure TForm1.ClearGame;
begin
Timer1.Enabled := False;
LabelSteps.Text := '0';
LabelTime.Text := '0:00:00';
end;
procedure TForm1.FormCreate(Sender: TObject);
var
SizeCell, i: integer;
x, y: integer;
ItemCell: TRectangle;
ItemText: TLabel;
const
NamePrefixText = 'Counter';
NamePrefixCell = 'Cell';
NumberOfCells = 16;
begin
Timer1.Enabled := False;
TabControl1.ActiveTab := TabItem1;
Field1.Width := Trunc(Form1.ClientWidth - 18);
Field1.Height := Field1.Width;
SizeCell := Trunc(Field1.Width) div 4;
Randomize;
x := 1;
y := 1;
for i := 1 to NumberOfCells do
begin
ItemText := TLabel(FindComponent(NamePrefixText + IntToStr(i)));
ItemText.Text := i.ToString;
ItemCell := TRectangle(FindComponent(NamePrefixCell + IntToStr(i)));
ItemCell.TagString := i.ToString;
ItemCell.Parent := TRectangle(FindComponent('Field1'));
ItemCell.Width := SizeCell;
ItemCell.Height := SizeCell;
ItemCell.Position.X := (SizeCell * x) - SizeCell + 1;
if x = 4 then
begin
x := 1;
end
else
begin
Inc(x);
end;
ItemCell.Position.Y := (SizeCell * y) - SizeCell + 1;
if (Frac(i/4) = 0) AND (Trunc(i) mod 4 = 0) then
begin
y := (i div 4) + 1;
end;
if i = NumberOfCells then
begin
ItemText.Text := '';
ItemCell.TagString := '';
end;
end;
// Анимация не запущена
FAnimateNotRun := True;
end;
procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char;
Shift: TShiftState);
begin
if (Key = vkHardwareBack) AND (TabControl1.ActiveTab <> TabItem1) then
begin
TabControl1.ActiveTab := TabItem1;
ClearGame;
Key := 0;
end;
end;
procedure TForm1.RectangleAllMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Single);
var
Item: TRectangle;
begin
if FAnimateNotRun AND (Sender is TRectangle) then
begin
Item := TRectangle(Sender);
FXPosOld := Item.Position.X;
FYPosOld := Item.Position.Y;
FPressKey := True;
FSendItem := Item;
if not Timer1.Enabled then
begin
Timer1.Enabled := True;
end;
end;
end;
end.
На устройстве:
Скрины:
Видео:
Исходный код: Скачать с Google Drive
Файл для установки: Скачать с Google Drive


