Inferno
#03
22 ноября 2002 |
|
Gamedev - WORM-255F. Код игры Питон размером в 255 байт с комментариями.
; WORM-255F tinygame
; by Shiru Otaku/ANGEL2 18.o9.2oo2
; mailto: shiru@mail.ru
; Данная программка являет собой игру
; "Питон" размером в 255 байт.
; Уверен что можно сделать ещё короче,
; но в 128 байт при всех примочках
; она всё равно не поместится:).
; Эта версия использует некоторые
; подпрограммы 48basic rom (опрос BREAK).
; Эта версия использует очень упрощённое
; RND - по значению регистра R.
; Эта версия имеет звуковые эффекты через
; хрипер (целых 2;).
; В этой версии квадраты-кролики мигают
; (не FLASHем).
; В этой версии голова змия выделена
; отдельным цветом.
; Игре всё равно что было на экране до её
; запуска (все значения атрибутов
; используют одинаковые INK и PAPER).
; Управление - SINCLAIR JOY.
; Выход из игры - BREAK KEY (IY и стек
; не портит).
; максимальная длина змия - 255.
WORMBUF EQU #C16F ;адрес буфера змия
;(такой хитрый потому
;что младший байт
;адреса также
;используется в
;инициализации новой
;игры.
WORMCOL EQU #2D ;цвет змия, только
;нечётное число (bit 0
;is set).
RAMKCOL EQU #09 ;цвет края поля, только
;нечётное число.
;поехали...
ORG #C000
;начинаем новую игру - сюда управление
;передаётся и при запуске, и при
;рестарте вследствие гамовера.
START
;очищаем игровое поле и одновременно
;рисуем рамку поля - это выгоднее чем
;раздельно очистить экран и нарисовать
;рамку (меньше занимает).
;Рамка поля нам нужна чтобы проще
;определять касание змием края поля, по
;цвету. Это будет короче, нежели проверять
;значение адреса головы змия в области
;атрибутов (почти вдвое).
;в HL - начало области атрибутов.
LD HL,#5800
;закрасили верхнюю линию цветом края
;игрового поля.
LD B,#20
LD A,RAMKCOL
LD (HL),A
INC HL
DJNZ $-2
;теперь красим 22 строки - первый байт
;как край, 30 байт в 0, последний байт
;строки тоже как край.
LD B,22
SUDA
LD (HL),A
INC HL
;Причем если внешний цикл идёт по DJNZ,
;то внутренний (30 байт) - по регистру С,
;мы декрементим его пока не установится
;флаг переноса.
LD C,#1E
LD (HL),0
INC HL
DEC C
JR NZ,$-4
LD (HL),A
INC HL
DJNZ SUDA
;и красим нижнюю линию тоже в цвет края
;игрового поля.
LD B,#20
LD (HL),A
INC HL
DJNZ $-2
;Инициализируем переменные игры. Задаём
;длину змия равную 4-ём. Реально это будет
;3 знакоместа, 4-же потому-что последний
;элемент змия - нулевым цветом выводится,
;позволяет обойтись без очистки экрана
;каждый цикл.
LD A,4
LD (WORMLEN), A
;Заносим в переменную цвета бордюра двойку
;красный (типа гамовер)
;Экономим байт за счёт использования RRA.
;Переменная нужна чтобы цвет бордюра
;менялся с задержкой, а не сразу-же.
RRA
LD (BORDCOL), A
;Инициализируем буфер змия. Здесь
;используется младший байт адреса
;буфера (хранится в HL) для того чтобы
;и заносить его в память как младший байт
;адреса атрибутов, и для значения цикла
;одновременно.
;цикл при этом идёт дольше чем требуется
;(#71 раз), но это несущественно - зато
;экономим два байта на цикле без DJNZ.
LD HL,WORMBUF
ADD A,L
LD (HL),A
INC HL
LD (HL),#59
INC HL
DEC A
JR NZ,$-6
;Заносим в переменную направления движения
;змия код команды DEC HL (#2B) Это значит
;что при старте игры змий двигается влево.
LD A,#2B
LD (WORMDIR), A
;Вызываем подпрограмму вывода нового
;зайца-кролика. Подпрограмма использована
;потому-что генерация нового зайца нужна
;два раза - при старте и при съедении
;старого кролика.
CALL GENKROL
;Теперь начинается основной цикл игры.
MAINLOOP
;В качестве адреса переменной BORDCOL
;используется прямое указание на байт
;операнда команды LD A,n - позволяет
;сэкономить лишний байт кода
;В данном месте у нас в регистре A реально
;хранится не 0, а цвет бордюра.
BORDCOL=$+1
LD A,0
;Красим бордюр в этот цвет.
OUT (#FE), A
;И заносим в переменную BORDCOL единицу
;- в следующий раз бордюр станет синим
;(т.к. будут HALTы - глаз успеет увидеть
;предыдущий цвет).
LD A,1
LD (BORDCOL), A
;Переменную KROLADR - адрес текущего
;кролика в области атрибутов - также
;храним в команде (LD HL,nn).
KROLADR=$+1
LD HL,0
;Переменную цвета кролика тоже храним в
;команде. Она нужна для мигания кролика
;- мы не можем использовать FLASH
;потому-что мы не очищали собственно
;экранную область, и кто знает что там за
;мусор (мы ведь не будем надеяться что все
;нужные цвета установлены из
;BASIC-загрузчика:)
;Изначально эта переменная равна #36
;(зелёные INK и PAPER)
KROLCOL=$+1
LD A,#36
LD (HL),A
;Нарисуем змия. Его длина хранится в
;команде LD B,n. Надо помнить что длина
;змия реально на 1 меньше, чем хранится в
;этой переменной - после хвоста змия
;рисуем ещё одно знакоместо чёрным цветом.
WORMLEN=$+1
LD B,0
LD HL,WORMBUF
;Цвет текущего знакоместа змия мы храним в
;регистре A. Изначально он равен нулю,
;т.к. мы будем выводить змия от конца к
;началу (это позволит сэкономить несколько
;байт на затиралке хвоста).
XOR A
;Копируем через стек адрес буфера змия в
;IX - это короче чем загружать его туда
;как LD IX,nn (на байт).
PUSH HL
POP IX
;Цикл вывода змия.
PRWORM0
;Берём адрес из буфера змия в DE (адрес в
;области атрибутов, никаких координат - из
;них долго пересчитывать).
LD E,(HL)
INC HL
LD D,(HL)
INC HL
;Красим знакоместо по этому адресу цветом
;из регистра A.
LD (DE),A
;Первое знакоместо (хвост) покрасился
;нулём, а теперь мы грузим в A цвет
;собственно тушки змия (потери скорости от
;лишней команды в цикле не смертельны -
;нам важен размер:).
LD A,WORMCOL
;Одновременно с выводом мы сдвигаем адреса
;в буфере змия к его хвосту - если сделать
;это раздельно с выводом, то программа
;станет длинее.
;Т.к. в IX у нас хранится тоже что и в
;HL - используем относительное смещение,
;всё равно, даже при нулевом смещении байт
;на него тратится.
LD (IX-2),E
LD (IX-1),D
INC IX
INC IX
DJNZ PRWORM0
;После цикла в DE остался адрес головы
;змия - если мы хотим выделить её
;отдельным цветом, то делаем это
;(но выглядит не очень красиво, так что
;можно и не выделять, а сэкономить три
;байта). Я выделил включённой яркостью
XOR 64
LD (DE),A
;На случай если длина змия увеличится мы
;копируем последний адрес головы оного в
;следующую ячейку буфера (на неё указывает
;HL после цикла вывода).
LD (HL),E
INC HL
LD (HL),D
;Обменяем HL и DE, т.к. сейчас нам надо
;выяснить новый адрес головы в области
;атрибутов, а это удобнее делать в
;регистре HL.
EX DE,HL
;В BC мы загружаем #20 - чтобы сложением
;HL и BC можно было получить новый адрес
;при движении вниз. Старшую часть пары
;(B) мы не загружаем, т.к. она после
;последнего DJNZ равна нулю.
LD C,#20
;Логично предположить что для движения
;вверх нам нужно вычесть из HL BC.
;Но мы так не сделаем, т.к. команда
;SUB HL,BC занимает в памяти два байта
;вместо одного байта команды ADD HL,BC.
;Проблема не в лишнем байте, а в том что
;новый адрес мы вычисляем
;самомодифицирующимся кодом, и для
;движения вверх нам пришлось-бы
;модифицировать два байта (для остальных
;ставить ;NOP), в то время как для прочих
;направлений надо модифицировать всего
;один. Поэтому мы тратим три байта на
;загрузку в пару DE значения #FFE0.
;Это значение равно отрицательному числу
;#20, и если мы прибавим его к HL, то
;реально мы сделаем тоже самое, как
;если-бы отнимали #20.
LD DE,#FFE0
;Переменная WORMDIR указывает на адрес в
;памяти, в который мы в блоке управления
;змием будем писать команды INC HL/DEC HL
;/ADD HL,BC/ADD HL,DE в зависимости от
;выбранного направления движения.
WORMDIR=$
NOP
;теперь вернём найденный адрес обратно в
;DE, т.к. он нам ещё понадобится для
;определения факта смерти змия по
;различным обстоятельствам и для занесения
;в последнюю ячейку буфера если змий
;выживет.
EX DE,HL
;Переведём дух в течении семи прерываний -
;это чтобы играть было реально, а заодно
;чтобы можно было узреть мигание бордюра
;при сьедении кроликов или смерти змия.
LD B,7
HALT
DJNZ $-1
;Поменяем цвет кролика на новый (имитируем
;FLASH) XOR`ом. Для этого адрес переменной
;цвета кролика взять в HL, т.к. нам его
;обратно записывать ещё.
;Старый цвет кролика остаётся в регистре C
;для дальнейших проверок
LD HL,KROLCOL
LD A,(HL)
LD C,A
XOR 9
LD (HL),A
;Теперь покопаемся в желудке змия чтобы
;узнать, не съели-ли мы кого-нибудь (себя
;например:). Для начала узнаем, не был-ли
;съеден кролик.
LD A,(DE)
;Так кролик или нет?
CP C
JR NZ,CHKDEAD;Вроде нет...
;Точно кролик!
;Ну тогда надо-бы удлинить змия и
;сгенерировать нового кролика.
;Удлиняем. Но есть одна тонкость - длина
;змия задаётся у нас одним байтом, и на
;случай если игрок - гигант большого
;джойстика, то нужно предусмотреть
;случай когда длина змия стала равна 255.
;Чтобы не стал он после очередного кролика
;змием-невидимкой:) Потому после INC (HL)
;проверяем флаг переполнения, и если чего
;- восстанавливаем статус-кво. То-есть
;если змий был длиной в 255, то он таким и
;останется.
LD HL,WORMLEN
INC (HL)
JR NZ,$+3
DEC (HL)
;Продолжаем удлинять змия - увеличим
;указатель на голову змия в IX на два
;(то-есть на один знак). Надо заметить,
;что без этих команд обойтись нельзя
;(как может показаться) - ведь тогда IX
;будет указывать не на голову змия, а на
;предыдущую ячейку буфера, и тогда змию -
;хана.
INC IX
INC IX
;Позовём на сцену нового кролика:).
CALL GENKROL
;После GENKROL в регистре A всегда нуль -
;его мы и заносим в то место, где схавали
;кролика - чтобы при проверке на смерть
;змия не принять останки кролика за
;что-нибудь иное
LD (DE),A
;На радостях по поводу съедения зайца
;весело подмигнём зелёным бордюром.
LD A,4
LD (BORDCOL), A
;А заодно и пикнем:) Т.к. пикать по другим
;поводам мы не намерены - не станем
;выносить пикалку в подпрограмму. При
;работе пикалки важно значение регистра A7
;- в нем нужно хранить цвет бордюра (чтобы
;пока идёт пикание он не менялся). Как
;хорошо что именно оно там сейчас и есть..
;В регистре C у нас длительность пикания
;(внешний цикл).
LD C,70
;Чтобы был звук - надо менять значение
;4-ого бита в порте #FE с 0 на 1
XOR 16
OUT (#FE), A
;сделаем задержку между сменой значений
;бита, причём длиной в текущее значение
;внешнего цикла, это чтобы был не просто
;пик, а со сменой частоты.
LD B,C
DJNZ $
DEC C
JR NZ,$-8
;А теперь проверим, не сдохли-ли мы
;невзначай (надкусили себя/край экрана).
;Проверить будет удобнее всего с помощью
;команды RRA (один байт), ведь цвета всего
;невкусного - стенок, змиев всяких - у нас
;задано нечётным числом. Соответственно,
;от этих предметов после RRA установится
;флаг переноса. Если мы съели кролика -
;флаг не установится, т.к. в пикалке мы
;занесли в регистр A чётное число
;(со сброшенным младшим битом).
CHKDEAD
;Сдохли?..
RRA
JR NC,NODEAD;Нет!:)..
;Упс... Мои жубы...
;По этому поводу нужно вывести звук
;ломающихся зубов;). Он аналогичен
;предыдущей пикалке, только чтобы звук был
;не чистым тоном, а шумным - мы будем
;менять значение 4-ого бита в зависимости
;от того, что найдём в регистре R. И не
;забудем в младших трёх битах регистра A
;удерживать число 2 - красный бордюр.
LD C,250
LD A,R
AND 18
OR 2
OUT (#FE), A
LD B,C
DJNZ $
DEC C
JR NZ,$-12
;...И под звук крошащихся челюстей
;запускаем игру заново...
JP START
;А если мы не подохли до этого момента -
;значит надо записать новый адрес головы
;змия в конец буфера. Только надо помнить
;что после цикла вывода указатель на адрес
;(тот, что в IX - HL мы давно запортили)
;на 2 больше чем надо (INC'и-то
;выполнились); а если мы удлинились от
;съедения зайца - тогда значение IX как
;раз то что надо, но раздельные
;записывалки адреса сожрут лишние байты.
;Вобщем, заносим DE не по IX, а в адрес
;на 2 меньше (указываем это относительным
;смещением).
NODEAD
LD (IX-1),D
LD (IX-2),E
;Теперь надо поспрошать клавиатуру, а не
;тыкает-ли игрок в неё твёрдыми тупыми
;предметами?.. Особенно в районе клавиш
;67890 (Sinclair joy)?..
LD BC,#EFFE
;В HL взяли адрес WORMDIR - байта, который
;будем модифицировать в зависимости от
;тыканий игрока (либо оставим прежним,
;если юзер устал заниматься ерундой).
LD HL,WORMDIR
IN A,(C)
;Проверять биты полуряда 67890 будем не
;с помощью BIT n,r и не с помощью CP n -
;оба этих способа жрут по два байта, а нам
;нужно проверить весь полуряд. Потому
;выгоднее использовать однобайтовую
;команду RRA (проверять придётся в порядке
;следования битов клавиш, и один раз бит
;пропустить - клавишу 0, она у нас
;не используется).
RRA
RRA
JR C,$+4
;Нажата клавиша вверх (9) - заносим в
;WORMDIR код операции ADD HL,DE
LD (HL),#19
RRA
JR C,$+4
;Нажата клавиша вниз (8) - заносим в
;WORMDIR код операции ADD HL,BC
LD (HL),#09
RRA
JR C,$+4
;Нажата клавиша вправо (7) - заносим в
;WORMDIR код операции INC HL
LD (HL),#23
RRA
JR C,$+4
;Нажата клавиша влево (6) - заносим в
;WORMDIR код операции DEC HL
LD (HL),#2B
;Напоследок проверим, не нажал-ли игрок
;BREAK (бывают и такие огорчения:).
;Проверяем с помощью известной
;подпрограммы ROM BASIC48. Если не нажал -
;уходим на цикл, если нажал...
CALL #1F54
JP C,MAINLOOP
;...Ну раз нажал, так и досвидания...
RET
;Это - подпрограмма генерации адресов
;кроликов, сама заносит оные в KROLADR,
;сама проверяет - нет-ли чего нехорошего
;там где кролик собрался появиться, итд.
;RND использовано очень хреновое, просто
;по регистру R, но для данной игры это
;оправданно (другие способы сожрут много
;байт).
;Алгоритм генерации адреса в нужных
;пределах также очень туп (но пашет).
GENKROL
;Сначала сгенерим старший байт адреса - он
;должен быть в пределах #58..#5A.
;Этого можно достичь, сгенерировав число
;0..2 (AND`ом, и если вышло 3 - то
;генерировать заново). Потом добавляем к
;этому числу #58, и получаем старший байт
;адреса.
LD A,R
LD H,3
AND H
CP H
JR Z,GENKROL
ADD A,#58
LD H,A
;Теперь генерируем младший байт адреса,
;его значение может быть в пределах
;#00..#FF, так что никаких дополнительных
;извращений не понадобится.
LD A,R
LD L,A
;Проверяем, что за атрибут находится в
;сгенерированном адресе, и если это змий
;или край поля - генерируем адрес заново.
LD A,(HL)
CP 0
JR NZ,GENKROL
;Заносим сгенерированный адрес в
;переменную KROLADR.
LD (KROLADR), HL
;Как можно заметить, на выходе из этой
;подпрограммы регистр A всегда равен нулю
;- это свойство подпрограммы использовано
;выше.
RET
;Вот и всё.
Другие статьи номера:
Похожие статьи:
В этот день... 21 ноября