Яндекс цитирования
 

Игры на Delphi

 

[ В начало раздела ]

Пятнашки

дата публикации 24 ноября 2000г.

Практику разработки игр начнем с одной из самых простых логических игр - "Пятнашки". Кто не знает такую или подзабыл, напомню. На поле 4x4 располагаются 15 фишек с числами 1-15. Одно поле - пустое. Именно из-за него игра стала такой популярной. Цель - собрать разобранную головоломку за минимум ходов.

Теперь перейдем к делу. Решим, как хранить информацию об игровом поле и конкретной ячейке. Из правил игры выяснилось, что каждая фишка должна описываться параметрами: 1. Игровое число (1-15); 2. Текущая поциция на поле (1-16); Кроме того, игру мы будем делать под Windows, поэтому выберем подходящий визуальный элемент, например, обычную кнопку (TButton).

Игровое поле - массив из фишек. Тогда получим следующие описания типов:

type
  { храним информацию о фишке }
  APlace = record
    Pos: integer;     // текущая позиция
    Val: integer;     // значение
    btnPtr: TButton;  // фишка в виде кнопки
  end;
  { описываем массив фишек }
  APlaceArray = array[1..1] of APlace;

Теперь перейдем к проектированию игровой формы. Как обычно - создаем новый проект. Назовем - Puzzle. Разместим на форме панель (Panel1) - это наше игровое поле, метки Label1 - для отображения числа ходов.

В процессе игры нам потребуются следующие глобальные переменные:

  shuffled: boolean;         { Истина, если головоломка перемешана }
  numRows, numCols: integer; { размеры головоломки (4x4) }
  numShuffles: integer;      { число ходов для перемешивания }
  numMoves: integer;         { число выполненных ходов }

Теперь одно из самых важных этапов в проектировании - перечисление необходимых нам процедур и функций. Проанализируем игру, если бы играли сами.

  • 1. Создание головоломки - makePuzzle;
  • 2. Перемешивание головоломки - shuffle;
  • 3. Передвижение фишки на поле - doClick, doSwitch;
  • 4. Проверка на завершение головоломки - checkCompleted;
  • 5. Разрушение головоломки - destroyPuzzle.
Вот их и опишем в части public формы:
  procedure makePuzzle;    { создание головоломки }
  function doClick(btnNum: integer): boolean;   { нажатие на фишке }
  procedure checkCompleted; { проверка завершения игры }
  procedure doSwitch(i: integer; j: integer); { двигаю фишку }
  procedure destroyPuzzle;  { разрушаю головоломку }

Теперь начнем по-порядку. В обработчике OnCreate формы займемся созданием нашей головоломки.

procedure TForm1.FormCreate(Sender: TObject);
var
  i: integer;
begin
  numRows:= 4;  { размер по-умолчанию }
  numCols:= 4;
  numShuffles:= 160; { число ходов при перемешивании головоломки }
  numMoves:= 0;
  Label1.Caption:= inttostr(numMoves);
  makePuzzle;  { создаем головоломку }
end;

Теперь стоит создать собственную процедуру makePuzzle, отвечающую за создание игрового поля головоломки. Игровое поле будем создавать динамически, выделяя память. Но перед этим укажем глобальную переменную-указатель на игровое поле

pzlState : ^APlaceArray;  { указатель на динам.массив элементов APlaceArray }

Процедура будет автоматически изменять размер формы в зависимости от размера нашего игрового поля.

procedure TForm1.makePuzzle;
var
  panelW, panelH: integer;
  btnW, btnH: integer;
  btnL, btnT: integer;
  i: integer;
begin
  { изменяю размер формы }
  Form1.Height:= numRows*32 + 50;
  Form1.Width:= numCols*20 + 50;
  panelW:= Panel2.Width - 4;
  panelH:= Panel2.Height - 6;
  { выделяю память для головоломки }
  GetMem(pzlState, numRows*numCols*sizeof(APlace));
  { вычисляю размер каждой кнопки }
  btnW:= panelW div numCols;
  btnH:= panelH div numRows;
  for i:= 1 to numRows*numCols do
  begin
    { расчитываю местоположение кнопки }
    btnL:= 2 + ((i-1) mod numCols) * btnW;
    btnT:= 2 + ((i-1) div numCols) * btnH;
    { создаю, размещаю, и показываю кнопки }
    pzlState^[i].BtnPtr:= TButton.Create(Self);
    with pzlState^[i].BtnPtr do
    begin
      Parent:= Self;
      SetBounds(btnL, btnT, btnW, btnH);
      Caption:= inttoStr(i);
      onClick:= Button1Click;  // устанавливаю обработчик
    end;
    pzlState^[i].Pos:= i;
    pzlState^[i].Val:= i; { начальное значение такое же как позиция }
  end;
  { пустая фишка - значение=0, невидима }
  pzlState^[numRows*numCols].Val:= 0;
  pzlState^[numRows*numCols].BtnPtr.Visible:= FALSE;
  shuffled:= FALSE;
end;

Отметим корректное создание визуальных объектов. Также стоит поступать в любых случаях и в дальнейшем.

Головоломку создали, теперь создадим процедуру ее разрушающюю - destroyPuzzle. Она должна вызываться, когда форма закрывается. Освобождаю динамически выделенную память.

procedure TForm1.destroyPuzzle;  
var
  i: integer;
