unit UScrabbleGrid;
{ ********************************************************************************* }
{ (c) GNU                                                                           }
{ author: Heiko Tietze                                                              }
{ version: 1.0                                                                      }
{ date: 11.12.2004                                                                  }
{ description: 2D visualization unit; the grid is a descendant of TDrawGrid         }
{              all panels are based on TMoveablePanel (UMoveablePanel.pas); the     }
{              stones have an StoneDragobject in VCL mode; the chat will be shown   }
{              in network mode                                                      }
{ ********************************************************************************* }

{$I Def.inc}

interface

uses  Classes, Types, SysUtils,
      {$IFDEF VCL}
      Windows, Forms, Messages, Controls, Graphics, ExtCtrls, StdCtrls, Grids,
      {$ELSE}
      QForms, QControls, QGraphics, QExtCtrls, QStdCtrls, QGrids, Qt,
      {$ENDIF}
      UMoveablePanel, UScrabble, UDictionary, UGameOptions, UTools, ULanguage;

type TStoneCheck=(ckVisible,ckNotLocked);
     TStoneObject=class(TCustomControl)
         protected
            procedure PaintTo(Canvas : TCanvas); virtual;abstract;//PaintTo() is used for dragimage
         end;
     //www.blong.com\Conferences\BorCon2001\DragAndDrop\4114.htm
     TStoneDragObject = class(TDragControlObject)
     {$IFDEF VCL}
         private
            FDragImages: TDragImageList;
            FData : Char;
            FTag : integer;
         protected
            function GetDragImages: TDragImageList; override;
         public
            constructor Create(Sender:TObject; Caption:Char; Tag:Integer); reintroduce;
            destructor Destroy; override;
            property Data : Char read FData;
            property Tag : integer read FTag;
     {$ELSE}
         private
            FImageIndex : Integer;
            FHotSpot    : TPoint;
         protected
            function GetDragImageIndex: Integer; override;
            function GetDragImageHotSpot: TPoint; override;
         public
            constructor Create(Sender:TObject; Caption:Char; Tag:Integer); reintroduce;
            destructor Destroy; override;
     {$ENDIF}
         end; //TStoneDragObject
     TStone=class(TStoneObject)
         private
            FValue : byte;
            FCaption : Char;
            FStoneDragObject : TStoneDragObject;
            FLocked : boolean;
         protected
            procedure Paint; override;
            procedure StoneDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
            procedure StoneDragStart(Sender: TObject; var DragObject: TDragObject);
            procedure PaintTo(Canvas : TCanvas); reintroduce;
         public
            property Value : Byte read FValue write FValue;
            property Locked : Boolean read FLocked write FLocked;
            property Caption : Char read FCaption write FCaption;
            property DragImage : TStoneDragObject read FStoneDragObject write FStoneDragObject;
         end; //TStone
     TStonePanel=class(TMoveablePanel)
         private
            FStones : array of TStone;
            FStoneCount: byte;
            FPieceSize : byte;
            procedure SetStoneCount(const Value: byte);
            function GetStone(index: integer): TStone;
            procedure SetStone(index: integer; const Value: TStone);
            procedure SetPieceSize(const Value: byte);
            function GetCheckAllStones(index:TStoneCheck):boolean;
         public
            constructor Create(aOwner:TComponent);override;
            destructor Destroy; override;
            procedure Resize(Sender : TObject);
            procedure Update;reintroduce;
            property StoneCount : byte read FStoneCount write SetStoneCount;
            property Stone[index:integer] : TStone read GetStone write SetStone;
            property PieceSize : byte read FPieceSize write SetPieceSize;
            property CheckAllStones[index:TStoneCheck]:boolean read GetCheckAllStones;
         end; //TStonePanel
     TStatisticPanel=class(TMoveablePanel)
           constructor Create(aOwner:TComponent); override;
           destructor Destroy; override;
        private
           FPlayerCount : Byte;
           FValues      : array of Word;
           FMaxValue    : Word;
           FColors      : array[0..3] of TColor;
           function GetValue(index: Byte): Word;
           procedure SetValue(index: Byte; const Value: Word);
           procedure SetPlayerCount(const Value: Byte);
        public
           procedure Paint; override;
           property Value[index:Byte]:Word read GetValue write SetValue;
           property PlayerCount:Byte read FPlayerCount write SetPlayerCount;
        end;
     TBounds=(bdTop,bdLeft,bdHeight,bdWidth);
     TChat=class(TMoveablePanel)
           private
             FInput   : TEdit;
             FOutput  : TListBox;
             FOnSendMessage : TNetworkMessage; //defined in UTools
             procedure EditKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
           {$IFDEF VCL}
             procedure ListBoxDrawItem(Sender: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
             procedure ListBoxMeasureItem(Control: TWinControl; Index: Integer; var H: Integer);
             procedure ListBoxResize(Sender : TObject);
           {$ELSE}
             procedure ListBoxDrawItem(Sender: TObject; Index: Integer; Rect: TRect; State: TOwnerDrawState; var Handled: Boolean);
             procedure ListBoxMeasureItem(Control: TWidgetControl; Index: Integer; var H: Integer);
           {$ENDIF}
           protected
           public
             constructor Create(aOwner:TComponent); override;
             destructor Destroy; override;
             procedure Clear;
             procedure AddLine(Player:Byte; Value:string);
             property OnSendMessage:TNetworkMessage read FOnSendMessage write FOnSendMessage;
           end;//TChat;
     TScrabbleGrid=class(TDrawGrid)
        private
           FScrabble       : TScrabble;
           FStonePanel     : TStonePanel;
           FStatisticPanel : TStatisticPanel;
           FChat           : TChat;
           FNetworkMessage : TNetworkMessage;
           procedure MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);reintroduce;
           procedure MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);reintroduce;
           procedure StoneMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
           procedure ButtonDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
           procedure ButtonDragDrop(Sender, Source: TObject; X, Y: Integer);
           procedure StoneEndDrag(Sender, Target: TObject; X, Y: Integer);
           procedure DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);reintroduce;
           function GetLettersLocked: boolean;
        public
           constructor Create(aOwner: TWinControl;Scrabble : TScrabble);reintroduce;
           procedure UpdateAll;
           procedure SetSize(L,T,W,H:integer);
           property OnSendMessage : TNetworkMessage read FNetworkMessage write FNetworkMessage;
           property Chat : TChat read FChat write FChat;
           property LettersLocked : boolean read GetLettersLocked;
        end;

