Экспериментальный ассемблер Z80 с синтаксисом Си.

Однажды я подумал. А почему бы не попробовать в ассемблер не добавить условия if (flag_z) {… } else {… }, не добавить циклы do {… } while(--b). Это бы сократило количество меток в программе, сделало бы программу более наглядной. Сделало бы для меня. Я для себя всё это затеял. Помимо этих if, while, так же можно заменить команды ADD на символ +, команду LD на символ =. Таким образом появился компилятор cmm, на котором я уже написал несколько программ.

Вот, например, кусочек кода из игры, который выводит звуковые эффекты:

alemorf.ru/pi/4/index.html

А вот ПЗУ ЮТ-88, которое компилируется байт в байт в оригинальное ПЗУ

alemorf.ru/comps/ut88/rom.html

А тут есть спойлер, что бы код прятать?

А это логика врага — Паук


#include "dun/duncommon.h"

const int enemySpiderAtackDistX   = 15;
const int enemySpiderAtackDistY   = 3;
const int enemySpiderAtackTimeout = 6;
const int enemySpiderAtack        = 1;
const int enemySpiderWidth        = 3;
const int enemySpiderHeight       = 3;
const int enemySpiderLife         = 2;

void enemySpiderInit(de)
{
    swap(hl, de);
    hl++;                          // 1. enemyFlags
    hl++;                          // 2. enemyX
    (*hl)--; hl++;                 // 3. enemyY
    *hl = enemySpiderWidth; hl++;  // 4. enemyW
    *hl = enemySpiderHeight; hl++; // 5. enemyH
    *hl = enemySpiderLife; hl++;   // 6. enemyLife
    *hl = 0;                       // 7. enemyD
}

void enemyLogicSpider(a)
{
    // Атака
    if (a == enemyInAttack)
    {
        enemyAtack = a = enemySpiderAtackTimeout;
        subLife(b = enemySpiderAtack);
        bc = &enemySpiderSpriteRight;
        if ((a = enemyD) == 1) return;
        bc = &enemySpiderSpriteLeft;
        return;
    }

    // Спрайт для ожидания атаки
    if (a == enemyInWaitAttack)
        goto enemyLogicSpiderStdSprite;

    // Спрайт для падения
    if (a == enemyInFall)
    {
enemyLogicSpiderStdSprite:
        enemyStep = (a ^= a);
        bc = &enemySpiderSprite;
        return;
    }

    // Завершаем шаг
    if ((a = enemyStep) != 0)
        goto enemyLogicSpiderStdSprite;

    // Если враг видит игрока
    ((a = dunScrollY) += [playerY + 1 + enemySpiderAtackDistY]) -= *(hl = &enemyY);
    a &= mapYM;
    if (a < [enemySpiderAtackDistY * 2])
    {
        // Если враг видит игрока
        ((a = dunScrollX) += [playerX - (enemySpiderWidth + 1) / 2 + enemySpiderAtackDistX]) -= *(hl = &enemyX);
        if (a < [enemySpiderAtackDistX * 2])
        {
            // Поворачиваемся в сторону игрока
            ((a = dunScrollX) += [playerX - enemySpiderWidth / 2]) -= *(hl = &enemyX);
            a -@= a;
            if (flag_z) a++;
            enemyD = a;
        }
    }

    // Надо двигаться?
    b = a = enemyD;
    if (a == 0) goto enemyLogicSpiderStdSprite;

    // Пересечение
    enemyCheckVert();
    if (flag_nc) // Если мы уперлись в препядствие
    {
        if (flag_nz --e) goto enemyLogicSpiderStdSprite; // Это не ступенька
        // Пробуем встять на ступеньку
        enemyCheckHorz(d = -1, a = enemyD);
        if (flag_nc) goto enemyLogicSpiderStdSprite; // Голова упирается в потолок
        enemyY = --(a = enemyY); // Двигаемся
    }

    // Двигаемся
    enemyX = ((a = enemyX) += b);

    // Спрайт движения
    enemyStep = a = 1;
    b & 0x80;
    bc = &enemySpiderSpriteLeft;
    if (flag_z) return;
    bc = &enemySpiderSpriteRight;
}

uint8_t enemySpiderSprite[] =
{
    enemyTail_368_0,  enemyTail_376_0,  enemyTail_384_0,
    enemyTail_368_8,  enemyTail_376_8,  enemyTail_384_8,
    enemyTail_368_16, enemyTail_376_16, enemyTail_384_16
};

uint8_t enemySpiderSpriteLeft[] =
{
    enemyTail_392_0,  enemyTail_400_0,  enemyTail_400_24,
    enemyTail_392_8,  enemyTail_400_8,  enemyTail_400_24,
    enemyTail_392_16, enemyTail_400_16, enemyTail_400_24
};

uint8_t enemySpiderSpriteRight[] =
{
    enemyTail_400_24, enemyTail_392_0,  enemyTail_400_0,
    enemyTail_400_24, enemyTail_392_8,  enemyTail_400_8,
    enemyTail_400_24, enemyTail_392_16, enemyTail_400_16
};


А это главное меню


const int menuLogoX      = 1;
const int menuLogoY      = 1;
const int menuItemH      = 10;
const int menuItemsY     = 100;
const int menuItemsX     = 80;
const int menuItemsSX    = 72;
const int menuItemsCount = 3;
const int menuItemsM     = menuItemsCount * menuItemH;

uint8_t menuX = 0;
uint8_t menuX1 = 0;

void menuDrawCursor()
{
    gDrawTextEx(h = ((a = menuX1) += menuItemsY), l = menuItemsSX, de = "@", a = 0x44);
}

void wait()
{
    hl = &gFrame;
    a ^= a;
    do { a |= *hl; } while(flag_z);
    *hl = 0;
}

