1. Друзья, в это тяжёлое и непонятное для всех нас время мы просим вас воздержаться от любых упоминаний политики на форуме, - этим ситуации не поможешь, а только возникнут ненужные ссоры и обиды. Это касается также шуток и юмора на тему конфликта. Пусть войны будут только виртуальными, а политики решают разногласия дипломатическим путём. С уважением, администрация Old-Games.RU.

    Скрыть объявление
  2. Пожалуйста, внимательно прочитайте правила раздела.
  3. Если Вы видите это сообщение, значит, вы ещё не зарегистрировались на нашем форуме.

    Зарегистрируйтесь, если вы хотите принять участие в обсуждениях. Перед регистрацией примите к сведению:
    1. Не регистрируйтесь с никами типа asdfdadhgd, 354621 и тому подобными, не несущими смысловой нагрузки (ник должен быть читаемым!): такие пользователи будут сразу заблокированы!
    2. Не регистрируйте больше одной учётной записи. Если у вас возникли проблемы при регистрации, то вы можете воспользоваться формой обратной связи внизу страницы.
    3. Регистрируйтесь с реально существующими E-mail адресами, иначе вы не сможете завершить регистрацию.
    4. Обязательно ознакомьтесь с правилами поведения на нашем форуме, чтобы избежать дальнейших конфликтов и непонимания.
    С уважением, администрация форума Old-Games.RU
    Скрыть объявление

Ассемблер i8080: уроки, практика, ПК-01