implementation

const bvWidth=1;
      LeftShift=3; //letter is drawn not at the absolute center to ensure the value to be readable

{ ----- TStone ----- }

procedure TStone.StoneDragStart(Sender: TObject; var DragObject: TDragObject);
begin
   DragObject:=TStoneDragObject.Create(Sender, (Sender as TStone).FCaption,(Sender as TStone).Tag);
   {$IFNDEF VCL}
   DragObject.ShowDragImage;
   {$ENDIF}
   DragImage:=DragObject as TStoneDragObject;
end;

procedure TStone.StoneDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
   Accept:=(Source is {$IFDEF VCL}TStoneDragObject{$ELSE}TStone{$ENDIF});
end;

procedure TStone.Paint;
begin
   PaintTo(Canvas);
end;

procedure TStone.PaintTo(Canvas: TCanvas);
var Rect : TRect;
    s    : string;
begin
   Rect := GetClientRect;
   Frame3D(Canvas, Rect, clBtnHighlight, clBtnShadow, bvWidth);
   with Canvas do
   begin
      if not Locked then Brush.Color:=GameOptions.c7.Color
                    else Brush.Color:=clGray;
      FillRect(Rect);
      s:=Caption;
      Font.Name:=GameOptions.lbFontName.Caption;
      Font.Size:=round((Rect.Bottom-Rect.Top)*(strtoint(GameOptions.edLetterSize.Text)/100));
      TextOut(Rect.Left+((Rect.Right-Rect.Left) div 2-TextWidth(s) div 2)-LeftShift,
              Rect.Top+((Rect.Bottom-Rect.Top) div 2-TextHeight(s) div 2), s);
      Font.Size:=round((Rect.Bottom-Rect.Top)*(strtoint(GameOptions.edValueSize.Text)/100));
      s:=inttostr(Value);
      TextOut(Rect.Right-TextWidth(s)-1, Rect.Bottom-TextHeight(s)-1, s);
   end;