// Вывод текста по центру экрана
// Вход: A - цвет и шрифт, DE - текст, H - строка

void drawTextCenter(a)
{
    push(a)
    {
        push(hl, de)
        {
            gMeasureText(a);  // a - цвет и шрифт
            a = (((a = 256) -= c) >>= 1);
        }
        l = a;
    }
    gDrawTextEx(a); // a - цвет и шрифт
}

void main()
{
    newGame();

    drawImage(de = [screenAttrAddr1 + menuLogoX + (menuLogoY << 5)], hl = &image_logo);
    gDrawTextEx(a = 0x45, hl = [(menuItemsY + menuItemH * 0) * 256 + menuItemsX], de = "Начать новую игру");
    gDrawTextEx(a = 0x45, hl = [(menuItemsY + menuItemH * 1) * 256 + menuItemsX], de = "Настроить управление");
    gDrawTextEx(a = 0x45, hl = [(menuItemsY + menuItemH * 2) * 256 + menuItemsX], de = "Ввести пароль");

    drawTextCenter(a = 0x43, h = [192 - 8 - menuItemH * 2], de = "Игра | 2020 Алексей {Alemorf} Морозов");
    drawTextCenter(a = 0x43, h = [192 - 8 - menuItemH * 1], de = "Мюзикл | 1998 Антон {Саруман} Круглов,");
    drawTextCenter(a = 0x43, h = [192 - 8 - menuItemH * 0], de = "Елена {Мириам} Ханпира");

    menuX = a ^= a;
    menuX1 = (++a);

    menuDrawCursor();

    while()
    {
        wait();
        menuTick();
    }
}

void menuTick()
{
    // Получить нажатую клавишу
    hl = &gKeyTrigger;
    b = *hl;
    *hl = 0;

    // Нажат выстрел
    a = menuX;
    if (b & KEY_FIRE)
    {
        if (a == [0 * menuItemH]) return gExec(hl = "city");
        if (a == [1 * menuItemH]) return intro();
        //if (a == [2 * menuItemH]) return loadGame();
        //return saveGame();
        return;
    }

    // Перемещение курсора
    if (b & KEY_UP)
    {
        a -= menuItemH;
        if (flag_c) return;
    }
    else if (b & KEY_DOWN)
    {
        a += menuItemH;
        if (a >= menuItemsM) return;
    }
    menuX = a;

    // Плавное перемещение курсора
    hl = &menuX1;
    b = *hl;
    if (a == b) return; // Оставит флаг CF при выполнении menuX1 - menuX
    b++; // Не изменяет CF
    if (flag_c) ----b;

    push(bc);
    menuDrawCursor();
    pop(bc);

    *(hl = &menuX1) = b;

    return menuDrawCursor();
}


И как это выглядит



Устаревшие исходники компилятора можно посмотреть тут

github.com/alemorf/zx_spectrum_game/tree/master/cmm

9 комментариев

avatar
CMM надо понимать C--? Кодогенерация в Z80 или в любой ассемблер? Мне кажется или у Вас уже был похожий проект но для pdp-11? Я пользовался Вашим ассемблером для него — очень удобно!
  • SAA
  • +1
avatar
Сейчас кодогенерация только в Z80 поскольку он ассемблером быть не перестал.
Но если немного поменять синтаксис, заменив необычный для Си синтаксис на обычный.

1) if (flag_z) на if (flag_z())
2) a = [1 + 2] на a = calc(1 + 2)
3) Заменить конструкции push(hl) {… } на push(hl) + pop(hl).

То будет компилироваться любым компилятором Си.
avatar
Да, был для PDP-11
avatar
Шикарная штука! Уже использовал для пробного написания рейкастера.

Монитор действительно читается в разы лучше, чем портянка после дизасма.
avatar
Хороший проект!!!
  • idxi
  • 0
avatar
Прикольно, что все асмо-кодеры в итоге к чему-то похожему приходят:)
Я на текущем этапе использую свою надстройку над sjasm-ом, код получается вот такого вида:
;печать символа
PrintSymbol		        //proc
				a = 0: out (254),a
				alt_rampage 0 : hl = (text_pos): a = (hl)
				if a++: jr nz,end
				begin
					return false
				end
				if a = (hl): or a: jr z,end
			        begin
					hl = (text_y): e = 0: DrawSymbol32
					if a = (type_with_sound): or a: jr z,end
					begin
						a = #10: out (254),a
					end
				end
				hl = (text_pos): hl++: (text_pos) = hl
				alt_rampage 0: a = (hl)
				hl = text_x: (hl)++
				if or a: jr z,begin: a = (hl): cp 33: jr nz,end
				begin
					a = (x_win): a--: (hl) = a
					hl = text_y: (hl)++
					if b = a = (y_win): a = (height_win): a += b: cp (hl): jr nz,end
					begin
						(hl)--
						hl = (y_win): bc = (height_win): ScrollWindow
					end
				end
				return true
//endproc
avatar
Работа над новыми билдами cmm ведутся же? :/
  • idxi
  • 0
avatar
Да, я часто что то изменяю. Я сейчас на этом языке игру пишу. Я пока не выгружаю последнюю версию.
avatar
Напоминает 'высокоуровневый ассемблер' для NES — NESHLA.

В asm80 Медноногова был альтернативный синтаксис, которым я одно время сильно увлекался. Там были замены add на +, = вместо LD, команды через двоеточия, и прочее подобное. Код выглядел так:

bgdTileOut; вывод знакоместа фона (bc=адрес графики, hl=адрес экрана, de=адрес атрибутов)
REP 8:a=(bc):(hl)=a:b++:h++:ENDR
ORG $-1
ex de,hl:a=(bc):(hl)=a:ex de,hl
ret
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.