{************************************************}
{                                                }
{   Turbo Vision Demo                            }
{   Copyright (c) 1990 by Borland International  }
{                                                }
{************************************************}

unit Puzzle;

{$F+,O+,S-,D-}

{ Simple puzzle object. See TVDEMO.PAS for an example
  program that uses this unit.
}


interface

uses views, Drivers, Objects, Crt;

const
  CPuzzleView = #6#7;

type

 TBoard = array[0..5,0..5] of Char;
 PPuzzleView = ^TPuzzleView;
 TPuzzleView = object(TView)
   Board: TBoard;
   Moves: Word;
   Solved: Boolean;
   constructor Init(Bounds: TRect);
   constructor Load(var S: TStream);
   procedure HandleEvent(var Event: TEvent); Virtual;
   procedure Draw; Virtual;
   function  GetPalette: PPalette; virtual;
   procedure MoveKey(Key: Word);
   procedure MoveTile(Point: TPoint);
   procedure Scramble;
   procedure Store(var S: TStream);
   procedure WinCheck;
 end;

 PPuzzleWindow = ^TPuzzleWindow;
 TPuzzleWindow = object(TWindow)
   constructor Init;
 end;

const
  RPuzzleView: TStreamRec = (
     ObjType: 10010;
     VmtLink: Ofs(TypeOf(TPuzzleView)^);
     Load:    @TPuzzleView.Load;
     Store:   @TPuzzleView.Store
  );

const
  RPuzzleWindow: TStreamRec = (
     ObjType: 10011;
     VmtLink: Ofs(TypeOf(TPuzzleWindow)^);
     Load:    @TPuzzleWindow.Load;
     Store:   @TPuzzleWindow.Store
  );

procedure RegisterPuzzle;

implementation

{ TPuzzleWindow }

constructor TPuzzleWindow.Init;
var
  R: TRect;
begin
  R.Assign(1, 1, 21, 7);
  inherited Init(R, 'Puzzle', 0);
  Flags := Flags and not (wfZoom + wfGrow);
  GrowMode := 0;
  GetExtent(R);
  R.Grow(-1, -1);
  Insert(New(PPuzzleView, Init(R)));
end;

{ TPuzzleView }

constructor TPuzzleView.Init(Bounds: TRect);
type
  TBoardValue = array[1..16] of Char;
const
  SBoardValue: TBoardValue =
    ('A','B','C','D',
     'E','F','G','H',
     'I','J','K','L',
     'M','N','O',' ');
var
  I, J: Integer;
begin
  inherited Init(Bounds);
  Randomize;
  Options := Options or ofSelectable;
  FillChar(Board, SizeOf(Board), '?');
  for I := 0 to 3 do
    for J := 0 to 3 do
      Board[I+1, J+1] := SBoardValue[I*4 + J+1];
  Scramble;
end;

constructor TPuzzleView.Load(var S: TStream);
begin
  inherited Load(S);
  S.Read(Board, SizeOf(Board) + Sizeof(Moves) + SizeOf(Solved));
end;

Procedure TPuzzleView.Draw;
var
  I, J, K: Integer;
  B: array[0..17] of word;
  S1: String[3];
  Color: array[0..1] of byte;
  ColorBack: Byte;
const
  Map: array['A'..'O'] of Byte =
    (0, 1, 0, 1,
     1, 0, 1, 0,
     0, 1, 0, 1,
     1, 0, 1);
begin
  Color[0] := GetColor(1);
  Color[1] := GetColor(2);
  ColorBack := GetColor(1);
  if Solved then Color[1] := Color[0]
  else Color[1] := GetColor(2);
  for I := 1 to 4 do
  begin
    MoveChar(B, ' ', ColorBack, 18);
    if I = 2 then MoveStr(B[13], 'Move', ColorBack);
    if I = 3 Then
    begin
      Str(Moves: 3, S1);
      MoveStr(B[14], S1, ColorBack);
    end;
    for J := 1 to 4 do
    begin
      S1 := ' ' + Board[I, J] + ' ';
      K := (Byte(Board[I, J]) mod 2) +1;
      if Board[I, J] = ' ' then MoveStr(B[(J - 1) * 3], S1, Color[0])
      else
        MoveStr(B[(J - 1) * 3], S1, Color[Map[Board[I, J]]]);
    end;
    WriteLine(0, I - 1, 18, 1, B);
  end;
