Создание Internet-приложений в среде Delphi. Создание простого приложения для Web-сервера

   
На этом шаге мы рассмотрим создание простого приложения для Web-сервера: игра "Крестики-нолики".

   
Win32 GUI-приложения являются прикладными программами, управляемыми событиями.
Компоненты и формы таких программ имеют обработчики событий, которые отвечают на различные
действия, производимые пользователем. В случае прикладной программы Web, клиент не
поддерживает постоянное соединение с Web-сервером; следовательно, каждая транзакция является
для сервера независимым событием. При таком подходе возникает проблема: нет сведений
относительно предшествующих событий. Каждая Web-транзакция не знает результатов предыдущих
транзакций. Выход из создавшегося положения рассмотрим на примере следующей игры.
Пример программы игры в "крестики-нолики"

   
При создании стандартного игрового приложение Windows, конфигурация игрового поля
сохраняется в некоторой структуре, так как каждый раз, когда игрок делает ход, расположение элементов
на поле изменяется. Эту задачу несколько труднее решить для прикладной программы Web-сервера,
потому что в этом случае нужно применить некую замену для отсутствующей постоянной памяти.
В разрабатываемом приложении эта проблема решается следующим образом: каждый раз, когда
клиент делает новый ход, серверу посылается вся необходимая ему информация о состоянии задачи
для того, чтобы сделать ответный ход и перерисовать игровое поле.

   
Программа "крестики-нолики" демонстрирует три ключевых метода, которые часто используются при
разработке прикладных программ для сервера Web.

  • Как, в рамках одного приложения, выполнять различные задачи, используя информацию
    о пути для переключения на различные события свойства Action формы Web
    и вызова соответствующих обработчиков событий.
  • Как получать информацию, переданную от клиента серверу, используя для этой цели
    объект TWebRequest.
  • Как можно обойтись без хранения в памяти на сервере информации о текущем состоянии задачи,
    каждый раз передавая эту информацию от клиента.
  •    
    Для того чтобы создать приложение-игру, необходимо выполнить следующие шаги:

       
    1. Создать новое приложение Web-сервера.

       
    2. В свойстве OnAction формы Web создать два объекта OnAction;
    у одного из них свойство path будет иметь значение /desk, а у другого
    это свойство будет пусто.

       
    Ниже приведен исходный текст программного модуля. Функции и процедуры refr, drawdesk, check, mymove
    - автономные вспомогательные функции, которые внесены в программный модуль вручную.

       
    Обработчики двух событий OnAction могут быть добавлены, если выбрать нужный объект
    Action событие, переключиться в Инспекторе Объектов на страницу Events и
    щелкнуть дважды на строке OnAction.

       
    Данная программа должна находиться в виртуальном каталоге cgi-bin Web-сервера и
    будет называться CROS2.EXE.

    unit Unit1;
    
    interface
    
    uses
      SysUtils, Classes, HTTPApp;
    
    type
      TWebModule1 = class(TWebModule)
        procedure WebModule1WebActionItem1Action(Sender: TObject;
          Request: TWebRequest; Response: TWebResponse; var
          Handled: Boolean);
        procedure WebModule1deskAction(Sender: TObject;
          Request: TWebRequest; Response: TWebResponse; var  
          Handled: Boolean);
      private
      { Private declarations }
      public
      { Public declarations }
      end;
    
    var
      WebModule1: TWebModule1;
    
    implementation
    
    {$R *.DFM}
    
    function refr(desk0:string;step:integer):string;
    {Функция возвращает измененную строку URL, когда клиент ставит  
    крест в клетку, заданную по номеру во втором параметре.}
    var
      stemp:string;
    begin
      stemp:=copy(desk0,1,9);
      delete(stemp,step,1);
      insert('X',stemp,step);
      refr:='/cgi-bin/cross2.exe/desk?'+stemp;
    end;
    
    procedure drawdesk(plan:string; Response:TWebResponse; res:integer);
    {Процедура перерисовывает игровое поле. Распределение X и 0  
    задается в первом параметре. Если игра закончена, то параметр res задается  
    не равным нулю и в пустые клетки ссылки не проставляются.}
    Var
      i:integer;
      ch:string;
    begin
      Response.content:='<TITLE>Крестики-нолики</TITLE>';
      Response.content:=Response.content+'<H1>Крестики нолики</H1><HR>';
      Response.content:=Response.content+'<CENTER><TABLE BORDER=2><TR>';
      for i:=1 to 9 do
      begin
        ch:=copy(plan,i,1);
        if ch='0' then Response.content:=Response.content+'<TD>0';
        if ch='X' then Response.content:=Response.content+'<TD>X';
        if ch='_' then
          if res=0 then
            Response.content:=Response.content+'<TD><A HREF="'
                 +refr(plan,i)+' ">_'
          else Response.content:=Response.content+'<TD>_';
        if i mod 3=0 then Response.content:=Response.content+'</TR><TR>';
      end;
      Response.content:=Response.content+'</TABLE></CENTER><HR> 
         Крестики нолики V0.1';
    end;
    
    function check(plan:string; whois:integer):integer;
    {Функция возвращает 1 или -1, если выиграл клиент или машина;
    2 - если пустых клеток не осталось и 0 в других случаях.}
    Var
      A:Array[1..9] of integer;
      i,j,res:integer;
      ch:string;
    begin
      res:=0;
      j:=0;
      for i:=1 to 9 do
        begin
          ch:=copy(plan,i,1);
          if ch='0' then A[i]:=-1;
          if ch='X' then A[i]:= 1;
          if ch='_' then
             begin
               A[i]:=0;
               j:=j+1;
             end;
        end;
        for i:=0 to 2 do
          begin
            if A[3*i+1]=whois)and(A[3*i+2]=whois)and
                                     (A[3*i+3] = whois) then res:=whois;
            if (A[i+1]=whois)and(A[i+4]=whois)and
                                     (A[i+7]=whois) then res := whois;
          end;
        if (A[1]=whois)and(A[5]=whois)and(A[9]=whois) then  res:=whois;
        if (A[3]=whois)and(A[5]=whois)and(A[7]=whois) then  res:=whois;
        if (res=0)and(j=0) then res:=2;
        check:=res;
    end;
    
    function mymove(plan: string): string;
    {Функция делает ответный ход машины - ставит 0.}
    Var
      ch:string;
      A:Array[1..9] of integer;
      B:Array[1..8,1..3] of integer;
      C:Array[1..3] of integer;
      i,j,kk,closed,open:integer;
      OK:Boolean;
    begin
      j:=0;
      for i:=1 to 9 do
        begin
          ch:=copy(plan,i,1);
          if ch='0' then A[i]:=-1;
          if ch='X' then A[i]:=1;
          if ch='_' then
             begin
                A[i]:=0;
                j:=j+1;
             end;
        end;
      mymove:=plan;
      if j=0 then exit;
      OK:=false;
      for i:=1 to 3 do
        begin
          B[1,i]:=i;
          B[2,i]:=3+i;
          B[3,i]:=6+i;
         end;
       for i:=1 to 3 do
         begin
           B[4,i]:=3*(i-1)+1;
           B[5,i]:=3*(i-1)+2;
           B[6,i]:=3*(i-1)+3;
         end;
       for i:=1 to 3 do
         begin
           B[7,i]:=4*(i-1)+1;
           B[8,i]:=2*(i-1)+3;
         end;
       for i:=1 to 8 do
         begin
           C[1]:=A[B[i,1]];
           C[2]:=A[B[i,2]];
           C[3]:=A[B[i,3]];
           closed:=0;
           open:=0;
           for j:=1 to 3 do
             begin
               case C[j] of
                -1:closed:=closed+1;
                 0:open:=open+1;
               end;
              end;
            if (closed=2)and(open=1) then
              begin
                for  j:=1 to 3 do
                  begin
                    if C[j]=0 then break;
                  end;
                kk:=B[i,j];
                delete(plan,kk,1);
                insert('0',plan,kk);
                OK:=true;
                break;
              end;
         end;
       if OK=false then
         begin
           for i:=1 to 8 do
             begin
                C[1]:=A[B[i,1]];
                C[2]:=A[B[i,2]];
                C[3]:=A[B[i,3]];
                closed:=0;
                open:=0;
                for j:=1 to 3 do
                  begin
                    case C[j] of
                      1:closed:=closed+1;
                      0:open:=open+1;
                    end;
                  end;
                if (closed=2)and(open=1) then
                  begin
                    for  j:=1 to 3 do
                      begin
                        if C[j]=0 then break;
                      end;
                    kk:=B[i,j];
                    delete(plan,kk,1);
                    insert('0',plan,kk);
                    OK:=true;
                    break;
                  end;
              end;
           end;
          if OK=false then
            begin
              Randomize();
              j:=Random(9)+1;
              kk:=A[j];
              while kk<>0 do
                begin
                  j:=Random(9)+1;
                  kk:=A[j];
                end;
              delete(plan,j,1);
              insert('0',plan,j);
            end;
        mymove:=plan;
    end;
    
    procedure TWebModule1.WebModule1WebActionItem1Action
        (Sender: TObject;  Request: TWebRequest; Response: TWebResponse; 
        var Handled: Boolean);
    {Процедура создает начальное распределение игрового поля.}
    var
       desk0:string;
    begin
       desk0:='_________';
       Response.Content:='<TITLE>Крестики-нолики</TITLE>';
       Response.Content:=Response.Content+'<H1>Крестики-нолики</H1><HR>';
       Response.Content:=Response.Content+'<CENTER><TABLE BORDER=2><TR>';
       Response.Content:=Response.Content+'<TD><A HREF="  '+refr(desk0,1)+
          '">_</A><TD><A HREF=" '+
          refr(desk0,2)+'">_</A><TD><A HREF=" '+refr(desk0,3)+'">_<TR></TR>';
       Response.Content:=Response.Content+'<TD><A HREF="  '+
          refr(desk0,4)+'">_</A><TD><A HREF=" '+
          refr(desk0,5)+'">_</A><TD><A HREF=" '+refr(desk0,6)+'">_<TR></TR>';
       Response.Content:=Response.Content+'<TD><A HREF="   '+
          refr(desk0,7)+'">_</A><TD><A HREF=" '+
          refr(desk0,8)+'">_</A><TD><A HREF="  '+
          refr(desk0,9)+'">_<TR></TR>';
       Response.Content:=Response.Content+
          ' </TABLE></CENTER> <HR>Крестики-нолики V0.1';
    end;
    
    procedure TWebModule1.WebModule1deskAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    var
      s1:string;
      res:integer;
    begin
      s1:=copy(request.Query,1,9);
      res:=check(s1,1);
      drawdesk(s1,Response,res);
      if res=1 then
        begin
          Response.Content:=Response.Content+'<H2>Поздравляем, вы победили!!!</H2><HR>';
          Response.Content:=Response.Content+'<A HREF="../cross2.exe">'+
             'Чтобы начать новую игру, щелкните здесь</A><HR>';
        end
      else
        begin
          s1:=mymove(s1);
          res:=check(s1,-1);
          drawdesk(s1,Response,res);
          if res=-1 then
            begin
              Response.Content:=Response.Content+
                '<H2>Вы проиграли.В следующий раз повезет вам!</H2><HR>';
              Response.Content:=Response.Content+'<A HREF="../cross2.exe">'+
                'Чтобы начать новую игру, щелкните здесь</A><HR>';
            end
          else
            if res=2 then
              begin
                Response.Content:=Response.Content+'<H2>Игра окончена. Ничья.</H2><HR>';
                Response.Content:=Response.Content+'<A HREF="../cross2.exe">'+
                   'Чтобы начать новую игру, щелкните здесь</A><HR>';
              end;
         end;
    end;
    
    end.
    

    Текст приложения можно взять здесь.

       
    Рассмотрим работу написанной программы.

       
    Модуль Web может поддерживать работу с несколькими значениями свойства Path.
    Путь - это часть URL, следующая за именем прикладной программы, но перед строкой запроса.

       
    Например, следующий HTTP-запрос ресурса:

        http://myserver/cros2.exe/desk?___X__00_
    

    содержит путь Path= "/desk" и строку запроса "___X__00_". Таким образом,
    передавая различные значения пути прикладной программе Web, можно заставить ее
    выполнять различные действия.

       
    В рассматриваемой программе используется два значения пути. Первое значение - пустое и
    означает, что если прикладная программа вызывается без имени пути, то будет вызываться именно
    этот обработчик события OnAction. Для данной игры, это означает начало новой игры
    с пустым полем.

       
    Функция refr используется для создания точки привязки (якоря) на пустых клетках,
    в которые можно поставить "крестик" или "нолик". Ссылка содержит путь /desk и
    строку запроса, соответствующую виду игрового поля после того, как клиент поставит "крестик"
    в данную пустую клетку. Якорем в HTML называют набор тегов, который
    может выполнять роль гиперссылки, горячей связи. Таким образом, прогнозируется один шаг
    вперед для всех возможных ходов, которые клиент может сделать. Когда посылается запрос со
    значением пути /desk, то прикладная программа вызывает обработчик события OnAction,
    связанный с этим путем.

       
    Обработчик события для пути /desk проверяет, не завершилась ли на данном шаге игра победой
    игрока. Если клиент выиграл игру, выводится соответствующее сообщение. В противном случае,
    свой ход делает компьютер, опять проводится проверка, не выиграл ли на этот раз компьютер,
    игровое поле перерисовывается и в случае победы или окончания игры вничью выводится соответствующий текст.
    Пример окна этого приложения показан на рисунке 1.


    Рис.1. Игра в Крестики-нолики

       
    Строку запроса, передаваемую обработчику события PathInfo, поставляет объект
    TWebRequest. Этот объект содержит всю информацию, которую клиент (обычно это Web-браузер)
    послал серверу. Далее, работа приложения состоит в том, чтобы из полученной от пользователя через
    этот объект информации отобрать нужную, выполнить обработку, предписываемую логикой задачи и
    отослать информацию обратно пользователю через объект TWebResponse.

       
    На следующем шаге мы рассмотрим создание Web-браузера.



    Вы можете оставить комментарий, или Трекбэк с вашего сайта.

    Оставить комментарий