Продолжаю упражняться в программировании на ассемблере. На этот раз нарисовал сетку на экране, прикрутил клавиатуру, чтобы двигать точку по экрану, не вылезая за пределы. Разумеется, всё это в графическом режиме. Потрясающий размер программы в коде: 240 байт! Массив цвета пока не трогаю. Точка перемещается по экрану с шагом в один пиксель, поддерживается диагональное перемещение при одновременном нажатии двух кнопок. Скорость перемещения такая, что все фазы перемещения точки не успевают отрисовываться на экране. Состояние VRAM изменяется быстрее, чем к ней обращается "видеокарта".

Код:
	;ПК8000
	;Прекрасный ассемблер КР580ВМ80А
	;http://asdasd.rpg.fi/~svo/i8080/

	;Программа позволяет клавишами управления курсором перемещать точку по
	;экрану. Процедура определения адреса точки в видео-ОЗУ и маски выделена
	;в отдельный модуль, который вызывается из процедур "зажигания" и 
	;"гашения" точки.

.binfile GraphCursor.bin
.org 4000h	;начальный адрес программы в ОЗУ

;---------------------------------------------------------------------------------
; Константы
;---------------------------------------------------------------------------------

	;Включение режима экрана. Точка входа в ПЗУ: 006Ah или 286Dh A=режим {0,1,2}
Screen equ 286dh

	;Опрос нажатия Упр+Стоп.3759h Если «Упр+Стоп», то флаг C:=1
CtrlBreak equ 3759h

	;Возврат в СПА
Exit equ 0ea8ah

;---------------------------------------------------------------------------------
; Переменные
;---------------------------------------------------------------------------------

GraphCursor db 0ah,0ah	;координаты графического курсора x,y (0-255,0-191)
AdressVRAMCursor db 0,0
BackgroundCursor db 0

	;область в ОЗУ, в которой сохраняется состояние клавиатурной матрицы
Keyboard db 0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh,0ffh

;---------------------------------------------------------------------------------
; Главный модуль
;---------------------------------------------------------------------------------

Start:

	mvi a,02h	;выбор номера режима
	call Screen	;установка режима 2

	call Grid	;рисуем на фоне сетку

Drawing:
	
	call GetAdressCursor	;определение адреса и маски точки в массиве графики
	shld AdressVRAMCursor
	xra b		;зажигаем точку
	mov m,a		;кладём её в видео-ОЗУ
	lxi h,BackgroundCursor
	mov m,b		;сохраняем состояние

EventLoop:
	call KeyPress	;опрос клавиатуры
	jz EventLoop

	lxi h,BackgroundCursor
	mov b,m		;восстанавливаем видео-ОЗУ
	lhld AdressVRAMCursor
	mov m,b

	;нажатие и анализ
	lxi h, Keyboard+8	;адрес строки с состоянием кнопок управления курсором
	mov b, m	;запоминаем это состояние, чтобы не дёргать ОЗУ постоянно

	mvi a, 0ffh	;сбросили клавиатуру в исходное состояние
	mov m, a

	lhld GraphCursor	;помещаем в de координаты графического курсора
	xchg
	
	mvi a, 080h	;нажата стрелка вправо?
	ana b
	cz MoveRight

	mvi a, 040h	;нажата стрелка вниз?
	ana b
	cz MoveDown


	mvi a, 020h	;нажата стрелка вверх?
	ana b
	cz MoveUp

	
	mvi a, 010h	;нажата стрелка влево?
	ana b
	cz MoveLeft

	xchg
	shld GraphCursor

	call CtrlBreak
	jnc Drawing


	mvi a,0h	;выбор номера режима
	call Screen	;установка режима 0

	call Exit	;возврат в СПА

;---------------------------------------------------------------------------------
; Процедуры
;---------------------------------------------------------------------------------

	;изменение координат графического курсора

MoveDown:
	
	mvi a, 0c0h	;нижний предел экрана x=192
	inr d		;координата y:=y+1
	cmp d
	rnz		;всё в порядке, мы внутри, штатная ситуация
	dcr d		;отмена изменения координаты курсора
	ret

MoveUp:
	mvi a, 0ffh	;верхний предел экрана y=-1 (логически)
	dcr d		;координата y:=y-1
	cmp d
	rnz		;всё в порядке, мы внутри, штатная ситуация
	inr d		;отмена изменения координаты курсора
	ret


MoveRight:
	
	xra a		;правый предел экрана x=256 (логически)
	inr e		;координата х:=х+1
	cmp e
	rnz		;всё в порядке, мы внутри, штатная ситуация
	dcr e		;отмена изменения координаты курсора
	ret


MoveLeft:
	
	mvi a, 0ffh	;левый предел экрана x=-1 (логически)
	dcr e		;координата х:=х-1
	cmp e
	rnz		;всё в порядке, мы внутри, штатная ситуация
	inr e		;отмена изменения координаты курсора
	ret
;---------------------------------




	;Процедура опроса клавиатуры и регистрации нажатых клавиш
	;Идея состоит в том, чтобы опросить состояние строк клавиатурной матрицы
	;и запомнить его в ОЗУ для дальнейшего анализа
	;если флаг Z=0, то ничего не нажато
	;hl - адрес последней строки формируемого массива
	;b - текущее состояние порта 82h
	;с - счётчик количества строк клавиатуры (0ah)
	;d - флаг нажатия. Если ничего не нажато, то 0ffh