end;

{------- TControlDragObject -------}
{$IFDEF VCL}
constructor TStoneDragObject.Create(Sender:TObject; Caption:Char; Tag:Integer);
begin
  inherited Create(sender as TControl);
  FData := Caption;
  FTag := Tag;
end;

function TStoneDragObject.GetDragImages: TDragImageList;
var bmp : TBitmap;
    i   : integer;
begin
  if not Assigned(FDragImages) then FDragImages := TDragImageList.Create(nil);
  Result:=FDragImages;
  Result.Clear;
  bmp:=TBitmap.Create;
  try
    bmp.Width:=Control.Width;
    bmp.Height:=Control.Height;
    bmp.Canvas.Lock;
    try
      (Control as TWinControl).PaintTo(bmp.Canvas.Handle, 0, 0);
    finally
      bmp.Canvas.UnLock
    end;
    FDragImages.Width := Control.Width;
    FDragImages.Height := Control.Height;
    i:=FDragImages.AddMasked(bmp, clBtnFace);
    FDragImages.SetDragImage(i, Control.Width div 2, Control.Height div 2);
  finally
    bmp.Free
  end
end;

{$ELSE}
//constructor TStoneDragObject.Create(Stone: TStone);
constructor TStoneDragObject.Create(Sender:TObject; Caption:Char; Tag:Integer);
var bmp:TBitmap;
begin
  inherited Create(Sender as TControl);
  FHotSpot.X:=(Sender as TControl).Width;
  FHotSpot.Y:=(Sender as TControl).Height;
  bmp:=TBitmap.Create;
  with bmp do
  try
    Width:=(Sender as TStone).Width;
    Height:=(Sender as TControl).Height;
    (Sender as TStone).PaintTo(Canvas);
    DragImageList.Width:=Width;
    DragImageList.Height:=Height;
    FImageIndex:=DragImageList.AddMasked(bmp,clBtnFace);
  finally
    Free;
  end;
end;

function TStoneDragObject.GetDragImageHotSpot: TPoint;
begin
  Result:=FHotSpot;
end;

function TStoneDragObject.GetDragImageIndex: Integer;
begin
  Result:=FImageIndex;
end;
{$ENDIF}

destructor TStoneDragObject.Destroy;
begin
  {$IFDEF VCL}
  FDragImages.Free;
  {$ELSE}
  DragImageList.Clear;
  {$ENDIF}
  inherited;
end;

{ ----- TStonePanel ----- }

constructor TStonePanel.Create(aOwner:TComponent);
begin
  inherited;
  Parent:=aOwner as TWinControl;
  OnResize:=Resize;
end;

destructor TStonePanel.Destroy;
var i:integer;
begin
   for i:=0 to length(FStones)-1 do FStones[i].Free;
   setlength(FStones,0);
   inherited;
end;