end;

function TPuzzleView.GetPalette: PPalette;
const
  P: String[Length(CPuzzleView)] = CPuzzleView;
begin
  GetPalette := @P;
end;

procedure TPuzzleView.HandleEvent(var Event: TEvent);
begin
  inherited HandleEvent(Event);
  if Solved and (Event.What and (evKeyDown + evMouseDown) <> 0) then
  begin
    Scramble;
    ClearEvent(Event);
  end;
  case Event.What of
    evMouseDown: MoveTile(Event.Where);
    evKeyDown: MoveKey(Event.KeyCode);
  else
    Exit;
  end;
  ClearEvent(Event);
  WinCheck;
end;

procedure TPuzzleView.MoveKey(Key: Word);
var
  X, Y, I, J: Integer;
begin
  for I:=1 To 4 do
    for J:=1 To 4 do
      if Board[i,j] = ' ' then
      begin
        Y:=I;
        X:=J;
      end;

  case Key of
    kbDown:
      if Y > 1 then
      begin
        Board[Y, X] := Board[Y-1, X];
        Board[Y-1, X] := ' ';
        Inc(moves, Byte(moves<1000));
      end;
    kbUp:
      if Y < 4 then
      begin
        Board[Y, X] := Board[Y+1, X];
        Board[Y+1, X] := ' ';
        Inc(moves, Byte(moves<1000));
      end;
    kbRight:
      if X > 1 then
      begin
        Board[Y, X] := Board[Y, X-1];
        Board[Y, X-1] := ' ';
        Inc(moves, Byte(moves<1000));
      end;
    kbLeft:
      if X < 4 then
      begin
        Board[Y, X] := Board[Y, X+1];
        Board[Y, X+1] := ' ';
        Inc(moves,Byte(moves<1000));
      end;
  end;
  DrawView;
end;

procedure TPuzzleView.MoveTile(Point: TPoint);
var
  P: TPoint;
  X, Y: Word;
begin
  MakeLocal(Point, P);
  X := ((P.X + 3) div 3);
  Y := P.Y + 1;
  if (X > 0) and (X < 5) and (Y > 0) and (Y < 5) Then
  begin
    if Board[Y, X-1] = ' ' then
    begin
      Board[Y, X-1] := Board[Y, X];
      Board[Y, X] := ' ';
      Inc(moves, Byte(moves<1000));
    end;
    if Board[Y-1, X] = ' ' then
    begin
      Board[Y-1, X] := Board[Y, X];
      Board[Y, X] := ' ';
      Inc(moves, Byte(moves<1000));
    end;
    if Board[Y, X+1] = ' ' then
    begin
      Board[Y, X+1] := Board[Y, X];
      Board[Y, X] := ' ';
      Inc(moves, Byte(moves<1000));
    end;
    if Board[Y+1, X] = ' ' then
    begin
      Board[Y+1, X] := Board[Y, X];
      Board[Y, X] := ' ';
      Inc(moves, Byte(moves<1000));
    end;
    DrawView;
  end;
end;

procedure TPuzzleView.Scramble;
begin
  Moves := 0;
  Solved := False;
  repeat
    case Random(4) of
      0: MoveKey(kbUp);
      1: MoveKey(kbDown);
      2: MoveKey(kbRight);
      3: MoveKey(kbLeft);
    end;
  until Moves=500;
  Moves := 0;
  DrawView;
end;

procedure TPuzzleView.Store(var S: TStream);
begin
  inherited Store(S);
  S.Write(Board, SizeOf(Board) + Sizeof(Moves) + SizeOf(Solved));
end;

procedure TPuzzleView.WinCheck;
type
  BoardStr = array [0..35] of Char;
const
  FBoard: BoardStr = '???????ABCD??EFGH??IJKL??MNO ???????';
var
  I: Integer;
begin
  Solved := BoardStr(Board) = FBoard;
  DrawView;
end;

procedure RegisterPuzzle;
begin
  RegisterType(RPuzzleView);
  RegisterType(RPuzzleWindow);
end;

end.
