unit UMain;
{ ********************************************************************************* }
{ (c) GNU                                                                           }
{ author: Heiko Tietze                                                              }
{ version: 1.5                                                                      }
{ date: 23.04.2005                                                                  }
{ description: main unit; creation and initialization of the game is done here      }
{              primary in formcreate(); all communications with the user is done by }
{              the localmessage() or networkmessage(), per statusbar and so on      }
{ ********************************************************************************* }

{$I Def.inc}

interface

uses SysUtils, Types, Classes, Variants, IniFiles,
     {$IFDEF VCL}
     Windows, Controls, Forms, StdCtrls, ExtCtrls, Graphics, ComCtrls, Dialogs,
     ImgList, Menus, Grids, ToolWin, ActnList, MMSystem,
     {$ELSE}
     QControls, QForms, QStdCtrls, QExtCtrls, QGraphics, QComCtrls,
     QTypes, QDialogs, QImgList, QMenus, QGrids, QActnList,
     {$ENDIF}
     UScrabble, UGameOptions, UDictionary, UScrabbleGrid, UNewGame, UBrutForce,
     UStatistics, UNetwork, UPoll, UTools, ULanguage, UAbout;

type TMain = class(TForm)
        ActionList      : TActionList;
        acSingleMove    : TAction;
        acExit          : TAction;
        acOptions       : TAction;
        acLoad          : TAction;
        acSave          : TAction;
        acNewGame       : TAction;
        acNextPlayer    : TAction;
        acDemo          : TAction;
        acStatistics    : TAction;
        acNetwork       : TAction;
        acReset         : TAction;
        acUpdate        : TAction;
        acAbout         : TAction;
        ToolBar         : TToolBar;
        tbExit          : TToolButton;
        tbOpen          : TToolButton;
        tbSaveAs        : TToolButton;
        tbNewGame       : TToolButton;
        tbNextPlayer    : TToolButton;
        tbSingleMove    : TToolButton;
        tbConfiguration : TToolButton;
        tbStatistics    : TToolButton;
        tbN1            : TToolButton;
        tbN2            : TToolButton;
        tbN3            : TToolButton;
        SaveDialog      : TSaveDialog;
        OpenDialog      : TOpenDialog;
        tbReset         : TToolButton;
        tbN4            : TToolButton;
        MainMenu        : TMainMenu;
        miFile          : TMenuItem;
        miOpen          : TMenuItem;
        miSave          : TMenuItem;
        miN1            : TMenuItem;
        miExit          : TMenuItem;
        miGame          : TMenuItem;
        miNewGame       : TMenuItem;
        miNextPlayer    : TMenuItem;
        miN2            : TMenuItem;
        miDemo          : TMenuItem;
        miSingleMove    : TMenuItem;
        miReset         : TMenuItem;
        miN3            : TMenuItem;
        miNetwork       : TMenuItem;
        miConfiguration : TMenuItem;
        miOptions       : TMenuItem;
        miStatistics    : TMenuItem;
        miN4            : TMenuItem;
        miUpdate        : TMenuItem;
        miAbout         : TMenuItem;
        StatusBar       : TStatusBar;
        ImageList       : TImageList;
        procedure FormCreate(Sender: TObject);
        procedure FormShow(Sender: TObject);
        procedure FormResize(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
        procedure ActionListUpdate(Action: TBasicAction; var Handled: Boolean);
        procedure StatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; const Rect: TRect);
        procedure ExitExecute(Sender: TObject);
        procedure NewGameExecute(Sender: TObject);
        procedure NextPlayerExecute(Sender: TObject);
        procedure SingleMoveExecute(Sender: TObject);
        procedure DemoExecute(Sender: TObject);
        procedure OptionsExecute(Sender: TObject);
        procedure SaveExecute(Sender: TObject);
        procedure LoadExecute(Sender: TObject);
        procedure StatisticsExecute(Sender: TObject);
        procedure NetworkExecute(Sender: TObject);
        procedure StatusBarHint(Sender: TObject);
        procedure ResetExecute(Sender: TObject);
        procedure AboutExecute(Sender: TObject);
        procedure UpdateExecute(Sender: TObject);
     private
        GameState    : set of (gsHighScore,gsComputing,gsNetwork,gsLoading,gsDemo,gsIUpdateAvailable);
        Scrabble     : TScrabble;
        ScrabbleGrid : TScrabbleGrid;
        BrutForce    : TBrutForce;
        function AskForWord(Value:string):boolean;
        procedure Progress(Percent,ID:Byte);
        procedure FormIdle(Sender : TObject; var Done: Boolean);
        procedure NetworkMessage(Command:TNetMsgType; Sender, Recipient: Byte; Value:string);
        procedure LocalMessage(Value:string);
     end;

