В прошлой статье цикла "Кладовая программиста" мы вместе с вами создали довольно мощный текстовый редактор. Вы наверняка самостоятельно внесли в него не одно изменение, но надо двигаться дальше.
 Хочется сделать что-нибудь эдакое, но вот что? Не писать же сразу Doom IV? Это нам пока не по силам, но можно сделать что-то более простое. Сегодня мы с вами напишем настоящую игру. Игру "Угадай число". Разочарованные возгласы? Конечно, игра очень проста и стара как мир, но на ее примере вы поймете, как вообще делаются игры. Мы ведь будем писать не простую угадайку, какую обычно встраивают в сотовые телефоны, а угадайку продвинутую. Игру будет вести виртуальный ведущий. И пусть мы пока не в силах дать ему голос, зато обаяние — проще простого. Наша угадайка будет вести рейтинги игроков и сохранять их в отдельном файле.
 Планов громадье
 Рука потянулась к заветному значку "Дельфи" и уже готова совершить обряд двойного клика... Постойте! Любой профессиональный игроразработчик прежде всего пишет дизайн-документ. Это важнейший этап разработки, и если им пренебречь, проект вряд ли доживет до релиза. Мы не будем писать полноценный диз-док для "Угадайки", но в общих чертах наметить возможности стоит.
 Основное окно будет состоять из двух логических зон. Вверху мы поместим картинку ведущего. Вы видели, как "говорят" герои в комиксах? Вот так же будет говорить и наш персонаж. Мы сделаем пять стандартных фраз, которые полностью описывают геймплей. При старте новой игры ведущий говорит: "Я загадал число. Отгадай-ка его!". После этого игрок вводит свой вариант в текстовое поле ввода под персонажем и нажимает кнопку, которую мы поместим рядом. Шутки ради пусть название этой кнопки будет всегда разным, например:
 "Хм... может быть, это?"
 "Я знаю, твое число — вот это!"
 "Я разгадал тебя"
 "Ты хитрый, но я хитрее"
 "Попробуем вот так..."
 Если игрок отгадал, ведущий говорит: "Ты угадал! Молодец! Сыграем еще раз?". Если игрок не угадал, ведущий подсказывает: "Мое число больше" или "Мое число меньше". Во время игры подсчитывается число попыток, которые понадобились игроку, чтобы отгадать число. Если игрок побил рекорд и отгадал за минимальное число попыток, появляется таблица рекордов и предложение ввести свое имя. Таблицу рекордов можно также вызвать в любое время с помощью специальной кнопки. Геймплей мы расписали, на главной форме не хватает только кнопок Новая игра, Рекорды и Выход.
 Примерный план игры готов. Пора приступать к реализации.
 С пылу, с жару
 Кликните дважды по иконке Дельфи. Утилита запустится и откроет новый проект. Сохраните его. Главная форма у нас будет высокой и узкой, поэтому измените ее размеры соответствующим образом. Через Object Inspector установите свойство Caption формы в "Угадайке". Можно начинать проектировать саму форму. Но до этого ее необходимо настроить. Что будет, если игрок вздумает развернуть окно на весь экран? Все элементы управления (кнопки, рисунок нашего ведущего, поле ввода) окажутся в верхнем левом углу формы, а остальное пространство будет пустым. Зрелище не для слабонервных. Такие огрехи выдают новичков и неряшливых программистов. Исправим эту оплошность. Кроме того, игроку надо запретить мышкой изменять размеры окна (по умолчанию эта возможность включена).
 Изменим тип границы окна. Откройте свойство формы BorderStyle: вместо bsSizeable поставьте bsSingle. Разверните список подсвойств BorderIcons и свойство biMaximize установите в False. В прошлый раз (в предыдущей статье цикла) вы наверняка заметили, что после запуска программы окно оказывается в том месте, где вы оставили форму при проектировании. Это не очень удобно. Давайте сделаем так, чтобы каждый раз при запуске игры форма оказывалась строго в центре экрана. Для этого достаточно установить свойство Position формы в poDesktopCenter.
 Приступим к наполнению формы. Начнем с нижней части. Внизу формы поставьте три кнопки одинакового размера так, чтобы они тесно прилегали друг к другу и к нижней границе окна. В их свойства Caption запишите соответственно Новая игра, Рекорды... и Выход. Обратите внимание, что Рекорды мы написали с тремя точками. По правилам Windows это значит, что после клика по кнопке перед пользователем откроется новое окно. Чуть выше поместите поле ввода (TEdit), а рядом — кнопку без надписи (мы будем выбирать ее случайным образом). Не забудьте очистить поле ввода в свойстве Text.
 Займемся графикой. Нашим ведущим станет симпатичный вампир за авторством Алексея Макаренкова. Вы можете нарисовать какого-нибудь персонажа сами, ну а если рисуете вы плохо, то можно воспользоваться отсканированной фотокарточкой или позаимствовать рисунок вампира с нашего компакта.
 Поместите на самое видное место нашей формы картинку (компонент TImage на вкладке Additional) и с помощью маленькой кнопочки с тремя точками напротив свойства Picture загрузите pic.jpg с вампиром-ведущим. Но вот беда: не понимает "Дельфи" формат jpg. Это надо исправить. Добавьте в строчку uses модуль jpeg. Теперь строчка uses выглядит примерно так:
 uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, jpeg;
 Пора загружать картинку. Но вампир у нас статичный. Как же он будет разговаривать с игроком? Разговаривать он будет так же, как персонажи "разговаривают" в комиксах, с помощью воздушных шариков-фраз. Нарисуйте картинки всех возможных фраз, или возьмите их с
