unit U_Puzzle15A;
{Copyright 2000, Gary Darby, Intellitech Systems Inc., www.DelphiForFun.org

 This program may be used or modified for any non-commercial purpose
 so long as this original notice remains in place.
 All other rights are reserved
 }

{The 15 puzzle - scramble the sliders and put them back in sequence}
{Version 1.0 - no automatic solution} 
interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls;

type
TBoardrec=record
SliderIndex:integer;  {index of slider in this slot}
col,row:Integer;      {column and row for this slot}
 end;

  TSliderBoard=class(TPanel)
public
Sidex,Sidey:integer;
    Board: array of  TBoardrec; {Slots for the sliders}
Sliders:array of TPanel; {the sliders}
Empty :integer; {index of the empty slot in the board array}
Slotw:integer; {width & height of slider}
UpdateMode:boolean;  {true=don't show animated moves}

constructor create(panel:TPanel; newSideX,newSideY:integer);
destructor destroy;
procedure move(index:integer); {move index slider to empty slot}
procedure slideit(sender:Tobject); {slider onclick even handler}
function canmove(index:integer):boolean; {check if move posible}
procedure beginupdate;
procedure endupdate;
end;

  TForm1 = class(TForm)
    Panel1: TPanel;
    SolveBtn: TButton;
    ScrambleBtn: TButton;
    SizeGrp: TRadioGroup;
procedure FormCreate(Sender: TObject);
procedure ScrambleBtnClick(Sender: TObject);
procedure SizeGrpClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
SliderBoard:TSliderBoard;
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

const
steps=20; {nbr of steps while moving a slider}

{********************* TSliderBoard.Create *************************}
constructor TSliderBoard.create(panel:TPanel;newsidex,newsidey:integer);
{Create s new sliderboard}
var
i:integer;
begin
sidex:=newsidex;
  sidey:=newsidey;
  setlength(sliders,sidex*sidey);
  setlength(board,sidex*sidey);
inherited create(panel.owner);
  height:=panel.height;
  width:=panel.Width;
if height>width then height:=width
else width:=height;
  left:=panel.Left;
  top:=panel.Top;
  parent:=panel.parent;
  color:=panel.color;
  slotw:=width div sidex;
for i:=1 to sidex*sidey-1 do {note that 0th entry is unused} 
begin
sliders[i]:=TPanel.create(self);
with sliders[i] do
begin
parent:=self;
      board[i-1].sliderindex:=i;
      board[i-1].col:=((i-1) mod sidex);
      board[i-1].row:=((i-1) div sidex);
      left:= board[i-1].col*slotw;
      top:=  board[i-1].row*slotw;
      width:=slotw;
      height:=slotw;
      caption:=inttostr(i);
      tag:=i;
      onclick:=slideit;
end;
end;
  empty:=sidex*sidey-1;
  board[empty].Sliderindex:=0;
with board[empty] do
begin
col:=empty mod sidex;
    row:=empty div sidex;
end;
  invalidate;
  updatemode:=false;
end;

{***************** TsliderBoard.Destroy ***************}
destructor TSliderBoard.destroy;
var
i:integer;
begin
for i:= 1 to high(sliders) do sliders[i].free;
    setlength(board,0);
    setlength(sliders,0);
inherited;
end;

{********************* TSliderBoard.SlideIt **************}
procedure TSliderBoard.slideit(sender:TObject);
{Onclick exit for slider - move to empty if adjacent}
var
i,j:integer;
begin
i:=TPanel(sender).tag;
for j:=0 to high(board) do
if board[j].sliderindex=i then break;
if canmove(j) then move(j);
end;

{*********************** TSliderVBoard.Move *****************}
procedure TSliderBoard.move(index:integer);
{move slider at board[index] to empty slot}
var
stepx,stepy:integer;
  destx,desty,i:integer;
begin
destx:=board[empty].col*slotw;
  desty:=board[empty].row*slotw;
with sliders[board[index].Sliderindex] do
begin
stepx:=(destx-left) div steps;
    stepy:=(desty-top) div steps;
if not updatemode then
for i:= 1 to steps do
begin
left:=left+stepx;
      top:=top+stepy;
      invalidate;
      sleep(10);
      application.processmessages;
end;
    left:=destx;
    top:=desty;
    invalidate;
end;
  board[empty].sliderindex:=board[index].sliderindex;
  board[index].sliderindex:=0;
  empty:=index;
end;

{******************** TSliderBoard.CanMove *****************}
function TSliderBoard.canmove(index:Integer):boolean;
{check to make sure the empty slot is adjacent to index slot}
var
x,y:integer;
begin
x:=board[index].col;
  y:=board[index].row;
  result:=false;
if x=board[empty].col then
if (y+1=board[empty].row)
or (y-1=board[empty].row)
then result:=true
else
else
if y=board[empty].row then
if (x+1=board[empty].col)
or (x-1=board[empty].col)
then result:=true;
end;

{********************* TSliderBoard.BeginUpdate ************}
procedure TSliderBoard.beginupdate;
{set update mode}
begin
updatemode:=true;
end;

{******************** TSliderBoard.EndUpdate ***************}
procedure TSliderBoard.endupdate;
{reset update mode}
var
i:integer;
begin
updatemode:=false;
for i:=1 to high(sliders) do sliders[i].invalidate;
end;

{****************** Form Methods ******************}

procedure TForm1.FormCreate(Sender: TObject);
var
n:integer;
begin
randomize;
  n:=sizegrp.itemindex+3;
  SliderBoard:=TSliderBoard.create(Panel1,n,n);
end;

procedure TForm1.ScrambleBtnClick(Sender: TObject);
{scramble the board by moving the empty space around}
var
n,prev,i:integer;
begin
sizegrp.enabled:=false;
  sliderboard.beginupdate;
  i:=0;
  prev:=-1; {keep prev move to eliminate reversals}
with sliderboard do
repeat
n:=random(4);
case n of
0: {left}
if (prev<>2) and (board[empty].col>0) then
begin
move(empty-1);
            inc(i);
            prev:=n;
end;
      1: {up}
if (prev<>3) and (board[empty].row>0) then
begin
move(empty-sidex);
           inc(i);
           prev:=n;
end;
      2: {right}
if (prev<>0) and (board[empty].col<sidex-1) then
begin
move(empty+1);
           inc(i);
           prev:=n;
end;
      3: {down}
if (prev<>1) and (board[empty].row<sidey-1) then
begin
move(empty+sidex);
           inc(i);
           prev:=n;
end;
end;
until i>=100;
  sliderboard.endupdate;
  sizegrp.enabled:=true;
end;

procedure TForm1.SizeGrpClick(Sender: TObject);
{select a new board size}
var
n:integer;
begin
sliderboard.free;
  n:=sizegrp.itemindex+3;
  SliderBoard:=TSliderBoard.create(panel1,n,n);
end;

end.