procedure TStonePanel.Resize(Sender : TObject);
var i,x,y:integer;
begin
   x:=-FPieceSize; y:=2;
   for i:=0 to length(FStones)-1 do
   begin
      FStones[i].Width:=FPieceSize;
      FStones[i].Height:=FPieceSize;
      if (x+FPieceSize+1>Width-FPieceSize) and (i>0) then
      begin
         x:=1;
         inc(y,FPieceSize+1);
         if State<>stSizing then Width:=FStones[i-1].Left+FPieceSize+2;
      end else inc(x,FPieceSize+1);
      if FStones[i].Parent is TStonePanel then //do not replace if piece is in gamegrid
      begin
        FStones[i].Left:=x;
        FStones[i].Top:=y;
      end;
      if (i=length(FStones)-1) and (State<>stSizing) then
         Height:=FStones[high(FStones)].Top+FPieceSize+20;//FCaption.Height;
   end;
end;

procedure TStonePanel.SetStoneCount(const Value: byte);
var i:integer;
begin
  if Value<>FStoneCount then
  begin
    for i:=0 to length(FStones)-1 do FStones[i].Free;
    setlength(FStones,Value);
    for i:=0 to length(FStones)-1 do
    begin
      FStones[i]:=TStone.Create(self);
      with FStones[i] do
      begin
        Parent:=self;
        Brush.Color:=clYellow;
        BevelWidth:=2;
        OnDragOver:=StoneDragOver;
        OnStartDrag:=StoneDragStart;
      end;
    end;
    FStoneCount:=Value;
    Resize(self);
  end;
end;

function TStonePanel.GetStone(index: integer): TStone;
begin
    if (index>=low(FStones)) and (index<=high(FStones)) then
       Result:=FStones[index] else Result:=nil;
end;

procedure TStonePanel.SetStone(index: integer; const Value: TStone);
begin
    if (index>=low(FStones)) and (index<=high(FStones)) then FStones[index]:=Value;
end;

procedure TStonePanel.SetPieceSize(const Value: byte);
begin
  FPieceSize := Value;
  Resize(self);
end;

function TStonePanel.GetCheckAllStones(index:TStoneCheck):boolean;
var i:integer;
begin
   Result:=true;
   for i:=0 to FStoneCount-1 do
    case index of
     ckVisible : Result:=Result and FStones[i].Visible;
     ckNotLocked  : Result:=Result and not FStones[i].Locked;
    end;
end;

procedure TStonePanel.Update;
var i:integer;
begin
   inherited;
   for i:=0 to ComponentCount-1 do (Components[i] as TWinControl).Repaint;
end;

{ ----- TStatisticPanel ----- }

constructor TStatisticPanel.Create(aOwner: TComponent);
begin
   inherited;
   Parent:=aOwner as TWinControl;
   Caption.Caption:=''; //the caption couldn't be translated at this point
   Caption.Align:=alTop;
   Height:=200;
end;

destructor TStatisticPanel.Destroy;
begin
   setlength(FValues,0);
   inherited;
end;

procedure TStatisticPanel.SetValue(index: Byte; const Value: Word);
begin
   FValues[index]:=Value;
   if FValues[index]>FMaxValue then FMaxValue:=FValues[index];
   Repaint;
end;

function TStatisticPanel.GetValue(index: Byte): Word;
begin
   Result:=FValues[index];
end;

procedure TStatisticPanel.Paint;
const Stepping=100; //columns are scaled by multiple of this value
var R:TRect;
    i:integer;
begin
   R:=ClientRect;
   Canvas.Brush.Color:=clBtnFace;
   Canvas.FillRect(R);
   Frame3D(Canvas, R, clWindow, clGray, 1);
   for i:=0 to FPlayerCount-1 do
   begin
      Canvas.Brush.Color:=GameOptions.PlayerColor[i];
      R.Left:=i*(Width div FPlayerCount)+1;
      R.Right:=(i+1)*(Width div FPlayerCount);
      R.Bottom:=ClientRect.Bottom;
      R.Top:=R.Bottom-round((FValues[i] div (FMaxValue div Stepping+1)/Stepping)*(R.Bottom-Caption.Height-5));
      Canvas.FillRect(R);
      Canvas.Font.Color:=LightOrDark(FColors[i]);
      Canvas.TextRect(R,R.Left+(R.Right-R.Left) div 2-Canvas.TextWidth(inttostr(FValues[i])) div 2,R.Bottom-15,inttostr(FValues[i]));
   end;