нашего диска. Идея такова: мы помещаем поверх картинки ведущего картинки всех его фраз и делаем их невидимыми; как только ведущий что-то говорит, соответствующая фраза становится видимой. Если до этого была видимой другая фраза, она становится невидимой.  
 Поместите на форму картинки всех фраз. Не забудьте поставить свойство Transparent этих картинок в true, а свойство Visible в значение False, ведь с самого начала все картинки должны быть не видны. Фразы "Я загадал число..." и "Ты угадал..." размещаем сверху по центру, фразу "Мое число больше" — слева, а фразу "Мое число меньше" — справа.
 Вам не надоел серо-голубой однообразный интерфейс Windows? Давайте хоть нашу игру сделаем разноцветной. Неплохо бы покрасить форму в какой-нибудь цвет, например, в желтый. Просто установите свойство Color формы в clYellow. Поле ввода можно покрасить в небесный цвет. Для этого установите его свойство в clAqua. Чтобы придать некоторый стиль и необычность полю ввода, его стоит сровнять с формой; для этого установите свойство BorderStyle (поля ввода, а не формы) в bsNone.
 Давайте сделаем поле ввода активным по умолчанию. Поставьте у формы свойство ActiveControl в Edit1, и при каждом запуске курсор будет сразу появляться в поле ввода.
 Необходимо изменить шрифт всех четырех кнопок и поля ввода на что-то более подходящее. За шрифты любых элементов отвечает свойство Font. Можно изменять шрифт, регулируя подсвойства этого комплексного параметра, но лучше и проще щелкнуть на маленькую кнопочку с тремя точками напротив надписи Font в объектном инспекторе. Перед вами появится большое окно, в котором есть возможность изменить гарнитуру, начертание, кегль и цвет надписи.
 Договоримся о логических именах объектов, чтобы дальше по тексту не называть все время кнопки русскими именами. Кнопка Новая игра будет — Button1, Рекорды... — Button2, Выход — Button3, поле ввода — Edit1, картинка ведущего — Image1, картинка "Я загадал число..." — Image2, картинка "Ты угадал..." — Image3, картинка "Мое число больше" — Image4, картинка "Мое число меньше" — Image5.
 10240 по Фаренгейту
 Интерфейс готов. Пора программировать игровую модель. Осмотрим форму. Первое, что бросается в глаза, — безымянная кнопка. На этой кнопке каждый раз должны меняться надписи. Мы можем прописать изменение надписи во всех местах, где это требуется, но зачем усложнять себе жизнь? Давайте создадим отдельную процедуру, которая только тем и будет заниматься, что менять надписи на кнопке. Переключайтесь на окно редактора кода и после слова implementation пишите:
 procedure ChangeCaption;
 begin
 case random(5) of
 0:Form1.Button4.Caption:='Хм... может быть, это?';
 1:Form1.Button4.Caption:='Я знаю, твое число — вот это!';
 2:Form1.Button4.Caption:='Я разгадал тебя';
 3:Form1.Button4.Caption:='Ты хитрый, но я хитрее';
 4:Form1.Button4.Caption:='Попробуем вот так...';
 end;
 end;
 Разберем процедуру. Заголовок и объявление процедуры — понятны. Дальше идет конструкция case A of B [C,D...]. Это конструкция выбора какой-то команды в зависимости от состояния переменной A. Если конструкция random(5) будет равна, скажем, единице, выполнится команда Form1.Button4.Caption:='Я знаю, твое число — вот это!'. Random(N) — функция, которая возвращает случайное число из промежутка от нуля до N-1. Дальше мы свойству Caption объекта Button4 (наша кнопка) каждый раз присваиваем новое значение. Но вот почему перед Button 4 стоит какой-то объект Form1? Это — форма. Ведь кнопка принадлежит форме, а значит, объект Button4 можно рассматривать как свойство объекта Form1.
 Внимательный читатель спросит: "А почему мы раньше никогда Form1 перед объектами формы не указывали, а сейчас указали?". Дело в том, что есть процедуры и функции классов (они, если вы еще не забыли, называются методами), а есть просто процедуры и функции. Сейчас перед вами просто процедура. А в тестовом редакторе мы писали код только в обработчиках событий формы. Обработчик события — процедура класса. Мы везде должны были бы писать перед объектом имя более глобального объекта, к которому он принадлежит. Однако, если мы пишем код в методе глобального объекта, его идентификатор нужно опустить, что мы с успехом и делали. Процедура ChangeCaption не входит ни в какой класс, она сама по себе. Поэтому в ней надо указывать полный путь до необходимого свойства или объекта. Немного сложно, но, попрактиковавшись, вы все поймете.
 После первого запуска название кнопки должно смениться случайным образом. Запишем команду запуска этой процедуры в обработчик события формы OnCreate. Создать обработчик можно или через вторую вкладку объектного инспектора, или просто щелкнув два раза на пустом пространстве формы. Пишите между begin и end всего одну строчку:
 ChangeCaption;
 Запустите игру. После запуска название кнопки меняется. Что и требовалось сделать. Попробуйте запустить игру несколько раз. Странно, название кнопки всегда одно и то же. Дело все в принципе действия генератора случайных чисел. На самом деле, числа он выдает не случайные, а псевдослучайные, основанные на специальной таблице случайных чисел. Каждый раз при вызове берутся числа из таблицы в одном и том же порядке, поэтому вся случайность теряется. Чтобы это обойти, перед запуском программы надо один раз сбросить указатель таблицы случайных чисел с помощью команды Randomize. Поместить ее лучше всего в обработчик OnCreate, ведь он запускается самым первым. В итоге обработчик OnCreate у вас будет выглядеть так:
 procedure TForm1.FormCreate(Sender: TObject);
 begin
 Randomize;
 ChangeCaption;
 end;
 Попробуйте запустить игру несколько раз. Теперь надписи будут разными.
 Напишем процедуру, которая заставит ведущего говорить. В качестве параметра мы будем передавать процедуре целое число, которое обозначает нужную фразу. Вот полный текст этой процедуры:
 procedure Say(text:integer);
 begin
 Form1.Image2.Visible:=false;
 Form1.Image3.Visible:=false;
 Form1.Image4.Visible:=false;
 Form1.Image5.Visible:=false;
 case text of
 0:Form1.Image2.Visible:=true;
 1:Form1.Image3.Visible:=true;
 2:Form1.Image4.Visible:=true;
 3:Form1.Image5.Visible:=true;
 end;
 end;
 Коротко пробежимся по тексту. Сначала мы делаем невидимыми все картинки фраз. Потом с помощью уже знакомой вам конструкции case в соответствии со значением переменной-параметра text делаем видимой нужную картинку-фразу.
 Тонкости геймплея
 Когда все подготовительные действия выполнены, нужно приступать к проработке геймплея. Нажимаем на кнопку Новая игра; ведущий должен поприветствовать игрока. Для этого создайте обработчик события нажатия на кнопку. В нем пишите:
 say(0);
 n:=random(100);
 c:=0;
 В переменную n мы заносим случайное число, которое должен отгадать игрок. Переменная с — счетчик попыток. Эти переменные должны быть глобальными, потому что мы их будем использовать в нескольких процедурах и обработчиках. Объявите процедуру глобальной в разделе var:
 n:integer;
 c:integer;
 Теперь игрок вводит свой вариант ответа в поле Edit1 и нажимает кнопку Button4. В ее обработчике мы должны сравнить вариант игрока со значением в переменной n и вывести на экран соответствующую фразу. Обработчик кнопки Button4 будет выглядеть так:
 ChangeCaption;
 if strtoint(edit1.text)>n then begin
 say(3);
 c:=c+1;
 end;
 if strtoint(edit1.text)<n then begin
 say(2);
 c:=c+1;
 end;
 if strtoint(edit1.text)=n then begin
 say(1);
 if c<maxc then form2.showmodal;
 end;
 Edit1.Text:='';
 Этот обработчик достаточно сложный, поэтому его разберем детально. Первая команда — смена надписи на кнопке Button4. Потом начинаются проверки. Взгляните на первое условие. Переменная n сравнивается с конструкцией strtoint — это функция, которая преобразует строковую переменную в целочисленную и возвращает эту переменную. Данной функцией мы конвертируем значение edit1.text. В нем содержится строчка, которую ввел игрок. Зачем нужно что-то конвертировать? В "Дельфи" есть несколько типов переменных, в частности, целые числа и строки. Переменные разных типов хранят данные в разных форматах и поэтому несовместимы между собой. Чтобы сравнивать две переменные разных типов, надо одну из них привести к типу другой с помощью специальных функций, таких, как strtoint.
 Если то, что ввел игрок, больше значения переменной n, то на экран с помощью процедуры say выводится информация. После этого увеличивается на единицу счетчик попыток. Следующая проверка почти такая же, с той лишь разницей, что теперь предположение игрока оказывается меньше загаданного числа.
 Самое интересное условие — третье. Оно выполняется, если игрок угадал число. Ведущий сообщает об этом (команда say(1)). Потом мы проверяем, вдруг игрок отгадал число за рекордное количество попыток (не забывайте, что чем меньше, тем лучше, а не наоборот). Рекорд должен быть записан в глобальную переменную maxc (не забудьте ее объявить). Процедуру для загрузки значения в maxc мы напишем потом. А пока просто считайте, что в этой переменной содержится предыдущий рекорд. Если рекорд побит, мы должны запросить имя игрока, дабы записать его в таблице рекордов, и отобразить данную таблицу. Наконец, мы очищаем поле ввода (присваиваем его свойству text нулевую строку).
 Сделаем новую форму, которая будет запрашивать у игрока его имя. Щелкните на кнопку New Form, которая находится на панели инструментов. Появится новая форма. Надо ее "причесать". В верхнем левом углу разместите поле ввода, а под ним — кнопку с надписью ОК. В свойство Caption формы пишите что-то вроде "Вы побили рекорд! Введите свое имя:". Уменьшите форму так, чтобы все выглядело красиво. С помощью свойств запретите изменение размеров формы, очистите содержимое поля ввода.
 Вернемся ненадолго к первой форме и обработчику события кнопки Button4. Метод формы showmodal выводит скрытую форму на экран (у любого приложения по умолчанию все формы, кроме главной, скрыты). Есть одна тонкость: этот метод выводит форму со статусом модальной, то есть как диалог. До тех пор, пока форма не закроется, пользователь не получит доступа к программе. Например, он не сможет переключиться на главное окно программы. Можно использовать столь полезную особенность.
 Стоит задуматься о том, где и как мы будем хранить данные о лучшем игроке. Создадим файл в папке игры и назовем его record.rcd. Это будет обыкновенный текстовый документ, в первой строчке которого будет храниться имя рекордсмена, а во второй — число его попыток. Для начала искусственно наполните файл содержимым, например:
 Костя
 12
 Создайте форму, которая будет таблицей рекордов. Но там будет храниться всего один рекорд.
