	DEVICE ZXSPECTRUM128

; 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 #13        ;цвет края поля, только нечётное число.


;поехали...

     ORG #C000-1
STARTFORSNA
     EI ;это делает игру на 1 байт длинее

;начинаем новую игру - сюда управление передаётся и при запуске, и при
;рестарте вследствие гамовера.

START

;очищаем игровое поле и одновременно рисуем рамку поля - это выгоднее чем
;раздельно очистить экран и нарисовать рамку (меньше занимает).
;Рамка поля нам нужна чтобы проще определять касание змием края поля, по
;цвету. Это будет короче, нежели проверять значение адреса головы змия в
;области атрибутов (почти вдвое).

;в HL - начало области атрибутов.

     LD HL,#5800

;закрасили верхнюю линию цветом края игрового поля.

     LD B,#20
     LD A,RAMKCOL
     LD (HL),A
     INC HL
     DJNZ $-2
	 
;	 LD B,#10
;     LD A,#11
 ;    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 HрL,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

;А заодно и пикнем:) Т.к. пикать по другим поводам мы не намерены - не
;станем выносить пикалку в подпрограмму. При работе пикалки важно значение
;регистра A - в нем нужно хранить цвет бордюра (чтобы пока идёт пикание он
;не менялся). Как хорошо что именно оно там сейчас и есть...
;В регистре 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 будем не с помощью BT 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

;Вот и всё.

	SAVESNA "worm255.sna",STARTFORSNA

