unit _20_Game15_Form1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, Buttons, Menus;

type
  TForm1 = class(TForm)
    Panel_GameDesk: TPanel;
    Memo_Solution: TMemo;
    MemoCaption: TLabel;
    Button_Solve: TSpeedButton;
    Button_Animate: TSpeedButton;
    Label1: TLabel;
    PaintBox_Desk: TPaintBox;
    Button_GameReset: TSpeedButton;
    Panel_Info: TPanel;
    Panel_MovesNum: TPanel;
    SpeedButton1: TSpeedButton;
    OpenDialog1: TOpenDialog;
    Label_Answer: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure Button_ExitClick(Sender: TObject);
    procedure PaintBox_DeskPaint(Sender: TObject);
    procedure PaintBox_DeskClick(Sender: TObject);
    procedure PaintBox_DeskMouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
    procedure Button_GameResetClick(Sender: TObject);
    procedure Button_SolveClick(Sender: TObject);
    procedure Button_AnimateClick(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  {store array of 4x4 numbers}
  type TValueArray=array[1..4,1..4]of integer;

  const EmptyGame:TValueArray=((0,0,0,0),(0,0,0,0),(0,0,0,0),(0,0,0,0));

  {store and handle TValueArray}
  type TValues=object
    Values:TValueArray;
    procedure SetV(m,n,value:integer);
    procedure Swap(m1,n1,m2,n2:integer);
  end;

  {game desk}
  type TGameDesk=object
    Values:TValues;
    MousePosX:integer;
    MousePosY:integer;
    CellPosX:integer;
    CellPosY:integer;
    procedure Show;
    procedure Move(m,n:integer);
  end;

var
  Form1: TForm1;
  StartValues: TValueArray;
  GameDesk: TGameDesk;
  AutoSolve: boolean;
  AutoShow: boolean;
  MovesNum:integer;
implementation

{$R *.DFM}

{public}

{this procedure is used to load values from the input file}
{to array in program}
procedure LoadInput(FileName:string);
var InpFile:text;
    n:integer;
    LineStr:string;
    HoleError:boolean;
    IsHole:boolean;

  procedure GetNumbers(Line:string;var NumbArr:TValueArray;WhichLine:integer);
  var ActChar:char; {actually readed character}
      ActPos:integer;{actual position in line}
      NumbStr:string;{number from line}
      NumbPos:integer;{wchich number is now readed}
      LineLen:integer;{length of line with the numbers}
  begin
    ActPos:=1;
    LineLen:=Length(Line);

    for NumbPos:=1 to 4 do
    begin
      repeat ActChar:=Line[ActPos];Inc(ActPos) until ActChar<>' ';
      NumbStr:='';
      repeat
        NumbStr:=NumbStr+ActChar;
        ActChar:=Line[ActPos];
        Inc(ActPos);
      until (ActChar=' ')or(ActPos>(LineLen+1));
      if(UpCase(NumbStr[1])='X') then
      begin
        NumbStr:='0';
        if(IsHole) then HoleError:=true
        else IsHole:=true;
      end;
      NumbArr[NumbPos,WhichLine]:=StrToInt(NumbStr);
    end;
  end;

begin
  AssignFile(InpFile,FileName);
  {$i-}
  Reset(InpFile);
  {$i+}
  if IOResult<>0 then
  begin
    MessageDlg('Cannot open specified file "'+FileName+'" !',mtError,[mbOk],0);
  end;

  IsHole:=false;
  HoleError:=false;
  for n:=1 to 4 do
  begin
    ReadLn(InpFile,LineStr);
    GetNumbers(LineStr,StartValues,n);
  end;
  if HoleError then
  begin
    GameDesk.Values.Values:=EmptyGame;
    StartValues:=EmptyGame;
    MessageDlg('There were TWO HOLES!',mtError,[mbOk],0);
    Form1.Label_Answer.Caption:='NO';
  end else
  begin
    GameDesk.Values.Values:=StartValues;
    Form1.Label_Answer.Caption:='I don`t know';
  end;
  GameDesk.Show;
end;

{this procedure checks the parameters}
{and call procedure which loads the input file}
procedure AcceptParameters;
var FirstChar:char; {used for recognizing the switch command}
    FileArgNum:integer; {specifies, which argument does contain the input file name}

  {this procedure is used to recognize, if the switch is correct}
  {and to handle it}
  procedure CheckSwitch;
  var SwitchStr:string;
  begin
    FileArgNum:=2;
    SwitchStr:=Copy(ParamStr(1),2,Length(ParamStr(1))-1);
    if(SwitchStr='solve') then AutoSolve:=true
    else if(SwitchStr='show') then
    begin
      AutoSolve:=true;
      AutoShow:=true;
    end;
    if not(AutoSolve) then MessageDlg('Wrong switch used!',mtWarning,[mbOk],0);
  end;

begin
  if (ParamCount=0) then
    begin
      {show error message and halt program}
      MessageDlg('You haven`t specified any input file!',
        mtWarning,[mbOk],0);
      Form1.Label_Answer.Caption:='NO';  
    end
  else
    begin
      {check switches and load values from file}
      FileArgNum:=1;
      AutoSolve:=false;
      AutoShow:=false;

      FirstChar:=ParamStr(1)[1];
      if FirstChar='-' then CheckSwitch;
      if(FileArgNum>ParamCount) then
      begin
        MessageDlg('You have specified only switch'+char(13)+'but no input file',
          mtWarning,[mbOk],0);
      end else LoadInput(ParamStr(FileArgNum));
    end;
end;

{TValues}
procedure TValues.SetV(m,n,value:integer);
begin
  Values[m,n]:=value;
end;

procedure TValues.Swap(m1,n1,m2,n2:integer);
var pom:integer;
begin
  pom:=Values[m1,n1];
  Values[m1,n1]:=Values[m2,n2];
  Values[m2,n2]:=pom;
end;


{TGameDesk}
{draw current cards positions to the screen}
procedure TGameDesk.Show;
var m,n:integer;
    OutStr:string;
begin
  for m:=1 to 4 do
  for n:=1 to 4 do
  begin
    if Values.Values[m,n]=0 then
    begin
      OutStr:='';
      Form1.PaintBox_Desk.Canvas.Brush.Color:=clGray;
      Form1.PaintBox_Desk.Canvas.Pen.Color:=clGray;
    end else
    begin
      OutStr:=IntToStr(Values.Values[m,n]);
      Form1.PaintBox_Desk.Canvas.Brush.Color:=clWhite;
      Form1.PaintBox_Desk.Canvas.Pen.Color:=clBlack;
    end;

    Form1.PaintBox_Desk.Canvas.Rectangle((m-1)*40,(n-1)*40,m*40,n*40);
    Form1.PaintBox_Desk.Canvas.TextOut((m-1)*40+4,(n-1)*40+4,OutStr);
  end;
end;

{move specified cell (if it is possible)}
procedure TGameDesk.Move(m,n:integer);
var EmptyNear:boolean; {is the empty field near = can this field be moved}
    EmptyPosX:integer;
    EmptyPosY:integer;
    MoveStr:string;
label hotovo;
begin
  EmptyNear:=false;

{testing if there is any empty field near this field}
{and getting it's position}
  if Values.Values[m,n]=0 then
  begin
    EmptyNear:=false;
    goto hotovo;
  end;

  EmptyNear:=((n>1)and(Values.Values[m,n-1]=0));
  if EmptyNear then
  begin
    EmptyPosX:=m;
    EmptyPosY:=n-1;
    MoveStr:='up';
    goto hotovo;
  end else EmptyNear:=((m<4)and(Values.Values[m+1,n]=0));
  if EmptyNear then
  begin
    EmptyPosX:=m+1;
    EmptyPosY:=n;
    MoveStr:='right';
    goto hotovo;
  end else EmptyNear:=((n<4)and(Values.Values[m,n+1]=0));
  if EmptyNear then
  begin
    EmptyPosX:=m;
    EmptyPosY:=n+1;
    MoveStr:='down';
    goto hotovo;
  end else EmptyNear:=((m>1)and(Values.Values[m-1,n]=0));
  if EmptyNear then
  begin
    EmptyPosX:=m-1;
    EmptyPosY:=n;
    MoveStr:='left';
  end;

hotovo:

  if EmptyNear then
  begin
    Values.Swap(EmptyPosX,EmptyPosY,m,n);
    Form1.Memo_Solution.Lines.Add(MoveStr);
    Form1.Panel_Info.Caption:='moved '+MoveStr;
    Inc(MovesNum);
    Form1.Panel_MovesNum.Caption:=IntToStr(MovesNum);
  end
  else
  begin
    MessageBeep(1);
    Form1.Panel_Info.Caption:='This field can`t be moved!';
  end;
end;

{TForm1}
procedure TForm1.FormCreate(Sender: TObject);
begin
  AcceptParameters;
end;

procedure TForm1.Exit1Click(Sender: TObject);
begin
  Halt;
end;

procedure TForm1.Button_ExitClick(Sender: TObject);
begin
  Halt;
end;

procedure TForm1.PaintBox_DeskPaint(Sender: TObject);
begin
  GameDesk.Show;
end;

procedure TForm1.PaintBox_DeskClick(Sender: TObject);
var m,n:integer;
begin
  GameDesk.Move(GameDesk.CellPosX,GameDesk.CellPosY);
  GameDesk.Show;
end;

procedure TForm1.PaintBox_DeskMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  GameDesk.MousePosX:=x;
  GameDesk.MousePosY:=y;
  GameDesk.CellPosX:=(x div 40)+1;
  GameDesk.CellPosY:=(y div 40)+1;
end;

procedure TForm1.Button_GameResetClick(Sender: TObject);
begin
  GameDesk.Values.Values:=StartValues;
  GameDesk.Show;
  Memo_Solution.Clear;
  Panel_Info.Caption:='board cleared';
  Panel_MovesNum.Caption:='';
end;

procedure TForm1.Button_SolveClick(Sender: TObject);
begin
  MessageDlg('Sorry, I can`t do this',mtInformation,[mbOk],0);
end;

procedure TForm1.Button_AnimateClick(Sender: TObject);
begin
  MessageDlg('Sorry, I can`t do this',mtInformation,[mbOk],0);
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    LoadInput(OpenDialog1.FileName);
    GameDesk.Values.Values:=StartValues;
    GameDesk.Show;
    Memo_Solution.Clear;
    Panel_Info.Caption:='board cleared';
    Panel_MovesNum.Caption:='';
  end;
end;

end.

