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 #09 ;цвет края поля, только нечётное число. ;поехали... ORG #C000-1 STARTFORSNA EI ;это делает игру на 1 байт длинее ;начинаем новую игру - сюда управление передаётся и при запуске, и при ;рестарте вследствие гамовера. 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 ;А заодно и пикнем:) Т.к. пикать по другим поводам мы не намерены - не ;станем выносить пикалку в подпрограмму. При работе пикалки важно значение ;регистра 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