Расширить список рекордов до десяти — это ваше домашнее задание. Напишите в заголовке формы "Рекорд", сделайте форму маленькой и "причешите", как мы уже поступали с другими формами. Поместите на форму компонент TLabel с палитры Standart, который является простой на дписью. В его свойстве Caption пишите "Рекордсмен". Рядом ставьте еще одну метку (Label2), но оставьте ее пустой. Сюда мы запишем имя рекордсмена. Ниже ставьте еще одну метку и пишите в ней "Его рекорд:", рядом — пустую метку Label4. Ниже помещайте кнопку с надписью OK. Присвойте надписям и кнопке шрифт покрасивее. В обработчике кнопки пишите:
 modalresult:=1;
 hide;
 Modalresult нужно присвоить любое число, кроме нуля, чтобы убрать модальность.
 Где мы будем загружать данные о рекордсмене, а где мы их будем сохранять? Таблица рекордов будет появляться два раза: при нажатии на соответствующую кнопку и после появления нового рекордсмена. Не будем изобретать велосипед, и создадим только одну процедуру (а точнее, обработчик), который будем вызывать из двух разных участков кода. Переключайтесь на первую форму и дважды кликайте на кнопке Рекорды. Откроется обработчик события. Вот его полный текст:
 procedure TForm1.Button2Click(Sender: TObject);
 var
 f:textfile;
 nm:string;
 begin
 assignfile(f,extractfilepath(application.exename)+'record.rcd');
 reset(f);
 readln(f,nm);
 readln(f,maxc);
 closefile(f);
 form3.showmodal;
 end;
 Во-первых, мы выводим форму с рекордами на экран. Во-вторых, открываем файл record.rcd для чтения как текстовый. Потом читаем из файла первую строчку и записываем ее значение в текст метки 2 на форме рекордов. Это имя рекордсмена. Непосредственно сделать это не получится, поэтому приходится использовать вспомогательную переменную nm. В переменную maxc мы записываем значение второй строчки файла. Потом это же значение записываем в таблицу рекордов. И, наконец, закрываем файл.
 Обратите внимание, что командой assignfile мы как бы присваиваем файл значению переменной f. На самом деле f — ссылка на файл. Теперь мы сможем обращаться к этому файлу с помощью команд, подобных readln. Команда reset открывает файл только для чтения. Если бы вы попытались записать что-то в файл, возникла бы ошибка. Почему в команде assignfile мы не написали просто имя открываемого файла, а добавили спереди какую-то странную конструкцию? Мы могли сделать и так, но срабатывала бы команда не всегда. Дело в том, что при старте программы текущая папка назначается ее рабочей папкой, и на нее перемещается специальный указатель файловой системы Windows. Если программа будет обращаться к какому-то файлу по относительному пути, он будет трактоваться относительно этого указателя.
 Все вроде бы работает. Но в процессе работы программы указатель может переместиться в другое место. Например, Windows потребуется обратиться к какому-то своему драйверу или записать что-нибудь в файл подкачки. Текущей становится другая папка, и относительный путь уже не сработает. Поэтому желательно указывать именно абсолютный путь к файлу. Application.exename возвращает полный путь до исполняемого файла программы, а команда extractfilepath "выкусывает" из этого пути имя файла программы, и остается только путь до папки. К этому пути мы прибавляем имя нужного файла, и получаем абсолютный путь, который работает всегда.
 Переменная maxc нам нужна еще до появления окна рекордов. С ней мы сравниваем количество попыток игрока, если он отгадал число. Мы должны загрузить значение этой переменной при запуске игры. Добавим соответствующие строки в событие OnCreate:
 procedure TForm1.FormCreate(Sender: TObject);
 var
 f:textfile;
 nm:string;
 begin
 Randomize;
 ChangeCaption;
 assignfile(f,extractfilepath(application.exename)+'record.rcd');
 reset(f);
 readln(f,nm);
 Form3.Label2.Caption:=nm;
 readln(f,maxc);
 Form3.label4.caption:=inttostr(maxc);
 closefile(f);
 end;
 Теперь можно вернуться к кнопке OK второй формы. Здесь мы должны сохранить в файл данные нового рекордсмена, закрыть текущую форму и вывести форму рекордов. Вот текст обработчика этой кнопки:
 procedure TForm2.Button1Click(Sender: TObject);
 var
 f:textfile;
 begin
 hide;
 modalresult:=1;
 assignfile(f,extractfilepath(application.exename)+'record.rcd');
 rewrite(f);
 writeln(f,Edit1.text);
 maxc:=c;
 writeln(f,maxc);
 closefile(f);
 form1.Button2Click(nil);
 end;
 Сначала мы скрываем форму и убираем ее модальность. Потом связываем файловую переменную f с файлом рекордов. С помощью команды rewrite мы открываем файл для записи, причем, если в файле что-то до этого было, все сотрется. Чтобы не стирать содержимое, а добавлять новые строки в конец файла, надо применять команду append. В первую строчку файла мы пишем только что введенное игроком имя, а затем присваиваем переменной maxc новый рекорд, и тут же пишем его во вторую строчку файла. Потом файл закрываем и... И эмулируем нажатие на кнопку Рекорды... на главной форме. Не удивляйтесь, такая запись вполне возможна. Ведь обработчик события — это процедура, а значит, ее несложно вызвать. Что мы и сделали. Параметр Sender в теле обработчика мы не использовали, поэтому его можно забить универсальным объектным нулем — nil. Его еще называют указателем на пустоту.
 Сохраните проект и две только что созданные формы, и запустите компиляцию. "Дельфи" несколько раз спросит разрешения добавить один модуль в раздел uses другого модуля. Все время подтверждайте. Игра запустится. Можете играть.
 Когда эйфория успеха пройдет, вы заметите, что кое-что забыли. Кнопка Выход не работает. Чтобы исправить оплошность, достаточно в обработчике OnClick данной кнопки написать:
 Application.Terminate;
 Application — это глобальный объект, который указывает на вашу программу. У него есть несколько полезных свойств и методов, которыми мы уже не раз пользовались. Например, ExeName и Terminate. Еще Application обрабатывает сообщения, организует работу окон, распределяет потоки.
 Последние штрихи
 Что еще можно добавить к игре? Окошко About, где вы гордо напишете свое имя и нарисуете логотип. В игре практически не проверяются действия пользователя, нет "защиты от дурака". Например, если ввести в Edit1 не число, а какое-нибудь слово, возникнет ошибка и игра вылетит. Поэтому вводимые игроком числа надо проверять. Нет управления последовательностью действий. Вместо того, чтобы нажать на кнопку Новая игра, игрок может сразу же нажать на кнопку Button4. Естественно, игра поведет себя неправильно. Может повредиться файл рекордов, опять возникнет ошибка. Все это, конечно, мелочи, но именно из таких мелочей складывается хорошее отношение пользователей к программистам и компании, выпустившей игру.
 * * *
 В следующей статье мы сделаем настоящий медиа-плеер, который сможет проигрывать музыкальные и видео-файлы многих форматов, просматривать картинки...
 Если у вас что-то не получилось, не переживайте. Полные исходники игры в число вы можете взять с нашего компакт-диска.
 Хочется сделать что-нибудь эдакое, но вот что? Не писать же сразу Doom IV? Это нам пока не по силам, но можно сделать что-то более простое. Сегодня мы с вами напишем настоящую игру. Игру "Угадай число". Разочарованные возгласы? Конечно, игра очень проста и стара как мир, но на ее примере вы поймете, как вообще делаются игры. Мы ведь будем писать не простую угадайку, какую обычно встраивают в сотовые телефоны, а угадайку продвинутую. Игру будет вести виртуальный ведущий. И пусть мы пока не в силах дать ему голос, зато обаяние — проще простого. Наша угадайка будет вести рейтинги игроков и сохранять их в отдельном файле.
 Планов громадье
 Рука потянулась к заветному значку "Дельфи" и уже готова совершить обряд двойного клика... Постойте! Любой профессиональный игроразработчик прежде всего пишет дизайн-документ. Это важнейший этап разработки, и если им пренебречь, проект вряд ли доживет до релиза. Мы не будем писать полноценный диз-док для "Угадайки", но в общих чертах наметить возможности стоит.
 Основное окно будет состоять из двух логических зон. Вверху мы поместим картинку ведущего. Вы видели, как "говорят" герои в комиксах? Вот так же будет говорить и наш персонаж. Мы сделаем пять стандартных фраз, которые полностью описывают геймплей. При старте новой игры ведущий говорит: "Я загадал число. Отгадай-ка его!". После этого игрок вводит свой вариант в текстовое поле ввода под персонажем и нажимает кнопку, которую мы поместим рядом. Шутки ради пусть название этой кнопки будет всегда разным, например:
 "Хм... может быть, это?"
 "Я знаю, твое число — вот это!"
 "Я разгадал тебя"
 "Ты хитрый, но я хитрее"
 "Попробуем вот так..."
 Если игрок отгадал, ведущий говорит: "Ты угадал! Молодец! Сыграем еще раз?". Если игрок не угадал, ведущий подсказывает: "Мое число больше" или "Мое число меньше". Во время игры подсчитывается число попыток, которые понадобились игроку, чтобы отгадать число. Если игрок побил рекорд и отгадал за минимальное число попыток, появляется таблица рекордов и предложение ввести свое имя. Таблицу рекордов можно также вызвать в любое время с помощью специальной кнопки. Геймплей мы расписали, на главной форме не хватает только кнопок Новая игра, Рекорды и Выход.
 Примерный план игры готов. Пора приступать к реализации.
 С пылу, с жару
 Кликните дважды по иконке Дельфи. Утилита запустится и откроет новый проект. Сохраните его. Главная форма у нас будет высокой и узкой, поэтому измените ее размеры соответствующим образом. Через Object Inspector установите свойство Caption формы в "Угадайке". Можно начинать проектировать саму форму. Но до этого ее необходимо настроить. Что будет, если игрок вздумает развернуть окно на весь экран? Все элементы управления (кнопки, рисунок нашего ведущего, поле ввода) окажутся в верхнем левом углу формы, а остальное пространство будет пустым. Зрелище не для слабонервных. Такие огрехи выдают новичков и неряшливых программистов. Исправим эту оплошность. Кроме того, игроку надо запретить мышкой изменять размеры окна (по умолчанию эта возможность включена).
 Изменим тип границы окна. Откройте свойство формы BorderStyle: вместо bsSizeable поставьте bsSingle. Разверните список подсвойств BorderIcons и свойство biMaximize установите в False. В прошлый раз (в предыдущей статье цикла) вы наверняка заметили, что после запуска программы окно оказывается в том месте, где вы оставили форму при проектировании. Это не очень удобно. Давайте сделаем так, чтобы каждый раз при запуске игры форма оказывалась строго в центре экрана. Для этого достаточно установить свойство Position формы в poDesktopCenter.
 Приступим к наполнению формы. Начнем с нижней части. Внизу формы поставьте три кнопки одинакового размера так, чтобы они тесно прилегали друг к другу и к нижней границе окна. В их свойства Caption запишите соответственно Новая игра, Рекорды... и Выход. Обратите внимание, что Рекорды мы написали с тремя точками. По правилам Windows это значит, что после клика по кнопке перед пользователем откроется новое окно. Чуть выше поместите поле ввода (TEdit), а рядом — кнопку без надписи (мы будем выбирать ее случайным образом). Не забудьте очистить поле ввода в свойстве Text.
 Займемся графикой. Нашим ведущим станет симпатичный вампир за авторством Алексея Макаренкова. Вы можете нарисовать какого-нибудь персонажа сами, ну а если рисуете вы плохо, то можно воспользоваться отсканированной фотокарточкой или позаимствовать рисунок вампира с нашего компакта.
 Поместите на самое видное место нашей формы картинку (компонент TImage на вкладке Additional) и с помощью маленькой кнопочки с тремя точками напротив свойства Picture загрузите pic.jpg с вампиром-ведущим. Но вот беда: не понимает "Дельфи" формат jpg. Это надо исправить. Добавьте в строчку uses модуль jpeg. Теперь строчка uses выглядит примерно так:
 uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, jpeg;
 Пора загружать картинку. Но вампир у нас статичный. Как же он будет разговаривать с игроком? Разговаривать он будет так же, как персонажи "разговаривают" в комиксах, с помощью воздушных шариков-фраз. Нарисуйте картинки всех возможных фраз, или возьмите их с
