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

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

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

Пример программы игры в "крестики-нолики"

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

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

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

    Для того чтобы создать приложение-игру, необходимо выполнить следующие шаги:

    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-браузера.




Предыдущий шаг Содержание Следующий шаг