var Main: TMain;

implementation

{$IFDEF VCL}
  {$R *.dfm}
{$ELSE}
  {$R *.xfm}
{$ENDIF}

//constants for easy identification of panels in statusbar
const sbProgress=0;
      sbMessage=1;
      sbLettersLeft=2;
      sbPlayers=3;
      sbState=4;
      sbDummy=5;

procedure TMain.FormCreate(Sender: TObject);

 procedure FixControlStyles(Parent: TControl);
 var i: Integer;
 begin
   Parent.ControlStyle := Parent.ControlStyle + [csDisplayDragImage];
   if (Parent is TWinControl) then
      with TWinControl(Parent) do
         for i:=0 to ControlCount-1 do
           FixControlStyles(Controls[i]);
 end;

begin
   Scrabble:=TScrabble.Create(D2);    //rules, basic variables and control procedures are defined in UScrabble.pas
   Scrabble.OnAskForWord:=AskForWord; //we need a link if the word is not found in library the user(s) are asked
   ScrabbleGrid:=TScrabbleGrid.Create(Main as TControl, Scrabble); //the 2D visualization (UScrabbleGrid.pas)
   Dictionary:=TDictionary.Create;    //dictionary text will be loaded in OnIdle to show progress in SB
   Dictionary.OnProgress:=Progress;
   Application.OnIdle:=FormIdle;      //only to visualize the loading of the dictionary content
   BrutForce:=TBrutForce.Create(Scrabble);  //algorithms to compute a move
   BrutForce.OnProgress:=Progress;
   Application.HintHidePause:=10000;
   FixControlStyles(Self);            //csDisplayDragImage has to be set for each control

   {$IFDEF WIN32}
   if FileExists(ExtractFilePath(Application.ExeName)+'IUpdate.exe')
   {$ELSE}
   if FileExists('./IUpdate')
   {$ENDIF}
      then include(GameState,gsIUpdateAvailable) else exclude(GameState,gsIUpdateAvailable);
   {$IFNDEF VCL}
   Application.Font.Name:='Helvetia';
   Application.Font.Size:=8;
   {$ENDIF}
end;

procedure TMain.FormShow(Sender: TObject);
var ini : TIniFile;
begin
   ini:=TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
   with ini do
   try
      Main.Left:=ReadInteger('MainForm','Left',100);
      Top:=ReadInteger('MainForm','Top',100);
      Width:=ReadInteger('MainForm','Width',640);
      Height:=ReadInteger('MainForm','Height',480);
      if Left=-1 then WindowState:=wsMaximized;
   finally
      Free;
   end;
end;

procedure TMain.FormIdle(Sender: TObject; var Done: Boolean);
begin
   if (Dictionary.DictFileName='') and Visible then //CLX fires OnIdle before form is shown
   begin
      include(GameState,gsLoading);
      StatusBar.Panels[sbMessage].Text:=Language.Translate['Dictionary is being loaded...'];
      SetBounds(Left,Top,Width+1,Height); //resize ensures that status bar and progress are shown correctly
      if not GameOptions.LoadDictionary(GameOptions.DictionaryName) then //load dictionary
         LocalMessage(Language.Translate['Dictionary couldn''t be loaded!']) else
         StatusBar.Panels[sbMessage].Text:='';
      SetBounds(Left,Top,Width-1,Height);
      exclude(GameState,gsLoading);
      GameOptions.OnOptionsChanged:=ScrabbleGrid.UpdateAll; //changes of options are shown immediately
      Done:=true;
      Application.OnIdle:=nil; //disconnect this handler
    end;
end;

procedure TMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
   if (GameState*[gsLoading,gsComputing,gsNetwork]=[]) then //do not close if network or computer is active or is loading
   begin
      with TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini')) do
      try
        if WindowState=wsMaximized then WriteInteger('MainForm','Left',-1)
                                   else WriteInteger('MainForm','Left',Main.Left);
        WriteInteger('MainForm','Top',Main.Top);
        WriteInteger('MainForm','Width',Main.Width);
        WriteInteger('MainForm','Height',Main.Height);
      finally
        Free;
      end;
      if GameOptions.cbSaveLibOnExit.Checked and Dictionary.Changed then
         Dictionary.SaveAsText(Dictionary.DictFileName);
      ScrabbleGrid.Free;
      BrutForce.Free;
      Dictionary.Free;
      Scrabble.Free;
      Action:=caFree;
   end else
   begin
     LocalMessage(Language.Translate['Computer or Network still active!']);
     Action:=caNone;
   end;
