На этом шаге мы рассмотрим создание простого приложения для Web-сервера: игра "Крестики-нолики".
Win32 GUI-приложения являются прикладными программами, управляемыми событиями. Компоненты и формы таких программ имеют обработчики событий, которые отвечают на различные действия, производимые пользователем. В случае прикладной программы Web, клиент не поддерживает постоянное соединение с Web-сервером; следовательно, каждая транзакция является для сервера независимым событием. При таком подходе возникает проблема: нет сведений относительно предшествующих событий. Каждая Web-транзакция не знает результатов предыдущих транзакций. Выход из создавшегося положения рассмотрим на примере следующей игры.
При создании стандартного игрового приложение Windows, конфигурация игрового поля сохраняется в некоторой структуре, так как каждый раз, когда игрок делает ход, расположение элементов на поле изменяется. Эту задачу несколько труднее решить для прикладной программы Web-сервера, потому что в этом случае нужно применить некую замену для отсутствующей постоянной памяти. В разрабатываемом приложении эта проблема решается следующим образом: каждый раз, когда клиент делает новый ход, серверу посылается вся необходимая ему информация о состоянии задачи для того, чтобы сделать ответный ход и перерисовать игровое поле.
Программа "крестики-нолики" демонстрирует три ключевых метода, которые часто используются при разработке прикладных программ для сервера Web.
Для того чтобы создать приложение-игру, необходимо выполнить следующие шаги:
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-браузера.