|
 Поместите на форму картинки всех фраз. Не забудьте поставить свойство Transparent этих картинок в true, а свойство Visible в значение False, ведь с самого начала все картинки должны быть не видны. Фразы "Я загадал число..." и "Ты угадал..." размещаем сверху по центру, фразу "Мое число больше" — слева, а фразу "Мое число меньше" — справа.
 Вам не надоел серо-голубой однообразный интерфейс Windows? Давайте хоть нашу игру сделаем разноцветной. Неплохо бы покрасить форму в какой-нибудь цвет, например, в желтый. Просто установите свойство Color формы в clYellow. Поле ввода можно покрасить в небесный цвет. Для этого установите его свойство в clAqua. Чтобы придать некоторый стиль и необычность полю ввода, его стоит сровнять с формой; для этого установите свойство BorderStyle (поля ввода, а не формы) в bsNone.
 Давайте сделаем поле ввода активным по умолчанию. Поставьте у формы свойство ActiveControl в Edit1, и при каждом запуске курсор будет сразу появляться в поле ввода.
 Необходимо изменить шрифт всех четырех кнопок и поля ввода на что-то более подходящее. За шрифты любых элементов отвечает свойство Font. Можно изменять шрифт, регулируя подсвойства этого комплексного параметра, но лучше и проще щелкнуть на маленькую кнопочку с тремя точками напротив надписи Font в объектном инспекторе. Перед вами появится большое окно, в котором есть возможность изменить гарнитуру, начертание, кегль и цвет надписи.
 Договоримся о логических именах объектов, чтобы дальше по тексту не называть все время кнопки русскими именами. Кнопка Новая игра будет — Button1, Рекорды... — Button2, Выход — Button3, поле ввода — Edit1, картинка ведущего — Image1, картинка "Я загадал число..." — Image2, картинка "Ты угадал..." — Image3, картинка "Мое число больше" — Image4, картинка "Мое число меньше" — Image5.
 10240 по Фаренгейту
 Интерфейс готов. Пора программировать игровую модель. Осмотрим форму. Первое, что бросается в глаза, — безымянная кнопка. На этой кнопке каждый раз должны меняться надписи. Мы можем прописать изменение надписи во всех местах, где это требуется, но зачем усложнять себе жизнь? Давайте создадим отдельную процедуру, которая только тем и будет заниматься, что менять надписи на кнопке. Переключайтесь на окно редактора кода и после слова implementation пишите:
 procedure ChangeCaption;
 begin
 case random(5) of
 0:Form1.Button4.Caption:='Хм... может быть, это?';
 1:Form1.Button4.Caption:='Я знаю, твое число — вот это!';
 2:Form1.Button4.Caption:='Я разгадал тебя';
 3:Form1.Button4.Caption:='Ты хитрый, но я хитрее';
 4:Form1.Button4.Caption:='Попробуем вот так...';
 end;
 end;
 Разберем процедуру. Заголовок и объявление процедуры — понятны. Дальше идет конструкция case A of B [C,D...]. Это конструкция выбора какой-то команды в зависимости от состояния переменной A. Если конструкция random(5) будет равна, скажем, единице, выполнится команда Form1.Button4.Caption:='Я знаю, твое число — вот это!'. Random(N) — функция, которая возвращает случайное число из промежутка от нуля до N-1. Дальше мы свойству Caption объекта Button4 (наша кнопка) каждый раз присваиваем новое значение. Но вот почему перед Button 4 стоит какой-то объект Form1? Это — форма. Ведь кнопка принадлежит форме, а значит, объект Button4 можно рассматривать как свойство объекта Form1.
 Внимательный читатель спросит: "А почему мы раньше никогда Form1 перед объектами формы не указывали, а сейчас указали?". Дело в том, что есть процедуры и функции классов (они, если вы еще не забыли, называются методами), а есть просто процедуры и функции. Сейчас перед вами просто процедура. А в тестовом редакторе мы писали код только в обработчиках событий формы. Обработчик события — процедура класса. Мы везде должны были бы писать перед объектом имя более глобального объекта, к которому он принадлежит. Однако, если мы пишем код в методе глобального объекта, его идентификатор нужно опустить, что мы с успехом и делали. Процедура ChangeCaption не входит ни в какой класс, она сама по себе. Поэтому в ней надо указывать полный путь до необходимого свойства или объекта. Немного сложно, но, попрактиковавшись, вы все поймете.
 После первого запуска название кнопки должно смениться случайным образом. Запишем команду запуска этой процедуры в обработчик события формы OnCreate. Создать обработчик можно или через вторую вкладку объектного инспектора, или просто щелкнув два раза на пустом пространстве формы. Пишите между begin и end всего одну строчку:
 ChangeCaption;
 Запустите игру. После запуска название кнопки меняется. Что и требовалось сделать. Попробуйте запустить игру несколько раз. Странно, название кнопки всегда одно и то же. Дело все в принципе действия генератора случайных чисел. На самом деле, числа он выдает не случайные, а псевдослучайные, основанные на специальной таблице случайных чисел. Каждый раз при вызове берутся числа из таблицы в одном и том же порядке, поэтому вся случайность теряется. Чтобы это обойти, перед запуском программы надо один раз сбросить указатель таблицы случайных чисел с помощью команды Randomize. Поместить ее лучше всего в обработчик OnCreate, ведь он запускается самым первым. В итоге обработчик OnCreate у вас будет выглядеть так:
 procedure TForm1.FormCreate(Sender: TObject);
 begin
 Randomize;
 ChangeCaption;
 end;
 Попробуйте запустить игру несколько раз. Теперь надписи будут разными.
 Напишем процедуру, которая заставит ведущего говорить. В качестве параметра мы будем передавать процедуре целое число, которое обозначает нужную фразу. Вот полный текст этой процедуры:
|
 procedure Say(text:integer);
 begin
 Form1.Image2.Visible:=false;
 Form1.Image3.Visible:=false;
 Form1.Image4.Visible:=false;
 Form1.Image5.Visible:=false;
 case text of
 0:Form1.Image2.Visible:=true;
 1:Form1.Image3.Visible:=true;
 2:Form1.Image4.Visible:=true;
 3:Form1.Image5.Visible:=true;
 end;
 end;
 Коротко пробежимся по тексту. Сначала мы делаем невидимыми все картинки фраз. Потом с помощью уже знакомой вам конструкции case в соответствии со значением переменной-параметра text делаем видимой нужную картинку-фразу.
 Тонкости геймплея
 Когда все подготовительные действия выполнены, нужно приступать к проработке геймплея. Нажимаем на кнопку Новая игра; ведущий должен поприветствовать игрока. Для этого создайте обработчик события нажатия на кнопку. В нем пишите:
 say(0);
 n:=random(100);
 c:=0;
 В переменную n мы заносим случайное число, которое должен отгадать игрок. Переменная с — счетчик попыток. Эти переменные должны быть глобальными, потому что мы их будем использовать в нескольких процедурах и обработчиках. Объявите процедуру глобальной в разделе var:
 n:integer;
 c:integer;
 Теперь игрок вводит свой вариант ответа в поле Edit1 и нажимает кнопку Button4. В ее обработчике мы должны сравнить вариант игрока со значением в переменной n и вывести на экран соответствующую фразу. Обработчик кнопки Button4 будет выглядеть так:
 ChangeCaption;
 if strtoint(edit1.text)>n then begin
 say(3);
 c:=c+1;
 end;
 if strtoint(edit1.text)<n then begin
 say(2);
 c:=c+1;
 end;
 if strtoint(edit1.text)=n then begin
 say(1);
 if c<maxc then form2.showmodal;
 end;
 Edit1.Text:='';
 Этот обработчик достаточно сложный, поэтому его разберем детально. Первая команда — смена надписи на кнопке Button4. Потом начинаются проверки. Взгляните на первое условие. Переменная n сравнивается с конструкцией strtoint — это функция, которая преобразует строковую переменную в целочисленную и возвращает эту переменную. Данной функцией мы конвертируем значение edit1.text. В нем содержится строчка, которую ввел игрок. Зачем нужно что-то конвертировать? В "Дельфи" есть несколько типов переменных, в частности, целые числа и строки. Переменные разных типов хранят данные в разных форматах и поэтому несовместимы между собой. Чтобы сравнивать две переменные разных типов, надо одну из них привести к типу другой с помощью специальных функций, таких, как strtoint.
 Если то, что ввел игрок, больше значения переменной n, то на экран с помощью процедуры say выводится информация. После этого увеличивается на единицу счетчик попыток. Следующая проверка почти такая же, с той лишь разницей, что теперь предположение игрока оказывается меньше загаданного числа.
 Самое интересное условие — третье. Оно выполняется, если игрок угадал число. Ведущий сообщает об этом (команда say(1)). Потом мы проверяем, вдруг игрок отгадал число за рекордное количество попыток (не забывайте, что чем меньше, тем лучше, а не наоборот). Рекорд должен быть записан в глобальную переменную maxc (не забудьте ее объявить). Процедуру для загрузки значения в maxc мы напишем потом. А пока просто считайте, что в этой переменной содержится предыдущий рекорд. Если рекорд побит, мы должны запросить имя игрока, дабы записать его в таблице рекордов, и отобразить данную таблицу. Наконец, мы очищаем поле ввода (присваиваем его свойству text нулевую строку).
 Сделаем новую форму, которая будет запрашивать у игрока его имя. Щелкните на кнопку New Form, которая находится на панели инструментов. Появится новая форма. Надо ее "причесать". В верхнем левом углу разместите поле ввода, а под ним — кнопку с надписью ОК. В свойство Caption формы пишите что-то вроде "Вы побили рекорд! Введите свое имя:". Уменьшите форму так, чтобы все выглядело красиво. С помощью свойств запретите изменение размеров формы, очистите содержимое поля ввода.
 Вернемся ненадолго к первой форме и обработчику события кнопки Button4. Метод формы showmodal выводит скрытую форму на экран (у любого приложения по умолчанию все формы, кроме главной, скрыты). Есть одна тонкость: этот метод выводит форму со статусом модальной, то есть как диалог. До тех пор, пока форма не закроется, пользователь не получит доступа к программе. Например, он не сможет переключиться на главное окно программы. Можно использовать столь полезную особенность.
 Стоит задуматься о том, где и как мы будем хранить данные о лучшем игроке. Создадим файл в папке игры и назовем его record.rcd. Это будет обыкновенный текстовый документ, в первой строчке которого будет храниться имя рекордсмена, а во второй — число его попыток. Для начала искусственно наполните файл содержимым, например:
 Костя
 12
 Создайте форму, которая будет таблицей рекордов. Но там будет храниться всего один рекорд.