end;

procedure TStatisticPanel.SetPlayerCount(const Value: Byte);
var i:integer;
begin
   FPlayerCount:=Value;
   setlength(FValues,FPlayerCount);
   for i:=0 to FPlayerCount-1 do FValues[i]:=0;
   FMaxValue:=0;
end;

{ ----- TChat ----- }

constructor TChat.Create(aOwner: TComponent);
begin
  inherited;
  Parent:=aOwner as TWinControl;
  Caption.Caption:='Chat';
  Height:=100;
  FInput:=TEdit.Create(self);
  with FInput do
  begin
    Parent:=self;
    Align:=alTop;
    Color:=$00E1FFFA;
    OnKeyUp:=EditKeyUp;
  end;
  FOutput:=TListBox.Create(self);
  with FOutput do
  begin
    Parent:=self;
    Align:=alClient;
    Color:=$00E1FFFA;
    Style:=lbOwnerDrawVariable;
    OnMeasureItem:=ListBoxMeasureItem;
    OnDrawItem:=ListBoxDrawItem;
    {$IFDEF VCL} //only VCL doesn't remeasure correct after resize
    OnResize:=ListBoxResize;
    {$ELSE} // CLX creates scollbars automatically even if wordwrap is made
    QListBox_SetAutoBottomScrollBar(Handle,false);
    QListBox_SetBottomScrollBar(Handle, false);
    {$ENDIF}
  end;
end;

destructor TChat.Destroy;
begin
  FOutput.Free;
  FInput.Free;
  inherited;
end;

procedure TChat.EditKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if (Key=ke_Enter) and ((Sender as TEdit).Text<>'') then
  begin
    if assigned(OnSendMessage) then OnSendMessage(nmChat, 0, 255,(Sender as TEdit).Text);
    //message is sent and will be added by network
    (Sender as TEdit).Text:='';
  end;
end;

procedure TChat.AddLine(Player: Byte; Value: string);
begin
  FOutput.Items.Insert(0,IntToStr(Player)+Value);
end;

procedure TChat.Clear;
begin
  FOutput.Items.Clear;
end;

{$IFDEF VCL} //under VCL the items are not remeasured
procedure TChat.ListBoxResize(Sender: TObject);
var sl:TStringList;
begin
  sl:=TStringList.Create;
  try
    sl.AddStrings(FOutput.Items);
    FOutput.Items.Clear;
    FOutput.Items.AddStrings(sl);
  finally
    sl.Free;
  end;
end;
{$ENDIF}

{$IFDEF VCL}
procedure TChat.ListBoxMeasureItem(Control: TWinControl; Index: Integer; var H: Integer);
{$ELSE}
procedure TChat.ListBoxMeasureItem(Control: TWidgetControl; Index: Integer; var H: Integer);
{$ENDIF}
var R:TRect;
    s:string;
begin
  with Control as TListBox do
  begin
    s:=copy(Items[index],2,length(Items[index]));
    R.Top:=0;R.Left:=0;R.Right:=Clientwidth; R.Bottom:=Canvas.TextHeight('ABCDE');
    {$IFDEF VCL}
    H:=DrawText(Canvas.Handle, PChar(s),length(s), R, DT_Left or DT_WORDBREAK or DT_CALCRECT);
    {$ELSE}
    Canvas.TextExtent(s, R, integer(AlignmentFlags_AlignLeft) or integer(AlignmentFlags_WordBreak)); //see Qt.pas
    H:=R.Bottom;
    {$ENDIF}
  end;
end;

{$IFDEF VCL}
procedure TChat.ListBoxDrawItem(Sender: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState);
{$ELSE}
procedure TChat.ListBoxDrawItem(Sender: TObject; Index: Integer; Rect: TRect; State: TOwnerDrawState; var Handled: Boolean);
{$ENDIF}
var R:TRect;
    s:string;