end;

procedure TMain.FormResize(Sender: TObject);
begin
   if not Application.Terminated and not (fsCreating in FormState) then //resize is called after close and free
   begin
      ScrabbleGrid.SetSize(20,40,Width,Height-30);
      StatusBar.Panels[sbMessage].Width:=StatusBar.Width-300;
   end;
end;

procedure TMain.StatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; const Rect: TRect);
var bmp : TBitmap;
    i   : integer;
begin
   if Panel=StatusBar.Panels[sbState] then
   begin
      ImageList.Draw(StatusBar.Canvas,Rect.Left+01,Rect.Top+1,18+integer(not (gsNetwork in GameState)));
      ImageList.Draw(StatusBar.Canvas,Rect.Left+22,Rect.Top+1,10+integer(not (gsComputing in GameState)));
      ImageList.Draw(StatusBar.Canvas,Rect.Left+43,Rect.Top+1,14+integer(not (gsHighscore in GameState)));
      bmp:=TBitmap.Create;
      try
        bmp.Width:=16; bmp.Height:=16;
        Poll.AnswerToBitmap(bmp); //depending on playercount and answers the bmp is modified in UPoll.pas
        StatusBar.Canvas.Draw(Rect.Left+64,Rect.Top+1,bmp);
      finally
        bmp.Free;
      end;
   end else
   if Panel=StatusBar.Panels[sbLettersLeft] then
   begin
     i:=(Rect.Right-Rect.Left) div 2-StatusBar.Canvas.TextWidth('#'+inttostr(length(Scrabble.LettersLeft))) div 2;
     if Scrabble.GameEnd or (Scrabble.PlayerCount=0) then StatusBar.Canvas.Font.Color:=clGray
                                                     else StatusBar.Canvas.Font.Color:=clBlack;
     StatusBar.Canvas.TextOut(Rect.Left+i,StatusBar.Canvas.TextHeight('ABCDE') div 2-1,'#'+inttostr(length(Scrabble.LettersLeft)));
   end else
   if Panel=StatusBar.Panels[sbPlayers] then
   begin
     bmp:=TBitmap.Create;
     try
       bmp.Width:=10; bmp.Height:=StatusBar.Height-4;
       bmp.Canvas.Brush.Style:=bsSolid;
       bmp.Canvas.Brush.Color:=clBtnFace;
       bmp.Canvas.Pen.Color:=clRed;
       for i:=0 to 3 do
       begin
         bmp.Canvas.FillRect(Bounds(0,0,10,bmp.Height));//bmp.Canvas.ClipRect -> doesn't work in CLX
         if (i>Scrabble.PlayerCount-1) then bmp.Canvas.Font.Color:=clGray
                                       else bmp.Canvas.Font.Color:=clBlack;
         if (Scrabble.PlayerCount>0) then
         begin
           if (i=Scrabble.CurrentPlayer) then bmp.Canvas.Font.Color:=clRed;
           if (i=Scrabble.LocalPlayer) and (Scrabble.Player[i].Name<>'Computer') then
              bmp.Canvas.Rectangle(Bounds(0,2,bmp.Width,bmp.Height-3));
         end;
         bmp.Canvas.TextOut(2, 3, inttostr(i+1));
         StatusBar.Canvas.Draw(Rect.Left+1+i*10,3,bmp);
       end;
     finally
       bmp.Free;
     end;
   end;
end;

function TMain.AskForWord(Value: string): boolean;
//ask local player and network players for unknown word
var sl : TStringList;
    i  : integer;
