ПРОФЕССИОНАЛЬНЫЙ ПОДХОД
(Продолжение. Начало на стр. 49, 70)
Разработка программы.
Мой подход к разработке программ базируется на той профессиональной подготовке, которую я получил на фирмах ICL и IBM.
Сам процесс разработки не зависит от того, какой язык программирования я применяю. Конкретное же содержание каждого этапа может изменяться в зависимости от того, какими инструментальными средствами я располагаю на своей машине и, в определенной степени, от типа разрабатываемой программы.
Этап 1. Постановка задачи.
Для чего предназначена программа, что она будет делать? Для игровых программ есть несколько важных моментов, которые должны быть определены уже на ранней стадии:
- каким будет экранный интерфейс (какой метод будет применяться для связи между программой и пользователем);
- тема игры;
- каков предполагаемый размер игры и сколько памяти можно отвести под хранение экранов и таблиц данных.
Выходная информация:
Это прежде всего экраны, но сюда можно отнести и распечатки на принтере, если они предполагаются, здесь же должны быть намечены те выгрузки на ленту или диск, которые могут потребоваться по ходу игры. И, конечно же, должен использоваться звук. Любая программа, разумно его использующая, только выигрывает от этого.
Я говорю о выходной информации, не сказав о входной вовсе не потому, что считаю ее более важной. Просто Вы не определитесь с тем, что надо дать программе, если не знаете, что Вы будете от нее брать.
Для игровых программ стоит рассмотреть в качестве выходных характеристик такие очевидные параметры, как:
- степень использования цвета;
- предполагаемая скорость;
- притягательность и т.п. Составьте список того, что Вы хотели бы иметь в своей программе и, по мере ее разработки, периодически к нему возвращайтесь для сверки.
Входная информация:
Сюда относятся те данные, которые поступают в программу во время игры от клавиатуры или от джойстика. Если необходимо во время игры делать подзагрузку, то сюда же можно отнести данные, считываемые с ленты или диска.
От того, как происходит управление игрой в большинстве случаев зависит судьба этой игры - успех или неудача.
Применение джойстика всегда вносит в игру ограничение в связи с тем, что у него только одна кнопка. Ах, если бы у него было две функциональных кнопки! Тогда первая исполняла бы какое-то действие, а вторая - переключала бы вид этого действия. Например, первая означала бы "огонь", а вторая переключала бы оружие: нож, пистолет, автомат, гранатомет и т.п.
Однако кнопка у нас только одна и все функции, которые Вы хотели бы иметь, надо все-таки как-то к джойстику привязать. Здесь очень легко можно зайти так далеко, что игра окажется трудной в освоении.
Тщательно продумывайте технологию управления игрой. Не хватайтесь за первый попавшийся вариант. Взвесьте несколько альтернативных, выберите лучший и будьте готовы в любой момент от него отказаться, если окажется, что для других людей он слишком сложен.
Файлы и таблицы данных:
На этой стадии достаточно только сделать грубую прикидку того, какие данные могут потребоваться программе во время работы. Оцените их размер, предварительно наметьте метод, которым данные будут выбираться из таблиц.
Например, если в игре есть много комнат, надо наметить, как эти комнаты будут нумероваться и как будут кодироваться двери, связывающие разные комнаты между собой.
Первый этап должен закончиться твердым ответом на вопрос: возможно ли создание той программы, которую я задумал или все надо начать сначала. Обычно к этому моменту мой стол уже завален грудой набросков, эскизов, таблиц, которые смогут пригодиться на следующем этапе.
Этап 2. Проектирование программы.
Теперь настало время решать как Вы будете программировать все то, что задумали и наметили на первом этапе.
Работа начинается с подчистки и увязки результатов первого этапа. Я предпочитаю сначала строго расписать все таблицы данных, которые были намечены и заканчиваю эту работу присвоением имен переменным, которые будут хранить эти данные. Обратите внимание на то, что значительная часть пути уже пройдена, а компьютер Вам до сих пор еще не был нужен. Именно в этом и состоит суть моего подхода. Тщательная проработка структур данных на бумаге до начала программирования позволяет разбить предполагаемую программу на несколько независимых частей, с каждой из которых работать проще, чем со всей программой в целом. Каждая часть, в свою очередь делится на процедуры. Каждая процедура выполняет отдельную конкретную задачу, достаточно простую, чтобы можно было запрограммировать ее за один прием, без необходимости делить на части.
Правильное проектирование программы - самый критический фактор для успеха или провала всей Вашей работы. Плохо спроектированные или вообще неспроектированные программы занимают столько времени на программирование и отладку (если вообще отлаживаются до конца), что когда они готовы, то обычно быстродействие их работы бывает неудовлетворительным. Вам очень дорого будет стоить пренебрежение этим этапом. Начинающему не терпится начать программирование, он полагает, что методом проб и ошибок, внося коррективы в программу по ходу разработки, решит все проблемы. Опытный программист, наоборот, отдает половину своего времени именно этой работе без компьютера, потому что знает, что когда коррекций в программе становится слишком много, то они начинают влиять друг на друга и быстро губят всю работу. Более того, еще на докомпьютерном этапе он уже наметит, что будет подлежать последующим уточнениям и коррекциям с целью наилучшей играбельности программы и так организует программу, чтобы эти уточняемые параметры не влияли друг на друга.
Итак, одним словом, для всех языков программирования желательно, а при программировании в машинном коде (или на Ассемблере) абсолютно необходимо расчленить задачу на множество микрозадач, каждую из которых можно запрограммировать так, чтобы текст этой микропрограммы мог сформироваться и уместиться в Вашей голове.
Этап 3. Кодирование.
На этом этапе записывается процедура в машинных кодах (на ассемблере), но сначала надо завести рукописный справочник по всем переменным, используемым в Вашей программе.
Если Вы полагаете, что и так все запомните, то лучше не экспериментируйте, для этого надо иметь необычную память. По каждой переменной я записываю имя, размер, содержание, примечание.
Н |
апример: |
Имя |
Размер |
Содержание |
Примечание |
BOMB |
1 байт |
0 - нет |
Наличие бомбы устанавливается в процедуре GETBOMB. |
|
|
1 - есть |
Изменяется в процедуре USEBOMB. |
Кроме справочника по переменным, надо вести еще и справочник по процедурам:
- имя процедуры;
- что делает;
- содержание регистров на входе;
- содержание регистров на выходе;
- какие переменные использует.
Н |
апример: |
Имя |
Что делает |
Вход |
Выход |
|
|
A - вид оружия 0...3 |
|
FIRE |
Инициализирует |
0 - нож |
A - сила выстрела от 0 |
|
пакет процедур, |
1 - пистолет |
до 255 |
|
управляющих |
2 - автомат |
|
|
стрельбой. |
3 - гранатомет
BC,DE,HL - не определены
F - флаг C=0 - выполнить стрельбу
- флаг C=1 - поменять оружие
- прочие флаги не определены |
|
Используемые |
WEAPON 1 байт Вид оружия |
переменные: |
CHARGE 3 байта Количество зарядов на каждый |
|
|
вид оружия |
Конечно, если Вы пишете программу с помощью Ассемблера, то эту информацию можно включать в исходный текст в виде комментария, но во-первых, лучше иметь эти листки на каждую процедуру под рукой, во-вторых, Вы быстрее приобретете профессиональный стиль и, в-третьих, это серьезный шаг вперед на пути создания собственной библиотеки процедур, которая позволит Вам в несколько раз сократить время на разработку будущих проектов.
Не надо пытаться написать сразу всю программу. Начните с программирования самых малых, не взаимодействующих с другими процедур. Их Вы быстро напишете и испытаете.
Этап 4. Отладка.
Я предпочитаю всякую процедуру проверять сразу же после того, как написал. Как правило, для этого необходимо приписывать несколько строк, чтобы обеспечить те данные, которые эта процедура должна получать из главной программы. Иногда даже необходимо сразу писать несколько процедур, испытывать и отлаживать совместно, если они друг без друга не отлаживаются, но чем их меньше, тем лучше. Есть закон, согласно которому если размер текста возрастает вдвое, то количество ошибок в нем возрастет в четыре раза. Есть и другой закон - если количество ошибок возрастает вдвое, то время на их отыскание и устранение тоже возрастает в 4 раза. Так что сами думайте, какой размер должны иметь Ваши элементарные процедуры. Заканчивается проверка и отладка процедуры тогда, когда Вы уверены, что в проведенном независимом испытании она работает и делает все, что положено. Правда, к сожалению, это еще не дает 100%-ной уверенности в том, что она также надежно будет работать в комплексе с другими при запуске всей программы в целом. Но если этап проектирования был хорошо проработан, то Вы сможете подкорректировать ее работу в составе всей программы без нарушения работы других блоков.
В основном эти проблемы совместной работы отлаженных порознь процедур порождаются взаимообменом данными между процедурами, который происходит через переменные, содержащиеся в заданных ячейках памяти, через регистры процессора или через флаги процессора. Эти ошибки будут минимальными, если Вы в свое время четко расписали для каждой процедуры, что она имеет на входе, что выдает на выходе и с какими регистрами работает.
Чтобы минимизировать вред от ошибок, связанных с взаимодействием между процедурами, я обычно "прикрепляю" новую к уже отлаженной. Так, постепенно, отлаженные процедуры создают среду для проверки и отладки новых.
Что такое хорошо и что такое плохо?
Хорошее программирование означает понятное программирование и минимум ошибок. Помните, что Вам многократно предстоит возвращаться назад и что-то переделывать, поэтому пожалуйста не оставляйте сами себе ловушек. Вот примерный список приемов плохого стиля:
1. Использование "хитрых", головоломных приемов.
Я считаю, что в принципе этого делать не надо. Исключение - тот случай, когда это абсолютно необходимо для достижения максимальной скорости работы, но и здесь это может быть в минимальном количестве особо важных (по быстродействию) процедур.
2. Злоупотребление применением стека.
Лучше оставить стек в покое. Особенно плохо применять его для хранения программных переменных. Вызов переменной по имени (в Ассемблере) - намного понятнее, быстрее и создает меньше условий для появления ошибок.
3. Неправильное использование процедур.
Каждая процедура должна иметь один вход и один выход. Если Вам их нужно больше, то вернитесь на этап 2 и повторите проектирование.
Никогда не имитируйте вызов процедуры (CALL) путем перехода (JP) с последующей манипуляцией стеком. Прием слишком головоломный, чтобы пользоваться им часто. Всегда завершайте процедуру естественным путем (команда RET в конце процедуры).
4. Злоупотребление переходами (JP).
Команда JP, как и GO TO в Бейсике очень затрудняет читаемость программы. При широком применении переходов JP Вы затрудняете себе отладку. Используйте JP только для организации циклов и для исполнения условного ветвления (IF... THEN...).
5. Работа с адресами.
Программируя на Ассемблере, не задавайте адреса, а определяйте только имена переменных, процедур и т.п. Если Вы будете вызывать процедуру по адресу, то все будет хорошо, пока Вам не придется ее переместить в другое место и тогда вся программа рассыплется как карточный домик.
К чертам хорошего стиля я отношу:
1. Определение имен со смыслом (название отражает содержание).
2. Комментарий к каждой процедуре.
3. Комментария к каждому "головоломному" (нестандартному) приему.
4. Применение регистров процессора по назначению:
A - аккумулятор;
B - восьмиразрядный обратный счетчик;
BC - шестнадцатиразрядный обратный счетчик;
HL - для хранения адреса или как двухбайтный аккумулятор.
DE - для хранения адреса в операциях, в которых необходимо использование двух адресов.
IX,IY - для выборки элемента из таблицы с использованием индексной адресации.
ПРИМЕЧАНИЕ ИНФОРКОМА.
Если Ваша программа предполагает обращение к системному ПЗУ "Спектрума", то лучше в регистр IY ничего не засылать, а использовать его для выбора данных из таблицы системных переменных "Спектрума", - прием, ставший стандартным.
Регистр же IX в этом случае наиболее часто используют в операциях загрузки/выгрузки.
В качестве примера распечатки программы в машинных кодах, обещанной в прошлом выпуске "ZX-РЕВЮ", мы приводим процедуру Стива Тернера для печати текстовых сообщений в любой точке экрана с точностью до пиксела (а не только в координатах знакомест).
Она может применяться в играх, например для выдачи на экран текущего счета.
Данный пример распечатает на экране две строки: EXAMPLE OF TEXT TABLE. Первая строка содержится в относительном адресе 00AB, а вторая - в 00B3. Обратите внимание на то, что перед строкой, выводимой на печать, должен стоять байт, в котором указана длина этой строки:
00AA - 07 (7 символов в слове "EXAMPLE")
00B2 - 0D (13 символов в строке "OF TEXT TABLE").
(Продолжение в следующем выпуске)
0000 |
|
00010 |
|
; Пример программы |
0000 |
|
00020 |
|
; печати текста |
0000 |
3E01 |
00030 |
|
LD A,1 ; Номер сообщения |
0002 |
160A |
00040 |
|
LD D,10 ; Координата "y" места печати |
0004 |
1E0A |
00050 |
|
LD E,10 ; Координата "x" |
0006 |
CD0A00 |
00060 |
|
CALL TEXT |
0009 |
C9 |
00070 |
|
RET |
000A |
|
00080 |
|
; Подпрограмма TEXT |
000A |
|
00090 |
|
; A - номер сообщения |
000A |
|
00095 |
|
; DE - координаты печати. |
000A |
|
00100 |
TEXT |
|
000A |
21AA00 |
00110 |
|
LD HL,TXADD; |
000D |
0600 |
00120 |
LOOK |
LD B,0 |
000F |
1803 |
00130 |
|
JR GOTRY |
0011 |
4E |
00140 |
LONG |
LD C,(HL) ; поиск текста |
0012 |
23 |
00150 |
|
INC HL |
0013 |
09 |
00160 |
|
ADD HL,BC |
0014 |
3D |
00170 |
GOTRY |
DEC A |
0015 |
20FA |
00180 |
|
JR NZ,LONG |
0017 |
CD1B00 |
00190 |
|
CALL PRINT |
001A |
C9 |
00200 |
|
RET |
001B |
|
00210 |
|
|
001B |
|
00220 |
|
|
001B |
|
00230 |
|
; Подпрограмма PRINT |
001B |
22A800 |
00240 |
PRINT |
LD (INPUT),HL |
001E |
7A |
00250 |
|
LD A,D |
001F |
E638 |
00260 |
|
AND 38H ; определяем ряд |
0021 |
87 |
00270 |
|
ADD A,A |
0022 |
87 |
00280 |
|
ADD A,A |
0023 |
83 |
00290 |
|
ADD A,E ; прибавили "x" |
0024 |
5F |
00300 |
|
LD E,A ; младший байт |
0025 |
70 |
00310 |
|
LD A,D |
0026 |
1F |
00320 |
|
RRA |
0027 |
1F |
00330 |
|
RRA |
0028 |
1F |
00340 |
|
RRA |
0029 |
E618 |
00350 |
|
AND 018H ; определяем треть экрана |
002B |
4F |
00360 |
|
LD C,A |
002C |
7A |
00370 |
|
LD A,D |
002D |
E607 |
00380 |
|
AND 07H ; строка в ряду |
002F |
81 |
00390 |
|
ADD C |
0030 |
C640 |
00400 |
|
ADD 40H ; старший байт |
0032 |
57 |
00405 |
|
LD D,A |
0033 |
2AA800 |
00410 |
|
LD HL,(INPUT) |
0036 |
ED53A400 |
00420 |
|
LD (OUTLIN),DE |
003A |
7E |
00430 |
|
LD A,(HL) |
003B |
A7 |
00440 |
|
AND A |
003C |
C8 |
00450 |
|
RET Z ; неверное значение |
003D |
4F |
00460 |
|
LD C,A |
003E |
ED53A600 |
00470 |
PROLET |
LD (OUTPUT),DE |
0042 |
23 |
00480 |
|
INC HL |
0043 |
22A800 |
00490 |
|
LD (INPUT),HL |
0046 |
7E |
00500 |
|
LD A,(HL) |
0047 |
FEFF |
00510 |
|
CP 0FFH |
0049 |
2016 |
00520 |
|
JR NZ,CHAR |
004B |
|
00530 |
|
; переход к новой строке |
004B |
|
00540 |
|
; под первой |
позицией |
004B |
|
00550 |
|
; печати |
|
|
004B |
ED5BA400 |
00560 |
|
LD DE,(OUTLIN) |
|
004F |
7B |
00570 |
|
LD A,E |
|
|
0050 |
C620 |
00580 |
|
ADD 20H |
|
|
0052 |
5F |
00590 |
|
LD E,A |
|
|
0053 |
D0 |
00600 |
|
RET NC |
|
|
0054 |
7A |
00610 |
|
LD A,D |
|
другая треть экрана |
0055 |
C608 |
00620 |
|
ADD 8 |
|
|
0057 |
FE58 |
00630 |
|
CP 58H |
|
|
0059 |
D0 |
00640 |
|
RET NC |
|
|
005A |
57 |
00650 |
|
LD D,A |
|
|
005B |
ED53A400 |
00660 |
|
LD (OUTLIN),DE |
|
005F |
183C |
00670 |
|
JR REJOIN |
|
|
0061 |
|
00680 |
|
|
|
|
0061 |
6F |
00690 |
CHAR |
LD L,A |
|
поиск символа |
0062 |
2600 |
00700 |
|
LD H,0 |
|
|
0064 |
29 |
00710 |
|
ADD HL,HL |
|
|
0065 |
29 |
00720 |
|
ADD HL,HL |
|
|
0066 |
29 |
00730 |
|
ADD HL,HL |
|
|
0067 |
ED5B365C |
00740 |
|
LD DE,(CHASET) |
|
006B |
19 |
00760 |
|
ADD HL,DE |
|
|
006C |
ED5BA600 |
00770 |
|
LD DE,(OUTPUT) |
|
0070 |
7A |
00780 |
|
LD A,D |
|
|
0071 |
FE58 |
00790 |
|
CP 58H |
|
|
0073 |
D0 |
00800 |
|
RET NC |
|
|
0074 |
0608 |
00810 |
|
LD B,8 |
|
|
0076 |
7E |
00820 |
EIGHT |
LD A,(HL) |
|
|
0077 |
12 |
00830 |
|
LD (DE),A |
|
выдает |
0078 |
23 |
00840 |
|
INC HL |
|
восемь |
0079 |
7A |
00850 |
|
LD A,D |
|
строк |
007A |
E607 |
00860 |
|
AND 07 |
|
символа |
007C |
3C |
00870 |
|
INC A |
|
|
007D |
FE08 |
00880 |
|
CP 8 |
|
|
007F |
200E |
00890 |
|
JR NZ,NONEW |
|
|
0081 |
7A |
00900 |
|
LD A,D |
|
|
0082 |
E6F8 |
00910 |
|
AND 0F8H |
|
|
0084 |
57 |
00920 |
|
LD D,A |
|
|
0085 |
7B |
00930 |
|
LD A,E |
|
|
0086 |
C620 |
00940 |
|
ADD 20H |
|
|
0088 |
5F |
00950 |
|
LD E,A |
|
|
0089 |
3005 |
00960 |
|
JR NC,NEXTSL |
|
|
008B |
7A |
00970 |
|
LD A,D |
|
|
008C |
C608 |
00980 |
|
ADD 8 |
|
|
008E |
57 |
00990 |
|
LD D,A |
|
|
008F |
14 |
01000 |
NONEW |
INC D |
|
|
0090 |
10E4 |
01010 |
NEXTSL |
DJNZ EIGHT |
|
|
0092 |
ED5BA600 |
01020 |
|
LD DE,(OUTPUT) |
|
0096 |
1C |
01030 |
NEXTLT |
INC E |
|
|
0097 |
2004 |
01040 |
|
JR NZ,REJOIN |
|
|
0099 |
7A |
01050 |
|
LD A,D |
|
|
009A |
C608 |
01060 |
|
ADD A,8 |
|
|
009C |
57 |
01070 |
|
LD A,D |
|
|
009D |
2AA800 |
01080 |
REJOIN |
LD HL,(INPUT) |
|
|
00A0 |
0D |
01090 |
|
DEC C |
|
|
00A1 |
209B |
01100 |
|
JR NZ,PROLET |
|
|
00A3 |
C9 |
01110 |
|
RET |
|
|
00A4 |
01120 |
|
|
|
|
|
00A4 |
0000 |
01130 |
OUTLIN |
DW 0 |
|
начальная строка |
00A6 |
0000 |
01140 |
OUTPUT |
DW 0 |
|
адрес печати |
00A8 |
0000 |
01150 |
INPUT |
DW 0 |
|
адрес текста |
5C36 |
|
01160 |
CHASET |
EQU 23606 |
|
указывает на |
00AA |
|
01170 |
|
|
|
адрес шрифта в ПЗУ ми |
00AA |
|
01180 |
|
|
|
нус 256 байтов |
00AA |
|
01190 |
|
; Таблица текста |
00AA 07 01200 TXADD
00AB 4558414D 01210
00AF 504C45
00B2 0D 01220
00B3 4F462054 01230
00B7 45585420
00BB 5441424C
00BF 45
00000 TOTALERRORS
DEFB 7
DEFM 'EXAMPLE'
DEFB 13
DEFM 'OF TEXT TABLE'