begin
  with (Sender as TListBox) do
  begin
    Canvas.Brush.Color:=$00E1FFFA;
    Canvas.Brush.Style:=bsSolid;
    Canvas.Font.Color:=GameOptions.PlayerColor[strtoint(Items[Index][1])];
    R:=Rect; R.Right:=ClientWidth;
    s:=copy(Items[index],2,length(Items[index]));
    {$IFDEF VCL}
    DrawText(Canvas.Handle, PChar(s),length(s), R, DT_Left or DT_WORDBREAK or DT_CALCRECT);
    DrawText(Canvas.Handle, PChar(s),length(s), R, DT_Left or DT_WORDBREAK);
    {$ELSE}
    Canvas.TextRect(R,R.Left,R.Top,s,integer(AlignmentFlags_AlignLeft) or integer(AlignmentFlags_WordBreak)); //Qt.pas
    Handled:=true;
    {$ENDIF}
  end;
end;

{ ----- TScrabbleGrid ----- }

constructor TScrabbleGrid.Create(aOwner: TWinControl; Scrabble : TScrabble);
begin
   inherited Create(aOwner);
   FScrabble:=Scrabble;
   Parent:=aOwner;
   RowCount:=15; ColCount:=15;
   FixedCols:=0; FixedRows:=0;
   ScrollBars:=ssNone;
   Visible:=true;
   DefaultDrawing:=false; OnDrawCell:=DrawCell;
   DragMode:=dmManual;
   OnDragOver:=ButtonDragOver;
   OnDragDrop:=ButtonDragDrop;
   OnMouseDown:=MouseDown;//start dragging
   OnMouseMove:=MouseMove;
   FStonePanel:=TStonePanel.Create(aOwner);          //a panel for game stones
   FStonePanel.Visible:=false;
   FStatisticPanel:=TStatisticPanel.Create(aOwner);  //a panel for statistics; parent is set to main in constructor
   FChat:=TChat.Create(aOwner);
   FChat.Visible:=false;
end;

procedure TScrabbleGrid.DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
const LeftShift=3; //letter is drawn not at the absolute center to ensure the value to be readable
var s:string;
    ft:TFieldType;
begin
   with Canvas do
   begin
     ft:=FScrabble.GetFieldType(ACol,ARow,7);
     Brush.Style:=bsSolid; //not need in VCL but CLX defaults to bsClear
     Brush.Color:=GameOptions.FieldColor[integer(ft)];
     FillRect(Rect);
     if ft in [ftLetter, ftNewLetter] then
     begin
        Frame3D(Canvas, Rect, clBtnHighlight, clBtnShadow, bvWidth);
        s:=FScrabble.Placed[ACol,ARow,7].what;
        Font.Name:=GameOptions.lbFontName.Caption;
        Font.Size:=round(DefaultColWidth*(strtoint(GameOptions.edLetterSize.Text)/100));
        if (FScrabble.GetFieldType(ACol,ARow,7) in [ftLetter,ftNewLetter]) then
           Font.Color:=GameOptions.PlayerColor[FScrabble.Placed[ACol,ARow,7].who];
        TextOut(Rect.Left+((Rect.Right-Rect.Left) div 2-TextWidth(s) div 2)-LeftShift,
                Rect.Top+((Rect.Bottom-Rect.Top) div 2-TextHeight(s) div 2), s);
        Font.Size:=round(DefaultColWidth*(strtoint(GameOptions.edValueSize.Text)/100));
        Str(FScrabble.LetterValue[s[1]],s);
        TextOut(Rect.Right-TextWidth(s)-1, Rect.Bottom-TextHeight(s)-1, s);
     end else
     if (GameOptions.cbCoordinates.Checked) and ((aCol=0) or (aRow=14)) then
     begin
        Font.Color:=LightOrDark(Brush.Color);
        Font.Name:='Arial';
        Font.Size:=8;
        if aCol=0 then TextOut(Rect.Left+2,Rect.Top+1,Chr(aRow+65));
        if aRow=14 then TextOut(Rect.Right-Canvas.TextWidth(inttostr(aCol+1))-2,
                                Rect.Bottom-Canvas.TextHeight(inttostr(aCol+1))-1,inttostr(aCol+1));
     end;
   end;