KeyPress:
	di
	lxi h, Keyboard+9	;адрес ОЗУ, в который сохраняем состояние кнопок
	mvi c, 0ah	;счётчик количества строк клавиатуры (10)
	in 82h		;читаем порт, чтобы не потерять его состояние
	ani 0f0h	;формируем в младшем полубайте номер строки клавиатуры
	adi 09h		;опрос от 9-й строки к 0-й
	mvi d, 0ffh	;флаг нажатия. Если нажато, появляются нулевые биты

loop001:	
	out 82h		;выбираем текущую строку клавиатуры
	mov b, a	;сохраняем состояние порта 82h
	in 81h		;читаем состояние текущей строки
	mov m,a		;сохранили состояние текущей строки в массиве KeyPress
	ana d		;если есть нажатие, то появятся нулевые биты
	mov d, a	;запоминаем любые нули, важно их наличие в принципе
	dcx h		;очередной байт в массиве KeyPress
	dcr b		;очередная строка клавиатурной матрицы
	dcr c		;счётчик количества строк
	mov a,b		;формируем данные для выбора следующей строки
	jnz loop001
	
	mvi a,0ffh	;проверка наличия нажатых клавиш
	xra d		;флаг Z=0, если имеется хотя бы один нулевой бит
	ei
	ret
;---------------------------------



	;Процедура определяет адрес байта в массиве графики для графического курсора
	;Массив графики располагается в адресах ОЗУ 0000h-17ffh
	;Нумерация идёт по принципу знакомест 32х24. Знакоместо (0,0) имеет адреса
	;0000h-0007h, (0,1) 0008h-000fh, (1,0) 0100h-0107h и т.д.
	;в hl - искомый адрес видео озу, с - счётчик, номер бита точки на которую 		
	;указывает графический курсор, b - байт, извлечённый из видео-ОЗУ
	;a - маска для операции с точкой (гашение/свечение)
	;

	;Временно по найденной координате зажигается точка.


GetAdressCursor:
	lhld GraphCursor	;координаты курсора в привычной форме
	mov a,l		;взяли координату х
	ani 07h		;в аккумуляторе остаток от деления x/8
			;это - номер (положение) точки внутри байта в массиве графики
	inr a		;используется как счётчик при формировании маски
	mov c,a		;запоминаем его в регистре c

	mov a,l		;взяли повторно координату х
	ani 0f8h	;отбросили остаток от деления x/8
			;это - адрес нулевого байта соответствующего знакоместа
	mov l,a		;запоминаем его в регистре l

	mov a,h 	;взяли координату y
	ani 07h		;остаток от деления x/8 - номер строки внутри знакоместа
	add l		;прибавили его к адресу нулевого байта знакоместа
	mov l,a		;младший полубайт адреса готов.

	mov a,h		;взяли координату y
	ani 0f8h	;отбросили остаток от деления y/8
	rrc
	rrc
	rrc		;разделили нацело y/8
	mov h,a		;старший полубайт адреса готов

	xra a		;формируем в аккумуляторе маску для видеоОЗУ
	inr a		;инициализация маски. Записываем в аккумулятор 01h
loop002:
	rrc		;устанавливаем соответствующий бит маски
	dcr c		;номер бита, полученный ранее из координаты x
	jnz loop002
	mov c,a		;сохранили маску в регистр c

	;извлечение байта данных из видео-ОЗУ, сохраняем в  регистре b

	di		;обработчики прерываний в ПЗУ, поэтому запрещаем прерывания
	in 80h		;сохраняем состояние порта 80h в регистре d
	mov d,a
	mvi a,0ffh	;записываем в порт 80h единицы, тем самым выбирая ОЗУ для
	out 80h		;всех диапазонов адресов
	mov b,m		;читаем байт из видеоОЗУ в регистр b
	mov a,d		;восстанавливаем конфигурацию адресного пространства
	out 80h

	mov a,c		;кладём маску в аккумулятор
	ei		;разрешаем прерывания
	ret
;---------------------------------




	;Процедура Grid рисования сетки на экране

Grid:

	lxi h,1800h	;чтобы сработало тело цикла при нулевом значении "+1"
	mvi c,08h	;считаем попутно байты внутри знакоместа
Grid001:
	dcx h
	mvi m,81h	;рисуем вертикальные линии
	dcr c
	jnz Grid002
	mvi m,0ffh	;в нужных местах вертикальные линии заменяем горизонтальными
	mvi c,08h
Grid002:
	mvi a,07h	;в нижней строке знакоместа тоже горизонтальная линия
	cmp c
	jnz Grid003
	mvi m,0ffh
Grid003:
	mov a,l		;проверяем, досчитали до нуля или ещё нет
	ora h
	jnz Grid001
	ret
Если кому интересно, как это работает в эмуляторе, ниже файл состояния эмулятора EMU. Разархивируем, открываем файл из EMU, попадаем сразу в Debugger, в нём жмём F5, оказываемся в среде ASSM, в которой подаём команду на запуск G 400F. Возврат в ASSM по нажатию Ctrl+F12 (Упр+Стоп).

GraphCursor.zip

Следующим шагом хочу сделать плавное перемещение спрайта 8 на 8 пикселей. Что меня всегда бесило в играх на ПК8000 - дискретное "тырканье" по знакоместам. Интересно выяснить, это проблема аппаратной реализации, нехватки быстродействия, чтобы обрабатывать плавную анимацию, или криворукость программистов.