begin
   sl:=TStringList.Create;
   try
      sl.Delimiter:=',';
      sl.CommaText:=copy(Value,1,length(Value)-1);//delete the last added separator
      for i:=sl.Count-1 downto 0 do
        if Dictionary.IsWordInDictionary[sl[i]] then sl.Delete(i);
      Result:=(sl.Count=0);
      if (not Result) and not (gsComputing in GameState) then
      begin
        if (sl.Count=1) and not (gsNetwork in GameState) then
        case MessageDlg(Language.Translate['The following word was not found:']+#13#10+sl[0]+#13#10+
                        Language.Translate['Add to library?'], mtConfirmation,[mbYes,mbNo,mbIgnore],0) of
         mrYes    : begin
                       Dictionary.Add(sl[0]);
                       Result:=true;
                    end;
         mrIgnore : Result:=true;
         mrNo     : Result:=false;
        end //case
        else //if sl.count
        Result:=MessageDlg(Language.Translate['The following words are not in library:']+#13#10+sl.CommaText+#13#10+Language.Translate['Accept anyway?'], mtConfirmation,[mbYes,mbNo],0)=mrYes;
        if Result and (gsNetwork in GameState) then
           Result:=(Poll.WaitForAnswers(Language.Translate['The following words are not in library:']+#13#10+sl.CommaText+#13#10+Language.Translate['Accept anyway?'])=mrYes);
      end;
   finally
     sl.Free;
   end;
end;

procedure TMain.Progress(Percent, ID: Byte);
var R : TRect;
begin
   R:=Bounds(1,3,StatusBar.Panels[sbProgress].Width-2,StatusBar.Height-4);
   if Percent>100 then
   begin
     StatusBar.Canvas.Brush.Color:=clBtnFace;
     StatusBar.Canvas.FillRect(R);
   end else
   begin
     if (Percent>0) then R.Right:=round(StatusBar.Panels[sbProgress].Width/(100/Percent))-2
                    else R.Right:=R.Left;
     case ID of
       0 : StatusBar.Canvas.Brush.Color:=clBlack;
       1 : begin R.Bottom:=8; StatusBar.Canvas.Brush.Color:=clBlue; end;
       2 : begin R.Top:=8; R.Bottom:=13; StatusBar.Canvas.Brush.Color:=clRed; end;
       3 : begin R.Top:=13; R.Bottom:=18; StatusBar.Canvas.Brush.Color:=clGreen; end;
     end;
     StatusBar.Canvas.FillRect(R);
     StatusBar.Canvas.Brush.Color:=clBtnFace;
     R.Left:=R.Right; R.Right:=StatusBar.Panels[sbProgress].Width;
     StatusBar.Canvas.FillRect(R);
     Application.ProcessMessages;
   end;
end;

procedure TMain.LocalMessage(Value: string);
begin
  if GameOptions.cbShowErrors.Checked then Main.StatusBar.Panels[sbMessage].Text:=Value
                                      else ShowMessage(Value);
end;

procedure TMain.ExitExecute(Sender: TObject);
begin
  Close;
end;

procedure TMain.OptionsExecute(Sender: TObject);
begin
   GameOptions.ShowModal;
end;

procedure TMain.SaveExecute(Sender: TObject);
begin
   if SaveDialog.Execute then Scrabble.SaveToFile(SaveDialog.FileName,gsHighScore in GameState);
end;

procedure TMain.LoadExecute(Sender: TObject);
begin
   if OpenDialog.Execute then
   begin
      include(GameState,gsLoading);
      NewGameExecute(nil);
      if Scrabble.LoadFromFile(OpenDialog.FileName) then include(GameState,gsHighScore)
                                                    else exclude(GameState,gsHighScore);
      Scrabble.LocalPlayer:=Scrabble.CurrentPlayer;//game cannot be saved/loaded in network mode
      ScrabbleGrid.UpdateAll;
      StatusBar.Repaint;
      exclude(GameState,gsLoading);
   end;
end;

procedure TMain.StatisticsExecute(Sender: TObject);
var i : integer;
begin
   Statistics.sgMoves.RowCount:=Scrabble.HistoryCount+1;
   for i:=0 to Scrabble.HistoryCount-1 do
   begin
      Statistics.sgMoves.Cells[0,i+1]:=IntToStr(Scrabble.History[i].MoveNumber);
      Statistics.sgMoves.Cells[1,i+1]:=IntToStr(Scrabble.History[i].Player)+Scrabble.Player[Scrabble.History[i].Player].Name;
      Statistics.sgMoves.Cells[2,i+1]:=Scrabble.History[i].PlacedWord;
      Statistics.sgMoves.Cells[3,i+1]:=IntToStr(Scrabble.History[i].Value);
   end;
   Statistics.ShowModal;
end;

procedure TMain.SingleMoveExecute(Sender: TObject);
begin
   if not Scrabble.GameEnd then
   begin
      if (Sender<>self) and (gsHighScore in GameState) then
         if MessageDlg(Format(Language.Translate['After suggestion by the computer no highscore will be accepted!%sExecute anyway?'],[#13#10]),mtConfirmation,[mbYes,mbNo],0)=mrYes then
            exclude(GameState,gsHighScore);
      if (Sender=self) or not (gsHighScore in GameState) then
      begin
        include(GameState,gsComputing);
        ActionList.UpdateAction(nil);
        StatusBar.Repaint;
        BrutForce.ComputeMove;
        exclude(GameState,gsComputing);
        ScrabbleGrid.UpdateAll;
        StatusBar.Repaint;
      end;
   end;
end;

procedure TMain.DemoExecute(Sender: TObject);
begin
  if gsDemo in GameState then
  begin
    exclude(GameState,gsDemo);
    acDemo.Caption:='Demo';
  end else
  begin
    include(GameState,gsDemo);
    acDemo.Caption:=Language.Translate['Cancel Demo'];
    Scrabble.SaveToFile('savegame.dat',gsHighScore in GameState);
    NewGame.rgPlayers.ItemIndex:=0; NewGame.cbPlayer1.Text:='Demo';
    try
      while gsDemo in GameState do
      begin
        NewGameExecute(nil);
        while not Scrabble.GameEnd and (gsDemo in GameState) do
        begin
          SingleMoveExecute(self);
          NextPlayerExecute(self);
        end;
      end;
      if Scrabble.LoadFromFile('savegame.dat') then include(GameState,gsHighScore)
                                               else exclude(GameState,gsHighScore);
      ScrabbleGrid.UpdateAll;
      StatusBar.Repaint;
    finally
      DeleteFile('savegame.dat');
    end;
  end;
end;

procedure TMain.ResetExecute(Sender: TObject);
begin
   Scrabble.ResetLetters;
   ScrabbleGrid.UpdateAll;
end;

procedure TMain.ActionListUpdate(Action: TBasicAction; var Handled: Boolean);
begin
   acExit.Enabled:=GameState*[gsLoading,gsComputing,gsNetwork]=[];
   acLoad.Enabled:=GameState*[gsLoading,gsComputing,gsNetwork]=[];
   acSave.Enabled:=GameState*[gsLoading,gsComputing,gsNetwork]=[];
   acNewGame.Enabled:=GameState*[gsLoading,gsComputing]=[];
   acReset.Enabled:=(GameState*[gsLoading,gsComputing]=[]) and (Scrabble.PlayerCount>0) and (Scrabble.CurrentPlayer=Scrabble.LocalPlayer) and not ScrabbleGrid.LettersLocked;
   acNextPlayer.Enabled:=(GameState*[gsComputing]=[]) and not Scrabble.GameEnd and (Scrabble.PlayerCount>0) and (Scrabble.CurrentPlayer=Scrabble.LocalPlayer);
   acSingleMove.Enabled:=(GameState*[gsLoading,gsNetwork,gsComputing]=[]) and not Scrabble.GameEnd and (Scrabble.PlayerCount>0);
   acDemo.Enabled:=GameState*[gsLoading,gsNetwork]=[];
   acOptions.Enabled:=GameState*[gsLoading,gsComputing,gsDemo]=[];
   acStatistics.Enabled:=GameState*[gsLoading,gsComputing,gsDemo]=[];
   acNetwork.Enabled:=GameState*[gsLoading,gsComputing,gsDemo]=[];
   acAbout.Enabled:=GameState*[gsLoading]=[];
   acUpdate.Enabled:=(GameState*[gsLoading]=[]) and (gsIUpdateAvailable in GameState);
   if Poll.Visible then ActionList.State:=asSuspended else ActionList.State:=asNormal;
   Handled:=true;
end;

procedure TMain.NewGameExecute(Sender: TObject);
var i,j,Seed : integer;
    LetterAvail : string;
    LetterValue : array[Char] of Byte;
begin
   //in network mode only other players are ask for a new game
   if (gsNetwork in GameState) and (Sender<>nil) then
   begin
     if (Scrabble.PlayerCount=0) or Scrabble.GameEnd or
        ((MessageDlg(Language.Translate['Really cancel this game?'],mtConfirmation,[mbYes,mbNo],0)=mrYes) and
         (Poll.WaitForAnswers(Format(Language.Translate['Player %s would like to start a new game.%sAccept?'],[inttostr(Network.LocalPlayer),#13#10]))=mrYes)
        ) then Network.OnSendMessage(nmNewGame,Network.LocalPlayer,255,'');
   end else
   //if network and sender is nil (server has sent nmNewGame) or demo or newgame is OKed
   if (GameState*[gsNetwork,gsDemo,gsLoading]<>[]) or (NewGame.ShowModal=mrOK) then
   begin
     //randomization
     if gsNetwork in GameState then
     begin
       Seed:=GameOptions.sgLetters.Tag;
       Scrabble.LocalPlayer:=Network.LocalPlayer;
     end else
     begin
       Randomize;
       Seed:=Random(999)+1;
       Scrabble.LocalPlayer:=0;
     end;
     //collect options
     LetterAvail:='';
     BrutForce.Characters:='';
     FillChar(LetterValue,sizeof(LetterValue),0);
     for i:=1 to GameOptions.sgLetters.RowCount-1 do
      if (length(GameOptions.sgLetters.Cells[0,i])=1) and
         (GameOptions.sgLetters.Cells[1,i]<>'') and (GameOptions.sgLetters.Cells[2,i]<>'') then
      begin
        for j:=1 to StrToIntDef(GameOptions.sgLetters.Cells[1,i],1) do LetterAvail:=LetterAvail+GameOptions.sgLetters.Cells[0,i];
        LetterValue[Char(GameOptions.sgLetters.Cells[0,i][1])]:=StrToIntDef(GameOptions.sgLetters.Cells[2,i],1);
        BrutForce.Characters:=BrutForce.Characters+GameOptions.sgLetters.Cells[0,i];
      end;
     //start new game
     Scrabble.NewGame(NewGame.PlayerNames,
                      LetterAvail,
                      StrToIntDef(GameOptions.edLetterCount.Text,7),
                      LetterValue, Seed,
                      GameOptions.cbDoubleFirstMove.Checked);
     if gsNetwork in GameState then
     begin
       Poll.PlayerCount:=Scrabble.PlayerCount;
       for i:=0 to Scrabble.PlayerCount-1 do
        case i of
         0 : Poll.lbPlayer1.Caption:=Scrabble.Player[0].Name;
         1 : Poll.lbPlayer2.Caption:=Scrabble.Player[1].Name;
         2 : Poll.lbPlayer3.Caption:=Scrabble.Player[2].Name;
         3 : Poll.lbPlayer4.Caption:=Scrabble.Player[3].Name;
        end;
     end;
     //update gamestate
     if (gsDemo in GameState) then exclude(GameState,gsHighScore)
                              else include(GameState,gsHighScore);
     GameOptions.btnAdd.Enabled:=not (gsNetwork in GameState);
     ScrabbleGrid.UpdateAll;
     StatusBar.Repaint;
     ToolBar.Repaint;
     if Scrabble.Player[Scrabble.CurrentPlayer].Name='Computer' then
     begin
        SingleMoveExecute(self);
        NextPlayerExecute(self);
     end;
   end;
end;

procedure TMain.NextPlayerExecute(Sender: TObject);
var i:integer;
    s:string;
    LastPlacedValue : word;
begin
   //in network mode the move is checked only, for words not in library is asked and message nmNextPlayer will be send to server
   if (gsNetwork in GameState) and (Sender<>nil) then
   begin
      Scrabble.CheckMove([cmAskForWords]);
      if Scrabble.LastError=erNone then
      begin
        s:='';
        for i:=0 to Scrabble.LettersCount-1 do
            with Scrabble.Letter[i] do s:=s+chr(where[dx]+2)+chr(where[dy]+2)+chr(where[dz]+2)+chr(integer(State));
        Network.OnSendMessage(nmNextPlayer,Network.LocalPlayer,255,s);
      end else
      begin
        LocalMessage(Scrabble.LastErrorName);
        if GameOptions.cbResetLetters.Checked then Scrabble.ResetLetters;
        ScrabbleGrid.UpdateAll;
      end;
   end else
   //not network or nmNextplayer was sent by the server
   begin
     //do not ask for words in network mode
     if gsNetwork in GameState then
        LastPlacedValue:=Scrabble.NextPlayer([cmAddToHistory]) else
        LastPlacedValue:=Scrabble.NextPlayer([cmAddToHistory,cmAskForWords]);

     //update game
     if Scrabble.LastError=erNone then
     begin
        StatusBar.Panels[sbMessage].Text:=Language.Translate['Total value of the last move: ']+' '+inttostr(LastPlacedValue);
        //clear the Poll
        if (gsNetwork in GameState) then Poll.Clear;
        //if network isn't active all currentplayers are localplayers
        if not (gsNetwork in GameState) then Scrabble.LocalPlayer:=Scrabble.CurrentPlayer;
        //check for highscore if gameend is set
        if Scrabble.GameEnd and (gsHighScore in GameState) then
        begin
          for i:=0 to Scrabble.PlayerCount-1 do
          begin
            if (Scrabble.Player[i].Name<>'Computer') or Gameoptions.cbComputerInHighScore.Checked then
            begin
               if Statistics.AddToHighScore(Scrabble.Player[i].Name,Scrabble.Player[i].Points,Scrabble.PlayerCount) then
                  LocalMessage(Format(Language.Translate['%s has achieved a new highscore value'],[Scrabble.Player[i].Name]));
            end;
          end;
          exclude(GameState,gsHighScore); //add to highscore only once
        end;
     end else
     begin
        LocalMessage(Scrabble.LastErrorName);
        if GameOptions.cbResetLetters.Checked then Scrabble.ResetLetters;
     end;
     ScrabbleGrid.UpdateAll;
     StatusBar.Repaint;
     {$IFDEF VCL}
     if (gsNetwork in GameState) and (Scrabble.LocalPlayer=Scrabble.CurrentPlayer) and
        GameOptions.cbSoundOnMove.Checked then
        sndPlaySound(PChar(GameOptions.edSoundName.Text),SND_ASYNC);
     {$ENDIF}
     s:='';  for i:=0 to Scrabble.LettersCount-1 do if Scrabble.Letter[i].State=lsInvisible then s:=s+' ';
     if not Scrabble.GameEnd and //end of game is set in UScrabble.pas if all players didn't place a letter and if no more letters available to change
        (length(s)=Scrabble.LettersCount) then //if current player doesn't have letters left to place
        NextPlayerExecute(self) else
       if (Scrabble.Player[Scrabble.CurrentPlayer].Name='Computer') then
       begin
         SingleMoveExecute(self);
         NextPlayerExecute(self);
       end;
   end;
end;

procedure TMain.NetworkExecute(Sender: TObject);
begin
   if not (gsNetwork in GameState) then
   begin
      Application.CreateForm(TNetwork,Network);
      Network.OnReceive:=NetworkMessage;
      if Network.ShowModal=mrOK then
      begin
        include(GameState,gsNetWork);
        acNetwork.Caption:=Language.Translate['Close network'];
        ScrabbleGrid.OnSendMessage:=Network.OnSendMessage;
        ScrabbleGrid.Chat.Visible:=true;
        ScrabbleGrid.Chat.OnSendMessage:=Network.OnSendMessage;
        Resize;
      end else Network.Free;
   end else //not in GameState
   if MessageDlg(Language.Translate['Really close network?'],mtConfirmation,[mbYes,mbNo],0)=mrYes then
      NetworkMessage(nmServerDisconnect,0,255,Language.Translate['Network has been closed']);
   StatusBar.Repaint;
end;

procedure TMain.NetworkMessage(Command: TNetMsgType; Sender, Recipient: Byte; Value: string);
var i:integer;
begin
  case Command of
    nmChat             : if Value<>'' then
                         begin
                            {$IFDEF VCL}
                            if (Sender<>Scrabble.LocalPlayer) and GameOptions.cbSoundOnChat.Checked then
                               sndPlaySound(PChar(GameOptions.edSoundName.Text),SND_ASYNC);
                            {$ENDIF}
                            ScrabbleGrid.Chat.AddLine(Sender,Value);
                         end;
    nmConnect          : begin
                           if Network.LocalPlayer=255 then Network.LocalPlayer:=Sender;
                           LocalMessage(Format(Language.Translate['%s enters the network game'],[Value]));
                           Scrabble.GameEnd:=true;
                           ScrabbleGrid.UpdateAll;
                         end;
    nmAsk              : if Sender<>Network.LocalPlayer then
                         begin
                            {$IFDEF VCL}
                            if GameOptions.cbSoundOnPoll.Checked then
                               sndPlaySound(PChar(GameOptions.edSoundName.Text),SND_ASYNC);
                            {$ENDIF}
                            Network.OnSendMessage(nmAnswer,Network.LocalPlayer,255,
                              BoolToStr(MessageDlg(Value,mtConfirmation,[mbYes,mbNo],0)=mrYes,true));
                         end;
    nmAnswer           : begin
                           Poll.Answer[Sender]:=StrToBool(Value);
                           StatusBar.Repaint;
                         end;
    nmServerDisconnect : begin
                           ScrabbleGrid.Chat.Visible:=false;
                           ScrabbleGrid.OnSendMessage:=nil;
                           exclude(GameState,gsNetwork);
                           acNetwork.Caption:=Language.Translate['Network'];
                           Scrabble.GameEnd:=true;
                           ScrabbleGrid.UpdateAll;
                           StatusBar.Panels[sbMessage].Text:='Network is being closed...';
                           Network.Free;
                           LocalMessage(Value);
                           StatusBar.Repaint;
                         end;
    nmClientDisconnect : begin
                           Network.LocalPlayer:=Recipient;
                           Scrabble.GameEnd:=true;
                           ScrabbleGrid.UpdateAll;
                           LocalMessage(Value);
                         end;
    // if a new game is initiated the game options of the server are transmitted to all clients
    // after set up of these options, the new game will be started
    nmNewGame          : begin
                           //Letters -> GameOptions.sgLetters
                           GameOptions.sgLetters.RowCount:=ord(Value[1]);
                           for i:=1 to GameOptions.sgLetters.RowCount-1 do
                           begin
                             GameOptions.sgLetters.Cells[0,i]:=Value[i*3-1];
                             GameOptions.sgLetters.Cells[1,i]:=IntToStr(ord(Value[i*3]));
                             GameOptions.sgLetters.Cells[2,i]:=IntToStr(ord(Value[i*3+1]));
                           end;
                           //RandSeed for Scrabble
                           i:=(GameOptions.sgLetters.RowCount-1)*3+2;
                           GameOptions.sgLetters.Tag:=ord(Value[i]); inc(i);
                           GameOptions.edLetterCount.Text:=IntToStr(ord(Value[i])); inc(i);
                           GameOptions.cbDoubleFirstMove.Checked:=boolean(ord(Value[i])-1); inc(i);
                           //player names
                           NewGame.rgPlayers.ItemIndex:=0;
                           while length(Value)>i do
                           begin
                              case NewGame.rgPlayers.ItemIndex of
                               0 : NewGame.cbPlayer1.Text:=copy(Value,i+1,ord(Value[i]));
                               1 : NewGame.cbPlayer2.Text:=copy(Value,i+1,ord(Value[i]));
                               2 : NewGame.cbPlayer3.Text:=copy(Value,i+1,ord(Value[i]));
                               3 : NewGame.cbPlayer4.Text:=copy(Value,i+1,ord(Value[i]));
                              end;
                              inc(i,ord(Value[i])+1);
                              if i<length(Value) then NewGame.rgPlayers.ItemIndex:=NewGame.rgPlayers.ItemIndex+1;
                           end;
                           NewGameExecute(nil);
                         end;
    nmNextPlayer       : begin
                           for i:=0 to Scrabble.LettersCount-1 do
                            case TLetterState(ord(Value[i*4+4])) of
                             lsChange : Scrabble.LetterPrepareToChange[i]:=true;
                             lsPlaced : Scrabble.PlaceFromLetter[ord(Value[i*4+1])-2,ord(Value[i*4+2])-2,ord(Value[i*4+3])-2]:=i;
                            end;//case
                           NextPlayerExecute(nil);
                         end;
    nmInterChangeLetters : begin
                              Scrabble.InterChangeLetters(ord(Value[1])-1,ord(Value[2])-1,Sender);
                              ScrabbleGrid.UpdateAll;
                           end;
    else LocalMessage('unknown message received: '+Value);
  end;//case
end;

procedure TMain.StatusBarHint(Sender: TObject);
var p   : TPoint;
    i,j : integer;
begin
  p:=ScreenToClient(Mouse.CursorPos);
  j:=0;
  for i:=0 to StatusBar.Panels.Count-1 do
    if (p.x>j) and (p.x<j+StatusBar.Panels[i].Width)
       then break else inc(j,StatusBar.Panels[i].Width);
  case i of
   sbProgress : StatusBar.Hint:=Language.Translate['Progress'];
   sbMessage  : StatusBar.Hint:=Language.Translate['Messages'];
   sbLettersLeft : begin
                     StatusBar.Hint:=Format(Language.Translate['letters left%s'],[#13#10]);
                     for j:=1 to length(Scrabble.LettersLeft) do
                     begin
                        if (j>1) and (Scrabble.LettersLeft[j]<>Scrabble.LettersLeft[j-1]) then
                           StatusBar.Hint:=StatusBar.Hint+',';
                        if (length(Scrabble.LettersLeft)>50) and (j=length(Scrabble.LettersLeft) div 2) then
                           StatusBar.Hint:=StatusBar.Hint+#13#10;
                        StatusBar.Hint:=StatusBar.Hint+Scrabble.LettersLeft[j];
                     end;
                   end;
   sbPlayers  : StatusBar.Hint:=Language.Translate['Players'];
   sbState    : StatusBar.Hint:=Language.Translate['Status'];
  else StatusBar.Hint:='Statusbar'; //dummy
  end;
end;

procedure TMain.AboutExecute(Sender: TObject);
begin
   Application.CreateForm(TAbout,About);
   About.ShowModal;
end;

procedure TMain.UpdateExecute(Sender: TObject);
begin
  {$IFDEF WIN32}
   {$IFDEF VCL}
   WinExec(PChar(ExtractFilePath(Application.ExeName)+'IUpdate.exe'),SW_SHOWNORMAL);
   {$ENDIF}
  {$ELSEIF LINUX}
  Libc.system('./IUpdate')
  {$IFEND}
end;

end.