end;

procedure TScrabbleGrid.SetSize(L,T,W,H : integer);
begin
   if (H)<(W) then DefaultColWidth:=(H-100) div 15 else DefaultColWidth:=(W-100) div 15;
   DefaultRowHeight:=DefaultColWidth;
   SetBounds(L,T,(DefaultColWidth+1)*15+3,(DefaultRowHeight+1)*15+3);

   if akLeft in FStonePanel.Anchors then FStonePanel.Left:=Left+Width+10;
   if akTop in FStonePanel.Anchors then FStonePanel.Top:=Top;

   if akLeft in FStatisticPanel.Anchors then FStatisticPanel.Left:=Left+Width+10;
   if akTop in FStatisticPanel.Anchors then FStatisticPanel.Top:=Top+Height-FStatisticPanel.Height;

   if akLeft in FChat.Anchors then FChat.Left:=Left+Width+10;
   if akTop in FChat.Anchors then FChat.Top:=FStatisticPanel.Top-FChat.Height-10;;

   FStonePanel.PieceSize:=DefaultColWidth;
end;

procedure TScrabbleGrid.MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var aCol, aRow,i : integer;
begin
   MouseToCell(x,y,aCol,aRow);
   if FScrabble.Placed[aCol,aRow,7].State=psNew then
   begin
      i:=FScrabble.Placed[aCol,aRow,7].Letter;
      FScrabble.PlaceFromLetter[aCol, aRow, 7]:=ClearPlaced;
      Repaint;
      FStonePanel.Stone[i].BeginDrag(true);
   end;
end;

procedure TScrabbleGrid.StoneMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
   with (Sender as TStone) do
   begin
     if (Button=mbLeft) and FStonePanel.CheckAllStones[ckNotLocked] then
     begin //do place stones only if no letter change is initiated
       Visible:=false;
       BeginDrag(true);
     end else
     if (Button=mbRight) and (FScrabble.LocalPlayer=FScrabble.CurrentPlayer) and
        FStonePanel.CheckAllStones[ckVisible] then
     begin //do change stones only if no stone is placed
       FScrabble.LetterPrepareToChange[Tag]:=not Locked;
       Locked:=boolean(FScrabble.Letter[(Sender as TStone).Tag].State=lsChange);
       Repaint;
     end;
   end;
end;

procedure TScrabbleGrid.ButtonDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
var aCol, aRow : integer;
begin
   MouseToCell(x,y,aCol,aRow);
   Accept:=(aCol in [0..14]) and (aRow in [0..14]) and
           (Source is {$IFDEF VCL}TStoneDragObject{$ELSE}TStone{$ENDIF}) and
           (FScrabble.CurrentPlayer=FScrabble.LocalPlayer) and
           (FScrabble.Player[FScrabble.CurrentPlayer].Name<>'Computer') and
           (FScrabble.Placed[aCol,aRow,7].State=psClear);
end;

procedure TScrabbleGrid.StoneEndDrag(Sender, Target: TObject; X,Y: Integer);
begin
   if (Target is TStone) then //interchange letters
   begin
      if assigned(FNetworkMessage) then
         //instead of sending #0 inc Tag
         FNetworkMessage(nmInterChangeLetters,FScrabble.LocalPlayer,255,chr((Sender as TStone).Tag+1)+chr((Target as TStone).Tag+1))
         else begin
            FScrabble.InterChangeLetters((Sender as TStone).Tag,(Target as TStone).Tag);
            UpdateAll;
         end;
   end else if not (Target is TDrawGrid) then (Sender as TStone).Visible:=true;
   //DragObject should be freed
   (Sender as TStone).DragImage.Free;