|
 modalresult:=1;
 hide;
 Modalresult нужно присвоить любое число, кроме нуля, чтобы убрать модальность.
 Где мы будем загружать данные о рекордсмене, а где мы их будем сохранять? Таблица рекордов будет появляться два раза: при нажатии на соответствующую кнопку и после появления нового рекордсмена. Не будем изобретать велосипед, и создадим только одну процедуру (а точнее, обработчик), который будем вызывать из двух разных участков кода. Переключайтесь на первую форму и дважды кликайте на кнопке Рекорды. Откроется обработчик события. Вот его полный текст:
 procedure TForm1.Button2Click(Sender: TObject);
 var
 f:textfile;
 nm:string;
 begin
 assignfile(f,extractfilepath(application.exename)+'record.rcd');
 reset(f);
 readln(f,nm);
 readln(f,maxc);
 closefile(f);
 form3.showmodal;
 end;
 Во-первых, мы выводим форму с рекордами на экран. Во-вторых, открываем файл record.rcd для чтения как текстовый. Потом читаем из файла первую строчку и записываем ее значение в текст метки 2 на форме рекордов. Это имя рекордсмена. Непосредственно сделать это не получится, поэтому приходится использовать вспомогательную переменную nm. В переменную maxc мы записываем значение второй строчки файла. Потом это же значение записываем в таблицу рекордов. И, наконец, закрываем файл.
 Обратите внимание, что командой assignfile мы как бы присваиваем файл значению переменной f. На самом деле f — ссылка на файл. Теперь мы сможем обращаться к этому файлу с помощью команд, подобных readln. Команда reset открывает файл только для чтения. Если бы вы попытались записать что-то в файл, возникла бы ошибка. Почему в команде assignfile мы не написали просто имя открываемого файла, а добавили спереди какую-то странную конструкцию? Мы могли сделать и так, но срабатывала бы команда не всегда. Дело в том, что при старте программы текущая папка назначается ее рабочей папкой, и на нее перемещается специальный указатель файловой системы Windows. Если программа будет обращаться к какому-то файлу по относительному пути, он будет трактоваться относительно этого указателя.
 Все вроде бы работает. Но в процессе работы программы указатель может переместиться в другое место. Например, Windows потребуется обратиться к какому-то своему драйверу или записать что-нибудь в файл подкачки. Текущей становится другая папка, и относительный путь уже не сработает. Поэтому желательно указывать именно абсолютный путь к файлу. Application.exename возвращает полный путь до исполняемого файла программы, а команда extractfilepath "выкусывает" из этого пути имя файла программы, и остается только путь до папки. К этому пути мы прибавляем имя нужного файла, и получаем абсолютный путь, который работает всегда.
 Переменная maxc нам нужна еще до появления окна рекордов. С ней мы сравниваем количество попыток игрока, если он отгадал число. Мы должны загрузить значение этой переменной при запуске игры. Добавим соответствующие строки в событие OnCreate:
 procedure TForm1.FormCreate(Sender: TObject);
 var
 f:textfile;
 nm:string;
 begin
 Randomize;
 ChangeCaption;
 assignfile(f,extractfilepath(application.exename)+'record.rcd');
 reset(f);
 readln(f,nm);
 Form3.Label2.Caption:=nm;
 readln(f,maxc);
 Form3.label4.caption:=inttostr(maxc);
 closefile(f);
 end;
 Теперь можно вернуться к кнопке OK второй формы. Здесь мы должны сохранить в файл данные нового рекордсмена, закрыть текущую форму и вывести форму рекордов. Вот текст обработчика этой кнопки:
 procedure TForm2.Button1Click(Sender: TObject);
 var
 f:textfile;
 begin
 hide;
 modalresult:=1;
 assignfile(f,extractfilepath(application.exename)+'record.rcd');
 rewrite(f);
 writeln(f,Edit1.text);
 maxc:=c;
 writeln(f,maxc);
 closefile(f);
 form1.Button2Click(nil);
 end;
 Сначала мы скрываем форму и убираем ее модальность. Потом связываем файловую переменную f с файлом рекордов. С помощью команды rewrite мы открываем файл для записи, причем, если в файле что-то до этого было, все сотрется. Чтобы не стирать содержимое, а добавлять новые строки в конец файла, надо применять команду append. В первую строчку файла мы пишем только что введенное игроком имя, а затем присваиваем переменной maxc новый рекорд, и тут же пишем его во вторую строчку файла. Потом файл закрываем и... И эмулируем нажатие на кнопку Рекорды... на главной форме. Не удивляйтесь, такая запись вполне возможна. Ведь обработчик события — это процедура, а значит, ее несложно вызвать. Что мы и сделали. Параметр Sender в теле обработчика мы не использовали, поэтому его можно забить универсальным объектным нулем — nil. Его еще называют указателем на пустоту.
 Сохраните проект и две только что созданные формы, и запустите компиляцию. "Дельфи" несколько раз спросит разрешения добавить один модуль в раздел uses другого модуля. Все время подтверждайте. Игра запустится. Можете играть.
 Когда эйфория успеха пройдет, вы заметите, что кое-что забыли. Кнопка Выход не работает. Чтобы исправить оплошность, достаточно в обработчике OnClick данной кнопки написать:
 Application.Terminate;
 Application — это глобальный объект, который указывает на вашу программу. У него есть несколько полезных свойств и методов, которыми мы уже не раз пользовались. Например, ExeName и Terminate. Еще Application обрабатывает сообщения, организует работу окон, распределяет потоки.
 Последние штрихи
 Что еще можно добавить к игре? Окошко About, где вы гордо напишете свое имя и нарисуете логотип. В игре практически не проверяются действия пользователя, нет "защиты от дурака". Например, если ввести в Edit1 не число, а какое-нибудь слово, возникнет ошибка и игра вылетит. Поэтому вводимые игроком числа надо проверять. Нет управления последовательностью действий. Вместо того, чтобы нажать на кнопку Новая игра, игрок может сразу же нажать на кнопку Button4. Естественно, игра поведет себя неправильно. Может повредиться файл рекордов, опять возникнет ошибка. Все это, конечно, мелочи, но именно из таких мелочей складывается хорошее отношение пользователей к программистам и компании, выпустившей игру.
 * * *
 В следующей статье мы сделаем настоящий медиа-плеер, который сможет проигрывать музыкальные и видео-файлы многих форматов, просматривать картинки...
 Если у вас что-то не получилось, не переживайте. Полные исходники игры в число вы можете взять с нашего компакт-диска.