Тема в разделе "IBM PC-несовместимое", создана пользователем Zelya, 27 янв 2012.

  1. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    TheBypasser,
    Регистры действительно заполнены мусором, но мы и не расчитываем на их начальные значения, а каждый раз заполняем нужными данными. Обнулять их в начале программы не имеет смысла. Кстати, в памяти тоже мусор. Поэтому в цикле инициализации игрового поля мы заполняем каждый байт, который будем использовать.
     
  2. TheBypasser

    TheBypasser

    Регистрация:
    7 июл 2006
    Сообщения:
    756
    Со сна посмотрел криво, реально ж весь диапазон забивается :)
     
  3. Steel Rat Stainless

    Steel Rat

    Регистрация:
    28 дек 2006
    Сообщения:
    3.260
    Извините, никак не мог проигнорировать, но как десантник и программист в одном теле просто весь рвусь шаблонами. Чем же мы вам так не угодили?
     
    Helmut, SAS и Grue13 нравится это.
  4. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    Урок 6. Персонаж на поле. Работа с координатами. Анимация перемещения.

    Сегодня мы попробуем поместить анимированного врага на поле, пока, с упрощенной логикой поведения. Но для начала немного оптимизируем процедуру draw. Раньше она получала три параметра, L - X, H - Y, B - цвет. Теперь, с картой в памяти, будем вычитывать цвет оттуда. Так что начало процедуры теперь выглядит так:

    Код:
    draw:	
    	MOV B, M
    	MVI A, 0	;Video on
    	OUT 0C2h	;Video on
    А код заполнения карты больше не будет использовать регистр B:

    Код:
    	MVI H,33
    init1:
    	MVI L,33
    init2: 	PUSH H
    	MOV A,L
    	CPI 0
    	JZ init_put127
    	CPI 33
    	JZ init_put127
    	MOV A,H
    	CPI 0
    	JZ init_put127
    	CPI 33
    	JZ init_put127
    	JMP init3
    init_put127:
    	MVI M, 127	;(7F)
    	JMP init4	;Don't draw
    init3:
    	MVI M,15	;Temporary use 15 (0F)
    	MOV A,L
    	CPI 3
    	JM init5	;Don't put 0
    	CPI 31
    	JP init5	;Don't put 0
    	MOV A,H
    	CPI 3
    	JM init5	;Don't put 0
    	CPI 31
    	JP init5	;Don't put 0
    	MVI M, 0
    init5:	
    	CALL draw
    
    init4:	POP H
    	DCR L
    	JP init2
    	DCR H
    	JP init1
    
    Для врага нам потребуются четыре переменные, которые будут размешать в памяти его положение и скорость по осям X и Y. В начале программы разместим код:

    Код:
    	ENEMY_X = 9C40h ;40000
    	ENEMY_Y = 9C41h ;40001
    	ENEMY_SPD_X = 9C42h ;40002
    	ENEMY_SPD_Y = 9C43h ;40003
    
    	MVI A, 3
    	STA ENEMY_X
    	MVI A, 4
    	STA ENEMY_Y
    
    	MVI A, 1
    	STA ENEMY_SPD_X
    	MVI A, 1
    	STA ENEMY_SPD_Y
    
    Сначала мы привязываем переменные к конкретым адресам в памяти, а потом заносим в них данные. Пусть, начальное положение будет (3,4), а скорость (1,1). Цвет противника назначим красным (255). Как выглядит перемещение по полю: в клетку, где находится персонаж, нужно поставить 0 (пустое поле); вызвать draw; определить новое место, прибавив скорость; поставить там 255 и снова вызвать draw. В виде процедуры приблизительно так:

    Код:
    enemy_move:
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	MVI M, 0
    	CALL draw
    
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	STA ENEMY_X
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	STA ENEMY_Y
    	MVI M, 255
    	CALL draw
    	RET
    С одним важным замечанием: когда противник дойдет до конца свободной области, он должен "отбиться". Пока, мы упростим этот процесс, и противник просто поменяет скорость на противоположную. "Отбивание" реализируем в следующем уроке. Процедуры будут выглядеть так:

    Разберем написаный код. speed_back - это вспомогательная процедура. Она получает в аккумуляторе текущее значение, а на выходе выдает обратное. Как вы, наверное, заметили обратное значение 255, практически аналогично -1. Почему, думаю объяснять не надо. enemy_speed вначале загружает значение координат в HL, после чего меняет их согласно скоростей. Дальше мы читаем паямть в аккумулятор для сравнения. После CPI 0 идет незнакомый нам опертор RZ. Ничего сложного в нем нет, он почти полностью соответствует JZ - проверки на ноль. Только вместо перехода по адресу, вызывается RET и мы покидаем процедуру , если клетка свободна. Если же она не нулевая, мы вызываем speed_back для скорости по X и Y. Осталось добавить вызов enemy_speed в enemy_move:
    Код:
    enemy_move:
    	CALL enemy_speed
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	MVI M, 0
    	CALL draw
    
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	STA ENEMY_X
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	STA ENEMY_Y
    	MVI M, 255
    	CALL draw
    	RET
    Процедура, ответственная за анимацию врага готова. Нужно только поместить ее вызов в main_cycle:

    Код:
    main_cycle:
    	MVI A, 20
    pause1:	MVI B, 255
    pause2:	DCR B
    	JNZ pause2
    	DCR A
    	JNZ pause1
    	
    	CALL enemy_move
    	JMP main_cycle
    Обратите внимание, что кроме вызова процедуры enemy_move я добавил двойной цикл. Несмотря на всю медленность ПК-01, пока, мы проводим слишком мало вычислений и анимация получится очень быстрой. Данный цикл служит обычной паузой. Резултат:

    [​IMG]


    Вся программа:

    Код:
    	ENEMY_X = 9C40h ;40000
    	ENEMY_Y = 9C41h ;40001
    	ENEMY_SPD_X = 9C42h ;40002
    	ENEMY_SPD_Y = 9C43h ;40003
    
    	MVI A, 3
    	STA ENEMY_X
    	MVI A, 4
    	STA ENEMY_Y
    
    	MVI A, 1
    	STA ENEMY_SPD_X
    	MVI A, 1
    	STA ENEMY_SPD_Y
    
    	MVI H,33
    init1:
    	MVI L,33
    init2: 	PUSH H
    	MOV A,L
    	CPI 0
    	JZ init_put127
    	CPI 33
    	JZ init_put127
    	MOV A,H
    	CPI 0
    	JZ init_put127
    	CPI 33
    	JZ init_put127
    	JMP init3
    init_put127:
    	MVI M, 127	;(7F)
    	JMP init4	;Don't draw
    init3:
    	MVI M,15	;Temporary use 15 (0F)
    	MOV A,L
    	CPI 3
    	JM init5	;Don't put 0
    	CPI 31
    	JP init5	;Don't put 0
    	MOV A,H
    	CPI 3
    	JM init5	;Don't put 0
    	CPI 31
    	JP init5	;Don't put 0
    	MVI M, 0
    init5:	
    	CALL draw
    
    init4:	POP H
    	DCR L
    	JP init2
    	DCR H
    	JP init1
    
    main_cycle:
    
    	MVI A, 20
    pause1:	MVI B, 255
    pause2:	DCR B
    	JNZ pause2
    	DCR A
    	JNZ pause1
    	
    	CALL enemy_move
    	JMP main_cycle
    
    enemy_move:
    	CALL enemy_speed
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	MVI M, 0
    	CALL draw
    
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	STA ENEMY_X
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	STA ENEMY_Y
    	MVI M, 255
    	CALL draw
    	RET
    
    enemy_speed:
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	MOV A, M
    	CPI 0
    	RZ
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    	LDA ENEMY_SPD_Y
    	CALL speed_back
    	STA ENEMY_SPD_Y
    	RET
    
    speed_back:
    	CPI 1
    	JNZ speed_back1
    	MVI A, 255
    	RET
    speed_back1:
    	MVI A, 1
    	RET
    
    draw:	
    	MOV B, M
    	MVI A, 0	;Video on
    	OUT 0C2h	;Video on
    
    	DCR L		;Coordinate -1
    	DCR H		;Coordinate -1
    	
    	MOV A,L
    	RLC
    	MOV L,A
    
    	MOV A,H
    	RLC
    	MOV H,A
    
    	MVI A, 64
    	ADD H
    	MOV H,A
    
    	LXI D, 63	
    	MVI A, 8	
    draw_cycle:
    	MOV M, B	
    	INX H		
    	MOV M, B	
    	DAD D		
    	DCR A		
    	JNZ draw_cycle
    	MVI A, 2	;Video off
    	OUT 0C2h	;Video off
    	RET
    Также, предвижу вопрос. Мы часто вычитываем координаты в HL, было б удобно вынести их в процедуру.

    Код:
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    Это абсолютно правильное замечание, с одним большим "НО". В процессе движения у нас есть момент, когда старая позиция уже зарисована, а новая еще не появилась. Это причина возможного эффекта "мигания". Чтобы эффект был максимально незаметным, нужно сократить время исполнения программы во время смены позиции. А вызов процедуры, немного увеличивает это время. Так что удобство тут неуместно.
    В следующем уроке мы сделаем "честное отбивание" и увеличим количество персонажей. Вопросы?
     
    Последнее редактирование модератором: 19 июл 2015
    AndyFox, A.P.$lasH, kreol и 7 другим нравится это.
  5. TheBypasser

    TheBypasser

    Регистрация:
    7 июл 2006
    Сообщения:
    756
    Кстати!
    Сам так писать люблю иногда (особенно желании организовать перекрытие) - но всё же. Есть ли директивы препроцессора, чтоб в духе:
    ?
     
  6. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    TheBypasser
    Нет, к сожалению, в "студии" они не предусмотрены. Но если будет настойчивое желание - можно добавить. А теперь перейдем к уроку:

    Урок 7. Отбивания


    Сегодня, как и планировалось, реализируем "честные" отбивания врагов. Для начала рассмотрим как движется персонаж. Движение происходит всегда по диагонали. При достижении препятствия одна или обе скоростей (по X и Y) могут отразиться на противоположную (1-255), но никогда не стать нулем. Всего вариантов отражения есть три:

    [​IMG]

    На рисунке красным цветом нарисован враг, синим знаятая область, белым - свободная (просите уже после написания урока заметил, что одна стрелочка не красная, а черная - это ничего не значит). Первые два рисунка относятся к ситуации, когда отражаются обе скорости, вторая, когда только X, третья - Y. На тип отражения влияют три клетки, я пометил их буквами A, B, C. Понятно, что клетка A должна быть занята, иначе персонаж просто пролетит дальше. Если клетки B и C одинаковы - имеем первый вариант, если нет, то выбираем один из оставшихся. Внесем этот код в процедуру enemy_speed

    Код:
    enemy_speed:
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	MOV A, M
    	CPI 0
    	RZ
    	
    	MOV D,L        
    	LDA ENEMY_X    ;======
    	MOV L,A        ;Store B
    	MOV B,M        ;======
    	
    	MOV L,D
    	
    	LDA ENEMY_Y    ;======
    	MOV H,A        ;Store C
    	MOV C,M        ;======
    	
    	MOV A,B
    	CMP C
    	JZ back_xy
    	CPI 0
    	JNZ back_y
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    	RET
    back_xy:
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    back_y:	LDA ENEMY_SPD_Y
    	CALL speed_back
    	STA ENEMY_SPD_Y
    	RET
    Командами CPI 0; RZ мы проверям клетку A. Если она свободна, RZ возвращает нас из процедуры не изменив скорость. Дальше мы занесем значения клеток B и C в одноименные регистры. Чтобы меньше обращаться к памяти (это одна из самых медленных функций) поступим следующим образом. Мы уже имеем в паре HL клетку A, место куда хочет попасть враг, и которое мы вычислили прибавив обе скорости. Запомним координату X (регистр L) в регистр D, потом вычитываем старое значение координаты X. Имея "новый" Y и "старый" X мы получаем как раз клетку B. Ее значение сохраняем в регистре. Потом возвращаем в L "новый" X (из регистра D), и повторяем логику для Y. В результате ргистры B и C заполнены нужными данными. Преносим B в аккумулятор, и вызываем оператор CMP C. Этот оператор схож с уже известным нам CPI, только он сравнивает аккумулятор не с числом, а с другим регистром. Так вот, если B одинкаов с C "джампаем" на строчку back_xy, из названия которой понятно, что мы меняем скорость как по X так и по Y. Иначе проверяем B (который уже скопирован в аккумулятор) на ноль (свободное поле). Если поле не ноль (закрашено) JNZ back_y - отражаем только Y. Ну и самый последний вариант - отражаем только X.
    Но и это еще не все. Просчитав отбивание по всем канонам, мы можем попасть в уже закрашенную клетку, либо занятую другим персонажем. Чтобы избежать проблем, в процедуру нужно добавить проверку, дейстивтельно ли клетка на которую мы попадем свободна. Если нет то нужно попробовать вернуться назад. Если и это не удается (персонаж зажатый своими товарищами) остаемся этот ход на месте.
    Для начала адаптируем процедуру enemy_speed, чтобы после ее исполнения, координаты, куда переместится персонаж, заносились в HL:

    Код:
    enemy_speed:
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	MOV A, M
    	CPI 0
    	RZ
    	
    	MOV D,L        
    	LDA ENEMY_X    ;======
    	MOV L,A        ;Store B
    	MOV B,M        ;======
    	
    	MOV L,D
    	
    	LDA ENEMY_Y    ;======
    	MOV H,A        ;Store C
    	MOV C,M        ;======
    	
    	LDA ENEMY_X    
    	MOV L,A        
    	MOV A,B
    	CMP C
    	JZ back_xy
    	CPI 0
    	JNZ back_y
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    	ADD L
    	MOV L,A
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	RET
    back_xy:
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    back_y:	LDA ENEMY_SPD_Y
    	CALL speed_back
    	STA ENEMY_SPD_Y
    	ADD H
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	RET
    Теперь нам будет намного проще проверять свободна ли клетка, куда мы метим. Пришло время переделать процедуру enemy_move:

    Код:
    enemy_move:
    	LDA ENEMY_X		;Draw empty
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	MVI M, 0
    	CALL draw
    
    	CALL enemy_speed	;First attempt to move
    	MOV A, M
    	CPI 0
    	JZ en_mov_fin
    
    	CALL enemy_speed	;Second attempt to move
    	MOV A, M
    	CPI 0
    	JZ en_mov_fin
    
    	CALL enemy_speed	;Third attempt to move
    	MOV A, M
    	CPI 0
    	JZ en_mov_fin
    
    	LDA ENEMY_X		;No move
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    
    en_mov_fin:
    	MOV A,L			;Save position. Draw enemy
    	STA ENEMY_X
    	MOV A,H
    	STA ENEMY_Y
    	MVI M, 255
    	CALL draw
    	RET
    Первая часть процедуры - зарисовка "старого" места. Вторая часть - попытка совершить движение по всем правилам. Если все нормально - переходим к пятой чатсти. Иначе, пробуем "отбиться" обратно. Причем мы должны пробовать "отбиваться" три раза. Попробую пояснить, почему. Смотрим на рисунок:
    [​IMG]
    Первая попытка "отбивания" (желтая) - некорректная.
    Вторая (зеленая) - тоже.
    И только третья попытка возвращает нас в противоположную сторону, откуда прилетел враг. Если и эта позиция занята, тогда не остается ничего, как пропустить ход (четвертая часть процедуры): координаты остаются теми же. Ну и пятая часть - сохраняем новую позицию из HL и рисуем персонажа. Всё! Вот полный листинг программы с "честным" отбиванием:

    Код:
    	ENEMY_X = 9C40h ;40000
    	ENEMY_Y = 9C41h ;40001
    	ENEMY_SPD_X = 9C42h ;40002
    	ENEMY_SPD_Y = 9C43h ;40003
    
    
    	MVI A, 3
    	STA ENEMY_X
    	MVI A, 4
    	STA ENEMY_Y
    
    	MVI A, 1
    	STA ENEMY_SPD_X
    	MVI A, 1
    	STA ENEMY_SPD_Y
    
    	MVI H,33
    init1:
    	MVI L,33
    init2: 	PUSH H
    	MOV A,L
    	CPI 0
    	JZ init_put127
    	CPI 33
    	JZ init_put127
    	MOV A,H
    	CPI 0
    	JZ init_put127
    	CPI 33
    	JZ init_put127
    	JMP init3
    init_put127:
    	MVI M, 127	;(7F)
    	JMP init4	;Don't draw
    init3:
    	MVI M,15	;Temporary use 15 (0F)
    	MOV A,L
    	CPI 3
    	JM init5	;Don't put 0
    	CPI 31
    	JP init5	;Don't put 0
    	MOV A,H
    	CPI 3
    	JM init5	;Don't put 0
    	CPI 31
    	JP init5	;Don't put 0
    	MVI M, 0
    init5:	
    	CALL draw
    
    init4:	POP H
    	DCR L
    	JP init2
    	DCR H
    	JP init1
    
    main_cycle:
    
    	MVI A, 20
    pause1:	MVI B, 255
    pause2:	DCR B
    	JNZ pause2
    	DCR A
    	JNZ pause1
    	CALL enemy_move
    	JMP main_cycle
    
    enemy_move:
    	LDA ENEMY_X		;Draw empty
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	MVI M, 0
    	CALL draw
    
    	CALL enemy_speed	;First attempt to move
    	MOV A, M
    	CPI 0
    	JZ en_mov_fin
    
    	CALL enemy_speed	;Second attempt to move
    	MOV A, M
    	CPI 0
    	JZ en_mov_fin
    
    	LDA ENEMY_X		;No move
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    
    en_mov_fin:
    	MOV A,L			;Save position. Draw enemy
    	STA ENEMY_X
    	MOV A,H
    	STA ENEMY_Y
    	MVI M, 255
    	CALL draw
    	RET
    
    enemy_speed:
    	LDA ENEMY_X
    	MOV L,A
    	LDA ENEMY_Y
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	MOV A, M
    	CPI 0
    	RZ
    	
    	MOV D,L        
    	LDA ENEMY_X    ;======
    	MOV L,A        ;Store B
    	MOV B,M        ;======
    	
    	MOV L,D
    	
    	LDA ENEMY_Y    ;======
    	MOV H,A        ;Store C
    	MOV C,M        ;======
    	
    	LDA ENEMY_X    
    	MOV L,A        
    	MOV A,B
    	CMP C
    	JZ back_xy
    	CPI 0
    	JNZ back_y
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    	ADD L
    	MOV L,A
    	LDA ENEMY_SPD_Y
    	ADD H
    	MOV H, A
    	RET
    back_xy:
    	LDA ENEMY_SPD_X
    	CALL speed_back
    	STA ENEMY_SPD_X
    back_y:	LDA ENEMY_SPD_Y
    	CALL speed_back
    	STA ENEMY_SPD_Y
    	ADD H
    	MOV H,A
    	LDA ENEMY_SPD_X
    	ADD L
    	MOV L, A
    	RET
    
    speed_back:
    	CPI 1
    	JNZ speed_back1
    	MVI A, 255
    	RET
    speed_back1:
    	MVI A, 1
    	RET
    
    draw:	
    	MOV B, M
    	MVI A, 0	;Video on
    	OUT 0C2h	;Video on
    
    	DCR L		;Coordinate -1
    	DCR H		;Coordinate -1
    	
    	MOV A,L
    	RLC
    	MOV L,A
    
    	MOV A,H
    	RLC
    	MOV H,A
    
    	MVI A, 64
    	ADD H
    	MOV H,A
    
    	LXI D, 63	
    	MVI A, 8	
    draw_cycle:
    	MOV M, B	
    	INX H		
    	MOV M, B	
    	DAD D		
    	DCR A		
    	JNZ draw_cycle
    	MVI A, 2	;Video off
    	OUT 0C2h	;Video off
    	RET
    Рекомендую потестировать меняя стартовые координаты и расставляя дополнительные препятствия. Например, строки

    Код:
    	MVI H,29
    	MVI L,30
    	MVI M,15
    	CALL draw
    продемонстрируют алгоритм в действии.

    Урок получился немного коротким, но и довольно сложноватым. Как всегда рад вопросам.
     
    Последнее редактирование модератором: 19 июл 2015
    AndyFox, A.P.$lasH, hobot и 7 другим нравится это.
  7. Shane

    Shane

    Регистрация:
    20 сен 2012
    Сообщения:
    2
    К сожалению не понял команду DCR A. (понятно она уменьшает на 1 содержимое аккумулятора)
    У меня немного другой пример: DCR A, если в (А)=00Н - чему будет равно содержимое аккумулятора и причем тут (Н)
    Извиняюсь заранее за нубский вопрос
     
  8. Bato-San Чеширский волк-киборг

    Bato-San

    Регистрация:
    24 июн 2010
    Сообщения:
    14.136
    Shane, H - указывает ассемблеру, что число шестнадцатиричное (Hex).
    А 00h - 01h = FFh и установленный флаг S (знак).
    Таким образом можно интерпретировать это число как 255 или как -1.
     
    Последнее редактирование: 21 сен 2012
  9. MisterGrim Very old

    MisterGrim

    Legacy

    Регистрация:
    29 ноя 2007
    Сообщения:
    25.423
    Вообще-то как -1.
     
    Bato-San нравится это.
  10. Shane

    Shane

    Регистрация:
    20 сен 2012
    Сообщения:
    2
    Спасибо большое - оказалось то все просто :)
     
  11. Bato-San Чеширский волк-киборг

    Bato-San

    Регистрация:
    24 июн 2010
    Сообщения:
    14.136
    hobot и Shane нравится это.
  12. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    Несколько месяцев из-за событий в личной жизни совершенно не имел времени заниматься любимым ПК-01. Сейчас лед постепенно оттаивает. За это время получил довольно много писем, мол а где новые уроки-то, что стало для меня сюрпризом. Приятным сюрпризом, не спорю :). К сожалению, продолжить в том же русле начатое будет немного проблематично. Старая "студия" уже давно мной брошена, я сам перешел на новую версию. Но и новая не доведена до ума. А доводка может занять, как показывает пркатика, вего лишь вечность. Поэтому созрела следующая идея-пердложение: а если перевести все уроки на нейтральную среду разработки? Например, можно использовать, как стандарт, вот такую прелестную штучку:
    http://asdasd.rpg.fi/~svo/i8080/
    Pretty 8080 Assebler на джаваскриптах. Во-первых, мультиплатформенность; во-вторых, на любой сайт прицепить можно, или даже в оффлайне просто в браузере использовать; в-третьих открытый код компилятора.
    Конечно, такой вариант несет некоторые неудобства. Чтобы оттестить программу, ее нужно скомпилить и подсунуть какому-нибудь эмулятору. Дебаг только через эмулятор. Проблемы работы с файлами. Некоторые возможные ограничения и ошибки компилятора.
    Возможно есть предложения по какой-нибудь другой, более удобной среде разработки. С удовольствием выслушаю мнения.
     
    AndyFox, A.P.$lasH и Dimouse нравится это.
  13. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    Если всех устраивает Pretty Assembelr, то вследующий вопрос. Как лучше поступить, переправить существующие уроки, или потереть тему и начать пискать Ксоникс по-новому?
     
  14. Dimouse King of Mice

    Dimouse

    Администратор Переводчик

    Регистрация:
    18 апр 2003
    Сообщения:
    34.625
    Zelya, ну, тереть-то точно ничего не надо. А "пискать" надо. Насчет ассемблера что-то сказать затрудняюсь, тут уж тебе виднее.
     
  15. Bato-San Чеширский волк-киборг

    Bato-San

    Регистрация:
    24 июн 2010
    Сообщения:
    14.136
    Zelya, посмотрел как предложенная тобой хрень компилирует несуществующие команды и подумал, что привязывать уроки к конкретной студии вообще глупо. Проще закрепить шапку темы и указать в ней несколько инструментов.
     
  16. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    А по другому никак. 90% синтаксиса, конечно, сохранится. Тем не менее, в Pretty Assempler, например, нужно добавлять хедер, который моя студия генерила автоматически. Плюс еще некоторые отличия. В зависимости от среды, по-разному организовывается работа с файлами. Так же людям нужно указать, как потестить программу, как дебагать. Конечно, освоив основы, можно будет гораздо легче поменять студию. Но для азов нужно конкретно указывать, что нажать, чтобы получить результат, и тут без привязки к среде не обойдешся.
     
    daemolisher нравится это.
  17. Bato-San Чеширский волк-киборг

    Bato-San

    Регистрация:
    24 июн 2010
    Сообщения:
    14.136
    если человек решил заняться программированием - читать документацию он наверняка умеет, как и уверенно обращаться с компьютером. Если человек этого не умеет - его желание "быть программистом" эквивалентно пустой прихоти. Он просто не знает о чём речь. Поэтому не надо делать из учеников "щенят" и ходить за ними с тряпочкой, подтирая лужицы. Наоборот - надо их активно носом в эти лужицы тыкать, что бы совершенствовались самостоятельно. Это уж, если такое отношение.

    Ну научишь ты кого то программировать в конкретной среде и не более того. Вроде бы человек знает асм. Угу. Другая среда и... нет программиста. А виноват - учитель.
     
  18. daemolisher

    daemolisher

    Регистрация:
    2 дек 2009
    Сообщения:
    1.704
    "лучшее - враг хорошего" или как там говорят....
    "не чини то, что работает" и прочие пословицы....

    всё верно.
    лучше писать универсальную теретическую часть урока,
    а практическую часть урока разъяснять подробно только для одной единтсвенной среды

    лучше доступное описание работы в одной среде, чем сумбурное - во многих средах программирования.
     
  19. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    Почти Урок 7.5. Переход на Pretty 8080 Assembler. Исправление ошибок.
    Для начала хотел бы извиниться за допущенный бег в последнем уроке. Я пишу код одновременно с выкладыванием его на форум, пытаясь еще и оптимизировать "на лету" поэтому от ошибок не застрахован. Жертвой подобной "оптимизации" и стала процедура enemy_move. В ней я написал две попыкти "отбивания" по-честному. После них, считается, что проход заблокирован и пропускается ход. Где же была проблема. А вот где:
    [​IMG]
    Смторим, что происходит при такой конфигурации клеток.
    Первая попытка отбиться (желтая) - неудачная.
    Вторая (зеленая) - тоже.
    Согласно моей неправильной логике, враг считался бы заблокированным, пропустил бы ход и попробовал бы выбраться только уже в следующей итерации игрового цикла. Кончено, это не критическая проблема (в следующей итреации он бы все-таки выбрался), но такое поведение не отвечает правильной игровой логике. Поэтому я добавил третью пробу (фиолетовую). Она будет проверять то место, откуда прилетел враг, и если и оно заблокировано, то тогда уже никуда не денешься. Я уже поправил седьмой урок и теперь там правильная процедура.
    Теперь несколько слов о Pretty 8080 Assembler. Чтобы адаптировать наш код и скомпилировать его Pretty ассемблером, нужно:
    Во-первых, заменить знак равенства "=" на ключекове слово "equ".
    Во-вторых. Для студии мы указывали адреса начала программы, ее название и т.д. в свойствах проекта. Здесь проекта нет, пожтому нужно добваить следующий заголовок в начале программы:
    Код:
    begin      .equ   08000h ;start address
    
          .org   begin-16-6
    
          .db "LVOV/2.0/"
          .db 0D0h
          .db "XONIX "
          .dw begin,end-1,start
    start:
    
    Что он означает. Первой инструкцей begin .equ 08000h мы присваиваем значение старта нашей программы. Дальше, мы отступаем назад на 16+6 байт .org begin-16-6 - это размер заголовка *.lvt файла. Сам заголовок состоит из двух частей:
    1. Строчки (16 байт):
    *Надписи: "LVOV/2.0/" (9 байт)
    *Байт: 0D0h (1 байт)
    *Название файла: "XONIX " (6 байт)
    2. Технические данные (6 байт):
    *Начало файла: переменная begin
    *Конец файла: переменная end
    *Старт программы (не обзяательно совпадает с началом файла): переменная start
    Две из трех переменных уже оглашены: begin и start, осталось только огласить строчку end, поставив ее в самый конец программы.
    Вот и все. Приведу полный листинг:
    Код:
    begin      .equ   08000h ;start address
    
          .org   begin-16-6
    
          .db "LVOV/2.0/"
          .db 0D0h
          .db "XONIX "
          .dw begin,end-1,start
    start:
    
    ENEMY_X equ 9C40h ;40000
        ENEMY_Y equ 9C41h ;40001
        ENEMY_SPD_X equ 9C42h ;40002
        ENEMY_SPD_Y equ 9C43h ;40003
    
        MVI A, 4
        STA ENEMY_X
        MVI A, 3
        STA ENEMY_Y
    
        MVI A, 1
        STA ENEMY_SPD_X
        MVI A, 1
        STA ENEMY_SPD_Y
    
        MVI H,33
    init1:
        MVI L,33
    init2: 	PUSH H
        MOV A,L
        CPI 0
        JZ init_put127
        CPI 33
        JZ init_put127
        MOV A,H
        CPI 0
        JZ init_put127
        CPI 33
        JZ init_put127
        JMP init3
    init_put127:
        MVI M, 127	;(7F)
        JMP init4	;Don't draw
    init3:
        MVI M,15	;Temporary use 15 (0F)
        MOV A,L
        CPI 3
        JM init5	;Don't put 0
        CPI 31
        JP init5	;Don't put 0
        MOV A,H
        CPI 3
        JM init5	;Don't put 0
        CPI 31
        JP init5	;Don't put 0
        MVI M, 0
    init5:	
        CALL draw
    
    init4:	POP H
        DCR L
        JP init2
        DCR H
        JP init1
    
    main_cycle:
    
        MVI A, 20
    pause1:	MVI B, 255
    pause2:	DCR B
        JNZ pause2
        DCR A
        JNZ pause1
        
        CALL enemy_move
        JMP main_cycle
    
    enemy_move:
        LDA ENEMY_X		;Draw empty
        MOV L,A
        LDA ENEMY_Y
        MOV H,A
        MVI M, 0
        CALL draw
    
        CALL enemy_speed	;First attempt to move
        MOV A, M
        CPI 0
        JZ en_mov_fin
    
        CALL enemy_speed	;Second attempt to move
        MOV A, M
        CPI 0
        JZ en_mov_fin
    
        CALL enemy_speed	;Third attempt to move
        MOV A, M
        CPI 0
        JZ en_mov_fin
    
        LDA ENEMY_X		;No move
        MOV L,A
        LDA ENEMY_Y
        MOV H,A
    
    en_mov_fin:
        MOV A,L			;Save position. Draw enemy
        STA ENEMY_X
        MOV A,H
        STA ENEMY_Y
        MVI M, 255
        CALL draw
        RET
    
    enemy_speed:
        LDA ENEMY_X
        MOV L,A
        LDA ENEMY_Y
        MOV H,A
        LDA ENEMY_SPD_X
        ADD L
        MOV L, A
        LDA ENEMY_SPD_Y
        ADD H
        MOV H, A
        MOV A, M
        CPI 0
        RZ
        
        MOV D,L        
        LDA ENEMY_X    ;======
        MOV L,A        ;Store B
        MOV B,M        ;======
        
        MOV L,D
        
        LDA ENEMY_Y    ;======
        MOV H,A        ;Store C
        MOV C,M        ;======
        
        LDA ENEMY_X    
        MOV L,A        
        MOV A,B
        CMP C
        JZ back_xy
        CPI 0
        JNZ back_y
        LDA ENEMY_SPD_X
        CALL speed_back
        STA ENEMY_SPD_X
        ADD L
        MOV L,A
        LDA ENEMY_SPD_Y
        ADD H
        MOV H, A
        RET
    back_xy:
        LDA ENEMY_SPD_X
        CALL speed_back
        STA ENEMY_SPD_X
    back_y:	LDA ENEMY_SPD_Y
        CALL speed_back
        STA ENEMY_SPD_Y
        ADD H
        MOV H,A
        LDA ENEMY_SPD_X
        ADD L
        MOV L, A
        RET
    
    speed_back:
        CPI 1
        JNZ speed_back1
        MVI A, 255
        RET
    speed_back1:
        MVI A, 1
        RET
    
    draw:	
        MOV B, M
        MVI A, 0	;Video on
        OUT 0C2h	;Video on
    
        DCR L		;Coordinate -1
        DCR H		;Coordinate -1
        
        MOV A,L
        RLC
        MOV L,A
    
        MOV A,H
        RLC
        MOV H,A
    
        MVI A, 64
        ADD H
        MOV H,A
    
        LXI D, 63	
        MVI A, 8	
    draw_cycle:
        MOV M, B	
        INX H		
        MOV M, B	
        DAD D		
        DCR A		
        JNZ draw_cycle
        MVI A, 2	;Video off
        OUT 0C2h	;Video off
        RET
    end:
    Этот код можно вставить в претти ассемблер и нажать кнопку "Make Beautiful Code". Не забудьте перед этим убрать фокус с окошка с кодом, а то может скомпилить старые сорсы. Вот и все. На выходе нам предложат сохранить файл "test.bin". Его можно просто переименовать в "test.lvt" и запустить любым эмулятором, которые умеет ПК-01 Львов.

    Жду вопросов о переходе.
     
    Последнее редактирование модератором: 19 июл 2015
    AndyFox, A.P.$lasH, Val07og и 2 другим нравится это.
  20. Zelya

    Zelya

    Регистрация:
    20 апр 2007
    Сообщения:
    722
    Урок 8. Оптимизация. Непосредственные данные.
    Если кто-то дотянул до этого урока, то его уже ничем не испугаешь. Пожтому я чуть-чуть оптимизирую написанный код, чтобы гуру ассемблера не думали, что их учат веники взяать. И так, оптимизация коснется двух мест. Во-первых, процедура
    Код:
    speed_back:
        CPI 1
        JNZ speed_back1
        MVI A, 255
        RET
    speed_back1:
        MVI A, 1
        RET
    не нужна совсем. Да, она нагляда и понятна любому программисту. Но весь этот огромный код заменяется одним оператором. Да, да. Везде, где мы вызывали эту процедуру CALL speed_back можно поставить просто XRI 11111110b, а саму процедуру удалить. Что же это за магия? Рассмотрим подробнее. Оператор XRI, согласно мануалу проводит логический XOR между аккумулятором A и любы байтовым числом. В данном случае мы используем число 11111110b - это двоичная запись для 254. Сейчас объясню, почему именно это число и почему двоичная запись более удобная в данном варианте. И так, что делает оператор XOR. Для каждого бита будет следующее действие:

    0 XOR 0 = 0
    0 XOR 1 = 1
    1 XOR 0 = 1
    1 XOR 1 = 0

    Что делала процедура speed_back? Она меняла значения аккумулятора между 1 и 255, в двоичной записи: 00000001b и 11111111b. Легко заметить, что нам достаточно инвертировать первые семь бит, чтобы получить тот же результат. Как этого достичь? Теперь, думаю очевидно. Мы делаем логический XOR аккумулятора с числом, где последний бит равен 0 (и в результате наш последний бит аккумулятора всегда будет равен 1). А первые семь бит будут единичками, что позволит менять их занчение то на нолик то на единичку. Вот и вся магия. Получается, что

    1 XOR 11111110b = 255
    255 XOR 11111110b = 1

    Вторая оптимизация будет попроще. Мы имеем целую кучу мест, где читаем координаты ENEMY_X и ENEMY_Y в HL, таким макаром:
    Код:
        LDA ENEMY_X
        MOV L,A
        LDA ENEMY_Y
        MOV H,A
    Код правильный, но занимает слишком много тактов. Оптимизировать его нам поможет великолепная команда LHLD (Load HL Directly). Эта команда загружает сразу два байта, начиная от указанного адреса в HL. Первый - в L, второй в H. Например, если у нас по адресу 1000 находится число 1, а по адресу 1001 чило 2, то LHLD 1000 поместит 1 в L, и 2 в H. Так как нащи координаты ENEMY_X, ENEMY_Y идут в памяти друг за другом, то достаточно вызвать LHLD ENEMY_X, чтобы загрузить сразу оба значения в нужные регистры, минуя аккумулятор.
    Аналогично, сохранение координат:
    Код:
        MOV A,L		
        STA ENEMY_X
        MOV A,H
        STA ENEMY_Y
    
    Можно заменить командой SHLD ENEMY_X (SaveHL Directly). Так же хочу отметить, что такая прекрасная процедура считки/записи двух байт доступна только для пары HL.
    Теперь рассмотрим, как вставить в программу непосредственные чила-данные. В предыдущем уроке по миграции на новый компилятор мы уже встретили непонятные нам ключевые слова .db (Data Byte), студия их так же поддерживает. Это слово позволяет писать далее байты данны в числовом виде. Например:
    Код:
    do_read:
      LDA my_data    ;A=100
      LDA my_data+1  ;A=101
      LHLD my_data   ;L=100, H=101, HL = 25956
      RET
    my_data:
    .db 100,101
    
    Более того, таким макаром, можно писать не только данные, но и код (i8080 не разделяет эти понятия). Например, мы знаем, что инструкция RET имеет код 0C9h (201). Мы можем переписать пердыдущую процедуру в таком виде:
    Код:
    do_read:
      LDA my_data    ;A=100
      LDA my_data+1  ;A=101
      LHLD my_data   ;L=100, H=101, HL = 25956
    .db 0C9h
    my_data:
    .db 100,101
    
    Но такой стиль программирования, конечно, не рекомендуется.
    Аналогично, ключевому слову .db существует слово .dw (Data Word), которое позволяет писать 16-битные числа, занимая два байта. Например, предыдущая процедура может иметь вид:
    Код:
    do_read:
      LDA my_data    ;A=100
      LDA my_data+1  ;A=101
      LHLD my_data   ;L=100, H=101, HL = 25956
      RET
    my_data:
    .dw 25956
    
    Теперь мы обладаем багажом знаний, чтобы продолжить программирование нашей игры.
     
    AndyFox, A.P.$lasH, Dimouse и ещё 1-му нравится это.
  1. На этом сайте используются файлы cookie, чтобы персонализировать содержимое, хранить Ваши предпочтения и держать Вас авторизованным в системе, если Вы зарегистрировались.
    Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie.
    Скрыть объявление