end;

procedure TScrabbleGrid.ButtonDragDrop(Sender, Source: TObject; X, Y: Integer);
var aCol, aRow : integer;
begin
   MouseToCell(x,y,aCol,aRow);
   FScrabble.PlaceFromLetter[aCol,aRow,7]:=(Source as {$IFDEF VCL}TStoneDragObject{$ELSE}TStone{$ENDIF}).Tag;
   RePaint;
end;

procedure TScrabbleGrid.UpdateAll;
var i:integer;
begin
   FStonePanel.Visible:=not (FScrabble.GameEnd or (FScrabble.PlayerCount=0));
   FStatisticPanel.Caption.Caption:=Language.Translate['game score'];
   //reset StonePanel
   FStonePanel.Caption.Caption:=FScrabble.Player[FScrabble.LocalPlayer].Name;
   FStonePanel.StoneCount:=FScrabble.LettersCount;
   for i:=0 to FStonePanel.FStoneCount-1 do
    with FStonePanel.Stone[i] do
    begin
       OnMouseDown:=StoneMouseDown;
       OnEndDrag:=StoneEndDrag;
       Caption:=FScrabble.Letter[i].what;
       Value:=FScrabble.LetterValue[FScrabble.Letter[i].what];
       Tag:=i;
       Locked:=boolean(FScrabble.Letter[i].State=lsChange);
       Visible:=not (FScrabble.Letter[i].State in [lsInvisible,lsPlaced]);
    end;
   if (FScrabble.CurrentPlayer=FScrabble.LocalPlayer) then
      FStonePanel.State:=stNormal else FStonePanel.State:=stDisabled;
   FStonePanel.Update;
   //reset StatisticPanel
   FStatisticPanel.PlayerCount:=FScrabble.PlayerCount;
   for i:=0 to FStatisticPanel.PlayerCount-1 do
      FStatisticPanel.Value[i]:=FScrabble.Player[i].Points;
   FStatisticPanel.Repaint;
   //repaint DrawGrid
   Repaint;
end;

procedure TScrabbleGrid.MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var aCol, aRow : integer;
    tmp : TDimensions;
    dim : TDimension;
    s,Name : string;
    Value  : Word;
begin
   MouseToCell(x,y,aCol,aRow);
   s:='';
   if (FScrabble.Placed[aCol,aRow,7].State<>psClear) then
   begin
      tmp[dx]:=aCol; tmp[dy]:=aRow; tmp[dz]:=7;
      for dim:=dx to dy do
      begin
         FScrabble.GetWordAtPos(dim,tmp,Name,Value);
         if length(Name)>1 then
         begin
           if s<>'' then s:=s+#13#10;
           s:=s+Name;
           if Dictionary.IndexOf(Name)=-1 then Name:=Language.Translate[' not found']
                                          else Name:=Dictionary.Meaning[Dictionary.IndexOf(Name)];
           if Name<>'' then s:=s+' ('+Name+')';
         end;
      end;
      if FScrabble.Placed[aCol,aRow,7].State=psNew then
      begin
         Value:=FScrabble.CheckMove([]);
         if FScrabble.LastError<>erNone then s:=s+#13#10+'---------------'+#13#10+FScrabble.LastErrorName
                                        else s:=s+#13#10+'---------------'+#13#10+Language.Translate['total score: ']+IntToStr(Value);
      end;
      if not AnsiSameText(s,Hint) then Application.CancelHint;
      Hint:=s;
      ShowHint:=true;
   end else Application.CancelHint;
end;

function TScrabbleGrid.GetLettersLocked: boolean;
begin
   Result:=not FStonePanel.CheckAllStones[ckNotLocked];
end;

end.