begin
  for i:= 1 to numRows*numCols do
    pzlState^[i].BtnPtr.Free;
  FreeMem(pzlState, numRows*numCols*sizeof(APlace));
end;

Из фрагмента кода makePuzzle можно отметить, что мы для каждой фишки-кнопки задали обработкик события OnClick. Основная цель обработчика - перемещение выбранной фишки на поле (если это можно) и отслеживание момента завершения игры.

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  { находим, какая кнопка была нажата }
  for i:= 1 to numRows*numCols do
    if Sender = pzlState^[i].btnPtr then
      doClick(i);  { перемещаем фишку, если возможно }
  if shuffled then
    checkCompleted; { следим за завершением головоломки }
end;

Передвижение выбранной фишки. Заметьте, что это функция типа Boolean. Как увидимниже это необходимо для процедуры перемешивания головоломки. А что касается самой функции, то в ней мы анализируем возможность передвижения фишки, и если передвинуть удается - в процедуре doSwitch мы выполняем перемещение.

function TForm1.doClick(btnNum: integer): boolean;
begin
  { move the button (this is accomplished by swapping 'Val')
    if the empty space is adjacent }
  doClick:= FALSE;
  { двигаем вверх, если возможно }
  if (btnNum > numCols) and
     (pzlState^[btnNum-numCols].Val = 0) then
  begin
    doSwitch(btnNum, btnNum-numCols); { двигаем вверх }
    doClick:= TRUE;                   { передвижение совершено }
    exit;
  end;
  { двигаем влево, если возможно }
  if ((btnNum mod numCols) <> 1) and
      (pzlState^[btnNum-1].Val = 0) then
  begin
    doSwitch(btnNum, btnNum-1);  { двигаем влево }
    doClick:= TRUE;              { передвижение совершено }
    exit;
  end;
  { двигаем вправо, если возможно }
  if ((btnNum mod numCols) <> 0) and
      (pzlState^[btnNum+1].Val = 0) then
  begin
    doSwitch(btnNum, btnNum+1); { двигаем вправо }
    doClick:= TRUE;             { передвижение совершено }
    exit;
  end;
  { двигаем вниз, если возможно }
  if (btnNum <= (numRows-1)*numCols) and
     (pzlState^[btnNum+numCols].Val = 0) then
  begin
    doSwitch(btnNum, btnNum+numCols); { двигаем вниз }
    doClick:= TRUE;                   { передвижение совершено }
    exit;
  end;
end;

Передвигаем фишку на пустое место. Для этого просто заменяем значения 'Val' и Visible передвигаемых фишек.

procedure TForm1.doSwitch(i: integer; j: integer);
var
  tmp: integer;
  isVisible: boolean;
begin
  tmp:= pzlState^[i].Val;
  pzlState^[i].Val:= 0;      { фишка становится пустой }
  isVisible:= pzlState^[i].btnPtr.Visible;
  pzlState^[i].btnPtr.Visible:= FALSE;
  pzlState^[j].Val:= tmp;    { показываю фишку на новом месте }
  pzlState^[j].btnPtr.Visible:= isVisible;
  pzlState^[j].btnPtr.Caption:= inttoStr(tmp);
  inc(numMoves);
  Label1.Caption:= inttostr(numMoves);
end;

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

procedure TForm1.Shuffle1Click(Sender: TObject);
var
  i: integer;
  pick: integer;
begin
  randomize;
  { все кнопки - невидимые }
  for i:= 1 to numRows*numCols do
    pzlState^[i].BtnPtr.Visible:= FALSE;
  i:= 0;
  { перемешиваю выбранное число раз }
  while i < numShuffles do
  begin
    { передвигаю случайную фишку. 
      Если передвинул, увеличиваю счетчик }
    pick:= random(numRows*numCols) + 1;
    if pzlState^[pick].Val <> 0 then
      if doClick(pick) then
        inc(i);
  end;
  { восстанавливаю состояние фишек }
  for i:= 1 to numRows*numCols do
    if pzlState^[i].Val <> 0 then
      pzlState^[i].BtnPtr.Visible:= TRUE;
  shuffled:= TRUE;
  numMoves:= 0;
  Label1.Caption:= inttostr(numMoves);
end;

{ показываю поздравительное сообщение, если собрали правильно головоломку } procedure TForm1.checkCompleted; var i: integer; ret: integer; begin { условие правильно собранной головоломки (Val = номер фишки) } for i:= 1 to numRows*numCols-1 do if pzlState^[i].Val <> i then exit; { показываю поздравительное сообщение, если собрали правильно головоломку } ret:= Application.MessageBox('Поздравляю! Ты собрал головоломку.', 'Пятнашки', 0); shuffled:= FALSE; end;

Эту игру Вы сможете самостоятельно доработать, добавить форму настройки параметров, списком рекодсменов игры.

Подробнее - в исходниках.

P.S. Аналог рассмотренного кода и некоторые подходы в программировании для Delphi в свое время дали мне большой заряд энергии на многие года. Программирование игр - стало моим хобби.

 

© Долгов Сергей

 

[ В начало раздела ]


 

 

Все для web-дизана!!! Бард-Путеводитель Много Всего CGI-Гид. Лучшие скрипты... WDH - WebDesignHelp - CGI, JAVA, APPLETS, TOP100! Раскрутка, увеличение посещаемости и индекса цитируемости в поисковых системах.

© 2000-2002 Долгов Сергей

dolgov_sergei@mail.ru

X